@sema-agent/server 7.136.0 → 7.137.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 (214) hide show
  1. package/MIGRATION.md +173 -0
  2. package/USAGE.md +84 -16
  3. package/deploy/sema-up/README.md +14 -0
  4. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  5. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  6. package/dist/approval-content-kind.d.ts +6 -0
  7. package/dist/approval-content-kind.js +7 -0
  8. package/dist/approval-gate-currency.d.ts +48 -6
  9. package/dist/approval-gate-currency.js +16 -9
  10. package/dist/approval-hmac.d.ts +13 -1
  11. package/dist/approval-hmac.js +1 -1
  12. package/dist/approval.js +3 -3
  13. package/dist/auth-bridge.js +2 -2
  14. package/dist/boot/execution-env.d.ts +24 -1
  15. package/dist/boot/execution-env.js +69 -6
  16. package/dist/boot/reapers.d.ts +3 -0
  17. package/dist/boot/reapers.js +15 -1
  18. package/dist/boot/resolve-spec.d.ts +4 -0
  19. package/dist/boot/resolve-spec.js +16 -17
  20. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  21. package/dist/boot/stage-04-stores.d.ts +1 -1
  22. package/dist/boot/stage-05-execution-env.d.ts +3 -1
  23. package/dist/boot/stage-05-execution-env.js +11 -2
  24. package/dist/boot/stage-06-runners.d.ts +3 -1
  25. package/dist/boot/stage-06-runners.js +5 -1
  26. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  27. package/dist/boot/stage-07-capability-layer.js +1 -1
  28. package/dist/boot/stage-08-reapers.d.ts +4 -1
  29. package/dist/boot/stage-08-reapers.js +2 -1
  30. package/dist/boot/stage-09-leader.d.ts +4 -1
  31. package/dist/boot/stage-10-http-server.d.ts +4 -1
  32. package/dist/boot/stage-10-http-server.js +3 -2
  33. package/dist/boot/stores.d.ts +1 -1
  34. package/dist/broker/broker.d.ts +89 -0
  35. package/dist/broker/broker.js +287 -0
  36. package/dist/broker/constants.d.ts +39 -0
  37. package/dist/broker/constants.js +15 -0
  38. package/dist/broker/http1.d.ts +111 -0
  39. package/dist/broker/http1.js +256 -0
  40. package/dist/broker/lease-book.d.ts +87 -0
  41. package/dist/broker/lease-book.js +142 -0
  42. package/dist/broker/lease-file.d.ts +60 -0
  43. package/dist/broker/lease-file.js +127 -0
  44. package/dist/broker/main.d.ts +2 -0
  45. package/dist/broker/main.js +20 -0
  46. package/dist/capabilities/composite-members.d.ts +2 -1
  47. package/dist/capabilities/composite-members.js +2 -1
  48. package/dist/capabilities/engine-capabilities.js +1 -0
  49. package/dist/capabilities/scenarios.d.ts +23 -8
  50. package/dist/capabilities/scenarios.js +13 -8
  51. package/dist/capabilities/skills.d.ts +28 -5
  52. package/dist/capabilities/skills.js +8 -0
  53. package/dist/center-credential-renewer.js +5 -1
  54. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  55. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  56. package/dist/checkpoint-identity.d.ts +3 -3
  57. package/dist/config-catalog.js +9 -6
  58. package/dist/config-types.d.ts +9 -0
  59. package/dist/config.d.ts +1 -1
  60. package/dist/config.js +34 -8
  61. package/dist/deployment-governance.d.ts +2 -2
  62. package/dist/deployment-governance.js +7 -9
  63. package/dist/device-ws-protocol.d.ts +1 -0
  64. package/dist/device-ws-protocol.js +2 -1
  65. package/dist/file-history-disclosure.d.ts +4 -2
  66. package/dist/file-history-disclosure.js +2 -0
  67. package/dist/hooks/classic-stations.d.ts +11 -54
  68. package/dist/http/admission.js +10 -7
  69. package/dist/http/approval-sweeps.js +4 -3
  70. package/dist/http/caller-trust.d.ts +2 -1
  71. package/dist/http/caller-trust.js +1 -3
  72. package/dist/http/checkpoint-ctx.d.ts +48 -7
  73. package/dist/http/checkpoint-ctx.js +40 -0
  74. package/dist/http/dispatch.js +15 -10
  75. package/dist/http/durable-run-leg.d.ts +5 -1
  76. package/dist/http/durable-run-leg.js +1 -1
  77. package/dist/http/health-self-report.d.ts +2 -2
  78. package/dist/http/mcp-remote-text.d.ts +5 -0
  79. package/dist/http/mcp-remote-text.js +7 -0
  80. package/dist/http/principal-gate.d.ts +15 -10
  81. package/dist/http/principal-gate.js +48 -9
  82. package/dist/http/resume-legs.d.ts +2 -20
  83. package/dist/http/resume-legs.js +52 -38
  84. package/dist/http/route-ctx.d.ts +8 -2
  85. package/dist/http/routes/approvals-assistant.js +18 -4
  86. package/dist/http/routes/capabilities.js +11 -1
  87. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  88. package/dist/http/routes/mcp-reconnect.js +5 -7
  89. package/dist/http/routes/runs.js +2 -1
  90. package/dist/http/routes/session-sync.js +42 -6
  91. package/dist/http/routes/sessions.js +2 -1
  92. package/dist/http/routes/tasks.js +8 -6
  93. package/dist/http/server.d.ts +4 -0
  94. package/dist/http/stored-body-replay.d.ts +3 -3
  95. package/dist/http/stored-body-replay.js +6 -1
  96. package/dist/http/wire-types.d.ts +52 -27
  97. package/dist/identity-source.d.ts +65 -0
  98. package/dist/identity-source.js +45 -0
  99. package/dist/memory-store-refusal.d.ts +3 -3
  100. package/dist/model-entry-refusal.d.ts +2 -0
  101. package/dist/model-entry-refusal.js +11 -0
  102. package/dist/model-select.d.ts +6 -17
  103. package/dist/model-select.js +0 -3
  104. package/dist/namespaced-rule-name.d.ts +1 -1
  105. package/dist/observability/fail-open.d.ts +13 -1
  106. package/dist/observability/fail-open.js +13 -1
  107. package/dist/observability/metrics.js +1 -1
  108. package/dist/observability/run-terminal.js +1 -0
  109. package/dist/operator-ask.d.ts +2 -2
  110. package/dist/org-delivery-gate.js +1 -1
  111. package/dist/org-memory-admission.d.ts +2 -2
  112. package/dist/org-memory-admission.js +39 -2
  113. package/dist/parked-decide.js +3 -2
  114. package/dist/peer-directory.js +2 -0
  115. package/dist/permission-rule-list.d.ts +2 -2
  116. package/dist/plugins/checkpoint-store-sql.d.ts +19 -9
  117. package/dist/plugins/checkpoint-store-sql.js +6 -6
  118. package/dist/plugins/file-history-store-sql.js +7 -14
  119. package/dist/plugins/file-run-store.js +8 -2
  120. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  121. package/dist/plugins/k8s-exec-protocol.js +116 -28
  122. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  123. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  124. package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
  125. package/dist/plugins/k8s-pod-reaper.js +122 -0
  126. package/dist/plugins/local-checkpoint-store.d.ts +3 -6
  127. package/dist/plugins/local-checkpoint-store.js +3 -3
  128. package/dist/plugins/memory-run-store.js +2 -0
  129. package/dist/plugins/one-time-migrations.d.ts +28 -9
  130. package/dist/plugins/one-time-migrations.js +48 -5
  131. package/dist/plugins/permission-rule-store-file.js +2 -2
  132. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  133. package/dist/plugins/permission-rule-store-sql.js +16 -17
  134. package/dist/plugins/pg-pool.js +4 -0
  135. package/dist/plugins/pg-session-storage.js +2 -2
  136. package/dist/plugins/remote-env-adb.d.ts +3 -0
  137. package/dist/plugins/remote-env-adb.js +1 -0
  138. package/dist/plugins/remote-env-device.d.ts +3 -0
  139. package/dist/plugins/remote-env-device.js +4 -0
  140. package/dist/plugins/remote-env-e2b.d.ts +3 -0
  141. package/dist/plugins/remote-env-e2b.js +1 -0
  142. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  143. package/dist/plugins/remote-env-file-error.js +7 -1
  144. package/dist/plugins/remote-env-host.d.ts +7 -1
  145. package/dist/plugins/remote-env-host.js +7 -1
  146. package/dist/plugins/remote-env-k8s.d.ts +137 -10
  147. package/dist/plugins/remote-env-k8s.js +276 -14
  148. package/dist/plugins/remote-env-local-docker.d.ts +3 -0
  149. package/dist/plugins/remote-env-local-docker.js +1 -0
  150. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  151. package/dist/plugins/remote-env-ssh.js +1 -0
  152. package/dist/plugins/run-store-sql.js +10 -4
  153. package/dist/plugins/session-ownership.d.ts +17 -3
  154. package/dist/plugins/session-ownership.js +19 -5
  155. package/dist/plugins/sql-errors.d.ts +23 -2
  156. package/dist/plugins/sql-errors.js +15 -0
  157. package/dist/plugins/store-contracts.d.ts +10 -1
  158. package/dist/plugins/store-fault.d.ts +2 -2
  159. package/dist/plugins/store-fault.js +4 -2
  160. package/dist/plugins/tidb-pool.js +7 -0
  161. package/dist/plugins/tidb-session-store.js +2 -2
  162. package/dist/request-key-closure.js +1 -1
  163. package/dist/request-value-closure.d.ts +31 -2
  164. package/dist/request-value-closure.js +7 -1
  165. package/dist/rule-entry-list.d.ts +3 -2
  166. package/dist/rule-entry-list.js +2 -2
  167. package/dist/rules-consent.js +7 -4
  168. package/dist/run-local.d.ts +2 -2
  169. package/dist/runs.js +2 -1
  170. package/dist/runtime-governance.d.ts +5 -5
  171. package/dist/sandbox-git-lease.d.ts +49 -0
  172. package/dist/sandbox-git-lease.js +60 -0
  173. package/dist/sandbox-git-plane.d.ts +84 -0
  174. package/dist/sandbox-git-plane.js +105 -0
  175. package/dist/security.d.ts +16 -4
  176. package/dist/security.js +8 -3
  177. package/dist/server-secret-env.d.ts +6 -0
  178. package/dist/server-secret-env.js +14 -0
  179. package/dist/session-error-wire.d.ts +14 -6
  180. package/dist/session-error-wire.js +2 -1
  181. package/dist/session-policy-wire.d.ts +1 -1
  182. package/dist/session-policy-wire.js +2 -2
  183. package/dist/session-sync.d.ts +1 -1
  184. package/dist/spec-fields.d.ts +43 -13
  185. package/dist/spec-fields.js +33 -12
  186. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  187. package/dist/stored-request-prompt-profile-m30.js +25 -0
  188. package/dist/task-mcp.d.ts +1 -1
  189. package/dist/task-settings.d.ts +2 -2
  190. package/dist/tool-approval.d.ts +12 -2
  191. package/dist/tool-approval.js +8 -3
  192. package/dist/tool-rule-names.d.ts +5 -0
  193. package/dist/trace/core-keyset-guard.d.ts +4 -4
  194. package/dist/trace/engine-notice-wire.d.ts +1 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/exit-seat.js +2 -2
  197. package/dist/trace/ledger-sink.d.ts +4 -5
  198. package/dist/trace/ledger-sink.js +3 -7
  199. package/dist/trace/project.d.ts +37 -3
  200. package/dist/trace/project.js +20 -6
  201. package/dist/trace/redact.d.ts +18 -6
  202. package/dist/trace/redact.js +118 -53
  203. package/dist/trace/self-minted-wire.d.ts +377 -3
  204. package/dist/trace/self-minted-wire.js +122 -0
  205. package/dist/trace/wire-projection-faces.d.ts +5 -3
  206. package/dist/trace/wire-projection-faces.js +7 -3
  207. package/dist/web-admin-client/apply-effective.js +0 -1
  208. package/dist/web-admin-client/center-request.d.ts +20 -2
  209. package/dist/web-admin-client/center-request.js +40 -5
  210. package/dist/web-admin-client/http-client.js +6 -6
  211. package/dist/web-admin-client/types.d.ts +5 -5
  212. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  213. package/package.json +2 -2
  214. package/wire-changes/server@7.137.0.json +825 -0
@@ -0,0 +1,20 @@
1
+ import { BROKER_HOST, BROKER_LEASE_PATH, BROKER_LEASE_POLL_MS, BROKER_PORT, BROKER_PROBE_ARG } from "./constants.js";
2
+ import { probeBroker, runBroker } from "./broker.js";
3
+ const PROBE_TIMEOUT_MS = 2_000;
4
+ async function main() {
5
+ if (process.argv.includes(BROKER_PROBE_ARG)) {
6
+ process.exit((await probeBroker(BROKER_HOST, BROKER_PORT, PROBE_TIMEOUT_MS)) ? 0 : 1);
7
+ }
8
+ await runBroker({
9
+ leasePath: BROKER_LEASE_PATH,
10
+ host: BROKER_HOST,
11
+ port: BROKER_PORT,
12
+ pollMs: BROKER_LEASE_POLL_MS,
13
+ log: (e) => void process.stdout.write(`${JSON.stringify(e)}\n`),
14
+ });
15
+ }
16
+ main().catch((e) => {
17
+ process.stderr.write(`${JSON.stringify({ event: "broker_failed", error: e instanceof Error ? e.message : String(e) })}\n`);
18
+ process.exit(1);
19
+ });
20
+ //# sourceMappingURL=main.js.map
@@ -38,7 +38,7 @@ import type { NamedKeysOf } from "../request-key-closure.js";
38
38
  * B.2b)与 CHANGELOG。present-iff 的位与成员照样登记(在场律写在附录的「在场律」列)。
39
39
  */
40
40
  export declare const COMPOSITE_CAPABILITY_MEMBERS: {
41
- readonly deviceExecutor: readonly ["enabled", "protocolVersion", "maxInflightPerDevice", "wsPath", "management"];
41
+ readonly deviceExecutor: readonly ["enabled", "protocolVersion", "maxInflightPerDevice", "wsPath", "management", "relayUrl"];
42
42
  readonly fleet: readonly ["stream", "sessionScope", "observe", "resume", "bgNotifyFailClosed", "steerPriority", "hookFailureNotice", "leaseClient", "generationEcho"];
43
43
  readonly memoryEngine: readonly ["backend", "vectorMode"];
44
44
  readonly store: readonly ["declared", "backend", "durable", "degradedReason", "sql"];
@@ -55,6 +55,7 @@ export declare const COMPOSITE_CAPABILITY_MEMBERS: {
55
55
  readonly sealedKey: readonly ["alg", "publicKeyId", "publicKey"];
56
56
  readonly workspaceSync: readonly ["persist", "keep", "timeoutMs", "cache"];
57
57
  readonly userAttachments: readonly ["entries", "kinds", "supplies", "limits"];
58
+ readonly credentialPlane: readonly ["sandboxGit"];
58
59
  };
59
60
  type CompositeTable = typeof COMPOSITE_CAPABILITY_MEMBERS;
60
61
  /** 值的**具名成员对象臂**(标量 / 字面词 / `false` / `null` / `undefined` / 数组 / 函数臂剥掉)。 */
@@ -1,5 +1,5 @@
1
1
  export const COMPOSITE_CAPABILITY_MEMBERS = {
2
- deviceExecutor: ["enabled", "protocolVersion", "maxInflightPerDevice", "wsPath", "management"],
2
+ deviceExecutor: ["enabled", "protocolVersion", "maxInflightPerDevice", "wsPath", "management", "relayUrl"],
3
3
  fleet: ["stream", "sessionScope", "observe", "resume", "bgNotifyFailClosed", "steerPriority", "hookFailureNotice", "leaseClient", "generationEcho"],
4
4
  memoryEngine: ["backend", "vectorMode"],
5
5
  store: ["declared", "backend", "durable", "degradedReason", "sql"],
@@ -16,6 +16,7 @@ export const COMPOSITE_CAPABILITY_MEMBERS = {
16
16
  sealedKey: ["alg", "publicKeyId", "publicKey"],
17
17
  workspaceSync: ["persist", "keep", "timeoutMs", "cache"],
18
18
  userAttachments: ["entries", "kinds", "supplies", "limits"],
19
+ credentialPlane: ["sandboxGit"],
19
20
  };
20
21
  export function gateCompositeMembers(body) {
21
22
  return body;
@@ -3,6 +3,7 @@ import { ownCredentialsWithheld, ownCredentialValuesWithheld } from "../server-s
3
3
  const ENGINE_CAPABILITY_FACTS = {
4
4
  "inherit_env.own_credentials_withheld": ownCredentialsWithheld,
5
5
  "inherit_env.own_credential_values_withheld": ownCredentialValuesWithheld,
6
+ "mods.skill_seat": () => false,
6
7
  };
7
8
  export function engineCapabilityBits() {
8
9
  const out = {};
@@ -2,7 +2,7 @@ import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, t
2
2
  import type { Logger } from "../observability/logger.js";
3
3
  import { type SubagentExtraToolSet } from "./builtin-tools.js";
4
4
  import { type RepoReadClient } from "./repo-tools.js";
5
- import { type LoadedSkill, type ScenarioSkillEntry } from "./skills.js";
5
+ import { type LoadedSkill, type RunSkillEntry, type ScenarioSkillEntry, type SkillSource } from "./skills.js";
6
6
  import { type ScenarioHands } from "./hands-lane.js";
7
7
  import type { DelegationRootWitness } from "../delegated-session-owner.js";
8
8
  /**
@@ -315,14 +315,29 @@ export declare function centerScenarios(deps: ScenarioDeps, specs: CenterScenari
315
315
  shadows: string[];
316
316
  };
317
317
  /**
318
- * Personalization seam: merge the caller's own skills AFTER the scenario's, with
319
- * SCENARIO-WINS on a name clash — the scenario / center skills own the name (they are the deployment's baseline
320
- * wording, not an enforcement face: permissions / write gates / hooks do not ride on skills, and a caller may
321
- * unload the whole set with `disableSkills`, [ref]); user content never shadows them. Duplicate names within the user list keep the first. Clashes are logged for audit
322
- * (silent drops would make "my skill didn't fire" undebuggable). Input is already shape/size-validated
323
- * by the HTTP layer (validateUserSkills); this stays pure merge policy.
318
+ * 一条 run 的技能(交给引擎的 `TaskSpec.skills`)与它的名册(`wiring_manifest.skills`)—— **同一次合并的两个投影**,铸点只有 {@link buildRunSkills}。
319
+ * · `specs` —— 交给引擎的那一份(`undefined` = 一只都没有且没被关;`disable` ⇒ 显式 `[]`,引擎对空表零 `<skills>` 块、不挂 `Skill`);
320
+ * · `roster` —— 同一组、同一个序,逐条带来处;零技能 ⇒ `[]`(零技能是一句陈述,不是缺席)。
324
321
  */
325
- export declare function mergeUserSkills(scenarioSkills: SkillSpec[] | undefined, userSkills: SkillSpec[] | undefined, logger?: Logger): SkillSpec[] | undefined;
322
+ export interface RunSkills {
323
+ readonly specs: SkillSpec[] | undefined;
324
+ readonly roster: RunSkillEntry[];
325
+ }
326
+ /**
327
+ * Personalization seam + run 名册的**唯一铸点**:调用方自带的技能接在场景的之后,**同名场景赢**(场景 / 中心的技能拥有那个名字 —— 它们是部署的基线措辞,
328
+ * 不是执法面:权限 / 写门 / hook 都不骑在技能上,调用方要整组卸掉用 `disableSkills`,[ref]);用户内容永不遮住它们。用户表内重名留第一个。
329
+ * 被丢的名字落审计行(静默丢会让「我的技能没生效」查不出来)。形已由 HTTP 门判过(`validateUserSkills`);本函数只做合并政策与来处。
330
+ *
331
+ * 来处(`roster[].source`):场景那一半按**部署装载簿**的来处标(`deploymentSources`:装载腿写下的 `LoadedSkill.source`,名字在装载簿里唯一 ——
332
+ * 装载器同名即抛、配置源按名覆盖);场景里有、装载簿里没有的技能是嵌入方场景代码自带的 ⇒ `factory`(本部署自带,不是配置源交来的、也不是这次请求带的)。
333
+ * 请求那一半恒 `request`。`disable` ⇒ 两个投影都空(`specs: []`、`roster: []`)。
334
+ */
335
+ export declare function buildRunSkills(args: {
336
+ readonly scenario: SkillSpec[] | undefined;
337
+ readonly request: readonly SkillSpec[] | undefined;
338
+ readonly disable: boolean;
339
+ readonly deploymentSources: ReadonlyMap<string, SkillSource>;
340
+ }, logger?: Logger): RunSkills;
326
341
  /** center caps view 的解析结果两键(形状锚定)。`scenario` null = 三层皆未配。 */
327
342
  export interface ScenarioRuling {
328
343
  scenario: string | null;
@@ -262,23 +262,28 @@ const REVIEWER_ROLE_TEXT = "You are a senior code reviewer. Call repo_tree to ma
262
262
  "the files in scope, then review them yourself. " +
263
263
  "Produce ONE prioritized review: a short summary, then findings grouped by severity " +
264
264
  "(blocker / major / minor), each with `path:line`, the problem, and a concrete fix. Cite real code — never invent files or symbols.";
265
- export function mergeUserSkills(scenarioSkills, userSkills, logger) {
266
- if (!userSkills || userSkills.length === 0)
267
- return scenarioSkills;
268
- const reserved = new Set((scenarioSkills ?? []).map((s) => s.name));
269
- const out = [...(scenarioSkills ?? [])];
265
+ export function buildRunSkills(args, logger) {
266
+ if (args.disable)
267
+ return { specs: [], roster: [] };
268
+ const scenario = args.scenario ?? [];
269
+ const roster = scenario.map((s) => ({ name: s.name, description: s.description, source: args.deploymentSources.get(s.name) ?? "factory" }));
270
+ if (!args.request || args.request.length === 0)
271
+ return { specs: args.scenario, roster };
272
+ const reserved = new Set(scenario.map((s) => s.name));
273
+ const specs = [...scenario];
270
274
  const dropped = [];
271
- for (const s of userSkills) {
275
+ for (const s of args.request) {
272
276
  if (reserved.has(s.name)) {
273
277
  dropped.push(s.name);
274
278
  continue;
275
279
  }
276
280
  reserved.add(s.name);
277
- out.push(s);
281
+ specs.push(s);
282
+ roster.push({ name: s.name, description: s.description, source: "request" });
278
283
  }
279
284
  if (dropped.length > 0)
280
285
  logger?.warn("user_skills_dropped", { reason: "name reserved by scenario (or duplicate)", names: dropped });
281
- return out;
286
+ return { specs, roster };
282
287
  }
283
288
  function registeredScenariosClause(known) {
284
289
  return `this deployment's scenarios are: ${known.join(", ")}`;
@@ -1,4 +1,4 @@
1
- import type { SkillSpec } from "@sema-agent/core";
1
+ import type { SkillSpec, TaskSpec } from "@sema-agent/core";
2
2
  import type { RemoteExecProvider } from "../execution-lane-caps.js";
3
3
  import { type Logger } from "../observability/logger.js";
4
4
  /**
@@ -90,14 +90,37 @@ export declare function loadDeploymentSkills(config: {
90
90
  /** 一个技能范围交进 `TaskSpec` 的技能(场景工厂的 `skills`)。范围从哪来见 `scenarios.ts` 的 `BUILTIN_SCENARIO_SKILL_SCOPE`(center 场景 = 它自己的名字)。 */
91
91
  export declare function skillsForScenario(loaded: readonly LoadedSkill[], scope: string): SkillSpec[];
92
92
  /**
93
- * [ref] —— 场景卡技能清单的一条(`GET /v1/capabilities/scenarios/:name` 的 `skills[]`):名字 / 描述 / 来处,**不带正文**,也不带技能上的两只工具清单
94
- * (`allowedTools` / `disallowedTools`)。名字就是提交体 `userInvokedSkills` 点名时用的那个串。
93
+ * 技能清单的一条 —— **一张形,两个面**:场景卡(`GET /v1/capabilities/scenarios/:name` 的 `skills[]`,[ref])与 run 级名册
94
+ * (`wiring_manifest.skills[]`)。名字 / 描述 / 来处,**不带正文**,也不带技能上的两只工具清单(`allowedTools` / `disallowedTools`)。
95
+ * 名字就是提交体 `userInvokedSkills` 点名时用的那个串。两个面只差来处的值域(`S`):场景卡上只有部署两处,run 名册上多一个 `request`。
95
96
  */
96
- export interface ScenarioSkillEntry {
97
+ export interface SkillListEntry<S extends string> {
97
98
  readonly name: string;
98
99
  readonly description: string;
99
- readonly source: SkillSource;
100
+ readonly source: S;
100
101
  }
102
+ /** 场景卡技能清单的一条(来处 = 部署两处)。 */
103
+ export type ScenarioSkillEntry = SkillListEntry<SkillSource>;
104
+ /** run 级技能来处的闭集 = 部署两处({@link SkillSource})∪ 本请求体 `skills`。 */
105
+ export type RunSkillSource = SkillSource | "request";
106
+ /** run 级名册的一条(`wiring_manifest.skills[]`)。铸点唯一:`capabilities/scenarios.ts` 的 `buildRunSkills`。 */
107
+ export type RunSkillEntry = SkillListEntry<RunSkillSource>;
108
+ /**
109
+ * run 级技能名册在 TaskSpec 上的**显式字段键** —— 与执行车道字段(`hands-lane.ts` 的 `HANDS_LANE_FIELD`,[ref])同一形:symbol 键随
110
+ * spread / rest 解构走(续跑腿的 `taskConfig` 就是 rest 解构出来的),对上游 core 的键面不可见(JSON / 结构化克隆跳过 symbol 键)。
111
+ * 铸点唯一 = `boot/resolve-spec.ts` 的 resolveSpec 出口({@link stampRunSkills});读点唯一 = {@link runSkillsOf}(每条投 `wiring_manifest` 的腿)。
112
+ */
113
+ export declare const RUN_SKILLS_FIELD: unique symbol;
114
+ /**
115
+ * 铸名册(**单一属主**:只许 resolveSpec 的出口调用)。字段 `enumerable`(复制得走)、`writable:false` + `configurable:false`(同一只 spec 被第二次铸 ⇒
116
+ * 当场 TypeError,两个写者是漂移的成因)。名册冻结一份拷贝(spec 与腿不共享可变引用)。
117
+ */
118
+ export declare function stampRunSkills<T extends TaskSpec>(spec: T, roster: readonly RunSkillEntry[]): T;
119
+ /**
120
+ * 读名册。`undefined` = 这只 spec 不是本仓装配出口产的(嵌入方自写 `resolveSpec` / 测试桩)⇒ 腿上没有关于技能的陈述(段缺席)。
121
+ * 不是保护型量(缺席只少一段披露,不放宽任何东西)⇒ 缺席照常跑,与车道字段的「缺席即拒」不同。入参收 `object`:续跑腿手里是 rest 解构出来的 taskConfig。
122
+ */
123
+ export declare function runSkillsOf(spec: object): readonly RunSkillEntry[] | undefined;
101
124
  /** 一个技能范围的卡上清单 —— 与 {@link skillsForScenario} 同一只过滤、同一个序,只是投影不同。 */
102
125
  export declare function scenarioSkillEntries(loaded: readonly LoadedSkill[], scope: string): ScenarioSkillEntry[];
103
126
  /** {@link parseFrontmatter} 的三种读数(闭集):`absent` = 第一行不是围栏,没有 frontmatter(正当的全局技能)· `block` = 围栏收好了的块 · `unclosed` = 第一行是围栏而块没收好(读不了 —— 调用方拒载)。 */
@@ -127,6 +127,14 @@ function skillsInScope(loaded, scope) {
127
127
  export function skillsForScenario(loaded, scope) {
128
128
  return skillsInScope(loaded, scope).map((k) => k.spec);
129
129
  }
130
+ export const RUN_SKILLS_FIELD = Symbol("sema-server.runSkills");
131
+ export function stampRunSkills(spec, roster) {
132
+ Object.defineProperty(spec, RUN_SKILLS_FIELD, { value: Object.freeze(roster.map((e) => Object.freeze({ ...e }))), enumerable: true, writable: false, configurable: false });
133
+ return spec;
134
+ }
135
+ export function runSkillsOf(spec) {
136
+ return spec[RUN_SKILLS_FIELD];
137
+ }
130
138
  export function scenarioSkillEntries(loaded, scope) {
131
139
  return skillsInScope(loaded, scope).map((k) => ({ name: k.spec.name, description: k.spec.description, source: k.source }));
132
140
  }
@@ -3,7 +3,7 @@ import { closeSync, fchmodSync, fsyncSync, openSync, readdirSync, readFileSync,
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { WEB_ADMIN_ENV, WebAdminCredentialEnvelope, parseWebAdminCredentialFile } from "@sema-agent/settings-schema";
5
5
  import { ACCESS_TOKEN_TTL_SECONDS, OAuthErrorResponse, TokenGrantResponse } from "@sema-agent/settings-schema/api/auth";
6
- import { CENTER_PULL_TIMEOUT_MS, centerTokenRefresh } from "./web-admin-client/center-request.js";
6
+ import { CENTER_PULL_TIMEOUT_MS, CenterRedirectRefusedError, centerTokenRefresh } from "./web-admin-client/center-request.js";
7
7
  export const CENTER_CREDENTIAL_RENEW_LEAD_KEY = "CENTER_CREDENTIAL_RENEW_LEAD_MS";
8
8
  export const CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS = 5 * 60_000;
9
9
  export const CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS = CENTER_PULL_TIMEOUT_MS;
@@ -277,6 +277,10 @@ export function createCenterCredentialRenewer(deps) {
277
277
  res = await centerTokenRefresh(envelope.issuer, envelope.refreshToken, { timeoutMs: CENTER_PULL_TIMEOUT_MS, ...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}) });
278
278
  }
279
279
  catch (err) {
280
+ if (err instanceof CenterRedirectRefusedError) {
281
+ judgeRefusal(usedDigest, "rejected", { status: err.status });
282
+ return;
283
+ }
280
284
  settleRetrying("center_unavailable", usedDigest, { error: err instanceof Error ? err.name : "fetch error" });
281
285
  return;
282
286
  }
@@ -12,16 +12,9 @@
12
12
  * 本条在起服第 6 段 checkpoint 店建成、封签器的装配判(`CHECKPOINT_CTX_SEALING_REQUIRED`)过了之后调一次,早于任何路由装配(与 M10 / M19 / M25
13
13
  * 同为在迁移段之外跑的条目)。读数一行 info `checkpoint_ctx_credentials_sealed`,不进 `OneTimeMigrationReport`(登记行 `reportKeys` 空)。
14
14
  */
15
- import { type CheckpointCtxSealer, type SealedCheckpointCtx } from "./http/checkpoint-ctx.js";
15
+ import { type CheckpointCtxSealer } from "./http/checkpoint-ctx.js";
16
16
  import { type OneTimeMigrationWindow } from "./plugins/one-time-migrations.js";
17
- import type { CtxRowsRewriteM28 } from "./plugins/checkpoint-store-sql.js";
18
- /** 两孪生的店腿(`SqlCheckpointStore` / `LocalCheckpointStore` 的 `rewriteCtxRowsM28`)。 */
19
- export interface CheckpointCtxRowsRewriterM28 {
20
- rewriteCtxRowsM28(rewrite: (sessionId: string, ctx: object) => {
21
- readonly ctx: SealedCheckpointCtx;
22
- readonly values: number;
23
- } | undefined): Promise<CtxRowsRewriteM28>;
24
- }
17
+ import type { CheckpointCtxRowsRewriter } from "./plugins/checkpoint-store-sql.js";
25
18
  /** M28 一次起服的读数(起服 info 行逐键打出;测试按它断言)。`window: "closed"` ⇒ 一行都没读(其余全 0)。 */
26
19
  export interface CheckpointCtxSealM28Report {
27
20
  readonly window: "open" | "closed";
@@ -35,7 +28,7 @@ export interface CheckpointCtxSealM28Report {
35
28
  * 跑一次 M28(`today` = UTC 日历日 YYYY-MM-DD;`window` 缺省 = 登记表 M28 那一行 —— 只有测试递别的窗)。窗关 ⇒ 不扫。
36
29
  * 店腿的每一行:`sealStoredCheckpointCtxM28` 交回新体(凭据位上的明文封好)⇒ 回写;交回 `undefined`(没有明文)⇒ 不写。
37
30
  */
38
- export declare function sealCheckpointCtxCredentialsM28(store: CheckpointCtxRowsRewriterM28, sealer: CheckpointCtxSealer, opts: {
31
+ export declare function sealCheckpointCtxCredentialsM28(store: CheckpointCtxRowsRewriter, sealer: CheckpointCtxSealer, opts: {
39
32
  readonly today: string;
40
33
  readonly window?: Pick<OneTimeMigrationWindow, "removableAfter">;
41
34
  }): Promise<CheckpointCtxSealM28Report>;
@@ -10,7 +10,7 @@ export async function sealCheckpointCtxCredentialsM28(store, sealer, opts) {
10
10
  if (!migrationWindowOpen(opts.window ?? m28Window(), opts.today)) {
11
11
  return { window: "closed", rowsScanned: 0, rowsSealed: 0, valuesSealed: 0, rowsUnreadable: 0, rowsTooLarge: 0 };
12
12
  }
13
- const r = await store.rewriteCtxRowsM28((sessionId, ctx) => {
13
+ const r = await store.rewriteCtxRows((sessionId, ctx) => {
14
14
  const out = sealStoredCheckpointCtxM28(ctx, sealer, sessionId);
15
15
  return out === undefined ? undefined : { ctx: out.ctx, values: out.valuesSealed };
16
16
  });
@@ -7,13 +7,13 @@ export declare const MAX_CHECKPOINT_ID_CHARS = 128;
7
7
  export declare function withoutCheckpointToken(text: string, token: unknown): string;
8
8
  /** 持久层读出的 `checkpointId` 过信任边界:是串、非空、不超长、不含 token(大小写不敏感)⇒ 原值;否则 `undefined`。判据全文见本文件头注。 */
9
9
  export declare function trustedCheckpointId(raw: unknown, token: unknown): string | undefined;
10
- /** 带 `checkpointId` 当 join 键的两类 durable 读面(计数 detail 的 `face`)。 */
11
- export type DurableCheckpointFace = "approvals-queue" | "assistant-inbox";
10
+ /** 带 `checkpointId` 当 join 键的 durable 面(计数 detail 的 `face`):运维队列行、停驻收件行,与 7.137.0 起的 park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`,写时判一次)。 */
11
+ export type DurableCheckpointFace = "approvals-queue" | "assistant-inbox" | "run-events-park";
12
12
  /**
13
13
  * durable 读面的一行 → 它的身份片段,**由调用方原样展开进行投影**(`...durableRowIdentity(face, raw, token)`):读得出可信值 ⇒ `{ checkpointId }`;
14
14
  * 读不出 ⇒ `{}`(这一行**照列**,键缺席)+ 计一次(见本文件头注「durable 面的 join 键」)。
15
15
  * `raw === undefined` = 行上没有这一格(`why=absent`,老行);在场却没过信任边界(非串 / 空串 / 超长 / 含 token / 读不出)= `why=untrusted`。
16
- * 三处读面(两只店的 `listPending` + 收件 handler)只经这一只 —— 读法、计数、缺席形各只有一份。
16
+ * 三处读面(两只店的 `listPending` + 收件 handler)与 park 账本行的唯一构造器(`trace/project.ts` 的 `parkEventData`)只经这一只 —— 读法、计数、缺席形各只有一份。
17
17
  */
18
18
  export declare function durableRowIdentity(face: DurableCheckpointFace, raw: unknown, token: unknown): {
19
19
  readonly checkpointId?: string;
@@ -7,6 +7,7 @@ import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE
7
7
  import { SESSION_SNAPSHOT_BUCKET_DEFAULT, WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "./object-store-buckets.js";
8
8
  import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE } from "./workspace-space.js";
9
9
  import { APPROVAL_ASK_RETENTION_DEFAULT_MS, APPROVAL_ASK_RETENTION_FLOOR_MS } from "./approval-ask-retention.js";
10
+ import { SANDBOX_SECRET_SEAT_VERBS } from "./plugins/k8s-exec-protocol.js";
10
11
  import { LIVENESS_MS_DEFAULT } from "./exec-bounds.js";
11
12
  import { THINKING_FORMATS } from "./model-compat.js";
12
13
  import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CENTER_BASE_URL_RULE, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, DEVICE_DELIVERY_TIMEOUT_BOUNDS, DEVICE_EXEC_TIMEOUT_BOUNDS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
@@ -125,6 +126,7 @@ export const CONFIG_CATALOG = [
125
126
  r("DEVICE_MAX_INFLIGHT_PER_DEVICE", "orchestration", "number", `per-device 在途上限(超额云侧排队;原值进 helloAck.maxInflight;须为 ≥ ${DEVICE_HELLO_ACK_BOUNDS.maxInflight.min} 的规范十进制整数,其余拒启 —— 0 会让每次派发都 device.busy)`, { derivedDefaultNote: "缺席 ⇒ 4(§11-O3 裁定值)" }),
126
127
  r("DEVICE_MAX_PREAUTH_PER_REMOTE", "orchestration", "number", "未鉴权握手的 per-remote 并发帽(正整数或字面 off;XFF/NAT 形用 off+全局帽)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 8" }),
127
128
  r("DEVICE_RECONNECT_GRACE_MS", "orchestration", "number", `device 断连后在途不判丢的窗(ms;同 epoch 重连续接;原值进 helloAck.reconnectGraceMs;须为 [${DEVICE_HELLO_ACK_BOUNDS.reconnectGraceMs.min}, ${DEVICE_HELLO_ACK_BOUNDS.reconnectGraceMs.max}] 内规范十进制整数,其余拒启)`, { derivedDefaultNote: "缺席 ⇒ adapter 默认 120s" }),
129
+ r("DEVICE_RELAY_URL", "orchestration", "url", "设备直达入口:设备连不到本引擎自己的地址(引擎在中继 / 网关之后)时拨的那一个 ws(s) URL,逐字上 /v1/capabilities 的 deviceExecutor.relayUrl;须为无 userinfo / query / fragment、不带凭据形的 ws:// 或 wss:// 绝对地址,其余拒启。不从 FLEET_ADVERTISE_ADDRESS 派生(那是对中心公告的地址,受众不是设备)", { derivedDefaultNote: "缺席 ⇒ 能力面不带 relayUrl,设备按引擎地址 + deviceExecutor.wsPath 拨号" }),
128
130
  r("DEVICE_SUPERSEDE", "orchestration", "enum", "同 deviceId 二连的顶替策略", { enumValues: ["stale_only", "deny"], staticDefault: "stale_only", danger: SEC }),
129
131
  r("DIRECT_APPROVAL_DOOR", "approval", "boolean", "直连 /decide 门(无 BFF 的桌面/CLI 客户端;开 ⇒ 要求签名 JWT+HMAC 信封,principal 头降级不可信)", { staticDefault: "false", danger: SEC, configKey: "directApprovalDoor" }),
130
132
  r("DOCKER_COMMAND_TIMEOUT_MS", "orchestration", "number", "local-docker 车道单命令超时(ms)"),
@@ -163,7 +165,7 @@ export const CONFIG_CATALOG = [
163
165
  r("FLEET_GENERATION", "integrations", "string", "fleet 成员的代际(配置渲染器写;引擎回声到心跳与 /health 的 generation)。缺席 ⇔ 非 fleet 成员(不续服务租约、不受围栏);在场必须是规范十进制串(\"0\" / \"7\";空串、前导零、符号、空白拒启)", { danger: SEC, configKey: "fleetGeneration" }),
164
166
  r("FLEET_LEASE_FAIL_CLOSED", "integrations", "boolean", "租约连接级失败(中心鉴权拒 / 凭证不可读 / 不可达 / 5xx / 超时 / 坏体)时计费入口拒 503 fleet.lease_unavailable(true)还是放行(false);409 not-lease 与耗尽 429 不受它管;词表外拒启", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 按部署姿态派生:REQUIRE_PRINCIPAL=true ⇒ true(多租:额度是安全轴);单用户 ⇒ false(AP 姿态)。显式值压过派生。生效值见 caps centerWiring.failMode", configKey: "fleetLeaseFailClosed" }),
165
167
  r("FLEET_LEASE_OUTAGE_MAX_MS", "integrations", "number", "一台实例「活着而不服务」的最长时长 T_max(ms),两处读它:① 服务租约中心不可达的容忍上限 —— 租约过期满这么久 ⇒ 新计费提交 503 state.lease_expired(取消 / HITL 应答 / 续跑 / 读面照放行,在飞不动,/readyz 不翻),只在 fleet 成员 ∧ 中心宣告 servingLease 能力位时生效;② 排空口 POST /v1/admin/drain 的期限上界 —— deadlineAt 离此刻超过它 ⇒ 400 request.field_invalid,成员与非成员同读;< 60000(租约 TTL 地板)或非整数拒启", { danger: SEC, staticDefault: String(FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS), configKey: "fleetLeaseOutageMaxMs" }),
166
- r("FLEET_SCOPE", "integrations", "string", "本引擎所在的租户 scope(配置渲染器为多租引擎写;唯一读者 = 排空口 POST /v1/admin/drain 的身份比对,请求不带 scope 也读成缺省 scope)。在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启);与 FLEET_GENERATION 不联动(成员缺席 ⇒ 缺省 scope)。续租体与心跳不带它(scope 由中心凭证绑定)", { danger: SEC, staticDefault: GLOBAL_SCOPE, configKey: "fleetScope" }),
168
+ r("FLEET_SCOPE", "integrations", "string", "本引擎所在的租户 scope(配置渲染器对每台受管引擎写,缺省 scope 也写;手接的引擎缺席 = 缺省 scope;唯一读者 = 排空口 POST /v1/admin/drain 的身份比对,请求不带 scope 也读成缺省 scope)。在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启);与 FLEET_GENERATION 不联动(成员缺席 ⇒ 缺省 scope)。续租体与心跳不带它(scope 由中心凭证绑定)", { danger: SEC, staticDefault: GLOBAL_SCOPE, configKey: "fleetScope" }),
167
169
  r("FORK_ENABLED", "orchestration", "boolean", "CC /fork(core Fork 工具)装配开关", { derivedDefaultNote: "posture 三态:缺席 = 单用户 ⇒ on / 多租户 ⇒ off(per-principal entitlement 另判)", configKey: "forkEnabled" }),
168
170
  r("GATEWAY_MAX_RETRIES", "modelPlane", "number", "openai 兼容腿每次调用重试上限", { derivedDefaultNote: "缺席 ⇒ 不传引擎,引擎默认当家" }),
169
171
  r("GIT_API_BASEURL", "integrations", "url", "repo 工具的 Git host API base", { configKey: "gitApiBaseUrl" }),
@@ -204,7 +206,8 @@ export const CONFIG_CATALOG = [
204
206
  r("K8S_MOUNT_PATH", "orchestration", "path", "k8s 工作区挂载路径"),
205
207
  r("K8S_NAMESPACE", "orchestration", "string", "沙箱 pod namespace"),
206
208
  r("K8S_RUNTIME_CLASS", "orchestration", "string", "沙箱 pod runtimeClass(Kata 等)"),
207
- r("K8S_SANDBOX_ENV", "orchestration", "secret", "转发进 k8s 沙箱 pod 容器 env 的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值只进 pod env —— 不进 spec 的其它位置 / 工作区 / 快照 / center / 事件日志;与 LSP_ENV_ALLOW / E2B_SANDBOX_ENV / DOCKER_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;与包源派生 env 撞名时显式名单胜;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
209
+ r("K8S_SANDBOX_BROKER_IMAGE", "orchestration", "string", "成员 git 凭据代理容器的镜像(k8s 沙箱 pod 的 containers[1],只挂在成员 run 的 pod 上;建议填本引擎自身镜像 —— 代理是本包里固定路径的一只程序,命令固定);不设 ⇒ 成员 git 凭据平面关(GET /v1/capabilities 的 credentialPlane.sandboxGit = broker_image_unset),任何 pod 都不挂代理,pod 形逐字节同不设时;只在 REMOTE_EXEC=k8s 被选中时求值"),
210
+ r("K8S_SANDBOX_ENV", "orchestration", "secret", `转发进 k8s 沙箱 pod 容器 env 的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值只进该 pod 自己的 Secret —— pod spec 的容器 env 只带 secretKeyRef 引用、不带值,Secret 随 pod 由集群回收;不进工作区 / 快照 / center / 事件日志;沙箱命名空间的 Role 要 secrets ${SANDBOX_SECRET_SEAT_VERBS.env.join(" / ")},起服问不许即拒启;与 LSP_ENV_ALLOW / E2B_SANDBOX_ENV / DOCKER_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;与包源派生 env 撞名时显式名单胜;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)`, { danger: SEC }),
208
211
  r("K8S_SANDBOX_IMAGE", "orchestration", "string", "k8s 车道沙箱镜像(REMOTE_EXEC=k8s 必需件)"),
209
212
  r("K8S_SANDBOX_IPV6", "orchestration", "enum", "k8s 沙箱 pod 的 IPv6:disabled = pod 级 sysctl 关 IPv6(net.ipv6.conf.all / default.disable_ipv6=1;两只都不在 k8s 的 safe sysctl 表里 ⇒ 每个节点 kubelet 要 --allowed-unsafe-sysctls 点名,否则 pod 起不来;沙箱 ns 不能 enforce PodSecurity baseline / restricted);enabled = 不动 pod(pod 保留平台给的 IPv6,只执行 IPv4 的出网策略管不到它,起服自检每次一行 sandbox_egress_policy_gap{kind: ipv6_enabled});词表外拒启", { enumValues: SANDBOX_IPV6_WORDS, staticDefault: SANDBOX_IPV6_DEFAULT, danger: SEC }),
210
213
  r("K8S_TIMEOUT_MS", "orchestration", "number", "k8s 沙箱 pod 寿命(activeDeadlineSeconds,ms;整只 pod 的泄漏护栏,后台任务超时也被它钳;各条命令的超时见工具超时)"),
@@ -290,9 +293,9 @@ export const CONFIG_CATALOG = [
290
293
  r("MODEL_CHEAP_ID", "modelPlane", "string", "cheap 槽模型 id(subagent/summarize 两 role 走它;未设或与 MODEL_ID 同 id=全落主模型,同 id 不铸 cheap 槽)", { danger: BILL, center: cModels("models[].id") }),
291
294
  r("MODEL_CHEAP_MAX_TOKENS", "modelPlane", "number", "cheap 槽输出上限", { derivedDefaultNote: "缺席 ⇒ 继承主模型", center: cModels("models[].maxTokens") }),
292
295
  r("MODEL_CHEAP_REASONING", "modelPlane", "boolean", "cheap 槽 reasoning 声明(三态:缺席继承主模型)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
293
- r("MODEL_CHEAP_VISION", "modelPlane", "boolean", "cheap 槽 vision 声明(三态:缺席继承主模型;text-only cheap 必设 false 让预检真拦)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
296
+ r("MODEL_CHEAP_VISION", "modelPlane", "boolean", "cheap 槽 vision 声明(三态:缺席继承主模型;text-only cheap 必设 false,引擎才会在替换图片时于会话流发 `model.capability_downgraded`)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
294
297
  r("MODEL_CIRCUIT_BREAKER", "modelPlane", "boolean", "模型腿熔断开关", { staticDefault: String(RESILIENCE_DEFAULTS.circuitBreaker.value) }),
295
- r("MODEL_CODE_ROLES", "modelPlane", "csv", "给点名 role 附编码 persona(role 表内名字才生效)"),
298
+ r("MODEL_CODE_ROLES", "modelPlane", "csv", "给点名 role 按词点名引擎的编码角色基底(`promptRole: \"code\"`;role 表内名字才生效)"),
296
299
  r("MODEL_CONTEXT_WINDOW", "modelPlane", "number", "主模型上下文窗声明", { staticDefault: "262144", configKey: "model", resolve: (c) => c.model.contextWindow ?? null, center: cModels("models[].contextWindow") }),
297
300
  r("MODEL_COST_CACHE_READ", "modelPlane", "number", "主模型 cache-read 单价(per-1M USD;显式 0=声明免费,不是「没配」)", { derivedDefaultNote: "四键全缺席 ⇒ 未定价(不铸 cost 键,成本读面缺席);任一设 ⇒ 四键在场,未设的按 0 显式", danger: BILL, resolve: (c) => c.model.cost?.cacheRead ?? null, center: cModels("models[].cost.cacheRead") }),
298
301
  r("MODEL_COST_CACHE_WRITE", "modelPlane", "number", "主模型 cache-write 单价", { derivedDefaultNote: "四键全缺席 ⇒ 未定价(不铸 cost 键,成本读面缺席);任一设 ⇒ 四键在场,未设的按 0 显式", danger: BILL, resolve: (c) => c.model.cost?.cacheWrite ?? null, center: cModels("models[].cost.cacheWrite") }),
@@ -341,8 +344,8 @@ export const CONFIG_CATALOG = [
341
344
  },
342
345
  }),
343
346
  r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
344
- r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(三态:true / false / 缺席 = 未声明;false = 明说不收图:带图请求的图被换成占位句交给模型,提交时计数并告警)", {
345
- derivedDefaultNote: "缺席 ⇒ 不铸 `Model.input`(没有 true 缺省);引擎与带图预检都按「收」读(图原样交给模型,不出信号);`GET /v1/models` 该行不带 `vision` 键。要让预检对纯文本模型出信号写 MODEL_VISION=false",
347
+ r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(三态:true / false / 缺席 = 未声明;false = 明说不收图:带图请求的图被换成占位句交给模型,引擎在替换的那一刻于会话流发 `model.capability_downgraded`,每会话每模型一次)", {
348
+ derivedDefaultNote: "缺席 ⇒ 不铸 `Model.input`(没有 true 缺省);引擎按「收」读(图原样交给模型,不发 `model.capability_downgraded`);`GET /v1/models` 该行不带 `vision` 键。要让引擎对纯文本模型发该信号写 MODEL_VISION=false",
346
349
  resolve: (c) => {
347
350
  const v = modelImageInput(c.model);
348
351
  return v === "undeclared" ? null : v === "yes";
@@ -944,6 +944,10 @@ export interface ServiceConfigFlat {
944
944
  /** `K8S_SANDBOX_IPV6`(闭集与缺省的属主 `sandbox-egress-policy.ts`):`disabled` ⇒ 沙箱 pod 级 sysctl 关 IPv6;`enabled` ⇒ pod spec 不带 sysctl。
945
945
  * loader 恒铸;可选只为手搓配置。 */
946
946
  sandboxIpv6?: import("./sandbox-egress-policy.js").SandboxIpv6;
947
+ /** `K8S_SANDBOX_BROKER_IMAGE` — the member git-credential broker container's image (`containers[1]` of a member's sandbox pod; this
948
+ * engine's own image is the recommended value — the broker is a program of this package at a fixed path, `broker/constants.ts`). Absent /
949
+ * empty ⇒ the plane is off (`credentialPlane.sandboxGit` = `broker_image_unset`) and no pod carries a broker. */
950
+ brokerImage?: string;
947
951
  /** `K8S_SANDBOX_ENV=NAME,NAME` 点到、且本进程环境里真有的键 ⇒ 原值(与 e2b 的 `sandboxEnv` 同名同形、同一只解析器、同一只谓词 `allowlistedEnv`,
948
952
  * 空串值照带)。只进 pod 容器 env;装配点与包源派生 env 合并一处,显式名单胜。键缺席 = 旋钮未设 / 空串(零转发)。 */
949
953
  sandboxEnv?: Record<string, string>;
@@ -1014,6 +1018,11 @@ export interface ServiceConfigFlat {
1014
1018
  * 域 = `[1, 86400]` 的十进制正整数,坏值**拒启**([ref] A 档;解析器 `parseDeviceEnrollTokenTtlEnv`)。
1015
1019
  * 缺席 ⇒ 签发点的属主默认 900(不在 config 层抄第二份默认,与本族其余旋钮同姿势)。 */
1016
1020
  enrollTokenTtlSec?: number;
1021
+ /** `DEVICE_RELAY_URL` = 设备的**直达入口**:设备连不到本引擎自己的地址(引擎在中继 / 网关之后)时该拨的那一个 ws(s) URL,运维声明、**逐字**上能力面
1022
+ * `deviceExecutor.relayUrl`(唯一消费点 `http/routes/capabilities.ts`)。缺席 ⇒ 键缺席 = 设备按本引擎地址 + `deviceExecutor.wsPath` 拨号(既有形)。
1023
+ * **不派生**:`FLEET_ADVERTISE_ADDRESS` 是对中心公告的地址(内网 pod IP / 容器别名),受众不是用户机器上的设备;从它拼出来的入口对设备多半不可达。
1024
+ * 形门 = ws / wss 绝对地址,无 userinfo / query / fragment、不带凭据形(值逐字上能力面),坏值**拒启**(解析器 `parseDeviceRelayUrlEnv`)。 */
1025
+ relayUrl?: string;
1017
1026
  /** `DEVICE_SUPERSEDE`(默认 `stale_only`)= 同 deviceId 二连的顶替策略(§5.2)。 */
1018
1027
  supersede?: "stale_only" | "deny";
1019
1028
  } | {
package/dist/config.d.ts CHANGED
@@ -274,7 +274,7 @@ export declare function isRosterGateArmed(webAdmin: ServiceConfig["webAdmin"]):
274
274
  * core 的每一只 name-keyed policy(`createAllowDenyPolicy` / `createPermissionRulePolicy` /
275
275
  * `createApprovalPolicy`)都解 covering 拼法,而 operator 腿的消费者 `createDurableAskPolicy`
276
276
  * (`src/approval.ts`)自 core 5.60.1 的谓词根导出到货后,deny/neverAuto/require 三集合也改成
277
- * 「精确 ∪ covering」(同一只 `namespacedRuleNameCovers`)。于是本函数放行的
277
+ * 「精确 ∪ covering」(同一只 `ruleNameCovers`)。于是本函数放行的
278
278
  * `<ns>__<server>__*` / `<ns>__<server>__<glob>` 在**每一条**腿上都是**真判**,不再是
279
279
  * 「放行进 no-op」—— 而那正是 [ref] 病形(`APPROVAL_DENY=mcp__figma__*` 判 allow)的根。
280
280
  *
package/dist/config.js CHANGED
@@ -3,7 +3,7 @@ import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF, mintCapabilityKeys } from "./mod
3
3
  import { homedir } from "node:os";
4
4
  import { isAbsolute, join, posix as posixPath } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { compileAllowList, inheritEnvPolicyProblem, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, principalInboxConsumerOf } from "@sema-agent/core";
6
+ import { compileAllowList, inheritEnvPolicyProblem, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, principalInboxConsumerOf } from "@sema-agent/core";
7
7
  import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES, WEB_ADMIN_ENV, retiredEnvRefusal, stripControlAndBidi } from "@sema-agent/settings-schema";
8
8
  import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
9
9
  import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
@@ -45,7 +45,7 @@ import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js
45
45
  import { modelProviderRefusal } from "./model-provider.js";
46
46
  import { modelRouteUrlRefusal } from "./model-route-url.js";
47
47
  import { httpUrlFlaws } from "@sema-agent/settings-schema";
48
- import { buildHttpUrlRefusal } from "./url-refusal.js";
48
+ import { buildHttpUrlRefusal, buildRefusalShownUrl } from "./url-refusal.js";
49
49
  import { customPkgSourceFromEnv } from "./sandbox-pkg-source.js";
50
50
  import { anthropicCredentialRefusal, csvOf, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
51
51
  import { displayListEntry } from "./list-entry-display.js";
@@ -372,6 +372,30 @@ function parseDeviceEnrollTokenTtlEnv() {
372
372
  }
373
373
  return Number(raw);
374
374
  }
375
+ function credentialLawRewriteOf(href) {
376
+ const scanned = redactSecrets(href);
377
+ return scanned === href ? undefined : scanned;
378
+ }
379
+ function parseDeviceRelayUrlEnv() {
380
+ const raw = process.env.DEVICE_RELAY_URL;
381
+ if (raw === undefined || raw === "")
382
+ return undefined;
383
+ const scheme = /^wss?:\/\//i.exec(raw)?.[0];
384
+ const asHttp = scheme === undefined ? undefined : `${scheme.length === "wss://".length ? "https" : "http"}://${raw.slice(scheme.length)}`;
385
+ const flaws = asHttp === undefined ? ["not-ws-url"] : httpUrlFlaws(asHttp, ["userinfo", "query", "fragment"]).map((f) => (f === "not-http-url" ? "not-ws-url" : f));
386
+ if (flaws.length > 0) {
387
+ throw new Error(`env DEVICE_RELAY_URL must be an absolute ws:// or wss:// URL with no userinfo, query or fragment (ws(s)://host[:port][/path]) — found: ${flaws.join(", ")}; got ${JSON.stringify(buildRefusalShownUrl(raw))}. ` +
388
+ `It is published verbatim on GET /v1/capabilities (deviceExecutor.relayUrl) as the address devices dial, so it has no room for credentials (devices authenticate by signature). ` +
389
+ `Leave it unset when devices reach this engine at its own address — they then dial it at deviceExecutor.wsPath.`);
390
+ }
391
+ const href = new URL(raw).href;
392
+ const credentialScan = credentialLawRewriteOf(href);
393
+ if (credentialScan !== undefined) {
394
+ throw new Error(`env DEVICE_RELAY_URL carries something the credential-redaction law recognizes (${credentialScan}) — it is published verbatim on GET /v1/capabilities (deviceExecutor.relayUrl), ` +
395
+ `so it must not carry a token-shaped path segment; keep it to ws(s)://host[:port][/path] (devices authenticate by signature, not by the URL).`);
396
+ }
397
+ return raw;
398
+ }
375
399
  function parseDevicePreAuthPerRemoteEnv() {
376
400
  const raw = process.env.DEVICE_MAX_PREAUTH_PER_REMOTE;
377
401
  if (raw === undefined || raw === "")
@@ -1090,7 +1114,7 @@ function parseModelDomain() {
1090
1114
  }
1091
1115
  const cheapRef = cheapModel ? cheapModel.id : DEFAULT_MODEL_REF;
1092
1116
  const verifierEnv = process.env.MODEL_VERIFIER;
1093
- const verifierRef = verifierEnv && models[verifierEnv] ? verifierEnv : DEFAULT_MODEL_REF;
1117
+ const verifierRef = verifierEnv && Object.hasOwn(models, verifierEnv) ? verifierEnv : DEFAULT_MODEL_REF;
1094
1118
  const roles = {
1095
1119
  verifier: verifierRef,
1096
1120
  };
@@ -1105,8 +1129,8 @@ function parseModelDomain() {
1105
1129
  continue;
1106
1130
  roles[role] =
1107
1131
  typeof cur === "string"
1108
- ? { model: cur, systemPrompt: CODE_AGENT_PROMPT }
1109
- : { ...cur, systemPrompt: CODE_AGENT_PROMPT };
1132
+ ? { model: cur, promptRole: "code" }
1133
+ : { ...cur, promptRole: "code" };
1110
1134
  }
1111
1135
  const degradeOn = parseDegradeOn(csv("MODEL_DEGRADE_ON"));
1112
1136
  return {
@@ -1942,6 +1966,7 @@ function parseOrchestrationDomain(ctx) {
1942
1966
  ...(process.env.K8S_MEMORY ? { memory: process.env.K8S_MEMORY } : {}),
1943
1967
  ...(process.env.K8S_CPU ? { cpu: process.env.K8S_CPU } : {}),
1944
1968
  sandboxIpv6: enumEnv("K8S_SANDBOX_IPV6", SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS),
1969
+ ...(process.env.K8S_SANDBOX_BROKER_IMAGE ? { brokerImage: process.env.K8S_SANDBOX_BROKER_IMAGE } : {}),
1945
1970
  ...((names) => (names !== undefined ? { sandboxEnv: allowlistedEnv(names, process.env) } : {}))(parseEnvNameAllowlist("K8S_SANDBOX_ENV", singleUserTurnkey)),
1946
1971
  ...k8sWorkspaceStore(),
1947
1972
  ...((c) => (c !== undefined ? { workspaceCache: c } : {}))(k8sWorkspaceCache()),
@@ -2000,6 +2025,7 @@ function parseOrchestrationDomain(ctx) {
2000
2025
  ...((n) => (n !== undefined ? { maxInflightPerDevice: n } : {}))(parseDeviceBoundedIntEnv("DEVICE_MAX_INFLIGHT_PER_DEVICE", process.env.DEVICE_MAX_INFLIGHT_PER_DEVICE, DEVICE_HELLO_ACK_BOUNDS.maxInflight)),
2001
2026
  ...((n) => (n !== undefined ? { maxPreAuthPerRemote: n } : {}))(parseDevicePreAuthPerRemoteEnv()),
2002
2027
  ...((n) => (n !== undefined ? { enrollTokenTtlSec: n } : {}))(parseDeviceEnrollTokenTtlEnv()),
2028
+ ...((n) => (n !== undefined ? { relayUrl: n } : {}))(parseDeviceRelayUrlEnv()),
2003
2029
  ...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
2004
2030
  }
2005
2031
  :
@@ -2241,7 +2267,7 @@ function parseFleetScope(raw) {
2241
2267
  if (raw !== undefined && !ScopeId.safeParse(raw).success) {
2242
2268
  const shown = raw.length > 40 ? `${raw.slice(0, 40)}…(${raw.length} chars)` : raw;
2243
2269
  throw fleetEnvRefusal("scope_invalid", `env ${name}=${JSON.stringify(shown)} is not a scope id: it must be a lowercase slug of letters, digits and inner dashes, 1–64 characters, ` +
2244
- `not empty. The config renderer sets it on engines that serve a tenant scope; unset it for the default scope ("${fleetScopeOf(undefined)}"). ` +
2270
+ `not empty. The config renderer sets it on every engine it manages (the default scope included); on a hand-wired engine leave it unset for the default scope ("${fleetScopeOf(undefined)}"). ` +
2245
2271
  `Refusing to boot rather than answering drain requests under the wrong identity.`);
2246
2272
  }
2247
2273
  return fleetScopeOf(raw);
@@ -2330,8 +2356,8 @@ function parseA2aServe() {
2330
2356
  `inbound authentication on this endpoint is header-shaped (a service credential), not URL-embedded.`);
2331
2357
  }
2332
2358
  const parsed = new URL(rawUrl);
2333
- const a2aCredentialScan = redactSecrets(parsed.href);
2334
- if (a2aCredentialScan !== parsed.href) {
2359
+ const a2aCredentialScan = credentialLawRewriteOf(parsed.href);
2360
+ if (a2aCredentialScan !== undefined) {
2335
2361
  throw new Error(`A2A_SERVE_URL carries something the credential-redaction law recognizes (${a2aCredentialScan}) — this address is published VERBATIM on the ANONYMOUSLY readable agent card (the card's url is a functional address: peers fetch it, so it is never rewritten for display), ` +
2336
2362
  `so it must not carry credentials of ANY shape: no userinfo, no credential-named query parameter (api_key/token/…), no token-shaped path segment or parameter value. ` +
2337
2363
  `Keep A2A_SERVE_URL to scheme://host[:port][/path] and move the secret into the caller's headers.`);
@@ -103,8 +103,8 @@ export interface AbsentApprovalSeat {
103
103
  * durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留([ref] 后无活流腿放行执行
104
104
  * 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
105
105
  * park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
106
- * 工具名取 `approval-content-kind.ts` 的单一属主(它 re-export core 根导出的 `ASK_USER_QUESTION_TOOL_NAME`);
107
- * server.ts 的 pre-CAS 守卫读同一枚([ref]①:此处旧注称该常量「core 未根导出」= 失实,曾据此手抄 13 处)。
106
+ * 「是不是引擎的问题工具」不在本层判:core 7.56.0 [ref] 起本门**组合** core 原形的答(它读调用的脸 —— 引擎挂载点 + 目录契约 id,不读名字;
107
+ * 它答 `ask` ⇒ 是;本层只在那之上按活流分腿)。决断口的 pre-CAS 守卫(「批准问答必须带答」)仍按保留名读(core 同版「Not changed here」那一条)。
108
108
  * ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
109
109
  * 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
110
110
  * 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
@@ -1,18 +1,16 @@
1
1
  import { join, posix } from "node:path";
2
2
  import { FileError, StubExecutionEnv, combinePolicies, createAllowDenyPolicy, createDurableQuestionPolicy, createSensitivePathPolicy, createTranscriptIntegrityPolicy, err, isAbsoluteForFamily, isProtectedWrite, ok, pathTargetValue, protectivePathTargetOf, } from "@sema-agent/core";
3
3
  import { createDurableAskPolicy, hasApprovalListIntent } from "./approval.js";
4
- import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
5
- import { heldRuleName } from "./tool-rule-names.js";
6
4
  export function createDurableQuestionGate(live) {
5
+ const durable = createDurableQuestionPolicy();
7
6
  if (live === undefined)
8
- return createDurableQuestionPolicy();
7
+ return durable;
9
8
  return {
10
- check(req) {
11
- if (heldRuleName(req, (n) => n === ASK_USER_QUESTION_TOOL_NAME) === undefined)
12
- return { action: "allow" };
13
- return live.hasLiveContext()
14
- ? { action: "allow" }
15
- : { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
9
+ async check(req) {
10
+ const answer = await durable.check(req);
11
+ if (answer.action !== "ask")
12
+ return answer;
13
+ return live.hasLiveContext() ? { action: "allow" } : answer;
16
14
  },
17
15
  };
18
16
  }
@@ -74,6 +74,7 @@ export declare function asInstructionKind(v: string): DeviceInstructionKind | nu
74
74
  export declare const DEVICE_OUTCOME_ERROR_CODES_BY_REVISION: {
75
75
  readonly 1: readonly ["aborted", "already_exists", "auth_failed", "callback_error", "invalid", "is_directory", "not_directory", "not_found", "not_supported", "outcome_unknown", "permission_denied", "precondition_failed", "shell_unavailable", "spawn_error", "suspended", "target_unavailable", "timeout", "transport_lost", "unknown"];
76
76
  readonly 2: readonly ["liveness_no_output"];
77
+ readonly 3: readonly ["name_too_long"];
77
78
  };
78
79
  /** 本包认得的协议修订号(= 增量表的行键)。 */
79
80
  export type DeviceProtocolRevision = keyof typeof DEVICE_OUTCOME_ERROR_CODES_BY_REVISION;
@@ -1,7 +1,7 @@
1
1
  import { createPublicKey, randomBytes, randomInt, verify as edVerify } from "node:crypto";
2
2
  import { MAX_TIMER_DELAY_MS, inWholeNumberBounds, wholeNumberBoundsRule } from "./config-types.js";
3
3
  export const DEVICE_WS_PATH = "/v1/device/ws";
4
- export const DEVICE_PROTOCOL_VERSION = 2;
4
+ export const DEVICE_PROTOCOL_VERSION = 3;
5
5
  export const DEVICE_MIN_PROTOCOL_VERSION = 1;
6
6
  export const DEVICE_HELLO_DOMAIN_TAG = "sema.device.hello.v1";
7
7
  export const DEVICE_WS_LIMITS = {
@@ -66,6 +66,7 @@ export const DEVICE_OUTCOME_ERROR_CODES_BY_REVISION = {
66
66
  "unknown",
67
67
  ],
68
68
  2: ["liveness_no_output"],
69
+ 3: ["name_too_long"],
69
70
  };
70
71
  export function deviceOutcomeErrorCodesAt(revision) {
71
72
  if (!Number.isInteger(revision) || revision < 1 || revision > DEVICE_PROTOCOL_VERSION) {
@@ -25,7 +25,8 @@
25
25
  * · **判断面**(同一只函数的 `fault`,core 7.55.0 `FileHistoryError.permanence`):永久码表里的 typed 码 ⇒
26
26
  * `{ permanence: "terminal" }`;**连接类故障**(到库的连接坏了 / 库此刻不接 —— 判官是整仓唯一一只
27
27
  * `plugins/store-fault.ts` 的 `storeFaultClassOf`,识别表在 `plugins/sql-errors.ts`,本文件不再抄第二份)⇒
28
- * `{ permanence: "transient" }`;其余 ⇒ `{}`(没判 —— 引擎照旧给重试引导语)。`terminal` 只对永久码。
28
+ * `{ permanence: "transient" }`;**部署错配类**(缺表 / 权限拒:连接是好的、重试注定失败,修表 / 授权之前什么都不会变 —— 同一只判官的 `deployment` 一词,表在 `plugins/sql-errors.ts`)⇒
29
+ * `{ permanence: "terminal" }`;其余 ⇒ `{}`(没判 —— 引擎照旧给重试引导语)。`terminal` 只对永久码与部署错配类。
29
30
  * 折点把它摊进店答的 `FileHistoryError`;引擎的拒句引导语、rewind 披露、`terminal.detail` 都读这一格,**不读散文**。
30
31
  * · **机器面**(同一只函数的副作用):码取自 typed 错**对象**、对闭集词表判定,**绝不从散文里抠**。
31
32
  * 🔴 这一条是 codex R1-[high] 验真后的定形:文本通道**天然可伪造** —— 一个名叫
@@ -54,7 +55,8 @@ export interface CaughtFailure {
54
55
  * `text` = 散文形:带 typed `code` 的前缀一个括号词(`(blob_too_large_for_sql) blob of … exceeds …`),
55
56
  * 不带的**逐字**是原 message(transient 失败的文案因此与本批之前一字不差)。
56
57
  * `fault` = 判断形(core 7.55.0 `FileHistoryError.permanence`):永久码表里的码 ⇒ `{ permanence: "terminal" }`;连接类故障 ⇒ `{ permanence: "transient" }`
57
- * (什么都不改稍后再试可能成);其余 ⇒ `{}`(店不知道 —— 认不出的驱动 / 库错是不是会自己好,不归本店判)。永久码先于连接类判(一只错不会两个都是)。
58
+ * (什么都不改稍后再试可能成);部署错配类故障 ⇒ `{ permanence: "terminal" }`(修表 / 授权之前重试注定失败);其余 ⇒ `{}`(店不知道 —— 认不出的驱动 / 库错是不是会自己好,不归本店判)。
59
+ * 永久码先于分类判(一只错不会两个都是);分类词只读判官 `storeFaultClassOf`,本文件不写码。
58
60
  * 副作用 = 机器形:码取自错误对象,逐次计数 + 每进程每码一条 warn。观测绝不抛。
59
61
  */
60
62
  export declare function foldCaughtFailure(err: unknown): CaughtFailure;