mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 00:02:05 +00:00
fix(ssh): preserve process evidence polling semantics
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import { recognizeAgentProcessFromCommandLine } from '../../shared/agent-process-recognition'
|
||||
import {
|
||||
isAgentForegroundWrapperProcess,
|
||||
isExpectedAgentProcess,
|
||||
recognizeAgentProcessFromCommandLine
|
||||
} from '../../shared/agent-process-recognition'
|
||||
import { getFirstCommandToken } from '../../shared/command-token-scanner'
|
||||
import { resolveOuterWrapperForegroundProcess } from '../../shared/foreground-wrapper-agent'
|
||||
import type { ForegroundProcessEvidence } from '../../shared/foreground-process-evidence'
|
||||
import {
|
||||
@@ -103,7 +108,19 @@ export function resolveAgentForegroundProcessesFromIndex(
|
||||
reason: 'no_controlling_tty'
|
||||
}
|
||||
}
|
||||
const candidates = (rowsByOwner.get(root.pid) ?? []).filter((row) => row.pgid === root.tpgid)
|
||||
const allCandidates = rowsByOwner.get(root.pid) ?? []
|
||||
const foregroundCandidates = allCandidates.filter((row) => row.pgid === root.tpgid)
|
||||
const fallbackProcess = request.fallbackProcess
|
||||
const wrapperFallback =
|
||||
typeof fallbackProcess === 'string' && isAgentForegroundWrapperProcess(fallbackProcess)
|
||||
const candidates = wrapperFallback
|
||||
? foregroundCandidates.filter((candidate) =>
|
||||
isExpectedAgentProcess(getFirstCommandToken(candidate.command), fallbackProcess)
|
||||
)
|
||||
: foregroundCandidates
|
||||
if (wrapperFallback && candidates.length !== 1) {
|
||||
return { available: true, processName: null }
|
||||
}
|
||||
let bestCandidate: (ProcessTableRow & { depth: number }) | null = null
|
||||
let bestName: ReturnType<typeof recognizeAgentProcessFromCommandLine> = null
|
||||
for (const candidate of candidates) {
|
||||
@@ -119,7 +136,7 @@ export function resolveAgentForegroundProcessesFromIndex(
|
||||
if (bestCandidate && bestName) {
|
||||
return {
|
||||
available: true,
|
||||
processName: resolveOuterWrapperForegroundProcess(bestName, bestCandidate, candidates)
|
||||
processName: resolveOuterWrapperForegroundProcess(bestName, bestCandidate, allCandidates)
|
||||
}
|
||||
}
|
||||
return { available: true, processName: null }
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
resolveProcessCwd,
|
||||
processHasChildren,
|
||||
getForegroundProcessName,
|
||||
getForegroundProcessNameFromProcessTable,
|
||||
isProcessAlive,
|
||||
listShellProfiles
|
||||
} from './pty-shell-utils'
|
||||
@@ -2286,13 +2285,10 @@ export class PtyHandler {
|
||||
this.reapExitedPty(managed)
|
||||
continue
|
||||
}
|
||||
// Reuse batched correlation; per-PTY tree scans recreate O(PTY × rows) work.
|
||||
const title =
|
||||
(evidenceRows
|
||||
? getForegroundProcessNameFromProcessTable(
|
||||
[...evidenceRows],
|
||||
managed.pty.pid,
|
||||
managed.pty.process || null
|
||||
)
|
||||
? (evidenceResults[entryIndex]?.processName ?? managed.pty.process ?? null)
|
||||
: await getForegroundProcessName(managed.pty.pid, managed.pty.process || null)) || 'shell'
|
||||
const foregroundProcessEvidence =
|
||||
process.platform !== 'win32'
|
||||
|
||||
+14
@@ -124,6 +124,20 @@ describe('agent completion no-evidence inspection cadence', () => {
|
||||
expect(inspectProcess).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
it('does not re-arm no-evidence scans for output from hidden panes', async () => {
|
||||
const inspectProcess = vi.fn(async () => processResult(null, false))
|
||||
const { coordinator } = createCoordinator(inspectProcess, {
|
||||
shouldPollProcessCadence: () => false,
|
||||
shouldPollNoEvidenceProcessCadence: () => false
|
||||
})
|
||||
|
||||
coordinator.startProcessTracking()
|
||||
coordinator.observeOutputActivity()
|
||||
await vi.advanceTimersByTimeAsync(60_000)
|
||||
|
||||
expect(inspectProcess).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('escalates to the hot cadence when PTY output appears mid-interval', async () => {
|
||||
const inspectProcess = vi.fn(async () => processResult(null, false))
|
||||
const { coordinator } = createCoordinator(inspectProcess)
|
||||
|
||||
@@ -202,7 +202,8 @@ export function createAgentCompletionProcessMonitor({
|
||||
state.lastForegroundAgent !== null ||
|
||||
(options.shouldPollProcessCadence?.() !== false &&
|
||||
options.shouldPollNoEvidenceProcessCadence?.() !== false) ||
|
||||
(state.lastPaneActivityAt !== null &&
|
||||
(options.shouldPollProcessCadence?.() !== false &&
|
||||
state.lastPaneActivityAt !== null &&
|
||||
Date.now() - state.lastPaneActivityAt < NO_EVIDENCE_ACTIVITY_HOT_WINDOW_MS)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -229,12 +229,11 @@ export function installTerminalKeydownFit(session: ConnectPanePtySession): void
|
||||
}),
|
||||
shouldPollProcessCadence: () =>
|
||||
isAgentTaskCompleteTrackingEnabled() && session.deps.isVisibleRef.current,
|
||||
shouldPollNoEvidenceProcessCadence: () =>
|
||||
!isRemoteExecutionHostPtyId(session.transport.getPtyId()),
|
||||
isProcessInspectionCostly: () => {
|
||||
// Why: local Windows inspection forks a powershell.exe whole-process-table
|
||||
// CIM scan per poll (~10-40x heavier than POSIX `ps`). Remote authorities
|
||||
// use the zero-idle cadence gate above instead.
|
||||
// CIM scan per poll (~10-40x heavier than POSIX `ps`). Keep the no-evidence
|
||||
// cadence enabled until inventory evidence is consumed by this renderer;
|
||||
// mixed-version relays may omit the optional field.
|
||||
if (!navigator.userAgent.includes('Windows')) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ const strictReader = createProcessTableSnapshotReader<ProcessTableRow[]>({
|
||||
})
|
||||
|
||||
/**
|
||||
* Run (or reuse a recent) `ps -axo pid=,ppid=,stat=,command=` scan and return
|
||||
* Run (or reuse a recent) `ps -axo` process-table scan and return
|
||||
* its parsed rows. Per-process singleton: the relay and local main processes
|
||||
* each dedupe their own scans and share a single parse per TTL window.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user