@otto-code/server 0.5.0

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