@sema-agent/server 1.322.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/approval-hmac.d.ts +40 -0
- package/dist/approval-hmac.js +67 -0
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +72 -4
- package/dist/audit.d.ts +26 -0
- package/dist/audit.js +40 -1
- package/dist/auth-bridge.d.ts +10 -0
- package/dist/auth-bridge.js +47 -4
- package/dist/auth-keys.d.ts +19 -0
- package/dist/auth-keys.js +13 -0
- package/dist/bake-runner/main.d.ts +5 -0
- package/dist/bake-runner/main.js +43 -4
- package/dist/bake-runner/protocol.d.ts +57 -0
- package/dist/bake-runner/protocol.js +58 -3
- package/dist/bake-runner/runner.d.ts +54 -0
- package/dist/bake-runner/runner.js +87 -5
- package/dist/bench/l8/artifact.d.ts +63 -0
- package/dist/bench/l8/artifact.js +61 -2
- package/dist/bench/l8/escape.d.ts +36 -0
- package/dist/bench/l8/escape.js +25 -0
- package/dist/bench/l8/index.d.ts +14 -0
- package/dist/bench/l8/index.js +14 -0
- package/dist/bench/l8/probes.d.ts +74 -0
- package/dist/bench/l8/probes.js +60 -0
- package/dist/bench/l8/run-probes.d.ts +43 -0
- package/dist/bench/l8/run-probes.js +45 -2
- package/dist/bench/s1/arms.d.ts +126 -0
- package/dist/bench/s1/arms.js +188 -13
- package/dist/bench/s1/live-deps.d.ts +66 -0
- package/dist/bench/s1/live-deps.js +249 -11
- package/dist/bench/s1/oracle.d.ts +49 -0
- package/dist/bench/s1/oracle.js +36 -1
- package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
- package/dist/bench/s1/repair-oracle-adapter.js +30 -0
- package/dist/bench/s1/reviewer.d.ts +38 -0
- package/dist/bench/s1/reviewer.js +34 -0
- package/dist/bench/s1/row.d.ts +77 -0
- package/dist/bench/s1/row.js +62 -0
- package/dist/bench/s1/run-firm.d.ts +47 -0
- package/dist/bench/s1/run-firm.js +70 -4
- package/dist/bench/s1/runner-ctx.d.ts +71 -0
- package/dist/bench/s1/runner-ctx.js +36 -0
- package/dist/bench/s1/tasks.d.ts +108 -0
- package/dist/bench/s1/tasks.js +115 -0
- package/dist/boot-reclaim.d.ts +19 -0
- package/dist/boot-reclaim.js +20 -0
- package/dist/brain.d.ts +25 -0
- package/dist/brain.js +61 -1
- package/dist/budget.d.ts +66 -0
- package/dist/budget.js +128 -4
- package/dist/capabilities/builtin-tools.d.ts +3 -0
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/center-plugins.d.ts +17 -1
- package/dist/capabilities/center-plugins.js +45 -4
- package/dist/capabilities/center-prompts.d.ts +46 -0
- package/dist/capabilities/center-prompts.js +57 -3
- package/dist/capabilities/code-review-council.d.ts +28 -0
- package/dist/capabilities/code-review-council.js +40 -7
- package/dist/capabilities/collab-wire.d.ts +2 -0
- package/dist/capabilities/collab-workflows.d.ts +7 -0
- package/dist/capabilities/collab-workflows.js +44 -1
- package/dist/capabilities/oa-tools.d.ts +6 -0
- package/dist/capabilities/oa-tools.js +10 -0
- package/dist/capabilities/prompt.d.ts +14 -0
- package/dist/capabilities/prompt.js +14 -0
- package/dist/capabilities/prompts/code-review.d.ts +14 -0
- package/dist/capabilities/prompts/code-review.js +14 -0
- package/dist/capabilities/prompts/identity.d.ts +6 -0
- package/dist/capabilities/prompts/identity.js +6 -0
- package/dist/capabilities/prompts/team.d.ts +4 -0
- package/dist/capabilities/prompts/team.js +4 -0
- package/dist/capabilities/repo-tools.d.ts +11 -0
- package/dist/capabilities/repo-tools.js +8 -0
- package/dist/capabilities/sandbox-file-send.d.ts +95 -0
- package/dist/capabilities/sandbox-file-send.js +91 -3
- package/dist/capabilities/scenarios.d.ts +130 -0
- package/dist/capabilities/scenarios.js +180 -7
- package/dist/capabilities/select-environment-tool.d.ts +7 -0
- package/dist/capabilities/select-environment-tool.js +30 -0
- package/dist/capabilities/send-user-file-tool.d.ts +33 -0
- package/dist/capabilities/send-user-file-tool.js +65 -1
- package/dist/capabilities/skills.d.ts +29 -0
- package/dist/capabilities/skills.js +13 -2
- package/dist/capabilities/team.d.ts +22 -0
- package/dist/capabilities/team.js +22 -2
- package/dist/capabilities/tool-defer.d.ts +26 -0
- package/dist/capabilities/tool-defer.js +3 -0
- package/dist/config-center/apply-effective.d.ts +65 -0
- package/dist/config-center/apply-effective.js +204 -12
- package/dist/config-center/http-client.d.ts +34 -0
- package/dist/config-center/http-client.js +68 -0
- package/dist/config-center/restart-signal.d.ts +22 -0
- package/dist/config-center/restart-signal.js +39 -0
- package/dist/config-center/skills-mcp.d.ts +20 -1
- package/dist/config-center/skills-mcp.js +42 -7
- package/dist/config-center/types.d.ts +92 -1
- package/dist/config-lkg.d.ts +12 -0
- package/dist/config-lkg.js +41 -0
- package/dist/config-provider.d.ts +78 -0
- package/dist/config-provider.js +165 -2
- package/dist/config-types.d.ts +546 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.js +455 -60
- package/dist/degenerate-instrument.d.ts +38 -0
- package/dist/degenerate-instrument.js +55 -3
- package/dist/elicitation.d.ts +39 -0
- package/dist/elicitation.js +87 -3
- package/dist/env-facts.d.ts +61 -0
- package/dist/env-facts.js +73 -7
- package/dist/fleet/fleet-bus.d.ts +185 -0
- package/dist/fleet/fleet-bus.js +333 -27
- package/dist/fleet/subagent-tail-bus.d.ts +11 -0
- package/dist/fleet/subagent-tail-bus.js +33 -2
- package/dist/fleet-client.d.ts +38 -0
- package/dist/fleet-client.js +44 -9
- package/dist/fleet-lease.d.ts +46 -0
- package/dist/fleet-lease.js +73 -5
- package/dist/hooks/branch-transcript.d.ts +8 -0
- package/dist/hooks/branch-transcript.js +33 -0
- package/dist/hooks/cc-stop-prompt.d.ts +42 -0
- package/dist/hooks/cc-stop-prompt.js +43 -1
- package/dist/hooks/hook-llm.d.ts +15 -0
- package/dist/hooks/hook-llm.js +47 -0
- package/dist/hooks/hook-runner.d.ts +89 -0
- package/dist/hooks/hook-runner.js +327 -18
- package/dist/http/idempotency.d.ts +31 -0
- package/dist/http/idempotency.js +34 -0
- package/dist/http/principal-gate.d.ts +17 -0
- package/dist/http/principal-gate.js +35 -3
- package/dist/http/route-ctx.d.ts +36 -0
- package/dist/http/routes/approvals-assistant.d.ts +25 -0
- package/dist/http/routes/approvals-assistant.js +253 -23
- package/dist/http/routes/attachments.js +22 -4
- package/dist/http/routes/capabilities.d.ts +7 -0
- package/dist/http/routes/capabilities.js +202 -1
- package/dist/http/routes/fleet.d.ts +6 -0
- package/dist/http/routes/fleet.js +94 -5
- package/dist/http/routes/images.d.ts +17 -0
- package/dist/http/routes/images.js +222 -14
- package/dist/http/routes/leader.d.ts +7 -0
- package/dist/http/routes/leader.js +10 -0
- package/dist/http/routes/memory-policy.d.ts +6 -0
- package/dist/http/routes/memory-policy.js +38 -6
- package/dist/http/routes/notify-wake.d.ts +10 -0
- package/dist/http/routes/notify-wake.js +19 -3
- package/dist/http/routes/observability.d.ts +6 -0
- package/dist/http/routes/observability.js +19 -2
- package/dist/http/routes/runs.d.ts +15 -0
- package/dist/http/routes/runs.js +464 -41
- package/dist/http/routes/session-sync.d.ts +15 -0
- package/dist/http/routes/session-sync.js +229 -19
- package/dist/http/routes/sessions-list.d.ts +6 -0
- package/dist/http/routes/sessions-list.js +35 -4
- package/dist/http/routes/sessions.d.ts +14 -0
- package/dist/http/routes/sessions.js +176 -31
- package/dist/http/routes/side-query.d.ts +8 -0
- package/dist/http/routes/side-query.js +26 -1
- package/dist/http/routes/tasks.d.ts +18 -0
- package/dist/http/routes/tasks.js +501 -38
- package/dist/http/routes/trace-usage.d.ts +10 -0
- package/dist/http/routes/trace-usage.js +66 -14
- package/dist/http/routes/workflows.d.ts +12 -0
- package/dist/http/routes/workflows.js +180 -19
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +6 -0
- package/dist/http/send.d.ts +19 -0
- package/dist/http/send.js +11 -0
- package/dist/http/server.d.ts +255 -0
- package/dist/http/server.js +921 -75
- package/dist/http/sse-log.d.ts +15 -0
- package/dist/http/sse-log.js +16 -0
- package/dist/http/tar.d.ts +9 -0
- package/dist/http/tar.js +21 -5
- package/dist/http/wire-gate.d.ts +9 -0
- package/dist/http/wire-gate.js +9 -0
- package/dist/http/wire-types.d.ts +187 -0
- package/dist/http/workspace-content.d.ts +8 -0
- package/dist/http/workspace-content.js +10 -0
- package/dist/images/bake-validate.d.ts +63 -0
- package/dist/images/bake-validate.js +71 -1
- package/dist/images/manifest.d.ts +19 -0
- package/dist/images/manifest.js +6 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.js +10 -5
- package/dist/key-resolver.d.ts +23 -0
- package/dist/key-resolver.js +27 -2
- package/dist/leader/diffout.d.ts +23 -0
- package/dist/leader/diffout.js +8 -1
- package/dist/leader/diffup.d.ts +32 -0
- package/dist/leader/diffup.js +43 -0
- package/dist/leader/endpoint.d.ts +20 -0
- package/dist/leader/endpoint.js +25 -2
- package/dist/leader/fanout.d.ts +60 -0
- package/dist/leader/fanout.js +29 -3
- package/dist/leader/grader-env-factory.d.ts +74 -0
- package/dist/leader/grader-env-factory.js +62 -3
- package/dist/leader/leader.d.ts +97 -0
- package/dist/leader/leader.js +151 -4
- package/dist/leader/merge.d.ts +94 -0
- package/dist/leader/merge.js +112 -9
- package/dist/leader/planner.d.ts +52 -0
- package/dist/leader/planner.js +47 -3
- package/dist/leader/repair-oracle.d.ts +61 -0
- package/dist/leader/repair-oracle.js +59 -1
- package/dist/leader/repair-wire.d.ts +78 -0
- package/dist/leader/repair-wire.js +79 -2
- package/dist/leader/wire.d.ts +79 -0
- package/dist/leader/wire.js +255 -8
- package/dist/lsp/e2b-bridge.d.ts +16 -0
- package/dist/lsp/e2b-bridge.js +32 -4
- package/dist/lsp/e2b-manager.d.ts +10 -0
- package/dist/lsp/e2b-manager.js +18 -2
- package/dist/lsp/lsp-frames.d.ts +12 -0
- package/dist/lsp/lsp-frames.js +12 -0
- package/dist/lsp/manager.d.ts +12 -0
- package/dist/lsp/manager.js +35 -2
- package/dist/lsp/types.d.ts +10 -0
- package/dist/lsp/ws-transport.d.ts +12 -0
- package/dist/lsp/ws-transport.js +53 -5
- package/dist/lsp-evict.d.ts +14 -0
- package/dist/lsp-evict.js +15 -1
- package/dist/main.js +2132 -96
- package/dist/memory-export.d.ts +1 -0
- package/dist/memory-export.js +4 -0
- package/dist/memory-scope.d.ts +49 -0
- package/dist/memory-scope.js +87 -4
- package/dist/memory-sync-client.d.ts +24 -0
- package/dist/memory-sync-client.js +41 -2
- package/dist/memory-sync.d.ts +23 -0
- package/dist/memory-sync.js +70 -1
- package/dist/model-select.d.ts +39 -0
- package/dist/model-select.js +42 -1
- package/dist/observability/cost-quota.d.ts +20 -0
- package/dist/observability/cost-quota.js +4 -0
- package/dist/observability/cost-taxonomy.d.ts +45 -0
- package/dist/observability/cost-taxonomy.js +34 -0
- package/dist/observability/logger.d.ts +1 -0
- package/dist/observability/logger.js +6 -0
- package/dist/observability/metrics.d.ts +13 -0
- package/dist/observability/metrics.js +63 -0
- package/dist/observability/otel-exporter.d.ts +9 -0
- package/dist/observability/otel-exporter.js +11 -3
- package/dist/observability/principal-context.d.ts +9 -0
- package/dist/observability/principal-context.js +9 -0
- package/dist/observability/prompt-manifest.d.ts +37 -0
- package/dist/observability/prompt-manifest.js +35 -1
- package/dist/observability/rate-limit.d.ts +10 -0
- package/dist/observability/rate-limit.js +1 -0
- package/dist/observability/tool-trace.d.ts +24 -0
- package/dist/observability/tool-trace.js +41 -1
- package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
- package/dist/orchestration/hardened-vm-runner.js +119 -4
- package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
- package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
- package/dist/orchestration/hardened-vm-worker.js +27 -0
- package/dist/orchestration/subagent-steer.d.ts +44 -0
- package/dist/orchestration/subagent-steer.js +45 -1
- package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
- package/dist/orchestration/workflow-agent-steer.js +80 -1
- package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
- package/dist/orchestration/workflow-completion-inbox.js +283 -32
- package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
- package/dist/orchestration/workflow-notify-journal.js +187 -13
- package/dist/parked-decide.d.ts +61 -0
- package/dist/parked-decide.js +58 -1
- package/dist/per-task-image.d.ts +54 -0
- package/dist/per-task-image.js +41 -0
- package/dist/plan-cache-probe.d.ts +35 -0
- package/dist/plan-cache-probe.js +18 -3
- package/dist/plugins/approval-exemption-store.d.ts +7 -0
- package/dist/plugins/approval-exemption-store.js +28 -2
- package/dist/plugins/approval-store-sql.d.ts +64 -0
- package/dist/plugins/approval-store-sql.js +32 -0
- package/dist/plugins/background-agent-store-sql.d.ts +55 -0
- package/dist/plugins/background-agent-store-sql.js +91 -4
- package/dist/plugins/background-shell-support.d.ts +98 -0
- package/dist/plugins/background-shell-support.js +117 -13
- package/dist/plugins/blob-backend.d.ts +104 -0
- package/dist/plugins/blob-backend.js +163 -6
- package/dist/plugins/breaker-state-sql.d.ts +65 -1
- package/dist/plugins/breaker-state-sql.js +46 -8
- package/dist/plugins/caching-session-store.d.ts +42 -0
- package/dist/plugins/caching-session-store.js +63 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
- package/dist/plugins/checkpoint-store-sql.js +239 -11
- package/dist/plugins/file-outcome-sink.d.ts +4 -0
- package/dist/plugins/file-outcome-sink.js +9 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
- package/dist/plugins/file-resume-anchor-store.js +43 -4
- package/dist/plugins/file-run-store.d.ts +90 -0
- package/dist/plugins/file-run-store.js +145 -13
- package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
- package/dist/plugins/file-snapshot-store-sql.js +178 -10
- package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
- package/dist/plugins/file-workflow-journal-store.js +10 -0
- package/dist/plugins/fork-routing-session-store.d.ts +47 -0
- package/dist/plugins/fork-routing-session-store.js +51 -3
- package/dist/plugins/host-platform.d.ts +69 -0
- package/dist/plugins/host-platform.js +81 -2
- package/dist/plugins/image-bake-store-sql.d.ts +136 -1
- package/dist/plugins/image-bake-store-sql.js +241 -7
- package/dist/plugins/image-index-sql.d.ts +46 -0
- package/dist/plugins/image-index-sql.js +113 -4
- package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
- package/dist/plugins/k8s-bg-scripts.js +88 -4
- package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
- package/dist/plugins/k8s-exec-protocol.js +41 -0
- package/dist/plugins/local-checkpoint-store.d.ts +40 -0
- package/dist/plugins/local-checkpoint-store.js +86 -7
- package/dist/plugins/local-session-store.d.ts +157 -0
- package/dist/plugins/local-session-store.js +289 -34
- package/dist/plugins/local-task-attachment-store.js +14 -2
- package/dist/plugins/mailbox-store-sql.d.ts +38 -0
- package/dist/plugins/mailbox-store-sql.js +39 -6
- package/dist/plugins/memory-engine-pg.d.ts +30 -0
- package/dist/plugins/memory-engine-pg.js +122 -4
- package/dist/plugins/memory-engine-tidb.d.ts +26 -0
- package/dist/plugins/memory-engine-tidb.js +115 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
- package/dist/plugins/memory-engine-vector-util.js +5 -0
- package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
- package/dist/plugins/memory-resume-anchor-store.js +17 -0
- package/dist/plugins/memory-run-store.d.ts +63 -0
- package/dist/plugins/memory-run-store.js +73 -8
- package/dist/plugins/memory-session-policy-store.d.ts +17 -0
- package/dist/plugins/memory-session-policy-store.js +17 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
- package/dist/plugins/memory-sync-store-pg.js +45 -2
- package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
- package/dist/plugins/memory-sync-store-tidb.js +31 -1
- package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
- package/dist/plugins/outcome-ledger-sql.js +97 -3
- package/dist/plugins/pg-approval-store.d.ts +7 -0
- package/dist/plugins/pg-approval-store.js +7 -0
- package/dist/plugins/pg-breaker-state.d.ts +6 -0
- package/dist/plugins/pg-breaker-state.js +6 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
- package/dist/plugins/pg-checkpoint-store.js +8 -0
- package/dist/plugins/pg-cost-quota.d.ts +17 -0
- package/dist/plugins/pg-cost-quota.js +6 -0
- package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/pg-file-snapshot-store.js +6 -0
- package/dist/plugins/pg-image-bake.d.ts +9 -0
- package/dist/plugins/pg-image-bake.js +9 -0
- package/dist/plugins/pg-image-index.d.ts +9 -0
- package/dist/plugins/pg-image-index.js +9 -0
- package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
- package/dist/plugins/pg-outcome-ledger.js +9 -0
- package/dist/plugins/pg-pool.d.ts +50 -0
- package/dist/plugins/pg-pool.js +78 -0
- package/dist/plugins/pg-query.d.ts +11 -0
- package/dist/plugins/pg-rate-limiter.d.ts +23 -0
- package/dist/plugins/pg-rate-limiter.js +12 -2
- package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/pg-resume-anchor-store.js +5 -0
- package/dist/plugins/pg-run-store.d.ts +7 -0
- package/dist/plugins/pg-run-store.js +7 -0
- package/dist/plugins/pg-safe-json.d.ts +13 -0
- package/dist/plugins/pg-safe-json.js +40 -4
- package/dist/plugins/pg-session-policy-store.d.ts +5 -0
- package/dist/plugins/pg-session-policy-store.js +5 -0
- package/dist/plugins/pg-session-storage.d.ts +72 -0
- package/dist/plugins/pg-session-storage.js +152 -20
- package/dist/plugins/pg-session-store.d.ts +10 -0
- package/dist/plugins/pg-session-store.js +10 -0
- package/dist/plugins/pg-tool-result-store.d.ts +7 -0
- package/dist/plugins/pg-tool-result-store.js +7 -0
- package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-journal-store.js +7 -0
- package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-run-store.js +7 -0
- package/dist/plugins/posix-shell-fs.d.ts +3 -0
- package/dist/plugins/posix-shell-fs.js +31 -1
- package/dist/plugins/remote-env-adb.d.ts +25 -0
- package/dist/plugins/remote-env-adb.js +95 -12
- package/dist/plugins/remote-env-e2b.d.ts +146 -0
- package/dist/plugins/remote-env-e2b.js +371 -40
- package/dist/plugins/remote-env-file-error.d.ts +34 -0
- package/dist/plugins/remote-env-file-error.js +37 -0
- package/dist/plugins/remote-env-host.d.ts +151 -0
- package/dist/plugins/remote-env-host.js +468 -70
- package/dist/plugins/remote-env-k8s.d.ts +135 -0
- package/dist/plugins/remote-env-k8s.js +318 -32
- package/dist/plugins/remote-env-local-docker.d.ts +65 -0
- package/dist/plugins/remote-env-local-docker.js +149 -24
- package/dist/plugins/remote-env-ssh.d.ts +35 -0
- package/dist/plugins/remote-env-ssh.js +117 -20
- package/dist/plugins/remote-scratchpad.d.ts +38 -0
- package/dist/plugins/remote-scratchpad.js +32 -2
- package/dist/plugins/remote-shell.d.ts +22 -0
- package/dist/plugins/remote-shell.js +27 -1
- package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
- package/dist/plugins/resume-anchor-store-sql.js +11 -0
- package/dist/plugins/roster-store-sql.d.ts +27 -0
- package/dist/plugins/roster-store-sql.js +54 -0
- package/dist/plugins/run-store-sql.d.ts +194 -0
- package/dist/plugins/run-store-sql.js +210 -9
- package/dist/plugins/s3-presign.d.ts +20 -0
- package/dist/plugins/s3-presign.js +49 -2
- package/dist/plugins/scheduler-support.d.ts +12 -0
- package/dist/plugins/scheduler-support.js +75 -3
- package/dist/plugins/send-file-ledger.d.ts +15 -0
- package/dist/plugins/send-file-ledger.js +53 -4
- package/dist/plugins/send-user-file.d.ts +77 -0
- package/dist/plugins/send-user-file.js +92 -4
- package/dist/plugins/session-policy-store-sql.d.ts +15 -0
- package/dist/plugins/session-policy-store-sql.js +75 -3
- package/dist/plugins/session-store.d.ts +45 -0
- package/dist/plugins/session-store.js +51 -0
- package/dist/plugins/sql-driver.d.ts +77 -0
- package/dist/plugins/sql-driver.js +4 -0
- package/dist/plugins/sql-escape.d.ts +8 -0
- package/dist/plugins/sql-escape.js +8 -0
- package/dist/plugins/sql-row-helpers.d.ts +25 -0
- package/dist/plugins/sql-row-helpers.js +25 -0
- package/dist/plugins/store-backend.d.ts +103 -0
- package/dist/plugins/store-backend.js +120 -27
- package/dist/plugins/store-contracts.d.ts +62 -0
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +60 -0
- package/dist/plugins/task-attachment-store.js +49 -3
- package/dist/plugins/task-list-store-sql.d.ts +25 -0
- package/dist/plugins/task-list-store-sql.js +21 -0
- package/dist/plugins/tidb-approval-store.d.ts +6 -0
- package/dist/plugins/tidb-approval-store.js +6 -0
- package/dist/plugins/tidb-breaker-state.d.ts +5 -0
- package/dist/plugins/tidb-breaker-state.js +5 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
- package/dist/plugins/tidb-checkpoint-store.js +7 -0
- package/dist/plugins/tidb-cost-quota.d.ts +6 -0
- package/dist/plugins/tidb-cost-quota.js +1 -0
- package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/tidb-file-snapshot-store.js +6 -0
- package/dist/plugins/tidb-image-bake.d.ts +8 -0
- package/dist/plugins/tidb-image-bake.js +8 -0
- package/dist/plugins/tidb-image-index.d.ts +8 -0
- package/dist/plugins/tidb-image-index.js +8 -0
- package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
- package/dist/plugins/tidb-outcome-ledger.js +10 -0
- package/dist/plugins/tidb-pool.d.ts +56 -0
- package/dist/plugins/tidb-pool.js +181 -1
- package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
- package/dist/plugins/tidb-rate-limiter.js +5 -2
- package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/tidb-resume-anchor-store.js +5 -0
- package/dist/plugins/tidb-run-store.d.ts +7 -0
- package/dist/plugins/tidb-run-store.js +7 -0
- package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
- package/dist/plugins/tidb-session-policy-store.js +5 -0
- package/dist/plugins/tidb-session-storage.d.ts +43 -0
- package/dist/plugins/tidb-session-storage.js +62 -3
- package/dist/plugins/tidb-session-store.d.ts +142 -0
- package/dist/plugins/tidb-session-store.js +264 -16
- package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
- package/dist/plugins/tidb-tool-result-store.js +7 -0
- package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/tidb-workflow-journal-store.js +7 -0
- package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
- package/dist/plugins/tidb-workflow-run-store.js +8 -0
- package/dist/plugins/tool-result-store-sql.d.ts +58 -0
- package/dist/plugins/tool-result-store-sql.js +38 -2
- package/dist/plugins/web-search.d.ts +35 -0
- package/dist/plugins/web-search.js +45 -9
- package/dist/plugins/workflow-journal-limits.d.ts +10 -0
- package/dist/plugins/workflow-journal-limits.js +10 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
- package/dist/plugins/workflow-journal-store-sql.js +59 -3
- package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
- package/dist/plugins/workflow-run-store-sql.js +84 -6
- package/dist/plugins/worktree-isolation.d.ts +45 -0
- package/dist/plugins/worktree-isolation.js +105 -6
- package/dist/plugins/write-behind-counter.d.ts +56 -1
- package/dist/plugins/write-behind-counter.js +53 -14
- package/dist/principal-jwt.d.ts +24 -0
- package/dist/principal-jwt.js +60 -5
- package/dist/project-memory.d.ts +22 -0
- package/dist/project-memory.js +144 -15
- package/dist/prompts-domain-validate.d.ts +28 -0
- package/dist/prompts-domain-validate.js +42 -1
- package/dist/question.d.ts +33 -0
- package/dist/question.js +69 -2
- package/dist/resource-suspend.d.ts +21 -0
- package/dist/resource-suspend.js +16 -0
- package/dist/router/route-orchestration.d.ts +78 -0
- package/dist/router/route-orchestration.js +77 -0
- package/dist/run-local.d.ts +32 -0
- package/dist/run-local.js +172 -8
- package/dist/runs.d.ts +189 -4
- package/dist/runs.js +465 -24
- package/dist/runtime-caps-resolver.d.ts +106 -0
- package/dist/runtime-caps-resolver.js +89 -4
- package/dist/runtime-governance.d.ts +84 -0
- package/dist/runtime-governance.js +127 -1
- package/dist/sandbox-pkg-source.d.ts +23 -0
- package/dist/sandbox-pkg-source.js +37 -0
- package/dist/sealed-key.d.ts +54 -0
- package/dist/sealed-key.js +68 -3
- package/dist/security.d.ts +108 -0
- package/dist/security.js +145 -6
- package/dist/sema-registry.d.ts +21 -0
- package/dist/sema-registry.js +21 -0
- package/dist/session-leaf-bus.d.ts +31 -0
- package/dist/session-leaf-bus.js +32 -0
- package/dist/session-sync-content.d.ts +46 -0
- package/dist/session-sync-content.js +47 -1
- package/dist/session-sync-kernel.d.ts +89 -0
- package/dist/session-sync-kernel.js +48 -3
- package/dist/session-sync.d.ts +125 -0
- package/dist/session-sync.js +181 -8
- package/dist/session-titler.d.ts +26 -0
- package/dist/session-titler.js +41 -7
- package/dist/session-watch.d.ts +53 -1
- package/dist/session-watch.js +73 -9
- package/dist/sighup-idle.d.ts +30 -0
- package/dist/sighup-idle.js +12 -1
- package/dist/spec-fields.d.ts +91 -3
- package/dist/spec-fields.js +116 -4
- package/dist/task-cwd.d.ts +50 -0
- package/dist/task-cwd.js +68 -1
- package/dist/task-mcp.d.ts +47 -0
- package/dist/task-mcp.js +29 -0
- package/dist/task-settings.d.ts +175 -0
- package/dist/task-settings.js +226 -11
- package/dist/task-workflow.d.ts +67 -0
- package/dist/task-workflow.js +75 -8
- package/dist/tool-approval.d.ts +91 -0
- package/dist/tool-approval.js +215 -0
- package/dist/trace/artifacts.d.ts +9 -0
- package/dist/trace/artifacts.js +30 -5
- package/dist/trace/core-keyset-guard.d.ts +18 -0
- package/dist/trace/ledger-sink.d.ts +43 -0
- package/dist/trace/ledger-sink.js +64 -5
- package/dist/trace/project.d.ts +133 -0
- package/dist/trace/project.js +189 -2
- package/dist/trace/redact.d.ts +19 -0
- package/dist/trace/redact.js +66 -9
- package/dist/usage-analytics.d.ts +26 -0
- package/dist/usage-analytics.js +26 -3
- package/package.json +1 -1
|
@@ -7,7 +7,17 @@ const DEFAULTS = {
|
|
|
7
7
|
concurrency: 12,
|
|
8
8
|
maxHeapMb: 128,
|
|
9
9
|
};
|
|
10
|
+
/** Own keys that carry a prototype-tampering gadget across the JSON membrane. */
|
|
10
11
|
const PROTO_TAMPER_KEYS = ["__proto__", "constructor", "prototype"];
|
|
12
|
+
/**
|
|
13
|
+
* Recursively delete own `__proto__` / `constructor` / `prototype` keys from JSON-parsed data. Closes the
|
|
14
|
+
* tampering gadget a script passes across the membrane: `Reflect.defineProperty(o, "__proto__",
|
|
15
|
+
* {enumerable:true, value:{...}})` (Reflect is reachable; `codeGeneration:{strings:false}` does NOT block it)
|
|
16
|
+
* makes `__proto__` an OWN ENUMERABLE key, which in-context `JSON.stringify` emits and host `JSON.parse`
|
|
17
|
+
* re-materializes as a real own data property — feeding a host fn (or the returned result) a crafted
|
|
18
|
+
* prototype a downstream deep-merge / key-assignment sink splats onto the HOST realm's `Object.prototype`
|
|
19
|
+
* (process-wide, cross-tenant). Caught by the s8-sandbox-robustness marshal-bridge lens (core's battery + the
|
|
20
|
+
* result-path probes both miss it). Apply ONLY to JSON.parse output (plain, acyclic, getter-free). */
|
|
11
21
|
function scrubProtoDeep(value) {
|
|
12
22
|
if (value === null || typeof value !== "object")
|
|
13
23
|
return;
|
|
@@ -24,8 +34,22 @@ function scrubProtoDeep(value) {
|
|
|
24
34
|
scrubProtoDeep(value[key]);
|
|
25
35
|
}
|
|
26
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* [1818]§二邻位 — script-realm rejection guard. The vm context shares the HOST isolate's unhandledRejection
|
|
39
|
+
* machinery, and Node's default posture is CRASH: an untrusted script's un-awaited rejection (a bare
|
|
40
|
+
* `(async () => { throw ... })()`, an un-awaited `agent()`/`phase()` whose body fails) would take the whole
|
|
41
|
+
* server process down — a resource/availability hole, not a data-boundary one (clay's live incident hit the
|
|
42
|
+
* sibling shape that HANGS instead of rejecting, which is the only reason it didn't crash). There is no
|
|
43
|
+
* per-context hook in Node, so the guard is a process listener that tells realms apart by intrinsic:
|
|
44
|
+
* a promise minted INSIDE the context fails `instanceof` against the HOST `Promise`. Context-realm rejections
|
|
45
|
+
* are contained (logged, never fatal); host-realm rejections keep Node's fail-loud default (rethrown as an
|
|
46
|
+
* uncaughtException) UNLESS the deployment installed its own handler — then we defer to it entirely.
|
|
47
|
+
* Installed lazily on the first hardened run: only processes that actually execute untrusted scripts change
|
|
48
|
+
* their global posture, and only then is the hazard live. Idempotent.
|
|
49
|
+
*/
|
|
27
50
|
let rejectionGuardInstalled = false;
|
|
28
51
|
let onScriptRealmRejection;
|
|
52
|
+
/** Optional observability hook for contained script-realm rejections (e.g. wire to the deployment logger). */
|
|
29
53
|
export function setScriptRealmRejectionObserver(observer) {
|
|
30
54
|
onScriptRealmRejection = observer;
|
|
31
55
|
}
|
|
@@ -35,24 +59,32 @@ export function installScriptRealmRejectionGuard() {
|
|
|
35
59
|
rejectionGuardInstalled = true;
|
|
36
60
|
process.on("unhandledRejection", (reason, promise) => {
|
|
37
61
|
if (!(promise instanceof Promise)) {
|
|
62
|
+
// cross-realm ⇒ minted in a vm context ⇒ an un-awaited rejection inside an untrusted script. Contain.
|
|
38
63
|
const summary = reason instanceof Error ? `${reason.name}: ${reason.message}` : String(reason);
|
|
39
64
|
try {
|
|
40
65
|
(onScriptRealmRejection ?? ((s) => console.warn(`hardened-vm: un-awaited script rejection contained: ${s}`)))(summary);
|
|
41
66
|
}
|
|
42
67
|
catch {
|
|
68
|
+
/* the guard must never become a crash source itself */
|
|
43
69
|
}
|
|
44
70
|
return;
|
|
45
71
|
}
|
|
72
|
+
// Host-realm: preserve fail-loud unless another handler exists (a deployment that installed one chose
|
|
73
|
+
// its own posture — a rethrow here would override that choice).
|
|
46
74
|
if (process.listenerCount("unhandledRejection") > 1)
|
|
47
75
|
return;
|
|
48
76
|
throw reason;
|
|
49
77
|
});
|
|
50
78
|
}
|
|
79
|
+
/** Host-side parse that strips the tampering gadget from data crossing INTO a host fn. */
|
|
51
80
|
function sterilizingParse(json) {
|
|
52
81
|
const parsed = JSON.parse(json);
|
|
53
82
|
scrubProtoDeep(parsed);
|
|
54
83
|
return parsed;
|
|
55
84
|
}
|
|
85
|
+
/** Marshal a (possibly live cross-realm) script value to PLAIN, sterilized host data: JSON round-trips it
|
|
86
|
+
* (firing in-context getters/toJSON harmlessly in-context → plain data; drops functions; no live context
|
|
87
|
+
* object with traps reaches the host) then strips the tampering gadget. Throws on a non-serializable result. */
|
|
56
88
|
function marshalOut(value) {
|
|
57
89
|
let json;
|
|
58
90
|
try {
|
|
@@ -63,14 +95,21 @@ function marshalOut(value) {
|
|
|
63
95
|
}
|
|
64
96
|
return sterilizingParse(json === undefined ? "null" : json);
|
|
65
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The shared hardened-vm core. Builds the membrane, lets `buildGlobals` construct the context-local globals,
|
|
100
|
+
* runs the untrusted `body` as an async function under the caps, and returns the MARSHALLED (plain, sterilized)
|
|
101
|
+
* result. Both public surfaces delegate here.
|
|
102
|
+
*/
|
|
66
103
|
async function runHardened(opts) {
|
|
67
104
|
const cfg = { ...DEFAULTS, ...opts.limits };
|
|
68
105
|
const { signal } = opts;
|
|
69
106
|
if (signal?.aborted)
|
|
70
107
|
throw new WorkflowScriptError("hardened-vm aborted before start");
|
|
71
108
|
installScriptRealmRejectionGuard();
|
|
109
|
+
// 1. hardened context
|
|
72
110
|
const sandbox = { __proto__: null };
|
|
73
111
|
const ctx = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
112
|
+
// 2. determinism guard (resume-safety; matches CC): nondeterministic clocks/RNG throw in-context.
|
|
74
113
|
vm.runInContext(`(() => {
|
|
75
114
|
const block = (l) => { throw new Error(l + " is unavailable in workflow scripts (nondeterministic — breaks resume). Pass timestamps via args; vary by index for randomness."); };
|
|
76
115
|
Date.now = () => block("Date.now()");
|
|
@@ -87,17 +126,30 @@ async function runHardened(opts) {
|
|
|
87
126
|
globalThis[n] = () => noTimer(n + "()");
|
|
88
127
|
}
|
|
89
128
|
})()`, ctx);
|
|
129
|
+
// 3. membrane helpers (factory bodies COMPILED IN-CONTEXT → context-rooted prototypes)
|
|
90
130
|
const ctxJsonParse = vm.runInContext("(s => JSON.parse(s))", ctx);
|
|
91
131
|
const dataIn = (v) => {
|
|
132
|
+
// sterilize the gadget host-side BEFORE it re-materializes in-context (a __proto__ own key would otherwise
|
|
133
|
+
// pollute the CONTEXT's Object.prototype — sandboxed, but cheap to close).
|
|
92
134
|
const clean = JSON.parse(JSON.stringify(v === undefined ? null : v));
|
|
93
135
|
scrubProtoDeep(clean);
|
|
94
136
|
return ctxJsonParse(JSON.stringify(clean));
|
|
95
137
|
};
|
|
138
|
+
// a {ok,v} | {ok:false,err,name} JSON round-trip; the IN-CONTEXT wrapper throws a CONTEXT-LOCAL Error on
|
|
139
|
+
// failure so a host error never reaches the script (beyond-battery fix).
|
|
96
140
|
const mkBridge = vm.runInContext(`(hostCall => async function (...a) {
|
|
97
141
|
const r = JSON.parse(await hostCall(JSON.stringify(a)));
|
|
98
142
|
if (r.ok) return r.v;
|
|
99
143
|
const e = new Error(r.err); e.name = r.name || "Error"; throw e;
|
|
100
144
|
})`, ctx);
|
|
145
|
+
// [1818]§二 — quiescence deadlock detector. Inside the membrane the ONLY way a suspended script can ever be
|
|
146
|
+
// resumed is a bridged host call settling: timers are blocked, there is no I/O, and every other await is a
|
|
147
|
+
// microtask chain that drains on its own. So "zero bridged calls in flight + the microtask AND macrotask
|
|
148
|
+
// queues have both drained + scriptPromise still pending" is not a heuristic — it PROVES the script can never
|
|
149
|
+
// progress again (clay's live shape: an un-awaited `const r = phase(...)` whose body returns `r` → a promise
|
|
150
|
+
// cycle that neither rejects nor resolves; pre-fix it burned the full totalTimeoutMs blind and then blamed
|
|
151
|
+
// the timeout). setImmediate (a macrotask) is the drain fence: any continuation the settling host call woke
|
|
152
|
+
// runs first, and a continuation that starts a new bridged call increments the counter synchronously.
|
|
101
153
|
let inFlightHostCalls = 0;
|
|
102
154
|
let scriptSettled = false;
|
|
103
155
|
let fireDeadlock;
|
|
@@ -113,6 +165,8 @@ async function runHardened(opts) {
|
|
|
113
165
|
const bridge = (hostFn) => mkBridge(async (argsJson) => {
|
|
114
166
|
inFlightHostCalls++;
|
|
115
167
|
try {
|
|
168
|
+
// sterilizingParse (not bare JSON.parse): strip a slipped-in __proto__/constructor/prototype gadget
|
|
169
|
+
// BEFORE the args reach the host fn — the dangerous (script→host) direction.
|
|
116
170
|
const out = await hostFn(...sterilizingParse(argsJson));
|
|
117
171
|
return JSON.stringify({ ok: true, v: out ?? null });
|
|
118
172
|
}
|
|
@@ -129,6 +183,7 @@ async function runHardened(opts) {
|
|
|
129
183
|
}
|
|
130
184
|
});
|
|
131
185
|
const membrane = { bridge, dataIn, ctxEval: (src) => vm.runInContext(src, ctx) };
|
|
186
|
+
// 4. expose the surface's context-local globals + run the body under the sync vm timeout
|
|
132
187
|
for (const [name, value] of Object.entries(opts.buildGlobals(membrane)))
|
|
133
188
|
sandbox[name] = value;
|
|
134
189
|
let invocation;
|
|
@@ -138,9 +193,14 @@ async function runHardened(opts) {
|
|
|
138
193
|
catch (err) {
|
|
139
194
|
throw new WorkflowScriptError(`script failed to compile: ${err instanceof Error ? err.message : String(err)}`);
|
|
140
195
|
}
|
|
196
|
+
// `timeout` bounds the SYNCHRONOUS run up to the first await (a sync infinite loop is killed here); an async
|
|
197
|
+
// loop is bounded by the wall-clock race below.
|
|
141
198
|
const scriptPromise = invocation.runInContext(ctx, { timeout: cfg.syncTimeoutMs });
|
|
142
199
|
scriptPromise.then(() => (scriptSettled = true), () => (scriptSettled = true));
|
|
200
|
+
// Initial probe: a script that suspends without EVER touching the bridge (`await new Promise(() => {})`)
|
|
201
|
+
// never decrements the counter, so the settle-side probe alone would miss it.
|
|
143
202
|
probeQuiescence();
|
|
203
|
+
// 5. wall-clock + external-abort + quiescence-deadlock race
|
|
144
204
|
let timer;
|
|
145
205
|
let onAbort;
|
|
146
206
|
try {
|
|
@@ -153,14 +213,24 @@ async function runHardened(opts) {
|
|
|
153
213
|
onAbort = () => reject(new WorkflowScriptError("hardened-vm aborted"));
|
|
154
214
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
155
215
|
}
|
|
216
|
+
// Diagnosis-first message: it reaches the model via failureSummary (bounded), so the self-correction
|
|
217
|
+
// hint has to be up front.
|
|
156
218
|
fireDeadlock = () => reject(new WorkflowScriptError("script deadlocked: no host call (agent/phase) in flight and nothing can resume the script — usually an un-awaited promise cycle, e.g. `const r = phase(...)` without await whose body reads `r`."));
|
|
157
219
|
}),
|
|
158
220
|
]);
|
|
221
|
+
// hand the host PLAIN sterilized data, never a live cross-realm object (getter/toJSON/then traps) and
|
|
222
|
+
// never a slipped-in __proto__ (return-path tampering).
|
|
159
223
|
return marshalOut(result);
|
|
160
224
|
}
|
|
161
225
|
catch (err) {
|
|
226
|
+
// Live dogfood catch 2026-07-03 ( `setTimeout` blew up as a bare "workflow failed"): a RUNTIME script-realm
|
|
227
|
+
// rejection (ReferenceError, an uncaught script throw, a bridged agent() error the script didn't catch) used
|
|
228
|
+
// to propagate UN-typed — `failureSummary` only surfaces the message for `workflow.*`-coded errors, so the model/
|
|
229
|
+
// operator got zero diagnosis. Wrap it as `workflow.script_error`; core's failureSummary already bounds (200
|
|
230
|
+
// chars) + `redactHostLeaks`-redacts the message before it reaches the notification, so a bridged host error
|
|
231
|
+
// message is safe to carry here.
|
|
162
232
|
if (err instanceof WorkflowScriptError)
|
|
163
|
-
throw err;
|
|
233
|
+
throw err; // compile/timeout/abort/marshal — already typed
|
|
164
234
|
throw new WorkflowScriptError(`script failed at runtime: ${err instanceof Error ? err.message : String(err)}`);
|
|
165
235
|
}
|
|
166
236
|
finally {
|
|
@@ -170,6 +240,23 @@ async function runHardened(opts) {
|
|
|
170
240
|
signal.removeEventListener("abort", onAbort);
|
|
171
241
|
}
|
|
172
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* phases=[] ROOT CAUSE + fix — the SCOPED-PHASE GATE. The old in-context `phase` was pure sugar
|
|
245
|
+
* (log a "◆ phase" line, run the body) that NEVER called the host `primitives.phase`, so core recorded ZERO
|
|
246
|
+
* phase state: terminal rows had `phases: []` and agents carried no `phase` key (exactly the storage-row
|
|
247
|
+
* autopsy — agents fine, phase accounting gone). The membrane's JSON bridge cannot carry the script's BODY
|
|
248
|
+
* function across, so the bridge is split into three JSON-safe host calls, and the body stays IN-CONTEXT:
|
|
249
|
+
* - `mark(title)` → `primitives.phase(title, undefined)` (core's bare CC-marker path);
|
|
250
|
+
* - `enter(title)` → opens core's scoped phase with a DEFERRED body (`await gate`), returns a numeric token.
|
|
251
|
+
* core pushes the phase record + sets `currentPhase` SYNCHRONOUSLY before its first await, so by the time
|
|
252
|
+
* `enter` returns, `agent()` calls made inside the VM body land with the right phase label;
|
|
253
|
+
* - `exit(token, ok, err?)` → releases the gate (ok → core closes the phase `completed`; !ok → the deferred
|
|
254
|
+
* body throws and core closes it `failed`), then awaits core's settle so the close is persisted before the
|
|
255
|
+
* VM continues.
|
|
256
|
+
* A finalized-run `phase()` (core throws) surfaces on `enter` via the settled-race; an unknown/double `exit`
|
|
257
|
+
* is idempotent. The VM wrapper rethrows the body's own error — the gate's synthetic error never reaches the
|
|
258
|
+
* script.
|
|
259
|
+
*/
|
|
173
260
|
export function scopedPhaseGate(phase) {
|
|
174
261
|
let seq = 0;
|
|
175
262
|
const open = new Map();
|
|
@@ -182,7 +269,9 @@ export function scopedPhaseGate(phase) {
|
|
|
182
269
|
if (!r.ok)
|
|
183
270
|
throw new Error(r.err ?? "phase body failed");
|
|
184
271
|
}));
|
|
185
|
-
settled.catch(() => undefined);
|
|
272
|
+
settled.catch(() => undefined); // the VM path rethrows the body's own error — never an unhandled rejection here
|
|
273
|
+
// Surface an IMMEDIATE rejection (core's finalized guard rejects before its first await settles) while a
|
|
274
|
+
// healthy still-pending phase resolves the race via the resolved sentinel.
|
|
186
275
|
await Promise.race([settled, Promise.resolve()]);
|
|
187
276
|
const token = ++seq;
|
|
188
277
|
open.set(token, { release, settled });
|
|
@@ -191,13 +280,19 @@ export function scopedPhaseGate(phase) {
|
|
|
191
280
|
async exit(token, ok, err) {
|
|
192
281
|
const o = open.get(token);
|
|
193
282
|
if (!o)
|
|
194
|
-
return;
|
|
283
|
+
return; // unknown/double exit — idempotent
|
|
195
284
|
open.delete(token);
|
|
196
285
|
o.release({ ok, ...(err !== undefined ? { err } : {}) });
|
|
197
|
-
await o.settled.catch(() => undefined);
|
|
286
|
+
await o.settled.catch(() => undefined); // core closed + persisted the phase before the VM continues
|
|
198
287
|
},
|
|
199
288
|
};
|
|
200
289
|
}
|
|
290
|
+
/** Build the orchestration context-local globals (agent/parallel/pipeline/phase/log/budget/args) from the
|
|
291
|
+
* host {@link WorkflowPrimitives}. agent/log/phase are host-bridged (phase via the three-verb
|
|
292
|
+
* {@link scopedPhaseGate} — the body function itself never crosses the membrane); parallel/pipeline are
|
|
293
|
+
* in-context sugar; budget is a frozen null-proto object over host accessors.
|
|
294
|
+
* Exported so the worker-isolated runner (hardened-vm-worker.ts) reuses the EXACT same surface with
|
|
295
|
+
* IPC-backed primitives — one definition of the orchestration globals, no drift. */
|
|
201
296
|
export function orchestrationGlobals(primitives, scriptArgs, limits = {}) {
|
|
202
297
|
const cfg = { ...DEFAULTS, ...limits };
|
|
203
298
|
return (m) => {
|
|
@@ -222,6 +317,8 @@ export function orchestrationGlobals(primitives, scriptArgs, limits = {}) {
|
|
|
222
317
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
223
318
|
return out;
|
|
224
319
|
})`)(cfg.concurrency);
|
|
320
|
+
// Phase-gate fix: REAL phase bridging via the three-verb gate (see scopedPhaseGate) — the old in-context sugar
|
|
321
|
+
// never told core a phase existed (phases:[] / label-less agents in every hardened-runner deployment).
|
|
225
322
|
const gate = scopedPhaseGate((t, b) => primitives.phase(t, b));
|
|
226
323
|
const phaseMark = m.bridge(async (title) => void (await primitives.phase(String(title), undefined)));
|
|
227
324
|
const phaseEnter = m.bridge(async (title) => gate.enter(String(title)));
|
|
@@ -235,6 +332,9 @@ export function orchestrationGlobals(primitives, scriptArgs, limits = {}) {
|
|
|
235
332
|
return { agent, parallel, pipeline, phase, log, budget, args: m.dataIn(scriptArgs) };
|
|
236
333
|
};
|
|
237
334
|
}
|
|
335
|
+
/** Build the hardened-vm S8 `WorkflowScriptRunner` (TOC+TOB workflow orchestration). `safeForUntrustedScripts`
|
|
336
|
+
* is `true` — it has passed the conformance battery + robustness suite; the S8 gate mounts `run_workflow` only for
|
|
337
|
+
* a runner that asserts this. */
|
|
238
338
|
export function createHardenedVmRunner(limits = {}) {
|
|
239
339
|
const cfg = { ...DEFAULTS, ...limits };
|
|
240
340
|
return {
|
|
@@ -247,10 +347,25 @@ export function createHardenedVmRunner(limits = {}) {
|
|
|
247
347
|
...(signal ? { signal } : {}),
|
|
248
348
|
buildGlobals: orchestrationGlobals(primitives, scriptArgs, cfg),
|
|
249
349
|
});
|
|
350
|
+
// `meta` is core-parsed (AST, not eval) but its values are LLM-controlled — sterilize it too.
|
|
250
351
|
return { result, meta: marshalOut(meta) };
|
|
251
352
|
},
|
|
252
353
|
};
|
|
253
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* General untrusted-code exec on the SAME hardened membrane — the TOC ephemeral execution sandbox. Runs
|
|
357
|
+
* `code` (an async function body that may `return`) with ONLY the safe built-ins plus whatever host
|
|
358
|
+
* capabilities the caller bridges via `bindings` (each MUST go through `m.bridge` / `m.dataIn` — never a raw
|
|
359
|
+
* host reference). Returns the marshalled (plain, sterilized) result. With no `bindings`, the code is pure
|
|
360
|
+
* computation (no host reach at all).
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* const out = await runInHardenedVm({
|
|
364
|
+
* code: "return await fetchJson(args.url)",
|
|
365
|
+
* bindings: (m) => ({ fetchJson: m.bridge((u) => safeFetch(String(u))), args: m.dataIn({ url }) }),
|
|
366
|
+
* limits: { totalTimeoutMs: 5000 },
|
|
367
|
+
* });
|
|
368
|
+
*/
|
|
254
369
|
export async function runInHardenedVm(opts) {
|
|
255
370
|
return runHardened({
|
|
256
371
|
body: opts.code,
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { type WorkflowScriptRunner } from "@sema-agent/core";
|
|
2
2
|
import { type HardenedVmLimits } from "./hardened-vm-runner.js";
|
|
3
|
+
/**
|
|
4
|
+
* Worker-isolated S8 hard runner (design/98 v2). Runs the hardened-vm membrane inside a `worker_thread`, adding
|
|
5
|
+
* RESOURCE isolation on top of the in-process {@link import("./hardened-vm-runner.js").createHardenedVmRunner}'s
|
|
6
|
+
* BOUNDARY isolation:
|
|
7
|
+
* - HEAP cap (`resourceLimits.maxOldGenerationSizeMb`): a runaway-allocation script OOMs the WORKER (an `error`
|
|
8
|
+
* event), never the shared main process — closes the in-process resource-exhaustion gap.
|
|
9
|
+
* - `terminate()` on wall-clock timeout / abort: kills a MICROTASK-starvation loop (`while(true){await null}`)
|
|
10
|
+
* the in-process wall-clock race cannot pre-empt (it starves its own event loop).
|
|
11
|
+
* - credential isolation: the worker gets only the script + args; `agent`/`log` IPC to the main thread (which
|
|
12
|
+
* holds the real Runner/DB/keys); a (validated-impossible) boundary crossing lands in a credential-free isolate.
|
|
13
|
+
*
|
|
14
|
+
* `safeForUntrustedScripts: true` — the membrane it runs is the conformance-validated + robustness-tested one. Use
|
|
15
|
+
* this (over the in-process runner) for genuinely-untrusted multi-tenant scripts where resource isolation matters.
|
|
16
|
+
*/
|
|
3
17
|
export declare function createWorkerHardenedVmRunner(limits?: HardenedVmLimits): WorkflowScriptRunner;
|
|
4
18
|
//# sourceMappingURL=hardened-vm-worker-runner.d.ts.map
|
|
@@ -2,9 +2,26 @@ import { Worker } from "node:worker_threads";
|
|
|
2
2
|
import { WorkflowScriptError } from "@sema-agent/core";
|
|
3
3
|
import { scopedPhaseGate } from "./hardened-vm-runner.js";
|
|
4
4
|
const DEFAULTS = { totalTimeoutMs: 600_000, syncTimeoutMs: 5_000, maxAgents: 1000, concurrency: 12, maxHeapMb: 128 };
|
|
5
|
+
// Resolve the worker bootstrap next to this module. In a tsc build it's the sibling `.js`; under vitest/tsx
|
|
6
|
+
// (this module's URL ends in `.ts`) point at the `.ts` and register tsx as the worker's loader so the worker
|
|
7
|
+
// can import TypeScript. Prod (node dist/) never needs tsx.
|
|
5
8
|
const IS_TS = import.meta.url.endsWith(".ts");
|
|
6
9
|
const WORKER_URL = new URL(IS_TS ? "./hardened-vm-worker.ts" : "./hardened-vm-worker.js", import.meta.url);
|
|
7
10
|
const WORKER_EXEC_ARGV = IS_TS ? ["--import", "tsx/esm"] : undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Worker-isolated S8 hard runner (design/98 v2). Runs the hardened-vm membrane inside a `worker_thread`, adding
|
|
13
|
+
* RESOURCE isolation on top of the in-process {@link import("./hardened-vm-runner.js").createHardenedVmRunner}'s
|
|
14
|
+
* BOUNDARY isolation:
|
|
15
|
+
* - HEAP cap (`resourceLimits.maxOldGenerationSizeMb`): a runaway-allocation script OOMs the WORKER (an `error`
|
|
16
|
+
* event), never the shared main process — closes the in-process resource-exhaustion gap.
|
|
17
|
+
* - `terminate()` on wall-clock timeout / abort: kills a MICROTASK-starvation loop (`while(true){await null}`)
|
|
18
|
+
* the in-process wall-clock race cannot pre-empt (it starves its own event loop).
|
|
19
|
+
* - credential isolation: the worker gets only the script + args; `agent`/`log` IPC to the main thread (which
|
|
20
|
+
* holds the real Runner/DB/keys); a (validated-impossible) boundary crossing lands in a credential-free isolate.
|
|
21
|
+
*
|
|
22
|
+
* `safeForUntrustedScripts: true` — the membrane it runs is the conformance-validated + robustness-tested one. Use
|
|
23
|
+
* this (over the in-process runner) for genuinely-untrusted multi-tenant scripts where resource isolation matters.
|
|
24
|
+
*/
|
|
8
25
|
export function createWorkerHardenedVmRunner(limits = {}) {
|
|
9
26
|
const cfg = { ...DEFAULTS, ...limits };
|
|
10
27
|
return {
|
|
@@ -14,6 +31,8 @@ export function createWorkerHardenedVmRunner(limits = {}) {
|
|
|
14
31
|
return Promise.reject(new WorkflowScriptError("worker hardened-vm aborted before start"));
|
|
15
32
|
return new Promise((resolve, reject) => {
|
|
16
33
|
const worker = new Worker(WORKER_URL, {
|
|
34
|
+
// budgetSpent seeds the worker's cache with the CURRENT spend (core 1.238 wiring battery caught the
|
|
35
|
+
// cold-start gap: a cache seeded 0 made remaining() read total until the first agent reply refreshed it).
|
|
17
36
|
workerData: { scriptSource, scriptArgs, limits: cfg, budgetTotal: primitives.budget.total, budgetSpent: primitives.budget.spent() },
|
|
18
37
|
resourceLimits: { maxOldGenerationSizeMb: cfg.maxHeapMb },
|
|
19
38
|
...(WORKER_EXEC_ARGV ? { execArgv: WORKER_EXEC_ARGV } : {}),
|
|
@@ -26,7 +45,7 @@ export function createWorkerHardenedVmRunner(limits = {}) {
|
|
|
26
45
|
clearTimeout(timer);
|
|
27
46
|
if (signal)
|
|
28
47
|
signal.removeEventListener("abort", onAbort);
|
|
29
|
-
void worker.terminate();
|
|
48
|
+
void worker.terminate(); // hard-stops a starved/runaway worker; no-op if it already exited
|
|
30
49
|
};
|
|
31
50
|
const finish = (settle) => {
|
|
32
51
|
if (settled)
|
|
@@ -35,10 +54,15 @@ export function createWorkerHardenedVmRunner(limits = {}) {
|
|
|
35
54
|
cleanup();
|
|
36
55
|
settle();
|
|
37
56
|
};
|
|
57
|
+
// Wall-clock backstop (terminate). Grace over the in-worker totalTimeoutMs so the worker's CLEAN error
|
|
58
|
+
// (stuck host primitive) wins normally; terminate is the backstop for microtask-starvation.
|
|
38
59
|
timer = setTimeout(() => finish(() => reject(new WorkflowScriptError(`worker hardened-vm exceeded totalTimeoutMs (${cfg.totalTimeoutMs})`))), cfg.totalTimeoutMs + 1500);
|
|
39
60
|
timer.unref?.();
|
|
40
61
|
if (signal)
|
|
41
62
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
63
|
+
// Phase-gate fix: the worker's phase IPC verbs land on the REAL primitives.phase via the same three-verb gate
|
|
64
|
+
// the in-process runner uses (the body function stays in the worker; only mark/enter/exit cross). Replies
|
|
65
|
+
// reuse the worker's id-keyed "agentResult" resolver (same shape — no second pending map in the worker).
|
|
42
66
|
const phaseGate = scopedPhaseGate((t, b) => primitives.phase(t, b));
|
|
43
67
|
const reply = (id, p) => void p.then((value) => worker.postMessage({ type: "agentResult", id, ok: true, value: value ?? null, budgetSpent: primitives.budget.spent() }), (e) => worker.postMessage({ type: "agentResult", id, ok: false, error: e instanceof Error ? e.message : String(e), name: e instanceof Error ? e.name : "Error", budgetSpent: primitives.budget.spent() }));
|
|
44
68
|
worker.on("message", (msg) => {
|
|
@@ -67,6 +91,7 @@ export function createWorkerHardenedVmRunner(limits = {}) {
|
|
|
67
91
|
finish(() => reject(new WorkflowScriptError(msg.message ?? "worker hardened-vm script failed")));
|
|
68
92
|
}
|
|
69
93
|
});
|
|
94
|
+
// An `error` event = an UNCAUGHT worker exception, most often the heap-cap OOM ("Reached heap limit").
|
|
70
95
|
worker.on("error", (err) => finish(() => reject(new WorkflowScriptError(`worker hardened-vm crashed (likely heap/OOM): ${err.message}`))));
|
|
71
96
|
worker.on("exit", (code) => {
|
|
72
97
|
if (!settled)
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S8 hardened-vm WORKER bootstrap (design/98 v2 resource isolation). Runs inside a `worker_thread` spawned by
|
|
3
|
+
* {@link import("./hardened-vm-worker-runner.js").createWorkerHardenedVmRunner}. The SAME hardened-vm membrane
|
|
4
|
+
* ({@link runInHardenedVm} + {@link orchestrationGlobals}) executes the untrusted script HERE — so on top of
|
|
5
|
+
* the membrane's BOUNDARY isolation we get the worker's RESOURCE isolation: a separate V8 isolate + heap (capped
|
|
6
|
+
* by `resourceLimits.maxOldGenerationSizeMb` → a memory bomb OOMs the WORKER, not the shared main process) and
|
|
7
|
+
* `terminate()` (the main thread kills a microtask-starvation / runaway loop the in-process wall-clock cannot).
|
|
8
|
+
*
|
|
9
|
+
* The worker holds NO credentials: `agent`/`log` are STUBS that IPC to the main thread (which runs the real
|
|
10
|
+
* primitives with the real Runner/DB/model keys); `budget` reads a worker-local cache the main thread refreshes
|
|
11
|
+
* on each agent reply. So even a (validated-impossible) membrane boundary crossing lands in a credential-free isolate.
|
|
12
|
+
*/
|
|
1
13
|
import { parentPort, workerData } from "node:worker_threads";
|
|
2
14
|
import { splitWorkflowMeta } from "@sema-agent/core";
|
|
15
|
+
// Resolve the sibling membrane module with an EXPLICIT extension so it loads under both a tsc build (`.js`) and
|
|
16
|
+
// tsx (`.ts`) — tsx does not map a `.js` specifier to `.ts` for a worker's static import. The template literal
|
|
17
|
+
// keeps tsc from rejecting a literal `.ts` specifier; the cast restores the types.
|
|
3
18
|
const ext = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
4
19
|
const { runInHardenedVm, orchestrationGlobals } = (await import(`./hardened-vm-runner.${ext}`));
|
|
5
20
|
const port = parentPort;
|
|
6
21
|
if (!port)
|
|
7
22
|
throw new Error("hardened-vm-worker must run inside a worker_thread");
|
|
8
23
|
const input = workerData;
|
|
24
|
+
// agent: async IPC (request/response keyed by id) to the main thread's real primitive.
|
|
9
25
|
let nextId = 0;
|
|
10
26
|
const pending = new Map();
|
|
27
|
+
// budget: worker-local cache the main thread refreshes (piggybacked on every agentResult).
|
|
11
28
|
const budgetCache = { total: input.budgetTotal, spent: input.budgetSpent ?? 0 };
|
|
12
29
|
port.on("message", (msg) => {
|
|
13
30
|
if (msg.type !== "agentResult" || msg.id === undefined)
|
|
@@ -26,6 +43,8 @@ port.on("message", (msg) => {
|
|
|
26
43
|
p.rej(e);
|
|
27
44
|
}
|
|
28
45
|
});
|
|
46
|
+
// IPC-backed WorkflowPrimitives handed to orchestrationGlobals — agent/log/phase cross to the main thread;
|
|
47
|
+
// budget is the live cache; parallel/pipeline are built in-context by orchestrationGlobals.
|
|
29
48
|
const ipcCall = (type, extra) => new Promise((res, rej) => {
|
|
30
49
|
const id = nextId++;
|
|
31
50
|
pending.set(id, { res, rej });
|
|
@@ -39,8 +58,14 @@ const ipcPrimitives = {
|
|
|
39
58
|
spent: () => budgetCache.spent,
|
|
40
59
|
remaining: () => (budgetCache.total === null ? Infinity : budgetCache.total - budgetCache.spent),
|
|
41
60
|
},
|
|
61
|
+
// never invoked (orchestrationGlobals builds parallel/pipeline in-context), but required by the type.
|
|
42
62
|
parallel: async () => [],
|
|
43
63
|
pipeline: async () => [],
|
|
64
|
+
// Phase-gate fix: phase accounting must reach the MAIN thread's real primitives (the old stub ran the body and told
|
|
65
|
+
// core nothing — phases:[] on every worker-isolated run). The body function cannot cross the IPC boundary, so
|
|
66
|
+
// the same three-verb protocol as the in-process gate: mark (bare) / enter→token / exit(token, ok, err).
|
|
67
|
+
// orchestrationGlobals' own gate wraps THIS phase — its deferred body resolves when the in-VM body settles,
|
|
68
|
+
// which chains 1:1 onto these IPC verbs (enter here ⇒ the main-side gate's enter; exit ⇒ its exit).
|
|
44
69
|
phase: async (t, b) => {
|
|
45
70
|
if (b === undefined) {
|
|
46
71
|
await ipcCall("phaseMark", { title: String(t) });
|
|
@@ -57,6 +82,7 @@ const ipcPrimitives = {
|
|
|
57
82
|
await ipcCall("phaseExit", { token, ok: false, err: e instanceof Error ? e.message : String(e) });
|
|
58
83
|
}
|
|
59
84
|
catch {
|
|
85
|
+
/* exit is best-effort — the body's own error wins */
|
|
60
86
|
}
|
|
61
87
|
throw e;
|
|
62
88
|
}
|
|
@@ -70,6 +96,7 @@ void (async () => {
|
|
|
70
96
|
bindings: orchestrationGlobals(ipcPrimitives, input.scriptArgs, input.limits),
|
|
71
97
|
limits: input.limits,
|
|
72
98
|
});
|
|
99
|
+
// meta is core-parsed; JSON round-trip strips any slipped-in gadget before it crosses back via structuredClone.
|
|
73
100
|
const safeMeta = JSON.parse(JSON.stringify(meta));
|
|
74
101
|
port.postMessage({ type: "done", result, meta: safeMeta });
|
|
75
102
|
}
|
|
@@ -1,12 +1,56 @@
|
|
|
1
1
|
import type { SubagentSteerHandle } from "@sema-agent/core";
|
|
2
|
+
/**
|
|
3
|
+
* C2 (core 1.219) + design/122 (core 1.225) — the SERVICE-side registry for
|
|
4
|
+
* STEERING and RESUMING a Task SUB-AGENT (a sync delegation spawned by the `Agent` tool), the sibling of
|
|
5
|
+
* {@link import("./workflow-agent-steer.js").WorkflowAgentRegistry} for workflow agents.
|
|
6
|
+
*
|
|
7
|
+
* core emits a {@link SubagentSteerHandle} to the deployment's `RunInternals.onSubagentSpawn` sink the
|
|
8
|
+
* moment a SYNC child spawns (the handle NEVER reaches the model). The service registers it under the
|
|
9
|
+
* PARENT RUN's taskId so `POST /v1/runs/:runId/subagents/:target/{steer,resume}` can route into it.
|
|
10
|
+
* Delivery is replica-local (the handle is an in-memory bridge, like `steerableRuns`); a cross-replica
|
|
11
|
+
* steer/resume is an honest 409, never a silent drop.
|
|
12
|
+
*
|
|
13
|
+
* 🔴 LIFECYCLE (design/122 item ③ — the "不改必踩" change): handles are NO LONGER evicted on child settle.
|
|
14
|
+
* `resume` is only legal AFTER the child settled (core rejects `steering.still_running` before), so a
|
|
15
|
+
* settle-evicted registry would make resume structurally unreachable. Instead:
|
|
16
|
+
* - one handle PER parentToolCallId, NEWEST WINS (core D2: each resume re-emits a FRESH handle under the
|
|
17
|
+
* same id whose `settled` tracks the revived run — `Map.set` replace is exactly that contract);
|
|
18
|
+
* - handles live until the OWNING LEG ends (each run leg collects its `register()` unregister thunks and
|
|
19
|
+
* runs them in its finally — identity-guarded, so a suspend→fast-resume sibling leg's fresh handles are
|
|
20
|
+
* never clobbered by the old leg's teardown, mirroring the `steerableRuns` identity guard);
|
|
21
|
+
* - a per-run cap (oldest-evicted) bounds the map for a delegation-heavy parent (handles now outlive
|
|
22
|
+
* settle, so an unbounded list would grow for the whole run).
|
|
23
|
+
* After the parent run ends core disposes the retain ledger anyway (abort in-flight resumes + release all),
|
|
24
|
+
* so a post-teardown resume would only ever 409 — the eviction just keeps the map honest.
|
|
25
|
+
*
|
|
26
|
+
* Addressing: `target` matches `parentToolCallId` FIRST (spawn-time identity, unique per delegation), then
|
|
27
|
+
* falls back to `agentName` (display name; two children of the same agent type collide → the route 409s
|
|
28
|
+
* rather than steering/resuming an arbitrary sibling). Steer-in redaction is shared with the workflow route
|
|
29
|
+
* (`redactSteerIn`) — one owner of the trust gate. 🔐 `handle.childSessionId` is a continuation
|
|
30
|
+
* CAPABILITY: control-plane only — no route response may include it.
|
|
31
|
+
*
|
|
32
|
+
* SCOPE (core r1-M3): only SYNC delegations emit a handle — `run_in_background` children have neither steer
|
|
33
|
+
* nor resume (poll/stop via TaskOutput/TaskStop; durable bg revival is a separate core item).
|
|
34
|
+
*/
|
|
35
|
+
/** Per-run handle cap — handles now live until the leg ends (not settle), so bound the map. Oldest-evicted;
|
|
36
|
+
* 256 mirrors the ToolDetachHub bound (a run with >256 live-or-resumable delegations is pathological). */
|
|
2
37
|
export declare const SUBAGENT_HANDLES_PER_RUN_CAP = 256;
|
|
3
38
|
export declare class SubagentSteerRegistry {
|
|
39
|
+
/** parent run taskId → (parentToolCallId → NEWEST handle). Map iteration order = insertion = age. */
|
|
4
40
|
private readonly byRun;
|
|
41
|
+
/** Register a child handle under its parent run. SAME parentToolCallId replaces (core D2: a resume
|
|
42
|
+
* re-emits a fresh handle for the revived run — newest wins). Returns an IDENTITY-GUARDED unregister
|
|
43
|
+
* thunk: it removes the entry only while it still holds THIS handle (a fresh sibling-leg registration
|
|
44
|
+
* under the same id survives the old leg's teardown). Call it at LEG END, not on settle (design/122 ③). */
|
|
5
45
|
register(runId: string, handle: SubagentSteerHandle): () => void;
|
|
46
|
+
/** Resolve `runId` + `target` to its handle on this replica. Exact `parentToolCallId` match wins (unique —
|
|
47
|
+
* newest-per-id by construction → count is 0|1); otherwise match by `agentName` (count may be >1 →
|
|
48
|
+
* ambiguous, `handle` undefined → the route 409s). count=0 → no such child here. */
|
|
6
49
|
resolve(runId: string, target: string): {
|
|
7
50
|
handle?: SubagentSteerHandle;
|
|
8
51
|
count: number;
|
|
9
52
|
};
|
|
53
|
+
/** Total registered handles (test/observability). */
|
|
10
54
|
get size(): number;
|
|
11
55
|
}
|
|
12
56
|
//# sourceMappingURL=subagent-steer.d.ts.map
|
|
@@ -1,13 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C2 (core 1.219) + design/122 (core 1.225) — the SERVICE-side registry for
|
|
3
|
+
* STEERING and RESUMING a Task SUB-AGENT (a sync delegation spawned by the `Agent` tool), the sibling of
|
|
4
|
+
* {@link import("./workflow-agent-steer.js").WorkflowAgentRegistry} for workflow agents.
|
|
5
|
+
*
|
|
6
|
+
* core emits a {@link SubagentSteerHandle} to the deployment's `RunInternals.onSubagentSpawn` sink the
|
|
7
|
+
* moment a SYNC child spawns (the handle NEVER reaches the model). The service registers it under the
|
|
8
|
+
* PARENT RUN's taskId so `POST /v1/runs/:runId/subagents/:target/{steer,resume}` can route into it.
|
|
9
|
+
* Delivery is replica-local (the handle is an in-memory bridge, like `steerableRuns`); a cross-replica
|
|
10
|
+
* steer/resume is an honest 409, never a silent drop.
|
|
11
|
+
*
|
|
12
|
+
* 🔴 LIFECYCLE (design/122 item ③ — the "不改必踩" change): handles are NO LONGER evicted on child settle.
|
|
13
|
+
* `resume` is only legal AFTER the child settled (core rejects `steering.still_running` before), so a
|
|
14
|
+
* settle-evicted registry would make resume structurally unreachable. Instead:
|
|
15
|
+
* - one handle PER parentToolCallId, NEWEST WINS (core D2: each resume re-emits a FRESH handle under the
|
|
16
|
+
* same id whose `settled` tracks the revived run — `Map.set` replace is exactly that contract);
|
|
17
|
+
* - handles live until the OWNING LEG ends (each run leg collects its `register()` unregister thunks and
|
|
18
|
+
* runs them in its finally — identity-guarded, so a suspend→fast-resume sibling leg's fresh handles are
|
|
19
|
+
* never clobbered by the old leg's teardown, mirroring the `steerableRuns` identity guard);
|
|
20
|
+
* - a per-run cap (oldest-evicted) bounds the map for a delegation-heavy parent (handles now outlive
|
|
21
|
+
* settle, so an unbounded list would grow for the whole run).
|
|
22
|
+
* After the parent run ends core disposes the retain ledger anyway (abort in-flight resumes + release all),
|
|
23
|
+
* so a post-teardown resume would only ever 409 — the eviction just keeps the map honest.
|
|
24
|
+
*
|
|
25
|
+
* Addressing: `target` matches `parentToolCallId` FIRST (spawn-time identity, unique per delegation), then
|
|
26
|
+
* falls back to `agentName` (display name; two children of the same agent type collide → the route 409s
|
|
27
|
+
* rather than steering/resuming an arbitrary sibling). Steer-in redaction is shared with the workflow route
|
|
28
|
+
* (`redactSteerIn`) — one owner of the trust gate. 🔐 `handle.childSessionId` is a continuation
|
|
29
|
+
* CAPABILITY: control-plane only — no route response may include it.
|
|
30
|
+
*
|
|
31
|
+
* SCOPE (core r1-M3): only SYNC delegations emit a handle — `run_in_background` children have neither steer
|
|
32
|
+
* nor resume (poll/stop via TaskOutput/TaskStop; durable bg revival is a separate core item).
|
|
33
|
+
*/
|
|
34
|
+
/** Per-run handle cap — handles now live until the leg ends (not settle), so bound the map. Oldest-evicted;
|
|
35
|
+
* 256 mirrors the ToolDetachHub bound (a run with >256 live-or-resumable delegations is pathological). */
|
|
1
36
|
export const SUBAGENT_HANDLES_PER_RUN_CAP = 256;
|
|
2
37
|
export class SubagentSteerRegistry {
|
|
38
|
+
/** parent run taskId → (parentToolCallId → NEWEST handle). Map iteration order = insertion = age. */
|
|
3
39
|
byRun = new Map();
|
|
40
|
+
/** Register a child handle under its parent run. SAME parentToolCallId replaces (core D2: a resume
|
|
41
|
+
* re-emits a fresh handle for the revived run — newest wins). Returns an IDENTITY-GUARDED unregister
|
|
42
|
+
* thunk: it removes the entry only while it still holds THIS handle (a fresh sibling-leg registration
|
|
43
|
+
* under the same id survives the old leg's teardown). Call it at LEG END, not on settle (design/122 ③). */
|
|
4
44
|
register(runId, handle) {
|
|
5
45
|
let handles = this.byRun.get(runId);
|
|
6
46
|
if (!handles) {
|
|
7
47
|
handles = new Map();
|
|
8
48
|
this.byRun.set(runId, handles);
|
|
9
49
|
}
|
|
10
|
-
handles.delete(handle.parentToolCallId);
|
|
50
|
+
handles.delete(handle.parentToolCallId); // re-insert → moves to newest position (age = last registration)
|
|
11
51
|
handles.set(handle.parentToolCallId, handle);
|
|
12
52
|
if (handles.size > SUBAGENT_HANDLES_PER_RUN_CAP) {
|
|
13
53
|
const oldest = handles.keys().next().value;
|
|
@@ -25,6 +65,9 @@ export class SubagentSteerRegistry {
|
|
|
25
65
|
}
|
|
26
66
|
};
|
|
27
67
|
}
|
|
68
|
+
/** Resolve `runId` + `target` to its handle on this replica. Exact `parentToolCallId` match wins (unique —
|
|
69
|
+
* newest-per-id by construction → count is 0|1); otherwise match by `agentName` (count may be >1 →
|
|
70
|
+
* ambiguous, `handle` undefined → the route 409s). count=0 → no such child here. */
|
|
28
71
|
resolve(runId, target) {
|
|
29
72
|
const handles = this.byRun.get(runId);
|
|
30
73
|
if (!handles || handles.size === 0)
|
|
@@ -35,6 +78,7 @@ export class SubagentSteerRegistry {
|
|
|
35
78
|
const byName = [...handles.values()].filter((h) => h.agentName === target);
|
|
36
79
|
return byName.length === 1 ? { handle: byName[0], count: 1 } : { count: byName.length };
|
|
37
80
|
}
|
|
81
|
+
/** Total registered handles (test/observability). */
|
|
38
82
|
get size() {
|
|
39
83
|
let n = 0;
|
|
40
84
|
for (const handles of this.byRun.values())
|
|
@@ -1,15 +1,69 @@
|
|
|
1
1
|
import { type WorkflowAgentHandle } from "@sema-agent/core";
|
|
2
|
+
/** The result of resolving a steer target. core does NOT enforce label uniqueness within a run (a script may
|
|
3
|
+
* spawn two steerable agents with the same explicit `label`), so a (runId,label) can address MORE THAN ONE live
|
|
4
|
+
* agent — steering then has no well-defined target and must be refused, not sent to an arbitrary one. */
|
|
2
5
|
export interface SteerResolution {
|
|
6
|
+
/** The live handle iff EXACTLY ONE matches; undefined when none (not here) or when ambiguous (>1). */
|
|
3
7
|
handle?: WorkflowAgentHandle;
|
|
8
|
+
/** Live handles matching (0 = none on this replica, 1 = unique target, >1 = ambiguous → the route 409s). */
|
|
4
9
|
count: number;
|
|
5
10
|
}
|
|
11
|
+
/** A process-local registry of the steerable workflow-agent handles live on THIS replica. Mirrors the
|
|
12
|
+
* `steerableRuns` / `preemptableRuns` pattern (a live in-memory handle is replica-local — a cross-replica steer
|
|
13
|
+
* is a structured 409, never a silent drop). Stores a LIST per (runId,label): core does not enforce label
|
|
14
|
+
* uniqueness, so a duplicate-label collision must NOT silently last-writer-wins (that would steer the WRONG
|
|
15
|
+
* agent + strand the sibling) — {@link resolve} reports the count so the route can 409-on-ambiguous instead. */
|
|
6
16
|
export declare class WorkflowAgentRegistry {
|
|
7
17
|
private readonly handles;
|
|
18
|
+
/** Register a handle when its `agentStream` starts on this replica. Returns an unregister thunk to call when
|
|
19
|
+
* the agent settles (keeps the map from leaking). APPENDS (does not overwrite) so a duplicate-label sibling is
|
|
20
|
+
* also tracked → {@link resolve} sees count>1 and the route refuses rather than steering an arbitrary one. */
|
|
8
21
|
register(handle: WorkflowAgentHandle): () => void;
|
|
22
|
+
/** Resolve `runId`+`label` to its UNIQUE live handle on this replica. count=0 → not here (terminal /
|
|
23
|
+
* cross-replica); count=1 → `handle` set; count>1 → ambiguous (`handle` undefined → the route 409s rather
|
|
24
|
+
* than steering an arbitrary one of the colliding same-label agents). */
|
|
9
25
|
resolve(runId: string, label: string): SteerResolution;
|
|
26
|
+
/** Total live handles (test/observability). */
|
|
10
27
|
get size(): number;
|
|
11
28
|
}
|
|
29
|
+
/** Max steer-in length fed to a worker (chars). A steer is a short redirection, not a payload; core's
|
|
30
|
+
* `untrustedEgressForHuman` bounds + truncates beyond this.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ 纠正(2026-07-26,core 1.414 + 亲测):这里原本写的是「truncates with an **honest marker**」,现在只有
|
|
33
|
+
* 一半成立 —— 实测(`test/svc5-steer-live.test.ts` 里已钉):
|
|
34
|
+
* · **「被截断过」仍有信号**:超界时正文段尾出现 `…`(未超界时不出现)⇒ 消费方能判断"这不是全文";
|
|
35
|
+
* · **「截了多少」不再披露**:`[+N chars]` 在**所有**档位都没有了(实测 +1 / +10 / +100 / +5000 / +50000
|
|
36
|
+
* 全部无披露,输出长度恒 4103)。所以一个超出 5 万字符的输入与只超 1 个字符的输入,**外观完全相同**。
|
|
37
|
+
* ⚠️ core [1707]⑤ 的措辞是「**刚过界**的输入现在是裸 `…` 结尾」,而实测是**全档位**都没有披露 —— 范围比
|
|
38
|
+
* 它描述的宽。已带实测数据上黑板问 core 这是否是有意的(可能只是描述省略,也可能是它没注意到)。
|
|
39
|
+
* 在得到答复前**不改行为**:core 的截断器是它那侧的安全件,server 不该自己在外面补一层"+N chars"去顶
|
|
40
|
+
* ——那会变成两处各说一套。 */
|
|
12
41
|
export declare const STEER_IN_MAX_CHARS = 4000;
|
|
42
|
+
/**
|
|
43
|
+
* **入参**上限(与 {@link STEER_IN_MAX_CHARS} 是两件事:那个限的是**输出**,这个限的是**请求带进来的**)。
|
|
44
|
+
*
|
|
45
|
+
* ── 为什么需要它 ───────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
* 脱敏门是**同步**函数,烧的是**事件循环** ⇒ 一次大 steer 卡住的是**整个副本**,不只是那个请求。
|
|
47
|
+
* 此前这里没有上限:`body.content` 只校验类型与非空,而 JSON 请求体上限是 8 MiB。
|
|
48
|
+
*
|
|
49
|
+
* ── 数是怎么定的(实测,不是拍的)────────────────────────────────────────────────────────────────
|
|
50
|
+
* core 1.421.0 起该函数对输入是**线性**的(此前对「最长不间断词字符段」二次,已修)。
|
|
51
|
+
* 本机实测最坏形状(纯词字符段):64 KiB ≈ 17ms ⇒ **256 KiB ≈ 68ms**、8 MiB ≈ 2.2s。
|
|
52
|
+
* ⇒ 取 **256 KiB**:最坏一拍 ~70ms(可接受的事件循环占用),而它是输出上限的 **64 倍** ——
|
|
53
|
+
* 任何正当的粘贴都装得下,而超出部分**本来就会被丢弃**(只保留 4000 字符 + 一个"丢了多少"的披露)。
|
|
54
|
+
*
|
|
55
|
+
* ── 为什么是**拒**而不是**服务端先截** ──────────────────────────────────────────────────────────
|
|
56
|
+
* 先截会让 core 的 `[+N chars]` 披露**低报**(N 会变成相对截断后输入的),
|
|
57
|
+
* 而"诚实标记不得低报"是本仓另一条钉守着的性质。**为一个已经不紧迫的成本去改一条正确性性质,不划算。**
|
|
58
|
+
* ⇒ 拒 + 在错误里说清上限,调用方自己决定发什么。
|
|
59
|
+
*/
|
|
13
60
|
export declare const STEER_IN_MAX_REQUEST_CHARS: number;
|
|
61
|
+
/**
|
|
62
|
+
* The steer-in REDACTION gate (SVC-5 信任门). Treat the human/leader steer content as UNTRUSTED before it
|
|
63
|
+
* reaches the worker: redact host-internal leaks (URLs / tokens / named secrets / absolute paths), size-bound,
|
|
64
|
+
* and FENCE it so it can't pose as authority / forge a `</system-reminder>`. `label` tags the fenced block for
|
|
65
|
+
* the worker's reader. This is the service's half of the depth — `handle.steer` fences again inside its trusted
|
|
66
|
+
* marker framing.
|
|
67
|
+
*/
|
|
14
68
|
export declare function redactSteerIn(content: string, label: string): string;
|
|
15
69
|
//# sourceMappingURL=workflow-agent-steer.d.ts.map
|