mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 00:02:29 +00:00
* fix(agents): lift the pane retirement fence when a live PTY re-attaches (STA-4114) A detach/reattach cycle retires the pane on both sides — the main hook server's closedAgentStatusPaneKeys and the renderer's recentlyRetiredAgentStatusPaneKeys — and nothing ever cleared either one. The pane then rejected every later working/done event for the rest of its life while Pi kept running normally in the same PTY. Bind the fence to the fact it asserts: retirement claims the pane is gone, and binding a live PTY to that exact pane disproves it. Clear both tombstones at the spawn/attach chokepoint and at the daemon-backed reattach path, so recovery does not depend on the agent starting another turn — a pane re-attached mid-turn only has agent_end left to report, and one re-attached while idle emits nothing at all. Closed-tab tombstones are a separate, stronger claim and are deliberately left standing. * test(agent-hooks): re-arm the idle re-attach test against a turn-boundary fix The idle re-attach assertion posted only before_agent_start, which #14626 turns into a fence-lifting turn boundary. Under that change the test passes whether or not restorePaneAuthority runs, so it stops pinning this PR's mechanism. Assert first on agent_end — a non-turn event — so the test proves the fence was already down when the hook arrived. Verified: with restorePaneAuthority neutered AND before_agent_start added to the restart predicate, the old assertion passes and the new one fails. * fix(agents): lift a retired pane's whole fence, aliases included (STA-4114) Retirement fences the pane, its resolved owner, and every alias of it, then deletes those aliases. Restoring only the key handed to us left the rest standing — and a detached pane's process keeps posting the key it launched under (server.ts:1614), so the canonical re-attach case stayed suppressed with the fence apparently lifted. Verified against the real omp binary: the row came back under the stale launch pane instead of the detached owner. Record what each retirement fenced and replay it as a unit, rebuilding the aliases it deleted. Keys and aliases belonging to a closed tab are skipped, so the stronger claim survives and a live process is never routed back into a closed tab. The record is indexed by every fenced key and bounded at 1024 like the maps it mirrors; an evicted record degrades to the old behaviour. Also records why the renderer's restore IPC is deliberately unguarded: that map is not a mirror of main's (retirePtyAgentLaunchAuthority fences main directly on command-finished and PTY exit, and nothing pushes it back), and it is per-window and non-persisted, so gating the send on a local tombstone reintroduces this bug for exactly those panes.
55 lines
2.8 KiB
TypeScript
55 lines
2.8 KiB
TypeScript
import type {
|
|
AgentStatusClearIpcPayload,
|
|
AgentStatusIpcPayload,
|
|
MigrationUnsupportedPtyEntry
|
|
} from '../../shared/agent-status-types'
|
|
import type { AgentInterruptInferenceRequest } from '../../shared/agent-interrupt-intent'
|
|
import type { AgentQuestionAnsweredInferenceRequest } from '../../shared/agent-question-answered-intent'
|
|
import type { ComputerAwakeStatus } from '../../shared/computer-awake-mode'
|
|
|
|
export type AgentStatusApi = {
|
|
/** Listen for agent status updates forwarded from native hook receivers. */
|
|
onSet: (callback: (data: AgentStatusIpcPayload) => void) => () => void
|
|
/** Listen for main-process cleanup that evicted cached hook status. */
|
|
onClear: (callback: (data: AgentStatusClearIpcPayload) => void) => () => void
|
|
/** Return the current main-process hook cache after renderer hydration. */
|
|
getSnapshot: () => Promise<AgentStatusIpcPayload[]>
|
|
inferInterrupt: (request: AgentInterruptInferenceRequest) => Promise<boolean>
|
|
/** Guarded clear for an answered AskUserQuestion wait — the CLI emits no hook at answer time, so the renderer reports the submit keystroke. */
|
|
inferQuestionAnswered: (request: AgentQuestionAnsweredInferenceRequest) => Promise<boolean>
|
|
/** Listen for PTYs on a legacy numeric pane key that have registry-backed UUID pane proof. */
|
|
onMigrationUnsupported: (callback: (entry: MigrationUnsupportedPtyEntry) => void) => () => void
|
|
onMigrationUnsupportedClear: (callback: (data: { ptyId: string }) => void) => () => void
|
|
onLegacyWorkerTerminalRecovery: (
|
|
callback: (data: {
|
|
paneKey: string
|
|
resolution: 'adopted' | 'exited' | 'rolled_back'
|
|
ptyId?: string
|
|
}) => void
|
|
) => () => void
|
|
getMigrationUnsupportedSnapshot: () => Promise<MigrationUnsupportedPtyEntry[]>
|
|
/** Drop a paneKey from the main-process hook cache and on-disk last-status file. Fire-and-forget. */
|
|
drop: (paneKey: string) => void
|
|
/** Drop every cached hook status under one terminal tab prefix. Fire-and-forget. */
|
|
dropByTabPrefix: (tabId: string) => void
|
|
/** Permanently retire one pane's hook authority while siblings stay live. */
|
|
retirePaneAuthority: (paneKey: string) => void
|
|
/** Lift one pane's retirement fence when a live PTY re-attaches to it. Closed tabs stay retired. */
|
|
restorePaneAuthority: (paneKey: string) => void
|
|
/** Move hook authority when a live pane is detached into another tab. */
|
|
transferPaneAuthority: (args: { fromPaneKey: string; toPaneKey: string; ptyId?: string }) => void
|
|
}
|
|
|
|
export type AgentTrustApi = {
|
|
markTrusted: (args: {
|
|
preset: 'cursor' | 'copilot' | 'codex'
|
|
workspacePath: string
|
|
connectionId?: string
|
|
}) => Promise<void>
|
|
}
|
|
|
|
export type AgentAwakeApi = {
|
|
getStatus: () => Promise<ComputerAwakeStatus>
|
|
onChanged: (callback: (status: ComputerAwakeStatus) => void) => () => void
|
|
}
|