mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 00:03:52 +00:00
* fix(agent-sleep): keep finished and interrupted agent sessions resumable after sleeping a workspace Manual workspace sleep ran a liveness filter over the panes it was about to kill (isValidManualSleepLiveAgentEntry), then wiped every pre-existing sleeping record in the worktree. A done, interrupted, typed-into, or >30-min-idle pane therefore lost its only resume handle and woke as a bare shell. A second filter at wake discarded any record carrying `interrupted: true`, which also killed interrupted sessions across an app restart. Capture now records every resumable pane, normalizing only `updatedAt` and `interrupted` and preserving the entry's real `state` so a finished pane keeps its passive record and resumes in place when its tab is opened instead of spawning a duplicate tab. The wake-side interrupted check is gone. A legacy-orchestration-worker block is carried onto the replacement record, and the Pi-compatible promoted checkpoint is no longer overwritten by a re-derived live record. Closes #11598 * fix(agent-sleep): keep durable slept records a repeat sleep cannot re-derive removeSleepingRecordsReplacedByManualWorktreeSleep wiped every record in the worktree, and only the freshly captured set was merged back. A slept `done` pane stays passive until its tab is opened, so a second sleep found no live status row to rebuild its record from and deleted the pane's only `--resume` handle with nothing written back — the original loss of #11598, one wake/sleep cycle later. The wipe now skips a record with no replacement in the new capture set when it is a durable capture (`origin` `worktree-sleep` or `quit`). Provisional `live`/legacy checkpoints are still cleared, so an unresumable Pi row does not survive a sleep it cannot back. Co-authored-by: Orca <help@stably.ai> * fix(agent-sleep): keep a slept workspace's finished panes out of the mobile wake fan-out A manual sleep now records every finished pane, and those passive records fed wakeSleepingAgentsForWorktreeInBackground step (b), which background-mounts one tab per passive record. A phone opening a slept 12-tab workspace would have cold-restored 12 agents at once, undoing the process shedding the sleep was for. Manual-sleep captures of finished panes carry restoreOnTabOpenOnly; step (b) skips them and the pane resumes in place when its own tab is opened, which is what desktop activation already did and what the phone's per-tab mount provides. Co-authored-by: Orca <help@stably.ai> * fix(agent-sleep): close the retained-row and shared-claim gaps in slept-session capture Three narrow holes left by the manual-sleep capture rewrite, all in the same "a slept session must stay resumable" contract: - The retained pass captured `retained.entry` verbatim, so a retained row - stale by construction, since it exists only after the pane's pty died - produced a `working` record that wake then discarded on the >30min staleness rule. It now takes the same `updatedAt`/`interrupted` normalization and the same `automaticResumeBlockedBy` carry-over as the live pass. - The retained pass also ran after the `origin: 'live'` promotion loop without its guard, so a pane holding both a promoted checkpoint and a retained row had the checkpoint (connectionId, transcript identity, active `working` class) overwritten by a re-derived passive record. - The mobile wake filtered `restoreOnTabOpenOnly` records *after* canonicalization, so a lazy record sharing a provider-session claim with an eligible hibernated record could win the claim, delete the hibernated record as a duplicate, and then be skipped - stranding the session with nothing mounted. --------- Co-authored-by: Orca <help@stably.ai>
570 lines
17 KiB
TypeScript
570 lines
17 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { parseWorkspaceSession } from './workspace-session-schema'
|
|
|
|
describe('parseWorkspaceSession sleeping agents', () => {
|
|
it('preserves valid sleeping agent resume records', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
terminalTitle: 'Codex',
|
|
lastAssistantMessage: 'done',
|
|
launchConfig: {
|
|
agentArgs: '',
|
|
agentEnv: {}
|
|
},
|
|
origin: 'live'
|
|
}
|
|
}
|
|
})
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.agent).toBe('codex')
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.origin).toBe('live')
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.launchConfig).toEqual({
|
|
agentArgs: '',
|
|
agentEnv: {}
|
|
})
|
|
}
|
|
})
|
|
|
|
it('preserves the authoritative Pi session file through hydration', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'pi',
|
|
providerSession: {
|
|
key: 'session_id',
|
|
id: 'pi-session',
|
|
transcriptPath: '/tmp/pi-session.jsonl'
|
|
},
|
|
prompt: '',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 10,
|
|
origin: 'live'
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.providerSession).toEqual(
|
|
{
|
|
key: 'session_id',
|
|
id: 'pi-session',
|
|
transcriptPath: '/tmp/pi-session.jsonl'
|
|
}
|
|
)
|
|
}
|
|
})
|
|
|
|
it('preserves the AI Vault OMP resume file through hydration', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'omp',
|
|
providerSession: { key: 'session_id', id: 'omp-session' },
|
|
prompt: '',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 10,
|
|
launchConfig: {
|
|
agentArgs: '',
|
|
agentEnv: {},
|
|
ompResumeFilePath: '/custom/omp-sessions/project/session.jsonl'
|
|
},
|
|
origin: 'quit'
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(
|
|
result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.launchConfig
|
|
?.ompResumeFilePath
|
|
).toBe('/custom/omp-sessions/project/session.jsonl')
|
|
}
|
|
})
|
|
|
|
it('drops Pi sleeping-agent records without an authoritative session file', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'pi',
|
|
providerSession: { key: 'session_id', id: 'pi-session' },
|
|
prompt: '',
|
|
state: 'done',
|
|
capturedAt: 10,
|
|
updatedAt: 10,
|
|
origin: 'live'
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('drops invalid sleeping agent launch config without dropping the record', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
launchConfig: {
|
|
agentArgs: '--model high',
|
|
agentEnv: { 'BAD=KEY': 'value' }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
const record = result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']
|
|
expect(record?.agent).toBe('codex')
|
|
expect(record?.launchConfig).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('drops launch config with prototype-polluting env keys without dropping siblings', () => {
|
|
const sessions = JSON.parse(`{
|
|
"__proto__": {
|
|
"paneKey": "__proto__",
|
|
"worktreeId": "wt",
|
|
"agent": "codex",
|
|
"providerSession": { "key": "session_id", "id": "bad-session" },
|
|
"prompt": "bad",
|
|
"state": "working",
|
|
"capturedAt": 10,
|
|
"updatedAt": 9
|
|
},
|
|
"tab1:pane-1": {
|
|
"paneKey": "tab1:pane-1",
|
|
"tabId": "tab1",
|
|
"worktreeId": "wt",
|
|
"agent": "codex",
|
|
"providerSession": { "key": "session_id", "id": "codex-session" },
|
|
"prompt": "continue",
|
|
"state": "working",
|
|
"capturedAt": 10,
|
|
"updatedAt": 9,
|
|
"launchConfig": {
|
|
"agentArgs": "",
|
|
"agentEnv": { "__proto__": "polluted" }
|
|
}
|
|
}
|
|
}`)
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: sessions
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(
|
|
Object.prototype.hasOwnProperty.call(
|
|
result.value.sleepingAgentSessionsByPaneKey ?? {},
|
|
'__proto__'
|
|
)
|
|
).toBe(false)
|
|
const record = result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']
|
|
expect(record?.agent).toBe('codex')
|
|
expect(record?.launchConfig).toBeUndefined()
|
|
expect(({} as Record<string, unknown>).polluted).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('preserves sleeping agent launch env values with whitespace characters', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
launchConfig: {
|
|
agentArgs: '',
|
|
agentEnv: { MULTILINE: 'line1\nline2\tok' }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(
|
|
result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.launchConfig?.agentEnv
|
|
).toEqual({ MULTILINE: 'line1\nline2\tok' })
|
|
}
|
|
})
|
|
|
|
it('drops sleeping agent launch config with NUL env values without dropping the record', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
launchConfig: {
|
|
agentArgs: '',
|
|
agentEnv: { BAD_VALUE: 'ok\0bad' }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
const record = result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']
|
|
expect(record?.agent).toBe('codex')
|
|
expect(record?.launchConfig).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('preserves sleeping agent record origin across hydration', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'devin',
|
|
providerSession: { key: 'session_id', id: 'devin-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
origin: 'quit'
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.origin).toBe('quit')
|
|
}
|
|
})
|
|
|
|
it('preserves the tab-open-only restore flag across hydration', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'claude',
|
|
providerSession: { key: 'session_id', id: 'claude-session' },
|
|
prompt: 'continue',
|
|
state: 'done',
|
|
capturedAt: 10,
|
|
updatedAt: 10,
|
|
origin: 'worktree-sleep',
|
|
restoreOnTabOpenOnly: true
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
// Why: dropping it on restart resurrects the mobile-wake fan-out this flag exists to stop.
|
|
expect(
|
|
result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.restoreOnTabOpenOnly
|
|
).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('preserves interrupted sleeping agent records across hydration', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'done',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
interrupted: true,
|
|
origin: 'worktree-sleep'
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.interrupted).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('preserves legacy live sleeping agent origins across hydration', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9,
|
|
origin: 'live'
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.origin).toBe('live')
|
|
}
|
|
})
|
|
|
|
it('drops malformed sleeping agent resume records without failing the whole session', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
worktreeId: 'wt',
|
|
agent: 'definitely-not-an-agent',
|
|
providerSession: { key: 'session_id', id: 'bogus-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
}
|
|
}
|
|
})
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('preserves valid sleeping agent resume records when sibling records are malformed', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
},
|
|
'tab2:pane-1': {
|
|
paneKey: 'tab2:pane-1',
|
|
worktreeId: 'wt',
|
|
agent: 'not-real',
|
|
providerSession: { key: 'session_id', id: 'bad-session' },
|
|
prompt: 'ignore me',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
}
|
|
}
|
|
})
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.agent).toBe('codex')
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab2:pane-1']).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('drops sleeping agent records with unsafe provider session ids without dropping valid siblings', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
},
|
|
'tab2:pane-1': {
|
|
paneKey: 'tab2:pane-1',
|
|
tabId: 'tab2',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: '--last' },
|
|
prompt: 'ignore me',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
}
|
|
}
|
|
})
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.providerSession.id).toBe(
|
|
'codex-session'
|
|
)
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab2:pane-1']).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
it('drops sleeping agent records whose embedded pane key differs from the map key', () => {
|
|
const result = parseWorkspaceSession({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {},
|
|
sleepingAgentSessionsByPaneKey: {
|
|
'tab1:pane-1': {
|
|
paneKey: 'tab1:pane-1',
|
|
tabId: 'tab1',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'codex-session' },
|
|
prompt: 'continue',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
},
|
|
'tab2:pane-1': {
|
|
paneKey: 'other-tab:pane-1',
|
|
tabId: 'tab2',
|
|
worktreeId: 'wt',
|
|
agent: 'codex',
|
|
providerSession: { key: 'session_id', id: 'mismatched-session' },
|
|
prompt: 'ignore me',
|
|
state: 'working',
|
|
capturedAt: 10,
|
|
updatedAt: 9
|
|
}
|
|
}
|
|
})
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab1:pane-1']?.providerSession.id).toBe(
|
|
'codex-session'
|
|
)
|
|
expect(result.value.sleepingAgentSessionsByPaneKey?.['tab2:pane-1']).toBeUndefined()
|
|
}
|
|
})
|
|
})
|