fix(hapi): consolidate approved web and Codex recovery fixes (#578)

This commit is contained in:
xiaobaifly7
2026-05-06 20:02:46 +08:00
committed by GitHub
parent 8185f0287e
commit 6df84df756
14 changed files with 1091 additions and 53 deletions
+193
View File
@@ -605,6 +605,199 @@ describe('session model', () => {
}
})
it('recovers claude resume session ID from stored messages when metadata is missing it', async () => {
const store = new Store(':memory:')
const engine = new SyncEngine(
store,
{} as never,
new RpcRegistry(),
{ broadcast() {} } as never
)
try {
const session = engine.getOrCreateSession(
'session-claude-resume-from-message',
{
path: '/tmp/project',
host: 'localhost',
machineId: 'machine-1',
flavor: 'claude'
},
null,
'default',
'sonnet'
)
store.messages.addMessage(session.id, {
role: 'agent',
content: {
type: 'output',
data: {
type: 'assistant',
sessionId: '7f5cd4ee-3a76-4601-a7b4-f9eb976bf515'
}
}
})
engine.getOrCreateMachine(
'machine-1',
{ host: 'localhost', platform: 'linux', happyCliVersion: '0.1.0' },
null,
'default'
)
engine.handleMachineAlive({ machineId: 'machine-1', time: Date.now() })
let capturedResumeSessionId: string | undefined
;(engine as any).rpcGateway.spawnSession = async (
_machineId: string,
_directory: string,
_agent: string,
_model?: string,
_modelReasoningEffort?: string,
_yolo?: boolean,
_sessionType?: 'simple' | 'worktree',
_worktreeName?: string,
resumeSessionId?: string
) => {
capturedResumeSessionId = resumeSessionId
return { type: 'success', sessionId: session.id }
}
;(engine as any).waitForSessionActive = async () => true
const result = await engine.resumeSession(session.id, 'default')
expect(result).toEqual({ type: 'success', sessionId: session.id })
expect(capturedResumeSessionId).toBe('7f5cd4ee-3a76-4601-a7b4-f9eb976bf515')
expect(store.sessions.getSession(session.id)?.metadata).toMatchObject({
claudeSessionId: '7f5cd4ee-3a76-4601-a7b4-f9eb976bf515'
})
} finally {
engine.stop()
}
})
it('recovers the newest claude session ID when stored messages contain multiple IDs', async () => {
const store = new Store(':memory:')
const engine = new SyncEngine(
store,
{} as never,
new RpcRegistry(),
{ broadcast() {} } as never
)
try {
const session = engine.getOrCreateSession(
'session-claude-resume-newest-message',
{
path: '/tmp/project',
host: 'localhost',
machineId: 'machine-1',
flavor: 'claude'
},
null,
'default',
'sonnet'
)
store.messages.addMessage(session.id, {
role: 'agent',
content: {
type: 'output',
data: {
type: 'assistant',
sessionId: '11111111-1111-4111-8111-111111111111'
}
}
})
store.messages.addMessage(session.id, {
role: 'agent',
content: {
type: 'output',
data: {
type: 'assistant',
sessionId: '22222222-2222-4222-8222-222222222222'
}
}
})
engine.getOrCreateMachine(
'machine-1',
{ host: 'localhost', platform: 'linux', happyCliVersion: '0.1.0' },
null,
'default'
)
engine.handleMachineAlive({ machineId: 'machine-1', time: Date.now() })
let capturedResumeSessionId: string | undefined
;(engine as any).rpcGateway.spawnSession = async (
_machineId: string,
_directory: string,
_agent: string,
_model?: string,
_modelReasoningEffort?: string,
_yolo?: boolean,
_sessionType?: 'simple' | 'worktree',
_worktreeName?: string,
resumeSessionId?: string
) => {
capturedResumeSessionId = resumeSessionId
return { type: 'success', sessionId: session.id }
}
;(engine as any).waitForSessionActive = async () => true
const result = await engine.resumeSession(session.id, 'default')
expect(result).toEqual({ type: 'success', sessionId: session.id })
expect(capturedResumeSessionId).toBe('22222222-2222-4222-8222-222222222222')
expect(store.sessions.getSession(session.id)?.metadata).toMatchObject({
claudeSessionId: '22222222-2222-4222-8222-222222222222'
})
} finally {
engine.stop()
}
})
it('does not recover a non-UUID sessionId from stored messages', async () => {
const store = new Store(':memory:')
const engine = new SyncEngine(
store,
{} as never,
new RpcRegistry(),
{ broadcast() {} } as never
)
try {
const session = engine.getOrCreateSession(
'session-claude-resume-no-token',
{
path: '/tmp/project',
host: 'localhost',
machineId: 'machine-1',
flavor: 'claude'
},
null,
'default',
'sonnet'
)
store.messages.addMessage(session.id, {
role: 'agent',
content: {
type: 'output',
data: {
sessionId: 'hapi-session-id-not-claude-uuid'
}
}
})
const result = await engine.resumeSession(session.id, 'default')
expect(result).toEqual({
type: 'error',
message: 'Resume session ID unavailable',
code: 'resume_unavailable'
})
} finally {
engine.stop()
}
})
it('passes the cached permissionMode when respawning a resumed session', async () => {
const store = new Store(':memory:')
const engine = new SyncEngine(