@sema-agent/server 7.56.0 → 7.57.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.
Files changed (110) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +2 -2
  3. package/USAGE.md +101 -9
  4. package/dist/approval-ask-audit-store.d.ts +100 -1
  5. package/dist/approval-ask-audit-store.js +103 -2
  6. package/dist/approval-card.d.ts +148 -13
  7. package/dist/approval-card.js +82 -13
  8. package/dist/approval.d.ts +31 -0
  9. package/dist/approval.js +18 -0
  10. package/dist/auto-mode-face.d.ts +111 -0
  11. package/dist/auto-mode-face.js +99 -0
  12. package/dist/bench/s1/arms.js +5 -5
  13. package/dist/bench/s1/live-deps.js +11 -11
  14. package/dist/bench/s1/run-firm.js +3 -0
  15. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  16. package/dist/bench/s1/runner-ctx.js +7 -0
  17. package/dist/boot/config-center.js +23 -1
  18. package/dist/boot/coordinators.js +13 -1
  19. package/dist/boot/parked-revive-gate.d.ts +7 -2
  20. package/dist/boot/parked-revive-gate.js +12 -1
  21. package/dist/boot/resolve-spec.d.ts +3 -0
  22. package/dist/boot/resolve-spec.js +3 -1
  23. package/dist/boot/runner-deps.d.ts +12 -0
  24. package/dist/boot/runner-deps.js +28 -3
  25. package/dist/boot/runtime-caps.d.ts +19 -9
  26. package/dist/boot/runtime-caps.js +49 -21
  27. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  28. package/dist/boot/session-shell-gate-registry.js +21 -0
  29. package/dist/config-catalog.js +14 -5
  30. package/dist/config-types.d.ts +41 -10
  31. package/dist/config.d.ts +21 -0
  32. package/dist/config.js +40 -5
  33. package/dist/http/route-ctx.d.ts +75 -0
  34. package/dist/http/routes/a2a-serve.js +5 -3
  35. package/dist/http/routes/approvals-assistant.js +19 -6
  36. package/dist/http/routes/capabilities.js +21 -4
  37. package/dist/http/routes/memory-origin.d.ts +2 -2
  38. package/dist/http/routes/rules.js +3 -2
  39. package/dist/http/routes/runs.d.ts +0 -14
  40. package/dist/http/routes/runs.js +20 -8
  41. package/dist/http/routes/tasks.js +34 -8
  42. package/dist/http/routes/workflows.js +19 -4
  43. package/dist/http/server.d.ts +11 -1
  44. package/dist/http/server.js +215 -43
  45. package/dist/http/wire-types.d.ts +9 -4
  46. package/dist/leader/diffout.js +2 -1
  47. package/dist/leader/endpoint.js +45 -12
  48. package/dist/leader/fanout.js +6 -5
  49. package/dist/leader/leader.js +17 -14
  50. package/dist/leader/merge.js +17 -12
  51. package/dist/leader/planner.js +3 -2
  52. package/dist/leader/repair-oracle.js +6 -4
  53. package/dist/leader/repair-wire.js +5 -3
  54. package/dist/leader/wire.d.ts +30 -1
  55. package/dist/leader/wire.js +36 -19
  56. package/dist/main.js +43 -7
  57. package/dist/observability/err-text.d.ts +6 -0
  58. package/dist/observability/err-text.js +10 -0
  59. package/dist/observability/fail-open.d.ts +40 -0
  60. package/dist/observability/fail-open.js +19 -0
  61. package/dist/observability/metrics.js +1 -1
  62. package/dist/observability/run-terminal-log.d.ts +120 -0
  63. package/dist/observability/run-terminal-log.js +360 -0
  64. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  65. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  66. package/dist/permission-rule-vocab.d.ts +40 -0
  67. package/dist/permission-rule-vocab.js +17 -0
  68. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  69. package/dist/plugins/checkpoint-store-sql.js +19 -3
  70. package/dist/plugins/file-run-store.d.ts +3 -34
  71. package/dist/plugins/file-run-store.js +19 -0
  72. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  73. package/dist/plugins/local-checkpoint-store.js +3 -0
  74. package/dist/plugins/memory-run-store.d.ts +3 -15
  75. package/dist/plugins/memory-run-store.js +19 -0
  76. package/dist/plugins/permission-rule-store-file.js +8 -3
  77. package/dist/plugins/permission-rule-store-sql.js +16 -10
  78. package/dist/plugins/remote-scratchpad.js +3 -2
  79. package/dist/plugins/run-store-sql.d.ts +3 -42
  80. package/dist/plugins/run-store-sql.js +30 -1
  81. package/dist/plugins/store-backend.d.ts +1 -1
  82. package/dist/plugins/store-contracts.d.ts +66 -1
  83. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  84. package/dist/plugins/workflow-run-store-sql.js +10 -2
  85. package/dist/rules-consent.js +20 -13
  86. package/dist/run-cancel-context.d.ts +13 -0
  87. package/dist/run-cancel-context.js +15 -0
  88. package/dist/run-local.js +2 -2
  89. package/dist/runs.d.ts +4 -1
  90. package/dist/runs.js +41 -10
  91. package/dist/runtime-caps-resolver.d.ts +133 -17
  92. package/dist/runtime-caps-resolver.js +41 -3
  93. package/dist/task-settings.d.ts +57 -3
  94. package/dist/task-settings.js +78 -5
  95. package/dist/task-workflow.d.ts +32 -2
  96. package/dist/task-workflow.js +8 -3
  97. package/dist/tool-approval.d.ts +26 -3
  98. package/dist/tool-approval.js +96 -13
  99. package/dist/trace/core-keyset-guard.d.ts +2 -2
  100. package/dist/trace/ledger-sink.d.ts +13 -4
  101. package/dist/trace/ledger-sink.js +14 -6
  102. package/dist/trace/project.d.ts +13 -0
  103. package/dist/trace/project.js +10 -1
  104. package/dist/trace/redact.d.ts +22 -11
  105. package/dist/trace/redact.js +655 -20
  106. package/dist/trace/sema-provenance.d.ts +26 -0
  107. package/dist/trace/sema-provenance.js +11 -0
  108. package/dist/turn-activity.d.ts +32 -2
  109. package/dist/turn-activity.js +29 -4
  110. package/package.json +2 -2
package/dist/run-local.js CHANGED
@@ -14,7 +14,7 @@ import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js
14
14
  import { taskWallClockSec } from "./task-workflow.js";
15
15
  import { applyCatalogToSource } from "./capabilities/center-prompts.js";
16
16
  import { validatePromptsDomain, CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
17
- import { loadConfig, logConfigDiagnostics, numEnv } from "./config.js";
17
+ import { loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./config.js";
18
18
  import { createConfigProvider } from "./config-provider.js";
19
19
  import { applyEffective, applyCenterReadFace, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./config-center/facade.js";
20
20
  import { hostExecutionEnvFactory } from "./plugins/remote-env-host.js";
@@ -445,7 +445,7 @@ export async function runLocal(argv, deps = {}) {
445
445
  }
446
446
  const mention = parseModelMention(args.objective, Object.keys(config.models));
447
447
  const scope = args.user ?? "local";
448
- const taskTimeoutSec = Math.max(0, Math.floor(numEnv("TASK_TIMEOUT_SEC", "0")));
448
+ const taskTimeoutSec = Math.floor(parseNumOrFailNonNegative("TASK_TIMEOUT_SEC", process.env.TASK_TIMEOUT_SEC || "0"));
449
449
  const timeoutSec = taskWallClockSec(taskTimeoutSec, false, scenarioName === "discuss");
450
450
  const mcp = mcpForScenario(config.mcpServers, scenarioName);
451
451
  const a2a = a2aForScenario(config.a2aPeers, scenarioName);
package/dist/runs.d.ts CHANGED
@@ -308,10 +308,13 @@ promptManifests?: PromptManifestTracker,
308
308
  * `streamApprovalOn` = 协议上场判据(`resolveStreamApprovalGate`,由路由层求值后传进来 —— 本函数是纯
309
309
  * 执行腿,不该自己读 backend/config)。为假 ⇒ 只接既有四键 ctx(呈卡口/腿轴都不接),本腿字节逐字不变。
310
310
  */
311
+ /** `windowMs` 收成 `number | undefined`([ref] + codex r1-[medium]):`undefined` = **这份装配没有
312
+ * `streamApproval` 段**(stub-harness),不是「运维显式关窗」—— 两者自 [ref] 起结局不同(后者即答
313
+ * 无人值守终局),路由层因此不许再折算,见 `resolveApprovalLeg` 的 `windowMs` 域注。 */
311
314
  approval?: {
312
315
  coordinator: ToolApprovalCoordinator;
313
316
  streamApprovalOn: boolean;
314
- windowMs: number;
317
+ windowMs: number | undefined;
315
318
  windowMarginMs: number;
316
319
  },
317
320
  /** [ref]:park 投影腿的 checkpoint 读口(`deps.checkpointStore`)。在场时,durable park 的
package/dist/runs.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import { runWithVerification, runCascade, defaultTaskRegistry, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
2
+ import { redactErrorMessage } from "./observability/run-terminal-log.js";
2
3
  import { withPrincipal } from "./observability/principal-context.js";
3
4
  import { redactSecrets } from "./trace/redact.js";
4
5
  import { taskNotificationEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "./trace/project.js";
5
6
  import { createLedgerSink } from "./trace/ledger-sink.js";
6
7
  import { registerEngineNoticeLeg } from "./trace/engine-notice-wire.js";
7
8
  import { recordTurnActivity } from "./turn-activity.js";
9
+ import { buildCancelContext } from "./run-cancel-context.js";
8
10
  import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
9
11
  import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
10
12
  import { defaultSubagentTailBus, projectTailFrame } from "./fleet/subagent-tail-bus.js";
11
13
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
12
14
  import { recordFailOpen } from "./observability/fail-open.js";
15
+ import { errorText as errText } from "./observability/err-text.js";
13
16
  import { LIVENESS_HEARTBEAT_MS } from "./config-invariants.js";
14
17
  export async function backgroundAgentOutput(registry, handle, access, agentStore) {
15
18
  const row = registry.getAccessibleTask(handle, access);
@@ -162,7 +165,10 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
162
165
  const preemptCtrl = new AbortController();
163
166
  if (preemptEligible)
164
167
  preemptable?.set(taskId, preemptCtrl);
165
- const cancelledResult = () => ({ taskId, sessionId: spec.sessionId, status: "failed", errorCode: "cancelled", errorMessage: "cancelled by user", stats: { turns: 0, tokens: 0 } });
168
+ const cancelledResult = () => {
169
+ const ctx = buildCancelContext(taskId, startedAt);
170
+ return { taskId, sessionId: spec.sessionId, status: "failed", errorCode: "cancelled", errorMessage: "cancelled by user", stats: { turns: 0, tokens: 0 }, ...(ctx ? { cancelContext: ctx } : {}) };
171
+ };
166
172
  const recordDone = (result, status) => {
167
173
  metrics?.addGauge("runs_active", -1);
168
174
  metrics?.inc("tasks_total", { status });
@@ -178,7 +184,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
178
184
  const owner = principal ?? null;
179
185
  const approvalLeg = resolveApprovalLeg({
180
186
  streamApprovalOn: approval?.streamApprovalOn === true,
181
- windowMs: approval?.windowMs ?? 0,
187
+ windowMs: approval?.windowMs,
182
188
  windowMarginMs: approval?.windowMarginMs ?? 0,
183
189
  ...(typeof spec.limits?.maxWalltimeMs === "number" ? { legWalltimeMs: spec.limits.maxWalltimeMs } : {}),
184
190
  nowMonotonicMs: performance.now(),
@@ -221,7 +227,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
221
227
  metrics?.addGauge("runs_active", -1);
222
228
  metrics?.inc("tasks_total", { status: "needs_review" });
223
229
  };
224
- const sink = createLedgerSink({ appendEvent: (seq, type, data) => runStore.appendEvent(taskId, seq, type, data), persistThinking, notifiedKeys, onActivity: () => recordTurnActivity(taskId) });
230
+ const sink = createLedgerSink({ appendEvent: (seq, type, data) => runStore.appendEvent(taskId, seq, type, data), persistThinking, notifiedKeys, onActivity: (kind, statusData) => recordTurnActivity(taskId, kind, statusData) });
225
231
  const append = (type, data) => sink.append(type, data);
226
232
  const flush = () => sink.flush();
227
233
  const flushModelUsage = () => appendModelUsageDelta(append, modelUsage, taskId);
@@ -410,18 +416,33 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
410
416
  : withApproval();
411
417
  });
412
418
  if (!reached && cancelCtrl.signal.aborted) {
413
- await flush();
419
+ try {
420
+ await flush();
421
+ }
422
+ catch (e) {
423
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-clean-cancel ${errText(e)}`);
424
+ }
414
425
  const c = cancelledResult();
415
- await append("failed", { errorMessage: c.errorMessage ?? "cancelled by user", errorCode: "cancelled" });
426
+ try {
427
+ await append("failed", { errorMessage: c.errorMessage ?? "cancelled by user", errorCode: "cancelled" });
428
+ }
429
+ catch (e) {
430
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-clean-cancel ${errText(e)}`);
431
+ }
416
432
  reached = { kind: "done", result: c };
417
433
  await runStore.setTerminal(taskId, "failed", c, c.errorMessage ?? null);
418
434
  recordDone(c, "cancelled");
419
435
  }
420
436
  }
421
437
  catch (err) {
422
- const message = err instanceof Error ? err.message : String(err);
438
+ const message = errText(err);
423
439
  try {
424
- await flush();
440
+ try {
441
+ await flush();
442
+ }
443
+ catch (e) {
444
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=flush-catch ${errText(e)}`);
445
+ }
425
446
  if (reached?.kind === "done") {
426
447
  await runStore.setTerminal(taskId, reached.result.status, reached.result, reached.result.errorMessage ?? null);
427
448
  }
@@ -433,12 +454,22 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
433
454
  }
434
455
  else if (cancelCtrl.signal.aborted) {
435
456
  const c = cancelledResult();
436
- await append("failed", { errorMessage: c.errorMessage ?? "cancelled by user", errorCode: "cancelled" });
457
+ try {
458
+ await append("failed", { errorMessage: c.errorMessage ?? "cancelled by user", errorCode: "cancelled" });
459
+ }
460
+ catch (e) {
461
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-thrown-cancel ${errText(e)}`);
462
+ }
437
463
  await runStore.setTerminal(taskId, "failed", c, c.errorMessage ?? null);
438
464
  }
439
465
  else {
440
- const safeMsg = redactSecrets(message);
441
- await append("failed", { errorMessage: safeMsg });
466
+ const safeMsg = redactErrorMessage(message) ?? message;
467
+ try {
468
+ await append("failed", { errorMessage: safeMsg });
469
+ }
470
+ catch (e) {
471
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=append-failed ${errText(e)}`);
472
+ }
442
473
  await runStore.setTerminal(taskId, "failed", null, safeMsg);
443
474
  }
444
475
  }
@@ -6,9 +6,35 @@
6
6
  * `EntitlementRuntimeCaps` themselves stay as-is below: they are core's / registry-core's own contract type
7
7
  * names, not ours to rename.)
8
8
  * It is the ENFORCE last-link of the three-stage workflows gate
9
- * (engine-can ∧ center-may ∧ shell-show): core calls it ONCE per task at prepare time with `spec.principal`,
10
- * and the engine enforces the two caps it CAN enforce — `allowWorkflows` (deny self-orchestration) and
11
- * `forceDurableGate` (force a policy `ask` onto the durable-approval wire).
9
+ * (engine-can ∧ center-may ∧ shell-show): core calls it with `spec.principal`.
10
+ *
11
+ * ⚠️ **调用次数不是「每任务一次」**([ref]① codex r1-[medium] 亲验后纠:旧句写「ONCE per task at
12
+ * prepare time」):prepare 期解析一次(prepare-task.js:1826),**并且** capture-opt-out 的 flip 动词每被打一次
13
+ * 就再解析一次(prepare-memory.js:572 的 `flip` → :538 `resolveCaptureEntitlementFresh` → :542 本 resolver)。
14
+ * 所以本 resolver 必须是**可重入、可多次调用**的,缓存的 TTL 语义对 flip 腿同样生效;把它当「每任务一次的
15
+ * 快照」来实现(例如在里面攒 per-task 状态)会在 flip 腿上出错。
16
+ *
17
+ * ⚠️ [ref]① 纠失实:核心那句曾写作「引擎只执法它能执法的**两**个 cap」—— 早已不属实。core 7.2.0
18
+ * 装树 dist **亲读**的消费点是**六**个(本仓 node_modules 实测;下列坐标除注明外均在 `dist/core/runner/` 下,
19
+ * `agents/subagent.js` 那一条已就地标出 —— 说「均在 runner 下」是 r1 抓到的第二处过头话):
20
+ * · `allowWorkflows` — prepare-task.js:1917 `runtimeCaps?.allowWorkflows !== false`(deny 自编排;:1918-1919 另发 onError)
21
+ * · `allowFork` — prepare-task.js:1867 `forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork)`(+:2418 拒由,agents/subagent.js:1575 拒词)
22
+ * · `allowObservers` — prepare-task.js:1868 `=== true`(**极性与 allow* 相反**:显式 opt-in,默认 OFF)
23
+ * · `forceDurableGate`— prepare-task.js:1644/1646 的 durable/content mandate、:2337 活 content-ask 抑制、
24
+ * :2365 durable question face(该行合取 `resolveCheckpointStore(spec, deps) !== undefined`
25
+ * ⇒ **无 checkpoint 店的部署上这一面本就不成立**,该键在那里 inert)
26
+ * · `autoMode` — prepare-task.js:1869 `=== true` **∧** `deps.autoMode !== undefined`(部署需先具备分类器;
27
+ * 显式 opt-in 极性,且 [ref] 起本侧传的是三态 org deny 位,见 toCoreRuntimeCaps 头注)
28
+ * · `allowMemoryOptOut` — prepare-memory.js:542-543 `resolveCaptureEntitlementFresh`(+prepare-task.js:2474-2475
29
+ * 的 value/faulted 投影);**极性反转,但只在 `open` 姿态下**——判决全表在
30
+ * `dist/core/runner/memory-capture-optout.js` 的 `adjudicateCaptureOptOut`,按
31
+ * `RunnerDeps.memoryCapturePolicy` 三姿态分叉(codex r1-[medium] 亲验后纠:旧句无条件
32
+ * 写「fault 读作 ALLOW」,只对其中一姿态成立):
33
+ * · `"open"`(缺省):1 只有显式 `false` 拒;**fault ⇒ ALLOW** + `faultDisclosure`(:32-37)
34
+ * · `"governed"` :14-23 要**显式 `true`** 才准;`false` / 缺席 / fault **一律拒**
35
+ * (拒由分「answered false」与「verdict absent / resolver fault」两句)
36
+ * · `"capture-required"`:4-12 **完全不看本 verdict**(逐字「resolver-independent」),恒拒
37
+ * 只有 `allowUltracode` 是被 DROP 的那一个(下文)。
12
38
  *
13
39
  * It pulls the PER-PRINCIPAL entitlement from center's `GET /api/config/effective?principal=` (via
14
40
  * {@link fetchPrincipalCaps}). center has ALREADY pre-resolved the tighten-only intersection (fleet defaults ∩
@@ -17,10 +43,16 @@
17
43
  * a per-principal TTL+ETag CACHE (so per-task prepare-time lookups don't hammer center) and FAIL-CLOSED degrade.
18
44
  *
19
45
  * 🔐 FAIL-CLOSED (core security mandate, prepare-task.ts): an entitlement-resolver outage must NEVER silently
20
- * GRANT a capability center may have denied. On a hard fetch error we return `{ allowWorkflows: false }` (deny the
21
- * OPTIONAL workflows privilege) and do NOT force the durable gate (that could strand an autonomous run on an
22
- * absent operator the same minimal degrade core applies when the resolver throws). We never serve a STALE
23
- * cached GRANT past its TTL on a fresh-fetch failure (a revoked cap must not linger because center blipped).
46
+ * GRANT a capability center may have denied. On a hard fetch error we mint `{ allowWorkflows: false, allowFork: false,
47
+ * autoMode: false }` — the three OPTIONAL **amplification** caps and do NOT force the durable gate (that could
48
+ * strand an autonomous run on an absent operator). We never serve a STALE cached GRANT past its TTL on a
49
+ * fresh-fetch failure (a revoked cap must not linger because center blipped).
50
+ * ⚠️ [ref]① 纠失实:这段曾写作「降级为 `{ allowWorkflows: false }`」,与真码不符 —— 真铸点在本文件
51
+ * `load()` 的 catch 臂(搜 `Caps face FAIL CLOSED`),逐字是上行那三键:`allowFork` 自提货批就在,
52
+ * `autoMode` 由 [ref] / core [ref] 同车加入(org deny 位不能因一次抖动在 denyTtl 窗内抬起)。
53
+ * 🔒 **三键以外的那三键刻意不铸**:`forceDurableGate` 同上(强挂人在环会搞死无人值守的 run);
54
+ * `allowObservers` 是 `=== true` opt-in,缺席已等于关;`allowMemoryOptOut` 是隐私轴的 FAULT-TRANSIT 契约
55
+ * (core types.d.ts 逐字禁止在 fault 上自造一个显式值)—— 铸 false 就是把故障臂默默换成判决臂。
24
56
  *
25
57
  * The resolver maps center's `EntitlementRuntimeCaps` → the SUBSET core enforces ({@link RuntimeCaps}): center
26
58
  * also carries `allowUltracode` (a shell/UX reasoning-tier cap the engine has no primitive for), which we DROP —
@@ -55,8 +87,11 @@ export interface EntitlementsResolverOpts {
55
87
  onError?: (err: Error, principal: string) => void;
56
88
  }
57
89
  /** Map center's `EntitlementRuntimeCaps` → the subset core's engine enforces. `allowUltracode` (shell/UX) is
58
- * dropped on purpose (not a core primitive); `allowWorkflows` / `forceDurableGate` / `allowFork` are the three the
59
- * engine enforces. Returns undefined when no enforceable cap is set (= no restriction). FAIL-CLOSED defense-in-depth:
90
+ * dropped on purpose (not a core primitive); `allowWorkflows` / `forceDurableGate` / `allowFork` are **three of the
91
+ * six** the engine reads the other three (`allowObservers` / `autoMode` / `allowMemoryOptOut`) are documented
92
+ * below and forwarded by the same body; the full consumption-point table with core 7.2.0 dist coordinates is in this
93
+ * module's header ([ref]①: this line used to say 「the three」, i.e. claimed the set was closed at three).
94
+ * Returns undefined when no enforceable cap is set (= no restriction). FAIL-CLOSED defense-in-depth:
60
95
  * do NOT trust the wire to be boolean. center serializes these as zod-validated booleans, but a schema drift / proxy
61
96
  * mangling that sent a TRUTHY non-boolean (e.g. the STRING "false") would slip past core's strict `=== false` deny
62
97
  * check and silently GRANT. So a cap that is PRESENT but not a real boolean is a contract breach → THROW (the
@@ -70,11 +105,20 @@ export interface EntitlementsResolverOpts {
70
105
  * 说过话」的显式判决,污染 env-baseline 合成序,见 applyObserverEnvOptIn)。非 boolean 虽然骗不过 core
71
106
  * 的 `=== true`(不会误 GRANT),但 PRESENT-非-boolean 与 allow* 同属同一 body 的契约破损信号 → 同款 THROW。
72
107
  *
73
- * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`):auto-mode 分类器 per-principal
74
- * 开闸线,极性同 allowObservers(`=== true` 显式 opt-in,默认 OFF;真执行还要 deployment 装配
75
- * `RunnerDeps.autoMode` 分类器面=信任门,center 键只是 entitlement 半场)。[ref]B-6a:registry-core
76
- * 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps(types.d.ts:1786)——宽读逃逸期结束,现为类型直读
77
- * (allowObservers 同款收编先例)。 */
108
+ * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`;**[ref] / core [ref] 起三态**,黑板 [ref]/[ref]):
109
+ * `undefined`(center 缺席/未答)= 不阻;`true` = 不阻;`false` = **org deny**(CC `permissions.disableAutoMode`
110
+ * 镜像)。core 武装式 = `spec.autoModeRequested === true ∧ deps.autoMode !== undefined runtimeCaps?.autoMode !== false`
111
+ * ——「用户开 + org 拒」极性(此前是 `=== true` 的「org 授予」极性,无 center 的本地部署零授予路径 ⇒ auto 永不
112
+ * 武装,真实用户诊断的病根)。
113
+ * 🔴 **上面那条武装式是 [ref] 的目标形,不是「本仓现在装的那版 core 的形」——引用它之前先看装的是哪版**
114
+ * ([ref]① 随 codex r2-[high] 补:我方在 WIRE 附录 F 照抄这一句,写成了「7.2.0 的行为」,而 npm **7.2.0
115
+ * 不含 [ref]** —— 它的 `dist/core/runner/prepare-task.js:1869` 逐字仍是 `runtimeCaps?.autoMode === true &&
116
+ * deps.autoMode !== undefined`,即旧「org 授予」极性、**缺键 ⇒ 不武装**)。判「现在是哪套极性」的唯一判据 =
117
+ * 安装包探针 `CORE_ARMS_AUTO_ON_INTENT`(`src/auto-mode-face.ts`;实测本树 7.2.0 ⇒ **false** = 旧式臂),
118
+ * 代码侧已按它分臂,失实的只是散落各处的散文。⚠️ 同形措辞在本文件 :209 / :362-364 / :548 与
119
+ * `src/boot/runtime-caps.ts:182` 还各有一处(均 [ref] 既有文字,本批未逐条改 —— 登记在 handoff 未闭环项)。
120
+ * 所以**缺键=不写键**在这条轴上是行为承重的(在新式 core 上):补一个 false 就是替 org 说了一句
121
+ * 它没说的 deny;在旧式 core 上两者同为「不武装」,差别不显形。[ref]B-6a:registry-core 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps——类型直读。 */
78
122
  export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | undefined): RuntimeCaps | undefined;
79
123
  /**
80
124
  * #A5([ref] 件2)—— 「这台 worker 上**存在** per-principal entitlement 源吗」的**单一谓词**。
@@ -87,8 +131,9 @@ export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | u
87
131
  * `permissionModeAuto.entitlementSource` 披露位。两处各写一份条件,就是本仓在册的
88
132
  * 「诊断页说亮、能力位说暗」病族的入口(装配与披露对同一件事各算各的)。
89
133
  *
90
- * ⚠️ **辖域**:说的是「源在不在」,不是「某个 principal 被授权了」。授权是 per-principal 的
91
- * (`RuntimeCaps.autoMode === true` principal ),任何部署级布尔都对它说不了话。
134
+ * ⚠️ **辖域**:说的是「源在不在」,不是「某个 principal 被授权/被拒了」。org deny 位是 per-principal 的
135
+ * (`RuntimeCaps.autoMode` 三态逐 principal 解,[ref] 起「缺席=不阻」),任何部署级布尔都对它说不了话——
136
+ * 所以本谓词为 false **不再**意味着 auto 不武装;「会不会武装」由 `auto-mode-face.ts` 的三项合取答。
92
137
  */
93
138
  export declare function centerEntitlementSourceWired(config: Pick<ServiceConfig, "configCenter">): boolean;
94
139
  /** A MISCONFIG diagnostic (review MEDIUM): a worker-scoped `wpt_<self>` pull token REQUIRES `?worker=<self>` to
@@ -99,8 +144,39 @@ export declare function centerEntitlementSourceWired(config: Pick<ServiceConfig,
99
144
  export declare function scopedTokenNeedsWorker(token: string, worker: string | undefined): boolean;
100
145
  /** The two per-principal faces served off ONE caps fetch + cache. */
101
146
  export interface PrincipalEntitlementsClient {
102
- /** core's `RunnerDeps.runtimeCapsResolver` seam — FAIL-CLOSED on error (deny workflows/fork). */
147
+ /** core's `RunnerDeps.runtimeCapsResolver` seam — FAIL-CLOSED on error (deny workflows/fork/autoMode;
148
+ * [ref]① 随批纠:此处是同一句降级声明的第二个写者,漏了 [ref] 加的 `autoMode`。真铸点同上)。 */
103
149
  resolveRuntimeCaps: (principal: string | undefined) => Promise<RuntimeCaps | undefined>;
150
+ /**
151
+ * 🔴 [ref] —— **只读**腿:与 {@link PrincipalEntitlementsClient.resolveRuntimeCaps} **同一只**缓存/同一条抓取/同一份
152
+ * fail-closed 判决,唯一的差别是 center 硬失败时它铸的降级项**只对自己算数**,提交腿不认。
153
+ *
154
+ * 病灶(合并重扫 finding):`/v1/capabilities` 的 auto 自查读面用的是提交腿那只 resolver,于是**一次 GET**
155
+ * 在 center 抖动的瞬间就会替这个 principal 铸下一条 `{allowWorkflows:false, allowFork:false, autoMode:false}`
156
+ * 的短窗 deny —— 随后 `denyTtlMs` 窗内该 principal 的**真提交**被这条读面留下的项拒掉。读面是纯披露,不该有
157
+ * 写副作用,更不该是别人任务被拒的原因。
158
+ *
159
+ * 姿态选择(为什么不是「读面改答 resolver_fault」):判决**逐字不变** —— 读面回答的是「此刻提交会不会武装」,
160
+ * 而 center 持续故障时提交腿确实看到 fail-closed 的 deny,所以答 `org_denied` 是当下真值;本修只摘掉副作用,
161
+ * 不改词、不改极性(`onError` 审计行照打,故障照样有可观察面)。
162
+ *
163
+ * 🔴 **界画在「这句话是谁说的」**(codex r2 [high] 逐条核后的定界,不是「读面完全不碰缓存」):
164
+ * · **成功项 = center 亲口答的那份** —— 与提交腿自己抓到的**逐字同一份**、同一条 `ttlMs`、同一个**已验证**
165
+ * 身份(读面的 principal 走 `gatedPrincipal` → `verifiedPrincipal`,与提交腿同源,读不到别人的格)。读面
166
+ * 只是把这个 principal 自己那一格**预热**了一下;org 撤销后的陈旧窗由部署级 `ttlMs` 定义,与哪一腿预热
167
+ * 的无关(提交腿自己预热同样如此),本批**零改动**。
168
+ * · **降级项 = server 自铸的判决** —— center 从没说过这句 deny,是本层在故障时替它说的。让一次**读**铸的
169
+ * 这句话去拒掉别人的**提交**,才是本修要摘的那件事。两者的差别不是「值一不一样」,是「谁是作者」。
170
+ *
171
+ * ⚠️ 两条缓存纪律(缺一就换出另一个病):
172
+ * · **成功抓取照旧落缓存**,且命中提交腿刚拉的那份即用 —— 读面不是缓存旁路;
173
+ * · **硬失败的降级项照旧落缓存,但标出处**(`readMintedDeny`):只读腿自己认(否则 center 持续故障时每次
174
+ * GET 一个 8s RTT,读面成了对着故障 center 的放大面),提交腿忽略它、照常重抓。
175
+ * in-flight 去重与提交腿共用(同 principal 的并发只一次往返);提交腿**亲历**了这次抓取(自起 or 搭在途)
176
+ * 而它硬失败 ⇒ 该降级项当场升格为执法项(codex r1 [medium]:否则一条总抢在提交前面的轮询会让每次提交
177
+ * 都重抓,denyTtl 的界对提交腿失效)。让它升格的是一次真提交自己吃到的硬失败,不是那次读。
178
+ */
179
+ peekRuntimeCaps: (principal: string | undefined) => Promise<RuntimeCaps | undefined>;
104
180
  /** The resolved per-principal scenario ruling for resolveSpec's gateScenarioRequest —
105
181
  * FAIL-OPEN on error / older center without scenario keys / no principal (undefined = no governance, legacy passthrough).
106
182
  * Scenario switching is not a privilege-amplifying face; a center blip must not pin users to `default`. */
@@ -170,6 +246,33 @@ export interface MemoryOptOutVerdictSource {
170
246
  export declare function applyMemoryOptOutGrant(base: EntitlementsResolverFn | undefined, source: MemoryOptOutVerdictSource | undefined, opts: {
171
247
  onFault: (err: Error, principal: string | undefined) => void;
172
248
  }): EntitlementsResolverFn | undefined;
249
+ /**
250
+ * [ref] —— `governed` 姿态 + **center-only** verdict 源那一形的**运行期**披露(纯观测,零判决改写)。
251
+ *
252
+ * 为什么单独存在:`allowMemoryOptOut` 在 settings-schema 的 `EntitlementRuntimeCaps` 里是**可选键**
253
+ * (`z.ZodOptional<z.ZodBoolean>`),而 {@link toCoreRuntimeCaps} 对缺席**不铸键**(FAULT-TRANSIT:缺席
254
+ * 原样过境,绝不铸 false)。于是「配了一个非 dry-run 的 center」只证**源在场**,不证 center 会**为这个
255
+ * principal 发这个键** —— 一台 center 从不发该键的 governed worker 会正常起动,然后把每一个
256
+ * `memory.capture:"off"` 都以 `memory.capture_optout_denied`(verdict absent)拒掉,而运维只看见「opt-out 全拒」。
257
+ * 启动期那一行 warn 说的是「这一形有风险」;这一行说的是「它**真的**发生了」。
258
+ *
259
+ * 姿态:**每因由一次**(防每任务刷屏)、**不改判**(caps 逐字返回,极性仍由 core 按姿态判)、
260
+ * 只在 `enabled`(= governed ∧ 无本地 grant 店 ∧ center 源在场)时挂;否则返回 **同一引用**(零行为差)。
261
+ * 缺席判据取最终合成结果:`typeof caps?.allowMemoryOptOut !== "boolean"` —— center 没发键、center 拉取
262
+ * 降级(fail-closed entry 不含本键)都算,两者对 core 是同一件事(verdict absent ⇒ 拒跑)。
263
+ *
264
+ * 🔴 **因由是闭集两员,各占各的格**(codex r1-[medium] 验真后修):`createPrincipalEntitlementsClient` 的
265
+ * `resolveRuntimeCaps` 对 `principal === undefined` **直接返回 undefined、根本不问 center**(匿名/系统 run
266
+ * 没有 per-principal 身份),而 core 对**每一个**任务都调这只 resolver(不只是带 opt-out 声明的那些)。
267
+ * 单格去重会让**第一个匿名任务**烧掉那一格,日志却说「center 省了键」——归因错、且此后真正的具名缺键
268
+ * 永不留证。两员分格,且两句话说的是两件不同的事:匿名腿在 center-only 源上**结构性**永远拿不到 verdict
269
+ * (本地 grant 表的部署缺省行才答匿名),具名腿则是「center 这次没发这个可选键」。
270
+ */
271
+ export type GovernedVerdictAbsenceReason = "anonymous_lane" | "center_verdict_absent";
272
+ export declare function applyGovernedVerdictAbsenceWarning(base: EntitlementsResolverFn | undefined, opts: {
273
+ enabled: boolean;
274
+ warn: (reason: GovernedVerdictAbsenceReason, principal: string | undefined) => void;
275
+ }): EntitlementsResolverFn | undefined;
173
276
  /**
174
277
  * observer 开闸线的 env 半场:`EXPERIMENTAL_OBSERVER_AGENTS=true` 时把
175
278
  * `RuntimeCaps.allowObservers: true` 作为**部署基线**合成进 caps resolver(core 1.270.0/1.273.0 的
@@ -193,4 +296,17 @@ export declare function applyObserverEnvOptIn(centerResolver: EntitlementsResolv
193
296
  requirePrincipal: boolean;
194
297
  warn?: (msg: string, fields?: Record<string, unknown>) => void;
195
298
  }): EntitlementsResolverFn | undefined;
299
+ /**
300
+ * [ref] ③(黑板 [ref]/[ref],core [ref] 合修)—— `PERMISSIONS_DISABLE_AUTO_MODE` 的合成层:CC
301
+ * `permissions.disableAutoMode` 的本地镜像(ANCHOR-250 §6.4 的只可更严棘轮),**tighten-only**:
302
+ * · deny=true ⇒ 每个 principal 的 `caps.autoMode` 折成 `false`(org deny 位)。center 授予 `true` **翻不回**——
303
+ * 本层写在最外(boot/runtime-caps.ts 合成序:center → observer env 基线 → memory grant → 本层),后写者赢,
304
+ * 且只往严处写;center 对该 principal 无答(undefined)同样折 false(deny 是部署级表态,不看 principal)。
305
+ * · deny 未设 ⇒ 原 resolver **同一引用**透传(零行为差;无 center 的部署仍是 `undefined` ⇒ caps 缺席 ⇒
306
+ * 三态之「缺席=不阻」,这正是 auto 在本地部署能武装起来的路径)。
307
+ * · 极性与 applyObserverEnvOptIn 相反:那条是「env 开」的放宽基线(center 带键则 center 赢),本条是
308
+ * 「env 拒」的收紧棘轮(center 带键也不能赢)。两条不共用一只函数正是因为方向相反。
309
+ * 🔴 没有 fail-open 臂:本层是纯合成,不做 I/O;base 抛错原样上抛(base 各层自带 fail-closed/缺席臂)。
310
+ */
311
+ export declare function applyLocalAutoModeDeny(base: EntitlementsResolverFn | undefined, deny: boolean): EntitlementsResolverFn | undefined;
196
312
  //# sourceMappingURL=runtime-caps-resolver.d.ts.map
@@ -80,6 +80,7 @@ export function createPrincipalEntitlementsClient(opts) {
80
80
  const onError = opts.onError ?? ((err, principal) => defaultLogger.warn("runtime_caps_resolve_failed", { principal, err: String(err) }));
81
81
  const cache = new Map();
82
82
  const inflight = new Map();
83
+ const readMintedDeny = new WeakSet();
83
84
  const MAX_CACHE = 10_000;
84
85
  const putCache = (key, entry) => {
85
86
  if (cache.size >= MAX_CACHE) {
@@ -90,9 +91,15 @@ export function createPrincipalEntitlementsClient(opts) {
90
91
  }
91
92
  cache.set(key, entry);
92
93
  };
93
- async function load(principal) {
94
+ async function load(principal, recordDeny = true) {
95
+ const entry = await loadRaw(principal, recordDeny);
96
+ if (recordDeny)
97
+ readMintedDeny.delete(entry);
98
+ return entry;
99
+ }
100
+ async function loadRaw(principal, recordDeny) {
94
101
  const cached = cache.get(principal);
95
- if (cached && cached.expiresAt > now())
102
+ if (cached && cached.expiresAt > now() && !(recordDeny && readMintedDeny.has(cached)))
96
103
  return cached;
97
104
  const dup = inflight.get(principal);
98
105
  if (dup)
@@ -135,7 +142,9 @@ export function createPrincipalEntitlementsClient(opts) {
135
142
  catch {
136
143
  e = new Error("center fetch failed with an unstringifiable rejection reason");
137
144
  }
138
- const entry = { caps: { allowWorkflows: false, allowFork: false }, expiresAt: now() + denyTtlMs };
145
+ const entry = { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, expiresAt: now() + denyTtlMs };
146
+ if (!recordDeny)
147
+ readMintedDeny.add(entry);
139
148
  putCache(principal, entry);
140
149
  try {
141
150
  onError(e, principal);
@@ -152,6 +161,7 @@ export function createPrincipalEntitlementsClient(opts) {
152
161
  }
153
162
  return {
154
163
  resolveRuntimeCaps: async (principal) => (principal ? (await load(principal)).caps : undefined),
164
+ peekRuntimeCaps: async (principal) => (principal ? (await load(principal, false)).caps : undefined),
155
165
  scenarioRuling: async (principal) => (principal ? (await load(principal)).scenario : undefined),
156
166
  executionRuling: async (principal) => (principal ? (await load(principal)).execution : undefined),
157
167
  };
@@ -183,6 +193,27 @@ export function applyMemoryOptOutGrant(base, source, opts) {
183
193
  return { ...(caps ?? {}), allowMemoryOptOut: allow };
184
194
  };
185
195
  }
196
+ export function applyGovernedVerdictAbsenceWarning(base, opts) {
197
+ if (!opts.enabled)
198
+ return base;
199
+ const warned = new Set();
200
+ return async (principal) => {
201
+ const caps = base !== undefined ? await base(principal) : undefined;
202
+ if (typeof caps?.allowMemoryOptOut !== "boolean") {
203
+ const reason = principal === undefined ? "anonymous_lane" : "center_verdict_absent";
204
+ if (!warned.has(reason)) {
205
+ warned.add(reason);
206
+ try {
207
+ opts.warn(reason, principal);
208
+ }
209
+ catch (hookErr) {
210
+ recordFailOpen("server.memory-optout-verdict-absence.warn-hook-threw", String(hookErr));
211
+ }
212
+ }
213
+ }
214
+ return caps;
215
+ };
216
+ }
186
217
  export function applyObserverEnvOptIn(centerResolver, opts) {
187
218
  if (!opts.experimentalObserverAgents)
188
219
  return centerResolver;
@@ -196,4 +227,11 @@ export function applyObserverEnvOptIn(centerResolver, opts) {
196
227
  return () => ({ allowObservers: true });
197
228
  return async (principal) => ({ allowObservers: true, ...(await centerResolver(principal)) });
198
229
  }
230
+ export function applyLocalAutoModeDeny(base, deny) {
231
+ if (!deny)
232
+ return base;
233
+ if (base === undefined)
234
+ return () => ({ autoMode: false });
235
+ return async (principal) => ({ ...(await base(principal)), autoMode: false });
236
+ }
199
237
  //# sourceMappingURL=runtime-caps-resolver.js.map
@@ -66,6 +66,9 @@ export type SettingsPermissionMode = "default" | "acceptEdits" | "plan" | "bypas
66
66
  * two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
67
67
  */
68
68
  export declare function coercePermissionMode(raw: unknown): SettingsPermissionMode | undefined;
69
+ /** 五模式闭集的**严格**读法([ref] ④:`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,
70
+ * 不走上面那条给 resume 重放留的 `default` 宽臂)。上面的宽读只是本函数外再包一层折叠,词表单点在此。 */
71
+ export declare function coercePermissionModeStrict(raw: unknown): SettingsPermissionMode | undefined;
69
72
  /** L2 ultracode ([ref]): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
70
73
  * preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
71
74
  * (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
@@ -129,6 +132,18 @@ export interface ParsedTaskSettings {
129
132
  /** Carried for completeness; v1 folds `ask` into the gate via the deny-wins base (see {@link applyTaskSettings}). */
130
133
  ask?: string[];
131
134
  defaultMode?: SettingsPermissionMode;
135
+ /** [ref] 件⑥([ref] §3.4,[ref];PM 裁 [ref]):CC settings 的 kill-switch `permissions.disableAutoMode`,cli 投影
136
+ * (settingsRulesWire.ts toWireSettings)而此前本白名单丢弃 ⇒ 用户/managed 层关 auto 到不了引擎。语义 = 只能关
137
+ * 不能开(tighten-only):把生效模式 auto 折 default(座不写 ⇒ 不武装)。折叠单点 = {@link effectivePermissionMode}。
138
+ * `disableBypassPermissionsMode` 本批不做落点([ref] 另立)。
139
+ *
140
+ * 🔴 **受理集 = `"disable" | true | false`**([ref] 放宽一档,原为「只认字面 `"disable"`」)。两套拼写**都是已发布
141
+ * 契约**:CC 250 的 `Ct` 严极词 `"disable"`,与 `@sema-agent/sdk` 8.1.0 `SettingsPermissions.disableAutoMode?: boolean`
142
+ * (`dist/settings.d.ts`)。按已发布 SDK 类型写 `true`/`false` 的客户端此前一律 400 —— [ref] 的「只认 disable」是
143
+ * 「拒非法**拼错**」,boolean 不是拼错。归一化**不做**:三形逐字过 parse(拒体/日志里看得见调用方真发的是哪一形),
144
+ * 语义归一只在折叠单点做(`"disable"` ≡ `true`)。受理集之外(别的词/数字/对象)= fresh 提交 HTTP 门 400,
145
+ * resume 重放宽臂丢弃(两层姿势同 defaultMode)。 */
146
+ disableAutoMode?: "disable" | boolean;
132
147
  };
133
148
  model?: string;
134
149
  outputStyle?: string;
@@ -164,12 +179,51 @@ export declare const MAX_SETTINGS_PERMISSION_RULES = 1000;
164
179
  export declare const MAX_SETTINGS_ENV_VARS = 256;
165
180
  export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
166
181
  export declare const MAX_SETTINGS_ENV_KEY_CHARS = 256;
182
+ /** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
183
+ * `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode` 在 {@link parseTaskSettings};
184
+ * `webSearch` 在 `capabilities/scenarios.ts`(`webSearchConfigFromSettings`,单用户闸)。
185
+ * 加键必须同时加消费点,否则就是往闭集里塞一个新的静默丢。 */
186
+ export declare const TASK_SETTINGS_KEYS: readonly ["env", "hooks", "model", "outputStyle", "permissions", "ultracode", "webSearch"];
187
+ /** 本 service 消费的 `settings.permissions` 子键 —— 闭集(值形校验另有既有门,本门只判键名)。 */
188
+ export declare const TASK_SETTINGS_PERMISSION_KEYS: readonly ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
189
+ /** CC/SDK 合约里合法、本 service **不接线**的顶层键(出处 = `@sema-agent/sdk` `SemaSettings` 尾注逐字点名的
190
+ * 三个 deferred CC 键)。词表外的 CC 键落 `unknown` —— 两列都是 400,标签只影响文案,不影响判决。 */
191
+ export declare const TASK_SETTINGS_UNSUPPORTED_KEYS: readonly ["apiKeyHelper", "sandbox", "statusLine"];
192
+ /** 同上,`settings.permissions` 子层(出处 = SDK `SettingsPermissions` 的三个声明键)。
193
+ * `additionalDirectories` 有**同请求替代面**:顶层 `body.additionalDirectories`([ref],resolve-spec 真消费)。 */
194
+ export declare const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS: readonly ["additionalDirectories", "disableBypassPermissionsMode"];
195
+ /** 拒体里逐字回显的键名条数上限。键名原样回显(不猜语义),但**条数有界** —— 一个塞满键的体不许把 400 响应
196
+ * 放大成流量面。超出部分只报数。 */
197
+ export declare const MAX_SETTINGS_KEY_ECHO = 20;
198
+ /** {@link taskSettingsKeyIssue} 的拒体:一句 wire 文案 + 两张分类键名表(消费端可机读,不必解析文案)。 */
199
+ export interface TaskSettingsKeyIssue {
200
+ message: string;
201
+ /** 词表外的键(拼错/未来键),`settings.x` / `settings.permissions.x` 全路径形,已清洗+截断+排序+封顶。 */
202
+ unknownKeys: string[];
203
+ /** CC/SDK 合约合法但本 service 不接线的键,同形。 */
204
+ unsupportedKeys: string[];
205
+ }
206
+ /**
207
+ * FRESH 提交面的 `settings` 键闭集裁决:顶层或 `permissions` 子层出现受理集之外的键 ⇒ 返回拒体(调用方 400
208
+ * `request.body_shape`);全部受理 ⇒ `null`。
209
+ *
210
+ * 判据边界(有意窄):
211
+ * · 只判**键名**。值形校验(outputStyle 上限 / permissions 数组上限 / env 形 / hooks 契约 / defaultMode 闭集)
212
+ * 仍归各自既有门,本门零改动、零抢话。
213
+ * · `settings` 本身非对象、或 `permissions` 非对象(数组/标量/null)⇒ 本门**不判**,留给既有的
214
+ * 「settings must be an object」/「settings.permissions must be an object」两道门,免得同一个体被两句话抢答。
215
+ * · `permissions: null` 是既有钉住的「容器缺席」形,照旧不咬。
216
+ */
217
+ export declare function taskSettingsKeyIssue(raw: unknown): TaskSettingsKeyIssue | null;
167
218
  /**
168
219
  * Validate an untrusted `body.settings` (raw `unknown` off the wire) into the service-trusted {@link
169
- * ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked; unknown keys are ignored
170
- * (forward-compatible, matching the SDK's open `[k: string]: unknown`). `defaultMode` accepts the FIVE modes
171
- * (post-[ref]; unknown values coerce to `default` — see the header note). Returns the
220
+ * ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked. `defaultMode` accepts the FIVE
221
+ * modes (post-[ref]; unknown values coerce to `default` see the header note). Returns the
172
222
  * present-but-deferred field names so the caller can warn/advertise rather than silently dropping `env`/`hooks`.
223
+ *
224
+ * 🔴 未知键在**本层**照旧忽略,而那**不是** wire 的现行为([ref] 件①,PM 裁「拒」[ref]):本层是 RESUME
225
+ * 重放也走的宽容层,一个 4xx 在这里会把存量 parked 任务永久砖死;FRESH 提交面由 {@link taskSettingsKeyIssue}
226
+ * 在 `server.ts` prepareSpec 里 400 响亮拒 + 逐字列名。双层姿势与 outputStyle / permissionMode 两处逐字相同。
173
227
  */
174
228
  export declare function parseTaskSettings(raw: unknown): ParseTaskSettingsResult;
175
229
  /** Defensive acceptance of the `appendSystemPrompt` rider ([ref] R2 + codex F2): the HTTP 400 gate (prepareSpec,