@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/run-local.js
CHANGED
|
@@ -1,47 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* `run-local` — a ONE-SHOT local CLI for the agent engine.
|
|
4
|
-
*
|
|
5
|
-
* Boots the SAME runtime as `src/main.ts` (createBrain + Runner + the scenario/skills capability layer +
|
|
6
|
-
* the host execution env) IN-PROCESS, runs ONE task from the CLI objective to completion, prints the
|
|
7
|
-
* TaskResult, and exits. There is NO long-running HTTP server and NO submission auth gate — this is the
|
|
8
|
-
* whole point: it removes two friction points (the SERVICE_AUTH_TOKEN 503 on submit and the
|
|
9
|
-
* manual server lifecycle). The user fills `~/.ai-agent/.env` + `config.d/` (the LOCAL config source) and
|
|
10
|
-
* just runs `run-local "<objective>"`.
|
|
11
|
-
*
|
|
12
|
-
* Posture: LOCAL config mode (CONFIG_PROVIDER=local) → createConfigProvider → LocalConfigProvider →
|
|
13
|
-
* applyEffective (config.d/.env drives models/roles/mcp), and the `host` exec lane (run on THIS machine,
|
|
14
|
-
* no container) — identical building blocks to the server path so behavior matches (no drift). It reuses
|
|
15
|
-
* main.ts's helpers verbatim; the only thing it skips is the server-only surface (createHttpServer, listen,
|
|
16
|
-
* auth/rateLimit/quota/run-store/leader/durable-approval-decide/reapers).
|
|
17
|
-
*
|
|
18
|
-
* 🔴 **DEPLOYMENT GOVERNANCE IS NOT IN THAT SKIP LIST** (design/181 件三 / #180). Until 7.4.x this file
|
|
19
|
-
* assembled its `TaskSpec` with the governance chain entirely ABSENT — `loadConfig` + `applyEffective` had
|
|
20
|
-
* already parsed `autonomy` / `commandPolicy` / `sensitiveWritePatterns` into the config, and the spec
|
|
21
|
-
* literal simply never read them (compute-then-discard), so the same operator knob was live on the HTTP leg
|
|
22
|
-
* and structurally non-existent here. It now goes through the SAME single construction port as the other two
|
|
23
|
-
* legs ({@link applyLocalGovernance} → `src/deployment-governance.ts` → `applyRuntimeGovernance` → core's
|
|
24
|
-
* `tightenTaskSpec`). What this leg genuinely does NOT have is the **durable approval** face (a one-shot CLI
|
|
25
|
-
* has no `/v1/approvals/:id/decide` leg to resume from), so the approval baseline is the adjudicated
|
|
26
|
-
* allow-all arm and a gated `ask` is answered by {@link createLocalApprover} — the TTY human, or a
|
|
27
|
-
* fail-closed deny — instead of parking a checkpoint nobody will ever redeem.
|
|
28
|
-
*
|
|
29
|
-
* 🔴 **design/201 的 permissionMode → shellGate 翻译表 SCOPED-OUT 本腿**(设计稿 §5,显式裁定而非欠账)。
|
|
30
|
-
* 这条腿的 operator 就是亲手起这个进程的用户,它**没有 permission mode 这个概念**(一次性 CLI 收不到
|
|
31
|
-
* 客户端表态),今昔都落 core 的缺省 `shellGate: off`。给它硬塞一个 classify 档,在没有 HITL 面的形态下
|
|
32
|
-
* 等于把每一次 shell 调用送进 auto-deny —— 那不是加固,是废掉 run-local。部署治理段(上一段)照旧全接:
|
|
33
|
-
* `MANUAL_MODE_SHELL_GATE` 想要门,operator 在自己的 `.env` 里说一句就有。同族的另一条 scoped-out 是
|
|
34
|
-
* parked-revive 赎回腿(body 全盲,core 的 `InheritedGate` 以 `max(seed, live)` 兜底,自洽)。
|
|
35
|
-
* 「三腿不共享翻译点」是设计,不是漏接:名册对账钉在
|
|
36
|
-
* `test/operator-knob-client-posture-matrix.test.ts`(`shellGateForMode` 已并入构造点名册,第四条腿哪天
|
|
37
|
-
* 接上它必须同时挂矩阵腿)。
|
|
38
|
-
*
|
|
39
|
-
* Usage: run-local "<objective>" [--root <dir>] [--json] [--scenario <name>]
|
|
40
|
-
*
|
|
41
|
-
* `main()` is a thin wrapper over the testable {@link runLocal}, which takes argv + injectable
|
|
42
|
-
* deps (a mock Brain, a captured logger/printer, and an `exit` shim) so the assembly + one-shot flow can be
|
|
43
|
-
* exercised deterministically with no real LLM / network / keys.
|
|
44
|
-
*/
|
|
45
2
|
import { homedir } from "node:os";
|
|
46
3
|
import { join } from "node:path";
|
|
47
4
|
import { realpathSync } from "node:fs";
|
|
@@ -65,7 +22,7 @@ import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-me
|
|
|
65
22
|
import { loadSkills } from "./capabilities/skills.js";
|
|
66
23
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
67
24
|
import { webSearchConfigFromEnv, createWebSearchBackend } from "./plugins/web-search.js";
|
|
68
|
-
import { buildScenarios, selectScenario, centerScenarios
|
|
25
|
+
import { buildScenarios, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
|
|
69
26
|
import { createTaskListLane } from "./boot/task-list-lane.js";
|
|
70
27
|
import { pickHandsRunner, withoutExecutionEnv } from "./capabilities/hands-lane.js";
|
|
71
28
|
import { HttpError } from "./security.js";
|
|
@@ -74,14 +31,13 @@ import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync
|
|
|
74
31
|
import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
|
|
75
32
|
import { createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
76
33
|
import { createGovernanceSeams } from "./boot/governance-seams.js";
|
|
77
|
-
import { assertRetentionLaneWirable } from "./boot/retention-lane.js";
|
|
34
|
+
import { assertRetentionLaneWirable } from "./boot/retention-lane.js";
|
|
78
35
|
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
79
36
|
import { createPermissionDeniedMeter } from "./observability/tool-trace.js";
|
|
80
37
|
import { createKeyResolver } from "./key-resolver.js";
|
|
81
38
|
import { createLogger } from "./observability/logger.js";
|
|
82
39
|
import { createMetrics } from "./observability/metrics.js";
|
|
83
40
|
import { installSecretEnvScrubRecorder } from "./observability/secret-env-scrub.js";
|
|
84
|
-
/** Parse argv (everything AFTER `node run-local.js`). Throws a clear Error on a missing objective. */
|
|
85
41
|
export function parseArgs(argv) {
|
|
86
42
|
let objective;
|
|
87
43
|
let root;
|
|
@@ -92,11 +48,6 @@ export function parseArgs(argv) {
|
|
|
92
48
|
let json = false;
|
|
93
49
|
const valueFlag = (i, name) => {
|
|
94
50
|
const v = argv[i];
|
|
95
|
-
// 空串/纯空白 = **没给参数**,不是"用户就想要空"(core [1689] 第 3 条同款:`??` 兜不住 `""`)。
|
|
96
|
-
// 收下它会让空串变成**隔离键**:`--session ""` → `args.session ?? uuidv7()` 得到 `""`,所有这类调用
|
|
97
|
-
// 复用同一个空 id「会话」;`--user ""` → `args.user ?? "local"` 得到 `""`,长期记忆落进匿名桶而不是
|
|
98
|
-
// 文档写的默认身份,且「传空串」与「不传」成为两个不同的桶。在解析器这一层拒才对 —— 这个函数存在的
|
|
99
|
-
// 意义本就是「这个 flag 需要一个参数」。
|
|
100
51
|
if (v === undefined || v.trim() === "" || v.startsWith("--"))
|
|
101
52
|
throw new UsageError(`${name} needs an argument`);
|
|
102
53
|
return v;
|
|
@@ -122,7 +73,7 @@ export function parseArgs(argv) {
|
|
|
122
73
|
throw new UsageError(`unknown flag: ${a}`);
|
|
123
74
|
}
|
|
124
75
|
else if (objective === undefined) {
|
|
125
|
-
objective = a;
|
|
76
|
+
objective = a;
|
|
126
77
|
}
|
|
127
78
|
else {
|
|
128
79
|
throw new UsageError(`unexpected extra argument: ${a} (quote the objective as one argument)`);
|
|
@@ -133,35 +84,15 @@ export function parseArgs(argv) {
|
|
|
133
84
|
}
|
|
134
85
|
return { objective, root, json, ...(scenario ? { scenario } : {}), ...(session ? { session } : {}), ...(workspace ? { workspace } : {}), ...(user ? { user } : {}) };
|
|
135
86
|
}
|
|
136
|
-
/** A bad-usage error → print to stderr + exit 2 (distinct from a task failure exit 1). */
|
|
137
87
|
export class UsageError extends Error {
|
|
138
88
|
}
|
|
139
|
-
/**
|
|
140
|
-
* The model GATEWAY pre-flight (mirrors the doctor message): the engine cannot run a real
|
|
141
|
-
* task without (a) a gateway base URL that ends in `/v1` and (b) the gateway/model API key resolvable from
|
|
142
|
-
* env. We surface a "you still need to set: …" list rather than letting the first brain call crash with a
|
|
143
|
-
* cryptic auth/connect error. Returns a list of missing items (empty = OK).
|
|
144
|
-
*
|
|
145
|
-
* NOTE: with an injected mock `brain` the gateway is irrelevant (no real call is made), so the caller skips
|
|
146
|
-
* this check in that case — the check is about a REAL run, not the assembly.
|
|
147
|
-
*/
|
|
148
89
|
export function missingGatewayRequirements(config) {
|
|
149
90
|
const missing = [];
|
|
150
|
-
// (a) base URL: loadConfig always has a default, but a real local user must point it at THEIR gateway and
|
|
151
|
-
// it must carry the OpenAI-compatible `/v1` suffix (the brain appends `/chat/completions`).
|
|
152
91
|
if (!config.gatewayBaseUrl || !/\/v1\/?$/.test(config.gatewayBaseUrl)) {
|
|
153
92
|
missing.push("MODEL_GATEWAY_BASEURL (the OpenAI-compatible gateway URL, must end in /v1)");
|
|
154
93
|
}
|
|
155
|
-
// (b) the key: either the single gateway key (MODEL_API_KEY) OR — when the catalog pins a per-model
|
|
156
|
-
// apiKeyEnv (sema-registry / config.d) — that named env var must be present. A model without its own
|
|
157
|
-
// apiKeyEnv falls back to the gateway key, so the gateway key is the floor.
|
|
158
94
|
const perModelEnvs = Object.values(config.modelApiKeyEnv ?? {});
|
|
159
95
|
const haveGatewayKey = !!config.gatewayApiKey;
|
|
160
|
-
// The gateway key is the FLOOR: per-model keys only WAIVE it when EVERY catalog model the resolver may be
|
|
161
|
-
// asked about carries (and has set) its own apiKeyEnv. A model with no per-model key falls back to the
|
|
162
|
-
// gateway key, so a MIXED catalog (some models keyed, some not) with the gateway key unset would crash on
|
|
163
|
-
// the unkeyed model's first brain call. We dedupe via the model objects' `.name` so the synthetic `default`
|
|
164
|
-
// / model-id ALIAS entries of config.models (which mirror an existing per-name model) are not over-counted.
|
|
165
96
|
const catalogModelNames = new Set(Object.values(config.models ?? {}).map((m) => m.name));
|
|
166
97
|
const allModelsHaveOwnKey = catalogModelNames.size > 0 &&
|
|
167
98
|
[...catalogModelNames].every((n) => {
|
|
@@ -180,47 +111,9 @@ export function missingGatewayRequirements(config) {
|
|
|
180
111
|
}
|
|
181
112
|
return missing;
|
|
182
113
|
}
|
|
183
|
-
/**
|
|
184
|
-
* design/181 件三:run-local 腿的**部署治理链装配**(#180)。
|
|
185
|
-
*
|
|
186
|
-
* 与另外两条腿(HTTP 的 `resolveSpec`、durable park 的赎回腿)装的是**同一条**链,经**同一个**构造口:
|
|
187
|
-
* 审批基线作 base 的 `toolPolicy` 座,再经**折叠属主** `applyRuntimeGovernance`(→ core `tightenTaskSpec`)
|
|
188
|
-
* 叠上部署治理段(autonomy / commandPolicy / MANUAL_MODE_SHELL_GATE / 守卫集)。本函数一条 policy 都不
|
|
189
|
-
* 自己合成。
|
|
190
|
-
*
|
|
191
|
-
* · **审批基线 = adjudicated allow-all**:一次性 CLI 没有 durable 审批设施(没有 `/decide` 腿可赎回),
|
|
192
|
-
* 所以 `createApprovalBaselinePolicy` 走 durable 缺席那一臂。它是一条**在场的** effect-aware 策略
|
|
193
|
-
* (满足 core 的 `hasEffectAwareGate`),运维照旧用 `AUTONOMY` / `commandPolicy` / 守卫集在其上收紧。
|
|
194
|
-
* · **裁决 env 恒 host 形,锚 `workspaceDir`**:run-local 的手就跑在这台机器的这个目录上
|
|
195
|
-
* (`--workspace` / config.d workdir / cwd 三选一,调用方已解析好),所以 `cwd` 恒在场 —— 相对形写目标
|
|
196
|
-
* 由 core 按 `rootPath` 解析进真身裁决,构造口的词法补层(沙箱腿那条)在这里自动不铺。
|
|
197
|
-
* ✅ **旧残余面已收口(core 5.19.0 #108,2026-08-08 校正)**:`rootPath` 是装配期的**静态**值,而 core
|
|
198
|
-
* 的写工具解析相对形用的是被 Bash `cd` 就地改写的活 cwd —— 于是 `cd .git/hooks` 之后
|
|
199
|
-
* `Write("pre-commit")` 落进守卫段却判不出来(两条 host 腿同形,本腿与 resolve-spec)。5.19.0 起守卫
|
|
200
|
-
* 按 `ToolCallRequest.cwd`(引擎每次调用从同一只 tracked cwd 盖戳)解析写目标,本腿零改动即得保护;
|
|
201
|
-
* **不对称是 core 故意的**:目标跟活 cwd 走,守卫自己配置的目录仍锚静态根,`cd` 搬不动栅栏。全文与
|
|
202
|
-
* 证据见 `deployment-governance.ts` 的 `RelativeTargetLexicalEnv` 顶注,端到端正控钉见
|
|
203
|
-
* test/run-local.test.ts(该钉已由 🟡 特征化翻成 🔴 正控)。
|
|
204
|
-
* · **禁 memoize**:四个旋钮都是热改字段,每次装配现读活 config(与另两条腿同一姿势)。
|
|
205
|
-
*/
|
|
206
114
|
export function applyLocalGovernance(base, config, workspaceDir) {
|
|
207
115
|
return applyRuntimeGovernance({ ...base, toolPolicy: createApprovalBaselinePolicy(config) }, createDeploymentGovernanceInputs(config, { env: new NodeExecutionEnv({ cwd: workspaceDir }), cwd: workspaceDir }));
|
|
208
116
|
}
|
|
209
|
-
/**
|
|
210
|
-
* design/181 件三:**onAsk 座**(§0 潜雷 / §5 锁 6)。
|
|
211
|
-
*
|
|
212
|
-
* 🔴 为什么这个座位是接治理链的**同窗必做项**:run-local 有 checkpointStore(file backend),所以
|
|
213
|
-
* `AUTONOMY=ask` ⇒ `shellGate:"always"` ⇒ Bash 进 `irreversibleTools` ⇒ park lane effective ⇒ **每一次
|
|
214
|
-
* shell 调用都 durable park 成一张永远没人 resume 的 checkpoint**,任务挂起、CLI 退 1。一次性 CLI 里
|
|
215
|
-
* 没有任何赎回腿,那不是「等人来批」,是卡死。core 的 `isLiveApproverSeat(onAsk)` 为真时 `suspendAsk`
|
|
216
|
-
* 早退(prepare-task dist 亲读),ask 于是落回本回调 —— 把 park 换成「问人 / 当场拒」的唯一正当接法。
|
|
217
|
-
*
|
|
218
|
-
* ⛔ **不在 toolPolicy 层做 ask→deny 映射**:core 自己就有 headless auto-deny 的语义与文案,在策略层
|
|
219
|
-
* 复刻一份 = 同源谎(两处判据日后各改各的)。裁决归 core,座位归这里。
|
|
220
|
-
*
|
|
221
|
-
* 🔴 非 TTY 臂答 `false`(真 deny),**绝不答 `"unavailable"`**:后者被 core 读成「席位够不到」并**改道
|
|
222
|
-
* `suspendAsk`**(dist `core/hooks.js:296` 的 `approverUnavailable` 重投),上面那颗雷会原样回来。
|
|
223
|
-
*/
|
|
224
117
|
export function createLocalApprover(io) {
|
|
225
118
|
return async (req, signal) => {
|
|
226
119
|
if (!io.isTty) {
|
|
@@ -232,15 +125,6 @@ export function createLocalApprover(io) {
|
|
|
232
125
|
return io.askYesNo(`approve "${req.toolName}"? ${req.message} [y/N] `, signal);
|
|
233
126
|
};
|
|
234
127
|
}
|
|
235
|
-
/** 默认的 TTY 问人器:一问一答,回答写 **stderr**(stdout 是任务结果的专用管道,别污染它)。
|
|
236
|
-
* streams 可注入=EOF/abort 两臂测得动(默认 process.stdin/stderr)。
|
|
237
|
-
*
|
|
238
|
-
* 🔴 决断三臂(codex 验收轮 high,红先修):`readline/promises` 的 `question` 在输入流 EOF/close 时
|
|
239
|
-
* **不决**(promise 悬挂)——Ctrl-D、关闭的 stdin、任务取消都会把 runLocal 永久挂住,文件后端的锁
|
|
240
|
-
* 跟着被拖住,下一次同 root 调用直接卡死。三臂全部 fail-closed 决 false:
|
|
241
|
-
* · `signal` abort(core `OnAsk` 递进来的取消,含预先 aborted);
|
|
242
|
-
* · readline `close`(输入流 end/close 时 readline 自关);
|
|
243
|
-
* · `question` 自身 reject(abort throw / 流错误)。 */
|
|
244
128
|
export async function promptYesNoOnTty(prompt, signal, streams = { input: process.stdin, output: process.stderr }) {
|
|
245
129
|
if (signal?.aborted)
|
|
246
130
|
return false;
|
|
@@ -263,11 +147,6 @@ export async function promptYesNoOnTty(prompt, signal, streams = { input: proces
|
|
|
263
147
|
rl.close();
|
|
264
148
|
}
|
|
265
149
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Boot the engine in LOCAL mode, run ONE task to completion, print the result. Returns the process exit
|
|
268
|
-
* code (0 = completed, 1 = the task did not complete / a fatal error, 2 = bad usage / unmet env). Pure of
|
|
269
|
-
* `process.exit` so a test can assert the code; `main()` applies it.
|
|
270
|
-
*/
|
|
271
150
|
export async function runLocal(argv, deps = {}) {
|
|
272
151
|
const print = deps.print ?? ((l) => console.log(l));
|
|
273
152
|
const printErr = deps.printErr ?? ((l) => console.error(l));
|
|
@@ -282,47 +161,18 @@ export async function runLocal(argv, deps = {}) {
|
|
|
282
161
|
}
|
|
283
162
|
throw err;
|
|
284
163
|
}
|
|
285
|
-
// FORCE local posture: a one-shot CLI is a single-machine run. Set the knob BEFORE loadConfig so
|
|
286
|
-
// (1) createConfigProvider selects the LocalConfigProvider and (2) config.ts defaults the `host` exec lane
|
|
287
|
-
// (the TOC first-run default for CONFIG_PROVIDER=local with no explicit REMOTE_EXEC). Honor an operator who
|
|
288
|
-
// already set it to something else (don't override an explicit choice) — but never leave it remote-by-default.
|
|
289
164
|
if (!process.env.CONFIG_PROVIDER)
|
|
290
165
|
process.env.CONFIG_PROVIDER = "local";
|
|
291
|
-
// FORCE the durable backends OFF: a one-shot, single-machine CLI has nothing durable to persist
|
|
292
|
-
// and deliberately does NOT assemble the server-only StoreBackend. A shared .env that carries
|
|
293
|
-
// SESSION_BACKEND set to anything but the enum's "memory"/"mysql"/"auto" would otherwise boot-reject
|
|
294
|
-
// outright (SESSION_BACKEND is an enum env, validated at load — an old value like the retired public
|
|
295
|
-
// name "tidb" is rejected, not silently coerced), and even a legit "mysql" value would pull in a
|
|
296
|
-
// server-only SQL session store this one-shot CLI has no business assembling. Unlike CONFIG_PROVIDER/
|
|
297
|
-
// REMOTE_EXEC (where an explicit operator choice can be HONORED), a DB session store cannot work here
|
|
298
|
-
// at all, so we FORCE — never honor — this knob to in-memory so the CLI just works regardless of the
|
|
299
|
-
// shared config. Long-term memory needs no forcing: the design/138 memory ENGINE is file-based and
|
|
300
|
-
// default-ON here (single-user; MEMORY_ENGINE=off and MEMORY_ENGINE_DIR pass through loadConfig as-is).
|
|
301
166
|
process.env.SESSION_BACKEND = "memory";
|
|
302
|
-
// 🔴 REQUIRE_PRINCIPAL is the same shared-config footgun class: run-local IS the single
|
|
303
|
-
// user at their own terminal — there is no untrusted caller to gate — but a server .env carrying
|
|
304
|
-
// REQUIRE_PRINCIPAL=true would flip `memoryEngineBackendFor`/`memoryScopeFor` into the multi-tenant
|
|
305
|
-
// memory-dark branch (plus the host-power gates), silently killing the CLI's long-term memory. FORCE it off.
|
|
306
167
|
process.env.REQUIRE_PRINCIPAL = "false";
|
|
307
|
-
// --root wins, then the standard local roots (mirrors main.ts's local-root resolution).
|
|
308
168
|
const root = args.root ?? process.env.CONFIG_LOCAL_DIR ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
309
169
|
if (args.root)
|
|
310
|
-
process.env.CONFIG_LOCAL_DIR = args.root;
|
|
170
|
+
process.env.CONFIG_LOCAL_DIR = args.root;
|
|
311
171
|
const config = loadConfig();
|
|
312
172
|
const logger = deps.logger ?? createLogger(config.logLevel);
|
|
313
173
|
const metrics = createMetrics();
|
|
314
|
-
// A-033.3(#256,codex 复审 medium 真修):run-local **也**要装密钥剥除的留痕面。这不是 main.ts 的
|
|
315
|
-
// 复制粘贴 —— 它恰恰是最该有的那条腿:run-local 是 TOC 本地形,模型可驱动的 shell 跑在**用户自己的
|
|
316
|
-
// 机器**上、继承的是用户**真实的** env(脑钥/仓库令牌/DB 凭据),而这两条剥除腿(hostExecutionEnvFactory
|
|
317
|
-
// 的 mergeEnv 与 makeLoadProjectMemory 的 git 子进程)在本入口全都接线了。不装 ⇒ 收集器有了、sink 是空的,
|
|
318
|
-
// 这条腿保留 A-033.3 要消灭的那个「零披露」原状(而且进程一退,连计数都没了)。
|
|
319
174
|
installSecretEnvScrubRecorder({ logger, metrics });
|
|
320
|
-
// F10 (codex audit): run-local used to drop the pre-logger config diagnostics on the floor (S20 soft-knob
|
|
321
|
-
// warnings + [792]④ boot notices) — main.ts drained them, this entrypoint didn't. Shared helper, no drift.
|
|
322
175
|
logConfigDiagnostics(logger);
|
|
323
|
-
// ── Config source (DUAL-MODE §4): resolve the effective config from the LOCAL FileConfigStore and apply it
|
|
324
|
-
// OVER the env defaults (config.d/.env drives models/roles). Unreachable/empty → keep the env-derived
|
|
325
|
-
// config (the local root may not exist yet — "fill .env and run" still works off env). ──
|
|
326
176
|
const configProvider = createConfigProvider({
|
|
327
177
|
provider: config.configProvider ?? "local",
|
|
328
178
|
...(config.configLocalDir ? { localDir: config.configLocalDir } : { localDir: root }),
|
|
@@ -331,30 +181,14 @@ export async function runLocal(argv, deps = {}) {
|
|
|
331
181
|
try {
|
|
332
182
|
const r = await configProvider.fetchEffective(undefined);
|
|
333
183
|
if (r) {
|
|
334
|
-
// [898] tolerant read(registry-core 0.10.12):catalog 域坏文件不再连坐整包回落 env——坏域按 default
|
|
335
|
-
// 落+这里 per-domain warn 点名(文件路径在 error 文案里),好域照常生效。gate 域坏仍走下面整包 catch。
|
|
336
184
|
for (const de of r.domainErrors ?? [])
|
|
337
185
|
logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: "domain fell to schema defaults; other domains applied normally" });
|
|
338
186
|
effective = r.effective;
|
|
339
187
|
applyEffective(config, r.effective, logger);
|
|
340
|
-
// MCP servers (config.d/mcp.json or center) — applied here (NOT inside applyEffective, which only
|
|
341
|
-
// covers models/roles/gates/teams) so run-local matches the server path (main.ts:446). Restart-to-apply.
|
|
342
188
|
if (effective?.mcp)
|
|
343
189
|
config.mcpServers = resolveMcpServers(effective.mcp, logger);
|
|
344
|
-
// DESIGN-269 车1 件1:A2A peers, same line as MCP — applied HERE so run-local matches the server path
|
|
345
|
-
// (boot/config-center.ts). Restart-to-apply.
|
|
346
|
-
// ⚠️ 如实记(codex 对抗复审 finding 4,验真后采纳为**文档更正**):今天只有**远端 center** 的
|
|
347
|
-
// `/effective` 里带 `a2a` 域时这一行才写得进值。**本地 config.d 车道还接不上** —— registry-core
|
|
348
|
-
// 锁定版的可移植域表里没有 a2a,`config-provider.ts` 的 `mapToServiceEffective` 闭式投影也就没有这
|
|
349
|
-
// 一域可投。补齐属于 registry-core(上游件),不在本车射程;在那之前别在文档里承诺 config.d/a2a.json。
|
|
350
190
|
if (effective?.a2a)
|
|
351
191
|
config.a2aPeers = resolveA2aPeers(effective.a2a, logger);
|
|
352
|
-
// #A4([4147] 件1):READ 容纳面四键的 center 腿 —— 与 server 腿同一只函数、同一条 restart-to-apply
|
|
353
|
-
// 线(本腿是一次性 CLI,"restart" 即下次运行)。env 显式恒赢的判据在函数内,两腿零分家。
|
|
354
|
-
// ⚠️ 如实记(与上面 a2a 那条同款诚实句):**本地 config.d 车道今天接不上** —— registry-core 的
|
|
355
|
-
// 可移植域表里没有 readFace 域,`config-provider.ts` 的 `mapToServiceEffective` 闭式投影也就没有这一
|
|
356
|
-
// 域可投;这一行今天只在 `CONFIG_PROVIDER=remote` 的 run-local 形上写得进值。补齐属于 registry-core
|
|
357
|
-
// (上游件),在那之前别在文档里承诺 config.d/read-face.json。
|
|
358
192
|
applyCenterReadFace(config, r.effective, logger);
|
|
359
193
|
logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version });
|
|
360
194
|
}
|
|
@@ -362,10 +196,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
362
196
|
catch (err) {
|
|
363
197
|
logger.warn("config_source_unreachable_using_env", { source: configProvider.kind, err: String(err) });
|
|
364
198
|
}
|
|
365
|
-
// ── remoteExec (DUAL-MODE §5): in local mode, honor the SHARED config.d/remote-exec.json via the registry
|
|
366
|
-
// loader (the SAME file the client's `agent-cli config` writes). MISSING → keep config.ts's host default;
|
|
367
|
-
// PRESENT host → it wins (workdir→workspaceBase); corrupt → fail-closed. Mirrors main.ts's block; the
|
|
368
|
-
// isolated lanes (e2b/k8s/ssh/adb) stay env-sourced (a one-shot CLI is the host lane by design). ──
|
|
369
199
|
try {
|
|
370
200
|
const fileRx = await loadRemoteExec(root);
|
|
371
201
|
if (fileRx?.provider === "host") {
|
|
@@ -385,10 +215,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
385
215
|
printErr(`config.d/remote-exec.json is invalid: ${String(err)}`);
|
|
386
216
|
return 2;
|
|
387
217
|
}
|
|
388
|
-
// ── design/181 件三 boot 期两件(与 HTTP 腿同一只判据)────────────────────────────────────────
|
|
389
|
-
// ① 守卫集的**可编译性**门:编译发生在每一次装配上,一条「一个路径段都没有」的模式(`"/"`)会 THROW,
|
|
390
|
-
// 运维从那条错误里看不出是自己的 env 写错了。共享 .env 的坏值必须炸在启动上——这条腿按 run-local 的
|
|
391
|
-
// 既定 UX 落地(doctor 文案 + 退 2),而不是把栈抛给用户。
|
|
392
218
|
try {
|
|
393
219
|
assertGuardPatternsUsable(config);
|
|
394
220
|
}
|
|
@@ -397,18 +223,12 @@ export async function runLocal(argv, deps = {}) {
|
|
|
397
223
|
printErr(`(local config root: ${root} — fix it in ${join(root, ".env")} or your shell env; \`SENSITIVE_WRITE_PATTERNS=off\` disables the guard set)`);
|
|
398
224
|
return 2;
|
|
399
225
|
}
|
|
400
|
-
// ② UNGATED 补偿:接上审批基线后 core 的 `hasEffectAwareGate` 恒真 ⇒ 它那条 "write-capable hand tools
|
|
401
|
-
// are present but UNGATED" 的信号在这条腿上结构性消失。判据与文案是**同一只**构造口的
|
|
402
|
-
// (buildOnlySensitiveBaselineWarning),两个座位量按本腿的真形取:durable 门恒不在场(一次性 CLI
|
|
403
|
-
// 没有 /decide 赎回腿),单用户 auto-accept 基线的适用性判据与 main.ts 逐字同源。
|
|
404
226
|
const ungatedCompensation = buildOnlySensitiveBaselineWarning(config, {
|
|
405
227
|
durableEnabled: false,
|
|
406
228
|
singleUserAutoAcceptBaseline: config.requirePrincipal !== true && !hasOperatorGateIntent(config),
|
|
407
229
|
});
|
|
408
230
|
if (ungatedCompensation)
|
|
409
231
|
logger.warn(ungatedCompensation.event, ungatedCompensation.fields);
|
|
410
|
-
// ── Pre-flight: with a REAL brain, validate the gateway + key and print the doctor-style guidance instead
|
|
411
|
-
// of crashing on the first brain call. With an injected mock brain, no real call happens → skip. ──
|
|
412
232
|
if (!deps.brain) {
|
|
413
233
|
const missing = missingGatewayRequirements(config);
|
|
414
234
|
if (missing.length > 0) {
|
|
@@ -419,22 +239,11 @@ export async function runLocal(argv, deps = {}) {
|
|
|
419
239
|
return 2;
|
|
420
240
|
}
|
|
421
241
|
}
|
|
422
|
-
// ── Minimal runtime assembly (the SAME building blocks as main.ts, server-only bits omitted). ──
|
|
423
242
|
const brain = deps.brain ?? createBrain(config, deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {});
|
|
424
243
|
const pricing = buildPricing(config.models);
|
|
425
|
-
// Persistent LOCAL stores (TOC): core's file-backed StorageBackend under the data root → session + long-term
|
|
426
|
-
// memory + checkpoints SURVIVE across invocations (no DB). flock'd single-instance per data dir; a second
|
|
427
|
-
// concurrent instance throws in the constructor — surface a clear message instead of a stack.
|
|
428
244
|
let fileBackend;
|
|
429
245
|
try {
|
|
430
246
|
fileBackend = new FileStorageBackend({
|
|
431
|
-
// 🔴 腐读披露座(codex 复审 R1-medium,#205 件3 连带):本批把 `sessionPolicyStore` 接进了两只
|
|
432
|
-
// Runner,而 core 对**坏掉的**策略文件是 documented fail-open —— 读不出来按「没有规则」处理。
|
|
433
|
-
// 这条 fail-open 本身是 core 的裁定(抛会让整条任务死、也会自锁修复写),但它**不许无声**
|
|
434
|
-
// (#157 安全轴纪律)。一个座覆盖 core 今天转发的三张脸,后果**各不相同**,所以文案只陈述共同事实
|
|
435
|
-
// (「一次持久读被当成缺席」)并把后果按脸分列,不下统一结论(codex R2-medium:原文案把「少了
|
|
436
|
-
// 会话规则这一层」写成「整个任务无约束」——那会误导事故定级,而且对另外两张脸根本不成立)。
|
|
437
|
-
// ENOENT 不触发(那是真缺席)。
|
|
438
247
|
root,
|
|
439
248
|
onCorruptRead: (info) => logger.warn("file_store_corrupt_read", {
|
|
440
249
|
path: info.path,
|
|
@@ -450,47 +259,24 @@ export async function runLocal(argv, deps = {}) {
|
|
|
450
259
|
}
|
|
451
260
|
catch (e) {
|
|
452
261
|
printErr(`cannot open local data dir ${root}: ${e instanceof Error ? e.message : String(e)}`);
|
|
453
|
-
// 🔴 core 5.23.0([3372] 件④):这个构造器的第二个拒绝面是 design/183 的 I6 adoption boot 门。
|
|
454
|
-
// 那一类失败**不能**再补「换一个 --root」这句 —— 换根 = 丢下一个迁移到一半的数据根(与
|
|
455
|
-
// `plugins/store-backend.ts` 的 local 臂同案同判)。core 的拒绝句已自带出路,上面那行已如实打印。
|
|
456
262
|
if (!(e instanceof AdoptionError)) {
|
|
457
263
|
printErr("(another run-local/engine instance may own it; finish it first, or use a different --root)");
|
|
458
264
|
}
|
|
459
265
|
return 2;
|
|
460
266
|
}
|
|
461
267
|
const sessionStore = fileBackend.sessionStore;
|
|
462
|
-
const keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
|
|
463
|
-
// Host exec lane: run on THIS machine, in a FIXED workspace (CC-like) — the cwd by default (or --workspace /
|
|
464
|
-
// a config.d workdir). The agent's file edits PERSIST there + are user-visible; the dir is the user's OWN and
|
|
465
|
-
// is NEVER deleted (workspaceDir mode). One env per task; the Runner owns its lifetime.
|
|
268
|
+
const keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
|
|
466
269
|
const hostRx = config.remoteExec?.provider === "host" ? config.remoteExec : undefined;
|
|
467
270
|
const workspaceDir = args.workspace ?? hostRx?.workspaceBase ?? process.cwd();
|
|
468
271
|
const executionEnvFactory = hostRx
|
|
469
272
|
? hostExecutionEnvFactory({
|
|
470
273
|
workspaceDir,
|
|
471
|
-
// systematic-audit (uniformity with main.ts:442 + the worktree-lane gates): gate background-shell by tenancy.
|
|
472
|
-
// run-local is a single-user CLI (no untrusted caller) so requirePrincipal is ~always false → ON; passing the
|
|
473
|
-
// gate keeps the policy uniform if a shared .env ever sets REQUIRE_PRINCIPAL, never relying on the `?? true`.
|
|
474
274
|
backgroundShell: config.requirePrincipal !== true,
|
|
475
275
|
...(hostRx.commandTimeoutMs != null ? { commandTimeoutMs: hostRx.commandTimeoutMs } : {}),
|
|
476
276
|
})
|
|
477
277
|
: undefined;
|
|
478
|
-
// TOC local LSP (core 1.190): run-local IS the pure-local host lane, so mount core's `NodeLspManager` (local
|
|
479
|
-
// child_process language servers over stdio, CC `services/lsp` parity) on the host lane. Default ON (degrades
|
|
480
|
-
// gracefully to grep/read if a server binary is absent); `LSP_HOST_ENABLED=false` opts out (design/158 B4 split
|
|
481
|
-
// this lane off `LSP_ENABLED`; 🪦 server 3.0.0 起 `LSP_ENABLED=false` 是 fail-loud 墓碑,拒启指路新名,
|
|
482
|
-
// 不再是可用的 opt-out)。`requirePrincipal !== true`
|
|
483
|
-
// keeps it uniform with main.ts (a no-op here — run-local is single-user — but never relies on that). Both runners.
|
|
484
278
|
const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ? new NodeLspManager({ log: (event, fields) => logger.info(event, fields) }) : undefined;
|
|
485
|
-
// Long-term memory (design/138 S1): run-local IS the single-user lane, so the file-based memory ENGINE is
|
|
486
|
-
// default-ON (MEMORY_ENGINE=off opts out). The data root doubles as the engine config-root fallback so a
|
|
487
|
-
// run-local and the local HTTP service on one box share ONE memory home (MEMORY_ENGINE_DIR overrides).
|
|
488
279
|
const memoryEngine = memoryEngineBackendFor(config, root);
|
|
489
|
-
// 142-S2.5-W1: TOC 同步 client 腿——run-local 是 file memory 形态本尊(loadConfig 已拒
|
|
490
|
-
// DB backend 上的 MEMORY_SYNC_*)。run-local 没接 onMemoryHarvestReport 钩子(一次性 CLI:harvest 只在
|
|
491
|
-
// 任务收尾发生一次,fire-and-forget 轮会被进程退出腰斩),按设计的后备姿势:boot fire-and-forget 一轮
|
|
492
|
-
// (拉中心增量,失败 warn 不阻断;进程退出腰斩也安全——cursor 保留纪律下轮重收敛)+ 任务结束后 await
|
|
493
|
-
// 一轮(推本次 harvest 的新记忆;syncOnce 串行链自动排在 boot 轮之后)。
|
|
494
280
|
let memorySync;
|
|
495
281
|
if (config.memorySync && memoryEngine) {
|
|
496
282
|
memorySync = createMemorySyncRunner({
|
|
@@ -506,12 +292,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
506
292
|
else if (config.memorySync) {
|
|
507
293
|
logger.warn("memory_sync_configured_but_memory_dark", { note: "MEMORY_SYNC_URL is set but the file memory engine is dark (MEMORY_ENGINE=off / remote exec lane) — no sync rounds will run" });
|
|
508
294
|
}
|
|
509
|
-
// codex-2 F3([985]e run-local 半场):config.d/prompts.json 的 catalog 轴在这条 lane 也要真消费——
|
|
510
|
-
// 判形(与 server 同一候选门)→ CenterPromptSource(File 形,<root>/prompt-epoch 自管)→ 两 Runner 的
|
|
511
|
-
// promptSource。坏形/apply 失败=warn + 内置照跑(单进程本地形,无 refresh 重试拍;下次启动再试)。
|
|
512
295
|
const promptEpochDir = join(root, "prompt-epoch");
|
|
513
|
-
// codex-7:守护工厂与 main.ts 同姿势——File 店构造同步 mkdir,坏 prompt-epoch 路径不得把可选特性
|
|
514
|
-
// 变成 run-local 启动致命(Memory 兜底=candidate 不跨启动持久;内置照跑)。
|
|
515
296
|
const promptSource = (() => {
|
|
516
297
|
try {
|
|
517
298
|
return new CenterPromptSource({
|
|
@@ -526,7 +307,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
526
307
|
}
|
|
527
308
|
})();
|
|
528
309
|
try {
|
|
529
|
-
await promptSource.restore();
|
|
310
|
+
await promptSource.restore();
|
|
530
311
|
const promptsRaw = effective?.prompts;
|
|
531
312
|
if (promptsRaw !== undefined) {
|
|
532
313
|
const pv = validatePromptsDomain(promptsRaw);
|
|
@@ -536,33 +317,23 @@ export async function runLocal(argv, deps = {}) {
|
|
|
536
317
|
logger.warn("center_prompts_invalid", { phase: "run-local", error: pv.error, note: "malformed prompts face — built-ins serve" });
|
|
537
318
|
}
|
|
538
319
|
else {
|
|
539
|
-
// no prompts face on this boot: a persisted candidate whose file was removed must not survive (same
|
|
540
|
-
// cleared semantics as the server lane) — applyCatalogToSource's current() probe handles the owed disable.
|
|
541
320
|
await applyCatalogToSource(promptSource, undefined, undefined, logger, "run-local");
|
|
542
321
|
}
|
|
543
322
|
}
|
|
544
323
|
catch (e) {
|
|
545
324
|
logger.warn("center_prompts_catalog_restore_failed", { err: String(e), note: "catalog candidate absent — built-ins serve" });
|
|
546
325
|
}
|
|
547
|
-
// design/181 件三:**onAsk 座**(全文理由见 {@link createLocalApprover})。两只 Runner 同装 —— 委派出去
|
|
548
|
-
// 的子任务也在同一台机器上动手,少装一只就是「主任务问人、子任务 park 卡死」。
|
|
549
326
|
const onAsk = createLocalApprover({
|
|
550
327
|
isTty: deps.isTty ?? process.stdin.isTTY === true,
|
|
551
328
|
askYesNo: deps.askYesNo ?? promptYesNoOnTty,
|
|
552
329
|
printErr,
|
|
553
330
|
});
|
|
554
|
-
/** core 的 `RunnerDeps.onError` 座(两只 Runner 共用一只闭包 —— 两处各写一份正是漂移的成因)。 */
|
|
555
331
|
const engineOnError = (err, ctx) => logger.warn("engine_error", {
|
|
556
332
|
phase: ctx.phase,
|
|
557
333
|
sessionId: ctx.sessionId,
|
|
558
334
|
...(ctx.classification !== undefined ? { classification: ctx.classification } : {}),
|
|
559
335
|
err: String(err),
|
|
560
336
|
});
|
|
561
|
-
// ⚠️ `createOrgMemoryAdmissionWiring` 有一条 **throw** 路径:`MEMORY_ORG_DIRECTORY_JSON` 形坏时
|
|
562
|
-
// `parseOrgDirectoryStatic` 启动期炸(config 层按 A4 分层刻意不校验它)。server 那边炸=进程拒启,
|
|
563
|
-
// 正确;但本腿的既定 UX 是「doctor 文案 + 退 2」(同 assertGuardPatternsUsable / remote-exec-file-invalid
|
|
564
|
-
// 两处),把栈抛给在终端里敲命令的人是回归。另一条 throw(多租户 org 键 + 无目录源)在本腿**结构上
|
|
565
|
-
// 不可达**——上面已把 `REQUIRE_PRINCIPAL` 强制成 false。
|
|
566
337
|
let orgMemoryAdmission;
|
|
567
338
|
try {
|
|
568
339
|
orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
|
|
@@ -573,50 +344,10 @@ export async function runLocal(argv, deps = {}) {
|
|
|
573
344
|
await fileBackend.dispose();
|
|
574
345
|
return 2;
|
|
575
346
|
}
|
|
576
|
-
/**
|
|
577
|
-
* [3397]①②③ / #205 件3 —— run-local 的两份 Runner deps 走 **server 同一只共享基座**
|
|
578
|
-
* (`createSharedRunnerDeps`,main.ts:432 的同款展开)。
|
|
579
|
-
*
|
|
580
|
-
* 为什么必须收编:这两份 deps 此前各自手写同源键,而漏配**没有任何编译期或运行期信号** —— 复扫亲验
|
|
581
|
-
* 到三处真漂移:`toolResultStore` 只在主 runner(委派子代的 `ReadToolResult(ref)` 恒空,正是 core
|
|
582
|
-
* 1.219 修过的那个病在 sub 腿复发)、`config.tiers` **全文件零接线**(而上面的 `applyEffective` 真在
|
|
583
|
-
* 填它 ⇒ tier 词 / CC 别名在这条腿上解不出来)、`hooks` / `tracer` / `sessionPolicyStore` 两处都没有。
|
|
584
|
-
* 基座是一份,新增共享键不可能只挂一半。
|
|
585
|
-
*
|
|
586
|
-
* ── 逐参裁定(local 车道**真差异**保留,不为收编硬造 stub)───────────────────────────────────
|
|
587
|
-
* · `brain` / `pricing` / `promptSource` / `executionEnvFactory` / `lspManager`:本腿真有,直供。
|
|
588
|
-
* · `config`(基座据以取 models / roles / **tiers** / usageWindows):本腿真有 —— tiers 正是本件修的漏。
|
|
589
|
-
* · `tracer`:`createTracer(metrics)` 真形。配额/舰队/prompt-manifest 四个可选跟踪器是 server 面的
|
|
590
|
-
* 设施,本腿没有 ⇒ 不传(不是 stub:那几个参数本就可选,缺席即该轴不记账)。
|
|
591
|
-
* · `deploymentHooks`:`createPermissionDeniedMeter(metrics)` —— main.ts 在**没有** toolTracer 时的
|
|
592
|
-
* 同一只值(tool-trace 落库面是 server 侧设施,一次性 CLI 无处落)。
|
|
593
|
-
* · `sessionPolicyStore`:`fileBackend.sessionPolicyStore`(core 的 File 形,其文档原话就是「本地部署
|
|
594
|
-
* 要让会话规则跨重启存活就接这里」)。E6 规则是 subtract-only ⇒ 方向只会更紧;同一个数据根上的
|
|
595
|
-
* 本地 HTTP 服务写下的规则,这条腿本就该认。
|
|
596
|
-
* · `usageWindowStore`:与 `config.usageWindows` **成对**(main.ts 同规:两键同真同假)—— 配了窗才接
|
|
597
|
-
* File 账本,没配窗时接一个空账本只会让 core 以为这条腿有治理窗。
|
|
598
|
-
* · `toolResultStore`:`fileBackend.toolResultStore`(本件的主修点:现在主/子同源)。
|
|
599
|
-
* · `orgMemoryAdmission`:`createOrgMemoryAdmissionWiring(...)` 真形。本腿 `REQUIRE_PRINCIPAL` 被强制
|
|
600
|
-
* false(见上),所以那条多租户拒启探测结构上不可达;产物在无 center / 无 `MEMORY_ORG_DIRECTORY_JSON`
|
|
601
|
-
* 时是「resolver 缺席 + deployment 自证集」——即 `MEMORY_SCOPE=org:x` 的本地用户从此走得通自证通道。
|
|
602
|
-
* · `backgroundAgentStore` / `mailboxStore` / `rosterStore`:**undefined** —— core 的 `FileStorageBackend`
|
|
603
|
-
* 根本不供这三个店(durable 后台子代 / tier-3 懒复活 / 持久名册都是 server+SQL 面的设施)。造个内存
|
|
604
|
-
* 冒牌货等于让 core 以为「具名子代能跨进程复活」,而一次性 CLI 的进程下一秒就没了。
|
|
605
|
-
* · `sharedMemoryStores`:**undefined** —— 供给面只有 SQL 形(`store-backend.ts` 明写:单机造 file 形
|
|
606
|
-
* 等于给一个人的部署做「团队共享」,能力面会因此说谎)。
|
|
607
|
-
*/
|
|
608
|
-
// design/170 件B/C/D(#252):三个部署治理座席 —— 本地形与 server 形**同一只装配口**。为什么本地形也要
|
|
609
|
-
// 有:`COMPLIANCE_PROFILE` / `LOCKED_CONFIG_KEYS` / `RETENTION_MAX_AGE_DAYS` 是从同一份本地 `.env` 读出来
|
|
610
|
-
// 的,一个写下了档位的用户在 `sema` 与本机 HTTP 服务上必须得到同一个答案(两条腿对同一份配置两套行为
|
|
611
|
-
// 正是本仓在册的病族)。三键未配 = 三座席缺席 = 本腿逐字零变化。
|
|
612
|
-
// #270 车2:lane 自持门在本地形也要跑,理由与上一段逐字相同 —— `RETENTION_SWEEP_INTERVAL_SEC>0`
|
|
613
|
-
// 是从同一份 `.env` 读出来的,而本地形**结构上**没有执行器(file 店三方法不存在,诚实声明 "none")。
|
|
614
|
-
// 少了这道门,一个写下了那根旋钮的用户会在 `sema` 上得到一台自称有留存治理、其实什么都不删的机器,
|
|
615
|
-
// 而在 HTTP 形上得到一条拒启 —— 两条腿对同一份配置两套行为,正是本仓在册的病族。
|
|
616
347
|
assertRetentionLaneWirable({
|
|
617
348
|
intervalSec: config.retentionSweep.intervalSec,
|
|
618
349
|
policy: config.retentionPolicy,
|
|
619
|
-
executor: undefined,
|
|
350
|
+
executor: undefined,
|
|
620
351
|
stores: [
|
|
621
352
|
{ name: "sessionStore", store: sessionStore },
|
|
622
353
|
{ name: "checkpointStore", store: fileBackend.checkpointStore },
|
|
@@ -636,11 +367,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
636
367
|
const sharedRunnerDeps = createSharedRunnerDeps({
|
|
637
368
|
config,
|
|
638
369
|
governanceSeams,
|
|
639
|
-
logger,
|
|
640
|
-
// 交接件⑤:commit 尾注署名座进**共享基座** ⇒ 主 runner 与 subRunner 同源。
|
|
641
|
-
// [931]① clay 拍:run-local = Sema 品牌本地形态,commit 尾注接 Sema 署名(core 1.300 缺省已翻转不署)。
|
|
642
|
-
// 修前它只写在下面主 runner 的差异键里 ⇒ **委派出去的子代提交不带署名**,而它提交进的是同一个仓、
|
|
643
|
-
// 代表的是同一个部署。那不是一次有理由的分歧(差异键注释块逐条列了理由,唯独没提它),是漏配。
|
|
370
|
+
logger,
|
|
644
371
|
hands: { commitCoAuthor: "Sema <noreply@vivi-ai.com>" },
|
|
645
372
|
brain,
|
|
646
373
|
pricing,
|
|
@@ -657,87 +384,42 @@ export async function runLocal(argv, deps = {}) {
|
|
|
657
384
|
usageWindowStore: config.usageWindows ? fileBackend.usageWindowStore : undefined,
|
|
658
385
|
orgMemoryAdmission,
|
|
659
386
|
sharedMemoryStores: undefined,
|
|
660
|
-
// #324:run-local 无 configCenter runtime = 无中心撤销面 ⇒ 席诚实缺席(pre-338 语义)。
|
|
661
387
|
mcpRevocations: undefined,
|
|
662
388
|
});
|
|
663
389
|
const runnerDeps = {
|
|
664
390
|
...sharedRunnerDeps,
|
|
665
|
-
// ── 以下为主 runner 的差异键(不在共享基座;逐个有因)──────────────────────────────────────
|
|
666
|
-
// (`hands` 已随共享基座展开 —— 交接件⑤;这里手写同名键会 override 展开、把 subRunner 重新甩开。)
|
|
667
391
|
sessionStore,
|
|
668
392
|
...(memoryEngine ? { memoryBackend: memoryEngine.backend, memoryEngineDir: memoryEngine.root } : {}),
|
|
669
393
|
checkpointStore: fileBackend.checkpointStore,
|
|
670
|
-
// design/181 件三:core 的降级/配置类告警此前被 `deps.onError?.()` 的可选链整条吞掉(这条腿从不接这
|
|
671
|
-
// 个座),包括那条 "write-capable hand tools are present but UNGATED"。接上 stderr(logger.warn 走
|
|
672
|
-
// stderr)后其余告警——compaction / prompt-cache / mcp / memory / hook 的降级——也不再无声。
|
|
673
394
|
onError: engineOnError,
|
|
674
395
|
onAsk,
|
|
675
|
-
// design/113 C4: run-local IS the single-user host lane (cwd = --workspace ?? process.cwd()) — the CLI path where
|
|
676
|
-
// CLAUDE.md project-awareness most belongs. Wire it unless explicitly disabled.
|
|
677
396
|
...(config.projectMemoryEnabled
|
|
678
397
|
? { loadProjectMemory: makeLoadProjectMemory({ logger }), probeInstructionSources: makeProbeInstructionSources() }
|
|
679
398
|
: {}),
|
|
680
399
|
};
|
|
681
400
|
const runner = new Runner(runnerDeps);
|
|
682
|
-
// Sub-task runner (council lenses/discussion members) — mirrors main.ts's subRunner, INCLUDING executionEnvFactory so a delegated
|
|
683
|
-
// child gets hands (Bash/Read/Edit) on the host lane. Without it the child's handsEnabled is false — the exact
|
|
684
|
-
// "subagent couldn't run pwd" defect fixed at main.ts:799; this is its run-local twin, caught by the systematic audit.
|
|
685
|
-
// [1367]① fork server half(main.ts subRunner 同款):ForkRoutingSessionStore——fork/resume 形
|
|
686
|
-
// (requireExisting)→ host 文件店优先(core 1.350 hostSessionFork 在宿主店 fork,拆店=响亮
|
|
687
|
-
// resume.session_not_found),普通子任务 → 私有 TTL 店(throwaway 姿势保留)。
|
|
688
|
-
// #205 件3:与主 runner **同一只**共享基座展开(上面那份 `sharedRunnerDeps`)——此前这里是一行手写
|
|
689
|
-
// 字面量,`toolResultStore`/`tiers`/`hooks`/`tracer`/`sessionPolicyStore` 全漏,子代 lane 静默降级。
|
|
690
|
-
// 差异键只剩会话店与两个座位;**checkpointStore 有意不给**(main.ts 的 sub 腿给它是为了子代 durable
|
|
691
|
-
// park,而本腿的 park 设施结构上不存在:一次性 CLI 没有 `/decide` 赎回腿,gated ask 由 TTY 真人当场答
|
|
692
|
-
// ——上面 `buildOnlySensitiveBaselineWarning` 传 `durableEnabled: false` 记的就是同一件事实)。
|
|
693
401
|
const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
|
|
694
402
|
const subRunner = new Runner(subRunnerDeps);
|
|
695
|
-
// #196:无手孪生一对 —— **mirrors main.ts**(那边是 `handslessRunner` / `handslessSubRunner` 两只,同 deps
|
|
696
|
-
// 摘掉 executionEnvFactory)。run-local 是同形装配点,漏这一对 = 本地 lane 上 scan/council/discuss 的最终
|
|
697
|
-
// roster 仍被并集进全量可写手(server 修了、CLI 静默留病 —— 这条腿的 subRunner 当年就是这么漏配的)。
|
|
698
403
|
const handslessRunner = new Runner(withoutExecutionEnv(runnerDeps));
|
|
699
404
|
const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
|
|
700
|
-
// Capability layer (loadSkills + buildScenarios + selectScenario) — assembled exactly like main.ts.
|
|
701
405
|
const skills = loadSkills(config.skillsDir);
|
|
702
406
|
const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
|
|
703
|
-
// systematic-audit: wire the env-configured WebSearch backend (WEB_SEARCH_PROVIDER) exactly like main.ts, so the
|
|
704
|
-
// default scenario's WebSearch tool is assembled on the CLI path too (it was silently never built before).
|
|
705
407
|
const webSearchCfg = webSearchConfigFromEnv();
|
|
706
408
|
const webSearch = webSearchCfg ? createWebSearchBackend(webSearchCfg) : undefined;
|
|
707
|
-
// #318:会话级任务清单车道。run-local 无 SQL 后端 ⇒ 内存臂(进程内 per-session)。一次性 CLI 的
|
|
708
|
-
// 单进程内本来就只解析一次场景,接它的意义在于**装配面同源**:server 与 run-local 走同一条
|
|
709
|
-
// `ScenarioDeps.taskListStoreFor` 契约,不让 CLI 腿静默留在「per-call 私店」的修前形上。
|
|
710
|
-
// `--session` 跨调用续聊的待办清单不跨进程存活(local 形的诚实边界,USAGE 已登记)。
|
|
711
409
|
const taskListLane = createTaskListLane({ logger });
|
|
712
410
|
const scenarioDeps = {
|
|
713
411
|
runner, subRunner, handslessSubRunner, model: "default", skills, repoClient, metrics, logger,
|
|
714
412
|
...(webSearch ? { webSearch } : {}),
|
|
715
413
|
taskListStoreFor: taskListLane.storeFor,
|
|
716
|
-
brandIdentity: true,
|
|
414
|
+
brandIdentity: true,
|
|
717
415
|
};
|
|
718
416
|
const scenarios = buildScenarios(scenarioDeps);
|
|
719
|
-
// Center-/config.d-declared scenarios (composition-as-config): overlay over the built-ins, center wins —
|
|
720
|
-
// mirrors main.ts:453-457 so a config.d/scenarios.json scenario resolves identically in run-local (no drift).
|
|
721
417
|
if (effective?.scenarios) {
|
|
722
418
|
const { overlay } = centerScenarios(scenarioDeps, effective.scenarios.scenarios, Object.keys(scenarios), logger);
|
|
723
419
|
Object.assign(scenarios, overlay);
|
|
724
420
|
}
|
|
725
|
-
|
|
726
|
-
// no auth/principal/durable/approval/budget-cap-from-body). Fresh sessionId; @-model mention honored. ──
|
|
727
|
-
// #273 alias:run-local lane 的唯一归一点(旧名 team → discuss + 响亮 warn),与 HTTP lane 的
|
|
728
|
-
// `gateScenarioRequest` 同一只属主。归一之后 `scenarioName` 只可能是现役主名——下面的墙钟/mcp/a2a
|
|
729
|
-
// 判据点按主名写,不做双名判。
|
|
730
|
-
const requested = canonicalScenarioName(args.scenario ?? config.defaultScenario, Object.keys(scenarios), logger);
|
|
421
|
+
const requested = args.scenario ?? config.defaultScenario;
|
|
731
422
|
const scenarioName = scenarios[requested] ? requested : "default";
|
|
732
|
-
// A scenario may THROW HttpError synchronously when a prerequisite is unmet (e.g. `scan` needs
|
|
733
|
-
// GIT_API_BASEURL + a repo — which a one-shot CLI may not
|
|
734
|
-
// supplies). The server catches this in resolveSpec/prepareSpec and returns a clean 4xx; here we mirror that
|
|
735
|
-
// with the doctor-style UX (message to stderr + exit 2) instead of letting it bubble to the fatal exit-1 path.
|
|
736
|
-
// --session REUSES a session (durable multi-turn across invocations, via the file session store); absent →
|
|
737
|
-
// a fresh uuidv7 (still persisted). --user is the stable memory/identity scope so long-term memory
|
|
738
|
-
// accumulates per user (default "local" = the single TOC user). The file stores acquire/append lazily.
|
|
739
|
-
// #318:解析点上提到场景装配**之前** —— 场景工厂现在要 `ScenarioContext.sessionId`(会话级任务
|
|
740
|
-
// 清单的分区键)。纯移动,取值逐字不变(uuidv7() 无前置依赖)。
|
|
741
423
|
const sessionId = args.session ?? uuidv7();
|
|
742
424
|
let cap;
|
|
743
425
|
try {
|
|
@@ -748,7 +430,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
748
430
|
printErr(`cannot run scenario "${scenarioName}": ${err.message}`);
|
|
749
431
|
if (scenarioName === "scan")
|
|
750
432
|
printErr("(the `scan` scenario needs a git API base URL and a repo — run-local cannot supply a repo; use the default scenario)");
|
|
751
|
-
void lspManager?.dispose();
|
|
433
|
+
void lspManager?.dispose();
|
|
752
434
|
await fileBackend.dispose();
|
|
753
435
|
return 2;
|
|
754
436
|
}
|
|
@@ -756,87 +438,49 @@ export async function runLocal(argv, deps = {}) {
|
|
|
756
438
|
}
|
|
757
439
|
const mention = parseModelMention(args.objective, Object.keys(config.models));
|
|
758
440
|
const scope = args.user ?? "local";
|
|
759
|
-
// Timeout policy mirrors the server (taskWallClockSec): run-local IS the pure single-user
|
|
760
|
-
// TOC lane → NO wall clock by default (core's wedge safety-net still applies); explicit TASK_TIMEOUT_SEC
|
|
761
|
-
// wins with its raise-only floor (discuss 3600s — a multi-agent coordinator legitimately exceeds the base).
|
|
762
|
-
// [854]④ 不落此腿:per-request body.limits 是 HTTP body 车道(resolveTaskLimits,main.ts resolveSpec);
|
|
763
|
-
// run-local 无 body,配速仍走 env(要 CLI flag 时另开件,别在这里半做)。
|
|
764
|
-
// 🔴 2026-08-01 env fail-loud 族:原为 `Math.max(0, Math.floor(Number(env ?? "0")) || 0)` ——
|
|
765
|
-
// `TASK_TIMEOUT_SEC="3600s"`(带单位后缀,运维最常见的写法)得 NaN,再被 `|| 0` 折成 0,
|
|
766
|
-
// 而 0 在这里的语义是**无超时**。运维以为设了一小时上限,实际把超时关掉了,方向恰好相反,
|
|
767
|
-
// 且启动期零提示。改走 numEnv:非法值当场 fail-loud 并指名键与实际值。
|
|
768
|
-
// (numEnv 的注释早就预言了这个失败模式,只是它没 export、跨文件够不到,于是这两处手写了。)
|
|
769
441
|
const taskTimeoutSec = Math.max(0, Math.floor(numEnv("TASK_TIMEOUT_SEC", "0")));
|
|
770
442
|
const timeoutSec = taskWallClockSec(taskTimeoutSec, false, scenarioName === "discuss");
|
|
771
443
|
const mcp = mcpForScenario(config.mcpServers, scenarioName);
|
|
772
|
-
// DESIGN-269 车1 件3(本腿半场):A2A peers 同样按场景过滤进 spec。**只有 center/config 基线这一半**
|
|
773
|
-
// —— run-local 无 HTTP body,所以没有「调用方自带 peer」那条腿(与 [854]④ 的 body.limits 同姿势:
|
|
774
|
-
// 要 CLI flag 时另开件,别在这里半做)。缺席仍是缺席:`a2aForScenario` 恒返回 undefined 而非 []。
|
|
775
444
|
const a2a = a2aForScenario(config.a2aPeers, scenarioName);
|
|
776
|
-
// A-002.9(#180 governance 缺口的同文件兄弟残余):运营方预算天花板两枚 —— 与 server 主路径
|
|
777
|
-
// (boot/resolve-spec.ts)**同键同算法**,共用 budget.ts 的 `cappedCeiling`。此腿无 body(见上一段
|
|
778
|
-
// 的 [854]④ 记账),所以 requested 恒缺席 ⇒ env 天花板在场时**直接成为** spec 值,天花板为 0/未设时
|
|
779
|
-
// 仍是 undefined(键缺席=无预算,与改前逐字等价)。
|
|
780
|
-
// ⚠️ 只接这两枚:`degrade`(MODEL_DEGRADE_*)是另一条旋钮线,不在本条案射程内。
|
|
781
445
|
const maxCostUsd = cappedCeiling(undefined, config.maxTaskCostUsd);
|
|
782
446
|
const maxTokens = cappedCeiling(undefined, config.maxTaskTokens);
|
|
783
|
-
// core 5.8.0:预算族与墙钟同住 limits;三键各自缺席就不写(全缺 ⇒ 整个 limits 键缺席)。
|
|
784
447
|
const limits = {
|
|
785
448
|
...(timeoutSec !== undefined ? { maxWalltimeMs: timeoutSec * 1000 } : {}),
|
|
786
449
|
...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
|
|
787
450
|
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
788
451
|
};
|
|
789
|
-
// design/181 件三:自建的 spec 字面量经**同一条**部署治理链(见 {@link applyLocalGovernance})——
|
|
790
|
-
// 审批基线 + autonomy/commandPolicy/MANUAL_MODE_SHELL_GATE/守卫集,tighten-only,折叠属主仍是 core。
|
|
791
452
|
const spec = applyLocalGovernance({
|
|
792
453
|
objective: mention.cleanedText || args.objective,
|
|
793
454
|
sessionId,
|
|
794
|
-
// design/129: run-local IS the pure TOC lane → session-scoped background children
|
|
795
|
-
// (CC Backgrounded semantics; parity with the server's single-user posture).
|
|
796
455
|
backgroundScope: "session",
|
|
797
|
-
// [1909]⑧(core 5.23.0 `TaskSpec.oneShot`,#205 件1 的**本腿**半场,codex R2-high 抓获):这条腿
|
|
798
|
-
// 是**定义上**的一次性提交 —— 本文件头注第一句就是「跑完一条任务、打印 TaskResult、退出」,
|
|
799
|
-
// `runLocal` 返回后 Runner 全被 dispose、进程即散。缺席这个键时,core 会照默认(交互态)给后台
|
|
800
|
-
// 委派 / run_workflow 发「结束回合,你会被通知」的回执,而这里**没有下一个回合**能接住那条通知 ——
|
|
801
|
-
// 那正是本键被造出来要消灭的丢结果病族(BGB drilldown case 2)。
|
|
802
|
-
// 与 HTTP 腿的差别在**来源**而不在语义:那边是调用方表态(缺席不写键),这边是**进程形态的事实**,
|
|
803
|
-
// 所以无条件为真,不看任何 body/表态(本腿根本收不到客户端表态,同 permissionMode 的 scoped-out 理由)。
|
|
804
456
|
oneShot: true,
|
|
805
|
-
principal: scope,
|
|
806
|
-
// design/138 S1: enable the memory engine for this run (spec.memory is core's per-task activation half of
|
|
807
|
-
// the deps.memoryBackend switch). Scope = the --user identity (default "local", the single TOC user).
|
|
457
|
+
principal: scope,
|
|
808
458
|
...(memoryEngine ? { memory: memorySpecForRequest(scope, undefined) } : {}),
|
|
809
459
|
model: mention.model ?? "default",
|
|
810
460
|
getApiKeyAndHeaders: keyResolver,
|
|
811
461
|
tools: cap.tools,
|
|
812
462
|
skills: cap.skills,
|
|
813
463
|
...(cap.promptProvider ? { promptProvider: cap.promptProvider } : {}),
|
|
814
|
-
// [849]→[2400] 场景层定死终验已无生产者(autonomous 退役);OR 折入形保留,与 resolveSpec 同语义。
|
|
815
464
|
...(cap.finalVerification === true ? { finalVerification: true } : {}),
|
|
816
465
|
...(mcp ? { mcp } : {}),
|
|
817
466
|
...(a2a ? { a2a } : {}),
|
|
818
|
-
// core 5.8.0:timeoutSec 键退役 → maxWalltimeMs(毫秒);taskWallClockSec 仍产秒,换算在上方 limits 合成处。
|
|
819
467
|
...(Object.keys(limits).length > 0 ? { limits } : {}),
|
|
820
468
|
}, config, workspaceDir);
|
|
821
469
|
logger.info("run_local_start", { scenario: scenarioName, model: spec.model, sessionId, exec: config.remoteExec?.provider ?? "in-process" });
|
|
822
|
-
// ── Run ONE task to completion (the sync /v1/tasks path: plain runTask, no verify/cascade). ──
|
|
823
470
|
let result;
|
|
824
471
|
let exitCode = 0;
|
|
825
|
-
// #196:与 HTTP 腿同一条选路 —— 场景声明 hands:"none" 时任务跑无手 Runner(band 不 mount)。
|
|
826
472
|
const taskRunner = pickHandsRunner(cap.hands, { full: runner, handsless: handslessRunner });
|
|
827
473
|
try {
|
|
828
474
|
result = await taskRunner.runTask(spec);
|
|
829
475
|
}
|
|
830
476
|
catch (err) {
|
|
831
477
|
printErr(`task crashed: ${err instanceof Error ? err.message : String(err)}`);
|
|
832
|
-
// Clean shutdown even on a crash.
|
|
833
478
|
runner.sessions.dispose();
|
|
834
479
|
subRunner.sessions.dispose();
|
|
835
|
-
void lspManager?.dispose();
|
|
480
|
+
void lspManager?.dispose();
|
|
836
481
|
await fileBackend.dispose();
|
|
837
482
|
return 1;
|
|
838
483
|
}
|
|
839
|
-
// ── Output: --json → the full TaskResult; else the final assistant text (or salvaged/blocked/error info). ──
|
|
840
484
|
if (args.json) {
|
|
841
485
|
print(JSON.stringify(result, null, 2));
|
|
842
486
|
}
|
|
@@ -844,7 +488,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
844
488
|
print(result.result);
|
|
845
489
|
}
|
|
846
490
|
else {
|
|
847
|
-
// Non-completed: surface the most useful text we have, then a one-line status footer to stderr.
|
|
848
491
|
const salvage = result.result || result.salvagedOutput || result.blockedReason || "";
|
|
849
492
|
if (salvage)
|
|
850
493
|
print(salvage);
|
|
@@ -852,9 +495,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
852
495
|
}
|
|
853
496
|
if (result.status !== "completed")
|
|
854
497
|
exitCode = 1;
|
|
855
|
-
// 142-S2.5-W1: 任务结束后 await 一轮同步(推本次 harvest 的新记忆到中心;串行链自动排在 boot 轮
|
|
856
|
-
// 之后,绝不并发互踩)。await 保证轮次在进程退出前完成(fire-and-forget 会被 exit 腰斩);失败只
|
|
857
|
-
// warn——同步是增益面,绝不改变任务本身的退出码。输出已打印完,这里的等待不挡用户看结果。
|
|
858
498
|
if (memorySync) {
|
|
859
499
|
try {
|
|
860
500
|
await memorySync.syncOnce("post-task");
|
|
@@ -863,23 +503,16 @@ export async function runLocal(argv, deps = {}) {
|
|
|
863
503
|
logger.warn("memory_sync_failed", { reason: "post-task", err: String(err) });
|
|
864
504
|
}
|
|
865
505
|
}
|
|
866
|
-
// ── Clean shutdown: dispose the warm sessions; the host env workspace is rm -rf'd by the Runner on task
|
|
867
|
-
// end (executionEnvFactory env → destroy()). No server/pool/reaper to tear down (none were created). ──
|
|
868
506
|
runner.sessions.dispose();
|
|
869
507
|
subRunner.sessions.dispose();
|
|
870
|
-
void lspManager?.dispose();
|
|
871
|
-
// SUCCESS path too. `runLocal` is exported/reusable (tests/embeddings call it directly, no process.exit), so
|
|
872
|
-
// without this a completed LSP-touching run orphans a fresh set of stdio servers; the leak is per-invocation.
|
|
508
|
+
void lspManager?.dispose();
|
|
873
509
|
await fileBackend.dispose();
|
|
874
510
|
return exitCode;
|
|
875
511
|
}
|
|
876
|
-
/** Process entrypoint: run, print, exit with the resolved code. */
|
|
877
512
|
async function main() {
|
|
878
513
|
const code = await runLocal(process.argv.slice(2));
|
|
879
514
|
process.exit(code);
|
|
880
515
|
}
|
|
881
|
-
// Only auto-run when invoked as a script (not when imported by the test). Compare REALPATH-resolved paths
|
|
882
|
-
// so symlinked roots (/tmp→/private/tmp) and the tsx loader's file URL still match the entrypoint argv[1].
|
|
883
516
|
function isMainModule() {
|
|
884
517
|
const entry = process.argv[1];
|
|
885
518
|
if (!entry)
|