@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,411 +1,434 @@
1
- import type { MockedFunction } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as os from 'os';
4
- import * as path from 'path';
5
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
-
7
- import { AgentStatus, type ProcessInfo } from '../../../adapters/AgentAdapter.js';
8
- import { ClaudeSessionLocator } from '../../../providers/claude/ClaudeSessionLocator.js';
9
- import { batchGetSessionFileBirthtimes } from '../../../utils/session.js';
10
- import type { SessionFile } from '../../../utils/session.js';
11
-
12
- vi.mock('../../../utils/session.js', async (importOriginal) => {
13
- const actual = await importOriginal() as typeof import('../../../utils/session.js');
14
- return {
15
- ...actual,
16
- batchGetSessionFileBirthtimes: vi.fn(),
17
- };
1
+ import type { MockedFunction } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { AgentStatus, type ProcessInfo } from "../../../adapters/AgentAdapter.js";
8
+ import { ClaudeSessionLocator } from "../../../providers/claude/ClaudeSessionLocator.js";
9
+ import { batchGetSessionFileBirthtimes } from "../../../utils/session.js";
10
+ import type { SessionFile } from "../../../utils/session.js";
11
+
12
+ vi.mock("../../../utils/session.js", async (importOriginal) => {
13
+ const actual = (await importOriginal()) as typeof import("../../../utils/session.js");
14
+ return {
15
+ ...actual,
16
+ batchGetSessionFileBirthtimes: vi.fn(),
17
+ };
18
18
  });
19
19
 
20
- const mockedBatchGetSessionFileBirthtimes =
21
- batchGetSessionFileBirthtimes as MockedFunction<typeof batchGetSessionFileBirthtimes>;
20
+ const mockedBatchGetSessionFileBirthtimes = batchGetSessionFileBirthtimes as MockedFunction<
21
+ typeof batchGetSessionFileBirthtimes
22
+ >;
22
23
 
23
24
  const tmpDirs: string[] = [];
24
25
 
25
26
  function makeTmpDir(): string {
26
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-locator-test-'));
27
- tmpDirs.push(dir);
28
- return dir;
27
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-locator-test-"));
28
+ tmpDirs.push(dir);
29
+ return dir;
29
30
  }
30
31
 
31
32
  function makeProcess(overrides: Partial<ProcessInfo> = {}): ProcessInfo {
32
- return {
33
- pid: 123,
34
- command: 'claude',
35
- cwd: '/repo/my-app',
36
- tty: 'ttys001',
37
- startTime: new Date('2026-08-23T10:00:00.000Z'),
38
- ...overrides,
39
- };
33
+ return {
34
+ pid: 123,
35
+ command: "claude",
36
+ cwd: "/repo/my-app",
37
+ tty: "ttys001",
38
+ startTime: new Date("2026-08-23T10:00:00.000Z"),
39
+ ...overrides,
40
+ };
40
41
  }
41
42
 
42
43
  type PidFileMatching = (processes: ProcessInfo[]) => {
43
- direct: Array<{ process: ProcessInfo; sessionFile: SessionFile }>;
44
- fallback: ProcessInfo[];
44
+ direct: Array<{ process: ProcessInfo; sessionFile: SessionFile }>;
45
+ fallback: ProcessInfo[];
45
46
  };
46
47
 
47
48
  /** `tryPidFileMatching` is private; tests drive it directly like the sibling adapter suites do. */
48
49
  function bindPidFileMatching(locator: ClaudeSessionLocator): PidFileMatching {
49
- return (locator as unknown as { tryPidFileMatching: PidFileMatching })
50
- .tryPidFileMatching.bind(locator) as PidFileMatching;
50
+ return (locator as unknown as { tryPidFileMatching: PidFileMatching }).tryPidFileMatching.bind(
51
+ locator,
52
+ ) as PidFileMatching;
51
53
  }
52
54
 
53
55
  /** `getProjectDir` is private; bound here so the encoding tests read cleanly. */
54
56
  function bindProjectDir(locator: ClaudeSessionLocator): (cwd: string) => string {
55
- return (locator as unknown as { getProjectDir: (cwd: string) => string })
56
- .getProjectDir.bind(locator);
57
+ return (locator as unknown as { getProjectDir: (cwd: string) => string }).getProjectDir.bind(
58
+ locator,
59
+ );
57
60
  }
58
61
 
59
- describe('ClaudeSessionLocator', () => {
62
+ describe("ClaudeSessionLocator", () => {
63
+ beforeEach(() => {
64
+ mockedBatchGetSessionFileBirthtimes.mockReset();
65
+ });
66
+
67
+ afterEach(() => {
68
+ for (const dir of tmpDirs.splice(0)) {
69
+ fs.rmSync(dir, { recursive: true, force: true });
70
+ }
71
+ });
72
+
73
+ it("matches resumed sessions directly and carries live PID status metadata", () => {
74
+ const root = makeTmpDir();
75
+ const projectsDir = path.join(root, "projects");
76
+ const sessionsDir = path.join(root, "sessions");
77
+ const cwd = "/repo/my-app";
78
+ const sessionId = "12345678-1234-1234-1234-123456789abc";
79
+ const projectDir = path.join(projectsDir, "-repo-my-app");
80
+ const sessionFile = path.join(projectDir, `${sessionId}.jsonl`);
81
+ fs.mkdirSync(projectDir, { recursive: true });
82
+ fs.mkdirSync(sessionsDir, { recursive: true });
83
+ fs.writeFileSync(sessionFile, "{}\n");
84
+ fs.writeFileSync(
85
+ path.join(sessionsDir, "123.json"),
86
+ JSON.stringify({
87
+ pid: 123,
88
+ sessionId,
89
+ cwd,
90
+ startedAt: new Date("2026-08-23T10:00:00.000Z").getTime(),
91
+ kind: "interactive",
92
+ entrypoint: "cli",
93
+ status: "waiting",
94
+ waitingFor: "approve Read",
95
+ }),
96
+ );
97
+
98
+ const locator = new ClaudeSessionLocator({ projectsDir, sessionsDir });
99
+ const matches = locator.matchRunningProcesses([
100
+ makeProcess({ command: `claude --resume ${sessionId}`, cwd }),
101
+ ]);
102
+
103
+ expect(matches.direct).toHaveLength(1);
104
+ expect(matches.legacyMatches).toEqual([]);
105
+ expect(matches.direct[0]).toMatchObject({
106
+ pidStatus: AgentStatus.WAITING,
107
+ waitingFor: "approve Read",
108
+ sessionFile: {
109
+ sessionId,
110
+ filePath: sessionFile,
111
+ projectDir,
112
+ resolvedCwd: cwd,
113
+ },
114
+ });
115
+ });
116
+ describe("discoverLiveSessions", () => {
117
+ let tmpDir: string;
118
+ let projectsDir: string;
119
+ let locator: ClaudeSessionLocator;
120
+
121
+ beforeEach(() => {
122
+ tmpDir = makeTmpDir();
123
+ projectsDir = path.join(tmpDir, "projects");
124
+ locator = new ClaudeSessionLocator({
125
+ projectsDir,
126
+ sessionsDir: path.join(tmpDir, "sessions"),
127
+ });
128
+ });
129
+
130
+ it("should return empty when projects dir does not exist", () => {
131
+ const missing = new ClaudeSessionLocator({
132
+ projectsDir: path.join(tmpDir, "nonexistent"),
133
+ sessionsDir: path.join(tmpDir, "sessions"),
134
+ });
135
+
136
+ const result = missing.discoverLiveSessions([
137
+ { pid: 1, command: "claude", cwd: "/test", tty: "" },
138
+ ]);
139
+ expect(result).toEqual([]);
140
+ });
141
+
142
+ it("should scan only directories matching process CWDs", () => {
143
+ // /my/project → -my-project (encoded dir)
144
+ const encodedDir = path.join(projectsDir, "-my-project");
145
+ fs.mkdirSync(encodedDir, { recursive: true });
146
+
147
+ // Also create another dir that should NOT be scanned
148
+ const otherDir = path.join(projectsDir, "-other-project");
149
+ fs.mkdirSync(otherDir, { recursive: true });
150
+
151
+ const mockFiles: SessionFile[] = [
152
+ {
153
+ sessionId: "s1",
154
+ filePath: path.join(encodedDir, "s1.jsonl"),
155
+ projectDir: encodedDir,
156
+ birthtimeMs: 1710800324000,
157
+ resolvedCwd: "",
158
+ },
159
+ ];
160
+ mockedBatchGetSessionFileBirthtimes.mockReturnValue(mockFiles);
161
+
162
+ const processes = [{ pid: 1, command: "claude", cwd: "/my/project", tty: "" }];
163
+
164
+ const result = locator.discoverLiveSessions(processes);
165
+ expect(result).toHaveLength(1);
166
+ expect(result[0].resolvedCwd).toBe("/my/project");
167
+ // batchGetSessionFileBirthtimes called once with all dirs
168
+ expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledTimes(1);
169
+ expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledWith([encodedDir]);
170
+ });
171
+
172
+ it("should return empty when encoded dir does not exist", () => {
173
+ fs.mkdirSync(projectsDir, { recursive: true });
174
+
175
+ // Process CWD /test encodes to -test, but that dir doesn't exist
176
+ const result = locator.discoverLiveSessions([
177
+ { pid: 1, command: "claude", cwd: "/test", tty: "" },
178
+ ]);
179
+ expect(result).toEqual([]);
180
+ expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();
181
+ });
182
+
183
+ it("should deduplicate when multiple processes share same CWD", () => {
184
+ const encodedDir = path.join(projectsDir, "-my-project");
185
+ fs.mkdirSync(encodedDir, { recursive: true });
186
+
187
+ mockedBatchGetSessionFileBirthtimes.mockReturnValue([
188
+ {
189
+ sessionId: "s1",
190
+ filePath: path.join(encodedDir, "s1.jsonl"),
191
+ projectDir: encodedDir,
192
+ birthtimeMs: 1710800324000,
193
+ resolvedCwd: "",
194
+ },
195
+ ]);
196
+
197
+ const processes = [
198
+ { pid: 1, command: "claude", cwd: "/my/project", tty: "" },
199
+ { pid: 2, command: "claude", cwd: "/my/project", tty: "" },
200
+ ];
201
+
202
+ const result = locator.discoverLiveSessions(processes);
203
+ // Should only call batch once with deduplicated dir
204
+ expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledTimes(1);
205
+ expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledWith([encodedDir]);
206
+ expect(result).toHaveLength(1);
207
+ });
208
+
209
+ it("should skip processes with empty cwd", () => {
210
+ fs.mkdirSync(projectsDir, { recursive: true });
211
+
212
+ const result = locator.discoverLiveSessions([
213
+ { pid: 1, command: "claude", cwd: "", tty: "" },
214
+ ]);
215
+ expect(result).toEqual([]);
216
+ });
217
+ });
218
+
219
+ describe("tryPidFileMatching", () => {
220
+ let tmpDir: string;
221
+ let sessionsDir: string;
222
+ let projectsDir: string;
223
+ let tryMatch: (processes: ProcessInfo[]) => {
224
+ direct: Array<{ process: ProcessInfo; sessionFile: SessionFile }>;
225
+ fallback: ProcessInfo[];
226
+ };
227
+
60
228
  beforeEach(() => {
61
- mockedBatchGetSessionFileBirthtimes.mockReset();
229
+ tmpDir = makeTmpDir();
230
+ sessionsDir = path.join(tmpDir, "sessions");
231
+ projectsDir = path.join(tmpDir, "projects");
232
+ fs.mkdirSync(sessionsDir, { recursive: true });
233
+ tryMatch = bindPidFileMatching(new ClaudeSessionLocator({ projectsDir, sessionsDir }));
62
234
  });
63
235
 
64
- afterEach(() => {
65
- for (const dir of tmpDirs.splice(0)) {
66
- fs.rmSync(dir, { recursive: true, force: true });
67
- }
236
+ const makeProc = (pid: number, cwd = "/project/test", startTime?: Date): ProcessInfo => ({
237
+ pid,
238
+ command: "claude",
239
+ cwd,
240
+ tty: "ttys001",
241
+ startTime,
68
242
  });
69
243
 
70
- it('matches resumed sessions directly and carries live PID status metadata', () => {
71
- const root = makeTmpDir();
72
- const projectsDir = path.join(root, 'projects');
73
- const sessionsDir = path.join(root, 'sessions');
74
- const cwd = '/repo/my-app';
75
- const sessionId = '12345678-1234-1234-1234-123456789abc';
76
- const projectDir = path.join(projectsDir, '-repo-my-app');
77
- const sessionFile = path.join(projectDir, `${sessionId}.jsonl`);
78
- fs.mkdirSync(projectDir, { recursive: true });
79
- fs.mkdirSync(sessionsDir, { recursive: true });
80
- fs.writeFileSync(sessionFile, '{}\n');
81
- fs.writeFileSync(path.join(sessionsDir, '123.json'), JSON.stringify({
82
- pid: 123,
83
- sessionId,
84
- cwd,
85
- startedAt: new Date('2026-08-23T10:00:00.000Z').getTime(),
86
- kind: 'interactive',
87
- entrypoint: 'cli',
88
- status: 'waiting',
89
- waitingFor: 'approve Read',
90
- }));
91
-
92
- const locator = new ClaudeSessionLocator({ projectsDir, sessionsDir });
93
- const matches = locator.matchRunningProcesses([
94
- makeProcess({ command: `claude --resume ${sessionId}`, cwd }),
95
- ]);
96
-
97
- expect(matches.direct).toHaveLength(1);
98
- expect(matches.legacyMatches).toEqual([]);
99
- expect(matches.direct[0]).toMatchObject({
100
- pidStatus: AgentStatus.WAITING,
101
- waitingFor: 'approve Read',
102
- sessionFile: {
103
- sessionId,
104
- filePath: sessionFile,
105
- projectDir,
106
- resolvedCwd: cwd,
107
- },
108
- });
244
+ const writePidFile = (pid: number, sessionId: string, cwd: string, startedAt: number) => {
245
+ fs.writeFileSync(
246
+ path.join(sessionsDir, `${pid}.json`),
247
+ JSON.stringify({ pid, sessionId, cwd, startedAt, kind: "interactive", entrypoint: "cli" }),
248
+ );
249
+ };
250
+
251
+ const writeJsonl = (cwd: string, sessionId: string) => {
252
+ const encoded = cwd.replace(/[^a-zA-Z0-9]/g, "-");
253
+ const projDir = path.join(projectsDir, encoded);
254
+ fs.mkdirSync(projDir, { recursive: true });
255
+ const filePath = path.join(projDir, `${sessionId}.jsonl`);
256
+ fs.writeFileSync(
257
+ filePath,
258
+ JSON.stringify({ type: "assistant", timestamp: new Date().toISOString() }),
259
+ );
260
+ return filePath;
261
+ };
262
+
263
+ it("should return direct match when PID file and JSONL both exist within time tolerance", () => {
264
+ const startTime = new Date();
265
+ const proc = makeProc(1001, "/project/test", startTime);
266
+ writePidFile(1001, "session-abc", "/project/test", startTime.getTime());
267
+ writeJsonl("/project/test", "session-abc");
268
+
269
+ const { direct, fallback } = tryMatch([proc]);
270
+
271
+ expect(direct).toHaveLength(1);
272
+ expect(fallback).toHaveLength(0);
273
+ expect(direct[0].sessionFile.sessionId).toBe("session-abc");
274
+ expect(direct[0].sessionFile.resolvedCwd).toBe("/project/test");
275
+ expect(direct[0].process.pid).toBe(1001);
109
276
  });
110
- describe('discoverLiveSessions', () => {
111
- let tmpDir: string;
112
- let projectsDir: string;
113
- let locator: ClaudeSessionLocator;
114
-
115
- beforeEach(() => {
116
- tmpDir = makeTmpDir();
117
- projectsDir = path.join(tmpDir, 'projects');
118
- locator = new ClaudeSessionLocator({
119
- projectsDir,
120
- sessionsDir: path.join(tmpDir, 'sessions'),
121
- });
122
- });
123
-
124
- it('should return empty when projects dir does not exist', () => {
125
- const missing = new ClaudeSessionLocator({
126
- projectsDir: path.join(tmpDir, 'nonexistent'),
127
- sessionsDir: path.join(tmpDir, 'sessions'),
128
- });
129
-
130
- const result = missing.discoverLiveSessions([
131
- { pid: 1, command: 'claude', cwd: '/test', tty: '' },
132
- ]);
133
- expect(result).toEqual([]);
134
- });
135
-
136
- it('should scan only directories matching process CWDs', () => {
137
- // /my/project → -my-project (encoded dir)
138
- const encodedDir = path.join(projectsDir, '-my-project');
139
- fs.mkdirSync(encodedDir, { recursive: true });
140
-
141
- // Also create another dir that should NOT be scanned
142
- const otherDir = path.join(projectsDir, '-other-project');
143
- fs.mkdirSync(otherDir, { recursive: true });
144
-
145
- const mockFiles: SessionFile[] = [
146
- {
147
- sessionId: 's1',
148
- filePath: path.join(encodedDir, 's1.jsonl'),
149
- projectDir: encodedDir,
150
- birthtimeMs: 1710800324000,
151
- resolvedCwd: '',
152
- },
153
- ];
154
- mockedBatchGetSessionFileBirthtimes.mockReturnValue(mockFiles);
155
-
156
- const processes = [
157
- { pid: 1, command: 'claude', cwd: '/my/project', tty: '' },
158
- ];
159
-
160
- const result = locator.discoverLiveSessions(processes);
161
- expect(result).toHaveLength(1);
162
- expect(result[0].resolvedCwd).toBe('/my/project');
163
- // batchGetSessionFileBirthtimes called once with all dirs
164
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledTimes(1);
165
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledWith([encodedDir]);
166
- });
167
-
168
- it('should return empty when encoded dir does not exist', () => {
169
- fs.mkdirSync(projectsDir, { recursive: true });
170
-
171
- // Process CWD /test encodes to -test, but that dir doesn't exist
172
- const result = locator.discoverLiveSessions([
173
- { pid: 1, command: 'claude', cwd: '/test', tty: '' },
174
- ]);
175
- expect(result).toEqual([]);
176
- expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();
177
- });
178
-
179
- it('should deduplicate when multiple processes share same CWD', () => {
180
- const encodedDir = path.join(projectsDir, '-my-project');
181
- fs.mkdirSync(encodedDir, { recursive: true });
182
-
183
- mockedBatchGetSessionFileBirthtimes.mockReturnValue([
184
- { sessionId: 's1', filePath: path.join(encodedDir, 's1.jsonl'), projectDir: encodedDir, birthtimeMs: 1710800324000, resolvedCwd: '' },
185
- ]);
186
-
187
- const processes = [
188
- { pid: 1, command: 'claude', cwd: '/my/project', tty: '' },
189
- { pid: 2, command: 'claude', cwd: '/my/project', tty: '' },
190
- ];
191
-
192
- const result = locator.discoverLiveSessions(processes);
193
- // Should only call batch once with deduplicated dir
194
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledTimes(1);
195
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledWith([encodedDir]);
196
- expect(result).toHaveLength(1);
197
- });
198
-
199
- it('should skip processes with empty cwd', () => {
200
- fs.mkdirSync(projectsDir, { recursive: true });
201
-
202
- const result = locator.discoverLiveSessions([
203
- { pid: 1, command: 'claude', cwd: '', tty: '' },
204
- ]);
205
- expect(result).toEqual([]);
206
- });
277
+
278
+ it("should fall back when PID file exists but JSONL is missing", () => {
279
+ const startTime = new Date();
280
+ const proc = makeProc(1002, "/project/test", startTime);
281
+ writePidFile(1002, "nonexistent-session", "/project/test", startTime.getTime());
282
+ // No JSONL file written
283
+
284
+ const { direct, fallback } = tryMatch([proc]);
285
+
286
+ expect(direct).toHaveLength(0);
287
+ expect(fallback).toHaveLength(1);
288
+ expect(fallback[0].pid).toBe(1002);
207
289
  });
208
290
 
209
- describe('tryPidFileMatching', () => {
210
- let tmpDir: string;
211
- let sessionsDir: string;
212
- let projectsDir: string;
213
- let tryMatch: (processes: ProcessInfo[]) => {
214
- direct: Array<{ process: ProcessInfo; sessionFile: SessionFile }>;
215
- fallback: ProcessInfo[];
216
- };
217
-
218
- beforeEach(() => {
219
- tmpDir = makeTmpDir();
220
- sessionsDir = path.join(tmpDir, 'sessions');
221
- projectsDir = path.join(tmpDir, 'projects');
222
- fs.mkdirSync(sessionsDir, { recursive: true });
223
- tryMatch = bindPidFileMatching(new ClaudeSessionLocator({ projectsDir, sessionsDir }));
224
- });
225
-
226
- const makeProc = (pid: number, cwd = '/project/test', startTime?: Date): ProcessInfo => ({
227
- pid, command: 'claude', cwd, tty: 'ttys001', startTime,
228
- });
229
-
230
- const writePidFile = (pid: number, sessionId: string, cwd: string, startedAt: number) => {
231
- fs.writeFileSync(
232
- path.join(sessionsDir, `${pid}.json`),
233
- JSON.stringify({ pid, sessionId, cwd, startedAt, kind: 'interactive', entrypoint: 'cli' }),
234
- );
235
- };
236
-
237
- const writeJsonl = (cwd: string, sessionId: string) => {
238
- const encoded = cwd.replace(/[^a-zA-Z0-9]/g, '-');
239
- const projDir = path.join(projectsDir, encoded);
240
- fs.mkdirSync(projDir, { recursive: true });
241
- const filePath = path.join(projDir, `${sessionId}.jsonl`);
242
- fs.writeFileSync(filePath, JSON.stringify({ type: 'assistant', timestamp: new Date().toISOString() }));
243
- return filePath;
244
- };
245
-
246
- it('should return direct match when PID file and JSONL both exist within time tolerance', () => {
247
- const startTime = new Date();
248
- const proc = makeProc(1001, '/project/test', startTime);
249
- writePidFile(1001, 'session-abc', '/project/test', startTime.getTime());
250
- writeJsonl('/project/test', 'session-abc');
251
-
252
- const { direct, fallback } = tryMatch([proc]);
253
-
254
- expect(direct).toHaveLength(1);
255
- expect(fallback).toHaveLength(0);
256
- expect(direct[0].sessionFile.sessionId).toBe('session-abc');
257
- expect(direct[0].sessionFile.resolvedCwd).toBe('/project/test');
258
- expect(direct[0].process.pid).toBe(1001);
259
- });
260
-
261
- it('should fall back when PID file exists but JSONL is missing', () => {
262
- const startTime = new Date();
263
- const proc = makeProc(1002, '/project/test', startTime);
264
- writePidFile(1002, 'nonexistent-session', '/project/test', startTime.getTime());
265
- // No JSONL file written
266
-
267
- const { direct, fallback } = tryMatch([proc]);
268
-
269
- expect(direct).toHaveLength(0);
270
- expect(fallback).toHaveLength(1);
271
- expect(fallback[0].pid).toBe(1002);
272
- });
273
-
274
- it('should fall back when startedAt is stale (>60s from proc.startTime)', () => {
275
- const startTime = new Date();
276
- const staleTime = startTime.getTime() - 90_000; // 90 seconds earlier
277
- const proc = makeProc(1003, '/project/test', startTime);
278
- writePidFile(1003, 'stale-session', '/project/test', staleTime);
279
- writeJsonl('/project/test', 'stale-session');
280
-
281
- const { direct, fallback } = tryMatch([proc]);
282
-
283
- expect(direct).toHaveLength(0);
284
- expect(fallback).toHaveLength(1);
285
- });
286
-
287
- it('should accept PID file when startedAt is within 60s tolerance', () => {
288
- const startTime = new Date();
289
- const closeTime = startTime.getTime() - 30_000; // 30 seconds earlier — within tolerance
290
- const proc = makeProc(1004, '/project/test', startTime);
291
- writePidFile(1004, 'close-session', '/project/test', closeTime);
292
- writeJsonl('/project/test', 'close-session');
293
-
294
- const { direct, fallback } = tryMatch([proc]);
295
-
296
- expect(direct).toHaveLength(1);
297
- expect(fallback).toHaveLength(0);
298
- });
299
-
300
- it('should fall back when PID file is absent', () => {
301
- const proc = makeProc(1005, '/project/test', new Date());
302
- // No PID file written
303
-
304
- const { direct, fallback } = tryMatch([proc]);
305
-
306
- expect(direct).toHaveLength(0);
307
- expect(fallback).toHaveLength(1);
308
- });
309
-
310
- it('should fall back when PID file contains malformed JSON', () => {
311
- const proc = makeProc(1006, '/project/test', new Date());
312
- fs.writeFileSync(path.join(sessionsDir, '1006.json'), 'not valid json {{{');
313
-
314
- expect(() => {
315
- const { direct, fallback } = tryMatch([proc]);
316
- expect(direct).toHaveLength(0);
317
- expect(fallback).toHaveLength(1);
318
- }).not.toThrow();
319
- });
320
-
321
- it('should fall back for all processes when sessions dir does not exist', () => {
322
- const missingSessions = bindPidFileMatching(new ClaudeSessionLocator({
323
- projectsDir,
324
- sessionsDir: path.join(tmpDir, 'nonexistent-sessions'),
325
- }));
326
- const processes = [makeProc(2001, '/a', new Date()), makeProc(2002, '/b', new Date())];
327
-
328
- const { direct, fallback } = missingSessions(processes);
329
-
330
- expect(direct).toHaveLength(0);
331
- expect(fallback).toHaveLength(2);
332
- });
333
-
334
- it('should correctly split mixed processes (some with PID files, some without)', () => {
335
- const startTime = new Date();
336
- const proc1 = makeProc(3001, '/project/one', startTime);
337
- const proc2 = makeProc(3002, '/project/two', startTime);
338
- const proc3 = makeProc(3003, '/project/three', startTime);
339
-
340
- writePidFile(3001, 'session-one', '/project/one', startTime.getTime());
341
- writeJsonl('/project/one', 'session-one');
342
- writePidFile(3003, 'session-three', '/project/three', startTime.getTime());
343
- writeJsonl('/project/three', 'session-three');
344
- // proc2 has no PID file
345
-
346
- const { direct, fallback } = tryMatch([proc1, proc2, proc3]);
347
-
348
- expect(direct).toHaveLength(2);
349
- expect(fallback).toHaveLength(1);
350
- expect(direct.map((d) => d.process.pid).sort()).toEqual([3001, 3003]);
351
- expect(fallback[0].pid).toBe(3002);
352
- });
353
-
354
- it('should skip stale-file check when proc.startTime is undefined', () => {
355
- const proc = makeProc(4001, '/project/test', undefined); // no startTime
356
- writePidFile(4001, 'no-time-session', '/project/test', Date.now() - 999_999);
357
- writeJsonl('/project/test', 'no-time-session');
358
-
359
- const { direct, fallback } = tryMatch([proc]);
360
-
361
- // startTime undefined → stale check skipped → direct match
362
- expect(direct).toHaveLength(1);
363
- expect(fallback).toHaveLength(0);
364
- });
291
+ it("should fall back when startedAt is stale (>60s from proc.startTime)", () => {
292
+ const startTime = new Date();
293
+ const staleTime = startTime.getTime() - 90_000; // 90 seconds earlier
294
+ const proc = makeProc(1003, "/project/test", startTime);
295
+ writePidFile(1003, "stale-session", "/project/test", staleTime);
296
+ writeJsonl("/project/test", "stale-session");
297
+
298
+ const { direct, fallback } = tryMatch([proc]);
299
+
300
+ expect(direct).toHaveLength(0);
301
+ expect(fallback).toHaveLength(1);
365
302
  });
366
303
 
367
- describe('getProjectDir', () => {
368
- let tmpDir: string;
369
- let projectsDir: string;
370
- let encode: (cwd: string) => string;
371
-
372
- beforeEach(() => {
373
- tmpDir = makeTmpDir();
374
- projectsDir = path.join(tmpDir, 'projects');
375
- encode = bindProjectDir(new ClaudeSessionLocator({
376
- projectsDir,
377
- sessionsDir: path.join(tmpDir, 'sessions'),
378
- }));
379
- });
380
-
381
- it('should replace path separators with hyphens', () => {
382
- expect(encode('/Users/foo/bar')).toBe(path.join(projectsDir, '-Users-foo-bar'));
383
- });
384
-
385
- it('should encode underscores as hyphens (matches Claude Code CLI)', () => {
386
- expect(encode('/Users/foo/my_project')).toBe(path.join(projectsDir, '-Users-foo-my-project'));
387
- });
388
-
389
- it('should encode dots as hyphens', () => {
390
- expect(encode('/Users/foo/.worktrees/x')).toBe(path.join(projectsDir, '-Users-foo--worktrees-x'));
391
- });
392
-
393
- it('should collide paths that differ only in non-alphanumeric chars', () => {
394
- // The encoding is intentionally lossy — callers must
395
- // disambiguate via session JSONL contents, not dir name.
396
- expect(encode('/a/b_c')).toBe(encode('/a/b-c'));
397
- expect(encode('/a/b_c')).toBe(encode('/a/b.c'));
398
- });
399
-
400
- it('should resolve to a real session dir when cwd contains underscores', () => {
401
- const cwd = '/Users/foo/my_project';
402
- const expectedDir = path.join(projectsDir, '-Users-foo-my-project');
403
- fs.mkdirSync(expectedDir, { recursive: true });
404
- const sessionFile = path.join(expectedDir, 'session-underscore.jsonl');
405
- fs.writeFileSync(sessionFile, '');
406
-
407
- expect(encode(cwd)).toBe(expectedDir);
408
- expect(fs.existsSync(path.join(encode(cwd), 'session-underscore.jsonl'))).toBe(true);
409
- });
304
+ it("should accept PID file when startedAt is within 60s tolerance", () => {
305
+ const startTime = new Date();
306
+ const closeTime = startTime.getTime() - 30_000; // 30 seconds earlier — within tolerance
307
+ const proc = makeProc(1004, "/project/test", startTime);
308
+ writePidFile(1004, "close-session", "/project/test", closeTime);
309
+ writeJsonl("/project/test", "close-session");
310
+
311
+ const { direct, fallback } = tryMatch([proc]);
312
+
313
+ expect(direct).toHaveLength(1);
314
+ expect(fallback).toHaveLength(0);
315
+ });
316
+
317
+ it("should fall back when PID file is absent", () => {
318
+ const proc = makeProc(1005, "/project/test", new Date());
319
+ // No PID file written
320
+
321
+ const { direct, fallback } = tryMatch([proc]);
322
+
323
+ expect(direct).toHaveLength(0);
324
+ expect(fallback).toHaveLength(1);
325
+ });
326
+
327
+ it("should fall back when PID file contains malformed JSON", () => {
328
+ const proc = makeProc(1006, "/project/test", new Date());
329
+ fs.writeFileSync(path.join(sessionsDir, "1006.json"), "not valid json {{{");
330
+
331
+ expect(() => {
332
+ const { direct, fallback } = tryMatch([proc]);
333
+ expect(direct).toHaveLength(0);
334
+ expect(fallback).toHaveLength(1);
335
+ }).not.toThrow();
336
+ });
337
+
338
+ it("should fall back for all processes when sessions dir does not exist", () => {
339
+ const missingSessions = bindPidFileMatching(
340
+ new ClaudeSessionLocator({
341
+ projectsDir,
342
+ sessionsDir: path.join(tmpDir, "nonexistent-sessions"),
343
+ }),
344
+ );
345
+ const processes = [makeProc(2001, "/a", new Date()), makeProc(2002, "/b", new Date())];
346
+
347
+ const { direct, fallback } = missingSessions(processes);
348
+
349
+ expect(direct).toHaveLength(0);
350
+ expect(fallback).toHaveLength(2);
351
+ });
352
+
353
+ it("should correctly split mixed processes (some with PID files, some without)", () => {
354
+ const startTime = new Date();
355
+ const proc1 = makeProc(3001, "/project/one", startTime);
356
+ const proc2 = makeProc(3002, "/project/two", startTime);
357
+ const proc3 = makeProc(3003, "/project/three", startTime);
358
+
359
+ writePidFile(3001, "session-one", "/project/one", startTime.getTime());
360
+ writeJsonl("/project/one", "session-one");
361
+ writePidFile(3003, "session-three", "/project/three", startTime.getTime());
362
+ writeJsonl("/project/three", "session-three");
363
+ // proc2 has no PID file
364
+
365
+ const { direct, fallback } = tryMatch([proc1, proc2, proc3]);
366
+
367
+ expect(direct).toHaveLength(2);
368
+ expect(fallback).toHaveLength(1);
369
+ expect(direct.map((d) => d.process.pid).sort()).toEqual([3001, 3003]);
370
+ expect(fallback[0].pid).toBe(3002);
371
+ });
372
+
373
+ it("should skip stale-file check when proc.startTime is undefined", () => {
374
+ const proc = makeProc(4001, "/project/test", undefined); // no startTime
375
+ writePidFile(4001, "no-time-session", "/project/test", Date.now() - 999_999);
376
+ writeJsonl("/project/test", "no-time-session");
377
+
378
+ const { direct, fallback } = tryMatch([proc]);
379
+
380
+ // startTime undefined → stale check skipped → direct match
381
+ expect(direct).toHaveLength(1);
382
+ expect(fallback).toHaveLength(0);
383
+ });
384
+ });
385
+
386
+ describe("getProjectDir", () => {
387
+ let tmpDir: string;
388
+ let projectsDir: string;
389
+ let encode: (cwd: string) => string;
390
+
391
+ beforeEach(() => {
392
+ tmpDir = makeTmpDir();
393
+ projectsDir = path.join(tmpDir, "projects");
394
+ encode = bindProjectDir(
395
+ new ClaudeSessionLocator({
396
+ projectsDir,
397
+ sessionsDir: path.join(tmpDir, "sessions"),
398
+ }),
399
+ );
400
+ });
401
+
402
+ it("should replace path separators with hyphens", () => {
403
+ expect(encode("/Users/foo/bar")).toBe(path.join(projectsDir, "-Users-foo-bar"));
404
+ });
405
+
406
+ it("should encode underscores as hyphens (matches Claude Code CLI)", () => {
407
+ expect(encode("/Users/foo/my_project")).toBe(path.join(projectsDir, "-Users-foo-my-project"));
408
+ });
409
+
410
+ it("should encode dots as hyphens", () => {
411
+ expect(encode("/Users/foo/.worktrees/x")).toBe(
412
+ path.join(projectsDir, "-Users-foo--worktrees-x"),
413
+ );
414
+ });
415
+
416
+ it("should collide paths that differ only in non-alphanumeric chars", () => {
417
+ // The encoding is intentionally lossy — callers must
418
+ // disambiguate via session JSONL contents, not dir name.
419
+ expect(encode("/a/b_c")).toBe(encode("/a/b-c"));
420
+ expect(encode("/a/b_c")).toBe(encode("/a/b.c"));
421
+ });
422
+
423
+ it("should resolve to a real session dir when cwd contains underscores", () => {
424
+ const cwd = "/Users/foo/my_project";
425
+ const expectedDir = path.join(projectsDir, "-Users-foo-my-project");
426
+ fs.mkdirSync(expectedDir, { recursive: true });
427
+ const sessionFile = path.join(expectedDir, "session-underscore.jsonl");
428
+ fs.writeFileSync(sessionFile, "");
429
+
430
+ expect(encode(cwd)).toBe(expectedDir);
431
+ expect(fs.existsSync(path.join(encode(cwd), "session-underscore.jsonl"))).toBe(true);
410
432
  });
433
+ });
411
434
  });