@zergai/ztc 0.4.0-beta.1 → 0.5.0-beta.1

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 (718) hide show
  1. package/README.md +398 -45
  2. package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
  3. package/THIRD_PARTY_NOTICES.md +273 -12
  4. package/bin/zmux-darwin-arm64 +0 -0
  5. package/bin/ztc-audio-darwin-arm64 +0 -0
  6. package/bin/ztc-capture-darwin-arm64 +0 -0
  7. package/bin/ztc-computer-darwin-arm64 +0 -0
  8. package/bin/ztc-llm-darwin-arm64 +0 -0
  9. package/bin/ztc-tts-darwin-arm64 +0 -0
  10. package/bin/ztc-tui-darwin-arm64 +0 -0
  11. package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
  12. package/bin/zterm-darwin-arm64 +0 -0
  13. package/dist/App.js +1206 -209
  14. package/dist/agent/agent.d.ts +74 -8
  15. package/dist/agent/agent.js +1073 -184
  16. package/dist/agent/backends/account_rotation.d.ts +4 -0
  17. package/dist/agent/backends/account_rotation.js +8 -2
  18. package/dist/agent/backends/anthropic.d.ts +13 -0
  19. package/dist/agent/backends/anthropic.js +169 -28
  20. package/dist/agent/backends/claude_cli.js +18 -8
  21. package/dist/agent/backends/codex_app_server.d.ts +1 -0
  22. package/dist/agent/backends/codex_app_server.js +55 -3
  23. package/dist/agent/backends/codex_auth.d.ts +1 -0
  24. package/dist/agent/backends/codex_auth.js +67 -13
  25. package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
  26. package/dist/agent/backends/codex_buffered_recovery.js +293 -0
  27. package/dist/agent/backends/gemini.js +3 -1
  28. package/dist/agent/backends/http_error.d.ts +26 -0
  29. package/dist/agent/backends/http_error.js +221 -0
  30. package/dist/agent/backends/index.d.ts +9 -2
  31. package/dist/agent/backends/index.js +22 -2
  32. package/dist/agent/backends/openai.d.ts +0 -6
  33. package/dist/agent/backends/openai.js +5 -2
  34. package/dist/agent/backends/openai_compatible.d.ts +5 -4
  35. package/dist/agent/backends/openai_compatible.js +26 -19
  36. package/dist/agent/backends/openai_responses.d.ts +8 -3
  37. package/dist/agent/backends/openai_responses.js +77 -33
  38. package/dist/agent/backends/provider_ids.d.ts +1 -0
  39. package/dist/agent/backends/provider_ids.js +4 -0
  40. package/dist/agent/backends/request_metrics.js +12 -2
  41. package/dist/agent/backends/types.d.ts +109 -6
  42. package/dist/agent/backends/types.js +5 -1
  43. package/dist/agent/backends/zrouter_stream.d.ts +6 -1
  44. package/dist/agent/backends/zrouter_stream.js +160 -42
  45. package/dist/agent/background_registry.d.ts +2 -0
  46. package/dist/agent/background_registry.js +1 -0
  47. package/dist/agent/claim_observation.d.ts +6 -0
  48. package/dist/agent/claim_observation.js +54 -0
  49. package/dist/agent/commands/accounts.js +46 -177
  50. package/dist/agent/commands/attention.d.ts +2 -0
  51. package/dist/agent/commands/attention.js +67 -0
  52. package/dist/agent/commands/auth.js +18 -2
  53. package/dist/agent/commands/board.d.ts +2 -0
  54. package/dist/agent/commands/board.js +169 -0
  55. package/dist/agent/commands/clipboard.js +50 -1
  56. package/dist/agent/commands/compact.js +21 -13
  57. package/dist/agent/commands/config.js +4 -1
  58. package/dist/agent/commands/context.js +6 -1
  59. package/dist/agent/commands/desktop.d.ts +2 -0
  60. package/dist/agent/commands/desktop.js +29 -0
  61. package/dist/agent/commands/effort.js +14 -6
  62. package/dist/agent/commands/environment.d.ts +3 -0
  63. package/dist/agent/commands/environment.js +109 -0
  64. package/dist/agent/commands/evidence.d.ts +2 -0
  65. package/dist/agent/commands/evidence.js +9 -0
  66. package/dist/agent/commands/fork.js +4 -3
  67. package/dist/agent/commands/goal.d.ts +33 -1
  68. package/dist/agent/commands/goal.js +174 -14
  69. package/dist/agent/commands/goal_exit.d.ts +36 -0
  70. package/dist/agent/commands/goal_exit.js +120 -0
  71. package/dist/agent/commands/help.js +3 -1
  72. package/dist/agent/commands/history.js +3 -1
  73. package/dist/agent/commands/inbox.d.ts +2 -0
  74. package/dist/agent/commands/inbox.js +148 -0
  75. package/dist/agent/commands/index.js +16 -1
  76. package/dist/agent/commands/input_mode.js +8 -6
  77. package/dist/agent/commands/job.d.ts +2 -0
  78. package/dist/agent/commands/job.js +93 -0
  79. package/dist/agent/commands/keybindings.js +4 -1
  80. package/dist/agent/commands/label.d.ts +2 -0
  81. package/dist/agent/commands/label.js +77 -0
  82. package/dist/agent/commands/layout.d.ts +2 -0
  83. package/dist/agent/commands/layout.js +20 -0
  84. package/dist/agent/commands/mirror.js +16 -1
  85. package/dist/agent/commands/model.js +12 -2
  86. package/dist/agent/commands/permissions.d.ts +3 -1
  87. package/dist/agent/commands/permissions.js +79 -89
  88. package/dist/agent/commands/plan.js +5 -15
  89. package/dist/agent/commands/profile.js +21 -2
  90. package/dist/agent/commands/provider_selection.js +20 -1
  91. package/dist/agent/commands/rename.js +1 -1
  92. package/dist/agent/commands/resources.d.ts +2 -0
  93. package/dist/agent/commands/resources.js +67 -0
  94. package/dist/agent/commands/rewind.d.ts +3 -0
  95. package/dist/agent/commands/rewind.js +16 -0
  96. package/dist/agent/commands/runbooks.d.ts +2 -0
  97. package/dist/agent/commands/runbooks.js +73 -0
  98. package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
  99. package/dist/agent/commands/runbooks_lazy.js +8 -0
  100. package/dist/agent/commands/sessions.d.ts +1 -1
  101. package/dist/agent/commands/sessions.js +33 -5
  102. package/dist/agent/commands/settings.js +185 -39
  103. package/dist/agent/commands/setup.d.ts +2 -0
  104. package/dist/agent/commands/setup.js +78 -0
  105. package/dist/agent/commands/share.d.ts +2 -0
  106. package/dist/agent/commands/share.js +64 -0
  107. package/dist/agent/commands/status.js +14 -2
  108. package/dist/agent/commands/types.d.ts +99 -6
  109. package/dist/agent/commands/update.js +8 -0
  110. package/dist/agent/commands/usage.d.ts +2 -0
  111. package/dist/agent/commands/usage.js +8 -0
  112. package/dist/agent/factory.d.ts +11 -0
  113. package/dist/agent/factory.js +113 -88
  114. package/dist/agent/loop_policy.d.ts +6 -3
  115. package/dist/agent/loop_policy.js +12 -3
  116. package/dist/agent/measurement_guidance.d.ts +2 -0
  117. package/dist/agent/measurement_guidance.js +5 -0
  118. package/dist/agent/request_encoder.d.ts +8 -1
  119. package/dist/agent/request_encoder.js +95 -23
  120. package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
  121. package/dist/agent/runtime/benchmark_comparison.js +94 -0
  122. package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
  123. package/dist/agent/runtime/benchmark_contract.js +90 -0
  124. package/dist/agent/runtime/checked_commands.d.ts +32 -0
  125. package/dist/agent/runtime/checked_commands.js +119 -0
  126. package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
  127. package/dist/agent/runtime/command_execution_policy.js +91 -0
  128. package/dist/agent/runtime/command_jobs.d.ts +36 -18
  129. package/dist/agent/runtime/command_jobs.js +151 -13
  130. package/dist/agent/runtime/command_panes.d.ts +59 -0
  131. package/dist/agent/runtime/command_panes.js +398 -0
  132. package/dist/agent/runtime/command_wait.d.ts +19 -0
  133. package/dist/agent/runtime/command_wait.js +167 -0
  134. package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
  135. package/dist/agent/runtime/cpu_benchmark.js +126 -0
  136. package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
  137. package/dist/agent/runtime/delegated_evidence.js +80 -0
  138. package/dist/agent/runtime/evidence.d.ts +127 -0
  139. package/dist/agent/runtime/evidence.js +468 -0
  140. package/dist/agent/runtime/execution_context.d.ts +7 -0
  141. package/dist/agent/runtime/execution_context.js +39 -3
  142. package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
  143. package/dist/agent/runtime/json_verifier_recipe.js +66 -0
  144. package/dist/agent/runtime/measurements.d.ts +110 -0
  145. package/dist/agent/runtime/measurements.js +317 -0
  146. package/dist/agent/runtime/policy.d.ts +4 -0
  147. package/dist/agent/runtime/policy.js +31 -20
  148. package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
  149. package/dist/agent/runtime/provider_native_evidence.js +86 -0
  150. package/dist/agent/runtime/recovery.d.ts +2 -0
  151. package/dist/agent/runtime/recovery.js +45 -2
  152. package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
  153. package/dist/agent/runtime/remote_broker_transport.js +84 -0
  154. package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
  155. package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
  156. package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
  157. package/dist/agent/runtime/remote_journal_replay.js +233 -0
  158. package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
  159. package/dist/agent/runtime/remote_mutation_journal.js +208 -0
  160. package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
  161. package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
  162. package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
  163. package/dist/agent/runtime/remote_resource_errors.js +9 -0
  164. package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
  165. package/dist/agent/runtime/remote_resource_script.js +1009 -0
  166. package/dist/agent/runtime/remote_resources.d.ts +29 -0
  167. package/dist/agent/runtime/remote_resources.js +160 -0
  168. package/dist/agent/runtime/source_verification.d.ts +12 -0
  169. package/dist/agent/runtime/source_verification.js +210 -0
  170. package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
  171. package/dist/agent/runtime/source_verification_contract.js +203 -0
  172. package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
  173. package/dist/agent/runtime/tool_checkpoints.js +26 -0
  174. package/dist/agent/runtime/tool_effects.d.ts +5 -0
  175. package/dist/agent/runtime/tool_progress.d.ts +12 -0
  176. package/dist/agent/runtime/tool_progress.js +76 -0
  177. package/dist/agent/runtime/tool_runtime.d.ts +16 -1
  178. package/dist/agent/runtime/tool_runtime.js +204 -10
  179. package/dist/agent/runtime/tracing.d.ts +1 -1
  180. package/dist/agent/runtime/verifier_capture.d.ts +9 -0
  181. package/dist/agent/runtime/verifier_capture.js +100 -0
  182. package/dist/agent/runtime/verifier_contract.d.ts +24 -0
  183. package/dist/agent/runtime/verifier_contract.js +41 -0
  184. package/dist/agent/subagent.d.ts +20 -10
  185. package/dist/agent/subagent.js +266 -29
  186. package/dist/agent/tool_response_directory.d.ts +24 -0
  187. package/dist/agent/tool_response_directory.js +61 -0
  188. package/dist/agent/tool_response_source.d.ts +8 -0
  189. package/dist/agent/tool_response_source.js +49 -0
  190. package/dist/agent/tool_response_store.d.ts +75 -4
  191. package/dist/agent/tool_response_store.js +583 -64
  192. package/dist/agent/tools/ask_user.d.ts +3 -1
  193. package/dist/agent/tools/ask_user.js +30 -11
  194. package/dist/agent/tools/background.js +1 -0
  195. package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
  196. package/dist/agent/tools/benchmark_cpu.js +102 -0
  197. package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
  198. package/dist/agent/tools/compare_benchmarks.js +53 -0
  199. package/dist/agent/tools/deferred.js +1 -0
  200. package/dist/agent/tools/delegate.js +18 -2
  201. package/dist/agent/tools/helper_requirements.d.ts +7 -0
  202. package/dist/agent/tools/helper_requirements.js +19 -0
  203. package/dist/agent/tools/index.d.ts +1 -0
  204. package/dist/agent/tools/index.js +3 -0
  205. package/dist/agent/tools/measurement_report.d.ts +22 -0
  206. package/dist/agent/tools/measurement_report.js +190 -0
  207. package/dist/agent/tools/pane.d.ts +0 -7
  208. package/dist/agent/tools/pane.js +5 -4
  209. package/dist/agent/tools/project_context.d.ts +3 -0
  210. package/dist/agent/tools/project_context.js +45 -0
  211. package/dist/agent/tools/recall_tool_response.d.ts +3 -5
  212. package/dist/agent/tools/recall_tool_response.js +19 -29
  213. package/dist/agent/tools/registry.d.ts +1 -0
  214. package/dist/agent/tools/registry.js +23 -0
  215. package/dist/agent/tools/related_sessions.d.ts +2 -0
  216. package/dist/agent/tools/related_sessions.js +156 -0
  217. package/dist/agent/tools/routine.js +13 -3
  218. package/dist/agent/tools/session_evidence.d.ts +3 -0
  219. package/dist/agent/tools/session_evidence.js +72 -0
  220. package/dist/agent/tools/shell.d.ts +4 -3
  221. package/dist/agent/tools/shell.js +174 -47
  222. package/dist/agent/tools/tool_search.js +3 -2
  223. package/dist/agent/tools/types.d.ts +28 -0
  224. package/dist/agent/tools/verify_artifact.d.ts +2 -0
  225. package/dist/agent/tools/verify_artifact.js +60 -0
  226. package/dist/agent/tools/workflow.js +1 -0
  227. package/dist/app/activity.d.ts +6 -1
  228. package/dist/app/activity.js +81 -12
  229. package/dist/app/agent_runner.d.ts +6 -1
  230. package/dist/app/agent_runner.js +34 -19
  231. package/dist/app/command_context.js +106 -10
  232. package/dist/app/controller.d.ts +15 -4
  233. package/dist/app/controller.js +29 -3
  234. package/dist/app/escape_stop.d.ts +11 -0
  235. package/dist/app/escape_stop.js +15 -0
  236. package/dist/app/liveness.d.ts +6 -4
  237. package/dist/app/liveness.js +13 -3
  238. package/dist/app/model_catalog_cache.d.ts +5 -0
  239. package/dist/app/model_catalog_cache.js +138 -0
  240. package/dist/app/ordered_transcript.js +43 -4
  241. package/dist/app/prompt_turn.d.ts +14 -0
  242. package/dist/app/prompt_turn.js +53 -0
  243. package/dist/app/recap.d.ts +3 -0
  244. package/dist/app/recap.js +9 -3
  245. package/dist/app/restart.d.ts +3 -2
  246. package/dist/app/restart.js +22 -7
  247. package/dist/app/rewind.d.ts +3 -3
  248. package/dist/app/rewind.js +3 -3
  249. package/dist/app/selection_completion.d.ts +17 -2
  250. package/dist/app/selection_completion.js +84 -9
  251. package/dist/app/session_manager.d.ts +38 -6
  252. package/dist/app/session_manager.js +263 -60
  253. package/dist/app/session_messages.d.ts +3 -0
  254. package/dist/app/session_messages.js +60 -0
  255. package/dist/app/task_scope_router.d.ts +20 -0
  256. package/dist/app/task_scope_router.js +173 -0
  257. package/dist/app/transcript_window.d.ts +2 -0
  258. package/dist/app/transcript_window.js +4 -0
  259. package/dist/app/workspace_admission_details.d.ts +3 -0
  260. package/dist/app/workspace_admission_details.js +26 -0
  261. package/dist/app/workspace_layout_controller.d.ts +11 -0
  262. package/dist/app/workspace_layout_controller.js +88 -0
  263. package/dist/app/workspace_monitor.d.ts +1 -0
  264. package/dist/app/workspace_monitor.js +216 -0
  265. package/dist/app/workspace_presets.d.ts +29 -0
  266. package/dist/app/workspace_presets.js +192 -0
  267. package/dist/attention/queue.d.ts +37 -0
  268. package/dist/attention/queue.js +183 -0
  269. package/dist/bench/rf_shared.d.ts +2 -0
  270. package/dist/board/binding.d.ts +42 -0
  271. package/dist/board/binding.js +38 -0
  272. package/dist/board/client.d.ts +42 -0
  273. package/dist/board/client.js +60 -0
  274. package/dist/cli.js +1 -1
  275. package/dist/cli_args.js +19 -17
  276. package/dist/cli_main.js +110 -13
  277. package/dist/cli_ratatui.d.ts +183 -16
  278. package/dist/cli_ratatui.js +3989 -1255
  279. package/dist/components/ActivityLine.d.ts +3 -2
  280. package/dist/components/ActivityLine.js +2 -2
  281. package/dist/components/InputArea.d.ts +19 -1
  282. package/dist/components/InputArea.js +264 -190
  283. package/dist/components/MessageList.d.ts +2 -0
  284. package/dist/components/MessageList.js +3 -1
  285. package/dist/components/SingleMessage.d.ts +2 -0
  286. package/dist/components/SingleMessage.js +4 -2
  287. package/dist/components/StatusBar.d.ts +4 -2
  288. package/dist/components/StatusBar.js +3 -1
  289. package/dist/conductor_branch.js +3 -0
  290. package/dist/config/api_retry.d.ts +6 -1
  291. package/dist/config/api_retry.js +19 -1
  292. package/dist/config/defaults.d.ts +2 -1
  293. package/dist/config/defaults.js +9 -2
  294. package/dist/config/message_display.d.ts +1 -0
  295. package/dist/config/message_display.js +17 -2
  296. package/dist/config/permissions.d.ts +21 -0
  297. package/dist/config/permissions.js +89 -0
  298. package/dist/config/resolved.d.ts +2 -2
  299. package/dist/config/types.d.ts +32 -5
  300. package/dist/config.d.ts +38 -4
  301. package/dist/config.js +290 -42
  302. package/dist/context/project_intelligence.d.ts +44 -0
  303. package/dist/context/project_intelligence.js +246 -0
  304. package/dist/desktop/cli_launch.d.ts +8 -0
  305. package/dist/desktop/cli_launch.js +6 -0
  306. package/dist/desktop/cli_shim.d.ts +8 -0
  307. package/dist/desktop/cli_shim.js +120 -0
  308. package/dist/desktop/runtime.d.ts +16 -0
  309. package/dist/desktop/runtime.js +161 -0
  310. package/dist/desktop/worker_launch.d.ts +7 -0
  311. package/dist/desktop/worker_launch.js +7 -0
  312. package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
  313. package/dist/diagnostics/cargo_mutants_selection.js +48 -0
  314. package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
  315. package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
  316. package/dist/diagnostics/session_evidence_package.d.ts +40 -0
  317. package/dist/diagnostics/session_evidence_package.js +234 -0
  318. package/dist/diagnostics/session_export.d.ts +93 -0
  319. package/dist/diagnostics/session_export.js +634 -0
  320. package/dist/emulation/tool_adapters.js +48 -18
  321. package/dist/emulation/ztc_addendum.d.ts +1 -0
  322. package/dist/emulation/ztc_addendum.js +13 -12
  323. package/dist/environment/service_runner.d.ts +1 -0
  324. package/dist/environment/service_runner.js +68 -0
  325. package/dist/environment/store.d.ts +120 -0
  326. package/dist/environment/store.js +387 -0
  327. package/dist/events.d.ts +40 -1
  328. package/dist/events.js +10 -17
  329. package/dist/goal_contract.d.ts +25 -0
  330. package/dist/goal_contract.js +44 -0
  331. package/dist/headless.d.ts +31 -0
  332. package/dist/headless.js +578 -70
  333. package/dist/headless_cli.js +8 -0
  334. package/dist/headless_cli_args.d.ts +1 -0
  335. package/dist/headless_cli_args.js +1 -0
  336. package/dist/intake/cli.d.ts +1 -0
  337. package/dist/intake/cli.js +33 -0
  338. package/dist/intake/format.d.ts +2 -0
  339. package/dist/intake/format.js +14 -0
  340. package/dist/intake/notifications.d.ts +16 -0
  341. package/dist/intake/notifications.js +132 -0
  342. package/dist/intake/store.d.ts +56 -0
  343. package/dist/intake/store.js +238 -0
  344. package/dist/library.d.ts +8 -0
  345. package/dist/library.js +19 -10
  346. package/dist/mcp/client.d.ts +1 -0
  347. package/dist/mcp/client.js +20 -0
  348. package/dist/modes/builtin/plan.yaml +2 -2
  349. package/dist/modes/handlers.js +1 -1
  350. package/dist/onboarding/first_run.d.ts +3 -0
  351. package/dist/onboarding/first_run.js +33 -25
  352. package/dist/onboarding/setup.d.ts +32 -0
  353. package/dist/onboarding/setup.js +133 -0
  354. package/dist/onboarding/setup_agent_health.d.ts +17 -0
  355. package/dist/onboarding/setup_agent_health.js +79 -0
  356. package/dist/onboarding/setup_environment.d.ts +66 -0
  357. package/dist/onboarding/setup_environment.js +219 -0
  358. package/dist/onboarding/setup_workspace.d.ts +17 -0
  359. package/dist/onboarding/setup_workspace.js +129 -0
  360. package/dist/optimize/adapters/index.d.ts +4 -1
  361. package/dist/optimize/adapters/index.js +7 -0
  362. package/dist/optimize/adapters/kernelbench.js +14 -5
  363. package/dist/optimize/adapters/popcorn.js +3 -2
  364. package/dist/optimize/adapters/profile_benchmark.js +13 -7
  365. package/dist/optimize/adapters/rfbench.js +25 -14
  366. package/dist/optimize/adapters/types.d.ts +3 -0
  367. package/dist/optimize/adapters/zergpod.js +5 -2
  368. package/dist/optimize/baseline.js +10 -21
  369. package/dist/optimize/branch.js +9 -12
  370. package/dist/optimize/combine.js +2 -3
  371. package/dist/optimize/executor.js +34 -48
  372. package/dist/optimize/measurements.d.ts +55 -0
  373. package/dist/optimize/measurements.js +173 -0
  374. package/dist/optimize/results.d.ts +1 -0
  375. package/dist/optimize/results.js +11 -33
  376. package/dist/optimize/tge.js +2 -1
  377. package/dist/panes/initial_input.d.ts +19 -0
  378. package/dist/panes/initial_input.js +206 -0
  379. package/dist/panes/labels.d.ts +16 -0
  380. package/dist/panes/labels.js +42 -0
  381. package/dist/panes/model_catalog.d.ts +3 -3
  382. package/dist/panes/model_catalog.js +28 -4
  383. package/dist/panes/pane_registry.js +6 -2
  384. package/dist/panes/types.d.ts +3 -0
  385. package/dist/panes/ztc_launch.js +19 -0
  386. package/dist/plugin/optimization.d.ts +2 -0
  387. package/dist/plugin/optimization.js +1 -0
  388. package/dist/plugin/runtime.d.ts +2 -0
  389. package/dist/plugin/simulation.d.ts +6 -1
  390. package/dist/plugin/simulation_contract.d.ts +836 -1
  391. package/dist/plugin/simulation_documents.d.ts +22 -0
  392. package/dist/plugin/simulation_documents.js +1 -0
  393. package/dist/plugin/simulation_interventions.d.ts +103 -0
  394. package/dist/plugin/simulation_interventions.js +1 -0
  395. package/dist/plugin/simulation_living.d.ts +1 -0
  396. package/dist/plugin/simulation_living.js +3 -0
  397. package/dist/plugin/simulation_meetings.d.ts +121 -0
  398. package/dist/plugin/simulation_meetings.js +1 -0
  399. package/dist/plugin/simulation_story_review.d.ts +111 -0
  400. package/dist/plugin/simulation_story_review.js +1 -0
  401. package/dist/plugin/simulation_storyteller.d.ts +91 -0
  402. package/dist/plugin/simulation_storyteller.js +1 -0
  403. package/dist/plugin/simulation_structured.d.ts +2 -0
  404. package/dist/plugin/simulation_structured.js +2 -0
  405. package/dist/plugin/simulation_toolkit.d.ts +21 -0
  406. package/dist/plugin/simulation_toolkit.js +24 -0
  407. package/dist/prompts/tool_delegate_task.j2 +1 -1
  408. package/dist/remote-broker/Dockerfile +10 -0
  409. package/dist/remote-broker/archive.py +1227 -0
  410. package/dist/remote-broker/cutover.py +761 -0
  411. package/dist/remote-broker/requirements.txt +5 -0
  412. package/dist/remote-broker/resources.json +6 -0
  413. package/dist/remote-broker/resources.write-staging.example.json +18 -0
  414. package/dist/remote-broker/retained.py +686 -0
  415. package/dist/remote-broker/retained_cleanup.py +550 -0
  416. package/dist/remote-broker/server.py +648 -0
  417. package/dist/remote-broker/worker.py +1007 -0
  418. package/dist/router_auth.d.ts +31 -0
  419. package/dist/router_auth.js +155 -9
  420. package/dist/runbooks/catalog.d.ts +3 -0
  421. package/dist/runbooks/catalog.js +26 -0
  422. package/dist/runbooks/library.d.ts +11 -0
  423. package/dist/runbooks/library.js +95 -0
  424. package/dist/runbooks/package.d.ts +43 -0
  425. package/dist/runbooks/package.js +102 -0
  426. package/dist/session.d.ts +65 -13
  427. package/dist/session.js +548 -43
  428. package/dist/session_handoff.d.ts +25 -0
  429. package/dist/session_handoff.js +322 -0
  430. package/dist/session_handoff_contract.d.ts +89 -0
  431. package/dist/session_handoff_contract.js +179 -0
  432. package/dist/session_media.d.ts +41 -0
  433. package/dist/session_media.js +357 -0
  434. package/dist/session_ownership.d.ts +16 -0
  435. package/dist/session_ownership.js +221 -0
  436. package/dist/session_public.d.ts +10 -2
  437. package/dist/session_public.js +6 -1
  438. package/dist/simulation/dynamic_history.d.ts +42 -0
  439. package/dist/simulation/dynamic_history.js +173 -0
  440. package/dist/simulation/engines/swarm/pane_manager.js +7 -6
  441. package/dist/simulation/inference.d.ts +3 -1
  442. package/dist/simulation/inference.js +19 -1
  443. package/dist/simulation/intervention_generation.d.ts +27 -0
  444. package/dist/simulation/intervention_generation.js +246 -0
  445. package/dist/simulation/intervention_material.d.ts +4 -0
  446. package/dist/simulation/intervention_material.js +81 -0
  447. package/dist/simulation/living/context.d.ts +3130 -0
  448. package/dist/simulation/living/context.js +944 -0
  449. package/dist/simulation/living/definer.d.ts +12 -0
  450. package/dist/simulation/living/definer.js +223 -0
  451. package/dist/simulation/living/definition.d.ts +257 -0
  452. package/dist/simulation/living/definition.js +341 -0
  453. package/dist/simulation/living/frame.d.ts +259 -0
  454. package/dist/simulation/living/frame.js +223 -0
  455. package/dist/simulation/living/identifiers.d.ts +2 -0
  456. package/dist/simulation/living/identifiers.js +12 -0
  457. package/dist/simulation/living/index.d.ts +15 -0
  458. package/dist/simulation/living/index.js +15 -0
  459. package/dist/simulation/living/inference.d.ts +219 -0
  460. package/dist/simulation/living/inference.js +576 -0
  461. package/dist/simulation/living/mechanics.d.ts +54 -0
  462. package/dist/simulation/living/mechanics.js +588 -0
  463. package/dist/simulation/living/model-input.d.ts +25 -0
  464. package/dist/simulation/living/model-input.js +215 -0
  465. package/dist/simulation/living/open-world.d.ts +257 -0
  466. package/dist/simulation/living/open-world.js +102 -0
  467. package/dist/simulation/living/projection.d.ts +7 -0
  468. package/dist/simulation/living/projection.js +31 -0
  469. package/dist/simulation/living/recall.d.ts +17 -0
  470. package/dist/simulation/living/recall.js +25 -0
  471. package/dist/simulation/living/reduce.d.ts +15 -0
  472. package/dist/simulation/living/reduce.js +1853 -0
  473. package/dist/simulation/living/replay.d.ts +6 -0
  474. package/dist/simulation/living/replay.js +22 -0
  475. package/dist/simulation/living/runtime.d.ts +140 -0
  476. package/dist/simulation/living/runtime.js +3020 -0
  477. package/dist/simulation/living/shared-kinds.d.ts +24 -0
  478. package/dist/simulation/living/shared-kinds.js +96 -0
  479. package/dist/simulation/living/types.d.ts +2785 -0
  480. package/dist/simulation/living/types.js +669 -0
  481. package/dist/simulation/llm.d.ts +3 -1
  482. package/dist/simulation/llm.js +90 -30
  483. package/dist/simulation/memory/embedding.d.ts +1 -1
  484. package/dist/simulation/memory/embedding.js +9 -3
  485. package/dist/simulation/memory/filesystem.d.ts +2 -0
  486. package/dist/simulation/memory/filesystem.js +5 -1
  487. package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
  488. package/dist/simulation/memory/filesystem_file_tree.js +41 -10
  489. package/dist/simulation/memory/filesystem_invariants.js +1 -2
  490. package/dist/simulation/memory/filesystem_types.d.ts +1 -0
  491. package/dist/simulation/memory/memory.d.ts +2 -0
  492. package/dist/simulation/memory/memory.js +5 -1
  493. package/dist/simulation/orchestration/intent.js +0 -2
  494. package/dist/simulation/orchestration/lifecycle.js +0 -2
  495. package/dist/simulation/plugin_host.d.ts +25 -1
  496. package/dist/simulation/plugin_host.js +334 -17
  497. package/dist/simulation/session_documents.d.ts +20 -0
  498. package/dist/simulation/session_documents.js +62 -0
  499. package/dist/simulation/session_input_validation.d.ts +43 -0
  500. package/dist/simulation/session_input_validation.js +364 -0
  501. package/dist/simulation/session_inputs.d.ts +32 -0
  502. package/dist/simulation/session_inputs.js +154 -0
  503. package/dist/simulation/telemetry.d.ts +11 -14
  504. package/dist/simulation/telemetry.js +177 -54
  505. package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
  506. package/dist/simulation/toolkit/bounded_turns.js +80 -0
  507. package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
  508. package/dist/simulation/toolkit/dynamic_agents.js +218 -0
  509. package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
  510. package/dist/simulation/toolkit/dynamic_digest.js +8 -0
  511. package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
  512. package/dist/simulation/toolkit/dynamic_documents.js +71 -0
  513. package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
  514. package/dist/simulation/toolkit/dynamic_driver.js +269 -0
  515. package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
  516. package/dist/simulation/toolkit/dynamic_game.js +805 -0
  517. package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
  518. package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
  519. package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
  520. package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
  521. package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
  522. package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
  523. package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
  524. package/dist/simulation/toolkit/dynamic_progress.js +214 -0
  525. package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
  526. package/dist/simulation/toolkit/dynamic_projection.js +219 -0
  527. package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
  528. package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
  529. package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
  530. package/dist/simulation/toolkit/dynamic_state.js +1967 -0
  531. package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
  532. package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
  533. package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
  534. package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
  535. package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
  536. package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
  537. package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
  538. package/dist/simulation/toolkit/dynamic_world.js +377 -0
  539. package/dist/simulation/toolkit/structured_output.d.ts +5 -0
  540. package/dist/simulation/toolkit/structured_output.js +321 -0
  541. package/dist/simulation/types/world.d.ts +2 -0
  542. package/dist/simulation/world/artifact_store.js +1 -1
  543. package/dist/simulation/world/memory_manager.d.ts +5 -0
  544. package/dist/simulation/world/memory_manager.js +17 -2
  545. package/dist/simulation/world/state.d.ts +3 -2
  546. package/dist/simulation/world/state.js +15 -5
  547. package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
  548. package/dist/terminal_attach.d.ts +1 -0
  549. package/dist/terminal_attach.js +113 -0
  550. package/dist/tool_progress.d.ts +3 -0
  551. package/dist/tool_progress.js +27 -0
  552. package/dist/types.d.ts +145 -10
  553. package/dist/types.js +3 -0
  554. package/dist/ui/core/input.d.ts +3 -0
  555. package/dist/ui/core/input_segments.d.ts +18 -1
  556. package/dist/ui/core/input_segments.js +131 -47
  557. package/dist/ui/core/input_state.d.ts +12 -0
  558. package/dist/ui/core/layout_yoga.d.ts +1 -1
  559. package/dist/ui/core/turn_metadata.d.ts +5 -0
  560. package/dist/ui/core/turn_metadata.js +22 -0
  561. package/dist/ui/ink/render.d.ts +2 -1
  562. package/dist/ui/ink/render.js +6 -5
  563. package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
  564. package/dist/ui/ratatui/headless_pane_manager.js +293 -104
  565. package/dist/ui/ratatui/index.d.ts +1 -0
  566. package/dist/ui/ratatui/input.d.ts +4 -0
  567. package/dist/ui/ratatui/input.js +15 -0
  568. package/dist/ui/ratatui/ipc_path.d.ts +2 -0
  569. package/dist/ui/ratatui/ipc_path.js +29 -1
  570. package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
  571. package/dist/ui/ratatui/mux_persistence.js +139 -1
  572. package/dist/ui/ratatui/pane_relay.d.ts +1 -1
  573. package/dist/ui/ratatui/pane_relay.js +55 -27
  574. package/dist/ui/ratatui/process.d.ts +17 -1
  575. package/dist/ui/ratatui/process.js +80 -7
  576. package/dist/ui/ratatui/protocol.d.ts +236 -1
  577. package/dist/ui/ratatui/protocol.js +90 -0
  578. package/dist/ui/scrollback.d.ts +2 -1
  579. package/dist/ui/scrollback.js +9 -4
  580. package/dist/ui/stream_typewriter.d.ts +8 -3
  581. package/dist/ui/stream_typewriter.js +30 -1
  582. package/dist/ui/streaming_scrollback.d.ts +1 -0
  583. package/dist/ui/streaming_scrollback.js +1 -1
  584. package/dist/ui/transcript_render_cache.js +10 -0
  585. package/dist/ui/views/activity_line.d.ts +5 -3
  586. package/dist/ui/views/activity_line.js +65 -7
  587. package/dist/ui/views/app.d.ts +6 -3
  588. package/dist/ui/views/app.js +17 -6
  589. package/dist/ui/views/completion_menu.js +10 -5
  590. package/dist/ui/views/input_area.d.ts +7 -3
  591. package/dist/ui/views/input_area.js +15 -9
  592. package/dist/ui/views/message_list.d.ts +4 -1
  593. package/dist/ui/views/message_list.js +89 -19
  594. package/dist/ui/views/queue_panel.d.ts +3 -1
  595. package/dist/ui/views/queue_panel.js +9 -2
  596. package/dist/ui/views/status_bar.d.ts +4 -2
  597. package/dist/ui/views/status_bar.js +28 -13
  598. package/dist/ui/views/theme.d.ts +2 -0
  599. package/dist/ui/views/theme.js +2 -0
  600. package/dist/ui/widgets/color_panel.js +18 -0
  601. package/dist/ui/widgets/form.js +8 -0
  602. package/dist/ui/widgets/log_view.js +7 -0
  603. package/dist/ui/widgets/navigator_panel.js +8 -0
  604. package/dist/ui/widgets/prompt_viewer.js +2 -0
  605. package/dist/ui/widgets/questionnaire.d.ts +2 -0
  606. package/dist/ui/widgets/questionnaire.js +79 -16
  607. package/dist/ui/widgets/resume_panel.d.ts +2 -0
  608. package/dist/ui/widgets/resume_panel.js +26 -15
  609. package/dist/ui/widgets/rewind_panel.d.ts +2 -2
  610. package/dist/ui/widgets/rewind_panel.js +4 -2
  611. package/dist/ui/widgets/select.js +16 -27
  612. package/dist/ui/widgets/settings_panel.js +16 -0
  613. package/dist/ui/widgets/status_line_panel.d.ts +1 -0
  614. package/dist/ui/widgets/status_line_panel.js +30 -4
  615. package/dist/ui/widgets/text_input.d.ts +2 -0
  616. package/dist/ui/widgets/text_input.js +15 -1
  617. package/dist/ui/widgets/types.d.ts +2 -0
  618. package/dist/usage_projection.d.ts +24 -0
  619. package/dist/usage_projection.js +130 -0
  620. package/dist/usage_snapshot.d.ts +36 -0
  621. package/dist/usage_snapshot.js +131 -0
  622. package/dist/utils/assistant_message.d.ts +2 -0
  623. package/dist/utils/assistant_message.js +5 -0
  624. package/dist/utils/atomic_write.d.ts +6 -2
  625. package/dist/utils/atomic_write.js +29 -4
  626. package/dist/utils/checked_shell.d.ts +25 -0
  627. package/dist/utils/checked_shell.js +107 -0
  628. package/dist/utils/compaction.d.ts +4 -1
  629. package/dist/utils/compaction.js +31 -7
  630. package/dist/utils/context.d.ts +4 -4
  631. package/dist/utils/context.js +14 -12
  632. package/dist/utils/deepseek.d.ts +2 -2
  633. package/dist/utils/deepseek.js +13 -5
  634. package/dist/utils/ephemeral.js +4 -0
  635. package/dist/utils/ephemeral_tools.d.ts +18 -0
  636. package/dist/utils/ephemeral_tools.js +97 -0
  637. package/dist/utils/jev_client.d.ts +4 -0
  638. package/dist/utils/jev_client.js +15 -0
  639. package/dist/utils/model_cache.d.ts +15 -0
  640. package/dist/utils/model_cache.js +143 -0
  641. package/dist/utils/models.d.ts +4 -2
  642. package/dist/utils/models.js +12 -16
  643. package/dist/utils/reasoning.d.ts +8 -1
  644. package/dist/utils/reasoning.js +31 -11
  645. package/dist/utils/router_catalog.d.ts +49 -0
  646. package/dist/utils/router_catalog.js +271 -0
  647. package/dist/utils/sandbox.d.ts +9 -0
  648. package/dist/utils/sandbox.js +118 -15
  649. package/dist/utils/shell.d.ts +7 -0
  650. package/dist/utils/shell.js +98 -34
  651. package/dist/utils/subprocess_env.d.ts +3 -0
  652. package/dist/utils/subprocess_env.js +27 -1
  653. package/dist/web/accounts_mirror.d.ts +3 -1
  654. package/dist/web/accounts_mirror.js +13 -9
  655. package/dist/web/child_publication.d.ts +17 -0
  656. package/dist/web/child_publication.js +45 -0
  657. package/dist/web/mirror_authenticator.d.ts +31 -11
  658. package/dist/web/mirror_authenticator.js +67 -17
  659. package/dist/web/mirror_client.d.ts +31 -4
  660. package/dist/web/mirror_client.js +189 -23
  661. package/dist/web/mirror_stream.d.ts +84 -22
  662. package/dist/web/mirror_stream.js +676 -95
  663. package/dist/web/observer_query_redaction.d.ts +6 -0
  664. package/dist/web/observer_query_redaction.js +58 -0
  665. package/dist/web/pane_registry.d.ts +2 -0
  666. package/dist/web/pane_registry.js +86 -22
  667. package/dist/web/tool_image.d.ts +6 -0
  668. package/dist/web/tool_image.js +17 -0
  669. package/dist/web/view_plugin.d.ts +14 -0
  670. package/dist/web/view_plugin.js +23 -1
  671. package/dist/worker/executor.js +31 -12
  672. package/dist/worker/index.d.ts +1 -0
  673. package/dist/worker/toolsets.js +3 -1
  674. package/dist/worker/types.d.ts +12 -1
  675. package/dist/workflows/registry.js +1 -0
  676. package/dist/workflows/registry_handle.d.ts +2 -0
  677. package/dist/workflows/runtime.d.ts +2 -0
  678. package/dist/workflows/runtime.js +1 -0
  679. package/dist/workspace_agent.d.ts +7 -0
  680. package/dist/workspace_agent.js +36 -0
  681. package/dist/workspace_collision.d.ts +76 -0
  682. package/dist/workspace_collision.js +688 -0
  683. package/dist/workspace_layout.d.ts +18 -0
  684. package/dist/workspace_layout.js +121 -0
  685. package/dist/workspace_runtime.d.ts +44 -0
  686. package/dist/workspace_runtime.js +82 -0
  687. package/dist/workspace_session_context.d.ts +62 -0
  688. package/dist/workspace_session_context.js +160 -0
  689. package/dist/zlm-embed.mjs +32518 -9615
  690. package/dist/zlm.js +1 -0
  691. package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
  692. package/node_modules/@zerg/model-contracts/README.md +90 -0
  693. package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
  694. package/node_modules/@zerg/model-contracts/index.js +223 -0
  695. package/node_modules/@zerg/model-contracts/package.json +8 -0
  696. package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
  697. package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
  698. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  699. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  700. package/package.json +45 -24
  701. package/packages/model-contracts/LICENSE.md +375 -0
  702. package/packages/model-contracts/README.md +90 -0
  703. package/packages/model-contracts/index.d.ts +20 -0
  704. package/packages/model-contracts/index.js +223 -0
  705. package/packages/model-contracts/package.json +8 -0
  706. package/remote-broker/Dockerfile +10 -0
  707. package/remote-broker/README.md +308 -0
  708. package/remote-broker/archive.py +1227 -0
  709. package/remote-broker/cutover.py +761 -0
  710. package/remote-broker/requirements.txt +5 -0
  711. package/remote-broker/resources.example.json +6 -0
  712. package/remote-broker/resources.write-staging.example.json +18 -0
  713. package/remote-broker/retained.py +686 -0
  714. package/remote-broker/retained_cleanup.py +550 -0
  715. package/remote-broker/server.py +648 -0
  716. package/scripts/build-remote-broker.mjs +13 -0
  717. package/scripts/ensure-local-packages.mjs +6 -0
  718. package/scripts/node-pty-helpers.mjs +103 -0
@@ -1,28 +1,52 @@
1
1
  /**
2
2
  * ToolResponseStore — disk-backed store for tool results.
3
3
  *
4
- * Every tool result is written to
5
- * ~/.ztc/sessions/{projectHash}/{sessionId}/tool_responses/{tool_use_id}.json
4
+ * Non-ephemeral tool results are written to
5
+ * ~/.ztc/sessions/{projectHash}/{sessionId}/tool_responses/{bounded source filename}.json
6
6
  * The conversation thread keeps a small placeholder for large/old results and
7
7
  * inlines the full text only when the request-level inline budget allows it.
8
8
  * Mirror SSE events carry the full content from the agent's `tool_end`
9
9
  * emission, so UX is untouched — the store only governs what appears in the
10
- * model's context.
10
+ * model's context. Explicit host-selected ephemeral tools remain memory-only;
11
+ * their durable evidence contains a placeholder rather than the private body.
11
12
  */
12
- import { mkdir, readFile, readdir } from 'fs/promises';
13
+ import { lstat, mkdir, unlink } from 'fs/promises';
13
14
  import { join } from 'path';
15
+ import { createHash } from 'crypto';
14
16
  import { estimateTokens, getContextWindowSize } from '../utils/context.js';
15
17
  import { writeFileAtomic } from '../utils/atomic_write.js';
16
18
  import { debugLog } from '../debug/logger.js';
19
+ import { environmentEphemeralToolNames, environmentEphemeralToolRules, isEphemeralToolCall, EPHEMERAL_TOOL_PLACEHOLDER, validateEphemeralToolNames, validateEphemeralToolRules } from '../utils/ephemeral_tools.js';
17
20
  import { compressToolResult, hashToolResultBody, isReadToolName, } from './tool_compression.js';
18
21
  import { parseImageToolResult } from './tool_result_parsing.js';
19
22
  import { CORE_DISCOVERY_TOOL_NAMES } from './tools/deferred.js';
23
+ import { readBoundedSource, SourceAdmissionError } from './tool_response_source.js';
24
+ import { collectResponseFilenames, emptyDirectoryAdmission } from './tool_response_directory.js';
25
+ import { EVIDENCE_MAX_SNAPSHOT_BYTES, EvidenceLedger, parseEvidenceSnapshot } from './runtime/evidence.js';
26
+ export const TOOL_RESPONSE_MAX_STORED_RESPONSES = 128;
27
+ export const TOOL_RESPONSE_MAX_STORED_BYTES = 8 * 1024 * 1024;
28
+ export class ToolResponseIntegrityError extends Error {
29
+ constructor(message) {
30
+ super(message);
31
+ this.name = 'ToolResponseIntegrityError';
32
+ }
33
+ }
20
34
  export const INLINE_SIZE_THRESHOLD_TOKENS_DEFAULT = 2000;
21
35
  export const INLINE_AGE_TURNS_DEFAULT = 8;
22
36
  export const CONFIGURED_INLINE_BUDGET_TOKENS_DEFAULT = INLINE_SIZE_THRESHOLD_TOKENS_DEFAULT * 8; // 16k
23
37
  const PER_MODEL_BUDGET_FRACTION = 0.25;
24
38
  const PREVIEW_HEAD_TOKEN_CAP = 150;
25
39
  const PREVIEW_TAIL_TOKEN_CAP = 50;
40
+ const sourceDigest = (content) => createHash('sha256').update(content, 'utf8').digest('hex');
41
+ function validToolId(id) {
42
+ if (!/^[A-Za-z0-9_-]+$/.test(id) || Buffer.byteLength(id) > 256)
43
+ throw new TypeError('Invalid tool_use_id identity');
44
+ }
45
+ // Leave room for the atomic writer's PID/random suffix on 255-byte filesystems.
46
+ // The reserved dot prefix cannot collide with any accepted logical identity.
47
+ function sourceFilename(id) {
48
+ return Buffer.byteLength(id) <= 200 ? `${id}.json` : `.source-${sourceDigest(id)}.json`;
49
+ }
26
50
  /**
27
51
  * Render the canonical offload placeholder string. Centralized so the format
28
52
  * stays in sync with `describeOffloadPlaceholderFormat` below (quoted in the
@@ -137,8 +161,11 @@ function computePreview(content) {
137
161
  };
138
162
  }
139
163
  export class ToolResponseStore {
164
+ evidence = new EvidenceLedger();
140
165
  dir = null;
141
166
  config = {
167
+ ephemeralToolNames: environmentEphemeralToolNames(),
168
+ ephemeralToolRules: environmentEphemeralToolRules(),
142
169
  offloadEnabled: false,
143
170
  inlineSizeThresholdTokens: INLINE_SIZE_THRESHOLD_TOKENS_DEFAULT,
144
171
  inlineAgeTurns: INLINE_AGE_TURNS_DEFAULT,
@@ -152,6 +179,19 @@ export class ToolResponseStore {
152
179
  totalCachedTokens = 0;
153
180
  turnCounter = 0;
154
181
  pendingWrites = new Set();
182
+ persistenceError = null;
183
+ failedSourceWrites = new Map();
184
+ persistenceRecovery = null;
185
+ loadingEvidence = false;
186
+ generation = 0;
187
+ indexDirty = false;
188
+ indexWrite = null;
189
+ availability = new Map();
190
+ invalidSources = new Map();
191
+ retention;
192
+ retainedSources = new Map();
193
+ directoryAdmission = emptyDirectoryAdmission();
194
+ retentionQueue = Promise.resolve();
155
195
  // Ids that the most recent selectInlineSet pass decided to render inline.
156
196
  // Read by willInlineNextRender (used by the recall tool to pick ack vs
157
197
  // content shape) and by the agent dispatch short-circuit.
@@ -162,8 +202,135 @@ export class ToolResponseStore {
162
202
  // later request emits byte-identical text (prompt-cache prefix stability).
163
203
  renderMemo = new Map();
164
204
  compressionStats = { compressedResults: 0, dedupStubs: 0, tokensSaved: 0 };
205
+ constructor(retention = {}) {
206
+ this.retention = { maxStoredResponses: retention.maxStoredResponses ?? TOOL_RESPONSE_MAX_STORED_RESPONSES,
207
+ maxStoredBytes: retention.maxStoredBytes ?? TOOL_RESPONSE_MAX_STORED_BYTES };
208
+ for (const [field, maximum] of [['maxStoredResponses', TOOL_RESPONSE_MAX_STORED_RESPONSES], ['maxStoredBytes', TOOL_RESPONSE_MAX_STORED_BYTES]]) {
209
+ const value = this.retention[field];
210
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
211
+ throw new RangeError(`${field} must be an integer between 1 and ${maximum}`);
212
+ }
213
+ this.evidence.onChange(() => this.scheduleEvidenceWrite());
214
+ }
215
+ retentionVictim(ids) {
216
+ const snapshot = this.evidence.getSnapshot();
217
+ const current = new Set(snapshot.currentIds);
218
+ const referenced = new Set();
219
+ for (const record of snapshot.records) {
220
+ if ((record.kind === 'hypothesis' || record.kind === 'conclusion') && current.has(record.id)) {
221
+ for (const id of record.supportIds)
222
+ referenced.add(id);
223
+ }
224
+ }
225
+ const priority = (id) => referenced.has(this.meta.get(id)?.observation_id ?? id) ? 1 : 0;
226
+ return ids.reduce((oldest, id) => priority(id) < priority(oldest)
227
+ || (priority(id) === priority(oldest) && (this.meta.get(id)?.turn ?? 0) < (this.meta.get(oldest)?.turn ?? 0)) ? id : oldest);
228
+ }
229
+ forgetSource(id) {
230
+ const observationId = this.meta.get(id)?.observation_id ?? id;
231
+ const cached = this.contentCache.get(id);
232
+ if (cached !== undefined)
233
+ this.totalCachedTokens -= Math.max(1, estimateTokens(cached));
234
+ this.contentCache.delete(id);
235
+ this.meta.delete(id);
236
+ this.renderMemo.delete(id);
237
+ this.lastRenderInline.delete(id);
238
+ this.retainedSources.delete(id);
239
+ this.availability.delete(id);
240
+ this.invalidSources.delete(id);
241
+ this.failedSourceWrites.delete(id);
242
+ this.evidence.setAvailability(observationId, 'unavailable');
243
+ }
244
+ overRetentionLimit() {
245
+ return this.retainedSources.size > this.retention.maxStoredResponses
246
+ || [...this.retainedSources.values()].reduce((total, source) => total + source.bytes, 0) > this.retention.maxStoredBytes;
247
+ }
248
+ async enforceRetention(generation = this.generation) {
249
+ const directory = this.dir;
250
+ while (this.overRetentionLimit()) {
251
+ if (generation !== this.generation)
252
+ return;
253
+ const id = this.retentionVictim([...this.retainedSources.keys()]);
254
+ const original = this.retainedSources.get(id);
255
+ if (directory && original.dev !== undefined && original.ino !== undefined) {
256
+ const path = join(directory, original.filename);
257
+ try {
258
+ const info = await lstat(path);
259
+ if (!info.isFile() || info.dev !== original.dev || info.ino !== original.ino)
260
+ throw new ToolResponseIntegrityError(`Refusing retention cleanup of changed source file: ${id}`);
261
+ const source = await this.readSource(path, this.retention.maxStoredBytes);
262
+ if (source.info.dev !== original.dev || source.info.ino !== original.ino)
263
+ throw new ToolResponseIntegrityError(`Refusing retention cleanup of changed source file: ${id}`);
264
+ this.decodeRecord(source.text, id);
265
+ if (generation !== this.generation)
266
+ return;
267
+ await unlink(path);
268
+ }
269
+ catch (error) {
270
+ if (error.code !== 'ENOENT')
271
+ throw new Error(`Tool response retention pruning failed: ${error instanceof Error ? error.message : String(error)}`);
272
+ }
273
+ }
274
+ if (generation !== this.generation)
275
+ return;
276
+ this.forgetSource(id);
277
+ }
278
+ }
279
+ boundMemoryCache() {
280
+ const cacheBytes = () => [...this.contentCache.values()].reduce((total, content) => total + Buffer.byteLength(content), 0);
281
+ while (this.contentCache.size > this.retention.maxStoredResponses || cacheBytes() > this.retention.maxStoredBytes) {
282
+ const id = this.retentionVictim([...this.contentCache.keys()]);
283
+ if (!this.dir)
284
+ this.forgetSource(id);
285
+ else {
286
+ const content = this.contentCache.get(id);
287
+ this.totalCachedTokens -= Math.max(1, estimateTokens(content));
288
+ this.contentCache.delete(id);
289
+ }
290
+ }
291
+ }
292
+ scheduleEvidenceWrite() {
293
+ if (!this.dir || this.loadingEvidence)
294
+ return;
295
+ this.indexDirty = true;
296
+ if (this.indexWrite)
297
+ return;
298
+ const directory = this.dir;
299
+ const generation = this.generation;
300
+ const task = Promise.resolve().then(async () => {
301
+ while (this.indexDirty && generation === this.generation) {
302
+ this.indexDirty = false;
303
+ await writeFileAtomic(join(directory, '.evidence.json'), JSON.stringify(this.evidence.getSnapshot()));
304
+ }
305
+ }).catch((error) => {
306
+ if (generation !== this.generation)
307
+ return;
308
+ this.persistenceError ??= new Error(`Evidence index persistence failed: ${error instanceof Error ? error.message : String(error)}`);
309
+ this.loadingEvidence = true;
310
+ try {
311
+ for (const [id] of this.meta) {
312
+ this.availability.set(id, 'unavailable');
313
+ this.evidence.setAvailability(this.meta.get(id)?.observation_id ?? id, 'unavailable');
314
+ }
315
+ }
316
+ finally {
317
+ this.loadingEvidence = false;
318
+ }
319
+ this.indexDirty = false;
320
+ }).finally(() => {
321
+ this.pendingWrites.delete(task);
322
+ if (this.indexWrite === task) {
323
+ this.indexWrite = null;
324
+ if (this.indexDirty)
325
+ this.scheduleEvidenceWrite();
326
+ }
327
+ });
328
+ this.indexWrite = task;
329
+ this.pendingWrites.add(task);
330
+ }
165
331
  setConfig(cfg) {
166
- this.config = { ...this.config, ...cfg };
332
+ this.config = { ...this.config, ...cfg, ...(cfg.ephemeralToolNames ? { ephemeralToolNames: validateEphemeralToolNames(cfg.ephemeralToolNames) } : {}),
333
+ ...(cfg.ephemeralToolRules ? { ephemeralToolRules: validateEphemeralToolRules(cfg.ephemeralToolRules) } : {}) };
167
334
  }
168
335
  getConfig() {
169
336
  return { ...this.config };
@@ -173,9 +340,11 @@ export class ToolResponseStore {
173
340
  * fraction of the active model's context window so a small-context model
174
341
  * (gpt-4 8k → 2k effective) doesn't get crushed by a Claude-tuned default.
175
342
  */
176
- effectiveBudget(model) {
343
+ effectiveBudget(model, windowOverride) {
177
344
  const configured = this.config.configuredInlineBudgetTokens;
178
- const window = getContextWindowSize(model);
345
+ const window = getContextWindowSize(model, windowOverride);
346
+ if (window === 0)
347
+ return configured;
179
348
  return Math.min(configured, Math.floor(window * PER_MODEL_BUDGET_FRACTION));
180
349
  }
181
350
  cacheBudgetTokens() {
@@ -189,43 +358,134 @@ export class ToolResponseStore {
189
358
  */
190
359
  async setSessionDir(dir) {
191
360
  await this.flush();
361
+ this.clear();
192
362
  this.dir = dir;
193
- this.meta.clear();
194
- this.contentCache.clear();
195
- this.totalCachedTokens = 0;
196
- this.turnCounter = 0;
197
- this.lastRenderInline.clear();
198
- this.renderMemo.clear();
199
- this.resetCompressionStats();
200
- await mkdir(dir, { recursive: true });
201
- let files;
363
+ this.loadingEvidence = true;
364
+ let shouldPersist = false;
202
365
  try {
203
- files = await readdir(dir);
204
- }
205
- catch {
206
- return;
207
- }
208
- const jsonFiles = files.filter(f => f.endsWith('.json'));
209
- const records = await Promise.all(jsonFiles.map(async (f) => {
366
+ await mkdir(dir, { recursive: true });
367
+ let hadIndex = false;
210
368
  try {
211
- const raw = await readFile(join(dir, f), 'utf-8');
212
- const rec = JSON.parse(raw);
213
- return rec?.meta?.tool_use_id ? rec.meta : null;
369
+ const rawIndex = await this.readSource(join(dir, '.evidence.json'), EVIDENCE_MAX_SNAPSHOT_BYTES);
370
+ const index = parseEvidenceSnapshot(JSON.parse(rawIndex.text));
371
+ if (!index)
372
+ throw new ToolResponseIntegrityError('Invalid persisted evidence index');
373
+ this.evidence.restore(index);
374
+ hadIndex = true;
214
375
  }
215
- catch {
216
- return null;
376
+ catch (error) {
377
+ if (error.code !== 'ENOENT')
378
+ throw error;
217
379
  }
218
- }));
219
- for (const m of records) {
220
- if (!m)
221
- continue;
222
- this.meta.set(m.tool_use_id, m);
223
- if (m.turn >= this.turnCounter)
224
- this.turnCounter = m.turn + 1;
380
+ const before = JSON.stringify(this.evidence.getSnapshot());
381
+ const indexedIds = new Set();
382
+ const indexedNames = new Set();
383
+ for (const record of this.evidence.getSnapshot().records) {
384
+ if (record.kind !== 'observation' || !record.source.toolCallId)
385
+ continue;
386
+ const id = record.source.toolCallId;
387
+ try {
388
+ validToolId(id);
389
+ }
390
+ catch {
391
+ this.evidence.setAvailability(record.id, 'unavailable');
392
+ continue;
393
+ }
394
+ indexedIds.add(id);
395
+ indexedNames.add(sourceFilename(id));
396
+ if (Buffer.byteLength(id) > 200 && Buffer.byteLength(id) <= 250)
397
+ indexedNames.add(`${id}.json`);
398
+ }
399
+ let files;
400
+ try {
401
+ const collected = await collectResponseFilenames(dir, [...indexedNames]);
402
+ files = collected.files;
403
+ this.directoryAdmission = collected.admission;
404
+ }
405
+ catch (error) {
406
+ this.directoryAdmission = { ...emptyDirectoryAdmission(), status: 'failed', reasons: ['directory_error'],
407
+ errorCode: String(error.code ?? 'UNKNOWN').slice(0, 64) };
408
+ throw error;
409
+ }
410
+ // Sequential reads avoid loading every historical source body concurrently.
411
+ for (const file of files) {
412
+ let id = file.slice(0, -5);
413
+ try {
414
+ const info = await lstat(join(dir, file));
415
+ if (!info.isFile())
416
+ continue;
417
+ const source = await this.readSource(join(dir, file), this.retention.maxStoredBytes);
418
+ if (file.startsWith('.source-')) {
419
+ const indexed = this.evidence.getSnapshot().records.find(record => record.kind === 'observation'
420
+ && record.source.toolCallId && sourceFilename(record.source.toolCallId) === file);
421
+ const candidate = JSON.parse(source.text);
422
+ const identity = indexed?.kind === 'observation' ? indexed.source.toolCallId : candidate?.meta?.tool_use_id;
423
+ if (typeof identity !== 'string')
424
+ throw new ToolResponseIntegrityError(`Invalid source filename identity: ${file}`);
425
+ id = identity;
426
+ }
427
+ validToolId(id);
428
+ if (file !== sourceFilename(id) && file !== `${id}.json`)
429
+ throw new ToolResponseIntegrityError(`Invalid source filename identity: ${id}`);
430
+ if (this.invalidSources.has(id))
431
+ throw this.invalidSources.get(id);
432
+ if (this.retainedSources.has(id))
433
+ throw new ToolResponseIntegrityError(`Ambiguous duplicate source filenames: ${id}`);
434
+ const record = this.decodeRecord(source.text, id);
435
+ if (record.meta.ephemeral || this.config.ephemeralToolNames?.includes(record.meta.tool_name))
436
+ continue;
437
+ // Discovery cannot displace the current index with unindexed historical sources.
438
+ if (hadIndex && !indexedIds.has(id) && (this.retainedSources.size >= this.retention.maxStoredResponses
439
+ || [...this.retainedSources.values()].reduce((total, entry) => total + entry.bytes, 0) + source.info.size > this.retention.maxStoredBytes)) {
440
+ this.directoryAdmission.skippedSources++;
441
+ if (!this.directoryAdmission.reasons.includes('source_retention_limit'))
442
+ this.directoryAdmission.reasons.push('source_retention_limit');
443
+ this.directoryAdmission.status = 'limited';
444
+ this.directoryAdmission.complete = false;
445
+ continue;
446
+ }
447
+ this.admitLoadedRecord(record, !hadIndex || (record.schemaVersion === undefined && this.evidence.getSnapshot().omittedRecords === 0));
448
+ this.retainedSources.set(id, { bytes: source.info.size, filename: file, dev: source.info.dev, ino: source.info.ino });
449
+ }
450
+ catch (error) {
451
+ if (error.code === 'ENOENT')
452
+ continue;
453
+ this.meta.delete(id);
454
+ this.retainedSources.delete(id);
455
+ this.markInvalid(id, error);
456
+ continue;
457
+ }
458
+ // A valid owned source may be pruned; an I/O refusal is a failed bind,
459
+ // not a malformed unrelated file that can be ignored.
460
+ await this.enforceRetention();
461
+ }
462
+ this.directoryAdmission.admittedSources = this.retainedSources.size;
463
+ for (const record of this.evidence.getSnapshot().records) {
464
+ if (record.kind === 'observation' && record.source.toolCallId && !this.meta.has(record.source.toolCallId)) {
465
+ this.evidence.setAvailability(record.id, 'unavailable');
466
+ }
467
+ }
468
+ shouldPersist = before !== JSON.stringify(this.evidence.getSnapshot());
469
+ }
470
+ finally {
471
+ this.loadingEvidence = false;
225
472
  }
473
+ if (shouldPersist)
474
+ this.scheduleEvidenceWrite();
226
475
  }
227
476
  clear() {
477
+ this.generation++;
228
478
  this.dir = null;
479
+ this.indexDirty = false;
480
+ this.indexWrite = null;
481
+ this.persistenceError = null;
482
+ this.failedSourceWrites.clear();
483
+ this.persistenceRecovery = null;
484
+ this.availability.clear();
485
+ this.invalidSources.clear();
486
+ this.retainedSources.clear();
487
+ this.directoryAdmission = emptyDirectoryAdmission();
488
+ this.retentionQueue = Promise.resolve();
229
489
  this.meta.clear();
230
490
  this.contentCache.clear();
231
491
  this.totalCachedTokens = 0;
@@ -233,10 +493,15 @@ export class ToolResponseStore {
233
493
  this.lastRenderInline.clear();
234
494
  this.renderMemo.clear();
235
495
  this.resetCompressionStats();
496
+ this.evidence.clear();
236
497
  }
237
498
  hasSession() {
238
499
  return this.dir !== null;
239
500
  }
501
+ /** A bounded discovery result never claims that unseen historical files are absent. */
502
+ getDirectoryAdmission() {
503
+ return { ...this.directoryAdmission, reasons: [...this.directoryAdmission.reasons] };
504
+ }
240
505
  /** True when the content for `toolUseId` is currently in the in-memory cache. */
241
506
  hasCached(toolUseId) {
242
507
  return this.contentCache.has(toolUseId);
@@ -247,7 +512,113 @@ export class ToolResponseStore {
247
512
  * also used by tests that need to observe a settled filesystem.
248
513
  */
249
514
  async flush() {
250
- await Promise.all(this.pendingWrites);
515
+ await this.awaitWrites();
516
+ if (this.persistenceError && !this.persistenceRecovery)
517
+ this.persistenceRecovery = this.recoverPersistence();
518
+ const recovery = this.persistenceRecovery;
519
+ if (recovery) {
520
+ try {
521
+ await recovery;
522
+ }
523
+ finally {
524
+ if (this.persistenceRecovery === recovery)
525
+ this.persistenceRecovery = null;
526
+ }
527
+ }
528
+ await this.awaitWrites();
529
+ if (this.persistenceError)
530
+ throw this.persistenceError;
531
+ }
532
+ async awaitWrites() {
533
+ while (this.pendingWrites.size)
534
+ await Promise.all([...this.pendingWrites]);
535
+ }
536
+ rememberFailedSource(id, serialized) {
537
+ if (!this.meta.has(id))
538
+ return; // Already retired under the raw-source quota.
539
+ this.failedSourceWrites.set(id, serialized);
540
+ const bytes = () => [...this.failedSourceWrites.values()].reduce((total, value) => total + Buffer.byteLength(value), 0);
541
+ while (this.failedSourceWrites.size > this.retention.maxStoredResponses || bytes() > this.retention.maxStoredBytes) {
542
+ this.forgetSource(this.retentionVictim([...this.failedSourceWrites.keys()]));
543
+ }
544
+ }
545
+ /** Retry stored bytes and the evidence index, never the producing tool. */
546
+ async recoverPersistence() {
547
+ const directory = this.dir;
548
+ const generation = this.generation;
549
+ if (!directory)
550
+ return;
551
+ this.persistenceError = null;
552
+ for (const [id, serialized] of [...this.failedSourceWrites])
553
+ this.scheduleSourceWrite(id, serialized);
554
+ await this.awaitWrites();
555
+ if (generation !== this.generation || this.persistenceError)
556
+ return;
557
+ // Index failure can mark otherwise durable sources unavailable. Re-admit
558
+ // only their actual unchanged bytes before republishing availability.
559
+ this.loadingEvidence = true;
560
+ try {
561
+ for (const [id, retained] of this.retainedSources) {
562
+ if (this.availability.get(id) === 'persisted')
563
+ continue;
564
+ const source = await this.readSource(join(directory, retained.filename), this.retention.maxStoredBytes);
565
+ if (generation !== this.generation)
566
+ return;
567
+ const record = this.decodeRecord(source.text, id);
568
+ if (source.info.dev !== retained.dev || source.info.ino !== retained.ino
569
+ || JSON.stringify(record.meta) !== JSON.stringify(this.meta.get(id))) {
570
+ throw new ToolResponseIntegrityError(`Refusing recovery of changed tool source: ${id}`);
571
+ }
572
+ this.availability.set(id, 'persisted');
573
+ this.evidence.setAvailability(record.meta.observation_id, 'persisted');
574
+ }
575
+ }
576
+ catch (error) {
577
+ if (generation === this.generation)
578
+ this.persistenceError ??= new Error(`Tool response recovery persistence failed: ${String(error)}`);
579
+ }
580
+ finally {
581
+ if (generation === this.generation)
582
+ this.loadingEvidence = false;
583
+ }
584
+ if (generation === this.generation)
585
+ this.scheduleEvidenceWrite();
586
+ }
587
+ scheduleSourceWrite(toolUseId, serialized) {
588
+ const directory = this.dir;
589
+ const generation = this.generation;
590
+ const meta = this.meta.get(toolUseId);
591
+ const filename = sourceFilename(toolUseId);
592
+ const path = join(directory, filename);
593
+ const write = writeFileAtomic(path, serialized)
594
+ .then(async () => {
595
+ if (generation !== this.generation)
596
+ return;
597
+ const info = await lstat(path);
598
+ if (generation !== this.generation)
599
+ return;
600
+ if (!info.isFile())
601
+ throw new ToolResponseIntegrityError(`Published source is not a regular file: ${toolUseId}`);
602
+ this.retainedSources.set(toolUseId, { bytes: info.size, filename, dev: info.dev, ino: info.ino });
603
+ this.availability.set(toolUseId, 'persisted');
604
+ this.evidence.setAvailability(meta.observation_id, 'persisted');
605
+ this.retentionQueue = this.retentionQueue.catch(() => undefined).then(() => this.enforceRetention(generation));
606
+ await this.retentionQueue;
607
+ if (generation === this.generation)
608
+ this.failedSourceWrites.delete(toolUseId);
609
+ })
610
+ .catch((error) => {
611
+ if (generation !== this.generation)
612
+ return;
613
+ const message = error instanceof Error ? error.message : String(error);
614
+ this.persistenceError ??= new Error(`Tool response persistence failed for ${toolUseId}: ${message}`);
615
+ this.rememberFailedSource(toolUseId, serialized);
616
+ this.availability.set(toolUseId, 'unavailable');
617
+ this.evidence.setAvailability(meta.observation_id, 'unavailable');
618
+ debugLog(`[ToolResponseStore] atomic write failed for ${toolUseId}: ${message}`);
619
+ });
620
+ const task = write.finally(() => this.pendingWrites.delete(task));
621
+ this.pendingWrites.add(task);
251
622
  }
252
623
  /**
253
624
  * Persist a tool result and return the text to inject into context (full
@@ -255,72 +626,218 @@ export class ToolResponseStore {
255
626
  * fire-and-forget.
256
627
  */
257
628
  put(toolUseId, toolName, content, partial = false, context) {
629
+ validToolId(toolUseId);
630
+ if (this.invalidSources.has(toolUseId))
631
+ throw this.invalidSources.get(toolUseId);
632
+ const sha256 = sourceDigest(content);
633
+ const old = this.meta.get(toolUseId);
634
+ if (old) {
635
+ if (old.result_sha256 !== sha256 || old.tool_name !== toolName || Boolean(old.partial) !== partial
636
+ || Boolean(old.is_error) !== Boolean(context?.isError)
637
+ || (context?.observationId !== undefined && context.observationId !== old.observation_id)) {
638
+ throw new Error(`Immutable tool result identity conflict: ${toolUseId}`);
639
+ }
640
+ return { meta: structuredClone(old), rendered: this.renderForContext(toolUseId) ?? content };
641
+ }
258
642
  const sizeTokens = Math.max(1, estimateTokens(content));
259
643
  const withinSize = sizeTokens <= this.config.inlineSizeThresholdTokens;
260
644
  const isImage = parseImageToolResult(content) !== null;
261
645
  const isDiscovery = CORE_DISCOVERY_TOOL_NAMES.has(toolName);
646
+ const ephemeral = isEphemeralToolCall(toolName, context?.args, this.config.ephemeralToolNames, this.config.ephemeralToolRules);
262
647
  // Only generate a preview when the result will actually be offloaded.
263
648
  // Inlined small/image/discovery results show full content, no placeholder.
264
- const preview = withinSize || isImage || isDiscovery ? null : computePreview(content);
649
+ const preview = ephemeral || withinSize || isImage || isDiscovery ? null : computePreview(content);
265
650
  const meta = {
651
+ ...(ephemeral ? { ephemeral: true } : {}),
266
652
  tool_name: toolName,
267
653
  tool_use_id: toolUseId,
268
654
  size_tokens: sizeTokens,
269
655
  timestamp: new Date().toISOString(),
270
656
  turn: this.turnCounter++,
271
657
  partial,
658
+ result_sha256: sha256,
659
+ observation_id: context?.observationId ?? toolUseId,
272
660
  ...(context?.isError === undefined ? {} : { is_error: context.isError }),
273
661
  preview,
274
662
  };
275
- const sourcePath = context?.sourcePath ?? null;
663
+ const sourcePath = ephemeral ? null : context?.sourcePath ?? null;
276
664
  if (sourcePath)
277
665
  meta.source_path = sourcePath;
278
666
  if (sourcePath && context?.dedupable && isReadToolName(toolName)) {
279
667
  meta.content_hash = hashToolResultBody(content);
280
668
  }
669
+ this.evidence.observe({ id: meta.observation_id, recordedAt: meta.timestamp,
670
+ source: { origin: 'host_tool', ...(sourcePath ? { path: sourcePath } : {}), ...(ephemeral ? {} : context?.source), toolCallId: toolUseId, toolName },
671
+ excerpt: ephemeral ? EPHEMERAL_TOOL_PLACEHOLDER : content, resultSha256: sha256, resultBytes: Buffer.byteLength(content), partial,
672
+ isError: Boolean(context?.isError), availability: 'memory',
673
+ ...(ephemeral || context?.source?.job?.cursor === undefined ? {} : { cursor: context.source.job.cursor }) });
674
+ const observed = this.evidence.getSnapshot().records.find(record => record.id === meta.observation_id);
675
+ const observation = observed?.kind === 'observation' ? { ...observed, availability: 'persisted' } : undefined;
676
+ const serialized = JSON.stringify({ schemaVersion: 1, meta, content, observation });
677
+ const serializedBytes = Buffer.byteLength(serialized);
678
+ if (serializedBytes > this.retention.maxStoredBytes) {
679
+ this.evidence.setAvailability(meta.observation_id, 'unavailable');
680
+ // Preserve the caller's last usable full copy instead of offloading to nonexistent bytes.
681
+ return { meta: structuredClone(meta), rendered: content };
682
+ }
281
683
  this.meta.set(toolUseId, meta);
684
+ this.availability.set(toolUseId, 'memory');
282
685
  this.renderMemo.delete(toolUseId);
283
686
  this.cacheContent(toolUseId, content);
284
- if (this.dir) {
285
- const path = join(this.dir, `${toolUseId}.json`);
286
- // Fire-and-forget — put() stays off the await path. Write failures must
287
- // still surface: a silently-dropped write becomes a permanent null from
288
- // recall_tool_response once the LRU evicts the in-memory copy.
289
- const write = writeFileAtomic(path, JSON.stringify({ meta, content }))
290
- .catch((err) => {
291
- const message = err instanceof Error ? err.message : String(err);
292
- debugLog(`[ToolResponseStore] atomic write failed for ${toolUseId}: ${message}`);
293
- });
294
- const task = write.finally(() => this.pendingWrites.delete(task));
295
- this.pendingWrites.add(task);
296
- }
297
- const shouldOffload = this.config.offloadEnabled && !withinSize && !isImage && !isDiscovery;
687
+ if (this.dir && !ephemeral) {
688
+ this.scheduleSourceWrite(toolUseId, serialized);
689
+ }
690
+ else {
691
+ this.retainedSources.set(toolUseId, { bytes: serializedBytes });
692
+ while (this.overRetentionLimit())
693
+ this.forgetSource(this.retentionVictim([...this.retainedSources.keys()]));
694
+ }
695
+ const shouldOffload = !ephemeral && this.config.offloadEnabled && !withinSize && !isImage && !isDiscovery;
298
696
  const rendered = shouldOffload ? formatOffloadPlaceholder(meta) : content;
299
- return { meta, rendered };
697
+ return { meta: structuredClone(meta), rendered };
300
698
  }
301
699
  /** Load the full content for a tool_use_id. Returns null if missing. */
302
700
  async get(toolUseId) {
701
+ validToolId(toolUseId);
702
+ if (this.invalidSources.has(toolUseId))
703
+ throw this.invalidSources.get(toolUseId);
303
704
  const cached = this.contentCache.get(toolUseId);
304
705
  if (cached !== undefined)
305
706
  return cached;
707
+ if (this.meta.get(toolUseId)?.ephemeral || this.config.ephemeralToolNames?.includes(this.meta.get(toolUseId)?.tool_name ?? ''))
708
+ return null;
306
709
  if (!this.dir)
307
710
  return null;
308
711
  try {
309
- const raw = await readFile(join(this.dir, `${toolUseId}.json`), 'utf-8');
310
- const rec = JSON.parse(raw);
311
- if (rec && typeof rec.content === 'string') {
312
- if (rec.meta && !this.meta.has(toolUseId)) {
313
- this.meta.set(toolUseId, rec.meta);
314
- }
315
- this.cacheContent(toolUseId, rec.content);
316
- return rec.content;
712
+ let filename = this.retainedSources.get(toolUseId)?.filename ?? sourceFilename(toolUseId);
713
+ let source;
714
+ try {
715
+ source = await this.readSource(join(this.dir, filename), this.retention.maxStoredBytes);
716
+ }
717
+ catch (error) {
718
+ // Longer legacy records can be read in place; never issue an overlong lookup.
719
+ if (error.code !== 'ENOENT' || filename === `${toolUseId}.json`
720
+ || Buffer.byteLength(toolUseId) > 250)
721
+ throw error;
722
+ filename = `${toolUseId}.json`;
723
+ source = await this.readSource(join(this.dir, filename), this.retention.maxStoredBytes);
317
724
  }
725
+ const rec = this.decodeRecord(source.text, toolUseId);
726
+ if (rec.meta.ephemeral || this.config.ephemeralToolNames?.includes(rec.meta.tool_name))
727
+ return null;
728
+ this.admitLoadedRecord(rec, false);
729
+ this.retainedSources.set(toolUseId, { bytes: source.info.size, filename, dev: source.info.dev, ino: source.info.ino });
730
+ await this.enforceRetention();
731
+ if (!this.meta.has(toolUseId))
732
+ return null;
733
+ this.cacheContent(toolUseId, rec.content);
734
+ return rec.content;
318
735
  }
319
- catch {
320
- // ENOENT or corrupt file — treat as missing.
736
+ catch (error) {
737
+ if (error.code !== 'ENOENT') {
738
+ this.markInvalid(toolUseId, error);
739
+ throw this.invalidSources.get(toolUseId);
740
+ }
741
+ if (this.meta.has(toolUseId))
742
+ this.availability.set(toolUseId, 'unavailable');
743
+ this.evidence.setAvailability(this.meta.get(toolUseId)?.observation_id ?? toolUseId, 'unavailable');
321
744
  }
322
745
  return null;
323
746
  }
747
+ async getChunk(toolUseId, options = {}) {
748
+ const cursor = options.cursor ?? 0;
749
+ const maxBytes = options.maxBytes ?? 8192;
750
+ if (!Number.isSafeInteger(cursor) || cursor < 0)
751
+ throw new RangeError('cursor must be a nonnegative safe byte offset');
752
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > 32768)
753
+ throw new RangeError('maxBytes must be an integer between 1 and 32768');
754
+ const content = await this.get(toolUseId);
755
+ if (content === null)
756
+ return null;
757
+ const body = Buffer.from(content, 'utf8');
758
+ if (cursor > body.length || (cursor < body.length && (body[cursor] & 0xc0) === 0x80))
759
+ throw new RangeError('cursor must identify a UTF-8 boundary within the source');
760
+ let end = Math.min(body.length, cursor + maxBytes);
761
+ while (end > cursor && end < body.length && (body[end] & 0xc0) === 0x80)
762
+ end--;
763
+ if (end === cursor && cursor < body.length)
764
+ throw new RangeError('maxBytes cannot fit the next UTF-8 character');
765
+ const meta = this.meta.get(toolUseId);
766
+ return { toolUseId, observationId: meta.observation_id ?? toolUseId, content: body.subarray(cursor, end).toString('utf8'),
767
+ cursor, nextCursor: end, totalBytes: body.length, sha256: meta.result_sha256 ?? sourceDigest(content),
768
+ complete: end === body.length, availability: this.availability.get(toolUseId) ?? 'memory' };
769
+ }
770
+ markInvalid(id, error) {
771
+ const failure = error instanceof ToolResponseIntegrityError ? error
772
+ : new ToolResponseIntegrityError(`Source integrity failed for ${id}: ${error instanceof Error ? error.message : String(error)}`);
773
+ this.invalidSources.set(id, failure);
774
+ this.availability.set(id, 'unavailable');
775
+ while (this.invalidSources.size > this.retention.maxStoredResponses) {
776
+ const oldest = this.invalidSources.keys().next().value;
777
+ this.invalidSources.delete(oldest);
778
+ if (!this.meta.has(oldest))
779
+ this.availability.delete(oldest);
780
+ }
781
+ const source = this.evidence.getSnapshot().records.find(record => record.kind === 'observation' && record.source.toolCallId === id);
782
+ this.evidence.setAvailability(source?.id ?? this.meta.get(id)?.observation_id ?? id, 'unavailable');
783
+ }
784
+ async readSource(path, maximumBytes) {
785
+ try {
786
+ return await readBoundedSource(path, maximumBytes);
787
+ }
788
+ catch (error) {
789
+ if (error instanceof SourceAdmissionError)
790
+ throw new ToolResponseIntegrityError(error.message);
791
+ throw error;
792
+ }
793
+ }
794
+ decodeRecord(raw, id) {
795
+ const record = JSON.parse(raw);
796
+ if (!record || typeof record.content !== 'string' || !record.meta || record.meta.tool_use_id !== id
797
+ || typeof record.meta.tool_name !== 'string' || !record.meta.tool_name
798
+ || !Number.isSafeInteger(record.meta.turn) || record.meta.turn < 0
799
+ || !Number.isFinite(Date.parse(record.meta.timestamp))
800
+ || (record.schemaVersion !== undefined && record.schemaVersion !== 1)) {
801
+ throw new ToolResponseIntegrityError(`Invalid source record identity: ${id}`);
802
+ }
803
+ const sha256 = sourceDigest(record.content);
804
+ const expected = this.evidence.getSnapshot().records.find(entry => entry.kind === 'observation'
805
+ && (entry.id === (record.meta.observation_id ?? id) || entry.source.toolCallId === id));
806
+ if ((record.schemaVersion === 1 && (!record.meta.result_sha256 || !record.meta.observation_id))
807
+ || (record.meta.result_sha256 !== undefined && record.meta.result_sha256 !== sha256)
808
+ || (expected?.kind === 'observation' && expected.resultSha256 !== sha256)) {
809
+ throw new ToolResponseIntegrityError(`Source SHA256 integrity mismatch: ${id}`);
810
+ }
811
+ if (record.observation) {
812
+ const parsed = parseEvidenceSnapshot({ schemaVersion: 1, revision: 0, records: [record.observation], currentIds: [record.observation.id], omittedRecords: 0, omittedTextBytes: 0 });
813
+ if (!parsed || record.observation.kind !== 'observation' || record.observation.resultSha256 !== sha256
814
+ || record.observation.resultBytes !== Buffer.byteLength(record.content)
815
+ || record.observation.id !== record.meta.observation_id || record.observation.source.toolCallId !== id) {
816
+ throw new ToolResponseIntegrityError(`Invalid source observation metadata: ${id}`);
817
+ }
818
+ }
819
+ record.meta.result_sha256 = sha256;
820
+ record.meta.observation_id ??= id;
821
+ return record;
822
+ }
823
+ admitLoadedRecord(record, recreate) {
824
+ const id = record.meta.tool_use_id;
825
+ this.meta.set(id, record.meta);
826
+ this.availability.set(id, 'persisted');
827
+ this.turnCounter = Math.max(this.turnCounter, record.meta.turn + 1);
828
+ const existing = this.evidence.getSnapshot().records.find(entry => entry.id === record.meta.observation_id);
829
+ if (existing)
830
+ this.evidence.setAvailability(existing.id, 'persisted');
831
+ else if (recreate) {
832
+ const observation = record.observation ?? {
833
+ id: record.meta.observation_id, kind: 'observation', recordedAt: record.meta.timestamp,
834
+ source: { origin: 'legacy', toolCallId: id, toolName: record.meta.tool_name, ...(record.meta.source_path ? { path: record.meta.source_path } : {}) },
835
+ excerpt: record.content, resultSha256: record.meta.result_sha256, resultBytes: Buffer.byteLength(record.content),
836
+ partial: Boolean(record.meta.partial), isError: Boolean(record.meta.is_error), availability: 'persisted', verification: 'unknown',
837
+ };
838
+ this.evidence.observe({ ...observation, availability: 'persisted' });
839
+ }
840
+ }
324
841
  cacheContent(toolUseId, content) {
325
842
  const newSize = this.meta.get(toolUseId)?.size_tokens ?? Math.max(1, estimateTokens(content));
326
843
  const existing = this.contentCache.get(toolUseId);
@@ -332,6 +849,7 @@ export class ToolResponseStore {
332
849
  }
333
850
  this.contentCache.set(toolUseId, content);
334
851
  this.totalCachedTokens += newSize;
852
+ this.boundMemoryCache();
335
853
  const budget = this.cacheBudgetTokens();
336
854
  while (this.totalCachedTokens > budget && this.contentCache.size > 1) {
337
855
  // Prefer evicting non-pinned (non-recalled) entries first, oldest-LRU
@@ -360,7 +878,8 @@ export class ToolResponseStore {
360
878
  }
361
879
  }
362
880
  getMeta(toolUseId) {
363
- return this.meta.get(toolUseId);
881
+ const meta = this.meta.get(toolUseId);
882
+ return meta ? structuredClone(meta) : undefined;
364
883
  }
365
884
  /**
366
885
  * Mark a tool result as freshly recalled. Stores `recalledAtTurn` so the