fix(mobile): restore Codex chat session identity (#11636)

* fix(mobile): restore Codex chat session identity

* fix(mobile): reconcile native chat session ownership
This commit is contained in:
Brennan Benson 2026-07-31 00:40:11 -07:00 committed by GitHub
parent 451baa1bc4
commit 4f00b21186
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 161 additions and 3 deletions

View File

@ -22157,6 +22157,128 @@ describe('OrcaRuntimeService', () => {
])
})
it.each([
{
behavior: 'fills a missing renderer session',
hookAgentType: 'codex',
hookOffset: 0,
rendererSessionId: null,
expectedSessionId: 'hook-session'
},
{
behavior: 'replaces a stale renderer session at the same event timestamp',
hookAgentType: 'codex',
hookOffset: 0,
rendererSessionId: 'stale-renderer-session',
expectedSessionId: 'hook-session'
},
{
behavior: 'preserves a renderer session newer than the hook row',
hookAgentType: 'codex',
hookOffset: -1,
rendererSessionId: 'newer-renderer-session',
expectedSessionId: 'newer-renderer-session'
},
{
behavior: 'rejects a hook session owned by another agent',
hookAgentType: 'claude',
hookOffset: 1,
rendererSessionId: null,
expectedSessionId: null
}
] as const)(
'$behavior',
async ({ hookAgentType, hookOffset, rendererSessionId, expectedSessionId }) => {
const leafId = '11111111-1111-4111-8111-111111111111'
const paneKey = `codex-tab:${leafId}`
const providerSession = {
key: 'session_id' as const,
id: 'hook-session'
}
const now = Date.now()
const runtime = new OrcaRuntimeService(store, undefined, {
getAgentStatusSnapshot: () => [
{
paneKey,
state: 'done',
prompt: '',
agentType: hookAgentType,
connectionId: null,
receivedAt: now + hookOffset,
stateStartedAt: now + hookOffset,
tabId: 'codex-tab',
worktreeId: TEST_WORKTREE_ID,
providerSession
}
]
})
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: null,
activeTabId: `codex-tab::${leafId}`,
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: `codex-tab::${leafId}`,
parentTabId: 'codex-tab',
leafId,
title: 'Codex',
launchAgent: 'codex',
agentStatus: {
state: 'working',
prompt: 'Reply with MOBILE QA OK and nothing else.',
updatedAt: now,
stateStartedAt: now,
agentType: 'codex',
paneKey,
stateHistory: [],
...(rendererSessionId
? {
providerSession: {
key: 'session_id' as const,
id: rendererSessionId
}
}
: {})
},
isActive: true
}
]
}
]
})
const result = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
expect(result.tabs[0]).toEqual(
expect.objectContaining({
type: 'terminal',
agentStatus: expect.objectContaining({
state: 'working',
prompt: 'Reply with MOBILE QA OK and nothing else.',
agentType: 'codex'
})
})
)
if (expectedSessionId) {
expect(result.tabs[0]).toHaveProperty('agentStatus.providerSession', {
key: 'session_id',
id: expectedSessionId
})
} else {
expect(result.tabs[0]).not.toHaveProperty('agentStatus.providerSession')
}
}
)
it('preserves authoritative OMP identity for Pi-compatible remote terminal snapshots', async () => {
const runtime = new OrcaRuntimeService(store)
const leafId = '11111111-1111-4111-8111-111111111111'

View File

@ -69,7 +69,8 @@ import {
import {
hasCompatibleAgentTitleIdentity,
normalizeCompatibleAgentStatusEntryForOwner,
normalizeCompatibleAgentTitleForOwner
normalizeCompatibleAgentTitleForOwner,
resolveCompatibleAgentTypeForOwner
} from '../../shared/agent-title-owner'
import { resolvePaneAgentOwner } from '../../shared/pane-agent-owner'
import {
@ -28674,6 +28675,9 @@ export class OrcaRuntimeService {
const retainedAgentStatus = tab.agentStatus
? null
: this.getFreshRetainedAgentStatusForMobileTab(paneKey, liveLeafPty ?? mobileStatusPty, tab)
const hookAgentStatus = tab.agentStatus
? this.getHookAgentRowForPane(getHookRowsForPane(paneKey))
: null
const leafTitle = leaf
? getLatestAgentCandidateTitle(
{ title: leaf.paneTitle, updatedAt: leaf.paneTitleUpdatedAt },
@ -28691,7 +28695,11 @@ export class OrcaRuntimeService {
const ownerAgent =
resolvePaneAgentOwner({
launchAgent,
hookAgent: tab.agentStatus?.agentType ?? retainedAgentStatus?.payload.agentType ?? null
hookAgent:
tab.agentStatus?.agentType ??
hookAgentStatus?.agentType ??
retainedAgentStatus?.payload.agentType ??
null
}) ??
liveLeafPty?.foregroundAgent ??
pty?.foregroundAgent ??
@ -28702,8 +28710,32 @@ export class OrcaRuntimeService {
)
const liveTitleEvidence = leafTitle ?? ptyTitle
const liveTitleEvidenceClassification = classifyAgentTitle(liveTitleEvidence)
// Why: renderer status can precede hook session identity, leaving native chat with no transcript address.
const rendererStatusAgent =
resolveCompatibleAgentTypeForOwner(tab.agentStatus?.agentType, ownerAgent) ??
ownerAgent ??
undefined
const hookSessionAgent = resolveCompatibleAgentTypeForOwner(
hookAgentStatus?.providerSessionAgentType,
ownerAgent
)
const hookSessionMatchesRenderer =
!rendererStatusAgent || !hookSessionAgent || rendererStatusAgent === hookSessionAgent
const hookProviderSession =
hookAgentStatus?.providerSession &&
hookSessionMatchesRenderer &&
(!tab.agentStatus?.providerSession ||
(hookAgentStatus.providerSessionReceivedAt ?? -1) >= tab.agentStatus.updatedAt)
? hookAgentStatus.providerSession
: tab.agentStatus?.providerSession
const normalizedTabAgentStatus = tab.agentStatus
? normalizeCompatibleAgentStatusEntryForOwner(tab.agentStatus, ownerAgent)
? normalizeCompatibleAgentStatusEntryForOwner(
{
...tab.agentStatus,
...(hookProviderSession ? { providerSession: hookProviderSession } : {})
},
ownerAgent
)
: null
// Why: keep rich hook status on a live prompt/tool (authoritative even under a non-agent title), else interactivePrompt is lost.
const hasLiveAgentSignal =
@ -28942,6 +28974,8 @@ export class OrcaRuntimeService {
* read would keep offering native chat for what is now a plain shell. */
private getHookAgentRowForPane(rows: readonly AgentStatusIpcPayload[]): {
providerSession: AgentProviderSessionMetadata | null
providerSessionAgentType: string | null
providerSessionReceivedAt: number | null
agentType: string | null
} {
let session: AgentStatusIpcPayload | null = null
@ -28967,6 +29001,8 @@ export class OrcaRuntimeService {
}
return {
providerSession: session?.providerSession ?? null,
providerSessionAgentType: session?.agentType ?? null,
providerSessionReceivedAt: session?.receivedAt ?? null,
agentType: agent?.agentType ?? null
}
}