mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: add hapi resume command (#647)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const runLocalRemoteSessionMock = vi.hoisted(() => vi.fn(async (options: { session: { stopKeepAlive: () => void } }) => {
|
||||
options.session.stopKeepAlive()
|
||||
}))
|
||||
|
||||
vi.mock('@/agent/loopBase', () => ({
|
||||
runLocalRemoteSession: runLocalRemoteSessionMock
|
||||
}))
|
||||
|
||||
vi.mock('@/ui/logger', () => ({
|
||||
logger: {
|
||||
logFilePath: '/tmp/hapi.log',
|
||||
debug: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('./claudeLocalLauncher', () => ({
|
||||
claudeLocalLauncher: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./claudeRemoteLauncher', () => ({
|
||||
claudeRemoteLauncher: vi.fn()
|
||||
}))
|
||||
|
||||
import { loop } from './loop'
|
||||
|
||||
describe('claude loop', () => {
|
||||
it('initializes the Claude session id from resumeSessionId', async () => {
|
||||
const sessionClient = {
|
||||
keepAlive: vi.fn(),
|
||||
emitMessagesConsumed: vi.fn(),
|
||||
updateMetadata: vi.fn()
|
||||
}
|
||||
|
||||
await loop({
|
||||
path: '/tmp/project',
|
||||
startingMode: 'local',
|
||||
onModeChange: () => {},
|
||||
mcpServers: {},
|
||||
session: sessionClient as never,
|
||||
api: {} as never,
|
||||
messageQueue: {} as never,
|
||||
hookSettingsPath: '/tmp/hooks.json',
|
||||
resumeSessionId: '11111111-1111-4111-8111-111111111111'
|
||||
})
|
||||
|
||||
expect(runLocalRemoteSessionMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||
session: expect.objectContaining({
|
||||
sessionId: '11111111-1111-4111-8111-111111111111'
|
||||
})
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -39,6 +39,7 @@ interface LoopOptions {
|
||||
allowedTools?: string[]
|
||||
onSessionReady?: (session: Session) => void
|
||||
hookSettingsPath: string
|
||||
resumeSessionId?: string
|
||||
}
|
||||
|
||||
export async function loop(opts: LoopOptions) {
|
||||
@@ -51,7 +52,7 @@ export async function loop(opts: LoopOptions) {
|
||||
api: opts.api,
|
||||
client: opts.session,
|
||||
path: opts.path,
|
||||
sessionId: null,
|
||||
sessionId: opts.resumeSessionId ?? null,
|
||||
claudeEnvVars: opts.claudeEnvVars,
|
||||
claudeArgs: opts.claudeArgs,
|
||||
mcpServers: opts.mcpServers,
|
||||
|
||||
+24
-10
@@ -12,7 +12,8 @@ import { startHookServer } from '@/claude/utils/startHookServer';
|
||||
import { generateHookSettingsFile, cleanupHookSettingsFile } from '@/modules/common/hooks/generateHookSettings';
|
||||
import { registerKillSessionHandler } from './registerKillSessionHandler';
|
||||
import type { Session } from './session';
|
||||
import { bootstrapSession } from '@/agent/sessionFactory';
|
||||
import { bootstrapExistingSession, bootstrapSession } from '@/agent/sessionFactory';
|
||||
import { registerLocalHandoffHandler } from '@/agent/localHandoff';
|
||||
import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } from '@/agent/runnerLifecycle';
|
||||
import { isPermissionModeAllowedForFlavor } from '@hapi/protocol';
|
||||
import { PermissionModeSchema } from '@hapi/protocol/schemas';
|
||||
@@ -30,10 +31,13 @@ export interface StartOptions {
|
||||
claudeEnvVars?: Record<string, string>
|
||||
claudeArgs?: string[]
|
||||
startedBy?: 'runner' | 'terminal'
|
||||
existingSessionId?: string
|
||||
workingDirectory?: string
|
||||
resumeSessionId?: string
|
||||
}
|
||||
|
||||
export async function runClaude(options: StartOptions = {}): Promise<void> {
|
||||
const workingDirectory = getInvokedCwd();
|
||||
const workingDirectory = options.workingDirectory ?? getInvokedCwd();
|
||||
const startedBy = options.startedBy ?? 'terminal';
|
||||
|
||||
// Log environment info at startup
|
||||
@@ -51,14 +55,22 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
|
||||
const initialState: AgentState = {};
|
||||
const initialModel = normalizeClaudeSessionModel(options.model);
|
||||
const initialEffort = normalizeClaudeSessionEffort(options.effort);
|
||||
const { api, session, sessionInfo } = await bootstrapSession({
|
||||
flavor: 'claude',
|
||||
startedBy,
|
||||
workingDirectory,
|
||||
agentState: initialState,
|
||||
model: initialModel ?? undefined,
|
||||
effort: initialEffort ?? undefined
|
||||
});
|
||||
const bootstrap = options.existingSessionId
|
||||
? await bootstrapExistingSession({
|
||||
sessionId: options.existingSessionId,
|
||||
flavor: 'claude',
|
||||
startedBy,
|
||||
workingDirectory
|
||||
})
|
||||
: await bootstrapSession({
|
||||
flavor: 'claude',
|
||||
startedBy,
|
||||
workingDirectory,
|
||||
agentState: initialState,
|
||||
model: initialModel ?? undefined,
|
||||
effort: initialEffort ?? undefined
|
||||
});
|
||||
const { api, session, sessionInfo } = bootstrap;
|
||||
logger.debug(`Session created: ${sessionInfo.id}`);
|
||||
|
||||
// Extract SDK metadata in background and update session when ready
|
||||
@@ -133,6 +145,7 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
|
||||
|
||||
lifecycle.registerProcessHandlers();
|
||||
registerKillSessionHandler(session.rpcHandlerManager, lifecycle.cleanupAndExit);
|
||||
registerLocalHandoffHandler(session.rpcHandlerManager, lifecycle);
|
||||
|
||||
// Set initial agent state
|
||||
const startingMode = options.startingMode ?? (startedBy === 'runner' ? 'remote' : 'local');
|
||||
@@ -419,6 +432,7 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
|
||||
claudeEnvVars: options.claudeEnvVars,
|
||||
claudeArgs: options.claudeArgs,
|
||||
startedBy,
|
||||
resumeSessionId: options.resumeSessionId,
|
||||
hookSettingsPath
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user