@getpaseo/server 0.1.35 → 0.1.38

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 (363) hide show
  1. package/dist/scripts/dev-runner.js +1 -1
  2. package/dist/scripts/dev-runner.js.map +1 -1
  3. package/dist/scripts/{daemon-runner.js → supervisor-entrypoint.js} +38 -10
  4. package/dist/scripts/supervisor-entrypoint.js.map +1 -0
  5. package/dist/scripts/supervisor.js +33 -7
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +175 -0
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +235 -0
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +13 -0
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +53 -0
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-storage.d.ts +10 -10
  16. package/dist/server/server/agent/agent-storage.js +9 -4
  17. package/dist/server/server/agent/agent-storage.js.map +1 -1
  18. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  19. package/dist/server/server/agent/provider-launch-config.js +38 -1
  20. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  21. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  22. package/dist/server/server/agent/providers/claude-agent.js +24 -5
  23. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  24. package/dist/server/server/agent-attention-policy.d.ts +1 -1
  25. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  26. package/dist/server/server/bootstrap.d.ts +0 -4
  27. package/dist/server/server/bootstrap.d.ts.map +1 -1
  28. package/dist/server/server/bootstrap.js +65 -40
  29. package/dist/server/server/bootstrap.js.map +1 -1
  30. package/dist/server/server/chat/chat-mentions.d.ts +31 -0
  31. package/dist/server/server/chat/chat-mentions.d.ts.map +1 -0
  32. package/dist/server/server/chat/chat-mentions.js +71 -0
  33. package/dist/server/server/chat/chat-mentions.js.map +1 -0
  34. package/dist/server/server/chat/chat-rpc-schemas.d.ts +728 -0
  35. package/dist/server/server/chat/chat-rpc-schemas.d.ts.map +1 -0
  36. package/dist/server/server/chat/chat-rpc-schemas.js +103 -0
  37. package/dist/server/server/chat/chat-rpc-schemas.js.map +1 -0
  38. package/dist/server/server/chat/chat-service.d.ts +74 -0
  39. package/dist/server/server/chat/chat-service.d.ts.map +1 -0
  40. package/dist/server/server/chat/chat-service.js +330 -0
  41. package/dist/server/server/chat/chat-service.js.map +1 -0
  42. package/dist/server/server/chat/chat-types.d.ts +75 -0
  43. package/dist/server/server/chat/chat-types.d.ts.map +1 -0
  44. package/dist/server/server/chat/chat-types.js +22 -0
  45. package/dist/server/server/chat/chat-types.js.map +1 -0
  46. package/dist/server/server/checkout-diff-manager.d.ts +41 -0
  47. package/dist/server/server/checkout-diff-manager.d.ts.map +1 -0
  48. package/dist/server/server/checkout-diff-manager.js +272 -0
  49. package/dist/server/server/checkout-diff-manager.js.map +1 -0
  50. package/dist/server/server/checkout-git-utils.d.ts +9 -0
  51. package/dist/server/server/checkout-git-utils.d.ts.map +1 -0
  52. package/dist/server/server/checkout-git-utils.js +37 -0
  53. package/dist/server/server/checkout-git-utils.js.map +1 -0
  54. package/dist/server/server/index.js +0 -4
  55. package/dist/server/server/index.js.map +1 -1
  56. package/dist/server/server/loop/rpc-schemas.d.ts +2937 -0
  57. package/dist/server/server/loop/rpc-schemas.d.ts.map +1 -0
  58. package/dist/server/server/loop/rpc-schemas.js +159 -0
  59. package/dist/server/server/loop/rpc-schemas.js.map +1 -0
  60. package/dist/server/server/loop-service.d.ts +520 -0
  61. package/dist/server/server/loop-service.d.ts.map +1 -0
  62. package/dist/server/server/loop-service.js +741 -0
  63. package/dist/server/server/loop-service.js.map +1 -0
  64. package/dist/server/server/persisted-config.d.ts +10 -10
  65. package/dist/server/server/pid-lock.d.ts +7 -2
  66. package/dist/server/server/pid-lock.d.ts.map +1 -1
  67. package/dist/server/server/pid-lock.js +21 -0
  68. package/dist/server/server/pid-lock.js.map +1 -1
  69. package/dist/server/server/schedule/cron.d.ts +4 -0
  70. package/dist/server/server/schedule/cron.d.ts.map +1 -0
  71. package/dist/server/server/schedule/cron.js +103 -0
  72. package/dist/server/server/schedule/cron.js.map +1 -0
  73. package/dist/server/server/schedule/rpc-schemas.d.ts +2773 -0
  74. package/dist/server/server/schedule/rpc-schemas.d.ts.map +1 -0
  75. package/dist/server/server/schedule/rpc-schemas.js +112 -0
  76. package/dist/server/server/schedule/rpc-schemas.js.map +1 -0
  77. package/dist/server/server/schedule/service.d.ts +39 -0
  78. package/dist/server/server/schedule/service.d.ts.map +1 -0
  79. package/dist/server/server/schedule/service.js +397 -0
  80. package/dist/server/server/schedule/service.js.map +1 -0
  81. package/dist/server/server/schedule/store.d.ts +13 -0
  82. package/dist/server/server/schedule/store.d.ts.map +1 -0
  83. package/dist/server/server/schedule/store.js +56 -0
  84. package/dist/server/server/schedule/store.js.map +1 -0
  85. package/dist/server/server/schedule/types.d.ts +710 -0
  86. package/dist/server/server/schedule/types.d.ts.map +1 -0
  87. package/dist/server/server/schedule/types.js +73 -0
  88. package/dist/server/server/schedule/types.js.map +1 -0
  89. package/dist/server/server/session.d.ts +40 -19
  90. package/dist/server/server/session.d.ts.map +1 -1
  91. package/dist/server/server/session.js +779 -568
  92. package/dist/server/server/session.js.map +1 -1
  93. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts.map +1 -1
  94. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +19 -3
  95. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -1
  96. package/dist/server/server/websocket-server.d.ts +12 -1
  97. package/dist/server/server/websocket-server.d.ts.map +1 -1
  98. package/dist/server/server/websocket-server.js +71 -14
  99. package/dist/server/server/websocket-server.js.map +1 -1
  100. package/dist/server/shared/messages.d.ts +37933 -16895
  101. package/dist/server/shared/messages.d.ts.map +1 -1
  102. package/dist/server/shared/messages.js +41 -0
  103. package/dist/server/shared/messages.js.map +1 -1
  104. package/dist/server/terminal/terminal-manager.js +2 -2
  105. package/dist/server/terminal/terminal-manager.js.map +1 -1
  106. package/dist/server/utils/checkout-git.d.ts +12 -0
  107. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  108. package/dist/server/utils/checkout-git.js +73 -3
  109. package/dist/server/utils/checkout-git.js.map +1 -1
  110. package/dist/server/utils/directory-suggestions.js +20 -4
  111. package/dist/server/utils/directory-suggestions.js.map +1 -1
  112. package/dist/src/server/pid-lock.js +21 -0
  113. package/dist/src/server/pid-lock.js.map +1 -1
  114. package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +19 -3
  115. package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -1
  116. package/package.json +4 -3
  117. package/dist/scripts/daemon-runner.js.map +0 -1
  118. package/dist/src/server/agent/activity-curator.js +0 -243
  119. package/dist/src/server/agent/activity-curator.js.map +0 -1
  120. package/dist/src/server/agent/agent-manager.js +0 -1802
  121. package/dist/src/server/agent/agent-manager.js.map +0 -1
  122. package/dist/src/server/agent/agent-metadata-generator.js +0 -161
  123. package/dist/src/server/agent/agent-metadata-generator.js.map +0 -1
  124. package/dist/src/server/agent/agent-projections.js +0 -254
  125. package/dist/src/server/agent/agent-projections.js.map +0 -1
  126. package/dist/src/server/agent/agent-response-loop.js +0 -304
  127. package/dist/src/server/agent/agent-response-loop.js.map +0 -1
  128. package/dist/src/server/agent/agent-sdk-types.js +0 -12
  129. package/dist/src/server/agent/agent-sdk-types.js.map +0 -1
  130. package/dist/src/server/agent/agent-storage.js +0 -297
  131. package/dist/src/server/agent/agent-storage.js.map +0 -1
  132. package/dist/src/server/agent/agent-title-limits.js +0 -3
  133. package/dist/src/server/agent/agent-title-limits.js.map +0 -1
  134. package/dist/src/server/agent/audio-utils.js +0 -19
  135. package/dist/src/server/agent/audio-utils.js.map +0 -1
  136. package/dist/src/server/agent/dictation-debug.js +0 -50
  137. package/dist/src/server/agent/dictation-debug.js.map +0 -1
  138. package/dist/src/server/agent/mcp-server.js +0 -754
  139. package/dist/src/server/agent/mcp-server.js.map +0 -1
  140. package/dist/src/server/agent/orchestrator-instructions.js +0 -51
  141. package/dist/src/server/agent/orchestrator-instructions.js.map +0 -1
  142. package/dist/src/server/agent/pcm16-resampler.js +0 -63
  143. package/dist/src/server/agent/pcm16-resampler.js.map +0 -1
  144. package/dist/src/server/agent/provider-launch-config.js +0 -176
  145. package/dist/src/server/agent/provider-launch-config.js.map +0 -1
  146. package/dist/src/server/agent/provider-manifest.js +0 -127
  147. package/dist/src/server/agent/provider-manifest.js.map +0 -1
  148. package/dist/src/server/agent/provider-registry.js +0 -45
  149. package/dist/src/server/agent/provider-registry.js.map +0 -1
  150. package/dist/src/server/agent/providers/claude/partial-json.js +0 -306
  151. package/dist/src/server/agent/providers/claude/partial-json.js.map +0 -1
  152. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js +0 -104
  153. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js.map +0 -1
  154. package/dist/src/server/agent/providers/claude/sidechain-tracker.js +0 -230
  155. package/dist/src/server/agent/providers/claude/sidechain-tracker.js.map +0 -1
  156. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +0 -267
  157. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +0 -1
  158. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +0 -121
  159. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +0 -1
  160. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +0 -252
  161. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +0 -1
  162. package/dist/src/server/agent/providers/claude-agent.js +0 -3147
  163. package/dist/src/server/agent/providers/claude-agent.js.map +0 -1
  164. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +0 -104
  165. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +0 -1
  166. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +0 -758
  167. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +0 -1
  168. package/dist/src/server/agent/providers/codex-app-server-agent.js +0 -2949
  169. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +0 -1
  170. package/dist/src/server/agent/providers/codex-rollout-timeline.js +0 -544
  171. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +0 -1
  172. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +0 -39
  173. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +0 -1
  174. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +0 -144
  175. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +0 -1
  176. package/dist/src/server/agent/providers/opencode-agent.js +0 -1193
  177. package/dist/src/server/agent/providers/opencode-agent.js.map +0 -1
  178. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +0 -686
  179. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +0 -1
  180. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +0 -115
  181. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +0 -1
  182. package/dist/src/server/agent/recordings-debug.js +0 -19
  183. package/dist/src/server/agent/recordings-debug.js.map +0 -1
  184. package/dist/src/server/agent/stt-debug.js +0 -33
  185. package/dist/src/server/agent/stt-debug.js.map +0 -1
  186. package/dist/src/server/agent/stt-manager.js +0 -232
  187. package/dist/src/server/agent/stt-manager.js.map +0 -1
  188. package/dist/src/server/agent/timeline-append.js +0 -27
  189. package/dist/src/server/agent/timeline-append.js.map +0 -1
  190. package/dist/src/server/agent/timeline-projection.js +0 -215
  191. package/dist/src/server/agent/timeline-projection.js.map +0 -1
  192. package/dist/src/server/agent/tool-name-normalization.js +0 -45
  193. package/dist/src/server/agent/tool-name-normalization.js.map +0 -1
  194. package/dist/src/server/agent/tts-debug.js +0 -24
  195. package/dist/src/server/agent/tts-debug.js.map +0 -1
  196. package/dist/src/server/agent/tts-manager.js +0 -374
  197. package/dist/src/server/agent/tts-manager.js.map +0 -1
  198. package/dist/src/server/agent/wait-for-agent-tracker.js +0 -53
  199. package/dist/src/server/agent/wait-for-agent-tracker.js.map +0 -1
  200. package/dist/src/server/agent-attention-policy.js +0 -40
  201. package/dist/src/server/agent-attention-policy.js.map +0 -1
  202. package/dist/src/server/allowed-hosts.js +0 -94
  203. package/dist/src/server/allowed-hosts.js.map +0 -1
  204. package/dist/src/server/bootstrap.js +0 -581
  205. package/dist/src/server/bootstrap.js.map +0 -1
  206. package/dist/src/server/client-message-id.js +0 -12
  207. package/dist/src/server/client-message-id.js.map +0 -1
  208. package/dist/src/server/config.js +0 -73
  209. package/dist/src/server/config.js.map +0 -1
  210. package/dist/src/server/connection-offer.js +0 -59
  211. package/dist/src/server/connection-offer.js.map +0 -1
  212. package/dist/src/server/daemon-keypair.js +0 -40
  213. package/dist/src/server/daemon-keypair.js.map +0 -1
  214. package/dist/src/server/daemon-version.js +0 -22
  215. package/dist/src/server/daemon-version.js.map +0 -1
  216. package/dist/src/server/dictation/dictation-stream-manager.js +0 -571
  217. package/dist/src/server/dictation/dictation-stream-manager.js.map +0 -1
  218. package/dist/src/server/file-download/token-store.js +0 -40
  219. package/dist/src/server/file-download/token-store.js.map +0 -1
  220. package/dist/src/server/file-explorer/service.js +0 -180
  221. package/dist/src/server/file-explorer/service.js.map +0 -1
  222. package/dist/src/server/json-utils.js +0 -45
  223. package/dist/src/server/json-utils.js.map +0 -1
  224. package/dist/src/server/messages.js +0 -29
  225. package/dist/src/server/messages.js.map +0 -1
  226. package/dist/src/server/package-version.js +0 -46
  227. package/dist/src/server/package-version.js.map +0 -1
  228. package/dist/src/server/pairing-offer.js +0 -45
  229. package/dist/src/server/pairing-offer.js.map +0 -1
  230. package/dist/src/server/pairing-qr.js +0 -45
  231. package/dist/src/server/pairing-qr.js.map +0 -1
  232. package/dist/src/server/path-utils.js +0 -20
  233. package/dist/src/server/path-utils.js.map +0 -1
  234. package/dist/src/server/persisted-config.js +0 -265
  235. package/dist/src/server/persisted-config.js.map +0 -1
  236. package/dist/src/server/persistence-hooks.js +0 -60
  237. package/dist/src/server/persistence-hooks.js.map +0 -1
  238. package/dist/src/server/push/push-service.js +0 -68
  239. package/dist/src/server/push/push-service.js.map +0 -1
  240. package/dist/src/server/push/token-store.js +0 -70
  241. package/dist/src/server/push/token-store.js.map +0 -1
  242. package/dist/src/server/relay-transport.js +0 -461
  243. package/dist/src/server/relay-transport.js.map +0 -1
  244. package/dist/src/server/server-id.js +0 -63
  245. package/dist/src/server/server-id.js.map +0 -1
  246. package/dist/src/server/session.js +0 -6170
  247. package/dist/src/server/session.js.map +0 -1
  248. package/dist/src/server/speech/audio.js +0 -101
  249. package/dist/src/server/speech/audio.js.map +0 -1
  250. package/dist/src/server/speech/provider-resolver.js +0 -7
  251. package/dist/src/server/speech/provider-resolver.js.map +0 -1
  252. package/dist/src/server/speech/providers/local/config.js +0 -74
  253. package/dist/src/server/speech/providers/local/config.js.map +0 -1
  254. package/dist/src/server/speech/providers/local/models.js +0 -17
  255. package/dist/src/server/speech/providers/local/models.js.map +0 -1
  256. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +0 -436
  257. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +0 -1
  258. package/dist/src/server/speech/providers/local/runtime.js +0 -238
  259. package/dist/src/server/speech/providers/local/runtime.js.map +0 -1
  260. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +0 -166
  261. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +0 -1
  262. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +0 -165
  263. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +0 -1
  264. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +0 -73
  265. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +0 -1
  266. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +0 -84
  267. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +0 -1
  268. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +0 -11
  269. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +0 -1
  270. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +0 -102
  271. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +0 -1
  272. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +0 -135
  273. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +0 -1
  274. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +0 -130
  275. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +0 -1
  276. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +0 -110
  277. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +0 -1
  278. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +0 -138
  279. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +0 -1
  280. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +0 -98
  281. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +0 -1
  282. package/dist/src/server/speech/providers/local/sherpa/silero-vad-provider.js +0 -23
  283. package/dist/src/server/speech/providers/local/sherpa/silero-vad-provider.js.map +0 -1
  284. package/dist/src/server/speech/providers/local/sherpa/silero-vad-session.js +0 -107
  285. package/dist/src/server/speech/providers/local/sherpa/silero-vad-session.js.map +0 -1
  286. package/dist/src/server/speech/providers/openai/config.js +0 -80
  287. package/dist/src/server/speech/providers/openai/config.js.map +0 -1
  288. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +0 -168
  289. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +0 -1
  290. package/dist/src/server/speech/providers/openai/runtime.js +0 -112
  291. package/dist/src/server/speech/providers/openai/runtime.js.map +0 -1
  292. package/dist/src/server/speech/providers/openai/stt.js +0 -206
  293. package/dist/src/server/speech/providers/openai/stt.js.map +0 -1
  294. package/dist/src/server/speech/providers/openai/tts.js +0 -46
  295. package/dist/src/server/speech/providers/openai/tts.js.map +0 -1
  296. package/dist/src/server/speech/speech-config-resolver.js +0 -102
  297. package/dist/src/server/speech/speech-config-resolver.js.map +0 -1
  298. package/dist/src/server/speech/speech-provider.js +0 -2
  299. package/dist/src/server/speech/speech-provider.js.map +0 -1
  300. package/dist/src/server/speech/speech-runtime.js +0 -530
  301. package/dist/src/server/speech/speech-runtime.js.map +0 -1
  302. package/dist/src/server/speech/speech-types.js +0 -8
  303. package/dist/src/server/speech/speech-types.js.map +0 -1
  304. package/dist/src/server/speech/turn-detection-provider.js +0 -2
  305. package/dist/src/server/speech/turn-detection-provider.js.map +0 -1
  306. package/dist/src/server/utils/diff-highlighter.js +0 -257
  307. package/dist/src/server/utils/diff-highlighter.js.map +0 -1
  308. package/dist/src/server/voice/fixed-duration-pcm-ring-buffer.js +0 -35
  309. package/dist/src/server/voice/fixed-duration-pcm-ring-buffer.js.map +0 -1
  310. package/dist/src/server/voice/voice-turn-controller.js +0 -159
  311. package/dist/src/server/voice/voice-turn-controller.js.map +0 -1
  312. package/dist/src/server/voice-config.js +0 -51
  313. package/dist/src/server/voice-config.js.map +0 -1
  314. package/dist/src/server/voice-mcp-bridge-command.js +0 -31
  315. package/dist/src/server/voice-mcp-bridge-command.js.map +0 -1
  316. package/dist/src/server/voice-mcp-bridge.js +0 -109
  317. package/dist/src/server/voice-mcp-bridge.js.map +0 -1
  318. package/dist/src/server/voice-permission-policy.js +0 -13
  319. package/dist/src/server/voice-permission-policy.js.map +0 -1
  320. package/dist/src/server/voice-types.js +0 -2
  321. package/dist/src/server/voice-types.js.map +0 -1
  322. package/dist/src/server/websocket-server.js +0 -991
  323. package/dist/src/server/websocket-server.js.map +0 -1
  324. package/dist/src/server/workspace-registry-bootstrap.js +0 -98
  325. package/dist/src/server/workspace-registry-bootstrap.js.map +0 -1
  326. package/dist/src/server/workspace-registry-model.js +0 -175
  327. package/dist/src/server/workspace-registry-model.js.map +0 -1
  328. package/dist/src/server/workspace-registry.js +0 -151
  329. package/dist/src/server/workspace-registry.js.map +0 -1
  330. package/dist/src/server/worktree-bootstrap.js +0 -508
  331. package/dist/src/server/worktree-bootstrap.js.map +0 -1
  332. package/dist/src/shared/agent-attention-notification.js +0 -130
  333. package/dist/src/shared/agent-attention-notification.js.map +0 -1
  334. package/dist/src/shared/agent-lifecycle.js +0 -8
  335. package/dist/src/shared/agent-lifecycle.js.map +0 -1
  336. package/dist/src/shared/connection-offer.js +0 -17
  337. package/dist/src/shared/connection-offer.js.map +0 -1
  338. package/dist/src/shared/daemon-endpoints.js +0 -122
  339. package/dist/src/shared/daemon-endpoints.js.map +0 -1
  340. package/dist/src/shared/messages.js +0 -2066
  341. package/dist/src/shared/messages.js.map +0 -1
  342. package/dist/src/shared/path-utils.js +0 -16
  343. package/dist/src/shared/path-utils.js.map +0 -1
  344. package/dist/src/shared/terminal-stream-protocol.js +0 -99
  345. package/dist/src/shared/terminal-stream-protocol.js.map +0 -1
  346. package/dist/src/shared/tool-call-display.js +0 -122
  347. package/dist/src/shared/tool-call-display.js.map +0 -1
  348. package/dist/src/terminal/terminal-manager.js +0 -136
  349. package/dist/src/terminal/terminal-manager.js.map +0 -1
  350. package/dist/src/terminal/terminal.js +0 -333
  351. package/dist/src/terminal/terminal.js.map +0 -1
  352. package/dist/src/utils/checkout-git.js +0 -1448
  353. package/dist/src/utils/checkout-git.js.map +0 -1
  354. package/dist/src/utils/directory-suggestions.js +0 -655
  355. package/dist/src/utils/directory-suggestions.js.map +0 -1
  356. package/dist/src/utils/path.js +0 -15
  357. package/dist/src/utils/path.js.map +0 -1
  358. package/dist/src/utils/project-icon.js +0 -389
  359. package/dist/src/utils/project-icon.js.map +0 -1
  360. package/dist/src/utils/worktree-metadata.js +0 -116
  361. package/dist/src/utils/worktree-metadata.js.map +0 -1
  362. package/dist/src/utils/worktree.js +0 -744
  363. package/dist/src/utils/worktree.js.map +0 -1
@@ -1,3147 +0,0 @@
1
- import { 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 { mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./claude/task-notification-tool-call.js";
10
- import { normalizeClaudeModelIdFromText, resolveClaudeModelsFromSdkModels, } from "./claude/sdk-model-resolver.js";
11
- import { parsePartialJsonObject } from "./claude/partial-json.js";
12
- import { ClaudeSidechainTracker } from "./claude/sidechain-tracker.js";
13
- import { applyProviderEnv } from "../provider-launch-config.js";
14
- import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
15
- const fsPromises = promises;
16
- const CLAUDE_SETTING_SOURCES = ["user", "project"];
17
- const CLAUDE_CAPABILITIES = {
18
- supportsStreaming: true,
19
- supportsSessionPersistence: true,
20
- supportsDynamicModes: true,
21
- supportsMcpServers: true,
22
- supportsReasoningStream: true,
23
- supportsToolInvocations: true,
24
- };
25
- const DEFAULT_MODES = [
26
- {
27
- id: "default",
28
- label: "Always Ask",
29
- description: "Prompts for permission the first time a tool is used",
30
- },
31
- {
32
- id: "acceptEdits",
33
- label: "Accept File Edits",
34
- description: "Automatically approves edit-focused tools without prompting",
35
- },
36
- {
37
- id: "plan",
38
- label: "Plan Mode",
39
- description: "Analyze the codebase without executing tools or edits",
40
- },
41
- {
42
- id: "bypassPermissions",
43
- label: "Bypass",
44
- description: "Skip all permission prompts (use with caution)",
45
- },
46
- ];
47
- const VALID_CLAUDE_MODES = new Set(DEFAULT_MODES.map((mode) => mode.id));
48
- const REWIND_COMMAND_NAME = "rewind";
49
- const REWIND_COMMAND = {
50
- name: REWIND_COMMAND_NAME,
51
- description: "Rewind tracked files to a previous user message",
52
- argumentHint: "[user_message_uuid]",
53
- };
54
- const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
55
- const INTERRUPT_PLACEHOLDER_PATTERN = /^\[Request interrupted by user(?:[^\]]*)\]$/;
56
- const NO_RESPONSE_REQUESTED_PLACEHOLDER = "No response requested.";
57
- 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;
58
- function resolveClaudeSpawnCommand(spawnOptions, runtimeSettings) {
59
- const commandConfig = runtimeSettings?.command;
60
- if (!commandConfig || commandConfig.mode === "default") {
61
- return {
62
- command: spawnOptions.command,
63
- args: [...spawnOptions.args],
64
- };
65
- }
66
- if (commandConfig.mode === "append") {
67
- return {
68
- command: spawnOptions.command,
69
- args: [...spawnOptions.args, ...(commandConfig.args ?? [])],
70
- };
71
- }
72
- return {
73
- command: commandConfig.argv[0],
74
- args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
75
- };
76
- }
77
- function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings, launchEnv) {
78
- return {
79
- ...options,
80
- spawnClaudeCodeProcess: (spawnOptions) => {
81
- const resolved = resolveClaudeSpawnCommand(spawnOptions, runtimeSettings);
82
- // The SDK defaults to spawning "node" via PATH lookup, which fails when
83
- // running from the managed runtime bundle where node isn't in PATH.
84
- // Always use process.execPath — the actual node binary running the daemon.
85
- const command = resolved.command === spawnOptions.command ? process.execPath : resolved.command;
86
- const child = spawn(command, resolved.args, {
87
- cwd: spawnOptions.cwd,
88
- env: {
89
- ...applyProviderEnv(spawnOptions.env, runtimeSettings),
90
- ...(launchEnv ?? {}),
91
- },
92
- signal: spawnOptions.signal,
93
- stdio: ["pipe", "pipe", "pipe"],
94
- });
95
- if (typeof options.stderr === "function") {
96
- child.stderr?.on("data", (chunk) => {
97
- options.stderr?.(chunk.toString());
98
- });
99
- }
100
- return child;
101
- },
102
- };
103
- }
104
- function createEmptyClaudePrompt() {
105
- return (async function* empty() { })();
106
- }
107
- function isClaudeThinkingEffort(value) {
108
- return value === "low" || value === "medium" || value === "high" || value === "max";
109
- }
110
- const MAX_RECENT_STDERR_CHARS = 4000;
111
- const STDERR_FLUSH_WAIT_MS = 150;
112
- const STDERR_FLUSH_POLL_INTERVAL_MS = 10;
113
- function summarizeClaudeOptionsForLog(options) {
114
- const systemPromptRaw = options.systemPrompt;
115
- const systemPromptSummary = (() => {
116
- if (!systemPromptRaw) {
117
- return { mode: "none", preset: null };
118
- }
119
- if (typeof systemPromptRaw === "string") {
120
- return { mode: "string", preset: null };
121
- }
122
- const prompt = systemPromptRaw;
123
- const promptType = typeof prompt.type === "string" ? prompt.type : "custom";
124
- return {
125
- mode: promptType === "preset" ? "preset" : "custom",
126
- preset: typeof prompt.preset === "string" && prompt.preset.length > 0 ? prompt.preset : null,
127
- };
128
- })();
129
- const mcpServerNames = options.mcpServers ? Object.keys(options.mcpServers).sort() : [];
130
- return {
131
- cwd: typeof options.cwd === "string" ? options.cwd : null,
132
- permissionMode: typeof options.permissionMode === "string" ? options.permissionMode : null,
133
- model: typeof options.model === "string" ? options.model : null,
134
- includePartialMessages: options.includePartialMessages === true,
135
- settingSources: Array.isArray(options.settingSources) ? options.settingSources : [],
136
- enableFileCheckpointing: options.enableFileCheckpointing === true,
137
- hasResume: typeof options.resume === "string" && options.resume.length > 0,
138
- maxThinkingTokens: typeof options.maxThinkingTokens === "number" ? options.maxThinkingTokens : null,
139
- hasEnv: !!options.env,
140
- envKeyCount: Object.keys(options.env ?? {}).length,
141
- hasMcpServers: mcpServerNames.length > 0,
142
- mcpServerNames,
143
- systemPromptMode: systemPromptSummary.mode,
144
- systemPromptPreset: systemPromptSummary.preset,
145
- hasCanUseTool: typeof options.canUseTool === "function",
146
- hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
147
- hasStderrHandler: typeof options.stderr === "function",
148
- };
149
- }
150
- function isToolResultTextBlock(value) {
151
- return (!!value &&
152
- typeof value === "object" &&
153
- value.type === "text" &&
154
- typeof value.text === "string");
155
- }
156
- function normalizeForDeterministicString(value, seen) {
157
- if (value === null ||
158
- typeof value === "string" ||
159
- typeof value === "number" ||
160
- typeof value === "boolean") {
161
- return value;
162
- }
163
- if (typeof value === "bigint") {
164
- return value.toString();
165
- }
166
- if (typeof value === "function") {
167
- return "[function]";
168
- }
169
- if (typeof value === "symbol") {
170
- return value.toString();
171
- }
172
- if (typeof value === "undefined") {
173
- return "[undefined]";
174
- }
175
- if (Array.isArray(value)) {
176
- return value.map((entry) => normalizeForDeterministicString(entry, seen));
177
- }
178
- if (typeof value === "object") {
179
- const objectValue = value;
180
- if (seen.has(objectValue)) {
181
- return "[circular]";
182
- }
183
- seen.add(objectValue);
184
- const record = value;
185
- const normalized = {};
186
- for (const key of Object.keys(record).sort()) {
187
- normalized[key] = normalizeForDeterministicString(record[key], seen);
188
- }
189
- seen.delete(objectValue);
190
- return normalized;
191
- }
192
- return String(value);
193
- }
194
- function deterministicStringify(value) {
195
- if (typeof value === "undefined") {
196
- return "";
197
- }
198
- try {
199
- const normalized = normalizeForDeterministicString(value, new WeakSet());
200
- if (typeof normalized === "string") {
201
- return normalized;
202
- }
203
- return JSON.stringify(normalized);
204
- }
205
- catch {
206
- try {
207
- return String(value);
208
- }
209
- catch {
210
- return "[unserializable]";
211
- }
212
- }
213
- }
214
- function coerceToolResultContentToString(content) {
215
- if (typeof content === "string") {
216
- return content;
217
- }
218
- if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
219
- return content.map((block) => block.text).join("");
220
- }
221
- return deterministicStringify(content);
222
- }
223
- function normalizeClaudeTranscriptText(value) {
224
- if (typeof value !== "string") {
225
- return null;
226
- }
227
- const normalized = value.trim();
228
- return normalized.length > 0 ? normalized : null;
229
- }
230
- function isClaudeInterruptPlaceholderText(value) {
231
- const normalized = normalizeClaudeTranscriptText(value);
232
- return normalized !== null && INTERRUPT_PLACEHOLDER_PATTERN.test(normalized);
233
- }
234
- function isClaudeNoResponsePlaceholderText(value) {
235
- return normalizeClaudeTranscriptText(value) === NO_RESPONSE_REQUESTED_PLACEHOLDER;
236
- }
237
- function isClaudeTranscriptNoiseText(value) {
238
- return isClaudeInterruptPlaceholderText(value) || isClaudeNoResponsePlaceholderText(value);
239
- }
240
- function collectClaudeTextContentParts(content) {
241
- if (typeof content === "string") {
242
- const normalized = normalizeClaudeTranscriptText(content);
243
- return normalized ? [normalized] : [];
244
- }
245
- if (!Array.isArray(content)) {
246
- return [];
247
- }
248
- const parts = [];
249
- for (const block of content) {
250
- if (!block || typeof block !== "object") {
251
- continue;
252
- }
253
- const text = normalizeClaudeTranscriptText(block.text);
254
- if (text) {
255
- parts.push(text);
256
- continue;
257
- }
258
- const input = normalizeClaudeTranscriptText(block.input);
259
- if (input) {
260
- parts.push(input);
261
- }
262
- }
263
- return parts;
264
- }
265
- function isClaudeTranscriptNoiseContent(content) {
266
- const parts = collectClaudeTextContentParts(content);
267
- return parts.length > 0 && parts.every((part) => isClaudeTranscriptNoiseText(part));
268
- }
269
- export function extractUserMessageText(content) {
270
- if (typeof content === "string") {
271
- const normalized = content.trim();
272
- if (!normalized || isClaudeTranscriptNoiseText(normalized)) {
273
- return null;
274
- }
275
- return normalized;
276
- }
277
- if (!Array.isArray(content)) {
278
- return null;
279
- }
280
- const parts = [];
281
- for (const block of content) {
282
- if (!block || typeof block !== "object") {
283
- continue;
284
- }
285
- const text = typeof block.text === "string" ? block.text : undefined;
286
- if (text && text.trim()) {
287
- const trimmed = text.trim();
288
- if (!isClaudeTranscriptNoiseText(trimmed)) {
289
- parts.push(trimmed);
290
- }
291
- continue;
292
- }
293
- const input = typeof block.input === "string" ? block.input : undefined;
294
- if (input && input.trim()) {
295
- const trimmed = input.trim();
296
- if (!isClaudeTranscriptNoiseText(trimmed)) {
297
- parts.push(trimmed);
298
- }
299
- }
300
- }
301
- if (parts.length === 0) {
302
- return null;
303
- }
304
- const combined = parts.join("\n\n").trim();
305
- return combined.length > 0 ? combined : null;
306
- }
307
- function isMetadata(value) {
308
- return typeof value === "object" && value !== null;
309
- }
310
- function readTrimmedString(value) {
311
- if (typeof value !== "string") {
312
- return undefined;
313
- }
314
- const trimmed = value.trim();
315
- return trimmed.length > 0 ? trimmed : undefined;
316
- }
317
- function isMcpServerConfig(value) {
318
- if (!isMetadata(value)) {
319
- return false;
320
- }
321
- const type = value.type;
322
- if (type === "stdio") {
323
- return typeof value.command === "string";
324
- }
325
- if (type === "http" || type === "sse") {
326
- return typeof value.url === "string";
327
- }
328
- return false;
329
- }
330
- function isMcpServersRecord(value) {
331
- if (!isMetadata(value)) {
332
- return false;
333
- }
334
- for (const config of Object.values(value)) {
335
- if (!isMcpServerConfig(config)) {
336
- return false;
337
- }
338
- }
339
- return true;
340
- }
341
- function isPermissionMode(value) {
342
- return typeof value === "string" && VALID_CLAUDE_MODES.has(value);
343
- }
344
- function coerceSessionMetadata(metadata) {
345
- if (!isMetadata(metadata)) {
346
- return {};
347
- }
348
- const result = {};
349
- if (metadata.provider === "claude" || metadata.provider === "codex") {
350
- result.provider = metadata.provider;
351
- }
352
- if (typeof metadata.cwd === "string") {
353
- result.cwd = metadata.cwd;
354
- }
355
- if (typeof metadata.modeId === "string") {
356
- result.modeId = metadata.modeId;
357
- }
358
- if (typeof metadata.model === "string") {
359
- result.model = metadata.model;
360
- }
361
- if (typeof metadata.title === "string" || metadata.title === null) {
362
- result.title = metadata.title;
363
- }
364
- if (typeof metadata.approvalPolicy === "string") {
365
- result.approvalPolicy = metadata.approvalPolicy;
366
- }
367
- if (typeof metadata.sandboxMode === "string") {
368
- result.sandboxMode = metadata.sandboxMode;
369
- }
370
- if (typeof metadata.networkAccess === "boolean") {
371
- result.networkAccess = metadata.networkAccess;
372
- }
373
- if (typeof metadata.webSearch === "boolean") {
374
- result.webSearch = metadata.webSearch;
375
- }
376
- if (isMetadata(metadata.extra)) {
377
- const extra = {};
378
- if (isMetadata(metadata.extra.codex)) {
379
- extra.codex = metadata.extra.codex;
380
- }
381
- if (isClaudeExtra(metadata.extra.claude)) {
382
- extra.claude = metadata.extra.claude;
383
- }
384
- if (extra.codex || extra.claude) {
385
- result.extra = extra;
386
- }
387
- }
388
- if (typeof metadata.systemPrompt === "string") {
389
- result.systemPrompt = metadata.systemPrompt;
390
- }
391
- if (isMcpServersRecord(metadata.mcpServers)) {
392
- result.mcpServers = metadata.mcpServers;
393
- }
394
- return result;
395
- }
396
- function toClaudeSdkMcpConfig(config) {
397
- switch (config.type) {
398
- case "stdio":
399
- return {
400
- type: "stdio",
401
- command: config.command,
402
- args: config.args,
403
- env: config.env,
404
- };
405
- case "http":
406
- return {
407
- type: "http",
408
- url: config.url,
409
- headers: config.headers,
410
- };
411
- case "sse":
412
- return {
413
- type: "sse",
414
- url: config.url,
415
- headers: config.headers,
416
- };
417
- }
418
- }
419
- function isClaudeContentChunk(value) {
420
- return isMetadata(value) && typeof value.type === "string";
421
- }
422
- function isClaudeExtra(value) {
423
- return isMetadata(value);
424
- }
425
- function isPermissionUpdate(value) {
426
- if (!isMetadata(value)) {
427
- return false;
428
- }
429
- const type = value.type;
430
- if (type !== "addRules" && type !== "replaceRules" && type !== "removeRules") {
431
- return false;
432
- }
433
- const rules = value.rules;
434
- const behavior = value.behavior;
435
- const destination = value.destination;
436
- return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
437
- }
438
- function resolvePermissionKind(toolName, input) {
439
- if (toolName === "ExitPlanMode")
440
- return "plan";
441
- if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
442
- return "question";
443
- }
444
- return "tool";
445
- }
446
- class TimelineAssembler {
447
- constructor() {
448
- this.messages = new Map();
449
- this.finalizedMessageIds = new Set();
450
- this.activeMessageByRun = new Map();
451
- this.syntheticMessageCounter = 0;
452
- }
453
- consume(input) {
454
- if (input.message.type === "assistant") {
455
- return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
456
- }
457
- if (input.message.type === "stream_event") {
458
- return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
459
- }
460
- return [];
461
- }
462
- consumeAssistantMessage(message, runId, messageIdHint) {
463
- const messageId = this.readMessageIdFromAssistantMessage(message) ??
464
- messageIdHint ??
465
- this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
466
- if (!messageId) {
467
- return [];
468
- }
469
- if (this.finalizedMessageIds.has(messageId)) {
470
- return [];
471
- }
472
- const state = this.ensureMessageState(messageId, runId);
473
- const fragments = this.extractFragments(message.message?.content);
474
- return this.applyAbsoluteFragments(state, fragments);
475
- }
476
- consumeStreamEvent(message, runId, messageIdHint) {
477
- const event = message.event;
478
- const eventType = readTrimmedString(event.type);
479
- const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
480
- if (eventType === "message_start") {
481
- const messageId = this.resolveMessageId({
482
- runId,
483
- createIfMissing: true,
484
- messageId: streamEventMessageId,
485
- });
486
- if (!messageId) {
487
- return [];
488
- }
489
- this.ensureMessageState(messageId, runId);
490
- return [];
491
- }
492
- if (eventType === "message_stop") {
493
- const messageId = this.resolveMessageId({
494
- runId,
495
- createIfMissing: false,
496
- messageId: streamEventMessageId,
497
- });
498
- if (!messageId) {
499
- return [];
500
- }
501
- return this.finalizeMessage(messageId, runId);
502
- }
503
- if (eventType === "content_block_start") {
504
- return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
505
- }
506
- if (eventType === "content_block_delta") {
507
- return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
508
- }
509
- return [];
510
- }
511
- consumeDeltaContent(content, runId, messageIdHint) {
512
- const fragments = this.extractFragments(content);
513
- if (fragments.length === 0) {
514
- return [];
515
- }
516
- const messageId = this.resolveMessageId({
517
- runId,
518
- createIfMissing: true,
519
- messageId: messageIdHint,
520
- });
521
- if (!messageId) {
522
- return [];
523
- }
524
- const state = this.ensureMessageState(messageId, runId);
525
- return this.appendFragments(state, fragments);
526
- }
527
- appendFragments(state, fragments) {
528
- for (const fragment of fragments) {
529
- if (fragment.kind === "assistant") {
530
- state.assistantText += fragment.text;
531
- }
532
- else {
533
- state.reasoningText += fragment.text;
534
- }
535
- }
536
- return this.emitNewContent(state);
537
- }
538
- applyAbsoluteFragments(state, fragments) {
539
- const assistantText = fragments
540
- .filter((fragment) => fragment.kind === "assistant")
541
- .map((fragment) => fragment.text)
542
- .join("");
543
- const reasoningText = fragments
544
- .filter((fragment) => fragment.kind === "reasoning")
545
- .map((fragment) => fragment.text)
546
- .join("");
547
- if (assistantText.length > 0) {
548
- if (!assistantText.startsWith(state.assistantText)) {
549
- state.emittedAssistantLength = 0;
550
- }
551
- state.assistantText = assistantText;
552
- }
553
- if (reasoningText.length > 0) {
554
- if (!reasoningText.startsWith(state.reasoningText)) {
555
- state.emittedReasoningLength = 0;
556
- }
557
- state.reasoningText = reasoningText;
558
- }
559
- return this.emitNewContent(state);
560
- }
561
- finalizeMessage(messageId, runId) {
562
- const state = this.messages.get(messageId);
563
- if (!state) {
564
- return [];
565
- }
566
- state.stopped = true;
567
- const items = this.emitNewContent(state);
568
- if (runId && this.activeMessageByRun.get(runId) === messageId) {
569
- this.activeMessageByRun.delete(runId);
570
- }
571
- this.finalizedMessageIds.add(messageId);
572
- this.messages.delete(messageId);
573
- return items;
574
- }
575
- emitNewContent(state) {
576
- const items = [];
577
- const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
578
- if (nextAssistantText.length > 0 &&
579
- nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER &&
580
- !isClaudeTranscriptNoiseText(nextAssistantText)) {
581
- state.emittedAssistantLength = state.assistantText.length;
582
- items.push({ type: "assistant_message", text: nextAssistantText });
583
- }
584
- const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
585
- if (nextReasoningText.length > 0) {
586
- state.emittedReasoningLength = state.reasoningText.length;
587
- items.push({ type: "reasoning", text: nextReasoningText });
588
- }
589
- return items;
590
- }
591
- ensureMessageState(messageId, runId) {
592
- const existing = this.messages.get(messageId);
593
- if (existing) {
594
- existing.stopped = false;
595
- if (runId) {
596
- this.activeMessageByRun.set(runId, messageId);
597
- }
598
- return existing;
599
- }
600
- const created = {
601
- id: messageId,
602
- assistantText: "",
603
- reasoningText: "",
604
- emittedAssistantLength: 0,
605
- emittedReasoningLength: 0,
606
- stopped: false,
607
- };
608
- this.messages.set(messageId, created);
609
- if (runId) {
610
- this.activeMessageByRun.set(runId, messageId);
611
- }
612
- return created;
613
- }
614
- resolveMessageId(input) {
615
- if (input.messageId) {
616
- return input.messageId;
617
- }
618
- if (input.runId) {
619
- const active = this.activeMessageByRun.get(input.runId);
620
- if (active) {
621
- return active;
622
- }
623
- }
624
- if (!input.createIfMissing) {
625
- return null;
626
- }
627
- const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
628
- if (input.runId) {
629
- this.activeMessageByRun.set(input.runId, synthetic);
630
- }
631
- return synthetic;
632
- }
633
- extractFragments(content) {
634
- if (typeof content === "string") {
635
- if (content.length === 0) {
636
- return [];
637
- }
638
- return [{ kind: "assistant", text: content }];
639
- }
640
- const blocks = Array.isArray(content) ? content : [content];
641
- const fragments = [];
642
- for (const rawBlock of blocks) {
643
- if (!isClaudeContentChunk(rawBlock)) {
644
- continue;
645
- }
646
- if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
647
- typeof rawBlock.text === "string" &&
648
- rawBlock.text.length > 0) {
649
- fragments.push({ kind: "assistant", text: rawBlock.text });
650
- }
651
- if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
652
- typeof rawBlock.thinking === "string" &&
653
- rawBlock.thinking.length > 0) {
654
- fragments.push({ kind: "reasoning", text: rawBlock.thinking });
655
- }
656
- }
657
- return fragments;
658
- }
659
- readMessageIdFromAssistantMessage(message) {
660
- const candidate = message;
661
- return (readTrimmedString(candidate.message_id) ?? readTrimmedString(candidate.message?.id) ?? null);
662
- }
663
- readMessageIdFromStreamEvent(event) {
664
- const message = event.message;
665
- return readTrimmedString(event.message_id) ?? readTrimmedString(message?.id) ?? null;
666
- }
667
- }
668
- function isSyntheticUserEntry(entry) {
669
- if (!entry || typeof entry !== "object") {
670
- return false;
671
- }
672
- return entry.isSynthetic === true;
673
- }
674
- export function readEventIdentifiers(message) {
675
- const root = message;
676
- const messageType = readTrimmedString(root.type);
677
- const streamEvent = root.event;
678
- const streamEventMessage = streamEvent?.message;
679
- const messageContainer = root.message;
680
- return {
681
- taskId: readTrimmedString(root.task_id) ??
682
- readTrimmedString(streamEvent?.task_id) ??
683
- readTrimmedString(streamEventMessage?.task_id) ??
684
- readTrimmedString(messageContainer?.task_id) ??
685
- null,
686
- parentMessageId: readTrimmedString(root.parent_message_id) ??
687
- readTrimmedString(streamEvent?.parent_message_id) ??
688
- readTrimmedString(streamEventMessage?.parent_message_id) ??
689
- readTrimmedString(messageContainer?.parent_message_id) ??
690
- null,
691
- messageId: readTrimmedString(root.message_id) ??
692
- readTrimmedString(streamEvent?.message_id) ??
693
- readTrimmedString(streamEventMessage?.id) ??
694
- readTrimmedString(streamEventMessage?.message_id) ??
695
- readTrimmedString(messageContainer?.id) ??
696
- readTrimmedString(messageContainer?.message_id) ??
697
- (messageType === "user" ? readTrimmedString(root.uuid) : null) ??
698
- null,
699
- };
700
- }
701
- export class ClaudeAgentClient {
702
- constructor(options) {
703
- this.provider = "claude";
704
- this.capabilities = CLAUDE_CAPABILITIES;
705
- this.defaults = options.defaults;
706
- this.logger = options.logger.child({ module: "agent", provider: "claude" });
707
- this.runtimeSettings = options.runtimeSettings;
708
- this.queryFactory = options.queryFactory ?? query;
709
- }
710
- async createSession(config, launchContext) {
711
- const claudeConfig = this.assertConfig(config);
712
- return new ClaudeAgentSession(claudeConfig, {
713
- defaults: this.defaults,
714
- runtimeSettings: this.runtimeSettings,
715
- launchEnv: launchContext?.env,
716
- logger: this.logger,
717
- queryFactory: this.queryFactory,
718
- });
719
- }
720
- async resumeSession(handle, overrides, launchContext) {
721
- const metadata = coerceSessionMetadata(handle.metadata);
722
- const merged = { ...metadata, ...overrides };
723
- if (!merged.cwd) {
724
- throw new Error("Claude resume requires the original working directory in metadata");
725
- }
726
- const mergedConfig = { ...merged, provider: "claude", cwd: merged.cwd };
727
- const claudeConfig = this.assertConfig(mergedConfig);
728
- return new ClaudeAgentSession(claudeConfig, {
729
- defaults: this.defaults,
730
- runtimeSettings: this.runtimeSettings,
731
- handle,
732
- launchEnv: launchContext?.env,
733
- logger: this.logger,
734
- queryFactory: this.queryFactory,
735
- });
736
- }
737
- async listModels(options) {
738
- const claudeQuery = this.queryFactory({
739
- prompt: createEmptyClaudePrompt(),
740
- options: applyRuntimeSettingsToClaudeOptions({
741
- cwd: options?.cwd ?? process.cwd(),
742
- permissionMode: "plan",
743
- includePartialMessages: false,
744
- settingSources: CLAUDE_SETTING_SOURCES,
745
- }, this.runtimeSettings),
746
- });
747
- try {
748
- const supportedModels = await claudeQuery.supportedModels();
749
- return resolveClaudeModelsFromSdkModels(supportedModels);
750
- }
751
- catch (error) {
752
- this.logger.warn({ err: error }, "Failed to query Claude supportedModels()");
753
- throw error;
754
- }
755
- finally {
756
- try {
757
- await claudeQuery.return?.();
758
- }
759
- catch {
760
- // ignore control-plane shutdown errors
761
- }
762
- }
763
- }
764
- async listPersistedAgents(options) {
765
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
766
- const projectsRoot = path.join(configDir, "projects");
767
- if (!(await pathExists(projectsRoot))) {
768
- return [];
769
- }
770
- const limit = options?.limit ?? 20;
771
- const candidates = await collectRecentClaudeSessions(projectsRoot, limit * 3);
772
- const descriptors = [];
773
- for (const candidate of candidates) {
774
- const descriptor = await parseClaudeSessionDescriptor(candidate.path, candidate.mtime);
775
- if (descriptor) {
776
- descriptors.push(descriptor);
777
- }
778
- if (descriptors.length >= limit) {
779
- break;
780
- }
781
- }
782
- return descriptors;
783
- }
784
- async isAvailable() {
785
- const command = this.runtimeSettings?.command;
786
- if (command?.mode === "replace") {
787
- return fs.existsSync(command.argv[0]);
788
- }
789
- return true;
790
- }
791
- assertConfig(config) {
792
- if (config.provider !== "claude") {
793
- throw new Error(`ClaudeAgentClient received config for provider '${config.provider}'`);
794
- }
795
- return { ...config, provider: "claude" };
796
- }
797
- }
798
- class ClaudeAgentSession {
799
- constructor(config, options) {
800
- this.provider = "claude";
801
- this.capabilities = CLAUDE_CAPABILITIES;
802
- this.query = null;
803
- this.input = null;
804
- this.availableModes = DEFAULT_MODES;
805
- this.toolUseCache = new Map();
806
- this.toolUseIndexToId = new Map();
807
- this.toolUseInputBuffers = new Map();
808
- this.pendingPermissions = new Map();
809
- this.activeForegroundTurnId = null;
810
- this.autonomousTurn = null;
811
- this.subscribers = new Set();
812
- this.timelineAssembler = new TimelineAssembler();
813
- this.sidechainTracker = new ClaudeSidechainTracker({
814
- getToolInput: (toolUseId) => this.toolUseCache.get(toolUseId)?.input ?? null,
815
- });
816
- this.persistedHistory = [];
817
- this.historyPending = false;
818
- this.turnState = "idle";
819
- this.nextTurnOrdinal = 1;
820
- this.cancelCurrentTurn = null;
821
- this.cachedRuntimeInfo = null;
822
- this.lastOptionsModel = null;
823
- this.lastRuntimeModel = null;
824
- this.compacting = false;
825
- this.queryPumpPromise = null;
826
- this.queryRestartNeeded = false;
827
- this.pendingInterruptAbort = false;
828
- this.lastForegroundPromptText = null;
829
- this.foregroundHasVisibleActivity = false;
830
- this.userMessageIds = [];
831
- this.recentStderr = "";
832
- this.closed = false;
833
- this.handlePermissionRequest = async (toolName, input, options) => {
834
- const requestId = `permission-${randomUUID()}`;
835
- const kind = resolvePermissionKind(toolName, input);
836
- const metadata = {};
837
- if (options.toolUseID) {
838
- metadata.toolUseId = options.toolUseID;
839
- }
840
- if (toolName === "ExitPlanMode" && typeof input.plan === "string") {
841
- metadata.planText = input.plan;
842
- }
843
- const toolDetail = kind === "tool"
844
- ? mapClaudeRunningToolCall({
845
- name: toolName,
846
- callId: options.toolUseID ?? requestId,
847
- input,
848
- output: null,
849
- })?.detail
850
- : undefined;
851
- const request = {
852
- id: requestId,
853
- provider: "claude",
854
- name: toolName,
855
- kind,
856
- input,
857
- detail: toolDetail,
858
- suggestions: options.suggestions?.map((suggestion) => ({ ...suggestion })),
859
- metadata: Object.keys(metadata).length ? metadata : undefined,
860
- };
861
- this.pushEvent({ type: "permission_requested", provider: "claude", request });
862
- return await new Promise((resolve, reject) => {
863
- const cleanupFns = [];
864
- const cleanup = () => {
865
- while (cleanupFns.length) {
866
- const fn = cleanupFns.pop();
867
- try {
868
- fn?.();
869
- }
870
- catch {
871
- // ignore cleanup errors
872
- }
873
- }
874
- };
875
- const abortHandler = () => {
876
- this.pendingPermissions.delete(requestId);
877
- cleanup();
878
- reject(new Error("Permission request aborted"));
879
- };
880
- if (options?.signal) {
881
- if (options.signal.aborted) {
882
- abortHandler();
883
- return;
884
- }
885
- options.signal.addEventListener("abort", abortHandler, { once: true });
886
- cleanupFns.push(() => options.signal?.removeEventListener("abort", abortHandler));
887
- }
888
- this.pendingPermissions.set(requestId, {
889
- request,
890
- resolve,
891
- reject,
892
- cleanup,
893
- });
894
- });
895
- };
896
- this.config = config;
897
- this.launchEnv = options.launchEnv;
898
- this.defaults = options.defaults;
899
- this.runtimeSettings = options.runtimeSettings;
900
- this.logger = options.logger;
901
- this.queryFactory = options.queryFactory ?? query;
902
- const handle = options.handle;
903
- if (handle) {
904
- if (!handle.sessionId) {
905
- throw new Error("Cannot resume: persistence handle has no sessionId");
906
- }
907
- this.claudeSessionId = handle.sessionId;
908
- this.persistence = handle;
909
- this.loadPersistedHistory(handle.sessionId);
910
- }
911
- else {
912
- this.claudeSessionId = null;
913
- this.persistence = null;
914
- }
915
- // Validate mode if provided
916
- if (config.modeId && !VALID_CLAUDE_MODES.has(config.modeId)) {
917
- const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
918
- throw new Error(`Invalid mode '${config.modeId}' for Claude provider. Valid modes: ${validModesList}`);
919
- }
920
- this.currentMode = isPermissionMode(config.modeId) ? config.modeId : "default";
921
- }
922
- get id() {
923
- return this.claudeSessionId;
924
- }
925
- async getRuntimeInfo() {
926
- if (this.cachedRuntimeInfo) {
927
- return { ...this.cachedRuntimeInfo };
928
- }
929
- const info = {
930
- provider: "claude",
931
- sessionId: this.claudeSessionId,
932
- model: this.lastOptionsModel,
933
- modeId: this.currentMode ?? null,
934
- ...(this.lastRuntimeModel
935
- ? {
936
- extra: {
937
- runtimeModel: this.lastRuntimeModel,
938
- },
939
- }
940
- : {}),
941
- };
942
- this.cachedRuntimeInfo = info;
943
- return { ...info };
944
- }
945
- async run(prompt, options) {
946
- const timeline = [];
947
- let finalText = "";
948
- let usage;
949
- let turnId = null;
950
- const bufferedEvents = [];
951
- let settled = false;
952
- let resolveCompletion;
953
- let rejectCompletion;
954
- const processEvent = (event) => {
955
- if (settled) {
956
- return;
957
- }
958
- const eventTurnId = event.turnId;
959
- if (turnId && eventTurnId && eventTurnId !== turnId) {
960
- return;
961
- }
962
- if (event.type === "timeline") {
963
- timeline.push(event.item);
964
- if (event.item.type === "assistant_message") {
965
- if (!finalText) {
966
- finalText = event.item.text;
967
- }
968
- else if (event.item.text.startsWith(finalText)) {
969
- finalText = event.item.text;
970
- }
971
- else {
972
- finalText += event.item.text;
973
- }
974
- }
975
- return;
976
- }
977
- if (event.type === "turn_completed") {
978
- usage = event.usage;
979
- settled = true;
980
- resolveCompletion();
981
- return;
982
- }
983
- if (event.type === "turn_failed") {
984
- settled = true;
985
- rejectCompletion(new Error(event.error));
986
- return;
987
- }
988
- if (event.type === "turn_canceled") {
989
- settled = true;
990
- resolveCompletion();
991
- }
992
- };
993
- const completion = new Promise((resolve, reject) => {
994
- resolveCompletion = resolve;
995
- rejectCompletion = reject;
996
- });
997
- const unsubscribe = this.subscribe((event) => {
998
- if (!turnId) {
999
- bufferedEvents.push(event);
1000
- return;
1001
- }
1002
- processEvent(event);
1003
- });
1004
- try {
1005
- const result = await this.startTurn(prompt, options);
1006
- turnId = result.turnId;
1007
- for (const event of bufferedEvents) {
1008
- processEvent(event);
1009
- }
1010
- if (!settled) {
1011
- await completion;
1012
- }
1013
- }
1014
- finally {
1015
- unsubscribe();
1016
- }
1017
- this.cachedRuntimeInfo = {
1018
- provider: "claude",
1019
- sessionId: this.claudeSessionId,
1020
- model: this.lastOptionsModel,
1021
- modeId: this.currentMode ?? null,
1022
- };
1023
- if (!this.claudeSessionId) {
1024
- throw new Error("Session ID not set after run completed");
1025
- }
1026
- return {
1027
- sessionId: this.claudeSessionId,
1028
- finalText,
1029
- usage,
1030
- timeline,
1031
- };
1032
- }
1033
- async startTurn(prompt, _options) {
1034
- if (this.closed) {
1035
- throw new Error("Claude session is closed");
1036
- }
1037
- if (this.activeForegroundTurnId) {
1038
- throw new Error("A foreground turn is already active");
1039
- }
1040
- const slashCommand = this.resolveSlashCommandInvocation(prompt);
1041
- if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
1042
- const turnId = this.createTurnId("foreground");
1043
- this.activeForegroundTurnId = turnId;
1044
- this.transitionTurnState("foreground", "rewind command");
1045
- void this.executeRewindTurn(turnId, slashCommand);
1046
- return { turnId };
1047
- }
1048
- if (this.autonomousTurn) {
1049
- this.completeAutonomousTurn();
1050
- }
1051
- const sdkMessage = this.toSdkUserMessage(prompt);
1052
- this.lastForegroundPromptText = this.extractPromptText(prompt);
1053
- const turnId = this.createTurnId("foreground");
1054
- this.activeForegroundTurnId = turnId;
1055
- this.foregroundHasVisibleActivity = false;
1056
- this.transitionTurnState("foreground", "foreground turn started");
1057
- this.clearRecentStderr();
1058
- let cancelIssued = false;
1059
- const requestCancel = () => {
1060
- if (cancelIssued) {
1061
- return;
1062
- }
1063
- cancelIssued = true;
1064
- if (this.cancelCurrentTurn === requestCancel) {
1065
- this.cancelCurrentTurn = null;
1066
- }
1067
- this.rejectAllPendingPermissions(new Error("Permission request aborted"));
1068
- this.finishForegroundTurn({
1069
- type: "turn_canceled",
1070
- provider: "claude",
1071
- reason: "Interrupted",
1072
- });
1073
- void this.interruptActiveTurn().catch((error) => {
1074
- this.logger.warn({ err: error }, "Failed to interrupt during cancel");
1075
- });
1076
- };
1077
- this.cancelCurrentTurn = requestCancel;
1078
- this.notifySubscribers({ type: "turn_started", provider: "claude" });
1079
- try {
1080
- await this.ensureQuery();
1081
- if (!this.input) {
1082
- throw new Error("Claude session input stream not initialized");
1083
- }
1084
- this.startQueryPump();
1085
- this.input.push(sdkMessage);
1086
- }
1087
- catch (error) {
1088
- this.finishForegroundTurn(this.buildTurnFailedEvent(error instanceof Error ? error.message : "Claude stream failed"));
1089
- }
1090
- return { turnId };
1091
- }
1092
- subscribe(callback) {
1093
- this.subscribers.add(callback);
1094
- return () => {
1095
- this.subscribers.delete(callback);
1096
- };
1097
- }
1098
- async interrupt() {
1099
- if (this.cancelCurrentTurn) {
1100
- this.cancelCurrentTurn();
1101
- return;
1102
- }
1103
- if (this.autonomousTurn) {
1104
- this.flushPendingToolCalls();
1105
- this.completeAutonomousTurn();
1106
- }
1107
- await this.interruptActiveTurn();
1108
- }
1109
- async *streamHistory() {
1110
- if (!this.historyPending || this.persistedHistory.length === 0) {
1111
- return;
1112
- }
1113
- const history = this.persistedHistory;
1114
- this.persistedHistory = [];
1115
- this.historyPending = false;
1116
- for (const item of history) {
1117
- yield { type: "timeline", item, provider: "claude" };
1118
- }
1119
- }
1120
- async getAvailableModes() {
1121
- return this.availableModes;
1122
- }
1123
- async getCurrentMode() {
1124
- return this.currentMode ?? null;
1125
- }
1126
- async setMode(modeId) {
1127
- // Validate mode
1128
- if (!VALID_CLAUDE_MODES.has(modeId)) {
1129
- const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
1130
- throw new Error(`Invalid mode '${modeId}' for Claude provider. Valid modes: ${validModesList}`);
1131
- }
1132
- const normalized = isPermissionMode(modeId) ? modeId : "default";
1133
- const query = await this.ensureQuery();
1134
- await query.setPermissionMode(normalized);
1135
- this.currentMode = normalized;
1136
- }
1137
- async setModel(modelId) {
1138
- const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
1139
- const query = await this.ensureQuery();
1140
- await query.setModel(normalizedModelId ?? undefined);
1141
- this.config.model = normalizedModelId ?? undefined;
1142
- this.lastOptionsModel = normalizedModelId ?? this.lastOptionsModel;
1143
- this.lastRuntimeModel = null;
1144
- this.cachedRuntimeInfo = null;
1145
- // Model change affects persistence metadata, so invalidate cached handle.
1146
- this.persistence = null;
1147
- }
1148
- async setThinkingOption(thinkingOptionId) {
1149
- const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
1150
- ? thinkingOptionId
1151
- : null;
1152
- if (!normalizedThinkingOptionId || normalizedThinkingOptionId === "default") {
1153
- this.config.thinkingOptionId = undefined;
1154
- }
1155
- else if (isClaudeThinkingEffort(normalizedThinkingOptionId)) {
1156
- this.config.thinkingOptionId = normalizedThinkingOptionId;
1157
- }
1158
- else {
1159
- throw new Error(`Unknown thinking option: ${normalizedThinkingOptionId}`);
1160
- }
1161
- this.queryRestartNeeded = true;
1162
- }
1163
- getPendingPermissions() {
1164
- return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
1165
- }
1166
- async respondToPermission(requestId, response) {
1167
- const pending = this.pendingPermissions.get(requestId);
1168
- if (!pending) {
1169
- throw new Error(`No pending permission request with id '${requestId}'`);
1170
- }
1171
- this.pendingPermissions.delete(requestId);
1172
- pending.cleanup?.();
1173
- if (response.behavior === "allow") {
1174
- if (pending.request.kind === "plan") {
1175
- await this.setMode("acceptEdits");
1176
- this.pushToolCall(mapClaudeCompletedToolCall({
1177
- name: "plan_approval",
1178
- callId: pending.request.id,
1179
- input: pending.request.input ?? null,
1180
- output: { approved: true },
1181
- }));
1182
- }
1183
- const result = {
1184
- behavior: "allow",
1185
- updatedInput: response.updatedInput ?? pending.request.input ?? {},
1186
- updatedPermissions: this.normalizePermissionUpdates(response.updatedPermissions),
1187
- };
1188
- pending.resolve(result);
1189
- }
1190
- else {
1191
- if (pending.request.kind === "tool") {
1192
- this.pushToolCall(mapClaudeFailedToolCall({
1193
- name: pending.request.name,
1194
- callId: (typeof pending.request.metadata?.toolUseId === "string"
1195
- ? pending.request.metadata.toolUseId
1196
- : null) ?? pending.request.id,
1197
- input: pending.request.input ?? null,
1198
- output: null,
1199
- error: { message: response.message ?? "Permission denied" },
1200
- }));
1201
- }
1202
- const result = {
1203
- behavior: "deny",
1204
- message: response.message ?? "Permission request denied",
1205
- interrupt: response.interrupt,
1206
- };
1207
- pending.resolve(result);
1208
- }
1209
- this.pushEvent({
1210
- type: "permission_resolved",
1211
- provider: "claude",
1212
- requestId,
1213
- resolution: response,
1214
- });
1215
- }
1216
- describePersistence() {
1217
- if (this.persistence) {
1218
- return this.persistence;
1219
- }
1220
- if (!this.claudeSessionId) {
1221
- return null;
1222
- }
1223
- this.persistence = {
1224
- provider: "claude",
1225
- sessionId: this.claudeSessionId,
1226
- nativeHandle: this.claudeSessionId,
1227
- metadata: { ...this.config },
1228
- };
1229
- return this.persistence;
1230
- }
1231
- async close() {
1232
- this.logger.trace({
1233
- claudeSessionId: this.claudeSessionId,
1234
- turnState: this.turnState,
1235
- hasQuery: Boolean(this.query),
1236
- hasInput: Boolean(this.input),
1237
- hasActiveForegroundTurnId: Boolean(this.activeForegroundTurnId),
1238
- }, "Claude session close: start");
1239
- this.closed = true;
1240
- this.rejectAllPendingPermissions(new Error("Claude session closed"));
1241
- this.cancelCurrentTurn?.();
1242
- this.subscribers.clear();
1243
- this.activeForegroundTurnId = null;
1244
- this.autonomousTurn = null;
1245
- this.cancelCurrentTurn = null;
1246
- this.turnState = "idle";
1247
- this.sidechainTracker.clear();
1248
- this.input?.end();
1249
- this.query?.close?.();
1250
- await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
1251
- await this.awaitWithTimeout(this.query?.return?.(), "close query return");
1252
- this.query = null;
1253
- this.input = null;
1254
- this.logger.trace({ claudeSessionId: this.claudeSessionId, turnState: this.turnState }, "Claude session close: completed");
1255
- }
1256
- async listCommands() {
1257
- const q = await this.ensureQuery();
1258
- const commands = await q.supportedCommands();
1259
- const commandMap = new Map();
1260
- for (const cmd of commands) {
1261
- if (!commandMap.has(cmd.name)) {
1262
- commandMap.set(cmd.name, {
1263
- name: cmd.name,
1264
- description: cmd.description,
1265
- argumentHint: cmd.argumentHint,
1266
- });
1267
- }
1268
- }
1269
- if (!commandMap.has(REWIND_COMMAND_NAME)) {
1270
- commandMap.set(REWIND_COMMAND_NAME, REWIND_COMMAND);
1271
- }
1272
- return Array.from(commandMap.values()).sort((a, b) => a.name.localeCompare(b.name));
1273
- }
1274
- resolveSlashCommandInvocation(prompt) {
1275
- if (typeof prompt !== "string") {
1276
- return null;
1277
- }
1278
- const parsed = this.parseSlashCommandInput(prompt);
1279
- if (!parsed) {
1280
- return null;
1281
- }
1282
- return parsed.commandName === REWIND_COMMAND_NAME ? parsed : null;
1283
- }
1284
- parseSlashCommandInput(text) {
1285
- const trimmed = text.trim();
1286
- if (!trimmed.startsWith("/") || trimmed.length <= 1) {
1287
- return null;
1288
- }
1289
- const withoutPrefix = trimmed.slice(1);
1290
- const firstWhitespaceIdx = withoutPrefix.search(/\s/);
1291
- const commandName = firstWhitespaceIdx === -1 ? withoutPrefix : withoutPrefix.slice(0, firstWhitespaceIdx);
1292
- if (!commandName || commandName.includes("/")) {
1293
- return null;
1294
- }
1295
- const rawArgs = firstWhitespaceIdx === -1 ? "" : withoutPrefix.slice(firstWhitespaceIdx + 1).trim();
1296
- return rawArgs.length > 0
1297
- ? { commandName, args: rawArgs, rawInput: trimmed }
1298
- : { commandName, rawInput: trimmed };
1299
- }
1300
- buildRewindSuccessMessage(targetUserMessageId, rewindResult) {
1301
- const fileCount = Array.isArray(rewindResult.filesChanged)
1302
- ? rewindResult.filesChanged.length
1303
- : undefined;
1304
- const stats = [];
1305
- if (typeof fileCount === "number") {
1306
- stats.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
1307
- }
1308
- if (typeof rewindResult.insertions === "number") {
1309
- stats.push(`${rewindResult.insertions} insertions`);
1310
- }
1311
- if (typeof rewindResult.deletions === "number") {
1312
- stats.push(`${rewindResult.deletions} deletions`);
1313
- }
1314
- if (stats.length > 0) {
1315
- return `Rewound tracked files to message ${targetUserMessageId} (${stats.join(", ")}).`;
1316
- }
1317
- return `Rewound tracked files to message ${targetUserMessageId}.`;
1318
- }
1319
- async attemptRewind(args) {
1320
- if (typeof args === "string" && args.trim().length > 0) {
1321
- const candidate = args.trim().split(/\s+/)[0] ?? "";
1322
- if (!UUID_PATTERN.test(candidate)) {
1323
- return {
1324
- messageId: null,
1325
- error: "Invalid message UUID. Usage: /rewind <user_message_uuid> or /rewind",
1326
- };
1327
- }
1328
- const rewindResult = await this.rewindFilesOnce(candidate);
1329
- if (rewindResult.canRewind) {
1330
- return { messageId: candidate, result: rewindResult };
1331
- }
1332
- return {
1333
- messageId: null,
1334
- error: rewindResult.error ?? `No file checkpoint found for message ${candidate}.`,
1335
- };
1336
- }
1337
- const candidates = this.getRewindCandidateUserMessageIds();
1338
- if (candidates.length === 0) {
1339
- return {
1340
- messageId: null,
1341
- error: "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
1342
- };
1343
- }
1344
- let lastError;
1345
- for (const candidate of candidates) {
1346
- try {
1347
- const rewindResult = await this.rewindFilesOnce(candidate);
1348
- if (rewindResult.canRewind) {
1349
- return { messageId: candidate, result: rewindResult };
1350
- }
1351
- if (rewindResult.error) {
1352
- lastError = rewindResult.error;
1353
- }
1354
- }
1355
- catch (error) {
1356
- lastError = error instanceof Error ? error.message : "Failed to rewind tracked files.";
1357
- }
1358
- }
1359
- return {
1360
- messageId: null,
1361
- error: lastError ?? "No rewind checkpoints are currently available for this session.",
1362
- };
1363
- }
1364
- async rewindFilesOnce(messageId) {
1365
- try {
1366
- const query = await this.ensureFreshQuery();
1367
- return await query.rewindFiles(messageId, { dryRun: false });
1368
- }
1369
- catch (error) {
1370
- // The Claude SDK transport can close after a rewind call.
1371
- // If that happens, mark the query stale so a follow-up attempt uses a fresh query.
1372
- this.queryRestartNeeded = true;
1373
- throw error;
1374
- }
1375
- }
1376
- async ensureFreshQuery() {
1377
- if (this.query) {
1378
- this.queryRestartNeeded = true;
1379
- }
1380
- return this.ensureQuery();
1381
- }
1382
- getRewindCandidateUserMessageIds() {
1383
- const candidates = [];
1384
- const pushUnique = (value) => {
1385
- if (typeof value === "string" && value.length > 0 && !candidates.includes(value)) {
1386
- candidates.push(value);
1387
- }
1388
- };
1389
- const historyIds = this.readUserMessageIdsFromHistoryFile();
1390
- for (let idx = historyIds.length - 1; idx >= 0; idx -= 1) {
1391
- pushUnique(historyIds[idx]);
1392
- }
1393
- for (let idx = this.persistedHistory.length - 1; idx >= 0; idx -= 1) {
1394
- const item = this.persistedHistory[idx];
1395
- if (item?.type === "user_message") {
1396
- pushUnique(item.messageId);
1397
- }
1398
- }
1399
- for (let idx = this.userMessageIds.length - 1; idx >= 0; idx -= 1) {
1400
- pushUnique(this.userMessageIds[idx]);
1401
- }
1402
- return candidates;
1403
- }
1404
- readUserMessageIdsFromHistoryFile() {
1405
- if (!this.claudeSessionId) {
1406
- return [];
1407
- }
1408
- const historyPath = this.resolveHistoryPath(this.claudeSessionId);
1409
- if (!historyPath || !fs.existsSync(historyPath)) {
1410
- return [];
1411
- }
1412
- try {
1413
- const ids = [];
1414
- const content = fs.readFileSync(historyPath, "utf8");
1415
- for (const line of content.split(/\n+/)) {
1416
- const trimmed = line.trim();
1417
- if (!trimmed)
1418
- continue;
1419
- try {
1420
- const entry = JSON.parse(trimmed);
1421
- if (entry?.type === "user" && typeof entry.uuid === "string") {
1422
- ids.push(entry.uuid);
1423
- }
1424
- }
1425
- catch {
1426
- // ignore malformed lines
1427
- }
1428
- }
1429
- return ids;
1430
- }
1431
- catch {
1432
- return [];
1433
- }
1434
- }
1435
- rememberUserMessageId(messageId) {
1436
- if (typeof messageId !== "string" || messageId.length === 0) {
1437
- return;
1438
- }
1439
- const last = this.userMessageIds[this.userMessageIds.length - 1];
1440
- if (last === messageId) {
1441
- return;
1442
- }
1443
- this.userMessageIds.push(messageId);
1444
- }
1445
- async ensureQuery() {
1446
- if (this.query && !this.queryRestartNeeded) {
1447
- return this.query;
1448
- }
1449
- if (this.queryRestartNeeded && this.query) {
1450
- const oldQuery = this.query;
1451
- const oldInput = this.input;
1452
- // Null out query/input BEFORE awaiting the old iterator's return so the
1453
- // old pump sees this.query !== activeQuery and skips failActiveTurns.
1454
- this.query = null;
1455
- this.input = null;
1456
- this.queryPumpPromise = null;
1457
- this.queryRestartNeeded = false;
1458
- oldInput?.end();
1459
- oldQuery.close?.();
1460
- try {
1461
- await oldQuery.return?.();
1462
- }
1463
- catch {
1464
- /* ignore */
1465
- }
1466
- }
1467
- const input = createAsyncMessageInput();
1468
- const options = this.buildOptions();
1469
- this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
1470
- this.input = input;
1471
- this.query = this.queryFactory({ prompt: input.iterable, options });
1472
- // Do not kick off background control-plane queries here. Methods like
1473
- // supportedCommands()/setPermissionMode() may execute immediately after
1474
- // ensureQuery() (for listCommands()/setMode()), and sharing the same query
1475
- // control plane can cause those calls to wait behind supportedModels().
1476
- return this.query;
1477
- }
1478
- async awaitWithTimeout(promise, label) {
1479
- if (!promise) {
1480
- this.logger.trace({ label }, "Claude query operation skipped (no promise)");
1481
- return;
1482
- }
1483
- const startedAt = Date.now();
1484
- this.logger.trace({ label }, "Claude query operation wait start");
1485
- try {
1486
- await Promise.race([
1487
- promise,
1488
- new Promise((_, reject) => {
1489
- setTimeout(() => reject(new Error("timeout")), 3000);
1490
- }),
1491
- ]);
1492
- this.logger.trace({ label, durationMs: Date.now() - startedAt }, "Claude query operation settled");
1493
- }
1494
- catch (error) {
1495
- this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
1496
- }
1497
- }
1498
- buildOptions() {
1499
- const thinkingOptionId = this.config.thinkingOptionId && this.config.thinkingOptionId !== "default"
1500
- ? this.config.thinkingOptionId
1501
- : undefined;
1502
- let thinking;
1503
- let effort;
1504
- if (thinkingOptionId && isClaudeThinkingEffort(thinkingOptionId)) {
1505
- thinking = { type: "adaptive" };
1506
- effort = thinkingOptionId;
1507
- }
1508
- const appendedSystemPrompt = [
1509
- getOrchestratorModeInstructions(),
1510
- this.config.systemPrompt?.trim(),
1511
- ]
1512
- .filter((entry) => typeof entry === "string" && entry.length > 0)
1513
- .join("\n\n");
1514
- const base = {
1515
- cwd: this.config.cwd,
1516
- includePartialMessages: true,
1517
- permissionMode: this.currentMode,
1518
- agents: this.defaults?.agents,
1519
- canUseTool: this.handlePermissionRequest,
1520
- // Use Claude Code preset system prompt and load CLAUDE.md files
1521
- // Append provider-agnostic system prompt and orchestrator instructions for agents.
1522
- systemPrompt: {
1523
- type: "preset",
1524
- preset: "claude_code",
1525
- append: appendedSystemPrompt,
1526
- },
1527
- settingSources: CLAUDE_SETTING_SOURCES,
1528
- stderr: (data) => {
1529
- this.captureStderr(data);
1530
- this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
1531
- },
1532
- env: {
1533
- ...process.env,
1534
- // Increase MCP timeouts for long-running tool calls (10 minutes)
1535
- MCP_TIMEOUT: "600000",
1536
- MCP_TOOL_TIMEOUT: "600000",
1537
- ...(this.launchEnv ?? {}),
1538
- },
1539
- // Required for provider-level /rewind support.
1540
- enableFileCheckpointing: true,
1541
- // If we have a session ID from a previous query (e.g., after interrupt),
1542
- // resume that session to continue the conversation history.
1543
- ...(this.claudeSessionId ? { resume: this.claudeSessionId } : {}),
1544
- ...(thinking ? { thinking } : {}),
1545
- ...(effort ? { effort } : {}),
1546
- ...this.config.extra?.claude,
1547
- };
1548
- if (this.config.mcpServers) {
1549
- base.mcpServers = this.normalizeMcpServers(this.config.mcpServers);
1550
- }
1551
- if (this.config.model) {
1552
- base.model = this.config.model;
1553
- }
1554
- this.lastOptionsModel = base.model ?? null;
1555
- if (this.claudeSessionId) {
1556
- base.resume = this.claudeSessionId;
1557
- }
1558
- return this.applyRuntimeSettings(base);
1559
- }
1560
- applyRuntimeSettings(options) {
1561
- return applyRuntimeSettingsToClaudeOptions(options, this.runtimeSettings, this.launchEnv);
1562
- }
1563
- normalizeMcpServers(servers) {
1564
- const result = {};
1565
- for (const [name, config] of Object.entries(servers)) {
1566
- result[name] = toClaudeSdkMcpConfig(config);
1567
- }
1568
- return result;
1569
- }
1570
- toSdkUserMessage(prompt) {
1571
- const content = [];
1572
- if (Array.isArray(prompt)) {
1573
- for (const chunk of prompt) {
1574
- if (chunk.type === "text") {
1575
- content.push({ type: "text", text: chunk.text });
1576
- }
1577
- else if (chunk.type === "image") {
1578
- content.push({
1579
- type: "image",
1580
- source: {
1581
- type: "base64",
1582
- media_type: chunk.mimeType,
1583
- data: chunk.data,
1584
- },
1585
- });
1586
- }
1587
- }
1588
- }
1589
- else {
1590
- content.push({ type: "text", text: prompt });
1591
- }
1592
- const messageId = randomUUID();
1593
- this.rememberUserMessageId(messageId);
1594
- return {
1595
- type: "user",
1596
- message: {
1597
- role: "user",
1598
- content,
1599
- },
1600
- parent_tool_use_id: null,
1601
- uuid: messageId,
1602
- session_id: this.claudeSessionId ?? "",
1603
- };
1604
- }
1605
- transitionTurnState(next, reason) {
1606
- if (this.turnState === next) {
1607
- return;
1608
- }
1609
- this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
1610
- this.turnState = next;
1611
- }
1612
- syncTurnState(reason) {
1613
- if (this.activeForegroundTurnId) {
1614
- this.transitionTurnState("foreground", reason);
1615
- return;
1616
- }
1617
- if (this.autonomousTurn) {
1618
- this.transitionTurnState("autonomous", reason);
1619
- return;
1620
- }
1621
- this.transitionTurnState("idle", reason);
1622
- }
1623
- isAbortError(message) {
1624
- const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
1625
- return errors.some((e) => /\baborted\b/i.test(e));
1626
- }
1627
- buildTurnFailedEvent(errorMessage) {
1628
- const normalized = errorMessage.trim() || "Claude run failed";
1629
- const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
1630
- const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
1631
- const diagnostic = this.getRecentStderrDiagnostic();
1632
- return {
1633
- type: "turn_failed",
1634
- provider: "claude",
1635
- error: normalized,
1636
- ...(code ? { code } : {}),
1637
- ...(diagnostic ? { diagnostic } : {}),
1638
- };
1639
- }
1640
- captureStderr(data) {
1641
- const text = data.trim();
1642
- if (!text) {
1643
- return;
1644
- }
1645
- const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
1646
- this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
1647
- }
1648
- clearRecentStderr() {
1649
- this.recentStderr = "";
1650
- }
1651
- getRecentStderrDiagnostic() {
1652
- return this.recentStderr.trim() || undefined;
1653
- }
1654
- async awaitRecentStderrAfterProcessExit(error) {
1655
- if (this.getRecentStderrDiagnostic()) {
1656
- return;
1657
- }
1658
- const message = typeof error === "string" ? error : error instanceof Error ? error.message : "";
1659
- if (!/\bprocess exited with code\b/i.test(message) && !/\bterminated by signal\b/i.test(message)) {
1660
- return;
1661
- }
1662
- const startedAt = Date.now();
1663
- while (!this.closed && !this.getRecentStderrDiagnostic()) {
1664
- if (Date.now() - startedAt >= STDERR_FLUSH_WAIT_MS) {
1665
- return;
1666
- }
1667
- await new Promise((resolve) => setTimeout(resolve, STDERR_FLUSH_POLL_INTERVAL_MS));
1668
- }
1669
- }
1670
- createTurnId(owner) {
1671
- return `${owner}-turn-${this.nextTurnOrdinal++}`;
1672
- }
1673
- isTerminalTurnEvent(event) {
1674
- return (event.type === "turn_completed" ||
1675
- event.type === "turn_failed" ||
1676
- event.type === "turn_canceled");
1677
- }
1678
- extractPromptText(prompt) {
1679
- if (typeof prompt === "string") {
1680
- return prompt;
1681
- }
1682
- const textParts = prompt
1683
- .filter((block) => block.type === "text")
1684
- .map((block) => block.text);
1685
- return textParts.length > 0 ? textParts.join("\n") : null;
1686
- }
1687
- async executeRewindTurn(_turnId, invocation) {
1688
- this.notifySubscribers({ type: "turn_started", provider: "claude" });
1689
- try {
1690
- const rewindAttempt = await this.attemptRewind(invocation.args);
1691
- if (!rewindAttempt.messageId || !rewindAttempt.result) {
1692
- this.finishForegroundTurn({
1693
- type: "turn_failed",
1694
- provider: "claude",
1695
- error: rewindAttempt.error ??
1696
- "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
1697
- });
1698
- return;
1699
- }
1700
- this.notifySubscribers({
1701
- type: "timeline",
1702
- provider: "claude",
1703
- item: {
1704
- type: "assistant_message",
1705
- text: this.buildRewindSuccessMessage(rewindAttempt.messageId, rewindAttempt.result),
1706
- },
1707
- });
1708
- this.finishForegroundTurn({ type: "turn_completed", provider: "claude" });
1709
- }
1710
- catch (error) {
1711
- this.finishForegroundTurn({
1712
- type: "turn_failed",
1713
- provider: "claude",
1714
- error: error instanceof Error ? error.message : "Failed to rewind tracked files",
1715
- });
1716
- }
1717
- }
1718
- shouldRecoverInterruptedQueryAbort(error, consecutiveRecoveries) {
1719
- if (consecutiveRecoveries >= 3) {
1720
- return false;
1721
- }
1722
- const message = typeof error === "string"
1723
- ? error
1724
- : error instanceof Error
1725
- ? `${error.message}\n${error.stack ?? ""}`
1726
- : JSON.stringify(error);
1727
- return message.toLowerCase().includes("request was aborted");
1728
- }
1729
- finishForegroundTurn(event) {
1730
- if (event.type === "turn_failed" || event.type === "turn_canceled") {
1731
- this.flushPendingToolCalls();
1732
- }
1733
- this.notifySubscribers(event);
1734
- this.activeForegroundTurnId = null;
1735
- this.lastForegroundPromptText = null;
1736
- this.cancelCurrentTurn = null;
1737
- this.syncTurnState("foreground turn terminal");
1738
- }
1739
- dispatchEvents(events) {
1740
- let terminalSeen = false;
1741
- for (const event of events) {
1742
- this.notifySubscribers(event);
1743
- terminalSeen || (terminalSeen = this.isTerminalTurnEvent(event));
1744
- }
1745
- if (terminalSeen) {
1746
- if (this.activeForegroundTurnId) {
1747
- this.activeForegroundTurnId = null;
1748
- this.lastForegroundPromptText = null;
1749
- this.cancelCurrentTurn = null;
1750
- this.syncTurnState("foreground turn terminal");
1751
- }
1752
- else if (this.autonomousTurn) {
1753
- this.autonomousTurn = null;
1754
- this.syncTurnState("autonomous turn terminal");
1755
- }
1756
- }
1757
- }
1758
- startAutonomousTurn() {
1759
- if (this.autonomousTurn) {
1760
- return;
1761
- }
1762
- this.autonomousTurn = {
1763
- id: this.createTurnId("autonomous"),
1764
- };
1765
- this.notifySubscribers({ type: "turn_started", provider: "claude" });
1766
- this.syncTurnState("autonomous turn started");
1767
- }
1768
- completeAutonomousTurn() {
1769
- if (!this.autonomousTurn) {
1770
- return;
1771
- }
1772
- this.notifySubscribers({ type: "turn_completed", provider: "claude" });
1773
- this.autonomousTurn = null;
1774
- this.syncTurnState("autonomous turn completed");
1775
- }
1776
- failActiveTurns(errorMessage) {
1777
- const failure = this.buildTurnFailedEvent(errorMessage);
1778
- this.flushPendingToolCalls();
1779
- if (this.activeForegroundTurnId) {
1780
- this.finishForegroundTurn(failure);
1781
- return;
1782
- }
1783
- if (this.autonomousTurn) {
1784
- this.dispatchEvents([failure]);
1785
- }
1786
- }
1787
- startQueryPump() {
1788
- if (this.closed || this.queryPumpPromise) {
1789
- return;
1790
- }
1791
- const pump = this.runQueryPump().catch((error) => {
1792
- this.logger.trace({ err: error }, "Claude query pump exited unexpectedly");
1793
- });
1794
- this.queryPumpPromise = pump;
1795
- pump.finally(() => {
1796
- if (this.queryPumpPromise === pump) {
1797
- this.queryPumpPromise = null;
1798
- }
1799
- });
1800
- }
1801
- async runQueryPump() {
1802
- let activeQuery;
1803
- try {
1804
- activeQuery = await this.ensureQuery();
1805
- }
1806
- catch (error) {
1807
- this.logger.trace({ err: error }, "Failed to initialize Claude query pump");
1808
- this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
1809
- return;
1810
- }
1811
- let consecutiveInterruptAbortRecoveries = 0;
1812
- try {
1813
- while (!this.closed && this.query === activeQuery) {
1814
- try {
1815
- for await (const message of activeQuery) {
1816
- this.logger.trace({
1817
- claudeSessionId: this.claudeSessionId,
1818
- messageType: message.type,
1819
- messageSubtype: "subtype" in message ? message.subtype : undefined,
1820
- messageUuid: "uuid" in message ? message.uuid : undefined,
1821
- }, "Claude query pump: raw SDK message");
1822
- consecutiveInterruptAbortRecoveries = 0;
1823
- if (await this.handleMissingResumedConversation(message, activeQuery)) {
1824
- return;
1825
- }
1826
- this.routeSdkMessageFromPump(message);
1827
- }
1828
- if (!this.closed && this.query === activeQuery) {
1829
- this.failActiveTurns("Claude stream ended before terminal result");
1830
- }
1831
- return;
1832
- }
1833
- catch (error) {
1834
- if (!this.closed &&
1835
- this.query === activeQuery &&
1836
- this.shouldRecoverInterruptedQueryAbort(error, consecutiveInterruptAbortRecoveries)) {
1837
- consecutiveInterruptAbortRecoveries += 1;
1838
- this.logger.debug({ recoveries: consecutiveInterruptAbortRecoveries }, "Recovering Claude query pump after interrupt abort");
1839
- continue;
1840
- }
1841
- if (!this.closed && this.query === activeQuery) {
1842
- await this.awaitRecentStderrAfterProcessExit(error);
1843
- this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
1844
- }
1845
- return;
1846
- }
1847
- }
1848
- }
1849
- finally {
1850
- if (this.query === activeQuery) {
1851
- this.query = null;
1852
- this.input = null;
1853
- }
1854
- }
1855
- }
1856
- routeSdkMessageFromPump(message) {
1857
- // Suppress stale results from interrupted requests. The cancel path already
1858
- // emitted the terminal event; this result is leftover from the killed API
1859
- // request. Consume the flag on ANY result so it doesn't linger.
1860
- if (message.type === "result" && this.pendingInterruptAbort) {
1861
- this.pendingInterruptAbort = false;
1862
- if (message.subtype !== "success") {
1863
- this.logger.debug("Suppressing stale non-success result from interrupted request");
1864
- return;
1865
- }
1866
- }
1867
- if (message.type === "result" &&
1868
- message.subtype !== "success" &&
1869
- this.isAbortError(message)) {
1870
- this.logger.debug("Suppressing abort result by content");
1871
- return;
1872
- }
1873
- const isForeground = Boolean(this.activeForegroundTurnId);
1874
- const assistantishMessage = message.type === "assistant" ||
1875
- message.type === "stream_event" ||
1876
- message.type === "tool_progress" ||
1877
- (message.type === "system" && message.subtype === "task_notification");
1878
- if (!isForeground && assistantishMessage) {
1879
- this.startAutonomousTurn();
1880
- }
1881
- if (!isForeground && !this.autonomousTurn && message.type === "result") {
1882
- return;
1883
- }
1884
- const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? null;
1885
- const identifiers = readEventIdentifiers(message);
1886
- this.logger.trace({
1887
- claudeSessionId: this.claudeSessionId,
1888
- messageType: message.type,
1889
- turnId,
1890
- }, "Claude query pump: SDK message");
1891
- const messageEvents = this.translateMessageToEvents(message, {
1892
- suppressAssistantText: true,
1893
- suppressReasoning: true,
1894
- });
1895
- const assistantTimelineEvents = this.timelineAssembler
1896
- .consume({
1897
- message,
1898
- runId: turnId,
1899
- messageIdHint: identifiers.messageId,
1900
- })
1901
- .map((item) => ({
1902
- type: "timeline",
1903
- item,
1904
- provider: "claude",
1905
- }));
1906
- // User message dedup: suppress echoed user messages that match the foreground prompt
1907
- const filteredMessageEvents = messageEvents.filter((event) => {
1908
- if (event.type === "timeline" &&
1909
- event.item.type === "user_message" &&
1910
- this.activeForegroundTurnId &&
1911
- this.lastForegroundPromptText) {
1912
- if (event.item.text.trim() === this.lastForegroundPromptText.trim()) {
1913
- return false;
1914
- }
1915
- }
1916
- return true;
1917
- });
1918
- const events = [...filteredMessageEvents, ...assistantTimelineEvents];
1919
- if (events.length === 0) {
1920
- return;
1921
- }
1922
- if (this.pendingInterruptAbort &&
1923
- message.type === "result" &&
1924
- events.some((event) => event.type === "turn_completed" || event.type === "turn_failed") &&
1925
- (!this.activeForegroundTurnId || !this.foregroundHasVisibleActivity)) {
1926
- this.pendingInterruptAbort = false;
1927
- this.logger.debug("Suppressing stale Claude interrupt terminal result");
1928
- return;
1929
- }
1930
- if (this.activeForegroundTurnId &&
1931
- events.some((event) => event.type === "timeline" ||
1932
- event.type === "permission_requested" ||
1933
- event.type === "permission_resolved")) {
1934
- this.foregroundHasVisibleActivity = true;
1935
- }
1936
- this.dispatchEvents(events);
1937
- }
1938
- async handleMissingResumedConversation(message, query) {
1939
- const staleResumeError = this.readMissingResumedConversationError(message);
1940
- if (!staleResumeError) {
1941
- return false;
1942
- }
1943
- this.logger.warn({
1944
- claudeSessionId: this.claudeSessionId,
1945
- error: staleResumeError,
1946
- }, "Claude resumed session no longer exists; invalidating persisted session");
1947
- this.failActiveTurns(staleResumeError);
1948
- this.input?.end();
1949
- await this.awaitWithTimeout(query.return?.(), "query pump return on missing resumed conversation");
1950
- if (this.query === query) {
1951
- this.query = null;
1952
- this.input = null;
1953
- }
1954
- this.claudeSessionId = null;
1955
- this.persistence = null;
1956
- this.persistedHistory = [];
1957
- this.historyPending = false;
1958
- this.cachedRuntimeInfo = null;
1959
- this.queryRestartNeeded = false;
1960
- this.autonomousTurn = null;
1961
- this.activeForegroundTurnId = null;
1962
- this.syncTurnState("missing resumed conversation");
1963
- return true;
1964
- }
1965
- async interruptActiveTurn() {
1966
- const queryToInterrupt = this.query;
1967
- if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
1968
- this.logger.trace("interruptActiveTurn: no query to interrupt");
1969
- return;
1970
- }
1971
- this.pendingInterruptAbort = true;
1972
- try {
1973
- await this.awaitWithTimeout(queryToInterrupt.interrupt(), "interruptActiveTurn query.interrupt()");
1974
- }
1975
- catch (error) {
1976
- this.logger.warn({ err: error }, "Failed to interrupt active turn");
1977
- }
1978
- }
1979
- translateMessageToEvents(message, options) {
1980
- const parentToolUseId = "parent_tool_use_id" in message
1981
- ? message.parent_tool_use_id
1982
- : null;
1983
- if (parentToolUseId) {
1984
- return this.sidechainTracker.handleMessage(message, parentToolUseId);
1985
- }
1986
- const events = [];
1987
- const fallbackThreadSessionId = this.captureSessionIdFromMessage(message);
1988
- if (fallbackThreadSessionId) {
1989
- events.push({
1990
- type: "thread_started",
1991
- provider: "claude",
1992
- sessionId: fallbackThreadSessionId,
1993
- });
1994
- }
1995
- switch (message.type) {
1996
- case "system":
1997
- if (message.subtype === "init") {
1998
- const threadSessionId = this.handleSystemMessage(message);
1999
- if (threadSessionId) {
2000
- events.push({
2001
- type: "thread_started",
2002
- provider: "claude",
2003
- sessionId: threadSessionId,
2004
- });
2005
- }
2006
- }
2007
- else if (message.subtype === "status") {
2008
- const status = message.status;
2009
- if (status === "compacting") {
2010
- this.compacting = true;
2011
- events.push({
2012
- type: "timeline",
2013
- item: { type: "compaction", status: "loading" },
2014
- provider: "claude",
2015
- });
2016
- }
2017
- }
2018
- else if (message.subtype === "compact_boundary") {
2019
- const compactMetadata = readCompactionMetadata(message);
2020
- events.push({
2021
- type: "timeline",
2022
- item: {
2023
- type: "compaction",
2024
- status: "completed",
2025
- trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
2026
- preTokens: compactMetadata?.preTokens,
2027
- },
2028
- provider: "claude",
2029
- });
2030
- }
2031
- else if (message.subtype === "task_notification") {
2032
- const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
2033
- if (taskNotificationItem) {
2034
- events.push({
2035
- type: "timeline",
2036
- item: taskNotificationItem,
2037
- provider: "claude",
2038
- });
2039
- }
2040
- }
2041
- break;
2042
- case "user": {
2043
- if (isSyntheticUserEntry(message)) {
2044
- break;
2045
- }
2046
- if (this.compacting) {
2047
- this.compacting = false;
2048
- break;
2049
- }
2050
- const messageId = typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
2051
- this.rememberUserMessageId(messageId);
2052
- const content = message.message?.content;
2053
- const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
2054
- content,
2055
- messageId,
2056
- });
2057
- if (taskNotificationItem) {
2058
- events.push({
2059
- type: "timeline",
2060
- item: taskNotificationItem,
2061
- provider: "claude",
2062
- });
2063
- break;
2064
- }
2065
- if (typeof content === "string" && content.length > 0) {
2066
- if (!isClaudeTranscriptNoiseText(content)) {
2067
- events.push({
2068
- type: "timeline",
2069
- item: {
2070
- type: "user_message",
2071
- text: content,
2072
- ...(messageId ? { messageId } : {}),
2073
- },
2074
- provider: "claude",
2075
- });
2076
- }
2077
- }
2078
- else if (Array.isArray(content)) {
2079
- const timelineItems = this.mapBlocksToTimeline(content, {
2080
- textMessageType: "user_message",
2081
- });
2082
- for (const item of timelineItems) {
2083
- if (item.type === "user_message" && messageId && !item.messageId) {
2084
- events.push({
2085
- type: "timeline",
2086
- item: { ...item, messageId },
2087
- provider: "claude",
2088
- });
2089
- continue;
2090
- }
2091
- events.push({ type: "timeline", item, provider: "claude" });
2092
- }
2093
- }
2094
- break;
2095
- }
2096
- case "assistant": {
2097
- const timelineItems = this.mapBlocksToTimeline(message.message.content, {
2098
- suppressAssistantText: options?.suppressAssistantText ?? false,
2099
- suppressReasoning: options?.suppressReasoning ?? false,
2100
- });
2101
- for (const item of timelineItems) {
2102
- events.push({ type: "timeline", item, provider: "claude" });
2103
- }
2104
- break;
2105
- }
2106
- case "stream_event": {
2107
- const timelineItems = this.mapPartialEvent(message.event, {
2108
- suppressAssistantText: options?.suppressAssistantText ?? false,
2109
- suppressReasoning: options?.suppressReasoning ?? false,
2110
- });
2111
- for (const item of timelineItems) {
2112
- events.push({ type: "timeline", item, provider: "claude" });
2113
- }
2114
- break;
2115
- }
2116
- case "result": {
2117
- const usage = this.convertUsage(message);
2118
- if (message.subtype === "success") {
2119
- events.push({ type: "turn_completed", provider: "claude", usage });
2120
- }
2121
- else {
2122
- const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
2123
- ? message.errors.join("\n")
2124
- : "Claude run failed";
2125
- events.push(this.buildTurnFailedEvent(errorMessage));
2126
- }
2127
- break;
2128
- }
2129
- default:
2130
- break;
2131
- }
2132
- return events;
2133
- }
2134
- captureSessionIdFromMessage(message) {
2135
- const msg = message;
2136
- const sessionIdRaw = typeof msg.session_id === "string"
2137
- ? msg.session_id
2138
- : typeof msg.sessionId === "string"
2139
- ? msg.sessionId
2140
- : typeof msg.session?.id === "string"
2141
- ? msg.session.id
2142
- : "";
2143
- const sessionId = sessionIdRaw.trim();
2144
- if (!sessionId) {
2145
- return null;
2146
- }
2147
- if (this.claudeSessionId === null) {
2148
- this.claudeSessionId = sessionId;
2149
- this.persistence = null;
2150
- return sessionId;
2151
- }
2152
- if (this.claudeSessionId === sessionId) {
2153
- return null;
2154
- }
2155
- throw new Error(`CRITICAL: Claude session ID overwrite detected! ` +
2156
- `Existing: ${this.claudeSessionId}, New: ${sessionId}. ` +
2157
- `This indicates a session identity corruption bug.`);
2158
- }
2159
- handleSystemMessage(message) {
2160
- if (message.subtype !== "init") {
2161
- return null;
2162
- }
2163
- const msg = message;
2164
- const newSessionIdRaw = typeof msg.session_id === "string"
2165
- ? msg.session_id
2166
- : typeof msg.sessionId === "string"
2167
- ? msg.sessionId
2168
- : typeof msg.session?.id === "string"
2169
- ? msg.session.id
2170
- : "";
2171
- const newSessionId = newSessionIdRaw.trim();
2172
- if (!newSessionId) {
2173
- return null;
2174
- }
2175
- const existingSessionId = this.claudeSessionId;
2176
- let threadStartedSessionId = null;
2177
- if (existingSessionId === null) {
2178
- this.claudeSessionId = newSessionId;
2179
- threadStartedSessionId = newSessionId;
2180
- this.logger.debug({ sessionId: newSessionId }, "Claude session ID set for the first time");
2181
- }
2182
- else if (existingSessionId === newSessionId) {
2183
- this.logger.debug({ sessionId: newSessionId }, "Claude session ID unchanged (same value)");
2184
- }
2185
- else {
2186
- throw new Error(`CRITICAL: Claude session ID overwrite detected! ` +
2187
- `Existing: ${existingSessionId}, New: ${newSessionId}. ` +
2188
- `This indicates a session identity corruption bug.`);
2189
- }
2190
- this.availableModes = DEFAULT_MODES;
2191
- this.currentMode = message.permissionMode;
2192
- this.persistence = null;
2193
- if (message.model) {
2194
- const normalizedRuntimeModel = normalizeClaudeModelIdFromText(message.model);
2195
- this.logger.debug({ runtimeModel: message.model, normalizedRuntimeModel }, "Captured runtime model from SDK init");
2196
- if (normalizedRuntimeModel) {
2197
- this.lastOptionsModel = normalizedRuntimeModel;
2198
- }
2199
- else if (!this.lastOptionsModel) {
2200
- this.lastOptionsModel = this.config.model ?? null;
2201
- }
2202
- this.lastRuntimeModel = message.model;
2203
- this.cachedRuntimeInfo = null;
2204
- }
2205
- return threadStartedSessionId;
2206
- }
2207
- readMissingResumedConversationError(message) {
2208
- if (message.type !== "result" || message.subtype !== "error_during_execution") {
2209
- return null;
2210
- }
2211
- if (!this.claudeSessionId) {
2212
- return null;
2213
- }
2214
- const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
2215
- for (const entry of errors) {
2216
- if (typeof entry !== "string") {
2217
- continue;
2218
- }
2219
- const match = entry.match(/^No conversation found with session ID:\s*(.+)$/);
2220
- if (!match) {
2221
- continue;
2222
- }
2223
- if (match[1]?.trim() === this.claudeSessionId) {
2224
- return entry.trim();
2225
- }
2226
- }
2227
- return null;
2228
- }
2229
- convertUsage(message) {
2230
- if (!message.usage) {
2231
- return undefined;
2232
- }
2233
- return {
2234
- inputTokens: message.usage.input_tokens,
2235
- cachedInputTokens: message.usage.cache_read_input_tokens,
2236
- outputTokens: message.usage.output_tokens,
2237
- totalCostUsd: message.total_cost_usd,
2238
- };
2239
- }
2240
- enqueueTimeline(item) {
2241
- this.pushEvent({ type: "timeline", item, provider: "claude" });
2242
- }
2243
- flushPendingToolCalls() {
2244
- for (const [id, entry] of this.toolUseCache) {
2245
- if (entry.started) {
2246
- this.pushToolCall(mapClaudeCanceledToolCall({
2247
- name: entry.name,
2248
- callId: id,
2249
- input: entry.input ?? null,
2250
- output: null,
2251
- }));
2252
- }
2253
- }
2254
- this.toolUseCache.clear();
2255
- this.sidechainTracker.clear();
2256
- }
2257
- pushToolCall(item, target) {
2258
- if (!item) {
2259
- return;
2260
- }
2261
- if (target) {
2262
- target.push(item);
2263
- return;
2264
- }
2265
- this.enqueueTimeline(item);
2266
- }
2267
- pushEvent(event) {
2268
- this.notifySubscribers(event);
2269
- }
2270
- notifySubscribers(event) {
2271
- const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id;
2272
- const tagged = turnId ? { ...event, turnId } : event;
2273
- for (const callback of this.subscribers) {
2274
- try {
2275
- callback(tagged);
2276
- }
2277
- catch (error) {
2278
- this.logger.warn({ err: error }, "Subscriber callback threw");
2279
- }
2280
- }
2281
- }
2282
- normalizePermissionUpdates(updates) {
2283
- if (!updates || updates.length === 0) {
2284
- return undefined;
2285
- }
2286
- const normalized = updates.filter(isPermissionUpdate);
2287
- return normalized.length > 0 ? normalized : undefined;
2288
- }
2289
- rejectAllPendingPermissions(error) {
2290
- for (const [id, pending] of this.pendingPermissions) {
2291
- pending.cleanup?.();
2292
- pending.reject(error);
2293
- this.pendingPermissions.delete(id);
2294
- }
2295
- }
2296
- loadPersistedHistory(sessionId) {
2297
- try {
2298
- const historyPath = this.resolveHistoryPath(sessionId);
2299
- if (!historyPath || !fs.existsSync(historyPath)) {
2300
- return;
2301
- }
2302
- this.ingestPersistedHistory(fs.readFileSync(historyPath, "utf8"));
2303
- }
2304
- catch (error) {
2305
- // ignore history load failures
2306
- }
2307
- }
2308
- ingestPersistedHistory(content) {
2309
- if (!content) {
2310
- return;
2311
- }
2312
- const timeline = [];
2313
- for (const line of content.split(/\r?\n/)) {
2314
- this.ingestPersistedHistoryLine(line, timeline);
2315
- }
2316
- if (timeline.length > 0) {
2317
- this.persistedHistory = [...this.persistedHistory, ...timeline];
2318
- this.historyPending = true;
2319
- }
2320
- }
2321
- ingestPersistedHistoryLine(line, timeline) {
2322
- const trimmed = line.trim();
2323
- if (!trimmed) {
2324
- return;
2325
- }
2326
- let entry;
2327
- try {
2328
- entry = JSON.parse(trimmed);
2329
- }
2330
- catch {
2331
- return;
2332
- }
2333
- if (entry.isSidechain) {
2334
- return;
2335
- }
2336
- if (entry.type === "user" && typeof entry.uuid === "string") {
2337
- this.rememberUserMessageId(entry.uuid);
2338
- }
2339
- const items = this.convertHistoryEntry(entry);
2340
- if (items.length > 0) {
2341
- timeline.push(...items);
2342
- }
2343
- }
2344
- resolveHistoryPath(sessionId) {
2345
- const cwd = this.config.cwd;
2346
- if (!cwd)
2347
- return null;
2348
- // Match Claude CLI's path sanitization: replace slashes, dots, and underscores with dashes
2349
- const sanitized = cwd.replace(/[\\/\.]/g, "-").replace(/_/g, "-");
2350
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
2351
- const dir = path.join(configDir, "projects", sanitized);
2352
- return path.join(dir, `${sessionId}.jsonl`);
2353
- }
2354
- convertHistoryEntry(entry) {
2355
- return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
2356
- }
2357
- // Maps Claude content blocks into AgentTimelineItems.
2358
- //
2359
- // textMessageType controls what type text blocks emit:
2360
- // - "assistant_message" (default): one item per text block (streaming granularity)
2361
- // - "user_message": coalesces all text blocks into a single user_message
2362
- // (matches extractUserMessageText semantics: trim each block, join with "\n\n")
2363
- //
2364
- // suppressAssistantText only applies when textMessageType is "assistant_message" — user text
2365
- // must never be suppressed since the TimelineAssembler only handles assistant text.
2366
- //
2367
- // NOTE: convertClaudeHistoryEntry uses extractUserMessageText directly instead of this function
2368
- // for user entries. Both paths must produce equivalent user_message items.
2369
- mapBlocksToTimeline(content, options) {
2370
- const textMessageType = options?.textMessageType ?? "assistant_message";
2371
- const suppressText = textMessageType === "assistant_message" && (options?.suppressAssistantText ?? false);
2372
- const suppressReasoning = options?.suppressReasoning ?? false;
2373
- if (typeof content === "string") {
2374
- if (!content ||
2375
- content === INTERRUPT_TOOL_USE_PLACEHOLDER ||
2376
- isClaudeTranscriptNoiseText(content)) {
2377
- return [];
2378
- }
2379
- if (suppressText) {
2380
- return [];
2381
- }
2382
- return [{ type: textMessageType, text: content }];
2383
- }
2384
- const items = [];
2385
- // User SDK entries can arrive as multiple text blocks, but Paseo treats them as one message.
2386
- const userTextParts = [];
2387
- for (const block of content) {
2388
- switch (block.type) {
2389
- case "text":
2390
- case "text_delta":
2391
- if (block.text &&
2392
- block.text !== INTERRUPT_TOOL_USE_PLACEHOLDER &&
2393
- !isClaudeTranscriptNoiseText(block.text)) {
2394
- if (textMessageType === "user_message") {
2395
- const trimmed = block.text.trim();
2396
- if (trimmed) {
2397
- userTextParts.push(trimmed);
2398
- }
2399
- }
2400
- else if (!suppressText) {
2401
- items.push({ type: "assistant_message", text: block.text });
2402
- }
2403
- }
2404
- break;
2405
- case "thinking":
2406
- case "thinking_delta":
2407
- if (block.thinking) {
2408
- if (!suppressReasoning) {
2409
- items.push({ type: "reasoning", text: block.thinking });
2410
- }
2411
- }
2412
- break;
2413
- case "tool_use":
2414
- case "server_tool_use":
2415
- case "mcp_tool_use": {
2416
- this.handleToolUseStart(block, items);
2417
- break;
2418
- }
2419
- case "tool_result":
2420
- case "mcp_tool_result":
2421
- case "web_fetch_tool_result":
2422
- case "web_search_tool_result":
2423
- case "code_execution_tool_result":
2424
- case "bash_code_execution_tool_result":
2425
- case "text_editor_code_execution_tool_result": {
2426
- this.handleToolResult(block, items);
2427
- break;
2428
- }
2429
- default:
2430
- break;
2431
- }
2432
- }
2433
- if (textMessageType === "user_message" && userTextParts.length > 0) {
2434
- items.unshift({
2435
- type: "user_message",
2436
- text: userTextParts.join("\n\n"),
2437
- });
2438
- }
2439
- return items;
2440
- }
2441
- handleToolUseStart(block, items) {
2442
- const entry = this.upsertToolUseEntry(block);
2443
- if (!entry) {
2444
- return;
2445
- }
2446
- if (entry.started) {
2447
- return;
2448
- }
2449
- entry.started = true;
2450
- this.toolUseCache.set(entry.id, entry);
2451
- this.pushToolCall(mapClaudeRunningToolCall({
2452
- name: entry.name,
2453
- callId: entry.id,
2454
- input: entry.input ?? this.normalizeToolInput(block.input) ?? null,
2455
- output: null,
2456
- }), items);
2457
- }
2458
- handleToolResult(block, items) {
2459
- const entry = typeof block.tool_use_id === "string" ? this.toolUseCache.get(block.tool_use_id) : undefined;
2460
- const toolName = entry?.name ?? block.tool_name ?? "tool";
2461
- const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
2462
- ? block.tool_use_id
2463
- : (entry?.id ?? null);
2464
- // Extract output from block.content (SDK always returns content in string form)
2465
- const output = this.buildToolOutput(block, entry);
2466
- if (block.is_error) {
2467
- this.pushToolCall(mapClaudeFailedToolCall({
2468
- name: toolName,
2469
- callId,
2470
- input: entry?.input ?? null,
2471
- output: output ?? null,
2472
- error: block,
2473
- }), items);
2474
- }
2475
- else {
2476
- this.pushToolCall(mapClaudeCompletedToolCall({
2477
- name: toolName,
2478
- callId,
2479
- input: entry?.input ?? null,
2480
- output: output ?? null,
2481
- }), items);
2482
- }
2483
- if (typeof block.tool_use_id === "string") {
2484
- this.toolUseCache.delete(block.tool_use_id);
2485
- this.sidechainTracker.delete(block.tool_use_id);
2486
- }
2487
- }
2488
- buildToolOutput(block, entry) {
2489
- if (block.is_error) {
2490
- return undefined;
2491
- }
2492
- const server = entry?.server ?? block.server ?? "tool";
2493
- const tool = entry?.name ?? block.tool_name ?? "tool";
2494
- const content = coerceToolResultContentToString(block.content);
2495
- const input = entry?.input;
2496
- // Build structured result based on tool type
2497
- const structured = this.buildStructuredToolResult(server, tool, content, input);
2498
- if (structured) {
2499
- return structured;
2500
- }
2501
- // Fallback format - try to parse JSON first
2502
- const result = {};
2503
- if (content.length > 0) {
2504
- try {
2505
- // If content is a JSON string, parse it
2506
- result.output = JSON.parse(content);
2507
- }
2508
- catch {
2509
- // If not JSON, return unchanged (no extra wrapping)
2510
- result.output = content;
2511
- }
2512
- }
2513
- // Preserve file changes tracked during tool execution
2514
- if (entry?.files?.length) {
2515
- result.files = entry.files;
2516
- }
2517
- return Object.keys(result).length > 0 ? result : undefined;
2518
- }
2519
- buildStructuredToolResult(server, tool, output, input) {
2520
- const normalizedServer = server.toLowerCase();
2521
- const normalizedTool = tool.toLowerCase();
2522
- // Command execution tools
2523
- if (normalizedServer.includes("bash") ||
2524
- normalizedServer.includes("shell") ||
2525
- normalizedServer.includes("command") ||
2526
- normalizedTool.includes("bash") ||
2527
- normalizedTool.includes("shell") ||
2528
- normalizedTool.includes("command") ||
2529
- (input && (typeof input.command === "string" || Array.isArray(input.command)))) {
2530
- const command = this.extractCommandText(input ?? {}) ?? "command";
2531
- return {
2532
- type: "command",
2533
- command,
2534
- output,
2535
- cwd: typeof input?.cwd === "string" ? input.cwd : undefined,
2536
- };
2537
- }
2538
- // File write tools (new files or complete replacements)
2539
- if (normalizedTool.includes("write") ||
2540
- normalizedTool === "write_file" ||
2541
- normalizedTool === "create_file") {
2542
- if (input && typeof input.file_path === "string") {
2543
- return {
2544
- type: "file_write",
2545
- filePath: input.file_path,
2546
- oldContent: "",
2547
- newContent: typeof input.content === "string" ? input.content : output,
2548
- };
2549
- }
2550
- }
2551
- // File edit/patch tools
2552
- if (normalizedTool.includes("edit") ||
2553
- normalizedTool.includes("patch") ||
2554
- normalizedTool === "apply_patch" ||
2555
- normalizedTool === "apply_diff") {
2556
- if (input && typeof input.file_path === "string") {
2557
- // Support both old_str/new_str and old_string/new_string parameter names
2558
- const oldContent = typeof input.old_str === "string"
2559
- ? input.old_str
2560
- : typeof input.old_string === "string"
2561
- ? input.old_string
2562
- : undefined;
2563
- const newContent = typeof input.new_str === "string"
2564
- ? input.new_str
2565
- : typeof input.new_string === "string"
2566
- ? input.new_string
2567
- : undefined;
2568
- return {
2569
- type: "file_edit",
2570
- filePath: input.file_path,
2571
- diff: typeof input.patch === "string"
2572
- ? input.patch
2573
- : typeof input.diff === "string"
2574
- ? input.diff
2575
- : undefined,
2576
- oldContent,
2577
- newContent,
2578
- };
2579
- }
2580
- }
2581
- // File read tools
2582
- if (normalizedTool.includes("read") ||
2583
- normalizedTool === "read_file" ||
2584
- normalizedTool === "view_file") {
2585
- if (input && typeof input.file_path === "string") {
2586
- return {
2587
- type: "file_read",
2588
- filePath: input.file_path,
2589
- content: output,
2590
- };
2591
- }
2592
- }
2593
- return undefined;
2594
- }
2595
- mapPartialEvent(event, options) {
2596
- if (event.type === "content_block_start") {
2597
- const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
2598
- if (block?.type === "tool_use" &&
2599
- typeof event.index === "number" &&
2600
- typeof block.id === "string") {
2601
- this.toolUseIndexToId.set(event.index, block.id);
2602
- this.toolUseInputBuffers.delete(block.id);
2603
- }
2604
- }
2605
- else if (event.type === "content_block_delta") {
2606
- const delta = isClaudeContentChunk(event.delta) ? event.delta : null;
2607
- if (delta?.type === "input_json_delta") {
2608
- const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : undefined;
2609
- this.handleToolInputDelta(event.index, partialJson);
2610
- return [];
2611
- }
2612
- }
2613
- else if (event.type === "content_block_stop" && typeof event.index === "number") {
2614
- const toolId = this.toolUseIndexToId.get(event.index);
2615
- if (toolId) {
2616
- this.toolUseIndexToId.delete(event.index);
2617
- this.toolUseInputBuffers.delete(toolId);
2618
- }
2619
- }
2620
- switch (event.type) {
2621
- case "content_block_start":
2622
- return isClaudeContentChunk(event.content_block)
2623
- ? this.mapBlocksToTimeline([event.content_block], {
2624
- suppressAssistantText: options?.suppressAssistantText,
2625
- suppressReasoning: options?.suppressReasoning,
2626
- })
2627
- : [];
2628
- case "content_block_delta":
2629
- return isClaudeContentChunk(event.delta)
2630
- ? this.mapBlocksToTimeline([event.delta], {
2631
- suppressAssistantText: options?.suppressAssistantText,
2632
- suppressReasoning: options?.suppressReasoning,
2633
- })
2634
- : [];
2635
- default:
2636
- return [];
2637
- }
2638
- }
2639
- upsertToolUseEntry(block) {
2640
- const id = typeof block.id === "string" ? block.id : undefined;
2641
- if (!id) {
2642
- return null;
2643
- }
2644
- const existing = this.toolUseCache.get(id) ??
2645
- {
2646
- id,
2647
- name: typeof block.name === "string" && block.name.length > 0 ? block.name : "tool",
2648
- server: typeof block.server === "string" && block.server.length > 0
2649
- ? block.server
2650
- : typeof block.name === "string" && block.name.length > 0
2651
- ? block.name
2652
- : "tool",
2653
- classification: "generic",
2654
- started: false,
2655
- };
2656
- if (typeof block.name === "string" && block.name.length > 0) {
2657
- existing.name = block.name;
2658
- }
2659
- if (typeof block.server === "string" && block.server.length > 0) {
2660
- existing.server = block.server;
2661
- }
2662
- else if (!existing.server) {
2663
- existing.server = existing.name;
2664
- }
2665
- if (block.type === "tool_use" ||
2666
- block.type === "mcp_tool_use" ||
2667
- block.type === "server_tool_use") {
2668
- const input = this.normalizeToolInput(block.input);
2669
- if (input) {
2670
- this.applyToolInput(existing, input);
2671
- }
2672
- }
2673
- this.toolUseCache.set(id, existing);
2674
- return existing;
2675
- }
2676
- handleToolInputDelta(index, partialJson) {
2677
- if (typeof index !== "number" || typeof partialJson !== "string") {
2678
- return;
2679
- }
2680
- const toolId = this.toolUseIndexToId.get(index);
2681
- if (!toolId) {
2682
- return;
2683
- }
2684
- const buffer = (this.toolUseInputBuffers.get(toolId) ?? "") + partialJson;
2685
- this.toolUseInputBuffers.set(toolId, buffer);
2686
- const entry = this.toolUseCache.get(toolId);
2687
- const parsed = parsePartialJsonObject(buffer);
2688
- if (!entry || !parsed) {
2689
- return;
2690
- }
2691
- const normalized = this.normalizeToolInput(parsed.value);
2692
- if (!normalized) {
2693
- return;
2694
- }
2695
- if (!parsed.complete && Object.keys(normalized).length === 0) {
2696
- return;
2697
- }
2698
- if (this.areToolInputsEqual(entry.input ?? undefined, normalized)) {
2699
- return;
2700
- }
2701
- this.applyToolInput(entry, normalized);
2702
- this.toolUseCache.set(toolId, entry);
2703
- this.pushToolCall(mapClaudeRunningToolCall({
2704
- name: entry.name,
2705
- callId: toolId,
2706
- input: normalized,
2707
- output: null,
2708
- }));
2709
- }
2710
- normalizeToolInput(input) {
2711
- if (!isMetadata(input)) {
2712
- return null;
2713
- }
2714
- return input;
2715
- }
2716
- areToolInputsEqual(left, right) {
2717
- if (!left) {
2718
- return false;
2719
- }
2720
- const leftKeys = Object.keys(left);
2721
- const rightKeys = Object.keys(right);
2722
- if (leftKeys.length !== rightKeys.length) {
2723
- return false;
2724
- }
2725
- return rightKeys.every((key) => left[key] === right[key]);
2726
- }
2727
- applyToolInput(entry, input) {
2728
- entry.input = input;
2729
- if (this.isCommandTool(entry.name, input)) {
2730
- entry.classification = "command";
2731
- entry.commandText = this.extractCommandText(input) ?? entry.commandText;
2732
- }
2733
- else {
2734
- const files = this.extractFileChanges(input);
2735
- if (files?.length) {
2736
- entry.classification = "file_change";
2737
- entry.files = files;
2738
- }
2739
- }
2740
- }
2741
- isCommandTool(name, input) {
2742
- const normalized = name.toLowerCase();
2743
- if (normalized.includes("bash") ||
2744
- normalized.includes("shell") ||
2745
- normalized.includes("terminal") ||
2746
- normalized.includes("command")) {
2747
- return true;
2748
- }
2749
- if (typeof input.command === "string" || Array.isArray(input.command)) {
2750
- return true;
2751
- }
2752
- return false;
2753
- }
2754
- extractCommandText(input) {
2755
- const command = input.command;
2756
- if (typeof command === "string" && command.length > 0) {
2757
- return command;
2758
- }
2759
- if (Array.isArray(command)) {
2760
- const tokens = command.filter((value) => typeof value === "string");
2761
- if (tokens.length > 0) {
2762
- return tokens.join(" ");
2763
- }
2764
- }
2765
- if (typeof input.description === "string" && input.description.length > 0) {
2766
- return input.description;
2767
- }
2768
- return undefined;
2769
- }
2770
- extractFileChanges(input) {
2771
- if (typeof input.file_path === "string" && input.file_path.length > 0) {
2772
- const relative = this.relativizePath(input.file_path);
2773
- if (relative) {
2774
- return [{ path: relative, kind: this.detectFileKind(input.file_path) }];
2775
- }
2776
- }
2777
- if (typeof input.patch === "string" && input.patch.length > 0) {
2778
- const files = this.parsePatchFileList(input.patch);
2779
- if (files.length > 0) {
2780
- return files.map((entry) => ({
2781
- path: this.relativizePath(entry.path) ?? entry.path,
2782
- kind: entry.kind,
2783
- }));
2784
- }
2785
- }
2786
- if (Array.isArray(input.files)) {
2787
- const files = [];
2788
- for (const value of input.files) {
2789
- if (typeof value === "string" && value.length > 0) {
2790
- files.push({
2791
- path: this.relativizePath(value) ?? value,
2792
- kind: this.detectFileKind(value),
2793
- });
2794
- }
2795
- }
2796
- if (files.length > 0) {
2797
- return files;
2798
- }
2799
- }
2800
- return undefined;
2801
- }
2802
- detectFileKind(filePath) {
2803
- try {
2804
- return fs.existsSync(filePath) ? "update" : "add";
2805
- }
2806
- catch {
2807
- return "update";
2808
- }
2809
- }
2810
- relativizePath(target) {
2811
- if (!target) {
2812
- return undefined;
2813
- }
2814
- const cwd = this.config.cwd;
2815
- if (cwd && target.startsWith(cwd)) {
2816
- const relative = path.relative(cwd, target);
2817
- return relative.length > 0 ? relative : path.basename(target);
2818
- }
2819
- return target;
2820
- }
2821
- parsePatchFileList(patch) {
2822
- const files = [];
2823
- const seen = new Set();
2824
- for (const line of patch.split(/\r?\n/)) {
2825
- const trimmed = line.trim();
2826
- let kind = null;
2827
- let parsedPath = null;
2828
- if (trimmed.startsWith("*** Add File:")) {
2829
- kind = "add";
2830
- parsedPath = trimmed.replace("*** Add File:", "").trim();
2831
- }
2832
- else if (trimmed.startsWith("*** Delete File:")) {
2833
- kind = "delete";
2834
- parsedPath = trimmed.replace("*** Delete File:", "").trim();
2835
- }
2836
- else if (trimmed.startsWith("*** Update File:")) {
2837
- kind = "update";
2838
- parsedPath = trimmed.replace("*** Update File:", "").trim();
2839
- }
2840
- if (kind && parsedPath && !seen.has(`${kind}:${parsedPath}`)) {
2841
- seen.add(`${kind}:${parsedPath}`);
2842
- files.push({ path: parsedPath, kind });
2843
- }
2844
- }
2845
- return files;
2846
- }
2847
- }
2848
- function hasToolLikeBlock(block) {
2849
- if (!block || typeof block !== "object") {
2850
- return false;
2851
- }
2852
- const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
2853
- return type.includes("tool");
2854
- }
2855
- function readCompactionMetadata(source) {
2856
- const candidates = [source.compact_metadata, source.compactMetadata, source.compactionMetadata];
2857
- for (const candidate of candidates) {
2858
- if (!candidate || typeof candidate !== "object") {
2859
- continue;
2860
- }
2861
- const metadata = candidate;
2862
- const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
2863
- const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
2864
- const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
2865
- return { trigger, preTokens };
2866
- }
2867
- return null;
2868
- }
2869
- function normalizeHistoryBlocks(content) {
2870
- if (Array.isArray(content)) {
2871
- const blocks = content.filter((entry) => isClaudeContentChunk(entry));
2872
- return blocks.length > 0 ? blocks : null;
2873
- }
2874
- if (isClaudeContentChunk(content)) {
2875
- return [content];
2876
- }
2877
- return null;
2878
- }
2879
- export function convertClaudeHistoryEntry(entry, mapBlocks) {
2880
- if (entry.type === "system" && entry.subtype === "compact_boundary") {
2881
- const compactMetadata = readCompactionMetadata(entry);
2882
- return [
2883
- {
2884
- type: "compaction",
2885
- status: "completed",
2886
- trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
2887
- preTokens: compactMetadata?.preTokens,
2888
- },
2889
- ];
2890
- }
2891
- const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
2892
- if (taskNotificationItem) {
2893
- return [taskNotificationItem];
2894
- }
2895
- if (entry.isCompactSummary) {
2896
- return [];
2897
- }
2898
- if (entry.type === "user" && isSyntheticUserEntry(entry)) {
2899
- return [];
2900
- }
2901
- const message = entry?.message;
2902
- if (!message || !("content" in message)) {
2903
- return [];
2904
- }
2905
- const content = message.content;
2906
- if ((entry.type === "user" || entry.type === "assistant") &&
2907
- isClaudeTranscriptNoiseContent(content)) {
2908
- return [];
2909
- }
2910
- const normalizedBlocks = normalizeHistoryBlocks(content);
2911
- const contentValue = typeof content === "string" ? content : normalizedBlocks;
2912
- const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
2913
- const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
2914
- ? entry.uuid
2915
- : null;
2916
- if (entry.type === "user") {
2917
- const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
2918
- content,
2919
- messageId: userMessageId,
2920
- });
2921
- if (taskNotificationItem) {
2922
- return [taskNotificationItem];
2923
- }
2924
- }
2925
- const timeline = [];
2926
- if (entry.type === "user") {
2927
- const text = extractUserMessageText(content);
2928
- if (text) {
2929
- timeline.push({
2930
- type: "user_message",
2931
- text,
2932
- ...(userMessageId ? { messageId: userMessageId } : {}),
2933
- });
2934
- }
2935
- }
2936
- if (hasToolBlock && normalizedBlocks) {
2937
- const mapped = mapBlocks(normalizedBlocks);
2938
- if (entry.type === "user") {
2939
- const toolItems = mapped.filter((item) => item.type === "tool_call");
2940
- return timeline.length ? [...timeline, ...toolItems] : toolItems;
2941
- }
2942
- return mapped;
2943
- }
2944
- if (entry.type === "assistant" && contentValue) {
2945
- return mapBlocks(contentValue);
2946
- }
2947
- return timeline;
2948
- }
2949
- function createAsyncMessageInput() {
2950
- const queue = [];
2951
- const resolvers = [];
2952
- let closed = false;
2953
- return {
2954
- push(item) {
2955
- if (closed) {
2956
- return;
2957
- }
2958
- const resolve = resolvers.shift();
2959
- if (resolve) {
2960
- resolve({ value: item, done: false });
2961
- return;
2962
- }
2963
- queue.push(item);
2964
- },
2965
- end() {
2966
- closed = true;
2967
- while (resolvers.length > 0) {
2968
- const resolve = resolvers.shift();
2969
- resolve?.({ value: undefined, done: true });
2970
- }
2971
- },
2972
- iterable: {
2973
- [Symbol.asyncIterator]() {
2974
- return {
2975
- next: () => {
2976
- if (queue.length > 0) {
2977
- const value = queue.shift();
2978
- if (value !== undefined) {
2979
- return Promise.resolve({ value, done: false });
2980
- }
2981
- }
2982
- if (closed) {
2983
- return Promise.resolve({ value: undefined, done: true });
2984
- }
2985
- return new Promise((resolve) => {
2986
- resolvers.push(resolve);
2987
- });
2988
- },
2989
- };
2990
- },
2991
- },
2992
- };
2993
- }
2994
- async function pathExists(target) {
2995
- try {
2996
- await fsPromises.access(target);
2997
- return true;
2998
- }
2999
- catch {
3000
- return false;
3001
- }
3002
- }
3003
- async function collectRecentClaudeSessions(root, limit) {
3004
- let projectDirs;
3005
- try {
3006
- projectDirs = await fsPromises.readdir(root);
3007
- }
3008
- catch {
3009
- return [];
3010
- }
3011
- const candidates = [];
3012
- for (const dirName of projectDirs) {
3013
- const projectPath = path.join(root, dirName);
3014
- let stats;
3015
- try {
3016
- stats = await fsPromises.stat(projectPath);
3017
- }
3018
- catch {
3019
- continue;
3020
- }
3021
- if (!stats.isDirectory()) {
3022
- continue;
3023
- }
3024
- let files;
3025
- try {
3026
- files = await fsPromises.readdir(projectPath);
3027
- }
3028
- catch {
3029
- continue;
3030
- }
3031
- for (const file of files) {
3032
- if (!file.endsWith(".jsonl")) {
3033
- continue;
3034
- }
3035
- const fullPath = path.join(projectPath, file);
3036
- try {
3037
- const fileStats = await fsPromises.stat(fullPath);
3038
- candidates.push({ path: fullPath, mtime: fileStats.mtime });
3039
- }
3040
- catch {
3041
- // ignore stat errors for individual files
3042
- }
3043
- }
3044
- }
3045
- return candidates.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).slice(0, limit);
3046
- }
3047
- async function parseClaudeSessionDescriptor(filePath, mtime) {
3048
- let content;
3049
- try {
3050
- content = await fsPromises.readFile(filePath, "utf8");
3051
- }
3052
- catch {
3053
- return null;
3054
- }
3055
- let sessionId = null;
3056
- let cwd = null;
3057
- let title = null;
3058
- const timeline = [];
3059
- for (const rawLine of content.split(/\r?\n/)) {
3060
- const line = rawLine.trim();
3061
- if (!line)
3062
- continue;
3063
- let entry;
3064
- try {
3065
- entry = JSON.parse(line);
3066
- }
3067
- catch {
3068
- continue;
3069
- }
3070
- if (entry?.isSidechain) {
3071
- continue;
3072
- }
3073
- if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
3074
- continue;
3075
- }
3076
- if (!sessionId && typeof entry.sessionId === "string") {
3077
- sessionId = entry.sessionId;
3078
- }
3079
- if (!cwd && typeof entry.cwd === "string") {
3080
- cwd = entry.cwd;
3081
- }
3082
- if (entry.type === "user" && entry.message) {
3083
- const text = extractClaudeUserText(entry.message);
3084
- if (text) {
3085
- if (!title) {
3086
- title = text;
3087
- }
3088
- timeline.push({ type: "user_message", text });
3089
- }
3090
- }
3091
- else if (entry.type === "assistant" && entry.message) {
3092
- const text = extractClaudeUserText(entry.message);
3093
- if (text) {
3094
- timeline.push({ type: "assistant_message", text });
3095
- }
3096
- }
3097
- if (sessionId && cwd && title) {
3098
- break;
3099
- }
3100
- }
3101
- if (!sessionId || !cwd) {
3102
- return null;
3103
- }
3104
- const persistence = {
3105
- provider: "claude",
3106
- sessionId,
3107
- nativeHandle: sessionId,
3108
- metadata: {
3109
- provider: "claude",
3110
- cwd,
3111
- },
3112
- };
3113
- return {
3114
- provider: "claude",
3115
- sessionId,
3116
- cwd,
3117
- title: (title ?? "").trim() || `Claude session ${sessionId.slice(0, 8)}`,
3118
- lastActivityAt: mtime,
3119
- persistence,
3120
- timeline,
3121
- };
3122
- }
3123
- function extractClaudeUserText(message) {
3124
- if (!message) {
3125
- return null;
3126
- }
3127
- if (typeof message.content === "string") {
3128
- const normalized = message.content.trim();
3129
- return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
3130
- }
3131
- if (typeof message.text === "string") {
3132
- const normalized = message.text.trim();
3133
- return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
3134
- }
3135
- if (Array.isArray(message.content)) {
3136
- for (const block of message.content) {
3137
- if (block && typeof block.text === "string") {
3138
- const normalized = block.text.trim();
3139
- if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
3140
- return normalized;
3141
- }
3142
- }
3143
- }
3144
- }
3145
- return null;
3146
- }
3147
- //# sourceMappingURL=claude-agent.js.map