@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/task-settings.js
CHANGED
|
@@ -1,71 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TaskRequest.settings wire — project a client's per-request `SemaSettings` stamp onto the per-task `TaskSpec`,
|
|
3
|
-
* TIGHTEN-ONLY (deny-wins), at the SAME seam as runtime-governance (after the approval baseline + operator
|
|
4
|
-
* governance). Backs the remote-mode settings path: sema runs over HTTP→engine with no local Runner, so the
|
|
5
|
-
* resolved settings can't be applied by a local `tightenTaskSpec` — they ride on `TaskRequest.settings` and the
|
|
6
|
-
* SERVICE projects them into the engine here.
|
|
7
|
-
*
|
|
8
|
-
* Design: `TaskRequest.settings` is a
|
|
9
|
-
* PER-REQUEST ephemeral stamp = the v2-design TASK layer (a single-run override, NOT durable). It folds into THIS
|
|
10
|
-
* spec via {@link tightenTaskSpec} and that is all — it is NEVER written to the durable `SessionPolicyStore` (the
|
|
11
|
-
* operator/principal axis, design/99 §E6, a different trust axis + lifecycle). Re-applying on a resume leg is
|
|
12
|
-
* idempotent (the same tighten), so no special resume handling is needed (mirrors runtime-governance).
|
|
13
|
-
*
|
|
14
|
-
* 🔒 SECURITY INVARIANTS (SDK settings.ts §3.3):
|
|
15
|
-
* - permissions are TIGHTEN-ONLY — folded via `tightenTaskSpec` (deny-wins `combinePolicies`); it THROWS on any
|
|
16
|
-
* override that would LOOSEN a stricter base safety field. The client can only ADD denies/asks/handsReadOnly on
|
|
17
|
-
* top of the deployment + operator baseline, never widen it.
|
|
18
|
-
* - permissionMode(轴B #2 注释保鲜,1.254——[816]/[820] 后的真实现):`defaultMode` 收**五**模式
|
|
19
|
-
* (default | acceptEdits | plan | bypassPermissions | auto),bypass/auto 被 HONOR 为 gate-shape 选择
|
|
20
|
-
* ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
|
|
21
|
-
* ask 照压);精化语义见 :2xx 行内注释。旧头注的「NEVER honored/三模式白名单」是 pre-[816] 拍照。
|
|
22
|
-
*
|
|
23
|
-
* 🔴 SYNC WITH sema-cli(壳仓)的 `src/sema/settings/seam.ts`: the permissions→toolPolicy + §6.4 permissionMode derive MIRRORS
|
|
24
|
-
* the shell's LOCAL-Runner resolver — both compose the SAME core primitives (`createAllowDenyPolicy` /
|
|
25
|
-
* `combinePolicies` / `tightenTaskSpec`), so the deny-wins ENFORCEMENT is core-single-source. Only the COMPOSITION
|
|
26
|
-
* is duplicated for the remote path; a proposed follow-up extracts it to `@sema-agent/sdk` for true
|
|
27
|
-
* single-source. Because the core backstops (deny-wins + tighten-throws-on-loosen + this bypass-reject) hold either
|
|
28
|
-
* way, a composition drift would be a local/remote CONSISTENCY gap, never a privilege escalation.
|
|
29
|
-
*
|
|
30
|
-
* v1 scope: `permissions` + `permissions.defaultMode` + `model` + `outputStyle` + `env` (shipped 1.26.0). DEFERRED
|
|
31
|
-
* (need a seam, flagged to the team + advertised in capabilities so a caller never silently loses a setting):
|
|
32
|
-
* - (`env` is NO LONGER deferred — systematic-audit doc fix: core kept no untrusted `TaskSpec.env` seam (types §7
|
|
33
|
-
* Q4), so the SERVICE applies it on the TRUSTED host lane: `settings.env` → `cleanEnvMap` → `shellEnv` on the
|
|
34
|
-
* executionEnvFactory, single-user-gated. See `deriveSettings` below + main.ts.)
|
|
35
|
-
* - (`hooks` NO LONGER deferred either — 轴B #2 注释保鲜:applied 形已接线(本文件 hooks 分片 +
|
|
36
|
-
* main.ts 单用户闸),仅 malformed 才报 deferred;旧「需要 remote hook-runner」是接线前拍照。)
|
|
37
|
-
*/
|
|
38
1
|
import { combinePolicies, createAllowDenyPolicy, createFsWriteGatePolicy, tightenTaskSpec, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, } from "@sema-agent/core";
|
|
39
2
|
import { findCcRuleFormNames, findUnmatchableToolNames, formatUnmatchableToolNames } from "./config.js";
|
|
40
3
|
import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
41
|
-
/**
|
|
42
|
-
* R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends the
|
|
43
|
-
* CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
|
|
44
|
-
* 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
|
|
45
|
-
* gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
|
|
46
|
-
* folds via tightenTaskSpec, deny-wins). Post-[816] the honored set is the five-word table above (NOT "CC verbatim" —
|
|
47
|
-
* see the {@link SettingsPermissionMode} header for the three-set contrast; the earlier "five CC modes" wording here
|
|
48
|
-
* was A-065 P2-1's finding: no CC set has five members). `acceptEdits`,
|
|
49
|
-
* `bypassPermissions` and `auto` are HONORED as gate-shape choices (they no longer coerce to `default`, which
|
|
50
|
-
* would FORCE the manual ask gate onto a caller that explicitly asked for less asking — with the gate live, the old
|
|
51
|
-
* coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
|
|
52
|
-
* UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
|
|
53
|
-
* #157-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-five-mode value
|
|
54
|
-
* (server.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
|
|
55
|
-
* population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
|
|
56
|
-
* two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
|
|
57
|
-
*/
|
|
58
4
|
export function coercePermissionMode(raw) {
|
|
59
5
|
if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
|
|
60
6
|
return raw;
|
|
61
7
|
if (typeof raw === "string" && raw.length > 0)
|
|
62
|
-
return "default";
|
|
63
|
-
return undefined;
|
|
8
|
+
return "default";
|
|
9
|
+
return undefined;
|
|
64
10
|
}
|
|
65
|
-
/** L2 ultracode (design/111): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
|
|
66
|
-
* preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
|
|
67
|
-
* (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
|
|
68
|
-
* future-proof). Awareness rides for free: core composes ORCHESTRATION_AWARENESS from thinking∈{xhigh,max}, no field. */
|
|
69
11
|
export function effectiveThinking(reasoningEffort, ultracode) {
|
|
70
12
|
const explicit = isThinkingLevel(reasoningEffort) ? reasoningEffort : undefined;
|
|
71
13
|
if (!ultracode)
|
|
@@ -73,56 +15,13 @@ export function effectiveThinking(reasoningEffort, ultracode) {
|
|
|
73
15
|
const ultra = resolveReasoningProfile("ultra").thinking;
|
|
74
16
|
return explicit && rankOf(explicit) > rankOf(ultra) ? explicit : ultra;
|
|
75
17
|
}
|
|
76
|
-
/** R4: fold a top-level `body.permissionMode` onto the parsed settings as `permissions.defaultMode` (the top-level
|
|
77
|
-
* field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
|
|
78
|
-
* no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
|
|
79
18
|
export function withPermissionMode(settings, mode) {
|
|
80
19
|
const base = settings ?? {};
|
|
81
20
|
return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
|
|
82
21
|
}
|
|
83
|
-
/**
|
|
84
|
-
* design/201 §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
|
|
85
|
-
* 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
|
|
86
|
-
* 都没有 ⇒ `undefined` = **无表态**(调用点据此不写键,而不是替调用方选一个默认值)。
|
|
87
|
-
*
|
|
88
|
-
* 🔴 为什么必须是一只函数:同一个「生效模式」此前在 resolve-spec 里被算了两次(hooks 腿的
|
|
89
|
-
* `permission_mode` 载荷、settings 折叠腿的 `withPermissionMode`),三审同点判定翻译表**不得**成为
|
|
90
|
-
* 第三个算点 —— 三处各算各的,任何一次优先序修订都会让三面分家,而分家在这条轴上的形态是
|
|
91
|
-
* 「壳发的 bundle bypass 在 A 面生效、在 B 面没生效」这种最难被外部发现的静默偏差。
|
|
92
|
-
*/
|
|
93
22
|
export function effectivePermissionMode(body, settings) {
|
|
94
23
|
return coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
|
|
95
24
|
}
|
|
96
|
-
/**
|
|
97
|
-
* design/201 §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
|
|
98
|
-
*
|
|
99
|
-
* · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
|
|
100
|
-
* (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
|
|
101
|
-
* · `auto` ⇒ `"classify"` —— **design/201 §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
|
|
102
|
-
* §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
|
|
103
|
-
*
|
|
104
|
-
* ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
|
|
105
|
-
* 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
|
|
106
|
-
* · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
|
|
107
|
-
* decision.action === "ask" …)`);
|
|
108
|
-
* · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
|
|
109
|
-
* `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
|
|
110
|
-
* 却没有 shell 安全轴折叠」)。
|
|
111
|
-
* 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
|
|
112
|
-
* 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
|
|
113
|
-
* (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [3378] 裁定链披露此
|
|
114
|
-
* 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
|
|
115
|
-
* · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
|
|
116
|
-
* 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
|
|
117
|
-
* shellGate 无效),给它 classify 纯为一致性。
|
|
118
|
-
*
|
|
119
|
-
* 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
|
|
120
|
-
* 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
|
|
121
|
-
* 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
|
|
122
|
-
*
|
|
123
|
-
* 🔴 **闭集 exhaustive switch,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
|
|
124
|
-
* 是**编译错误**(#157 的安全轴纪律:词表的未知项不许有静默兜底臂)。
|
|
125
|
-
*/
|
|
126
25
|
export function shellGateForMode(effMode) {
|
|
127
26
|
switch (effMode) {
|
|
128
27
|
case "bypassPermissions":
|
|
@@ -134,18 +33,11 @@ export function shellGateForMode(effMode) {
|
|
|
134
33
|
return "classify";
|
|
135
34
|
}
|
|
136
35
|
}
|
|
137
|
-
/** Cap on `outputStyle` length — it lands in the system prompt, so it shares the `MAX_SYSTEM_PROMPT_CHARS` (16384)
|
|
138
|
-
* posture (an uncapped prompt body is a per-turn token-cost hole; adversarial review). Exported so the HTTP layer
|
|
139
|
-
* (prepareSpec) can 400 fail-loud on submit with the SAME bound this defensive parse enforces on every path. */
|
|
140
36
|
export const MAX_SETTINGS_OUTPUT_STYLE_CHARS = 16_384;
|
|
141
|
-
/** Cap on the element count of each permission list (allow/deny/ask) — bounds a pathological rule blast. */
|
|
142
37
|
export const MAX_SETTINGS_PERMISSION_RULES = 1_000;
|
|
143
|
-
/** Caps on `settings.env` — bound a pathological env blast (count + per-value length; values land in the shell env). */
|
|
144
38
|
export const MAX_SETTINGS_ENV_VARS = 256;
|
|
145
39
|
export const MAX_SETTINGS_ENV_VALUE_CHARS = 32_768;
|
|
146
40
|
export const MAX_SETTINGS_ENV_KEY_CHARS = 256;
|
|
147
|
-
/** A `Record<string,string>` of non-empty string keys → string values (capped), or undefined. Untrusted input →
|
|
148
|
-
* defensive (drop non-string entries, over-long values, and bound the count). */
|
|
149
41
|
function cleanEnvMap(v) {
|
|
150
42
|
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
151
43
|
return undefined;
|
|
@@ -154,7 +46,7 @@ function cleanEnvMap(v) {
|
|
|
154
46
|
let seen = 0;
|
|
155
47
|
for (const [k, val] of Object.entries(v)) {
|
|
156
48
|
if (n >= MAX_SETTINGS_ENV_VARS || seen >= MAX_SETTINGS_ENV_VARS * 8)
|
|
157
|
-
break;
|
|
49
|
+
break;
|
|
158
50
|
seen++;
|
|
159
51
|
if (typeof k === "string" && k.length > 0 && k.length <= MAX_SETTINGS_ENV_KEY_CHARS && typeof val === "string" && val.length <= MAX_SETTINGS_ENV_VALUE_CHARS) {
|
|
160
52
|
out[k] = val;
|
|
@@ -163,29 +55,17 @@ function cleanEnvMap(v) {
|
|
|
163
55
|
}
|
|
164
56
|
return n > 0 ? out : undefined;
|
|
165
57
|
}
|
|
166
|
-
/** A string[] of non-empty strings (capped at {@link MAX_SETTINGS_PERMISSION_RULES}), or undefined. Untrusted input
|
|
167
|
-
* → defensive (drop non-strings/empties; cap the count). */
|
|
168
58
|
function cleanStringArray(v) {
|
|
169
59
|
if (!Array.isArray(v))
|
|
170
60
|
return undefined;
|
|
171
61
|
const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
|
|
172
62
|
return out.length > 0 ? out : undefined;
|
|
173
63
|
}
|
|
174
|
-
/**
|
|
175
|
-
* Validate an untrusted `body.settings` (raw `unknown` off the wire) into the service-trusted {@link
|
|
176
|
-
* ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked; unknown keys are ignored
|
|
177
|
-
* (forward-compatible, matching the SDK's open `[k: string]: unknown`). `defaultMode` accepts the FIVE modes
|
|
178
|
-
* (post-[816]; unknown values coerce to `default` — see the header note). Returns the
|
|
179
|
-
* present-but-deferred field names so the caller can warn/advertise rather than silently dropping `env`/`hooks`.
|
|
180
|
-
*/
|
|
181
64
|
export function parseTaskSettings(raw) {
|
|
182
65
|
if (raw === null || raw === undefined || typeof raw !== "object")
|
|
183
66
|
return { deferred: [] };
|
|
184
67
|
const s = raw;
|
|
185
68
|
const deferred = [];
|
|
186
|
-
// `hooks` is NO LONGER deferred(hook-runner 阶段一):契约校验(registry-core HooksConfig +
|
|
187
|
-
// 服务侧上限)通过 → 进 applied 形状,由 apply 侧单用户闸决定翻不翻;malformed → 报 deferred(镜像 env 的
|
|
188
|
-
// present-but-malformed 姿势——本函数是 resume 等路径的防御层,submit 路径另有 fail-loud 400)。
|
|
189
69
|
let hooksConfig;
|
|
190
70
|
if (s.hooks !== undefined && s.hooks !== null) {
|
|
191
71
|
const parsedHooks = parseHooksConfig(s.hooks);
|
|
@@ -196,11 +76,9 @@ export function parseTaskSettings(raw) {
|
|
|
196
76
|
}
|
|
197
77
|
const shellEnv = cleanEnvMap(s.env);
|
|
198
78
|
if (s.env !== undefined && s.env !== null && shellEnv === undefined)
|
|
199
|
-
deferred.push("env");
|
|
79
|
+
deferred.push("env");
|
|
200
80
|
const out = {};
|
|
201
81
|
const perms = s.permissions;
|
|
202
|
-
// F1(#157 复审留裁裁修):出现但形错(数组/标量)——本层是 RESUME 重放也走的宽容层,不 throw,但必
|
|
203
|
-
// 留痕 deferred(镜像 env/hooks malformed 姿势;fresh submit 已被 http 门 400 拦,这里护的是存量行)。
|
|
204
82
|
if (perms !== undefined && perms !== null && (typeof perms !== "object" || Array.isArray(perms))) {
|
|
205
83
|
deferred.push("permissions");
|
|
206
84
|
}
|
|
@@ -209,13 +87,6 @@ export function parseTaskSettings(raw) {
|
|
|
209
87
|
const allow = cleanStringArray(p.allow);
|
|
210
88
|
const deny = cleanStringArray(p.deny);
|
|
211
89
|
const ask = cleanStringArray(p.ask);
|
|
212
|
-
// 🔒 whitelist the five honored modes (OUR set, not a CC set — see SettingsPermissionMode header / A-065 P2-1);
|
|
213
|
-
// any other value is dropped (absent = no opinion). This drop arm is the RESUME-replay lenient layer — fresh
|
|
214
|
-
// submits already 400 at the HTTP gate (server.ts #157-③, [2766] ruling), same two-layer posture as the
|
|
215
|
-
// top-level permissionMode leg. `bypassPermissions` is
|
|
216
|
-
// accepted post-[816] because it now means "do not ADD the mode-derived fs-write ask gate" — exactly every
|
|
217
|
-
// mode's pre-[816] behavior — and can never subtract from the deployment baseline (tightenTaskSpec deny-wins;
|
|
218
|
-
// the module-header §3.3 invariant is thereby REFINED, not weakened: bypass still cannot LOOSEN anything).
|
|
219
90
|
const mode = p.defaultMode;
|
|
220
91
|
const defaultMode = mode === "default" || mode === "acceptEdits" || mode === "plan" || mode === "bypassPermissions" || mode === "auto" ? mode : undefined;
|
|
221
92
|
if (allow || deny || ask || defaultMode) {
|
|
@@ -224,15 +95,11 @@ export function parseTaskSettings(raw) {
|
|
|
224
95
|
}
|
|
225
96
|
if (typeof s.model === "string" && s.model.length > 0)
|
|
226
97
|
out.model = s.model;
|
|
227
|
-
// outputStyle is appended to the system prompt → cap it (defense in depth; prepareSpec 400s an over-cap value on
|
|
228
|
-
// submit, this drops it on any path the HTTP gate didn't cover, e.g. a resume of a pre-cap stored body).
|
|
229
98
|
if (typeof s.outputStyle === "string" && s.outputStyle.length > 0 && s.outputStyle.length <= MAX_SETTINGS_OUTPUT_STYLE_CHARS) {
|
|
230
99
|
out.outputStyle = s.outputStyle;
|
|
231
100
|
}
|
|
232
101
|
if (shellEnv)
|
|
233
102
|
out.shellEnv = shellEnv;
|
|
234
|
-
// L2 ultracode preset (design/111): a CC-parity boolean. Only `true` opts in; any other value is ignored (a settings
|
|
235
|
-
// bundle that ONLY sets ultracode still returns `settings` via the hasApplied OR below).
|
|
236
103
|
if (s.ultracode === true)
|
|
237
104
|
out.ultracode = true;
|
|
238
105
|
if (hooksConfig)
|
|
@@ -240,33 +107,12 @@ export function parseTaskSettings(raw) {
|
|
|
240
107
|
const hasApplied = out.permissions !== undefined || out.model !== undefined || out.outputStyle !== undefined || out.shellEnv !== undefined || out.ultracode !== undefined || out.hooks !== undefined;
|
|
241
108
|
return { settings: hasApplied ? out : undefined, deferred };
|
|
242
109
|
}
|
|
243
|
-
/** Defensive acceptance of the `appendSystemPrompt` rider ([1478] R2 + codex F2): the HTTP 400 gate (prepareSpec,
|
|
244
|
-
* MAX_SYSTEM_PROMPT_CHARS) only covers FRESH submits — every resume family re-enters resolveSpec on a STORED body,
|
|
245
|
-
* and a pre-1.243 store preserved unknown request keys verbatim (the field was INERT then), so an over-cap legacy
|
|
246
|
-
* value must not become active prompt content after rollout (context exhaustion / spend hole). Over-cap ⇒ dropped +
|
|
247
|
-
* warned — the rider was inert pre-upgrade, so dropping preserves the stored task's prior behavior; a 4xx here would
|
|
248
|
-
* permanently brick that task's resume instead (same defensive posture as parseTaskSettings' outputStyle drop).
|
|
249
|
-
* Cap value = the submit gate's (16384; MAX_SETTINGS_OUTPUT_STYLE_CHARS — both land in the same spec field). */
|
|
250
|
-
/** TRUE when `text` is an ALREADY-ASSEMBLED system prompt — core's migration guard detects the same three
|
|
251
|
-
* constitution anchors and takes the pass-through assembler arm, whose section subset (OPAQUE_KEPT, dist-read)
|
|
252
|
-
* has NO `core/role.append`: any append rider (top-level or settings.outputStyle) would be SILENTLY discarded
|
|
253
|
-
* there. Shared predicate for the submit 400 gate, the resolveSpec resume mirror, and the center-declaration
|
|
254
|
-
* probe — one definition so the three gates can't drift. */
|
|
255
110
|
export function hasConstitutionAnchors(text) {
|
|
256
111
|
return typeof text === "string" && text.includes(CYBER_RISK) && text.includes(URL_SAFETY) && text.includes(HARNESS_SECTION_ANCHOR);
|
|
257
112
|
}
|
|
258
|
-
/** codex R9 (provider-aware append-less detection): mirrors core's ACTUAL pass-through triggers instead of a raw
|
|
259
|
-
* body-text heuristic — the assembler's migration guard probes (a) `stableBlocks` identity declarations and
|
|
260
|
-
* (b) `stableSystem` OUTPUT for the three anchors; a plain `userSystemPrompt` under the DEFAULT provider (no
|
|
261
|
-
* scenario provider) never triggers it and `core/role.append` is retained, so an anchored systemPrompt there
|
|
262
|
-
* must NOT block/strip a rider (R9 false-positive: unbranded default / scan rejected valid combos). Probing
|
|
263
|
-
* calls the provider hooks with a minimal ctx — server providers (and centerPromptProvider) are pure functions
|
|
264
|
-
* of ctx, and the same hooks run again inside core's assembly; a hook throw = conservative false (core will
|
|
265
|
-
* fail the task loudly on its own). `replaceAll` owns the whole prompt(legacy free-form `system()` 腿已随 core 2.0.0 删除)
|
|
266
|
-
* (REPLACE_ALL_EXCLUDED strips role.append) → append-less true. */
|
|
267
113
|
export function providerDropsAppend(provider, userSystemPrompt) {
|
|
268
114
|
if (!provider)
|
|
269
|
-
return false;
|
|
115
|
+
return false;
|
|
270
116
|
if (provider.replaceAll === true)
|
|
271
117
|
return true;
|
|
272
118
|
const ctx = { userSystemPrompt, tools: [], memoryEnabled: false, consolidationEnabled: false };
|
|
@@ -286,11 +132,11 @@ export function providerDropsAppend(provider, userSystemPrompt) {
|
|
|
286
132
|
return false;
|
|
287
133
|
}
|
|
288
134
|
}
|
|
289
|
-
return false;
|
|
135
|
+
return false;
|
|
290
136
|
}
|
|
291
137
|
export function acceptAppendSystemPrompt(v, warn, packDropsAppend) {
|
|
292
138
|
if (v === undefined)
|
|
293
|
-
return undefined;
|
|
139
|
+
return undefined;
|
|
294
140
|
if (typeof v !== "string" || v.length === 0) {
|
|
295
141
|
warn?.(`appendSystemPrompt dropped: defined but malformed (${typeof v === "string" ? "empty string" : `non-string: ${typeof v}`}) — fresh submits 400 this shape; a stored resume body carrying it is normalized to absent`);
|
|
296
142
|
return undefined;
|
|
@@ -299,29 +145,13 @@ export function acceptAppendSystemPrompt(v, warn, packDropsAppend) {
|
|
|
299
145
|
warn?.(`appendSystemPrompt dropped: ${v.length} chars exceeds the ${MAX_SETTINGS_OUTPUT_STYLE_CHARS} cap (pre-cap stored body on a resume leg?)`);
|
|
300
146
|
return undefined;
|
|
301
147
|
}
|
|
302
|
-
// codex R2/R3/R9: `packDropsAppend` = the caller's PROVIDER-AWARE append-less verdict ({@link
|
|
303
|
-
// providerDropsAppend} over the effective provider / center snapshot — NOT a raw body-text heuristic, which
|
|
304
|
-
// false-positived on default-provider scenarios where core retains role.append). Fresh submits 400 the combo
|
|
305
|
-
// in resolveSpec; this is the resume-family mirror — the rider was INERT pre-1.243, so dropping WITH a warn
|
|
306
|
-
// preserves the stored task's prior behavior and leaves an audit trail. (core typed error at the assembly
|
|
307
|
-
// boundary = the structural endgame, requested on the board.)
|
|
308
148
|
if (packDropsAppend === true) {
|
|
309
149
|
warn?.("appendSystemPrompt dropped: the effective prompt pack cannot mount the append rider (assembled-identity/stableSystem pass-through, replaceAll, or legacy provider — no core/role.append section)");
|
|
310
150
|
return undefined;
|
|
311
151
|
}
|
|
312
152
|
return v;
|
|
313
153
|
}
|
|
314
|
-
/** A name-keyed ASK-list ToolPolicy: a tool whose NAME is in `askNames` tightens to `ask` (HITL gate); every other
|
|
315
|
-
* tool is out of scope (`allow`). core's `createAllowDenyPolicy` has only allow/deny slots (no `ask`), so the
|
|
316
|
-
* CC-parity `permissions.ask` list — "prompt the human before THIS tool" — needs this thin policy. Composed
|
|
317
|
-
* deny-wins with the allow/deny base, so `ask` only ever tightens (a baseline `deny` still wins; `allow`→`ask`). */
|
|
318
154
|
function createAskListPolicy(askNames) {
|
|
319
|
-
// RAW matching (core 5.0.0 RB-476 — the canonicalToolName fold is GONE): the rule name is compared to
|
|
320
|
-
// `req.toolName` verbatim, no alias folding in either direction. A rule written against a retired alias
|
|
321
|
-
// ("MultiEdit"/"Task") therefore matches NOTHING — which is why such a list is rejected up front
|
|
322
|
-
// ({@link rejectUnmatchableSettingsNames}) instead of being silently mounted as a no-op gate.
|
|
323
|
-
// (旧注写的 "Canonical-space matching, mirroring core's createAllowDenyPolicy (1.202)" 是折叠面还在时的
|
|
324
|
-
// 拍照,与下面这行 RAW 代码整段矛盾——同批改正,连同 :348 的 "canonical toolName key space"。)
|
|
325
155
|
const askSet = new Set(askNames);
|
|
326
156
|
return {
|
|
327
157
|
check(req) {
|
|
@@ -329,59 +159,28 @@ function createAskListPolicy(askNames) {
|
|
|
329
159
|
? { action: "ask", message: `tool "${req.toolName}" requires approval by your settings`, decisionReason: "rule" }
|
|
330
160
|
: { action: "allow" };
|
|
331
161
|
},
|
|
332
|
-
// [931]③ core 1.300 名单池审计面:自定义 closure 无元数据曾是审计盲区(`ask:["*"]`/typo 名静默
|
|
333
|
-
// no-op)——附 nameSets 后 core prepare 对实挂宇宙交叉核,无匹配名 → onError config.toolpolicy.
|
|
334
|
-
// unmatched_names(enforcement 字节不变,纯观测)。core combinePolicies 全链透传。
|
|
335
162
|
nameSets: [{ ask: askNames }],
|
|
336
163
|
};
|
|
337
164
|
}
|
|
338
|
-
/** [1245]/[1247]③ — the mode-derived dynamic-workflow ask gate (CC "Run a dynamic workflow?" dialog, engine half).
|
|
339
|
-
* core mounts `run_workflow` with effect="write" and NO exemption in the gate stack, but the mode-derived leg here
|
|
340
|
-
* only ever gated fs writes — so manual/default mode ran workflows un-asked ([1245] clay dogfood). This leg keys
|
|
341
|
-
* the canonical name(`Workflow`;5.0.0:旧拼法 aliases 已随 core RB-476 退役,raw 直比)
|
|
342
|
-
* and attaches on default/auto/acceptEdits, independent of the fs-gate wiring (workflow gating is not
|
|
343
|
-
* fs-dependent — sandbox lanes get it too). acceptEdits asks ON PURPOSE: CC's acceptEdits auto-allows file edits
|
|
344
|
-
* only, a workflow confirmation is not an edit ([1248]② — cli to verify against the 212 live shell; flip there
|
|
345
|
-
* if the real shell differs). bypassPermissions/undefined-mode attach nothing (pre-existing behavior); plan is
|
|
346
|
-
* blocked upstream by handsReadOnly (effect="write"). Deployment disable stays `RuntimeCaps.allowWorkflows` —
|
|
347
|
-
* this leg is the per-request HITL gate, not the capability switch.
|
|
348
|
-
*
|
|
349
|
-
* 🔴 A-033.1(#256):这三处曾**手抄字面量** `"Workflow"`。core 改名该工具时,手抄的名字与 core 实挂的名字
|
|
350
|
-
* 分家 ⇒ ask 腿的 `req.toolName !== "Workflow"` 恒真、deny 腿恒假,**整条门不触发**,而且不报错、不留痕
|
|
351
|
-
* —— 安全轴上的静默 fail-open。现在四处引用(本文件两条腿 + `nameSets` 审计面 + `http/server.ts` 的
|
|
352
|
-
* 可满足性预检)全部派生自 core 导出的 {@link RUN_WORKFLOW_TOOL_NAME},core 一改名这四处同步跟着改。 */
|
|
353
165
|
const WORKFLOW_ASK_NAMES = [RUN_WORKFLOW_TOOL_NAME];
|
|
354
166
|
function createWorkflowAskPolicy(isExempt) {
|
|
355
167
|
return {
|
|
356
168
|
async check(req) {
|
|
357
169
|
if (req.toolName !== RUN_WORKFLOW_TOOL_NAME)
|
|
358
170
|
return { action: "allow" };
|
|
359
|
-
// codex F2 (durable lane): the "don't ask again this session" grant (decide remember="session" →
|
|
360
|
-
// approvalExemptionStore) is probed at the POLICY layer here — the durable lane parks every policy `ask`
|
|
361
|
-
// and its own exempt probe only covers the deployment policy's asks, so without this probe a remembered
|
|
362
|
-
// Workflow approval would re-park on every later call. Same posture as the fs-write gate's isExempt
|
|
363
|
-
// (RAW toolName key space — 5.0.0 折叠面退役后各层比对的都是 `req.toolName` 逐字,一个 grant 服务所有层
|
|
364
|
-
// 靠的是「同一个 raw 名」而不是「同一个 canonical 空间」);a THROWN probe ⇒ not exempt (fail-closed).
|
|
365
171
|
if (isExempt) {
|
|
366
172
|
try {
|
|
367
173
|
if (await isExempt(req.toolName))
|
|
368
174
|
return { action: "allow", decisionReason: "rule" };
|
|
369
175
|
}
|
|
370
176
|
catch {
|
|
371
|
-
/* fail-closed: keep asking */
|
|
372
177
|
}
|
|
373
178
|
}
|
|
374
179
|
return { action: "ask", message: `dynamic workflow "${req.toolName}" requires approval in this permission mode`, decisionReason: "rule" };
|
|
375
180
|
},
|
|
376
|
-
nameSets: [{ ask: WORKFLOW_ASK_NAMES }],
|
|
181
|
+
nameSets: [{ ask: WORKFLOW_ASK_NAMES }],
|
|
377
182
|
};
|
|
378
183
|
}
|
|
379
|
-
/** codex F1 (plan-mode escape): `run_workflow` mounts off self-orchestration INDEPENDENT of the hands band
|
|
380
|
-
* (core prepare-task dist 亲读:handsReadOnly only strips/reads the hand tools; the runtime plan blocker
|
|
381
|
-
* `planModeRef` starts INACTIVE and only the model-driven EnterPlanMode tool arms it), so USER-driven plan
|
|
382
|
-
* (permissionMode=plan) would run workflows — whose child agents write by default — during "read-only"
|
|
383
|
-
* research. Plan therefore composes an explicit canonical-space DENY (not ask: plan is research, CC blocks
|
|
384
|
-
* write-effect tools outright), deny-wins under the fold so nothing can un-tighten it. */
|
|
385
184
|
function createWorkflowPlanDenyPolicy() {
|
|
386
185
|
return {
|
|
387
186
|
check(req) {
|
|
@@ -392,52 +191,6 @@ function createWorkflowPlanDenyPolicy() {
|
|
|
392
191
|
nameSets: [{ deny: WORKFLOW_ASK_NAMES }],
|
|
393
192
|
};
|
|
394
193
|
}
|
|
395
|
-
/**
|
|
396
|
-
* Build the per-request permission ToolPolicy from the parsed settings (mirrors shell `seam.ts` — see header).
|
|
397
|
-
* - base = name-keyed allow/deny (`createAllowDenyPolicy`): an `allow` list narrows to those tools; a `deny` list
|
|
398
|
-
* hard-blocks — composed deny-wins with an ASK-list ({@link createAskListPolicy}) so `permissions.ask` actually
|
|
399
|
-
* forces an approval gate (NOT silently dropped — review finding). `deny` > `ask` > `allow` on overlap.
|
|
400
|
-
* - `defaultMode` derive onto that base ([820]/[822] five-mode table; the fs-write gate needs `gate` wiring —
|
|
401
|
-
* without it every mode degrades to the base rules only, the pre-[816] behavior):
|
|
402
|
-
* `default` → base + `createFsWriteGatePolicy({rootPath: cwd, exemptDirs:[scratchpad], ask})` — CC
|
|
403
|
-
* manual: every fs write asks (the [816] P0 fix), scratchpad writes ride free.
|
|
404
|
-
* `acceptEdits` → base + the same factory with `acceptDirs:[cwd]` — writes INSIDE the cwd subtree
|
|
405
|
-
* auto-allow, OUTSIDE still ask (CC 207 dirname-variant card). NOT a loosen vs the
|
|
406
|
-
* deployment: the factory only ever emits allow/ask and is folded deny-wins, so a
|
|
407
|
-
* baseline deny/ask still wins; vs `default` it is simply a narrower CLIENT gate, and
|
|
408
|
-
* both are client-side ADDITIONS the client may size itself.
|
|
409
|
-
* `plan` → handsReadOnly + present_plan (unchanged; the write tools never reach an ask).
|
|
410
|
-
* `bypassPermissions` → base only, NO factory ([820] table: bypass = "不加门" not "开门" — deployment
|
|
411
|
-
* approval/governance baselines still apply; this is exactly every mode's pre-[816]
|
|
412
|
-
* derive, so bypass grants nothing that wasn't already granted).
|
|
413
|
-
* `auto` → base + the `default` factory (ask 兜底) — core's auto-mode classifier (entitlement-
|
|
414
|
-
* gated: RunnerDeps.autoMode ∧ runtimeCaps.autoMode, 1.276/1.277) screens asks UPSTREAM
|
|
415
|
-
* of resolveAsk, allowing safe calls and leaving suspicious ones on the ask path ([822]②).
|
|
416
|
-
* absent (undefined) → base only, NO factory: an expressed-no-mode request keeps its pre-[816] behavior
|
|
417
|
-
* (attaching the ask gate on a mere allow/deny-list stamp would regress headless callers
|
|
418
|
-
* that never asked for a mode; the shell always sends its mode explicitly).
|
|
419
|
-
* Returns the composed ToolPolicy + whether `handsReadOnly` should tighten. Returns `undefined` policy when there
|
|
420
|
-
* is no permission rule and no mode-derived gate (nothing to project).
|
|
421
|
-
*/
|
|
422
|
-
/**
|
|
423
|
-
* A3 — the per-request leg of the ONE "this name can never match a live tool" judgment (single source:
|
|
424
|
-
* config.ts {@link findUnmatchableToolNames}; the other two legs are the `APPROVAL_*` env vars, which reject at
|
|
425
|
-
* boot, and the center-published `runtime.approvalRequire`, which rejects that domain and keeps the prior value).
|
|
426
|
-
*
|
|
427
|
-
* WHY this leg exists: `ask`/`deny` are mounted as RAW name comparisons (`createAskListPolicy` /
|
|
428
|
-
* core's `createAllowDenyPolicy`, the 5.0.0 fold removal), so a retired name ("Task"/"bash"/"KillShell") or a
|
|
429
|
-
* pre-prefix/incomplete MCP name ("figma__x"/"mcp__figma") mounts a gate that matches NOTHING — the caller
|
|
430
|
-
* believes it added an approval gate or a hard block and got neither. That is the FAIL-OPEN direction, and it was
|
|
431
|
-
* silent on this leg. Throwing here surfaces as a 422 at the HTTP boundary (resolve-spec already wraps every
|
|
432
|
-
* throw out of `applyTaskSettings`), i.e. the same fail-loud posture the other two legs have.
|
|
433
|
-
*
|
|
434
|
-
* `allow` is deliberately NOT judged by the GENERAL arm: it is a WHITELIST — a name that matches nothing leaves
|
|
435
|
-
* the allowlist effectively empty, so every tool falls to "not in the allowlist" and is DENIED. That fails closed
|
|
436
|
-
* and is immediately visible to the caller, which is a different (and acceptable) failure mode from a silent hole.
|
|
437
|
-
* The ONE exception is the CC-rule-form arm ({@link findCcRuleFormNames}, #186): `allow: ["Bash(ps:*)"]` is the
|
|
438
|
-
* shape a CC user writes to get FEWER prompts and it silently costs them Bash entirely — a spelling-family error,
|
|
439
|
-
* not a typo, so it is refused on all three lists. See the config.ts docblock for the two knobs it points at.
|
|
440
|
-
*/
|
|
441
194
|
function rejectUnmatchableSettingsNames(perms) {
|
|
442
195
|
for (const [field, names] of [["permissions.ask", perms.ask], ["permissions.deny", perms.deny]]) {
|
|
443
196
|
if (!names)
|
|
@@ -458,39 +211,22 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
458
211
|
return {};
|
|
459
212
|
const { allow, deny, ask, defaultMode } = perms;
|
|
460
213
|
rejectUnmatchableSettingsNames(perms);
|
|
461
|
-
// The base name-keyed gate: allow/deny via core's helper + an ask-list (FINDING-1: `ask` must enforce, not drop).
|
|
462
|
-
// Each is composed deny-wins (`deny` > `ask` > `allow`), so the fold can only ever tighten.
|
|
463
214
|
const policies = [];
|
|
464
215
|
if (allow || deny)
|
|
465
216
|
policies.push(createAllowDenyPolicy({ ...(allow ? { allow } : {}), ...(deny ? { deny } : {}) }));
|
|
466
217
|
if (ask)
|
|
467
218
|
policies.push(createAskListPolicy(ask));
|
|
468
|
-
// The mode-derived fs-write ask gate (core 1.290 factory — real shape read in dist: {env, rootPath?, acceptDirs?,
|
|
469
|
-
// exemptDirs?, defaultWrite}, gates Write/Edit/NotebookEdit, exempt→accept→default layering, canonicalize inside
|
|
470
|
-
// the given env, only ever allow/ask). undefined without wiring (see FsWriteGateWiring). #156/#165: the sandbox
|
|
471
|
-
// lanes supply wiring too — same factory, a deferred real-env proxy and no cwd; the shape difference lives
|
|
472
|
-
// entirely in what `resolve-spec.ts` puts in the wiring, not in a second code path here.
|
|
473
|
-
// (③ sensitive-path DENY 已随 #177 搬 governance 层——见 FsWriteGateWiring 尾注。deny-wins 由
|
|
474
|
-
// tightenTaskSpec 折叠保证:豁免/acceptDirs/settings allow 仍然越不过部署 DENY 基线。)
|
|
475
219
|
const fsWriteGate = (acceptCwd) => {
|
|
476
220
|
if (!gate)
|
|
477
221
|
return undefined;
|
|
478
|
-
// #156/#165: `cwd` is optional (absent on the sandbox lanes). Omitting `rootPath` makes core leave a relative
|
|
479
|
-
// target relative → the sandbox proxy's lexical `absolutePath` refuses it → ask; omitting `acceptDirs` means
|
|
480
|
-
// acceptEdits mints no auto-allow domain at all. Both are the intended fail-safe, NOT a degraded host arm.
|
|
481
222
|
const gatePolicy = createFsWriteGatePolicy({
|
|
482
223
|
env: gate.env,
|
|
483
224
|
...(gate.cwd !== undefined ? { rootPath: gate.cwd } : {}),
|
|
484
225
|
...(acceptCwd && gate.cwd !== undefined ? { acceptDirs: [gate.cwd] } : {}),
|
|
485
226
|
...(gate.scratchpadDir ? { exemptDirs: [gate.scratchpadDir] } : {}),
|
|
486
|
-
...(gate.isExempt ? { isExempt: gate.isExempt } : {}),
|
|
227
|
+
...(gate.isExempt ? { isExempt: gate.isExempt } : {}),
|
|
487
228
|
defaultWrite: "ask",
|
|
488
229
|
});
|
|
489
|
-
// NotebookEdit coverage is core-native since 1.296.1(我方 [886]① 请修 core 已兑现,1.207 的临时参数桥
|
|
490
|
-
// 就此撤除):DEFAULT_GUARDED_TOOLS 含 NotebookEdit,且两 policy 共用 `writeTargetPath` 提取器——canonical
|
|
491
|
-
// NotebookEdit 时 notebook_path 恒先(诱饵 file_path 失效),其余工具 file_path 先(反向诱饵也封);均 dist
|
|
492
|
-
// 亲读。回归锚保留在 test/task-settings.test.ts(NotebookEdit deny + 双向诱饵),现在锁的是 core 行为经
|
|
493
|
-
// 我方折叠——core 若回退,锚变红。
|
|
494
230
|
return gatePolicy;
|
|
495
231
|
};
|
|
496
232
|
const compose = (...extra) => {
|
|
@@ -500,10 +236,6 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
500
236
|
const basePolicy = compose();
|
|
501
237
|
switch (defaultMode) {
|
|
502
238
|
case "plan":
|
|
503
|
-
// plan = read-only hands + the plan workflow: a TIGHTEN (handsReadOnly false→true), honored remotely.
|
|
504
|
-
// ALSO mount core's `present_plan` (enablePlanMode) so the model explores read-only then
|
|
505
|
-
// proposes a plan (CC EnterPlanMode→ExitPlanMode parity). Both are tighten/additive — no loosening.
|
|
506
|
-
// codex F1: run_workflow mounts OUTSIDE the hands band — deny it explicitly (see createWorkflowPlanDenyPolicy).
|
|
507
239
|
return { toolPolicy: compose(createWorkflowPlanDenyPolicy()), handsReadOnly: true, enablePlanMode: true };
|
|
508
240
|
case "acceptEdits": {
|
|
509
241
|
const p = compose(fsWriteGate(true), createWorkflowAskPolicy(workflowGate?.isExempt));
|
|
@@ -511,10 +243,6 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
511
243
|
}
|
|
512
244
|
case "default":
|
|
513
245
|
case "auto": {
|
|
514
|
-
// (historical note kept: the pre-[816] impl fell through to base-only here — with no gate to attach,
|
|
515
|
-
// acceptEdits-as-loosen could not be honored. The [820] factory inverts the frame: default now ADDS an ask
|
|
516
|
-
// gate and acceptEdits ADDS a narrower one, both tighten-safe under the deny-wins fold.)
|
|
517
|
-
// ([1557]§四 的 shellGate 便车已随 #153 搬 governance 层——见 FsWriteGateWiring 尾注。)
|
|
518
246
|
const p = compose(fsWriteGate(false), createWorkflowAskPolicy(workflowGate?.isExempt));
|
|
519
247
|
return p ? { toolPolicy: p } : {};
|
|
520
248
|
}
|
|
@@ -523,20 +251,6 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
523
251
|
return basePolicy ? { toolPolicy: basePolicy } : {};
|
|
524
252
|
}
|
|
525
253
|
}
|
|
526
|
-
/**
|
|
527
|
-
* Apply a client's parsed per-request settings onto a base `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link
|
|
528
|
-
* tightenTaskSpec} pass (so the permission override composes deny-wins onto the baseline — TRAP #1 — and
|
|
529
|
-
* `handsReadOnly`/`onAsk` can only narrow). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any override would
|
|
530
|
-
* LOOSEN a stricter base — surfaced by the caller as a 4xx (a client settings stamp that tries to widen is a
|
|
531
|
-
* client error, never a silent weakening). `model`/`outputStyle` are applied directly (model is the caller's
|
|
532
|
-
* choice WITHIN the catalog — the caller gates it before calling; outputStyle appends to the system prompt).
|
|
533
|
-
*
|
|
534
|
-
* Returns `base` untouched when the parsed settings project nothing onto the spec.
|
|
535
|
-
*/
|
|
536
|
-
// ([1557]§四 的 SHELL_GATE_RANK 副本与 shellGateSafe 守卫已随 #153 搬 runtime-governance ——
|
|
537
|
-
// settings 折叠不再触碰 TaskSpec.shellGate,base 上 governance 层施加的值原样透传。design/201 的
|
|
538
|
-
// {@link shellGateForMode} 也不改这句话:那只纯函数产的是 governance 的 base,由 resolve-spec 阶段④
|
|
539
|
-
// 写进 spec 字面量;本折叠拿到的 `base` 里可能因此已经带着一档 shellGate,而它照旧原样透传。)
|
|
540
254
|
export function applyTaskSettings(base, settings, gate, workflowGate) {
|
|
541
255
|
const { toolPolicy, handsReadOnly, enablePlanMode } = deriveSettingsPolicy(settings, gate, workflowGate);
|
|
542
256
|
let next = base;
|
|
@@ -546,14 +260,10 @@ export function applyTaskSettings(base, settings, gate, workflowGate) {
|
|
|
546
260
|
overrides.toolPolicy = toolPolicy;
|
|
547
261
|
if (handsReadOnly !== undefined)
|
|
548
262
|
overrides.handsReadOnly = handsReadOnly;
|
|
549
|
-
next = tightenTaskSpec(next, overrides);
|
|
263
|
+
next = tightenTaskSpec(next, overrides);
|
|
550
264
|
}
|
|
551
|
-
// R4: enablePlanMode (mount present_plan) is ADDITIVE — it widens NOTHING (a read-only plan workflow), so it rides
|
|
552
|
-
// directly (not through tightenTaskSpec, which guards safety fields). Only ever set true (plan), never cleared.
|
|
553
265
|
if (enablePlanMode)
|
|
554
266
|
next = { ...next, enablePlanMode: true };
|
|
555
|
-
// outputStyle → appended system prompt (additive UX directive; never a safety field). The caller already set any
|
|
556
|
-
// business systemPrompt; core's appendSystemPrompt rides after the scenario base.
|
|
557
267
|
if (settings.outputStyle) {
|
|
558
268
|
const appended = next.appendSystemPrompt ? `${next.appendSystemPrompt}\n\n${settings.outputStyle}` : settings.outputStyle;
|
|
559
269
|
next = { ...next, appendSystemPrompt: appended };
|
package/dist/task-workflow.js
CHANGED
|
@@ -1,82 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflow super-set unlock — map the per-task `body.selfOrchestration` intent onto
|
|
3
|
-
* `TaskSpec.selfOrchestration`, the gate that mounts core's LLM-authored `run_workflow` engine (the workflow.ts
|
|
4
|
-
* agent/parallel/pipeline orchestrator). Same shape as the rank-1 roster gap: the engine (core run_workflow +
|
|
5
|
-
* workflow.ts) is BUILT, but nothing mapped a body field to the per-task spec flag, so it was unreachable over HTTP
|
|
6
|
-
* (capabilities advertised `workflows:true` but the LLM could never call run_workflow).
|
|
7
|
-
*
|
|
8
|
-
* Gating:
|
|
9
|
-
* - DEPLOYMENT: `selfOrchestrationEnabled` must be on (the workflowScriptRunner + stores are wired). Otherwise a set
|
|
10
|
-
* flag is a harmless no-op (core fail-closes run_workflow without the sandbox runner), so it's dropped for clarity.
|
|
11
|
-
* - MULTI-TENANT: honored ONLY when a per-principal entitlement RESOLVER is wired (`hasEntitlementResolver`). 🔴 core's
|
|
12
|
-
* `allowWorkflows` (RuntimeCaps) is TIGHTEN-ONLY — a resolver can only DENY; `undefined` = NO restriction, so the
|
|
13
|
-
* deployment capability governs. Therefore WITHOUT a resolver the engine would mount run_workflow UNGATED for every
|
|
14
|
-
* tenant. So we fail-closed here (defense-in-depth: a misconfigured multi-tenant deployment with no entitlement
|
|
15
|
-
* source must NOT leak the LLM-authored workflow engine). WITH a resolver, core enforces `allowWorkflows`
|
|
16
|
-
* per-principal (deny → fail-closed). Single-user (owns its own worker) honors directly. Three-gate: engine-can ∧
|
|
17
|
-
* center-may(allowWorkflows) ∧ shell-show.
|
|
18
|
-
*/
|
|
19
1
|
export function selfOrchestrationFromBody(body, config, hasEntitlementResolver) {
|
|
20
2
|
if (body.selfOrchestration !== true || !config.selfOrchestrationEnabled)
|
|
21
3
|
return undefined;
|
|
22
4
|
if (config.requirePrincipal !== true)
|
|
23
|
-
return true;
|
|
24
|
-
return hasEntitlementResolver ? true : undefined;
|
|
5
|
+
return true;
|
|
6
|
+
return hasEntitlementResolver ? true : undefined;
|
|
25
7
|
}
|
|
26
|
-
/**
|
|
27
|
-
* §4 (Fork) — map the per-task `body.enableFork` intent onto `TaskSpec.enableFork`. core 1.257 (design/136 BREAKING)
|
|
28
|
-
* RETIRED the standalone `Fork` tool; a fork is now `Agent(subagent_type:"fork")`, and `enableFork` is one of the two
|
|
29
|
-
* governance axes (`forkGovernanceDenial(spec.enableFork, runtimeCaps.allowFork)`) that gate it.
|
|
30
|
-
*
|
|
31
|
-
* 🔴 CONSUME-CRITICAL — the default FLIPPED to opt-OUT: core now treats `enableFork === undefined` as **available**
|
|
32
|
-
* (only an EXPLICIT `enableFork:false` / `allowFork:false` denies — CC's default posture). So where the SERVICE intends
|
|
33
|
-
* to DENY, it must return `false` EXPLICITLY — the old "return undefined = not granted" is now fail-OPEN.
|
|
34
|
-
* - DEPLOYMENT posture OFF (`!forkEnabled`, e.g. multi-tenant default): `false` (explicit deny — preserve opt-in posture).
|
|
35
|
-
* - PER-TASK OPT-OUT (`body.enableFork === false`): `false` (any tenancy).
|
|
36
|
-
* - SINGLE-USER TURNKEY: `true` — DEFAULT ON for the LLM (clay 2026-07-01). (core still only lets it fork where the
|
|
37
|
-
* session store is fork-capable and never inside a fork / read-only band — graceful degrade, no capability lie.)
|
|
38
|
-
* - MULTI-TENANT: `true` ONLY with a per-principal entitlement RESOLVER wired (core then enforces `allowFork`
|
|
39
|
-
* tighten-only per-principal); WITHOUT a resolver `false` (fail-closed — `allowFork` is tighten-only so an absent
|
|
40
|
-
* resolver = `undefined` = ungated, and now `enableFork:undefined` also auto-grants, so the service must hard-deny).
|
|
41
|
-
*/
|
|
42
8
|
export function enableForkFromBody(body, config, hasEntitlementResolver) {
|
|
43
9
|
if (!config.forkEnabled)
|
|
44
|
-
return false;
|
|
10
|
+
return false;
|
|
45
11
|
if (body.enableFork === false)
|
|
46
|
-
return false;
|
|
12
|
+
return false;
|
|
47
13
|
if (config.requirePrincipal !== true)
|
|
48
|
-
return true;
|
|
49
|
-
return hasEntitlementResolver;
|
|
14
|
+
return true;
|
|
15
|
+
return hasEntitlementResolver;
|
|
50
16
|
}
|
|
51
|
-
/**
|
|
52
|
-
* The workflow-script MODEL allowlist (`agent({model})` picks). core is
|
|
53
|
-
* fail-closed: NO allowlist configured ⇒ `WorkflowModelNotAllowedError` on ANY model pick, which on a TOC
|
|
54
|
-
* single-user deployment broke clay's own dual-model scripts (决策 v4-pro + 执行 v4-flash) out of the box —
|
|
55
|
-
* the operator configured every catalog entry themselves, so there is nothing to protect them from.
|
|
56
|
-
* - Explicit `SELF_ORCHESTRATION_MODELS` always wins (any tenancy — the operator's deliberate narrowing).
|
|
57
|
-
* - SINGLE-USER: default to the deployment's OWN model catalog (`Object.keys(models)`).
|
|
58
|
-
* - MULTI-TENANT: keep core's fail-closed empty default (a model pick is a cost/capability grant the
|
|
59
|
-
* operator must make explicitly; a tenant script must not self-select the expensive rung).
|
|
60
|
-
* Returns undefined when no allowlist should be set (core then fail-closes picks; the script's `agent()`
|
|
61
|
-
* without a model still runs on the workflow's default role — only PICKING is gated).
|
|
62
|
-
*/
|
|
63
17
|
export function workflowModelAllowlistFor(config) {
|
|
64
18
|
if (config.selfOrchestrationModels.length > 0)
|
|
65
19
|
return config.selfOrchestrationModels;
|
|
66
20
|
return config.requirePrincipal !== true ? Object.keys(config.models) : undefined;
|
|
67
21
|
}
|
|
68
|
-
|
|
69
|
-
* is run-scoped and fully released at parent end, so these only bound the WITHIN-RUN memory pin). */
|
|
70
|
-
export const RETAIN_SUBAGENT_TTL_MS_CAP = 24 * 60 * 60 * 1000; // 24h — far above any sane interactive run
|
|
22
|
+
export const RETAIN_SUBAGENT_TTL_MS_CAP = 24 * 60 * 60 * 1000;
|
|
71
23
|
export const RETAIN_SUBAGENT_MAX_CAP = 64;
|
|
72
|
-
/**
|
|
73
|
-
* design/122 (core 1.225): normalize an untrusted `body.retainSubagentSessions` onto the TaskSpec field.
|
|
74
|
-
* `true`/`false` pass through (core's own defaults apply); an object keeps only NUMERIC ttlMs/max, each
|
|
75
|
-
* clamped to a positive integer under the cap (retention pins settled child sessions in memory for the
|
|
76
|
-
* parent run's lifetime — the knob must not become a caller-controlled resource hold). Anything malformed →
|
|
77
|
-
* undefined = OFF (core default). Defensive, never throws: RESUME re-runs resolveSpec WITHOUT the HTTP-layer
|
|
78
|
-
* validation, so this normalizer is the single guard on every path.
|
|
79
|
-
*/
|
|
80
24
|
export function normalizeRetainSubagentSessions(raw) {
|
|
81
25
|
if (raw === true || raw === false)
|
|
82
26
|
return raw;
|
|
@@ -87,20 +31,9 @@ export function normalizeRetainSubagentSessions(raw) {
|
|
|
87
31
|
const ttlMs = clamp(o.ttlMs, RETAIN_SUBAGENT_TTL_MS_CAP);
|
|
88
32
|
const max = clamp(o.max, RETAIN_SUBAGENT_MAX_CAP);
|
|
89
33
|
if (ttlMs === undefined && max === undefined)
|
|
90
|
-
return undefined;
|
|
34
|
+
return undefined;
|
|
91
35
|
return { ...(ttlMs !== undefined ? { ttlMs } : {}), ...(max !== undefined ? { max } : {}) };
|
|
92
36
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Per-task wall clock by TENANCY POSTURE (clay 2026-07-04, make-real 教训①:600s 默认墙钟砍掉了健康推进的
|
|
95
|
-
* 长任务/chaos 恢复 run):
|
|
96
|
-
* - SINGLE-USER turnkey(`requirePrincipal !== true`)= the user IS the superadmin → **NO wall clock**
|
|
97
|
-
* (undefined; core arms none — its separate wedge safety-net still catches a stuck tool call, so "no wall"
|
|
98
|
-
* never means "hangs forever"). CC has no whole-task decapitation either.
|
|
99
|
-
* - MULTI-TENANT: base 2400s (big tasks — council/debate/discuss — 3600s). (PAIR-REVIEW F-4 附带修:此行
|
|
100
|
-
* 曾写 600s/1500s 与代码漂移——真值以下方 base 为准。)
|
|
101
|
-
* - Explicit `TASK_TIMEOUT_SEC` wins in ANY tenancy (the operator's deliberate wall) and keeps its documented
|
|
102
|
-
* raise-only floor (`Math.max` with the base, so a misconfigured low value cannot shrink a council budget).
|
|
103
|
-
*/
|
|
104
37
|
export function taskWallClockSec(envTimeoutSec, requirePrincipal, big) {
|
|
105
38
|
const base = big ? 3600 : 2400;
|
|
106
39
|
if (envTimeoutSec > 0)
|