feat: add hapi resume command (#647)

This commit is contained in:
leko
2026-05-20 06:18:42 +08:00
committed by GitHub
parent 79d919675e
commit 197f327590
29 changed files with 1777 additions and 93 deletions
+141
View File
@@ -0,0 +1,141 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { runCodex } from './runCodex'
const mockCodexSession = vi.hoisted(() => ({
setPermissionMode: vi.fn(),
setModel: vi.fn(),
setModelReasoningEffort: vi.fn(),
setCollaborationMode: vi.fn(),
stopKeepAlive: vi.fn()
}))
const harness = vi.hoisted(() => ({
bootstrapArgs: [] as Array<Record<string, unknown>>,
loopArgs: [] as Array<Record<string, unknown>>,
session: {
onUserMessage: vi.fn(),
onCancelQueuedMessage: vi.fn(),
rpcHandlerManager: {
registerHandler: vi.fn()
}
}
}))
vi.mock('@/agent/sessionFactory', () => ({
bootstrapSession: vi.fn(async (options: Record<string, unknown>) => {
harness.bootstrapArgs.push(options)
return {
api: {},
session: harness.session
}
}),
bootstrapExistingSession: vi.fn(async (options: Record<string, unknown>) => {
harness.bootstrapArgs.push(options)
return {
api: {},
session: harness.session
}
})
}))
vi.mock('./loop', () => ({
loop: vi.fn(async (options: Record<string, unknown>) => {
harness.loopArgs.push(options)
const onSessionReady = options.onSessionReady as ((session: unknown) => void) | undefined
onSessionReady?.(mockCodexSession)
})
}))
vi.mock('@/claude/registerKillSessionHandler', () => ({
registerKillSessionHandler: vi.fn()
}))
const lifecycleMock = vi.hoisted(() => ({
registerProcessHandlers: vi.fn(),
cleanupAndExit: vi.fn(async () => {}),
markCrash: vi.fn(),
setExitCode: vi.fn(),
setArchiveReason: vi.fn(),
setSessionEndReason: vi.fn()
}))
vi.mock('@/agent/runnerLifecycle', () => ({
createModeChangeHandler: vi.fn(() => vi.fn()),
createRunnerLifecycle: vi.fn(() => lifecycleMock),
setControlledByUser: vi.fn()
}))
vi.mock('@/agent/localHandoff', () => ({
registerLocalHandoffHandler: vi.fn()
}))
vi.mock('@/ui/logger', () => ({
logger: {
debug: vi.fn()
}
}))
vi.mock('@/utils/attachmentFormatter', () => ({
formatMessageWithAttachments: vi.fn((text: string) => text)
}))
vi.mock('@/modules/common/slashCommands', () => ({
listSlashCommands: vi.fn(async () => [])
}))
vi.mock('./utils/slashCommands', () => ({
resolveCodexSlashCommand: vi.fn(() => ({
kind: 'passthrough'
}))
}))
vi.mock('./codexSpecialCommands', () => ({
parseCodexSpecialCommand: vi.fn(() => ({}))
}))
vi.mock('./utils/codexCliOverrides', () => ({
parseCodexCliOverrides: vi.fn(() => ({}))
}))
import { runCodex as runCodexImpl } from './runCodex'
describe('runCodex', () => {
beforeEach(() => {
harness.bootstrapArgs.length = 0
harness.loopArgs.length = 0
harness.session.onUserMessage.mockReset()
harness.session.onCancelQueuedMessage.mockReset()
harness.session.rpcHandlerManager.registerHandler.mockReset()
mockCodexSession.setPermissionMode.mockReset()
mockCodexSession.setModel.mockReset()
mockCodexSession.setModelReasoningEffort.mockReset()
mockCodexSession.setCollaborationMode.mockReset()
lifecycleMock.registerProcessHandlers.mockClear()
lifecycleMock.cleanupAndExit.mockClear()
lifecycleMock.markCrash.mockClear()
lifecycleMock.setExitCode.mockClear()
lifecycleMock.setArchiveReason.mockClear()
lifecycleMock.setSessionEndReason.mockClear()
})
it('uses the requested collaboration mode when resuming locally', async () => {
const options = {
existingSessionId: 'hapi-session-1',
workingDirectory: '/tmp/project',
resumeSessionId: 'codex-thread-1',
collaborationMode: 'plan'
} as Parameters<typeof runCodex>[0] & { collaborationMode: 'plan' }
await runCodexImpl(options)
expect(harness.bootstrapArgs[0]).toEqual(expect.objectContaining({
sessionId: 'hapi-session-1',
workingDirectory: '/tmp/project'
}))
expect(harness.loopArgs[0]).toEqual(expect.objectContaining({
resumeSessionId: 'codex-thread-1',
collaborationMode: 'plan'
}))
expect(mockCodexSession.setCollaborationMode).toHaveBeenLastCalledWith('plan')
})
})