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:
parent
451baa1bc4
commit
4f00b21186
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue