@ai-devkit/agent-manager 0.32.0 → 0.33.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 (572) hide show
  1. package/dist/AgentManager.d.ts +13 -4
  2. package/dist/AgentManager.d.ts.map +1 -1
  3. package/dist/AgentManager.js +116 -84
  4. package/dist/AgentManager.js.map +1 -1
  5. package/dist/__tests__/AgentManager.test.js +623 -391
  6. package/dist/__tests__/AgentManager.test.js.map +1 -1
  7. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +642 -642
  8. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  9. package/dist/__tests__/adapters/CodexAdapter.test.js +322 -2321
  10. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  11. package/dist/__tests__/adapters/GrokCliAdapter.test.js +114 -114
  12. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +1 -1
  13. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +170 -169
  14. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -1
  15. package/dist/__tests__/capacity/codex.test.js +93 -93
  16. package/dist/__tests__/capacity/codex.test.js.map +1 -1
  17. package/dist/__tests__/capacity/index.test.js +17 -17
  18. package/dist/__tests__/capacity/index.test.js.map +1 -1
  19. package/dist/__tests__/durable/CodexCliProbe.test.js +60 -60
  20. package/dist/__tests__/durable/CodexCliProbe.test.js.map +1 -1
  21. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js +28 -28
  22. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js.map +1 -1
  23. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js +30 -30
  24. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js.map +1 -1
  25. package/dist/__tests__/durable/CodexPrintAgentService.test.js +71 -71
  26. package/dist/__tests__/durable/CodexPrintAgentService.test.js.map +1 -1
  27. package/dist/__tests__/durable/CodexPrintRunner.test.js +125 -125
  28. package/dist/__tests__/durable/CodexPrintRunner.test.js.map +1 -1
  29. package/dist/__tests__/print/ClaudeCliProbe.test.js +20 -20
  30. package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -1
  31. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +20 -20
  32. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
  33. package/dist/__tests__/print/ClaudePrintAgentService.test.js +58 -58
  34. package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
  35. package/dist/__tests__/print/ClaudePrintRunner.test.js +55 -55
  36. package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
  37. package/dist/__tests__/print/DurableAgent.test.js +9 -9
  38. package/dist/__tests__/print/DurableAgent.test.js.map +1 -1
  39. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js +40 -40
  40. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js.map +1 -1
  41. package/dist/__tests__/print/DurableAgentRepository.test.js +66 -66
  42. package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -1
  43. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js +34 -34
  44. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js.map +1 -1
  45. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js +132 -132
  46. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js.map +1 -1
  47. package/dist/__tests__/providers/codex/CodexSessionLocator.test.js +194 -0
  48. package/dist/__tests__/providers/codex/CodexSessionLocator.test.js.map +1 -0
  49. package/dist/__tests__/providers/codex/CodexSessionMapping.test.js +86 -0
  50. package/dist/__tests__/providers/codex/CodexSessionMapping.test.js.map +1 -0
  51. package/dist/__tests__/providers/codex/CodexSessionParser.test.js +254 -0
  52. package/dist/__tests__/providers/codex/CodexSessionParser.test.js.map +1 -0
  53. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +641 -0
  54. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -0
  55. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +1398 -0
  56. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -0
  57. package/dist/__tests__/providers/pi/PiAdapter.test.js +337 -0
  58. package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -0
  59. package/dist/__tests__/providers/pi/PiSessionParser.test.js +246 -0
  60. package/dist/__tests__/providers/pi/PiSessionParser.test.js.map +1 -0
  61. package/dist/__tests__/providers/pi/PiSessionTracker.test.js +108 -0
  62. package/dist/__tests__/providers/pi/PiSessionTracker.test.js.map +1 -0
  63. package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js +33 -33
  64. package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js.map +1 -1
  65. package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js +16 -16
  66. package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js.map +1 -1
  67. package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js +52 -52
  68. package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js.map +1 -1
  69. package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js +44 -44
  70. package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js.map +1 -1
  71. package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js +126 -126
  72. package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js.map +1 -1
  73. package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js +14 -14
  74. package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js.map +1 -1
  75. package/dist/__tests__/readiness/AgentReadiness.test.js +134 -134
  76. package/dist/__tests__/readiness/AgentReadiness.test.js.map +1 -1
  77. package/dist/__tests__/runtime/AgentRuntime.test.js +13 -0
  78. package/dist/__tests__/runtime/AgentRuntime.test.js.map +1 -0
  79. package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js +164 -0
  80. package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js.map +1 -0
  81. package/dist/__tests__/runtime/HerdrAgentRuntime.test.js +488 -0
  82. package/dist/__tests__/runtime/HerdrAgentRuntime.test.js.map +1 -0
  83. package/dist/__tests__/runtime/ManagedAgentRuntime.test.js +588 -0
  84. package/dist/__tests__/runtime/ManagedAgentRuntime.test.js.map +1 -0
  85. package/dist/__tests__/terminal/TerminalFocusManager.test.js +100 -100
  86. package/dist/__tests__/terminal/TerminalFocusManager.test.js.map +1 -1
  87. package/dist/__tests__/terminal/TmuxManager.test.js +101 -101
  88. package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -1
  89. package/dist/__tests__/terminal/TtyWriter.test.js +185 -185
  90. package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -1
  91. package/dist/__tests__/utils/AgentRegistry.test.js +343 -218
  92. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
  93. package/dist/__tests__/utils/ClaudeSessionParser.test.js +103 -101
  94. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -1
  95. package/dist/__tests__/utils/agent-requests.test.js +43 -43
  96. package/dist/__tests__/utils/agent-requests.test.js.map +1 -1
  97. package/dist/__tests__/utils/agents.test.js +17 -17
  98. package/dist/__tests__/utils/agents.test.js.map +1 -1
  99. package/dist/__tests__/utils/matching.test.js +92 -92
  100. package/dist/__tests__/utils/matching.test.js.map +1 -1
  101. package/dist/__tests__/utils/process.test.js +108 -108
  102. package/dist/__tests__/utils/process.test.js.map +1 -1
  103. package/dist/__tests__/utils/session.test.js +53 -53
  104. package/dist/__tests__/utils/session.test.js.map +1 -1
  105. package/dist/adapters/AgentAdapter.d.ts +2 -2
  106. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  107. package/dist/adapters/AgentAdapter.js +1 -1
  108. package/dist/adapters/AgentAdapter.js.map +1 -1
  109. package/dist/adapters/GrokCliAdapter.d.ts +1 -1
  110. package/dist/adapters/GrokCliAdapter.d.ts.map +1 -1
  111. package/dist/adapters/GrokCliAdapter.js +80 -80
  112. package/dist/adapters/GrokCliAdapter.js.map +1 -1
  113. package/dist/adapters/OpenCodeAdapter.d.ts +2 -1
  114. package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -1
  115. package/dist/adapters/OpenCodeAdapter.js +41 -37
  116. package/dist/adapters/OpenCodeAdapter.js.map +1 -1
  117. package/dist/adapters/index.d.ts +9 -9
  118. package/dist/adapters/index.d.ts.map +1 -1
  119. package/dist/adapters/index.js +8 -8
  120. package/dist/adapters/index.js.map +1 -1
  121. package/dist/capacity/codex.d.ts +3 -3
  122. package/dist/capacity/codex.d.ts.map +1 -1
  123. package/dist/capacity/codex.js +57 -57
  124. package/dist/capacity/codex.js.map +1 -1
  125. package/dist/capacity/index.d.ts +3 -3
  126. package/dist/capacity/index.d.ts.map +1 -1
  127. package/dist/capacity/index.js +8 -8
  128. package/dist/capacity/index.js.map +1 -1
  129. package/dist/capacity/types.d.ts +1 -1
  130. package/dist/capacity/types.js.map +1 -1
  131. package/dist/database/connection.d.ts +1 -1
  132. package/dist/database/connection.d.ts.map +1 -1
  133. package/dist/database/connection.js +15 -15
  134. package/dist/database/connection.js.map +1 -1
  135. package/dist/database/index.d.ts +3 -3
  136. package/dist/database/index.d.ts.map +1 -1
  137. package/dist/database/index.js +2 -2
  138. package/dist/database/index.js.map +1 -1
  139. package/dist/database/migrations/005_agent_runtime.sql +2 -0
  140. package/dist/database/schema.d.ts +1 -1
  141. package/dist/database/schema.d.ts.map +1 -1
  142. package/dist/database/schema.js +7 -7
  143. package/dist/database/schema.js.map +1 -1
  144. package/dist/durable/DurableAgent.d.ts +9 -9
  145. package/dist/durable/DurableAgent.d.ts.map +1 -1
  146. package/dist/durable/DurableAgent.js +16 -16
  147. package/dist/durable/DurableAgent.js.map +1 -1
  148. package/dist/durable/DurableAgentRepository.d.ts +4 -4
  149. package/dist/durable/DurableAgentRepository.d.ts.map +1 -1
  150. package/dist/durable/DurableAgentRepository.js +49 -48
  151. package/dist/durable/DurableAgentRepository.js.map +1 -1
  152. package/dist/durable/process.d.ts +1 -1
  153. package/dist/durable/process.d.ts.map +1 -1
  154. package/dist/durable/process.js +14 -14
  155. package/dist/durable/process.js.map +1 -1
  156. package/dist/durable/run.d.ts +2 -2
  157. package/dist/durable/run.d.ts.map +1 -1
  158. package/dist/durable/run.js +6 -6
  159. package/dist/durable/run.js.map +1 -1
  160. package/dist/durable/utils.d.ts +1 -1
  161. package/dist/durable/utils.d.ts.map +1 -1
  162. package/dist/durable/utils.js +4 -4
  163. package/dist/durable/utils.js.map +1 -1
  164. package/dist/index.d.ts +48 -43
  165. package/dist/index.d.ts.map +1 -1
  166. package/dist/index.js +28 -26
  167. package/dist/index.js.map +1 -1
  168. package/dist/providers/claude/ClaudeAgentMapper.d.ts +4 -4
  169. package/dist/providers/claude/ClaudeAgentMapper.d.ts.map +1 -1
  170. package/dist/providers/claude/ClaudeAgentMapper.js +10 -10
  171. package/dist/providers/claude/ClaudeAgentMapper.js.map +1 -1
  172. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +1 -1
  173. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  174. package/dist/providers/claude/ClaudeCodeAdapter.js +14 -14
  175. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  176. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -4
  177. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  178. package/dist/providers/claude/ClaudeSessionLocator.js +79 -79
  179. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  180. package/dist/providers/claude/ClaudeSessionParser.d.ts +2 -2
  181. package/dist/providers/claude/ClaudeSessionParser.d.ts.map +1 -1
  182. package/dist/providers/claude/ClaudeSessionParser.js +99 -99
  183. package/dist/providers/claude/ClaudeSessionParser.js.map +1 -1
  184. package/dist/providers/claude/durable/ClaudeCliProbe.d.ts.map +1 -1
  185. package/dist/providers/claude/durable/ClaudeCliProbe.js +16 -16
  186. package/dist/providers/claude/durable/ClaudeCliProbe.js.map +1 -1
  187. package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts +5 -5
  188. package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts.map +1 -1
  189. package/dist/providers/claude/durable/ClaudePrintAgentService.js +10 -10
  190. package/dist/providers/claude/durable/ClaudePrintAgentService.js.map +1 -1
  191. package/dist/providers/claude/durable/ClaudePrintRunner.d.ts +4 -4
  192. package/dist/providers/claude/durable/ClaudePrintRunner.d.ts.map +1 -1
  193. package/dist/providers/claude/durable/ClaudePrintRunner.js +18 -18
  194. package/dist/providers/claude/durable/ClaudePrintRunner.js.map +1 -1
  195. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts +2 -2
  196. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts.map +1 -1
  197. package/dist/providers/claude/durable/ClaudeStreamParser.js +17 -17
  198. package/dist/providers/claude/durable/ClaudeStreamParser.js.map +1 -1
  199. package/dist/providers/codex/CodexAdapter.d.ts +27 -0
  200. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -0
  201. package/dist/providers/codex/CodexAdapter.js +162 -0
  202. package/dist/providers/codex/CodexAdapter.js.map +1 -0
  203. package/dist/providers/codex/CodexAgentMapper.d.ts +9 -0
  204. package/dist/providers/codex/CodexAgentMapper.d.ts.map +1 -0
  205. package/dist/providers/codex/CodexAgentMapper.js +35 -0
  206. package/dist/providers/codex/CodexAgentMapper.js.map +1 -0
  207. package/dist/providers/codex/CodexSessionLocator.d.ts +44 -0
  208. package/dist/providers/codex/CodexSessionLocator.d.ts.map +1 -0
  209. package/dist/providers/codex/CodexSessionLocator.js +207 -0
  210. package/dist/providers/codex/CodexSessionLocator.js.map +1 -0
  211. package/dist/providers/codex/CodexSessionMapping.d.ts +23 -0
  212. package/dist/providers/codex/CodexSessionMapping.d.ts.map +1 -0
  213. package/dist/providers/codex/CodexSessionMapping.js +68 -0
  214. package/dist/providers/codex/CodexSessionMapping.js.map +1 -0
  215. package/dist/providers/codex/CodexSessionParser.d.ts +58 -0
  216. package/dist/providers/codex/CodexSessionParser.d.ts.map +1 -0
  217. package/dist/providers/codex/CodexSessionParser.js +262 -0
  218. package/dist/providers/codex/CodexSessionParser.js.map +1 -0
  219. package/dist/providers/codex/durable/CodexCliProbe.d.ts.map +1 -1
  220. package/dist/providers/codex/durable/CodexCliProbe.js +22 -22
  221. package/dist/providers/codex/durable/CodexCliProbe.js.map +1 -1
  222. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts +6 -6
  223. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts.map +1 -1
  224. package/dist/providers/codex/durable/CodexPrintAgentService.js +10 -10
  225. package/dist/providers/codex/durable/CodexPrintAgentService.js.map +1 -1
  226. package/dist/providers/codex/durable/CodexPrintRunner.d.ts +4 -4
  227. package/dist/providers/codex/durable/CodexPrintRunner.d.ts.map +1 -1
  228. package/dist/providers/codex/durable/CodexPrintRunner.js +20 -20
  229. package/dist/providers/codex/durable/CodexPrintRunner.js.map +1 -1
  230. package/dist/providers/codex/durable/CodexStreamParser.d.ts +2 -2
  231. package/dist/providers/codex/durable/CodexStreamParser.d.ts.map +1 -1
  232. package/dist/providers/codex/durable/CodexStreamParser.js +21 -21
  233. package/dist/providers/codex/durable/CodexStreamParser.js.map +1 -1
  234. package/dist/{adapters → providers/copilot}/CopilotAdapter.d.ts +11 -25
  235. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -0
  236. package/dist/providers/copilot/CopilotAdapter.js +108 -0
  237. package/dist/providers/copilot/CopilotAdapter.js.map +1 -0
  238. package/dist/providers/copilot/CopilotAgentMapper.d.ts +9 -0
  239. package/dist/providers/copilot/CopilotAgentMapper.d.ts.map +1 -0
  240. package/dist/providers/copilot/CopilotAgentMapper.js +37 -0
  241. package/dist/providers/copilot/CopilotAgentMapper.js.map +1 -0
  242. package/dist/providers/copilot/CopilotSessionLocator.d.ts +19 -0
  243. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -0
  244. package/dist/providers/copilot/CopilotSessionLocator.js +41 -0
  245. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -0
  246. package/dist/providers/copilot/CopilotSessionParser.d.ts +30 -0
  247. package/dist/providers/copilot/CopilotSessionParser.d.ts.map +1 -0
  248. package/dist/providers/copilot/CopilotSessionParser.js +181 -0
  249. package/dist/providers/copilot/CopilotSessionParser.js.map +1 -0
  250. package/dist/providers/gemini/GeminiAgentMapper.d.ts +9 -0
  251. package/dist/providers/gemini/GeminiAgentMapper.d.ts.map +1 -0
  252. package/dist/providers/gemini/GeminiAgentMapper.js +37 -0
  253. package/dist/providers/gemini/GeminiAgentMapper.js.map +1 -0
  254. package/dist/providers/gemini/GeminiCliAdapter.d.ts +52 -0
  255. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -0
  256. package/dist/providers/gemini/GeminiCliAdapter.js +188 -0
  257. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -0
  258. package/dist/providers/gemini/GeminiSessionLocator.d.ts +30 -0
  259. package/dist/providers/gemini/GeminiSessionLocator.d.ts.map +1 -0
  260. package/dist/providers/gemini/GeminiSessionLocator.js +142 -0
  261. package/dist/providers/gemini/GeminiSessionLocator.js.map +1 -0
  262. package/dist/providers/gemini/GeminiSessionParser.d.ts +94 -0
  263. package/dist/providers/gemini/GeminiSessionParser.d.ts.map +1 -0
  264. package/dist/providers/gemini/GeminiSessionParser.js +166 -0
  265. package/dist/providers/gemini/GeminiSessionParser.js.map +1 -0
  266. package/dist/providers/pi/PiAdapter.d.ts +28 -0
  267. package/dist/providers/pi/PiAdapter.d.ts.map +1 -0
  268. package/dist/providers/pi/PiAdapter.js +153 -0
  269. package/dist/providers/pi/PiAdapter.js.map +1 -0
  270. package/dist/providers/pi/PiAgentMapper.d.ts +9 -0
  271. package/dist/providers/pi/PiAgentMapper.d.ts.map +1 -0
  272. package/dist/providers/pi/PiAgentMapper.js +37 -0
  273. package/dist/providers/pi/PiAgentMapper.js.map +1 -0
  274. package/dist/providers/pi/PiSessionLocator.d.ts +23 -0
  275. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -0
  276. package/dist/providers/pi/PiSessionLocator.js +79 -0
  277. package/dist/providers/pi/PiSessionLocator.js.map +1 -0
  278. package/dist/providers/pi/PiSessionParser.d.ts +36 -0
  279. package/dist/providers/pi/PiSessionParser.d.ts.map +1 -0
  280. package/dist/providers/pi/PiSessionParser.js +191 -0
  281. package/dist/providers/pi/PiSessionParser.js.map +1 -0
  282. package/dist/providers/pi/PiSessionTracker.d.ts +23 -0
  283. package/dist/providers/pi/PiSessionTracker.d.ts.map +1 -0
  284. package/dist/providers/pi/PiSessionTracker.js +69 -0
  285. package/dist/providers/pi/PiSessionTracker.js.map +1 -0
  286. package/dist/providers/pi/durable/PiCliProbe.d.ts.map +1 -1
  287. package/dist/providers/pi/durable/PiCliProbe.js +15 -15
  288. package/dist/providers/pi/durable/PiCliProbe.js.map +1 -1
  289. package/dist/providers/pi/durable/PiPrintAgentService.d.ts +6 -6
  290. package/dist/providers/pi/durable/PiPrintAgentService.d.ts.map +1 -1
  291. package/dist/providers/pi/durable/PiPrintAgentService.js +10 -10
  292. package/dist/providers/pi/durable/PiPrintAgentService.js.map +1 -1
  293. package/dist/providers/pi/durable/PiPrintProtocol.d.ts.map +1 -1
  294. package/dist/providers/pi/durable/PiPrintProtocol.js +18 -18
  295. package/dist/providers/pi/durable/PiPrintProtocol.js.map +1 -1
  296. package/dist/providers/pi/durable/PiPrintRunner.d.ts +4 -4
  297. package/dist/providers/pi/durable/PiPrintRunner.d.ts.map +1 -1
  298. package/dist/providers/pi/durable/PiPrintRunner.js +15 -15
  299. package/dist/providers/pi/durable/PiPrintRunner.js.map +1 -1
  300. package/dist/providers/pi/durable/PiStreamParser.d.ts +1 -1
  301. package/dist/providers/pi/durable/PiStreamParser.d.ts.map +1 -1
  302. package/dist/providers/pi/durable/PiStreamParser.js +16 -16
  303. package/dist/providers/pi/durable/PiStreamParser.js.map +1 -1
  304. package/dist/readiness/AgentReadiness.d.ts +6 -6
  305. package/dist/readiness/AgentReadiness.d.ts.map +1 -1
  306. package/dist/readiness/AgentReadiness.js +130 -130
  307. package/dist/readiness/AgentReadiness.js.map +1 -1
  308. package/dist/runtime/ManagedAgentRuntime.d.ts +69 -0
  309. package/dist/runtime/ManagedAgentRuntime.d.ts.map +1 -0
  310. package/dist/runtime/ManagedAgentRuntime.js +156 -0
  311. package/dist/runtime/ManagedAgentRuntime.js.map +1 -0
  312. package/dist/runtime/RuntimeFactory.d.ts +4 -0
  313. package/dist/runtime/RuntimeFactory.d.ts.map +1 -0
  314. package/dist/runtime/RuntimeFactory.js +11 -0
  315. package/dist/runtime/RuntimeFactory.js.map +1 -0
  316. package/dist/runtime/errors.d.ts +27 -0
  317. package/dist/runtime/errors.d.ts.map +1 -0
  318. package/dist/runtime/errors.js +42 -0
  319. package/dist/runtime/errors.js.map +1 -0
  320. package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts +18 -0
  321. package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -0
  322. package/dist/runtime/herdr/HerdrAgentDiscovery.js +183 -0
  323. package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -0
  324. package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +36 -0
  325. package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -0
  326. package/dist/runtime/herdr/HerdrAgentRuntime.js +202 -0
  327. package/dist/runtime/herdr/HerdrAgentRuntime.js.map +1 -0
  328. package/dist/runtime/herdr/HerdrCliClient.d.ts +13 -0
  329. package/dist/runtime/herdr/HerdrCliClient.d.ts.map +1 -0
  330. package/dist/runtime/herdr/HerdrCliClient.js +60 -0
  331. package/dist/runtime/herdr/HerdrCliClient.js.map +1 -0
  332. package/dist/runtime/herdr/HerdrErrors.d.ts +7 -0
  333. package/dist/runtime/herdr/HerdrErrors.d.ts.map +1 -0
  334. package/dist/runtime/herdr/HerdrErrors.js +10 -0
  335. package/dist/runtime/herdr/HerdrErrors.js.map +1 -0
  336. package/dist/runtime/herdr/HerdrResponseParsers.d.ts +16 -0
  337. package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -0
  338. package/dist/runtime/herdr/HerdrResponseParsers.js +107 -0
  339. package/dist/runtime/herdr/HerdrResponseParsers.js.map +1 -0
  340. package/dist/runtime/herdr/HerdrRuntimeRef.d.ts +9 -0
  341. package/dist/runtime/herdr/HerdrRuntimeRef.d.ts.map +1 -0
  342. package/dist/runtime/herdr/HerdrRuntimeRef.js +33 -0
  343. package/dist/runtime/herdr/HerdrRuntimeRef.js.map +1 -0
  344. package/dist/runtime/tmux/PidPolling.d.ts +5 -0
  345. package/dist/runtime/tmux/PidPolling.d.ts.map +1 -0
  346. package/dist/runtime/tmux/PidPolling.js +24 -0
  347. package/dist/runtime/tmux/PidPolling.js.map +1 -0
  348. package/dist/runtime/tmux/TmuxAgentRuntime.d.ts +17 -0
  349. package/dist/runtime/tmux/TmuxAgentRuntime.d.ts.map +1 -0
  350. package/dist/runtime/tmux/TmuxAgentRuntime.js +80 -0
  351. package/dist/runtime/tmux/TmuxAgentRuntime.js.map +1 -0
  352. package/dist/runtime/tmux/TmuxRuntimeRef.d.ts +5 -0
  353. package/dist/runtime/tmux/TmuxRuntimeRef.d.ts.map +1 -0
  354. package/dist/runtime/tmux/TmuxRuntimeRef.js +9 -0
  355. package/dist/runtime/tmux/TmuxRuntimeRef.js.map +1 -0
  356. package/dist/runtime/types.d.ts +54 -0
  357. package/dist/runtime/types.d.ts.map +1 -0
  358. package/dist/runtime/types.js +3 -0
  359. package/dist/runtime/types.js.map +1 -0
  360. package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
  361. package/dist/terminal/TerminalFocusManager.js +50 -50
  362. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  363. package/dist/terminal/TmuxManager.d.ts.map +1 -1
  364. package/dist/terminal/TmuxManager.js +47 -47
  365. package/dist/terminal/TmuxManager.js.map +1 -1
  366. package/dist/terminal/TtyWriter.d.ts +1 -1
  367. package/dist/terminal/TtyWriter.d.ts.map +1 -1
  368. package/dist/terminal/TtyWriter.js +89 -89
  369. package/dist/terminal/TtyWriter.js.map +1 -1
  370. package/dist/terminal/index.d.ts +4 -4
  371. package/dist/terminal/index.js +3 -3
  372. package/dist/terminal/index.js.map +1 -1
  373. package/dist/utils/AgentRegistry.d.ts +10 -2
  374. package/dist/utils/AgentRegistry.d.ts.map +1 -1
  375. package/dist/utils/AgentRegistry.js +59 -26
  376. package/dist/utils/AgentRegistry.js.map +1 -1
  377. package/dist/utils/agent-requests.d.ts.map +1 -1
  378. package/dist/utils/agent-requests.js +5 -5
  379. package/dist/utils/agent-requests.js.map +1 -1
  380. package/dist/utils/agents.d.ts +2 -2
  381. package/dist/utils/agents.d.ts.map +1 -1
  382. package/dist/utils/agents.js +15 -15
  383. package/dist/utils/agents.js.map +1 -1
  384. package/dist/utils/applescript.js +1 -1
  385. package/dist/utils/applescript.js.map +1 -1
  386. package/dist/utils/index.d.ts +6 -6
  387. package/dist/utils/index.d.ts.map +1 -1
  388. package/dist/utils/index.js +4 -4
  389. package/dist/utils/index.js.map +1 -1
  390. package/dist/utils/matching.d.ts +2 -2
  391. package/dist/utils/matching.d.ts.map +1 -1
  392. package/dist/utils/matching.js +4 -4
  393. package/dist/utils/matching.js.map +1 -1
  394. package/dist/utils/process.d.ts +1 -1
  395. package/dist/utils/process.d.ts.map +1 -1
  396. package/dist/utils/process.js +74 -74
  397. package/dist/utils/process.js.map +1 -1
  398. package/dist/utils/session.d.ts +1 -1
  399. package/dist/utils/session.d.ts.map +1 -1
  400. package/dist/utils/session.js +7 -7
  401. package/dist/utils/session.js.map +1 -1
  402. package/dist/utils/sortAgents.d.ts +2 -2
  403. package/dist/utils/sortAgents.d.ts.map +1 -1
  404. package/dist/utils/sortAgents.js +5 -5
  405. package/dist/utils/sortAgents.js.map +1 -1
  406. package/package.json +18 -19
  407. package/src/AgentManager.ts +338 -274
  408. package/src/__tests__/AgentManager.test.ts +1143 -907
  409. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1657 -1368
  410. package/src/__tests__/adapters/CodexAdapter.test.ts +365 -1698
  411. package/src/__tests__/adapters/GrokCliAdapter.test.ts +325 -272
  412. package/src/__tests__/adapters/OpenCodeAdapter.test.ts +511 -455
  413. package/src/__tests__/capacity/codex.test.ts +178 -91
  414. package/src/__tests__/capacity/index.test.ts +27 -18
  415. package/src/__tests__/durable/CodexCliProbe.test.ts +75 -53
  416. package/src/__tests__/durable/CodexDurableAgentRepository.test.ts +51 -40
  417. package/src/__tests__/durable/CodexPrintAgent.integration.test.ts +89 -64
  418. package/src/__tests__/durable/CodexPrintAgentService.test.ts +196 -118
  419. package/src/__tests__/durable/CodexPrintRunner.test.ts +282 -162
  420. package/src/__tests__/fixtures/fake-claude.cjs +20 -13
  421. package/src/__tests__/fixtures/fake-codex.cjs +41 -30
  422. package/src/__tests__/print/ClaudeCliProbe.test.ts +27 -21
  423. package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +48 -40
  424. package/src/__tests__/print/ClaudePrintAgentService.test.ts +118 -67
  425. package/src/__tests__/print/ClaudePrintRunner.test.ts +136 -83
  426. package/src/__tests__/print/DurableAgent.test.ts +19 -16
  427. package/src/__tests__/print/DurableAgentRepository.sqlite.test.ts +109 -86
  428. package/src/__tests__/print/DurableAgentRepository.test.ts +170 -137
  429. package/src/__tests__/providers/claude/ClaudeAgentMapper.test.ts +65 -65
  430. package/src/__tests__/providers/claude/ClaudeSessionLocator.test.ts +400 -377
  431. package/src/__tests__/providers/codex/CodexSessionLocator.test.ts +182 -0
  432. package/src/__tests__/providers/codex/CodexSessionMapping.test.ts +63 -0
  433. package/src/__tests__/providers/codex/CodexSessionParser.test.ts +192 -0
  434. package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +602 -0
  435. package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +1299 -0
  436. package/src/__tests__/providers/pi/PiAdapter.test.ts +310 -0
  437. package/src/__tests__/providers/pi/PiSessionParser.test.ts +190 -0
  438. package/src/__tests__/providers/pi/PiSessionTracker.test.ts +79 -0
  439. package/src/__tests__/providers/pi/durable/PiCliProbe.test.ts +48 -32
  440. package/src/__tests__/providers/pi/durable/PiDurableAgentRepository.test.ts +24 -23
  441. package/src/__tests__/providers/pi/durable/PiPrintAgentService.test.ts +131 -59
  442. package/src/__tests__/providers/pi/durable/PiPrintProtocol.test.ts +66 -28
  443. package/src/__tests__/providers/pi/durable/PiPrintRunner.test.ts +236 -90
  444. package/src/__tests__/providers/pi/durable/PiStreamParser.test.ts +20 -18
  445. package/src/__tests__/readiness/AgentReadiness.test.ts +217 -169
  446. package/src/__tests__/runtime/AgentRuntime.test.ts +8 -0
  447. package/src/__tests__/runtime/HerdrAgentDiscovery.test.ts +131 -0
  448. package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +377 -0
  449. package/src/__tests__/runtime/ManagedAgentRuntime.test.ts +555 -0
  450. package/src/__tests__/terminal/TerminalFocusManager.test.ts +254 -253
  451. package/src/__tests__/terminal/TmuxManager.test.ts +157 -157
  452. package/src/__tests__/terminal/TtyWriter.test.ts +423 -419
  453. package/src/__tests__/utils/AgentRegistry.test.ts +516 -402
  454. package/src/__tests__/utils/ClaudeSessionParser.test.ts +276 -242
  455. package/src/__tests__/utils/agent-requests.test.ts +76 -56
  456. package/src/__tests__/utils/agents.test.ts +20 -20
  457. package/src/__tests__/utils/matching.test.ts +206 -171
  458. package/src/__tests__/utils/process.test.ts +276 -262
  459. package/src/__tests__/utils/session.test.ts +133 -139
  460. package/src/adapters/AgentAdapter.ts +134 -126
  461. package/src/adapters/GrokCliAdapter.ts +353 -342
  462. package/src/adapters/OpenCodeAdapter.ts +289 -259
  463. package/src/adapters/index.ts +15 -9
  464. package/src/capacity/codex.ts +137 -84
  465. package/src/capacity/index.ts +18 -13
  466. package/src/capacity/types.ts +1 -1
  467. package/src/database/connection.ts +73 -68
  468. package/src/database/index.ts +6 -6
  469. package/src/database/migrations/005_agent_runtime.sql +2 -0
  470. package/src/database/schema.ts +40 -38
  471. package/src/durable/DurableAgent.ts +85 -85
  472. package/src/durable/DurableAgentRepository.ts +409 -260
  473. package/src/durable/process.ts +22 -21
  474. package/src/durable/run.ts +61 -51
  475. package/src/durable/utils.ts +46 -41
  476. package/src/index.ts +151 -103
  477. package/src/providers/claude/ClaudeAgentMapper.ts +51 -50
  478. package/src/providers/claude/ClaudeCodeAdapter.ts +144 -136
  479. package/src/providers/claude/ClaudeSessionLocator.ts +291 -293
  480. package/src/providers/claude/ClaudeSessionParser.ts +322 -338
  481. package/src/providers/claude/durable/ClaudeCliProbe.ts +42 -37
  482. package/src/providers/claude/durable/ClaudePrintAgentService.ts +63 -50
  483. package/src/providers/claude/durable/ClaudePrintRunner.ts +79 -69
  484. package/src/providers/claude/durable/ClaudeStreamParser.ts +91 -62
  485. package/src/providers/codex/CodexAdapter.ts +205 -0
  486. package/src/providers/codex/CodexAgentMapper.ts +35 -0
  487. package/src/providers/codex/CodexSessionLocator.ts +279 -0
  488. package/src/providers/codex/CodexSessionMapping.ts +89 -0
  489. package/src/providers/codex/CodexSessionParser.ts +385 -0
  490. package/src/providers/codex/durable/CodexCliProbe.ts +50 -42
  491. package/src/providers/codex/durable/CodexPrintAgentService.ts +63 -49
  492. package/src/providers/codex/durable/CodexPrintRunner.ts +79 -69
  493. package/src/providers/codex/durable/CodexStreamParser.ts +97 -84
  494. package/src/providers/copilot/CopilotAdapter.ts +145 -0
  495. package/src/providers/copilot/CopilotAgentMapper.ts +36 -0
  496. package/src/providers/copilot/CopilotSessionLocator.ts +57 -0
  497. package/src/providers/copilot/CopilotSessionParser.ts +289 -0
  498. package/src/providers/gemini/GeminiAgentMapper.ts +36 -0
  499. package/src/providers/gemini/GeminiCliAdapter.ts +266 -0
  500. package/src/providers/gemini/GeminiSessionLocator.ts +183 -0
  501. package/src/providers/gemini/GeminiSessionParser.ts +271 -0
  502. package/src/providers/pi/PiAdapter.ts +183 -0
  503. package/src/providers/pi/PiAgentMapper.ts +36 -0
  504. package/src/providers/pi/PiSessionLocator.ts +95 -0
  505. package/src/providers/pi/PiSessionParser.ts +284 -0
  506. package/src/providers/pi/PiSessionTracker.ts +87 -0
  507. package/src/providers/pi/durable/PiCliProbe.ts +42 -34
  508. package/src/providers/pi/durable/PiPrintAgentService.ts +61 -53
  509. package/src/providers/pi/durable/PiPrintProtocol.ts +31 -29
  510. package/src/providers/pi/durable/PiPrintRunner.ts +75 -68
  511. package/src/providers/pi/durable/PiStreamParser.ts +82 -72
  512. package/src/readiness/AgentReadiness.ts +585 -513
  513. package/src/runtime/ManagedAgentRuntime.ts +266 -0
  514. package/src/runtime/RuntimeFactory.ts +13 -0
  515. package/src/runtime/errors.ts +50 -0
  516. package/src/runtime/herdr/HerdrAgentDiscovery.ts +178 -0
  517. package/src/runtime/herdr/HerdrAgentRuntime.ts +216 -0
  518. package/src/runtime/herdr/HerdrCliClient.ts +78 -0
  519. package/src/runtime/herdr/HerdrErrors.ts +9 -0
  520. package/src/runtime/herdr/HerdrResponseParsers.ts +86 -0
  521. package/src/runtime/herdr/HerdrRuntimeRef.ts +41 -0
  522. package/src/runtime/tmux/PidPolling.ts +34 -0
  523. package/src/runtime/tmux/TmuxAgentRuntime.ts +113 -0
  524. package/src/runtime/tmux/TmuxRuntimeRef.ts +9 -0
  525. package/src/runtime/types.ts +56 -0
  526. package/src/terminal/TerminalFocusManager.ts +215 -212
  527. package/src/terminal/TmuxManager.ts +99 -92
  528. package/src/terminal/TtyWriter.ts +203 -194
  529. package/src/terminal/index.ts +4 -4
  530. package/src/utils/AgentRegistry.ts +301 -238
  531. package/src/utils/agent-requests.ts +16 -16
  532. package/src/utils/agents.ts +41 -38
  533. package/src/utils/applescript.ts +4 -4
  534. package/src/utils/index.ts +12 -6
  535. package/src/utils/matching.ts +43 -43
  536. package/src/utils/process.ts +261 -258
  537. package/src/utils/session.ts +66 -66
  538. package/src/utils/sortAgents.ts +35 -33
  539. package/tsconfig.json +2 -8
  540. package/vitest.config.ts +16 -16
  541. package/.eslintrc.json +0 -31
  542. package/dist/__tests__/adapters/CopilotAdapter.test.js +0 -630
  543. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +0 -1
  544. package/dist/__tests__/adapters/GeminiCliAdapter.test.js +0 -1384
  545. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +0 -1
  546. package/dist/__tests__/adapters/PiAdapter.test.js +0 -618
  547. package/dist/__tests__/adapters/PiAdapter.test.js.map +0 -1
  548. package/dist/__tests__/database/DurableAgentsDatabase.test.js +0 -128
  549. package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +0 -1
  550. package/dist/adapters/CodexAdapter.d.ts +0 -109
  551. package/dist/adapters/CodexAdapter.d.ts.map +0 -1
  552. package/dist/adapters/CodexAdapter.js +0 -710
  553. package/dist/adapters/CodexAdapter.js.map +0 -1
  554. package/dist/adapters/CopilotAdapter.d.ts.map +0 -1
  555. package/dist/adapters/CopilotAdapter.js +0 -329
  556. package/dist/adapters/CopilotAdapter.js.map +0 -1
  557. package/dist/adapters/GeminiCliAdapter.d.ts +0 -98
  558. package/dist/adapters/GeminiCliAdapter.d.ts.map +0 -1
  559. package/dist/adapters/GeminiCliAdapter.js +0 -513
  560. package/dist/adapters/GeminiCliAdapter.js.map +0 -1
  561. package/dist/adapters/PiAdapter.d.ts +0 -63
  562. package/dist/adapters/PiAdapter.d.ts.map +0 -1
  563. package/dist/adapters/PiAdapter.js +0 -453
  564. package/dist/adapters/PiAdapter.js.map +0 -1
  565. package/src/__tests__/adapters/CopilotAdapter.test.ts +0 -510
  566. package/src/__tests__/adapters/GeminiCliAdapter.test.ts +0 -1259
  567. package/src/__tests__/adapters/PiAdapter.test.ts +0 -458
  568. package/src/__tests__/database/DurableAgentsDatabase.test.ts +0 -102
  569. package/src/adapters/CodexAdapter.ts +0 -923
  570. package/src/adapters/CopilotAdapter.ts +0 -473
  571. package/src/adapters/GeminiCliAdapter.ts +0 -701
  572. package/src/adapters/PiAdapter.ts +0 -598
@@ -1,418 +1,532 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import Database from 'better-sqlite3';
5
- import { AgentRegistry, RenameNotFoundError, RenameConflictError, type RegistryEntry } from '../../utils/AgentRegistry.js';
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import Database from "better-sqlite3";
5
+ import {
6
+ AgentRegistry,
7
+ RenameNotFoundError,
8
+ RenameConflictError,
9
+ type RegistryEntry,
10
+ } from "../../utils/AgentRegistry.js";
6
11
 
7
12
  function makeEntry(over: Partial<RegistryEntry> = {}): RegistryEntry {
8
- return {
9
- name: 'agent1',
10
- type: 'claude',
11
- pid: process.pid,
12
- tmuxSession: 'agent1',
13
- cwd: '/tmp',
14
- startedAt: '2026-05-30T00:00:00.000Z',
15
- sessionId: 'sid-1',
16
- sessionFilePath: '/tmp/session.jsonl',
17
- pinned: false,
18
- ...over,
19
- };
13
+ return {
14
+ name: "agent1",
15
+ type: "claude",
16
+ pid: process.pid,
17
+ cwd: "/tmp",
18
+ startedAt: "2026-05-30T00:00:00.000Z",
19
+ sessionId: "sid-1",
20
+ sessionFilePath: "/tmp/session.jsonl",
21
+ pinned: false,
22
+ runtime: "tmux",
23
+ runtimeRef: { session: "agent1" },
24
+ ...over,
25
+ };
20
26
  }
21
27
 
22
- describe('AgentRegistry', () => {
23
- let tmpDir: string;
24
- let regPath: string;
25
- let registry: AgentRegistry;
26
-
27
- beforeEach(() => {
28
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-registry-'));
29
- regPath = path.join(tmpDir, 'nested', 'agents.json');
30
- registry = new AgentRegistry(regPath);
31
- });
32
-
33
- afterEach(() => {
34
- vi.restoreAllMocks();
35
- fs.rmSync(tmpDir, { recursive: true, force: true });
36
- });
37
-
38
- describe('register', () => {
39
- it('creates the SQLite database and parent directory if missing', () => {
40
- registry.register(makeEntry());
41
- expect(fs.existsSync(regPath.replace(/\.json$/, '.db'))).toBe(true);
42
- expect(registry.list()[0].name).toBe('agent1');
43
- });
44
-
45
- it('appends a new entry when name is unique', () => {
46
- registry.register(makeEntry({ name: 'a' }));
47
- registry.register(makeEntry({ name: 'b', pid: process.ppid }));
48
- expect(registry.list()).toHaveLength(2);
49
- });
50
-
51
- it('upserts in place when type and pid already exist', () => {
52
- registry.register(makeEntry({ name: 'a', pid: process.pid }));
53
- registry.register(makeEntry({ name: 'fallback', pid: process.pid, tmuxSession: '' }));
54
- const all = registry.list();
55
- expect(all).toHaveLength(1);
56
- expect(all[0].pid).toBe(process.pid);
57
- expect(all[0].name).toBe('a');
58
- });
59
-
60
- it('does not write through the legacy fixed .tmp path', () => {
61
- registry.register(makeEntry());
62
- expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);
63
- });
64
-
65
- it('persists session fields', () => {
66
- registry.register(makeEntry({ sessionId: 'sid-xyz', sessionFilePath: '/foo/bar.jsonl' }));
67
- const saved = registry.list()[0];
68
- expect(saved.sessionId).toBe('sid-xyz');
69
- expect(saved.sessionFilePath).toBe('/foo/bar.jsonl');
70
- });
71
-
72
- it('preserves existing tmuxSession when incoming is empty string', () => {
73
- registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));
74
- registry.register(makeEntry({ name: 'fallback', tmuxSession: '', pid: process.pid }));
75
- const saved = registry.lookup('a');
76
- expect(saved?.tmuxSession).toBe('pinned');
77
- expect(saved?.pid).toBe(process.pid);
78
- });
79
-
80
- it('lets a managed start entry replace a generated fallback for the same pid', () => {
81
- registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));
82
- registry.register(makeEntry({ name: 'custom-name', tmuxSession: 'custom-name' }));
83
- expect(registry.lookup('custom-name')?.tmuxSession).toBe('custom-name');
84
- expect(registry.lookup(`ai-devkit-${process.pid}`)).toBeNull();
85
- expect(registry.list()).toHaveLength(1);
86
- });
87
-
88
- it('preserves an existing name conflict when its probe fails with EPERM', () => {
89
- registry.register(makeEntry({ name: 'claimed-name', pid: process.pid }));
90
- vi.spyOn(process, 'kill').mockImplementation(() => {
91
- throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });
92
- });
93
-
94
- expect(() => registry.register(makeEntry({
95
- name: 'claimed-name',
96
- pid: process.pid + 1,
97
- }))).toThrow();
98
- expect(registry.lookup('claimed-name')?.pid).toBe(process.pid);
99
- });
100
- });
101
-
102
- describe('registerBatch', () => {
103
- it('is a no-op on empty array', () => {
104
- registry.registerBatch([]);
105
- expect(fs.existsSync(regPath)).toBe(false);
106
- });
107
-
108
- it('upserts multiple entries in a single batch', () => {
109
- registry.registerBatch([
110
- makeEntry({ name: 'a' }),
111
- makeEntry({ name: 'b', pid: process.pid + 1 }),
112
- makeEntry({ name: 'c', pid: process.pid + 2 }),
113
- ]);
114
- expect(registry.list()).toHaveLength(3);
115
- });
116
-
117
- it('applies the tmuxSession merge per entry', () => {
118
- registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));
119
- registry.registerBatch([
120
- makeEntry({ name: 'fallback', tmuxSession: '', pid: process.pid }),
121
- makeEntry({ name: 'b', tmuxSession: '', pid: process.pid + 1 }),
122
- ]);
123
- expect(registry.lookup('a')?.tmuxSession).toBe('pinned');
124
- expect(registry.lookup('a')?.pid).toBe(process.pid);
125
- expect(registry.lookup('b')?.tmuxSession).toBe('');
126
- });
127
-
128
- it('handles concurrent registry instances without duplicate pid rows', () => {
129
- const other = new AgentRegistry(regPath);
130
- registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));
131
- other.register(makeEntry({ name: 'custom-name', tmuxSession: 'custom-name' }));
132
- registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));
133
-
134
- expect(registry.list()).toHaveLength(1);
135
- expect(registry.lookup('custom-name')?.pid).toBe(process.pid);
136
- });
137
-
138
- it('cleans up a cross-type row when its pid has been reused', () => {
139
- registry.register(makeEntry({ name: 'old-claude', type: 'claude', pid: process.pid }));
140
-
141
- registry.register(makeEntry({
142
- name: 'new-codex',
143
- type: 'codex',
144
- pid: process.pid,
145
- tmuxSession: '',
146
- }));
147
-
148
- expect(registry.lookup('old-claude')).toBeNull();
149
- expect(registry.lookup('new-codex')).toMatchObject({ type: 'codex', pid: process.pid });
150
- expect(registry.list()).toHaveLength(1);
151
- });
152
-
153
- it('rolls back the whole batch when a live name conflict rejects one entry', () => {
154
- registry.register(makeEntry({ name: 'taken', pid: process.pid }));
155
-
156
- expect(() => registry.registerBatch([
157
- makeEntry({ name: 'fresh', pid: 999998 }),
158
- makeEntry({ name: 'taken', type: 'codex', pid: 999997 }),
159
- ])).toThrow(/UNIQUE constraint failed/);
160
-
161
- expect(registry.lookup('fresh')).toBeNull();
162
- expect(registry.lookup('taken')?.pid).toBe(process.pid);
163
- });
164
- });
165
-
166
- describe('lookup', () => {
167
- it('returns null when name not found', () => {
168
- expect(registry.lookup('missing')).toBeNull();
169
- });
170
-
171
- it('returns the entry when name matches', () => {
172
- registry.register(makeEntry({ name: 'a' }));
173
- expect(registry.lookup('a')?.name).toBe('a');
174
- });
175
- });
176
-
177
- describe('pinning', () => {
178
- it('defaults new rows to unpinned and toggles the persisted state', () => {
179
- registry.register(makeEntry());
180
-
181
- expect(registry.lookup('agent1')?.pinned).toBe(false);
182
- expect(registry.togglePin('claude', process.pid)).toBe(true);
183
- expect(registry.lookup('agent1')?.pinned).toBe(true);
184
- expect(registry.togglePin('claude', process.pid)).toBe(false);
185
- expect(registry.lookup('agent1')?.pinned).toBe(false);
186
- });
187
-
188
- it('updates existing recency when toggled', () => {
189
- let now = new Date('2026-08-16T10:00:00.000Z');
190
- const clocked = new AgentRegistry(regPath, { now: () => now });
191
- clocked.register(makeEntry());
192
- now = new Date('2026-08-16T10:01:00.000Z');
193
-
194
- clocked.togglePin('claude', process.pid);
195
-
196
- expect(clocked.lookup('agent1')?.updatedAt).toBe(now.toISOString());
197
- const db = new Database(regPath.replace(/\.json$/, '.db'), { readonly: true });
198
- const row = db.prepare('SELECT updated_at FROM agents WHERE type = ? AND pid = ?')
199
- .get('claude', process.pid) as { updated_at: string };
200
- db.close();
201
- expect(row.updated_at).toBe(now.toISOString());
202
- });
203
-
204
- it('returns null when the process row has disappeared', () => {
205
- expect(registry.togglePin('claude', 999999)).toBeNull();
206
- });
207
-
208
- it('preserves a pin when poll registration updates the row', () => {
209
- registry.register(makeEntry({ sessionId: 'before' }));
210
- registry.togglePin('claude', process.pid);
211
-
212
- registry.register(makeEntry({ sessionId: 'after' }));
213
-
214
- expect(registry.lookup('agent1')).toMatchObject({ sessionId: 'after', pinned: true });
215
- });
216
-
217
- it('preserves a pin through rename', () => {
218
- registry.register(makeEntry({ name: 'before' }));
219
- registry.togglePin('claude', process.pid);
220
-
221
- registry.rename('before', 'after');
222
-
223
- expect(registry.lookup('after')?.pinned).toBe(true);
224
- });
225
-
226
- it('removes the pin with a pruned process row', () => {
227
- registry.register(makeEntry({ pid: 999999 }));
228
- registry.togglePin('claude', 999999);
229
-
230
- registry.prune();
231
-
232
- expect(registry.lookup('agent1')).toBeNull();
233
- });
28
+ describe("AgentRegistry", () => {
29
+ let tmpDir: string;
30
+ let regPath: string;
31
+ let registry: AgentRegistry;
32
+
33
+ beforeEach(() => {
34
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-registry-"));
35
+ regPath = path.join(tmpDir, "nested", "agents.json");
36
+ registry = new AgentRegistry(regPath);
37
+ });
38
+
39
+ afterEach(() => {
40
+ vi.restoreAllMocks();
41
+ fs.rmSync(tmpDir, { recursive: true, force: true });
42
+ });
43
+
44
+ describe("register", () => {
45
+ it("creates the SQLite database and parent directory if missing", () => {
46
+ registry.register(makeEntry());
47
+ expect(fs.existsSync(regPath.replace(/\.json$/, ".db"))).toBe(true);
48
+ expect(registry.list()[0].name).toBe("agent1");
49
+ });
50
+
51
+ it("appends a new entry when name is unique", () => {
52
+ registry.register(makeEntry({ name: "a" }));
53
+ registry.register(makeEntry({ name: "b", pid: process.ppid }));
54
+ expect(registry.list()).toHaveLength(2);
55
+ });
56
+
57
+ it("upserts in place when type and pid already exist", () => {
58
+ registry.register(makeEntry({ name: "a", pid: process.pid }));
59
+ registry.register(makeEntry({ name: "fallback", pid: process.pid, runtimeRef: null }));
60
+ const all = registry.list();
61
+ expect(all).toHaveLength(1);
62
+ expect(all[0].pid).toBe(process.pid);
63
+ expect(all[0].name).toBe("a");
64
+ });
65
+
66
+ it("does not write through the legacy fixed .tmp path", () => {
67
+ registry.register(makeEntry());
68
+ expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);
69
+ });
70
+
71
+ it("persists session fields", () => {
72
+ registry.register(makeEntry({ sessionId: "sid-xyz", sessionFilePath: "/foo/bar.jsonl" }));
73
+ const saved = registry.list()[0];
74
+ expect(saved.sessionId).toBe("sid-xyz");
75
+ expect(saved.sessionFilePath).toBe("/foo/bar.jsonl");
76
+ });
77
+
78
+ it("persists runtime metadata for Herdr-backed entries", () => {
79
+ const runtimeRef = { session: "default", paneId: "w1:p2", agentName: "reviewer" };
80
+
81
+ registry.register(
82
+ makeEntry({
83
+ name: "reviewer",
84
+ runtime: "herdr",
85
+ runtimeRef,
86
+ }),
87
+ );
88
+
89
+ expect(registry.lookup("reviewer")).toMatchObject({
90
+ runtime: "herdr",
91
+ runtimeRef,
92
+ });
93
+ });
94
+
95
+ it("preserves existing identity and routing fields for same-pid refreshes", () => {
96
+ const runtimeRef = { session: "default", paneId: "w1:p2", agentName: "requested-name" };
97
+
98
+ registry.register(
99
+ makeEntry({
100
+ name: "old-name",
101
+ pid: process.pid,
102
+ runtime: "tmux",
103
+ runtimeRef: { session: "old-name" },
104
+ cwd: "/original",
105
+ }),
106
+ );
107
+ registry.register(
108
+ makeEntry({
109
+ name: "requested-name",
110
+ pid: process.pid,
111
+ runtime: "herdr",
112
+ runtimeRef,
113
+ cwd: "/incoming",
114
+ }),
115
+ );
116
+
117
+ expect(registry.lookup("requested-name")).toBeNull();
118
+ expect(registry.lookup("old-name")).toMatchObject({
119
+ pid: process.pid,
120
+ runtime: "tmux",
121
+ runtimeRef: { session: "old-name" },
122
+ cwd: "/original",
123
+ });
124
+ });
125
+
126
+ it("preserves existing runtime ref when incoming runtime ref is empty", () => {
127
+ registry.register(makeEntry({ name: "a", runtimeRef: { session: "pinned" } }));
128
+ registry.register(makeEntry({ name: "fallback", runtimeRef: null, pid: process.pid }));
129
+ const saved = registry.lookup("a");
130
+ expect(saved?.runtimeRef).toEqual({ session: "pinned" });
131
+ expect(saved?.pid).toBe(process.pid);
132
+ });
133
+
134
+ it("defaults an empty tmux runtime ref from the tmux session", () => {
135
+ registry.register(makeEntry({ name: "a", runtimeRef: null }));
136
+
137
+ expect(registry.lookup("a")?.runtimeRef).toBeNull();
138
+ });
139
+
140
+ it("preserves an existing generated fallback for the same pid", () => {
141
+ registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
142
+ registry.register(makeEntry({ name: "custom-name", runtimeRef: { session: "custom-name" } }));
143
+ expect(registry.lookup("custom-name")).toBeNull();
144
+ expect(registry.lookup(`ai-devkit-${process.pid}`)?.runtimeRef).toBeNull();
145
+ expect(registry.list()).toHaveLength(1);
146
+ });
147
+
148
+ it("preserves an existing name conflict when its probe fails with EPERM", () => {
149
+ registry.register(makeEntry({ name: "claimed-name", pid: process.pid }));
150
+ vi.spyOn(process, "kill").mockImplementation(() => {
151
+ throw Object.assign(new Error("operation not permitted"), { code: "EPERM" });
152
+ });
153
+
154
+ expect(() =>
155
+ registry.register(
156
+ makeEntry({
157
+ name: "claimed-name",
158
+ pid: process.pid + 1,
159
+ }),
160
+ ),
161
+ ).toThrow();
162
+ expect(registry.lookup("claimed-name")?.pid).toBe(process.pid);
163
+ });
164
+ });
165
+
166
+ describe("registerBatch", () => {
167
+ it("is a no-op on empty array", () => {
168
+ registry.registerBatch([]);
169
+ expect(fs.existsSync(regPath)).toBe(false);
170
+ });
171
+
172
+ it("upserts multiple entries in a single batch", () => {
173
+ registry.registerBatch([
174
+ makeEntry({ name: "a" }),
175
+ makeEntry({ name: "b", pid: process.pid + 1 }),
176
+ makeEntry({ name: "c", pid: process.pid + 2 }),
177
+ ]);
178
+ expect(registry.list()).toHaveLength(3);
179
+ });
180
+
181
+ it("applies the tmux runtime ref merge per entry", () => {
182
+ registry.register(makeEntry({ name: "a", runtimeRef: { session: "pinned" } }));
183
+ registry.registerBatch([
184
+ makeEntry({ name: "fallback", runtimeRef: null, pid: process.pid }),
185
+ makeEntry({ name: "b", runtimeRef: null, pid: process.pid + 1 }),
186
+ ]);
187
+ expect(registry.lookup("a")?.runtimeRef).toEqual({ session: "pinned" });
188
+ expect(registry.lookup("a")?.pid).toBe(process.pid);
189
+ expect(registry.lookup("b")?.runtimeRef).toBeNull();
190
+ });
191
+
192
+ it("handles concurrent registry instances without duplicate pid rows", () => {
193
+ const other = new AgentRegistry(regPath);
194
+ registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
195
+ other.register(makeEntry({ name: "custom-name", runtimeRef: { session: "custom-name" } }));
196
+ registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
197
+
198
+ expect(registry.list()).toHaveLength(1);
199
+ expect(registry.lookup(`ai-devkit-${process.pid}`)?.pid).toBe(process.pid);
200
+ });
201
+
202
+ it("cleans up a cross-type row when its pid has been reused", () => {
203
+ registry.register(makeEntry({ name: "old-claude", type: "claude", pid: process.pid }));
204
+
205
+ registry.register(
206
+ makeEntry({
207
+ name: "new-codex",
208
+ type: "codex",
209
+ pid: process.pid,
210
+ runtimeRef: null,
211
+ }),
212
+ );
213
+
214
+ expect(registry.lookup("old-claude")).toBeNull();
215
+ expect(registry.lookup("new-codex")).toMatchObject({ type: "codex", pid: process.pid });
216
+ expect(registry.list()).toHaveLength(1);
217
+ });
218
+
219
+ it("rolls back the whole batch when a live name conflict rejects one entry", () => {
220
+ registry.register(makeEntry({ name: "taken", pid: process.pid }));
221
+
222
+ expect(() =>
223
+ registry.registerBatch([
224
+ makeEntry({ name: "fresh", pid: 999998 }),
225
+ makeEntry({ name: "taken", type: "codex", pid: 999997 }),
226
+ ]),
227
+ ).toThrow(/UNIQUE constraint failed/);
228
+
229
+ expect(registry.lookup("fresh")).toBeNull();
230
+ expect(registry.lookup("taken")?.pid).toBe(process.pid);
231
+ });
232
+ });
233
+
234
+ describe("lookup", () => {
235
+ it("returns null when name not found", () => {
236
+ expect(registry.lookup("missing")).toBeNull();
237
+ });
238
+
239
+ it("returns the entry when name matches", () => {
240
+ registry.register(makeEntry({ name: "a" }));
241
+ expect(registry.lookup("a")?.name).toBe("a");
242
+ });
243
+ });
244
+
245
+ describe("pinning", () => {
246
+ it("defaults new rows to unpinned and toggles the persisted state", () => {
247
+ registry.register(makeEntry());
248
+
249
+ expect(registry.lookup("agent1")?.pinned).toBe(false);
250
+ expect(registry.togglePin("claude", process.pid)).toBe(true);
251
+ expect(registry.lookup("agent1")?.pinned).toBe(true);
252
+ expect(registry.togglePin("claude", process.pid)).toBe(false);
253
+ expect(registry.lookup("agent1")?.pinned).toBe(false);
254
+ });
255
+
256
+ it("updates existing recency when toggled", () => {
257
+ let now = new Date("2026-08-16T10:00:00.000Z");
258
+ const clocked = new AgentRegistry(regPath, { now: () => now });
259
+ clocked.register(makeEntry());
260
+ now = new Date("2026-08-16T10:01:00.000Z");
261
+
262
+ clocked.togglePin("claude", process.pid);
263
+
264
+ expect(clocked.lookup("agent1")?.updatedAt).toBe(now.toISOString());
265
+ const db = new Database(regPath.replace(/\.json$/, ".db"), { readonly: true });
266
+ const row = db
267
+ .prepare("SELECT updated_at FROM agents WHERE type = ? AND pid = ?")
268
+ .get("claude", process.pid) as { updated_at: string };
269
+ db.close();
270
+ expect(row.updated_at).toBe(now.toISOString());
271
+ });
272
+
273
+ it("returns null when the process row has disappeared", () => {
274
+ expect(registry.togglePin("claude", 999999)).toBeNull();
275
+ });
276
+
277
+ it("preserves a pin when poll registration updates the row", () => {
278
+ registry.register(makeEntry({ sessionId: "before" }));
279
+ registry.togglePin("claude", process.pid);
280
+
281
+ registry.register(makeEntry({ sessionId: "after" }));
282
+
283
+ expect(registry.lookup("agent1")).toMatchObject({ sessionId: "after", pinned: true });
284
+ });
285
+
286
+ it("preserves a pin through rename", () => {
287
+ registry.register(makeEntry({ name: "before" }));
288
+ registry.togglePin("claude", process.pid);
289
+
290
+ registry.rename("before", "after");
291
+
292
+ expect(registry.lookup("after")?.pinned).toBe(true);
293
+ });
294
+
295
+ it("removes the pin with a pruned process row", () => {
296
+ registry.register(makeEntry({ pid: 999999 }));
297
+ registry.togglePin("claude", 999999);
298
+
299
+ registry.prune();
234
300
 
235
- it('reports a clear error when a readonly registry toggles a pin', () => {
236
- registry.register(makeEntry());
237
- const readonlyRegistry = new AgentRegistry(regPath, { readonly: true });
301
+ expect(registry.lookup("agent1")).toBeNull();
302
+ });
303
+
304
+ it("reports a clear error when a readonly registry toggles a pin", () => {
305
+ registry.register(makeEntry());
306
+ const readonlyRegistry = new AgentRegistry(regPath, { readonly: true });
307
+
308
+ expect(() => readonlyRegistry.togglePin("claude", process.pid)).toThrow(/readonly/i);
309
+ });
310
+ });
238
311
 
239
- expect(() => readonlyRegistry.togglePin('claude', process.pid)).toThrow(/readonly/i);
240
- });
312
+ describe("list", () => {
313
+ it("returns empty array when database does not contain entries", () => {
314
+ expect(registry.list()).toEqual([]);
241
315
  });
242
316
 
243
- describe('list', () => {
244
- it('returns empty array when database does not contain entries', () => {
245
- expect(registry.list()).toEqual([]);
246
- });
317
+ it("ignores existing legacy agents.json entries", () => {
318
+ const legacyEntry = makeEntry({ name: "legacy", runtimeRef: { session: "legacy" } });
319
+ fs.mkdirSync(path.dirname(regPath), { recursive: true });
320
+ fs.writeFileSync(regPath, JSON.stringify({ entries: [legacyEntry] }), "utf8");
247
321
 
248
- it('ignores existing legacy agents.json entries', () => {
249
- const legacyEntry = makeEntry({ name: 'legacy', tmuxSession: 'legacy' });
250
- fs.mkdirSync(path.dirname(regPath), { recursive: true });
251
- fs.writeFileSync(regPath, JSON.stringify({ entries: [legacyEntry] }), 'utf8');
252
-
253
- const legacyRegistry = new AgentRegistry(regPath);
254
-
255
- expect(legacyRegistry.lookup('legacy')).toBeNull();
256
- expect(legacyRegistry.list()).toEqual([]);
257
- expect(fs.existsSync(regPath.replace(/\.json$/, '.db'))).toBe(true);
258
- });
322
+ const legacyRegistry = new AgentRegistry(regPath);
323
+
324
+ expect(legacyRegistry.lookup("legacy")).toBeNull();
325
+ expect(legacyRegistry.list()).toEqual([]);
326
+ expect(fs.existsSync(regPath.replace(/\.json$/, ".db"))).toBe(true);
259
327
  });
260
328
 
261
- describe('isAlive', () => {
262
- it('returns true for the current process', () => {
263
- expect(registry.isAlive(makeEntry({ pid: process.pid }))).toBe(true);
264
- });
329
+ it("reads pre-runtime SQLite rows as tmux-backed records", () => {
330
+ const dbPath = regPath.replace(/\.json$/, ".db");
331
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
332
+ fs.rmSync(dbPath, { force: true });
333
+ fs.rmSync(`${dbPath}-wal`, { force: true });
334
+ fs.rmSync(`${dbPath}-shm`, { force: true });
335
+ const db = new Database(dbPath);
336
+ db.exec(`
337
+ CREATE TABLE agents (
338
+ type TEXT NOT NULL,
339
+ pid INTEGER NOT NULL,
340
+ name TEXT NOT NULL UNIQUE,
341
+ tmux_session TEXT NOT NULL DEFAULT '',
342
+ cwd TEXT NOT NULL DEFAULT '',
343
+ started_at TEXT NOT NULL,
344
+ session_id TEXT NOT NULL DEFAULT '',
345
+ session_file_path TEXT NOT NULL DEFAULT '',
346
+ updated_at TEXT NOT NULL,
347
+ PRIMARY KEY (type, pid)
348
+ );
349
+ INSERT INTO agents (
350
+ type, pid, name, tmux_session, cwd, started_at, session_id, session_file_path, updated_at
351
+ ) VALUES (
352
+ 'claude', ${process.pid}, 'legacy-sqlite', 'legacy-sqlite', '/tmp',
353
+ '2026-05-30T00:00:00.000Z', 'sid-1', '/tmp/session.jsonl', '2026-05-30T00:00:00.000Z'
354
+ );
355
+ PRAGMA user_version = 1;
356
+ `);
357
+ db.close();
358
+
359
+ const migratedRegistry = new AgentRegistry(regPath);
360
+
361
+ expect(migratedRegistry.lookup("legacy-sqlite")).toMatchObject({
362
+ runtime: "tmux",
363
+ runtimeRef: { session: "legacy-sqlite" },
364
+ });
365
+ });
366
+ });
265
367
 
266
- it('returns false for a PID that does not exist', () => {
267
- expect(registry.isAlive(makeEntry({ pid: 999999 }))).toBe(false);
268
- });
368
+ describe("isAlive", () => {
369
+ it("returns true for the current process", () => {
370
+ expect(registry.isAlive(makeEntry({ pid: process.pid }))).toBe(true);
371
+ });
269
372
 
270
- it('returns true when the process probe is forbidden with EPERM', () => {
271
- vi.spyOn(process, 'kill').mockImplementation(() => {
272
- throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });
273
- });
373
+ it("returns false for a PID that does not exist", () => {
374
+ expect(registry.isAlive(makeEntry({ pid: 999999 }))).toBe(false);
375
+ });
376
+
377
+ it("returns true when the process probe is forbidden with EPERM", () => {
378
+ vi.spyOn(process, "kill").mockImplementation(() => {
379
+ throw Object.assign(new Error("operation not permitted"), { code: "EPERM" });
380
+ });
381
+
382
+ expect(registry.isAlive(makeEntry())).toBe(true);
383
+ });
384
+
385
+ it("returns false when the process probe reports ESRCH", () => {
386
+ vi.spyOn(process, "kill").mockImplementation(() => {
387
+ throw Object.assign(new Error("no such process"), { code: "ESRCH" });
388
+ });
389
+
390
+ expect(registry.isAlive(makeEntry())).toBe(false);
391
+ });
392
+
393
+ it("returns true when the process probe fails without a definitive error code", () => {
394
+ vi.spyOn(process, "kill").mockImplementation(() => {
395
+ throw new Error("indeterminate probe failure");
396
+ });
397
+
398
+ expect(registry.isAlive(makeEntry())).toBe(true);
399
+ });
400
+ });
401
+
402
+ describe("prune", () => {
403
+ it("removes entries whose PIDs are dead", () => {
404
+ registry.register(makeEntry({ name: "alive", pid: process.pid }));
405
+ registry.register(makeEntry({ name: "dead", pid: 999999 }));
406
+ registry.prune();
407
+ const remaining = registry.list();
408
+ expect(remaining).toHaveLength(1);
409
+ expect(remaining[0].name).toBe("alive");
410
+ });
411
+
412
+ it("is a no-op when all entries are alive", () => {
413
+ registry.register(makeEntry({ pid: process.pid }));
414
+ const before = registry.list();
415
+ registry.prune();
416
+ const after = registry.list();
417
+ expect(after).toEqual(before);
418
+ });
419
+
420
+ it("preserves entries when liveness probing fails with EPERM", () => {
421
+ registry.register(makeEntry({ name: "custom-name", runtimeRef: { session: "tmux-custom" } }));
422
+ vi.spyOn(process, "kill").mockImplementation(() => {
423
+ throw Object.assign(new Error("operation not permitted"), { code: "EPERM" });
424
+ });
425
+
426
+ registry.prune();
427
+
428
+ expect(registry.lookup("custom-name")).toMatchObject({
429
+ name: "custom-name",
430
+ runtimeRef: { session: "tmux-custom" },
431
+ });
432
+ });
433
+
434
+ it("removes entries when liveness probing fails with ESRCH", () => {
435
+ registry.register(makeEntry({ name: "dead" }));
436
+ vi.spyOn(process, "kill").mockImplementation(() => {
437
+ throw Object.assign(new Error("no such process"), { code: "ESRCH" });
438
+ });
439
+
440
+ registry.prune();
441
+
442
+ expect(registry.lookup("dead")).toBeNull();
443
+ });
444
+
445
+ it("does nothing when file is missing", () => {
446
+ expect(() => registry.prune()).not.toThrow();
447
+ });
448
+
449
+ it("keeps forced prune available before the passive cadence is due", () => {
450
+ let nowMs = Date.parse("2026-08-14T10:00:00.000Z");
451
+ const clocked = new AgentRegistry(regPath, {
452
+ now: () => new Date(nowMs),
453
+ pruneIntervalMs: 30_000,
454
+ });
455
+ clocked.register(makeEntry({ name: "forced", pid: process.pid }));
456
+ const alive = vi.spyOn(clocked, "isAlive").mockReturnValue(true);
457
+ clocked.pruneIfDue();
458
+ alive.mockReturnValue(false);
459
+ nowMs += 1;
460
+
461
+ clocked.prune();
462
+
463
+ expect(alive).toHaveBeenCalledTimes(2);
464
+ expect(clocked.lookup("forced")).toBeNull();
465
+ });
466
+ });
467
+
468
+ describe("default()", () => {
469
+ it("returns a singleton instance", () => {
470
+ expect(AgentRegistry.default()).toBe(AgentRegistry.default());
471
+ });
472
+ });
473
+
474
+ describe("rename", () => {
475
+ it("updates the name of an existing entry", () => {
476
+ registry.register(makeEntry({ name: "old-name", pid: process.pid }));
477
+ registry.rename("old-name", "new-name");
478
+ expect(registry.lookup("new-name")?.name).toBe("new-name");
479
+ expect(registry.lookup("old-name")).toBeNull();
480
+ });
481
+
482
+ it("preserves all other fields on the renamed entry", () => {
483
+ registry.register(
484
+ makeEntry({
485
+ name: "old-name",
486
+ pid: process.pid,
487
+ runtimeRef: { session: "old-name" },
488
+ cwd: "/my/cwd",
489
+ }),
490
+ );
491
+ registry.rename("old-name", "new-name");
492
+ const entry = registry.lookup("new-name");
493
+ expect(entry?.runtimeRef).toEqual({ session: "old-name" });
494
+ expect(entry?.cwd).toBe("/my/cwd");
495
+ expect(entry?.pid).toBe(process.pid);
496
+ });
497
+
498
+ it("throws RenameNotFoundError when current name does not exist", () => {
499
+ expect(() => registry.rename("ghost", "new-name")).toThrow(RenameNotFoundError);
500
+ });
501
+
502
+ it("throws RenameConflictError when new name is already in use by a live entry", () => {
503
+ registry.register(makeEntry({ name: "agent-a", pid: process.pid }));
504
+ registry.register(makeEntry({ name: "agent-b", pid: process.ppid }));
505
+ expect(() => registry.rename("agent-a", "agent-b")).toThrow(RenameConflictError);
506
+ });
507
+
508
+ it("throws RenameConflictError when the conflicting entry probe fails with EPERM", () => {
509
+ registry.register(makeEntry({ name: "agent-a", pid: process.pid }));
510
+ registry.register(makeEntry({ name: "agent-b", pid: process.ppid }));
511
+ vi.spyOn(process, "kill").mockImplementation(() => {
512
+ throw Object.assign(new Error("operation not permitted"), { code: "EPERM" });
513
+ });
514
+
515
+ expect(() => registry.rename("agent-a", "agent-b")).toThrow(RenameConflictError);
516
+ expect(registry.lookup("agent-b")?.pid).toBe(process.ppid);
517
+ });
518
+
519
+ it("succeeds when new name exists only as a stale (dead) entry", () => {
520
+ registry.register(makeEntry({ name: "agent-a", pid: process.pid }));
521
+ registry.register(makeEntry({ name: "agent-b", pid: 999999 }));
522
+ expect(() => registry.rename("agent-a", "agent-b")).not.toThrow();
523
+ expect(registry.lookup("agent-b")?.pid).toBe(process.pid);
524
+ });
274
525
 
275
- expect(registry.isAlive(makeEntry())).toBe(true);
276
- });
277
-
278
- it('returns false when the process probe reports ESRCH', () => {
279
- vi.spyOn(process, 'kill').mockImplementation(() => {
280
- throw Object.assign(new Error('no such process'), { code: 'ESRCH' });
281
- });
282
-
283
- expect(registry.isAlive(makeEntry())).toBe(false);
284
- });
285
-
286
- it('returns true when the process probe fails without a definitive error code', () => {
287
- vi.spyOn(process, 'kill').mockImplementation(() => {
288
- throw new Error('indeterminate probe failure');
289
- });
290
-
291
- expect(registry.isAlive(makeEntry())).toBe(true);
292
- });
293
- });
294
-
295
- describe('prune', () => {
296
- it('removes entries whose PIDs are dead', () => {
297
- registry.register(makeEntry({ name: 'alive', pid: process.pid }));
298
- registry.register(makeEntry({ name: 'dead', pid: 999999 }));
299
- registry.prune();
300
- const remaining = registry.list();
301
- expect(remaining).toHaveLength(1);
302
- expect(remaining[0].name).toBe('alive');
303
- });
304
-
305
- it('is a no-op when all entries are alive', () => {
306
- registry.register(makeEntry({ pid: process.pid }));
307
- const before = registry.list();
308
- registry.prune();
309
- const after = registry.list();
310
- expect(after).toEqual(before);
311
- });
312
-
313
- it('preserves entries when liveness probing fails with EPERM', () => {
314
- registry.register(makeEntry({ name: 'custom-name', tmuxSession: 'tmux-custom' }));
315
- vi.spyOn(process, 'kill').mockImplementation(() => {
316
- throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });
317
- });
318
-
319
- registry.prune();
320
-
321
- expect(registry.lookup('custom-name')).toMatchObject({
322
- name: 'custom-name',
323
- tmuxSession: 'tmux-custom',
324
- });
325
- });
326
-
327
- it('removes entries when liveness probing fails with ESRCH', () => {
328
- registry.register(makeEntry({ name: 'dead' }));
329
- vi.spyOn(process, 'kill').mockImplementation(() => {
330
- throw Object.assign(new Error('no such process'), { code: 'ESRCH' });
331
- });
332
-
333
- registry.prune();
334
-
335
- expect(registry.lookup('dead')).toBeNull();
336
- });
337
-
338
- it('does nothing when file is missing', () => {
339
- expect(() => registry.prune()).not.toThrow();
340
- });
341
-
342
- it('keeps forced prune available before the passive cadence is due', () => {
343
- let nowMs = Date.parse('2026-08-14T10:00:00.000Z');
344
- const clocked = new AgentRegistry(regPath, {
345
- now: () => new Date(nowMs),
346
- pruneIntervalMs: 30_000,
347
- });
348
- clocked.register(makeEntry({ name: 'forced', pid: process.pid }));
349
- const alive = vi.spyOn(clocked, 'isAlive').mockReturnValue(true);
350
- clocked.pruneIfDue();
351
- alive.mockReturnValue(false);
352
- nowMs += 1;
353
-
354
- clocked.prune();
355
-
356
- expect(alive).toHaveBeenCalledTimes(2);
357
- expect(clocked.lookup('forced')).toBeNull();
358
- });
359
- });
360
-
361
- describe('default()', () => {
362
- it('returns a singleton instance', () => {
363
- expect(AgentRegistry.default()).toBe(AgentRegistry.default());
364
- });
365
- });
366
-
367
- describe('rename', () => {
368
- it('updates the name of an existing entry', () => {
369
- registry.register(makeEntry({ name: 'old-name', pid: process.pid }));
370
- registry.rename('old-name', 'new-name');
371
- expect(registry.lookup('new-name')?.name).toBe('new-name');
372
- expect(registry.lookup('old-name')).toBeNull();
373
- });
374
-
375
- it('preserves all other fields on the renamed entry', () => {
376
- registry.register(makeEntry({ name: 'old-name', pid: process.pid, tmuxSession: 'old-name', cwd: '/my/cwd' }));
377
- registry.rename('old-name', 'new-name');
378
- const entry = registry.lookup('new-name');
379
- expect(entry?.tmuxSession).toBe('old-name');
380
- expect(entry?.cwd).toBe('/my/cwd');
381
- expect(entry?.pid).toBe(process.pid);
382
- });
383
-
384
- it('throws RenameNotFoundError when current name does not exist', () => {
385
- expect(() => registry.rename('ghost', 'new-name')).toThrow(RenameNotFoundError);
386
- });
387
-
388
- it('throws RenameConflictError when new name is already in use by a live entry', () => {
389
- registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));
390
- registry.register(makeEntry({ name: 'agent-b', pid: process.ppid }));
391
- expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);
392
- });
393
-
394
- it('throws RenameConflictError when the conflicting entry probe fails with EPERM', () => {
395
- registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));
396
- registry.register(makeEntry({ name: 'agent-b', pid: process.ppid }));
397
- vi.spyOn(process, 'kill').mockImplementation(() => {
398
- throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });
399
- });
400
-
401
- expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);
402
- expect(registry.lookup('agent-b')?.pid).toBe(process.ppid);
403
- });
404
-
405
- it('succeeds when new name exists only as a stale (dead) entry', () => {
406
- registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));
407
- registry.register(makeEntry({ name: 'agent-b', pid: 999999 }));
408
- expect(() => registry.rename('agent-a', 'agent-b')).not.toThrow();
409
- expect(registry.lookup('agent-b')?.pid).toBe(process.pid);
410
- });
411
-
412
- it('does not create the legacy fixed .tmp path on rename', () => {
413
- registry.register(makeEntry({ name: 'old-name', pid: process.pid }));
414
- registry.rename('old-name', 'new-name');
415
- expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);
416
- });
526
+ it("does not create the legacy fixed .tmp path on rename", () => {
527
+ registry.register(makeEntry({ name: "old-name", pid: process.pid }));
528
+ registry.rename("old-name", "new-name");
529
+ expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);
417
530
  });
531
+ });
418
532
  });