instrument pane identity evidence availability

This commit is contained in:
Brennan Benson
2026-08-27 12:03:23 -07:00
parent fecdf0bde8
commit 145e28cfe8
33 changed files with 3280 additions and 175 deletions
+13
View File
@@ -79,6 +79,10 @@ import { registerCoreHandlers } from './ipc/register-core-handlers/register-core
import { initObservability, shutdownObservability } from './observability'
import { registerMobileHandlers } from './ipc/mobile'
import { initTelemetry, shutdownTelemetry, trackAppOpenedOnce, track } from './telemetry/client'
import {
createDesktopPaneAgentIdentityCensus,
createHeadlessPaneAgentIdentityCensus
} from './telemetry/pane-agent-identity-census-composition'
import { classifyError } from './telemetry/classify-error'
import { recordManagedHookInstallFailure } from './agent-hooks/install-telemetry'
import {
@@ -2490,6 +2494,9 @@ void app.whenReady().then(async () => {
runtime?.touchMobileSessionTabsForPane(enriched.paneKey, enriched.worktreeId ?? null)
}
})
const unsubscribePaneAgentIdentityHook = agentHookServer.subscribeEnrichedStatus((enriched) => {
runtime?.observeAgentHookStatus(enriched)
})
// Teardown: agent exit, pane close, and the SSH transient-disconnect batch all land
// here. Without it the live state published above becomes a zombie question card.
const unsubscribeHookStatusClear = agentHookServer.subscribePaneStatusClear((clear) => {
@@ -2506,6 +2513,7 @@ void app.whenReady().then(async () => {
unsubscribeStatusChanges()
unsubscribeProviderSessionChanges()
unsubscribeHookStatusSessionTabs()
unsubscribePaneAgentIdentityHook()
unsubscribeHookStatusClear()
}
// Why: telemetry must init before any IPC handler/renderer can call track(); it's a no-op in dev and while TELEMETRY_ENABLED is false, so it's safe early.
@@ -2723,7 +2731,11 @@ void app.whenReady().then(async () => {
envelope
)
}
const paneAgentIdentityCensus = isServeMode
? createHeadlessPaneAgentIdentityCensus()
: createDesktopPaneAgentIdentityCensus()
const runtimeService = new OrcaRuntimeService(store, stats, {
paneAgentIdentityCensus,
agentSessionClaimSigner: loadAgentSessionClaimSigner(
getProfileUserDataPath(),
getProfileUserDataPath()
@@ -3508,6 +3520,7 @@ app.on('will-quit', (e) => {
pluginService = null
setUnreadDockBadgeCount(0)
agentHookServer.stop()
runtime?.shutdownPaneAgentIdentityCensus()
// Why Windows only: POSIX hooks short-circuit on ORCA_PANE_KEY, while Windows must register a
// bare script path that cannot express the guard and would otherwise keep spawning after quit.
// Why bounded here: every other teardown member carries its own ceiling, and this one reaches
+1
View File
@@ -78,6 +78,7 @@ export async function commitPtyIpcSpawn(ctx: PtyIpcSpawnState): Promise<PtySpawn
spawnEnv: ctx.spawnEnv,
launchAgent: args.launchAgent,
launchConfig: ctx.effectiveLaunchConfig,
resumeProviderSession: args.resumeProviderSession,
isReattach: ctx.result.isReattach === true,
hasStablePaneOwner: ctx.stablePaneOwner !== null,
incarnationId: ctx.result.incarnationId
+26 -2
View File
@@ -163,7 +163,19 @@ export function createPtyWriteInput(deps: {
if (visibleRendererPtys.has(args.id)) {
clearHiddenRendererResizeOutput(args.id)
}
return writePtyProviderInput(provider, args.id, args.data)
const result = writePtyProviderInput(provider, args.id, args.data)
if (typeof result === 'boolean') {
if (result) {
runtime?.observeAcceptedPtyWrite?.(args.id, args.data)
}
return result
}
return result.then((accepted) => {
if (accepted) {
runtime?.observeAcceptedPtyWrite?.(args.id, args.data)
}
return accepted
})
} catch {
return false
}
@@ -188,7 +200,19 @@ export function createPtyWriteInput(deps: {
if (visibleRendererPtys.has(args.id)) {
clearHiddenRendererResizeOutput(args.id)
}
return writePtyProviderInput(provider, args.id, args.data)
const result = writePtyProviderInput(provider, args.id, args.data)
if (typeof result === 'boolean') {
if (result) {
runtime?.observeAcceptedPtyWrite?.(args.id, args.data)
}
return result
}
return result.then((accepted) => {
if (accepted) {
runtime?.observeAcceptedPtyWrite?.(args.id, args.data)
}
return accepted
})
} catch {
return false
}
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'
import { admitRendererAgentLaunchAuthority } from './launch-authority'
function admit(resumeProviderSession: unknown) {
return admitRendererAgentLaunchAuthority({
launchToken: 'token',
spawnEnv: { ORCA_AGENT_LAUNCH_TOKEN: 'token' },
launchAgent: 'codex',
launchConfig: {} as never,
resumeProviderSession,
isReattach: false,
hasStablePaneOwner: false,
incarnationId: 'incarnation-1'
})
}
describe('renderer agent launch authority', () => {
it('classifies fresh launches and provider-session resumes distinctly', () => {
expect(admit(undefined)).toMatchObject({ launchMode: 'orca-launch' })
expect(admit('provider-session')).toMatchObject({ launchMode: 'resume' })
})
})
+11 -2
View File
@@ -10,10 +10,15 @@ export function admitRendererAgentLaunchAuthority(args: {
spawnEnv: Record<string, string> | undefined
launchAgent: unknown
launchConfig: SleepingAgentLaunchConfig | undefined
resumeProviderSession: unknown
isReattach: boolean
hasStablePaneOwner: boolean
incarnationId: unknown
}): { launchToken: string; launchAgent: TuiAgent } | null {
}): {
launchToken: string
launchAgent: TuiAgent
launchMode: 'orca-launch' | 'resume'
} | null {
if (
args.isReattach ||
args.hasStablePaneOwner ||
@@ -27,7 +32,11 @@ export function admitRendererAgentLaunchAuthority(args: {
) {
return null
}
return { launchToken: args.launchToken, launchAgent: args.launchAgent }
return {
launchToken: args.launchToken,
launchAgent: args.launchAgent,
launchMode: args.resumeProviderSession ? 'resume' : 'orca-launch'
}
}
export function shouldRefreshNativeClaudeAgentTeamsEnv(args: {
@@ -458,7 +458,12 @@ describe('registerCoreHandlers', () => {
it('passes the store through to handler registrars that need it', async () => {
const store = { marker: 'store' }
const runtime = { marker: 'runtime', getAgentBrowserBridge: () => null }
const paneAgentIdentityCensus = { marker: 'paneAgentIdentityCensus' }
const runtime = {
marker: 'runtime',
getAgentBrowserBridge: () => null,
getPaneAgentIdentityCensus: () => paneAgentIdentityCensus
}
const stats = { marker: 'stats' }
const claudeUsage = { marker: 'claudeUsage' }
const codexUsage = { marker: 'codexUsage' }
@@ -539,7 +544,7 @@ describe('registerCoreHandlers', () => {
expect(registerWorkspaceSpaceHandlersMock).toHaveBeenCalledWith(store)
expect(registerWorkspacePortHandlersMock).toHaveBeenCalledWith(store)
expect(registerLocalhostWorktreeLabelHandlersMock).toHaveBeenCalledWith(store)
expect(registerTelemetryHandlersMock).toHaveBeenCalledWith(store)
expect(registerTelemetryHandlersMock).toHaveBeenCalledWith(store, paneAgentIdentityCensus)
expect(registerOrcaProfileHandlersMock).toHaveBeenCalledWith(store, { onBeforeRelaunch })
expect(registerSessionHandlersMock).toHaveBeenCalledWith(store)
expect(registerUIHandlersMock).toHaveBeenCalledWith(store, {
@@ -189,7 +189,7 @@ export function registerCoreHandlers(
if (pluginService) {
registerPluginHandlers(store, pluginService, runtime, marketplaceServices)
}
registerTelemetryHandlers(store)
registerTelemetryHandlers(store, runtime.getPaneAgentIdentityCensus())
registerOrcaProfileHandlers(store, {
onBeforeRelaunch: lifecycleOptions.onBeforeRelaunch,
onAuthMutation: lifecycleOptions.onOrcaProfileAuthMutation,
+30 -1
View File
@@ -40,6 +40,7 @@ vi.mock('../telemetry/onboarding-cohort-classifier', () => ({
}))
import { _resetStoreForTests, registerTelemetryHandlers } from './telemetry'
import { PaneAgentIdentityCensus } from '../telemetry/pane-agent-identity-census'
function captureHandlers(): void {
handlers.clear()
@@ -71,7 +72,7 @@ function makeFakeStore(telemetry: GlobalSettings['telemetry']): {
function registerWith(telemetry: GlobalSettings['telemetry']): FakeStoreState {
const { store, state } = makeFakeStore(telemetry)
registerTelemetryHandlers(store)
registerTelemetryHandlers(store, new PaneAgentIdentityCensus({ emit: null }))
captureHandlers()
return state
}
@@ -161,10 +162,38 @@ describe('telemetry IPC handlers', () => {
bucket_source: 'crossed_now'
})
handler({}, 'daemon_audit_eligibility', {})
handler({}, 'pane_agent_identity_availability', { rows: [] })
expect(trackMock).not.toHaveBeenCalled()
expect(getCohortAtEmitMock).not.toHaveBeenCalled()
})
it('accepts identity availability only through the dedicated validated ingestion path', () => {
const census = new PaneAgentIdentityCensus({ emit: null })
const ingest = vi.spyOn(census, 'ingestRelaySnapshot')
const { store } = makeFakeStore({
installId: 'x',
existedBeforeTelemetryRelease: false,
optedIn: true
})
registerTelemetryHandlers(store, census)
captureHandlers()
handlers.get('telemetry:ingestPaneAgentIdentityAvailability')!(
{},
{
environmentKey: 'relay-1',
snapshot: { epoch: 'epoch-1', revision: 1, rows: [] }
}
)
expect(ingest).toHaveBeenCalledWith('relay-1', {
epoch: 'epoch-1',
revision: 1,
rows: []
})
expect(trackMock).not.toHaveBeenCalled()
})
it('injects cohort for setup script prompt events', () => {
registerWith({ installId: 'x', existedBeforeTelemetryRelease: false, optedIn: true })
getCohortAtEmitMock.mockReturnValue({ nth_repo_added: 3 })
+36 -1
View File
@@ -18,6 +18,9 @@ import { resolveConsent, type ConsentState } from '../telemetry/consent'
import type { Store } from '../persistence'
import { isCohortExtendedEvent, isOnboardingEvent } from '../../shared/telemetry-events'
import type { EventName, EventProps, OptInVia } from '../../shared/telemetry-events'
import type { PaneAgentIdentityCensus } from '../telemetry/pane-agent-identity-census'
import type { PaneAgentIdentityAvailabilitySnapshot } from '../../shared/pane-agent-identity-availability'
import { isPaneAgentIdentityAvailabilitySnapshot } from '../../shared/pane-agent-identity-availability-validator'
// Module-level store ref: handlers need a synchronous `settings.telemetry` read to derive `via` before any mutation.
let storeRef: Store | null = null
@@ -25,6 +28,7 @@ let storeRef: Store | null = null
const MAIN_OWNED_TELEMETRY_EVENTS = new Set<EventName>([
'app_starred_orca',
'daemon_audit_eligibility',
'pane_agent_identity_availability',
'star_nag_outcome',
'feature_interaction_usage_bucket_reached'
])
@@ -49,7 +53,10 @@ function deriveOptInVia(store: Store, incomingOptedIn: boolean): OptInVia {
return 'settings'
}
export function registerTelemetryHandlers(store: Store): void {
export function registerTelemetryHandlers(
store: Store,
paneIdentityCensus: PaneAgentIdentityCensus
): void {
storeRef = store
ipcMain.handle('telemetry:track', (_event, name: unknown, props: unknown): void => {
@@ -78,6 +85,34 @@ export function registerTelemetryHandlers(store: Store): void {
track(eventName, finalProps as EventProps<EventName>)
})
ipcMain.handle(
'telemetry:ingestPaneAgentIdentityAvailability',
(_event, payload: unknown): void => {
const environmentKey =
payload && typeof payload === 'object'
? (payload as { environmentKey?: unknown }).environmentKey
: undefined
const snapshot =
payload && typeof payload === 'object'
? (payload as { snapshot?: unknown }).snapshot
: undefined
if (
typeof environmentKey !== 'string' ||
environmentKey.length === 0 ||
environmentKey.length > 256
) {
return
}
if (snapshot !== undefined && !isPaneAgentIdentityAvailabilitySnapshot(snapshot)) {
return
}
paneIdentityCensus.ingestRelaySnapshot(
environmentKey,
snapshot as PaneAgentIdentityAvailabilitySnapshot | undefined
)
}
)
ipcMain.handle('telemetry:setOptIn', (_event, optedIn: unknown): Promise<void> | void => {
// Strict input typing — renderer can pass anything over IPC.
if (typeof optedIn !== 'boolean') {
+7
View File
@@ -140,6 +140,8 @@ async function startOrcadRuntime(
const { resolveAdvertisedPairingEndpoint } = await import('../runtime/pairing-endpoint')
const { ServeReadinessPublisher } = await import('../server/serve-readiness')
const { Store } = await import('../persistence/loading-store/store')
const { createHeadlessPaneAgentIdentityCensus } =
await import('../telemetry/pane-agent-identity-census-composition')
const { ensureActiveOrcaProfile, initOrcaProfilePaths } =
await import('../orca-profiles/profile-index-store')
const { initSshHostKeyStoreFile } = await import('../ssh/ssh-host-key-store')
@@ -165,7 +167,9 @@ async function startOrcadRuntime(
// registerPtyHandlers so the IPC layer routes through the daemon from the first call.
await startOrcadDaemon()
const paneAgentIdentityCensus = createHeadlessPaneAgentIdentityCensus()
const runtime = new OrcaRuntimeService(store, undefined, {
paneAgentIdentityCensus,
// Why lazy: a daemon swap replaces the provider after construction, so an eager
// reference would freeze the pre-daemon one.
getLocalProvider: () => getLocalPtyProvider(),
@@ -265,6 +269,9 @@ async function startOrcadRuntime(
try {
await rpc.stop()
} finally {
// Why the census first: it reads runtime state, so it must settle before the daemon
// link goes away.
runtime.shutdownPaneAgentIdentityCensus(false)
// Why disconnect and not shut down: the daemon must outlive this process, or an
// orcad restart goes back to killing every running terminal. See
// orcad-daemon-supervision.ts.
+381 -2
View File
@@ -344,7 +344,6 @@ import type {
TerminalPaneLayoutNode,
TerminalTab
} from '../../shared/terminal-tab-types'
import type { TuiAgent } from '../../shared/tui-agent'
import type { BranchPrefixStrategy } from '../../shared/ui-chrome-types'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import type { WorkspaceSource as WorkspaceCreateTelemetrySource } from '../../shared/workspace-source'
@@ -449,6 +448,19 @@ import type {
LinearStatusSetResult
} from '../../shared/linear/agent-access'
import { runtimeTerminalDegradation } from './native-terminal-availability'
import {
MAX_PENDING_IDENTITY_OBSERVATIONS,
TITLE_CANDIDATE_WINDOW_MS
} from '../../shared/pane-agent-identity-availability'
import type {
PaneAgentIdentityAvailability,
PaneAgentIdentityHostKind
} from '../../shared/pane-agent-identity-evidence'
import { ManualAgentCommandTracker } from '../../shared/manual-agent-command-tracker'
import { resolveExplicitTerminalTitleAgentType } from '../../shared/terminal-title-agent-type'
import type { TuiAgent } from '../../shared/tui-agent'
import { PaneAgentIdentityCensus } from '../telemetry/pane-agent-identity-census'
import { PaneAgentIdentityObservationAuthority } from './pane-agent-identity-observation-authority'
import {
BROWSER_UNAVAILABLE_ERROR_CODE,
browserUnavailableMessage,
@@ -3130,6 +3142,21 @@ type ProviderSnapshotReadOptions = {
export class OrcaRuntimeService {
private readonly runtimeId = randomUUID()
private readonly paneAgentIdentityCensus: PaneAgentIdentityCensus
private readonly paneAgentIdentityObservations: PaneAgentIdentityObservationAuthority
private readonly paneAgentCommandTrackers = new Map<string, ManualAgentCommandTracker>()
private readonly paneAgentIdentityOwnedIncarnations = new Map<string, PtyIncarnationId | null>()
private readonly paneAgentIdentityLiveHookObservedAtByPty = new Map<string, number>()
private readonly sshTypedAgentCandidates = new Map<
string,
{
agent: TuiAgent
incarnationId: PtyIncarnationId | null
lifecycleGeneration: number
commandOrdinal: number
timer: ReturnType<typeof setTimeout>
}
>()
private readonly startedAt = Date.now()
private readonly store: RuntimeStore | null
private managedHookReconciliationGeneration = 0
@@ -3874,9 +3901,16 @@ export class OrcaRuntimeService {
agentSessionClaimSigner?: AgentSessionClaimSigner
orchestrationEnvironmentTransport?: OrchestrationEnvironmentTransport
skillTransactionRecovery?: Promise<unknown>
paneAgentIdentityCensus?: PaneAgentIdentityCensus
}
) {
this.store = store
this.paneAgentIdentityCensus =
deps?.paneAgentIdentityCensus ?? new PaneAgentIdentityCensus({ emit: null })
this.paneAgentIdentityObservations = new PaneAgentIdentityObservationAuthority(
this.runtimeId,
this.paneAgentIdentityCensus
)
// Why: per-device tab selections must survive host restarts, or every phone snaps back to the first tab on return.
const persistedClientTabSelections = store?.getMobileClientTabSelections?.()
if (persistedClientTabSelections) {
@@ -11442,7 +11476,11 @@ export class OrcaRuntimeService {
tabId: string
leafId: string
incarnationId?: PtyIncarnationId
agentLaunchAuthority?: { launchToken: string; launchAgent: TuiAgent }
agentLaunchAuthority?: {
launchToken: string
launchAgent: TuiAgent
launchMode?: 'orca-launch' | 'resume'
}
},
isWsl?: boolean
): void {
@@ -11480,6 +11518,11 @@ export class OrcaRuntimeService {
pty.launchToken = agentLaunchAuthority.launchToken
pty.launchIncarnationId = binding.incarnationId
pty.launchAgent = agentLaunchAuthority.launchAgent
this.observePtyAgentLaunch(
pty,
agentLaunchAuthority.launchMode ?? 'orca-launch',
agentLaunchAuthority.launchToken
)
}
const pendingIncarnation = this.pendingPtyRegistrationIncarnations.get(ptyId)
if (
@@ -12452,6 +12495,13 @@ export class OrcaRuntimeService {
pty.lastOscTitleEpochMs = observedAtEpochMs
pty.lastAgentStatus = agentStatus
pty.lastAgentStatusObservedLive = true
const titleAgent = resolveExplicitTerminalTitleAgentType(rawTitle)
if (titleAgent) {
this.paneAgentIdentityObservations.observeTitle(
this.getPaneAgentIdentityPtyContext(pty),
titleAgent
)
}
if (prevStatus !== agentStatus) {
pty.lastAgentStatusStartedAtEpochMs = observedAtEpochMs
}
@@ -12551,7 +12601,13 @@ export class OrcaRuntimeService {
private resetTrackedTerminalStateForProviderGeneration(ptyId: string): void {
// Why: a replacement daemon session can reuse the PTY id, but title/parser
// state from the prior process must not bleed into its snapshots or chunks.
const previousPty = this.ptysById.get(ptyId)
this.releasePaneAgentIdentityOwner(ptyId, previousPty?.incarnationId)
this.paneAgentIdentityObservations.exitOrRebind(ptyId, previousPty?.incarnationId)
this.disposePtyTitleTracker(ptyId)
this.paneAgentCommandTrackers.delete(ptyId)
this.paneAgentIdentityLiveHookObservedAtByPty.delete(ptyId)
this.clearSshTypedAgentCandidate(ptyId)
this.oscTitleScanTailByPtyId.delete(ptyId)
this.osc7ScanTailByPtyId.delete(ptyId)
this.agentStatusOscProcessorsByPtyId.delete(ptyId)
@@ -12721,6 +12777,19 @@ export class OrcaRuntimeService {
}
let retainedChanged = false
for (const payload of chunk.payloads) {
if (pty && isTuiAgent(payload.agentType)) {
this.rememberPaneAgentIdentityHookSurface(ptyId, payload.state)
if (connectionId && payload.state !== 'done') {
this.corroborateSshTypedAgent(ptyId, payload.agentType, 'live-hook')
}
this.paneAgentIdentityObservations.observeEvidence(
this.getPaneAgentIdentityPtyContext(pty),
{
source: payload.state === 'done' ? 'completed-hook' : 'live-hook',
agent: payload.agentType
}
)
}
this.recordAgentPromptLifecycleState(
ptyId,
mapExplicitAgentStateToRuntimeTerminalStatus(payload.state)
@@ -14662,6 +14731,8 @@ export class OrcaRuntimeService {
private retirePtyAgentLaunchAuthority(ptyId: string): void {
const pty = this.ptysById.get(ptyId)
this.releasePaneAgentIdentityOwner(ptyId, pty?.incarnationId)
this.paneAgentIdentityLiveHookObservedAtByPty.delete(ptyId)
if (!pty) {
return
}
@@ -15924,6 +15995,8 @@ export class OrcaRuntimeService {
if (exitIncarnationId && pty?.incarnationId && exitIncarnationId !== pty.incarnationId) {
return
}
this.paneAgentCommandTrackers.delete(ptyId)
this.clearSshTypedAgentCandidate(ptyId)
// Why intent first: a requested stop can still be delivered by the provider's
// own exit event, whose status looks exactly like a natural finish.
//
@@ -15944,6 +16017,10 @@ export class OrcaRuntimeService {
pty?.connectionId != null &&
exitCode < 0 &&
options?.hostExitConfirmed !== true
if (!preservesAbnormalSshSurface && pty) {
this.paneAgentIdentityLiveHookObservedAtByPty.delete(ptyId)
this.paneAgentIdentityObservations.exitOrRebind(ptyId, pty.incarnationId)
}
// Why: collect before retirePtyAgentLaunchAuthority, which deletes the restored-authority
// receipt a receipt-only pane's key comes from.
const exitPaneKeys = this.collectPaneKeysForPty(ptyId)
@@ -18022,6 +18099,7 @@ export class OrcaRuntimeService {
return {
terminals: listedTerminals,
agentIdentityAvailability: this.paneAgentIdentityCensus.snapshot(),
hostScope: this.buildTerminalListHostScope(
targetWorktreeId,
matchingTerminals,
@@ -18040,6 +18118,269 @@ export class OrcaRuntimeService {
}
}
recordPaneAgentIdentityAvailability(observation: PaneAgentIdentityAvailability): void {
this.paneAgentIdentityCensus.record(observation)
}
getPaneAgentIdentityCensus(): PaneAgentIdentityCensus {
return this.paneAgentIdentityCensus
}
private getPaneAgentIdentityPtyContext(pty: RuntimePtyWorktreeRecord): {
ptyId: string
incarnationId: PtyIncarnationId | null
hostKinds: readonly PaneAgentIdentityHostKind[]
} {
const hostKinds: readonly PaneAgentIdentityHostKind[] = pty.connectionId
? ['ssh']
: pty.isWsl || pty.wslDistro || this.wslDistroByPtyId.has(pty.ptyId)
? ['wsl-host', 'wsl-distro']
: ['native']
return {
ptyId: pty.ptyId,
incarnationId: pty.incarnationId,
hostKinds: this.paneAgentIdentityCensus.hostKindsForObservation(hostKinds)
}
}
private observePtyAgentLaunch(
pty: RuntimePtyWorktreeRecord,
launchMode: 'orca-launch' | 'resume',
attestationId: string
): void {
if (!pty.launchAgent) {
return
}
if (
this.paneAgentIdentityObservations.attestRun(
this.getPaneAgentIdentityPtyContext(pty),
launchMode,
pty.launchAgent,
`${launchMode}:${attestationId}`
)
) {
this.paneAgentIdentityOwnedIncarnations.set(pty.ptyId, pty.incarnationId)
}
}
private releasePaneAgentIdentityOwner(
ptyId: string,
incarnationId?: PtyIncarnationId | null
): void {
if (
!this.paneAgentIdentityOwnedIncarnations.has(ptyId) ||
(incarnationId !== undefined &&
this.paneAgentIdentityOwnedIncarnations.get(ptyId) !== incarnationId)
) {
return
}
this.paneAgentIdentityOwnedIncarnations.delete(ptyId)
this.paneAgentCommandTrackers.get(ptyId)?.reset()
}
observeAgentHookStatus(args: {
paneKey: string
connectionId?: string | null
isReplay?: boolean
restoredUnconfirmed?: boolean
providerSessionOnly?: boolean
payload: { state: string; agentType?: string }
}): boolean {
if (
args.isReplay === true ||
args.restoredUnconfirmed === true ||
args.providerSessionOnly === true ||
!isTuiAgent(args.payload.agentType)
) {
return false
}
const pty = this.getPtyRecordForPaneKey(args.paneKey)
const isWslRelay = isWslHookRelayConnectionId(args.connectionId)
const hookOwnerConnectionId = isWslRelay ? null : (args.connectionId ?? null)
const isWslPty =
pty?.isWsl === true ||
!!pty?.wslDistro ||
(pty ? this.wslDistroByPtyId.has(pty.ptyId) : false)
if (
!pty?.connected ||
(pty.connectionId ?? null) !== hookOwnerConnectionId ||
(isWslRelay && !isWslPty)
) {
return false
}
if (pty.connectionId && args.payload.state !== 'done') {
this.corroborateSshTypedAgent(pty.ptyId, args.payload.agentType, 'live-hook')
}
this.rememberPaneAgentIdentityHookSurface(pty.ptyId, args.payload.state)
return this.paneAgentIdentityObservations.observeEvidence(
this.getPaneAgentIdentityPtyContext(pty),
{
source: args.payload.state === 'done' ? 'completed-hook' : 'live-hook',
agent: args.payload.agentType
}
)
}
observeAcceptedPtyWrite(ptyId: string, data: string): void {
const pty = this.ptysById.get(ptyId)
if (!pty?.connected) {
return
}
if (this.paneAgentIdentityOwnedIncarnations.get(ptyId) === pty.incarnationId) {
this.paneAgentCommandTrackers.get(ptyId)?.reset()
return
}
if (this.hasFreshPaneAgentIdentitySurface(pty)) {
this.paneAgentCommandTrackers.get(ptyId)?.reset()
return
}
const tracker = this.paneAgentCommandTrackers.get(ptyId) ?? new ManualAgentCommandTracker()
this.paneAgentCommandTrackers.set(ptyId, tracker)
const [agent] = tracker.ingest(data)
if (!agent) {
return
}
if (pty.connectionId) {
this.rememberSshTypedAgentCandidate(ptyId, agent, pty, tracker.acceptedCommandCount)
return
}
const context = this.getPaneAgentIdentityPtyContext(pty)
if (
this.paneAgentIdentityObservations.attestRun(
context,
'typed',
agent,
`typed:${this.getPtyLifecycleGeneration(ptyId)}:${tracker.acceptedCommandCount}`
)
) {
this.paneAgentIdentityOwnedIncarnations.set(ptyId, pty.incarnationId)
}
}
private rememberSshTypedAgentCandidate(
ptyId: string,
agent: TuiAgent,
pty: RuntimePtyWorktreeRecord,
commandOrdinal: number
): void {
const previous = this.sshTypedAgentCandidates.get(ptyId)
if (!previous && this.sshTypedAgentCandidates.size >= MAX_PENDING_IDENTITY_OBSERVATIONS) {
this.paneAgentIdentityCensus.addCoverage('ssh', 'overflow')
return
}
if (previous) {
clearTimeout(previous.timer)
}
const timer = setTimeout(
() => this.clearSshTypedAgentCandidate(ptyId),
TITLE_CANDIDATE_WINDOW_MS
)
timer.unref?.()
this.sshTypedAgentCandidates.set(ptyId, {
agent,
incarnationId: pty.incarnationId,
lifecycleGeneration: this.getPtyLifecycleGeneration(ptyId),
commandOrdinal,
timer
})
}
private rememberPaneAgentIdentityHookSurface(ptyId: string, state: string): void {
if (state === 'done') {
this.paneAgentIdentityLiveHookObservedAtByPty.delete(ptyId)
return
}
this.paneAgentIdentityLiveHookObservedAtByPty.set(ptyId, Date.now())
}
private hasFreshPaneAgentIdentitySurface(pty: RuntimePtyWorktreeRecord): boolean {
if (pty.foregroundAgent || (pty.lastAgentStatusObservedLive && pty.lastAgentStatus !== null)) {
return true
}
const hookObservedAt = this.paneAgentIdentityLiveHookObservedAtByPty.get(pty.ptyId)
if (hookObservedAt === undefined) {
return false
}
if (Date.now() - hookObservedAt <= AGENT_STATUS_STALE_AFTER_MS) {
return true
}
this.paneAgentIdentityLiveHookObservedAtByPty.delete(pty.ptyId)
return false
}
private clearSshTypedAgentCandidate(ptyId: string): void {
const candidate = this.sshTypedAgentCandidates.get(ptyId)
if (candidate) {
clearTimeout(candidate.timer)
}
this.sshTypedAgentCandidates.delete(ptyId)
}
corroborateSshTypedAgent(
ptyId: string,
targetAgent: TuiAgent,
source: 'process' | 'live-hook' = 'process'
): boolean {
const candidate = this.sshTypedAgentCandidates.get(ptyId)
const pty = this.ptysById.get(ptyId)
if (
candidate?.agent !== targetAgent ||
!pty?.connected ||
!pty.connectionId ||
candidate.incarnationId !== pty.incarnationId ||
candidate.lifecycleGeneration !== this.getPtyLifecycleGeneration(ptyId)
) {
return false
}
this.clearSshTypedAgentCandidate(ptyId)
const context = this.getPaneAgentIdentityPtyContext(pty)
if (
!this.paneAgentIdentityObservations.attestRun(
context,
'typed',
targetAgent,
`typed:${candidate.lifecycleGeneration}:${candidate.commandOrdinal}`
)
) {
return false
}
this.paneAgentIdentityOwnedIncarnations.set(ptyId, pty.incarnationId)
if (source === 'process') {
this.paneAgentIdentityObservations.observeEvidence(context, {
source,
agent: targetAgent,
processProvenance: 'target-origin'
})
} else {
this.paneAgentIdentityObservations.observeEvidence(context, {
source,
agent: targetAgent
})
}
return true
}
shutdownPaneAgentIdentityCensus(emitTelemetry = true): void {
if (this.sshTypedAgentCandidates.size > 0) {
this.paneAgentIdentityCensus.addCoverage(
'ssh',
'truncated',
this.sshTypedAgentCandidates.size
)
}
this.paneAgentIdentityObservations.shutdown()
if (emitTelemetry) {
this.paneAgentIdentityCensus.flush()
}
this.paneAgentIdentityCensus.shutdown()
this.paneAgentCommandTrackers.clear()
this.paneAgentIdentityOwnedIncarnations.clear()
this.paneAgentIdentityLiveHookObservedAtByPty.clear()
for (const ptyId of this.sshTypedAgentCandidates.keys()) {
this.clearSshTypedAgentCandidate(ptyId)
}
}
// A worktree-scoped request answers for one host only, so name the hosts it
// skipped: absence from a scoped listing is not evidence a worker exited.
private buildTerminalListHostScope(
@@ -19362,6 +19703,7 @@ export class OrcaRuntimeService {
}
await assertTerminalInputWithinLimitWithYield(action.text)
await this.writeTerminalAction(pty.pty.ptyId, action, payload, options)
this.observeAcceptedPtyWrite(pty.pty.ptyId, payload)
return {
handle,
accepted: true,
@@ -19387,6 +19729,7 @@ export class OrcaRuntimeService {
}
await this.writeTerminalAction(leaf.ptyId, action, payload, options)
this.observeAcceptedPtyWrite(leaf.ptyId, payload)
return {
handle,
@@ -19861,6 +20204,7 @@ export class OrcaRuntimeService {
}
return
}
this.releasePaneAgentIdentityOwner(ptyId, current.incarnationId)
this.recordTerminalSideEffectFact(ptyId, { kind: 'agent-exited' })
})
}
@@ -19984,9 +20328,24 @@ export class OrcaRuntimeService {
const foregroundAgent = foregroundProcess
? (recognizeAgentProcess(foregroundProcess)?.agent ?? null)
: null
const isWsl = pty.isWsl || pty.wslDistro || this.wslDistroByPtyId.has(ptyId)
if (foregroundAgent && !isWsl) {
const processProvenance = pty.connectionId ? 'target-origin' : 'execution-host'
this.paneAgentIdentityObservations.observeEvidence(this.getPaneAgentIdentityPtyContext(pty), {
source: 'process',
agent: foregroundAgent,
processProvenance
})
if (pty.connectionId) {
this.corroborateSshTypedAgent(ptyId, foregroundAgent, 'process')
}
}
if (pty.foregroundAgent === foregroundAgent) {
return false
}
if (pty.foregroundAgent && !foregroundAgent) {
this.releasePaneAgentIdentityOwner(ptyId, pty.incarnationId)
}
pty.foregroundAgent = foregroundAgent
this.touchMobileSessionSnapshotsForPty(ptyId)
return true
@@ -29183,6 +29542,15 @@ export class OrcaRuntimeService {
pty.launchToken = launchToken ?? null
pty.launchIncarnationId = launchToken ? pty.incarnationId : null
pty.launchAgent = launchOpts.launchAgent ?? null
if (pty.launchAgent && launchToken) {
this.observePtyAgentLaunch(
pty,
launchOpts.resumeProviderSession || launchOpts.agentSessionClaim
? 'resume'
: 'orca-launch',
launchToken
)
}
}
pty.tabId = tabId
pty.paneKey = paneKey
@@ -33349,6 +33717,11 @@ export class OrcaRuntimeService {
pty.worktreeId = worktreeId
if (state.incarnationId !== undefined) {
if (pty.incarnationId && state.incarnationId && pty.incarnationId !== state.incarnationId) {
this.releasePaneAgentIdentityOwner(ptyId, pty.incarnationId)
this.paneAgentCommandTrackers.delete(ptyId)
this.paneAgentIdentityLiveHookObservedAtByPty.delete(ptyId)
this.clearSshTypedAgentCandidate(ptyId)
this.paneAgentIdentityObservations.exitOrRebind(ptyId, pty.incarnationId)
this.invalidatePtyIncarnationHandle(ptyId)
}
pty.incarnationId = state.incarnationId
@@ -33839,7 +34212,13 @@ export class OrcaRuntimeService {
private dropDisconnectedPtyRecord(ptyId: string): void {
// Why: pruning can remove a PTY without the normal exit callback.
const pty = this.ptysById.get(ptyId)
this.releasePaneAgentIdentityOwner(ptyId, pty?.incarnationId)
this.paneAgentIdentityObservations.exitOrRebind(ptyId, pty?.incarnationId)
this.advancePtyLifecycleGeneration(ptyId)
this.paneAgentCommandTrackers.delete(ptyId)
this.paneAgentIdentityLiveHookObservedAtByPty.delete(ptyId)
this.clearSshTypedAgentCandidate(ptyId)
this.pairedRendererSessionOwnedPtyIds.delete(ptyId)
this.ptysById.delete(ptyId)
this.recentPtyOutputById.delete(ptyId)
@@ -0,0 +1,511 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
import { track } from '../telemetry/client'
import { PaneAgentIdentityCensus } from '../telemetry/pane-agent-identity-census'
vi.mock('../telemetry/client', () => ({ track: vi.fn() }))
async function availabilityRows(runtime: OrcaRuntimeService) {
return ((await runtime.listTerminals()).agentIdentityAvailability?.rows ?? []).map((row) => ({
hostKind: row[0],
launchMode: row[1],
attestedRuns: row[2],
noEvidence: row[3],
titleOnly: row[4],
identityNull: row[5],
ambiguousTopRank: row[6]
}))
}
describe('runtime pane identity census wiring', () => {
afterEach(() => {
vi.useRealTimers()
})
it('publishes paired WSL typed views without a false native row', async () => {
vi.useFakeTimers()
vi.mocked(track).mockClear()
const runtime = new OrcaRuntimeService(undefined, undefined, {
paneAgentIdentityCensus: new PaneAgentIdentityCensus()
})
runtime.registerPty('pty-wsl', 'folder:/tmp', null, undefined, true)
runtime.observeAcceptedPtyWrite('pty-wsl', 'codex\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'wsl-host', launchMode: 'typed', attestedRuns: 1 }),
expect.objectContaining({ hostKind: 'wsl-distro', launchMode: 'typed', attestedRuns: 1 })
])
runtime.shutdownPaneAgentIdentityCensus()
expect(track).toHaveBeenCalledTimes(1)
expect(track).toHaveBeenCalledWith('pane_agent_identity_availability', {
rows: expect.arrayContaining([
expect.objectContaining({ host_kind: 'wsl-host', attested_runs: 1 }),
expect.objectContaining({ host_kind: 'wsl-distro', attested_runs: 1 })
])
})
})
it('does not relabel a Windows-side WSL foreground read as distro process provenance', async () => {
vi.useFakeTimers()
const census = new PaneAgentIdentityCensus({ emit: null })
const record = vi.spyOn(census, 'record')
const runtime = new OrcaRuntimeService(undefined, undefined, {
paneAgentIdentityCensus: census
})
runtime.registerPty('pty-wsl', 'folder:/tmp', null, undefined, true)
runtime.setPtyController({ getForegroundProcess: async () => 'codex' } as never)
runtime.observeAcceptedPtyWrite('pty-wsl', 'codex\r')
await (
runtime as unknown as {
loadPtyForegroundAgentFromController: (ptyId: string) => Promise<boolean>
}
).loadPtyForegroundAgentFromController('pty-wsl')
vi.advanceTimersByTime(5_000)
expect(record).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ hostKind: 'wsl-host', sourceMask: 4 })
)
expect(record).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ hostKind: 'wsl-distro', sourceMask: 4 })
)
expect(census.snapshot().rows).toEqual([
['wsl-host', 'typed', 1, 0, 0, 0, 0],
['wsl-distro', 'typed', 1, 0, 0, 0, 0]
])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('requires same-incarnation SSH target corroboration', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const binding = {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002',
incarnationId: 'incarnation-1'
}
runtime.registerPty('pty-ssh', 'folder:/tmp', 'ssh-1', binding)
runtime.observeAcceptedPtyWrite('pty-ssh', 'codex\r')
expect(runtime.corroborateSshTypedAgent('pty-ssh', 'claude')).toBe(false)
expect(await availabilityRows(runtime)).toEqual([])
runtime.registerPty('pty-ssh', 'folder:/tmp', 'ssh-1', {
...binding,
incarnationId: 'incarnation-2'
})
expect(runtime.corroborateSshTypedAgent('pty-ssh', 'codex')).toBe(false)
expect(await availabilityRows(runtime)).toEqual([])
runtime.observeAcceptedPtyWrite('pty-ssh', 'codex\r')
runtime.onPtyData(
'pty-ssh',
'\x1b]9999;{"state":"working","agentType":"codex"}\x07',
Date.now()
)
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'ssh', launchMode: 'typed', attestedRuns: 1 })
])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('does not let a replayed SSH hook certify a typed run', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const tabId = '00000000-0000-4000-8000-000000000001'
const leafId = '00000000-0000-4000-8000-000000000002'
runtime.registerPty('pty-ssh', 'folder:/tmp', 'ssh-1', {
tabId,
leafId,
incarnationId: 'incarnation-1'
})
runtime.observeAcceptedPtyWrite('pty-ssh', 'codex\r')
expect(
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${leafId}`,
connectionId: 'ssh-1',
isReplay: true,
payload: { state: 'working', agentType: 'codex' }
})
).toBe(false)
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('accepts live WSL-relayed hook evidence only for its local WSL pane', async () => {
vi.useFakeTimers()
const census = new PaneAgentIdentityCensus({ emit: null })
const record = vi.spyOn(census, 'record')
const runtime = new OrcaRuntimeService(undefined, undefined, {
paneAgentIdentityCensus: census
})
const tabId = '00000000-0000-4000-8000-000000000001'
const wslLeafId = '00000000-0000-4000-8000-000000000002'
const nativeLeafId = '00000000-0000-4000-8000-000000000003'
runtime.registerPty(
'pty-wsl',
'folder:/tmp',
null,
{
tabId,
leafId: wslLeafId,
incarnationId: 'incarnation-1'
},
true
)
runtime.registerPty('pty-native', 'folder:/tmp', null, {
tabId,
leafId: nativeLeafId,
incarnationId: 'incarnation-1'
})
runtime.observeAcceptedPtyWrite('pty-wsl', 'codex\r')
runtime.observeAcceptedPtyWrite('pty-native', 'codex\r')
expect(
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${nativeLeafId}`,
connectionId: 'wsl:LiveDistro',
payload: { state: 'working', agentType: 'codex' }
})
).toBe(false)
expect(
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${wslLeafId}`,
connectionId: 'wsl:LiveDistro',
payload: { state: 'working', agentType: 'codex' }
})
).toBe(true)
vi.advanceTimersByTime(5_000)
expect(record).toHaveBeenCalledWith(
expect.objectContaining({ hostKind: 'wsl-distro', sourceMask: 5 })
)
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('records launch and resume through renderer registration authority', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const binding = {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002',
incarnationId: 'incarnation-1'
}
runtime.registerPty('pty-launch', 'folder:/tmp', null, {
...binding,
agentLaunchAuthority: {
launchToken: 'launch-token',
launchAgent: 'codex',
launchMode: 'orca-launch'
}
})
runtime.registerPty('pty-resume', 'folder:/tmp', null, {
...binding,
leafId: '00000000-0000-4000-8000-000000000003',
agentLaunchAuthority: {
launchToken: 'resume-token',
launchAgent: 'claude',
launchMode: 'resume'
}
})
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual(
expect.arrayContaining([
expect.objectContaining({ hostKind: 'native', launchMode: 'orca-launch', attestedRuns: 1 }),
expect.objectContaining({ hostKind: 'native', launchMode: 'resume', attestedRuns: 1 })
])
)
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('does not guess a launch mode for an unstructured legacy terminal create', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
vi.spyOn(
runtime as unknown as {
resolveTerminalWorkspaceLaunchScope: (selector: string) => Promise<unknown>
},
'resolveTerminalWorkspaceLaunchScope'
).mockResolvedValue({
id: 'folder:/tmp',
path: '/tmp',
connectionId: null,
repo: null,
folderWorkspace: null
})
runtime.setPtyController({
spawn: vi.fn().mockResolvedValue({ id: 'pty-legacy', incarnationId: 'incarnation-1' }),
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
await runtime.createTerminal('id:folder:/tmp', {
command: 'codex resume provider-session-1',
launchAgent: 'codex'
})
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('does not classify live-agent prompt text as a typed launch', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const binding = {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002',
incarnationId: 'incarnation-1',
agentLaunchAuthority: {
launchToken: 'launch-token',
launchAgent: 'codex' as const,
launchMode: 'orca-launch' as const
}
}
runtime.registerPty('pty-native', 'folder:/tmp', null, binding)
runtime.observeAcceptedPtyWrite('pty-native', 'claude\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'native', launchMode: 'orca-launch', attestedRuns: 1 })
])
runtime.emitDaemonPtyTransientFact('pty-native', {
kind: 'command-finished',
exitCode: 0
})
runtime.observeAcceptedPtyWrite('pty-native', 'claude\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual(
expect.arrayContaining([
expect.objectContaining({ launchMode: 'orca-launch', attestedRuns: 1 }),
expect.objectContaining({ launchMode: 'typed', attestedRuns: 1 })
])
)
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('does not classify prompt text while a fresh hook owns an otherwise unattributed pane', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const tabId = '00000000-0000-4000-8000-000000000001'
const leafId = '00000000-0000-4000-8000-000000000002'
runtime.registerPty('pty-native', 'folder:/tmp', null, {
tabId,
leafId,
incarnationId: 'incarnation-1'
})
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${leafId}`,
payload: { state: 'working', agentType: 'codex' }
})
runtime.observeAcceptedPtyWrite('pty-native', 'claude\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('does not carry a partial typed command across a PTY incarnation rebind', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const binding = {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002'
}
runtime.registerPty('pty-native', 'folder:/tmp', null, {
...binding,
incarnationId: 'incarnation-1'
})
runtime.observeAcceptedPtyWrite('pty-native', 'co')
runtime.registerPty('pty-native', 'folder:/tmp', null, {
...binding,
incarnationId: 'incarnation-2'
})
runtime.observeAcceptedPtyWrite('pty-native', 'dex\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([])
runtime.observeAcceptedPtyWrite('pty-native', 'codex\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'native', launchMode: 'typed', attestedRuns: 1 })
])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('attests only the first agent command in one accepted multiline write', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const tabId = '00000000-0000-4000-8000-000000000001'
const leafId = '00000000-0000-4000-8000-000000000002'
runtime.registerPty('pty-ssh', 'folder:/tmp', 'ssh-1', {
tabId,
leafId,
incarnationId: 'incarnation-1'
})
runtime.observeAcceptedPtyWrite('pty-ssh', 'claude\rcodex\r')
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${leafId}`,
connectionId: 'ssh-1',
payload: { state: 'working', agentType: 'claude' }
})
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'ssh', launchMode: 'typed', attestedRuns: 1 })
])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('collects primary hook-server evidence for the exact live pane', async () => {
vi.useFakeTimers()
const census = new PaneAgentIdentityCensus({ emit: null })
const record = vi.spyOn(census, 'record')
const runtime = new OrcaRuntimeService(undefined, undefined, {
paneAgentIdentityCensus: census
})
const tabId = '00000000-0000-4000-8000-000000000001'
const leafId = '00000000-0000-4000-8000-000000000002'
runtime.registerPty('pty-native', 'folder:/tmp', null, {
tabId,
leafId,
incarnationId: 'incarnation-1',
agentLaunchAuthority: {
launchToken: 'launch-token',
launchAgent: 'codex',
launchMode: 'orca-launch'
}
})
expect(
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${leafId}`,
connectionId: 'wrong-host',
payload: { state: 'working', agentType: 'codex' }
})
).toBe(false)
expect(
runtime.observeAgentHookStatus({
paneKey: `${tabId}:${leafId}`,
payload: { state: 'working', agentType: 'codex' }
})
).toBe(true)
vi.advanceTimersByTime(5_000)
expect(record).toHaveBeenCalledWith(
expect.objectContaining({ hostKind: 'native', sourceMask: 5 })
)
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('normalizes headless authority observations to one relay row per run', async () => {
vi.useFakeTimers()
const census = new PaneAgentIdentityCensus({ emit: null, snapshotHostKind: 'relay' })
const runtime = new OrcaRuntimeService(undefined, undefined, {
paneAgentIdentityCensus: census
})
runtime.registerPty('pty-wsl', 'folder:/tmp', null, undefined, true)
runtime.observeAcceptedPtyWrite('pty-wsl', 'codex\r')
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'relay', launchMode: 'typed', attestedRuns: 1 })
])
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('collects current process, hook, and title evidence through PTY production paths', async () => {
vi.useFakeTimers()
const census = new PaneAgentIdentityCensus({ emit: null })
const record = vi.spyOn(census, 'record')
const runtime = new OrcaRuntimeService(undefined, undefined, {
paneAgentIdentityCensus: census
})
runtime.registerPty('pty-native', 'folder:/tmp', null, {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002',
incarnationId: 'incarnation-1'
})
runtime.setPtyController({ getForegroundProcess: async () => 'codex' } as never)
runtime.observeAcceptedPtyWrite('pty-native', 'codex\r')
await (
runtime as unknown as {
loadPtyForegroundAgentFromController: (ptyId: string) => Promise<boolean>
}
).loadPtyForegroundAgentFromController('pty-native')
runtime.onPtyData(
'pty-native',
'\x1b]9999;{"state":"working","agentType":"codex"}\x07' +
'\x1b]9999;{"state":"done","agentType":"codex"}\x07' +
'\x1b]0;Codex working\x07',
Date.now()
)
vi.advanceTimersByTime(5_000)
expect(record).toHaveBeenCalledWith({
hostKind: 'native',
launchMode: 'typed',
sourceMask: 79,
identityNull: false,
ambiguousTopRank: false
})
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('freezes an accepted run on a confirmed production exit', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
runtime.registerPty('pty-native', 'folder:/tmp', null, {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002',
incarnationId: 'incarnation-1'
})
runtime.observeAcceptedPtyWrite('pty-native', 'codex\r')
runtime.onPtyExit('pty-native', 0, 'incarnation-1', { providerExitObserved: true })
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'native', launchMode: 'typed', attestedRuns: 1 })
])
vi.advanceTimersByTime(5_000)
expect(await availabilityRows(runtime)).toHaveLength(1)
runtime.shutdownPaneAgentIdentityCensus(false)
})
it('publishes title-candidate coverage and freezes a run on production rebind', async () => {
vi.useFakeTimers()
const runtime = new OrcaRuntimeService()
const binding = {
tabId: '00000000-0000-4000-8000-000000000001',
leafId: '00000000-0000-4000-8000-000000000002',
incarnationId: 'incarnation-1'
}
runtime.registerPty('pty-title', 'folder:/tmp', null, binding)
runtime.onPtyData('pty-title', '\x1b]0;Codex working\x07', Date.now())
vi.advanceTimersByTime(30_000)
expect((await runtime.listTerminals()).agentIdentityAvailability?.candidateCoverage).toEqual([
['native', 1]
])
runtime.registerPty('pty-rebind', 'folder:/tmp', null, binding)
runtime.observeAcceptedPtyWrite('pty-rebind', 'codex\r')
runtime.registerPty('pty-rebind', 'folder:/tmp', null, {
...binding,
incarnationId: 'incarnation-2'
})
expect(await availabilityRows(runtime)).toEqual([
expect.objectContaining({ hostKind: 'native', launchMode: 'typed', attestedRuns: 1 })
])
runtime.shutdownPaneAgentIdentityCensus(false)
})
})
@@ -0,0 +1,160 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { PaneAgentIdentityAvailability } from '../../shared/pane-agent-identity-evidence'
import { MAX_LIVE_FINALIZED_RUN_KEYS } from '../../shared/pane-agent-identity-availability'
import { PaneAgentIdentityObservationAuthority } from './pane-agent-identity-observation-authority'
function createHarness() {
const records: PaneAgentIdentityAvailability[] = []
const coverage: { hostKind: string; reason: string; amount: number }[] = []
const authority = new PaneAgentIdentityObservationAuthority('authority', {
record: (row) => records.push(row),
addCoverage: (hostKind, reason, amount = 1) => coverage.push({ hostKind, reason, amount })
})
return { authority, records, coverage }
}
describe('PaneAgentIdentityObservationAuthority', () => {
afterEach(() => {
vi.useRealTimers()
})
it('settles once after five seconds and dedupes the finalized attestation', () => {
vi.useFakeTimers()
const { authority, records } = createHarness()
const context = { ptyId: 'pty', incarnationId: 'inc-1', hostKinds: ['native'] as const }
expect(authority.attestRun(context, 'typed', 'codex', 'typed:1')).toBe(true)
vi.advanceTimersByTime(4_999)
expect(records).toEqual([])
vi.advanceTimersByTime(1)
expect(records).toEqual([
expect.objectContaining({ hostKind: 'native', launchMode: 'typed', sourceMask: 4 })
])
expect(authority.attestRun(context, 'typed', 'codex', 'typed:1')).toBe(true)
vi.advanceTimersByTime(5_000)
expect(records).toHaveLength(1)
expect(authority.attestRun(context, 'typed', 'claude', 'typed:2')).toBe(true)
vi.advanceTimersByTime(5_000)
expect(records).toHaveLength(2)
authority.shutdown()
})
it('publishes paired WSL facts with execution evidence only on the distro view', () => {
vi.useFakeTimers()
const { authority, records } = createHarness()
const context = {
ptyId: 'pty',
incarnationId: 'inc-1',
hostKinds: ['wsl-host', 'wsl-distro'] as const
}
authority.attestRun(context, 'orca-launch', 'codex', 'launch:1')
authority.observeEvidence(context, {
source: 'process',
agent: 'codex',
processProvenance: 'distro-origin'
})
authority.observeEvidence(context, { source: 'live-hook', agent: 'codex' })
authority.observeTitle(context, 'codex')
vi.advanceTimersByTime(5_000)
expect(records).toEqual([
expect.objectContaining({ hostKind: 'wsl-host', sourceMask: 4 }),
expect.objectContaining({ hostKind: 'wsl-distro', sourceMask: 71 })
])
authority.shutdown()
})
it('freezes pending runs immediately on exit and truncates only live windows on shutdown', () => {
vi.useFakeTimers()
const { authority, records, coverage } = createHarness()
const first = { ptyId: 'pty-1', incarnationId: 'inc-1', hostKinds: ['ssh'] as const }
const second = { ptyId: 'pty-2', incarnationId: 'inc-1', hostKinds: ['native'] as const }
authority.attestRun(first, 'resume', 'claude', 'resume:1')
authority.exitOrRebind(first.ptyId, first.incarnationId)
expect(records).toEqual([
expect.objectContaining({ hostKind: 'ssh', launchMode: 'resume', sourceMask: 20 })
])
authority.attestRun(second, 'typed', 'codex', 'typed:1')
authority.shutdown()
expect(coverage).toContainEqual({ hostKind: 'native', reason: 'truncated', amount: 1 })
vi.advanceTimersByTime(5_000)
expect(records).toHaveLength(1)
})
it('records a title candidate only after its authority window and cancels it on attestation', () => {
vi.useFakeTimers()
const { authority, coverage } = createHarness()
const context = { ptyId: 'pty', incarnationId: 'inc-1', hostKinds: ['ssh'] as const }
authority.observeTitle(context, 'codex')
vi.advanceTimersByTime(29_999)
expect(coverage).toEqual([])
vi.advanceTimersByTime(1)
expect(coverage).toEqual([{ hostKind: 'ssh', reason: 'candidate', amount: 1 }])
authority.exitOrRebind(context.ptyId, context.incarnationId)
authority.observeTitle(context, 'codex')
authority.attestRun(context, 'typed', 'codex', 'typed:1')
vi.advanceTimersByTime(30_000)
expect(coverage).toHaveLength(1)
authority.observeTitle(context, 'codex')
vi.advanceTimersByTime(30_000)
expect(coverage).toHaveLength(1)
authority.shutdown()
})
it('releases a title candidate after the finalized-key cap rejects it', () => {
vi.useFakeTimers()
const { authority, coverage } = createHarness()
for (let index = 0; index < MAX_LIVE_FINALIZED_RUN_KEYS; index += 1) {
authority.attestRun(
{ ptyId: `run-${index}`, incarnationId: 'inc-1', hostKinds: ['native'] },
'typed',
'codex',
`typed:${index}`
)
}
vi.advanceTimersByTime(5_000)
const candidate = {
ptyId: 'candidate',
incarnationId: 'inc-1',
hostKinds: ['native'] as const
}
authority.observeTitle(candidate, 'codex')
vi.advanceTimersByTime(30_000)
expect(coverage).toContainEqual({ hostKind: 'native', reason: 'overflow', amount: 1 })
authority.observeTitle(candidate, 'codex')
vi.advanceTimersByTime(30_000)
authority.shutdown()
expect(coverage).toEqual([{ hostKind: 'native', reason: 'overflow', amount: 1 }])
})
it('tombstones a title candidate when the pending-window cap rejects it', () => {
vi.useFakeTimers()
const { authority, coverage } = createHarness()
for (let index = 0; index < MAX_LIVE_FINALIZED_RUN_KEYS; index += 1) {
authority.attestRun(
{ ptyId: `run-${index}`, incarnationId: 'inc-1', hostKinds: ['native'] },
'typed',
'codex',
`typed:${index}`
)
}
const candidate = {
ptyId: 'candidate',
incarnationId: 'inc-1',
hostKinds: ['native'] as const
}
authority.observeTitle(candidate, 'codex')
authority.observeTitle(candidate, 'codex')
expect(coverage).toEqual([{ hostKind: 'native', reason: 'overflow', amount: 1 }])
authority.shutdown()
})
})
@@ -0,0 +1,301 @@
import type { PtyIncarnationId } from '../../shared/pty-incarnation'
import type { TuiAgent } from '../../shared/tui-agent'
import {
PaneAgentIdentityObservationScheduler,
type PaneAgentIdentityObservationSchedulerDeps
} from '../../shared/pane-agent-identity-availability'
import {
reducePaneAgentIdentityAvailability,
type PaneAgentIdentityAvailability,
type PaneAgentIdentityEvidenceFact,
type PaneAgentIdentityHostKind,
type PaneAgentIdentityLaunchMode
} from '../../shared/pane-agent-identity-evidence'
import type { PaneAgentRunKey } from '../../shared/pane-agent-identity-resolver'
import {
appendEvidenceToActiveRun,
titleHostKind,
uniqueHostKinds,
type ActiveRun,
type Candidate,
type EvidenceObservation,
type PtyObservationState
} from './pane-agent-identity-observation-model'
type CensusCoverageReason = 'candidate' | 'overflow' | 'truncated'
export type PaneAgentIdentityObservationSink = {
record(observation: PaneAgentIdentityAvailability): void
addCoverage(
hostKind: PaneAgentIdentityHostKind,
reason: CensusCoverageReason,
amount?: number
): void
}
export type PaneAgentIdentityPtyContext = {
ptyId: string
incarnationId: PtyIncarnationId | null
hostKinds: readonly PaneAgentIdentityHostKind[]
}
/** Owns run/candidate windows and reduces authority facts before they reach a sink. */
export class PaneAgentIdentityObservationAuthority {
private readonly states = new Map<string, PtyObservationState>()
private readonly runsByKey = new Map<string, ActiveRun>()
private readonly candidatesByKey = new Map<string, Candidate>()
private readonly scheduler: PaneAgentIdentityObservationScheduler
constructor(
private readonly authorityId: string,
private readonly sink: PaneAgentIdentityObservationSink,
schedulerDeps: Pick<PaneAgentIdentityObservationSchedulerDeps, 'now'> = {}
) {
this.scheduler = new PaneAgentIdentityObservationScheduler({
...schedulerDeps,
onRunFreeze: (key) => this.freezeRun(key),
onTitleCandidateFreeze: (hostKind, key) => this.freezeCandidate(hostKind, key),
onFinalizedOverflow: (kind, hostKind, key) =>
this.recordFinalizedOverflow(kind, hostKind, key)
})
}
attestRun(
context: PaneAgentIdentityPtyContext,
launchMode: PaneAgentIdentityLaunchMode,
owner: TuiAgent,
attestationId: string
): boolean {
const state = this.getState(context)
if (state.lastAttestationId === attestationId) {
return true
}
if (state.activeRun) {
const previousRun = state.activeRun
if (!this.scheduler.freezeRun(previousRun.key)) {
for (const hostKind of previousRun.hostKinds) {
this.sink.addCoverage(hostKind, 'overflow')
}
this.runsByKey.delete(previousRun.key)
state.activeRun = null
}
}
if (state.candidate) {
this.scheduler.cancelCandidate(state.candidate.key)
this.scheduler.exitOrRebind(state.candidate.key)
this.candidatesByKey.delete(state.candidate.key)
state.candidate = null
}
if (state.finalizedRunKey) {
this.scheduler.exitOrRebind(state.finalizedRunKey)
state.finalizedRunKey = null
}
if (state.finalizedCandidateKey) {
this.scheduler.exitOrRebind(state.finalizedCandidateKey)
state.finalizedCandidateKey = null
}
state.ordinal += 1
const run: PaneAgentRunKey = {
authorityId: `${this.authorityId}:${context.ptyId}:${context.incarnationId ?? 'unknown'}`,
incarnation: state.ordinal
}
const key = `${run.authorityId}:${run.incarnation}`
const hostKinds = uniqueHostKinds(context.hostKinds)
const factsByHostKind = new Map<PaneAgentIdentityHostKind, PaneAgentIdentityEvidenceFact[]>()
for (const hostKind of hostKinds) {
const facts: PaneAgentIdentityEvidenceFact[] = [{ source: 'launch', agent: owner, run }]
if (launchMode === 'resume') {
facts.push({ source: 'sleeping-session', agent: owner, run })
}
factsByHostKind.set(hostKind, facts)
}
const activeRun: ActiveRun = {
key,
ptyId: context.ptyId,
run,
launchMode,
owner,
hostKinds,
factsByHostKind
}
state.lastAttestationId = attestationId
state.activeRun = activeRun
this.runsByKey.set(key, activeRun)
if (this.scheduler.attestRun(key)) {
return true
}
this.runsByKey.delete(key)
state.activeRun = null
for (const hostKind of hostKinds) {
this.sink.addCoverage(hostKind, 'overflow')
}
return false
}
observeEvidence(context: PaneAgentIdentityPtyContext, evidence: EvidenceObservation): boolean {
const state = this.states.get(context.ptyId)
const activeRun = state?.activeRun
if (!state || state.incarnationId !== context.incarnationId || !activeRun) {
return false
}
return appendEvidenceToActiveRun(activeRun, evidence)
}
observeTitle(context: PaneAgentIdentityPtyContext, agent: TuiAgent): void {
if (this.observeEvidence(context, { source: 'title', agent })) {
return
}
const state = this.getState(context)
if (state.ordinal > 0 || state.candidate || state.finalizedCandidateKey) {
return
}
const hostKind = titleHostKind(context.hostKinds)
const candidate = {
key: `${this.authorityId}:${context.ptyId}:${context.incarnationId ?? 'unknown'}:candidate`,
ptyId: context.ptyId,
hostKind
}
if (!this.scheduler.scheduleTitleCandidate(candidate.key, candidate.hostKind)) {
this.sink.addCoverage(hostKind, 'overflow')
state.finalizedCandidateKey = candidate.key
return
}
state.candidate = candidate
this.candidatesByKey.set(candidate.key, candidate)
}
exitOrRebind(ptyId: string, incarnationId?: PtyIncarnationId | null): void {
const state = this.states.get(ptyId)
if (!state || (incarnationId !== undefined && state.incarnationId !== incarnationId)) {
return
}
if (state.activeRun && this.scheduler.isPendingRun(state.activeRun.key)) {
if (!this.scheduler.freezeRun(state.activeRun.key)) {
for (const hostKind of state.activeRun.hostKinds) {
this.sink.addCoverage(hostKind, 'overflow')
}
}
}
if (state.candidate && this.scheduler.isPendingCandidate(state.candidate.key)) {
if (!this.scheduler.freezeTitleCandidate(state.candidate.key, state.candidate.hostKind)) {
this.sink.addCoverage(state.candidate.hostKind, 'overflow')
}
}
if (state.activeRun) {
this.scheduler.exitOrRebind(state.activeRun.key)
this.runsByKey.delete(state.activeRun.key)
}
if (state.finalizedRunKey) {
this.scheduler.exitOrRebind(state.finalizedRunKey)
}
if (state.candidate) {
this.scheduler.exitOrRebind(state.candidate.key)
this.candidatesByKey.delete(state.candidate.key)
}
if (state.finalizedCandidateKey) {
this.scheduler.exitOrRebind(state.finalizedCandidateKey)
}
this.states.delete(ptyId)
}
shutdown(): void {
for (const state of this.states.values()) {
if (state.activeRun && this.scheduler.isPendingRun(state.activeRun.key)) {
for (const hostKind of state.activeRun.hostKinds) {
this.sink.addCoverage(hostKind, 'truncated')
}
}
if (state.candidate && this.scheduler.isPendingCandidate(state.candidate.key)) {
this.sink.addCoverage(state.candidate.hostKind, 'truncated')
}
}
this.scheduler.shutdown()
this.states.clear()
this.runsByKey.clear()
this.candidatesByKey.clear()
}
private getState(context: PaneAgentIdentityPtyContext): PtyObservationState {
const existing = this.states.get(context.ptyId)
if (existing && existing.incarnationId === context.incarnationId) {
return existing
}
if (existing) {
this.exitOrRebind(context.ptyId, existing.incarnationId)
}
const state: PtyObservationState = {
incarnationId: context.incarnationId,
ordinal: 0,
lastAttestationId: null,
activeRun: null,
finalizedRunKey: null,
candidate: null,
finalizedCandidateKey: null
}
this.states.set(context.ptyId, state)
return state
}
private freezeRun(key: string): void {
const run = this.runsByKey.get(key)
if (!run) {
return
}
for (const hostKind of run.hostKinds) {
this.sink.record(
reducePaneAgentIdentityAvailability(hostKind, run.launchMode, {
facts: run.factsByHostKind.get(hostKind) ?? [],
currentRun: run.run,
owner: { agent: run.owner, run: run.run }
})
)
}
this.runsByKey.delete(key)
const state = this.states.get(run.ptyId)
if (state?.activeRun?.key === key) {
state.activeRun = null
state.finalizedRunKey = key
}
}
private freezeCandidate(hostKind: PaneAgentIdentityHostKind, key: string): void {
this.sink.addCoverage(hostKind, 'candidate')
const candidate = this.candidatesByKey.get(key)
this.candidatesByKey.delete(key)
const candidateState = candidate ? this.states.get(candidate.ptyId) : undefined
if (candidateState) {
candidateState.candidate = null
candidateState.finalizedCandidateKey = key
}
}
private recordFinalizedOverflow(
kind: 'run' | 'title-candidate',
hostKind: PaneAgentIdentityHostKind | undefined,
key: string
): void {
if (kind === 'title-candidate' && hostKind) {
this.sink.addCoverage(hostKind, 'overflow')
const candidate = this.candidatesByKey.get(key)
this.candidatesByKey.delete(key)
const state = candidate ? this.states.get(candidate.ptyId) : undefined
if (state?.candidate?.key === key) {
state.candidate = null
state.finalizedCandidateKey = key
}
return
}
const run = this.runsByKey.get(key)
if (!run) {
return
}
for (const runHostKind of run.hostKinds) {
this.sink.addCoverage(runHostKind, 'overflow')
}
this.runsByKey.delete(key)
const state = this.states.get(run.ptyId)
if (state?.activeRun?.key === key) {
state.activeRun = null
}
}
}
@@ -0,0 +1,119 @@
import type { PtyIncarnationId } from '../../shared/pty-incarnation'
import type { TuiAgent } from '../../shared/tui-agent'
import type {
PaneAgentIdentityEvidenceFact,
PaneAgentIdentityHostKind,
PaneAgentIdentityLaunchMode
} from '../../shared/pane-agent-identity-evidence'
import type {
PaneAgentEvidenceSource,
PaneAgentRunKey
} from '../../shared/pane-agent-identity-resolver'
export type ActiveRun = {
key: string
ptyId: string
run: PaneAgentRunKey
launchMode: PaneAgentIdentityLaunchMode
owner: TuiAgent
hostKinds: readonly PaneAgentIdentityHostKind[]
factsByHostKind: Map<PaneAgentIdentityHostKind, PaneAgentIdentityEvidenceFact[]>
}
export type Candidate = {
key: string
ptyId: string
hostKind: PaneAgentIdentityHostKind
}
export type PtyObservationState = {
incarnationId: PtyIncarnationId | null
ordinal: number
lastAttestationId: string | null
activeRun: ActiveRun | null
finalizedRunKey: string | null
candidate: Candidate | null
finalizedCandidateKey: string | null
}
export type EvidenceObservation =
| {
source: 'process'
agent: TuiAgent
processProvenance: 'execution-host' | 'distro-origin' | 'target-origin'
}
| {
source: Exclude<
PaneAgentEvidenceSource,
'launch' | 'sleeping-session' | 'sibling' | 'process'
>
agent: TuiAgent
processProvenance?: never
}
export const uniqueHostKinds = (
hostKinds: readonly PaneAgentIdentityHostKind[]
): PaneAgentIdentityHostKind[] => [...new Set(hostKinds)]
export function appendEvidenceToActiveRun(
activeRun: ActiveRun,
evidence: EvidenceObservation
): boolean {
for (const hostKind of activeRun.hostKinds) {
if (!evidenceBelongsToHost(hostKind, evidence)) {
continue
}
const facts = activeRun.factsByHostKind.get(hostKind)
if (!facts) {
continue
}
const fact: PaneAgentIdentityEvidenceFact =
evidence.source === 'process'
? {
source: evidence.source,
agent: evidence.agent,
run: activeRun.run,
executionHostProvenance: evidence.processProvenance
}
: { source: evidence.source, agent: evidence.agent, run: activeRun.run }
if (
!facts.some((existing) => existing.source === fact.source && existing.agent === fact.agent)
) {
facts.push(fact)
}
}
return true
}
export function titleHostKind(
hostKinds: readonly PaneAgentIdentityHostKind[]
): PaneAgentIdentityHostKind {
if (hostKinds.includes('ssh')) {
return 'ssh'
}
if (hostKinds.includes('wsl-distro')) {
return 'wsl-distro'
}
if (hostKinds.includes('relay')) {
return 'relay'
}
return 'native'
}
function evidenceBelongsToHost(
hostKind: PaneAgentIdentityHostKind,
evidence: EvidenceObservation
): boolean {
if (hostKind === 'wsl-host') {
return false
}
if (evidence.source !== 'process') {
return true
}
return (
(hostKind === 'native' && evidence.processProvenance === 'execution-host') ||
(hostKind === 'wsl-distro' && evidence.processProvenance === 'distro-origin') ||
(hostKind === 'ssh' && evidence.processProvenance === 'target-origin') ||
hostKind === 'relay'
)
}
@@ -177,6 +177,27 @@ describe('sendTerminal absence gate for leaf-branch writes', () => {
expect(write).toHaveBeenCalledWith(STALE_PTY_ID, 'ping')
})
it('observes a recognized terminal.send command only after its accepted provider write', async () => {
vi.useFakeTimers()
try {
const { runtime, handle } = await makeRuntimeWithLeafHandle({
hasPty: (ptyId) => ptyId === STALE_PTY_ID
})
const send = runtime.sendTerminal(handle, { text: 'codex', enter: true })
await vi.advanceTimersByTimeAsync(500)
await send
vi.advanceTimersByTime(5_000)
expect((await runtime.listTerminals()).agentIdentityAvailability?.rows).toEqual([
['native', 'typed', 1, 0, 0, 0, 0]
])
runtime.shutdownPaneAgentIdentityCensus(false)
} finally {
vi.useRealTimers()
}
})
it('reuses a proven-absent verdict across repeated sends instead of re-probing', async () => {
const probe = vi.fn(async () => false)
const { runtime, handle } = await makeRuntimeWithLeafHandle({ probePtyLiveness: probe })
@@ -0,0 +1,95 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { PaneAgentIdentityAuthoritySnapshot } from '../../shared/pane-agent-identity-availability'
import type {
PaneAgentIdentityHostKind,
PaneAgentIdentityLaunchMode
} from '../../shared/pane-agent-identity-evidence'
import { track } from './client'
import {
createDesktopPaneAgentIdentityCensus,
createHeadlessPaneAgentIdentityCensus
} from './pane-agent-identity-census-composition'
vi.mock('./client', () => ({ track: vi.fn() }))
describe('pane identity census composition', () => {
it('constructs the desktop census as a telemetry emitter', () => {
const census = createDesktopPaneAgentIdentityCensus()
census.record({
hostKind: 'native',
launchMode: 'typed',
sourceMask: 4,
identityNull: false,
ambiguousTopRank: false
})
census.flush()
expect(track).toHaveBeenCalledWith('pane_agent_identity_availability', {
rows: [expect.objectContaining({ kind: 'aggregate', host_kind: 'native' })]
})
census.shutdown()
})
it('constructs the headless census as one snapshot-only relay authority', () => {
vi.mocked(track).mockClear()
const census = createHeadlessPaneAgentIdentityCensus()
census.record({
hostKind: 'wsl-distro',
launchMode: 'resume',
sourceMask: 20,
identityNull: false,
ambiguousTopRank: false
})
census.flush()
expect(census.snapshot().rows).toEqual([['relay', 'resume', 1, 0, 0, 0, 0]])
expect(track).not.toHaveBeenCalled()
census.shutdown()
})
it('keeps the maximum authority snapshot below the terminal-list wire budget', () => {
const snapshot = new PaneAgentIdentityAuthoritySnapshot('e'.repeat(128))
const hostKinds: PaneAgentIdentityHostKind[] = [
'native',
'wsl-host',
'wsl-distro',
'ssh',
'relay'
]
const launchModes: PaneAgentIdentityLaunchMode[] = ['typed', 'orca-launch', 'resume']
for (const hostKind of hostKinds) {
for (const launchMode of launchModes) {
snapshot.record({
hostKind,
launchMode,
sourceMask: 64,
identityNull: true,
ambiguousTopRank: true
})
}
snapshot.recordCandidateCoverage(hostKind)
}
expect(Buffer.byteLength(JSON.stringify(snapshot.snapshot()), 'utf8')).toBeLessThan(2_048)
})
it('installs the census and primary hook facts in both production entry points', () => {
const desktop = readFileSync(resolve(process.cwd(), 'src/main/index.ts'), 'utf8')
const headless = readFileSync(resolve(process.cwd(), 'src/main/orcad/orcad-entry.ts'), 'utf8')
expect(desktop).toContain(
'const paneAgentIdentityCensus = isServeMode\n ? createHeadlessPaneAgentIdentityCensus()\n : createDesktopPaneAgentIdentityCensus()'
)
expect(headless).toContain(
'const paneAgentIdentityCensus = createHeadlessPaneAgentIdentityCensus()'
)
for (const source of [desktop, headless]) {
expect(source).toMatch(
/new OrcaRuntimeService\([\s\S]*?\{[\s\S]*?paneAgentIdentityCensus[,\s]/
)
}
expect(desktop).toContain('runtime?.observeAgentHookStatus(enriched)')
})
})
@@ -0,0 +1,9 @@
import { PaneAgentIdentityCensus } from './pane-agent-identity-census'
export function createDesktopPaneAgentIdentityCensus(): PaneAgentIdentityCensus {
return new PaneAgentIdentityCensus()
}
export function createHeadlessPaneAgentIdentityCensus(): PaneAgentIdentityCensus {
return new PaneAgentIdentityCensus({ emit: null, snapshotHostKind: 'relay' })
}
@@ -0,0 +1,65 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { PaneAgentIdentityCensus } from './pane-agent-identity-census'
describe('PaneAgentIdentityCensus', () => {
afterEach(() => {
vi.useRealTimers()
})
it('schedules relay-only deltas and preserves relay coverage host attribution', () => {
vi.useFakeTimers()
const emit = vi.fn()
const census = new PaneAgentIdentityCensus(emit)
const row = ['relay', 'typed', 1, 0, 0, 0, 0] as const
census.ingestRelaySnapshot('env', { epoch: 'e', revision: 1, rows: [row] })
vi.advanceTimersByTime(300_000)
expect(emit).toHaveBeenCalledTimes(1)
expect(emit).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
kind: 'coverage',
host_kind: 'relay',
reason: 'baseline',
count: 1
})
])
)
emit.mockClear()
census.ingestRelaySnapshot('env', {
epoch: 'e',
revision: 2,
rows: [['relay', 'typed', 2, 0, 0, 0, 0]],
candidateCoverage: [['relay', 2]]
})
expect(emit).not.toHaveBeenCalled()
expect(census.snapshot().candidateCoverage).toBeUndefined()
vi.advanceTimersByTime(300_000)
expect(emit).toHaveBeenCalledTimes(1)
expect(emit).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
kind: 'aggregate',
host_kind: 'relay',
attested_runs: 1
}),
expect.objectContaining({
kind: 'coverage',
host_kind: 'relay',
reason: 'snapshot',
count: 1
}),
expect.objectContaining({
kind: 'coverage',
host_kind: 'relay',
reason: 'candidate',
count: 2
})
])
)
census.shutdown()
})
})
@@ -0,0 +1,240 @@
import {
aggregatePaneAgentIdentityAvailability,
emptyPaneAgentIdentityAggregate,
PaneAgentIdentityRelaySnapshotMerger,
PaneAgentIdentityAuthoritySnapshot,
PANE_AGENT_IDENTITY_MAX_BATCH_ROWS,
type PaneAgentIdentityAggregate,
type PaneAgentIdentityAvailabilitySnapshot
} from '../../shared/pane-agent-identity-availability'
import type {
PaneAgentIdentityAvailability,
PaneAgentIdentityHostKind
} from '../../shared/pane-agent-identity-evidence'
import { track } from './client'
import { randomUUID } from 'node:crypto'
type CoverageReason =
| 'snapshot'
| 'baseline'
| 'epoch_changed'
| 'counter_regressed'
| 'capacity'
| 'candidate'
| 'overflow'
| 'truncated'
type AggregateRow = {
kind: 'aggregate'
host_kind: PaneAgentIdentityAggregate['hostKind']
launch_mode: PaneAgentIdentityAggregate['launchMode']
attested_runs: number
no_evidence: number
title_only: number
identity_null: number
ambiguous_top_rank: number
}
type CoverageRow = {
kind: 'coverage'
host_kind: PaneAgentIdentityAggregate['hostKind']
reason: CoverageReason
count: number
}
type TelemetryRow = AggregateRow | CoverageRow
type TelemetryEmitter = (rows: readonly TelemetryRow[]) => void
type PaneAgentIdentityCensusOptions = {
emit: TelemetryEmitter | null
snapshotHostKind?: PaneAgentIdentityHostKind
epoch?: string
}
const keyOf = (hostKind: string, launchMode: string): string => `${hostKind}:${launchMode}`
export class PaneAgentIdentityCensus {
private readonly aggregate = new Map<string, PaneAgentIdentityAggregate>()
private readonly relay = new PaneAgentIdentityRelaySnapshotMerger()
private readonly coverage = new Map<string, CoverageRow>()
private timer: ReturnType<typeof setTimeout> | undefined
private closed = false
private readonly emit: TelemetryEmitter | null
private readonly snapshotHostKind: PaneAgentIdentityHostKind | undefined
private readonly authoritySnapshot: PaneAgentIdentityAuthoritySnapshot
constructor(input: TelemetryEmitter | PaneAgentIdentityCensusOptions = defaultOptions()) {
const options = typeof input === 'function' ? { emit: input } : input
this.emit = options.emit
this.snapshotHostKind = options.snapshotHostKind
this.authoritySnapshot = new PaneAgentIdentityAuthoritySnapshot(options.epoch ?? randomUUID())
}
record(row: PaneAgentIdentityAvailability): void {
if (this.closed) {
return
}
const normalized = this.normalizeObservation(row)
this.authoritySnapshot.record(normalized)
if (!this.emit) {
return
}
const key = keyOf(normalized.hostKind, normalized.launchMode)
this.aggregate.set(
key,
aggregatePaneAgentIdentityAvailability(
normalized,
this.aggregate.get(key) ??
emptyPaneAgentIdentityAggregate(normalized.hostKind, normalized.launchMode)
)
)
this.scheduleFlush()
}
ingestRelaySnapshot(
environmentKey: string,
snapshot: PaneAgentIdentityAvailabilitySnapshot | undefined
): void {
if (this.closed) {
return
}
const result = this.relay.merge(environmentKey, snapshot)
if (result.kind !== 'unavailable') {
this.addCoverage('relay', 'snapshot')
}
if (result.kind === 'delta') {
for (const row of result.rows) {
// Apply cumulative deltas without reclassifying every run.
const key = keyOf(row.hostKind, row.launchMode)
const previous =
this.aggregate.get(key) ?? emptyPaneAgentIdentityAggregate(row.hostKind, row.launchMode)
this.aggregate.set(key, {
...previous,
attestedRuns: Math.min(1_000_000_000, previous.attestedRuns + row.attestedRuns),
noEvidence: Math.min(1_000_000_000, previous.noEvidence + row.noEvidence),
titleOnly: Math.min(1_000_000_000, previous.titleOnly + row.titleOnly),
identityNull: Math.min(1_000_000_000, previous.identityNull + row.identityNull),
ambiguousTopRank: Math.min(
1_000_000_000,
previous.ambiguousTopRank + row.ambiguousTopRank
)
})
}
for (const candidate of result.candidateCoverage) {
this.addTelemetryCoverage(candidate.hostKind, 'candidate', candidate.exposures)
}
} else if (result.kind === 'baseline') {
this.addCoverage('relay', 'baseline')
} else if (result.kind === 'failed-closed') {
this.addCoverage(
'relay',
result.reason === 'capacity'
? 'capacity'
: result.reason === 'counter-regressed'
? 'counter_regressed'
: 'epoch_changed'
)
}
}
addCoverage(
hostKind: PaneAgentIdentityAggregate['hostKind'],
reason: CoverageReason,
amount = 1
): void {
if (this.closed) {
return
}
const normalizedHostKind = this.snapshotHostKind ?? hostKind
if (reason === 'candidate') {
this.authoritySnapshot.recordCandidateCoverage(normalizedHostKind, amount)
}
this.addTelemetryCoverage(normalizedHostKind, reason, amount)
}
private addTelemetryCoverage(
hostKind: PaneAgentIdentityAggregate['hostKind'],
reason: CoverageReason,
amount: number
): void {
if (!this.emit) {
return
}
const key = `${hostKind}:${reason}`
const previous = this.coverage.get(key)
this.coverage.set(key, {
kind: 'coverage',
host_kind: hostKind,
reason,
count: Math.min(1_000_000_000, (previous?.count ?? 0) + Math.max(0, Math.floor(amount)))
})
this.scheduleFlush()
}
scheduleFlush(delayMs = 300_000): void {
if (this.closed || !this.emit || this.timer) {
return
}
this.timer = setTimeout(() => {
this.timer = undefined
this.flush()
}, delayMs)
this.timer.unref?.()
}
flush(): void {
if (this.closed) {
return
}
const rows: TelemetryRow[] = [...this.aggregate.values()].map((row) => ({
kind: 'aggregate',
host_kind: row.hostKind,
launch_mode: row.launchMode,
attested_runs: row.attestedRuns,
no_evidence: row.noEvidence,
title_only: row.titleOnly,
identity_null: row.identityNull,
ambiguous_top_rank: row.ambiguousTopRank
}))
for (const coverage of this.coverage.values()) {
rows.push(coverage)
}
if (rows.length === 0) {
return
}
for (let offset = 0; offset < rows.length; offset += PANE_AGENT_IDENTITY_MAX_BATCH_ROWS) {
this.emit?.(rows.slice(offset, offset + PANE_AGENT_IDENTITY_MAX_BATCH_ROWS))
}
this.aggregate.clear()
this.coverage.clear()
}
shutdown(): void {
this.closed = true
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = undefined
this.aggregate.clear()
this.coverage.clear()
this.relay.clear()
}
snapshot(): PaneAgentIdentityAvailabilitySnapshot {
return this.authoritySnapshot.snapshot()
}
hostKindsForObservation(
hostKinds: readonly PaneAgentIdentityHostKind[]
): readonly PaneAgentIdentityHostKind[] {
return this.snapshotHostKind ? [this.snapshotHostKind] : hostKinds
}
private normalizeObservation(row: PaneAgentIdentityAvailability): PaneAgentIdentityAvailability {
return this.snapshotHostKind ? { ...row, hostKind: this.snapshotHostKind } : row
}
}
function defaultOptions(): PaneAgentIdentityCensusOptions {
return {
emit: (rows) => track('pane_agent_identity_availability', { rows: [...rows] })
}
}
export type PaneAgentIdentityTelemetryRow = TelemetryRow
+13 -4
View File
@@ -326,7 +326,10 @@ import type {
LocalLogTailReadResult,
LocalLogTailWatchArgs
} from '../shared/local-log-tail-types'
import { subscribeRuntimeEnvironmentFromPreload } from './runtime-environment-subscriptions'
import {
observeRuntimeEnvironmentTerminalListResponse,
subscribeRuntimeEnvironmentFromPreload
} from './runtime-environment-subscriptions'
import type { RuntimeEnvironmentSubscriptionHandle } from './runtime-environment-subscriptions'
import type { HostedReviewForBranchArgs } from '../shared/hosted-review'
import type { ReadClipboardTextOptions } from '../shared/clipboard-text'
@@ -4700,14 +4703,20 @@ const api = {
ipcRenderer.invoke('runtimeEnvironments:prepareBrowserClientHostPlacement', args),
retryConnectionsNow: (): Promise<void> =>
ipcRenderer.invoke('runtimeEnvironments:retryConnectionsNow'),
call: (args: {
call: async (args: {
selector: string
method: string
params?: unknown
timeoutMs?: number
expectedEnvironmentPairingRevision?: number
}): Promise<RuntimeRpcResponse<unknown>> =>
ipcRenderer.invoke('runtimeEnvironments:call', args),
}): Promise<RuntimeRpcResponse<unknown>> => {
const response = (await ipcRenderer.invoke(
'runtimeEnvironments:call',
args
)) as RuntimeRpcResponse<unknown>
observeRuntimeEnvironmentTerminalListResponse(ipcRenderer, args, response)
return response
},
subscribe: async (
args: {
selector: string
@@ -1,5 +1,8 @@
import { describe, expect, it, vi } from 'vitest'
import { subscribeRuntimeEnvironmentFromPreload } from './runtime-environment-subscriptions'
import {
observeRuntimeEnvironmentTerminalListResponse,
subscribeRuntimeEnvironmentFromPreload
} from './runtime-environment-subscriptions'
function deferred<T>(): {
promise: Promise<T>
@@ -55,6 +58,96 @@ function dispatch(ipc: ReturnType<typeof createIpc>, event: SubscriptionEvent):
}
describe('subscribeRuntimeEnvironmentFromPreload', () => {
it('observes one-shot terminal lists at the preload boundary and skips old peers', () => {
const ipc = createIpc()
const snapshot = { epoch: 'epoch', revision: 1, rows: [] }
observeRuntimeEnvironmentTerminalListResponse(
ipc,
{ selector: 'desk', method: 'terminal.list' },
{
id: 'rpc-list',
ok: true,
result: { terminals: [], agentIdentityAvailability: snapshot },
_meta: { runtimeId: 'rt' }
}
)
expect(ipc.invoke).toHaveBeenCalledWith('telemetry:ingestPaneAgentIdentityAvailability', {
environmentKey: 'desk',
snapshot
})
ipc.invoke.mockClear()
observeRuntimeEnvironmentTerminalListResponse(
ipc,
{ selector: 'desk', method: 'terminal.list' },
{
id: 'rpc-old-peer',
ok: true,
result: { terminals: [] },
_meta: { runtimeId: 'rt' }
}
)
observeRuntimeEnvironmentTerminalListResponse(
ipc,
{ selector: 'desk', method: 'terminal.show' },
{
id: 'rpc-other',
ok: true,
result: { agentIdentityAvailability: snapshot },
_meta: { runtimeId: 'rt' }
}
)
observeRuntimeEnvironmentTerminalListResponse(
ipc,
{ selector: 'desk', method: 'terminal.list' },
{
id: 'rpc-private-extra',
ok: true,
result: { agentIdentityAvailability: { ...snapshot, rawTitle: 'private' } },
_meta: { runtimeId: 'rt' }
}
)
expect(ipc.invoke).not.toHaveBeenCalled()
})
it('observes each successful terminal-list subscription response once', async () => {
const ipc = createIpc()
ipc.invoke.mockImplementation((channel: string) =>
channel === 'runtimeEnvironments:subscribe'
? Promise.resolve({ subscriptionId: 'sub-list', requestId: 'rpc-list' })
: (Promise.resolve({}) as Promise<unknown>)
)
const onResponse = vi.fn()
const cleanup = await subscribeRuntimeEnvironmentFromPreload(
ipc,
{ selector: 'desk', method: 'terminal.list' },
{ onResponse },
() => 'sub-list'
)
const snapshot = { epoch: 'epoch', revision: 1, rows: [] }
dispatch(ipc, {
subscriptionId: 'sub-list',
type: 'response',
response: {
id: 'rpc-list',
ok: true,
result: { terminals: [], agentIdentityAvailability: snapshot },
_meta: { runtimeId: 'rt' }
}
})
expect(onResponse).toHaveBeenCalledTimes(1)
expect(ipc.invoke).toHaveBeenCalledTimes(2)
expect(ipc.invoke).toHaveBeenLastCalledWith('telemetry:ingestPaneAgentIdentityAvailability', {
environmentKey: 'desk',
snapshot
})
cleanup.unsubscribe()
})
it('registers the subscription event listener before invoking main', async () => {
const subscription = deferred<{ subscriptionId: string; requestId: string }>()
const ipc = createIpc()
@@ -1,4 +1,5 @@
import type { RuntimeRpcResponse } from '../shared/runtime-rpc-envelope'
import { isPaneAgentIdentityAvailabilitySnapshot } from '../shared/pane-agent-identity-availability-validator'
type RuntimeEnvironmentSubscribeArgs = {
selector: string
@@ -38,6 +39,8 @@ type RuntimeEnvironmentSubscriptionIpc = {
) => void
}
type RuntimeEnvironmentTerminalListObserverIpc = Pick<RuntimeEnvironmentSubscriptionIpc, 'invoke'>
const SUBSCRIPTION_EVENT_CHANNEL = 'runtimeEnvironments:subscriptionEvent'
// Why: each subscribe() previously attached its own channel listener that the
@@ -55,6 +58,28 @@ const subscriptionDispatchers = new WeakMap<
RuntimeEnvironmentSubscriptionDispatcher
>()
export function observeRuntimeEnvironmentTerminalListResponse(
ipc: RuntimeEnvironmentTerminalListObserverIpc,
args: Pick<RuntimeEnvironmentSubscribeArgs, 'selector' | 'method'>,
response: RuntimeRpcResponse<unknown>
): void {
if (args.method !== 'terminal.list' || !response.ok || !response.result) {
return
}
if (typeof response.result !== 'object') {
return
}
const snapshot = (response.result as { agentIdentityAvailability?: unknown })
.agentIdentityAvailability
if (snapshot === undefined || !isPaneAgentIdentityAvailabilitySnapshot(snapshot)) {
return
}
void ipc.invoke('telemetry:ingestPaneAgentIdentityAvailability', {
environmentKey: args.selector,
snapshot
})
}
function releaseIdleDispatcher(
ipc: RuntimeEnvironmentSubscriptionIpc,
callbacks: Map<string, RuntimeEnvironmentSubscriptionCallbacks>,
@@ -132,7 +157,17 @@ export async function subscribeRuntimeEnvironmentFromPreload(
// Why: streaming RPCs can emit their first frame before ipcMain.handle()
// resolves, so the dispatcher must be routing this id before invoking.
const dispatcher = getOrCreateDispatcher(ipc)
dispatcher.callbacks.set(subscriptionId, callbacks)
const observedCallbacks =
args.method === 'terminal.list'
? {
...callbacks,
onResponse: (response: RuntimeRpcResponse<unknown>): void => {
observeRuntimeEnvironmentTerminalListResponse(ipc, args, response)
callbacks.onResponse(response)
}
}
: callbacks
dispatcher.callbacks.set(subscriptionId, observedCallbacks)
const releaseCurrentSubscription = (): void => {
releaseSubscription(ipc, dispatcher, subscriptionId)
}
@@ -9,12 +9,10 @@ import { registerAgentHookTerminalLifecycleHandler } from '../agent-hook-termina
import type { AgentCompletionStatusSnapshot } from '../agent-completion-coordinator-types'
import { resolveCompatibleAgentTypeForOwner } from '../../../../../shared/agent-title-owner'
import { resolveCommittedTitleAgentType } from '@/lib/pane-agent-evidence'
import { recognizeAgentProcessFromCommandLine } from '../../../../../shared/agent-process-recognition'
import { ManualAgentCommandTracker } from '../../../../../shared/manual-agent-command-tracker'
import type { TuiAgent } from '../../../../../shared/tui-agent'
import { isTuiAgent } from '../../../../../shared/tui-agent-config'
import { MANUAL_AGENT_COMMAND_MAX_CHARS } from './pty-connect-limits'
import type { ConnectPanePtySession } from './connect-pane-pty-session'
export function installCommandInferredPaneAgent(session: ConnectPanePtySession): void {
@@ -22,22 +20,17 @@ export function installCommandInferredPaneAgent(session: ConnectPanePtySession):
// shadowing the shell's current command line, for generic terminals where no
// launch metadata exists. Consumed by getAuthoritativePaneAgent below.
session.commandInferredPaneAgent = null
session.pendingShellCommandLine = ''
session.pendingShellCommandCursor = 0
session.manualAgentCommandTracker = new ManualAgentCommandTracker()
session.commandInferredPaneAgentGeneration = 0
session.shellCommandInferenceSuspendedUntilCommandEnd = false
session.startAcceptedInferredCommand = (_agent: TuiAgent): void => {}
session.requestKnownWindowsShiftEnterReconfirmation = (): void => {}
session.resetPendingShellCommandLine = (): void => {
session.pendingShellCommandLine = ''
session.pendingShellCommandCursor = 0
session.manualAgentCommandTracker.reset()
}
session.rememberCommandInferredPaneAgent = (): void => {
const commandLine = session.pendingShellCommandLine.trim()
session.resetPendingShellCommandLine()
const candidateAgent = commandLine
? (recognizeAgentProcessFromCommandLine(commandLine)?.agent ?? null)
: null
session.cancelSuspendedShellCommandInference = (): void => {
session.manualAgentCommandTracker.cancelSuspendedInference()
}
session.rememberCommandInferredPaneAgent = (candidateAgent: TuiAgent): void => {
const state = useAppStore.getState()
const registeredLaunchAgent =
state.agentLaunchConfigByPaneKey[session.cacheKey]?.identity.agentType
@@ -69,93 +62,6 @@ export function installCommandInferredPaneAgent(session: ConnectPanePtySession):
}, 0)
})
}
session.appendPendingShellCommandInput = (text: string): void => {
const available = MANUAL_AGENT_COMMAND_MAX_CHARS - session.pendingShellCommandLine.length
if (available <= 0) {
session.shellCommandInferenceSuspendedUntilCommandEnd = true
return
}
const inserted = text.slice(0, available)
session.pendingShellCommandLine =
session.pendingShellCommandLine.slice(0, session.pendingShellCommandCursor) +
inserted +
session.pendingShellCommandLine.slice(session.pendingShellCommandCursor)
session.pendingShellCommandCursor += inserted.length
if (inserted.length < text.length) {
session.shellCommandInferenceSuspendedUntilCommandEnd = true
}
}
session.deletePendingShellCommandWord = (): void => {
const beforeCursor = session.pendingShellCommandLine.slice(0, session.pendingShellCommandCursor)
const afterCursor = session.pendingShellCommandLine.slice(session.pendingShellCommandCursor)
const nextBeforeCursor = beforeCursor.replace(/[^\S\r\n]*\S+[^\S\r\n]*$/, '')
session.pendingShellCommandLine = nextBeforeCursor + afterCursor
session.pendingShellCommandCursor = nextBeforeCursor.length
}
session.cancelSuspendedShellCommandInference = (): void => {
if (!session.shellCommandInferenceSuspendedUntilCommandEnd) {
return
}
session.shellCommandInferenceSuspendedUntilCommandEnd = false
session.resetPendingShellCommandLine()
}
session.deletePendingShellCommandCharacter = (): void => {
if (session.pendingShellCommandCursor === 0) {
return
}
session.pendingShellCommandLine =
session.pendingShellCommandLine.slice(0, session.pendingShellCommandCursor - 1) +
session.pendingShellCommandLine.slice(session.pendingShellCommandCursor)
session.pendingShellCommandCursor -= 1
}
session.deletePendingShellCommandCharacterAtCursor = (): void => {
if (session.pendingShellCommandCursor >= session.pendingShellCommandLine.length) {
return
}
session.pendingShellCommandLine =
session.pendingShellCommandLine.slice(0, session.pendingShellCommandCursor) +
session.pendingShellCommandLine.slice(session.pendingShellCommandCursor + 1)
}
session.movePendingShellCommandCursor = (delta: number): void => {
session.pendingShellCommandCursor = Math.min(
session.pendingShellCommandLine.length,
Math.max(0, session.pendingShellCommandCursor + delta)
)
}
session.consumeShellCommandCsiSequence = (data: string, index: number): number | null => {
if (data.charCodeAt(index) !== 0x1b || data[index + 1] !== '[') {
return null
}
let cursor = index + 2
while (cursor < data.length && /[0-9;?]/.test(data[cursor]!)) {
cursor += 1
}
const final = data[cursor]
if (!final || !/[~A-Za-z]/.test(final)) {
return null
}
const params = data.slice(index + 2, cursor)
// Why: only emulate a bare one-column move. Parameterized/modified cursor keys
// (e.g. Ctrl+Left `\x1b[1;5D` = word-jump) move the real cursor by more than
// one, so tracking them as ±1 would desync the shadow line — fall through to
// reset instead of silently corrupting the sampled command.
if (final === 'D' && params === '') {
session.movePendingShellCommandCursor(-1)
} else if (final === 'C' && params === '') {
session.movePendingShellCommandCursor(1)
} else if (final === 'H' || (final === '~' && params === '1')) {
session.pendingShellCommandCursor = 0
} else if (final === 'F' || (final === '~' && params === '4')) {
session.pendingShellCommandCursor = session.pendingShellCommandLine.length
} else if (final === '~' && params === '3') {
session.deletePendingShellCommandCharacterAtCursor()
} else if (final === '~' && (params === '200' || params === '201')) {
// Bracketed paste wrappers are terminal framing, not shell command text.
} else {
session.resetPendingShellCommandLine()
}
return cursor + 1
}
session.getLivePaneAgentTitle = (): string | null => {
const state = useAppStore.getState()
const runtimeTitle = state.runtimePaneTitlesByTabId?.[session.deps.tabId]?.[session.pane.id]
@@ -4,7 +4,6 @@ import type { AgentType } from '../../../../../shared/agent-status-types'
import { AGENT_INTERRUPT_SETTLE_MS } from '../../../../../shared/agent-interrupt-intent'
import { resolvePaneAgentOwner } from '../../../../../shared/pane-agent-owner'
import { MANUAL_AGENT_COMMAND_MAX_CHARS } from './pty-connect-limits'
import {
CURSOR_AGENT_REATTACH_HEADER,
terminalOwnsDomFocus,
@@ -34,62 +33,10 @@ export function installShellCommandInference(session: ConnectPanePtySession): vo
session.resetPendingShellCommandLine()
return
}
if (session.shellCommandInferenceSuspendedUntilCommandEnd) {
if (data.includes('\x03') || data.includes('\x15')) {
session.shellCommandInferenceSuspendedUntilCommandEnd = false
session.resetPendingShellCommandLine()
}
if (data.includes('\r') || data.includes('\n')) {
session.shellCommandInferenceSuspendedUntilCommandEnd = false
}
return
}
if (data.length > MANUAL_AGENT_COMMAND_MAX_CHARS) {
session.resetPendingShellCommandLine()
session.shellCommandInferenceSuspendedUntilCommandEnd =
!data.includes('\r') && !data.includes('\n')
return
}
for (let index = 0; index < data.length; index += 1) {
const char = data[index]!
if (char === '\r' || char === '\n') {
session.shellCommandInferenceSuspendedUntilCommandEnd = false
session.rememberCommandInferredPaneAgent()
if (session.commandInferredPaneAgent) {
return
}
continue
}
if (char === '\x7f' || char === '\b') {
session.deletePendingShellCommandCharacter()
continue
}
if (char === '\x17') {
session.deletePendingShellCommandWord()
continue
}
if (char === '\x03' || char === '\x15') {
session.resetPendingShellCommandLine()
continue
}
if (char === '\x1b') {
const nextIndex = session.consumeShellCommandCsiSequence(data, index)
if (nextIndex !== null) {
index = nextIndex - 1
continue
}
session.resetPendingShellCommandLine()
continue
}
if (char < ' ') {
session.resetPendingShellCommandLine()
continue
}
if (char >= ' ') {
session.appendPendingShellCommandInput(char)
if (session.shellCommandInferenceSuspendedUntilCommandEnd) {
return
}
for (const candidate of session.manualAgentCommandTracker.ingest(data)) {
session.rememberCommandInferredPaneAgent(candidate)
if (session.commandInferredPaneAgent) {
return
}
}
}
+125
View File
@@ -0,0 +1,125 @@
import { recognizeAgentProcessFromCommandLine } from './agent-process-recognition'
import type { TuiAgent } from './tui-agent'
export const MANUAL_AGENT_COMMAND_MAX_CHARS = 4_096
/** Bounded shell-line shadow used by both UI ownership inference and execution authorities. */
export class ManualAgentCommandTracker {
private line = ''
private cursor = 0
private suspended = false
private acceptedCount = 0
ingest(data: string): TuiAgent[] {
const accepted: TuiAgent[] = []
if (this.suspended) {
if (data.includes('\x03') || data.includes('\x15')) {
this.reset()
} else if (data.includes('\r') || data.includes('\n')) {
this.suspended = false
}
return accepted
}
if (data.length > MANUAL_AGENT_COMMAND_MAX_CHARS) {
this.reset()
this.suspended = !data.includes('\r') && !data.includes('\n')
return accepted
}
for (let index = 0; index < data.length; index += 1) {
const char = data[index]!
if (char === '\r' || char === '\n') {
if (!this.suspended) {
const recognized = recognizeAgentProcessFromCommandLine(this.line.trim())
if (recognized) {
accepted.push(recognized.agent)
this.acceptedCount += 1
}
}
this.reset()
continue
}
if (char === '\x7f' || char === '\b') {
this.backspace()
continue
}
if (char === '\x03' || char === '\x15') {
this.reset()
continue
}
if (char === '\x17') {
this.deleteWord()
continue
}
if (char === '\x1b' && data[index + 1] === '[') {
const nextIndex = this.consumeCsi(data, index)
if (nextIndex === null) {
this.reset()
} else {
index = nextIndex - 1
}
continue
}
if (char < ' ') {
this.reset()
continue
}
if (this.line.length >= MANUAL_AGENT_COMMAND_MAX_CHARS) {
this.suspended = true
return accepted
}
this.line = this.line.slice(0, this.cursor) + char + this.line.slice(this.cursor)
this.cursor += 1
}
return accepted
}
reset(): void {
this.line = ''
this.cursor = 0
this.suspended = false
}
cancelSuspendedInference(): void {
if (this.suspended) {
this.reset()
}
}
get acceptedCommandCount(): number {
return this.acceptedCount
}
private backspace(): void {
if (this.cursor > 0) {
this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor)
this.cursor -= 1
}
}
private deleteWord(): void {
const before = this.line.slice(0, this.cursor).replace(/[^\S\r\n]*\S+[^\S\r\n]*$/, '')
this.line = before + this.line.slice(this.cursor)
this.cursor = before.length
}
private consumeCsi(data: string, index: number): number | null {
let cursor = index + 2
while (cursor < data.length && /[0-9;?]/.test(data[cursor]!)) {
cursor += 1
}
const final = data[cursor]
if (!final || !/[~A-Za-z]/.test(final)) {
return null
}
const params = data.slice(index + 2, cursor)
if (final === 'D' && params === '') {
this.cursor = Math.max(0, this.cursor - 1)
} else if (final === 'C' && params === '') {
this.cursor = Math.min(this.line.length, this.cursor + 1)
} else if (final === 'H' || (final === '~' && params === '1')) {
this.cursor = 0
} else if (final === 'F' || (final === '~' && params === '4')) {
this.cursor = this.line.length
} else if (final === '~' && params === '3' && this.cursor < this.line.length) {
this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1)
} else if (final !== '~' || (params !== '200' && params !== '201')) {
this.reset()
}
return cursor + 1
}
}
@@ -0,0 +1,88 @@
import {
PANE_AGENT_IDENTITY_MAX_COUNT,
type PaneAgentIdentityAvailabilitySnapshot
} from './pane-agent-identity-availability'
export function isPaneAgentIdentityAvailabilitySnapshot(
value: unknown
): value is PaneAgentIdentityAvailabilitySnapshot {
if (!value || typeof value !== 'object') {
return false
}
const candidate = value as Record<string, unknown>
const snapshotKeys = new Set(['epoch', 'revision', 'rows', 'candidateCoverage'])
if (Object.keys(candidate).some((key) => !snapshotKeys.has(key))) {
return false
}
if (
typeof candidate.epoch !== 'string' ||
candidate.epoch.length === 0 ||
candidate.epoch.length > 128
) {
return false
}
if (!Number.isSafeInteger(candidate.revision) || (candidate.revision as number) < 0) {
return false
}
if (!Array.isArray(candidate.rows) || candidate.rows.length > 15) {
return false
}
const rowKeys = new Set<string>()
const rowsValid = candidate.rows.every((row) => {
if (!Array.isArray(row) || row.length !== 7) {
return false
}
const [hostKind, launchMode, attestedRuns, ...counters] = row
if (
!['native', 'wsl-host', 'wsl-distro', 'ssh', 'relay'].includes(hostKind as string) ||
!['typed', 'orca-launch', 'resume'].includes(launchMode as string)
) {
return false
}
const key = `${hostKind as string}:${launchMode as string}`
if (rowKeys.has(key)) {
return false
}
rowKeys.add(key)
if (
!Number.isSafeInteger(attestedRuns) ||
(attestedRuns as number) < 1 ||
(attestedRuns as number) > PANE_AGENT_IDENTITY_MAX_COUNT
) {
return false
}
return counters.every(
(count) =>
Number.isSafeInteger(count) &&
(count as number) >= 0 &&
(count as number) <= (attestedRuns as number)
)
})
if (!rowsValid) {
return false
}
if (candidate.candidateCoverage === undefined) {
return true
}
if (!Array.isArray(candidate.candidateCoverage) || candidate.candidateCoverage.length > 5) {
return false
}
const coverageHosts = new Set<string>()
return candidate.candidateCoverage.every((row) => {
if (!Array.isArray(row) || row.length !== 2) {
return false
}
const [hostKind, exposures] = row
if (
!['native', 'wsl-host', 'wsl-distro', 'ssh', 'relay'].includes(hostKind as string) ||
!Number.isSafeInteger(exposures) ||
(exposures as number) < 1 ||
(exposures as number) > PANE_AGENT_IDENTITY_MAX_COUNT ||
coverageHosts.has(hostKind as string)
) {
return false
}
coverageHosts.add(hostKind as string)
return true
})
}
@@ -0,0 +1,275 @@
import type {
PaneAgentIdentityAvailability,
PaneAgentIdentityHostKind,
PaneAgentIdentityLaunchMode
} from './pane-agent-identity-evidence'
export const PANE_AGENT_IDENTITY_MAX_COUNT = 1_000_000_000
export const PANE_AGENT_IDENTITY_MAX_BATCH_ROWS = 64
export const PANE_AGENT_IDENTITY_MAX_REMOTE_ENVIRONMENTS = 256
export {
MAX_LIVE_FINALIZED_RUN_KEYS,
MAX_PENDING_IDENTITY_OBSERVATIONS,
PaneAgentIdentityObservationScheduler,
RUN_SETTLE_MS,
TITLE_CANDIDATE_WINDOW_MS,
type PaneAgentIdentityObservationSchedulerDeps
} from './pane-agent-identity-observation-scheduler'
export type PaneAgentIdentityAggregate = {
hostKind: PaneAgentIdentityHostKind
launchMode: PaneAgentIdentityLaunchMode
attestedRuns: number
noEvidence: number
titleOnly: number
identityNull: number
ambiguousTopRank: number
}
export type PaneAgentIdentityCandidateCoverage = {
hostKind: PaneAgentIdentityHostKind
exposures: number
}
export type PaneAgentIdentityAggregateWireRow = readonly [
hostKind: PaneAgentIdentityHostKind,
launchMode: PaneAgentIdentityLaunchMode,
attestedRuns: number,
noEvidence: number,
titleOnly: number,
identityNull: number,
ambiguousTopRank: number
]
export type PaneAgentIdentityCandidateCoverageWireRow = readonly [
hostKind: PaneAgentIdentityHostKind,
exposures: number
]
export type PaneAgentIdentityAvailabilitySnapshot = {
epoch: string
revision: number
/** Compact fixed tuples keep the cumulative field below the terminal-list wire budget. */
rows: readonly PaneAgentIdentityAggregateWireRow[]
candidateCoverage?: readonly PaneAgentIdentityCandidateCoverageWireRow[]
}
export function emptyPaneAgentIdentityAggregate(
hostKind: PaneAgentIdentityHostKind,
launchMode: PaneAgentIdentityLaunchMode
): PaneAgentIdentityAggregate {
return {
hostKind,
launchMode,
attestedRuns: 0,
noEvidence: 0,
titleOnly: 0,
identityNull: 0,
ambiguousTopRank: 0
}
}
function addCount(value: number, amount: number): number {
return Math.min(PANE_AGENT_IDENTITY_MAX_COUNT, value + Math.max(0, Math.floor(amount)))
}
export function aggregatePaneAgentIdentityAvailability(
row: PaneAgentIdentityAvailability,
aggregate = emptyPaneAgentIdentityAggregate(row.hostKind, row.launchMode)
): PaneAgentIdentityAggregate {
const titleOnly = row.sourceMask === 64
return {
...aggregate,
attestedRuns: addCount(aggregate.attestedRuns, 1),
noEvidence: addCount(aggregate.noEvidence, row.sourceMask === 0 ? 1 : 0),
titleOnly: addCount(aggregate.titleOnly, titleOnly ? 1 : 0),
identityNull: addCount(aggregate.identityNull, row.identityNull ? 1 : 0),
ambiguousTopRank: addCount(aggregate.ambiguousTopRank, row.ambiguousTopRank ? 1 : 0)
}
}
export type RelayMergeResult =
| { kind: 'baseline' }
| {
kind: 'delta'
rows: PaneAgentIdentityAggregate[]
candidateCoverage: PaneAgentIdentityCandidateCoverage[]
}
| { kind: 'stale' }
| {
kind: 'failed-closed'
reason: 'epoch-changed' | 'counter-regressed' | 'capacity'
}
| { kind: 'unavailable' }
type RelayState = {
epoch: string
revision: number
rows: Map<string, PaneAgentIdentityAggregate>
candidates: Map<PaneAgentIdentityHostKind, number>
failedClosedReason: 'epoch-changed' | 'counter-regressed' | null
}
const rowKey = (row: PaneAgentIdentityAggregate): string => `${row.hostKind}:${row.launchMode}`
const COUNTER_KEYS = [
'attestedRuns',
'noEvidence',
'titleOnly',
'identityNull',
'ambiguousTopRank'
] as const
const decodeAggregateRow = (
row: PaneAgentIdentityAggregateWireRow
): PaneAgentIdentityAggregate => ({
hostKind: row[0],
launchMode: row[1],
attestedRuns: row[2],
noEvidence: row[3],
titleOnly: row[4],
identityNull: row[5],
ambiguousTopRank: row[6]
})
/** Session-scoped cumulative snapshot merger. State is intentionally not evicted. */
export class PaneAgentIdentityRelaySnapshotMerger {
private readonly states = new Map<string, RelayState>()
private readonly maxEnvironments: number
constructor(maxEnvironments = PANE_AGENT_IDENTITY_MAX_REMOTE_ENVIRONMENTS) {
this.maxEnvironments = maxEnvironments
}
merge(
environmentKey: string,
snapshot: PaneAgentIdentityAvailabilitySnapshot | undefined
): RelayMergeResult {
if (
!snapshot ||
typeof snapshot.epoch !== 'string' ||
!Number.isSafeInteger(snapshot.revision) ||
snapshot.revision < 0
) {
return { kind: 'unavailable' }
}
let state = this.states.get(environmentKey)
const decodedRows = snapshot.rows.map(decodeAggregateRow)
if (!state) {
if (this.states.size >= this.maxEnvironments) {
return { kind: 'failed-closed', reason: 'capacity' }
}
state = {
epoch: snapshot.epoch,
revision: snapshot.revision,
rows: new Map(decodedRows.map((row) => [rowKey(row), row])),
candidates: new Map((snapshot.candidateCoverage ?? []).map((row) => [row[0], row[1]])),
failedClosedReason: null
}
this.states.set(environmentKey, state)
return { kind: 'baseline' }
}
if (state.failedClosedReason) {
return { kind: 'failed-closed', reason: state.failedClosedReason }
}
if (state.epoch !== snapshot.epoch) {
state.failedClosedReason = 'epoch-changed'
return { kind: 'failed-closed', reason: 'epoch-changed' }
}
if (snapshot.revision <= state.revision) {
return { kind: 'stale' }
}
const incomingRows = new Map(decodedRows.map((row) => [rowKey(row), row]))
const incomingCandidates = new Map(
(snapshot.candidateCoverage ?? []).map((row) => [row[0], row[1]])
)
const counterRegressed =
[...state.rows].some(([key, previous]) => {
const incoming = incomingRows.get(key)
return !incoming || COUNTER_KEYS.some((counter) => incoming[counter] < previous[counter])
}) ||
[...state.candidates].some(
([hostKind, previous]) => (incomingCandidates.get(hostKind) ?? -1) < previous
)
if (counterRegressed) {
state.failedClosedReason = 'counter-regressed'
return { kind: 'failed-closed', reason: 'counter-regressed' }
}
const deltas: PaneAgentIdentityAggregate[] = []
for (const incoming of decodedRows) {
const previous =
state.rows.get(rowKey(incoming)) ??
emptyPaneAgentIdentityAggregate(incoming.hostKind, incoming.launchMode)
const delta = {
...incoming,
...Object.fromEntries(
COUNTER_KEYS.map((key) => [key, Math.max(0, incoming[key] - previous[key])])
)
} as PaneAgentIdentityAggregate
if (COUNTER_KEYS.some((key) => delta[key] > 0)) {
deltas.push(delta)
}
state.rows.set(rowKey(incoming), { ...incoming })
}
const candidateCoverage: PaneAgentIdentityCandidateCoverage[] = []
for (const [hostKind, exposures] of incomingCandidates) {
const delta = exposures - (state.candidates.get(hostKind) ?? 0)
if (delta > 0) {
candidateCoverage.push({ hostKind, exposures: delta })
}
state.candidates.set(hostKind, exposures)
}
state.revision = snapshot.revision
return { kind: 'delta', rows: deltas, candidateCoverage }
}
clear(): void {
this.states.clear()
}
get size(): number {
return this.states.size
}
}
export class PaneAgentIdentityAuthoritySnapshot {
private readonly rows = new Map<string, PaneAgentIdentityAggregate>()
private readonly candidates = new Map<PaneAgentIdentityHostKind, number>()
private revision = 0
constructor(private readonly epoch: string) {}
record(observation: PaneAgentIdentityAvailability): void {
const key = `${observation.hostKind}:${observation.launchMode}`
this.rows.set(
key,
aggregatePaneAgentIdentityAvailability(
observation,
this.rows.get(key) ??
emptyPaneAgentIdentityAggregate(observation.hostKind, observation.launchMode)
)
)
this.revision += 1
}
recordCandidateCoverage(hostKind: PaneAgentIdentityHostKind, amount = 1): void {
this.candidates.set(hostKind, addCount(this.candidates.get(hostKind) ?? 0, amount))
this.revision += 1
}
snapshot(): PaneAgentIdentityAvailabilitySnapshot {
const candidateCoverage: PaneAgentIdentityCandidateCoverageWireRow[] = [...this.candidates]
return {
epoch: this.epoch,
revision: this.revision,
rows: [...this.rows.values()].map(
(row): PaneAgentIdentityAggregateWireRow => [
row.hostKind,
row.launchMode,
row.attestedRuns,
row.noEvidence,
row.titleOnly,
row.identityNull,
row.ambiguousTopRank
]
),
...(candidateCoverage.length > 0 ? { candidateCoverage } : {})
}
}
}
@@ -0,0 +1,255 @@
import { describe, expect, it } from 'vitest'
import {
assemblePaneAgentIdentityEvidence,
reducePaneAgentIdentityAvailability
} from './pane-agent-identity-evidence'
import {
aggregatePaneAgentIdentityAvailability,
PaneAgentIdentityRelaySnapshotMerger
} from './pane-agent-identity-availability'
import { isPaneAgentIdentityAvailabilitySnapshot } from './pane-agent-identity-availability-validator'
import {
MANUAL_AGENT_COMMAND_MAX_CHARS,
ManualAgentCommandTracker
} from './manual-agent-command-tracker'
import { eventSchemas } from './telemetry-events'
describe('pane identity evidence census', () => {
it('returns eligible evidence in the canonical resolver order', () => {
expect(
assemblePaneAgentIdentityEvidence({
facts: [
{ source: 'title', agent: 'codex' },
{ source: 'completed-hook', agent: 'codex' },
{
source: 'process',
agent: 'codex',
executionHostProvenance: 'execution-host'
},
{ source: 'live-hook', agent: 'codex' }
]
}).evidence.map((fact) => fact.source)
).toEqual(['live-hook', 'process', 'completed-hook', 'title'])
})
it('drops process facts without positive provenance and emits fixed source bits', () => {
const result = assemblePaneAgentIdentityEvidence({
facts: [
{ source: 'process', agent: 'codex', executionHostProvenance: undefined } as never,
{ source: 'title', agent: 'codex' }
]
})
expect(result.sourceMask).toBe(64)
expect(result.identityNull).toBe(false)
})
it('reduces no evidence and ambiguity without exposing the winning agent', () => {
expect(reducePaneAgentIdentityAvailability('native', 'typed', { facts: [] })).toMatchObject({
sourceMask: 0,
identityNull: true
})
const ambiguous = reducePaneAgentIdentityAvailability('native', 'orca-launch', {
facts: [
{ source: 'live-hook', agent: 'codex' },
{ source: 'live-hook', agent: 'claude' }
]
})
expect(ambiguous.ambiguousTopRank).toBe(true)
expect(ambiguous).not.toHaveProperty('resolvedAgent')
expect(
aggregatePaneAgentIdentityAvailability({
hostKind: 'native',
launchMode: 'typed',
sourceMask: 64,
identityNull: false,
ambiguousTopRank: false
}).titleOnly
).toBe(1)
})
it('excludes superseded and pane-ineligible evidence from the availability mask', () => {
const currentRun = { authorityId: 'authority', incarnation: 2 }
const result = reducePaneAgentIdentityAvailability('native', 'orca-launch', {
currentRun,
facts: [
{
source: 'live-hook',
agent: 'claude',
run: { authorityId: 'authority', incarnation: 1 }
},
{ source: 'sibling', agent: 'claude', run: currentRun },
{ source: 'title', agent: 'codex', run: currentRun }
]
})
expect(result).toEqual({
hostKind: 'native',
launchMode: 'orca-launch',
sourceMask: 64,
identityNull: false,
ambiguousTopRank: false
})
})
it('uses same-run owner compatibility and preserves relay baselines', () => {
const key = { authorityId: 'a', incarnation: 1 }
expect(
assemblePaneAgentIdentityEvidence({
currentRun: key,
owner: { agent: 'omp', run: key },
facts: [{ source: 'launch', agent: 'pi', run: key }]
}).resolvedAgent
).toBe('omp')
expect(
assemblePaneAgentIdentityEvidence({
currentRun: key,
owner: { agent: 'claude', run: key },
facts: [{ source: 'launch', agent: 'codex', run: key }]
}).resolvedAgent
).toBe('codex')
const merger = new PaneAgentIdentityRelaySnapshotMerger()
const snapshot = {
epoch: 'e',
revision: 1,
rows: [['relay', 'typed', 1, 1, 0, 1, 0] as const]
}
expect(merger.merge('env', snapshot)).toEqual({ kind: 'baseline' })
expect(merger.merge('env', { ...snapshot, revision: 0 })).toEqual({ kind: 'stale' })
expect(
merger.merge('env', {
...snapshot,
revision: 2,
rows: [['relay', 'typed', 2, 1, 0, 1, 0] as const]
})
).toMatchObject({ kind: 'delta' })
expect(merger.merge('env', { ...snapshot, revision: 3, epoch: 'new' })).toEqual({
kind: 'failed-closed',
reason: 'epoch-changed'
})
})
it('fail-closes cumulative counter regressions without moving the baseline backward', () => {
const merger = new PaneAgentIdentityRelaySnapshotMerger()
const row = ['relay', 'typed', 5, 1, 0, 1, 0] as const
expect(merger.merge('env', { epoch: 'e', revision: 1, rows: [row] })).toEqual({
kind: 'baseline'
})
expect(
merger.merge('env', {
epoch: 'e',
revision: 2,
rows: [['relay', 'typed', 4, 1, 0, 1, 0]]
})
).toEqual({ kind: 'failed-closed', reason: 'counter-regressed' })
expect(
merger.merge('env', {
epoch: 'e',
revision: 3,
rows: [['relay', 'typed', 6, 1, 0, 1, 0]]
})
).toEqual({ kind: 'failed-closed', reason: 'counter-regressed' })
})
it('merges candidate coverage as a cumulative delta and fail-closes regressions', () => {
const merger = new PaneAgentIdentityRelaySnapshotMerger()
const baseline = {
epoch: 'e',
revision: 1,
rows: [],
candidateCoverage: [['relay', 2] as const]
}
expect(merger.merge('env', baseline)).toEqual({ kind: 'baseline' })
expect(
merger.merge('env', {
...baseline,
revision: 2,
candidateCoverage: [['relay', 5] as const]
})
).toEqual({
kind: 'delta',
rows: [],
candidateCoverage: [{ hostKind: 'relay', exposures: 3 }]
})
expect(
merger.merge('env', {
...baseline,
revision: 3,
candidateCoverage: [['relay', 4] as const]
})
).toEqual({ kind: 'failed-closed', reason: 'counter-regressed' })
})
it('strictly validates privacy-reduced snapshots and optional candidate coverage', () => {
const snapshot = {
epoch: 'e',
revision: 1,
rows: [['relay', 'typed', 1, 0, 0, 0, 0] as const],
candidateCoverage: [['ssh', 1] as const]
}
expect(isPaneAgentIdentityAvailabilitySnapshot(snapshot)).toBe(true)
expect(
isPaneAgentIdentityAvailabilitySnapshot({ ...snapshot, rawTitle: 'private title' })
).toBe(false)
expect(
isPaneAgentIdentityAvailabilitySnapshot({
...snapshot,
candidateCoverage: [['ssh', 1, '/private']]
})
).toBe(false)
expect(
isPaneAgentIdentityAvailabilitySnapshot({
...snapshot,
rows: [['relay', 'typed', 1, 2, 0, 0, 0]]
})
).toBe(false)
})
it('accepts only bounded enum-and-count telemetry rows', () => {
const valid = {
rows: [
{
kind: 'coverage' as const,
host_kind: 'relay' as const,
reason: 'counter_regressed' as const,
count: 1
}
]
}
expect(eventSchemas.pane_agent_identity_availability.safeParse(valid).success).toBe(true)
expect(
eventSchemas.pane_agent_identity_availability.safeParse({
rows: [{ ...valid.rows[0], title: 'private' }]
}).success
).toBe(false)
expect(
eventSchemas.pane_agent_identity_availability.safeParse({
rows: [{ ...valid.rows[0], count: '1' }]
}).success
).toBe(false)
})
it('retains bounded command edit semantics and discards input after classification', () => {
const tracker = new ManualAgentCommandTracker()
expect(tracker.ingest('codex\r')).toEqual(['codex'])
expect(tracker.ingest('cod\u0008dex\r')).toEqual(['codex'])
expect(tracker.ingest('claude\rcodex\r')).toEqual(['claude', 'codex'])
tracker.ingest('x'.repeat(MANUAL_AGENT_COMMAND_MAX_CHARS))
expect(tracker.ingest('y\rcodex\r')).toEqual([])
expect(tracker.ingest('\r')).toEqual([])
expect(tracker.ingest('codex\r')).toEqual([])
tracker.ingest('\x15')
expect(tracker.ingest('codex\r')).toEqual(['codex'])
})
it('only clears a pending command when cancelling suspended inference', () => {
const tracker = new ManualAgentCommandTracker()
tracker.ingest('co')
tracker.cancelSuspendedInference()
expect(tracker.ingest('dex\r')).toEqual(['codex'])
tracker.ingest('x'.repeat(MANUAL_AGENT_COMMAND_MAX_CHARS + 1))
tracker.cancelSuspendedInference()
expect(tracker.ingest('claude\r')).toEqual(['claude'])
})
})
+139
View File
@@ -0,0 +1,139 @@
import {
PANE_AGENT_EVIDENCE_SOURCES,
type PaneAgentEvidence,
type PaneAgentEvidenceSource,
type PaneAgentRunKey,
resolvePaneAgentIdentity
} from './pane-agent-identity-resolver'
import { resolveCompatibleAgentTypeForOwner } from './agent-title-owner'
import type { TuiAgent } from './tui-agent'
export type PaneAgentIdentityHostKind = 'native' | 'wsl-host' | 'wsl-distro' | 'ssh' | 'relay'
export type PaneAgentIdentityLaunchMode = 'typed' | 'orca-launch' | 'resume'
type PaneAgentIdentityNonProcessFact = Omit<PaneAgentEvidence<TuiAgent>, 'source'> & {
source: Exclude<PaneAgentEvidenceSource, 'process'>
executionHostProvenance?: never
}
type PaneAgentIdentityProcessFact = Omit<PaneAgentEvidence<TuiAgent>, 'source'> & {
source: 'process'
/** Process observations must identify the execution host that produced them. */
executionHostProvenance: 'execution-host' | 'distro-origin' | 'target-origin'
}
export type PaneAgentIdentityEvidenceFact =
| PaneAgentIdentityNonProcessFact
| PaneAgentIdentityProcessFact
export type PaneAgentIdentityOwnerFact = {
agent: TuiAgent
run: PaneAgentRunKey
}
export type PaneAgentIdentityEvidenceInput = {
facts: readonly PaneAgentIdentityEvidenceFact[]
currentRun?: PaneAgentRunKey
owner?: PaneAgentIdentityOwnerFact
}
/** Stable encoding. Do not derive telemetry bits from array indexes. */
export const PANE_AGENT_IDENTITY_SOURCE_BITS: Readonly<Record<PaneAgentEvidenceSource, number>> = {
'live-hook': 1,
process: 2,
launch: 4,
'completed-hook': 8,
'sleeping-session': 16,
sibling: 32,
title: 64
}
export type PaneAgentIdentityEvidence = {
evidence: readonly PaneAgentEvidence<TuiAgent>[]
resolvedAgent: TuiAgent | null
sourceMask: number
identityNull: boolean
ambiguousTopRank: boolean
}
/** Assemble already-redacted authority facts; raw titles/commands never enter this layer. */
export function assemblePaneAgentIdentityEvidence(
input: PaneAgentIdentityEvidenceInput
): PaneAgentIdentityEvidence {
const evidence = input.facts
.filter((fact) => {
if (fact.source === 'process' && fact.executionHostProvenance === undefined) {
return false
}
if (fact.source === 'sibling') {
return false
}
if (
input.currentRun &&
fact.run?.authorityId === input.currentRun.authorityId &&
fact.run.incarnation !== input.currentRun.incarnation
) {
return false
}
return true
})
.sort(
(left, right) =>
PANE_AGENT_EVIDENCE_SOURCES.indexOf(left.source) -
PANE_AGENT_EVIDENCE_SOURCES.indexOf(right.source)
)
const resolved = resolvePaneAgentIdentity({
evidence,
currentRun: input.currentRun
})
let resolvedAgent = resolved.agent
if (
resolvedAgent &&
input.owner &&
input.currentRun &&
input.owner.run.authorityId === input.currentRun.authorityId &&
input.owner.run.incarnation === input.currentRun.incarnation
) {
// Owner compatibility is deliberately same-run only; incomparable owners do not rewrite.
if (
input.owner.agent !== resolvedAgent &&
resolveCompatibleAgentTypeForOwner(resolvedAgent, input.owner.agent) === input.owner.agent
) {
resolvedAgent = input.owner.agent
}
}
return {
evidence,
resolvedAgent,
sourceMask: evidence.reduce(
(mask, item) => mask | PANE_AGENT_IDENTITY_SOURCE_BITS[item.source],
0
),
identityNull: resolved.agent === null,
ambiguousTopRank: resolved.ambiguousAt !== undefined
}
}
export type PaneAgentIdentityAvailability = {
hostKind: PaneAgentIdentityHostKind
launchMode: PaneAgentIdentityLaunchMode
sourceMask: number
identityNull: boolean
ambiguousTopRank: boolean
}
export function reducePaneAgentIdentityAvailability(
hostKind: PaneAgentIdentityHostKind,
launchMode: PaneAgentIdentityLaunchMode,
input: PaneAgentIdentityEvidenceInput
): PaneAgentIdentityAvailability {
const assembled = assemblePaneAgentIdentityEvidence(input)
return {
hostKind,
launchMode,
sourceMask: assembled.sourceMask,
identityNull: assembled.identityNull,
ambiguousTopRank: assembled.ambiguousTopRank
}
}
export const PANE_AGENT_TITLE_SOURCE_BIT = PANE_AGENT_IDENTITY_SOURCE_BITS.title
export const PANE_AGENT_IDENTITY_SOURCE_ORDER = PANE_AGENT_EVIDENCE_SOURCES
@@ -0,0 +1,145 @@
import type { PaneAgentIdentityHostKind } from './pane-agent-identity-evidence'
export const RUN_SETTLE_MS = 5_000
export const TITLE_CANDIDATE_WINDOW_MS = 30_000
export const MAX_PENDING_IDENTITY_OBSERVATIONS = 4_096
export const MAX_LIVE_FINALIZED_RUN_KEYS = 4_096
export type PaneAgentIdentityObservationSchedulerDeps = {
now?: () => number
onRunFreeze: (key: string) => void
onTitleCandidateFreeze: (hostKind: PaneAgentIdentityHostKind, key: string) => void
onFinalizedOverflow?: (
kind: 'run' | 'title-candidate',
hostKind: PaneAgentIdentityHostKind | undefined,
key: string
) => void
}
/** Event-driven settle/deadline scheduler; it never probes or polls a host. */
export class PaneAgentIdentityObservationScheduler {
private readonly pending = new Map<string, ReturnType<typeof setTimeout>>()
private readonly candidates = new Map<string, ReturnType<typeof setTimeout>>()
private readonly finalized = new Set<string>()
private readonly now: () => number
constructor(private readonly deps: PaneAgentIdentityObservationSchedulerDeps) {
this.now = deps.now ?? Date.now
}
scheduleRun(key: string, settleMs = RUN_SETTLE_MS): boolean {
if (
this.pending.size + this.candidates.size >= MAX_PENDING_IDENTITY_OBSERVATIONS &&
!this.pending.has(key)
) {
return false
}
if (this.finalized.has(key)) {
return false
}
this.cancelRun(key)
const timer = setTimeout(
() => {
if (!this.freezeRun(key)) {
this.deps.onFinalizedOverflow?.('run', undefined, key)
}
},
Math.max(0, settleMs)
)
timer.unref?.()
this.pending.set(key, timer)
return true
}
scheduleTitleCandidate(
key: string,
hostKind: PaneAgentIdentityHostKind,
windowMs = TITLE_CANDIDATE_WINDOW_MS
): boolean {
if (
this.pending.size + this.candidates.size >= MAX_PENDING_IDENTITY_OBSERVATIONS &&
!this.candidates.has(key)
) {
return false
}
if (this.candidates.has(key)) {
return true
}
const timer = setTimeout(
() => {
if (!this.freezeTitleCandidate(key, hostKind)) {
this.deps.onFinalizedOverflow?.('title-candidate', hostKind, key)
}
},
Math.max(0, windowMs)
)
timer.unref?.()
this.candidates.set(key, timer)
return true
}
attestRun(key: string): boolean {
return this.scheduleRun(key)
}
freezeRun(key: string): boolean {
if (this.finalized.has(key)) {
return false
}
if (this.finalized.size >= MAX_LIVE_FINALIZED_RUN_KEYS) {
this.cancelRun(key)
return false
}
this.cancelRun(key)
this.finalized.add(key)
this.deps.onRunFreeze(key)
return true
}
freezeTitleCandidate(key: string, hostKind: PaneAgentIdentityHostKind): boolean {
if (this.finalized.has(key)) {
return false
}
if (this.finalized.size >= MAX_LIVE_FINALIZED_RUN_KEYS) {
this.cancelCandidate(key)
return false
}
this.cancelCandidate(key)
this.finalized.add(key)
this.deps.onTitleCandidateFreeze(hostKind, key)
return true
}
cancelCandidate(key: string): void {
const timer = this.candidates.get(key)
if (timer) {
clearTimeout(timer)
}
this.candidates.delete(key)
}
cancelRun(key: string): void {
const timer = this.pending.get(key)
if (timer) {
clearTimeout(timer)
}
this.pending.delete(key)
}
exitOrRebind(key: string): void {
this.cancelRun(key)
this.cancelCandidate(key)
this.finalized.delete(key)
}
shutdown(): void {
for (const timer of this.pending.values()) {
clearTimeout(timer)
}
for (const timer of this.candidates.values()) {
clearTimeout(timer)
}
this.pending.clear()
this.candidates.clear()
this.finalized.clear()
}
isPendingRun(key: string): boolean {
return this.pending.has(key)
}
isPendingCandidate(key: string): boolean {
return this.candidates.has(key)
}
get clock(): number {
return this.now()
}
}
+3
View File
@@ -10,6 +10,7 @@ import type { TabGroupLayoutNode } from './tab-types'
import type { TerminalExitCause } from './terminal-exit-cause'
import type { TerminalPaneLayoutNode } from './terminal-tab-types'
import type { TuiAgent } from './tui-agent'
import type { PaneAgentIdentityAvailabilitySnapshot } from './pane-agent-identity-availability'
export type RuntimeTerminalSummary = {
handle: string
@@ -93,6 +94,8 @@ export type RuntimeTerminalListResult = {
truncated: boolean
/** Absent from hosts that predate the field; treat that scope as unverifiable. */
hostScope?: RuntimeTerminalListHostScope
/** Optional cumulative, privacy-reduced pane identity census from the authority. */
agentIdentityAvailability?: PaneAgentIdentityAvailabilitySnapshot
}
export type RuntimeTerminalOrphanAdoptionClaim = {
+40
View File
@@ -1351,6 +1351,45 @@ const terminalPaneSplitSchema = z
})
.strict()
const paneAgentIdentityHostKindSchema = z.enum(['native', 'wsl-host', 'wsl-distro', 'ssh', 'relay'])
const paneAgentIdentityLaunchModeSchema = z.enum(['typed', 'orca-launch', 'resume'])
const paneAgentIdentityAggregateRowSchema = z
.object({
kind: z.literal('aggregate'),
host_kind: paneAgentIdentityHostKindSchema,
launch_mode: paneAgentIdentityLaunchModeSchema,
attested_runs: z.number().int().min(1).max(1_000_000_000),
no_evidence: z.number().int().min(0).max(1_000_000_000),
title_only: z.number().int().min(0).max(1_000_000_000),
identity_null: z.number().int().min(0).max(1_000_000_000),
ambiguous_top_rank: z.number().int().min(0).max(1_000_000_000)
})
.strict()
const paneAgentIdentityCoverageRowSchema = z
.object({
kind: z.literal('coverage'),
host_kind: paneAgentIdentityHostKindSchema,
reason: z.enum([
'snapshot',
'baseline',
'epoch_changed',
'counter_regressed',
'capacity',
'candidate',
'overflow',
'truncated'
]),
count: z.number().int().min(1).max(1_000_000_000)
})
.strict()
const paneAgentIdentityAvailabilitySchema = z
.object({
rows: z
.array(z.union([paneAgentIdentityAggregateRowSchema, paneAgentIdentityCoverageRowSchema]))
.max(64)
})
.strict()
// Why: measures the changed-on-disk conflict flow (issue #7265) per transport; deliberately path-free.
const editorExternalChangeConflictShownSchema = z
.object({
@@ -1518,6 +1557,7 @@ export const eventSchemas = {
editor_external_change_conflict_action: editorExternalChangeConflictActionSchema,
direct_ssh_reconnect_operation: directSshReconnectOperationSchema,
pane_agent_identity_availability: paneAgentIdentityAvailabilitySchema,
smart_sort_class_distribution: smartSortClassDistributionSchema,
smart_sort_class_1_promotion: smartSortClass1PromotionSchema,