@hyperdrive.bot/paseo-server 0.2.6

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 (804) hide show
  1. package/.env.example +31 -0
  2. package/README.md +111 -0
  3. package/dist/scripts/dev-runner.js +37 -0
  4. package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
  5. package/dist/scripts/supervisor-entrypoint.js +141 -0
  6. package/dist/scripts/supervisor-log-config.js +31 -0
  7. package/dist/scripts/supervisor.js +252 -0
  8. package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
  9. package/dist/server/executable-resolution/executable-resolution.js +118 -0
  10. package/dist/server/executable-resolution/windows.d.ts +18 -0
  11. package/dist/server/executable-resolution/windows.js +65 -0
  12. package/dist/server/extensions/daemon-backend.d.ts +11 -0
  13. package/dist/server/extensions/daemon-backend.js +146 -0
  14. package/dist/server/extensions/extension-host-manager.d.ts +48 -0
  15. package/dist/server/extensions/extension-host-manager.js +141 -0
  16. package/dist/server/extensions/extension-loader.d.ts +11 -0
  17. package/dist/server/extensions/extension-loader.js +47 -0
  18. package/dist/server/extensions/extension-service.d.ts +17 -0
  19. package/dist/server/extensions/extension-service.js +76 -0
  20. package/dist/server/server/agent/activity-curator.d.ts +28 -0
  21. package/dist/server/server/agent/activity-curator.js +227 -0
  22. package/dist/server/server/agent/agent-archive.d.ts +11 -0
  23. package/dist/server/server/agent/agent-archive.js +16 -0
  24. package/dist/server/server/agent/agent-loading.d.ts +12 -0
  25. package/dist/server/server/agent/agent-loading.js +55 -0
  26. package/dist/server/server/agent/agent-manager.d.ts +462 -0
  27. package/dist/server/server/agent/agent-manager.js +2955 -0
  28. package/dist/server/server/agent/agent-projections.d.ts +26 -0
  29. package/dist/server/server/agent/agent-projections.js +404 -0
  30. package/dist/server/server/agent/agent-prompt.d.ts +76 -0
  31. package/dist/server/server/agent/agent-prompt.js +230 -0
  32. package/dist/server/server/agent/agent-response-loop.d.ts +67 -0
  33. package/dist/server/server/agent/agent-response-loop.js +326 -0
  34. package/dist/server/server/agent/agent-routes.d.ts +18 -0
  35. package/dist/server/server/agent/agent-routes.js +107 -0
  36. package/dist/server/server/agent/agent-sdk-types.d.ts +677 -0
  37. package/dist/server/server/agent/agent-sdk-types.js +22 -0
  38. package/dist/server/server/agent/agent-storage.d.ts +152 -0
  39. package/dist/server/server/agent/agent-storage.js +342 -0
  40. package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
  41. package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
  42. package/dist/server/server/agent/agent-timeline-store-types.d.ts +50 -0
  43. package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
  44. package/dist/server/server/agent/agent-timeline-store.d.ts +27 -0
  45. package/dist/server/server/agent/agent-timeline-store.js +230 -0
  46. package/dist/server/server/agent/audio-utils.d.ts +3 -0
  47. package/dist/server/server/agent/audio-utils.js +19 -0
  48. package/dist/server/server/agent/create-agent/create.d.ts +83 -0
  49. package/dist/server/server/agent/create-agent/create.js +289 -0
  50. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +54 -0
  51. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +152 -0
  52. package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
  53. package/dist/server/server/agent/create-agent-mode.js +62 -0
  54. package/dist/server/server/agent/create-agent-title.d.ts +10 -0
  55. package/dist/server/server/agent/create-agent-title.js +34 -0
  56. package/dist/server/server/agent/dictation-debug.d.ts +13 -0
  57. package/dist/server/server/agent/dictation-debug.js +50 -0
  58. package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
  59. package/dist/server/server/agent/foreground-run-state.js +160 -0
  60. package/dist/server/server/agent/import-sessions.d.ts +47 -0
  61. package/dist/server/server/agent/import-sessions.js +145 -0
  62. package/dist/server/server/agent/lifecycle-command.d.ts +73 -0
  63. package/dist/server/server/agent/lifecycle-command.js +92 -0
  64. package/dist/server/server/agent/mcp-server.d.ts +22 -0
  65. package/dist/server/server/agent/mcp-server.js +544 -0
  66. package/dist/server/server/agent/mcp-shared.d.ts +193 -0
  67. package/dist/server/server/agent/mcp-shared.js +197 -0
  68. package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
  69. package/dist/server/server/agent/pcm16-resampler.js +63 -0
  70. package/dist/server/server/agent/permission-response.d.ts +15 -0
  71. package/dist/server/server/agent/permission-response.js +14 -0
  72. package/dist/server/server/agent/prompt-attachments.d.ts +7 -0
  73. package/dist/server/server/agent/prompt-attachments.js +90 -0
  74. package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
  75. package/dist/server/server/agent/provider-history-timestamps.js +16 -0
  76. package/dist/server/server/agent/provider-launch-config.d.ts +51 -0
  77. package/dist/server/server/agent/provider-launch-config.js +183 -0
  78. package/dist/server/server/agent/provider-notices.d.ts +3 -0
  79. package/dist/server/server/agent/provider-notices.js +5 -0
  80. package/dist/server/server/agent/provider-registry.d.ts +49 -0
  81. package/dist/server/server/agent/provider-registry.js +578 -0
  82. package/dist/server/server/agent/provider-session-import.d.ts +10 -0
  83. package/dist/server/server/agent/provider-session-import.js +49 -0
  84. package/dist/server/server/agent/provider-snapshot-manager.d.ts +137 -0
  85. package/dist/server/server/agent/provider-snapshot-manager.js +694 -0
  86. package/dist/server/server/agent/providers/acp-agent.d.ts +354 -0
  87. package/dist/server/server/agent/providers/acp-agent.js +2438 -0
  88. package/dist/server/server/agent/providers/claude/agent.d.ts +341 -0
  89. package/dist/server/server/agent/providers/claude/agent.js +4606 -0
  90. package/dist/server/server/agent/providers/claude/background-shell-kill-keys.d.ts +32 -0
  91. package/dist/server/server/agent/providers/claude/background-shell-kill-keys.js +34 -0
  92. package/dist/server/server/agent/providers/claude/background-task-tracker.d.ts +49 -0
  93. package/dist/server/server/agent/providers/claude/background-task-tracker.js +119 -0
  94. package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
  95. package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
  96. package/dist/server/server/agent/providers/claude/hook-handler.d.ts +37 -0
  97. package/dist/server/server/agent/providers/claude/hook-handler.js +193 -0
  98. package/dist/server/server/agent/providers/claude/hooks/event-translator.d.ts +49 -0
  99. package/dist/server/server/agent/providers/claude/hooks/event-translator.js +151 -0
  100. package/dist/server/server/agent/providers/claude/image-compression.d.ts +10 -0
  101. package/dist/server/server/agent/providers/claude/image-compression.js +32 -0
  102. package/dist/server/server/agent/providers/claude/model-manifest.d.ts +104 -0
  103. package/dist/server/server/agent/providers/claude/model-manifest.js +221 -0
  104. package/dist/server/server/agent/providers/claude/models.d.ts +11 -0
  105. package/dist/server/server/agent/providers/claude/models.js +97 -0
  106. package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
  107. package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
  108. package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
  109. package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
  110. package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +41 -0
  111. package/dist/server/server/agent/providers/claude/pty-session-launcher.js +131 -0
  112. package/dist/server/server/agent/providers/claude/resume-source.d.ts +11 -0
  113. package/dist/server/server/agent/providers/claude/resume-source.js +37 -0
  114. package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
  115. package/dist/server/server/agent/providers/claude/rewind.js +30 -0
  116. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +22 -0
  117. package/dist/server/server/agent/providers/claude/sidechain-tracker.js +232 -0
  118. package/dist/server/server/agent/providers/claude/spawn-config-writer.d.ts +70 -0
  119. package/dist/server/server/agent/providers/claude/spawn-config-writer.js +229 -0
  120. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +47 -0
  121. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +292 -0
  122. package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
  123. package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
  124. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
  125. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
  126. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
  127. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
  128. package/dist/server/server/agent/providers/claude/transport/claude-paths.d.ts +31 -0
  129. package/dist/server/server/agent/providers/claude/transport/claude-paths.js +63 -0
  130. package/dist/server/server/agent/providers/claude/transport/continue-flag.d.ts +31 -0
  131. package/dist/server/server/agent/providers/claude/transport/continue-flag.js +42 -0
  132. package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +62 -0
  133. package/dist/server/server/agent/providers/claude/transport/pty-query.js +265 -0
  134. package/dist/server/server/agent/providers/claude/transport/pty.d.ts +50 -0
  135. package/dist/server/server/agent/providers/claude/transport/pty.js +211 -0
  136. package/dist/server/server/agent/providers/claude/transport/redact.d.ts +2 -0
  137. package/dist/server/server/agent/providers/claude/transport/redact.js +5 -0
  138. package/dist/server/server/agent/providers/claude/transport/sdk.d.ts +63 -0
  139. package/dist/server/server/agent/providers/claude/transport/sdk.js +207 -0
  140. package/dist/server/server/agent/providers/claude/transport/transcript-sdk-reader.d.ts +27 -0
  141. package/dist/server/server/agent/providers/claude/transport/transcript-sdk-reader.js +162 -0
  142. package/dist/server/server/agent/providers/claude/transport/transcript-tailer.d.ts +28 -0
  143. package/dist/server/server/agent/providers/claude/transport/transcript-tailer.js +166 -0
  144. package/dist/server/server/agent/providers/claude/transport/truncate-session.d.ts +41 -0
  145. package/dist/server/server/agent/providers/claude/transport/truncate-session.js +86 -0
  146. package/dist/server/server/agent/providers/claude/transport/types.d.ts +47 -0
  147. package/dist/server/server/agent/providers/claude/transport/types.js +2 -0
  148. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +90 -0
  149. package/dist/server/server/agent/providers/codex/app-server-transport.js +262 -0
  150. package/dist/server/server/agent/providers/codex/rewind.d.ts +21 -0
  151. package/dist/server/server/agent/providers/codex/rewind.js +46 -0
  152. package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.d.ts +36 -0
  153. package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.js +217 -0
  154. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
  155. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  156. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
  157. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +830 -0
  158. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +308 -0
  159. package/dist/server/server/agent/providers/codex-app-server-agent.js +4514 -0
  160. package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
  161. package/dist/server/server/agent/providers/codex-feature-definitions.js +45 -0
  162. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
  163. package/dist/server/server/agent/providers/copilot-acp-agent.js +187 -0
  164. package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +15 -0
  165. package/dist/server/server/agent/providers/cursor-acp-agent.js +18 -0
  166. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
  167. package/dist/server/server/agent/providers/diagnostic-utils.js +355 -0
  168. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +42 -0
  169. package/dist/server/server/agent/providers/generic-acp-agent.js +161 -0
  170. package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
  171. package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
  172. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +85 -0
  173. package/dist/server/server/agent/providers/mock-load-test-agent.js +1010 -0
  174. package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
  175. package/dist/server/server/agent/providers/mock-slow-provider.js +39 -0
  176. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  177. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  178. package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
  179. package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
  180. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +91 -0
  181. package/dist/server/server/agent/providers/opencode/server-manager.js +425 -0
  182. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +25 -0
  183. package/dist/server/server/agent/providers/opencode/test-server-manager.js +39 -0
  184. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.d.ts +92 -0
  185. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.js +268 -0
  186. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
  187. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
  188. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
  189. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
  190. package/dist/server/server/agent/providers/opencode-agent.d.ts +286 -0
  191. package/dist/server/server/agent/providers/opencode-agent.js +2857 -0
  192. package/dist/server/server/agent/providers/pi/agent.d.ts +137 -0
  193. package/dist/server/server/agent/providers/pi/agent.js +1650 -0
  194. package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
  195. package/dist/server/server/agent/providers/pi/cli-runtime.js +233 -0
  196. package/dist/server/server/agent/providers/pi/history-mapper.d.ts +9 -0
  197. package/dist/server/server/agent/providers/pi/history-mapper.js +140 -0
  198. package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
  199. package/dist/server/server/agent/providers/pi/rewind.js +8 -0
  200. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +208 -0
  201. package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
  202. package/dist/server/server/agent/providers/pi/runtime.d.ts +57 -0
  203. package/dist/server/server/agent/providers/pi/runtime.js +56 -0
  204. package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
  205. package/dist/server/server/agent/providers/pi/session-descriptor.js +346 -0
  206. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +83 -0
  207. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +184 -0
  208. package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +112 -0
  209. package/dist/server/server/agent/providers/pi/tool-call-mapper.js +284 -0
  210. package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
  211. package/dist/server/server/agent/providers/provider-image-output.js +111 -0
  212. package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
  213. package/dist/server/server/agent/providers/provider-runner.js +81 -0
  214. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
  215. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +60 -0
  216. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
  217. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
  218. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
  219. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
  220. package/dist/server/server/agent/recordings-debug.d.ts +3 -0
  221. package/dist/server/server/agent/recordings-debug.js +19 -0
  222. package/dist/server/server/agent/render-file-attachment.d.ts +29 -0
  223. package/dist/server/server/agent/render-file-attachment.js +41 -0
  224. package/dist/server/server/agent/resolve-resume-cwd.d.ts +14 -0
  225. package/dist/server/server/agent/resolve-resume-cwd.js +49 -0
  226. package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
  227. package/dist/server/server/agent/rewind/rewind.js +29 -0
  228. package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
  229. package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
  230. package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
  231. package/dist/server/server/agent/runtime-mcp-config.js +53 -0
  232. package/dist/server/server/agent/session-digest-generator.d.ts +37 -0
  233. package/dist/server/server/agent/session-digest-generator.js +115 -0
  234. package/dist/server/server/agent/session-digest.d.ts +35 -0
  235. package/dist/server/server/agent/session-digest.js +104 -0
  236. package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
  237. package/dist/server/server/agent/structured-generation-providers.js +236 -0
  238. package/dist/server/server/agent/stt-debug.d.ts +10 -0
  239. package/dist/server/server/agent/stt-debug.js +33 -0
  240. package/dist/server/server/agent/stt-manager.d.ts +38 -0
  241. package/dist/server/server/agent/stt-manager.js +246 -0
  242. package/dist/server/server/agent/system-prompt.d.ts +2 -0
  243. package/dist/server/server/agent/system-prompt.js +8 -0
  244. package/dist/server/server/agent/timeline-append.d.ts +10 -0
  245. package/dist/server/server/agent/timeline-append.js +27 -0
  246. package/dist/server/server/agent/timeline-projection.d.ts +71 -0
  247. package/dist/server/server/agent/timeline-projection.js +373 -0
  248. package/dist/server/server/agent/tools/paseo-tools.d.ts +54 -0
  249. package/dist/server/server/agent/tools/paseo-tools.js +2197 -0
  250. package/dist/server/server/agent/tools/types.d.ts +36 -0
  251. package/dist/server/server/agent/tools/types.js +2 -0
  252. package/dist/server/server/agent/tts-debug.d.ts +8 -0
  253. package/dist/server/server/agent/tts-debug.js +24 -0
  254. package/dist/server/server/agent/tts-manager.d.ts +41 -0
  255. package/dist/server/server/agent/tts-manager.js +375 -0
  256. package/dist/server/server/agent/wait-for-agent-tracker.d.ts +15 -0
  257. package/dist/server/server/agent/wait-for-agent-tracker.js +53 -0
  258. package/dist/server/server/agent-attention-policy.d.ts +29 -0
  259. package/dist/server/server/agent-attention-policy.js +36 -0
  260. package/dist/server/server/atomic-file.d.ts +3 -0
  261. package/dist/server/server/atomic-file.js +19 -0
  262. package/dist/server/server/auth.d.ts +38 -0
  263. package/dist/server/server/auth.js +143 -0
  264. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
  265. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +92 -0
  266. package/dist/server/server/auto-archive-on-merge/index.d.ts +8 -0
  267. package/dist/server/server/auto-archive-on-merge/index.js +15 -0
  268. package/dist/server/server/bootstrap.d.ts +126 -0
  269. package/dist/server/server/bootstrap.js +1193 -0
  270. package/dist/server/server/browser-tools/broker.d.ts +45 -0
  271. package/dist/server/server/browser-tools/broker.js +390 -0
  272. package/dist/server/server/browser-tools/errors.d.ts +22 -0
  273. package/dist/server/server/browser-tools/errors.js +27 -0
  274. package/dist/server/server/browser-tools/policy.d.ts +10 -0
  275. package/dist/server/server/browser-tools/policy.js +16 -0
  276. package/dist/server/server/browser-tools/tools.d.ts +16 -0
  277. package/dist/server/server/browser-tools/tools.js +845 -0
  278. package/dist/server/server/chat/chat-mentions.d.ts +47 -0
  279. package/dist/server/server/chat/chat-mentions.js +113 -0
  280. package/dist/server/server/chat/chat-service.d.ts +78 -0
  281. package/dist/server/server/chat/chat-service.js +337 -0
  282. package/dist/server/server/checkout/status-projection.d.ts +19 -0
  283. package/dist/server/server/checkout/status-projection.js +102 -0
  284. package/dist/server/server/checkout-diff-manager.d.ts +41 -0
  285. package/dist/server/server/checkout-diff-manager.js +189 -0
  286. package/dist/server/server/checkout-git-utils.d.ts +11 -0
  287. package/dist/server/server/checkout-git-utils.js +34 -0
  288. package/dist/server/server/client-message-id.d.ts +3 -0
  289. package/dist/server/server/client-message-id.js +12 -0
  290. package/dist/server/server/config.d.ts +17 -0
  291. package/dist/server/server/config.js +320 -0
  292. package/dist/server/server/connection-offer.d.ts +20 -0
  293. package/dist/server/server/connection-offer.js +59 -0
  294. package/dist/server/server/daemon-config-store.d.ts +25 -0
  295. package/dist/server/server/daemon-config-store.js +194 -0
  296. package/dist/server/server/daemon-keypair.d.ts +8 -0
  297. package/dist/server/server/daemon-keypair.js +42 -0
  298. package/dist/server/server/daemon-version.d.ts +5 -0
  299. package/dist/server/server/daemon-version.js +22 -0
  300. package/dist/server/server/daemon-worker.d.ts +2 -0
  301. package/dist/server/server/daemon-worker.js +251 -0
  302. package/dist/server/server/dictation/dictation-stream-manager.d.ts +87 -0
  303. package/dist/server/server/dictation/dictation-stream-manager.js +572 -0
  304. package/dist/server/server/editor-targets.d.ts +18 -0
  305. package/dist/server/server/editor-targets.js +109 -0
  306. package/dist/server/server/exports.d.ts +24 -0
  307. package/dist/server/server/exports.js +24 -0
  308. package/dist/server/server/file-download/token-store.d.ts +24 -0
  309. package/dist/server/server/file-download/token-store.js +40 -0
  310. package/dist/server/server/file-explorer/file-download-frames.d.ts +27 -0
  311. package/dist/server/server/file-explorer/file-download-frames.js +47 -0
  312. package/dist/server/server/file-explorer/service.d.ts +75 -0
  313. package/dist/server/server/file-explorer/service.js +308 -0
  314. package/dist/server/server/file-upload/index.d.ts +27 -0
  315. package/dist/server/server/file-upload/index.js +158 -0
  316. package/dist/server/server/hostnames.d.ts +13 -0
  317. package/dist/server/server/hostnames.js +94 -0
  318. package/dist/server/server/json-utils.d.ts +11 -0
  319. package/dist/server/server/json-utils.js +45 -0
  320. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  321. package/dist/server/server/lifecycle-reasons.js +6 -0
  322. package/dist/server/server/logger.d.ts +29 -0
  323. package/dist/server/server/logger.js +125 -0
  324. package/dist/server/server/loop-service.d.ts +234 -0
  325. package/dist/server/server/loop-service.js +769 -0
  326. package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
  327. package/dist/server/server/managed-processes/managed-processes.js +326 -0
  328. package/dist/server/server/meetings/capture-http.d.ts +3 -0
  329. package/dist/server/server/meetings/capture-http.js +71 -0
  330. package/dist/server/server/meetings/capture-orchestrator.d.ts +123 -0
  331. package/dist/server/server/meetings/capture-orchestrator.js +531 -0
  332. package/dist/server/server/meetings/capture-stream-ws.d.ts +42 -0
  333. package/dist/server/server/meetings/capture-stream-ws.js +68 -0
  334. package/dist/server/server/meetings/live-transcription.d.ts +57 -0
  335. package/dist/server/server/meetings/live-transcription.js +280 -0
  336. package/dist/server/server/meetings/meetings-http.d.ts +42 -0
  337. package/dist/server/server/meetings/meetings-http.js +266 -0
  338. package/dist/server/server/messages.d.ts +9 -0
  339. package/dist/server/server/messages.js +29 -0
  340. package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
  341. package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
  342. package/dist/server/server/monitoring/telemetry.d.ts +13 -0
  343. package/dist/server/server/monitoring/telemetry.js +21 -0
  344. package/dist/server/server/package-version.d.ts +19 -0
  345. package/dist/server/server/package-version.js +60 -0
  346. package/dist/server/server/pagination/cursor.d.ts +16 -0
  347. package/dist/server/server/pagination/cursor.js +62 -0
  348. package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
  349. package/dist/server/server/pagination/sortable-pager.js +68 -0
  350. package/dist/server/server/pairing-offer.d.ts +18 -0
  351. package/dist/server/server/pairing-offer.js +47 -0
  352. package/dist/server/server/pairing-qr.d.ts +7 -0
  353. package/dist/server/server/pairing-qr.js +45 -0
  354. package/dist/server/server/paseo-env.d.ts +13 -0
  355. package/dist/server/server/paseo-env.js +53 -0
  356. package/dist/server/server/paseo-home.d.ts +2 -0
  357. package/dist/server/server/paseo-home.js +19 -0
  358. package/dist/server/server/paseo-worktree-service.d.ts +52 -0
  359. package/dist/server/server/paseo-worktree-service.js +294 -0
  360. package/dist/server/server/path-utils.d.ts +5 -0
  361. package/dist/server/server/path-utils.js +34 -0
  362. package/dist/server/server/persisted-config.d.ts +333 -0
  363. package/dist/server/server/persisted-config.js +402 -0
  364. package/dist/server/server/persistence-hooks.d.ts +32 -0
  365. package/dist/server/server/persistence-hooks.js +97 -0
  366. package/dist/server/server/pid-lock.d.ts +32 -0
  367. package/dist/server/server/pid-lock.js +170 -0
  368. package/dist/server/server/private-files.d.ts +6 -0
  369. package/dist/server/server/private-files.js +37 -0
  370. package/dist/server/server/process-diagnostics.d.ts +17 -0
  371. package/dist/server/server/process-diagnostics.js +22 -0
  372. package/dist/server/server/push/categories.d.ts +35 -0
  373. package/dist/server/server/push/categories.js +40 -0
  374. package/dist/server/server/push/expo-worker.d.ts +51 -0
  375. package/dist/server/server/push/expo-worker.js +149 -0
  376. package/dist/server/server/push/index.d.ts +39 -0
  377. package/dist/server/server/push/index.js +45 -0
  378. package/dist/server/server/push/notifications.d.ts +9 -0
  379. package/dist/server/server/push/notifications.js +15 -0
  380. package/dist/server/server/push/push-service.d.ts +20 -0
  381. package/dist/server/server/push/push-service.js +66 -0
  382. package/dist/server/server/push/routes.d.ts +28 -0
  383. package/dist/server/server/push/routes.js +74 -0
  384. package/dist/server/server/push/subscriptions.d.ts +42 -0
  385. package/dist/server/server/push/subscriptions.js +112 -0
  386. package/dist/server/server/push/token-store.d.ts +29 -0
  387. package/dist/server/server/push/token-store.js +78 -0
  388. package/dist/server/server/push/triggers.d.ts +68 -0
  389. package/dist/server/server/push/triggers.js +103 -0
  390. package/dist/server/server/push/vapid-keys.d.ts +15 -0
  391. package/dist/server/server/push/vapid-keys.js +52 -0
  392. package/dist/server/server/push/web-worker.d.ts +44 -0
  393. package/dist/server/server/push/web-worker.js +99 -0
  394. package/dist/server/server/relay-transport.d.ts +31 -0
  395. package/dist/server/server/relay-transport.js +514 -0
  396. package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
  397. package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
  398. package/dist/server/server/resolve-worktree-creation-intent.d.ts +32 -0
  399. package/dist/server/server/resolve-worktree-creation-intent.js +121 -0
  400. package/dist/server/server/schedule/cron.d.ts +4 -0
  401. package/dist/server/server/schedule/cron.js +81 -0
  402. package/dist/server/server/schedule/service.d.ts +52 -0
  403. package/dist/server/server/schedule/service.js +628 -0
  404. package/dist/server/server/schedule/store.d.ts +13 -0
  405. package/dist/server/server/schedule/store.js +57 -0
  406. package/dist/server/server/script-health-monitor.d.ts +39 -0
  407. package/dist/server/server/script-health-monitor.js +159 -0
  408. package/dist/server/server/script-proxy.d.ts +3 -0
  409. package/dist/server/server/script-proxy.js +2 -0
  410. package/dist/server/server/script-route-branch-handler.d.ts +10 -0
  411. package/dist/server/server/script-route-branch-handler.js +11 -0
  412. package/dist/server/server/script-status-projection.d.ts +36 -0
  413. package/dist/server/server/script-status-projection.js +200 -0
  414. package/dist/server/server/server-id.d.ts +17 -0
  415. package/dist/server/server/server-id.js +68 -0
  416. package/dist/server/server/service-proxy.d.ts +237 -0
  417. package/dist/server/server/service-proxy.js +714 -0
  418. package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
  419. package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
  420. package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
  421. package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
  422. package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
  423. package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
  424. package/dist/server/server/session/checkout/checkout-session.d.ts +140 -0
  425. package/dist/server/server/session/checkout/checkout-session.js +927 -0
  426. package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
  427. package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
  428. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  429. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  430. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  431. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  432. package/dist/server/server/session/daemon/daemon-session.d.ts +76 -0
  433. package/dist/server/server/session/daemon/daemon-session.js +148 -0
  434. package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
  435. package/dist/server/server/session/daemon/diagnostics.js +431 -0
  436. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  437. package/dist/server/server/session/daemon/install-origin.js +64 -0
  438. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  439. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  440. package/dist/server/server/session/files/workspace-files-session.d.ts +43 -0
  441. package/dist/server/server/session/files/workspace-files-session.js +218 -0
  442. package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
  443. package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
  444. package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
  445. package/dist/server/server/session/project-config/project-config-session.js +125 -0
  446. package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
  447. package/dist/server/server/session/provider/provider-catalog-session.js +343 -0
  448. package/dist/server/server/session/session-rename-route.d.ts +4 -0
  449. package/dist/server/server/session/session-rename-route.js +71 -0
  450. package/dist/server/server/session/session-rename-service.d.ts +25 -0
  451. package/dist/server/server/session/session-rename-service.js +76 -0
  452. package/dist/server/server/session/voice/voice-session.d.ts +166 -0
  453. package/dist/server/server/session/voice/voice-session.js +893 -0
  454. package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
  455. package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
  456. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
  457. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
  458. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
  459. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
  460. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
  461. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
  462. package/dist/server/server/session.d.ts +562 -0
  463. package/dist/server/server/session.js +5787 -0
  464. package/dist/server/server/speech/audio.d.ts +10 -0
  465. package/dist/server/server/speech/audio.js +101 -0
  466. package/dist/server/server/speech/provider-resolver.d.ts +3 -0
  467. package/dist/server/server/speech/provider-resolver.js +7 -0
  468. package/dist/server/server/speech/providers/local/config.d.ts +30 -0
  469. package/dist/server/server/speech/providers/local/config.js +134 -0
  470. package/dist/server/server/speech/providers/local/models.d.ts +11 -0
  471. package/dist/server/server/speech/providers/local/models.js +17 -0
  472. package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
  473. package/dist/server/server/speech/providers/local/runtime.js +135 -0
  474. package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
  475. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +56 -0
  476. package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +74 -0
  477. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +15 -0
  478. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +142 -0
  479. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
  480. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
  481. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
  482. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
  483. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
  484. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
  485. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
  486. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
  487. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
  488. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
  489. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +21 -0
  490. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +101 -0
  491. package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
  492. package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
  493. package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
  494. package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
  495. package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
  496. package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
  497. package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
  498. package/dist/server/server/speech/providers/local/worker-client.js +611 -0
  499. package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
  500. package/dist/server/server/speech/providers/local/worker-process.js +270 -0
  501. package/dist/server/server/speech/providers/local/worker-protocol.d.ts +95 -0
  502. package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
  503. package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
  504. package/dist/server/server/speech/providers/openai/config.js +148 -0
  505. package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
  506. package/dist/server/server/speech/providers/openai/runtime.js +102 -0
  507. package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
  508. package/dist/server/server/speech/providers/openai/stt.js +209 -0
  509. package/dist/server/server/speech/providers/openai/tts.d.ts +19 -0
  510. package/dist/server/server/speech/providers/openai/tts.js +48 -0
  511. package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
  512. package/dist/server/server/speech/speech-config-resolver.js +132 -0
  513. package/dist/server/server/speech/speech-provider.d.ts +59 -0
  514. package/dist/server/server/speech/speech-provider.js +2 -0
  515. package/dist/server/server/speech/speech-runtime.d.ts +45 -0
  516. package/dist/server/server/speech/speech-runtime.js +562 -0
  517. package/dist/server/server/speech/speech-types.d.ts +22 -0
  518. package/dist/server/server/speech/speech-types.js +8 -0
  519. package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
  520. package/dist/server/server/speech/turn-detection-provider.js +2 -0
  521. package/dist/server/server/uploads/file-attachment-validation.d.ts +61 -0
  522. package/dist/server/server/uploads/file-attachment-validation.js +56 -0
  523. package/dist/server/server/uploads/staging-service.d.ts +91 -0
  524. package/dist/server/server/uploads/staging-service.js +144 -0
  525. package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
  526. package/dist/server/server/utils/diff-highlighter.js +279 -0
  527. package/dist/server/server/voice/voice-turn-controller.d.ts +38 -0
  528. package/dist/server/server/voice/voice-turn-controller.js +393 -0
  529. package/dist/server/server/voice-config.d.ts +15 -0
  530. package/dist/server/server/voice-config.js +54 -0
  531. package/dist/server/server/voice-permission-policy.d.ts +4 -0
  532. package/dist/server/server/voice-permission-policy.js +13 -0
  533. package/dist/server/server/voice-types.d.ts +12 -0
  534. package/dist/server/server/voice-types.js +2 -0
  535. package/dist/server/server/watch/composer-dictate.d.ts +42 -0
  536. package/dist/server/server/watch/composer-dictate.js +37 -0
  537. package/dist/server/server/watch/notification-action.d.ts +38 -0
  538. package/dist/server/server/watch/notification-action.js +54 -0
  539. package/dist/server/server/watch/notification-registry.d.ts +16 -0
  540. package/dist/server/server/watch/notification-registry.js +2 -0
  541. package/dist/server/server/web-ui.d.ts +10 -0
  542. package/dist/server/server/web-ui.js +205 -0
  543. package/dist/server/server/websocket/runtime-metrics.d.ts +94 -0
  544. package/dist/server/server/websocket/runtime-metrics.js +148 -0
  545. package/dist/server/server/websocket-server.d.ts +193 -0
  546. package/dist/server/server/websocket-server.js +1768 -0
  547. package/dist/server/server/workflow/workflow-feature.d.ts +2 -0
  548. package/dist/server/server/workflow/workflow-feature.js +8 -0
  549. package/dist/server/server/workflow/workflow-http-routes.d.ts +27 -0
  550. package/dist/server/server/workflow/workflow-http-routes.js +97 -0
  551. package/dist/server/server/workflow/workflow-manager.d.ts +228 -0
  552. package/dist/server/server/workflow/workflow-manager.js +444 -0
  553. package/dist/server/server/workflow/workflow-progress.d.ts +95 -0
  554. package/dist/server/server/workflow/workflow-progress.js +178 -0
  555. package/dist/server/server/workflow/workflow-storage.d.ts +36 -0
  556. package/dist/server/server/workflow/workflow-storage.js +143 -0
  557. package/dist/server/server/workspace/all-sessions-route.d.ts +32 -0
  558. package/dist/server/server/workspace/all-sessions-route.js +138 -0
  559. package/dist/server/server/workspace/session-openable.d.ts +16 -0
  560. package/dist/server/server/workspace/session-openable.js +48 -0
  561. package/dist/server/server/workspace/sessions-route.d.ts +15 -0
  562. package/dist/server/server/workspace/sessions-route.js +108 -0
  563. package/dist/server/server/workspace/sessions-search-route.d.ts +68 -0
  564. package/dist/server/server/workspace/sessions-search-route.js +203 -0
  565. package/dist/server/server/workspace-archive-service.d.ts +65 -0
  566. package/dist/server/server/workspace-archive-service.js +229 -0
  567. package/dist/server/server/workspace-auto-name.d.ts +55 -0
  568. package/dist/server/server/workspace-auto-name.js +128 -0
  569. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  570. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  571. package/dist/server/server/workspace-directory.d.ts +93 -0
  572. package/dist/server/server/workspace-directory.js +422 -0
  573. package/dist/server/server/workspace-git-metadata.d.ts +24 -0
  574. package/dist/server/server/workspace-git-metadata.js +55 -0
  575. package/dist/server/server/workspace-git-service.d.ts +257 -0
  576. package/dist/server/server/workspace-git-service.js +1386 -0
  577. package/dist/server/server/workspace-reconciliation-service.d.ts +57 -0
  578. package/dist/server/server/workspace-reconciliation-service.js +278 -0
  579. package/dist/server/server/workspace-registry-bootstrap.d.ts +13 -0
  580. package/dist/server/server/workspace-registry-bootstrap.js +121 -0
  581. package/dist/server/server/workspace-registry-model.d.ts +55 -0
  582. package/dist/server/server/workspace-registry-model.js +190 -0
  583. package/dist/server/server/workspace-registry.d.ts +117 -0
  584. package/dist/server/server/workspace-registry.js +194 -0
  585. package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
  586. package/dist/server/server/workspace-script-runtime-store.js +78 -0
  587. package/dist/server/server/workspace-service-env.d.ts +18 -0
  588. package/dist/server/server/workspace-service-env.js +85 -0
  589. package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
  590. package/dist/server/server/workspace-service-port-registry.js +59 -0
  591. package/dist/server/server/worktree/commands.d.ts +55 -0
  592. package/dist/server/server/worktree/commands.js +107 -0
  593. package/dist/server/server/worktree-bootstrap.d.ts +76 -0
  594. package/dist/server/server/worktree-bootstrap.js +720 -0
  595. package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
  596. package/dist/server/server/worktree-branch-name-generator.js +91 -0
  597. package/dist/server/server/worktree-core.d.ts +31 -0
  598. package/dist/server/server/worktree-core.js +105 -0
  599. package/dist/server/server/worktree-errors.d.ts +12 -0
  600. package/dist/server/server/worktree-errors.js +28 -0
  601. package/dist/server/server/worktree-session.d.ts +152 -0
  602. package/dist/server/server/worktree-session.js +466 -0
  603. package/dist/server/services/github-service.d.ts +337 -0
  604. package/dist/server/services/github-service.js +2101 -0
  605. package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
  606. package/dist/server/services/quota-fetcher/manifest.js +52 -0
  607. package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
  608. package/dist/server/services/quota-fetcher/provider.js +2 -0
  609. package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
  610. package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
  611. package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
  612. package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
  613. package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
  614. package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
  615. package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
  616. package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
  617. package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
  618. package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
  619. package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
  620. package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
  621. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  622. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  623. package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
  624. package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
  625. package/dist/server/services/quota-fetcher/service.d.ts +28 -0
  626. package/dist/server/services/quota-fetcher/service.js +58 -0
  627. package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
  628. package/dist/server/services/quota-fetcher/usage.js +49 -0
  629. package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
  630. package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
  631. package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
  632. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
  633. package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
  634. package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
  635. package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
  636. package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
  637. package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
  638. package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
  639. package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
  640. package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
  641. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
  642. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
  643. package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
  644. package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
  645. package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
  646. package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
  647. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
  648. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
  649. package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
  650. package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
  651. package/dist/server/terminal/terminal-capture.d.ts +12 -0
  652. package/dist/server/terminal/terminal-capture.js +43 -0
  653. package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
  654. package/dist/server/terminal/terminal-manager-factory.js +5 -0
  655. package/dist/server/terminal/terminal-manager.d.ts +78 -0
  656. package/dist/server/terminal/terminal-manager.js +319 -0
  657. package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
  658. package/dist/server/terminal/terminal-output-coalescer.js +73 -0
  659. package/dist/server/terminal/terminal-restore.d.ts +18 -0
  660. package/dist/server/terminal/terminal-restore.js +52 -0
  661. package/dist/server/terminal/terminal-session-controller.d.ts +85 -0
  662. package/dist/server/terminal/terminal-session-controller.js +819 -0
  663. package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
  664. package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
  665. package/dist/server/terminal/terminal-worker-process.js +304 -0
  666. package/dist/server/terminal/terminal-worker-protocol.d.ts +119 -0
  667. package/dist/server/terminal/terminal-worker-protocol.js +2 -0
  668. package/dist/server/terminal/terminal.d.ts +151 -0
  669. package/dist/server/terminal/terminal.js +1161 -0
  670. package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
  671. package/dist/server/terminal/worker-terminal-manager.js +688 -0
  672. package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
  673. package/dist/server/utils/build-metadata-prompt.js +29 -0
  674. package/dist/server/utils/checkout-git.d.ts +253 -0
  675. package/dist/server/utils/checkout-git.js +2238 -0
  676. package/dist/server/utils/directory-suggestions.d.ts +26 -0
  677. package/dist/server/utils/directory-suggestions.js +830 -0
  678. package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
  679. package/dist/server/utils/git-rev-parse-path.js +21 -0
  680. package/dist/server/utils/github-remote.d.ts +9 -0
  681. package/dist/server/utils/github-remote.js +62 -0
  682. package/dist/server/utils/paseo-config-file.d.ts +30 -0
  683. package/dist/server/utils/paseo-config-file.js +90 -0
  684. package/dist/server/utils/path.d.ts +18 -0
  685. package/dist/server/utils/path.js +123 -0
  686. package/dist/server/utils/project-icon.d.ts +39 -0
  687. package/dist/server/utils/project-icon.js +364 -0
  688. package/dist/server/utils/promise-timeout.d.ts +9 -0
  689. package/dist/server/utils/promise-timeout.js +25 -0
  690. package/dist/server/utils/run-git-command.d.ts +37 -0
  691. package/dist/server/utils/run-git-command.js +232 -0
  692. package/dist/server/utils/spawn.d.ts +33 -0
  693. package/dist/server/utils/spawn.js +71 -0
  694. package/dist/server/utils/string-command-shell.d.ts +10 -0
  695. package/dist/server/utils/string-command-shell.js +21 -0
  696. package/dist/server/utils/tree-kill.d.ts +18 -0
  697. package/dist/server/utils/tree-kill.js +77 -0
  698. package/dist/server/utils/windows-command.d.ts +15 -0
  699. package/dist/server/utils/windows-command.js +41 -0
  700. package/dist/server/utils/worktree-metadata.d.ts +39 -0
  701. package/dist/server/utils/worktree-metadata.js +174 -0
  702. package/dist/server/utils/worktree.d.ts +205 -0
  703. package/dist/server/utils/worktree.js +1119 -0
  704. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  705. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  706. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  707. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js +5 -0
  708. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.br +0 -0
  709. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.gz +0 -0
  710. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.map.br +0 -0
  711. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.map.gz +0 -0
  712. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js +3 -0
  713. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.br +0 -0
  714. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.gz +0 -0
  715. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.map.br +0 -0
  716. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.map.gz +0 -0
  717. package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js +16533 -0
  718. package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.br +0 -0
  719. package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.gz +0 -0
  720. package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.map.br +0 -0
  721. package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.map.gz +0 -0
  722. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js +3 -0
  723. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.br +0 -0
  724. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.gz +0 -0
  725. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.map.br +0 -0
  726. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.map.gz +0 -0
  727. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js +5 -0
  728. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.br +0 -0
  729. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.gz +0 -0
  730. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.map.br +0 -0
  731. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.map.gz +0 -0
  732. package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js +11 -0
  733. package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.br +0 -0
  734. package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.gz +0 -0
  735. package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.map.br +0 -0
  736. package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.map.gz +0 -0
  737. package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js +60 -0
  738. package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.br +0 -0
  739. package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.gz +0 -0
  740. package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.map.br +0 -0
  741. package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.map.gz +0 -0
  742. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  743. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  744. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  745. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  746. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  747. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  748. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  749. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  750. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  751. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  752. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  753. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  754. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  755. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  756. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  757. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  758. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  759. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  760. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  761. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  762. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  763. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  764. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  765. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  766. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  767. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  768. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  769. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  770. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  771. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  772. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  773. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  774. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  775. package/dist/server/web-ui/favicon.ico +0 -0
  776. package/dist/server/web-ui/index.html +90 -0
  777. package/dist/server/web-ui/index.html.br +0 -0
  778. package/dist/server/web-ui/index.html.gz +0 -0
  779. package/dist/server/web-ui/manifest.json +27 -0
  780. package/dist/server/web-ui/manifest.json.br +0 -0
  781. package/dist/server/web-ui/manifest.json.gz +0 -0
  782. package/dist/server/web-ui/metadata.json +1 -0
  783. package/dist/server/web-ui/metadata.json.br +1 -0
  784. package/dist/server/web-ui/metadata.json.gz +0 -0
  785. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  786. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  787. package/dist/server/web-ui/robots.txt +2 -0
  788. package/dist/server/web-ui/service-worker.js +87 -0
  789. package/dist/server/web-ui/service-worker.js.br +0 -0
  790. package/dist/server/web-ui/service-worker.js.gz +0 -0
  791. package/dist/src/executable-resolution/executable-resolution.js +118 -0
  792. package/dist/src/executable-resolution/windows.js +65 -0
  793. package/dist/src/server/agent/provider-launch-config.js +183 -0
  794. package/dist/src/server/paseo-env.js +53 -0
  795. package/dist/src/server/paseo-home.js +19 -0
  796. package/dist/src/server/persisted-config.js +402 -0
  797. package/dist/src/server/pid-lock.js +170 -0
  798. package/dist/src/server/private-files.js +37 -0
  799. package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
  800. package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
  801. package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
  802. package/dist/src/utils/spawn.js +71 -0
  803. package/dist/src/utils/windows-command.js +41 -0
  804. package/package.json +143 -0
@@ -0,0 +1,4606 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import { promises } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./tool-call-mapper.js";
7
+ import { mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./task-notification-tool-call.js";
8
+ import { findClaudeModel, getClaudeModelsWithSettings, normalizeClaudeRuntimeModelId, } from "./models.js";
9
+ import { CLAUDE_ULTRACODE_THINKING_OPTION_ID } from "./model-manifest.js";
10
+ import { parsePartialJsonObject } from "./partial-json.js";
11
+ import { resolveTranscriptPath, findTranscriptAcrossProjects } from "./transport/claude-paths.js";
12
+ import { ClaudeSidechainTracker } from "./sidechain-tracker.js";
13
+ import { ClaudeBackgroundTaskTracker } from "./background-task-tracker.js";
14
+ import { buildBackgroundKillPresses } from "./background-shell-kill-keys.js";
15
+ import { extractTaskNotificationLifecycleFromSystemRecord, extractTaskNotificationLifecycleFromUserContent, } from "./task-notification-tool-call.js";
16
+ import { buildClaudeFeatures, claudeModelSupportsFastMode } from "./feature-definitions.js";
17
+ import { buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, formatProviderDiagnostic, formatProviderDiagnosticError, } from "../diagnostic-utils.js";
18
+ import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "../provider-runner.js";
19
+ import { renderPromptAttachmentAsText } from "../../prompt-attachments.js";
20
+ import { compressImageForApi } from "./image-compression.js";
21
+ import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from "./rewind.js";
22
+ import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
23
+ import { composeSystemPromptParts } from "../../system-prompt.js";
24
+ import { SdkTransport } from "./transport/sdk.js";
25
+ import { PtyTransport } from "./transport/pty.js";
26
+ import { createPtySession } from "./pty-session-launcher.js";
27
+ import { trackTransportSpawned } from "../../../monitoring/telemetry.js";
28
+ import { isProviderImageMarkdown, materializeProviderImage, renderProviderImageOutputAsAssistantMarkdown, } from "../provider-image-output.js";
29
+ import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
30
+ import { importSessionFromPersistence } from "../../provider-session-import.js";
31
+ import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
32
+ import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
33
+ import { isNodePtyAvailable, NODE_PTY_UNAVAILABLE_REASON, resolveClaudeTransport, } from "../../provider-registry.js";
34
+ import { isCommandAvailable } from "../../../../executable-resolution/executable-resolution.js";
35
+ import { withTimeout } from "../../../../utils/promise-timeout.js";
36
+ import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
37
+ import { execCommand } from "../../../../utils/spawn.js";
38
+ const fsPromises = promises;
39
+ const CLAUDE_SETTING_SOURCES = [
40
+ "user",
41
+ "project",
42
+ "local",
43
+ ];
44
+ function readNonEmptyString(value) {
45
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
46
+ }
47
+ export function normalizeClaudeAskUserQuestionRequestInput(toolName, input) {
48
+ if (toolName !== "AskUserQuestion" || !Array.isArray(input.questions)) {
49
+ return input;
50
+ }
51
+ // Claude Code's AskUserQuestion schema says "Other" is host-provided, not a
52
+ // model-supplied option. Paseo's shared question UI uses allowOther for that
53
+ // freeform answer path.
54
+ return {
55
+ ...input,
56
+ questions: input.questions.map((item) => {
57
+ if (!isMetadata(item)) {
58
+ return item;
59
+ }
60
+ return {
61
+ ...item,
62
+ allowOther: true,
63
+ };
64
+ }),
65
+ };
66
+ }
67
+ function stripClaudeAskUserQuestionUiMetadata(input) {
68
+ if (!Array.isArray(input.questions)) {
69
+ return input;
70
+ }
71
+ return {
72
+ ...input,
73
+ questions: input.questions.map((item) => {
74
+ if (!isMetadata(item) || !("allowOther" in item)) {
75
+ return item;
76
+ }
77
+ const itemForClaude = { ...item };
78
+ delete itemForClaude.allowOther;
79
+ return itemForClaude;
80
+ }),
81
+ };
82
+ }
83
+ export function normalizeClaudeAskUserQuestionUpdatedInput(updatedInput, fallbackInput) {
84
+ const fallback = isMetadata(fallbackInput) ? fallbackInput : {};
85
+ const base = isMetadata(updatedInput) ? updatedInput : {};
86
+ // Paseo's shared question UI serializes answers by question header, but Claude's
87
+ // AskUserQuestion tool expects answer keys to match the full question text. Merge
88
+ // the original request payload back in so provider callbacks that only return
89
+ // `{ answers }` still satisfy Claude's full tool input schema.
90
+ const merged = stripClaudeAskUserQuestionUiMetadata({ ...fallback, ...base });
91
+ const questions = (Array.isArray(base.questions) ? base.questions : null) ??
92
+ (Array.isArray(fallback.questions) ? fallback.questions : null);
93
+ const answers = isMetadata(base.answers) ? base.answers : null;
94
+ if (!questions || !answers) {
95
+ return merged;
96
+ }
97
+ const normalizedAnswers = {};
98
+ for (const item of questions) {
99
+ const question = isMetadata(item) ? item : null;
100
+ if (!question) {
101
+ continue;
102
+ }
103
+ const questionText = readNonEmptyString(question.question);
104
+ if (!questionText) {
105
+ continue;
106
+ }
107
+ const header = readNonEmptyString(question.header);
108
+ const answer = readNonEmptyString(answers[questionText]) ??
109
+ (header ? readNonEmptyString(answers[header]) : null);
110
+ if (answer) {
111
+ normalizedAnswers[questionText] = answer;
112
+ }
113
+ }
114
+ if (Object.keys(normalizedAnswers).length === 0) {
115
+ return merged;
116
+ }
117
+ return {
118
+ ...merged,
119
+ answers: normalizedAnswers,
120
+ };
121
+ }
122
+ function isObjectRecord(value) {
123
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
124
+ }
125
+ function toObjectRecord(value) {
126
+ return isObjectRecord(value) ? value : undefined;
127
+ }
128
+ function isUnknownArray(value) {
129
+ return Array.isArray(value);
130
+ }
131
+ function isImageMimeType(value) {
132
+ return (value === "image/jpeg" ||
133
+ value === "image/png" ||
134
+ value === "image/gif" ||
135
+ value === "image/webp");
136
+ }
137
+ // Anthropic enforces a 32MB cap on a single API request body. A long session that reads
138
+ // many images (e.g. iterating video frames) accumulates base64 image blocks in the SDK's
139
+ // conversation history and eventually breaches the cap mid-turn with a cryptic upstream
140
+ // error. We compress on input and track cumulative bytes so we either stay well under
141
+ // the cap or fail fast with a clean message before the SDK does.
142
+ const CUMULATIVE_IMAGE_WARN_BYTES = 25 * 1024 * 1024;
143
+ const CUMULATIVE_IMAGE_LIMIT_BYTES = 30 * 1024 * 1024;
144
+ // Stub: rewind plumbing is dormant after the PTY migration. The SDK transport
145
+ // used to expose a `--version` resolver here; the PTY transport reads version
146
+ // via `claude --version` at spawn time and caches it on the session. Returning
147
+ // undefined makes the version telemetry log "unknown" until the PTY-flavored
148
+ // rewind/version resolver is wired up. Tracked in docs/tech-debt.md.
149
+ async function resolveClaudeVersion(_runtimeSettings) {
150
+ return undefined;
151
+ }
152
+ const CLAUDE_CAPABILITIES = {
153
+ supportsStreaming: true,
154
+ supportsSessionPersistence: true,
155
+ supportsSessionListing: true,
156
+ supportsDynamicModes: true,
157
+ supportsMcpServers: true,
158
+ supportsReasoningStream: true,
159
+ supportsToolInvocations: true,
160
+ supportsRewindConversation: true,
161
+ supportsRewindFiles: true,
162
+ supportsRewindBoth: true,
163
+ };
164
+ const DEFAULT_MODES = [
165
+ {
166
+ id: "default",
167
+ label: "Always Ask",
168
+ description: "Prompts for permission the first time a tool is used",
169
+ },
170
+ {
171
+ id: "auto",
172
+ label: "Auto mode",
173
+ description: "Uses a model classifier to review permission prompts automatically",
174
+ },
175
+ {
176
+ id: "acceptEdits",
177
+ label: "Accept File Edits",
178
+ description: "Automatically approves edit-focused tools without prompting",
179
+ },
180
+ {
181
+ id: "plan",
182
+ label: "Plan Mode",
183
+ description: "Analyze the codebase without executing tools or edits",
184
+ },
185
+ {
186
+ id: "bypassPermissions",
187
+ label: "Bypass",
188
+ description: "Skip all permission prompts (use with caution)",
189
+ },
190
+ ];
191
+ const VALID_CLAUDE_MODES = new Set(DEFAULT_MODES.map((mode) => mode.id));
192
+ const REWIND_COMMAND_NAME = "rewind";
193
+ const REWIND_COMMAND = {
194
+ name: REWIND_COMMAND_NAME,
195
+ description: "Rewind tracked files to a previous user message",
196
+ argumentHint: "[user_message_uuid]",
197
+ };
198
+ const CLAUDE_ROOT_ONLY_COMMANDS = new Set([
199
+ "clear",
200
+ "compact",
201
+ "context",
202
+ "debug",
203
+ "extra-usage",
204
+ "heapdump",
205
+ "init",
206
+ "loop",
207
+ "schedule",
208
+ "usage",
209
+ ]);
210
+ const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
211
+ const INTERRUPT_PLACEHOLDER_PATTERN = /^\[Request interrupted by user(?:[^\]]*)\]$/;
212
+ const NO_RESPONSE_REQUESTED_PLACEHOLDER = "No response requested.";
213
+ 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;
214
+ function classifyClaudeSlashCommand(commandName) {
215
+ // Claude exposes commands and skills as one flat SDK list, without structured source
216
+ // metadata. Keep obvious root-only/session controls out of inline autocomplete and
217
+ // treat the rest as skills; the worst failure mode is an inert inline suggestion.
218
+ return CLAUDE_ROOT_ONLY_COMMANDS.has(commandName) ? "command" : "skill";
219
+ }
220
+ function resolvePathEnvKey() {
221
+ if (process.env["Path"] !== undefined)
222
+ return "Path";
223
+ if (process.env["PATH"] !== undefined)
224
+ return "PATH";
225
+ return null;
226
+ }
227
+ function errorToMessageString(error) {
228
+ if (typeof error === "string")
229
+ return error;
230
+ if (error instanceof Error)
231
+ return error.message;
232
+ return "";
233
+ }
234
+ function firstStringField(input, primaryKey, secondaryKey) {
235
+ const primary = input[primaryKey];
236
+ if (typeof primary === "string")
237
+ return primary;
238
+ const secondary = input[secondaryKey];
239
+ if (typeof secondary === "string")
240
+ return secondary;
241
+ return undefined;
242
+ }
243
+ function extractSessionIdRaw(msg) {
244
+ if (typeof msg.session_id === "string")
245
+ return msg.session_id;
246
+ if (typeof msg.sessionId === "string")
247
+ return msg.sessionId;
248
+ if (typeof msg.session?.id === "string")
249
+ return msg.session.id;
250
+ return "";
251
+ }
252
+ function isClaudeThinkingEffort(value) {
253
+ return (value === "low" ||
254
+ value === "medium" ||
255
+ value === "high" ||
256
+ value === "xhigh" ||
257
+ value === "max");
258
+ }
259
+ function isClaudeThinkingOption(value) {
260
+ return value === CLAUDE_ULTRACODE_THINKING_OPTION_ID || isClaudeThinkingEffort(value);
261
+ }
262
+ const MAX_RECENT_STDERR_CHARS = 4000;
263
+ const STDERR_FLUSH_WAIT_MS = 150;
264
+ const STDERR_FLUSH_POLL_INTERVAL_MS = 10;
265
+ function summarizeClaudeOptionsForLog(options) {
266
+ const systemPromptRaw = options.systemPrompt;
267
+ const systemPromptSummary = (() => {
268
+ if (!systemPromptRaw) {
269
+ return { mode: "none", preset: null };
270
+ }
271
+ if (typeof systemPromptRaw === "string") {
272
+ return { mode: "string", preset: null };
273
+ }
274
+ const prompt = toObjectRecord(systemPromptRaw);
275
+ const promptType = typeof prompt?.type === "string" ? prompt.type : "custom";
276
+ return {
277
+ mode: promptType === "preset" ? "preset" : "custom",
278
+ preset: typeof prompt?.preset === "string" && prompt.preset.length > 0 ? prompt.preset : null,
279
+ };
280
+ })();
281
+ const mcpServerNames = options.mcpServers ? Object.keys(options.mcpServers).sort() : [];
282
+ return {
283
+ cwd: typeof options.cwd === "string" ? options.cwd : null,
284
+ permissionMode: typeof options.permissionMode === "string" ? options.permissionMode : null,
285
+ model: typeof options.model === "string" ? options.model : null,
286
+ includePartialMessages: options.includePartialMessages === true,
287
+ settingSources: Array.isArray(options.settingSources) ? options.settingSources : [],
288
+ enableFileCheckpointing: options.enableFileCheckpointing === true,
289
+ hasResume: typeof options.resume === "string" && options.resume.length > 0,
290
+ maxThinkingTokens: typeof options.maxThinkingTokens === "number" ? options.maxThinkingTokens : null,
291
+ hasEnv: !!options.env,
292
+ envKeyCount: Object.keys(options.env ?? {}).length,
293
+ hasMcpServers: mcpServerNames.length > 0,
294
+ mcpServerNames,
295
+ systemPromptMode: systemPromptSummary.mode,
296
+ systemPromptPreset: systemPromptSummary.preset,
297
+ hasCanUseTool: typeof options.canUseTool === "function",
298
+ hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
299
+ hasStderrHandler: typeof options.stderr === "function",
300
+ pathToClaudeCodeExecutable: typeof options.pathToClaudeCodeExecutable === "string"
301
+ ? options.pathToClaudeCodeExecutable
302
+ : null,
303
+ persistSession: typeof options.persistSession === "boolean" ? options.persistSession : null,
304
+ fastMode: readClaudeFastModeSetting(options.settings),
305
+ };
306
+ }
307
+ function readClaudeFastModeSetting(settings) {
308
+ if (!settings || typeof settings === "string") {
309
+ return null;
310
+ }
311
+ return typeof settings.fastMode === "boolean" ? settings.fastMode : null;
312
+ }
313
+ function mergeClaudeSettings(settings, updates) {
314
+ if (!settings || typeof settings === "string") {
315
+ return settings ?? updates;
316
+ }
317
+ return { ...settings, ...updates };
318
+ }
319
+ function isToolResultTextBlock(value) {
320
+ return (!!value &&
321
+ typeof value === "object" &&
322
+ value.type === "text" &&
323
+ typeof value.text === "string");
324
+ }
325
+ function normalizeForDeterministicString(value, seen) {
326
+ if (value === null ||
327
+ typeof value === "string" ||
328
+ typeof value === "number" ||
329
+ typeof value === "boolean") {
330
+ return value;
331
+ }
332
+ if (typeof value === "bigint") {
333
+ return value.toString();
334
+ }
335
+ if (typeof value === "function") {
336
+ return "[function]";
337
+ }
338
+ if (typeof value === "symbol") {
339
+ return value.toString();
340
+ }
341
+ if (typeof value === "undefined") {
342
+ return "[undefined]";
343
+ }
344
+ if (Array.isArray(value)) {
345
+ return value.map((entry) => normalizeForDeterministicString(entry, seen));
346
+ }
347
+ if (typeof value === "object") {
348
+ const objectValue = value;
349
+ if (seen.has(objectValue)) {
350
+ return "[circular]";
351
+ }
352
+ seen.add(objectValue);
353
+ const record = toObjectRecord(value);
354
+ if (!record) {
355
+ seen.delete(objectValue);
356
+ return "[invalid]";
357
+ }
358
+ const normalized = {};
359
+ for (const key of Object.keys(record).sort()) {
360
+ normalized[key] = normalizeForDeterministicString(record[key], seen);
361
+ }
362
+ seen.delete(objectValue);
363
+ return normalized;
364
+ }
365
+ if (typeof value === "number" || typeof value === "boolean") {
366
+ return String(value);
367
+ }
368
+ return "[unsupported]";
369
+ }
370
+ function deterministicStringify(value) {
371
+ if (typeof value === "undefined") {
372
+ return "";
373
+ }
374
+ try {
375
+ const normalized = normalizeForDeterministicString(value, new WeakSet());
376
+ if (typeof normalized === "string") {
377
+ return normalized;
378
+ }
379
+ return JSON.stringify(normalized);
380
+ }
381
+ catch {
382
+ if (typeof value === "string") {
383
+ return value;
384
+ }
385
+ if (typeof value === "number" || typeof value === "boolean") {
386
+ return String(value);
387
+ }
388
+ return "[unserializable]";
389
+ }
390
+ }
391
+ function coerceToolResultContentToString(content) {
392
+ if (typeof content === "string") {
393
+ return content;
394
+ }
395
+ if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
396
+ return content.map((block) => block.text).join("");
397
+ }
398
+ return deterministicStringify(content);
399
+ }
400
+ function toBase64ImageOutput(block) {
401
+ const record = toObjectRecord(block);
402
+ if (!record || record.type !== "image") {
403
+ return null;
404
+ }
405
+ const source = toObjectRecord(record.source);
406
+ if (!source || source.type !== "base64" || typeof source.data !== "string") {
407
+ return null;
408
+ }
409
+ return {
410
+ data: source.data,
411
+ mimeType: typeof source.media_type === "string" ? source.media_type : null,
412
+ };
413
+ }
414
+ // Claude returns images inside tool_result content as base64 Anthropic blocks. Left in place they
415
+ // reach coerceToolResultContentToString, which JSON.stringifies the whole array — dumping base64
416
+ // into the tool output. We pull those blocks out to render them as image markdown and leave a
417
+ // "[image]" placeholder so image-only results still produce non-empty output.
418
+ function splitClaudeToolResultImages(content) {
419
+ if (!Array.isArray(content)) {
420
+ return { images: [], text: content };
421
+ }
422
+ const images = [];
423
+ const text = content.map((block) => {
424
+ const image = toBase64ImageOutput(block);
425
+ if (image) {
426
+ images.push(image);
427
+ return { type: "text", text: "[image]" };
428
+ }
429
+ return block;
430
+ });
431
+ return { images, text };
432
+ }
433
+ function normalizeClaudeTranscriptText(value) {
434
+ if (typeof value !== "string") {
435
+ return null;
436
+ }
437
+ const normalized = value.trim();
438
+ return normalized.length > 0 ? normalized : null;
439
+ }
440
+ function isClaudeInterruptPlaceholderText(value) {
441
+ const normalized = normalizeClaudeTranscriptText(value);
442
+ return normalized !== null && INTERRUPT_PLACEHOLDER_PATTERN.test(normalized);
443
+ }
444
+ function isClaudeNoResponsePlaceholderText(value) {
445
+ return normalizeClaudeTranscriptText(value) === NO_RESPONSE_REQUESTED_PLACEHOLDER;
446
+ }
447
+ const LOCAL_COMMAND_STDOUT_PATTERN = /^\s*<local-command-stdout>[\s\S]*<\/local-command-stdout>\s*$/;
448
+ const CLAUDE_COMMAND_MESSAGE_PATTERN = /<command-message>([\s\S]*?)<\/command-message>/;
449
+ const CLAUDE_COMMAND_ARGS_PATTERN = /<command-args>([\s\S]*?)<\/command-args>/;
450
+ const CLAUDE_COMMAND_NAME_PATTERN = /<command-name>([\s\S]*?)<\/command-name>/;
451
+ function isClaudeLocalCommandStdout(value) {
452
+ const normalized = normalizeClaudeTranscriptText(value);
453
+ return normalized !== null && LOCAL_COMMAND_STDOUT_PATTERN.test(normalized);
454
+ }
455
+ function isClaudeTranscriptNoiseText(value) {
456
+ return (isClaudeInterruptPlaceholderText(value) ||
457
+ isClaudeNoResponsePlaceholderText(value) ||
458
+ isClaudeLocalCommandStdout(value));
459
+ }
460
+ function collectClaudeTextContentParts(content) {
461
+ if (typeof content === "string") {
462
+ const normalized = normalizeClaudeTranscriptText(content);
463
+ return normalized ? [normalized] : [];
464
+ }
465
+ if (!isUnknownArray(content)) {
466
+ return [];
467
+ }
468
+ const parts = [];
469
+ for (const block of content) {
470
+ const blockRecord = toObjectRecord(block);
471
+ if (!blockRecord) {
472
+ continue;
473
+ }
474
+ const text = normalizeClaudeTranscriptText(blockRecord.text);
475
+ if (text) {
476
+ parts.push(text);
477
+ continue;
478
+ }
479
+ const input = normalizeClaudeTranscriptText(blockRecord.input);
480
+ if (input) {
481
+ parts.push(input);
482
+ }
483
+ }
484
+ return parts;
485
+ }
486
+ function isClaudeTranscriptNoiseContent(content) {
487
+ const parts = collectClaudeTextContentParts(content);
488
+ return parts.length > 0 && parts.every((part) => isClaudeTranscriptNoiseText(part));
489
+ }
490
+ export function extractUserMessageText(content) {
491
+ if (typeof content === "string") {
492
+ const normalized = content.trim();
493
+ if (!normalized || isClaudeTranscriptNoiseText(normalized)) {
494
+ return null;
495
+ }
496
+ return normalizeClaudeUserPromptText(normalized);
497
+ }
498
+ if (!Array.isArray(content)) {
499
+ return null;
500
+ }
501
+ const parts = [];
502
+ for (const block of content) {
503
+ if (!block || typeof block !== "object") {
504
+ continue;
505
+ }
506
+ const text = typeof block.text === "string" ? block.text : undefined;
507
+ if (text && text.trim()) {
508
+ const trimmed = text.trim();
509
+ if (!isClaudeTranscriptNoiseText(trimmed)) {
510
+ const normalized = normalizeClaudeUserPromptText(trimmed);
511
+ if (normalized) {
512
+ parts.push(normalized);
513
+ }
514
+ }
515
+ continue;
516
+ }
517
+ const input = typeof block.input === "string" ? block.input : undefined;
518
+ if (input && input.trim()) {
519
+ const trimmed = input.trim();
520
+ if (!isClaudeTranscriptNoiseText(trimmed)) {
521
+ const normalized = normalizeClaudeUserPromptText(trimmed);
522
+ if (normalized) {
523
+ parts.push(normalized);
524
+ }
525
+ }
526
+ }
527
+ }
528
+ if (parts.length === 0) {
529
+ return null;
530
+ }
531
+ const combined = parts.join("\n\n").trim();
532
+ return combined.length > 0 ? combined : null;
533
+ }
534
+ function isMetadata(value) {
535
+ return typeof value === "object" && value !== null;
536
+ }
537
+ function createDefaultToolUseCacheEntry(id, block) {
538
+ const nameFromBlock = typeof block.name === "string" && block.name.length > 0 ? block.name : "tool";
539
+ let server;
540
+ if (typeof block.server === "string" && block.server.length > 0) {
541
+ server = block.server;
542
+ }
543
+ else if (typeof block.name === "string" && block.name.length > 0) {
544
+ server = block.name;
545
+ }
546
+ else {
547
+ server = "tool";
548
+ }
549
+ return {
550
+ id,
551
+ name: nameFromBlock,
552
+ server,
553
+ classification: "generic",
554
+ started: false,
555
+ };
556
+ }
557
+ function readTrimmedString(value) {
558
+ if (typeof value !== "string") {
559
+ return undefined;
560
+ }
561
+ const trimmed = value.trim();
562
+ return trimmed.length > 0 ? trimmed : undefined;
563
+ }
564
+ function isMcpServerConfig(value) {
565
+ if (!isMetadata(value)) {
566
+ return false;
567
+ }
568
+ const type = value.type;
569
+ if (type === "stdio") {
570
+ return typeof value.command === "string";
571
+ }
572
+ if (type === "http" || type === "sse") {
573
+ return typeof value.url === "string";
574
+ }
575
+ return false;
576
+ }
577
+ function isMcpServersRecord(value) {
578
+ if (!isMetadata(value)) {
579
+ return false;
580
+ }
581
+ for (const config of Object.values(value)) {
582
+ if (!isMcpServerConfig(config)) {
583
+ return false;
584
+ }
585
+ }
586
+ return true;
587
+ }
588
+ function isPermissionMode(value) {
589
+ return typeof value === "string" && VALID_CLAUDE_MODES.has(value);
590
+ }
591
+ function isTruthyEnvValue(value) {
592
+ const normalized = value?.trim().toLowerCase();
593
+ return (normalized !== undefined &&
594
+ normalized.length > 0 &&
595
+ normalized !== "0" &&
596
+ normalized !== "false" &&
597
+ normalized !== "no" &&
598
+ normalized !== "off");
599
+ }
600
+ function detectIneligibleAutoModeTransport(env) {
601
+ if (isTruthyEnvValue(env.CLAUDE_CODE_USE_BEDROCK)) {
602
+ return "Bedrock";
603
+ }
604
+ if (isTruthyEnvValue(env.CLAUDE_CODE_USE_VERTEX)) {
605
+ return "Vertex";
606
+ }
607
+ return null;
608
+ }
609
+ function assertClaudeAutoModeEligible(mode, env) {
610
+ if (mode !== "auto") {
611
+ return;
612
+ }
613
+ const transport = detectIneligibleAutoModeTransport(env);
614
+ if (transport === null) {
615
+ return;
616
+ }
617
+ throw new Error(`Claude Auto mode requires the Anthropic API and is not supported when Claude Code uses ${transport}. Select another permission mode or unset the ${transport === "Bedrock" ? "CLAUDE_CODE_USE_BEDROCK" : "CLAUDE_CODE_USE_VERTEX"} environment variable.`);
618
+ }
619
+ function coerceSessionMetadata(metadata) {
620
+ if (!isMetadata(metadata)) {
621
+ return {};
622
+ }
623
+ const result = {};
624
+ if (metadata.provider === "claude" || metadata.provider === "codex") {
625
+ result.provider = metadata.provider;
626
+ }
627
+ if (typeof metadata.cwd === "string") {
628
+ result.cwd = metadata.cwd;
629
+ }
630
+ if (typeof metadata.modeId === "string") {
631
+ result.modeId = metadata.modeId;
632
+ }
633
+ if (typeof metadata.model === "string") {
634
+ result.model = metadata.model;
635
+ }
636
+ if (typeof metadata.title === "string" || metadata.title === null) {
637
+ result.title = metadata.title;
638
+ }
639
+ if (typeof metadata.approvalPolicy === "string") {
640
+ result.approvalPolicy = metadata.approvalPolicy;
641
+ }
642
+ if (typeof metadata.sandboxMode === "string") {
643
+ result.sandboxMode = metadata.sandboxMode;
644
+ }
645
+ if (typeof metadata.networkAccess === "boolean") {
646
+ result.networkAccess = metadata.networkAccess;
647
+ }
648
+ if (typeof metadata.webSearch === "boolean") {
649
+ result.webSearch = metadata.webSearch;
650
+ }
651
+ if (isMetadata(metadata.extra)) {
652
+ const extra = {};
653
+ if (isMetadata(metadata.extra.codex)) {
654
+ extra.codex = metadata.extra.codex;
655
+ }
656
+ if (isClaudeExtra(metadata.extra.claude)) {
657
+ extra.claude = metadata.extra.claude;
658
+ }
659
+ if (extra.codex || extra.claude) {
660
+ result.extra = extra;
661
+ }
662
+ }
663
+ if (typeof metadata.systemPrompt === "string") {
664
+ result.systemPrompt = metadata.systemPrompt;
665
+ }
666
+ if (isMcpServersRecord(metadata.mcpServers)) {
667
+ result.mcpServers = metadata.mcpServers;
668
+ }
669
+ return result;
670
+ }
671
+ export function toClaudeSdkMcpConfig(config) {
672
+ switch (config.type) {
673
+ case "stdio":
674
+ return {
675
+ type: "stdio",
676
+ command: config.command,
677
+ args: config.args,
678
+ env: config.env,
679
+ alwaysLoad: config.alwaysLoad,
680
+ };
681
+ case "http":
682
+ return {
683
+ type: "http",
684
+ url: config.url,
685
+ headers: config.headers,
686
+ alwaysLoad: config.alwaysLoad,
687
+ };
688
+ case "sse":
689
+ return {
690
+ type: "sse",
691
+ url: config.url,
692
+ headers: config.headers,
693
+ alwaysLoad: config.alwaysLoad,
694
+ };
695
+ }
696
+ throw new Error("Unhandled MCP server config type");
697
+ }
698
+ function isClaudeContentChunk(value) {
699
+ return isMetadata(value) && typeof value.type === "string";
700
+ }
701
+ function isClaudeExtra(value) {
702
+ return isMetadata(value);
703
+ }
704
+ function isPermissionUpdate(value) {
705
+ if (!isMetadata(value)) {
706
+ return false;
707
+ }
708
+ const type = value.type;
709
+ if (type !== "addRules" && type !== "replaceRules" && type !== "removeRules") {
710
+ return false;
711
+ }
712
+ const rules = value.rules;
713
+ const behavior = value.behavior;
714
+ const destination = value.destination;
715
+ return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
716
+ }
717
+ function resolvePermissionKind(toolName, input) {
718
+ if (toolName === "ExitPlanMode")
719
+ return "plan";
720
+ if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
721
+ return "question";
722
+ }
723
+ return "tool";
724
+ }
725
+ function getClaudeModeLabel(modeId) {
726
+ return DEFAULT_MODES.find((mode) => mode.id === modeId)?.label ?? modeId;
727
+ }
728
+ function buildClaudePlanPermissionActions(resumeMode) {
729
+ const actions = [
730
+ {
731
+ id: "reject",
732
+ label: "Reject",
733
+ behavior: "deny",
734
+ variant: "danger",
735
+ intent: "dismiss",
736
+ },
737
+ {
738
+ id: "implement",
739
+ label: "Implement",
740
+ behavior: "allow",
741
+ variant: "primary",
742
+ intent: "implement",
743
+ },
744
+ ];
745
+ if (resumeMode === "bypassPermissions") {
746
+ actions.push({
747
+ id: "implement_resume",
748
+ label: `Implement with ${getClaudeModeLabel(resumeMode)}`,
749
+ behavior: "allow",
750
+ variant: "secondary",
751
+ intent: "implement_resume",
752
+ });
753
+ }
754
+ return actions;
755
+ }
756
+ class TimelineAssembler {
757
+ constructor() {
758
+ this.messages = new Map();
759
+ this.finalizedMessageIds = new Set();
760
+ this.activeMessageByRun = new Map();
761
+ this.syntheticMessageCounter = 0;
762
+ }
763
+ consume(input) {
764
+ if (input.message.type === "assistant") {
765
+ return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
766
+ }
767
+ if (input.message.type === "stream_event") {
768
+ return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
769
+ }
770
+ return [];
771
+ }
772
+ consumeAssistantMessage(message, runId, messageIdHint) {
773
+ const messageId = this.readMessageIdFromAssistantMessage(message) ??
774
+ messageIdHint ??
775
+ this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
776
+ if (!messageId) {
777
+ return [];
778
+ }
779
+ if (this.finalizedMessageIds.has(messageId)) {
780
+ return [];
781
+ }
782
+ const state = this.ensureMessageState(messageId, runId);
783
+ const fragments = this.extractFragments(message.message?.content);
784
+ return this.applyAbsoluteFragments(state, fragments);
785
+ }
786
+ consumeStreamEvent(message, runId, messageIdHint) {
787
+ const event = toObjectRecord(message.event) ?? {};
788
+ const eventType = readTrimmedString(event.type);
789
+ const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
790
+ if (eventType === "message_start") {
791
+ const messageId = this.resolveMessageId({
792
+ runId,
793
+ createIfMissing: true,
794
+ messageId: streamEventMessageId,
795
+ });
796
+ if (!messageId) {
797
+ return [];
798
+ }
799
+ this.ensureMessageState(messageId, runId);
800
+ return [];
801
+ }
802
+ if (eventType === "message_stop") {
803
+ const messageId = this.resolveMessageId({
804
+ runId,
805
+ createIfMissing: false,
806
+ messageId: streamEventMessageId,
807
+ });
808
+ if (!messageId) {
809
+ return [];
810
+ }
811
+ return this.finalizeMessage(messageId, runId);
812
+ }
813
+ if (eventType === "content_block_start") {
814
+ return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
815
+ }
816
+ if (eventType === "content_block_delta") {
817
+ return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
818
+ }
819
+ return [];
820
+ }
821
+ consumeDeltaContent(content, runId, messageIdHint) {
822
+ const fragments = this.extractFragments(content);
823
+ if (fragments.length === 0) {
824
+ return [];
825
+ }
826
+ const messageId = this.resolveMessageId({
827
+ runId,
828
+ createIfMissing: true,
829
+ messageId: messageIdHint,
830
+ });
831
+ if (!messageId) {
832
+ return [];
833
+ }
834
+ const state = this.ensureMessageState(messageId, runId);
835
+ return this.appendFragments(state, fragments);
836
+ }
837
+ appendFragments(state, fragments) {
838
+ for (const fragment of fragments) {
839
+ if (fragment.kind === "assistant") {
840
+ state.assistantText += fragment.text;
841
+ }
842
+ else {
843
+ state.reasoningText += fragment.text;
844
+ }
845
+ }
846
+ return this.emitNewContent(state);
847
+ }
848
+ applyAbsoluteFragments(state, fragments) {
849
+ const assistantText = fragments
850
+ .filter((fragment) => fragment.kind === "assistant")
851
+ .map((fragment) => fragment.text)
852
+ .join("");
853
+ const reasoningText = fragments
854
+ .filter((fragment) => fragment.kind === "reasoning")
855
+ .map((fragment) => fragment.text)
856
+ .join("");
857
+ if (assistantText.length > 0) {
858
+ if (!assistantText.startsWith(state.assistantText)) {
859
+ state.emittedAssistantLength = 0;
860
+ }
861
+ state.assistantText = assistantText;
862
+ }
863
+ if (reasoningText.length > 0) {
864
+ if (!reasoningText.startsWith(state.reasoningText)) {
865
+ state.emittedReasoningLength = 0;
866
+ }
867
+ state.reasoningText = reasoningText;
868
+ }
869
+ return this.emitNewContent(state);
870
+ }
871
+ finalizeMessage(messageId, runId) {
872
+ const state = this.messages.get(messageId);
873
+ if (!state) {
874
+ return [];
875
+ }
876
+ state.stopped = true;
877
+ const items = this.emitNewContent(state);
878
+ if (runId && this.activeMessageByRun.get(runId) === messageId) {
879
+ this.activeMessageByRun.delete(runId);
880
+ }
881
+ this.finalizedMessageIds.add(messageId);
882
+ this.messages.delete(messageId);
883
+ return items;
884
+ }
885
+ emitNewContent(state) {
886
+ const items = [];
887
+ const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
888
+ if (nextAssistantText.length > 0 &&
889
+ nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER &&
890
+ !isClaudeTranscriptNoiseText(nextAssistantText)) {
891
+ state.emittedAssistantLength = state.assistantText.length;
892
+ items.push({ type: "assistant_message", text: nextAssistantText, messageId: state.id });
893
+ }
894
+ const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
895
+ if (nextReasoningText.length > 0) {
896
+ state.emittedReasoningLength = state.reasoningText.length;
897
+ items.push({ type: "reasoning", text: nextReasoningText });
898
+ }
899
+ return items;
900
+ }
901
+ ensureMessageState(messageId, runId) {
902
+ const existing = this.messages.get(messageId);
903
+ if (existing) {
904
+ existing.stopped = false;
905
+ if (runId) {
906
+ this.activeMessageByRun.set(runId, messageId);
907
+ }
908
+ return existing;
909
+ }
910
+ const created = {
911
+ id: messageId,
912
+ assistantText: "",
913
+ reasoningText: "",
914
+ emittedAssistantLength: 0,
915
+ emittedReasoningLength: 0,
916
+ stopped: false,
917
+ };
918
+ this.messages.set(messageId, created);
919
+ if (runId) {
920
+ this.activeMessageByRun.set(runId, messageId);
921
+ }
922
+ return created;
923
+ }
924
+ resolveMessageId(input) {
925
+ if (input.messageId) {
926
+ return input.messageId;
927
+ }
928
+ if (input.runId) {
929
+ const active = this.activeMessageByRun.get(input.runId);
930
+ if (active) {
931
+ return active;
932
+ }
933
+ }
934
+ if (!input.createIfMissing) {
935
+ return null;
936
+ }
937
+ const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
938
+ if (input.runId) {
939
+ this.activeMessageByRun.set(input.runId, synthetic);
940
+ }
941
+ return synthetic;
942
+ }
943
+ extractFragments(content) {
944
+ if (typeof content === "string") {
945
+ if (content.length === 0) {
946
+ return [];
947
+ }
948
+ return [{ kind: "assistant", text: content }];
949
+ }
950
+ const blocks = Array.isArray(content) ? content : [content];
951
+ const fragments = [];
952
+ for (const rawBlock of blocks) {
953
+ if (!isClaudeContentChunk(rawBlock)) {
954
+ continue;
955
+ }
956
+ if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
957
+ typeof rawBlock.text === "string" &&
958
+ rawBlock.text.length > 0) {
959
+ fragments.push({ kind: "assistant", text: rawBlock.text });
960
+ }
961
+ if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
962
+ typeof rawBlock.thinking === "string" &&
963
+ rawBlock.thinking.length > 0) {
964
+ fragments.push({ kind: "reasoning", text: rawBlock.thinking });
965
+ }
966
+ }
967
+ return fragments;
968
+ }
969
+ readMessageIdFromAssistantMessage(message) {
970
+ const candidate = toObjectRecord(message);
971
+ const messageContainer = toObjectRecord(candidate?.message);
972
+ return (readTrimmedString(candidate?.message_id) ?? readTrimmedString(messageContainer?.id) ?? null);
973
+ }
974
+ readMessageIdFromStreamEvent(event) {
975
+ const messageContainer = toObjectRecord(event.message);
976
+ return readTrimmedString(event.message_id) ?? readTrimmedString(messageContainer?.id) ?? null;
977
+ }
978
+ }
979
+ function isSyntheticUserEntry(entry) {
980
+ const candidate = toObjectRecord(entry);
981
+ if (!candidate) {
982
+ return false;
983
+ }
984
+ return (candidate.isSynthetic === true || candidate.isMeta === true || Boolean(candidate.toolUseResult));
985
+ }
986
+ function isToolResultUserEntry(entry) {
987
+ const candidate = toObjectRecord(entry);
988
+ if (!candidate) {
989
+ return false;
990
+ }
991
+ const message = toObjectRecord(candidate.message);
992
+ const content = message?.content;
993
+ return (Array.isArray(content) && content.some((block) => toObjectRecord(block)?.type === "tool_result"));
994
+ }
995
+ function isSyntheticHistoryUserEntry(entry) {
996
+ return isSyntheticUserEntry(entry) && !isToolResultUserEntry(entry);
997
+ }
998
+ function firstTrimmedString(sources) {
999
+ for (const source of sources) {
1000
+ const value = readTrimmedString(source);
1001
+ if (value) {
1002
+ return value;
1003
+ }
1004
+ }
1005
+ return null;
1006
+ }
1007
+ function readTranscriptUuid(message) {
1008
+ const root = toObjectRecord(message) ?? {};
1009
+ const messageType = readTrimmedString(root.type);
1010
+ if (messageType !== "user" && messageType !== "assistant") {
1011
+ return null;
1012
+ }
1013
+ return firstTrimmedString([root.uuid]);
1014
+ }
1015
+ export function readEventIdentifiers(message) {
1016
+ const root = toObjectRecord(message) ?? {};
1017
+ const messageType = readTrimmedString(root.type);
1018
+ const streamEvent = toObjectRecord(root.event);
1019
+ const streamEventMessage = toObjectRecord(streamEvent?.message);
1020
+ const messageContainer = toObjectRecord(root.message);
1021
+ const messageIdFromUuid = messageType === "user" || messageType === "assistant" || messageType === "system"
1022
+ ? root.uuid
1023
+ : undefined;
1024
+ return {
1025
+ taskId: firstTrimmedString([
1026
+ root.task_id,
1027
+ streamEvent?.task_id,
1028
+ streamEventMessage?.task_id,
1029
+ messageContainer?.task_id,
1030
+ ]),
1031
+ parentMessageId: firstTrimmedString([
1032
+ root.parent_message_id,
1033
+ streamEvent?.parent_message_id,
1034
+ streamEventMessage?.parent_message_id,
1035
+ messageContainer?.parent_message_id,
1036
+ ]),
1037
+ messageId: firstTrimmedString([
1038
+ root.message_id,
1039
+ streamEvent?.message_id,
1040
+ streamEventMessage?.id,
1041
+ streamEventMessage?.message_id,
1042
+ messageContainer?.id,
1043
+ messageContainer?.message_id,
1044
+ messageIdFromUuid,
1045
+ ]),
1046
+ };
1047
+ }
1048
+ export class ClaudeAgentClient {
1049
+ constructor(options) {
1050
+ this.provider = "claude";
1051
+ this.capabilities = CLAUDE_CAPABILITIES;
1052
+ this.defaults = options.defaults;
1053
+ this.logger = options.logger.child({ module: "agent", provider: "claude" });
1054
+ this.runtimeSettings = options.runtimeSettings;
1055
+ this.queryFactory = options.queryFactory;
1056
+ this.resolveBinary = options.resolveBinary ?? (() => resolveClaudeBinary(this.runtimeSettings));
1057
+ this.configDir = options.configDir;
1058
+ }
1059
+ async createSession(config, launchContext, options) {
1060
+ const claudeConfig = this.assertConfig(config);
1061
+ return new ClaudeAgentSession(claudeConfig, {
1062
+ defaults: this.defaults,
1063
+ runtimeSettings: this.runtimeSettings,
1064
+ agentId: launchContext?.agentId,
1065
+ launchEnv: launchContext?.env,
1066
+ persistSession: options?.persistSession,
1067
+ logger: this.logger,
1068
+ queryFactory: this.queryFactory,
1069
+ resolveBinary: this.resolveBinary,
1070
+ // Story 3.6 — Fork-resume: seed claudeSessionId without a persistence
1071
+ // handle so the SDK/PTY paths emit `--resume <sid>`, but no read/write
1072
+ // is performed against the source JSONL by paseo itself. Claude CLI
1073
+ // forks the conversation into a new SID; source remains byte-identical.
1074
+ ...(options?.resumeSessionId ? { resumeSessionId: options.resumeSessionId } : {}),
1075
+ });
1076
+ }
1077
+ async resumeSession(handle, overrides, launchContext) {
1078
+ const metadata = coerceSessionMetadata(handle.metadata);
1079
+ const merged = { ...metadata, ...overrides };
1080
+ if (!merged.cwd) {
1081
+ throw new Error("Claude resume requires the original working directory in metadata");
1082
+ }
1083
+ const mergedConfig = {
1084
+ ...merged,
1085
+ provider: "claude",
1086
+ cwd: merged.cwd,
1087
+ };
1088
+ const claudeConfig = this.assertConfig(mergedConfig);
1089
+ return new ClaudeAgentSession(claudeConfig, {
1090
+ defaults: this.defaults,
1091
+ runtimeSettings: this.runtimeSettings,
1092
+ handle,
1093
+ agentId: launchContext?.agentId,
1094
+ launchEnv: launchContext?.env,
1095
+ logger: this.logger,
1096
+ queryFactory: this.queryFactory,
1097
+ resolveBinary: this.resolveBinary,
1098
+ });
1099
+ }
1100
+ async fetchCatalog(_options) {
1101
+ // Claude exposes a global catalog here; cwd/force are intentionally irrelevant.
1102
+ const models = await getClaudeModelsWithSettings(this.logger, this.configDir);
1103
+ return { models, modes: DEFAULT_MODES };
1104
+ }
1105
+ async listFeatures(config) {
1106
+ const claudeConfig = this.assertConfig(config);
1107
+ return buildClaudeFeatures({
1108
+ modelId: claudeConfig.model,
1109
+ fastModeEnabled: claudeConfig.featureValues?.fast_mode === true,
1110
+ });
1111
+ }
1112
+ async listImportableSessions(options) {
1113
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
1114
+ const projectsRoot = path.join(configDir, "projects");
1115
+ if (!(await pathExists(projectsRoot))) {
1116
+ return [];
1117
+ }
1118
+ const limit = options?.limit ?? 20;
1119
+ const candidates = await collectRecentClaudeSessions(projectsRoot, limit * 3);
1120
+ const parsed = await Promise.all(candidates.map((candidate) => parseClaudeSessionDescriptor(candidate.path, candidate.mtime)));
1121
+ return parsed
1122
+ .filter((session) => session !== null)
1123
+ .slice(0, limit);
1124
+ }
1125
+ async importSession(input, context) {
1126
+ return importSessionFromPersistence({
1127
+ provider: "claude",
1128
+ request: input,
1129
+ context,
1130
+ resumeSession: this.resumeSession.bind(this),
1131
+ });
1132
+ }
1133
+ async isAvailable() {
1134
+ // Story 4.1 — When transport is explicitly "pty", the daemon needs node-pty
1135
+ // loadable. Short-circuit unavailable with a reason exposed via diagnostic.
1136
+ if (this.runtimeSettings?.transport === "pty" && !isNodePtyAvailable()) {
1137
+ return false;
1138
+ }
1139
+ const command = this.runtimeSettings?.command;
1140
+ if (command?.mode === "replace") {
1141
+ return await isCommandAvailable(command.argv[0]);
1142
+ }
1143
+ return await isCommandAvailable("claude");
1144
+ }
1145
+ /**
1146
+ * Story 4.1 — Detailed availability with rebuild diagnostic. The public
1147
+ * `isAvailable()` keeps its Promise<boolean> contract (AgentClient interface);
1148
+ * tests and `getDiagnostic()` consume this for the rebuild hint.
1149
+ */
1150
+ async checkAvailability() {
1151
+ if (this.runtimeSettings?.transport === "pty" && !isNodePtyAvailable()) {
1152
+ return { available: false, reason: NODE_PTY_UNAVAILABLE_REASON };
1153
+ }
1154
+ return { available: await this.isAvailable() };
1155
+ }
1156
+ async getDiagnostic() {
1157
+ try {
1158
+ const launch = await resolveProviderLaunch({
1159
+ commandConfig: this.runtimeSettings?.command,
1160
+ defaultBinary: "claude",
1161
+ });
1162
+ const availability = await checkProviderLaunchAvailable(launch);
1163
+ const auth = availability.available
1164
+ ? await resolveClaudeAuth(launch, availability, this.runtimeSettings)
1165
+ : null;
1166
+ return {
1167
+ diagnostic: formatProviderDiagnostic("Claude Code", [
1168
+ ...(await buildCommandResolutionDiagnosticRows(launch, {
1169
+ knownBinaryNames: ["claude"],
1170
+ })),
1171
+ ...(await buildBinaryDiagnosticRows(launch, availability)),
1172
+ ...(auth ? [{ label: "Auth", value: auth }] : []),
1173
+ ]),
1174
+ };
1175
+ }
1176
+ catch (error) {
1177
+ return {
1178
+ diagnostic: formatProviderDiagnosticError("Claude Code", error),
1179
+ };
1180
+ }
1181
+ }
1182
+ assertConfig(config) {
1183
+ if (config.provider !== "claude") {
1184
+ throw new Error(`ClaudeAgentClient received config for provider '${config.provider}'`);
1185
+ }
1186
+ return { ...config, provider: "claude" };
1187
+ }
1188
+ }
1189
+ async function resolveClaudeBinary(runtimeSettings) {
1190
+ const launch = await resolveProviderLaunch({
1191
+ commandConfig: runtimeSettings?.command,
1192
+ defaultBinary: "claude",
1193
+ });
1194
+ const availability = await checkProviderLaunchAvailable(launch);
1195
+ if (availability.available) {
1196
+ return availability.resolvedPath ?? launch.command;
1197
+ }
1198
+ throw new Error("Claude binary not found. Install Claude Code (https://github.com/anthropics/claude-code) and ensure it is available in your shell PATH.");
1199
+ }
1200
+ async function resolveClaudeAuth(launch, availability, runtimeSettings) {
1201
+ const run = async (executable, args) => {
1202
+ try {
1203
+ return await execCommand(executable, args, {
1204
+ ...createProviderEnvSpec({ runtimeSettings }),
1205
+ timeout: 5000,
1206
+ });
1207
+ }
1208
+ catch (error) {
1209
+ const err = toObjectRecord(error);
1210
+ const stdout = typeof err?.stdout === "string" ? err.stdout : "";
1211
+ const stderr = typeof err?.stderr === "string" ? err.stderr : "";
1212
+ const fallbackMessage = typeof err?.message === "string" ? err.message : "";
1213
+ return { stdout, stderr: stderr || fallbackMessage };
1214
+ }
1215
+ };
1216
+ try {
1217
+ const executable = availability.resolvedPath ?? launch.command;
1218
+ const result = await run(executable, [...launch.args, "auth", "status"]);
1219
+ const combined = [result.stdout, result.stderr]
1220
+ .map((s) => s.trim())
1221
+ .filter((s) => s.length > 0)
1222
+ .join("\n");
1223
+ return combined || null;
1224
+ }
1225
+ catch {
1226
+ return null;
1227
+ }
1228
+ }
1229
+ function extractContextWindowSize(modelUsage) {
1230
+ const usageRecord = toObjectRecord(modelUsage);
1231
+ if (!usageRecord) {
1232
+ return undefined;
1233
+ }
1234
+ let maxContextWindow;
1235
+ for (const value of Object.values(usageRecord)) {
1236
+ const valueRecord = toObjectRecord(value);
1237
+ if (!valueRecord) {
1238
+ continue;
1239
+ }
1240
+ const contextWindow = valueRecord.contextWindow;
1241
+ if (typeof contextWindow !== "number" ||
1242
+ !Number.isFinite(contextWindow) ||
1243
+ contextWindow <= 0) {
1244
+ continue;
1245
+ }
1246
+ maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow);
1247
+ }
1248
+ return maxContextWindow;
1249
+ }
1250
+ function readStreamRequestInputTokens(event) {
1251
+ const messageUsage = toObjectRecord(toObjectRecord(event.message)?.usage);
1252
+ if (!messageUsage) {
1253
+ return undefined;
1254
+ }
1255
+ const usage = messageUsage;
1256
+ const inputTokens = typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
1257
+ ? usage.input_tokens
1258
+ : undefined;
1259
+ const cacheCreationInputTokens = typeof usage.cache_creation_input_tokens === "number" &&
1260
+ Number.isFinite(usage.cache_creation_input_tokens)
1261
+ ? usage.cache_creation_input_tokens
1262
+ : 0;
1263
+ const cacheReadInputTokens = typeof usage.cache_read_input_tokens === "number" &&
1264
+ Number.isFinite(usage.cache_read_input_tokens)
1265
+ ? usage.cache_read_input_tokens
1266
+ : 0;
1267
+ if (typeof inputTokens !== "number" || inputTokens < 0) {
1268
+ return undefined;
1269
+ }
1270
+ return inputTokens + cacheCreationInputTokens + cacheReadInputTokens;
1271
+ }
1272
+ function readStreamRequestOutputTokens(event) {
1273
+ const outputTokens = toObjectRecord(event.usage)?.output_tokens;
1274
+ if (typeof outputTokens !== "number" || !Number.isFinite(outputTokens) || outputTokens < 0) {
1275
+ return undefined;
1276
+ }
1277
+ return outputTokens;
1278
+ }
1279
+ function readLastUsageIteration(usage) {
1280
+ const iterations = toObjectRecord(usage)?.iterations;
1281
+ if (!Array.isArray(iterations)) {
1282
+ return undefined;
1283
+ }
1284
+ for (let index = iterations.length - 1; index >= 0; index -= 1) {
1285
+ const candidate = toObjectRecord(iterations[index]);
1286
+ if (candidate) {
1287
+ return candidate;
1288
+ }
1289
+ }
1290
+ return undefined;
1291
+ }
1292
+ function readUsageTokenTotal(usage) {
1293
+ const usageWithCacheCreation = usage;
1294
+ const inputTokens = typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
1295
+ ? usage.input_tokens
1296
+ : 0;
1297
+ const cacheCreationInputTokens = typeof usageWithCacheCreation.cache_creation_input_tokens === "number" &&
1298
+ Number.isFinite(usageWithCacheCreation.cache_creation_input_tokens)
1299
+ ? usageWithCacheCreation.cache_creation_input_tokens
1300
+ : 0;
1301
+ const cacheReadInputTokens = typeof usage.cache_read_input_tokens === "number" &&
1302
+ Number.isFinite(usage.cache_read_input_tokens)
1303
+ ? usage.cache_read_input_tokens
1304
+ : 0;
1305
+ const outputTokens = typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens)
1306
+ ? usage.output_tokens
1307
+ : 0;
1308
+ const total = inputTokens + cacheCreationInputTokens + cacheReadInputTokens + outputTokens;
1309
+ return total > 0 ? total : undefined;
1310
+ }
1311
+ function readActiveUsageTokens(usage) {
1312
+ const activeUsage = readLastUsageIteration(usage);
1313
+ return activeUsage ? readUsageTokenTotal(activeUsage) : undefined;
1314
+ }
1315
+ function readLegacyResultUsageTokens(usage) {
1316
+ const usageRecord = toObjectRecord(usage);
1317
+ return usageRecord ? readUsageTokenTotal(usageRecord) : undefined;
1318
+ }
1319
+ function isClaudeSubagentToolName(name) {
1320
+ return name === "Task" || name === "Agent";
1321
+ }
1322
+ function readClaudeParentToolUseId(message) {
1323
+ if (!("parent_tool_use_id" in message)) {
1324
+ return null;
1325
+ }
1326
+ const parentToolUseId = message.parent_tool_use_id;
1327
+ return typeof parentToolUseId === "string" && parentToolUseId.length > 0 ? parentToolUseId : null;
1328
+ }
1329
+ class ClaudeContextUsageState {
1330
+ constructor(initialContextWindowMaxTokens) {
1331
+ this.completedResultTurns = 0;
1332
+ this.contextWindowMaxTokens = initialContextWindowMaxTokens;
1333
+ }
1334
+ beginTurn() {
1335
+ this.streamRequestInputTokens = undefined;
1336
+ this.streamRequestOutputTokens = undefined;
1337
+ this.compactedContextWindowUsedTokens = undefined;
1338
+ }
1339
+ setInitialContextWindowMaxTokens(contextWindowMaxTokens) {
1340
+ this.contextWindowMaxTokens = contextWindowMaxTokens;
1341
+ }
1342
+ recordModelUsage(modelUsage) {
1343
+ const contextWindowMaxTokens = extractContextWindowSize(modelUsage);
1344
+ if (contextWindowMaxTokens !== undefined) {
1345
+ this.contextWindowMaxTokens = contextWindowMaxTokens;
1346
+ }
1347
+ return this.contextWindowMaxTokens;
1348
+ }
1349
+ buildStreamUsageEvent(event) {
1350
+ const streamEvent = toObjectRecord(event);
1351
+ if (!streamEvent) {
1352
+ return null;
1353
+ }
1354
+ const eventType = readTrimmedString(streamEvent.type);
1355
+ if (eventType === "message_start") {
1356
+ const inputTokens = readStreamRequestInputTokens(streamEvent);
1357
+ if (typeof inputTokens !== "number") {
1358
+ return null;
1359
+ }
1360
+ this.streamRequestInputTokens = inputTokens;
1361
+ this.streamRequestOutputTokens = 0;
1362
+ }
1363
+ else if (eventType === "message_delta") {
1364
+ const outputTokens = readStreamRequestOutputTokens(streamEvent);
1365
+ if (typeof outputTokens !== "number") {
1366
+ return null;
1367
+ }
1368
+ this.streamRequestOutputTokens = outputTokens;
1369
+ }
1370
+ else {
1371
+ return null;
1372
+ }
1373
+ const usedTokens = this.streamUsedTokens();
1374
+ if (usedTokens === undefined) {
1375
+ return null;
1376
+ }
1377
+ return this.createUsageUpdatedEvent(usedTokens);
1378
+ }
1379
+ buildResultUsage(message, modelUsage) {
1380
+ try {
1381
+ if (!message.usage) {
1382
+ return undefined;
1383
+ }
1384
+ const usage = {
1385
+ inputTokens: message.usage.input_tokens,
1386
+ cachedInputTokens: message.usage.cache_read_input_tokens,
1387
+ outputTokens: message.usage.output_tokens,
1388
+ totalCostUsd: message.total_cost_usd,
1389
+ };
1390
+ const modelContextWindowMaxTokens = this.recordModelUsage(modelUsage ?? message.modelUsage);
1391
+ if (this.contextWindowMaxTokens !== undefined) {
1392
+ usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
1393
+ }
1394
+ else if (modelContextWindowMaxTokens !== undefined) {
1395
+ usage.contextWindowMaxTokens = modelContextWindowMaxTokens;
1396
+ }
1397
+ const activeResultUsageTokens = readActiveUsageTokens(message.usage) ??
1398
+ (this.completedResultTurns === 0 ? readLegacyResultUsageTokens(message.usage) : undefined);
1399
+ const usedTokens = this.streamUsedTokens() ?? activeResultUsageTokens ?? this.compactedContextWindowUsedTokens;
1400
+ if (usedTokens !== undefined) {
1401
+ usage.contextWindowUsedTokens = usedTokens;
1402
+ }
1403
+ return usage;
1404
+ }
1405
+ finally {
1406
+ this.compactedContextWindowUsedTokens = undefined;
1407
+ this.completedResultTurns += 1;
1408
+ }
1409
+ }
1410
+ streamUsedTokens() {
1411
+ if (typeof this.streamRequestInputTokens !== "number" ||
1412
+ typeof this.streamRequestOutputTokens !== "number") {
1413
+ return undefined;
1414
+ }
1415
+ const usedTokens = this.streamRequestInputTokens + this.streamRequestOutputTokens;
1416
+ return usedTokens > 0 ? usedTokens : undefined;
1417
+ }
1418
+ createUsageUpdatedEvent(contextWindowUsedTokens) {
1419
+ const usage = {
1420
+ contextWindowUsedTokens,
1421
+ };
1422
+ if (this.contextWindowMaxTokens !== undefined) {
1423
+ usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
1424
+ }
1425
+ return {
1426
+ type: "usage_updated",
1427
+ provider: "claude",
1428
+ usage,
1429
+ };
1430
+ }
1431
+ buildCompactionUsageEvent(postTokens) {
1432
+ this.streamRequestInputTokens = undefined;
1433
+ this.streamRequestOutputTokens = undefined;
1434
+ this.compactedContextWindowUsedTokens = postTokens;
1435
+ const usage = {};
1436
+ if (this.contextWindowMaxTokens !== undefined) {
1437
+ usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
1438
+ }
1439
+ if (postTokens !== undefined) {
1440
+ usage.contextWindowUsedTokens = postTokens;
1441
+ }
1442
+ return {
1443
+ type: "usage_updated",
1444
+ provider: "claude",
1445
+ usage,
1446
+ };
1447
+ }
1448
+ }
1449
+ export class ClaudeAgentSession {
1450
+ /**
1451
+ * Register a hook event handler for an agent. Stub in Epic 1 — Epic 3 wires
1452
+ * incoming hook events from the PTY transport into this handler.
1453
+ */
1454
+ registerHookHandler(agentId, handler) {
1455
+ this.hookEventHandlers.set(agentId, handler);
1456
+ }
1457
+ unregisterHookHandler(agentId) {
1458
+ this.hookEventHandlers.delete(agentId);
1459
+ }
1460
+ constructor(config, options) {
1461
+ this.provider = "claude";
1462
+ this.capabilities = CLAUDE_CAPABILITIES;
1463
+ this.query = null;
1464
+ this.childProcess = null;
1465
+ this.input = null;
1466
+ this.planResumeMode = null;
1467
+ this.availableModes = DEFAULT_MODES;
1468
+ this.toolUseCache = new Map();
1469
+ this.toolUseIndexToId = new Map();
1470
+ this.toolUseInputBuffers = new Map();
1471
+ this.pendingPermissions = new Map();
1472
+ this.activeForegroundTurnId = null;
1473
+ this.autonomousTurn = null;
1474
+ this.subscribers = new Set();
1475
+ this.timelineAssembler = new TimelineAssembler();
1476
+ this.sidechainTracker = new ClaudeSidechainTracker({
1477
+ getToolInput: (toolUseId) => this.toolUseCache.get(toolUseId)?.input ?? null,
1478
+ });
1479
+ // Tracks Claude Code background shells (`Bash` run_in_background) that outlive
1480
+ // the turn, so the agent keeps surfacing as "Running" until they finish or are
1481
+ // dismissed. Fed from the transport-agnostic message stream (works for PTY).
1482
+ this.backgroundTaskTracker = new ClaudeBackgroundTaskTracker();
1483
+ this.persistedHistory = [];
1484
+ this.historyPending = false;
1485
+ this.turnState = "idle";
1486
+ this.nextTurnOrdinal = 1;
1487
+ this.cancelCurrentTurn = null;
1488
+ this.cachedRuntimeInfo = null;
1489
+ this.lastOptionsModel = null;
1490
+ this.lastRuntimeModel = null;
1491
+ this.compacting = false;
1492
+ this.queryPumpPromise = null;
1493
+ this.queryRestartNeeded = false;
1494
+ // Story 3.3 — transport handle used by setMode/setModel/setThinkingOption to
1495
+ // discriminate between PTY (slash-command) and SDK (query-object) paths.
1496
+ // Populated by ensureQuery() with the live SdkTransport; tests inject a
1497
+ // PtyTransport stub directly.
1498
+ this.activeTransport = null;
1499
+ // Story 4.1 — Resolved transport ("sdk" | "pty"), cached for session lifetime.
1500
+ // Populated lazily by ensureTransport() from runtimeSettings.transport.
1501
+ this.resolvedTransport = null;
1502
+ // Story 4.4 — Cached `claude --version` string, populated lazily in the
1503
+ // background after the first ensureTransport() call. "unknown" if probing
1504
+ // fails or has not yet resolved when telemetry fires.
1505
+ this.claudeVersion = null;
1506
+ this.transportSpawnedTelemetryFired = false;
1507
+ this.pendingInterruptAbort = false;
1508
+ this.foregroundHasVisibleActivity = false;
1509
+ this.activeTurnHasAssistantText = false;
1510
+ this.userMessageIds = [];
1511
+ this.emittedUserMessageIds = new Set();
1512
+ this.rewindTurnAnchors = [];
1513
+ this.pendingFreshSessionId = null;
1514
+ this.cumulativeImageBase64Bytes = 0;
1515
+ this.cumulativeImageWarnEmitted = false;
1516
+ this.recentStderr = "";
1517
+ this.closed = false;
1518
+ this.hookEventHandlers = new Map();
1519
+ this.handlePermissionRequest = async (toolName, input, options) => {
1520
+ const requestId = `permission-${randomUUID()}`;
1521
+ const kind = resolvePermissionKind(toolName, input);
1522
+ const requestInput = normalizeClaudeAskUserQuestionRequestInput(toolName, input);
1523
+ const metadata = {};
1524
+ if (options.toolUseID) {
1525
+ metadata.toolUseId = options.toolUseID;
1526
+ }
1527
+ if (toolName === "ExitPlanMode" && typeof input.plan === "string") {
1528
+ metadata.planText = input.plan;
1529
+ }
1530
+ const toolDetail = kind === "tool"
1531
+ ? mapClaudeRunningToolCall({
1532
+ name: toolName,
1533
+ callId: options.toolUseID ?? requestId,
1534
+ input,
1535
+ output: null,
1536
+ })?.detail
1537
+ : undefined;
1538
+ const request = {
1539
+ id: requestId,
1540
+ provider: "claude",
1541
+ name: toolName,
1542
+ kind,
1543
+ input: requestInput,
1544
+ detail: toolDetail,
1545
+ suggestions: options.suggestions?.map((suggestion) => ({
1546
+ ...suggestion,
1547
+ })),
1548
+ actions: kind === "plan" ? buildClaudePlanPermissionActions(this.planResumeMode) : undefined,
1549
+ metadata: Object.keys(metadata).length ? metadata : undefined,
1550
+ };
1551
+ this.pushEvent({
1552
+ type: "permission_requested",
1553
+ provider: "claude",
1554
+ request,
1555
+ });
1556
+ return await new Promise((resolve, reject) => {
1557
+ const cleanupFns = [];
1558
+ const cleanup = () => {
1559
+ while (cleanupFns.length) {
1560
+ const fn = cleanupFns.pop();
1561
+ try {
1562
+ fn?.();
1563
+ }
1564
+ catch {
1565
+ // ignore cleanup errors
1566
+ }
1567
+ }
1568
+ };
1569
+ const abortHandler = () => {
1570
+ this.pendingPermissions.delete(requestId);
1571
+ cleanup();
1572
+ reject(new Error("Permission request aborted"));
1573
+ };
1574
+ if (options?.signal) {
1575
+ if (options.signal.aborted) {
1576
+ abortHandler();
1577
+ return;
1578
+ }
1579
+ options.signal.addEventListener("abort", abortHandler, { once: true });
1580
+ cleanupFns.push(() => options.signal?.removeEventListener("abort", abortHandler));
1581
+ }
1582
+ this.pendingPermissions.set(requestId, {
1583
+ request,
1584
+ resolve,
1585
+ reject,
1586
+ cleanup,
1587
+ });
1588
+ });
1589
+ };
1590
+ this.config = config;
1591
+ this.launchEnv = options.launchEnv;
1592
+ this.agentId = options.agentId;
1593
+ this.defaults = options.defaults;
1594
+ this.runtimeSettings = options.runtimeSettings;
1595
+ this.persistSession = options.persistSession;
1596
+ this.logger = options.logger.child({ agentId: this.agentId });
1597
+ this.queryFactory = options.queryFactory;
1598
+ this.resolveBinary = options.resolveBinary;
1599
+ this.contextUsage = new ClaudeContextUsageState(findClaudeModel(this.config.model)?.contextWindowMaxTokens);
1600
+ const handle = options.handle;
1601
+ if (handle) {
1602
+ if (!handle.sessionId) {
1603
+ throw new Error("Cannot resume: persistence handle has no sessionId");
1604
+ }
1605
+ this.claudeSessionId = handle.sessionId;
1606
+ this.persistence = handle;
1607
+ this.loadPersistedHistory(handle.sessionId);
1608
+ }
1609
+ else if (options.resumeSessionId) {
1610
+ // Story 3.6 — Fork-resume: seed the SID so buildOptions() / PTY emits
1611
+ // `--resume <sid>`. Do NOT set a persistence handle — Claude CLI forks to
1612
+ // a NEW SID at spawn time and the SOURCE JSONL stays byte-identical (AC4:
1613
+ // only WRITES fork; reads are fine).
1614
+ this.claudeSessionId = options.resumeSessionId;
1615
+ this.persistence = null;
1616
+ // Load the source transcript into persistedHistory for DISPLAY only.
1617
+ // loadPersistedHistory is a read-only fs.readFileSync, so the source
1618
+ // stays byte-identical — but without it the resumed chat renders empty
1619
+ // ("Start chatting…") until the next turn, even though Claude has the
1620
+ // full context internally. The create handler calls
1621
+ // hydrateTimelineFromProvider to stream this into the timeline.
1622
+ this.loadPersistedHistory(options.resumeSessionId);
1623
+ }
1624
+ else {
1625
+ this.claudeSessionId = null;
1626
+ this.persistence = null;
1627
+ }
1628
+ // Validate mode if provided
1629
+ if (config.modeId && !VALID_CLAUDE_MODES.has(config.modeId)) {
1630
+ const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
1631
+ throw new Error(`Invalid mode '${config.modeId}' for Claude provider. Valid modes: ${validModesList}`);
1632
+ }
1633
+ this.currentMode = isPermissionMode(config.modeId) ? config.modeId : "default";
1634
+ if (this.currentMode !== "plan") {
1635
+ this.planResumeMode = this.currentMode;
1636
+ }
1637
+ }
1638
+ get id() {
1639
+ return this.claudeSessionId;
1640
+ }
1641
+ get features() {
1642
+ return buildClaudeFeatures({
1643
+ modelId: this.config.model,
1644
+ fastModeEnabled: this.config.featureValues?.fast_mode === true,
1645
+ });
1646
+ }
1647
+ async getRuntimeInfo() {
1648
+ if (this.cachedRuntimeInfo) {
1649
+ return { ...this.cachedRuntimeInfo };
1650
+ }
1651
+ const info = {
1652
+ provider: "claude",
1653
+ sessionId: this.claudeSessionId,
1654
+ model: this.lastOptionsModel,
1655
+ modeId: this.currentMode ?? null,
1656
+ ...(this.lastRuntimeModel
1657
+ ? {
1658
+ extra: {
1659
+ runtimeModel: this.lastRuntimeModel,
1660
+ },
1661
+ }
1662
+ : {}),
1663
+ };
1664
+ this.cachedRuntimeInfo = info;
1665
+ return { ...info };
1666
+ }
1667
+ async run(prompt, options) {
1668
+ const result = await runProviderTurn({
1669
+ prompt,
1670
+ runOptions: options,
1671
+ startTurn: (p, o) => this.startTurn(p, o),
1672
+ subscribe: (callback) => this.subscribe(callback),
1673
+ getSessionId: () => this.claudeSessionId ?? "",
1674
+ reduceFinalText: appendOrReplaceGrowingAssistantMessage,
1675
+ });
1676
+ this.cachedRuntimeInfo = {
1677
+ provider: "claude",
1678
+ sessionId: this.claudeSessionId,
1679
+ model: this.lastOptionsModel,
1680
+ modeId: this.currentMode ?? null,
1681
+ };
1682
+ if (!this.claudeSessionId) {
1683
+ throw new Error("Session ID not set after run completed");
1684
+ }
1685
+ return result;
1686
+ }
1687
+ async startTurn(prompt, _options) {
1688
+ if (this.closed) {
1689
+ throw new Error("Claude session is closed");
1690
+ }
1691
+ if (this.activeForegroundTurnId) {
1692
+ throw new Error("A foreground turn is already active");
1693
+ }
1694
+ const slashCommand = this.resolveSlashCommandInvocation(prompt);
1695
+ if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
1696
+ const turnId = this.createTurnId("foreground");
1697
+ this.activeForegroundTurnId = turnId;
1698
+ this.transitionTurnState("foreground", "rewind command");
1699
+ void this.executeRewindTurn(turnId, slashCommand);
1700
+ return { turnId };
1701
+ }
1702
+ if (this.autonomousTurn) {
1703
+ this.completeAutonomousTurn();
1704
+ }
1705
+ const turnId = this.createTurnId("foreground");
1706
+ this.activeForegroundTurnId = turnId;
1707
+ this.foregroundHasVisibleActivity = false;
1708
+ this.activeTurnHasAssistantText = false;
1709
+ this.contextUsage.beginTurn();
1710
+ this.transitionTurnState("foreground", "foreground turn started");
1711
+ this.clearRecentStderr();
1712
+ let sdkMessage;
1713
+ try {
1714
+ sdkMessage = await this.toSdkUserMessage(prompt);
1715
+ }
1716
+ catch (error) {
1717
+ this.finishForegroundTurn(this.buildTurnFailedEvent(error instanceof Error ? error.message : "Failed to prepare user message"));
1718
+ return { turnId };
1719
+ }
1720
+ let cancelIssued = false;
1721
+ const requestCancel = () => {
1722
+ if (cancelIssued) {
1723
+ return;
1724
+ }
1725
+ cancelIssued = true;
1726
+ if (this.cancelCurrentTurn === requestCancel) {
1727
+ this.cancelCurrentTurn = null;
1728
+ }
1729
+ this.rejectAllPendingPermissions(new Error("Permission request aborted"));
1730
+ this.finishForegroundTurn({
1731
+ type: "turn_canceled",
1732
+ provider: "claude",
1733
+ reason: "Interrupted",
1734
+ });
1735
+ void this.interruptActiveTurn().catch((error) => {
1736
+ this.logger.warn({ err: error }, "Failed to interrupt during cancel");
1737
+ });
1738
+ };
1739
+ this.cancelCurrentTurn = requestCancel;
1740
+ this.notifySubscribers({ type: "turn_started", provider: "claude" });
1741
+ try {
1742
+ await this.ensureQuery();
1743
+ if (!this.input) {
1744
+ throw new Error("Claude session input stream not initialized");
1745
+ }
1746
+ this.startQueryPump();
1747
+ this.input.push(sdkMessage);
1748
+ setTimeout(() => {
1749
+ if (this.activeForegroundTurnId === turnId) {
1750
+ this.emitSubmittedUserMessage(sdkMessage, turnId);
1751
+ }
1752
+ }, 0);
1753
+ }
1754
+ catch (error) {
1755
+ this.finishForegroundTurn(this.buildTurnFailedEvent(error instanceof Error ? error.message : "Claude stream failed"));
1756
+ }
1757
+ return { turnId };
1758
+ }
1759
+ subscribe(callback) {
1760
+ this.subscribers.add(callback);
1761
+ return () => {
1762
+ this.subscribers.delete(callback);
1763
+ };
1764
+ }
1765
+ async interrupt() {
1766
+ if (this.cancelCurrentTurn) {
1767
+ this.cancelCurrentTurn();
1768
+ return;
1769
+ }
1770
+ if (this.autonomousTurn) {
1771
+ this.flushPendingToolCalls();
1772
+ this.completeAutonomousTurn();
1773
+ }
1774
+ await this.interruptActiveTurn();
1775
+ }
1776
+ async *streamHistory() {
1777
+ if (!this.historyPending || this.persistedHistory.length === 0) {
1778
+ return;
1779
+ }
1780
+ const history = this.persistedHistory;
1781
+ this.persistedHistory = [];
1782
+ this.historyPending = false;
1783
+ for (const entry of history) {
1784
+ yield {
1785
+ type: "timeline",
1786
+ item: entry.item,
1787
+ provider: "claude",
1788
+ timestamp: entry.timestamp,
1789
+ };
1790
+ }
1791
+ }
1792
+ async getAvailableModes() {
1793
+ return this.availableModes;
1794
+ }
1795
+ async getCurrentMode() {
1796
+ return this.currentMode ?? null;
1797
+ }
1798
+ async setMode(modeId) {
1799
+ // Validate mode
1800
+ if (!VALID_CLAUDE_MODES.has(modeId)) {
1801
+ const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
1802
+ throw new Error(`Invalid mode '${modeId}' for Claude provider. Valid modes: ${validModesList}`);
1803
+ }
1804
+ const normalized = isPermissionMode(modeId) ? modeId : "default";
1805
+ assertClaudeAutoModeEligible(normalized, this.buildSdkEnv(this.config.extra?.claude));
1806
+ const previousMode = this.currentMode;
1807
+ const activeQuery = await this.ensureQuery();
1808
+ if (this.activeTransport instanceof PtyTransport) {
1809
+ // PTY path: claude 2.1.119 has no /mode or /permission-mode slash command.
1810
+ // Force a session reload so the new spawn picks up --permission-mode <mode>.
1811
+ await this.reloadAgentSession({ overrides: { permissionMode: normalized } });
1812
+ }
1813
+ else {
1814
+ await activeQuery.setPermissionMode(normalized);
1815
+ }
1816
+ if (normalized === "plan") {
1817
+ if (previousMode !== "plan") {
1818
+ this.planResumeMode = previousMode;
1819
+ }
1820
+ }
1821
+ else {
1822
+ this.planResumeMode = normalized;
1823
+ }
1824
+ this.currentMode = normalized;
1825
+ }
1826
+ async setModel(modelId) {
1827
+ const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
1828
+ const activeQuery = await this.ensureQuery();
1829
+ if (this.activeTransport instanceof PtyTransport) {
1830
+ // PTY path: /model <name> slash command. Null/empty → omit write, state-only.
1831
+ if (normalizedModelId !== null) {
1832
+ await this.activeTransport.write(`/model ${normalizedModelId}\r`);
1833
+ }
1834
+ }
1835
+ else {
1836
+ await activeQuery.setModel(normalizedModelId ?? undefined);
1837
+ }
1838
+ // Shared self-state update — both transport paths.
1839
+ this.config.model = normalizedModelId ?? undefined;
1840
+ if (!claudeModelSupportsFastMode(this.config.model) && this.config.featureValues?.fast_mode) {
1841
+ await this.applyFastModeFeature(false, activeQuery);
1842
+ }
1843
+ this.contextUsage.setInitialContextWindowMaxTokens(findClaudeModel(this.config.model)?.contextWindowMaxTokens);
1844
+ this.lastOptionsModel = normalizedModelId ?? this.lastOptionsModel;
1845
+ this.lastRuntimeModel = null;
1846
+ this.cachedRuntimeInfo = null;
1847
+ // Model change affects persistence metadata, so invalidate cached handle.
1848
+ this.persistence = null;
1849
+ }
1850
+ async setThinkingOption(thinkingOptionId) {
1851
+ const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
1852
+ ? thinkingOptionId
1853
+ : null;
1854
+ if (!normalizedThinkingOptionId || normalizedThinkingOptionId === "default") {
1855
+ this.config.thinkingOptionId = undefined;
1856
+ }
1857
+ else if (isClaudeThinkingOption(normalizedThinkingOptionId)) {
1858
+ this.config.thinkingOptionId = normalizedThinkingOptionId;
1859
+ }
1860
+ else {
1861
+ throw new Error(`Unknown thinking option: ${normalizedThinkingOptionId}`);
1862
+ }
1863
+ if (this.activeTransport instanceof PtyTransport) {
1864
+ // PTY path: /effort <level> slash command, takes effect immediately.
1865
+ // Do NOT set queryRestartNeeded — that's an SDK-only restart signal.
1866
+ const effort = normalizedThinkingOptionId && normalizedThinkingOptionId !== "default"
1867
+ ? normalizedThinkingOptionId
1868
+ : "low";
1869
+ await this.activeTransport.write(`/effort ${effort}\r`);
1870
+ return;
1871
+ }
1872
+ this.queryRestartNeeded = true;
1873
+ if (this.activeForegroundTurnId || this.autonomousTurn) {
1874
+ return SETTING_APPLIES_NEXT_TURN_NOTICE;
1875
+ }
1876
+ }
1877
+ /**
1878
+ * Story 3.3 — PTY-path session reload hook.
1879
+ *
1880
+ * On the PTY transport, runtime config changes that require a respawn (e.g.
1881
+ * permission mode) call this method. Default impl is a no-op stub so the
1882
+ * unit tests can `vi.spyOn(session, "reloadAgentSession")` to assert the
1883
+ * branch fires. The full integration is wired by agent-manager in a later
1884
+ * story; until then this method updates the in-session `currentMode` so the
1885
+ * next ensureQuery() rebuild picks up the new mode.
1886
+ */
1887
+ async reloadAgentSession(opts) {
1888
+ if (opts?.overrides?.permissionMode) {
1889
+ this.currentMode = opts.overrides.permissionMode;
1890
+ }
1891
+ // Force the next ensureQuery() to rebuild so the new spawn picks up the
1892
+ // updated --permission-mode value.
1893
+ this.queryRestartNeeded = true;
1894
+ }
1895
+ async setFeature(featureId, value) {
1896
+ if (featureId !== "fast_mode") {
1897
+ throw new Error(`Unknown Claude feature: ${featureId}`);
1898
+ }
1899
+ const enabled = Boolean(value);
1900
+ if (enabled && !claudeModelSupportsFastMode(this.config.model)) {
1901
+ throw new Error(`Claude fast mode is not available for model '${this.config.model ?? "default"}'`);
1902
+ }
1903
+ await this.applyFastModeFeature(enabled);
1904
+ }
1905
+ async applyFastModeFeature(enabled, query) {
1906
+ this.config.featureValues = {
1907
+ ...this.config.featureValues,
1908
+ fast_mode: enabled,
1909
+ };
1910
+ const activeQuery = query ?? this.query;
1911
+ if (activeQuery) {
1912
+ await activeQuery.applyFlagSettings({ fastMode: enabled });
1913
+ }
1914
+ this.cachedRuntimeInfo = null;
1915
+ }
1916
+ getPendingPermissions() {
1917
+ return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
1918
+ }
1919
+ getActiveBackgroundTaskCount() {
1920
+ return this.backgroundTaskTracker.count;
1921
+ }
1922
+ /**
1923
+ * Dismiss live background shell(s) the way a human would in the Claude Code
1924
+ * CLI: drive Claude Code's own TUI over the PTY with its `ctrl+x ctrl+k` kill
1925
+ * chord (sent twice — arm + confirm), killing the shell INSIDE Claude Code
1926
+ * rather than via an external OS kill. Then clear our tracked set.
1927
+ *
1928
+ * Caveat: `ctrl+x ctrl+k` is Claude Code's *global* stop-background-work
1929
+ * chord (no per-shell-by-id interactive binding exists), so we clear the
1930
+ * whole set afterward. `taskId` is accepted for API symmetry/logging.
1931
+ *
1932
+ * Returns true if keystrokes were delivered to a live PTY-driven session. On
1933
+ * the SDK transport (no TUI to drive) it falls back to untracking the id.
1934
+ */
1935
+ async dismissBackgroundTask(taskId) {
1936
+ const transport = this.activeTransport;
1937
+ if (transport instanceof PtyTransport && typeof transport.writeRaw === "function") {
1938
+ for (const press of buildBackgroundKillPresses()) {
1939
+ await transport.writeRaw(press);
1940
+ }
1941
+ this.backgroundTaskTracker.clear();
1942
+ this.logger.debug({ taskId }, "Dismissed background shells via Claude Code ctrl+x ctrl+k");
1943
+ return true;
1944
+ }
1945
+ // No interactive TUI to drive (e.g. SDK transport) — best-effort untrack.
1946
+ return this.backgroundTaskTracker.remove(taskId);
1947
+ }
1948
+ async respondToPermission(requestId, response) {
1949
+ const pending = this.pendingPermissions.get(requestId);
1950
+ if (!pending) {
1951
+ throw new Error(`No pending permission request with id '${requestId}'`);
1952
+ }
1953
+ this.pendingPermissions.delete(requestId);
1954
+ pending.cleanup?.();
1955
+ if (response.behavior === "allow") {
1956
+ if (pending.request.kind === "plan") {
1957
+ const selectedActionId = response.selectedActionId;
1958
+ const shouldResumePriorMode = selectedActionId === "implement_resume" && this.planResumeMode === "bypassPermissions";
1959
+ const targetMode = shouldResumePriorMode
1960
+ ? "bypassPermissions"
1961
+ : "acceptEdits";
1962
+ await this.setMode(targetMode);
1963
+ this.pushToolCall(mapClaudeCompletedToolCall({
1964
+ name: "plan_approval",
1965
+ callId: pending.request.id,
1966
+ input: pending.request.input ?? null,
1967
+ output: {
1968
+ approved: true,
1969
+ actionId: selectedActionId ?? "implement",
1970
+ },
1971
+ }));
1972
+ }
1973
+ const updatedInput = pending.request.kind === "question"
1974
+ ? normalizeClaudeAskUserQuestionUpdatedInput(response.updatedInput, pending.request.input ?? undefined)
1975
+ : (response.updatedInput ?? pending.request.input ?? {});
1976
+ const result = {
1977
+ behavior: "allow",
1978
+ updatedInput,
1979
+ updatedPermissions: this.normalizePermissionUpdates(response.updatedPermissions),
1980
+ };
1981
+ pending.resolve(result);
1982
+ }
1983
+ else {
1984
+ if (pending.request.kind === "tool") {
1985
+ this.pushToolCall(mapClaudeFailedToolCall({
1986
+ name: pending.request.name,
1987
+ callId: (typeof pending.request.metadata?.toolUseId === "string"
1988
+ ? pending.request.metadata.toolUseId
1989
+ : null) ?? pending.request.id,
1990
+ input: pending.request.input ?? null,
1991
+ output: null,
1992
+ error: { message: response.message ?? "Permission denied" },
1993
+ }));
1994
+ }
1995
+ const result = {
1996
+ behavior: "deny",
1997
+ message: response.message ?? "Permission request denied",
1998
+ interrupt: response.interrupt,
1999
+ };
2000
+ pending.resolve(result);
2001
+ }
2002
+ this.pushEvent({
2003
+ type: "permission_resolved",
2004
+ provider: "claude",
2005
+ requestId,
2006
+ resolution: response,
2007
+ });
2008
+ }
2009
+ describePersistence() {
2010
+ if (this.persistence) {
2011
+ return this.persistence;
2012
+ }
2013
+ if (!this.claudeSessionId) {
2014
+ return null;
2015
+ }
2016
+ this.persistence = {
2017
+ provider: "claude",
2018
+ sessionId: this.claudeSessionId,
2019
+ nativeHandle: this.claudeSessionId,
2020
+ metadata: { ...this.config },
2021
+ };
2022
+ return this.persistence;
2023
+ }
2024
+ async close() {
2025
+ this.logger.trace({
2026
+ agentId: this.agentId,
2027
+ provider: "claude",
2028
+ sessionId: this.claudeSessionId,
2029
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2030
+ turnState: this.turnState,
2031
+ hasQuery: Boolean(this.query),
2032
+ hasInput: Boolean(this.input),
2033
+ hasActiveForegroundTurnId: Boolean(this.activeForegroundTurnId),
2034
+ }, "provider.claude.session_close.start");
2035
+ this.closed = true;
2036
+ this.rejectAllPendingPermissions(new Error("Claude session closed"));
2037
+ this.cancelCurrentTurn?.();
2038
+ this.subscribers.clear();
2039
+ this.activeForegroundTurnId = null;
2040
+ this.autonomousTurn = null;
2041
+ this.cancelCurrentTurn = null;
2042
+ this.turnState = "idle";
2043
+ this.sidechainTracker.clear();
2044
+ this.backgroundTaskTracker.clear();
2045
+ this.input?.end();
2046
+ this.query?.close?.();
2047
+ await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
2048
+ await this.awaitWithTimeout(this.query?.return?.(), "close query return");
2049
+ this.query = null;
2050
+ this.input = null;
2051
+ // Terminate the entire process tree (claude + MCP children) to prevent
2052
+ // orphan accumulation. The SDK's internal cleanup may only kill the
2053
+ // direct child process.
2054
+ if (this.childProcess) {
2055
+ const result = await terminateWithTreeKill(this.childProcess, {
2056
+ gracefulTimeoutMs: 2000,
2057
+ forceTimeoutMs: 2000,
2058
+ });
2059
+ if (result === "kill-timeout") {
2060
+ this.logger.warn({ pid: this.childProcess.pid, agentId: this.agentId }, "Claude process tree did not report exit after SIGKILL");
2061
+ }
2062
+ this.childProcess = null;
2063
+ }
2064
+ if (this.persistSession === false && this.claudeSessionId) {
2065
+ // Claude Code currently ignores --no-session-persistence outside --print mode
2066
+ // (see `claude --help`), so the SDK's persistSession=false is silently dropped
2067
+ // in stream-json mode. Sweep the transcript ourselves so ephemeral runs
2068
+ // (metadata generator, branch-name generator) don't show up as resumable.
2069
+ const historyPath = this.resolveHistoryPath(this.claudeSessionId);
2070
+ if (historyPath) {
2071
+ try {
2072
+ await promises.rm(historyPath, { force: true });
2073
+ }
2074
+ catch (error) {
2075
+ this.logger.warn({ err: error, historyPath, claudeSessionId: this.claudeSessionId }, "Failed to delete ephemeral Claude session transcript");
2076
+ }
2077
+ }
2078
+ }
2079
+ this.logger.trace({
2080
+ agentId: this.agentId,
2081
+ provider: "claude",
2082
+ sessionId: this.claudeSessionId,
2083
+ turnState: this.turnState,
2084
+ }, "provider.claude.session_close.complete");
2085
+ }
2086
+ async listCommands() {
2087
+ const q = await this.ensureQuery();
2088
+ const commands = await q.supportedCommands();
2089
+ const commandMap = new Map();
2090
+ for (const cmd of commands) {
2091
+ if (!commandMap.has(cmd.name)) {
2092
+ commandMap.set(cmd.name, {
2093
+ name: cmd.name,
2094
+ description: cmd.description,
2095
+ argumentHint: cmd.argumentHint,
2096
+ kind: classifyClaudeSlashCommand(cmd.name),
2097
+ });
2098
+ }
2099
+ }
2100
+ if (!commandMap.has(REWIND_COMMAND_NAME)) {
2101
+ commandMap.set(REWIND_COMMAND_NAME, REWIND_COMMAND);
2102
+ }
2103
+ return Array.from(commandMap.values()).sort((a, b) => a.name.localeCompare(b.name));
2104
+ }
2105
+ async revertConversation(input) {
2106
+ const target = this.resolveConversationRewindTarget(input.messageId);
2107
+ if (target.kind === "fresh-session") {
2108
+ this.startFreshConversationSession();
2109
+ return;
2110
+ }
2111
+ await revertClaudeConversation({
2112
+ sdk: realClaudeRewindSdk,
2113
+ sessionId: this.claudeSessionId,
2114
+ messageId: target.messageId,
2115
+ resolveMessageId: (messageId) => this.resolveClaudeMessageId(messageId),
2116
+ setSessionId: (sessionId) => {
2117
+ this.rebindConversationSession(sessionId);
2118
+ },
2119
+ });
2120
+ }
2121
+ async revertFiles(input) {
2122
+ const messageId = await this.resolveClaudeMessageId(input.messageId);
2123
+ await revertClaudeFiles({
2124
+ query: await this.ensureQuery(),
2125
+ messageId,
2126
+ });
2127
+ }
2128
+ async revertBoth(input) {
2129
+ await this.revertFiles(input);
2130
+ await this.revertConversation(input);
2131
+ }
2132
+ resolveSlashCommandInvocation(prompt) {
2133
+ if (typeof prompt !== "string") {
2134
+ return null;
2135
+ }
2136
+ const parsed = this.parseSlashCommandInput(prompt);
2137
+ if (!parsed) {
2138
+ return null;
2139
+ }
2140
+ return parsed.commandName === REWIND_COMMAND_NAME ? parsed : null;
2141
+ }
2142
+ parseSlashCommandInput(text) {
2143
+ const trimmed = text.trim();
2144
+ if (!trimmed.startsWith("/") || trimmed.length <= 1) {
2145
+ return null;
2146
+ }
2147
+ const withoutPrefix = trimmed.slice(1);
2148
+ const firstWhitespaceIdx = withoutPrefix.search(/\s/);
2149
+ const commandName = firstWhitespaceIdx === -1 ? withoutPrefix : withoutPrefix.slice(0, firstWhitespaceIdx);
2150
+ if (!commandName || commandName.includes("/")) {
2151
+ return null;
2152
+ }
2153
+ const rawArgs = firstWhitespaceIdx === -1 ? "" : withoutPrefix.slice(firstWhitespaceIdx + 1).trim();
2154
+ return rawArgs.length > 0
2155
+ ? { commandName, args: rawArgs, rawInput: trimmed }
2156
+ : { commandName, rawInput: trimmed };
2157
+ }
2158
+ buildRewindSuccessMessage(targetUserMessageId, rewindResult) {
2159
+ const fileCount = Array.isArray(rewindResult.filesChanged)
2160
+ ? rewindResult.filesChanged.length
2161
+ : undefined;
2162
+ const stats = [];
2163
+ if (typeof fileCount === "number") {
2164
+ stats.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
2165
+ }
2166
+ if (typeof rewindResult.insertions === "number") {
2167
+ stats.push(`${rewindResult.insertions} insertions`);
2168
+ }
2169
+ if (typeof rewindResult.deletions === "number") {
2170
+ stats.push(`${rewindResult.deletions} deletions`);
2171
+ }
2172
+ if (stats.length > 0) {
2173
+ return `Rewound tracked files to message ${targetUserMessageId} (${stats.join(", ")}).`;
2174
+ }
2175
+ return `Rewound tracked files to message ${targetUserMessageId}.`;
2176
+ }
2177
+ async attemptRewind(args) {
2178
+ if (typeof args === "string" && args.trim().length > 0) {
2179
+ const candidate = args.trim().split(/\s+/)[0] ?? "";
2180
+ if (!UUID_PATTERN.test(candidate)) {
2181
+ return {
2182
+ messageId: null,
2183
+ error: "Invalid message UUID. Usage: /rewind <user_message_uuid> or /rewind",
2184
+ };
2185
+ }
2186
+ const rewindResult = await this.rewindFilesOnce(candidate);
2187
+ if (rewindResult.canRewind) {
2188
+ return { messageId: candidate, result: rewindResult };
2189
+ }
2190
+ return {
2191
+ messageId: null,
2192
+ error: rewindResult.error ?? `No file checkpoint found for message ${candidate}.`,
2193
+ };
2194
+ }
2195
+ const candidates = this.getRewindCandidateUserMessageIds();
2196
+ if (candidates.length === 0) {
2197
+ return {
2198
+ messageId: null,
2199
+ error: "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
2200
+ };
2201
+ }
2202
+ let lastError;
2203
+ for (const candidate of candidates) {
2204
+ try {
2205
+ const rewindResult = await this.rewindFilesOnce(candidate);
2206
+ if (rewindResult.canRewind) {
2207
+ return { messageId: candidate, result: rewindResult };
2208
+ }
2209
+ if (rewindResult.error) {
2210
+ lastError = rewindResult.error;
2211
+ }
2212
+ }
2213
+ catch (error) {
2214
+ lastError = error instanceof Error ? error.message : "Failed to rewind tracked files.";
2215
+ }
2216
+ }
2217
+ return {
2218
+ messageId: null,
2219
+ error: lastError ?? "No rewind checkpoints are currently available for this session.",
2220
+ };
2221
+ }
2222
+ async rewindFilesOnce(messageId) {
2223
+ try {
2224
+ const activeQuery = await this.ensureFreshQuery();
2225
+ return await activeQuery.rewindFiles(messageId, { dryRun: false });
2226
+ }
2227
+ catch (error) {
2228
+ // The Claude SDK transport can close after a rewind call.
2229
+ // If that happens, mark the query stale so a follow-up attempt uses a fresh query.
2230
+ this.queryRestartNeeded = true;
2231
+ throw error;
2232
+ }
2233
+ }
2234
+ async ensureFreshQuery() {
2235
+ if (this.query) {
2236
+ this.queryRestartNeeded = true;
2237
+ }
2238
+ return this.ensureQuery();
2239
+ }
2240
+ getRewindCandidateUserMessageIds() {
2241
+ const candidates = [];
2242
+ const pushUnique = (value) => {
2243
+ if (typeof value === "string" && value.length > 0 && !candidates.includes(value)) {
2244
+ candidates.push(value);
2245
+ }
2246
+ };
2247
+ for (let idx = this.persistedHistory.length - 1; idx >= 0; idx -= 1) {
2248
+ const entry = this.persistedHistory[idx];
2249
+ if (entry?.item.type === "user_message") {
2250
+ pushUnique(entry.item.messageId);
2251
+ }
2252
+ }
2253
+ for (let idx = this.userMessageIds.length - 1; idx >= 0; idx -= 1) {
2254
+ pushUnique(this.userMessageIds[idx]);
2255
+ }
2256
+ return candidates;
2257
+ }
2258
+ rebindConversationSession(sessionId) {
2259
+ const oldSessionId = this.claudeSessionId;
2260
+ this.claudeSessionId = sessionId;
2261
+ this.pendingFreshSessionId = null;
2262
+ this.persistence = null;
2263
+ this.cachedRuntimeInfo = null;
2264
+ this.queryRestartNeeded = true;
2265
+ this.persistedHistory = [];
2266
+ this.historyPending = false;
2267
+ this.userMessageIds = [];
2268
+ this.emittedUserMessageIds.clear();
2269
+ this.rewindTurnAnchors.length = 0;
2270
+ this.loadPersistedHistory(sessionId);
2271
+ if (oldSessionId && oldSessionId !== sessionId) {
2272
+ this.dispatchEvents([
2273
+ {
2274
+ type: "timeline",
2275
+ provider: "claude",
2276
+ item: this.createClaudeSessionChangedNotice(oldSessionId, sessionId),
2277
+ },
2278
+ {
2279
+ type: "thread_started",
2280
+ provider: "claude",
2281
+ sessionId,
2282
+ },
2283
+ ]);
2284
+ }
2285
+ }
2286
+ startFreshConversationSession() {
2287
+ const sessionId = randomUUID();
2288
+ this.claudeSessionId = sessionId;
2289
+ this.pendingFreshSessionId = sessionId;
2290
+ this.persistence = null;
2291
+ this.cachedRuntimeInfo = null;
2292
+ this.queryRestartNeeded = true;
2293
+ this.persistedHistory = [];
2294
+ this.historyPending = false;
2295
+ this.userMessageIds = [];
2296
+ this.emittedUserMessageIds.clear();
2297
+ this.rewindTurnAnchors.length = 0;
2298
+ }
2299
+ rememberUserMessageId(messageId) {
2300
+ if (typeof messageId !== "string" || messageId.length === 0) {
2301
+ return;
2302
+ }
2303
+ const last = this.userMessageIds[this.userMessageIds.length - 1];
2304
+ if (last === messageId) {
2305
+ return;
2306
+ }
2307
+ this.userMessageIds.push(messageId);
2308
+ }
2309
+ /**
2310
+ * Story 4.1 — Resolve the effective transport for this session and cache it.
2311
+ * Called once per session lifetime; further calls return the cached value.
2312
+ * Epic 3 wiring will use this to choose between SdkTransport and PtyTransport.
2313
+ */
2314
+ ensureTransport() {
2315
+ if (this.resolvedTransport)
2316
+ return this.resolvedTransport;
2317
+ this.resolvedTransport = resolveClaudeTransport(this.runtimeSettings?.transport);
2318
+ this.fireTransportSpawnedTelemetry(this.resolvedTransport);
2319
+ return this.resolvedTransport;
2320
+ }
2321
+ /**
2322
+ * Story 4.4 — Fire `agent.transport.spawned` once per session. Probes
2323
+ * `claude --version` in the background; emits with `version: "unknown"` if
2324
+ * the probe is not yet resolved or fails. Never throws.
2325
+ */
2326
+ fireTransportSpawnedTelemetry(transport) {
2327
+ if (this.transportSpawnedTelemetryFired)
2328
+ return;
2329
+ this.transportSpawnedTelemetryFired = true;
2330
+ const emit = (version) => {
2331
+ try {
2332
+ trackTransportSpawned({ transport, provider: "claude", version }, this.logger);
2333
+ }
2334
+ catch {
2335
+ // belt-and-suspenders: telemetry must never crash the session
2336
+ }
2337
+ };
2338
+ if (this.claudeVersion) {
2339
+ emit(this.claudeVersion);
2340
+ return;
2341
+ }
2342
+ resolveClaudeVersion(this.runtimeSettings)
2343
+ .then((version) => {
2344
+ const resolved = version ?? "unknown";
2345
+ this.claudeVersion = resolved;
2346
+ emit(resolved);
2347
+ return resolved;
2348
+ })
2349
+ .catch(() => {
2350
+ emit("unknown");
2351
+ });
2352
+ }
2353
+ // ──────────────────────────────────────────────────────────────────────────
2354
+ // Rewind anchor bookkeeping. Ported from the SDK-transport implementation;
2355
+ // currently inert under the PTY transport (the PTY path doesn't call the
2356
+ // remember*/resolveConversationRewindTarget paths yet). Restored so the
2357
+ // file compiles end-to-end; the PTY-aware rewind rewrite is tracked in
2358
+ // docs/tech-debt.md. Do not delete without coordinating the rewrite.
2359
+ // ──────────────────────────────────────────────────────────────────────────
2360
+ rememberEmittedUserMessageId(messageId) {
2361
+ if (typeof messageId !== "string" || messageId.length === 0) {
2362
+ return;
2363
+ }
2364
+ this.emittedUserMessageIds.add(messageId);
2365
+ }
2366
+ rememberRewindUserAnchor(userMessageId) {
2367
+ if (typeof userMessageId !== "string" || userMessageId.length === 0) {
2368
+ return;
2369
+ }
2370
+ if (this.rewindTurnAnchors.some((anchor) => anchor.userMessageId === userMessageId)) {
2371
+ return;
2372
+ }
2373
+ this.rewindTurnAnchors.push({
2374
+ userMessageId,
2375
+ assistantMessageId: null,
2376
+ });
2377
+ }
2378
+ rememberRewindAssistantAnchor(assistantMessageId) {
2379
+ if (typeof assistantMessageId !== "string" || assistantMessageId.length === 0) {
2380
+ return;
2381
+ }
2382
+ for (let index = this.rewindTurnAnchors.length - 1; index >= 0; index -= 1) {
2383
+ const anchor = this.rewindTurnAnchors[index];
2384
+ if (!anchor) {
2385
+ continue;
2386
+ }
2387
+ anchor.assistantMessageId = assistantMessageId;
2388
+ return;
2389
+ }
2390
+ }
2391
+ rememberTranscriptProgress(message, messageId) {
2392
+ if (!messageId) {
2393
+ return;
2394
+ }
2395
+ if (message.type === "user" &&
2396
+ !isSyntheticUserEntry(message) &&
2397
+ !isToolResultUserEntry(message)) {
2398
+ this.rememberRewindUserAnchor(messageId);
2399
+ return;
2400
+ }
2401
+ if (message.type === "assistant") {
2402
+ this.rememberRewindAssistantAnchor(messageId);
2403
+ return;
2404
+ }
2405
+ if (message.type === "stream_event") {
2406
+ const event = toObjectRecord(message.event) ?? {};
2407
+ const eventType = readTrimmedString(event.type);
2408
+ if (eventType === "message_start") {
2409
+ this.rememberRewindAssistantAnchor(messageId);
2410
+ }
2411
+ return;
2412
+ }
2413
+ }
2414
+ resolveClaudeMessageId(messageId) {
2415
+ return messageId;
2416
+ }
2417
+ resolveConversationRewindTarget(messageId) {
2418
+ const targetUserMessageId = this.resolveClaudeMessageId(messageId);
2419
+ const index = this.rewindTurnAnchors.findIndex((anchor) => anchor.userMessageId === targetUserMessageId);
2420
+ if (index < 0) {
2421
+ throw new Error(`Claude rewind target ${messageId} is not in the tracked conversation`);
2422
+ }
2423
+ if (index === 0) {
2424
+ return { kind: "fresh-session" };
2425
+ }
2426
+ const previousTurn = this.rewindTurnAnchors[index - 1];
2427
+ if (!previousTurn?.assistantMessageId) {
2428
+ throw new Error(`Claude rewind cannot preserve turn ${index} because its assistant response id was not observed`);
2429
+ }
2430
+ return { kind: "fork", messageId: previousTurn.assistantMessageId };
2431
+ }
2432
+ async ensureQuery() {
2433
+ // Story 4.1 — Cache the resolved transport on first use. Even though the
2434
+ // current path always instantiates SdkTransport, recording the choice means
2435
+ // setMode/setModel/swapAgentProvider can read it without re-probing.
2436
+ this.ensureTransport();
2437
+ if (this.query && !this.queryRestartNeeded) {
2438
+ return this.query;
2439
+ }
2440
+ if (this.queryRestartNeeded && this.query) {
2441
+ const oldQuery = this.query;
2442
+ const oldInput = this.input;
2443
+ // Null out query/input BEFORE awaiting the old iterator's return so the
2444
+ // old pump sees this.query !== activeQuery and skips failActiveTurns.
2445
+ this.query = null;
2446
+ this.input = null;
2447
+ this.queryPumpPromise = null;
2448
+ this.queryRestartNeeded = false;
2449
+ oldInput?.end();
2450
+ oldQuery.close?.();
2451
+ try {
2452
+ await oldQuery.return?.();
2453
+ }
2454
+ catch {
2455
+ /* ignore */
2456
+ }
2457
+ // Tree-kill the old process tree now that the SDK has cleaned up.
2458
+ // If we skip this, MCP children of the previous claude process can
2459
+ // survive as orphans when the session spawns a replacement query.
2460
+ if (this.childProcess) {
2461
+ await terminateWithTreeKill(this.childProcess, {
2462
+ gracefulTimeoutMs: 2000,
2463
+ forceTimeoutMs: 2000,
2464
+ }).catch(() => {
2465
+ /* process may already be dead */
2466
+ });
2467
+ this.childProcess = null;
2468
+ }
2469
+ }
2470
+ // Preserve claudeSessionId across query recreation so buildOptions() passes
2471
+ // resume: sessionId and the new query continues the existing conversation.
2472
+ this.persistence = null;
2473
+ const input = createAsyncMessageInput();
2474
+ const options = await this.buildOptions();
2475
+ this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
2476
+ this.input = input;
2477
+ if (this.runtimeSettings?.transport === "pty") {
2478
+ // Interactive-PTY path (Max-plan billing). Reconstructs the SDKMessage stream from
2479
+ // the session transcript instead of --output-format stream-json. See
2480
+ // pty-session-launcher / PtyQuery for the details.
2481
+ //
2482
+ // OPT-IN ONLY: gated on an EXPLICIT `transport: "pty"` — deliberately NOT on
2483
+ // `resolvedTransport`/"auto", because resolveClaudeTransport(undefined) resolves to
2484
+ // "pty" whenever node-pty is present, which would silently move every agent onto the
2485
+ // (ToS-gray) Max-plan path. Rollout to auto is a separate, later decision.
2486
+ const freshId = this.pendingFreshSessionId;
2487
+ const resumeId = !freshId && this.claudeSessionId ? this.claudeSessionId : undefined;
2488
+ const ptySessionId = freshId ?? resumeId ?? randomUUID();
2489
+ const ptySession = await createPtySession({
2490
+ binary: await this.resolveBinary(),
2491
+ cwd: this.config.cwd,
2492
+ sessionId: ptySessionId,
2493
+ resume: Boolean(resumeId),
2494
+ model: this.config.model,
2495
+ permissionMode: this.currentMode,
2496
+ appendSystemPrompt: this.buildAppendedSystemPrompt(),
2497
+ input: input.iterable,
2498
+ runtimeSettings: this.runtimeSettings,
2499
+ launchEnv: this.launchEnv,
2500
+ mcpServers: this.config.mcpServers
2501
+ ? this.normalizeMcpServers(this.config.mcpServers)
2502
+ : undefined,
2503
+ logger: this.logger,
2504
+ });
2505
+ this.activeTransport = ptySession.transport;
2506
+ this.query = ptySession.query;
2507
+ // Pin the resolved session id so a later query restart resumes this conversation.
2508
+ this.claudeSessionId = ptySessionId;
2509
+ }
2510
+ else {
2511
+ const sdkTransport = new SdkTransport({
2512
+ runtimeSettings: this.runtimeSettings,
2513
+ launchEnv: this.launchEnv,
2514
+ queryFactory: this.queryFactory,
2515
+ logger: this.logger,
2516
+ });
2517
+ this.activeTransport = sdkTransport;
2518
+ this.query = sdkTransport.createQuery({ prompt: input.iterable, options });
2519
+ }
2520
+ // Apply initial flag-settings to the freshly-created query. The options
2521
+ // we passed to createQuery seeded fastMode in settings, but the SDK's
2522
+ // applyFlagSettings is what actually flips the per-turn flag on the live
2523
+ // query; ensure both paths are in sync from turn 1.
2524
+ if (this.config.featureValues?.fast_mode === true) {
2525
+ try {
2526
+ await this.query.applyFlagSettings({ fastMode: true });
2527
+ }
2528
+ catch (err) {
2529
+ this.logger.warn({ err }, "applyFlagSettings({fastMode:true}) failed at ensureQuery");
2530
+ }
2531
+ }
2532
+ // Do not kick off background control-plane queries here. Methods like
2533
+ // supportedCommands()/setPermissionMode() may execute immediately after
2534
+ // ensureQuery() (for listCommands()/setMode()), and sharing the same query
2535
+ // control plane can cause those calls to wait behind supportedModels().
2536
+ return this.query;
2537
+ }
2538
+ async awaitWithTimeout(promise, label) {
2539
+ if (!promise) {
2540
+ this.logger.trace({
2541
+ agentId: this.agentId,
2542
+ provider: "claude",
2543
+ sessionId: this.claudeSessionId,
2544
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2545
+ label,
2546
+ }, "provider.claude.query_operation.skip");
2547
+ return;
2548
+ }
2549
+ const startedAt = Date.now();
2550
+ this.logger.trace({
2551
+ agentId: this.agentId,
2552
+ provider: "claude",
2553
+ sessionId: this.claudeSessionId,
2554
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2555
+ label,
2556
+ }, "provider.claude.query_operation.start");
2557
+ try {
2558
+ await withTimeout(promise, 3000, "timeout");
2559
+ this.logger.trace({
2560
+ agentId: this.agentId,
2561
+ provider: "claude",
2562
+ sessionId: this.claudeSessionId,
2563
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2564
+ label,
2565
+ durationMs: Date.now() - startedAt,
2566
+ }, "provider.claude.query_operation.settled");
2567
+ }
2568
+ catch (error) {
2569
+ this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
2570
+ }
2571
+ }
2572
+ resolveThinkingConfig() {
2573
+ const thinkingOptionId = this.config.thinkingOptionId && this.config.thinkingOptionId !== "default"
2574
+ ? this.config.thinkingOptionId
2575
+ : undefined;
2576
+ if (thinkingOptionId === CLAUDE_ULTRACODE_THINKING_OPTION_ID) {
2577
+ return { thinking: { type: "adaptive" }, effort: "xhigh", ultracode: true };
2578
+ }
2579
+ if (thinkingOptionId && isClaudeThinkingEffort(thinkingOptionId)) {
2580
+ return { thinking: { type: "adaptive" }, effort: thinkingOptionId, ultracode: false };
2581
+ }
2582
+ return { thinking: undefined, effort: undefined, ultracode: false };
2583
+ }
2584
+ buildAppendedSystemPrompt() {
2585
+ return (composeSystemPromptParts(this.config.systemPrompt, this.config.daemonAppendSystemPrompt) ?? "");
2586
+ }
2587
+ buildSdkEnv(extraClaudeOptions) {
2588
+ return createProviderEnv({
2589
+ baseEnv: process.env,
2590
+ runtimeSettings: this.runtimeSettings,
2591
+ overlays: [
2592
+ extraClaudeOptions?.env,
2593
+ {
2594
+ // Increase MCP timeouts for long-running tool calls (10 minutes)
2595
+ MCP_TIMEOUT: "600000",
2596
+ MCP_TOOL_TIMEOUT: "600000",
2597
+ },
2598
+ this.launchEnv,
2599
+ ],
2600
+ });
2601
+ }
2602
+ async buildOptions() {
2603
+ const { thinking, effort, ultracode } = this.resolveThinkingConfig();
2604
+ const appendedSystemPrompt = this.buildAppendedSystemPrompt();
2605
+ const extraClaudeOptions = this.config.extra?.claude;
2606
+ const settingsOptions = this.buildSettingsOptions(extraClaudeOptions, { ultracode });
2607
+ const sdkEnv = this.buildSdkEnv(extraClaudeOptions);
2608
+ assertClaudeAutoModeEligible(this.currentMode, sdkEnv);
2609
+ const claudeBinary = await this.resolveBinary();
2610
+ this.logger.debug({
2611
+ claudeBinary,
2612
+ pathEnvKey: resolvePathEnvKey(),
2613
+ pathIncludesClaudeLocalBin: (process.env["Path"] ?? process.env["PATH"] ?? "")
2614
+ .toLowerCase()
2615
+ .includes("\\.local\\bin"),
2616
+ }, "Resolved Claude executable");
2617
+ const sessionBinding = {};
2618
+ if (this.pendingFreshSessionId) {
2619
+ sessionBinding.sessionId = this.pendingFreshSessionId;
2620
+ }
2621
+ else if (this.claudeSessionId) {
2622
+ sessionBinding.resume = this.claudeSessionId;
2623
+ }
2624
+ const base = {
2625
+ cwd: this.config.cwd,
2626
+ includePartialMessages: true,
2627
+ permissionMode: this.currentMode,
2628
+ // Dynamic mode switching can recreate the underlying Claude query. Keep the
2629
+ // bypass launch capability available so later setPermissionMode("bypassPermissions")
2630
+ // calls do not fail after a model/thinking/rewind-driven restart.
2631
+ allowDangerouslySkipPermissions: true,
2632
+ agents: this.defaults?.agents,
2633
+ canUseTool: this.handlePermissionRequest,
2634
+ pathToClaudeCodeExecutable: claudeBinary,
2635
+ // Use Claude Code preset system prompt and load CLAUDE.md files
2636
+ // Append provider-agnostic system prompts for agents.
2637
+ systemPrompt: {
2638
+ type: "preset",
2639
+ preset: "claude_code",
2640
+ append: appendedSystemPrompt,
2641
+ },
2642
+ settingSources: CLAUDE_SETTING_SOURCES,
2643
+ stderr: (data) => {
2644
+ this.captureStderr(data);
2645
+ this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
2646
+ },
2647
+ // Required for provider-level /rewind support.
2648
+ enableFileCheckpointing: true,
2649
+ // If we have a session ID from a previous query (e.g., after interrupt),
2650
+ // resume that session to continue the conversation history.
2651
+ ...sessionBinding,
2652
+ ...(thinking ? { thinking } : {}),
2653
+ ...(effort ? { effort } : {}),
2654
+ ...extraClaudeOptions,
2655
+ ...settingsOptions,
2656
+ ...(this.persistSession === undefined ? {} : { persistSession: this.persistSession }),
2657
+ env: sdkEnv,
2658
+ };
2659
+ if (this.config.mcpServers) {
2660
+ base.mcpServers = this.normalizeMcpServers(this.config.mcpServers);
2661
+ }
2662
+ if (this.config.model) {
2663
+ base.model = this.config.model;
2664
+ }
2665
+ this.lastOptionsModel = base.model ?? null;
2666
+ if (this.claudeSessionId && !this.pendingFreshSessionId) {
2667
+ base.resume = this.claudeSessionId;
2668
+ }
2669
+ if (this.runtimeSettings?.disallowedTools?.length) {
2670
+ base.disallowedTools = [
2671
+ ...(base.disallowedTools ?? []),
2672
+ ...this.runtimeSettings.disallowedTools,
2673
+ ];
2674
+ }
2675
+ return base;
2676
+ }
2677
+ buildSettingsOptions(extraClaudeOptions, input) {
2678
+ const fastMode = this.resolveFastModeSetting();
2679
+ if (fastMode === null && !input.ultracode) {
2680
+ return {};
2681
+ }
2682
+ return {
2683
+ settings: mergeClaudeSettings(extraClaudeOptions?.settings, {
2684
+ ...(fastMode === null ? {} : { fastMode }),
2685
+ ...(input.ultracode ? { ultracode: true } : {}),
2686
+ }),
2687
+ };
2688
+ }
2689
+ resolveFastModeSetting() {
2690
+ if (!claudeModelSupportsFastMode(this.config.model)) {
2691
+ return null;
2692
+ }
2693
+ return this.config.featureValues?.fast_mode === true;
2694
+ }
2695
+ normalizeMcpServers(servers) {
2696
+ const result = {};
2697
+ for (const [name, config] of Object.entries(servers)) {
2698
+ result[name] = toClaudeSdkMcpConfig(config);
2699
+ }
2700
+ return result;
2701
+ }
2702
+ async toSdkUserMessage(prompt) {
2703
+ const content = [];
2704
+ if (Array.isArray(prompt)) {
2705
+ for (const chunk of prompt) {
2706
+ if (chunk.type === "text") {
2707
+ content.push({ type: "text", text: chunk.text });
2708
+ }
2709
+ else if (chunk.type === "image") {
2710
+ if (isImageMimeType(chunk.mimeType)) {
2711
+ const compressed = await compressImageForApi(chunk.data, chunk.mimeType, this.logger);
2712
+ this.enforceCumulativeImageBudget(compressed.compressedBytes);
2713
+ this.logger.debug({
2714
+ sourceMimeType: chunk.mimeType,
2715
+ originalBytes: compressed.originalBytes,
2716
+ compressedBytes: compressed.compressedBytes,
2717
+ cumulativeBytes: this.cumulativeImageBase64Bytes,
2718
+ }, "Compressed inline image before forwarding to Claude SDK");
2719
+ content.push({
2720
+ type: "image",
2721
+ source: {
2722
+ type: "base64",
2723
+ media_type: compressed.mediaType,
2724
+ data: compressed.data,
2725
+ },
2726
+ });
2727
+ }
2728
+ }
2729
+ else {
2730
+ content.push({ type: "text", text: renderPromptAttachmentAsText(chunk) });
2731
+ }
2732
+ }
2733
+ }
2734
+ else {
2735
+ content.push({ type: "text", text: prompt });
2736
+ }
2737
+ const messageId = randomUUID();
2738
+ this.rememberUserMessageId(messageId);
2739
+ return {
2740
+ type: "user",
2741
+ message: {
2742
+ role: "user",
2743
+ content,
2744
+ },
2745
+ parent_tool_use_id: null,
2746
+ uuid: messageId,
2747
+ session_id: this.claudeSessionId ?? "",
2748
+ };
2749
+ }
2750
+ enforceCumulativeImageBudget(addedBytes) {
2751
+ const projected = this.cumulativeImageBase64Bytes + addedBytes;
2752
+ if (projected > CUMULATIVE_IMAGE_LIMIT_BYTES) {
2753
+ const limitMb = (CUMULATIVE_IMAGE_LIMIT_BYTES / (1024 * 1024)).toFixed(0);
2754
+ const projectedMb = (projected / (1024 * 1024)).toFixed(1);
2755
+ throw new Error(`Aborting image attach: cumulative inline images in this session would reach ${projectedMb}MB, ` +
2756
+ `over the ${limitMb}MB safety limit set to stay under Anthropic's 32MB single-request cap. ` +
2757
+ "Start a new agent session to clear the image history, or send fewer/smaller images in this one.");
2758
+ }
2759
+ this.cumulativeImageBase64Bytes = projected;
2760
+ if (!this.cumulativeImageWarnEmitted &&
2761
+ this.cumulativeImageBase64Bytes > CUMULATIVE_IMAGE_WARN_BYTES) {
2762
+ this.cumulativeImageWarnEmitted = true;
2763
+ this.logger.warn({
2764
+ cumulativeBytes: this.cumulativeImageBase64Bytes,
2765
+ warnThresholdBytes: CUMULATIVE_IMAGE_WARN_BYTES,
2766
+ hardLimitBytes: CUMULATIVE_IMAGE_LIMIT_BYTES,
2767
+ }, "Cumulative inline image bytes nearing per-session safety limit; further images may be rejected");
2768
+ }
2769
+ }
2770
+ transitionTurnState(next, reason) {
2771
+ if (this.turnState === next) {
2772
+ return;
2773
+ }
2774
+ this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
2775
+ this.turnState = next;
2776
+ }
2777
+ syncTurnState(reason) {
2778
+ if (this.activeForegroundTurnId) {
2779
+ this.transitionTurnState("foreground", reason);
2780
+ return;
2781
+ }
2782
+ if (this.autonomousTurn) {
2783
+ this.transitionTurnState("autonomous", reason);
2784
+ return;
2785
+ }
2786
+ this.transitionTurnState("idle", reason);
2787
+ }
2788
+ isAbortError(message) {
2789
+ const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
2790
+ return errors.some((e) => /\baborted\b/i.test(e));
2791
+ }
2792
+ buildTurnFailedEvent(errorMessage) {
2793
+ const normalized = errorMessage.trim() || "Claude run failed";
2794
+ const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
2795
+ const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
2796
+ const diagnostic = this.getRecentStderrDiagnostic();
2797
+ return {
2798
+ type: "turn_failed",
2799
+ provider: "claude",
2800
+ error: normalized,
2801
+ ...(code ? { code } : {}),
2802
+ ...(diagnostic ? { diagnostic } : {}),
2803
+ };
2804
+ }
2805
+ captureStderr(data) {
2806
+ const text = data.trim();
2807
+ if (!text) {
2808
+ return;
2809
+ }
2810
+ const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
2811
+ this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
2812
+ }
2813
+ clearRecentStderr() {
2814
+ this.recentStderr = "";
2815
+ }
2816
+ getRecentStderrDiagnostic() {
2817
+ return this.recentStderr.trim() || undefined;
2818
+ }
2819
+ async awaitRecentStderrAfterProcessExit(error) {
2820
+ if (this.getRecentStderrDiagnostic()) {
2821
+ return;
2822
+ }
2823
+ const message = errorToMessageString(error);
2824
+ if (!/\bprocess exited with code\b/i.test(message) &&
2825
+ !/\bterminated by signal\b/i.test(message)) {
2826
+ return;
2827
+ }
2828
+ const startedAt = Date.now();
2829
+ while (!this.closed && !this.getRecentStderrDiagnostic()) {
2830
+ if (Date.now() - startedAt >= STDERR_FLUSH_WAIT_MS) {
2831
+ return;
2832
+ }
2833
+ await new Promise((resolve) => setTimeout(resolve, STDERR_FLUSH_POLL_INTERVAL_MS));
2834
+ }
2835
+ }
2836
+ createTurnId(owner) {
2837
+ return `${owner}-turn-${this.nextTurnOrdinal++}`;
2838
+ }
2839
+ isTerminalTurnEvent(event) {
2840
+ return (event.type === "turn_completed" ||
2841
+ event.type === "turn_failed" ||
2842
+ event.type === "turn_canceled");
2843
+ }
2844
+ async executeRewindTurn(_turnId, invocation) {
2845
+ this.notifySubscribers({ type: "turn_started", provider: "claude" });
2846
+ try {
2847
+ const rewindAttempt = await this.attemptRewind(invocation.args);
2848
+ if (!rewindAttempt.messageId || !rewindAttempt.result) {
2849
+ this.finishForegroundTurn({
2850
+ type: "turn_failed",
2851
+ provider: "claude",
2852
+ error: rewindAttempt.error ??
2853
+ "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
2854
+ });
2855
+ return;
2856
+ }
2857
+ this.notifySubscribers({
2858
+ type: "timeline",
2859
+ provider: "claude",
2860
+ item: {
2861
+ type: "assistant_message",
2862
+ text: this.buildRewindSuccessMessage(rewindAttempt.messageId, rewindAttempt.result),
2863
+ },
2864
+ });
2865
+ this.finishForegroundTurn({ type: "turn_completed", provider: "claude" });
2866
+ }
2867
+ catch (error) {
2868
+ this.finishForegroundTurn({
2869
+ type: "turn_failed",
2870
+ provider: "claude",
2871
+ error: error instanceof Error ? error.message : "Failed to rewind tracked files",
2872
+ });
2873
+ }
2874
+ }
2875
+ shouldRecoverInterruptedQueryAbort(error, consecutiveRecoveries) {
2876
+ if (consecutiveRecoveries >= 3) {
2877
+ return false;
2878
+ }
2879
+ let message;
2880
+ if (typeof error === "string") {
2881
+ message = error;
2882
+ }
2883
+ else if (error instanceof Error) {
2884
+ message = `${error.message}\n${error.stack ?? ""}`;
2885
+ }
2886
+ else {
2887
+ message = JSON.stringify(error);
2888
+ }
2889
+ return message.toLowerCase().includes("request was aborted");
2890
+ }
2891
+ finishForegroundTurn(event) {
2892
+ if (event.type === "turn_failed" || event.type === "turn_canceled") {
2893
+ this.flushPendingToolCalls();
2894
+ }
2895
+ this.notifySubscribers(event);
2896
+ this.activeForegroundTurnId = null;
2897
+ this.cancelCurrentTurn = null;
2898
+ this.activeTurnHasAssistantText = false;
2899
+ this.syncTurnState("foreground turn terminal");
2900
+ }
2901
+ dispatchEvents(events) {
2902
+ let terminalSeen = false;
2903
+ for (const event of events) {
2904
+ this.notifySubscribers(event);
2905
+ terminalSeen || (terminalSeen = this.isTerminalTurnEvent(event));
2906
+ }
2907
+ if (terminalSeen) {
2908
+ if (this.activeForegroundTurnId) {
2909
+ this.activeForegroundTurnId = null;
2910
+ this.cancelCurrentTurn = null;
2911
+ this.activeTurnHasAssistantText = false;
2912
+ this.syncTurnState("foreground turn terminal");
2913
+ }
2914
+ else if (this.autonomousTurn) {
2915
+ this.autonomousTurn = null;
2916
+ this.activeTurnHasAssistantText = false;
2917
+ this.syncTurnState("autonomous turn terminal");
2918
+ }
2919
+ }
2920
+ }
2921
+ startAutonomousTurn() {
2922
+ if (this.autonomousTurn) {
2923
+ return;
2924
+ }
2925
+ this.autonomousTurn = {
2926
+ id: this.createTurnId("autonomous"),
2927
+ };
2928
+ this.activeTurnHasAssistantText = false;
2929
+ this.contextUsage.beginTurn();
2930
+ this.notifySubscribers({ type: "turn_started", provider: "claude" });
2931
+ this.syncTurnState("autonomous turn started");
2932
+ }
2933
+ completeAutonomousTurn() {
2934
+ if (!this.autonomousTurn) {
2935
+ return;
2936
+ }
2937
+ this.notifySubscribers({ type: "turn_completed", provider: "claude" });
2938
+ this.autonomousTurn = null;
2939
+ this.activeTurnHasAssistantText = false;
2940
+ this.syncTurnState("autonomous turn completed");
2941
+ }
2942
+ failActiveTurns(errorMessage) {
2943
+ const failure = this.buildTurnFailedEvent(errorMessage);
2944
+ this.flushPendingToolCalls();
2945
+ if (this.activeForegroundTurnId) {
2946
+ this.finishForegroundTurn(failure);
2947
+ return;
2948
+ }
2949
+ if (this.autonomousTurn) {
2950
+ this.dispatchEvents([failure]);
2951
+ }
2952
+ }
2953
+ startQueryPump() {
2954
+ if (this.closed || this.queryPumpPromise) {
2955
+ return;
2956
+ }
2957
+ const pump = this.runQueryPump().catch((error) => {
2958
+ this.logger.trace({
2959
+ agentId: this.agentId,
2960
+ provider: "claude",
2961
+ sessionId: this.claudeSessionId,
2962
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2963
+ err: error,
2964
+ }, "provider.claude.query_pump.exit_unexpected");
2965
+ });
2966
+ this.queryPumpPromise = pump;
2967
+ void pump.finally(() => {
2968
+ if (this.queryPumpPromise === pump) {
2969
+ this.queryPumpPromise = null;
2970
+ }
2971
+ });
2972
+ }
2973
+ async runQueryPump() {
2974
+ let activeQuery;
2975
+ try {
2976
+ activeQuery = await this.ensureQuery();
2977
+ }
2978
+ catch (error) {
2979
+ this.logger.trace({
2980
+ agentId: this.agentId,
2981
+ provider: "claude",
2982
+ sessionId: this.claudeSessionId,
2983
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2984
+ err: error,
2985
+ }, "provider.claude.query_pump.init_failed");
2986
+ this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
2987
+ return;
2988
+ }
2989
+ let consecutiveInterruptAbortRecoveries = 0;
2990
+ const logRawMessage = (message) => {
2991
+ this.logger.trace({
2992
+ agentId: this.agentId,
2993
+ provider: "claude",
2994
+ sessionId: this.claudeSessionId,
2995
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
2996
+ messageType: message.type,
2997
+ messageSubtype: "subtype" in message ? message.subtype : undefined,
2998
+ messageUuid: "uuid" in message ? message.uuid : undefined,
2999
+ rawEvent: message,
3000
+ }, "provider.claude.raw_event");
3001
+ };
3002
+ const handlePumpedMessage = async (message) => {
3003
+ logRawMessage(message);
3004
+ consecutiveInterruptAbortRecoveries = 0;
3005
+ if (await this.handleMissingResumedConversation(message, activeQuery)) {
3006
+ return true;
3007
+ }
3008
+ await this.routeSdkMessageFromPump(message);
3009
+ return false;
3010
+ };
3011
+ const drainActiveQuery = async () => {
3012
+ for await (const message of activeQuery) {
3013
+ if (await handlePumpedMessage(message)) {
3014
+ return true;
3015
+ }
3016
+ }
3017
+ return false;
3018
+ };
3019
+ try {
3020
+ while (!this.closed && this.query === activeQuery) {
3021
+ try {
3022
+ if (await drainActiveQuery()) {
3023
+ return;
3024
+ }
3025
+ if (!this.closed && this.query === activeQuery) {
3026
+ this.failActiveTurns("Claude stream ended before terminal result");
3027
+ }
3028
+ return;
3029
+ }
3030
+ catch (error) {
3031
+ if (!this.closed &&
3032
+ this.query === activeQuery &&
3033
+ this.shouldRecoverInterruptedQueryAbort(error, consecutiveInterruptAbortRecoveries)) {
3034
+ consecutiveInterruptAbortRecoveries += 1;
3035
+ this.logger.debug({ recoveries: consecutiveInterruptAbortRecoveries }, "Recovering Claude query pump after interrupt abort");
3036
+ continue;
3037
+ }
3038
+ if (!this.closed && this.query === activeQuery) {
3039
+ await this.awaitRecentStderrAfterProcessExit(error);
3040
+ this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
3041
+ }
3042
+ return;
3043
+ }
3044
+ }
3045
+ }
3046
+ finally {
3047
+ if (this.query === activeQuery) {
3048
+ this.query = null;
3049
+ this.input = null;
3050
+ }
3051
+ }
3052
+ }
3053
+ shouldSuppressStaleResult(message) {
3054
+ // Suppress stale results from interrupted requests. The cancel path already
3055
+ // emitted the terminal event; this result is leftover from the killed API
3056
+ // request. Consume the flag on ANY result so it doesn't linger.
3057
+ if (message.type === "result" && this.pendingInterruptAbort) {
3058
+ this.pendingInterruptAbort = false;
3059
+ if (message.subtype !== "success") {
3060
+ this.logger.debug("Suppressing stale non-success result from interrupted request");
3061
+ return true;
3062
+ }
3063
+ }
3064
+ if (message.type === "result" && message.subtype !== "success" && this.isAbortError(message)) {
3065
+ this.logger.debug("Suppressing abort result by content");
3066
+ return true;
3067
+ }
3068
+ return false;
3069
+ }
3070
+ isAssistantishMessage(message) {
3071
+ return (message.type === "assistant" ||
3072
+ message.type === "stream_event" ||
3073
+ message.type === "tool_progress" ||
3074
+ (message.type === "system" && message.subtype === "task_notification"));
3075
+ }
3076
+ async routeSdkMessageFromPump(message) {
3077
+ if (this.shouldSuppressStaleResult(message)) {
3078
+ return;
3079
+ }
3080
+ const isForeground = Boolean(this.activeForegroundTurnId);
3081
+ if (!isForeground && this.isAssistantishMessage(message)) {
3082
+ this.startAutonomousTurn();
3083
+ }
3084
+ if (!isForeground && !this.autonomousTurn && message.type === "result") {
3085
+ return;
3086
+ }
3087
+ const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? null;
3088
+ const identifiers = readEventIdentifiers(message);
3089
+ this.rememberTranscriptProgress(message, readTranscriptUuid(message));
3090
+ this.logger.trace({
3091
+ agentId: this.agentId,
3092
+ provider: "claude",
3093
+ sessionId: this.claudeSessionId,
3094
+ turnId: turnId ?? undefined,
3095
+ messageType: message.type,
3096
+ identifiers,
3097
+ rawEvent: message,
3098
+ }, "provider.claude.parsed_event");
3099
+ const events = await this.buildPumpedMessageEvents(message, identifiers.messageId, turnId);
3100
+ if (events.length === 0) {
3101
+ return;
3102
+ }
3103
+ if (this.pendingInterruptAbort &&
3104
+ message.type === "result" &&
3105
+ events.some((event) => event.type === "turn_completed" || event.type === "turn_failed") &&
3106
+ (!this.activeForegroundTurnId || !this.foregroundHasVisibleActivity)) {
3107
+ this.pendingInterruptAbort = false;
3108
+ this.logger.debug("Suppressing stale Claude interrupt terminal result");
3109
+ return;
3110
+ }
3111
+ if (events.some((event) => event.type === "timeline" && event.item.type === "assistant_message")) {
3112
+ this.activeTurnHasAssistantText = true;
3113
+ }
3114
+ if (this.activeForegroundTurnId &&
3115
+ events.some((event) => event.type === "timeline" ||
3116
+ event.type === "permission_requested" ||
3117
+ event.type === "permission_resolved")) {
3118
+ this.foregroundHasVisibleActivity = true;
3119
+ }
3120
+ this.dispatchEvents(events);
3121
+ }
3122
+ async buildPumpedMessageEvents(message, messageIdHint, turnId) {
3123
+ const messageEvents = this.translateMessageToEvents(message, {
3124
+ suppressAssistantText: true,
3125
+ suppressReasoning: true,
3126
+ });
3127
+ const assistantTimelineEvents = readClaudeParentToolUseId(message)
3128
+ ? []
3129
+ : this.timelineAssembler
3130
+ .consume({
3131
+ message,
3132
+ runId: turnId,
3133
+ messageIdHint,
3134
+ })
3135
+ .map((item) => ({
3136
+ type: "timeline",
3137
+ item,
3138
+ provider: "claude",
3139
+ }));
3140
+ return [...messageEvents, ...assistantTimelineEvents];
3141
+ }
3142
+ async handleMissingResumedConversation(message, activeQuery) {
3143
+ const staleResumeError = this.readMissingResumedConversationError(message);
3144
+ if (!staleResumeError) {
3145
+ return false;
3146
+ }
3147
+ this.logger.warn({
3148
+ error: staleResumeError,
3149
+ }, "Claude resumed session no longer exists; invalidating persisted session");
3150
+ this.failActiveTurns(staleResumeError);
3151
+ this.input?.end();
3152
+ await this.awaitWithTimeout(activeQuery.return?.(), "query pump return on missing resumed conversation");
3153
+ if (this.query === activeQuery) {
3154
+ this.query = null;
3155
+ this.input = null;
3156
+ }
3157
+ this.persistence = null;
3158
+ this.persistedHistory = [];
3159
+ this.historyPending = false;
3160
+ this.cachedRuntimeInfo = null;
3161
+ this.queryRestartNeeded = false;
3162
+ this.autonomousTurn = null;
3163
+ this.activeForegroundTurnId = null;
3164
+ this.syncTurnState("missing resumed conversation");
3165
+ return true;
3166
+ }
3167
+ async interruptActiveTurn() {
3168
+ const queryToInterrupt = this.query;
3169
+ if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
3170
+ this.logger.trace({
3171
+ agentId: this.agentId,
3172
+ provider: "claude",
3173
+ sessionId: this.claudeSessionId,
3174
+ turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
3175
+ }, "provider.claude.interrupt.no_query");
3176
+ return;
3177
+ }
3178
+ this.pendingInterruptAbort = true;
3179
+ try {
3180
+ await this.awaitWithTimeout(queryToInterrupt.interrupt(), "interruptActiveTurn query.interrupt()");
3181
+ }
3182
+ catch (error) {
3183
+ this.logger.warn({ err: error }, "Failed to interrupt active turn");
3184
+ }
3185
+ }
3186
+ translateMessageToEvents(message, options) {
3187
+ const parentToolUseId = readClaudeParentToolUseId(message);
3188
+ if (parentToolUseId) {
3189
+ return this.sidechainTracker.handleMessage(message, parentToolUseId);
3190
+ }
3191
+ const events = [];
3192
+ if (message.type !== "system") {
3193
+ const sessionCapture = this.captureSessionIdFromMessage(message);
3194
+ if (sessionCapture.notice) {
3195
+ events.push({
3196
+ type: "timeline",
3197
+ provider: "claude",
3198
+ item: sessionCapture.notice,
3199
+ });
3200
+ }
3201
+ if (sessionCapture.threadStartedSessionId) {
3202
+ events.push({
3203
+ type: "thread_started",
3204
+ provider: "claude",
3205
+ sessionId: sessionCapture.threadStartedSessionId,
3206
+ });
3207
+ }
3208
+ }
3209
+ switch (message.type) {
3210
+ case "system":
3211
+ this.appendSystemMessageEvents(message, events);
3212
+ break;
3213
+ case "user":
3214
+ this.appendUserMessageEvents(message, events);
3215
+ break;
3216
+ case "assistant": {
3217
+ const timelineItems = this.mapBlocksToTimeline(message.message.content, {
3218
+ suppressAssistantText: options?.suppressAssistantText ?? false,
3219
+ suppressReasoning: options?.suppressReasoning ?? false,
3220
+ });
3221
+ for (const item of timelineItems) {
3222
+ events.push({ type: "timeline", item, provider: "claude" });
3223
+ }
3224
+ break;
3225
+ }
3226
+ case "stream_event":
3227
+ this.appendStreamEventEvents(message, events, options);
3228
+ break;
3229
+ case "result":
3230
+ this.appendResultEvents(message, events);
3231
+ break;
3232
+ default:
3233
+ break;
3234
+ }
3235
+ return events;
3236
+ }
3237
+ emitSubmittedUserMessage(message, turnId) {
3238
+ const events = [];
3239
+ this.appendUserMessageEvents(message, events);
3240
+ if (events.length === 0) {
3241
+ return;
3242
+ }
3243
+ this.foregroundHasVisibleActivity = true;
3244
+ for (const event of events) {
3245
+ if (event.type === "timeline") {
3246
+ this.notifySubscribers({ ...event, turnId });
3247
+ }
3248
+ else {
3249
+ this.notifySubscribers(event);
3250
+ }
3251
+ }
3252
+ }
3253
+ appendSystemMessageEvents(message, events) {
3254
+ if (message.subtype === "init") {
3255
+ const sessionUpdate = this.handleSystemMessage(message);
3256
+ if (sessionUpdate.notice) {
3257
+ events.push({
3258
+ type: "timeline",
3259
+ provider: "claude",
3260
+ item: sessionUpdate.notice,
3261
+ });
3262
+ }
3263
+ if (sessionUpdate.threadStartedSessionId) {
3264
+ events.push({
3265
+ type: "thread_started",
3266
+ provider: "claude",
3267
+ sessionId: sessionUpdate.threadStartedSessionId,
3268
+ });
3269
+ }
3270
+ return;
3271
+ }
3272
+ if (message.subtype === "status") {
3273
+ const status = toObjectRecord(message)?.status;
3274
+ if (status === "compacting") {
3275
+ this.compacting = true;
3276
+ events.push({
3277
+ type: "timeline",
3278
+ item: { type: "compaction", status: "loading" },
3279
+ provider: "claude",
3280
+ });
3281
+ }
3282
+ return;
3283
+ }
3284
+ if (message.subtype === "compact_boundary") {
3285
+ const compactMetadata = readCompactionMetadata(message);
3286
+ events.push({
3287
+ type: "timeline",
3288
+ item: {
3289
+ type: "compaction",
3290
+ status: "completed",
3291
+ trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
3292
+ preTokens: compactMetadata?.preTokens,
3293
+ },
3294
+ provider: "claude",
3295
+ });
3296
+ events.push(this.contextUsage.buildCompactionUsageEvent(compactMetadata?.postTokens));
3297
+ return;
3298
+ }
3299
+ if (message.subtype === "task_notification") {
3300
+ this.appendTaskNotificationEvents(message, events);
3301
+ return;
3302
+ }
3303
+ if (message.subtype === "task_progress") {
3304
+ return;
3305
+ }
3306
+ }
3307
+ appendTaskNotificationEvents(message, events) {
3308
+ // Expire a tracked background shell when its terminal notification lands.
3309
+ // Runs before the Task-subagent drop below — background shells aren't Task
3310
+ // calls, but feeding here keeps the signal independent of that routing.
3311
+ const lifecycle = extractTaskNotificationLifecycleFromSystemRecord(message);
3312
+ if (lifecycle) {
3313
+ this.backgroundTaskTracker.noteTaskNotification(lifecycle);
3314
+ }
3315
+ // TODO: subagent timelines are best-effort. Subagent task_notifications
3316
+ // arrive without parent_tool_use_id but with tool_use_id pointing at the
3317
+ // the parent's subagent tool call, so they slip past the sidechain router and pollute
3318
+ // the parent timeline. Drop them here; eventually thread them into the
3319
+ // parent tool call's sub_agent log instead.
3320
+ const taskUseId = message.tool_use_id;
3321
+ const cachedTool = taskUseId ? this.toolUseCache.get(taskUseId) : undefined;
3322
+ if (isClaudeSubagentToolName(cachedTool?.name)) {
3323
+ return;
3324
+ }
3325
+ const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
3326
+ if (taskNotificationItem) {
3327
+ events.push({
3328
+ type: "timeline",
3329
+ item: taskNotificationItem,
3330
+ provider: "claude",
3331
+ });
3332
+ }
3333
+ }
3334
+ appendUserMessageEvents(message, events) {
3335
+ if (isSyntheticUserEntry(message)) {
3336
+ return;
3337
+ }
3338
+ if (this.compacting) {
3339
+ this.compacting = false;
3340
+ return;
3341
+ }
3342
+ const messageId = typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
3343
+ if (messageId && this.emittedUserMessageIds.has(messageId)) {
3344
+ return;
3345
+ }
3346
+ this.rememberUserMessageId(messageId);
3347
+ this.rememberEmittedUserMessageId(messageId);
3348
+ const content = message.message?.content;
3349
+ // Expire a tracked background shell when its terminal notification arrives
3350
+ // as user-message content (the transcript path Claude Code uses).
3351
+ const backgroundLifecycle = extractTaskNotificationLifecycleFromUserContent({
3352
+ content,
3353
+ messageId,
3354
+ });
3355
+ if (backgroundLifecycle) {
3356
+ this.backgroundTaskTracker.noteTaskNotification(backgroundLifecycle);
3357
+ }
3358
+ const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
3359
+ content,
3360
+ messageId,
3361
+ });
3362
+ if (taskNotificationItem) {
3363
+ events.push({
3364
+ type: "timeline",
3365
+ item: taskNotificationItem,
3366
+ provider: "claude",
3367
+ });
3368
+ return;
3369
+ }
3370
+ if (typeof content === "string" && content.length > 0) {
3371
+ if (!isClaudeTranscriptNoiseText(content)) {
3372
+ events.push({
3373
+ type: "timeline",
3374
+ item: {
3375
+ type: "user_message",
3376
+ text: content,
3377
+ ...(messageId ? { messageId } : {}),
3378
+ },
3379
+ provider: "claude",
3380
+ });
3381
+ }
3382
+ return;
3383
+ }
3384
+ if (Array.isArray(content)) {
3385
+ this.appendUserContentArrayEvents(content, messageId, events);
3386
+ }
3387
+ }
3388
+ appendUserContentArrayEvents(content, messageId, events) {
3389
+ const timelineItems = this.mapBlocksToTimeline(content, {
3390
+ textMessageType: "user_message",
3391
+ });
3392
+ for (const item of timelineItems) {
3393
+ if (item.type === "user_message" && messageId && !item.messageId) {
3394
+ events.push({
3395
+ type: "timeline",
3396
+ item: { ...item, messageId },
3397
+ provider: "claude",
3398
+ });
3399
+ continue;
3400
+ }
3401
+ events.push({ type: "timeline", item, provider: "claude" });
3402
+ }
3403
+ }
3404
+ appendStreamEventEvents(message, events, options) {
3405
+ const usageUpdatedEvent = this.contextUsage.buildStreamUsageEvent(message.event);
3406
+ if (usageUpdatedEvent) {
3407
+ events.push(usageUpdatedEvent);
3408
+ }
3409
+ const timelineItems = this.mapPartialEvent(message.event, {
3410
+ suppressAssistantText: options?.suppressAssistantText ?? false,
3411
+ suppressReasoning: options?.suppressReasoning ?? false,
3412
+ });
3413
+ for (const item of timelineItems) {
3414
+ events.push({ type: "timeline", item, provider: "claude" });
3415
+ }
3416
+ }
3417
+ appendResultEvents(message, events) {
3418
+ const usage = this.convertUsage(message, message.modelUsage);
3419
+ if (message.subtype === "success") {
3420
+ // Built-in slash commands (e.g. /voice, /usage, "Unknown command: …")
3421
+ // run client-side in the Claude CLI with no model turn — output_tokens
3422
+ // is 0 and the user-visible text is carried in `result`. Surface it only
3423
+ // when the turn has not already emitted assistant text so zero-token
3424
+ // accounting from provider gateways does not duplicate streamed output.
3425
+ const resultText = typeof message.result === "string" ? message.result.trim() : "";
3426
+ const outputTokens = message.usage?.output_tokens;
3427
+ if (resultText.length > 0 && outputTokens === 0 && !this.activeTurnHasAssistantText) {
3428
+ events.push({
3429
+ type: "timeline",
3430
+ provider: "claude",
3431
+ item: {
3432
+ type: "assistant_message",
3433
+ text: resultText,
3434
+ messageId: message.uuid,
3435
+ },
3436
+ });
3437
+ }
3438
+ events.push({ type: "turn_completed", provider: "claude", usage });
3439
+ return;
3440
+ }
3441
+ const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
3442
+ ? message.errors.join("\n")
3443
+ : "Claude run failed";
3444
+ events.push(this.buildTurnFailedEvent(errorMessage));
3445
+ }
3446
+ createClaudeSessionChangedNotice(oldSessionId, newSessionId) {
3447
+ return {
3448
+ type: "assistant_message",
3449
+ text: `Claude switched to a new session: ${oldSessionId} -> ${newSessionId}`,
3450
+ };
3451
+ }
3452
+ captureSessionIdFromMessage(message) {
3453
+ const msgRecord = toObjectRecord(message) ?? {};
3454
+ const sessionId = extractSessionIdRaw({
3455
+ session_id: msgRecord.session_id,
3456
+ sessionId: msgRecord.sessionId,
3457
+ session: isObjectRecord(msgRecord.session) ? { id: msgRecord.session.id } : null,
3458
+ }).trim();
3459
+ if (!sessionId) {
3460
+ return { threadStartedSessionId: null, notice: null };
3461
+ }
3462
+ if (this.claudeSessionId === null) {
3463
+ this.claudeSessionId = sessionId;
3464
+ this.pendingFreshSessionId = null;
3465
+ this.persistence = null;
3466
+ return { threadStartedSessionId: sessionId, notice: null };
3467
+ }
3468
+ if (this.claudeSessionId === sessionId) {
3469
+ this.pendingFreshSessionId = null;
3470
+ return { threadStartedSessionId: null, notice: null };
3471
+ }
3472
+ const oldSessionId = this.claudeSessionId;
3473
+ // Session ID changed mid-stream (e.g. a hook caused Claude to restart
3474
+ // with a new session). Accept the new ID and continue — the turn should
3475
+ // not be failed just because the underlying subprocess cycled.
3476
+ this.logger.warn({ existingSessionId: this.claudeSessionId, newSessionId: sessionId }, "Claude session ID changed in message; accepting new session");
3477
+ this.claudeSessionId = sessionId;
3478
+ this.pendingFreshSessionId = null;
3479
+ this.persistence = null;
3480
+ return {
3481
+ threadStartedSessionId: sessionId,
3482
+ notice: this.createClaudeSessionChangedNotice(oldSessionId, sessionId),
3483
+ };
3484
+ }
3485
+ handleSystemMessage(message) {
3486
+ if (message.subtype !== "init") {
3487
+ return { threadStartedSessionId: null, notice: null };
3488
+ }
3489
+ const msgRecord = toObjectRecord(message) ?? {};
3490
+ const newSessionId = extractSessionIdRaw({
3491
+ session_id: msgRecord.session_id,
3492
+ sessionId: msgRecord.sessionId,
3493
+ session: isObjectRecord(msgRecord.session) ? { id: msgRecord.session.id } : null,
3494
+ }).trim();
3495
+ if (!newSessionId) {
3496
+ return { threadStartedSessionId: null, notice: null };
3497
+ }
3498
+ const existingSessionId = this.claudeSessionId;
3499
+ let threadStartedSessionId = null;
3500
+ let notice = null;
3501
+ if (existingSessionId === null) {
3502
+ this.claudeSessionId = newSessionId;
3503
+ this.pendingFreshSessionId = null;
3504
+ threadStartedSessionId = newSessionId;
3505
+ this.logger.debug({ sessionId: newSessionId }, "Claude session ID set for the first time");
3506
+ }
3507
+ else if (existingSessionId === newSessionId) {
3508
+ this.pendingFreshSessionId = null;
3509
+ this.logger.debug({ sessionId: newSessionId }, "Claude session ID unchanged (same value)");
3510
+ }
3511
+ else {
3512
+ // Session ID changed in an init message (e.g. a hook restarted Claude
3513
+ // with a new session mid-turn). Accept the new ID and continue.
3514
+ this.logger.warn({ existingSessionId, newSessionId }, "Claude session ID changed in init message; accepting new session");
3515
+ this.claudeSessionId = newSessionId;
3516
+ this.pendingFreshSessionId = null;
3517
+ threadStartedSessionId = newSessionId;
3518
+ notice = this.createClaudeSessionChangedNotice(existingSessionId, newSessionId);
3519
+ }
3520
+ this.availableModes = DEFAULT_MODES;
3521
+ this.currentMode = message.permissionMode;
3522
+ if (this.currentMode !== "plan") {
3523
+ this.planResumeMode = this.currentMode;
3524
+ }
3525
+ this.persistence = null;
3526
+ if (message.model) {
3527
+ const normalizedRuntimeModel = normalizeClaudeRuntimeModelId(message.model);
3528
+ this.logger.debug({ runtimeModel: message.model, normalizedRuntimeModel }, "Captured runtime model from SDK init");
3529
+ if (normalizedRuntimeModel) {
3530
+ this.lastOptionsModel = normalizedRuntimeModel;
3531
+ }
3532
+ else if (!this.lastOptionsModel) {
3533
+ this.lastOptionsModel = this.config.model ?? null;
3534
+ }
3535
+ this.lastRuntimeModel = message.model;
3536
+ this.cachedRuntimeInfo = null;
3537
+ }
3538
+ return { threadStartedSessionId, notice };
3539
+ }
3540
+ readMissingResumedConversationError(message) {
3541
+ if (message.type !== "result" || message.subtype !== "error_during_execution") {
3542
+ return null;
3543
+ }
3544
+ if (!this.claudeSessionId) {
3545
+ return null;
3546
+ }
3547
+ const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
3548
+ for (const entry of errors) {
3549
+ if (typeof entry !== "string") {
3550
+ continue;
3551
+ }
3552
+ const match = entry.match(/^No conversation found with session ID:\s*(.+)$/);
3553
+ if (!match) {
3554
+ continue;
3555
+ }
3556
+ if (match[1]?.trim() === this.claudeSessionId) {
3557
+ return entry.trim();
3558
+ }
3559
+ }
3560
+ return null;
3561
+ }
3562
+ convertUsage(message, modelUsage) {
3563
+ return this.contextUsage.buildResultUsage(message, modelUsage);
3564
+ }
3565
+ enqueueTimeline(item) {
3566
+ this.pushEvent({ type: "timeline", item, provider: "claude" });
3567
+ }
3568
+ flushPendingToolCalls() {
3569
+ for (const [id, entry] of this.toolUseCache) {
3570
+ if (entry.started) {
3571
+ this.pushToolCall(mapClaudeCanceledToolCall({
3572
+ name: entry.name,
3573
+ callId: id,
3574
+ input: entry.input ?? null,
3575
+ output: null,
3576
+ }));
3577
+ }
3578
+ }
3579
+ this.toolUseCache.clear();
3580
+ this.sidechainTracker.clear();
3581
+ this.backgroundTaskTracker.clear();
3582
+ }
3583
+ pushToolCall(item, target) {
3584
+ if (!item) {
3585
+ return;
3586
+ }
3587
+ if (target) {
3588
+ target.push(item);
3589
+ return;
3590
+ }
3591
+ this.enqueueTimeline(item);
3592
+ }
3593
+ pushEvent(event) {
3594
+ this.notifySubscribers(event);
3595
+ }
3596
+ notifySubscribers(event) {
3597
+ const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id;
3598
+ const tagged = turnId ? { ...event, turnId } : event;
3599
+ this.logger.trace({
3600
+ agentId: this.agentId,
3601
+ provider: "claude",
3602
+ sessionId: this.claudeSessionId,
3603
+ turnId: getAgentStreamEventTurnId(tagged),
3604
+ event: tagged,
3605
+ }, "provider.claude.event_emit");
3606
+ for (const callback of this.subscribers) {
3607
+ try {
3608
+ callback(tagged);
3609
+ }
3610
+ catch (error) {
3611
+ this.logger.warn({ err: error }, "Subscriber callback threw");
3612
+ }
3613
+ }
3614
+ }
3615
+ normalizePermissionUpdates(updates) {
3616
+ if (!updates || updates.length === 0) {
3617
+ return undefined;
3618
+ }
3619
+ const normalized = updates.filter(isPermissionUpdate);
3620
+ return normalized.length > 0 ? normalized : undefined;
3621
+ }
3622
+ rejectAllPendingPermissions(error) {
3623
+ for (const [id, pending] of this.pendingPermissions) {
3624
+ pending.cleanup?.();
3625
+ pending.reject(error);
3626
+ this.pendingPermissions.delete(id);
3627
+ }
3628
+ }
3629
+ loadPersistedHistory(sessionId) {
3630
+ try {
3631
+ const historyPath = this.resolveHistoryPath(sessionId);
3632
+ if (!historyPath || !fs.existsSync(historyPath)) {
3633
+ return;
3634
+ }
3635
+ this.ingestPersistedHistory(fs.readFileSync(historyPath, "utf8"));
3636
+ }
3637
+ catch {
3638
+ // ignore history load failures
3639
+ }
3640
+ }
3641
+ ingestPersistedHistory(content) {
3642
+ if (!content) {
3643
+ return;
3644
+ }
3645
+ const timeline = [];
3646
+ for (const line of content.split(/\r?\n/)) {
3647
+ this.ingestPersistedHistoryLine(line, timeline);
3648
+ }
3649
+ if (timeline.length > 0) {
3650
+ this.persistedHistory = [...this.persistedHistory, ...timeline];
3651
+ this.historyPending = true;
3652
+ }
3653
+ }
3654
+ ingestPersistedHistoryLine(line, timeline) {
3655
+ const trimmed = line.trim();
3656
+ if (!trimmed) {
3657
+ return;
3658
+ }
3659
+ let entry;
3660
+ try {
3661
+ const parsed = JSON.parse(trimmed);
3662
+ const record = toObjectRecord(parsed);
3663
+ if (!record) {
3664
+ return;
3665
+ }
3666
+ entry = record;
3667
+ }
3668
+ catch {
3669
+ return;
3670
+ }
3671
+ if (entry.isSidechain) {
3672
+ return;
3673
+ }
3674
+ const historyTimestamp = normalizeProviderReplayTimestamp(entry.timestamp);
3675
+ const items = this.convertHistoryEntry(entry);
3676
+ const isVisibleUserEntry = entry.type === "user" &&
3677
+ typeof entry.uuid === "string" &&
3678
+ !isSyntheticHistoryUserEntry(entry) &&
3679
+ !isToolResultUserEntry(entry);
3680
+ if (isVisibleUserEntry && typeof entry.uuid === "string") {
3681
+ this.rememberUserMessageId(entry.uuid);
3682
+ this.rememberRewindUserAnchor(entry.uuid);
3683
+ }
3684
+ if (entry.type === "assistant" && typeof entry.uuid === "string") {
3685
+ this.rememberRewindAssistantAnchor(entry.uuid);
3686
+ }
3687
+ if (items.length > 0) {
3688
+ timeline.push(...items.map((item) => ({
3689
+ item,
3690
+ timestamp: historyTimestamp ?? undefined,
3691
+ })));
3692
+ }
3693
+ }
3694
+ resolveHistoryPath(sessionId) {
3695
+ const cwd = this.config.cwd;
3696
+ if (cwd) {
3697
+ const direct = resolveTranscriptPath({ cwd, sessionId });
3698
+ if (fs.existsSync(direct))
3699
+ return direct;
3700
+ }
3701
+ // The transcript may live under a different project-dir slug than the
3702
+ // current cwd — e.g. the session was created under a now-renamed $HOME
3703
+ // (/Users/marcelo/…) and is resumed from /Users/claudinho/… (or any
3704
+ // fallback cwd). Claude's per-cwd path no longer matches, so scan all
3705
+ // project dirs; without this the resumed chat hydrates empty (blank).
3706
+ return findTranscriptAcrossProjects(sessionId);
3707
+ }
3708
+ convertHistoryEntry(entry) {
3709
+ return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
3710
+ }
3711
+ // Maps Claude content blocks into AgentTimelineItems.
3712
+ //
3713
+ // textMessageType controls what type text blocks emit:
3714
+ // - "assistant_message" (default): one item per text block (streaming granularity)
3715
+ // - "user_message": coalesces all text blocks into a single user_message
3716
+ // (matches extractUserMessageText semantics: trim each block, join with "\n\n")
3717
+ //
3718
+ // suppressAssistantText only applies when textMessageType is "assistant_message" — user text
3719
+ // must never be suppressed since the TimelineAssembler only handles assistant text.
3720
+ //
3721
+ // NOTE: convertClaudeHistoryEntry uses extractUserMessageText directly instead of this function
3722
+ // for user entries. Both paths must produce equivalent user_message items.
3723
+ mapBlocksToTimeline(content, options) {
3724
+ const textMessageType = options?.textMessageType ?? "assistant_message";
3725
+ const suppressText = textMessageType === "assistant_message" && (options?.suppressAssistantText ?? false);
3726
+ const suppressReasoning = options?.suppressReasoning ?? false;
3727
+ if (typeof content === "string") {
3728
+ if (!content ||
3729
+ content === INTERRUPT_TOOL_USE_PLACEHOLDER ||
3730
+ isClaudeTranscriptNoiseText(content)) {
3731
+ return [];
3732
+ }
3733
+ if (suppressText) {
3734
+ return [];
3735
+ }
3736
+ return [{ type: textMessageType, text: content }];
3737
+ }
3738
+ const items = [];
3739
+ // User SDK entries can arrive as multiple text blocks, but Paseo treats them as one message.
3740
+ const userTextParts = [];
3741
+ for (const block of content) {
3742
+ if (!isClaudeContentChunk(block)) {
3743
+ continue;
3744
+ }
3745
+ this.mapBlockToTimeline(block, {
3746
+ items,
3747
+ userTextParts,
3748
+ textMessageType,
3749
+ suppressText,
3750
+ suppressReasoning,
3751
+ });
3752
+ }
3753
+ if (textMessageType === "user_message" && userTextParts.length > 0) {
3754
+ items.unshift({
3755
+ type: "user_message",
3756
+ text: userTextParts.join("\n\n"),
3757
+ });
3758
+ }
3759
+ return items;
3760
+ }
3761
+ appendTextBlockToTimeline(block, context) {
3762
+ const { items, userTextParts, textMessageType, suppressText } = context;
3763
+ const text = typeof block.text === "string" ? block.text : "";
3764
+ if (!text || text === INTERRUPT_TOOL_USE_PLACEHOLDER || isClaudeTranscriptNoiseText(text)) {
3765
+ return;
3766
+ }
3767
+ if (textMessageType === "user_message") {
3768
+ const trimmed = text.trim();
3769
+ if (trimmed) {
3770
+ userTextParts.push(trimmed);
3771
+ }
3772
+ return;
3773
+ }
3774
+ if (!suppressText) {
3775
+ items.push({ type: "assistant_message", text });
3776
+ }
3777
+ }
3778
+ mapBlockToTimeline(block, context) {
3779
+ switch (block.type) {
3780
+ case "text":
3781
+ case "text_delta":
3782
+ this.appendTextBlockToTimeline(block, context);
3783
+ break;
3784
+ case "thinking":
3785
+ case "thinking_delta":
3786
+ if (typeof block.thinking === "string" && block.thinking && !context.suppressReasoning) {
3787
+ context.items.push({ type: "reasoning", text: block.thinking });
3788
+ }
3789
+ break;
3790
+ case "tool_use":
3791
+ case "server_tool_use":
3792
+ case "mcp_tool_use":
3793
+ this.handleToolUseStart(block, context.items);
3794
+ break;
3795
+ case "tool_result":
3796
+ case "mcp_tool_result":
3797
+ case "web_fetch_tool_result":
3798
+ case "web_search_tool_result":
3799
+ case "code_execution_tool_result":
3800
+ case "bash_code_execution_tool_result":
3801
+ case "text_editor_code_execution_tool_result":
3802
+ this.handleToolResult(block, context.items);
3803
+ break;
3804
+ default:
3805
+ break;
3806
+ }
3807
+ }
3808
+ handleToolUseStart(block, items) {
3809
+ const entry = this.upsertToolUseEntry(block);
3810
+ if (!entry) {
3811
+ return;
3812
+ }
3813
+ if (entry.started) {
3814
+ return;
3815
+ }
3816
+ entry.started = true;
3817
+ this.toolUseCache.set(entry.id, entry);
3818
+ this.pushToolCall(mapClaudeRunningToolCall({
3819
+ name: entry.name,
3820
+ callId: entry.id,
3821
+ input: entry.input ?? this.normalizeToolInput(block.input) ?? null,
3822
+ output: null,
3823
+ }), items);
3824
+ }
3825
+ handleToolResult(block, items) {
3826
+ const entry = typeof block.tool_use_id === "string" ? this.toolUseCache.get(block.tool_use_id) : undefined;
3827
+ const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
3828
+ const toolName = entry?.name ?? blockToolName ?? "tool";
3829
+ const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
3830
+ ? block.tool_use_id
3831
+ : (entry?.id ?? null);
3832
+ // Pull image blocks out of the result so base64 never reaches the tool output, and render each
3833
+ // one as an assistant_message markdown image after the tool_call (matching how Codex emits).
3834
+ const { images, text } = splitClaudeToolResultImages(block.content);
3835
+ const output = this.buildToolOutput(text, block, entry);
3836
+ this.trackBackgroundShellStart(block, toolName);
3837
+ if (block.is_error) {
3838
+ this.pushToolCall(mapClaudeFailedToolCall({
3839
+ name: toolName,
3840
+ callId,
3841
+ input: entry?.input ?? null,
3842
+ output: output ?? null,
3843
+ error: { ...block, content: text },
3844
+ }), items);
3845
+ }
3846
+ else {
3847
+ this.pushToolCall(mapClaudeCompletedToolCall({
3848
+ name: toolName,
3849
+ callId,
3850
+ input: entry?.input ?? null,
3851
+ output: output ?? null,
3852
+ }), items);
3853
+ }
3854
+ for (const image of images) {
3855
+ const imageItem = renderProviderImageOutputAsAssistantMarkdown(image, {
3856
+ materialize: materializeProviderImage,
3857
+ });
3858
+ if (imageItem) {
3859
+ items.push(imageItem);
3860
+ }
3861
+ }
3862
+ if (typeof block.tool_use_id === "string") {
3863
+ this.toolUseCache.delete(block.tool_use_id);
3864
+ this.sidechainTracker.delete(block.tool_use_id);
3865
+ }
3866
+ }
3867
+ /**
3868
+ * A backgrounded Bash ("Bash" with run_in_background) returns its handle
3869
+ * immediately — "Command running in background with ID: <id>" — so its
3870
+ * tool_result is the START signal for a shell that will outlive the turn.
3871
+ */
3872
+ trackBackgroundShellStart(block, toolName) {
3873
+ if (toolName !== "Bash" || block.is_error) {
3874
+ return;
3875
+ }
3876
+ const resultText = typeof block.content === "string" ? block.content : JSON.stringify(block.content ?? "");
3877
+ this.backgroundTaskTracker.noteToolResultText(resultText);
3878
+ }
3879
+ buildToolOutput(content, block, entry) {
3880
+ if (block.is_error) {
3881
+ return undefined;
3882
+ }
3883
+ const blockServer = typeof block.server === "string" ? block.server : undefined;
3884
+ const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
3885
+ const server = entry?.server ?? blockServer ?? "tool";
3886
+ const tool = entry?.name ?? blockToolName ?? "tool";
3887
+ const coercedContent = coerceToolResultContentToString(content);
3888
+ const input = entry?.input;
3889
+ // Build structured result based on tool type
3890
+ const structured = this.buildStructuredToolResult(server, tool, coercedContent, input);
3891
+ if (structured) {
3892
+ return structured;
3893
+ }
3894
+ // Fallback format - try to parse JSON first
3895
+ const result = {};
3896
+ if (coercedContent.length > 0) {
3897
+ try {
3898
+ // If content is a JSON string, parse it
3899
+ result.output = JSON.parse(coercedContent);
3900
+ }
3901
+ catch {
3902
+ // If not JSON, return unchanged (no extra wrapping)
3903
+ result.output = coercedContent;
3904
+ }
3905
+ }
3906
+ // Preserve file changes tracked during tool execution
3907
+ if (entry?.files?.length) {
3908
+ result.files = entry.files;
3909
+ }
3910
+ return Object.keys(result).length > 0 ? result : undefined;
3911
+ }
3912
+ isCommandExecutionTool(normalizedServer, normalizedTool, input) {
3913
+ if (normalizedServer.includes("bash") ||
3914
+ normalizedServer.includes("shell") ||
3915
+ normalizedServer.includes("command")) {
3916
+ return true;
3917
+ }
3918
+ if (normalizedTool.includes("bash") ||
3919
+ normalizedTool.includes("shell") ||
3920
+ normalizedTool.includes("command")) {
3921
+ return true;
3922
+ }
3923
+ return Boolean(input && (typeof input.command === "string" || Array.isArray(input.command)));
3924
+ }
3925
+ static isFileWriteTool(normalizedTool) {
3926
+ return (normalizedTool.includes("write") ||
3927
+ normalizedTool === "write_file" ||
3928
+ normalizedTool === "create_file");
3929
+ }
3930
+ static isFileEditTool(normalizedTool) {
3931
+ return (normalizedTool.includes("edit") ||
3932
+ normalizedTool.includes("patch") ||
3933
+ normalizedTool === "apply_patch" ||
3934
+ normalizedTool === "apply_diff");
3935
+ }
3936
+ static isFileReadTool(normalizedTool) {
3937
+ return (normalizedTool.includes("read") ||
3938
+ normalizedTool === "read_file" ||
3939
+ normalizedTool === "view_file");
3940
+ }
3941
+ buildStructuredToolResult(server, tool, output, input) {
3942
+ const normalizedServer = server.toLowerCase();
3943
+ const normalizedTool = tool.toLowerCase();
3944
+ if (this.isCommandExecutionTool(normalizedServer, normalizedTool, input)) {
3945
+ const command = this.extractCommandText(input ?? {}) ?? "command";
3946
+ return {
3947
+ type: "command",
3948
+ command,
3949
+ output,
3950
+ cwd: typeof input?.cwd === "string" ? input.cwd : undefined,
3951
+ };
3952
+ }
3953
+ if (ClaudeAgentSession.isFileWriteTool(normalizedTool) &&
3954
+ input &&
3955
+ typeof input.file_path === "string") {
3956
+ return {
3957
+ type: "file_write",
3958
+ filePath: input.file_path,
3959
+ oldContent: "",
3960
+ newContent: typeof input.content === "string" ? input.content : output,
3961
+ };
3962
+ }
3963
+ if (ClaudeAgentSession.isFileEditTool(normalizedTool) &&
3964
+ input &&
3965
+ typeof input.file_path === "string") {
3966
+ // Support both old_str/new_str and old_string/new_string parameter names
3967
+ const oldContent = firstStringField(input, "old_str", "old_string");
3968
+ const newContent = firstStringField(input, "new_str", "new_string");
3969
+ const diff = firstStringField(input, "patch", "diff");
3970
+ return {
3971
+ type: "file_edit",
3972
+ filePath: input.file_path,
3973
+ diff,
3974
+ oldContent,
3975
+ newContent,
3976
+ };
3977
+ }
3978
+ if (ClaudeAgentSession.isFileReadTool(normalizedTool) &&
3979
+ input &&
3980
+ typeof input.file_path === "string") {
3981
+ return {
3982
+ type: "file_read",
3983
+ filePath: input.file_path,
3984
+ content: output,
3985
+ };
3986
+ }
3987
+ return undefined;
3988
+ }
3989
+ updatePartialEventToolState(event) {
3990
+ if (event.type === "content_block_start") {
3991
+ const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
3992
+ if (block?.type === "tool_use" &&
3993
+ typeof event.index === "number" &&
3994
+ typeof block.id === "string") {
3995
+ this.toolUseIndexToId.set(event.index, block.id);
3996
+ this.toolUseInputBuffers.delete(block.id);
3997
+ }
3998
+ return false;
3999
+ }
4000
+ if (event.type === "content_block_delta") {
4001
+ const delta = isClaudeContentChunk(event.delta) ? event.delta : null;
4002
+ if (delta?.type === "input_json_delta") {
4003
+ const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : undefined;
4004
+ this.handleToolInputDelta(event.index, partialJson);
4005
+ return true;
4006
+ }
4007
+ return false;
4008
+ }
4009
+ if (event.type === "content_block_stop" && typeof event.index === "number") {
4010
+ const toolId = this.toolUseIndexToId.get(event.index);
4011
+ if (toolId) {
4012
+ this.toolUseIndexToId.delete(event.index);
4013
+ this.toolUseInputBuffers.delete(toolId);
4014
+ }
4015
+ }
4016
+ return false;
4017
+ }
4018
+ mapPartialEvent(event, options) {
4019
+ if (this.updatePartialEventToolState(event)) {
4020
+ return [];
4021
+ }
4022
+ switch (event.type) {
4023
+ case "content_block_start":
4024
+ return isClaudeContentChunk(event.content_block)
4025
+ ? this.mapBlocksToTimeline([event.content_block], {
4026
+ suppressAssistantText: options?.suppressAssistantText,
4027
+ suppressReasoning: options?.suppressReasoning,
4028
+ })
4029
+ : [];
4030
+ case "content_block_delta":
4031
+ return isClaudeContentChunk(event.delta)
4032
+ ? this.mapBlocksToTimeline([event.delta], {
4033
+ suppressAssistantText: options?.suppressAssistantText,
4034
+ suppressReasoning: options?.suppressReasoning,
4035
+ })
4036
+ : [];
4037
+ default:
4038
+ return [];
4039
+ }
4040
+ }
4041
+ upsertToolUseEntry(block) {
4042
+ const id = typeof block.id === "string" ? block.id : undefined;
4043
+ if (!id) {
4044
+ return null;
4045
+ }
4046
+ const existing = this.toolUseCache.get(id) ?? createDefaultToolUseCacheEntry(id, block);
4047
+ if (typeof block.name === "string" && block.name.length > 0) {
4048
+ existing.name = block.name;
4049
+ }
4050
+ if (typeof block.server === "string" && block.server.length > 0) {
4051
+ existing.server = block.server;
4052
+ }
4053
+ else if (!existing.server) {
4054
+ existing.server = existing.name;
4055
+ }
4056
+ if (block.type === "tool_use" ||
4057
+ block.type === "mcp_tool_use" ||
4058
+ block.type === "server_tool_use") {
4059
+ const input = this.normalizeToolInput(block.input);
4060
+ if (input) {
4061
+ this.applyToolInput(existing, input);
4062
+ }
4063
+ }
4064
+ this.toolUseCache.set(id, existing);
4065
+ return existing;
4066
+ }
4067
+ handleToolInputDelta(index, partialJson) {
4068
+ if (typeof index !== "number" || typeof partialJson !== "string") {
4069
+ return;
4070
+ }
4071
+ const toolId = this.toolUseIndexToId.get(index);
4072
+ if (!toolId) {
4073
+ return;
4074
+ }
4075
+ const buffer = (this.toolUseInputBuffers.get(toolId) ?? "") + partialJson;
4076
+ this.toolUseInputBuffers.set(toolId, buffer);
4077
+ const entry = this.toolUseCache.get(toolId);
4078
+ const parsed = parsePartialJsonObject(buffer);
4079
+ if (!entry || !parsed) {
4080
+ return;
4081
+ }
4082
+ const normalized = this.normalizeToolInput(parsed.value);
4083
+ if (!normalized) {
4084
+ return;
4085
+ }
4086
+ if (!parsed.complete && Object.keys(normalized).length === 0) {
4087
+ return;
4088
+ }
4089
+ if (this.areToolInputsEqual(entry.input ?? undefined, normalized)) {
4090
+ return;
4091
+ }
4092
+ this.applyToolInput(entry, normalized);
4093
+ this.toolUseCache.set(toolId, entry);
4094
+ this.pushToolCall(mapClaudeRunningToolCall({
4095
+ name: entry.name,
4096
+ callId: toolId,
4097
+ input: normalized,
4098
+ output: null,
4099
+ }));
4100
+ }
4101
+ normalizeToolInput(input) {
4102
+ if (!isMetadata(input)) {
4103
+ return null;
4104
+ }
4105
+ return input;
4106
+ }
4107
+ areToolInputsEqual(left, right) {
4108
+ if (!left) {
4109
+ return false;
4110
+ }
4111
+ const leftKeys = Object.keys(left);
4112
+ const rightKeys = Object.keys(right);
4113
+ if (leftKeys.length !== rightKeys.length) {
4114
+ return false;
4115
+ }
4116
+ return rightKeys.every((key) => left[key] === right[key]);
4117
+ }
4118
+ applyToolInput(entry, input) {
4119
+ entry.input = input;
4120
+ if (this.isCommandTool(entry.name, input)) {
4121
+ entry.classification = "command";
4122
+ entry.commandText = this.extractCommandText(input) ?? entry.commandText;
4123
+ }
4124
+ else {
4125
+ const files = this.extractFileChanges(input);
4126
+ if (files?.length) {
4127
+ entry.classification = "file_change";
4128
+ entry.files = files;
4129
+ }
4130
+ }
4131
+ }
4132
+ isCommandTool(name, input) {
4133
+ const normalized = name.toLowerCase();
4134
+ if (normalized.includes("bash") ||
4135
+ normalized.includes("shell") ||
4136
+ normalized.includes("terminal") ||
4137
+ normalized.includes("command")) {
4138
+ return true;
4139
+ }
4140
+ if (typeof input.command === "string" || Array.isArray(input.command)) {
4141
+ return true;
4142
+ }
4143
+ return false;
4144
+ }
4145
+ extractCommandText(input) {
4146
+ const command = input.command;
4147
+ if (typeof command === "string" && command.length > 0) {
4148
+ return command;
4149
+ }
4150
+ if (Array.isArray(command)) {
4151
+ const tokens = command.filter((value) => typeof value === "string");
4152
+ if (tokens.length > 0) {
4153
+ return tokens.join(" ");
4154
+ }
4155
+ }
4156
+ if (typeof input.description === "string" && input.description.length > 0) {
4157
+ return input.description;
4158
+ }
4159
+ return undefined;
4160
+ }
4161
+ extractFileChanges(input) {
4162
+ if (typeof input.file_path === "string" && input.file_path.length > 0) {
4163
+ const relative = this.relativizePath(input.file_path);
4164
+ if (relative) {
4165
+ return [{ path: relative, kind: this.detectFileKind(input.file_path) }];
4166
+ }
4167
+ }
4168
+ if (typeof input.patch === "string" && input.patch.length > 0) {
4169
+ const files = this.parsePatchFileList(input.patch);
4170
+ if (files.length > 0) {
4171
+ return files.map((entry) => ({
4172
+ path: this.relativizePath(entry.path) ?? entry.path,
4173
+ kind: entry.kind,
4174
+ }));
4175
+ }
4176
+ }
4177
+ if (Array.isArray(input.files)) {
4178
+ const files = [];
4179
+ for (const value of input.files) {
4180
+ if (typeof value === "string" && value.length > 0) {
4181
+ files.push({
4182
+ path: this.relativizePath(value) ?? value,
4183
+ kind: this.detectFileKind(value),
4184
+ });
4185
+ }
4186
+ }
4187
+ if (files.length > 0) {
4188
+ return files;
4189
+ }
4190
+ }
4191
+ return undefined;
4192
+ }
4193
+ detectFileKind(filePath) {
4194
+ try {
4195
+ return fs.existsSync(filePath) ? "update" : "add";
4196
+ }
4197
+ catch {
4198
+ return "update";
4199
+ }
4200
+ }
4201
+ relativizePath(target) {
4202
+ if (!target) {
4203
+ return undefined;
4204
+ }
4205
+ const cwd = this.config.cwd;
4206
+ if (cwd && target.startsWith(cwd)) {
4207
+ const relative = path.relative(cwd, target);
4208
+ return relative.length > 0 ? relative : path.basename(target);
4209
+ }
4210
+ return target;
4211
+ }
4212
+ parsePatchFileList(patch) {
4213
+ const files = [];
4214
+ const seen = new Set();
4215
+ for (const line of patch.split(/\r?\n/)) {
4216
+ const trimmed = line.trim();
4217
+ let kind = null;
4218
+ let parsedPath = null;
4219
+ if (trimmed.startsWith("*** Add File:")) {
4220
+ kind = "add";
4221
+ parsedPath = trimmed.replace("*** Add File:", "").trim();
4222
+ }
4223
+ else if (trimmed.startsWith("*** Delete File:")) {
4224
+ kind = "delete";
4225
+ parsedPath = trimmed.replace("*** Delete File:", "").trim();
4226
+ }
4227
+ else if (trimmed.startsWith("*** Update File:")) {
4228
+ kind = "update";
4229
+ parsedPath = trimmed.replace("*** Update File:", "").trim();
4230
+ }
4231
+ if (kind && parsedPath && !seen.has(`${kind}:${parsedPath}`)) {
4232
+ seen.add(`${kind}:${parsedPath}`);
4233
+ files.push({ path: parsedPath, kind });
4234
+ }
4235
+ }
4236
+ return files;
4237
+ }
4238
+ }
4239
+ function hasToolLikeBlock(block) {
4240
+ if (!block || typeof block !== "object") {
4241
+ return false;
4242
+ }
4243
+ const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
4244
+ return type.includes("tool");
4245
+ }
4246
+ function readCompactionMetadata(source) {
4247
+ const sourceRecord = toObjectRecord(source);
4248
+ if (!sourceRecord) {
4249
+ return null;
4250
+ }
4251
+ const candidates = [
4252
+ sourceRecord.compact_metadata,
4253
+ sourceRecord.compactMetadata,
4254
+ sourceRecord.compactionMetadata,
4255
+ ];
4256
+ for (const candidate of candidates) {
4257
+ const metadata = toObjectRecord(candidate);
4258
+ if (!metadata) {
4259
+ continue;
4260
+ }
4261
+ const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
4262
+ const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
4263
+ const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
4264
+ const postTokensRaw = metadata.postTokens ?? metadata.post_tokens;
4265
+ const postTokens = typeof postTokensRaw === "number" ? postTokensRaw : undefined;
4266
+ return { trigger, preTokens, postTokens };
4267
+ }
4268
+ return null;
4269
+ }
4270
+ function normalizeHistoryBlocks(content) {
4271
+ if (Array.isArray(content)) {
4272
+ const blocks = content.filter((entry) => isClaudeContentChunk(entry));
4273
+ return blocks.length > 0 ? blocks : null;
4274
+ }
4275
+ if (isClaudeContentChunk(content)) {
4276
+ return [content];
4277
+ }
4278
+ return null;
4279
+ }
4280
+ function mapAssistantHistoryBlocksWithMessageId(entry, content, mapBlocks) {
4281
+ const items = mapBlocks(content);
4282
+ const assistantMessageId = typeof entry.uuid === "string" && entry.uuid.length > 0 ? entry.uuid : null;
4283
+ if (!assistantMessageId) {
4284
+ return items;
4285
+ }
4286
+ for (const item of items) {
4287
+ if (item.type === "assistant_message" && !item.messageId) {
4288
+ item.messageId = assistantMessageId;
4289
+ }
4290
+ }
4291
+ return items;
4292
+ }
4293
+ function convertClaudeHistoryEntryPreamble(entry) {
4294
+ if (entry.type === "system" && entry.subtype === "compact_boundary") {
4295
+ const compactMetadata = readCompactionMetadata(entry);
4296
+ return {
4297
+ shortCircuit: [
4298
+ {
4299
+ type: "compaction",
4300
+ status: "completed",
4301
+ trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
4302
+ preTokens: compactMetadata?.preTokens,
4303
+ },
4304
+ ],
4305
+ };
4306
+ }
4307
+ const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
4308
+ if (taskNotificationItem) {
4309
+ return { shortCircuit: [taskNotificationItem] };
4310
+ }
4311
+ if (entry.isCompactSummary) {
4312
+ return { shortCircuit: [] };
4313
+ }
4314
+ if (entry.type === "user" && isSyntheticHistoryUserEntry(entry)) {
4315
+ return { shortCircuit: [] };
4316
+ }
4317
+ const message = entry?.message;
4318
+ if (!message || !("content" in message)) {
4319
+ return { shortCircuit: [] };
4320
+ }
4321
+ const content = message.content;
4322
+ if ((entry.type === "user" || entry.type === "assistant") &&
4323
+ isClaudeTranscriptNoiseContent(content)) {
4324
+ return { shortCircuit: [] };
4325
+ }
4326
+ return { proceed: { content } };
4327
+ }
4328
+ function isProviderImageMessage(item) {
4329
+ return item.type === "assistant_message" && isProviderImageMarkdown(item.text);
4330
+ }
4331
+ export function convertClaudeHistoryEntry(entry, mapBlocks) {
4332
+ const preamble = convertClaudeHistoryEntryPreamble(entry);
4333
+ if ("shortCircuit" in preamble) {
4334
+ return preamble.shortCircuit;
4335
+ }
4336
+ const { content } = preamble.proceed;
4337
+ const normalizedBlocks = normalizeHistoryBlocks(content);
4338
+ const contentValue = typeof content === "string" ? content : normalizedBlocks;
4339
+ const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
4340
+ const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
4341
+ ? entry.uuid
4342
+ : null;
4343
+ if (entry.type === "user") {
4344
+ const userTaskNotificationItem = mapTaskNotificationUserContentToToolCall({
4345
+ content,
4346
+ messageId: userMessageId,
4347
+ });
4348
+ if (userTaskNotificationItem) {
4349
+ return [userTaskNotificationItem];
4350
+ }
4351
+ }
4352
+ const timeline = [];
4353
+ if (entry.type === "user") {
4354
+ const text = extractUserMessageText(content);
4355
+ if (text) {
4356
+ timeline.push({
4357
+ type: "user_message",
4358
+ text,
4359
+ ...(userMessageId ? { messageId: userMessageId } : {}),
4360
+ });
4361
+ }
4362
+ }
4363
+ if (hasToolBlock && normalizedBlocks) {
4364
+ const mapped = mapBlocks(normalizedBlocks);
4365
+ if (entry.type === "user") {
4366
+ // tool_result handling (handleToolResult) emits image markdown as an assistant_message
4367
+ // alongside the tool_call. User-entry text blocks also map to assistant_message in this path
4368
+ // and must stay suppressed, so keep tool_calls plus only the image assistant_messages.
4369
+ const toolItems = mapped.filter((item) => item.type === "tool_call" || isProviderImageMessage(item));
4370
+ return timeline.length ? [...timeline, ...toolItems] : toolItems;
4371
+ }
4372
+ return mapped;
4373
+ }
4374
+ if (entry.type === "assistant" && contentValue) {
4375
+ return mapAssistantHistoryBlocksWithMessageId(entry, contentValue, mapBlocks);
4376
+ }
4377
+ return timeline;
4378
+ }
4379
+ function createAsyncMessageInput() {
4380
+ const queue = [];
4381
+ const resolvers = [];
4382
+ let closed = false;
4383
+ return {
4384
+ push(item) {
4385
+ if (closed) {
4386
+ return;
4387
+ }
4388
+ const resolve = resolvers.shift();
4389
+ if (resolve) {
4390
+ resolve({ value: item, done: false });
4391
+ return;
4392
+ }
4393
+ queue.push(item);
4394
+ },
4395
+ end() {
4396
+ closed = true;
4397
+ while (resolvers.length > 0) {
4398
+ const resolve = resolvers.shift();
4399
+ resolve?.({ value: undefined, done: true });
4400
+ }
4401
+ },
4402
+ iterable: {
4403
+ [Symbol.asyncIterator]() {
4404
+ return {
4405
+ next: () => {
4406
+ if (queue.length > 0) {
4407
+ const value = queue.shift();
4408
+ if (value !== undefined) {
4409
+ return Promise.resolve({ value, done: false });
4410
+ }
4411
+ }
4412
+ if (closed) {
4413
+ return Promise.resolve({ value: undefined, done: true });
4414
+ }
4415
+ return new Promise((resolve) => {
4416
+ resolvers.push(resolve);
4417
+ });
4418
+ },
4419
+ };
4420
+ },
4421
+ },
4422
+ };
4423
+ }
4424
+ async function pathExists(target) {
4425
+ try {
4426
+ await fsPromises.access(target);
4427
+ return true;
4428
+ }
4429
+ catch {
4430
+ return false;
4431
+ }
4432
+ }
4433
+ async function collectRecentClaudeSessions(root, limit) {
4434
+ let projectDirs;
4435
+ try {
4436
+ projectDirs = await fsPromises.readdir(root);
4437
+ }
4438
+ catch {
4439
+ return [];
4440
+ }
4441
+ const projectFileLists = await Promise.all(projectDirs.map(async (dirName) => {
4442
+ const projectPath = path.join(root, dirName);
4443
+ try {
4444
+ const stats = await fsPromises.stat(projectPath);
4445
+ if (!stats.isDirectory())
4446
+ return { projectPath, files: [] };
4447
+ const files = await fsPromises.readdir(projectPath);
4448
+ return { projectPath, files };
4449
+ }
4450
+ catch {
4451
+ return { projectPath, files: [] };
4452
+ }
4453
+ }));
4454
+ const fileEntries = projectFileLists.flatMap(({ projectPath, files }) => files.filter((f) => f.endsWith(".jsonl")).map((f) => path.join(projectPath, f)));
4455
+ const statResults = await Promise.all(fileEntries.map(async (fullPath) => {
4456
+ try {
4457
+ const fileStats = await fsPromises.stat(fullPath);
4458
+ return { path: fullPath, mtime: fileStats.mtime };
4459
+ }
4460
+ catch {
4461
+ return null;
4462
+ }
4463
+ }));
4464
+ const candidates = statResults.filter((entry) => entry !== null);
4465
+ return candidates.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).slice(0, limit);
4466
+ }
4467
+ function isFinishedAccumulator(acc) {
4468
+ return Boolean(acc.sessionId && acc.cwd && acc.title);
4469
+ }
4470
+ function applyClaudeSessionEntryToAccumulator(entryRaw, acc) {
4471
+ const entry = toObjectRecord(entryRaw);
4472
+ if (!entry) {
4473
+ return;
4474
+ }
4475
+ if (entry.isSidechain) {
4476
+ return;
4477
+ }
4478
+ if (entry.type === "user" && isSyntheticUserEntry(entry)) {
4479
+ return;
4480
+ }
4481
+ if (!acc.sessionId && typeof entry.sessionId === "string") {
4482
+ acc.sessionId = entry.sessionId;
4483
+ }
4484
+ if (!acc.cwd && typeof entry.cwd === "string") {
4485
+ acc.cwd = entry.cwd;
4486
+ }
4487
+ if (entry.type === "user" && entry.message) {
4488
+ const text = extractClaudeUserText(entry.message);
4489
+ if (text) {
4490
+ if (!acc.title) {
4491
+ acc.title = text;
4492
+ }
4493
+ const preview = normalizeImportablePromptPreview(text);
4494
+ acc.firstPromptPreview ?? (acc.firstPromptPreview = preview);
4495
+ acc.lastPromptPreview = preview;
4496
+ }
4497
+ return;
4498
+ }
4499
+ }
4500
+ async function parseClaudeSessionDescriptor(filePath, mtime) {
4501
+ let content;
4502
+ try {
4503
+ content = await fsPromises.readFile(filePath, "utf8");
4504
+ }
4505
+ catch {
4506
+ return null;
4507
+ }
4508
+ const acc = {
4509
+ sessionId: null,
4510
+ cwd: null,
4511
+ title: null,
4512
+ firstPromptPreview: null,
4513
+ lastPromptPreview: null,
4514
+ };
4515
+ for (const rawLine of content.split(/\r?\n/)) {
4516
+ const line = rawLine.trim();
4517
+ if (!line)
4518
+ continue;
4519
+ let entry;
4520
+ try {
4521
+ entry = JSON.parse(line);
4522
+ }
4523
+ catch {
4524
+ continue;
4525
+ }
4526
+ applyClaudeSessionEntryToAccumulator(entry, acc);
4527
+ if (isFinishedAccumulator(acc)) {
4528
+ break;
4529
+ }
4530
+ }
4531
+ const { sessionId, cwd, title } = acc;
4532
+ if (!sessionId || !cwd) {
4533
+ return null;
4534
+ }
4535
+ return {
4536
+ providerHandleId: sessionId,
4537
+ cwd,
4538
+ title: (title ?? "").trim() || `Claude session ${sessionId.slice(0, 8)}`,
4539
+ firstPromptPreview: acc.firstPromptPreview,
4540
+ lastPromptPreview: acc.lastPromptPreview,
4541
+ lastActivityAt: mtime,
4542
+ };
4543
+ }
4544
+ function normalizeImportablePromptPreview(text) {
4545
+ const normalized = text.trim().replace(/\s+/g, " ");
4546
+ if (!normalized)
4547
+ return null;
4548
+ return normalized.length > 160 ? normalized.slice(0, 160) : normalized;
4549
+ }
4550
+ function normalizeClaudeUserPromptText(text) {
4551
+ const normalized = text.trim();
4552
+ if (!CLAUDE_COMMAND_MESSAGE_PATTERN.test(normalized)) {
4553
+ return normalized || null;
4554
+ }
4555
+ const command = readClaudeCommandPromptName(normalized);
4556
+ if (!command) {
4557
+ return null;
4558
+ }
4559
+ const commandArgs = normalized.match(CLAUDE_COMMAND_ARGS_PATTERN)?.[1]?.trim();
4560
+ if (commandArgs) {
4561
+ return `${command} ${commandArgs}`;
4562
+ }
4563
+ return command;
4564
+ }
4565
+ function readClaudeCommandPromptName(text) {
4566
+ const commandName = text.match(CLAUDE_COMMAND_NAME_PATTERN)?.[1]?.trim();
4567
+ if (commandName) {
4568
+ return commandName.startsWith("/") ? commandName : `/${commandName}`;
4569
+ }
4570
+ const commandMessage = text.match(CLAUDE_COMMAND_MESSAGE_PATTERN)?.[1]?.trim();
4571
+ if (!commandMessage) {
4572
+ return null;
4573
+ }
4574
+ return commandMessage.startsWith("/") ? commandMessage : `/${commandMessage}`;
4575
+ }
4576
+ function extractClaudeUserText(messageRaw) {
4577
+ const message = toObjectRecord(messageRaw);
4578
+ if (!message) {
4579
+ return null;
4580
+ }
4581
+ if (typeof message.content === "string") {
4582
+ const normalized = message.content.trim();
4583
+ if (!normalized || isClaudeTranscriptNoiseText(normalized))
4584
+ return null;
4585
+ return normalizeClaudeUserPromptText(normalized);
4586
+ }
4587
+ if (typeof message.text === "string") {
4588
+ const normalized = message.text.trim();
4589
+ if (!normalized || isClaudeTranscriptNoiseText(normalized))
4590
+ return null;
4591
+ return normalizeClaudeUserPromptText(normalized);
4592
+ }
4593
+ if (isUnknownArray(message.content)) {
4594
+ for (const block of message.content) {
4595
+ const blockRecord = toObjectRecord(block);
4596
+ if (blockRecord && typeof blockRecord.text === "string") {
4597
+ const normalized = blockRecord.text.trim();
4598
+ if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
4599
+ return normalizeClaudeUserPromptText(normalized);
4600
+ }
4601
+ }
4602
+ }
4603
+ }
4604
+ return null;
4605
+ }
4606
+ //# sourceMappingURL=agent.js.map