@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.
- package/README.md +398 -45
- package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
- package/THIRD_PARTY_NOTICES.md +273 -12
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/bin/ztc-capture-darwin-arm64 +0 -0
- package/bin/ztc-computer-darwin-arm64 +0 -0
- package/bin/ztc-llm-darwin-arm64 +0 -0
- package/bin/ztc-tts-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +1206 -209
- package/dist/agent/agent.d.ts +74 -8
- package/dist/agent/agent.js +1073 -184
- package/dist/agent/backends/account_rotation.d.ts +4 -0
- package/dist/agent/backends/account_rotation.js +8 -2
- package/dist/agent/backends/anthropic.d.ts +13 -0
- package/dist/agent/backends/anthropic.js +169 -28
- package/dist/agent/backends/claude_cli.js +18 -8
- package/dist/agent/backends/codex_app_server.d.ts +1 -0
- package/dist/agent/backends/codex_app_server.js +55 -3
- package/dist/agent/backends/codex_auth.d.ts +1 -0
- package/dist/agent/backends/codex_auth.js +67 -13
- package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
- package/dist/agent/backends/codex_buffered_recovery.js +293 -0
- package/dist/agent/backends/gemini.js +3 -1
- package/dist/agent/backends/http_error.d.ts +26 -0
- package/dist/agent/backends/http_error.js +221 -0
- package/dist/agent/backends/index.d.ts +9 -2
- package/dist/agent/backends/index.js +22 -2
- package/dist/agent/backends/openai.d.ts +0 -6
- package/dist/agent/backends/openai.js +5 -2
- package/dist/agent/backends/openai_compatible.d.ts +5 -4
- package/dist/agent/backends/openai_compatible.js +26 -19
- package/dist/agent/backends/openai_responses.d.ts +8 -3
- package/dist/agent/backends/openai_responses.js +77 -33
- package/dist/agent/backends/provider_ids.d.ts +1 -0
- package/dist/agent/backends/provider_ids.js +4 -0
- package/dist/agent/backends/request_metrics.js +12 -2
- package/dist/agent/backends/types.d.ts +109 -6
- package/dist/agent/backends/types.js +5 -1
- package/dist/agent/backends/zrouter_stream.d.ts +6 -1
- package/dist/agent/backends/zrouter_stream.js +160 -42
- package/dist/agent/background_registry.d.ts +2 -0
- package/dist/agent/background_registry.js +1 -0
- package/dist/agent/claim_observation.d.ts +6 -0
- package/dist/agent/claim_observation.js +54 -0
- package/dist/agent/commands/accounts.js +46 -177
- package/dist/agent/commands/attention.d.ts +2 -0
- package/dist/agent/commands/attention.js +67 -0
- package/dist/agent/commands/auth.js +18 -2
- package/dist/agent/commands/board.d.ts +2 -0
- package/dist/agent/commands/board.js +169 -0
- package/dist/agent/commands/clipboard.js +50 -1
- package/dist/agent/commands/compact.js +21 -13
- package/dist/agent/commands/config.js +4 -1
- package/dist/agent/commands/context.js +6 -1
- package/dist/agent/commands/desktop.d.ts +2 -0
- package/dist/agent/commands/desktop.js +29 -0
- package/dist/agent/commands/effort.js +14 -6
- package/dist/agent/commands/environment.d.ts +3 -0
- package/dist/agent/commands/environment.js +109 -0
- package/dist/agent/commands/evidence.d.ts +2 -0
- package/dist/agent/commands/evidence.js +9 -0
- package/dist/agent/commands/fork.js +4 -3
- package/dist/agent/commands/goal.d.ts +33 -1
- package/dist/agent/commands/goal.js +174 -14
- package/dist/agent/commands/goal_exit.d.ts +36 -0
- package/dist/agent/commands/goal_exit.js +120 -0
- package/dist/agent/commands/help.js +3 -1
- package/dist/agent/commands/history.js +3 -1
- package/dist/agent/commands/inbox.d.ts +2 -0
- package/dist/agent/commands/inbox.js +148 -0
- package/dist/agent/commands/index.js +16 -1
- package/dist/agent/commands/input_mode.js +8 -6
- package/dist/agent/commands/job.d.ts +2 -0
- package/dist/agent/commands/job.js +93 -0
- package/dist/agent/commands/keybindings.js +4 -1
- package/dist/agent/commands/label.d.ts +2 -0
- package/dist/agent/commands/label.js +77 -0
- package/dist/agent/commands/layout.d.ts +2 -0
- package/dist/agent/commands/layout.js +20 -0
- package/dist/agent/commands/mirror.js +16 -1
- package/dist/agent/commands/model.js +12 -2
- package/dist/agent/commands/permissions.d.ts +3 -1
- package/dist/agent/commands/permissions.js +79 -89
- package/dist/agent/commands/plan.js +5 -15
- package/dist/agent/commands/profile.js +21 -2
- package/dist/agent/commands/provider_selection.js +20 -1
- package/dist/agent/commands/rename.js +1 -1
- package/dist/agent/commands/resources.d.ts +2 -0
- package/dist/agent/commands/resources.js +67 -0
- package/dist/agent/commands/rewind.d.ts +3 -0
- package/dist/agent/commands/rewind.js +16 -0
- package/dist/agent/commands/runbooks.d.ts +2 -0
- package/dist/agent/commands/runbooks.js +73 -0
- package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
- package/dist/agent/commands/runbooks_lazy.js +8 -0
- package/dist/agent/commands/sessions.d.ts +1 -1
- package/dist/agent/commands/sessions.js +33 -5
- package/dist/agent/commands/settings.js +185 -39
- package/dist/agent/commands/setup.d.ts +2 -0
- package/dist/agent/commands/setup.js +78 -0
- package/dist/agent/commands/share.d.ts +2 -0
- package/dist/agent/commands/share.js +64 -0
- package/dist/agent/commands/status.js +14 -2
- package/dist/agent/commands/types.d.ts +99 -6
- package/dist/agent/commands/update.js +8 -0
- package/dist/agent/commands/usage.d.ts +2 -0
- package/dist/agent/commands/usage.js +8 -0
- package/dist/agent/factory.d.ts +11 -0
- package/dist/agent/factory.js +113 -88
- package/dist/agent/loop_policy.d.ts +6 -3
- package/dist/agent/loop_policy.js +12 -3
- package/dist/agent/measurement_guidance.d.ts +2 -0
- package/dist/agent/measurement_guidance.js +5 -0
- package/dist/agent/request_encoder.d.ts +8 -1
- package/dist/agent/request_encoder.js +95 -23
- package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
- package/dist/agent/runtime/benchmark_comparison.js +94 -0
- package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
- package/dist/agent/runtime/benchmark_contract.js +90 -0
- package/dist/agent/runtime/checked_commands.d.ts +32 -0
- package/dist/agent/runtime/checked_commands.js +119 -0
- package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
- package/dist/agent/runtime/command_execution_policy.js +91 -0
- package/dist/agent/runtime/command_jobs.d.ts +36 -18
- package/dist/agent/runtime/command_jobs.js +151 -13
- package/dist/agent/runtime/command_panes.d.ts +59 -0
- package/dist/agent/runtime/command_panes.js +398 -0
- package/dist/agent/runtime/command_wait.d.ts +19 -0
- package/dist/agent/runtime/command_wait.js +167 -0
- package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
- package/dist/agent/runtime/cpu_benchmark.js +126 -0
- package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
- package/dist/agent/runtime/delegated_evidence.js +80 -0
- package/dist/agent/runtime/evidence.d.ts +127 -0
- package/dist/agent/runtime/evidence.js +468 -0
- package/dist/agent/runtime/execution_context.d.ts +7 -0
- package/dist/agent/runtime/execution_context.js +39 -3
- package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
- package/dist/agent/runtime/json_verifier_recipe.js +66 -0
- package/dist/agent/runtime/measurements.d.ts +110 -0
- package/dist/agent/runtime/measurements.js +317 -0
- package/dist/agent/runtime/policy.d.ts +4 -0
- package/dist/agent/runtime/policy.js +31 -20
- package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
- package/dist/agent/runtime/provider_native_evidence.js +86 -0
- package/dist/agent/runtime/recovery.d.ts +2 -0
- package/dist/agent/runtime/recovery.js +45 -2
- package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
- package/dist/agent/runtime/remote_broker_transport.js +84 -0
- package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
- package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
- package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
- package/dist/agent/runtime/remote_journal_replay.js +233 -0
- package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
- package/dist/agent/runtime/remote_mutation_journal.js +208 -0
- package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
- package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
- package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
- package/dist/agent/runtime/remote_resource_errors.js +9 -0
- package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
- package/dist/agent/runtime/remote_resource_script.js +1009 -0
- package/dist/agent/runtime/remote_resources.d.ts +29 -0
- package/dist/agent/runtime/remote_resources.js +160 -0
- package/dist/agent/runtime/source_verification.d.ts +12 -0
- package/dist/agent/runtime/source_verification.js +210 -0
- package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
- package/dist/agent/runtime/source_verification_contract.js +203 -0
- package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
- package/dist/agent/runtime/tool_checkpoints.js +26 -0
- package/dist/agent/runtime/tool_effects.d.ts +5 -0
- package/dist/agent/runtime/tool_progress.d.ts +12 -0
- package/dist/agent/runtime/tool_progress.js +76 -0
- package/dist/agent/runtime/tool_runtime.d.ts +16 -1
- package/dist/agent/runtime/tool_runtime.js +204 -10
- package/dist/agent/runtime/tracing.d.ts +1 -1
- package/dist/agent/runtime/verifier_capture.d.ts +9 -0
- package/dist/agent/runtime/verifier_capture.js +100 -0
- package/dist/agent/runtime/verifier_contract.d.ts +24 -0
- package/dist/agent/runtime/verifier_contract.js +41 -0
- package/dist/agent/subagent.d.ts +20 -10
- package/dist/agent/subagent.js +266 -29
- package/dist/agent/tool_response_directory.d.ts +24 -0
- package/dist/agent/tool_response_directory.js +61 -0
- package/dist/agent/tool_response_source.d.ts +8 -0
- package/dist/agent/tool_response_source.js +49 -0
- package/dist/agent/tool_response_store.d.ts +75 -4
- package/dist/agent/tool_response_store.js +583 -64
- package/dist/agent/tools/ask_user.d.ts +3 -1
- package/dist/agent/tools/ask_user.js +30 -11
- package/dist/agent/tools/background.js +1 -0
- package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
- package/dist/agent/tools/benchmark_cpu.js +102 -0
- package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
- package/dist/agent/tools/compare_benchmarks.js +53 -0
- package/dist/agent/tools/deferred.js +1 -0
- package/dist/agent/tools/delegate.js +18 -2
- package/dist/agent/tools/helper_requirements.d.ts +7 -0
- package/dist/agent/tools/helper_requirements.js +19 -0
- package/dist/agent/tools/index.d.ts +1 -0
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/measurement_report.d.ts +22 -0
- package/dist/agent/tools/measurement_report.js +190 -0
- package/dist/agent/tools/pane.d.ts +0 -7
- package/dist/agent/tools/pane.js +5 -4
- package/dist/agent/tools/project_context.d.ts +3 -0
- package/dist/agent/tools/project_context.js +45 -0
- package/dist/agent/tools/recall_tool_response.d.ts +3 -5
- package/dist/agent/tools/recall_tool_response.js +19 -29
- package/dist/agent/tools/registry.d.ts +1 -0
- package/dist/agent/tools/registry.js +23 -0
- package/dist/agent/tools/related_sessions.d.ts +2 -0
- package/dist/agent/tools/related_sessions.js +156 -0
- package/dist/agent/tools/routine.js +13 -3
- package/dist/agent/tools/session_evidence.d.ts +3 -0
- package/dist/agent/tools/session_evidence.js +72 -0
- package/dist/agent/tools/shell.d.ts +4 -3
- package/dist/agent/tools/shell.js +174 -47
- package/dist/agent/tools/tool_search.js +3 -2
- package/dist/agent/tools/types.d.ts +28 -0
- package/dist/agent/tools/verify_artifact.d.ts +2 -0
- package/dist/agent/tools/verify_artifact.js +60 -0
- package/dist/agent/tools/workflow.js +1 -0
- package/dist/app/activity.d.ts +6 -1
- package/dist/app/activity.js +81 -12
- package/dist/app/agent_runner.d.ts +6 -1
- package/dist/app/agent_runner.js +34 -19
- package/dist/app/command_context.js +106 -10
- package/dist/app/controller.d.ts +15 -4
- package/dist/app/controller.js +29 -3
- package/dist/app/escape_stop.d.ts +11 -0
- package/dist/app/escape_stop.js +15 -0
- package/dist/app/liveness.d.ts +6 -4
- package/dist/app/liveness.js +13 -3
- package/dist/app/model_catalog_cache.d.ts +5 -0
- package/dist/app/model_catalog_cache.js +138 -0
- package/dist/app/ordered_transcript.js +43 -4
- package/dist/app/prompt_turn.d.ts +14 -0
- package/dist/app/prompt_turn.js +53 -0
- package/dist/app/recap.d.ts +3 -0
- package/dist/app/recap.js +9 -3
- package/dist/app/restart.d.ts +3 -2
- package/dist/app/restart.js +22 -7
- package/dist/app/rewind.d.ts +3 -3
- package/dist/app/rewind.js +3 -3
- package/dist/app/selection_completion.d.ts +17 -2
- package/dist/app/selection_completion.js +84 -9
- package/dist/app/session_manager.d.ts +38 -6
- package/dist/app/session_manager.js +263 -60
- package/dist/app/session_messages.d.ts +3 -0
- package/dist/app/session_messages.js +60 -0
- package/dist/app/task_scope_router.d.ts +20 -0
- package/dist/app/task_scope_router.js +173 -0
- package/dist/app/transcript_window.d.ts +2 -0
- package/dist/app/transcript_window.js +4 -0
- package/dist/app/workspace_admission_details.d.ts +3 -0
- package/dist/app/workspace_admission_details.js +26 -0
- package/dist/app/workspace_layout_controller.d.ts +11 -0
- package/dist/app/workspace_layout_controller.js +88 -0
- package/dist/app/workspace_monitor.d.ts +1 -0
- package/dist/app/workspace_monitor.js +216 -0
- package/dist/app/workspace_presets.d.ts +29 -0
- package/dist/app/workspace_presets.js +192 -0
- package/dist/attention/queue.d.ts +37 -0
- package/dist/attention/queue.js +183 -0
- package/dist/bench/rf_shared.d.ts +2 -0
- package/dist/board/binding.d.ts +42 -0
- package/dist/board/binding.js +38 -0
- package/dist/board/client.d.ts +42 -0
- package/dist/board/client.js +60 -0
- package/dist/cli.js +1 -1
- package/dist/cli_args.js +19 -17
- package/dist/cli_main.js +110 -13
- package/dist/cli_ratatui.d.ts +183 -16
- package/dist/cli_ratatui.js +3989 -1255
- package/dist/components/ActivityLine.d.ts +3 -2
- package/dist/components/ActivityLine.js +2 -2
- package/dist/components/InputArea.d.ts +19 -1
- package/dist/components/InputArea.js +264 -190
- package/dist/components/MessageList.d.ts +2 -0
- package/dist/components/MessageList.js +3 -1
- package/dist/components/SingleMessage.d.ts +2 -0
- package/dist/components/SingleMessage.js +4 -2
- package/dist/components/StatusBar.d.ts +4 -2
- package/dist/components/StatusBar.js +3 -1
- package/dist/conductor_branch.js +3 -0
- package/dist/config/api_retry.d.ts +6 -1
- package/dist/config/api_retry.js +19 -1
- package/dist/config/defaults.d.ts +2 -1
- package/dist/config/defaults.js +9 -2
- package/dist/config/message_display.d.ts +1 -0
- package/dist/config/message_display.js +17 -2
- package/dist/config/permissions.d.ts +21 -0
- package/dist/config/permissions.js +89 -0
- package/dist/config/resolved.d.ts +2 -2
- package/dist/config/types.d.ts +32 -5
- package/dist/config.d.ts +38 -4
- package/dist/config.js +290 -42
- package/dist/context/project_intelligence.d.ts +44 -0
- package/dist/context/project_intelligence.js +246 -0
- package/dist/desktop/cli_launch.d.ts +8 -0
- package/dist/desktop/cli_launch.js +6 -0
- package/dist/desktop/cli_shim.d.ts +8 -0
- package/dist/desktop/cli_shim.js +120 -0
- package/dist/desktop/runtime.d.ts +16 -0
- package/dist/desktop/runtime.js +161 -0
- package/dist/desktop/worker_launch.d.ts +7 -0
- package/dist/desktop/worker_launch.js +7 -0
- package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
- package/dist/diagnostics/cargo_mutants_selection.js +48 -0
- package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
- package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
- package/dist/diagnostics/session_evidence_package.d.ts +40 -0
- package/dist/diagnostics/session_evidence_package.js +234 -0
- package/dist/diagnostics/session_export.d.ts +93 -0
- package/dist/diagnostics/session_export.js +634 -0
- package/dist/emulation/tool_adapters.js +48 -18
- package/dist/emulation/ztc_addendum.d.ts +1 -0
- package/dist/emulation/ztc_addendum.js +13 -12
- package/dist/environment/service_runner.d.ts +1 -0
- package/dist/environment/service_runner.js +68 -0
- package/dist/environment/store.d.ts +120 -0
- package/dist/environment/store.js +387 -0
- package/dist/events.d.ts +40 -1
- package/dist/events.js +10 -17
- package/dist/goal_contract.d.ts +25 -0
- package/dist/goal_contract.js +44 -0
- package/dist/headless.d.ts +31 -0
- package/dist/headless.js +578 -70
- package/dist/headless_cli.js +8 -0
- package/dist/headless_cli_args.d.ts +1 -0
- package/dist/headless_cli_args.js +1 -0
- package/dist/intake/cli.d.ts +1 -0
- package/dist/intake/cli.js +33 -0
- package/dist/intake/format.d.ts +2 -0
- package/dist/intake/format.js +14 -0
- package/dist/intake/notifications.d.ts +16 -0
- package/dist/intake/notifications.js +132 -0
- package/dist/intake/store.d.ts +56 -0
- package/dist/intake/store.js +238 -0
- package/dist/library.d.ts +8 -0
- package/dist/library.js +19 -10
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +20 -0
- package/dist/modes/builtin/plan.yaml +2 -2
- package/dist/modes/handlers.js +1 -1
- package/dist/onboarding/first_run.d.ts +3 -0
- package/dist/onboarding/first_run.js +33 -25
- package/dist/onboarding/setup.d.ts +32 -0
- package/dist/onboarding/setup.js +133 -0
- package/dist/onboarding/setup_agent_health.d.ts +17 -0
- package/dist/onboarding/setup_agent_health.js +79 -0
- package/dist/onboarding/setup_environment.d.ts +66 -0
- package/dist/onboarding/setup_environment.js +219 -0
- package/dist/onboarding/setup_workspace.d.ts +17 -0
- package/dist/onboarding/setup_workspace.js +129 -0
- package/dist/optimize/adapters/index.d.ts +4 -1
- package/dist/optimize/adapters/index.js +7 -0
- package/dist/optimize/adapters/kernelbench.js +14 -5
- package/dist/optimize/adapters/popcorn.js +3 -2
- package/dist/optimize/adapters/profile_benchmark.js +13 -7
- package/dist/optimize/adapters/rfbench.js +25 -14
- package/dist/optimize/adapters/types.d.ts +3 -0
- package/dist/optimize/adapters/zergpod.js +5 -2
- package/dist/optimize/baseline.js +10 -21
- package/dist/optimize/branch.js +9 -12
- package/dist/optimize/combine.js +2 -3
- package/dist/optimize/executor.js +34 -48
- package/dist/optimize/measurements.d.ts +55 -0
- package/dist/optimize/measurements.js +173 -0
- package/dist/optimize/results.d.ts +1 -0
- package/dist/optimize/results.js +11 -33
- package/dist/optimize/tge.js +2 -1
- package/dist/panes/initial_input.d.ts +19 -0
- package/dist/panes/initial_input.js +206 -0
- package/dist/panes/labels.d.ts +16 -0
- package/dist/panes/labels.js +42 -0
- package/dist/panes/model_catalog.d.ts +3 -3
- package/dist/panes/model_catalog.js +28 -4
- package/dist/panes/pane_registry.js +6 -2
- package/dist/panes/types.d.ts +3 -0
- package/dist/panes/ztc_launch.js +19 -0
- package/dist/plugin/optimization.d.ts +2 -0
- package/dist/plugin/optimization.js +1 -0
- package/dist/plugin/runtime.d.ts +2 -0
- package/dist/plugin/simulation.d.ts +6 -1
- package/dist/plugin/simulation_contract.d.ts +836 -1
- package/dist/plugin/simulation_documents.d.ts +22 -0
- package/dist/plugin/simulation_documents.js +1 -0
- package/dist/plugin/simulation_interventions.d.ts +103 -0
- package/dist/plugin/simulation_interventions.js +1 -0
- package/dist/plugin/simulation_living.d.ts +1 -0
- package/dist/plugin/simulation_living.js +3 -0
- package/dist/plugin/simulation_meetings.d.ts +121 -0
- package/dist/plugin/simulation_meetings.js +1 -0
- package/dist/plugin/simulation_story_review.d.ts +111 -0
- package/dist/plugin/simulation_story_review.js +1 -0
- package/dist/plugin/simulation_storyteller.d.ts +91 -0
- package/dist/plugin/simulation_storyteller.js +1 -0
- package/dist/plugin/simulation_structured.d.ts +2 -0
- package/dist/plugin/simulation_structured.js +2 -0
- package/dist/plugin/simulation_toolkit.d.ts +21 -0
- package/dist/plugin/simulation_toolkit.js +24 -0
- package/dist/prompts/tool_delegate_task.j2 +1 -1
- package/dist/remote-broker/Dockerfile +10 -0
- package/dist/remote-broker/archive.py +1227 -0
- package/dist/remote-broker/cutover.py +761 -0
- package/dist/remote-broker/requirements.txt +5 -0
- package/dist/remote-broker/resources.json +6 -0
- package/dist/remote-broker/resources.write-staging.example.json +18 -0
- package/dist/remote-broker/retained.py +686 -0
- package/dist/remote-broker/retained_cleanup.py +550 -0
- package/dist/remote-broker/server.py +648 -0
- package/dist/remote-broker/worker.py +1007 -0
- package/dist/router_auth.d.ts +31 -0
- package/dist/router_auth.js +155 -9
- package/dist/runbooks/catalog.d.ts +3 -0
- package/dist/runbooks/catalog.js +26 -0
- package/dist/runbooks/library.d.ts +11 -0
- package/dist/runbooks/library.js +95 -0
- package/dist/runbooks/package.d.ts +43 -0
- package/dist/runbooks/package.js +102 -0
- package/dist/session.d.ts +65 -13
- package/dist/session.js +548 -43
- package/dist/session_handoff.d.ts +25 -0
- package/dist/session_handoff.js +322 -0
- package/dist/session_handoff_contract.d.ts +89 -0
- package/dist/session_handoff_contract.js +179 -0
- package/dist/session_media.d.ts +41 -0
- package/dist/session_media.js +357 -0
- package/dist/session_ownership.d.ts +16 -0
- package/dist/session_ownership.js +221 -0
- package/dist/session_public.d.ts +10 -2
- package/dist/session_public.js +6 -1
- package/dist/simulation/dynamic_history.d.ts +42 -0
- package/dist/simulation/dynamic_history.js +173 -0
- package/dist/simulation/engines/swarm/pane_manager.js +7 -6
- package/dist/simulation/inference.d.ts +3 -1
- package/dist/simulation/inference.js +19 -1
- package/dist/simulation/intervention_generation.d.ts +27 -0
- package/dist/simulation/intervention_generation.js +246 -0
- package/dist/simulation/intervention_material.d.ts +4 -0
- package/dist/simulation/intervention_material.js +81 -0
- package/dist/simulation/living/context.d.ts +3130 -0
- package/dist/simulation/living/context.js +944 -0
- package/dist/simulation/living/definer.d.ts +12 -0
- package/dist/simulation/living/definer.js +223 -0
- package/dist/simulation/living/definition.d.ts +257 -0
- package/dist/simulation/living/definition.js +341 -0
- package/dist/simulation/living/frame.d.ts +259 -0
- package/dist/simulation/living/frame.js +223 -0
- package/dist/simulation/living/identifiers.d.ts +2 -0
- package/dist/simulation/living/identifiers.js +12 -0
- package/dist/simulation/living/index.d.ts +15 -0
- package/dist/simulation/living/index.js +15 -0
- package/dist/simulation/living/inference.d.ts +219 -0
- package/dist/simulation/living/inference.js +576 -0
- package/dist/simulation/living/mechanics.d.ts +54 -0
- package/dist/simulation/living/mechanics.js +588 -0
- package/dist/simulation/living/model-input.d.ts +25 -0
- package/dist/simulation/living/model-input.js +215 -0
- package/dist/simulation/living/open-world.d.ts +257 -0
- package/dist/simulation/living/open-world.js +102 -0
- package/dist/simulation/living/projection.d.ts +7 -0
- package/dist/simulation/living/projection.js +31 -0
- package/dist/simulation/living/recall.d.ts +17 -0
- package/dist/simulation/living/recall.js +25 -0
- package/dist/simulation/living/reduce.d.ts +15 -0
- package/dist/simulation/living/reduce.js +1853 -0
- package/dist/simulation/living/replay.d.ts +6 -0
- package/dist/simulation/living/replay.js +22 -0
- package/dist/simulation/living/runtime.d.ts +140 -0
- package/dist/simulation/living/runtime.js +3020 -0
- package/dist/simulation/living/shared-kinds.d.ts +24 -0
- package/dist/simulation/living/shared-kinds.js +96 -0
- package/dist/simulation/living/types.d.ts +2785 -0
- package/dist/simulation/living/types.js +669 -0
- package/dist/simulation/llm.d.ts +3 -1
- package/dist/simulation/llm.js +90 -30
- package/dist/simulation/memory/embedding.d.ts +1 -1
- package/dist/simulation/memory/embedding.js +9 -3
- package/dist/simulation/memory/filesystem.d.ts +2 -0
- package/dist/simulation/memory/filesystem.js +5 -1
- package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
- package/dist/simulation/memory/filesystem_file_tree.js +41 -10
- package/dist/simulation/memory/filesystem_invariants.js +1 -2
- package/dist/simulation/memory/filesystem_types.d.ts +1 -0
- package/dist/simulation/memory/memory.d.ts +2 -0
- package/dist/simulation/memory/memory.js +5 -1
- package/dist/simulation/orchestration/intent.js +0 -2
- package/dist/simulation/orchestration/lifecycle.js +0 -2
- package/dist/simulation/plugin_host.d.ts +25 -1
- package/dist/simulation/plugin_host.js +334 -17
- package/dist/simulation/session_documents.d.ts +20 -0
- package/dist/simulation/session_documents.js +62 -0
- package/dist/simulation/session_input_validation.d.ts +43 -0
- package/dist/simulation/session_input_validation.js +364 -0
- package/dist/simulation/session_inputs.d.ts +32 -0
- package/dist/simulation/session_inputs.js +154 -0
- package/dist/simulation/telemetry.d.ts +11 -14
- package/dist/simulation/telemetry.js +177 -54
- package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
- package/dist/simulation/toolkit/bounded_turns.js +80 -0
- package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
- package/dist/simulation/toolkit/dynamic_agents.js +218 -0
- package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
- package/dist/simulation/toolkit/dynamic_digest.js +8 -0
- package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
- package/dist/simulation/toolkit/dynamic_documents.js +71 -0
- package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
- package/dist/simulation/toolkit/dynamic_driver.js +269 -0
- package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
- package/dist/simulation/toolkit/dynamic_game.js +805 -0
- package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
- package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
- package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
- package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
- package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
- package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
- package/dist/simulation/toolkit/dynamic_progress.js +214 -0
- package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
- package/dist/simulation/toolkit/dynamic_projection.js +219 -0
- package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
- package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
- package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
- package/dist/simulation/toolkit/dynamic_state.js +1967 -0
- package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
- package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
- package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
- package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
- package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_world.js +377 -0
- package/dist/simulation/toolkit/structured_output.d.ts +5 -0
- package/dist/simulation/toolkit/structured_output.js +321 -0
- package/dist/simulation/types/world.d.ts +2 -0
- package/dist/simulation/world/artifact_store.js +1 -1
- package/dist/simulation/world/memory_manager.d.ts +5 -0
- package/dist/simulation/world/memory_manager.js +17 -2
- package/dist/simulation/world/state.d.ts +3 -2
- package/dist/simulation/world/state.js +15 -5
- package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
- package/dist/terminal_attach.d.ts +1 -0
- package/dist/terminal_attach.js +113 -0
- package/dist/tool_progress.d.ts +3 -0
- package/dist/tool_progress.js +27 -0
- package/dist/types.d.ts +145 -10
- package/dist/types.js +3 -0
- package/dist/ui/core/input.d.ts +3 -0
- package/dist/ui/core/input_segments.d.ts +18 -1
- package/dist/ui/core/input_segments.js +131 -47
- package/dist/ui/core/input_state.d.ts +12 -0
- package/dist/ui/core/layout_yoga.d.ts +1 -1
- package/dist/ui/core/turn_metadata.d.ts +5 -0
- package/dist/ui/core/turn_metadata.js +22 -0
- package/dist/ui/ink/render.d.ts +2 -1
- package/dist/ui/ink/render.js +6 -5
- package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
- package/dist/ui/ratatui/headless_pane_manager.js +293 -104
- package/dist/ui/ratatui/index.d.ts +1 -0
- package/dist/ui/ratatui/input.d.ts +4 -0
- package/dist/ui/ratatui/input.js +15 -0
- package/dist/ui/ratatui/ipc_path.d.ts +2 -0
- package/dist/ui/ratatui/ipc_path.js +29 -1
- package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
- package/dist/ui/ratatui/mux_persistence.js +139 -1
- package/dist/ui/ratatui/pane_relay.d.ts +1 -1
- package/dist/ui/ratatui/pane_relay.js +55 -27
- package/dist/ui/ratatui/process.d.ts +17 -1
- package/dist/ui/ratatui/process.js +80 -7
- package/dist/ui/ratatui/protocol.d.ts +236 -1
- package/dist/ui/ratatui/protocol.js +90 -0
- package/dist/ui/scrollback.d.ts +2 -1
- package/dist/ui/scrollback.js +9 -4
- package/dist/ui/stream_typewriter.d.ts +8 -3
- package/dist/ui/stream_typewriter.js +30 -1
- package/dist/ui/streaming_scrollback.d.ts +1 -0
- package/dist/ui/streaming_scrollback.js +1 -1
- package/dist/ui/transcript_render_cache.js +10 -0
- package/dist/ui/views/activity_line.d.ts +5 -3
- package/dist/ui/views/activity_line.js +65 -7
- package/dist/ui/views/app.d.ts +6 -3
- package/dist/ui/views/app.js +17 -6
- package/dist/ui/views/completion_menu.js +10 -5
- package/dist/ui/views/input_area.d.ts +7 -3
- package/dist/ui/views/input_area.js +15 -9
- package/dist/ui/views/message_list.d.ts +4 -1
- package/dist/ui/views/message_list.js +89 -19
- package/dist/ui/views/queue_panel.d.ts +3 -1
- package/dist/ui/views/queue_panel.js +9 -2
- package/dist/ui/views/status_bar.d.ts +4 -2
- package/dist/ui/views/status_bar.js +28 -13
- package/dist/ui/views/theme.d.ts +2 -0
- package/dist/ui/views/theme.js +2 -0
- package/dist/ui/widgets/color_panel.js +18 -0
- package/dist/ui/widgets/form.js +8 -0
- package/dist/ui/widgets/log_view.js +7 -0
- package/dist/ui/widgets/navigator_panel.js +8 -0
- package/dist/ui/widgets/prompt_viewer.js +2 -0
- package/dist/ui/widgets/questionnaire.d.ts +2 -0
- package/dist/ui/widgets/questionnaire.js +79 -16
- package/dist/ui/widgets/resume_panel.d.ts +2 -0
- package/dist/ui/widgets/resume_panel.js +26 -15
- package/dist/ui/widgets/rewind_panel.d.ts +2 -2
- package/dist/ui/widgets/rewind_panel.js +4 -2
- package/dist/ui/widgets/select.js +16 -27
- package/dist/ui/widgets/settings_panel.js +16 -0
- package/dist/ui/widgets/status_line_panel.d.ts +1 -0
- package/dist/ui/widgets/status_line_panel.js +30 -4
- package/dist/ui/widgets/text_input.d.ts +2 -0
- package/dist/ui/widgets/text_input.js +15 -1
- package/dist/ui/widgets/types.d.ts +2 -0
- package/dist/usage_projection.d.ts +24 -0
- package/dist/usage_projection.js +130 -0
- package/dist/usage_snapshot.d.ts +36 -0
- package/dist/usage_snapshot.js +131 -0
- package/dist/utils/assistant_message.d.ts +2 -0
- package/dist/utils/assistant_message.js +5 -0
- package/dist/utils/atomic_write.d.ts +6 -2
- package/dist/utils/atomic_write.js +29 -4
- package/dist/utils/checked_shell.d.ts +25 -0
- package/dist/utils/checked_shell.js +107 -0
- package/dist/utils/compaction.d.ts +4 -1
- package/dist/utils/compaction.js +31 -7
- package/dist/utils/context.d.ts +4 -4
- package/dist/utils/context.js +14 -12
- package/dist/utils/deepseek.d.ts +2 -2
- package/dist/utils/deepseek.js +13 -5
- package/dist/utils/ephemeral.js +4 -0
- package/dist/utils/ephemeral_tools.d.ts +18 -0
- package/dist/utils/ephemeral_tools.js +97 -0
- package/dist/utils/jev_client.d.ts +4 -0
- package/dist/utils/jev_client.js +15 -0
- package/dist/utils/model_cache.d.ts +15 -0
- package/dist/utils/model_cache.js +143 -0
- package/dist/utils/models.d.ts +4 -2
- package/dist/utils/models.js +12 -16
- package/dist/utils/reasoning.d.ts +8 -1
- package/dist/utils/reasoning.js +31 -11
- package/dist/utils/router_catalog.d.ts +49 -0
- package/dist/utils/router_catalog.js +271 -0
- package/dist/utils/sandbox.d.ts +9 -0
- package/dist/utils/sandbox.js +118 -15
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.js +98 -34
- package/dist/utils/subprocess_env.d.ts +3 -0
- package/dist/utils/subprocess_env.js +27 -1
- package/dist/web/accounts_mirror.d.ts +3 -1
- package/dist/web/accounts_mirror.js +13 -9
- package/dist/web/child_publication.d.ts +17 -0
- package/dist/web/child_publication.js +45 -0
- package/dist/web/mirror_authenticator.d.ts +31 -11
- package/dist/web/mirror_authenticator.js +67 -17
- package/dist/web/mirror_client.d.ts +31 -4
- package/dist/web/mirror_client.js +189 -23
- package/dist/web/mirror_stream.d.ts +84 -22
- package/dist/web/mirror_stream.js +676 -95
- package/dist/web/observer_query_redaction.d.ts +6 -0
- package/dist/web/observer_query_redaction.js +58 -0
- package/dist/web/pane_registry.d.ts +2 -0
- package/dist/web/pane_registry.js +86 -22
- package/dist/web/tool_image.d.ts +6 -0
- package/dist/web/tool_image.js +17 -0
- package/dist/web/view_plugin.d.ts +14 -0
- package/dist/web/view_plugin.js +23 -1
- package/dist/worker/executor.js +31 -12
- package/dist/worker/index.d.ts +1 -0
- package/dist/worker/toolsets.js +3 -1
- package/dist/worker/types.d.ts +12 -1
- package/dist/workflows/registry.js +1 -0
- package/dist/workflows/registry_handle.d.ts +2 -0
- package/dist/workflows/runtime.d.ts +2 -0
- package/dist/workflows/runtime.js +1 -0
- package/dist/workspace_agent.d.ts +7 -0
- package/dist/workspace_agent.js +36 -0
- package/dist/workspace_collision.d.ts +76 -0
- package/dist/workspace_collision.js +688 -0
- package/dist/workspace_layout.d.ts +18 -0
- package/dist/workspace_layout.js +121 -0
- package/dist/workspace_runtime.d.ts +44 -0
- package/dist/workspace_runtime.js +82 -0
- package/dist/workspace_session_context.d.ts +62 -0
- package/dist/workspace_session_context.js +160 -0
- package/dist/zlm-embed.mjs +32518 -9615
- package/dist/zlm.js +1 -0
- package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
- package/node_modules/@zerg/model-contracts/README.md +90 -0
- package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
- package/node_modules/@zerg/model-contracts/index.js +223 -0
- package/node_modules/@zerg/model-contracts/package.json +8 -0
- package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
- package/package.json +45 -24
- package/packages/model-contracts/LICENSE.md +375 -0
- package/packages/model-contracts/README.md +90 -0
- package/packages/model-contracts/index.d.ts +20 -0
- package/packages/model-contracts/index.js +223 -0
- package/packages/model-contracts/package.json +8 -0
- package/remote-broker/Dockerfile +10 -0
- package/remote-broker/README.md +308 -0
- package/remote-broker/archive.py +1227 -0
- package/remote-broker/cutover.py +761 -0
- package/remote-broker/requirements.txt +5 -0
- package/remote-broker/resources.example.json +6 -0
- package/remote-broker/resources.write-staging.example.json +18 -0
- package/remote-broker/retained.py +686 -0
- package/remote-broker/retained_cleanup.py +550 -0
- package/remote-broker/server.py +648 -0
- package/scripts/build-remote-broker.mjs +13 -0
- package/scripts/ensure-local-packages.mjs +6 -0
- 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
|
-
*
|
|
5
|
-
* ~/.ztc/sessions/{projectHash}/{sessionId}/tool_responses/{
|
|
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 {
|
|
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.
|
|
194
|
-
|
|
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
|
-
|
|
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
|
|
212
|
-
const
|
|
213
|
-
|
|
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
|
-
|
|
376
|
+
catch (error) {
|
|
377
|
+
if (error.code !== 'ENOENT')
|
|
378
|
+
throw error;
|
|
217
379
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|