@ai-devkit/agent-manager 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/dist/AgentManager.d.ts +3 -3
  2. package/dist/AgentManager.d.ts.map +1 -1
  3. package/dist/AgentManager.js +80 -79
  4. package/dist/AgentManager.js.map +1 -1
  5. package/dist/__tests__/AgentManager.test.js +413 -388
  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/CopilotAdapter.test.js +254 -248
  12. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -1
  13. package/dist/__tests__/adapters/GeminiCliAdapter.test.js +564 -558
  14. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -1
  15. package/dist/__tests__/adapters/GrokCliAdapter.test.js +114 -114
  16. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +1 -1
  17. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +169 -169
  18. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -1
  19. package/dist/__tests__/capacity/codex.test.js +93 -93
  20. package/dist/__tests__/capacity/codex.test.js.map +1 -1
  21. package/dist/__tests__/capacity/index.test.js +17 -17
  22. package/dist/__tests__/capacity/index.test.js.map +1 -1
  23. package/dist/__tests__/durable/CodexCliProbe.test.js +60 -60
  24. package/dist/__tests__/durable/CodexCliProbe.test.js.map +1 -1
  25. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js +28 -28
  26. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js.map +1 -1
  27. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js +30 -30
  28. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js.map +1 -1
  29. package/dist/__tests__/durable/CodexPrintAgentService.test.js +71 -71
  30. package/dist/__tests__/durable/CodexPrintAgentService.test.js.map +1 -1
  31. package/dist/__tests__/durable/CodexPrintRunner.test.js +125 -125
  32. package/dist/__tests__/durable/CodexPrintRunner.test.js.map +1 -1
  33. package/dist/__tests__/print/ClaudeCliProbe.test.js +20 -20
  34. package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -1
  35. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +20 -20
  36. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
  37. package/dist/__tests__/print/ClaudePrintAgentService.test.js +58 -58
  38. package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
  39. package/dist/__tests__/print/ClaudePrintRunner.test.js +55 -55
  40. package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
  41. package/dist/__tests__/print/DurableAgent.test.js +9 -9
  42. package/dist/__tests__/print/DurableAgent.test.js.map +1 -1
  43. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js +40 -40
  44. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js.map +1 -1
  45. package/dist/__tests__/print/DurableAgentRepository.test.js +66 -66
  46. package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -1
  47. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js +34 -34
  48. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js.map +1 -1
  49. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js +132 -132
  50. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js.map +1 -1
  51. package/dist/__tests__/providers/codex/CodexSessionLocator.test.js +194 -0
  52. package/dist/__tests__/providers/codex/CodexSessionLocator.test.js.map +1 -0
  53. package/dist/__tests__/providers/codex/CodexSessionMapping.test.js +86 -0
  54. package/dist/__tests__/providers/codex/CodexSessionMapping.test.js.map +1 -0
  55. package/dist/__tests__/providers/codex/CodexSessionParser.test.js +254 -0
  56. package/dist/__tests__/providers/codex/CodexSessionParser.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/HerdrAgentRuntime.test.js +452 -0
  80. package/dist/__tests__/runtime/HerdrAgentRuntime.test.js.map +1 -0
  81. package/dist/__tests__/runtime/ManagedAgentRuntime.test.js +588 -0
  82. package/dist/__tests__/runtime/ManagedAgentRuntime.test.js.map +1 -0
  83. package/dist/__tests__/terminal/TerminalFocusManager.test.js +100 -100
  84. package/dist/__tests__/terminal/TerminalFocusManager.test.js.map +1 -1
  85. package/dist/__tests__/terminal/TmuxManager.test.js +101 -101
  86. package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -1
  87. package/dist/__tests__/terminal/TtyWriter.test.js +185 -185
  88. package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -1
  89. package/dist/__tests__/utils/AgentRegistry.test.js +335 -217
  90. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
  91. package/dist/__tests__/utils/ClaudeSessionParser.test.js +103 -101
  92. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -1
  93. package/dist/__tests__/utils/agent-requests.test.js +43 -43
  94. package/dist/__tests__/utils/agent-requests.test.js.map +1 -1
  95. package/dist/__tests__/utils/agents.test.js +17 -17
  96. package/dist/__tests__/utils/agents.test.js.map +1 -1
  97. package/dist/__tests__/utils/matching.test.js +92 -92
  98. package/dist/__tests__/utils/matching.test.js.map +1 -1
  99. package/dist/__tests__/utils/process.test.js +108 -108
  100. package/dist/__tests__/utils/process.test.js.map +1 -1
  101. package/dist/__tests__/utils/session.test.js +53 -53
  102. package/dist/__tests__/utils/session.test.js.map +1 -1
  103. package/dist/adapters/AgentAdapter.d.ts +2 -2
  104. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  105. package/dist/adapters/AgentAdapter.js +1 -1
  106. package/dist/adapters/AgentAdapter.js.map +1 -1
  107. package/dist/adapters/CopilotAdapter.d.ts +2 -2
  108. package/dist/adapters/CopilotAdapter.d.ts.map +1 -1
  109. package/dist/adapters/CopilotAdapter.js +51 -51
  110. package/dist/adapters/CopilotAdapter.js.map +1 -1
  111. package/dist/adapters/GeminiCliAdapter.d.ts +2 -2
  112. package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
  113. package/dist/adapters/GeminiCliAdapter.js +86 -86
  114. package/dist/adapters/GeminiCliAdapter.js.map +1 -1
  115. package/dist/adapters/GrokCliAdapter.d.ts +1 -1
  116. package/dist/adapters/GrokCliAdapter.d.ts.map +1 -1
  117. package/dist/adapters/GrokCliAdapter.js +80 -80
  118. package/dist/adapters/GrokCliAdapter.js.map +1 -1
  119. package/dist/adapters/OpenCodeAdapter.d.ts +1 -1
  120. package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -1
  121. package/dist/adapters/OpenCodeAdapter.js +37 -36
  122. package/dist/adapters/OpenCodeAdapter.js.map +1 -1
  123. package/dist/adapters/index.d.ts +9 -9
  124. package/dist/adapters/index.d.ts.map +1 -1
  125. package/dist/adapters/index.js +8 -8
  126. package/dist/adapters/index.js.map +1 -1
  127. package/dist/capacity/codex.d.ts +3 -3
  128. package/dist/capacity/codex.d.ts.map +1 -1
  129. package/dist/capacity/codex.js +57 -57
  130. package/dist/capacity/codex.js.map +1 -1
  131. package/dist/capacity/index.d.ts +3 -3
  132. package/dist/capacity/index.d.ts.map +1 -1
  133. package/dist/capacity/index.js +8 -8
  134. package/dist/capacity/index.js.map +1 -1
  135. package/dist/capacity/types.d.ts +1 -1
  136. package/dist/capacity/types.js.map +1 -1
  137. package/dist/database/connection.d.ts +1 -1
  138. package/dist/database/connection.d.ts.map +1 -1
  139. package/dist/database/connection.js +15 -15
  140. package/dist/database/connection.js.map +1 -1
  141. package/dist/database/index.d.ts +3 -3
  142. package/dist/database/index.d.ts.map +1 -1
  143. package/dist/database/index.js +2 -2
  144. package/dist/database/index.js.map +1 -1
  145. package/dist/database/migrations/005_agent_runtime.sql +2 -0
  146. package/dist/database/schema.d.ts +1 -1
  147. package/dist/database/schema.d.ts.map +1 -1
  148. package/dist/database/schema.js +7 -7
  149. package/dist/database/schema.js.map +1 -1
  150. package/dist/durable/DurableAgent.d.ts +9 -9
  151. package/dist/durable/DurableAgent.d.ts.map +1 -1
  152. package/dist/durable/DurableAgent.js +16 -16
  153. package/dist/durable/DurableAgent.js.map +1 -1
  154. package/dist/durable/DurableAgentRepository.d.ts +4 -4
  155. package/dist/durable/DurableAgentRepository.d.ts.map +1 -1
  156. package/dist/durable/DurableAgentRepository.js +49 -48
  157. package/dist/durable/DurableAgentRepository.js.map +1 -1
  158. package/dist/durable/process.d.ts +1 -1
  159. package/dist/durable/process.d.ts.map +1 -1
  160. package/dist/durable/process.js +14 -14
  161. package/dist/durable/process.js.map +1 -1
  162. package/dist/durable/run.d.ts +2 -2
  163. package/dist/durable/run.d.ts.map +1 -1
  164. package/dist/durable/run.js +6 -6
  165. package/dist/durable/run.js.map +1 -1
  166. package/dist/durable/utils.d.ts +1 -1
  167. package/dist/durable/utils.d.ts.map +1 -1
  168. package/dist/durable/utils.js +4 -4
  169. package/dist/durable/utils.js.map +1 -1
  170. package/dist/index.d.ts +46 -43
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +27 -26
  173. package/dist/index.js.map +1 -1
  174. package/dist/providers/claude/ClaudeAgentMapper.d.ts +4 -4
  175. package/dist/providers/claude/ClaudeAgentMapper.d.ts.map +1 -1
  176. package/dist/providers/claude/ClaudeAgentMapper.js +10 -10
  177. package/dist/providers/claude/ClaudeAgentMapper.js.map +1 -1
  178. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +1 -1
  179. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  180. package/dist/providers/claude/ClaudeCodeAdapter.js +14 -14
  181. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  182. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -4
  183. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  184. package/dist/providers/claude/ClaudeSessionLocator.js +79 -79
  185. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  186. package/dist/providers/claude/ClaudeSessionParser.d.ts +2 -2
  187. package/dist/providers/claude/ClaudeSessionParser.d.ts.map +1 -1
  188. package/dist/providers/claude/ClaudeSessionParser.js +99 -99
  189. package/dist/providers/claude/ClaudeSessionParser.js.map +1 -1
  190. package/dist/providers/claude/durable/ClaudeCliProbe.d.ts.map +1 -1
  191. package/dist/providers/claude/durable/ClaudeCliProbe.js +16 -16
  192. package/dist/providers/claude/durable/ClaudeCliProbe.js.map +1 -1
  193. package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts +5 -5
  194. package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts.map +1 -1
  195. package/dist/providers/claude/durable/ClaudePrintAgentService.js +10 -10
  196. package/dist/providers/claude/durable/ClaudePrintAgentService.js.map +1 -1
  197. package/dist/providers/claude/durable/ClaudePrintRunner.d.ts +4 -4
  198. package/dist/providers/claude/durable/ClaudePrintRunner.d.ts.map +1 -1
  199. package/dist/providers/claude/durable/ClaudePrintRunner.js +18 -18
  200. package/dist/providers/claude/durable/ClaudePrintRunner.js.map +1 -1
  201. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts +2 -2
  202. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts.map +1 -1
  203. package/dist/providers/claude/durable/ClaudeStreamParser.js +17 -17
  204. package/dist/providers/claude/durable/ClaudeStreamParser.js.map +1 -1
  205. package/dist/providers/codex/CodexAdapter.d.ts +27 -0
  206. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -0
  207. package/dist/providers/codex/CodexAdapter.js +162 -0
  208. package/dist/providers/codex/CodexAdapter.js.map +1 -0
  209. package/dist/providers/codex/CodexAgentMapper.d.ts +9 -0
  210. package/dist/providers/codex/CodexAgentMapper.d.ts.map +1 -0
  211. package/dist/providers/codex/CodexAgentMapper.js +35 -0
  212. package/dist/providers/codex/CodexAgentMapper.js.map +1 -0
  213. package/dist/providers/codex/CodexSessionLocator.d.ts +44 -0
  214. package/dist/providers/codex/CodexSessionLocator.d.ts.map +1 -0
  215. package/dist/providers/codex/CodexSessionLocator.js +207 -0
  216. package/dist/providers/codex/CodexSessionLocator.js.map +1 -0
  217. package/dist/providers/codex/CodexSessionMapping.d.ts +23 -0
  218. package/dist/providers/codex/CodexSessionMapping.d.ts.map +1 -0
  219. package/dist/providers/codex/CodexSessionMapping.js +68 -0
  220. package/dist/providers/codex/CodexSessionMapping.js.map +1 -0
  221. package/dist/providers/codex/CodexSessionParser.d.ts +58 -0
  222. package/dist/providers/codex/CodexSessionParser.d.ts.map +1 -0
  223. package/dist/providers/codex/CodexSessionParser.js +262 -0
  224. package/dist/providers/codex/CodexSessionParser.js.map +1 -0
  225. package/dist/providers/codex/durable/CodexCliProbe.d.ts.map +1 -1
  226. package/dist/providers/codex/durable/CodexCliProbe.js +22 -22
  227. package/dist/providers/codex/durable/CodexCliProbe.js.map +1 -1
  228. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts +6 -6
  229. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts.map +1 -1
  230. package/dist/providers/codex/durable/CodexPrintAgentService.js +10 -10
  231. package/dist/providers/codex/durable/CodexPrintAgentService.js.map +1 -1
  232. package/dist/providers/codex/durable/CodexPrintRunner.d.ts +4 -4
  233. package/dist/providers/codex/durable/CodexPrintRunner.d.ts.map +1 -1
  234. package/dist/providers/codex/durable/CodexPrintRunner.js +20 -20
  235. package/dist/providers/codex/durable/CodexPrintRunner.js.map +1 -1
  236. package/dist/providers/codex/durable/CodexStreamParser.d.ts +2 -2
  237. package/dist/providers/codex/durable/CodexStreamParser.d.ts.map +1 -1
  238. package/dist/providers/codex/durable/CodexStreamParser.js +21 -21
  239. package/dist/providers/codex/durable/CodexStreamParser.js.map +1 -1
  240. package/dist/providers/pi/PiAdapter.d.ts +28 -0
  241. package/dist/providers/pi/PiAdapter.d.ts.map +1 -0
  242. package/dist/providers/pi/PiAdapter.js +153 -0
  243. package/dist/providers/pi/PiAdapter.js.map +1 -0
  244. package/dist/providers/pi/PiAgentMapper.d.ts +9 -0
  245. package/dist/providers/pi/PiAgentMapper.d.ts.map +1 -0
  246. package/dist/providers/pi/PiAgentMapper.js +37 -0
  247. package/dist/providers/pi/PiAgentMapper.js.map +1 -0
  248. package/dist/providers/pi/PiSessionLocator.d.ts +23 -0
  249. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -0
  250. package/dist/providers/pi/PiSessionLocator.js +79 -0
  251. package/dist/providers/pi/PiSessionLocator.js.map +1 -0
  252. package/dist/providers/pi/PiSessionParser.d.ts +36 -0
  253. package/dist/providers/pi/PiSessionParser.d.ts.map +1 -0
  254. package/dist/providers/pi/PiSessionParser.js +191 -0
  255. package/dist/providers/pi/PiSessionParser.js.map +1 -0
  256. package/dist/providers/pi/PiSessionTracker.d.ts +23 -0
  257. package/dist/providers/pi/PiSessionTracker.d.ts.map +1 -0
  258. package/dist/providers/pi/PiSessionTracker.js +69 -0
  259. package/dist/providers/pi/PiSessionTracker.js.map +1 -0
  260. package/dist/providers/pi/durable/PiCliProbe.d.ts.map +1 -1
  261. package/dist/providers/pi/durable/PiCliProbe.js +15 -15
  262. package/dist/providers/pi/durable/PiCliProbe.js.map +1 -1
  263. package/dist/providers/pi/durable/PiPrintAgentService.d.ts +6 -6
  264. package/dist/providers/pi/durable/PiPrintAgentService.d.ts.map +1 -1
  265. package/dist/providers/pi/durable/PiPrintAgentService.js +10 -10
  266. package/dist/providers/pi/durable/PiPrintAgentService.js.map +1 -1
  267. package/dist/providers/pi/durable/PiPrintProtocol.d.ts.map +1 -1
  268. package/dist/providers/pi/durable/PiPrintProtocol.js +18 -18
  269. package/dist/providers/pi/durable/PiPrintProtocol.js.map +1 -1
  270. package/dist/providers/pi/durable/PiPrintRunner.d.ts +4 -4
  271. package/dist/providers/pi/durable/PiPrintRunner.d.ts.map +1 -1
  272. package/dist/providers/pi/durable/PiPrintRunner.js +15 -15
  273. package/dist/providers/pi/durable/PiPrintRunner.js.map +1 -1
  274. package/dist/providers/pi/durable/PiStreamParser.d.ts +1 -1
  275. package/dist/providers/pi/durable/PiStreamParser.d.ts.map +1 -1
  276. package/dist/providers/pi/durable/PiStreamParser.js +16 -16
  277. package/dist/providers/pi/durable/PiStreamParser.js.map +1 -1
  278. package/dist/readiness/AgentReadiness.d.ts +6 -6
  279. package/dist/readiness/AgentReadiness.d.ts.map +1 -1
  280. package/dist/readiness/AgentReadiness.js +130 -130
  281. package/dist/readiness/AgentReadiness.js.map +1 -1
  282. package/dist/runtime/ManagedAgentRuntime.d.ts +69 -0
  283. package/dist/runtime/ManagedAgentRuntime.d.ts.map +1 -0
  284. package/dist/runtime/ManagedAgentRuntime.js +156 -0
  285. package/dist/runtime/ManagedAgentRuntime.js.map +1 -0
  286. package/dist/runtime/RuntimeFactory.d.ts +4 -0
  287. package/dist/runtime/RuntimeFactory.d.ts.map +1 -0
  288. package/dist/runtime/RuntimeFactory.js +11 -0
  289. package/dist/runtime/RuntimeFactory.js.map +1 -0
  290. package/dist/runtime/errors.d.ts +27 -0
  291. package/dist/runtime/errors.d.ts.map +1 -0
  292. package/dist/runtime/errors.js +42 -0
  293. package/dist/runtime/errors.js.map +1 -0
  294. package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +31 -0
  295. package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -0
  296. package/dist/runtime/herdr/HerdrAgentRuntime.js +184 -0
  297. package/dist/runtime/herdr/HerdrAgentRuntime.js.map +1 -0
  298. package/dist/runtime/herdr/HerdrCliClient.d.ts +13 -0
  299. package/dist/runtime/herdr/HerdrCliClient.d.ts.map +1 -0
  300. package/dist/runtime/herdr/HerdrCliClient.js +60 -0
  301. package/dist/runtime/herdr/HerdrCliClient.js.map +1 -0
  302. package/dist/runtime/herdr/HerdrErrors.d.ts +7 -0
  303. package/dist/runtime/herdr/HerdrErrors.d.ts.map +1 -0
  304. package/dist/runtime/herdr/HerdrErrors.js +10 -0
  305. package/dist/runtime/herdr/HerdrErrors.js.map +1 -0
  306. package/dist/runtime/herdr/HerdrResponseParsers.d.ts +14 -0
  307. package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -0
  308. package/dist/runtime/herdr/HerdrResponseParsers.js +96 -0
  309. package/dist/runtime/herdr/HerdrResponseParsers.js.map +1 -0
  310. package/dist/runtime/herdr/HerdrRuntimeRef.d.ts +9 -0
  311. package/dist/runtime/herdr/HerdrRuntimeRef.d.ts.map +1 -0
  312. package/dist/runtime/herdr/HerdrRuntimeRef.js +33 -0
  313. package/dist/runtime/herdr/HerdrRuntimeRef.js.map +1 -0
  314. package/dist/runtime/tmux/PidPolling.d.ts +5 -0
  315. package/dist/runtime/tmux/PidPolling.d.ts.map +1 -0
  316. package/dist/runtime/tmux/PidPolling.js +24 -0
  317. package/dist/runtime/tmux/PidPolling.js.map +1 -0
  318. package/dist/runtime/tmux/TmuxAgentRuntime.d.ts +17 -0
  319. package/dist/runtime/tmux/TmuxAgentRuntime.d.ts.map +1 -0
  320. package/dist/runtime/tmux/TmuxAgentRuntime.js +80 -0
  321. package/dist/runtime/tmux/TmuxAgentRuntime.js.map +1 -0
  322. package/dist/runtime/tmux/TmuxRuntimeRef.d.ts +5 -0
  323. package/dist/runtime/tmux/TmuxRuntimeRef.d.ts.map +1 -0
  324. package/dist/runtime/tmux/TmuxRuntimeRef.js +9 -0
  325. package/dist/runtime/tmux/TmuxRuntimeRef.js.map +1 -0
  326. package/dist/runtime/types.d.ts +54 -0
  327. package/dist/runtime/types.d.ts.map +1 -0
  328. package/dist/runtime/types.js +3 -0
  329. package/dist/runtime/types.js.map +1 -0
  330. package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
  331. package/dist/terminal/TerminalFocusManager.js +50 -50
  332. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  333. package/dist/terminal/TmuxManager.d.ts.map +1 -1
  334. package/dist/terminal/TmuxManager.js +47 -47
  335. package/dist/terminal/TmuxManager.js.map +1 -1
  336. package/dist/terminal/TtyWriter.d.ts +1 -1
  337. package/dist/terminal/TtyWriter.d.ts.map +1 -1
  338. package/dist/terminal/TtyWriter.js +89 -89
  339. package/dist/terminal/TtyWriter.js.map +1 -1
  340. package/dist/terminal/index.d.ts +4 -4
  341. package/dist/terminal/index.js +3 -3
  342. package/dist/terminal/index.js.map +1 -1
  343. package/dist/utils/AgentRegistry.d.ts +10 -2
  344. package/dist/utils/AgentRegistry.d.ts.map +1 -1
  345. package/dist/utils/AgentRegistry.js +57 -24
  346. package/dist/utils/AgentRegistry.js.map +1 -1
  347. package/dist/utils/agent-requests.d.ts.map +1 -1
  348. package/dist/utils/agent-requests.js +5 -5
  349. package/dist/utils/agent-requests.js.map +1 -1
  350. package/dist/utils/agents.d.ts +2 -2
  351. package/dist/utils/agents.d.ts.map +1 -1
  352. package/dist/utils/agents.js +15 -15
  353. package/dist/utils/agents.js.map +1 -1
  354. package/dist/utils/applescript.js +1 -1
  355. package/dist/utils/applescript.js.map +1 -1
  356. package/dist/utils/index.d.ts +6 -6
  357. package/dist/utils/index.d.ts.map +1 -1
  358. package/dist/utils/index.js +4 -4
  359. package/dist/utils/index.js.map +1 -1
  360. package/dist/utils/matching.d.ts +2 -2
  361. package/dist/utils/matching.d.ts.map +1 -1
  362. package/dist/utils/matching.js +4 -4
  363. package/dist/utils/matching.js.map +1 -1
  364. package/dist/utils/process.d.ts +1 -1
  365. package/dist/utils/process.d.ts.map +1 -1
  366. package/dist/utils/process.js +74 -74
  367. package/dist/utils/process.js.map +1 -1
  368. package/dist/utils/session.d.ts +1 -1
  369. package/dist/utils/session.d.ts.map +1 -1
  370. package/dist/utils/session.js +7 -7
  371. package/dist/utils/session.js.map +1 -1
  372. package/dist/utils/sortAgents.d.ts +2 -2
  373. package/dist/utils/sortAgents.d.ts.map +1 -1
  374. package/dist/utils/sortAgents.js +5 -5
  375. package/dist/utils/sortAgents.js.map +1 -1
  376. package/package.json +18 -19
  377. package/src/AgentManager.ts +283 -281
  378. package/src/__tests__/AgentManager.test.ts +917 -907
  379. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1657 -1368
  380. package/src/__tests__/adapters/CodexAdapter.test.ts +365 -1698
  381. package/src/__tests__/adapters/CopilotAdapter.test.ts +576 -481
  382. package/src/__tests__/adapters/GeminiCliAdapter.test.ts +1189 -1157
  383. package/src/__tests__/adapters/GrokCliAdapter.test.ts +325 -272
  384. package/src/__tests__/adapters/OpenCodeAdapter.test.ts +510 -455
  385. package/src/__tests__/capacity/codex.test.ts +178 -91
  386. package/src/__tests__/capacity/index.test.ts +27 -18
  387. package/src/__tests__/durable/CodexCliProbe.test.ts +75 -53
  388. package/src/__tests__/durable/CodexDurableAgentRepository.test.ts +51 -40
  389. package/src/__tests__/durable/CodexPrintAgent.integration.test.ts +89 -64
  390. package/src/__tests__/durable/CodexPrintAgentService.test.ts +196 -118
  391. package/src/__tests__/durable/CodexPrintRunner.test.ts +282 -162
  392. package/src/__tests__/fixtures/fake-claude.cjs +20 -13
  393. package/src/__tests__/fixtures/fake-codex.cjs +41 -30
  394. package/src/__tests__/print/ClaudeCliProbe.test.ts +27 -21
  395. package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +48 -40
  396. package/src/__tests__/print/ClaudePrintAgentService.test.ts +118 -67
  397. package/src/__tests__/print/ClaudePrintRunner.test.ts +136 -83
  398. package/src/__tests__/print/DurableAgent.test.ts +19 -16
  399. package/src/__tests__/print/DurableAgentRepository.sqlite.test.ts +109 -86
  400. package/src/__tests__/print/DurableAgentRepository.test.ts +170 -137
  401. package/src/__tests__/providers/claude/ClaudeAgentMapper.test.ts +65 -65
  402. package/src/__tests__/providers/claude/ClaudeSessionLocator.test.ts +400 -377
  403. package/src/__tests__/providers/codex/CodexSessionLocator.test.ts +182 -0
  404. package/src/__tests__/providers/codex/CodexSessionMapping.test.ts +63 -0
  405. package/src/__tests__/providers/codex/CodexSessionParser.test.ts +192 -0
  406. package/src/__tests__/providers/pi/PiAdapter.test.ts +310 -0
  407. package/src/__tests__/providers/pi/PiSessionParser.test.ts +190 -0
  408. package/src/__tests__/providers/pi/PiSessionTracker.test.ts +79 -0
  409. package/src/__tests__/providers/pi/durable/PiCliProbe.test.ts +48 -32
  410. package/src/__tests__/providers/pi/durable/PiDurableAgentRepository.test.ts +24 -23
  411. package/src/__tests__/providers/pi/durable/PiPrintAgentService.test.ts +131 -59
  412. package/src/__tests__/providers/pi/durable/PiPrintProtocol.test.ts +66 -28
  413. package/src/__tests__/providers/pi/durable/PiPrintRunner.test.ts +236 -90
  414. package/src/__tests__/providers/pi/durable/PiStreamParser.test.ts +20 -18
  415. package/src/__tests__/readiness/AgentReadiness.test.ts +217 -169
  416. package/src/__tests__/runtime/AgentRuntime.test.ts +8 -0
  417. package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +358 -0
  418. package/src/__tests__/runtime/ManagedAgentRuntime.test.ts +555 -0
  419. package/src/__tests__/terminal/TerminalFocusManager.test.ts +254 -253
  420. package/src/__tests__/terminal/TmuxManager.test.ts +157 -157
  421. package/src/__tests__/terminal/TtyWriter.test.ts +423 -419
  422. package/src/__tests__/utils/AgentRegistry.test.ts +506 -402
  423. package/src/__tests__/utils/ClaudeSessionParser.test.ts +276 -242
  424. package/src/__tests__/utils/agent-requests.test.ts +76 -56
  425. package/src/__tests__/utils/agents.test.ts +20 -20
  426. package/src/__tests__/utils/matching.test.ts +206 -171
  427. package/src/__tests__/utils/process.test.ts +276 -262
  428. package/src/__tests__/utils/session.test.ts +133 -139
  429. package/src/adapters/AgentAdapter.ts +134 -126
  430. package/src/adapters/CopilotAdapter.ts +405 -395
  431. package/src/adapters/GeminiCliAdapter.ts +598 -600
  432. package/src/adapters/GrokCliAdapter.ts +353 -342
  433. package/src/adapters/OpenCodeAdapter.ts +285 -259
  434. package/src/adapters/index.ts +15 -9
  435. package/src/capacity/codex.ts +137 -84
  436. package/src/capacity/index.ts +18 -13
  437. package/src/capacity/types.ts +1 -1
  438. package/src/database/connection.ts +73 -68
  439. package/src/database/index.ts +6 -6
  440. package/src/database/migrations/005_agent_runtime.sql +2 -0
  441. package/src/database/schema.ts +40 -38
  442. package/src/durable/DurableAgent.ts +85 -85
  443. package/src/durable/DurableAgentRepository.ts +409 -260
  444. package/src/durable/process.ts +22 -21
  445. package/src/durable/run.ts +61 -51
  446. package/src/durable/utils.ts +46 -41
  447. package/src/index.ts +144 -103
  448. package/src/providers/claude/ClaudeAgentMapper.ts +51 -50
  449. package/src/providers/claude/ClaudeCodeAdapter.ts +144 -136
  450. package/src/providers/claude/ClaudeSessionLocator.ts +291 -293
  451. package/src/providers/claude/ClaudeSessionParser.ts +322 -338
  452. package/src/providers/claude/durable/ClaudeCliProbe.ts +42 -37
  453. package/src/providers/claude/durable/ClaudePrintAgentService.ts +63 -50
  454. package/src/providers/claude/durable/ClaudePrintRunner.ts +79 -69
  455. package/src/providers/claude/durable/ClaudeStreamParser.ts +91 -62
  456. package/src/providers/codex/CodexAdapter.ts +205 -0
  457. package/src/providers/codex/CodexAgentMapper.ts +35 -0
  458. package/src/providers/codex/CodexSessionLocator.ts +279 -0
  459. package/src/providers/codex/CodexSessionMapping.ts +89 -0
  460. package/src/providers/codex/CodexSessionParser.ts +385 -0
  461. package/src/providers/codex/durable/CodexCliProbe.ts +50 -42
  462. package/src/providers/codex/durable/CodexPrintAgentService.ts +63 -49
  463. package/src/providers/codex/durable/CodexPrintRunner.ts +79 -69
  464. package/src/providers/codex/durable/CodexStreamParser.ts +97 -84
  465. package/src/providers/pi/PiAdapter.ts +183 -0
  466. package/src/providers/pi/PiAgentMapper.ts +36 -0
  467. package/src/providers/pi/PiSessionLocator.ts +95 -0
  468. package/src/providers/pi/PiSessionParser.ts +284 -0
  469. package/src/providers/pi/PiSessionTracker.ts +87 -0
  470. package/src/providers/pi/durable/PiCliProbe.ts +42 -34
  471. package/src/providers/pi/durable/PiPrintAgentService.ts +61 -53
  472. package/src/providers/pi/durable/PiPrintProtocol.ts +31 -29
  473. package/src/providers/pi/durable/PiPrintRunner.ts +75 -68
  474. package/src/providers/pi/durable/PiStreamParser.ts +82 -72
  475. package/src/readiness/AgentReadiness.ts +585 -513
  476. package/src/runtime/ManagedAgentRuntime.ts +266 -0
  477. package/src/runtime/RuntimeFactory.ts +13 -0
  478. package/src/runtime/errors.ts +50 -0
  479. package/src/runtime/herdr/HerdrAgentRuntime.ts +190 -0
  480. package/src/runtime/herdr/HerdrCliClient.ts +78 -0
  481. package/src/runtime/herdr/HerdrErrors.ts +9 -0
  482. package/src/runtime/herdr/HerdrResponseParsers.ts +76 -0
  483. package/src/runtime/herdr/HerdrRuntimeRef.ts +41 -0
  484. package/src/runtime/tmux/PidPolling.ts +34 -0
  485. package/src/runtime/tmux/TmuxAgentRuntime.ts +113 -0
  486. package/src/runtime/tmux/TmuxRuntimeRef.ts +9 -0
  487. package/src/runtime/types.ts +56 -0
  488. package/src/terminal/TerminalFocusManager.ts +215 -212
  489. package/src/terminal/TmuxManager.ts +99 -92
  490. package/src/terminal/TtyWriter.ts +203 -194
  491. package/src/terminal/index.ts +4 -4
  492. package/src/utils/AgentRegistry.ts +299 -238
  493. package/src/utils/agent-requests.ts +16 -16
  494. package/src/utils/agents.ts +41 -38
  495. package/src/utils/applescript.ts +4 -4
  496. package/src/utils/index.ts +12 -6
  497. package/src/utils/matching.ts +43 -43
  498. package/src/utils/process.ts +261 -258
  499. package/src/utils/session.ts +66 -66
  500. package/src/utils/sortAgents.ts +35 -33
  501. package/tsconfig.json +2 -8
  502. package/vitest.config.ts +16 -16
  503. package/.eslintrc.json +0 -31
  504. package/dist/__tests__/adapters/PiAdapter.test.js +0 -618
  505. package/dist/__tests__/adapters/PiAdapter.test.js.map +0 -1
  506. package/dist/__tests__/database/DurableAgentsDatabase.test.js +0 -128
  507. package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +0 -1
  508. package/dist/adapters/CodexAdapter.d.ts +0 -109
  509. package/dist/adapters/CodexAdapter.d.ts.map +0 -1
  510. package/dist/adapters/CodexAdapter.js +0 -710
  511. package/dist/adapters/CodexAdapter.js.map +0 -1
  512. package/dist/adapters/PiAdapter.d.ts +0 -63
  513. package/dist/adapters/PiAdapter.d.ts.map +0 -1
  514. package/dist/adapters/PiAdapter.js +0 -453
  515. package/dist/adapters/PiAdapter.js.map +0 -1
  516. package/src/__tests__/adapters/PiAdapter.test.ts +0 -458
  517. package/src/__tests__/database/DurableAgentsDatabase.test.ts +0 -102
  518. package/src/adapters/CodexAdapter.ts +0 -923
  519. package/src/adapters/PiAdapter.ts +0 -598
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Tests for GeminiCliAdapter
3
- */ import * as fs from 'fs';
4
- import * as os from 'os';
5
- import * as path from 'path';
6
- import { GeminiCliAdapter } from '../../adapters/GeminiCliAdapter.js';
7
- import { AgentStatus } from '../../adapters/AgentAdapter.js';
8
- import { AgentRegistry } from '../../utils/AgentRegistry.js';
9
- import { listAgentProcesses, enrichProcesses, captureProcessSnapshot } from '../../utils/process.js';
10
- import { matchProcessesToSessions, generateAgentName } from '../../utils/matching.js';
11
- import * as crypto from 'crypto';
12
- vi.mock('../../utils/process.js', async (importOriginal)=>{
3
+ */ import * as fs from "fs";
4
+ import * as os from "os";
5
+ import * as path from "path";
6
+ import { GeminiCliAdapter } from "../../adapters/GeminiCliAdapter.js";
7
+ import { AgentStatus } from "../../adapters/AgentAdapter.js";
8
+ import { AgentRegistry } from "../../utils/AgentRegistry.js";
9
+ import { listAgentProcesses, enrichProcesses, captureProcessSnapshot } from "../../utils/process.js";
10
+ import { matchProcessesToSessions, generateAgentName } from "../../utils/matching.js";
11
+ import * as crypto from "crypto";
12
+ vi.mock("../../utils/process.js", async (importOriginal)=>{
13
13
  const actual = await importOriginal();
14
14
  return {
15
15
  ...actual,
@@ -18,7 +18,7 @@ vi.mock('../../utils/process.js', async (importOriginal)=>{
18
18
  captureProcessSnapshot: vi.fn()
19
19
  };
20
20
  });
21
- vi.mock('../../utils/matching.js', ()=>({
21
+ vi.mock("../../utils/matching.js", ()=>({
22
22
  matchProcessesToSessions: vi.fn(),
23
23
  generateAgentName: vi.fn()
24
24
  }));
@@ -27,13 +27,13 @@ const mockedEnrichProcesses = enrichProcesses;
27
27
  const mockedCaptureProcessSnapshot = captureProcessSnapshot;
28
28
  const mockedMatchProcessesToSessions = matchProcessesToSessions;
29
29
  const mockedGenerateAgentName = generateAgentName;
30
- describe('GeminiCliAdapter', ()=>{
30
+ describe("GeminiCliAdapter", ()=>{
31
31
  let adapter;
32
32
  let tmpHome;
33
33
  beforeEach(()=>{
34
- tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-adapter-test-'));
34
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "gemini-adapter-test-"));
35
35
  process.env.HOME = tmpHome;
36
- adapter = new GeminiCliAdapter(new AgentRegistry(path.join(tmpHome, 'agents.json')));
36
+ adapter = new GeminiCliAdapter(new AgentRegistry(path.join(tmpHome, "agents.json")));
37
37
  mockedListAgentProcesses.mockReset();
38
38
  mockedEnrichProcesses.mockReset();
39
39
  mockedCaptureProcessSnapshot.mockReset();
@@ -44,7 +44,7 @@ describe('GeminiCliAdapter', ()=>{
44
44
  mockedCaptureProcessSnapshot.mockImplementation(async (names)=>enrichProcesses(names.flatMap((name)=>listAgentProcesses(name))));
45
45
  mockedMatchProcessesToSessions.mockReturnValue([]);
46
46
  mockedGenerateAgentName.mockImplementation((cwd, pid)=>{
47
- const folder = path.basename(cwd) || 'unknown';
47
+ const folder = path.basename(cwd) || "unknown";
48
48
  return `${folder} (${pid})`;
49
49
  });
50
50
  });
@@ -54,89 +54,89 @@ describe('GeminiCliAdapter', ()=>{
54
54
  force: true
55
55
  });
56
56
  });
57
- describe('initialization', ()=>{
58
- it('should expose gemini_cli type', ()=>{
59
- expect(adapter.type).toBe('gemini_cli');
57
+ describe("initialization", ()=>{
58
+ it("should expose gemini_cli type", ()=>{
59
+ expect(adapter.type).toBe("gemini_cli");
60
60
  });
61
61
  });
62
- describe('canHandle', ()=>{
63
- it('should return true for plain gemini command', ()=>{
62
+ describe("canHandle", ()=>{
63
+ it("should return true for plain gemini command", ()=>{
64
64
  expect(adapter.canHandle({
65
65
  pid: 1,
66
- command: 'gemini',
67
- cwd: '/repo',
68
- tty: 'ttys001'
66
+ command: "gemini",
67
+ cwd: "/repo",
68
+ tty: "ttys001"
69
69
  })).toBe(true);
70
70
  });
71
- it('should return true for gemini with full path (case-insensitive)', ()=>{
71
+ it("should return true for gemini with full path (case-insensitive)", ()=>{
72
72
  expect(adapter.canHandle({
73
73
  pid: 2,
74
- command: '/usr/local/bin/GEMINI --yolo',
75
- cwd: '/repo',
76
- tty: 'ttys002'
74
+ command: "/usr/local/bin/GEMINI --yolo",
75
+ cwd: "/repo",
76
+ tty: "ttys002"
77
77
  })).toBe(true);
78
78
  });
79
- it('should return false for non-gemini processes', ()=>{
79
+ it("should return false for non-gemini processes", ()=>{
80
80
  expect(adapter.canHandle({
81
81
  pid: 3,
82
- command: 'node app.js',
83
- cwd: '/repo',
84
- tty: 'ttys003'
82
+ command: "node app.js",
83
+ cwd: "/repo",
84
+ tty: "ttys003"
85
85
  })).toBe(false);
86
86
  });
87
87
  it('should return false when "gemini" appears only in path arguments', ()=>{
88
88
  expect(adapter.canHandle({
89
89
  pid: 4,
90
- command: 'node /path/to/gemini-runner.js',
91
- cwd: '/repo',
92
- tty: 'ttys004'
90
+ command: "node /path/to/gemini-runner.js",
91
+ cwd: "/repo",
92
+ tty: "ttys004"
93
93
  })).toBe(false);
94
94
  });
95
- it('should return true for Node-invoked gemini script (real install layout)', ()=>{
95
+ it("should return true for Node-invoked gemini script (real install layout)", ()=>{
96
96
  expect(adapter.canHandle({
97
97
  pid: 5,
98
- command: 'node /Users/foo/.volta/tools/image/node/24.14.0/bin/gemini --help',
99
- cwd: '/repo',
100
- tty: 'ttys005'
98
+ command: "node /Users/foo/.volta/tools/image/node/24.14.0/bin/gemini --help",
99
+ cwd: "/repo",
100
+ tty: "ttys005"
101
101
  })).toBe(true);
102
102
  });
103
- it('should return true for Node-invoked gemini.js bundle entrypoint', ()=>{
103
+ it("should return true for Node-invoked gemini.js bundle entrypoint", ()=>{
104
104
  expect(adapter.canHandle({
105
105
  pid: 6,
106
- command: 'node /opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/gemini.js',
107
- cwd: '/repo',
108
- tty: 'ttys006'
106
+ command: "node /opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/gemini.js",
107
+ cwd: "/repo",
108
+ tty: "ttys006"
109
109
  })).toBe(true);
110
110
  });
111
- it('should recognize Windows executable and entrypoint paths', ()=>{
111
+ it("should recognize Windows executable and entrypoint paths", ()=>{
112
112
  expect(adapter.canHandle({
113
113
  pid: 7,
114
- command: 'C:\\tools\\node.exe C:\\lib\\gemini.js',
115
- cwd: 'C:\\repo',
116
- tty: ''
114
+ command: "C:\\tools\\node.exe C:\\lib\\gemini.js",
115
+ cwd: "C:\\repo",
116
+ tty: ""
117
117
  })).toBe(true);
118
118
  });
119
119
  });
120
- describe('detectAgents', ()=>{
121
- it('should return empty array when no gemini processes are running', async ()=>{
120
+ describe("detectAgents", ()=>{
121
+ it("should return empty array when no gemini processes are running", async ()=>{
122
122
  mockedListAgentProcesses.mockReturnValue([]);
123
123
  const agents = await adapter.detectAgents();
124
124
  expect(agents).toEqual([]);
125
125
  });
126
- it('should filter non-gemini Node processes out of the node process pool', async ()=>{
126
+ it("should filter non-gemini Node processes out of the node process pool", async ()=>{
127
127
  const geminiProc = {
128
128
  pid: 100,
129
- command: 'node /Users/foo/.volta/tools/image/node/24.14.0/bin/gemini --help',
130
- cwd: '/repo',
131
- tty: 'ttys001',
132
- startTime: new Date('2026-04-18T00:00:00Z')
129
+ command: "node /Users/foo/.volta/tools/image/node/24.14.0/bin/gemini --help",
130
+ cwd: "/repo",
131
+ tty: "ttys001",
132
+ startTime: new Date("2026-04-18T00:00:00Z")
133
133
  };
134
134
  const unrelatedNodeProc = {
135
135
  pid: 200,
136
- command: 'node /usr/local/bin/eslint src/',
137
- cwd: '/other-repo',
138
- tty: 'ttys002',
139
- startTime: new Date('2026-04-18T00:00:00Z')
136
+ command: "node /usr/local/bin/eslint src/",
137
+ cwd: "/other-repo",
138
+ tty: "ttys002",
139
+ startTime: new Date("2026-04-18T00:00:00Z")
140
140
  };
141
141
  mockedListAgentProcesses.mockReturnValue([
142
142
  geminiProc,
@@ -146,13 +146,13 @@ describe('GeminiCliAdapter', ()=>{
146
146
  expect(agents).toHaveLength(1);
147
147
  expect(agents[0].pid).toBe(100);
148
148
  });
149
- it('should return process-only agents when no session files exist for the process', async ()=>{
149
+ it("should return process-only agents when no session files exist for the process", async ()=>{
150
150
  const proc = {
151
151
  pid: 1234,
152
- command: 'node /usr/local/bin/gemini',
153
- cwd: '/repo',
154
- tty: 'ttys001',
155
- startTime: new Date('2026-04-18T00:00:00Z')
152
+ command: "node /usr/local/bin/gemini",
153
+ cwd: "/repo",
154
+ tty: "ttys001",
155
+ startTime: new Date("2026-04-18T00:00:00Z")
156
156
  };
157
157
  mockedListAgentProcesses.mockReturnValue([
158
158
  proc
@@ -160,29 +160,29 @@ describe('GeminiCliAdapter', ()=>{
160
160
  const agents = await adapter.detectAgents();
161
161
  expect(agents).toHaveLength(1);
162
162
  expect(agents[0]).toMatchObject({
163
- type: 'gemini_cli',
163
+ type: "gemini_cli",
164
164
  pid: 1234,
165
- projectPath: '/repo',
165
+ projectPath: "/repo",
166
166
  status: AgentStatus.RUNNING,
167
- sessionId: 'pid-1234'
167
+ sessionId: "pid-1234"
168
168
  });
169
169
  });
170
- it('should suppress Gemini wrapper process-only agents before a session file exists', async ()=>{
170
+ it("should suppress Gemini wrapper process-only agents before a session file exists", async ()=>{
171
171
  const wrapperProc = {
172
172
  pid: 20452,
173
173
  ppid: 17530,
174
- command: '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini',
175
- cwd: '/repo',
176
- tty: 'ttys007',
177
- startTime: new Date('2026-06-13T08:25:21Z')
174
+ command: "/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini",
175
+ cwd: "/repo",
176
+ tty: "ttys007",
177
+ startTime: new Date("2026-06-13T08:25:21Z")
178
178
  };
179
179
  const childProc = {
180
180
  pid: 21373,
181
181
  ppid: 20452,
182
- command: '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini',
183
- cwd: '/repo',
184
- tty: 'ttys007',
185
- startTime: new Date('2026-06-13T08:25:26Z')
182
+ command: "/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini",
183
+ cwd: "/repo",
184
+ tty: "ttys007",
185
+ startTime: new Date("2026-06-13T08:25:26Z")
186
186
  };
187
187
  mockedListAgentProcesses.mockReturnValue([
188
188
  wrapperProc,
@@ -192,39 +192,42 @@ describe('GeminiCliAdapter', ()=>{
192
192
  expect(agents).toHaveLength(1);
193
193
  expect(agents[0]).toMatchObject({
194
194
  pid: 21373,
195
- sessionId: 'pid-21373',
196
- summary: 'Gemini CLI process running'
195
+ sessionId: "pid-21373",
196
+ summary: "Gemini CLI process running"
197
197
  });
198
198
  });
199
- it('should carry the managed wrapper name to a process-only child before a session file exists', async ()=>{
200
- const regPath = path.join(tmpHome, 'agents.json');
199
+ it("should carry the managed wrapper name to a process-only child before a session file exists", async ()=>{
200
+ const regPath = path.join(tmpHome, "agents.json");
201
201
  const registry = new AgentRegistry(regPath);
202
202
  const namedAdapter = new GeminiCliAdapter(registry);
203
203
  const wrapperProc = {
204
204
  pid: 35792,
205
205
  ppid: 33068,
206
- command: '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini',
207
- cwd: '/repo',
208
- tty: 'ttys002',
209
- startTime: new Date('2026-06-13T19:15:16Z')
206
+ command: "/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini",
207
+ cwd: "/repo",
208
+ tty: "ttys002",
209
+ startTime: new Date("2026-06-13T19:15:16Z")
210
210
  };
211
211
  const childProc = {
212
212
  pid: 36514,
213
213
  ppid: 35792,
214
- command: '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini',
215
- cwd: '/repo',
216
- tty: 'ttys002',
217
- startTime: new Date('2026-06-13T19:15:18Z')
214
+ command: "/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini",
215
+ cwd: "/repo",
216
+ tty: "ttys002",
217
+ startTime: new Date("2026-06-13T19:15:18Z")
218
218
  };
219
219
  registry.register({
220
- name: 'cli-mqcqj469',
221
- type: 'gemini_cli',
220
+ name: "cli-mqcqj469",
221
+ type: "gemini_cli",
222
222
  pid: wrapperProc.pid,
223
- tmuxSession: 'cli-mqcqj469',
223
+ runtime: "tmux",
224
+ runtimeRef: {
225
+ session: "cli-mqcqj469"
226
+ },
224
227
  cwd: wrapperProc.cwd,
225
- startedAt: '2026-06-13T19:15:16.211Z',
228
+ startedAt: "2026-06-13T19:15:16.211Z",
226
229
  sessionId: `pid-${wrapperProc.pid}`,
227
- sessionFilePath: ''
230
+ sessionFilePath: ""
228
231
  });
229
232
  mockedListAgentProcesses.mockReturnValue([
230
233
  wrapperProc,
@@ -233,42 +236,42 @@ describe('GeminiCliAdapter', ()=>{
233
236
  const agents = await namedAdapter.detectAgents();
234
237
  expect(agents).toHaveLength(1);
235
238
  expect(agents[0]).toMatchObject({
236
- name: 'cli-mqcqj469',
239
+ name: "cli-mqcqj469",
237
240
  pid: childProc.pid,
238
241
  sessionId: `pid-${childProc.pid}`
239
242
  });
240
243
  });
241
- it('should map a process to its matching session file via projectHash', async ()=>{
242
- const cwd = '/repo/project-a';
244
+ it("should map a process to its matching session file via projectHash", async ()=>{
245
+ const cwd = "/repo/project-a";
243
246
  const projectHash = hashProjectRoot(cwd);
244
- const shortId = 'abc123';
245
- const chatsDir = path.join(tmpHome, '.gemini', 'tmp', shortId, 'chats');
247
+ const shortId = "abc123";
248
+ const chatsDir = path.join(tmpHome, ".gemini", "tmp", shortId, "chats");
246
249
  fs.mkdirSync(chatsDir, {
247
250
  recursive: true
248
251
  });
249
- const sessionPath = path.join(chatsDir, 'session-2026-04-18T00-00-session1.json');
250
- const sessionStart = new Date('2026-04-18T00:00:00Z').toISOString();
252
+ const sessionPath = path.join(chatsDir, "session-2026-04-18T00-00-session1.json");
253
+ const sessionStart = new Date("2026-04-18T00:00:00Z").toISOString();
251
254
  fs.writeFileSync(sessionPath, JSON.stringify({
252
- sessionId: 'session1',
255
+ sessionId: "session1",
253
256
  projectHash,
254
257
  startTime: sessionStart,
255
258
  lastUpdated: sessionStart,
256
- kind: 'main',
259
+ kind: "main",
257
260
  messages: [
258
261
  {
259
- id: 'm1',
262
+ id: "m1",
260
263
  timestamp: sessionStart,
261
- type: 'user',
262
- content: 'hello gemini'
264
+ type: "user",
265
+ content: "hello gemini"
263
266
  }
264
267
  ]
265
268
  }));
266
269
  const proc = {
267
270
  pid: 42,
268
- command: 'node /usr/local/bin/gemini',
271
+ command: "node /usr/local/bin/gemini",
269
272
  cwd,
270
- tty: 'ttys001',
271
- startTime: new Date('2026-04-18T00:00:00Z')
273
+ tty: "ttys001",
274
+ startTime: new Date("2026-04-18T00:00:00Z")
272
275
  };
273
276
  mockedListAgentProcesses.mockReturnValue([
274
277
  proc
@@ -277,7 +280,7 @@ describe('GeminiCliAdapter', ()=>{
277
280
  {
278
281
  process: proc,
279
282
  session: {
280
- sessionId: 'session1',
283
+ sessionId: "session1",
281
284
  filePath: sessionPath,
282
285
  projectDir: chatsDir,
283
286
  birthtimeMs: Date.now(),
@@ -289,55 +292,55 @@ describe('GeminiCliAdapter', ()=>{
289
292
  const agents = await adapter.detectAgents();
290
293
  expect(agents).toHaveLength(1);
291
294
  expect(agents[0]).toMatchObject({
292
- type: 'gemini_cli',
295
+ type: "gemini_cli",
293
296
  pid: 42,
294
297
  projectPath: cwd,
295
- sessionId: 'session1',
298
+ sessionId: "session1",
296
299
  sessionFilePath: sessionPath
297
300
  });
298
- expect(agents[0].summary).toContain('hello gemini');
301
+ expect(agents[0].summary).toContain("hello gemini");
299
302
  });
300
- it('should collapse Gemini wrapper and child processes matched to the same session', async ()=>{
301
- const cwd = '/repo/project-a';
303
+ it("should collapse Gemini wrapper and child processes matched to the same session", async ()=>{
304
+ const cwd = "/repo/project-a";
302
305
  const projectHash = hashProjectRoot(cwd);
303
- const shortId = 'abc123';
304
- const chatsDir = path.join(tmpHome, '.gemini', 'tmp', shortId, 'chats');
306
+ const shortId = "abc123";
307
+ const chatsDir = path.join(tmpHome, ".gemini", "tmp", shortId, "chats");
305
308
  fs.mkdirSync(chatsDir, {
306
309
  recursive: true
307
310
  });
308
- const sessionPath = path.join(chatsDir, 'session-2026-04-18T00-00-session1.json');
309
- const sessionStart = new Date('2026-04-18T00:00:00Z').toISOString();
311
+ const sessionPath = path.join(chatsDir, "session-2026-04-18T00-00-session1.json");
312
+ const sessionStart = new Date("2026-04-18T00:00:00Z").toISOString();
310
313
  fs.writeFileSync(sessionPath, JSON.stringify({
311
- sessionId: 'session1',
314
+ sessionId: "session1",
312
315
  projectHash,
313
316
  startTime: sessionStart,
314
317
  lastUpdated: sessionStart,
315
- kind: 'main',
318
+ kind: "main",
316
319
  directories: [
317
320
  cwd
318
321
  ],
319
322
  messages: [
320
323
  {
321
- id: 'm1',
324
+ id: "m1",
322
325
  timestamp: sessionStart,
323
- type: 'user',
324
- content: 'hello gemini'
326
+ type: "user",
327
+ content: "hello gemini"
325
328
  }
326
329
  ]
327
330
  }));
328
331
  const wrapperProc = {
329
332
  pid: 88394,
330
- command: 'node /Users/foo/.nvm/versions/node/v23.9.0/bin/gemini',
333
+ command: "node /Users/foo/.nvm/versions/node/v23.9.0/bin/gemini",
331
334
  cwd,
332
- tty: 'ttys015',
333
- startTime: new Date('2026-04-18T00:00:00Z')
335
+ tty: "ttys015",
336
+ startTime: new Date("2026-04-18T00:00:00Z")
334
337
  };
335
338
  const childProc = {
336
339
  pid: 88460,
337
- command: '/Users/foo/.nvm/versions/node/v24.13.1/bin/node /Users/foo/.nvm/versions/node/v23.9.0/bin/gemini',
340
+ command: "/Users/foo/.nvm/versions/node/v24.13.1/bin/node /Users/foo/.nvm/versions/node/v23.9.0/bin/gemini",
338
341
  cwd,
339
- tty: 'ttys015',
340
- startTime: new Date('2026-04-18T00:00:01Z')
342
+ tty: "ttys015",
343
+ startTime: new Date("2026-04-18T00:00:01Z")
341
344
  };
342
345
  mockedListAgentProcesses.mockReturnValue([
343
346
  wrapperProc,
@@ -347,7 +350,7 @@ describe('GeminiCliAdapter', ()=>{
347
350
  {
348
351
  process: wrapperProc,
349
352
  session: {
350
- sessionId: 'session1',
353
+ sessionId: "session1",
351
354
  filePath: sessionPath,
352
355
  projectDir: chatsDir,
353
356
  birthtimeMs: Date.now(),
@@ -358,7 +361,7 @@ describe('GeminiCliAdapter', ()=>{
358
361
  {
359
362
  process: childProc,
360
363
  session: {
361
- sessionId: 'session1',
364
+ sessionId: "session1",
362
365
  filePath: sessionPath,
363
366
  projectDir: chatsDir,
364
367
  birthtimeMs: Date.now(),
@@ -371,32 +374,32 @@ describe('GeminiCliAdapter', ()=>{
371
374
  expect(agents).toHaveLength(1);
372
375
  expect(agents[0]).toMatchObject({
373
376
  pid: 88460,
374
- sessionId: 'session1',
377
+ sessionId: "session1",
375
378
  sessionFilePath: sessionPath
376
379
  });
377
380
  });
378
- it('should not match sessions from other projects', async ()=>{
379
- const procCwd = '/repo/project-a';
380
- const otherCwd = '/repo/project-b';
381
+ it("should not match sessions from other projects", async ()=>{
382
+ const procCwd = "/repo/project-a";
383
+ const otherCwd = "/repo/project-b";
381
384
  const otherHash = hashProjectRoot(otherCwd);
382
- const chatsDir = path.join(tmpHome, '.gemini', 'tmp', 'other', 'chats');
385
+ const chatsDir = path.join(tmpHome, ".gemini", "tmp", "other", "chats");
383
386
  fs.mkdirSync(chatsDir, {
384
387
  recursive: true
385
388
  });
386
- const sessionPath = path.join(chatsDir, 'session-2026-04-18T00-00-other.json');
389
+ const sessionPath = path.join(chatsDir, "session-2026-04-18T00-00-other.json");
387
390
  fs.writeFileSync(sessionPath, JSON.stringify({
388
- sessionId: 'other-session',
391
+ sessionId: "other-session",
389
392
  projectHash: otherHash,
390
393
  startTime: new Date().toISOString(),
391
394
  lastUpdated: new Date().toISOString(),
392
- kind: 'main',
395
+ kind: "main",
393
396
  messages: []
394
397
  }));
395
398
  const proc = {
396
399
  pid: 7,
397
- command: 'node /usr/local/bin/gemini',
400
+ command: "node /usr/local/bin/gemini",
398
401
  cwd: procCwd,
399
- tty: 'ttys001',
402
+ tty: "ttys001",
400
403
  startTime: new Date()
401
404
  };
402
405
  mockedListAgentProcesses.mockReturnValue([
@@ -409,55 +412,56 @@ describe('GeminiCliAdapter', ()=>{
409
412
  expect(agents[0].sessionId).toBe(`pid-${proc.pid}`);
410
413
  });
411
414
  });
412
- describe('detectAgents — registry cache short-circuit', ()=>{
415
+ describe("detectAgents — registry cache short-circuit", ()=>{
413
416
  let regPath;
414
417
  let registry;
415
418
  let cachedAdapter;
416
419
  let sessionFilePath;
417
420
  function registerEntry(over = {}) {
418
421
  registry.register({
419
- name: 'gemini-100',
420
- type: 'gemini_cli',
422
+ name: "gemini-100",
423
+ type: "gemini_cli",
421
424
  pid: 100,
422
- tmuxSession: '',
423
- cwd: '/repo-a',
424
- startedAt: '2026-05-30T00:00:00.000Z',
425
- sessionId: 's-cached',
425
+ runtime: "tmux",
426
+ runtimeRef: null,
427
+ cwd: "/repo-a",
428
+ startedAt: "2026-05-30T00:00:00.000Z",
429
+ sessionId: "s-cached",
426
430
  sessionFilePath,
427
431
  ...over
428
432
  });
429
433
  }
430
434
  beforeEach(()=>{
431
- regPath = path.join(tmpHome, 'agents.json');
435
+ regPath = path.join(tmpHome, "agents.json");
432
436
  registry = new AgentRegistry(regPath);
433
437
  cachedAdapter = new GeminiCliAdapter(registry);
434
438
  const now = new Date().toISOString();
435
- sessionFilePath = path.join(tmpHome, 'gemini-session.json');
439
+ sessionFilePath = path.join(tmpHome, "gemini-session.json");
436
440
  fs.writeFileSync(sessionFilePath, JSON.stringify({
437
- sessionId: 's-cached',
438
- projectHash: 'h',
441
+ sessionId: "s-cached",
442
+ projectHash: "h",
439
443
  startTime: now,
440
444
  lastUpdated: now,
441
445
  directories: [
442
- '/repo-a'
446
+ "/repo-a"
443
447
  ],
444
448
  messages: [
445
449
  {
446
- id: 'm1',
450
+ id: "m1",
447
451
  timestamp: now,
448
- type: 'user',
449
- content: 'Hello from gemini cache'
452
+ type: "user",
453
+ content: "Hello from gemini cache"
450
454
  }
451
455
  ]
452
456
  }));
453
457
  });
454
- it('short-circuits matching when registry has a valid entry', async ()=>{
458
+ it("short-circuits matching when registry has a valid entry", async ()=>{
455
459
  registerEntry();
456
460
  const proc = {
457
461
  pid: 100,
458
- command: 'node /path/to/gemini --help',
459
- cwd: '/repo-a',
460
- tty: 'ttys001',
462
+ command: "node /path/to/gemini --help",
463
+ cwd: "/repo-a",
464
+ tty: "ttys001",
461
465
  startTime: new Date()
462
466
  };
463
467
  mockedListAgentProcesses.mockReturnValue([
@@ -466,101 +470,103 @@ describe('GeminiCliAdapter', ()=>{
466
470
  const agents = await cachedAdapter.detectAgents();
467
471
  expect(agents).toHaveLength(1);
468
472
  expect(agents[0]).toMatchObject({
469
- type: 'gemini_cli',
473
+ type: "gemini_cli",
470
474
  pid: 100,
471
- sessionId: 's-cached',
472
- summary: 'Hello from gemini cache'
475
+ sessionId: "s-cached",
476
+ summary: "Hello from gemini cache"
473
477
  });
474
478
  expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
475
479
  });
476
- it('falls through when no registry entry exists for the pid', async ()=>{
480
+ it("falls through when no registry entry exists for the pid", async ()=>{
477
481
  const proc = {
478
482
  pid: 100,
479
- command: 'node /path/to/gemini --help',
480
- cwd: '/repo-a',
481
- tty: 'ttys001',
483
+ command: "node /path/to/gemini --help",
484
+ cwd: "/repo-a",
485
+ tty: "ttys001",
482
486
  startTime: new Date()
483
487
  };
484
488
  mockedListAgentProcesses.mockReturnValue([
485
489
  proc
486
490
  ]);
487
491
  const agents = await cachedAdapter.detectAgents();
488
- expect(agents[0].sessionId).toBe('pid-100');
492
+ expect(agents[0].sessionId).toBe("pid-100");
489
493
  });
490
- it('falls through when registry entry type does not match', async ()=>{
494
+ it("falls through when registry entry type does not match", async ()=>{
491
495
  registerEntry({
492
- type: 'claude'
496
+ type: "claude"
493
497
  });
494
498
  const proc = {
495
499
  pid: 100,
496
- command: 'node /path/to/gemini --help',
497
- cwd: '/repo-a',
498
- tty: 'ttys001',
500
+ command: "node /path/to/gemini --help",
501
+ cwd: "/repo-a",
502
+ tty: "ttys001",
499
503
  startTime: new Date()
500
504
  };
501
505
  mockedListAgentProcesses.mockReturnValue([
502
506
  proc
503
507
  ]);
504
508
  const agents = await cachedAdapter.detectAgents();
505
- expect(agents[0].sessionId).toBe('pid-100');
509
+ expect(agents[0].sessionId).toBe("pid-100");
506
510
  });
507
- it('falls through when the cached session file no longer exists', async ()=>{
511
+ it("falls through when the cached session file no longer exists", async ()=>{
508
512
  registerEntry({
509
- sessionFilePath: path.join(tmpHome, 'deleted.json')
513
+ sessionFilePath: path.join(tmpHome, "deleted.json")
510
514
  });
511
515
  const proc = {
512
516
  pid: 100,
513
- command: 'node /path/to/gemini --help',
514
- cwd: '/repo-a',
515
- tty: 'ttys001',
517
+ command: "node /path/to/gemini --help",
518
+ cwd: "/repo-a",
519
+ tty: "ttys001",
516
520
  startTime: new Date()
517
521
  };
518
522
  mockedListAgentProcesses.mockReturnValue([
519
523
  proc
520
524
  ]);
521
525
  const agents = await cachedAdapter.detectAgents();
522
- expect(agents[0].sessionId).toBe('pid-100');
526
+ expect(agents[0].sessionId).toBe("pid-100");
523
527
  });
524
- it('carries the managed wrapper name to the detected child process', async ()=>{
528
+ it("carries the managed wrapper name to the detected child process", async ()=>{
525
529
  const wrapperProc = {
526
530
  pid: 20339,
527
531
  ppid: 17570,
528
- command: '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini',
529
- cwd: '/repo-a',
530
- tty: 'ttys002',
531
- startTime: new Date('2026-06-13T19:00:53Z')
532
+ command: "/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini",
533
+ cwd: "/repo-a",
534
+ tty: "ttys002",
535
+ startTime: new Date("2026-06-13T19:00:53Z")
532
536
  };
533
537
  const childProc = {
534
538
  pid: 21038,
535
539
  ppid: 20339,
536
- command: '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini',
537
- cwd: '/repo-a',
538
- tty: 'ttys002',
539
- startTime: new Date('2026-06-13T19:00:57Z')
540
+ command: "/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini",
541
+ cwd: "/repo-a",
542
+ tty: "ttys002",
543
+ startTime: new Date("2026-06-13T19:00:57Z")
540
544
  };
541
545
  const now = new Date().toISOString();
542
- sessionFilePath = writeSession(tmpHome, 'cli-2', 'session-2026-06-13T19-00-s-cached', {
543
- sessionId: 's-cached',
544
- projectHash: hashProjectRoot('/repo-a'),
546
+ sessionFilePath = writeSession(tmpHome, "cli-2", "session-2026-06-13T19-00-s-cached", {
547
+ sessionId: "s-cached",
548
+ projectHash: hashProjectRoot("/repo-a"),
545
549
  startTime: now,
546
550
  lastUpdated: now,
547
551
  directories: [
548
- '/repo-a'
552
+ "/repo-a"
549
553
  ],
550
554
  messages: [
551
555
  {
552
- id: 'm1',
556
+ id: "m1",
553
557
  timestamp: now,
554
- type: 'user',
555
- content: 'Hello from child process'
558
+ type: "user",
559
+ content: "Hello from child process"
556
560
  }
557
561
  ]
558
562
  });
559
563
  registerEntry({
560
- name: 'cli-mqcq0mg5',
564
+ name: "cli-mqcq0mg5",
561
565
  pid: wrapperProc.pid,
562
- tmuxSession: 'cli-mqcq0mg5',
563
- sessionId: 's-cached',
566
+ runtimeRef: {
567
+ session: "cli-mqcq0mg5"
568
+ },
569
+ sessionId: "s-cached",
564
570
  sessionFilePath
565
571
  });
566
572
  mockedListAgentProcesses.mockReturnValue([
@@ -571,11 +577,11 @@ describe('GeminiCliAdapter', ()=>{
571
577
  {
572
578
  process: childProc,
573
579
  session: {
574
- sessionId: 's-cached',
580
+ sessionId: "s-cached",
575
581
  filePath: sessionFilePath,
576
582
  projectDir: path.dirname(sessionFilePath),
577
583
  birthtimeMs: Date.now(),
578
- resolvedCwd: '/repo-a'
584
+ resolvedCwd: "/repo-a"
579
585
  },
580
586
  deltaMs: 0
581
587
  }
@@ -583,19 +589,19 @@ describe('GeminiCliAdapter', ()=>{
583
589
  const agents = await cachedAdapter.detectAgents();
584
590
  expect(agents).toHaveLength(1);
585
591
  expect(agents[0]).toMatchObject({
586
- name: 'cli-mqcq0mg5',
592
+ name: "cli-mqcq0mg5",
587
593
  pid: childProc.pid,
588
- sessionId: 's-cached'
594
+ sessionId: "s-cached"
589
595
  });
590
596
  });
591
597
  });
592
- describe('discoverSessions', ()=>{
593
- it('should return empty when ~/.gemini/tmp does not exist', ()=>{
598
+ describe("discoverSessions", ()=>{
599
+ it("should return empty when ~/.gemini/tmp does not exist", ()=>{
594
600
  const proc = {
595
601
  pid: 1,
596
- command: 'gemini',
597
- cwd: '/repo',
598
- tty: 'ttys001',
602
+ command: "gemini",
603
+ cwd: "/repo",
604
+ tty: "ttys001",
599
605
  startTime: new Date()
600
606
  };
601
607
  // tmp dir absent by default
@@ -605,20 +611,20 @@ describe('GeminiCliAdapter', ()=>{
605
611
  expect(result.sessions).toEqual([]);
606
612
  expect(result.contentCache.size).toBe(0);
607
613
  });
608
- it('should skip processes with empty cwd when building the hash map', ()=>{
614
+ it("should skip processes with empty cwd when building the hash map", ()=>{
609
615
  const proc = {
610
616
  pid: 1,
611
- command: 'gemini',
612
- cwd: '',
613
- tty: 'ttys001',
617
+ command: "gemini",
618
+ cwd: "",
619
+ tty: "ttys001",
614
620
  startTime: new Date()
615
621
  };
616
- writeSession(tmpHome, 'abc', 'session-x', {
617
- sessionId: 's1',
618
- projectHash: hashProjectRoot('/some/where'),
622
+ writeSession(tmpHome, "abc", "session-x", {
623
+ sessionId: "s1",
624
+ projectHash: hashProjectRoot("/some/where"),
619
625
  startTime: new Date().toISOString(),
620
626
  lastUpdated: new Date().toISOString(),
621
- kind: 'main',
627
+ kind: "main",
622
628
  messages: []
623
629
  });
624
630
  const result = adapter.discoverSessions([
@@ -626,20 +632,20 @@ describe('GeminiCliAdapter', ()=>{
626
632
  ]);
627
633
  expect(result.sessions).toEqual([]);
628
634
  });
629
- it('should ignore sessions whose projectHash does not match any process cwd', ()=>{
635
+ it("should ignore sessions whose projectHash does not match any process cwd", ()=>{
630
636
  const proc = {
631
637
  pid: 1,
632
- command: 'gemini',
633
- cwd: '/repo/a',
634
- tty: 'ttys001',
638
+ command: "gemini",
639
+ cwd: "/repo/a",
640
+ tty: "ttys001",
635
641
  startTime: new Date()
636
642
  };
637
- writeSession(tmpHome, 'other', 'session-other', {
638
- sessionId: 's-other',
639
- projectHash: hashProjectRoot('/repo/different'),
643
+ writeSession(tmpHome, "other", "session-other", {
644
+ sessionId: "s-other",
645
+ projectHash: hashProjectRoot("/repo/different"),
640
646
  startTime: new Date().toISOString(),
641
647
  lastUpdated: new Date().toISOString(),
642
- kind: 'main',
648
+ kind: "main",
643
649
  messages: []
644
650
  });
645
651
  const result = adapter.discoverSessions([
@@ -647,52 +653,52 @@ describe('GeminiCliAdapter', ()=>{
647
653
  ]);
648
654
  expect(result.sessions).toEqual([]);
649
655
  });
650
- it('should skip malformed JSON files and still return valid ones', ()=>{
651
- const cwd = '/repo/valid';
656
+ it("should skip malformed JSON files and still return valid ones", ()=>{
657
+ const cwd = "/repo/valid";
652
658
  const proc = {
653
659
  pid: 1,
654
- command: 'gemini',
660
+ command: "gemini",
655
661
  cwd,
656
- tty: 'ttys001',
662
+ tty: "ttys001",
657
663
  startTime: new Date()
658
664
  };
659
- const chatsDir = path.join(tmpHome, '.gemini', 'tmp', 'abc', 'chats');
665
+ const chatsDir = path.join(tmpHome, ".gemini", "tmp", "abc", "chats");
660
666
  fs.mkdirSync(chatsDir, {
661
667
  recursive: true
662
668
  });
663
- fs.writeFileSync(path.join(chatsDir, 'session-bad.json'), '{ not valid');
664
- writeSession(tmpHome, 'abc', 'session-good', {
665
- sessionId: 's-good',
669
+ fs.writeFileSync(path.join(chatsDir, "session-bad.json"), "{ not valid");
670
+ writeSession(tmpHome, "abc", "session-good", {
671
+ sessionId: "s-good",
666
672
  projectHash: hashProjectRoot(cwd),
667
673
  startTime: new Date().toISOString(),
668
674
  lastUpdated: new Date().toISOString(),
669
- kind: 'main',
675
+ kind: "main",
670
676
  messages: []
671
677
  });
672
678
  const result = adapter.discoverSessions([
673
679
  proc
674
680
  ]);
675
681
  expect(result.sessions).toHaveLength(1);
676
- expect(result.sessions[0].sessionId).toBe('s-good');
682
+ expect(result.sessions[0].sessionId).toBe("s-good");
677
683
  });
678
- it('should match sessions whose projectHash is a parent of the process cwd (git root case)', ()=>{
679
- const gitRoot = '/repo/monorepo';
680
- const procCwd = '/repo/monorepo/packages/inner';
684
+ it("should match sessions whose projectHash is a parent of the process cwd (git root case)", ()=>{
685
+ const gitRoot = "/repo/monorepo";
686
+ const procCwd = "/repo/monorepo/packages/inner";
681
687
  const proc = {
682
688
  pid: 1,
683
- command: 'gemini',
689
+ command: "gemini",
684
690
  cwd: procCwd,
685
- tty: 'ttys001',
691
+ tty: "ttys001",
686
692
  startTime: new Date()
687
693
  };
688
- writeSession(tmpHome, 'abc', 'session-rootmatch', {
689
- sessionId: 's-root',
694
+ writeSession(tmpHome, "abc", "session-rootmatch", {
695
+ sessionId: "s-root",
690
696
  // Gemini CLI stores the hash of the walked-up project root,
691
697
  // not the process CWD.
692
698
  projectHash: hashProjectRoot(gitRoot),
693
699
  startTime: new Date().toISOString(),
694
700
  lastUpdated: new Date().toISOString(),
695
- kind: 'main',
701
+ kind: "main",
696
702
  messages: []
697
703
  });
698
704
  const result = adapter.discoverSessions([
@@ -702,20 +708,20 @@ describe('GeminiCliAdapter', ()=>{
702
708
  expect(result.sessions[0].resolvedCwd).toBe(procCwd);
703
709
  });
704
710
  it('should skip files that do not start with "session-"', ()=>{
705
- const cwd = '/repo/keep';
711
+ const cwd = "/repo/keep";
706
712
  const proc = {
707
713
  pid: 1,
708
- command: 'gemini',
714
+ command: "gemini",
709
715
  cwd,
710
- tty: 'ttys001',
716
+ tty: "ttys001",
711
717
  startTime: new Date()
712
718
  };
713
- const chatsDir = path.join(tmpHome, '.gemini', 'tmp', 'abc', 'chats');
719
+ const chatsDir = path.join(tmpHome, ".gemini", "tmp", "abc", "chats");
714
720
  fs.mkdirSync(chatsDir, {
715
721
  recursive: true
716
722
  });
717
- fs.writeFileSync(path.join(chatsDir, 'notsession.json'), JSON.stringify({
718
- sessionId: 'skip',
723
+ fs.writeFileSync(path.join(chatsDir, "notsession.json"), JSON.stringify({
724
+ sessionId: "skip",
719
725
  projectHash: hashProjectRoot(cwd),
720
726
  messages: []
721
727
  }));
@@ -725,183 +731,183 @@ describe('GeminiCliAdapter', ()=>{
725
731
  expect(result.sessions).toEqual([]);
726
732
  });
727
733
  });
728
- describe('helper methods', ()=>{
729
- describe('determineStatus', ()=>{
734
+ describe("helper methods", ()=>{
735
+ describe("determineStatus", ()=>{
730
736
  it('should return "waiting" when the last message is from gemini', ()=>{
731
737
  const session = {
732
- sessionId: 's',
733
- projectPath: '',
734
- summary: '',
738
+ sessionId: "s",
739
+ projectPath: "",
740
+ summary: "",
735
741
  sessionStart: new Date(),
736
742
  lastActive: new Date(),
737
- lastMessageType: 'gemini'
743
+ lastMessageType: "gemini"
738
744
  };
739
745
  expect(adapter.determineStatus(session)).toBe(AgentStatus.WAITING);
740
746
  });
741
747
  it('should return "waiting" when the last message is from assistant', ()=>{
742
748
  const session = {
743
- sessionId: 's',
744
- projectPath: '',
745
- summary: '',
749
+ sessionId: "s",
750
+ projectPath: "",
751
+ summary: "",
746
752
  sessionStart: new Date(),
747
753
  lastActive: new Date(),
748
- lastMessageType: 'assistant'
754
+ lastMessageType: "assistant"
749
755
  };
750
756
  expect(adapter.determineStatus(session)).toBe(AgentStatus.WAITING);
751
757
  });
752
758
  it('should return "running" when the last message is from the user', ()=>{
753
759
  const session = {
754
- sessionId: 's',
755
- projectPath: '',
756
- summary: '',
760
+ sessionId: "s",
761
+ projectPath: "",
762
+ summary: "",
757
763
  sessionStart: new Date(),
758
764
  lastActive: new Date(),
759
- lastMessageType: 'user'
765
+ lastMessageType: "user"
760
766
  };
761
767
  expect(adapter.determineStatus(session)).toBe(AgentStatus.RUNNING);
762
768
  });
763
769
  it('should return "idle" when last activity is older than the threshold', ()=>{
764
770
  const session = {
765
- sessionId: 's',
766
- projectPath: '',
767
- summary: '',
771
+ sessionId: "s",
772
+ projectPath: "",
773
+ summary: "",
768
774
  sessionStart: new Date(),
769
775
  lastActive: new Date(Date.now() - 10 * 60 * 1000),
770
- lastMessageType: 'gemini'
776
+ lastMessageType: "gemini"
771
777
  };
772
778
  expect(adapter.determineStatus(session)).toBe(AgentStatus.IDLE);
773
779
  });
774
780
  });
775
- describe('parseSession', ()=>{
776
- it('should parse a valid session file', ()=>{
777
- const filePath = writeSession(tmpHome, 'p', 'session-a', {
778
- sessionId: 's1',
779
- projectHash: 'h',
780
- startTime: '2026-04-18T00:00:00Z',
781
- lastUpdated: '2026-04-18T00:05:00Z',
782
- kind: 'main',
781
+ describe("parseSession", ()=>{
782
+ it("should parse a valid session file", ()=>{
783
+ const filePath = writeSession(tmpHome, "p", "session-a", {
784
+ sessionId: "s1",
785
+ projectHash: "h",
786
+ startTime: "2026-04-18T00:00:00Z",
787
+ lastUpdated: "2026-04-18T00:05:00Z",
788
+ kind: "main",
783
789
  directories: [
784
- '/repo'
790
+ "/repo"
785
791
  ],
786
792
  messages: [
787
793
  {
788
- id: 'm1',
789
- timestamp: '2026-04-18T00:00:01Z',
790
- type: 'user',
791
- content: 'hello'
794
+ id: "m1",
795
+ timestamp: "2026-04-18T00:00:01Z",
796
+ type: "user",
797
+ content: "hello"
792
798
  }
793
799
  ]
794
800
  });
795
801
  const result = adapter.parseSession(undefined, filePath);
796
802
  expect(result).toMatchObject({
797
- sessionId: 's1',
798
- projectPath: '/repo',
799
- summary: 'hello'
803
+ sessionId: "s1",
804
+ projectPath: "/repo",
805
+ summary: "hello"
800
806
  });
801
807
  });
802
- it('should parse from cached content without reading disk', ()=>{
808
+ it("should parse from cached content without reading disk", ()=>{
803
809
  const content = JSON.stringify({
804
- sessionId: 's2',
805
- projectHash: 'h',
806
- startTime: '2026-04-18T00:00:00Z',
807
- lastUpdated: '2026-04-18T00:00:00Z',
810
+ sessionId: "s2",
811
+ projectHash: "h",
812
+ startTime: "2026-04-18T00:00:00Z",
813
+ lastUpdated: "2026-04-18T00:00:00Z",
808
814
  messages: []
809
815
  });
810
- const result = adapter.parseSession(content, '/does/not/exist.json');
811
- expect(result?.sessionId).toBe('s2');
816
+ const result = adapter.parseSession(content, "/does/not/exist.json");
817
+ expect(result?.sessionId).toBe("s2");
812
818
  });
813
- it('should return null for a missing file with no cached content', ()=>{
814
- expect(adapter.parseSession(undefined, '/missing.json')).toBeNull();
819
+ it("should return null for a missing file with no cached content", ()=>{
820
+ expect(adapter.parseSession(undefined, "/missing.json")).toBeNull();
815
821
  });
816
- it('should return null when the file is not valid JSON', ()=>{
817
- const filePath = path.join(tmpHome, 'broken.json');
818
- fs.writeFileSync(filePath, 'not json');
822
+ it("should return null when the file is not valid JSON", ()=>{
823
+ const filePath = path.join(tmpHome, "broken.json");
824
+ fs.writeFileSync(filePath, "not json");
819
825
  expect(adapter.parseSession(undefined, filePath)).toBeNull();
820
826
  });
821
- it('should return null when sessionId is missing', ()=>{
822
- const filePath = path.join(tmpHome, 'no-id.json');
827
+ it("should return null when sessionId is missing", ()=>{
828
+ const filePath = path.join(tmpHome, "no-id.json");
823
829
  fs.writeFileSync(filePath, JSON.stringify({
824
830
  messages: []
825
831
  }));
826
832
  expect(adapter.parseSession(undefined, filePath)).toBeNull();
827
833
  });
828
- it('should default the summary when no user message has content', ()=>{
829
- const filePath = writeSession(tmpHome, 'p', 'session-empty', {
830
- sessionId: 's3',
831
- projectHash: 'h',
832
- startTime: '2026-04-18T00:00:00Z',
833
- lastUpdated: '2026-04-18T00:00:00Z',
834
+ it("should default the summary when no user message has content", ()=>{
835
+ const filePath = writeSession(tmpHome, "p", "session-empty", {
836
+ sessionId: "s3",
837
+ projectHash: "h",
838
+ startTime: "2026-04-18T00:00:00Z",
839
+ lastUpdated: "2026-04-18T00:00:00Z",
834
840
  messages: [
835
841
  {
836
- id: 'm1',
837
- timestamp: '2026-04-18T00:00:01Z',
838
- type: 'gemini',
839
- content: 'only assistant'
842
+ id: "m1",
843
+ timestamp: "2026-04-18T00:00:01Z",
844
+ type: "gemini",
845
+ content: "only assistant"
840
846
  }
841
847
  ]
842
848
  });
843
849
  const result = adapter.parseSession(undefined, filePath);
844
- expect(result?.summary).toBe('Gemini CLI session active');
850
+ expect(result?.summary).toBe("Gemini CLI session active");
845
851
  });
846
- it('should truncate long summaries to 120 characters', ()=>{
847
- const longContent = 'x'.repeat(200);
848
- const filePath = writeSession(tmpHome, 'p', 'session-long', {
849
- sessionId: 's4',
850
- projectHash: 'h',
851
- startTime: '2026-04-18T00:00:00Z',
852
- lastUpdated: '2026-04-18T00:00:00Z',
852
+ it("should truncate long summaries to 120 characters", ()=>{
853
+ const longContent = "x".repeat(200);
854
+ const filePath = writeSession(tmpHome, "p", "session-long", {
855
+ sessionId: "s4",
856
+ projectHash: "h",
857
+ startTime: "2026-04-18T00:00:00Z",
858
+ lastUpdated: "2026-04-18T00:00:00Z",
853
859
  messages: [
854
860
  {
855
- id: 'm1',
856
- timestamp: '2026-04-18T00:00:01Z',
857
- type: 'user',
861
+ id: "m1",
862
+ timestamp: "2026-04-18T00:00:01Z",
863
+ type: "user",
858
864
  content: longContent
859
865
  }
860
866
  ]
861
867
  });
862
868
  const result = adapter.parseSession(undefined, filePath);
863
869
  expect(result?.summary.length).toBe(120);
864
- expect(result?.summary.endsWith('...')).toBe(true);
870
+ expect(result?.summary.endsWith("...")).toBe(true);
865
871
  });
866
- it('should extract summary when user content is an array of parts (real Gemini shape)', ()=>{
867
- const filePath = writeSession(tmpHome, 'p', 'session-parts', {
868
- sessionId: 's-parts',
869
- projectHash: 'h',
870
- startTime: '2026-04-18T00:00:00Z',
871
- lastUpdated: '2026-04-18T00:00:00Z',
872
+ it("should extract summary when user content is an array of parts (real Gemini shape)", ()=>{
873
+ const filePath = writeSession(tmpHome, "p", "session-parts", {
874
+ sessionId: "s-parts",
875
+ projectHash: "h",
876
+ startTime: "2026-04-18T00:00:00Z",
877
+ lastUpdated: "2026-04-18T00:00:00Z",
872
878
  messages: [
873
879
  {
874
- id: 'm1',
875
- timestamp: '2026-04-18T00:00:01Z',
876
- type: 'user',
880
+ id: "m1",
881
+ timestamp: "2026-04-18T00:00:01Z",
882
+ type: "user",
877
883
  content: [
878
884
  {
879
- text: 'hello from part'
885
+ text: "hello from part"
880
886
  },
881
887
  {
882
- text: ' continued'
888
+ text: " continued"
883
889
  }
884
890
  ]
885
891
  }
886
892
  ]
887
893
  });
888
894
  const result = adapter.parseSession(undefined, filePath);
889
- expect(result?.summary).toBe('hello from part continued');
895
+ expect(result?.summary).toBe("hello from part continued");
890
896
  });
891
- it('should not throw when user content is an array and there is no displayContent', ()=>{
892
- const filePath = writeSession(tmpHome, 'p', 'session-parts-only', {
893
- sessionId: 's-parts-only',
894
- projectHash: 'h',
895
- startTime: '2026-04-18T00:00:00Z',
896
- lastUpdated: '2026-04-18T00:00:00Z',
897
+ it("should not throw when user content is an array and there is no displayContent", ()=>{
898
+ const filePath = writeSession(tmpHome, "p", "session-parts-only", {
899
+ sessionId: "s-parts-only",
900
+ projectHash: "h",
901
+ startTime: "2026-04-18T00:00:00Z",
902
+ lastUpdated: "2026-04-18T00:00:00Z",
897
903
  messages: [
898
904
  {
899
- id: 'm1',
900
- timestamp: '2026-04-18T00:00:01Z',
901
- type: 'user',
905
+ id: "m1",
906
+ timestamp: "2026-04-18T00:00:01Z",
907
+ type: "user",
902
908
  content: [
903
909
  {
904
- text: 'only via parts'
910
+ text: "only via parts"
905
911
  }
906
912
  ]
907
913
  }
@@ -909,115 +915,115 @@ describe('GeminiCliAdapter', ()=>{
909
915
  });
910
916
  expect(()=>adapter.parseSession(undefined, filePath)).not.toThrow();
911
917
  });
912
- it('should drop non-text parts (data/file) when resolving user content', ()=>{
913
- const filePath = writeSession(tmpHome, 'p', 'session-mixed-parts', {
914
- sessionId: 's-mixed',
915
- projectHash: 'h',
916
- startTime: '2026-04-18T00:00:00Z',
917
- lastUpdated: '2026-04-18T00:00:00Z',
918
+ it("should drop non-text parts (data/file) when resolving user content", ()=>{
919
+ const filePath = writeSession(tmpHome, "p", "session-mixed-parts", {
920
+ sessionId: "s-mixed",
921
+ projectHash: "h",
922
+ startTime: "2026-04-18T00:00:00Z",
923
+ lastUpdated: "2026-04-18T00:00:00Z",
918
924
  messages: [
919
925
  {
920
- id: 'm1',
921
- timestamp: '2026-04-18T00:00:01Z',
922
- type: 'user',
926
+ id: "m1",
927
+ timestamp: "2026-04-18T00:00:01Z",
928
+ type: "user",
923
929
  content: [
924
930
  {
925
- text: 'readable text'
931
+ text: "readable text"
926
932
  },
927
933
  {
928
934
  inlineData: {
929
- mimeType: 'image/png',
930
- data: 'base64...'
935
+ mimeType: "image/png",
936
+ data: "base64..."
931
937
  }
932
938
  },
933
939
  {
934
- text: ' + more'
940
+ text: " + more"
935
941
  }
936
942
  ]
937
943
  }
938
944
  ]
939
945
  });
940
946
  const result = adapter.parseSession(undefined, filePath);
941
- expect(result?.summary).toBe('readable text + more');
947
+ expect(result?.summary).toBe("readable text + more");
942
948
  });
943
- it('should prefer lastUpdated over entry timestamp for lastActive', ()=>{
944
- const filePath = writeSession(tmpHome, 'p', 'session-last', {
945
- sessionId: 's5',
946
- projectHash: 'h',
947
- startTime: '2026-04-18T00:00:00Z',
948
- lastUpdated: '2026-04-18T00:10:00Z',
949
+ it("should prefer lastUpdated over entry timestamp for lastActive", ()=>{
950
+ const filePath = writeSession(tmpHome, "p", "session-last", {
951
+ sessionId: "s5",
952
+ projectHash: "h",
953
+ startTime: "2026-04-18T00:00:00Z",
954
+ lastUpdated: "2026-04-18T00:10:00Z",
949
955
  messages: [
950
956
  {
951
- id: 'm1',
952
- timestamp: '2026-04-18T00:00:01Z',
953
- type: 'user',
954
- content: 'hi'
957
+ id: "m1",
958
+ timestamp: "2026-04-18T00:00:01Z",
959
+ type: "user",
960
+ content: "hi"
955
961
  }
956
962
  ]
957
963
  });
958
964
  const result = adapter.parseSession(undefined, filePath);
959
- expect(result?.lastActive.toISOString()).toBe('2026-04-18T00:10:00.000Z');
965
+ expect(result?.lastActive.toISOString()).toBe("2026-04-18T00:10:00.000Z");
960
966
  });
961
967
  });
962
968
  });
963
- describe('getConversation', ()=>{
964
- it('should return messages from a valid Gemini session file', ()=>{
965
- const sessionPath = path.join(tmpHome, 'session-2026-04-18T00-00-id.json');
969
+ describe("getConversation", ()=>{
970
+ it("should return messages from a valid Gemini session file", ()=>{
971
+ const sessionPath = path.join(tmpHome, "session-2026-04-18T00-00-id.json");
966
972
  fs.writeFileSync(sessionPath, JSON.stringify({
967
- sessionId: 'abc',
968
- projectHash: 'hash',
969
- startTime: '2026-04-18T00:00:00Z',
970
- lastUpdated: '2026-04-18T00:00:00Z',
971
- kind: 'main',
973
+ sessionId: "abc",
974
+ projectHash: "hash",
975
+ startTime: "2026-04-18T00:00:00Z",
976
+ lastUpdated: "2026-04-18T00:00:00Z",
977
+ kind: "main",
972
978
  messages: [
973
979
  {
974
- id: 'm1',
975
- timestamp: '2026-04-18T00:00:01Z',
976
- type: 'user',
977
- content: 'hi'
980
+ id: "m1",
981
+ timestamp: "2026-04-18T00:00:01Z",
982
+ type: "user",
983
+ content: "hi"
978
984
  },
979
985
  {
980
- id: 'm2',
981
- timestamp: '2026-04-18T00:00:02Z',
982
- type: 'gemini',
983
- content: 'hello'
986
+ id: "m2",
987
+ timestamp: "2026-04-18T00:00:02Z",
988
+ type: "gemini",
989
+ content: "hello"
984
990
  },
985
991
  {
986
- id: 'm3',
987
- timestamp: '2026-04-18T00:00:03Z',
988
- type: 'tool',
989
- content: 'unused'
992
+ id: "m3",
993
+ timestamp: "2026-04-18T00:00:03Z",
994
+ type: "tool",
995
+ content: "unused"
990
996
  }
991
997
  ]
992
998
  }));
993
999
  const messages = adapter.getConversation(sessionPath);
994
1000
  expect(messages).toEqual([
995
1001
  {
996
- role: 'user',
997
- content: 'hi',
998
- timestamp: '2026-04-18T00:00:01Z'
1002
+ role: "user",
1003
+ content: "hi",
1004
+ timestamp: "2026-04-18T00:00:01Z"
999
1005
  },
1000
1006
  {
1001
- role: 'assistant',
1002
- content: 'hello',
1003
- timestamp: '2026-04-18T00:00:02Z'
1007
+ role: "assistant",
1008
+ content: "hello",
1009
+ timestamp: "2026-04-18T00:00:02Z"
1004
1010
  }
1005
1011
  ]);
1006
1012
  });
1007
- it('should include tool entries when verbose is true', ()=>{
1008
- const sessionPath = path.join(tmpHome, 'session-verbose.json');
1013
+ it("should include tool entries when verbose is true", ()=>{
1014
+ const sessionPath = path.join(tmpHome, "session-verbose.json");
1009
1015
  fs.writeFileSync(sessionPath, JSON.stringify({
1010
- sessionId: 'abc',
1011
- projectHash: 'hash',
1012
- startTime: '2026-04-18T00:00:00Z',
1013
- lastUpdated: '2026-04-18T00:00:00Z',
1014
- kind: 'main',
1016
+ sessionId: "abc",
1017
+ projectHash: "hash",
1018
+ startTime: "2026-04-18T00:00:00Z",
1019
+ lastUpdated: "2026-04-18T00:00:00Z",
1020
+ kind: "main",
1015
1021
  messages: [
1016
1022
  {
1017
- id: 'm1',
1018
- timestamp: '2026-04-18T00:00:01Z',
1019
- type: 'tool',
1020
- content: 'tool call'
1023
+ id: "m1",
1024
+ timestamp: "2026-04-18T00:00:01Z",
1025
+ type: "tool",
1026
+ content: "tool call"
1021
1027
  }
1022
1028
  ]
1023
1029
  }));
@@ -1026,125 +1032,125 @@ describe('GeminiCliAdapter', ()=>{
1026
1032
  });
1027
1033
  expect(messages).toEqual([
1028
1034
  {
1029
- role: 'system',
1030
- content: 'tool call',
1031
- timestamp: '2026-04-18T00:00:01Z'
1035
+ role: "system",
1036
+ content: "tool call",
1037
+ timestamp: "2026-04-18T00:00:01Z"
1032
1038
  }
1033
1039
  ]);
1034
1040
  });
1035
- it('should return empty array for missing or malformed files', ()=>{
1036
- expect(adapter.getConversation('/nonexistent/file.json')).toEqual([]);
1037
- const brokenPath = path.join(tmpHome, 'broken.json');
1038
- fs.writeFileSync(brokenPath, '{ not valid json');
1041
+ it("should return empty array for missing or malformed files", ()=>{
1042
+ expect(adapter.getConversation("/nonexistent/file.json")).toEqual([]);
1043
+ const brokenPath = path.join(tmpHome, "broken.json");
1044
+ fs.writeFileSync(brokenPath, "{ not valid json");
1039
1045
  expect(adapter.getConversation(brokenPath)).toEqual([]);
1040
1046
  });
1041
- it('should prefer displayContent over content when both are present', ()=>{
1042
- const sessionPath = path.join(tmpHome, 'session-display.json');
1047
+ it("should prefer displayContent over content when both are present", ()=>{
1048
+ const sessionPath = path.join(tmpHome, "session-display.json");
1043
1049
  fs.writeFileSync(sessionPath, JSON.stringify({
1044
- sessionId: 'abc',
1050
+ sessionId: "abc",
1045
1051
  messages: [
1046
1052
  {
1047
- id: 'm1',
1048
- timestamp: '2026-04-18T00:00:01Z',
1049
- type: 'user',
1050
- content: 'raw',
1051
- displayContent: 'rendered'
1053
+ id: "m1",
1054
+ timestamp: "2026-04-18T00:00:01Z",
1055
+ type: "user",
1056
+ content: "raw",
1057
+ displayContent: "rendered"
1052
1058
  }
1053
1059
  ]
1054
1060
  }));
1055
1061
  const messages = adapter.getConversation(sessionPath);
1056
- expect(messages[0].content).toBe('rendered');
1062
+ expect(messages[0].content).toBe("rendered");
1057
1063
  });
1058
- it('should skip entries with empty content', ()=>{
1059
- const sessionPath = path.join(tmpHome, 'session-empty.json');
1064
+ it("should skip entries with empty content", ()=>{
1065
+ const sessionPath = path.join(tmpHome, "session-empty.json");
1060
1066
  fs.writeFileSync(sessionPath, JSON.stringify({
1061
- sessionId: 'abc',
1067
+ sessionId: "abc",
1062
1068
  messages: [
1063
1069
  {
1064
- id: 'm1',
1065
- timestamp: '2026-04-18T00:00:01Z',
1066
- type: 'user',
1067
- content: ''
1070
+ id: "m1",
1071
+ timestamp: "2026-04-18T00:00:01Z",
1072
+ type: "user",
1073
+ content: ""
1068
1074
  },
1069
1075
  {
1070
- id: 'm2',
1071
- timestamp: '2026-04-18T00:00:02Z',
1072
- type: 'user',
1073
- content: 'real'
1076
+ id: "m2",
1077
+ timestamp: "2026-04-18T00:00:02Z",
1078
+ type: "user",
1079
+ content: "real"
1074
1080
  }
1075
1081
  ]
1076
1082
  }));
1077
1083
  const messages = adapter.getConversation(sessionPath);
1078
1084
  expect(messages).toHaveLength(1);
1079
- expect(messages[0].content).toBe('real');
1085
+ expect(messages[0].content).toBe("real");
1080
1086
  });
1081
- it('should skip entries without a type', ()=>{
1082
- const sessionPath = path.join(tmpHome, 'session-no-type.json');
1087
+ it("should skip entries without a type", ()=>{
1088
+ const sessionPath = path.join(tmpHome, "session-no-type.json");
1083
1089
  fs.writeFileSync(sessionPath, JSON.stringify({
1084
- sessionId: 'abc',
1090
+ sessionId: "abc",
1085
1091
  messages: [
1086
1092
  {
1087
- id: 'm1',
1088
- timestamp: '2026-04-18T00:00:01Z',
1089
- content: 'typeless'
1093
+ id: "m1",
1094
+ timestamp: "2026-04-18T00:00:01Z",
1095
+ content: "typeless"
1090
1096
  }
1091
1097
  ]
1092
1098
  }));
1093
1099
  expect(adapter.getConversation(sessionPath)).toEqual([]);
1094
1100
  });
1095
- it('should resolve user messages whose content is an array of text parts', ()=>{
1096
- const sessionPath = path.join(tmpHome, 'session-user-parts.json');
1101
+ it("should resolve user messages whose content is an array of text parts", ()=>{
1102
+ const sessionPath = path.join(tmpHome, "session-user-parts.json");
1097
1103
  fs.writeFileSync(sessionPath, JSON.stringify({
1098
- sessionId: 'abc',
1104
+ sessionId: "abc",
1099
1105
  messages: [
1100
1106
  {
1101
- id: 'm1',
1102
- timestamp: '2026-04-18T00:00:01Z',
1103
- type: 'user',
1107
+ id: "m1",
1108
+ timestamp: "2026-04-18T00:00:01Z",
1109
+ type: "user",
1104
1110
  content: [
1105
1111
  {
1106
- text: 'hello'
1112
+ text: "hello"
1107
1113
  },
1108
1114
  {
1109
- text: ' world'
1115
+ text: " world"
1110
1116
  }
1111
1117
  ]
1112
1118
  },
1113
1119
  {
1114
- id: 'm2',
1115
- timestamp: '2026-04-18T00:00:02Z',
1116
- type: 'gemini',
1117
- content: 'hi there'
1120
+ id: "m2",
1121
+ timestamp: "2026-04-18T00:00:02Z",
1122
+ type: "gemini",
1123
+ content: "hi there"
1118
1124
  }
1119
1125
  ]
1120
1126
  }));
1121
1127
  const messages = adapter.getConversation(sessionPath);
1122
1128
  expect(messages).toEqual([
1123
1129
  {
1124
- role: 'user',
1125
- content: 'hello world',
1126
- timestamp: '2026-04-18T00:00:01Z'
1130
+ role: "user",
1131
+ content: "hello world",
1132
+ timestamp: "2026-04-18T00:00:01Z"
1127
1133
  },
1128
1134
  {
1129
- role: 'assistant',
1130
- content: 'hi there',
1131
- timestamp: '2026-04-18T00:00:02Z'
1135
+ role: "assistant",
1136
+ content: "hi there",
1137
+ timestamp: "2026-04-18T00:00:02Z"
1132
1138
  }
1133
1139
  ]);
1134
1140
  });
1135
- it('should not throw when content is an array but no part carries text', ()=>{
1136
- const sessionPath = path.join(tmpHome, 'session-no-text-parts.json');
1141
+ it("should not throw when content is an array but no part carries text", ()=>{
1142
+ const sessionPath = path.join(tmpHome, "session-no-text-parts.json");
1137
1143
  fs.writeFileSync(sessionPath, JSON.stringify({
1138
- sessionId: 'abc',
1144
+ sessionId: "abc",
1139
1145
  messages: [
1140
1146
  {
1141
- id: 'm1',
1142
- timestamp: '2026-04-18T00:00:01Z',
1143
- type: 'user',
1147
+ id: "m1",
1148
+ timestamp: "2026-04-18T00:00:01Z",
1149
+ type: "user",
1144
1150
  content: [
1145
1151
  {
1146
1152
  inlineData: {
1147
- mimeType: 'image/png'
1153
+ mimeType: "image/png"
1148
1154
  }
1149
1155
  }
1150
1156
  ]
@@ -1154,57 +1160,57 @@ describe('GeminiCliAdapter', ()=>{
1154
1160
  expect(()=>adapter.getConversation(sessionPath)).not.toThrow();
1155
1161
  expect(adapter.getConversation(sessionPath)).toEqual([]);
1156
1162
  });
1157
- it('should return empty array when messages is not an array', ()=>{
1158
- const sessionPath = path.join(tmpHome, 'session-bad-messages.json');
1163
+ it("should return empty array when messages is not an array", ()=>{
1164
+ const sessionPath = path.join(tmpHome, "session-bad-messages.json");
1159
1165
  fs.writeFileSync(sessionPath, JSON.stringify({
1160
- sessionId: 'abc',
1161
- messages: 'not-an-array'
1166
+ sessionId: "abc",
1167
+ messages: "not-an-array"
1162
1168
  }));
1163
1169
  expect(adapter.getConversation(sessionPath)).toEqual([]);
1164
1170
  });
1165
1171
  });
1166
- describe('listSessions', ()=>{
1167
- it('returns empty when ~/.gemini/tmp does not exist', async ()=>{
1172
+ describe("listSessions", ()=>{
1173
+ it("returns empty when ~/.gemini/tmp does not exist", async ()=>{
1168
1174
  // tmpHome has no .gemini dir by default
1169
1175
  const result = await adapter.listSessions();
1170
1176
  expect(result).toEqual([]);
1171
1177
  });
1172
- it('walks every shortId/chats dir and returns sessions', async ()=>{
1173
- writeSession(tmpHome, 'aaa', 'session-1', {
1174
- sessionId: 's-1',
1175
- projectHash: hashProjectRoot('/repo-a'),
1176
- startTime: '2025-01-01T00:00:00Z',
1177
- lastUpdated: '2025-01-01T00:01:00Z',
1178
+ it("walks every shortId/chats dir and returns sessions", async ()=>{
1179
+ writeSession(tmpHome, "aaa", "session-1", {
1180
+ sessionId: "s-1",
1181
+ projectHash: hashProjectRoot("/repo-a"),
1182
+ startTime: "2025-01-01T00:00:00Z",
1183
+ lastUpdated: "2025-01-01T00:01:00Z",
1178
1184
  directories: [
1179
- '/repo-a'
1185
+ "/repo-a"
1180
1186
  ],
1181
1187
  messages: [
1182
1188
  {
1183
- type: 'user',
1184
- timestamp: '2025-01-01T00:00:00Z',
1189
+ type: "user",
1190
+ timestamp: "2025-01-01T00:00:00Z",
1185
1191
  content: [
1186
1192
  {
1187
- text: 'hello a'
1193
+ text: "hello a"
1188
1194
  }
1189
1195
  ]
1190
1196
  }
1191
1197
  ]
1192
1198
  });
1193
- writeSession(tmpHome, 'bbb', 'session-2', {
1194
- sessionId: 's-2',
1195
- projectHash: hashProjectRoot('/repo-b'),
1196
- startTime: '2025-01-02T00:00:00Z',
1197
- lastUpdated: '2025-01-02T00:01:00Z',
1199
+ writeSession(tmpHome, "bbb", "session-2", {
1200
+ sessionId: "s-2",
1201
+ projectHash: hashProjectRoot("/repo-b"),
1202
+ startTime: "2025-01-02T00:00:00Z",
1203
+ lastUpdated: "2025-01-02T00:01:00Z",
1198
1204
  directories: [
1199
- '/repo-b'
1205
+ "/repo-b"
1200
1206
  ],
1201
1207
  messages: [
1202
1208
  {
1203
- type: 'user',
1204
- timestamp: '2025-01-02T00:00:00Z',
1209
+ type: "user",
1210
+ timestamp: "2025-01-02T00:00:00Z",
1205
1211
  content: [
1206
1212
  {
1207
- text: 'hello b'
1213
+ text: "hello b"
1208
1214
  }
1209
1215
  ]
1210
1216
  }
@@ -1216,148 +1222,148 @@ describe('GeminiCliAdapter', ()=>{
1216
1222
  r.sessionId,
1217
1223
  r
1218
1224
  ]));
1219
- expect(byId['s-1']).toMatchObject({
1220
- type: 'gemini_cli',
1221
- cwd: '/repo-a',
1222
- firstUserMessage: 'hello a'
1225
+ expect(byId["s-1"]).toMatchObject({
1226
+ type: "gemini_cli",
1227
+ cwd: "/repo-a",
1228
+ firstUserMessage: "hello a"
1223
1229
  });
1224
- expect(byId['s-2']).toMatchObject({
1225
- type: 'gemini_cli',
1226
- cwd: '/repo-b',
1227
- firstUserMessage: 'hello b'
1230
+ expect(byId["s-2"]).toMatchObject({
1231
+ type: "gemini_cli",
1232
+ cwd: "/repo-b",
1233
+ firstUserMessage: "hello b"
1228
1234
  });
1229
1235
  });
1230
- it('applies strict-equality cwd filter against directories[0]', async ()=>{
1231
- writeSession(tmpHome, 'aaa', 'session-keep', {
1232
- sessionId: 'keep',
1233
- projectHash: hashProjectRoot('/repo'),
1234
- startTime: '2025-01-01T00:00:00Z',
1236
+ it("applies strict-equality cwd filter against directories[0]", async ()=>{
1237
+ writeSession(tmpHome, "aaa", "session-keep", {
1238
+ sessionId: "keep",
1239
+ projectHash: hashProjectRoot("/repo"),
1240
+ startTime: "2025-01-01T00:00:00Z",
1235
1241
  directories: [
1236
- '/repo'
1242
+ "/repo"
1237
1243
  ],
1238
1244
  messages: [
1239
1245
  {
1240
- type: 'user',
1241
- timestamp: '2025-01-01T00:00:00Z',
1242
- content: 'yes'
1246
+ type: "user",
1247
+ timestamp: "2025-01-01T00:00:00Z",
1248
+ content: "yes"
1243
1249
  }
1244
1250
  ]
1245
1251
  });
1246
- writeSession(tmpHome, 'bbb', 'session-drop', {
1247
- sessionId: 'drop',
1248
- projectHash: hashProjectRoot('/other'),
1249
- startTime: '2025-01-01T00:00:00Z',
1252
+ writeSession(tmpHome, "bbb", "session-drop", {
1253
+ sessionId: "drop",
1254
+ projectHash: hashProjectRoot("/other"),
1255
+ startTime: "2025-01-01T00:00:00Z",
1250
1256
  directories: [
1251
- '/other'
1257
+ "/other"
1252
1258
  ],
1253
1259
  messages: [
1254
1260
  {
1255
- type: 'user',
1256
- timestamp: '2025-01-01T00:00:00Z',
1257
- content: 'no'
1261
+ type: "user",
1262
+ timestamp: "2025-01-01T00:00:00Z",
1263
+ content: "no"
1258
1264
  }
1259
1265
  ]
1260
1266
  });
1261
1267
  const result = await adapter.listSessions({
1262
- cwd: '/repo'
1268
+ cwd: "/repo"
1263
1269
  });
1264
1270
  expect(result).toHaveLength(1);
1265
- expect(result[0].sessionId).toBe('keep');
1271
+ expect(result[0].sessionId).toBe("keep");
1266
1272
  });
1267
- it('skips malformed JSON files', async ()=>{
1268
- const chatsDir = path.join(tmpHome, '.gemini', 'tmp', 'aaa', 'chats');
1273
+ it("skips malformed JSON files", async ()=>{
1274
+ const chatsDir = path.join(tmpHome, ".gemini", "tmp", "aaa", "chats");
1269
1275
  fs.mkdirSync(chatsDir, {
1270
1276
  recursive: true
1271
1277
  });
1272
- fs.writeFileSync(path.join(chatsDir, 'session-bad.json'), '{ not json');
1273
- writeSession(tmpHome, 'aaa', 'session-good', {
1274
- sessionId: 'good',
1275
- projectHash: hashProjectRoot('/repo'),
1276
- startTime: '2025-01-01T00:00:00Z',
1278
+ fs.writeFileSync(path.join(chatsDir, "session-bad.json"), "{ not json");
1279
+ writeSession(tmpHome, "aaa", "session-good", {
1280
+ sessionId: "good",
1281
+ projectHash: hashProjectRoot("/repo"),
1282
+ startTime: "2025-01-01T00:00:00Z",
1277
1283
  directories: [
1278
- '/repo'
1284
+ "/repo"
1279
1285
  ],
1280
1286
  messages: [
1281
1287
  {
1282
- type: 'user',
1283
- timestamp: '2025-01-01T00:00:00Z',
1284
- content: 'ok'
1288
+ type: "user",
1289
+ timestamp: "2025-01-01T00:00:00Z",
1290
+ content: "ok"
1285
1291
  }
1286
1292
  ]
1287
1293
  });
1288
1294
  const result = await adapter.listSessions();
1289
1295
  expect(result).toHaveLength(1);
1290
- expect(result[0].sessionId).toBe('good');
1296
+ expect(result[0].sessionId).toBe("good");
1291
1297
  });
1292
- it('skips files missing sessionId', async ()=>{
1293
- writeSession(tmpHome, 'aaa', 'session-no-id', {
1294
- projectHash: hashProjectRoot('/repo'),
1295
- startTime: '2025-01-01T00:00:00Z',
1298
+ it("skips files missing sessionId", async ()=>{
1299
+ writeSession(tmpHome, "aaa", "session-no-id", {
1300
+ projectHash: hashProjectRoot("/repo"),
1301
+ startTime: "2025-01-01T00:00:00Z",
1296
1302
  directories: [
1297
- '/repo'
1303
+ "/repo"
1298
1304
  ],
1299
1305
  messages: []
1300
1306
  });
1301
1307
  const result = await adapter.listSessions();
1302
1308
  expect(result).toEqual([]);
1303
1309
  });
1304
- it('captures the first user-typed message as firstUserMessage', async ()=>{
1305
- writeSession(tmpHome, 'aaa', 'session-x', {
1306
- sessionId: 's',
1307
- projectHash: hashProjectRoot('/repo'),
1308
- startTime: '2025-01-01T00:00:00Z',
1310
+ it("captures the first user-typed message as firstUserMessage", async ()=>{
1311
+ writeSession(tmpHome, "aaa", "session-x", {
1312
+ sessionId: "s",
1313
+ projectHash: hashProjectRoot("/repo"),
1314
+ startTime: "2025-01-01T00:00:00Z",
1309
1315
  directories: [
1310
- '/repo'
1316
+ "/repo"
1311
1317
  ],
1312
1318
  messages: [
1313
1319
  {
1314
- type: 'gemini',
1315
- timestamp: '2025-01-01T00:00:00Z',
1316
- content: 'preamble'
1320
+ type: "gemini",
1321
+ timestamp: "2025-01-01T00:00:00Z",
1322
+ content: "preamble"
1317
1323
  },
1318
1324
  {
1319
- type: 'user',
1320
- timestamp: '2025-01-01T00:00:01Z',
1325
+ type: "user",
1326
+ timestamp: "2025-01-01T00:00:01Z",
1321
1327
  content: [
1322
1328
  {
1323
- text: 'first user'
1329
+ text: "first user"
1324
1330
  }
1325
1331
  ]
1326
1332
  },
1327
1333
  {
1328
- type: 'user',
1329
- timestamp: '2025-01-01T00:00:02Z',
1330
- content: 'second user'
1334
+ type: "user",
1335
+ timestamp: "2025-01-01T00:00:02Z",
1336
+ content: "second user"
1331
1337
  }
1332
1338
  ]
1333
1339
  });
1334
1340
  const result = await adapter.listSessions({
1335
- cwd: '/repo'
1341
+ cwd: "/repo"
1336
1342
  });
1337
1343
  expect(result).toHaveLength(1);
1338
- expect(result[0].firstUserMessage).toBe('first user');
1344
+ expect(result[0].firstUserMessage).toBe("first user");
1339
1345
  });
1340
- it('returns empty firstUserMessage when no user message exists', async ()=>{
1341
- writeSession(tmpHome, 'aaa', 'session-x', {
1342
- sessionId: 's',
1343
- projectHash: hashProjectRoot('/repo'),
1344
- startTime: '2025-01-01T00:00:00Z',
1346
+ it("returns empty firstUserMessage when no user message exists", async ()=>{
1347
+ writeSession(tmpHome, "aaa", "session-x", {
1348
+ sessionId: "s",
1349
+ projectHash: hashProjectRoot("/repo"),
1350
+ startTime: "2025-01-01T00:00:00Z",
1345
1351
  directories: [
1346
- '/repo'
1352
+ "/repo"
1347
1353
  ],
1348
1354
  messages: [
1349
1355
  {
1350
- type: 'gemini',
1351
- timestamp: '2025-01-01T00:00:00Z',
1352
- content: 'agent only'
1356
+ type: "gemini",
1357
+ timestamp: "2025-01-01T00:00:00Z",
1358
+ content: "agent only"
1353
1359
  }
1354
1360
  ]
1355
1361
  });
1356
1362
  const result = await adapter.listSessions({
1357
- cwd: '/repo'
1363
+ cwd: "/repo"
1358
1364
  });
1359
1365
  expect(result).toHaveLength(1);
1360
- expect(result[0].firstUserMessage).toBe('');
1366
+ expect(result[0].firstUserMessage).toBe("");
1361
1367
  });
1362
1368
  });
1363
1369
  });
@@ -1365,7 +1371,7 @@ describe('GeminiCliAdapter', ()=>{
1365
1371
  * Write a Gemini session JSON to the temporary home under the expected
1366
1372
  * ~/.gemini/tmp/<shortId>/chats/<fileName>.json layout. Returns the full path.
1367
1373
  */ function writeSession(home, shortId, fileName, body) {
1368
- const chatsDir = path.join(home, '.gemini', 'tmp', shortId, 'chats');
1374
+ const chatsDir = path.join(home, ".gemini", "tmp", shortId, "chats");
1369
1375
  fs.mkdirSync(chatsDir, {
1370
1376
  recursive: true
1371
1377
  });
@@ -1378,7 +1384,7 @@ describe('GeminiCliAdapter', ()=>{
1378
1384
  * sha256(projectRoot) as hex.
1379
1385
  */ function hashProjectRoot(projectRoot) {
1380
1386
  // eslint-disable-next-line @typescript-eslint/no-require-imports
1381
- return crypto.createHash('sha256').update(projectRoot).digest('hex');
1387
+ return crypto.createHash("sha256").update(projectRoot).digest("hex");
1382
1388
  }
1383
1389
 
1384
1390
  //# sourceMappingURL=GeminiCliAdapter.test.js.map