@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
package/dist/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, RECOMMENDED_SENSITIVE_PATTERNS } from "@sema-agent/core";
|
|
5
5
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/registry-core";
|
|
6
|
-
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
6
|
+
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js"; // design/158 A4: the parser leaf — NOT security.js (base config layer must not value-import the 55KiB auth module)
|
|
7
7
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
8
8
|
function csv(name) {
|
|
9
9
|
return (process.env[name] ?? "")
|
|
@@ -12,6 +12,11 @@ function csv(name) {
|
|
|
12
12
|
.filter(Boolean);
|
|
13
13
|
}
|
|
14
14
|
const AUTONOMY_MODES = ["read-only", "ask", "plan", "auto"];
|
|
15
|
+
/** Parse the AUTONOMY env into a validated autonomy mode. Unset/empty → undefined (unmanaged → no extra
|
|
16
|
+
* tightening). An UNKNOWN value FAILS at startup rather than silently becoming a no-op (a typo'd `AUTONOMY=readonly`
|
|
17
|
+
* must not silently leave a deployment ungoverned — fail-loud, same discipline as numEnv). Exported so the HOT
|
|
18
|
+
* config overlay (sema-registry.ts `applyRuntimeHot`) can re-derive the ENV BASELINE to revert to when center
|
|
19
|
+
* stops managing `autonomy` (a stale center override must not stick — see applyRuntimeHot). */
|
|
15
20
|
export function parseAutonomy(raw) {
|
|
16
21
|
const v = raw?.trim();
|
|
17
22
|
if (!v)
|
|
@@ -21,6 +26,9 @@ export function parseAutonomy(raw) {
|
|
|
21
26
|
}
|
|
22
27
|
return v;
|
|
23
28
|
}
|
|
29
|
+
/** Resolve a CSV of env-var NAMES into a {NAME: value} map from THIS process's env (out-of-band secret
|
|
30
|
+
* forwarding into a sandbox). A name with no value set is skipped (not forwarded as undefined). The values
|
|
31
|
+
* are secrets — they live only in the returned map (in-memory), never logged. */
|
|
24
32
|
function resolveEnvForward(csvNames) {
|
|
25
33
|
const out = {};
|
|
26
34
|
for (const name of csvNames.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
@@ -39,14 +47,21 @@ function env(name, fallback) {
|
|
|
39
47
|
}
|
|
40
48
|
return v;
|
|
41
49
|
}
|
|
50
|
+
/** 出厂 skill 目录的缺省 = 包内 `skills/`(相对本模块解析),不是 cwd 相对的裸 "skills"。
|
|
51
|
+
* 真 bug(live 战役 2026-07-15 抓获):npm 全局装的 CLE 从用户 cwd 起引擎,裸 "skills" 解析到
|
|
52
|
+
* cwd/skills(不存在)⇒ skills:0,所有烤入 skill 在 npm 分发面全空转(bench 日志 skills:0 即此)。
|
|
53
|
+
* dist/config.js 与 skills/ 同在包根下的 ../skills;src 跑测试时 ../skills 同样是仓根 skills(一致)。
|
|
54
|
+
* SKILLS_DIR 显式设仍覆盖此缺省;Docker(WORKDIR 包根+COPY skills)解析到 /app/skills 不破。 */
|
|
42
55
|
function defaultSkillsDir() {
|
|
43
56
|
try {
|
|
44
57
|
return fileURLToPath(new URL("../skills", import.meta.url));
|
|
45
58
|
}
|
|
46
59
|
catch {
|
|
47
|
-
return "skills";
|
|
60
|
+
return "skills"; // import.meta 不可用的兜底(不应发生在 ESM 构建里)
|
|
48
61
|
}
|
|
49
62
|
}
|
|
63
|
+
/** Two-name env with the first set winning (MYSQL_* preferred over the TIDB_* back-compat alias); throws
|
|
64
|
+
* when neither is set and no fallback is given — same fail-loud contract as `env`. */
|
|
50
65
|
function env2(primary, alias, fallback) {
|
|
51
66
|
const v = process.env[primary] || process.env[alias];
|
|
52
67
|
if (v === undefined || v === "") {
|
|
@@ -56,6 +71,8 @@ function env2(primary, alias, fallback) {
|
|
|
56
71
|
}
|
|
57
72
|
return v;
|
|
58
73
|
}
|
|
74
|
+
/** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
|
|
75
|
+
* (which e.g. slips past the `runStaleSec` liveness guard and silently disables the reaper). */
|
|
59
76
|
function numEnv(name, fallback) {
|
|
60
77
|
const raw = env(name, fallback);
|
|
61
78
|
const n = Number(raw);
|
|
@@ -63,25 +80,51 @@ function numEnv(name, fallback) {
|
|
|
63
80
|
throw new Error(`env ${name}="${raw}" must be a number`);
|
|
64
81
|
return n;
|
|
65
82
|
}
|
|
83
|
+
/** Numeric env with a [min,max] bound (BL-17): an out-of-range value FAILS at startup instead of being
|
|
84
|
+
* silently clamped/applied (e.g. a multi-hour MCP_ELICITATION_TTL_MS would silently never expire). */
|
|
66
85
|
function numEnvBounded(name, fallback, min, max) {
|
|
67
86
|
const n = numEnv(name, fallback);
|
|
68
87
|
if (n < min || n > max)
|
|
69
88
|
throw new Error(`env ${name}=${n} must be in [${min}, ${max}]`);
|
|
70
89
|
return n;
|
|
71
90
|
}
|
|
91
|
+
/** Optional FAIL-SAFE numeric env for an OPTIONAL feature knob: a non-numeric / non-positive value yields `undefined`
|
|
92
|
+
* (the caller falls back to its own default) rather than NaN OR a hard startup throw. Used for SESSION_SNAPSHOT_TTL_SEC
|
|
93
|
+
* (finding 20): a typo like "600s" must NOT sign every presigned URL with X-Amz-Expires=NaN (total snapshot outage),
|
|
94
|
+
* but ALSO must not crash boot of an unrelated subsystem — degrade to the MinioBlobBackend default. */
|
|
95
|
+
/** S20 (SILENT-FALLBACK P1): soft-knob parse rejects, collected here because config parsing runs BEFORE the
|
|
96
|
+
* logger exists. main.ts drains this once after createLogger and warns per entry ("configured X=raw is
|
|
97
|
+
* invalid, using the default"). Module-level on purpose; drain clears it (repeat loadConfig in tests is fine). */
|
|
72
98
|
const CONFIG_WARNINGS = [];
|
|
73
99
|
export function drainConfigWarnings() {
|
|
74
100
|
return CONFIG_WARNINGS.splice(0, CONFIG_WARNINGS.length);
|
|
75
101
|
}
|
|
102
|
+
/** Boot NOTICES ([792]④): non-error decisions loadConfig makes that the operator should see once (e.g. the
|
|
103
|
+
* MODEL_PROVIDER inference below). Same pre-logger collection pattern as CONFIG_WARNINGS, but each entry
|
|
104
|
+
* carries its own event name + fields (the warnings drain has one fixed "invalid, using default" message
|
|
105
|
+
* which would misdescribe an inference). main.ts drains + logs right after createLogger. */
|
|
76
106
|
const CONFIG_NOTICES = [];
|
|
77
107
|
export function drainConfigNotices() {
|
|
78
108
|
return CONFIG_NOTICES.splice(0, CONFIG_NOTICES.length);
|
|
79
109
|
}
|
|
110
|
+
/** F10 (codex audit): drain + log BOTH pre-logger diagnostic collectors (S20 soft-knob warnings + [792]④ boot
|
|
111
|
+
* notices) in one place. EVERY entrypoint that calls loadConfig() then builds a logger must call this right
|
|
112
|
+
* after (main.ts, run-local.ts) — a new entry that forgets it would silently drop operator-facing diagnostics
|
|
113
|
+
* (run-local historically did exactly that), so the drain lives beside the collectors, not in one entrypoint. */
|
|
80
114
|
export function logConfigDiagnostics(logger) {
|
|
81
115
|
for (const w of drainConfigWarnings())
|
|
82
116
|
logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
|
|
83
117
|
for (const n of drainConfigNotices())
|
|
84
118
|
logger.warn(n.event, n.fields);
|
|
119
|
+
// design/158 N9 (polarity self-report): a `*_ENABLED` suffix does NOT carry its default — the repo grew four
|
|
120
|
+
// dialects for it (opt-in / opt-out / posture tri-state / negative name). Rather than force operators to read
|
|
121
|
+
// config.ts to learn what a knob does when unset, boolEnv-parsed knobs register themselves and we print one
|
|
122
|
+
// debug line per knob: name → polarity → effective value → where that value came from. `debug` is optional so
|
|
123
|
+
// an older embedder-supplied logger shape (warn-only) still compiles and still gets the warnings above.
|
|
124
|
+
// 复审 2026-07-29 #6 — the table covers a LOAD, not the catalog: five boolEnv calls sit inside conditional
|
|
125
|
+
// config shapes (E2B_ALLOW_NET only when set; K8S_INSECURE_TLS / DOCKER_DROP_CAPS only under their provider
|
|
126
|
+
// branch; MODEL_DEGRADE_REACTIVE / MODEL_DEGRADE_TO_VISION only when MODEL_DEGRADE_TO is set) and register
|
|
127
|
+
// only when that shape is active — absence of a row means "not a live knob in this config", not "off".
|
|
85
128
|
if (logger.debug) {
|
|
86
129
|
for (const k of configKnobTable()) {
|
|
87
130
|
logger.debug("config_knob_polarity", {
|
|
@@ -94,6 +137,11 @@ export function logConfigDiagnostics(logger) {
|
|
|
94
137
|
}
|
|
95
138
|
}
|
|
96
139
|
}
|
|
140
|
+
/** F8 (codex audit): first NON-EMPTY env among aliased names — "" = unset, the same convention `env()` uses.
|
|
141
|
+
* A plain `??` chain treats an EMPTY first choice as SET and shadows a configured fallback (e.g.
|
|
142
|
+
* `ANTHROPIC_BASEURL=""` from a compose `${VAR:-}` template hiding a real `ANTHROPIC_BASE_URL`, or an empty
|
|
143
|
+
* `ANTHROPIC_API_KEY` suppressing the whole route despite a valid `ANTHROPIC_AUTH_TOKEN`). Shared by the
|
|
144
|
+
* MODEL_PROVIDER inference AND the anthropic route assembly so the two predicates can never drift. */
|
|
97
145
|
function firstSetEnv(...names) {
|
|
98
146
|
for (const n of names) {
|
|
99
147
|
const v = process.env[n];
|
|
@@ -108,13 +156,18 @@ function optFinitePositiveEnv(name) {
|
|
|
108
156
|
return undefined;
|
|
109
157
|
const n = Number(raw);
|
|
110
158
|
if (!(Number.isFinite(n) && n > 0)) {
|
|
111
|
-
CONFIG_WARNINGS.push({ env: name, raw });
|
|
159
|
+
CONFIG_WARNINGS.push({ env: name, raw }); // S20: previously a silent swallow — caller quietly used the default
|
|
112
160
|
return undefined;
|
|
113
161
|
}
|
|
114
162
|
return n;
|
|
115
163
|
}
|
|
164
|
+
/** Keyed by canonical name so a knob read twice in one load (e.g. DURABLE_APPROVAL, once for the D-G boot
|
|
165
|
+
* invariant and once for the returned field) registers one row, not two. Reset per `loadConfig()`. */
|
|
116
166
|
const CONFIG_KNOBS = new Map();
|
|
167
|
+
/** Deprecated aliases already announced for this load — the lazily-read twins (HOST_BG/HOST_EXEC_SPOOL) resolve
|
|
168
|
+
* again on every use, and an operator does not need the same deprecation line once per command. */
|
|
117
169
|
const KNOB_DEPRECATIONS_SEEN = new Set();
|
|
170
|
+
/** The registered boolean knobs, sorted by name — the data behind `logConfigDiagnostics`'s polarity table. */
|
|
118
171
|
export function configKnobTable() {
|
|
119
172
|
return [...CONFIG_KNOBS.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
120
173
|
}
|
|
@@ -126,6 +179,7 @@ function boolEnv(name, def) {
|
|
|
126
179
|
let value = def;
|
|
127
180
|
let source = "default";
|
|
128
181
|
if (raw !== undefined && raw !== "") {
|
|
182
|
+
// "" = unset, the same convention `env()`/`firstSetEnv()` use (a compose `${VAR:-}` template passes empty through).
|
|
129
183
|
if (raw === "true") {
|
|
130
184
|
value = true;
|
|
131
185
|
source = "env";
|
|
@@ -135,12 +189,30 @@ function boolEnv(name, def) {
|
|
|
135
189
|
source = "env";
|
|
136
190
|
}
|
|
137
191
|
else if (!CONFIG_KNOBS.has(name)) {
|
|
192
|
+
// Previously a silent default at every hand-written site. Guarded on first registration because a few
|
|
193
|
+
// knobs are legitimately read twice in one load (IMAGE_BAKES_ENABLED by its two boot invariants and then
|
|
194
|
+
// by the returned field; DURABLE_APPROVAL by the D-G gate and then the field) — one typo, one warning.
|
|
138
195
|
CONFIG_WARNINGS.push({ env: name, raw });
|
|
139
196
|
}
|
|
140
197
|
}
|
|
141
198
|
registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source });
|
|
142
199
|
return value;
|
|
143
200
|
}
|
|
201
|
+
/** design/158 N8 (D-family) — a knob whose CANONICAL name is positive (`X_ENABLED`, default ON) but which keeps
|
|
202
|
+
* its retired negative name (`X_DISABLED`) working for one deprecation window.
|
|
203
|
+
*
|
|
204
|
+
* Why the flip at all: `X_DISABLED !== "true"` is a double negative at the read site, so the four knobs that
|
|
205
|
+
* used it (`PROJECT_MEMORY`, `CONFIG_LKG`, `HOST_BG`, `HOST_EXEC_SPOOL`) could not be read for their default
|
|
206
|
+
* without inverting twice in your head, and `projectMemoryDisabled` was the ONE negative boolean on a
|
|
207
|
+
* `ServiceConfig` that otherwise carries 16 positive `*Enabled`/`*Allowed` fields.
|
|
208
|
+
*
|
|
209
|
+
* Resolution order (new name wins, so there is never an ambiguous both-set state):
|
|
210
|
+
* 1. `X_ENABLED` set to a literal ⇒ that.
|
|
211
|
+
* 2. `X_ENABLED` set to a non-literal ⇒ default + warning (the legacy name is NOT consulted — a typo in the
|
|
212
|
+
* new name must not silently hand control back to the deprecated one).
|
|
213
|
+
* 3. `X_ENABLED` unset ∧ `X_DISABLED` set ⇒ the legacy site's exact contract, `X_DISABLED !== "true"`, plus a
|
|
214
|
+
* one-shot deprecation notice naming the replacement.
|
|
215
|
+
* 4. neither ⇒ default. */
|
|
144
216
|
export function boolEnvWithLegacyNegated(name, legacyNegatedName, def) {
|
|
145
217
|
const raw = process.env[name];
|
|
146
218
|
const legacy = process.env[legacyNegatedName];
|
|
@@ -152,13 +224,16 @@ export function boolEnvWithLegacyNegated(name, legacyNegatedName, def) {
|
|
|
152
224
|
}
|
|
153
225
|
else if (raw !== undefined && raw !== "") {
|
|
154
226
|
if (!CONFIG_KNOBS.has(name)) {
|
|
227
|
+
// Same first-registration guard as boolEnv — the three consumers of this helper are the LAZILY re-read
|
|
228
|
+
// ones (remote-env-host reads per exec/probe), so an unguarded push would grow CONFIG_WARNINGS for the
|
|
229
|
+
// whole process lifetime on a single operator typo (复审 2026-07-29 #1).
|
|
155
230
|
CONFIG_WARNINGS.push({ env: name, raw });
|
|
156
231
|
}
|
|
157
232
|
value = def;
|
|
158
233
|
source = "default";
|
|
159
234
|
}
|
|
160
235
|
else if (legacy !== undefined && legacy !== "") {
|
|
161
|
-
value = legacy !== "true";
|
|
236
|
+
value = legacy !== "true"; // verbatim legacy contract: only the literal "true" disabled the feature
|
|
162
237
|
source = "legacy-env";
|
|
163
238
|
if (!KNOB_DEPRECATIONS_SEEN.has(legacyNegatedName)) {
|
|
164
239
|
KNOB_DEPRECATIONS_SEEN.add(legacyNegatedName);
|
|
@@ -175,33 +250,55 @@ export function boolEnvWithLegacyNegated(name, legacyNegatedName, def) {
|
|
|
175
250
|
registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source, legacyName: legacyNegatedName });
|
|
176
251
|
return value;
|
|
177
252
|
}
|
|
253
|
+
/** LKG (last-known-good) persistence of the remote effective-config fetch. Read LAZILY at both main.ts sites
|
|
254
|
+
* rather than lifted onto `ServiceConfig`, so this stays a pure env knob; `loadConfig` pre-registers it (below)
|
|
255
|
+
* so its polarity row and any deprecation notice appear at boot with everything else. */
|
|
178
256
|
export const configLkgEnabled = () => boolEnvWithLegacyNegated("CONFIG_LKG_ENABLED", "CONFIG_LKG_DISABLED", true);
|
|
257
|
+
/** The `host` exec lane's background-shell capability kill switch (read per capability probe in remote-env-host.ts). */
|
|
179
258
|
export const hostBackgroundShellEnabled = () => boolEnvWithLegacyNegated("HOST_BG_ENABLED", "HOST_BG_DISABLED", true);
|
|
259
|
+
/** The `host` exec lane's spool-file stdio form; false ⇒ the pre-1.226 pipe fallback (read per exec). */
|
|
180
260
|
export const hostExecSpoolEnabled = () => boolEnvWithLegacyNegated("HOST_EXEC_SPOOL_ENABLED", "HOST_EXEC_SPOOL_DISABLED", true);
|
|
261
|
+
/** Enum env with validation: a typo (e.g. SESSION_BACKEND=Tidb) FAILS at startup instead of being silently
|
|
262
|
+
* cast to the union type and degrading to the default backend (dropping the persistence the operator asked for). */
|
|
181
263
|
function enumEnv(name, fallback, allowed) {
|
|
182
264
|
const v = (process.env[name] ?? fallback);
|
|
183
265
|
if (!allowed.includes(v))
|
|
184
266
|
throw new Error(`env ${name}="${v}" must be one of: ${allowed.join(", ")}`);
|
|
185
267
|
return v;
|
|
186
268
|
}
|
|
269
|
+
/** A single HTTP header-name env: it is both read as a request header AND interpolated into the CORS
|
|
270
|
+
* `access-control-allow-headers` list, so a value with a comma/space would split that list (and isn't a
|
|
271
|
+
* valid header name anyway). Reject anything but header-token chars at startup (council). */
|
|
187
272
|
function headerNameEnv(name, fallback) {
|
|
188
273
|
const v = env(name, fallback);
|
|
189
274
|
if (!/^[A-Za-z0-9_-]+$/.test(v))
|
|
190
275
|
throw new Error(`env ${name}="${v}" must be a single header name ([A-Za-z0-9_-]+)`);
|
|
191
276
|
return v;
|
|
192
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Accept an SSH private key in whatever single-line form a secret store can hold. A PEM key is multi-line, but
|
|
280
|
+
* sema-registry's `POST /api/config/secrets` rejects newlines — so let the value arrive base64-encoded (the
|
|
281
|
+
* recommended self-serve form) or with escaped `\n`, and reconstruct the real PEM here for ssh2.
|
|
282
|
+
*/
|
|
193
283
|
function normalizeSshPrivateKey(raw) {
|
|
194
284
|
const v = raw.trim();
|
|
195
285
|
if (v.includes("BEGIN ") && v.includes("PRIVATE KEY")) {
|
|
196
|
-
return v.includes("\n") ? raw : v.replace(/\\n/g, "\n");
|
|
286
|
+
return v.includes("\n") ? raw : v.replace(/\\n/g, "\n"); // already PEM (real newlines) or escaped-\n single line
|
|
197
287
|
}
|
|
198
288
|
try {
|
|
199
|
-
return Buffer.from(v, "base64").toString("utf8");
|
|
289
|
+
return Buffer.from(v, "base64").toString("utf8"); // base64-of-PEM (newline-free → secrets-API friendly)
|
|
200
290
|
}
|
|
201
291
|
catch {
|
|
202
292
|
return raw;
|
|
203
293
|
}
|
|
204
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Build `Model.extraBody` (core 1.60 generation-tuning passthrough) from env, or undefined when nothing
|
|
297
|
+
* is set (so a deployment that sets none is byte-for-byte unaffected). MODEL_EXTRA_BODY (raw JSON) is the
|
|
298
|
+
* general escape hatch; MODEL_FREQUENCY_PENALTY / MODEL_PRESENCE_PENALTY are typed conveniences merged on
|
|
299
|
+
* top (they win over the same key in MODEL_EXTRA_BODY). Throws on malformed JSON — fail loud at boot, not
|
|
300
|
+
* a silently-ignored tuning knob.
|
|
301
|
+
*/
|
|
205
302
|
function buildExtraBody() {
|
|
206
303
|
const body = {};
|
|
207
304
|
const raw = process.env.MODEL_EXTRA_BODY;
|
|
@@ -226,6 +323,10 @@ function buildExtraBody() {
|
|
|
226
323
|
body.presence_penalty = Number(pp);
|
|
227
324
|
return Object.keys(body).length > 0 ? body : undefined;
|
|
228
325
|
}
|
|
326
|
+
/** [818]① 1M dual-window derivation (core 1.289 `Model.autoCompactTokens`): an EXPLICIT positive value wins
|
|
327
|
+
* (a center roster may declare its own); else contextWindow>=1e6 derives the CC-aligned 967000; else the field
|
|
328
|
+
* is REMOVED (a spread-inherited value from a bigger-window base model would mis-trigger a small window).
|
|
329
|
+
* Shared by the env lane (main + cheap models) and the center lane (sema-registry toModel). */
|
|
229
330
|
export function applyAutoCompactWindow(m, explicit) {
|
|
230
331
|
const target = m;
|
|
231
332
|
if (explicit !== undefined && Number.isFinite(explicit) && explicit > 0) {
|
|
@@ -238,14 +339,27 @@ export function applyAutoCompactWindow(m, explicit) {
|
|
|
238
339
|
delete target.autoCompactTokens;
|
|
239
340
|
}
|
|
240
341
|
export function loadConfig() {
|
|
241
|
-
CONFIG_WARNINGS.length = 0;
|
|
342
|
+
CONFIG_WARNINGS.length = 0; // repeated loadConfig() calls (test setup) must not accumulate stale warnings
|
|
242
343
|
CONFIG_NOTICES.length = 0;
|
|
243
|
-
CONFIG_KNOBS.clear();
|
|
344
|
+
CONFIG_KNOBS.clear(); // the polarity table describes THIS load, never a previous one's env
|
|
244
345
|
KNOB_DEPRECATIONS_SEEN.clear();
|
|
346
|
+
// npm 卫生纪律:默认值不烤内网坐标 — localhost 占位(公开分发正确缺省);真网关一律显式 env。
|
|
245
347
|
const gatewayBaseUrl = env("MODEL_GATEWAY_BASEURL", "http://127.0.0.1:8000/v1");
|
|
246
348
|
const modelId = env("MODEL_ID", "Qwen3.5-35B");
|
|
349
|
+
// Posture gate: single-user turnkey (`REQUIRE_PRINCIPAL !== "true"` = one trusted super-admin on their own
|
|
350
|
+
// box/tenant, CC's trust model) ⇒ the CC capability set defaults ON, each composed with its infra prereq; multi-tenant
|
|
351
|
+
// (requirePrincipal) stays opt-in (gated + secured). This is the SAME discriminator LSP's host-default-ON uses,
|
|
352
|
+
// generalized into one posture switch (the PM's "not per-flag" ask). In `postureOn`, an explicit `X_ENABLED=true/false`
|
|
353
|
+
// ALWAYS wins; else the default = single-user ∧ infra-ready. 🔒 Multi-tenant (prod) is UNAFFECTED (postureOn → false).
|
|
354
|
+
// Parsed ONCE here (design/158 B4) instead of re-comparing `process.env.REQUIRE_PRINCIPAL` at the four sites
|
|
355
|
+
// that needed it — two of which spelled it `!== "true"` and two `=== "true"`, i.e. the same knob read in both
|
|
356
|
+
// polarities within one function. `boolEnv(x, false)` is value-identical to the old `=== "true"`.
|
|
247
357
|
const requirePrincipal = boolEnv("REQUIRE_PRINCIPAL", false);
|
|
248
358
|
const singleUserTurnkey = !requirePrincipal;
|
|
359
|
+
// `postureOn` takes the NAME (not the pre-read value) so the knob can register itself in the polarity table —
|
|
360
|
+
// the posture family is precisely the one whose default is NOT readable from its name, so leaving it out of the
|
|
361
|
+
// self-report would omit the worst offenders. Its tri-state semantics are unchanged: an explicit literal wins,
|
|
362
|
+
// anything else (including a typo — deliberately NOT warned here, unlike boolEnv) falls to the posture default.
|
|
249
363
|
const postureOn = (name, infraReady = true) => {
|
|
250
364
|
const envVal = process.env[name];
|
|
251
365
|
const explicit = envVal === "true" ? true : envVal === "false" ? false : undefined;
|
|
@@ -253,7 +367,24 @@ export function loadConfig() {
|
|
|
253
367
|
registerKnob({ name, polarity: "posture", value, source: explicit === undefined ? "posture" : "env" });
|
|
254
368
|
return value;
|
|
255
369
|
};
|
|
370
|
+
// `local` (clay 2026-06-25 seamless local↔cloud) = the DB-less in-memory/file StoreBackend, so a locally-run HTTP
|
|
371
|
+
// service serves the same contract a cloud worker does. It has no SQL host/coords (createStoreBackend builds it
|
|
372
|
+
// without tidb/pg), so it's always "reachable". (Parsed FIRST so the session/memory posture defaults below can
|
|
373
|
+
// key off the chosen engine.) Wording (clay 2026-07-06): canonical value `mysql` = any MySQL-protocol server
|
|
374
|
+
// (MySQL / TiDB / MariaDB); `tidb` accepted as a back-compat alias. TiDB stays fully supported and is the
|
|
375
|
+
// recommended MySQL-protocol engine when semantic memory is on (native VECTOR(dim) recall).
|
|
376
|
+
// clay 拍(2026-07-27,[1845] 桌面撞 501 案后):**裸 boot 默认 durable(local file 店)**。
|
|
377
|
+
// 旧默认「纯内存」让每个宿主(cli fixture/桌面/web BFF)都要手工记得 DB_BACKEND=local,漏设 =
|
|
378
|
+
// 「跑完的任务重启就丢 + durable runs 面 501」——单机是主流形,直觉预期是留得住。三态:
|
|
379
|
+
// - 未设 ⇒ local(单用户形;多租户例外见下)。默认推导的 local 在 main.ts 侧 mkdir/ensure 失败
|
|
380
|
+
// 时**降级 memory + warn + gauge**(auto-DB 不可达先例),显式 DB_BACKEND=local 才 fail-loud。
|
|
381
|
+
// - DB_BACKEND=memory ⇒ 显式纯内存 opt-out(CI/临时 server「不留盘上残余」的出口)。
|
|
382
|
+
// - 多租户裸 boot(REQUIRE_PRINCIPAL=true 且未设)⇒ 保持 memory + notice:local 与多租户互斥
|
|
383
|
+
// (main.ts 硬拒,owner map 无租户隔离),默认不得把多租户裸 boot 变成拒启。
|
|
256
384
|
const dbBackendSet = !!process.env.DB_BACKEND;
|
|
385
|
+
// 「裸 boot」判定必须**无任何 SQL 信号**才成立——SESSION_BACKEND=mysql/auto 或 TIDB/MYSQL/PG coords
|
|
386
|
+
// 在场时,dbBackend 的旧默认 "mysql" 是**引擎选择器**(auto 探测/coords 构建都 key 它),默认切
|
|
387
|
+
// local 会把这些既有形错切到文件店(回归钉在 config-coverage)。
|
|
257
388
|
const sqlSignal = (!!process.env.SESSION_BACKEND && process.env.SESSION_BACKEND !== "memory") ||
|
|
258
389
|
!!(process.env.TIDB_HOST || process.env.MYSQL_HOST || process.env.PG_HOST);
|
|
259
390
|
const bareBoot = !dbBackendSet && !sqlSignal;
|
|
@@ -268,21 +399,48 @@ export function loadConfig() {
|
|
|
268
399
|
},
|
|
269
400
|
});
|
|
270
401
|
}
|
|
402
|
+
// Asymmetry收编 (clay 2026-07-06, overseas-pilot pit #1): an EXPLICIT `DB_BACKEND=mysql/tidb/pg` is the operator
|
|
403
|
+
// asking for a durable SQL deployment — default sessions onto it (fail-fast, same as an explicit
|
|
404
|
+
// SESSION_BACKEND=mysql) instead of silently keeping the in-memory default ("DB_BACKEND=pg engaged nothing,
|
|
405
|
+
// session:memory, PG 0 tables"). DB_BACKEND=local keeps its own coercion in main.ts (it needs the built
|
|
406
|
+
// backend); an UNSET DB_BACKEND keeps the memory default (no surprise DB requirement on a bare boot).
|
|
407
|
+
// `mysql` is accepted as an alias of the internal durable label `tidb` (see ServiceConfig.sessionBackend).
|
|
271
408
|
const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
|
|
272
409
|
const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", ["memory", "mysql", "tidb", "auto"]);
|
|
273
410
|
const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
|
|
411
|
+
// design/138 S1 (clay 2026-07-08): long-term memory = the injection-first file-based memory ENGINE
|
|
412
|
+
// (core RunnerDeps.memoryBackend), default ON with MEMORY_ENGINE=off as the explicit kill-switch.
|
|
413
|
+
// The legacy MEMORY_BACKEND/EMBEDDING_* store plane was dropped without migration (those env vars are
|
|
414
|
+
// no longer read). Single-user gating lives in main.ts (the engine is never wired multi-tenant).
|
|
274
415
|
const memoryEngineEnabled = process.env.MEMORY_ENGINE !== "off";
|
|
416
|
+
// S3-TOB(边界重切后的 backend 选择,设计 §1.3):记忆持久面方言。缺省 file=现状零变化;
|
|
417
|
+
// pg/tidb=DB 真身(零卷主档)——多租户点亮的唯一门(显式 opt-in,复审 F5:绝不静默翻转)。
|
|
275
418
|
const memoryEngineBackendRaw = process.env.MEMORY_ENGINE_BACKEND ?? "file";
|
|
276
419
|
if (!["file", "pg", "tidb"].includes(memoryEngineBackendRaw)) {
|
|
277
420
|
throw new Error(`MEMORY_ENGINE_BACKEND must be file|pg|tidb, got "${memoryEngineBackendRaw}"`);
|
|
278
421
|
}
|
|
279
422
|
const memoryEngineBackend = memoryEngineBackendRaw;
|
|
423
|
+
// workflowSizeGuideline: CC normalizes unknown→unrestricted (lenient), but an env knob typo
|
|
424
|
+
// silently degrading to "no guidance" is the half-config trap — fail-loud here (memoryEngineBackend
|
|
425
|
+
// posture; the ADVISORY nature is core's concern, the env spelling is ours).
|
|
280
426
|
const workflowSizeGuidelineRaw = process.env.WORKFLOW_SIZE_GUIDELINE;
|
|
281
427
|
if (workflowSizeGuidelineRaw !== undefined && !["small", "medium", "large", "unrestricted"].includes(workflowSizeGuidelineRaw)) {
|
|
282
428
|
throw new Error(`WORKFLOW_SIZE_GUIDELINE must be small|medium|large|unrestricted, got "${workflowSizeGuidelineRaw}"`);
|
|
283
429
|
}
|
|
284
430
|
const workflowSizeGuideline = workflowSizeGuidelineRaw;
|
|
431
|
+
// Single-user memory scope (hoisted from the config literal so the sync-scope default below can consume it):
|
|
432
|
+
// explicit MEMORY_SCOPE always wins; default "local" only when the engine is on AND single-user (see the
|
|
433
|
+
// literal-site comment on why multi-tenant/engine-off must stay undefined = memory dark).
|
|
285
434
|
const memoryScope = process.env.MEMORY_SCOPE ?? (memoryEngineEnabled && !requirePrincipal ? "local" : undefined);
|
|
435
|
+
// 142-S2.5-W1: TOC 同步 client 腿的三键(file memory 形态专用)。MEMORY_SYNC_URL 设了=开;
|
|
436
|
+
// 缺省 undefined=纯本地现状零变化。半配置 fail-loud(memoryEngineBackend 同款姿势,绝不静默降级):
|
|
437
|
+
// - URL 有 TOKEN 无 ⇒ throw(中心同步面要 Bearer,缺 token 的“开了但永远 401”是半配置陷阱);
|
|
438
|
+
// - TOKEN/SCOPE 有 URL 无 ⇒ throw(operator 显然想开同步,静默不跑=同款陷阱,对称拒启);
|
|
439
|
+
// - DB backend(pg/tidb)配了任意同步键 ⇒ throw(中心侧自己就是 POST /v1/memory/sync/:scope 的权威,
|
|
440
|
+
// 不该再跑 client 腿——两个半场叠在一个进程是拓扑错误)。
|
|
441
|
+
// scope 缺省 = formatUserScope(memoryScope)(中心侧 owner 门的 user 盘键铸法);memoryScope 本身已是
|
|
442
|
+
// v2 typed key(user:/org:/proj:/userproj:,只可能来自 operator 显式 MEMORY_SCOPE)则原样直通——
|
|
443
|
+
// 再包一层会铸出 `user:user%3A...` 双包键(诚实偏离,报告记档)。
|
|
286
444
|
const memorySyncUrl = process.env.MEMORY_SYNC_URL || undefined;
|
|
287
445
|
const memorySyncToken = process.env.MEMORY_SYNC_TOKEN || undefined;
|
|
288
446
|
const memorySyncScopeRaw = process.env.MEMORY_SYNC_SCOPE || undefined;
|
|
@@ -306,11 +464,32 @@ export function loadConfig() {
|
|
|
306
464
|
}
|
|
307
465
|
memorySync = { url: memorySyncUrl, token: memorySyncToken, scope, ...((v) => (v !== undefined && Number.isInteger(v) && v >= 1 ? { maxPushEntries: v } : {}))(process.env.MEMORY_SYNC_MAX_PUSH_ENTRIES !== undefined ? Number(process.env.MEMORY_SYNC_MAX_PUSH_ENTRIES) : undefined), ...((v) => (v !== undefined && Number.isInteger(v) && v >= 1 ? { maxPullEntries: v } : {}))(process.env.MEMORY_SYNC_MAX_PULL_ENTRIES !== undefined ? Number(process.env.MEMORY_SYNC_MAX_PULL_ENTRIES) : undefined) };
|
|
308
466
|
}
|
|
467
|
+
// P0.5 variant-2: the file-backed `local` backend's data root. SAME resolution main.ts:102 (localRoot =
|
|
468
|
+
// CONFIG_LOCAL_DIR ?? AGENT_DATA_DIR ?? ~/.ai-agent) + run-local.ts:198, so the HTTP service + a run-local on one
|
|
469
|
+
// box open ONE boot-locked data dir. `LOCAL_DATA_ROOT` is an additional highest-priority override (the explicit
|
|
470
|
+
// "the local backend's data lives HERE" knob). Only the `local` backend consumes it (createStoreBackend), but it's
|
|
471
|
+
// always resolved (cheap, env-deterministic).
|
|
309
472
|
const localDataRoot = process.env.LOCAL_DATA_ROOT || process.env.CONFIG_LOCAL_DIR || process.env.AGENT_DATA_DIR || join(homedir(), ".ai-agent");
|
|
473
|
+
// `auto` builds the DB coords only when a host is actually configured — otherwise it stays memory with no
|
|
474
|
+
// probe. (Explicit mysql always builds them so a missing host fails loudly.) `needsDb` is engine-agnostic;
|
|
475
|
+
// the connection coords are built for whichever engine `dbBackend` selects (MYSQL_* preferred, TIDB_* alias).
|
|
310
476
|
const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!(process.env.MYSQL_HOST || process.env.TIDB_HOST);
|
|
311
477
|
const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
|
|
312
478
|
const needsDb = needsTidb;
|
|
313
|
-
|
|
479
|
+
// Model.api must reflect the brain that actually serves it (routing is by provider): an "anthropic"
|
|
480
|
+
// provider runs the Anthropic brain, whose usage reports `input` EXCLUDING cached tokens. core ≥1.22
|
|
481
|
+
// normalizes the cache-hit-rate denominator per api family — a wrong api makes promptTokens too small
|
|
482
|
+
// and cacheHitRate exceed 100%. (Brains select by provider, not api, so this only fixes accounting.)
|
|
483
|
+
//
|
|
484
|
+
// Default inference ([792]④, core ruling 2026-07-14): an EXPLICIT MODEL_PROVIDER always wins (unchanged).
|
|
485
|
+
// When it is unset but the operator explicitly configured an Anthropic-protocol base URL AND a matching
|
|
486
|
+
// credential (the EXACT same `firstSetEnv` predicates the `anthropic` route assembly below uses — F8:
|
|
487
|
+
// non-empty selection, so an EMPTY first alias can't shadow a set fallback), the default is "anthropic" +
|
|
488
|
+
// a one-line boot notice — a clean-machine user pointing ANTHROPIC_BASE_URL at an Anthropic-compatible
|
|
489
|
+
// upstream must not have the model silently routed to the (unconfigured) openai gateway. Base URL alone or
|
|
490
|
+
// credential alone does NOT infer (a bare ANTHROPIC_API_KEY historically only assembles the cloud route for
|
|
491
|
+
// registry-declared anthropic models); neither present ⇒ "gateway", byte-identical to before.
|
|
492
|
+
const explicitProvider = process.env.MODEL_PROVIDER; // env() semantics: "" = unset
|
|
314
493
|
const inferredAnthropicBaseUrl = firstSetEnv("ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL");
|
|
315
494
|
const inferredAnthropicCred = firstSetEnv("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN");
|
|
316
495
|
const provider = (() => {
|
|
@@ -329,6 +508,12 @@ export function loadConfig() {
|
|
|
329
508
|
}
|
|
330
509
|
return "gateway";
|
|
331
510
|
})();
|
|
511
|
+
// Generation-tuning passthrough (core 1.60 `Model.extraBody`, merged via applyExtraBody — brain-owned
|
|
512
|
+
// keys like temperature/max_tokens ALWAYS win, core warns on collision; unset → request unchanged).
|
|
513
|
+
// frequency/presence penalty are the common case (suppress degenerate looping at the source — the
|
|
514
|
+
// prevention to core's degenerate-repetition safety net); MODEL_EXTRA_BODY is the JSON escape hatch
|
|
515
|
+
// (top_k, logit_bias, …). MUST be STATIC (stable key order per cache namespace, else the prefix cache
|
|
516
|
+
// breaks — design/9/31), so it's built once here from fixed env, never per-task.
|
|
332
517
|
const extraBody = buildExtraBody();
|
|
333
518
|
const thinkingDefault = (() => { const v = process.env.MODEL_DEFAULT_THINKING; return v && isThinkingLevel(v) && v !== "off" ? v : undefined; })();
|
|
334
519
|
const effortLevels = (process.env.MODEL_REASONING_EFFORT_LEVELS ?? "").split(",").map((s) => s.trim()).filter((s) => isThinkingLevel(s) && s !== "off");
|
|
@@ -337,9 +522,23 @@ export function loadConfig() {
|
|
|
337
522
|
name: modelId,
|
|
338
523
|
api: provider === "anthropic" ? "anthropic-messages" : "openai-completions",
|
|
339
524
|
provider,
|
|
525
|
+
// Empty so the URL is owned by the BRAIN layer: each composed brain (gateway / failover fallback /
|
|
526
|
+
// Anthropic route) supplies its own baseUrl, and the brain resolves `model.baseUrl || config.baseUrl`.
|
|
527
|
+
// A non-empty value here would override every brain to one URL — breaking failover AND the Anthropic
|
|
528
|
+
// route (it would POST to the vLLM gateway). See design/15.
|
|
340
529
|
baseUrl: "",
|
|
341
530
|
reasoning: env("MODEL_REASONING", "true") === "true",
|
|
531
|
+
// vision precheck: the modality list drives BOTH the `model_no_vision` 422 (main.ts resolveSpec,
|
|
532
|
+
// when body.images hits a text-only model) AND the vision flag on GET /v1/models. The env-lane default model
|
|
533
|
+
// declares vision via MODEL_VISION — DEFAULT "true" keeps the historical image-capable behavior, so set
|
|
534
|
+
// MODEL_VISION=false for a text-only model (e.g. deepseek) to make the precheck actually fire instead of letting
|
|
535
|
+
// images透传 to an opaque downstream-gateway 400. (The centralized sema-registry lane derives input from its own
|
|
536
|
+
// per-model vision flag — see sema-registry.ts.)
|
|
342
537
|
input: env("MODEL_VISION", "true") === "true" ? ["text", "image"] : ["text"],
|
|
538
|
+
// Per-1M-token USD pricing (core `modelCostToPricing`: input→inputPer1M, …). Default 0 keeps the
|
|
539
|
+
// historical behavior (spend reads $0) — set MODEL_COST_* on a deploy to make `model_cost_micro_usd`
|
|
540
|
+
// + the Fleet card's costUsd real. Numbers are the gateway's contract price (e.g. the review gateway's
|
|
541
|
+
// deepseek-pro), owned by the operator — the center never stores them (design/15: secrets+billing in env).
|
|
343
542
|
cost: {
|
|
344
543
|
input: Number(env("MODEL_COST_INPUT", "0")),
|
|
345
544
|
output: Number(env("MODEL_COST_OUTPUT", "0")),
|
|
@@ -347,73 +546,139 @@ export function loadConfig() {
|
|
|
347
546
|
cacheWrite: Number(env("MODEL_COST_CACHE_WRITE", "0")),
|
|
348
547
|
},
|
|
349
548
|
contextWindow: Number(env("MODEL_CONTEXT_WINDOW", "262144")),
|
|
549
|
+
// [854]③a: env 车道缺省 4096 → 32000。4096 对 reasoning 模型太小(一次完整思考+答案都放不下,长输出
|
|
550
|
+
// 任务被腰斩;上游同类缺省 32000)。per-model clamp:缺省值不越过本 entry 自己声明的 contextWindow
|
|
551
|
+
// (操作员配了小窗口模型时,缺省输出上限不得超过窗口本身 —— env 车道 model entry 就在此装配,唯一可用
|
|
552
|
+
// 约束即 MODEL_CONTEXT_WINDOW)。显式 MODEL_MAX_TOKENS 一如既往原样生效(显式配置=操作员意图,不 clamp);
|
|
553
|
+
// catalog(config.d/models.json)与 center 车道不动(那是显式配置,BL-8 继承链读的是这里的最终值)。
|
|
350
554
|
maxTokens: process.env.MODEL_MAX_TOKENS !== undefined && process.env.MODEL_MAX_TOKENS !== ""
|
|
351
555
|
? Number(env("MODEL_MAX_TOKENS"))
|
|
352
556
|
: Math.min(32000, Number(env("MODEL_CONTEXT_WINDOW", "262144"))),
|
|
557
|
+
// design/123 D2 (core 1.226): structural token-estimate coefficient (chars ≈ 1 token) feeding every
|
|
558
|
+
// non-usage-anchored estimate in core (compaction trigger fallback, clearStale/trim defenses, cut-point
|
|
559
|
+
// accounting). UNSET → omit = core default 4 (byte-compatible chars/4 heuristic). CJK-heavy models
|
|
560
|
+
// (deepseek on Chinese tasks) should set 2–3 — chars/4 underestimates Chinese by 60–75%, letting the
|
|
561
|
+
// request-layer defenses pass over-window requests. optFinitePositiveEnv: a typo'd value degrades to
|
|
562
|
+
// the safe core default rather than zeroing every structural estimate (soft knob, no fail-loud boot).
|
|
353
563
|
...(optFinitePositiveEnv("MODEL_CHARS_PER_TOKEN") !== undefined ? { charsPerToken: optFinitePositiveEnv("MODEL_CHARS_PER_TOKEN") } : {}),
|
|
564
|
+
// Cache family = the BRAIN's usage semantics (design/18): the Anthropic brain reports `input`
|
|
565
|
+
// EXCLUDING cached, the openai gateway brain INCLUDES it. Declare it EXPLICITLY (core 1.23
|
|
566
|
+
// `model.params.promptCacheFamily`) instead of leaving core to INFER it from `model.api` — cleaner,
|
|
567
|
+
// and `api` stays free to mean only "wire format". core warns + clamps if it's wrong (1.23).
|
|
354
568
|
params: { promptCacheFamily: provider === "anthropic" ? "input-excludes-cached" : "input-includes-cached" },
|
|
569
|
+
// design/111 L2②: defaultThinking = this model's DEFAULT tier (resolution chain `spec.thinking ?? role.thinking ??
|
|
570
|
+
// model.defaultThinking`, prepare-task). reasoningEffortLevels (openai endpoints only — Anthropic budget honors the
|
|
571
|
+
// full range) DECLARES which tiers the endpoint accepts so a higher requested tier clamps DOWN instead of 422'ing.
|
|
355
572
|
...(thinkingDefault ? { defaultThinking: thinkingDefault } : {}),
|
|
356
573
|
...(effortLevels.length > 0 && provider !== "anthropic" ? { compat: { reasoningEffortLevels: effortLevels } } : {}),
|
|
357
574
|
...(extraBody ? { extraBody } : {}),
|
|
358
575
|
};
|
|
576
|
+
// 1.24 role map: a catalog + role→model so subsystems resolve a ROLE (cost tiering) instead of a hard
|
|
577
|
+
// model name. An optional cheap model on the SAME gateway (MODEL_CHEAP_ID — just a different id) serves
|
|
578
|
+
// the `subagent`+`summarize` roles, so council lenses & compaction run cheap while the lead/arbiter/team
|
|
579
|
+
// stay on the main model. Unset MODEL_CHEAP_ID → every role resolves to the main model (unchanged).
|
|
359
580
|
const cheapId = process.env.MODEL_CHEAP_ID;
|
|
360
581
|
const cheapModel = cheapId
|
|
361
582
|
? {
|
|
362
583
|
...model,
|
|
363
584
|
id: cheapId,
|
|
364
585
|
name: cheapId,
|
|
586
|
+
// vision precheck (adversarial-review finding): the cheap model must declare its OWN vision
|
|
587
|
+
// capability. Spreading `...model` inherits the MAIN model's `input` modality, so a vision-capable main +
|
|
588
|
+
// text-only cheap (e.g. MODEL_CHEAP_ID=deepseek for subagent/summarize) would advertise vision on /v1/models
|
|
589
|
+
// AND defeat the precheck (config.models[cheapId] is user-pickable via body.model) — the SAME dead-code-on-a-lane
|
|
590
|
+
// class clay caught on the env main model. MODEL_CHEAP_VISION declares it (strict "true"); UNSET → inherit the
|
|
591
|
+
// main model's input (prior behavior, no change — the documented "same gateway, just a different id" case).
|
|
365
592
|
input: process.env.MODEL_CHEAP_VISION === undefined
|
|
366
593
|
? model.input
|
|
367
594
|
: process.env.MODEL_CHEAP_VISION === "true"
|
|
368
595
|
? ["text", "image"]
|
|
369
596
|
: ["text"],
|
|
597
|
+
// Own pricing: pricing is keyed by model id (budget.buildPricing), so without this the cheap model
|
|
598
|
+
// inherits the MAIN model's `cost` and subagent/summarize spend (council lenses, compaction) is
|
|
599
|
+
// metered at the PRO per-token price → cost metric + per-principal quota over-count. Set
|
|
600
|
+
// MODEL_CHEAP_COST_* to the real cheap price; unset → inherit the main cost (prior behavior, no change).
|
|
370
601
|
cost: {
|
|
371
602
|
input: Number(process.env.MODEL_CHEAP_COST_INPUT ?? model.cost.input),
|
|
372
603
|
output: Number(process.env.MODEL_CHEAP_COST_OUTPUT ?? model.cost.output),
|
|
373
604
|
cacheRead: Number(process.env.MODEL_CHEAP_COST_CACHE_READ ?? model.cost.cacheRead),
|
|
374
605
|
cacheWrite: Number(process.env.MODEL_CHEAP_COST_CACHE_WRITE ?? model.cost.cacheWrite),
|
|
375
606
|
},
|
|
607
|
+
// systematic-audit: the cheap model also inherited the MAIN model's contextWindow/maxTokens/reasoning verbatim
|
|
608
|
+
// via the `...model` spread — wrong if MODEL_CHEAP_ID points at a model with different limits/capability. Declare
|
|
609
|
+
// them per cheap (MODEL_CHEAP_*); UNSET → inherit main (the documented "same gateway, just a different id" case).
|
|
376
610
|
contextWindow: process.env.MODEL_CHEAP_CONTEXT_WINDOW ? Number(process.env.MODEL_CHEAP_CONTEXT_WINDOW) : model.contextWindow,
|
|
377
611
|
maxTokens: process.env.MODEL_CHEAP_MAX_TOKENS ? Number(process.env.MODEL_CHEAP_MAX_TOKENS) : model.maxTokens,
|
|
612
|
+
// design/123 D2: charsPerToken per cheap slot — the spread inherits the MAIN model's coefficient,
|
|
613
|
+
// wrong when the cheap slot is a different tokenizer family (e.g. CJK-dense deepseek cheap under an
|
|
614
|
+
// anthropic main). MODEL_CHEAP_CHARS_PER_TOKEN overrides; UNSET → inherit main (same knob family).
|
|
378
615
|
...(optFinitePositiveEnv("MODEL_CHEAP_CHARS_PER_TOKEN") !== undefined ? { charsPerToken: optFinitePositiveEnv("MODEL_CHEAP_CHARS_PER_TOKEN") } : {}),
|
|
379
616
|
reasoning: process.env.MODEL_CHEAP_REASONING === undefined ? model.reasoning : process.env.MODEL_CHEAP_REASONING === "true",
|
|
380
617
|
}
|
|
381
618
|
: undefined;
|
|
619
|
+
// [818]① 1M dual-window (core 1.289 `Model.autoCompactTokens`, additive): a 1M-window model gets
|
|
620
|
+
// autoCompactTokens=967000 (CC's 1M special-case = the autocompact window is 1M-33000; core then triggers at
|
|
621
|
+
// 967k-33k=934k with the guard on the PHYSICAL window — the exact CC alignment core recommends embedders set).
|
|
622
|
+
// Static catalog audit for this batch: ZERO baked 1M entries exist (env lane defaults 262144; center lane
|
|
623
|
+
// inherits/declares per roster) — so this is a DERIVATION on the declared window, not a data edit: any
|
|
624
|
+
// deployment that sets MODEL_CONTEXT_WINDOW>=1e6 (or a center roster with a 1M contextWindow —
|
|
625
|
+
// sema-registry.ts applies the same helper) gets the field automatically. <1M ⇒ field ABSENT (core's plain
|
|
626
|
+
// W-33000/0.7W geometry already matches CC there).
|
|
382
627
|
applyAutoCompactWindow(model);
|
|
383
628
|
if (cheapModel)
|
|
384
|
-
applyAutoCompactWindow(cheapModel);
|
|
629
|
+
applyAutoCompactWindow(cheapModel); // the ...model spread must not leak the MAIN window's field onto a smaller cheap window
|
|
385
630
|
const models = { default: model, [model.id]: model };
|
|
386
631
|
if (cheapModel)
|
|
387
632
|
models[cheapModel.id] = cheapModel;
|
|
388
633
|
const cheapRef = cheapModel ? cheapModel.id : "default";
|
|
634
|
+
// M1(接入审计 2026-07-08):core verify 契约(verify.d.ts)把「verifier 必须异于 implementer」定为
|
|
635
|
+
// 🔴 部署契约——verifier 落 default(=implementer)时对抗验证者自评自批,去相关性名存实亡。
|
|
636
|
+
// `MODEL_VERIFIER=<catalog 模型 id>` 真兑现旧注释的 "operator can override"(此前只有注释没有机制);
|
|
637
|
+
// 值必须命中 catalog,typo 回落 default 并由 main.ts boot warn 曝光(连同"单模型部署未配 verifier"一起)。
|
|
389
638
|
const verifierEnv = process.env.MODEL_VERIFIER;
|
|
390
639
|
const verifierRef = verifierEnv && models[verifierEnv] ? verifierEnv : "default";
|
|
640
|
+
// rosterMarkerRoles 收敛(0.7 worker.model 真删同车):role→tier 的名单语义
|
|
641
|
+
// 单一来源 = registry-core 的 roster 标记常量(0.6.3 契约:ROSTER_PRIMARY_ROLES=["default","team","synthesize"],
|
|
642
|
+
// ROSTER_CHEAP_ROLES=["subagent","summarize"])——service 不再自带第二份「哪些 role 吃 cheap/primary」清单
|
|
643
|
+
// (双源并存期空转结束)。值仍指我方 catalog ref(cheap 槽未配 ⇒ cheapRef="default" 全落主模型,行为不变);
|
|
644
|
+
// `verifier` 是 service 专有 role(core verify 契约),不在 roster 标记语义内,保留显式。
|
|
391
645
|
const roles = {
|
|
392
|
-
verifier: verifierRef,
|
|
646
|
+
verifier: verifierRef, // adversarial verification gate (1.44) — MODEL_VERIFIER 指异构模型,未设=default(见 boot warn)
|
|
393
647
|
};
|
|
394
648
|
for (const r of ROSTER_PRIMARY_ROLES)
|
|
395
649
|
roles[r] = "default";
|
|
396
650
|
for (const r of ROSTER_CHEAP_ROLES)
|
|
397
651
|
roles[r] = cheapRef;
|
|
652
|
+
// Developer-mode role preset (core 1.43, opt-in, default-neutral): attach the de-branded
|
|
653
|
+
// CODE_AGENT_PROMPT to the named roles via RoleSpec.systemPrompt — those roles run a coding prompt
|
|
654
|
+
// (when the task doesn't supply its own systemPrompt) while every other role keeps the neutral
|
|
655
|
+
// default. `MODEL_CODE_ROLES` unset → no change. e.g. MODEL_CODE_ROLES=default,subagent.
|
|
398
656
|
for (const r of csv("MODEL_CODE_ROLES")) {
|
|
399
657
|
const role = r;
|
|
400
658
|
const cur = roles[role];
|
|
401
659
|
if (cur === undefined)
|
|
402
|
-
continue;
|
|
660
|
+
continue; // only decorate roles we actually define
|
|
661
|
+
// our role targets are model-name strings; keep an object target's fields if config ever sets one.
|
|
403
662
|
roles[role] =
|
|
404
663
|
typeof cur === "string"
|
|
405
664
|
? { model: cur, systemPrompt: CODE_AGENT_PROMPT }
|
|
406
665
|
: { ...cur, systemPrompt: CODE_AGENT_PROMPT };
|
|
407
666
|
}
|
|
667
|
+
// design/80 D-G: the direct-connect approval door anchors (all must be present to ACTIVATE; fail-closed).
|
|
408
668
|
const directApprovalDoor = boolEnv("DIRECT_APPROVAL_DOOR", false);
|
|
409
669
|
const principalJwtPubkeys = parsePrincipalJwks(process.env.PRINCIPAL_JWT_PUBKEYS);
|
|
410
670
|
const principalJwtIss = process.env.PRINCIPAL_JWT_ISS || undefined;
|
|
411
671
|
const principalJwtAud = process.env.PRINCIPAL_JWT_AUD || undefined;
|
|
412
|
-
const principalJwtMaxTtlSec = Math.max(0, numEnv("PRINCIPAL_JWT_MAX_TTL_SEC", "120"));
|
|
672
|
+
const principalJwtMaxTtlSec = Math.max(0, numEnv("PRINCIPAL_JWT_MAX_TTL_SEC", "120")); // 0 = no cap; default ≤120s
|
|
413
673
|
const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS);
|
|
414
674
|
const dgOperators = csv("OPERATOR_PRINCIPALS");
|
|
675
|
+
// 🔴 the direct-door crypto gate lives ONLY in the durable (checkpointStore) /decide branch — so DURABLE_APPROVAL
|
|
676
|
+
// is a HARD precondition. Without it, checkpointStore is undefined, that branch is skipped, and the LEGACY
|
|
677
|
+
// approvalStore /decide (trusted-header, NO crypto) becomes the live path = a full bypass (adversarial CRITICAL).
|
|
415
678
|
const dgDurable = boolEnv("DURABLE_APPROVAL", false);
|
|
416
679
|
const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
|
|
680
|
+
// 🔴 boot invariant (D-G §3): a half-configured direct door is the WORST state (looks on, verifies nothing) — and
|
|
681
|
+
// an empty OPERATOR_PRINCIPALS makes isOperator() true-for-all (= cross-tenant escalation). Refuse to start.
|
|
417
682
|
if (directApprovalDoor) {
|
|
418
683
|
const missing = [];
|
|
419
684
|
if (!dgDurable)
|
|
@@ -428,12 +693,17 @@ export function loadConfig() {
|
|
|
428
693
|
missing.push("PRINCIPAL_JWT_AUD");
|
|
429
694
|
if (dgOperators.length === 0)
|
|
430
695
|
missing.push("OPERATOR_PRINCIPALS (else isOperator is true-for-all)");
|
|
696
|
+
// 🔴 F-fix (dim-6): a direct door is inherently MULTI-TENANT (per-task verified JWT principal). Without
|
|
697
|
+
// REQUIRE_PRINCIPAL=true an absent/invalid principal falls through as ANONYMOUS and the owner/quota checks
|
|
698
|
+
// degrade to the spoofable header for null-owner rows — refuse a direct door that doesn't require a principal.
|
|
431
699
|
if (!requirePrincipal)
|
|
432
700
|
missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the spoofable header governs owner/quota checks)");
|
|
433
701
|
if (missing.length) {
|
|
434
702
|
throw new Error(`DIRECT_APPROVAL_DOOR=true but D-G anchors are missing: ${missing.join(", ")} — refusing to start a half-open direct door (design/80 D-G boot invariant)`);
|
|
435
703
|
}
|
|
436
704
|
}
|
|
705
|
+
// Per-system service credentials (token → system name). Parsed HERE (not just in the return object) so the
|
|
706
|
+
// bake boot invariant below can assert the runner-credential→runnerPrincipal mapping exists.
|
|
437
707
|
const authTokens = Object.fromEntries((process.env.SERVICE_AUTH_TOKENS ?? "")
|
|
438
708
|
.split(",")
|
|
439
709
|
.map((s) => s.trim())
|
|
@@ -441,16 +711,34 @@ export function loadConfig() {
|
|
|
441
711
|
.map((s) => [s.slice(0, s.indexOf("=")).trim(), s.slice(s.indexOf("=") + 1).trim()])
|
|
442
712
|
.filter(([tok, sys]) => tok.length > 0 && sys.length > 0));
|
|
443
713
|
const bakeRunnerPrincipal = env("BAKE_RUNNER_PRINCIPAL", "system:bake-runner");
|
|
714
|
+
// 🔴 boot invariant (IMAGE-API-DESIGN.md §P2.4a): the bake door is build-host-RCE-capable, and isOperator([],p)
|
|
715
|
+
// is true-for-all — so enabling bakes with an empty OPERATOR_PRINCIPALS = a WORLD-WRITABLE RCE door. Refuse to
|
|
716
|
+
// start (never a half-open bake door). The explicit-operator gate in server.ts is the runtime twin of this.
|
|
444
717
|
if (boolEnv("IMAGE_BAKES_ENABLED", false) && dgOperators.length === 0) {
|
|
445
718
|
throw new Error("IMAGE_BAKES_ENABLED=true but OPERATOR_PRINCIPALS is empty — the bake door runs build.sh on the privileged build host; an empty operator set makes isOperator() true-for-all (world-writable RCE). Set OPERATOR_PRINCIPALS or disable bakes (IMAGE-API-DESIGN.md §P2.4a boot invariant)");
|
|
446
719
|
}
|
|
720
|
+
// 🔴 boot invariant (IMAGE-API-DESIGN.md §P2.12 / H1): the bake-runner identity is DERIVED from its bearer
|
|
721
|
+
// credential (token-derived `source`), never a self-asserted header — so a SERVICE_AUTH_TOKENS entry whose
|
|
722
|
+
// system == BAKE_RUNNER_PRINCIPAL MUST exist, else the runner can never satisfy isRunner and the entire
|
|
723
|
+
// claim/ingest/heartbeat pipeline is silently dead. Refuse to start a bake door with no runner credential.
|
|
447
724
|
if (boolEnv("IMAGE_BAKES_ENABLED", false) && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
|
|
448
725
|
throw new Error(`IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${bakeRunnerPrincipal}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${bakeRunnerPrincipal}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`);
|
|
449
726
|
}
|
|
727
|
+
// ── design/158 B4 ② — the four knobs whose canonical name went from negative to positive. Only the first
|
|
728
|
+
// lands on `ServiceConfig`; the other three are read lazily at their use sites (main.ts's LKG gate,
|
|
729
|
+
// remote-env-host's capability probe and exec path) but are resolved HERE too so that their polarity row and
|
|
730
|
+
// any legacy-alias deprecation notice are emitted once, at boot, with every other config diagnostic — an
|
|
731
|
+
// operator must not have to trigger a background shell before being told the name they set is retired.
|
|
450
732
|
const projectMemoryEnabled = boolEnvWithLegacyNegated("PROJECT_MEMORY_ENABLED", "PROJECT_MEMORY_DISABLED", true);
|
|
451
733
|
configLkgEnabled();
|
|
452
734
|
hostBackgroundShellEnabled();
|
|
453
735
|
hostExecSpoolEnabled();
|
|
736
|
+
// ── design/158 B4 ③ — LSP: one knob per lane. `LSP_ENABLED` used to drive BOTH the sandbox lane (opt-in,
|
|
737
|
+
// default OFF — it needs a baked `sema-code-lsp` template) and the host lane (opt-out, default ON — it needs
|
|
738
|
+
// nothing and degrades to grep/read), i.e. one env name with two OPPOSITE defaults and no way to silence the
|
|
739
|
+
// host lane without also naming the sandbox knob. `LSP_HOST_ENABLED` now owns the host lane; an explicit
|
|
740
|
+
// `LSP_ENABLED=false` keeps working as a host opt-out (it was the only one operators ever had) with a notice
|
|
741
|
+
// naming the replacement, and `LSP_HOST_ENABLED` always wins when set.
|
|
454
742
|
const lspHostEnabled = (() => {
|
|
455
743
|
const explicitHost = process.env.LSP_HOST_ENABLED;
|
|
456
744
|
if (explicitHost === undefined || explicitHost === "") {
|
|
@@ -470,6 +758,12 @@ export function loadConfig() {
|
|
|
470
758
|
gatewayBaseUrl,
|
|
471
759
|
gatewayApiKey: process.env.MODEL_API_KEY,
|
|
472
760
|
gatewayFallbackUrls: csv("MODEL_GATEWAY_FALLBACK_URLS"),
|
|
761
|
+
// Anthropic-compatible-ecosystem env compat (2026-07-03): the route now assembles when EITHER credential
|
|
762
|
+
// is present — `ANTHROPIC_API_KEY` (x-api-key) or `ANTHROPIC_AUTH_TOKEN` (Authorization: Bearer; many such
|
|
763
|
+
// providers accept only this). AUTH_TOKEN wins when both are set. Base URL reads the widely-adopted
|
|
764
|
+
// standard name `ANTHROPIC_BASE_URL` too — the explicit sema name (no underscore) wins on a conflict.
|
|
765
|
+
// F8: alias selection via `firstSetEnv` ("" = unset) — the SAME helper the provider inference above
|
|
766
|
+
// consumes, so an empty first alias can neither suppress the route nor shadow the fallback base URL.
|
|
473
767
|
anthropic: firstSetEnv("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN")
|
|
474
768
|
? {
|
|
475
769
|
apiKey: firstSetEnv("ANTHROPIC_API_KEY"),
|
|
@@ -481,8 +775,16 @@ export function loadConfig() {
|
|
|
481
775
|
}
|
|
482
776
|
: undefined,
|
|
483
777
|
resilience: {
|
|
778
|
+
// BEHAVIOR CHANGE ([880] stall-watchdog audit): connect/firstToken default ON. core's idle
|
|
779
|
+
// timer only arms AFTER the first token (bumpIdle lives in sawContentToken), so with these
|
|
780
|
+
// two off a stream that opens but never emits hangs unbounded — the one true blind spot the
|
|
781
|
+
// fleet audit found. Values follow the [880] recommendation; =0 restores off.
|
|
484
782
|
connectTimeoutMs: Number(env("MODEL_CONNECT_TIMEOUT_MS", "30000")),
|
|
485
783
|
firstTokenTimeoutMs: Number(env("MODEL_FIRST_TOKEN_TIMEOUT_MS", "120000")),
|
|
784
|
+
// BEHAVIOR CHANGE (CC 196 parity): mid-stream stall watchdog default ON at 5min —
|
|
785
|
+
// after output starts, 5min of zero deltas cancels the stream as a retryable [network] failure
|
|
786
|
+
// (retry/failover recovers; without it a hung gateway pins the turn until the user Escapes).
|
|
787
|
+
// MODEL_IDLE_TIMEOUT_MS=0 restores off.
|
|
486
788
|
idleTimeoutMs: Number(env("MODEL_IDLE_TIMEOUT_MS", "300000")),
|
|
487
789
|
circuitBreaker: boolEnv("MODEL_CIRCUIT_BREAKER", false),
|
|
488
790
|
failureThreshold: Number(env("MODEL_CB_FAILURE_THRESHOLD", "5")),
|
|
@@ -490,21 +792,27 @@ export function loadConfig() {
|
|
|
490
792
|
},
|
|
491
793
|
model,
|
|
492
794
|
models,
|
|
493
|
-
modelApiKeyEnv: {},
|
|
494
|
-
modelApiKeys: {},
|
|
495
|
-
modelQuotaWeights: {},
|
|
496
|
-
tiers: {},
|
|
497
|
-
projects: {},
|
|
795
|
+
modelApiKeyEnv: {}, // env path has a single gateway key; sema-registry populates per-model overrides
|
|
796
|
+
modelApiKeys: {}, // sealed-box 解封产物(内存-only 明文):env lane 恒空;applyEffective 解封填充
|
|
797
|
+
modelQuotaWeights: {}, // weight-at-burn 表:env lane 恒空(weight=1 兜底);registry apply 填充
|
|
798
|
+
tiers: {}, // 档位表:env lane 恒空(INERT);applyEffective 从 models.tierGroups 解析填充(保引用)
|
|
799
|
+
projects: {}, // 142-S4 项目登记簿:env lane 恒空(INERT);applyEffective 从 projects 域填充(保引用)
|
|
498
800
|
roles,
|
|
499
801
|
sessionBackend,
|
|
500
802
|
sessionCacheTtlSec: Number(env("SESSION_CACHE_TTL_SEC", "300")),
|
|
501
|
-
rewindSnapshotMaxMb: optFinitePositiveEnv("REWIND_SNAPSHOT_MAX_MB"),
|
|
803
|
+
rewindSnapshotMaxMb: optFinitePositiveEnv("REWIND_SNAPSHOT_MAX_MB"), // soft knob (S20: bad value warns + default)
|
|
502
804
|
memoryEngineEnabled,
|
|
503
805
|
memoryEngineDir: process.env.MEMORY_ENGINE_DIR || undefined,
|
|
504
|
-
memoryEngineRemoteLaneAllowed: process.env.MEMORY_ENGINE_REMOTE_LANE === "allow",
|
|
806
|
+
memoryEngineRemoteLaneAllowed: process.env.MEMORY_ENGINE_REMOTE_LANE === "allow", // N0: fail-closed default
|
|
807
|
+
// A single-user deployment has no principal, so `memoryScopeFor` falls back to `config.memoryScope` — and if
|
|
808
|
+
// THAT were unset the resolved scope would be undefined ⇒ resolveSpec builds NO `spec.memory` ⇒ the memory
|
|
809
|
+
// engine never materializes (the "wired-not-triggered" gap a §4 real test hit on the legacy plane).
|
|
810
|
+
// Default it to "local" for single-user (REQUIRE_PRINCIPAL !== "true") so TOC local memory works out-of-the-box
|
|
811
|
+
// (CC-parity). Multi-tenant memory is DARK by design (design/138 S1: the file basement has no tenant isolation —
|
|
812
|
+
// memoryScopeFor returns undefined there). An explicit MEMORY_SCOPE always wins.
|
|
505
813
|
memoryEngineBackend,
|
|
506
814
|
...(workflowSizeGuideline ? { workflowSizeGuideline } : {}),
|
|
507
|
-
memoryScope,
|
|
815
|
+
memoryScope, // hoisted above (the 142-S2.5-W1 sync-scope default consumes it)
|
|
508
816
|
...(memorySync ? { memorySync } : {}),
|
|
509
817
|
remoteExec: process.env.REMOTE_EXEC === "e2b" && process.env.E2B_API_KEY
|
|
510
818
|
? {
|
|
@@ -512,26 +820,45 @@ export function loadConfig() {
|
|
|
512
820
|
apiKey: process.env.E2B_API_KEY,
|
|
513
821
|
...(process.env.E2B_TEMPLATE ? { template: process.env.E2B_TEMPLATE } : {}),
|
|
514
822
|
...(process.env.E2B_TIMEOUT_MS ? { timeoutMs: Number(process.env.E2B_TIMEOUT_MS) } : {}),
|
|
823
|
+
// Idle/liveness bound for a command (default 120s, the hang-fix). A long SILENT build —
|
|
824
|
+
// gradle's first dependency download produces no output for minutes (gate#2 P1 finding) — looks
|
|
825
|
+
// like a hang and gets killed; raise this for workers that run such tools. Agents should still
|
|
826
|
+
// prefer background+poll, but this is the operator escape hatch.
|
|
515
827
|
...(process.env.E2B_LIVENESS_MS ? { livenessMs: Number(process.env.E2B_LIVENESS_MS) } : {}),
|
|
516
828
|
...(process.env.E2B_ALLOW_NET ? { allowInternetAccess: boolEnv("E2B_ALLOW_NET", true) } : {}),
|
|
829
|
+
// Out-of-band secret/config env injected sandbox-wide (OA Phase-B, design/48 §5 gap 7):
|
|
830
|
+
// E2B_SANDBOX_ENV is a CSV of THIS worker's OWN env-var NAMES to forward into the sandbox under the
|
|
831
|
+
// same name (name-only config keeps the secret in the worker's env, never in the spec/center). e.g.
|
|
832
|
+
// `E2B_SANDBOX_ENV=GIT_TOKEN` → the agent's git can auth via $GIT_TOKEN (GIT_ASKPASS / credential
|
|
833
|
+
// helper) without the token ever entering the prompt, the command string, or the durable event log.
|
|
517
834
|
...(process.env.E2B_SANDBOX_ENV ? { sandboxEnv: resolveEnvForward(process.env.E2B_SANDBOX_ENV) } : {}),
|
|
835
|
+
// [1452]/[1454] CWD-A:workspace 根显式可配(其余四车道 *_MOUNT_PATH 早有;e2b 此前配置层
|
|
836
|
+
// 不可达,恒 /home/user)。exec/spawnBackground/fs 三腿同源消费(remote-env-e2b this.cwd)。
|
|
837
|
+
// ⚠️ 变更纪律(codex 终审 high):resume 契约只回传 snapshotId,恢复的沙箱以**当前**配置值为
|
|
838
|
+
// cwd——改此值前先 drain 挂起中的 e2b 任务,否则 resumed 任务的 exec 落新根而产物在旧根
|
|
839
|
+
// (根修=core resumeVM 契约带回 WorkspaceHandle.mountPath,已转 core)。
|
|
518
840
|
...(process.env.E2B_MOUNT_PATH ? { mountPath: process.env.E2B_MOUNT_PATH } : {}),
|
|
519
841
|
}
|
|
520
|
-
:
|
|
842
|
+
: // Kata pod sandbox on our k3s: image required; auth explicit (K8S_API_URL/K8S_TOKEN) or in-cluster SA.
|
|
521
843
|
process.env.REMOTE_EXEC === "k8s" && process.env.K8S_SANDBOX_IMAGE
|
|
522
844
|
? {
|
|
523
845
|
provider: "k8s",
|
|
524
846
|
image: process.env.K8S_SANDBOX_IMAGE,
|
|
525
847
|
...(process.env.K8S_API_URL ? { apiUrl: process.env.K8S_API_URL } : {}),
|
|
526
848
|
...(process.env.K8S_TOKEN ? { token: process.env.K8S_TOKEN } : {}),
|
|
849
|
+
// base64 to survive sema-registry's no-newline secrets API (same constraint as SSH_PRIVATE_KEY).
|
|
527
850
|
...(process.env.K8S_CA_CERT_B64 ? { caCert: Buffer.from(process.env.K8S_CA_CERT_B64, "base64").toString("utf8") } : {}),
|
|
528
851
|
...(boolEnv("K8S_INSECURE_TLS", false) ? { insecureTls: true } : {}),
|
|
529
852
|
...(process.env.K8S_NAMESPACE ? { namespace: process.env.K8S_NAMESPACE } : {}),
|
|
530
853
|
...(process.env.K8S_RUNTIME_CLASS != null ? { runtimeClass: process.env.K8S_RUNTIME_CLASS } : {}),
|
|
531
854
|
...(process.env.K8S_MOUNT_PATH ? { mountPath: process.env.K8S_MOUNT_PATH } : {}),
|
|
532
855
|
...(process.env.K8S_TIMEOUT_MS ? { timeoutMs: Number(process.env.K8S_TIMEOUT_MS) } : {}),
|
|
856
|
+
// Resource profile knob (resources are profile data, not mechanism — center/service
|
|
857
|
+
// config, never wire-hardcoded). Found live: 25-module gradle integration OOM'd the 2Gi default.
|
|
533
858
|
...(process.env.K8S_MEMORY ? { memory: process.env.K8S_MEMORY } : {}),
|
|
534
859
|
...(process.env.K8S_CPU ? { cpu: process.env.K8S_CPU } : {}),
|
|
860
|
+
// Workspace-durable suspend/resume: adapter-held MinIO creds (secret minio-agent-worker);
|
|
861
|
+
// the sandbox only ever sees a presigned URL. All three keys required to enable — else suspendable:false.
|
|
535
862
|
...(process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
536
863
|
? {
|
|
537
864
|
s3Snapshot: {
|
|
@@ -546,7 +873,7 @@ export function loadConfig() {
|
|
|
546
873
|
}
|
|
547
874
|
: {}),
|
|
548
875
|
}
|
|
549
|
-
:
|
|
876
|
+
: // design/61: SSH → a real host. Key from the adapter-held secret env SSH_PRIVATE_KEY (never the workspace).
|
|
550
877
|
process.env.REMOTE_EXEC === "ssh" && process.env.SSH_HOST && process.env.SSH_USER && process.env.SSH_PRIVATE_KEY
|
|
551
878
|
? {
|
|
552
879
|
provider: "ssh",
|
|
@@ -556,7 +883,7 @@ export function loadConfig() {
|
|
|
556
883
|
...(process.env.SSH_PORT ? { port: Number(process.env.SSH_PORT) } : {}),
|
|
557
884
|
...(process.env.SSH_MOUNT_PATH ? { mountPath: process.env.SSH_MOUNT_PATH } : {}),
|
|
558
885
|
}
|
|
559
|
-
:
|
|
886
|
+
: // design/61: ADB → a real device (serial or host:port). adb binary must be on PATH in the image.
|
|
560
887
|
process.env.REMOTE_EXEC === "adb" && process.env.ADB_SERIAL
|
|
561
888
|
? {
|
|
562
889
|
provider: "adb",
|
|
@@ -564,14 +891,18 @@ export function loadConfig() {
|
|
|
564
891
|
...(process.env.ADB_PATH ? { adbPath: process.env.ADB_PATH } : {}),
|
|
565
892
|
...(process.env.ADB_MOUNT_PATH ? { mountPath: process.env.ADB_MOUNT_PATH } : {}),
|
|
566
893
|
}
|
|
567
|
-
:
|
|
894
|
+
: // DUAL-MODE §5: `host` = run on THIS machine, no container (the TOC default). No required env.
|
|
568
895
|
process.env.REMOTE_EXEC === "host"
|
|
569
896
|
? {
|
|
570
897
|
provider: "host",
|
|
571
898
|
...(process.env.HOST_WORKSPACE_BASE ? { workspaceBase: process.env.HOST_WORKSPACE_BASE } : {}),
|
|
572
899
|
...(process.env.HOST_COMMAND_TIMEOUT_MS ? { commandTimeoutMs: Number(process.env.HOST_COMMAND_TIMEOUT_MS) } : {}),
|
|
573
900
|
}
|
|
574
|
-
:
|
|
901
|
+
: // DUAL-MODE §5: `local-docker` = a per-task container on THIS machine's docker daemon (isolation:true,
|
|
902
|
+
// suspendable:false). DOCKER_IMAGE is REQUIRED — no docker.io default (domestic-images iron rule); a
|
|
903
|
+
// deployment points it at the docker.io/claybobby public pool or an in-network mirror (SWR
|
|
904
|
+
// deprecated). Secrets are env-NAMEs the worker resolves from its
|
|
905
|
+
// own process.env (DOCKER_SANDBOX_ENV, a CSV of NAMEs) — never the values in the spec/center.
|
|
575
906
|
process.env.REMOTE_EXEC === "local-docker" && process.env.DOCKER_IMAGE
|
|
576
907
|
? {
|
|
577
908
|
provider: "local-docker",
|
|
@@ -589,10 +920,17 @@ export function loadConfig() {
|
|
|
589
920
|
...(process.env.DOCKER_COMMAND_TIMEOUT_MS ? { commandTimeoutMs: Number(process.env.DOCKER_COMMAND_TIMEOUT_MS) } : {}),
|
|
590
921
|
...(process.env.DOCKER_SANDBOX_ENV ? { env: resolveEnvForward(process.env.DOCKER_SANDBOX_ENV) } : {}),
|
|
591
922
|
}
|
|
592
|
-
:
|
|
923
|
+
: // TOC first-run default: in LOCAL config mode (CONFIG_PROVIDER=local — the
|
|
924
|
+
// single-machine source) with no explicit REMOTE_EXEC, default to the `host` lane so "fill .env
|
|
925
|
+
// and run" works without the user setting REMOTE_EXEC. Fleet/remote (CONFIG_PROVIDER≠local) keeps
|
|
926
|
+
// the in-process stub default (REMOTE_EXEC unset = OA/review posture) — unchanged.
|
|
593
927
|
process.env.CONFIG_PROVIDER === "local"
|
|
594
928
|
? { provider: "host" }
|
|
595
929
|
: undefined,
|
|
930
|
+
// SVC-3 git-worktree isolation: opt-in, operator-supplied ONLY. WORKTREE_REPO_ROOT is the trusted base
|
|
931
|
+
// repo; WORKTREE_ALLOWED_ROOTS (CSV) is the defence-in-depth allowlist (default = [repoRoot]);
|
|
932
|
+
// WORKTREE_BASE_COMMIT pins the worktree ref (default core HEAD). Enabled only when both the flag and the
|
|
933
|
+
// repo root are present — a half-set config stays OFF (no silent default repo).
|
|
596
934
|
worktreeIsolation: boolEnv("WORKTREE_ISOLATION_ENABLED", false) && process.env.WORKTREE_REPO_ROOT
|
|
597
935
|
? {
|
|
598
936
|
repoRoot: process.env.WORKTREE_REPO_ROOT,
|
|
@@ -608,6 +946,7 @@ export function loadConfig() {
|
|
|
608
946
|
tidb: needsDb && dbBackend === "mysql"
|
|
609
947
|
? {
|
|
610
948
|
host: env2("MYSQL_HOST", "TIDB_HOST"),
|
|
949
|
+
// default port follows the env family in use: MYSQL_HOST → 3306 (stock MySQL), TIDB_HOST → 4000 (TiDB)
|
|
611
950
|
port: Number(env2("MYSQL_PORT", "TIDB_PORT", process.env.MYSQL_HOST ? "3306" : "4000")),
|
|
612
951
|
user: env2("MYSQL_USER", "TIDB_USER"),
|
|
613
952
|
password: env2("MYSQL_PASSWORD", "TIDB_PASSWORD", ""),
|
|
@@ -627,7 +966,13 @@ export function loadConfig() {
|
|
|
627
966
|
connectionLimit: process.env.PG_POOL_SIZE ? Number(process.env.PG_POOL_SIZE) : undefined,
|
|
628
967
|
}
|
|
629
968
|
: undefined,
|
|
969
|
+
// S9 deeper fix: per-query DB timeout (see the interface doc). Soft knob — a typo degrades to the default
|
|
970
|
+
// posture (pool-wide off / counter-family 30s) with an S20 boot warning, never NaN into a driver timer.
|
|
630
971
|
dbQueryTimeoutMs: optFinitePositiveEnv("DB_QUERY_TIMEOUT_MS"),
|
|
972
|
+
// E19/2c snapshot-BLOB object-store offload (clay 2026-06-26). Reuses the SAME adapter-held MinIO creds as the k8s
|
|
973
|
+
// workspace-snapshot lane (secret minio-agent-worker); all three keys required to enable, else the file-snapshot
|
|
974
|
+
// stores keep the SQL `snapshot_blob` default (byte-identical to today). Bucket = SESSION_SNAPSHOT_BUCKET (default
|
|
975
|
+
// "session-snapshots"); keyPrefix optional (default "blobs/" in MinioBlobBackend).
|
|
631
976
|
snapshotBlobStore: process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
632
977
|
? {
|
|
633
978
|
endpoint: process.env.MINIO_ENDPOINT,
|
|
@@ -636,15 +981,25 @@ export function loadConfig() {
|
|
|
636
981
|
secretKey: process.env.MINIO_SECRET_KEY,
|
|
637
982
|
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
638
983
|
...(process.env.SESSION_SNAPSHOT_PREFIX ? { keyPrefix: process.env.SESSION_SNAPSHOT_PREFIX } : {}),
|
|
984
|
+
// Fail-SAFE parse (finding 20): a non-numeric/non-positive value → omit (the MinioBlobBackend default 3600s
|
|
985
|
+
// applies), never NaN (which would sign every URL with X-Amz-Expires=NaN → total snapshot outage).
|
|
639
986
|
...((ttl) => (ttl !== undefined ? { presignTtlSec: ttl } : {}))(optFinitePositiveEnv("SESSION_SNAPSHOT_TTL_SEC")),
|
|
640
987
|
}
|
|
641
988
|
: undefined,
|
|
989
|
+
// (a+c)(clay 2026-07-27)snapshot_blob 包墙修复两旋钮——语义见 ServiceConfig 字段注释。
|
|
642
990
|
snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
|
|
643
991
|
snapshotBlobAllowSql: boolEnv("SNAPSHOT_BLOB_ALLOW_SQL_BYTES", false),
|
|
992
|
+
// plugins 域 clone host 白名单(契约 [1361]①;缺省 github.com,逗号扩 https 镜像域)。
|
|
644
993
|
pluginsAllowHosts: (process.env.PLUGINS_ALLOW_HOSTS ?? "github.com").split(",").map((h) => h.trim()).filter(Boolean),
|
|
994
|
+
// 绑址([1934]):BIND_HOST 优先,HOST 兼容(cli/桌面传的就是它);空串=未设。
|
|
645
995
|
bindHost: process.env.BIND_HOST || process.env.HOST || undefined,
|
|
996
|
+
// D-1 孤儿对象 GC grace(复审 F10;0=关掉本腿,负/坏值走默认)。
|
|
646
997
|
attachmentOrphanGraceMs: ((v) => (v !== undefined && Number.isFinite(v) && v >= 0 ? v : 3_600_000))(process.env.ATTACHMENT_ORPHAN_GRACE_MS !== undefined ? Number(process.env.ATTACHMENT_ORPHAN_GRACE_MS) : undefined),
|
|
998
|
+
// workspace 浏览面(#3,[1894]①)单文件读上限。
|
|
647
999
|
workspaceFileMaxBytes: optFinitePositiveEnv("WORKSPACE_FILE_MAX_BYTES") ?? 8 * 1024 * 1024,
|
|
1000
|
+
// SendUserFile 双轨公网直链(同三键开闸;S3_ENDPOINT 兼容位=外接 S3 场景内网/公网同址)。
|
|
1001
|
+
// `||` 非 `??`(配置面自查 2026-07-14):env 惯例(env() helper 同款)空串=未设——`?? ` 会让
|
|
1002
|
+
// 一个显式置空的 MINIO_ENDPOINT/S3_PUBLIC_ENDPOINT 把后备名吞掉(compose `${X:-}` 透传空串是常态)。
|
|
648
1003
|
sendUserFile: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT) && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
649
1004
|
? {
|
|
650
1005
|
endpoint: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT),
|
|
@@ -658,6 +1013,7 @@ export function loadConfig() {
|
|
|
658
1013
|
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
659
1014
|
secretKey: process.env.MINIO_SECRET_KEY,
|
|
660
1015
|
...(process.env.MINIO_REGION ? { region: process.env.MINIO_REGION } : {}),
|
|
1016
|
+
// fail-SAFE:非法值回落 0(永久轨缺省),不 NaN 进签名。
|
|
661
1017
|
defaultTtlSec: ((v) => (v !== undefined && Number.isInteger(v) && v >= 0 ? v : 0))(process.env.SEND_USER_FILE_URL_TTL !== undefined ? Number(process.env.SEND_USER_FILE_URL_TTL) : undefined),
|
|
662
1018
|
}
|
|
663
1019
|
: undefined,
|
|
@@ -674,6 +1030,8 @@ export function loadConfig() {
|
|
|
674
1030
|
attachmentUnboundTtlMs: Math.max(60_000, numEnv("ATTACHMENT_UNBOUND_TTL_MS", String(24 * 3600 * 1000))),
|
|
675
1031
|
principalHeader: headerNameEnv("PRINCIPAL_HEADER", "x-agent-principal"),
|
|
676
1032
|
requirePrincipal,
|
|
1033
|
+
// Runtime governance second baton (center §10): AUTONOMY env for the env/local path; sema-registry overlays
|
|
1034
|
+
// it (applyRuntimeHot). commandPolicy is structured config — sema-registry-only (no flat env scalar form).
|
|
677
1035
|
autonomy: parseAutonomy(process.env.AUTONOMY),
|
|
678
1036
|
approvalRequire: csv("APPROVAL_REQUIRE"),
|
|
679
1037
|
operatorPrincipals: csv("OPERATOR_PRINCIPALS"),
|
|
@@ -682,10 +1040,10 @@ export function loadConfig() {
|
|
|
682
1040
|
approvalPollMs: Number(env("APPROVAL_POLL_MS", "1500")),
|
|
683
1041
|
durableApproval: boolEnv("DURABLE_APPROVAL", false),
|
|
684
1042
|
resourceSuspend: boolEnv("RESOURCE_SUSPEND", false),
|
|
685
|
-
resourceSuspendTtlSec: Math.max(0, numEnv("RESOURCE_SUSPEND_TTL_SEC", "0")),
|
|
686
|
-
approvalAutoBudget: Math.min(10000, Math.max(0, numEnv("APPROVAL_AUTO_BUDGET", "0"))),
|
|
1043
|
+
resourceSuspendTtlSec: Math.max(0, numEnv("RESOURCE_SUSPEND_TTL_SEC", "0")), // 0 ⇒ core default (30d)
|
|
1044
|
+
approvalAutoBudget: Math.min(10000, Math.max(0, numEnv("APPROVAL_AUTO_BUDGET", "0"))), // clamp [0,10000]; 0 = off
|
|
687
1045
|
approvalNeverAuto: csv("APPROVAL_NEVER_AUTO"),
|
|
688
|
-
approvalHmacKeys: dgHmacKeys,
|
|
1046
|
+
approvalHmacKeys: dgHmacKeys, // D-G: WIRED (see the field's JSDoc); empty ⇒ inactive, not unimplemented
|
|
689
1047
|
directApprovalDoor,
|
|
690
1048
|
principalJwtPubkeys,
|
|
691
1049
|
principalJwtIss,
|
|
@@ -699,33 +1057,39 @@ export function loadConfig() {
|
|
|
699
1057
|
},
|
|
700
1058
|
leaderEnabled: boolEnv("LEADER_ENABLED", false),
|
|
701
1059
|
leaderFanoutEnabled: boolEnv("LEADER_FANOUT_ENABLED", true),
|
|
702
|
-
routerEnabled: boolEnv("ROUTER_ENABLED", false),
|
|
703
|
-
selfOrchestrationEnabled: postureOn("SELF_ORCHESTRATION_ENABLED"),
|
|
704
|
-
forkEnabled: postureOn("FORK_ENABLED"),
|
|
705
|
-
experimentalObserverAgents: boolEnv("EXPERIMENTAL_OBSERVER_AGENTS", false),
|
|
706
|
-
schedulerEnabled: postureOn("SCHEDULER_ENABLED"),
|
|
707
|
-
projectMemoryEnabled,
|
|
708
|
-
planModeEnabled: boolEnv("PLAN_MODE_ENABLED", true),
|
|
1060
|
+
routerEnabled: boolEnv("ROUTER_ENABLED", false), // A value router; default OFF (ship dark, enable after live validation)
|
|
1061
|
+
selfOrchestrationEnabled: postureOn("SELF_ORCHESTRATION_ENABLED"), // posture: single-user turnkey → ON (CC local trust; sandboxed vm); multi-tenant opt-in (RCE-ish, gated)
|
|
1062
|
+
forkEnabled: postureOn("FORK_ENABLED"), // §4 CC /fork posture: single-user turnkey → default-ON for the LLM; multi-tenant honored only with a per-principal entitlement resolver (core enforces allowFork, registry-core 0.1.47; enableForkFromBody)
|
|
1063
|
+
experimentalObserverAgents: boolEnv("EXPERIMENTAL_OBSERVER_AGENTS", false), // observer 开闸线:显式 "true" 才开(实验面,core ships dark——非 postureOn);多租户只认 center caps,env 被忽略(applyObserverEnvOptIn boot warn)
|
|
1064
|
+
schedulerEnabled: postureOn("SCHEDULER_ENABLED"), // R7 自唤醒 posture: single-user turnkey → ON (CC self-wake); multi-tenant routes to center (opt-in)
|
|
1065
|
+
projectMemoryEnabled, // design/113 C4 opt-out, flipped positive in design/158 B4 (legacy PROJECT_MEMORY_DISABLED still honored)
|
|
1066
|
+
planModeEnabled: boolEnv("PLAN_MODE_ENABLED", true), // EnterPlanMode (core 1.167) model-driven plan; 🆕 default ON (resident — clay), PLAN_MODE_ENABLED=false opts out
|
|
709
1067
|
...(process.env.SCHEDULER_STORE_PATH ? { schedulerStorePath: process.env.SCHEDULER_STORE_PATH } : {}),
|
|
710
|
-
configBootFetchBudgetMs: Math.max(0, numEnv("CONFIG_BOOT_FETCH_BUDGET_MS", "1500")),
|
|
711
|
-
schedulerSessionWakeup: boolEnv("SCHEDULER_SESSION_WAKEUP", true),
|
|
1068
|
+
configBootFetchBudgetMs: Math.max(0, numEnv("CONFIG_BOOT_FETCH_BUDGET_MS", "1500")), // boot 首拉抢答窗;过窗转后台补齐(clay 2026-07-17:本地 5s 启动=黑洞中心同步等待)
|
|
1069
|
+
schedulerSessionWakeup: boolEnv("SCHEDULER_SESSION_WAKEUP", true), // [1009]② host-signal: default ON, the spawning shell opts a daemon-less engine out (PLAN_MODE_ENABLED 同形解析)
|
|
712
1070
|
selfOrchestrationModels: csv("SELF_ORCHESTRATION_MODELS"),
|
|
713
1071
|
selfOrchestrationWorkerIsolation: boolEnv("SELF_ORCHESTRATION_WORKER_ISOLATION", false),
|
|
714
|
-
workflowRunStoreBackend: enumEnv("WORKFLOW_RUN_STORE", "auto", ["auto", "file", "memory"]),
|
|
715
|
-
workflowOrphanGraceMs: Math.max(60_000, numEnv("WORKFLOW_ORPHAN_GRACE_MS", String(24 * 60 * 60 * 1000))),
|
|
716
|
-
workflowJournalRetentionMs: Math.max(60_000, numEnv("WORKFLOW_JOURNAL_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))),
|
|
717
|
-
workflowRunRetentionMs: Math.max(60_000, numEnv("WORKFLOW_RUN_RETENTION_MS", String(30 * 24 * 60 * 60 * 1000))),
|
|
718
|
-
backgroundAgentRetentionMs: Math.max(60_000, numEnv("BG_AGENT_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))),
|
|
719
|
-
backgroundAgentStaleRunningMs: Math.max(10 * 60_000, numEnv("BG_AGENT_STALE_RUNNING_MS", String(15 * 60_000))),
|
|
720
|
-
backgroundAgentParkClaimStaleMs: Math.max(60_000, numEnv("BG_AGENT_PARK_CLAIM_STALE_MS", String(60 * 60_000))),
|
|
721
|
-
rosterRetentionMs: Math.max(60_000, numEnv("ROSTER_RETENTION_MS", String(30 * 24 * 60 * 60 * 1000))),
|
|
1072
|
+
workflowRunStoreBackend: enumEnv("WORKFLOW_RUN_STORE", "auto", ["auto", "file", "memory"]), // SVC-1 + P1: auto = SQL backend when present (cross-replica), else File
|
|
1073
|
+
workflowOrphanGraceMs: Math.max(60_000, numEnv("WORKFLOW_ORPHAN_GRACE_MS", String(24 * 60 * 60 * 1000))), // SVC-1: 24h default, floor 1m
|
|
1074
|
+
workflowJournalRetentionMs: Math.max(60_000, numEnv("WORKFLOW_JOURNAL_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))), // SVC-2: 7d default, floor 1m
|
|
1075
|
+
workflowRunRetentionMs: Math.max(60_000, numEnv("WORKFLOW_RUN_RETENTION_MS", String(30 * 24 * 60 * 60 * 1000))), // 30d default, floor 1m; SQL twins only
|
|
1076
|
+
backgroundAgentRetentionMs: Math.max(60_000, numEnv("BG_AGENT_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))), // core 1.364: 7d default, floor 1m
|
|
1077
|
+
backgroundAgentStaleRunningMs: Math.max(10 * 60_000, numEnv("BG_AGENT_STALE_RUNNING_MS", String(15 * 60_000))), // core 契约 ≥10min 硬钳(60s 写者心跳)
|
|
1078
|
+
backgroundAgentParkClaimStaleMs: Math.max(60_000, numEnv("BG_AGENT_PARK_CLAIM_STALE_MS", String(60 * 60_000))), // [1575] F1 修:default 1h,floor 1m(零/负值都不该让整块清算失活)
|
|
1079
|
+
rosterRetentionMs: Math.max(60_000, numEnv("ROSTER_RETENTION_MS", String(30 * 24 * 60 * 60 * 1000))), // [1522] MED2: 30d default, floor 1m; SQL twins only
|
|
1080
|
+
// 修8: scratchpad sweep TTL — 0(or negative)= disabled; enabled values floor at 1m (a sub-minute TTL would
|
|
1081
|
+
// race in-flight tasks' scratchpad writes for no operational gain).
|
|
722
1082
|
scratchpadSweepTtlMs: ((n) => (n <= 0 ? 0 : Math.max(60_000, n)))(numEnv("SCRATCHPAD_SWEEP_TTL_MS", String(7 * 24 * 60 * 60 * 1000))),
|
|
1083
|
+
// RFC B5 region package-source selector — 🆕 default `global` (clay 拍 2026-07-13 海外为准:官方源 env-first
|
|
1084
|
+
// 对存量镜像即生效);国内部署显式 SANDBOX_PKG_SOURCE=cn;`none` = 完全不注入(旧 unset 行为,字节级不变)。
|
|
723
1085
|
...(process.env.SANDBOX_PKG_SOURCE !== "none"
|
|
724
1086
|
? { sandboxPkgSource: process.env.SANDBOX_PKG_SOURCE || "global" }
|
|
725
1087
|
: {}),
|
|
726
|
-
sessionAutoTitle: boolEnv("SESSION_AUTO_TITLE", true),
|
|
727
|
-
selectEnvironmentTool: boolEnv("SELECT_ENVIRONMENT_TOOL", true),
|
|
728
|
-
envFactsEnabled: boolEnv("SANDBOX_ENV_FACTS", true),
|
|
1088
|
+
sessionAutoTitle: boolEnv("SESSION_AUTO_TITLE", true), // session auto-title: default ON (one cheap call per session)
|
|
1089
|
+
selectEnvironmentTool: boolEnv("SELECT_ENVIRONMENT_TOOL", true), // RFC A2: default ON (mount additionally gated on k8s+catalog)
|
|
1090
|
+
envFactsEnabled: boolEnv("SANDBOX_ENV_FACTS", true), // RFC A1: default ON (core 1.240.0 TaskSpec.envFacts; no-facts = no field)
|
|
1091
|
+
// ③ sensitive-path write deny set: unset = core's recommended set; explicit value = full replacement
|
|
1092
|
+
// (comma-separated); "off"/empty = disabled. Set curation is core's (成文 core-side); server passes through.
|
|
729
1093
|
sensitiveWritePatterns: ((raw) => {
|
|
730
1094
|
if (raw === undefined)
|
|
731
1095
|
return [...RECOMMENDED_SENSITIVE_PATTERNS];
|
|
@@ -734,16 +1098,18 @@ export function loadConfig() {
|
|
|
734
1098
|
return [];
|
|
735
1099
|
return trimmed.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
736
1100
|
})(process.env.SENSITIVE_WRITE_PATTERNS),
|
|
1101
|
+
// [1557]§四: opt-in only — any value other than the two real gate shapes (incl. "off"/unset) stays undefined
|
|
1102
|
+
// (no field on the config object at all), so the fs-write gate wiring never even considers it.
|
|
737
1103
|
...(process.env.MANUAL_MODE_SHELL_GATE === "always" || process.env.MANUAL_MODE_SHELL_GATE === "classify"
|
|
738
1104
|
? { manualModeShellGate: process.env.MANUAL_MODE_SHELL_GATE }
|
|
739
1105
|
: {}),
|
|
740
|
-
toolDeferLongtail: boolEnv("TOOL_DEFER_LONGTAIL", false),
|
|
741
|
-
lspEnabled: boolEnv("LSP_ENABLED", false),
|
|
742
|
-
lspHostEnabled,
|
|
743
|
-
drainGraceMs: Math.max(10_000, numEnv("DRAIN_GRACE_MS", String(10 * 60 * 1000))),
|
|
744
|
-
sighupIdleGraceMs: Math.max(5_000, numEnv("SIGHUP_IDLE_GRACE_MS", String(120_000))),
|
|
1106
|
+
toolDeferLongtail: boolEnv("TOOL_DEFER_LONGTAIL", false), // [803]④ defer face: EXPERIMENTAL, default OFF
|
|
1107
|
+
lspEnabled: boolEnv("LSP_ENABLED", false), // sandbox lane: opt-in (needs a baked sema-code-lsp template)
|
|
1108
|
+
lspHostEnabled, // host lane: LSP_HOST_ENABLED, DEFAULT ON (CC-parity, degrades gracefully) — see the derivation above
|
|
1109
|
+
drainGraceMs: Math.max(10_000, numEnv("DRAIN_GRACE_MS", String(10 * 60 * 1000))), // SIGTERM drain window, default 10min, floor 10s
|
|
1110
|
+
sighupIdleGraceMs: Math.max(5_000, numEnv("SIGHUP_IDLE_GRACE_MS", String(120_000))), // B3: SIGHUP idle-shutdown window, default 2min, floor 5s
|
|
745
1111
|
mcpElicitation: {
|
|
746
|
-
enabled: boolEnv("MCP_ELICITATION_ENABLED", false),
|
|
1112
|
+
enabled: boolEnv("MCP_ELICITATION_ENABLED", false), // E23 inbound elicitation; default OFF (fail-closed)
|
|
747
1113
|
throttle: {
|
|
748
1114
|
maxConcurrentPerRun: numEnvBounded("MCP_ELICITATION_MAX_CONCURRENT", String(DEFAULT_ELICITATION_THROTTLE.maxConcurrentPerRun), 1, 64),
|
|
749
1115
|
maxTotalPerRun: numEnvBounded("MCP_ELICITATION_MAX_TOTAL", String(DEFAULT_ELICITATION_THROTTLE.maxTotalPerRun), 1, 10_000),
|
|
@@ -751,9 +1117,9 @@ export function loadConfig() {
|
|
|
751
1117
|
ttlMs: numEnvBounded("MCP_ELICITATION_TTL_MS", String(DEFAULT_ELICITATION_THROTTLE.ttlMs), 1_000, 3_600_000),
|
|
752
1118
|
},
|
|
753
1119
|
},
|
|
754
|
-
askQuestionEnabled: postureOn("ASK_QUESTION_ENABLED"),
|
|
755
|
-
toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"),
|
|
756
|
-
workflowAgentsReadOnly: boolEnv("WORKFLOW_AGENTS_READONLY", false),
|
|
1120
|
+
askQuestionEnabled: postureOn("ASK_QUESTION_ENABLED"), // §4④ AskUserQuestion live HITL; posture-gated (single-user → ON)
|
|
1121
|
+
toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"), // [816]/[820]② live tool-approval HITL; posture-gated (single-user → ON), mirrors askQuestion
|
|
1122
|
+
workflowAgentsReadOnly: boolEnv("WORKFLOW_AGENTS_READONLY", false), // [824]① TOB 保守旋钮:默认 off = workflow 子 agent 同权(CC parity)
|
|
757
1123
|
imageBakes: {
|
|
758
1124
|
enabled: boolEnv("IMAGE_BAKES_ENABLED", false),
|
|
759
1125
|
runnerPrincipal: bakeRunnerPrincipal,
|
|
@@ -766,8 +1132,10 @@ export function loadConfig() {
|
|
|
766
1132
|
submitRateWindowSec: Math.max(1, numEnv("BAKE_SUBMIT_RATE_WINDOW_SEC", "60")),
|
|
767
1133
|
},
|
|
768
1134
|
toolTrace: boolEnv("TOOL_TRACE", false),
|
|
769
|
-
traceThinking: boolEnv("TRACE_THINKING", true),
|
|
1135
|
+
traceThinking: boolEnv("TRACE_THINKING", true), // default ON (clay)
|
|
770
1136
|
logLevel: env("LOG_LEVEL", "info"),
|
|
1137
|
+
// BL-9: numEnv (not bare Number) — a non-numeric typo FAILS at startup instead of becoming NaN, which would
|
|
1138
|
+
// silently DISABLE the operator cost/rate ceiling (every `> ceiling` comparison against NaN is false).
|
|
771
1139
|
rateLimitPerMin: numEnv("RATE_LIMIT_RPM", "0"),
|
|
772
1140
|
maxTaskCostUsd: numEnv("MAX_TASK_COST_USD", "0"),
|
|
773
1141
|
maxTaskTokens: numEnv("MAX_TASK_TOKENS", "0"),
|
|
@@ -781,6 +1149,12 @@ export function loadConfig() {
|
|
|
781
1149
|
downgradeOn: csv("MODEL_DEGRADE_ON").length
|
|
782
1150
|
? csv("MODEL_DEGRADE_ON")
|
|
783
1151
|
: undefined,
|
|
1152
|
+
// vision precheck (adversarial-review finding): the degrade TARGET is an external gateway
|
|
1153
|
+
// model NOT in config.models, so resolveSpec's precheck (which only sees the picked model) can't see its
|
|
1154
|
+
// vision capability — a vision-capable main + text-only degrade target would let images reach the text-only
|
|
1155
|
+
// gateway as the opaque 400 the precheck prevents, AFTER admission, at runtime degrade. Declare it via
|
|
1156
|
+
// MODEL_DEGRADE_TO_VISION (strict "true"). DEFAULT false = assume the cheaper degrade target is text-only
|
|
1157
|
+
// (the common case, e.g. deepseek), so resolveSpec drops degrade for image-carrying tasks (main.ts).
|
|
784
1158
|
toSupportsImages: boolEnv("MODEL_DEGRADE_TO_VISION", false),
|
|
785
1159
|
}
|
|
786
1160
|
: undefined,
|
|
@@ -789,12 +1163,16 @@ export function loadConfig() {
|
|
|
789
1163
|
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT.replace(/\/$/, ""),
|
|
790
1164
|
intervalMs: Number(env("OTEL_EXPORT_INTERVAL_MS", "15000")),
|
|
791
1165
|
serviceName: env("OTEL_SERVICE_NAME", "sema-server"),
|
|
1166
|
+
// `OTEL_EXPORTER_OTLP_HEADERS` = comma-separated `k=v` pairs (e.g. an auth header).
|
|
792
1167
|
headers: Object.fromEntries(csv("OTEL_EXPORTER_OTLP_HEADERS").map((kv) => {
|
|
793
1168
|
const i = kv.indexOf("=");
|
|
794
1169
|
return i === -1 ? [kv, ""] : [kv.slice(0, i).trim(), kv.slice(i + 1).trim()];
|
|
795
1170
|
})),
|
|
796
1171
|
}
|
|
797
1172
|
: undefined,
|
|
1173
|
+
// [875]a 成文:0(缺省)= durable HITL 无限期等人,时间型 reapSuspended 不跑;file/memory lane 的回收
|
|
1174
|
+
// 探针只在 DURABLE_APPROVAL=true 时注入(无 durable 的部署两只 suspended 回收器恒 NO-OP,parked 行的
|
|
1175
|
+
// 恢复把手 = POST /v1/runs/:id/cancel,[868]①)。checkpoint 过期驱动的那只不受本旋钮门控。
|
|
798
1176
|
approvalTimeoutSec: numEnv("APPROVAL_TIMEOUT_SEC", "0"),
|
|
799
1177
|
reapIntervalSec: numEnv("REAP_INTERVAL_SEC", "60"),
|
|
800
1178
|
runStaleSec: numEnv("REAP_RUN_STALE_SEC", "120"),
|
|
@@ -802,12 +1180,16 @@ export function loadConfig() {
|
|
|
802
1180
|
toolResultTtlSec: Number(env("TOOL_RESULT_TTL_SEC", "86400")),
|
|
803
1181
|
gitApiBaseUrl: process.env.GIT_API_BASEURL,
|
|
804
1182
|
gitApiToken: process.env.GIT_API_TOKEN,
|
|
1183
|
+
// BEHAVIOR CHANGE([891] clay 硬裁定):出厂缺省场景 default→code(CC 编码 persona 蒸馏版)——
|
|
1184
|
+
// 编码产品出厂态对标 CC 出厂态,通用域中性 persona 用 DEFAULT_SCENARIO=default 显式选回。
|
|
805
1185
|
defaultScenario: env("DEFAULT_SCENARIO", "code"),
|
|
806
1186
|
skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
|
|
807
1187
|
oaApiBaseUrl: process.env.OA_API_BASEURL,
|
|
808
1188
|
oaServiceToken: process.env.OA_SERVICE_TOKEN,
|
|
1189
|
+
// 开 issue 工具:默认开到 OA 系统仓(AI-Only/oa-asset-system),写权限 token 走独立 env。
|
|
809
1190
|
oaIssue: process.env.OA_ISSUE_TOKEN
|
|
810
1191
|
? {
|
|
1192
|
+
// npm 卫生纪律:不再烤内网 git 主机缺省 — 开了 OA_ISSUE_TOKEN 就必须给 host(fail-loud)。
|
|
811
1193
|
baseUrl: (() => {
|
|
812
1194
|
const u = process.env.OA_ISSUE_BASEURL ?? process.env.GIT_API_BASEURL;
|
|
813
1195
|
if (!u)
|
|
@@ -820,6 +1202,11 @@ export function loadConfig() {
|
|
|
820
1202
|
defaultLabels: csv("OA_ISSUE_LABELS").map(Number).filter((n) => Number.isInteger(n)),
|
|
821
1203
|
}
|
|
822
1204
|
: undefined,
|
|
1205
|
+
// sema-registry (control plane) URL/token/worker. The orchestrator renamed the worker-injected env from
|
|
1206
|
+
// CONFIG_CENTER_* → SEMA_REGISTRY_* (the control-plane host was renamed to match); it dual-injects
|
|
1207
|
+
// both names at the SAME value during the transition, so we DUAL-READ here — new name preferred, old name
|
|
1208
|
+
// fallback — and keep working after the orchestrator drops the legacy CONFIG_CENTER_* names. Pure config
|
|
1209
|
+
// plumbing: no API/auth/task-logic change.
|
|
823
1210
|
configCenter: (process.env.SEMA_REGISTRY_URL || process.env.CONFIG_CENTER_URL)
|
|
824
1211
|
? {
|
|
825
1212
|
baseUrl: process.env.SEMA_REGISTRY_URL || env("CONFIG_CENTER_URL"),
|
|
@@ -832,6 +1219,14 @@ export function loadConfig() {
|
|
|
832
1219
|
configLocalDir: process.env.CONFIG_LOCAL_DIR || undefined,
|
|
833
1220
|
};
|
|
834
1221
|
}
|
|
1222
|
+
/** 有效监听绑址([1934])。**显式 `BIND_HOST`/`HOST` 恒生效**(operator 保留在任何形下明示暴露的
|
|
1223
|
+
* 权利);未显式时:**无鉴权写面 ⇒ 127.0.0.1**,其余 ⇒ undefined(Node 默认全接口,既有部署零影响)。
|
|
1224
|
+
*
|
|
1225
|
+
* 为什么缺省要自己收窄、而不是只加旋钮:桌面形的安全结论原本写着「safe ONLY because HOST is
|
|
1226
|
+
* pinned to 127.0.0.1」,而那个前提在本仓从未成立(HOST 零消费)——把一个没人实现的假设当防线,
|
|
1227
|
+
* 是「沉默陷阱」。零凭证 + 全接口 + host lane(用户真机非沙箱)= 同网段任意执行,缺省必须安全。
|
|
1228
|
+
* "无鉴权写面" 的判据与 http 层 503 fail-closed 门同源:allowUnauthedWrites 且**完全没有**凭证
|
|
1229
|
+
* (半配=凭证必须使用,不算无鉴权)。 */
|
|
835
1230
|
export function resolveBindHost(config) {
|
|
836
1231
|
if (config.bindHost)
|
|
837
1232
|
return config.bindHost;
|