@sema-ai/core 1.275.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/CHANGELOG.md +5433 -0
- package/LICENSE +27 -0
- package/NOTICE.md +30 -0
- package/README.md +1040 -0
- package/dist/agents/agent-definition.d.ts +10 -0
- package/dist/agents/agent-definition.d.ts.map +1 -0
- package/dist/agents/agent-definition.js +18 -0
- package/dist/agents/agent-definition.js.map +1 -0
- package/dist/agents/builtin-agents.d.ts +69 -0
- package/dist/agents/builtin-agents.d.ts.map +1 -0
- package/dist/agents/builtin-agents.js +177 -0
- package/dist/agents/builtin-agents.js.map +1 -0
- package/dist/agents/cascade.d.ts +101 -0
- package/dist/agents/cascade.d.ts.map +1 -0
- package/dist/agents/cascade.js +189 -0
- package/dist/agents/cascade.js.map +1 -0
- package/dist/agents/observer.d.ts +209 -0
- package/dist/agents/observer.d.ts.map +1 -0
- package/dist/agents/observer.js +495 -0
- package/dist/agents/observer.js.map +1 -0
- package/dist/agents/repair-loop.d.ts +236 -0
- package/dist/agents/repair-loop.d.ts.map +1 -0
- package/dist/agents/repair-loop.js +293 -0
- package/dist/agents/repair-loop.js.map +1 -0
- package/dist/agents/session-util.d.ts +8 -0
- package/dist/agents/session-util.d.ts.map +1 -0
- package/dist/agents/session-util.js +9 -0
- package/dist/agents/session-util.js.map +1 -0
- package/dist/agents/subagent-steps.d.ts +93 -0
- package/dist/agents/subagent-steps.d.ts.map +1 -0
- package/dist/agents/subagent-steps.js +206 -0
- package/dist/agents/subagent-steps.js.map +1 -0
- package/dist/agents/subagent.d.ts +441 -0
- package/dist/agents/subagent.d.ts.map +1 -0
- package/dist/agents/subagent.js +2492 -0
- package/dist/agents/subagent.js.map +1 -0
- package/dist/agents/suspend-guard.d.ts +35 -0
- package/dist/agents/suspend-guard.d.ts.map +1 -0
- package/dist/agents/suspend-guard.js +48 -0
- package/dist/agents/suspend-guard.js.map +1 -0
- package/dist/agents/teacher.d.ts +137 -0
- package/dist/agents/teacher.d.ts.map +1 -0
- package/dist/agents/teacher.js +575 -0
- package/dist/agents/teacher.js.map +1 -0
- package/dist/agents/team.d.ts +211 -0
- package/dist/agents/team.d.ts.map +1 -0
- package/dist/agents/team.js +293 -0
- package/dist/agents/team.js.map +1 -0
- package/dist/agents/tool-filter.d.ts +29 -0
- package/dist/agents/tool-filter.d.ts.map +1 -0
- package/dist/agents/tool-filter.js +50 -0
- package/dist/agents/tool-filter.js.map +1 -0
- package/dist/agents/verify.d.ts +177 -0
- package/dist/agents/verify.d.ts.map +1 -0
- package/dist/agents/verify.js +305 -0
- package/dist/agents/verify.js.map +1 -0
- package/dist/bench/metrics.d.ts +714 -0
- package/dist/bench/metrics.d.ts.map +1 -0
- package/dist/bench/metrics.js +618 -0
- package/dist/bench/metrics.js.map +1 -0
- package/dist/bin/sema-tb.d.ts +3 -0
- package/dist/bin/sema-tb.d.ts.map +1 -0
- package/dist/bin/sema-tb.js +516 -0
- package/dist/bin/sema-tb.js.map +1 -0
- package/dist/bin/tb-env.d.ts +13 -0
- package/dist/bin/tb-env.d.ts.map +1 -0
- package/dist/bin/tb-env.js +26 -0
- package/dist/bin/tb-env.js.map +1 -0
- package/dist/brain/anthropic.d.ts +46 -0
- package/dist/brain/anthropic.d.ts.map +1 -0
- package/dist/brain/anthropic.js +830 -0
- package/dist/brain/anthropic.js.map +1 -0
- package/dist/brain/circuit-breaker.d.ts +46 -0
- package/dist/brain/circuit-breaker.d.ts.map +1 -0
- package/dist/brain/circuit-breaker.js +173 -0
- package/dist/brain/circuit-breaker.js.map +1 -0
- package/dist/brain/degrading.d.ts +98 -0
- package/dist/brain/degrading.d.ts.map +1 -0
- package/dist/brain/degrading.js +249 -0
- package/dist/brain/degrading.js.map +1 -0
- package/dist/brain/errors.d.ts +31 -0
- package/dist/brain/errors.d.ts.map +1 -0
- package/dist/brain/errors.js +58 -0
- package/dist/brain/errors.js.map +1 -0
- package/dist/brain/failover.d.ts +18 -0
- package/dist/brain/failover.d.ts.map +1 -0
- package/dist/brain/failover.js +118 -0
- package/dist/brain/failover.js.map +1 -0
- package/dist/brain/model-presets.d.ts +36 -0
- package/dist/brain/model-presets.d.ts.map +1 -0
- package/dist/brain/model-presets.js +54 -0
- package/dist/brain/model-presets.js.map +1 -0
- package/dist/brain/openai.d.ts +43 -0
- package/dist/brain/openai.d.ts.map +1 -0
- package/dist/brain/openai.js +784 -0
- package/dist/brain/openai.js.map +1 -0
- package/dist/brain/reasoning.d.ts +138 -0
- package/dist/brain/reasoning.d.ts.map +1 -0
- package/dist/brain/reasoning.js +146 -0
- package/dist/brain/reasoning.js.map +1 -0
- package/dist/brain/repetition.d.ts +60 -0
- package/dist/brain/repetition.d.ts.map +1 -0
- package/dist/brain/repetition.js +121 -0
- package/dist/brain/repetition.js.map +1 -0
- package/dist/brain/request-params.d.ts +29 -0
- package/dist/brain/request-params.d.ts.map +1 -0
- package/dist/brain/request-params.js +78 -0
- package/dist/brain/request-params.js.map +1 -0
- package/dist/brain/retry.d.ts +18 -0
- package/dist/brain/retry.d.ts.map +1 -0
- package/dist/brain/retry.js +42 -0
- package/dist/brain/retry.js.map +1 -0
- package/dist/brain/routing.d.ts +19 -0
- package/dist/brain/routing.d.ts.map +1 -0
- package/dist/brain/routing.js +44 -0
- package/dist/brain/routing.js.map +1 -0
- package/dist/brain/status-sink.d.ts +46 -0
- package/dist/brain/status-sink.d.ts.map +1 -0
- package/dist/brain/status-sink.js +36 -0
- package/dist/brain/status-sink.js.map +1 -0
- package/dist/brain/stream-engine.d.ts +85 -0
- package/dist/brain/stream-engine.d.ts.map +1 -0
- package/dist/brain/stream-engine.js +391 -0
- package/dist/brain/stream-engine.js.map +1 -0
- package/dist/brain/timeout.d.ts +44 -0
- package/dist/brain/timeout.d.ts.map +1 -0
- package/dist/brain/timeout.js +41 -0
- package/dist/brain/timeout.js.map +1 -0
- package/dist/brain/tool-call-repair.d.ts +15 -0
- package/dist/brain/tool-call-repair.d.ts.map +1 -0
- package/dist/brain/tool-call-repair.js +67 -0
- package/dist/brain/tool-call-repair.js.map +1 -0
- package/dist/brain/walltime.d.ts +14 -0
- package/dist/brain/walltime.d.ts.map +1 -0
- package/dist/brain/walltime.js +14 -0
- package/dist/brain/walltime.js.map +1 -0
- package/dist/core/arg-summary.d.ts +19 -0
- package/dist/core/arg-summary.d.ts.map +1 -0
- package/dist/core/arg-summary.js +96 -0
- package/dist/core/arg-summary.js.map +1 -0
- package/dist/core/ask-question.d.ts +113 -0
- package/dist/core/ask-question.d.ts.map +1 -0
- package/dist/core/ask-question.js +246 -0
- package/dist/core/ask-question.js.map +1 -0
- package/dist/core/auto-compaction.d.ts +341 -0
- package/dist/core/auto-compaction.d.ts.map +1 -0
- package/dist/core/auto-compaction.js +495 -0
- package/dist/core/auto-compaction.js.map +1 -0
- package/dist/core/auto-promote.d.ts +126 -0
- package/dist/core/auto-promote.d.ts.map +1 -0
- package/dist/core/auto-promote.js +171 -0
- package/dist/core/auto-promote.js.map +1 -0
- package/dist/core/background-shell.d.ts +128 -0
- package/dist/core/background-shell.d.ts.map +1 -0
- package/dist/core/background-shell.js +42 -0
- package/dist/core/background-shell.js.map +1 -0
- package/dist/core/cache-break-detector.d.ts +63 -0
- package/dist/core/cache-break-detector.d.ts.map +1 -0
- package/dist/core/cache-break-detector.js +0 -0
- package/dist/core/cache-break-detector.js.map +1 -0
- package/dist/core/canonical-json.d.ts +45 -0
- package/dist/core/canonical-json.d.ts.map +1 -0
- package/dist/core/canonical-json.js +132 -0
- package/dist/core/canonical-json.js.map +1 -0
- package/dist/core/checkpoint-store.d.ts +1101 -0
- package/dist/core/checkpoint-store.d.ts.map +1 -0
- package/dist/core/checkpoint-store.js +514 -0
- package/dist/core/checkpoint-store.js.map +1 -0
- package/dist/core/consolidate-scope.d.ts +94 -0
- package/dist/core/consolidate-scope.d.ts.map +1 -0
- package/dist/core/consolidate-scope.js +158 -0
- package/dist/core/consolidate-scope.js.map +1 -0
- package/dist/core/context-edit.d.ts +82 -0
- package/dist/core/context-edit.d.ts.map +1 -0
- package/dist/core/context-edit.js +149 -0
- package/dist/core/context-edit.js.map +1 -0
- package/dist/core/context-guard.d.ts +43 -0
- package/dist/core/context-guard.d.ts.map +1 -0
- package/dist/core/context-guard.js +171 -0
- package/dist/core/context-guard.js.map +1 -0
- package/dist/core/dynamic-recall.d.ts +100 -0
- package/dist/core/dynamic-recall.d.ts.map +1 -0
- package/dist/core/dynamic-recall.js +108 -0
- package/dist/core/dynamic-recall.js.map +1 -0
- package/dist/core/exec-gate.d.ts +74 -0
- package/dist/core/exec-gate.d.ts.map +1 -0
- package/dist/core/exec-gate.js +143 -0
- package/dist/core/exec-gate.js.map +1 -0
- package/dist/core/exec-output-tail.d.ts +37 -0
- package/dist/core/exec-output-tail.d.ts.map +1 -0
- package/dist/core/exec-output-tail.js +63 -0
- package/dist/core/exec-output-tail.js.map +1 -0
- package/dist/core/file-snapshot-store.d.ts +137 -0
- package/dist/core/file-snapshot-store.d.ts.map +1 -0
- package/dist/core/file-snapshot-store.js +319 -0
- package/dist/core/file-snapshot-store.js.map +1 -0
- package/dist/core/git-worktree-env.d.ts +41 -0
- package/dist/core/git-worktree-env.d.ts.map +1 -0
- package/dist/core/git-worktree-env.js +64 -0
- package/dist/core/git-worktree-env.js.map +1 -0
- package/dist/core/hooks.d.ts +372 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +269 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/lsp-diagnostics.d.ts +74 -0
- package/dist/core/lsp-diagnostics.d.ts.map +1 -0
- package/dist/core/lsp-diagnostics.js +125 -0
- package/dist/core/lsp-diagnostics.js.map +1 -0
- package/dist/core/lsp-protocol.d.ts +28 -0
- package/dist/core/lsp-protocol.d.ts.map +1 -0
- package/dist/core/lsp-protocol.js +179 -0
- package/dist/core/lsp-protocol.js.map +1 -0
- package/dist/core/lsp-session.d.ts +56 -0
- package/dist/core/lsp-session.d.ts.map +1 -0
- package/dist/core/lsp-session.js +185 -0
- package/dist/core/lsp-session.js.map +1 -0
- package/dist/core/lsp.d.ts +132 -0
- package/dist/core/lsp.d.ts.map +1 -0
- package/dist/core/lsp.js +147 -0
- package/dist/core/lsp.js.map +1 -0
- package/dist/core/mcp.d.ts +294 -0
- package/dist/core/mcp.d.ts.map +1 -0
- package/dist/core/mcp.js +992 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/media-byte-cap.d.ts +34 -0
- package/dist/core/media-byte-cap.d.ts.map +1 -0
- package/dist/core/media-byte-cap.js +76 -0
- package/dist/core/media-byte-cap.js.map +1 -0
- package/dist/core/memory-engine/data-plane.d.ts +103 -0
- package/dist/core/memory-engine/data-plane.d.ts.map +1 -0
- package/dist/core/memory-engine/data-plane.js +272 -0
- package/dist/core/memory-engine/data-plane.js.map +1 -0
- package/dist/core/memory-engine/dual-root.d.ts +40 -0
- package/dist/core/memory-engine/dual-root.d.ts.map +1 -0
- package/dist/core/memory-engine/dual-root.js +95 -0
- package/dist/core/memory-engine/dual-root.js.map +1 -0
- package/dist/core/memory-engine/engine.d.ts +161 -0
- package/dist/core/memory-engine/engine.d.ts.map +1 -0
- package/dist/core/memory-engine/engine.js +1110 -0
- package/dist/core/memory-engine/engine.js.map +1 -0
- package/dist/core/memory-engine/file-backend.d.ts +162 -0
- package/dist/core/memory-engine/file-backend.d.ts.map +1 -0
- package/dist/core/memory-engine/file-backend.js +948 -0
- package/dist/core/memory-engine/file-backend.js.map +1 -0
- package/dist/core/memory-engine/frontmatter.d.ts +24 -0
- package/dist/core/memory-engine/frontmatter.d.ts.map +1 -0
- package/dist/core/memory-engine/frontmatter.js +135 -0
- package/dist/core/memory-engine/frontmatter.js.map +1 -0
- package/dist/core/memory-engine/index.d.ts +13 -0
- package/dist/core/memory-engine/index.d.ts.map +1 -0
- package/dist/core/memory-engine/index.js +28 -0
- package/dist/core/memory-engine/index.js.map +1 -0
- package/dist/core/memory-engine/layout.d.ts +123 -0
- package/dist/core/memory-engine/layout.d.ts.map +1 -0
- package/dist/core/memory-engine/layout.js +643 -0
- package/dist/core/memory-engine/layout.js.map +1 -0
- package/dist/core/memory-engine/memory-backend-contract.d.ts +22 -0
- package/dist/core/memory-engine/memory-backend-contract.d.ts.map +1 -0
- package/dist/core/memory-engine/memory-backend-contract.js +271 -0
- package/dist/core/memory-engine/memory-backend-contract.js.map +1 -0
- package/dist/core/memory-engine/migrate.d.ts +17 -0
- package/dist/core/memory-engine/migrate.d.ts.map +1 -0
- package/dist/core/memory-engine/migrate.js +35 -0
- package/dist/core/memory-engine/migrate.js.map +1 -0
- package/dist/core/memory-engine/scan.d.ts +20 -0
- package/dist/core/memory-engine/scan.d.ts.map +1 -0
- package/dist/core/memory-engine/scan.js +84 -0
- package/dist/core/memory-engine/scan.js.map +1 -0
- package/dist/core/memory-engine/scope-contract.d.ts +104 -0
- package/dist/core/memory-engine/scope-contract.d.ts.map +1 -0
- package/dist/core/memory-engine/scope-contract.js +288 -0
- package/dist/core/memory-engine/scope-contract.js.map +1 -0
- package/dist/core/memory-engine/sync-client.d.ts +100 -0
- package/dist/core/memory-engine/sync-client.d.ts.map +1 -0
- package/dist/core/memory-engine/sync-client.js +328 -0
- package/dist/core/memory-engine/sync-client.js.map +1 -0
- package/dist/core/memory-engine/sync.d.ts +97 -0
- package/dist/core/memory-engine/sync.d.ts.map +1 -0
- package/dist/core/memory-engine/sync.js +91 -0
- package/dist/core/memory-engine/sync.js.map +1 -0
- package/dist/core/memory-engine/types.d.ts +279 -0
- package/dist/core/memory-engine/types.d.ts.map +1 -0
- package/dist/core/memory-engine/types.js +9 -0
- package/dist/core/memory-engine/types.js.map +1 -0
- package/dist/core/memory-recall.d.ts +261 -0
- package/dist/core/memory-recall.d.ts.map +1 -0
- package/dist/core/memory-recall.js +600 -0
- package/dist/core/memory-recall.js.map +1 -0
- package/dist/core/memory-vector.d.ts +24 -0
- package/dist/core/memory-vector.d.ts.map +1 -0
- package/dist/core/memory-vector.js +51 -0
- package/dist/core/memory-vector.js.map +1 -0
- package/dist/core/memory.d.ts +502 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +881 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/message-utils.d.ts +7 -0
- package/dist/core/message-utils.d.ts.map +1 -0
- package/dist/core/message-utils.js +10 -0
- package/dist/core/message-utils.js.map +1 -0
- package/dist/core/oracle-isolation.d.ts +85 -0
- package/dist/core/oracle-isolation.d.ts.map +1 -0
- package/dist/core/oracle-isolation.js +121 -0
- package/dist/core/oracle-isolation.js.map +1 -0
- package/dist/core/permission-rules.d.ts +102 -0
- package/dist/core/permission-rules.d.ts.map +1 -0
- package/dist/core/permission-rules.js +357 -0
- package/dist/core/permission-rules.js.map +1 -0
- package/dist/core/present-plan-tool.d.ts +28 -0
- package/dist/core/present-plan-tool.d.ts.map +1 -0
- package/dist/core/present-plan-tool.js +119 -0
- package/dist/core/present-plan-tool.js.map +1 -0
- package/dist/core/pricing.d.ts +48 -0
- package/dist/core/pricing.d.ts.map +1 -0
- package/dist/core/pricing.js +46 -0
- package/dist/core/pricing.js.map +1 -0
- package/dist/core/property-harness.d.ts +126 -0
- package/dist/core/property-harness.d.ts.map +1 -0
- package/dist/core/property-harness.js +192 -0
- package/dist/core/property-harness.js.map +1 -0
- package/dist/core/push-queue.d.ts +10 -0
- package/dist/core/push-queue.d.ts.map +1 -0
- package/dist/core/push-queue.js +42 -0
- package/dist/core/push-queue.js.map +1 -0
- package/dist/core/remote-env.d.ts +432 -0
- package/dist/core/remote-env.d.ts.map +1 -0
- package/dist/core/remote-env.js +77 -0
- package/dist/core/remote-env.js.map +1 -0
- package/dist/core/roles.d.ts +74 -0
- package/dist/core/roles.d.ts.map +1 -0
- package/dist/core/roles.js +272 -0
- package/dist/core/roles.js.map +1 -0
- package/dist/core/runner/active-skill-scope.d.ts +64 -0
- package/dist/core/runner/active-skill-scope.d.ts.map +1 -0
- package/dist/core/runner/active-skill-scope.js +197 -0
- package/dist/core/runner/active-skill-scope.js.map +1 -0
- package/dist/core/runner/assemble-result.d.ts +191 -0
- package/dist/core/runner/assemble-result.d.ts.map +1 -0
- package/dist/core/runner/assemble-result.js +250 -0
- package/dist/core/runner/assemble-result.js.map +1 -0
- package/dist/core/runner/call-cap.d.ts +130 -0
- package/dist/core/runner/call-cap.d.ts.map +1 -0
- package/dist/core/runner/call-cap.js +151 -0
- package/dist/core/runner/call-cap.js.map +1 -0
- package/dist/core/runner/grounding-signal.d.ts +12 -0
- package/dist/core/runner/grounding-signal.d.ts.map +1 -0
- package/dist/core/runner/grounding-signal.js +94 -0
- package/dist/core/runner/grounding-signal.js.map +1 -0
- package/dist/core/runner/image.d.ts +23 -0
- package/dist/core/runner/image.d.ts.map +1 -0
- package/dist/core/runner/image.js +151 -0
- package/dist/core/runner/image.js.map +1 -0
- package/dist/core/runner/memory-consolidation.d.ts +112 -0
- package/dist/core/runner/memory-consolidation.d.ts.map +1 -0
- package/dist/core/runner/memory-consolidation.js +372 -0
- package/dist/core/runner/memory-consolidation.js.map +1 -0
- package/dist/core/runner/prepare-task.d.ts +583 -0
- package/dist/core/runner/prepare-task.d.ts.map +1 -0
- package/dist/core/runner/prepare-task.js +3612 -0
- package/dist/core/runner/prepare-task.js.map +1 -0
- package/dist/core/runner/prompt-suggestions.d.ts +33 -0
- package/dist/core/runner/prompt-suggestions.d.ts.map +1 -0
- package/dist/core/runner/prompt-suggestions.js +116 -0
- package/dist/core/runner/prompt-suggestions.js.map +1 -0
- package/dist/core/runner/runtask.d.ts +274 -0
- package/dist/core/runner/runtask.d.ts.map +1 -0
- package/dist/core/runner/runtask.js +4606 -0
- package/dist/core/runner/runtask.js.map +1 -0
- package/dist/core/runner/session-rule-policy.d.ts +28 -0
- package/dist/core/runner/session-rule-policy.d.ts.map +1 -0
- package/dist/core/runner/session-rule-policy.js +110 -0
- package/dist/core/runner/session-rule-policy.js.map +1 -0
- package/dist/core/runner/strict-output-schema.d.ts +36 -0
- package/dist/core/runner/strict-output-schema.d.ts.map +1 -0
- package/dist/core/runner/strict-output-schema.js +365 -0
- package/dist/core/runner/strict-output-schema.js.map +1 -0
- package/dist/core/runner/synthetic-tools.d.ts +72 -0
- package/dist/core/runner/synthetic-tools.d.ts.map +1 -0
- package/dist/core/runner/synthetic-tools.js +203 -0
- package/dist/core/runner/synthetic-tools.js.map +1 -0
- package/dist/core/runner/tool-disclosure.d.ts +117 -0
- package/dist/core/runner/tool-disclosure.d.ts.map +1 -0
- package/dist/core/runner/tool-disclosure.js +0 -0
- package/dist/core/runner/tool-disclosure.js.map +1 -0
- package/dist/core/runner/turn-attachments.d.ts +381 -0
- package/dist/core/runner/turn-attachments.d.ts.map +1 -0
- package/dist/core/runner/turn-attachments.js +596 -0
- package/dist/core/runner/turn-attachments.js.map +1 -0
- package/dist/core/runner/usage-accounting.d.ts +35 -0
- package/dist/core/runner/usage-accounting.d.ts.map +1 -0
- package/dist/core/runner/usage-accounting.js +45 -0
- package/dist/core/runner/usage-accounting.js.map +1 -0
- package/dist/core/runtime.d.ts +9 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +17 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/safety-axis-vocab.d.ts +33 -0
- package/dist/core/safety-axis-vocab.d.ts.map +1 -0
- package/dist/core/safety-axis-vocab.js +35 -0
- package/dist/core/safety-axis-vocab.js.map +1 -0
- package/dist/core/safety-merge-corpus.d.ts +48 -0
- package/dist/core/safety-merge-corpus.d.ts.map +1 -0
- package/dist/core/safety-merge-corpus.js +203 -0
- package/dist/core/safety-merge-corpus.js.map +1 -0
- package/dist/core/scheduler.d.ts +126 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +54 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/secret-env.d.ts +23 -0
- package/dist/core/secret-env.d.ts.map +1 -0
- package/dist/core/secret-env.js +35 -0
- package/dist/core/secret-env.js.map +1 -0
- package/dist/core/select-model.d.ts +26 -0
- package/dist/core/select-model.d.ts.map +1 -0
- package/dist/core/select-model.js +58 -0
- package/dist/core/select-model.js.map +1 -0
- package/dist/core/sensitive-path-policy.d.ts +24 -0
- package/dist/core/sensitive-path-policy.d.ts.map +1 -0
- package/dist/core/sensitive-path-policy.js +96 -0
- package/dist/core/sensitive-path-policy.js.map +1 -0
- package/dist/core/session-policy-store.d.ts +102 -0
- package/dist/core/session-policy-store.d.ts.map +1 -0
- package/dist/core/session-policy-store.js +177 -0
- package/dist/core/session-policy-store.js.map +1 -0
- package/dist/core/session-reconcile.d.ts +63 -0
- package/dist/core/session-reconcile.d.ts.map +1 -0
- package/dist/core/session-reconcile.js +71 -0
- package/dist/core/session-reconcile.js.map +1 -0
- package/dist/core/session-store.d.ts +99 -0
- package/dist/core/session-store.d.ts.map +1 -0
- package/dist/core/session-store.js +298 -0
- package/dist/core/session-store.js.map +1 -0
- package/dist/core/session.d.ts +144 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +56 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/spec-contract.d.ts +147 -0
- package/dist/core/spec-contract.d.ts.map +1 -0
- package/dist/core/spec-contract.js +340 -0
- package/dist/core/spec-contract.js.map +1 -0
- package/dist/core/strategy-store.d.ts +50 -0
- package/dist/core/strategy-store.d.ts.map +1 -0
- package/dist/core/strategy-store.js +100 -0
- package/dist/core/strategy-store.js.map +1 -0
- package/dist/core/stub-env.d.ts +35 -0
- package/dist/core/stub-env.d.ts.map +1 -0
- package/dist/core/stub-env.js +69 -0
- package/dist/core/stub-env.js.map +1 -0
- package/dist/core/task-notification.d.ts +115 -0
- package/dist/core/task-notification.d.ts.map +1 -0
- package/dist/core/task-notification.js +247 -0
- package/dist/core/task-notification.js.map +1 -0
- package/dist/core/task-outcome.d.ts +80 -0
- package/dist/core/task-outcome.d.ts.map +1 -0
- package/dist/core/task-outcome.js +40 -0
- package/dist/core/task-outcome.js.map +1 -0
- package/dist/core/task-registry.d.ts +349 -0
- package/dist/core/task-registry.d.ts.map +1 -0
- package/dist/core/task-registry.js +1751 -0
- package/dist/core/task-registry.js.map +1 -0
- package/dist/core/tighten-task-spec.d.ts +27 -0
- package/dist/core/tighten-task-spec.d.ts.map +1 -0
- package/dist/core/tighten-task-spec.js +95 -0
- package/dist/core/tighten-task-spec.js.map +1 -0
- package/dist/core/tool-detach.d.ts +31 -0
- package/dist/core/tool-detach.d.ts.map +1 -0
- package/dist/core/tool-detach.js +62 -0
- package/dist/core/tool-detach.js.map +1 -0
- package/dist/core/tool-errors.d.ts +72 -0
- package/dist/core/tool-errors.d.ts.map +1 -0
- package/dist/core/tool-errors.js +194 -0
- package/dist/core/tool-errors.js.map +1 -0
- package/dist/core/tool-name-aliases.d.ts +5 -0
- package/dist/core/tool-name-aliases.d.ts.map +1 -0
- package/dist/core/tool-name-aliases.js +130 -0
- package/dist/core/tool-name-aliases.js.map +1 -0
- package/dist/core/tool-policy.d.ts +295 -0
- package/dist/core/tool-policy.d.ts.map +1 -0
- package/dist/core/tool-policy.js +567 -0
- package/dist/core/tool-policy.js.map +1 -0
- package/dist/core/tool-result-budget.d.ts +44 -0
- package/dist/core/tool-result-budget.d.ts.map +1 -0
- package/dist/core/tool-result-budget.js +140 -0
- package/dist/core/tool-result-budget.js.map +1 -0
- package/dist/core/tool-result-store.d.ts +136 -0
- package/dist/core/tool-result-store.d.ts.map +1 -0
- package/dist/core/tool-result-store.js +219 -0
- package/dist/core/tool-result-store.js.map +1 -0
- package/dist/core/tools.d.ts +14 -0
- package/dist/core/tools.d.ts.map +1 -0
- package/dist/core/tools.js +98 -0
- package/dist/core/tools.js.map +1 -0
- package/dist/core/trace.d.ts +340 -0
- package/dist/core/trace.d.ts.map +1 -0
- package/dist/core/trace.js +12 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/core/types.d.ts +2828 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/untrusted-egress.d.ts +32 -0
- package/dist/core/untrusted-egress.d.ts.map +1 -0
- package/dist/core/untrusted-egress.js +49 -0
- package/dist/core/untrusted-egress.js.map +1 -0
- package/dist/core/untrusted-text.d.ts +64 -0
- package/dist/core/untrusted-text.d.ts.map +1 -0
- package/dist/core/untrusted-text.js +119 -0
- package/dist/core/untrusted-text.js.map +1 -0
- package/dist/core/warm-resume.d.ts +23 -0
- package/dist/core/warm-resume.d.ts.map +1 -0
- package/dist/core/warm-resume.js +28 -0
- package/dist/core/warm-resume.js.map +1 -0
- package/dist/core/with-retry.d.ts +43 -0
- package/dist/core/with-retry.d.ts.map +1 -0
- package/dist/core/with-retry.js +59 -0
- package/dist/core/with-retry.js.map +1 -0
- package/dist/core/workflow-journal-store.d.ts +44 -0
- package/dist/core/workflow-journal-store.d.ts.map +1 -0
- package/dist/core/workflow-journal-store.js +47 -0
- package/dist/core/workflow-journal-store.js.map +1 -0
- package/dist/core/workflow-run-store-contract.d.ts +31 -0
- package/dist/core/workflow-run-store-contract.d.ts.map +1 -0
- package/dist/core/workflow-run-store-contract.js +191 -0
- package/dist/core/workflow-run-store-contract.js.map +1 -0
- package/dist/core/workflow-run-store.d.ts +149 -0
- package/dist/core/workflow-run-store.d.ts.map +1 -0
- package/dist/core/workflow-run-store.js +110 -0
- package/dist/core/workflow-run-store.js.map +1 -0
- package/dist/engine/compaction/compaction.d.ts +139 -0
- package/dist/engine/compaction/compaction.d.ts.map +1 -0
- package/dist/engine/compaction/compaction.js +1030 -0
- package/dist/engine/compaction/compaction.js.map +1 -0
- package/dist/engine/compaction/utils.d.ts +45 -0
- package/dist/engine/compaction/utils.d.ts.map +1 -0
- package/dist/engine/compaction/utils.js +203 -0
- package/dist/engine/compaction/utils.js.map +1 -0
- package/dist/engine/execution-env/kill-tree.d.ts +20 -0
- package/dist/engine/execution-env/kill-tree.d.ts.map +1 -0
- package/dist/engine/execution-env/kill-tree.js +183 -0
- package/dist/engine/execution-env/kill-tree.js.map +1 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +133 -0
- package/dist/engine/execution-env/node-execution-env.d.ts.map +1 -0
- package/dist/engine/execution-env/node-execution-env.js +1031 -0
- package/dist/engine/execution-env/node-execution-env.js.map +1 -0
- package/dist/engine/harness/agent-harness.d.ts +174 -0
- package/dist/engine/harness/agent-harness.d.ts.map +1 -0
- package/dist/engine/harness/agent-harness.js +1053 -0
- package/dist/engine/harness/agent-harness.js.map +1 -0
- package/dist/engine/harness/messages.d.ts +17 -0
- package/dist/engine/harness/messages.d.ts.map +1 -0
- package/dist/engine/harness/messages.js +122 -0
- package/dist/engine/harness/messages.js.map +1 -0
- package/dist/engine/harness/types.d.ts +713 -0
- package/dist/engine/harness/types.d.ts.map +1 -0
- package/dist/engine/harness/types.js +100 -0
- package/dist/engine/harness/types.js.map +1 -0
- package/dist/engine/llm/diagnostics.d.ts +23 -0
- package/dist/engine/llm/diagnostics.d.ts.map +1 -0
- package/dist/engine/llm/diagnostics.js +32 -0
- package/dist/engine/llm/diagnostics.js.map +1 -0
- package/dist/engine/llm/event-stream.d.ts +25 -0
- package/dist/engine/llm/event-stream.d.ts.map +1 -0
- package/dist/engine/llm/event-stream.js +103 -0
- package/dist/engine/llm/event-stream.js.map +1 -0
- package/dist/engine/llm/index.d.ts +12 -0
- package/dist/engine/llm/index.d.ts.map +1 -0
- package/dist/engine/llm/index.js +12 -0
- package/dist/engine/llm/index.js.map +1 -0
- package/dist/engine/llm/types.d.ts +753 -0
- package/dist/engine/llm/types.d.ts.map +1 -0
- package/dist/engine/llm/types.js +2 -0
- package/dist/engine/llm/types.js.map +1 -0
- package/dist/engine/llm/validation.d.ts +8 -0
- package/dist/engine/llm/validation.d.ts.map +1 -0
- package/dist/engine/llm/validation.js +318 -0
- package/dist/engine/llm/validation.js.map +1 -0
- package/dist/engine/loop/agent-loop.d.ts +74 -0
- package/dist/engine/loop/agent-loop.d.ts.map +1 -0
- package/dist/engine/loop/agent-loop.js +1490 -0
- package/dist/engine/loop/agent-loop.js.map +1 -0
- package/dist/engine/loop/runtime-deps.d.ts +17 -0
- package/dist/engine/loop/runtime-deps.d.ts.map +1 -0
- package/dist/engine/loop/runtime-deps.js +21 -0
- package/dist/engine/loop/runtime-deps.js.map +1 -0
- package/dist/engine/loop/types.d.ts +609 -0
- package/dist/engine/loop/types.d.ts.map +1 -0
- package/dist/engine/loop/types.js +2 -0
- package/dist/engine/loop/types.js.map +1 -0
- package/dist/engine/lsp/frame-decoder.d.ts +16 -0
- package/dist/engine/lsp/frame-decoder.d.ts.map +1 -0
- package/dist/engine/lsp/frame-decoder.js +53 -0
- package/dist/engine/lsp/frame-decoder.js.map +1 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +105 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts.map +1 -0
- package/dist/engine/lsp/node-lsp-manager.js +343 -0
- package/dist/engine/lsp/node-lsp-manager.js.map +1 -0
- package/dist/engine/lsp/stdio-lsp-transport.d.ts +37 -0
- package/dist/engine/lsp/stdio-lsp-transport.d.ts.map +1 -0
- package/dist/engine/lsp/stdio-lsp-transport.js +185 -0
- package/dist/engine/lsp/stdio-lsp-transport.js.map +1 -0
- package/dist/engine/session/import-validate.d.ts +44 -0
- package/dist/engine/session/import-validate.d.ts.map +1 -0
- package/dist/engine/session/import-validate.js +92 -0
- package/dist/engine/session/import-validate.js.map +1 -0
- package/dist/engine/session/memory-repo.d.ts +25 -0
- package/dist/engine/session/memory-repo.d.ts.map +1 -0
- package/dist/engine/session/memory-repo.js +59 -0
- package/dist/engine/session/memory-repo.js.map +1 -0
- package/dist/engine/session/memory-storage.d.ts +14 -0
- package/dist/engine/session/memory-storage.d.ts.map +1 -0
- package/dist/engine/session/memory-storage.js +17 -0
- package/dist/engine/session/memory-storage.js.map +1 -0
- package/dist/engine/session/repo-utils.d.ts +11 -0
- package/dist/engine/session/repo-utils.d.ts.map +1 -0
- package/dist/engine/session/repo-utils.js +40 -0
- package/dist/engine/session/repo-utils.js.map +1 -0
- package/dist/engine/session/session.d.ts +34 -0
- package/dist/engine/session/session.d.ts.map +1 -0
- package/dist/engine/session/session.js +227 -0
- package/dist/engine/session/session.js.map +1 -0
- package/dist/engine/session/storage-base.d.ts +38 -0
- package/dist/engine/session/storage-base.d.ts.map +1 -0
- package/dist/engine/session/storage-base.js +157 -0
- package/dist/engine/session/storage-base.js.map +1 -0
- package/dist/engine/session/timestamps.d.ts +3 -0
- package/dist/engine/session/timestamps.d.ts.map +1 -0
- package/dist/engine/session/timestamps.js +15 -0
- package/dist/engine/session/timestamps.js.map +1 -0
- package/dist/engine/session/uuid.d.ts +2 -0
- package/dist/engine/session/uuid.d.ts.map +1 -0
- package/dist/engine/session/uuid.js +50 -0
- package/dist/engine/session/uuid.js.map +1 -0
- package/dist/index.d.ts +160 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/harness.d.ts +26 -0
- package/dist/internal/harness.d.ts.map +1 -0
- package/dist/internal/harness.js +24 -0
- package/dist/internal/harness.js.map +1 -0
- package/dist/internal/llm.d.ts +9 -0
- package/dist/internal/llm.d.ts.map +1 -0
- package/dist/internal/llm.js +8 -0
- package/dist/internal/llm.js.map +1 -0
- package/dist/orchestration/builtin-workflows.d.ts +44 -0
- package/dist/orchestration/builtin-workflows.d.ts.map +1 -0
- package/dist/orchestration/builtin-workflows.js +165 -0
- package/dist/orchestration/builtin-workflows.js.map +1 -0
- package/dist/orchestration/dev-vm-script-runner.d.ts +24 -0
- package/dist/orchestration/dev-vm-script-runner.d.ts.map +1 -0
- package/dist/orchestration/dev-vm-script-runner.js +106 -0
- package/dist/orchestration/dev-vm-script-runner.js.map +1 -0
- package/dist/orchestration/goal.d.ts +97 -0
- package/dist/orchestration/goal.d.ts.map +1 -0
- package/dist/orchestration/goal.js +248 -0
- package/dist/orchestration/goal.js.map +1 -0
- package/dist/orchestration/run-spec.d.ts +63 -0
- package/dist/orchestration/run-spec.d.ts.map +1 -0
- package/dist/orchestration/run-spec.js +274 -0
- package/dist/orchestration/run-spec.js.map +1 -0
- package/dist/orchestration/run-workflow-tool.d.ts +187 -0
- package/dist/orchestration/run-workflow-tool.d.ts.map +1 -0
- package/dist/orchestration/run-workflow-tool.js +612 -0
- package/dist/orchestration/run-workflow-tool.js.map +1 -0
- package/dist/orchestration/workflow-governance.d.ts +70 -0
- package/dist/orchestration/workflow-governance.d.ts.map +1 -0
- package/dist/orchestration/workflow-governance.js +176 -0
- package/dist/orchestration/workflow-governance.js.map +1 -0
- package/dist/orchestration/workflow-meta.d.ts +41 -0
- package/dist/orchestration/workflow-meta.d.ts.map +1 -0
- package/dist/orchestration/workflow-meta.js +426 -0
- package/dist/orchestration/workflow-meta.js.map +1 -0
- package/dist/orchestration/workflow-observe.d.ts +77 -0
- package/dist/orchestration/workflow-observe.d.ts.map +1 -0
- package/dist/orchestration/workflow-observe.js +177 -0
- package/dist/orchestration/workflow-observe.js.map +1 -0
- package/dist/orchestration/workflow-primitives.d.ts +31 -0
- package/dist/orchestration/workflow-primitives.d.ts.map +1 -0
- package/dist/orchestration/workflow-primitives.js +104 -0
- package/dist/orchestration/workflow-primitives.js.map +1 -0
- package/dist/orchestration/workflow-sandbox-conformance.d.ts +34 -0
- package/dist/orchestration/workflow-sandbox-conformance.d.ts.map +1 -0
- package/dist/orchestration/workflow-sandbox-conformance.js +301 -0
- package/dist/orchestration/workflow-sandbox-conformance.js.map +1 -0
- package/dist/orchestration/workflow-script-runner.d.ts +127 -0
- package/dist/orchestration/workflow-script-runner.d.ts.map +1 -0
- package/dist/orchestration/workflow-script-runner.js +48 -0
- package/dist/orchestration/workflow-script-runner.js.map +1 -0
- package/dist/orchestration/workflow-script-store.d.ts +99 -0
- package/dist/orchestration/workflow-script-store.d.ts.map +1 -0
- package/dist/orchestration/workflow-script-store.js +133 -0
- package/dist/orchestration/workflow-script-store.js.map +1 -0
- package/dist/orchestration/workflow-size-guideline.d.ts +50 -0
- package/dist/orchestration/workflow-size-guideline.d.ts.map +1 -0
- package/dist/orchestration/workflow-size-guideline.js +78 -0
- package/dist/orchestration/workflow-size-guideline.js.map +1 -0
- package/dist/orchestration/workflow.d.ts +624 -0
- package/dist/orchestration/workflow.d.ts.map +1 -0
- package/dist/orchestration/workflow.js +1596 -0
- package/dist/orchestration/workflow.js.map +1 -0
- package/dist/prompts/default.d.ts +474 -0
- package/dist/prompts/default.d.ts.map +1 -0
- package/dist/prompts/default.js +676 -0
- package/dist/prompts/default.js.map +1 -0
- package/dist/prompts/supervisor.d.ts +60 -0
- package/dist/prompts/supervisor.d.ts.map +1 -0
- package/dist/prompts/supervisor.js +162 -0
- package/dist/prompts/supervisor.js.map +1 -0
- package/dist/scenarios/env.d.ts +41 -0
- package/dist/scenarios/env.d.ts.map +1 -0
- package/dist/scenarios/env.js +65 -0
- package/dist/scenarios/env.js.map +1 -0
- package/dist/scenarios/full-body.d.ts +47 -0
- package/dist/scenarios/full-body.d.ts.map +1 -0
- package/dist/scenarios/full-body.js +36 -0
- package/dist/scenarios/full-body.js.map +1 -0
- package/dist/scenarios/scenario-registry.d.ts +107 -0
- package/dist/scenarios/scenario-registry.d.ts.map +1 -0
- package/dist/scenarios/scenario-registry.js +192 -0
- package/dist/scenarios/scenario-registry.js.map +1 -0
- package/dist/scenarios/teacher-quickstart.d.ts +41 -0
- package/dist/scenarios/teacher-quickstart.d.ts.map +1 -0
- package/dist/scenarios/teacher-quickstart.js +32 -0
- package/dist/scenarios/teacher-quickstart.js.map +1 -0
- package/dist/server/http.d.ts +36 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +144 -0
- package/dist/server/http.js.map +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +62 -0
- package/dist/stores/file/checkpoint-store.d.ts.map +1 -0
- package/dist/stores/file/checkpoint-store.js +250 -0
- package/dist/stores/file/checkpoint-store.js.map +1 -0
- package/dist/stores/file/file-snapshot-store.d.ts +43 -0
- package/dist/stores/file/file-snapshot-store.d.ts.map +1 -0
- package/dist/stores/file/file-snapshot-store.js +374 -0
- package/dist/stores/file/file-snapshot-store.js.map +1 -0
- package/dist/stores/file/fs-atomic.d.ts +95 -0
- package/dist/stores/file/fs-atomic.d.ts.map +1 -0
- package/dist/stores/file/fs-atomic.js +415 -0
- package/dist/stores/file/fs-atomic.js.map +1 -0
- package/dist/stores/file/index.d.ts +98 -0
- package/dist/stores/file/index.d.ts.map +1 -0
- package/dist/stores/file/index.js +109 -0
- package/dist/stores/file/index.js.map +1 -0
- package/dist/stores/file/memory-store.d.ts +95 -0
- package/dist/stores/file/memory-store.d.ts.map +1 -0
- package/dist/stores/file/memory-store.js +393 -0
- package/dist/stores/file/memory-store.js.map +1 -0
- package/dist/stores/file/session-policy-store.d.ts +24 -0
- package/dist/stores/file/session-policy-store.d.ts.map +1 -0
- package/dist/stores/file/session-policy-store.js +123 -0
- package/dist/stores/file/session-policy-store.js.map +1 -0
- package/dist/stores/file/session-store.d.ts +34 -0
- package/dist/stores/file/session-store.d.ts.map +1 -0
- package/dist/stores/file/session-store.js +175 -0
- package/dist/stores/file/session-store.js.map +1 -0
- package/dist/stores/file/tool-result-store.d.ts +23 -0
- package/dist/stores/file/tool-result-store.d.ts.map +1 -0
- package/dist/stores/file/tool-result-store.js +61 -0
- package/dist/stores/file/tool-result-store.js.map +1 -0
- package/dist/stores/file/workflow-journal-store.d.ts +36 -0
- package/dist/stores/file/workflow-journal-store.d.ts.map +1 -0
- package/dist/stores/file/workflow-journal-store.js +157 -0
- package/dist/stores/file/workflow-journal-store.js.map +1 -0
- package/dist/stores/file/workflow-run-store.d.ts +58 -0
- package/dist/stores/file/workflow-run-store.d.ts.map +1 -0
- package/dist/stores/file/workflow-run-store.js +178 -0
- package/dist/stores/file/workflow-run-store.js.map +1 -0
- package/dist/stores/pg.d.ts +210 -0
- package/dist/stores/pg.d.ts.map +1 -0
- package/dist/stores/pg.js +652 -0
- package/dist/stores/pg.js.map +1 -0
- package/dist/tools/fs/encoding.d.ts +51 -0
- package/dist/tools/fs/encoding.d.ts.map +1 -0
- package/dist/tools/fs/encoding.js +74 -0
- package/dist/tools/fs/encoding.js.map +1 -0
- package/dist/tools/fs/gh-rate-limit.d.ts +19 -0
- package/dist/tools/fs/gh-rate-limit.d.ts.map +1 -0
- package/dist/tools/fs/gh-rate-limit.js +35 -0
- package/dist/tools/fs/gh-rate-limit.js.map +1 -0
- package/dist/tools/fs/index.d.ts +246 -0
- package/dist/tools/fs/index.d.ts.map +1 -0
- package/dist/tools/fs/index.js +2130 -0
- package/dist/tools/fs/index.js.map +1 -0
- package/dist/tools/fs/pdf.d.ts +106 -0
- package/dist/tools/fs/pdf.d.ts.map +1 -0
- package/dist/tools/fs/pdf.js +178 -0
- package/dist/tools/fs/pdf.js.map +1 -0
- package/dist/tools/fs/repo-map.d.ts +15 -0
- package/dist/tools/fs/repo-map.d.ts.map +1 -0
- package/dist/tools/fs/repo-map.js +251 -0
- package/dist/tools/fs/repo-map.js.map +1 -0
- package/dist/tools/fs/safety.d.ts +168 -0
- package/dist/tools/fs/safety.d.ts.map +1 -0
- package/dist/tools/fs/safety.js +479 -0
- package/dist/tools/fs/safety.js.map +1 -0
- package/dist/tools/fs/search.d.ts +118 -0
- package/dist/tools/fs/search.d.ts.map +1 -0
- package/dist/tools/fs/search.js +650 -0
- package/dist/tools/fs/search.js.map +1 -0
- package/dist/tools/gitea-issue.d.ts +39 -0
- package/dist/tools/gitea-issue.d.ts.map +1 -0
- package/dist/tools/gitea-issue.js +91 -0
- package/dist/tools/gitea-issue.js.map +1 -0
- package/dist/tools/monitor.d.ts +64 -0
- package/dist/tools/monitor.d.ts.map +1 -0
- package/dist/tools/monitor.js +135 -0
- package/dist/tools/monitor.js.map +1 -0
- package/dist/tools/scheduler-tools.d.ts +48 -0
- package/dist/tools/scheduler-tools.d.ts.map +1 -0
- package/dist/tools/scheduler-tools.js +449 -0
- package/dist/tools/scheduler-tools.js.map +1 -0
- package/dist/tools/sql-adapters.d.ts +32 -0
- package/dist/tools/sql-adapters.d.ts.map +1 -0
- package/dist/tools/sql-adapters.js +31 -0
- package/dist/tools/sql-adapters.js.map +1 -0
- package/dist/tools/sql.d.ts +36 -0
- package/dist/tools/sql.d.ts.map +1 -0
- package/dist/tools/sql.js +131 -0
- package/dist/tools/sql.js.map +1 -0
- package/dist/tools/task-list.d.ts +26 -0
- package/dist/tools/task-list.d.ts.map +1 -0
- package/dist/tools/task-list.js +352 -0
- package/dist/tools/task-list.js.map +1 -0
- package/dist/tools/todo.d.ts +11 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +126 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/web.d.ts +59 -0
- package/dist/tools/web.d.ts.map +1 -0
- package/dist/tools/web.js +607 -0
- package/dist/tools/web.js.map +1 -0
- package/dist/tools/worktree.d.ts +88 -0
- package/dist/tools/worktree.d.ts.map +1 -0
- package/dist/tools/worktree.js +434 -0
- package/dist/tools/worktree.js.map +1 -0
- package/docs/A1-ATTRIBUTION-ROUND1-2026-07-09.md +181 -0
- package/docs/AB-ORACLE-GATE-VERDICT-2026-07-10.md +100 -0
- package/docs/ANCHOR-GAP-INVENTORY-2026-07-09.md +143 -0
- package/docs/ARCHITECTURE.md +125 -0
- package/docs/BUGHUNT-2026-07-05-NIGHT.md +98 -0
- package/docs/BUILD.md +63 -0
- package/docs/CANCEL-ASYNC-ATTRIBUTION-2026-07-09.md +98 -0
- package/docs/CC-198-VS-201-DRIFT-2026-07-09.md +70 -0
- package/docs/CC-BASELINE-FAKE-ZERO-AUDIT-2026-07-09.md +51 -0
- package/docs/CC-DRIFT-198-206-AGENT-TYPES-2026-07-10.md +82 -0
- package/docs/CC-DRIFT-198-206-CHANGELOG-2026-07-10.md +82 -0
- package/docs/CC-DRIFT-198-206-REMINDERS-2026-07-10.md +96 -0
- package/docs/CC-DRIFT-198-206-ROADMAP-2026-07-10.md +110 -0
- package/docs/CC-DRIFT-198-206-TOOL-INVENTORY-2026-07-10.md +53 -0
- package/docs/CC-DRIFT-198-206-USAGE-CONSTANTS-2026-07-10.md +64 -0
- package/docs/CC-DRIFT-198-206-WORKFLOW-2026-07-10.md +83 -0
- package/docs/CC-PARITY-CHECKLIST.md +220 -0
- package/docs/CC-PARITY-DEEP-SWEEP-2026-07-07.md +149 -0
- package/docs/CC-SUBAGENT-PROMPT-STUDY-2026-07-09.md +219 -0
- package/docs/CC-TASK-REMINDER-CADENCE-STUDY-2026-07-09.md +396 -0
- package/docs/CC-TOOL-PARITY-SWEEP-2026-07.md +56 -0
- package/docs/CC-TOOL-PROMPT-DEEP-DIFF-2026-07-08.md +274 -0
- package/docs/CC-TOOLSURFACE-DEEP-DIVE-2026-07-07.md +197 -0
- package/docs/CC206-OBSERVER-ANCHORS-2026-07-11.md +463 -0
- package/docs/CC207-CORE-SWEEP-BACKLOG-2026-07-12.md +155 -0
- package/docs/CENTER-CONTROL-PLANE-SEAM.md +86 -0
- package/docs/CLEANUP-CONTRACT-ANCHOR-2026-07-09.md +83 -0
- package/docs/COLD-START-RSI-2026-07-09.md +72 -0
- package/docs/COMPACTION-LIVE-AUDIT-2026-07-09.md +114 -0
- package/docs/CONTEXT-lifecycle-extension-points-and-stop-attribution.md +71 -0
- package/docs/COREWARS-POLYGLOT-TRIAGE-2026-07-09.md +85 -0
- package/docs/DEADTARGET-OVERFULL-HBOX-2026-07-10.md +92 -0
- package/docs/DEADTARGET-PATH-TRACING-REVERSE-2026-07-10.md +87 -0
- package/docs/DEADTARGET-WINNING-AVG-COREWARS-2026-07-10.md +96 -0
- package/docs/DEEPSEEK-API-LIMITS-PROBE-2026-07-09.md +118 -0
- package/docs/DESIGN-140-MOCK-VERIFY-2026-07-11.md +79 -0
- package/docs/DESIGN-140-VERIFICATION-2026-07-11.md +43 -0
- package/docs/DESIGN-DRAFT-workflow-collab-2026-07-11.md +56 -0
- package/docs/DESIGN-ORACLE-GROUNDING-GATE-2026-07-10.md +167 -0
- package/docs/DESIGN-TFRAG-RECONCILE-SCAFFOLD-2026-07-09.md +102 -0
- package/docs/EXP-PA2-PE-DESIGN-2026-07-11.md +220 -0
- package/docs/EXT-DOGFOOD-2026-07-12.md +50 -0
- package/docs/FINAL-VERIFY-SURFACE-AUDIT-2026-07-09.md +44 -0
- package/docs/FRAG-RECON-INVESTIGATION-2026-07-09.md +183 -0
- package/docs/HANDOFF.md +121 -0
- package/docs/KNOWN-ISSUES.md +96 -0
- package/docs/LEGW-WINDOW-TAX-2026-07-10.md +82 -0
- package/docs/LIVE-PROBE-BATCH-2026-07-11.md +207 -0
- package/docs/M2-CERT-ATTRIBUTION-2026-07-10.md +61 -0
- package/docs/M25-LEG-VARIANCE-ATTRIBUTION-2026-07-10.md +81 -0
- package/docs/MAILMAN-TIMEOUT-ATTRIBUTION-2026-07-10.md +88 -0
- package/docs/MEMORY-BACKEND-DIALECT-NOTES.md +69 -0
- package/docs/MTEB-ESCAPE-HATCH-ATTRIBUTION-2026-07-09.md +57 -0
- package/docs/MTEB-LEADERBOARD-ATTRIBUTION-2026-07-10.md +134 -0
- package/docs/ORACLE-GATE-NEGATIVE-SAMPLE-2026-07-11.md +28 -0
- package/docs/PARITY-CONTEXT-TRANSACTIONS-2026-07-10.md +67 -0
- package/docs/PARITY-PROGRESS-MATRIX-2026-07-10.md +37 -0
- package/docs/PARITY-SPOT-AGENT-TYPES-2026-07-10.md +70 -0
- package/docs/PARITY-SPOT-REMINDERS-2026-07-10.md +144 -0
- package/docs/PARITY-SPOT-TOOL-INVENTORY-2026-07-10.md +61 -0
- package/docs/PARITY-SPOT-USAGE-CONSTANTS-2026-07-10.md +95 -0
- package/docs/PARITY-SPOT-WORKFLOW-2026-07-10.md +93 -0
- package/docs/PASSWORD-RECOVERY-PANEL-VERDICT-2026-07-10.md +62 -0
- package/docs/R8-R9-FINALIZE-COORDINATION-2026-07-09.md +124 -0
- package/docs/REPL-CODE-MODE-EVALUATION-2026-07-10.md +187 -0
- package/docs/REPORT-1249-TARGETED.md +48 -0
- package/docs/RESEARCH-3P-MEMORY-SWAP-2026-07-12.md +273 -0
- package/docs/ROADMAP.md +111 -0
- package/docs/RSI-CLOUD-DEPLOY-2026-07-06.md +289 -0
- package/docs/RSI-PROCESS-AI-COLDSTART-2026-07-11.md +68 -0
- package/docs/S18-THOROUGHNESS-AUDIT-2026-07-09.md +113 -0
- package/docs/SERVICE-INTEGRATION-GUIDE.md +298 -0
- package/docs/SUBAGENT-STREAM-RESILIENCE-PARITY-2026-07-10.md +118 -0
- package/docs/SWEBENCH-PRO-RECON-2026-07-11.md +31 -0
- package/docs/TB-EFFICIENCY-TELEMETRY-2026-07-08.md +114 -0
- package/docs/TB-FAILURE-AUTOPSY-2026-07-05-RERUN.md +67 -0
- package/docs/TB-FAIRNESS-AUDIT-2026-07-08.md +141 -0
- package/docs/TB-FULL-1257-2026-07-08.md +77 -0
- package/docs/TB-LEG-GAP-ATTRIBUTION-1261-1263-2026-07-11.md +154 -0
- package/docs/TB-REGRESSION-DAEMON-KILL-2026-07-08.md +58 -0
- package/docs/TB-RSI-CROSSCHECK-2026-07-06.md +68 -0
- package/docs/TB-RSI-LEDGER.md +30 -0
- package/docs/TB-STABLE-SET-2026-07-08.md +127 -0
- package/docs/TB21-RECON-2026-07-11.md +28 -0
- package/docs/TEST-DESIGN-BATCH-1-2-3.md +97 -0
- package/docs/THIRD-PARTY-INTEGRATION.md +45 -0
- package/docs/TOKEN-ESTIMATE-CALIBRATION-LIVE-2026-07-09.md +102 -0
- package/docs/audit-2026-06-10-/347/254/254/344/272/214/350/275/256-/347/251/272/347/231/275/345/214/272/344/270/216/345/277/230/346/216/245/347/272/277.md +147 -0
- package/docs/audit-2026-06-10-/350/256/241/347/256/227bug/346/211/253/346/217/217.md +163 -0
- package/docs/audit-2026-06-11-/344/277/256/345/244/215/345/244/215/345/256/241/344/270/216/345/233/236/345/275/222/344/270/223/346/211/253.md +95 -0
- package/docs/audit-2026-06-12-design71-P1/346/275/234/344/274/217/351/235/242/350/243/201/345/206/263/344/270/216/345/220/236/351/224/231/345/256/241/350/256/241.md +45 -0
- package/docs/audit-2026-06-12-/345/205/254/345/274/200/346/227/213/351/222/256/346/264/273/346/200/247/344/270/216vendored/346/255/273/350/267/257.md +63 -0
- package/docs/audit-2026-06-15/ADDENDUM-second-pass.md +88 -0
- package/docs/audit-2026-06-15/ARCHITECTURE-AND-ROADMAP.md +187 -0
- package/docs/audit-2026-06-15/DOC-CORRECTIONS.md +105 -0
- package/docs/audit-2026-06-15/INDEX.md +49 -0
- package/docs/audit-2026-06-15/ORCHESTRATION-STATUS.md +98 -0
- package/docs/audit-2026-06-15/PRODUCTION-ISSUES.md +189 -0
- package/docs/audit-2026-06-15/REFERENCES-literature.md +139 -0
- package/docs/audit-2026-06-15/VENDOR-REFACTOR-PLAN.md +96 -0
- package/docs/cc-probe-198/README.md +72 -0
- package/docs/cc-probe-198/STABILITY-AUDIT.md +118 -0
- package/docs/cc-probe-198/live-schema-monitor-worktree-2026-07-09.json +56 -0
- package/docs/cc-probe-198/mount-variants.json +536 -0
- package/docs/cc-probe-198/plan-mode-run.json +1 -0
- package/docs/cc-probe-198/probe-bash-bg.json +224 -0
- package/docs/cc-probe-198/probe-edit-mismatch.json +120 -0
- package/docs/cc-probe-198/probe-read-missing.json +62 -0
- package/docs/cc-probe-198/probe-search-empty.json +127 -0
- package/docs/cc-probe-198/probe-task-family.json +227 -0
- package/docs/cc-probe-198/probes.status +6 -0
- package/docs/cc-probe-198/raw-capture-interactive.json +948 -0
- package/docs/cc-probe-198/raw-capture-print.json +787 -0
- package/docs/cc-probe-198/raw-capture2-interactive.json +948 -0
- package/docs/cc-probe-198/raw-capture2-print.json +787 -0
- package/docs/cc-probe-198/system-prompt.txt +135 -0
- package/docs/cc-probe-198/tools-array.json +962 -0
- package/docs/durability-boundary.md +63 -0
- package/docs/fix-2026-06-10-/350/256/241/347/256/227bug/344/277/256/345/244/215-search/346/211/271.md +75 -0
- package/docs/fix-2026-06-11-/347/254/254/344/272/214/350/275/256-/344/277/256/345/244/215-search/346/211/271.md +62 -0
- package/docs/parity-sweep/REPORT-DYNAMIC.md +97 -0
- package/docs/parity-sweep/REPORT-STATIC-GGB.md +127 -0
- package/docs/parity-sweep/REPORT-STATIC-RWE.md +133 -0
- package/docs/vendor-history.md +385 -0
- package/docs/vendored-upstream-diff-2026-07.md +83 -0
- package/package.json +70 -0
|
@@ -0,0 +1,1490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party agent loop (design/118 ④a — de-vendoring 4/6).
|
|
3
|
+
*
|
|
4
|
+
* Rewritten from the vendored openclaw distillation with the loop's control flow made EXPLICIT as a
|
|
5
|
+
* state machine (the CC query.ts State/transition shape, sema deps injected): every "go around again"
|
|
6
|
+
* is a named {@link LoopContinue} and every exit is a named {@link LoopTerminal}, so tests can assert
|
|
7
|
+
* WHICH path ran (test/loop-transitions.test.ts) instead of inferring it from event order. ④a is
|
|
8
|
+
* behavior-1:1 with the vendored loop — no new recovery chains; those land in ④b as new Continue
|
|
9
|
+
* reasons on this skeleton.
|
|
10
|
+
*
|
|
11
|
+
* Load-bearing invariants carried over verbatim (the VENDORED EDIT provenance comments are kept at
|
|
12
|
+
* their sites as history):
|
|
13
|
+
* - A-1 layer ② abort-before-stream guard (a mid-batch abort must never reach the next provider
|
|
14
|
+
* request) — {@link runSingleTurn} entry guard, Terminal `aborted_before_stream`.
|
|
15
|
+
* - Durable double-execution gate (a deferred parallel tool closure must never run its side effect
|
|
16
|
+
* after a mid-batch suspend/abort) — {@link executePreparedToolCall} entry guard.
|
|
17
|
+
* - D4 post-hook posture (afterToolCall throw must not rewrite a committed side effect into a flat
|
|
18
|
+
* failure) — {@link finalizeExecutedToolCall}.
|
|
19
|
+
* - The TWO queue drain points (1.204 priority wiring基石): steer drains at every turn end
|
|
20
|
+
* (Continue `steer_injected` / mid-work boundary), followUp drains only when the agent would
|
|
21
|
+
* otherwise stop (Continue `follow_up_injected`). Never merge these into one queue.
|
|
22
|
+
* - text_delta.partial reconstruction (upstream cherry-pick 6fcc945702).
|
|
23
|
+
*/
|
|
24
|
+
import { EventStream as LlmEventStream } from "../llm/index.js";
|
|
25
|
+
import { findToolByName, validateToolArguments } from "../llm/index.js";
|
|
26
|
+
// VENDORED EDIT (design/136 §2.4, same class as the design/115 P0 findToolByName edit): the retired-tool
|
|
27
|
+
// tombstone table lives with OLD_TO_NEW in core (single "historical names" archive point); the roster-miss
|
|
28
|
+
// branch consults it AFTER findToolByName so a deployment's own same-named tool always wins.
|
|
29
|
+
import { TOMBSTONED_TOOLS } from "../../core/tool-name-aliases.js";
|
|
30
|
+
import { resolveAgentCoreStreamFn } from "./runtime-deps.js";
|
|
31
|
+
const EMPTY_USAGE = {
|
|
32
|
+
input: 0,
|
|
33
|
+
output: 0,
|
|
34
|
+
cacheRead: 0,
|
|
35
|
+
cacheWrite: 0,
|
|
36
|
+
totalTokens: 0,
|
|
37
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
38
|
+
};
|
|
39
|
+
const EventStreamConstructor = LlmEventStream;
|
|
40
|
+
function appendTextDeltaToAssistantMessage(message, contentIndex, delta) {
|
|
41
|
+
const content = [...message.content];
|
|
42
|
+
const currentContent = content[contentIndex];
|
|
43
|
+
content[contentIndex] =
|
|
44
|
+
currentContent?.type === "text"
|
|
45
|
+
? { ...currentContent, text: currentContent.text + delta }
|
|
46
|
+
: { type: "text", text: delta };
|
|
47
|
+
return { ...message, content };
|
|
48
|
+
}
|
|
49
|
+
function resolveAssistantMessageUpdate(event, currentMessage) {
|
|
50
|
+
if ("partial" in event && event.partial) {
|
|
51
|
+
return event.partial;
|
|
52
|
+
}
|
|
53
|
+
if (event.type === "text_delta") {
|
|
54
|
+
return appendTextDeltaToAssistantMessage(currentMessage, event.contentIndex, event.delta);
|
|
55
|
+
}
|
|
56
|
+
return currentMessage;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Start an agent loop with a new prompt message.
|
|
60
|
+
* The prompt is added to the context and events are emitted for it.
|
|
61
|
+
*/
|
|
62
|
+
export function agentLoop(prompts, context, config, signal, streamFn, runtime) {
|
|
63
|
+
const stream = createAgentStream();
|
|
64
|
+
void runAgentLoop(prompts, context, config, async (event) => {
|
|
65
|
+
stream.push(event);
|
|
66
|
+
}, signal, streamFn, runtime)
|
|
67
|
+
.then((messages) => {
|
|
68
|
+
stream.end(messages);
|
|
69
|
+
})
|
|
70
|
+
.catch((error) => {
|
|
71
|
+
pushLoopFailure(stream, config, error, signal?.aborted === true);
|
|
72
|
+
});
|
|
73
|
+
return stream;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Continue an agent loop from the current context without adding a new message.
|
|
77
|
+
* Used for retries - context already has user message or tool results.
|
|
78
|
+
*
|
|
79
|
+
* **Important:** The last message in context must convert to a `user` or `toolResult` message
|
|
80
|
+
* via `convertToLlm`. If it doesn't, the LLM provider will reject the request.
|
|
81
|
+
* This cannot be validated here since `convertToLlm` is only called once per turn.
|
|
82
|
+
*/
|
|
83
|
+
export function agentLoopContinue(context, config, signal, streamFn, runtime) {
|
|
84
|
+
if (context.messages.length === 0) {
|
|
85
|
+
throw new Error("Cannot continue: no messages in context");
|
|
86
|
+
}
|
|
87
|
+
if (context.messages[context.messages.length - 1].role === "assistant") {
|
|
88
|
+
throw new Error("Cannot continue from message role: assistant");
|
|
89
|
+
}
|
|
90
|
+
const stream = createAgentStream();
|
|
91
|
+
void runAgentLoopContinue(context, config, async (event) => {
|
|
92
|
+
stream.push(event);
|
|
93
|
+
}, signal, streamFn, runtime)
|
|
94
|
+
.then((messages) => {
|
|
95
|
+
stream.end(messages);
|
|
96
|
+
})
|
|
97
|
+
.catch((error) => {
|
|
98
|
+
pushLoopFailure(stream, config, error, signal?.aborted === true);
|
|
99
|
+
});
|
|
100
|
+
return stream;
|
|
101
|
+
}
|
|
102
|
+
/** Run a prompt-started loop and emit events through a caller-owned sink. */
|
|
103
|
+
export async function runAgentLoop(prompts, context, config, emit, signal, streamFn, runtime, trace) {
|
|
104
|
+
const newMessages = [...prompts];
|
|
105
|
+
const currentContext = {
|
|
106
|
+
...context,
|
|
107
|
+
messages: [...context.messages, ...prompts],
|
|
108
|
+
};
|
|
109
|
+
await emit({ type: "agent_start" });
|
|
110
|
+
await emit({ type: "turn_start" });
|
|
111
|
+
for (const prompt of prompts) {
|
|
112
|
+
await emit({ type: "message_start", message: prompt });
|
|
113
|
+
await emit({ type: "message_end", message: prompt });
|
|
114
|
+
}
|
|
115
|
+
await runLoop(currentContext, newMessages, config, signal, emit, streamFn, runtime, trace);
|
|
116
|
+
return newMessages;
|
|
117
|
+
}
|
|
118
|
+
/** Continue an existing loop context and emit only newly produced messages. */
|
|
119
|
+
export async function runAgentLoopContinue(context, config, emit, signal, streamFn, runtime, trace) {
|
|
120
|
+
if (context.messages.length === 0) {
|
|
121
|
+
throw new Error("Cannot continue: no messages in context");
|
|
122
|
+
}
|
|
123
|
+
if (context.messages[context.messages.length - 1].role === "assistant") {
|
|
124
|
+
throw new Error("Cannot continue from message role: assistant");
|
|
125
|
+
}
|
|
126
|
+
const newMessages = [];
|
|
127
|
+
const currentContext = { ...context };
|
|
128
|
+
await emit({ type: "agent_start" });
|
|
129
|
+
await emit({ type: "turn_start" });
|
|
130
|
+
await runLoop(currentContext, newMessages, config, signal, emit, streamFn, runtime, trace);
|
|
131
|
+
return newMessages;
|
|
132
|
+
}
|
|
133
|
+
function createAgentStream() {
|
|
134
|
+
return new EventStreamConstructor((event) => event.type === "agent_end", (event) => (event.type === "agent_end" ? event.messages : []));
|
|
135
|
+
}
|
|
136
|
+
function createLoopFailureMessage(config, error, aborted) {
|
|
137
|
+
return {
|
|
138
|
+
role: "assistant",
|
|
139
|
+
content: [{ type: "text", text: "" }],
|
|
140
|
+
api: config.model.api,
|
|
141
|
+
provider: config.model.provider,
|
|
142
|
+
model: config.model.id,
|
|
143
|
+
usage: EMPTY_USAGE,
|
|
144
|
+
stopReason: aborted ? "aborted" : "error",
|
|
145
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function pushLoopFailure(stream, config, error, aborted) {
|
|
150
|
+
const failureMessage = createLoopFailureMessage(config, error, aborted);
|
|
151
|
+
stream.push({ type: "message_start", message: failureMessage });
|
|
152
|
+
stream.push({ type: "message_end", message: failureMessage });
|
|
153
|
+
stream.push({ type: "turn_end", message: failureMessage, toolResults: [] });
|
|
154
|
+
stream.push({ type: "agent_end", messages: [failureMessage] });
|
|
155
|
+
}
|
|
156
|
+
// ④b default prompt-too-long classifier: conservative provider-message patterns (an integrator with a
|
|
157
|
+
// typed error channel overrides via `recovery.promptTooLong.detect`). Only consulted when a recovery
|
|
158
|
+
// hook is configured. Honest cost note: a false positive triggers one bounded recover() call — at the
|
|
159
|
+
// harness wiring that PERSISTS a forced compaction to the session (visible, not free), so patterns
|
|
160
|
+
// stay word-bounded and error-scoped; recovery-less runs are untouched either way.
|
|
161
|
+
const PROMPT_TOO_LONG_RE = /context.{0,3}length|context_length|context window (?:limit|exceeded)|reached its context window|prompt is too long|too many tokens|maximum context|exceeds? .{0,30}(?:context|token)|input is too long|\b413\b/i;
|
|
162
|
+
/** No non-blank text/thinking and no tool calls — mirrors the harness's empty-failure-persistence skip. */
|
|
163
|
+
function isBlankFailureContent(message) {
|
|
164
|
+
return message.content.every((c) => c.type === "text" ? c.text.trim() === "" : c.type === "thinking" ? c.thinking.trim() === "" : false);
|
|
165
|
+
}
|
|
166
|
+
function defaultDetectPromptTooLong(message) {
|
|
167
|
+
return message.stopReason === "error" && PROMPT_TOO_LONG_RE.test(message.errorMessage ?? "");
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* M1 (docs/PARITY-CONTEXT-TRANSACTIONS-2026-07-10 §4, CC 2.1.198 withhold parity): hold back the
|
|
171
|
+
* message_start/message_end of a stream-final that LOOKS like a recoverable prompt-too-long failure
|
|
172
|
+
* (error stop + classifier match + blank content — the same predicate the recovery gate uses), so
|
|
173
|
+
* the loop can decide the recovery outcome BEFORE consumers see any error. CC 198 does this inside
|
|
174
|
+
* its stream decoder (`claude-cli-2.1.198.pretty.js:473371-473374`, `if (!$s) yield`); ours is a
|
|
175
|
+
* hold-buffer at the loop's emit seam (degrading.ts heldStart pattern, adapted to the final).
|
|
176
|
+
*
|
|
177
|
+
* Safety shape: a real PTL rejection fails BEFORE the provider streams anything, so its
|
|
178
|
+
* message_start is the terminal-path one (emitted together with message_end, carrying the final
|
|
179
|
+
* error message) — both get held as a unit. A LIVE partial start (stopReason "stop" placeholder,
|
|
180
|
+
* stream-engine emptyAssistant) is never a candidate and passes through untouched, and once a live
|
|
181
|
+
* start went out we never hold the end (no dangling message_start on a later discard). All other
|
|
182
|
+
* events pass through verbatim — non-PTL error ordering is untouched by construction.
|
|
183
|
+
*/
|
|
184
|
+
function createPtlWithholdBuffer(emit, detect) {
|
|
185
|
+
const held = [];
|
|
186
|
+
let liveStartEmitted = false;
|
|
187
|
+
const isCandidate = (message) => message.role === "assistant" &&
|
|
188
|
+
message.stopReason === "error" &&
|
|
189
|
+
detect(message) &&
|
|
190
|
+
isBlankFailureContent(message);
|
|
191
|
+
return {
|
|
192
|
+
sink: async (event) => {
|
|
193
|
+
if (event.type === "message_start" && isCandidate(event.message)) {
|
|
194
|
+
held.push(event); // terminal-path start of a candidate failure (paired with its end below)
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (event.type === "message_end" && !liveStartEmitted && isCandidate(event.message)) {
|
|
198
|
+
held.push(event);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (event.type === "message_start")
|
|
202
|
+
liveStartEmitted = true;
|
|
203
|
+
await emit(event);
|
|
204
|
+
},
|
|
205
|
+
async flush() {
|
|
206
|
+
for (const event of held.splice(0))
|
|
207
|
+
await emit(event);
|
|
208
|
+
},
|
|
209
|
+
discard() {
|
|
210
|
+
held.length = 0;
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/** S2 (design/119): retry nudge for a `toolUse` stop that carried zero parseable tool calls. */
|
|
215
|
+
function createMalformedToolUseNudge() {
|
|
216
|
+
return {
|
|
217
|
+
role: "custom",
|
|
218
|
+
customType: "loop:malformed-tool-use-retry",
|
|
219
|
+
content: "Your previous response indicated tool use but contained no valid tool call. Re-issue the tool call(s) now, with complete and valid JSON arguments — provide exactly one JSON value per parameter, and do not truncate.",
|
|
220
|
+
display: false,
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** S2 (design/119): retry nudge for a turn that produced only internal reasoning, no visible output. */
|
|
225
|
+
function createThinkingOnlyNudge() {
|
|
226
|
+
return {
|
|
227
|
+
role: "custom",
|
|
228
|
+
customType: "loop:thinking-only-retry",
|
|
229
|
+
content: "Your previous response had no visible output — it contained only internal reasoning. Respond now with your answer as visible text (or make the required tool calls); do not end the turn with reasoning alone.",
|
|
230
|
+
display: false,
|
|
231
|
+
timestamp: Date.now(),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** design/124 tier A: continue nudge after a partially-finalized (mid-stream cut, promoted) "stop" turn.
|
|
235
|
+
* Separate message + counter from the truncated-output nudge — the cause differs (connection loss, not a
|
|
236
|
+
* token budget) and the model already sees the brain's "may be incomplete" warning in the transcript. */
|
|
237
|
+
function createMidstreamPartialContinueNudge() {
|
|
238
|
+
return {
|
|
239
|
+
role: "custom",
|
|
240
|
+
customType: "loop:midstream-partial-continue",
|
|
241
|
+
content: "Your previous response was cut off mid-stream by a connection failure — the content above may be incomplete. Continue EXACTLY where you left off; do not repeat any content you already produced, do not re-introduce or summarize.",
|
|
242
|
+
display: false,
|
|
243
|
+
timestamp: Date.now(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** The ④b synthetic continue nudge — a non-displayed custom message (convertToLlm renders it as user). */
|
|
247
|
+
/** T1-4: the different-approach nudge after a degenerate-repetition cutoff (same custom-message shape as
|
|
248
|
+
* the truncated-output continue — event ordering flows through the normal pending-message path). */
|
|
249
|
+
function createDegenerateRecoveryNudge() {
|
|
250
|
+
return {
|
|
251
|
+
role: "custom",
|
|
252
|
+
customType: "loop:degenerate-output-recovery",
|
|
253
|
+
content: "Your previous reply degenerated into repetition and was cut off. Do NOT repeat or continue that text. " +
|
|
254
|
+
"Take a DIFFERENT approach: state your next concrete step or conclusion in one short sentence, then proceed normally.",
|
|
255
|
+
display: false,
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/** design/130 P2b: the write-out nudge after a walltime cutoff — the salvaged prefix stays in the
|
|
260
|
+
* transcript; the model gets ONE short final turn inside the reserved cushion. */
|
|
261
|
+
function createWalltimeCutoffRecoveryNudge() {
|
|
262
|
+
return {
|
|
263
|
+
role: "custom",
|
|
264
|
+
customType: "loop:walltime-cutoff-recovery",
|
|
265
|
+
content: "Your previous response crossed the task's wall-clock deadline and was cut off (the partial text above is preserved). " +
|
|
266
|
+
"This is the FINAL turn: write your best current answer / deliverables NOW in one short response — no new work, no long commands.",
|
|
267
|
+
display: false,
|
|
268
|
+
timestamp: Date.now(),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** service [405] codex delta-b: the length-stop variant for a turn whose whole budget went to
|
|
272
|
+
* (non-replayed) reasoning — "continue where you left off" would point at nothing the provider
|
|
273
|
+
* can see. Ask for the answer directly. */
|
|
274
|
+
function createReasoningCutContinueNudge() {
|
|
275
|
+
return {
|
|
276
|
+
role: "custom",
|
|
277
|
+
customType: "loop:truncated-output-continue",
|
|
278
|
+
content: "Your previous response hit the output-token limit before producing any visible text (the budget went to internal reasoning). " +
|
|
279
|
+
"Do NOT restart the long reasoning: state your current best answer or next concrete action directly as visible text (or make the required tool call) now.",
|
|
280
|
+
display: false,
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function createContinueNudge() {
|
|
285
|
+
return {
|
|
286
|
+
role: "custom",
|
|
287
|
+
customType: "loop:truncated-output-continue",
|
|
288
|
+
content: "Your previous response was cut off because it hit the output-token limit. Continue EXACTLY where you left off — do not repeat any content you already produced, do not re-introduce or summarize.",
|
|
289
|
+
display: false,
|
|
290
|
+
timestamp: Date.now(),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Main loop logic shared by agentLoop and agentLoopContinue.
|
|
295
|
+
*
|
|
296
|
+
* Drive rule (identical to the vendored nested while-loops, made explicit):
|
|
297
|
+
* - While work remains (`hasMoreToolCalls || pendingMessages`), run turns; after each turn drain
|
|
298
|
+
* the STEERING queue (drain point A — mid-work boundary).
|
|
299
|
+
* - When work runs out, drain the FOLLOW-UP queue (drain point B — the agent was about to stop);
|
|
300
|
+
* a non-empty drain re-enters the turn loop, an empty one is the natural `completed` Terminal.
|
|
301
|
+
* Every Terminal path emits `agent_end` exactly once.
|
|
302
|
+
*/
|
|
303
|
+
async function runLoop(initialContext, newMessages, initialConfig, signal, emit, streamFn, runtime, trace) {
|
|
304
|
+
const state = {
|
|
305
|
+
context: initialContext,
|
|
306
|
+
config: initialConfig,
|
|
307
|
+
newMessages,
|
|
308
|
+
firstTurn: true,
|
|
309
|
+
// Check for steering messages at start (user may have typed while waiting)
|
|
310
|
+
pendingMessages: (await initialConfig.getSteeringMessages?.()) || [],
|
|
311
|
+
hasMoreToolCalls: true,
|
|
312
|
+
truncatedOutputContinues: 0,
|
|
313
|
+
// [477] 1.253 双轨终审 MED: seed from HISTORY, not 0 — the `staticReasoningCut` tag is persisted
|
|
314
|
+
// on each downgraded assistant message, so a resumed run (suspend/resume rebuilds LoopState)
|
|
315
|
+
// counts prior downgrades instead of re-granting a fresh 2-recovery budget every runLoop.
|
|
316
|
+
staticReasoningCutRecoveries: initialContext.messages.filter((m) => m.role === "assistant" && m.staticReasoningCut === true).length,
|
|
317
|
+
degenerateContinues: 0,
|
|
318
|
+
walltimeCutoffContinues: 0,
|
|
319
|
+
malformedToolUseRetries: 0,
|
|
320
|
+
thinkingOnlyRetries: 0,
|
|
321
|
+
midstreamPartialContinues: 0,
|
|
322
|
+
};
|
|
323
|
+
while (true) {
|
|
324
|
+
if (!state.hasMoreToolCalls && state.pendingMessages.length === 0) {
|
|
325
|
+
// Drain point B: the agent would otherwise stop. Follow-up messages route through the same
|
|
326
|
+
// pending-message path so event ordering matches steering messages.
|
|
327
|
+
const followUpMessages = (await state.config.getFollowUpMessages?.()) || [];
|
|
328
|
+
if (followUpMessages.length === 0) {
|
|
329
|
+
trace?.({ kind: "terminal", reason: "completed" });
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
state.pendingMessages = followUpMessages;
|
|
333
|
+
trace?.({ kind: "continue", reason: "follow_up_injected" });
|
|
334
|
+
}
|
|
335
|
+
const outcome = await runSingleTurn(state, signal, emit, streamFn, runtime, trace);
|
|
336
|
+
if (outcome.kind === "terminal") {
|
|
337
|
+
trace?.({ kind: "terminal", reason: outcome.reason });
|
|
338
|
+
// Terminal turns have already emitted `agent_end`.
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Only a turn with actual further work is a Continue; a turn that ran dry falls through to the
|
|
342
|
+
// top-of-loop decision (drain point B → follow_up_injected, or the natural `completed` Terminal).
|
|
343
|
+
if (outcome.recovered) {
|
|
344
|
+
trace?.({ kind: "continue", reason: outcome.recovered });
|
|
345
|
+
}
|
|
346
|
+
else if (state.pendingMessages.length > 0) {
|
|
347
|
+
trace?.({ kind: "continue", reason: "steer_injected" });
|
|
348
|
+
}
|
|
349
|
+
else if (state.hasMoreToolCalls) {
|
|
350
|
+
trace?.({ kind: "continue", reason: "next_turn" });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
await emit({ type: "agent_end", messages: state.newMessages });
|
|
354
|
+
}
|
|
355
|
+
/** [477]: per-run stop-loss for static reasoning-cut downgrades — the 3rd reasoning-only static
|
|
356
|
+
* max_tokens hit fails loud again (no infinite truncate/continue token burn on a budget-ignoring
|
|
357
|
+
* thinking model). */
|
|
358
|
+
const MAX_STATIC_REASONING_CUT_RECOVERIES = 2;
|
|
359
|
+
/** One turn: A-1 guard → inject pending → stream assistant → tool batch → boundary hooks → drain steer. */
|
|
360
|
+
async function runSingleTurn(state, signal, emit, streamFn, runtime, trace) {
|
|
361
|
+
// VENDORED EDIT provenance (audit A-1 layer ②, design/64 §28.3): an abort that fired during the
|
|
362
|
+
// previous turn's tool batch used to reach the next streamAssistantResponse anyway, which fails
|
|
363
|
+
// fast on the aborted signal and synthesizes an EMPTY stopReason:"aborted" assistant message —
|
|
364
|
+
// persisted into the session BETWEEN the interrupted batch and its later reconcile tool_results,
|
|
365
|
+
// leaving the session a permanently invalid sequence for strict providers (assistant tool_calls
|
|
366
|
+
// not all answered before the next assistant). Exit cleanly instead: no doomed request, no junk
|
|
367
|
+
// message; the caller's interrupt reconcile then appends its synthetic tool_results directly
|
|
368
|
+
// after the real batch — a legal transcript. (Durable invariant — see design/45.)
|
|
369
|
+
if (signal?.aborted) {
|
|
370
|
+
await emit({ type: "agent_end", messages: state.newMessages });
|
|
371
|
+
return { kind: "terminal", reason: "aborted_before_stream" };
|
|
372
|
+
}
|
|
373
|
+
if (!state.firstTurn) {
|
|
374
|
+
await emit({ type: "turn_start" });
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
state.firstTurn = false;
|
|
378
|
+
}
|
|
379
|
+
// Process pending messages (inject before next assistant response)
|
|
380
|
+
if (state.pendingMessages.length > 0) {
|
|
381
|
+
for (const message of state.pendingMessages) {
|
|
382
|
+
await emit({ type: "message_start", message });
|
|
383
|
+
await emit({ type: "message_end", message });
|
|
384
|
+
state.context.messages.push(message);
|
|
385
|
+
state.newMessages.push(message);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Stream assistant response. design/120 P2: the in-stream executor is PER-STREAM (a PTL retry
|
|
389
|
+
// below gets a fresh one); it is created unconditionally — with streamingToolExecution unset it
|
|
390
|
+
// admits nothing and every path below sees an empty executor (exactly the pre-P2 behavior).
|
|
391
|
+
let executor = new StreamToolExecutor(state.context, state.config, signal, emit);
|
|
392
|
+
// design/120 §5.2 settle barrier: after ANY stream final, await every in-flight in-stream call
|
|
393
|
+
// before any later step — recovery, terminal reconciliation and the batch pipeline must all start
|
|
394
|
+
// with zero in-stream work in flight. try/finally (codex impl-review BLOCKER-2): a THROW out of
|
|
395
|
+
// the stream (an emit/hook throw is the reachable case — brains report errors as events, not
|
|
396
|
+
// throws) must still settle, or admitted side effects would outlive the loop-failure terminal
|
|
397
|
+
// with nobody awaiting them. settle() never rejects (runOne's belt catch). Results still held at
|
|
398
|
+
// a throw are unrecoverable by design (no final assistant exists to own them in the transcript) —
|
|
399
|
+
// documented limitation, same shape as the pre-P2 loop-failure path.
|
|
400
|
+
// [477]: grant the brain the static reasoning-cut → recoverable-length downgrade only while the
|
|
401
|
+
// loop can actually recover it (truncated-output recovery configured — without the continue chain
|
|
402
|
+
// a downgraded `length` would just end the run as a silent empty stop, strictly worse than the
|
|
403
|
+
// fail-loud error) and the per-run stop-loss budget lasts.
|
|
404
|
+
const staticReasoningCutDowngrade = state.config.recovery?.truncatedOutput !== undefined &&
|
|
405
|
+
state.staticReasoningCutRecoveries < MAX_STATIC_REASONING_CUT_RECOVERIES;
|
|
406
|
+
// M1 (PARITY-CONTEXT-TRANSACTIONS §4): while a PTL recovery could still be attempted, withhold a
|
|
407
|
+
// candidate failure's message_start/message_end from consumers — CC 198 holds these errors inside
|
|
408
|
+
// its stream decoder (`:473371-473374`) and only leaks them once recovery's outcome is known.
|
|
409
|
+
// Recovery succeeded ⇒ the failed assistant is never seen (matches the existing pop-from-context);
|
|
410
|
+
// failed/declined/gate-unmet ⇒ flush() leaks the events at that point. The terminal truth is
|
|
411
|
+
// unchanged — only the interim "error → continued anyway" sequence disappears. Live visibility of
|
|
412
|
+
// the retry is deliberately given up (by-design trade, `withholdErrorEvents: false` restores it).
|
|
413
|
+
const ptl = state.config.recovery?.promptTooLong;
|
|
414
|
+
const ptlDetect = ptl?.detect ?? defaultDetectPromptTooLong;
|
|
415
|
+
const withholdEnabled = ptl !== undefined && (ptl.withholdErrorEvents ?? true);
|
|
416
|
+
let withhold = withholdEnabled ? createPtlWithholdBuffer(emit, ptlDetect) : undefined;
|
|
417
|
+
let message;
|
|
418
|
+
try {
|
|
419
|
+
message = await streamAssistantResponse(state.context, state.config, signal, withhold?.sink ?? emit, streamFn, runtime, executor, staticReasoningCutDowngrade);
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
await executor.settle();
|
|
423
|
+
}
|
|
424
|
+
// ④b recovery chain — prompt-too-long: retry INSIDE the turn (no extra `turn_start`). The failed
|
|
425
|
+
// assistant is dropped from the retry context and never enters the run's returned messages; its
|
|
426
|
+
// stream events are WITHHELD above (M1) until the recovery outcome is known. The recover() hook
|
|
427
|
+
// owns the policy (compact/trim); the loop owns the bounded transition (CC reactive-compact adapted).
|
|
428
|
+
{
|
|
429
|
+
if (ptl) {
|
|
430
|
+
const detect = ptlDetect;
|
|
431
|
+
const maxRetries = ptl.maxRetries ?? 2;
|
|
432
|
+
let attempt = 0;
|
|
433
|
+
while (message.stopReason === "error" &&
|
|
434
|
+
!signal?.aborted &&
|
|
435
|
+
detect(message) &&
|
|
436
|
+
// Recovery is gated on an EMPTY failure (no non-blank text/thinking, no tool calls) — the
|
|
437
|
+
// same predicate the harness uses to SKIP persisting failure assistants. A partial-content
|
|
438
|
+
// failure was already persisted by message_end, so popping it here would fork the in-memory
|
|
439
|
+
// context from the session (and a retry would leave adjacent assistants in the transcript);
|
|
440
|
+
// surface it as the terminal error instead, exactly like the pre-④b loop.
|
|
441
|
+
isBlankFailureContent(message) &&
|
|
442
|
+
// design/120 P2 guard: an in-stream side effect already happened ⇒ never pop-and-retry
|
|
443
|
+
// (the retried model would re-issue the call = double execution). A REAL prompt-too-long
|
|
444
|
+
// failure produces no output, so its executor is always empty — zero loss. This also covers
|
|
445
|
+
// the content-loss tear path isBlankFailureContent alone can't see (empty content, work
|
|
446
|
+
// done); note the merge-back in streamAssistantResponse already de-blanks that message, so
|
|
447
|
+
// this clause is belt-and-suspenders.
|
|
448
|
+
executor.admittedCount === 0 &&
|
|
449
|
+
attempt < maxRetries) {
|
|
450
|
+
attempt++;
|
|
451
|
+
// Drop the failed assistant from the context tail before handing the transcript to recover().
|
|
452
|
+
if (state.context.messages[state.context.messages.length - 1] === message) {
|
|
453
|
+
state.context.messages.pop();
|
|
454
|
+
}
|
|
455
|
+
// Contract says recover() must not throw; treat a throw as "recovery declined" (belt), never
|
|
456
|
+
// as a new failure that would swallow the original prompt-too-long error.
|
|
457
|
+
let replaced;
|
|
458
|
+
try {
|
|
459
|
+
replaced = await ptl.recover(state.context.messages, attempt);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
replaced = undefined;
|
|
463
|
+
}
|
|
464
|
+
if (!replaced) {
|
|
465
|
+
// Recovery declined — restore the failed assistant so downstream sees the true transcript.
|
|
466
|
+
// (Its withheld events leak via the flush below — the decline IS the known outcome.)
|
|
467
|
+
state.context.messages.push(message);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
// M1: recovery replaced the transcript — the failed assistant's withheld events are
|
|
471
|
+
// discarded (consumers never see the recovered error), and the retry stream gets a fresh
|
|
472
|
+
// buffer (the retry itself may hit prompt-too-long again).
|
|
473
|
+
withhold?.discard();
|
|
474
|
+
withhold = withholdEnabled ? createPtlWithholdBuffer(emit, ptlDetect) : undefined;
|
|
475
|
+
state.context.messages = replaced;
|
|
476
|
+
trace?.({ kind: "continue", reason: "reactive_compact_retry" });
|
|
477
|
+
executor = new StreamToolExecutor(state.context, state.config, signal, emit); // per-stream
|
|
478
|
+
try {
|
|
479
|
+
message = await streamAssistantResponse(state.context, state.config, signal, withhold?.sink ?? emit, streamFn, runtime, executor, staticReasoningCutDowngrade);
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
await executor.settle(); // same throw-safety as the first stream (BLOCKER-2)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// M1: whatever is still held now has a KNOWN outcome — retries exhausted, recovery declined, or
|
|
488
|
+
// the gate never fired (aborted / maxRetries 0). Leak it here so the consumer's terminal truth is
|
|
489
|
+
// exactly the pre-M1 one (message_start+message_end of the surviving failure, original order).
|
|
490
|
+
await withhold?.flush();
|
|
491
|
+
state.newMessages.push(message);
|
|
492
|
+
// [477]: count each downgraded reasoning-cut turn against the per-run stop-loss (the brain tags
|
|
493
|
+
// them; once the budget is spent the grant above goes false and the next hit fails loud).
|
|
494
|
+
if (message.staticReasoningCut === true)
|
|
495
|
+
state.staticReasoningCutRecoveries++;
|
|
496
|
+
// T1-4 recovery — degenerate-repetition cutoff: the brain already cancelled the stream and salvaged the
|
|
497
|
+
// prefix into this errored turn. Ending the run here voids all prior work over one bad sampling run; nudge
|
|
498
|
+
// a DIFFERENT-approach continue instead, bounded to consecutive maxContinues (default 2). The cut-off turn
|
|
499
|
+
// stays in the transcript (its salvaged text is real context; the model sees what it repeated). On the
|
|
500
|
+
// cap, fall through to the normal error terminal (assemble → output.degenerate + salvagedOutput).
|
|
501
|
+
{
|
|
502
|
+
const degen = state.config.recovery?.degenerateOutput;
|
|
503
|
+
if (degen &&
|
|
504
|
+
message.stopReason === "error" &&
|
|
505
|
+
!signal?.aborted &&
|
|
506
|
+
degen.detect(message) &&
|
|
507
|
+
// codex 1.238 F2 (BLOCKER, r2 补角): a tool_call anywhere on this errored assistant — an admitted
|
|
508
|
+
// in-stream call OR a completed-but-unexecuted call in the final content (non-streaming path) —
|
|
509
|
+
// means recovering past it hands the provider a tool_call with no tool_result (4xx on strict
|
|
510
|
+
// providers). Gate BOTH faces; the rare degenerate+tool-call combo falls through to the error
|
|
511
|
+
// terminal, whose harvest writes the results correctly.
|
|
512
|
+
executor.admittedCount === 0 &&
|
|
513
|
+
message.content.every((b) => b.type !== "toolCall") &&
|
|
514
|
+
state.degenerateContinues < (degen.maxContinues ?? 2)) {
|
|
515
|
+
state.degenerateContinues++;
|
|
516
|
+
// codex F1: this turn still ENDED — emit its turn_end (the zero-shell usage rides the message with
|
|
517
|
+
// the brain's usageMissing flag) so turn accounting and save-points hold across the recovery.
|
|
518
|
+
await emit({ type: "turn_end", message, toolResults: [] });
|
|
519
|
+
// codex F5 (steer wins): drain the boundary steer first — a user redirect drives the next turn and
|
|
520
|
+
// suppresses the nudge; the continue is counted either way (bounded).
|
|
521
|
+
const drained = (await state.config.getSteeringMessages?.()) || [];
|
|
522
|
+
state.pendingMessages = drained.length > 0 ? drained : [createDegenerateRecoveryNudge()];
|
|
523
|
+
return { kind: "ran", recovered: "degenerate_output_recovery" };
|
|
524
|
+
}
|
|
525
|
+
if (message.stopReason !== "error")
|
|
526
|
+
state.degenerateContinues = 0;
|
|
527
|
+
}
|
|
528
|
+
// design/130 P2b — walltime-cutoff write-out recovery: the brain cut this turn at the soft
|
|
529
|
+
// per-call deadline (deliberate, salvaged prefix in the transcript). Ending the run here would
|
|
530
|
+
// void the reserved write-out cushion; nudge ONE bounded final turn instead. Same shape and
|
|
531
|
+
// gates as the degenerate recovery above (codex 1.238 F2: any tool_call face — admitted
|
|
532
|
+
// in-stream OR completed-but-unexecuted in content — forces the error terminal, whose harvest
|
|
533
|
+
// writes the results correctly). The counter is NOT reset on success: the deadline only gets
|
|
534
|
+
// nearer, so the cap bounds the whole run (no cutoff/recover ping-pong; the error terminal then
|
|
535
|
+
// assembles as timeout + salvagedOutput).
|
|
536
|
+
{
|
|
537
|
+
const wc = state.config.recovery?.walltimeCutoff;
|
|
538
|
+
if (wc &&
|
|
539
|
+
message.stopReason === "error" &&
|
|
540
|
+
!signal?.aborted &&
|
|
541
|
+
wc.detect(message) &&
|
|
542
|
+
executor.admittedCount === 0 &&
|
|
543
|
+
message.content.every((b) => b.type !== "toolCall") &&
|
|
544
|
+
state.walltimeCutoffContinues < (wc.maxContinues ?? 1)) {
|
|
545
|
+
state.walltimeCutoffContinues++;
|
|
546
|
+
// codex F1 (same as degenerate): this turn still ENDED — emit its turn_end so turn
|
|
547
|
+
// accounting and save-points hold across the recovery.
|
|
548
|
+
await emit({ type: "turn_end", message, toolResults: [] });
|
|
549
|
+
// codex F5 (steer wins): a boundary steer (e.g. the runner's finalize instruction, if it
|
|
550
|
+
// already fired) drives the write-out turn and suppresses the generic nudge.
|
|
551
|
+
const drained = (await state.config.getSteeringMessages?.()) || [];
|
|
552
|
+
state.pendingMessages = drained.length > 0 ? drained : [createWalltimeCutoffRecoveryNudge()];
|
|
553
|
+
return { kind: "ran", recovered: "walltime_cutoff_recovery" };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
557
|
+
// design/120 §5.4: in-stream side effects already happened — commit their REAL results before
|
|
558
|
+
// the terminal (a real result beats the wake-reconcile [INTERRUPTED] synthesis); unstarted
|
|
559
|
+
// calls stay for wake A-1. With an empty executor this is exactly the pre-P2 path.
|
|
560
|
+
const errorFinalResults = await harvestExecutorOnErrorFinal(executor, state, message, signal, emit);
|
|
561
|
+
// design/130 P2b (codex HIGH): a WALLTIME-CUT final may carry tool calls that CLOSED before the
|
|
562
|
+
// cut but were never admitted for execution — the harvest above only covers executor-admitted
|
|
563
|
+
// entries, so those calls would persist unanswered (a strict provider 4xxes the next request on
|
|
564
|
+
// this session forever). Synthesize an error toolResult for each uncovered call, scoped to the
|
|
565
|
+
// walltime cutoff ONLY (the pre-existing degenerate/error terminals keep their long-standing
|
|
566
|
+
// behavior — widening this is a separate decision).
|
|
567
|
+
if (state.config.recovery?.walltimeCutoff?.detect(message)) {
|
|
568
|
+
const covered = new Set(errorFinalResults.map((r) => r.toolCallId));
|
|
569
|
+
for (const tc of message.content.filter((c) => c.type === "toolCall")) {
|
|
570
|
+
if (covered.has(tc.id))
|
|
571
|
+
continue;
|
|
572
|
+
const synthetic = {
|
|
573
|
+
role: "toolResult",
|
|
574
|
+
toolCallId: tc.id,
|
|
575
|
+
toolName: tc.name,
|
|
576
|
+
content: [{ type: "text", text: "[call aborted by walltime cutoff — never executed]" }],
|
|
577
|
+
isError: true,
|
|
578
|
+
timestamp: Date.now(),
|
|
579
|
+
};
|
|
580
|
+
await emitToolResultMessage(synthetic, emit);
|
|
581
|
+
state.context.messages.push(synthetic);
|
|
582
|
+
state.newMessages.push(synthetic);
|
|
583
|
+
errorFinalResults.push(synthetic);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
await emit({ type: "turn_end", message, toolResults: errorFinalResults });
|
|
587
|
+
await emit({ type: "agent_end", messages: state.newMessages });
|
|
588
|
+
return { kind: "terminal", reason: "assistant_error" };
|
|
589
|
+
}
|
|
590
|
+
// Check for tool calls
|
|
591
|
+
const toolCalls = message.content.filter((c) => c.type === "toolCall");
|
|
592
|
+
const toolResults = [];
|
|
593
|
+
state.hasMoreToolCalls = false;
|
|
594
|
+
if (toolCalls.length > 0) {
|
|
595
|
+
const executedToolBatch = await executeToolCalls(state.context, message, state.config, signal, emit, executor);
|
|
596
|
+
toolResults.push(...executedToolBatch.messages);
|
|
597
|
+
state.hasMoreToolCalls = !executedToolBatch.terminate;
|
|
598
|
+
for (const result of toolResults) {
|
|
599
|
+
state.context.messages.push(result);
|
|
600
|
+
state.newMessages.push(result);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// Extreme contract breach: a done final with ZERO toolCall content while the executor admitted
|
|
605
|
+
// work (the batch pipeline never runs, so its orphan drain can't). Close the pairs here (F-3).
|
|
606
|
+
for (const orphan of executor.remaining()) {
|
|
607
|
+
executor.take(orphan.toolCall.id);
|
|
608
|
+
await closeOrphanedStreamEntry(orphan, emit);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
await emit({ type: "turn_end", message, toolResults });
|
|
612
|
+
const nextTurnContext = {
|
|
613
|
+
message,
|
|
614
|
+
toolResults,
|
|
615
|
+
context: state.context,
|
|
616
|
+
newMessages: state.newMessages,
|
|
617
|
+
};
|
|
618
|
+
const nextTurnSnapshot = await state.config.prepareNextTurn?.(nextTurnContext);
|
|
619
|
+
if (nextTurnSnapshot) {
|
|
620
|
+
state.context = nextTurnSnapshot.context ?? state.context;
|
|
621
|
+
state.config = Object.assign({}, state.config, {
|
|
622
|
+
model: nextTurnSnapshot.model ?? state.config.model,
|
|
623
|
+
reasoning: nextTurnSnapshot.thinkingLevel === undefined
|
|
624
|
+
? state.config.reasoning
|
|
625
|
+
: nextTurnSnapshot.thinkingLevel === "off"
|
|
626
|
+
? undefined
|
|
627
|
+
: nextTurnSnapshot.thinkingLevel,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
if (await state.config.shouldStopAfterTurn?.({
|
|
631
|
+
message,
|
|
632
|
+
toolResults,
|
|
633
|
+
context: state.context,
|
|
634
|
+
newMessages: state.newMessages,
|
|
635
|
+
})) {
|
|
636
|
+
await emit({ type: "agent_end", messages: state.newMessages });
|
|
637
|
+
return { kind: "terminal", reason: "stop_requested" };
|
|
638
|
+
}
|
|
639
|
+
// Drain point A (mid-work boundary): steering messages inject before the next provider request.
|
|
640
|
+
state.pendingMessages = (await state.config.getSteeringMessages?.()) || [];
|
|
641
|
+
// ④b recovery chain — truncated output: a `length`-stopped answer with NO tool calls would end
|
|
642
|
+
// the run mid-sentence. When enabled, inject a synthetic continue nudge (through the normal
|
|
643
|
+
// pending-message path so event ordering matches every other injected message) and run another
|
|
644
|
+
// turn — bounded to `maxContinues` CONSECUTIVE times; any non-length turn resets the counter.
|
|
645
|
+
// A length-stop WITH tool calls needs no nudge (the tool results drive the next turn anyway).
|
|
646
|
+
const truncated = state.config.recovery?.truncatedOutput;
|
|
647
|
+
if (message.stopReason === "length") {
|
|
648
|
+
if (truncated &&
|
|
649
|
+
toolCalls.length === 0 &&
|
|
650
|
+
// A steer drained at this same boundary means the USER redirected — their message drives the
|
|
651
|
+
// next turn; appending a "continue exactly where you left off" nudge after it would fight the
|
|
652
|
+
// redirect from the recency position. The continue counter is untouched (no nudge happened).
|
|
653
|
+
state.pendingMessages.length === 0 &&
|
|
654
|
+
state.truncatedOutputContinues < (truncated.maxContinues ?? 3)) {
|
|
655
|
+
state.truncatedOutputContinues++;
|
|
656
|
+
// codex 实现审 #2: this early return would skip the midstream-partial reset below, so a
|
|
657
|
+
// partial→length-recovery→partial alternation would be miscounted as CONSECUTIVE partials and
|
|
658
|
+
// burn the 3-nudge budget early. A length turn is never a partial-finalized stop — reset here too.
|
|
659
|
+
state.midstreamPartialContinues = 0;
|
|
660
|
+
// service [405] codex delta-b: a length stop with NO visible text (reasoning consumed the
|
|
661
|
+
// budget — the dynamicCut shape) must NOT say "continue where you left off": reasoning is not
|
|
662
|
+
// replayed to OpenAI-family providers, so the model would see an empty assistant + a request
|
|
663
|
+
// to seamlessly continue nothing. Ask for a fresh visible answer instead.
|
|
664
|
+
const noVisibleText = !message.content.some((c) => c.type === "text" && c.text.trim() !== "");
|
|
665
|
+
state.pendingMessages = [...state.pendingMessages, noVisibleText ? createReasoningCutContinueNudge() : createContinueNudge()];
|
|
666
|
+
return { kind: "ran", recovered: "max_output_tokens_recovery" };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
state.truncatedOutputContinues = 0;
|
|
671
|
+
}
|
|
672
|
+
// design/124 tier A downstream: a partially-finalized "stop" turn (mid-stream cut whose substantive
|
|
673
|
+
// prefix was promoted to content by the brain) must not end an autonomous run as an incomplete
|
|
674
|
+
// success — there is no user to notice the "may be incomplete" warning and re-prompt. Bounded
|
|
675
|
+
// continue nudge, truncatedOutput 范式: steer wins (a drained redirect suppresses the nudge), own
|
|
676
|
+
// counter + own message (never mixed with the length-stop budget), consecutive-reset. Always on
|
|
677
|
+
// (no config gate): the flag only ever appears when the brain recovered a mid-stream failure, and
|
|
678
|
+
// ending silently there is strictly worse for every integrator. A `partialFinalized` toolUse turn
|
|
679
|
+
// takes the normal tool path above — its results drive the next turn anyway.
|
|
680
|
+
if (message.stopReason === "stop" && message.partialFinalized === true) {
|
|
681
|
+
if (toolCalls.length === 0 &&
|
|
682
|
+
state.pendingMessages.length === 0 &&
|
|
683
|
+
state.midstreamPartialContinues < 3) {
|
|
684
|
+
state.midstreamPartialContinues++;
|
|
685
|
+
state.pendingMessages = [createMidstreamPartialContinueNudge()];
|
|
686
|
+
return { kind: "ran", recovered: "midstream_partial_recovery" };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
state.midstreamPartialContinues = 0;
|
|
691
|
+
}
|
|
692
|
+
// S2 recovery (design/119, CC 198 :473745-): a `toolUse` stop whose every tool call was dropped as
|
|
693
|
+
// malformed (the brain already appended a visible note) used to end the run silently mid-task —
|
|
694
|
+
// the model believes it acted, nothing ran. Nudge a retry, bounded per run. Steer wins (same
|
|
695
|
+
// reasoning as the truncated-output guard above).
|
|
696
|
+
const malformed = state.config.recovery?.malformedToolUse;
|
|
697
|
+
if (malformed &&
|
|
698
|
+
message.stopReason === "toolUse" &&
|
|
699
|
+
toolCalls.length === 0 &&
|
|
700
|
+
state.pendingMessages.length === 0 &&
|
|
701
|
+
state.malformedToolUseRetries < (malformed.maxRetries ?? 1)) {
|
|
702
|
+
state.malformedToolUseRetries++;
|
|
703
|
+
state.pendingMessages = [createMalformedToolUseNudge()];
|
|
704
|
+
return { kind: "ran", recovered: "malformed_tool_use_retry" };
|
|
705
|
+
}
|
|
706
|
+
// S2 recovery (design/119, CC 198 :473786- — the adaptive-thinking companion): a normal stop whose
|
|
707
|
+
// content is ONLY thinking (no non-blank text, no tool calls) is a silent empty success. Nudge the
|
|
708
|
+
// model to answer visibly, bounded per run. Steer wins.
|
|
709
|
+
const thinkingOnly = state.config.recovery?.thinkingOnly;
|
|
710
|
+
if (thinkingOnly &&
|
|
711
|
+
message.stopReason === "stop" &&
|
|
712
|
+
toolCalls.length === 0 &&
|
|
713
|
+
state.pendingMessages.length === 0 &&
|
|
714
|
+
state.thinkingOnlyRetries < (thinkingOnly.maxRetries ?? 1) &&
|
|
715
|
+
message.content.some((c) => c.type === "thinking") &&
|
|
716
|
+
!message.content.some((c) => c.type === "text" && c.text.trim() !== "")) {
|
|
717
|
+
state.thinkingOnlyRetries++;
|
|
718
|
+
state.pendingMessages = [createThinkingOnlyNudge()];
|
|
719
|
+
return { kind: "ran", recovered: "thinking_only_retry" };
|
|
720
|
+
}
|
|
721
|
+
return { kind: "ran" };
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Stream an assistant response from the LLM.
|
|
725
|
+
* This is where AgentMessage[] gets transformed to Message[] for the LLM.
|
|
726
|
+
*/
|
|
727
|
+
async function streamAssistantResponse(context, config, signal, emit, streamFn, runtime, executor, staticReasoningCutDowngrade) {
|
|
728
|
+
// Apply context transform if configured (AgentMessage[] → AgentMessage[])
|
|
729
|
+
let messages = context.messages;
|
|
730
|
+
if (config.transformContext) {
|
|
731
|
+
messages = await config.transformContext(messages, signal);
|
|
732
|
+
}
|
|
733
|
+
// Convert to LLM-compatible messages (AgentMessage[] → Message[])
|
|
734
|
+
const llmMessages = await config.convertToLlm(messages);
|
|
735
|
+
// Build LLM context
|
|
736
|
+
const llmContext = {
|
|
737
|
+
systemPrompt: context.systemPrompt,
|
|
738
|
+
messages: llmMessages,
|
|
739
|
+
tools: context.tools,
|
|
740
|
+
};
|
|
741
|
+
const streamFunction = resolveAgentCoreStreamFn(runtime, streamFn);
|
|
742
|
+
// Resolve API key (important for expiring tokens)
|
|
743
|
+
const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
|
|
744
|
+
// design/130 P1: a wall-clock-aware host may shrink THIS call's output cap; evaluated per call
|
|
745
|
+
// (the spread below would otherwise carry the run-static maxTokens snapshot).
|
|
746
|
+
const perCallMaxTokens = config.maxTokensPerCall?.();
|
|
747
|
+
// design/130 P2b: soft per-call wall-clock deadline — a brain that observes the wall clock
|
|
748
|
+
// crossing it cuts the stream (salvaging the prefix) instead of riding into the hard abort.
|
|
749
|
+
const perCallDeadlineMs = config.callDeadlineMsPerCall?.();
|
|
750
|
+
const response = await streamFunction(config.model, llmContext, {
|
|
751
|
+
...config,
|
|
752
|
+
// service [405]: `maxTokensDynamic` marks the cap as ENGINE-shrunk — a reasoning-only
|
|
753
|
+
// max_tokens hit then degrades to a recoverable `length` stop instead of the fail-loud error.
|
|
754
|
+
...(perCallMaxTokens !== undefined ? { maxTokens: perCallMaxTokens, maxTokensDynamic: true } : {}),
|
|
755
|
+
...(perCallDeadlineMs !== undefined ? { callDeadlineMs: perCallDeadlineMs } : {}),
|
|
756
|
+
// [477]: the loop's per-run stop-loss grant for the STATIC reasoning-cut downgrade (the static
|
|
757
|
+
// sibling of maxTokensDynamic above) — a reasoning-only static max_tokens hit then degrades to
|
|
758
|
+
// a recoverable `length` the truncated-output continue picks up.
|
|
759
|
+
...(staticReasoningCutDowngrade === true ? { staticReasoningCutDowngrade: true } : {}),
|
|
760
|
+
apiKey: resolvedApiKey,
|
|
761
|
+
signal,
|
|
762
|
+
});
|
|
763
|
+
let partialMessage = null;
|
|
764
|
+
let addedPartial = false;
|
|
765
|
+
for await (const event of response) {
|
|
766
|
+
switch (event.type) {
|
|
767
|
+
case "start": {
|
|
768
|
+
const message = event.partial;
|
|
769
|
+
partialMessage = message;
|
|
770
|
+
context.messages.push(message);
|
|
771
|
+
addedPartial = true;
|
|
772
|
+
await emit({ type: "message_start", message: { ...message } });
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
case "text_start":
|
|
776
|
+
case "text_delta":
|
|
777
|
+
case "text_end":
|
|
778
|
+
case "thinking_start":
|
|
779
|
+
case "thinking_delta":
|
|
780
|
+
case "thinking_end":
|
|
781
|
+
case "toolcall_start":
|
|
782
|
+
case "toolcall_delta":
|
|
783
|
+
case "toolcall_end":
|
|
784
|
+
if (partialMessage) {
|
|
785
|
+
const message = resolveAssistantMessageUpdate(event, partialMessage);
|
|
786
|
+
partialMessage = message;
|
|
787
|
+
context.messages[context.messages.length - 1] = message;
|
|
788
|
+
await emit({
|
|
789
|
+
type: "message_update",
|
|
790
|
+
assistantMessageEvent: event,
|
|
791
|
+
message: { ...message },
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
// design/120 P2: a completed-arguments call may start executing while the stream is live.
|
|
795
|
+
// Admission (opt-in flag + safe tool + source-order barrier + cap) lives in the executor;
|
|
796
|
+
// when it declines, the call simply waits for the post-stream batch pipeline as before.
|
|
797
|
+
// `partialMessage` may be null on a protocol-violating stream (toolcall_end before start) —
|
|
798
|
+
// that DECLINE must still go through the executor so the source-order barrier rises
|
|
799
|
+
// (impl-review F-1: skipping maybeAdmit entirely let a LATER safe call execute in-stream
|
|
800
|
+
// before this earlier bounced one, breaking the admitted-is-a-prefix invariant).
|
|
801
|
+
if (event.type === "toolcall_end" && executor) {
|
|
802
|
+
executor.maybeAdmit(event.toolCall, partialMessage);
|
|
803
|
+
}
|
|
804
|
+
break;
|
|
805
|
+
case "done":
|
|
806
|
+
case "error": {
|
|
807
|
+
const finalMessage = await response.result();
|
|
808
|
+
// design/120 §5.4 content-loss defense: an error/aborted final must not drop a toolCall
|
|
809
|
+
// block whose call ALREADY executed in-stream (persistence point is message_end below).
|
|
810
|
+
if (executor && (finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
|
|
811
|
+
mergeBackAdmittedCalls(finalMessage, executor);
|
|
812
|
+
}
|
|
813
|
+
if (addedPartial) {
|
|
814
|
+
context.messages[context.messages.length - 1] = finalMessage;
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
context.messages.push(finalMessage);
|
|
818
|
+
}
|
|
819
|
+
if (!addedPartial) {
|
|
820
|
+
await emit({ type: "message_start", message: { ...finalMessage } });
|
|
821
|
+
}
|
|
822
|
+
await emit({ type: "message_end", message: finalMessage });
|
|
823
|
+
return finalMessage;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const finalMessage = await response.result();
|
|
828
|
+
if (executor && (finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
|
|
829
|
+
mergeBackAdmittedCalls(finalMessage, executor); // same defense for the no-terminal-event path
|
|
830
|
+
}
|
|
831
|
+
if (addedPartial) {
|
|
832
|
+
context.messages[context.messages.length - 1] = finalMessage;
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
context.messages.push(finalMessage);
|
|
836
|
+
await emit({ type: "message_start", message: { ...finalMessage } });
|
|
837
|
+
}
|
|
838
|
+
await emit({ type: "message_end", message: finalMessage });
|
|
839
|
+
return finalMessage;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Execute tool calls from an assistant message.
|
|
843
|
+
*/
|
|
844
|
+
async function executeToolCalls(currentContext, assistantMessage, config, signal, emit, executor) {
|
|
845
|
+
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
|
846
|
+
if (config.toolExecution === "sequential") {
|
|
847
|
+
// Explicit whole-batch serial override — semantics preserved (design/120 §1.2). The in-stream
|
|
848
|
+
// executor never admits under this mode (its very first admission check), so there is nothing
|
|
849
|
+
// to harvest here.
|
|
850
|
+
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit);
|
|
851
|
+
}
|
|
852
|
+
// design/120 P1: partitioned execution replaces the old any-sequential→whole-batch-serial pre-scan
|
|
853
|
+
// (which kept mixed read/write batches — the main beneficiary — fully serial forever). Consecutive
|
|
854
|
+
// concurrency-safe calls run as one capped parallel batch; unsafe calls run as singleton batches;
|
|
855
|
+
// batches execute strictly in assistant source order. Safe = explicit `executionMode:"parallel"`
|
|
856
|
+
// or a true-returning input hook — fail-closed for undeclared tools (CC 2.1.206 TOOL_DEFAULTS
|
|
857
|
+
// parity; see isCallConcurrencySafe below). The defineTool effect derivation maps read → parallel
|
|
858
|
+
// and write/idempotent/undeclared → sequential.
|
|
859
|
+
return executeToolCallsPartitioned(currentContext, assistantMessage, toolCalls, config, signal, emit, executor);
|
|
860
|
+
}
|
|
861
|
+
async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit) {
|
|
862
|
+
const finalizedCalls = [];
|
|
863
|
+
const messages = [];
|
|
864
|
+
for (const toolCall of toolCalls) {
|
|
865
|
+
await emit({
|
|
866
|
+
type: "tool_execution_start",
|
|
867
|
+
toolCallId: toolCall.id,
|
|
868
|
+
toolName: toolCall.name,
|
|
869
|
+
args: toolCall.arguments,
|
|
870
|
+
});
|
|
871
|
+
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
|
|
872
|
+
let finalized;
|
|
873
|
+
if (preparation.kind === "immediate") {
|
|
874
|
+
finalized = {
|
|
875
|
+
toolCall,
|
|
876
|
+
result: preparation.result,
|
|
877
|
+
isError: preparation.isError,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
const executed = await executePreparedToolCall(preparation, signal, emit);
|
|
882
|
+
finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, preparation, executed, config, signal);
|
|
883
|
+
}
|
|
884
|
+
await emitToolExecutionEnd(finalized, emit);
|
|
885
|
+
const toolResultMessage = createToolResultMessage(finalized);
|
|
886
|
+
await emitToolResultMessage(toolResultMessage, emit);
|
|
887
|
+
finalizedCalls.push(finalized);
|
|
888
|
+
messages.push(toolResultMessage);
|
|
889
|
+
if (signal?.aborted) {
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
messages,
|
|
895
|
+
terminate: shouldTerminateToolBatch(finalizedCalls),
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* design/120 P1.5 (CC `isConcurrencySafe(parsedInput)` parity): per-CALL concurrency safety.
|
|
900
|
+
* FAIL-CLOSED direction (CC 2.1.206 `TOOL_DEFAULTS.isConcurrencySafe → false`, pretty.js:280839):
|
|
901
|
+
* only an explicit `executionMode:"parallel"` (static opt-in — CC's statically-true read tools) or a
|
|
902
|
+
* true-returning input-level `isConcurrencySafe` hook (Bash read-only commands — the one real CC
|
|
903
|
+
* consumer) makes a call batch-safe. An UNDECLARED tool, an unknown tool name (CC partitioner
|
|
904
|
+
* `o?.isConcurrencySafe(...)` → false, pretty.js:520091), and a throwing hook are all unsafe.
|
|
905
|
+
*/
|
|
906
|
+
function isCallConcurrencySafe(tool, args) {
|
|
907
|
+
if (!tool)
|
|
908
|
+
return false;
|
|
909
|
+
if (tool.executionMode === "parallel")
|
|
910
|
+
return true;
|
|
911
|
+
if (!tool.isConcurrencySafe)
|
|
912
|
+
return false;
|
|
913
|
+
try {
|
|
914
|
+
return tool.isConcurrencySafe(args) === true;
|
|
915
|
+
}
|
|
916
|
+
catch {
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* CC 2.1.206 partition parity (pretty.js:520087-520096): a call is batch-safe ONLY if its arguments
|
|
922
|
+
* VALIDATE against the tool's schema (CC `safeParse` — a malformed call never rides a parallel batch;
|
|
923
|
+
* it stays a singleton and surfaces its validation error at execution) AND the tool/hook opts in.
|
|
924
|
+
* The hook sees the validated+coerced args — same as CC handing `parsed.data` to `isConcurrencySafe`.
|
|
925
|
+
*/
|
|
926
|
+
function isBatchSafeToolCall(tool, toolCall) {
|
|
927
|
+
if (!tool)
|
|
928
|
+
return false;
|
|
929
|
+
let args;
|
|
930
|
+
try {
|
|
931
|
+
args = validateToolArguments(tool, prepareToolCallArguments(tool, toolCall));
|
|
932
|
+
}
|
|
933
|
+
catch {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
return isCallConcurrencySafe(tool, args);
|
|
937
|
+
}
|
|
938
|
+
/** design/120 P1: consecutive-safe partitioning (CC 198 partitionToolCalls semantics). */
|
|
939
|
+
function partitionToolCalls(toolCalls, tools) {
|
|
940
|
+
const batches = [];
|
|
941
|
+
for (const tc of toolCalls) {
|
|
942
|
+
// VENDORED EDIT provenance (design/115 P0): alias-aware lookup, same as the old pre-scan.
|
|
943
|
+
const safe = isBatchSafeToolCall(findToolByName(tools, tc.name), tc);
|
|
944
|
+
const last = batches[batches.length - 1];
|
|
945
|
+
if (safe && last?.safe) {
|
|
946
|
+
last.calls.push(tc);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
batches.push({ safe, calls: [tc] });
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return batches;
|
|
953
|
+
}
|
|
954
|
+
/** Minimal semaphore for the per-batch concurrency cap (design/120 §1.3; CC default 10). */
|
|
955
|
+
async function runCapped(thunks, limit) {
|
|
956
|
+
const results = new Array(thunks.length);
|
|
957
|
+
let next = 0;
|
|
958
|
+
const workers = Array.from({ length: Math.max(1, Math.min(limit, thunks.length)) }, async () => {
|
|
959
|
+
while (next < thunks.length) {
|
|
960
|
+
const i = next++;
|
|
961
|
+
results[i] = await thunks[i]();
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
await Promise.all(workers);
|
|
965
|
+
return results;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* design/120 P1 — partitioned batch execution. Per partition: SERIAL preflight in source order
|
|
969
|
+
* (tool_execution_start + prepare + the design/37 gate — a suspending gate can therefore never fire
|
|
970
|
+
* before an earlier-source-order batch has executed: deliberate divergence from CC, whose per-call
|
|
971
|
+
* chains run concurrently but which has no durable suspend), then capped-concurrent execution, then
|
|
972
|
+
* the batch's tool-result message artifacts in source order (so each batch's results are committed
|
|
973
|
+
* before the next batch's gates run — design/120 §1.2 premise). The durable double-execution gate
|
|
974
|
+
* inside executePreparedToolCall and the mid-batch abort semantics are unchanged: an abort leaves
|
|
975
|
+
* the current batch's unstarted closures and ALL later batches unexecuted (the dangling set the
|
|
976
|
+
* interrupt reconcile / suspendedBatch machinery already handles — sub-batch boundaries are NOT
|
|
977
|
+
* persisted, checkpoint batchToolCallIds stays the whole assistant batch).
|
|
978
|
+
*/
|
|
979
|
+
async function executeToolCallsPartitioned(currentContext, assistantMessage, toolCalls, config, signal, emit, executor) {
|
|
980
|
+
const cap = config.maxToolConcurrency ?? 10;
|
|
981
|
+
const allFinalized = [];
|
|
982
|
+
const messages = [];
|
|
983
|
+
// design/120 P2 harvest: commit the in-stream executor's held outcomes FIRST (finalize with the
|
|
984
|
+
// FINAL assistant message + tool_execution_end + tool-result message), before any batch
|
|
985
|
+
// preflight/gate runs — a later gate's durable checkpoint (batchContextAt scans committed
|
|
986
|
+
// toolResults) then counts every already-executed call in completedCallIds, so the resume-side
|
|
987
|
+
// [DEFERRED] ("it was NOT executed") can never lie about an executed call. The source-order
|
|
988
|
+
// barrier makes admitted calls a leading prefix of the batch, so this commit order IS the P1
|
|
989
|
+
// source order. Harvest iterates ALL calls (not prefix-break) as a hardening: even if a provider
|
|
990
|
+
// violated the ordering contract, an executed call is never re-run by the batch pipeline below.
|
|
991
|
+
const remainingCalls = [];
|
|
992
|
+
for (const toolCall of toolCalls) {
|
|
993
|
+
const held = executor?.take(toolCall.id);
|
|
994
|
+
if (!held) {
|
|
995
|
+
remainingCalls.push(toolCall);
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
const finalized = await finalizeStreamEntry(currentContext, assistantMessage, held, config, signal);
|
|
999
|
+
await emitToolExecutionEnd(finalized, emit);
|
|
1000
|
+
const toolResultMessage = createToolResultMessage(finalized);
|
|
1001
|
+
await emitToolResultMessage(toolResultMessage, emit);
|
|
1002
|
+
allFinalized.push(finalized);
|
|
1003
|
+
messages.push(toolResultMessage);
|
|
1004
|
+
}
|
|
1005
|
+
// A done-final content is a superset of admitted calls (the P2.0 provider contract), so the
|
|
1006
|
+
// executor is fully drained above; anything left is a contract breach — drop the outcome but
|
|
1007
|
+
// close its in-stream start with a synthetic end so id-pairing consumers never hang (F-3).
|
|
1008
|
+
if (executor) {
|
|
1009
|
+
for (const orphan of executor.remaining()) {
|
|
1010
|
+
executor.take(orphan.toolCall.id);
|
|
1011
|
+
await closeOrphanedStreamEntry(orphan, emit);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const batches = partitionToolCalls(remainingCalls, currentContext.tools);
|
|
1015
|
+
outer: for (const batch of batches) {
|
|
1016
|
+
if (signal?.aborted)
|
|
1017
|
+
break;
|
|
1018
|
+
// Serial preflight (source order): start events + prepare + gate. Immediate outcomes (blocked /
|
|
1019
|
+
// not-found / validation failures) finalize here, exactly like both pre-P1 paths.
|
|
1020
|
+
const entries = [];
|
|
1021
|
+
for (const toolCall of batch.calls) {
|
|
1022
|
+
await emit({
|
|
1023
|
+
type: "tool_execution_start",
|
|
1024
|
+
toolCallId: toolCall.id,
|
|
1025
|
+
toolName: toolCall.name,
|
|
1026
|
+
args: toolCall.arguments,
|
|
1027
|
+
});
|
|
1028
|
+
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
|
|
1029
|
+
if (preparation.kind === "immediate") {
|
|
1030
|
+
const finalized = { toolCall, result: preparation.result, isError: preparation.isError };
|
|
1031
|
+
await emitToolExecutionEnd(finalized, emit);
|
|
1032
|
+
entries.push({ finalized, toolCall });
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
entries.push({ prepared: preparation, toolCall });
|
|
1036
|
+
}
|
|
1037
|
+
if (signal?.aborted) {
|
|
1038
|
+
// Mid-preflight abort: later calls in this batch and all later batches stay unstarted —
|
|
1039
|
+
// same dangling-set shape as the pre-P1 paths.
|
|
1040
|
+
await settleBatch(entries, true);
|
|
1041
|
+
break outer;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
await settleBatch(entries, batch.safe);
|
|
1045
|
+
}
|
|
1046
|
+
return { messages, terminate: shouldTerminateToolBatch(allFinalized) };
|
|
1047
|
+
/** Execute the batch's prepared calls (capped-concurrent when safe, serial otherwise), then emit
|
|
1048
|
+
* this batch's tool-result messages in source order. */
|
|
1049
|
+
async function settleBatch(entries, concurrent) {
|
|
1050
|
+
const runOne = (prepared) => async () => {
|
|
1051
|
+
const executed = await executePreparedToolCall(prepared, signal, emit);
|
|
1052
|
+
const finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal);
|
|
1053
|
+
await emitToolExecutionEnd(finalized, emit);
|
|
1054
|
+
return finalized;
|
|
1055
|
+
};
|
|
1056
|
+
const preparedEntries = entries.filter((e) => e.prepared);
|
|
1057
|
+
let executedResults;
|
|
1058
|
+
if (concurrent && preparedEntries.length > 1) {
|
|
1059
|
+
executedResults = await runCapped(preparedEntries.map((e) => runOne(e.prepared)), cap);
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
executedResults = [];
|
|
1063
|
+
for (const e of preparedEntries) {
|
|
1064
|
+
executedResults.push(await runOne(e.prepared)());
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
let idx = 0;
|
|
1068
|
+
for (const e of entries) {
|
|
1069
|
+
const finalized = e.finalized ?? executedResults[idx++];
|
|
1070
|
+
if (!finalized)
|
|
1071
|
+
continue;
|
|
1072
|
+
allFinalized.push(finalized);
|
|
1073
|
+
const toolResultMessage = createToolResultMessage(finalized);
|
|
1074
|
+
await emitToolResultMessage(toolResultMessage, emit);
|
|
1075
|
+
messages.push(toolResultMessage);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* design/120 P2: executes concurrency-safe tool calls WHILE the model is still streaming.
|
|
1081
|
+
*
|
|
1082
|
+
* Lifecycle is per-stream (one instance per streamAssistantResponse call — the PTL-retry re-stream
|
|
1083
|
+
* gets a fresh one). Admission (see {@link AgentLoopConfig.streamingToolExecution}) is deliberately
|
|
1084
|
+
* narrower than CC's: opt-in flag + non-sequential dispatch + safe tool + source-order barrier + cap.
|
|
1085
|
+
* The barrier is the CC processQueue FIFO-break equivalent, hardened: once ANY call bounces to the
|
|
1086
|
+
* post-stream batch pipeline, every later call bounces too, so an admitted set is always a leading
|
|
1087
|
+
* prefix of the assistant batch — a source-order-later call can never execute before a bounced
|
|
1088
|
+
* source-order-earlier one, and the harvest's commit order stays identical to the P1 source order.
|
|
1089
|
+
*
|
|
1090
|
+
* In-stream we emit ONLY `tool_execution_start` (at launch) and `tool_execution_update` (the tool's
|
|
1091
|
+
* own onUpdate). Finalization (afterToolCall), `tool_execution_end` and the tool-result message
|
|
1092
|
+
* artifacts are all deferred to the post-stream harvest, where the FINAL assistant message exists —
|
|
1093
|
+
* a mid-stream partial must never reach the tool_result hook or the session (message artifacts
|
|
1094
|
+
* committed mid-stream would interleave illegally with the still-open assistant message).
|
|
1095
|
+
*/
|
|
1096
|
+
class StreamToolExecutor {
|
|
1097
|
+
context;
|
|
1098
|
+
config;
|
|
1099
|
+
signal;
|
|
1100
|
+
emit;
|
|
1101
|
+
entries = new Map();
|
|
1102
|
+
admittedOrder = [];
|
|
1103
|
+
barrier = false;
|
|
1104
|
+
inFlight = 0;
|
|
1105
|
+
constructor(context, config, signal, emit) {
|
|
1106
|
+
this.context = context;
|
|
1107
|
+
this.config = config;
|
|
1108
|
+
this.signal = signal;
|
|
1109
|
+
this.emit = emit;
|
|
1110
|
+
}
|
|
1111
|
+
/** Number of calls this executor admitted (0 ⇒ the stream behaved exactly as pre-P2). */
|
|
1112
|
+
get admittedCount() {
|
|
1113
|
+
return this.admittedOrder.length;
|
|
1114
|
+
}
|
|
1115
|
+
/** All admitted calls in admission order (read-only; used by the error-final content merge-back). */
|
|
1116
|
+
admittedToolCalls() {
|
|
1117
|
+
return this.admittedOrder.map((id) => this.entries.get(id)?.toolCall).filter((tc) => tc !== undefined);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Admission check + launch for a call whose arguments just completed mid-stream (`toolcall_end`).
|
|
1121
|
+
* Every rejection raises the source-order barrier — from then on this executor admits nothing.
|
|
1122
|
+
*/
|
|
1123
|
+
maybeAdmit(toolCall, partialAssistant) {
|
|
1124
|
+
if (this.barrier)
|
|
1125
|
+
return;
|
|
1126
|
+
if (this.config.streamingToolExecution !== true ||
|
|
1127
|
+
this.config.toolExecution === "sequential" ||
|
|
1128
|
+
this.signal?.aborted ||
|
|
1129
|
+
partialAssistant === null || // protocol violation (no start yet) — bounce AND raise the barrier
|
|
1130
|
+
!isBatchSafeToolCall(findToolByName(this.context.tools, toolCall.name), toolCall) || // P1.5: same per-call judgment as the batch partitioner (schema-invalid ⇒ unsafe, CC 206 parity)
|
|
1131
|
+
this.inFlight >= (this.config.maxToolConcurrency ?? 10) ||
|
|
1132
|
+
this.entries.has(toolCall.id) // defensive: at-most-once per id (provider contract)
|
|
1133
|
+
) {
|
|
1134
|
+
this.barrier = true;
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const entry = { toolCall, promise: Promise.resolve() };
|
|
1138
|
+
this.entries.set(toolCall.id, entry);
|
|
1139
|
+
this.admittedOrder.push(toolCall.id);
|
|
1140
|
+
entry.promise = this.runOne(entry, partialAssistant);
|
|
1141
|
+
}
|
|
1142
|
+
async runOne(entry, partialAssistant) {
|
|
1143
|
+
this.inFlight++;
|
|
1144
|
+
try {
|
|
1145
|
+
await this.emit({
|
|
1146
|
+
type: "tool_execution_start",
|
|
1147
|
+
toolCallId: entry.toolCall.id,
|
|
1148
|
+
toolName: entry.toolCall.name,
|
|
1149
|
+
args: entry.toolCall.arguments,
|
|
1150
|
+
});
|
|
1151
|
+
// Full prepare chain — defense in depth: beforeToolCall (block / updatedInput) works exactly
|
|
1152
|
+
// as on the batch path. The host's streamingToolExecution opt-in promises it cannot durably
|
|
1153
|
+
// suspend here. `partialAssistant` is the documented hook-payload snapshot (design/120 §5).
|
|
1154
|
+
const preparation = await prepareToolCall(this.context, partialAssistant, entry.toolCall, this.config, this.signal);
|
|
1155
|
+
if (preparation.kind === "immediate") {
|
|
1156
|
+
entry.immediate = preparation;
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
entry.prepared = preparation;
|
|
1160
|
+
entry.executed = await executePreparedToolCall(preparation, this.signal, this.emit);
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
// prepare/execute return error results by contract; this belt catches an emit() throw so
|
|
1164
|
+
// settle() never rejects and the held outcome is still a well-formed error result.
|
|
1165
|
+
entry.immediate = {
|
|
1166
|
+
kind: "immediate",
|
|
1167
|
+
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
|
1168
|
+
isError: true,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
finally {
|
|
1172
|
+
this.inFlight--;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* The stream-final barrier (design/120 §5.2): await every in-flight call. MUST run after ANY
|
|
1177
|
+
* stream final (done/error/aborted) and before any later step — the batch pipeline therefore
|
|
1178
|
+
* starts with zero in-stream calls in flight (sequential exclusivity + the concurrency cap hold
|
|
1179
|
+
* structurally), and nothing can emit after the turn exits.
|
|
1180
|
+
*/
|
|
1181
|
+
async settle() {
|
|
1182
|
+
this.barrier = true;
|
|
1183
|
+
await Promise.all([...this.entries.values()].map((e) => e.promise));
|
|
1184
|
+
}
|
|
1185
|
+
/** After settle: remove and return the held outcome for a call id (undefined = not admitted). */
|
|
1186
|
+
take(id) {
|
|
1187
|
+
const entry = this.entries.get(id);
|
|
1188
|
+
if (entry)
|
|
1189
|
+
this.entries.delete(id);
|
|
1190
|
+
return entry;
|
|
1191
|
+
}
|
|
1192
|
+
/** After settle: entries not yet taken, in admission order (the error-final harvest). */
|
|
1193
|
+
remaining() {
|
|
1194
|
+
return this.admittedOrder
|
|
1195
|
+
.map((id) => this.entries.get(id))
|
|
1196
|
+
.filter((e) => e !== undefined);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
/** Finalize one held executor entry into the standard outcome shape (afterToolCall runs HERE, with
|
|
1200
|
+
* the final assistant message — never a mid-stream partial). */
|
|
1201
|
+
async function finalizeStreamEntry(currentContext, assistantMessage, entry, config, signal) {
|
|
1202
|
+
if (entry.prepared && entry.executed) {
|
|
1203
|
+
return finalizeExecutedToolCall(currentContext, assistantMessage, entry.prepared, entry.executed, config, signal);
|
|
1204
|
+
}
|
|
1205
|
+
const immediate = entry.immediate ?? {
|
|
1206
|
+
kind: "immediate",
|
|
1207
|
+
// Unreachable after settle() (runOne always sets one of the outcome fields) — fail honest.
|
|
1208
|
+
result: createErrorToolResult("in-stream execution produced no outcome"),
|
|
1209
|
+
isError: true,
|
|
1210
|
+
};
|
|
1211
|
+
return { toolCall: entry.toolCall, result: immediate.result, isError: immediate.isError };
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* design/120 impl-review F-3: an executor entry whose call cannot be attached to the transcript
|
|
1215
|
+
* (missing from the final content — a provider-contract breach) already emitted
|
|
1216
|
+
* `tool_execution_start` in-stream; close the start/end pair with a synthetic error end so
|
|
1217
|
+
* activity/spinner consumers that pair by toolCallId never hold a permanently-open call. The
|
|
1218
|
+
* outcome itself is dropped (an ownerless toolResult would be an illegal transcript).
|
|
1219
|
+
*/
|
|
1220
|
+
async function closeOrphanedStreamEntry(entry, emit) {
|
|
1221
|
+
await emit({
|
|
1222
|
+
type: "tool_execution_end",
|
|
1223
|
+
toolCallId: entry.toolCall.id,
|
|
1224
|
+
toolName: entry.toolCall.name,
|
|
1225
|
+
result: createErrorToolResult("in-stream result dropped: the call is missing from the final assistant content (provider contract breach)"),
|
|
1226
|
+
isError: true,
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* design/120 §5.4 error/aborted-final harvest: commit every held outcome (the side effects already
|
|
1231
|
+
* happened — a real result beats a wake-reconcile [INTERRUPTED] synthesis), in admission order,
|
|
1232
|
+
* BEFORE the terminal turn_end/agent_end. Unstarted calls stay untouched — the existing wake A-1
|
|
1233
|
+
* reconcile closes them honestly.
|
|
1234
|
+
*/
|
|
1235
|
+
async function harvestExecutorOnErrorFinal(executor, state, finalMessage, signal, emit) {
|
|
1236
|
+
const held = executor.remaining();
|
|
1237
|
+
if (held.length === 0)
|
|
1238
|
+
return [];
|
|
1239
|
+
const results = [];
|
|
1240
|
+
const persistedCallIds = new Set(finalMessage.content.filter((c) => c.type === "toolCall").map((c) => c.id));
|
|
1241
|
+
for (const entry of held) {
|
|
1242
|
+
executor.take(entry.toolCall.id);
|
|
1243
|
+
// A result may only enter the transcript when its toolCall exists on the persisted assistant —
|
|
1244
|
+
// the merge-back in streamAssistantResponse makes this the norm; a missing id here means the
|
|
1245
|
+
// merge-back path was bypassed (defensive), and an ownerless toolResult would be an illegal
|
|
1246
|
+
// transcript, so the outcome is dropped — with a synthetic end closing its in-stream start (F-3).
|
|
1247
|
+
if (!persistedCallIds.has(entry.toolCall.id)) {
|
|
1248
|
+
await closeOrphanedStreamEntry(entry, emit);
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const finalized = await finalizeStreamEntry(state.context, finalMessage, entry, state.config, signal);
|
|
1252
|
+
await emitToolExecutionEnd(finalized, emit);
|
|
1253
|
+
const toolResultMessage = createToolResultMessage(finalized);
|
|
1254
|
+
await emitToolResultMessage(toolResultMessage, emit);
|
|
1255
|
+
state.context.messages.push(toolResultMessage);
|
|
1256
|
+
state.newMessages.push(toolResultMessage);
|
|
1257
|
+
results.push(toolResultMessage);
|
|
1258
|
+
}
|
|
1259
|
+
return results;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* design/120 §5.4 content-loss defense: an error/aborted final whose message dropped mid-stream
|
|
1263
|
+
* blocks (the stream-engine tear path synthesizes an EMPTY-content error assistant) would leave an
|
|
1264
|
+
* already-executed in-stream call with zero transcript trace — no toolCall on the assistant, so its
|
|
1265
|
+
* result could never be attached and even wake reconcile couldn't see an orphan. Merge the
|
|
1266
|
+
* executor-held calls back into the final content BEFORE message_end (the persistence point).
|
|
1267
|
+
* The message also stops qualifying as an empty-failure skip, so it persists.
|
|
1268
|
+
*/
|
|
1269
|
+
function mergeBackAdmittedCalls(finalMessage, executor) {
|
|
1270
|
+
const admitted = executor.admittedToolCalls();
|
|
1271
|
+
if (admitted.length === 0)
|
|
1272
|
+
return;
|
|
1273
|
+
const present = new Set(finalMessage.content.filter((c) => c.type === "toolCall").map((c) => c.id));
|
|
1274
|
+
const missing = admitted.filter((tc) => !present.has(tc.id));
|
|
1275
|
+
if (missing.length === 0)
|
|
1276
|
+
return;
|
|
1277
|
+
finalMessage.content = [...finalMessage.content, ...missing];
|
|
1278
|
+
}
|
|
1279
|
+
function shouldTerminateToolBatch(finalizedCalls) {
|
|
1280
|
+
return (finalizedCalls.length > 0 &&
|
|
1281
|
+
finalizedCalls.every((finalized) => finalized.result.terminate === true));
|
|
1282
|
+
}
|
|
1283
|
+
function prepareToolCallArguments(tool, toolCall) {
|
|
1284
|
+
if (!tool.prepareArguments) {
|
|
1285
|
+
return toolCall;
|
|
1286
|
+
}
|
|
1287
|
+
const preparedArguments = tool.prepareArguments(toolCall.arguments);
|
|
1288
|
+
if (preparedArguments === toolCall.arguments) {
|
|
1289
|
+
return toolCall;
|
|
1290
|
+
}
|
|
1291
|
+
return {
|
|
1292
|
+
...toolCall,
|
|
1293
|
+
arguments: preparedArguments,
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
async function prepareToolCall(currentContext, assistantMessage, toolCall, config, signal) {
|
|
1297
|
+
const tool = findToolByName(currentContext.tools, toolCall.name);
|
|
1298
|
+
if (!tool) {
|
|
1299
|
+
// design/136 §2.4 — TOMBSTONED tool names get an explicit migration message (immediate error result,
|
|
1300
|
+
// model can self-correct) instead of the generic not-found. Checked ONLY on a roster miss, so a
|
|
1301
|
+
// deployment-defined custom tool of the same name wins (the tombstone yields).
|
|
1302
|
+
const tombstone = TOMBSTONED_TOOLS[toolCall.name];
|
|
1303
|
+
if (tombstone !== undefined) {
|
|
1304
|
+
return {
|
|
1305
|
+
kind: "immediate",
|
|
1306
|
+
result: createErrorToolResult(tombstone),
|
|
1307
|
+
isError: true,
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
// make-real field finding (design/120 验证): a weaker model that guesses a wrong tool name
|
|
1311
|
+
// (bash/cat/exec/read_file…) got a bare "not found" and kept guessing until the run died.
|
|
1312
|
+
// List the real roster so the very next attempt can self-correct — the highest-leverage
|
|
1313
|
+
// recovery text in the loop (bounded: names only).
|
|
1314
|
+
const available = (currentContext.tools ?? []).map((t) => t.name).join(", ");
|
|
1315
|
+
return {
|
|
1316
|
+
kind: "immediate",
|
|
1317
|
+
result: createErrorToolResult(`Tool ${toolCall.name} not found.${available ? ` Available tools: ${available}` : ""}`),
|
|
1318
|
+
isError: true,
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
try {
|
|
1322
|
+
const preparedToolCall = prepareToolCallArguments(tool, toolCall);
|
|
1323
|
+
let finalArgs = validateToolArguments(tool, preparedToolCall);
|
|
1324
|
+
if (config.beforeToolCall) {
|
|
1325
|
+
const beforeResult = await config.beforeToolCall({
|
|
1326
|
+
assistantMessage,
|
|
1327
|
+
toolCall,
|
|
1328
|
+
args: finalArgs,
|
|
1329
|
+
context: currentContext,
|
|
1330
|
+
}, signal);
|
|
1331
|
+
if (signal?.aborted) {
|
|
1332
|
+
return {
|
|
1333
|
+
kind: "immediate",
|
|
1334
|
+
result: createErrorToolResult("Operation aborted"),
|
|
1335
|
+
isError: true,
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
if (beforeResult?.block) {
|
|
1339
|
+
return {
|
|
1340
|
+
kind: "immediate",
|
|
1341
|
+
result: createErrorToolResult(beforeResult.reason || "Tool execution was blocked"),
|
|
1342
|
+
isError: true,
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
// A non-blocking hook may rewrite the args (design/37 `updatedInput`). Re-validate the rewrite
|
|
1346
|
+
// against the tool schema before it can reach `tool.execute()` — a hook must never be able to
|
|
1347
|
+
// smuggle schema-invalid args past validation. Throws propagate to the catch below as an error
|
|
1348
|
+
// tool result, exactly like an invalid model-supplied call.
|
|
1349
|
+
if (beforeResult?.updatedInput !== undefined) {
|
|
1350
|
+
finalArgs = validateToolArguments(tool, {
|
|
1351
|
+
...preparedToolCall,
|
|
1352
|
+
arguments: beforeResult.updatedInput,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (signal?.aborted) {
|
|
1357
|
+
return {
|
|
1358
|
+
kind: "immediate",
|
|
1359
|
+
result: createErrorToolResult("Operation aborted"),
|
|
1360
|
+
isError: true,
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
return {
|
|
1364
|
+
kind: "prepared",
|
|
1365
|
+
toolCall,
|
|
1366
|
+
tool,
|
|
1367
|
+
args: finalArgs,
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
return {
|
|
1372
|
+
kind: "immediate",
|
|
1373
|
+
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
|
1374
|
+
isError: true,
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
async function executePreparedToolCall(prepared, signal, emit) {
|
|
1379
|
+
const updateEvents = [];
|
|
1380
|
+
// Upstream-parity gate (2026-07 diff, openclaw 5ea80c8d80 class): a misbehaving tool that keeps
|
|
1381
|
+
// calling onUpdate AFTER its execute() promise settled would otherwise emit
|
|
1382
|
+
// `tool_execution_update` events after `tool_execution_end`/result persistence — an event-order
|
|
1383
|
+
// contract violation. Once the outcome is settled, late updates are silently dropped.
|
|
1384
|
+
let acceptingUpdates = true;
|
|
1385
|
+
// VENDORED EDIT provenance (sema durable-checkpoint hardening): a deferred PARALLEL tool closure
|
|
1386
|
+
// (preflight-gated, then run together in Promise.all) can reach here AFTER a mid-batch durable
|
|
1387
|
+
// suspend/abort fired `signal.abort()`. Without this guard, a tool whose own `execute` ignores
|
|
1388
|
+
// `signal.aborted` would run its side effect on the off-branch turn, yet the durable resume tells
|
|
1389
|
+
// the model that sibling "was NOT executed" → DOUBLE-execution. Short-circuit BEFORE `tool.execute`
|
|
1390
|
+
// so the "not executed" assertion holds unconditionally — even for a non-abort-cooperative tool —
|
|
1391
|
+
// keeping the once-only/exactly-once continuation invariant (design/45 §4.ter).
|
|
1392
|
+
if (signal?.aborted) {
|
|
1393
|
+
return { result: createErrorToolResult("operation aborted before execution"), isError: true };
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
const result = await prepared.tool.execute(prepared.toolCall.id, prepared.args, signal, (partialResult) => {
|
|
1397
|
+
if (!acceptingUpdates) {
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
updateEvents.push(Promise.resolve(emit({
|
|
1401
|
+
type: "tool_execution_update",
|
|
1402
|
+
toolCallId: prepared.toolCall.id,
|
|
1403
|
+
toolName: prepared.toolCall.name,
|
|
1404
|
+
args: prepared.toolCall.arguments,
|
|
1405
|
+
partialResult,
|
|
1406
|
+
})));
|
|
1407
|
+
});
|
|
1408
|
+
acceptingUpdates = false;
|
|
1409
|
+
await Promise.all(updateEvents);
|
|
1410
|
+
return { result, isError: false };
|
|
1411
|
+
}
|
|
1412
|
+
catch (error) {
|
|
1413
|
+
acceptingUpdates = false;
|
|
1414
|
+
await Promise.all(updateEvents);
|
|
1415
|
+
return {
|
|
1416
|
+
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
|
1417
|
+
isError: true,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal) {
|
|
1422
|
+
let result = executed.result;
|
|
1423
|
+
let isError = executed.isError;
|
|
1424
|
+
if (config.afterToolCall) {
|
|
1425
|
+
try {
|
|
1426
|
+
const afterResult = await config.afterToolCall({
|
|
1427
|
+
assistantMessage,
|
|
1428
|
+
toolCall: prepared.toolCall,
|
|
1429
|
+
args: prepared.args,
|
|
1430
|
+
result,
|
|
1431
|
+
isError,
|
|
1432
|
+
context: currentContext,
|
|
1433
|
+
}, signal);
|
|
1434
|
+
if (afterResult) {
|
|
1435
|
+
result = {
|
|
1436
|
+
content: afterResult.content ?? result.content,
|
|
1437
|
+
details: afterResult.details ?? result.details,
|
|
1438
|
+
terminate: afterResult.terminate ?? result.terminate,
|
|
1439
|
+
};
|
|
1440
|
+
isError = afterResult.isError ?? isError;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
catch (error) {
|
|
1444
|
+
// VENDORED EDIT provenance (sema vendored bug-hunt D4): the tool ALREADY executed (its side
|
|
1445
|
+
// effect committed in executePreparedToolCall) BEFORE afterToolCall runs. A throw in the
|
|
1446
|
+
// post-tool hook (sema design/37 tool_result policy) must NOT rewrite a SUCCEEDED
|
|
1447
|
+
// side-effecting tool into a FLAT failure — in our autonomous loop the model "recovers" by
|
|
1448
|
+
// RE-issuing it (duplicate open_pr / double write_file). Keep the real outcome (result/isError
|
|
1449
|
+
// unchanged) and APPEND a note that POST-processing failed; never force isError.
|
|
1450
|
+
const note = `[post-tool processing failed (the tool already executed): ${error instanceof Error ? error.message : String(error)}]`;
|
|
1451
|
+
result = { ...result, content: [...result.content, { type: "text", text: note }] };
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
toolCall: prepared.toolCall,
|
|
1456
|
+
result,
|
|
1457
|
+
isError,
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
function createErrorToolResult(message) {
|
|
1461
|
+
return {
|
|
1462
|
+
content: [{ type: "text", text: message }],
|
|
1463
|
+
details: {},
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
async function emitToolExecutionEnd(finalized, emit) {
|
|
1467
|
+
await emit({
|
|
1468
|
+
type: "tool_execution_end",
|
|
1469
|
+
toolCallId: finalized.toolCall.id,
|
|
1470
|
+
toolName: finalized.toolCall.name,
|
|
1471
|
+
result: finalized.result,
|
|
1472
|
+
isError: finalized.isError,
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
function createToolResultMessage(finalized) {
|
|
1476
|
+
return {
|
|
1477
|
+
role: "toolResult",
|
|
1478
|
+
toolCallId: finalized.toolCall.id,
|
|
1479
|
+
toolName: finalized.toolCall.name,
|
|
1480
|
+
content: finalized.result.content,
|
|
1481
|
+
details: finalized.result.details,
|
|
1482
|
+
isError: finalized.isError,
|
|
1483
|
+
timestamp: Date.now(),
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
async function emitToolResultMessage(toolResultMessage, emit) {
|
|
1487
|
+
await emit({ type: "message_start", message: toolResultMessage });
|
|
1488
|
+
await emit({ type: "message_end", message: toolResultMessage });
|
|
1489
|
+
}
|
|
1490
|
+
//# sourceMappingURL=agent-loop.js.map
|