@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
package/dist/main.js
CHANGED
|
@@ -9,7 +9,7 @@ import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-to
|
|
|
9
9
|
import { brainSummary } from "./brain.js";
|
|
10
10
|
import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, numEnv } from "./config.js";
|
|
11
11
|
import { cardRuleScopeRoot } from "./task-cwd.js";
|
|
12
|
-
import { isPricingConfigured } from "./budget.js";
|
|
12
|
+
import { isPricingConfigured } from "./budget.js";
|
|
13
13
|
import { drainNumEnvWarnings } from "./plugins/remote-shell.js";
|
|
14
14
|
import { ensureChildSessionDurableWithPromotion } from "./plugins/session-store.js";
|
|
15
15
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
@@ -45,13 +45,13 @@ import { createConfigCenterRuntime } from "./boot/config-center.js";
|
|
|
45
45
|
import { createResolveSpec } from "./boot/resolve-spec.js";
|
|
46
46
|
import { createParkedReviveInheritedGate } from "./boot/parked-revive-gate.js";
|
|
47
47
|
import { startReapers } from "./boot/reapers.js";
|
|
48
|
-
import { assertRetentionLaneWirable, startRetentionLane, RETENTION_LEASE_TTL_FACTOR } from "./boot/retention-lane.js";
|
|
48
|
+
import { assertRetentionLaneWirable, startRetentionLane, RETENTION_LEASE_TTL_FACTOR } from "./boot/retention-lane.js";
|
|
49
49
|
import { startFleetReconciler } from "./fleet/fleet-reconciler.js";
|
|
50
50
|
import { openStores } from "./boot/stores.js";
|
|
51
51
|
import { runAdoptionBootScan } from "./boot/adoption.js";
|
|
52
52
|
import { auditDormantPermissionRules } from "./boot/permission-rules-audit.js";
|
|
53
53
|
import { createBudgetAndTracing } from "./boot/budget-tracing.js";
|
|
54
|
-
import { createLimitSync } from "./boot/limit-sync.js";
|
|
54
|
+
import { createLimitSync } from "./boot/limit-sync.js";
|
|
55
55
|
import { createSideQueryLane } from "./boot/side-query-lane.js";
|
|
56
56
|
import { createWebFetchSummarizeLane } from "./boot/webfetch-summarize-lane.js";
|
|
57
57
|
import { createRuleConsentLane } from "./rules-consent.js";
|
|
@@ -67,8 +67,6 @@ import { createSessionFaces } from "./boot/session-faces.js";
|
|
|
67
67
|
import { createLeaderFace } from "./boot/leader.js";
|
|
68
68
|
import { assertStaticWiringConsistent } from "./http/routes/diagnostics.js";
|
|
69
69
|
import { installShutdownHandlers } from "./boot/shutdown.js";
|
|
70
|
-
/** Service version for GET /v1/capabilities — read from the package.json shipped next to src/ in the
|
|
71
|
-
* image (Dockerfile COPYs it). Best-effort: an unreadable file reports "unknown", never crashes boot. */
|
|
72
70
|
function serviceVersion() {
|
|
73
71
|
try {
|
|
74
72
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
@@ -78,93 +76,30 @@ function serviceVersion() {
|
|
|
78
76
|
return "unknown";
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Service entrypoint. **装配次序表**(design/158 A10)。
|
|
83
|
-
*
|
|
84
|
-
* 各 boot 段已按内聚下沉到 `src/boot/*.ts` 叶子(纯搬运,逐字);本函数保留的是**顺序本身**——
|
|
85
|
-
* 谁先谁后是行为(env 读取时序、诊断排空点、信号注册时点、工厂装饰顺序),每个叶子的文件头
|
|
86
|
-
* 都写明了它那条「位置即契约」。当前次序:
|
|
87
|
-
*
|
|
88
|
-
* loadConfig → logger/metrics → 诊断排空(logConfigDiagnostics + drainNumEnvWarnings)
|
|
89
|
-
* → boot/config-center(sealed-key/provider/prompt-epoch 店/effective 拉取/LKG 兜底/tolerant apply)
|
|
90
|
-
* → boot/config-center.applyLocalRemoteExec(local 形 config.d/remote-exec.json 覆写)
|
|
91
|
-
* → boot/stores(StoreBackend + memory + 四个 durable 店 + sessionStore + 拒启断言)
|
|
92
|
-
* → boot/budget-tracing(brain/pricing/quota/tracer)
|
|
93
|
-
* → boot/execution-env(remote-exec 工厂链 + LSP)
|
|
94
|
-
* → toolTracer/deploymentHooks
|
|
95
|
-
* → boot/workflow-orchestration → boot/coordinators → boot/runtime-caps
|
|
96
|
-
* → boot/runner-deps → new Runner → hook LLM/agent → subRunner
|
|
97
|
-
* → runStore/anchors/approval 面 → skills → boot/config-center.applyCenterCapabilities
|
|
98
|
-
* (center skills/plugins/boot-LKG/mcp)→ buildScenarios → .applyCenterScenarios
|
|
99
|
-
* → boot/reapers → OTLP → boot/config-center.startRefreshLoop → .initKeyResolver
|
|
100
|
-
* → boot/session-faces → boot/leader → ServiceDeps 七组 → boot/resolve-spec
|
|
101
|
-
* → createHttpServer → listen → fleet client → boot/shutdown(必须最后:信号注册时点即契约)
|
|
102
|
-
*
|
|
103
|
-
* A10 尾刀(config-center):`ccEtag/effective/latestEffective/appliedPlaneEff/planeDeferredNoHandoff/
|
|
104
|
-
* pendingRestart/bootSkillStale/keyResolver/centerPrompts/…` 这组跨段可变状态,已整体搬进
|
|
105
|
-
* `boot/config-center.ts` 那只工厂的**闭包**(仍是 `let`,每一处读写的文本与次序逐字未改 ——
|
|
106
|
-
* codex R10–R38 的重启风暴判据全部按原样保留);main 只经窄口读:`getKeyResolver/getCenterPrompts/
|
|
107
|
-
* modelReady/restartState/planeDeferredState/promptSource`,与本目录既有的晚绑 getter 惯例同款。
|
|
108
|
-
*
|
|
109
|
-
* Wires the three planes:
|
|
110
|
-
* Brain = external LLM gateway (createBrain)
|
|
111
|
-
* Session = pluggable store (TiDB durable event-log center, or in-memory)
|
|
112
|
-
* Hands = tools/MCP injected per request in resolveSpec (deployment-owned)
|
|
113
|
-
* Persistence (L1 sessions + L2 memory) lives in the external TiDB center; this container is stateless.
|
|
114
|
-
*/
|
|
115
79
|
async function main() {
|
|
116
80
|
const config = loadConfig();
|
|
117
|
-
// ── CROSS-INVARIANT-REGION:begin ────────────────────────────────────────────────────────────────
|
|
118
|
-
// Liveness invariant: the running instance refreshes updated_at every HEARTBEAT_MS; the reaper fails
|
|
119
|
-
// a run whose updated_at is older than runStaleSec. If the stale window were ≤ the heartbeat, the
|
|
120
|
-
// reaper would race a perfectly alive run. Fail fast on a misconfiguration instead of reaping live work.
|
|
121
|
-
// #322:判据本体已搬进 `config-invariants.ts` 的单源表 —— 热路径(config-center stage)跑的是**同一条**,
|
|
122
|
-
// 所以一个过小的 runStaleSec 既不能从 env 起服,也不能从中心热发布进来(禁手抄第二份判据)。
|
|
123
81
|
if (config.runStaleSec * 1000 <= HEARTBEAT_MS) {
|
|
124
|
-
// CROSS-INVARIANT:run-stale-heartbeat
|
|
125
82
|
throw new Error(`REAP_RUN_STALE_SEC (${config.runStaleSec}s) must exceed the ${HEARTBEAT_MS / 1000}s liveness heartbeat`);
|
|
126
83
|
}
|
|
127
|
-
// ── CROSS-INVARIANT-REGION:end ──────────────────────────────────────────────────────────────────
|
|
128
84
|
const logger = createLogger(config.logLevel);
|
|
129
85
|
const metrics = createMetrics();
|
|
130
|
-
// #157 fail-open 治理:把 logger/metrics 接到进程级 failOpen 记录器上(见 observability/fail-open.ts)。
|
|
131
|
-
// 排在这里而不是更晚:兜底臂遍布装配链,越早接上,越少的 fail-open 只剩探针行没有结构化痕迹。
|
|
132
|
-
// 装配前已累计的计数与已喊过的 tag 由 install 继承,不重置也不重喊。
|
|
133
86
|
installFailOpenRecorder({ logger, metrics });
|
|
134
|
-
// A-033.3(#256):同一处接上密钥形 env 剥除的留痕面(core `scrubSecretEnv` 的 findings 收集器半场)。
|
|
135
|
-
// 同因排在这里:两条剥除腿(host shell 继承 env / project-memory 的 git 子进程)都在没有观测句柄的深处。
|
|
136
87
|
installSecretEnvScrubRecorder({ logger, metrics });
|
|
137
|
-
// S20 (SILENT-FALLBACK P1) + [792]④ boot notices: config parsing runs before the logger exists — drain both
|
|
138
|
-
// pre-logger collectors via the shared helper (F10: run-local uses the same one, so entrypoints can't drift).
|
|
139
88
|
logConfigDiagnostics(logger);
|
|
140
|
-
// design/158 B4 交接小件:plugins 数值旋钮(HOST_BG_*/K8S_BG_*/E2B_*,模块级 import 时读)的非法
|
|
141
|
-
// 字面量告警——通道在 remote-shell.ts 自带(不造 plugins→config 依赖边),与上面同一 boot 诊断点排空。
|
|
142
89
|
for (const w of drainNumEnvWarnings())
|
|
143
90
|
logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
|
|
144
|
-
// M1(接入审计 2026-07-08):core verify 契约=verifier 必须异于 implementer(去相关性是 🔴 部署契约)。
|
|
145
|
-
// verifier 角色落 "default" 时对抗验证者=实现者自评自批——比无验证更糟(假安全感)。曝光而非静默:
|
|
146
|
-
// 单模型部署配 MODEL_VERIFIER 指异构模型即消警(typo/不在 catalog 同样落到这里)。
|
|
147
91
|
if ((config.roles?.verifier ?? "default") === "default") {
|
|
148
92
|
logger.warn("verify_decorrelation_unavailable", {
|
|
149
93
|
hint: "verifier role = default (= implementer) — adversarial verification grades its own work; set MODEL_VERIFIER=<catalog model id> to a heterogeneous model",
|
|
150
94
|
...(process.env.MODEL_VERIFIER ? { rejected: process.env.MODEL_VERIFIER, reason: "not in catalog" } : {}),
|
|
151
95
|
});
|
|
152
96
|
}
|
|
153
|
-
// S24: redaction rewrites by pattern; LOW: web-search non-array payloads by provider.
|
|
154
97
|
setRedactionObserver((pattern, n) => metrics.inc("redactions_applied_total", { pattern }, n));
|
|
155
98
|
setWebSearchBadPayloadObserver((provider) => {
|
|
156
99
|
metrics.inc("web_search_bad_payload_total", { provider });
|
|
157
100
|
logger.warn("web_search_non_array_payload", { provider });
|
|
158
101
|
});
|
|
159
|
-
// 🔴 ONE local root for BOTH the config provider (models/skills/mcp) AND the remoteExec loader (avoids a
|
|
160
|
-
// split-brain — createConfigProvider defaulted to process.cwd() while loadRemoteExec used
|
|
161
|
-
// AGENT_DATA_DIR, so config.d and remote-exec.json could resolve to DIFFERENT directories). run-local already
|
|
162
|
-
// computes this once; main.ts now does too. Shared with the config-center runtime and every boot leaf below.
|
|
163
102
|
const localRoot = config.configLocalDir ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
164
|
-
// 缝合审 M6:上面这条论证合并了 config provider 与 remoteExec 两个根,**漏了第三个** —— store 的
|
|
165
|
-
// 数据根另有一条解析(config.ts 的 `localDataRoot`,它吃 `LOCAL_DATA_ROOT` 而这里不吃)。只设
|
|
166
|
-
// LOCAL_DATA_ROOT 的部署因此数据在一处、config.d/remote-exec.json 在另一处,而且**一声不响**。
|
|
167
|
-
// 不改解析(改了现有部署的 config.d 会当场失踪),改成说出来 + 给合一旋钮。
|
|
168
103
|
const rootsSplit = splitLocalRoots({
|
|
169
104
|
localDataRoot: config.localDataRoot,
|
|
170
105
|
configLocalDir: config.configLocalDir,
|
|
@@ -177,49 +112,17 @@ async function main() {
|
|
|
177
112
|
note: "LOCAL_DATA_ROOT moved the local backend's DATA root, but config.d / remote-exec.json are still read from the config root (LOCAL_DATA_ROOT is not consulted there). If you meant to move both, set CONFIG_LOCAL_DIR to the same directory.",
|
|
178
113
|
});
|
|
179
114
|
}
|
|
180
|
-
// design/158 A10 尾刀:config-center 的 boot 半场(sealed-key 托管 → provider → prompt-epoch 店 →
|
|
181
|
-
// effective 拉取 / LKG 兜底 / tolerant apply)搬到 src/boot/config-center.ts。该段与 refresh 循环共享的
|
|
182
|
-
// 12 个跨段可变 `let` 现在是那只工厂的闭包私有字段(每一处读写的文本与次序逐字保留,见该文件头注);
|
|
183
|
-
// main 只经窄口读:getKeyResolver/getCenterPrompts/modelReady/restartState/planeDeferredState/promptSource。
|
|
184
|
-
// ⚠️ 位置即契约:必须在 openStores 之前 —— models/roles/teams 要在 Runner/持久层建起来之前应用进 config。
|
|
185
115
|
const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
|
|
186
|
-
// design/158 A10 尾刀:local 形的 config.d/remote-exec.json 覆写搬到 boot/config-center.ts(逐字)。
|
|
187
|
-
// ⚠️ 位置即契约:就地改写 config.remoteExec,必须早于 createExecutionEnv 这个消费点。
|
|
188
116
|
await configCenter.applyLocalRemoteExec();
|
|
189
|
-
// design/158 A10:持久层装配搬到 src/boot/stores.ts(逐字)。⚠️ 该段就地归一 `config.sessionBackend`
|
|
190
|
-
// 且承载三条 fail-loud 拒启断言 —— 位置即契约,理由见该文件头注。
|
|
191
117
|
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, } = await openStores({ config, logger, metrics, localRoot });
|
|
192
|
-
// design/183 I6(server 同族):**每副本 boot 必查**收编日志的在飞行 —— 续跑或响亮留痕,禁静默跳过。
|
|
193
|
-
// 位置:store 开完之后(要 backend)、任何路由装配之前(半迁移状态绝不带进服务期)。判据与「为什么
|
|
194
|
-
// 是续跑而不是拒启」见 boot/adoption.ts 顶注。
|
|
195
|
-
// #264 v2-c:治理携出 bundle 两面(POST /v1/memory/{export,import})的引擎接线。**一次**构造、两口共用
|
|
196
|
-
// —— 与 orgMemoryDirectory / sharedMemoryStore 同姿势(一个进程对同一件事只有一个实例)。
|
|
197
|
-
// 引擎为什么在这里现构、以及「这两条路径是店级的、不读挂载树」的亲读依据,全文见 memory-bundle-engine.ts。
|
|
198
|
-
// 缺席(记忆引擎未接线)⇒ 两口 501 `capability.memory_engine_required`。
|
|
199
118
|
const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine) : undefined;
|
|
200
119
|
const adoptionBoot = await runAdoptionBootScan({ backend, logger });
|
|
201
120
|
if (adoptionBoot.scanned > 0 || adoptionBoot.error !== undefined) {
|
|
202
121
|
logger.info("adoption_boot_scan", { ...adoptionBoot });
|
|
203
122
|
}
|
|
204
|
-
// design/158 A10:计费/追踪/预算装配搬到 src/boot/budget-tracing.ts(逐字;tracer 与 side-query 同 sink 实例的
|
|
205
|
-
// 「同段构造」契约见该文件头注)。
|
|
206
123
|
const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileSnapshotStore, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
|
|
207
|
-
// #154 车二:持久化权限规则店(core 5.18.0 design/179 + 5.22.0 design/182)。三面一束 —— 规则桶
|
|
208
|
-
// provider 上 `RunnerDeps.permissionRuleStore`(引擎据它铸 ruleSuggestions + 把 manifest 的
|
|
209
|
-
// `permissionRules.storeWired` 报成真),审批记录 + 导入票喂同意车道(HTTP 两口的属主)。
|
|
210
|
-
// 缺席(local 车道 / 无 backend)⇒ 诚实缺席:storeWired:false、帧上零候选、两口 501。
|
|
211
|
-
// 🔴 总开关在最前(codex round7 [high] 一):关 ⇒ 整条车道根本不装配(店/车道/帧键/两口一起消失)。
|
|
212
124
|
const permissionRuleStores = config.permissionRulesEnabled && backend ? backend.permissionRule() : undefined;
|
|
213
125
|
const ruleConsent = permissionRuleStores ? createRuleConsentLane(permissionRuleStores) : undefined;
|
|
214
|
-
// 🔴 A-010.10(验真后修):**旋钮开着却上不了场 ⇒ 打一行**,与同族旋钮 `STREAM_APPROVAL_ENABLED` 的
|
|
215
|
-
// `stream_approval_disabled` 一行(`boot/coordinators.ts`)对称。
|
|
216
|
-
// 此前这一格全程静默:`PERMISSION_RULES_ENABLED=true` 的部署若没有 backend、或 backend 没实装规则店,
|
|
217
|
-
// 车道就悄悄整条不装配 —— 运维看到的是「设了旋钮,可 `/v1/rules` 恒 501、卡上恒无候选」,而启动日志
|
|
218
|
-
// 里一个字都没有。判据逐字照那条先例:①**info 不是 warn**(这是合法部署形,不是配置错误);
|
|
219
|
-
// ②旋钮本来就没开的部署**不打**(显式关掉的人自己知道为什么,一行噪音零信息);
|
|
220
|
-
// ③原因写成机读位(哪一项不成立),不是一句人话。
|
|
221
|
-
// 与紧随其后的 `auditDormantPermissionRules` 是两件事、互不遮蔽:那条说的是「车道上了场,并且库里有
|
|
222
|
-
// 既有桶被默认 ON 唤醒」,这条说的是「车道压根没上场」——两者的合取恒假,永远只会打其中一行。
|
|
223
126
|
if (config.permissionRulesEnabled && permissionRuleStores === undefined) {
|
|
224
127
|
logger.info("permission_rules_lane_unavailable", {
|
|
225
128
|
knob: "PERMISSION_RULES_ENABLED",
|
|
@@ -228,107 +131,38 @@ async function main() {
|
|
|
228
131
|
note: "PERMISSION_RULES_ENABLED is on but no permission-rule store is wired — /v1/rules/* answer 501, approval cards carry no rule suggestions, and respond's persistRule is refused with rule_lane_unavailable",
|
|
229
132
|
});
|
|
230
133
|
}
|
|
231
|
-
// #203 §3(设计稿 v2 F4 残余):默认 ON 会把**既有**规则桶一并唤醒 —— 在启动日志里把它说出来。
|
|
232
|
-
// 三条判据(店缺席 / 零桶 / 运维显式表过态 ⇒ 都不打行)与失败方向(数不出来只 warn,绝不拒启、
|
|
233
|
-
// 也绝不编一个 0)逐字见 `boot/permission-rules-audit.ts`。
|
|
234
134
|
await auditDormantPermissionRules({ stores: permissionRuleStores, logger, explicit: config.permissionRulesEnabledExplicit });
|
|
235
|
-
// design/158 A10:执行环境装配搬到 src/boot/execution-env.ts(逐字)。
|
|
236
|
-
// ⚠️ 工厂装饰顺序=行为(scratchpad → worktree → SendUserFile 登记 → 附件物化最外层),见该文件头注。
|
|
237
135
|
const { perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv, executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager, } = createExecutionEnv({ config, logger, metrics, taskAttachmentStore });
|
|
238
|
-
// prove-before-scale stopgap: a no-op tool tracer (logs every tool call incl. resumed legs that
|
|
239
|
-
// /events hides) so we can diagnose single-agent coding behaviour. Off unless TOOL_TRACE=true.
|
|
240
136
|
const toolTracer = config.toolTrace ? createToolTracer(logger) : undefined;
|
|
241
137
|
if (toolTracer)
|
|
242
138
|
logger.info("tool_trace_enabled", {});
|
|
243
|
-
// Goal B4 (2026-07-08): the ALWAYS-ON gate-deny meter (`permission_denied_total{source}`) — an
|
|
244
|
-
// operational/security signal that must not sit behind the TOOL_TRACE diagnostic switch. Composed
|
|
245
|
-
// UNDER the tracer (deployment slot order: meter first, tracer second — composeHooks double-runs the
|
|
246
|
-
// void permissionDenied slot); this composite is what every hooks site below uses as the deployment
|
|
247
|
-
// baseline, so a task-hooks fold (resolveSpec) shadows neither.
|
|
248
139
|
const deploymentHooks = toolTracer ? composeHooks(createPermissionDeniedMeter(metrics), toolTracer) : createPermissionDeniedMeter(metrics);
|
|
249
|
-
// design/158 A10:workflow 编排面搬到 src/boot/workflow-orchestration.ts(逐字;构造次序契约见该文件头注)。
|
|
250
140
|
const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
|
|
251
|
-
// #329:PARKED 行迟到决议的赎回席 —— **晚绑** holder(席本体是 `createHttpServer` 的产物,而协调器段
|
|
252
|
-
// 按其自身的次序契约必须排在它之前;同款先例 = 下方 reaper 的 `runDenySweep`)。接线在 server 造出来
|
|
253
|
-
// 之后一行,那里同时是**直连门**的 fail-closed 判定点(理由写在赋值处)。
|
|
254
141
|
let parkedAskRedeem;
|
|
255
|
-
// design/158 A10:活体协调器 + SendUserFile 工具面搬到 src/boot/coordinators.ts(逐字;durableEnabled 的
|
|
256
|
-
// 「必须早于 runnerDeps 求值」次序契约见该文件头注)。
|
|
257
142
|
const { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({
|
|
258
143
|
config, logger, backend, sendUserFileTaskEnvs, ruleConsent,
|
|
259
144
|
getParkedRedeem: () => parkedAskRedeem,
|
|
260
|
-
// #295(F-1,[4512] 修向 (b')):卡批「不再询问」的 project root——语义单点见 task-cwd.ts
|
|
261
|
-
// `cardRuleScopeRoot` 顶注(session 注册 cwd ?? in-process 单用户形 process.cwd();其余 undefined)。
|
|
262
145
|
ruleScopeRootFor: (sessionId) => cardRuleScopeRoot(sessionId !== undefined ? perSessionCwd.get(sessionId) : undefined, config),
|
|
263
146
|
});
|
|
264
|
-
// design/158 A10:per-principal caps 段搬到 src/boot/runtime-caps.ts(逐字)。
|
|
265
147
|
const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger });
|
|
266
|
-
// design/170 件A(#148 件3③):org 记忆准入装配(目录源三态选择+C12 能力探测,坏配置在此拒启动)。
|
|
267
148
|
const orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
|
|
268
|
-
// design/177 —— org 共享记忆库(memory_list/memory_read + `/v1/shared-memory/*` 只读面)。
|
|
269
|
-
// 🔴 铸它的合取式是**唯一**的挂载条件,模型面与 HTTP 面共用:
|
|
270
|
-
// ① SQL 供给面在场(`backend.sharedMemoryStore` —— local 车道诚实缺席,理由在 store-backend.ts);
|
|
271
|
-
// ② org 折叠面在场(目录源)—— 没有成员性判据的共享读面只能全放或全拒,两个都比"这个面不存在"差。
|
|
272
|
-
// deployment-origin scope 只在**无租户边界**的部署里授予:多租户下一条部署级声明会同时授予每一个
|
|
273
|
-
// principal,那是跨租户读而不是配置便利(org-memory.ts 的 N2 同判)。择净在这一行,授权模块只忠实使用。
|
|
274
149
|
const sharedMemoryStore = orgMemoryAdmission.directory !== undefined
|
|
275
150
|
? backend?.sharedMemoryStore?.(createSharedMemoryScopeAuthorizer({
|
|
276
151
|
directory: orgMemoryAdmission.directory,
|
|
277
|
-
// 🔴 A-057.40:递的是**取值口**不是值 —— `config.projects` 由 config-center 就地热应用,
|
|
278
|
-
// boot 期快照会让新登记的 org 库变成终局式空集、让已撤销的 scope 继续被放行(后者是
|
|
279
|
-
// fail-open,且 projects 不在 RESTART_SLICES 里 ⇒ /health 一句提示都没有)。多租户择净
|
|
280
|
-
// 仍留在这一行(那是部署形态判据,boot 期定死,不随热应用变)。
|
|
281
152
|
deploymentScopes: config.requirePrincipal === true ? () => [] : orgMemoryAdmission.deploymentMemoryScopesLive,
|
|
282
153
|
}))
|
|
283
154
|
: undefined;
|
|
284
155
|
if (sharedMemoryStore)
|
|
285
156
|
logger.info("shared_memory_stores_enabled", { backend: backend?.kind });
|
|
286
|
-
// 🔴 codex 复审(#196 finding-2b):`toolResultStore` 在**无 backend** 形下是 undefined,而 core 的
|
|
287
|
-
// Runner 构造函数会在缺席时**每只各自私建**一份 `RunnerSharedToolResultStore`(runtask.js
|
|
288
|
-
// `if (!this.deps.toolResultStore)`)。本部署有多只 Runner(主 / subRunner / hookAgent / #196 的两只无手
|
|
289
|
-
// 孪生),私建 ⇒ 每只一个独立店 ⇒ A 车道 offload 出去的 `ReadToolResult` 引用在 B 车道解引用不到
|
|
290
|
-
// (fork 子任务跨 runner 读父的 offload 是既有可达形,#196 的车道分家又多了一条)。这里显式铸**一份**
|
|
291
|
-
// 共享兜底(参数与 core 私建那只逐字相同:`RunnerSharedToolResultStore` 是 `InMemoryToolResultStore`
|
|
292
|
-
// 的空子类,cap 64M chars),只喂 Runner 侧。
|
|
293
|
-
// ⚠️ 刻意**不动** `toolResultStore` 本身:那个键的「present ⇔ durable 后端在场」语义还管着 E21 purge、
|
|
294
|
-
// reaper 清扫、leader 装配与 /health 的接线自述(改它会让无 backend 的部署自述成 "shared(sql)" = 谎)。
|
|
295
157
|
const runnerOffloadStore = toolResultStore ?? new InMemoryToolResultStore({ maxTotalChars: 64_000_000 });
|
|
296
|
-
// 交接件⑤:commit 尾注署名座**算一次**,主/sub 两只 Runner 经共享基座同源。
|
|
297
|
-
// [931]① clay 拍(core 1.300 BREAKING:缺省不署 Co-Authored-By,署名=产品身份资产归部署):
|
|
298
|
-
// branded 形态(local provider = Sema 产品线,scenarios brandIdentity 同判据)commit 尾注接 Sema 署名;
|
|
299
|
-
// 非 brand 部署维持 core 新缺省(不署)。判据逐字不变,变的只是它现在**也**喂给 subRunner ——
|
|
300
|
-
// 一个被委派的子代提交进的是同一个仓、代表同一个部署,它的 commit 少一行 trailer 没有理由。
|
|
301
158
|
const commitHands = config.configProvider === "local" ? { commitCoAuthor: "Sema <noreply@vivi-ai.com>" } : undefined;
|
|
302
|
-
// checkpointStore 构造(只依赖 backend/config——顺序安全;DURABLE_APPROVAL 仍是唯一门,ALL three
|
|
303
|
-
// backends carry checkpoint(),local = core FileCheckpointStore + service half)。四个消费点同一实例:
|
|
304
|
-
// 主 runner 的 per-task spec 面(resolveSpec durableEnabled 分支)、scenarioDeps(subagent spawn 面
|
|
305
|
-
// forwardDurableApproval)、subRunner deps(子代执行面 suspendAsk——[1584] 接线,裁定见 subRunner 构造
|
|
306
|
-
// 内注),以及紧随其下的 design/170 件D 留存能力校验(它要读这只店的 `retention` 声明位)。
|
|
307
|
-
// ⚠️ 位置即契约:必须早于 `createGovernanceSeams`(件D 校验的对象之一)与 `createRunnerDeps`。
|
|
308
159
|
const checkpointStore = backend?.checkpoint && config.durableApproval ? backend.checkpoint(logger) : undefined;
|
|
309
|
-
// design/170 件B/C/D(#252 件1):三个部署治理座席 + 两条拒启判据(锁的装配相容性、留存能力校验)。
|
|
310
|
-
// ⚠️ 位置即契约:必须在 store 三件都构造完之后(件D 校验读它们的 `retention` 声明)、`new Runner` 之前
|
|
311
|
-
// (座席进 RunnerDeps);拒启在这里发生 = 还没开始服务。
|
|
312
|
-
// #270 —— 托管留存的**执行面装配**(设计稿 §7)。⚠️ 位置即契约:必须在 `createGovernanceSeams` 之前
|
|
313
|
-
// (件① 的 `retentionExecutorWired` 是它的入参),也必须在 store 三件构造完之后(自持门要读它们的
|
|
314
|
-
// 声明位)。两只店都是 **SQL 后端专有**,`local`/无 backend 上恒 undefined —— 那正是 lane 自持门
|
|
315
|
-
// 在 `RETENTION_SWEEP_INTERVAL_SEC>0` 时拒启的那一支。
|
|
316
|
-
//
|
|
317
|
-
// `taskAttachmentTable` 如实告知(车1 交接件③):它是「boot 真的 ensure 过那张表吗」的事实,
|
|
318
|
-
// 不是一个默认值 —— 无对象存储的部署上附件面整段不接线、表不建,一条盲发的 DELETE 会以
|
|
319
|
-
// unknown-table 打红整轮 sweep(理由逐字见 retention-store-sql.ts 的 `SqlRetentionStoreOptions`)。
|
|
320
|
-
// `taskListTables`(#318)同姿势但事实不同:`boot/task-list-lane.ts` 在**任何** SQL 后端上都无条件
|
|
321
|
-
// ensure 那两张表(不像附件那样挂在对象存储配置上),所以这里的如实答案就是「车道落在 SQL 臂上」。
|
|
322
160
|
const retentionExecutor = backend?.retention?.({ taskAttachmentTable: taskAttachmentStore !== undefined, taskListTables: taskListLane.backend !== "memory", logger });
|
|
323
161
|
const retentionLaneStore = backend?.retentionLane?.();
|
|
324
|
-
// lane **自持**的 boot 不变式(件④;codex F3)——与下面 core 的 `assertRetentionCapability` 是两道门,
|
|
325
|
-
// 各答各的问题,本道**不看 locked**。判据与三条拒启臂逐字见 `boot/retention-lane.ts`。
|
|
326
162
|
assertRetentionLaneWirable({
|
|
327
163
|
intervalSec: config.retentionSweep.intervalSec,
|
|
328
164
|
policy: config.retentionPolicy,
|
|
329
165
|
executor: retentionExecutor,
|
|
330
|
-
// 与件D 校验**同一份**店清单(下面 `retentionStores` 那三行的同源读点):两道门看的是同一批真身,
|
|
331
|
-
// 一门放行另一门拒的分歧才有意义,而"看的不是同一批店"造成的分歧只是 bug。
|
|
332
166
|
stores: [
|
|
333
167
|
{ name: "sessionStore", store: sessionStore },
|
|
334
168
|
{ name: "checkpointStore", store: checkpointStore },
|
|
@@ -339,25 +173,13 @@ async function main() {
|
|
|
339
173
|
const governanceSeams = createGovernanceSeams({
|
|
340
174
|
config,
|
|
341
175
|
logger,
|
|
342
|
-
// 件D 的首批治理对象(design/170 §5.1 点名的三族)。**喂真身**:这道校验的全部价值就是读真店上的
|
|
343
|
-
// 声明位,喂一个"我以为装了什么"的替身等于把校验变成自证。`store: undefined` 的行由 core 跳过。
|
|
344
176
|
retentionStores: [
|
|
345
177
|
{ name: "sessionStore", store: sessionStore },
|
|
346
178
|
{ name: "checkpointStore", store: checkpointStore },
|
|
347
|
-
// 🔴 R2-F3(codex 对抗复审,验真后修):喂 **Runner 真用的那一只**(`runnerOffloadStore`),不是
|
|
348
|
-
// `toolResultStore` —— 后者缺席时 Runner 拿的是本文件铸的 InMemory 兜底,而那只店同样受留存策略管辖。
|
|
349
|
-
// 喂错实例 = 校验漏掉了真正在跑的那一个,正是「校验读的不是真身」这类假绿。
|
|
350
179
|
{ name: "toolResultStore", store: runnerOffloadStore },
|
|
351
180
|
],
|
|
352
|
-
// 🔴 件①(车1 交接件,codex R2-[critical] 的修复面):**本 build 真的接了执行器吗**。
|
|
353
|
-
// core 的 `assertRetentionCapability` 只读店的声明位,而三只 SQL 店从车1 起就诚实地声明了
|
|
354
|
-
// `"managed"` —— 于是那道门会为一台**没有任何东西在删**的机器放行 locked policy。这一位问的是
|
|
355
|
-
// 另一个问题:有没有东西真会删。合取式逐字 = 上面那道自持门放行之后 lane 真的会起的条件。
|
|
356
181
|
retentionExecutorWired: config.retentionSweep.intervalSec > 0 && retentionExecutor !== undefined && retentionLaneStore !== undefined,
|
|
357
182
|
});
|
|
358
|
-
// design/158 A10:RunnerDeps 装配段搬到 src/boot/runner-deps.ts(逐字;runStore 晚绑改取值,见该文件头注)。
|
|
359
|
-
// #324:撤销台账席取一次、两处(主 runnerDeps + subRunner 共享基座)同一引用。takeMcpRevocations 的
|
|
360
|
-
// 取走动作同时打开 restart-signal 的删除向豁免(承接与豁免绑定,见 config-center 的方法注)。
|
|
361
183
|
const mcpRevocations = configCenter.takeMcpRevocations();
|
|
362
184
|
const runnerDeps = createRunnerDeps({
|
|
363
185
|
hands: commitHands,
|
|
@@ -373,77 +195,9 @@ async function main() {
|
|
|
373
195
|
getRunStore: () => runStore,
|
|
374
196
|
});
|
|
375
197
|
const runner = new Runner(runnerDeps);
|
|
376
|
-
// #196:`runner` 的无手孪生 —— 同 deps 同实例(sessionStore/toolResultStore/checkpointStore… 全共享,
|
|
377
|
-
// `Runner.sessions` 就是传进去的那只 store,两只 Runner 不会各持一份会话缓存),唯独不挂
|
|
378
|
-
// `executionEnvFactory`。core 的 `handsEnabled = ownedEnv || deps.executionEnv` 是 **per-Runner** 合同,
|
|
379
|
-
// 单 runner 服务全场景时结构上无法逐场景兑现 —— 这只孪生就是兑现它的那一半(设计小票 §2a)。
|
|
380
|
-
// 声明 hands:"none" 的场景(scan / code-review 两腿 / discuss / center overlay)在 HTTP 执行点被路由到这里。
|
|
381
|
-
//
|
|
382
|
-
// 🔴 两条**已知边界**(codex 复审 2026-08-09 两轮提出,逐条亲验 core dist 后如实记账;两条的部署条件
|
|
383
|
-
// 互不相同 —— 不要把它们并成一句)。
|
|
384
|
-
//
|
|
385
|
-
// ① 会话锁是 **per-Runner** 的(core runtask.js 的 `sessionLocks` Map,每只 Runner 一份)。
|
|
386
|
-
// **条件:无 store backend 的部署形**。有 runStore 时同会话并发提交被服务端的 run 认领挡成 409
|
|
387
|
-
// (routes/tasks.ts:317 / routes/runs.ts:335 的 `createRun` 认领),两车道并发结构上不可达;无
|
|
388
|
-
// backend 的部署没有那道认领,同一 sessionId 上「一条 full + 一条 none」并发就失去了修前由单
|
|
389
|
-
// Runner 提供的串行化。同类缺口本就存在(subRunner 经 ForkRoutingSessionStore 也会碰宿主会话),
|
|
390
|
-
// 本件让它多一条可达路径。补偿:配任一 backend(含 `DB_BACKEND=local`)即恢复认领。
|
|
391
|
-
//
|
|
392
|
-
// ② 升级窗的旧 checkpoint。**条件恰恰相反:必须有 checkpointStore**(= 有 backend + DURABLE_APPROVAL),
|
|
393
|
-
// 且部署在 REMOTE_EXEC 车道上 —— 修前建立的 hands=none 场景 durable checkpoint 会带 workspaceHandle
|
|
394
|
-
// (可挂起 env 的快照形,以及**非**可挂起远端 env 的 `restoreMode:"park_only"` 形,两者都算;审批 park
|
|
395
|
-
// 与 resource suspend 都会写),续跑时被路由到这只无手 Runner,core 在 **CAS 之前**抛
|
|
396
|
-
// `CheckpointError("checkpoint.unsupported_version", "checkpoint has a remote workspaceHandle but no
|
|
397
|
-
// RunnerDeps.executionEnvFactory is wired to rebuild the env")`(runtask.js:3704)。
|
|
398
|
-
// 🟢 **裁定已翻新(#209 件2,core 5.25.0 提货批 [3443]):本形现在归 RETRIABLE。**
|
|
399
|
-
// 沿革留档(免下次重开同一个问题):该码在 core 上共三个铸点 —— 本形 / 版本超上限 / org 治理行
|
|
400
|
-
// 落到无 `permissionRuleOrg` 接线的 worker —— 三形都是 PRE-CAS、卡一律留 pending,但**可恢复性
|
|
401
|
-
// 不同**;#205 当时 core **不给判别式**(同码、`detail` 同样缺席),两版下游修法各被一轮 codex
|
|
402
|
-
// 对抗复审验伪(①三形一律 retriable ②按 `gate.realApproval` 分流),按「源头修复禁下游旁路」全
|
|
403
|
-
// 回退、维持 TERMINAL,并把判别式列成上游请托([3434] 请托 1)。
|
|
404
|
-
// 请托已兑现:core 5.25.0 给三个铸点各带 `CheckpointError.detail.reason`,本形 = `env_factory_missing`,
|
|
405
|
-
// d.ts 逐字「retryable on a factory-wired worker」。⇒ `http/server.ts` 的分类表现在**读词表**判
|
|
406
|
-
// (`WORKER_SWAP_REDEEMABLE`,闭集、core 加词 tsc 红),不做形状推断;本形的行**重新 park
|
|
407
|
-
// suspended**、那条仍 pending 的卡不再被孤儿化(#205 登记的孤儿代价至此销账)。
|
|
408
|
-
// ⚠️ 与旧裁定的差别在**代价的方向**,如实说清:修前拿「释放 task_active」换「卡变死件(下一次
|
|
409
|
-
// /decide → 404,只能等 `terminal_at_ms`)」;修后行停在 suspended 攥着会话锁,与它 park 时逐字
|
|
410
|
-
// 相同,直到运维给这条车道接上 `executionEnvFactory`(或把该场景路由回有手 Runner)。#205 当年
|
|
411
|
-
// 驳掉 blanket-retriable 的那条理由(`handsLanes.laneOf(spec)` 按场景判 ⇒ 换副本重投必同样失败)
|
|
412
|
-
// 仍然成立,但它说的是「换个副本再点一次没用」,而 core 的词说的是「接上 factory 的 worker 兑得掉」
|
|
413
|
-
// —— 那是一次**运维动作**,与 `governed_unwired` 要求接 `permissionRuleOrg` 完全同族。
|
|
414
|
-
// 升级窗仍是**响亮的**(409 + 该 errorCode + `retriable: true`),不是静默损坏;发车说明照写。
|
|
415
|
-
// 论证与钉:`http/server.ts` 分类表下方那段 + `test/durable-resume-http.test.ts` 的「#209 件2」六格。
|
|
416
|
-
// ⚠️ 未覆盖登记(原样保留):本形需要「真 durable 店 + 带 workspaceHandle 的旧 checkpoint」才能
|
|
417
|
-
// 驱动,真店端到端仍属真双库/迁移测试面,未建。
|
|
418
198
|
const handslessRunner = new Runner(withoutExecutionEnv(runnerDeps));
|
|
419
|
-
// codex R10: TRUE ⇒ the Runner just froze a PRIVATE tier-expanded catalog copy (core runtask.js constructor,
|
|
420
|
-
// dist-read) — in-place model-plane mutation no longer reaches it, so refresh-time plane changes must be
|
|
421
|
-
// DEFERRED to restart (see appliedPlaneEff / applyEffective deferModelPlane). Captured HERE, at the same
|
|
422
|
-
// config generation the constructor read.
|
|
423
199
|
const runnerTierFrozen = Object.keys(config.tiers).length > 0;
|
|
424
|
-
// hooks 阶段三b(design/HOOKS-PHASE3B-PROMPT-AGENT.md):prompt/agent 条目的模型调用载体。
|
|
425
|
-
// - hookLlm(prompt 条目)= 直连网关一次非流式 completion——一次判定调用不起 core task(重炮打蚊子
|
|
426
|
-
// 且引入可重入面);系统注入=事件分档(A-065 P3-10 订正,旧句「无系统注入」已过期):Stop/SubagentStop
|
|
427
|
-
// 注入 CC 逐字系统提示(cc-stop-prompt.ts,hook-runner.ts 的 extra.system),其余事件无注入。
|
|
428
|
-
// agent 条目现状零系统注入=A-065 P2-5 立案中(UNANCHORED,CC 该臂有自己的两档提示)。默认模型=
|
|
429
|
-
// summarize 角色(cheap tier,
|
|
430
|
-
// council/压缩同款;缺角色→主模型);entry.model 覆盖须命中部署 catalog(单用户=自家目录全开,与
|
|
431
|
-
// workflow 模型 allowlist 同纪律),未命中=该条目 fail(非阻断记账)。费用不折 task budget(hook=
|
|
432
|
-
// 部署策略面开销;hook_llm_calls_total/hook_llm_cost 由 metrics 记)。
|
|
433
|
-
// - hookAgent(agent 条目)= runner.runTask 读-only 子代理:handsReadOnly + enableFork:false +
|
|
434
|
-
// 不装 hooks(🔴 可重入禁——结构性:这个 spec 永不携带 TaskSpec.hooks)+ maxTurns 10 + maxCostUsd
|
|
435
|
-
// 0.05 独立小预算,影响范围受限(CC 该臂 M=50 步,收窄=已声明理由的正当偏离)。系统提示=CC
|
|
436
|
-
// hook_agent 两档逐字(cc-agent-hook-prompt.ts,A-065 P2-5 修;hook-runner 按事件铸、此处消费进
|
|
437
|
-
// spec.systemPrompt 整替形)。final result 文本即输出(与 prompt 同一 SyncHookOutput 决策面)。
|
|
438
|
-
// hookModelFor/hookLlm 抽到 src/hooks/hook-llm.ts(可注入 fetch/keyResolver ⇒ 可单测):api 形分派
|
|
439
|
-
// (openai /chat/completions 与 anthropic /v1/messages 双腿)、缺省 baseUrl 按 api 分家、per-model key
|
|
440
|
-
// 恒胜 + foreign-no-key 连 prompt 都不发的 fail-closed 纪律,全在该模块内(注释含 audit 原文)。
|
|
441
|
-
// getKeyResolver 取函数:keyResolver 住在 config-center 运行时的闭包里,registry 热应用会整个换引用,
|
|
442
|
-
// 这里必须每次现取。
|
|
443
200
|
const { hookModelFor, hookLlm } = createHookLlm({ config, getKeyResolver: () => configCenter.getKeyResolver(), metrics });
|
|
444
|
-
// hook agent 的 session 绝不进 durable store——每次 runTask 会写 session_meta 且无人
|
|
445
|
-
// 清理(reaper 只扫 staging orphan),cap=4/事件 × 长会话=无主行堆积。专用 Runner 覆盖 sessionStore 为
|
|
446
|
-
// 进程内 TTL 店(1h 短 TTL,hook 子代理无 resume 语义,行随进程/TTL 消失);其余 deps 原样共享。
|
|
447
201
|
const hookAgentRunner = new Runner({ ...runnerDeps, sessionStore: new TtlSessionStore({ defaultTtlDays: 1 / 24 }) });
|
|
448
202
|
const hookAgent = async ({ prompt, model, timeoutMs, system }) => {
|
|
449
203
|
const pick = hookModelFor(model);
|
|
@@ -454,19 +208,11 @@ async function main() {
|
|
|
454
208
|
objective: prompt,
|
|
455
209
|
sessionId: uuidv7(),
|
|
456
210
|
model: pick.catalogRef,
|
|
457
|
-
// A-065 P2-5:CC hook_agent 评估者系统提示(cc-agent-hook-prompt.ts 两档,hook-runner 按事件铸)。
|
|
458
|
-
// 整替形与 CC 对齐(CC 该臂 systemPrompt=O 整段)。maxOutputTokens 座刻意不消费:agent 载体
|
|
459
|
-
// 的输出预算=limits(maxTurns/maxCostUsd),非单次 completion 概念。
|
|
460
211
|
...(system !== undefined ? { systemPrompt: system } : {}),
|
|
461
212
|
handsReadOnly: true,
|
|
462
213
|
enableFork: false,
|
|
463
|
-
// core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);timeoutSec(秒)→ maxWalltimeMs(毫秒,原生同单位)
|
|
464
214
|
limits: { maxTurns: 10, maxCostUsd: 0.05, maxWalltimeMs: Math.max(30_000, timeoutMs) },
|
|
465
|
-
// spec 无 toolPolicy → core `hasEffectAwareGate` 为假 → 每次 agent hook 触发一条
|
|
466
|
-
// error 级 UNGATED 警告(非致命但纯噪声)。挂已裁决的 auto-accept 基线(与 singleUserAutoAcceptBaseline
|
|
467
|
-
// 同构;hook 子代理本就 handsReadOnly + 单用户信任面,auto-accept 是既定姿态,只是让闸机制在场)。
|
|
468
215
|
toolPolicy: createAllowDenyPolicy({}),
|
|
469
|
-
// 🔴 可重入禁:此 spec 不带 hooks 字段(deps.hooks 是部署观测 tracer,void 观察者,无决策面)。
|
|
470
216
|
});
|
|
471
217
|
metrics.inc("hook_llm_calls_total", { type: "agent" });
|
|
472
218
|
if (result.status !== "completed")
|
|
@@ -478,15 +224,12 @@ async function main() {
|
|
|
478
224
|
}
|
|
479
225
|
};
|
|
480
226
|
const clipHookErr = (s) => (s ?? "").slice(0, 300);
|
|
481
|
-
// Auto session titles — fire-and-forget at first
|
|
482
|
-
// submit, one cheap hookLlm call (summarize role → MODEL_CHEAP_ID when configured), write-once via the session
|
|
483
|
-
// store's setTitleIfNull (SQL twins + local sidecar carry it; the memory dev store doesn't → titler dark, honest).
|
|
484
227
|
const titleCapable = sessionStore;
|
|
485
228
|
const sessionTitler = config.sessionAutoTitle && titleCapable.setTitleIfNull && titleCapable.probeTitle
|
|
486
229
|
? createSessionTitler({
|
|
487
230
|
setTitleIfNull: titleCapable.setTitleIfNull.bind(sessionStore),
|
|
488
231
|
probeTitle: titleCapable.probeTitle.bind(sessionStore),
|
|
489
|
-
redact: redactSecrets,
|
|
232
|
+
redact: redactSecrets,
|
|
490
233
|
llm: (o) => hookLlm({ prompt: o.prompt, timeoutMs: o.timeoutMs, model: o.model }),
|
|
491
234
|
logger,
|
|
492
235
|
metrics,
|
|
@@ -494,60 +237,14 @@ async function main() {
|
|
|
494
237
|
: undefined;
|
|
495
238
|
if (sessionTitler)
|
|
496
239
|
logger.info("session_auto_title_enabled", {});
|
|
497
|
-
// ① workflowsCapability SINGLE-SOURCE (core 1.155 `workflowsCapability(deps)`; ruling = SPLIT two caps):
|
|
498
|
-
// `capabilities.workflows` = core's own truth (`workflowScriptRunner.safeForUntrustedScripts` ∧ governance), boot-
|
|
499
|
-
// computed from the SAME RunnerDeps the engine uses = the ENGINE-CAN axis ("can the engine self-orchestrate /
|
|
500
|
-
// ultracode"), ORTHOGONAL to the durable-LIST axis (`Boolean(workflowRunStore)`, surfaced as `workflowsList`).
|
|
501
|
-
// ⚠️ Today these COINCIDE — `workflowRunStore` is built iff self-orch (InMemory/File, ~L527), the same gate that
|
|
502
|
-
// wires the safe runner — so this is core's FORWARD-LOOKING contract, not a live-divergence fix (the earlier
|
|
503
|
-
// "memory backend diverges" premise was wrong; double-review caught it — see the cap site in http/server.ts).
|
|
504
|
-
// Static per deployment (deps are boot-fixed).
|
|
505
240
|
const workflowsCapable = workflowsCapability(runnerDeps);
|
|
506
|
-
// Sub-task runner (code-review council lenses/arbiter, subagent/discussion children). [1364]①/[1367]①(core
|
|
507
|
-
// 1.350 fork 终修的部署要求):fork 子任务带 `requireExistingSession:true` 在这里执行而源会话在 HOST 店
|
|
508
|
-
// ——拆店=响亮 `resume.session_not_found`(cli fork 案的 server 半场)。但整店共享被 codex 判两 high
|
|
509
|
-
// (普通子任务铸 committed durable 会话:无 reaper 覆盖=无界增长+污染 /v1/sessions 枚举),故走
|
|
510
|
-
// ForkRoutingSessionStore:fork/resume 形(requireExisting)→ host 店优先,普通子任务 → 私有 TTL 店
|
|
511
|
-
// (throwaway 姿势保留)。hookAgentRunner 仍保独立 TtlSessionStore(1h)——纯瞬态 hook 车道,与 fork 无关。
|
|
512
|
-
// CC-parity (CRITICAL — 子代理 hands, root cause is the service spawn path): the subagent delegation
|
|
513
|
-
// tool runs child tasks on THIS subRunner. Without executionEnvFactory the child's `handsEnabled` (= ownedEnv ||
|
|
514
|
-
// deps.executionEnv, prepare-task) is FALSE → the child gets NO hands (Bash/Read/Edit) and can't run `pwd` — the
|
|
515
|
-
// "subagent couldn't run pwd" defect hit in real TUI use. core's createSubagentTool gives hands when the env is
|
|
516
|
-
// wired (gate3: opts.tools:[] still yields Bash), so the fix is to give the subRunner the SAME executionEnvFactory +
|
|
517
|
-
// lspManager as the main runner. Deliberately NOT inherited: selfOrchestration (no recursive workflow spawn),
|
|
518
|
-
// scheduler, memory, fileSnapshot, runtimeCaps — a delegated child is a short focused task, not a full session.
|
|
519
|
-
// codex-2 F1:promptSource 必须随子代——core 1.341 把父的 centerArtifactDigest 传给委派子任务并在子
|
|
520
|
-
// runner 上按 digest 解析;缺席=每个 catalog 任务的 Agent 子代 fail-loud prompt_snapshot_unavailable。
|
|
521
|
-
// backgroundAgentStore(1.248 复审 F2):core prepare-task 给每个 task 的 TaskOutput/TaskStop/
|
|
522
|
-
// AgentTranscript 装 durable 臂用的是所在 runner 的 deps——子代任务跑在 subRunner 上,缺席=子代读
|
|
523
|
-
// 工具无 durable 回落(第三消费点,与 RunnerDeps/scenarioDeps 同一实例)。
|
|
524
|
-
// [1543]§三族A(core 穿透审查,历史欠账键批——主 runner 挂了、subRunner 漏挂 = 子代 lane 静默降级;
|
|
525
|
-
// 全部直引 runnerDeps 同一实例,双点永不漂移):
|
|
526
|
-
// - tiers(H):模型目录双 runner 分叉——tier 词 AgentDefinition 在子代 lane 抛 unknown_model、Agent
|
|
527
|
-
// model 参数("opus")静默 modelNote 降级、S3c revive 臂 row.model 记 tier 词时复活换模型。
|
|
528
|
-
// - rosterStore(M):具名 spawn 不落 durable 名册 → tier-3 按名复活从子代不可达。
|
|
529
|
-
// - onBackgroundChildEvent(M):子代触发的 revive/孙代对 fleet 流全盲(同一 publisher 实例=单管道)。
|
|
530
|
-
// - loadProjectMemory/probeInstructionSources(M):委派子代 project-blind(core types 已为子代 ctx
|
|
531
|
-
// 设计 isSubagent/agentName;门禁条件随 runnerDeps 条件键自动一致)。
|
|
532
|
-
// - hooks/onError/toolResultStore/sessionPolicyStore(L):子代进 tracer/计量;best-effort 故障不静默;
|
|
533
|
-
// fork 子代跨 wake 保 offload 引用;fork 形(有 sessionId)会话规则可达。
|
|
534
|
-
// 1877 注释的「Deliberately NOT inherited」清单(selfOrchestration/scheduler/memory/fileSnapshot/
|
|
535
|
-
// runtimeCaps)不变——本批只补漏配,不动裁定。
|
|
536
|
-
// (checkpointStore 的构造已再上移到 RunnerDeps 装配之前 —— design/170 件D 的留存能力校验要把它与
|
|
537
|
-
// session/tool-result 一起交给 core 的 `assertRetentionCapability`,而那道校验必须在拒启窗内跑完。)
|
|
538
|
-
// [1593] 提名成变量:parked 子代的 ensureChildSessionDurable 晋升半场要拿它做 promoteToHost
|
|
539
|
-
// (子会话住私有 TTL 店,park 前整树拷进 host durable 店——只查不迁的 1.263 形在拆店生产形下恒
|
|
540
|
-
// 否决 park,cli 真机 14ms~2s expired 即此)。
|
|
541
241
|
const subRunnerSessions = new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 }));
|
|
542
242
|
const subRunnerDeps = {
|
|
543
|
-
// [1543]§三族A 结构性根治(design/158 冲刺尾件):与主 runnerDeps 重复的 ~15 键统一走
|
|
544
|
-
// createSharedRunnerDeps 展开——「双点挂载」从人工纪律变结构性保证(新共享键漏配其一=不可能,
|
|
545
|
-
// 基座只有一份)。差异键在展开后显式列出,每个都有为何不同的理由(见 boot/runner-deps.ts 头注)。
|
|
546
243
|
...createSharedRunnerDeps({
|
|
547
244
|
config,
|
|
548
|
-
logger,
|
|
549
|
-
hands: commitHands,
|
|
550
|
-
mcpRevocations,
|
|
245
|
+
logger,
|
|
246
|
+
hands: commitHands,
|
|
247
|
+
mcpRevocations,
|
|
551
248
|
brain,
|
|
552
249
|
pricing,
|
|
553
250
|
tracer,
|
|
@@ -562,61 +259,24 @@ async function main() {
|
|
|
562
259
|
sessionPolicyStore,
|
|
563
260
|
usageWindowStore,
|
|
564
261
|
orgMemoryAdmission,
|
|
565
|
-
governanceSeams,
|
|
566
|
-
sharedMemoryStores: sharedMemoryStore ? sharedMemoryStore : undefined,
|
|
262
|
+
governanceSeams,
|
|
263
|
+
sharedMemoryStores: sharedMemoryStore ? sharedMemoryStore : undefined,
|
|
567
264
|
}),
|
|
568
|
-
|
|
569
|
-
sessionStore: subRunnerSessions, // 子代转录=私有短 TTL fork 路由店,生命周期异于宿主 durable 店
|
|
265
|
+
sessionStore: subRunnerSessions,
|
|
570
266
|
onBackgroundChildEvent: runnerDeps.onBackgroundChildEvent ? runnerDeps.onBackgroundChildEvent : undefined,
|
|
571
267
|
loadProjectMemory: runnerDeps.loadProjectMemory ? runnerDeps.loadProjectMemory : undefined,
|
|
572
268
|
probeInstructionSources: runnerDeps.probeInstructionSources ? runnerDeps.probeInstructionSources : undefined,
|
|
573
269
|
onError: runnerDeps.onError ? runnerDeps.onError : undefined,
|
|
574
|
-
// checkpointStore = 子代**执行面**的 park 设施([1582] cli TiDB 真验撞获缺口,[1584] core 定谳
|
|
575
|
-
// 接线步):core prepare-task `spec.checkpointStore ?? deps.checkpointStore` 里 spec 侧永不被
|
|
576
|
-
// subagent 设置,子代 suspendAsk 全靠这里——缺席 = 具名 bg 子代的 gated ask 只能 deny+continue,
|
|
577
|
-
// checkpoint 表零行(cli T2 复现原话「denied by an approval constraint」)。必须与 scenarioDeps
|
|
578
|
-
// (spawn 面 forwardDurableApproval 判定)**同一实例**:claim/expire/consumeParkedFlip 都作用于
|
|
579
|
-
// 同一行。旧 [1546] HIGH-2 撤载(「core 不转发 durableApproval + SAFETY ask 会 park 出搁浅
|
|
580
|
-
// checkpoint」)的前提被 core 1.388 件4(转发臂)消除,搁浅顾虑由件1-4+1.389 逐项闭合:watcher
|
|
581
|
-
// park 翻行 + reap 豁免 + reconciliation 兜孤儿;unattended SAFETY park 恒带缺省 deadline;
|
|
582
|
-
// TaskStop on parked 走 expire CAS 仲裁(1.389 F-1);/decide 可赎回。行为钉 =
|
|
583
|
-
// test/subagent-park-wiring.test.ts(SOURCE PIN + park 全链 + 旧形反向控制)。
|
|
584
270
|
checkpointStore: checkpointStore ? checkpointStore : undefined,
|
|
585
271
|
};
|
|
586
272
|
const subRunner = new Runner(subRunnerDeps);
|
|
587
|
-
// #196:`subRunner` 的无手孪生(同一份 deps 字面量摘掉 executionEnvFactory —— 差异面只有这一处,
|
|
588
|
-
// 结构上不可能漂移)。council 的 lens/arbiter、discuss 的成员/synthesizer 走它:那些子任务的人格声明
|
|
589
|
-
// 里一件工具都没有,却在修前照拿全量可写手(设计小票 §1 里最意外的一支)。
|
|
590
|
-
// ⚠️ 与主 subRunner 共享 `subRunnerSessions` 与 `checkpointStore` 同实例 —— park/fork 的行是同一批。
|
|
591
273
|
const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
|
|
592
|
-
// S6: resolve identity + owned session from the authenticated channel (never the body).
|
|
593
274
|
const authorize = createAuthorizer(config, sessionStore);
|
|
594
|
-
// S1/S2: durable async run registry + replayable event log (TiDB-backed).
|
|
595
275
|
const runStore = backend ? backend.run() : undefined;
|
|
596
|
-
// E18 resume-at: the eventId→entryId anchor map. REQUIRED on every backend (works local — needs only the session
|
|
597
|
-
// tree + this map, no cloud-only checkpoint), so the only undefined case is the env-only/no-backend deploy (which
|
|
598
|
-
// also has no async runs — consistent). The shell sends body.resumeAt=eventId; resolveSpec resolves it here.
|
|
599
276
|
const resumeAnchorStore = backend ? backend.resumeAnchor() : undefined;
|
|
600
|
-
// Per-session approval exemption ("本会话不再询问") — REQUIRED on every backend like the anchor
|
|
601
|
-
// map; undefined only on the env-only/no-backend deploy (which has no durable approvals either — consistent).
|
|
602
277
|
const approvalExemptionStore = backend ? backend.approvalExemption() : undefined;
|
|
603
278
|
const instanceId = uuidv7();
|
|
604
|
-
// Per-task wall-clock override (seconds). 0/absent = keep the tenancy default (see taskWallClockSec:
|
|
605
|
-
// 2400s, 3600s for the big lane = council/debate/discuss) —
|
|
606
|
-
// the override only ever RAISES (Math.max at the use site), so a misconfigured low value cannot shrink
|
|
607
|
-
// the council budget.
|
|
608
|
-
// 🔴 2026-08-01 env fail-loud 族:原为 `Math.max(0, Math.floor(Number(env ?? "0")) || 0)` ——
|
|
609
|
-
// `TASK_TIMEOUT_SEC="3600s"`(带单位后缀,运维最常见的写法)得 NaN,再被 `|| 0` 折成 0,
|
|
610
|
-
// 而 0 在这里的语义是**无超时**。运维以为设了一小时上限,实际把超时关掉了,方向恰好相反,
|
|
611
|
-
// 且启动期零提示。改走 numEnv:非法值当场 fail-loud 并指名键与实际值。
|
|
612
|
-
// (numEnv 的注释早就预言了这个失败模式,只是它没 export、跨文件够不到,于是这两处手写了。)
|
|
613
279
|
const taskTimeoutSec = Math.max(0, Math.floor(numEnv("TASK_TIMEOUT_SEC", "0")));
|
|
614
|
-
// [854]④ per-request 配速的运营方上限旋钮(可选;缺省不设=不封顶):body.limits 每键各自被对应旋钮
|
|
615
|
-
// Math.min 封顶(normalizeLimits)。多租部署想约束 caller 自报配速时才设;单用户 turnkey 通常留空。
|
|
616
|
-
// ⚠️ 语义=只钳「显式请求值」,不是全队默认限额:body 缺席(或重放体里的非法值被 defensive DROP)⇒ 该键
|
|
617
|
-
// 不设限,与升级前行为同形——要给所有任务强加限额是另一个旋钮(未建),别指望这三个(交叉评审 F1 判读)。
|
|
618
|
-
// 缝合审 M4:非法值仍 fail-open(不设限),但**必须出声** —— 这三个旋钮的全部用途就是封顶,
|
|
619
|
-
// 打错一个字符让封顶静默消失是它最坏的失败形。判据单一真源在 config.ts 的 parseCapEnv。
|
|
620
280
|
const capEnv = (name) => {
|
|
621
281
|
const r = parseCapEnv(name, process.env[name]);
|
|
622
282
|
if (r.invalid !== undefined) {
|
|
@@ -633,41 +293,11 @@ async function main() {
|
|
|
633
293
|
maxOutputTokens: capEnv("TASK_MAX_OUTPUT_TOKENS_MAX"),
|
|
634
294
|
maxTurns: capEnv("TASK_MAX_TURNS_MAX"),
|
|
635
295
|
};
|
|
636
|
-
// [2354]/[2356] A-5(5.0.0):F4 轮询门(approvalStore pending 行)整体退役——durable checkpoint 是唯一门形。
|
|
637
|
-
// Finding #2 (edge case): the single-user auto-accept tool-policy BASELINE applies ONLY when the operator
|
|
638
|
-
// expressed NO gate intent. If they SET approval flags (APPROVAL_REQUIRE/APPROVAL_DENY/DURABLE_APPROVAL) but no
|
|
639
|
-
// backing store is wired (e.g. DB_BACKEND=local → no checkpointStore ⇒ durableEnabled
|
|
640
|
-
// false), that is a MISCONFIG — do NOT mask it with allow-all; leave toolPolicy undefined. So the baseline needs
|
|
641
|
-
// single-user turnkey ∧ zero expressed gate intent.
|
|
642
|
-
// 🔴 #177 后本段的**信号面**已换人(原文「so core's UNGATED warning still surfaces it」已过期):守卫集
|
|
643
|
-
// (SENSITIVE_WRITE_PATTERNS,缺省非空)现在从 governance 拍无条件铸一条 DENY 基线 ⇒ 缺省部署的
|
|
644
|
-
// `spec.toolPolicy` 恒在场 ⇒ core 的 `hasEffectAwareGate`(prepare-task,判据是 policyLayers.length>0)
|
|
645
|
-
// 恒为真,那条 UNGATED onError 不再触发。补偿件在 boot/resolve-spec.ts:同一形(durable 门关 ∧ 单用户
|
|
646
|
-
// 基线不适用 ∧ 守卫集开)在 boot 期发一次 `tool_policy_only_sensitive_baseline` warn,把「守卫段之外
|
|
647
|
-
// 无裁决」这件事按我们自己的话说出来。本行留 `undefined` 仍是对的(不拿 allow-all 掩盖 misconfig),
|
|
648
|
-
// 只是它不再是那条信号的产地。
|
|
649
|
-
// 单源在 approval.ts(与 inv#2 同文件)—— 这里原是内联的三格枚举,漏了 approvalNeverAuto,详见该函数顶注。
|
|
650
296
|
const operatorGateIntent = hasOperatorGateIntent(config);
|
|
651
|
-
// D3-F1(全窗复审,HIGH):门意图已表达但唯一门形(durable)不可服务 ⇒ boot 拒启带双出路指引。
|
|
652
|
-
// 5.0.0 前这形走轮询门真拦;退役后若只留 core 每任务 warn=原先拦、现在放,升级存量静默失守。
|
|
653
297
|
assertGateIntentServiceable(config, checkpointStore !== undefined);
|
|
654
298
|
const singleUserAutoAcceptBaseline = config.requirePrincipal !== true && !operatorGateIntent;
|
|
655
|
-
// design/45 durable-checkpoint (core 1.67): opt-in (DURABLE_APPROVAL + a pool). When on, F4 uses durable
|
|
656
|
-
// suspend/resume instead of the poll gate — a gated `ask` suspends the task (status:"suspended" + checkpoint)
|
|
657
|
-
// and an operator resumes it on any replica. Default off → unchanged poll-policy.
|
|
658
|
-
// checkpointStore 构造已上移到 subRunner 之前([1535] server 半场:子代 park 设施);本行原位注释保留。
|
|
659
|
-
// TOC integration ask③ (INTEGRATION-DESIGN §11): reclaim runs orphaned by a previous host-lane engine
|
|
660
|
-
// process (orphaned `running` → failed + release its EEXIST-blocking claim; exclude resume-in-progress rows). The
|
|
661
|
-
// logic + its null-safety (runStore undefined on the env-only/no-backend lane) live in the unit-tested
|
|
662
|
-
// `reclaimOrphansAtBoot` helper — extracted after a workflow re-review caught a boot crash there (HIGH, 1.49.0).
|
|
663
299
|
await reclaimOrphansAtBoot(runStore, checkpointStore, logger);
|
|
664
|
-
|
|
665
|
-
// of the bake manifests + publish lifecycle. Present whenever a pool is — read-only catalog until P2/P3 bakes.
|
|
666
|
-
const imageIndex = backend?.imageIndex ? backend.imageIndex() : undefined; // local omits imageIndex() → sandbox-image-pool is cloud/fleet-only
|
|
667
|
-
// RFC A2 (A 切片先行): the model-facing SelectEnvironment tool — mounted via spec.tools
|
|
668
|
-
// (RFC 判定②(a)) whenever the per-task image chain is actually usable (k8s backend + image catalog), opt-out
|
|
669
|
-
// via SELECT_ENVIRONMENT_TOOL=false. Viewer scope is rebuilt per-execute from the Runner-threaded read-only
|
|
670
|
-
// ctx.principal (auth-channel-derived — a tool argument can never widen visibility).
|
|
300
|
+
const imageIndex = backend?.imageIndex ? backend.imageIndex() : undefined;
|
|
671
301
|
const selectEnvTool = config.selectEnvironmentTool && config.remoteExec?.provider === "k8s" && imageIndex
|
|
672
302
|
? selectEnvironmentTool({
|
|
673
303
|
catalog: imageIndex,
|
|
@@ -675,28 +305,11 @@ async function main() {
|
|
|
675
305
|
viewerFor: (principal) => ({ operator: explicitOperatorOk(principal, config.operatorPrincipals), tenantId: principal ?? null }),
|
|
676
306
|
})
|
|
677
307
|
: undefined;
|
|
678
|
-
|
|
679
|
-
// AND IMAGE_BAKES_ENABLED. The config boot-invariant already fail-fasts an empty OPERATOR_PRINCIPALS (a
|
|
680
|
-
// world-writable build-host-RCE door). image-api stays the UNPRIVILEGED controller; the privileged build runs
|
|
681
|
-
// on the sema-registry-managed bake-runner worker on build-host (it claims + ingests over the internal routes).
|
|
682
|
-
const imageBakes = backend?.imageBake && config.imageBakes.enabled ? backend.imageBake() : undefined; // local omits imageBake() → cloud/fleet-only
|
|
308
|
+
const imageBakes = backend?.imageBake && config.imageBakes.enabled ? backend.imageBake() : undefined;
|
|
683
309
|
if (imageBakes)
|
|
684
310
|
logger.info("image_bakes_enabled", { operators: config.operatorPrincipals.length, runner: config.imageBakes.runnerPrincipal });
|
|
685
|
-
// Durable HITL surface = checkpointStore ALONE (DURABLE_APPROVAL=true). It mounts AskUserQuestion-suspend (the
|
|
686
|
-
// agent asks the human + the portal approval center) AND the resumable checkpoint. The F4 TOOL-APPROVAL
|
|
687
|
-
// gate is an ADDITIVE layer driven by APPROVAL_REQUIRE — empty = no tool is gated, but AskUserQuestion HITL
|
|
688
|
-
// still works. (Decoupled 2026-06-13: previously these were coupled, forcing a placeholder APPROVAL_REQUIRE
|
|
689
|
-
// to get HITL — clay: OA writes don't gate (OA-backend RBAC is the authority), but portal HITL must work.)
|
|
690
|
-
// durableEnabled 已在 runnerDeps 前按同一条件求值([1.294 G1] 前置声明处)——checkpointStore 构造条件与其
|
|
691
|
-
// 同源,恒有 durableEnabled === !!checkpointStore。
|
|
692
311
|
if (durableEnabled)
|
|
693
312
|
logger.info("durable_approval_enabled", { toolApprovalGate: config.approvalRequire.length });
|
|
694
|
-
// DESIGN-269 车2 件3 —— server-as-peer 上场的**感知链路**(#266 memory_persistence_capable_auto_declared
|
|
695
|
-
// 同族纪律:一条对外暴露面上了线,必须在 boot 日志里一眼可核)。这行是 `A2A_SERVE_ENABLED=true` **唯一**
|
|
696
|
-
// 的启动期回声:运维据此确认「这台机现在对外是一个 A2A agent、卡在这个路径上、公告的是这个 URL、
|
|
697
|
-
// 声明了几条 skill」。OFF(默认)⇒ 一个字都不发,零噪声。
|
|
698
|
-
// ⚠️ 半配置的诚实:开了旋钮却没有 durable run store 时,两个 RPC 方法会回具名 -32004 —— 那不是静默
|
|
699
|
-
// 降级,但它发生在**调用时**,而运维想在启动时就知道。所以这一行连带把 runStore 在不在场说出来。
|
|
700
313
|
if (config.a2aServe) {
|
|
701
314
|
logger.info("a2a_serve_enabled", {
|
|
702
315
|
card: "/.well-known/agent-card.json",
|
|
@@ -704,89 +317,41 @@ async function main() {
|
|
|
704
317
|
advertisedUrl: config.a2aServe.url,
|
|
705
318
|
skills: config.a2aServe.skills.length,
|
|
706
319
|
blockingWaitMs: config.a2aServe.blockingWaitMs,
|
|
707
|
-
// false = 卡照发(发现面不依赖 store),但 message/send 与 tasks/get 会回 -32004 UnsupportedOperation。
|
|
708
320
|
tasksUsable: runStore !== undefined,
|
|
709
321
|
});
|
|
710
322
|
}
|
|
711
|
-
// 修4(三路复审 W3):the fs-write ask gate (resolveSpec, applyTaskSettings wiring below) mounts purely off the
|
|
712
|
-
// host-semantics lane — DELIBERATELY decoupled from any approval face, so no assembly path is ever gateless:
|
|
713
|
-
// with no live coordinator and no durable park, a permissionMode default/auto Write/Edit ask fail-closed
|
|
714
|
-
// auto-denies via core resolveAsk (onAsk absent ⇒ headless auto-deny, loud in-transcript). What that posture
|
|
715
|
-
// must NOT be is silent at boot: an operator who explicitly set TOOL_APPROVAL_ENABLED=false (or a multi-tenant
|
|
716
|
-
// default) would otherwise read the resulting hand-tool denials as an outage. Warn once, at boot.
|
|
717
323
|
if ((config.remoteExec === undefined || config.remoteExec.provider === "host") && !toolApproval && !durableEnabled) {
|
|
718
324
|
logger.warn("fs_write_gate_no_responder", {
|
|
719
325
|
hint: "host-semantics lane with no approval face: permissionMode default/auto Write/Edit asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
|
|
720
326
|
});
|
|
721
327
|
}
|
|
722
|
-
// [1245]/[1248]② the workflow ask leg attaches on EVERY lane (not fs-dependent — see createWorkflowAskPolicy),
|
|
723
|
-
// so a sandbox-lane deployment running self-orchestration with no approval face hits the same fail-closed
|
|
724
|
-
// auto-deny class. Same loud-at-boot posture; host lanes are already covered by the broader warn above.
|
|
725
328
|
if (config.selfOrchestrationEnabled && config.remoteExec !== undefined && config.remoteExec.provider !== "host" && !toolApproval && !durableEnabled) {
|
|
726
329
|
logger.warn("workflow_gate_no_responder", {
|
|
727
330
|
hint: "self-orchestration with no approval face: permissionMode default/auto/acceptEdits run_workflow asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
|
|
728
331
|
});
|
|
729
332
|
}
|
|
730
|
-
// Finding #2: a single-user turnkey worker with no explicit approval/durable/commandPolicy gets an
|
|
731
|
-
// ADJUDICATED auto-accept tool-policy BASELINE (createAllowDenyPolicy — a PRESENT effect-aware policy, so core's
|
|
732
|
-
// write-capable hand tools are never UNGATED / unadjudicated). Log the posture so the auto-accept is OBSERVABLE,
|
|
733
|
-
// not silent; tighten irreversible ops via APPROVAL_REQUIRE / AUTONOMY / commandPolicy (layered tighten-only).
|
|
734
333
|
if (singleUserAutoAcceptBaseline) {
|
|
735
|
-
// Blanket auto-accept over ALL write/hand/MCP tool surfaces (core's per-surface UNGATED enumeration — hand band /
|
|
736
|
-
// destructive-MCP / first-party-write — is suppressed for single-user turnkey by design). Log it so the posture is
|
|
737
|
-
// observable; tighten (which also re-surfaces per-surface gating) via APPROVAL_REQUIRE / AUTONOMY / commandPolicy.
|
|
738
334
|
logger.info("tool_policy_single_user_default", {
|
|
739
335
|
posture: "adjudicated auto-accept — ALL write/hand/MCP tool surfaces auto-allowed (core per-surface UNGATED enumeration suppressed for single-user turnkey)",
|
|
740
336
|
tighten: "APPROVAL_REQUIRE / AUTONOMY / commandPolicy (re-surfaces + gates specific surfaces)",
|
|
741
337
|
});
|
|
742
338
|
}
|
|
743
|
-
// D-G: a boot-time signal that this worker is in crypto-bound direct-door mode (trusted-header path disabled).
|
|
744
|
-
// Pairs with the /health directDoorActive field for the DG-M2 go-live runbook (a positive go signal, not just
|
|
745
|
-
// a behavioral 401). The boot invariant in config.ts already fail-fasts a half-configured direct door.
|
|
746
339
|
if (config.directDoorActive)
|
|
747
340
|
logger.info("direct_door_active", { iss: config.principalJwtIss, aud: config.principalJwtAud, operators: config.operatorPrincipals.length });
|
|
748
|
-
// CC-parity (adversarial-review finding): a MULTI-TENANT deployment running self-orchestration WITHOUT worker
|
|
749
|
-
// isolation is an availability risk — the in-process hardened VM is ESCAPE-isolated but NOT RESOURCE-isolated, so an
|
|
750
|
-
// entitled tenant's workflow script can starve the host event loop (microtask loop / OOM), affecting every tenant
|
|
751
|
-
// (src/orchestration/hardened-vm-runner.ts documents this). Loud boot warning so the operator sets
|
|
752
|
-
// SELF_ORCHESTRATION_WORKER_ISOLATION=true. (Single-user is unaffected — the one principal owns its own worker.)
|
|
753
341
|
if (config.requirePrincipal && config.selfOrchestrationEnabled && !config.selfOrchestrationWorkerIsolation)
|
|
754
342
|
logger.warn("self_orchestration_no_worker_isolation", { detail: "multi-tenant self-orchestration without SELF_ORCHESTRATION_WORKER_ISOLATION=true — a tenant workflow can starve the host event loop (availability risk). Set it to enable worker_thread resource isolation." });
|
|
755
|
-
// 🔒 (systematic-audit HIGH fix, defense-in-depth): a multi-tenant deployment (requirePrincipal) with NO service/trace
|
|
756
|
-
// token now correctly fails CLOSED — fleet/trace/session-list are owner-gated, never fleet-wide (isFleetWide, server.ts).
|
|
757
|
-
// Warn the operator: without SERVICE_AUTH_TOKEN/authTokens/TRACE_TOKEN those endpoints only ever return the caller's own
|
|
758
|
-
// rows (safe, but may surprise an operator expecting cross-tenant ops visibility — set a token for ops-relay).
|
|
759
343
|
if (config.requirePrincipal && !config.authToken && Object.keys(config.authTokens ?? {}).length === 0 && !config.traceToken)
|
|
760
344
|
logger.warn("multi_tenant_no_service_token", { detail: "REQUIRE_PRINCIPAL is on but no SERVICE_AUTH_TOKEN/authTokens/TRACE_TOKEN — fleet/trace/session-list are owner-gated only (never fleet-wide, tenant-isolation safe). Set a token for cross-tenant ops visibility." });
|
|
761
|
-
|
|
762
|
-
// when a pool exists, else in-memory per-replica. Both satisfy RateGate. (design/30 P0-2)
|
|
763
|
-
//
|
|
764
|
-
// 🔴 #322 批1:与 costQuota 同一条改造(恒构造 + 0=关断哨兵)——修前 `rateLimitPerMin > 0` 才构造,
|
|
765
|
-
// 默认部署零实例,于是热改 0→N 无座可 set。恒构造之后 0↔N 是纯值变(boot/limit-sync.ts 一次 setLimits),
|
|
766
|
-
// 关断态由座位自己表达:恒放行、不记窗、SQL 腿不起刷新环(零 SQL)。
|
|
767
|
-
const rateLimiter = backend?.rateLimiter // local omits rateLimiter() → in-memory per-process RateLimiter (single-replica)
|
|
345
|
+
const rateLimiter = backend?.rateLimiter
|
|
768
346
|
? backend.rateLimiter(config.rateLimitPerMin, counterDegradeHook).startRefresh()
|
|
769
347
|
: new RateLimiter(config.rateLimitPerMin);
|
|
770
|
-
// Capability layer: scenario → {tools (+ subagent roster), prompt, skills}. Heavy bits (skills,
|
|
771
|
-
// git client) are built ONCE here; per request we only select a scenario and bind coordinates.
|
|
772
348
|
let skills = loadSkills(config.skillsDir);
|
|
773
|
-
// design/158 A10 尾刀:center skills overlay + plugins 消费 + boot LKG 落盘/skill-stale 判定 + mcp
|
|
774
|
-
// 解析搬到 boot/config-center.ts(逐字)。⚠️ 位置即契约:loadSkills 之后、buildScenarios 之前 ——
|
|
775
|
-
// LKG 落盘点必须晚于 skill 正文装载(F7/codex R26),plugins 让位判据要求 plugins 晚于 center 直发 skills。
|
|
776
349
|
skills = await configCenter.applyCenterCapabilities(skills);
|
|
777
350
|
const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
|
|
778
|
-
// CC-parity: deployment-injected WebSearch backend (the leg core leaves open). Absent WEB_SEARCH_PROVIDER →
|
|
779
|
-
// undefined → the default scenario doesn't assemble the WebSearch tool. The API key stays in the backend closure.
|
|
780
351
|
const webSearchCfg = webSearchConfigFromEnv();
|
|
781
352
|
const webSearch = webSearchCfg ? createWebSearchBackend(webSearchCfg) : undefined;
|
|
782
353
|
if (webSearch)
|
|
783
354
|
logger.info(`WebSearch enabled (provider=${webSearchCfg.provider}, maxResults=${webSearch.maxResults})`);
|
|
784
|
-
// #81④ 探活(boot 期一次性,**默认 OFF**):部署方此前只能靠「跑一个真任务」验证搜索配没配对 ——
|
|
785
|
-
// cli 的一键装 SearXNG 向导([2226] 裁 1)装完那一步正需要这个。消费 core 的 `probeSearchBackend`
|
|
786
|
-
// (不自铸第二真源,与 searxng adapter 同一条纪律)。
|
|
787
|
-
// 🔴 **失败只 warn,不拒启**:搜索是可选工具,配错不该挡住整个服务(功能型能力缺席 ⇒ 降级;
|
|
788
|
-
// 与本仓「保护型 ⇒ fail-closed」的方向判据是同一条判据的两面)。
|
|
789
|
-
// 🔴 **不 await**:探活是一次真出网,让它拖住 boot 就是把一个可选工具变成启动路径上的依赖。
|
|
790
355
|
if (webSearch && shouldProbeWebSearchOnBoot()) {
|
|
791
356
|
void probeSearchBackend(webSearch.search, { timeoutMs: 10_000 })
|
|
792
357
|
.then((r) => {
|
|
@@ -797,56 +362,16 @@ async function main() {
|
|
|
797
362
|
})
|
|
798
363
|
.catch((e) => logger.warn("web_search_probe_failed", { provider: webSearchCfg.provider, error: e instanceof Error ? e.message : String(e) }));
|
|
799
364
|
}
|
|
800
|
-
// core 1.382([1561] 提货单①②,design/153 parked 状态机)「同车必接」的 ensureChildSessionDurable 挂点:
|
|
801
|
-
// 只在 checkpointStore 真在场时构造(与 core parkEligible 门「两者全在场才成立」的判据同源——checkpoint
|
|
802
|
-
// 不在场时构造这个闭包毫无意义,连带把「local/无 durable backend 部署不该跑这条 acquire/release」的
|
|
803
|
-
// 边界直接编码进构造条件,不用额外判断)。实现体拆到 `ensureSessionDurable`(session-store.ts,可独立
|
|
804
|
-
// 测试;双复审②抓的 F-7 同族空壳缺口——host 店可能存在 `register()` 留下的零内容 session_meta 行——
|
|
805
|
-
// 的校验也在那边)。
|
|
806
|
-
// 🔴 [1575] F3 修(cli 复查,红先行确认真红):`checkpointStore` 在场 ⇏ `sessionStore` 就是 TiDB 真店
|
|
807
|
-
// ——这是两个独立 config 轴(DB_BACKEND 供 checkpoint/runs 等用途 vs SESSION_BACKEND 单独控 session
|
|
808
|
-
// 落哪),`SESSION_BACKEND` 显式留在默认值时 `sessionStore` 是纯内存 `TtlSessionStore`(无 repo 构造,
|
|
809
|
-
// core `release()` 语义在该形态下是**整删会话**,不是"归还租约")——探针的 finally release 会销毁它
|
|
810
|
-
// 刚证明存在的会话。追加门 `config.sessionBackend === "tidb"`(此刻已被前面装配区从 "auto" 归一,
|
|
811
|
-
// 恒是 "tidb"|"memory" 二选一,时序安全):唯一保留可达的店形正是 release() 真安全的那种
|
|
812
|
-
// (TiDBSessionStore.release 逐字注释「Durable history is retained; just drop any in-flight cache
|
|
813
|
-
// entry」)。非 tidb 场景下 undefined = 子代按 pre-153 settle failed,零回归(与 checkpointStore 缺席
|
|
814
|
-
// 同一处置)。
|
|
815
|
-
// [1593] 只查不迁 → 主动晋升(cli TiDB 真机撞获:bg 子会话住 subRunner 私有 TTL 店、从不在 host 店,
|
|
816
|
-
// 旧形恒 veto → core 当场 expire checkpoint(14ms~2s)+ settle failed,赎回窗口不存在)。晋升链=
|
|
817
|
-
// 快查 host → promoteToHost(整树拷贝+私店 forget+路由记 host)→ 同一严检复验(leaf 非空);任何
|
|
818
|
-
// 一步失败仍如实 veto(pre-153 行为,不放大)。tidb 门与 F3 裁定不变。
|
|
819
365
|
const ensureChildSessionDurable = checkpointStore && config.sessionBackend === "tidb" ? (sessionId) => ensureChildSessionDurableWithPromotion(sessionStore, subRunnerSessions, sessionId) : undefined;
|
|
820
|
-
// [1900]/[1904] WebFetch 摘要器:core 工厂 + summarize 角色模型(cheap tier;resolveTaskModel 自带
|
|
821
|
-
// 默认回落——与压缩线同族档位,不新造)。构造一次全场景共享;多租户形 WebFetch 整体被 filter,
|
|
822
|
-
// 本注入无效化由 scenarios 现行为保证。惰性解析 / fail-soft(E-MED-2/E-MED-3)与本席的 key plane
|
|
823
|
-
// (A-057.5:#303 的第四条直发面)都在 `boot/webfetch-summarize-lane.ts`,顶注写着全部判据与红先读数。
|
|
824
366
|
const webFetchSummarize = createWebFetchSummarizeLane({ brain, config, getKeyResolver: () => configCenter.getKeyResolver(), logger });
|
|
825
|
-
// design/158 S4:标注 `: ScenarioDeps` —— 无标注的字面量连自己的键名都不校验(打错/多写一个键
|
|
826
|
-
// 只是多一个没人读的属性,编译期无声),这是 1.304 落错家那一族的另一半土壤。
|
|
827
367
|
const scenarioDeps = {
|
|
828
368
|
runner, subRunner, handslessSubRunner, model: "default", skills, repoClient, requirePrincipal: config.requirePrincipal,
|
|
829
369
|
webSearch: webSearch ? webSearch : undefined, metrics, logger, webFetchSummarize,
|
|
830
|
-
// core 1.364 写半场:与 RunnerDeps.backgroundAgentStore 同实例(组装区注释;半接=静默死特性)。
|
|
831
370
|
backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
|
|
832
|
-
// core 1.382 parked 状态机「同车必接」一对——checkpointStore 与 main runner 的 checkpointStore(subRunner
|
|
833
|
-
// 构造区上方 const)同实例;ensureChildSessionDurable 见上方构造闭包注释。
|
|
834
371
|
checkpointStore: checkpointStore ? checkpointStore : undefined,
|
|
835
372
|
ensureChildSessionDurable: ensureChildSessionDurable ? ensureChildSessionDurable : undefined,
|
|
836
|
-
// #318([4659] F2 → [4692] 定谳):会话级任务清单店解析器(boot/task-list-lane.ts 的产物,
|
|
837
|
-
// openStores 里按后端三态构造一次)。修前不接这一键 ⇒ core 每 turn 铸一只私有内存店。
|
|
838
373
|
taskListStoreFor: taskListLane.storeFor,
|
|
839
374
|
brandIdentity: config.configProvider === "local",
|
|
840
|
-
// core 1.287 extraTools seam ([802]③ server half): SendUserFile reaches the delegated-child roster. SAME
|
|
841
|
-
// existence gate as the top-level mount (the one sendUserFileToolSpec built above — lane/tenancy/config
|
|
842
|
-
// posture already encoded in its presence). The spec is execute-time late-bound (ctx.taskId →
|
|
843
|
-
// TaskEnvRegistry lookup; emit via ALS to the top-level frame tail), so returning the SAME instance per
|
|
844
|
-
// spawn is correct — per-spawn state lives in the registry, not the spec ([797] confirmed with core).
|
|
845
|
-
// codex F1/F2 gates (subagentSendUserFileExtraTools): (F1) the subRunner carries no toolPolicy and core
|
|
846
|
-
// does not propagate the parent's policy into child specs, so an APPROVAL_DENY/ask-gated SendUserFile was
|
|
847
|
-
// reachable from delegated children — the factory now pre-checks the LIVE deployment approval face
|
|
848
|
-
// (deny/require/neverAuto, canonical-space) and injects nothing when gated (conservative stopgap until a
|
|
849
|
-
// core child-policy seam lands); (F2) read-only builtin agents (Explore/Plan) never receive the tool.
|
|
850
375
|
subagentExtraTools: sendUserFileToolSpec
|
|
851
376
|
? subagentSendUserFileExtraTools({
|
|
852
377
|
spec: sendUserFileToolSpec,
|
|
@@ -855,34 +380,7 @@ async function main() {
|
|
|
855
380
|
: undefined,
|
|
856
381
|
};
|
|
857
382
|
const scenarios = buildScenarios(scenarioDeps);
|
|
858
|
-
// design/153 件3d:/decide parked 赎回腿的裸 Agent 工具——boot 期从**内建 default** 场景工厂取一份
|
|
859
|
-
// (必须在 center overlay 盖名之前取:赎回腿要的是与 spawn 同源的生产装配,subRunner/bg opts/
|
|
860
|
-
// checkpointStore 同实例天然成立;per-request 项如 webSearch 对 revive 无关——objective 在 core
|
|
861
|
-
// driveParkedResume 里被丢弃)。checkpointStore/agentStore 任一缺席 = park 本就不可达,分支不接线。
|
|
862
|
-
// #318:这条取用**刻意不带** ScenarioContext —— boot 期没有任何一条真会话,给它编一个假 sessionId
|
|
863
|
-
// 会在清单表里铸出一份无人认领的分区。它只取裸 Agent 工具,task-list 家族在这份 bundle 里根本不被
|
|
864
|
-
// 消费([4692]③ 已核),缺席 = 修前字节形。
|
|
865
|
-
// A-067 C3-3(F1b):裸 "Agent" 字面量换 core 单源常量——runtime-governance.ts 同概念已用
|
|
866
|
-
// DEFAULT_SUBAGENT_TOOL_NAME(5.0.0 RB-476 旧名 "Task" 改名史=这个字面量真漂过一次)。
|
|
867
383
|
const parkedReviveTool = checkpointStore && backgroundAgentStore ? scenarios.default?.({})?.tools.find((t) => t.name === DEFAULT_SUBAGENT_TOOL_NAME) : undefined;
|
|
868
|
-
// [1596]/[1597] 跨副本父约束重供席工厂:host 任务的 toolPolicy 解析槽按**当前**部署配置重建
|
|
869
|
-
// 「部署 ⊇ 操作员」两层完整链(design/181 件二收编;实现与全部理由在 boot/parked-revive-gate.ts,
|
|
870
|
-
// 提出去的唯一理由是 main.ts 顶层 `void main()` 让那条腿的运行期语义在原地一格都钉不住)。
|
|
871
|
-
// 构造条件逐字保持:裸 Agent 工具在场 ∧ 部署真开了 durable 审批。
|
|
872
|
-
// `approverSeat`:与 `RunnerDeps.onAsk` **同一个具名工厂**(单一属主,禁在此处手搓等价闭包)。core
|
|
873
|
-
// 5.22.0 起链条目的 `durableMandate` 位进了摘要,而该位的判据正是这只席位在不在场——两处若不同源,
|
|
874
|
-
// 赎回腿算出的摘要与 park 记的对不上,跨副本赎回整条腿被 pre-CAS 拒(见 parked-revive-gate.ts
|
|
875
|
-
// `mandatePostureOf`)。此处新铸的转发闭包与 runnerDeps 那只行为逐字相同(都只转 `toolApproval.ask`),
|
|
876
|
-
// 摘要只看在场性;仅 core 的 hook 席位去重按函数身份判,那一侧的身份未命中是**多筛一次**(core 自述
|
|
877
|
-
// 的保守方向),不是漏筛。
|
|
878
|
-
// A-010.1 同族的另外两位(`resolveRuntimeCaps` / `autoModeSeatMounted`):5.22.0 的摘要卷进的是**三**位
|
|
879
|
-
// 决议链元数据,其中 `autoModeArmed` 与 `durableMandate` 的 `forceDurableGate` 项都由 per-principal 的
|
|
880
|
-
// `RuntimeCaps` 决定。两处必须与 core 看到的**同一只**事实源接线,否则同样是「park 记 true 而赎回腿供
|
|
881
|
-
// false ⇒ 摘要恒不匹配 ⇒ 这批 principal 的卡永不可赎」:
|
|
882
|
-
// · `resolveRuntimeCaps` = 递给 `RunnerDeps.runtimeCapsResolver` 的同一只(单一属主 boot/runtime-caps.ts);
|
|
883
|
-
// · `autoModeSeatMounted` = **读** runnerDeps 上那一位,不写字面 `true` —— core 的武装式是
|
|
884
|
-
// `runtimeCaps.autoMode === true ∧ deps.autoMode !== undefined`,第二个半场的真值只有 runnerDeps 知道,
|
|
885
|
-
// 在此处手抄 `true` 会在 runner-deps 那边改成有条件挂载的那一天静默错供一位。
|
|
886
384
|
const parkedReviveInheritedGate = parkedReviveTool && config.durableApproval
|
|
887
385
|
? createParkedReviveInheritedGate({
|
|
888
386
|
config, question, approvalExemptionStore, logger, localRoot,
|
|
@@ -891,20 +389,11 @@ async function main() {
|
|
|
891
389
|
autoModeSeatMounted: runnerDeps.autoMode !== undefined,
|
|
892
390
|
})
|
|
893
391
|
: undefined;
|
|
894
|
-
// 场景详情:内建 details 必须在 overlay 合并【前】构建(探针要打纯内建工厂,不是被 center 顶掉的);
|
|
895
|
-
// center details 随 overlay 同判定源盖同名——source 语义与 selectScenario 实际取用永一致(约定①)。
|
|
896
392
|
const scenarioDetails = builtinScenarioDetails(scenarios, scenarioDeps);
|
|
897
|
-
const builtinScenarioNames = Object.keys(scenarioDetails);
|
|
898
|
-
// design/158 A10 尾刀:center 声明场景 overlay 搬到 boot/config-center.ts(逐字)。⚠️ 位置即契约:
|
|
899
|
-
// 必须在内建 details + 六内建名捕获之后 —— 探针要打纯内建工厂,不是被 center 顶掉的。
|
|
393
|
+
const builtinScenarioNames = Object.keys(scenarioDetails);
|
|
900
394
|
configCenter.applyCenterScenarios({ scenarioDeps, scenarios, scenarioDetails, builtinScenarioNames });
|
|
901
395
|
logger.info("capabilities", { skills: skills.length, scenarios: Object.keys(scenarios), repoTools: Boolean(repoClient) });
|
|
902
|
-
// Background reapers: recover runs whose instance died, expire never-answered approvals, sweep
|
|
903
|
-
// rate windows. Unref'd so they never hold the process open.
|
|
904
|
-
// design/80 D-D (SLA-timer): the deny-sweep reuses the server's resumeCheckpoint, so it is wired in AFTER
|
|
905
|
-
// createHttpServer returns it (below) via this holder — the reaper is defined before the server.
|
|
906
396
|
let runDenySweep;
|
|
907
|
-
// design/158 A10:reaper 段搬到 src/boot/reapers.ts(逐字;deny-sweep 晚绑改取值,见该文件头注)。
|
|
908
397
|
const reaper = startReapers({
|
|
909
398
|
config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore,
|
|
910
399
|
rateLimiter, costQuota, toolResultStore, fileSnapshotStore, taskAttachmentStore, imageBakes, worktreeReap,
|
|
@@ -912,11 +401,6 @@ async function main() {
|
|
|
912
401
|
backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores,
|
|
913
402
|
getRunDenySweep: () => runDenySweep,
|
|
914
403
|
});
|
|
915
|
-
// #270 车2 —— 托管留存 sweep lane(**独立**定时器,不折进上面那条维护 tick;理由逐字见
|
|
916
|
-
// `boot/retention-lane.ts` 的文件头:那二十条腿全都容忍多副本,而本 lane 必须单执行者)。
|
|
917
|
-
// 两只店在场性已由上面的自持门保证(interval>0 而店缺席 ⇒ 那道门已经拒启了);这里的
|
|
918
|
-
// `&&` 只是把类型收窄出来,不是第二道门。
|
|
919
|
-
/** 租约 TTL = 2× sweep 间隔(§3)。抢租与每 domain 的续租共用同一个值 —— 两处手抄迟早只改一处。 */
|
|
920
404
|
const ttlMs = config.retentionSweep.intervalSec * 1000 * RETENTION_LEASE_TTL_FACTOR;
|
|
921
405
|
const retentionLaneStarted = retentionExecutor !== undefined && retentionLaneStore !== undefined && config.retentionPolicy !== undefined
|
|
922
406
|
? startRetentionLane({
|
|
@@ -924,20 +408,15 @@ async function main() {
|
|
|
924
408
|
policy: config.retentionPolicy,
|
|
925
409
|
mode: config.retentionSweep.mode,
|
|
926
410
|
executor: retentionExecutor,
|
|
927
|
-
// 租约 holder = 本副本的 `instanceId`(与 run 行的 claim 同一个身份):审计行里的 holder/token
|
|
928
|
-
// 于是能直接对上"哪台副本跑的那一轮"。
|
|
929
411
|
lease: {
|
|
930
412
|
acquire: async () => {
|
|
931
413
|
const claim = await retentionLaneStore.acquire(instanceId, ttlMs, Date.now());
|
|
932
414
|
return claim.held ? { held: true, fencingToken: claim.fencingToken } : { held: false };
|
|
933
415
|
},
|
|
934
|
-
// 复核**并续租**(一条 CAS)——续租的机会只有这一处:重入守卫会跳过下一 tick(理由逐字见
|
|
935
|
-
// `boot/retention-lane.ts` 的 `startRetentionLane` 头注与店侧 `renew` 的顶注)。
|
|
936
416
|
renew: (fencingToken) => retentionLaneStore.renew(instanceId, fencingToken, ttlMs, Date.now()),
|
|
937
417
|
release: () => retentionLaneStore.release(instanceId),
|
|
938
418
|
},
|
|
939
419
|
audit: { append: (row) => retentionLaneStore.appendAudit(row, Date.now()) },
|
|
940
|
-
// 省调预检(§5 v1.3:**不承重** —— 承重判在店事务内的哨兵行锁上)。
|
|
941
420
|
holdInForce: (domain) => retentionLaneStore.holdInForce(domain),
|
|
942
421
|
logger,
|
|
943
422
|
metrics,
|
|
@@ -952,25 +431,14 @@ async function main() {
|
|
|
952
431
|
holder: instanceId,
|
|
953
432
|
});
|
|
954
433
|
}
|
|
955
|
-
// Optional OTLP/HTTP metrics export (1.37). Periodically pushes the registry to an OTel collector;
|
|
956
|
-
// best-effort (a collector outage is logged, never affects serving). /metrics stays available too.
|
|
957
434
|
const otelExporter = config.otel
|
|
958
435
|
? startOtlpExporter(metrics, {
|
|
959
436
|
...config.otel,
|
|
960
437
|
onError: (err) => logger.warn("otel_export_failed", { err: String(err) }),
|
|
961
438
|
})
|
|
962
439
|
: undefined;
|
|
963
|
-
// design/158 A10 尾刀:60s refresh 循环 + boot-deferred 到货续接搬到 boot/config-center.ts(逐字)。
|
|
964
|
-
// ⚠️ 位置即契约:必须在 new Runner 之后 —— runnerTierFrozen 与构造函数读的是同一 config 代际。
|
|
965
|
-
// #322 批1:限额座的换代口(恒构造的两只座 —— rateLimiter 上面建、costQuota 在 budget-tracing 段建)。
|
|
966
|
-
// boot 期先 sync 一次:让「上一代读数」落位,于是第一次真变更才会留 `limit_gates_reloaded` 那一行。
|
|
967
440
|
const limitSync = createLimitSync({ config, rateGate: rateLimiter, quota: costQuota, logger });
|
|
968
441
|
limitSync.sync();
|
|
969
|
-
// core 5.49 `Runner.swapModels` 接缝([4775]①,models-tiers defer 臂退役):plane 候选在 commit 前
|
|
970
|
-
// 喂给**全部** boot 单例 Runner 原子换代。五实例必须同代——swap 替代 restart 后,漏换的长命 Runner
|
|
971
|
-
// 会永久停在旧代(比 defer+restart 更糟);leader lane 的 Runner 是逐调用现构(读 config.models 活
|
|
972
|
-
// 引用),不在此列。校验对同一候选输入是确定性的:首个 swap 抛 ⇒ 零实例换装、候选整拒;首个成功后
|
|
973
|
-
// 后继实例抛=结构性不可达,若真发生则 error 留痕后照抛(下一拍幂等重 swap 收敛)。
|
|
974
442
|
const bootRunners = [
|
|
975
443
|
["runner", runner],
|
|
976
444
|
["handsless", handslessRunner],
|
|
@@ -992,23 +460,14 @@ async function main() {
|
|
|
992
460
|
}
|
|
993
461
|
};
|
|
994
462
|
configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerModels });
|
|
995
|
-
// design/158 A10 尾刀:per-model key resolver 首建(+ Bearer 网关组合告警)搬到 boot/config-center.ts。
|
|
996
|
-
// ⚠️ 位置即契约:首建读的必须是 boot apply **之后**的 config.modelApiKeyEnv/modelApiKeys。
|
|
997
463
|
configCenter.initKeyResolver();
|
|
998
|
-
// design/158 A10:session 只读/删除面搬到 src/boot/session-faces.ts(逐字;watch→purge 次序即契约,见该文件头注)。
|
|
999
464
|
const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
|
|
1000
465
|
config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
|
|
1001
466
|
resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileSnapshotStore,
|
|
1002
467
|
workflowCompletionInbox, taskListLane,
|
|
1003
468
|
});
|
|
1004
|
-
// design/158 A10:leader 段搬到 src/boot/leader.ts(逐字)。
|
|
1005
469
|
const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend });
|
|
1006
|
-
// Graceful drain: shared mutable state — SIGTERM flips `draining` (shutdown below), createHttpServer
|
|
1007
|
-
// assigns `inflight` (live leg count on this instance), /health + the submit 503 gate read it.
|
|
1008
470
|
const drainState = { draining: false };
|
|
1009
|
-
// 鉴权桥(registry-core api/auth-bridge):SEMA_REGISTRY_URL 在位即点亮(零新配置——worker 已信
|
|
1010
|
-
// registry 的 effective config,同一信任根延伸到其 JWKS);split 部署用 AUTH_BRIDGE_ISSUER/JWKS_URL/AUDIENCE
|
|
1011
|
-
// 显式覆盖。缺 registry(纯 env 部署)=分支不存在,bearer 行为与今天字节一致。
|
|
1012
471
|
const authBridgeIssuer = process.env.AUTH_BRIDGE_ISSUER || config.configCenter?.baseUrl;
|
|
1013
472
|
const registryJwtVerifier = authBridgeIssuer
|
|
1014
473
|
? createRegistryJwtVerifier({
|
|
@@ -1021,238 +480,129 @@ async function main() {
|
|
|
1021
480
|
: undefined;
|
|
1022
481
|
if (registryJwtVerifier)
|
|
1023
482
|
logger.info("auth_bridge_enabled", { issuer: authBridgeIssuer });
|
|
1024
|
-
// asyncRewake wake 管道 holder:server 启动时装 deliver(session→本副本活流 steer);createTaskHooks 的
|
|
1025
|
-
// ctx.wake 闭包经它投递(main 建壳/server 填实,双向不循环依赖)。
|
|
1026
483
|
const hookWakeBus = {};
|
|
1027
|
-
// ═══════════ #154 件二:启动期静态装配自检(coordinators 构造后、listen 之前)═══════════
|
|
1028
|
-
// core 的 `describeStaticWiring` 与本仓的装配谓词是**两个独立实现**在回答同一组问题。答案矛盾 =
|
|
1029
|
-
// 装配谎言(不是「配置不理想」:是服务端对自己接了什么线有两套互斥说法),fail-loud 拒启 ——
|
|
1030
|
-
// 与 memory+durable 两条既有拒启同族。判据本体在 http/routes/diagnostics.ts(诊断读面与拒启判据同源)。
|
|
1031
|
-
//
|
|
1032
|
-
// specTemplate = resolve-spec 对**每一条**任务腿都会盖的那两个 durable 键(它的 durableEnabled 分支)。
|
|
1033
|
-
// 静态半场只读它们的**在场性**(`parkDurableApprovalOptIn = spec.durableApproval !== undefined`),
|
|
1034
|
-
// 所以这里给的是那个形,而不是某条腿的 principal 真值。
|
|
1035
484
|
const wiringSpecTemplate = durableEnabled
|
|
1036
485
|
? { checkpointStore, durableApproval: { scope: encodeCheckpointScope(undefined) } }
|
|
1037
486
|
: {};
|
|
1038
487
|
const staticWiring = describeStaticWiring(runnerDeps, wiringSpecTemplate);
|
|
1039
|
-
// `parkFacility` 传的必须是**协调器真喂给 `resolveStreamApprovalGate` 的那个实参**(durableEnabled),
|
|
1040
|
-
// 不是在这里重算一遍 —— 重算会把「两个站点算出了不同答案」这件正要被查的事抹平。
|
|
1041
488
|
assertStaticWiringConsistent({ manifest: staticWiring, parkFacility: durableEnabled, backendKind: backend?.kind }, logger);
|
|
1042
|
-
// operator 日志面(**非 wire**)。`configFingerprint` 只有 effective 半场才铸(core 按腿算),静态半场
|
|
1043
|
-
// 没有这一位 ⇒ 诚实报 null,不自造一个服务端哈希(那会是一个要跨仓对表的新语义)。
|
|
1044
489
|
logger.info("wiring_static", {
|
|
1045
490
|
fingerprint: staticWiring.configFingerprint ?? null,
|
|
1046
491
|
parkEffective: staticWiring.parkLane.effective,
|
|
1047
492
|
sessionDurability: staticWiring.session.store,
|
|
1048
493
|
posture: staticWiring.interaction.posture,
|
|
1049
494
|
streamApproval: streamApprovalGate.active ? "active" : streamApprovalGate.reason,
|
|
1050
|
-
// debt #292-manifest:C18 部署级读数上 operator 日志——`full` = agent 行店在线 ∧ session 店
|
|
1051
|
-
// 声明 subagent placement 分区 durable(ForkRoutingSessionStore 从 host 透传声明)。诊断端点
|
|
1052
|
-
// GET /v1/diagnostics/wiring 携带全量 manifest,这里只投这枚部署级结论。
|
|
1053
495
|
subagentTranscripts: staticWiring.fleet.subagentTranscripts,
|
|
1054
496
|
});
|
|
1055
|
-
// ═══════════ design/158 A8:ServiceDeps 分组装配(七组窄接口)═══════════
|
|
1056
|
-
// 每组一个**带类型标注**的中间 const —— 标注位触发 TS 多余属性检查,键放错组当场编译红。
|
|
1057
|
-
// (平铺形做不到:69 键的全可选面里任何键名都「属于」它,于是键落错家/从未接线只能靠源码钉
|
|
1058
|
-
// 逐个盯 —— 1.300/1.301/1.304/1.307/1.310 五起真缺陷都是这个病族。)组形状门见
|
|
1059
|
-
// test/deps-literal-shape-gate.test.ts;消费点仍读平铺 `deps.x`(createHttpServer 入口摊平)。
|
|
1060
|
-
/** durable 持久面(键 present ⇔ 路由/能力位在场;absent ⇒ 501 诚实缺席) */
|
|
1061
497
|
const stores = {
|
|
1062
498
|
runStore,
|
|
1063
|
-
resumeAnchorStore: resumeAnchorStore ? resumeAnchorStore : undefined,
|
|
1064
|
-
approvalExemptionStore: approvalExemptionStore ? approvalExemptionStore : undefined,
|
|
499
|
+
resumeAnchorStore: resumeAnchorStore ? resumeAnchorStore : undefined,
|
|
500
|
+
approvalExemptionStore: approvalExemptionStore ? approvalExemptionStore : undefined,
|
|
1065
501
|
checkpointStore,
|
|
1066
|
-
sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined,
|
|
1067
|
-
ruleConsent,
|
|
1068
|
-
// E19 cap: core's gate-split (1.134.0) REMOVED the isRemoteExecutionEnv skip — core now snapshots each
|
|
1069
|
-
// completed turn + restores on resumeAt for ANY ExecutionEnv when fileSnapshotStore is wired (captureManifest/
|
|
1070
|
-
// applyManifest run over any env's FileSystem ops; a 30s timeout bounds a slow remote walk). So rewind works for
|
|
1071
|
-
// host/e2b/k8s/ssh/adb/local-docker AND the in-process worker → advertise `rewindFiles` whenever the store is wired.
|
|
502
|
+
sessionPolicyStore: sessionPolicyStore ? sessionPolicyStore : undefined,
|
|
503
|
+
ruleConsent,
|
|
1072
504
|
fileSnapshotStore: fileSnapshotStore ? fileSnapshotStore : undefined,
|
|
1073
|
-
// [3321] tool-results 读面(GET /v1/tasks/:id/tool-results/:ref)的数据源。传的是 **durable** 的那一只
|
|
1074
|
-
// (`toolResultStore`,present ⇔ 有 store backend),**不是** Runner 侧的内存兜底 `runnerOffloadStore`:
|
|
1075
|
-
// 无 backend 部署里那只是进程内的、跨副本读不到的,把它接上读面只会让调用方读到「有时有有时无」。
|
|
1076
|
-
// undefined ⇒ 路由 404 同形(理由见 ServiceStoreDeps.toolResultStore 的头注)。
|
|
1077
505
|
toolResultStore,
|
|
1078
|
-
taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
|
|
1079
|
-
// design/153 件3d(/decide parked 赎回腿):durable bg 行店 + boot 裸 Agent 工具,与 RunnerDeps/
|
|
1080
|
-
// scenarioDeps 同实例(claim/expire/consumeParkedFlip 作用于同一行)。任一缺席=分支不存在。
|
|
506
|
+
taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
|
|
1081
507
|
backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
|
|
1082
|
-
// §0.5 session-ownership: the durable session abstraction (SessionRepo-style list/fork/delete) + the delete
|
|
1083
|
-
// coordinator. `ownerAware` is the (possibly cache-wrapped) session store; both delegate to the DB-backed
|
|
1084
|
-
// TiDB/PG store. Undefined-safe: on an in-memory dev store these carry no SessionRepo ops → the routes 501.
|
|
1085
508
|
sessionStorage: ownerAware,
|
|
1086
509
|
workflowRunStore: workflowRunStore ? workflowRunStore : undefined,
|
|
1087
|
-
workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
|
|
510
|
+
workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
|
|
1088
511
|
imageIndex,
|
|
1089
512
|
imageBakes,
|
|
1090
|
-
outcomeSink: outcomeSink ? outcomeSink : undefined,
|
|
1091
|
-
sendFileLedger: sendFileLedger ? sendFileLedger : undefined,
|
|
1092
|
-
// 2c session-sync (P1d): the WHOLE backend, because the /v1/sessions/:id/sync/* routes fan out across session
|
|
1093
|
-
// entries + fileSnapshot + sessionPolicy + resumeAnchor, which must come from ONE backend in the §8 atomic
|
|
1094
|
-
// order. (2026-07-29 纠正:原写「so the routes can call exportSession/importSession」——路由早已是流式实现,
|
|
1095
|
-
// 不调那两个整包编排器;它们现为 test-only,判定见 src/session-sync.ts 顶注。理由不变,指路改对。)
|
|
1096
|
-
// Undefined-safe: the env-only/no-backend worker 501s the routes.
|
|
513
|
+
outcomeSink: outcomeSink ? outcomeSink : undefined,
|
|
514
|
+
sendFileLedger: sendFileLedger ? sendFileLedger : undefined,
|
|
1097
515
|
backend: backend ? backend : undefined,
|
|
1098
516
|
};
|
|
1099
|
-
// [3193] 病理1:fleet 快照的 recovery 屏障(deferred 形,声明先于 deps 装配、落定在下方 recover 块——
|
|
1100
|
-
// 对装配顺序鲁棒;无 notify gate 的部署在 recover 块 else 臂立即落)。
|
|
1101
517
|
let resolveFleetRecovery;
|
|
1102
518
|
const fleetRecoveryDone = new Promise((r) => { resolveFleetRecovery = r; });
|
|
1103
|
-
/** 进程内活体协调面(本副本 only,重启即空:HITL 协调器/可 steer 句柄注册表/总线/收件箱) */
|
|
1104
519
|
const coordinators = {
|
|
1105
|
-
elicitation: elicitation ? elicitation : undefined,
|
|
1106
|
-
question: question ? question : undefined,
|
|
1107
|
-
toolApproval: toolApproval ? toolApproval : undefined,
|
|
1108
|
-
workflowAgentRegistry: workflowAgentRegistry ? workflowAgentRegistry : undefined,
|
|
1109
|
-
subagentSteerRegistry,
|
|
1110
|
-
workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined,
|
|
520
|
+
elicitation: elicitation ? elicitation : undefined,
|
|
521
|
+
question: question ? question : undefined,
|
|
522
|
+
toolApproval: toolApproval ? toolApproval : undefined,
|
|
523
|
+
workflowAgentRegistry: workflowAgentRegistry ? workflowAgentRegistry : undefined,
|
|
524
|
+
subagentSteerRegistry,
|
|
525
|
+
workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined,
|
|
1111
526
|
fleetBus,
|
|
1112
|
-
fleetRecoveryDone,
|
|
1113
|
-
|
|
1114
|
-
// /head 探针同门(getLeafId+ownerOf);缺 store 面不建=路由 501+capabilities.sessionEvents=false。
|
|
1115
|
-
sessionWatch: sessionWatchRegistry ? sessionWatchRegistry : undefined, // [1196] 建于 purgeSession 之前(delete 即 dropSession 的同副本围栏)
|
|
527
|
+
fleetRecoveryDone,
|
|
528
|
+
sessionWatch: sessionWatchRegistry ? sessionWatchRegistry : undefined,
|
|
1116
529
|
hookWakeBus,
|
|
1117
|
-
sendUserFile: sendUserFileEmitter ? sendUserFileEmitter : undefined,
|
|
530
|
+
sendUserFile: sendUserFileEmitter ? sendUserFileEmitter : undefined,
|
|
1118
531
|
leaderEndpoint,
|
|
1119
|
-
sessionTitler: sessionTitler ? sessionTitler : undefined,
|
|
532
|
+
sessionTitler: sessionTitler ? sessionTitler : undefined,
|
|
1120
533
|
};
|
|
1121
|
-
/** 函数/构件缝(main 把跨子系统的动作编译成一个可调用面交给路由) */
|
|
1122
534
|
const seams = {
|
|
1123
535
|
sessionAudit,
|
|
1124
536
|
purgeSession: purgeSession ? purgeSession : undefined,
|
|
1125
|
-
// [1488]③(b): bg agent-handle read face — GET /v1/runs/:id/subagents/:handle/output → the same registry
|
|
1126
|
-
// face the TaskOutput tool reads (defaultTaskRegistry is the process singleton every run leg registers
|
|
1127
|
-
// its background children into). Access tuple is derived from the run row in server.ts, never the caller.
|
|
1128
|
-
// codex R2: kind-gated BEFORE the poll (backgroundAgentOutput) — generic pollTask has side effects on
|
|
1129
|
-
// other kinds (workflow onServedTerminal ack, bash output-cursor advance).
|
|
1130
537
|
subagentTaskOutput: (handle, access) => backgroundAgentOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
|
|
1131
|
-
// [1499] CC TaskOutput/TaskStop 人侧对位 — the GENERIC task-handle verbs over the same registry singleton
|
|
1132
|
-
// (kind gate at the seam: bash/monitor/agent pass, workflow refused — journal face owns it).
|
|
1133
538
|
taskHandleOutput: (handle, access) => taskHandleOutput(defaultTaskRegistry, handle, access, backgroundAgentStore),
|
|
1134
539
|
taskHandleStop: (handle, access) => taskHandleStop(defaultTaskRegistry, handle, access, backgroundAgentStore),
|
|
1135
|
-
// 142-S5 §1.4: GET /v1/memory/export 数据源(DB memory plane only;file 形态 route 501)。
|
|
1136
540
|
memoryExport: memoryExportBackend ? (scope) => exportMemoryScope(memoryExportBackend, scope) : undefined,
|
|
1137
|
-
// 142-S2.5: POST /v1/memory/sync/:scope 执行位(同一 DB entry plane + sync_cursors 游标面;
|
|
1138
|
-
// plan 随数据走——core 纯函数在 performMemorySync 内直调,file 形态 route 501)。
|
|
1139
541
|
memorySync: memoryExportBackend && memorySyncCursors
|
|
1140
542
|
? (scope, syncReq) => performMemorySync(memoryExportBackend, memorySyncCursors, scope, syncReq)
|
|
1141
543
|
: undefined,
|
|
1142
|
-
// #264 v2-c(core 5.40.0):治理携出 bundle 的两口 —— POST /v1/memory/export · POST /v1/memory/import。
|
|
1143
|
-
// 数据源是**装配好的记忆引擎本体**(不是 memoryExportBackend 那条 DB entry plane):bundle 是引擎
|
|
1144
|
-
// 的单锁复合面(条目 + 托管链 + 挑战 + lineage + 污染标记同一个 epoch),不是条目读的超集。
|
|
1145
|
-
// 🔴 两口共用一个在场判据,因为它们是同一个引擎面的两半(半挂 = 导得出导不进,而这条产品面的全部
|
|
1146
|
-
// 意义就是那趟往返)。判据是**两项**:引擎在场 ∧ 其后端实现了 bundle 复合面 —— 后者由
|
|
1147
|
-
// createMemoryBundleFaces 在**挂载期**判(返回 undefined ⇒ 两口不挂、能力位翻假、打上去诚实 501),
|
|
1148
|
-
// 而不是照常挂着让每次调用确定性 409(codex 对抗复审 [medium] 驳回的那一版:一个恒假的 yes)。
|
|
1149
|
-
// 绝不在本层为缺面的后端现造替代实现——理由逐字见 routes/memory-bundle.ts 的「能力面的诚实形」。
|
|
1150
544
|
memoryBundleExport: memoryBundleFaces ? memoryBundleFaces.exportScopes : undefined,
|
|
1151
545
|
memoryBundleImport: memoryBundleFaces ? memoryBundleFaces.importBundle : undefined,
|
|
1152
|
-
// design/170 件A §7 收编:memory-policy 面的 `org:` 属主门与 core 准入 seam 读**同一个**目录实例
|
|
1153
|
-
// (`createOrgMemoryAdmissionWiring` 的产物),两面因此共享 TTL 缓存/退避窗/gen 高水位 —— 一个进程
|
|
1154
|
-
// 对「谁属于 org:acme」只有一个答案。缺席(无 center 且无 env 表)⇒ 策略面 `org:` 键仍 operator-only。
|
|
1155
546
|
orgMemoryDirectory: orgMemoryAdmission.directory,
|
|
1156
|
-
// design/177:HTTP 只读面与模型面共用**同一个** provider 实例 —— 两面看到的库集合按定义一致,
|
|
1157
|
-
// 不可能出现「壳能读到模型读不到的库」。缺席 ⇒ `/v1/shared-memory/*` 整域不挂载(见 server.ts 域头)。
|
|
1158
547
|
sharedMemoryStore: sharedMemoryStore ? sharedMemoryStore : undefined,
|
|
1159
|
-
// sessionMirror 观测面(server 非执法端,论证在 ServiceDeps.sessionMirrorRuling):
|
|
1160
|
-
// 与 executionRuling 同车同缓存(零额外 center RTT);无 center/dry-run ⇒ 不接线,观测面暗、零行为差。
|
|
1161
548
|
sessionMirrorRuling: principalCaps
|
|
1162
549
|
? async (p) => (await principalCaps.executionRuling(p))?.sessionMirror
|
|
1163
550
|
: undefined,
|
|
1164
551
|
instrumentDegenerate,
|
|
1165
|
-
sideQueryAccounting,
|
|
552
|
+
sideQueryAccounting,
|
|
1166
553
|
parkedReviveTool: parkedReviveTool ? parkedReviveTool : undefined,
|
|
1167
|
-
// design/158 S2(F-1)修:工厂在 :2308 造好、server.ts 消费分支在等,但从未进本实参段——HIGH-1
|
|
1168
|
-
// 同族第二例(条件 spread 的键从不接线,编译期无声)。缺席后果=带 requiresParentConstraint 的
|
|
1169
|
-
// parked checkpoint 重启后恒被 core pre-CAS 门拒,**不可赎回**。接线钉:test/parked-revive-gate-wiring.test.ts。
|
|
1170
554
|
parkedReviveInheritedGate: parkedReviveInheritedGate ? parkedReviveInheritedGate : undefined,
|
|
1171
555
|
};
|
|
1172
|
-
/** 可观测面(全体 fail-open,观测失败绝不改业务面行为) */
|
|
1173
556
|
const observability = {
|
|
1174
557
|
logger,
|
|
1175
558
|
metrics,
|
|
1176
|
-
modelUsage: modelUsageTracker,
|
|
1177
|
-
promptManifests: promptManifestTracker,
|
|
559
|
+
modelUsage: modelUsageTracker,
|
|
560
|
+
promptManifests: promptManifestTracker,
|
|
1178
561
|
planCacheProbe,
|
|
1179
562
|
};
|
|
1180
|
-
/** 准入/治理面(每件都是拒绝的权力;缺席 = 该道门不存在,不是「默认放行的门」) */
|
|
1181
563
|
const governance = {
|
|
1182
564
|
authorize,
|
|
1183
565
|
registryJwtVerifier: registryJwtVerifier ? registryJwtVerifier : undefined,
|
|
1184
566
|
rateLimiter,
|
|
1185
567
|
costQuota,
|
|
1186
|
-
fleetLease: fleetLease ? fleetLease : undefined,
|
|
1187
|
-
usageWindowStore: usageWindowStore ? usageWindowStore : undefined,
|
|
568
|
+
fleetLease: fleetLease ? fleetLease : undefined,
|
|
569
|
+
usageWindowStore: usageWindowStore ? usageWindowStore : undefined,
|
|
1188
570
|
};
|
|
1189
|
-
// B1 活体 store 探测环(门与判据见 store-live-probe.ts 头注;探针原语=既有 S10 dbNowMs 真 DB 往返)
|
|
1190
571
|
const storeLiveProbe = backend && backend.kind !== "local" && backend.dbNowMs && config.storeProbeIntervalMs > 0
|
|
1191
572
|
? createStoreLiveProbe({ probe: () => backend.dbNowMs(), intervalMs: config.storeProbeIntervalMs, logger })
|
|
1192
573
|
: undefined;
|
|
1193
|
-
/** 部署自述面(/health + /v1/capabilities + 提交前置门读的部署事实;多为 live getter) */
|
|
1194
574
|
const deployment = {
|
|
1195
|
-
// 海外 pilot 实机发现:/health 曾 verbatim 回显 sessionBackend 枚举——auto/DB_BACKEND 收编把它
|
|
1196
|
-
// coerce 成 LABEL "tidb"(泛指 durable),pg 部署上误读成 SQL-TiDB 带偏排障。回显有效 store(与 listening
|
|
1197
|
-
// 日志 session 标签同款诚实修)。
|
|
1198
575
|
sessionStoreLabel: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
|
|
1199
|
-
modelReady: () => configCenter.modelReady(),
|
|
1200
|
-
scenarioDetails,
|
|
1201
|
-
workflowsCapable,
|
|
1202
|
-
staticWiring,
|
|
1203
|
-
memoryPosture,
|
|
576
|
+
modelReady: () => configCenter.modelReady(),
|
|
577
|
+
scenarioDetails,
|
|
578
|
+
workflowsCapable,
|
|
579
|
+
staticWiring,
|
|
580
|
+
memoryPosture,
|
|
1204
581
|
instanceId,
|
|
1205
|
-
// Capability discovery: the deployment facts only main knows. Store-derived booleans
|
|
1206
|
-
// (asyncRuns/artifacts/approvals/leader) are computed inside the server from the deps themselves.
|
|
1207
|
-
// pricingConfigured disambiguates costMicroUsd=0 ("free run" vs "MODEL_COST_* not configured") —
|
|
1208
|
-
// it is worker-level config, so one flag here beats decorating every result's stats.
|
|
1209
582
|
capabilities: {
|
|
1210
583
|
version: serviceVersion(),
|
|
1211
584
|
scenarios: Object.keys(scenarios),
|
|
1212
|
-
// 缝合审 M3:**getter,不是 boot 快照** —— `pricing` 是被 config-center 就地热改的同一个引用
|
|
1213
|
-
// (`boot/config-center.ts` 的 `mutateInPlace(pricing, buildPricing(config.models))`)。算成快照的话,
|
|
1214
|
-
// 运维加上/撤掉价目表之后这个键还报旧值,而它的全部用途就是给消费端消歧「0 = 免费跑 vs 没配价目表」——
|
|
1215
|
-
// 报错了就把消歧信号变成误导信号。判据单一真源在 `budget.ts` 的 `isPricingConfigured`。
|
|
1216
|
-
// (同一字面量里紧邻的 `restartState` 已经是 live getter 并写着为什么;这个兄弟键当时漏了。)
|
|
1217
585
|
get pricingConfigured() {
|
|
1218
586
|
return isPricingConfigured(pricing);
|
|
1219
587
|
},
|
|
1220
588
|
},
|
|
1221
|
-
// center: orchestrator polls GET /health for this — set by the sema-registry refresh loop when a
|
|
1222
|
-
// pull's restart-to-apply slices differ from boot. A live getter (not a snapshot) so /health always reads current.
|
|
1223
589
|
restartState: () => configCenter.restartState(),
|
|
1224
|
-
planeDeferredState: () => configCenter.planeDeferredState(),
|
|
1225
|
-
configApplyState: () => configCenter.configApplyState(),
|
|
1226
|
-
mcpRevocations,
|
|
1227
|
-
mcpRevocationState: () => configCenter.mcpRevocationState(),
|
|
1228
|
-
configRefreshNow: () => configCenter.refreshNow(),
|
|
1229
|
-
// shared drain state — SIGTERM flips `draining`, createServer assigns `inflight`, /health mirrors it.
|
|
590
|
+
planeDeferredState: () => configCenter.planeDeferredState(),
|
|
591
|
+
configApplyState: () => configCenter.configApplyState(),
|
|
592
|
+
mcpRevocations,
|
|
593
|
+
mcpRevocationState: () => configCenter.mcpRevocationState(),
|
|
594
|
+
configRefreshNow: () => configCenter.refreshNow(),
|
|
1230
595
|
drainState,
|
|
1231
|
-
storeDegraded: storeBackendDegraded,
|
|
1232
|
-
// B1 活体 store 探测:后台缓存环(座注见 http/server.ts storeLiveState;环判据见 store-live-probe.ts)。
|
|
1233
|
-
// 门 = SQL 后端 ∧ dbNowMs 在场 ∧ 旋钮>0;local/memory 恒不挂(/health 形状不变)。timer 全 unref,
|
|
1234
|
-
// 进程退出零阻塞;pool 生命周期归 backend.close()。直键非条件展开(deps-literal-shape 门:条件
|
|
1235
|
-
// 展开旁路多余属性检查——E-HIGH-1 死键病族)。
|
|
596
|
+
storeDegraded: storeBackendDegraded,
|
|
1236
597
|
storeLiveState: storeLiveProbe ? () => storeLiveProbe.state() : undefined,
|
|
1237
598
|
};
|
|
1238
|
-
/** 数值旋钮(缺省写在消费点,此处只承载覆写) */
|
|
1239
599
|
const knobs = {
|
|
1240
|
-
// (a) SQL 默认店的 per-blob 帽 → PUT /sync/blobs 前置 413(与 SqlBlobBackend 方言默认同源:
|
|
1241
|
-
// 显式 env > tidb 的 6MiB entry-限默认;pg 默认无帽=不前置)。
|
|
1242
|
-
// 🔴 复审 2026-07-28(E-HIGH-1)修:此键是 **ServiceDeps** 字段,原先被 spread 进 `runnerDeps`
|
|
1243
|
-
// (core 的 RunnerDeps 无此字段,条件 spread 绕过 TS 多余属性检查 ⇒ 编译期无声)⇒ HTTP 面恒
|
|
1244
|
-
// undefined、413 前置是死枝、超帽仍走 502。落错家的死键病族(verify-cost 先例)——接线钉见
|
|
1245
|
-
// test/deps-literal-shape-gate.test.ts(2026-07-28 那批复审文件已按仓规折进主题文件,钉本体在 E-HIGH-1 那个 describe)。
|
|
1246
600
|
snapshotBlobSqlCapBytes: backend && backend.kind !== "local" && !config.snapshotBlobStore
|
|
1247
601
|
? (config.snapshotBlobSqlMaxBytes ?? (backend.kind === "mysql" ? SQL_BLOB_DEFAULT_MAX_BYTES : undefined))
|
|
1248
602
|
: undefined,
|
|
1249
|
-
// 帽只在 watch 面在场时才有意义(缺席时路由 501,读不到它)——保持与原条件段同门,不单独接线。
|
|
1250
603
|
sessionEventsMaxConnections: sessionWatchRegistry ? posIntEnv(process.env.SESSION_EVENTS_MAX_CONNS, 256, 100_000) : undefined,
|
|
1251
604
|
};
|
|
1252
|
-
// #196:hands lane 登记簿 —— resolveSpec 登记本请求场景的表态,HTTP 执行点凭 spec 取回选 Runner。
|
|
1253
|
-
// 两处**同一实例**(交给 createResolveSpec 与 runnerFor 闭包),配对由这一行结构性保证。
|
|
1254
605
|
const handsLanes = createHandsLaneRegistry();
|
|
1255
|
-
// design/158 A10:resolveSpec 段搬到 src/boot/resolve-spec.ts(逐字;两处活引用改取值,见该文件头注)。
|
|
1256
606
|
const resolveSpec = createResolveSpec({
|
|
1257
607
|
handsLanes,
|
|
1258
608
|
config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver,
|
|
@@ -1263,35 +613,17 @@ async function main() {
|
|
|
1263
613
|
selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore,
|
|
1264
614
|
singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage,
|
|
1265
615
|
sessionEnvSelection,
|
|
1266
|
-
lockedKeys: governanceSeams.lockedKeys,
|
|
1267
|
-
liveQuestionFace: question,
|
|
616
|
+
lockedKeys: governanceSeams.lockedKeys,
|
|
617
|
+
liveQuestionFace: question,
|
|
1268
618
|
});
|
|
1269
|
-
// #196:执行点选路。resolveSpec 已按场景判别位登记 lane;`full` 恒回主 runner(修前行为逐字不变)。
|
|
1270
619
|
const runnerFor = (spec) => pickHandsRunner(handsLanes.laneOf(spec), { full: runner, handsless: handslessRunner });
|
|
1271
|
-
// #303:side-query 执行席 —— **不是** runner.sideQuery(core 的 SideQuerySpec 无 key 座位,那条路恒把
|
|
1272
|
-
// 网关 key 发到模型自己的 baseUrl)。key plane 第三面在此接线,取的是与另两面(任务面
|
|
1273
|
-
// boot/resolve-spec.ts、hook 面 hooks/hook-llm.ts)**同一只**活引用 getKeyResolver;brain 与
|
|
1274
|
-
// RunnerDeps.brain 同实例(wrapper 在外层包,主推理链零扰动)。
|
|
1275
620
|
const sideQuery = createSideQueryLane({ brain, config, getKeyResolver: () => configCenter.getKeyResolver() });
|
|
1276
621
|
const server = createHttpServer({ runner, runnerFor, sideQuery, config, resolveSpec, stores, coordinators, seams, observability, governance, deployment, knobs });
|
|
1277
|
-
// D-D SLA-timer: wire the server's deny-sweep into the reaper holder declared above (the reaper is defined
|
|
1278
|
-
// before the server, so it calls through this late-bound reference).
|
|
1279
622
|
runDenySweep = server.denyExpiredApprovals;
|
|
1280
|
-
// #329:PARKED 迟到决议的赎回席接线(晚绑 holder,见其声明处)。
|
|
1281
|
-
// 🔴 **直连门部署上刻意不接**(fail-closed,#157 无静默 fail-open):`directDoorActive` 的形下
|
|
1282
|
-
// `/v1/approvals/:sessionId/decide` 要求每条决议携密码学信封(签名 principal JWT + cnf.bnd + HMAC over
|
|
1283
|
-
// 决议内容),而 live 回决腿 `POST /v1/tool-approvals/:id/respond` 的门只有 principal —— 把赎回席接上去
|
|
1284
|
-
// 等于给这类部署开一条**免信封**的 gate 决议口(信封正是为「BFF 被攻陷也不能替人批」而立的)。缺席 ⇒
|
|
1285
|
-
// 那条臂逐字回落修前 404,壳按既有 durable gate 流程走(行的 410 体已带 sessionId 指路)。
|
|
1286
|
-
// 收紧方向、不改任何已有腿:非直连门部署(绝大多数)照常受益。
|
|
1287
623
|
if (!config.directDoorActive)
|
|
1288
624
|
parkedAskRedeem = server.redeemParkedAsk;
|
|
1289
625
|
else
|
|
1290
626
|
logger.info("parked_late_decision_disabled", { reason: "direct_door_requires_signed_decision_envelope" });
|
|
1291
|
-
// #261:fleet 总线存活对账腿。⚠️ 位置即契约 —— 必须在 `createHttpServer` **之后**:活体谓词
|
|
1292
|
-
// (`inflightRuns` ∪ `steerableRuns` 的探针)是 server 造出来的,而 reaper 段按其自身的位置契约必须在
|
|
1293
|
-
// server 之前起,所以本腿独立起表(60s 档,unref;判据/五条界全在 fleet-reconciler.ts)。
|
|
1294
|
-
// 零写 durable:窄口 `getRun`/`get` 两个读方法,判死属主仍是 reaper。
|
|
1295
627
|
const fleetReconcile = startFleetReconciler({
|
|
1296
628
|
bus: fleetBus,
|
|
1297
629
|
isLiveOnThisReplica: server.isRunLiveOnThisReplica,
|
|
@@ -1301,15 +633,7 @@ async function main() {
|
|
|
1301
633
|
logger,
|
|
1302
634
|
metrics,
|
|
1303
635
|
});
|
|
1304
|
-
// [1934]:绑址旋钮 + 无鉴权自收窄(理由见 resolveBindHost 顶注)。undefined = Node 默认全接口。
|
|
1305
636
|
const bindHost = resolveBindHost(config);
|
|
1306
|
-
// [2062]③ HOST 继承暗通道告警:zsh 常把 HOST 设成机器名,`{...process.env}` 起底的壳会静默继承给
|
|
1307
|
-
// 引擎——bind 面被 shell 环境暗配而部署者不知情(HOST=机器名解析到 LAN 口时=静默扩暴露面)。
|
|
1308
|
-
// 🔴 [2067]③ 次序承重:必须在 listen **之前**打——HOST=不可解析名时 listen 直接 ENOTFOUND 崩溃,
|
|
1309
|
-
// 恰是「继承 HOST 把 boot 弄崩」这个最需要解释的场景;warn 在 listen 后=诊断在崩溃里缺席,用户只见
|
|
1310
|
-
// 裸 getaddrinfo 栈(cli 真机取证)。
|
|
1311
|
-
// 缝合审 M5:收窄现在会**压过**继承的 HOST(见 resolveBindHost)。分两臂 —— 否则这条 warn 打的
|
|
1312
|
-
// `bindHost` 已是收窄后的 127.0.0.1,而 note 还说「came from the HOST env var」,自己变成一句谎。
|
|
1313
637
|
if (config.bindHostSource === "HOST" && bindHost !== config.bindHost) {
|
|
1314
638
|
logger.warn("bind_host_from_HOST_env_overridden", {
|
|
1315
639
|
bindHost,
|
|
@@ -1323,20 +647,15 @@ async function main() {
|
|
|
1323
647
|
note: "the bind address came from the HOST env var (not BIND_HOST). Shells like zsh commonly set HOST to the machine name — if you did not set it yourself, this may be an inherited shell variable silently configuring the bind face. Set BIND_HOST explicitly to silence this warning.",
|
|
1324
648
|
});
|
|
1325
649
|
}
|
|
1326
|
-
// 缝合审 M8:listen 期的 rejecter 必须**具名**,因为它必须在 listen 结束后被摘掉。留着不摘的话,
|
|
1327
|
-
// 运行期第一个 server-level error 会喂给一个已 settle 的 Promise(reject = no-op)⇒ 零日志零抛;
|
|
1328
|
-
// 而 `once` 自摘之后,第二个 error 才 uncaught 崩进程 —— 这条缺口吃掉的恰好是**第一个**证据,
|
|
1329
|
-
// 而第一个证据往往是唯一能解释后面那次崩溃的东西。
|
|
1330
650
|
let onListenError;
|
|
1331
651
|
try {
|
|
1332
652
|
await new Promise((resolve, reject) => {
|
|
1333
|
-
onListenError = reject;
|
|
653
|
+
onListenError = reject;
|
|
1334
654
|
server.once("error", onListenError);
|
|
1335
655
|
bindHost ? server.listen(config.port, bindHost, resolve) : server.listen(config.port, resolve);
|
|
1336
656
|
});
|
|
1337
657
|
}
|
|
1338
658
|
catch (e) {
|
|
1339
|
-
// [2067]③:崩溃路径自带来源诊断——不可解析的 HOST 继承名在这里最常见。
|
|
1340
659
|
logger.error("listen_failed", {
|
|
1341
660
|
port: config.port,
|
|
1342
661
|
bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
|
|
@@ -1347,8 +666,6 @@ async function main() {
|
|
|
1347
666
|
throw e;
|
|
1348
667
|
}
|
|
1349
668
|
finally {
|
|
1350
|
-
// listen 结束(成功或失败)即摘。换上持久 handler:server-level error 之后进程通常仍能服务,
|
|
1351
|
-
// 崩掉整个副本太重 —— 但绝不能无声。
|
|
1352
669
|
if (onListenError)
|
|
1353
670
|
server.removeListener("error", onListenError);
|
|
1354
671
|
}
|
|
@@ -1358,9 +675,6 @@ async function main() {
|
|
|
1358
675
|
...("code" in e ? { code: String(e.code) } : {}),
|
|
1359
676
|
});
|
|
1360
677
|
});
|
|
1361
|
-
// fleet worker 接线(announce/heartbeat + usage 批报;lease 消费后一拍):boot 完成后
|
|
1362
|
-
// 注册(=首次心跳)。门 = center lane 配置 ∧ FLEET_ADVERTISE_ADDRESS 显式声明(不自猜可达地址);任一缺 → undefined
|
|
1363
|
-
// = 非 fleet 部署零行为。draining 翻转的即刻 announce 在 drainThenShutdown;退出 DELETE 在 hardShutdown。
|
|
1364
678
|
const fleetClient = startFleetClientFromEnv(config, {
|
|
1365
679
|
instanceId,
|
|
1366
680
|
version: serviceVersion(),
|
|
@@ -1370,9 +684,6 @@ async function main() {
|
|
|
1370
684
|
});
|
|
1371
685
|
if (fleetClient)
|
|
1372
686
|
logger.info("fleet_client_started", { worker: config.configCenter?.worker, address: process.env.FLEET_ADVERTISE_ADDRESS });
|
|
1373
|
-
// 缝合审 M7:这是**唯一**一处 `listening`。此前 listen 成功处还有第二条同名事件(只带
|
|
1374
|
-
// port/bindHost/note),日志消费方 grep "listening" 会撞见两条键集不同的同名事件,按前一条解析
|
|
1375
|
-
// 部署快照的人拿到残缺形。bindHost + loopback 收窄 note 并进这里,别再另起一行。
|
|
1376
687
|
logger.info("listening", {
|
|
1377
688
|
port: config.port,
|
|
1378
689
|
bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
|
|
@@ -1381,44 +692,28 @@ async function main() {
|
|
|
1381
692
|
: {}),
|
|
1382
693
|
model: config.model.id,
|
|
1383
694
|
brain: brainSummary(config),
|
|
1384
|
-
|
|
1385
|
-
breakerState: config.resilience.circuitBreaker ? (breakerState ? `shared(${backend.kind})` : "in-process") : "off", // 1.108: was hard-coded "shared(tidb)" — lied under DB_BACKEND=pg
|
|
1386
|
-
// On DB_BACKEND=local the wiring coerces sessionBackend to the LABEL "tidb" (meaning "the
|
|
1387
|
-
// backend's durable store, generically") — printing it verbatim read as a SQL deployment on a file-everything
|
|
1388
|
-
// box. Print the effective store instead (same honesty fix as breakerState above).
|
|
695
|
+
breakerState: config.resilience.circuitBreaker ? (breakerState ? `shared(${backend.kind})` : "in-process") : "off",
|
|
1389
696
|
session: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
|
|
1390
|
-
// design/138 S1: the file-based memory engine (single-user only); "off" = MEMORY_ENGINE=off or multi-tenant.
|
|
1391
697
|
memory: memoryEngine ? `engine(${memoryEngine.root})` : "off",
|
|
1392
698
|
instanceId,
|
|
1393
699
|
rateLimitPerMin: config.rateLimitPerMin,
|
|
1394
700
|
rateLimit: config.rateLimitPerMin > 0 ? counterStoreLabel(backend?.rateLimiter, backend?.kind) : "off",
|
|
1395
701
|
maxPrincipalCostUsd: config.maxPrincipalCostUsd || null,
|
|
1396
702
|
costQuota: config.maxPrincipalCostUsd > 0 ? counterStoreLabel(backend?.costQuota, backend?.kind) : "off",
|
|
1397
|
-
// durable offload store for large tool results (cross-replica wake); else core's in-process default
|
|
1398
703
|
toolResultStore: toolResultStore ? (backend?.kind === "local" ? "file(local)" : "shared(sql)") : "in-process",
|
|
1399
704
|
otel: config.otel ? config.otel.endpoint : null,
|
|
1400
|
-
perModelKeys: Object.keys(config.modelApiKeyEnv).length,
|
|
705
|
+
perModelKeys: Object.keys(config.modelApiKeyEnv).length,
|
|
1401
706
|
sealedModelKeys: Object.values(config.modelApiKeys).filter((v) => typeof v === "string").length,
|
|
1402
|
-
sealedPoisonedModels: Object.values(config.modelApiKeys).filter((v) => typeof v !== "string").length,
|
|
1403
|
-
// SVC-1: durable workflow record + at-least-once notify backend (file = crash-safe ledger; memory = ephemeral)
|
|
707
|
+
sealedPoisonedModels: Object.values(config.modelApiKeys).filter((v) => typeof v !== "string").length,
|
|
1404
708
|
workflowRunStore: config.selfOrchestrationEnabled
|
|
1405
709
|
? config.workflowRunStoreBackend === "memory"
|
|
1406
710
|
? "in-process"
|
|
1407
711
|
: sqlWorkflowRunStore
|
|
1408
|
-
? `db(${backend.kind})`
|
|
712
|
+
? `db(${backend.kind})`
|
|
1409
713
|
: "file"
|
|
1410
714
|
: "off",
|
|
1411
715
|
});
|
|
1412
|
-
// SVC-1 at-least-once notify — CRASH-RECOVERY sweep: once at boot, re-derive every un-acked (started-but-not-
|
|
1413
|
-
// delivered) workflow's terminal state from the durable run store and re-deliver the completion notify that a
|
|
1414
|
-
// prior replica crash dropped (idempotent on the receiver). Fire-and-forget AFTER listen so startup isn't
|
|
1415
|
-
// blocked on it (recovery only re-fires completion notifications — independent of serving new requests). A
|
|
1416
|
-
// sweep throw is logged, never fatal.
|
|
1417
716
|
if (workflowNotifyGate) {
|
|
1418
|
-
// [2995]/[2999] opts 单点合成在 workflow-orchestration(可见性回灌 + replica-local 形 boot 孤儿判死)。
|
|
1419
|
-
// [3193] 病理1:recover 收官(成败皆算)即落 fleet 快照屏障——「serving 不等 recovery」对**提交请求**
|
|
1420
|
-
// 仍成立,但 fleet 快照组装现在对它做有界等待(routes/fleet.ts,3s cap):SIGKILL 后壳重连不再
|
|
1421
|
-
// 与 boot 判死赛跑(cli [3193] 插桩实证的竞态)。
|
|
1422
717
|
void workflowNotifyGate
|
|
1423
718
|
.recover(workflowRecoverOpts)
|
|
1424
719
|
.then((tally) => logger.info("workflow_notify_recovered", tally))
|
|
@@ -1428,14 +723,11 @@ async function main() {
|
|
|
1428
723
|
else {
|
|
1429
724
|
resolveFleetRecovery();
|
|
1430
725
|
}
|
|
1431
|
-
// design/158 A10:收尾段搬到 src/boot/shutdown.ts(逐字)。⚠️ 调用点必须留在 listen 之后 —— 位置即契约,
|
|
1432
|
-
// 理由(信号注册时点/三信号相对次序/clearInterval 先于 server.close)见该文件头注。
|
|
1433
726
|
installShutdownHandlers({
|
|
1434
727
|
config, logger, server, reaper, fleetReconcile, otelExporter, breakerState, costQuota, rateLimiter,
|
|
1435
|
-
version: serviceVersion(),
|
|
728
|
+
version: serviceVersion(),
|
|
1436
729
|
runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState,
|
|
1437
|
-
storeLiveProbe, configCenter,
|
|
1438
|
-
// #270 车2:留存 lane 的定时器 + 主动让租(收尾契约 3 同族,理由见 shutdown.ts 的两条注)。
|
|
730
|
+
storeLiveProbe, configCenter,
|
|
1439
731
|
retentionLane: retentionLaneStarted,
|
|
1440
732
|
releaseRetentionLease: retentionLaneStore !== undefined ? () => retentionLaneStore.release(instanceId) : undefined,
|
|
1441
733
|
});
|