refactor(agent-status): isolate legacy status ingress behind one admission point (#20716)

* refactor(agent-status): isolate legacy status ingress

* fix(agent-hooks): move advertised-capability source onto the ingest envelope

ingestRemote() gained a third positional argument in this PR
(advertisedAgentStatusCapabilities) to satisfy a new ratchet requiring
every legacy-ingress call site to name its capability source. Both
production callers pass the same constant every time, so the argument
carries zero runtime information — but Vitest's toHaveBeenCalledWith
matches argument count exactly, so the pre-existing SSH relay
integration test (which asserts a 2-argument call) started failing
even though nothing about the actual admission decision changed.

Capabilities are a property of the producing peer/connection, not an
orthogonal call parameter, so move the field onto the envelope object
instead of adding a third positional argument: ingestRemote reads
envelope.advertisedAgentStatusCapabilities (defaulting to the
unadvertised-legacy-peer set), and both call sites stamp the constant
onto their envelope literal. Call arity stays at two arguments, so the
pre-existing evidence test needs no change.

The envelope never crosses the wire in either caller: SSH rebuilds it
field-by-field from the RPC params, and the WSL path copies (never
mutates) the wire-deserialized notification before stamping the field
on, so this is purely an internal main-process shape change.

Also strengthens the ingress ratchet test that required this: it
previously only checked that the capability constant's name appeared
somewhere in each caller's source, which a stray unused import could
satisfy. It now asserts the actual
`advertisedAgentStatusCapabilities: AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES`
key:value binding is present.
This commit is contained in:
Brennan Benson
2026-09-15 10:26:44 -07:00
committed by GitHub
parent 36ef93a64f
commit 9ab0a18e82
27 changed files with 1135 additions and 69 deletions
@@ -4,8 +4,11 @@ import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { RelayAgentHookServer } from '../../relay/agent-hook-server'
import { seedLegacyAgentStatusForTests } from '../../shared/agent-hook-listener/listener-state'
import { seedClaudeSubagentRosterFromSnapshots } from '../../shared/agent-hook-listener/providers/claude-roster-state'
import type { AgentHookEventPayload } from '../../shared/agent-hook-listener/listener-event'
import type { AgentHookRelayEnvelope } from '../../shared/agent-hook-relay'
import type { AgentSubagentSnapshot } from '../../shared/agent-status-types'
import { makePaneKey } from '../../shared/stable-pane-id'
import { AgentHookServer } from './server'
@@ -71,8 +74,10 @@ function legacyRelayCompactEnvelope(
* the turn had spawned — restored from disk, so proof of nothing. */
function seedHydratedStuckPane(server: AgentHookServer, receivedAt: number) {
const state = server._getStateForTests()
const subagents = [{ id: 'child-1', state: 'working', startedAt: 0, agentType: 'general' }]
state.lastStatusByPaneKey.set(PANE_KEY, {
const subagents: AgentSubagentSnapshot[] = [
{ id: 'child-1', state: 'working', startedAt: 0, agentType: 'general' }
]
const status = {
paneKey: PANE_KEY,
source: 'claude',
connectionId: null,
@@ -82,8 +87,9 @@ function seedHydratedStuckPane(server: AgentHookServer, receivedAt: number) {
restoredUnconfirmed: true,
receivedAt,
payload: { state: 'working', prompt: 'work before the restart', agentType: 'claude', subagents }
} as never)
seedClaudeSubagentRosterFromSnapshots(state, PANE_KEY, subagents as never)
} satisfies AgentHookEventPayload & { receivedAt: number }
seedLegacyAgentStatusForTests(state, status)
seedClaudeSubagentRosterFromSnapshots(state, PANE_KEY, subagents)
}
describe('manual Claude compact hook stream', () => {
@@ -1,6 +1,7 @@
import { createHash } from 'node:crypto'
import { afterEach, describe, expect, it } from 'vitest'
import type { AgentHookEventPayload } from '../../shared/agent-hook-listener/listener-event'
import { seedLegacyAgentStatusForTests } from '../../shared/agent-hook-listener/listener-state'
import { makePaneKey } from '../../shared/stable-pane-id'
import { AgentHookServer } from './server'
@@ -30,7 +31,7 @@ describe('AgentHookServer authority evidence', () => {
receivedAt: 100,
stateStartedAt: 100
} satisfies AgentHookEventPayload & { receivedAt: number; stateStartedAt: number }
server._getStateForTests().lastStatusByPaneKey.set(PANE_KEY, hydrated)
seedLegacyAgentStatusForTests(server._getStateForTests(), hydrated)
await server.start()
const commitments = server.getHydratedAuthorityCommitments()
@@ -195,7 +196,7 @@ describe('AgentHookServer authority evidence', () => {
receivedAt: 100,
stateStartedAt: 100
} satisfies AgentHookEventPayload & { receivedAt: number; stateStartedAt: number }
server._getStateForTests().lastStatusByPaneKey.set(PANE_KEY, hydrated)
seedLegacyAgentStatusForTests(server._getStateForTests(), hydrated)
server.registerPaneKeyAlias('tab-authority:0', PANE_KEY, 'old-pty')
await server.start()
server.ingestRemote(
@@ -5,6 +5,7 @@ import {
parseAgentStatusPayload
} from '../../shared/agent-status-types'
import { PANE } from './server.test-fixtures'
import { AGENT_STATUS_RUNS_RUNTIME_CAPABILITY } from '../../shared/agent-status-run-capability'
const { getCohortAtEmitMock, trackMock } = vi.hoisted(() => ({
getCohortAtEmitMock: vi.fn(),
@@ -644,4 +645,32 @@ describe('AgentHookServer ingestRemote', () => {
const event = listener.mock.calls[0][0] as { payload: { prompt: string } }
expect(event.payload.prompt.length).toBe(200)
})
it('never falls back to the legacy writer for a run-capable peer', () => {
const server = new AgentHookServer()
server.ingestRemote(
{
paneKey: PANE,
tabId: 'tab-1',
worktreeId: 'wt-1',
advertisedAgentStatusCapabilities: [],
payload: { state: 'working', prompt: 'unsupported peer', agentType: 'claude' }
},
'conn-1'
)
const olderPeerRow = server.getStatusSnapshot()[0]
server.ingestRemote(
{
paneKey: PANE,
tabId: 'tab-1',
worktreeId: 'wt-1',
advertisedAgentStatusCapabilities: [AGENT_STATUS_RUNS_RUNTIME_CAPABILITY],
payload: { state: 'done', prompt: 'capable peer', agentType: 'claude' }
},
'conn-1'
)
expect(server.getStatusSnapshot()).toEqual([olderPeerRow])
})
})
@@ -1,4 +1,8 @@
import { movePaneCacheState } from '../../../shared/agent-hook-listener/listener-state'
import {
admitLegacyAgentStatus,
movePaneCacheState
} from '../../../shared/agent-hook-listener/listener-state'
import { AGENT_STATUS_2A_CURRENT_PRODUCER_MODE } from '../../../shared/agent-status-legacy-adapter'
import { canRegisterPaneKeyAlias, isOpaqueRemintedPaneKey } from '../../../shared/pane-key-alias'
import { parsePaneKey } from '../../../shared/stable-pane-id'
import { PANE_KEY_ALIASES_MAX } from './server-constants'
@@ -152,11 +156,16 @@ export abstract class AgentHookServerAuthorityAliases extends AgentHookServerAut
| undefined
if (movedStatus) {
const owner = parsePaneKey(toPaneKey)
this.state.lastStatusByPaneKey.set(toPaneKey, {
...movedStatus,
paneKey: toPaneKey,
tabId: owner?.tabId
})
admitLegacyAgentStatus(
this.state,
'main-pane-alias-transfer',
{
...movedStatus,
paneKey: toPaneKey,
tabId: owner?.tabId
},
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
}
const transferredStatus = this.state.lastStatusByPaneKey.get(toPaneKey) as
| EnrichedAgentHookEventPayload
+25 -5
View File
@@ -1,4 +1,9 @@
import { paneHasStateClaims } from '../../../shared/agent-hook-listener/listener-state'
import {
admitLegacyAgentStatus,
deleteLegacyAgentStatus,
paneHasStateClaims
} from '../../../shared/agent-hook-listener/listener-state'
import { AGENT_STATUS_2A_CURRENT_PRODUCER_MODE } from '../../../shared/agent-status-legacy-adapter'
import type { AgentStatusCacheIdentity } from '../../../shared/agent-status-types'
import type { EnrichedAgentHookEventPayload } from './server-types'
import { AgentHookServerAuthorityFences } from './server-authority-fences'
@@ -35,7 +40,12 @@ export abstract class AgentHookServerCleanup extends AgentHookServerAuthorityFen
const retained =
options?.preserveResumeIdentity === false ? null : this.toRetainedProviderSessionRow(deleted)
if (retained) {
this.state.lastStatusByPaneKey.set(deleted.paneKey, retained)
admitLegacyAgentStatus(
this.state,
'main-status-cleanup',
retained,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
}
this.commitStatusRowMutation(deleted, retained)
this.scheduleStatusPersist()
@@ -73,7 +83,12 @@ export abstract class AgentHookServerCleanup extends AgentHookServerAuthorityFen
}
const retained = this.toRetainedProviderSessionRow(deleted)
if (retained) {
this.state.lastStatusByPaneKey.set(deleted.paneKey, retained)
admitLegacyAgentStatus(
this.state,
'main-status-cleanup',
retained,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
}
this.commitStatusRowMutation(deleted, retained)
evicted.push(deleted.paneKey)
@@ -126,7 +141,12 @@ export abstract class AgentHookServerCleanup extends AgentHookServerAuthorityFen
| undefined
this.clearPaneState(resolvedPaneKey, { emitStatusRowMutation: false })
if (retained) {
this.state.lastStatusByPaneKey.set(resolvedPaneKey, retained)
admitLegacyAgentStatus(
this.state,
'main-status-cleanup',
retained,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
this.scheduleStatusPersist()
this.notifyStatusChangeListeners()
}
@@ -208,7 +228,7 @@ export abstract class AgentHookServerCleanup extends AgentHookServerAuthorityFen
if (!existing) {
return null
}
this.state.lastStatusByPaneKey.delete(resolvedPaneKey)
deleteLegacyAgentStatus(this.state, resolvedPaneKey)
this.activeHookTurnCompletedAtByPaneKey.delete(resolvedPaneKey)
if (!options?.preserveAuthority) {
this.hydratedLaunchTokenHashByPaneKey.delete(resolvedPaneKey)
@@ -1,10 +1,15 @@
import { readFileSync } from 'node:fs'
import {
admitLegacyAgentStatus,
clearLegacyAgentStatuses
} from '../../../shared/agent-hook-listener/listener-state'
import {
seedClaudeLeadTurnFromPersistedStatus,
seedClaudeSubagentRosterFromSnapshots
} from '../../../shared/agent-hook-listener/providers/claude-roster-state'
import { seedCodexStateFromSnapshot } from '../../../shared/agent-hook-listener/providers/codex-state'
import { AGENT_STATUS_PERSISTED_HYDRATION_MODE } from '../../../shared/agent-status-legacy-adapter'
import { HYDRATE_MAX_AGE_MS, LAST_STATUS_FILE_VERSION } from './server-constants'
import type { LastStatusFile } from './server-types'
import {
@@ -23,7 +28,7 @@ export abstract class AgentHookServerHydration extends AgentHookServerReaping {
return
}
// Why: keep hydrate idempotent so a future re-start path can't merge prior-session state.
this.state.lastStatusByPaneKey.clear()
clearLegacyAgentStatuses(this.state)
this.hydratedLaunchTokenHashByPaneKey.clear()
this.persistedAuthorityCommitmentsByPaneKey.clear()
let raw: string
@@ -100,7 +105,12 @@ export abstract class AgentHookServerHydration extends AgentHookServerReaping {
// Why: the terminal transition may have fired while no receiver was up; restore as unconfirmed, never as live truth.
entry.restoredUnconfirmed = true
}
this.state.lastStatusByPaneKey.set(resolvedPaneKey, entry)
admitLegacyAgentStatus(
this.state,
'main-status-hydration',
entry,
AGENT_STATUS_PERSISTED_HYDRATION_MODE
)
if (entry.connectionId) {
// Why: a restart can see an earlier wall clock; seed ordering so new events stay after disk state.
const previousWatermark = this.connectionTimestampWatermarkById.get(entry.connectionId)
@@ -17,6 +17,11 @@ import {
import { launchTokenHash } from '../../../shared/agent-hook-spool'
import { parsePaneKey } from '../../../shared/stable-pane-id'
import type { AgentHookEventPayload } from '../../../shared/agent-hook-listener/listener-event'
import {
AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES,
canAdmitLegacyAgentStatus,
olderPeerAgentStatusLegacyMode
} from '../../../shared/agent-status-legacy-adapter'
import { isValidPiProviderSessionOnly } from './server-status-identity'
import { AgentHookServerIngestStructured } from './server-ingest-structured'
@@ -47,10 +52,23 @@ export abstract class AgentHookServerIngestRemote extends AgentHookServerIngestS
/** Payload fields the relay dropped to fit an oversized frame; validated below. */
shedFields?: unknown
claudeRunningNonAgentTask?: unknown
/** The producing peer's advertised run-capability set — a property of the peer/connection that built this envelope, not an orthogonal call parameter. Absent (older relay/HTTP paths) defaults to the unadvertised-legacy-peer set. */
advertisedAgentStatusCapabilities?: readonly string[]
payload: unknown
},
connectionId: string | null
): void {
if (
!canAdmitLegacyAgentStatus(
'main-status-update',
olderPeerAgentStatusLegacyMode(
envelope?.advertisedAgentStatusCapabilities ??
AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES
)
)
) {
return
}
// Why: wire crosses a trust boundary — re-check/trim so an empty connectionId can't poison caches.
if (connectionId !== null && typeof connectionId !== 'string') {
return
@@ -3,7 +3,9 @@ import {
claudeRosterHasWorkingSubagent,
claudeRosterToSnapshots
} from '../../../shared/claude-subagent-roster'
import { admitLegacyAgentStatus } from '../../../shared/agent-hook-listener/listener-state'
import { reapRestoredClaudeSubagentsForDeadPane } from '../../../shared/agent-hook-listener/providers/claude-roster-state'
import { AGENT_STATUS_PERSISTED_HYDRATION_MODE } from '../../../shared/agent-status-legacy-adapter'
import { AgentHookServerTabCleanup } from './server-tab-cleanup'
import type { EnrichedAgentHookEventPayload } from './server-types'
@@ -113,7 +115,12 @@ export abstract class AgentHookServerReaping extends AgentHookServerTabCleanup {
subagents
}
}
this.state.lastStatusByPaneKey.set(paneKey, reconciled)
admitLegacyAgentStatus(
this.state,
'main-restored-status-reaping',
reconciled,
AGENT_STATUS_PERSISTED_HYDRATION_MODE
)
this.commitStatusRowMutation(enriched, reconciled)
}
if (changedPanes > 0) {
@@ -10,6 +10,8 @@ import { INTERRUPTED_DONE_LATE_WORKING_SUPPRESSION_MS } from './server-constants
import type { EnrichedAgentHookEventPayload } from './server-types'
import type { AgentHookEventPayload } from '../../../shared/agent-hook-listener/listener-event'
import type { AgentStatusObservationOrigin } from '../../../shared/agent-status-observation'
import { AGENT_STATUS_2A_CURRENT_PRODUCER_MODE } from '../../../shared/agent-status-legacy-adapter'
import { admitLegacyAgentStatus } from '../../../shared/agent-hook-listener/listener-state'
import {
attachClaudeChildOnlyBoundary,
attachClaudePermissionToolUseId,
@@ -70,7 +72,7 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA
}
this.clearAssistantMessageRetry(enriched.paneKey)
this.runtimeObservedStatusPaneKeys.delete(enriched.paneKey)
this.state.lastStatusByPaneKey.set(enriched.paneKey, enriched)
this.writeLegacyStatusRow(enriched)
this.commitStatusRowMutation(rowBefore, enriched)
this.scheduleStatusPersist()
this.notifyStatusChangeListeners()
@@ -123,7 +125,7 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA
if (boundaryReconciledPrevious !== previous) {
previous = boundaryReconciledPrevious
if (previous) {
this.state.lastStatusByPaneKey.set(previous.paneKey, previous)
this.writeLegacyStatusRow(previous)
this.scheduleStatusPersist()
}
}
@@ -222,7 +224,7 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA
} else {
this.runtimeObservedStatusPaneKeys.add(enriched.paneKey)
}
this.state.lastStatusByPaneKey.set(enriched.paneKey, enriched)
this.writeLegacyStatusRow(enriched)
this.commitStatusRowMutation(rowBefore, enriched)
// Why skipped for structured rows: the serializer drops them, so the whole walk and stringify
// can only ever reproduce the last file — once per debounce window for a streaming chat.
@@ -264,7 +266,7 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA
}
const firstRuntimeObservation = !this.runtimeObservedStatusPaneKeys.has(refreshed.paneKey)
this.runtimeObservedStatusPaneKeys.add(refreshed.paneKey)
this.state.lastStatusByPaneKey.set(refreshed.paneKey, refreshed)
this.writeLegacyStatusRow(refreshed)
this.commitStatusRowMutation(mutationBefore ?? previous, refreshed)
this.scheduleStatusPersist()
// A dismissed row may retain only provider resume identity. Its preserved payload can still
@@ -301,4 +303,13 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA
}
}
}
private writeLegacyStatusRow(entry: EnrichedAgentHookEventPayload): void {
admitLegacyAgentStatus(
this.state,
'main-status-update',
entry,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
}
}
@@ -3,6 +3,7 @@ import { AgentHookServer } from './server'
import { AGENT_STATUS_STALE_AFTER_MS } from '../../shared/agent-status-types'
import { selectFreshExplicitAgentStatus } from '../runtime/runtime-hook-agent-row-selection'
import { wslHookRelayConnectionId } from '../../shared/wsl-hook-relay-contract'
import { seedLegacyAgentStatusForTests } from '../../shared/agent-hook-listener/listener-state'
const PANE_KEY = 'tab-handle:33333333-3333-4333-8333-333333333333'
const HANDLE = 'term_identity'
@@ -205,13 +206,15 @@ describe('the terminal handle a status row is stamped with', () => {
server.subscribeStatusRowMutations(mutations)
const payload = { state: 'working' as const, prompt: 'ship it', agentType: 'claude' as const }
ingest(server, { payload })
const row = server._getStateForTests().lastStatusByPaneKey.get(PANE_KEY) as
| { claudeLeadBoundaryChildOnly?: true }
| undefined
const row = server._getStateForTests().lastStatusByPaneKey.get(PANE_KEY)
if (!row) {
throw new Error('expected seeded status row')
}
row.claudeLeadBoundaryChildOnly = true
const childOnlyRow = {
...row,
claudeLeadBoundaryChildOnly: true
}
seedLegacyAgentStatusForTests(server._getStateForTests(), childOnlyRow)
enriched.mockClear()
mutations.mockClear()
+12 -5
View File
@@ -5,6 +5,7 @@ import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { isAgentStatusHooksEnabled } from './managed-agent-hook-controls'
import { AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES } from '../../shared/agent-status-legacy-adapter'
import { agentHookServer } from './server'
import type { ManagedHookDetectionSettings } from './managed-hook-detection-commands'
import { installRemoteManagedAgentHooks } from './remote-managed-hook-installers'
@@ -99,11 +100,17 @@ export const defaultWslHookRelayDeps: WslHookRelayManagerDeps = {
spawnRelay: spawnWslRelayProcess,
runInstall: runWslInstallProcess,
waitForSentinel: waitForWslRelaySentinel,
ingest: (envelope, connectionId) =>
agentHookServer.ingestRemote(
envelope as Parameters<typeof agentHookServer.ingestRemote>[0],
connectionId
),
// Why: the WSL relay protocol advertises no run-serving capability; stamped onto a copy so the
// wire-deserialized notification object itself is never mutated.
ingest: (envelope, connectionId) => {
const capped = {
...envelope,
advertisedAgentStatusCapabilities: AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES
}
type IngestEnvelope = Parameters<typeof agentHookServer.ingestRemote>[0]
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: envelope is the wire-deserialized notification; ingestRemote independently re-validates paneKey's type before trusting anything here.
return agentHookServer.ingestRemote(capped as IngestEnvelope, connectionId)
},
installHooks: installRemoteManagedAgentHooks,
installCodex: (runtimeHomePath, distro) =>
codexHookService.installForRuntimeHomeSerialized(runtimeHomePath, {
@@ -6,6 +6,7 @@ import {
clearPaneCacheState,
createHookListenerState,
movePaneCacheState,
seedLegacyAgentStatusForTests,
type HookListenerState
} from '../../shared/agent-hook-listener/listener-state'
import { seedClaudeSubagentRosterFromSnapshots } from '../../shared/agent-hook-listener/providers/claude-roster-state'
@@ -31,7 +32,7 @@ function deliverIfRegistered(
}
const event = normalizeHookPayload(state, 'claude', { paneKey: PANE_KEY, payload }, 'production')
if (event) {
state.lastStatusByPaneKey.set(PANE_KEY, event)
seedLegacyAgentStatusForTests(state, event)
}
return event
}
@@ -89,7 +90,7 @@ function hydrateStuckRow(
...(subagents ? { subagents } : {})
}
} as unknown as AgentHookEventPayload
state.lastStatusByPaneKey.set(PANE_KEY, hydrated)
seedLegacyAgentStatusForTests(state, hydrated)
if (subagents) {
seedClaudeSubagentRosterFromSnapshots(state, PANE_KEY, subagents)
}
+3
View File
@@ -35,6 +35,7 @@ import {
AGENT_HOOK_REQUEST_REPLAY_METHOD,
isRemoteAgentHooksEnabled
} from '../../shared/agent-hook-relay'
import { AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES } from '../../shared/agent-status-legacy-adapter'
import { _internals as openCodeInternals } from '../opencode/hook-service'
import { getPiAgentStatusExtensionSource } from '../pi/agent-status-extension-source'
import {
@@ -1606,6 +1607,8 @@ export class SshRelaySession {
typeof envelope.claudeRunningNonAgentTask === 'boolean'
? envelope.claudeRunningNonAgentTask
: undefined,
// Why: the SSH relay protocol advertises no run-serving capability.
advertisedAgentStatusCapabilities: AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES,
payload: envelope.payload
},
this.targetId
+11 -10
View File
@@ -12,6 +12,7 @@ import {
createHookListenerState,
type HookListenerState
} from '../shared/agent-hook-listener/listener-state'
import { cacheRelayLegacyAgentStatus } from '../shared/agent-status-legacy-relay-cache'
import {
getEndpointFileName,
writeEndpointFile
@@ -41,10 +42,7 @@ import {
import { buildRelayHookPtyEnv, defaultEndpointDir } from './agent-hook-endpoint-coordinates'
import { buildRelayHookEnvelope, hookBodyEnv, hookBodyVersion } from './agent-hook-envelope-build'
import { AgentHookResultRetryScheduler } from './agent-hook-result-retry-scheduler'
import {
evictCachedPanesOverCap,
selectReplayableCachedPanes
} from './agent-hook-cached-pane-status'
import { MAX_CACHED_PANES, selectReplayableCachedPanes } from './agent-hook-cached-pane-status'
export type RelayHookForward = (envelope: AgentHookRelayEnvelope) => void
@@ -209,8 +207,9 @@ export class RelayAgentHookServer {
/** Request-driven replay: re-forwards each cached paneKey payload as a fresh notification. Forwards are
* issued before the request handler returns, so the response trails all replayed notifications. */
replayCachedPayloadsForPanes(): number {
const cachedSnapshot = new Map(this.state.lastStatusByPaneKey)
const replayable = selectReplayableCachedPanes({
cachedByPaneKey: this.state.lastStatusByPaneKey,
cachedByPaneKey: cachedSnapshot,
metaByPaneKey: this.lastEnvelopeMetaByPaneKey,
isPaneSurfaceRetired: this.isPaneSurfaceRetired,
dropPane: (paneKey) => this.clearPaneState(paneKey)
@@ -325,13 +324,15 @@ export class RelayAgentHookServer {
// Why: keep PostCompact identity in the replay cache so the client can re-run ownership when
// it reconnects. Stripping it would let a cold relay replay a completion as an ordinary `done`
// row and resurrect a pane that the client had already retired.
const cachedEvent = event
// Why: delete-then-set makes Map insertion order = recency, so the cap below evicts the longest-idle pane.
this.state.lastStatusByPaneKey.delete(event.paneKey)
this.state.lastStatusByPaneKey.set(event.paneKey, cachedEvent)
if (
!cacheRelayLegacyAgentStatus(this.state, event, MAX_CACHED_PANES, (paneKey) =>
this.clearPaneState(paneKey)
)
) {
return
}
this.lastEnvelopeMetaByPaneKey.delete(event.paneKey)
this.lastEnvelopeMetaByPaneKey.set(event.paneKey, { source, env, version })
evictCachedPanesOverCap(this.state.lastStatusByPaneKey, (key) => this.clearPaneState(key))
this.forward(buildRelayHookEnvelope(event, source, env, version, options))
}
@@ -5,7 +5,8 @@ import {
clearAllListenerCaches,
clearPaneCacheState,
createHookListenerState,
movePaneCacheState
movePaneCacheState,
seedLegacyAgentStatusForTests
} from './agent-hook-listener/listener-state'
import { warnOnHookEnvOrVersionMismatch } from './agent-hook-listener/listener-limits'
import { resolveHookSource } from './agent-hook-listener/source-routing'
@@ -122,7 +123,6 @@ describe('agent hook extraction boundaries', () => {
const paneMaps = [
state.lastPromptByPaneKey,
state.lastToolByPaneKey,
state.lastStatusByPaneKey,
state.antigravityCompletedTranscriptByPaneKey,
state.claudeSubagentRosterByPaneKey,
state.claudeLeadStateByPaneKey,
@@ -137,6 +137,17 @@ describe('agent hook extraction boundaries', () => {
cache.set(scoped, 'scoped')
cache.set(sibling, 'sibling')
}
for (const [paneKey, prompt] of [
[PANE, 'exact'],
[scoped, 'scoped'],
[sibling, 'sibling']
]) {
seedLegacyAgentStatusForTests(state, {
paneKey,
connectionId: null,
payload: { state: 'working', prompt }
})
}
const paneSets = [
state.ampCompletedCacheKeys,
state.claudeUnconfirmedRestoredStatusPaneKeys,
@@ -158,6 +169,10 @@ describe('agent hook extraction boundaries', () => {
expect(cache.get(sibling)).toBe('sibling')
expect(cache.has(PANE)).toBe(false)
}
expect(state.lastStatusByPaneKey.get(MOVED_PANE)?.payload.prompt).toBe('exact')
expect(state.lastStatusByPaneKey.get(movedScoped)?.payload.prompt).toBe('scoped')
expect(state.lastStatusByPaneKey.get(sibling)?.payload.prompt).toBe('sibling')
expect(state.lastStatusByPaneKey.has(PANE)).toBe(false)
for (const set of paneSets) {
expect(set.has(MOVED_PANE)).toBe(true)
expect(set.has(movedScoped)).toBe(true)
@@ -176,7 +191,6 @@ describe('agent hook extraction boundaries', () => {
const paneMaps = [
state.lastPromptByPaneKey,
state.lastToolByPaneKey,
state.lastStatusByPaneKey,
state.antigravityCompletedTranscriptByPaneKey
]
for (const map of paneMaps) {
@@ -185,6 +199,17 @@ describe('agent hook extraction boundaries', () => {
cache.set(scoped, 'scoped')
cache.set(sibling, 'sibling')
}
for (const [paneKey, prompt] of [
[PANE, 'exact'],
[scoped, 'scoped'],
[sibling, 'sibling']
]) {
seedLegacyAgentStatusForTests(state, {
paneKey,
connectionId: null,
payload: { state: 'working', prompt }
})
}
state.ampCompletedCacheKeys.add(PANE)
state.ampCompletedCacheKeys.add(scoped)
state.ampCompletedCacheKeys.add(sibling)
@@ -200,6 +225,9 @@ describe('agent hook extraction boundaries', () => {
expect(cache.has(scoped)).toBe(false)
expect(cache.get(sibling)).toBe('sibling')
}
expect(state.lastStatusByPaneKey.has(PANE)).toBe(false)
expect(state.lastStatusByPaneKey.has(scoped)).toBe(false)
expect(state.lastStatusByPaneKey.get(sibling)?.payload.prompt).toBe('sibling')
expect(state.ampCompletedCacheKeys.has(scoped)).toBe(false)
expect(state.ampCompletedCacheKeys.has(sibling)).toBe(true)
expect(state.claudeLeadStateByPaneKey.has(PANE)).toBe(false)
@@ -211,7 +239,11 @@ describe('agent hook extraction boundaries', () => {
const state = createHookListenerState()
state.lastPromptByPaneKey.set(PANE, 'old prompt')
state.lastToolByPaneKey.set(PANE, { toolName: 'old tool' })
state.lastStatusByPaneKey.set(PANE, {} as never)
seedLegacyAgentStatusForTests(state, {
paneKey: PANE,
connectionId: null,
payload: { state: 'working', prompt: 'old status' }
})
const event = normalizeHookPayload(
state,
@@ -1,5 +1,8 @@
import { normalizeHookPayload } from './agent-hook-listener'
import type { HookListenerState } from './agent-hook-listener/listener-state'
import {
seedLegacyAgentStatusForTests,
type HookListenerState
} from './agent-hook-listener/listener-state'
import { makePaneKey } from './stable-pane-id'
const LEAF_ID = '11111111-1111-4111-8111-111111111111'
@@ -14,7 +17,7 @@ export function normalizeAndAccept(
): ReturnType<typeof normalizeHookPayload> {
const event = normalizeHookPayload(state, source, { paneKey: PANE_KEY, payload }, 'production')
if (event) {
state.lastStatusByPaneKey.set(PANE_KEY, event)
seedLegacyAgentStatusForTests(state, event)
}
return event
}
@@ -1,4 +1,12 @@
import type { AgentStatusState } from '../agent-status-types'
import {
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
createAgentStatusLegacyAdapter,
type AgentStatusLegacyAdapter,
type AgentStatusLegacyAdapterOptions,
type AgentStatusLegacyAdmissionMode
} from '../agent-status-legacy-adapter'
import type { AgentStatusLegacyIngressCaller } from '../agent-status-legacy-ingress-manifest'
import type { ClaudeSubagentRoster } from '../claude-subagent-roster'
import type { CodexSubagentRoster } from '../codex-subagent-roster'
import type { CodexSubagentTranscriptState } from '../codex-subagent-transcript'
@@ -10,7 +18,8 @@ export type HookListenerState = {
warnedEnvs: Set<string>
lastPromptByPaneKey: Map<string, string>
lastToolByPaneKey: Map<string, ToolSnapshot>
lastStatusByPaneKey: Map<string, AgentHookEventPayload>
/** Read-only compatibility view. All writes pass through the isolated legacy adapter. */
lastStatusByPaneKey: ReadonlyMap<string, AgentHookEventPayload>
antigravityCompletedTranscriptByPaneKey: Map<string, string>
ampCompletedCacheKeys: Set<string>
/** Live subagents/teammates per Claude pane; survives turn boundaries since background children outlive the lead turn. */
@@ -62,13 +71,26 @@ export type CodexLeadTurnState = {
model?: string
}
export function createHookListenerState(): HookListenerState {
return {
const legacyStatusAdapterByState = new WeakMap<HookListenerState, AgentStatusLegacyAdapter>()
function legacyStatusAdapter(state: HookListenerState): AgentStatusLegacyAdapter {
const adapter = legacyStatusAdapterByState.get(state)
if (!adapter) {
throw new Error('Hook listener state has no legacy agent-status adapter')
}
return adapter
}
export function createHookListenerState(
options: AgentStatusLegacyAdapterOptions = {}
): HookListenerState {
const adapter = createAgentStatusLegacyAdapter(options)
const state: HookListenerState = {
warnedVersions: new Set(),
warnedEnvs: new Set(),
lastPromptByPaneKey: new Map(),
lastToolByPaneKey: new Map(),
lastStatusByPaneKey: new Map(),
lastStatusByPaneKey: adapter.view,
antigravityCompletedTranscriptByPaneKey: new Map(),
ampCompletedCacheKeys: new Set(),
claudeSubagentRosterByPaneKey: new Map(),
@@ -83,12 +105,69 @@ export function createHookListenerState(): HookListenerState {
codexLeadStateByPaneKey: new Map(),
grokActiveTurnByPaneKey: new Map()
}
legacyStatusAdapterByState.set(state, adapter)
return state
}
export function admitLegacyAgentStatus(
state: HookListenerState,
caller: AgentStatusLegacyIngressCaller,
entry: AgentHookEventPayload,
mode: AgentStatusLegacyAdmissionMode,
options?: { moveToEnd?: boolean }
): boolean {
return legacyStatusAdapter(state).admit(caller, mode, entry, options)
}
export function deleteLegacyAgentStatus(state: HookListenerState, paneKey: string): boolean {
return legacyStatusAdapter(state).delete(paneKey)
}
export function clearLegacyAgentStatuses(state: HookListenerState): void {
legacyStatusAdapter(state).clear()
}
export function moveLegacyAgentStatuses(
state: HookListenerState,
fromPaneKey: string,
toPaneKey: string
): void {
legacyStatusAdapter(state).move(fromPaneKey, toPaneKey)
}
export function getLegacyStatusListingOrder(
state: HookListenerState,
paneKey: string
): number | undefined {
return legacyStatusAdapter(state).listingOrder(paneKey)
}
/** Test harnesses seed the same compatibility region without exposing a mutable Map. */
export function seedLegacyAgentStatusForTests(
state: HookListenerState,
entry: AgentHookEventPayload
): void {
if (
!admitLegacyAgentStatus(
state,
'main-status-update',
entry,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
) {
throw new Error('Test legacy agent-status seed was refused')
}
}
export function clearPaneCacheState(state: HookListenerState, paneKey: string): void {
deletePaneScopedCacheEntry(state.lastPromptByPaneKey, paneKey)
deletePaneScopedCacheEntry(state.lastToolByPaneKey, paneKey)
deletePaneScopedCacheEntry(state.lastStatusByPaneKey, paneKey)
deleteLegacyAgentStatus(state, paneKey)
for (const key of state.lastStatusByPaneKey.keys()) {
if (key.startsWith(`${paneKey}\0`)) {
deleteLegacyAgentStatus(state, key)
}
}
deletePaneScopedCacheEntry(state.antigravityCompletedTranscriptByPaneKey, paneKey)
deletePaneScopedSetEntry(state.ampCompletedCacheKeys, paneKey)
deletePaneScopedCacheEntry(state.claudeConsumedCompactPromptIdByPaneKey, paneKey)
@@ -162,7 +241,7 @@ export function movePaneCacheState(
}
movePaneScopedMapEntries(state.lastPromptByPaneKey, fromPaneKey, toPaneKey)
movePaneScopedMapEntries(state.lastToolByPaneKey, fromPaneKey, toPaneKey)
movePaneScopedMapEntries(state.lastStatusByPaneKey, fromPaneKey, toPaneKey)
moveLegacyAgentStatuses(state, fromPaneKey, toPaneKey)
movePaneScopedMapEntries(state.antigravityCompletedTranscriptByPaneKey, fromPaneKey, toPaneKey)
movePaneScopedSetEntries(state.ampCompletedCacheKeys, fromPaneKey, toPaneKey)
movePaneScopedMapEntries(state.claudeConsumedCompactPromptIdByPaneKey, fromPaneKey, toPaneKey)
@@ -209,7 +288,7 @@ export function deletePaneScopedSetEntry(set: Set<string>, paneKey: string): voi
export function clearAllListenerCaches(state: HookListenerState): void {
state.lastPromptByPaneKey.clear()
state.lastToolByPaneKey.clear()
state.lastStatusByPaneKey.clear()
clearLegacyAgentStatuses(state)
state.antigravityCompletedTranscriptByPaneKey.clear()
state.ampCompletedCacheKeys.clear()
state.claudeConsumedCompactPromptIdByPaneKey.clear()
+8 -8
View File
@@ -40,15 +40,15 @@ describe('bounded agent hook status cache', () => {
maxPanes: 3,
now
})
upsertBoundedAgentHookStatus(listener, status('stale', 'working', now), {
maxPanes: 3,
now
})
upsertBoundedAgentHookStatus(
listener,
status('stale', 'working', now - AGENT_STATUS_STALE_AFTER_MS - 1),
{
maxPanes: 3,
now
}
)
upsertBoundedAgentHookStatus(listener, status('done', 'done', now), { maxPanes: 3, now })
const stale = listener.lastStatusByPaneKey.get('stale') as AgentHookEventPayload & {
receivedAt: number
}
stale.receivedAt = now - AGENT_STATUS_STALE_AFTER_MS - 1
listener.lastPromptByPaneKey.set('stale', 'cached prompt')
listener.lastToolByPaneKey.set('stale\0tool', {} as never)
+17 -3
View File
@@ -1,5 +1,10 @@
import { clearPaneCacheState, type HookListenerState } from './agent-hook-listener/listener-state'
import {
admitLegacyAgentStatus,
clearPaneCacheState,
type HookListenerState
} from './agent-hook-listener/listener-state'
import type { AgentHookEventPayload } from './agent-hook-listener/listener-event'
import { AGENT_STATUS_2A_CURRENT_PRODUCER_MODE } from './agent-status-legacy-adapter'
import { AGENT_STATUS_STALE_AFTER_MS } from './agent-status-types'
export const MAX_AGENT_HOOK_STATUS_CACHE_PANES = 500
@@ -19,8 +24,17 @@ export function upsertBoundedAgentHookStatus(
throw new RangeError('Agent hook status cache limit must be a positive safe integer')
}
state.lastStatusByPaneKey.delete(entry.paneKey)
state.lastStatusByPaneKey.set(entry.paneKey, entry)
if (
!admitLegacyAgentStatus(
state,
'shared-bounded-status-cache',
entry,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
{ moveToEnd: true }
)
) {
return []
}
const evicted: AgentHookStatusCacheEviction[] = []
const now = options.now ?? Date.now()
while (state.lastStatusByPaneKey.size > maxPanes) {
@@ -0,0 +1,164 @@
import { describe, expect, it } from 'vitest'
import type { AgentHookEventPayload } from './agent-hook-listener/listener-event'
import {
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
AGENT_STATUS_PERSISTED_HYDRATION_MODE,
createAgentStatusLegacyAdapter,
olderPeerAgentStatusLegacyMode
} from './agent-status-legacy-adapter'
import { AGENT_STATUS_RUNS_RUNTIME_CAPABILITY } from './agent-status-run-capability'
function status(paneKey: string, prompt = 'work'): AgentHookEventPayload {
return {
paneKey,
connectionId: null,
payload: { state: 'working', prompt, agentType: 'claude' }
}
}
describe('legacy agent-status adapter', () => {
it('keeps incomplete PTY scope exclusively in the legacy region', () => {
const adapter = createAgentStatusLegacyAdapter()
const entry = status('pane-with-no-workspace-or-host-scope')
expect(adapter.admit('main-status-update', AGENT_STATUS_2A_CURRENT_PRODUCER_MODE, entry)).toBe(
true
)
expect(adapter.view.get(entry.paneKey)).toBe(entry)
})
it('refuses keys already owned by the canonical projection', () => {
const canonicalPaneKeys = new Set<string>()
const adapter = createAgentStatusLegacyAdapter({
isCanonicalPaneKey: (paneKey) => canonicalPaneKeys.has(paneKey)
})
const prior = status('canonical-pane', 'legacy before canonical publication')
expect(adapter.admit('main-status-update', AGENT_STATUS_2A_CURRENT_PRODUCER_MODE, prior)).toBe(
true
)
adapter.delete(prior.paneKey)
canonicalPaneKeys.add(prior.paneKey)
expect(
adapter.admit(
'main-status-update',
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
status(prior.paneKey, 'late legacy write')
)
).toBe(false)
expect(adapter.view.has(prior.paneKey)).toBe(false)
})
it('does not move an existing legacy row onto a canonical projection key', () => {
const canonicalPaneKeys = new Set<string>()
const adapter = createAgentStatusLegacyAdapter({
isCanonicalPaneKey: (paneKey) => canonicalPaneKeys.has(paneKey)
})
adapter.admit(
'main-status-update',
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
status('legacy-pane')
)
canonicalPaneKeys.add('canonical-pane')
adapter.move('legacy-pane', 'canonical-pane')
expect(adapter.view.has('legacy-pane')).toBe(false)
expect(adapter.view.has('canonical-pane')).toBe(false)
})
it('admits an unsupported older peer but never falls back for a capable peer', () => {
const adapter = createAgentStatusLegacyAdapter()
const older = status('same-pane', 'older peer')
expect(adapter.admit('main-status-update', olderPeerAgentStatusLegacyMode([]), older)).toBe(
true
)
const capable = status('same-pane', 'capable peer must use canonical serving')
expect(
adapter.admit(
'main-status-update',
olderPeerAgentStatusLegacyMode([AGENT_STATUS_RUNS_RUNTIME_CAPABILITY]),
capable
)
).toBe(false)
expect(adapter.view.get('same-pane')).toBe(older)
})
it('fails closed on malformed older-peer capability evidence', () => {
const adapter = createAgentStatusLegacyAdapter()
expect(
adapter.admit(
'main-status-update',
olderPeerAgentStatusLegacyMode(Array.from({ length: 257 }, () => 'unknown')),
status('malformed-peer')
)
).toBe(false)
expect(adapter.view.size).toBe(0)
})
it('restricts hydration admission to the named quarantine callers', () => {
const adapter = createAgentStatusLegacyAdapter()
expect(
adapter.admit(
'main-status-hydration',
AGENT_STATUS_PERSISTED_HYDRATION_MODE,
status('hydrated')
)
).toBe(true)
expect(
adapter.admit(
'main-status-update',
AGENT_STATUS_PERSISTED_HYDRATION_MODE,
status('wrong-caller')
)
).toBe(false)
})
it('exposes Map reads without writable methods or mutable values', () => {
const adapter = createAgentStatusLegacyAdapter()
const entry = status('immutable')
adapter.admit('main-status-update', AGENT_STATUS_2A_CURRENT_PRODUCER_MODE, entry)
expect(adapter.view.get('immutable')).toBe(entry)
expect(adapter.view.has('immutable')).toBe(true)
expect(Array.from(adapter.view.keys())).toEqual(['immutable'])
expect('set' in adapter.view).toBe(false)
expect('delete' in adapter.view).toBe(false)
expect(Object.isFrozen(adapter.view)).toBe(true)
expect(Object.isFrozen(entry)).toBe(true)
expect(Object.isFrozen(entry.payload)).toBe(true)
expect(() => {
entry.payload.prompt = 'mutated outside the adapter'
}).toThrow()
expect(adapter.view.get('immutable')?.payload.prompt).toBe('work')
})
it('assigns listing order once per live row and preserves it across refresh and move', () => {
let nextOrder = 40
const adapter = createAgentStatusLegacyAdapter({ nextListingOrder: () => nextOrder++ })
adapter.admit('main-status-update', AGENT_STATUS_2A_CURRENT_PRODUCER_MODE, status('pane'))
expect(adapter.listingOrder('pane')).toBe(40)
adapter.admit(
'main-status-update',
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
status('pane', 'refresh'),
{ moveToEnd: true }
)
expect(adapter.listingOrder('pane')).toBe(40)
adapter.move('pane', 'moved-pane')
expect(adapter.listingOrder('moved-pane')).toBe(40)
adapter.delete('moved-pane')
adapter.admit(
'main-status-update',
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
status('moved-pane', 'new lifecycle')
)
expect(adapter.listingOrder('moved-pane')).toBe(41)
})
})
+197
View File
@@ -0,0 +1,197 @@
import type { AgentHookEventPayload } from './agent-hook-listener/listener-event'
import {
deserializeAgentStatusCapabilities,
hasAgentStatusRunCapability
} from './agent-status-run-capability'
import {
findAgentStatusLegacyIngressManifestEntry,
type AgentStatusLegacyIngressCaller
} from './agent-status-legacy-ingress-manifest'
import {
AGENT_STATUS_2A_SERVING_READINESS,
isAgentStatusRunServingAdvertised,
type AgentStatusServingReadiness
} from './agent-status-serving-readiness'
export type AgentStatusLegacyAdmissionMode =
| Readonly<{
kind: 'current-producer'
servingReadiness: AgentStatusServingReadiness
}>
| Readonly<{
kind: 'older-peer'
advertisedCapabilities: readonly string[]
}>
| Readonly<{ kind: 'persisted-hydration' }>
export const AGENT_STATUS_2A_CURRENT_PRODUCER_MODE: AgentStatusLegacyAdmissionMode = Object.freeze({
kind: 'current-producer',
servingReadiness: AGENT_STATUS_2A_SERVING_READINESS
})
export const AGENT_STATUS_PERSISTED_HYDRATION_MODE: AgentStatusLegacyAdmissionMode = Object.freeze({
kind: 'persisted-hydration'
})
/** Existing relay protocols advertise no run-serving capability. Production ingress call sites stamp this onto the envelope explicitly. */
export const AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES: readonly string[] = Object.freeze(
[]
)
export function olderPeerAgentStatusLegacyMode(
advertisedCapabilities: readonly string[]
): AgentStatusLegacyAdmissionMode {
return Object.freeze({
kind: 'older-peer',
advertisedCapabilities: Object.freeze([...advertisedCapabilities])
})
}
export function canAdmitLegacyAgentStatus(
caller: AgentStatusLegacyIngressCaller,
mode: AgentStatusLegacyAdmissionMode
): boolean {
const manifestEntry = findAgentStatusLegacyIngressManifestEntry(caller)
if (!manifestEntry || !manifestEntry.allowedModes.includes(mode.kind)) {
return false
}
if (mode.kind === 'older-peer') {
return (
deserializeAgentStatusCapabilities(mode.advertisedCapabilities) !== null &&
!hasAgentStatusRunCapability(mode.advertisedCapabilities)
)
}
if (mode.kind === 'persisted-hydration') {
return true
}
return !isAgentStatusRunServingAdvertised(mode.servingReadiness)
}
export type AgentStatusLegacyAdapter = {
readonly view: ReadonlyMap<string, AgentHookEventPayload>
admit(
caller: AgentStatusLegacyIngressCaller,
mode: AgentStatusLegacyAdmissionMode,
entry: AgentHookEventPayload,
options?: { moveToEnd?: boolean }
): boolean
delete(paneKey: string): boolean
clear(): void
move(fromPaneKey: string, toPaneKey: string): void
listingOrder(paneKey: string): number | undefined
}
export type AgentStatusLegacyAdapterOptions = {
nextListingOrder?: () => number
isCanonicalPaneKey?: (paneKey: string) => boolean
}
function freezeRecursively(value: unknown, seen: WeakSet<object>): void {
if (typeof value !== 'object' || value === null || seen.has(value)) {
return
}
seen.add(value)
for (const key of Reflect.ownKeys(value)) {
freezeRecursively(Reflect.get(value, key), seen)
}
Object.freeze(value)
}
function freezeStatusEntry(entry: AgentHookEventPayload): void {
freezeRecursively(entry, new WeakSet())
}
function createReadonlyView(
entries: Map<string, AgentHookEventPayload>
): ReadonlyMap<string, AgentHookEventPayload> {
let view: ReadonlyMap<string, AgentHookEventPayload>
view = Object.freeze({
get size() {
return entries.size
},
get: (key: string) => entries.get(key),
has: (key: string) => entries.has(key),
entries: () => entries.entries(),
keys: () => entries.keys(),
values: () => entries.values(),
forEach: (
callback: (
value: AgentHookEventPayload,
key: string,
map: ReadonlyMap<string, AgentHookEventPayload>
) => void,
thisArg?: unknown
) => entries.forEach((value, key) => callback.call(thisArg, value, key, view)),
[Symbol.iterator]: () => entries[Symbol.iterator](),
[Symbol.toStringTag]: 'AgentStatusLegacyReadonlyMap'
})
return view
}
export function createAgentStatusLegacyAdapter(
options: AgentStatusLegacyAdapterOptions = {}
): AgentStatusLegacyAdapter {
const entries = new Map<string, AgentHookEventPayload>()
const listingOrderByPaneKey = new Map<string, number>()
let nextLocalListingOrder = 0
const nextListingOrder = options.nextListingOrder ?? (() => nextLocalListingOrder++)
const isCanonicalPaneKey = options.isCanonicalPaneKey ?? (() => false)
const view = createReadonlyView(entries)
return {
view,
admit: (caller, mode, entry, admitOptions = {}) => {
if (isCanonicalPaneKey(entry.paneKey) || !canAdmitLegacyAgentStatus(caller, mode)) {
return false
}
if (!listingOrderByPaneKey.has(entry.paneKey)) {
const order = nextListingOrder()
if (!Number.isSafeInteger(order) || order < 0) {
throw new RangeError(
'Legacy agent-status listing order must be a non-negative safe integer'
)
}
listingOrderByPaneKey.set(entry.paneKey, order)
}
freezeStatusEntry(entry)
if (admitOptions.moveToEnd) {
entries.delete(entry.paneKey)
}
entries.set(entry.paneKey, entry)
return true
},
delete: (paneKey) => {
listingOrderByPaneKey.delete(paneKey)
return entries.delete(paneKey)
},
clear: () => {
entries.clear()
listingOrderByPaneKey.clear()
},
move: (fromPaneKey, toPaneKey) => {
if (fromPaneKey === toPaneKey) {
return
}
for (const [key, value] of Array.from(entries)) {
if (key !== fromPaneKey && !key.startsWith(`${fromPaneKey}\0`)) {
continue
}
const movedKey = `${toPaneKey}${key.slice(fromPaneKey.length)}`
const priorTargetOrder = listingOrderByPaneKey.get(movedKey)
const sourceOrder = listingOrderByPaneKey.get(key)
entries.delete(key)
listingOrderByPaneKey.delete(key)
if (isCanonicalPaneKey(movedKey)) {
continue
}
entries.set(movedKey, value)
if (priorTargetOrder !== undefined) {
listingOrderByPaneKey.set(movedKey, priorTargetOrder)
} else if (sourceOrder !== undefined) {
listingOrderByPaneKey.set(movedKey, sourceOrder)
}
}
},
listingOrder: (paneKey) => listingOrderByPaneKey.get(paneKey)
}
}
@@ -0,0 +1,115 @@
export type AgentStatusLegacyIngressDestination = '2B' | '6'
export type AgentStatusLegacyIngressCaller =
| 'main-status-update'
| 'main-status-cleanup'
| 'main-pane-alias-transfer'
| 'main-status-hydration'
| 'main-restored-status-reaping'
| 'relay-status-cache'
| 'shared-bounded-status-cache'
export type AgentStatusLegacyIngressManifestEntry = {
caller: AgentStatusLegacyIngressCaller
sourcePath: string
reason: string
owner: 'main-agent-hooks' | 'relay-agent-hooks' | 'shared-hook-listener'
destination: AgentStatusLegacyIngressDestination
gate: string
allowedModes: readonly AgentStatusLegacyIngressModeKind[]
}
export type AgentStatusLegacyIngressModeKind =
| 'current-producer'
| 'older-peer'
| 'persisted-hydration'
function entry(
value: AgentStatusLegacyIngressManifestEntry
): Readonly<AgentStatusLegacyIngressManifestEntry> {
return Object.freeze({ ...value, allowedModes: Object.freeze([...value.allowedModes]) })
}
/** Every writable legacy ingress. Entries may only disappear as their destination gate lands. */
export const AGENT_STATUS_LEGACY_INGRESS_MANIFEST = Object.freeze([
entry({
caller: 'main-status-update',
sourcePath: 'src/main/agent-hooks/server/server-status-update.ts',
reason: 'Hook, OSC, and unsupported-peer observations still use pane ownership in 2A.',
owner: 'main-agent-hooks',
destination: '2B',
gate: 'Trusted PTY scope plus owner-atomic producer handover',
allowedModes: ['current-producer', 'older-peer']
}),
entry({
caller: 'main-status-cleanup',
sourcePath: 'src/main/agent-hooks/server/server-cleanup.ts',
reason: 'Legacy provider-session remnants preserve resume identity during pane cleanup.',
owner: 'main-agent-hooks',
destination: '2B',
gate: 'Canonical run retirement and resume-identity mutation',
allowedModes: ['current-producer']
}),
entry({
caller: 'main-pane-alias-transfer',
sourcePath: 'src/main/agent-hooks/server/server-authority-aliases.ts',
reason: 'A verified pane remint transfers the existing legacy row without minting a run.',
owner: 'main-agent-hooks',
destination: '2B',
gate: 'Scope-preserving canonical pane attachment relocation',
allowedModes: ['current-producer']
}),
entry({
caller: 'main-status-hydration',
sourcePath: 'src/main/agent-hooks/server/server-hydration.ts',
reason: 'Persisted pane evidence is quarantined until host evidence confirms ownership.',
owner: 'main-agent-hooks',
destination: '6',
gate: 'Trusted adoption or bounded unconfirmed-observation retention expiry',
allowedModes: ['persisted-hydration']
}),
entry({
caller: 'main-restored-status-reaping',
sourcePath: 'src/main/agent-hooks/server/server-reaping.ts',
reason: 'Process-probe reconciliation can update a quarantined hydrated pane row.',
owner: 'main-agent-hooks',
destination: '6',
gate: 'Canonical hydration adoption fixtures and compatibility-branch ablation',
allowedModes: ['persisted-hydration']
}),
entry({
caller: 'relay-status-cache',
sourcePath: 'src/shared/agent-status-legacy-relay-cache.ts',
reason: 'The relay retains receiver-fenced replay state until trusted host binding exists.',
owner: 'relay-agent-hooks',
destination: '2B',
gate: 'Relay trusted scope binding plus owner-atomic producer handover',
allowedModes: ['current-producer']
}),
entry({
caller: 'shared-bounded-status-cache',
sourcePath: 'src/shared/agent-hook-status-cache.ts',
reason: 'The bounded legacy cache seam remains available to hook listener owners in 2A.',
owner: 'shared-hook-listener',
destination: '2B',
gate: 'All hook listener owners use the canonical mutation core',
allowedModes: ['current-producer']
})
])
const LEGACY_INGRESS_BY_CALLER = new Map(
AGENT_STATUS_LEGACY_INGRESS_MANIFEST.map((candidate) => [candidate.caller, candidate])
)
const CURRENT_PRODUCER_LEGACY_INGRESS_MANIFEST = Object.freeze(
AGENT_STATUS_LEGACY_INGRESS_MANIFEST.filter((candidate) => candidate.destination === '2B')
)
export function findAgentStatusLegacyIngressManifestEntry(
caller: AgentStatusLegacyIngressCaller
): Readonly<AgentStatusLegacyIngressManifestEntry> | undefined {
return LEGACY_INGRESS_BY_CALLER.get(caller)
}
export function currentProducerAgentStatusLegacyIngressManifest(): readonly Readonly<AgentStatusLegacyIngressManifestEntry>[] {
return CURRENT_PRODUCER_LEGACY_INGRESS_MANIFEST
}
@@ -0,0 +1,142 @@
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
AGENT_STATUS_LEGACY_INGRESS_MANIFEST,
currentProducerAgentStatusLegacyIngressManifest
} from './agent-status-legacy-ingress-manifest'
import { findAgentStatusLegacyMutationBypasses } from './agent-status-legacy-source-scan'
import { scanSourceTree, stripComments } from './source-scan/source-tree-scan'
const SOURCE_ROOT = resolve(__dirname, '..')
const ADMISSION_CALL = /admitLegacyAgentStatus\(\s*(?:this\.)?state\s*,\s*['"]([^'"]+)['"]/gs
describe('legacy agent-status ingress ratchet', () => {
const productionFiles = scanSourceTree(SOURCE_ROOT)
it('keeps every production admission call in the explicit manifest', () => {
const actual = new Set<string>()
let parsedCalls = 0
let rawCalls = 0
for (const file of productionFiles) {
if (file.relativePath === 'shared/agent-hook-listener/listener-state.ts') {
continue
}
const source = stripComments(file.source)
rawCalls += source.match(/\badmitLegacyAgentStatus\s*\(/g)?.length ?? 0
for (const match of source.matchAll(ADMISSION_CALL)) {
parsedCalls += 1
actual.add(`src/${file.relativePath}:${match[1]}`)
}
}
expect(parsedCalls, 'Every admission must pass a literal caller id directly.').toBe(rawCalls)
const declared = new Set(
AGENT_STATUS_LEGACY_INGRESS_MANIFEST.map((entry) => `${entry.sourcePath}:${entry.caller}`)
)
expect([...actual].filter((call) => !declared.has(call))).toEqual([])
expect([...declared].filter((call) => !actual.has(call))).toEqual([])
const indirectAdmissions = productionFiles
.filter((file) =>
/\badmitLegacyAgentStatus\s+as\s+|=\s*admitLegacyAgentStatus\b|\(\s*admitLegacyAgentStatus\s*[,)]/.test(
stripComments(file.source)
)
)
.map((file) => file.relativePath)
expect(indirectAdmissions).toEqual([])
})
it('allows no mutable Map path around the adapter', () => {
const bypasses = productionFiles
.filter((file) => file.source.includes('lastStatusByPaneKey'))
.flatMap((file) =>
findAgentStatusLegacyMutationBypasses(file.source).map(
(bypass) => `${file.relativePath}: ${bypass.kind}: ${bypass.detail}`
)
)
expect(bypasses).toEqual([])
})
it('detects direct, aliased, cast, and passed-map mutation bypasses', () => {
const planted = findAgentStatusLegacyMutationBypasses(`
state.lastStatusByPaneKey.set('pane', row)
const alias = state.lastStatusByPaneKey
alias.delete('pane')
const { lastStatusByPaneKey } = state
lastStatusByPaneKey.clear()
;(state.lastStatusByPaneKey as unknown as Map<string, Row>).clear()
state['lastStatusByPaneKey'].set('pane', row)
mutateStatusMap(state.lastStatusByPaneKey)
const { lastStatusByPaneKey: passed } = state
mutateStatusMap(passed)
`)
expect(new Set(planted.map((bypass) => bypass.kind))).toEqual(
new Set(['direct-mutation', 'alias-mutation', 'map-cast', 'passed-map'])
)
})
it('keeps the manifest immutable, descriptive, and partitioned by its exit gate', () => {
expect(Object.isFrozen(AGENT_STATUS_LEGACY_INGRESS_MANIFEST)).toBe(true)
const currentProducerManifest = currentProducerAgentStatusLegacyIngressManifest()
expect(currentProducerManifest.length).toBeGreaterThan(0)
expect(currentProducerAgentStatusLegacyIngressManifest()).toBe(currentProducerManifest)
expect(Object.isFrozen(currentProducerManifest)).toBe(true)
expect(new Set(AGENT_STATUS_LEGACY_INGRESS_MANIFEST.map((entry) => entry.caller)).size).toBe(
AGENT_STATUS_LEGACY_INGRESS_MANIFEST.length
)
for (const entry of AGENT_STATUS_LEGACY_INGRESS_MANIFEST) {
expect(Object.isFrozen(entry)).toBe(true)
expect(Object.isFrozen(entry.allowedModes)).toBe(true)
expect(entry.reason.length).toBeGreaterThan(20)
expect(entry.gate.length).toBeGreaterThan(20)
expect(['2B', '6']).toContain(entry.destination)
}
})
it('keeps adapter construction and test seeding behind listener-state', () => {
const forbidden = productionFiles
.filter(
(file) =>
file.relativePath !== 'shared/agent-hook-listener/listener-state.ts' &&
file.relativePath !== 'shared/agent-status-legacy-adapter.ts'
)
.filter(
(file) =>
stripComments(file.source).includes('createAgentStatusLegacyAdapter') ||
stripComments(file.source).includes('seedLegacyAgentStatusForTests')
)
.map((file) => file.relativePath)
expect(forbidden).toEqual([])
})
it('requires every production remote ingress to name the unsupported-peer capability source', () => {
const callers = productionFiles.filter((file) => /\.ingestRemote\s*\(/.test(file.source))
expect(callers.map((file) => file.relativePath).sort()).toEqual([
'main/agent-hooks/wsl-hook-relay-deps.ts',
'main/ssh/ssh-relay-session.ts'
])
// Why: a bare import of the constant (unused elsewhere) would pass a substring check
// without ever stamping it onto the envelope — require the actual key:value binding.
for (const caller of callers) {
expect(stripComments(caller.source)).toMatch(
/advertisedAgentStatusCapabilities\s*:\s*AGENT_STATUS_LEGACY_UNADVERTISED_PEER_CAPABILITIES\b/
)
}
})
it('keeps run-capability advertisement behind the serving gate', () => {
const constantUsers = productionFiles
.filter((file) => file.source.includes('AGENT_STATUS_RUNS_RUNTIME_CAPABILITY'))
.map((file) => file.relativePath)
.sort()
expect(constantUsers).toEqual([
'shared/agent-status-run-capability.ts',
'shared/agent-status-serving-readiness.ts'
])
const literalUsers = productionFiles
.filter((file) => /['"]agent-status\.runs\.v1['"]/.test(file.source))
.map((file) => file.relativePath)
expect(literalUsers).toEqual(['shared/agent-status-run-capability.ts'])
})
})
@@ -0,0 +1,33 @@
import type { AgentHookEventPayload } from './agent-hook-listener/listener-event'
import {
admitLegacyAgentStatus,
type HookListenerState
} from './agent-hook-listener/listener-state'
import { AGENT_STATUS_2A_CURRENT_PRODUCER_MODE } from './agent-status-legacy-adapter'
export function cacheRelayLegacyAgentStatus(
state: HookListenerState,
entry: AgentHookEventPayload,
maxPanes: number,
dropPane: (paneKey: string) => void
): boolean {
if (
!admitLegacyAgentStatus(
state,
'relay-status-cache',
entry,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE,
{ moveToEnd: true }
)
) {
return false
}
while (state.lastStatusByPaneKey.size > maxPanes) {
const oldest = state.lastStatusByPaneKey.keys().next().value
if (oldest === undefined) {
return false
}
dropPane(oldest)
}
return true
}
@@ -0,0 +1,97 @@
import {
blankStringContents,
blankStringContentsDesynced,
stripComments
} from './source-scan/source-tree-scan'
export type AgentStatusLegacyMutationBypass = {
kind: 'direct-mutation' | 'alias-mutation' | 'map-cast' | 'passed-map' | 'scan-desync'
detail: string
}
const MUTATOR_NAMES = '(?:set|delete|clear)'
const IDENTIFIER = '[A-Za-z_$][A-Za-z0-9_$]*'
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function aliasesForLegacyStatusMap(source: string): Set<string> {
const aliases = new Set<string>()
const assignment = new RegExp(
`\\b(?:const|let|var)\\s+(${IDENTIFIER})\\s*=\\s*(?:this\\.)?state\\.lastStatusByPaneKey\\s*(?:;|\\n|$)`,
'g'
)
for (const match of source.matchAll(assignment)) {
aliases.add(match[1]!)
}
const destructuring = new RegExp(
`\\b(?:const|let|var)\\s*\\{[^}]*\\blastStatusByPaneKey(?:\\s*:\\s*(${IDENTIFIER}))?`,
'g'
)
for (const match of source.matchAll(destructuring)) {
aliases.add(match[1] ?? 'lastStatusByPaneKey')
}
return aliases
}
function isReadonlySnapshotCallPrefix(prefix: string): boolean {
return /(?:Array\.from|new\s+Map)\(\s*$/.test(prefix)
}
export function findAgentStatusLegacyMutationBypasses(
source: string
): AgentStatusLegacyMutationBypass[] {
const stripped = stripComments(source)
if (blankStringContentsDesynced(stripped)) {
return [{ kind: 'scan-desync', detail: 'source scanner lost quote or template state' }]
}
const code = blankStringContents(stripped)
const bypasses: AgentStatusLegacyMutationBypass[] = []
if (
new RegExp(`\\blastStatusByPaneKey\\s*\\.\\s*${MUTATOR_NAMES}\\s*\\(`).test(code) ||
/\[['"]lastStatusByPaneKey['"]\]\s*\.\s*(?:set|delete|clear)\s*\(/.test(stripped)
) {
bypasses.push({ kind: 'direct-mutation', detail: 'lastStatusByPaneKey mutator call' })
}
if (
new RegExp(
`\\blastStatusByPaneKey\\b[\\s\\S]{0,100}\\bas\\s+(?:unknown\\s+as\\s+)?(?:Readonly)?Map\\b[\\s\\S]{0,100}\\.\\s*${MUTATOR_NAMES}\\s*\\(`
).test(code)
) {
bypasses.push({ kind: 'map-cast', detail: 'lastStatusByPaneKey cast back to a mutable Map' })
}
const aliases = aliasesForLegacyStatusMap(code)
for (const alias of aliases) {
const escaped = escapeRegExp(alias)
if (new RegExp(`\\b${escaped}\\s*\\.\\s*${MUTATOR_NAMES}\\s*\\(`).test(code)) {
bypasses.push({ kind: 'alias-mutation', detail: `${alias} mutates an aliased status map` })
}
const passed = new RegExp(`\\b${IDENTIFIER}(?:\\.${IDENTIFIER})*\\s*\\(\\s*${escaped}\\b`, 'g')
for (const match of code.matchAll(passed)) {
const prefix = code.slice(
Math.max(0, match.index - 24),
match.index + match[0].indexOf('(') + 1
)
if (!isReadonlySnapshotCallPrefix(prefix)) {
bypasses.push({ kind: 'passed-map', detail: `${alias} is passed to another function` })
break
}
}
}
const directPass = new RegExp(
`\\b(${IDENTIFIER}(?:\\.${IDENTIFIER})*)\\s*\\(\\s*((?:this\\.)?state\\.lastStatusByPaneKey)\\s*[,)]`,
'g'
)
for (const match of code.matchAll(directPass)) {
if (match[1] !== 'Array.from' && match[1] !== 'Map') {
bypasses.push({
kind: 'passed-map',
detail: 'lastStatusByPaneKey is passed to another function'
})
}
}
return bypasses
}
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import {
AGENT_STATUS_2A_SERVING_READINESS,
agentStatusRunServingGatePasses,
advertisedAgentStatusRunCapabilities,
isAgentStatusRunServingAdvertised
} from './agent-status-serving-readiness'
describe('agent-status run serving readiness', () => {
it('keeps run serving unadvertised throughout 2A', () => {
expect(isAgentStatusRunServingAdvertised(AGENT_STATUS_2A_SERVING_READINESS)).toBe(false)
expect(advertisedAgentStatusRunCapabilities(AGENT_STATUS_2A_SERVING_READINESS)).toEqual([])
})
it('requires both serving readiness and an empty current-producer manifest', () => {
const ready = { servingReady: true }
expect(
agentStatusRunServingGatePasses({
readiness: ready,
currentProducerManifest: [{ caller: 'still-legacy' }]
})
).toBe(false)
expect(agentStatusRunServingGatePasses({ readiness: ready, currentProducerManifest: [] })).toBe(
true
)
expect(advertisedAgentStatusRunCapabilities(ready)).toEqual([])
})
})
@@ -0,0 +1,35 @@
import { AGENT_STATUS_RUNS_RUNTIME_CAPABILITY } from './agent-status-run-capability'
import { currentProducerAgentStatusLegacyIngressManifest } from './agent-status-legacy-ingress-manifest'
export type AgentStatusServingReadiness = Readonly<{
servingReady: boolean
}>
export type AgentStatusServingGateEvidence = {
readiness: AgentStatusServingReadiness
currentProducerManifest: readonly unknown[]
}
/** 2A defines the gate but deliberately does not claim run-serving readiness. */
export const AGENT_STATUS_2A_SERVING_READINESS: AgentStatusServingReadiness = Object.freeze({
servingReady: false
})
export function agentStatusRunServingGatePasses(evidence: AgentStatusServingGateEvidence): boolean {
return evidence.readiness.servingReady && evidence.currentProducerManifest.length === 0
}
export function isAgentStatusRunServingAdvertised(readiness: AgentStatusServingReadiness): boolean {
return agentStatusRunServingGatePasses({
readiness,
currentProducerManifest: currentProducerAgentStatusLegacyIngressManifest()
})
}
export function advertisedAgentStatusRunCapabilities(
readiness: AgentStatusServingReadiness
): readonly string[] {
return isAgentStatusRunServingAdvertised(readiness)
? Object.freeze([AGENT_STATUS_RUNS_RUNTIME_CAPABILITY])
: Object.freeze([])
}