@getpaseo/server 0.1.16 → 0.1.18

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 (350) hide show
  1. package/dist/scripts/daemon-runner.js +53 -14
  2. package/dist/scripts/daemon-runner.js.map +1 -1
  3. package/dist/scripts/dev-runner.js +9 -16
  4. package/dist/scripts/dev-runner.js.map +1 -1
  5. package/dist/scripts/supervisor.js +40 -13
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +23 -3
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +81 -8
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +3 -1
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +146 -24
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-metadata-generator.js +13 -4
  17. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  18. package/dist/server/server/agent/agent-response-loop.js +1 -1
  19. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  20. package/dist/server/server/agent/agent-sdk-types.d.ts +9 -0
  21. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  22. package/dist/server/server/agent/agent-sdk-types.js +11 -1
  23. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  24. package/dist/server/server/agent/agent-storage.d.ts +5 -1
  25. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  26. package/dist/server/server/agent/agent-storage.js +41 -72
  27. package/dist/server/server/agent/agent-storage.js.map +1 -1
  28. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  29. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  30. package/dist/server/server/agent/agent-title-limits.js +3 -0
  31. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  32. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  33. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  34. package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
  35. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  36. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  37. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  38. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  39. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  40. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  41. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +15 -0
  42. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  43. package/dist/server/server/agent/providers/claude-agent.d.ts +3 -2
  44. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/claude-agent.js +244 -107
  46. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  47. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
  49. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  50. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  51. package/dist/server/server/agent/providers/codex-app-server-agent.js +31 -5
  52. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  53. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
  54. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/opencode-agent.js +207 -176
  56. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  57. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +15 -0
  58. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  59. package/dist/server/server/agent/timeline-projection.d.ts +20 -0
  60. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  61. package/dist/server/server/agent/timeline-projection.js +73 -0
  62. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  63. package/dist/server/server/bootstrap.d.ts +15 -0
  64. package/dist/server/server/bootstrap.d.ts.map +1 -1
  65. package/dist/server/server/bootstrap.js +27 -4
  66. package/dist/server/server/bootstrap.js.map +1 -1
  67. package/dist/server/server/file-download/token-store.d.ts +0 -1
  68. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  69. package/dist/server/server/file-download/token-store.js.map +1 -1
  70. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  71. package/dist/server/server/file-explorer/service.js +56 -36
  72. package/dist/server/server/file-explorer/service.js.map +1 -1
  73. package/dist/server/server/index.js +85 -29
  74. package/dist/server/server/index.js.map +1 -1
  75. package/dist/server/server/logger.d.ts +24 -3
  76. package/dist/server/server/logger.d.ts.map +1 -1
  77. package/dist/server/server/logger.js +157 -21
  78. package/dist/server/server/logger.js.map +1 -1
  79. package/dist/server/server/persisted-config.d.ts +86 -0
  80. package/dist/server/server/persisted-config.d.ts.map +1 -1
  81. package/dist/server/server/persisted-config.js +25 -3
  82. package/dist/server/server/persisted-config.js.map +1 -1
  83. package/dist/server/server/pid-lock.d.ts +6 -2
  84. package/dist/server/server/pid-lock.d.ts.map +1 -1
  85. package/dist/server/server/pid-lock.js +7 -10
  86. package/dist/server/server/pid-lock.js.map +1 -1
  87. package/dist/server/server/relay-transport.d.ts.map +1 -1
  88. package/dist/server/server/relay-transport.js +1 -0
  89. package/dist/server/server/relay-transport.js.map +1 -1
  90. package/dist/server/server/session.d.ts +57 -3
  91. package/dist/server/server/session.d.ts.map +1 -1
  92. package/dist/server/server/session.js +755 -182
  93. package/dist/server/server/session.js.map +1 -1
  94. package/dist/server/server/websocket-server.d.ts +16 -1
  95. package/dist/server/server/websocket-server.d.ts.map +1 -1
  96. package/dist/server/server/websocket-server.js +135 -9
  97. package/dist/server/server/websocket-server.js.map +1 -1
  98. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  99. package/dist/server/server/worktree-bootstrap.js +45 -2
  100. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  101. package/dist/server/shared/messages.d.ts +2841 -541
  102. package/dist/server/shared/messages.d.ts.map +1 -1
  103. package/dist/server/shared/messages.js +99 -5
  104. package/dist/server/shared/messages.js.map +1 -1
  105. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  106. package/dist/server/shared/tool-call-display.js +3 -0
  107. package/dist/server/shared/tool-call-display.js.map +1 -1
  108. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  109. package/dist/server/terminal/terminal-manager.js +1 -13
  110. package/dist/server/terminal/terminal-manager.js.map +1 -1
  111. package/dist/server/terminal/terminal.d.ts.map +1 -1
  112. package/dist/server/terminal/terminal.js +29 -5
  113. package/dist/server/terminal/terminal.js.map +1 -1
  114. package/dist/server/utils/project-icon.d.ts +1 -1
  115. package/dist/server/utils/project-icon.d.ts.map +1 -1
  116. package/dist/server/utils/project-icon.js +9 -2
  117. package/dist/server/utils/project-icon.js.map +1 -1
  118. package/dist/server/utils/worktree.d.ts +1 -0
  119. package/dist/server/utils/worktree.d.ts.map +1 -1
  120. package/dist/server/utils/worktree.js +17 -2
  121. package/dist/server/utils/worktree.js.map +1 -1
  122. package/dist/src/server/agent/activity-curator.js +228 -0
  123. package/dist/src/server/agent/activity-curator.js.map +1 -0
  124. package/dist/src/server/agent/agent-manager.js +1712 -0
  125. package/dist/src/server/agent/agent-manager.js.map +1 -0
  126. package/dist/src/server/agent/agent-metadata-generator.js +163 -0
  127. package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
  128. package/dist/src/server/agent/agent-projections.js +262 -0
  129. package/dist/src/server/agent/agent-projections.js.map +1 -0
  130. package/dist/src/server/agent/agent-response-loop.js +304 -0
  131. package/dist/src/server/agent/agent-response-loop.js.map +1 -0
  132. package/dist/src/server/agent/agent-sdk-types.js +12 -0
  133. package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
  134. package/dist/src/server/agent/agent-storage.js +299 -0
  135. package/dist/src/server/agent/agent-storage.js.map +1 -0
  136. package/dist/src/server/agent/agent-title-limits.js +3 -0
  137. package/dist/src/server/agent/agent-title-limits.js.map +1 -0
  138. package/dist/src/server/agent/audio-utils.js +19 -0
  139. package/dist/src/server/agent/audio-utils.js.map +1 -0
  140. package/dist/src/server/agent/dictation-debug.js +50 -0
  141. package/dist/src/server/agent/dictation-debug.js.map +1 -0
  142. package/dist/src/server/agent/mcp-server.js +787 -0
  143. package/dist/src/server/agent/mcp-server.js.map +1 -0
  144. package/dist/src/server/agent/orchestrator-instructions.js +51 -0
  145. package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
  146. package/dist/src/server/agent/pcm16-resampler.js +63 -0
  147. package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
  148. package/dist/src/server/agent/provider-launch-config.js +83 -0
  149. package/dist/src/server/agent/provider-launch-config.js.map +1 -0
  150. package/dist/src/server/agent/provider-manifest.js +97 -0
  151. package/dist/src/server/agent/provider-manifest.js.map +1 -0
  152. package/dist/src/server/agent/provider-registry.js +45 -0
  153. package/dist/src/server/agent/provider-registry.js.map +1 -0
  154. package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
  155. package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
  156. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  157. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  158. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  159. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  160. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
  161. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  162. package/dist/src/server/agent/providers/claude-agent.js +3750 -0
  163. package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
  164. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  165. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  166. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
  167. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  168. package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
  169. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
  170. package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
  171. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  172. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  173. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  174. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  175. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  176. package/dist/src/server/agent/providers/opencode-agent.js +905 -0
  177. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
  178. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
  179. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  180. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
  181. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  182. package/dist/src/server/agent/recordings-debug.js +19 -0
  183. package/dist/src/server/agent/recordings-debug.js.map +1 -0
  184. package/dist/src/server/agent/stt-debug.js +33 -0
  185. package/dist/src/server/agent/stt-debug.js.map +1 -0
  186. package/dist/src/server/agent/stt-manager.js +233 -0
  187. package/dist/src/server/agent/stt-manager.js.map +1 -0
  188. package/dist/src/server/agent/timeline-append.js +27 -0
  189. package/dist/src/server/agent/timeline-append.js.map +1 -0
  190. package/dist/src/server/agent/timeline-projection.js +215 -0
  191. package/dist/src/server/agent/timeline-projection.js.map +1 -0
  192. package/dist/src/server/agent/tool-name-normalization.js +45 -0
  193. package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
  194. package/dist/src/server/agent/tts-debug.js +24 -0
  195. package/dist/src/server/agent/tts-debug.js.map +1 -0
  196. package/dist/src/server/agent/tts-manager.js +249 -0
  197. package/dist/src/server/agent/tts-manager.js.map +1 -0
  198. package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
  199. package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
  200. package/dist/src/server/agent-attention-policy.js +40 -0
  201. package/dist/src/server/agent-attention-policy.js.map +1 -0
  202. package/dist/src/server/allowed-hosts.js +94 -0
  203. package/dist/src/server/allowed-hosts.js.map +1 -0
  204. package/dist/src/server/bootstrap.js +498 -0
  205. package/dist/src/server/bootstrap.js.map +1 -0
  206. package/dist/src/server/client-message-id.js +12 -0
  207. package/dist/src/server/client-message-id.js.map +1 -0
  208. package/dist/src/server/config.js +84 -0
  209. package/dist/src/server/config.js.map +1 -0
  210. package/dist/src/server/connection-offer.js +60 -0
  211. package/dist/src/server/connection-offer.js.map +1 -0
  212. package/dist/src/server/daemon-keypair.js +40 -0
  213. package/dist/src/server/daemon-keypair.js.map +1 -0
  214. package/dist/src/server/daemon-version.js +22 -0
  215. package/dist/src/server/daemon-version.js.map +1 -0
  216. package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
  217. package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
  218. package/dist/src/server/file-download/token-store.js +40 -0
  219. package/dist/src/server/file-download/token-store.js.map +1 -0
  220. package/dist/src/server/file-explorer/service.js +183 -0
  221. package/dist/src/server/file-explorer/service.js.map +1 -0
  222. package/dist/src/server/json-utils.js +45 -0
  223. package/dist/src/server/json-utils.js.map +1 -0
  224. package/dist/src/server/messages.js +29 -0
  225. package/dist/src/server/messages.js.map +1 -0
  226. package/dist/src/server/package-version.js +47 -0
  227. package/dist/src/server/package-version.js.map +1 -0
  228. package/dist/src/server/paseo-home.js +19 -0
  229. package/dist/src/server/paseo-home.js.map +1 -0
  230. package/dist/src/server/path-utils.js +20 -0
  231. package/dist/src/server/path-utils.js.map +1 -0
  232. package/dist/src/server/persisted-config.js +259 -0
  233. package/dist/src/server/persisted-config.js.map +1 -0
  234. package/dist/src/server/persistence-hooks.js +60 -0
  235. package/dist/src/server/persistence-hooks.js.map +1 -0
  236. package/dist/src/server/pid-lock.js +126 -0
  237. package/dist/src/server/pid-lock.js.map +1 -0
  238. package/dist/src/server/push/push-service.js +68 -0
  239. package/dist/src/server/push/push-service.js.map +1 -0
  240. package/dist/src/server/push/token-store.js +70 -0
  241. package/dist/src/server/push/token-store.js.map +1 -0
  242. package/dist/src/server/relay-transport.js +457 -0
  243. package/dist/src/server/relay-transport.js.map +1 -0
  244. package/dist/src/server/server-id.js +63 -0
  245. package/dist/src/server/server-id.js.map +1 -0
  246. package/dist/src/server/session.js +5947 -0
  247. package/dist/src/server/session.js.map +1 -0
  248. package/dist/src/server/speech/audio.js +101 -0
  249. package/dist/src/server/speech/audio.js.map +1 -0
  250. package/dist/src/server/speech/provider-resolver.js +7 -0
  251. package/dist/src/server/speech/provider-resolver.js.map +1 -0
  252. package/dist/src/server/speech/providers/local/config.js +83 -0
  253. package/dist/src/server/speech/providers/local/config.js.map +1 -0
  254. package/dist/src/server/speech/providers/local/models.js +17 -0
  255. package/dist/src/server/speech/providers/local/models.js.map +1 -0
  256. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
  257. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
  258. package/dist/src/server/speech/providers/local/runtime.js +253 -0
  259. package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
  260. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
  261. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
  262. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
  263. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
  264. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
  265. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
  266. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
  267. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
  268. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
  269. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
  270. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
  271. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
  272. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
  273. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
  274. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
  275. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
  276. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
  277. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
  278. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
  279. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
  280. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
  281. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
  282. package/dist/src/server/speech/providers/openai/config.js +99 -0
  283. package/dist/src/server/speech/providers/openai/config.js.map +1 -0
  284. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
  285. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
  286. package/dist/src/server/speech/providers/openai/runtime.js +114 -0
  287. package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
  288. package/dist/src/server/speech/providers/openai/stt.js +208 -0
  289. package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
  290. package/dist/src/server/speech/providers/openai/tts.js +46 -0
  291. package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
  292. package/dist/src/server/speech/speech-config-resolver.js +85 -0
  293. package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
  294. package/dist/src/server/speech/speech-provider.js +2 -0
  295. package/dist/src/server/speech/speech-provider.js.map +1 -0
  296. package/dist/src/server/speech/speech-runtime.js +497 -0
  297. package/dist/src/server/speech/speech-runtime.js.map +1 -0
  298. package/dist/src/server/speech/speech-types.js +8 -0
  299. package/dist/src/server/speech/speech-types.js.map +1 -0
  300. package/dist/src/server/utils/diff-highlighter.js +244 -0
  301. package/dist/src/server/utils/diff-highlighter.js.map +1 -0
  302. package/dist/src/server/utils/syntax-highlighter.js +145 -0
  303. package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
  304. package/dist/src/server/voice-config.js +51 -0
  305. package/dist/src/server/voice-config.js.map +1 -0
  306. package/dist/src/server/voice-mcp-bridge-command.js +31 -0
  307. package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
  308. package/dist/src/server/voice-mcp-bridge.js +109 -0
  309. package/dist/src/server/voice-mcp-bridge.js.map +1 -0
  310. package/dist/src/server/voice-permission-policy.js +13 -0
  311. package/dist/src/server/voice-permission-policy.js.map +1 -0
  312. package/dist/src/server/voice-types.js +2 -0
  313. package/dist/src/server/voice-types.js.map +1 -0
  314. package/dist/src/server/websocket-server.js +967 -0
  315. package/dist/src/server/websocket-server.js.map +1 -0
  316. package/dist/src/server/worktree-bootstrap.js +497 -0
  317. package/dist/src/server/worktree-bootstrap.js.map +1 -0
  318. package/dist/src/shared/agent-attention-notification.js +130 -0
  319. package/dist/src/shared/agent-attention-notification.js.map +1 -0
  320. package/dist/src/shared/agent-lifecycle.js +8 -0
  321. package/dist/src/shared/agent-lifecycle.js.map +1 -0
  322. package/dist/src/shared/binary-mux.js +114 -0
  323. package/dist/src/shared/binary-mux.js.map +1 -0
  324. package/dist/src/shared/connection-offer.js +17 -0
  325. package/dist/src/shared/connection-offer.js.map +1 -0
  326. package/dist/src/shared/daemon-endpoints.js +113 -0
  327. package/dist/src/shared/daemon-endpoints.js.map +1 -0
  328. package/dist/src/shared/messages.js +2001 -0
  329. package/dist/src/shared/messages.js.map +1 -0
  330. package/dist/src/shared/path-utils.js +16 -0
  331. package/dist/src/shared/path-utils.js.map +1 -0
  332. package/dist/src/shared/tool-call-display.js +93 -0
  333. package/dist/src/shared/tool-call-display.js.map +1 -0
  334. package/dist/src/terminal/terminal-manager.js +136 -0
  335. package/dist/src/terminal/terminal-manager.js.map +1 -0
  336. package/dist/src/terminal/terminal.js +410 -0
  337. package/dist/src/terminal/terminal.js.map +1 -0
  338. package/dist/src/utils/checkout-git.js +1397 -0
  339. package/dist/src/utils/checkout-git.js.map +1 -0
  340. package/dist/src/utils/directory-suggestions.js +655 -0
  341. package/dist/src/utils/directory-suggestions.js.map +1 -0
  342. package/dist/src/utils/path.js +15 -0
  343. package/dist/src/utils/path.js.map +1 -0
  344. package/dist/src/utils/project-icon.js +398 -0
  345. package/dist/src/utils/project-icon.js.map +1 -0
  346. package/dist/src/utils/worktree-metadata.js +116 -0
  347. package/dist/src/utils/worktree-metadata.js.map +1 -0
  348. package/dist/src/utils/worktree.js +741 -0
  349. package/dist/src/utils/worktree.js.map +1 -0
  350. package/package.json +14 -6
@@ -0,0 +1,3750 @@
1
+ import { execSync, spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import fs from "node:fs";
4
+ import { promises } from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { query, } from "@anthropic-ai/claude-agent-sdk";
8
+ import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./claude/tool-call-mapper.js";
9
+ import { isTaskNotificationUserContent, mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./claude/task-notification-tool-call.js";
10
+ import { buildClaudeModelFamilyAliases, buildClaudeSelectableModelIds, listClaudeCatalogModels, } from "./claude/model-catalog.js";
11
+ import { buildToolCallDisplayModel } from "../../../shared/tool-call-display.js";
12
+ import { applyProviderEnv, isProviderCommandAvailable, } from "../provider-launch-config.js";
13
+ import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
14
+ const fsPromises = promises;
15
+ const CLAUDE_SETTING_SOURCES = [
16
+ "user",
17
+ "project",
18
+ ];
19
+ function normalizeModelIdCandidate(modelId) {
20
+ if (typeof modelId !== "string") {
21
+ return null;
22
+ }
23
+ const trimmed = modelId.trim();
24
+ return trimmed.length > 0 ? trimmed : null;
25
+ }
26
+ function pickSupportedModelId(supportedModelIds, candidate) {
27
+ const normalizedCandidate = normalizeModelIdCandidate(candidate);
28
+ if (!normalizedCandidate) {
29
+ return null;
30
+ }
31
+ return supportedModelIds.has(normalizedCandidate) ? normalizedCandidate : null;
32
+ }
33
+ function inferClaudeModelFamilyFromText(text) {
34
+ if (typeof text !== "string") {
35
+ return null;
36
+ }
37
+ const lowerText = text.toLowerCase();
38
+ if (lowerText.includes("sonnet")) {
39
+ return "sonnet";
40
+ }
41
+ if (lowerText.includes("opus")) {
42
+ return "opus";
43
+ }
44
+ if (lowerText.includes("haiku")) {
45
+ return "haiku";
46
+ }
47
+ return null;
48
+ }
49
+ function pickFamilyAliasModelId(familyAliases, family) {
50
+ if (!familyAliases) {
51
+ return null;
52
+ }
53
+ return normalizeModelIdCandidate(familyAliases.get(family) ?? null);
54
+ }
55
+ export function normalizeClaudeRuntimeModelId(options) {
56
+ const runtimeModel = options.runtimeModelId.trim();
57
+ if (!runtimeModel) {
58
+ return runtimeModel;
59
+ }
60
+ const supportedModelIds = options.supportedModelIds;
61
+ if (!supportedModelIds || supportedModelIds.size === 0) {
62
+ return runtimeModel;
63
+ }
64
+ if (supportedModelIds.has(runtimeModel)) {
65
+ return runtimeModel;
66
+ }
67
+ const runtimeFamily = inferClaudeModelFamilyFromText(runtimeModel);
68
+ const familyAlias = runtimeFamily
69
+ ? pickFamilyAliasModelId(options.supportedModelFamilyAliases, runtimeFamily)
70
+ : null;
71
+ if (runtimeFamily === "sonnet") {
72
+ const explicitSonnet = pickSupportedModelId(supportedModelIds, "sonnet");
73
+ if (explicitSonnet) {
74
+ return explicitSonnet;
75
+ }
76
+ if (familyAlias && supportedModelIds.has(familyAlias)) {
77
+ return familyAlias;
78
+ }
79
+ const defaultAlias = pickSupportedModelId(supportedModelIds, "default");
80
+ if (defaultAlias) {
81
+ return defaultAlias;
82
+ }
83
+ }
84
+ if (runtimeFamily === "opus") {
85
+ const alias = pickSupportedModelId(supportedModelIds, "opus");
86
+ if (alias) {
87
+ return alias;
88
+ }
89
+ if (familyAlias && supportedModelIds.has(familyAlias)) {
90
+ return familyAlias;
91
+ }
92
+ }
93
+ if (runtimeFamily === "haiku") {
94
+ const alias = pickSupportedModelId(supportedModelIds, "haiku");
95
+ if (alias) {
96
+ return alias;
97
+ }
98
+ if (familyAlias && supportedModelIds.has(familyAlias)) {
99
+ return familyAlias;
100
+ }
101
+ }
102
+ const configuredModelId = pickSupportedModelId(supportedModelIds, options.configuredModelId);
103
+ if (configuredModelId) {
104
+ return configuredModelId;
105
+ }
106
+ const currentModelId = pickSupportedModelId(supportedModelIds, options.currentModelId);
107
+ if (currentModelId) {
108
+ return currentModelId;
109
+ }
110
+ // If Claude reports a concrete family ID we can't map directly, prefer the
111
+ // provider default alias for unconfigured sessions so UI model/thinking state
112
+ // can still reconcile against the current model catalog.
113
+ const defaultAlias = pickSupportedModelId(supportedModelIds, "default");
114
+ const hasConfiguredModel = normalizeModelIdCandidate(options.configuredModelId) !== null;
115
+ const hasCurrentModel = normalizeModelIdCandidate(options.currentModelId) !== null;
116
+ if (runtimeFamily && defaultAlias && !hasConfiguredModel && !hasCurrentModel) {
117
+ return defaultAlias;
118
+ }
119
+ return runtimeModel;
120
+ }
121
+ const CLAUDE_CAPABILITIES = {
122
+ supportsStreaming: true,
123
+ supportsSessionPersistence: true,
124
+ supportsDynamicModes: true,
125
+ supportsMcpServers: true,
126
+ supportsReasoningStream: true,
127
+ supportsToolInvocations: true,
128
+ };
129
+ const DEFAULT_MODES = [
130
+ {
131
+ id: "default",
132
+ label: "Always Ask",
133
+ description: "Prompts for permission the first time a tool is used",
134
+ },
135
+ {
136
+ id: "acceptEdits",
137
+ label: "Accept File Edits",
138
+ description: "Automatically approves edit-focused tools without prompting",
139
+ },
140
+ {
141
+ id: "plan",
142
+ label: "Plan Mode",
143
+ description: "Analyze the codebase without executing tools or edits",
144
+ },
145
+ {
146
+ id: "bypassPermissions",
147
+ label: "Bypass",
148
+ description: "Skip all permission prompts (use with caution)",
149
+ },
150
+ ];
151
+ const VALID_CLAUDE_MODES = new Set(DEFAULT_MODES.map((mode) => mode.id));
152
+ const REWIND_COMMAND_NAME = "rewind";
153
+ const REWIND_COMMAND = {
154
+ name: REWIND_COMMAND_NAME,
155
+ description: "Rewind tracked files to a previous user message",
156
+ argumentHint: "[user_message_uuid]",
157
+ };
158
+ const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
159
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
160
+ function resolveClaudeBinary() {
161
+ try {
162
+ const claudePath = execSync("which claude", { encoding: "utf8" }).trim();
163
+ if (claudePath) {
164
+ return claudePath;
165
+ }
166
+ }
167
+ catch {
168
+ // fall through
169
+ }
170
+ throw new Error("Claude CLI not found. Install claude or configure agents.providers.claude.command.mode='replace'.");
171
+ }
172
+ function resolveClaudeSpawnCommand(spawnOptions, runtimeSettings) {
173
+ const commandConfig = runtimeSettings?.command;
174
+ if (!commandConfig || commandConfig.mode === "default") {
175
+ return {
176
+ command: spawnOptions.command,
177
+ args: [...spawnOptions.args],
178
+ };
179
+ }
180
+ if (commandConfig.mode === "append") {
181
+ return {
182
+ command: spawnOptions.command,
183
+ args: [...(commandConfig.args ?? []), ...spawnOptions.args],
184
+ };
185
+ }
186
+ return {
187
+ command: commandConfig.argv[0],
188
+ args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
189
+ };
190
+ }
191
+ function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings) {
192
+ const hasEnvOverrides = Object.keys(runtimeSettings?.env ?? {}).length > 0;
193
+ const commandMode = runtimeSettings?.command?.mode;
194
+ const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
195
+ if (!needsCustomSpawn) {
196
+ return options;
197
+ }
198
+ return {
199
+ ...options,
200
+ spawnClaudeCodeProcess: (spawnOptions) => {
201
+ const resolved = resolveClaudeSpawnCommand(spawnOptions, runtimeSettings);
202
+ return spawn(resolved.command, resolved.args, {
203
+ cwd: spawnOptions.cwd,
204
+ env: applyProviderEnv(spawnOptions.env, runtimeSettings),
205
+ signal: spawnOptions.signal,
206
+ stdio: ["pipe", "pipe", "pipe"],
207
+ });
208
+ },
209
+ };
210
+ }
211
+ const MAX_RECENT_STDERR_CHARS = 4000;
212
+ function summarizeClaudeOptionsForLog(options) {
213
+ const systemPromptRaw = options.systemPrompt;
214
+ const systemPromptSummary = (() => {
215
+ if (!systemPromptRaw) {
216
+ return { mode: "none", preset: null };
217
+ }
218
+ if (typeof systemPromptRaw === "string") {
219
+ return { mode: "string", preset: null };
220
+ }
221
+ const prompt = systemPromptRaw;
222
+ const promptType = typeof prompt.type === "string" ? prompt.type : "custom";
223
+ return {
224
+ mode: promptType === "preset"
225
+ ? "preset"
226
+ : "custom",
227
+ preset: typeof prompt.preset === "string" && prompt.preset.length > 0
228
+ ? prompt.preset
229
+ : null,
230
+ };
231
+ })();
232
+ const mcpServerNames = options.mcpServers
233
+ ? Object.keys(options.mcpServers).sort()
234
+ : [];
235
+ return {
236
+ cwd: typeof options.cwd === "string" ? options.cwd : null,
237
+ permissionMode: typeof options.permissionMode === "string"
238
+ ? options.permissionMode
239
+ : null,
240
+ model: typeof options.model === "string" ? options.model : null,
241
+ includePartialMessages: options.includePartialMessages === true,
242
+ settingSources: Array.isArray(options.settingSources)
243
+ ? options.settingSources
244
+ : [],
245
+ enableFileCheckpointing: options.enableFileCheckpointing === true,
246
+ hasResume: typeof options.resume === "string" && options.resume.length > 0,
247
+ maxThinkingTokens: typeof options.maxThinkingTokens === "number"
248
+ ? options.maxThinkingTokens
249
+ : null,
250
+ hasEnv: !!options.env,
251
+ envKeyCount: Object.keys(options.env ?? {}).length,
252
+ hasMcpServers: mcpServerNames.length > 0,
253
+ mcpServerNames,
254
+ systemPromptMode: systemPromptSummary.mode,
255
+ systemPromptPreset: systemPromptSummary.preset,
256
+ hasCanUseTool: typeof options.canUseTool === "function",
257
+ hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
258
+ hasStderrHandler: typeof options.stderr === "function",
259
+ };
260
+ }
261
+ function isToolResultTextBlock(value) {
262
+ return (!!value &&
263
+ typeof value === "object" &&
264
+ value.type === "text" &&
265
+ typeof value.text === "string");
266
+ }
267
+ function normalizeForDeterministicString(value, seen) {
268
+ if (value === null ||
269
+ typeof value === "string" ||
270
+ typeof value === "number" ||
271
+ typeof value === "boolean") {
272
+ return value;
273
+ }
274
+ if (typeof value === "bigint") {
275
+ return value.toString();
276
+ }
277
+ if (typeof value === "function") {
278
+ return "[function]";
279
+ }
280
+ if (typeof value === "symbol") {
281
+ return value.toString();
282
+ }
283
+ if (typeof value === "undefined") {
284
+ return "[undefined]";
285
+ }
286
+ if (Array.isArray(value)) {
287
+ return value.map((entry) => normalizeForDeterministicString(entry, seen));
288
+ }
289
+ if (typeof value === "object") {
290
+ const objectValue = value;
291
+ if (seen.has(objectValue)) {
292
+ return "[circular]";
293
+ }
294
+ seen.add(objectValue);
295
+ const record = value;
296
+ const normalized = {};
297
+ for (const key of Object.keys(record).sort()) {
298
+ normalized[key] = normalizeForDeterministicString(record[key], seen);
299
+ }
300
+ seen.delete(objectValue);
301
+ return normalized;
302
+ }
303
+ return String(value);
304
+ }
305
+ function deterministicStringify(value) {
306
+ if (typeof value === "undefined") {
307
+ return "";
308
+ }
309
+ try {
310
+ const normalized = normalizeForDeterministicString(value, new WeakSet());
311
+ if (typeof normalized === "string") {
312
+ return normalized;
313
+ }
314
+ return JSON.stringify(normalized);
315
+ }
316
+ catch {
317
+ try {
318
+ return String(value);
319
+ }
320
+ catch {
321
+ return "[unserializable]";
322
+ }
323
+ }
324
+ }
325
+ function coerceToolResultContentToString(content) {
326
+ if (typeof content === "string") {
327
+ return content;
328
+ }
329
+ if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
330
+ return content.map((block) => block.text).join("");
331
+ }
332
+ return deterministicStringify(content);
333
+ }
334
+ export function extractUserMessageText(content) {
335
+ if (typeof content === "string") {
336
+ const normalized = content.trim();
337
+ return normalized.length > 0 ? normalized : null;
338
+ }
339
+ if (!Array.isArray(content)) {
340
+ return null;
341
+ }
342
+ const parts = [];
343
+ for (const block of content) {
344
+ if (!block || typeof block !== "object") {
345
+ continue;
346
+ }
347
+ const text = typeof block.text === "string" ? block.text : undefined;
348
+ if (text && text.trim()) {
349
+ parts.push(text.trim());
350
+ continue;
351
+ }
352
+ const input = typeof block.input === "string" ? block.input : undefined;
353
+ if (input && input.trim()) {
354
+ parts.push(input.trim());
355
+ }
356
+ }
357
+ if (parts.length === 0) {
358
+ return null;
359
+ }
360
+ const combined = parts.join("\n\n").trim();
361
+ return combined.length > 0 ? combined : null;
362
+ }
363
+ const MAX_SUB_AGENT_LOG_ENTRIES = 200;
364
+ const MAX_SUB_AGENT_SUMMARY_CHARS = 160;
365
+ function isMetadata(value) {
366
+ return typeof value === "object" && value !== null;
367
+ }
368
+ function readTrimmedString(value) {
369
+ if (typeof value !== "string") {
370
+ return undefined;
371
+ }
372
+ const trimmed = value.trim();
373
+ return trimmed.length > 0 ? trimmed : undefined;
374
+ }
375
+ function isMcpServerConfig(value) {
376
+ if (!isMetadata(value)) {
377
+ return false;
378
+ }
379
+ const type = value.type;
380
+ if (type === "stdio") {
381
+ return typeof value.command === "string";
382
+ }
383
+ if (type === "http" || type === "sse") {
384
+ return typeof value.url === "string";
385
+ }
386
+ return false;
387
+ }
388
+ function isMcpServersRecord(value) {
389
+ if (!isMetadata(value)) {
390
+ return false;
391
+ }
392
+ for (const config of Object.values(value)) {
393
+ if (!isMcpServerConfig(config)) {
394
+ return false;
395
+ }
396
+ }
397
+ return true;
398
+ }
399
+ function isPermissionMode(value) {
400
+ return typeof value === "string" && VALID_CLAUDE_MODES.has(value);
401
+ }
402
+ function coerceSessionMetadata(metadata) {
403
+ if (!isMetadata(metadata)) {
404
+ return {};
405
+ }
406
+ const result = {};
407
+ if (metadata.provider === "claude" || metadata.provider === "codex") {
408
+ result.provider = metadata.provider;
409
+ }
410
+ if (typeof metadata.cwd === "string") {
411
+ result.cwd = metadata.cwd;
412
+ }
413
+ if (typeof metadata.modeId === "string") {
414
+ result.modeId = metadata.modeId;
415
+ }
416
+ if (typeof metadata.model === "string") {
417
+ result.model = metadata.model;
418
+ }
419
+ if (typeof metadata.title === "string" || metadata.title === null) {
420
+ result.title = metadata.title;
421
+ }
422
+ if (typeof metadata.approvalPolicy === "string") {
423
+ result.approvalPolicy = metadata.approvalPolicy;
424
+ }
425
+ if (typeof metadata.sandboxMode === "string") {
426
+ result.sandboxMode = metadata.sandboxMode;
427
+ }
428
+ if (typeof metadata.networkAccess === "boolean") {
429
+ result.networkAccess = metadata.networkAccess;
430
+ }
431
+ if (typeof metadata.webSearch === "boolean") {
432
+ result.webSearch = metadata.webSearch;
433
+ }
434
+ if (isMetadata(metadata.extra)) {
435
+ const extra = {};
436
+ if (isMetadata(metadata.extra.codex)) {
437
+ extra.codex = metadata.extra.codex;
438
+ }
439
+ if (isClaudeExtra(metadata.extra.claude)) {
440
+ extra.claude = metadata.extra.claude;
441
+ }
442
+ if (extra.codex || extra.claude) {
443
+ result.extra = extra;
444
+ }
445
+ }
446
+ if (typeof metadata.systemPrompt === "string") {
447
+ result.systemPrompt = metadata.systemPrompt;
448
+ }
449
+ if (isMcpServersRecord(metadata.mcpServers)) {
450
+ result.mcpServers = metadata.mcpServers;
451
+ }
452
+ return result;
453
+ }
454
+ function toClaudeSdkMcpConfig(config) {
455
+ switch (config.type) {
456
+ case "stdio":
457
+ return {
458
+ type: "stdio",
459
+ command: config.command,
460
+ args: config.args,
461
+ env: config.env,
462
+ };
463
+ case "http":
464
+ return {
465
+ type: "http",
466
+ url: config.url,
467
+ headers: config.headers,
468
+ };
469
+ case "sse":
470
+ return {
471
+ type: "sse",
472
+ url: config.url,
473
+ headers: config.headers,
474
+ };
475
+ }
476
+ }
477
+ function isClaudeContentChunk(value) {
478
+ return isMetadata(value) && typeof value.type === "string";
479
+ }
480
+ function isClaudeExtra(value) {
481
+ return isMetadata(value);
482
+ }
483
+ function isPermissionUpdate(value) {
484
+ if (!isMetadata(value)) {
485
+ return false;
486
+ }
487
+ const type = value.type;
488
+ if (type !== "addRules" && type !== "replaceRules" && type !== "removeRules") {
489
+ return false;
490
+ }
491
+ const rules = value.rules;
492
+ const behavior = value.behavior;
493
+ const destination = value.destination;
494
+ return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
495
+ }
496
+ function resolvePermissionKind(toolName, input) {
497
+ if (toolName === "ExitPlanMode")
498
+ return "plan";
499
+ if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
500
+ return "question";
501
+ }
502
+ return "tool";
503
+ }
504
+ const ACTIVE_RUN_STATES = new Set([
505
+ "queued",
506
+ "awaiting_response",
507
+ "streaming",
508
+ "finalizing",
509
+ ]);
510
+ class RunTracker {
511
+ constructor() {
512
+ this.runs = new Map();
513
+ this.runByTaskId = new Map();
514
+ this.runByParentMessageId = new Map();
515
+ this.runByMessageId = new Map();
516
+ }
517
+ createRun(input) {
518
+ const run = {
519
+ id: input.id,
520
+ owner: input.owner,
521
+ queue: input.queue,
522
+ state: "queued",
523
+ promptReplaySeen: input.promptReplaySeen ?? true,
524
+ taskIds: new Set(),
525
+ parentMessageIds: new Set(),
526
+ messageIds: new Set(),
527
+ };
528
+ this.runs.set(run.id, run);
529
+ return run;
530
+ }
531
+ getRun(runId) {
532
+ return this.runs.get(runId) ?? null;
533
+ }
534
+ getForegroundRun() {
535
+ for (const run of this.runs.values()) {
536
+ if (run.owner === "foreground" && this.isActive(run.state)) {
537
+ return run;
538
+ }
539
+ }
540
+ return null;
541
+ }
542
+ listActiveRuns(owner) {
543
+ const runs = [];
544
+ for (const run of this.runs.values()) {
545
+ if (!this.isActive(run.state)) {
546
+ continue;
547
+ }
548
+ if (owner && run.owner !== owner) {
549
+ continue;
550
+ }
551
+ runs.push(run);
552
+ }
553
+ return runs;
554
+ }
555
+ hasActiveRuns(owner) {
556
+ for (const run of this.runs.values()) {
557
+ if (!this.isActive(run.state)) {
558
+ continue;
559
+ }
560
+ if (owner && run.owner !== owner) {
561
+ continue;
562
+ }
563
+ return true;
564
+ }
565
+ return false;
566
+ }
567
+ getLatestActiveRun(owner) {
568
+ let latest = null;
569
+ for (const run of this.runs.values()) {
570
+ if (!this.isActive(run.state)) {
571
+ continue;
572
+ }
573
+ if (owner && run.owner !== owner) {
574
+ continue;
575
+ }
576
+ latest = run;
577
+ }
578
+ return latest;
579
+ }
580
+ isRunActive(run) {
581
+ if (!run) {
582
+ return false;
583
+ }
584
+ return this.isActive(run.state);
585
+ }
586
+ resolveByIdentifiers(identifiers) {
587
+ if (identifiers.taskId) {
588
+ const run = this.resolveMappedRun(this.runByTaskId, identifiers.taskId);
589
+ if (run) {
590
+ return { run, reason: "task_id" };
591
+ }
592
+ }
593
+ if (identifiers.parentMessageId) {
594
+ const run = this.resolveMappedRun(this.runByParentMessageId, identifiers.parentMessageId);
595
+ if (run) {
596
+ return { run, reason: "parent_message_id" };
597
+ }
598
+ }
599
+ if (identifiers.messageId) {
600
+ const run = this.resolveMappedRun(this.runByMessageId, identifiers.messageId);
601
+ if (run) {
602
+ return { run, reason: "message_id" };
603
+ }
604
+ }
605
+ return { run: null, reason: "metadata" };
606
+ }
607
+ bindIdentifiers(run, identifiers) {
608
+ if (identifiers.taskId) {
609
+ run.taskIds.add(identifiers.taskId);
610
+ this.runByTaskId.set(identifiers.taskId, run.id);
611
+ }
612
+ if (identifiers.parentMessageId) {
613
+ run.parentMessageIds.add(identifiers.parentMessageId);
614
+ this.runByParentMessageId.set(identifiers.parentMessageId, run.id);
615
+ }
616
+ if (identifiers.messageId) {
617
+ run.messageIds.add(identifiers.messageId);
618
+ this.runByMessageId.set(identifiers.messageId, run.id);
619
+ }
620
+ }
621
+ transition(run, nextState) {
622
+ run.state = nextState;
623
+ }
624
+ complete(run, terminalState) {
625
+ run.state = terminalState;
626
+ this.clearRunIndex(run);
627
+ }
628
+ deriveLifecycle(pendingPermissionCount) {
629
+ for (const run of this.runs.values()) {
630
+ if (this.isActive(run.state)) {
631
+ return "running";
632
+ }
633
+ }
634
+ if (pendingPermissionCount > 0) {
635
+ return "permission";
636
+ }
637
+ for (const run of this.runs.values()) {
638
+ if (run.state === "error") {
639
+ return "error";
640
+ }
641
+ }
642
+ return "idle";
643
+ }
644
+ resolveMappedRun(mapping, identifier) {
645
+ const runId = mapping.get(identifier);
646
+ if (!runId) {
647
+ return null;
648
+ }
649
+ const run = this.runs.get(runId);
650
+ if (!run || !this.isActive(run.state)) {
651
+ mapping.delete(identifier);
652
+ return null;
653
+ }
654
+ return run;
655
+ }
656
+ clearRunIndex(run) {
657
+ for (const taskId of run.taskIds) {
658
+ this.runByTaskId.delete(taskId);
659
+ }
660
+ for (const parentMessageId of run.parentMessageIds) {
661
+ this.runByParentMessageId.delete(parentMessageId);
662
+ }
663
+ for (const messageId of run.messageIds) {
664
+ this.runByMessageId.delete(messageId);
665
+ }
666
+ run.taskIds.clear();
667
+ run.parentMessageIds.clear();
668
+ run.messageIds.clear();
669
+ }
670
+ isActive(state) {
671
+ return ACTIVE_RUN_STATES.has(state);
672
+ }
673
+ }
674
+ class TimelineAssembler {
675
+ constructor() {
676
+ this.messages = new Map();
677
+ this.activeMessageByRun = new Map();
678
+ this.syntheticMessageCounter = 0;
679
+ }
680
+ consume(input) {
681
+ if (input.message.type === "assistant") {
682
+ return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
683
+ }
684
+ if (input.message.type === "stream_event") {
685
+ return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
686
+ }
687
+ return [];
688
+ }
689
+ consumeAssistantMessage(message, runId, messageIdHint) {
690
+ const messageId = this.readMessageIdFromAssistantMessage(message) ??
691
+ messageIdHint ??
692
+ this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
693
+ if (!messageId) {
694
+ return [];
695
+ }
696
+ const state = this.ensureMessageState(messageId, runId);
697
+ const fragments = this.extractFragments(message.message?.content);
698
+ return this.applyAbsoluteFragments(state, fragments);
699
+ }
700
+ consumeStreamEvent(message, runId, messageIdHint) {
701
+ const event = message.event;
702
+ const eventType = readTrimmedString(event.type);
703
+ const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
704
+ if (eventType === "message_start") {
705
+ const messageId = this.resolveMessageId({
706
+ runId,
707
+ createIfMissing: true,
708
+ messageId: streamEventMessageId,
709
+ });
710
+ if (!messageId) {
711
+ return [];
712
+ }
713
+ this.ensureMessageState(messageId, runId);
714
+ return [];
715
+ }
716
+ if (eventType === "message_stop") {
717
+ const messageId = this.resolveMessageId({
718
+ runId,
719
+ createIfMissing: false,
720
+ messageId: streamEventMessageId,
721
+ });
722
+ if (!messageId) {
723
+ return [];
724
+ }
725
+ return this.finalizeMessage(messageId, runId);
726
+ }
727
+ if (eventType === "content_block_start") {
728
+ return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
729
+ }
730
+ if (eventType === "content_block_delta") {
731
+ return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
732
+ }
733
+ return [];
734
+ }
735
+ consumeDeltaContent(content, runId, messageIdHint) {
736
+ const fragments = this.extractFragments(content);
737
+ if (fragments.length === 0) {
738
+ return [];
739
+ }
740
+ const messageId = this.resolveMessageId({
741
+ runId,
742
+ createIfMissing: true,
743
+ messageId: messageIdHint,
744
+ });
745
+ if (!messageId) {
746
+ return [];
747
+ }
748
+ const state = this.ensureMessageState(messageId, runId);
749
+ return this.appendFragments(state, fragments);
750
+ }
751
+ appendFragments(state, fragments) {
752
+ for (const fragment of fragments) {
753
+ if (fragment.kind === "assistant") {
754
+ state.assistantText += fragment.text;
755
+ }
756
+ else {
757
+ state.reasoningText += fragment.text;
758
+ }
759
+ }
760
+ return this.emitNewContent(state);
761
+ }
762
+ applyAbsoluteFragments(state, fragments) {
763
+ const assistantText = fragments
764
+ .filter((fragment) => fragment.kind === "assistant")
765
+ .map((fragment) => fragment.text)
766
+ .join("");
767
+ const reasoningText = fragments
768
+ .filter((fragment) => fragment.kind === "reasoning")
769
+ .map((fragment) => fragment.text)
770
+ .join("");
771
+ if (assistantText.length > 0) {
772
+ if (!assistantText.startsWith(state.assistantText)) {
773
+ state.emittedAssistantLength = 0;
774
+ }
775
+ state.assistantText = assistantText;
776
+ }
777
+ if (reasoningText.length > 0) {
778
+ if (!reasoningText.startsWith(state.reasoningText)) {
779
+ state.emittedReasoningLength = 0;
780
+ }
781
+ state.reasoningText = reasoningText;
782
+ }
783
+ return this.emitNewContent(state);
784
+ }
785
+ finalizeMessage(messageId, runId) {
786
+ const state = this.messages.get(messageId);
787
+ if (!state) {
788
+ return [];
789
+ }
790
+ state.stopped = true;
791
+ const items = this.emitNewContent(state);
792
+ if (runId && this.activeMessageByRun.get(runId) === messageId) {
793
+ this.activeMessageByRun.delete(runId);
794
+ }
795
+ return items;
796
+ }
797
+ emitNewContent(state) {
798
+ const items = [];
799
+ const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
800
+ if (nextAssistantText.length > 0 &&
801
+ nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER) {
802
+ state.emittedAssistantLength = state.assistantText.length;
803
+ items.push({ type: "assistant_message", text: nextAssistantText });
804
+ }
805
+ const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
806
+ if (nextReasoningText.length > 0) {
807
+ state.emittedReasoningLength = state.reasoningText.length;
808
+ items.push({ type: "reasoning", text: nextReasoningText });
809
+ }
810
+ return items;
811
+ }
812
+ ensureMessageState(messageId, runId) {
813
+ const existing = this.messages.get(messageId);
814
+ if (existing) {
815
+ existing.stopped = false;
816
+ if (runId) {
817
+ this.activeMessageByRun.set(runId, messageId);
818
+ }
819
+ return existing;
820
+ }
821
+ const created = {
822
+ id: messageId,
823
+ assistantText: "",
824
+ reasoningText: "",
825
+ emittedAssistantLength: 0,
826
+ emittedReasoningLength: 0,
827
+ stopped: false,
828
+ };
829
+ this.messages.set(messageId, created);
830
+ if (runId) {
831
+ this.activeMessageByRun.set(runId, messageId);
832
+ }
833
+ return created;
834
+ }
835
+ resolveMessageId(input) {
836
+ if (input.messageId) {
837
+ return input.messageId;
838
+ }
839
+ if (input.runId) {
840
+ const active = this.activeMessageByRun.get(input.runId);
841
+ if (active) {
842
+ return active;
843
+ }
844
+ }
845
+ if (!input.createIfMissing) {
846
+ return null;
847
+ }
848
+ const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
849
+ if (input.runId) {
850
+ this.activeMessageByRun.set(input.runId, synthetic);
851
+ }
852
+ return synthetic;
853
+ }
854
+ extractFragments(content) {
855
+ if (typeof content === "string") {
856
+ if (content.length === 0) {
857
+ return [];
858
+ }
859
+ return [{ kind: "assistant", text: content }];
860
+ }
861
+ const blocks = Array.isArray(content) ? content : [content];
862
+ const fragments = [];
863
+ for (const rawBlock of blocks) {
864
+ if (!isClaudeContentChunk(rawBlock)) {
865
+ continue;
866
+ }
867
+ if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
868
+ typeof rawBlock.text === "string" &&
869
+ rawBlock.text.length > 0) {
870
+ fragments.push({ kind: "assistant", text: rawBlock.text });
871
+ }
872
+ if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
873
+ typeof rawBlock.thinking === "string" &&
874
+ rawBlock.thinking.length > 0) {
875
+ fragments.push({ kind: "reasoning", text: rawBlock.thinking });
876
+ }
877
+ }
878
+ return fragments;
879
+ }
880
+ readMessageIdFromAssistantMessage(message) {
881
+ const candidate = message;
882
+ return readTrimmedString(candidate.message_id) ??
883
+ readTrimmedString(candidate.message?.id) ??
884
+ null;
885
+ }
886
+ readMessageIdFromStreamEvent(event) {
887
+ const message = event.message;
888
+ return (readTrimmedString(event.message_id) ??
889
+ readTrimmedString(message?.id) ??
890
+ null);
891
+ }
892
+ }
893
+ function isMetadataOnlySdkMessage(message) {
894
+ if (message.type === "system") {
895
+ return true;
896
+ }
897
+ if (message.type !== "user") {
898
+ return false;
899
+ }
900
+ if (isSyntheticUserEntry(message)) {
901
+ return true;
902
+ }
903
+ return isTaskNotificationUserContent(message.message?.content);
904
+ }
905
+ function isSyntheticUserEntry(entry) {
906
+ if (!entry || typeof entry !== "object") {
907
+ return false;
908
+ }
909
+ return entry.isSynthetic === true;
910
+ }
911
+ export function readEventIdentifiers(message) {
912
+ const root = message;
913
+ const messageType = readTrimmedString(root.type);
914
+ const streamEvent = root.event;
915
+ const streamEventMessage = streamEvent?.message;
916
+ const messageContainer = root.message;
917
+ return {
918
+ taskId: readTrimmedString(root.task_id) ??
919
+ readTrimmedString(streamEvent?.task_id) ??
920
+ readTrimmedString(streamEventMessage?.task_id) ??
921
+ readTrimmedString(messageContainer?.task_id) ??
922
+ null,
923
+ parentMessageId: readTrimmedString(root.parent_message_id) ??
924
+ readTrimmedString(streamEvent?.parent_message_id) ??
925
+ readTrimmedString(streamEventMessage?.parent_message_id) ??
926
+ readTrimmedString(messageContainer?.parent_message_id) ??
927
+ null,
928
+ messageId: readTrimmedString(root.message_id) ??
929
+ readTrimmedString(streamEvent?.message_id) ??
930
+ readTrimmedString(streamEventMessage?.id) ??
931
+ readTrimmedString(streamEventMessage?.message_id) ??
932
+ readTrimmedString(messageContainer?.id) ??
933
+ readTrimmedString(messageContainer?.message_id) ??
934
+ (messageType === "user" ? readTrimmedString(root.uuid) : null) ??
935
+ null,
936
+ };
937
+ }
938
+ export class ClaudeAgentClient {
939
+ constructor(options) {
940
+ this.provider = "claude";
941
+ this.capabilities = CLAUDE_CAPABILITIES;
942
+ this.defaults = options.defaults;
943
+ this.logger = options.logger.child({ module: "agent", provider: "claude" });
944
+ this.runtimeSettings = options.runtimeSettings;
945
+ try {
946
+ this.claudePath = execSync("which claude", { encoding: "utf8" }).trim() || null;
947
+ }
948
+ catch {
949
+ this.claudePath = null;
950
+ }
951
+ }
952
+ async createSession(config) {
953
+ const claudeConfig = this.assertConfig(config);
954
+ return new ClaudeAgentSession(claudeConfig, {
955
+ defaults: this.defaults,
956
+ claudePath: this.claudePath,
957
+ runtimeSettings: this.runtimeSettings,
958
+ logger: this.logger,
959
+ });
960
+ }
961
+ async resumeSession(handle, overrides) {
962
+ const metadata = coerceSessionMetadata(handle.metadata);
963
+ const merged = { ...metadata, ...overrides };
964
+ if (!merged.cwd) {
965
+ throw new Error("Claude resume requires the original working directory in metadata");
966
+ }
967
+ const mergedConfig = { ...merged, provider: "claude", cwd: merged.cwd };
968
+ const claudeConfig = this.assertConfig(mergedConfig);
969
+ return new ClaudeAgentSession(claudeConfig, {
970
+ defaults: this.defaults,
971
+ claudePath: this.claudePath,
972
+ runtimeSettings: this.runtimeSettings,
973
+ handle,
974
+ logger: this.logger,
975
+ });
976
+ }
977
+ async listModels(_options) {
978
+ return listClaudeCatalogModels();
979
+ }
980
+ async listPersistedAgents(options) {
981
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
982
+ const projectsRoot = path.join(configDir, "projects");
983
+ if (!(await pathExists(projectsRoot))) {
984
+ return [];
985
+ }
986
+ const limit = options?.limit ?? 20;
987
+ const candidates = await collectRecentClaudeSessions(projectsRoot, limit * 3);
988
+ const descriptors = [];
989
+ for (const candidate of candidates) {
990
+ const descriptor = await parseClaudeSessionDescriptor(candidate.path, candidate.mtime);
991
+ if (descriptor) {
992
+ descriptors.push(descriptor);
993
+ }
994
+ if (descriptors.length >= limit) {
995
+ break;
996
+ }
997
+ }
998
+ return descriptors;
999
+ }
1000
+ async isAvailable() {
1001
+ const commandConfig = this.runtimeSettings?.command;
1002
+ if (commandConfig?.mode === "replace") {
1003
+ return isProviderCommandAvailable(commandConfig, resolveClaudeBinary);
1004
+ }
1005
+ return this.claudePath !== null;
1006
+ }
1007
+ assertConfig(config) {
1008
+ if (config.provider !== "claude") {
1009
+ throw new Error(`ClaudeAgentClient received config for provider '${config.provider}'`);
1010
+ }
1011
+ return { ...config, provider: "claude" };
1012
+ }
1013
+ }
1014
+ class ClaudeAgentSession {
1015
+ constructor(config, options) {
1016
+ this.provider = "claude";
1017
+ this.capabilities = CLAUDE_CAPABILITIES;
1018
+ this.query = null;
1019
+ this.input = null;
1020
+ this.availableModes = DEFAULT_MODES;
1021
+ this.toolUseCache = new Map();
1022
+ this.toolUseIndexToId = new Map();
1023
+ this.toolUseInputBuffers = new Map();
1024
+ this.pendingPermissions = new Map();
1025
+ this.activeForegroundTurn = null;
1026
+ this.liveEventQueue = new Pushable();
1027
+ this.runTracker = new RunTracker();
1028
+ this.timelineAssembler = new TimelineAssembler();
1029
+ this.persistedHistory = [];
1030
+ this.historyPending = false;
1031
+ this.turnState = "idle";
1032
+ this.preReplayMetadataSeen = false;
1033
+ this.pendingAutonomousWakeReservations = 0;
1034
+ this.nextRunOrdinal = 1;
1035
+ this.cancelCurrentTurn = null;
1036
+ this.pendingInterruptPromise = null;
1037
+ this.activeTurnPromise = null;
1038
+ this.cachedRuntimeInfo = null;
1039
+ this.lastOptionsModel = null;
1040
+ this.selectableModelIds = buildClaudeSelectableModelIds();
1041
+ this.selectableModelFamilyAliases = buildClaudeModelFamilyAliases();
1042
+ this.activeSidechains = new Map();
1043
+ this.compacting = false;
1044
+ this.queryPumpPromise = null;
1045
+ this.queryRestartNeeded = false;
1046
+ this.userMessageIds = [];
1047
+ this.localUserMessageIds = new Set();
1048
+ this.suppressLocalReplayActivity = false;
1049
+ this.recentStderr = "";
1050
+ this.closed = false;
1051
+ this.handlePermissionRequest = async (toolName, input, options) => {
1052
+ const requestId = `permission-${randomUUID()}`;
1053
+ const kind = resolvePermissionKind(toolName, input);
1054
+ const metadata = {};
1055
+ if (options.toolUseID) {
1056
+ metadata.toolUseId = options.toolUseID;
1057
+ }
1058
+ if (toolName === "ExitPlanMode" && typeof input.plan === "string") {
1059
+ metadata.planText = input.plan;
1060
+ }
1061
+ const toolDetail = kind === "tool"
1062
+ ? mapClaudeRunningToolCall({
1063
+ name: toolName,
1064
+ callId: options.toolUseID ?? requestId,
1065
+ input,
1066
+ output: null,
1067
+ })?.detail
1068
+ : undefined;
1069
+ const request = {
1070
+ id: requestId,
1071
+ provider: "claude",
1072
+ name: toolName,
1073
+ kind,
1074
+ input,
1075
+ detail: toolDetail,
1076
+ suggestions: options.suggestions?.map((suggestion) => ({ ...suggestion })),
1077
+ metadata: Object.keys(metadata).length ? metadata : undefined,
1078
+ };
1079
+ this.pushEvent({ type: "permission_requested", provider: "claude", request });
1080
+ return await new Promise((resolve, reject) => {
1081
+ const cleanupFns = [];
1082
+ const cleanup = () => {
1083
+ while (cleanupFns.length) {
1084
+ const fn = cleanupFns.pop();
1085
+ try {
1086
+ fn?.();
1087
+ }
1088
+ catch {
1089
+ // ignore cleanup errors
1090
+ }
1091
+ }
1092
+ };
1093
+ const abortHandler = () => {
1094
+ this.pendingPermissions.delete(requestId);
1095
+ cleanup();
1096
+ reject(new Error("Permission request aborted"));
1097
+ };
1098
+ if (options?.signal) {
1099
+ if (options.signal.aborted) {
1100
+ abortHandler();
1101
+ return;
1102
+ }
1103
+ options.signal.addEventListener("abort", abortHandler, { once: true });
1104
+ cleanupFns.push(() => options.signal?.removeEventListener("abort", abortHandler));
1105
+ }
1106
+ this.pendingPermissions.set(requestId, {
1107
+ request,
1108
+ resolve,
1109
+ reject,
1110
+ cleanup,
1111
+ });
1112
+ });
1113
+ };
1114
+ this.config = config;
1115
+ this.defaults = options.defaults;
1116
+ this.claudePath = options.claudePath;
1117
+ this.runtimeSettings = options.runtimeSettings;
1118
+ this.logger = options.logger;
1119
+ const handle = options.handle;
1120
+ if (handle) {
1121
+ if (!handle.sessionId) {
1122
+ throw new Error("Cannot resume: persistence handle has no sessionId");
1123
+ }
1124
+ this.claudeSessionId = handle.sessionId;
1125
+ this.persistence = handle;
1126
+ this.loadPersistedHistory(handle.sessionId);
1127
+ }
1128
+ else {
1129
+ this.claudeSessionId = null;
1130
+ this.persistence = null;
1131
+ }
1132
+ // Validate mode if provided
1133
+ if (config.modeId && !VALID_CLAUDE_MODES.has(config.modeId)) {
1134
+ const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
1135
+ throw new Error(`Invalid mode '${config.modeId}' for Claude provider. Valid modes: ${validModesList}`);
1136
+ }
1137
+ this.currentMode = isPermissionMode(config.modeId) ? config.modeId : "default";
1138
+ }
1139
+ get id() {
1140
+ return this.claudeSessionId;
1141
+ }
1142
+ async getRuntimeInfo() {
1143
+ if (this.cachedRuntimeInfo) {
1144
+ return { ...this.cachedRuntimeInfo };
1145
+ }
1146
+ const info = {
1147
+ provider: "claude",
1148
+ sessionId: this.claudeSessionId,
1149
+ model: this.lastOptionsModel,
1150
+ modeId: this.currentMode ?? null,
1151
+ };
1152
+ this.cachedRuntimeInfo = info;
1153
+ return { ...info };
1154
+ }
1155
+ async run(prompt, options) {
1156
+ const events = this.stream(prompt, options);
1157
+ const timeline = [];
1158
+ let finalText = "";
1159
+ let usage;
1160
+ for await (const event of events) {
1161
+ if (event.type === "timeline") {
1162
+ timeline.push(event.item);
1163
+ if (event.item.type === "assistant_message") {
1164
+ if (!finalText) {
1165
+ finalText = event.item.text;
1166
+ }
1167
+ else if (event.item.text.startsWith(finalText)) {
1168
+ finalText = event.item.text;
1169
+ }
1170
+ else {
1171
+ finalText += event.item.text;
1172
+ }
1173
+ }
1174
+ }
1175
+ else if (event.type === "turn_completed") {
1176
+ usage = event.usage;
1177
+ }
1178
+ else if (event.type === "turn_failed") {
1179
+ throw new Error(event.error);
1180
+ }
1181
+ }
1182
+ this.cachedRuntimeInfo = {
1183
+ provider: "claude",
1184
+ sessionId: this.claudeSessionId,
1185
+ model: this.lastOptionsModel,
1186
+ modeId: this.currentMode ?? null,
1187
+ };
1188
+ if (!this.claudeSessionId) {
1189
+ throw new Error("Session ID not set after run completed");
1190
+ }
1191
+ return {
1192
+ sessionId: this.claudeSessionId,
1193
+ finalText,
1194
+ usage,
1195
+ timeline,
1196
+ };
1197
+ }
1198
+ async *stream(prompt, options) {
1199
+ void options;
1200
+ if (this.cancelCurrentTurn) {
1201
+ this.cancelCurrentTurn();
1202
+ }
1203
+ this.suppressLocalReplayActivity = false;
1204
+ this.pendingAutonomousWakeReservations = 0;
1205
+ const slashCommand = this.resolveSlashCommandInvocation(prompt);
1206
+ if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
1207
+ yield* this.streamRewindCommand(slashCommand);
1208
+ return;
1209
+ }
1210
+ await this.awaitPendingInterruptPromise();
1211
+ if (this.turnState === "autonomous" &&
1212
+ this.runTracker.hasActiveRuns("autonomous")) {
1213
+ await this.transitionAutonomousToForeground();
1214
+ }
1215
+ const sdkMessage = this.toSdkUserMessage(prompt);
1216
+ const queue = new Pushable();
1217
+ const run = this.createRun("foreground", queue);
1218
+ this.runTracker.bindIdentifiers(run, {
1219
+ taskId: null,
1220
+ parentMessageId: null,
1221
+ messageId: typeof sdkMessage.uuid === "string" ? sdkMessage.uuid : null,
1222
+ });
1223
+ const foregroundTurn = {
1224
+ runId: run.id,
1225
+ queue,
1226
+ };
1227
+ this.activeForegroundTurn = foregroundTurn;
1228
+ this.preReplayMetadataSeen = false;
1229
+ this.transitionTurnState("foreground", "foreground stream started");
1230
+ this.clearRecentStderr();
1231
+ let finishedNaturally = false;
1232
+ let cancelIssued = false;
1233
+ let queueDrainedWithoutTerminal = false;
1234
+ const turnPromise = Promise.resolve();
1235
+ this.activeTurnPromise = turnPromise;
1236
+ const requestCancel = () => {
1237
+ if (cancelIssued) {
1238
+ return;
1239
+ }
1240
+ cancelIssued = true;
1241
+ if (this.activeForegroundTurn?.runId === run.id) {
1242
+ this.activeForegroundTurn = null;
1243
+ }
1244
+ if (this.cancelCurrentTurn === requestCancel) {
1245
+ this.cancelCurrentTurn = null;
1246
+ }
1247
+ this.rejectAllPendingPermissions(new Error("Permission request aborted"));
1248
+ this.cancelRun(run, {
1249
+ type: "turn_canceled",
1250
+ provider: "claude",
1251
+ reason: "Interrupted",
1252
+ });
1253
+ this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
1254
+ this.logger.warn({ err: error }, "Failed to interrupt during cancel");
1255
+ });
1256
+ };
1257
+ this.cancelCurrentTurn = requestCancel;
1258
+ try {
1259
+ await this.ensureQuery();
1260
+ if (!this.input) {
1261
+ throw new Error("Claude session input stream not initialized");
1262
+ }
1263
+ this.startQueryPump();
1264
+ this.input.push(sdkMessage);
1265
+ }
1266
+ catch (error) {
1267
+ this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
1268
+ finishedNaturally = true;
1269
+ }
1270
+ try {
1271
+ for await (const event of queue) {
1272
+ const isTerminalEvent = event.type === "turn_completed" ||
1273
+ event.type === "turn_failed" ||
1274
+ event.type === "turn_canceled";
1275
+ if (isTerminalEvent) {
1276
+ finishedNaturally = true;
1277
+ }
1278
+ yield event;
1279
+ if (isTerminalEvent) {
1280
+ break;
1281
+ }
1282
+ }
1283
+ if (!finishedNaturally && !cancelIssued) {
1284
+ queueDrainedWithoutTerminal = true;
1285
+ }
1286
+ }
1287
+ finally {
1288
+ if (!finishedNaturally && !cancelIssued && !queueDrainedWithoutTerminal) {
1289
+ requestCancel();
1290
+ }
1291
+ if (this.activeForegroundTurn === foregroundTurn) {
1292
+ this.activeForegroundTurn = null;
1293
+ }
1294
+ if (this.cancelCurrentTurn === requestCancel) {
1295
+ this.cancelCurrentTurn = null;
1296
+ }
1297
+ if (this.activeTurnPromise === turnPromise) {
1298
+ this.activeTurnPromise = null;
1299
+ }
1300
+ }
1301
+ }
1302
+ async interrupt() {
1303
+ if (this.cancelCurrentTurn) {
1304
+ this.cancelCurrentTurn();
1305
+ return;
1306
+ }
1307
+ const autonomousRuns = this.runTracker.listActiveRuns("autonomous");
1308
+ if (autonomousRuns.length > 0) {
1309
+ this.flushPendingToolCalls();
1310
+ for (const run of autonomousRuns) {
1311
+ this.emitRunEvent(run, {
1312
+ type: "turn_canceled",
1313
+ provider: "claude",
1314
+ reason: "Interrupted",
1315
+ });
1316
+ }
1317
+ }
1318
+ await this.interruptActiveTurn();
1319
+ }
1320
+ async *streamHistory() {
1321
+ if (!this.historyPending || this.persistedHistory.length === 0) {
1322
+ return;
1323
+ }
1324
+ const history = this.persistedHistory;
1325
+ this.persistedHistory = [];
1326
+ this.historyPending = false;
1327
+ for (const item of history) {
1328
+ yield { type: "timeline", item, provider: "claude" };
1329
+ }
1330
+ }
1331
+ async *streamLiveEvents() {
1332
+ if (this.claudeSessionId) {
1333
+ this.startQueryPump();
1334
+ }
1335
+ for await (const event of this.liveEventQueue) {
1336
+ yield event;
1337
+ }
1338
+ }
1339
+ async getAvailableModes() {
1340
+ return this.availableModes;
1341
+ }
1342
+ async getCurrentMode() {
1343
+ return this.currentMode ?? null;
1344
+ }
1345
+ async setMode(modeId) {
1346
+ // Validate mode
1347
+ if (!VALID_CLAUDE_MODES.has(modeId)) {
1348
+ const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
1349
+ throw new Error(`Invalid mode '${modeId}' for Claude provider. Valid modes: ${validModesList}`);
1350
+ }
1351
+ const normalized = isPermissionMode(modeId) ? modeId : "default";
1352
+ const query = await this.ensureQuery();
1353
+ await query.setPermissionMode(normalized);
1354
+ this.currentMode = normalized;
1355
+ }
1356
+ async setModel(modelId) {
1357
+ const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
1358
+ const query = await this.ensureQuery();
1359
+ await query.setModel(normalizedModelId ?? undefined);
1360
+ this.config.model = normalizedModelId ?? undefined;
1361
+ this.lastOptionsModel = normalizedModelId ?? this.lastOptionsModel;
1362
+ this.cachedRuntimeInfo = null;
1363
+ // Model change affects persistence metadata, so invalidate cached handle.
1364
+ this.persistence = null;
1365
+ }
1366
+ async setThinkingOption(thinkingOptionId) {
1367
+ const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
1368
+ ? thinkingOptionId
1369
+ : null;
1370
+ if (!normalizedThinkingOptionId || normalizedThinkingOptionId === "default") {
1371
+ this.config.thinkingOptionId = undefined;
1372
+ }
1373
+ else if (normalizedThinkingOptionId === "on") {
1374
+ this.config.thinkingOptionId = "on";
1375
+ }
1376
+ else if (normalizedThinkingOptionId === "off") {
1377
+ this.config.thinkingOptionId = "off";
1378
+ }
1379
+ else {
1380
+ throw new Error(`Unknown thinking option: ${normalizedThinkingOptionId}`);
1381
+ }
1382
+ this.queryRestartNeeded = true;
1383
+ }
1384
+ getPendingPermissions() {
1385
+ return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
1386
+ }
1387
+ async respondToPermission(requestId, response) {
1388
+ const pending = this.pendingPermissions.get(requestId);
1389
+ if (!pending) {
1390
+ throw new Error(`No pending permission request with id '${requestId}'`);
1391
+ }
1392
+ this.pendingPermissions.delete(requestId);
1393
+ pending.cleanup?.();
1394
+ if (response.behavior === "allow") {
1395
+ if (pending.request.kind === "plan") {
1396
+ await this.setMode("acceptEdits");
1397
+ this.pushToolCall(mapClaudeCompletedToolCall({
1398
+ name: "plan_approval",
1399
+ callId: pending.request.id,
1400
+ input: pending.request.input ?? null,
1401
+ output: { approved: true },
1402
+ }));
1403
+ }
1404
+ const result = {
1405
+ behavior: "allow",
1406
+ updatedInput: response.updatedInput ?? pending.request.input ?? {},
1407
+ updatedPermissions: this.normalizePermissionUpdates(response.updatedPermissions),
1408
+ };
1409
+ pending.resolve(result);
1410
+ }
1411
+ else {
1412
+ if (pending.request.kind === "tool") {
1413
+ this.pushToolCall(mapClaudeFailedToolCall({
1414
+ name: pending.request.name,
1415
+ callId: (typeof pending.request.metadata?.toolUseId === "string"
1416
+ ? pending.request.metadata.toolUseId
1417
+ : null) ?? pending.request.id,
1418
+ input: pending.request.input ?? null,
1419
+ output: null,
1420
+ error: { message: response.message ?? "Permission denied" },
1421
+ }));
1422
+ }
1423
+ const result = {
1424
+ behavior: "deny",
1425
+ message: response.message ?? "Permission request denied",
1426
+ interrupt: response.interrupt,
1427
+ };
1428
+ pending.resolve(result);
1429
+ }
1430
+ this.pushEvent({
1431
+ type: "permission_resolved",
1432
+ provider: "claude",
1433
+ requestId,
1434
+ resolution: response,
1435
+ });
1436
+ }
1437
+ describePersistence() {
1438
+ if (this.persistence) {
1439
+ return this.persistence;
1440
+ }
1441
+ if (!this.claudeSessionId) {
1442
+ return null;
1443
+ }
1444
+ this.persistence = {
1445
+ provider: "claude",
1446
+ sessionId: this.claudeSessionId,
1447
+ nativeHandle: this.claudeSessionId,
1448
+ metadata: this.config,
1449
+ };
1450
+ return this.persistence;
1451
+ }
1452
+ async close() {
1453
+ this.logger.trace({
1454
+ claudeSessionId: this.claudeSessionId,
1455
+ turnState: this.turnState,
1456
+ hasQuery: Boolean(this.query),
1457
+ hasInput: Boolean(this.input),
1458
+ hasActiveForegroundTurn: Boolean(this.activeForegroundTurn),
1459
+ }, "Claude session close: start");
1460
+ this.closed = true;
1461
+ this.rejectAllPendingPermissions(new Error("Claude session closed"));
1462
+ this.cancelCurrentTurn?.();
1463
+ this.activeForegroundTurn?.queue.end();
1464
+ this.activeForegroundTurn = null;
1465
+ this.cancelCurrentTurn = null;
1466
+ this.turnState = "idle";
1467
+ this.suppressLocalReplayActivity = false;
1468
+ this.pendingAutonomousWakeReservations = 0;
1469
+ this.liveEventQueue.end();
1470
+ this.activeTurnPromise = null;
1471
+ this.input?.end();
1472
+ await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
1473
+ await this.awaitWithTimeout(this.query?.return?.(), "close query return");
1474
+ this.query = null;
1475
+ this.input = null;
1476
+ this.logger.trace({ claudeSessionId: this.claudeSessionId, turnState: this.turnState }, "Claude session close: completed");
1477
+ }
1478
+ async listCommands() {
1479
+ const q = await this.ensureQuery();
1480
+ const commands = await q.supportedCommands();
1481
+ const commandMap = new Map();
1482
+ for (const cmd of commands) {
1483
+ if (!commandMap.has(cmd.name)) {
1484
+ commandMap.set(cmd.name, {
1485
+ name: cmd.name,
1486
+ description: cmd.description,
1487
+ argumentHint: cmd.argumentHint,
1488
+ });
1489
+ }
1490
+ }
1491
+ if (!commandMap.has(REWIND_COMMAND_NAME)) {
1492
+ commandMap.set(REWIND_COMMAND_NAME, REWIND_COMMAND);
1493
+ }
1494
+ return Array.from(commandMap.values()).sort((a, b) => a.name.localeCompare(b.name));
1495
+ }
1496
+ resolveSlashCommandInvocation(prompt) {
1497
+ if (typeof prompt !== "string") {
1498
+ return null;
1499
+ }
1500
+ const parsed = this.parseSlashCommandInput(prompt);
1501
+ if (!parsed) {
1502
+ return null;
1503
+ }
1504
+ return parsed.commandName === REWIND_COMMAND_NAME ? parsed : null;
1505
+ }
1506
+ parseSlashCommandInput(text) {
1507
+ const trimmed = text.trim();
1508
+ if (!trimmed.startsWith("/") || trimmed.length <= 1) {
1509
+ return null;
1510
+ }
1511
+ const withoutPrefix = trimmed.slice(1);
1512
+ const firstWhitespaceIdx = withoutPrefix.search(/\s/);
1513
+ const commandName = firstWhitespaceIdx === -1
1514
+ ? withoutPrefix
1515
+ : withoutPrefix.slice(0, firstWhitespaceIdx);
1516
+ if (!commandName || commandName.includes("/")) {
1517
+ return null;
1518
+ }
1519
+ const rawArgs = firstWhitespaceIdx === -1
1520
+ ? ""
1521
+ : withoutPrefix.slice(firstWhitespaceIdx + 1).trim();
1522
+ return rawArgs.length > 0
1523
+ ? { commandName, args: rawArgs, rawInput: trimmed }
1524
+ : { commandName, rawInput: trimmed };
1525
+ }
1526
+ async *streamRewindCommand(invocation) {
1527
+ yield { type: "turn_started", provider: "claude" };
1528
+ try {
1529
+ const rewindAttempt = await this.attemptRewind(invocation.args);
1530
+ if (!rewindAttempt.messageId || !rewindAttempt.result) {
1531
+ yield {
1532
+ type: "turn_failed",
1533
+ provider: "claude",
1534
+ error: rewindAttempt.error ??
1535
+ "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
1536
+ };
1537
+ return;
1538
+ }
1539
+ yield {
1540
+ type: "timeline",
1541
+ provider: "claude",
1542
+ item: {
1543
+ type: "assistant_message",
1544
+ text: this.buildRewindSuccessMessage(rewindAttempt.messageId, rewindAttempt.result),
1545
+ },
1546
+ };
1547
+ yield { type: "turn_completed", provider: "claude" };
1548
+ }
1549
+ catch (error) {
1550
+ yield {
1551
+ type: "turn_failed",
1552
+ provider: "claude",
1553
+ error: error instanceof Error
1554
+ ? error.message
1555
+ : "Failed to rewind tracked files",
1556
+ };
1557
+ }
1558
+ }
1559
+ buildRewindSuccessMessage(targetUserMessageId, rewindResult) {
1560
+ const fileCount = Array.isArray(rewindResult.filesChanged)
1561
+ ? rewindResult.filesChanged.length
1562
+ : undefined;
1563
+ const stats = [];
1564
+ if (typeof fileCount === "number") {
1565
+ stats.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
1566
+ }
1567
+ if (typeof rewindResult.insertions === "number") {
1568
+ stats.push(`${rewindResult.insertions} insertions`);
1569
+ }
1570
+ if (typeof rewindResult.deletions === "number") {
1571
+ stats.push(`${rewindResult.deletions} deletions`);
1572
+ }
1573
+ if (stats.length > 0) {
1574
+ return `Rewound tracked files to message ${targetUserMessageId} (${stats.join(", ")}).`;
1575
+ }
1576
+ return `Rewound tracked files to message ${targetUserMessageId}.`;
1577
+ }
1578
+ async attemptRewind(args) {
1579
+ if (typeof args === "string" && args.trim().length > 0) {
1580
+ const candidate = args.trim().split(/\s+/)[0] ?? "";
1581
+ if (!UUID_PATTERN.test(candidate)) {
1582
+ return {
1583
+ messageId: null,
1584
+ error: "Invalid message UUID. Usage: /rewind <user_message_uuid> or /rewind",
1585
+ };
1586
+ }
1587
+ const rewindResult = await this.rewindFilesOnce(candidate);
1588
+ if (rewindResult.canRewind) {
1589
+ return { messageId: candidate, result: rewindResult };
1590
+ }
1591
+ return {
1592
+ messageId: null,
1593
+ error: rewindResult.error ??
1594
+ `No file checkpoint found for message ${candidate}.`,
1595
+ };
1596
+ }
1597
+ const candidates = this.getRewindCandidateUserMessageIds();
1598
+ if (candidates.length === 0) {
1599
+ return {
1600
+ messageId: null,
1601
+ error: "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
1602
+ };
1603
+ }
1604
+ let lastError;
1605
+ for (const candidate of candidates) {
1606
+ try {
1607
+ const rewindResult = await this.rewindFilesOnce(candidate);
1608
+ if (rewindResult.canRewind) {
1609
+ return { messageId: candidate, result: rewindResult };
1610
+ }
1611
+ if (rewindResult.error) {
1612
+ lastError = rewindResult.error;
1613
+ }
1614
+ }
1615
+ catch (error) {
1616
+ lastError =
1617
+ error instanceof Error
1618
+ ? error.message
1619
+ : "Failed to rewind tracked files.";
1620
+ }
1621
+ }
1622
+ return {
1623
+ messageId: null,
1624
+ error: lastError ??
1625
+ "No rewind checkpoints are currently available for this session.",
1626
+ };
1627
+ }
1628
+ async rewindFilesOnce(messageId) {
1629
+ try {
1630
+ const query = await this.ensureFreshQuery();
1631
+ return await query.rewindFiles(messageId, { dryRun: false });
1632
+ }
1633
+ catch (error) {
1634
+ // The Claude SDK transport can close after a rewind call.
1635
+ // If that happens, mark the query stale so a follow-up attempt uses a fresh query.
1636
+ this.queryRestartNeeded = true;
1637
+ throw error;
1638
+ }
1639
+ }
1640
+ async ensureFreshQuery() {
1641
+ if (this.query) {
1642
+ this.queryRestartNeeded = true;
1643
+ }
1644
+ return this.ensureQuery();
1645
+ }
1646
+ getRewindCandidateUserMessageIds() {
1647
+ const candidates = [];
1648
+ const pushUnique = (value) => {
1649
+ if (typeof value === "string" &&
1650
+ value.length > 0 &&
1651
+ !candidates.includes(value)) {
1652
+ candidates.push(value);
1653
+ }
1654
+ };
1655
+ const historyIds = this.readUserMessageIdsFromHistoryFile();
1656
+ for (let idx = historyIds.length - 1; idx >= 0; idx -= 1) {
1657
+ pushUnique(historyIds[idx]);
1658
+ }
1659
+ for (let idx = this.persistedHistory.length - 1; idx >= 0; idx -= 1) {
1660
+ const item = this.persistedHistory[idx];
1661
+ if (item?.type === "user_message") {
1662
+ pushUnique(item.messageId);
1663
+ }
1664
+ }
1665
+ for (let idx = this.userMessageIds.length - 1; idx >= 0; idx -= 1) {
1666
+ pushUnique(this.userMessageIds[idx]);
1667
+ }
1668
+ return candidates;
1669
+ }
1670
+ readUserMessageIdsFromHistoryFile() {
1671
+ if (!this.claudeSessionId) {
1672
+ return [];
1673
+ }
1674
+ const historyPath = this.resolveHistoryPath(this.claudeSessionId);
1675
+ if (!historyPath || !fs.existsSync(historyPath)) {
1676
+ return [];
1677
+ }
1678
+ try {
1679
+ const ids = [];
1680
+ const content = fs.readFileSync(historyPath, "utf8");
1681
+ for (const line of content.split(/\n+/)) {
1682
+ const trimmed = line.trim();
1683
+ if (!trimmed)
1684
+ continue;
1685
+ try {
1686
+ const entry = JSON.parse(trimmed);
1687
+ if (entry?.type === "user" && typeof entry.uuid === "string") {
1688
+ ids.push(entry.uuid);
1689
+ }
1690
+ }
1691
+ catch {
1692
+ // ignore malformed lines
1693
+ }
1694
+ }
1695
+ return ids;
1696
+ }
1697
+ catch {
1698
+ return [];
1699
+ }
1700
+ }
1701
+ rememberUserMessageId(messageId) {
1702
+ if (typeof messageId !== "string" || messageId.length === 0) {
1703
+ return;
1704
+ }
1705
+ const last = this.userMessageIds[this.userMessageIds.length - 1];
1706
+ if (last === messageId) {
1707
+ return;
1708
+ }
1709
+ this.userMessageIds.push(messageId);
1710
+ }
1711
+ async ensureQuery() {
1712
+ if (this.query && !this.queryRestartNeeded) {
1713
+ return this.query;
1714
+ }
1715
+ if (this.queryRestartNeeded && this.query) {
1716
+ this.input?.end();
1717
+ try {
1718
+ await this.query.return?.();
1719
+ }
1720
+ catch { /* ignore */ }
1721
+ this.query = null;
1722
+ this.input = null;
1723
+ this.queryRestartNeeded = false;
1724
+ }
1725
+ const input = new Pushable();
1726
+ const options = this.buildOptions();
1727
+ this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
1728
+ this.input = input;
1729
+ this.query = query({ prompt: input, options });
1730
+ // Do not kick off background control-plane queries here. Methods like
1731
+ // supportedCommands()/setPermissionMode() may execute immediately after
1732
+ // ensureQuery() (for listCommands()/setMode()), and sharing the same query
1733
+ // control plane can cause those calls to wait behind supportedModels().
1734
+ return this.query;
1735
+ }
1736
+ async awaitWithTimeout(promise, label) {
1737
+ if (!promise) {
1738
+ this.logger.trace({ label }, "Claude query operation skipped (no promise)");
1739
+ return;
1740
+ }
1741
+ const startedAt = Date.now();
1742
+ this.logger.trace({ label }, "Claude query operation wait start");
1743
+ try {
1744
+ await Promise.race([
1745
+ promise,
1746
+ new Promise((_, reject) => {
1747
+ setTimeout(() => reject(new Error("timeout")), 3000);
1748
+ }),
1749
+ ]);
1750
+ this.logger.trace({ label, durationMs: Date.now() - startedAt }, "Claude query operation settled");
1751
+ }
1752
+ catch (error) {
1753
+ this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
1754
+ }
1755
+ }
1756
+ buildOptions() {
1757
+ const configuredThinkingOptionId = this.config.thinkingOptionId;
1758
+ const thinkingOptionId = configuredThinkingOptionId && configuredThinkingOptionId !== "default"
1759
+ ? configuredThinkingOptionId
1760
+ : "off";
1761
+ let maxThinkingTokens;
1762
+ if (thinkingOptionId === "on") {
1763
+ maxThinkingTokens = 10000;
1764
+ }
1765
+ else if (thinkingOptionId === "off") {
1766
+ maxThinkingTokens = 0;
1767
+ }
1768
+ const appendedSystemPrompt = [
1769
+ getOrchestratorModeInstructions(),
1770
+ this.config.systemPrompt?.trim(),
1771
+ ]
1772
+ .filter((entry) => typeof entry === "string" && entry.length > 0)
1773
+ .join("\n\n");
1774
+ const base = {
1775
+ cwd: this.config.cwd,
1776
+ includePartialMessages: true,
1777
+ permissionMode: this.currentMode,
1778
+ agents: this.defaults?.agents,
1779
+ canUseTool: this.handlePermissionRequest,
1780
+ ...(this.claudePath ? { pathToClaudeCodeExecutable: this.claudePath } : {}),
1781
+ // Use Claude Code preset system prompt and load CLAUDE.md files
1782
+ // Append provider-agnostic system prompt and orchestrator instructions for agents.
1783
+ systemPrompt: {
1784
+ type: "preset",
1785
+ preset: "claude_code",
1786
+ append: appendedSystemPrompt,
1787
+ },
1788
+ settingSources: CLAUDE_SETTING_SOURCES,
1789
+ stderr: (data) => {
1790
+ this.captureStderr(data);
1791
+ this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
1792
+ },
1793
+ env: {
1794
+ ...process.env,
1795
+ // Increase MCP timeouts for long-running tool calls (10 minutes)
1796
+ MCP_TIMEOUT: "600000",
1797
+ MCP_TOOL_TIMEOUT: "600000",
1798
+ },
1799
+ // Required for provider-level /rewind support.
1800
+ enableFileCheckpointing: true,
1801
+ // If we have a session ID from a previous query (e.g., after interrupt),
1802
+ // resume that session to continue the conversation history.
1803
+ ...(this.claudeSessionId ? { resume: this.claudeSessionId } : {}),
1804
+ ...(maxThinkingTokens !== undefined ? { maxThinkingTokens } : {}),
1805
+ ...this.config.extra?.claude,
1806
+ };
1807
+ if (this.config.mcpServers) {
1808
+ base.mcpServers = this.normalizeMcpServers(this.config.mcpServers);
1809
+ }
1810
+ if (this.config.model) {
1811
+ base.model = this.config.model;
1812
+ }
1813
+ this.lastOptionsModel = base.model ?? null;
1814
+ if (this.claudeSessionId) {
1815
+ base.resume = this.claudeSessionId;
1816
+ }
1817
+ return this.applyRuntimeSettings(base);
1818
+ }
1819
+ applyRuntimeSettings(options) {
1820
+ return applyRuntimeSettingsToClaudeOptions(options, this.runtimeSettings);
1821
+ }
1822
+ normalizeMcpServers(servers) {
1823
+ const result = {};
1824
+ for (const [name, config] of Object.entries(servers)) {
1825
+ result[name] = toClaudeSdkMcpConfig(config);
1826
+ }
1827
+ return result;
1828
+ }
1829
+ toSdkUserMessage(prompt) {
1830
+ const content = [];
1831
+ if (Array.isArray(prompt)) {
1832
+ for (const chunk of prompt) {
1833
+ if (chunk.type === "text") {
1834
+ content.push({ type: "text", text: chunk.text });
1835
+ }
1836
+ else if (chunk.type === "image") {
1837
+ content.push({
1838
+ type: "image",
1839
+ source: {
1840
+ type: "base64",
1841
+ media_type: chunk.mimeType,
1842
+ data: chunk.data,
1843
+ },
1844
+ });
1845
+ }
1846
+ }
1847
+ }
1848
+ else {
1849
+ content.push({ type: "text", text: prompt });
1850
+ }
1851
+ const messageId = randomUUID();
1852
+ this.rememberUserMessageId(messageId);
1853
+ this.localUserMessageIds.add(messageId);
1854
+ return {
1855
+ type: "user",
1856
+ message: {
1857
+ role: "user",
1858
+ content,
1859
+ },
1860
+ parent_tool_use_id: null,
1861
+ uuid: messageId,
1862
+ session_id: this.claudeSessionId ?? "",
1863
+ };
1864
+ }
1865
+ async awaitPendingInterruptPromise() {
1866
+ if (!this.pendingInterruptPromise) {
1867
+ return;
1868
+ }
1869
+ await this.pendingInterruptPromise;
1870
+ this.pendingInterruptPromise = null;
1871
+ }
1872
+ createRun(owner, queue) {
1873
+ const runId = `${owner}-run-${this.nextRunOrdinal++}`;
1874
+ const run = this.runTracker.createRun({
1875
+ id: runId,
1876
+ owner,
1877
+ queue,
1878
+ promptReplaySeen: owner === "autonomous",
1879
+ });
1880
+ this.logger.debug({ runId, owner, state: run.state }, "Created Claude run");
1881
+ return run;
1882
+ }
1883
+ transitionTurnState(next, reason) {
1884
+ if (this.turnState === next) {
1885
+ return;
1886
+ }
1887
+ this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
1888
+ this.turnState = next;
1889
+ }
1890
+ transitionTurnStateFromActiveRuns(reason) {
1891
+ if (this.runTracker.hasActiveRuns("foreground")) {
1892
+ this.transitionTurnState("foreground", reason);
1893
+ return;
1894
+ }
1895
+ if (this.runTracker.hasActiveRuns("autonomous")) {
1896
+ this.transitionTurnState("autonomous", reason);
1897
+ return;
1898
+ }
1899
+ this.transitionTurnState("idle", reason);
1900
+ }
1901
+ failRun(run, errorMessage) {
1902
+ this.emitRunEvent(run, this.buildTurnFailedEvent(errorMessage));
1903
+ }
1904
+ buildTurnFailedEvent(errorMessage) {
1905
+ const normalized = errorMessage.trim() || "Claude run failed";
1906
+ const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
1907
+ const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
1908
+ const diagnostic = this.getRecentStderrDiagnostic();
1909
+ return {
1910
+ type: "turn_failed",
1911
+ provider: "claude",
1912
+ error: normalized,
1913
+ ...(code ? { code } : {}),
1914
+ ...(diagnostic ? { diagnostic } : {}),
1915
+ };
1916
+ }
1917
+ captureStderr(data) {
1918
+ const text = data.trim();
1919
+ if (!text) {
1920
+ return;
1921
+ }
1922
+ const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
1923
+ this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
1924
+ }
1925
+ clearRecentStderr() {
1926
+ this.recentStderr = "";
1927
+ }
1928
+ getRecentStderrDiagnostic() {
1929
+ const text = this.recentStderr.trim();
1930
+ return text.length > 0 ? text : undefined;
1931
+ }
1932
+ cancelRun(run, event) {
1933
+ this.flushPendingToolCalls();
1934
+ this.emitRunEvent(run, event);
1935
+ }
1936
+ emitRunEvent(run, event) {
1937
+ if (event.type === "turn_started" ||
1938
+ event.type === "turn_completed" ||
1939
+ event.type === "turn_failed" ||
1940
+ event.type === "turn_canceled") {
1941
+ this.logger.trace({
1942
+ runId: run.id,
1943
+ owner: run.owner,
1944
+ runState: run.state,
1945
+ eventType: event.type,
1946
+ routedTo: run.owner === "foreground" && run.queue ? "foreground_queue" : "live_queue",
1947
+ }, "Claude run event emitted");
1948
+ }
1949
+ if (run.owner === "foreground" && run.queue) {
1950
+ run.queue.push(event);
1951
+ if (event.type === "turn_completed" ||
1952
+ event.type === "turn_failed" ||
1953
+ event.type === "turn_canceled") {
1954
+ run.queue.end();
1955
+ }
1956
+ }
1957
+ else {
1958
+ this.liveEventQueue.push(event);
1959
+ }
1960
+ this.handleRunTerminalEvent(run, event);
1961
+ }
1962
+ handleRunTerminalEvent(run, event) {
1963
+ if (event.type === "turn_completed") {
1964
+ this.runTracker.complete(run, "completed");
1965
+ }
1966
+ else if (event.type === "turn_failed") {
1967
+ this.runTracker.complete(run, "error");
1968
+ }
1969
+ else if (event.type === "turn_canceled") {
1970
+ this.runTracker.complete(run, "interrupted");
1971
+ }
1972
+ else {
1973
+ return;
1974
+ }
1975
+ if (this.activeForegroundTurn?.runId === run.id) {
1976
+ this.activeForegroundTurn = null;
1977
+ this.preReplayMetadataSeen = false;
1978
+ }
1979
+ this.logger.trace({
1980
+ runId: run.id,
1981
+ owner: run.owner,
1982
+ eventType: event.type,
1983
+ runState: run.state,
1984
+ hasActiveForegroundTurn: Boolean(this.activeForegroundTurn),
1985
+ }, "Claude run terminal event handled");
1986
+ this.transitionTurnStateFromActiveRuns(`run ${run.id} terminal`);
1987
+ }
1988
+ async transitionAutonomousToForeground() {
1989
+ const autonomousRuns = this.runTracker.listActiveRuns("autonomous");
1990
+ if (autonomousRuns.length === 0) {
1991
+ this.transitionTurnStateFromActiveRuns("no autonomous runs to transition");
1992
+ return;
1993
+ }
1994
+ this.logger.debug({ runIds: autonomousRuns.map((run) => run.id) }, "Transitioning autonomous runs to foreground ownership");
1995
+ this.flushPendingToolCalls();
1996
+ for (const run of autonomousRuns) {
1997
+ this.emitRunEvent(run, {
1998
+ type: "turn_canceled",
1999
+ provider: "claude",
2000
+ reason: "Interrupted by foreground prompt",
2001
+ });
2002
+ }
2003
+ this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
2004
+ this.logger.warn({ err: error }, "Failed to interrupt autonomous run during foreground transition");
2005
+ });
2006
+ await this.awaitPendingInterruptPromise();
2007
+ this.transitionTurnStateFromActiveRuns("autonomous interrupted for foreground");
2008
+ }
2009
+ routeMessage(normalized) {
2010
+ if (normalized.metadataOnly) {
2011
+ if (normalized.message.type === "user" &&
2012
+ isTaskNotificationUserContent(normalized.message.message?.content)) {
2013
+ this.reserveAutonomousWake("task_notification");
2014
+ }
2015
+ this.notePreReplayMetadata(normalized.message);
2016
+ return { run: null, reason: "metadata" };
2017
+ }
2018
+ const hasIdentifiers = Boolean(normalized.identifiers.taskId ||
2019
+ normalized.identifiers.parentMessageId ||
2020
+ normalized.identifiers.messageId);
2021
+ const byIdentifiers = this.runTracker.resolveByIdentifiers(normalized.identifiers);
2022
+ if (byIdentifiers.run) {
2023
+ return byIdentifiers;
2024
+ }
2025
+ if (this.turnState === "autonomous") {
2026
+ const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
2027
+ if (activeAutonomousRun) {
2028
+ return { run: activeAutonomousRun, reason: "unbound_autonomous" };
2029
+ }
2030
+ }
2031
+ const foregroundRun = this.activeForegroundTurn
2032
+ ? this.runTracker.getRun(this.activeForegroundTurn.runId)
2033
+ : null;
2034
+ // A previously unseen task_id during foreground ownership is deterministic
2035
+ // evidence of a distinct autonomous wake/run, not foreground response text.
2036
+ if (this.turnState === "foreground" &&
2037
+ foregroundRun &&
2038
+ normalized.identifiers.taskId) {
2039
+ const incomingTaskId = normalized.identifiers.taskId;
2040
+ // Foreground must claim its first task_id; otherwise early foreground
2041
+ // result events can be misrouted to autonomous fallback runs.
2042
+ if (foregroundRun.taskIds.size === 0) {
2043
+ if (foregroundRun.state !== "finalizing") {
2044
+ return { run: foregroundRun, reason: "foreground" };
2045
+ }
2046
+ }
2047
+ else if (foregroundRun.taskIds.has(incomingTaskId)) {
2048
+ return { run: foregroundRun, reason: "foreground" };
2049
+ }
2050
+ const autonomousRun = this.createRun("autonomous", null);
2051
+ this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
2052
+ return { run: autonomousRun, reason: "task_id_new" };
2053
+ }
2054
+ if (this.turnState === "foreground" &&
2055
+ foregroundRun &&
2056
+ this.shouldPreferForegroundRun({
2057
+ run: foregroundRun,
2058
+ message: normalized.message,
2059
+ })) {
2060
+ return { run: foregroundRun, reason: "foreground" };
2061
+ }
2062
+ if (!hasIdentifiers) {
2063
+ if (this.pendingAutonomousWakeReservations > 0) {
2064
+ const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_unbound");
2065
+ return {
2066
+ run: reservedAutonomousRun,
2067
+ reason: "unbound_autonomous",
2068
+ };
2069
+ }
2070
+ const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
2071
+ if (activeAutonomousRun) {
2072
+ return { run: activeAutonomousRun, reason: "unbound_autonomous" };
2073
+ }
2074
+ }
2075
+ if (this.pendingAutonomousWakeReservations > 0) {
2076
+ const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_fallback");
2077
+ return { run: reservedAutonomousRun, reason: "fallback" };
2078
+ }
2079
+ const autonomousRun = this.createRun("autonomous", null);
2080
+ this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
2081
+ return { run: autonomousRun, reason: "fallback" };
2082
+ }
2083
+ shouldPreferForegroundRun(input) {
2084
+ const { run, message } = input;
2085
+ if (run.state === "completed" ||
2086
+ run.state === "interrupted" ||
2087
+ run.state === "error") {
2088
+ return false;
2089
+ }
2090
+ // Before prompt replay is observed, prefer foreground by default so the
2091
+ // first turn cannot be stranded in autonomous fallback. If metadata churn
2092
+ // was observed pre-replay, stay conservative and wait for replay.
2093
+ if (!run.promptReplaySeen) {
2094
+ if (this.isToolUseBoundaryStreamEvent(input.message)) {
2095
+ return true;
2096
+ }
2097
+ // Keep pre-replay result events with the foreground run so stale result
2098
+ // bursts cannot consume autonomous wake reservations.
2099
+ if (message.type === "result") {
2100
+ return true;
2101
+ }
2102
+ if (message.type === "assistant" ||
2103
+ message.type === "stream_event" ||
2104
+ message.type === "tool_progress") {
2105
+ return !this.preReplayMetadataSeen;
2106
+ }
2107
+ return true;
2108
+ }
2109
+ if (run.state === "finalizing" &&
2110
+ (message.type === "assistant" || message.type === "stream_event")) {
2111
+ return false;
2112
+ }
2113
+ return true;
2114
+ }
2115
+ isToolUseBoundaryStreamEvent(message) {
2116
+ if (message.type !== "stream_event") {
2117
+ return false;
2118
+ }
2119
+ const event = message.event;
2120
+ if (!event || event.type !== "message_delta") {
2121
+ return false;
2122
+ }
2123
+ const delta = "delta" in event && event.delta && typeof event.delta === "object"
2124
+ ? event.delta
2125
+ : null;
2126
+ return delta?.stop_reason === "tool_use";
2127
+ }
2128
+ notePreReplayMetadata(message) {
2129
+ if (this.turnState !== "foreground") {
2130
+ return;
2131
+ }
2132
+ const foregroundRun = this.activeForegroundTurn
2133
+ ? this.runTracker.getRun(this.activeForegroundTurn.runId)
2134
+ : null;
2135
+ if (!foregroundRun || foregroundRun.promptReplaySeen) {
2136
+ return;
2137
+ }
2138
+ // System metadata (init/hook callbacks/etc.) can precede the first prompt
2139
+ // replay for a legitimate foreground run. Treating that as churn strands
2140
+ // one-shot helper runs in autonomous fallback.
2141
+ if (message.type === "system") {
2142
+ return;
2143
+ }
2144
+ this.preReplayMetadataSeen = true;
2145
+ }
2146
+ reserveAutonomousWake(reason) {
2147
+ this.pendingAutonomousWakeReservations += 1;
2148
+ this.logger.debug({
2149
+ reason,
2150
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2151
+ }, "Reserved autonomous wake");
2152
+ }
2153
+ claimOrCreateAutonomousRun(reason) {
2154
+ const existing = this.runTracker.getLatestActiveRun("autonomous");
2155
+ if (existing) {
2156
+ if (this.pendingAutonomousWakeReservations > 0) {
2157
+ this.pendingAutonomousWakeReservations -= 1;
2158
+ }
2159
+ this.logger.debug({
2160
+ reason,
2161
+ runId: existing.id,
2162
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2163
+ }, "Claimed autonomous wake reservation on existing run");
2164
+ return existing;
2165
+ }
2166
+ const run = this.createRun("autonomous", null);
2167
+ this.emitRunEvent(run, { type: "turn_started", provider: "claude" });
2168
+ if (this.pendingAutonomousWakeReservations > 0) {
2169
+ this.pendingAutonomousWakeReservations -= 1;
2170
+ }
2171
+ this.logger.debug({
2172
+ reason,
2173
+ runId: run.id,
2174
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2175
+ }, "Claimed autonomous wake reservation with new run");
2176
+ return run;
2177
+ }
2178
+ startQueryPump() {
2179
+ if (this.closed || this.queryPumpPromise) {
2180
+ return;
2181
+ }
2182
+ const pump = this.runQueryPump().catch((error) => {
2183
+ this.logger.warn({ err: error }, "Claude query pump exited unexpectedly");
2184
+ });
2185
+ this.queryPumpPromise = pump;
2186
+ pump.finally(() => {
2187
+ if (this.queryPumpPromise === pump) {
2188
+ this.queryPumpPromise = null;
2189
+ }
2190
+ });
2191
+ }
2192
+ async runQueryPump() {
2193
+ while (!this.closed) {
2194
+ if (!this.claudeSessionId && !this.activeForegroundTurn && !this.query) {
2195
+ await this.waitForLiveHistoryPoll();
2196
+ continue;
2197
+ }
2198
+ let q;
2199
+ try {
2200
+ q = await this.ensureQuery();
2201
+ }
2202
+ catch (error) {
2203
+ this.logger.warn({ err: error }, "Failed to initialize Claude query pump");
2204
+ await this.waitForLiveHistoryPoll();
2205
+ continue;
2206
+ }
2207
+ let next;
2208
+ try {
2209
+ next = await q.next();
2210
+ this.logger.info({ claudeSessionId: this.claudeSessionId, next }, "Claude query pump raw next()");
2211
+ }
2212
+ catch (error) {
2213
+ this.logger.warn({ err: error }, "Claude query pump next() failed");
2214
+ for (const run of this.runTracker.listActiveRuns()) {
2215
+ this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
2216
+ }
2217
+ this.input?.end();
2218
+ await this.awaitWithTimeout(q.return?.(), "query pump return after failure");
2219
+ if (this.query === q) {
2220
+ this.query = null;
2221
+ this.input = null;
2222
+ }
2223
+ await this.waitForLiveHistoryPoll();
2224
+ continue;
2225
+ }
2226
+ if (next.done) {
2227
+ this.logger.trace({
2228
+ claudeSessionId: this.claudeSessionId,
2229
+ activeRunCount: this.runTracker.listActiveRuns().length,
2230
+ }, "Claude query pump next() returned done");
2231
+ this.input?.end();
2232
+ await this.awaitWithTimeout(q.return?.(), "query pump return on done");
2233
+ if (this.query === q) {
2234
+ this.query = null;
2235
+ this.input = null;
2236
+ }
2237
+ const activeRuns = this.runTracker.listActiveRuns();
2238
+ if (activeRuns.length > 0) {
2239
+ for (const run of activeRuns) {
2240
+ this.failRun(run, "Claude stream ended before terminal result");
2241
+ }
2242
+ }
2243
+ await this.waitForLiveHistoryPoll();
2244
+ continue;
2245
+ }
2246
+ const sdkMessage = next.value;
2247
+ if (!sdkMessage) {
2248
+ continue;
2249
+ }
2250
+ try {
2251
+ this.routeSdkMessageFromPump(sdkMessage);
2252
+ }
2253
+ catch (error) {
2254
+ this.logger.warn({ err: error }, "Failed to route Claude SDK message from query pump");
2255
+ }
2256
+ }
2257
+ }
2258
+ routeSdkMessageFromPump(message) {
2259
+ if (this.shouldSuppressLocalReplayActivity(message)) {
2260
+ return;
2261
+ }
2262
+ const identifiers = readEventIdentifiers(message);
2263
+ const metadataOnly = isMetadataOnlySdkMessage(message);
2264
+ const route = this.routeMessage({
2265
+ message,
2266
+ identifiers,
2267
+ metadataOnly,
2268
+ });
2269
+ const suppressTerminalEvents = this.shouldSuppressReplayResultTerminal({
2270
+ run: route.run,
2271
+ message,
2272
+ });
2273
+ this.logger.trace({
2274
+ claudeSessionId: this.claudeSessionId,
2275
+ messageType: message.type,
2276
+ routeReason: route.reason,
2277
+ runId: route.run?.id ?? null,
2278
+ runOwner: route.run?.owner ?? null,
2279
+ suppressTerminalEvents,
2280
+ metadataOnly,
2281
+ }, "Claude query pump routed SDK message");
2282
+ if (route.run) {
2283
+ this.transitionTurnStateFromActiveRuns(`routed via ${route.reason}`);
2284
+ this.runTracker.bindIdentifiers(route.run, identifiers);
2285
+ if (!suppressTerminalEvents) {
2286
+ this.updateRunLifecycleForMessage(route.run, message, identifiers);
2287
+ }
2288
+ }
2289
+ const messageEvents = this.translateMessageToEvents(message, {
2290
+ suppressAssistantText: true,
2291
+ suppressReasoning: true,
2292
+ suppressTerminalEvents,
2293
+ });
2294
+ const assistantTimelineItems = this.timelineAssembler.consume({
2295
+ message,
2296
+ runId: route.run?.id ?? null,
2297
+ messageIdHint: identifiers.messageId,
2298
+ });
2299
+ const assistantTimelineEvents = assistantTimelineItems.map((item) => ({
2300
+ type: "timeline",
2301
+ item,
2302
+ provider: "claude",
2303
+ }));
2304
+ const events = [...messageEvents, ...assistantTimelineEvents];
2305
+ if (events.length === 0) {
2306
+ return;
2307
+ }
2308
+ if (!route.run) {
2309
+ this.dispatchMetadataEvents(events);
2310
+ return;
2311
+ }
2312
+ for (const event of events) {
2313
+ this.emitRunEvent(route.run, event);
2314
+ }
2315
+ }
2316
+ shouldSuppressReplayResultTerminal(input) {
2317
+ const { run, message } = input;
2318
+ if (!run || run.owner !== "foreground" || message.type !== "result") {
2319
+ return false;
2320
+ }
2321
+ if (run.promptReplaySeen) {
2322
+ return false;
2323
+ }
2324
+ if (run.state === "streaming" || run.state === "finalizing") {
2325
+ return false;
2326
+ }
2327
+ const resultSubtype = "subtype" in message && typeof message.subtype === "string"
2328
+ ? message.subtype
2329
+ : null;
2330
+ // Pre-replay success results are stale in practice (leftover from an
2331
+ // earlier query segment) and must not end the current foreground run.
2332
+ if (resultSubtype === "success") {
2333
+ this.logger.trace({
2334
+ runId: run.id,
2335
+ runOwner: run.owner,
2336
+ runState: run.state,
2337
+ promptReplaySeen: run.promptReplaySeen,
2338
+ resultSubtype,
2339
+ }, "Suppressing pre-replay foreground success result terminal event");
2340
+ return true;
2341
+ }
2342
+ // For non-success results, keep the metadata-churn guard to avoid
2343
+ // suppressing legitimate hard failures.
2344
+ return this.preReplayMetadataSeen;
2345
+ }
2346
+ dispatchMetadataEvents(events) {
2347
+ for (const event of events) {
2348
+ this.pushEvent(event);
2349
+ }
2350
+ }
2351
+ updateRunLifecycleForMessage(run, message, identifiers) {
2352
+ const previousState = run.state;
2353
+ if (message.type === "user" &&
2354
+ identifiers.messageId &&
2355
+ run.messageIds.has(identifiers.messageId)) {
2356
+ run.promptReplaySeen = true;
2357
+ this.preReplayMetadataSeen = false;
2358
+ }
2359
+ if (run.state === "queued") {
2360
+ this.runTracker.transition(run, "awaiting_response");
2361
+ }
2362
+ if (message.type === "assistant" ||
2363
+ message.type === "stream_event" ||
2364
+ message.type === "tool_progress") {
2365
+ this.runTracker.transition(run, "streaming");
2366
+ return;
2367
+ }
2368
+ if (message.type === "result") {
2369
+ this.runTracker.transition(run, "finalizing");
2370
+ }
2371
+ else {
2372
+ return;
2373
+ }
2374
+ if (run.state !== previousState) {
2375
+ this.logger.trace({
2376
+ runId: run.id,
2377
+ owner: run.owner,
2378
+ messageType: message.type,
2379
+ previousState,
2380
+ nextState: run.state,
2381
+ taskId: identifiers.taskId,
2382
+ parentMessageId: identifiers.parentMessageId,
2383
+ messageId: identifiers.messageId,
2384
+ }, "Updated Claude run lifecycle from SDK message");
2385
+ }
2386
+ }
2387
+ shouldSuppressLocalReplayActivity(message) {
2388
+ const localReplay = this.isLocalReplayUserMessage(message);
2389
+ if (!this.activeForegroundTurn && localReplay) {
2390
+ this.suppressLocalReplayActivity = true;
2391
+ this.logger.debug({ uuid: message.uuid }, "Suppressing local replay user message from live pump");
2392
+ return true;
2393
+ }
2394
+ if (!this.suppressLocalReplayActivity) {
2395
+ return false;
2396
+ }
2397
+ // Suppress only replay scaffolding. Do not suppress autonomous
2398
+ // assistant/result events; otherwise task-notification replies can be dropped.
2399
+ if (localReplay) {
2400
+ return true;
2401
+ }
2402
+ if (message.type === "system") {
2403
+ return true;
2404
+ }
2405
+ const identifiers = readEventIdentifiers(message);
2406
+ const hasIdentifiers = Boolean(identifiers.taskId || identifiers.parentMessageId || identifiers.messageId);
2407
+ if (message.type !== "user" && !hasIdentifiers) {
2408
+ if (this.pendingAutonomousWakeReservations > 0) {
2409
+ this.suppressLocalReplayActivity = false;
2410
+ return false;
2411
+ }
2412
+ return true;
2413
+ }
2414
+ if (message.type === "user") {
2415
+ this.suppressLocalReplayActivity = false;
2416
+ return false;
2417
+ }
2418
+ this.suppressLocalReplayActivity = false;
2419
+ return false;
2420
+ }
2421
+ isLocalReplayUserMessage(message) {
2422
+ if (message.type !== "user") {
2423
+ return false;
2424
+ }
2425
+ const uuid = readTrimmedString(message.uuid);
2426
+ if (!uuid) {
2427
+ return false;
2428
+ }
2429
+ return this.localUserMessageIds.has(uuid);
2430
+ }
2431
+ async interruptActiveTurn() {
2432
+ const queryToInterrupt = this.query;
2433
+ if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
2434
+ this.logger.debug("interruptActiveTurn: no query to interrupt");
2435
+ return;
2436
+ }
2437
+ try {
2438
+ this.logger.debug("interruptActiveTurn: calling query.interrupt()...");
2439
+ const t0 = Date.now();
2440
+ await queryToInterrupt.interrupt();
2441
+ this.logger.debug({ durationMs: Date.now() - t0 }, "interruptActiveTurn: query.interrupt() returned");
2442
+ // After interrupt(), the query iterator is done (returns done: true).
2443
+ // Clear it so ensureQuery() creates a fresh query for the next turn.
2444
+ // Also end the input stream and call return() to clean up the SDK process.
2445
+ this.input?.end();
2446
+ this.logger.debug("interruptActiveTurn: calling query.return()...");
2447
+ const t1 = Date.now();
2448
+ await queryToInterrupt.return?.();
2449
+ this.logger.debug({ durationMs: Date.now() - t1 }, "interruptActiveTurn: query.return() returned");
2450
+ this.query = null;
2451
+ this.input = null;
2452
+ this.queryRestartNeeded = false;
2453
+ }
2454
+ catch (error) {
2455
+ this.logger.warn({ err: error }, "Failed to interrupt active turn");
2456
+ // If interrupt fails, the SDK iterator may remain in an indeterminate state.
2457
+ // Force a teardown/recreate path so the next turn cannot reuse stale query state.
2458
+ this.queryRestartNeeded = true;
2459
+ }
2460
+ }
2461
+ handleSidechainMessage(message, parentToolUseId) {
2462
+ const state = this.activeSidechains.get(parentToolUseId) ??
2463
+ {
2464
+ actions: [],
2465
+ actionKeys: [],
2466
+ nextActionIndex: 1,
2467
+ actionIndexByKey: new Map(),
2468
+ };
2469
+ this.activeSidechains.set(parentToolUseId, state);
2470
+ const contextUpdated = this.updateSubAgentContextFromTaskInput(state, parentToolUseId);
2471
+ const actionCandidates = this.extractSubAgentActionCandidates(message);
2472
+ let actionUpdated = false;
2473
+ for (const action of actionCandidates) {
2474
+ if (this.appendSubAgentAction(state, action)) {
2475
+ actionUpdated = true;
2476
+ }
2477
+ }
2478
+ if (!contextUpdated && !actionUpdated) {
2479
+ return [];
2480
+ }
2481
+ const toolCall = mapClaudeRunningToolCall({
2482
+ name: "Task",
2483
+ callId: parentToolUseId,
2484
+ input: null,
2485
+ output: null,
2486
+ });
2487
+ if (!toolCall) {
2488
+ return [];
2489
+ }
2490
+ const detail = {
2491
+ type: "sub_agent",
2492
+ ...(state.subAgentType ? { subAgentType: state.subAgentType } : {}),
2493
+ ...(state.description ? { description: state.description } : {}),
2494
+ log: state.actions
2495
+ .map((action) => action.summary
2496
+ ? `[${action.toolName}] ${action.summary}`
2497
+ : `[${action.toolName}]`)
2498
+ .join("\n"),
2499
+ actions: state.actions.map((action) => ({
2500
+ index: action.index,
2501
+ toolName: action.toolName,
2502
+ ...(action.summary ? { summary: action.summary } : {}),
2503
+ })),
2504
+ };
2505
+ return [
2506
+ {
2507
+ type: "timeline",
2508
+ item: {
2509
+ ...toolCall,
2510
+ detail,
2511
+ },
2512
+ provider: "claude",
2513
+ },
2514
+ ];
2515
+ }
2516
+ updateSubAgentContextFromTaskInput(state, parentToolUseId) {
2517
+ const taskInput = this.toolUseCache.get(parentToolUseId)?.input;
2518
+ const nextSubAgentType = this.normalizeSubAgentText(taskInput?.subagent_type);
2519
+ const nextDescription = this.normalizeSubAgentText(taskInput?.description);
2520
+ let changed = false;
2521
+ if (nextSubAgentType && nextSubAgentType !== state.subAgentType) {
2522
+ state.subAgentType = nextSubAgentType;
2523
+ changed = true;
2524
+ }
2525
+ if (nextDescription && nextDescription !== state.description) {
2526
+ state.description = nextDescription;
2527
+ changed = true;
2528
+ }
2529
+ return changed;
2530
+ }
2531
+ normalizeSubAgentText(value) {
2532
+ const normalized = readTrimmedString(value)?.replace(/\s+/g, " ");
2533
+ if (!normalized) {
2534
+ return undefined;
2535
+ }
2536
+ if (normalized.length <= MAX_SUB_AGENT_SUMMARY_CHARS) {
2537
+ return normalized;
2538
+ }
2539
+ return `${normalized.slice(0, MAX_SUB_AGENT_SUMMARY_CHARS)}...`;
2540
+ }
2541
+ extractSubAgentActionCandidates(message) {
2542
+ if (message.type === "assistant") {
2543
+ const content = message.message?.content;
2544
+ if (!Array.isArray(content)) {
2545
+ return [];
2546
+ }
2547
+ const actions = [];
2548
+ for (const block of content) {
2549
+ if (!isClaudeContentChunk(block) ||
2550
+ !(block.type === "tool_use" ||
2551
+ block.type === "mcp_tool_use" ||
2552
+ block.type === "server_tool_use") ||
2553
+ typeof block.name !== "string") {
2554
+ continue;
2555
+ }
2556
+ const key = readTrimmedString(block.id) ??
2557
+ `assistant:${block.name}:${actions.length}`;
2558
+ actions.push({
2559
+ key,
2560
+ toolName: block.name,
2561
+ input: block.input ?? null,
2562
+ });
2563
+ }
2564
+ return actions;
2565
+ }
2566
+ if (message.type === "stream_event") {
2567
+ const event = message.event;
2568
+ if (event.type !== "content_block_start") {
2569
+ return [];
2570
+ }
2571
+ const block = isClaudeContentChunk(event.content_block)
2572
+ ? event.content_block
2573
+ : null;
2574
+ if (!block ||
2575
+ !(block.type === "tool_use" ||
2576
+ block.type === "mcp_tool_use" ||
2577
+ block.type === "server_tool_use") ||
2578
+ typeof block.name !== "string") {
2579
+ return [];
2580
+ }
2581
+ const key = readTrimmedString(block.id) ??
2582
+ `stream:${block.name}:${typeof event.index === "number" ? event.index : 0}`;
2583
+ return [
2584
+ {
2585
+ key,
2586
+ toolName: block.name,
2587
+ input: block.input ?? null,
2588
+ },
2589
+ ];
2590
+ }
2591
+ if (message.type === "tool_progress") {
2592
+ const toolName = readTrimmedString(message.tool_name);
2593
+ if (!toolName) {
2594
+ return [];
2595
+ }
2596
+ const key = readTrimmedString(message.tool_use_id) ?? `progress:${toolName}`;
2597
+ return [{ key, toolName, input: null }];
2598
+ }
2599
+ return [];
2600
+ }
2601
+ appendSubAgentAction(state, candidate) {
2602
+ const normalizedToolName = readTrimmedString(candidate.toolName);
2603
+ if (!normalizedToolName) {
2604
+ return false;
2605
+ }
2606
+ const summary = this.deriveSubAgentActionSummary(normalizedToolName, candidate.input);
2607
+ const existingIndex = state.actionIndexByKey.get(candidate.key);
2608
+ if (existingIndex !== undefined) {
2609
+ const existing = state.actions[existingIndex];
2610
+ if (!existing) {
2611
+ return false;
2612
+ }
2613
+ const nextSummary = existing.summary ?? summary;
2614
+ const unchanged = existing.toolName === normalizedToolName &&
2615
+ existing.summary === nextSummary;
2616
+ if (unchanged) {
2617
+ return false;
2618
+ }
2619
+ state.actions[existingIndex] = {
2620
+ ...existing,
2621
+ toolName: normalizedToolName,
2622
+ ...(nextSummary ? { summary: nextSummary } : {}),
2623
+ };
2624
+ return true;
2625
+ }
2626
+ const nextEntry = {
2627
+ index: state.nextActionIndex,
2628
+ toolName: normalizedToolName,
2629
+ ...(summary ? { summary } : {}),
2630
+ };
2631
+ state.nextActionIndex += 1;
2632
+ state.actions.push(nextEntry);
2633
+ state.actionKeys.push(candidate.key);
2634
+ this.trimSubAgentTail(state);
2635
+ this.rebuildSubAgentActionIndex(state);
2636
+ return true;
2637
+ }
2638
+ trimSubAgentTail(state) {
2639
+ while (state.actions.length > MAX_SUB_AGENT_LOG_ENTRIES) {
2640
+ state.actions.shift();
2641
+ state.actionKeys.shift();
2642
+ }
2643
+ }
2644
+ rebuildSubAgentActionIndex(state) {
2645
+ state.actionIndexByKey.clear();
2646
+ for (let index = 0; index < state.actionKeys.length; index += 1) {
2647
+ const key = state.actionKeys[index];
2648
+ if (key) {
2649
+ state.actionIndexByKey.set(key, index);
2650
+ }
2651
+ }
2652
+ }
2653
+ deriveSubAgentActionSummary(toolName, input) {
2654
+ const runningToolCall = mapClaudeRunningToolCall({
2655
+ name: toolName,
2656
+ callId: `sub-agent-summary-${toolName}`,
2657
+ input,
2658
+ output: null,
2659
+ });
2660
+ if (!runningToolCall) {
2661
+ return undefined;
2662
+ }
2663
+ const display = buildToolCallDisplayModel({
2664
+ name: runningToolCall.name,
2665
+ status: runningToolCall.status,
2666
+ error: runningToolCall.error,
2667
+ detail: runningToolCall.detail,
2668
+ metadata: runningToolCall.metadata,
2669
+ });
2670
+ return this.normalizeSubAgentText(display.summary);
2671
+ }
2672
+ translateMessageToEvents(message, options) {
2673
+ const parentToolUseId = "parent_tool_use_id" in message
2674
+ ? message.parent_tool_use_id
2675
+ : null;
2676
+ if (parentToolUseId) {
2677
+ return this.handleSidechainMessage(message, parentToolUseId);
2678
+ }
2679
+ const events = [];
2680
+ const fallbackThreadSessionId = this.captureSessionIdFromMessage(message);
2681
+ if (fallbackThreadSessionId) {
2682
+ events.push({
2683
+ type: "thread_started",
2684
+ provider: "claude",
2685
+ sessionId: fallbackThreadSessionId,
2686
+ });
2687
+ }
2688
+ switch (message.type) {
2689
+ case "system":
2690
+ if (message.subtype === "init") {
2691
+ const threadSessionId = this.handleSystemMessage(message);
2692
+ if (threadSessionId) {
2693
+ events.push({
2694
+ type: "thread_started",
2695
+ provider: "claude",
2696
+ sessionId: threadSessionId,
2697
+ });
2698
+ }
2699
+ }
2700
+ else if (message.subtype === "status") {
2701
+ const status = message.status;
2702
+ if (status === "compacting") {
2703
+ this.compacting = true;
2704
+ events.push({
2705
+ type: "timeline",
2706
+ item: { type: "compaction", status: "loading" },
2707
+ provider: "claude",
2708
+ });
2709
+ }
2710
+ }
2711
+ else if (message.subtype === "compact_boundary") {
2712
+ const compactMetadata = readCompactionMetadata(message);
2713
+ events.push({
2714
+ type: "timeline",
2715
+ item: {
2716
+ type: "compaction",
2717
+ status: "completed",
2718
+ trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
2719
+ preTokens: compactMetadata?.preTokens,
2720
+ },
2721
+ provider: "claude",
2722
+ });
2723
+ }
2724
+ else if (message.subtype === "task_notification") {
2725
+ const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
2726
+ if (taskNotificationItem) {
2727
+ events.push({
2728
+ type: "timeline",
2729
+ item: taskNotificationItem,
2730
+ provider: "claude",
2731
+ });
2732
+ }
2733
+ }
2734
+ break;
2735
+ case "user": {
2736
+ if (isSyntheticUserEntry(message)) {
2737
+ break;
2738
+ }
2739
+ if (this.compacting) {
2740
+ this.compacting = false;
2741
+ break;
2742
+ }
2743
+ const messageId = typeof message.uuid === "string" && message.uuid.length > 0
2744
+ ? message.uuid
2745
+ : undefined;
2746
+ this.rememberUserMessageId(messageId);
2747
+ const content = message.message?.content;
2748
+ const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
2749
+ content,
2750
+ messageId,
2751
+ });
2752
+ if (taskNotificationItem) {
2753
+ events.push({
2754
+ type: "timeline",
2755
+ item: taskNotificationItem,
2756
+ provider: "claude",
2757
+ });
2758
+ break;
2759
+ }
2760
+ if (typeof content === "string" && content.length > 0) {
2761
+ // String content from user messages (e.g., local command output)
2762
+ events.push({
2763
+ type: "timeline",
2764
+ item: {
2765
+ type: "user_message",
2766
+ text: content,
2767
+ ...(messageId ? { messageId } : {}),
2768
+ },
2769
+ provider: "claude",
2770
+ });
2771
+ }
2772
+ else if (Array.isArray(content)) {
2773
+ const timelineItems = this.mapBlocksToTimeline(content);
2774
+ for (const item of timelineItems) {
2775
+ if (item.type === "user_message" && messageId && !item.messageId) {
2776
+ events.push({
2777
+ type: "timeline",
2778
+ item: { ...item, messageId },
2779
+ provider: "claude",
2780
+ });
2781
+ continue;
2782
+ }
2783
+ events.push({ type: "timeline", item, provider: "claude" });
2784
+ }
2785
+ }
2786
+ break;
2787
+ }
2788
+ case "assistant": {
2789
+ const timelineItems = this.mapBlocksToTimeline(message.message.content, {
2790
+ suppressAssistantText: options?.suppressAssistantText ?? false,
2791
+ suppressReasoning: options?.suppressReasoning ?? false,
2792
+ });
2793
+ for (const item of timelineItems) {
2794
+ events.push({ type: "timeline", item, provider: "claude" });
2795
+ }
2796
+ break;
2797
+ }
2798
+ case "stream_event": {
2799
+ const timelineItems = this.mapPartialEvent(message.event, {
2800
+ suppressAssistantText: options?.suppressAssistantText ?? false,
2801
+ suppressReasoning: options?.suppressReasoning ?? false,
2802
+ });
2803
+ for (const item of timelineItems) {
2804
+ events.push({ type: "timeline", item, provider: "claude" });
2805
+ }
2806
+ break;
2807
+ }
2808
+ case "result": {
2809
+ if (options?.suppressTerminalEvents) {
2810
+ break;
2811
+ }
2812
+ const usage = this.convertUsage(message);
2813
+ if (message.subtype === "success") {
2814
+ events.push({ type: "turn_completed", provider: "claude", usage });
2815
+ }
2816
+ else {
2817
+ const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
2818
+ ? message.errors.join("\n")
2819
+ : "Claude run failed";
2820
+ events.push(this.buildTurnFailedEvent(errorMessage));
2821
+ }
2822
+ break;
2823
+ }
2824
+ default:
2825
+ break;
2826
+ }
2827
+ return events;
2828
+ }
2829
+ captureSessionIdFromMessage(message) {
2830
+ const msg = message;
2831
+ const sessionIdRaw = typeof msg.session_id === "string"
2832
+ ? msg.session_id
2833
+ : typeof msg.sessionId === "string"
2834
+ ? msg.sessionId
2835
+ : typeof msg.session?.id === "string"
2836
+ ? msg.session.id
2837
+ : "";
2838
+ const sessionId = sessionIdRaw.trim();
2839
+ if (!sessionId) {
2840
+ return null;
2841
+ }
2842
+ if (this.claudeSessionId === null) {
2843
+ this.claudeSessionId = sessionId;
2844
+ this.persistence = null;
2845
+ return sessionId;
2846
+ }
2847
+ if (this.claudeSessionId === sessionId) {
2848
+ return null;
2849
+ }
2850
+ throw new Error(`CRITICAL: Claude session ID overwrite detected! ` +
2851
+ `Existing: ${this.claudeSessionId}, New: ${sessionId}. ` +
2852
+ `This indicates a session identity corruption bug.`);
2853
+ }
2854
+ handleSystemMessage(message) {
2855
+ if (message.subtype !== "init") {
2856
+ return null;
2857
+ }
2858
+ const msg = message;
2859
+ const newSessionIdRaw = typeof msg.session_id === "string"
2860
+ ? msg.session_id
2861
+ : typeof msg.sessionId === "string"
2862
+ ? msg.sessionId
2863
+ : typeof msg.session?.id === "string"
2864
+ ? msg.session.id
2865
+ : "";
2866
+ const newSessionId = newSessionIdRaw.trim();
2867
+ if (!newSessionId) {
2868
+ return null;
2869
+ }
2870
+ const existingSessionId = this.claudeSessionId;
2871
+ let threadStartedSessionId = null;
2872
+ if (existingSessionId === null) {
2873
+ // First time setting session ID (empty → filled) - this is expected
2874
+ this.claudeSessionId = newSessionId;
2875
+ threadStartedSessionId = newSessionId;
2876
+ this.logger.debug({ sessionId: newSessionId }, "Claude session ID set for the first time");
2877
+ }
2878
+ else if (existingSessionId === newSessionId) {
2879
+ // Same session ID - no-op, but log for visibility
2880
+ this.logger.debug({ sessionId: newSessionId }, "Claude session ID unchanged (same value)");
2881
+ }
2882
+ else {
2883
+ // CRITICAL: Session ID is being overwritten with a different value
2884
+ // This should NEVER happen and indicates a serious bug
2885
+ throw new Error(`CRITICAL: Claude session ID overwrite detected! ` +
2886
+ `Existing: ${existingSessionId}, New: ${newSessionId}. ` +
2887
+ `This indicates a session identity corruption bug.`);
2888
+ }
2889
+ this.availableModes = DEFAULT_MODES;
2890
+ this.currentMode = message.permissionMode;
2891
+ this.persistence = null;
2892
+ // Capture actual model from SDK init message (not just the configured model)
2893
+ if (message.model) {
2894
+ const normalizedModel = normalizeClaudeRuntimeModelId({
2895
+ runtimeModelId: message.model,
2896
+ supportedModelIds: this.selectableModelIds,
2897
+ supportedModelFamilyAliases: this.selectableModelFamilyAliases,
2898
+ configuredModelId: this.config.model ?? null,
2899
+ currentModelId: this.lastOptionsModel,
2900
+ });
2901
+ this.logger.debug({ model: message.model, normalizedModel }, "Captured model from SDK init");
2902
+ this.lastOptionsModel = normalizedModel;
2903
+ // Invalidate cached runtime info so it picks up the new model
2904
+ this.cachedRuntimeInfo = null;
2905
+ }
2906
+ return threadStartedSessionId;
2907
+ }
2908
+ convertUsage(message) {
2909
+ if (!message.usage) {
2910
+ return undefined;
2911
+ }
2912
+ return {
2913
+ inputTokens: message.usage.input_tokens,
2914
+ cachedInputTokens: message.usage.cache_read_input_tokens,
2915
+ outputTokens: message.usage.output_tokens,
2916
+ totalCostUsd: message.total_cost_usd,
2917
+ };
2918
+ }
2919
+ enqueueTimeline(item) {
2920
+ this.pushEvent({ type: "timeline", item, provider: "claude" });
2921
+ }
2922
+ flushPendingToolCalls() {
2923
+ for (const [id, entry] of this.toolUseCache) {
2924
+ if (entry.started) {
2925
+ this.pushToolCall(mapClaudeCanceledToolCall({
2926
+ name: entry.name,
2927
+ callId: id,
2928
+ input: entry.input ?? null,
2929
+ output: null,
2930
+ }));
2931
+ }
2932
+ }
2933
+ this.toolUseCache.clear();
2934
+ this.activeSidechains.clear();
2935
+ }
2936
+ pushToolCall(item, target) {
2937
+ if (!item) {
2938
+ return;
2939
+ }
2940
+ if (target) {
2941
+ target.push(item);
2942
+ return;
2943
+ }
2944
+ this.enqueueTimeline(item);
2945
+ }
2946
+ pushEvent(event) {
2947
+ const foregroundTurn = this.activeForegroundTurn;
2948
+ if (foregroundTurn) {
2949
+ const run = this.runTracker.getRun(foregroundTurn.runId);
2950
+ if (run &&
2951
+ run.owner === "foreground" &&
2952
+ run.queue === foregroundTurn.queue &&
2953
+ this.runTracker.isRunActive(run)) {
2954
+ foregroundTurn.queue.push(event);
2955
+ return;
2956
+ }
2957
+ this.activeForegroundTurn = null;
2958
+ }
2959
+ this.liveEventQueue.push(event);
2960
+ }
2961
+ normalizePermissionUpdates(updates) {
2962
+ if (!updates || updates.length === 0) {
2963
+ return undefined;
2964
+ }
2965
+ const normalized = updates.filter(isPermissionUpdate);
2966
+ return normalized.length > 0 ? normalized : undefined;
2967
+ }
2968
+ rejectAllPendingPermissions(error) {
2969
+ for (const [id, pending] of this.pendingPermissions) {
2970
+ pending.cleanup?.();
2971
+ pending.reject(error);
2972
+ this.pendingPermissions.delete(id);
2973
+ }
2974
+ }
2975
+ waitForLiveHistoryPoll() {
2976
+ return new Promise((resolve) => setTimeout(resolve, 250));
2977
+ }
2978
+ loadPersistedHistory(sessionId) {
2979
+ try {
2980
+ const historyPath = this.resolveHistoryPath(sessionId);
2981
+ if (!historyPath || !fs.existsSync(historyPath)) {
2982
+ return;
2983
+ }
2984
+ const content = fs.readFileSync(historyPath, "utf8");
2985
+ const timeline = [];
2986
+ for (const line of content.split(/\n+/)) {
2987
+ const trimmed = line.trim();
2988
+ if (!trimmed)
2989
+ continue;
2990
+ try {
2991
+ const entry = JSON.parse(trimmed);
2992
+ if (entry.isSidechain) {
2993
+ continue;
2994
+ }
2995
+ if (entry.type === "user" && typeof entry.uuid === "string") {
2996
+ this.rememberUserMessageId(entry.uuid);
2997
+ }
2998
+ const items = this.convertHistoryEntry(entry);
2999
+ if (items.length > 0) {
3000
+ timeline.push(...items);
3001
+ }
3002
+ }
3003
+ catch (error) {
3004
+ // ignore malformed history line
3005
+ }
3006
+ }
3007
+ if (timeline.length > 0) {
3008
+ this.persistedHistory = timeline;
3009
+ this.historyPending = true;
3010
+ }
3011
+ }
3012
+ catch (error) {
3013
+ // ignore history load failures
3014
+ }
3015
+ }
3016
+ resolveHistoryPath(sessionId) {
3017
+ const cwd = this.config.cwd;
3018
+ if (!cwd)
3019
+ return null;
3020
+ // Match Claude CLI's path sanitization: replace slashes, dots, and underscores with dashes
3021
+ const sanitized = cwd.replace(/[\\/\.]/g, "-").replace(/_/g, "-");
3022
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
3023
+ const dir = path.join(configDir, "projects", sanitized);
3024
+ return path.join(dir, `${sessionId}.jsonl`);
3025
+ }
3026
+ convertHistoryEntry(entry) {
3027
+ return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
3028
+ }
3029
+ mapBlocksToTimeline(content, options) {
3030
+ const suppressAssistant = options?.suppressAssistantText ?? false;
3031
+ const suppressReasoning = options?.suppressReasoning ?? false;
3032
+ if (typeof content === "string") {
3033
+ if (!content || content === INTERRUPT_TOOL_USE_PLACEHOLDER) {
3034
+ return [];
3035
+ }
3036
+ if (suppressAssistant) {
3037
+ return [];
3038
+ }
3039
+ return [{ type: "assistant_message", text: content }];
3040
+ }
3041
+ const items = [];
3042
+ for (const block of content) {
3043
+ switch (block.type) {
3044
+ case "text":
3045
+ case "text_delta":
3046
+ if (block.text && block.text !== INTERRUPT_TOOL_USE_PLACEHOLDER) {
3047
+ if (!suppressAssistant) {
3048
+ items.push({ type: "assistant_message", text: block.text });
3049
+ }
3050
+ }
3051
+ break;
3052
+ case "thinking":
3053
+ case "thinking_delta":
3054
+ if (block.thinking) {
3055
+ if (!suppressReasoning) {
3056
+ items.push({ type: "reasoning", text: block.thinking });
3057
+ }
3058
+ }
3059
+ break;
3060
+ case "tool_use":
3061
+ case "server_tool_use":
3062
+ case "mcp_tool_use": {
3063
+ this.handleToolUseStart(block, items);
3064
+ break;
3065
+ }
3066
+ case "tool_result":
3067
+ case "mcp_tool_result":
3068
+ case "web_fetch_tool_result":
3069
+ case "web_search_tool_result":
3070
+ case "code_execution_tool_result":
3071
+ case "bash_code_execution_tool_result":
3072
+ case "text_editor_code_execution_tool_result": {
3073
+ this.handleToolResult(block, items);
3074
+ break;
3075
+ }
3076
+ default:
3077
+ break;
3078
+ }
3079
+ }
3080
+ return items;
3081
+ }
3082
+ handleToolUseStart(block, items) {
3083
+ const entry = this.upsertToolUseEntry(block);
3084
+ if (!entry) {
3085
+ return;
3086
+ }
3087
+ if (entry.started) {
3088
+ return;
3089
+ }
3090
+ entry.started = true;
3091
+ this.toolUseCache.set(entry.id, entry);
3092
+ this.pushToolCall(mapClaudeRunningToolCall({
3093
+ name: entry.name,
3094
+ callId: entry.id,
3095
+ input: entry.input ?? this.normalizeToolInput(block.input) ?? null,
3096
+ output: null,
3097
+ }), items);
3098
+ }
3099
+ handleToolResult(block, items) {
3100
+ const entry = typeof block.tool_use_id === "string" ? this.toolUseCache.get(block.tool_use_id) : undefined;
3101
+ const toolName = entry?.name ?? block.tool_name ?? "tool";
3102
+ const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
3103
+ ? block.tool_use_id
3104
+ : entry?.id ?? null;
3105
+ // Extract output from block.content (SDK always returns content in string form)
3106
+ const output = this.buildToolOutput(block, entry);
3107
+ if (block.is_error) {
3108
+ this.pushToolCall(mapClaudeFailedToolCall({
3109
+ name: toolName,
3110
+ callId,
3111
+ input: entry?.input ?? null,
3112
+ output: output ?? null,
3113
+ error: block,
3114
+ }), items);
3115
+ }
3116
+ else {
3117
+ this.pushToolCall(mapClaudeCompletedToolCall({
3118
+ name: toolName,
3119
+ callId,
3120
+ input: entry?.input ?? null,
3121
+ output: output ?? null,
3122
+ }), items);
3123
+ }
3124
+ if (typeof block.tool_use_id === "string") {
3125
+ this.toolUseCache.delete(block.tool_use_id);
3126
+ this.activeSidechains.delete(block.tool_use_id);
3127
+ }
3128
+ }
3129
+ buildToolOutput(block, entry) {
3130
+ if (block.is_error) {
3131
+ return undefined;
3132
+ }
3133
+ const server = entry?.server ?? block.server ?? "tool";
3134
+ const tool = entry?.name ?? block.tool_name ?? "tool";
3135
+ const content = coerceToolResultContentToString(block.content);
3136
+ const input = entry?.input;
3137
+ // Build structured result based on tool type
3138
+ const structured = this.buildStructuredToolResult(server, tool, content, input);
3139
+ if (structured) {
3140
+ return structured;
3141
+ }
3142
+ // Fallback format - try to parse JSON first
3143
+ const result = {};
3144
+ if (content.length > 0) {
3145
+ try {
3146
+ // If content is a JSON string, parse it
3147
+ result.output = JSON.parse(content);
3148
+ }
3149
+ catch {
3150
+ // If not JSON, return unchanged (no extra wrapping)
3151
+ result.output = content;
3152
+ }
3153
+ }
3154
+ // Preserve file changes tracked during tool execution
3155
+ if (entry?.files?.length) {
3156
+ result.files = entry.files;
3157
+ }
3158
+ return Object.keys(result).length > 0 ? result : undefined;
3159
+ }
3160
+ buildStructuredToolResult(server, tool, output, input) {
3161
+ const normalizedServer = server.toLowerCase();
3162
+ const normalizedTool = tool.toLowerCase();
3163
+ // Command execution tools
3164
+ if (normalizedServer.includes("bash") ||
3165
+ normalizedServer.includes("shell") ||
3166
+ normalizedServer.includes("command") ||
3167
+ normalizedTool.includes("bash") ||
3168
+ normalizedTool.includes("shell") ||
3169
+ normalizedTool.includes("command") ||
3170
+ (input && (typeof input.command === "string" || Array.isArray(input.command)))) {
3171
+ const command = this.extractCommandText(input ?? {}) ?? "command";
3172
+ return {
3173
+ type: "command",
3174
+ command,
3175
+ output,
3176
+ cwd: typeof input?.cwd === "string" ? input.cwd : undefined,
3177
+ };
3178
+ }
3179
+ // File write tools (new files or complete replacements)
3180
+ if (normalizedTool.includes("write") ||
3181
+ normalizedTool === "write_file" ||
3182
+ normalizedTool === "create_file") {
3183
+ if (input && typeof input.file_path === "string") {
3184
+ return {
3185
+ type: "file_write",
3186
+ filePath: input.file_path,
3187
+ oldContent: "",
3188
+ newContent: typeof input.content === "string" ? input.content : output,
3189
+ };
3190
+ }
3191
+ }
3192
+ // File edit/patch tools
3193
+ if (normalizedTool.includes("edit") ||
3194
+ normalizedTool.includes("patch") ||
3195
+ normalizedTool === "apply_patch" ||
3196
+ normalizedTool === "apply_diff") {
3197
+ if (input && typeof input.file_path === "string") {
3198
+ // Support both old_str/new_str and old_string/new_string parameter names
3199
+ const oldContent = typeof input.old_str === "string" ? input.old_str : typeof input.old_string === "string" ? input.old_string : undefined;
3200
+ const newContent = typeof input.new_str === "string" ? input.new_str : typeof input.new_string === "string" ? input.new_string : undefined;
3201
+ return {
3202
+ type: "file_edit",
3203
+ filePath: input.file_path,
3204
+ diff: typeof input.patch === "string" ? input.patch : typeof input.diff === "string" ? input.diff : undefined,
3205
+ oldContent,
3206
+ newContent,
3207
+ };
3208
+ }
3209
+ }
3210
+ // File read tools
3211
+ if (normalizedTool.includes("read") ||
3212
+ normalizedTool === "read_file" ||
3213
+ normalizedTool === "view_file") {
3214
+ if (input && typeof input.file_path === "string") {
3215
+ return {
3216
+ type: "file_read",
3217
+ filePath: input.file_path,
3218
+ content: output,
3219
+ };
3220
+ }
3221
+ }
3222
+ return undefined;
3223
+ }
3224
+ mapPartialEvent(event, options) {
3225
+ if (event.type === "content_block_start") {
3226
+ const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
3227
+ if (block?.type === "tool_use" && typeof event.index === "number" && typeof block.id === "string") {
3228
+ this.toolUseIndexToId.set(event.index, block.id);
3229
+ this.toolUseInputBuffers.delete(block.id);
3230
+ }
3231
+ }
3232
+ else if (event.type === "content_block_delta") {
3233
+ const delta = isClaudeContentChunk(event.delta) ? event.delta : null;
3234
+ if (delta?.type === "input_json_delta") {
3235
+ const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : undefined;
3236
+ this.handleToolInputDelta(event.index, partialJson);
3237
+ return [];
3238
+ }
3239
+ }
3240
+ else if (event.type === "content_block_stop" && typeof event.index === "number") {
3241
+ const toolId = this.toolUseIndexToId.get(event.index);
3242
+ if (toolId) {
3243
+ this.toolUseIndexToId.delete(event.index);
3244
+ this.toolUseInputBuffers.delete(toolId);
3245
+ }
3246
+ }
3247
+ switch (event.type) {
3248
+ case "content_block_start":
3249
+ return isClaudeContentChunk(event.content_block)
3250
+ ? this.mapBlocksToTimeline([event.content_block], {
3251
+ suppressAssistantText: options?.suppressAssistantText,
3252
+ suppressReasoning: options?.suppressReasoning,
3253
+ })
3254
+ : [];
3255
+ case "content_block_delta":
3256
+ return isClaudeContentChunk(event.delta)
3257
+ ? this.mapBlocksToTimeline([event.delta], {
3258
+ suppressAssistantText: options?.suppressAssistantText,
3259
+ suppressReasoning: options?.suppressReasoning,
3260
+ })
3261
+ : [];
3262
+ default:
3263
+ return [];
3264
+ }
3265
+ }
3266
+ upsertToolUseEntry(block) {
3267
+ const id = typeof block.id === "string" ? block.id : undefined;
3268
+ if (!id) {
3269
+ return null;
3270
+ }
3271
+ const existing = this.toolUseCache.get(id) ??
3272
+ {
3273
+ id,
3274
+ name: typeof block.name === "string" && block.name.length > 0 ? block.name : "tool",
3275
+ server: typeof block.server === "string" && block.server.length > 0
3276
+ ? block.server
3277
+ : typeof block.name === "string" && block.name.length > 0
3278
+ ? block.name
3279
+ : "tool",
3280
+ classification: "generic",
3281
+ started: false,
3282
+ };
3283
+ if (typeof block.name === "string" && block.name.length > 0) {
3284
+ existing.name = block.name;
3285
+ }
3286
+ if (typeof block.server === "string" && block.server.length > 0) {
3287
+ existing.server = block.server;
3288
+ }
3289
+ else if (!existing.server) {
3290
+ existing.server = existing.name;
3291
+ }
3292
+ if (block.type === "tool_use" || block.type === "mcp_tool_use" || block.type === "server_tool_use") {
3293
+ const input = this.normalizeToolInput(block.input);
3294
+ if (input) {
3295
+ this.applyToolInput(existing, input);
3296
+ }
3297
+ }
3298
+ this.toolUseCache.set(id, existing);
3299
+ return existing;
3300
+ }
3301
+ handleToolInputDelta(index, partialJson) {
3302
+ if (typeof index !== "number" || typeof partialJson !== "string") {
3303
+ return;
3304
+ }
3305
+ const toolId = this.toolUseIndexToId.get(index);
3306
+ if (!toolId) {
3307
+ return;
3308
+ }
3309
+ const buffer = (this.toolUseInputBuffers.get(toolId) ?? "") + partialJson;
3310
+ this.toolUseInputBuffers.set(toolId, buffer);
3311
+ let parsed;
3312
+ try {
3313
+ parsed = JSON.parse(buffer);
3314
+ }
3315
+ catch {
3316
+ return;
3317
+ }
3318
+ const entry = this.toolUseCache.get(toolId);
3319
+ const normalized = this.normalizeToolInput(parsed);
3320
+ if (!entry || !normalized) {
3321
+ return;
3322
+ }
3323
+ this.applyToolInput(entry, normalized);
3324
+ this.toolUseCache.set(toolId, entry);
3325
+ this.pushToolCall(mapClaudeRunningToolCall({
3326
+ name: entry.name,
3327
+ callId: toolId,
3328
+ input: normalized,
3329
+ output: null,
3330
+ }));
3331
+ }
3332
+ normalizeToolInput(input) {
3333
+ if (!isMetadata(input)) {
3334
+ return null;
3335
+ }
3336
+ return input;
3337
+ }
3338
+ applyToolInput(entry, input) {
3339
+ entry.input = input;
3340
+ if (this.isCommandTool(entry.name, input)) {
3341
+ entry.classification = "command";
3342
+ entry.commandText = this.extractCommandText(input) ?? entry.commandText;
3343
+ }
3344
+ else {
3345
+ const files = this.extractFileChanges(input);
3346
+ if (files?.length) {
3347
+ entry.classification = "file_change";
3348
+ entry.files = files;
3349
+ }
3350
+ }
3351
+ }
3352
+ isCommandTool(name, input) {
3353
+ const normalized = name.toLowerCase();
3354
+ if (normalized.includes("bash") || normalized.includes("shell") || normalized.includes("terminal") || normalized.includes("command")) {
3355
+ return true;
3356
+ }
3357
+ if (typeof input.command === "string" || Array.isArray(input.command)) {
3358
+ return true;
3359
+ }
3360
+ return false;
3361
+ }
3362
+ extractCommandText(input) {
3363
+ const command = input.command;
3364
+ if (typeof command === "string" && command.length > 0) {
3365
+ return command;
3366
+ }
3367
+ if (Array.isArray(command)) {
3368
+ const tokens = command.filter((value) => typeof value === "string");
3369
+ if (tokens.length > 0) {
3370
+ return tokens.join(" ");
3371
+ }
3372
+ }
3373
+ if (typeof input.description === "string" && input.description.length > 0) {
3374
+ return input.description;
3375
+ }
3376
+ return undefined;
3377
+ }
3378
+ extractFileChanges(input) {
3379
+ if (typeof input.file_path === "string" && input.file_path.length > 0) {
3380
+ const relative = this.relativizePath(input.file_path);
3381
+ if (relative) {
3382
+ return [{ path: relative, kind: this.detectFileKind(input.file_path) }];
3383
+ }
3384
+ }
3385
+ if (typeof input.patch === "string" && input.patch.length > 0) {
3386
+ const files = this.parsePatchFileList(input.patch);
3387
+ if (files.length > 0) {
3388
+ return files.map((entry) => ({
3389
+ path: this.relativizePath(entry.path) ?? entry.path,
3390
+ kind: entry.kind,
3391
+ }));
3392
+ }
3393
+ }
3394
+ if (Array.isArray(input.files)) {
3395
+ const files = [];
3396
+ for (const value of input.files) {
3397
+ if (typeof value === "string" && value.length > 0) {
3398
+ files.push({ path: this.relativizePath(value) ?? value, kind: this.detectFileKind(value) });
3399
+ }
3400
+ }
3401
+ if (files.length > 0) {
3402
+ return files;
3403
+ }
3404
+ }
3405
+ return undefined;
3406
+ }
3407
+ detectFileKind(filePath) {
3408
+ try {
3409
+ return fs.existsSync(filePath) ? "update" : "add";
3410
+ }
3411
+ catch {
3412
+ return "update";
3413
+ }
3414
+ }
3415
+ relativizePath(target) {
3416
+ if (!target) {
3417
+ return undefined;
3418
+ }
3419
+ const cwd = this.config.cwd;
3420
+ if (cwd && target.startsWith(cwd)) {
3421
+ const relative = path.relative(cwd, target);
3422
+ return relative.length > 0 ? relative : path.basename(target);
3423
+ }
3424
+ return target;
3425
+ }
3426
+ parsePatchFileList(patch) {
3427
+ const files = [];
3428
+ const seen = new Set();
3429
+ for (const line of patch.split(/\r?\n/)) {
3430
+ const trimmed = line.trim();
3431
+ let kind = null;
3432
+ let parsedPath = null;
3433
+ if (trimmed.startsWith("*** Add File:")) {
3434
+ kind = "add";
3435
+ parsedPath = trimmed.replace("*** Add File:", "").trim();
3436
+ }
3437
+ else if (trimmed.startsWith("*** Delete File:")) {
3438
+ kind = "delete";
3439
+ parsedPath = trimmed.replace("*** Delete File:", "").trim();
3440
+ }
3441
+ else if (trimmed.startsWith("*** Update File:")) {
3442
+ kind = "update";
3443
+ parsedPath = trimmed.replace("*** Update File:", "").trim();
3444
+ }
3445
+ if (kind && parsedPath && !seen.has(`${kind}:${parsedPath}`)) {
3446
+ seen.add(`${kind}:${parsedPath}`);
3447
+ files.push({ path: parsedPath, kind });
3448
+ }
3449
+ }
3450
+ return files;
3451
+ }
3452
+ }
3453
+ function hasToolLikeBlock(block) {
3454
+ if (!block || typeof block !== "object") {
3455
+ return false;
3456
+ }
3457
+ const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
3458
+ return type.includes("tool");
3459
+ }
3460
+ function readCompactionMetadata(source) {
3461
+ const candidates = [
3462
+ source.compact_metadata,
3463
+ source.compactMetadata,
3464
+ source.compactionMetadata,
3465
+ ];
3466
+ for (const candidate of candidates) {
3467
+ if (!candidate || typeof candidate !== "object") {
3468
+ continue;
3469
+ }
3470
+ const metadata = candidate;
3471
+ const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
3472
+ const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
3473
+ const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
3474
+ return { trigger, preTokens };
3475
+ }
3476
+ return null;
3477
+ }
3478
+ function normalizeHistoryBlocks(content) {
3479
+ if (Array.isArray(content)) {
3480
+ const blocks = content.filter((entry) => isClaudeContentChunk(entry));
3481
+ return blocks.length > 0 ? blocks : null;
3482
+ }
3483
+ if (isClaudeContentChunk(content)) {
3484
+ return [content];
3485
+ }
3486
+ return null;
3487
+ }
3488
+ export function convertClaudeHistoryEntry(entry, mapBlocks) {
3489
+ if (entry.type === "system" && entry.subtype === "compact_boundary") {
3490
+ const compactMetadata = readCompactionMetadata(entry);
3491
+ return [{
3492
+ type: "compaction",
3493
+ status: "completed",
3494
+ trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
3495
+ preTokens: compactMetadata?.preTokens,
3496
+ }];
3497
+ }
3498
+ if (entry.type === "system") {
3499
+ const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
3500
+ if (taskNotificationItem) {
3501
+ return [taskNotificationItem];
3502
+ }
3503
+ }
3504
+ if (entry.isCompactSummary) {
3505
+ return [];
3506
+ }
3507
+ if (entry.type === "user" && isSyntheticUserEntry(entry)) {
3508
+ return [];
3509
+ }
3510
+ const message = entry?.message;
3511
+ if (!message || !("content" in message)) {
3512
+ return [];
3513
+ }
3514
+ const content = message.content;
3515
+ const normalizedBlocks = normalizeHistoryBlocks(content);
3516
+ const contentValue = typeof content === "string"
3517
+ ? content
3518
+ : normalizedBlocks;
3519
+ const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
3520
+ const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
3521
+ ? entry.uuid
3522
+ : null;
3523
+ if (entry.type === "user") {
3524
+ const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
3525
+ content,
3526
+ messageId: userMessageId,
3527
+ });
3528
+ if (taskNotificationItem) {
3529
+ return [taskNotificationItem];
3530
+ }
3531
+ }
3532
+ const timeline = [];
3533
+ if (entry.type === "user") {
3534
+ const text = extractUserMessageText(content);
3535
+ if (text) {
3536
+ timeline.push({
3537
+ type: "user_message",
3538
+ text,
3539
+ ...(userMessageId ? { messageId: userMessageId } : {}),
3540
+ });
3541
+ }
3542
+ }
3543
+ if (hasToolBlock && normalizedBlocks) {
3544
+ const mapped = mapBlocks(normalizedBlocks);
3545
+ if (entry.type === "user") {
3546
+ const toolItems = mapped.filter((item) => item.type === "tool_call");
3547
+ return timeline.length ? [...timeline, ...toolItems] : toolItems;
3548
+ }
3549
+ return mapped;
3550
+ }
3551
+ if (entry.type === "assistant" && contentValue) {
3552
+ return mapBlocks(contentValue);
3553
+ }
3554
+ return timeline;
3555
+ }
3556
+ class Pushable {
3557
+ constructor() {
3558
+ this.queue = [];
3559
+ this.resolvers = [];
3560
+ this.closed = false;
3561
+ }
3562
+ push(item) {
3563
+ if (this.closed) {
3564
+ return;
3565
+ }
3566
+ if (this.resolvers.length > 0) {
3567
+ const resolve = this.resolvers.shift();
3568
+ resolve({ value: item, done: false });
3569
+ }
3570
+ else {
3571
+ this.queue.push(item);
3572
+ }
3573
+ }
3574
+ end() {
3575
+ this.closed = true;
3576
+ while (this.resolvers.length > 0) {
3577
+ const resolve = this.resolvers.shift();
3578
+ resolve({ value: undefined, done: true });
3579
+ }
3580
+ }
3581
+ [Symbol.asyncIterator]() {
3582
+ return {
3583
+ next: () => {
3584
+ if (this.queue.length > 0) {
3585
+ const value = this.queue.shift();
3586
+ if (value !== undefined) {
3587
+ return Promise.resolve({ value, done: false });
3588
+ }
3589
+ }
3590
+ if (this.closed) {
3591
+ return Promise.resolve({ value: undefined, done: true });
3592
+ }
3593
+ return new Promise((resolve) => {
3594
+ this.resolvers.push(resolve);
3595
+ });
3596
+ },
3597
+ };
3598
+ }
3599
+ }
3600
+ async function pathExists(target) {
3601
+ try {
3602
+ await fsPromises.access(target);
3603
+ return true;
3604
+ }
3605
+ catch {
3606
+ return false;
3607
+ }
3608
+ }
3609
+ async function collectRecentClaudeSessions(root, limit) {
3610
+ let projectDirs;
3611
+ try {
3612
+ projectDirs = await fsPromises.readdir(root);
3613
+ }
3614
+ catch {
3615
+ return [];
3616
+ }
3617
+ const candidates = [];
3618
+ for (const dirName of projectDirs) {
3619
+ const projectPath = path.join(root, dirName);
3620
+ let stats;
3621
+ try {
3622
+ stats = await fsPromises.stat(projectPath);
3623
+ }
3624
+ catch {
3625
+ continue;
3626
+ }
3627
+ if (!stats.isDirectory()) {
3628
+ continue;
3629
+ }
3630
+ let files;
3631
+ try {
3632
+ files = await fsPromises.readdir(projectPath);
3633
+ }
3634
+ catch {
3635
+ continue;
3636
+ }
3637
+ for (const file of files) {
3638
+ if (!file.endsWith(".jsonl")) {
3639
+ continue;
3640
+ }
3641
+ const fullPath = path.join(projectPath, file);
3642
+ try {
3643
+ const fileStats = await fsPromises.stat(fullPath);
3644
+ candidates.push({ path: fullPath, mtime: fileStats.mtime });
3645
+ }
3646
+ catch {
3647
+ // ignore stat errors for individual files
3648
+ }
3649
+ }
3650
+ }
3651
+ return candidates
3652
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
3653
+ .slice(0, limit);
3654
+ }
3655
+ async function parseClaudeSessionDescriptor(filePath, mtime) {
3656
+ let content;
3657
+ try {
3658
+ content = await fsPromises.readFile(filePath, "utf8");
3659
+ }
3660
+ catch {
3661
+ return null;
3662
+ }
3663
+ let sessionId = null;
3664
+ let cwd = null;
3665
+ let title = null;
3666
+ const timeline = [];
3667
+ for (const rawLine of content.split(/\r?\n/)) {
3668
+ const line = rawLine.trim();
3669
+ if (!line)
3670
+ continue;
3671
+ let entry;
3672
+ try {
3673
+ entry = JSON.parse(line);
3674
+ }
3675
+ catch {
3676
+ continue;
3677
+ }
3678
+ if (entry?.isSidechain) {
3679
+ continue;
3680
+ }
3681
+ if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
3682
+ continue;
3683
+ }
3684
+ if (!sessionId && typeof entry.sessionId === "string") {
3685
+ sessionId = entry.sessionId;
3686
+ }
3687
+ if (!cwd && typeof entry.cwd === "string") {
3688
+ cwd = entry.cwd;
3689
+ }
3690
+ if (entry.type === "user" && entry.message) {
3691
+ const text = extractClaudeUserText(entry.message);
3692
+ if (text) {
3693
+ if (!title) {
3694
+ title = text;
3695
+ }
3696
+ timeline.push({ type: "user_message", text });
3697
+ }
3698
+ }
3699
+ else if (entry.type === "assistant" && entry.message) {
3700
+ const text = extractClaudeUserText(entry.message);
3701
+ if (text) {
3702
+ timeline.push({ type: "assistant_message", text });
3703
+ }
3704
+ }
3705
+ if (sessionId && cwd && title) {
3706
+ break;
3707
+ }
3708
+ }
3709
+ if (!sessionId || !cwd) {
3710
+ return null;
3711
+ }
3712
+ const persistence = {
3713
+ provider: "claude",
3714
+ sessionId,
3715
+ nativeHandle: sessionId,
3716
+ metadata: {
3717
+ provider: "claude",
3718
+ cwd,
3719
+ },
3720
+ };
3721
+ return {
3722
+ provider: "claude",
3723
+ sessionId,
3724
+ cwd,
3725
+ title: (title ?? "").trim() || `Claude session ${sessionId.slice(0, 8)}`,
3726
+ lastActivityAt: mtime,
3727
+ persistence,
3728
+ timeline,
3729
+ };
3730
+ }
3731
+ function extractClaudeUserText(message) {
3732
+ if (!message) {
3733
+ return null;
3734
+ }
3735
+ if (typeof message.content === "string") {
3736
+ return message.content.trim();
3737
+ }
3738
+ if (typeof message.text === "string") {
3739
+ return message.text.trim();
3740
+ }
3741
+ if (Array.isArray(message.content)) {
3742
+ for (const block of message.content) {
3743
+ if (block && typeof block.text === "string") {
3744
+ return block.text.trim();
3745
+ }
3746
+ }
3747
+ }
3748
+ return null;
3749
+ }
3750
+ //# sourceMappingURL=claude-agent.js.map