refactor(agent-hooks): split remote and status application logic

This commit is contained in:
Neil
2026-09-19 07:20:07 -07:00
parent 911db95173
commit 602b49881c
4 changed files with 172 additions and 126 deletions
@@ -1,11 +1,8 @@
import { track } from '../../telemetry/client'
import { normalizeAgentStatusPayload } from '../../../shared/agent-status-types'
import { normalizeAgentProviderSession } from '../../../shared/agent-session-resume'
import { isAgentHookSource, restoreShedStatusFields } from '../../../shared/agent-hook-relay'
import { restoreShedStatusFields } from '../../../shared/agent-hook-relay'
import {
MAX_PANE_KEY_LEN,
normalizeClaudePromptId,
normalizeGrokPromptId,
warnOnHookEnvOrVersionMismatch
} from '../../../shared/agent-hook-listener/listener-limits'
import {
@@ -23,6 +20,7 @@ import {
olderPeerAgentStatusLegacyMode
} from '../../../shared/agent-status-legacy-adapter'
import { isValidPiProviderSessionOnly } from './server-status-identity'
import { normalizeRemoteEnvelopeFields } from './server-remote-envelope-normalization'
import { AgentHookServerIngestStructured } from './server-ingest-structured'
export abstract class AgentHookServerIngestRemote extends AgentHookServerIngestStructured {
@@ -124,50 +122,20 @@ export abstract class AgentHookServerIngestRemote extends AgentHookServerIngestS
return
}
let tabId = paneKey !== physicalPaneKey ? parsedPaneKey.tabId : reportedTabId
const hookEventName =
typeof envelope.hookEventName === 'string' && envelope.hookEventName.trim().length > 0
? envelope.hookEventName.trim()
: undefined
const source = isAgentHookSource(envelope.source) ? envelope.source : undefined
const providerPromptId =
source === 'claude'
? normalizeClaudePromptId(envelope.providerPromptId)
: source === 'grok'
? normalizeGrokPromptId(envelope.providerPromptId)
: undefined
const grokPromptBoundary =
source === 'grok' && envelope.grokPromptBoundary === true ? true : undefined
const compactTrigger =
source === 'claude' &&
(envelope.compactTrigger === 'manual' || envelope.compactTrigger === 'auto')
? envelope.compactTrigger
: undefined
const worktreeId =
envelope.worktreeId !== undefined && envelope.worktreeId.trim().length > 0
? envelope.worktreeId.trim()
: undefined
const promptInteractionKey =
typeof envelope.promptInteractionKey === 'string' &&
envelope.promptInteractionKey.trim().length > 0
? envelope.promptInteractionKey.trim()
: undefined
const toolUseId =
typeof envelope.toolUseId === 'string' && envelope.toolUseId.trim().length > 0
? envelope.toolUseId.trim()
: undefined
const toolAgentId =
typeof envelope.toolAgentId === 'string' && envelope.toolAgentId.trim().length > 0
? envelope.toolAgentId.trim()
: undefined
const teammateName =
typeof envelope.teammateName === 'string' && envelope.teammateName.trim().length > 0
? envelope.teammateName.trim()
: undefined
const toolAgentType =
typeof envelope.toolAgentType === 'string' && envelope.toolAgentType.trim().length > 0
? envelope.toolAgentType.trim()
: undefined
const providerSession = normalizeAgentProviderSession(envelope.providerSession) ?? undefined
const {
hookEventName,
source,
providerPromptId,
grokPromptBoundary,
compactTrigger,
worktreeId,
promptInteractionKey,
toolUseId,
toolAgentId,
teammateName,
toolAgentType,
providerSession
} = normalizeRemoteEnvelopeFields(envelope)
// Why: relay crosses a trust boundary — re-run the canonical normalizer to enforce caps/invariants (returns null on malformed).
const validatedPayload = normalizeAgentStatusPayload(envelope.payload)
if (!validatedPayload) {
@@ -261,17 +229,13 @@ export abstract class AgentHookServerIngestRemote extends AgentHookServerIngestS
paneKey,
providerPromptId
)
// Why: an older relay built this payload before the boundary flag existed, so it arrives as a
// plain `done` — which every completion-reactive consumer reads as a finished turn. Stamp the
// boundary here so a compact stays silent regardless of which relay normalized it.
// Older relays omit the boundary flag; stamp it so compact completion stays silent.
if (normalizedPayload.sessionBoundary !== true) {
normalizedPayload = { ...normalizedPayload, sessionBoundary: true }
}
acceptedCompactCompletion = true
}
// Why: keyed on "did we accept a completion", not on the trigger surviving the wire — the
// trigger-stripped replay is exactly the shape that arrives without one, and it is still the
// compact's own promptless event, so it still needs the summarized turn's label.
// Accepted compact completions retain the summarized turn label, including trigger-stripped replays.
if (
source === 'claude' &&
(compactTrigger !== undefined || acceptedCompactCompletion) &&
@@ -283,7 +247,6 @@ export abstract class AgentHookServerIngestRemote extends AgentHookServerIngestS
const applyClaudeBackgroundWork =
normalizedPayload.agentType === 'claude' &&
typeof envelope.claudeRunningNonAgentTask === 'boolean' &&
// Why: reconnect replay may seed a restarted listener, but cannot override any observation made by this runtime.
(envelope.isReplay !== true || !this.runtimeObservedStatusPaneKeys.has(paneKey))
// Why: run the HTTP path's warn-once version/env-mismatch diagnostics with this.env as expected.
warnOnHookEnvOrVersionMismatch(this.state, {
@@ -0,0 +1,84 @@
import { normalizeAgentProviderSession } from '../../../shared/agent-session-resume'
import {
normalizeClaudePromptId,
normalizeGrokPromptId
} from '../../../shared/agent-hook-listener/listener-limits'
import { isAgentHookSource, type AgentHookSource } from '../../../shared/agent-hook-relay'
export type RemoteEnvelopeFields = {
hookEventName?: string
source?: AgentHookSource
providerPromptId?: string
grokPromptBoundary?: true
compactTrigger?: 'manual' | 'auto'
worktreeId?: string
promptInteractionKey?: string
toolUseId?: string
toolAgentId?: string
teammateName?: string
toolAgentType?: string
providerSession?: NonNullable<ReturnType<typeof normalizeAgentProviderSession>>
}
export function normalizeRemoteEnvelopeFields(envelope: {
hookEventName?: string
source?: unknown
providerPromptId?: unknown
grokPromptBoundary?: unknown
compactTrigger?: unknown
worktreeId?: string
promptInteractionKey?: string
toolUseId?: string
toolAgentId?: string
teammateName?: string
toolAgentType?: string
providerSession?: unknown
}): RemoteEnvelopeFields {
const source = isAgentHookSource(envelope.source) ? envelope.source : undefined
return {
hookEventName:
typeof envelope.hookEventName === 'string' && envelope.hookEventName.trim().length > 0
? envelope.hookEventName.trim()
: undefined,
source,
providerPromptId:
source === 'claude'
? normalizeClaudePromptId(envelope.providerPromptId)
: source === 'grok'
? normalizeGrokPromptId(envelope.providerPromptId)
: undefined,
grokPromptBoundary:
source === 'grok' && envelope.grokPromptBoundary === true ? true : undefined,
compactTrigger:
source === 'claude' &&
(envelope.compactTrigger === 'manual' || envelope.compactTrigger === 'auto')
? envelope.compactTrigger
: undefined,
worktreeId:
envelope.worktreeId !== undefined && envelope.worktreeId.trim().length > 0
? envelope.worktreeId.trim()
: undefined,
promptInteractionKey:
typeof envelope.promptInteractionKey === 'string' &&
envelope.promptInteractionKey.trim().length > 0
? envelope.promptInteractionKey.trim()
: undefined,
toolUseId:
typeof envelope.toolUseId === 'string' && envelope.toolUseId.trim().length > 0
? envelope.toolUseId.trim()
: undefined,
toolAgentId:
typeof envelope.toolAgentId === 'string' && envelope.toolAgentId.trim().length > 0
? envelope.toolAgentId.trim()
: undefined,
teammateName:
typeof envelope.teammateName === 'string' && envelope.teammateName.trim().length > 0
? envelope.teammateName.trim()
: undefined,
toolAgentType:
typeof envelope.toolAgentType === 'string' && envelope.toolAgentType.trim().length > 0
? envelope.toolAgentType.trim()
: undefined,
providerSession: normalizeAgentProviderSession(envelope.providerSession) ?? undefined
}
}
@@ -9,6 +9,8 @@ import type {
AgentStatusObservation,
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 type { EnrichedAgentHookEventPayload } from './server-types'
import { agentTypeToPromptSentAgentKind } from './server-status-identity'
import { AgentHookServerStatusDisposition } from './server-status-disposition'
@@ -17,6 +19,74 @@ import { AgentHookServerStatusDisposition } from './server-status-disposition'
const MAX_REMEMBERED_EVIDENCE_OBSERVATIONS = 1024
export abstract class AgentHookServerStatusApplication extends AgentHookServerStatusDisposition {
protected refreshTerminalStatusEvidence(
previous: EnrichedAgentHookEventPayload,
mutationBefore?: EnrichedAgentHookEventPayload,
emitEnrichedStatus = false
): void {
if (!this.canWriteLegacyStatusRow(previous)) {
return
}
const connectionClearWatermark = previous.connectionId
? this.connectionTimestampWatermarkById.get(previous.connectionId)
: undefined
const now = Math.max(Date.now(), (connectionClearWatermark ?? -1) + 1)
if (previous.connectionId) {
this.connectionTimestampWatermarkById.set(previous.connectionId, now)
}
const {
receivedAt: _receivedAt,
evidenceObservedAt: _evidenceObservedAt,
stateStartedAt,
observation: _observation,
restoredUnconfirmed: _restoredUnconfirmed,
isReplay: _isReplay,
...payload
} = previous
const refreshed: EnrichedAgentHookEventPayload = {
...payload,
receivedAt: now,
evidenceObservedAt: now,
stateStartedAt,
observation: this.stampObservation(payload, 'osc', now)
}
const firstRuntimeObservation = !this.runtimeObservedStatusPaneKeys.has(refreshed.paneKey)
this.runtimeObservedStatusPaneKeys.add(refreshed.paneKey)
if (!this.writeLegacyStatusRow(refreshed)) {
return
}
this.commitStatusRowMutation(mutationBefore ?? previous, refreshed)
this.scheduleStatusPersist()
// A dismissed row may retain only provider resume identity. Its preserved payload can still
// read `working`, but it is deliberately hidden from live readers and must not renew awake or
// mobile freshness leases.
if (refreshed.providerSessionOnly === true) {
return
}
if (firstRuntimeObservation) {
this.notifyStatusChangeListeners()
}
this.emitStatusFreshnessObservation({
paneKey: refreshed.paneKey,
state: refreshed.payload.state,
receivedAt: refreshed.receivedAt,
observedInCurrentRuntime: true,
...(refreshed.worktreeId ? { worktreeId: refreshed.worktreeId } : {}),
...(refreshed.terminalHandle ? { terminalHandle: refreshed.terminalHandle } : {})
})
if (emitEnrichedStatus) {
this.emitEnrichedStatus(refreshed)
}
}
protected writeLegacyStatusRow(entry: EnrichedAgentHookEventPayload): boolean {
return admitLegacyAgentStatus(
this.state,
'main-status-update',
entry,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
}
/** `observedAt` is the producer's own clock for evidence that has one (a session journal); it
* stamps the evidence and state-start times while `receivedAt` keeps delivery order. */
protected attachStatusTiming(
@@ -10,8 +10,6 @@ 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,
@@ -249,73 +247,4 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA
)
return enriched
}
protected refreshTerminalStatusEvidence(
previous: EnrichedAgentHookEventPayload,
mutationBefore?: EnrichedAgentHookEventPayload,
emitEnrichedStatus = false
): void {
if (!this.canWriteLegacyStatusRow(previous)) {
return
}
const connectionClearWatermark = previous.connectionId
? this.connectionTimestampWatermarkById.get(previous.connectionId)
: undefined
const now = Math.max(Date.now(), (connectionClearWatermark ?? -1) + 1)
if (previous.connectionId) {
this.connectionTimestampWatermarkById.set(previous.connectionId, now)
}
const {
receivedAt: _receivedAt,
evidenceObservedAt: _evidenceObservedAt,
stateStartedAt,
observation: _observation,
restoredUnconfirmed: _restoredUnconfirmed,
isReplay: _isReplay,
...payload
} = previous
const refreshed: EnrichedAgentHookEventPayload = {
...payload,
receivedAt: now,
evidenceObservedAt: now,
stateStartedAt,
observation: this.stampObservation(payload, 'osc', now)
}
const firstRuntimeObservation = !this.runtimeObservedStatusPaneKeys.has(refreshed.paneKey)
this.runtimeObservedStatusPaneKeys.add(refreshed.paneKey)
if (!this.writeLegacyStatusRow(refreshed)) {
return
}
this.commitStatusRowMutation(mutationBefore ?? previous, refreshed)
this.scheduleStatusPersist()
// A dismissed row may retain only provider resume identity. Its preserved payload can still
// read `working`, but it is deliberately hidden from live readers and must not renew awake or
// mobile freshness leases.
if (refreshed.providerSessionOnly === true) {
return
}
if (firstRuntimeObservation) {
this.notifyStatusChangeListeners()
}
this.emitStatusFreshnessObservation({
paneKey: refreshed.paneKey,
state: refreshed.payload.state,
receivedAt: refreshed.receivedAt,
observedInCurrentRuntime: true,
...(refreshed.worktreeId ? { worktreeId: refreshed.worktreeId } : {}),
...(refreshed.terminalHandle ? { terminalHandle: refreshed.terminalHandle } : {})
})
if (emitEnrichedStatus) {
this.emitEnrichedStatus(refreshed)
}
}
private writeLegacyStatusRow(entry: EnrichedAgentHookEventPayload): boolean {
return admitLegacyAgentStatus(
this.state,
'main-status-update',
entry,
AGENT_STATUS_2A_CURRENT_PRODUCER_MODE
)
}
}