agent-runtime-storage.test.ts
1 import { mkdtempSync, rmSync } from 'node:fs' 2 import { tmpdir } from 'node:os' 3 import path from 'node:path' 4 5 import { afterEach, beforeEach, describe, expect, it } from 'vitest' 6 7 import { ensureSession } from '@/server/storage/chat-store' 8 import { 9 appendAgentTaskEvent, 10 cancelAgentTask, 11 getPendingWorkflowInput, 12 listAgentTaskEvents, 13 listAgentTasks, 14 recordWorkflowInput, 15 resolveWorkflowInput, 16 upsertAgentTask, 17 upsertWorkflowRun, 18 } from '@/server/storage/agent-runtime' 19 20 const originalEnv = { ...process.env } 21 22 let tempDataDir = '' 23 24 describe('agent runtime storage', () => { 25 beforeEach(() => { 26 tempDataDir = mkdtempSync(path.join(tmpdir(), 'helper-agent-runtime-')) 27 resetDbConnection() 28 process.env = { 29 ...originalEnv, 30 APP_DATA_DIR: tempDataDir, 31 } 32 }) 33 34 afterEach(() => { 35 resetDbConnection() 36 rmSync(tempDataDir, { recursive: true, force: true }) 37 process.env = originalEnv 38 }) 39 40 it('persists task lifecycle and timeline events', () => { 41 ensureSession('session-agent-runtime') 42 43 const task = upsertAgentTask({ 44 id: 'task-1', 45 sessionId: 'session-agent-runtime', 46 runId: 'run-1', 47 title: 'Agent run', 48 kind: 'agent_run', 49 status: 'running', 50 source: 'chat-stream', 51 }) 52 expect(task.status).toBe('running') 53 54 appendAgentTaskEvent({ 55 taskId: task.id, 56 type: 'agent.started', 57 text: 'Started run.', 58 }) 59 appendAgentTaskEvent({ 60 taskId: task.id, 61 type: 'agent.progress', 62 text: 'Working...', 63 }) 64 65 const events = listAgentTaskEvents({ 66 taskId: task.id, 67 limit: 10, 68 }) 69 expect(events).toHaveLength(2) 70 expect(events[0]?.type).toBe('agent.started') 71 expect(events[1]?.type).toBe('agent.progress') 72 73 const cancelled = cancelAgentTask(task.id) 74 expect(cancelled?.status).toBe('cancelled') 75 expect( 76 listAgentTasks({ 77 scope: 'active', 78 }).some((entry) => entry.id === task.id), 79 ).toBe(false) 80 }) 81 82 it('records workflow input and resolves it', () => { 83 ensureSession('session-workflow-runtime') 84 85 upsertAgentTask({ 86 id: 'task-workflow', 87 sessionId: 'session-workflow-runtime', 88 title: 'Workflow task', 89 kind: 'workflow', 90 status: 'waiting_input', 91 source: 'workflow', 92 }) 93 upsertWorkflowRun({ 94 id: 'wf-1', 95 taskId: 'task-workflow', 96 sessionId: 'session-workflow-runtime', 97 commandId: 'plan', 98 status: 'waiting_input', 99 awaitingInput: true, 100 resumeToken: 'resume-1', 101 }) 102 103 const request = recordWorkflowInput({ 104 workflowId: 'wf-1', 105 taskId: 'task-workflow', 106 commandId: 'plan', 107 prompt: 'Pick a repo', 108 fields: [ 109 { 110 id: 'repo', 111 label: 'Repo', 112 type: 'text', 113 }, 114 ], 115 resumeToken: 'resume-1', 116 }) 117 const pending = getPendingWorkflowInput({ 118 workflowId: request.workflowId, 119 resumeToken: request.resumeToken, 120 }) 121 expect(pending?.prompt).toBe('Pick a repo') 122 123 const resolved = resolveWorkflowInput({ 124 workflowId: request.workflowId, 125 resumeToken: request.resumeToken, 126 values: { 127 repo: 'owner/repo', 128 }, 129 }) 130 expect(resolved?.status).toBe('running') 131 expect(resolved?.awaitingInput).toBe(false) 132 }) 133 }) 134 135 function resetDbConnection(): void { 136 const globalWithDb = globalThis as typeof globalThis & { 137 __helperDb__?: { close: () => void } 138 } 139 globalWithDb.__helperDb__?.close() 140 globalWithDb.__helperDb__ = undefined 141 }