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