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:
@@ -2,6 +2,7 @@ export * from './messages'
|
||||
export * from './flavors'
|
||||
export * from './models'
|
||||
export * from './modes'
|
||||
export * from './resume'
|
||||
export * from './socket'
|
||||
export * from './sessionSummary'
|
||||
export * from './utils'
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import { LocalResumeTargetSchema, ResumableSessionSchema } from './resume'
|
||||
import { SyncEventSchema } from './schemas'
|
||||
import { SessionEndReasonSchema } from './socket'
|
||||
|
||||
describe('resume schemas', () => {
|
||||
it('accepts a local resume target', () => {
|
||||
const parsed = LocalResumeTargetSchema.safeParse({
|
||||
sessionId: 'hapi-session-1',
|
||||
flavor: 'codex',
|
||||
directory: '/tmp/project',
|
||||
machineId: 'machine-1',
|
||||
host: 'devbox',
|
||||
active: true,
|
||||
thinking: false,
|
||||
controlledByUser: false,
|
||||
agentSessionId: 'codex-thread-1',
|
||||
model: 'gpt-5.4',
|
||||
effort: null,
|
||||
modelReasoningEffort: 'xhigh',
|
||||
permissionMode: 'default',
|
||||
collaborationMode: 'default'
|
||||
})
|
||||
|
||||
expect(parsed.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a resumable session summary', () => {
|
||||
const parsed = ResumableSessionSchema.safeParse({
|
||||
sessionId: 'hapi-session-1',
|
||||
flavor: 'claude',
|
||||
directory: '/tmp/project',
|
||||
active: false,
|
||||
thinking: false,
|
||||
controlledByUser: false,
|
||||
agentSessionId: '11111111-1111-4111-8111-111111111111',
|
||||
updatedAt: 123,
|
||||
name: 'project work',
|
||||
summary: 'finish docs'
|
||||
})
|
||||
|
||||
expect(parsed.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts handoff as a session end reason', () => {
|
||||
expect(SessionEndReasonSchema.parse('handoff')).toBe('handoff')
|
||||
})
|
||||
|
||||
it('accepts handoff in session-ended sync events', () => {
|
||||
const parsed = SyncEventSchema.safeParse({
|
||||
type: 'session-ended',
|
||||
sessionId: 'hapi-session-1',
|
||||
reason: 'handoff'
|
||||
})
|
||||
|
||||
expect(parsed.success).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { z } from 'zod'
|
||||
import { CodexCollaborationModeSchema, PermissionModeSchema } from './schemas'
|
||||
|
||||
export const AgentFlavorSchema = z.enum(['claude', 'codex', 'gemini', 'opencode', 'cursor'])
|
||||
|
||||
export const LocalResumeTargetSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
flavor: AgentFlavorSchema,
|
||||
directory: z.string().min(1),
|
||||
machineId: z.string().optional(),
|
||||
host: z.string().optional(),
|
||||
active: z.boolean(),
|
||||
thinking: z.boolean(),
|
||||
controlledByUser: z.boolean(),
|
||||
agentSessionId: z.string().min(1),
|
||||
model: z.string().nullable().optional(),
|
||||
effort: z.string().nullable().optional(),
|
||||
modelReasoningEffort: z.string().nullable().optional(),
|
||||
permissionMode: PermissionModeSchema.optional(),
|
||||
collaborationMode: CodexCollaborationModeSchema.optional()
|
||||
})
|
||||
|
||||
export type LocalResumeTarget = z.infer<typeof LocalResumeTargetSchema>
|
||||
|
||||
export const ResumableSessionSchema = LocalResumeTargetSchema.extend({
|
||||
updatedAt: z.number(),
|
||||
name: z.string().optional(),
|
||||
summary: z.string().optional()
|
||||
})
|
||||
|
||||
export type ResumableSession = z.infer<typeof ResumableSessionSchema>
|
||||
|
||||
export const LocalResumeTargetResponseSchema = z.object({
|
||||
target: LocalResumeTargetSchema
|
||||
})
|
||||
|
||||
export type LocalResumeTargetResponse = z.infer<typeof LocalResumeTargetResponseSchema>
|
||||
|
||||
export const ResumableSessionsResponseSchema = z.object({
|
||||
sessions: z.array(ResumableSessionSchema)
|
||||
})
|
||||
|
||||
export type ResumableSessionsResponse = z.infer<typeof ResumableSessionsResponseSchema>
|
||||
|
||||
export const LocalHandoffResponseSchema = z.object({
|
||||
ok: z.boolean()
|
||||
})
|
||||
|
||||
export type LocalHandoffResponse = z.infer<typeof LocalHandoffResponseSchema>
|
||||
@@ -3,6 +3,8 @@ import { CODEX_COLLABORATION_MODES, PERMISSION_MODES } from './modes'
|
||||
|
||||
export const PermissionModeSchema = z.enum(PERMISSION_MODES)
|
||||
export const CodexCollaborationModeSchema = z.enum(CODEX_COLLABORATION_MODES)
|
||||
export const SessionEndReasonSchema = z.enum(['completed', 'terminated', 'error', 'handoff'])
|
||||
export type SessionEndReason = z.infer<typeof SessionEndReasonSchema>
|
||||
|
||||
const MetadataSummarySchema = z.object({
|
||||
text: z.string(),
|
||||
@@ -242,7 +244,7 @@ export const SyncEventSchema = z.discriminatedUnion('type', [
|
||||
}),
|
||||
SessionChangedSchema.extend({
|
||||
type: z.literal('session-ended'),
|
||||
reason: z.enum(['completed', 'terminated', 'error']).optional()
|
||||
reason: SessionEndReasonSchema.optional()
|
||||
}),
|
||||
MachineChangedSchema.extend({
|
||||
type: z.literal('machine-updated'),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import type { CodexCollaborationMode, PermissionMode } from './modes'
|
||||
import type { SessionEndReason } from './schemas'
|
||||
export { SessionEndReasonSchema, type SessionEndReason } from './schemas'
|
||||
|
||||
export type SocketErrorReason = 'namespace-missing' | 'access-denied' | 'not-found'
|
||||
|
||||
@@ -67,9 +69,6 @@ export const TerminalErrorPayloadSchema = z.object({
|
||||
})
|
||||
|
||||
export type TerminalErrorPayload = z.infer<typeof TerminalErrorPayloadSchema>
|
||||
export const SessionEndReasonSchema = z.enum(['completed', 'terminated', 'error'])
|
||||
export type SessionEndReason = z.infer<typeof SessionEndReasonSchema>
|
||||
|
||||
export const UpdateNewMessageBodySchema = z.object({
|
||||
t: z.literal('new-message'),
|
||||
sid: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user