test: harden daemon lifecycle and e2e reliability across app/cli/server

This commit is contained in:
Mohamed Boudra
2026-02-25 10:21:46 +07:00
parent 094ce49d4f
commit a060ae1256
98 changed files with 5716 additions and 1771 deletions
@@ -206,6 +206,119 @@ describe('DaemonClient', () => {
expect(client.getConnectionState().status).toBe('disposed')
})
test('sends explicit shutdown_server_request via shutdownServer', async () => {
const logger = createMockLogger()
const mock = createMockTransport()
const client = new DaemonClient({
url: 'ws://test',
clientId: 'clsk_unit_test',
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
})
clients.push(client)
const connectPromise = client.connect()
mock.triggerOpen()
await connectPromise
const lifecycleClient = client as unknown as {
shutdownServer: (requestId?: string) => Promise<{
status: 'shutdown_requested'
clientId: string
requestId: string
}>
}
expect(typeof lifecycleClient.shutdownServer).toBe('function')
const promise = lifecycleClient.shutdownServer('req-shutdown-1')
expect(mock.sent).toHaveLength(1)
const request = JSON.parse(mock.sent[0]) as {
type: 'session'
message: {
type: string
requestId: string
}
}
expect(request.message).toEqual({
type: 'shutdown_server_request',
requestId: 'req-shutdown-1',
})
mock.triggerMessage(
wrapSessionMessage({
type: 'status',
payload: {
status: 'shutdown_requested',
clientId: 'clsk_unit_test',
requestId: 'req-shutdown-1',
},
})
)
await expect(promise).resolves.toEqual({
status: 'shutdown_requested',
clientId: 'clsk_unit_test',
requestId: 'req-shutdown-1',
})
})
test('restartServer remains restart-only and sends restart_server_request', async () => {
const logger = createMockLogger()
const mock = createMockTransport()
const client = new DaemonClient({
url: 'ws://test',
clientId: 'clsk_unit_test',
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
})
clients.push(client)
const connectPromise = client.connect()
mock.triggerOpen()
await connectPromise
const promise = client.restartServer('settings_update', 'req-restart-1')
expect(mock.sent).toHaveLength(1)
const request = JSON.parse(mock.sent[0]) as {
type: 'session'
message: {
type: string
reason?: string
requestId: string
}
}
expect(request.message).toEqual({
type: 'restart_server_request',
reason: 'settings_update',
requestId: 'req-restart-1',
})
mock.triggerMessage(
wrapSessionMessage({
type: 'status',
payload: {
status: 'restart_requested',
clientId: 'clsk_unit_test',
reason: 'settings_update',
requestId: 'req-restart-1',
},
})
)
await expect(promise).resolves.toEqual({
status: 'restart_requested',
clientId: 'clsk_unit_test',
reason: 'settings_update',
requestId: 'req-restart-1',
})
})
test('transitions out of connecting when connect timeout elapses', async () => {
vi.useFakeTimers()
try {
+31 -1
View File
@@ -5,6 +5,7 @@ import {
AgentRefreshedStatusPayloadSchema,
AgentResumedStatusPayloadSchema,
RestartRequestedStatusPayloadSchema,
ShutdownRequestedStatusPayloadSchema,
SessionInboundMessageSchema,
type WSWelcomeMessage,
WSOutboundMessageSchema,
@@ -275,6 +276,7 @@ export type FetchAgentTimelineOptions = {
type AgentRefreshedStatusPayload = z.infer<typeof AgentRefreshedStatusPayloadSchema>
type RestartRequestedStatusPayload = z.infer<typeof RestartRequestedStatusPayloadSchema>
type ShutdownRequestedStatusPayload = z.infer<typeof ShutdownRequestedStatusPayloadSchema>
type FetchAgentsPayload = Extract<
SessionOutboundMessage,
{ type: 'fetch_agents_response' }
@@ -1542,6 +1544,33 @@ export class DaemonClient {
})
}
async shutdownServer(requestId?: string): Promise<ShutdownRequestedStatusPayload> {
const resolvedRequestId = this.createRequestId(requestId)
const message = SessionInboundMessageSchema.parse({
type: 'shutdown_server_request',
requestId: resolvedRequestId,
})
return this.sendRequest({
requestId: resolvedRequestId,
message,
timeout: 10000,
options: { skipQueue: true },
select: (msg) => {
if (msg.type !== 'status') {
return null
}
const shutdown = ShutdownRequestedStatusPayloadSchema.safeParse(msg.payload)
if (!shutdown.success) {
return null
}
if (shutdown.data.requestId !== resolvedRequestId) {
return null
}
return shutdown.data
},
})
}
// ============================================================================
// Audio / Voice
// ============================================================================
@@ -2203,7 +2232,8 @@ export class DaemonClient {
cwd: options?.cwd,
},
responseType: 'list_provider_models_response',
timeout: 30000,
// Provider SDK cold starts (especially model discovery) can exceed 30s.
timeout: 45000,
})
}