@ai-devkit/agent-manager 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +1 -342
  2. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  3. package/dist/__tests__/capacity/codex.test.js +343 -0
  4. package/dist/__tests__/capacity/codex.test.js.map +1 -0
  5. package/dist/__tests__/capacity/index.test.js +50 -0
  6. package/dist/__tests__/capacity/index.test.js.map +1 -0
  7. package/dist/__tests__/database/DurableAgentsDatabase.test.js +31 -3
  8. package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +1 -1
  9. package/dist/__tests__/durable/CodexCliProbe.test.js +129 -0
  10. package/dist/__tests__/durable/CodexCliProbe.test.js.map +1 -0
  11. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js +84 -0
  12. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js.map +1 -0
  13. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js +126 -0
  14. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js.map +1 -0
  15. package/dist/__tests__/durable/CodexPrintAgentService.test.js +271 -0
  16. package/dist/__tests__/durable/CodexPrintAgentService.test.js.map +1 -0
  17. package/dist/__tests__/durable/CodexPrintRunner.test.js +393 -0
  18. package/dist/__tests__/durable/CodexPrintRunner.test.js.map +1 -0
  19. package/dist/__tests__/print/ClaudeCliProbe.test.js +3 -7
  20. package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -1
  21. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +33 -27
  22. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
  23. package/dist/__tests__/print/ClaudePrintAgentService.test.js +65 -0
  24. package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
  25. package/dist/__tests__/print/ClaudePrintRunner.test.js +4 -10
  26. package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
  27. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js +79 -0
  28. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js.map +1 -0
  29. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js +436 -0
  30. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js.map +1 -0
  31. package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js +76 -0
  32. package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js.map +1 -0
  33. package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js +43 -0
  34. package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js.map +1 -0
  35. package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js +202 -0
  36. package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js.map +1 -0
  37. package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js +119 -0
  38. package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js.map +1 -0
  39. package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js +386 -0
  40. package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js.map +1 -0
  41. package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js +34 -0
  42. package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js.map +1 -0
  43. package/dist/__tests__/utils/ClaudeSessionParser.test.js +2 -2
  44. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -1
  45. package/dist/adapters/index.d.ts +1 -1
  46. package/dist/adapters/index.d.ts.map +1 -1
  47. package/dist/adapters/index.js +1 -1
  48. package/dist/adapters/index.js.map +1 -1
  49. package/dist/capacity/codex.d.ts +36 -0
  50. package/dist/capacity/codex.d.ts.map +1 -0
  51. package/dist/capacity/codex.js +317 -0
  52. package/dist/capacity/codex.js.map +1 -0
  53. package/dist/capacity/index.d.ts +11 -0
  54. package/dist/capacity/index.d.ts.map +1 -0
  55. package/dist/capacity/index.js +49 -0
  56. package/dist/capacity/index.js.map +1 -0
  57. package/dist/capacity/types.d.ts +17 -0
  58. package/dist/capacity/types.d.ts.map +1 -0
  59. package/dist/capacity/types.js +3 -0
  60. package/dist/capacity/types.js.map +1 -0
  61. package/dist/database/migrations/004_codex_durable_sessions.sql +1 -0
  62. package/dist/durable/DurableAgent.d.ts +23 -3
  63. package/dist/durable/DurableAgent.d.ts.map +1 -1
  64. package/dist/durable/DurableAgent.js +12 -0
  65. package/dist/durable/DurableAgent.js.map +1 -1
  66. package/dist/durable/DurableAgentRepository.d.ts +6 -7
  67. package/dist/durable/DurableAgentRepository.d.ts.map +1 -1
  68. package/dist/durable/DurableAgentRepository.js +73 -42
  69. package/dist/durable/DurableAgentRepository.js.map +1 -1
  70. package/dist/durable/process.d.ts +8 -0
  71. package/dist/durable/process.d.ts.map +1 -0
  72. package/dist/durable/process.js +40 -0
  73. package/dist/durable/process.js.map +1 -0
  74. package/dist/durable/run.d.ts +32 -0
  75. package/dist/durable/run.d.ts.map +1 -0
  76. package/dist/durable/run.js +46 -0
  77. package/dist/durable/run.js.map +1 -0
  78. package/dist/durable/utils.d.ts +19 -0
  79. package/dist/durable/utils.d.ts.map +1 -0
  80. package/dist/durable/utils.js +52 -0
  81. package/dist/durable/utils.js.map +1 -0
  82. package/dist/index.d.ts +19 -13
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +10 -8
  85. package/dist/index.js.map +1 -1
  86. package/dist/providers/claude/ClaudeAgentMapper.d.ts +21 -0
  87. package/dist/providers/claude/ClaudeAgentMapper.d.ts.map +1 -0
  88. package/dist/providers/claude/ClaudeAgentMapper.js +42 -0
  89. package/dist/providers/claude/ClaudeAgentMapper.js.map +1 -0
  90. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +29 -0
  91. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -0
  92. package/dist/providers/claude/ClaudeCodeAdapter.js +129 -0
  93. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -0
  94. package/dist/{adapters/ClaudeCodeAdapter.d.ts → providers/claude/ClaudeSessionLocator.d.ts} +68 -52
  95. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -0
  96. package/dist/{adapters/ClaudeCodeAdapter.js → providers/claude/ClaudeSessionLocator.js} +71 -189
  97. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -0
  98. package/dist/{utils → providers/claude}/ClaudeSessionParser.d.ts +2 -2
  99. package/dist/providers/claude/ClaudeSessionParser.d.ts.map +1 -0
  100. package/dist/{utils → providers/claude}/ClaudeSessionParser.js +1 -1
  101. package/dist/providers/claude/ClaudeSessionParser.js.map +1 -0
  102. package/dist/providers/claude/durable/ClaudeCliProbe.d.ts.map +1 -0
  103. package/dist/{durable → providers/claude/durable}/ClaudeCliProbe.js +4 -9
  104. package/dist/providers/claude/durable/ClaudeCliProbe.js.map +1 -0
  105. package/dist/{durable → providers/claude/durable}/ClaudePrintAgentService.d.ts +4 -9
  106. package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts.map +1 -0
  107. package/dist/providers/claude/durable/ClaudePrintAgentService.js +45 -0
  108. package/dist/providers/claude/durable/ClaudePrintAgentService.js.map +1 -0
  109. package/dist/{durable → providers/claude/durable}/ClaudePrintRunner.d.ts +3 -3
  110. package/dist/providers/claude/durable/ClaudePrintRunner.d.ts.map +1 -0
  111. package/dist/providers/claude/durable/ClaudePrintRunner.js +75 -0
  112. package/dist/providers/claude/durable/ClaudePrintRunner.js.map +1 -0
  113. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts +17 -0
  114. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts.map +1 -0
  115. package/dist/providers/claude/durable/ClaudeStreamParser.js +69 -0
  116. package/dist/providers/claude/durable/ClaudeStreamParser.js.map +1 -0
  117. package/dist/providers/codex/durable/CodexCliProbe.d.ts +20 -0
  118. package/dist/providers/codex/durable/CodexCliProbe.d.ts.map +1 -0
  119. package/dist/providers/codex/durable/CodexCliProbe.js +61 -0
  120. package/dist/providers/codex/durable/CodexCliProbe.js.map +1 -0
  121. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts +40 -0
  122. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts.map +1 -0
  123. package/dist/providers/codex/durable/CodexPrintAgentService.js +50 -0
  124. package/dist/providers/codex/durable/CodexPrintAgentService.js.map +1 -0
  125. package/dist/providers/codex/durable/CodexPrintRunner.d.ts +34 -0
  126. package/dist/providers/codex/durable/CodexPrintRunner.d.ts.map +1 -0
  127. package/dist/providers/codex/durable/CodexPrintRunner.js +78 -0
  128. package/dist/providers/codex/durable/CodexPrintRunner.js.map +1 -0
  129. package/dist/providers/codex/durable/CodexStreamParser.d.ts +21 -0
  130. package/dist/providers/codex/durable/CodexStreamParser.d.ts.map +1 -0
  131. package/dist/providers/codex/durable/CodexStreamParser.js +86 -0
  132. package/dist/providers/codex/durable/CodexStreamParser.js.map +1 -0
  133. package/dist/providers/pi/durable/PiCliProbe.d.ts +20 -0
  134. package/dist/providers/pi/durable/PiCliProbe.d.ts.map +1 -0
  135. package/dist/providers/pi/durable/PiCliProbe.js +51 -0
  136. package/dist/providers/pi/durable/PiCliProbe.js.map +1 -0
  137. package/dist/providers/pi/durable/PiPrintAgentService.d.ts +39 -0
  138. package/dist/providers/pi/durable/PiPrintAgentService.d.ts.map +1 -0
  139. package/dist/providers/pi/durable/PiPrintAgentService.js +47 -0
  140. package/dist/providers/pi/durable/PiPrintAgentService.js.map +1 -0
  141. package/dist/providers/pi/durable/PiPrintProtocol.d.ts +4 -0
  142. package/dist/providers/pi/durable/PiPrintProtocol.d.ts.map +1 -0
  143. package/dist/providers/pi/durable/PiPrintProtocol.js +44 -0
  144. package/dist/providers/pi/durable/PiPrintProtocol.js.map +1 -0
  145. package/dist/providers/pi/durable/PiPrintRunner.d.ts +32 -0
  146. package/dist/providers/pi/durable/PiPrintRunner.d.ts.map +1 -0
  147. package/dist/providers/pi/durable/PiPrintRunner.js +61 -0
  148. package/dist/providers/pi/durable/PiPrintRunner.js.map +1 -0
  149. package/dist/providers/pi/durable/PiStreamParser.d.ts +21 -0
  150. package/dist/providers/pi/durable/PiStreamParser.d.ts.map +1 -0
  151. package/dist/providers/pi/durable/PiStreamParser.js +71 -0
  152. package/dist/providers/pi/durable/PiStreamParser.js.map +1 -0
  153. package/package.json +1 -1
  154. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1 -307
  155. package/src/__tests__/capacity/codex.test.ts +184 -0
  156. package/src/__tests__/capacity/index.test.ts +38 -0
  157. package/src/__tests__/database/DurableAgentsDatabase.test.ts +28 -3
  158. package/src/__tests__/durable/CodexCliProbe.test.ts +63 -0
  159. package/src/__tests__/durable/CodexDurableAgentRepository.test.ts +54 -0
  160. package/src/__tests__/durable/CodexPrintAgent.integration.test.ts +79 -0
  161. package/src/__tests__/durable/CodexPrintAgentService.test.ts +139 -0
  162. package/src/__tests__/durable/CodexPrintRunner.test.ts +189 -0
  163. package/src/__tests__/fixtures/fake-codex.cjs +45 -0
  164. package/src/__tests__/print/ClaudeCliProbe.test.ts +3 -7
  165. package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +19 -22
  166. package/src/__tests__/print/ClaudePrintAgentService.test.ts +33 -1
  167. package/src/__tests__/print/ClaudePrintRunner.test.ts +4 -10
  168. package/src/__tests__/providers/claude/ClaudeAgentMapper.test.ts +84 -0
  169. package/src/__tests__/providers/claude/ClaudeSessionLocator.test.ts +411 -0
  170. package/src/__tests__/providers/pi/durable/PiCliProbe.test.ts +38 -0
  171. package/src/__tests__/providers/pi/durable/PiDurableAgentRepository.test.ts +32 -0
  172. package/src/__tests__/providers/pi/durable/PiPrintAgentService.test.ts +69 -0
  173. package/src/__tests__/providers/pi/durable/PiPrintProtocol.test.ts +33 -0
  174. package/src/__tests__/providers/pi/durable/PiPrintRunner.test.ts +107 -0
  175. package/src/__tests__/providers/pi/durable/PiStreamParser.test.ts +24 -0
  176. package/src/__tests__/utils/ClaudeSessionParser.test.ts +2 -2
  177. package/src/adapters/index.ts +1 -1
  178. package/src/capacity/codex.ts +320 -0
  179. package/src/capacity/index.ts +57 -0
  180. package/src/capacity/types.ts +18 -0
  181. package/src/database/migrations/004_codex_durable_sessions.sql +1 -0
  182. package/src/durable/DurableAgent.ts +51 -3
  183. package/src/durable/DurableAgentRepository.ts +60 -31
  184. package/src/durable/process.ts +28 -0
  185. package/src/durable/run.ts +74 -0
  186. package/src/durable/utils.ts +61 -0
  187. package/src/index.ts +37 -15
  188. package/src/providers/claude/ClaudeAgentMapper.ts +62 -0
  189. package/src/providers/claude/ClaudeCodeAdapter.ts +160 -0
  190. package/src/{adapters/ClaudeCodeAdapter.ts → providers/claude/ClaudeSessionLocator.ts} +97 -239
  191. package/src/{utils → providers/claude}/ClaudeSessionParser.ts +2 -2
  192. package/src/{durable → providers/claude/durable}/ClaudeCliProbe.ts +4 -10
  193. package/src/providers/claude/durable/ClaudePrintAgentService.ts +66 -0
  194. package/src/providers/claude/durable/ClaudePrintRunner.ts +92 -0
  195. package/src/providers/claude/durable/ClaudeStreamParser.ts +79 -0
  196. package/src/providers/codex/durable/CodexCliProbe.ts +60 -0
  197. package/src/providers/codex/durable/CodexPrintAgentService.ts +65 -0
  198. package/src/providers/codex/durable/CodexPrintRunner.ts +94 -0
  199. package/src/providers/codex/durable/CodexStreamParser.ts +104 -0
  200. package/src/providers/pi/durable/PiCliProbe.ts +48 -0
  201. package/src/providers/pi/durable/PiPrintAgentService.ts +69 -0
  202. package/src/providers/pi/durable/PiPrintProtocol.ts +39 -0
  203. package/src/providers/pi/durable/PiPrintRunner.ts +88 -0
  204. package/src/providers/pi/durable/PiStreamParser.ts +84 -0
  205. package/dist/adapters/ClaudeCodeAdapter.d.ts.map +0 -1
  206. package/dist/adapters/ClaudeCodeAdapter.js.map +0 -1
  207. package/dist/durable/ClaudeCliProbe.d.ts.map +0 -1
  208. package/dist/durable/ClaudeCliProbe.js.map +0 -1
  209. package/dist/durable/ClaudePrintAgentService.d.ts.map +0 -1
  210. package/dist/durable/ClaudePrintAgentService.js +0 -66
  211. package/dist/durable/ClaudePrintAgentService.js.map +0 -1
  212. package/dist/durable/ClaudePrintRunner.d.ts.map +0 -1
  213. package/dist/durable/ClaudePrintRunner.js +0 -128
  214. package/dist/durable/ClaudePrintRunner.js.map +0 -1
  215. package/dist/utils/ClaudeSessionParser.d.ts.map +0 -1
  216. package/dist/utils/ClaudeSessionParser.js.map +0 -1
  217. package/src/durable/ClaudePrintAgentService.ts +0 -94
  218. package/src/durable/ClaudePrintRunner.ts +0 -139
  219. /package/dist/{durable → providers/claude/durable}/ClaudeCliProbe.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/durable/CodexPrintAgentService.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\nconst SESSION = '22222222-2222-4222-8222-222222222222';\nconst base = {\n id: 'id', name: 'reviewer', provider: 'codex', providerSessionId: null, sessionHealth: 'uninitialized',\n};\n\ndescribe('CodexPrintAgentService', () => {\n it('constructs default non-billable dependencies without invoking them', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const Service = api.CodexPrintAgentService as new () => any;\n expect(new Service().repository).toBeDefined();\n });\n\n it('validates before provider-aware create and never runs Codex', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('CodexPrintAgentService');\n const probe = { validate: vi.fn().mockResolvedValue({ executable: 'codex', version: '0.147.0' }) };\n const repository = { create: vi.fn().mockResolvedValue(base), list: vi.fn() };\n const runner = { run: vi.fn() };\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n await new Service({ repository, probe, runner }).create({ name: 'reviewer', cwd: '/project' });\n expect(repository.create).toHaveBeenCalledWith({ name: 'reviewer', cwd: '/project', provider: 'codex' });\n expect(runner.run).not.toHaveBeenCalled();\n });\n\n it('binds during first send and explicitly resumes later sends', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const repository = {\n list: vi.fn(),\n resolve: vi.fn().mockResolvedValue(base),\n acquireRun: vi.fn()\n .mockResolvedValueOnce({ agent: base, token: 'one' })\n .mockResolvedValueOnce({ agent: { ...base, providerSessionId: SESSION, sessionHealth: 'healthy' }, token: 'two' }),\n recordProviderProcess: vi.fn(), bindProviderSession: vi.fn(), completeRun: vi.fn(),\n };\n const runner = { run: vi.fn().mockImplementation(async (request) => {\n await request.onSpawn({ pid: 42, startedAt: 'start' });\n await request.onSession(SESSION);\n return { sessionId: SESSION, result: 'answer', messages: ['answer'], exitCode: 0 };\n }) };\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n const service = new Service({ repository, probe: { validate: vi.fn() }, runner, executable: 'fake-codex' });\n\n await service.send('reviewer', 'first');\n await service.send('reviewer', 'later');\n\n expect(repository.bindProviderSession).toHaveBeenNthCalledWith(1, 'id', 'one', SESSION);\n expect(repository.bindProviderSession).toHaveBeenNthCalledWith(2, 'id', 'two', SESSION);\n expect(repository.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({\n status: 'succeeded', sessionHealth: 'healthy',\n }));\n });\n\n it('records mismatch separately from unknown failures', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const ErrorType = api.CodexPrintError as new (message: string, code: string) => Error;\n const completeRun = vi.fn();\n const repository = {\n list: vi.fn(),\n resolve: vi.fn().mockResolvedValue(base), acquireRun: vi.fn().mockResolvedValue({ agent: base, token: 'one' }),\n recordProviderProcess: vi.fn(), bindProviderSession: vi.fn(), completeRun,\n };\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n const service = new Service({ repository, probe: { validate: vi.fn() }, runner: {\n run: vi.fn().mockRejectedValue(new ErrorType('mismatch', 'CODEX_SESSION_MISMATCH')),\n } });\n await expect(service.send('reviewer', 'x')).rejects.toMatchObject({ code: 'CODEX_SESSION_MISMATCH' });\n expect(completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({ sessionHealth: 'mismatch' }));\n });\n\n it('rejects a non-Codex target without acquiring or mutating it', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n const repository = {\n resolve: vi.fn().mockResolvedValue({ ...base, provider: 'claude' }),\n acquireRun: vi.fn(), recordProviderProcess: vi.fn(), bindProviderSession: vi.fn(), completeRun: vi.fn(),\n };\n\n await expect(new Service({ repository, probe: { validate: vi.fn() }, runner: { run: vi.fn() } })\n .send('reviewer', 'x')).rejects.toMatchObject({ code: 'CODEX_UNSUPPORTED' });\n expect(repository.acquireRun).not.toHaveBeenCalled();\n expect(repository.completeRun).not.toHaveBeenCalled();\n });\n\n it('does not turn a successful completion write failure into a second completion', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n const completionFailure = new Error('completion failed');\n const repository = {\n resolve: vi.fn().mockResolvedValue(base),\n acquireRun: vi.fn().mockResolvedValue({ agent: base, token: 'one' }),\n recordProviderProcess: vi.fn(), bindProviderSession: vi.fn(),\n completeRun: vi.fn().mockRejectedValue(completionFailure),\n };\n const runner = { run: vi.fn().mockResolvedValue({\n sessionId: SESSION, result: 'answer', messages: ['answer'], exitCode: 0,\n }) };\n\n await expect(new Service({ repository, probe: { validate: vi.fn() }, runner })\n .send('reviewer', 'x')).rejects.toBe(completionFailure);\n expect(repository.completeRun).toHaveBeenCalledOnce();\n });\n\n it('records a repository binding conflict as a session mismatch', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const BindingError = api.CodexPrintError as new (message: string, code: string) => Error;\n const completeRun = vi.fn();\n const repository = {\n list: vi.fn(), resolve: vi.fn().mockResolvedValue(base),\n acquireRun: vi.fn().mockResolvedValue({ agent: base, token: 'one' }),\n recordProviderProcess: vi.fn(),\n bindProviderSession: vi.fn().mockRejectedValue(new BindingError('binding mismatch', 'CODEX_SESSION_MISMATCH')),\n completeRun,\n };\n const runner = { run: vi.fn().mockImplementation(async (request) => {\n await request.onSession(SESSION);\n return { sessionId: SESSION, result: 'x', messages: ['x'], exitCode: 0 };\n }) };\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n\n await expect(new Service({ repository, probe: { validate: vi.fn() }, runner }).send('reviewer', 'x'))\n .rejects.toMatchObject({ code: 'CODEX_SESSION_MISMATCH' });\n expect(completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({ sessionHealth: 'mismatch' }));\n });\n\n it('rejects missing and ambiguous records before acquiring a run', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const Service = api.CodexPrintAgentService as new (options: unknown) => any;\n const repository = {\n list: vi.fn(), create: vi.fn(), resolve: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce([base, base]),\n acquireRun: vi.fn(), recordProviderProcess: vi.fn(), bindProviderSession: vi.fn(), completeRun: vi.fn(),\n };\n const service = new Service({ repository, probe: { validate: vi.fn() }, runner: { run: vi.fn() } });\n await expect(service.send('missing', 'x')).rejects.toMatchObject({ code: 'DURABLE_AGENT_NOT_FOUND' });\n await expect(service.send('ambiguous', 'x')).rejects.toMatchObject({ code: 'CODEX_UNSUPPORTED' });\n expect(repository.acquireRun).not.toHaveBeenCalled();\n });\n});\n"],"names":["describe","expect","it","vi","SESSION","base","id","name","provider","providerSessionId","sessionHealth","api","Service","CodexPrintAgentService","repository","toBeDefined","toHaveProperty","probe","validate","fn","mockResolvedValue","executable","version","create","list","runner","run","cwd","toHaveBeenCalledWith","not","toHaveBeenCalled","resolve","acquireRun","mockResolvedValueOnce","agent","token","recordProviderProcess","bindProviderSession","completeRun","mockImplementation","request","onSpawn","pid","startedAt","onSession","sessionId","result","messages","exitCode","service","send","toHaveBeenNthCalledWith","objectContaining","status","ErrorType","CodexPrintError","mockRejectedValue","rejects","toMatchObject","code","completionFailure","Error","toBe","toHaveBeenCalledOnce","BindingError"],"mappings":"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"}
@@ -0,0 +1,393 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { PassThrough, Writable } from 'node:stream';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ const SESSION = '22222222-2222-4222-8222-222222222222';
5
+ function agent(providerSessionId = null) {
6
+ return {
7
+ id: '11111111-1111-4111-8111-111111111111',
8
+ name: 'reviewer',
9
+ provider: 'codex',
10
+ mode: 'durable',
11
+ cwd: '/project',
12
+ providerSessionId,
13
+ state: 'running',
14
+ sessionHealth: 'uninitialized',
15
+ createdAt: '',
16
+ updatedAt: '',
17
+ lastActiveAt: null,
18
+ lastResult: null,
19
+ activeRun: null
20
+ };
21
+ }
22
+ function fakeSpawn(lines, exitCode = 0, chunks = false) {
23
+ const promptChunks = [];
24
+ const child = new EventEmitter();
25
+ child.pid = 4242;
26
+ child.stdout = new PassThrough();
27
+ child.stderr = new PassThrough();
28
+ child.kill = vi.fn();
29
+ child.stdin = new Writable({
30
+ write (chunk, _encoding, callback) {
31
+ promptChunks.push(Buffer.from(chunk));
32
+ callback();
33
+ },
34
+ final (callback) {
35
+ const output = lines.join('\n');
36
+ if (chunks) {
37
+ const bytes = Buffer.from(output);
38
+ child.stdout.write(bytes.subarray(0, 7));
39
+ child.stdout.write(bytes.subarray(7));
40
+ } else child.stdout.write(output);
41
+ child.stdout.end();
42
+ queueMicrotask(()=>child.emit('close', exitCode, null));
43
+ callback();
44
+ }
45
+ });
46
+ const spawn = vi.fn(()=>child);
47
+ return {
48
+ child,
49
+ spawn,
50
+ promptChunks
51
+ };
52
+ }
53
+ function events(session = SESSION) {
54
+ return [
55
+ JSON.stringify({
56
+ type: 'thread.started',
57
+ thread_id: session
58
+ }),
59
+ JSON.stringify({
60
+ type: 'turn.started'
61
+ }),
62
+ JSON.stringify({
63
+ type: 'future.event',
64
+ anything: true
65
+ }),
66
+ JSON.stringify({
67
+ type: 'item.completed',
68
+ item: {
69
+ type: 'agent_message',
70
+ text: 'first'
71
+ }
72
+ }),
73
+ JSON.stringify({
74
+ type: 'item.completed',
75
+ item: {
76
+ type: 'agent_message',
77
+ text: 'final'
78
+ }
79
+ }),
80
+ JSON.stringify({
81
+ type: 'turn.completed'
82
+ }),
83
+ ''
84
+ ];
85
+ }
86
+ async function runner(fixture, maxLineBytes) {
87
+ const api = await import('../../index.js');
88
+ expect(api).toHaveProperty('CodexPrintRunner');
89
+ const Runner = api.CodexPrintRunner;
90
+ return new Runner({
91
+ spawn: fixture.spawn,
92
+ maxLineBytes,
93
+ processInspector: {
94
+ getIdentity: ()=>({
95
+ pid: 4242,
96
+ startedAt: 'provider-start'
97
+ })
98
+ }
99
+ });
100
+ }
101
+ describe('CodexPrintRunner', ()=>{
102
+ it('constructs default process dependencies without spawning', async ()=>{
103
+ const api = await import('../../index.js');
104
+ const Runner = api.CodexPrintRunner;
105
+ expect(new Runner()).toBeDefined();
106
+ });
107
+ it('binds an initial thread before returning ordered assistant output', async ()=>{
108
+ const fixture = fakeSpawn(events(), 0, true);
109
+ const instance = await runner(fixture);
110
+ const order = [];
111
+ const result = await instance.run({
112
+ agent: agent(),
113
+ prompt: 'secret prompt',
114
+ executable: 'fake-codex',
115
+ onSpawn: async ()=>{
116
+ expect(fixture.promptChunks).toHaveLength(0);
117
+ order.push('spawn');
118
+ },
119
+ onSession: async (id)=>{
120
+ expect(id).toBe(SESSION);
121
+ order.push('session');
122
+ }
123
+ });
124
+ expect(order).toEqual([
125
+ 'spawn',
126
+ 'session'
127
+ ]);
128
+ expect(fixture.spawn).toHaveBeenCalledWith('fake-codex', [
129
+ 'exec',
130
+ '--json',
131
+ '-'
132
+ ], expect.objectContaining({
133
+ cwd: '/project',
134
+ shell: false,
135
+ stdio: [
136
+ 'pipe',
137
+ 'pipe',
138
+ 'pipe'
139
+ ]
140
+ }));
141
+ expect(JSON.stringify(fixture.spawn.mock.calls)).not.toContain('secret prompt');
142
+ expect(Buffer.concat(fixture.promptChunks).toString()).toBe('secret prompt');
143
+ expect(result).toEqual({
144
+ sessionId: SESSION,
145
+ result: 'final',
146
+ messages: [
147
+ 'first',
148
+ 'final'
149
+ ],
150
+ exitCode: 0
151
+ });
152
+ });
153
+ it('resumes the exact stored UUID and rejects a mismatch', async ()=>{
154
+ const mismatch = '33333333-3333-4333-8333-333333333333';
155
+ const fixture = fakeSpawn(events(mismatch));
156
+ const instance = await runner(fixture);
157
+ await expect(instance.run({
158
+ agent: agent(SESSION),
159
+ prompt: 'later',
160
+ onSpawn: vi.fn(),
161
+ onSession: vi.fn()
162
+ })).rejects.toMatchObject({
163
+ code: 'CODEX_SESSION_MISMATCH'
164
+ });
165
+ expect(fixture.spawn.mock.calls[0][1]).toEqual([
166
+ 'exec',
167
+ 'resume',
168
+ '--json',
169
+ SESSION,
170
+ '-'
171
+ ]);
172
+ });
173
+ it.each([
174
+ [
175
+ 'malformed JSON',
176
+ [
177
+ '{bad\n'
178
+ ],
179
+ 'CODEX_PROTOCOL'
180
+ ],
181
+ [
182
+ 'non-object JSON',
183
+ [
184
+ '[]\n'
185
+ ],
186
+ 'CODEX_PROTOCOL'
187
+ ],
188
+ [
189
+ 'truncated JSON',
190
+ [
191
+ '{}'
192
+ ],
193
+ 'CODEX_PROTOCOL'
194
+ ],
195
+ [
196
+ 'missing thread',
197
+ [
198
+ JSON.stringify({
199
+ type: 'item.completed',
200
+ item: {
201
+ type: 'agent_message',
202
+ text: 'x'
203
+ }
204
+ }),
205
+ JSON.stringify({
206
+ type: 'turn.completed'
207
+ }),
208
+ ''
209
+ ],
210
+ 'CODEX_PROTOCOL'
211
+ ],
212
+ [
213
+ 'missing assistant',
214
+ [
215
+ JSON.stringify({
216
+ type: 'thread.started',
217
+ thread_id: SESSION
218
+ }),
219
+ JSON.stringify({
220
+ type: 'turn.completed'
221
+ }),
222
+ ''
223
+ ],
224
+ 'CODEX_RESULT_MISSING'
225
+ ],
226
+ [
227
+ 'missing completion',
228
+ [
229
+ JSON.stringify({
230
+ type: 'thread.started',
231
+ thread_id: SESSION
232
+ }),
233
+ JSON.stringify({
234
+ type: 'item.completed',
235
+ item: {
236
+ type: 'agent_message',
237
+ text: 'x'
238
+ }
239
+ }),
240
+ ''
241
+ ],
242
+ 'CODEX_PROTOCOL'
243
+ ]
244
+ ])('rejects %s', async (_name, lines, code)=>{
245
+ const fixture = fakeSpawn(lines);
246
+ await expect((await runner(fixture)).run({
247
+ agent: agent(),
248
+ prompt: 'x',
249
+ onSpawn: vi.fn(),
250
+ onSession: vi.fn()
251
+ })).rejects.toMatchObject({
252
+ code
253
+ });
254
+ });
255
+ it('rejects oversized output, non-zero exit, and missing process identity without leaking stderr', async ()=>{
256
+ const oversized = fakeSpawn([
257
+ `${'x'.repeat(20)}\n`
258
+ ]);
259
+ await expect((await runner(oversized, 10)).run({
260
+ agent: agent(),
261
+ prompt: 'x',
262
+ onSpawn: vi.fn(),
263
+ onSession: vi.fn()
264
+ })).rejects.toMatchObject({
265
+ code: 'CODEX_PROTOCOL'
266
+ });
267
+ const oversizedWithoutNewline = fakeSpawn([
268
+ 'x'.repeat(20)
269
+ ]);
270
+ await expect((await runner(oversizedWithoutNewline, 10)).run({
271
+ agent: agent(),
272
+ prompt: 'x',
273
+ onSpawn: vi.fn(),
274
+ onSession: vi.fn()
275
+ })).rejects.toMatchObject({
276
+ code: 'CODEX_PROTOCOL'
277
+ });
278
+ const failed = fakeSpawn(events(), 1);
279
+ failed.child.stderr.end('secret-looking provider diagnostic');
280
+ await expect((await runner(failed)).run({
281
+ agent: agent(),
282
+ prompt: 'x',
283
+ onSpawn: vi.fn(),
284
+ onSession: vi.fn()
285
+ })).rejects.toMatchObject({
286
+ code: 'CODEX_PROCESS'
287
+ });
288
+ const missing = fakeSpawn([]);
289
+ missing.child.pid = undefined;
290
+ await expect((await runner(missing)).run({
291
+ agent: agent(),
292
+ prompt: 'x',
293
+ onSpawn: vi.fn(),
294
+ onSession: vi.fn()
295
+ })).rejects.toMatchObject({
296
+ code: 'CODEX_PROCESS'
297
+ });
298
+ });
299
+ it('rejects invalid or duplicate thread identities', async ()=>{
300
+ for (const lines of [
301
+ [
302
+ JSON.stringify({
303
+ type: 'thread.started',
304
+ thread_id: 'bad'
305
+ }),
306
+ ''
307
+ ],
308
+ [
309
+ JSON.stringify({
310
+ type: 'thread.started',
311
+ thread_id: SESSION
312
+ }),
313
+ JSON.stringify({
314
+ type: 'thread.started',
315
+ thread_id: SESSION
316
+ }),
317
+ ''
318
+ ]
319
+ ]){
320
+ const fixture = fakeSpawn(lines);
321
+ await expect((await runner(fixture)).run({
322
+ agent: agent(),
323
+ prompt: 'x',
324
+ onSpawn: vi.fn(),
325
+ onSession: vi.fn()
326
+ })).rejects.toMatchObject({
327
+ code: 'CODEX_PROTOCOL'
328
+ });
329
+ }
330
+ });
331
+ it('classifies callback processing failure and kills when spawn persistence fails', async ()=>{
332
+ const callbackFailure = fakeSpawn(events());
333
+ await expect((await runner(callbackFailure)).run({
334
+ agent: agent(),
335
+ prompt: 'x',
336
+ onSpawn: vi.fn(),
337
+ onSession: vi.fn().mockRejectedValue(new Error('storage unavailable'))
338
+ })).rejects.toMatchObject({
339
+ code: 'CODEX_PROTOCOL'
340
+ });
341
+ const spawnFailure = fakeSpawn([]);
342
+ const failure = new Error('cannot persist process');
343
+ await expect((await runner(spawnFailure)).run({
344
+ agent: agent(),
345
+ prompt: 'x',
346
+ onSpawn: vi.fn().mockRejectedValue(failure),
347
+ onSession: vi.fn()
348
+ })).rejects.toBe(failure);
349
+ expect(spawnFailure.child.kill).toHaveBeenCalledOnce();
350
+ });
351
+ it('rejects an unverifiable positive PID', async ()=>{
352
+ const api = await import('../../index.js');
353
+ const fixture = fakeSpawn([]);
354
+ const Runner = api.CodexPrintRunner;
355
+ const instance = new Runner({
356
+ spawn: fixture.spawn,
357
+ processInspector: {
358
+ getIdentity: ()=>null
359
+ }
360
+ });
361
+ await expect(instance.run({
362
+ agent: agent(),
363
+ prompt: 'x',
364
+ onSpawn: vi.fn(),
365
+ onSession: vi.fn()
366
+ })).rejects.toMatchObject({
367
+ code: 'CODEX_PROCESS'
368
+ });
369
+ expect(fixture.child.kill).toHaveBeenCalledOnce();
370
+ });
371
+ it('classifies a child spawn error as a process failure', async ()=>{
372
+ const fixture = fakeSpawn([]);
373
+ fixture.child.stdin = new Writable({
374
+ write (_chunk, _encoding, callback) {
375
+ callback();
376
+ },
377
+ final (callback) {
378
+ queueMicrotask(()=>fixture.child.emit('error', new Error('spawn failed')));
379
+ callback();
380
+ }
381
+ });
382
+ await expect((await runner(fixture)).run({
383
+ agent: agent(),
384
+ prompt: 'x',
385
+ onSpawn: vi.fn(),
386
+ onSession: vi.fn()
387
+ })).rejects.toMatchObject({
388
+ code: 'CODEX_PROCESS'
389
+ });
390
+ });
391
+ });
392
+
393
+ //# sourceMappingURL=CodexPrintRunner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/durable/CodexPrintRunner.test.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { PassThrough, Writable } from 'node:stream';\nimport { describe, expect, it, vi } from 'vitest';\nimport type { CodexDurableAgent } from '../../index.js';\n\nconst SESSION = '22222222-2222-4222-8222-222222222222';\n\nfunction agent(providerSessionId: string | null = null): CodexDurableAgent {\n return {\n id: '11111111-1111-4111-8111-111111111111', name: 'reviewer', provider: 'codex', mode: 'durable',\n cwd: '/project', providerSessionId, state: 'running', sessionHealth: 'uninitialized',\n createdAt: '', updatedAt: '', lastActiveAt: null, lastResult: null, activeRun: null,\n };\n}\n\nfunction fakeSpawn(lines: string[], exitCode = 0, chunks = false) {\n const promptChunks: Buffer[] = [];\n const child = new EventEmitter() as any;\n child.pid = 4242;\n child.stdout = new PassThrough();\n child.stderr = new PassThrough();\n child.kill = vi.fn();\n child.stdin = new Writable({\n write(chunk, _encoding, callback) { promptChunks.push(Buffer.from(chunk)); callback(); },\n final(callback) {\n const output = lines.join('\\n');\n if (chunks) {\n const bytes = Buffer.from(output);\n child.stdout.write(bytes.subarray(0, 7));\n child.stdout.write(bytes.subarray(7));\n } else child.stdout.write(output);\n child.stdout.end();\n queueMicrotask(() => child.emit('close', exitCode, null));\n callback();\n },\n });\n const spawn = vi.fn(() => child);\n return { child, spawn, promptChunks };\n}\n\nfunction events(session = SESSION): string[] {\n return [\n JSON.stringify({ type: 'thread.started', thread_id: session }),\n JSON.stringify({ type: 'turn.started' }),\n JSON.stringify({ type: 'future.event', anything: true }),\n JSON.stringify({ type: 'item.completed', item: { type: 'agent_message', text: 'first' } }),\n JSON.stringify({ type: 'item.completed', item: { type: 'agent_message', text: 'final' } }),\n JSON.stringify({ type: 'turn.completed' }),\n '',\n ];\n}\n\nasync function runner(fixture: ReturnType<typeof fakeSpawn>, maxLineBytes?: number) {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('CodexPrintRunner');\n const Runner = api.CodexPrintRunner as new (options: unknown) => any;\n return new Runner({ spawn: fixture.spawn, maxLineBytes, processInspector: {\n getIdentity: () => ({ pid: 4242, startedAt: 'provider-start' }),\n } });\n}\n\ndescribe('CodexPrintRunner', () => {\n it('constructs default process dependencies without spawning', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const Runner = api.CodexPrintRunner as new () => any;\n expect(new Runner()).toBeDefined();\n });\n\n it('binds an initial thread before returning ordered assistant output', async () => {\n const fixture = fakeSpawn(events(), 0, true);\n const instance = await runner(fixture);\n const order: string[] = [];\n const result = await instance.run({\n agent: agent(), prompt: 'secret prompt', executable: 'fake-codex',\n onSpawn: async () => { expect(fixture.promptChunks).toHaveLength(0); order.push('spawn'); },\n onSession: async (id: string) => { expect(id).toBe(SESSION); order.push('session'); },\n });\n\n expect(order).toEqual(['spawn', 'session']);\n expect(fixture.spawn).toHaveBeenCalledWith('fake-codex', ['exec', '--json', '-'], expect.objectContaining({\n cwd: '/project', shell: false, stdio: ['pipe', 'pipe', 'pipe'],\n }));\n expect(JSON.stringify(fixture.spawn.mock.calls)).not.toContain('secret prompt');\n expect(Buffer.concat(fixture.promptChunks).toString()).toBe('secret prompt');\n expect(result).toEqual({ sessionId: SESSION, result: 'final', messages: ['first', 'final'], exitCode: 0 });\n });\n\n it('resumes the exact stored UUID and rejects a mismatch', async () => {\n const mismatch = '33333333-3333-4333-8333-333333333333';\n const fixture = fakeSpawn(events(mismatch));\n const instance = await runner(fixture);\n await expect(instance.run({ agent: agent(SESSION), prompt: 'later', onSpawn: vi.fn(), onSession: vi.fn() }))\n .rejects.toMatchObject({ code: 'CODEX_SESSION_MISMATCH' });\n expect(fixture.spawn.mock.calls[0]![1]).toEqual(['exec', 'resume', '--json', SESSION, '-']);\n });\n\n it.each([\n ['malformed JSON', ['{bad\\n'], 'CODEX_PROTOCOL'],\n ['non-object JSON', ['[]\\n'], 'CODEX_PROTOCOL'],\n ['truncated JSON', ['{}'], 'CODEX_PROTOCOL'],\n ['missing thread', [JSON.stringify({ type: 'item.completed', item: { type: 'agent_message', text: 'x' } }), JSON.stringify({ type: 'turn.completed' }), ''], 'CODEX_PROTOCOL'],\n ['missing assistant', [JSON.stringify({ type: 'thread.started', thread_id: SESSION }), JSON.stringify({ type: 'turn.completed' }), ''], 'CODEX_RESULT_MISSING'],\n ['missing completion', [JSON.stringify({ type: 'thread.started', thread_id: SESSION }), JSON.stringify({ type: 'item.completed', item: { type: 'agent_message', text: 'x' } }), ''], 'CODEX_PROTOCOL'],\n ])('rejects %s', async (_name, lines, code) => {\n const fixture = fakeSpawn(lines as string[]);\n await expect((await runner(fixture)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code });\n });\n\n it('rejects oversized output, non-zero exit, and missing process identity without leaking stderr', async () => {\n const oversized = fakeSpawn([`${'x'.repeat(20)}\\n`]);\n await expect((await runner(oversized, 10)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROTOCOL' });\n const oversizedWithoutNewline = fakeSpawn(['x'.repeat(20)]);\n await expect((await runner(oversizedWithoutNewline, 10)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROTOCOL' });\n\n const failed = fakeSpawn(events(), 1);\n failed.child.stderr.end('secret-looking provider diagnostic');\n await expect((await runner(failed)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROCESS' });\n\n const missing = fakeSpawn([]);\n missing.child.pid = undefined;\n await expect((await runner(missing)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROCESS' });\n });\n\n it('rejects invalid or duplicate thread identities', async () => {\n for (const lines of [\n [JSON.stringify({ type: 'thread.started', thread_id: 'bad' }), ''],\n [\n JSON.stringify({ type: 'thread.started', thread_id: SESSION }),\n JSON.stringify({ type: 'thread.started', thread_id: SESSION }),\n '',\n ],\n ]) {\n const fixture = fakeSpawn(lines);\n await expect((await runner(fixture)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROTOCOL' });\n }\n });\n\n it('classifies callback processing failure and kills when spawn persistence fails', async () => {\n const callbackFailure = fakeSpawn(events());\n await expect((await runner(callbackFailure)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(),\n onSession: vi.fn().mockRejectedValue(new Error('storage unavailable')),\n })).rejects.toMatchObject({ code: 'CODEX_PROTOCOL' });\n\n const spawnFailure = fakeSpawn([]);\n const failure = new Error('cannot persist process');\n await expect((await runner(spawnFailure)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn().mockRejectedValue(failure), onSession: vi.fn(),\n })).rejects.toBe(failure);\n expect(spawnFailure.child.kill).toHaveBeenCalledOnce();\n });\n\n it('rejects an unverifiable positive PID', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const fixture = fakeSpawn([]);\n const Runner = api.CodexPrintRunner as new (options: unknown) => any;\n const instance = new Runner({ spawn: fixture.spawn, processInspector: { getIdentity: () => null } });\n await expect(instance.run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROCESS' });\n expect(fixture.child.kill).toHaveBeenCalledOnce();\n });\n\n it('classifies a child spawn error as a process failure', async () => {\n const fixture = fakeSpawn([]);\n fixture.child.stdin = new Writable({\n write(_chunk, _encoding, callback) { callback(); },\n final(callback) {\n queueMicrotask(() => fixture.child.emit('error', new Error('spawn failed')));\n callback();\n },\n });\n await expect((await runner(fixture)).run({\n agent: agent(), prompt: 'x', onSpawn: vi.fn(), onSession: vi.fn(),\n })).rejects.toMatchObject({ code: 'CODEX_PROCESS' });\n });\n});\n"],"names":["EventEmitter","PassThrough","Writable","describe","expect","it","vi","SESSION","agent","providerSessionId","id","name","provider","mode","cwd","state","sessionHealth","createdAt","updatedAt","lastActiveAt","lastResult","activeRun","fakeSpawn","lines","exitCode","chunks","promptChunks","child","pid","stdout","stderr","kill","fn","stdin","write","chunk","_encoding","callback","push","Buffer","from","final","output","join","bytes","subarray","end","queueMicrotask","emit","spawn","events","session","JSON","stringify","type","thread_id","anything","item","text","runner","fixture","maxLineBytes","api","toHaveProperty","Runner","CodexPrintRunner","processInspector","getIdentity","startedAt","toBeDefined","instance","order","result","run","prompt","executable","onSpawn","toHaveLength","onSession","toBe","toEqual","toHaveBeenCalledWith","objectContaining","shell","stdio","mock","calls","not","toContain","concat","toString","sessionId","messages","mismatch","rejects","toMatchObject","code","each","_name","oversized","repeat","oversizedWithoutNewline","failed","missing","undefined","callbackFailure","mockRejectedValue","Error","spawnFailure","failure","toHaveBeenCalledOnce","_chunk"],"mappings":"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"}
@@ -1,8 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
+ import { ClaudeCliProbe } from '../../providers/claude/durable/ClaudeCliProbe.js';
2
3
  describe('ClaudeCliProbe', ()=>{
3
4
  it('validates only version/help and requires the print session flags', async ()=>{
4
- const api = await import('../../index.js');
5
- expect(api).toHaveProperty('ClaudeCliProbe');
6
5
  const exec = vi.fn().mockResolvedValueOnce({
7
6
  stdout: '2.1.220\n',
8
7
  stderr: ''
@@ -10,8 +9,7 @@ describe('ClaudeCliProbe', ()=>{
10
9
  stdout: '--print --session-id --resume --output-format stream-json',
11
10
  stderr: ''
12
11
  });
13
- const Probe = api.ClaudeCliProbe;
14
- await expect(new Probe({
12
+ await expect(new ClaudeCliProbe({
15
13
  exec
16
14
  }).validate()).resolves.toEqual({
17
15
  executable: 'claude',
@@ -33,7 +31,6 @@ describe('ClaudeCliProbe', ()=>{
33
31
  ]);
34
32
  });
35
33
  it('rejects a CLI missing a required capability', async ()=>{
36
- const api = await import('../../index.js');
37
34
  const exec = vi.fn().mockResolvedValueOnce({
38
35
  stdout: 'old',
39
36
  stderr: ''
@@ -41,8 +38,7 @@ describe('ClaudeCliProbe', ()=>{
41
38
  stdout: '--print only',
42
39
  stderr: ''
43
40
  });
44
- const Probe = api.ClaudeCliProbe;
45
- await expect(new Probe({
41
+ await expect(new ClaudeCliProbe({
46
42
  exec
47
43
  }).validate()).rejects.toMatchObject({
48
44
  code: 'CLAUDE_CLI_UNSUPPORTED'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/__tests__/print/ClaudeCliProbe.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\ndescribe('ClaudeCliProbe', () => {\n it('validates only version/help and requires the print session flags', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('ClaudeCliProbe');\n const exec = vi.fn()\n .mockResolvedValueOnce({ stdout: '2.1.220\\n', stderr: '' })\n .mockResolvedValueOnce({\n stdout: '--print --session-id --resume --output-format stream-json', stderr: '',\n });\n const Probe = api.ClaudeCliProbe as new (options: unknown) => { validate(): Promise<unknown> };\n\n await expect(new Probe({ exec }).validate()).resolves.toEqual({\n executable: 'claude', version: '2.1.220',\n });\n expect(exec.mock.calls).toEqual([\n ['claude', ['--version']],\n ['claude', ['--help']],\n ]);\n });\n\n it('rejects a CLI missing a required capability', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const exec = vi.fn()\n .mockResolvedValueOnce({ stdout: 'old', stderr: '' })\n .mockResolvedValueOnce({ stdout: '--print only', stderr: '' });\n const Probe = api.ClaudeCliProbe as new (options: unknown) => { validate(): Promise<unknown> };\n\n await expect(new Probe({ exec }).validate()).rejects.toMatchObject({ code: 'CLAUDE_CLI_UNSUPPORTED' });\n });\n});\n"],"names":["describe","expect","it","vi","api","toHaveProperty","exec","fn","mockResolvedValueOnce","stdout","stderr","Probe","ClaudeCliProbe","validate","resolves","toEqual","executable","version","mock","calls","rejects","toMatchObject","code"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAElDH,SAAS,kBAAkB;IACvBE,GAAG,oEAAoE;QACnE,MAAME,MAAM,MAAM,MAAM,CAAC;QACzBH,OAAOG,KAAKC,cAAc,CAAC;QAC3B,MAAMC,OAAOH,GAAGI,EAAE,GACbC,qBAAqB,CAAC;YAAEC,QAAQ;YAAaC,QAAQ;QAAG,GACxDF,qBAAqB,CAAC;YACnBC,QAAQ;YAA6DC,QAAQ;QACjF;QACJ,MAAMC,QAAQP,IAAIQ,cAAc;QAEhC,MAAMX,OAAO,IAAIU,MAAM;YAAEL;QAAK,GAAGO,QAAQ,IAAIC,QAAQ,CAACC,OAAO,CAAC;YAC1DC,YAAY;YAAUC,SAAS;QACnC;QACAhB,OAAOK,KAAKY,IAAI,CAACC,KAAK,EAAEJ,OAAO,CAAC;YAC5B;gBAAC;gBAAU;oBAAC;iBAAY;aAAC;YACzB;gBAAC;gBAAU;oBAAC;iBAAS;aAAC;SACzB;IACL;IAEAb,GAAG,+CAA+C;QAC9C,MAAME,MAAM,MAAM,MAAM,CAAC;QACzB,MAAME,OAAOH,GAAGI,EAAE,GACbC,qBAAqB,CAAC;YAAEC,QAAQ;YAAOC,QAAQ;QAAG,GAClDF,qBAAqB,CAAC;YAAEC,QAAQ;YAAgBC,QAAQ;QAAG;QAChE,MAAMC,QAAQP,IAAIQ,cAAc;QAEhC,MAAMX,OAAO,IAAIU,MAAM;YAAEL;QAAK,GAAGO,QAAQ,IAAIO,OAAO,CAACC,aAAa,CAAC;YAAEC,MAAM;QAAyB;IACxG;AACJ"}
1
+ {"version":3,"sources":["../../../src/__tests__/print/ClaudeCliProbe.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\nimport { ClaudeCliProbe } from '../../providers/claude/durable/ClaudeCliProbe.js';\n\ndescribe('ClaudeCliProbe', () => {\n it('validates only version/help and requires the print session flags', async () => {\n const exec = vi.fn()\n .mockResolvedValueOnce({ stdout: '2.1.220\\n', stderr: '' })\n .mockResolvedValueOnce({\n stdout: '--print --session-id --resume --output-format stream-json', stderr: '',\n });\n\n await expect(new ClaudeCliProbe({ exec }).validate()).resolves.toEqual({\n executable: 'claude', version: '2.1.220',\n });\n expect(exec.mock.calls).toEqual([\n ['claude', ['--version']],\n ['claude', ['--help']],\n ]);\n });\n\n it('rejects a CLI missing a required capability', async () => {\n const exec = vi.fn()\n .mockResolvedValueOnce({ stdout: 'old', stderr: '' })\n .mockResolvedValueOnce({ stdout: '--print only', stderr: '' });\n\n await expect(new ClaudeCliProbe({ exec }).validate()).rejects.toMatchObject({ code: 'CLAUDE_CLI_UNSUPPORTED' });\n });\n});\n"],"names":["describe","expect","it","vi","ClaudeCliProbe","exec","fn","mockResolvedValueOnce","stdout","stderr","validate","resolves","toEqual","executable","version","mock","calls","rejects","toMatchObject","code"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAClD,SAASC,cAAc,QAAQ,mDAAmD;AAElFJ,SAAS,kBAAkB;IACvBE,GAAG,oEAAoE;QACnE,MAAMG,OAAOF,GAAGG,EAAE,GACbC,qBAAqB,CAAC;YAAEC,QAAQ;YAAaC,QAAQ;QAAG,GACxDF,qBAAqB,CAAC;YACnBC,QAAQ;YAA6DC,QAAQ;QACjF;QAEJ,MAAMR,OAAO,IAAIG,eAAe;YAAEC;QAAK,GAAGK,QAAQ,IAAIC,QAAQ,CAACC,OAAO,CAAC;YACnEC,YAAY;YAAUC,SAAS;QACnC;QACAb,OAAOI,KAAKU,IAAI,CAACC,KAAK,EAAEJ,OAAO,CAAC;YAC5B;gBAAC;gBAAU;oBAAC;iBAAY;aAAC;YACzB;gBAAC;gBAAU;oBAAC;iBAAS;aAAC;SACzB;IACL;IAEAV,GAAG,+CAA+C;QAC9C,MAAMG,OAAOF,GAAGG,EAAE,GACbC,qBAAqB,CAAC;YAAEC,QAAQ;YAAOC,QAAQ;QAAG,GAClDF,qBAAqB,CAAC;YAAEC,QAAQ;YAAgBC,QAAQ;QAAG;QAEhE,MAAMR,OAAO,IAAIG,eAAe;YAAEC;QAAK,GAAGK,QAAQ,IAAIO,OAAO,CAACC,aAAa,CAAC;YAAEC,MAAM;QAAyB;IACjH;AACJ"}
@@ -1,14 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { afterEach, describe, expect, it } from 'vitest';
6
- import { ClaudeCliProbe, ClaudePrintAgentService, ClaudePrintRunner, DurableAgentRepository } from '../../index.js';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { ClaudePrintAgentService, DurableAgentRepository } from '../../index.js';
7
6
  const roots = [];
8
- const originalCapture = process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;
9
7
  afterEach(()=>{
10
- if (originalCapture === undefined) delete process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;
11
- else process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = originalCapture;
12
8
  for (const root of roots.splice(0))fs.rmSync(root, {
13
9
  recursive: true,
14
10
  force: true
@@ -20,44 +16,54 @@ describe('Claude durable-agent fake-provider journey', ()=>{
20
16
  roots.push(root);
21
17
  const cwd = path.join(root, 'project');
22
18
  fs.mkdirSync(cwd);
23
- const capture = path.join(root, 'capture.jsonl');
24
- process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;
25
- const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));
19
+ const processInspector = {
20
+ getIdentity: (pid)=>({
21
+ pid,
22
+ startedAt: `process-${pid}`
23
+ })
24
+ };
26
25
  const repository = new DurableAgentRepository({
27
- dbPath: path.join(root, 'state', 'agents.db')
26
+ dbPath: path.join(root, 'state', 'agents.db'),
27
+ processInspector
28
28
  });
29
+ const runner = {
30
+ run: vi.fn().mockImplementation(async (request)=>{
31
+ await request.onSpawn({
32
+ pid: 42,
33
+ startedAt: 'process-42'
34
+ });
35
+ return {
36
+ sessionId: request.agent.providerSessionId,
37
+ result: `answer:${request.prompt}`,
38
+ exitCode: 0
39
+ };
40
+ })
41
+ };
29
42
  const service = new ClaudePrintAgentService({
30
43
  repository,
31
- probe: new ClaudeCliProbe({
32
- executable
33
- }),
34
- runner: new ClaudePrintRunner(),
35
- executable
44
+ probe: {
45
+ validate: vi.fn()
46
+ },
47
+ runner
36
48
  });
37
49
  const created = await service.create({
38
50
  name: 'reviewer',
39
51
  cwd
40
52
  });
41
- expect(fs.existsSync(capture)).toBe(false);
42
53
  await expect(service.send(created.id, 'first secret')).resolves.toMatchObject({
43
54
  result: 'answer:first secret'
44
55
  });
45
56
  await expect(service.send(created.id, 'follow up')).resolves.toMatchObject({
46
57
  result: 'answer:follow up'
47
58
  });
48
- const invocations = fs.readFileSync(capture, 'utf8').trim().split('\n').map((line)=>JSON.parse(line));
49
- expect(invocations[0]).toMatchObject({
50
- prompt: 'first secret',
51
- cwd: fs.realpathSync(cwd)
59
+ expect(runner.run.mock.calls[0][0]).toMatchObject({
60
+ firstRun: true,
61
+ prompt: 'first secret'
52
62
  });
53
- expect(invocations[0].args).toContain('--session-id');
54
- expect(invocations[0].args).not.toContain('first secret');
55
- expect(invocations[1]).toMatchObject({
56
- prompt: 'follow up',
57
- cwd: fs.realpathSync(cwd)
63
+ expect(runner.run.mock.calls[1][0]).toMatchObject({
64
+ firstRun: false,
65
+ prompt: 'follow up'
58
66
  });
59
- expect(invocations[1].args).toContain('--resume');
60
- expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);
61
67
  const persisted = await repository.getById(created.id);
62
68
  expect(persisted).toMatchObject({
63
69
  state: 'ready',