@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
package/dist/agent/agent.js
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
// Standard library
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { existsSync, readFileSync, realpathSync } from 'fs';
|
|
5
|
+
import { extname, resolve } from 'path';
|
|
6
|
+
import { formatTaskHandoffContext, parseTaskHandoffProjection } from '../session_handoff_contract.js';
|
|
5
7
|
import { createBackendForProvider } from './backends/index.js';
|
|
6
8
|
import { mergeTokenUsage } from './backends/token_usage.js';
|
|
9
|
+
import { createUsageSnapshot, mergeUsageSnapshots, parseUsageSnapshot, recordUsageReceipt } from '../usage_snapshot.js';
|
|
7
10
|
import { isTransportCodeError, isTransportMessageError } from './backends/transport_errors.js';
|
|
11
|
+
import { retryAfterDelayMs } from './backends/http_error.js';
|
|
8
12
|
import { inferDefaultProvider, getDefaultApiKeyForProvider, getDefaultModelForProvider } from '../config/defaults.js';
|
|
9
|
-
import { resolveApiRetrySettings } from '../config/api_retry.js';
|
|
13
|
+
import { apiRetryDelayMs, resolveApiRetrySettings } from '../config/api_retry.js';
|
|
10
14
|
import { resolveExecutionSettings } from '../config/execution.js';
|
|
11
15
|
import { createRecoveryGuard } from './runtime/recovery.js';
|
|
12
16
|
import { listCommandJobs, interruptCommandJobs } from './runtime/command_jobs.js';
|
|
13
|
-
import {
|
|
17
|
+
import { awaitManagedCommandProgress, formatManagedCommandObservations } from './runtime/command_wait.js';
|
|
18
|
+
import { describeExecutionContext, DELETION_EVIDENCE_GUIDANCE } from './runtime/execution_context.js';
|
|
14
19
|
import { scheduleToolBatch, commandStillRunning } from './runtime/tool_scheduler.js';
|
|
15
20
|
import { AllowAllPolicy } from './runtime/policy.js';
|
|
16
21
|
import { NoopTracer } from './runtime/tracing.js';
|
|
22
|
+
import { createDiagnosticSnapshot, mergeDiagnosticSnapshots, parseDiagnosticSnapshot, recordDiagnosticEvent as appendDiagnosticEvent } from '../diagnostics/session_export.js';
|
|
23
|
+
import { getVersion } from '../utils/version.js';
|
|
17
24
|
import { defaultTools as baseDefaultTools } from './tools/registry.js';
|
|
18
25
|
import { toolSearchTool } from './tools/tool_search.js';
|
|
19
26
|
import { recallToolResponseTool } from './tools/recall_tool_response.js';
|
|
20
27
|
import { adaptationRunTool } from './tools/adaptation.js';
|
|
21
28
|
import { getRequestToolDefinitions } from './tools/deferred.js';
|
|
22
29
|
import { getLatestTodos } from './tools/todo.js';
|
|
23
|
-
import { ToolResponseStore, describeOffloadPlaceholderFormat } from './tool_response_store.js';
|
|
24
|
-
import {
|
|
30
|
+
import { ToolResponseStore, ToolResponseIntegrityError, describeOffloadPlaceholderFormat } from './tool_response_store.js';
|
|
31
|
+
import { formatEvidenceContext } from './runtime/evidence.js';
|
|
32
|
+
import { captureNativeReport, NativeObservationLimitError } from './runtime/provider_native_evidence.js';
|
|
33
|
+
import { projectCurrentVerification } from './runtime/source_verification.js';
|
|
34
|
+
import { sessionEvidenceTool } from './tools/session_evidence.js';
|
|
35
|
+
import { MEASUREMENT_EVIDENCE_GUIDANCE } from './measurement_guidance.js';
|
|
36
|
+
import { RequestEncoder, STABLE_RESULT_LIMIT, nativeHistoryContextId } from './request_encoder.js';
|
|
25
37
|
import { parseImageToolResult } from './tool_result_parsing.js';
|
|
26
38
|
import { createRepeatGuard, pinnedTaskSystem } from './loop_policy.js';
|
|
27
39
|
import { extractSourcePath } from './tool_compression.js';
|
|
28
40
|
import { executeToolWithRuntime } from './runtime/tool_runtime.js';
|
|
41
|
+
import { createToolProgressAccumulator } from './runtime/tool_progress.js';
|
|
42
|
+
import { ToolCapability } from './runtime/capabilities.js';
|
|
29
43
|
import { buildUserSubprocessEnv } from '../utils/subprocess_env.js';
|
|
30
44
|
import { resolvePromptPrefixId } from '../utils/compaction.js';
|
|
31
45
|
import { INCOMPLETE_MODEL_RESPONSE_CODE, incompleteModelResponseMessage, isIncompleteModelResponse, } from './completion_state.js';
|
|
46
|
+
import { remoteDeletionReport, NO_VERIFIED_DELETION_REPORT } from './runtime/remote_deletion_receipts.js';
|
|
32
47
|
const defaultTools = [
|
|
33
48
|
...baseDefaultTools,
|
|
34
49
|
toolSearchTool,
|
|
35
50
|
recallToolResponseTool,
|
|
51
|
+
sessionEvidenceTool,
|
|
36
52
|
adaptationRunTool,
|
|
37
53
|
];
|
|
38
54
|
function getTool(name, tools) {
|
|
@@ -160,6 +176,8 @@ export class Agent {
|
|
|
160
176
|
backend;
|
|
161
177
|
policy;
|
|
162
178
|
tracer;
|
|
179
|
+
diagnostics = createDiagnosticSnapshot();
|
|
180
|
+
usageSnapshot = createUsageSnapshot();
|
|
163
181
|
streamChunkSize = 32;
|
|
164
182
|
/**
|
|
165
183
|
* True when the last callAPI() consumed the backend's generateStream and
|
|
@@ -173,13 +191,134 @@ export class Agent {
|
|
|
173
191
|
/** Auto-continues spent since the last tool run; reset per turn and per tool. */
|
|
174
192
|
_consecutiveAutoContinues = 0;
|
|
175
193
|
_subprocessEnv;
|
|
194
|
+
commandPolicy;
|
|
176
195
|
toolEffectObserver;
|
|
196
|
+
onLocalFileChange;
|
|
197
|
+
toolCheckpoint;
|
|
198
|
+
toolCheckpointTail = Promise.resolve();
|
|
199
|
+
toolCheckpointFailure = null;
|
|
200
|
+
failedToolCheckpoint = null;
|
|
201
|
+
nativeEvidencePending = Promise.resolve();
|
|
202
|
+
nativeEvidenceFailure = null;
|
|
203
|
+
nativeEvidenceCaptureDepth = 0;
|
|
204
|
+
// Invocation identity is independent of command text: a fresh call ID is a
|
|
205
|
+
// new operation, while a recorded ID must never reach dispatch a second time.
|
|
206
|
+
recordedToolCalls = new Map();
|
|
207
|
+
remoteResourceSession;
|
|
177
208
|
#conversationBinding;
|
|
178
209
|
// Per-instance offload store + tool-discovery set. Owning these at the Agent
|
|
179
210
|
// level (rather than as module singletons) means subagents get isolated
|
|
180
211
|
// copies — their tool_search discoveries and tool-result writes never leak
|
|
181
212
|
// into the parent's request schemas or tool_responses/ directory.
|
|
182
|
-
toolResponseStore
|
|
213
|
+
toolResponseStore;
|
|
214
|
+
evidenceUnsubscribe;
|
|
215
|
+
getEvidenceSnapshot() {
|
|
216
|
+
return this.toolResponseStore.evidence.getSnapshot();
|
|
217
|
+
}
|
|
218
|
+
getDiagnosticSessionId() { return this.config.sessionId; }
|
|
219
|
+
getUsageSessionId() { return this.config.sessionId; }
|
|
220
|
+
getUsageSnapshot() { return structuredClone(this.usageSnapshot); }
|
|
221
|
+
restoreUsageSnapshot(snapshot) {
|
|
222
|
+
const admitted = parseUsageSnapshot(snapshot);
|
|
223
|
+
if (admitted === null)
|
|
224
|
+
throw new TypeError('Invalid usage snapshot');
|
|
225
|
+
this.usageSnapshot = mergeUsageSnapshots(admitted, this.usageSnapshot);
|
|
226
|
+
}
|
|
227
|
+
retainUsageReceipt(receipt) {
|
|
228
|
+
this.usageSnapshot = recordUsageReceipt(this.usageSnapshot, receipt);
|
|
229
|
+
this.emit({ type: 'request_usage', receipt: structuredClone(receipt) });
|
|
230
|
+
}
|
|
231
|
+
getDiagnosticSnapshot() { return structuredClone(this.diagnostics); }
|
|
232
|
+
restoreDiagnosticSnapshot(snapshot) {
|
|
233
|
+
const admitted = parseDiagnosticSnapshot(snapshot);
|
|
234
|
+
if (admitted === null)
|
|
235
|
+
throw new TypeError('Invalid diagnostic snapshot');
|
|
236
|
+
// Retain both the restored history and this newly constructed runtime epoch.
|
|
237
|
+
this.diagnostics = mergeDiagnosticSnapshots(admitted, this.diagnostics);
|
|
238
|
+
}
|
|
239
|
+
recordDiagnosticEvent(kind, data) {
|
|
240
|
+
this.diagnostics = appendDiagnosticEvent(this.diagnostics, { id: `diagnostic_${randomUUID()}`,
|
|
241
|
+
kind, origin: kind === 'provider_response' ? 'provider' : 'host', storedRole: null,
|
|
242
|
+
timestamp: new Date().toISOString(), data });
|
|
243
|
+
}
|
|
244
|
+
/** Persist only factual API metadata, never prompts, credentials, or raw response bodies. */
|
|
245
|
+
captureDiagnosticTrace(event) {
|
|
246
|
+
const data = event.data;
|
|
247
|
+
if (!data)
|
|
248
|
+
return;
|
|
249
|
+
if (event.type === 'api_request') {
|
|
250
|
+
const tools = Array.isArray(data.tools) ? data.tools.flatMap(tool => typeof tool === 'object' && tool !== null && 'name' in tool && typeof tool.name === 'string' ? [tool.name] : []) : [];
|
|
251
|
+
this.recordDiagnosticEvent('runtime', { phase: 'provider_request',
|
|
252
|
+
turnId: typeof data.turnId === 'string' ? data.turnId : null,
|
|
253
|
+
model: typeof data.model === 'string' ? data.model : null,
|
|
254
|
+
maxTokens: typeof data.maxTokens === 'number' ? data.maxTokens : null, tools });
|
|
255
|
+
}
|
|
256
|
+
else if (event.type === 'api_response') {
|
|
257
|
+
const usage = {};
|
|
258
|
+
if (typeof data.usage === 'object' && data.usage !== null) {
|
|
259
|
+
for (const [key, value] of Object.entries(data.usage)) {
|
|
260
|
+
if (['inputTokens', 'outputTokens', 'totalTokens', 'cachedInputTokens', 'cacheWriteInputTokens', 'reasoningTokens', 'contextWindow'].includes(key)
|
|
261
|
+
&& typeof value === 'number' && Number.isFinite(value))
|
|
262
|
+
usage[key] = value;
|
|
263
|
+
if (key === 'estimated' && typeof value === 'boolean')
|
|
264
|
+
usage[key] = value;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.recordDiagnosticEvent('provider_response', {
|
|
268
|
+
turnId: typeof data.turnId === 'string' ? data.turnId : null,
|
|
269
|
+
model: typeof data.model === 'string' ? data.model : null,
|
|
270
|
+
provider: this.config.runtimeFacts.provider ?? null,
|
|
271
|
+
providerStopReason: typeof data.stopReason === 'string' ? data.stopReason : null,
|
|
272
|
+
stopReasonSource: 'backend_response',
|
|
273
|
+
usage: data.usage === undefined ? null : usage,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
toolTraceData(call) {
|
|
278
|
+
const source = this.getEvidenceSnapshot().records.find(record => record.kind === 'observation' && record.source.toolCallId === call.id);
|
|
279
|
+
return { tool: call.name, toolCallId: call.id, args: structuredClone(call.args), status: call.status,
|
|
280
|
+
startedAt: call.startedAt?.toISOString() ?? null, completedAt: call.completedAt?.toISOString() ?? null,
|
|
281
|
+
...(source?.kind === 'observation' ? { resultReference: { observationId: source.id,
|
|
282
|
+
sha256: source.resultSha256, bytes: source.resultBytes, partial: source.partial,
|
|
283
|
+
availability: source.availability }, job: source.source.job ?? null } : {}) };
|
|
284
|
+
}
|
|
285
|
+
/** Fresh applicability is a projection; original process/source observations remain immutable. */
|
|
286
|
+
async refreshVerificationCoverage() {
|
|
287
|
+
const latest = new Map();
|
|
288
|
+
for (const record of this.getEvidenceSnapshot().records) {
|
|
289
|
+
if (record.source.verification) {
|
|
290
|
+
latest.delete(record.source.verification.receipt_id);
|
|
291
|
+
latest.set(record.source.verification.receipt_id, record.source.verification);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!latest.size)
|
|
295
|
+
return;
|
|
296
|
+
const receipts = [...latest.values()].reverse();
|
|
297
|
+
const selected = [...receipts.filter(receipt => receipt.process_outcome === 'running'),
|
|
298
|
+
...receipts.filter(receipt => receipt.process_outcome !== 'running').slice(0, 8)];
|
|
299
|
+
const coverage = [];
|
|
300
|
+
for (const receipt of selected) {
|
|
301
|
+
let admitted = !this.remoteResourceSession && receipt.source.origin === 'local';
|
|
302
|
+
if (admitted) {
|
|
303
|
+
try {
|
|
304
|
+
admitted = realpathSync(this._cwd) === receipt.source.cwd;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
admitted = false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (admitted)
|
|
311
|
+
admitted = receipt.source.files.every(file => this.policy.evaluateTool({ name: 'read_file',
|
|
312
|
+
args: { path: resolve(receipt.source.cwd, file.path) }, capabilities: [ToolCapability.FILE_READ] }).allowed);
|
|
313
|
+
coverage.push(admitted ? await projectCurrentVerification(receipt) : {
|
|
314
|
+
receipt_id: receipt.receipt_id, checked_at: new Date().toISOString(), source_id: null, applicability: 'unknown',
|
|
315
|
+
reason: 'The current host, working directory or file policy does not admit a fresh check of this source.',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const retained = new Set(this.getEvidenceSnapshot().records.flatMap(record => record.source.verification
|
|
319
|
+
? [record.source.verification.receipt_id] : []));
|
|
320
|
+
this.toolResponseStore.evidence.setVerificationCoverage(coverage.filter(row => retained.has(row.receipt_id)));
|
|
321
|
+
}
|
|
183
322
|
discoveredTools = new Set();
|
|
184
323
|
// Host-injected agent-domain tools that ship every request regardless of the
|
|
185
324
|
// deferred-tool registry. Unlike discoveredTools (session-scoped, cleared on
|
|
@@ -192,6 +331,7 @@ export class Agent {
|
|
|
192
331
|
_hasCustomSystemPrompt;
|
|
193
332
|
_authenticationManagedByBackend;
|
|
194
333
|
constructor(config = {}) {
|
|
334
|
+
this.toolResponseStore = config.toolResponseStore ?? new ToolResponseStore();
|
|
195
335
|
const ambientDiscovery = config.ambientDiscovery ?? true;
|
|
196
336
|
const defaultProvider = ambientDiscovery ? inferDefaultProvider() : 'anthropic';
|
|
197
337
|
const defaultModel = getDefaultModelForProvider(defaultProvider);
|
|
@@ -204,6 +344,7 @@ export class Agent {
|
|
|
204
344
|
apiKey,
|
|
205
345
|
apiEndpoint: config.apiEndpoint,
|
|
206
346
|
});
|
|
347
|
+
const tools = config.tools ?? (ambientDiscovery ? defaultTools : []);
|
|
207
348
|
this.config = {
|
|
208
349
|
model: config.model || defaultModel,
|
|
209
350
|
apiKey,
|
|
@@ -218,8 +359,8 @@ export class Agent {
|
|
|
218
359
|
runtimeFacts: config.runtimeFacts ?? {},
|
|
219
360
|
apiRetry: resolveApiRetrySettings({ apiRetry: config.apiRetry }),
|
|
220
361
|
llmRequestTimeoutMs: resolveLlmRequestTimeoutMs(config.llmRequestTimeoutMs, config.apiRetry?.requestTimeoutMs),
|
|
221
|
-
tools
|
|
222
|
-
systemPrompt: config.systemPrompt || this.getDefaultSystemPrompt(),
|
|
362
|
+
tools,
|
|
363
|
+
systemPrompt: config.systemPrompt || this.getDefaultSystemPrompt(tools),
|
|
223
364
|
backend,
|
|
224
365
|
ambientDiscovery,
|
|
225
366
|
policy: config.policy || new AllowAllPolicy(),
|
|
@@ -233,7 +374,11 @@ export class Agent {
|
|
|
233
374
|
this._subprocessEnv = config.subprocessEnv === undefined
|
|
234
375
|
? undefined
|
|
235
376
|
: buildUserSubprocessEnv(config.subprocessEnv);
|
|
377
|
+
this.commandPolicy = config.commandPolicy === undefined ? undefined : structuredClone(config.commandPolicy);
|
|
236
378
|
this.toolEffectObserver = config.toolEffectObserver;
|
|
379
|
+
this.onLocalFileChange = config.onLocalFileChange;
|
|
380
|
+
this.toolCheckpoint = config.toolCheckpoint;
|
|
381
|
+
this.remoteResourceSession = config.remoteResourceSession;
|
|
237
382
|
this.#conversationBinding = config.conversationBinding;
|
|
238
383
|
// Host-injected agent-domain tools always ship their schema (they're the
|
|
239
384
|
// agent's primary surface, not a corpus to search). Kept separate from
|
|
@@ -242,8 +387,16 @@ export class Agent {
|
|
|
242
387
|
this.alwaysLoadedToolNames.add(name);
|
|
243
388
|
}
|
|
244
389
|
this.backend = this.config.backend;
|
|
390
|
+
if (this.backend.getModelCapabilities?.(this.config.model)?.tools === false) {
|
|
391
|
+
this.config.tools = [];
|
|
392
|
+
this.config.autoContinue = false;
|
|
393
|
+
}
|
|
245
394
|
this.policy = this.config.policy;
|
|
246
|
-
|
|
395
|
+
const suppliedTracer = this.config.tracer;
|
|
396
|
+
this.tracer = { event: event => {
|
|
397
|
+
this.captureDiagnosticTrace(event);
|
|
398
|
+
suppliedTracer.event(event);
|
|
399
|
+
} };
|
|
247
400
|
this._cwd = this.config.cwd;
|
|
248
401
|
this._autoContinue = this.config.autoContinue;
|
|
249
402
|
this.hooks = config.hooks;
|
|
@@ -255,9 +408,25 @@ export class Agent {
|
|
|
255
408
|
this._codexEffort = config.codexEffort;
|
|
256
409
|
this._codexFastMode = config.codexFastMode ?? false;
|
|
257
410
|
this._hasCustomSystemPrompt = typeof config.systemPrompt === 'string' && config.systemPrompt.trim().length > 0;
|
|
411
|
+
this.evidenceUnsubscribe = this.toolResponseStore.evidence.onChange(() => {
|
|
412
|
+
if (this.nativeEvidenceCaptureDepth > 0)
|
|
413
|
+
return;
|
|
414
|
+
this.emit({ type: 'evidence', sessionId: this.config.sessionId, snapshot: this.getEvidenceSnapshot() });
|
|
415
|
+
});
|
|
416
|
+
const version = getVersion();
|
|
417
|
+
this.recordDiagnosticEvent('runtime', { phase: 'agent_constructed',
|
|
418
|
+
version: version === '0.0.0' ? null : version,
|
|
419
|
+
provider: this.config.runtimeFacts.provider ?? null, model: this.config.model,
|
|
420
|
+
mode: this.config.runtimeFacts.mode ?? null, mux: this.config.runtimeFacts.mux ?? null,
|
|
421
|
+
configuredTools: this.getConfiguredToolNames(),
|
|
422
|
+
runtimeCredentialsAvailable: this.config.runtimeCredentialsAvailable,
|
|
423
|
+
backendAuthenticationConfigured: this._authenticationManagedByBackend,
|
|
424
|
+
llmRequestTimeoutMs: this.config.llmRequestTimeoutMs,
|
|
425
|
+
execution: { ...this.config.execution },
|
|
426
|
+
});
|
|
258
427
|
}
|
|
259
428
|
setAutoContinue(enabled) {
|
|
260
|
-
this._autoContinue = enabled;
|
|
429
|
+
this._autoContinue = enabled && this.backend.getModelCapabilities?.(this.config.model)?.tools !== false;
|
|
261
430
|
}
|
|
262
431
|
/** Override the agent's system prompt (e.g., for simulation persona injection). */
|
|
263
432
|
setSystemPrompt(prompt) {
|
|
@@ -271,7 +440,7 @@ export class Agent {
|
|
|
271
440
|
refreshSystemPrompt() {
|
|
272
441
|
if (this._hasCustomSystemPrompt)
|
|
273
442
|
return;
|
|
274
|
-
this.config.systemPrompt = this.getDefaultSystemPrompt();
|
|
443
|
+
this.config.systemPrompt = this.getDefaultSystemPrompt(this.config.tools);
|
|
275
444
|
}
|
|
276
445
|
getAutoContinue() {
|
|
277
446
|
return this._autoContinue;
|
|
@@ -319,12 +488,12 @@ export class Agent {
|
|
|
319
488
|
getConfiguredToolNames() {
|
|
320
489
|
return this.config.tools.map((tool) => tool.definition.name);
|
|
321
490
|
}
|
|
322
|
-
getDefaultSystemPrompt() {
|
|
491
|
+
getDefaultSystemPrompt(tools) {
|
|
323
492
|
const offloadEnabled = this.toolResponseStore.getConfig().offloadEnabled;
|
|
324
493
|
const offloadSection = offloadEnabled
|
|
325
494
|
? `\nTool results older than a handful of turns or larger than ~2k tokens appear as offload placeholders (e.g. \`${describeOffloadPlaceholderFormat()}\`). Full content is stored on disk per-session; call \`recall_tool_response\` with the \`tool_use_id\` to retrieve it.
|
|
326
495
|
|
|
327
|
-
Recall
|
|
496
|
+
Recall returns bounded source bytes with \`cursor\`, \`nextCursor\`, and \`complete\`; repeat at \`nextCursor\` until the needed source is available. An acknowledgement is used only for a small result whose exact full bytes are already visible. Large original results may remain historical previews, so read the returned recall content. If a placeholder is followed by \`First lines:\` / \`Last lines:\` previews, you can often answer from those without recalling.\n`
|
|
328
497
|
: '';
|
|
329
498
|
return `You are ZTC (Zerg Terminal Client), an AI assistant that helps users interact with the Zerg continual AI system and manage local development tasks.
|
|
330
499
|
|
|
@@ -348,9 +517,9 @@ ${offloadSection}
|
|
|
348
517
|
INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow this sequence:
|
|
349
518
|
1. State your hypothesis: what do you think is happening and why? Which files or functions are involved?
|
|
350
519
|
2. Plan your reads: identify the specific locations you need to check. Prefer one larger read (100–200 lines) over many small 20-line windows — fewer tool calls, more context per call.
|
|
351
|
-
3. Read, then extract:
|
|
520
|
+
3. Read, then extract: distinguish captured observations from your interpretation. Use session_evidence to discover source IDs and record hypotheses/conclusions; corrections require supporting evidence and an explicit supersedes link. Assistant prose never replaces a tool outcome or grants user authorization. Recall the original source when a summary conflicts with it; missing evidence remains unknown.
|
|
352
521
|
4. Verify before acting: trace the full chain (caller → function → side effects → re-render) in one pass before proposing a fix. Jumping between scattered reads wastes rounds.
|
|
353
|
-
5. Never re-read a file section you already read in this session unless the file was modified since. If you need data from a prior read, check your earlier analysis first
|
|
522
|
+
5. Never re-read a file section you already read in this session unless the file was modified since. If you need data from a prior read, check your earlier analysis first.\n\n${DELETION_EVIDENCE_GUIDANCE}${tools.some(tool => tool.definition.name === 'measurement_report') ? `\n\n${MEASUREMENT_EVIDENCE_GUIDANCE}` : ''}`;
|
|
354
523
|
}
|
|
355
524
|
// Event handling
|
|
356
525
|
on(handler) {
|
|
@@ -362,6 +531,28 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
362
531
|
handler(event);
|
|
363
532
|
}
|
|
364
533
|
}
|
|
534
|
+
toolOutputObserver(toolCall, signal) {
|
|
535
|
+
const progress = createToolProgressAccumulator(toolCall.startedAt.getTime(), snapshot => {
|
|
536
|
+
if (signal?.aborted || toolCall.status !== 'running')
|
|
537
|
+
return;
|
|
538
|
+
const event = { type: 'tool_progress', toolCallId: toolCall.id, tool: toolCall.name, progress: snapshot };
|
|
539
|
+
// A failed display subscriber must neither fail the admitted tool nor
|
|
540
|
+
// prevent another subscriber from observing its informational output.
|
|
541
|
+
for (const handler of this.eventHandlers) {
|
|
542
|
+
try {
|
|
543
|
+
handler(event);
|
|
544
|
+
}
|
|
545
|
+
catch { /* informational observer only */ }
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
return {
|
|
549
|
+
onOutput: (stream, chunk) => {
|
|
550
|
+
if (!signal?.aborted && toolCall.status === 'running')
|
|
551
|
+
progress.onOutput(stream, chunk);
|
|
552
|
+
},
|
|
553
|
+
close: progress.close,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
365
556
|
nextToolEventId() {
|
|
366
557
|
this.toolEventSequence += 1;
|
|
367
558
|
return `tool_${this.toolEventSequence}`;
|
|
@@ -381,6 +572,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
381
572
|
// confusing reversal). We subtract this from the emitted context so the bar
|
|
382
573
|
// tracks durable context and only moves backward on a real reset/compaction.
|
|
383
574
|
_currentTransientOverageTokens = 0;
|
|
575
|
+
_nativeContextId;
|
|
384
576
|
formatMessages(messages) {
|
|
385
577
|
// Pre-pass: collect every tool_use_id appearing in tool_result messages,
|
|
386
578
|
// and remember the LAST batch (treated as current turn). Running the
|
|
@@ -409,17 +601,26 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
409
601
|
this._currentRequestToolIds = currentTurnToolIds;
|
|
410
602
|
if (allToolIds.length > 0) {
|
|
411
603
|
const activeModel = this.config.model || 'default';
|
|
412
|
-
const budget = this.toolResponseStore.effectiveBudget(activeModel);
|
|
604
|
+
const budget = this.toolResponseStore.effectiveBudget(activeModel, this.backend.getModelCapabilities?.(activeModel) ? this.backend.getModelCapabilities(activeModel)?.context_window ?? null : undefined);
|
|
413
605
|
this._selectorVerdict = this.toolResponseStore.selectInlineSet(allToolIds, currentTurnToolIds, budget);
|
|
414
606
|
}
|
|
415
607
|
else {
|
|
416
608
|
this._selectorVerdict = null;
|
|
609
|
+
this.toolResponseStore.selectInlineSet([], new Set(), 0);
|
|
417
610
|
}
|
|
418
611
|
// The role filter, per-message content encoding, and same-role merge live in
|
|
419
612
|
// RequestEncoder (provider-neutral, shared shape). The selector verdict +
|
|
420
613
|
// current-turn ids computed above are injected; the transient-overage count
|
|
421
614
|
// comes back as an explicit return instead of a hidden field mutation.
|
|
422
|
-
|
|
615
|
+
// Preserve opaque provider context through saved history without placing it
|
|
616
|
+
// in the display-only reasoningContent field. Copies are wire-only.
|
|
617
|
+
const wireMessages = messages.map(message => {
|
|
618
|
+
const providerReasoning = message.metadata?.providerReasoning;
|
|
619
|
+
return message.role === 'assistant' && typeof providerReasoning === 'string'
|
|
620
|
+
? { ...message, metadata: { ...message.metadata, reasoningContent: providerReasoning } }
|
|
621
|
+
: message;
|
|
622
|
+
});
|
|
623
|
+
const { messages: encoded, transientOverageTokens } = new RequestEncoder(this._encodeDeps()).encode(wireMessages);
|
|
423
624
|
this._currentTransientOverageTokens = transientOverageTokens;
|
|
424
625
|
return encoded;
|
|
425
626
|
}
|
|
@@ -428,6 +629,9 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
428
629
|
// buildContentBlocks pipeline entry point below.
|
|
429
630
|
_encodeDeps() {
|
|
430
631
|
return {
|
|
632
|
+
preserveNativeHistory: this.backend.preservesNativeHistory,
|
|
633
|
+
model: this.config.model,
|
|
634
|
+
nativeContextId: this._nativeContextId,
|
|
431
635
|
selectorVerdict: this._selectorVerdict,
|
|
432
636
|
currentTurnToolIds: this._currentRequestToolIds,
|
|
433
637
|
renderForContext: (id) => this.toolResponseStore.renderForContext(id),
|
|
@@ -635,7 +839,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
635
839
|
}
|
|
636
840
|
const missingBeforeStrip = activeToolCalls.filter(tc => !resultIds.has(tc.id));
|
|
637
841
|
const hasStoredResults = missingBeforeStrip.some(tc => Boolean(tc.error) || (tc.result !== undefined && tc.result !== null));
|
|
638
|
-
const stripIds = new Set(hasRoleToolEvidence && !hasStoredResults
|
|
842
|
+
const stripIds = new Set(hasRoleToolEvidence && !hasStoredResults && msg.metadata?.toolCheckpoint !== true
|
|
639
843
|
? missingBeforeStrip.map(tc => tc.id)
|
|
640
844
|
: []);
|
|
641
845
|
const keptToolCalls = activeToolCalls.filter(tc => !stripIds.has(tc.id));
|
|
@@ -671,7 +875,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
671
875
|
content = tc.result;
|
|
672
876
|
}
|
|
673
877
|
else {
|
|
674
|
-
content = '
|
|
878
|
+
content = 'Tool outcome unknown: the original result was not durably observed. Inspect this exact call and its existing receipts/current state before deciding whether to retry. Do not automatically repeat the original command; local interruption does not prove that an external operation stopped.';
|
|
675
879
|
}
|
|
676
880
|
if (content.length > SYNTH_RESULT_CAP) {
|
|
677
881
|
content = content.slice(0, SYNTH_RESULT_CAP) + `\n[truncated — ${content.length} chars total]`;
|
|
@@ -695,6 +899,14 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
695
899
|
return repaired;
|
|
696
900
|
}
|
|
697
901
|
buildRequest(messages, availableTools = this.config.tools) {
|
|
902
|
+
this.captureUserInstructions(messages);
|
|
903
|
+
const evidence = formatEvidenceContext(this.getEvidenceSnapshot());
|
|
904
|
+
const evidenceTurn = [...messages].reverse().find(message => message.role === 'user');
|
|
905
|
+
// Stamp each source turn once. Render the host index next to that turn
|
|
906
|
+
// without changing its text, the system prefix or prior request records.
|
|
907
|
+
if (evidence && evidenceTurn && typeof evidenceTurn.metadata?.evidenceContext !== 'string') {
|
|
908
|
+
evidenceTurn.metadata = { ...evidenceTurn.metadata, evidenceContext: evidence };
|
|
909
|
+
}
|
|
698
910
|
// Repair any orphaned tool_use blocks before building request
|
|
699
911
|
if (this.config.pinTaskHeader) {
|
|
700
912
|
const facts = describeExecutionContext({ ...this.getExecutionContext(), tools: availableTools.map(tool => tool.definition.name) });
|
|
@@ -705,22 +917,70 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
705
917
|
latestUser.metadata = { ...latestUser.metadata, executionContext: facts };
|
|
706
918
|
}
|
|
707
919
|
}
|
|
920
|
+
const currentCoverage = this.getEvidenceSnapshot().verificationCoverage;
|
|
921
|
+
if (currentCoverage?.length && evidenceTurn) {
|
|
922
|
+
const rows = [];
|
|
923
|
+
let remaining = 12 * 1024 - 512;
|
|
924
|
+
for (const projection of currentCoverage) {
|
|
925
|
+
const row = JSON.stringify(projection);
|
|
926
|
+
const size = Buffer.byteLength(row) + 1;
|
|
927
|
+
if (size > remaining)
|
|
928
|
+
continue;
|
|
929
|
+
rows.push(row);
|
|
930
|
+
remaining -= size;
|
|
931
|
+
}
|
|
932
|
+
evidenceTurn.metadata = { ...evidenceTurn.metadata,
|
|
933
|
+
verificationContext: '[Current source applicability — host observations]\n' + rows.join('\n')
|
|
934
|
+
+ `\n${currentCoverage.length - rows.length} additional applicability checks omitted. Checks are scoped and as-of checked_at. Historical process outcomes and declared purposes do not qualify edited source; matching endpoints do not prove immutable inputs. Qualification remains unverified.`,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
708
937
|
const repairedMessages = this.repairConversationHistory(messages);
|
|
709
938
|
// Facts are attached to the user/tool-result turn that observed them. Keep
|
|
710
939
|
// earlier snapshots immutable so provider threads can reuse their prefix.
|
|
711
|
-
const contextMessages =
|
|
712
|
-
const facts = message.metadata?.executionContext;
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
940
|
+
const contextMessages = repairedMessages.flatMap(message => {
|
|
941
|
+
const facts = this.config.pinTaskHeader ? message.metadata?.executionContext : undefined;
|
|
942
|
+
const observations = message.metadata?.evidenceContext;
|
|
943
|
+
const applicability = message.metadata?.verificationContext;
|
|
944
|
+
return [message, ...(typeof facts === 'string' ? [{
|
|
945
|
+
id: `${message.id}_execution`, role: 'user', content: facts, timestamp: message.timestamp,
|
|
946
|
+
metadata: { synthetic: true, hostContext: true },
|
|
947
|
+
}] : []), ...(typeof observations === 'string' ? [{
|
|
948
|
+
id: `${message.id}_evidence`, role: 'user', content: observations, timestamp: message.timestamp,
|
|
949
|
+
metadata: { synthetic: true, hostContext: true },
|
|
950
|
+
}] : []), ...(typeof applicability === 'string' ? [{
|
|
951
|
+
id: `${message.id}_verification`, role: 'user', content: applicability, timestamp: message.timestamp,
|
|
952
|
+
metadata: { synthetic: true, hostContext: true },
|
|
953
|
+
}] : [])];
|
|
954
|
+
});
|
|
955
|
+
if (this.config.pinTaskHeader && this.taskHandoff) {
|
|
956
|
+
const snapshot = this.taskHandoff.snapshot;
|
|
957
|
+
// Saved user/tool text retains conversation priority, alongside the other
|
|
958
|
+
// host observations, without changing a hosted thread's base instructions.
|
|
959
|
+
let currentUserIndex = contextMessages.length - 1;
|
|
960
|
+
for (; currentUserIndex >= 0; currentUserIndex--) {
|
|
961
|
+
const message = contextMessages[currentUserIndex];
|
|
962
|
+
if (message.role === 'user' && !message.metadata?.synthetic && !message.metadata?.hostContext
|
|
963
|
+
&& !message.metadata?.toolResult && !message.metadata?.compactionSummary)
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
// Historical context belongs before the current instruction. Never split
|
|
967
|
+
// an assistant/tool-result pair or replace the request tail with history.
|
|
968
|
+
contextMessages.splice(currentUserIndex < 0 ? 0 : currentUserIndex, 0, {
|
|
969
|
+
id: `handoff_${snapshot.source.sessionId}_${snapshot.revision}`,
|
|
970
|
+
role: 'user', content: formatTaskHandoffContext(snapshot),
|
|
971
|
+
timestamp: new Date(snapshot.source.capturedAt),
|
|
972
|
+
metadata: { synthetic: true, hostContext: true, preserveUserBoundary: true }
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
const system = this.systemWithTaskHeader(messages);
|
|
976
|
+
const tools = getRequestToolDefinitions(availableTools, this.discoveredTools, this.config.deferToolSchemas !== false, this.alwaysLoadedToolNames);
|
|
977
|
+
this._nativeContextId = this.backend.preservesNativeHistory ? nativeHistoryContextId(system, tools) : undefined;
|
|
718
978
|
return {
|
|
719
979
|
model: this.config.model,
|
|
720
980
|
maxTokens: this.config.maxTokens,
|
|
721
|
-
system
|
|
981
|
+
system,
|
|
722
982
|
messages: this.formatMessages(contextMessages),
|
|
723
|
-
tools
|
|
983
|
+
tools,
|
|
724
984
|
// A container pin identifies a cached prompt prefix, so the transport has to
|
|
725
985
|
// know when this conversation is no longer sending the prefix its container
|
|
726
986
|
// was warmed for. That is compaction, and only this layer can see it.
|
|
@@ -779,12 +1039,16 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
779
1039
|
* visible text, so an empty or whitespace-only response emits no stream
|
|
780
1040
|
* events (EPO-622), matching emitStreamText.
|
|
781
1041
|
*/
|
|
782
|
-
async streamAndAssemble(request, signal, onAnswerOutput) {
|
|
1042
|
+
async streamAndAssemble(request, signal, onAnswerOutput, onUsage) {
|
|
783
1043
|
let text = '';
|
|
784
1044
|
let reasoning = '';
|
|
785
1045
|
const toolUses = [];
|
|
1046
|
+
const nativeContent = [];
|
|
786
1047
|
let usage;
|
|
1048
|
+
let reportedUsage;
|
|
1049
|
+
let costUsd;
|
|
787
1050
|
let stopReason = 'end_turn';
|
|
1051
|
+
let stopDetails;
|
|
788
1052
|
let streamOpen = false;
|
|
789
1053
|
// Text that arrived but is still whitespace only. Providers open a turn
|
|
790
1054
|
// this way — DeepSeek streams "\n\n" before its tool calls — and each delta
|
|
@@ -802,14 +1066,22 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
802
1066
|
switch (ev.type) {
|
|
803
1067
|
case 'heartbeat':
|
|
804
1068
|
break;
|
|
1069
|
+
case 'content_block':
|
|
1070
|
+
nativeContent.push(ev.block);
|
|
1071
|
+
break;
|
|
805
1072
|
case 'reasoning_delta':
|
|
806
1073
|
if (ev.text) {
|
|
807
1074
|
reasoning += ev.text;
|
|
808
|
-
this.
|
|
1075
|
+
if (!this.remoteResourceSession)
|
|
1076
|
+
this.emit({ type: 'reasoning_delta', content: ev.text });
|
|
809
1077
|
}
|
|
810
1078
|
break;
|
|
811
1079
|
case 'text_delta':
|
|
812
1080
|
if (ev.text) {
|
|
1081
|
+
if (this.remoteResourceSession) {
|
|
1082
|
+
text += ev.text;
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
813
1085
|
if (streamOpen) {
|
|
814
1086
|
text += ev.text;
|
|
815
1087
|
onAnswerOutput();
|
|
@@ -849,11 +1121,31 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
849
1121
|
}
|
|
850
1122
|
case 'tool_use_end':
|
|
851
1123
|
break;
|
|
852
|
-
case 'usage':
|
|
1124
|
+
case 'usage': {
|
|
853
1125
|
usage = ev.usage;
|
|
1126
|
+
// Usage snapshots are cumulative for this request. Report increments
|
|
1127
|
+
// as they arrive so cancellation cannot discard already billed work.
|
|
1128
|
+
onUsage?.({
|
|
1129
|
+
...usage,
|
|
1130
|
+
inputTokens: Math.max(0, usage.inputTokens - (reportedUsage?.inputTokens ?? 0)),
|
|
1131
|
+
outputTokens: Math.max(0, usage.outputTokens - (reportedUsage?.outputTokens ?? 0)),
|
|
1132
|
+
totalTokens: Math.max(0, usage.totalTokens - (reportedUsage?.totalTokens ?? 0)),
|
|
1133
|
+
});
|
|
1134
|
+
reportedUsage = {
|
|
1135
|
+
...usage,
|
|
1136
|
+
inputTokens: Math.max(usage.inputTokens, reportedUsage?.inputTokens ?? 0),
|
|
1137
|
+
outputTokens: Math.max(usage.outputTokens, reportedUsage?.outputTokens ?? 0),
|
|
1138
|
+
totalTokens: Math.max(usage.totalTokens, reportedUsage?.totalTokens ?? 0),
|
|
1139
|
+
};
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case 'cost':
|
|
1143
|
+
if (Number.isFinite(ev.costUsd) && ev.costUsd >= 0)
|
|
1144
|
+
costUsd = ev.costUsd;
|
|
854
1145
|
break;
|
|
855
1146
|
case 'done':
|
|
856
1147
|
stopReason = ev.stopReason;
|
|
1148
|
+
stopDetails = ev.stopDetails;
|
|
857
1149
|
break;
|
|
858
1150
|
}
|
|
859
1151
|
}
|
|
@@ -879,7 +1171,9 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
879
1171
|
}
|
|
880
1172
|
content.push({ type: 'tool_use', id: tu.id, name: tu.name, input });
|
|
881
1173
|
}
|
|
882
|
-
return { content
|
|
1174
|
+
return { content: nativeContent.length > 0 ? nativeContent : content, stopReason,
|
|
1175
|
+
...(stopDetails ? { stopDetails } : {}), reasoning: reasoning || undefined, usage,
|
|
1176
|
+
...(costUsd === undefined ? {} : { costUsd }) };
|
|
883
1177
|
}
|
|
884
1178
|
emitReasoningText(text) {
|
|
885
1179
|
if (!text || !text.trim()) {
|
|
@@ -894,7 +1188,28 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
894
1188
|
* Server). The action has already run, so a command emits tool_start
|
|
895
1189
|
* immediately followed by tool_end.
|
|
896
1190
|
*/
|
|
897
|
-
emitObservation(observation) {
|
|
1191
|
+
async emitObservation(observation, nativeTurnId, canPublish) {
|
|
1192
|
+
// Provider observations have no host-managed operation receipt.
|
|
1193
|
+
if (this.remoteResourceSession)
|
|
1194
|
+
return;
|
|
1195
|
+
if (observation.kind !== 'reasoning') {
|
|
1196
|
+
const captured = captureNativeReport(observation, { provider: this.config.runtimeFacts.provider ?? 'unknown-provider', turnId: nativeTurnId });
|
|
1197
|
+
if (!captured)
|
|
1198
|
+
return;
|
|
1199
|
+
this.nativeEvidenceCaptureDepth++;
|
|
1200
|
+
try {
|
|
1201
|
+
this.toolResponseStore.put(captured.handle, `provider_${observation.kind}`, captured.content, captured.partial, { source: captured.source, isError: captured.isError });
|
|
1202
|
+
await this.toolResponseStore.flush();
|
|
1203
|
+
}
|
|
1204
|
+
finally {
|
|
1205
|
+
this.nativeEvidenceCaptureDepth--;
|
|
1206
|
+
}
|
|
1207
|
+
this.emit({ type: 'evidence', sessionId: this.config.sessionId, snapshot: this.getEvidenceSnapshot() });
|
|
1208
|
+
}
|
|
1209
|
+
// Retain admitted original source facts even when persistence finishes
|
|
1210
|
+
// after cancellation; visible progress belongs to its producing request.
|
|
1211
|
+
if (!canPublish())
|
|
1212
|
+
return;
|
|
898
1213
|
switch (observation.kind) {
|
|
899
1214
|
case 'reasoning':
|
|
900
1215
|
this.emitReasoningText(observation.text);
|
|
@@ -944,6 +1259,17 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
944
1259
|
* genuinely multi-task long-lived agents is deferred (plan A10).
|
|
945
1260
|
*/
|
|
946
1261
|
_taskHeader = null;
|
|
1262
|
+
taskHandoff = null;
|
|
1263
|
+
/** Only a host-selected checkpoint may supply historical task context. */
|
|
1264
|
+
setTaskHandoff(projection) {
|
|
1265
|
+
const admitted = projection === null ? null : parseTaskHandoffProjection(projection);
|
|
1266
|
+
if (projection !== null && !admitted)
|
|
1267
|
+
throw new TypeError('Invalid task handoff projection');
|
|
1268
|
+
if (admitted && this.config.sessionId && admitted.snapshot.source.sessionId !== this.config.sessionId) {
|
|
1269
|
+
throw new TypeError('Task handoff belongs to a different selected session');
|
|
1270
|
+
}
|
|
1271
|
+
this.taskHandoff = admitted;
|
|
1272
|
+
}
|
|
947
1273
|
/**
|
|
948
1274
|
* Drop the explicitly pinned task for the next conversation. Wired into the /clear-only path (clearTransientState), never into
|
|
949
1275
|
* /compact — the pin must survive compaction (that is the whole point of R7).
|
|
@@ -971,6 +1297,10 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
971
1297
|
/** Append the pinned task header (R7) to the static system prompt. */
|
|
972
1298
|
getExecutionContext() {
|
|
973
1299
|
return {
|
|
1300
|
+
permissions: this.config.runtimeFacts.permissions,
|
|
1301
|
+
...(this.remoteResourceSession ? { remoteResourceGuard: {
|
|
1302
|
+
ownerId: this.remoteResourceSession.ownerId, resources: this.remoteResourceSession.resources.map(resource => resource.id),
|
|
1303
|
+
} } : {}),
|
|
974
1304
|
mode: this.config.runtimeFacts.mode ?? 'normal', cwd: this._cwd,
|
|
975
1305
|
tools: this.getConfiguredToolNames(), mux: this.config.runtimeFacts.mux ?? false,
|
|
976
1306
|
configScope: this.config.runtimeFacts.configScope ?? 'host configuration',
|
|
@@ -985,26 +1315,114 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
985
1315
|
return this.config.systemPrompt;
|
|
986
1316
|
return pinnedTaskSystem(this.config.systemPrompt, this._taskHeader);
|
|
987
1317
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1318
|
+
async persistToolCheckpoint(phase, toolCallId, messages, handler = this.toolCheckpoint) {
|
|
1319
|
+
if (this.toolCheckpointFailure)
|
|
1320
|
+
throw this.toolCheckpointFailure;
|
|
1321
|
+
if (!handler) {
|
|
1322
|
+
if (phase === 'terminal')
|
|
1323
|
+
await this.toolResponseStore.flush();
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
// Capture at enqueue time: a later sibling completion or a host retaining
|
|
1327
|
+
// this payload cannot rewrite the arguments admitted for this invocation.
|
|
1328
|
+
const checkpoint = { sessionId: this.config.sessionId || 'ztc-session', phase,
|
|
1329
|
+
toolCallId, messages: structuredClone(messages) };
|
|
1330
|
+
const pending = this.toolCheckpointTail.then(async () => {
|
|
1331
|
+
if (this.toolCheckpointFailure)
|
|
1332
|
+
throw this.toolCheckpointFailure;
|
|
1333
|
+
try {
|
|
1334
|
+
if (phase === 'terminal')
|
|
1335
|
+
await this.toolResponseStore.flush();
|
|
1336
|
+
await handler(structuredClone(checkpoint));
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1339
|
+
this.failedToolCheckpoint = { checkpoint, handler };
|
|
1340
|
+
this.toolCheckpointFailure = this.checkpointPersistenceError(checkpoint, error);
|
|
1341
|
+
throw this.toolCheckpointFailure;
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
this.toolCheckpointTail = pending.catch(() => { });
|
|
1345
|
+
await pending;
|
|
1346
|
+
}
|
|
1347
|
+
checkpointPersistenceError(checkpoint, error) {
|
|
1348
|
+
return new AgentError(`Unable to persist ${checkpoint.phase === 'terminal' ? 'terminal outcome' : 'call intent'} checkpoint for ${checkpoint.toolCallId}: ${error instanceof Error ? error.message : String(error)}. Stop this turn and inspect the original checkpoint and existing receipts before deciding whether to repeat any work.`, 'TOOL_CHECKPOINT_FAILED');
|
|
1349
|
+
}
|
|
1350
|
+
/** Retry persistence only, before a new public invocation can admit effects. */
|
|
1351
|
+
async recoverToolCheckpoint() {
|
|
1352
|
+
await this.toolCheckpointTail;
|
|
1353
|
+
if (!this.toolCheckpointFailure)
|
|
1354
|
+
return;
|
|
1355
|
+
const failed = this.failedToolCheckpoint;
|
|
1356
|
+
if (!failed)
|
|
1357
|
+
throw this.toolCheckpointFailure;
|
|
1358
|
+
const pending = this.toolCheckpointTail.then(async () => {
|
|
1359
|
+
// Another entrant can have repaired this exact record while we queued.
|
|
1360
|
+
if (this.failedToolCheckpoint !== failed)
|
|
1361
|
+
return;
|
|
1362
|
+
try {
|
|
1363
|
+
if (failed.checkpoint.phase === 'terminal')
|
|
1364
|
+
await this.toolResponseStore.flush();
|
|
1365
|
+
await failed.handler(structuredClone(failed.checkpoint));
|
|
1366
|
+
this.failedToolCheckpoint = null;
|
|
1367
|
+
this.toolCheckpointFailure = null;
|
|
1368
|
+
}
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
this.toolCheckpointFailure = this.checkpointPersistenceError(failed.checkpoint, error);
|
|
1371
|
+
throw this.toolCheckpointFailure;
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
this.toolCheckpointTail = pending.catch(() => { });
|
|
1375
|
+
await pending;
|
|
1376
|
+
}
|
|
1377
|
+
/** Execute a host-bound or standalone turn through the same core loop. */
|
|
1378
|
+
async run(messages, signal, takeSteering, toolCheckpoint = this.toolCheckpoint, steeringAdmission, onUsage) {
|
|
1379
|
+
const diagnosticTurn = { id: `turn_${randomUUID()}`, iterations: 0 };
|
|
1380
|
+
let result;
|
|
1381
|
+
let failure;
|
|
994
1382
|
const onInterrupt = () => { if (this.config.execution.interruptBackgroundJobs)
|
|
995
1383
|
void interruptCommandJobs(this.config.sessionId); };
|
|
996
1384
|
signal?.addEventListener('abort', onInterrupt, { once: true });
|
|
997
1385
|
try {
|
|
998
|
-
|
|
1386
|
+
await this.recoverToolCheckpoint();
|
|
1387
|
+
await this.nativeEvidencePending;
|
|
1388
|
+
if (this.nativeEvidenceFailure)
|
|
1389
|
+
throw this.nativeEvidenceFailure;
|
|
1390
|
+
result = await this.runBound(messages, signal, takeSteering, toolCheckpoint, steeringAdmission, diagnosticTurn, onUsage);
|
|
1391
|
+
return result;
|
|
1392
|
+
}
|
|
1393
|
+
catch (error) {
|
|
1394
|
+
failure = error;
|
|
1395
|
+
throw error;
|
|
999
1396
|
}
|
|
1000
1397
|
finally {
|
|
1001
1398
|
signal?.removeEventListener('abort', onInterrupt);
|
|
1399
|
+
try {
|
|
1400
|
+
await this.nativeEvidencePending;
|
|
1401
|
+
if (this.nativeEvidenceFailure)
|
|
1402
|
+
throw this.nativeEvidenceFailure;
|
|
1403
|
+
await this.toolResponseStore.flush();
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
failure = error;
|
|
1407
|
+
throw error;
|
|
1408
|
+
}
|
|
1409
|
+
finally {
|
|
1410
|
+
const errorCode = failure instanceof AgentError ? failure.code ?? null : null;
|
|
1411
|
+
const reason = failure !== undefined
|
|
1412
|
+
? signal?.aborted || errorCode === 'CANCELLED' ? 'cancelled' : 'error'
|
|
1413
|
+
: result?.stopReason ?? 'error';
|
|
1414
|
+
const data = { reason, errorCode, iterations: diagnosticTurn.iterations };
|
|
1415
|
+
// This outcome belongs to the invocation, even after a UI cancels its
|
|
1416
|
+
// event subscription or a hosted driver fails before the model loop.
|
|
1417
|
+
this.recordDiagnosticEvent('turn_end', { ...data, turnId: diagnosticTurn.id });
|
|
1418
|
+
this.tracer.event({ type: 'turn_end', timestamp: new Date(), data });
|
|
1419
|
+
}
|
|
1002
1420
|
}
|
|
1003
1421
|
}
|
|
1004
|
-
async runBound(messages, signal, takeSteering) {
|
|
1422
|
+
async runBound(messages, signal, takeSteering, toolCheckpoint = this.toolCheckpoint, steeringAdmission, diagnosticTurn, onUsage) {
|
|
1005
1423
|
const binding = this.#conversationBinding;
|
|
1006
1424
|
if (!binding)
|
|
1007
|
-
return this.runCore(messages, signal, takeSteering);
|
|
1425
|
+
return this.runCore(messages, signal, takeSteering, toolCheckpoint, steeringAdmission, diagnosticTurn, onUsage);
|
|
1008
1426
|
const userMessage = [...messages].reverse().find((message) => message.role === 'user'
|
|
1009
1427
|
&& !message.metadata?.synthetic
|
|
1010
1428
|
&& !message.metadata?.toolResult);
|
|
@@ -1036,14 +1454,15 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1036
1454
|
// The host driver publishes only the initial user and final assistant
|
|
1037
1455
|
// for this turn. Leave busy input pending for a new hosted turn so the
|
|
1038
1456
|
// authoritative conversation cannot silently omit it.
|
|
1039
|
-
const result = await this.runCore(workingMessages, signal);
|
|
1457
|
+
const result = await this.runCore(workingMessages, signal, undefined, toolCheckpoint, undefined, diagnosticTurn, onUsage);
|
|
1040
1458
|
const assistantMessage = {
|
|
1041
1459
|
id: `host_assistant_${randomUUID()}`,
|
|
1042
1460
|
role: 'assistant',
|
|
1043
1461
|
content: result.content,
|
|
1462
|
+
...(result.nativeContent ? { nativeContent: result.nativeContent, nativeModel: result.nativeModel, nativeContextId: result.nativeContextId } : {}),
|
|
1044
1463
|
timestamp: new Date(),
|
|
1045
1464
|
...(result.toolCalls.length > 0 ? { toolCalls: result.toolCalls } : {}),
|
|
1046
|
-
...(
|
|
1465
|
+
metadata: { ...(this.config.runtimeFacts.mode === 'plan' ? { planMode: true } : {}), ...(result.reasoning ? { reasoningContent: result.reasoning } : {}) },
|
|
1047
1466
|
};
|
|
1048
1467
|
const receipt = await binding.driver.publish({
|
|
1049
1468
|
turn,
|
|
@@ -1082,10 +1501,18 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1082
1501
|
}
|
|
1083
1502
|
}
|
|
1084
1503
|
// Main execution loop shared by hosted and standalone turns.
|
|
1085
|
-
async runCore(messages, signal, takeSteering) {
|
|
1504
|
+
async runCore(messages, signal, takeSteering, toolCheckpoint = this.toolCheckpoint, steeringAdmission, diagnosticTurn, onUsage) {
|
|
1086
1505
|
if (!this.hasApiKey()) {
|
|
1087
1506
|
throw new AgentError('No API key configured. Use /config key <key> to set one.', 'NO_API_KEY');
|
|
1088
1507
|
}
|
|
1508
|
+
for (const message of messages) {
|
|
1509
|
+
if (message.role !== 'assistant')
|
|
1510
|
+
continue;
|
|
1511
|
+
for (const call of message.toolCalls ?? []) {
|
|
1512
|
+
if (!this.recordedToolCalls.has(call.id))
|
|
1513
|
+
this.recordedToolCalls.set(call.id, structuredClone(call));
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1089
1516
|
const allToolCalls = [];
|
|
1090
1517
|
const turnMessages = [];
|
|
1091
1518
|
let iterations = 0;
|
|
@@ -1098,6 +1525,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1098
1525
|
// "as-is, including any reasoning_content"; without this the trace survives
|
|
1099
1526
|
// only inside one run() and every later user turn sends a stripped history.
|
|
1100
1527
|
let finalReasoning;
|
|
1528
|
+
let finalNativeContent;
|
|
1101
1529
|
let lastToolTurnText = '';
|
|
1102
1530
|
let totalUsage = null;
|
|
1103
1531
|
let runError = null;
|
|
@@ -1118,11 +1546,14 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1118
1546
|
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1119
1547
|
}
|
|
1120
1548
|
iterations++;
|
|
1549
|
+
if (diagnosticTurn)
|
|
1550
|
+
diagnosticTurn.iterations = iterations;
|
|
1121
1551
|
this.emit({ type: 'thinking_start' });
|
|
1122
1552
|
try {
|
|
1123
1553
|
// Call the API
|
|
1124
1554
|
const availability = await Promise.all(this.config.tools.map(tool => tool.isAvailable?.({ cwd: this._cwd }) ?? true));
|
|
1125
1555
|
const availableTools = this.config.tools.filter((_tool, index) => availability[index]);
|
|
1556
|
+
await this.refreshVerificationCoverage();
|
|
1126
1557
|
const request = this.buildRequest(messages, availableTools);
|
|
1127
1558
|
request.signal = signal;
|
|
1128
1559
|
request.onMetrics = metrics => {
|
|
@@ -1131,9 +1562,55 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1131
1562
|
};
|
|
1132
1563
|
// Providers that run their own tool loop (Codex App Server) report each
|
|
1133
1564
|
// command/reasoning/file action here so it shows in the mirror/TUI.
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1565
|
+
const nativeTurnId = `native_request_${randomUUID()}`;
|
|
1566
|
+
const captureNativeObservation = (observation, attemptCanPublish) => {
|
|
1567
|
+
const capture = this.emitObservation(observation, nativeTurnId, attemptCanPublish);
|
|
1568
|
+
this.nativeEvidencePending = Promise.all([this.nativeEvidencePending, capture]).then(() => { }).catch(error => {
|
|
1569
|
+
this.nativeEvidenceFailure ??= new AgentError(`Unable to retain native provider report: ${error instanceof Error ? error.message : String(error)}. Inspect existing reports before repeating work.`, 'NATIVE_EVIDENCE_FAILED');
|
|
1570
|
+
});
|
|
1571
|
+
return capture;
|
|
1572
|
+
};
|
|
1573
|
+
this.tracer.event({ type: 'api_request', timestamp: new Date(), data: { turnId: diagnosticTurn?.id ?? null,
|
|
1574
|
+
model: request.model, maxTokens: request.maxTokens, system: request.system, messages: request.messages, tools: request.tools } });
|
|
1575
|
+
// One identity covers this logical call, including its internal transport retries.
|
|
1576
|
+
// Selected account pointers are not evidence of which account served the request.
|
|
1577
|
+
const identity = { id: `usage_${randomUUID()}`, sessionId: this.config.sessionId,
|
|
1578
|
+
projectHash: createHash('sha256').update(this._cwd).digest('hex').slice(0, 12),
|
|
1579
|
+
provider: this.config.runtimeFacts.provider ?? null, model: request.model, accountId: null };
|
|
1580
|
+
const retainReturnedAccounting = (response) => {
|
|
1581
|
+
const rawContent = response.content;
|
|
1582
|
+
const accountingResponse = { ...response, content: Array.isArray(rawContent) ? rawContent
|
|
1583
|
+
: typeof rawContent === 'string' && rawContent ? [{ type: 'text', text: rawContent }] : [] };
|
|
1584
|
+
const usage = response.usage ?? this.estimateUsage(request, accountingResponse);
|
|
1585
|
+
this.retainUsageReceipt({ ...identity, observedAt: new Date().toISOString(),
|
|
1586
|
+
source: usage.estimated === true ? 'estimated' : 'reported', usage,
|
|
1587
|
+
costUsd: typeof response.costUsd === 'number' && Number.isFinite(response.costUsd) && response.costUsd >= 0
|
|
1588
|
+
? response.costUsd : null });
|
|
1589
|
+
return usage;
|
|
1590
|
+
};
|
|
1591
|
+
const requestUsage = onUsage?.forRequest?.() ?? onUsage;
|
|
1592
|
+
let response;
|
|
1593
|
+
let returnedResponse;
|
|
1594
|
+
try {
|
|
1595
|
+
response = await this.callAPI(request, captureNativeObservation, signal, { messages, turnMessages, toolCalls: allToolCalls, handler: toolCheckpoint }, observed => { returnedResponse = observed; }, requestUsage);
|
|
1596
|
+
}
|
|
1597
|
+
catch (error) {
|
|
1598
|
+
// A success metrics observer may cancel synchronously just before the
|
|
1599
|
+
// completed response promise wins the race. Its returned facts remain known.
|
|
1600
|
+
if (returnedResponse)
|
|
1601
|
+
retainReturnedAccounting(returnedResponse);
|
|
1602
|
+
else
|
|
1603
|
+
this.retainUsageReceipt({ ...identity, observedAt: new Date().toISOString(),
|
|
1604
|
+
source: 'unavailable', usage: null, costUsd: null });
|
|
1605
|
+
throw error;
|
|
1606
|
+
}
|
|
1607
|
+
// Retain returned accounting before a later cancellation/checkpoint guard can discard the answer.
|
|
1608
|
+
const usage = retainReturnedAccounting(response);
|
|
1609
|
+
await this.nativeEvidencePending;
|
|
1610
|
+
if (this.nativeEvidenceFailure)
|
|
1611
|
+
throw this.nativeEvidenceFailure;
|
|
1612
|
+
if (this.toolCheckpointFailure)
|
|
1613
|
+
throw this.toolCheckpointFailure;
|
|
1137
1614
|
if (signal?.aborted) {
|
|
1138
1615
|
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1139
1616
|
}
|
|
@@ -1144,16 +1621,19 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1144
1621
|
? [{ type: 'text', text: raw }]
|
|
1145
1622
|
: [];
|
|
1146
1623
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1624
|
+
if (!this.backend.preservesNativeHistory)
|
|
1625
|
+
response.content = normalizeRepeatedResponseContent(response.content);
|
|
1626
|
+
this.tracer.event({ type: 'api_response', timestamp: new Date(), data: { turnId: diagnosticTurn?.id ?? null,
|
|
1627
|
+
model: request.model, stopReason: response.stopReason, stopReasonSource: 'backend_response', content: response.content, usage: response.usage } });
|
|
1149
1628
|
// Forward ZRouter prompt echo if present (superuser-authorized by the server)
|
|
1150
1629
|
if (response.promptEcho) {
|
|
1151
1630
|
this.tracer.event({ type: 'prompt_echo', timestamp: new Date(), data: response.promptEcho });
|
|
1152
1631
|
}
|
|
1153
1632
|
// Skip when the response was streamed live (reasoning_delta already emitted).
|
|
1154
|
-
if (!this._lastResponseStreamed)
|
|
1633
|
+
if (!this.remoteResourceSession && !this._lastResponseStreamed)
|
|
1155
1634
|
this.emitReasoningText(response.reasoning);
|
|
1156
|
-
|
|
1635
|
+
if (!response.usage)
|
|
1636
|
+
requestUsage?.(usage);
|
|
1157
1637
|
totalUsage = mergeTokenUsage(totalUsage, usage);
|
|
1158
1638
|
// Durable context = real input_tokens minus the current-turn tool-result
|
|
1159
1639
|
// bytes that get stubbed next turn. Without this the bar spikes on a big
|
|
@@ -1173,12 +1653,59 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1173
1653
|
if (isIncompleteModelResponse(response.stopReason)) {
|
|
1174
1654
|
throw new AgentError(incompleteModelResponseMessage(response.stopReason, request.maxTokens, response.usage?.outputTokens), INCOMPLETE_MODEL_RESPONSE_CODE);
|
|
1175
1655
|
}
|
|
1656
|
+
if (response.stopReason === 'refusal') {
|
|
1657
|
+
throw new AgentError('The model declined this request.', 'MODEL_REFUSAL');
|
|
1658
|
+
}
|
|
1176
1659
|
// Check for tool use
|
|
1177
|
-
const
|
|
1660
|
+
const requestedToolBlocks = response.content.filter(this.isToolUseBlock);
|
|
1661
|
+
const requestedIds = new Set();
|
|
1662
|
+
for (const block of requestedToolBlocks) {
|
|
1663
|
+
if (requestedIds.has(block.id)) {
|
|
1664
|
+
throw new AgentError(`Provider response contains duplicate tool invocation ID ${block.id}; no call in this batch was executed.`, 'DUPLICATE_TOOL_CALL_ID');
|
|
1665
|
+
}
|
|
1666
|
+
requestedIds.add(block.id);
|
|
1667
|
+
}
|
|
1668
|
+
// Provider IDs identify calls within this response, not operations for
|
|
1669
|
+
// the lifetime of the Agent. Opaque IDs outside the store's bounded
|
|
1670
|
+
// filename alphabet and reused IDs need a fresh canonical identity for
|
|
1671
|
+
// transcript pairing, receipts and immutable stored result bytes.
|
|
1672
|
+
// Explicit host executeHarnessToolCall retries retain their own replay
|
|
1673
|
+
// contract; provider response reuse must never replay those outcomes.
|
|
1674
|
+
const occupiedIds = new Set([...this.recordedToolCalls.keys(), ...requestedIds]);
|
|
1675
|
+
const toolUseBlocks = [];
|
|
1676
|
+
for (const block of requestedToolBlocks) {
|
|
1677
|
+
if (/^[A-Za-z0-9_-]{1,256}$/.test(block.id)
|
|
1678
|
+
&& !this.recordedToolCalls.has(block.id) && !await this.hasStoredToolIdentity(block.id)) {
|
|
1679
|
+
toolUseBlocks.push(block);
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
let id;
|
|
1683
|
+
let identityAttempts = 0;
|
|
1684
|
+
do {
|
|
1685
|
+
if (++identityAttempts > 16) {
|
|
1686
|
+
throw new AgentError('Unable to allocate a fresh tool invocation identity. Inspect retained source availability before retrying.', 'TOOL_CALL_IDENTITY_UNAVAILABLE');
|
|
1687
|
+
}
|
|
1688
|
+
id = `tool_${randomUUID()}`;
|
|
1689
|
+
} while (occupiedIds.has(id) || await this.hasStoredToolIdentity(id));
|
|
1690
|
+
occupiedIds.add(id);
|
|
1691
|
+
toolUseBlocks.push({ ...block, id });
|
|
1692
|
+
}
|
|
1693
|
+
if (this.backend.preservesNativeHistory && toolUseBlocks.length > 0) {
|
|
1694
|
+
// Native replay keeps opaque signed blocks unchanged. Its tool-use
|
|
1695
|
+
// IDs still need to pair with the canonical identities admitted above.
|
|
1696
|
+
const canonicalIds = new Map(requestedToolBlocks.map((block, index) => [block.id, toolUseBlocks[index].id]));
|
|
1697
|
+
response.content = response.content.map(block => this.isToolUseBlock(block)
|
|
1698
|
+
? { ...block, id: canonicalIds.get(block.id) } : block);
|
|
1699
|
+
}
|
|
1178
1700
|
if (toolUseBlocks.length === 0) {
|
|
1701
|
+
if (this.backend.preservesNativeHistory)
|
|
1702
|
+
finalNativeContent = response.content;
|
|
1179
1703
|
// No tools, extract text
|
|
1180
1704
|
const textBlocks = response.content.filter(this.isTextBlock);
|
|
1181
|
-
const iterationText =
|
|
1705
|
+
const iterationText = this.remoteResourceSession
|
|
1706
|
+
? allToolCalls.filter(call => call.name === 'remote_resource' && call.status === 'complete')
|
|
1707
|
+
.flatMap(call => remoteDeletionReport(call.result ?? '') ?? []).join('\n') || NO_VERIFIED_DELETION_REPORT
|
|
1708
|
+
: textBlocks.map(b => b.text).join('\n');
|
|
1182
1709
|
// Last non-empty text wins. An empty no-tool iteration (e.g. the
|
|
1183
1710
|
// follow-up turn after an auto-continue prompt) must NOT clobber text
|
|
1184
1711
|
// an earlier iteration already produced and streamed — otherwise run()
|
|
@@ -1187,16 +1714,49 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1187
1714
|
// (ZTC-28). Mirrors the tool-branch lastToolTurnText guard below.
|
|
1188
1715
|
if (iterationText.trim()) {
|
|
1189
1716
|
finalContent = iterationText;
|
|
1717
|
+
finalReasoning = this.remoteResourceSession ? undefined : response.reasoning;
|
|
1718
|
+
}
|
|
1719
|
+
else if (!this.remoteResourceSession && response.reasoning?.trim()) {
|
|
1720
|
+
// Reasoning-only output is observable activity too. Keep it without
|
|
1721
|
+
// inventing text or losing earlier text on an empty follow-up.
|
|
1190
1722
|
finalReasoning = response.reasoning;
|
|
1191
1723
|
}
|
|
1192
1724
|
// Skip the buffered pacing when the text was already streamed live.
|
|
1193
|
-
if (!this._lastResponseStreamed)
|
|
1725
|
+
if (this.remoteResourceSession ? lastToolTurnText !== iterationText : !this._lastResponseStreamed)
|
|
1194
1726
|
await this.emitStreamText(iterationText, signal);
|
|
1727
|
+
// Deliver busy input before automatic continuation or ending this turn.
|
|
1728
|
+
// At the iteration cap leave the queue intact for the renderer's next turn.
|
|
1729
|
+
if (iterations < this.config.maxIterations) {
|
|
1730
|
+
const steering = takeSteering?.() ?? [];
|
|
1731
|
+
if (steering.length > 0) {
|
|
1732
|
+
if (iterationText.trim()) {
|
|
1733
|
+
const answer = {
|
|
1734
|
+
id: `assistant_${Date.now()}_${iterations}`, role: 'assistant', content: iterationText, timestamp: new Date(),
|
|
1735
|
+
turn: requestUsage?.getTurn?.(),
|
|
1736
|
+
metadata: { ...(this.config.runtimeFacts.mode === 'plan' ? { planMode: true } : {}), ...(response.reasoning ? { reasoningContent: response.reasoning } : {}) },
|
|
1737
|
+
};
|
|
1738
|
+
messages.push(answer);
|
|
1739
|
+
turnMessages.push(answer);
|
|
1740
|
+
}
|
|
1741
|
+
messages.push(...steering);
|
|
1742
|
+
turnMessages.push(...steering);
|
|
1743
|
+
this.emit({ type: 'history_checkpoint', messages: [...turnMessages] });
|
|
1744
|
+
if (signal?.aborted)
|
|
1745
|
+
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1746
|
+
await steeringAdmission?.({ messages: [...turnMessages], sourceMessageIds: steering.map(message => message.id),
|
|
1747
|
+
deliveryState: 'unknown', signal });
|
|
1748
|
+
if (signal?.aborted)
|
|
1749
|
+
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1750
|
+
finalContent = '';
|
|
1751
|
+
finalReasoning = undefined;
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1195
1755
|
// Check if we should auto-continue. Bounded: every continuation is a
|
|
1196
1756
|
// full-context request, so a model that keeps narrating an intention
|
|
1197
1757
|
// instead of calling a tool must not be able to spend maxIterations
|
|
1198
1758
|
// round trips on it. The budget is reset by any executed tool.
|
|
1199
|
-
if (this.shouldContinue(iterationText)
|
|
1759
|
+
if (!this.remoteResourceSession && this.shouldContinue(iterationText)
|
|
1200
1760
|
&& this._consecutiveAutoContinues < this.config.maxConsecutiveAutoContinues) {
|
|
1201
1761
|
this._consecutiveAutoContinues++;
|
|
1202
1762
|
const acTodos = getLatestTodos();
|
|
@@ -1214,8 +1774,9 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1214
1774
|
id: `assistant_${Date.now()}`,
|
|
1215
1775
|
role: 'assistant',
|
|
1216
1776
|
content: iterationText,
|
|
1777
|
+
...(this.backend.preservesNativeHistory ? { nativeContent: structuredClone(response.content), nativeModel: this.config.model, nativeContextId: this._nativeContextId } : {}),
|
|
1217
1778
|
timestamp: new Date(),
|
|
1218
|
-
...(
|
|
1779
|
+
metadata: { ...(this.config.runtimeFacts.mode === 'plan' ? { planMode: true } : {}), ...(response.reasoning ? { reasoningContent: response.reasoning } : {}) },
|
|
1219
1780
|
});
|
|
1220
1781
|
// Add auto-continue prompt as user message
|
|
1221
1782
|
messages.push({
|
|
@@ -1229,42 +1790,92 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1229
1790
|
stopReason = 'complete';
|
|
1230
1791
|
break;
|
|
1231
1792
|
}
|
|
1232
|
-
//
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1235
|
-
// tool_use ↔ tool_result API invariant holds.
|
|
1793
|
+
// Identical no-range recall proposals may share an execution. Explicit
|
|
1794
|
+
// ranges need their own bytes and validation, and even normalized source
|
|
1795
|
+
// IDs must preserve each invocation's original arguments and pairing.
|
|
1236
1796
|
const sharedRecallExec = new Map();
|
|
1237
|
-
const
|
|
1797
|
+
const toolUseMessageId = `assistant_${randomUUID()}`;
|
|
1798
|
+
const batchCalls = toolUseBlocks.map(block => ({
|
|
1799
|
+
id: block.id, name: block.name, args: structuredClone(block.input), status: 'pending',
|
|
1800
|
+
}));
|
|
1801
|
+
for (const call of batchCalls)
|
|
1802
|
+
this.recordedToolCalls.set(call.id, call);
|
|
1803
|
+
const toolUseMessage = {
|
|
1804
|
+
id: toolUseMessageId, role: 'assistant',
|
|
1805
|
+
content: this.remoteResourceSession ? '[Using tools...]'
|
|
1806
|
+
: response.content.filter(this.isTextBlock).map(block => block.text).join('\n') || '[Using tools...]',
|
|
1807
|
+
...(this.backend.preservesNativeHistory ? { nativeContent: structuredClone(response.content), nativeModel: this.config.model, nativeContextId: this._nativeContextId } : {}),
|
|
1808
|
+
timestamp: new Date(), toolCalls: batchCalls,
|
|
1809
|
+
metadata: { toolCheckpoint: true, ...(this.config.runtimeFacts.mode === 'plan' ? { planMode: true } : {}), ...(response.reasoning ? this.remoteResourceSession
|
|
1810
|
+
? { providerReasoning: response.reasoning } : { reasoningContent: response.reasoning } : {}) },
|
|
1811
|
+
};
|
|
1812
|
+
messages.push(toolUseMessage);
|
|
1813
|
+
turnMessages.push(toolUseMessage);
|
|
1814
|
+
allToolCalls.push(...batchCalls);
|
|
1815
|
+
const toolResultMessage = {
|
|
1816
|
+
id: `tool_${randomUUID()}`, role: 'user', content: '[]', timestamp: new Date(),
|
|
1817
|
+
metadata: { toolResult: true, toolCheckpoint: true },
|
|
1818
|
+
};
|
|
1819
|
+
const completedResults = new Map();
|
|
1820
|
+
const retainToolResult = async (toolCall, rawContent, guardedContent, managedCommand, contentTruncated = false, verificationReceipt, verifierDecision, benchmarkComparisonDecision) => {
|
|
1821
|
+
const partial = Boolean(signal?.aborted || contentTruncated || managedCommand?.result.has_more || managedCommand?.result.output_truncated);
|
|
1822
|
+
const { rendered } = this.toolResponseStore.put(toolCall.id, toolCall.name, rawContent, partial, {
|
|
1823
|
+
args: toolCall.args,
|
|
1824
|
+
sourcePath: extractSourcePath(toolCall.name, toolCall.args), dedupable: toolCall.status === 'complete',
|
|
1825
|
+
isError: toolCall.status === 'error',
|
|
1826
|
+
source: { ...this.evidenceSource(toolCall.id, toolCall.name, toolCall.args, managedCommand, verificationReceipt, partial || toolCall.status === 'error' ? undefined : verifierDecision, partial || toolCall.status === 'error' ? undefined : benchmarkComparisonDecision), messageId: toolUseMessageId },
|
|
1827
|
+
});
|
|
1828
|
+
const annotation = guardedContent.slice(rawContent.length);
|
|
1829
|
+
completedResults.set(toolCall.id, { tool_use_id: toolCall.id, content: rendered,
|
|
1830
|
+
is_error: toolCall.status === 'error', ...(annotation ? { context_annotation: annotation } : {}) });
|
|
1831
|
+
toolResultMessage.content = JSON.stringify(batchCalls.flatMap(call => completedResults.has(call.id) ? [completedResults.get(call.id)] : []));
|
|
1832
|
+
if (!turnMessages.some(message => message.id === toolResultMessage.id)) {
|
|
1833
|
+
messages.push(toolResultMessage);
|
|
1834
|
+
turnMessages.push(toolResultMessage);
|
|
1835
|
+
}
|
|
1836
|
+
await this.refreshVerificationCoverage();
|
|
1837
|
+
await this.persistToolCheckpoint('terminal', toolCall.id, turnMessages, toolCheckpoint);
|
|
1838
|
+
};
|
|
1839
|
+
const recordToolSuccess = async (toolBlock, toolCall, content, effectiveArgs, managedCommand, contentTruncated = false, verificationReceipt, verifierDecision, benchmarkComparisonDecision) => {
|
|
1238
1840
|
toolCall.completedAt = new Date();
|
|
1239
1841
|
toolCall.args = effectiveArgs;
|
|
1240
1842
|
toolCall.status = 'complete';
|
|
1241
1843
|
toolCall.result = content;
|
|
1844
|
+
const guarded = applyRepeatGuard(toolBlock.name, toolBlock.input, content, managedCommand);
|
|
1845
|
+
await retainToolResult(toolCall, content, guarded, managedCommand, contentTruncated, verificationReceipt, verifierDecision, benchmarkComparisonDecision);
|
|
1242
1846
|
this.emit({ type: 'tool_end', tool: toolBlock.name, result: content, toolCallId: toolBlock.id });
|
|
1243
1847
|
this.tracer.event({
|
|
1244
1848
|
type: 'tool_end',
|
|
1245
1849
|
timestamp: new Date(),
|
|
1246
|
-
data:
|
|
1850
|
+
data: this.toolTraceData(toolCall),
|
|
1247
1851
|
});
|
|
1248
|
-
|
|
1249
|
-
return { toolCall, toolResult: { tool_use_id: toolBlock.id, content: guarded, is_error: false } };
|
|
1852
|
+
return { toolCall, managedCommand, contentTruncated, verificationReceipt, verifierDecision, benchmarkComparisonDecision, rawContent: content, toolResult: { tool_use_id: toolBlock.id, content: guarded, is_error: false } };
|
|
1250
1853
|
};
|
|
1251
|
-
const recordToolError = (toolBlock, toolCall, error, effectiveArgs) => {
|
|
1854
|
+
const recordToolError = async (toolBlock, toolCall, error, effectiveArgs, managedCommand, contentTruncated = false, verificationReceipt, verifierDecision, benchmarkComparisonDecision) => {
|
|
1252
1855
|
toolCall.completedAt = new Date();
|
|
1253
1856
|
if (effectiveArgs !== undefined)
|
|
1254
1857
|
toolCall.args = effectiveArgs;
|
|
1255
1858
|
toolCall.status = 'error';
|
|
1256
1859
|
toolCall.error = error;
|
|
1860
|
+
const guarded = applyRepeatGuard(toolBlock.name, toolBlock.input, `Error: ${error}`);
|
|
1861
|
+
await retainToolResult(toolCall, `Error: ${error}`, guarded, managedCommand, contentTruncated, verificationReceipt, verifierDecision, benchmarkComparisonDecision);
|
|
1257
1862
|
this.emit({ type: 'tool_error', tool: toolBlock.name, error, toolCallId: toolBlock.id });
|
|
1258
1863
|
this.tracer.event({
|
|
1259
1864
|
type: 'tool_error',
|
|
1260
1865
|
timestamp: new Date(),
|
|
1261
|
-
data: {
|
|
1866
|
+
data: { ...this.toolTraceData(toolCall), error },
|
|
1262
1867
|
});
|
|
1263
1868
|
return {
|
|
1264
1869
|
toolCall,
|
|
1870
|
+
managedCommand,
|
|
1871
|
+
contentTruncated,
|
|
1872
|
+
verificationReceipt,
|
|
1873
|
+
verifierDecision,
|
|
1874
|
+
benchmarkComparisonDecision,
|
|
1875
|
+
rawContent: `Error: ${error}`,
|
|
1265
1876
|
toolResult: {
|
|
1266
1877
|
tool_use_id: toolBlock.id,
|
|
1267
|
-
content:
|
|
1878
|
+
content: guarded,
|
|
1268
1879
|
is_error: true,
|
|
1269
1880
|
},
|
|
1270
1881
|
};
|
|
@@ -1283,17 +1894,19 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1283
1894
|
// current agent is rebuilt before any subsequent iteration.
|
|
1284
1895
|
const exclusiveTransitionId = toolUseBlocks.find((toolBlock) => toolBlock.name === 'plan_mode')?.id ?? null;
|
|
1285
1896
|
const combined = await scheduleToolBatch(toolUseBlocks, this.config.tools, this.config.execution, async (toolBlock, skipReason) => {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1897
|
+
if (this.toolCheckpointFailure)
|
|
1898
|
+
throw this.toolCheckpointFailure;
|
|
1899
|
+
const toolCall = batchCalls.find(call => call.id === toolBlock.id);
|
|
1900
|
+
toolCall.status = 'running';
|
|
1901
|
+
toolCall.startedAt = new Date();
|
|
1902
|
+
const beforeExecute = async (args) => {
|
|
1903
|
+
toolCall.args = structuredClone(args);
|
|
1904
|
+
await this.persistToolCheckpoint('before_execute', toolBlock.id, turnMessages, toolCheckpoint);
|
|
1292
1905
|
};
|
|
1293
1906
|
this.tracer.event({
|
|
1294
1907
|
type: 'tool_start',
|
|
1295
1908
|
timestamp: new Date(),
|
|
1296
|
-
data: {
|
|
1909
|
+
data: { ...this.toolTraceData(toolCall), argsSource: 'provider_proposal' }
|
|
1297
1910
|
});
|
|
1298
1911
|
this.emit({ type: 'tool_start', tool: toolBlock.name, args: toolBlock.input, toolCallId: toolBlock.id });
|
|
1299
1912
|
if (skipReason)
|
|
@@ -1306,15 +1919,19 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1306
1919
|
if (tool?.finalResponse && toolUseBlocks.length !== 1) {
|
|
1307
1920
|
return recordToolError(toolBlock, toolCall, 'finish_task must be the only tool call; observe other results before completing the task.');
|
|
1308
1921
|
}
|
|
1309
|
-
//
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
//
|
|
1313
|
-
//
|
|
1922
|
+
// Selector intent does not prove visibility: the encoder may cap or
|
|
1923
|
+
// compress the result. Acknowledge only exact encoded source bytes
|
|
1924
|
+
// that also remain within the historical stability limit. Larger
|
|
1925
|
+
// current results become previews after this recall is appended.
|
|
1926
|
+
// A hook dispatcher still owns each invocation's admission and outcome.
|
|
1314
1927
|
const innerId = recallInnerIdOf(toolBlock);
|
|
1315
|
-
if (innerId !== null &&
|
|
1928
|
+
if (!this.hooks && innerId !== null &&
|
|
1929
|
+
toolBlock.input.cursor === undefined && toolBlock.input.max_bytes === undefined &&
|
|
1316
1930
|
this.toolResponseStore.willInlineNextRender(innerId) &&
|
|
1317
|
-
this.toolResponseStore.hasCached(innerId)
|
|
1931
|
+
this.toolResponseStore.hasCached(innerId) &&
|
|
1932
|
+
request.messages.some(message => typeof message.content !== 'string' && message.content.some(block => block.type === 'tool_result' && block.tool_use_id === innerId &&
|
|
1933
|
+
typeof block.content === 'string' && block.content.length <= STABLE_RESULT_LIMIT &&
|
|
1934
|
+
createHash('sha256').update(block.content).digest('hex') === this.toolResponseStore.getMeta(innerId)?.result_sha256))) {
|
|
1318
1935
|
const ack = JSON.stringify({
|
|
1319
1936
|
ok: true,
|
|
1320
1937
|
tool_use_id: innerId,
|
|
@@ -1323,124 +1940,133 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1323
1940
|
});
|
|
1324
1941
|
return recordToolSuccess(toolBlock, toolCall, ack, toolBlock.input);
|
|
1325
1942
|
}
|
|
1943
|
+
const outputObserver = this.toolOutputObserver(toolCall, signal);
|
|
1326
1944
|
try {
|
|
1327
|
-
//
|
|
1945
|
+
// Hooks own per-invocation transformations, admission and outcomes.
|
|
1946
|
+
// Only a host without a hook dispatcher may share default recalls.
|
|
1328
1947
|
let result;
|
|
1329
|
-
if (innerId !== null) {
|
|
1330
|
-
const
|
|
1948
|
+
if (!this.hooks && innerId !== null && toolBlock.input.cursor === undefined && toolBlock.input.max_bytes === undefined) {
|
|
1949
|
+
const recallShareKey = JSON.stringify(toolBlock.input);
|
|
1950
|
+
const existing = sharedRecallExec.get(recallShareKey);
|
|
1331
1951
|
if (existing !== undefined) {
|
|
1332
1952
|
result = await existing;
|
|
1333
1953
|
}
|
|
1334
1954
|
else {
|
|
1335
1955
|
const fresh = executeToolWithRuntime(toolBlock.name, toolBlock.input, {
|
|
1336
1956
|
tools: this.config.tools,
|
|
1957
|
+
remoteResourceSession: this.remoteResourceSession,
|
|
1337
1958
|
context: {
|
|
1338
1959
|
cwd: this._cwd,
|
|
1339
1960
|
toolCallId: toolBlock.id,
|
|
1961
|
+
onUsage: requestUsage,
|
|
1340
1962
|
sessionId: this.config.sessionId,
|
|
1963
|
+
managedCommandWaitSliceMs: 0,
|
|
1341
1964
|
signal,
|
|
1342
1965
|
toolResponseStore: this.toolResponseStore,
|
|
1343
1966
|
discoveredTools: this.discoveredTools,
|
|
1344
1967
|
availableTools: this.config.tools,
|
|
1345
1968
|
subprocessEnv: this.copySubprocessEnv(),
|
|
1969
|
+
commandPolicy: this.commandPolicy,
|
|
1970
|
+
onOutput: outputObserver.onOutput,
|
|
1346
1971
|
},
|
|
1347
1972
|
policy: this.policy,
|
|
1348
1973
|
hooks: this.hooks,
|
|
1349
1974
|
approvalRequester: this.approvalRequester,
|
|
1350
1975
|
onToolEffect: this.toolEffectObserver,
|
|
1976
|
+
onLocalFileChange: this.onLocalFileChange,
|
|
1977
|
+
beforeExecute,
|
|
1351
1978
|
approvalRequiredCapabilities: this.approvalRequiredCapabilities,
|
|
1352
1979
|
approvalContext: this.approvalContext,
|
|
1353
1980
|
});
|
|
1354
|
-
sharedRecallExec.set(
|
|
1981
|
+
sharedRecallExec.set(recallShareKey, fresh);
|
|
1355
1982
|
result = await fresh;
|
|
1356
1983
|
}
|
|
1357
1984
|
}
|
|
1358
1985
|
else {
|
|
1359
1986
|
result = await executeToolWithRuntime(toolBlock.name, toolBlock.input, {
|
|
1360
1987
|
tools: this.config.tools,
|
|
1988
|
+
remoteResourceSession: this.remoteResourceSession,
|
|
1361
1989
|
context: {
|
|
1362
1990
|
cwd: this._cwd,
|
|
1363
1991
|
toolCallId: toolBlock.id,
|
|
1992
|
+
onUsage: requestUsage,
|
|
1364
1993
|
sessionId: this.config.sessionId,
|
|
1994
|
+
managedCommandWaitSliceMs: 0,
|
|
1365
1995
|
signal,
|
|
1366
1996
|
toolResponseStore: this.toolResponseStore,
|
|
1367
1997
|
discoveredTools: this.discoveredTools,
|
|
1368
1998
|
availableTools: this.config.tools,
|
|
1369
1999
|
subprocessEnv: this.copySubprocessEnv(),
|
|
2000
|
+
commandPolicy: this.commandPolicy,
|
|
2001
|
+
onOutput: outputObserver.onOutput,
|
|
1370
2002
|
},
|
|
1371
2003
|
policy: this.policy,
|
|
1372
2004
|
hooks: this.hooks,
|
|
1373
2005
|
approvalRequester: this.approvalRequester,
|
|
1374
2006
|
onToolEffect: this.toolEffectObserver,
|
|
2007
|
+
onLocalFileChange: this.onLocalFileChange,
|
|
2008
|
+
beforeExecute,
|
|
1375
2009
|
approvalRequiredCapabilities: this.approvalRequiredCapabilities,
|
|
1376
2010
|
approvalContext: this.approvalContext,
|
|
1377
2011
|
});
|
|
1378
2012
|
}
|
|
2013
|
+
outputObserver.close();
|
|
1379
2014
|
if (result.isError) {
|
|
1380
|
-
return recordToolError(toolBlock, toolCall, result.content, result.effectiveArgs);
|
|
2015
|
+
return recordToolError(toolBlock, toolCall, result.content, result.effectiveArgs, result.managedCommand, result.contentTruncated, result.verificationReceipt, result.verifierDecision, result.benchmarkComparisonDecision);
|
|
1381
2016
|
}
|
|
1382
|
-
return recordToolSuccess(toolBlock, toolCall, result.content, result.effectiveArgs);
|
|
2017
|
+
return recordToolSuccess(toolBlock, toolCall, result.content, result.effectiveArgs, result.managedCommand, result.contentTruncated, result.verificationReceipt, result.verifierDecision, result.benchmarkComparisonDecision);
|
|
1383
2018
|
}
|
|
1384
2019
|
catch (err) {
|
|
2020
|
+
outputObserver.close();
|
|
2021
|
+
if (this.toolCheckpointFailure)
|
|
2022
|
+
throw this.toolCheckpointFailure;
|
|
1385
2023
|
const error = err.message;
|
|
1386
2024
|
return recordToolError(toolBlock, toolCall, error);
|
|
1387
2025
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
sourcePath: extractSourcePath(r.toolCall.name, r.toolCall.args),
|
|
1394
|
-
dedupable: r.toolCall.status === 'complete',
|
|
1395
|
-
isError: r.toolResult.is_error,
|
|
1396
|
-
});
|
|
1397
|
-
return { tool_use_id: r.toolResult.tool_use_id, content: rendered, is_error: r.toolResult.is_error };
|
|
1398
|
-
});
|
|
2026
|
+
finally {
|
|
2027
|
+
outputObserver.close();
|
|
2028
|
+
}
|
|
2029
|
+
}, result => result.toolCall.status === 'error' || result.managedCommand?.result.status === 'running'
|
|
2030
|
+
|| commandStillRunning(result.toolResult.content), signal, exclusiveTransitionId);
|
|
1399
2031
|
// Real work happened, so the turn has demonstrably not stalled on
|
|
1400
2032
|
// narration: give it a fresh continuation budget.
|
|
1401
2033
|
this._consecutiveAutoContinues = 0;
|
|
1402
2034
|
// Add assistant message with tool use
|
|
1403
|
-
const textContent =
|
|
1404
|
-
.filter(
|
|
1405
|
-
|
|
1406
|
-
.join('\n');
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
role: 'assistant',
|
|
1410
|
-
content: textContent || '[Using tools...]',
|
|
1411
|
-
timestamp: new Date(),
|
|
1412
|
-
toolCalls: allToolCalls.slice(-toolUseBlocks.length),
|
|
1413
|
-
...(response.reasoning ? { metadata: { reasoningContent: response.reasoning } } : {}),
|
|
1414
|
-
};
|
|
1415
|
-
messages.push(toolUseMessage);
|
|
1416
|
-
turnMessages.push(toolUseMessage);
|
|
1417
|
-
const toolResultMessage = {
|
|
1418
|
-
id: `tool_${Date.now()}`,
|
|
1419
|
-
role: 'user',
|
|
1420
|
-
content: JSON.stringify(toolResults),
|
|
1421
|
-
timestamp: new Date(),
|
|
1422
|
-
metadata: { toolResult: true },
|
|
1423
|
-
};
|
|
1424
|
-
messages.push(toolResultMessage);
|
|
1425
|
-
turnMessages.push(toolResultMessage);
|
|
2035
|
+
const textContent = this.remoteResourceSession
|
|
2036
|
+
? combined.filter(result => result.toolCall.name === 'remote_resource' && result.toolCall.status === 'complete')
|
|
2037
|
+
.flatMap(result => remoteDeletionReport(result.toolResult.content) ?? []).join('\n')
|
|
2038
|
+
: response.content.filter(this.isTextBlock).map(b => b.text).join('\n');
|
|
2039
|
+
toolUseMessage.content = textContent || '[Using tools...]';
|
|
2040
|
+
toolUseMessage.turn = requestUsage?.getTurn?.();
|
|
1426
2041
|
this.emit({ type: 'history_checkpoint', messages: [...turnMessages] });
|
|
1427
2042
|
if (signal?.aborted)
|
|
1428
2043
|
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1429
2044
|
// Stream any text content that came with tool calls (important for user feedback!)
|
|
1430
2045
|
if (textContent && textContent.trim() && !signal?.aborted) {
|
|
1431
|
-
if (!this._lastResponseStreamed)
|
|
2046
|
+
if (this.remoteResourceSession || !this._lastResponseStreamed)
|
|
1432
2047
|
await this.emitStreamText(textContent, signal);
|
|
1433
2048
|
// Last non-empty text wins — empty iterations preserve the prior value
|
|
1434
2049
|
// so maxIterations exhaustion still has something to return.
|
|
1435
2050
|
lastToolTurnText = textContent;
|
|
1436
2051
|
}
|
|
2052
|
+
// Recorded IDs were reconciled above; the scheduler receives only new
|
|
2053
|
+
// calls. Add one host notice after retaining every refused result pair.
|
|
2054
|
+
const batchLimit = resolveExecutionSettings(this.config.execution).maxCallsPerResponse;
|
|
2055
|
+
if (toolUseBlocks.length > batchLimit) {
|
|
2056
|
+
const recoveryMessage = { id: `batch_recovery_${randomUUID()}`, role: 'user',
|
|
2057
|
+
content: `[Tool batch recovery] The ${toolUseBlocks.length} new-call batch exceeds the limit of ${batchLimit} total calls (reads + effects) per response. None of this rejected batch executed. Submit smaller batches of at most ${batchLimit} calls in bounded rounds, and inspect prerequisite results before dependent effects. Retry only operations explicitly marked not_executed, using fresh invocation IDs for those proven unexecuted retries. Reuse retained completed results; inspect and reconcile running or unknown outcomes before considering another operation.`,
|
|
2058
|
+
metadata: { synthetic: true }, timestamp: new Date() };
|
|
2059
|
+
messages.push(recoveryMessage);
|
|
2060
|
+
turnMessages.push(recoveryMessage);
|
|
2061
|
+
this.emit({ type: 'history_checkpoint', messages: [...turnMessages] });
|
|
2062
|
+
}
|
|
1437
2063
|
const recoveryAction = recovery(combined.map(result => ({ name: result.toolCall.name,
|
|
1438
|
-
content: result.toolResult.content, isError: result.toolResult.is_error })));
|
|
2064
|
+
content: result.toolResult.content, isError: result.toolResult.is_error, managedCommand: result.managedCommand })));
|
|
1439
2065
|
if (recoveryAction === 'stop')
|
|
1440
2066
|
throw new AgentError('Automatic recovery budget exhausted after repeated tool failures. Inspect the retained results and current capabilities before retrying.', 'RECOVERY_EXHAUSTED');
|
|
1441
2067
|
if (recoveryAction === 'recover') {
|
|
1442
2068
|
const recoveryMessage = { id: `recovery_${randomUUID()}`, role: 'user',
|
|
1443
|
-
content: '[Recovery] Repeated failures share the same cause. Stop that sequence. Inspect current capabilities and active jobs, then make one changed recovery attempt. Use file/stdin tools for scripts and raw Ctrl+C for stuck shell input, then read the pane in the same response or immediately afterward.
|
|
2069
|
+
content: '[Recovery] Repeated failures share the same cause. Stop that sequence. Inspect current capabilities and active jobs, then make one changed recovery attempt. Use file/stdin tools for scripts and raw Ctrl+C for stuck shell input, then read the pane in the same response or immediately afterward. Healthy managed job waiting preserves this failure budget; silence is not a reason to relaunch or abandon the owned job. Continue authorized work; use ask_user only for a concrete missing prerequisite. Do not repeat an acknowledgement as proof of success.',
|
|
1444
2070
|
metadata: { synthetic: true }, timestamp: new Date() };
|
|
1445
2071
|
messages.push(recoveryMessage);
|
|
1446
2072
|
turnMessages.push(recoveryMessage);
|
|
@@ -1469,7 +2095,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1469
2095
|
const hasSuccessfulInteractiveTool = toolUseBlocks.some(tb => {
|
|
1470
2096
|
if (!Agent.TURN_ENDING_TOOLS.has(tb.name))
|
|
1471
2097
|
return false;
|
|
1472
|
-
const result =
|
|
2098
|
+
const result = completedResults.get(tb.id);
|
|
1473
2099
|
return result ? !result.content.startsWith('Error:') : false;
|
|
1474
2100
|
});
|
|
1475
2101
|
if (hasSuccessfulInteractiveTool) {
|
|
@@ -1480,10 +2106,53 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1480
2106
|
// and before the next inference. Keep terminal tools and exhausted turns
|
|
1481
2107
|
// on the normal next-turn path because no model request remains here.
|
|
1482
2108
|
if (iterations < this.config.maxIterations) {
|
|
1483
|
-
const
|
|
2109
|
+
const observations = combined.flatMap(result => result.managedCommand && !result.toolResult.is_error
|
|
2110
|
+
? [{ ...result.managedCommand, toolCallId: result.toolCall.id }] : []);
|
|
2111
|
+
const consumedSteering = [];
|
|
2112
|
+
let waited;
|
|
2113
|
+
try {
|
|
2114
|
+
waited = observations.length === combined.length
|
|
2115
|
+
? await awaitManagedCommandProgress(observations, { signal, deadlineAt: Date.now() + 60_000,
|
|
2116
|
+
takeSteering: takeSteering ? () => {
|
|
2117
|
+
const next = takeSteering();
|
|
2118
|
+
consumedSteering.push(...next);
|
|
2119
|
+
return next;
|
|
2120
|
+
} : undefined, emit: event => this.emit(event) })
|
|
2121
|
+
: { waited: false, observations: [], steering: [] };
|
|
2122
|
+
}
|
|
2123
|
+
catch (error) {
|
|
2124
|
+
// A failed receipt refresh must not discard Enter messages already drained.
|
|
2125
|
+
if (consumedSteering.length) {
|
|
2126
|
+
messages.push(...consumedSteering);
|
|
2127
|
+
turnMessages.push(...consumedSteering);
|
|
2128
|
+
this.emit({ type: 'history_checkpoint', messages: [...turnMessages] });
|
|
2129
|
+
}
|
|
2130
|
+
throw error;
|
|
2131
|
+
}
|
|
2132
|
+
if (waited.waited) {
|
|
2133
|
+
const observation = { id: `command_observation_${randomUUID()}`, role: 'user',
|
|
2134
|
+
content: formatManagedCommandObservations(waited.observations), timestamp: new Date(),
|
|
2135
|
+
metadata: { synthetic: true, hostContext: true, managedCommandObservation: true } };
|
|
2136
|
+
for (const [index, item] of waited.observations.entries()) {
|
|
2137
|
+
this.toolResponseStore.put(`${observation.id}_${index}`, 'read_command', JSON.stringify(item.result), item.result.has_more || Boolean(item.result.output_truncated), {
|
|
2138
|
+
source: { ...this.evidenceSource(item.toolCallId, 'read_command', {}, item), originToolCallId: item.toolCallId },
|
|
2139
|
+
isError: item.result.status !== 'running' && item.result.status !== 'completed',
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
messages.push(observation);
|
|
2143
|
+
turnMessages.push(observation);
|
|
2144
|
+
this.emit({ type: 'history_checkpoint', messages: [...turnMessages] });
|
|
2145
|
+
}
|
|
2146
|
+
const steering = [...waited.steering, ...(takeSteering?.() ?? [])];
|
|
1484
2147
|
if (steering.length > 0) {
|
|
1485
2148
|
messages.push(...steering);
|
|
1486
2149
|
turnMessages.push(...steering);
|
|
2150
|
+
this.emit({ type: 'history_checkpoint', messages: [...turnMessages] });
|
|
2151
|
+
if (signal?.aborted)
|
|
2152
|
+
throw new AgentError('Cancelled', 'CANCELLED');
|
|
2153
|
+
await steeringAdmission?.({ messages: [...turnMessages], sourceMessageIds: steering.map(message => message.id), signal });
|
|
2154
|
+
if (signal?.aborted)
|
|
2155
|
+
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1487
2156
|
}
|
|
1488
2157
|
}
|
|
1489
2158
|
}
|
|
@@ -1534,11 +2203,12 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1534
2203
|
}
|
|
1535
2204
|
}
|
|
1536
2205
|
return {
|
|
1537
|
-
content: finalContent || lastToolTurnText,
|
|
2206
|
+
content: finalContent || lastToolTurnText || (this.remoteResourceSession ? NO_VERIFIED_DELETION_REPORT : ''),
|
|
1538
2207
|
toolCalls: allToolCalls,
|
|
1539
2208
|
usage: totalUsage || undefined,
|
|
1540
2209
|
stopReason,
|
|
1541
2210
|
...(finalReasoning ? { reasoning: finalReasoning } : {}),
|
|
2211
|
+
...(finalNativeContent ? { nativeContent: finalNativeContent, nativeModel: this.config.model, nativeContextId: this._nativeContextId } : {}),
|
|
1542
2212
|
turnMessages,
|
|
1543
2213
|
};
|
|
1544
2214
|
}
|
|
@@ -1551,6 +2221,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1551
2221
|
* implements it, so this is a no-op for every other provider.
|
|
1552
2222
|
*/
|
|
1553
2223
|
dispose() {
|
|
2224
|
+
this.evidenceUnsubscribe();
|
|
1554
2225
|
this.backend.close?.();
|
|
1555
2226
|
}
|
|
1556
2227
|
/**
|
|
@@ -1560,7 +2231,50 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1560
2231
|
* approval requester, tool-response store, and mirror-visible tool events.
|
|
1561
2232
|
*/
|
|
1562
2233
|
async executeHarnessToolCall(name, args, options = {}) {
|
|
2234
|
+
await this.recoverToolCheckpoint();
|
|
2235
|
+
return this.executeHarnessToolCallWithCheckpoint(name, args, options);
|
|
2236
|
+
}
|
|
2237
|
+
recordedToolCall(id, name, args) {
|
|
2238
|
+
const recorded = this.recordedToolCalls.get(id);
|
|
2239
|
+
if (recorded && (recorded.name !== name || !isDeepStrictEqual(recorded.args, args))) {
|
|
2240
|
+
throw new AgentError(`Tool invocation identity conflict for ${id}: the recorded tool or arguments differ. Inspect the original call; it was not executed again.`, 'TOOL_CALL_IDENTITY_CONFLICT');
|
|
2241
|
+
}
|
|
2242
|
+
return recorded;
|
|
2243
|
+
}
|
|
2244
|
+
async hasStoredToolIdentity(id) {
|
|
2245
|
+
if (this.toolResponseStore.getMeta(id))
|
|
2246
|
+
return true;
|
|
2247
|
+
try {
|
|
2248
|
+
// Compaction can remove the call row while its immutable source remains.
|
|
2249
|
+
// Lazy lookup also reserves unavailable or unindexed retained sources.
|
|
2250
|
+
return await this.toolResponseStore.get(id) !== null;
|
|
2251
|
+
}
|
|
2252
|
+
catch (error) {
|
|
2253
|
+
if (error instanceof ToolResponseIntegrityError)
|
|
2254
|
+
return true;
|
|
2255
|
+
throw error;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
async executeHarnessToolCallWithCheckpoint(name, args, options, context) {
|
|
2259
|
+
if (this.toolCheckpointFailure)
|
|
2260
|
+
throw this.toolCheckpointFailure;
|
|
1563
2261
|
options.onActivity?.();
|
|
2262
|
+
const toolCallId = options.callId ?? `tool_${randomUUID()}`;
|
|
2263
|
+
const recorded = this.recordedToolCall(toolCallId, name, args);
|
|
2264
|
+
if (recorded) {
|
|
2265
|
+
if (recorded.error !== undefined)
|
|
2266
|
+
return { success: false, output: `Error: ${recorded.error}` };
|
|
2267
|
+
if (recorded.result !== undefined) {
|
|
2268
|
+
const image = parseImageToolResult(recorded.result);
|
|
2269
|
+
return { success: true, output: image
|
|
2270
|
+
? { text: image.description ?? '', images: [{ mediaType: image.mediaType, data: image.data }] }
|
|
2271
|
+
: recorded.result };
|
|
2272
|
+
}
|
|
2273
|
+
return { success: false, output: `Tool outcome unknown for recorded invocation ${toolCallId}. Inspect the original call and reconcile its existing receipts/current state; it was not executed again.` };
|
|
2274
|
+
}
|
|
2275
|
+
if (await this.hasStoredToolIdentity(toolCallId)) {
|
|
2276
|
+
return { success: false, output: `Tool outcome unknown for retained invocation ${toolCallId}: its original argument record is unavailable. Inspect its retained source and reconcile existing receipts/current state; it was not executed again.` };
|
|
2277
|
+
}
|
|
1564
2278
|
const tool = this.config.tools.find(candidate => candidate.definition.name === name);
|
|
1565
2279
|
if (!tool) {
|
|
1566
2280
|
return {
|
|
@@ -1568,59 +2282,156 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1568
2282
|
output: `Error: unknown tool '${name}' is not registered for this agent.`,
|
|
1569
2283
|
};
|
|
1570
2284
|
}
|
|
1571
|
-
|
|
2285
|
+
// Direct calls have an isolated canonical segment. Provider callbacks hold
|
|
2286
|
+
// the arrays and handler belonging to their original turn, even if their
|
|
2287
|
+
// underlying IO settles after cancellation and a newer turn has started.
|
|
2288
|
+
const owner = context ?? { messages: [], turnMessages: [], toolCalls: [], handler: this.toolCheckpoint };
|
|
2289
|
+
const toolCall = { id: toolCallId, name, args: structuredClone(args),
|
|
2290
|
+
status: 'running', startedAt: new Date() };
|
|
2291
|
+
this.recordedToolCalls.set(toolCallId, toolCall);
|
|
2292
|
+
const toolUseMessage = {
|
|
2293
|
+
id: `assistant_${randomUUID()}`, role: 'assistant', content: '[Using tools...]',
|
|
2294
|
+
timestamp: new Date(), toolCalls: [toolCall], metadata: { toolCheckpoint: true },
|
|
2295
|
+
};
|
|
2296
|
+
const toolResultMessage = {
|
|
2297
|
+
id: `tool_${randomUUID()}`, role: 'user', content: '[]', timestamp: new Date(),
|
|
2298
|
+
metadata: { toolResult: true, toolCheckpoint: true },
|
|
2299
|
+
};
|
|
2300
|
+
owner.messages.push(toolUseMessage);
|
|
2301
|
+
owner.turnMessages.push(toolUseMessage);
|
|
2302
|
+
owner.toolCalls.push(toolCall);
|
|
1572
2303
|
this.emit({ type: 'tool_start', tool: name, args, toolCallId });
|
|
1573
|
-
this.tracer.event({ type: 'tool_start', timestamp: new Date(), data:
|
|
2304
|
+
this.tracer.event({ type: 'tool_start', timestamp: new Date(), data: this.toolTraceData(toolCall) });
|
|
2305
|
+
const outputObserver = this.toolOutputObserver(toolCall, options.signal);
|
|
2306
|
+
let result;
|
|
1574
2307
|
try {
|
|
1575
|
-
|
|
2308
|
+
result = await executeToolWithRuntime(name, args, {
|
|
1576
2309
|
tools: this.config.tools,
|
|
2310
|
+
remoteResourceSession: this.remoteResourceSession,
|
|
1577
2311
|
context: {
|
|
1578
2312
|
cwd: this._cwd,
|
|
1579
2313
|
toolCallId,
|
|
1580
2314
|
sessionId: this.config.sessionId,
|
|
1581
2315
|
signal: options.signal,
|
|
2316
|
+
onUsage: options.onUsage,
|
|
1582
2317
|
toolResponseStore: this.toolResponseStore,
|
|
1583
2318
|
discoveredTools: this.discoveredTools,
|
|
1584
2319
|
availableTools: this.config.tools,
|
|
1585
2320
|
subprocessEnv: this.copySubprocessEnv(),
|
|
2321
|
+
commandPolicy: this.commandPolicy,
|
|
2322
|
+
onOutput: outputObserver.onOutput,
|
|
1586
2323
|
},
|
|
1587
2324
|
policy: this.policy,
|
|
1588
2325
|
hooks: this.hooks,
|
|
1589
2326
|
approvalRequester: this.approvalRequester,
|
|
1590
2327
|
onToolEffect: this.toolEffectObserver,
|
|
2328
|
+
onLocalFileChange: this.onLocalFileChange,
|
|
2329
|
+
beforeExecute: async (effectiveArgs) => {
|
|
2330
|
+
toolCall.args = structuredClone(effectiveArgs);
|
|
2331
|
+
await this.persistToolCheckpoint('before_execute', toolCallId, owner.turnMessages, owner.handler);
|
|
2332
|
+
},
|
|
1591
2333
|
approvalRequiredCapabilities: this.approvalRequiredCapabilities,
|
|
1592
2334
|
approvalContext: this.approvalContext,
|
|
1593
2335
|
});
|
|
1594
|
-
options.onActivity?.();
|
|
1595
|
-
if (result.isError) {
|
|
1596
|
-
this.emit({ type: 'tool_error', tool: name, error: result.content, toolCallId });
|
|
1597
|
-
this.tracer.event({ type: 'tool_error', timestamp: new Date(), data: { tool: name, error: result.content } });
|
|
1598
|
-
return { success: false, output: `Error: ${result.content}` };
|
|
1599
|
-
}
|
|
1600
|
-
this.emit({ type: 'tool_end', tool: name, result: result.content, toolCallId });
|
|
1601
|
-
this.tracer.event({ type: 'tool_end', timestamp: new Date(), data: { tool: name } });
|
|
1602
|
-
const image = parseImageToolResult(result.content);
|
|
1603
|
-
if (image) {
|
|
1604
|
-
return {
|
|
1605
|
-
success: true,
|
|
1606
|
-
output: {
|
|
1607
|
-
text: image.description ?? '',
|
|
1608
|
-
images: [{ mediaType: image.mediaType, data: image.data }],
|
|
1609
|
-
},
|
|
1610
|
-
};
|
|
1611
|
-
}
|
|
1612
|
-
return { success: true, output: result.content };
|
|
1613
2336
|
}
|
|
1614
2337
|
catch (error) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
2338
|
+
if (this.toolCheckpointFailure)
|
|
2339
|
+
throw this.toolCheckpointFailure;
|
|
2340
|
+
result = { content: error instanceof Error ? error.message : String(error),
|
|
2341
|
+
isError: true, effectiveArgs: toolCall.args };
|
|
2342
|
+
}
|
|
2343
|
+
finally {
|
|
2344
|
+
outputObserver.close();
|
|
2345
|
+
}
|
|
2346
|
+
toolCall.args = structuredClone(result.effectiveArgs);
|
|
2347
|
+
toolCall.completedAt = new Date();
|
|
2348
|
+
toolCall.status = result.isError ? 'error' : 'complete';
|
|
2349
|
+
if (result.isError)
|
|
2350
|
+
toolCall.error = result.content;
|
|
2351
|
+
else
|
|
2352
|
+
toolCall.result = result.content;
|
|
2353
|
+
const partial = Boolean(options.signal?.aborted || result.contentTruncated || result.managedCommand?.result.has_more || result.managedCommand?.result.output_truncated);
|
|
2354
|
+
const { rendered } = this.toolResponseStore.put(toolCallId, name, result.content, partial, {
|
|
2355
|
+
args: result.effectiveArgs,
|
|
2356
|
+
sourcePath: extractSourcePath(name, result.effectiveArgs), isError: result.isError,
|
|
2357
|
+
source: { ...this.evidenceSource(toolCallId, name, result.effectiveArgs, result.managedCommand, result.verificationReceipt, partial || result.isError ? undefined : result.verifierDecision, partial || result.isError ? undefined : result.benchmarkComparisonDecision),
|
|
2358
|
+
messageId: toolUseMessage.id },
|
|
2359
|
+
});
|
|
2360
|
+
toolResultMessage.content = JSON.stringify([{ tool_use_id: toolCallId, content: rendered, is_error: result.isError }]);
|
|
2361
|
+
owner.messages.push(toolResultMessage);
|
|
2362
|
+
owner.turnMessages.push(toolResultMessage);
|
|
2363
|
+
await this.refreshVerificationCoverage();
|
|
2364
|
+
await this.persistToolCheckpoint('terminal', toolCallId, owner.turnMessages, owner.handler);
|
|
2365
|
+
if (context)
|
|
2366
|
+
this.emit({ type: 'history_checkpoint', messages: [...owner.turnMessages] });
|
|
2367
|
+
options.onActivity?.();
|
|
2368
|
+
if (result.isError) {
|
|
2369
|
+
this.emit({ type: 'tool_error', tool: name, error: result.content, toolCallId });
|
|
2370
|
+
this.tracer.event({ type: 'tool_error', timestamp: new Date(), data: { ...this.toolTraceData(toolCall), error: result.content } });
|
|
2371
|
+
return { success: false, output: `Error: ${result.content}` };
|
|
2372
|
+
}
|
|
2373
|
+
this.emit({ type: 'tool_end', tool: name, result: result.content, toolCallId });
|
|
2374
|
+
this.tracer.event({ type: 'tool_end', timestamp: new Date(), data: this.toolTraceData(toolCall) });
|
|
2375
|
+
const image = parseImageToolResult(result.content);
|
|
2376
|
+
if (image) {
|
|
2377
|
+
return {
|
|
2378
|
+
success: true,
|
|
2379
|
+
output: {
|
|
2380
|
+
text: image.description ?? '',
|
|
2381
|
+
images: [{ mediaType: image.mediaType, data: image.data }],
|
|
2382
|
+
},
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
return { success: true, output: result.content };
|
|
2386
|
+
}
|
|
2387
|
+
captureUserInstructions(messages) {
|
|
2388
|
+
const known = new Set(this.getEvidenceSnapshot().records.map(record => record.id));
|
|
2389
|
+
for (const message of messages) {
|
|
2390
|
+
if (message.role !== 'user' || message.metadata?.synthetic || message.metadata?.toolResult
|
|
2391
|
+
|| message.metadata?.compactionSummary || message.metadata?.managedCommandObservation)
|
|
2392
|
+
continue;
|
|
2393
|
+
const id = `instruction_${message.id}`;
|
|
2394
|
+
if (known.has(id))
|
|
2395
|
+
continue;
|
|
2396
|
+
// Legacy tool-result arrays may lack the metadata marker. They remain
|
|
2397
|
+
// quoted tool data, never a source of user permission.
|
|
2398
|
+
try {
|
|
2399
|
+
const parsed = JSON.parse(message.content);
|
|
2400
|
+
if (Array.isArray(parsed) && parsed.some(item => item && typeof item === 'object' && 'tool_use_id' in item))
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
catch { /* ordinary user text */ }
|
|
2404
|
+
this.toolResponseStore.evidence.recordUserInstruction({ id, text: message.content,
|
|
2405
|
+
textSha256: createHash('sha256').update(message.content).digest('hex'), originalTextBytes: Buffer.byteLength(message.content),
|
|
2406
|
+
recordedAt: message.timestamp instanceof Date ? message.timestamp.toISOString() : new Date().toISOString(),
|
|
2407
|
+
source: { origin: 'user', messageId: message.id } });
|
|
2408
|
+
known.add(id);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
evidenceSource(toolCallId, toolName, args, managed, verificationReceipt, verifierDecision, benchmarkComparisonDecision) {
|
|
2412
|
+
const path = extractSourcePath(toolName, args);
|
|
2413
|
+
const verification = verificationReceipt ?? managed?.result.verification;
|
|
2414
|
+
const source = { origin: managed || verification || benchmarkComparisonDecision ? 'host_receipt' : 'host_tool', toolCallId, toolName,
|
|
2415
|
+
cwd: managed?.result.cwd ?? this._cwd, ...(path ? { path } : {}) };
|
|
2416
|
+
if (verification)
|
|
2417
|
+
source.verification = structuredClone(verification);
|
|
2418
|
+
if (verifierDecision)
|
|
2419
|
+
source.verifier = structuredClone(verifierDecision);
|
|
2420
|
+
if (benchmarkComparisonDecision)
|
|
2421
|
+
source.benchmarkComparison = structuredClone(benchmarkComparisonDecision);
|
|
2422
|
+
if (managed) {
|
|
2423
|
+
const result = managed.result;
|
|
2424
|
+
source.job = { sessionId: managed.sessionId, jobId: result.job_id, status: result.status,
|
|
2425
|
+
exitCode: result.exit_code ?? null, startedAt: result.started_at, endedAt: result.ended_at,
|
|
2426
|
+
cursor: result.cursor, hasMore: result.has_more, outputTruncated: Boolean(result.output_truncated),
|
|
2427
|
+
...(result.checked === undefined ? {} : { checked: structuredClone(result.checked) }) };
|
|
2428
|
+
source.artifact = { path: result.log_path };
|
|
1619
2429
|
}
|
|
2430
|
+
return source;
|
|
1620
2431
|
}
|
|
1621
2432
|
// API call with retry logic for transient errors
|
|
1622
|
-
async callAPI(request, signal) {
|
|
1623
|
-
const { maxAttempts,
|
|
2433
|
+
async callAPI(request, onProviderObservation, signal, checkpointContext, onReturnedResponse, onUsage) {
|
|
2434
|
+
const { maxAttempts, recoveryWindowMs } = this.config.apiRetry;
|
|
1624
2435
|
const timeoutMs = this.config.llmRequestTimeoutMs;
|
|
1625
2436
|
if (this.backend.handlesTransportRetries?.(request) === true) {
|
|
1626
2437
|
request = { ...request, transportTurnId: request.transportTurnId ?? randomUUID() };
|
|
@@ -1638,11 +2449,23 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1638
2449
|
// model like deepseek can regenerate the turn instead of killing the run.
|
|
1639
2450
|
const useStream = process.env.ZTC_NO_STREAM !== '1' && !this.backend.streamsSyntheticallyFor?.(request);
|
|
1640
2451
|
let answerOutputEmitted = false;
|
|
1641
|
-
|
|
2452
|
+
let effectObserved = false;
|
|
2453
|
+
let recoveryStartedAt;
|
|
2454
|
+
let lastReason = 'Server temporarily unavailable';
|
|
2455
|
+
const canRegenerate = () => this.backend.allowsRequestRetryAfterFailure?.(request) !== false;
|
|
2456
|
+
const returned = (response) => { onReturnedResponse?.(response); return response; };
|
|
2457
|
+
for (let attempt = 1;; attempt++) {
|
|
1642
2458
|
if (signal?.aborted) {
|
|
1643
2459
|
throw new AgentError('Cancelled', 'CANCELLED');
|
|
1644
2460
|
}
|
|
2461
|
+
if (recoveryStartedAt !== undefined && recoveryWindowMs !== null
|
|
2462
|
+
&& Date.now() - recoveryStartedAt >= recoveryWindowMs) {
|
|
2463
|
+
throw new AgentError(lastReason, 'API_ERROR');
|
|
2464
|
+
}
|
|
2465
|
+
if (attempt > 1)
|
|
2466
|
+
this.emit({ type: 'thinking_start' });
|
|
1645
2467
|
const attemptController = new AbortController();
|
|
2468
|
+
let providerAttemptActive = true;
|
|
1646
2469
|
let timedOut = false;
|
|
1647
2470
|
let removeAbortListener;
|
|
1648
2471
|
// Inactivity guard, not a total-duration cap: harness-owning backends
|
|
@@ -1659,30 +2482,47 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1659
2482
|
// this to tell a user cancel (skip failover) from a hung account (rotate).
|
|
1660
2483
|
userSignal: signal,
|
|
1661
2484
|
timeoutMs,
|
|
1662
|
-
onActivity: () =>
|
|
2485
|
+
onActivity: () => {
|
|
2486
|
+
if (providerAttemptActive && !signal?.aborted && !attemptController.signal.aborted)
|
|
2487
|
+
touchActivity();
|
|
2488
|
+
},
|
|
1663
2489
|
// A backend reconnect is a sign of life too: the Router is still holding
|
|
1664
2490
|
// the turn, so the inactivity guard must not fire during the backoff.
|
|
1665
2491
|
onTransportRetry: (info) => {
|
|
2492
|
+
if (!providerAttemptActive || signal?.aborted || attemptController.signal.aborted)
|
|
2493
|
+
return;
|
|
1666
2494
|
touchActivity();
|
|
1667
2495
|
this.emit({
|
|
1668
2496
|
type: 'api_retry',
|
|
1669
2497
|
attempt: info.attempt,
|
|
1670
2498
|
maxAttempts: info.maxAttempts,
|
|
1671
2499
|
delay: info.delayMs,
|
|
1672
|
-
reason: 'Connection to the LLM endpoint dropped',
|
|
2500
|
+
reason: info.rateLimited ? 'Rate limit exceeded' : 'Connection to the LLM endpoint dropped',
|
|
2501
|
+
...(info.rateLimited ? { rateLimited: true } : {}),
|
|
1673
2502
|
});
|
|
1674
2503
|
},
|
|
1675
2504
|
onObservation: (observation) => {
|
|
1676
|
-
|
|
1677
|
-
|
|
2505
|
+
if (providerAttemptActive && !signal?.aborted && !attemptController.signal.aborted) {
|
|
2506
|
+
touchActivity();
|
|
2507
|
+
if (observation.kind !== 'reasoning')
|
|
2508
|
+
effectObserved = true;
|
|
2509
|
+
}
|
|
2510
|
+
// Original reports remain evidence after retirement; liveness and
|
|
2511
|
+
// visible progress must stay with the request that produced them.
|
|
2512
|
+
return onProviderObservation(observation, () => providerAttemptActive && !signal?.aborted && !attemptController.signal.aborted);
|
|
1678
2513
|
},
|
|
1679
2514
|
onToolCall: async (name, args) => {
|
|
2515
|
+
if (!providerAttemptActive || signal?.aborted || attemptController.signal.aborted) {
|
|
2516
|
+
throw new AgentError('Provider request retired or cancelled; no new tool call was admitted.', 'CANCELLED');
|
|
2517
|
+
}
|
|
2518
|
+
effectObserved = true;
|
|
1680
2519
|
// A tool stops when the caller cancels or this provider attempt times
|
|
1681
2520
|
// out, whichever comes first.
|
|
1682
|
-
const result = await this.
|
|
2521
|
+
const result = await this.executeHarnessToolCallWithCheckpoint(name, args, {
|
|
1683
2522
|
signal: signal ? AbortSignal.any([signal, attemptController.signal]) : attemptController.signal,
|
|
1684
2523
|
onActivity: touchActivity,
|
|
1685
|
-
|
|
2524
|
+
onUsage,
|
|
2525
|
+
}, checkpointContext);
|
|
1686
2526
|
return result.output;
|
|
1687
2527
|
},
|
|
1688
2528
|
};
|
|
@@ -1717,34 +2557,52 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1717
2557
|
this._lastResponseStreamed = true;
|
|
1718
2558
|
try {
|
|
1719
2559
|
return await Promise.race([
|
|
1720
|
-
this.streamAndAssemble(guardedRequest, signal, () => { answerOutputEmitted = true; }),
|
|
2560
|
+
this.streamAndAssemble(guardedRequest, signal, () => { answerOutputEmitted = true; }, onUsage).then(returned),
|
|
1721
2561
|
guardPromise,
|
|
1722
2562
|
]);
|
|
1723
2563
|
}
|
|
1724
2564
|
catch (streamErr) {
|
|
2565
|
+
if (this.toolCheckpointFailure)
|
|
2566
|
+
throw this.toolCheckpointFailure;
|
|
1725
2567
|
// If streaming failed before emitting anything (and not because of
|
|
1726
2568
|
// cancel/timeout), fall back to the buffered generate() for this
|
|
1727
2569
|
// attempt — never regress a turn just because generateStream can't
|
|
1728
2570
|
// serve this request.
|
|
1729
2571
|
if (this.backend.allowsBufferedFallbackAfterStreamFailure?.(guardedRequest) === false
|
|
1730
|
-
|| answerOutputEmitted || timedOut || signal?.aborted
|
|
2572
|
+
|| !canRegenerate() || effectObserved || answerOutputEmitted || timedOut || signal?.aborted
|
|
1731
2573
|
|| (streamErr instanceof AgentError && streamErr.code === 'CANCELLED')) {
|
|
1732
2574
|
throw streamErr;
|
|
1733
2575
|
}
|
|
1734
2576
|
this._lastResponseStreamed = false;
|
|
1735
2577
|
return await Promise.race([
|
|
1736
|
-
this.backend.generate(guardedRequest)
|
|
2578
|
+
this.backend.generate(guardedRequest).then(response => {
|
|
2579
|
+
if (response.usage)
|
|
2580
|
+
onUsage?.(response.usage);
|
|
2581
|
+
return returned(response);
|
|
2582
|
+
}),
|
|
1737
2583
|
guardPromise,
|
|
1738
2584
|
]);
|
|
1739
2585
|
}
|
|
1740
2586
|
}
|
|
1741
2587
|
this._lastResponseStreamed = false;
|
|
1742
2588
|
return await Promise.race([
|
|
1743
|
-
this.backend.generate(guardedRequest)
|
|
2589
|
+
this.backend.generate(guardedRequest).then(response => {
|
|
2590
|
+
if (response.usage)
|
|
2591
|
+
onUsage?.(response.usage);
|
|
2592
|
+
return returned(response);
|
|
2593
|
+
}),
|
|
1744
2594
|
guardPromise,
|
|
1745
2595
|
]);
|
|
1746
2596
|
}
|
|
1747
2597
|
catch (err) {
|
|
2598
|
+
onUsage?.({ inputTokens: 0, outputTokens: 0, totalTokens: 0, estimated: true, unreported: true });
|
|
2599
|
+
providerAttemptActive = false;
|
|
2600
|
+
if (this.toolCheckpointFailure)
|
|
2601
|
+
throw this.toolCheckpointFailure;
|
|
2602
|
+
if (err instanceof NativeObservationLimitError) {
|
|
2603
|
+
this.nativeEvidenceFailure ??= new AgentError(err.message, 'NATIVE_OBSERVATION_LIMIT');
|
|
2604
|
+
throw this.nativeEvidenceFailure;
|
|
2605
|
+
}
|
|
1748
2606
|
if (err instanceof AgentError && err.code === 'CANCELLED') {
|
|
1749
2607
|
throw err;
|
|
1750
2608
|
}
|
|
@@ -1759,7 +2617,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1759
2617
|
// — re-streaming would duplicate that text in the UI. Fail the turn
|
|
1760
2618
|
// instead. (Connect-time failures, and failures during the reasoning
|
|
1761
2619
|
// phase only, still retry.)
|
|
1762
|
-
if (useStream && answerOutputEmitted) {
|
|
2620
|
+
if (effectObserved || !canRegenerate() || useStream && answerOutputEmitted) {
|
|
1763
2621
|
throw new AgentError(this.parseAPIError(error).reason, 'API_ERROR');
|
|
1764
2622
|
}
|
|
1765
2623
|
const { retryable, reason, kind } = this.parseAPIError(error);
|
|
@@ -1770,20 +2628,37 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1770
2628
|
// attempt resumes the same Router-buffered turn instead of appending a
|
|
1771
2629
|
// duplicate. Only a failure AFTER answer text is unrecoverable, and the
|
|
1772
2630
|
// answerOutputEmitted guard above already handled that case.
|
|
1773
|
-
if (!retryable || attempt ===
|
|
2631
|
+
if (!retryable || maxAttempts === 1 || attempt >= maxAttempts && recoveryWindowMs === 0) {
|
|
1774
2632
|
if (kind === 'context_overflow') {
|
|
1775
2633
|
throw new ContextOverflowError(reason, request, error.message);
|
|
1776
2634
|
}
|
|
1777
2635
|
throw new AgentError(reason, 'API_ERROR');
|
|
1778
2636
|
}
|
|
1779
|
-
|
|
1780
|
-
const
|
|
2637
|
+
lastReason = reason;
|
|
2638
|
+
const recovering = attempt >= maxAttempts;
|
|
2639
|
+
if (recovering)
|
|
2640
|
+
recoveryStartedAt ??= Date.now();
|
|
2641
|
+
const elapsedMs = recoveryStartedAt === undefined ? 0 : Math.max(0, Date.now() - recoveryStartedAt);
|
|
2642
|
+
if (recovering && recoveryWindowMs !== null && elapsedMs >= recoveryWindowMs) {
|
|
2643
|
+
throw new AgentError(reason, 'API_ERROR');
|
|
2644
|
+
}
|
|
2645
|
+
// Keep the exact pending inference alive. Never resubmit the user turn
|
|
2646
|
+
// or revisit already checkpointed tool results to recover an endpoint.
|
|
2647
|
+
let delay = Math.max(apiRetryDelayMs(attempt, this.config.apiRetry), retryAfterDelayMs(error));
|
|
2648
|
+
if (recovering) {
|
|
2649
|
+
delay = Math.max(1, delay);
|
|
2650
|
+
if (recoveryWindowMs !== null)
|
|
2651
|
+
delay = Math.min(delay, recoveryWindowMs - elapsedMs);
|
|
2652
|
+
}
|
|
1781
2653
|
this.emit({
|
|
1782
2654
|
type: 'api_retry',
|
|
1783
2655
|
attempt,
|
|
1784
2656
|
maxAttempts,
|
|
1785
2657
|
delay,
|
|
1786
|
-
reason
|
|
2658
|
+
reason,
|
|
2659
|
+
phase: recovering ? 'recovery' : 'retry',
|
|
2660
|
+
elapsedMs,
|
|
2661
|
+
...(kind === 'rate_limit' ? { rateLimited: true } : {}),
|
|
1787
2662
|
});
|
|
1788
2663
|
removeAbortListener?.();
|
|
1789
2664
|
removeAbortListener = undefined;
|
|
@@ -1791,15 +2666,16 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1791
2666
|
await waitForRetryDelay(delay, signal);
|
|
1792
2667
|
}
|
|
1793
2668
|
finally {
|
|
2669
|
+
providerAttemptActive = false;
|
|
1794
2670
|
removeAbortListener?.();
|
|
1795
2671
|
}
|
|
1796
2672
|
}
|
|
1797
|
-
// Should never reach here, but TypeScript needs it
|
|
1798
|
-
throw new AgentError('Unexpected retry loop exit', 'API_ERROR');
|
|
1799
2673
|
}
|
|
1800
2674
|
// Parse API error to determine if retryable and extract user-friendly message
|
|
1801
2675
|
parseAPIError(error) {
|
|
1802
|
-
|
|
2676
|
+
// A provider may embed an oversized/malformed diagnostic in an Error.
|
|
2677
|
+
// Keep all parsing bounded even for transports with their own error type.
|
|
2678
|
+
const message = error.message.slice(0, 64 * 1024);
|
|
1803
2679
|
// Check for status codes in error message
|
|
1804
2680
|
const statusMatch = message.match(/API error \((\d+)\)/);
|
|
1805
2681
|
const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : 0;
|
|
@@ -1811,9 +2687,12 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1811
2687
|
let errorMessage = '';
|
|
1812
2688
|
let admissionMessage = '';
|
|
1813
2689
|
try {
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2690
|
+
// Provider envelopes start with an object after the diagnostic prefix.
|
|
2691
|
+
// Index searches avoid a greedy regex restarting at every forged colon.
|
|
2692
|
+
const objectStart = message.indexOf('{');
|
|
2693
|
+
const objectEnd = message.lastIndexOf('}');
|
|
2694
|
+
if (objectStart >= 0 && objectEnd >= objectStart) {
|
|
2695
|
+
const parsed = JSON.parse(message.slice(objectStart, objectEnd + 1));
|
|
1817
2696
|
const errorPayload = parsed.error;
|
|
1818
2697
|
if (typeof errorPayload === 'string') {
|
|
1819
2698
|
errorMessage = errorPayload;
|
|
@@ -1848,6 +2727,11 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1848
2727
|
catch {
|
|
1849
2728
|
// Not valid JSON, use raw message
|
|
1850
2729
|
}
|
|
2730
|
+
if (statusCode === 410 && message.startsWith('ZRouter API error (')
|
|
2731
|
+
&& ['stream_producer_lost', 'stream_producer_failed', 'stream_resume_expired'].includes(errorCode)) {
|
|
2732
|
+
return { retryable: false, reason: 'The Router stream was interrupted and cannot be resumed. '
|
|
2733
|
+
+ 'Send a message to continue from the existing conversation.' };
|
|
2734
|
+
}
|
|
1851
2735
|
// Detected before the generic 400 bucket so the host can dump the request.
|
|
1852
2736
|
if (errorCode === 'context_length_exceeded' || isContextOverflowMessage(`${errorMessage} ${message}`)) {
|
|
1853
2737
|
return { retryable: false, reason: 'Context window exceeded', kind: 'context_overflow' };
|
|
@@ -1857,10 +2741,15 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1857
2741
|
return { retryable: true, reason: 'API is overloaded' };
|
|
1858
2742
|
}
|
|
1859
2743
|
if (statusCode === 429 || errorType === 'rate_limit_error' || errorType === 'RESOURCE_EXHAUSTED') {
|
|
1860
|
-
return { retryable: true, reason: 'Rate limit exceeded' };
|
|
2744
|
+
return { retryable: true, reason: 'Rate limit exceeded', kind: 'rate_limit' };
|
|
1861
2745
|
}
|
|
1862
|
-
if (statusCode === 503 || statusCode === 502 || statusCode === 500 || errorType === 'UNAVAILABLE' || errorType === 'INTERNAL') {
|
|
1863
|
-
|
|
2746
|
+
if (statusCode === 504 || statusCode === 503 || statusCode === 502 || statusCode === 500 || errorType === 'UNAVAILABLE' || errorType === 'INTERNAL') {
|
|
2747
|
+
// Only Router admission guidance is public. Provider diagnostics stay
|
|
2748
|
+
// private; a bounded response-header ID supplies support correlation.
|
|
2749
|
+
const requestId = /(?:fly-request-id|x-request-id)=([A-Za-z0-9._:-]{1,128})(?:[ \]]|$)/.exec(message)?.[1];
|
|
2750
|
+
const detail = [admissionMessage.trim().replace(/\s+/g, ' '), requestId && `request_id: ${requestId}`].filter(Boolean).join('; ');
|
|
2751
|
+
const cause = detail ? `: ${detail.length > 160 ? `${detail.slice(0, 159)}…` : detail}` : '';
|
|
2752
|
+
return { retryable: true, reason: `Upstream server error (${statusCode || 'no status'})${cause}` };
|
|
1864
2753
|
}
|
|
1865
2754
|
// Non-retryable errors with clean messages
|
|
1866
2755
|
if (statusCode === 401 || errorType === 'authentication_error' || errorType === 'UNAUTHENTICATED') {
|
|
@@ -1876,7 +2765,7 @@ INVESTIGATION STRATEGY — before making tool calls to explore or debug, follow
|
|
|
1876
2765
|
// unsubscribed user hits on the default route, so say what to do. Only the
|
|
1877
2766
|
// Router's H3 envelope may supply its public wording; any other 402 body
|
|
1878
2767
|
// stays private behind the canonical message.
|
|
1879
|
-
if (statusCode === 402) {
|
|
2768
|
+
if (statusCode === 402 || errorCode === 'billing_required') {
|
|
1880
2769
|
const guidance = 'Subscribe in Zerg Router (Dashboard → Billing), or choose another model with /model.';
|
|
1881
2770
|
return {
|
|
1882
2771
|
retryable: false,
|