@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
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/158 A10:composition root 分段 —— 执行环境装配(per-task 镜像 / cwd·shellEnv 注册表 /
|
|
3
|
-
* 五条 remote-exec 腿的工厂 / 三层工厂装饰 / LSP 管理器)。
|
|
4
|
-
*
|
|
5
|
-
* 纯搬运:函数体逐字来自 `main.ts`(原 997-1302 行),缩进不变;新增的只有 import 与包壳。
|
|
6
|
-
*
|
|
7
|
-
* ⚠️ **位置即契约 —— 装饰顺序就是行为**:`executionEnvFactory` 由内向外被依次包装,
|
|
8
|
-
* 次序逐字保留不可动:
|
|
9
|
-
* base(e2b|k8s|ssh|adb|host|local-docker)
|
|
10
|
-
* → `withRemoteScratchpad`(远程车道首 exec 才 mkdir)
|
|
11
|
-
* → `withWorktreeIsolation`(host 车道 per-agent worktree,定 cwd)
|
|
12
|
-
* → `TaskEnvRegistry.wrapFactory`(SendUserFile 直传登记 taskId→env)
|
|
13
|
-
* → 附件物化(内层先定 cwd,附件才落在最终 cwd —— 原文逐字注释即此)
|
|
14
|
-
* → `withSandboxPathEnvSlot`(#165,**最外层**:沙箱写门要裁决的是 core 最终拿到手的那个实例,
|
|
15
|
-
* 而内层会换实例/改写方法,所以登记必须排在全部换手之后)。
|
|
16
|
-
* 另:`lspManager` 声明在 host 工厂**之后**,host 工厂闭包对它是同作用域前向引用(闭包每任务才跑,
|
|
17
|
-
* 那时已初始化)——本段整体搬运保住了这个前向引用,拆开两个模块就会破。
|
|
18
|
-
*/
|
|
19
1
|
import { existsSync } from "node:fs";
|
|
20
2
|
import { join } from "node:path";
|
|
21
3
|
import { NodeLspManager } from "@sema-agent/core";
|
|
@@ -37,50 +19,24 @@ import { reapOrphanWorktrees, withWorktreeIsolation } from "../plugins/worktree-
|
|
|
37
19
|
import { customPkgSourceFromEnv, derivePkgSourceEnv } from "../sandbox-pkg-source.js";
|
|
38
20
|
import { effectiveHostWorkspace } from "../task-cwd.js";
|
|
39
21
|
import { isSandboxPathAdjudicationLane, sandboxPathEnvSlots, withSandboxPathEnvSlot } from "./deferred-sandbox-path-env.js";
|
|
40
|
-
/**
|
|
41
|
-
* #97 R11: the perSessionCwd/perSessionShellEnv setters below share one idiom (delete-then-set so a
|
|
42
|
-
* re-set moves the tail to MRU, then oldest-first eviction on overflow). Local to this file — NOT the
|
|
43
|
-
* public {@link import("../bounded-session-map.js").BoundedSessionMap} seam, because both callers below
|
|
44
|
-
* still hand out the raw `Map` (its declared type threads through `resolve-spec.ts` as `Map<string, V>`,
|
|
45
|
-
* not a class instance) — this only DRYs the two identical closures, it changes no public shape.
|
|
46
|
-
*/
|
|
47
22
|
function boundedSessionSetter(map, maxEntries) {
|
|
48
23
|
return (sid, value) => {
|
|
49
|
-
map.delete(sid);
|
|
24
|
+
map.delete(sid);
|
|
50
25
|
map.set(sid, value);
|
|
51
26
|
if (map.size > maxEntries)
|
|
52
|
-
map.delete(map.keys().next().value);
|
|
27
|
+
map.delete(map.keys().next().value);
|
|
53
28
|
};
|
|
54
29
|
}
|
|
55
30
|
export function createExecutionEnv(ctx) {
|
|
56
31
|
const { config, logger, metrics, taskAttachmentStore } = ctx;
|
|
57
|
-
// design/48 v1b: deployment-level remote execution(部署级路由 + 懒汉连接). When
|
|
58
|
-
// REMOTE_EXEC=e2b, this deployment is a "code-agent worker" — each task's hand runs in a per-task E2B VM,
|
|
59
|
-
// provisioned lazily on first hand use (a plan-only task pays zero VM cost). Unset → in-process stub env.
|
|
60
|
-
// design/48 v1b + design/61: pick the remote-exec backend by provider (peer adapters). SSH/ADB target REAL
|
|
61
|
-
// systems (not isolated/suspendable) — the leader's autonomy + the design/37 gate + HITL run accordingly.
|
|
62
|
-
// §7 P0.5 per-task sandbox image: the trusted-control-plane bridge from resolveSpec (resolves the requested
|
|
63
|
-
// profile → digest with the caller's principal, fail-closed) to the k8s factory (applies it per-pod). See
|
|
64
|
-
// per-task-image.ts + resolveSpec below. Worker-global default image is the fallback when no profile is requested.
|
|
65
32
|
const perTaskImage = new PerTaskImageRegistry();
|
|
66
|
-
// RFC A2: session-keyed environment selection (PROFILE intent, never a digest) written by the model-facing
|
|
67
|
-
// SelectEnvironment tool; resolveSpec re-resolves it FAIL-CLOSED per task (same re-admit as a body profile).
|
|
68
33
|
const sessionEnvSelection = new SessionEnvironmentSelection();
|
|
69
|
-
// [#40 / TOC cwd seam] per-session launch dir → the host lane's agent workspace (resolveSpec registers it gated by
|
|
70
|
-
// cwdHonored; the host factory reads it by ctx.sessionId). Only ever written for the single-user host lane.
|
|
71
|
-
// BOUNDED (avoid unbounded growth over the persistent local engine's lifetime): an LRU cap; the shell
|
|
72
|
-
// re-sends cwd on every request, so evicting a stale session is harmless (it re-registers on next use).
|
|
73
34
|
const MAX_CWD_SESSIONS = 4096;
|
|
74
35
|
const perSessionCwd = new Map();
|
|
75
36
|
const setSessionCwd = boundedSessionSetter(perSessionCwd, MAX_CWD_SESSIONS);
|
|
76
|
-
// [R-survey / TOC shellEnv seam, core PLAN批注] per-session `settings.env` → the host lane's agent shell env
|
|
77
|
-
// (resolveSpec registers it gated by cwdHonored — single-user host lane only; the host factory merges it by
|
|
78
|
-
// ctx.sessionId). design/107 "env = capability axis". Same LRU bound + re-send-on-every-request semantics as cwd.
|
|
79
37
|
const perSessionShellEnv = new Map();
|
|
80
38
|
const setSessionShellEnv = boundedSessionSetter(perSessionShellEnv, MAX_CWD_SESSIONS);
|
|
81
39
|
let executionEnvFactory;
|
|
82
|
-
// SVC-3 worktree isolation: the reaper (defined far below) reuses ONE long-lived git base env + repoRoot to
|
|
83
|
-
// `git worktree prune` crash-orphaned worktrees. Holders are populated when the wrapper is wired (host lane).
|
|
84
40
|
let worktreeReap;
|
|
85
41
|
if (config.remoteExec?.provider === "e2b") {
|
|
86
42
|
executionEnvFactory = e2bExecutionEnvFactory({
|
|
@@ -89,11 +45,7 @@ export function createExecutionEnv(ctx) {
|
|
|
89
45
|
...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
|
|
90
46
|
...(config.remoteExec.livenessMs != null ? { livenessMs: config.remoteExec.livenessMs } : {}),
|
|
91
47
|
...(config.remoteExec.allowInternetAccess != null ? { allowInternetAccess: config.remoteExec.allowInternetAccess } : {}),
|
|
92
|
-
// [1452]/[1454] CWD-A: workspace 根透传(E2B_MOUNT_PATH)——缺省仍 /home/user(adapter 默认)。
|
|
93
48
|
...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
|
|
94
|
-
// RFC B5: region package-source env set rides the operator-trusted sandboxEnv — ENV-FIRST, the derived
|
|
95
|
-
// ecosystem overrides (pip/uv/npm/go/rustup/flutter/…) beat the image's baked CN ENV on existing images,
|
|
96
|
-
// no rebake needed. Explicit E2B_SANDBOX_ENV keys win over derived on collision (deliberate override).
|
|
97
49
|
...(() => {
|
|
98
50
|
const merged = { ...derivePkgSourceEnv(config.sandboxPkgSource, customPkgSourceFromEnv()), ...(config.remoteExec.sandboxEnv ?? {}) };
|
|
99
51
|
return Object.keys(merged).length > 0 ? { sandboxEnv: merged } : {};
|
|
@@ -113,26 +65,14 @@ export function createExecutionEnv(ctx) {
|
|
|
113
65
|
...(config.remoteExec.runtimeClass != null ? { runtimeClass: config.remoteExec.runtimeClass } : {}),
|
|
114
66
|
...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
|
|
115
67
|
...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
|
|
116
|
-
// Resource-profile knobs. config.ts reads K8S_MEMORY/K8S_CPU; this passthrough was
|
|
117
|
-
// missing, so the adapter silently stayed on its 2Gi default (Kata VM = default_memory 2G + limit 2G = 4G
|
|
118
|
-
// MemTotal — task02b run9/10 integration gradle OOM'd there even after the env was set).
|
|
119
68
|
...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
|
|
120
69
|
...(config.remoteExec.cpu ? { cpu: config.remoteExec.cpu } : {}),
|
|
121
70
|
...(config.remoteExec.s3Snapshot ? { s3Snapshot: config.remoteExec.s3Snapshot } : {}),
|
|
122
|
-
// RFC B5: region package-source env set → pod container env (operator-trusted, never task-controlled).
|
|
123
|
-
// ENV-FIRST: pod env beats image ENV and reaches every exec — the derived ecosystem overrides switch
|
|
124
|
-
// region on existing images; the SEMA_PKG_SOURCE marker drives the in-image hook (file-bound pieces).
|
|
125
71
|
...(() => {
|
|
126
72
|
const derived = derivePkgSourceEnv(config.sandboxPkgSource, customPkgSourceFromEnv());
|
|
127
73
|
return Object.keys(derived).length > 0 ? { podEnv: derived } : {};
|
|
128
74
|
})(),
|
|
129
75
|
};
|
|
130
|
-
// §7 P0.5: per-pod image override. resolveSpec resolved the requested profile→digest (with the caller's
|
|
131
|
-
// principal, fail-closed) and registered it by sessionId — the ONLY identifier stable across the factory ctx
|
|
132
|
-
// on every path (adversarial-review round-2: /v1/runs mints its own durable taskId that CLOBBERS spec.taskId,
|
|
133
|
-
// so taskId-keying misses on the primary path; sessionId survives). get() is non-removing (the factory may be
|
|
134
|
-
// invoked >once per logical task). Absent ⇒ the worker-global k8sCfg.image. Building the factory per task is a
|
|
135
|
-
// cheap closure alloc; the ref is immutable (repo@digest). podSpecPatch can NOT carry the image (hard invariant).
|
|
136
76
|
executionEnvFactory = (ctx) => k8sExecutionEnvFactory({ ...k8sCfg, image: perTaskImage.get(ctx.sessionId) ?? k8sCfg.image })(ctx);
|
|
137
77
|
}
|
|
138
78
|
else if (config.remoteExec?.provider === "ssh") {
|
|
@@ -152,22 +92,10 @@ export function createExecutionEnv(ctx) {
|
|
|
152
92
|
});
|
|
153
93
|
}
|
|
154
94
|
else if (config.remoteExec?.provider === "host") {
|
|
155
|
-
// DUAL-MODE §5: the TOC `host` lane — run on THIS machine, no container (isolation=none). Fan-out still
|
|
156
|
-
// works (bounded by one box). Secrets are env-NAMEs the host resolves from its own process.env.
|
|
157
95
|
{
|
|
158
|
-
// R7 self-wake: inject the self-wake SchedulerCapability backend on the single-user TOC host lane
|
|
159
|
-
// (opt-in SCHEDULER_ENABLED). `hasScheduler(env)` then mounts CronCreate/CronDelete/CronList(旧名 CronCancel、Sleep 已 design/136 撤除——L8 注释订正,且挂载在 core prepare-task 非 createHandsToolkit); the TOC shell
|
|
160
|
-
// daemon reads the SAME ~/.sema/scheduled_tasks.json store (registry-core /node binding) and fires due intents.
|
|
161
|
-
// Multi-tenant (requirePrincipal) routes scheduling to center, NOT this host daemon → gate it off there. ONE
|
|
162
|
-
// backend instance at boot → every host task shares the same store file.
|
|
163
96
|
const hostScheduler = config.schedulerEnabled && config.requirePrincipal !== true
|
|
164
97
|
? new FileSchedulerBackend({
|
|
165
98
|
...(config.schedulerStorePath ? { storePath: config.schedulerStorePath } : {}),
|
|
166
|
-
// [1009]② host-signal: the spawning shell knows whether a resident daemon will honor session
|
|
167
|
-
// wakeups — SCHEDULER_SESSION_WAKEUP=false flips the capability off at CONSTRUCTION (instance-
|
|
168
|
-
// lifetime snapshot, core TOCTOU 契约) so core's ScheduleWakeup refuses up front.
|
|
169
|
-
// [2436] session-lifetime 同族 host-signal(5.6.0):headless(无 daemon 收割)⇒
|
|
170
|
-
// supportsSessionLifetime:false ⇒ CronCreate 前门拒收 + backend 第二道拒落盘,孤儿不产生。
|
|
171
99
|
...(config.schedulerSessionWakeup === false || config.schedulerSessionLifetime === false
|
|
172
100
|
? {
|
|
173
101
|
caps: {
|
|
@@ -184,37 +112,21 @@ export function createExecutionEnv(ctx) {
|
|
|
184
112
|
...(config.remoteExec.workspaceBase ? { workspaceBase: config.remoteExec.workspaceBase } : {}),
|
|
185
113
|
...(config.remoteExec.commandTimeoutMs != null ? { commandTimeoutMs: config.remoteExec.commandTimeoutMs } : {}),
|
|
186
114
|
...(hostScheduler ? { scheduler: hostScheduler } : {}),
|
|
187
|
-
// design/103 background shell: single-user host lane only (parity with scheduler/cwd). Multi-tenant host
|
|
188
|
-
// lane (which shouldn't exist — host runs on the worker's own box) → INERT (run_in_background/etc. don't mount).
|
|
189
115
|
backgroundShell: config.requirePrincipal !== true,
|
|
190
116
|
};
|
|
191
|
-
// [#40 / TOC cwd seam] per-request workspace: if resolveSpec registered a caller `cwd` for this session (gated by
|
|
192
|
-
// cwdHonored — single-user host lane only), run the agent VERBATIM in that dir (persistent, never deleted — the
|
|
193
|
-
// user's project). Else the boot-time host config (ephemeral random subdir). Mirrors the k8s perTaskImage wrap.
|
|
194
|
-
// core 1.219 `ctx.parentCwd` (dogfood finding: sub-agents landed in an EMPTY sandbox): a DELEGATED child
|
|
195
|
-
// (workflow ctx.agent / Task tool) now roots at its PARENT's working dir — CC parity — via core's TRUSTED
|
|
196
|
-
// RunInternals channel. Precedence in effectiveHostWorkspace: session cwd → parentCwd (unless the child asked
|
|
197
|
-
// for isolation:"worktree" — that wrapper wins below) → ephemeral. Container lanes never consult this.
|
|
198
117
|
executionEnvFactory = (ctx) => {
|
|
199
118
|
const cwd = effectiveHostWorkspace(perSessionCwd.get(ctx.sessionId), ctx);
|
|
200
119
|
const shellEnv = perSessionShellEnv.get(ctx.sessionId);
|
|
201
120
|
const env = hostExecutionEnvFactory({
|
|
202
121
|
...hostCfg,
|
|
203
122
|
...(cwd ? { workspaceDir: cwd } : {}),
|
|
204
|
-
...(shellEnv ? { env: shellEnv } : {}),
|
|
123
|
+
...(shellEnv ? { env: shellEnv } : {}),
|
|
205
124
|
})(ctx);
|
|
206
|
-
// core 1.191: an EPHEMERAL host workspace (no honored `cwd` → a per-task `sema-host-<id>` dir destroy()
|
|
207
|
-
// rm's) evicts its LSP servers at task-end so a busy server doesn't accumulate one heavy language server per
|
|
208
|
-
// task. cwdHonored (persistent project) is NOT evicted — its stable root stays warm across turns (CC-parity);
|
|
209
|
-
// worktree/fan-out is bounded by maxSessions=16. Only wraps when a NodeLspManager is actually active.
|
|
210
125
|
return lspManager && !cwd ? evictLspOnDestroy(env, lspManager) : env;
|
|
211
126
|
};
|
|
212
127
|
}
|
|
213
128
|
}
|
|
214
129
|
else if (config.remoteExec?.provider === "local-docker") {
|
|
215
|
-
// DUAL-MODE §5: the TOC `local-docker` lane — a per-task container on THIS machine's docker daemon
|
|
216
|
-
// (isolation:true, suspendable:false). The base image comes ENTIRELY from config (no docker.io default —
|
|
217
|
-
// domestic-images iron rule). Secrets are resolved env-NAME→value here (config.ts already did the forward).
|
|
218
130
|
executionEnvFactory = localDockerExecutionEnvFactory({
|
|
219
131
|
image: config.remoteExec.image,
|
|
220
132
|
...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
|
|
@@ -222,10 +134,6 @@ export function createExecutionEnv(ctx) {
|
|
|
222
134
|
...(config.remoteExec.dockerHost ? { dockerHost: config.remoteExec.dockerHost } : {}),
|
|
223
135
|
...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
|
|
224
136
|
...(config.remoteExec.cpus != null ? { cpus: config.remoteExec.cpus } : {}),
|
|
225
|
-
// A-002.7: the two sandbox-hardening knobs were parsed by loadConfig and honored by the adapter, but this
|
|
226
|
-
// call site dropped them — so DOCKER_PIDS_LIMIT always resolved to the adapter's `?? 512` default and
|
|
227
|
-
// DOCKER_DROP_CAPS never emitted `--cap-drop ALL`. Absent stays absent (the adapter's defaults are the
|
|
228
|
-
// unconfigured shape); only a SET knob rides, same additive form as the siblings above.
|
|
229
137
|
...(config.remoteExec.pidsLimit != null ? { pidsLimit: config.remoteExec.pidsLimit } : {}),
|
|
230
138
|
...(config.remoteExec.dropAllCaps ? { dropAllCaps: true } : {}),
|
|
231
139
|
...(config.remoteExec.network ? { network: config.remoteExec.network } : {}),
|
|
@@ -233,24 +141,10 @@ export function createExecutionEnv(ctx) {
|
|
|
233
141
|
...(config.remoteExec.env ? { env: config.remoteExec.env } : {}),
|
|
234
142
|
});
|
|
235
143
|
}
|
|
236
|
-
// [848] remote scratchpad: decorate the REMOTE sandbox lanes (e2b/k8s/local-docker/ssh) so each env
|
|
237
|
-
// lazily `mkdir -p`s `/tmp/scratchpad/<sessionId>` on its FIRST exec — never at factory time (e2b is
|
|
238
|
-
// lazy-VM; a factory-time exec would force-boot the VM). adb is skipped (no standard /tmp on Android);
|
|
239
|
-
// host keeps its worker-local scratchpad (ensureScratchpadDir at the envFacts consumer). The envFacts
|
|
240
|
-
// advertisement below uses the SAME remoteScratchpadDirFor rule, so fact and mkdir can never disagree.
|
|
241
144
|
if (executionEnvFactory && isRemoteScratchpadLane(config.remoteExec?.provider)) {
|
|
242
145
|
executionEnvFactory = withRemoteScratchpad(executionEnvFactory, logger);
|
|
243
146
|
}
|
|
244
|
-
// SVC-3 (design/97 CORE-6): wrap the factory with git-worktree isolation. When core marks an agent
|
|
245
|
-
// `isolation:"worktree"` (TRUSTED RunInternals — never a TaskSpec), the wrapper mints a per-agent detached
|
|
246
|
-
// worktree under the operator-trusted repoRoot; otherwise it passes the base env through untouched. v1
|
|
247
|
-
// covers the `host` lane (the TOC fan-out lane). Other lanes would need a worktree-ROOTED REMOTE env from
|
|
248
|
-
// `rootEnvAt` (a follow-on; a host/NodeExecutionEnv worktree is not durable-suspendable — see the plugin
|
|
249
|
-
// header) — they are left unwrapped here, isolation:"worktree" is then a no-op for them (the base env wins).
|
|
250
147
|
if (config.worktreeIsolation && executionEnvFactory) {
|
|
251
|
-
// M4 (adversarial-review): a mis-wired WORKTREE_REPO_ROOT must fail LOUD at boot, not per-task at runtime. The
|
|
252
|
-
// root must exist AND be a git repo (`.git` dir or worktree-link file). On a bad root, log an error + LEAVE
|
|
253
|
-
// isolation OFF (the factory unwrapped) rather than wiring a factory that throws on every isolated agent.
|
|
254
148
|
const wtRoot = config.worktreeIsolation.repoRoot;
|
|
255
149
|
const wtValid = existsSync(wtRoot) && existsSync(join(wtRoot, ".git"));
|
|
256
150
|
if (config.remoteExec?.provider === "host" && !wtValid) {
|
|
@@ -261,35 +155,18 @@ export function createExecutionEnv(ctx) {
|
|
|
261
155
|
});
|
|
262
156
|
}
|
|
263
157
|
else if (config.remoteExec?.provider === "host") {
|
|
264
|
-
// The SHARED base env that runs `git worktree add/remove/prune` (cwd:repoRoot is passed explicitly by
|
|
265
|
-
// core on every git call, so this env's own root is irrelevant — it just needs `exec` + git on PATH).
|
|
266
|
-
// ONE long-lived env per deployment; the reaper reuses it. `inheritEnv:"all"` so the operator's git
|
|
267
|
-
// config/credentials are visible (this is the operator's own machine + own repo, the TOC posture).
|
|
268
|
-
// systematic-audit (gate-fail-direction): every worktree-lane RemoteHostExecutionEnv carries the SAME
|
|
269
|
-
// background-shell gate as the primary host factory (main.ts ~442) — not the env's `?? true` default. The
|
|
270
|
-
// git-base env is git-ops only (agents run on rootEnvAt below), but gating it keeps the policy uniform / future-proof.
|
|
271
158
|
const gitBaseEnv = new RemoteHostExecutionEnv({ workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
|
|
272
159
|
executionEnvFactory = withWorktreeIsolation(executionEnvFactory, {
|
|
273
160
|
repoRoot: config.worktreeIsolation.repoRoot,
|
|
274
161
|
...(config.worktreeIsolation.allowedRoots ? { allowedRoots: config.worktreeIsolation.allowedRoots } : {}),
|
|
275
162
|
...(config.worktreeIsolation.commit ? { commit: config.worktreeIsolation.commit } : {}),
|
|
276
163
|
baseEnvForGit: gitBaseEnv,
|
|
277
|
-
// Host-lane worktree-rooted env: a host adapter in PERSISTENT-DIR mode — its own destroy() does NOT
|
|
278
|
-
// rm the dir (so core's `git worktree remove` owns teardown), and it keeps the host adapter's secret
|
|
279
|
-
// scrub + timeout semantics (vs a bare NodeExecutionEnv). inheritEnv defaults to "scrub" (model-driven).
|
|
280
164
|
rootEnvAt: (dir) => {
|
|
281
165
|
const env = new RemoteHostExecutionEnv({ workspaceDir: dir, backgroundShell: config.requirePrincipal !== true });
|
|
282
|
-
// A worktree root is per-agent + ephemeral (core's `git worktree remove` reclaims it at
|
|
283
|
-
// task-end), so evict its LSP servers DETERMINISTICALLY too — not just via the maxSessions LRU backstop. The
|
|
284
|
-
// worktree dir IS the LSP cache root (env.cwd), so evict(env.cwd) on destroy hits it. (`lspManager` is declared
|
|
285
|
-
// below but this closure only runs per-task, well after it's initialized — same forward-ref as the host factory.)
|
|
286
166
|
return lspManager ? evictLspOnDestroy(env, lspManager) : env;
|
|
287
167
|
},
|
|
288
168
|
logger,
|
|
289
169
|
});
|
|
290
|
-
// 复审 2026-07-30 F3:in-flight 单飞语义内建在铸造处——本函数有**两个**调用点(下方 boot recovery
|
|
291
|
-
// sweep 与 reapers.ts 的周期 tick),守卫只装 tick 侧会漏 boot 长跑(>REAP_INTERVAL_SEC 的
|
|
292
|
-
// `git worktree prune` 与首个 tick 叠跑,正是守卫要防的并发)。装在源头,调用点全免疫。
|
|
293
170
|
{
|
|
294
171
|
let inFlight = false;
|
|
295
172
|
worktreeReap = () => {
|
|
@@ -300,28 +177,15 @@ export function createExecutionEnv(ctx) {
|
|
|
300
177
|
};
|
|
301
178
|
}
|
|
302
179
|
logger.info("worktree_isolation_enabled", { provider: "host", repoRoot: config.worktreeIsolation.repoRoot });
|
|
303
|
-
// BOOT recovery sweep: reap any worktrees orphaned by a crash BEFORE this process started (the periodic
|
|
304
|
-
// reaper would otherwise wait a full interval). Best-effort, fire-and-forget; never blocks boot.
|
|
305
180
|
void worktreeReap().catch(() => undefined);
|
|
306
181
|
}
|
|
307
182
|
else {
|
|
308
|
-
// Not a no-op-silent: an operator who set WORKTREE_ISOLATION_ENABLED on a non-host lane should know it
|
|
309
|
-
// doesn't take effect yet (the worktree-rooted REMOTE env is a follow-on). Fail-loud-ish via a warning.
|
|
310
183
|
logger.warn("worktree_isolation_unsupported_lane", {
|
|
311
184
|
provider: config.remoteExec?.provider,
|
|
312
185
|
note: "git-worktree isolation v1 covers only the 'host' lane — isolation:'worktree' is a no-op here (the base env is used). Worktree-rooted remote envs are a follow-on.",
|
|
313
186
|
});
|
|
314
187
|
}
|
|
315
188
|
}
|
|
316
|
-
// SendUserFile v2(clay 拍 2026-07-14,Plan B 直传):沙箱 lane 的文件源需要「工具执行时拿到当前
|
|
317
|
-
// 任务的 env」——core 把 per-task env 关在 prepare-task 闭包里(ToolExecuteContext 无 env 面),但
|
|
318
|
-
// 工厂是我们装配的、工厂 ctx 自带 taskId,包一层登记 taskId→env(destroy 时注销)即可,零 core 改动
|
|
319
|
-
// (withWorktreeIsolation 同款包装先例)。lane 门+租户门=sandboxSendLaneEnabled(判定依据在其 doc):
|
|
320
|
-
// e2b/k8s=一任务一沙箱(沙箱文件系统=租户边界)任意租户可用;ssh(后补 2026-07-14)=同一 exec 契约
|
|
321
|
-
// (timeout 秒×1000/Result shape 同形)直传链零适配,但 peer=一台跨任务共享的常驻真实主机
|
|
322
|
-
// (sshExecutionEnvFactory 忽略 ctx、destroy=断连不删文件、isolation:false)→「沙箱 scoped 读=租户
|
|
323
|
-
// 隔离」不成立,与 host 同门单用户 only(requirePrincipal!==true 恒关多租户,不留口子)。
|
|
324
|
-
// 其余 lane 不包(零开销);签发面未配同样不包。
|
|
325
189
|
const sendUserFileTaskEnvs = (() => {
|
|
326
190
|
const provider = config.remoteExec?.provider;
|
|
327
191
|
if (!config.sendUserFile || !executionEnvFactory || !sandboxSendLaneEnabled(provider, config.requirePrincipal))
|
|
@@ -330,11 +194,6 @@ export function createExecutionEnv(ctx) {
|
|
|
330
194
|
executionEnvFactory = registry.wrapFactory(executionEnvFactory);
|
|
331
195
|
return registry;
|
|
332
196
|
})();
|
|
333
|
-
// D-1 附件物化包装(半场③):env 每次建立(fresh + resume/重建沙箱)都把该 session 绑定的附件写进
|
|
334
|
-
// 工作目录 `attachments/`。全 lane 统一(host/e2b/k8s/ssh/adb 都走 factory;ExecutionEnv.writeFile
|
|
335
|
-
// 双向通用);路径与 objective 告知共用 materializedRelPaths(确定性,两处不漂)。**fail-loud**:
|
|
336
|
-
// objective 已向模型宣告文件在场,静默缺文件=模型按幻影文件行动,比任务失败更糟 ⇒ 写失败即抛
|
|
337
|
-
// (env 建立失败,任务带明确错误)。链尾最外层:worktree 隔离等内层先定 cwd,附件落在最终 cwd。
|
|
338
197
|
if (taskAttachmentStore && executionEnvFactory) {
|
|
339
198
|
const inner = executionEnvFactory;
|
|
340
199
|
const attStore = taskAttachmentStore;
|
|
@@ -346,54 +205,29 @@ export function createExecutionEnv(ctx) {
|
|
|
346
205
|
return env;
|
|
347
206
|
};
|
|
348
207
|
}
|
|
349
|
-
// #165 沙箱写门的真 env slot(**最外层**,见文件头的装饰顺序):写门在 spec 期只能拿到一个代理,
|
|
350
|
-
// 裁决时刻由它去 slot 里取本 task 的真 env 转发 fs 读原语。所以登记的必须是 core 最终拿到手的那一个
|
|
351
|
-
// ——内层装饰器会换实例(worktree 的 rootEnvAt)或改写方法(scratchpad 的 exec/canonicalPath 前置)。
|
|
352
|
-
// 沙箱 lane 限定:host-semantics lane 的写门直接拿本机 NodeExecutionEnv,不经代理,包了也无人读。
|
|
353
208
|
if (executionEnvFactory && isSandboxPathAdjudicationLane(config.remoteExec?.provider)) {
|
|
354
209
|
executionEnvFactory = withSandboxPathEnvSlot(executionEnvFactory, sandboxPathEnvSlots, logger);
|
|
355
210
|
}
|
|
356
211
|
if (executionEnvFactory) {
|
|
357
|
-
// k8s (Kata) is isolated; it becomes WORKSPACE-suspendable when an S3 snapshot store is configured
|
|
358
|
-
// (tar→S3→fresh pod restore — files durable, in-VM memory not); e2b is fully suspendable.
|
|
359
212
|
const isolated = config.remoteExec.provider === "e2b" ||
|
|
360
213
|
config.remoteExec.provider === "k8s" ||
|
|
361
|
-
config.remoteExec.provider === "local-docker";
|
|
214
|
+
config.remoteExec.provider === "local-docker";
|
|
362
215
|
const suspendable = config.remoteExec.provider === "e2b" ||
|
|
363
216
|
(config.remoteExec.provider === "k8s" && !!config.remoteExec.s3Snapshot);
|
|
364
|
-
// Out-of-band sandbox env (e2b): log the KEY NAMES only (never the secret values) so ops can confirm
|
|
365
|
-
// injection is active without leaking the credential.
|
|
366
217
|
const sandboxEnvKeys = config.remoteExec.provider === "e2b" ? Object.keys(config.remoteExec.sandboxEnv ?? {}) : [];
|
|
367
218
|
logger.info("remote_exec_enabled", { provider: config.remoteExec.provider, isolated, suspendable, ...(sandboxEnvKeys.length > 0 ? { sandboxEnvKeys } : {}) });
|
|
368
219
|
if (!isolated)
|
|
369
220
|
logger.warn("remote_exec_real_system", { provider: config.remoteExec.provider, note: "non-isolated target — actions are permanent; rely on the policy gate + HITL (design/61 §5)" });
|
|
370
221
|
}
|
|
371
|
-
// LSP sidecar (design/64 §13.1, the 1.86.2 seam): ONE stateless manager on RunnerDeps — core passes each
|
|
372
|
-
// task's env into sessionFor at the tool mount point, so the manager reaches the SAME sandbox the agent edits
|
|
373
|
-
// (no sessionId registry / per-task construction). Opt-in (LSP_ENABLED — the SANDBOX lane's knob; the host lane
|
|
374
|
-
// below has its own, LSP_HOST_ENABLED, because its default is the opposite); with the baked `ai-agent-code-lsp`
|
|
375
|
-
// template the first call is fast, without it the language server installs on first use (slow once per sandbox).
|
|
376
|
-
// k8s lane (gate#2 alignment): same manager/bridge over the pod network — `ws://podIP:port` instead of E2B's
|
|
377
|
-
// public wss proxy, so it needs an IN-CLUSTER worker and a sandbox image with node + the language servers baked
|
|
378
|
-
// (no on-the-fly npm path on Kata; a miss degrades the lsp tool gracefully, same as E2B).
|
|
379
222
|
const lspProvider = config.remoteExec?.provider;
|
|
380
223
|
const lspManager = (lspProvider === "e2b" || lspProvider === "k8s") && config.lspEnabled
|
|
381
224
|
? createE2bLspManager({
|
|
382
225
|
log: (event, fields) => logger.info(event, fields),
|
|
383
226
|
scheme: lspProvider === "k8s" ? "ws" : "wss",
|
|
384
|
-
// §DESIGN-V2 F11: pass the lane explicitly — the bridge metrics' `provider` label names the
|
|
385
|
-
// DEPLOYMENT lane, not the WS scheme (the two happen to correlate today but are different axes).
|
|
386
227
|
provider: lspProvider,
|
|
387
228
|
metrics,
|
|
388
229
|
})
|
|
389
|
-
:
|
|
390
|
-
// language server as a LOCAL child_process over stdio (CC `services/lsp` parity). Its default resolveRoot uses
|
|
391
|
-
// `env.cwd` — and core passes each task's executionEnv (the per-agent WORKTREE env, so the server roots in the
|
|
392
|
-
// worktree not the base repo) into sessionFor. host ONLY: ssh/adb target another host/device and local-docker a
|
|
393
|
-
// container, none of which a local child_process can reach (those keep the E2B-style bridge / stay degraded).
|
|
394
|
-
// 🔒 `requirePrincipal !== true` gate = uniform with the sibling host powers (scheduler/backgroundShell/
|
|
395
|
-
// loadProjectMemory): the host lane is single-user by design; a (discouraged) multi-tenant host fails SAFE to
|
|
396
|
-
// grep/read rather than fanning a per-tenant×language×root pool of heavy language servers (double-review B).
|
|
230
|
+
:
|
|
397
231
|
lspProvider === "host" && config.lspHostEnabled && config.requirePrincipal !== true
|
|
398
232
|
? new NodeLspManager({ log: (event, fields) => logger.info(event, fields) })
|
|
399
233
|
: undefined;
|
|
@@ -1,59 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/170 件B/C/D(#252 件1)—— 三个**部署治理座席**的装配点:把 core 5.13.0 起就存在、本仓一直
|
|
3
|
-
* 零接线的三个 `RunnerDeps` seam 接上。
|
|
4
|
-
*
|
|
5
|
-
* · 件B `compliancePostureResolver` —— per-principal 合规档位否决(闭集 profile × 闭集 capability;
|
|
6
|
-
* 生效 deny 集 = `BUILTIN_COMPLIANCE_DENIES[profile] ∪ additionalDenies`,**供数只能收紧**)。
|
|
7
|
-
* · 件C `lockedConfig` —— 管理员锁定层的**声明**(闭集注册表;spec 占了锁着的字段 ⇒ core 整拒 prepare)。
|
|
8
|
-
* · 件D `retentionPolicy` —— 托管留存期。core **只**拿它做启动期能力校验,引擎在任务路径上从不删数据。
|
|
9
|
-
*
|
|
10
|
-
* ## 真源:为什么三件都只接 env
|
|
11
|
-
*
|
|
12
|
-
* design/170 §1 的三层分工表里,center 那一列对 B/C/D 写的都是「**新建**」——中心侧的档位真源 / 策略
|
|
13
|
-
* 真源 / 保留期真源**至今不存在**。接一条 center 腿等于造一个恒缺席的假面(件A 的 org 目录能接 center,
|
|
14
|
-
* 是因为那一面真被建出来了)。所以本装配点的供数只有部署配置面,并且这**恰好**是 design/170 §4.3 对
|
|
15
|
-
* 「谁能解锁」的裁定:锁是**配置期物**,铸锁真源只有 center governance 域与部署 env,二者都是 operator
|
|
16
|
-
* 控制的配置面,自带各自既有鉴权 —— 「谁能解锁」≡「谁能改部署配置」,不引入新鉴权面。
|
|
17
|
-
*
|
|
18
|
-
* ## 件B 的窄化:档位是**部署级**,不是 per-principal
|
|
19
|
-
*
|
|
20
|
-
* core 的座席签名按 principal 取值,本仓的 resolver 对每个 principal 返回同一份部署档位。这是诚实的窄
|
|
21
|
-
* 实现:per-principal 档位需要 center 的下发面(§3.2 R5「与 caps 同一次 fetch」),那一面尚未建。
|
|
22
|
-
* 座席在场即恒生效,不存在「某些 principal 悄悄没档位」的分岔。
|
|
23
|
-
*
|
|
24
|
-
* ## 件B 的**不采纳**:多租户缺解析器不拒启(显式定界)
|
|
25
|
-
*
|
|
26
|
-
* §3.4 建议「部署自称多租户却未配 B 的解析器 ⇒ 拒启动」。**本仓不采纳**,理由是三问里的第二问:
|
|
27
|
-
* 谁被伤到 —— 今天每一个 `REQUIRE_PRINCIPAL=true` 的部署都没有这一格,采纳即是让它们**全部**在下一次
|
|
28
|
-
* 升级时起不来,而它们并没有任何东西变得更不安全(B 缺席 = 无合规限制 = 与今天逐字相同的行为)。
|
|
29
|
-
* 补偿是这条 env 旋钮本身:要档位的部署显式写下它。将来 center 真有了档位下发面,「多租户必须能解析出
|
|
30
|
-
* 档位」才是一条有对象可指的判据,那时再立。
|
|
31
|
-
*
|
|
32
|
-
* ## 件C 的两条**装配相容性**判据(本仓特有,见 {@link assertLockAssemblable})
|
|
33
|
-
*
|
|
34
|
-
* core 的锁按「spec 是否占了这个字段」判,而本仓是**部署自己**在铸那两个 spec 字段:
|
|
35
|
-
* · `spec.toolPolicy` —— 治理拍(`applyRuntimeGovernance`)在**任何**客户端表态下都会铸出它(敏感路径
|
|
36
|
-
* 守卫集默认非空);于是 `toolPolicy` 锁会让 core 的 preflight **整拒每一条腿**。
|
|
37
|
-
* · `spec.mcp` —— 部署自带的 MCP 基线(场景/center 下发)同样经 `spec.mcp` 进 core;部署配了 MCP 又锁
|
|
38
|
-
* `mcp`,同样是每条腿必拒。
|
|
39
|
-
* 两者都在**启动期**拒(安全控件不得半开 §3.4 C 行),而不是让运维在每条任务腿上各撞一次
|
|
40
|
-
* `config.locked_key` 去猜。
|
|
41
|
-
*
|
|
42
|
-
* ## 件D 的边界(成文,别读成「数据会被删」)
|
|
43
|
-
*
|
|
44
|
-
* 本仓**没有任何** store 声明 `retention:"managed"`(亲验:session/checkpoint/tool-result 三族 SQL 双生
|
|
45
|
-
* 与 local 形都没有这一位),托管留存的执行面(调度/重试/副本协调/逐行审计)按 §5.2 是「server 半场四件
|
|
46
|
-
* 皆新建」,不在本批。所以本装配点对件D 只做两件事:①把策略递给 core 的启动期校验
|
|
47
|
-
* (`assertRetentionCapability` —— 锁着 + 店不能删 ⇒ 拒启,「策略锁着、数据永存」不许发生);②策略在场
|
|
48
|
-
* 而没有任何 managed 店时打一条响亮 warn。**不**假装有执行面。
|
|
49
|
-
*/
|
|
50
1
|
import { assertRetentionCapability, resolveComplianceDenies, resolveLockedKeys } from "@sema-agent/core";
|
|
51
|
-
/**
|
|
52
|
-
* 件C 的装配相容性判据(纯,可单测)——见文件头「两条判据」。
|
|
53
|
-
*
|
|
54
|
-
* 🔴 失败方向朝**拒启**:一把在本部署上必然把每条腿都拒掉的锁,不是「配得比较严」,是配错了;继续启动
|
|
55
|
-
* 只会让服务对每一个请求说 `config.locked_key`,而运维手里没有任何线索指向那把锁。
|
|
56
|
-
*/
|
|
57
2
|
export function assertLockAssemblable(input) {
|
|
58
3
|
if (input.lockedKeys.has("toolPolicy")) {
|
|
59
4
|
throw new Error('LOCKED_CONFIG_KEYS contains "toolPolicy", which this server cannot arm: it COMPOSES the effective tool policy itself on ' +
|
|
@@ -69,20 +14,6 @@ export function assertLockAssemblable(input) {
|
|
|
69
14
|
"MCP servers — a locked deployment mounts no MCP at all.");
|
|
70
15
|
}
|
|
71
16
|
}
|
|
72
|
-
/**
|
|
73
|
-
* 治理面的**装配相容性**总判据(codex 对抗复审 R2-F2 起扩面)——锁与合规档位在这条轴上是同一件事:
|
|
74
|
-
* 两者都可能把「本部署自己的装配」判成 core 门口的违规,而那不是「配得更严」,是**这台从此每条腿必失败**。
|
|
75
|
-
*
|
|
76
|
-
* 合规半场的判据来自亲核 core 的 prepare 门(`prepare-task` 的 compliance 段):被否决的能力若**已在
|
|
77
|
-
* spec 上**,core 是**整拒 prepare**(`config.compliance_denied`),不是「不挂载」。于是:
|
|
78
|
-
* · `mcp_servers` 被禁 ∧ 部署自带 MCP 基线 ⇒ 每条腿的 `spec.mcp` 都在 ⇒ 必拒;
|
|
79
|
-
* · `web_fetch` 被禁 ∧ 花名册挂了 WebFetch ⇒ 必拒。⚠️ `hipaa` 的**内建 floor 就含 `web_fetch`**,而本仓
|
|
80
|
-
* 的单用户花名册**恒挂** WebFetch(`capabilities/scenarios.ts` 的 roster 过滤只在多租户下摘它)——
|
|
81
|
-
* 也就是说「单用户 + hipaa」在本仓是一个每条腿必失败的组合。这条判据的全部价值就在这儿:让它在
|
|
82
|
-
* 启动期说出来,而不是让运维看着一台"启动成功、任务全失败"的 worker 猜。
|
|
83
|
-
* · `workflows` 被禁只在 `spec.selfOrchestration === true` 的腿上拒(per-task,不是部署恒真)⇒ 不在本
|
|
84
|
-
* 判据内,由那条腿自己响亮拒;`org_memory_mount` 走件A 的准入面。**成文定界,不留白。**
|
|
85
|
-
*/
|
|
86
17
|
export function assertGovernanceAssemblable(input) {
|
|
87
18
|
assertLockAssemblable({ lockedKeys: input.lockedKeys, deploymentMcpConfigured: input.deploymentMcpConfigured });
|
|
88
19
|
if (input.complianceDenies.has("mcp_servers") && input.deploymentMcpConfigured) {
|
|
@@ -97,25 +28,11 @@ export function assertGovernanceAssemblable(input) {
|
|
|
97
28
|
"currently honor a web_fetch denial. Run the posture multi-tenant, or drop the profile/deny.");
|
|
98
29
|
}
|
|
99
30
|
}
|
|
100
|
-
/**
|
|
101
|
-
* 装配三座席。**拒启口即装配口**:相容性判据与 core 的留存能力校验都在这里跑,调用方不必记得再调一次
|
|
102
|
-
* (件A 的 C12 探测同姿势)。
|
|
103
|
-
*/
|
|
104
31
|
export function createGovernanceSeams(ctx) {
|
|
105
32
|
const { config, logger, retentionStores } = ctx;
|
|
106
|
-
// 件C:锁集。config 层已用同一只 `resolveLockedKeys` 验过一遍;这里再验是因为 config 也可能由
|
|
107
|
-
// sema-registry 侧写入(env 不是唯一写点),而「安全控件不得半开」这条对每个写点都成立。
|
|
108
33
|
const lockedKeys = resolveLockedKeys(config.lockedConfigKeys !== undefined ? { keys: config.lockedConfigKeys } : undefined);
|
|
109
|
-
// 件B:合规档位。`resolveComplianceDenies` 在装配期跑一次 —— 它是 core 的 fail-loud 解析口,同时给出
|
|
110
|
-
// 这台部署真正在禁哪些能力(启动行的内容 + 下面那道相容性判据的输入)。resolver 本身返回**冻结的部署
|
|
111
|
-
// 档位**,不做任何 per-call 工作。
|
|
112
34
|
const posture = config.compliancePosture;
|
|
113
35
|
const complianceDenies = posture !== undefined ? resolveComplianceDenies(posture) : new Set();
|
|
114
|
-
// `mcpServers` 读的是**已应用 center 配置之后**的值(本装配点排在 config-center 段之后)。中心侧后来
|
|
115
|
-
// 加了 MCP 服务器怎么办:那一族是 baked-at-boot 面,变更会翻 `restartRequired` 让编排器滚动重启 ——
|
|
116
|
-
// 重启就撞上这道门。所以这里不需要第二条热路径判据。
|
|
117
|
-
// `webFetchMounted` 的判据与花名册那处的过滤**同源**(`capabilities/scenarios.ts`:
|
|
118
|
-
// `.filter((t) => t.name !== "WebFetch" || deps.requirePrincipal !== true)`)—— 多租户摘、单用户挂。
|
|
119
36
|
assertGovernanceAssemblable({
|
|
120
37
|
lockedKeys,
|
|
121
38
|
complianceDenies,
|
|
@@ -130,13 +47,10 @@ export function createGovernanceSeams(ctx) {
|
|
|
130
47
|
logger.info("compliance_posture_enabled", { profile: posture.profile, denies: [...complianceDenies].sort() });
|
|
131
48
|
compliancePostureResolver = () => posture;
|
|
132
49
|
}
|
|
133
|
-
// 件D:留存期。先跑 core 的启动期校验(策略值本身 + 锁着时的店能力),再补一条本仓特有的诚实 warn。
|
|
134
50
|
const retentionPolicy = config.retentionPolicy;
|
|
135
51
|
const locked = lockedKeys.has("retentionPolicy");
|
|
136
52
|
assertRetentionCapability({ policy: retentionPolicy, locked, stores: retentionStores });
|
|
137
53
|
if (retentionPolicy !== undefined) {
|
|
138
|
-
// 声明位的读法与 core 的校验口**同一个结构型**(`RetentionDeclaring`)——不自造一个平行的形状,
|
|
139
|
-
// 否则「core 认为这店 managed / 我方认为不是」这种分歧会静默存在。
|
|
140
54
|
const managed = retentionStores.filter((s) => s.store?.retention === "managed");
|
|
141
55
|
const executorWired = ctx.retentionExecutorWired === true;
|
|
142
56
|
if (managed.length === 0) {
|
|
@@ -148,12 +62,6 @@ export function createGovernanceSeams(ctx) {
|
|
|
148
62
|
});
|
|
149
63
|
}
|
|
150
64
|
else if (!executorWired) {
|
|
151
|
-
// 🔴 #270 车1(codex R2-[critical] 验真后修):**店说 managed ≠ 有人在删**。
|
|
152
|
-
// · locked ⇒ **拒启**:core 的门因为声明位而放行了,可这台机上没有执行器 —— 那正是它文案里
|
|
153
|
-
// 「policy locked, data immortal」的实况,只是伪装成了合规。拒启保住的是车1 之前的**同一个**
|
|
154
|
-
// 行为(那时店声明 none ⇒ core 直接拒),不是新增的严格。
|
|
155
|
-
// · 未锁 ⇒ **响亮 warn**(不拒启):未锁的策略本就是一句声明,但它此前那条 warn 会被「有 managed
|
|
156
|
-
// 店在场」静音,于是最需要说话的那一刻反而没声音。
|
|
157
65
|
const detail = `retention policy is configured (maxAgeDays=${retentionPolicy.maxAgeDays}) and ${managed.length} wired store(s) declare ` +
|
|
158
66
|
`retention:"managed", but THIS BUILD WIRES NO RETENTION EXECUTOR (the sweep lane) — nothing enumerates domains or calls ` +
|
|
159
67
|
`the destructive store methods, so no data is deleted on the policy's account. A "managed" declaration is a statement ` +
|
package/dist/boot/leader.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { createLeaderEndpoint } from "../leader/endpoint.js";
|
|
2
2
|
import { createLeaderRunner } from "../leader/wire.js";
|
|
3
|
-
/**
|
|
4
|
-
* 鲁棒性批5 A6(2026-08-05):k8s 腿要求 MinIO 三件套(ENDPOINT/ACCESS_KEY/SECRET_KEY)全在——半开(一件缺)
|
|
5
|
-
* 就让 `leaderEndpoint` 的三元式落到 undefined,此前**零披露**:运维显式开了 LEADER_ENABLED +
|
|
6
|
-
* REMOTE_EXEC=k8s,却在日志里既看不到 `leader_endpoint_enabled` 也看不到任何"为什么没启用"的信号——只能
|
|
7
|
-
* 靠读源码才知道要去检查 MinIO 三件套。
|
|
8
|
-
*
|
|
9
|
-
* 纯谓词,抽出独立函数:只吃 leaderEnabled/leaderProvider/hasS3 三个原语 + 直接读 process.env 取缺失的
|
|
10
|
-
* 具体变量名,不依赖完整 `LeaderCtx`(构造一整套 brain/pricing/sessionStore 只为测一个 warn 分支不值当)。
|
|
11
|
-
* 返回 null = 不适用该警告(未开启/非 k8s 腿/三件套齐全);非 null = 该报警,携带具体缺的变量名列表。
|
|
12
|
-
*/
|
|
13
3
|
export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
|
|
14
4
|
if (!(leaderEnabled && leaderProvider === "k8s" && !hasS3))
|
|
15
5
|
return null;
|
|
@@ -19,19 +9,6 @@ export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
|
|
|
19
9
|
!process.env.MINIO_SECRET_KEY && "MINIO_SECRET_KEY",
|
|
20
10
|
].filter((v) => typeof v === "string");
|
|
21
11
|
}
|
|
22
|
-
/**
|
|
23
|
-
* #193 车4 件2 的**能力面**门(codex 对抗复审 R1-high①,验真后修)。
|
|
24
|
-
*
|
|
25
|
-
* config 层的门②判的是 `DB_BACKEND` 的字面值 —— 那句话回答的是「运维要没要 durable」。它回答不了
|
|
26
|
-
* 「这次启动**真的拿到**了 durable 吗」:`openStoreBackendWithFallback` 在 `SESSION_BACKEND=auto`
|
|
27
|
-
* (默认)下,SQL 连不上 / 建表失败 ⇒ 返回 `backend=undefined` + 一条 warn 就继续启动。那次启动里
|
|
28
|
-
* `leaderRun()` 缺席,leader 端点会静默落进单副本内存臂:多副本各自受理同一个 Idempotency-Key、
|
|
29
|
-
* 跨副本 GET 404、重启失忆 —— 而这条腿的产物是**真 git push**。
|
|
30
|
-
*
|
|
31
|
-
* ⇒ 判据锚**真拿到了没有**。纯谓词、只吃两个原语(与 {@link leaderK8sMinioGap} 同姿势),便于单测。
|
|
32
|
-
* 抛而不是 warn:这正是 #157「安全/执行车道禁静默 fail-open」说的那一类 —— 降级的代价由用户承担,
|
|
33
|
-
* 而承担的方式是重复的真实推送。
|
|
34
|
-
*/
|
|
35
12
|
export function assertLeaderDurableStore(leaderEnabled, hasLeaderRunStore) {
|
|
36
13
|
if (!leaderEnabled || hasLeaderRunStore)
|
|
37
14
|
return;
|
|
@@ -44,11 +21,6 @@ export function assertLeaderDurableStore(leaderEnabled, hasLeaderRunStore) {
|
|
|
44
21
|
}
|
|
45
22
|
export function createLeaderFace(ctx) {
|
|
46
23
|
const { config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams } = ctx;
|
|
47
|
-
// v2 leader endpoint (design/50 + design/68): wire when LEADER_ENABLED + an isolated remote-exec backend
|
|
48
|
-
// (E2B or k8s/Kata — SSH/ADB are single-worker real-system backends, not fan-out targets). Default off →
|
|
49
|
-
// zero prod impact. 🔴 a real run also needs `git` on the host PATH + a durable remote with write creds.
|
|
50
|
-
// F2(a) factory mode (preferred) = the service's executionEnvFactory + MinIO self-upload diff-out; the k8s
|
|
51
|
-
// lane REQUIRES it (no static k8s mode). E2B without MinIO falls back to the static compat mode.
|
|
52
24
|
const leaderMinio = process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
53
25
|
? {
|
|
54
26
|
s3: {
|
|
@@ -61,73 +33,38 @@ export function createLeaderFace(ctx) {
|
|
|
61
33
|
}
|
|
62
34
|
: {};
|
|
63
35
|
const leaderProvider = config.remoteExec?.provider;
|
|
64
|
-
// #193 车4 件1:durable leader-run 登记表(SQL 后端专有;`backend` 本身可缺席=无 DB 的 env-only worker)。
|
|
65
|
-
// 件2 能力面门(codex R1-high①)紧随其后:开了 leader 却没真拿到店 ⇒ 拒启,绝不静默落内存臂。
|
|
66
36
|
const leaderRunStore = ctx.backend?.leaderRun?.();
|
|
67
37
|
assertLeaderDurableStore(config.leaderEnabled, leaderRunStore !== undefined);
|
|
68
38
|
const minioGap = leaderK8sMinioGap(config.leaderEnabled, leaderProvider, "s3" in leaderMinio);
|
|
69
39
|
if (minioGap)
|
|
70
40
|
logger.warn("leader_k8s_minio_incomplete", { missing: minioGap });
|
|
71
|
-
// DUAL-MODE §5: orchestration is an ENGINE capability, not fleet-only — the TOC `host` lane runs leader fan-out
|
|
72
|
-
// bounded by ONE box (`remote-env-host.ts` declares `capabilities = { isolation:false, suspendable:false }`).
|
|
73
|
-
// e2b/k8s keep their isolated/suspendable posture.
|
|
74
|
-
//
|
|
75
|
-
// 🔴 #193 车4 件4(台账 P2-10)核实更正 —— 这里原先断言 host 腿「既不 durable 也没有快照,所以下面那个
|
|
76
|
-
// durable 子 worker 挂起块会被跳过」。**没有任何判别式这样做**:下面的 `durable` 块只 gate 在
|
|
77
|
-
// `checkpointStore && approvalRequire.length > 0` 上,与 provider 无关,host 腿照样拿到它。core 侧亲读
|
|
78
|
-
// (5.35.0)也不是「跳过」而是**park-only 降级**:一个 `suspendable:false` 的 remote env 仍然 durable
|
|
79
|
-
// suspend,被跳过的只有 `suspendVM` 那一步 —— 见 `@sema-agent/core` `dist/core/remote-env.d.ts` 的
|
|
80
|
-
// `WorkspaceHandle.restoreMode:"park_only"`(「non-suspendable env;workspace persists on the target,
|
|
81
|
-
// resume skips resumeVM」)与 `dist/core/types.d.ts` 的 `TaskResult.workspaceRestoreMode:"snapshot"|"park_only"`
|
|
82
|
-
// (「the env declared itself non-suspendable …, so NOTHING was paused: the machine keeps running (and keeps
|
|
83
|
-
// costing) … resume simply reconnects to the still-present workspace」)。
|
|
84
|
-
// 运维面的实义差别(也是原注为什么危险):park-only 的 host worker 在挂起期间**继续占着这台机器、继续
|
|
85
|
-
// 计费**,而 `suspendable:false` 同时**断言工作区在目标机上外部持久**(core 那条 LOAD-BEARING INVARIANT)
|
|
86
|
-
// —— 所以谁清理这台机器上的残留由部署方负责,不能指望「反正被跳过了」。
|
|
87
41
|
const leaderEndpoint = config.leaderEnabled &&
|
|
88
42
|
(leaderProvider === "e2b" ||
|
|
89
43
|
(leaderProvider === "k8s" && "s3" in leaderMinio && executionEnvFactory) ||
|
|
90
44
|
(leaderProvider === "host" && executionEnvFactory))
|
|
91
45
|
? createLeaderEndpoint(createLeaderRunner({
|
|
92
46
|
brain, models: config.models, roles: config.roles, pricing, logger,
|
|
93
|
-
// design/170 件B/C/D:主车道装配出的**同一份**治理座席(单一属主,leader 不另判)。
|
|
94
47
|
governance: {
|
|
95
48
|
compliancePostureResolver: governanceSeams.compliancePostureResolver,
|
|
96
49
|
lockedConfig: governanceSeams.lockedConfig,
|
|
97
50
|
retentionPolicy: governanceSeams.retentionPolicy,
|
|
98
51
|
},
|
|
99
52
|
fanoutEnabled: config.leaderFanoutEnabled,
|
|
100
|
-
// Route the single-vs-fanout classification on the cheap model (deepseek-v4-flash): the heavy
|
|
101
|
-
// reasoning worker model returns empty ~2/3 of the time on the route prompt → silent collapse to
|
|
102
|
-
// single (observed 2026-06-14). MODEL_ROUTER_ID overrides; else the cheap model; else default.
|
|
103
53
|
...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
|
|
104
54
|
? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
|
|
105
55
|
: {}),
|
|
106
56
|
...(leaderProvider === "e2b" ? { e2bApiKey: config.remoteExec.apiKey } : {}),
|
|
107
57
|
...(executionEnvFactory ? { envFactory: executionEnvFactory } : {}),
|
|
108
58
|
...leaderMinio,
|
|
109
|
-
// Durable offload store for sub-worker tool results — without it core REFUSES the durable
|
|
110
|
-
// suspend (InMemory offload would not survive a cross-replica resume) and the gated call is
|
|
111
|
-
// denied via the unwired onAsk fallback → the worker fails instead of suspending (drill c3).
|
|
112
59
|
...(toolResultStore ? { toolResultStore } : {}),
|
|
113
|
-
// k8s lane: align the wire's workspace with the adapter's durable-snapshot root (/workspace) —
|
|
114
|
-
// the E2B default (/home/user) is OUTSIDE the k8s suspend tar, so a suspended sub-worker's repo
|
|
115
|
-
// would silently vanish on resume (drill c3: 105-byte empty snapshots).
|
|
116
60
|
...(leaderProvider === "k8s" ? { workspace: "/workspace" } : {}),
|
|
117
|
-
// Same store as the main runner: a suspended sub-worker's session must be readable on /decide
|
|
118
|
-
// resume (sub-runner-local in-memory sessions die with "Entry <leafId> not found" on resume).
|
|
119
61
|
sessionStore,
|
|
120
|
-
// design/68 C4: sub-workers can durably suspend when the service runs durable approvals.
|
|
121
62
|
...(checkpointStore && config.approvalRequire.length > 0
|
|
122
63
|
? {
|
|
123
64
|
durable: {
|
|
124
65
|
checkpointStore,
|
|
125
66
|
requireApproval: config.approvalRequire,
|
|
126
67
|
deny: config.approvalDeny,
|
|
127
|
-
// design/80: the leader's orchestrated sub-agents adopt the SAME supervisor loop as the
|
|
128
|
-
// top-level agent — the D-E auto-budget (auto-approve NORMAL asks up to the budget, then
|
|
129
|
-
// escalate) + the never-auto safety set. Without these a sub-agent's gated ask always
|
|
130
|
-
// suspends-to-human, defeating the budget circuit-breaker for the supervised team.
|
|
131
68
|
autoBudget: config.approvalAutoBudget,
|
|
132
69
|
neverAuto: config.approvalNeverAuto,
|
|
133
70
|
...(config.approvalTimeoutSec > 0 ? { ttlMs: config.approvalTimeoutSec * 1000 } : {}),
|
|
@@ -136,13 +73,7 @@ export function createLeaderFace(ctx) {
|
|
|
136
73
|
: {}),
|
|
137
74
|
}), {
|
|
138
75
|
logger,
|
|
139
|
-
// SDK 全量核查(2026-07-24,黑板 GD 组)撞获:wire.ts:368 的 runLeader 实际要求这五个字段全部
|
|
140
|
-
// 非空,此前同步门只查 objective,不完整请求会先拿到 202 再在后台必然失败——同步前移,让错误
|
|
141
|
-
// 尽早暴露而不是靠后续 GET 才发现。
|
|
142
76
|
requiredFields: ["objective", "durableRemote", "testCmd", "seedCmd", "baseSha"],
|
|
143
|
-
// #193 车4 件1:durable 登记表(跨副本 GET + 提交幂等)。SQL 后端专有;缺席 ⇒ endpoint 落到
|
|
144
|
-
// 显式的单副本内存降级臂。生产上这条缺席路径够不着 —— config 层已把「LEADER_ENABLED=true 且
|
|
145
|
-
// 无外部 SQL 店」整个组合拒启(件2 门②),所以这里的可选链只服务单机试跑与测试装配。
|
|
146
77
|
...(leaderRunStore ? { store: leaderRunStore } : {}),
|
|
147
78
|
})
|
|
148
79
|
: undefined;
|
package/dist/boot/limit-sync.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
/** USD/秒 → micro/ms 的**唯一**换算点(装配期构造与热同步共用;两处各算一次迟早会差一个 1e6)。 */
|
|
2
1
|
export function buildQuotaBounds(config) {
|
|
3
2
|
return { limitMicroUsd: Math.round(config.maxPrincipalCostUsd * 1e6), windowMs: config.costQuotaWindowSec * 1000 };
|
|
4
3
|
}
|
|
5
|
-
/** `create*`:返回带行为的活对象(闭包持座 + 上一代读数)。 */
|
|
6
4
|
export function createLimitSync(ctx) {
|
|
7
5
|
const { config, rateGate, quota, logger } = ctx;
|
|
8
6
|
let lastRate;
|