@sema-agent/server 7.55.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 (129) hide show
  1. package/MIGRATION.md +3 -3
  2. package/README.md +14 -7
  3. package/README.zh-CN.md +10 -6
  4. package/USAGE.md +207 -59
  5. package/deploy/sema-up/chart/values.yaml +1 -1
  6. package/dist/approval-ask-audit-store.d.ts +100 -1
  7. package/dist/approval-ask-audit-store.js +103 -2
  8. package/dist/approval-card.d.ts +148 -13
  9. package/dist/approval-card.js +82 -13
  10. package/dist/approval-content-kind.d.ts +22 -0
  11. package/dist/approval-content-kind.js +5 -0
  12. package/dist/approval.d.ts +31 -0
  13. package/dist/approval.js +18 -0
  14. package/dist/auto-mode-face.d.ts +111 -0
  15. package/dist/auto-mode-face.js +99 -0
  16. package/dist/bench/s1/arms.js +5 -5
  17. package/dist/bench/s1/live-deps.js +11 -11
  18. package/dist/bench/s1/run-firm.js +3 -0
  19. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  20. package/dist/bench/s1/runner-ctx.js +7 -0
  21. package/dist/boot/config-center.js +23 -1
  22. package/dist/boot/coordinators.js +13 -1
  23. package/dist/boot/leader.d.ts +21 -0
  24. package/dist/boot/leader.js +6 -0
  25. package/dist/boot/parked-revive-gate.d.ts +7 -2
  26. package/dist/boot/parked-revive-gate.js +12 -1
  27. package/dist/boot/resolve-spec.d.ts +3 -0
  28. package/dist/boot/resolve-spec.js +3 -1
  29. package/dist/boot/runner-deps.d.ts +12 -0
  30. package/dist/boot/runner-deps.js +28 -3
  31. package/dist/boot/runtime-caps.d.ts +19 -8
  32. package/dist/boot/runtime-caps.js +50 -20
  33. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  34. package/dist/boot/session-shell-gate-registry.js +21 -0
  35. package/dist/config-catalog.js +14 -5
  36. package/dist/config-center/types.d.ts +2 -1
  37. package/dist/config-provider.js +1 -0
  38. package/dist/config-types.d.ts +41 -10
  39. package/dist/config.d.ts +21 -0
  40. package/dist/config.js +40 -5
  41. package/dist/http/route-ctx.d.ts +90 -3
  42. package/dist/http/routes/a2a-serve.js +5 -3
  43. package/dist/http/routes/approvals-assistant.js +21 -8
  44. package/dist/http/routes/capabilities.js +21 -4
  45. package/dist/http/routes/leader.js +2 -2
  46. package/dist/http/routes/memory-origin.d.ts +2 -2
  47. package/dist/http/routes/rules.js +3 -2
  48. package/dist/http/routes/runs.d.ts +0 -14
  49. package/dist/http/routes/runs.js +20 -8
  50. package/dist/http/routes/tasks.js +34 -8
  51. package/dist/http/routes/workflows.js +20 -5
  52. package/dist/http/server.d.ts +11 -1
  53. package/dist/http/server.js +220 -34
  54. package/dist/http/wire-types.d.ts +9 -4
  55. package/dist/leader/diffout.js +2 -1
  56. package/dist/leader/endpoint.js +49 -15
  57. package/dist/leader/fanout.js +6 -5
  58. package/dist/leader/leader.js +17 -14
  59. package/dist/leader/merge.js +17 -12
  60. package/dist/leader/planner.js +3 -2
  61. package/dist/leader/repair-oracle.js +6 -4
  62. package/dist/leader/repair-wire.js +5 -3
  63. package/dist/leader/wire.d.ts +80 -1
  64. package/dist/leader/wire.js +55 -26
  65. package/dist/main.js +44 -8
  66. package/dist/observability/err-text.d.ts +6 -0
  67. package/dist/observability/err-text.js +10 -0
  68. package/dist/observability/fail-open.d.ts +40 -0
  69. package/dist/observability/fail-open.js +19 -0
  70. package/dist/observability/metrics.js +1 -1
  71. package/dist/observability/run-terminal-log.d.ts +120 -0
  72. package/dist/observability/run-terminal-log.js +360 -0
  73. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  74. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  75. package/dist/permission-rule-vocab.d.ts +40 -0
  76. package/dist/permission-rule-vocab.js +17 -0
  77. package/dist/plugins/checkpoint-store-sql.d.ts +26 -0
  78. package/dist/plugins/checkpoint-store-sql.js +23 -4
  79. package/dist/plugins/file-run-store.d.ts +3 -34
  80. package/dist/plugins/file-run-store.js +21 -0
  81. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  82. package/dist/plugins/local-checkpoint-store.js +5 -0
  83. package/dist/plugins/local-session-store.d.ts +9 -9
  84. package/dist/plugins/local-session-store.js +5 -3
  85. package/dist/plugins/memory-run-store.d.ts +3 -15
  86. package/dist/plugins/memory-run-store.js +21 -0
  87. package/dist/plugins/permission-rule-store-file.d.ts +22 -4
  88. package/dist/plugins/permission-rule-store-file.js +25 -11
  89. package/dist/plugins/permission-rule-store-sql.d.ts +60 -30
  90. package/dist/plugins/permission-rule-store-sql.js +39 -23
  91. package/dist/plugins/pg-session-storage.js +17 -13
  92. package/dist/plugins/remote-scratchpad.js +3 -2
  93. package/dist/plugins/run-store-sql.d.ts +3 -42
  94. package/dist/plugins/run-store-sql.js +36 -5
  95. package/dist/plugins/store-backend.d.ts +1 -1
  96. package/dist/plugins/store-contracts.d.ts +70 -1
  97. package/dist/plugins/tidb-session-store.js +18 -14
  98. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  99. package/dist/plugins/workflow-run-store-sql.js +10 -2
  100. package/dist/rules-consent.d.ts +5 -4
  101. package/dist/rules-consent.js +25 -46
  102. package/dist/run-cancel-context.d.ts +13 -0
  103. package/dist/run-cancel-context.js +15 -0
  104. package/dist/run-local.js +2 -2
  105. package/dist/runs.d.ts +4 -1
  106. package/dist/runs.js +41 -10
  107. package/dist/runtime-caps-resolver.d.ts +133 -17
  108. package/dist/runtime-caps-resolver.js +44 -4
  109. package/dist/security.d.ts +7 -0
  110. package/dist/task-settings.d.ts +57 -3
  111. package/dist/task-settings.js +78 -5
  112. package/dist/task-workflow.d.ts +32 -2
  113. package/dist/task-workflow.js +8 -3
  114. package/dist/tool-approval.d.ts +38 -63
  115. package/dist/tool-approval.js +102 -74
  116. package/dist/trace/core-keyset-guard.d.ts +2 -2
  117. package/dist/trace/ledger-sink.d.ts +13 -4
  118. package/dist/trace/ledger-sink.js +14 -6
  119. package/dist/trace/project.d.ts +13 -0
  120. package/dist/trace/project.js +10 -1
  121. package/dist/trace/redact.d.ts +22 -11
  122. package/dist/trace/redact.js +655 -20
  123. package/dist/trace/sema-provenance.d.ts +26 -0
  124. package/dist/trace/sema-provenance.js +11 -0
  125. package/dist/turn-activity.d.ts +32 -2
  126. package/dist/turn-activity.js +29 -4
  127. package/package.json +3 -3
  128. package/skills/find-skills.md +1 -1
  129. package/skills/loop.md +1 -1
@@ -14,6 +14,7 @@ import { configLkgEnabled } from "../config.js";
14
14
  import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg } from "../config-lkg.js";
15
15
  import { centerManagedConfigKeys } from "../config-center/apply-effective.js";
16
16
  import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "../config-provider.js";
17
+ import { executionLaneCaps, executionLaneOf } from "../execution-lane-caps.js";
17
18
  import { createKeyResolver } from "../key-resolver.js";
18
19
  import { ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
19
20
  import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, planeHasActiveTiers, fetchPromptArtifact } from "../config-center/facade.js";
@@ -366,12 +367,32 @@ export async function createConfigCenterRuntime(ctx) {
366
367
  try {
367
368
  const fileRx = await loadRemoteExec(localRoot);
368
369
  if (fileRx) {
370
+ const preemptedLane = config.remoteExec?.provider ?? "in-process";
371
+ const isolatedLane = (lane) => {
372
+ const word = executionLaneOf(lane === "in-process" ? undefined : lane);
373
+ return word !== undefined && executionLaneCaps(word).isolation === "supported";
374
+ };
375
+ const disclosePreemption = (winner) => {
376
+ if (winner === preemptedLane)
377
+ return;
378
+ const downgrade = isolatedLane(preemptedLane) && !isolatedLane(winner);
379
+ logger.warn("remote_exec_lane_preempted_by_file", {
380
+ preemptedLane,
381
+ effectiveLane: winner,
382
+ root: localRoot,
383
+ isolationDowngrade: downgrade,
384
+ note: `config.d/remote-exec.json (CONFIG_PROVIDER=local, file-wins) replaced the env-derived execution lane ` +
385
+ `"${preemptedLane}" with "${winner}" — any REMOTE_EXEC/lane coordinate env for "${preemptedLane}" is now INERT` +
386
+ (downgrade ? `. This is an ISOLATION DOWNGRADE: tasks run on a non-isolated target. Delete/fix the file, or unset the lane env if the file is the intended source.` : `. Delete/fix the file, or unset the lane env if the file is the intended source.`),
387
+ });
388
+ };
369
389
  if (fileRx.provider === "host") {
370
390
  config.remoteExec = {
371
391
  provider: "host",
372
392
  ...(fileRx.workdir ? { workspaceBase: fileRx.workdir } : {}),
373
393
  ...(fileRx.commandTimeoutMs != null ? { commandTimeoutMs: fileRx.commandTimeoutMs } : {}),
374
394
  };
395
+ disclosePreemption("host");
375
396
  logger.info("remote_exec_from_file", { provider: "host", root: localRoot });
376
397
  }
377
398
  else if (fileRx.provider === "local-docker") {
@@ -383,10 +404,11 @@ export async function createConfigCenterRuntime(ctx) {
383
404
  ...(fileRx.cpus != null ? { cpus: fileRx.cpus } : {}),
384
405
  ...(fileRx.network ? { network: fileRx.network } : {}),
385
406
  };
407
+ disclosePreemption("local-docker");
386
408
  logger.info("remote_exec_from_file", { provider: "local-docker", root: localRoot, image: fileRx.image });
387
409
  }
388
410
  else {
389
- logger.warn("remote_exec_file_isolated_lane_unwired", { provider: fileRx.provider, note: "e2b/k8s/ssh/adb are sourced from REMOTE_EXEC env until file-source NAME→value resolution lands (center field map)" });
411
+ logger.warn("remote_exec_file_isolated_lane_unwired", { provider: fileRx.provider, effectiveLane: preemptedLane, note: "e2b/k8s/ssh/adb are sourced from REMOTE_EXEC env until file-source NAME→value resolution lands (center field map) — the env-derived lane above stays in force" });
390
412
  }
391
413
  }
392
414
  }
@@ -26,6 +26,17 @@ export function createLiveCoordinators(ctx) {
26
26
  checkpointStore: backend?.checkpoint !== undefined,
27
27
  durableApproval: config.durableApproval === true,
28
28
  toolApproval: config.toolApprovalEnabled === true,
29
+ askWindowMs: config.streamApproval.windowMs,
30
+ });
31
+ }
32
+ if (config.toolApprovalEnabled === true && config.streamApproval.windowMs === 0) {
33
+ logger.warn("stream_ask_window_zero", {
34
+ unattendedPolicy: config.unattendedApprovalPolicy,
35
+ parkFacility: durableEnabled,
36
+ streamApproval: streamApprovalGate.active ? "active" : streamApprovalGate.reason,
37
+ note: durableEnabled && config.unattendedApprovalPolicy === "park"
38
+ ? "STREAM_ASK_WINDOW_MS=0 — no approval card is emitted on ANY leg; every gated ask parks immediately (answer it via POST /v1/approvals/:sessionId/decide). Set a positive window (ms) to get live cards back."
39
+ : "STREAM_ASK_WINDOW_MS=0 — no approval card is emitted on ANY leg, and this deployment has no park destination (UNATTENDED_APPROVAL_POLICY=deny and/or no checkpoint store), so every gated tool call is refused fail-closed. Set a positive window (ms), or wire DURABLE_APPROVAL + a checkpoint store.",
29
40
  });
30
41
  }
31
42
  const approvalAskAudit = config.toolApprovalEnabled && backend?.kind === "local" && config.localDataRoot !== undefined
@@ -45,8 +56,9 @@ export function createLiveCoordinators(ctx) {
45
56
  }
46
57
  const toolApproval = config.toolApprovalEnabled
47
58
  ? new ToolApprovalCoordinator({
59
+ ttlMs: config.streamApproval.windowMs,
48
60
  ...(streamApprovalGate.active
49
- ? { askStore: streamApprovalGate.askStore, ttlMs: config.streamApproval.windowMs, unreachedTtlMs: config.streamApproval.unreachedTtlMs }
61
+ ? { askStore: streamApprovalGate.askStore, unreachedTtlMs: config.streamApproval.unreachedTtlMs }
50
62
  : {}),
51
63
  windowMarginMs: config.streamAskWindowMarginMs,
52
64
  admitMaxPerTask: config.streamApproval.admitMaxPerTask,
@@ -25,6 +25,27 @@ export interface LeaderCtx {
25
25
  /** [ref] 件B/C/D([ref],codex R2-F1):部署治理三座席 —— leader 车道的每一只 Runner 都要带上它,
26
26
  * 否则开了 leader 端点的部署就有一条在部署治理**之外**的执行面(合规否决/锁都够不着)。 */
27
27
  governanceSeams: import("./governance-seams.js").GovernanceSeams;
28
+ /**
29
+ * [ref]([ref])—— 主车道 `boot/budget-tracing.ts` 造的**同一只** tracer。leader 车道的每一只 Runner
30
+ * 都要带:tracer 是 `brain.call` 的唯一记账点(costQuota / fleetUsage / fleetLease / 花费计数四个写口),
31
+ * 而 `POST /v1/leader` 的准入处**读**的正是它填的那两个桶 —— 缺席 = 门查的桶与花钱的手不是同一只。
32
+ *
33
+ * 🔴 **必填键**(值可为 undefined):这三席都刻意写成非可选,让「main.ts 铸了却忘了递」变成**编译红**
34
+ * 而不是又一次静默漏配([ref]§三族A 病族的机器护栏)。
35
+ */
36
+ tracer: RunnerDeps["tracer"];
37
+ /** [ref]([ref],[ref]-T1)—— 治理窗**账本店**(与 `config.usageWindows` 声明成对;声明直接从
38
+ * 本 ctx 的 config 取,leader 不另读)。缺席 = 未配治理窗的部署。必填键,理由同 `tracer`。 */
39
+ usageWindowStore: RunnerDeps["usageWindowStore"];
40
+ /** [ref]([ref])—— center 发布的 prompt catalog(`configCenter.promptSource`,与主 runner/subRunner
41
+ * 同一只)。缺席 ⇒ leader 的四类顶层任务跑引擎内置提示词,而同部署其余三条腿跑运维发布的那一份,
42
+ * 且 core 顶层新任务是**静默**落回(无 notice)。必填键,理由同 `tracer`。 */
43
+ promptSource: RunnerDeps["promptSource"];
44
+ /** [ref] 披露半件([ref],codex r2-[high])—— 主车道 `createRunnerDeps` 铸的**同一只**错误汇
45
+ * (main.ts 直引 `runnerDeps.onError`,与 subRunner 的先例同姿势)。core 对治理窗**最终扣账失败**的
46
+ * 唯一出口就是它(`runtask.js` 的 `catch (flushErr) => deps.onError`),所以它与 `usageWindowStore`
47
+ * 席同生共死:挂窗不挂汇 = 治理轴上的静默失败面([ref])。必填键,理由同 `tracer`。 */
48
+ onError: RunnerDeps["onError"];
28
49
  /** [ref] 车4 件1:durable store 后端(leader-run 登记表从这里取)。缺席 = 无 DB 的 env-only worker。 */
29
50
  backend?: StoreBackend | undefined;
30
51
  }
@@ -51,6 +51,12 @@ export function createLeaderFace(ctx) {
51
51
  retentionPolicy: governanceSeams.retentionPolicy,
52
52
  },
53
53
  deploymentPosture: buildDeploymentPostureSeats(config),
54
+ ...(ctx.tracer ? { tracer: ctx.tracer } : {}),
55
+ ...(config.usageWindows && ctx.usageWindowStore
56
+ ? { usageWindows: config.usageWindows, usageWindowStore: ctx.usageWindowStore }
57
+ : {}),
58
+ ...(ctx.promptSource ? { promptSource: ctx.promptSource } : {}),
59
+ ...(ctx.onError ? { onError: ctx.onError } : {}),
54
60
  fanoutEnabled: config.leaderFanoutEnabled,
55
61
  ...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
56
62
  ? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
@@ -57,9 +57,14 @@ export interface ParkedReviveGateDeps {
57
57
  * 手抄一个等价签名会让 core 改这条 seam 的那天变成静默漂移而不是编译期事件)。
58
58
  */
59
59
  readonly resolveRuntimeCaps?: RunnerDeps["runtimeCapsResolver"];
60
- /** `RunnerDeps.autoMode` 分类器面(信任门半场)在不在场 —— core 的武装条件是
61
- * `runtimeCaps.autoMode === true` **∧** 这一面在场,两个半场缺一不武装(prepare-task 原式) */
60
+ /** `RunnerDeps.autoMode` 分类器面(信任门半场)在不在场 —— core 7.2.0 的武装条件是
61
+ * `runtimeCaps.autoMode === true` **∧** 这一面在场(prepare-task 原式)。⚠️ [ref] / core [ref] 起武装式换成
62
+ * `spec.autoModeRequested === true ∧ 席位 ∧ runtimeCaps?.autoMode !== false`——本文件的镜像
63
+ * {@link autoModePostureOf} **未随改**(赎回行无父 run 的 auto 意图可读),见该函数头注的未闭环登记。 */
62
64
  readonly autoModeSeatMounted?: boolean;
65
+ /** [ref]:父 run 的 auto 意图(`TaskSpec.autoModeRequested` / 链位)——意图武装式下重建 decider 的第三项。⚠️ 今日
66
+ * **无持久源**(core [ref] 链位 live-only;RunRecord 无 body),生产装配不递 ⇒ 恒缺席;座留给 core 持久化到货。 */
67
+ readonly parentAutoModeIntent?: ((row: BackgroundAgentRecord) => boolean) | undefined;
63
68
  readonly logger: {
64
69
  info(event: string, fields?: Record<string, unknown>): void;
65
70
  warn(event: string, fields?: Record<string, unknown>): void;
@@ -1,3 +1,4 @@
1
+ import { coreArmsAutoOnIntent } from "../auto-mode-face.js";
1
2
  import { join } from "node:path";
2
3
  import { createAutoModeDecider, NodeExecutionEnv } from "@sema-agent/core";
3
4
  import { recordFailOpen } from "../observability/fail-open.js";
@@ -66,8 +67,18 @@ async function resolveCapsFor(deps, identity) {
66
67
  }
67
68
  }
68
69
  function autoModePostureOf(deps, row, caps) {
69
- if (deps.autoModeSeatMounted !== true || caps?.autoMode !== true)
70
+ if (deps.autoModeSeatMounted !== true)
70
71
  return {};
72
+ if (!coreArmsAutoOnIntent()) {
73
+ if (caps?.autoMode !== true)
74
+ return {};
75
+ }
76
+ else {
77
+ if (caps?.autoMode === false)
78
+ return {};
79
+ if (deps.parentAutoModeIntent?.(row) !== true)
80
+ return {};
81
+ }
71
82
  return { autoMode: { decider: createUnreachableAncestorClassifier(deps, row) } };
72
83
  }
73
84
  function createUnreachableAncestorClassifier(deps, row) {
@@ -50,6 +50,9 @@ export interface ResolveSpecCtx {
50
50
  hookWakeBus: {
51
51
  deliver?: (sessionId: string, text: string) => Promise<boolean>;
52
52
  };
53
+ /** [ref] 件⑤:per-session「显式 bypassPermissions」登记簿的写腿(`boot/session-shell-gate-registry.ts`);
54
+ * 阶段④ 算出 effMode 的那一行登记,runner-deps 的 config 忠告汇点按 sessionId 回查定级。缺席(桩)= 不登记。 */
55
+ noteSessionShellGate?: ((sessionId: string | undefined, explicitOff: boolean) => void) | undefined;
53
56
  resumeAnchorStore: ReturnType<StoreBackend["resumeAnchor"]> | undefined;
54
57
  ownerAware: OwnerAwareSessionStore;
55
58
  taskLimitCaps: {
@@ -36,7 +36,7 @@ import { redactSecrets } from "../trace/redact.js";
36
36
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
37
37
  import { effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
38
38
  export function createResolveSpec(ctx) {
39
- const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, handsLanes, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
39
+ const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, handsLanes, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, noteSessionShellGate, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
40
40
  assertGuardPatternsUsable(config);
41
41
  const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, { durableEnabled, singleUserAutoAcceptBaseline });
42
42
  if (onlySensitiveBaseline)
@@ -52,6 +52,7 @@ export function createResolveSpec(ctx) {
52
52
  assertRequestA2aUnlocked(body.a2aPeers, lockedKeys);
53
53
  const gated = await gateScenarioAndAppend(body, auth, opts);
54
54
  const effMode = effectivePermissionMode(body, gated.parsedSettings.settings);
55
+ noteSessionShellGate?.(auth?.sessionId, effMode === "bypassPermissions");
55
56
  const lane = bindSettingsCwdEnvAndModel(body, auth, gated, effMode, opts);
56
57
  const anchors = await resolveHistoryAnchors(body, auth);
57
58
  const spec = assembleSpecLiteral(body, auth, req, opts, { ...gated, ...lane, ...anchors }, effMode);
@@ -278,6 +279,7 @@ export function createResolveSpec(ctx) {
278
279
  additionalReadDirectories,
279
280
  enablePlanMode: config.planModeEnabled ? true : undefined,
280
281
  ...(effMode ? { shellGate: shellGateForMode(effMode) } : {}),
282
+ ...(effMode === "auto" ? { autoModeRequested: true } : {}),
281
283
  selfOrchestration: selfOrchestrationFromBody({ selfOrchestration: body.selfOrchestration === true || parsedSettings.settings?.ultracode === true }, config, Boolean(centerRuntimeCapsResolver)),
282
284
  forwardSubagentEvents: body.forwardSubagentEvents === true ? true : undefined,
283
285
  retainSubagentSessions: normalizeRetainSubagentSessions(body.retainSubagentSessions),
@@ -52,6 +52,14 @@ import type { MemorySyncRunner } from "../memory-sync-client.js";
52
52
  */
53
53
  export type RunnerDepsOnAsk = (req: Parameters<ToolApprovalCoordinator["ask"]>[0], signal?: AbortSignal) => Promise<AskOutcome>;
54
54
  export declare function createRunnerDepsOnAsk(toolApproval: ToolApprovalCoordinator | undefined): RunnerDepsOnAsk | undefined;
55
+ /** [ref] 件⑤:core `onError(phase:"config")` 的 `classification` 已知词表。⚠️ core d.ts(`RunnerDeps.onError` 注)只登记了
56
+ * prompt-cache 相的五词,config 相的词**只在 dist js 出现**(7.2.0 亲扫 dist 下全部 js 的 `phase: "config"` 发射点:五词——
57
+ * `interaction-posture-refused`(prepare-config-doors)/ `memory-tools-not-mounted` / `sandbox-admission-unavailable` /
58
+ * `shared-memory-not-mounted` / `shell-gate-off`(prepare-hands-readface));[ref] 去幻觉轮真抓:首版只写了 shell-gate-off
59
+ * 一词,其余四词会被误标 unknown。所以这是「按实现亲读」的开集快照,不是 core 类型闭集——词表外的值原样进日志并标
60
+ * `classificationKnown:false`(指标标签折 `unknown`),不吞不编;与安装包的锁步由 test/boot-runner-deps-shared-base.test.ts
61
+ * 的 dist 扫描格钉住(core 加词/收进 d.ts 那天先红)。 */
62
+ export declare const CONFIG_ADVISORY_CLASSIFICATIONS: ReadonlySet<string>;
55
63
  export interface RunnerDepsCtx {
56
64
  config: ServiceConfig;
57
65
  logger: Logger;
@@ -113,6 +121,10 @@ export interface RunnerDepsCtx {
113
121
  * 来源=configCenter.takeMcpRevocations()(取走即声明接线,restart 豁免与承接绑同一动作)。
114
122
  * 缺席=undefined=pre-338 语义(纯 env 部署无中心撤销面)。 */
115
123
  mcpRevocations: RunnerDeps["mcpRevocations"];
124
+ /** [ref] 件⑤([ref] server ③):per-session「显式 bypassPermissions」登记簿的读腿 —— `onError(phase:"config",
125
+ * classification:"shell-gate-off")` 在本 session 显式声明 bypass 时降级 info(正常态不打 warn)。缺席(桩 /
126
+ * run-local)= 照 warn(退化方向=多一条告警)。 */
127
+ sessionShellGateExplicitlyOff?: ((sessionId: string) => boolean) | undefined;
116
128
  /**
117
129
  * 交接件⑤ —— commit 尾注的署名座(`RunnerDeps.hands.commitCoAuthor`)。
118
130
  *
@@ -15,6 +15,13 @@ export function createRunnerDepsOnAsk(toolApproval) {
15
15
  return undefined;
16
16
  return (req, signal) => toolApproval.ask(req, signal);
17
17
  }
18
+ export const CONFIG_ADVISORY_CLASSIFICATIONS = new Set([
19
+ "interaction-posture-refused",
20
+ "memory-tools-not-mounted",
21
+ "sandbox-admission-unavailable",
22
+ "shared-memory-not-mounted",
23
+ "shell-gate-off",
24
+ ]);
18
25
  export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter) {
19
26
  return (notice) => {
20
27
  logger.warn("engine_notice", { code: notice.code, message: notice.message, ...(notice.detail !== undefined ? { detail: notice.detail } : {}) });
@@ -71,7 +78,7 @@ export function createSharedRunnerDeps(ctx) {
71
78
  return shared;
72
79
  }
73
80
  export function createRunnerDeps(ctx) {
74
- const { config, logger, metrics, localRoot, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine, sessionCaptureRecordStore, memorySyncRunner, runtimeCapsResolver, fileHistoryStore, legacyRewindBoundaryProbe, permissionRuleStore, fleetBus, workflowRunStore, workflowJournalStore, workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, getRunStore, } = ctx;
81
+ const { config, logger, metrics, localRoot, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine, sessionCaptureRecordStore, memorySyncRunner, runtimeCapsResolver, fileHistoryStore, legacyRewindBoundaryProbe, permissionRuleStore, fleetBus, workflowRunStore, workflowJournalStore, workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, getRunStore, sessionShellGateExplicitlyOff, } = ctx;
75
82
  const sharedRunnerDeps = createSharedRunnerDeps(ctx);
76
83
  const workflowModelAllowlist = workflowModelAllowlistFor(config);
77
84
  const selfOrchestrationDeps = config.selfOrchestrationEnabled
@@ -216,8 +223,26 @@ export function createRunnerDeps(ctx) {
216
223
  return;
217
224
  }
218
225
  if (ctx.phase === "config") {
219
- logger.warn("runner_config_advisory", { sessionId: ctx.sessionId, info: String(err) });
220
- metrics.inc("runner_config_advisory_total");
226
+ const code = typeof err?.code === "string" ? err.code : undefined;
227
+ const classification = ctx.classification;
228
+ const classificationKnown = classification === undefined ? undefined : CONFIG_ADVISORY_CLASSIFICATIONS.has(classification);
229
+ const expectedShellGateOff = classification === "shell-gate-off" && sessionShellGateExplicitlyOff?.(ctx.sessionId) === true;
230
+ const fields = {
231
+ sessionId: ctx.sessionId,
232
+ ...(code !== undefined ? { code } : {}),
233
+ ...(classification !== undefined ? { classification, classificationKnown } : {}),
234
+ ...(expectedShellGateOff ? { expected: "bypassPermissions declared by the caller for this session — shell gate off is the requested posture, not a misconfiguration" } : {}),
235
+ info: String(err),
236
+ };
237
+ if (expectedShellGateOff)
238
+ logger.info("runner_config_advisory", fields);
239
+ else
240
+ logger.warn("runner_config_advisory", fields);
241
+ metrics.inc("runner_config_advisory_total", {
242
+ ...(code !== undefined ? { code } : {}),
243
+ ...(classification !== undefined ? { classification: classificationKnown ? classification : "unknown" } : {}),
244
+ level: expectedShellGateOff ? "info" : "warn",
245
+ });
221
246
  return;
222
247
  }
223
248
  logger.error("runner_error", { phase: ctx.phase, sessionId: ctx.sessionId, err: String(err) });
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import type { ServiceConfig } from "../config.js";
11
11
  import type { Logger } from "../observability/logger.js";
12
- import { type MemoryOptOutVerdictSource } from "../runtime-caps-resolver.js";
12
+ import { type EntitlementsResolverFn, type MemoryOptOutVerdictSource } from "../runtime-caps-resolver.js";
13
13
  export interface RuntimeCapsCtx {
14
14
  config: ServiceConfig;
15
15
  logger: Logger;
@@ -22,23 +22,34 @@ export interface RuntimeCapsCtx {
22
22
  * D1 / `boot/retention-lane.ts` 同姿态:安全/合规控件不得半开)。
23
23
  *
24
24
  * governed 的语义是「per-principal verdict **必答**」:core 对 verdict 缺席一律**拒跑**(fail-closed,部署显式
25
- * 选的方向)。而 verdict 只能来自一个源 —— 本批唯一源 = SQL 后端上的 `memory_optout_grant` 表(三层折叠后
26
- * 恒答显式 boolean)。governed 而无源 每一个 `capture:"off"` 声明都被 core 以
25
+ * 选的方向)verdict 源**两条**(析取,[ref] 起):①SQL 后端上的 `memory_optout_grant` 表(三层折叠后
26
+ * 恒答显式 boolean),②非 dry-run config center `EntitlementRuntimeCaps.allowMemoryOptOut`。
27
+ * governed 而两源皆无 ⇒ 每一个 `capture:"off"` 声明都被 core 以
27
28
  * `memory.capture_optout_denied` 拒掉,而运维看到的只是一台「启动成功、opt-out 全拒」的机器 —— 正是
28
- * 「阀门开着而配不出来 ⇒ 悄悄读成别的」那一形。所以在**启动期**点名:要么接 SQL 后端(表随中央
29
- * ensureSchema 建),要么改姿态。`open`/`capture-required`/缺席 不依赖源(前者故障放行、后者全拒,
30
- * 都不问 verdict)。
29
+ * 「阀门开着而配不出来 ⇒ 悄悄读成别的」那一形。所以在**启动期**点名:接 SQL 后端(表随中央
30
+ * ensureSchema 建)、接非 dry-run center,或改姿态。`open`/`capture-required`/缺席 不依赖源(前者故障放行、
31
+ * 后者全拒,都不问 verdict)。
31
32
  *
32
33
  * ⚠️ center 键 `EntitlementRuntimeCaps.allowMemoryOptOut` 是 settings-schema 的独立小件([ref] §四 开放问题①),
33
- * 到货前**不算源**;到货后本断言加一条「center 在场」析取臂即可,判据不变。
34
+ * 已到货(1.4.0,[ref])——析取臂已加,dry-run 不算源(不 APPLY 即不 ENFORCE 同律)。
35
+ *
36
+ * 🔴 **[ref] —— 第三臂的辖域**:它证的是「center 这个**源**在场」,**不是**「center 会为每个 principal
37
+ * **发** `allowMemoryOptOut`」。该键在 settings-schema 里是**可选**的(`z.ZodOptional<z.ZodBoolean>`),
38
+ * `toCoreRuntimeCaps` 对缺席不铸键 ⇒ 一台 center 从不发该键的 governed worker 照样起动、然后每一个
39
+ * `memory.capture:"off"` 恒拒(core: verdict absent)。这条**不能**在启动期证死(部署级布尔对 per-principal
40
+ * 的授权说不了话,且 boot 期去问 center 会把拒启挂在一次网络上),所以取「拒启臂放行 + 两处响亮」:
41
+ * 启动期一行 `memory_capture_governed_center_only_source`(这一形有风险)+ 运行期首次真缺席一行
42
+ * (见 `applyGovernedVerdictAbsenceWarning`,一次性、不改判)。
34
43
  */
35
44
  export declare function assertMemoryCapturePolicyWirable(input: {
36
45
  policy: ServiceConfig["memoryCapturePolicy"];
37
46
  grantSourceWired: boolean;
47
+ centerEntitlementSourceWired?: boolean;
38
48
  }): void;
39
49
  export declare function createRuntimeCaps(ctx: RuntimeCapsCtx): {
40
50
  principalCaps: import("../runtime-caps-resolver.js").PrincipalEntitlementsClient | undefined;
41
51
  centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
42
- runtimeCapsResolver: import("../runtime-caps-resolver.js").EntitlementsResolverFn | undefined;
52
+ runtimeCapsResolver: EntitlementsResolverFn | undefined;
53
+ runtimeCapsPeekResolver: EntitlementsResolverFn | undefined;
43
54
  };
44
55
  //# sourceMappingURL=runtime-caps.d.ts.map
@@ -1,22 +1,42 @@
1
- import { applyMemoryOptOutGrant, applyObserverEnvOptIn, centerEntitlementSourceWired, createPrincipalEntitlementsClient, scopedTokenNeedsWorker, } from "../runtime-caps-resolver.js";
1
+ import { applyGovernedVerdictAbsenceWarning, applyLocalAutoModeDeny, applyMemoryOptOutGrant, applyObserverEnvOptIn, centerEntitlementSourceWired, createPrincipalEntitlementsClient, scopedTokenNeedsWorker, } from "../runtime-caps-resolver.js";
2
2
  export function assertMemoryCapturePolicyWirable(input) {
3
3
  if (input.policy !== "governed")
4
4
  return;
5
5
  if (input.grantSourceWired)
6
6
  return;
7
+ if (input.centerEntitlementSourceWired === true)
8
+ return;
7
9
  throw new Error(`MEMORY_CAPTURE_POLICY=governed requires a per-principal memory opt-out verdict source, and this deployment wires NONE ` +
8
10
  `— under "governed" core REFUSES every session memory-capture opt-out whose verdict is absent (fail-closed, the posture's ` +
9
11
  `own contract), so a governed worker without a source would refuse every \`memory.capture:"off"\` declaration and the ` +
10
- `refusal would read as an entitlement denial rather than the misconfiguration it is. The only verdict source in this ` +
11
- `version is the \`memory_optout_grant\` table on a SQL store backend (DB_BACKEND=mysql|pg; the table is created by the ` +
12
- `central ensureSchema and administered through /v1/admin/memory-optout). Wire a SQL backend, or set ` +
13
- `MEMORY_CAPTURE_POLICY=open (opt-outs take effect; only an explicit per-principal false denies) or ` +
12
+ `refusal would read as an entitlement denial rather than the misconfiguration it is. This version has TWO verdict sources ` +
13
+ `— wire either one: (1) the \`memory_optout_grant\` table on a SQL store backend (DB_BACKEND=mysql|pg; the table is ` +
14
+ `created by the central ensureSchema and administered through /v1/admin/memory-optout), or (2) a config center that ` +
15
+ `EMITS \`allowMemoryOptOut\` in each principal's entitlement caps (SEMA_REGISTRY_URL + SEMA_REGISTRY_TOKEN, ` +
16
+ `SEMA_REGISTRY_DRY_RUN off — dry-run observes without applying, so it is not a source; and note the key is OPTIONAL in ` +
17
+ `the entitlement schema: a center that never emits it leaves every verdict absent, i.e. every opt-out still refused). ` +
18
+ `Or set MEMORY_CAPTURE_POLICY=open (opt-outs take effect; only an explicit per-principal false denies) or ` +
14
19
  `MEMORY_CAPTURE_POLICY=capture-required (every opt-out declaration refused, no source consulted).`);
15
20
  }
21
+ function memoryVerdictAbsenceEffect(reason) {
22
+ switch (reason) {
23
+ case "anonymous_lane":
24
+ return 'an ANONYMOUS/system run (no principal) resolved no allowMemoryOptOut — the center entitlement source is per-principal and is never consulted without an identity, so on a center-only deployment the principal-less lane can NEVER obtain a verdict and core refuses every anonymous memory.capture:"off" (memory.capture_optout_denied). Only the local memory_optout_grant table answers for anonymous runs (its deployment-default row). One-shot per process.';
25
+ case "center_verdict_absent":
26
+ return 'governed posture, center-only verdict source, and this resolve produced NO allowMemoryOptOut for a NAMED principal (the center omitted the optional entitlement key, or the caps fetch degraded fail-closed) ⇒ core refuses that principal\'s memory.capture:"off" declarations (memory.capture_optout_denied). One-shot per process.';
27
+ }
28
+ }
16
29
  export function createRuntimeCaps(ctx) {
17
30
  const { config, logger } = ctx;
18
- assertMemoryCapturePolicyWirable({ policy: config.memoryCapturePolicy, grantSourceWired: ctx.memoryOptOutGrant !== undefined });
19
- if (config.configCenter && !config.configCenter.dryRun && scopedTokenNeedsWorker(config.configCenter.token, config.configCenter.worker)) {
31
+ const grantSourceWired = ctx.memoryOptOutGrant !== undefined;
32
+ assertMemoryCapturePolicyWirable({ policy: config.memoryCapturePolicy, grantSourceWired, centerEntitlementSourceWired: centerEntitlementSourceWired(config) });
33
+ const governedCenterOnlySource = config.memoryCapturePolicy === "governed" && !grantSourceWired && centerEntitlementSourceWired(config);
34
+ if (governedCenterOnlySource) {
35
+ logger.warn("memory_capture_governed_center_only_source", {
36
+ reason: "MEMORY_CAPTURE_POLICY=governed with NO local memory_optout_grant table: the only verdict source is the config center, and `allowMemoryOptOut` is an OPTIONAL entitlement key — for any principal whose caps omit it the verdict is ABSENT and core refuses that session's memory.capture:\"off\" (memory.capture_optout_denied, fail-closed). ANONYMOUS/system runs are worse off still: the center source is per-principal and is never consulted without an identity, so on this shape a principal-less run can never obtain a verdict at all. Verify the center emits allowMemoryOptOut for every principal, or wire a SQL backend (DB_BACKEND=mysql|pg) so the grant table answers (its deployment-default row also covers anonymous runs).",
37
+ });
38
+ }
39
+ if (centerEntitlementSourceWired(config) && config.configCenter && scopedTokenNeedsWorker(config.configCenter.token, config.configCenter.worker)) {
20
40
  logger.warn("runtime_caps_scoped_token_no_worker", {
21
41
  reason: "SEMA_REGISTRY_TOKEN is a worker-scoped wpt_ token but SEMA_REGISTRY_WORKER is unset → center 403s per-principal caps → ALL workflow self-orchestration will be fail-closed denied. Set SEMA_REGISTRY_WORKER (the orchestrator normally injects it) or use the full SERVICE_PULL_TOKEN.",
22
42
  });
@@ -30,18 +50,28 @@ export function createRuntimeCaps(ctx) {
30
50
  })
31
51
  : undefined;
32
52
  const centerRuntimeCapsResolver = principalCaps?.resolveRuntimeCaps;
33
- const withObservers = applyObserverEnvOptIn(centerRuntimeCapsResolver, {
34
- experimentalObserverAgents: config.experimentalObserverAgents,
35
- requirePrincipal: config.requirePrincipal,
36
- warn: (msg, fields) => logger.warn(msg, fields),
37
- });
38
- const runtimeCapsResolver = applyMemoryOptOutGrant(withObservers, ctx.memoryOptOutGrant, {
39
- onFault: (err, principal) => logger.warn("memory_optout_grant_resolve_failed", {
40
- principal,
41
- err: String(err),
42
- effect: "allowMemoryOptOut left ABSENT for this resolve (never coined as false): open posture ⇒ the opt-out takes effect; governed ⇒ core refuses the run (verdict absent)",
43
- }),
44
- });
45
- return { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver };
53
+ const compose = (base, observability) => {
54
+ const withObservers = applyObserverEnvOptIn(base, {
55
+ experimentalObserverAgents: config.experimentalObserverAgents,
56
+ requirePrincipal: config.requirePrincipal,
57
+ ...(observability ? { warn: (msg, fields) => logger.warn(msg, fields) } : {}),
58
+ });
59
+ const withMemoryGrant = applyMemoryOptOutGrant(withObservers, ctx.memoryOptOutGrant, {
60
+ onFault: (err, principal) => logger.warn("memory_optout_grant_resolve_failed", {
61
+ principal,
62
+ err: String(err),
63
+ ...(observability ? {} : { face: "capabilities-read" }),
64
+ effect: "allowMemoryOptOut left ABSENT for this resolve (never coined as false): open posture ⇒ the opt-out takes effect; governed ⇒ core refuses the run (verdict absent)",
65
+ }),
66
+ });
67
+ const withLocalAutoModeDeny = applyLocalAutoModeDeny(withMemoryGrant, config.permissionsDisableAutoMode);
68
+ return applyGovernedVerdictAbsenceWarning(withLocalAutoModeDeny, {
69
+ enabled: observability && governedCenterOnlySource,
70
+ warn: (reason, principal) => logger.warn("memory_capture_governed_verdict_absent", { principal, reason, effect: memoryVerdictAbsenceEffect(reason) }),
71
+ });
72
+ };
73
+ const runtimeCapsResolver = compose(centerRuntimeCapsResolver, true);
74
+ const runtimeCapsPeekResolver = compose(principalCaps?.peekRuntimeCaps, false);
75
+ return { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver, runtimeCapsPeekResolver };
46
76
  }
47
77
  //# sourceMappingURL=runtime-caps.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * [ref] 件⑤(core PM 三仓扫查 [ref] server 条 ③,并入 [ref])—— 「本 run 是否**显式**声明了 bypassPermissions」的
3
+ * per-session 登记簿,供 `boot/runner-deps.ts` 的 `onError(phase:"config")` 汇点给 core 的 `shell-gate-off`
4
+ * 忠告定级。
5
+ *
6
+ * ## 为什么需要它
7
+ * core 在「shell 门 doctrine=off ∧ 挂了真可写 Bash」时经 `onError(phase:"config", classification:"shell-gate-off")`
8
+ * 发一条忠告(`prepare-hands-readface.js`),但它分不清 off 是**调用方显式选的**(server 把 `permissionMode:
9
+ * "bypassPermissions"` 翻成 `shellGate:"off"`,[ref] 表)还是**没表态落到 core 默认**(无壳直连部署,真值得
10
+ * 一条 warn)。这正是真实用户提案里「bypass 下照打 shell gate doctrine is off 告警」的根:正常态被当异常打。
11
+ * 汇点只拿到 `ctx.sessionId`,而生效模式是 server 在 resolve-spec 阶段④ 单点算出的(`effectivePermissionMode`),
12
+ * 所以由 resolve-spec 在算出 effMode 的那一行登记、汇点按 sessionId 回查。
13
+ *
14
+ * ## 键
15
+ * `auth.sessionId`:authorizer 在服务路径上**恒**铸 session id(`security.ts` `uuidv7()`,core 的无 sessionId
16
+ * 分支在服务路径不可达),且 spec.sessionId = 同一值 ⇒ core 回调的 `ctx.sessionId` 与登记键同源。
17
+ * 缺 authorizer 的裸装配(测试桩)拿不到键 ⇒ 不登记 ⇒ 汇点按「未声明」处理 = 仍 warn(退化方向=多一条告警,
18
+ * 不是少一条)。
19
+ *
20
+ * ## 已知界(codex r2 [medium],登记不修:属主在 core 回调契约)
21
+ * 键是 session 而非**执行**:同 session 并发第二次提交在 resolveSpec 后才被 409 拒,其登记已覆盖前一次——若被拒的
22
+ * 那次是 bypass 而在跑的那次不是,且忠告恰在这一窗内发出,则一条本该 warn 的 `shell-gate-off` 被降成 info(仍整行
23
+ * 落日志、字段齐全,执法零变)。窗 = 在跑 run 的 resolveSpec 到其 prepare 期忠告之间(毫秒级,且要同 session 并发
24
+ * 提交 + 模式相反)。执行唯一的归因需要 core 回调 `ctx` 带 taskId / shellGate 出处(已作 core 侧诉求登记),
25
+ * 到货后本簿退役。
26
+ *
27
+ * ## 界
28
+ * 有界 Map(插入序淘汰,默认 10k 条,与 caps 缓存同量级):session id 是开集,长命 worker 不能无界累积。
29
+ * 淘汰掉的旧会话再来忠告时同样回落「未声明」⇒ warn(同一退化方向)。每次 resolve 覆写(同 session 后一轮改
30
+ * 模式时以最近一轮为准;续跑四条 rebuild 腿都经 resolveSpec ⇒ 同律登记)。
31
+ */
32
+ export interface SessionShellGateRegistry {
33
+ /** resolve-spec 阶段④:登记本 session 这一轮是否显式声明 bypassPermissions(= server 翻成 shellGate:"off")。 */
34
+ note(sessionId: string | undefined, explicitOff: boolean): void;
35
+ /** runner-deps 汇点:本 session 最近一轮是否显式声明了 off。未登记/已淘汰 ⇒ false(退化=照 warn)。 */
36
+ explicitlyOff(sessionId: string): boolean;
37
+ }
38
+ export declare function createSessionShellGateRegistry(cap?: number): SessionShellGateRegistry;
39
+ //# sourceMappingURL=session-shell-gate-registry.d.ts.map
@@ -0,0 +1,21 @@
1
+ export function createSessionShellGateRegistry(cap = 10_000) {
2
+ const off = new Map();
3
+ return {
4
+ note(sessionId, explicitOff) {
5
+ if (sessionId === undefined)
6
+ return;
7
+ if (off.has(sessionId))
8
+ off.delete(sessionId);
9
+ else if (off.size >= cap) {
10
+ const oldest = off.keys().next();
11
+ if (!oldest.done)
12
+ off.delete(oldest.value);
13
+ }
14
+ off.set(sessionId, explicitOff);
15
+ },
16
+ explicitlyOff(sessionId) {
17
+ return off.get(sessionId) === true;
18
+ },
19
+ };
20
+ }
21
+ //# sourceMappingURL=session-shell-gate-registry.js.map
@@ -254,7 +254,7 @@ export const CONFIG_CATALOG = [
254
254
  r("MODEL_COST_CACHE_WRITE", "modelPlane", "number", "主模型 cache-write 单价", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.cacheWrite ?? null, center: cModels("models[].cost.cacheWrite") }),
255
255
  r("MODEL_COST_INPUT", "modelPlane", "number", "主模型 input 单价(NaN 会让成本天花板整条消失 ⇒ 坏值拒启)", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.input ?? null, center: cModels("models[].cost.input") }),
256
256
  r("MODEL_COST_OUTPUT", "modelPlane", "number", "主模型 output 单价", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.output ?? null, center: cModels("models[].cost.output") }),
257
- r("MODEL_DEFAULT_THINKING", "modelPlane", "enum", "主模型默认思考档(llm-core ThinkingLevel,无 off)", { enumValues: ["minimal", "low", "medium", "high", "max"] }),
257
+ r("MODEL_DEFAULT_THINKING", "modelPlane", "enum", "主模型默认思考档(llm-core ThinkingLevel;off=不设模型级默认;未知词拒启)", { enumValues: ["minimal", "low", "medium", "high", "xhigh", "max", "off"], derivedDefaultNote: "缺席/off ⇒ 不铸 defaultThinking(逐任务档由 spec>role 决定)", resolve: (c) => c.model.defaultThinking ?? null }),
258
258
  r("MODEL_DEGRADE_AT_COST_FRACTION", "modelPlane", "number", "预算降级触发比(spent/budget;>1=实际别降级)", { staticDefault: "0.7", danger: BILL }),
259
259
  r("MODEL_DEGRADE_ON", "modelPlane", "csv", "反应式降级触发词表(core DegradeReason 全集;拼错拒启)", { center: { domain: "models", path: "degrade", centerKey: "models", precedence: "center-wins", timing: "restart", restartSlice: "degrade-route" } }),
260
260
  r("MODEL_DEGRADE_REACTIVE", "modelPlane", "boolean", "反应式降级开关(degrade-route 重启片只在它开时活)", { staticDefault: "false", center: { domain: "models", path: "degrade", centerKey: "models", precedence: "center-wins", timing: "restart", restartSlice: "degrade-route" } }),
@@ -271,7 +271,15 @@ export const CONFIG_CATALOG = [
271
271
  r("MODEL_PRESENCE_PENALTY", "modelPlane", "number", "presence_penalty 直通"),
272
272
  r("MODEL_PROVIDER", "modelPlane", "string", "主模型 provider(路由选 brain)", { derivedDefaultNote: "缺席:ANTHROPIC base+cred 同在 ⇒ anthropic,否则 gateway", resolve: (c) => c.model.provider, center: cModels("models[].provider") }),
273
273
  r("MODEL_REASONING", "modelPlane", "boolean", "主模型 reasoning 声明", { staticDefault: "true", resolve: (c) => c.model.reasoning ?? null, center: cModels("models[].reasoning") }),
274
- r("MODEL_REASONING_EFFORT_LEVELS", "modelPlane", "csv", "openai 端点接受的思考档集(高档 clamp-down 不 422;anthropic 腿不需要)"),
274
+ r("MODEL_REASONING_EFFORT_LEVELS", "modelPlane", "csv", "openai 端点接受的思考档集(六档子集;高档 clamp-down 不 422;anthropic 腿不铸此键;未知词拒启)", {
275
+ derivedDefaultNote: "缺席 ⇒ 不铸 compat,core 走保守梯(minimal|low|medium|high),xhigh/max 被 clamp 下来",
276
+ resolve: (c) => {
277
+ const compat = c.model.compat;
278
+ if (compat === undefined || !("reasoningEffortLevels" in compat))
279
+ return null;
280
+ return compat.reasoningEffortLevels ?? null;
281
+ },
282
+ }),
275
283
  r("MODEL_ROUTER_ID", "modelPlane", "string", "leader 路由分类模型覆写", { derivedDefaultNote: "缺席 ⇒ MODEL_CHEAP_ID,再退 default", danger: BILL }),
276
284
  r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
277
285
  r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false 让 model_no_vision 预检真拦)", { staticDefault: "true", resolve: (c) => (Array.isArray(c.model.input) ? c.model.input.includes("image") : true), center: cModels("models[].vision") }),
@@ -286,6 +294,7 @@ export const CONFIG_CATALOG = [
286
294
  r("OTEL_EXPORTER_OTLP_HEADERS", "observability", "secret", "OTLP 出口头(k=v CSV;常载鉴权头 ⇒ 按凭据处置)", { danger: SEC }),
287
295
  r("OTEL_EXPORT_INTERVAL_MS", "observability", "number", "OTLP 批报周期(ms)", { staticDefault: "15000" }),
288
296
  r("OTEL_SERVICE_NAME", "observability", "string", "OTLP service.name", { staticDefault: "sema-server" }),
297
+ r("PERMISSIONS_DISABLE_AUTO_MODE", "approval", "boolean", "auto 模式本地 org deny(CC/center/settings 键名 permissions.disableAutoMode 的 env 腿;tighten-only:每 principal caps.autoMode 折 false,center 授予翻不回;未设=不动 caps;词表外拒启)", { staticDefault: "false", danger: SEC, configKey: "permissionsDisableAutoMode", center: { domain: "permissions", path: "disableAutoMode", centerKey: "permissions.disableAutoMode", precedence: "env-wins", timing: "inert" } }),
289
298
  r("PERMISSION_RULES_ENABLED", "approval", "boolean", "持久化权限规则车道总开关(撤销面已建 ⇒ 默认 ON)", { staticDefault: "true", danger: SEC, configKey: "permissionRulesEnabled" }),
290
299
  r("PG_DATABASE", "store", "string", "PostgreSQL 库名", { danger: DATA }),
291
300
  r("PG_HOST", "store", "string", "PostgreSQL 主机(在场即算 SQL 信号)", { danger: DATA }),
@@ -361,7 +370,7 @@ export const CONFIG_CATALOG = [
361
370
  r("SERVICE_AUTH_TOKEN", "auth", "secret", "共享 service token(写面门;缺席且未开 ALLOW_UNAUTHED_WRITES ⇒ 拒提交)", { danger: SEC }),
362
371
  r("SERVICE_AUTH_TOKENS", "auth", "secret", "per-system 凭据表(tok=system CSV;source 从凭据派生,绝不自报)", { danger: SEC }),
363
372
  r("SESSION_AUTO_TITLE", "orchestration", "boolean", "首次提交自动铸 session 标题(一次 cheap 调用)", { staticDefault: "true", danger: BILL, configKey: "sessionAutoTitle" }),
364
- r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存)", { enumValues: ["memory", "mysql", "auto"], danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => c.sessionBackend }),
373
+ r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存;回显=boot 解析后的真引擎)", { enumValues: ["memory", "mysql", "auto"], danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
365
374
  r("SESSION_CACHE_TTL_SEC", "store", "number", "session 读缓存 TTL(秒;0=关)", { staticDefault: "300", configKey: "sessionCacheTtlSec" }),
366
375
  r("SESSION_EVENTS_HOT_MS", "limitsHttp", "number", "session SSE 订阅热探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 200" }),
367
376
  r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
@@ -391,12 +400,12 @@ export const CONFIG_CATALOG = [
391
400
  r("STREAM_APPROVAL_RECONCILE_BATCH", "approval", "number", "对账收敛器每 tick 每段行数帽(整数)", { staticDefault: "200" }),
392
401
  r("STREAM_APPROVAL_REPLAY_MAX", "approval", "number", "开流 preamble 重放帽", { staticDefault: "50" }),
393
402
  r("STREAM_ASK_WINDOW_MARGIN_MS", "approval", "number", "审批窗长三元安全余量(ms)", { staticDefault: "10000", configKey: "streamAskWindowMarginMs" }),
394
- r("STREAM_ASK_WINDOW_MS", "approval", "number", "流内 ask 窗长(ms)", { staticDefault: "300000" }),
403
+ r("STREAM_ASK_WINDOW_MS", "approval", "number", "活卡审批窗长(ms;所有腿生效,与流内协议上不上场无关;0=关窗⇒不发卡恒走无人值守终局;负值与超 Node 定时器上界 2147483647 的值拒启)", { staticDefault: "300000" }),
395
404
  r("SYNC_IMPORT_LEASE_STALE_SEC", "memory", "number", "session-sync staged import 租约陈旧阈(秒)", { staticDefault: "600", configKey: "syncImportLeaseStaleSec" }),
396
405
  r("TASK_MAX_OUTPUT_TOKENS_MAX", "limitsHttp", "number", "调用方自报 limits.maxOutputTokens 的运营方封顶(Math.min 钳显式请求值;坏值=封顶不生效+响亮 warn)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
397
406
  r("TASK_MAX_TURNS_MAX", "limitsHttp", "number", "调用方自报 limits.maxTurns 的运营方封顶(同族三旋钮;只钳显式请求值,不是全队默认限额)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
398
407
  r("TASK_TIMEOUT_MAX_SEC", "limitsHttp", "number", "调用方自报 limits.timeoutSec 的运营方封顶(同族三旋钮)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
399
- r("TASK_TIMEOUT_SEC", "limitsHttp", "number", "任务墙钟超时(秒;0=关)", { staticDefault: "0" }),
408
+ r("TASK_TIMEOUT_SEC", "limitsHttp", "number", "任务墙钟超时(秒);0/缺席=按 tenancy(单用户无墙;多租户 2400s、大任务 3600s);正值**只抬不降**(Math.max 基值);负值拒启", { staticDefault: "0", derivedDefaultNote: "0/缺席 ⇒ 单用户无墙、多租户 2400/3600s;要低于基值的配速请用 body.limits.maxWalltimeMs" }),
400
409
  r("TOOL_APPROVAL_ENABLED", "approval", "boolean", "活体工具审批 HITL(onAsk 装配;关 ⇒ core resolveAsk 走 headless 缺省)", { danger: SEC, derivedDefaultNote: "posture 三态:显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ on / 多租户 ⇒ off", configKey: "toolApprovalEnabled" }),
401
410
  r("TOOL_DEFER_LONGTAIL", "orchestration", "boolean", "长尾一方工具 defer 呈现(实验)", { staticDefault: "false", configKey: "toolDeferLongtail" }),
402
411
  r("TOOL_RESULT_TTL_SEC", "limitsHttp", "number", "工具结果店 TTL(秒)", { staticDefault: "86400", danger: DATA, configKey: "toolResultTtlSec", center: cLimits("toolResultTtlSec") }),
@@ -103,7 +103,8 @@ export interface CenterMcpServer {
103
103
  * 所以它的家在 center 下发这条腿;请求腿那半场的门与结构性兑现见 `task-mcp.ts`
104
104
  * 的 `assertRequestMcpContentOrigin` 顶注。
105
105
  *
106
- * ⚠️ **两条腿今天都到不了这里**([ref] / [ref]③ / [ref],2026-08-31 双绊线实测更正 —— 本注旧版那句
106
+ * **两条腿已通**([ref],settings-schema 1.4.0 补键 + config-provider 映射行,2026-09-02;双绊线翻正销账)。
107
+ * 病史([ref]③ / [ref]③ / [ref],2026-08-31 双绊线实测更正 —— 本注更旧版那句
107
108
  * 「远端 /effective.mcp 腿是直读 JSON,今天就通」**不成立**,doc-rot):settings-schema(原 registry-core)
108
109
  * 的 `McpServerSpec` 是默认 strip 的 zod object 且没有 `contentOrigin` 可选键,本地腿在 FileConfigStore 的
109
110
  * `parseDomainLoud(DOMAIN_SCHEMAS.mcp)`、远端腿在消费端边界 `readEffectiveWire`([ref] 裁B)**各被剥一次**。
@@ -145,6 +145,7 @@ export function mapToServiceEffective(eff, version) {
145
145
  enabled: s.enabled,
146
146
  ...(s.allowTools !== undefined ? { allowTools: s.allowTools } : {}),
147
147
  ...(s.elicitation !== undefined ? { elicitation: s.elicitation } : {}),
148
+ ...(s.contentOrigin !== undefined ? { contentOrigin: s.contentOrigin } : {}),
148
149
  };
149
150
  if (s.transport.kind === "stdio") {
150
151
  return {