test(agent-status): inventory legacy pane identity surfaces (#16575)

* feat(agent-status): measure identity evidence before migrating any consumer

PR 1 of the identity migration. It changes no displayed or routed identity — it only measures.

Why measure first: the hierarchy shipped in #16148/#16157 has zero consumers, while ~31 sites still
derive identity independently. Every migration decision after this is currently a guess, including
the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No
Claude status shown", and this design trades toward showing nothing when uncertain, so the blank
rate has to be a number before any surface moves.

- `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers
  stop each inventing their own ladder.
- `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host /
  wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of
  which sources were present and whether the resolver returned null or ambiguous. No titles,
  prompts, paths, handles, or agent text.
- `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper
  gains a new production caller, so the surface cannot grow while the migration runs.

Three review findings are encoded rather than deferred: launch stays above run-key-less completed
hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized
before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read
as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host
observes the distro wrapper rather than the agent inside it.

The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay
cohorts collected from real use, and that review is the gate on PR 3 — not this PR.

* test(agent-status): keep identity migration inventory-only

* test(agent-status): reuse reliable source scanner

* test(agent-status): bound inventory scan work

* test(agent-status): avoid inventory path false negatives

* test(agent-status): refresh identity inventory after base repair

* test(agent-status): correct inventory classifications

* test(agent-status): correct action boundary inventory

* test(agent-status): pin inventory occurrence counts

* test(agent-status): fail closed on scanner desync
This commit is contained in:
Brennan Benson
2026-08-26 14:33:02 -07:00
committed by GitHub
parent 87ede54eb8
commit a624e7cd5d
3 changed files with 371 additions and 5 deletions
@@ -115,8 +115,3 @@ export function resolvePaneAgentActivity(
livePtyRequired: false
}
}
// Deliberately absent: a resolvePaneAgentOwner precedence resolver. The only
// Phase 2 identity consumer (native-chat toggle) reads hook identity without a
// freshness gate, so a gated owner resolver would change its behavior; the
// owner resolver lands with its first real consumer in a later slice.
@@ -0,0 +1,363 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { glob } from 'tinyglobby'
import {
blankStringContents,
blankStringContentsDesynced,
isTestFile,
stripComments
} from './source-scan/source-tree-scan'
const HELPERS = [
'getAgentLabel',
'isClaudeAgent',
'titleHasAgentName',
'buildAgentNameRe',
'resolveTerminalTitleAgentType',
'resolveExplicitTerminalTitleAgentType',
'resolveCommittedTitleAgentType',
'resolvePaneAgentOwner',
'resolveCompatibleAgentTypeForOwner'
] as const
const TEST_SUPPORT_PATHS = new Set([
'src/renderer/src/components/terminal-pane/pty-connection-test-environment.ts'
])
type Helper = (typeof HELPERS)[number]
type InventoryPath = string | readonly [path: string, occurrences: number]
type Classification =
| 'parser-implementation'
| 'activity-only'
| 'enum-formatter'
| 'evidence-producer'
| 'identity-consumer'
| 'action-consumer'
type InventoryGroup = {
helper: Helper
classification: Classification
paths: readonly InventoryPath[]
}
const INVENTORY: readonly InventoryGroup[] = [
{
helper: 'getAgentLabel',
classification: 'enum-formatter',
paths: [
[
'src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.tsx',
2
],
['src/renderer/src/components/automations/AutomationListLocalRows.tsx', 2],
'src/renderer/src/components/automations/automation-draft-model.ts',
['src/renderer/src/components/automations/automation-list-search-rows.ts', 2],
['src/renderer/src/components/dashboard-popout/AgentMapSnapshotWorkspaceMenu.tsx', 2],
['src/renderer/src/components/dashboard-popout/AgentMapWorktreeRingNode.tsx', 2],
['src/renderer/src/components/settings/QuickCommandsList.tsx', 2],
['src/renderer/src/components/tab-bar/TabBarQuickCommandItem.tsx', 2],
['src/renderer/src/components/tab-bar/TabBarQuickCommandsMenu.tsx', 2],
'src/renderer/src/lib/agent-catalog.tsx',
['src/renderer/src/lib/launch-agent-session-continuation.ts', 3],
['src/renderer/src/lib/orchestration-skill-coverage.ts', 2]
]
},
{
helper: 'getAgentLabel',
classification: 'activity-only',
paths: [['src/renderer/src/lib/pane-agent-evidence.ts', 2]]
},
{
helper: 'getAgentLabel',
classification: 'parser-implementation',
paths: [
'src/renderer/src/lib/agent-status.ts',
'src/shared/agent-detection.ts',
'src/shared/agent-title-identity.ts',
['src/shared/agent-title-owner.ts', 2],
['src/shared/terminal-title-agent-type.ts', 2]
]
},
{
helper: 'isClaudeAgent',
classification: 'action-consumer',
paths: [
['src/renderer/src/components/terminal-pane/cache-timer-seeding.ts', 2],
['src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.ts', 2],
['src/renderer/src/components/terminal-pane/pty-connection/agent-task-complete-notify.ts', 2],
['src/renderer/src/store/terminals/terminal-ephemeral-state.ts', 2]
]
},
{
helper: 'isClaudeAgent',
classification: 'action-consumer',
paths: [
['src/renderer/src/components/terminal-pane/pty-connection/command-inferred-pane-agent.ts', 2]
]
},
{
helper: 'isClaudeAgent',
classification: 'parser-implementation',
paths: [
'src/renderer/src/lib/agent-status.ts',
'src/shared/agent-detection.ts',
['src/shared/agent-title-identity.ts', 2],
['src/shared/terminal-title-agent-type.ts', 2]
]
},
{
helper: 'titleHasAgentName',
classification: 'parser-implementation',
paths: [
'src/shared/agent-detection.ts',
'src/shared/agent-name-token-match.ts',
['src/shared/agent-title-core.ts', 4],
['src/shared/agent-title-evidence.ts', 2],
['src/shared/agent-title-identity.ts', 11],
['src/shared/terminal-title-agent-type.ts', 14]
]
},
{
helper: 'titleHasAgentName',
classification: 'evidence-producer',
paths: [['src/renderer/src/hooks/ipc-events/agent-status-routing.ts', 2]]
},
{
helper: 'buildAgentNameRe',
classification: 'action-consumer',
paths: [['src/main/runtime/orchestration/groups.ts', 2]]
},
{
helper: 'buildAgentNameRe',
classification: 'parser-implementation',
paths: [['src/shared/agent-name-token-match.ts', 2]]
},
{
helper: 'resolveTerminalTitleAgentType',
classification: 'identity-consumer',
paths: [['src/renderer/src/lib/notes-send-agent-targets.ts', 2]]
},
{
helper: 'resolveTerminalTitleAgentType',
classification: 'parser-implementation',
paths: [['src/shared/terminal-title-agent-type.ts', 2]]
},
{
helper: 'resolveExplicitTerminalTitleAgentType',
classification: 'identity-consumer',
paths: [
['mobile/src/session/mobile-terminal-tab-agent.ts', 2],
['src/renderer/src/lib/open-tab-occupant-agent.ts', 2],
['src/renderer/src/lib/use-tab-agent.ts', 3]
]
},
{
helper: 'resolveExplicitTerminalTitleAgentType',
classification: 'parser-implementation',
paths: [
['src/renderer/src/lib/pane-agent-evidence.ts', 2],
'src/shared/terminal-title-agent-type.ts'
]
},
{
helper: 'resolveCommittedTitleAgentType',
classification: 'action-consumer',
paths: [
['src/renderer/src/components/native-chat/use-native-chat-toggle-shortcut.ts', 3],
['src/renderer/src/components/terminal-pane/pty-connection/connect-pane-pty.ts', 2],
['src/renderer/src/components/terminal-pane/terminal-ctrl-enter.ts', 2],
['src/renderer/src/components/terminal-pane/terminal-windows-shift-enter.ts', 2],
['src/renderer/src/components/terminal-pane/use-notification-dispatch.ts', 2]
]
},
{
helper: 'resolveCommittedTitleAgentType',
classification: 'identity-consumer',
paths: [
['src/renderer/src/components/tab-bar/tab-bar-item-surface.tsx', 3],
['src/renderer/src/components/terminal-pane/native-chat-leaf-title-agent.ts', 4],
['src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts', 2]
]
},
{
helper: 'resolveCommittedTitleAgentType',
classification: 'parser-implementation',
paths: ['src/renderer/src/lib/pane-agent-evidence.ts']
},
{
helper: 'resolveCommittedTitleAgentType',
classification: 'action-consumer',
paths: [
['src/renderer/src/components/terminal-pane/pty-connection/command-inferred-pane-agent.ts', 2]
]
},
{
helper: 'resolvePaneAgentOwner',
classification: 'parser-implementation',
paths: ['src/shared/pane-agent-owner.ts']
},
{
helper: 'resolvePaneAgentOwner',
classification: 'evidence-producer',
paths: [['src/renderer/src/components/terminal-pane/parked-terminal-command-status.ts', 2]]
},
{
helper: 'resolvePaneAgentOwner',
classification: 'identity-consumer',
paths: [
['src/main/runtime/orca-runtime.ts', 3],
['src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts', 2],
['src/renderer/src/components/terminal-pane/pty-connection/shell-command-inference.ts', 2],
['src/renderer/src/lib/use-tab-agent.ts', 2],
['src/renderer/src/runtime/web-session-tabs-sync.ts', 3]
]
},
{
helper: 'resolveCompatibleAgentTypeForOwner',
classification: 'parser-implementation',
paths: [['src/shared/agent-title-owner.ts', 2]]
},
{
helper: 'resolveCompatibleAgentTypeForOwner',
classification: 'identity-consumer',
paths: [
['src/main/runtime/orca-runtime.ts', 3],
['src/renderer/src/components/sidebar/worktree-agent-rows.ts', 2],
['src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts', 2],
['src/renderer/src/lib/use-tab-agent.ts', 2]
]
},
{
helper: 'resolveCompatibleAgentTypeForOwner',
classification: 'action-consumer',
paths: [
['src/renderer/src/components/terminal-pane/pty-connection/agent-task-complete-notify.ts', 2],
[
'src/renderer/src/components/terminal-pane/pty-connection/command-inferred-pane-agent.ts',
3
],
['src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts', 3],
['src/renderer/src/components/terminal-pane/use-notification-dispatch.ts', 2]
]
},
{
helper: 'resolveCompatibleAgentTypeForOwner',
classification: 'evidence-producer',
paths: [
['src/renderer/src/components/terminal-pane/pty-connection/direct-ssh-retry-status.ts', 2],
['src/renderer/src/components/terminal-pane/pty-connection/title-spawn-bell.ts', 2]
]
}
]
const DIRECT_SINGLE_SOURCE_SURFACES: readonly {
path: string
classification: Classification
marker: string
}[] = [
{
path: 'src/renderer/src/components/terminal-pane/terminal-renderer-policy.ts',
classification: 'identity-consumer',
marker: 'resolveGeminiCompatFallback'
},
{
path: 'src/renderer/src/components/terminal-pane/terminal-title-evidence.ts',
classification: 'identity-consumer',
marker: 'resolvePaneTitleDecision'
},
{
path: 'src/renderer/src/components/terminal/terminal-close-copy-kind.ts',
classification: 'identity-consumer',
marker: 'resolveLeafCloseCopyKind'
},
{
path: 'src/main/runtime/orchestration/mailbox-pointer-delivery.ts',
classification: 'action-consumer',
marker: 'isCursorAgentTitle'
},
{
path: 'src/main/providers/local-pty-provider.ts',
classification: 'action-consumer',
marker: 'launchAgent'
},
{
path: 'src/renderer/src/components/terminal-pane/pty-connection/pane-serializer-settle.ts',
classification: 'action-consumer',
marker: 'sendStartupDraftPaste'
},
{
path: 'src/renderer/src/lib/active-agent-note-send.ts',
classification: 'action-consumer',
marker: 'sendNotesToActiveAgentSession'
},
{
path: 'src/renderer/src/components/native-chat/native-chat-runtime-send.ts',
classification: 'action-consumer',
marker: 'sendNativeChatMessage'
},
{
path: 'mobile/src/session/mobile-native-chat-send.ts',
classification: 'action-consumer',
marker: 'sendMobileNativeChatMessageWithOutcome'
},
{
path: 'mobile/src/session/mobile-native-chat-image-send.ts',
classification: 'action-consumer',
marker: 'pasteMobileNativeChatImagePaths'
},
{
path: 'mobile/src/session/pr-ai-triage-launch.ts',
classification: 'action-consumer',
marker: 'createTerminalAndSendPrompt'
}
]
describe('pane agent identity inventory ratchet', () => {
it('classifies every legacy helper definition, import, and callsite in src and mobile/src', async () => {
const files = await glob(['src/**/*.{ts,tsx}', 'mobile/src/**/*.{ts,tsx}'], {
ignore: ['**/*.test.*', '**/*.spec.*']
})
const actual: { helper: Helper; path: string; occurrences: number }[] = []
for (const path of files) {
if (isTestFile(path) || TEST_SUPPORT_PATHS.has(path)) {
continue
}
const rawSource = readFileSync(join(process.cwd(), path), 'utf8')
if (!HELPERS.some((helper) => rawSource.includes(helper))) {
continue
}
const decommentedSource = stripComments(rawSource)
if (blankStringContentsDesynced(decommentedSource)) {
throw new Error(`String scanner desynchronized while inventorying ${path}`)
}
const source = blankStringContents(decommentedSource)
for (const helper of HELPERS) {
const occurrences = source.match(new RegExp(`\\b${helper}\\b`, 'g'))?.length ?? 0
if (occurrences > 0) {
actual.push({ helper, path, occurrences })
}
}
}
const expected = INVENTORY.flatMap(({ helper, paths }) =>
paths.map((site) => {
const [path, occurrences] = typeof site === 'string' ? [site, 1] : site
return { helper, path, occurrences }
})
)
const byHelperAndPath = (left: (typeof actual)[number], right: (typeof actual)[number]) =>
left.helper.localeCompare(right.helper) || left.path.localeCompare(right.path)
expect(actual.sort(byHelperAndPath)).toEqual(expected.sort(byHelperAndPath))
})
it('pins direct single-source identity and action branches outside named helpers', () => {
for (const site of DIRECT_SINGLE_SOURCE_SURFACES) {
const source = stripComments(readFileSync(join(process.cwd(), site.path), 'utf8'))
expect({
path: site.path,
classification: site.classification,
hasMarker: source.includes(site.marker)
}).toEqual({ path: site.path, classification: site.classification, hasMarker: true })
}
})
})
@@ -85,6 +85,14 @@ describe('resolvePaneAgentIdentity', () => {
})
describe('mixed-version peers', () => {
it('keeps a run-key-less completed row from hijacking launch evidence', () => {
const result = resolve([
{ source: 'completed-hook', agent: 'claude' },
{ source: 'launch', agent: 'codex' }
])
expect(result).toMatchObject({ agent: 'codex', source: 'launch' })
})
it('treats evidence with no run id as eligible', () => {
// An old host publishes no run ids. Treating unknown as stale would blank every row.
const result = resolvePaneAgentIdentity({