@ai-devkit/agent-manager 0.27.0 → 0.28.1

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 (87) hide show
  1. package/README.md +12 -4
  2. package/dist/__tests__/adapters/CodexAdapter.test.js +199 -0
  3. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  4. package/dist/__tests__/database/DurableAgentsDatabase.test.js +100 -0
  5. package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +1 -0
  6. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +7 -7
  7. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
  8. package/dist/__tests__/print/ClaudePrintAgentService.test.js +7 -7
  9. package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
  10. package/dist/__tests__/print/ClaudePrintRunner.test.js +1 -1
  11. package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
  12. package/dist/__tests__/print/DurableAgent.test.js +17 -0
  13. package/dist/__tests__/print/DurableAgent.test.js.map +1 -0
  14. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js +186 -0
  15. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js.map +1 -0
  16. package/dist/__tests__/print/{PrintAgentStore.test.js → DurableAgentRepository.test.js} +63 -110
  17. package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -0
  18. package/dist/adapters/CodexAdapter.d.ts +1 -0
  19. package/dist/adapters/CodexAdapter.d.ts.map +1 -1
  20. package/dist/adapters/CodexAdapter.js +16 -6
  21. package/dist/adapters/CodexAdapter.js.map +1 -1
  22. package/dist/database/connection.d.ts.map +1 -1
  23. package/dist/database/connection.js +17 -9
  24. package/dist/database/connection.js.map +1 -1
  25. package/dist/database/migrations/003_durable_agents.sql +43 -0
  26. package/dist/durable/ClaudeCliProbe.d.ts.map +1 -0
  27. package/dist/{print → durable}/ClaudeCliProbe.js +1 -1
  28. package/dist/durable/ClaudeCliProbe.js.map +1 -0
  29. package/dist/{print → durable}/ClaudePrintAgentService.d.ts +11 -11
  30. package/dist/durable/ClaudePrintAgentService.d.ts.map +1 -0
  31. package/dist/{print → durable}/ClaudePrintAgentService.js +12 -12
  32. package/dist/durable/ClaudePrintAgentService.js.map +1 -0
  33. package/dist/{print → durable}/ClaudePrintRunner.d.ts +3 -3
  34. package/dist/durable/ClaudePrintRunner.d.ts.map +1 -0
  35. package/dist/{print → durable}/ClaudePrintRunner.js +2 -2
  36. package/dist/durable/ClaudePrintRunner.js.map +1 -0
  37. package/dist/durable/DurableAgent.d.ts +61 -0
  38. package/dist/durable/DurableAgent.d.ts.map +1 -0
  39. package/dist/durable/DurableAgent.js +46 -0
  40. package/dist/durable/DurableAgent.js.map +1 -0
  41. package/dist/durable/DurableAgentRepository.d.ts +60 -0
  42. package/dist/durable/DurableAgentRepository.d.ts.map +1 -0
  43. package/dist/durable/DurableAgentRepository.js +325 -0
  44. package/dist/durable/DurableAgentRepository.js.map +1 -0
  45. package/dist/index.d.ts +11 -11
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +6 -6
  48. package/dist/index.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/__tests__/adapters/CodexAdapter.test.ts +132 -0
  51. package/src/__tests__/database/DurableAgentsDatabase.test.ts +77 -0
  52. package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +6 -6
  53. package/src/__tests__/print/ClaudePrintAgentService.test.ts +7 -7
  54. package/src/__tests__/print/ClaudePrintRunner.test.ts +3 -3
  55. package/src/__tests__/print/{PrintAgent.test.ts → DurableAgent.test.ts} +6 -6
  56. package/src/__tests__/print/DurableAgentRepository.sqlite.test.ts +105 -0
  57. package/src/__tests__/print/DurableAgentRepository.test.ts +163 -0
  58. package/src/adapters/CodexAdapter.ts +14 -11
  59. package/src/database/connection.ts +15 -9
  60. package/src/database/migrations/003_durable_agents.sql +43 -0
  61. package/src/{print → durable}/ClaudeCliProbe.ts +1 -1
  62. package/src/{print → durable}/ClaudePrintAgentService.ts +21 -21
  63. package/src/{print → durable}/ClaudePrintRunner.ts +4 -4
  64. package/src/durable/DurableAgent.ts +91 -0
  65. package/src/durable/DurableAgentRepository.ts +307 -0
  66. package/src/index.ts +26 -25
  67. package/dist/__tests__/print/PrintAgent.test.js +0 -17
  68. package/dist/__tests__/print/PrintAgent.test.js.map +0 -1
  69. package/dist/__tests__/print/PrintAgentStore.test.js.map +0 -1
  70. package/dist/print/ClaudeCliProbe.d.ts.map +0 -1
  71. package/dist/print/ClaudeCliProbe.js.map +0 -1
  72. package/dist/print/ClaudePrintAgentService.d.ts.map +0 -1
  73. package/dist/print/ClaudePrintAgentService.js.map +0 -1
  74. package/dist/print/ClaudePrintRunner.d.ts.map +0 -1
  75. package/dist/print/ClaudePrintRunner.js.map +0 -1
  76. package/dist/print/PrintAgent.d.ts +0 -57
  77. package/dist/print/PrintAgent.d.ts.map +0 -1
  78. package/dist/print/PrintAgent.js +0 -42
  79. package/dist/print/PrintAgent.js.map +0 -1
  80. package/dist/print/PrintAgentStore.d.ts +0 -69
  81. package/dist/print/PrintAgentStore.d.ts.map +0 -1
  82. package/dist/print/PrintAgentStore.js +0 -484
  83. package/dist/print/PrintAgentStore.js.map +0 -1
  84. package/src/__tests__/print/PrintAgentStore.test.ts +0 -192
  85. package/src/print/PrintAgent.ts +0 -86
  86. package/src/print/PrintAgentStore.ts +0 -503
  87. /package/dist/{print → durable}/ClaudeCliProbe.d.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,YAAY,EACR,YAAY,EACZ,SAAS,EACT,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,GACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACtG,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACnG,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEzE,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE3G,OAAO,EACH,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,2BAA2B,EAC3B,gBAAgB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACR,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,GAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EACR,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,YAAY,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,YAAY,EACR,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,YAAY,EACR,8BAA8B,EAC9B,qBAAqB,GACxB,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,YAAY,EACR,YAAY,EACZ,SAAS,EACT,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,qBAAqB,GACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACtG,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACnG,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEzE,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE3G,OAAO,EACH,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,6BAA6B,EAC7B,gBAAgB,GACnB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACR,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,YAAY,EACR,uBAAuB,EACvB,6BAA6B,EAC7B,gBAAgB,EAChB,oBAAoB,GACvB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EACR,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,YAAY,EACR,8BAA8B,EAC9B,qBAAqB,GACxB,MAAM,sCAAsC,CAAC"}
package/dist/index.js CHANGED
@@ -15,11 +15,11 @@ export { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils
15
15
  export { TmuxManager } from './terminal/TmuxManager.js';
16
16
  export { AGENTS } from './utils/agents.js';
17
17
  export { getAgentRequestPath, readLatestAgentRequest, writeAgentRequest } from './utils/agent-requests.js';
18
- export { PrintAgentError, PrintAgentBusyError, PrintAgentNotFoundError, PrintAgentStoreError, PrintAgentNameConflictError, ClaudePrintError } from './print/PrintAgent.js';
19
- export { PrintAgentStore } from './print/PrintAgentStore.js';
20
- export { LocalProcessInspector } from './print/PrintAgentStore.js';
21
- export { ClaudeCliProbe } from './print/ClaudeCliProbe.js';
22
- export { ClaudePrintRunner } from './print/ClaudePrintRunner.js';
23
- export { ClaudePrintAgentService } from './print/ClaudePrintAgentService.js';
18
+ export { AGENT_MODES, DurableAgentError, DurableAgentBusyError, DurableAgentNotFoundError, DurableAgentRepositoryError, DurableAgentNameConflictError, ClaudePrintError } from './durable/DurableAgent.js';
19
+ export { DurableAgentRepository } from './durable/DurableAgentRepository.js';
20
+ export { LocalProcessInspector } from './durable/DurableAgentRepository.js';
21
+ export { ClaudeCliProbe } from './durable/ClaudeCliProbe.js';
22
+ export { ClaudePrintRunner } from './durable/ClaudePrintRunner.js';
23
+ export { ClaudePrintAgentService } from './durable/ClaudePrintAgentService.js';
24
24
 
25
25
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { AgentManager, AgentNotRunningError } from './AgentManager.js';\n\nexport { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter.js';\nexport { CodexAdapter } from './adapters/CodexAdapter.js';\nexport { CopilotAdapter } from './adapters/CopilotAdapter.js';\nexport { GeminiCliAdapter } from './adapters/GeminiCliAdapter.js';\nexport { GrokCliAdapter } from './adapters/GrokCliAdapter.js';\nexport { OpenCodeAdapter } from './adapters/OpenCodeAdapter.js';\nexport { PiAdapter } from './adapters/PiAdapter.js';\nexport { AgentStatus } from './adapters/AgentAdapter.js';\nexport type {\n AgentAdapter,\n AgentType,\n AgentInfo,\n ProcessInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n AgentDetectionContext,\n} from './adapters/AgentAdapter.js';\n\nexport { TerminalFocusManager, TerminalType } from './terminal/TerminalFocusManager.js';\nexport type { TerminalLocation } from './terminal/TerminalFocusManager.js';\nexport { TtyWriter } from './terminal/TtyWriter.js';\n\nexport { getProcessTty } from './utils/process.js';\nexport { captureProcessSnapshot, executableBasename, filterByProcessNames } from './utils/process.js';\nexport type { AgentSortKey } from './utils/sortAgents.js';\nexport type { ListAgentsOptions } from './AgentManager.js';\n\nexport { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils/AgentRegistry.js';\nexport type { AgentRegistryOptions, RegistryEntry } from './utils/AgentRegistry.js';\nexport { TmuxManager } from './terminal/TmuxManager.js';\nexport { AGENTS } from './utils/agents.js';\nexport type { AgentConfig, StartableAgentType } from './utils/agents.js';\n\nexport type { AgentRequest } from './utils/agent-requests.js';\nexport { getAgentRequestPath, readLatestAgentRequest, writeAgentRequest } from './utils/agent-requests.js';\n\nexport {\n PrintAgentError,\n PrintAgentBusyError,\n PrintAgentNotFoundError,\n PrintAgentStoreError,\n PrintAgentNameConflictError,\n ClaudePrintError,\n} from './print/PrintAgent.js';\nexport type {\n PrintAgent,\n PrintAgentState,\n PrintSessionHealth,\n PrintRunStatus,\n PrintActiveRun,\n PrintLastResult,\n ProcessIdentity,\n} from './print/PrintAgent.js';\nexport { PrintAgentStore } from './print/PrintAgentStore.js';\nexport { LocalProcessInspector } from './print/PrintAgentStore.js';\nexport type {\n CreatePrintAgentInput,\n PrintAgentStoreOptions,\n ProcessInspector,\n PrintRunCompletion,\n} from './print/PrintAgentStore.js';\nexport { ClaudeCliProbe } from './print/ClaudeCliProbe.js';\nexport type { ClaudeCliProbeOptions } from './print/ClaudeCliProbe.js';\nexport { ClaudePrintRunner } from './print/ClaudePrintRunner.js';\nexport type {\n ClaudePrintRunnerOptions,\n ClaudePrintRunRequest,\n ClaudePrintRunResult,\n} from './print/ClaudePrintRunner.js';\nexport { ClaudePrintAgentService } from './print/ClaudePrintAgentService.js';\nexport type {\n ClaudePrintAgentServiceOptions,\n ClaudePrintSendResult,\n} from './print/ClaudePrintAgentService.js';\n"],"names":["AgentManager","AgentNotRunningError","ClaudeCodeAdapter","CodexAdapter","CopilotAdapter","GeminiCliAdapter","GrokCliAdapter","OpenCodeAdapter","PiAdapter","AgentStatus","TerminalFocusManager","TerminalType","TtyWriter","getProcessTty","captureProcessSnapshot","executableBasename","filterByProcessNames","AgentRegistry","RenameNotFoundError","RenameConflictError","TmuxManager","AGENTS","getAgentRequestPath","readLatestAgentRequest","writeAgentRequest","PrintAgentError","PrintAgentBusyError","PrintAgentNotFoundError","PrintAgentStoreError","PrintAgentNameConflictError","ClaudePrintError","PrintAgentStore","LocalProcessInspector","ClaudeCliProbe","ClaudePrintRunner","ClaudePrintAgentService"],"mappings":"AAAA,SAASA,YAAY,EAAEC,oBAAoB,QAAQ,oBAAoB;AAEvE,SAASC,iBAAiB,QAAQ,kCAAkC;AACpE,SAASC,YAAY,QAAQ,6BAA6B;AAC1D,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,gBAAgB,QAAQ,iCAAiC;AAClE,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,eAAe,QAAQ,gCAAgC;AAChE,SAASC,SAAS,QAAQ,0BAA0B;AACpD,SAASC,WAAW,QAAQ,6BAA6B;AAYzD,SAASC,oBAAoB,EAAEC,YAAY,QAAQ,qCAAqC;AAExF,SAASC,SAAS,QAAQ,0BAA0B;AAEpD,SAASC,aAAa,QAAQ,qBAAqB;AACnD,SAASC,sBAAsB,EAAEC,kBAAkB,EAAEC,oBAAoB,QAAQ,qBAAqB;AAItG,SAASC,aAAa,EAAEC,mBAAmB,EAAEC,mBAAmB,QAAQ,2BAA2B;AAEnG,SAASC,WAAW,QAAQ,4BAA4B;AACxD,SAASC,MAAM,QAAQ,oBAAoB;AAI3C,SAASC,mBAAmB,EAAEC,sBAAsB,EAAEC,iBAAiB,QAAQ,4BAA4B;AAE3G,SACIC,eAAe,EACfC,mBAAmB,EACnBC,uBAAuB,EACvBC,oBAAoB,EACpBC,2BAA2B,EAC3BC,gBAAgB,QACb,wBAAwB;AAU/B,SAASC,eAAe,QAAQ,6BAA6B;AAC7D,SAASC,qBAAqB,QAAQ,6BAA6B;AAOnE,SAASC,cAAc,QAAQ,4BAA4B;AAE3D,SAASC,iBAAiB,QAAQ,+BAA+B;AAMjE,SAASC,uBAAuB,QAAQ,qCAAqC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { AgentManager, AgentNotRunningError } from './AgentManager.js';\n\nexport { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter.js';\nexport { CodexAdapter } from './adapters/CodexAdapter.js';\nexport { CopilotAdapter } from './adapters/CopilotAdapter.js';\nexport { GeminiCliAdapter } from './adapters/GeminiCliAdapter.js';\nexport { GrokCliAdapter } from './adapters/GrokCliAdapter.js';\nexport { OpenCodeAdapter } from './adapters/OpenCodeAdapter.js';\nexport { PiAdapter } from './adapters/PiAdapter.js';\nexport { AgentStatus } from './adapters/AgentAdapter.js';\nexport type {\n AgentAdapter,\n AgentType,\n AgentInfo,\n ProcessInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n AgentDetectionContext,\n} from './adapters/AgentAdapter.js';\n\nexport { TerminalFocusManager, TerminalType } from './terminal/TerminalFocusManager.js';\nexport type { TerminalLocation } from './terminal/TerminalFocusManager.js';\nexport { TtyWriter } from './terminal/TtyWriter.js';\n\nexport { getProcessTty } from './utils/process.js';\nexport { captureProcessSnapshot, executableBasename, filterByProcessNames } from './utils/process.js';\nexport type { AgentSortKey } from './utils/sortAgents.js';\nexport type { ListAgentsOptions } from './AgentManager.js';\n\nexport { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils/AgentRegistry.js';\nexport type { AgentRegistryOptions, RegistryEntry } from './utils/AgentRegistry.js';\nexport { TmuxManager } from './terminal/TmuxManager.js';\nexport { AGENTS } from './utils/agents.js';\nexport type { AgentConfig, StartableAgentType } from './utils/agents.js';\n\nexport type { AgentRequest } from './utils/agent-requests.js';\nexport { getAgentRequestPath, readLatestAgentRequest, writeAgentRequest } from './utils/agent-requests.js';\n\nexport {\n AGENT_MODES,\n DurableAgentError,\n DurableAgentBusyError,\n DurableAgentNotFoundError,\n DurableAgentRepositoryError,\n DurableAgentNameConflictError,\n ClaudePrintError,\n} from './durable/DurableAgent.js';\nexport type {\n DurableAgent,\n DurableAgentState,\n DurableSessionHealth,\n DurableRunStatus,\n DurableActiveRun,\n DurableLastResult,\n ProcessIdentity,\n} from './durable/DurableAgent.js';\nexport { DurableAgentRepository } from './durable/DurableAgentRepository.js';\nexport { LocalProcessInspector } from './durable/DurableAgentRepository.js';\nexport type {\n CreateDurableAgentInput,\n DurableAgentRepositoryOptions,\n ProcessInspector,\n DurableRunCompletion,\n} from './durable/DurableAgentRepository.js';\nexport { ClaudeCliProbe } from './durable/ClaudeCliProbe.js';\nexport type { ClaudeCliProbeOptions } from './durable/ClaudeCliProbe.js';\nexport { ClaudePrintRunner } from './durable/ClaudePrintRunner.js';\nexport type {\n ClaudePrintRunnerOptions,\n ClaudePrintRunRequest,\n ClaudePrintRunResult,\n} from './durable/ClaudePrintRunner.js';\nexport { ClaudePrintAgentService } from './durable/ClaudePrintAgentService.js';\nexport type {\n ClaudePrintAgentServiceOptions,\n ClaudePrintSendResult,\n} from './durable/ClaudePrintAgentService.js';\n"],"names":["AgentManager","AgentNotRunningError","ClaudeCodeAdapter","CodexAdapter","CopilotAdapter","GeminiCliAdapter","GrokCliAdapter","OpenCodeAdapter","PiAdapter","AgentStatus","TerminalFocusManager","TerminalType","TtyWriter","getProcessTty","captureProcessSnapshot","executableBasename","filterByProcessNames","AgentRegistry","RenameNotFoundError","RenameConflictError","TmuxManager","AGENTS","getAgentRequestPath","readLatestAgentRequest","writeAgentRequest","AGENT_MODES","DurableAgentError","DurableAgentBusyError","DurableAgentNotFoundError","DurableAgentRepositoryError","DurableAgentNameConflictError","ClaudePrintError","DurableAgentRepository","LocalProcessInspector","ClaudeCliProbe","ClaudePrintRunner","ClaudePrintAgentService"],"mappings":"AAAA,SAASA,YAAY,EAAEC,oBAAoB,QAAQ,oBAAoB;AAEvE,SAASC,iBAAiB,QAAQ,kCAAkC;AACpE,SAASC,YAAY,QAAQ,6BAA6B;AAC1D,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,gBAAgB,QAAQ,iCAAiC;AAClE,SAASC,cAAc,QAAQ,+BAA+B;AAC9D,SAASC,eAAe,QAAQ,gCAAgC;AAChE,SAASC,SAAS,QAAQ,0BAA0B;AACpD,SAASC,WAAW,QAAQ,6BAA6B;AAYzD,SAASC,oBAAoB,EAAEC,YAAY,QAAQ,qCAAqC;AAExF,SAASC,SAAS,QAAQ,0BAA0B;AAEpD,SAASC,aAAa,QAAQ,qBAAqB;AACnD,SAASC,sBAAsB,EAAEC,kBAAkB,EAAEC,oBAAoB,QAAQ,qBAAqB;AAItG,SAASC,aAAa,EAAEC,mBAAmB,EAAEC,mBAAmB,QAAQ,2BAA2B;AAEnG,SAASC,WAAW,QAAQ,4BAA4B;AACxD,SAASC,MAAM,QAAQ,oBAAoB;AAI3C,SAASC,mBAAmB,EAAEC,sBAAsB,EAAEC,iBAAiB,QAAQ,4BAA4B;AAE3G,SACIC,WAAW,EACXC,iBAAiB,EACjBC,qBAAqB,EACrBC,yBAAyB,EACzBC,2BAA2B,EAC3BC,6BAA6B,EAC7BC,gBAAgB,QACb,4BAA4B;AAUnC,SAASC,sBAAsB,QAAQ,sCAAsC;AAC7E,SAASC,qBAAqB,QAAQ,sCAAsC;AAO5E,SAASC,cAAc,QAAQ,8BAA8B;AAE7D,SAASC,iBAAiB,QAAQ,iCAAiC;AAMnE,SAASC,uBAAuB,QAAQ,uCAAuC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-devkit/agent-manager",
3
- "version": "0.27.0",
3
+ "version": "0.28.1",
4
4
  "type": "module",
5
5
  "description": "Standalone agent detection and management utilities for AI DevKit",
6
6
  "main": "dist/index.js",
@@ -750,6 +750,45 @@ describe('CodexAdapter', () => {
750
750
  expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();
751
751
  });
752
752
 
753
+ it('uses the hook session mapping instead of a stale registry entry for the same live pid', async () => {
754
+ registerEntry();
755
+ const sessionsDir = path.join(tmpDir, '.codex', 'sessions');
756
+ const dateDir = path.join(sessionsDir, '2026', '06', '26');
757
+ const mappingPath = path.join(tmpDir, '.codex', 'ai-devkit', 'sessions.json');
758
+ const currentSessionFile = path.join(dateDir, 'sess-current.jsonl');
759
+ const recentTs = new Date().toISOString();
760
+
761
+ fs.mkdirSync(dateDir, { recursive: true });
762
+ fs.mkdirSync(path.dirname(mappingPath), { recursive: true });
763
+ fs.writeFileSync(currentSessionFile, [
764
+ JSON.stringify({ type: 'session_meta', payload: { id: 'sess-current', timestamp: recentTs, cwd: '/repo-current' } }),
765
+ JSON.stringify({ type: 'event', timestamp: recentTs, payload: { type: 'token_count', message: 'Hello from current mapping' } }),
766
+ ].join('\n'));
767
+ fs.writeFileSync(mappingPath, JSON.stringify({ 100: currentSessionFile }));
768
+
769
+ (cachedAdapter as any).codexSessionsDir = sessionsDir;
770
+ (cachedAdapter as any).sessionMappingPath = mappingPath;
771
+
772
+ const processes: ProcessInfo[] = [
773
+ { pid: 100, command: 'codex', cwd: '/repo-current', tty: 'ttys001', startTime: new Date() },
774
+ ];
775
+ mockedListAgentProcesses.mockReturnValue(processes);
776
+ mockedEnrichProcesses.mockReturnValue(processes);
777
+
778
+ const agents = await cachedAdapter.detectAgents();
779
+
780
+ expect(agents).toHaveLength(1);
781
+ expect(agents[0]).toMatchObject({
782
+ type: 'codex',
783
+ pid: 100,
784
+ sessionId: 'sess-current',
785
+ sessionFilePath: currentSessionFile,
786
+ summary: 'Hello from current mapping',
787
+ });
788
+ expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
789
+ expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();
790
+ });
791
+
753
792
  it('falls through when no registry entry exists for the pid', async () => {
754
793
  const processes: ProcessInfo[] = [
755
794
  { pid: 100, command: 'codex', cwd: '/repo-a', tty: 'ttys001', startTime: new Date() },
@@ -1566,6 +1605,99 @@ describe('CodexAdapter', () => {
1566
1605
  expect(result[0].firstUserMessage).toBe('first user');
1567
1606
  });
1568
1607
 
1608
+ it('captures the first current Codex response_item user message as firstUserMessage', async () => {
1609
+ const day = path.join(sessionsDir, '2025', '01', '01');
1610
+ writeCodexSession(day, 'response-item-user', [
1611
+ { type: 'session_meta', payload: { id: 'response-item-user', cwd: '/repo', timestamp: '2025-01-01T00:00:00Z' } },
1612
+ {
1613
+ type: 'response_item',
1614
+ timestamp: '2025-01-01T00:00:00.500Z',
1615
+ payload: {
1616
+ type: 'message',
1617
+ role: 'user',
1618
+ content: [{ type: 'input_text', text: '<environment_context>\n <cwd>/repo</cwd>\n</environment_context>' }],
1619
+ },
1620
+ },
1621
+ {
1622
+ type: 'response_item',
1623
+ timestamp: '2025-01-01T00:00:01Z',
1624
+ payload: {
1625
+ type: 'message',
1626
+ role: 'assistant',
1627
+ content: [{ type: 'output_text', text: 'preamble' }],
1628
+ },
1629
+ },
1630
+ {
1631
+ type: 'response_item',
1632
+ timestamp: '2025-01-01T00:00:02Z',
1633
+ payload: {
1634
+ type: 'message',
1635
+ role: 'user',
1636
+ content: [{ type: 'input_text', text: 'first current user' }],
1637
+ },
1638
+ },
1639
+ {
1640
+ type: 'response_item',
1641
+ timestamp: '2025-01-01T00:00:03Z',
1642
+ payload: {
1643
+ type: 'message',
1644
+ role: 'user',
1645
+ content: [{ type: 'input_text', text: 'second current user' }],
1646
+ },
1647
+ },
1648
+ ]);
1649
+
1650
+ const result = await adapter.listSessions({ cwd: '/repo' });
1651
+
1652
+ expect(result).toHaveLength(1);
1653
+ expect(result[0].firstUserMessage).toBe('first current user');
1654
+ });
1655
+
1656
+ it('captures the first current Codex event_msg user message as firstUserMessage', async () => {
1657
+ const day = path.join(sessionsDir, '2025', '01', '01');
1658
+ writeCodexSession(day, 'event-msg-user', [
1659
+ { type: 'session_meta', payload: { id: 'event-msg-user', cwd: '/repo', timestamp: '2025-01-01T00:00:00Z' } },
1660
+ {
1661
+ type: 'event_msg',
1662
+ timestamp: '2025-01-01T00:00:01Z',
1663
+ payload: {
1664
+ type: 'item_completed',
1665
+ item: {
1666
+ type: 'AgentMessage',
1667
+ content: [{ type: 'Text', text: 'preamble' }],
1668
+ },
1669
+ },
1670
+ },
1671
+ {
1672
+ type: 'event_msg',
1673
+ timestamp: '2025-01-01T00:00:02Z',
1674
+ payload: {
1675
+ type: 'item_completed',
1676
+ item: {
1677
+ type: 'UserMessage',
1678
+ content: [{ type: 'text', text: 'first event user' }],
1679
+ },
1680
+ },
1681
+ },
1682
+ {
1683
+ type: 'event_msg',
1684
+ timestamp: '2025-01-01T00:00:03Z',
1685
+ payload: {
1686
+ type: 'item_completed',
1687
+ item: {
1688
+ type: 'UserMessage',
1689
+ content: [{ type: 'text', text: 'second event user' }],
1690
+ },
1691
+ },
1692
+ },
1693
+ ]);
1694
+
1695
+ const result = await adapter.listSessions({ cwd: '/repo' });
1696
+
1697
+ expect(result).toHaveLength(1);
1698
+ expect(result[0].firstUserMessage).toBe('first event user');
1699
+ });
1700
+
1569
1701
  it('returns empty firstUserMessage when no user_message exists', async () => {
1570
1702
  const day = path.join(sessionsDir, '2025', '01', '01');
1571
1703
  writeCodexSession(day, 's', [
@@ -0,0 +1,77 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import Database from 'better-sqlite3';
5
+ import { afterEach, describe, expect, it } from 'vitest';
6
+ import { DatabaseConnection } from '../../database/connection.js';
7
+ import { getSchemaVersion } from '../../database/schema.js';
8
+
9
+ const roots: string[] = [];
10
+
11
+ afterEach(() => {
12
+ for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
13
+ });
14
+
15
+ function dbPath(): string {
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-db-'));
17
+ roots.push(root);
18
+ return path.join(root, 'state', 'agents.db');
19
+ }
20
+
21
+ describe('durable agents schema', () => {
22
+ it('migrates to version 3 with durable constraints and indexes', () => {
23
+ const connection = new DatabaseConnection({ dbPath: dbPath() });
24
+ expect(getSchemaVersion(connection)).toBe(3);
25
+ const table = connection.queryOne<{ sql: string }>(
26
+ "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'durable_agents'",
27
+ );
28
+ expect(table?.sql).toContain("DEFAULT 'durable'");
29
+ expect(table?.sql).toContain("state IN ('ready','running','degraded')");
30
+ expect(connection.query<{ name: string }>(
31
+ "SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'durable_agents'",
32
+ ).map(({ name }) => name)).toEqual(expect.arrayContaining([
33
+ 'idx_durable_agents_state', 'idx_durable_agents_list',
34
+ ]));
35
+ connection.close();
36
+ });
37
+
38
+ it('enforces case-insensitive names and active-run consistency but permits new providers', () => {
39
+ const connection = new DatabaseConnection({ dbPath: dbPath() });
40
+ const insert = (name: string, provider: string, state = 'ready') => connection.execute(`
41
+ INSERT INTO durable_agents (
42
+ id, name, provider, mode, cwd, provider_session_id, state, session_health,
43
+ created_at, updated_at
44
+ ) VALUES (?, ?, ?, 'durable', '/tmp', ?, ?, 'uninitialized', ?, ?)
45
+ `, [crypto.randomUUID(), name, provider, crypto.randomUUID(), state, new Date().toISOString(), new Date().toISOString()]);
46
+ expect(() => insert('Alpha', 'future-provider')).not.toThrow();
47
+ expect(() => insert('alpha', 'claude')).toThrow(/UNIQUE/i);
48
+ expect(() => insert('Broken', 'claude', 'running')).toThrow(/CHECK/i);
49
+ connection.close();
50
+ });
51
+ });
52
+
53
+ describe('readonly DatabaseConnection', () => {
54
+ it('opens an already migrated database without changing it', () => {
55
+ const file = dbPath();
56
+ const writable = new DatabaseConnection({ dbPath: file });
57
+ writable.close();
58
+ const before = fs.statSync(file).mtimeMs;
59
+ const readonly = new DatabaseConnection({ dbPath: file, readonly: true });
60
+ expect(readonly.queryOne<{ user_version: number }>('PRAGMA user_version')?.user_version).toBe(3);
61
+ readonly.close();
62
+ expect(fs.statSync(file).mtimeMs).toBe(before);
63
+ });
64
+
65
+ it('does not create a missing database or migrate an old one', () => {
66
+ const missing = dbPath();
67
+ expect(() => new DatabaseConnection({ dbPath: missing, readonly: true })).toThrow(/readonly.*exist/i);
68
+ expect(fs.existsSync(missing)).toBe(false);
69
+
70
+ fs.mkdirSync(path.dirname(missing), { recursive: true });
71
+ const raw = new Database(missing);
72
+ raw.pragma('user_version = 2');
73
+ raw.close();
74
+ expect(() => new DatabaseConnection({ dbPath: missing, readonly: true })).toThrow(/schema version 3/i);
75
+ expect(new Database(missing, { readonly: true }).pragma('user_version', { simple: true })).toBe(2);
76
+ });
77
+ });
@@ -7,7 +7,7 @@ import {
7
7
  ClaudeCliProbe,
8
8
  ClaudePrintAgentService,
9
9
  ClaudePrintRunner,
10
- PrintAgentStore,
10
+ DurableAgentRepository,
11
11
  } from '../../index.js';
12
12
 
13
13
  const roots: string[] = [];
@@ -19,18 +19,18 @@ afterEach(() => {
19
19
  for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
20
20
  });
21
21
 
22
- describe('Claude print-agent fake-provider journey', () => {
22
+ describe('Claude durable-agent fake-provider journey', () => {
23
23
  it('creates without invocation, then starts and resumes the same session through stdin', async () => {
24
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'print-agent-integration-'));
24
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-integration-'));
25
25
  roots.push(root);
26
26
  const cwd = path.join(root, 'project');
27
27
  fs.mkdirSync(cwd);
28
28
  const capture = path.join(root, 'capture.jsonl');
29
29
  process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;
30
30
  const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));
31
- const store = new PrintAgentStore({ filePath: path.join(root, 'state', 'print-agents.json') });
31
+ const repository = new DurableAgentRepository({ dbPath: path.join(root, 'state', 'agents.db') });
32
32
  const service = new ClaudePrintAgentService({
33
- store,
33
+ repository,
34
34
  probe: new ClaudeCliProbe({ executable }),
35
35
  runner: new ClaudePrintRunner(),
36
36
  executable,
@@ -50,7 +50,7 @@ describe('Claude print-agent fake-provider journey', () => {
50
50
  expect(invocations[1].args).toContain('--resume');
51
51
  expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);
52
52
 
53
- const persisted = await store.getById(created.id);
53
+ const persisted = await repository.getById(created.id);
54
54
  expect(persisted).toMatchObject({ state: 'ready', sessionHealth: 'healthy' });
55
55
  });
56
56
  });
@@ -5,21 +5,21 @@ describe('ClaudePrintAgentService', () => {
5
5
  const api = await import('../../index.js') as Record<string, unknown>;
6
6
  expect(api).toHaveProperty('ClaudePrintAgentService');
7
7
  const probe = { validate: vi.fn().mockResolvedValue({ executable: 'claude', version: '2.1.220' }) };
8
- const store = { create: vi.fn().mockResolvedValue({ id: 'agent-id', name: 'reviewer' }) };
8
+ const repository = { create: vi.fn().mockResolvedValue({ id: 'agent-id', name: 'reviewer' }) };
9
9
  const runner = { run: vi.fn() };
10
10
  const Service = api.ClaudePrintAgentService as new (options: unknown) => any;
11
11
 
12
- await expect(new Service({ store, probe, runner }).create({ name: 'reviewer', cwd: '/project' }))
12
+ await expect(new Service({ repository, probe, runner }).create({ name: 'reviewer', cwd: '/project' }))
13
13
  .resolves.toMatchObject({ id: 'agent-id' });
14
14
  expect(probe.validate).toHaveBeenCalledOnce();
15
- expect(store.create).toHaveBeenCalledWith({ name: 'reviewer', cwd: '/project' });
15
+ expect(repository.create).toHaveBeenCalledWith({ name: 'reviewer', cwd: '/project' });
16
16
  expect(runner.run).not.toHaveBeenCalled();
17
17
  });
18
18
 
19
19
  it('runs first and resumed sends and records provider identity/results', async () => {
20
20
  const api = await import('../../index.js') as Record<string, unknown>;
21
21
  const base = { id: 'id', name: 'reviewer', providerSessionId: 'session', sessionHealth: 'uninitialized' };
22
- const store = {
22
+ const repository = {
23
23
  resolve: vi.fn().mockResolvedValue(base),
24
24
  acquireRun: vi.fn()
25
25
  .mockResolvedValueOnce({ agent: base, token: 'one' })
@@ -31,15 +31,15 @@ describe('ClaudePrintAgentService', () => {
31
31
  return { sessionId: 'session', result: 'answer', exitCode: 0 };
32
32
  }) };
33
33
  const Service = api.ClaudePrintAgentService as new (options: unknown) => any;
34
- const service = new Service({ store, probe: { validate: vi.fn() }, runner, executable: 'fake-claude' });
34
+ const service = new Service({ repository, probe: { validate: vi.fn() }, runner, executable: 'fake-claude' });
35
35
 
36
36
  await service.send('reviewer', 'first');
37
37
  await service.send('id', 'later');
38
38
 
39
39
  expect(runner.run.mock.calls[0][0]).toMatchObject({ prompt: 'first', firstRun: true, executable: 'fake-claude' });
40
40
  expect(runner.run.mock.calls[1][0]).toMatchObject({ prompt: 'later', firstRun: false, executable: 'fake-claude' });
41
- expect(store.recordProviderProcess).toHaveBeenCalledWith('id', 'one', { pid: 42, startedAt: 'start' });
42
- expect(store.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({
41
+ expect(repository.recordProviderProcess).toHaveBeenCalledWith('id', 'one', { pid: 42, startedAt: 'start' });
42
+ expect(repository.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({
43
43
  status: 'succeeded', exitCode: 0, sessionHealth: 'healthy',
44
44
  }));
45
45
  });
@@ -1,11 +1,11 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { PassThrough, Writable } from 'node:stream';
3
3
  import { describe, expect, it, vi } from 'vitest';
4
- import type { PrintAgent } from '../../index.js';
4
+ import type { DurableAgent } from '../../index.js';
5
5
 
6
- function agent(): PrintAgent {
6
+ function agent(): DurableAgent {
7
7
  return {
8
- id: '11111111-1111-4111-8111-111111111111', name: 'reviewer', provider: 'claude', mode: 'print',
8
+ id: '11111111-1111-4111-8111-111111111111', name: 'reviewer', provider: 'claude', mode: 'durable',
9
9
  cwd: '/project', providerSessionId: '22222222-2222-4222-8222-222222222222', state: 'running',
10
10
  sessionHealth: 'uninitialized', createdAt: '', updatedAt: '', lastActiveAt: null, lastResult: null,
11
11
  activeRun: null,
@@ -1,21 +1,21 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- describe('print-agent public domain', () => {
3
+ describe('durable-agent public domain', () => {
4
4
  it('exports a classified busy error without exposing prompt data', async () => {
5
5
  const api = await import('../../index.js') as Record<string, unknown>;
6
6
 
7
- expect(api).toHaveProperty('PrintAgentBusyError');
8
- const ErrorType = api.PrintAgentBusyError as new (agentId: string, name: string) => Error & {
7
+ expect(api).toHaveProperty('DurableAgentBusyError');
8
+ const ErrorType = api.DurableAgentBusyError as new (agentId: string, name: string) => Error & {
9
9
  code: string;
10
10
  agentId: string;
11
11
  };
12
12
  const error = new ErrorType('agent-id', 'reviewer');
13
13
 
14
14
  expect(error).toMatchObject({
15
- name: 'PrintAgentBusyError',
16
- code: 'PRINT_AGENT_BUSY',
15
+ name: 'DurableAgentBusyError',
16
+ code: 'DURABLE_AGENT_BUSY',
17
17
  agentId: 'agent-id',
18
- message: 'Print agent "reviewer" is busy.',
18
+ message: 'Durable agent "reviewer" is busy.',
19
19
  });
20
20
  });
21
21
  });
@@ -0,0 +1,105 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import Database from 'better-sqlite3';
5
+ import { afterEach, describe, expect, it } from 'vitest';
6
+ import { DurableAgentRepository } from '../../durable/DurableAgentRepository.js';
7
+
8
+ const roots: string[] = [];
9
+ afterEach(() => {
10
+ for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
11
+ });
12
+
13
+ function fixture() {
14
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-sqlite-'));
15
+ roots.push(root);
16
+ const cwd = path.join(root, 'project');
17
+ fs.mkdirSync(cwd);
18
+ return { root, cwd, dbPath: path.join(root, 'state', 'agents.db') };
19
+ }
20
+
21
+ describe('DurableAgentRepository SQLite concurrency', () => {
22
+ it('allows exactly one acquisition across two connections', async () => {
23
+ const { cwd, dbPath } = fixture();
24
+ const identity = { pid: process.pid, startedAt: 'owner-start' };
25
+ const processInspector = { getIdentity: (pid: number) => pid === process.pid ? identity : null };
26
+ const first = new DurableAgentRepository({ dbPath, processInspector });
27
+ const second = new DurableAgentRepository({ dbPath, processInspector });
28
+ const agent = await first.create({ name: 'race', cwd });
29
+ const results = await Promise.allSettled([first.acquireRun(agent.id), second.acquireRun(agent.id)]);
30
+ expect(results.filter(({ status }) => status === 'fulfilled')).toHaveLength(1);
31
+ const rejected = results.find(({ status }) => status === 'rejected');
32
+ expect(rejected).toMatchObject({ reason: { code: 'DURABLE_AGENT_BUSY' } });
33
+ });
34
+
35
+ it('accepts deprecated lock options without creating lock artifacts', async () => {
36
+ const { root, cwd, dbPath } = fixture();
37
+ const repository = new DurableAgentRepository({
38
+ dbPath, lockTimeoutMs: 1, incompleteLockGraceMs: 1, mutationLockStaleMs: 1,
39
+ });
40
+ await repository.create({ name: 'lockless', cwd });
41
+ expect(fs.existsSync(`${dbPath}.lock`)).toBe(false);
42
+ expect(fs.existsSync(path.join(root, 'state', 'durable-agent-locks'))).toBe(false);
43
+ });
44
+
45
+ it('keeps readonly listing pure', async () => {
46
+ const { cwd, dbPath } = fixture();
47
+ const live = new Map([[process.pid, 'owner-start']]);
48
+ const processInspector = { getIdentity: (pid: number) => {
49
+ const startedAt = live.get(pid);
50
+ return startedAt ? { pid, startedAt } : null;
51
+ } };
52
+ const writable = new DurableAgentRepository({ dbPath, processInspector });
53
+ const agent = await writable.create({ name: 'readonly', cwd });
54
+ await writable.acquireRun(agent.id);
55
+ live.clear();
56
+ const readonly = new DurableAgentRepository({ dbPath, readonly: true, processInspector });
57
+ expect((await readonly.list())[0]?.state).toBe('running');
58
+ });
59
+
60
+ it('rejects stale tokens and caps the persisted completion summary', async () => {
61
+ const { cwd, dbPath } = fixture();
62
+ const processInspector = { getIdentity: (pid: number) => ({ pid, startedAt: 'owner-start' }) };
63
+ const repository = new DurableAgentRepository({ dbPath, processInspector });
64
+ const agent = await repository.create({ name: 'token', cwd });
65
+ const run = await repository.acquireRun(agent.id);
66
+ await expect(repository.recordProviderProcess(agent.id, 'stale', { pid: 42, startedAt: 'provider' }))
67
+ .rejects.toMatchObject({ code: 'DURABLE_AGENT_REPOSITORY' });
68
+ const completed = await repository.completeRun(agent.id, run.token, {
69
+ status: 'succeeded', exitCode: 0, summary: 'x'.repeat(5000), sessionHealth: 'healthy',
70
+ });
71
+ expect(completed.lastResult?.summary).toHaveLength(4096);
72
+ });
73
+
74
+ it('does not reconcile over ownership changed after process inspection', async () => {
75
+ const { cwd, dbPath } = fixture();
76
+ let inspect: (() => void) | undefined;
77
+ const processInspector = { getIdentity: (pid: number) => {
78
+ if (inspect) inspect();
79
+ return inspect ? null : { pid, startedAt: 'owner-start' };
80
+ } };
81
+ const repository = new DurableAgentRepository({ dbPath, processInspector });
82
+ const agent = await repository.create({ name: 'cas', cwd });
83
+ await repository.acquireRun(agent.id);
84
+ const other = new Database(dbPath);
85
+ inspect = () => {
86
+ inspect = undefined;
87
+ other.prepare(`UPDATE durable_agents SET
88
+ active_run_token = 'replacement-token', active_owner_started_at = 'replacement-owner'
89
+ WHERE id = ?`).run(agent.id);
90
+ };
91
+
92
+ await repository.reconcile();
93
+
94
+ expect(other.prepare('SELECT state, active_run_token FROM durable_agents WHERE id = ?').get(agent.id))
95
+ .toEqual({ state: 'running', active_run_token: 'replacement-token' });
96
+ other.close();
97
+ });
98
+
99
+ it('maps a corrupt database to a store error', () => {
100
+ const { dbPath } = fixture();
101
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
102
+ fs.writeFileSync(dbPath, 'not sqlite');
103
+ expect(() => new DurableAgentRepository({ dbPath })).toThrow(/Cannot open durable-agent database/);
104
+ });
105
+ });