mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
Merge #24257 head 29e38f61cb (group proof counts any capture begun after the read)
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import type { RemoteForegroundEvidence } from '../../shared/foreground-process-evidence'
|
||||||
|
import { readLaunchedAgentForeground } from './launched-agent-foreground'
|
||||||
|
|
||||||
|
/** A live observation naming `claude` in the terminal's foreground group. */
|
||||||
|
function claudeInGroup(capturedAgeMs: number): RemoteForegroundEvidence {
|
||||||
|
return {
|
||||||
|
verdict: 'live',
|
||||||
|
processName: 'claude',
|
||||||
|
fence: {
|
||||||
|
platform: 'posix',
|
||||||
|
shellPid: 40100,
|
||||||
|
shellStartTime: 'Fri Oct 2 09:30:01 2026',
|
||||||
|
tty: 'ttys007',
|
||||||
|
foregroundPgid: 40210
|
||||||
|
},
|
||||||
|
authorityGeneration: 'gen-1',
|
||||||
|
observationEpoch: 1,
|
||||||
|
capturedAgeMs,
|
||||||
|
ptyId: 'pty-1',
|
||||||
|
ptyIncarnationId: 'inc-1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The fresh scan names the `sh` that leads the group, as it does behind a wrapper. */
|
||||||
|
function controllerAnswering(inspect: () => Promise<RemoteForegroundEvidence>) {
|
||||||
|
return {
|
||||||
|
getForegroundProcess: async () => 'sh',
|
||||||
|
confirmForegroundProcess: async () => 'sh',
|
||||||
|
confirmShellForeground: async () => false,
|
||||||
|
inspectProcess: async () => ({
|
||||||
|
foregroundProcess: 'sh',
|
||||||
|
hasChildProcesses: true,
|
||||||
|
foregroundProcessEvidence: await inspect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const POSIX_LOCAL = { remote: false, windows: false }
|
||||||
|
|
||||||
|
describe('the foreground group as proof of a launched agent', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
})
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
// On a loaded host the whole-machine capture can take seconds; it still describes the moment
|
||||||
|
// after the read was asked for.
|
||||||
|
it('counts a capture begun after the read was asked for, however long `ps` took', async () => {
|
||||||
|
const controller = controllerAnswering(async () => {
|
||||||
|
vi.advanceTimersByTime(1_800)
|
||||||
|
return claudeInGroup(1_800)
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
readLaunchedAgentForeground(controller, POSIX_LOCAL, 'pty-1', 'claude')
|
||||||
|
).resolves.toBe('agent')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('takes the fresh read over a capture reused from before the read was asked for', async () => {
|
||||||
|
const controller = controllerAnswering(async () => claudeInGroup(1_500))
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
readLaunchedAgentForeground(controller, POSIX_LOCAL, 'pty-1', 'claude')
|
||||||
|
).resolves.toBe('shell')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
isExpectedAgentProcess,
|
isExpectedAgentProcess,
|
||||||
recognizeAgentProcess
|
recognizeAgentProcess
|
||||||
} from '../../shared/agent-process-recognition'
|
} from '../../shared/agent-process-recognition'
|
||||||
|
import { PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS } from '../../shared/process-table-snapshot'
|
||||||
import { isShellProcess } from '../../shared/shell-process-detection'
|
import { isShellProcess } from '../../shared/shell-process-detection'
|
||||||
import type { TuiAgent } from '../../shared/tui-agent'
|
import type { TuiAgent } from '../../shared/tui-agent'
|
||||||
import { TUI_AGENT_CONFIG } from '../../shared/tui-agent-config'
|
import { TUI_AGENT_CONFIG } from '../../shared/tui-agent-config'
|
||||||
@@ -14,9 +15,6 @@ import type { RuntimePtyController } from './runtime-pty-controller-contract'
|
|||||||
*/
|
*/
|
||||||
export type LaunchedAgentForeground = 'agent' | 'shell' | 'unknown'
|
export type LaunchedAgentForeground = 'agent' | 'shell' | 'unknown'
|
||||||
|
|
||||||
/** Under the 1.5 s quiet a ready signal needs, so an observation this young postdates a crash. */
|
|
||||||
const FOREGROUND_GROUP_EVIDENCE_MAX_AGE_MS = 1_000
|
|
||||||
|
|
||||||
/** A login shell is reported as `-zsh`. */
|
/** A login shell is reported as `-zsh`. */
|
||||||
function isLaunchShell(processName: string): boolean {
|
function isLaunchShell(processName: string): boolean {
|
||||||
return isShellProcess(processName.replace(/^-/, ''))
|
return isShellProcess(processName.replace(/^-/, ''))
|
||||||
@@ -33,8 +31,10 @@ function isLaunchedAgent(processName: string, agent: TuiAgent): boolean {
|
|||||||
* First the host's process-group observation, which only ever proves the agent: the launched agent
|
* First the host's process-group observation, which only ever proves the agent: the launched agent
|
||||||
* among the members of the terminal's foreground group finds it behind a wrapper that did not
|
* among the members of the terminal's foreground group finds it behind a wrapper that did not
|
||||||
* `exec` it (a script, or the `/bin/sh` a tcsh or nu launch line runs), whose own name leads the
|
* `exec` it (a script, or the `/bin/sh` a tcsh or nu launch line runs), whose own name leads the
|
||||||
* group. It never proves a shell: a macOS pane's shell runs under `login`, so the group's root is
|
* group. It counts only from a capture begun after this read was asked for, less the window a
|
||||||
* not the shell, and a capture that ran out of time is no answer.
|
* shared capture is reused across, and never by how long `ps` took on a loaded host. It never
|
||||||
|
* proves a shell: a macOS pane's shell runs under `login`, so the group's root is not the shell,
|
||||||
|
* and a capture that ran out of time is no answer.
|
||||||
*
|
*
|
||||||
* Otherwise one fresh read of the terminal's foreground process: on a local POSIX host the scan
|
* Otherwise one fresh read of the terminal's foreground process: on a local POSIX host the scan
|
||||||
* behind `confirmForegroundProcess`, and on an SSH host the relay's name, which it reads from the
|
* behind `confirmForegroundProcess`, and on an SSH host the relay's name, which it reads from the
|
||||||
@@ -65,11 +65,12 @@ export async function readLaunchedAgentForeground(
|
|||||||
// An SSH pane has no such check, so on a Windows relay nothing proves a shell.
|
// An SSH pane has no such check, so on a Windows relay nothing proves a shell.
|
||||||
return (await controller.confirmShellForeground?.(ptyId)) ? 'shell' : 'unknown'
|
return (await controller.confirmShellForeground?.(ptyId)) ? 'shell' : 'unknown'
|
||||||
}
|
}
|
||||||
|
const askedAt = Date.now()
|
||||||
const evidence = (await controller.inspectProcess?.(ptyId))?.foregroundProcessEvidence
|
const evidence = (await controller.inspectProcess?.(ptyId))?.foregroundProcessEvidence
|
||||||
if (
|
if (
|
||||||
evidence?.verdict === 'live' &&
|
evidence?.verdict === 'live' &&
|
||||||
evidence.fence.platform === 'posix' &&
|
evidence.fence.platform === 'posix' &&
|
||||||
evidence.capturedAgeMs <= FOREGROUND_GROUP_EVIDENCE_MAX_AGE_MS &&
|
Date.now() - evidence.capturedAgeMs >= askedAt - PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS &&
|
||||||
evidence.processName &&
|
evidence.processName &&
|
||||||
isLaunchedAgent(evidence.processName, agent)
|
isLaunchedAgent(evidence.processName, agent)
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user