@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
@@ -10,30 +10,30 @@
10
10
  * 5. Extracting summary from the most recent user message in the session JSON
11
11
  */
12
12
 
13
- import * as crypto from 'crypto';
14
- import * as fs from 'fs';
15
- import * as path from 'path';
13
+ import * as crypto from "crypto";
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
16
  import type {
17
- AgentAdapter,
18
- AgentInfo,
19
- ProcessInfo,
20
- ConversationMessage,
21
- SessionSummary,
22
- ListSessionsOptions,
23
- AgentDetectionContext,
24
- } from './AgentAdapter.js';
25
- import { AgentStatus } from './AgentAdapter.js';
17
+ AgentAdapter,
18
+ AgentInfo,
19
+ ProcessInfo,
20
+ ConversationMessage,
21
+ SessionSummary,
22
+ ListSessionsOptions,
23
+ AgentDetectionContext,
24
+ } from "./AgentAdapter.js";
25
+ import { AgentStatus } from "./AgentAdapter.js";
26
26
  import {
27
- captureProcessSnapshot,
28
- executableBasename,
29
- filterByProcessNames,
30
- findWrapperProcess,
31
- findWrapperProcessPids,
32
- } from '../utils/process.js';
33
- import { isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
34
- import type { SessionFile } from '../utils/session.js';
35
- import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
36
- import { AgentRegistry, type RegistryEntry } from '../utils/AgentRegistry.js';
27
+ captureProcessSnapshot,
28
+ executableBasename,
29
+ filterByProcessNames,
30
+ findWrapperProcess,
31
+ findWrapperProcessPids,
32
+ } from "../utils/process.js";
33
+ import { isDirectory, safeReadFile, safeReaddir, safeStat } from "../utils/session.js";
34
+ import type { SessionFile } from "../utils/session.js";
35
+ import { matchProcessesToSessions, generateAgentName } from "../utils/matching.js";
36
+ import { AgentRegistry, type RegistryEntry } from "../utils/AgentRegistry.js";
37
37
 
38
38
  /**
39
39
  * A single Gemini CLI message content part. Mirrors the `{text?: string}`
@@ -43,659 +43,657 @@ import { AgentRegistry, type RegistryEntry } from '../utils/AgentRegistry.js';
43
43
  * `resolveContent` since the adapter only surfaces human-readable text.
44
44
  */
45
45
  interface GeminiContentPart {
46
- text?: string;
47
- [key: string]: unknown;
46
+ text?: string;
47
+ [key: string]: unknown;
48
48
  }
49
49
 
50
50
  type GeminiMessageContent = string | GeminiContentPart[];
51
51
 
52
52
  interface GeminiMessageEntry {
53
- id?: string;
54
- timestamp?: string;
55
- type?: string;
56
- /**
57
- * Gemini CLI stores two different content shapes depending on the
58
- * message origin:
59
- * - `type: "user"` messages carry the raw Part[] from userContent.parts
60
- * (e.g. `[{ text: "hello" }]`).
61
- * - `type: "gemini"` (assistant) messages carry a pre-joined string
62
- * built from `consolidatedParts.filter(p => p.text).join('').trim()`.
63
- * Both forms must be normalized via resolveContent before any string
64
- * operation is applied.
65
- */
66
- content?: GeminiMessageContent;
67
- displayContent?: GeminiMessageContent;
53
+ id?: string;
54
+ timestamp?: string;
55
+ type?: string;
56
+ /**
57
+ * Gemini CLI stores two different content shapes depending on the
58
+ * message origin:
59
+ * - `type: "user"` messages carry the raw Part[] from userContent.parts
60
+ * (e.g. `[{ text: "hello" }]`).
61
+ * - `type: "gemini"` (assistant) messages carry a pre-joined string
62
+ * built from `consolidatedParts.filter(p => p.text).join('').trim()`.
63
+ * Both forms must be normalized via resolveContent before any string
64
+ * operation is applied.
65
+ */
66
+ content?: GeminiMessageContent;
67
+ displayContent?: GeminiMessageContent;
68
68
  }
69
69
 
70
70
  interface GeminiSessionFile {
71
- sessionId?: string;
72
- projectHash?: string;
73
- startTime?: string;
74
- lastUpdated?: string;
75
- messages?: GeminiMessageEntry[];
76
- directories?: string[];
77
- kind?: string;
71
+ sessionId?: string;
72
+ projectHash?: string;
73
+ startTime?: string;
74
+ lastUpdated?: string;
75
+ messages?: GeminiMessageEntry[];
76
+ directories?: string[];
77
+ kind?: string;
78
78
  }
79
79
 
80
80
  interface GeminiSession {
81
- sessionId: string;
82
- projectPath: string;
83
- summary: string;
84
- sessionStart: Date;
85
- lastActive: Date;
86
- lastMessageType?: string;
81
+ sessionId: string;
82
+ projectPath: string;
83
+ summary: string;
84
+ sessionStart: Date;
85
+ lastActive: Date;
86
+ lastMessageType?: string;
87
87
  }
88
88
 
89
89
  export class GeminiCliAdapter implements AgentAdapter {
90
- readonly type = 'gemini_cli' as const;
91
- readonly processNames = ['node'] as const;
92
-
93
- private static readonly IDLE_THRESHOLD_MINUTES = 5;
94
- private static readonly SESSION_FILE_PREFIX = 'session-';
95
- private static readonly CHATS_DIR_NAME = 'chats';
96
- private static readonly TMP_DIR_NAME = 'tmp';
97
-
98
- private geminiTmpDir: string;
99
- private registry: AgentRegistry;
100
-
101
- constructor(registry: AgentRegistry = AgentRegistry.default()) {
102
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
103
- this.geminiTmpDir = path.join(homeDir, '.gemini', GeminiCliAdapter.TMP_DIR_NAME);
104
- this.registry = registry;
90
+ readonly type = "gemini_cli" as const;
91
+ readonly processNames = ["node"] as const;
92
+
93
+ private static readonly IDLE_THRESHOLD_MINUTES = 5;
94
+ private static readonly SESSION_FILE_PREFIX = "session-";
95
+ private static readonly CHATS_DIR_NAME = "chats";
96
+ private static readonly TMP_DIR_NAME = "tmp";
97
+
98
+ private geminiTmpDir: string;
99
+ private registry: AgentRegistry;
100
+
101
+ constructor(registry: AgentRegistry = AgentRegistry.default()) {
102
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
103
+ this.geminiTmpDir = path.join(homeDir, ".gemini", GeminiCliAdapter.TMP_DIR_NAME);
104
+ this.registry = registry;
105
+ }
106
+
107
+ canHandle(processInfo: ProcessInfo): boolean {
108
+ return this.isGeminiExecutable(processInfo.command);
109
+ }
110
+
111
+ /**
112
+ * Detect running Gemini CLI agents.
113
+ *
114
+ * Gemini CLI ships as a Node script (`bundle/gemini.js` with shebang
115
+ * `#!/usr/bin/env node`) — unlike Claude Code (native binary per
116
+ * platform) or Codex CLI (Node wrapper that execs a native Rust
117
+ * binary). The primary running process is therefore the Node runtime
118
+ * itself, and `ps aux` lists it as `node /path/to/gemini ...` with
119
+ * argv[0] = `node`. We scan the Node process pool via the shared
120
+ * snapshot and keep only those whose command line references the gemini
121
+ * executable or script via isGeminiExecutable().
122
+ */
123
+ async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
124
+ const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));
125
+ const relevant = filterByProcessNames(snapshot, this.processNames);
126
+ const processes = relevant.filter((process) => this.canHandle(process));
127
+ if (processes.length === 0) return [];
128
+
129
+ const wrapperPids = findWrapperProcessPids(processes);
130
+ const { cachedAgents, remaining } = this.tryRegistryCache(processes, wrapperPids);
131
+ if (remaining.length === 0) return this.deduplicateSessionAgents(cachedAgents);
132
+
133
+ const candidateProcesses = remaining.filter((proc) => !wrapperPids.has(proc.pid));
134
+ const registryEntriesByPid = new Map(this.registry.list().map((entry) => [entry.pid, entry]));
135
+
136
+ const { sessions, contentCache } = this.discoverSessions(candidateProcesses);
137
+ if (sessions.length === 0) {
138
+ const processOnlyAgents = candidateProcesses.map((proc) => {
139
+ const agent = this.mapProcessOnlyAgent(proc);
140
+ this.applyWrapperRegistryName(agent, proc, processes, registryEntriesByPid);
141
+ return agent;
142
+ });
143
+
144
+ return this.deduplicateSessionAgents([...cachedAgents, ...processOnlyAgents]);
105
145
  }
106
146
 
107
- canHandle(processInfo: ProcessInfo): boolean {
108
- return this.isGeminiExecutable(processInfo.command);
147
+ const matches = matchProcessesToSessions(candidateProcesses, sessions);
148
+ const matchedPids = new Set(matches.map((m) => m.process.pid));
149
+ const matchedProcesses: ProcessInfo[] = [];
150
+ const agents: AgentInfo[] = [];
151
+
152
+ for (const match of matches) {
153
+ const cachedContent = contentCache.get(match.session.filePath);
154
+ const sessionData = this.parseSession(cachedContent, match.session.filePath);
155
+ if (sessionData) {
156
+ const agent = this.mapSessionToAgent(sessionData, match.process, match.session.filePath);
157
+ this.applyWrapperRegistryName(agent, match.process, processes, registryEntriesByPid);
158
+ agents.push(agent);
159
+ matchedProcesses.push(match.process);
160
+ } else {
161
+ matchedPids.delete(match.process.pid);
162
+ }
109
163
  }
110
164
 
111
- /**
112
- * Detect running Gemini CLI agents.
113
- *
114
- * Gemini CLI ships as a Node script (`bundle/gemini.js` with shebang
115
- * `#!/usr/bin/env node`) — unlike Claude Code (native binary per
116
- * platform) or Codex CLI (Node wrapper that execs a native Rust
117
- * binary). The primary running process is therefore the Node runtime
118
- * itself, and `ps aux` lists it as `node /path/to/gemini ...` with
119
- * argv[0] = `node`. We scan the Node process pool via the shared
120
- * snapshot and keep only those whose command line references the gemini
121
- * executable or script via isGeminiExecutable().
122
- */
123
- async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
124
- const snapshot = context?.processes ?? await captureProcessSnapshot(this.processNames);
125
- const relevant = filterByProcessNames(snapshot, this.processNames);
126
- const processes = relevant.filter((process) => this.canHandle(process));
127
- if (processes.length === 0) return [];
128
-
129
- const wrapperPids = findWrapperProcessPids(processes);
130
- const { cachedAgents, remaining } = this.tryRegistryCache(processes, wrapperPids);
131
- if (remaining.length === 0) return this.deduplicateSessionAgents(cachedAgents);
132
-
133
- const candidateProcesses = remaining.filter((proc) => !wrapperPids.has(proc.pid));
134
- const registryEntriesByPid = new Map(this.registry.list().map((entry) => [entry.pid, entry]));
135
-
136
- const { sessions, contentCache } = this.discoverSessions(candidateProcesses);
137
- if (sessions.length === 0) {
138
- const processOnlyAgents = candidateProcesses.map((proc) => {
139
- const agent = this.mapProcessOnlyAgent(proc);
140
- this.applyWrapperRegistryName(agent, proc, processes, registryEntriesByPid);
141
- return agent;
142
- });
143
-
144
- return this.deduplicateSessionAgents([
145
- ...cachedAgents,
146
- ...processOnlyAgents,
147
- ]);
148
- }
149
-
150
- const matches = matchProcessesToSessions(candidateProcesses, sessions);
151
- const matchedPids = new Set(matches.map((m) => m.process.pid));
152
- const matchedProcesses: ProcessInfo[] = [];
153
- const agents: AgentInfo[] = [];
154
-
155
- for (const match of matches) {
156
- const cachedContent = contentCache.get(match.session.filePath);
157
- const sessionData = this.parseSession(cachedContent, match.session.filePath);
158
- if (sessionData) {
159
- const agent = this.mapSessionToAgent(sessionData, match.process, match.session.filePath);
160
- this.applyWrapperRegistryName(agent, match.process, processes, registryEntriesByPid);
161
- agents.push(agent);
162
- matchedProcesses.push(match.process);
163
- } else {
164
- matchedPids.delete(match.process.pid);
165
- }
166
- }
167
-
168
- const matchedWrapperPids = findWrapperProcessPids(candidateProcesses, matchedProcesses);
169
- for (const proc of candidateProcesses) {
170
- if (!matchedPids.has(proc.pid) && !matchedWrapperPids.has(proc.pid)) {
171
- const agent = this.mapProcessOnlyAgent(proc);
172
- this.applyWrapperRegistryName(agent, proc, processes, registryEntriesByPid);
173
- agents.push(agent);
174
- }
175
- }
176
-
177
- return this.deduplicateSessionAgents([...cachedAgents, ...agents]);
165
+ const matchedWrapperPids = findWrapperProcessPids(candidateProcesses, matchedProcesses);
166
+ for (const proc of candidateProcesses) {
167
+ if (!matchedPids.has(proc.pid) && !matchedWrapperPids.has(proc.pid)) {
168
+ const agent = this.mapProcessOnlyAgent(proc);
169
+ this.applyWrapperRegistryName(agent, proc, processes, registryEntriesByPid);
170
+ agents.push(agent);
171
+ }
178
172
  }
179
173
 
180
- private applyWrapperRegistryName(
181
- agent: AgentInfo,
182
- processInfo: ProcessInfo,
183
- processes: ProcessInfo[],
184
- registryEntriesByPid: Map<number, RegistryEntry>,
185
- ): void {
186
- const wrapper = findWrapperProcess(processes, processInfo);
187
- const wrapperEntry = wrapper ? registryEntriesByPid.get(wrapper.pid) : undefined;
188
- if (wrapperEntry?.type === this.type) {
189
- agent.name = wrapperEntry.name;
190
- }
174
+ return this.deduplicateSessionAgents([...cachedAgents, ...agents]);
175
+ }
176
+
177
+ private applyWrapperRegistryName(
178
+ agent: AgentInfo,
179
+ processInfo: ProcessInfo,
180
+ processes: ProcessInfo[],
181
+ registryEntriesByPid: Map<number, RegistryEntry>,
182
+ ): void {
183
+ const wrapper = findWrapperProcess(processes, processInfo);
184
+ const wrapperEntry = wrapper ? registryEntriesByPid.get(wrapper.pid) : undefined;
185
+ if (wrapperEntry?.type === this.type) {
186
+ agent.name = wrapperEntry.name;
191
187
  }
192
-
193
- private deduplicateSessionAgents(agents: AgentInfo[]): AgentInfo[] {
194
- const bySession = new Map<string, AgentInfo>();
195
- const result: AgentInfo[] = [];
196
-
197
- for (const agent of agents) {
198
- const sessionKey = this.sessionDeduplicationKey(agent);
199
- if (!sessionKey) {
200
- result.push(agent);
201
- continue;
202
- }
203
-
204
- const existing = bySession.get(sessionKey);
205
- if (!existing) {
206
- bySession.set(sessionKey, agent);
207
- result.push(agent);
208
- continue;
209
- }
210
-
211
- if (agent.pid > existing.pid) {
212
- bySession.set(sessionKey, agent);
213
- const index = result.indexOf(existing);
214
- if (index >= 0) result[index] = agent;
215
- }
216
- }
217
-
218
- return result;
219
- }
220
-
221
- private sessionDeduplicationKey(agent: AgentInfo): string | null {
222
- if (agent.sessionFilePath) return `file:${agent.sessionFilePath}`;
223
- if (agent.sessionId && !agent.sessionId.startsWith('pid-')) {
224
- return `session:${agent.sessionId}`;
225
- }
226
- return null;
188
+ }
189
+
190
+ private deduplicateSessionAgents(agents: AgentInfo[]): AgentInfo[] {
191
+ const bySession = new Map<string, AgentInfo>();
192
+ const result: AgentInfo[] = [];
193
+
194
+ for (const agent of agents) {
195
+ const sessionKey = this.sessionDeduplicationKey(agent);
196
+ if (!sessionKey) {
197
+ result.push(agent);
198
+ continue;
199
+ }
200
+
201
+ const existing = bySession.get(sessionKey);
202
+ if (!existing) {
203
+ bySession.set(sessionKey, agent);
204
+ result.push(agent);
205
+ continue;
206
+ }
207
+
208
+ if (agent.pid > existing.pid) {
209
+ bySession.set(sessionKey, agent);
210
+ const index = result.indexOf(existing);
211
+ if (index >= 0) result[index] = agent;
212
+ }
227
213
  }
228
214
 
229
- private tryRegistryCache(processes: ProcessInfo[], wrapperPids: Set<number>): {
230
- cachedAgents: AgentInfo[];
231
- remaining: ProcessInfo[];
232
- } {
233
- const cachedAgents: AgentInfo[] = [];
234
- const remaining: ProcessInfo[] = [];
235
- const byPid = new Map(this.registry.list().map((e) => [e.pid, e]));
236
-
237
- for (const proc of processes) {
238
- const entry = byPid.get(proc.pid);
239
- if (
240
- wrapperPids.has(proc.pid) ||
241
- !entry ||
242
- entry.type !== this.type ||
243
- !entry.sessionFilePath ||
244
- !fs.existsSync(entry.sessionFilePath)
245
- ) {
246
- remaining.push(proc);
247
- continue;
248
- }
249
-
250
- const content = safeReadFile(entry.sessionFilePath);
251
- const sessionData = this.parseSession(content, entry.sessionFilePath);
252
- if (!sessionData) {
253
- remaining.push(proc);
254
- continue;
255
- }
256
-
257
- cachedAgents.push(this.mapSessionToAgent(sessionData, proc, entry.sessionFilePath));
258
- }
215
+ return result;
216
+ }
259
217
 
260
- return { cachedAgents, remaining };
218
+ private sessionDeduplicationKey(agent: AgentInfo): string | null {
219
+ if (agent.sessionFilePath) return `file:${agent.sessionFilePath}`;
220
+ if (agent.sessionId && !agent.sessionId.startsWith("pid-")) {
221
+ return `session:${agent.sessionId}`;
261
222
  }
262
-
263
- /**
264
- * Discover session files for the given processes.
265
- *
266
- * Gemini CLI writes sessions to ~/.gemini/tmp/<shortId>/chats/session-*.json
267
- * where <shortId> is opaque (managed by a project registry). We scan every
268
- * shortId directory and filter by matching session.projectHash against
269
- * sha256(process.cwd) to bind each session to a candidate process CWD.
270
- */
271
- private discoverSessions(processes: ProcessInfo[]): {
272
- sessions: SessionFile[];
273
- contentCache: Map<string, string>;
274
- } {
275
- const empty = { sessions: [] as SessionFile[], contentCache: new Map<string, string>() };
276
- if (!fs.existsSync(this.geminiTmpDir)) return empty;
277
-
278
- const cwdHashMap = this.buildCwdHashMap(processes);
279
- if (cwdHashMap.size === 0) return empty;
280
-
281
- const contentCache = new Map<string, string>();
282
- const sessions: SessionFile[] = [];
283
-
284
- let shortIdEntries: string[];
285
- try {
286
- shortIdEntries = fs.readdirSync(this.geminiTmpDir);
287
- } catch {
288
- return empty;
289
- }
290
-
291
- for (const shortId of shortIdEntries) {
292
- const chatsDir = path.join(this.geminiTmpDir, shortId, GeminiCliAdapter.CHATS_DIR_NAME);
293
- try {
294
- if (!fs.statSync(chatsDir).isDirectory()) continue;
295
- } catch {
296
- continue;
297
- }
298
-
299
- let chatFiles: string[];
300
- try {
301
- chatFiles = fs.readdirSync(chatsDir);
302
- } catch {
303
- continue;
304
- }
305
-
306
- for (const fileName of chatFiles) {
307
- if (!fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) || !fileName.endsWith('.json')) {
308
- continue;
309
- }
310
-
311
- const filePath = path.join(chatsDir, fileName);
312
-
313
- let content: string;
314
- try {
315
- content = fs.readFileSync(filePath, 'utf-8');
316
- } catch {
317
- continue;
318
- }
319
-
320
- let parsed: GeminiSessionFile;
321
- try {
322
- parsed = JSON.parse(content);
323
- } catch {
324
- continue;
325
- }
326
-
327
- if (!parsed.projectHash) continue;
328
- const resolvedCwd = cwdHashMap.get(parsed.projectHash);
329
- if (!resolvedCwd) continue;
330
-
331
- let birthtimeMs = 0;
332
- try {
333
- birthtimeMs = fs.statSync(filePath).birthtimeMs;
334
- } catch {
335
- continue;
336
- }
337
-
338
- const sessionId =
339
- parsed.sessionId || fileName.replace(/\.json$/, '');
340
-
341
- contentCache.set(filePath, content);
342
- sessions.push({
343
- sessionId,
344
- filePath,
345
- projectDir: chatsDir,
346
- birthtimeMs,
347
- resolvedCwd,
348
- });
349
- }
350
- }
351
-
352
- return { sessions, contentCache };
223
+ return null;
224
+ }
225
+
226
+ private tryRegistryCache(
227
+ processes: ProcessInfo[],
228
+ wrapperPids: Set<number>,
229
+ ): {
230
+ cachedAgents: AgentInfo[];
231
+ remaining: ProcessInfo[];
232
+ } {
233
+ const cachedAgents: AgentInfo[] = [];
234
+ const remaining: ProcessInfo[] = [];
235
+ const byPid = new Map(this.registry.list().map((e) => [e.pid, e]));
236
+
237
+ for (const proc of processes) {
238
+ const entry = byPid.get(proc.pid);
239
+ if (
240
+ wrapperPids.has(proc.pid) ||
241
+ !entry ||
242
+ entry.type !== this.type ||
243
+ !entry.sessionFilePath ||
244
+ !fs.existsSync(entry.sessionFilePath)
245
+ ) {
246
+ remaining.push(proc);
247
+ continue;
248
+ }
249
+
250
+ const content = safeReadFile(entry.sessionFilePath);
251
+ const sessionData = this.parseSession(content, entry.sessionFilePath);
252
+ if (!sessionData) {
253
+ remaining.push(proc);
254
+ continue;
255
+ }
256
+
257
+ cachedAgents.push(this.mapSessionToAgent(sessionData, proc, entry.sessionFilePath));
353
258
  }
354
259
 
355
- private buildCwdHashMap(processes: ProcessInfo[]): Map<string, string> {
356
- const map = new Map<string, string>();
357
- for (const proc of processes) {
358
- if (!proc.cwd) continue;
359
- // Gemini CLI resolves its project root by walking up from the
360
- // startup directory looking for a `.git` boundary marker. A
361
- // session's projectHash therefore tracks that ancestor rather
362
- // than the process' actual CWD. Enumerate every ancestor as a
363
- // candidate so subdirectory invocations still line up with the
364
- // session the Gemini process wrote.
365
- for (const candidate of this.candidateProjectRoots(proc.cwd)) {
366
- if (!map.has(this.hashProjectRoot(candidate))) {
367
- map.set(this.hashProjectRoot(candidate), proc.cwd);
368
- }
369
- }
370
- }
371
- return map;
260
+ return { cachedAgents, remaining };
261
+ }
262
+
263
+ /**
264
+ * Discover session files for the given processes.
265
+ *
266
+ * Gemini CLI writes sessions to ~/.gemini/tmp/<shortId>/chats/session-*.json
267
+ * where <shortId> is opaque (managed by a project registry). We scan every
268
+ * shortId directory and filter by matching session.projectHash against
269
+ * sha256(process.cwd) to bind each session to a candidate process CWD.
270
+ */
271
+ private discoverSessions(processes: ProcessInfo[]): {
272
+ sessions: SessionFile[];
273
+ contentCache: Map<string, string>;
274
+ } {
275
+ const empty = { sessions: [] as SessionFile[], contentCache: new Map<string, string>() };
276
+ if (!fs.existsSync(this.geminiTmpDir)) return empty;
277
+
278
+ const cwdHashMap = this.buildCwdHashMap(processes);
279
+ if (cwdHashMap.size === 0) return empty;
280
+
281
+ const contentCache = new Map<string, string>();
282
+ const sessions: SessionFile[] = [];
283
+
284
+ let shortIdEntries: string[];
285
+ try {
286
+ shortIdEntries = fs.readdirSync(this.geminiTmpDir);
287
+ } catch {
288
+ return empty;
372
289
  }
373
290
 
374
- private candidateProjectRoots(cwd: string): string[] {
375
- const roots: string[] = [];
376
- let current = path.resolve(cwd);
377
- let parent = path.dirname(current);
378
- while (parent !== current) {
379
- roots.push(current);
380
- current = parent;
381
- parent = path.dirname(current);
291
+ for (const shortId of shortIdEntries) {
292
+ const chatsDir = path.join(this.geminiTmpDir, shortId, GeminiCliAdapter.CHATS_DIR_NAME);
293
+ try {
294
+ if (!fs.statSync(chatsDir).isDirectory()) continue;
295
+ } catch {
296
+ continue;
297
+ }
298
+
299
+ let chatFiles: string[];
300
+ try {
301
+ chatFiles = fs.readdirSync(chatsDir);
302
+ } catch {
303
+ continue;
304
+ }
305
+
306
+ for (const fileName of chatFiles) {
307
+ if (
308
+ !fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) ||
309
+ !fileName.endsWith(".json")
310
+ ) {
311
+ continue;
382
312
  }
383
- roots.push(current);
384
- return roots;
385
- }
386
313
 
387
- private hashProjectRoot(projectRoot: string): string {
388
- return crypto.createHash('sha256').update(projectRoot).digest('hex');
389
- }
314
+ const filePath = path.join(chatsDir, fileName);
390
315
 
391
- /**
392
- * Parse session file content into GeminiSession.
393
- * Uses cached content if available, otherwise reads from disk.
394
- */
395
- private parseSession(cachedContent: string | undefined, filePath: string): GeminiSession | null {
396
316
  let content: string;
397
- if (cachedContent !== undefined) {
398
- content = cachedContent;
399
- } else {
400
- try {
401
- content = fs.readFileSync(filePath, 'utf-8');
402
- } catch {
403
- return null;
404
- }
317
+ try {
318
+ content = fs.readFileSync(filePath, "utf-8");
319
+ } catch {
320
+ continue;
405
321
  }
406
322
 
407
323
  let parsed: GeminiSessionFile;
408
324
  try {
409
- parsed = JSON.parse(content);
325
+ parsed = JSON.parse(content);
410
326
  } catch {
411
- return null;
327
+ continue;
412
328
  }
413
329
 
414
- if (!parsed.sessionId) return null;
330
+ if (!parsed.projectHash) continue;
331
+ const resolvedCwd = cwdHashMap.get(parsed.projectHash);
332
+ if (!resolvedCwd) continue;
415
333
 
416
- const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
417
- const lastEntry = messages.length > 0 ? messages[messages.length - 1] : undefined;
418
-
419
- let mtime: Date | null = null;
334
+ let birthtimeMs = 0;
420
335
  try {
421
- mtime = fs.statSync(filePath).mtime;
336
+ birthtimeMs = fs.statSync(filePath).birthtimeMs;
422
337
  } catch {
423
- mtime = null;
338
+ continue;
424
339
  }
425
340
 
426
- const lastActive =
427
- this.parseTimestamp(parsed.lastUpdated) ||
428
- this.parseTimestamp(lastEntry?.timestamp) ||
429
- mtime ||
430
- new Date();
431
-
432
- const sessionStart =
433
- this.parseTimestamp(parsed.startTime) || lastActive;
434
-
435
- const projectPath =
436
- Array.isArray(parsed.directories) && parsed.directories.length > 0
437
- ? parsed.directories[0]
438
- : '';
439
-
440
- return {
441
- sessionId: parsed.sessionId,
442
- projectPath,
443
- summary: this.extractSummary(messages),
444
- sessionStart,
445
- lastActive,
446
- lastMessageType: lastEntry?.type,
447
- };
341
+ const sessionId = parsed.sessionId || fileName.replace(/\.json$/, "");
342
+
343
+ contentCache.set(filePath, content);
344
+ sessions.push({
345
+ sessionId,
346
+ filePath,
347
+ projectDir: chatsDir,
348
+ birthtimeMs,
349
+ resolvedCwd,
350
+ });
351
+ }
448
352
  }
449
353
 
450
- private mapSessionToAgent(session: GeminiSession, processInfo: ProcessInfo, filePath: string): AgentInfo {
451
- const projectPath = session.projectPath || processInfo.cwd || '';
452
- return {
453
- name: generateAgentName(projectPath, processInfo.pid),
454
- type: this.type,
455
- status: this.determineStatus(session),
456
- summary: session.summary || 'Gemini CLI session active',
457
- pid: processInfo.pid,
458
- projectPath,
459
- sessionId: session.sessionId,
460
- lastActive: session.lastActive,
461
- sessionFilePath: filePath,
462
- };
354
+ return { sessions, contentCache };
355
+ }
356
+
357
+ private buildCwdHashMap(processes: ProcessInfo[]): Map<string, string> {
358
+ const map = new Map<string, string>();
359
+ for (const proc of processes) {
360
+ if (!proc.cwd) continue;
361
+ // Gemini CLI resolves its project root by walking up from the
362
+ // startup directory looking for a `.git` boundary marker. A
363
+ // session's projectHash therefore tracks that ancestor rather
364
+ // than the process' actual CWD. Enumerate every ancestor as a
365
+ // candidate so subdirectory invocations still line up with the
366
+ // session the Gemini process wrote.
367
+ for (const candidate of this.candidateProjectRoots(proc.cwd)) {
368
+ if (!map.has(this.hashProjectRoot(candidate))) {
369
+ map.set(this.hashProjectRoot(candidate), proc.cwd);
370
+ }
371
+ }
463
372
  }
464
-
465
- private mapProcessOnlyAgent(processInfo: ProcessInfo): AgentInfo {
466
- return {
467
- name: generateAgentName(processInfo.cwd || '', processInfo.pid),
468
- type: this.type,
469
- status: AgentStatus.RUNNING,
470
- summary: 'Gemini CLI process running',
471
- pid: processInfo.pid,
472
- projectPath: processInfo.cwd || '',
473
- sessionId: `pid-${processInfo.pid}`,
474
- lastActive: new Date(),
475
- };
373
+ return map;
374
+ }
375
+
376
+ private candidateProjectRoots(cwd: string): string[] {
377
+ const roots: string[] = [];
378
+ let current = path.resolve(cwd);
379
+ let parent = path.dirname(current);
380
+ while (parent !== current) {
381
+ roots.push(current);
382
+ current = parent;
383
+ parent = path.dirname(current);
476
384
  }
477
-
478
- private parseTimestamp(value?: string): Date | null {
479
- if (!value) return null;
480
- const timestamp = new Date(value);
481
- return Number.isNaN(timestamp.getTime()) ? null : timestamp;
385
+ roots.push(current);
386
+ return roots;
387
+ }
388
+
389
+ private hashProjectRoot(projectRoot: string): string {
390
+ return crypto.createHash("sha256").update(projectRoot).digest("hex");
391
+ }
392
+
393
+ /**
394
+ * Parse session file content into GeminiSession.
395
+ * Uses cached content if available, otherwise reads from disk.
396
+ */
397
+ private parseSession(cachedContent: string | undefined, filePath: string): GeminiSession | null {
398
+ let content: string;
399
+ if (cachedContent !== undefined) {
400
+ content = cachedContent;
401
+ } else {
402
+ try {
403
+ content = fs.readFileSync(filePath, "utf-8");
404
+ } catch {
405
+ return null;
406
+ }
482
407
  }
483
408
 
484
- private determineStatus(session: GeminiSession): AgentStatus {
485
- const diffMs = Date.now() - session.lastActive.getTime();
486
- const diffMinutes = diffMs / 60000;
409
+ let parsed: GeminiSessionFile;
410
+ try {
411
+ parsed = JSON.parse(content);
412
+ } catch {
413
+ return null;
414
+ }
487
415
 
488
- if (diffMinutes > GeminiCliAdapter.IDLE_THRESHOLD_MINUTES) {
489
- return AgentStatus.IDLE;
490
- }
416
+ if (!parsed.sessionId) return null;
491
417
 
492
- if (session.lastMessageType === 'gemini' || session.lastMessageType === 'assistant') {
493
- return AgentStatus.WAITING;
494
- }
418
+ const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
419
+ const lastEntry = messages.length > 0 ? messages[messages.length - 1] : undefined;
495
420
 
496
- return AgentStatus.RUNNING;
421
+ let mtime: Date | null = null;
422
+ try {
423
+ mtime = fs.statSync(filePath).mtime;
424
+ } catch {
425
+ mtime = null;
497
426
  }
498
427
 
499
- private extractSummary(messages: GeminiMessageEntry[]): string {
500
- for (let i = messages.length - 1; i >= 0; i--) {
501
- const entry = messages[i];
502
- if (entry?.type !== 'user') continue;
503
- const text = this.messageText(entry).trim();
504
- if (text) return this.truncate(text, 120);
505
- }
506
-
507
- return 'Gemini CLI session active';
428
+ const lastActive =
429
+ this.parseTimestamp(parsed.lastUpdated) ||
430
+ this.parseTimestamp(lastEntry?.timestamp) ||
431
+ mtime ||
432
+ new Date();
433
+
434
+ const sessionStart = this.parseTimestamp(parsed.startTime) || lastActive;
435
+
436
+ const projectPath =
437
+ Array.isArray(parsed.directories) && parsed.directories.length > 0
438
+ ? parsed.directories[0]
439
+ : "";
440
+
441
+ return {
442
+ sessionId: parsed.sessionId,
443
+ projectPath,
444
+ summary: this.extractSummary(messages),
445
+ sessionStart,
446
+ lastActive,
447
+ lastMessageType: lastEntry?.type,
448
+ };
449
+ }
450
+
451
+ private mapSessionToAgent(
452
+ session: GeminiSession,
453
+ processInfo: ProcessInfo,
454
+ filePath: string,
455
+ ): AgentInfo {
456
+ const projectPath = session.projectPath || processInfo.cwd || "";
457
+ return {
458
+ name: generateAgentName(projectPath, processInfo.pid),
459
+ type: this.type,
460
+ status: this.determineStatus(session),
461
+ summary: session.summary || "Gemini CLI session active",
462
+ pid: processInfo.pid,
463
+ projectPath,
464
+ sessionId: session.sessionId,
465
+ lastActive: session.lastActive,
466
+ sessionFilePath: filePath,
467
+ };
468
+ }
469
+
470
+ private mapProcessOnlyAgent(processInfo: ProcessInfo): AgentInfo {
471
+ return {
472
+ name: generateAgentName(processInfo.cwd || "", processInfo.pid),
473
+ type: this.type,
474
+ status: AgentStatus.RUNNING,
475
+ summary: "Gemini CLI process running",
476
+ pid: processInfo.pid,
477
+ projectPath: processInfo.cwd || "",
478
+ sessionId: `pid-${processInfo.pid}`,
479
+ lastActive: new Date(),
480
+ };
481
+ }
482
+
483
+ private parseTimestamp(value?: string): Date | null {
484
+ if (!value) return null;
485
+ const timestamp = new Date(value);
486
+ return Number.isNaN(timestamp.getTime()) ? null : timestamp;
487
+ }
488
+
489
+ private determineStatus(session: GeminiSession): AgentStatus {
490
+ const diffMs = Date.now() - session.lastActive.getTime();
491
+ const diffMinutes = diffMs / 60000;
492
+
493
+ if (diffMinutes > GeminiCliAdapter.IDLE_THRESHOLD_MINUTES) {
494
+ return AgentStatus.IDLE;
508
495
  }
509
496
 
510
- /**
511
- * Normalize an entry's content/displayContent into a plain string.
512
- * Prefers displayContent when both are present (matches Gemini CLI's
513
- * own rendering priority for the /chat UI).
514
- */
515
- private messageText(entry: GeminiMessageEntry): string {
516
- const displayText = this.resolveContent(entry.displayContent);
517
- if (displayText) return displayText;
518
- return this.resolveContent(entry.content);
497
+ if (session.lastMessageType === "gemini" || session.lastMessageType === "assistant") {
498
+ return AgentStatus.WAITING;
519
499
  }
520
500
 
521
- /**
522
- * Collapse a Gemini message content field into plain text.
523
- * Accepts either a pre-joined string (assistant turns) or a Part[]
524
- * list (user turns carrying `[{text: "..."}]`). Non-text part
525
- * variants (data, file) are dropped since this helper is only used
526
- * for summary/conversation rendering.
527
- */
528
- private resolveContent(content: GeminiMessageContent | undefined): string {
529
- if (!content) return '';
530
- if (typeof content === 'string') return content;
531
- if (!Array.isArray(content)) return '';
532
-
533
- const parts: string[] = [];
534
- for (const part of content) {
535
- if (part && typeof part.text === 'string' && part.text) {
536
- parts.push(part.text);
537
- }
538
- }
539
- return parts.join('');
540
- }
501
+ return AgentStatus.RUNNING;
502
+ }
541
503
 
542
- private truncate(value: string, maxLength: number): string {
543
- if (value.length <= maxLength) return value;
544
- return `${value.slice(0, maxLength - 3)}...`;
504
+ private extractSummary(messages: GeminiMessageEntry[]): string {
505
+ for (let i = messages.length - 1; i >= 0; i--) {
506
+ const entry = messages[i];
507
+ if (entry?.type !== "user") continue;
508
+ const text = this.messageText(entry).trim();
509
+ if (text) return this.truncate(text, 120);
545
510
  }
546
511
 
547
- private isGeminiExecutable(command: string): boolean {
548
- // Accept any token in the command line whose basename matches a
549
- // known gemini entrypoint. This is intentionally broader than the
550
- // other adapters' argv[0]-only check because the Node-script
551
- // distribution puts the real gemini path in argv[1..], not argv[0].
552
- for (const token of command.trim().split(/\s+/)) {
553
- const base = executableBasename(token);
554
- if (base === 'gemini' || base === 'gemini.exe' || base === 'gemini.js') {
555
- return true;
556
- }
557
- }
558
- return false;
512
+ return "Gemini CLI session active";
513
+ }
514
+
515
+ /**
516
+ * Normalize an entry's content/displayContent into a plain string.
517
+ * Prefers displayContent when both are present (matches Gemini CLI's
518
+ * own rendering priority for the /chat UI).
519
+ */
520
+ private messageText(entry: GeminiMessageEntry): string {
521
+ const displayText = this.resolveContent(entry.displayContent);
522
+ if (displayText) return displayText;
523
+ return this.resolveContent(entry.content);
524
+ }
525
+
526
+ /**
527
+ * Collapse a Gemini message content field into plain text.
528
+ * Accepts either a pre-joined string (assistant turns) or a Part[]
529
+ * list (user turns carrying `[{text: "..."}]`). Non-text part
530
+ * variants (data, file) are dropped since this helper is only used
531
+ * for summary/conversation rendering.
532
+ */
533
+ private resolveContent(content: GeminiMessageContent | undefined): string {
534
+ if (!content) return "";
535
+ if (typeof content === "string") return content;
536
+ if (!Array.isArray(content)) return "";
537
+
538
+ const parts: string[] = [];
539
+ for (const part of content) {
540
+ if (part && typeof part.text === "string" && part.text) {
541
+ parts.push(part.text);
542
+ }
543
+ }
544
+ return parts.join("");
545
+ }
546
+
547
+ private truncate(value: string, maxLength: number): string {
548
+ if (value.length <= maxLength) return value;
549
+ return `${value.slice(0, maxLength - 3)}...`;
550
+ }
551
+
552
+ private isGeminiExecutable(command: string): boolean {
553
+ // Accept any token in the command line whose basename matches a
554
+ // known gemini entrypoint. This is intentionally broader than the
555
+ // other adapters' argv[0]-only check because the Node-script
556
+ // distribution puts the real gemini path in argv[1..], not argv[0].
557
+ for (const token of command.trim().split(/\s+/)) {
558
+ const base = executableBasename(token);
559
+ if (base === "gemini" || base === "gemini.exe" || base === "gemini.js") {
560
+ return true;
561
+ }
562
+ }
563
+ return false;
564
+ }
565
+
566
+ /**
567
+ * Read the full conversation from a Gemini CLI session JSON file.
568
+ *
569
+ * Gemini sessions store messages in an array with `type` field — typically
570
+ * 'user' or 'gemini' for visible turns, with tool and system entries mixed in.
571
+ */
572
+ getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
573
+ const verbose = options?.verbose ?? false;
574
+
575
+ const content = safeReadFile(sessionFilePath);
576
+ if (content === undefined) return [];
577
+
578
+ let parsed: GeminiSessionFile;
579
+ try {
580
+ parsed = JSON.parse(content);
581
+ } catch {
582
+ return [];
559
583
  }
560
584
 
561
- /**
562
- * Read the full conversation from a Gemini CLI session JSON file.
563
- *
564
- * Gemini sessions store messages in an array with `type` field — typically
565
- * 'user' or 'gemini' for visible turns, with tool and system entries mixed in.
566
- */
567
- getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
568
- const verbose = options?.verbose ?? false;
585
+ const messages: ConversationMessage[] = [];
586
+ if (!Array.isArray(parsed.messages)) return messages;
587
+
588
+ for (const entry of parsed.messages) {
589
+ const entryType = entry?.type;
590
+ if (!entryType) continue;
591
+
592
+ let role: ConversationMessage["role"];
593
+ if (entryType === "user") {
594
+ role = "user";
595
+ } else if (entryType === "gemini" || entryType === "assistant") {
596
+ role = "assistant";
597
+ } else if (verbose) {
598
+ role = "system";
599
+ } else {
600
+ continue;
601
+ }
602
+
603
+ const text = this.messageText(entry).trim();
604
+ if (!text) continue;
605
+
606
+ messages.push({
607
+ role,
608
+ content: text,
609
+ timestamp: entry.timestamp,
610
+ });
611
+ }
569
612
 
570
- const content = safeReadFile(sessionFilePath);
571
- if (content === undefined) return [];
613
+ return messages;
614
+ }
572
615
 
573
- let parsed: GeminiSessionFile;
574
- try {
575
- parsed = JSON.parse(content);
576
- } catch {
577
- return [];
578
- }
616
+ async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
617
+ if (!isDirectory(this.geminiTmpDir)) return [];
579
618
 
580
- const messages: ConversationMessage[] = [];
581
- if (!Array.isArray(parsed.messages)) return messages;
582
-
583
- for (const entry of parsed.messages) {
584
- const entryType = entry?.type;
585
- if (!entryType) continue;
586
-
587
- let role: ConversationMessage['role'];
588
- if (entryType === 'user') {
589
- role = 'user';
590
- } else if (entryType === 'gemini' || entryType === 'assistant') {
591
- role = 'assistant';
592
- } else if (verbose) {
593
- role = 'system';
594
- } else {
595
- continue;
596
- }
597
-
598
- const text = this.messageText(entry).trim();
599
- if (!text) continue;
600
-
601
- messages.push({
602
- role,
603
- content: text,
604
- timestamp: entry.timestamp,
605
- });
606
- }
619
+ const summaries: SessionSummary[] = [];
607
620
 
608
- return messages;
609
- }
621
+ for (const shortId of safeReaddir(this.geminiTmpDir)) {
622
+ const chatsDir = path.join(this.geminiTmpDir, shortId, GeminiCliAdapter.CHATS_DIR_NAME);
623
+ if (!isDirectory(chatsDir)) continue;
610
624
 
611
- async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
612
- if (!isDirectory(this.geminiTmpDir)) return [];
613
-
614
- const summaries: SessionSummary[] = [];
615
-
616
- for (const shortId of safeReaddir(this.geminiTmpDir)) {
617
- const chatsDir = path.join(
618
- this.geminiTmpDir,
619
- shortId,
620
- GeminiCliAdapter.CHATS_DIR_NAME,
621
- );
622
- if (!isDirectory(chatsDir)) continue;
623
-
624
- for (const fileName of safeReaddir(chatsDir)) {
625
- if (
626
- !fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) ||
627
- !fileName.endsWith('.json')
628
- ) {
629
- continue;
630
- }
631
-
632
- const filePath = path.join(chatsDir, fileName);
633
- const summary = this.fileToSessionSummary(filePath);
634
- if (!summary) continue;
635
- if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;
636
- summaries.push(summary);
637
- }
625
+ for (const fileName of safeReaddir(chatsDir)) {
626
+ if (
627
+ !fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) ||
628
+ !fileName.endsWith(".json")
629
+ ) {
630
+ continue;
638
631
  }
639
632
 
640
- return summaries;
633
+ const filePath = path.join(chatsDir, fileName);
634
+ const summary = this.fileToSessionSummary(filePath);
635
+ if (!summary) continue;
636
+ if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;
637
+ summaries.push(summary);
638
+ }
641
639
  }
642
640
 
643
- /**
644
- * Read a Gemini session JSON file and produce a {@link SessionSummary}.
645
- * Returns null when the file is unreadable, the JSON doesn't parse,
646
- * or the body lacks a sessionId.
647
- */
648
- private fileToSessionSummary(filePath: string): SessionSummary | null {
649
- const content = safeReadFile(filePath);
650
- if (content === undefined) return null;
651
-
652
- let parsed: GeminiSessionFile;
653
- try {
654
- parsed = JSON.parse(content);
655
- } catch {
656
- return null;
657
- }
658
-
659
- if (!parsed.sessionId) return null;
660
-
661
- const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
662
- const firstUserMessage = this.extractFirstUserMessage(messages);
663
-
664
- const cwd =
665
- Array.isArray(parsed.directories) && parsed.directories.length > 0
666
- ? parsed.directories[0]
667
- : '';
668
-
669
- const stat = safeStat(filePath);
670
-
671
- const lastEntryTimestamp = this.parseTimestamp(
672
- messages.length > 0 ? messages[messages.length - 1]?.timestamp : undefined,
673
- );
674
- const lastActive =
675
- this.parseTimestamp(parsed.lastUpdated) ||
676
- lastEntryTimestamp ||
677
- stat?.mtime ||
678
- new Date();
679
- const startedAt =
680
- this.parseTimestamp(parsed.startTime) || stat?.birthtime || stat?.mtime || lastActive;
681
-
682
- return {
683
- type: 'gemini_cli',
684
- sessionId: parsed.sessionId,
685
- cwd,
686
- firstUserMessage,
687
- lastActive,
688
- startedAt,
689
- sessionFilePath: filePath,
690
- };
641
+ return summaries;
642
+ }
643
+
644
+ /**
645
+ * Read a Gemini session JSON file and produce a {@link SessionSummary}.
646
+ * Returns null when the file is unreadable, the JSON doesn't parse,
647
+ * or the body lacks a sessionId.
648
+ */
649
+ private fileToSessionSummary(filePath: string): SessionSummary | null {
650
+ const content = safeReadFile(filePath);
651
+ if (content === undefined) return null;
652
+
653
+ let parsed: GeminiSessionFile;
654
+ try {
655
+ parsed = JSON.parse(content);
656
+ } catch {
657
+ return null;
691
658
  }
692
659
 
693
- private extractFirstUserMessage(messages: GeminiMessageEntry[]): string {
694
- for (const entry of messages) {
695
- if (entry?.type !== 'user') continue;
696
- const text = this.messageText(entry).trim();
697
- if (text) return text;
698
- }
699
- return '';
660
+ if (!parsed.sessionId) return null;
661
+
662
+ const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
663
+ const firstUserMessage = this.extractFirstUserMessage(messages);
664
+
665
+ const cwd =
666
+ Array.isArray(parsed.directories) && parsed.directories.length > 0
667
+ ? parsed.directories[0]
668
+ : "";
669
+
670
+ const stat = safeStat(filePath);
671
+
672
+ const lastEntryTimestamp = this.parseTimestamp(
673
+ messages.length > 0 ? messages[messages.length - 1]?.timestamp : undefined,
674
+ );
675
+ const lastActive =
676
+ this.parseTimestamp(parsed.lastUpdated) || lastEntryTimestamp || stat?.mtime || new Date();
677
+ const startedAt =
678
+ this.parseTimestamp(parsed.startTime) || stat?.birthtime || stat?.mtime || lastActive;
679
+
680
+ return {
681
+ type: "gemini_cli",
682
+ sessionId: parsed.sessionId,
683
+ cwd,
684
+ firstUserMessage,
685
+ lastActive,
686
+ startedAt,
687
+ sessionFilePath: filePath,
688
+ };
689
+ }
690
+
691
+ private extractFirstUserMessage(messages: GeminiMessageEntry[]): string {
692
+ for (const entry of messages) {
693
+ if (entry?.type !== "user") continue;
694
+ const text = this.messageText(entry).trim();
695
+ if (text) return text;
700
696
  }
697
+ return "";
698
+ }
701
699
  }