@jawcode-dev/coding-agent 1.1.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 (2104) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/README.md +37 -0
  3. package/dist/types/async/index.d.ts +2 -0
  4. package/dist/types/async/job-manager.d.ts +294 -0
  5. package/dist/types/async/support.d.ts +2 -0
  6. package/dist/types/autoresearch/dashboard.d.ts +4 -0
  7. package/dist/types/autoresearch/git.d.ts +36 -0
  8. package/dist/types/autoresearch/helpers.d.ts +24 -0
  9. package/dist/types/autoresearch/index.d.ts +2 -0
  10. package/dist/types/autoresearch/state.d.ts +17 -0
  11. package/dist/types/autoresearch/storage.d.ts +142 -0
  12. package/dist/types/autoresearch/tools/init-experiment.d.ts +31 -0
  13. package/dist/types/autoresearch/tools/log-experiment.d.ts +23 -0
  14. package/dist/types/autoresearch/tools/run-experiment.d.ts +8 -0
  15. package/dist/types/autoresearch/tools/update-notes.d.ts +12 -0
  16. package/dist/types/autoresearch/types.d.ts +154 -0
  17. package/dist/types/capability/context-file.d.ts +17 -0
  18. package/dist/types/capability/extension-module.d.ts +15 -0
  19. package/dist/types/capability/extension.d.ts +28 -0
  20. package/dist/types/capability/fs.d.ts +20 -0
  21. package/dist/types/capability/hook.d.ts +19 -0
  22. package/dist/types/capability/index.d.ts +80 -0
  23. package/dist/types/capability/instruction.d.ts +17 -0
  24. package/dist/types/capability/mcp.d.ts +45 -0
  25. package/dist/types/capability/prompt.d.ts +15 -0
  26. package/dist/types/capability/rule.d.ts +61 -0
  27. package/dist/types/capability/settings.d.ts +15 -0
  28. package/dist/types/capability/skill.d.ts +36 -0
  29. package/dist/types/capability/slash-command.d.ts +17 -0
  30. package/dist/types/capability/ssh.d.ts +23 -0
  31. package/dist/types/capability/system-prompt.d.ts +15 -0
  32. package/dist/types/capability/tool.d.ts +19 -0
  33. package/dist/types/capability/types.d.ts +154 -0
  34. package/dist/types/cli/agents-cli.d.ts +12 -0
  35. package/dist/types/cli/args.d.ts +56 -0
  36. package/dist/types/cli/auth-broker-cli.d.ts +25 -0
  37. package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
  38. package/dist/types/cli/classify-install-target.d.ts +18 -0
  39. package/dist/types/cli/commands/init-xdg.d.ts +1 -0
  40. package/dist/types/cli/config-cli.d.ts +22 -0
  41. package/dist/types/cli/fast-help.d.ts +1 -0
  42. package/dist/types/cli/file-processor.d.ts +11 -0
  43. package/dist/types/cli/grep-cli.d.ts +17 -0
  44. package/dist/types/cli/initial-message.d.ts +17 -0
  45. package/dist/types/cli/list-models.d.ts +30 -0
  46. package/dist/types/cli/notify-cli.d.ts +29 -0
  47. package/dist/types/cli/plugin-cli.d.ts +32 -0
  48. package/dist/types/cli/read-cli.d.ts +4 -0
  49. package/dist/types/cli/session-picker.d.ts +3 -0
  50. package/dist/types/cli/setup-cli.d.ts +45 -0
  51. package/dist/types/cli/shell-cli.d.ts +8 -0
  52. package/dist/types/cli/skills-cli.d.ts +9 -0
  53. package/dist/types/cli/ssh-cli.d.ts +21 -0
  54. package/dist/types/cli/stats-cli.d.ts +17 -0
  55. package/dist/types/cli/update-cli.d.ts +41 -0
  56. package/dist/types/cli/web-search-cli.d.ts +20 -0
  57. package/dist/types/cli/worktree-cli.d.ts +26 -0
  58. package/dist/types/cli.d.ts +4 -0
  59. package/dist/types/commands/acp.d.ts +12 -0
  60. package/dist/types/commands/agents.d.ts +34 -0
  61. package/dist/types/commands/auth-broker.d.ts +54 -0
  62. package/dist/types/commands/auth-gateway.d.ts +32 -0
  63. package/dist/types/commands/chat.d.ts +18 -0
  64. package/dist/types/commands/codex-native-hook.d.ts +6 -0
  65. package/dist/types/commands/commit.d.ts +27 -0
  66. package/dist/types/commands/config.d.ts +30 -0
  67. package/dist/types/commands/contribution-prep.d.ts +18 -0
  68. package/dist/types/commands/coordinator.d.ts +19 -0
  69. package/dist/types/commands/goal.d.ts +31 -0
  70. package/dist/types/commands/grep.d.ts +42 -0
  71. package/dist/types/commands/harness.d.ts +50 -0
  72. package/dist/types/commands/interview.d.ts +51 -0
  73. package/dist/types/commands/launch.d.ts +127 -0
  74. package/dist/types/commands/mcp-serve.d.ts +24 -0
  75. package/dist/types/commands/memory.d.ts +19 -0
  76. package/dist/types/commands/notify.d.ts +30 -0
  77. package/dist/types/commands/orchestrate.d.ts +48 -0
  78. package/dist/types/commands/planphase.d.ts +7 -0
  79. package/dist/types/commands/plugin.d.ts +52 -0
  80. package/dist/types/commands/ralplan.d.ts +7 -0
  81. package/dist/types/commands/read.d.ts +15 -0
  82. package/dist/types/commands/session.d.ts +24 -0
  83. package/dist/types/commands/setup.d.ts +95 -0
  84. package/dist/types/commands/shell.d.ts +21 -0
  85. package/dist/types/commands/skills.d.ts +26 -0
  86. package/dist/types/commands/ssh.d.ts +48 -0
  87. package/dist/types/commands/state.d.ts +7 -0
  88. package/dist/types/commands/stats.d.ts +25 -0
  89. package/dist/types/commands/team.d.ts +24 -0
  90. package/dist/types/commands/ultragoal.d.ts +8 -0
  91. package/dist/types/commands/update.d.ts +20 -0
  92. package/dist/types/commands/web-search.d.ts +33 -0
  93. package/dist/types/commands/worktree.d.ts +31 -0
  94. package/dist/types/commit/agentic/agent.d.ts +31 -0
  95. package/dist/types/commit/agentic/fallback.d.ts +5 -0
  96. package/dist/types/commit/agentic/index.d.ts +2 -0
  97. package/dist/types/commit/agentic/state.d.ts +58 -0
  98. package/dist/types/commit/agentic/tools/analyze-file.d.ts +19 -0
  99. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +10 -0
  100. package/dist/types/commit/agentic/tools/git-hunk.d.ts +9 -0
  101. package/dist/types/commit/agentic/tools/git-overview.d.ts +9 -0
  102. package/dist/types/commit/agentic/tools/index.d.ts +16 -0
  103. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +28 -0
  104. package/dist/types/commit/agentic/tools/propose-commit.d.ts +36 -0
  105. package/dist/types/commit/agentic/tools/recent-commits.d.ts +7 -0
  106. package/dist/types/commit/agentic/tools/schemas.d.ts +27 -0
  107. package/dist/types/commit/agentic/tools/split-commit.d.ts +53 -0
  108. package/dist/types/commit/agentic/topo-sort.d.ts +4 -0
  109. package/dist/types/commit/agentic/trivial.d.ts +7 -0
  110. package/dist/types/commit/agentic/validation.d.ts +20 -0
  111. package/dist/types/commit/analysis/conventional.d.ts +22 -0
  112. package/dist/types/commit/analysis/index.d.ts +4 -0
  113. package/dist/types/commit/analysis/scope.d.ts +6 -0
  114. package/dist/types/commit/analysis/summary.d.ts +19 -0
  115. package/dist/types/commit/analysis/validation.d.ts +8 -0
  116. package/dist/types/commit/changelog/detect.d.ts +2 -0
  117. package/dist/types/commit/changelog/generate.d.ts +30 -0
  118. package/dist/types/commit/changelog/index.d.ts +30 -0
  119. package/dist/types/commit/changelog/parse.d.ts +2 -0
  120. package/dist/types/commit/cli.d.ts +3 -0
  121. package/dist/types/commit/git/diff.d.ts +5 -0
  122. package/dist/types/commit/index.d.ts +4 -0
  123. package/dist/types/commit/map-reduce/index.d.ts +28 -0
  124. package/dist/types/commit/map-reduce/map-phase.d.ts +17 -0
  125. package/dist/types/commit/map-reduce/reduce-phase.d.ts +13 -0
  126. package/dist/types/commit/map-reduce/utils.d.ts +2 -0
  127. package/dist/types/commit/message.d.ts +2 -0
  128. package/dist/types/commit/model-selection.d.ts +15 -0
  129. package/dist/types/commit/pipeline.d.ts +5 -0
  130. package/dist/types/commit/shared-llm.d.ts +54 -0
  131. package/dist/types/commit/types.d.ts +78 -0
  132. package/dist/types/commit/utils/exclusions.d.ts +4 -0
  133. package/dist/types/commit/utils.d.ts +20 -0
  134. package/dist/types/config/config-file.d.ts +58 -0
  135. package/dist/types/config/file-lock.d.ts +6 -0
  136. package/dist/types/config/keybindings.d.ts +359 -0
  137. package/dist/types/config/model-equivalence.d.ts +24 -0
  138. package/dist/types/config/model-profile-activation.d.ts +36 -0
  139. package/dist/types/config/model-profiles.d.ts +19 -0
  140. package/dist/types/config/model-registry.d.ts +445 -0
  141. package/dist/types/config/model-resolver.d.ts +217 -0
  142. package/dist/types/config/models-config-schema.d.ts +652 -0
  143. package/dist/types/config/oauth-provider-aliases.d.ts +1 -0
  144. package/dist/types/config/prompt-templates.d.ts +32 -0
  145. package/dist/types/config/resolve-config-value.d.ts +17 -0
  146. package/dist/types/config/settings-schema.d.ts +3683 -0
  147. package/dist/types/config/settings.d.ts +134 -0
  148. package/dist/types/config/skill-settings-defaults.d.ts +21 -0
  149. package/dist/types/config.d.ts +66 -0
  150. package/dist/types/coordinator/contract.d.ts +4 -0
  151. package/dist/types/coordinator-mcp/policy.d.ts +24 -0
  152. package/dist/types/coordinator-mcp/safety.d.ts +26 -0
  153. package/dist/types/coordinator-mcp/server.d.ts +52 -0
  154. package/dist/types/cursor.d.ts +24 -0
  155. package/dist/types/dap/client.d.ts +38 -0
  156. package/dist/types/dap/config.d.ts +6 -0
  157. package/dist/types/dap/index.d.ts +4 -0
  158. package/dist/types/dap/session.d.ts +108 -0
  159. package/dist/types/dap/types.d.ts +524 -0
  160. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  161. package/dist/types/debug/index.d.ts +15 -0
  162. package/dist/types/debug/log-formatting.d.ts +4 -0
  163. package/dist/types/debug/log-viewer.d.ts +68 -0
  164. package/dist/types/debug/profiler.d.ts +24 -0
  165. package/dist/types/debug/raw-sse-buffer.d.ts +44 -0
  166. package/dist/types/debug/raw-sse.d.ts +16 -0
  167. package/dist/types/debug/report-bundle.d.ts +55 -0
  168. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  169. package/dist/types/debug/system-info.d.ts +26 -0
  170. package/dist/types/defaults/jwc-defaults.d.ts +69 -0
  171. package/dist/types/discovery/agents-md.d.ts +1 -0
  172. package/dist/types/discovery/agents.d.ts +12 -0
  173. package/dist/types/discovery/builtin.d.ts +1 -0
  174. package/dist/types/discovery/claude-plugins.d.ts +1 -0
  175. package/dist/types/discovery/claude.d.ts +1 -0
  176. package/dist/types/discovery/cli-jaw.d.ts +1 -0
  177. package/dist/types/discovery/cline.d.ts +1 -0
  178. package/dist/types/discovery/codex.d.ts +1 -0
  179. package/dist/types/discovery/cursor.d.ts +16 -0
  180. package/dist/types/discovery/gemini.d.ts +1 -0
  181. package/dist/types/discovery/github.d.ts +1 -0
  182. package/dist/types/discovery/helpers.d.ts +278 -0
  183. package/dist/types/discovery/index.d.ts +49 -0
  184. package/dist/types/discovery/mcp-json.d.ts +1 -0
  185. package/dist/types/discovery/opencode.d.ts +1 -0
  186. package/dist/types/discovery/plugin-dir-roots.d.ts +15 -0
  187. package/dist/types/discovery/ssh.d.ts +1 -0
  188. package/dist/types/discovery/substitute-plugin-root.d.ts +5 -0
  189. package/dist/types/discovery/vscode.d.ts +1 -0
  190. package/dist/types/discovery/windsurf.d.ts +13 -0
  191. package/dist/types/edit/apply-patch/index.d.ts +35 -0
  192. package/dist/types/edit/apply-patch/parser.d.ts +34 -0
  193. package/dist/types/edit/diff.d.ts +75 -0
  194. package/dist/types/edit/file-read-cache.d.ts +25 -0
  195. package/dist/types/edit/index.d.ts +58 -0
  196. package/dist/types/edit/modes/apply-patch.d.ts +24 -0
  197. package/dist/types/edit/modes/patch.d.ts +99 -0
  198. package/dist/types/edit/modes/replace.d.ts +149 -0
  199. package/dist/types/edit/normalize.d.ts +43 -0
  200. package/dist/types/edit/notebook.d.ts +23 -0
  201. package/dist/types/edit/read-file.d.ts +2 -0
  202. package/dist/types/edit/renderer.d.ts +110 -0
  203. package/dist/types/edit/streaming.d.ts +66 -0
  204. package/dist/types/eval/backend.d.ts +40 -0
  205. package/dist/types/eval/index.d.ts +4 -0
  206. package/dist/types/eval/js/context-manager.d.ts +24 -0
  207. package/dist/types/eval/js/executor.d.ts +28 -0
  208. package/dist/types/eval/js/index.d.ts +10 -0
  209. package/dist/types/eval/js/shared/helpers.d.ts +38 -0
  210. package/dist/types/eval/js/shared/indirect-eval.d.ts +14 -0
  211. package/dist/types/eval/js/shared/prelude.d.ts +1 -0
  212. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -0
  213. package/dist/types/eval/js/shared/runtime.d.ts +47 -0
  214. package/dist/types/eval/js/shared/types.d.ts +24 -0
  215. package/dist/types/eval/js/tool-bridge.d.ts +18 -0
  216. package/dist/types/eval/js/worker-core.d.ts +5 -0
  217. package/dist/types/eval/js/worker-entry.d.ts +1 -0
  218. package/dist/types/eval/js/worker-protocol.d.ts +77 -0
  219. package/dist/types/eval/py/display.d.ts +25 -0
  220. package/dist/types/eval/py/executor.d.ts +85 -0
  221. package/dist/types/eval/py/index.d.ts +10 -0
  222. package/dist/types/eval/py/kernel.d.ts +63 -0
  223. package/dist/types/eval/py/prelude.d.ts +1 -0
  224. package/dist/types/eval/py/runtime.d.ts +21 -0
  225. package/dist/types/eval/py/tool-bridge.d.ts +20 -0
  226. package/dist/types/eval/types.d.ts +52 -0
  227. package/dist/types/exa/factory.d.ts +13 -0
  228. package/dist/types/exa/index.d.ts +20 -0
  229. package/dist/types/exa/mcp-client.d.ts +45 -0
  230. package/dist/types/exa/render.d.ts +19 -0
  231. package/dist/types/exa/researcher.d.ts +9 -0
  232. package/dist/types/exa/search.d.ts +9 -0
  233. package/dist/types/exa/types.d.ts +155 -0
  234. package/dist/types/exa/websets.d.ts +9 -0
  235. package/dist/types/exec/bash-executor.d.ts +58 -0
  236. package/dist/types/exec/exec.d.ts +25 -0
  237. package/dist/types/exec/idle-timeout-watchdog.d.ts +18 -0
  238. package/dist/types/exec/non-interactive-env.d.ts +4 -0
  239. package/dist/types/export/custom-share.d.ts +20 -0
  240. package/dist/types/export/html/index.d.ts +10 -0
  241. package/dist/types/export/html/template.generated.d.ts +1 -0
  242. package/dist/types/export/html/template.macro.d.ts +5 -0
  243. package/dist/types/export/ttsr.d.ts +44 -0
  244. package/dist/types/extensibility/custom-commands/bundled/ci-green/index.d.ts +9 -0
  245. package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +10 -0
  246. package/dist/types/extensibility/custom-commands/index.d.ts +2 -0
  247. package/dist/types/extensibility/custom-commands/loader.d.ts +29 -0
  248. package/dist/types/extensibility/custom-commands/types.d.ts +106 -0
  249. package/dist/types/extensibility/custom-tools/index.d.ts +6 -0
  250. package/dist/types/extensibility/custom-tools/loader.d.ts +69 -0
  251. package/dist/types/extensibility/custom-tools/types.d.ts +227 -0
  252. package/dist/types/extensibility/custom-tools/wrapper.d.ts +23 -0
  253. package/dist/types/extensibility/extensions/compact-handler.d.ts +26 -0
  254. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +29 -0
  255. package/dist/types/extensibility/extensions/index.d.ts +8 -0
  256. package/dist/types/extensibility/extensions/loader.d.ts +43 -0
  257. package/dist/types/extensibility/extensions/runner.d.ts +134 -0
  258. package/dist/types/extensibility/extensions/types.d.ts +916 -0
  259. package/dist/types/extensibility/extensions/wrapper.d.ts +54 -0
  260. package/dist/types/extensibility/hooks/index.d.ts +5 -0
  261. package/dist/types/extensibility/hooks/loader.d.ts +89 -0
  262. package/dist/types/extensibility/hooks/runner.d.ts +126 -0
  263. package/dist/types/extensibility/hooks/tool-wrapper.d.ts +25 -0
  264. package/dist/types/extensibility/hooks/types.d.ts +431 -0
  265. package/dist/types/extensibility/jwc-plugins/activation.d.ts +14 -0
  266. package/dist/types/extensibility/jwc-plugins/index.d.ts +9 -0
  267. package/dist/types/extensibility/jwc-plugins/injection.d.ts +31 -0
  268. package/dist/types/extensibility/jwc-plugins/loader.d.ts +3 -0
  269. package/dist/types/extensibility/jwc-plugins/paths.d.ts +8 -0
  270. package/dist/types/extensibility/jwc-plugins/schema.d.ts +3 -0
  271. package/dist/types/extensibility/jwc-plugins/state.d.ts +9 -0
  272. package/dist/types/extensibility/jwc-plugins/tools.d.ts +8 -0
  273. package/dist/types/extensibility/jwc-plugins/types.d.ts +64 -0
  274. package/dist/types/extensibility/jwc-plugins/validation.d.ts +4 -0
  275. package/dist/types/extensibility/plugins/doctor.d.ts +3 -0
  276. package/dist/types/extensibility/plugins/git-url.d.ts +34 -0
  277. package/dist/types/extensibility/plugins/index.d.ts +7 -0
  278. package/dist/types/extensibility/plugins/installer.d.ts +5 -0
  279. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  280. package/dist/types/extensibility/plugins/loader.d.ts +31 -0
  281. package/dist/types/extensibility/plugins/manager.d.ts +65 -0
  282. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +41 -0
  283. package/dist/types/extensibility/plugins/marketplace/fetcher.d.ts +48 -0
  284. package/dist/types/extensibility/plugins/marketplace/index.d.ts +6 -0
  285. package/dist/types/extensibility/plugins/marketplace/manager.d.ts +57 -0
  286. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +34 -0
  287. package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +28 -0
  288. package/dist/types/extensibility/plugins/marketplace/types.d.ts +130 -0
  289. package/dist/types/extensibility/plugins/parser.d.ts +52 -0
  290. package/dist/types/extensibility/plugins/types.d.ts +149 -0
  291. package/dist/types/extensibility/shared-events.d.ts +280 -0
  292. package/dist/types/extensibility/skills.d.ts +82 -0
  293. package/dist/types/extensibility/slash-commands.d.ts +49 -0
  294. package/dist/types/extensibility/tool-proxy.d.ts +4 -0
  295. package/dist/types/extensibility/typebox.d.ts +155 -0
  296. package/dist/types/extensibility/utils.d.ts +12 -0
  297. package/dist/types/goals/goal-planning-start.d.ts +7 -0
  298. package/dist/types/goals/index.d.ts +3 -0
  299. package/dist/types/goals/runtime.d.ts +62 -0
  300. package/dist/types/goals/state.d.ts +40 -0
  301. package/dist/types/goals/tools/goal-tool.d.ts +62 -0
  302. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  303. package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
  304. package/dist/types/harness-control-plane/finalize.d.ts +55 -0
  305. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  306. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  307. package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
  308. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  309. package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
  310. package/dist/types/harness-control-plane/receipt-spool.d.ts +26 -0
  311. package/dist/types/harness-control-plane/receipts.d.ts +132 -0
  312. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  313. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  314. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  315. package/dist/types/harness-control-plane/state-machine.d.ts +24 -0
  316. package/dist/types/harness-control-plane/storage.d.ts +60 -0
  317. package/dist/types/harness-control-plane/types.d.ts +179 -0
  318. package/dist/types/hashline/anchors.d.ts +20 -0
  319. package/dist/types/hashline/apply.d.ts +14 -0
  320. package/dist/types/hashline/constants.d.ts +19 -0
  321. package/dist/types/hashline/diff-preview.d.ts +2 -0
  322. package/dist/types/hashline/diff.d.ts +17 -0
  323. package/dist/types/hashline/execute.d.ts +4 -0
  324. package/dist/types/hashline/hash.d.ts +123 -0
  325. package/dist/types/hashline/index.d.ts +13 -0
  326. package/dist/types/hashline/input.d.ts +11 -0
  327. package/dist/types/hashline/parser.d.ts +7 -0
  328. package/dist/types/hashline/prefixes.d.ts +7 -0
  329. package/dist/types/hashline/recovery.d.ts +27 -0
  330. package/dist/types/hashline/stream.d.ts +2 -0
  331. package/dist/types/hashline/types.d.ts +77 -0
  332. package/dist/types/hindsight/backend.d.ts +13 -0
  333. package/dist/types/hindsight/bank.d.ts +54 -0
  334. package/dist/types/hindsight/client.d.ts +224 -0
  335. package/dist/types/hindsight/config.d.ts +51 -0
  336. package/dist/types/hindsight/content.d.ts +71 -0
  337. package/dist/types/hindsight/index.d.ts +8 -0
  338. package/dist/types/hindsight/mental-models.d.ts +125 -0
  339. package/dist/types/hindsight/state.d.ts +99 -0
  340. package/dist/types/hindsight/transcript.d.ts +28 -0
  341. package/dist/types/hooks/codex-native-hooks-config.d.ts +29 -0
  342. package/dist/types/hooks/native-skill-hook.d.ts +16 -0
  343. package/dist/types/hooks/skill-keywords.d.ts +18 -0
  344. package/dist/types/hooks/skill-state.d.ts +88 -0
  345. package/dist/types/index.d.ts +33 -0
  346. package/dist/types/internal-urls/agent-protocol.d.ts +12 -0
  347. package/dist/types/internal-urls/artifact-protocol.d.ts +6 -0
  348. package/dist/types/internal-urls/docs-index.generated.d.ts +2 -0
  349. package/dist/types/internal-urls/index.d.ts +20 -0
  350. package/dist/types/internal-urls/issue-pr-protocol.d.ts +17 -0
  351. package/dist/types/internal-urls/json-query.d.ts +30 -0
  352. package/dist/types/internal-urls/jwc-protocol.d.ts +19 -0
  353. package/dist/types/internal-urls/local-protocol.d.ts +39 -0
  354. package/dist/types/internal-urls/mcp-protocol.d.ts +12 -0
  355. package/dist/types/internal-urls/memory-protocol.d.ts +23 -0
  356. package/dist/types/internal-urls/parse.d.ts +19 -0
  357. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  358. package/dist/types/internal-urls/router.d.ts +14 -0
  359. package/dist/types/internal-urls/rule-protocol.d.ts +6 -0
  360. package/dist/types/internal-urls/skill-protocol.d.ts +13 -0
  361. package/dist/types/internal-urls/types.d.ts +109 -0
  362. package/dist/types/jaw-interview/structured-renderer.d.ts +20 -0
  363. package/dist/types/jwc-runtime/actor-registry.d.ts +76 -0
  364. package/dist/types/jwc-runtime/agent-identity.d.ts +6 -0
  365. package/dist/types/jwc-runtime/cli-jaw-home.d.ts +1 -0
  366. package/dist/types/jwc-runtime/cli-jaw-vocab.d.ts +15 -0
  367. package/dist/types/jwc-runtime/cli-write-receipt.d.ts +24 -0
  368. package/dist/types/jwc-runtime/goal-cli.d.ts +7 -0
  369. package/dist/types/jwc-runtime/goal-engine.d.ts +193 -0
  370. package/dist/types/jwc-runtime/goal-guard.d.ts +26 -0
  371. package/dist/types/jwc-runtime/goal-mode-request.d.ts +78 -0
  372. package/dist/types/jwc-runtime/jaw-interview-runtime.d.ts +41 -0
  373. package/dist/types/jwc-runtime/launch-tmux.d.ts +70 -0
  374. package/dist/types/jwc-runtime/launch-worktree.d.ts +46 -0
  375. package/dist/types/jwc-runtime/legacy-storage.d.ts +19 -0
  376. package/dist/types/jwc-runtime/memory-runtime.d.ts +16 -0
  377. package/dist/types/jwc-runtime/orchestrate-runtime.d.ts +12 -0
  378. package/dist/types/jwc-runtime/orchestrate-state.d.ts +246 -0
  379. package/dist/types/jwc-runtime/plan-writer.d.ts +26 -0
  380. package/dist/types/jwc-runtime/restricted-role-agent-bash.d.ts +2 -0
  381. package/dist/types/jwc-runtime/session-state-sidecar.d.ts +17 -0
  382. package/dist/types/jwc-runtime/stage-skill-map.d.ts +7 -0
  383. package/dist/types/jwc-runtime/state-graph.d.ts +4 -0
  384. package/dist/types/jwc-runtime/state-migrations.d.ts +34 -0
  385. package/dist/types/jwc-runtime/state-renderer.d.ts +65 -0
  386. package/dist/types/jwc-runtime/state-runtime.d.ts +37 -0
  387. package/dist/types/jwc-runtime/state-schema.d.ts +324 -0
  388. package/dist/types/jwc-runtime/state-validation.d.ts +6 -0
  389. package/dist/types/jwc-runtime/state-writer.d.ts +147 -0
  390. package/dist/types/jwc-runtime/team-runtime.d.ts +349 -0
  391. package/dist/types/jwc-runtime/tmux-common.d.ts +49 -0
  392. package/dist/types/jwc-runtime/tmux-sessions.d.ts +17 -0
  393. package/dist/types/jwc-runtime/workflow-command-ref.d.ts +43 -0
  394. package/dist/types/jwc-runtime/workflow-manifest.d.ts +54 -0
  395. package/dist/types/lsp/client.d.ts +66 -0
  396. package/dist/types/lsp/clients/biome-client.d.ts +16 -0
  397. package/dist/types/lsp/clients/index.d.ts +19 -0
  398. package/dist/types/lsp/clients/lsp-linter-client.d.ts +16 -0
  399. package/dist/types/lsp/clients/swiftlint-client.d.ts +20 -0
  400. package/dist/types/lsp/config.d.ts +65 -0
  401. package/dist/types/lsp/edits.d.ts +23 -0
  402. package/dist/types/lsp/index.d.ts +130 -0
  403. package/dist/types/lsp/lspmux.d.ts +58 -0
  404. package/dist/types/lsp/render.d.ts +32 -0
  405. package/dist/types/lsp/startup-events.d.ts +11 -0
  406. package/dist/types/lsp/types.d.ts +302 -0
  407. package/dist/types/lsp/utils.d.ts +108 -0
  408. package/dist/types/main.d.ts +72 -0
  409. package/dist/types/mem0/backend.d.ts +2 -0
  410. package/dist/types/mem0/client.d.ts +49 -0
  411. package/dist/types/mem0/config.d.ts +24 -0
  412. package/dist/types/mem0/index.d.ts +5 -0
  413. package/dist/types/mem0/scope.d.ts +12 -0
  414. package/dist/types/mem0/state.d.ts +36 -0
  415. package/dist/types/memories/index.d.ts +28 -0
  416. package/dist/types/memories/local-query.d.ts +112 -0
  417. package/dist/types/memories/memory-config.d.ts +46 -0
  418. package/dist/types/memories/memory-fts.d.ts +51 -0
  419. package/dist/types/memories/memory-model-resolution.d.ts +20 -0
  420. package/dist/types/memories/memory-quality.d.ts +27 -0
  421. package/dist/types/memories/storage.d.ts +110 -0
  422. package/dist/types/memory-backend/index.d.ts +4 -0
  423. package/dist/types/memory-backend/local-backend.d.ts +9 -0
  424. package/dist/types/memory-backend/off-backend.d.ts +7 -0
  425. package/dist/types/memory-backend/resolve.d.ts +16 -0
  426. package/dist/types/memory-backend/types.d.ts +64 -0
  427. package/dist/types/migrate-config-dir-startup.d.ts +1 -0
  428. package/dist/types/migrate-config-dir.d.ts +22 -0
  429. package/dist/types/modes/acp/acp-agent.d.ts +61 -0
  430. package/dist/types/modes/acp/acp-client-bridge.d.ts +9 -0
  431. package/dist/types/modes/acp/acp-event-mapper.d.ts +34 -0
  432. package/dist/types/modes/acp/acp-mode.d.ts +5 -0
  433. package/dist/types/modes/acp/index.d.ts +2 -0
  434. package/dist/types/modes/acp/terminal-auth.d.ts +6 -0
  435. package/dist/types/modes/background-row-model.d.ts +3 -0
  436. package/dist/types/modes/bridge/auth.d.ts +12 -0
  437. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  438. package/dist/types/modes/bridge/bridge-mode.d.ts +46 -0
  439. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  440. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  441. package/dist/types/modes/components/_cli-jaw-entry.d.ts +31 -0
  442. package/dist/types/modes/components/agent-dashboard.d.ts +21 -0
  443. package/dist/types/modes/components/assistant-message.d.ts +33 -0
  444. package/dist/types/modes/components/background-footer-detail.d.ts +6 -0
  445. package/dist/types/modes/components/background-footer-panel-model.d.ts +18 -0
  446. package/dist/types/modes/components/background-footer-panel.d.ts +19 -0
  447. package/dist/types/modes/components/bash-execution.d.ts +37 -0
  448. package/dist/types/modes/components/bordered-loader.d.ts +11 -0
  449. package/dist/types/modes/components/branch-summary-message.d.ts +15 -0
  450. package/dist/types/modes/components/btw-panel.d.ts +16 -0
  451. package/dist/types/modes/components/compaction-summary-message.d.ts +15 -0
  452. package/dist/types/modes/components/composer-chrome.d.ts +7 -0
  453. package/dist/types/modes/components/composer-footer.d.ts +43 -0
  454. package/dist/types/modes/components/countdown-timer.d.ts +14 -0
  455. package/dist/types/modes/components/custom-editor.d.ts +80 -0
  456. package/dist/types/modes/components/custom-message.d.ts +17 -0
  457. package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
  458. package/dist/types/modes/components/diff.d.ts +11 -0
  459. package/dist/types/modes/components/dynamic-border.d.ts +14 -0
  460. package/dist/types/modes/components/eval-execution.d.ts +31 -0
  461. package/dist/types/modes/components/execution-shared.d.ts +46 -0
  462. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +26 -0
  463. package/dist/types/modes/components/extensions/extension-list.d.ts +39 -0
  464. package/dist/types/modes/components/extensions/index.d.ts +8 -0
  465. package/dist/types/modes/components/extensions/inspector-panel.d.ts +8 -0
  466. package/dist/types/modes/components/extensions/state-manager.d.ts +50 -0
  467. package/dist/types/modes/components/extensions/types.d.ts +151 -0
  468. package/dist/types/modes/components/footer.d.ts +30 -0
  469. package/dist/types/modes/components/full-transcript-overlay.d.ts +21 -0
  470. package/dist/types/modes/components/help-selector.d.ts +38 -0
  471. package/dist/types/modes/components/history-search.d.ts +7 -0
  472. package/dist/types/modes/components/hook-editor.d.ts +18 -0
  473. package/dist/types/modes/components/hook-input.d.ts +15 -0
  474. package/dist/types/modes/components/hook-message.d.ts +15 -0
  475. package/dist/types/modes/components/hook-selector.d.ts +64 -0
  476. package/dist/types/modes/components/index.d.ts +35 -0
  477. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  478. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  479. package/dist/types/modes/components/keybinding-hints.d.ts +40 -0
  480. package/dist/types/modes/components/login-dialog.d.ts +32 -0
  481. package/dist/types/modes/components/message-frame.d.ts +42 -0
  482. package/dist/types/modes/components/model-selector.d.ts +53 -0
  483. package/dist/types/modes/components/oauth-selector.d.ts +14 -0
  484. package/dist/types/modes/components/pabcd-border.d.ts +8 -0
  485. package/dist/types/modes/components/plugin-selector.d.ts +26 -0
  486. package/dist/types/modes/components/plugin-settings.d.ts +66 -0
  487. package/dist/types/modes/components/provider-onboarding-selector.d.ts +7 -0
  488. package/dist/types/modes/components/queue-mode-selector.d.ts +9 -0
  489. package/dist/types/modes/components/quota-panel.d.ts +21 -0
  490. package/dist/types/modes/components/read-tool-group.d.ts +34 -0
  491. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +25 -0
  492. package/dist/types/modes/components/session-observer-overlay.d.ts +11 -0
  493. package/dist/types/modes/components/session-selector.d.ts +30 -0
  494. package/dist/types/modes/components/settings-defs.d.ts +50 -0
  495. package/dist/types/modes/components/settings-selector.d.ts +56 -0
  496. package/dist/types/modes/components/show-images-selector.d.ts +9 -0
  497. package/dist/types/modes/components/skill-hud/render.d.ts +2 -0
  498. package/dist/types/modes/components/skill-message.d.ts +11 -0
  499. package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -0
  500. package/dist/types/modes/components/status-line/git-utils.d.ts +22 -0
  501. package/dist/types/modes/components/status-line/index.d.ts +4 -0
  502. package/dist/types/modes/components/status-line/presets.d.ts +3 -0
  503. package/dist/types/modes/components/status-line/segments.d.ts +5 -0
  504. package/dist/types/modes/components/status-line/separators.d.ts +3 -0
  505. package/dist/types/modes/components/status-line/token-rate.d.ts +10 -0
  506. package/dist/types/modes/components/status-line/types.d.ts +92 -0
  507. package/dist/types/modes/components/status-line/workflow-readers.d.ts +21 -0
  508. package/dist/types/modes/components/status-line.d.ts +82 -0
  509. package/dist/types/modes/components/theme-selector.d.ts +10 -0
  510. package/dist/types/modes/components/thinking-selector.d.ts +10 -0
  511. package/dist/types/modes/components/todo-reminder.d.ts +13 -0
  512. package/dist/types/modes/components/tool-execution.d.ts +69 -0
  513. package/dist/types/modes/components/tool-transcript-overlay.d.ts +18 -0
  514. package/dist/types/modes/components/tree-selector.d.ts +31 -0
  515. package/dist/types/modes/components/ttsr-notification.d.ts +15 -0
  516. package/dist/types/modes/components/user-message-selector.d.ts +28 -0
  517. package/dist/types/modes/components/user-message.d.ts +8 -0
  518. package/dist/types/modes/components/visual-truncate.d.ts +19 -0
  519. package/dist/types/modes/components/welcome.d.ts +42 -0
  520. package/dist/types/modes/controllers/btw-controller.d.ts +10 -0
  521. package/dist/types/modes/controllers/command-controller-shared.d.ts +47 -0
  522. package/dist/types/modes/controllers/command-controller.d.ts +39 -0
  523. package/dist/types/modes/controllers/event-controller.d.ts +12 -0
  524. package/dist/types/modes/controllers/extension-ui-controller.d.ts +80 -0
  525. package/dist/types/modes/controllers/input-controller.d.ts +49 -0
  526. package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +10 -0
  527. package/dist/types/modes/controllers/selector-controller.d.ts +78 -0
  528. package/dist/types/modes/controllers/ssh-command-controller.d.ts +10 -0
  529. package/dist/types/modes/controllers/todo-command-controller.d.ts +7 -0
  530. package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
  531. package/dist/types/modes/index.d.ts +10 -0
  532. package/dist/types/modes/interactive-mode.d.ts +295 -0
  533. package/dist/types/modes/jobs-observer.d.ts +67 -0
  534. package/dist/types/modes/oauth-manual-input.d.ts +8 -0
  535. package/dist/types/modes/print-mode.d.ts +27 -0
  536. package/dist/types/modes/prompt-action-autocomplete.d.ts +46 -0
  537. package/dist/types/modes/rpc/host-tools.d.ts +1 -0
  538. package/dist/types/modes/rpc/host-uris.d.ts +1 -0
  539. package/dist/types/modes/rpc/rpc-client.d.ts +267 -0
  540. package/dist/types/modes/rpc/rpc-mode.d.ts +67 -0
  541. package/dist/types/modes/rpc/rpc-types.d.ts +765 -0
  542. package/dist/types/modes/runtime-init.d.ts +21 -0
  543. package/dist/types/modes/session-observer-registry.d.ts +26 -0
  544. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
  545. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
  546. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +35 -0
  547. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  548. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
  549. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +38 -0
  550. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
  551. package/dist/types/modes/shared/agent-wire/handshake.d.ts +56 -0
  552. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  553. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  554. package/dist/types/modes/shared/agent-wire/jaw-interview-gate.d.ts +58 -0
  555. package/dist/types/modes/shared/agent-wire/protocol.d.ts +25 -0
  556. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  557. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  558. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
  559. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  560. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  561. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
  562. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
  563. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
  564. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
  565. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
  566. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
  567. package/dist/types/modes/shared.d.ts +15 -0
  568. package/dist/types/modes/theme/defaults/index.d.ts +499 -0
  569. package/dist/types/modes/theme/mermaid-cache.d.ts +9 -0
  570. package/dist/types/modes/theme/shimmer.d.ts +38 -0
  571. package/dist/types/modes/theme/theme.d.ts +274 -0
  572. package/dist/types/modes/types.d.ts +311 -0
  573. package/dist/types/modes/utils/abort-message.d.ts +4 -0
  574. package/dist/types/modes/utils/compaction-progress.d.ts +20 -0
  575. package/dist/types/modes/utils/context-usage.d.ts +53 -0
  576. package/dist/types/modes/utils/help-markdown.d.ts +17 -0
  577. package/dist/types/modes/utils/hotkeys-markdown.d.ts +5 -0
  578. package/dist/types/modes/utils/keybinding-matchers.d.ts +10 -0
  579. package/dist/types/modes/utils/session-transcript-replay.d.ts +24 -0
  580. package/dist/types/modes/utils/tools-markdown.d.ts +5 -0
  581. package/dist/types/modes/utils/ui-helpers.d.ts +77 -0
  582. package/dist/types/notifications/ask-bridge.d.ts +36 -0
  583. package/dist/types/notifications/config-command-parser.d.ts +27 -0
  584. package/dist/types/notifications/config.d.ts +37 -0
  585. package/dist/types/notifications/daemon-control.d.ts +29 -0
  586. package/dist/types/notifications/daemon-engine.d.ts +64 -0
  587. package/dist/types/notifications/daemon-inbound.d.ts +93 -0
  588. package/dist/types/notifications/daemon-loop.d.ts +28 -0
  589. package/dist/types/notifications/daemon-owner.d.ts +25 -0
  590. package/dist/types/notifications/daemon-runtime.d.ts +32 -0
  591. package/dist/types/notifications/discovery.d.ts +45 -0
  592. package/dist/types/notifications/index.d.ts +34 -0
  593. package/dist/types/notifications/lifecycle-audit.d.ts +60 -0
  594. package/dist/types/notifications/lifecycle-command-parser.d.ts +19 -0
  595. package/dist/types/notifications/lifecycle-control-runtime.d.ts +45 -0
  596. package/dist/types/notifications/notification-redaction.d.ts +36 -0
  597. package/dist/types/notifications/protocol.d.ts +43 -0
  598. package/dist/types/notifications/remote-answer.d.ts +78 -0
  599. package/dist/types/notifications/reply-bridge.d.ts +21 -0
  600. package/dist/types/notifications/server.d.ts +38 -0
  601. package/dist/types/notifications/session-lifecycle.d.ts +22 -0
  602. package/dist/types/notifications/session-registry.d.ts +31 -0
  603. package/dist/types/notifications/telegram-api.d.ts +93 -0
  604. package/dist/types/notifications/telegram-ask-keyboard.d.ts +57 -0
  605. package/dist/types/notifications/telegram-callback-ingest.d.ts +49 -0
  606. package/dist/types/notifications/telegram-inbound-router.d.ts +57 -0
  607. package/dist/types/notifications/telegram-media-policy.d.ts +27 -0
  608. package/dist/types/notifications/telegram-media-render.d.ts +29 -0
  609. package/dist/types/notifications/telegram-message-ingest.d.ts +54 -0
  610. package/dist/types/notifications/telegram-pairing.d.ts +21 -0
  611. package/dist/types/notifications/threaded-lifecycle.d.ts +23 -0
  612. package/dist/types/notifications/threaded-shutdown.d.ts +19 -0
  613. package/dist/types/notifications/threaded-surface.d.ts +71 -0
  614. package/dist/types/notifications/transport-shell.d.ts +47 -0
  615. package/dist/types/notifications/transport-state.d.ts +44 -0
  616. package/dist/types/notifications/workspace-path-confinement.d.ts +12 -0
  617. package/dist/types/plan-mode/approved-plan.d.ts +49 -0
  618. package/dist/types/plan-mode/state.d.ts +6 -0
  619. package/dist/types/registry/agent-registry.d.ts +62 -0
  620. package/dist/types/reminders/star-reminder.d.ts +115 -0
  621. package/dist/types/runtime-mcp/client.d.ts +74 -0
  622. package/dist/types/runtime-mcp/config-writer.d.ts +53 -0
  623. package/dist/types/runtime-mcp/config.d.ts +75 -0
  624. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +7 -0
  625. package/dist/types/runtime-mcp/index.d.ts +18 -0
  626. package/dist/types/runtime-mcp/json-rpc.d.ts +22 -0
  627. package/dist/types/runtime-mcp/loader.d.ts +43 -0
  628. package/dist/types/runtime-mcp/manager.d.ts +193 -0
  629. package/dist/types/runtime-mcp/oauth-discovery.d.ts +40 -0
  630. package/dist/types/runtime-mcp/oauth-flow.d.ts +59 -0
  631. package/dist/types/runtime-mcp/render.d.ts +25 -0
  632. package/dist/types/runtime-mcp/smithery-auth.d.ts +16 -0
  633. package/dist/types/runtime-mcp/smithery-connect.d.ts +38 -0
  634. package/dist/types/runtime-mcp/smithery-registry.d.ts +51 -0
  635. package/dist/types/runtime-mcp/tool-bridge.d.ts +86 -0
  636. package/dist/types/runtime-mcp/tool-cache.d.ts +8 -0
  637. package/dist/types/runtime-mcp/transports/http.d.ts +36 -0
  638. package/dist/types/runtime-mcp/transports/index.d.ts +5 -0
  639. package/dist/types/runtime-mcp/transports/stdio.d.ts +28 -0
  640. package/dist/types/runtime-mcp/types.d.ts +333 -0
  641. package/dist/types/sdk.d.ts +234 -0
  642. package/dist/types/secrets/index.d.ts +10 -0
  643. package/dist/types/secrets/obfuscator.d.ts +23 -0
  644. package/dist/types/secrets/regex.d.ts +2 -0
  645. package/dist/types/session/agent-session.d.ts +1052 -0
  646. package/dist/types/session/agent-storage.d.ts +100 -0
  647. package/dist/types/session/artifacts.d.ts +61 -0
  648. package/dist/types/session/auth-broker-config.d.ts +13 -0
  649. package/dist/types/session/auth-storage.d.ts +6 -0
  650. package/dist/types/session/blob-store.d.ts +112 -0
  651. package/dist/types/session/client-bridge.d.ts +88 -0
  652. package/dist/types/session/contribution-prep.d.ts +47 -0
  653. package/dist/types/session/history-storage.d.ts +16 -0
  654. package/dist/types/session/messages.d.ts +165 -0
  655. package/dist/types/session/pabcd-stage-header.d.ts +18 -0
  656. package/dist/types/session/session-dump-format.d.ts +22 -0
  657. package/dist/types/session/session-manager.d.ts +571 -0
  658. package/dist/types/session/session-storage.d.ts +88 -0
  659. package/dist/types/session/streaming-output.d.ts +196 -0
  660. package/dist/types/session/tool-choice-queue.d.ts +78 -0
  661. package/dist/types/session/yield-queue.d.ts +24 -0
  662. package/dist/types/setup/hermes-setup.d.ts +71 -0
  663. package/dist/types/setup/model-onboarding-guidance.d.ts +9 -0
  664. package/dist/types/setup/provider-onboarding.d.ts +52 -0
  665. package/dist/types/skill-state/active-state.d.ts +115 -0
  666. package/dist/types/skill-state/initial-phase.d.ts +12 -0
  667. package/dist/types/skill-state/jaw-interview-mutation-guard.d.ts +33 -0
  668. package/dist/types/skill-state/workflow-hud.d.ts +76 -0
  669. package/dist/types/skill-state/workflow-state-contract.d.ts +60 -0
  670. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  671. package/dist/types/slash-commands/acp-builtins.d.ts +18 -0
  672. package/dist/types/slash-commands/builtin-registry.d.ts +24 -0
  673. package/dist/types/slash-commands/helpers/context-report.d.ts +7 -0
  674. package/dist/types/slash-commands/helpers/format.d.ts +10 -0
  675. package/dist/types/slash-commands/helpers/mcp.d.ts +3 -0
  676. package/dist/types/slash-commands/helpers/parse.d.ts +33 -0
  677. package/dist/types/slash-commands/helpers/ssh.d.ts +3 -0
  678. package/dist/types/slash-commands/helpers/todo.d.ts +3 -0
  679. package/dist/types/slash-commands/helpers/usage-report.d.ts +11 -0
  680. package/dist/types/slash-commands/types.d.ts +120 -0
  681. package/dist/types/ssh/config-writer.d.ts +49 -0
  682. package/dist/types/ssh/connection-manager.d.ts +33 -0
  683. package/dist/types/ssh/ssh-executor.d.ts +37 -0
  684. package/dist/types/ssh/sshfs-mount.d.ts +6 -0
  685. package/dist/types/ssh/utils.d.ts +2 -0
  686. package/dist/types/stt/downloader.d.ts +9 -0
  687. package/dist/types/stt/index.d.ts +3 -0
  688. package/dist/types/stt/recorder.d.ts +13 -0
  689. package/dist/types/stt/setup.d.ts +18 -0
  690. package/dist/types/stt/stt-controller.d.ts +16 -0
  691. package/dist/types/stt/transcriber.d.ts +16 -0
  692. package/dist/types/system-prompt.d.ts +100 -0
  693. package/dist/types/task/agents.d.ts +28 -0
  694. package/dist/types/task/commands.d.ts +35 -0
  695. package/dist/types/task/discovery.d.ts +19 -0
  696. package/dist/types/task/executor.d.ts +113 -0
  697. package/dist/types/task/fork-context-advisory.d.ts +13 -0
  698. package/dist/types/task/id.d.ts +7 -0
  699. package/dist/types/task/index.d.ts +43 -0
  700. package/dist/types/task/jwc-command.d.ts +7 -0
  701. package/dist/types/task/model-presets.d.ts +17 -0
  702. package/dist/types/task/name-generator.d.ts +16 -0
  703. package/dist/types/task/output-manager.d.ts +36 -0
  704. package/dist/types/task/parallel.d.ts +34 -0
  705. package/dist/types/task/receipt.d.ts +86 -0
  706. package/dist/types/task/render.d.ts +28 -0
  707. package/dist/types/task/roi-reconciliation.d.ts +27 -0
  708. package/dist/types/task/simple-mode.d.ts +8 -0
  709. package/dist/types/task/spawn-gate.d.ts +38 -0
  710. package/dist/types/task/subprocess-tool-registry.d.ts +72 -0
  711. package/dist/types/task/types.d.ts +506 -0
  712. package/dist/types/task/worktree.d.ts +94 -0
  713. package/dist/types/thinking.d.ts +31 -0
  714. package/dist/types/tool-discovery/tool-index.d.ts +113 -0
  715. package/dist/types/tools/archive-reader.d.ts +40 -0
  716. package/dist/types/tools/ask.d.ts +152 -0
  717. package/dist/types/tools/ast-edit.d.ts +77 -0
  718. package/dist/types/tools/ast-grep.d.ts +69 -0
  719. package/dist/types/tools/auto-generated-guard.d.ts +17 -0
  720. package/dist/types/tools/background.d.ts +124 -0
  721. package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
  722. package/dist/types/tools/bash-command-fixup.d.ts +11 -0
  723. package/dist/types/tools/bash-interactive.d.ts +16 -0
  724. package/dist/types/tools/bash-interceptor.d.ts +24 -0
  725. package/dist/types/tools/bash-pty-selection.d.ts +7 -0
  726. package/dist/types/tools/bash-skill-urls.d.ts +31 -0
  727. package/dist/types/tools/bash.d.ts +156 -0
  728. package/dist/types/tools/browser/actions.d.ts +54 -0
  729. package/dist/types/tools/browser/attach.d.ts +34 -0
  730. package/dist/types/tools/browser/launch.d.ts +62 -0
  731. package/dist/types/tools/browser/readable.d.ts +17 -0
  732. package/dist/types/tools/browser/registry.d.ts +42 -0
  733. package/dist/types/tools/browser/render.d.ts +50 -0
  734. package/dist/types/tools/browser/tab-protocol.d.ts +144 -0
  735. package/dist/types/tools/browser/tab-supervisor.d.ts +63 -0
  736. package/dist/types/tools/browser/tab-worker-entry.d.ts +1 -0
  737. package/dist/types/tools/browser/tab-worker.d.ts +19 -0
  738. package/dist/types/tools/browser.d.ts +194 -0
  739. package/dist/types/tools/calculator.d.ts +76 -0
  740. package/dist/types/tools/checkpoint.d.ts +63 -0
  741. package/dist/types/tools/computer-use-backend.d.ts +6 -0
  742. package/dist/types/tools/computer-use.d.ts +100 -0
  743. package/dist/types/tools/conflict-detect.d.ts +205 -0
  744. package/dist/types/tools/context.d.ts +19 -0
  745. package/dist/types/tools/cron.d.ts +116 -0
  746. package/dist/types/tools/debug.d.ts +209 -0
  747. package/dist/types/tools/eval.d.ts +108 -0
  748. package/dist/types/tools/fetch.d.ts +61 -0
  749. package/dist/types/tools/file-recorder.d.ts +13 -0
  750. package/dist/types/tools/find.d.ts +95 -0
  751. package/dist/types/tools/fs-cache-invalidation.d.ts +15 -0
  752. package/dist/types/tools/gh-format.d.ts +6 -0
  753. package/dist/types/tools/gh-renderer.d.ts +25 -0
  754. package/dist/types/tools/gh.d.ts +356 -0
  755. package/dist/types/tools/github-cache.d.ts +103 -0
  756. package/dist/types/tools/grouped-file-output.d.ts +36 -0
  757. package/dist/types/tools/hindsight-recall.d.ts +21 -0
  758. package/dist/types/tools/hindsight-reflect.d.ts +23 -0
  759. package/dist/types/tools/hindsight-retain.d.ts +27 -0
  760. package/dist/types/tools/image-gen.d.ts +79 -0
  761. package/dist/types/tools/index.d.ts +281 -0
  762. package/dist/types/tools/inspect-image-renderer.d.ts +26 -0
  763. package/dist/types/tools/inspect-image.d.ts +31 -0
  764. package/dist/types/tools/irc.d.ts +79 -0
  765. package/dist/types/tools/job.d.ts +65 -0
  766. package/dist/types/tools/json-tree.d.ts +23 -0
  767. package/dist/types/tools/jtd-to-json-schema.d.ts +29 -0
  768. package/dist/types/tools/jtd-to-typescript.d.ts +26 -0
  769. package/dist/types/tools/jtd-utils.d.ts +42 -0
  770. package/dist/types/tools/list-limit.d.ts +12 -0
  771. package/dist/types/tools/match-line-format.d.ts +11 -0
  772. package/dist/types/tools/monitor.d.ts +65 -0
  773. package/dist/types/tools/output-meta.d.ts +204 -0
  774. package/dist/types/tools/path-utils.d.ts +147 -0
  775. package/dist/types/tools/plan-mode-guard.d.ts +18 -0
  776. package/dist/types/tools/read.d.ts +85 -0
  777. package/dist/types/tools/recipe/index.d.ts +45 -0
  778. package/dist/types/tools/recipe/render.d.ts +36 -0
  779. package/dist/types/tools/recipe/runner.d.ts +60 -0
  780. package/dist/types/tools/recipe/runners/cargo.d.ts +16 -0
  781. package/dist/types/tools/recipe/runners/index.d.ts +2 -0
  782. package/dist/types/tools/recipe/runners/just.d.ts +2 -0
  783. package/dist/types/tools/recipe/runners/make.d.ts +2 -0
  784. package/dist/types/tools/recipe/runners/pkg.d.ts +2 -0
  785. package/dist/types/tools/recipe/runners/task.d.ts +2 -0
  786. package/dist/types/tools/render-mermaid.d.ts +37 -0
  787. package/dist/types/tools/render-utils.d.ts +158 -0
  788. package/dist/types/tools/renderers.d.ts +26 -0
  789. package/dist/types/tools/resolve.d.ts +90 -0
  790. package/dist/types/tools/review.d.ts +63 -0
  791. package/dist/types/tools/search-tool-bm25.d.ts +65 -0
  792. package/dist/types/tools/search.d.ts +93 -0
  793. package/dist/types/tools/skill.d.ts +47 -0
  794. package/dist/types/tools/sqlite-reader.d.ts +93 -0
  795. package/dist/types/tools/ssh.d.ts +67 -0
  796. package/dist/types/tools/subagent-render.d.ts +25 -0
  797. package/dist/types/tools/subagent.d.ts +87 -0
  798. package/dist/types/tools/telegram-send.d.ts +53 -0
  799. package/dist/types/tools/todo-write.d.ts +120 -0
  800. package/dist/types/tools/tool-errors.d.ts +33 -0
  801. package/dist/types/tools/tool-result.d.ts +30 -0
  802. package/dist/types/tools/tool-timeouts.d.ts +51 -0
  803. package/dist/types/tools/vim.d.ts +58 -0
  804. package/dist/types/tools/write.d.ts +59 -0
  805. package/dist/types/tools/yield.d.ts +25 -0
  806. package/dist/types/tui/code-cell.d.ts +32 -0
  807. package/dist/types/tui/file-list.d.ts +22 -0
  808. package/dist/types/tui/hyperlink.d.ts +42 -0
  809. package/dist/types/tui/index.d.ts +11 -0
  810. package/dist/types/tui/output-block.d.ts +28 -0
  811. package/dist/types/tui/status-line.d.ts +18 -0
  812. package/dist/types/tui/tree-list.d.ts +19 -0
  813. package/dist/types/tui/types.d.ts +13 -0
  814. package/dist/types/tui/utils.d.ts +36 -0
  815. package/dist/types/utils/changelog.d.ts +36 -0
  816. package/dist/types/utils/clipboard.d.ts +22 -0
  817. package/dist/types/utils/command-args.d.ts +9 -0
  818. package/dist/types/utils/commit-message-generator.d.ts +7 -0
  819. package/dist/types/utils/edit-mode.d.ts +13 -0
  820. package/dist/types/utils/event-bus.d.ts +6 -0
  821. package/dist/types/utils/external-editor.d.ts +17 -0
  822. package/dist/types/utils/file-display-mode.d.ts +27 -0
  823. package/dist/types/utils/file-mentions.d.ts +11 -0
  824. package/dist/types/utils/git.d.ts +361 -0
  825. package/dist/types/utils/image-loading.d.ts +26 -0
  826. package/dist/types/utils/image-resize.d.ts +39 -0
  827. package/dist/types/utils/lang-from-path.d.ts +8 -0
  828. package/dist/types/utils/markit.d.ts +7 -0
  829. package/dist/types/utils/open.d.ts +2 -0
  830. package/dist/types/utils/session-color.d.ts +10 -0
  831. package/dist/types/utils/shell-snapshot.d.ts +5 -0
  832. package/dist/types/utils/sixel.d.ts +21 -0
  833. package/dist/types/utils/title-generator.d.ts +31 -0
  834. package/dist/types/utils/tokenizer-download.d.ts +4 -0
  835. package/dist/types/utils/tokenizer-encoding.d.ts +7 -0
  836. package/dist/types/utils/tool-choice.d.ts +20 -0
  837. package/dist/types/utils/tools-manager.d.ts +9 -0
  838. package/dist/types/vim/buffer.d.ts +41 -0
  839. package/dist/types/vim/commands.d.ts +6 -0
  840. package/dist/types/vim/engine.d.ts +47 -0
  841. package/dist/types/vim/parser.d.ts +3 -0
  842. package/dist/types/vim/render.d.ts +25 -0
  843. package/dist/types/vim/types.d.ts +182 -0
  844. package/dist/types/web/kagi.d.ts +23 -0
  845. package/dist/types/web/parallel.d.ts +58 -0
  846. package/dist/types/web/public-fetch-url.d.ts +1 -0
  847. package/dist/types/web/scrapers/artifacthub.d.ts +6 -0
  848. package/dist/types/web/scrapers/arxiv.d.ts +5 -0
  849. package/dist/types/web/scrapers/aur.d.ts +5 -0
  850. package/dist/types/web/scrapers/biorxiv.d.ts +5 -0
  851. package/dist/types/web/scrapers/bluesky.d.ts +5 -0
  852. package/dist/types/web/scrapers/brew.d.ts +5 -0
  853. package/dist/types/web/scrapers/cheatsh.d.ts +8 -0
  854. package/dist/types/web/scrapers/chocolatey.d.ts +5 -0
  855. package/dist/types/web/scrapers/choosealicense.d.ts +2 -0
  856. package/dist/types/web/scrapers/cisa-kev.d.ts +5 -0
  857. package/dist/types/web/scrapers/clojars.d.ts +5 -0
  858. package/dist/types/web/scrapers/coingecko.d.ts +5 -0
  859. package/dist/types/web/scrapers/crates-io.d.ts +5 -0
  860. package/dist/types/web/scrapers/crossref.d.ts +2 -0
  861. package/dist/types/web/scrapers/devto.d.ts +5 -0
  862. package/dist/types/web/scrapers/discogs.d.ts +8 -0
  863. package/dist/types/web/scrapers/discourse.d.ts +5 -0
  864. package/dist/types/web/scrapers/dockerhub.d.ts +5 -0
  865. package/dist/types/web/scrapers/docs-rs.d.ts +2 -0
  866. package/dist/types/web/scrapers/fdroid.d.ts +5 -0
  867. package/dist/types/web/scrapers/firefox-addons.d.ts +2 -0
  868. package/dist/types/web/scrapers/flathub.d.ts +2 -0
  869. package/dist/types/web/scrapers/github-gist.d.ts +5 -0
  870. package/dist/types/web/scrapers/github.d.ts +12 -0
  871. package/dist/types/web/scrapers/gitlab.d.ts +5 -0
  872. package/dist/types/web/scrapers/go-pkg.d.ts +5 -0
  873. package/dist/types/web/scrapers/hackage.d.ts +5 -0
  874. package/dist/types/web/scrapers/hackernews.d.ts +2 -0
  875. package/dist/types/web/scrapers/hex.d.ts +5 -0
  876. package/dist/types/web/scrapers/huggingface.d.ts +2 -0
  877. package/dist/types/web/scrapers/iacr.d.ts +5 -0
  878. package/dist/types/web/scrapers/index.d.ts +84 -0
  879. package/dist/types/web/scrapers/jetbrains-marketplace.d.ts +2 -0
  880. package/dist/types/web/scrapers/lemmy.d.ts +2 -0
  881. package/dist/types/web/scrapers/lobsters.d.ts +5 -0
  882. package/dist/types/web/scrapers/mastodon.d.ts +5 -0
  883. package/dist/types/web/scrapers/maven.d.ts +6 -0
  884. package/dist/types/web/scrapers/mdn.d.ts +2 -0
  885. package/dist/types/web/scrapers/metacpan.d.ts +5 -0
  886. package/dist/types/web/scrapers/musicbrainz.d.ts +5 -0
  887. package/dist/types/web/scrapers/npm.d.ts +5 -0
  888. package/dist/types/web/scrapers/nuget.d.ts +5 -0
  889. package/dist/types/web/scrapers/nvd.d.ts +5 -0
  890. package/dist/types/web/scrapers/ollama.d.ts +2 -0
  891. package/dist/types/web/scrapers/open-vsx.d.ts +5 -0
  892. package/dist/types/web/scrapers/opencorporates.d.ts +5 -0
  893. package/dist/types/web/scrapers/openlibrary.d.ts +5 -0
  894. package/dist/types/web/scrapers/orcid.d.ts +5 -0
  895. package/dist/types/web/scrapers/osv.d.ts +5 -0
  896. package/dist/types/web/scrapers/packagist.d.ts +5 -0
  897. package/dist/types/web/scrapers/pub-dev.d.ts +5 -0
  898. package/dist/types/web/scrapers/pubmed.d.ts +5 -0
  899. package/dist/types/web/scrapers/pypi.d.ts +5 -0
  900. package/dist/types/web/scrapers/rawg.d.ts +2 -0
  901. package/dist/types/web/scrapers/readthedocs.d.ts +2 -0
  902. package/dist/types/web/scrapers/reddit.d.ts +5 -0
  903. package/dist/types/web/scrapers/repology.d.ts +5 -0
  904. package/dist/types/web/scrapers/rfc.d.ts +5 -0
  905. package/dist/types/web/scrapers/rubygems.d.ts +5 -0
  906. package/dist/types/web/scrapers/searchcode.d.ts +2 -0
  907. package/dist/types/web/scrapers/sec-edgar.d.ts +5 -0
  908. package/dist/types/web/scrapers/semantic-scholar.d.ts +2 -0
  909. package/dist/types/web/scrapers/snapcraft.d.ts +2 -0
  910. package/dist/types/web/scrapers/sourcegraph.d.ts +2 -0
  911. package/dist/types/web/scrapers/spdx.d.ts +5 -0
  912. package/dist/types/web/scrapers/spotify.d.ts +8 -0
  913. package/dist/types/web/scrapers/stackoverflow.d.ts +6 -0
  914. package/dist/types/web/scrapers/terraform.d.ts +5 -0
  915. package/dist/types/web/scrapers/tldr.d.ts +7 -0
  916. package/dist/types/web/scrapers/twitter.d.ts +5 -0
  917. package/dist/types/web/scrapers/types.d.ts +78 -0
  918. package/dist/types/web/scrapers/utils.d.ts +26 -0
  919. package/dist/types/web/scrapers/vimeo.d.ts +5 -0
  920. package/dist/types/web/scrapers/vscode-marketplace.d.ts +5 -0
  921. package/dist/types/web/scrapers/w3c.d.ts +2 -0
  922. package/dist/types/web/scrapers/wikidata.d.ts +5 -0
  923. package/dist/types/web/scrapers/wikipedia.d.ts +5 -0
  924. package/dist/types/web/scrapers/youtube.d.ts +5 -0
  925. package/dist/types/web/search/index.d.ts +95 -0
  926. package/dist/types/web/search/provider.d.ts +35 -0
  927. package/dist/types/web/search/providers/anthropic.d.ts +32 -0
  928. package/dist/types/web/search/providers/base.d.ts +90 -0
  929. package/dist/types/web/search/providers/brave.d.ts +27 -0
  930. package/dist/types/web/search/providers/codex.d.ts +35 -0
  931. package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
  932. package/dist/types/web/search/providers/exa.d.ts +52 -0
  933. package/dist/types/web/search/providers/gemini.d.ts +59 -0
  934. package/dist/types/web/search/providers/jina.d.ts +26 -0
  935. package/dist/types/web/search/providers/kagi.d.ts +24 -0
  936. package/dist/types/web/search/providers/kimi.d.ts +27 -0
  937. package/dist/types/web/search/providers/parallel.d.ts +15 -0
  938. package/dist/types/web/search/providers/perplexity.d.ts +38 -0
  939. package/dist/types/web/search/providers/searxng.d.ts +44 -0
  940. package/dist/types/web/search/providers/synthetic.d.ts +21 -0
  941. package/dist/types/web/search/providers/tavily.d.ts +29 -0
  942. package/dist/types/web/search/providers/utils.d.ts +52 -0
  943. package/dist/types/web/search/providers/xai.d.ts +62 -0
  944. package/dist/types/web/search/providers/zai.d.ts +28 -0
  945. package/dist/types/web/search/render.d.ts +35 -0
  946. package/dist/types/web/search/types.d.ts +348 -0
  947. package/dist/types/web/search/utils.d.ts +4 -0
  948. package/dist/types/workspace-tree.d.ts +42 -0
  949. package/examples/README.md +21 -0
  950. package/examples/custom-tools/README.md +104 -0
  951. package/examples/custom-tools/hello/index.ts +20 -0
  952. package/examples/extensions/README.md +120 -0
  953. package/examples/extensions/api-demo.ts +79 -0
  954. package/examples/extensions/chalk-logger.ts +25 -0
  955. package/examples/extensions/hello.ts +31 -0
  956. package/examples/extensions/pirate.ts +43 -0
  957. package/examples/extensions/plan-mode.ts +549 -0
  958. package/examples/extensions/reload-runtime.ts +38 -0
  959. package/examples/extensions/tools.ts +144 -0
  960. package/examples/extensions/with-deps/index.ts +36 -0
  961. package/examples/extensions/with-deps/package-lock.json +31 -0
  962. package/examples/extensions/with-deps/package.json +16 -0
  963. package/examples/hooks/README.md +56 -0
  964. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  965. package/examples/hooks/confirm-destructive.ts +58 -0
  966. package/examples/hooks/custom-compaction.ts +115 -0
  967. package/examples/hooks/dirty-repo-guard.ts +51 -0
  968. package/examples/hooks/file-trigger.ts +40 -0
  969. package/examples/hooks/git-checkpoint.ts +52 -0
  970. package/examples/hooks/handoff.ts +149 -0
  971. package/examples/hooks/permission-gate.ts +33 -0
  972. package/examples/hooks/protected-paths.ts +29 -0
  973. package/examples/hooks/qna.ts +118 -0
  974. package/examples/hooks/status-line.ts +39 -0
  975. package/examples/sdk/01-minimal.ts +21 -0
  976. package/examples/sdk/02-custom-model.ts +49 -0
  977. package/examples/sdk/03-custom-prompt.ts +46 -0
  978. package/examples/sdk/04-skills.ts +43 -0
  979. package/examples/sdk/06-extensions.ts +82 -0
  980. package/examples/sdk/06-hooks.ts +61 -0
  981. package/examples/sdk/07-context-files.ts +35 -0
  982. package/examples/sdk/08-prompt-templates.ts +41 -0
  983. package/examples/sdk/08-slash-commands.ts +46 -0
  984. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  985. package/examples/sdk/11-sessions.ts +47 -0
  986. package/examples/sdk/README.md +172 -0
  987. package/package.json +539 -0
  988. package/scripts/build-binary.ts +84 -0
  989. package/scripts/format-prompts.ts +68 -0
  990. package/scripts/generate-docs-index.ts +71 -0
  991. package/scripts/generate-template.ts +33 -0
  992. package/src/async/index.ts +2 -0
  993. package/src/async/job-manager.ts +1299 -0
  994. package/src/async/support.ts +6 -0
  995. package/src/autoresearch/command-resume.md +14 -0
  996. package/src/autoresearch/dashboard.ts +446 -0
  997. package/src/autoresearch/git.ts +319 -0
  998. package/src/autoresearch/helpers.ts +218 -0
  999. package/src/autoresearch/index.ts +536 -0
  1000. package/src/autoresearch/prompt-setup.md +43 -0
  1001. package/src/autoresearch/prompt.md +103 -0
  1002. package/src/autoresearch/resume-message.md +10 -0
  1003. package/src/autoresearch/state.ts +273 -0
  1004. package/src/autoresearch/storage.ts +699 -0
  1005. package/src/autoresearch/tools/init-experiment.ts +272 -0
  1006. package/src/autoresearch/tools/log-experiment.ts +524 -0
  1007. package/src/autoresearch/tools/run-experiment.ts +475 -0
  1008. package/src/autoresearch/tools/update-notes.ts +109 -0
  1009. package/src/autoresearch/types.ts +168 -0
  1010. package/src/bun-imports.d.ts +28 -0
  1011. package/src/capability/context-file.ts +44 -0
  1012. package/src/capability/extension-module.ts +34 -0
  1013. package/src/capability/extension.ts +47 -0
  1014. package/src/capability/fs.ts +107 -0
  1015. package/src/capability/hook.ts +40 -0
  1016. package/src/capability/index.ts +436 -0
  1017. package/src/capability/instruction.ts +37 -0
  1018. package/src/capability/mcp.ts +74 -0
  1019. package/src/capability/prompt.ts +35 -0
  1020. package/src/capability/rule.ts +244 -0
  1021. package/src/capability/settings.ts +34 -0
  1022. package/src/capability/skill.ts +56 -0
  1023. package/src/capability/slash-command.ts +40 -0
  1024. package/src/capability/ssh.ts +41 -0
  1025. package/src/capability/system-prompt.ts +34 -0
  1026. package/src/capability/tool.ts +38 -0
  1027. package/src/capability/types.ts +168 -0
  1028. package/src/cli/agents-cli.ts +141 -0
  1029. package/src/cli/args.ts +309 -0
  1030. package/src/cli/auth-broker-cli.ts +747 -0
  1031. package/src/cli/auth-gateway-cli.ts +411 -0
  1032. package/src/cli/classify-install-target.ts +50 -0
  1033. package/src/cli/commands/init-xdg.ts +27 -0
  1034. package/src/cli/config-cli.ts +426 -0
  1035. package/src/cli/fast-help.ts +80 -0
  1036. package/src/cli/file-processor.ts +123 -0
  1037. package/src/cli/grep-cli.ts +160 -0
  1038. package/src/cli/initial-message.ts +58 -0
  1039. package/src/cli/list-models.ts +194 -0
  1040. package/src/cli/notify-cli.ts +135 -0
  1041. package/src/cli/plugin-cli.ts +942 -0
  1042. package/src/cli/read-cli.ts +57 -0
  1043. package/src/cli/session-picker.ts +52 -0
  1044. package/src/cli/setup-cli.ts +537 -0
  1045. package/src/cli/shell-cli.ts +176 -0
  1046. package/src/cli/skills-cli.ts +88 -0
  1047. package/src/cli/ssh-cli.ts +179 -0
  1048. package/src/cli/stats-cli.ts +238 -0
  1049. package/src/cli/update-cli.ts +468 -0
  1050. package/src/cli/web-search-cli.ts +133 -0
  1051. package/src/cli/worktree-cli.ts +291 -0
  1052. package/src/cli.ts +150 -0
  1053. package/src/commands/acp.ts +24 -0
  1054. package/src/commands/agents.ts +57 -0
  1055. package/src/commands/auth-broker.ts +96 -0
  1056. package/src/commands/auth-gateway.ts +63 -0
  1057. package/src/commands/chat.ts +25 -0
  1058. package/src/commands/codex-native-hook.ts +12 -0
  1059. package/src/commands/commit.ts +46 -0
  1060. package/src/commands/config.ts +51 -0
  1061. package/src/commands/contribution-prep.ts +41 -0
  1062. package/src/commands/coordinator.ts +71 -0
  1063. package/src/commands/goal.ts +37 -0
  1064. package/src/commands/grep.ts +48 -0
  1065. package/src/commands/harness.ts +1137 -0
  1066. package/src/commands/interview.ts +42 -0
  1067. package/src/commands/launch.ts +178 -0
  1068. package/src/commands/mcp-serve.ts +63 -0
  1069. package/src/commands/memory.ts +34 -0
  1070. package/src/commands/notify.ts +45 -0
  1071. package/src/commands/orchestrate.ts +55 -0
  1072. package/src/commands/planphase.ts +19 -0
  1073. package/src/commands/plugin.ts +78 -0
  1074. package/src/commands/ralplan.ts +25 -0
  1075. package/src/commands/read.ts +35 -0
  1076. package/src/commands/session.ts +151 -0
  1077. package/src/commands/setup.ts +85 -0
  1078. package/src/commands/shell.ts +29 -0
  1079. package/src/commands/skills.ts +49 -0
  1080. package/src/commands/ssh.ts +60 -0
  1081. package/src/commands/state.ts +25 -0
  1082. package/src/commands/stats.ts +29 -0
  1083. package/src/commands/team.ts +217 -0
  1084. package/src/commands/ultragoal.ts +40 -0
  1085. package/src/commands/update.ts +21 -0
  1086. package/src/commands/web-search.ts +42 -0
  1087. package/src/commands/worktree.ts +57 -0
  1088. package/src/commit/agentic/agent.ts +316 -0
  1089. package/src/commit/agentic/fallback.ts +96 -0
  1090. package/src/commit/agentic/index.ts +356 -0
  1091. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  1092. package/src/commit/agentic/prompts/session-user.md +25 -0
  1093. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  1094. package/src/commit/agentic/prompts/system.md +38 -0
  1095. package/src/commit/agentic/state.ts +60 -0
  1096. package/src/commit/agentic/tools/analyze-file.ts +127 -0
  1097. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  1098. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  1099. package/src/commit/agentic/tools/git-overview.ts +81 -0
  1100. package/src/commit/agentic/tools/index.ts +54 -0
  1101. package/src/commit/agentic/tools/propose-changelog.ts +144 -0
  1102. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  1103. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  1104. package/src/commit/agentic/tools/schemas.ts +23 -0
  1105. package/src/commit/agentic/tools/split-commit.ts +238 -0
  1106. package/src/commit/agentic/topo-sort.ts +44 -0
  1107. package/src/commit/agentic/trivial.ts +51 -0
  1108. package/src/commit/agentic/validation.ts +183 -0
  1109. package/src/commit/analysis/conventional.ts +64 -0
  1110. package/src/commit/analysis/index.ts +4 -0
  1111. package/src/commit/analysis/scope.ts +242 -0
  1112. package/src/commit/analysis/summary.ts +105 -0
  1113. package/src/commit/analysis/validation.ts +66 -0
  1114. package/src/commit/changelog/detect.ts +40 -0
  1115. package/src/commit/changelog/generate.ts +97 -0
  1116. package/src/commit/changelog/index.ts +234 -0
  1117. package/src/commit/changelog/parse.ts +44 -0
  1118. package/src/commit/cli.ts +85 -0
  1119. package/src/commit/git/diff.ts +148 -0
  1120. package/src/commit/index.ts +5 -0
  1121. package/src/commit/map-reduce/index.ts +69 -0
  1122. package/src/commit/map-reduce/map-phase.ts +193 -0
  1123. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  1124. package/src/commit/map-reduce/utils.ts +9 -0
  1125. package/src/commit/message.ts +11 -0
  1126. package/src/commit/model-selection.ts +51 -0
  1127. package/src/commit/pipeline.ts +244 -0
  1128. package/src/commit/prompts/analysis-system.md +148 -0
  1129. package/src/commit/prompts/analysis-user.md +38 -0
  1130. package/src/commit/prompts/changelog-system.md +50 -0
  1131. package/src/commit/prompts/changelog-user.md +18 -0
  1132. package/src/commit/prompts/file-observer-system.md +24 -0
  1133. package/src/commit/prompts/file-observer-user.md +8 -0
  1134. package/src/commit/prompts/reduce-system.md +50 -0
  1135. package/src/commit/prompts/reduce-user.md +17 -0
  1136. package/src/commit/prompts/summary-retry.md +3 -0
  1137. package/src/commit/prompts/summary-system.md +38 -0
  1138. package/src/commit/prompts/summary-user.md +13 -0
  1139. package/src/commit/prompts/types-description.md +2 -0
  1140. package/src/commit/shared-llm.ts +77 -0
  1141. package/src/commit/types.ts +118 -0
  1142. package/src/commit/utils/exclusions.ts +42 -0
  1143. package/src/commit/utils.ts +58 -0
  1144. package/src/config/config-file.ts +232 -0
  1145. package/src/config/file-lock.ts +121 -0
  1146. package/src/config/keybindings.ts +553 -0
  1147. package/src/config/mcp-schema.json +230 -0
  1148. package/src/config/model-equivalence.ts +816 -0
  1149. package/src/config/model-profile-activation.ts +165 -0
  1150. package/src/config/model-profiles.ts +188 -0
  1151. package/src/config/model-registry.ts +2751 -0
  1152. package/src/config/model-resolver.ts +1355 -0
  1153. package/src/config/models-config-schema.ts +254 -0
  1154. package/src/config/oauth-provider-aliases.ts +49 -0
  1155. package/src/config/prompt-templates.ts +310 -0
  1156. package/src/config/resolve-config-value.ts +94 -0
  1157. package/src/config/settings-schema.ts +3235 -0
  1158. package/src/config/settings.ts +979 -0
  1159. package/src/config/skill-settings-defaults.ts +35 -0
  1160. package/src/config.ts +212 -0
  1161. package/src/coordinator/contract.ts +20 -0
  1162. package/src/coordinator-mcp/policy.ts +174 -0
  1163. package/src/coordinator-mcp/safety.ts +80 -0
  1164. package/src/coordinator-mcp/server.ts +1319 -0
  1165. package/src/cursor.ts +372 -0
  1166. package/src/dap/client.ts +689 -0
  1167. package/src/dap/config.ts +150 -0
  1168. package/src/dap/defaults.json +211 -0
  1169. package/src/dap/index.ts +4 -0
  1170. package/src/dap/session.ts +1306 -0
  1171. package/src/dap/types.ts +600 -0
  1172. package/src/debug/crash-diagnostics.ts +223 -0
  1173. package/src/debug/index.ts +459 -0
  1174. package/src/debug/log-formatting.ts +58 -0
  1175. package/src/debug/log-viewer.ts +908 -0
  1176. package/src/debug/profiler.ts +162 -0
  1177. package/src/debug/raw-sse-buffer.ts +270 -0
  1178. package/src/debug/raw-sse.ts +213 -0
  1179. package/src/debug/report-bundle.ts +365 -0
  1180. package/src/debug/runtime-gauges.ts +20 -0
  1181. package/src/debug/system-info.ts +111 -0
  1182. package/src/defaults/jwc/mcp-defaults.json +8 -0
  1183. package/src/defaults/jwc/skill-fragments/browse/web-ai.md +130 -0
  1184. package/src/defaults/jwc/skills/browse/SKILL.md +113 -0
  1185. package/src/defaults/jwc/skills/goal/SKILL.md +323 -0
  1186. package/src/defaults/jwc/skills/goal/ai-slop-cleaner.md +61 -0
  1187. package/src/defaults/jwc/skills/jaw-interview/SKILL.md +891 -0
  1188. package/src/defaults/jwc/skills/jaw-interview/auto-answer-uncertain.md +37 -0
  1189. package/src/defaults/jwc/skills/jaw-interview/auto-research-greenfield.md +42 -0
  1190. package/src/defaults/jwc/skills/plan/SKILL.md +201 -0
  1191. package/src/defaults/jwc/skills/search/SKILL.md +120 -0
  1192. package/src/defaults/jwc/skills/team/SKILL.md +441 -0
  1193. package/src/defaults/jwc-defaults.ts +316 -0
  1194. package/src/discovery/agents-md.ts +67 -0
  1195. package/src/discovery/agents.ts +230 -0
  1196. package/src/discovery/builtin.ts +932 -0
  1197. package/src/discovery/claude-plugins.ts +407 -0
  1198. package/src/discovery/claude.ts +313 -0
  1199. package/src/discovery/cli-jaw.ts +36 -0
  1200. package/src/discovery/cline.ts +83 -0
  1201. package/src/discovery/codex.ts +330 -0
  1202. package/src/discovery/cursor.ts +220 -0
  1203. package/src/discovery/gemini.ts +383 -0
  1204. package/src/discovery/github.ts +118 -0
  1205. package/src/discovery/helpers.ts +1010 -0
  1206. package/src/discovery/index.ts +77 -0
  1207. package/src/discovery/mcp-json.ts +171 -0
  1208. package/src/discovery/opencode.ts +398 -0
  1209. package/src/discovery/plugin-dir-roots.ts +28 -0
  1210. package/src/discovery/ssh.ts +153 -0
  1211. package/src/discovery/substitute-plugin-root.ts +29 -0
  1212. package/src/discovery/vscode.ts +105 -0
  1213. package/src/discovery/windsurf.ts +147 -0
  1214. package/src/edit/apply-patch/index.ts +87 -0
  1215. package/src/edit/apply-patch/parser.ts +174 -0
  1216. package/src/edit/diff.ts +873 -0
  1217. package/src/edit/file-read-cache.ts +95 -0
  1218. package/src/edit/index.ts +528 -0
  1219. package/src/edit/modes/apply-patch.lark +19 -0
  1220. package/src/edit/modes/apply-patch.ts +53 -0
  1221. package/src/edit/modes/patch.ts +1835 -0
  1222. package/src/edit/modes/replace.ts +1161 -0
  1223. package/src/edit/normalize.ts +375 -0
  1224. package/src/edit/notebook.ts +222 -0
  1225. package/src/edit/read-file.ts +25 -0
  1226. package/src/edit/renderer.ts +683 -0
  1227. package/src/edit/streaming.ts +561 -0
  1228. package/src/eval/backend.ts +43 -0
  1229. package/src/eval/index.ts +4 -0
  1230. package/src/eval/js/context-manager.ts +414 -0
  1231. package/src/eval/js/executor.ts +134 -0
  1232. package/src/eval/js/index.ts +46 -0
  1233. package/src/eval/js/shared/helpers.ts +237 -0
  1234. package/src/eval/js/shared/indirect-eval.ts +30 -0
  1235. package/src/eval/js/shared/prelude.ts +2 -0
  1236. package/src/eval/js/shared/prelude.txt +70 -0
  1237. package/src/eval/js/shared/rewrite-imports.ts +415 -0
  1238. package/src/eval/js/shared/runtime.ts +301 -0
  1239. package/src/eval/js/shared/types.ts +18 -0
  1240. package/src/eval/js/tool-bridge.ts +144 -0
  1241. package/src/eval/js/worker-core.ts +146 -0
  1242. package/src/eval/js/worker-entry.ts +24 -0
  1243. package/src/eval/js/worker-protocol.ts +41 -0
  1244. package/src/eval/py/display.ts +71 -0
  1245. package/src/eval/py/executor.ts +622 -0
  1246. package/src/eval/py/index.ts +58 -0
  1247. package/src/eval/py/kernel.ts +683 -0
  1248. package/src/eval/py/prelude.py +462 -0
  1249. package/src/eval/py/prelude.ts +3 -0
  1250. package/src/eval/py/runner.py +910 -0
  1251. package/src/eval/py/runtime.ts +210 -0
  1252. package/src/eval/py/tool-bridge.ts +137 -0
  1253. package/src/eval/types.ts +48 -0
  1254. package/src/exa/factory.ts +60 -0
  1255. package/src/exa/index.ts +27 -0
  1256. package/src/exa/mcp-client.ts +364 -0
  1257. package/src/exa/render.ts +244 -0
  1258. package/src/exa/researcher.ts +36 -0
  1259. package/src/exa/search.ts +47 -0
  1260. package/src/exa/types.ts +166 -0
  1261. package/src/exa/websets.ts +248 -0
  1262. package/src/exec/bash-executor.ts +362 -0
  1263. package/src/exec/exec.ts +53 -0
  1264. package/src/exec/idle-timeout-watchdog.ts +126 -0
  1265. package/src/exec/non-interactive-env.ts +73 -0
  1266. package/src/export/custom-share.ts +65 -0
  1267. package/src/export/html/index.ts +168 -0
  1268. package/src/export/html/template.css +1060 -0
  1269. package/src/export/html/template.generated.ts +2 -0
  1270. package/src/export/html/template.html +47 -0
  1271. package/src/export/html/template.js +2268 -0
  1272. package/src/export/html/template.macro.ts +25 -0
  1273. package/src/export/html/vendor/highlight.min.js +1213 -0
  1274. package/src/export/html/vendor/marked.min.js +6 -0
  1275. package/src/export/ttsr.ts +444 -0
  1276. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
  1277. package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
  1278. package/src/extensibility/custom-commands/index.ts +2 -0
  1279. package/src/extensibility/custom-commands/loader.ts +238 -0
  1280. package/src/extensibility/custom-commands/types.ts +113 -0
  1281. package/src/extensibility/custom-tools/index.ts +7 -0
  1282. package/src/extensibility/custom-tools/loader.ts +245 -0
  1283. package/src/extensibility/custom-tools/types.ts +255 -0
  1284. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  1285. package/src/extensibility/extensions/compact-handler.ts +40 -0
  1286. package/src/extensibility/extensions/get-commands-handler.ts +80 -0
  1287. package/src/extensibility/extensions/index.ts +15 -0
  1288. package/src/extensibility/extensions/loader.ts +555 -0
  1289. package/src/extensibility/extensions/runner.ts +900 -0
  1290. package/src/extensibility/extensions/types.ts +1314 -0
  1291. package/src/extensibility/extensions/wrapper.ts +189 -0
  1292. package/src/extensibility/hooks/index.ts +5 -0
  1293. package/src/extensibility/hooks/loader.ts +257 -0
  1294. package/src/extensibility/hooks/runner.ts +425 -0
  1295. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  1296. package/src/extensibility/hooks/types.ts +599 -0
  1297. package/src/extensibility/jwc-plugins/activation.ts +87 -0
  1298. package/src/extensibility/jwc-plugins/index.ts +9 -0
  1299. package/src/extensibility/jwc-plugins/injection.ts +114 -0
  1300. package/src/extensibility/jwc-plugins/loader.ts +131 -0
  1301. package/src/extensibility/jwc-plugins/paths.ts +66 -0
  1302. package/src/extensibility/jwc-plugins/schema.ts +79 -0
  1303. package/src/extensibility/jwc-plugins/state.ts +29 -0
  1304. package/src/extensibility/jwc-plugins/tools.ts +47 -0
  1305. package/src/extensibility/jwc-plugins/types.ts +97 -0
  1306. package/src/extensibility/jwc-plugins/validation.ts +76 -0
  1307. package/src/extensibility/plugins/doctor.ts +66 -0
  1308. package/src/extensibility/plugins/git-url.ts +281 -0
  1309. package/src/extensibility/plugins/index.ts +9 -0
  1310. package/src/extensibility/plugins/installer.ts +192 -0
  1311. package/src/extensibility/plugins/legacy-pi-compat.ts +336 -0
  1312. package/src/extensibility/plugins/loader.ts +288 -0
  1313. package/src/extensibility/plugins/manager.ts +739 -0
  1314. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  1315. package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
  1316. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  1317. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  1318. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  1319. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  1320. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  1321. package/src/extensibility/plugins/parser.ts +105 -0
  1322. package/src/extensibility/plugins/types.ts +194 -0
  1323. package/src/extensibility/shared-events.ts +344 -0
  1324. package/src/extensibility/skills.ts +449 -0
  1325. package/src/extensibility/slash-commands.ts +249 -0
  1326. package/src/extensibility/tool-proxy.ts +25 -0
  1327. package/src/extensibility/typebox.ts +418 -0
  1328. package/src/extensibility/utils.ts +44 -0
  1329. package/src/goals/goal-planning-start.ts +21 -0
  1330. package/src/goals/index.ts +3 -0
  1331. package/src/goals/runtime.ts +418 -0
  1332. package/src/goals/state.ts +70 -0
  1333. package/src/goals/tools/goal-tool.ts +230 -0
  1334. package/src/harness-control-plane/classifier.ts +128 -0
  1335. package/src/harness-control-plane/control-endpoint.ts +148 -0
  1336. package/src/harness-control-plane/finalize.ts +306 -0
  1337. package/src/harness-control-plane/operate.ts +225 -0
  1338. package/src/harness-control-plane/owner.ts +664 -0
  1339. package/src/harness-control-plane/phase-rollup.ts +96 -0
  1340. package/src/harness-control-plane/preserve.ts +102 -0
  1341. package/src/harness-control-plane/receipt-ingest.ts +127 -0
  1342. package/src/harness-control-plane/receipt-spool.ts +147 -0
  1343. package/src/harness-control-plane/receipts.ts +320 -0
  1344. package/src/harness-control-plane/rpc-adapter.ts +282 -0
  1345. package/src/harness-control-plane/seams.ts +39 -0
  1346. package/src/harness-control-plane/session-lease.ts +388 -0
  1347. package/src/harness-control-plane/state-machine.ts +119 -0
  1348. package/src/harness-control-plane/storage.ts +423 -0
  1349. package/src/harness-control-plane/types.ts +240 -0
  1350. package/src/hashline/anchors.ts +113 -0
  1351. package/src/hashline/apply.ts +737 -0
  1352. package/src/hashline/bigrams.json +649 -0
  1353. package/src/hashline/constants.ts +25 -0
  1354. package/src/hashline/diff-preview.ts +43 -0
  1355. package/src/hashline/diff.ts +56 -0
  1356. package/src/hashline/execute.ts +267 -0
  1357. package/src/hashline/grammar.lark +21 -0
  1358. package/src/hashline/hash.ts +196 -0
  1359. package/src/hashline/index.ts +13 -0
  1360. package/src/hashline/input.ts +130 -0
  1361. package/src/hashline/parser.ts +246 -0
  1362. package/src/hashline/prefixes.ts +101 -0
  1363. package/src/hashline/recovery.ts +113 -0
  1364. package/src/hashline/stream.ts +123 -0
  1365. package/src/hashline/types.ts +68 -0
  1366. package/src/hindsight/backend.ts +205 -0
  1367. package/src/hindsight/bank.ts +131 -0
  1368. package/src/hindsight/client.ts +598 -0
  1369. package/src/hindsight/config.ts +175 -0
  1370. package/src/hindsight/content.ts +211 -0
  1371. package/src/hindsight/index.ts +8 -0
  1372. package/src/hindsight/mental-models.ts +382 -0
  1373. package/src/hindsight/seeds.json +32 -0
  1374. package/src/hindsight/state.ts +469 -0
  1375. package/src/hindsight/transcript.ts +71 -0
  1376. package/src/hooks/codex-native-hooks-config.ts +143 -0
  1377. package/src/hooks/native-skill-hook.ts +305 -0
  1378. package/src/hooks/skill-keywords.ts +95 -0
  1379. package/src/hooks/skill-state.ts +654 -0
  1380. package/src/index.ts +56 -0
  1381. package/src/internal-urls/agent-protocol.ts +176 -0
  1382. package/src/internal-urls/artifact-protocol.ts +75 -0
  1383. package/src/internal-urls/docs-index.generated.ts +92 -0
  1384. package/src/internal-urls/index.ts +21 -0
  1385. package/src/internal-urls/issue-pr-protocol.ts +577 -0
  1386. package/src/internal-urls/json-query.ts +126 -0
  1387. package/src/internal-urls/jwc-protocol.ts +91 -0
  1388. package/src/internal-urls/local-protocol.ts +249 -0
  1389. package/src/internal-urls/mcp-protocol.ts +151 -0
  1390. package/src/internal-urls/memory-protocol.ts +165 -0
  1391. package/src/internal-urls/parse.ts +72 -0
  1392. package/src/internal-urls/registry-helpers.ts +28 -0
  1393. package/src/internal-urls/router.ts +79 -0
  1394. package/src/internal-urls/rule-protocol.ts +38 -0
  1395. package/src/internal-urls/skill-protocol.ts +103 -0
  1396. package/src/internal-urls/types.ts +114 -0
  1397. package/src/jaw-interview/structured-renderer.ts +83 -0
  1398. package/src/jwc-runtime/actor-registry.ts +365 -0
  1399. package/src/jwc-runtime/agent-identity.ts +16 -0
  1400. package/src/jwc-runtime/cli-jaw-home.ts +14 -0
  1401. package/src/jwc-runtime/cli-jaw-vocab.ts +69 -0
  1402. package/src/jwc-runtime/cli-write-receipt.ts +31 -0
  1403. package/src/jwc-runtime/goal-cli.ts +428 -0
  1404. package/src/jwc-runtime/goal-engine.ts +2214 -0
  1405. package/src/jwc-runtime/goal-guard.ts +300 -0
  1406. package/src/jwc-runtime/goal-mode-request.ts +331 -0
  1407. package/src/jwc-runtime/jaw-interview-runtime.ts +809 -0
  1408. package/src/jwc-runtime/launch-tmux.ts +296 -0
  1409. package/src/jwc-runtime/launch-worktree.ts +291 -0
  1410. package/src/jwc-runtime/legacy-storage.ts +132 -0
  1411. package/src/jwc-runtime/memory-runtime.ts +434 -0
  1412. package/src/jwc-runtime/orchestrate-runtime.ts +612 -0
  1413. package/src/jwc-runtime/orchestrate-state.ts +395 -0
  1414. package/src/jwc-runtime/plan-writer.ts +661 -0
  1415. package/src/jwc-runtime/restricted-role-agent-bash.ts +5 -0
  1416. package/src/jwc-runtime/session-state-sidecar.ts +89 -0
  1417. package/src/jwc-runtime/stage-skill-map.ts +48 -0
  1418. package/src/jwc-runtime/state-graph.ts +86 -0
  1419. package/src/jwc-runtime/state-migrations.ts +183 -0
  1420. package/src/jwc-runtime/state-renderer.ts +340 -0
  1421. package/src/jwc-runtime/state-runtime.ts +1978 -0
  1422. package/src/jwc-runtime/state-schema.ts +218 -0
  1423. package/src/jwc-runtime/state-validation.ts +50 -0
  1424. package/src/jwc-runtime/state-writer.ts +765 -0
  1425. package/src/jwc-runtime/team-runtime.ts +4181 -0
  1426. package/src/jwc-runtime/tmux-common.ts +130 -0
  1427. package/src/jwc-runtime/tmux-sessions.ts +169 -0
  1428. package/src/jwc-runtime/workflow-command-ref.ts +240 -0
  1429. package/src/jwc-runtime/workflow-manifest.generated.json +1606 -0
  1430. package/src/jwc-runtime/workflow-manifest.ts +427 -0
  1431. package/src/lsp/client.ts +974 -0
  1432. package/src/lsp/clients/biome-client.ts +202 -0
  1433. package/src/lsp/clients/index.ts +50 -0
  1434. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  1435. package/src/lsp/clients/swiftlint-client.ts +120 -0
  1436. package/src/lsp/config.ts +492 -0
  1437. package/src/lsp/defaults.json +493 -0
  1438. package/src/lsp/edits.ts +166 -0
  1439. package/src/lsp/index.ts +2282 -0
  1440. package/src/lsp/lspmux.ts +233 -0
  1441. package/src/lsp/render.ts +692 -0
  1442. package/src/lsp/startup-events.ts +13 -0
  1443. package/src/lsp/types.ts +443 -0
  1444. package/src/lsp/utils.ts +678 -0
  1445. package/src/main.ts +1159 -0
  1446. package/src/mem0/backend.ts +75 -0
  1447. package/src/mem0/client.ts +122 -0
  1448. package/src/mem0/config.ts +82 -0
  1449. package/src/mem0/index.ts +5 -0
  1450. package/src/mem0/scope.ts +54 -0
  1451. package/src/mem0/state.ts +142 -0
  1452. package/src/memories/index.ts +1111 -0
  1453. package/src/memories/local-query.ts +699 -0
  1454. package/src/memories/memory-config.ts +175 -0
  1455. package/src/memories/memory-fts.ts +179 -0
  1456. package/src/memories/memory-model-resolution.ts +84 -0
  1457. package/src/memories/memory-quality.ts +163 -0
  1458. package/src/memories/storage.ts +588 -0
  1459. package/src/memory-backend/index.ts +4 -0
  1460. package/src/memory-backend/local-backend.ts +50 -0
  1461. package/src/memory-backend/off-backend.ts +16 -0
  1462. package/src/memory-backend/resolve.ts +27 -0
  1463. package/src/memory-backend/types.ts +82 -0
  1464. package/src/migrate-config-dir-startup.ts +15 -0
  1465. package/src/migrate-config-dir.ts +80 -0
  1466. package/src/modes/acp/acp-agent.ts +2218 -0
  1467. package/src/modes/acp/acp-client-bridge.ts +154 -0
  1468. package/src/modes/acp/acp-event-mapper.ts +912 -0
  1469. package/src/modes/acp/acp-mode.ts +23 -0
  1470. package/src/modes/acp/index.ts +2 -0
  1471. package/src/modes/acp/terminal-auth.ts +37 -0
  1472. package/src/modes/background-row-model.ts +17 -0
  1473. package/src/modes/bridge/auth.ts +41 -0
  1474. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  1475. package/src/modes/bridge/bridge-mode.ts +644 -0
  1476. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  1477. package/src/modes/bridge/event-stream.ts +70 -0
  1478. package/src/modes/components/_cli-jaw-entry.ts +36 -0
  1479. package/src/modes/components/agent-dashboard.ts +1120 -0
  1480. package/src/modes/components/assistant-message.ts +361 -0
  1481. package/src/modes/components/background-footer-detail.ts +27 -0
  1482. package/src/modes/components/background-footer-panel-model.ts +96 -0
  1483. package/src/modes/components/background-footer-panel.ts +89 -0
  1484. package/src/modes/components/bash-execution.ts +261 -0
  1485. package/src/modes/components/bordered-loader.ts +41 -0
  1486. package/src/modes/components/branch-summary-message.ts +65 -0
  1487. package/src/modes/components/btw-panel.ts +104 -0
  1488. package/src/modes/components/compaction-summary-message.ts +71 -0
  1489. package/src/modes/components/composer-chrome.ts +42 -0
  1490. package/src/modes/components/composer-footer.ts +113 -0
  1491. package/src/modes/components/countdown-timer.ts +75 -0
  1492. package/src/modes/components/custom-editor.ts +515 -0
  1493. package/src/modes/components/custom-message.ts +85 -0
  1494. package/src/modes/components/custom-provider-wizard.ts +318 -0
  1495. package/src/modes/components/diff.ts +363 -0
  1496. package/src/modes/components/dynamic-border.ts +25 -0
  1497. package/src/modes/components/eval-execution.ts +196 -0
  1498. package/src/modes/components/execution-shared.ts +112 -0
  1499. package/src/modes/components/extensions/extension-dashboard.ts +350 -0
  1500. package/src/modes/components/extensions/extension-list.ts +492 -0
  1501. package/src/modes/components/extensions/index.ts +9 -0
  1502. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  1503. package/src/modes/components/extensions/state-manager.ts +628 -0
  1504. package/src/modes/components/extensions/types.ts +191 -0
  1505. package/src/modes/components/footer.ts +270 -0
  1506. package/src/modes/components/full-transcript-overlay.ts +133 -0
  1507. package/src/modes/components/help-selector.ts +274 -0
  1508. package/src/modes/components/history-search.ts +158 -0
  1509. package/src/modes/components/hook-editor.ts +151 -0
  1510. package/src/modes/components/hook-input.ts +79 -0
  1511. package/src/modes/components/hook-message.ts +68 -0
  1512. package/src/modes/components/hook-selector.ts +873 -0
  1513. package/src/modes/components/index.ts +36 -0
  1514. package/src/modes/components/jobs-overlay-model.ts +109 -0
  1515. package/src/modes/components/jobs-overlay.ts +172 -0
  1516. package/src/modes/components/keybinding-hints.ts +65 -0
  1517. package/src/modes/components/login-dialog.ts +164 -0
  1518. package/src/modes/components/message-frame.ts +88 -0
  1519. package/src/modes/components/model-selector.ts +1653 -0
  1520. package/src/modes/components/oauth-selector.ts +248 -0
  1521. package/src/modes/components/pabcd-border.ts +98 -0
  1522. package/src/modes/components/plugin-selector.ts +95 -0
  1523. package/src/modes/components/plugin-settings.ts +489 -0
  1524. package/src/modes/components/provider-onboarding-selector.ts +94 -0
  1525. package/src/modes/components/queue-mode-selector.ts +56 -0
  1526. package/src/modes/components/quota-panel.ts +101 -0
  1527. package/src/modes/components/read-tool-group.ts +295 -0
  1528. package/src/modes/components/runtime-mcp-add-wizard.ts +1341 -0
  1529. package/src/modes/components/session-observer-overlay.ts +839 -0
  1530. package/src/modes/components/session-selector.ts +343 -0
  1531. package/src/modes/components/settings-defs.ts +175 -0
  1532. package/src/modes/components/settings-selector.ts +644 -0
  1533. package/src/modes/components/show-images-selector.ts +45 -0
  1534. package/src/modes/components/skill-hud/render.ts +93 -0
  1535. package/src/modes/components/skill-message.ts +110 -0
  1536. package/src/modes/components/status-line/context-thresholds.ts +68 -0
  1537. package/src/modes/components/status-line/git-utils.ts +42 -0
  1538. package/src/modes/components/status-line/index.ts +4 -0
  1539. package/src/modes/components/status-line/presets.ts +107 -0
  1540. package/src/modes/components/status-line/segments.ts +657 -0
  1541. package/src/modes/components/status-line/separators.ts +55 -0
  1542. package/src/modes/components/status-line/token-rate.ts +66 -0
  1543. package/src/modes/components/status-line/types.ts +102 -0
  1544. package/src/modes/components/status-line/workflow-readers.ts +97 -0
  1545. package/src/modes/components/status-line.ts +917 -0
  1546. package/src/modes/components/theme-selector.ts +63 -0
  1547. package/src/modes/components/thinking-selector.ts +52 -0
  1548. package/src/modes/components/todo-reminder.ts +40 -0
  1549. package/src/modes/components/tool-execution.ts +990 -0
  1550. package/src/modes/components/tool-transcript-overlay.ts +81 -0
  1551. package/src/modes/components/tree-selector.ts +930 -0
  1552. package/src/modes/components/ttsr-notification.ts +100 -0
  1553. package/src/modes/components/user-message-selector.ts +141 -0
  1554. package/src/modes/components/user-message.ts +53 -0
  1555. package/src/modes/components/visual-truncate.ts +63 -0
  1556. package/src/modes/components/welcome.ts +522 -0
  1557. package/src/modes/controllers/btw-controller.ts +105 -0
  1558. package/src/modes/controllers/command-controller-shared.ts +108 -0
  1559. package/src/modes/controllers/command-controller.ts +1703 -0
  1560. package/src/modes/controllers/event-controller.ts +1054 -0
  1561. package/src/modes/controllers/extension-ui-controller.ts +1010 -0
  1562. package/src/modes/controllers/input-controller.ts +1422 -0
  1563. package/src/modes/controllers/runtime-mcp-command-controller.ts +1934 -0
  1564. package/src/modes/controllers/selector-controller.ts +1605 -0
  1565. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  1566. package/src/modes/controllers/todo-command-controller.ts +485 -0
  1567. package/src/modes/data/emojis.json +1 -0
  1568. package/src/modes/emoji-autocomplete.ts +285 -0
  1569. package/src/modes/index.ts +35 -0
  1570. package/src/modes/interactive-mode.ts +2938 -0
  1571. package/src/modes/jobs-observer.ts +352 -0
  1572. package/src/modes/oauth-manual-input.ts +42 -0
  1573. package/src/modes/print-mode.ts +121 -0
  1574. package/src/modes/prompt-action-autocomplete.ts +409 -0
  1575. package/src/modes/rpc/host-tools.ts +1 -0
  1576. package/src/modes/rpc/host-uris.ts +1 -0
  1577. package/src/modes/rpc/rpc-client.ts +914 -0
  1578. package/src/modes/rpc/rpc-mode.ts +784 -0
  1579. package/src/modes/rpc/rpc-types.ts +601 -0
  1580. package/src/modes/runtime-init.ts +116 -0
  1581. package/src/modes/session-observer-registry.ts +146 -0
  1582. package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
  1583. package/src/modes/shared/agent-wire/command-contract.ts +18 -0
  1584. package/src/modes/shared/agent-wire/command-dispatch.ts +438 -0
  1585. package/src/modes/shared/agent-wire/command-validation.ts +164 -0
  1586. package/src/modes/shared/agent-wire/event-contract.ts +148 -0
  1587. package/src/modes/shared/agent-wire/event-envelope.ts +141 -0
  1588. package/src/modes/shared/agent-wire/event-observation.ts +424 -0
  1589. package/src/modes/shared/agent-wire/handshake.ts +157 -0
  1590. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  1591. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  1592. package/src/modes/shared/agent-wire/jaw-interview-gate.ts +253 -0
  1593. package/src/modes/shared/agent-wire/protocol.ts +46 -0
  1594. package/src/modes/shared/agent-wire/responses.ts +17 -0
  1595. package/src/modes/shared/agent-wire/scopes.ts +91 -0
  1596. package/src/modes/shared/agent-wire/session-registry.ts +109 -0
  1597. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  1598. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  1599. package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
  1600. package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
  1601. package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
  1602. package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
  1603. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
  1604. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
  1605. package/src/modes/shared.ts +55 -0
  1606. package/src/modes/theme/defaults/abyss-bite-light.json +124 -0
  1607. package/src/modes/theme/defaults/abyss-bite.json +124 -0
  1608. package/src/modes/theme/defaults/blue-crab.json +126 -0
  1609. package/src/modes/theme/defaults/index.ts +11 -0
  1610. package/src/modes/theme/defaults/red-claw.json +123 -0
  1611. package/src/modes/theme/mermaid-cache.ts +29 -0
  1612. package/src/modes/theme/shimmer.ts +219 -0
  1613. package/src/modes/theme/theme-schema.json +429 -0
  1614. package/src/modes/theme/theme.ts +2472 -0
  1615. package/src/modes/types.ts +343 -0
  1616. package/src/modes/utils/abort-message.ts +41 -0
  1617. package/src/modes/utils/compaction-progress.ts +174 -0
  1618. package/src/modes/utils/context-usage.ts +398 -0
  1619. package/src/modes/utils/help-markdown.ts +50 -0
  1620. package/src/modes/utils/hotkeys-markdown.ts +61 -0
  1621. package/src/modes/utils/keybinding-matchers.ts +30 -0
  1622. package/src/modes/utils/session-transcript-replay.ts +520 -0
  1623. package/src/modes/utils/tools-markdown.ts +27 -0
  1624. package/src/modes/utils/ui-helpers.ts +846 -0
  1625. package/src/notifications/ask-bridge.ts +74 -0
  1626. package/src/notifications/config-command-parser.ts +76 -0
  1627. package/src/notifications/config.ts +102 -0
  1628. package/src/notifications/daemon-control.ts +73 -0
  1629. package/src/notifications/daemon-engine.ts +165 -0
  1630. package/src/notifications/daemon-inbound.ts +220 -0
  1631. package/src/notifications/daemon-loop.ts +60 -0
  1632. package/src/notifications/daemon-owner.ts +53 -0
  1633. package/src/notifications/daemon-runtime.ts +84 -0
  1634. package/src/notifications/discovery.ts +119 -0
  1635. package/src/notifications/index.ts +34 -0
  1636. package/src/notifications/lifecycle-audit.ts +94 -0
  1637. package/src/notifications/lifecycle-command-parser.ts +51 -0
  1638. package/src/notifications/lifecycle-control-runtime.ts +127 -0
  1639. package/src/notifications/notification-redaction.ts +57 -0
  1640. package/src/notifications/protocol.ts +61 -0
  1641. package/src/notifications/remote-answer.ts +241 -0
  1642. package/src/notifications/reply-bridge.ts +85 -0
  1643. package/src/notifications/server.ts +280 -0
  1644. package/src/notifications/session-lifecycle.ts +46 -0
  1645. package/src/notifications/session-registry.ts +164 -0
  1646. package/src/notifications/telegram-api.ts +259 -0
  1647. package/src/notifications/telegram-ask-keyboard.ts +137 -0
  1648. package/src/notifications/telegram-callback-ingest.ts +126 -0
  1649. package/src/notifications/telegram-inbound-router.ts +166 -0
  1650. package/src/notifications/telegram-media-policy.ts +50 -0
  1651. package/src/notifications/telegram-media-render.ts +40 -0
  1652. package/src/notifications/telegram-message-ingest.ts +121 -0
  1653. package/src/notifications/telegram-pairing.ts +69 -0
  1654. package/src/notifications/threaded-lifecycle.ts +27 -0
  1655. package/src/notifications/threaded-shutdown.ts +37 -0
  1656. package/src/notifications/threaded-surface.ts +187 -0
  1657. package/src/notifications/transport-shell.ts +152 -0
  1658. package/src/notifications/transport-state.ts +161 -0
  1659. package/src/notifications/workspace-path-confinement.ts +65 -0
  1660. package/src/plan-mode/approved-plan.ts +163 -0
  1661. package/src/plan-mode/state.ts +6 -0
  1662. package/src/prompts/agents/architect.md +95 -0
  1663. package/src/prompts/agents/critic.md +68 -0
  1664. package/src/prompts/agents/executor.md +64 -0
  1665. package/src/prompts/agents/explore.md +58 -0
  1666. package/src/prompts/agents/frontmatter.md +14 -0
  1667. package/src/prompts/agents/init.md +34 -0
  1668. package/src/prompts/agents/plan.md +49 -0
  1669. package/src/prompts/agents/planner.md +63 -0
  1670. package/src/prompts/agents/reviewer.md +141 -0
  1671. package/src/prompts/agents/task.md +16 -0
  1672. package/src/prompts/ci-green-request.md +36 -0
  1673. package/src/prompts/goals/goal-continuation.md +33 -0
  1674. package/src/prompts/goals/goal-mode-active.md +23 -0
  1675. package/src/prompts/jaw/orchestrate-a.md +20 -0
  1676. package/src/prompts/jaw/orchestrate-audit-architect.md +17 -0
  1677. package/src/prompts/jaw/orchestrate-audit-planner.md +17 -0
  1678. package/src/prompts/jaw/orchestrate-b.md +17 -0
  1679. package/src/prompts/jaw/orchestrate-c.md +18 -0
  1680. package/src/prompts/jaw/orchestrate-d.md +32 -0
  1681. package/src/prompts/jaw/orchestrate-i.md +17 -0
  1682. package/src/prompts/jaw/orchestrate-p.md +23 -0
  1683. package/src/prompts/memories/consolidation.md +30 -0
  1684. package/src/prompts/memories/read-path.md +10 -0
  1685. package/src/prompts/memories/stage_one_input.md +6 -0
  1686. package/src/prompts/memories/stage_one_system.md +21 -0
  1687. package/src/prompts/memories/unavailable.md +9 -0
  1688. package/src/prompts/review-request.md +70 -0
  1689. package/src/prompts/system/agent-creation-architect.md +75 -0
  1690. package/src/prompts/system/agent-creation-user.md +6 -0
  1691. package/src/prompts/system/auto-continue.md +1 -0
  1692. package/src/prompts/system/btw-user.md +8 -0
  1693. package/src/prompts/system/commit-message-system.md +2 -0
  1694. package/src/prompts/system/custom-system-prompt.md +64 -0
  1695. package/src/prompts/system/eager-todo.md +13 -0
  1696. package/src/prompts/system/executor-self-fork.md +3 -0
  1697. package/src/prompts/system/irc-incoming.md +8 -0
  1698. package/src/prompts/system/plan-mode-active.md +116 -0
  1699. package/src/prompts/system/plan-mode-approved.md +28 -0
  1700. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  1701. package/src/prompts/system/plan-mode-reference.md +14 -0
  1702. package/src/prompts/system/plan-mode-subagent.md +34 -0
  1703. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  1704. package/src/prompts/system/project-prompt.md +46 -0
  1705. package/src/prompts/system/subagent-system-prompt.md +89 -0
  1706. package/src/prompts/system/subagent-user-prompt.md +3 -0
  1707. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  1708. package/src/prompts/system/system-prompt.md +409 -0
  1709. package/src/prompts/system/title-system.md +2 -0
  1710. package/src/prompts/system/ttsr-interrupt.md +7 -0
  1711. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  1712. package/src/prompts/system/web-search.md +25 -0
  1713. package/src/prompts/tools/apply-patch.md +65 -0
  1714. package/src/prompts/tools/ask.md +29 -0
  1715. package/src/prompts/tools/ast-edit.md +39 -0
  1716. package/src/prompts/tools/ast-grep.md +42 -0
  1717. package/src/prompts/tools/async-result.md +8 -0
  1718. package/src/prompts/tools/background.md +19 -0
  1719. package/src/prompts/tools/bash.md +50 -0
  1720. package/src/prompts/tools/browser.md +14 -0
  1721. package/src/prompts/tools/calculator.md +10 -0
  1722. package/src/prompts/tools/checkpoint.md +16 -0
  1723. package/src/prompts/tools/computer-use.md +15 -0
  1724. package/src/prompts/tools/cron.md +25 -0
  1725. package/src/prompts/tools/debug.md +33 -0
  1726. package/src/prompts/tools/eval.md +75 -0
  1727. package/src/prompts/tools/find.md +34 -0
  1728. package/src/prompts/tools/github.md +20 -0
  1729. package/src/prompts/tools/goal.md +20 -0
  1730. package/src/prompts/tools/hashline.md +130 -0
  1731. package/src/prompts/tools/image-gen.md +7 -0
  1732. package/src/prompts/tools/inspect-image-system.md +20 -0
  1733. package/src/prompts/tools/inspect-image.md +32 -0
  1734. package/src/prompts/tools/irc.md +49 -0
  1735. package/src/prompts/tools/job.md +22 -0
  1736. package/src/prompts/tools/lsp.md +42 -0
  1737. package/src/prompts/tools/monitor.md +32 -0
  1738. package/src/prompts/tools/patch.md +70 -0
  1739. package/src/prompts/tools/read.md +82 -0
  1740. package/src/prompts/tools/recall.md +6 -0
  1741. package/src/prompts/tools/recipe.md +16 -0
  1742. package/src/prompts/tools/reflect.md +6 -0
  1743. package/src/prompts/tools/render-mermaid.md +9 -0
  1744. package/src/prompts/tools/replace.md +36 -0
  1745. package/src/prompts/tools/resolve.md +9 -0
  1746. package/src/prompts/tools/retain.md +7 -0
  1747. package/src/prompts/tools/rewind.md +13 -0
  1748. package/src/prompts/tools/search-tool-bm25.md +37 -0
  1749. package/src/prompts/tools/search.md +25 -0
  1750. package/src/prompts/tools/skill.md +28 -0
  1751. package/src/prompts/tools/ssh.md +35 -0
  1752. package/src/prompts/tools/subagent.md +56 -0
  1753. package/src/prompts/tools/task-summary.md +22 -0
  1754. package/src/prompts/tools/task.md +106 -0
  1755. package/src/prompts/tools/todo-write.md +50 -0
  1756. package/src/prompts/tools/vim.md +98 -0
  1757. package/src/prompts/tools/web-search.md +10 -0
  1758. package/src/prompts/tools/write.md +14 -0
  1759. package/src/registry/agent-registry.ts +140 -0
  1760. package/src/reminders/star-reminder.ts +422 -0
  1761. package/src/runtime-mcp/client.ts +485 -0
  1762. package/src/runtime-mcp/config-writer.ts +225 -0
  1763. package/src/runtime-mcp/config.ts +365 -0
  1764. package/src/runtime-mcp/discoverable-tool-metadata.ts +25 -0
  1765. package/src/runtime-mcp/index.ts +29 -0
  1766. package/src/runtime-mcp/json-rpc.ts +84 -0
  1767. package/src/runtime-mcp/loader.ts +124 -0
  1768. package/src/runtime-mcp/manager.ts +1211 -0
  1769. package/src/runtime-mcp/oauth-discovery.ts +349 -0
  1770. package/src/runtime-mcp/oauth-flow.ts +409 -0
  1771. package/src/runtime-mcp/render.ts +123 -0
  1772. package/src/runtime-mcp/smithery-auth.ts +104 -0
  1773. package/src/runtime-mcp/smithery-connect.ts +145 -0
  1774. package/src/runtime-mcp/smithery-registry.ts +477 -0
  1775. package/src/runtime-mcp/tool-bridge.ts +416 -0
  1776. package/src/runtime-mcp/tool-cache.ts +117 -0
  1777. package/src/runtime-mcp/transports/http.ts +503 -0
  1778. package/src/runtime-mcp/transports/index.ts +6 -0
  1779. package/src/runtime-mcp/transports/stdio.ts +326 -0
  1780. package/src/runtime-mcp/types.ts +423 -0
  1781. package/src/sdk.ts +2248 -0
  1782. package/src/secrets/index.ts +120 -0
  1783. package/src/secrets/obfuscator.ts +352 -0
  1784. package/src/secrets/regex.ts +21 -0
  1785. package/src/session/agent-session.ts +10343 -0
  1786. package/src/session/agent-storage.ts +466 -0
  1787. package/src/session/artifacts.ts +135 -0
  1788. package/src/session/auth-broker-config.ts +102 -0
  1789. package/src/session/auth-storage.ts +23 -0
  1790. package/src/session/blob-store.ts +372 -0
  1791. package/src/session/client-bridge.ts +85 -0
  1792. package/src/session/contribution-prep.ts +320 -0
  1793. package/src/session/history-storage.ts +311 -0
  1794. package/src/session/messages.ts +406 -0
  1795. package/src/session/pabcd-stage-header.ts +77 -0
  1796. package/src/session/session-dump-format.ts +209 -0
  1797. package/src/session/session-manager.ts +3914 -0
  1798. package/src/session/session-storage.ts +415 -0
  1799. package/src/session/streaming-output.ts +1114 -0
  1800. package/src/session/tool-choice-queue.ts +213 -0
  1801. package/src/session/yield-queue.ts +155 -0
  1802. package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
  1803. package/src/setup/hermes-setup.ts +429 -0
  1804. package/src/setup/model-onboarding-guidance.ts +41 -0
  1805. package/src/setup/provider-onboarding.ts +358 -0
  1806. package/src/skill-state/active-state.ts +788 -0
  1807. package/src/skill-state/initial-phase.ts +21 -0
  1808. package/src/skill-state/jaw-interview-mutation-guard.ts +490 -0
  1809. package/src/skill-state/workflow-hud.ts +208 -0
  1810. package/src/skill-state/workflow-state-contract.ts +167 -0
  1811. package/src/skill-state/workflow-state-version.ts +3 -0
  1812. package/src/slash-commands/acp-builtins.ts +55 -0
  1813. package/src/slash-commands/builtin-registry.ts +1879 -0
  1814. package/src/slash-commands/helpers/context-report.ts +149 -0
  1815. package/src/slash-commands/helpers/format.ts +46 -0
  1816. package/src/slash-commands/helpers/mcp.ts +532 -0
  1817. package/src/slash-commands/helpers/parse.ts +85 -0
  1818. package/src/slash-commands/helpers/ssh.ts +195 -0
  1819. package/src/slash-commands/helpers/todo.ts +279 -0
  1820. package/src/slash-commands/helpers/usage-report.ts +105 -0
  1821. package/src/slash-commands/types.ts +127 -0
  1822. package/src/ssh/config-writer.ts +183 -0
  1823. package/src/ssh/connection-manager.ts +482 -0
  1824. package/src/ssh/ssh-executor.ts +133 -0
  1825. package/src/ssh/sshfs-mount.ts +140 -0
  1826. package/src/ssh/utils.ts +8 -0
  1827. package/src/stt/downloader.ts +71 -0
  1828. package/src/stt/index.ts +3 -0
  1829. package/src/stt/recorder.ts +351 -0
  1830. package/src/stt/setup.ts +52 -0
  1831. package/src/stt/stt-controller.ts +160 -0
  1832. package/src/stt/transcribe.py +70 -0
  1833. package/src/stt/transcriber.ts +91 -0
  1834. package/src/system-prompt.ts +661 -0
  1835. package/src/task/agents.ts +189 -0
  1836. package/src/task/commands.ts +131 -0
  1837. package/src/task/discovery.ts +136 -0
  1838. package/src/task/executor.ts +1664 -0
  1839. package/src/task/fork-context-advisory.ts +99 -0
  1840. package/src/task/id.ts +33 -0
  1841. package/src/task/index.ts +2173 -0
  1842. package/src/task/jwc-command.ts +26 -0
  1843. package/src/task/model-presets.ts +53 -0
  1844. package/src/task/name-generator.ts +1577 -0
  1845. package/src/task/output-manager.ts +108 -0
  1846. package/src/task/parallel.ts +116 -0
  1847. package/src/task/receipt.ts +304 -0
  1848. package/src/task/render.ts +1075 -0
  1849. package/src/task/roi-reconciliation.ts +90 -0
  1850. package/src/task/simple-mode.ts +27 -0
  1851. package/src/task/spawn-gate.ts +132 -0
  1852. package/src/task/subprocess-tool-registry.ts +88 -0
  1853. package/src/task/types.ts +433 -0
  1854. package/src/task/worktree.ts +506 -0
  1855. package/src/thinking.ts +93 -0
  1856. package/src/tool-discovery/tool-index.ts +408 -0
  1857. package/src/tools/archive-reader.ts +321 -0
  1858. package/src/tools/ask.ts +1011 -0
  1859. package/src/tools/ast-edit.ts +555 -0
  1860. package/src/tools/ast-grep.ts +424 -0
  1861. package/src/tools/auto-generated-guard.ts +305 -0
  1862. package/src/tools/background.ts +500 -0
  1863. package/src/tools/bash-allowed-prefixes.ts +169 -0
  1864. package/src/tools/bash-command-fixup.ts +37 -0
  1865. package/src/tools/bash-interactive.ts +388 -0
  1866. package/src/tools/bash-interceptor.ts +67 -0
  1867. package/src/tools/bash-pty-selection.ts +15 -0
  1868. package/src/tools/bash-skill-urls.ts +248 -0
  1869. package/src/tools/bash.ts +1242 -0
  1870. package/src/tools/browser/actions.ts +189 -0
  1871. package/src/tools/browser/attach.ts +175 -0
  1872. package/src/tools/browser/launch.ts +651 -0
  1873. package/src/tools/browser/readable.ts +95 -0
  1874. package/src/tools/browser/registry.ts +194 -0
  1875. package/src/tools/browser/render.ts +212 -0
  1876. package/src/tools/browser/tab-protocol.ts +105 -0
  1877. package/src/tools/browser/tab-supervisor.ts +577 -0
  1878. package/src/tools/browser/tab-worker-entry.ts +21 -0
  1879. package/src/tools/browser/tab-worker.ts +1054 -0
  1880. package/src/tools/browser.ts +380 -0
  1881. package/src/tools/calculator.ts +540 -0
  1882. package/src/tools/checkpoint.ts +134 -0
  1883. package/src/tools/computer-use-backend.ts +75 -0
  1884. package/src/tools/computer-use.ts +228 -0
  1885. package/src/tools/conflict-detect.ts +672 -0
  1886. package/src/tools/context.ts +39 -0
  1887. package/src/tools/cron.ts +713 -0
  1888. package/src/tools/debug.ts +1014 -0
  1889. package/src/tools/eval.ts +1101 -0
  1890. package/src/tools/fetch.ts +1485 -0
  1891. package/src/tools/file-recorder.ts +35 -0
  1892. package/src/tools/find.ts +543 -0
  1893. package/src/tools/fs-cache-invalidation.ts +28 -0
  1894. package/src/tools/gh-format.ts +12 -0
  1895. package/src/tools/gh-renderer.ts +428 -0
  1896. package/src/tools/gh.ts +3737 -0
  1897. package/src/tools/github-cache.ts +548 -0
  1898. package/src/tools/grouped-file-output.ts +96 -0
  1899. package/src/tools/hindsight-recall.ts +66 -0
  1900. package/src/tools/hindsight-reflect.ts +55 -0
  1901. package/src/tools/hindsight-retain.ts +54 -0
  1902. package/src/tools/image-gen.ts +1284 -0
  1903. package/src/tools/index.ts +580 -0
  1904. package/src/tools/inspect-image-renderer.ts +103 -0
  1905. package/src/tools/inspect-image.ts +167 -0
  1906. package/src/tools/irc.ts +239 -0
  1907. package/src/tools/job.ts +530 -0
  1908. package/src/tools/json-tree.ts +243 -0
  1909. package/src/tools/jtd-to-json-schema.ts +219 -0
  1910. package/src/tools/jtd-to-typescript.ts +136 -0
  1911. package/src/tools/jtd-utils.ts +102 -0
  1912. package/src/tools/list-limit.ts +40 -0
  1913. package/src/tools/match-line-format.ts +22 -0
  1914. package/src/tools/monitor.ts +255 -0
  1915. package/src/tools/output-meta.ts +754 -0
  1916. package/src/tools/path-utils.ts +741 -0
  1917. package/src/tools/plan-mode-guard.ts +68 -0
  1918. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  1919. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  1920. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  1921. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  1922. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  1923. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  1924. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  1925. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  1926. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  1927. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  1928. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  1929. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  1930. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  1931. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1932. package/src/tools/read.ts +2333 -0
  1933. package/src/tools/recipe/index.ts +80 -0
  1934. package/src/tools/recipe/render.ts +19 -0
  1935. package/src/tools/recipe/runner.ts +219 -0
  1936. package/src/tools/recipe/runners/cargo.ts +131 -0
  1937. package/src/tools/recipe/runners/index.ts +8 -0
  1938. package/src/tools/recipe/runners/just.ts +73 -0
  1939. package/src/tools/recipe/runners/make.ts +101 -0
  1940. package/src/tools/recipe/runners/pkg.ts +167 -0
  1941. package/src/tools/recipe/runners/task.ts +72 -0
  1942. package/src/tools/render-mermaid.ts +68 -0
  1943. package/src/tools/render-utils.ts +774 -0
  1944. package/src/tools/renderers.ts +79 -0
  1945. package/src/tools/resolve.ts +258 -0
  1946. package/src/tools/review.ts +252 -0
  1947. package/src/tools/search-tool-bm25.ts +364 -0
  1948. package/src/tools/search.ts +787 -0
  1949. package/src/tools/skill.ts +170 -0
  1950. package/src/tools/sqlite-reader.ts +736 -0
  1951. package/src/tools/ssh.ts +310 -0
  1952. package/src/tools/subagent-render.ts +160 -0
  1953. package/src/tools/subagent.ts +698 -0
  1954. package/src/tools/telegram-send.ts +178 -0
  1955. package/src/tools/todo-write.ts +695 -0
  1956. package/src/tools/tool-errors.ts +62 -0
  1957. package/src/tools/tool-result.ts +86 -0
  1958. package/src/tools/tool-timeouts.ts +30 -0
  1959. package/src/tools/vim.ts +949 -0
  1960. package/src/tools/write.ts +953 -0
  1961. package/src/tools/yield.ts +268 -0
  1962. package/src/tui/code-cell.ts +201 -0
  1963. package/src/tui/file-list.ts +55 -0
  1964. package/src/tui/hyperlink.ts +126 -0
  1965. package/src/tui/index.ts +12 -0
  1966. package/src/tui/output-block.ts +150 -0
  1967. package/src/tui/status-line.ts +39 -0
  1968. package/src/tui/tree-list.ts +84 -0
  1969. package/src/tui/types.ts +15 -0
  1970. package/src/tui/utils.ts +103 -0
  1971. package/src/utils/changelog.ts +121 -0
  1972. package/src/utils/clipboard.ts +156 -0
  1973. package/src/utils/command-args.ts +76 -0
  1974. package/src/utils/commit-message-generator.ts +135 -0
  1975. package/src/utils/edit-mode.ts +42 -0
  1976. package/src/utils/event-bus.ts +33 -0
  1977. package/src/utils/external-editor.ts +65 -0
  1978. package/src/utils/file-display-mode.ts +45 -0
  1979. package/src/utils/file-mentions.ts +376 -0
  1980. package/src/utils/git.ts +1536 -0
  1981. package/src/utils/image-loading.ts +102 -0
  1982. package/src/utils/image-resize.ts +309 -0
  1983. package/src/utils/lang-from-path.ts +239 -0
  1984. package/src/utils/markit.ts +89 -0
  1985. package/src/utils/open.ts +20 -0
  1986. package/src/utils/session-color.ts +43 -0
  1987. package/src/utils/shell-snapshot.ts +187 -0
  1988. package/src/utils/sixel.ts +69 -0
  1989. package/src/utils/title-generator.ts +237 -0
  1990. package/src/utils/tokenizer-download.ts +106 -0
  1991. package/src/utils/tokenizer-encoding.ts +29 -0
  1992. package/src/utils/tool-choice.ts +62 -0
  1993. package/src/utils/tools-manager.ts +363 -0
  1994. package/src/vim/buffer.ts +309 -0
  1995. package/src/vim/commands.ts +382 -0
  1996. package/src/vim/engine.ts +2409 -0
  1997. package/src/vim/parser.ts +134 -0
  1998. package/src/vim/render.ts +252 -0
  1999. package/src/vim/types.ts +197 -0
  2000. package/src/web/kagi.ts +183 -0
  2001. package/src/web/parallel.ts +349 -0
  2002. package/src/web/public-fetch-url.ts +74 -0
  2003. package/src/web/scrapers/artifacthub.ts +207 -0
  2004. package/src/web/scrapers/arxiv.ts +83 -0
  2005. package/src/web/scrapers/aur.ts +162 -0
  2006. package/src/web/scrapers/biorxiv.ts +133 -0
  2007. package/src/web/scrapers/bluesky.ts +262 -0
  2008. package/src/web/scrapers/brew.ts +172 -0
  2009. package/src/web/scrapers/cheatsh.ts +68 -0
  2010. package/src/web/scrapers/chocolatey.ts +196 -0
  2011. package/src/web/scrapers/choosealicense.ts +95 -0
  2012. package/src/web/scrapers/cisa-kev.ts +87 -0
  2013. package/src/web/scrapers/clojars.ts +154 -0
  2014. package/src/web/scrapers/coingecko.ts +177 -0
  2015. package/src/web/scrapers/crates-io.ts +97 -0
  2016. package/src/web/scrapers/crossref.ts +136 -0
  2017. package/src/web/scrapers/devto.ts +147 -0
  2018. package/src/web/scrapers/discogs.ts +306 -0
  2019. package/src/web/scrapers/discourse.ts +197 -0
  2020. package/src/web/scrapers/dockerhub.ts +138 -0
  2021. package/src/web/scrapers/docs-rs.ts +653 -0
  2022. package/src/web/scrapers/fdroid.ts +134 -0
  2023. package/src/web/scrapers/firefox-addons.ts +191 -0
  2024. package/src/web/scrapers/flathub.ts +223 -0
  2025. package/src/web/scrapers/github-gist.ts +58 -0
  2026. package/src/web/scrapers/github.ts +452 -0
  2027. package/src/web/scrapers/gitlab.ts +401 -0
  2028. package/src/web/scrapers/go-pkg.ts +266 -0
  2029. package/src/web/scrapers/hackage.ts +140 -0
  2030. package/src/web/scrapers/hackernews.ts +189 -0
  2031. package/src/web/scrapers/hex.ts +105 -0
  2032. package/src/web/scrapers/huggingface.ts +321 -0
  2033. package/src/web/scrapers/iacr.ts +89 -0
  2034. package/src/web/scrapers/index.ts +252 -0
  2035. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  2036. package/src/web/scrapers/lemmy.ts +203 -0
  2037. package/src/web/scrapers/lobsters.ts +175 -0
  2038. package/src/web/scrapers/mastodon.ts +292 -0
  2039. package/src/web/scrapers/maven.ts +138 -0
  2040. package/src/web/scrapers/mdn.ts +173 -0
  2041. package/src/web/scrapers/metacpan.ts +222 -0
  2042. package/src/web/scrapers/musicbrainz.ts +250 -0
  2043. package/src/web/scrapers/npm.ts +98 -0
  2044. package/src/web/scrapers/nuget.ts +183 -0
  2045. package/src/web/scrapers/nvd.ts +222 -0
  2046. package/src/web/scrapers/ollama.ts +239 -0
  2047. package/src/web/scrapers/open-vsx.ts +106 -0
  2048. package/src/web/scrapers/opencorporates.ts +292 -0
  2049. package/src/web/scrapers/openlibrary.ts +336 -0
  2050. package/src/web/scrapers/orcid.ts +286 -0
  2051. package/src/web/scrapers/osv.ts +176 -0
  2052. package/src/web/scrapers/packagist.ts +160 -0
  2053. package/src/web/scrapers/pub-dev.ts +143 -0
  2054. package/src/web/scrapers/pubmed.ts +211 -0
  2055. package/src/web/scrapers/pypi.ts +112 -0
  2056. package/src/web/scrapers/rawg.ts +110 -0
  2057. package/src/web/scrapers/readthedocs.ts +120 -0
  2058. package/src/web/scrapers/reddit.ts +95 -0
  2059. package/src/web/scrapers/repology.ts +251 -0
  2060. package/src/web/scrapers/rfc.ts +201 -0
  2061. package/src/web/scrapers/rubygems.ts +103 -0
  2062. package/src/web/scrapers/searchcode.ts +189 -0
  2063. package/src/web/scrapers/sec-edgar.ts +261 -0
  2064. package/src/web/scrapers/semantic-scholar.ts +171 -0
  2065. package/src/web/scrapers/snapcraft.ts +187 -0
  2066. package/src/web/scrapers/sourcegraph.ts +336 -0
  2067. package/src/web/scrapers/spdx.ts +108 -0
  2068. package/src/web/scrapers/spotify.ts +198 -0
  2069. package/src/web/scrapers/stackoverflow.ts +120 -0
  2070. package/src/web/scrapers/terraform.ts +277 -0
  2071. package/src/web/scrapers/tldr.ts +47 -0
  2072. package/src/web/scrapers/twitter.ts +93 -0
  2073. package/src/web/scrapers/types.ts +358 -0
  2074. package/src/web/scrapers/utils.ts +133 -0
  2075. package/src/web/scrapers/vimeo.ts +133 -0
  2076. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  2077. package/src/web/scrapers/w3c.ts +156 -0
  2078. package/src/web/scrapers/wikidata.ts +344 -0
  2079. package/src/web/scrapers/wikipedia.ts +84 -0
  2080. package/src/web/scrapers/youtube.ts +319 -0
  2081. package/src/web/search/index.ts +416 -0
  2082. package/src/web/search/provider.ts +236 -0
  2083. package/src/web/search/providers/anthropic.ts +319 -0
  2084. package/src/web/search/providers/base.ts +102 -0
  2085. package/src/web/search/providers/brave.ts +149 -0
  2086. package/src/web/search/providers/codex.ts +598 -0
  2087. package/src/web/search/providers/duckduckgo.ts +279 -0
  2088. package/src/web/search/providers/exa.ts +193 -0
  2089. package/src/web/search/providers/gemini.ts +462 -0
  2090. package/src/web/search/providers/jina.ts +101 -0
  2091. package/src/web/search/providers/kagi.ts +75 -0
  2092. package/src/web/search/providers/kimi.ts +171 -0
  2093. package/src/web/search/providers/parallel.ts +210 -0
  2094. package/src/web/search/providers/perplexity.ts +575 -0
  2095. package/src/web/search/providers/searxng.ts +309 -0
  2096. package/src/web/search/providers/synthetic.ts +108 -0
  2097. package/src/web/search/providers/tavily.ts +173 -0
  2098. package/src/web/search/providers/utils.ts +128 -0
  2099. package/src/web/search/providers/xai.ts +174 -0
  2100. package/src/web/search/providers/zai.ts +320 -0
  2101. package/src/web/search/render.ts +299 -0
  2102. package/src/web/search/types.ts +445 -0
  2103. package/src/web/search/utils.ts +17 -0
  2104. package/src/workspace-tree.ts +286 -0
@@ -0,0 +1,3914 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import type { AgentMessage } from "@jawcode-dev/agent-core";
5
+ import type {
6
+ ImageContent,
7
+ Message,
8
+ MessageAttribution,
9
+ ProviderPayload,
10
+ ServiceTier,
11
+ TextContent,
12
+ Usage,
13
+ } from "@jawcode-dev/ai";
14
+ import { getTerminalId } from "@jawcode-dev/tui";
15
+ import {
16
+ getBlobsDir,
17
+ getAgentDir as getDefaultAgentDir,
18
+ getProjectDir,
19
+ getSessionsDir,
20
+ getTerminalSessionsDir,
21
+ hasFsCode,
22
+ isEnoent,
23
+ logger,
24
+ parseJsonlLenient,
25
+ pathIsWithin,
26
+ resolveEquivalentPath,
27
+ Snowflake,
28
+ toError,
29
+ } from "@jawcode-dev/utils";
30
+ import { writeTextAtomic } from "../jwc-runtime/state-writer";
31
+ import { ArtifactManager } from "./artifacts";
32
+ import {
33
+ type BlobPutResult,
34
+ BlobStore,
35
+ EphemeralBlobStore,
36
+ externalizeImageData,
37
+ externalizeImageDataSync,
38
+ externalizeImageDataUrl,
39
+ externalizeImageDataUrlSync,
40
+ isBlobRef,
41
+ isImageDataUrl,
42
+ MemoryBlobStore,
43
+ resolveImageData,
44
+ resolveImageDataUrl,
45
+ resolveResidentImageDataSync,
46
+ resolveResidentImageDataUrlSync,
47
+ resolveTextBlobSync,
48
+ } from "./blob-store";
49
+ import {
50
+ type BashExecutionMessage,
51
+ type CustomMessage,
52
+ createBranchSummaryMessage,
53
+ createCompactionSummaryMessage,
54
+ createCustomMessage,
55
+ type FileMentionMessage,
56
+ type HookMessage,
57
+ type PythonExecutionMessage,
58
+ sanitizeRehydratedOpenAIResponsesAssistantMessage,
59
+ stripInternalDetailsFields,
60
+ } from "./messages";
61
+ import type { SessionStorage, SessionStorageWriter } from "./session-storage";
62
+ import { FileSessionStorage, MemorySessionStorage } from "./session-storage";
63
+
64
+ export const CURRENT_SESSION_VERSION = 3;
65
+ function isUnderProjectJwc(cwd: string, targetPath: string): boolean {
66
+ const relative = path.relative(path.join(path.resolve(cwd), ".jwc"), path.resolve(targetPath));
67
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
68
+ }
69
+
70
+ export interface SessionHeader {
71
+ type: "session";
72
+ version?: number; // v1 sessions don't have this
73
+ id: string;
74
+ title?: string; // Auto-generated title from first message
75
+ titleSource?: "auto" | "user";
76
+ timestamp: string;
77
+ cwd: string;
78
+ parentSession?: string;
79
+ }
80
+
81
+ export interface NewSessionOptions {
82
+ parentSession?: string;
83
+ /** Skip flushing the current session and delete it instead of saving. */
84
+ drop?: boolean;
85
+ }
86
+
87
+ export interface SessionEntryBase {
88
+ type: string;
89
+ id: string;
90
+ parentId: string | null;
91
+ timestamp: string;
92
+ }
93
+
94
+ export interface SessionMessageEntry extends SessionEntryBase {
95
+ type: "message";
96
+ message: AgentMessage;
97
+ }
98
+
99
+ export interface ThinkingLevelChangeEntry extends SessionEntryBase {
100
+ type: "thinking_level_change";
101
+ thinkingLevel?: string | null;
102
+ }
103
+
104
+ export interface ModelChangeEntry extends SessionEntryBase {
105
+ type: "model_change";
106
+ /** Model in "provider/modelId" format */
107
+ model: string;
108
+ /** Role: "default" or an agent role. Undefined treated as "default" */
109
+ role?: string;
110
+ previousModel?: string;
111
+ reason?: string;
112
+ thinkingLevel?: string | null;
113
+ }
114
+
115
+ export interface ServiceTierChangeEntry extends SessionEntryBase {
116
+ type: "service_tier_change";
117
+ serviceTier: ServiceTier | null;
118
+ }
119
+
120
+ export interface CompactionEntry<T = unknown> extends SessionEntryBase {
121
+ type: "compaction";
122
+ summary: string;
123
+ shortSummary?: string;
124
+ firstKeptEntryId: string;
125
+ tokensBefore: number;
126
+ /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
127
+ details?: T;
128
+ /** Hook-provided data to persist across compaction */
129
+ preserveData?: Record<string, unknown>;
130
+ /** True if generated by an extension, undefined/false if pi-generated (backward compatible) */
131
+ fromExtension?: boolean;
132
+ }
133
+
134
+ export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
135
+ type: "branch_summary";
136
+ fromId: string;
137
+ summary: string;
138
+ /** Extension-specific data (not sent to LLM) */
139
+ details?: T;
140
+ /** True if generated by an extension, false if pi-generated */
141
+ fromExtension?: boolean;
142
+ }
143
+
144
+ /**
145
+ * Custom entry for extensions to store extension-specific data in the session.
146
+ * Use customType to identify your extension's entries.
147
+ *
148
+ * Purpose: Persist extension state across session reloads. On reload, extensions can
149
+ * scan entries for their customType and reconstruct internal state.
150
+ *
151
+ * Does NOT participate in LLM context (ignored by buildSessionContext).
152
+ * For injecting content into context, see CustomMessageEntry.
153
+ */
154
+ export interface CustomEntry<T = unknown> extends SessionEntryBase {
155
+ type: "custom";
156
+ customType: string;
157
+ data?: T;
158
+ }
159
+
160
+ /** Label entry for user-defined bookmarks/markers on entries. */
161
+ export interface LabelEntry extends SessionEntryBase {
162
+ type: "label";
163
+ targetId: string;
164
+ label: string | undefined;
165
+ }
166
+
167
+ /** TTSR injection entry - tracks which time-traveling rules have been injected this session. */
168
+ export interface TtsrInjectionEntry extends SessionEntryBase {
169
+ type: "ttsr_injection";
170
+ /** Names of rules that were injected */
171
+ injectedRules: string[];
172
+ }
173
+
174
+ /** Persisted MCP discovery selection state for a session branch. */
175
+ export interface MCPToolSelectionEntry extends SessionEntryBase {
176
+ type: "mcp_tool_selection";
177
+ /** MCP tool names selected for visibility in discovery mode. */
178
+ selectedToolNames: string[];
179
+ }
180
+
181
+ /** Session init entry - captures initial context for subagent sessions (debugging/replay). */
182
+ export interface SessionInitEntry extends SessionEntryBase {
183
+ type: "session_init";
184
+ /** Full system prompt sent to the model */
185
+ systemPrompt: string;
186
+ /** Initial task/user message */
187
+ task: string;
188
+ /** Tools available to the agent */
189
+ tools: string[];
190
+ /** Output schema if structured output was requested */
191
+ outputSchema?: unknown;
192
+ /** Fork-context seed metadata for subagent debugging/replay. */
193
+ forkContext?: unknown;
194
+ }
195
+
196
+ /** Mode change entry - tracks agent mode transitions (e.g. plan mode). */
197
+ export interface ModeChangeEntry extends SessionEntryBase {
198
+ type: "mode_change";
199
+ /** Current mode name, or "none" when exiting a mode */
200
+ mode: string;
201
+ /** Optional mode-specific data (e.g. plan file path) */
202
+ data?: Record<string, unknown>;
203
+ }
204
+
205
+ /**
206
+ * Custom message entry for extensions to inject messages into LLM context.
207
+ * Use customType to identify your extension's entries.
208
+ *
209
+ * Unlike CustomEntry, this DOES participate in LLM context.
210
+ * The content participates in LLM context through convertToLlm().
211
+ * Use details for extension-specific metadata (not sent to LLM).
212
+ *
213
+ * display controls TUI rendering:
214
+ * - false: hidden entirely
215
+ * - true: rendered with distinct styling (different from user messages)
216
+ */
217
+ export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
218
+ type: "custom_message";
219
+ customType: string;
220
+ content: string | (TextContent | ImageContent)[];
221
+ details?: T;
222
+ display: boolean;
223
+ /** Who initiated this message for billing/attribution semantics. */
224
+ attribution?: MessageAttribution;
225
+ }
226
+
227
+ /** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */
228
+ export type SessionEntry =
229
+ | SessionMessageEntry
230
+ | ThinkingLevelChangeEntry
231
+ | ModelChangeEntry
232
+ | ServiceTierChangeEntry
233
+ | CompactionEntry
234
+ | BranchSummaryEntry
235
+ | CustomEntry
236
+ | CustomMessageEntry
237
+ | LabelEntry
238
+ | TtsrInjectionEntry
239
+ | MCPToolSelectionEntry
240
+ | SessionInitEntry
241
+ | ModeChangeEntry;
242
+
243
+ /** Raw file entry (includes header) */
244
+ export type FileEntry = SessionHeader | SessionEntry;
245
+
246
+ /** Tree node for getTree() - defensive copy of session structure */
247
+ export interface SessionTreeNode {
248
+ entry: SessionEntry;
249
+ children: SessionTreeNode[];
250
+ /** Resolved label for this entry, if any */
251
+ label?: string;
252
+ }
253
+
254
+ export interface SessionContext {
255
+ messages: AgentMessage[];
256
+ thinkingLevel?: string;
257
+ serviceTier?: ServiceTier;
258
+ /** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */
259
+ models: Record<string, string>;
260
+ /** Names of TTSR rules that have been injected this session */
261
+ injectedTtsrRules: string[];
262
+ /** MCP tool names selected through discovery for this session branch. */
263
+ selectedMCPToolNames: string[];
264
+ /** Whether this branch contains an explicit persisted MCP selection entry. */
265
+ hasPersistedMCPToolSelection: boolean;
266
+ /** Active mode (e.g. "plan") or "none" if no special mode is active */
267
+ mode: string;
268
+ /** Mode-specific data from the last mode_change entry */
269
+ modeData?: Record<string, unknown>;
270
+ }
271
+
272
+ export interface SessionInfo {
273
+ path: string;
274
+ id: string;
275
+ /** Working directory where the session was started. Empty string for old sessions. */
276
+ cwd: string;
277
+ title?: string;
278
+ /** Path to the parent session (if this session was forked). */
279
+ parentSessionPath?: string;
280
+ created: Date;
281
+ modified: Date;
282
+ messageCount: number;
283
+ /** File size in bytes on disk; used for compact list rendering. */
284
+ size: number;
285
+ firstMessage: string;
286
+ allMessagesText: string;
287
+ }
288
+
289
+ export type ReadonlySessionManager = Pick<
290
+ SessionManager,
291
+ | "getCwd"
292
+ | "getSessionDir"
293
+ | "getSessionId"
294
+ | "getSessionFile"
295
+ | "getSessionName"
296
+ | "getArtifactsDir"
297
+ | "getArtifactManager"
298
+ | "allocateArtifactPath"
299
+ | "saveArtifact"
300
+ | "getArtifactPath"
301
+ | "getLeafId"
302
+ | "getLeafEntry"
303
+ | "getEntry"
304
+ | "getLabel"
305
+ | "getBranch"
306
+ | "getHeader"
307
+ | "getEntries"
308
+ | "getTree"
309
+ | "getUsageStatistics"
310
+ | "putBlob"
311
+ >;
312
+
313
+ function createSessionId(): string {
314
+ return Bun.randomUUIDv7();
315
+ }
316
+
317
+ /** Generate a unique short ID (8 hex chars, collision-checked) */
318
+ function generateId(byId: { has(id: string): boolean }): string {
319
+ for (let i = 0; i < 100; i++) {
320
+ const id = crypto.randomUUID().slice(-8);
321
+ if (!byId.has(id)) return id;
322
+ }
323
+ return Snowflake.next(); // fallback to full snowflake id
324
+ }
325
+
326
+ /** Migrate v1 → v2: add id/parentId tree structure. Mutates in place. */
327
+ function migrateV1ToV2(entries: FileEntry[]): void {
328
+ const ids = new Set<string>();
329
+ let prevId: string | null = null;
330
+
331
+ for (const entry of entries) {
332
+ if (entry.type === "session") {
333
+ entry.version = 2;
334
+ continue;
335
+ }
336
+
337
+ entry.id = generateId(ids);
338
+ entry.parentId = prevId;
339
+ prevId = entry.id;
340
+
341
+ // Convert firstKeptEntryIndex to firstKeptEntryId for compaction
342
+ if (entry.type === "compaction") {
343
+ const comp = entry as CompactionEntry & { firstKeptEntryIndex?: number };
344
+ if (typeof comp.firstKeptEntryIndex === "number") {
345
+ const targetEntry = entries[comp.firstKeptEntryIndex];
346
+ if (targetEntry && targetEntry.type !== "session") {
347
+ comp.firstKeptEntryId = targetEntry.id;
348
+ }
349
+ delete comp.firstKeptEntryIndex;
350
+ }
351
+ }
352
+ }
353
+ }
354
+
355
+ /** Migrate v2 → v3: rename hookMessage role to custom. Mutates in place. */
356
+ function migrateV2ToV3(entries: FileEntry[]): void {
357
+ for (const entry of entries) {
358
+ if (entry.type === "session") {
359
+ entry.version = 3;
360
+ continue;
361
+ }
362
+
363
+ if (entry.type === "message") {
364
+ const msg = entry.message as { role?: string };
365
+ if (msg.role === "hookMessage") {
366
+ (entry.message as { role: string }).role = "custom";
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Run all necessary migrations to bring entries to current version.
374
+ * Mutates entries in place. Returns true if any migration was applied.
375
+ */
376
+ function migrateToCurrentVersion(entries: FileEntry[]): boolean {
377
+ const header = entries.find(e => e.type === "session") as SessionHeader | undefined;
378
+ const version = header?.version ?? 1;
379
+
380
+ if (version >= CURRENT_SESSION_VERSION) return false;
381
+
382
+ if (version < 2) migrateV1ToV2(entries);
383
+ if (version < 3) migrateV2ToV3(entries);
384
+
385
+ return true;
386
+ }
387
+
388
+ /** Exported for testing */
389
+ export function migrateSessionEntries(entries: FileEntry[]): void {
390
+ migrateToCurrentVersion(entries);
391
+ }
392
+
393
+ const migratedSessionRoots = new Set<string>();
394
+
395
+ /**
396
+ * Merge or rename a legacy session directory into its canonical target.
397
+ * Best effort: callers decide whether migration failures should surface.
398
+ */
399
+ function migrateSessionDirPath(oldPath: string, newPath: string): void {
400
+ // Session-dir lifecycle migration: moves/removes whole directories, not file content writes.
401
+ const existing = fs.statSync(newPath, { throwIfNoEntry: false });
402
+ if (existing?.isDirectory()) {
403
+ for (const file of fs.readdirSync(oldPath)) {
404
+ const src = path.join(oldPath, file);
405
+ const dst = path.join(newPath, file);
406
+ if (!fs.existsSync(dst)) {
407
+ fs.renameSync(src, dst);
408
+ }
409
+ }
410
+ fs.rmSync(oldPath, { recursive: true, force: true });
411
+ return;
412
+ }
413
+ if (existing) {
414
+ fs.rmSync(newPath, { recursive: true, force: true });
415
+ }
416
+ fs.renameSync(oldPath, newPath);
417
+ }
418
+
419
+ function encodeLegacyAbsoluteSessionDirName(cwd: string): string {
420
+ const resolvedCwd = path.resolve(cwd);
421
+ return `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
422
+ }
423
+
424
+ function encodeRelativeSessionDirName(prefix: string, root: string, cwd: string): string {
425
+ const relative = path.relative(root, cwd).replace(/[/\\:]/g, "-");
426
+ return relative ? (prefix.endsWith("-") ? `${prefix}${relative}` : `${prefix}-${relative}`) : prefix;
427
+ }
428
+
429
+ export function getDefaultSessionDirName(cwd: string): { encodedDirName: string; resolvedCwd: string } {
430
+ const resolvedCwd = path.resolve(cwd);
431
+ const canonicalCwd = resolveEquivalentPath(resolvedCwd);
432
+ const home = resolveEquivalentPath(os.homedir());
433
+ const tempRoot = resolveEquivalentPath(os.tmpdir());
434
+ const encodedDirName = pathIsWithin(home, canonicalCwd)
435
+ ? encodeRelativeSessionDirName("-", home, canonicalCwd)
436
+ : pathIsWithin(tempRoot, canonicalCwd)
437
+ ? encodeRelativeSessionDirName("-tmp", tempRoot, canonicalCwd)
438
+ : encodeLegacyAbsoluteSessionDirName(canonicalCwd);
439
+ return { encodedDirName, resolvedCwd };
440
+ }
441
+
442
+ /**
443
+ * Migrate old `--<home-encoded>-*--` session dirs to the new `-*` format.
444
+ * Runs once per sessions root on first access, best-effort.
445
+ */
446
+ function migrateHomeSessionDirs(sessionsRoot: string): void {
447
+ if (migratedSessionRoots.has(sessionsRoot)) return;
448
+ migratedSessionRoots.add(sessionsRoot);
449
+
450
+ const home = os.homedir();
451
+ const homeEncoded = home.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-");
452
+ const oldPrefix = `--${homeEncoded}-`;
453
+ const oldExact = `--${homeEncoded}--`;
454
+
455
+ let entries: string[];
456
+ try {
457
+ entries = fs.readdirSync(sessionsRoot);
458
+ } catch {
459
+ return;
460
+ }
461
+
462
+ for (const entry of entries) {
463
+ let remainder: string;
464
+ if (entry === oldExact) {
465
+ remainder = "";
466
+ } else if (entry.startsWith(oldPrefix) && entry.endsWith("--")) {
467
+ remainder = entry.slice(oldPrefix.length, -2);
468
+ } else {
469
+ continue;
470
+ }
471
+
472
+ const newName = remainder ? `-${remainder}` : "-";
473
+ const oldPath = path.join(sessionsRoot, entry);
474
+ const newPath = path.join(sessionsRoot, newName);
475
+
476
+ try {
477
+ migrateSessionDirPath(oldPath, newPath);
478
+ } catch {
479
+ // Best effort
480
+ }
481
+ }
482
+ }
483
+
484
+ function migrateLegacyAbsoluteSessionDir(cwd: string, sessionDir: string, sessionsRoot: string): void {
485
+ const legacyDir = path.join(sessionsRoot, encodeLegacyAbsoluteSessionDirName(cwd));
486
+ if (legacyDir === sessionDir || !fs.existsSync(legacyDir)) return;
487
+
488
+ try {
489
+ migrateSessionDirPath(legacyDir, sessionDir);
490
+ } catch {
491
+ // Best effort
492
+ }
493
+ }
494
+
495
+ function resolveManagedSessionRoot(sessionDir: string, cwd: string): string | undefined {
496
+ const currentDirName = path.basename(sessionDir);
497
+ const { encodedDirName } = getDefaultSessionDirName(cwd);
498
+ if (currentDirName !== encodedDirName && currentDirName !== encodeLegacyAbsoluteSessionDirName(cwd)) {
499
+ return undefined;
500
+ }
501
+ return path.dirname(sessionDir);
502
+ }
503
+
504
+ /** Exported for compaction.test.ts */
505
+ export function parseSessionEntries(content: string): FileEntry[] {
506
+ return parseJsonlLenient<FileEntry>(content);
507
+ }
508
+
509
+ export function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {
510
+ for (let i = entries.length - 1; i >= 0; i--) {
511
+ if (entries[i].type === "compaction") {
512
+ return entries[i] as CompactionEntry;
513
+ }
514
+ }
515
+ return null;
516
+ }
517
+
518
+ /**
519
+ * Build the session context from entries using tree traversal.
520
+ * If leafId is provided, walks from that entry to root.
521
+ * Handles compaction and branch summaries along the path.
522
+ */
523
+ export function buildSessionContext(
524
+ entries: SessionEntry[],
525
+ leafId?: string | null,
526
+ byId?: Map<string, SessionEntry>,
527
+ ): SessionContext {
528
+ // Build uuid index if not available
529
+ if (!byId) {
530
+ byId = new Map<string, SessionEntry>();
531
+ for (const entry of entries) {
532
+ byId.set(entry.id, entry);
533
+ }
534
+ }
535
+
536
+ // Find leaf
537
+ let leaf: SessionEntry | undefined;
538
+ if (leafId === null) {
539
+ // Explicitly null - return no messages (navigated to before first entry)
540
+ return {
541
+ messages: [],
542
+ thinkingLevel: "off",
543
+ serviceTier: undefined,
544
+ models: {},
545
+ injectedTtsrRules: [],
546
+ selectedMCPToolNames: [],
547
+ hasPersistedMCPToolSelection: false,
548
+ mode: "none",
549
+ };
550
+ }
551
+ if (leafId) {
552
+ leaf = byId.get(leafId);
553
+ }
554
+ if (!leaf) {
555
+ // Fallback to last entry (when leafId is undefined)
556
+ leaf = entries[entries.length - 1];
557
+ }
558
+
559
+ if (!leaf) {
560
+ return {
561
+ messages: [],
562
+ thinkingLevel: "off",
563
+ serviceTier: undefined,
564
+ models: {},
565
+ injectedTtsrRules: [],
566
+ selectedMCPToolNames: [],
567
+ hasPersistedMCPToolSelection: false,
568
+ mode: "none",
569
+ };
570
+ }
571
+
572
+ // Walk from leaf to root, collecting path
573
+ const path: SessionEntry[] = [];
574
+ let current: SessionEntry | undefined = leaf;
575
+ while (current) {
576
+ path.unshift(current);
577
+ current = current.parentId ? byId.get(current.parentId) : undefined;
578
+ }
579
+
580
+ // Extract settings and find compaction
581
+ let thinkingLevel: string | undefined = "off";
582
+ let serviceTier: ServiceTier | undefined;
583
+ const models: Record<string, string> = {};
584
+ let compaction: CompactionEntry | null = null;
585
+ const injectedTtsrRulesSet = new Set<string>();
586
+ let selectedMCPToolNames: string[] = [];
587
+ let hasPersistedMCPToolSelection = false;
588
+ let mode = "none";
589
+ let modeData: Record<string, unknown> | undefined;
590
+ // Track whether an explicit `model_change` with role="default" has been
591
+ // seen on this path. Once a user (or the agent itself) records an
592
+ // explicit default, later assistant-message inference must NOT overwrite
593
+ // it: temporary fallbacks (retry fallback, context promotion) and
594
+ // server-side model downgrades both produce assistant messages tagged
595
+ // with the wrong model id, which previously clobbered the user's pick on
596
+ // resume (issue #849).
597
+ let hasExplicitDefaultModel = false;
598
+
599
+ for (const entry of path) {
600
+ if (entry.type === "thinking_level_change") {
601
+ thinkingLevel = entry.thinkingLevel ?? "off";
602
+ } else if (entry.type === "model_change") {
603
+ // New format: { model: "provider/id", role?: string }
604
+ if (entry.model) {
605
+ const role = entry.role ?? "default";
606
+ models[role] = entry.model;
607
+ if (role === "default") {
608
+ hasExplicitDefaultModel = true;
609
+ }
610
+ }
611
+ } else if (entry.type === "service_tier_change") {
612
+ serviceTier = entry.serviceTier ?? undefined;
613
+ } else if (entry.type === "message" && entry.message.role === "assistant") {
614
+ // Legacy fallback: infer default model from assistant messages only
615
+ // when no explicit `model_change` (role=default) entry has been
616
+ // recorded yet. Newer sessions always record an explicit default
617
+ // model_change at the start of the conversation, so this branch is
618
+ // only used to keep pre-model_change sessions working.
619
+ if (!hasExplicitDefaultModel) {
620
+ models.default = `${entry.message.provider}/${entry.message.model}`;
621
+ }
622
+ } else if (entry.type === "compaction") {
623
+ compaction = entry;
624
+ } else if (entry.type === "ttsr_injection") {
625
+ // Collect injected TTSR rule names
626
+ for (const ruleName of entry.injectedRules) {
627
+ injectedTtsrRulesSet.add(ruleName);
628
+ }
629
+ } else if (entry.type === "mcp_tool_selection") {
630
+ selectedMCPToolNames = [...entry.selectedToolNames];
631
+ hasPersistedMCPToolSelection = true;
632
+ } else if (entry.type === "mode_change") {
633
+ mode = entry.mode;
634
+ modeData = entry.data;
635
+ }
636
+ }
637
+
638
+ const injectedTtsrRules = Array.from(injectedTtsrRulesSet);
639
+
640
+ // Build messages and collect corresponding entries
641
+ // When there's a compaction, we need to:
642
+ // 1. Emit summary first (entry = compaction)
643
+ // 2. Emit kept messages (from firstKeptEntryId up to compaction)
644
+ // 3. Emit messages after compaction
645
+ const messages: AgentMessage[] = [];
646
+
647
+ const appendMessage = (entry: SessionEntry) => {
648
+ if (entry.type === "message") {
649
+ messages.push(entry.message);
650
+ } else if (entry.type === "custom_message") {
651
+ messages.push(
652
+ createCustomMessage(
653
+ entry.customType,
654
+ entry.content,
655
+ entry.display,
656
+ entry.details,
657
+ entry.timestamp,
658
+ entry.attribution,
659
+ ),
660
+ );
661
+ } else if (entry.type === "branch_summary" && entry.summary) {
662
+ messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
663
+ }
664
+ };
665
+
666
+ if (compaction) {
667
+ const providerPayload: ProviderPayload | undefined = (() => {
668
+ const candidate = compaction.preserveData?.openaiRemoteCompaction;
669
+ if (!candidate || typeof candidate !== "object") return undefined;
670
+ const remote = candidate as { provider?: unknown; replacementHistory?: unknown };
671
+ if (typeof remote.provider !== "string" || remote.provider.length === 0) return undefined;
672
+ if (!Array.isArray(remote.replacementHistory)) return undefined;
673
+ return {
674
+ type: "openaiResponsesHistory",
675
+ provider: remote.provider,
676
+ items: remote.replacementHistory as Array<Record<string, unknown>>,
677
+ };
678
+ })();
679
+ const remoteReplacementHistory = providerPayload?.items;
680
+
681
+ // Emit summary first
682
+ messages.push(
683
+ createCompactionSummaryMessage(
684
+ compaction.summary,
685
+ compaction.tokensBefore,
686
+ compaction.timestamp,
687
+ compaction.shortSummary,
688
+ providerPayload,
689
+ ),
690
+ );
691
+
692
+ // Find compaction index in path
693
+ const compactionIdx = path.findIndex(e => e.type === "compaction" && e.id === compaction.id);
694
+
695
+ if (!remoteReplacementHistory) {
696
+ // Clamp search to entries after the previous compaction (if any)
697
+ // to prevent stale firstKeptEntryId from hydrating old messages.
698
+ let searchStart = 0;
699
+ for (let i = compactionIdx - 1; i >= 0; i--) {
700
+ if (path[i].type === "compaction") {
701
+ searchStart = i + 1;
702
+ break;
703
+ }
704
+ }
705
+
706
+ // Emit kept messages (before compaction, starting from firstKeptEntryId)
707
+ let foundFirstKept = false;
708
+ for (let i = searchStart; i < compactionIdx; i++) {
709
+ const entry = path[i];
710
+ if (entry.id === compaction.firstKeptEntryId) {
711
+ foundFirstKept = true;
712
+ }
713
+ if (foundFirstKept) {
714
+ appendMessage(entry);
715
+ }
716
+ }
717
+ }
718
+
719
+ // Emit messages after compaction
720
+ for (let i = compactionIdx + 1; i < path.length; i++) {
721
+ const entry = path[i];
722
+ appendMessage(entry);
723
+ }
724
+ } else {
725
+ // No compaction - emit all messages, handle branch summaries and custom messages
726
+ for (const entry of path) {
727
+ appendMessage(entry);
728
+ }
729
+ }
730
+
731
+ return {
732
+ messages,
733
+ thinkingLevel,
734
+ serviceTier,
735
+ models,
736
+ injectedTtsrRules,
737
+ selectedMCPToolNames,
738
+ hasPersistedMCPToolSelection,
739
+ mode,
740
+ modeData,
741
+ };
742
+ }
743
+
744
+ export function buildVisibleTranscriptContext(
745
+ entries: SessionEntry[],
746
+ leafId?: string | null,
747
+ byId?: Map<string, SessionEntry>,
748
+ ): SessionContext {
749
+ const base = buildSessionContext(entries, leafId, byId);
750
+ if (leafId === null || entries.length === 0) {
751
+ return { ...base, messages: [] };
752
+ }
753
+
754
+ const index = byId ?? new Map(entries.map(entry => [entry.id, entry]));
755
+ const leaf: SessionEntry | undefined = leafId ? index.get(leafId) : entries[entries.length - 1];
756
+ if (!leaf) {
757
+ return { ...base, messages: [] };
758
+ }
759
+
760
+ const path: SessionEntry[] = [];
761
+ let current: SessionEntry | undefined = leaf;
762
+ while (current) {
763
+ path.unshift(current);
764
+ current = current.parentId ? index.get(current.parentId) : undefined;
765
+ }
766
+
767
+ const messages: AgentMessage[] = [];
768
+ for (const entry of path) {
769
+ if (entry.type === "message") {
770
+ messages.push(entry.message);
771
+ } else if (entry.type === "custom_message") {
772
+ messages.push(
773
+ createCustomMessage(
774
+ entry.customType,
775
+ entry.content,
776
+ entry.display,
777
+ entry.details,
778
+ entry.timestamp,
779
+ entry.attribution,
780
+ ),
781
+ );
782
+ } else if (entry.type === "branch_summary" && entry.summary) {
783
+ messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
784
+ } else if (entry.type === "compaction") {
785
+ messages.push(
786
+ createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp, entry.shortSummary),
787
+ );
788
+ }
789
+ }
790
+
791
+ return { ...base, messages };
792
+ }
793
+
794
+ /**
795
+ * Compute the default session directory for a cwd.
796
+ * Classifies cwd by canonical location so symlink/alias paths resolve to the
797
+ * same home-relative or temp-root directory names as their real targets.
798
+ */
799
+ function computeDefaultSessionDir(
800
+ cwd: string,
801
+ storage: SessionStorage,
802
+ sessionsRoot: string = getSessionsDir(),
803
+ ): string {
804
+ const { encodedDirName, resolvedCwd } = getDefaultSessionDirName(cwd);
805
+ migrateHomeSessionDirs(sessionsRoot);
806
+ const sessionDir = path.join(sessionsRoot, encodedDirName);
807
+ migrateLegacyAbsoluteSessionDir(resolvedCwd, sessionDir, sessionsRoot);
808
+ storage.ensureDirSync(sessionDir);
809
+ return sessionDir;
810
+ }
811
+
812
+ // =============================================================================
813
+ // Terminal breadcrumbs: maps terminal (TTY) -> last session file for --continue
814
+ // =============================================================================
815
+
816
+ /**
817
+ * Write a breadcrumb linking the current terminal to a session file.
818
+ * The breadcrumb contains the cwd and session path so --continue can
819
+ * find "this terminal's last session" even when running concurrent instances.
820
+ */
821
+ function writeTerminalBreadcrumb(cwd: string, sessionFile: string): void {
822
+ const terminalId = getTerminalId();
823
+ if (!terminalId) return;
824
+
825
+ const breadcrumbDir = getTerminalSessionsDir();
826
+ const breadcrumbFile = path.join(breadcrumbDir, terminalId);
827
+ const content = `${cwd}\n${sessionFile}\n`;
828
+ // Best-effort — don't break session creation if breadcrumb fails
829
+ const write = isUnderProjectJwc(cwd, breadcrumbFile)
830
+ ? writeTextAtomic(breadcrumbFile, content, {
831
+ cwd,
832
+ audit: { category: "artifact", verb: "write", owner: "jwc-runtime" },
833
+ })
834
+ : Bun.write(breadcrumbFile, content);
835
+ write.catch(() => {});
836
+ }
837
+
838
+ /**
839
+ * Read the terminal breadcrumb for the current terminal, scoped to a cwd.
840
+ * Returns the session file path if it exists and matches the cwd, null otherwise.
841
+ */
842
+ async function readTerminalBreadcrumb(cwd: string): Promise<string | null> {
843
+ const terminalId = getTerminalId();
844
+ if (!terminalId) return null;
845
+
846
+ try {
847
+ const breadcrumbFile = path.join(getTerminalSessionsDir(), terminalId);
848
+ const content = await Bun.file(breadcrumbFile).text();
849
+ const lines = content.trim().split("\n");
850
+ if (lines.length < 2) return null;
851
+
852
+ const breadcrumbCwd = lines[0];
853
+ const sessionFile = lines[1];
854
+
855
+ // Only return if cwd matches (user might have cd'd)
856
+ if (path.resolve(breadcrumbCwd) !== path.resolve(cwd)) return null;
857
+
858
+ // Verify the session file still exists
859
+ const stat = fs.statSync(sessionFile, { throwIfNoEntry: false });
860
+ if (stat?.isFile()) return sessionFile;
861
+ } catch (err) {
862
+ if (!isEnoent(err)) logger.debug("Terminal breadcrumb read failed", { err });
863
+ // Breadcrumb doesn't exist or is corrupt — fall through
864
+ }
865
+ return null;
866
+ }
867
+
868
+ /** Exported for testing */
869
+ export async function loadEntriesFromFile(
870
+ filePath: string,
871
+ storage: SessionStorage = new FileSessionStorage(),
872
+ ): Promise<FileEntry[]> {
873
+ let content: string;
874
+ try {
875
+ content = await storage.readText(filePath);
876
+ } catch (err) {
877
+ if (isEnoent(err)) return [];
878
+ throw err;
879
+ }
880
+ const entries = parseJsonlLenient<FileEntry>(content);
881
+
882
+ // Validate session header
883
+ if (entries.length === 0) return entries;
884
+ const header = entries[0] as SessionHeader;
885
+ if (header.type !== "session" || typeof header.id !== "string") {
886
+ return [];
887
+ }
888
+
889
+ return entries;
890
+ }
891
+
892
+ /**
893
+ * Resolve blob references in loaded entries, restoring both session image blocks and persisted
894
+ * provider image URLs back to the inline data expected by downstream transports. Mutates entries in place.
895
+ */
896
+ function hasImageUrl(value: unknown): value is { image_url: string | { url?: string } } {
897
+ return typeof value === "object" && value !== null && "image_url" in value;
898
+ }
899
+
900
+ async function resolvePersistedBlobRefs(value: unknown, blobStore: BlobStore, key?: string): Promise<void> {
901
+ if (Array.isArray(value)) {
902
+ await Promise.all(value.map(item => resolvePersistedBlobRefs(item, blobStore, key)));
903
+ return;
904
+ }
905
+
906
+ if (typeof value !== "object" || value === null) return;
907
+
908
+ if (isImageBlock(value) && isBlobRef(value.data)) {
909
+ value.data = await resolveImageData(blobStore, value.data);
910
+ }
911
+
912
+ if (hasImageUrl(value)) {
913
+ if (typeof value.image_url === "string" && isBlobRef(value.image_url)) {
914
+ value.image_url = await resolveImageDataUrl(blobStore, value.image_url);
915
+ } else if (
916
+ typeof value.image_url === "object" &&
917
+ value.image_url !== null &&
918
+ typeof value.image_url.url === "string" &&
919
+ isBlobRef(value.image_url.url)
920
+ ) {
921
+ value.image_url.url = await resolveImageDataUrl(blobStore, value.image_url.url);
922
+ }
923
+ }
924
+
925
+ await Promise.all(
926
+ Object.entries(value).map(async ([childKey, item]) => {
927
+ if (childKey === "data" && typeof item === "string" && isBlobRef(item) && key !== TEXT_CONTENT_KEY) {
928
+ (value as Record<string, unknown>)[childKey] = await resolveImageDataUrl(blobStore, item);
929
+ return;
930
+ }
931
+ await resolvePersistedBlobRefs(item, blobStore, childKey);
932
+ }),
933
+ );
934
+ }
935
+
936
+ async function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobStore): Promise<void> {
937
+ const promises: Promise<void>[] = [];
938
+
939
+ for (const entry of entries) {
940
+ if (entry.type === "session") continue;
941
+
942
+ let contentArray: unknown[] | undefined;
943
+ if (entry.type === "message" && "content" in entry.message && Array.isArray(entry.message.content)) {
944
+ contentArray = entry.message.content;
945
+ } else if (entry.type === "custom_message" && Array.isArray(entry.content)) {
946
+ contentArray = entry.content;
947
+ }
948
+
949
+ if (contentArray) {
950
+ for (const block of contentArray) {
951
+ if (isImageBlock(block) && isBlobRef(block.data)) {
952
+ promises.push(
953
+ resolveImageData(blobStore, block.data).then(resolved => {
954
+ block.data = resolved;
955
+ }),
956
+ );
957
+ }
958
+ }
959
+ }
960
+
961
+ promises.push(resolvePersistedBlobRefs(entry, blobStore));
962
+ }
963
+
964
+ await Promise.all(promises);
965
+ }
966
+
967
+ /**
968
+ * Lightweight metadata for a session file, used in session picker UI.
969
+ * Uses lazy getters to defer string formatting until actually displayed.
970
+ */
971
+ function sanitizeSessionName(value: string | undefined): string | undefined {
972
+ if (!value) return undefined;
973
+ const firstLine = value.split(/\r?\n/)[0] ?? "";
974
+ const stripped = firstLine.replace(/[\x00-\x1F\x7F]/g, "");
975
+ const trimmed = stripped.trim();
976
+ return trimmed.length > 0 ? trimmed : undefined;
977
+ }
978
+
979
+ class RecentSessionInfo {
980
+ #fullName: string | undefined;
981
+ #timeAgo: string | undefined;
982
+ readonly #headerTimestamp: string | undefined;
983
+
984
+ constructor(
985
+ readonly path: string,
986
+ readonly mtime: number,
987
+ header: Record<string, unknown>,
988
+ firstPrompt?: string,
989
+ ) {
990
+ // Prefer an explicit title, then the first user prompt. The raw UUID `id` is
991
+ // intentionally not used as a fallback: showing it as a "name" is unfriendly and
992
+ // indistinguishable from neighboring sessions in the UI. The friendly fallback is
993
+ // derived lazily in `fullName` from the session timestamp.
994
+ const trystr = (v: unknown) => (typeof v === "string" ? v : undefined);
995
+ this.#fullName = sanitizeSessionName(trystr(header.title)) ?? sanitizeSessionName(firstPrompt);
996
+ this.#headerTimestamp = trystr(header.timestamp);
997
+ }
998
+
999
+ /** Display name. Falls back to a timestamp-based label, never the raw UUID. */
1000
+ get fullName(): string {
1001
+ if (this.#fullName) return this.#fullName;
1002
+ const ts = this.#headerTimestamp ? Date.parse(this.#headerTimestamp) : Number.NaN;
1003
+ const date = new Date(Number.isFinite(ts) ? ts : this.mtime);
1004
+ const time = date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
1005
+ this.#fullName = `Untitled · ${time}`;
1006
+ return this.#fullName;
1007
+ }
1008
+
1009
+ /**
1010
+ * Display name without an arbitrary length cap. The renderer is responsible for
1011
+ * width-aware truncation so adjacent fields (e.g. the relative time) stay visible.
1012
+ */
1013
+ get name(): string {
1014
+ return this.fullName;
1015
+ }
1016
+
1017
+ /** Human-readable relative time (e.g., "2 hours ago") */
1018
+ get timeAgo(): string {
1019
+ if (this.#timeAgo) return this.#timeAgo;
1020
+ this.#timeAgo = formatTimeAgo(new Date(this.mtime));
1021
+ return this.#timeAgo;
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * Extracts the text content from a user message entry.
1027
+ * Returns undefined if the entry is not a user message or has no text.
1028
+ */
1029
+ function extractFirstUserPrompt(entries: Array<Record<string, unknown>>): string | undefined {
1030
+ for (const entry of entries) {
1031
+ if (entry.type !== "message") continue;
1032
+ const message = entry.message as Record<string, unknown> | undefined;
1033
+ if (message?.role !== "user") continue;
1034
+ const content = message.content;
1035
+ if (typeof content === "string") return content;
1036
+ if (Array.isArray(content)) {
1037
+ for (const block of content) {
1038
+ if (typeof block === "object" && block !== null && "text" in block) {
1039
+ const text = (block as { text: unknown }).text;
1040
+ if (typeof text === "string") return text;
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ return undefined;
1046
+ }
1047
+
1048
+ /**
1049
+ * Promote orphaned `<basename>.jsonl.<snowflake>.bak` backups created by
1050
+ * `#replaceSessionFileAfterEperm` back to their primary path when the primary
1051
+ * is missing. This runs once per session-dir scan, before the main `*.jsonl`
1052
+ * glob, so a crash between the two renames in the EPERM-rewrite path does not
1053
+ * leave the user's last good state stranded outside the loader's view.
1054
+ *
1055
+ * Exported for testing.
1056
+ */
1057
+ export async function recoverOrphanedBackups(sessionDir: string, storage: SessionStorage): Promise<void> {
1058
+ let backups: string[];
1059
+ try {
1060
+ backups = storage.listFilesSync(sessionDir, "*.bak");
1061
+ } catch {
1062
+ return;
1063
+ }
1064
+ if (backups.length === 0) return;
1065
+ // For each primary path, pick the newest backup (highest mtime) as the recovery source.
1066
+ const candidates = new Map<string, { backup: string; mtimeMs: number }>();
1067
+ for (const backup of backups) {
1068
+ const name = path.basename(backup);
1069
+ // Expect "<primary>.<snowflake>.bak" where <primary> ends in ".jsonl".
1070
+ if (!name.endsWith(".bak")) continue;
1071
+ const trimmed = name.slice(0, -".bak".length);
1072
+ const dotIdx = trimmed.lastIndexOf(".");
1073
+ if (dotIdx <= 0) continue;
1074
+ const primaryName = trimmed.slice(0, dotIdx);
1075
+ if (!primaryName.endsWith(".jsonl")) continue;
1076
+ const primaryPath = path.join(sessionDir, primaryName);
1077
+ let mtimeMs = 0;
1078
+ try {
1079
+ mtimeMs = storage.statSync(backup).mtimeMs;
1080
+ } catch {
1081
+ continue;
1082
+ }
1083
+ const existing = candidates.get(primaryPath);
1084
+ if (!existing || mtimeMs > existing.mtimeMs) {
1085
+ candidates.set(primaryPath, { backup, mtimeMs });
1086
+ }
1087
+ }
1088
+ for (const [primaryPath, { backup }] of candidates) {
1089
+ if (storage.existsSync(primaryPath)) continue;
1090
+ try {
1091
+ await storage.rename(backup, primaryPath);
1092
+ logger.warn("Recovered orphaned session backup", {
1093
+ sessionFile: primaryPath,
1094
+ backupPath: backup,
1095
+ });
1096
+ } catch (err) {
1097
+ logger.warn("Failed to recover orphaned session backup", {
1098
+ sessionFile: primaryPath,
1099
+ backupPath: backup,
1100
+ error: toError(err).message,
1101
+ });
1102
+ }
1103
+ }
1104
+ }
1105
+
1106
+ /**
1107
+ * Reads all session files from the directory and returns them sorted by mtime (newest first).
1108
+ * Uses low-level file I/O to efficiently read only the first 4KB of each file
1109
+ * to extract the JSON header and first user message without loading entire session logs into memory.
1110
+ */
1111
+ async function getSortedSessions(sessionDir: string, storage: SessionStorage): Promise<RecentSessionInfo[]> {
1112
+ await recoverOrphanedBackups(sessionDir, storage);
1113
+ try {
1114
+ const files: string[] = storage.listFilesSync(sessionDir, "*.jsonl");
1115
+ const sessions: RecentSessionInfo[] = [];
1116
+ await Promise.all(
1117
+ files.map(async (path: string) => {
1118
+ try {
1119
+ const content = await storage.readTextPrefix(path, 4096);
1120
+ const entries = parseJsonlLenient<Record<string, unknown>>(content);
1121
+ if (entries.length === 0) return;
1122
+ const header = entries[0] as Record<string, unknown>;
1123
+ if (header.type !== "session" || typeof header.id !== "string") return;
1124
+ const mtime = storage.statSync(path).mtimeMs;
1125
+ const firstPrompt = header.title ? undefined : extractFirstUserPrompt(entries);
1126
+ sessions.push(new RecentSessionInfo(path, mtime, header, firstPrompt));
1127
+ } catch {}
1128
+ }),
1129
+ );
1130
+ return sessions.sort((a, b) => b.mtime - a.mtime);
1131
+ } catch {
1132
+ return [];
1133
+ }
1134
+ }
1135
+
1136
+ /** Exported for testing */
1137
+ export async function findMostRecentSession(
1138
+ sessionDir: string,
1139
+ storage: SessionStorage = new FileSessionStorage(),
1140
+ ): Promise<string | null> {
1141
+ const sessions = await getSortedSessions(sessionDir, storage);
1142
+ return sessions[0]?.path || null;
1143
+ }
1144
+
1145
+ /** Format a time difference as a human-readable string */
1146
+ function formatTimeAgo(date: Date): string {
1147
+ const now = Date.now();
1148
+ const diffMs = now - date.getTime();
1149
+ const diffMins = Math.floor(diffMs / 60000);
1150
+ const diffHours = Math.floor(diffMs / 3600000);
1151
+ const diffDays = Math.floor(diffMs / 86400000);
1152
+
1153
+ if (diffMins < 1) return "just now";
1154
+ if (diffMins < 60) return `${diffMins}m ago`;
1155
+ if (diffHours < 24) return `${diffHours}h ago`;
1156
+ if (diffDays < 7) return `${diffDays}d ago`;
1157
+ return date.toLocaleDateString();
1158
+ }
1159
+
1160
+ const MAX_PERSIST_CHARS = 500_000;
1161
+ const TRUNCATION_NOTICE = "\n\n[Session persistence truncated large content]";
1162
+ /** Minimum base64 length to externalize to blob store (skip tiny inline images) */
1163
+ const BLOB_EXTERNALIZE_THRESHOLD = 1024;
1164
+ const TEXT_CONTENT_KEY = "content";
1165
+ const RESIDENT_BLOB_SENTINEL_KEY = "__gjcResidentBlob";
1166
+ type ResidentBlobKind = "text" | "imageUrl" | "imageData";
1167
+ interface ResidentBlobSentinel {
1168
+ [RESIDENT_BLOB_SENTINEL_KEY]: true;
1169
+ kind: ResidentBlobKind;
1170
+ ref: string;
1171
+ }
1172
+
1173
+ function residentBlobSentinel(kind: ResidentBlobKind, ref: string): ResidentBlobSentinel {
1174
+ return { [RESIDENT_BLOB_SENTINEL_KEY]: true, kind, ref };
1175
+ }
1176
+
1177
+ function isResidentBlobSentinel(value: unknown): value is ResidentBlobSentinel {
1178
+ return (
1179
+ typeof value === "object" &&
1180
+ value !== null &&
1181
+ (value as { [RESIDENT_BLOB_SENTINEL_KEY]?: unknown })[RESIDENT_BLOB_SENTINEL_KEY] === true &&
1182
+ ((value as { kind?: unknown }).kind === "text" ||
1183
+ (value as { kind?: unknown }).kind === "imageUrl" ||
1184
+ (value as { kind?: unknown }).kind === "imageData") &&
1185
+ typeof (value as { ref?: unknown }).ref === "string" &&
1186
+ isBlobRef((value as { ref: string }).ref)
1187
+ );
1188
+ }
1189
+
1190
+ /**
1191
+ * Recursively truncate large strings in an object for session persistence.
1192
+ * - Truncates any oversized string fields (key-agnostic)
1193
+ * - Replaces oversized image blocks with text notices
1194
+ * - Updates lineCount when content is truncated
1195
+ * - Returns original object if no changes needed (structural sharing)
1196
+ */
1197
+ function truncateString(value: string, maxLength: number): string {
1198
+ if (value.length <= maxLength) return value;
1199
+ let truncated = value.slice(0, maxLength);
1200
+ if (truncated.length > 0) {
1201
+ const last = truncated.charCodeAt(truncated.length - 1);
1202
+ if (last >= 0xd800 && last <= 0xdbff) {
1203
+ truncated = truncated.slice(0, -1);
1204
+ }
1205
+ }
1206
+ return truncated;
1207
+ }
1208
+
1209
+ function isImageBlock(value: unknown): value is { type: "image"; data: string; mimeType?: string } {
1210
+ return (
1211
+ typeof value === "object" &&
1212
+ value !== null &&
1213
+ "type" in value &&
1214
+ (value as { type?: string }).type === "image" &&
1215
+ "data" in value &&
1216
+ typeof (value as { data?: string }).data === "string"
1217
+ );
1218
+ }
1219
+
1220
+ const RESIDENT_EXTERNALIZE_STRING_EXCLUDED_KEYS = new Set([
1221
+ "id",
1222
+ "type",
1223
+ "parentId",
1224
+ "timestamp",
1225
+ "role",
1226
+ "provider",
1227
+ "model",
1228
+ "api",
1229
+ "customType",
1230
+ "mode",
1231
+ "mimeType",
1232
+ "stopReason",
1233
+ "toolName",
1234
+ "targetId",
1235
+ "firstKeptEntryId",
1236
+ ]);
1237
+
1238
+ function shouldExternalizeResidentString(key: string | undefined): boolean {
1239
+ return !key || !RESIDENT_EXTERNALIZE_STRING_EXCLUDED_KEYS.has(key);
1240
+ }
1241
+
1242
+ interface ResidentBlobStores {
1243
+ text: BlobStore;
1244
+ image: BlobStore;
1245
+ }
1246
+
1247
+ interface ResidentMaterializeContext {
1248
+ sessionId?: string;
1249
+ sessionFile?: string;
1250
+ }
1251
+
1252
+ function cloneJsonSemantic<T>(value: T): T {
1253
+ return JSON.parse(JSON.stringify(value)) as T;
1254
+ }
1255
+
1256
+ function externalizeResidentValueSync(obj: unknown, stores: ResidentBlobStores, key?: string): unknown {
1257
+ if (obj === null || obj === undefined) return obj;
1258
+ if (typeof obj === "string") {
1259
+ if (key === "image_url" && isImageDataUrl(obj) && obj.length >= BLOB_EXTERNALIZE_THRESHOLD)
1260
+ return residentBlobSentinel("imageUrl", externalizeImageDataUrlSync(stores.image, obj));
1261
+ if (shouldExternalizeResidentString(key) && obj.length >= BLOB_EXTERNALIZE_THRESHOLD)
1262
+ return residentBlobSentinel("text", stores.text.putSync(Buffer.from(obj, "utf8")).ref);
1263
+ return obj;
1264
+ }
1265
+ if (Array.isArray(obj)) {
1266
+ let changed = false;
1267
+ const result: unknown[] = new Array(obj.length);
1268
+ for (let i = 0; i < obj.length; i++) {
1269
+ const item = obj[i];
1270
+ if (
1271
+ key === TEXT_CONTENT_KEY &&
1272
+ isImageBlock(item) &&
1273
+ !isBlobRef(item.data) &&
1274
+ item.data.length >= BLOB_EXTERNALIZE_THRESHOLD
1275
+ ) {
1276
+ changed = true;
1277
+ result[i] = {
1278
+ ...item,
1279
+ data: residentBlobSentinel("imageData", externalizeImageDataSync(stores.image, item.data)),
1280
+ };
1281
+ continue;
1282
+ }
1283
+ const newItem = externalizeResidentValueSync(item, stores, key);
1284
+ if (newItem !== item) changed = true;
1285
+ result[i] = newItem;
1286
+ }
1287
+ return changed ? result : obj;
1288
+ }
1289
+ if (typeof obj === "object") {
1290
+ let changed = false;
1291
+ const entries: Array<readonly [string, unknown]> = [];
1292
+ for (const [childKey, value] of Object.entries(obj)) {
1293
+ const newValue = externalizeResidentValueSync(value, stores, childKey);
1294
+ if (newValue !== value) changed = true;
1295
+ entries.push([childKey, newValue]);
1296
+ }
1297
+ return changed ? Object.fromEntries(entries) : obj;
1298
+ }
1299
+ return obj;
1300
+ }
1301
+
1302
+ function prepareEntryForResidentSync(entry: FileEntry, stores: ResidentBlobStores): FileEntry {
1303
+ return externalizeResidentValueSync(entry, stores) as FileEntry;
1304
+ }
1305
+
1306
+ function materializeResidentValueSync(
1307
+ obj: unknown,
1308
+ stores: ResidentBlobStores,
1309
+ key?: string,
1310
+ cache = new Map<string, string>(),
1311
+ context: ResidentMaterializeContext = {},
1312
+ ): unknown {
1313
+ if (obj === null || obj === undefined) return obj;
1314
+ if (typeof obj === "string") return obj;
1315
+ if (isResidentBlobSentinel(obj)) {
1316
+ const cacheKey = `${obj.kind}:${obj.ref}`;
1317
+ const cached = cache.get(cacheKey);
1318
+ if (cached !== undefined) return cached;
1319
+ const resolved =
1320
+ obj.kind === "imageUrl"
1321
+ ? resolveResidentImageDataUrlSync(stores.image, obj.ref, { ...context, kind: "imageUrl" })
1322
+ : obj.kind === "imageData"
1323
+ ? resolveResidentImageDataSync(stores.image, obj.ref, { ...context, kind: "imageData" })
1324
+ : resolveTextBlobSync(stores.text, obj.ref, { ...context, kind: "text" });
1325
+ cache.set(cacheKey, resolved);
1326
+ return resolved;
1327
+ }
1328
+ if (Array.isArray(obj)) {
1329
+ let changed = false;
1330
+ const result = obj.map(item => {
1331
+ const newItem = materializeResidentValueSync(item, stores, key, cache, context);
1332
+ if (newItem !== item) changed = true;
1333
+ return newItem;
1334
+ });
1335
+ return changed ? result : obj;
1336
+ }
1337
+ if (typeof obj === "object") {
1338
+ let changed = false;
1339
+ const entries = Object.entries(obj).map(([childKey, value]) => {
1340
+ const newValue = materializeResidentValueSync(value, stores, childKey, cache, context);
1341
+ if (newValue !== value) changed = true;
1342
+ return [childKey, newValue] as const;
1343
+ });
1344
+ return changed ? Object.fromEntries(entries) : obj;
1345
+ }
1346
+ return obj;
1347
+ }
1348
+
1349
+ function materializeResidentEntrySync<T extends FileEntry | SessionEntry>(
1350
+ entry: T,
1351
+ stores: ResidentBlobStores,
1352
+ cache: Map<string, string>,
1353
+ context: ResidentMaterializeContext = {},
1354
+ ): T {
1355
+ return materializeResidentValueSync(entry, stores, undefined, cache, context) as T;
1356
+ }
1357
+
1358
+ function materializeResidentEntriesSync<T extends FileEntry | SessionEntry>(
1359
+ entries: T[],
1360
+ stores: ResidentBlobStores,
1361
+ context: ResidentMaterializeContext = {},
1362
+ ): T[] {
1363
+ const cache = new Map<string, string>();
1364
+ return entries.map(entry => materializeResidentEntrySync(entry, stores, cache, context));
1365
+ }
1366
+
1367
+ async function truncateForPersistence(obj: FileEntry, blobStore: BlobStore, key?: string): Promise<FileEntry>;
1368
+ async function truncateForPersistence(obj: string, blobStore: BlobStore, key?: string): Promise<string>;
1369
+ async function truncateForPersistence(obj: unknown[], blobStore: BlobStore, key?: string): Promise<unknown[]>;
1370
+ async function truncateForPersistence(obj: object, blobStore: BlobStore, key?: string): Promise<object>;
1371
+ async function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?: string): Promise<unknown>;
1372
+ async function truncateForPersistence(
1373
+ obj: null | undefined,
1374
+ blobStore: BlobStore,
1375
+ key?: string,
1376
+ ): Promise<null | undefined>;
1377
+ async function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?: string): Promise<unknown> {
1378
+ if (obj === null || obj === undefined) return obj;
1379
+
1380
+ if (typeof obj === "string") {
1381
+ if ((key === "image_url" || key === "image_url.url") && isImageDataUrl(obj)) {
1382
+ return externalizeImageDataUrl(blobStore, obj);
1383
+ }
1384
+
1385
+ if (obj.length > MAX_PERSIST_CHARS) {
1386
+ // Cryptographic signatures must be preserved exactly or cleared entirely — never truncated.
1387
+ // Truncation would produce an invalid signature that the API rejects.
1388
+ if (key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature") {
1389
+ return "";
1390
+ }
1391
+
1392
+ const limit = Math.max(0, MAX_PERSIST_CHARS - TRUNCATION_NOTICE.length);
1393
+ return `${truncateString(obj, limit)}${TRUNCATION_NOTICE}`;
1394
+ }
1395
+
1396
+ return obj;
1397
+ }
1398
+
1399
+ if (Array.isArray(obj)) {
1400
+ let changed = false;
1401
+ const result = await Promise.all(
1402
+ obj.map(async item => {
1403
+ // Keep durable JSONL bounded and lossless for large images. Resident
1404
+ // sentinels are materialized before this serializer runs, so persistence
1405
+ // still owns the existing blob-ref-on-disk contract.
1406
+ if (key === TEXT_CONTENT_KEY && isImageBlock(item)) {
1407
+ if (!isBlobRef(item.data) && item.data.length >= BLOB_EXTERNALIZE_THRESHOLD) {
1408
+ changed = true;
1409
+ const blobRef = await externalizeImageData(blobStore, item.data);
1410
+ return { ...item, data: blobRef };
1411
+ }
1412
+ }
1413
+ const newItem = await truncateForPersistence(item, blobStore, key);
1414
+ if (newItem !== item) changed = true;
1415
+ return newItem;
1416
+ }),
1417
+ );
1418
+ return changed ? result : obj;
1419
+ }
1420
+
1421
+ if (typeof obj === "object") {
1422
+ let changed = false;
1423
+ const entries: Array<readonly [string, unknown]> = await Promise.all(
1424
+ Object.entries(obj).flatMap(([childKey, value]) => {
1425
+ // Strip transient/redundant properties that shouldn't be persisted.
1426
+ // - partialJson: streaming accumulator for tool call JSON parsing
1427
+ // - jsonlEvents: raw subprocess streaming events (already saved to artifact files)
1428
+ if (childKey === "partialJson" || childKey === "jsonlEvents") {
1429
+ changed = true;
1430
+ return [];
1431
+ }
1432
+
1433
+ return [
1434
+ (async () => {
1435
+ if (
1436
+ childKey === "image_url" &&
1437
+ typeof value === "object" &&
1438
+ value !== null &&
1439
+ typeof (value as { url?: unknown }).url === "string"
1440
+ ) {
1441
+ let imageUrlChanged = false;
1442
+ const imageUrlEntries = await Promise.all(
1443
+ Object.entries(value).map(async ([imageUrlKey, imageUrlValue]) => {
1444
+ const persistenceKey = imageUrlKey === "url" ? "image_url.url" : imageUrlKey;
1445
+ const newImageUrlValue = await truncateForPersistence(
1446
+ imageUrlValue,
1447
+ blobStore,
1448
+ persistenceKey,
1449
+ );
1450
+ if (newImageUrlValue !== imageUrlValue) imageUrlChanged = true;
1451
+ return [imageUrlKey, newImageUrlValue] as const;
1452
+ }),
1453
+ );
1454
+ if (imageUrlChanged) {
1455
+ changed = true;
1456
+ return [childKey, Object.fromEntries(imageUrlEntries)] as const;
1457
+ }
1458
+ }
1459
+ const newValue = await truncateForPersistence(value, blobStore, childKey);
1460
+ if (newValue !== value) changed = true;
1461
+ return [childKey, newValue] as const;
1462
+ })(),
1463
+ ];
1464
+ }),
1465
+ );
1466
+
1467
+ if (!changed) return obj;
1468
+
1469
+ const contentEntry = entries.find(([childKey]) => childKey === "content");
1470
+ const lineCountEntry = entries.find(([childKey]) => childKey === "lineCount");
1471
+ if (
1472
+ contentEntry &&
1473
+ typeof contentEntry[1] === "string" &&
1474
+ lineCountEntry &&
1475
+ typeof lineCountEntry[1] === "number"
1476
+ ) {
1477
+ const content = contentEntry[1];
1478
+ const updatedEntries = entries.map(([childKey, value]) =>
1479
+ childKey === "lineCount" ? ([childKey, content.split("\n").length] as const) : ([childKey, value] as const),
1480
+ );
1481
+ return Object.fromEntries(updatedEntries);
1482
+ }
1483
+ return Object.fromEntries(entries);
1484
+ }
1485
+
1486
+ return obj;
1487
+ }
1488
+
1489
+ async function prepareEntryForPersistence(entry: FileEntry, blobStore: BlobStore): Promise<FileEntry> {
1490
+ return truncateForPersistence(entry, blobStore);
1491
+ }
1492
+
1493
+ /**
1494
+ * Synchronous variant of {@link truncateForPersistence}.
1495
+ *
1496
+ * The async version's overhead — `Promise.all` over `Object.entries`/`Array.prototype.map`,
1497
+ * one microtask hop per nested node — is pure waste for entries without image blobs
1498
+ * (the vast majority). The fast path runs in one synchronous tick so an OOM/SIGKILL
1499
+ * landing right after `_persist` returns cannot lose the entry. Image externalization
1500
+ * still happens, but via the synchronous blob-store path (`fs.writeFileSync`), so the
1501
+ * blob bytes are in the kernel page cache before the JSONL line referencing them is
1502
+ * written.
1503
+ */
1504
+ function truncateForPersistenceSync(obj: unknown, blobStore: BlobStore, key?: string): unknown {
1505
+ if (obj === null || obj === undefined) return obj;
1506
+
1507
+ if (typeof obj === "string") {
1508
+ if ((key === "image_url" || key === "image_url.url") && isImageDataUrl(obj)) {
1509
+ return externalizeImageDataUrlSync(blobStore, obj);
1510
+ }
1511
+ if (obj.length > MAX_PERSIST_CHARS) {
1512
+ if (key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature") {
1513
+ return "";
1514
+ }
1515
+ const limit = Math.max(0, MAX_PERSIST_CHARS - TRUNCATION_NOTICE.length);
1516
+ return `${truncateString(obj, limit)}${TRUNCATION_NOTICE}`;
1517
+ }
1518
+ return obj;
1519
+ }
1520
+
1521
+ if (Array.isArray(obj)) {
1522
+ let changed = false;
1523
+ const result: unknown[] = new Array(obj.length);
1524
+ for (let i = 0; i < obj.length; i++) {
1525
+ const item = obj[i];
1526
+ if (key === TEXT_CONTENT_KEY && isImageBlock(item)) {
1527
+ if (!isBlobRef(item.data) && item.data.length >= BLOB_EXTERNALIZE_THRESHOLD) {
1528
+ changed = true;
1529
+ const blobRef = externalizeImageDataSync(blobStore, item.data);
1530
+ result[i] = { ...item, data: blobRef };
1531
+ continue;
1532
+ }
1533
+ }
1534
+ const newItem = truncateForPersistenceSync(item, blobStore, key);
1535
+ if (newItem !== item) changed = true;
1536
+ result[i] = newItem;
1537
+ }
1538
+ return changed ? result : obj;
1539
+ }
1540
+
1541
+ if (typeof obj === "object") {
1542
+ let changed = false;
1543
+ const entries: Array<readonly [string, unknown]> = [];
1544
+ for (const [childKey, value] of Object.entries(obj)) {
1545
+ if (childKey === "partialJson" || childKey === "jsonlEvents") {
1546
+ changed = true;
1547
+ continue;
1548
+ }
1549
+ if (
1550
+ childKey === "image_url" &&
1551
+ typeof value === "object" &&
1552
+ value !== null &&
1553
+ typeof (value as { url?: unknown }).url === "string"
1554
+ ) {
1555
+ let imageUrlChanged = false;
1556
+ const imageUrlEntries = Object.entries(value).map(([imageUrlKey, imageUrlValue]) => {
1557
+ const persistenceKey = imageUrlKey === "url" ? "image_url.url" : imageUrlKey;
1558
+ const newImageUrlValue = truncateForPersistenceSync(imageUrlValue, blobStore, persistenceKey);
1559
+ if (newImageUrlValue !== imageUrlValue) imageUrlChanged = true;
1560
+ return [imageUrlKey, newImageUrlValue] as const;
1561
+ });
1562
+ if (imageUrlChanged) {
1563
+ changed = true;
1564
+ entries.push([childKey, Object.fromEntries(imageUrlEntries)]);
1565
+ continue;
1566
+ }
1567
+ }
1568
+ const newValue = truncateForPersistenceSync(value, blobStore, childKey);
1569
+ if (newValue !== value) changed = true;
1570
+ entries.push([childKey, newValue]);
1571
+ }
1572
+ if (!changed) return obj;
1573
+
1574
+ const contentEntry = entries.find(([childKey]) => childKey === "content");
1575
+ const lineCountEntry = entries.find(([childKey]) => childKey === "lineCount");
1576
+ if (
1577
+ contentEntry &&
1578
+ typeof contentEntry[1] === "string" &&
1579
+ lineCountEntry &&
1580
+ typeof lineCountEntry[1] === "number"
1581
+ ) {
1582
+ const content = contentEntry[1];
1583
+ const updatedEntries = entries.map(([childKey, value]) =>
1584
+ childKey === "lineCount" ? ([childKey, content.split("\n").length] as const) : ([childKey, value] as const),
1585
+ );
1586
+ return Object.fromEntries(updatedEntries);
1587
+ }
1588
+ return Object.fromEntries(entries);
1589
+ }
1590
+
1591
+ return obj;
1592
+ }
1593
+
1594
+ function prepareEntryForPersistenceSync(entry: FileEntry, blobStore: BlobStore): FileEntry {
1595
+ return truncateForPersistenceSync(entry, blobStore) as FileEntry;
1596
+ }
1597
+
1598
+ class NdjsonFileWriter {
1599
+ #writer: SessionStorageWriter;
1600
+ #closed = false;
1601
+ #closing = false;
1602
+ #error: Error | undefined;
1603
+ #pendingWrites: Promise<void> = Promise.resolve();
1604
+ #onError: ((err: Error) => void) | undefined;
1605
+
1606
+ constructor(storage: SessionStorage, path: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }) {
1607
+ this.#onError = options?.onError;
1608
+ this.#writer = storage.openWriter(path, {
1609
+ flags: options?.flags ?? "a",
1610
+ onError: (err: Error) => this.#recordError(err),
1611
+ });
1612
+ }
1613
+
1614
+ #recordError(err: unknown): Error {
1615
+ const writeErr = toError(err);
1616
+ if (!this.#error) this.#error = writeErr;
1617
+ this.#onError?.(writeErr);
1618
+ return writeErr;
1619
+ }
1620
+
1621
+ #enqueue(task: () => Promise<void>): Promise<void> {
1622
+ const run = async () => {
1623
+ if (this.#error) throw this.#error;
1624
+ await task();
1625
+ };
1626
+ const next = this.#pendingWrites.then(run);
1627
+ void next.catch((err: unknown) => {
1628
+ if (!this.#error) this.#error = toError(err);
1629
+ });
1630
+ this.#pendingWrites = next;
1631
+ return next;
1632
+ }
1633
+
1634
+ async #writeLine(line: string): Promise<void> {
1635
+ if (this.#error) throw this.#error;
1636
+ try {
1637
+ await this.#writer.writeLine(line);
1638
+ } catch (err) {
1639
+ throw this.#recordError(err);
1640
+ }
1641
+ }
1642
+
1643
+ /** Queue a write. Returns a promise so callers can await if needed. */
1644
+ write(entry: FileEntry): Promise<void> {
1645
+ if (this.#closed || this.#closing) throw new Error("Writer closed");
1646
+ if (this.#error) throw this.#error;
1647
+ const line = `${JSON.stringify(entry)}\n`;
1648
+ return this.#enqueue(() => this.#writeLine(line));
1649
+ }
1650
+
1651
+ /**
1652
+ * Synchronously serialize and append the entry. Returns once `fs.writeSync` has handed
1653
+ * the bytes to the kernel page cache — durable across OOM/SIGKILL even before fsync.
1654
+ *
1655
+ * Callers MUST NOT mix this with pending async `write()` calls on the same writer:
1656
+ * the async path is queued through `#pendingWrites`, but this method bypasses the
1657
+ * queue. Use only when no concurrent async write is in flight (the session-manager
1658
+ * persist path enforces this via `#flushed`/`#needsFullRewriteOnNextPersist`).
1659
+ */
1660
+ writeSync(entry: FileEntry): void {
1661
+ if (this.#closed || this.#closing) throw new Error("Writer closed");
1662
+ if (this.#error) throw this.#error;
1663
+ const line = `${JSON.stringify(entry)}\n`;
1664
+ try {
1665
+ this.#writer.writeLineSync(line);
1666
+ } catch (err) {
1667
+ throw this.#recordError(err);
1668
+ }
1669
+ }
1670
+
1671
+ /** Flush all buffered data to disk. Waits for all queued writes. */
1672
+ async flush(): Promise<void> {
1673
+ if (this.#closed) return;
1674
+ if (this.#error) throw this.#error;
1675
+
1676
+ await this.#enqueue(async () => {});
1677
+
1678
+ if (this.#error) throw this.#error;
1679
+
1680
+ try {
1681
+ await this.#writer.flush();
1682
+ } catch (err) {
1683
+ throw this.#recordError(err);
1684
+ }
1685
+ }
1686
+
1687
+ /** Sync data to persistent storage. */
1688
+ async fsync(): Promise<void> {
1689
+ if (this.#closed) return;
1690
+ if (this.#error) throw this.#error;
1691
+ try {
1692
+ await this.#writer.fsync();
1693
+ } catch (err) {
1694
+ throw this.#recordError(err);
1695
+ }
1696
+ }
1697
+
1698
+ /** Close the writer, flushing all data. */
1699
+ async close(): Promise<void> {
1700
+ if (this.#closed || this.#closing) return;
1701
+ this.#closing = true;
1702
+
1703
+ let closeError: Error | undefined;
1704
+ try {
1705
+ await this.flush();
1706
+ } catch (err) {
1707
+ closeError = toError(err);
1708
+ }
1709
+
1710
+ try {
1711
+ await this.#pendingWrites;
1712
+ } catch (err) {
1713
+ if (!closeError) closeError = toError(err);
1714
+ }
1715
+
1716
+ try {
1717
+ await this.#writer.close();
1718
+ } catch (err) {
1719
+ const endErr = this.#recordError(err);
1720
+ if (!closeError) closeError = endErr;
1721
+ }
1722
+
1723
+ this.#closed = true;
1724
+
1725
+ if (!closeError && this.#error) closeError = this.#error;
1726
+ if (closeError) throw closeError;
1727
+ }
1728
+
1729
+ /** Check if there's a stored error. */
1730
+ getError(): Error | undefined {
1731
+ return this.#error;
1732
+ }
1733
+
1734
+ /** True while the writer accepts new writes (not closing or closed). */
1735
+ isOpen(): boolean {
1736
+ return !this.#closed && !this.#closing;
1737
+ }
1738
+
1739
+ closeSync(): void {
1740
+ if (this.#closed) return;
1741
+ this.#closed = true;
1742
+ this.#closing = true;
1743
+ this.#writer.close().catch(() => {});
1744
+ }
1745
+ }
1746
+
1747
+ /** Get recent sessions for display in welcome screen */
1748
+ export async function getRecentSessions(
1749
+ sessionDir: string,
1750
+ limit = 3,
1751
+ storage: SessionStorage = new FileSessionStorage(),
1752
+ ): Promise<RecentSessionInfo[]> {
1753
+ const sessions = await getSortedSessions(sessionDir, storage);
1754
+ return sessions.slice(0, limit);
1755
+ }
1756
+
1757
+ /**
1758
+ * Manages conversation sessions as append-only trees stored in JSONL files.
1759
+ *
1760
+ * Each session entry has an id and parentId forming a tree structure. The "leaf"
1761
+ * pointer tracks the current position. Appending creates a child of the current leaf.
1762
+ * Branching moves the leaf to an earlier entry, allowing new branches without
1763
+ * modifying history.
1764
+ *
1765
+ * Use buildSessionContext() to get the resolved message list for the LLM, which
1766
+ * handles compaction summaries and follows the path from root to current leaf.
1767
+ */
1768
+ export interface UsageStatistics {
1769
+ input: number;
1770
+ output: number;
1771
+ cacheRead: number;
1772
+ cacheWrite: number;
1773
+ premiumRequests: number;
1774
+ cost: number;
1775
+ }
1776
+
1777
+ function getTaskToolUsage(details: unknown): Usage | undefined {
1778
+ if (!details || typeof details !== "object") return undefined;
1779
+ const record = details as Record<string, unknown>;
1780
+ const usage = record.usage;
1781
+ if (!usage || typeof usage !== "object") return undefined;
1782
+ return usage as Usage;
1783
+ }
1784
+
1785
+ function extractTextFromContent(content: Message["content"]): string {
1786
+ if (typeof content === "string") return content;
1787
+ return content
1788
+ .filter((block): block is TextContent => block.type === "text")
1789
+ .map(block => block.text)
1790
+ .join(" ");
1791
+ }
1792
+
1793
+ const SESSION_LIST_PREFIX_BYTES = 4096;
1794
+ const SESSION_LIST_PARALLEL_THRESHOLD = 64;
1795
+ const SESSION_LIST_MAX_WORKERS = 16;
1796
+ const sessionListPrefixDecoder = new TextDecoder("utf-8", { fatal: false });
1797
+
1798
+ async function readSessionListPrefix(file: string, storage: SessionStorage, buffer: Buffer): Promise<string> {
1799
+ if (!(storage instanceof FileSessionStorage)) {
1800
+ return storage.readTextPrefix(file, buffer.byteLength);
1801
+ }
1802
+
1803
+ const handle = await fs.promises.open(file, "r");
1804
+ try {
1805
+ const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
1806
+ return sessionListPrefixDecoder.decode(buffer.subarray(0, bytesRead));
1807
+ } finally {
1808
+ await handle.close();
1809
+ }
1810
+ }
1811
+
1812
+ function decodeJsonStringFragment(value: string): string {
1813
+ const safeValue = value.endsWith("\\") ? value.slice(0, -1) : value;
1814
+ try {
1815
+ return JSON.parse(`"${safeValue}"`) as string;
1816
+ } catch {
1817
+ return safeValue
1818
+ .replace(/\\n/g, "\n")
1819
+ .replace(/\\r/g, "\r")
1820
+ .replace(/\\t/g, "\t")
1821
+ .replace(/\\"/g, '"')
1822
+ .replace(/\\\\/g, "\\");
1823
+ }
1824
+ }
1825
+
1826
+ function extractStringProperty(source: string, name: string, startIndex = 0): string | undefined {
1827
+ const propertyIndex = source.indexOf(`"${name}"`, startIndex);
1828
+ if (propertyIndex === -1) return undefined;
1829
+
1830
+ const colonIndex = source.indexOf(":", propertyIndex + name.length + 2);
1831
+ if (colonIndex === -1) return undefined;
1832
+
1833
+ let valueIndex = colonIndex + 1;
1834
+ while (valueIndex < source.length) {
1835
+ const char = source.charCodeAt(valueIndex);
1836
+ if (char !== 32 && char !== 9 && char !== 10 && char !== 13) break;
1837
+ valueIndex++;
1838
+ }
1839
+ if (source.charCodeAt(valueIndex) !== 34) return undefined;
1840
+
1841
+ const valueStart = valueIndex + 1;
1842
+ let escaped = false;
1843
+ for (let i = valueStart; i < source.length; i++) {
1844
+ const char = source.charCodeAt(i);
1845
+ if (escaped) {
1846
+ escaped = false;
1847
+ continue;
1848
+ }
1849
+ if (char === 92) {
1850
+ escaped = true;
1851
+ continue;
1852
+ }
1853
+ if (char === 34) {
1854
+ return decodeJsonStringFragment(source.slice(valueStart, i));
1855
+ }
1856
+ }
1857
+
1858
+ return decodeJsonStringFragment(source.slice(valueStart));
1859
+ }
1860
+
1861
+ function countMessageMarkers(content: string): number {
1862
+ let count = 0;
1863
+ let index = 0;
1864
+ while (index < content.length) {
1865
+ const typeIndex = content.indexOf('"type"', index);
1866
+ if (typeIndex === -1) break;
1867
+ const colonIndex = content.indexOf(":", typeIndex + 6);
1868
+ if (colonIndex === -1) break;
1869
+ const type = extractStringProperty(content, "type", typeIndex);
1870
+ if (type === "message") count++;
1871
+ index = colonIndex + 1;
1872
+ }
1873
+ return count;
1874
+ }
1875
+
1876
+ function extractFirstUserMessageFromPrefix(content: string): string | undefined {
1877
+ const roleIndex = content.indexOf('"role"');
1878
+ if (roleIndex === -1) return undefined;
1879
+
1880
+ let index = roleIndex;
1881
+ while (index !== -1) {
1882
+ const role = extractStringProperty(content, "role", index);
1883
+ if (role === "user") {
1884
+ return extractStringProperty(content, "content", index) ?? extractStringProperty(content, "text", index);
1885
+ }
1886
+ index = content.indexOf('"role"', index + 6);
1887
+ }
1888
+
1889
+ return undefined;
1890
+ }
1891
+
1892
+ interface SessionListHeader {
1893
+ type: "session";
1894
+ id: string;
1895
+ cwd?: string;
1896
+ title?: string;
1897
+ parentSession?: string;
1898
+ timestamp?: string;
1899
+ }
1900
+
1901
+ function parseSessionListHeader(
1902
+ content: string,
1903
+ entries: Array<Record<string, unknown>>,
1904
+ ): SessionListHeader | undefined {
1905
+ const parsedHeader = entries[0];
1906
+ if (parsedHeader?.type === "session" && typeof parsedHeader.id === "string") {
1907
+ return {
1908
+ type: "session",
1909
+ id: parsedHeader.id,
1910
+ cwd: typeof parsedHeader.cwd === "string" ? parsedHeader.cwd : undefined,
1911
+ title: typeof parsedHeader.title === "string" ? parsedHeader.title : undefined,
1912
+ parentSession: typeof parsedHeader.parentSession === "string" ? parsedHeader.parentSession : undefined,
1913
+ timestamp: typeof parsedHeader.timestamp === "string" ? parsedHeader.timestamp : undefined,
1914
+ };
1915
+ }
1916
+
1917
+ const firstLineEnd = content.indexOf("\n");
1918
+ const firstLine = firstLineEnd === -1 ? content : content.slice(0, firstLineEnd);
1919
+ if (extractStringProperty(firstLine, "type") !== "session") return undefined;
1920
+
1921
+ const id = extractStringProperty(firstLine, "id");
1922
+ if (!id) return undefined;
1923
+
1924
+ return {
1925
+ type: "session",
1926
+ id,
1927
+ cwd: extractStringProperty(firstLine, "cwd"),
1928
+ title: extractStringProperty(firstLine, "title"),
1929
+ parentSession: extractStringProperty(firstLine, "parentSession"),
1930
+ timestamp: extractStringProperty(firstLine, "timestamp"),
1931
+ };
1932
+ }
1933
+
1934
+ function getSessionListWorkerCount(fileCount: number): number {
1935
+ if (fileCount <= SESSION_LIST_PARALLEL_THRESHOLD) return 1;
1936
+ return Math.min(
1937
+ SESSION_LIST_MAX_WORKERS,
1938
+ os.availableParallelism(),
1939
+ Math.ceil(fileCount / SESSION_LIST_PARALLEL_THRESHOLD),
1940
+ );
1941
+ }
1942
+
1943
+ async function collectSessionFromFile(
1944
+ file: string,
1945
+ storage: SessionStorage,
1946
+ buffer: Buffer,
1947
+ ): Promise<SessionInfo | undefined> {
1948
+ try {
1949
+ const content = await readSessionListPrefix(file, storage, buffer);
1950
+ const entries = parseJsonlLenient<Record<string, unknown>>(content);
1951
+ const header = parseSessionListHeader(content, entries);
1952
+ if (!header) return undefined;
1953
+
1954
+ let parsedMessageCount = 0;
1955
+ let firstMessage = "";
1956
+ const allMessages: string[] = [];
1957
+ let shortSummary: string | undefined;
1958
+
1959
+ for (let i = 1; i < entries.length; i++) {
1960
+ const entry = entries[i] as { type?: string; message?: Message; shortSummary?: string };
1961
+
1962
+ if (entry.type === "compaction" && typeof entry.shortSummary === "string") {
1963
+ shortSummary = entry.shortSummary;
1964
+ }
1965
+
1966
+ if (entry.type === "message" && entry.message) {
1967
+ parsedMessageCount++;
1968
+
1969
+ if (entry.message.role === "user" || entry.message.role === "assistant") {
1970
+ const textContent = extractTextFromContent(entry.message.content);
1971
+
1972
+ if (textContent) {
1973
+ allMessages.push(textContent);
1974
+
1975
+ if (!firstMessage && entry.message.role === "user") {
1976
+ firstMessage = textContent;
1977
+ }
1978
+ }
1979
+ }
1980
+ }
1981
+ }
1982
+
1983
+ firstMessage ||= extractFirstUserMessageFromPrefix(content) ?? "";
1984
+ const messageCount = Math.max(parsedMessageCount, countMessageMarkers(content));
1985
+ const stats = storage.statSync(file);
1986
+ return {
1987
+ path: file,
1988
+ id: header.id,
1989
+ cwd: header.cwd ?? "",
1990
+ title: header.title ?? shortSummary,
1991
+ parentSessionPath: header.parentSession,
1992
+ created: new Date(header.timestamp ?? ""),
1993
+ modified: stats.mtime,
1994
+ messageCount,
1995
+ size: stats.size,
1996
+ firstMessage: firstMessage || "(no messages)",
1997
+ allMessagesText: allMessages.length > 0 ? allMessages.join(" ") : firstMessage,
1998
+ };
1999
+ } catch {
2000
+ return undefined;
2001
+ }
2002
+ }
2003
+
2004
+ async function collectSessionsFromFileStride(
2005
+ files: string[],
2006
+ storage: SessionStorage,
2007
+ startIndex: number,
2008
+ stride: number,
2009
+ ): Promise<SessionInfo[]> {
2010
+ const sessions: SessionInfo[] = [];
2011
+ const buffer = Buffer.allocUnsafe(SESSION_LIST_PREFIX_BYTES);
2012
+
2013
+ for (let i = startIndex; i < files.length; i += stride) {
2014
+ const session = await collectSessionFromFile(files[i], storage, buffer);
2015
+ if (session) sessions.push(session);
2016
+ }
2017
+
2018
+ return sessions;
2019
+ }
2020
+
2021
+ async function collectSessionsFromFiles(files: string[], storage: SessionStorage): Promise<SessionInfo[]> {
2022
+ const workerCount = getSessionListWorkerCount(files.length);
2023
+ const sessions =
2024
+ workerCount === 1
2025
+ ? await collectSessionsFromFileStride(files, storage, 0, 1)
2026
+ : (
2027
+ await Promise.all(
2028
+ Array.from({ length: workerCount }, (_, workerIndex) =>
2029
+ collectSessionsFromFileStride(files, storage, workerIndex, workerCount),
2030
+ ),
2031
+ )
2032
+ ).flat();
2033
+
2034
+ sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
2035
+ return sessions;
2036
+ }
2037
+
2038
+ export interface ResolvedSessionMatch {
2039
+ session: SessionInfo;
2040
+ scope: "local" | "global";
2041
+ }
2042
+
2043
+ function sessionMatchesResumeArg(session: SessionInfo, sessionArg: string): boolean {
2044
+ const normalizedArg = sessionArg.toLowerCase();
2045
+ const normalizedId = session.id.toLowerCase();
2046
+ if (normalizedId.startsWith(normalizedArg)) {
2047
+ return true;
2048
+ }
2049
+
2050
+ const fileName = path.basename(session.path, ".jsonl").toLowerCase();
2051
+ if (fileName.startsWith(normalizedArg)) {
2052
+ return true;
2053
+ }
2054
+
2055
+ const separator = fileName.lastIndexOf("_");
2056
+ if (separator < 0) {
2057
+ return false;
2058
+ }
2059
+
2060
+ const fileSessionId = fileName.slice(separator + 1);
2061
+ return fileSessionId.startsWith(normalizedArg);
2062
+ }
2063
+
2064
+ export async function resolveResumableSession(
2065
+ sessionArg: string,
2066
+ cwd: string,
2067
+ sessionDir?: string,
2068
+ storage: SessionStorage = new FileSessionStorage(),
2069
+ ): Promise<ResolvedSessionMatch | undefined> {
2070
+ const localSessionDir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
2071
+ const localSessions = await SessionManager.list(cwd, localSessionDir, storage);
2072
+ const localMatch = localSessions.find(session => sessionMatchesResumeArg(session, sessionArg));
2073
+ if (localMatch) {
2074
+ return { session: localMatch, scope: "local" };
2075
+ }
2076
+
2077
+ if (sessionDir) {
2078
+ return undefined;
2079
+ }
2080
+
2081
+ const globalSessions = await SessionManager.listAll(storage);
2082
+ const globalMatch = globalSessions.find(session => sessionMatchesResumeArg(session, sessionArg));
2083
+ if (!globalMatch) {
2084
+ return undefined;
2085
+ }
2086
+
2087
+ return { session: globalMatch, scope: "global" };
2088
+ }
2089
+ interface SessionManagerStateSnapshot {
2090
+ sessionId: string;
2091
+ sessionName: string | undefined;
2092
+ titleSource: "auto" | "user" | undefined;
2093
+ sessionFile: string | undefined;
2094
+ flushed: boolean;
2095
+ needsFullRewriteOnNextPersist: boolean;
2096
+ fileEntries: FileEntry[];
2097
+ }
2098
+
2099
+ export class SessionManager {
2100
+ #sessionId: string = "";
2101
+ #sessionName: string | undefined;
2102
+ #titleSource: "auto" | "user" | undefined;
2103
+ #sessionFile: string | undefined;
2104
+ #flushed: boolean = false;
2105
+ #needsFullRewriteOnNextPersist: boolean = false;
2106
+ #ensuredOnDisk: boolean = false;
2107
+ #fileEntries: FileEntry[] = [];
2108
+ #byId: Map<string, SessionEntry> = new Map();
2109
+ #labelsById: Map<string, string> = new Map();
2110
+ #leafId: string | null = null;
2111
+ #usageStatistics = {
2112
+ input: 0,
2113
+ output: 0,
2114
+ cacheRead: 0,
2115
+ cacheWrite: 0,
2116
+ premiumRequests: 0,
2117
+ cost: 0,
2118
+ } satisfies UsageStatistics;
2119
+ #persistWriter: NdjsonFileWriter | undefined;
2120
+ #persistWriterPath: string | undefined;
2121
+ #persistChain: Promise<void> = Promise.resolve();
2122
+ #persistError: Error | undefined;
2123
+ #persistErrorReported = false;
2124
+ #artifactManager: ArtifactManager | null = null;
2125
+ #artifactManagerSessionFile: string | null = null;
2126
+ // When set, take precedence over the lazily-derived per-session manager.
2127
+ // Subagents adopt the parent's manager so artifact IDs are unique across the
2128
+ // whole agent tree and all files land in the parent's artifacts dir.
2129
+ #adoptedArtifactManager: ArtifactManager | null = null;
2130
+ // In-memory artifact fallback for non-persistent sessions (persist=false).
2131
+ // Keyed by sequential numeric ID string; mirrors the file-based ArtifactManager ID scheme.
2132
+ #inMemoryArtifacts: Map<string, string> | null = null;
2133
+ #inMemoryArtifactCounter = 0;
2134
+ readonly #blobStore: BlobStore;
2135
+ readonly #residentTextBlobStore: BlobStore;
2136
+ readonly #residentImageBlobStore: BlobStore;
2137
+
2138
+ private constructor(
2139
+ private cwd: string,
2140
+ private sessionDir: string,
2141
+ private readonly persist: boolean,
2142
+ private readonly storage: SessionStorage,
2143
+ ) {
2144
+ if (persist) {
2145
+ this.#blobStore = new BlobStore(getBlobsDir());
2146
+ this.#residentImageBlobStore = this.#blobStore;
2147
+ this.#residentTextBlobStore = new EphemeralBlobStore(
2148
+ fs.mkdtempSync(path.join(os.tmpdir(), "jwc-resident-text-")),
2149
+ );
2150
+ } else {
2151
+ this.#residentImageBlobStore = new MemoryBlobStore();
2152
+ this.#blobStore = this.#residentImageBlobStore;
2153
+ this.#residentTextBlobStore = new MemoryBlobStore();
2154
+ }
2155
+ if (persist && sessionDir) {
2156
+ this.storage.ensureDirSync(sessionDir);
2157
+ }
2158
+ // Note: call _initSession() or _initSessionFile() after construction
2159
+ }
2160
+
2161
+ /** Puts a binary blob into the blob store and returns the blob reference */
2162
+ async putBlob(data: Buffer): Promise<BlobPutResult> {
2163
+ return this.#blobStore.put(data);
2164
+ }
2165
+
2166
+ #residentStores(): ResidentBlobStores {
2167
+ return { text: this.#residentTextBlobStore, image: this.#residentImageBlobStore };
2168
+ }
2169
+
2170
+ #residentContext(): ResidentMaterializeContext {
2171
+ return { sessionId: this.#sessionId, sessionFile: this.#sessionFile };
2172
+ }
2173
+
2174
+ #clearBlobStore(store: BlobStore): void {
2175
+ const maybeClear = store as BlobStore & { clear?: () => void };
2176
+ maybeClear.clear?.();
2177
+ }
2178
+
2179
+ #resetResidentTextBlobStore(): void {
2180
+ this.#clearBlobStore(this.#residentTextBlobStore);
2181
+ }
2182
+
2183
+ #resetResidentStores(): void {
2184
+ this.#resetResidentTextBlobStore();
2185
+ if (!this.persist) this.#clearBlobStore(this.#residentImageBlobStore);
2186
+ }
2187
+
2188
+ #disposeResidentTextBlobStore(): void {
2189
+ const maybeDispose = this.#residentTextBlobStore as BlobStore & { dispose?: () => void };
2190
+ maybeDispose.dispose?.();
2191
+ }
2192
+
2193
+ #reexternalizeFileEntriesForResidentStore(): void {
2194
+ const materialized = materializeResidentEntriesSync(
2195
+ this.#fileEntries,
2196
+ this.#residentStores(),
2197
+ this.#residentContext(),
2198
+ );
2199
+ this.#resetResidentStores();
2200
+ this.#fileEntries = materialized.map(entry => prepareEntryForResidentSync(entry, this.#residentStores()));
2201
+ this.#buildIndex();
2202
+ }
2203
+
2204
+ captureState(): SessionManagerStateSnapshot {
2205
+ return {
2206
+ sessionId: this.#sessionId,
2207
+ sessionName: this.#sessionName,
2208
+ titleSource: this.#titleSource,
2209
+ sessionFile: this.#sessionFile,
2210
+ flushed: this.#flushed,
2211
+ needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
2212
+ fileEntries: cloneJsonSemantic(this.#fileEntries),
2213
+ };
2214
+ }
2215
+
2216
+ restoreState(snapshot: SessionManagerStateSnapshot): void {
2217
+ this.#sessionId = snapshot.sessionId;
2218
+ this.#sessionName = snapshot.sessionName;
2219
+ this.#titleSource = snapshot.titleSource;
2220
+ this.#sessionFile = snapshot.sessionFile;
2221
+ this.#flushed = snapshot.flushed;
2222
+ this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
2223
+ const materializedSnapshot = materializeResidentEntriesSync(
2224
+ cloneJsonSemantic(snapshot.fileEntries),
2225
+ this.#residentStores(),
2226
+ this.#residentContext(),
2227
+ );
2228
+ this.#resetResidentStores();
2229
+ this.#fileEntries = materializedSnapshot.map(entry => prepareEntryForResidentSync(entry, this.#residentStores()));
2230
+ this.#persistWriter = undefined;
2231
+ this.#persistWriterPath = undefined;
2232
+ this.#persistChain = Promise.resolve();
2233
+ this.#persistError = undefined;
2234
+ this.#persistErrorReported = false;
2235
+ this.#artifactManager = null;
2236
+ this.#artifactManagerSessionFile = null;
2237
+ this.#adoptedArtifactManager = null;
2238
+ this.#buildIndex();
2239
+ if (this.#sessionFile) {
2240
+ writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
2241
+ }
2242
+ }
2243
+
2244
+ /** Initialize with a specific session file (used by factory methods) */
2245
+ async #initSessionFile(sessionFile: string): Promise<void> {
2246
+ await this.setSessionFile(sessionFile);
2247
+ }
2248
+ async #adoptLoadedSessionFile(sessionFile: string, entries: FileEntry[]): Promise<void> {
2249
+ this.#resetResidentStores();
2250
+ this.#sessionFile = path.resolve(sessionFile);
2251
+ writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
2252
+ this.#fileEntries = entries;
2253
+ if (this.#fileEntries.length > 0) {
2254
+ const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
2255
+ this.#sessionId = header?.id ?? createSessionId();
2256
+ this.#sessionName = header?.title;
2257
+ this.#titleSource = header?.titleSource;
2258
+
2259
+ this.#needsFullRewriteOnNextPersist = migrateToCurrentVersion(this.#fileEntries);
2260
+ await resolveBlobRefsInEntries(this.#fileEntries, this.#blobStore);
2261
+
2262
+ this.#fileEntries = this.#fileEntries.map(entry => prepareEntryForResidentSync(entry, this.#residentStores()));
2263
+ this.sanitizeLoadedOpenAIResponsesReplayMetadata();
2264
+
2265
+ this.#buildIndex();
2266
+ this.#flushed = true;
2267
+ this.#ensuredOnDisk = true;
2268
+ } else {
2269
+ const explicitPath = this.#sessionFile;
2270
+ this.#newSessionSync();
2271
+ this.#sessionFile = explicitPath; // preserve explicit path from --session flag
2272
+ await this.#rewriteFile();
2273
+ this.#flushed = true;
2274
+ this.#ensuredOnDisk = true;
2275
+ }
2276
+ }
2277
+ /** Initialize with a new session (used by factory methods) */
2278
+ #initNewSession(): void {
2279
+ this.#newSessionSync();
2280
+ }
2281
+
2282
+ /** Switch to a different session file (used for resume and branching) */
2283
+ async setSessionFile(sessionFile: string): Promise<void> {
2284
+ await this.#closePersistWriter();
2285
+ this.#resetResidentStores();
2286
+ this.#persistError = undefined;
2287
+ this.#persistErrorReported = false;
2288
+ const resolvedSessionFile = path.resolve(sessionFile);
2289
+ const entries = await loadEntriesFromFile(resolvedSessionFile, this.storage);
2290
+ await this.#adoptLoadedSessionFile(resolvedSessionFile, entries);
2291
+ }
2292
+
2293
+ /** Start a new session. Closes any existing writer first. */
2294
+ async newSession(options?: NewSessionOptions): Promise<string | undefined> {
2295
+ await this.#closePersistWriter();
2296
+ return this.#newSessionSync(options);
2297
+ }
2298
+
2299
+ /** Delete a session file and its artifacts. Drains the persist writer first to avoid EPERM on Windows. ENOENT is treated as success. */
2300
+ async dropSession(sessionPath: string): Promise<void> {
2301
+ await this.#closePersistWriter();
2302
+ try {
2303
+ await this.storage.deleteSessionWithArtifacts(sessionPath);
2304
+ } catch (err) {
2305
+ if (isEnoent(err)) return;
2306
+ throw err;
2307
+ }
2308
+ }
2309
+
2310
+ /**
2311
+ * Fork the current session, creating a new session file with the same entries.
2312
+ * Returns both the old and new session file paths for artifact copying.
2313
+ * @returns { oldSessionFile, newSessionFile } or undefined if not persisting
2314
+ */
2315
+ async fork(): Promise<{ oldSessionFile: string; newSessionFile: string } | undefined> {
2316
+ if (!this.persist || !this.#sessionFile) {
2317
+ return undefined;
2318
+ }
2319
+
2320
+ const oldSessionFile = this.#sessionFile;
2321
+ const oldSessionId = this.#sessionId;
2322
+
2323
+ // Close the current writer
2324
+ await this.#closePersistWriter();
2325
+ this.#persistChain = Promise.resolve();
2326
+ this.#persistError = undefined;
2327
+ this.#persistErrorReported = false;
2328
+
2329
+ // Create new session ID and header
2330
+ this.#sessionId = createSessionId();
2331
+ const timestamp = new Date().toISOString();
2332
+ const fileTimestamp = timestamp.replace(/[:.]/g, "-");
2333
+ this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
2334
+
2335
+ // Update the header with new ID but keep all entries
2336
+ const oldHeader = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
2337
+ const newHeader: SessionHeader = {
2338
+ type: "session",
2339
+ version: CURRENT_SESSION_VERSION,
2340
+ id: this.#sessionId,
2341
+ title: oldHeader?.title ?? this.#sessionName,
2342
+ titleSource: oldHeader?.titleSource ?? this.#titleSource,
2343
+ timestamp,
2344
+ cwd: this.cwd,
2345
+ parentSession: oldSessionId,
2346
+ };
2347
+ this.#sessionName = newHeader.title;
2348
+ this.#titleSource = newHeader.titleSource;
2349
+
2350
+ // Replace the header in fileEntries after materializing old resident-backed entries.
2351
+ const entries = materializeResidentEntriesSync(
2352
+ this.#fileEntries.filter((e): e is SessionEntry => e.type !== "session"),
2353
+ this.#residentStores(),
2354
+ { sessionId: oldSessionId, sessionFile: oldSessionFile },
2355
+ );
2356
+ this.#resetResidentStores();
2357
+ this.#fileEntries = [
2358
+ newHeader,
2359
+ ...entries.map(entry => prepareEntryForResidentSync(entry, this.#residentStores()) as SessionEntry),
2360
+ ];
2361
+
2362
+ // Write the new session file
2363
+ this.#flushed = false;
2364
+ await this.#rewriteFile();
2365
+
2366
+ return { oldSessionFile, newSessionFile: this.#sessionFile };
2367
+ }
2368
+
2369
+ /**
2370
+ * Move the session to a new working directory.
2371
+ * Moves session files and artifacts on disk, updates all internal references,
2372
+ * and rewrites the session header with the new cwd.
2373
+ */
2374
+ async moveTo(newCwd: string): Promise<void> {
2375
+ const resolvedCwd = path.resolve(newCwd);
2376
+ if (resolvedCwd === this.cwd) return;
2377
+
2378
+ const managedSessionsRoot = resolveManagedSessionRoot(this.sessionDir, this.cwd);
2379
+ const newSessionDir = managedSessionsRoot
2380
+ ? computeDefaultSessionDir(resolvedCwd, this.storage, managedSessionsRoot)
2381
+ : computeDefaultSessionDir(resolvedCwd, this.storage);
2382
+ let hadSessionFile = false;
2383
+
2384
+ if (this.persist && this.#sessionFile) {
2385
+ // Close the persist writer before moving files
2386
+ await this.#closePersistWriter();
2387
+ this.#persistChain = Promise.resolve();
2388
+ this.#persistError = undefined;
2389
+ this.#persistErrorReported = false;
2390
+
2391
+ const oldSessionFile = this.#sessionFile;
2392
+ const newSessionFile = path.join(newSessionDir, path.basename(oldSessionFile));
2393
+ const oldArtifactDir = oldSessionFile.slice(0, -6); // strip .jsonl
2394
+ const newArtifactDir = newSessionFile.slice(0, -6);
2395
+ hadSessionFile = this.storage.existsSync(oldSessionFile);
2396
+ let movedSessionFile = false;
2397
+ let movedArtifactDir = false;
2398
+
2399
+ try {
2400
+ // Guard: session file may not exist yet (no assistant messages persisted)
2401
+ if (hadSessionFile) {
2402
+ await fs.promises.rename(oldSessionFile, newSessionFile);
2403
+ movedSessionFile = true;
2404
+ }
2405
+
2406
+ try {
2407
+ const stat = await fs.promises.stat(oldArtifactDir);
2408
+ if (stat.isDirectory()) {
2409
+ await fs.promises.rename(oldArtifactDir, newArtifactDir);
2410
+ movedArtifactDir = true;
2411
+ }
2412
+ } catch (err) {
2413
+ if (!isEnoent(err)) throw err;
2414
+ }
2415
+ } catch (err) {
2416
+ if (movedArtifactDir) {
2417
+ try {
2418
+ await fs.promises.rename(newArtifactDir, oldArtifactDir);
2419
+ } catch (rollbackErr) {
2420
+ throw new Error(
2421
+ `Failed to move artifacts and rollback: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`,
2422
+ );
2423
+ }
2424
+ }
2425
+ if (movedSessionFile) {
2426
+ try {
2427
+ await fs.promises.rename(newSessionFile, oldSessionFile);
2428
+ } catch (rollbackErr) {
2429
+ throw new Error(
2430
+ `Failed to move session file and rollback: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`,
2431
+ );
2432
+ }
2433
+ }
2434
+ throw err;
2435
+ }
2436
+ this.#sessionFile = newSessionFile;
2437
+ }
2438
+
2439
+ // Update cwd and sessionDir after the move succeeds.
2440
+ this.cwd = resolvedCwd;
2441
+ this.sessionDir = newSessionDir;
2442
+
2443
+ // Update the session header in fileEntries
2444
+ const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
2445
+ if (header) {
2446
+ header.cwd = resolvedCwd;
2447
+ }
2448
+
2449
+ // Rewrite the session file at its new location with updated header.
2450
+ this.#reexternalizeFileEntriesForResidentStore();
2451
+
2452
+ // hadSessionFile: file existed before move → must rewrite to update cwd
2453
+ // hasAssistant: assistant messages in memory but file missing → recreate from memory
2454
+ // Neither true → fresh session, never written → preserve lazy-persist
2455
+ const hasAssistant = this.#fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
2456
+ if (this.persist && this.#sessionFile && (hadSessionFile || hasAssistant)) {
2457
+ await this.#rewriteFile();
2458
+ }
2459
+
2460
+ // Update terminal breadcrumb
2461
+ if (this.#sessionFile) {
2462
+ writeTerminalBreadcrumb(resolvedCwd, this.#sessionFile);
2463
+ }
2464
+ }
2465
+
2466
+ /** Sync version for initial creation (no existing writer to close) */
2467
+ #newSessionSync(options?: NewSessionOptions): string | undefined {
2468
+ this.#resetResidentStores();
2469
+ this.#persistChain = Promise.resolve();
2470
+ this.#persistError = undefined;
2471
+ this.#persistErrorReported = false;
2472
+ this.#sessionId = createSessionId();
2473
+ this.#sessionName = undefined;
2474
+ this.#titleSource = undefined;
2475
+ const timestamp = new Date().toISOString();
2476
+ const header: SessionHeader = {
2477
+ type: "session",
2478
+ version: CURRENT_SESSION_VERSION,
2479
+ id: this.#sessionId,
2480
+ timestamp,
2481
+ cwd: this.cwd,
2482
+ parentSession: options?.parentSession,
2483
+ };
2484
+ this.#fileEntries = [header];
2485
+ this.#byId.clear();
2486
+ this.#labelsById.clear();
2487
+ this.#leafId = null;
2488
+ this.#flushed = false;
2489
+ this.#needsFullRewriteOnNextPersist = false;
2490
+ this.#ensuredOnDisk = false;
2491
+ this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, premiumRequests: 0, cost: 0 };
2492
+ this.#inMemoryArtifacts = null;
2493
+ this.#inMemoryArtifactCounter = 0;
2494
+
2495
+ if (this.persist) {
2496
+ const fileTimestamp = timestamp.replace(/[:.]/g, "-");
2497
+ this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
2498
+ writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
2499
+ }
2500
+ return this.#sessionFile;
2501
+ }
2502
+
2503
+ #buildIndex(): void {
2504
+ this.#byId.clear();
2505
+ this.#labelsById.clear();
2506
+ this.#leafId = null;
2507
+ this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, premiumRequests: 0, cost: 0 };
2508
+ for (const entry of this.#fileEntries) {
2509
+ if (entry.type === "session") continue;
2510
+ this.#byId.set(entry.id, entry);
2511
+ this.#leafId = entry.id;
2512
+ if (entry.type === "label") {
2513
+ if (entry.label) {
2514
+ this.#labelsById.set(entry.targetId, entry.label);
2515
+ } else {
2516
+ this.#labelsById.delete(entry.targetId);
2517
+ }
2518
+ }
2519
+ if (entry.type === "message" && entry.message.role === "assistant") {
2520
+ const usage = entry.message.usage;
2521
+ this.#usageStatistics.input += usage.input;
2522
+ this.#usageStatistics.output += usage.output;
2523
+ this.#usageStatistics.cacheRead += usage.cacheRead;
2524
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
2525
+ this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
2526
+ this.#usageStatistics.cost += usage.cost.total;
2527
+ }
2528
+
2529
+ if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === "task") {
2530
+ const usage = getTaskToolUsage(entry.message.details);
2531
+ if (usage) {
2532
+ this.#usageStatistics.input += usage.input;
2533
+ this.#usageStatistics.output += usage.output;
2534
+ this.#usageStatistics.cacheRead += usage.cacheRead;
2535
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
2536
+ this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
2537
+ this.#usageStatistics.cost += usage.cost.total;
2538
+ }
2539
+ }
2540
+ }
2541
+ }
2542
+
2543
+ #recordPersistError(err: unknown): Error {
2544
+ const normalized = toError(err);
2545
+ if (!this.#persistError) this.#persistError = normalized;
2546
+ if (!this.#persistErrorReported) {
2547
+ this.#persistErrorReported = true;
2548
+ logger.error("Session persistence error.", {
2549
+ sessionFile: this.#sessionFile,
2550
+ error: normalized.message,
2551
+ stack: normalized.stack,
2552
+ });
2553
+ }
2554
+ return normalized;
2555
+ }
2556
+
2557
+ #queuePersistTask(task: () => Promise<void>, options?: { ignoreError?: boolean }): Promise<void> {
2558
+ const next = this.#persistChain.then(async () => {
2559
+ if (this.#persistError && !options?.ignoreError) throw this.#persistError;
2560
+ await task();
2561
+ });
2562
+ this.#persistChain = next.catch(err => {
2563
+ this.#recordPersistError(err);
2564
+ });
2565
+ return next;
2566
+ }
2567
+
2568
+ #ensurePersistWriter(): NdjsonFileWriter | undefined {
2569
+ if (!this.persist || !this.#sessionFile) return undefined;
2570
+ if (this.#persistError) throw this.#persistError;
2571
+ if (this.#persistWriter && this.#persistWriterPath === this.#sessionFile) {
2572
+ if (this.#persistWriter.isOpen()) return this.#persistWriter;
2573
+ // Cached writer for the current file is mid-close (queued
2574
+ // `#closePersistWriterInternal` has flipped `#closing` but not yet
2575
+ // cleared `#persistWriter`). Returning it would make `writeSync`
2576
+ // throw "Writer closed". Defer to the caller — `_persist` routes
2577
+ // the entry through the async rewrite path so it still lands on disk.
2578
+ return undefined;
2579
+ }
2580
+ // Note: caller must await _closePersistWriter() before calling this if switching files
2581
+ this.#persistWriter = new NdjsonFileWriter(this.storage, this.#sessionFile, {
2582
+ onError: err => {
2583
+ this.#recordPersistError(err);
2584
+ },
2585
+ });
2586
+ this.#persistWriterPath = this.#sessionFile;
2587
+ return this.#persistWriter;
2588
+ }
2589
+
2590
+ async #closePersistWriterInternal(): Promise<void> {
2591
+ if (this.#persistWriter) {
2592
+ await this.#persistWriter.close();
2593
+ this.#persistWriter = undefined;
2594
+ }
2595
+ this.#persistWriterPath = undefined;
2596
+ }
2597
+
2598
+ #closePersistWriterInternalSync(): void {
2599
+ if (this.#persistWriter) {
2600
+ this.#persistWriter.closeSync();
2601
+ this.#persistWriter = undefined;
2602
+ }
2603
+ this.#persistWriterPath = undefined;
2604
+ }
2605
+
2606
+ async #closePersistWriter(): Promise<void> {
2607
+ await this.#queuePersistTask(
2608
+ async () => {
2609
+ await this.#closePersistWriterInternal();
2610
+ },
2611
+ { ignoreError: true },
2612
+ );
2613
+ }
2614
+ // Windows can reject overwrite-style rename with EPERM even after our own writer is closed.
2615
+ // Move the old session file aside first so a failed retry can roll back to the last good file.
2616
+ // The backup uses a plain `<basename>.<snowflake>.bak` name (no leading dot) so that if the
2617
+ // process crashes between the two renames, `recoverOrphanedBackups` can find it via the
2618
+ // shared `*.bak` glob on both real and in-memory storage backends and promote it back to
2619
+ // the primary on the next session-dir scan.
2620
+
2621
+ #replaceSessionFileAfterEpermSync(tempPath: string, targetPath: string, renameError: unknown): void {
2622
+ const dir = path.resolve(targetPath, "..");
2623
+ const backupPath = path.join(dir, `${path.basename(targetPath)}.${Snowflake.next()}.bak`);
2624
+ try {
2625
+ this.storage.renameSync(targetPath, backupPath);
2626
+ } catch (err) {
2627
+ if (isEnoent(err)) {
2628
+ this.storage.renameSync(tempPath, targetPath);
2629
+ return;
2630
+ }
2631
+ throw toError(renameError);
2632
+ }
2633
+
2634
+ try {
2635
+ this.storage.renameSync(tempPath, targetPath);
2636
+ } catch (err) {
2637
+ const replaceError = toError(err);
2638
+ const originalError = toError(renameError);
2639
+ try {
2640
+ this.storage.renameSync(backupPath, targetPath);
2641
+ } catch (rollbackErr) {
2642
+ const rollbackError = toError(rollbackErr);
2643
+ throw new Error(
2644
+ `Failed to replace session file after EPERM (original: ${originalError.message}; retry: ${replaceError.message}); rollback from ${backupPath} also failed: ${rollbackError.message}`,
2645
+ { cause: originalError },
2646
+ );
2647
+ }
2648
+ throw replaceError;
2649
+ }
2650
+
2651
+ try {
2652
+ this.storage.unlinkSync(backupPath);
2653
+ } catch (err) {
2654
+ if (!isEnoent(err)) {
2655
+ logger.warn("Failed to remove session rewrite backup", {
2656
+ sessionFile: targetPath,
2657
+ backupPath,
2658
+ error: toError(err).message,
2659
+ });
2660
+ }
2661
+ }
2662
+ }
2663
+
2664
+ async #replaceSessionFileAfterEperm(tempPath: string, targetPath: string, renameError: unknown): Promise<void> {
2665
+ const dir = path.resolve(targetPath, "..");
2666
+ const backupPath = path.join(dir, `${path.basename(targetPath)}.${Snowflake.next()}.bak`);
2667
+ try {
2668
+ await this.storage.rename(targetPath, backupPath);
2669
+ } catch (err) {
2670
+ if (isEnoent(err)) {
2671
+ await this.storage.rename(tempPath, targetPath);
2672
+ return;
2673
+ }
2674
+ throw toError(renameError);
2675
+ }
2676
+
2677
+ try {
2678
+ await this.storage.rename(tempPath, targetPath);
2679
+ } catch (err) {
2680
+ const replaceError = toError(err);
2681
+ const originalError = toError(renameError);
2682
+ try {
2683
+ await this.storage.rename(backupPath, targetPath);
2684
+ } catch (rollbackErr) {
2685
+ const rollbackError = toError(rollbackErr);
2686
+ throw new Error(
2687
+ `Failed to replace session file after EPERM (original: ${originalError.message}; retry: ${replaceError.message}); rollback from ${backupPath} also failed: ${rollbackError.message}`,
2688
+ { cause: originalError },
2689
+ );
2690
+ }
2691
+ throw replaceError;
2692
+ }
2693
+
2694
+ try {
2695
+ await this.storage.unlink(backupPath);
2696
+ } catch (err) {
2697
+ if (!isEnoent(err)) {
2698
+ logger.warn("Failed to remove session rewrite backup", {
2699
+ sessionFile: targetPath,
2700
+ backupPath,
2701
+ error: toError(err).message,
2702
+ });
2703
+ }
2704
+ }
2705
+ }
2706
+
2707
+ async #replaceSessionFile(tempPath: string, targetPath: string): Promise<void> {
2708
+ try {
2709
+ await this.storage.rename(tempPath, targetPath);
2710
+ } catch (err) {
2711
+ if (!hasFsCode(err, "EPERM")) throw toError(err);
2712
+ await this.#replaceSessionFileAfterEperm(tempPath, targetPath, err);
2713
+ }
2714
+ }
2715
+
2716
+ #replaceSessionFileSync(tempPath: string, targetPath: string): void {
2717
+ try {
2718
+ this.storage.renameSync(tempPath, targetPath);
2719
+ } catch (err) {
2720
+ if (hasFsCode(err, "EPERM")) {
2721
+ this.#replaceSessionFileAfterEpermSync(tempPath, targetPath, err);
2722
+ return;
2723
+ }
2724
+
2725
+ throw toError(err);
2726
+ }
2727
+ }
2728
+
2729
+ #writeEntriesAtomicallySync(entries: FileEntry[]): void {
2730
+ if (!this.#sessionFile) return;
2731
+ const dir = path.resolve(this.#sessionFile, "..");
2732
+ const tempPath = path.join(dir, `.${path.basename(this.#sessionFile)}.${Snowflake.next()}.tmp`);
2733
+ const writer = new NdjsonFileWriter(this.storage, tempPath, { flags: "w" });
2734
+ try {
2735
+ for (const entry of entries) {
2736
+ writer.writeSync(entry);
2737
+ }
2738
+ writer.closeSync();
2739
+ this.#replaceSessionFileSync(tempPath, this.#sessionFile);
2740
+ } catch (err) {
2741
+ writer.closeSync();
2742
+ void this.storage.unlink(tempPath).catch(() => {});
2743
+ throw toError(err);
2744
+ }
2745
+ }
2746
+ async #writeEntriesAtomically(entries: FileEntry[]): Promise<void> {
2747
+ if (!this.#sessionFile) return;
2748
+ const dir = path.resolve(this.#sessionFile, "..");
2749
+ const tempPath = path.join(dir, `.${path.basename(this.#sessionFile)}.${Snowflake.next()}.tmp`);
2750
+ const writer = new NdjsonFileWriter(this.storage, tempPath, { flags: "w" });
2751
+ try {
2752
+ for (const entry of entries) {
2753
+ await writer.write(entry);
2754
+ }
2755
+ await writer.flush();
2756
+ await writer.fsync();
2757
+ await writer.close();
2758
+ await this.#replaceSessionFile(tempPath, this.#sessionFile);
2759
+ } catch (err) {
2760
+ try {
2761
+ await writer.close();
2762
+ } catch {
2763
+ // Ignore cleanup errors
2764
+ }
2765
+ try {
2766
+ await this.storage.unlink(tempPath);
2767
+ } catch {
2768
+ // Ignore cleanup errors
2769
+ }
2770
+ throw toError(err);
2771
+ }
2772
+ }
2773
+
2774
+ async #rewriteFile(): Promise<void> {
2775
+ if (!this.persist || !this.#sessionFile) return;
2776
+ await this.#queuePersistTask(async () => {
2777
+ await this.#closePersistWriterInternal();
2778
+ const entries = await Promise.all(
2779
+ materializeResidentEntriesSync(this.#fileEntries, this.#residentStores(), this.#residentContext()).map(
2780
+ entry => prepareEntryForPersistence(entry, this.#blobStore),
2781
+ ),
2782
+ );
2783
+ await this.#writeEntriesAtomically(entries);
2784
+ this.#needsFullRewriteOnNextPersist = false;
2785
+ this.#flushed = true;
2786
+ this.#ensuredOnDisk = true;
2787
+ });
2788
+ }
2789
+
2790
+ #rewriteFileSync(): void {
2791
+ if (!this.persist || !this.#sessionFile) return;
2792
+ this.#closePersistWriterInternalSync();
2793
+ const entries = materializeResidentEntriesSync(
2794
+ this.#fileEntries,
2795
+ this.#residentStores(),
2796
+ this.#residentContext(),
2797
+ ).map(entry => prepareEntryForPersistenceSync(entry, this.#blobStore));
2798
+ this.#writeEntriesAtomicallySync(entries);
2799
+ this.#needsFullRewriteOnNextPersist = false;
2800
+ this.#flushed = true;
2801
+ this.#ensuredOnDisk = true;
2802
+ }
2803
+
2804
+ isPersisted(): boolean {
2805
+ return this.persist;
2806
+ }
2807
+
2808
+ /**
2809
+ * Force-persist all current entries to disk, even when no assistant message exists yet.
2810
+ * Used by ACP mode where session/new must create a discoverable session immediately.
2811
+ */
2812
+ async ensureOnDisk(): Promise<void> {
2813
+ if (!this.persist || !this.#sessionFile) return;
2814
+ if (this.#flushed && !this.#needsFullRewriteOnNextPersist) return;
2815
+ await this.#rewriteFile();
2816
+ this.#ensuredOnDisk = true;
2817
+ }
2818
+
2819
+ /** Flush pending writes to disk. Call before switching sessions or on shutdown. */
2820
+ async flush(): Promise<void> {
2821
+ await this.#queuePersistTask(async () => {
2822
+ if (this.#persistWriter) {
2823
+ await this.#persistWriter.flush();
2824
+ await this.#persistWriter.fsync();
2825
+ }
2826
+ });
2827
+ if (this.#persistError) throw this.#persistError;
2828
+ }
2829
+
2830
+ /** Close the persistent writer after flushing all pending data. */
2831
+ async close(): Promise<void> {
2832
+ try {
2833
+ if (this.#persistWriter) {
2834
+ await this.#queuePersistTask(async () => {
2835
+ await this.#closePersistWriterInternal();
2836
+ this.#flushed = true;
2837
+ });
2838
+ }
2839
+ if (this.#persistError) throw this.#persistError;
2840
+ } finally {
2841
+ this.#disposeResidentTextBlobStore();
2842
+ }
2843
+ }
2844
+
2845
+ getCwd(): string {
2846
+ return this.cwd;
2847
+ }
2848
+
2849
+ /** Get usage statistics across all assistant messages in the session. */
2850
+ getUsageStatistics(): UsageStatistics {
2851
+ return this.#usageStatistics;
2852
+ }
2853
+
2854
+ getSessionDir(): string {
2855
+ return this.sessionDir;
2856
+ }
2857
+
2858
+ getSessionId(): string {
2859
+ return this.#sessionId;
2860
+ }
2861
+
2862
+ getSessionFile(): string | undefined {
2863
+ return this.#sessionFile;
2864
+ }
2865
+
2866
+ /**
2867
+ * Returns the session artifacts directory path (session file path without .jsonl).
2868
+ * Returns null when the session is not persisted to a file.
2869
+ * When this session has adopted an external ArtifactManager (subagent case),
2870
+ * returns that manager's directory so reads/writes land in the shared parent
2871
+ * dir instead of a private (non-existent) subdir.
2872
+ */
2873
+ getArtifactsDir(): string | null {
2874
+ if (this.#adoptedArtifactManager) return this.#adoptedArtifactManager.dir;
2875
+ const sessionFile = this.#sessionFile;
2876
+ return sessionFile ? sessionFile.slice(0, -6) : null;
2877
+ }
2878
+
2879
+ /**
2880
+ * Adopt an externally-owned ArtifactManager. Used by subagents to share
2881
+ * the parent session's artifact directory and ID counter.
2882
+ */
2883
+ adoptArtifactManager(manager: ArtifactManager): void {
2884
+ this.#adoptedArtifactManager = manager;
2885
+ }
2886
+
2887
+ /**
2888
+ * Returns the ArtifactManager this session writes through. Lazily creates
2889
+ * one bound to the current session file unless an external manager was
2890
+ * adopted via `adoptArtifactManager`. Returns null only for non-persistent
2891
+ * sessions with no adopted manager.
2892
+ */
2893
+ getArtifactManager(): ArtifactManager | null {
2894
+ return this.#getOrCreateArtifactManager();
2895
+ }
2896
+
2897
+ /**
2898
+ * Returns an artifact manager bound to the current session file.
2899
+ * Recreates the manager when the active session file changes.
2900
+ */
2901
+ #getOrCreateArtifactManager(): ArtifactManager | null {
2902
+ if (this.#adoptedArtifactManager) return this.#adoptedArtifactManager;
2903
+ const sessionFile = this.#sessionFile;
2904
+ if (!sessionFile) {
2905
+ this.#artifactManager = null;
2906
+ this.#artifactManagerSessionFile = null;
2907
+ return null;
2908
+ }
2909
+
2910
+ if (this.#artifactManager && this.#artifactManagerSessionFile === sessionFile) {
2911
+ return this.#artifactManager;
2912
+ }
2913
+
2914
+ const manager = new ArtifactManager(sessionFile.slice(0, -6));
2915
+ this.#artifactManager = manager;
2916
+ this.#artifactManagerSessionFile = sessionFile;
2917
+ return manager;
2918
+ }
2919
+
2920
+ /**
2921
+ * Allocate a new artifact path and ID for the current session.
2922
+ * Returns an empty object when the session is not persisted.
2923
+ */
2924
+ async allocateArtifactPath(toolType: string): Promise<{ id?: string; path?: string }> {
2925
+ const manager = this.#getOrCreateArtifactManager();
2926
+ if (!manager) return {};
2927
+ return manager.allocatePath(toolType);
2928
+ }
2929
+
2930
+ /**
2931
+ * Save artifact content under the current session and return artifact ID.
2932
+ * Returns an artifact ID for all sessions (file-backed for persistent, in-memory fallback otherwise).
2933
+ */
2934
+ async saveArtifact(content: string, toolType: string): Promise<string | undefined> {
2935
+ const manager = this.#getOrCreateArtifactManager();
2936
+ if (manager) return manager.save(content, toolType);
2937
+ // Non-persistent session: store in memory so spill truncation can proceed.
2938
+ if (!this.#inMemoryArtifacts) this.#inMemoryArtifacts = new Map();
2939
+ const id = String(this.#inMemoryArtifactCounter++);
2940
+ this.#inMemoryArtifacts.set(id, content);
2941
+ return id;
2942
+ }
2943
+
2944
+ /**
2945
+ * Resolve an artifact ID to an on-disk path for the current session.
2946
+ * Returns null when missing or when the session is not persisted.
2947
+ */
2948
+ async getArtifactPath(id: string): Promise<string | null> {
2949
+ const manager = this.#getOrCreateArtifactManager();
2950
+ if (!manager) return null;
2951
+ return manager.getPath(id);
2952
+ }
2953
+
2954
+ /**
2955
+ * Path to the unsent-input draft sidecar for the current session. Lives inside
2956
+ * the artifacts directory so it is removed together with the session on
2957
+ * `dropSession`. Returns null when the session has no on-disk identity.
2958
+ */
2959
+ #getDraftPath(): string | null {
2960
+ const dir = this.getArtifactsDir();
2961
+ return dir ? path.join(dir, "draft.txt") : null;
2962
+ }
2963
+
2964
+ /**
2965
+ * Persist (or clear) the current editor draft so the next resume of this
2966
+ * session can restore it. Empty text deletes any stale draft. No-op when the
2967
+ * session is not persisted.
2968
+ */
2969
+ async saveDraft(text: string): Promise<void> {
2970
+ const draftPath = this.#getDraftPath();
2971
+ if (!draftPath || !this.persist) return;
2972
+ if (text.length === 0) {
2973
+ try {
2974
+ await this.storage.unlink(draftPath);
2975
+ } catch (err) {
2976
+ if (!isEnoent(err)) throw err;
2977
+ }
2978
+ return;
2979
+ }
2980
+ // Force the session header onto disk so resume can find the file we are
2981
+ // attaching this draft to. Without this, a session whose first message
2982
+ // never produced an assistant reply would persist a draft next to a
2983
+ // session file that does not exist on disk.
2984
+ await this.ensureOnDisk();
2985
+ await this.storage.writeText(draftPath, text);
2986
+ }
2987
+
2988
+ /**
2989
+ * Read and remove the saved draft. Returns the previously-saved text, or
2990
+ * null when no draft is pending. Single-shot: a successful read removes the
2991
+ * sidecar so a subsequent resume does not re-restore the same text.
2992
+ */
2993
+ async consumeDraft(): Promise<string | null> {
2994
+ const draftPath = this.#getDraftPath();
2995
+ if (!draftPath) return null;
2996
+ let text: string;
2997
+ try {
2998
+ text = await this.storage.readText(draftPath);
2999
+ } catch (err) {
3000
+ if (isEnoent(err)) return null;
3001
+ throw err;
3002
+ }
3003
+ try {
3004
+ await this.storage.unlink(draftPath);
3005
+ } catch (err) {
3006
+ if (!isEnoent(err)) throw err;
3007
+ }
3008
+ return text;
3009
+ }
3010
+
3011
+ /** The source that set the session name: "user" (manual /rename or RPC) or "auto" (generated title). */
3012
+ get titleSource(): "auto" | "user" | undefined {
3013
+ return this.#titleSource;
3014
+ }
3015
+
3016
+ getSessionName(): string | undefined {
3017
+ return this.#sessionName;
3018
+ }
3019
+
3020
+ /** Strip C0/C1 control characters (includes ESC, so removes ANSI sequences) and collapse whitespace. */
3021
+ static #sanitizeName(name: string): string {
3022
+ return name
3023
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
3024
+ .replace(/ +/g, " ")
3025
+ .trim();
3026
+ }
3027
+
3028
+ /**
3029
+ * Set the session display name.
3030
+ * @param source - "user" for explicit renames (/rename command, RPC); "auto" for generated titles.
3031
+ * Auto-generated titles are silently ignored when the user has already set a name.
3032
+ */
3033
+ async setSessionName(name: string, source: "auto" | "user" = "auto"): Promise<boolean> {
3034
+ // User-set names take permanent precedence over auto-generated ones.
3035
+ if (this.#titleSource === "user" && source === "auto") return false;
3036
+
3037
+ const sanitized = SessionManager.#sanitizeName(name);
3038
+ if (!sanitized) return false;
3039
+
3040
+ this.#sessionName = sanitized;
3041
+ this.#titleSource = source;
3042
+
3043
+ // Update the in-memory header (so first flush includes title)
3044
+ const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
3045
+ if (header) {
3046
+ header.title = sanitized;
3047
+ header.titleSource = source;
3048
+ }
3049
+
3050
+ // Update the session file header with the title (if already flushed)
3051
+ const sessionFile = this.#sessionFile;
3052
+ if (this.persist && sessionFile && this.storage.existsSync(sessionFile)) {
3053
+ await this.#rewriteFile();
3054
+ }
3055
+ return true;
3056
+ }
3057
+
3058
+ _persist(entry: SessionEntry): void {
3059
+ if (!this.persist || !this.#sessionFile) return;
3060
+ if (this.#persistError) throw this.#persistError;
3061
+
3062
+ // Normally we wait for the first assistant message before persisting to avoid
3063
+ // creating files for sessions that never produce output. Once ensureOnDisk() has
3064
+ // been called, the session is already on disk and every entry must be flushed.
3065
+ if (!this.#ensuredOnDisk) {
3066
+ const hasAssistant = this.#fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
3067
+ if (!hasAssistant) {
3068
+ // Mark as not flushed so when assistant arrives, all entries get written.
3069
+ this.#flushed = false;
3070
+ this.#ensuredOnDisk = false;
3071
+ return;
3072
+ }
3073
+ }
3074
+
3075
+ if (this.#needsFullRewriteOnNextPersist || !this.#flushed) {
3076
+ // Cold path: rewrite the whole file atomically. Async — the writer is
3077
+ // closed/reopened and every entry is re-prepared. Errors flow through
3078
+ // `#persistChain` → `#recordPersistError`; we swallow the rejection
3079
+ // here to avoid an unhandled rejection when the persist dir races with
3080
+ // test-level tempDir cleanup.
3081
+ try {
3082
+ this.#rewriteFileSync();
3083
+ } catch (err) {
3084
+ this.#recordPersistError(err);
3085
+ throw this.#persistError ?? toError(err);
3086
+ }
3087
+ return;
3088
+ }
3089
+
3090
+ // Hot path: synchronously truncate + append. `fs.writeSync` returns once the
3091
+ // bytes are in the kernel page cache, so the entry survives an OOM/SIGKILL
3092
+ // landing immediately after this call. Image externalization (rare) runs via
3093
+ // the synchronous blob-store path so blob bytes are durable before the JSONL
3094
+ // line referencing them is written.
3095
+ try {
3096
+ const writer = this.#ensurePersistWriter();
3097
+ if (!writer) {
3098
+ // `#ensurePersistWriter` returns undefined here only when the cached
3099
+ // writer is mid-close (the `!persist`/`!sessionFile` cases are
3100
+ // rejected above). Route through `#rewriteFile` so the entry — which
3101
+ // is already in `#fileEntries` — persists once the close drains.
3102
+ this.#rewriteFile().catch(() => {});
3103
+ return;
3104
+ }
3105
+ const materializedEntry = materializeResidentEntrySync(
3106
+ entry,
3107
+ this.#residentStores(),
3108
+ new Map(),
3109
+ this.#residentContext(),
3110
+ );
3111
+ const persistedEntry = prepareEntryForPersistenceSync(materializedEntry, this.#blobStore);
3112
+ writer.writeSync(persistedEntry);
3113
+ } catch (err) {
3114
+ this.#recordPersistError(err);
3115
+ throw this.#persistError ?? toError(err);
3116
+ }
3117
+ }
3118
+
3119
+ #appendEntry(entry: SessionEntry): void {
3120
+ const residentEntry = prepareEntryForResidentSync(entry, this.#residentStores()) as SessionEntry;
3121
+ this.#fileEntries.push(residentEntry);
3122
+ this.#byId.set(residentEntry.id, residentEntry);
3123
+ this.#leafId = residentEntry.id;
3124
+ this._persist(residentEntry);
3125
+ if (entry.type === "message" && entry.message.role === "assistant") {
3126
+ const usage = entry.message.usage;
3127
+ this.#usageStatistics.input += usage.input;
3128
+ this.#usageStatistics.output += usage.output;
3129
+ this.#usageStatistics.cacheRead += usage.cacheRead;
3130
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
3131
+ this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
3132
+ this.#usageStatistics.cost += usage.cost.total;
3133
+ }
3134
+
3135
+ if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === "task") {
3136
+ const usage = getTaskToolUsage(entry.message.details);
3137
+ if (usage) {
3138
+ this.#usageStatistics.input += usage.input;
3139
+ this.#usageStatistics.output += usage.output;
3140
+ this.#usageStatistics.cacheRead += usage.cacheRead;
3141
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
3142
+ this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
3143
+ this.#usageStatistics.cost += usage.cost.total;
3144
+ }
3145
+ }
3146
+ }
3147
+
3148
+ /** Append a message as child of current leaf, then advance leaf. Returns entry id.
3149
+ * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
3150
+ * Reason: we want these to be top-level entries in the session, not message session entries,
3151
+ * so it is easier to find them.
3152
+ * These need to be appended via appendCompaction() and appendBranchSummary() methods.
3153
+ */
3154
+ appendMessage(
3155
+ message:
3156
+ | Message
3157
+ | CustomMessage
3158
+ | HookMessage
3159
+ | BashExecutionMessage
3160
+ | PythonExecutionMessage
3161
+ | FileMentionMessage,
3162
+ ): string {
3163
+ const entry: SessionMessageEntry = {
3164
+ type: "message",
3165
+ id: generateId(this.#byId),
3166
+ parentId: this.#leafId,
3167
+ timestamp: new Date().toISOString(),
3168
+ message,
3169
+ };
3170
+ this.#appendEntry(entry);
3171
+ return entry.id;
3172
+ }
3173
+
3174
+ /** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */
3175
+ appendThinkingLevelChange(thinkingLevel?: string): string {
3176
+ const entry: ThinkingLevelChangeEntry = {
3177
+ type: "thinking_level_change",
3178
+ id: generateId(this.#byId),
3179
+ parentId: this.#leafId,
3180
+ timestamp: new Date().toISOString(),
3181
+ thinkingLevel: thinkingLevel ?? null,
3182
+ };
3183
+ this.#appendEntry(entry);
3184
+ return entry.id;
3185
+ }
3186
+
3187
+ appendServiceTierChange(serviceTier: ServiceTier | null): string {
3188
+ const entry: ServiceTierChangeEntry = {
3189
+ type: "service_tier_change",
3190
+ id: generateId(this.#byId),
3191
+ parentId: this.#leafId,
3192
+ timestamp: new Date().toISOString(),
3193
+ serviceTier,
3194
+ };
3195
+ this.#appendEntry(entry);
3196
+ return entry.id;
3197
+ }
3198
+
3199
+ /** Append a mode change as child of current leaf, then advance leaf. Returns entry id. */
3200
+ appendModeChange(mode: string, data?: Record<string, unknown>): string {
3201
+ const entry: ModeChangeEntry = {
3202
+ type: "mode_change",
3203
+ id: generateId(this.#byId),
3204
+ parentId: this.#leafId,
3205
+ timestamp: new Date().toISOString(),
3206
+ mode,
3207
+ data,
3208
+ };
3209
+ this.#appendEntry(entry);
3210
+ return entry.id;
3211
+ }
3212
+
3213
+ /**
3214
+ * Append a model change as child of current leaf, then advance leaf. Returns entry id.
3215
+ * @param model Model in "provider/modelId" format
3216
+ * @param role Optional role (default: "default")
3217
+ */
3218
+ appendModelChange(
3219
+ model: string,
3220
+ role?: string,
3221
+ metadata?: { previousModel?: string; reason?: string; thinkingLevel?: string | null },
3222
+ ): string {
3223
+ const entry: ModelChangeEntry = {
3224
+ type: "model_change",
3225
+ id: generateId(this.#byId),
3226
+ parentId: this.#leafId,
3227
+ timestamp: new Date().toISOString(),
3228
+ model,
3229
+ role,
3230
+ previousModel: metadata?.previousModel,
3231
+ reason: metadata?.reason,
3232
+ thinkingLevel: metadata?.thinkingLevel,
3233
+ };
3234
+ this.#appendEntry(entry);
3235
+ return entry.id;
3236
+ }
3237
+
3238
+ /** Append session init metadata (for subagent debugging/replay). Returns entry id. */
3239
+ appendSessionInit(init: {
3240
+ systemPrompt: string;
3241
+ task: string;
3242
+ tools: string[];
3243
+ outputSchema?: unknown;
3244
+ forkContext?: unknown;
3245
+ }): string {
3246
+ const entry: SessionInitEntry = {
3247
+ type: "session_init",
3248
+ id: generateId(this.#byId),
3249
+ parentId: this.#leafId,
3250
+ timestamp: new Date().toISOString(),
3251
+ ...init,
3252
+ };
3253
+ this.#appendEntry(entry);
3254
+ return entry.id;
3255
+ }
3256
+
3257
+ /** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
3258
+ appendCompaction<T = unknown>(
3259
+ summary: string,
3260
+ shortSummary: string | undefined,
3261
+ firstKeptEntryId: string,
3262
+ tokensBefore: number,
3263
+ details?: T,
3264
+ fromExtension?: boolean,
3265
+ preserveData?: Record<string, unknown>,
3266
+ ): string {
3267
+ const entry: CompactionEntry<T> = {
3268
+ type: "compaction",
3269
+ id: generateId(this.#byId),
3270
+ parentId: this.#leafId,
3271
+ timestamp: new Date().toISOString(),
3272
+ summary,
3273
+ shortSummary,
3274
+ firstKeptEntryId,
3275
+ tokensBefore,
3276
+ details,
3277
+ fromExtension,
3278
+ preserveData,
3279
+ };
3280
+ this.#appendEntry(entry);
3281
+ return entry.id;
3282
+ }
3283
+
3284
+ /** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */
3285
+ appendCustomEntry(customType: string, data?: unknown): string {
3286
+ const entry: CustomEntry = {
3287
+ type: "custom",
3288
+ customType,
3289
+ data,
3290
+ id: generateId(this.#byId),
3291
+ parentId: this.#leafId,
3292
+ timestamp: new Date().toISOString(),
3293
+ };
3294
+ this.#appendEntry(entry);
3295
+ return entry.id;
3296
+ }
3297
+
3298
+ /**
3299
+ * Rewrite the session file after in-place entry updates.
3300
+ * Use sparingly (e.g., pruning old tool outputs).
3301
+ */
3302
+ async rewriteEntries(): Promise<void> {
3303
+ if (!this.persist || !this.#sessionFile) return;
3304
+ await this.#rewriteFile();
3305
+ }
3306
+
3307
+ /**
3308
+ * Apply message updates to resident session entries before rewriting the file.
3309
+ * `getBranch()` materializes blob-backed entries, so callers that transform
3310
+ * those materialized entries must write the changed messages back explicitly.
3311
+ */
3312
+ applyMessageEntryUpdates(entries: SessionMessageEntry[]): number {
3313
+ let updated = 0;
3314
+ for (const update of entries) {
3315
+ const current = this.#byId.get(update.id);
3316
+ if (current?.type !== "message") continue;
3317
+ const prepared = prepareEntryForResidentSync(
3318
+ { ...current, message: update.message },
3319
+ this.#residentStores(),
3320
+ ) as SessionMessageEntry;
3321
+ current.message = prepared.message;
3322
+ updated++;
3323
+ }
3324
+ return updated;
3325
+ }
3326
+
3327
+ /**
3328
+ * Append a custom message entry (for extensions) that participates in LLM context.
3329
+ * @param customType Hook identifier for filtering on reload
3330
+ * @param content Message content (string or TextContent/ImageContent array)
3331
+ * @param display Whether to show in TUI (true = styled display, false = hidden)
3332
+ * @param details Optional extension-specific metadata (not sent to LLM)
3333
+ * @param attribution Who initiated this message for billing/attribution semantics
3334
+ * @returns Entry id
3335
+ */
3336
+ appendCustomMessageEntry<T = unknown>(
3337
+ customType: string,
3338
+ content: string | (TextContent | ImageContent)[],
3339
+ display: boolean,
3340
+ details?: T,
3341
+ attribution: MessageAttribution = "agent",
3342
+ ): string {
3343
+ const entry: CustomMessageEntry<T> = {
3344
+ type: "custom_message",
3345
+ customType,
3346
+ content,
3347
+ display,
3348
+ // Drop AgentSession-internal transient fields (allowlist in
3349
+ // `INTERNAL_DETAILS_FIELDS`) before disk persistence. Single
3350
+ // chokepoint covers every CustomMessage write path.
3351
+ details: stripInternalDetailsFields(details),
3352
+ attribution,
3353
+ id: generateId(this.#byId),
3354
+ parentId: this.#leafId,
3355
+ timestamp: new Date().toISOString(),
3356
+ };
3357
+ this.#appendEntry(entry);
3358
+ return entry.id;
3359
+ }
3360
+
3361
+ // =========================================================================
3362
+ // TTSR (Time Traveling Stream Rules)
3363
+ // =========================================================================
3364
+
3365
+ /**
3366
+ * Append an MCP tool selection entry recording the discovery-selected MCP tools.
3367
+ * @param selectedToolNames MCP tool names selected for this branch
3368
+ * @returns Entry id
3369
+ */
3370
+ appendMCPToolSelection(selectedToolNames: string[]): string {
3371
+ const entry: MCPToolSelectionEntry = {
3372
+ type: "mcp_tool_selection",
3373
+ id: generateId(this.#byId),
3374
+ parentId: this.#leafId,
3375
+ timestamp: new Date().toISOString(),
3376
+ selectedToolNames: [...selectedToolNames],
3377
+ };
3378
+ this.#appendEntry(entry);
3379
+ return entry.id;
3380
+ }
3381
+
3382
+ /**
3383
+ * Append a TTSR injection entry recording which rules were injected.
3384
+ * @param ruleNames Names of rules that were injected
3385
+ * @returns Entry id
3386
+ */
3387
+ appendTtsrInjection(ruleNames: string[]): string {
3388
+ const entry: TtsrInjectionEntry = {
3389
+ type: "ttsr_injection",
3390
+ id: generateId(this.#byId),
3391
+ parentId: this.#leafId,
3392
+ timestamp: new Date().toISOString(),
3393
+ injectedRules: ruleNames,
3394
+ };
3395
+ this.#appendEntry(entry);
3396
+ return entry.id;
3397
+ }
3398
+
3399
+ /**
3400
+ * Get all unique TTSR rule names that have been injected in the current branch.
3401
+ * Scans from root to current leaf for ttsr_injection entries.
3402
+ */
3403
+ getInjectedTtsrRules(): string[] {
3404
+ const path = this.getBranch();
3405
+ const ruleNames = new Set<string>();
3406
+ for (const entry of path) {
3407
+ if (entry.type === "ttsr_injection") {
3408
+ for (const name of entry.injectedRules) {
3409
+ ruleNames.add(name);
3410
+ }
3411
+ }
3412
+ }
3413
+ return Array.from(ruleNames);
3414
+ }
3415
+
3416
+ // =========================================================================
3417
+ // Tree Traversal
3418
+ // =========================================================================
3419
+
3420
+ getLeafId(): string | null {
3421
+ return this.#leafId;
3422
+ }
3423
+
3424
+ getLeafEntry(): SessionEntry | undefined {
3425
+ if (!this.#leafId) return undefined;
3426
+ const entry = this.#byId.get(this.#leafId);
3427
+ return entry
3428
+ ? materializeResidentEntrySync(entry, this.#residentStores(), new Map(), this.#residentContext())
3429
+ : undefined;
3430
+ }
3431
+
3432
+ /**
3433
+ * Get the most recent model role from the current session path.
3434
+ * Returns undefined if no model change has been recorded.
3435
+ */
3436
+ getLastModelChangeRole(): string | undefined {
3437
+ let current = this.getLeafEntry();
3438
+ while (current) {
3439
+ if (current.type === "model_change") {
3440
+ return current.role ?? "default";
3441
+ }
3442
+ current = current.parentId ? this.#byId.get(current.parentId) : undefined;
3443
+ }
3444
+ return undefined;
3445
+ }
3446
+
3447
+ getEntry(id: string): SessionEntry | undefined {
3448
+ const entry = this.#byId.get(id);
3449
+ return entry
3450
+ ? materializeResidentEntrySync(entry, this.#residentStores(), new Map(), this.#residentContext())
3451
+ : undefined;
3452
+ }
3453
+
3454
+ /**
3455
+ * Get all direct children of an entry.
3456
+ */
3457
+ getChildren(parentId: string): SessionEntry[] {
3458
+ const cache = new Map<string, string>();
3459
+ const children: SessionEntry[] = [];
3460
+ for (const entry of this.#byId.values()) {
3461
+ if (entry.parentId === parentId) {
3462
+ children.push(materializeResidentEntrySync(entry, this.#residentStores(), cache, this.#residentContext()));
3463
+ }
3464
+ }
3465
+ return children;
3466
+ }
3467
+
3468
+ /**
3469
+ * Get the label for an entry, if any.
3470
+ */
3471
+ getLabel(id: string): string | undefined {
3472
+ return this.#labelsById.get(id);
3473
+ }
3474
+
3475
+ /**
3476
+ * Set or clear a label on an entry.
3477
+ * Labels are user-defined markers for bookmarking/navigation.
3478
+ * Pass undefined or empty string to clear the label.
3479
+ */
3480
+ appendLabelChange(targetId: string, label: string | undefined): string {
3481
+ if (!this.#byId.has(targetId)) {
3482
+ throw new Error(`Entry ${targetId} not found`);
3483
+ }
3484
+ const entry: LabelEntry = {
3485
+ type: "label",
3486
+ id: generateId(this.#byId),
3487
+ parentId: this.#leafId,
3488
+ timestamp: new Date().toISOString(),
3489
+ targetId,
3490
+ label,
3491
+ };
3492
+ this.#appendEntry(entry);
3493
+ if (label) {
3494
+ this.#labelsById.set(targetId, label);
3495
+ } else {
3496
+ this.#labelsById.delete(targetId);
3497
+ }
3498
+ return entry.id;
3499
+ }
3500
+
3501
+ /**
3502
+ * Walk from entry to root, returning all entries in path order.
3503
+ * Includes all entry types (messages, compaction, model changes, etc.).
3504
+ * Use buildSessionContext() to get the resolved messages for the LLM.
3505
+ */
3506
+ getBranch(fromId?: string): SessionEntry[] {
3507
+ const cache = new Map<string, string>();
3508
+ const path: SessionEntry[] = [];
3509
+ const startId = fromId ?? this.#leafId;
3510
+ let current = startId ? this.#byId.get(startId) : undefined;
3511
+ while (current) {
3512
+ path.unshift(materializeResidentEntrySync(current, this.#residentStores(), cache, this.#residentContext()));
3513
+ current = current.parentId ? this.#byId.get(current.parentId) : undefined;
3514
+ }
3515
+ return path;
3516
+ }
3517
+
3518
+ /**
3519
+ * Build the session context (what gets sent to the LLM).
3520
+ * Uses tree traversal from current leaf.
3521
+ */
3522
+ buildSessionContext(): SessionContext {
3523
+ return buildSessionContext(this.getEntries(), this.#leafId);
3524
+ }
3525
+
3526
+ buildVisibleTranscriptContext(): SessionContext {
3527
+ return buildVisibleTranscriptContext(this.getEntries(), this.#leafId);
3528
+ }
3529
+
3530
+ /** Strip stale OpenAI Responses assistant replay metadata from loaded in-memory entries. */
3531
+ sanitizeLoadedOpenAIResponsesReplayMetadata(): boolean {
3532
+ let didSanitize = false;
3533
+ for (const entry of this.#fileEntries) {
3534
+ if (entry.type !== "message" || entry.message.role !== "assistant") {
3535
+ continue;
3536
+ }
3537
+
3538
+ const sanitizedMessage = sanitizeRehydratedOpenAIResponsesAssistantMessage(entry.message);
3539
+ if (sanitizedMessage === entry.message) {
3540
+ continue;
3541
+ }
3542
+
3543
+ entry.message = sanitizedMessage;
3544
+ didSanitize = true;
3545
+ }
3546
+
3547
+ return didSanitize;
3548
+ }
3549
+
3550
+ /**
3551
+ * Get session header.
3552
+ */
3553
+ getHeader(): SessionHeader | null {
3554
+ const h = this.#fileEntries.find(e => e.type === "session");
3555
+ return h ? (h as SessionHeader) : null;
3556
+ }
3557
+
3558
+ /**
3559
+ * Get all session entries (excludes header). Returns a shallow copy.
3560
+ * The session is append-only: use appendXXX() to add entries, branch() to
3561
+ * change the leaf pointer. Entries cannot be modified or deleted.
3562
+ */
3563
+ getEntries(): SessionEntry[] {
3564
+ return materializeResidentEntriesSync(
3565
+ this.#fileEntries.filter((e): e is SessionEntry => e.type !== "session"),
3566
+ this.#residentStores(),
3567
+ this.#residentContext(),
3568
+ );
3569
+ }
3570
+
3571
+ /**
3572
+ * Get the session as a tree structure. Returns a shallow defensive copy of all entries.
3573
+ * A well-formed session has exactly one root (first entry with parentId === null).
3574
+ * Orphaned entries (broken parent chain) are also returned as roots.
3575
+ */
3576
+ getTree(): SessionTreeNode[] {
3577
+ const entries = this.getEntries();
3578
+ const nodeMap = new Map<string, SessionTreeNode>();
3579
+ const roots: SessionTreeNode[] = [];
3580
+
3581
+ // Create nodes with resolved labels
3582
+ for (const entry of entries) {
3583
+ const label = this.#labelsById.get(entry.id);
3584
+ nodeMap.set(entry.id, { entry, children: [], label });
3585
+ }
3586
+
3587
+ // Build tree
3588
+ for (const entry of entries) {
3589
+ const node = nodeMap.get(entry.id)!;
3590
+ if (entry.parentId === null || entry.parentId === entry.id) {
3591
+ roots.push(node);
3592
+ } else {
3593
+ const parent = nodeMap.get(entry.parentId);
3594
+ if (parent) {
3595
+ parent.children.push(node);
3596
+ } else {
3597
+ // Orphan - treat as root
3598
+ roots.push(node);
3599
+ }
3600
+ }
3601
+ }
3602
+
3603
+ // Sort children by timestamp (oldest first, newest at bottom)
3604
+ // Use iterative approach to avoid stack overflow on deep trees
3605
+ const stack: SessionTreeNode[] = [...roots];
3606
+ while (stack.length > 0) {
3607
+ const node = stack.pop()!;
3608
+ node.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());
3609
+ stack.push(...node.children);
3610
+ }
3611
+
3612
+ return roots;
3613
+ }
3614
+
3615
+ // =========================================================================
3616
+ // Branching
3617
+ // =========================================================================
3618
+
3619
+ /**
3620
+ * Start a new branch from an earlier entry.
3621
+ * Moves the leaf pointer to the specified entry. The next appendXXX() call
3622
+ * will create a child of that entry, forming a new branch. Existing entries
3623
+ * are not modified or deleted.
3624
+ */
3625
+ branch(branchFromId: string): void {
3626
+ if (!this.#byId.has(branchFromId)) {
3627
+ throw new Error(`Entry ${branchFromId} not found`);
3628
+ }
3629
+ this.#leafId = branchFromId;
3630
+ }
3631
+
3632
+ /**
3633
+ * Reset the leaf pointer to null (before any entries).
3634
+ * The next appendXXX() call will create a new root entry (parentId = null).
3635
+ * Use this when navigating to re-edit the first user message.
3636
+ */
3637
+ resetLeaf(): void {
3638
+ this.#leafId = null;
3639
+ }
3640
+
3641
+ /**
3642
+ * Start a new branch with a summary of the abandoned path.
3643
+ * Same as branch(), but also appends a branch_summary entry that captures
3644
+ * context from the abandoned conversation path.
3645
+ */
3646
+ branchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromExtension?: boolean): string {
3647
+ if (branchFromId !== null && !this.#byId.has(branchFromId)) {
3648
+ throw new Error(`Entry ${branchFromId} not found`);
3649
+ }
3650
+ this.#leafId = branchFromId;
3651
+ const entry: BranchSummaryEntry = {
3652
+ type: "branch_summary",
3653
+ id: generateId(this.#byId),
3654
+ parentId: branchFromId,
3655
+ timestamp: new Date().toISOString(),
3656
+ fromId: branchFromId ?? "root",
3657
+ summary,
3658
+ details,
3659
+ fromExtension,
3660
+ };
3661
+ this.#appendEntry(entry);
3662
+ return entry.id;
3663
+ }
3664
+
3665
+ /**
3666
+ * Create a new session file containing only the path from root to the specified leaf.
3667
+ * Useful for extracting a single conversation path from a branched session.
3668
+ * Returns the new session file path, or undefined if not persisting.
3669
+ */
3670
+ createBranchedSession(leafId: string): string | undefined {
3671
+ const previousSessionFile = this.#sessionFile;
3672
+ const branchPath = this.getBranch(leafId);
3673
+ if (branchPath.length === 0) {
3674
+ throw new Error(`Entry ${leafId} not found`);
3675
+ }
3676
+
3677
+ // Filter out LabelEntry from path - we'll recreate them from the resolved map
3678
+ const pathWithoutLabels = branchPath.filter(e => e.type !== "label");
3679
+
3680
+ const newSessionId = createSessionId();
3681
+ const timestamp = new Date().toISOString();
3682
+ const fileTimestamp = timestamp.replace(/[:.]/g, "-");
3683
+ const newSessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${newSessionId}.jsonl`);
3684
+
3685
+ const header: SessionHeader = {
3686
+ type: "session",
3687
+ version: CURRENT_SESSION_VERSION,
3688
+ id: newSessionId,
3689
+ timestamp,
3690
+ cwd: this.cwd,
3691
+ parentSession: this.persist ? previousSessionFile : undefined,
3692
+ };
3693
+
3694
+ // Collect labels for entries in the path
3695
+ const pathEntryIds = new Set(pathWithoutLabels.map(e => e.id));
3696
+ const labelsToWrite: Array<{ targetId: string; label: string }> = [];
3697
+ for (const [targetId, label] of this.#labelsById) {
3698
+ if (pathEntryIds.has(targetId)) {
3699
+ labelsToWrite.push({ targetId, label });
3700
+ }
3701
+ }
3702
+
3703
+ if (this.persist) {
3704
+ const lines: string[] = [];
3705
+ lines.push(JSON.stringify(header));
3706
+ for (const entry of pathWithoutLabels) {
3707
+ lines.push(JSON.stringify(prepareEntryForPersistenceSync(entry, this.#blobStore)));
3708
+ }
3709
+ // Write fresh label entries at the end
3710
+ const lastEntryId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id || null;
3711
+ let parentId = lastEntryId;
3712
+ const labelEntries: LabelEntry[] = [];
3713
+ for (const { targetId, label } of labelsToWrite) {
3714
+ const labelEntry: LabelEntry = {
3715
+ type: "label",
3716
+ id: generateId(new Set(pathEntryIds)),
3717
+ parentId,
3718
+ timestamp: new Date().toISOString(),
3719
+ targetId,
3720
+ label,
3721
+ };
3722
+ lines.push(JSON.stringify(prepareEntryForPersistenceSync(labelEntry, this.#blobStore)));
3723
+ pathEntryIds.add(labelEntry.id);
3724
+ labelEntries.push(labelEntry);
3725
+ parentId = labelEntry.id;
3726
+ }
3727
+ this.storage.writeTextSync(newSessionFile, `${lines.join("\n")}\n`);
3728
+ this.#resetResidentStores();
3729
+ this.#fileEntries = [
3730
+ header,
3731
+ ...pathWithoutLabels.map(
3732
+ entry => prepareEntryForResidentSync(entry, this.#residentStores()) as SessionEntry,
3733
+ ),
3734
+ ...labelEntries,
3735
+ ];
3736
+ this.#sessionId = newSessionId;
3737
+ this.#sessionFile = newSessionFile;
3738
+ this.#flushed = true;
3739
+ this.#buildIndex();
3740
+ return newSessionFile;
3741
+ }
3742
+
3743
+ // In-memory mode: replace current session with the path + labels
3744
+ const labelEntries: LabelEntry[] = [];
3745
+ let parentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id || null;
3746
+ for (const { targetId, label } of labelsToWrite) {
3747
+ const labelEntry: LabelEntry = {
3748
+ type: "label",
3749
+ id: generateId(new Set([...pathEntryIds, ...labelEntries.map(e => e.id)])),
3750
+ parentId,
3751
+ timestamp: new Date().toISOString(),
3752
+ targetId,
3753
+ label,
3754
+ };
3755
+ labelEntries.push(labelEntry);
3756
+ parentId = labelEntry.id;
3757
+ }
3758
+ this.#resetResidentStores();
3759
+ this.#fileEntries = [
3760
+ header,
3761
+ ...pathWithoutLabels.map(entry => prepareEntryForResidentSync(entry, this.#residentStores()) as SessionEntry),
3762
+ ...labelEntries,
3763
+ ];
3764
+ this.#sessionId = newSessionId;
3765
+ this.#buildIndex();
3766
+ return undefined;
3767
+ }
3768
+
3769
+ /**
3770
+ * Resolve the canonical default session directory for a cwd.
3771
+ */
3772
+ static getDefaultSessionDir(
3773
+ cwd: string,
3774
+ agentDir?: string,
3775
+ storage: SessionStorage = new FileSessionStorage(),
3776
+ ): string {
3777
+ return computeDefaultSessionDir(cwd, storage, getSessionsDir(agentDir));
3778
+ }
3779
+
3780
+ /**
3781
+ * Create a new session.
3782
+ * @param cwd Working directory (stored in session header)
3783
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.jwc/agent/sessions/<encoded-cwd>/).
3784
+ */
3785
+ static create(cwd: string, sessionDir?: string, storage: SessionStorage = new FileSessionStorage()): SessionManager {
3786
+ const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
3787
+ const manager = new SessionManager(cwd, dir, true, storage);
3788
+ manager.#initNewSession();
3789
+ return manager;
3790
+ }
3791
+
3792
+ /**
3793
+ * Fork a session into the current project directory.
3794
+ * Copies history from another session file while creating a new session file in the current sessionDir.
3795
+ */
3796
+ static async forkFrom(
3797
+ sourcePath: string,
3798
+ cwd: string,
3799
+ sessionDir?: string,
3800
+ storage: SessionStorage = new FileSessionStorage(),
3801
+ ): Promise<SessionManager> {
3802
+ const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
3803
+ const manager = new SessionManager(cwd, dir, true, storage);
3804
+ const forkEntries = structuredClone(await loadEntriesFromFile(sourcePath, storage)) as FileEntry[];
3805
+ migrateToCurrentVersion(forkEntries);
3806
+ await resolveBlobRefsInEntries(forkEntries, manager.#blobStore);
3807
+ const sourceHeader = forkEntries.find(e => e.type === "session") as SessionHeader | undefined;
3808
+ const historyEntries = forkEntries.filter((entry): entry is SessionEntry => entry.type !== "session");
3809
+ manager.#newSessionSync({ parentSession: sourceHeader?.id });
3810
+ const newHeader = manager.#fileEntries[0] as SessionHeader;
3811
+ newHeader.title = sourceHeader?.title;
3812
+ newHeader.titleSource = sourceHeader?.titleSource;
3813
+ manager.#fileEntries = [
3814
+ newHeader,
3815
+ ...historyEntries.map(entry => prepareEntryForResidentSync(entry, manager.#residentStores()) as SessionEntry),
3816
+ ];
3817
+ manager.#sessionName = newHeader.title;
3818
+ manager.#titleSource = newHeader.titleSource;
3819
+ manager.sanitizeLoadedOpenAIResponsesReplayMetadata();
3820
+ manager.#buildIndex();
3821
+ await manager.#rewriteFile();
3822
+ return manager;
3823
+ }
3824
+
3825
+ /**
3826
+ * Open a specific session file.
3827
+ * @param path Path to session file
3828
+ * @param sessionDir Optional session directory for /new or /branch. If omitted, derives from file's parent.
3829
+ */
3830
+ static async open(
3831
+ filePath: string,
3832
+ sessionDir?: string,
3833
+ storage: SessionStorage = new FileSessionStorage(),
3834
+ ): Promise<SessionManager> {
3835
+ // Extract cwd from session header if possible, otherwise use getProjectDir()
3836
+ const resolved = path.resolve(filePath);
3837
+ const entries = await loadEntriesFromFile(resolved, storage);
3838
+ const header = entries.find(e => e.type === "session") as SessionHeader | undefined;
3839
+ const cwd = header?.cwd ?? getProjectDir();
3840
+ // If no sessionDir provided, derive from file's parent directory
3841
+ const dir = sessionDir ?? path.resolve(resolved, "..");
3842
+ const manager = new SessionManager(cwd, dir, true, storage);
3843
+ await manager.#adoptLoadedSessionFile(resolved, entries);
3844
+ return manager;
3845
+ }
3846
+
3847
+ /**
3848
+ * Continue the most recent session, or create new if none.
3849
+ * @param cwd Working directory
3850
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.jwc/agent/sessions/<encoded-cwd>/).
3851
+ */
3852
+ static async continueRecent(
3853
+ cwd: string,
3854
+ sessionDir?: string,
3855
+ storage: SessionStorage = new FileSessionStorage(),
3856
+ ): Promise<SessionManager> {
3857
+ const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
3858
+ // Prefer terminal-scoped breadcrumb (handles concurrent sessions correctly)
3859
+ const terminalSession = await readTerminalBreadcrumb(cwd);
3860
+ const mostRecent = terminalSession ?? (await findMostRecentSession(dir, storage));
3861
+ const manager = new SessionManager(cwd, dir, true, storage);
3862
+ if (mostRecent) {
3863
+ await manager.#initSessionFile(mostRecent);
3864
+ } else {
3865
+ manager.#initNewSession();
3866
+ }
3867
+ return manager;
3868
+ }
3869
+
3870
+ /** Create an in-memory session (no file persistence) */
3871
+ static inMemory(
3872
+ cwd: string = getProjectDir(),
3873
+ storage: SessionStorage = new MemorySessionStorage(),
3874
+ ): SessionManager {
3875
+ const manager = new SessionManager(cwd, "", false, storage);
3876
+ manager.#initNewSession();
3877
+ return manager;
3878
+ }
3879
+
3880
+ /**
3881
+ * List all sessions.
3882
+ * @param cwd Working directory (used to compute default session directory)
3883
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.jwc/agent/sessions/<encoded-cwd>/).
3884
+ */
3885
+ static async list(
3886
+ cwd: string,
3887
+ sessionDir?: string,
3888
+ storage: SessionStorage = new FileSessionStorage(),
3889
+ ): Promise<SessionInfo[]> {
3890
+ const dir = sessionDir ?? SessionManager.getDefaultSessionDir(cwd, undefined, storage);
3891
+ try {
3892
+ await recoverOrphanedBackups(dir, storage);
3893
+ const files = storage.listFilesSync(dir, "*.jsonl");
3894
+ return await collectSessionsFromFiles(files, storage);
3895
+ } catch {
3896
+ return [];
3897
+ }
3898
+ }
3899
+
3900
+ /**
3901
+ * List all sessions across all project directories.
3902
+ */
3903
+ static async listAll(storage: SessionStorage = new FileSessionStorage()): Promise<SessionInfo[]> {
3904
+ const sessionsRoot = path.join(getDefaultAgentDir(), "sessions");
3905
+ try {
3906
+ const files = await Array.fromAsync(new Bun.Glob("*/*.jsonl").scan(sessionsRoot), name =>
3907
+ path.join(sessionsRoot, name),
3908
+ );
3909
+ return await collectSessionsFromFiles(files, storage);
3910
+ } catch {
3911
+ return [];
3912
+ }
3913
+ }
3914
+ }