fix(orchestration): trust a live own-process turn end instead of comparing clocks

Drop programActivitySince and the turn-end clock mapping: replays and SSH
host clocks made the comparison unreliable. A Claude lead counts as at rest
under a program working report when its hook turn end is a live transition
from this pane's host and launch, past the existing input and respawn floors.
This commit is contained in:
Jinwoo-H
2026-10-28 00:32:38 -04:00
parent 4c248ce001
commit 7090a2b2cf
6 changed files with 39 additions and 50 deletions
@@ -149,8 +149,6 @@ export type RuntimePtyTitleTrackerEntry = {
commandCodeDetector: { observe: (data: string) => boolean } | null
/** The program's OSC 7501 activity; while set it, not the title glyph, is the PTY's status. */
programActivity: AgentStatus | null
/** Wall clock of the last programActivity change. */
programActivitySince: number | null
/** The last observed title, so a program's activity change can re-classify it. */
lastTitle: { raw: string; normalized: string } | null
}
@@ -9,9 +9,6 @@ export abstract class OrcaRuntimeWithApplyPtyProgramActivity extends OrcaRuntime
if (!entry) {
return
}
if (entry.programActivity !== activity) {
entry.programActivitySince = Date.now()
}
entry.programActivity = activity
this.observeClaudeTerminalEvidence(ptyId, { kind: 'activity', status: activity })
const pty = this.ptysById.get(ptyId)
@@ -198,7 +198,6 @@ export abstract class OrcaRuntimeWithGetUnpersistedTrackedTitleForPty extends Or
? this.createTerminalSideEffectCommandCodeDetector(ptyId)
: null,
programActivity: null,
programActivitySince: null,
// The persisted title is already normalized, so it stands for both forms.
lastTitle: initialTitle !== null ? { raw: initialTitle, normalized: initialTitle } : null
}
@@ -190,7 +190,6 @@ export abstract class OrcaRuntimeWithResolveAuthoritativeTerminalWaitPermission
return null
}
const { handles, paneKeys, hookRows } = join
const tracker = this.ptyTitleTrackersByPtyId.get(ptyId)
return readTuiIdleHookTurn({
agent,
handles,
@@ -201,8 +200,7 @@ export abstract class OrcaRuntimeWithResolveAuthoritativeTerminalWaitPermission
launchToken: pty.launchToken,
titleObservedAtEpochMs: pty.lastOscTitleEpochMs,
hasExplicitIdleTitle: hasExplicitIdleTitle(pty),
programWorkingSince:
tracker?.programActivity === 'working' ? tracker.programActivitySince : null,
programWorking: this.ptyTitleTrackersByPtyId.get(ptyId)?.programActivity === 'working',
respawnedAt: this.agentPromptExplicitStatusFloorByPtyId.get(ptyId),
lastInputAt: this.terminalRunFacts.readLastInputAt(ptyId),
tailIncludes: (text) => retainedTailIncludes(pty.tailBuffer, pty.tailPartialLine, text),
+22 -23
View File
@@ -178,34 +178,33 @@ describe('readTuiIdleHookTurn', () => {
).toEqual({ state: 'permission', blockedReason: null })
})
it("marks a done newer than the program's working report, and joins Agent Teams to claude's row", () => {
const hookRow = row({ agentType: 'claude' })
const { receivedAt } = hookRow
const read = (programWorkingSince: number | null) =>
it("marks a live turn end under program work, and joins Agent Teams to claude's row", () => {
const transition = (kind: 'transition' | 'snapshot', launchToken = 'launch-1') =>
row({
agentType: 'claude',
launchToken,
observation: {
origin: 'hook',
authorityId: 'local',
incarnation: 1,
revision: 1,
observedAt: 1,
kind
}
})
const read = (hookRow: AgentStatusIpcPayload, programWorking = true) =>
readTuiIdleHookTurn({
...base,
agent: 'claude-agent-teams',
launchToken: 'launch-1',
hookRows: [hookRow],
programWorkingSince
programWorking
})
expect(read(receivedAt - 1000)).toEqual({ ...DONE, endedUnderProgramWork: true })
// A child's hook restamps the row, but the lead's turn still ended before the program's report.
const childChurn = row({
agentType: 'claude',
state: 'working',
mainAgent: { state: 'done', stateStartedAt: receivedAt - 2000 }
})
expect(
readTuiIdleHookTurn({
...base,
agent: 'claude',
hookRows: [childChurn],
programWorkingSince: receivedAt - 1000
})
).toEqual(DONE)
// Work the program reported after the turn end, or no working report at all, is not outlasted.
expect(read(receivedAt + 1)).toEqual(DONE)
expect(read(null)).toEqual(DONE)
expect(read(transition('transition'))).toEqual({ ...DONE, endedUnderProgramWork: true })
// A replayed done restates an old turn; another launch's done speaks for its predecessor.
expect(read(transition('snapshot'))).toEqual(DONE)
expect(read(transition('transition', 'launch-0'))).toEqual(DONE)
expect(read(transition('transition'), false)).toEqual(DONE)
})
it('takes the newest joined row', () => {
+16 -18
View File
@@ -14,8 +14,8 @@ export type TuiIdleHookTurn = {
taskWakeupPending?: true
/** When a fresh `done`'s turn has had time to paint its last frame. */
finalFrameAt?: number
/** A clear turn end newer than the program's ongoing `working` report, which then covers work
* beyond the lead's turn (e.g. live teammates) rather than the turn itself. */
/** A live turn end while the program still reports `working`, which then covers work beyond
* the lead's turn (e.g. live teammates) rather than the turn itself. */
endedUnderProgramWork?: true
}
@@ -30,13 +30,6 @@ function hookReportingAgent(agent: TuiAgent | null | undefined): TuiAgent | null
return agent === 'claude-agent-teams' ? 'claude' : agent
}
/** When the lead's own turn ended, on this runtime's clock: child events restamp `receivedAt`, and
* a remote host stamps the turn on its own clock. */
function leadTurnEndedAt(row: AgentStatusIpcPayload): number {
const ended = row.turnEndedAt ?? row.mainAgent?.stateStartedAt ?? row.stateStartedAt
return ended + row.receivedAt - (row.evidenceObservedAt ?? row.receivedAt)
}
/** Scans newest-first so the common hit (this turn's plan) returns early. */
export function retainedTailIncludes(
lines: readonly string[],
@@ -86,8 +79,8 @@ export type TuiIdleHookTurnRead = {
* the turn that input may have started (or the agent it restarted), whose first hook can still
* be in flight. */
lastInputAt?: number | null
/** When the program's current OSC 7501 `working` report began; null while it reports none. */
programWorkingSince?: number | null
/** Whether the program currently reports OSC 7501 `working`. */
programWorking?: boolean
/** Whether the pane's retained output holds `text`; read only while a fresh `done` may precede a menu. */
tailIncludes?: (text: string) => boolean
/** The existing permission arbiter, given the turn as the pane's explicit status. */
@@ -110,6 +103,11 @@ export function readTuiIdleHookTurn(read: TuiIdleHookTurnRead): TuiIdleHookTurn
return null
}
const blockedReason = read.resolveBlockedText(state, row)
// Why: an older host or launch's late hook speaks for its predecessor, not this process.
const ownHook =
row.observation?.origin === 'hook' &&
terminalHostConnectionMatches(row.connectionId, read.connectionId ?? null, read.wslDistro) &&
(!read.launchToken || row.launchToken === read.launchToken)
// Why the hook alone blocks: a question or custom modal paints no dialog text the arbiter knows,
// and these hooks report its answer. Input since may have answered it before the hook arrived.
const taskWakeupPending =
@@ -122,23 +120,23 @@ export function readTuiIdleHookTurn(read: TuiIdleHookTurnRead): TuiIdleHookTurn
typeof read.titleObservedAtEpochMs === 'number' &&
read.titleObservedAtEpochMs > (row.turnStartedAt ?? row.receivedAt)
))) &&
row.observation?.origin === 'hook' &&
row.providerSession?.key === 'session_id' &&
terminalHostConnectionMatches(row.connectionId, read.connectionId ?? null, read.wslDistro) &&
(!read.launchToken || row.launchToken === read.launchToken)
ownHook &&
row.providerSession?.key === 'session_id'
const finalFrameAt = row.receivedAt + TURN_END_FINAL_FRAME_MS
const finalFramePending =
state === 'done' &&
hookAuthority(read.agent) === 'turn-end' &&
Date.now() < finalFrameAt &&
read.tailIncludes?.(CODEX_PROPOSED_PLAN_HEADER) === true
// Why a transition: a replay or snapshot restates an old turn end and cannot prove this one ended.
const endedUnderProgramWork =
state === 'done' &&
read.programWorking === true &&
ownHook &&
row.observation?.kind === 'transition' &&
!taskWakeupPending &&
!finalFramePending &&
blockedReason === null &&
typeof read.programWorkingSince === 'number' &&
leadTurnEndedAt(row) > Math.max(read.programWorkingSince, read.lastInputAt ?? -1)
blockedReason === null
return {
state,
...(taskWakeupPending ? { taskWakeupPending: true as const } : {}),