@sema-agent/server 7.137.0 → 7.138.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 (209) hide show
  1. package/MIGRATION.md +328 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +67 -20
  5. package/dist/approval-card.d.ts +21 -14
  6. package/dist/approval-card.js +1 -0
  7. package/dist/audit.js +1 -0
  8. package/dist/background-agent-name.d.ts +5 -0
  9. package/dist/background-agent-name.js +5 -0
  10. package/dist/boot/budget-tracing.d.ts +1 -12
  11. package/dist/boot/deployment-mods.d.ts +46 -16
  12. package/dist/boot/deployment-mods.js +82 -6
  13. package/dist/boot/governance-seams.d.ts +10 -17
  14. package/dist/boot/governance-seams.js +5 -17
  15. package/dist/boot/leader.d.ts +3 -0
  16. package/dist/boot/leader.js +1 -0
  17. package/dist/boot/org-delivery.js +15 -2
  18. package/dist/boot/resolve-spec.d.ts +3 -0
  19. package/dist/boot/resolve-spec.js +23 -12
  20. package/dist/boot/retention-lane.d.ts +11 -5
  21. package/dist/boot/retention-lane.js +2 -2
  22. package/dist/boot/runner-deps.d.ts +7 -3
  23. package/dist/boot/runner-deps.js +1 -0
  24. package/dist/boot/stage-04-stores.d.ts +1 -12
  25. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  26. package/dist/boot/stage-05-execution-env.js +7 -4
  27. package/dist/boot/stage-06-runners.d.ts +3 -12
  28. package/dist/boot/stage-06-runners.js +5 -2
  29. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  30. package/dist/boot/stage-07-capability-layer.js +2 -2
  31. package/dist/boot/stage-08-reapers.d.ts +3 -12
  32. package/dist/boot/stage-09-leader.d.ts +3 -12
  33. package/dist/boot/stage-09-leader.js +2 -2
  34. package/dist/boot/stage-10-http-server.d.ts +3 -12
  35. package/dist/boot/stage-10-http-server.js +4 -2
  36. package/dist/boot/web-admin.d.ts +5 -2
  37. package/dist/boot/web-admin.js +3 -3
  38. package/dist/brain.js +5 -5
  39. package/dist/budget.d.ts +17 -15
  40. package/dist/budget.js +2 -2
  41. package/dist/capabilities/mcp-probe.d.ts +36 -11
  42. package/dist/capabilities/mcp-probe.js +44 -5
  43. package/dist/capability-facts.d.ts +6 -0
  44. package/dist/capability-facts.js +5 -0
  45. package/dist/checkpoint-identity.d.ts +13 -6
  46. package/dist/config-catalog.js +7 -1
  47. package/dist/config-types.d.ts +15 -7
  48. package/dist/config.js +8 -5
  49. package/dist/deployment-shape.d.ts +6 -6
  50. package/dist/gate-settlement.d.ts +71 -0
  51. package/dist/gate-settlement.js +40 -0
  52. package/dist/hooks/classic-stations.d.ts +1 -0
  53. package/dist/hooks/classic-stations.js +1 -0
  54. package/dist/hooks/command-spawn.d.ts +2 -2
  55. package/dist/hooks/hook-runner.js +14 -14
  56. package/dist/http/admission.js +4 -2
  57. package/dist/http/dispatch.d.ts +4 -0
  58. package/dist/http/dispatch.js +3 -0
  59. package/dist/http/durable-run-leg.js +4 -2
  60. package/dist/http/memory-compliance-failure-reply.js +3 -0
  61. package/dist/http/resume-legs.d.ts +5 -11
  62. package/dist/http/resume-legs.js +136 -91
  63. package/dist/http/route-ctx.d.ts +30 -51
  64. package/dist/http/route-ctx.js +2 -5
  65. package/dist/http/routes/a2a-serve.js +10 -4
  66. package/dist/http/routes/agents-roster.d.ts +1 -1
  67. package/dist/http/routes/agents-roster.js +5 -1
  68. package/dist/http/routes/approvals-assistant.js +6 -6
  69. package/dist/http/routes/capabilities.js +12 -10
  70. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  71. package/dist/http/routes/mcp-reconnect.js +7 -5
  72. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  73. package/dist/http/routes/memory-origin.d.ts +1 -1
  74. package/dist/http/routes/rules.js +3 -3
  75. package/dist/http/routes/runs.js +11 -7
  76. package/dist/http/routes/sessions.js +6 -4
  77. package/dist/http/routes/side-query.js +2 -3
  78. package/dist/http/routes/tasks.js +792 -780
  79. package/dist/http/server.d.ts +31 -25
  80. package/dist/http/wire-types.d.ts +14 -6
  81. package/dist/identity-source.d.ts +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +1 -0
  84. package/dist/leader/wire.d.ts +7 -0
  85. package/dist/leader/wire.js +5 -4
  86. package/dist/mcp-assertion-header.d.ts +51 -40
  87. package/dist/mcp-assertion-header.js +10 -11
  88. package/dist/mcp-authorization-face.d.ts +27 -0
  89. package/dist/mcp-authorization-face.js +15 -0
  90. package/dist/memory-operator-faces.d.ts +13 -4
  91. package/dist/memory-operator-faces.js +1 -17
  92. package/dist/memory-sync-client.js +72 -57
  93. package/dist/memory-sync.d.ts +1 -1
  94. package/dist/model-route-gap.d.ts +6 -22
  95. package/dist/model-route-gap.js +7 -5
  96. package/dist/mods-declaration.d.ts +110 -0
  97. package/dist/mods-declaration.js +109 -0
  98. package/dist/observability/err-text.d.ts +5 -0
  99. package/dist/observability/err-text.js +9 -0
  100. package/dist/observability/fail-open.d.ts +5 -1
  101. package/dist/observability/fail-open.js +5 -1
  102. package/dist/observability/secret-env-scrub.d.ts +1 -1
  103. package/dist/observability/secret-env-scrub.js +1 -1
  104. package/dist/parked-decide.d.ts +15 -2
  105. package/dist/parked-decide.js +14 -8
  106. package/dist/peer-seat.d.ts +150 -0
  107. package/dist/peer-seat.js +239 -0
  108. package/dist/permission-rule-vocab.d.ts +7 -7
  109. package/dist/permission-rule-vocab.js +3 -3
  110. package/dist/plain-string-record.d.ts +38 -0
  111. package/dist/plain-string-record.js +38 -0
  112. package/dist/plugins/background-agent-store-sql.js +6 -3
  113. package/dist/plugins/caching-session-store.d.ts +5 -0
  114. package/dist/plugins/caching-session-store.js +6 -0
  115. package/dist/plugins/checkpoint-store-sql.d.ts +15 -20
  116. package/dist/plugins/checkpoint-store-sql.js +8 -8
  117. package/dist/plugins/file-history-store-sql.js +3 -2
  118. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  119. package/dist/plugins/k8s-pod-reaper.js +3 -3
  120. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  121. package/dist/plugins/local-checkpoint-store.js +0 -1
  122. package/dist/plugins/local-session-store.d.ts +66 -27
  123. package/dist/plugins/local-session-store.js +60 -28
  124. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  125. package/dist/plugins/mailbox-store-sql.js +103 -15
  126. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  127. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  128. package/dist/plugins/one-time-migrations.d.ts +6 -3
  129. package/dist/plugins/one-time-migrations.js +3 -4
  130. package/dist/plugins/permission-rule-store-sql.js +21 -13
  131. package/dist/plugins/pg-session-storage.d.ts +10 -2
  132. package/dist/plugins/pg-session-storage.js +18 -5
  133. package/dist/plugins/remote-env-adb.d.ts +3 -1
  134. package/dist/plugins/remote-env-adb.js +1 -1
  135. package/dist/plugins/remote-env-device.d.ts +3 -1
  136. package/dist/plugins/remote-env-device.js +1 -1
  137. package/dist/plugins/remote-env-e2b.d.ts +2 -0
  138. package/dist/plugins/remote-env-e2b.js +1 -1
  139. package/dist/plugins/remote-env-host.d.ts +3 -1
  140. package/dist/plugins/remote-env-host.js +1 -1
  141. package/dist/plugins/remote-env-k8s.d.ts +3 -1
  142. package/dist/plugins/remote-env-k8s.js +1 -1
  143. package/dist/plugins/remote-env-local-docker.d.ts +2 -0
  144. package/dist/plugins/remote-env-local-docker.js +1 -1
  145. package/dist/plugins/remote-env-ssh.d.ts +3 -1
  146. package/dist/plugins/remote-env-ssh.js +1 -1
  147. package/dist/plugins/retention-store-sql.d.ts +14 -2
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/session-fork.d.ts +10 -13
  150. package/dist/plugins/session-fork.js +5 -25
  151. package/dist/plugins/session-ownership.d.ts +7 -0
  152. package/dist/plugins/session-ownership.js +5 -0
  153. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  154. package/dist/plugins/session-workspace-cell.js +38 -0
  155. package/dist/plugins/store-backend.js +7 -7
  156. package/dist/plugins/tidb-pool.js +6 -0
  157. package/dist/plugins/tidb-session-store.d.ts +17 -2
  158. package/dist/plugins/tidb-session-store.js +16 -5
  159. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  160. package/dist/plugins/tool-result-store-sql.js +8 -7
  161. package/dist/plugins/worktree-isolation.d.ts +3 -2
  162. package/dist/plugins/worktree-isolation.js +1 -7
  163. package/dist/project-identity.d.ts +1 -14
  164. package/dist/project-identity.js +0 -4
  165. package/dist/rules-consent.js +6 -4
  166. package/dist/run-local.js +12 -6
  167. package/dist/runs.d.ts +7 -1
  168. package/dist/runs.js +6 -1
  169. package/dist/sandbox-git-plane.d.ts +2 -2
  170. package/dist/server-secret-env.d.ts +6 -3
  171. package/dist/server-secret-env.js +4 -2
  172. package/dist/session-incarnation.d.ts +4 -5
  173. package/dist/session-incarnation.js +2 -2
  174. package/dist/session-workspace.d.ts +130 -0
  175. package/dist/session-workspace.js +83 -0
  176. package/dist/spec-fields.d.ts +1 -1
  177. package/dist/standing-answer-grant.d.ts +2 -13
  178. package/dist/standing-answer-grant.js +0 -7
  179. package/dist/task-a2a.d.ts +44 -14
  180. package/dist/task-a2a.js +46 -20
  181. package/dist/task-mcp.d.ts +33 -20
  182. package/dist/task-mcp.js +46 -22
  183. package/dist/tool-approval.d.ts +32 -8
  184. package/dist/tool-approval.js +49 -12
  185. package/dist/trace/core-keyset-guard.d.ts +33 -18
  186. package/dist/trace/engine-notice-wire.d.ts +1 -1
  187. package/dist/trace/engine-notice-wire.js +2 -0
  188. package/dist/trace/exit-seat.d.ts +20 -1
  189. package/dist/trace/exit-seat.js +3 -0
  190. package/dist/trace/extension-call-face.d.ts +33 -0
  191. package/dist/trace/extension-call-face.js +39 -0
  192. package/dist/trace/project.d.ts +25 -7
  193. package/dist/trace/project.js +29 -3
  194. package/dist/trace/redact.js +42 -13
  195. package/dist/trace/self-minted-wire.d.ts +129 -10
  196. package/dist/trace/self-minted-wire.js +62 -3
  197. package/dist/trace/wire-projection-faces.d.ts +6 -6
  198. package/dist/trace/wire-projection-faces.js +6 -5
  199. package/dist/user-authored-text.d.ts +2 -2
  200. package/dist/user-authored-text.js +16 -1
  201. package/dist/web-admin-client/center-request.d.ts +3 -2
  202. package/dist/web-admin-client/center-request.js +2 -3
  203. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  204. package/dist/web-admin-client/skills-mcp.js +31 -18
  205. package/dist/web-admin-client/types.d.ts +5 -0
  206. package/package.json +3 -3
  207. package/wire-changes/server@7.138.0.json +446 -0
  208. package/dist/http/mcp-remote-text.d.ts +0 -5
  209. package/dist/http/mcp-remote-text.js +0 -7
@@ -4,7 +4,8 @@ import { createFixedWindowRateLimiter } from "../device-enrollment.js";
4
4
  import { canonicalJsonStringify } from "@sema-agent/core";
5
5
  import { keyClosureClause, scanKeyClosure } from "../request-key-closure.js";
6
6
  import { requestMcpDeclarationRefusal, validateRequestMcp } from "../task-mcp.js";
7
- import { specWithholdsMcpAssertionHeader, toCoreMcpServerSpec } from "../mcp-assertion-header.js";
7
+ import { specWithholdsMcpAssertionHeader, toMcpDraft } from "../mcp-assertion-header.js";
8
+ import { mintDeclaration } from "../peer-seat.js";
8
9
  import { projectWiringManifestMcpRows } from "../trace/project.js";
9
10
  import { buildBoundedToolNames } from "../panel-tool-names.js";
10
11
  export const MCP_PROBE_REFUSAL_CODES = [
@@ -80,13 +81,26 @@ export function readMcpProbeBody(raw) {
80
81
  "Probe the entry without the key to see how the server answers without it"),
81
82
  };
82
83
  }
83
- return { ok: true, specs: validated.ok.map(toCoreMcpServerSpec) };
84
+ return { ok: true, drafts: validated.ok.map(toMcpDraft) };
84
85
  }
85
86
  export function buildMcpProbeIdentity(principal) {
86
87
  return principal !== undefined && principal.length > 0 ? principal : "local";
87
88
  }
88
- export function buildMcpProbeCacheKey(principal, specs) {
89
- return `${buildMcpProbeIdentity(principal)}::${createHash("sha256").update(canonicalJsonStringify(specs), "utf8").digest("hex")}`;
89
+ export function countMcpProbeDeclarations(input) {
90
+ switch (input.declaredBy) {
91
+ case "deployment":
92
+ return input.specs.length;
93
+ case "caller":
94
+ return input.drafts.length;
95
+ default: {
96
+ const unknownArm = input;
97
+ void unknownArm;
98
+ throw new Error("mcp probe: an input arm this module does not know (the arm set is closed: deployment / caller)");
99
+ }
100
+ }
101
+ }
102
+ export function buildMcpProbeCacheKey(principal, input) {
103
+ return `${buildMcpProbeIdentity(principal)}::${createHash("sha256").update(canonicalJsonStringify(input), "utf8").digest("hex")}`;
90
104
  }
91
105
  export function buildMcpProbeRateKey(principal) {
92
106
  return `mcp-probe:${buildMcpProbeIdentity(principal)}`;
@@ -155,7 +169,8 @@ export function buildMcpProbeFaceRows(rows, readings) {
155
169
  function isStringList(v) {
156
170
  return Array.isArray(v) && Array.from(v).every((t) => typeof t === "string");
157
171
  }
158
- export function createMcpProbeWalk(specs, principal, now = Date.now) {
172
+ export function createMcpProbeWalk(input, principal, desk, now = Date.now) {
173
+ const { specs, lease } = probeDeclarations(input, desk);
159
174
  const bound = buildMcpProbeWalkTimeoutMs(specs.length);
160
175
  const controller = new AbortController();
161
176
  const dials = probeMcpServers(specs, {
@@ -163,7 +178,10 @@ export function createMcpProbeWalk(specs, principal, now = Date.now) {
163
178
  timeoutMs: MCP_PROBE_VERDICT_TIMEOUT_MS,
164
179
  concurrency: MCP_PROBE_CONCURRENCY,
165
180
  signal: controller.signal,
181
+ peerSeat: desk.seat.answer,
166
182
  });
183
+ if (lease !== undefined)
184
+ void dials.then(() => lease.release(), () => lease.release());
167
185
  dials.catch(() => undefined);
168
186
  let timer;
169
187
  const deadline = new Promise((_resolve, reject) => {
@@ -189,6 +207,27 @@ export function createMcpProbeWalk(specs, principal, now = Date.now) {
189
207
  answer.catch(() => undefined);
190
208
  return { answer, dials };
191
209
  }
210
+ function probeDeclarations(input, desk) {
211
+ switch (input.declaredBy) {
212
+ case "deployment":
213
+ return { specs: input.specs };
214
+ case "caller": {
215
+ const lease = desk.lease("probe");
216
+ try {
217
+ return { specs: input.drafts.map((draft) => mintDeclaration(desk, draft, lease)), lease };
218
+ }
219
+ catch (err) {
220
+ lease.release();
221
+ throw err;
222
+ }
223
+ }
224
+ default: {
225
+ const unknownArm = input;
226
+ void unknownArm;
227
+ throw new Error("mcp probe: an input arm this module does not know (the arm set is closed: deployment / caller)");
228
+ }
229
+ }
230
+ }
192
231
  export class McpProbeMisaligned extends Error {
193
232
  constructor(message) {
194
233
  super(message);
@@ -87,6 +87,7 @@ export declare const CAPABILITY_FACT_WHY: {
87
87
  readonly not_k8s_lane: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s)";
88
88
  readonly workspace_snapshot_store_absent: "no workspace snapshot store is configured (MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY)";
89
89
  readonly workspace_cache_off: "the build-cache volume per workspace space is off (WORKSPACE_CACHE=off, the default)";
90
+ readonly deployment_mod_packages_absent: "no operator-declared mod package was loaded at boot (MODS_ROOT / MODS_PACKAGES)";
90
91
  };
91
92
  export type CapabilityFactWhy = keyof typeof CAPABILITY_FACT_WHY;
92
93
  /** 不可用臂。 */
@@ -355,6 +356,11 @@ export declare function workspaceSpace(c: Pick<ServiceConfig, "remoteExec">): Fa
355
356
  export declare function workspaceCache(c: Pick<ServiceConfig, "remoteExec">): Fact<"not_k8s_lane" | "workspace_cache_off", {
356
357
  cache: WorkspaceCacheCapability;
357
358
  }>;
359
+ /** 运维声明的 mod 包:本进程起服装上了几只(部署座装配口 `assembleDeploymentMods` 的读数;本服务自铸的部署注册不算)。
360
+ * `/health.mods.deploymentPackages`(投 `count`)与 caps `modsDeploymentPackages`(投 `.available`,false ⇒ 键缺席)同调这一只。 */
361
+ export declare function deploymentModPackages(d: Pick<Deps, "deploymentModPackages">): Fact<"deployment_mod_packages_absent", {
362
+ count: number;
363
+ }>;
358
364
  /** 自唤醒调度器挂在宿主 env 上:`SCHEDULER_ENABLED` ∧ 单用户 ∧ host 车道。装配(`boot/execution-env.ts`)与能力位同调。 */
359
365
  export declare function hostScheduler(c: Pick<ServiceConfig, "schedulerEnabled" | "requirePrincipal" | "remoteExec">): Fact<"scheduler_disabled" | "multi_tenant_deployment" | "not_host_lane">;
360
366
  /** 引擎能力词的事实(core `ENGINE_CAPABILITIES` 每词一只谓词,值 = 本进程的事实;`/v1/capabilities` 以顶层布尔键展开它们)—— 住在
@@ -67,6 +67,7 @@ export const CAPABILITY_FACT_WHY = {
67
67
  not_k8s_lane: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s)",
68
68
  workspace_snapshot_store_absent: "no workspace snapshot store is configured (MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY)",
69
69
  workspace_cache_off: "the build-cache volume per workspace space is off (WORKSPACE_CACHE=off, the default)",
70
+ deployment_mod_packages_absent: "no operator-declared mod package was loaded at boot (MODS_ROOT / MODS_PACKAGES)",
70
71
  };
71
72
  const no = (why) => ({ available: false, why });
72
73
  const yes = (w) => ({ available: true, ...w });
@@ -391,6 +392,10 @@ export function workspaceCache(c) {
391
392
  return no("workspace_cache_off");
392
393
  return yes({ cache: { mount: rx.workspaceCache.mount, size: rx.workspaceCache.size } });
393
394
  }
395
+ export function deploymentModPackages(d) {
396
+ const n = d.deploymentModPackages;
397
+ return n !== undefined && n > 0 ? yes({ count: n }) : no("deployment_mod_packages_absent");
398
+ }
394
399
  export function hostScheduler(c) {
395
400
  if (c.schedulerEnabled !== true)
396
401
  return no("scheduler_disabled");
@@ -7,15 +7,22 @@ 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`):运维队列行、停驻收件行,与 7.137.0 起的 park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`,写时判一次)。 */
11
- export type DurableCheckpointFace = "approvals-queue" | "assistant-inbox" | "run-events-park";
10
+ /**
11
+ * 带 `checkpointId` 当 join 键的面(计数 detail 的 `face`):运维队列行、停驻收件行、park 账本行(`GET /v1/runs/:id/events` 的 `suspended` /
12
+ * `needs_review`,写时判一次),与回决门族的 200 体(`decision-receipt`:`/decide` / `/resume` / `/plan_review` 执行**前**从选中的那一行捕获一次,
13
+ * 结清证据带它 —— 语义属主 = `gate-settlement.ts`)。四面同一只读法、同一个计数。
14
+ */
15
+ export type DurableCheckpointFace = "approvals-queue" | "assistant-inbox" | "run-events-park" | "decision-receipt";
16
+ /** 一行的身份片段:读得出可信值 ⇒ `{ checkpointId }`;读不出 ⇒ `{}`(键缺席)。由调用方原样展开进行投影 / 结清证据。 */
17
+ export type GateIdentity = {
18
+ readonly checkpointId?: string;
19
+ };
12
20
  /**
13
21
  * durable 读面的一行 → 它的身份片段,**由调用方原样展开进行投影**(`...durableRowIdentity(face, raw, token)`):读得出可信值 ⇒ `{ checkpointId }`;
14
22
  * 读不出 ⇒ `{}`(这一行**照列**,键缺席)+ 计一次(见本文件头注「durable 面的 join 键」)。
15
23
  * `raw === undefined` = 行上没有这一格(`why=absent`,老行);在场却没过信任边界(非串 / 空串 / 超长 / 含 token / 读不出)= `why=untrusted`。
16
- * 三处读面(两只店的 `listPending` + 收件 handler)与 park 账本行的唯一构造器(`trace/project.ts` 的 `parkEventData`)只经这一只 —— 读法、计数、缺席形各只有一份。
24
+ * 三处读面(两只店的 `listPending` + 收件 handler)、park 账本行的唯一构造器(`trace/project.ts` 的 `parkEventData`)与回决门族三条续跑腿的执行前捕获点
25
+ * (`http/resume-legs.ts`:`/decide` 在分流之前一次、`/resume`、`/plan_review`)只经这一只 —— 读法、计数、缺席形各只有一份。
17
26
  */
18
- export declare function durableRowIdentity(face: DurableCheckpointFace, raw: unknown, token: unknown): {
19
- readonly checkpointId?: string;
20
- };
27
+ export declare function durableRowIdentity(face: DurableCheckpointFace, raw: unknown, token: unknown): GateIdentity;
21
28
  //# sourceMappingURL=checkpoint-identity.d.ts.map
@@ -167,7 +167,11 @@ export const CONFIG_CATALOG = [
167
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" }),
168
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" }),
169
169
  r("FORK_ENABLED", "orchestration", "boolean", "CC /fork(core Fork 工具)装配开关", { derivedDefaultNote: "posture 三态:缺席 = 单用户 ⇒ on / 多租户 ⇒ off(per-principal entitlement 另判)", configKey: "forkEnabled" }),
170
- r("GATEWAY_MAX_RETRIES", "modelPlane", "number", "openai 兼容腿每次调用重试上限", { derivedDefaultNote: "缺席 ⇒ 不传引擎,引擎默认当家" }),
170
+ r("GATEWAY_MAX_RETRIES", "modelPlane", "number", "openai 兼容腿每次调用重试上限", {
171
+ derivedDefaultNote: "缺席 ⇒ 按外层拓扑分两态(启动日志 `brain` 字段的 `gatewayRetryCap` 回显哪一态):配了备用网关(`MODEL_GATEWAY_FALLBACK_URLS`)/ 开了断路器开关(`MODEL_CIRCUIT_BREAKER`,单路网关上也算)/ 开了反应式降级(配着 `MODEL_DEGRADE_TO` 且 `MODEL_DEGRADE_REACTIVE` 开)" +
172
+ "⇒ 网关各路 server 传 2(`fast-fail:2`),引擎的 `SEMA_MAX_RETRIES` 被遮蔽(降级兜底腿不吃这条收窄:不设时它不传,引擎默认当家);没有这些快切层 ⇒ 不传(`engine-default`),引擎默认当家(读 `SEMA_MAX_RETRIES`,没设是 10)。" +
173
+ "设了恒赢(`env:<n>`):原样传给引擎(降级兜底腿也吃显式值)并遮蔽 `SEMA_MAX_RETRIES`",
174
+ }),
171
175
  r("GIT_API_BASEURL", "integrations", "url", "repo 工具的 Git host API base", { configKey: "gitApiBaseUrl" }),
172
176
  r("GIT_API_KIND", "integrations", "enum", "Git host API 方言", { enumValues: ["gitea", "github"], staticDefault: "gitea", configKey: "gitApiKind" }),
173
177
  r("GIT_API_TOKEN", "integrations", "secret", "repo 工具的 Git host 凭据", { danger: SEC }),
@@ -352,6 +356,8 @@ export const CONFIG_CATALOG = [
352
356
  },
353
357
  center: cModels("models[].vision"),
354
358
  }),
359
+ r("MODS_PACKAGES", "integrations", "opaque", "部署座装载的 mod 包声明(JSON 数组,每项闭集 { name, dir, tier, options? }:name = 包自己的名、逐字同;dir 绝对路径、真实路径须在 MODS_ROOT 之下且是目录;tier ∈ prepend / user / append 宿主必填;options 原样交包的 register)。读包只有引擎的读包者,本服务不读包内文件;形错 / 越根 / 读包者拒 / 名不同 / 引擎准入拒 ⇒ 拒启,一行拒句点名哪一席或哪只包(本服务写的句子不回显声明的值;读包者 / 准入交回的话原样透传、凭据形换记号,可能点名包目录);起服装配一次,改声明或换包要重启;本目录只回在场位与长度(不回显声明);options 不是凭据通道(本服务不替包扣凭据:宿主车道 inheritEnv \"all\" 下子进程照样继承这根旋钮与包自己读的变量)", { danger: SEC, derivedDefaultNote: "缺席 / 空串 = 不声明 ⇒ 部署座只有本服务自铸的注册,/health 与 /v1/capabilities 逐字节同没有这根旋钮的版本" }),
360
+ r("MODS_ROOT", "integrations", "path", "声明的 mod 包允许所在的根(绝对路径;包的真实路径按路径分量严格落在它的真实路径之下 —— 符号链接出根、前缀相似目录、.. 遍历、根本身都拒启;建议只读物料树)。声明了包而没设它 ⇒ 拒启;单设它而不声明包 = 无事(形照判)", { danger: SEC, configKey: "modsDeclaration", derivedDefaultNote: "缺席 ⇒ 不许声明任何包", resolve: (c) => c.modsDeclaration.root ?? null }),
355
361
  r("MYSQL_DATABASE", "store", "string", "MySQL 协议库名(选中该引擎时必填)", { danger: DATA }),
356
362
  r("MYSQL_HOST", "store", "string", "MySQL 协议主机(在场即算 SQL 信号,参与 DB_BACKEND 默认推导)", { danger: DATA }),
357
363
  r("MYSQL_PASSWORD", "store", "secret", "MySQL 协议口令", { danger: SEC }),
@@ -23,6 +23,7 @@ import type { CustomPkgSource } from "./sandbox-pkg-source.js";
23
23
  import type { MemoryEngineBackend, StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
24
24
  import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
25
25
  import type { AtMention } from "@sema-agent/settings-schema";
26
+ import type { ModsDeclaration } from "./mods-declaration.js";
26
27
  /** [ref]:记忆向量面的 embedder 坐标(env `MEMORY_EMBEDDER_*` 解析结果;单一属主 = 本类型,
27
28
  * `plugins/memory-embedder.ts` 的工厂直接消费它,不复制形状)。语义与三问见
28
29
  * {@link ServiceConfigFlat.memoryEmbedder}。纯数据 —— 端点原文照存(拼 `/embeddings` 是 transport
@@ -130,7 +131,7 @@ export declare const DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS = 250;
130
131
  * 住在本叶(而不在 `fleet/drain-entry.ts`):`config.ts` 读它,而 drain-entry 值引 `plugins/store-contracts.ts`,config 不值引 plugins 层。
131
132
  */
132
133
  export declare function fleetScopeOf(raw: string | undefined): string;
133
- /** 中心基址三席(`SEMA_WEB_ADMIN_URL` / `SEMA_WEB_ADMIN_LIVE_URL` / `SEMA_WEB_ADMIN_WEB_URL`)与公告地址的形句 —— 判官 `web-admin-client/center-request.ts` `pathBaseUrlRefusal` 一只(形判 = settings-schema `httpUrlFlaws`),配置目录四行同读这一句。 */
134
+ /** 中心基址三席(`SEMA_WEB_ADMIN_URL` / `SEMA_WEB_ADMIN_LIVE_URL` / `SEMA_WEB_ADMIN_WEB_URL`)与公告地址的形句 —— 判官 `web-admin-client/center-request.ts` `pathBaseUrlRefusal` 一只(形判 = settings-schema `pathBaseUrlFlaws`,14.7.0 起),配置目录四行同读这一句。 */
134
135
  export declare const CENTER_BASE_URL_RULE = "\u8BBE\u4E86\u5C31\u5FC5\u987B\u662F\u53EF\u89E3\u6790\u7684 http(s) URL,\u4E14\u4E0D\u5E26\u51ED\u636E / query / fragment(\u57FA\u5740\u540E\u9762\u8981\u62FC\u8DEF\u5F84);\u574F\u5F62\u62D2\u542F(\u6CA1\u63A5\u4E2D\u5FC3\u4E5F\u62D2),\u62D2\u53E5\u53EA\u70B9\u540D\u89C4\u5219,\u4E0D\u56DE\u663E scheme";
135
136
  /** 编排器片 2:`FLEET_LEASE_OUTAGE_MAX_MS` 的缺省(24 h)—— 中心 / 库故障、中心降级在今天都靠 LKG 无限期容忍,10 分钟级的窗会把它们变成数据面停服。 */
136
137
  export declare const FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS = 86400000;
@@ -266,7 +267,7 @@ export interface MemoryConsolidationDriverConfig {
266
267
  * ①恰好一条时 = 两个 admin 口省略 `scope` 的**唯一**缺省(两条及以上恒不猜——在两个库之间猜一个
267
268
  * 去花钱是本设计要消灭的形);②周期腿落地时的 scope 源(设计稿 D2)。
268
269
  * 🔴 **不是授权边界**:本仓今天没有任何授权源能对一个 operator 收窄 scope(理由与亲读依据逐字见
269
- * `src/memory-operator-faces.ts` 的 `erasureEnvelopeRefusal` 头注),在本层拿它当白名单就是**发明
270
+ * `src/memory-operator-faces.ts` `MemoryComplianceFaces.erase` 的「授权座只有一只」段),在本层拿它当白名单就是**发明
270
271
  * 第二套授权语义**。显式传的 scope 照跑,表只管缺省。 */
271
272
  scopes: readonly string[];
272
273
  /**
@@ -297,7 +298,8 @@ export interface MemoryConsolidationDriverConfig {
297
298
  */
298
299
  autoRunOnRecommendation?: boolean;
299
300
  }
300
- /** A web-admin MCP server resolved to a core spec (env-NAME refs already → real values) plus the
301
+ /** A web-admin MCP server resolved to a core spec (env-NAME refs already read and registered on the peer desk under the
302
+ * roster lease: the spec carries the minted reference, never the values) plus the
301
303
  * scenarios it applies to (empty = all). `resolveSpec` filters by scenario and passes `spec` to core. */
302
304
  export interface ScopedMcpServer {
303
305
  scenarios: string[];
@@ -312,7 +314,8 @@ export interface CenterMcp {
312
314
  servers: ScopedMcpServer[];
313
315
  reservedNames: readonly string[];
314
316
  }
315
- /** DESIGN-269 §2.1 —— an A2A PEER resolved to a core spec (header-NAME refs already → real values) plus
317
+ /** DESIGN-269 §2.1 —— an A2A PEER resolved to a core spec (header-NAME refs already read and registered on the peer desk
318
+ * under the roster lease: the spec carries the minted reference, never the values) plus
316
319
  * the scenarios it applies to (empty = all). Exact sibling of {@link ScopedMcpServer}: `resolveSpec`
317
320
  * filters by scenario (`a2aForScenario`) and passes `spec` to core as `TaskSpec.a2a`.
318
321
  *
@@ -510,8 +513,10 @@ export interface ServiceConfigFlat {
510
513
  gatewayFallbackUrls: string[];
511
514
  /** `GATEWAY_MAX_RETRIES` —— openai 兼容腿的**每次调用**重试上限。
512
515
  *
513
- * 🔴 **缺席 = 不传给引擎**(不是"传个默认值"):此前这里是硬编码 `maxRetries: 2`,而显式传参**压过**
514
- * 引擎默认 ⇒ core 抬默认对 server 部署一点效果都没有。缺席不传之后,引擎默认当家、core 抬默认自动继承。
516
+ * 🔴 **配置层缺席 = 本字段 `undefined`**(不是"传个默认值"):此前这里是硬编码 `maxRetries: 2`,而显式传参**压过**
517
+ * 引擎默认 ⇒ core 抬默认对 server 部署一点效果都没有。缺席之后由 `brain.ts` 按**外层拓扑**分两态(启动日志 `gatewayRetryCap` 回显):
518
+ * 外层有快切层(备用网关 / 断路器开关 / 反应式降级 —— 后者须配着 `MODEL_DEGRADE_TO`)⇒ 网关各路传 `FAST_FAIL_MAX_RETRIES`(2)并遮蔽引擎的 `SEMA_MAX_RETRIES`;
519
+ * 没有快切层 ⇒ 不传,引擎默认当家、core 抬默认自动继承。降级兜底腿不吃这条收窄(只吃显式值)。设了恒赢(原样传、同样遮蔽 `SEMA_MAX_RETRIES`)。
515
520
  * (五报清查 R1① 的 server 半场,黑板 [ref]/[ref];anthropic 腿早有 `ANTHROPIC_MAX_RETRIES` 出口,
516
521
  * 这条只是把同样的出口补给限流 provider 真正走的那条腿。) */
517
522
  gatewayMaxRetries?: number;
@@ -2205,6 +2210,9 @@ export interface ServiceConfigFlat {
2205
2210
  defaultScenario: string;
2206
2211
  /** Directory of baked-in skill Markdown files, loaded once at startup. */
2207
2212
  skillsDir: string;
2213
+ /** 运维声明的 mod 包(`MODS_ROOT` + `MODS_PACKAGES`;`loadConfig` 读一次,唯一读法 `mods-declaration.ts` 的 `parseModsDeclaration`)。两只起服入口的部署座装配
2214
+ * (`boot/deployment-mods.ts` 的 `assembleDeploymentMods`)都读这一份;没声明 = 没有 `packages` 这一席(`{}` / 单设根时 `{ root }`;部署座只有本仓自铸的注册)。起服装配一次,配置刷新不重载。 */
2215
+ modsDeclaration: ModsDeclaration;
2208
2216
  /** Central web-admin (web-admin). When set, the service pulls the effective config
2209
2217
  * on boot (and refreshes) and applies it OVER the env defaults — env is the fallback. Logical config
2210
2218
  * only (model roster/roles/teams); secrets/gateways stay in this service's env.
@@ -2294,7 +2302,7 @@ export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachme
2294
2302
  /** 组:observability(可观测)。 */
2295
2303
  export type ServiceObservabilityConfig = Pick<ServiceConfigFlat, "metricsToken" | "traceToken" | "toolTrace" | "traceThinking" | "logLevel" | "otel">;
2296
2304
  /** 组:integrations(外部集成)。`mcpServers` / `a2aPeers` 由 web-admin 适配器填(无 env 腿),归本组。 */
2297
- export type ServiceIntegrationsConfig = Pick<ServiceConfigFlat, "pluginsAllowHosts" | "mcpServers" | "a2aPeers" | "a2aServe" | "configBootFetchBudgetMs" | "gitApiBaseUrl" | "gitApiToken" | "gitApiKind" | "defaultScenario" | "skillsDir" | "centerCredentialRenewLeadMs" | "webAdmin" | "fleetLeaseFailClosed" | "fleetGeneration" | "fleetScope" | "fleetAdvertiseAddress" | "fleetLeaseOutageMaxMs" | "configProvider" | "configLocalDir" | "configLocalOwnerUid">;
2305
+ export type ServiceIntegrationsConfig = Pick<ServiceConfigFlat, "pluginsAllowHosts" | "mcpServers" | "a2aPeers" | "a2aServe" | "configBootFetchBudgetMs" | "gitApiBaseUrl" | "gitApiToken" | "gitApiKind" | "defaultScenario" | "skillsDir" | "modsDeclaration" | "centerCredentialRenewLeadMs" | "webAdmin" | "fleetLeaseFailClosed" | "fleetGeneration" | "fleetScope" | "fleetAdvertiseAddress" | "fleetLeaseOutageMaxMs" | "configProvider" | "configLocalDir" | "configLocalOwnerUid">;
2298
2306
  /** 九个组槽。每组恒在场(`loadConfig` / `attachConfigGroups` 装好才交出配置),故不可选——新代码写
2299
2307
  * `config.modelPlane.model` 不需要 `?.`(可选组会把 `Model` 污染成 `Model | undefined`)。 */
2300
2308
  export interface ServiceConfigGroups {
package/dist/config.js CHANGED
@@ -15,7 +15,7 @@ import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
15
15
  import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
16
16
  import { CONFIG_PROVIDER_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, fleetScopeOf, DEVICE_DELIVERY_TIMEOUT_BOUNDS, DEVICE_EXEC_TIMEOUT_BOUNDS, inWholeNumberBounds, wholeNumberBoundsRule } from "./config-types.js";
17
17
  import { DEVICE_HELLO_ACK_BOUNDS } from "./device-ws-protocol.js";
18
- import { FLEET_GENERATION_ENV, FLEET_SCOPE_ENV, SERVING_LEASE_TTL_FLOOR_MS, isCanonicalGeneration } from "@sema-agent/settings-schema/fleet";
18
+ import { FLEET_ADVERTISE_ADDRESS_ENV, FLEET_GENERATION_ENV, FLEET_SCOPE_ENV, SERVING_LEASE_TTL_FLOOR_MS, isCanonicalGeneration } from "@sema-agent/settings-schema/fleet";
19
19
  import { ScopeId } from "@sema-agent/settings-schema/api/scopes";
20
20
  import { centerLaneConfigured, pathBaseUrlRefusal } from "./web-admin-client/center-request.js";
21
21
  import { resolveLocalDataRoot } from "./local-data-root.js";
@@ -47,7 +47,7 @@ import { modelRouteUrlRefusal } from "./model-route-url.js";
47
47
  import { httpUrlFlaws } from "@sema-agent/settings-schema";
48
48
  import { buildHttpUrlRefusal, buildRefusalShownUrl } from "./url-refusal.js";
49
49
  import { customPkgSourceFromEnv } from "./sandbox-pkg-source.js";
50
- import { anthropicCredentialRefusal, csvOf, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
50
+ import { anthropicCredentialRefusal, csvOf, declaredCredential, envLaneTable, mintedCheapId, readModelRouteEnv } from "./model-route-gap.js";
51
51
  import { displayListEntry } from "./list-entry-display.js";
52
52
  import { echoCallerText } from "./caller-text-echo.js";
53
53
  import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
@@ -55,6 +55,7 @@ import { parseConfigLocalOwnerUid } from "./config-dir-trust.js";
55
55
  import { allowlistedEnv } from "./server-secret-env.js";
56
56
  import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
57
57
  import { APPROVAL_ASK_RETENTION_DEFAULT_MS, APPROVAL_ASK_RETENTION_FLOOR_MS } from "./approval-ask-retention.js";
58
+ import { parseModsDeclaration } from "./mods-declaration.js";
58
59
  export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
59
60
  function csv(name) {
60
61
  return csvOf(process.env[name]);
@@ -1103,7 +1104,7 @@ function parseModelDomain() {
1103
1104
  }
1104
1105
  const models = envLaneTable(modelId, model, cheapModel);
1105
1106
  const gatewayApiKey = route.gatewayApiKey;
1106
- if (gatewayBaseUrl === undefined && gatewayApiKey !== undefined && gatewayApiKey !== "") {
1107
+ if (gatewayBaseUrl === undefined && declaredCredential(gatewayApiKey) !== undefined) {
1107
1108
  CONFIG_NOTICES.push({
1108
1109
  level: "warn",
1109
1110
  event: "model_gateway_key_unpaired",
@@ -2228,6 +2229,7 @@ function parseIntegrationsDomain(ctx) {
2228
2229
  gitApiKind: parseGitApiKind(process.env.GIT_API_KIND, process.env.GIT_API_BASEURL),
2229
2230
  defaultScenario: env("DEFAULT_SCENARIO", "code"),
2230
2231
  skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
2232
+ modsDeclaration: parseModsDeclaration(process.env.MODS_ROOT, process.env.MODS_PACKAGES),
2231
2233
  centerCredentialRenewLeadMs: parseCenterCredentialRenewLeadMs(process.env.CENTER_CREDENTIAL_RENEW_LEAD_MS),
2232
2234
  webAdmin,
2233
2235
  fleetLeaseFailClosed: parseFleetLeaseFailClosed(process.env.FLEET_LEASE_FAIL_CLOSED, DEPLOYMENT_SHAPE_PREDICATES.multiTenant.holds({ requirePrincipal })),
@@ -2273,9 +2275,10 @@ function parseFleetScope(raw) {
2273
2275
  return fleetScopeOf(raw);
2274
2276
  }
2275
2277
  function parseFleetAdvertiseAddress(raw) {
2278
+ const name = "FLEET_ADVERTISE_ADDRESS";
2276
2279
  if (raw === undefined || raw === "")
2277
2280
  return {};
2278
- const refusal = pathBaseUrlRefusal("env FLEET_ADVERTISE_ADDRESS", raw);
2281
+ const refusal = pathBaseUrlRefusal(`env ${name}`, raw);
2279
2282
  if (refusal !== undefined) {
2280
2283
  throw fleetEnvRefusal("advertise_address_invalid", `${refusal} — it must be an http(s) base URL such as "http://10.0.0.5:8090": the fleet center rejects any other shape on every announce. ` +
2281
2284
  `Unset it if this engine does not announce itself.`);
@@ -2460,7 +2463,7 @@ const LIMITS_HTTP_GROUP_KEYS = [
2460
2463
  const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
2461
2464
  const INTEGRATIONS_GROUP_KEYS = [
2462
2465
  "pluginsAllowHosts", "mcpServers", "a2aPeers", "a2aServe", "configBootFetchBudgetMs", "gitApiBaseUrl", "gitApiToken", "gitApiKind", "defaultScenario",
2463
- "skillsDir", "centerCredentialRenewLeadMs", "webAdmin", "fleetLeaseFailClosed", "fleetGeneration", "fleetScope", "fleetAdvertiseAddress", "fleetLeaseOutageMaxMs", "configProvider", "configLocalDir", "configLocalOwnerUid",
2466
+ "skillsDir", "modsDeclaration", "centerCredentialRenewLeadMs", "webAdmin", "fleetLeaseFailClosed", "fleetGeneration", "fleetScope", "fleetAdvertiseAddress", "fleetLeaseOutageMaxMs", "configProvider", "configLocalDir", "configLocalOwnerUid",
2464
2467
  ];
2465
2468
  export const CONFIG_GROUP_KEYS = {
2466
2469
  store: STORE_GROUP_KEYS,
@@ -93,23 +93,23 @@ export declare const DEPLOYMENT_SHAPE_PREDICATES: Readonly<{
93
93
  */
94
94
  multiTenant: DeploymentShapePredicate<"principal">;
95
95
  /** 读能力表 `callerCwd` 位(host = 单主才开;device = 路径落在发起者自己的设备上,归属由 `device_session` 绑定验过,不要求单主)。 */
96
- callerCwd: DeploymentShapePredicate<"principal" | "lane">;
96
+ callerCwd: DeploymentShapePredicate<"lane" | "principal">;
97
97
  /** 调用方给的宿主绝对路径(附加目录 / 项目上下文 / 本机 SendUserFile / 指令文件探针)只在「手就在本 worker 的盘上 ∧ 单主」时指向调用方自己的东西。 */
98
- callerOwnsWorkerDisk: DeploymentShapePredicate<"principal" | "lane">;
98
+ callerOwnsWorkerDisk: DeploymentShapePredicate<"lane" | "principal">;
99
99
  /** wire `executionLane.toolsOnThisHost` 与出厂 / 中心技能的 `baseDir`:只问文件系统同一性,与租户无关(出厂内容每个租户拿到同一份)。 */
100
100
  toolsOnThisHost: DeploymentShapePredicate<"lane">;
101
101
  /** `REMOTE_EXEC` 未设的进程内形(没有手)且单主:进程级消费者(hooks 命令、项目上下文本机读、卡批规则 root)看到的就是引擎自己的进程 cwd / env。 */
102
- inProcessSingleUser: DeploymentShapePredicate<"principal" | "lane">;
102
+ inProcessSingleUser: DeploymentShapePredicate<"lane" | "principal">;
103
103
  /** 任务层替部署声明宿主姿态(bypassPermissions 开写面 / host shell 继承整份环境)的射程:device 车道上跨租户宿主穿越结构性不存在(clay 裁定③)。 */
104
- taskHostPostureDeclarations: DeploymentShapePredicate<"principal" | "lane">;
104
+ taskHostPostureDeclarations: DeploymentShapePredicate<"lane" | "principal">;
105
105
  /** 写门走 [ref] 代理裁决(文件平面不在 worker 本机);认不出的词 ⇒ 走代理(代理 slot 空 ⇒ 写门 fail-closed)。 */
106
106
  sandboxPathAdjudication: DeploymentShapePredicate<"lane">;
107
107
  /** 读能力表 `sandboxSendUserFile` 位(e2b / k8s 每任务一只沙箱 ⇒ 任何租户;ssh 共享一台真机 ⇒ 只单主)。 */
108
- sandboxSendUserFile: DeploymentShapePredicate<"principal" | "lane">;
108
+ sandboxSendUserFile: DeploymentShapePredicate<"lane" | "principal">;
109
109
  /** 读能力表 `remoteScratchpad` 位(`/tmp/scratchpad/<sessionId>` 远端约定)。 */
110
110
  remoteScratchpad: DeploymentShapePredicate<"lane">;
111
111
  /** 读能力表 `tenantSeparation` 位(值来自 settings-schema 车道词表正本);唯一读者 = 跨域不变量 `multi-tenant-shared-lane`。 */
112
- laneSeparatesTenants: DeploymentShapePredicate<"principal" | "lane">;
112
+ laneSeparatesTenants: DeploymentShapePredicate<"lane" | "principal">;
113
113
  /**
114
114
  * [ref] —— **VM 级**隔离强度(独立内核:e2b 的 Firecracker microVM;k8s 只在 Kata / gVisor 运行时类上)。与能力表 `isolation` 位
115
115
  * (OS 级边界,容器即算;wire `executionLane.isolated` 的属主 `laneIsolated`)是两个问题,所以不叫「isolated」。读者:A 值路由。
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 回决门族 200 体上的**结清证据**(`decisionCommitted` / `checkpointId`)—— 唯一铸点与唯一读法。
3
+ *
4
+ * ## 律(一条)
5
+ * `decisionCommitted: true` = 「本请求点名的那道门,已以本请求的决定被耐久地结清过」的**历史证据**。它只在下面五个执行 / 回放边界之一
6
+ * 被跨过的那一拍、由 {@link buildSettlementEvidence} 铸(闭集 {@link SettlementCrossing},`switch` 穷尽):
7
+ * ① `engine_accepted` —— 引擎续跑腿的受理点(`resumeStream` 解析 = 引擎的 pre-CAS 守卫与 checkpoint resolve CAS 都过了);
8
+ * ② `engine_terminal` —— 引擎续跑腿交回结果之后的终局体(同步长调用形 / verify 臂;含「已提交、又重开同一张卡」的 `retriable:true` 那一形);
9
+ * ③ `replay_matched` —— `/decide` 幂等回放比对通过(同一决定早已与 resolve 原子地落在行上;回放不起续跑);
10
+ * ④ `agent_revived` —— 停驻后台子代复活成功(引擎以选中的 token 续跑、attach 之后才交回);
11
+ * ⑤ `card_voided` —— 复活不了的停驻子代被 deny 作废成功(这道门以本 deny **经作废结清**:卡 pending → expired;没有 resolve、没有 deny 结果行)。
12
+ * 调用方对一条引擎续跑腿的分类({@link GateCommitment})只说「这条腿到边界时能证什么」,**从不单独铸位** —— 位要有边界作证。
13
+ * 不铸的出口(都在调用点成文):`checkpoint.resume_aborted` 取消(拿不到 resolve 的返回值 ⇒ 证不出)、抛出臂的取消(受理之后也可能走到 ⇒
14
+ * **刻意不带**证据,fail-closed)、4xx、交给宿主 / 新铸一跑 / 同一决定重投的回执(`delivered`,门由别人结清或仍待决)、停驻子代认领到的
15
+ * token 不是选中的那一张(⇒ `409 decide.gate_moved`,不复活)。证据只随**同一次决定**的回放走:停驻赎回席的反向重试回放摘掉它
16
+ * ({@link withoutSettlementEvidence})。
17
+ *
18
+ * ## 读法(三句,契约 §4a「结清证据」同句)
19
+ * · 位在场 ⇒ 门已以此决定结清过(历史证据;与 `errorCode:"cancelled"` 或 `retriable:true` 并存时同样成立 —— 它不证明卡的**当前**状态、
20
+ * 不证明批过的动作执行了);
21
+ * · 位缺席 ⇒ 证不出或刻意不带,按「没提交」读;
22
+ * · `checkpointId` = 执行**前**从选中的那一行捕获、过信任边界(`checkpoint-identity.ts` 的 `durableRowIdentity`,face `decision-receipt`)
23
+ * 的门身份,只随位出现;缺席 = 身份不可用(老行 / 没过边界)或不适用(回放的投影不带门身份),不推断任何事。
24
+ *
25
+ * ## 读者
26
+ * 本仓两个:`/decide` 的 [ref] 迟到结算留痕与 remember 豁免落店,都经 {@link decisionCommittedOf} 读体上的位(一处读法;不读 `errorCode`)。
27
+ * 纯数据、零运行时依赖(停驻子代腿与 HTTP 层共用)。
28
+ */
29
+ import type { GateIdentity } from "./checkpoint-identity.js";
30
+ /**
31
+ * 一条**引擎续跑腿**到达边界时能证什么(调用方声明;闭集)。`DriveResumeArgs` 的必填位 —— 同一条驱动路径既续本请求点名的门,也替宿主投递。
32
+ * · `gate_resolved` —— 这条腿 resolve 的就是本请求点名的那道门(`/decide` 引擎臂、`/resume`、`/plan_review`);`checkpointId` = 执行前捕获的身份片段。
33
+ * · `delivered` —— 这条腿 resolve 的是宿主的门(wake;含 `/decide` workflow 车道的 wake 投递腿):本请求点名的那道门由别人结清或仍待决 ⇒ 到任何边界都不铸。
34
+ */
35
+ export type GateCommitment = {
36
+ readonly kind: "gate_resolved";
37
+ readonly checkpointId?: string;
38
+ } | {
39
+ readonly kind: "delivered";
40
+ };
41
+ /** 五个边界(铸点穷尽)。引擎两边界按腿的分类铸;回放不带身份;停驻子代两边界的门恒是本请求点名的那道,身份取执行前捕获的那一份。 */
42
+ export type SettlementCrossing = {
43
+ readonly at: "engine_accepted" | "engine_terminal";
44
+ readonly commitment: GateCommitment;
45
+ } | {
46
+ readonly at: "replay_matched";
47
+ } | {
48
+ readonly at: "agent_revived" | "card_voided";
49
+ readonly gate: GateIdentity;
50
+ };
51
+ /** 体上的结清证据(两键都可缺席;wire 只加)。由调用方原样展开进它自己的体形(各体形保持各自的键集,不并进一张受理形)。 */
52
+ export interface GateSettlementEvidence {
53
+ readonly decisionCommitted?: true;
54
+ readonly checkpointId?: string;
55
+ }
56
+ /** 没有跨过任何边界的回执(`/decide` workflow 车道的宿主新一跑、同一决定重投)带的证据:无。 */
57
+ export declare const NO_SETTLEMENT_EVIDENCE: GateSettlementEvidence;
58
+ /** **唯一铸点**:一次边界跨越 → 体上的证据片段。 */
59
+ export declare function buildSettlementEvidence(crossing: SettlementCrossing): GateSettlementEvidence;
60
+ /**
61
+ * 一张已带证据的回执被拿去答**另一个**决定(停驻赎回席的反向重试:回放首次受理的那一向、不改判)⇒ 证据两键都摘掉 ——
62
+ * 证据说的是当初被结清的那次决定,这一次请求的决定从没被结清。同向重试是同一次请求的回放,体原样(证据随它)。
63
+ * 只摘、不铸:本模块之外没有第二个写这两键的地方。
64
+ */
65
+ export declare function withoutSettlementEvidence<B extends Record<string, unknown>>(body: B): Omit<B, keyof GateSettlementEvidence>;
66
+ /** **唯一读法**:这张回决应答说「门已以此决定结清过」吗(`200` 且体上的位为真)。两个读者同调这一只。 */
67
+ export declare function decisionCommittedOf(out: {
68
+ readonly status: number;
69
+ readonly body: object;
70
+ }): boolean;
71
+ //# sourceMappingURL=gate-settlement.d.ts.map
@@ -0,0 +1,40 @@
1
+ export const NO_SETTLEMENT_EVIDENCE = Object.freeze({});
2
+ const settled = (gate) => ({
3
+ decisionCommitted: true,
4
+ ...(gate.checkpointId !== undefined ? { checkpointId: gate.checkpointId } : {}),
5
+ });
6
+ export function buildSettlementEvidence(crossing) {
7
+ switch (crossing.at) {
8
+ case "engine_accepted":
9
+ case "engine_terminal": {
10
+ const c = crossing.commitment;
11
+ switch (c.kind) {
12
+ case "gate_resolved":
13
+ return settled(c);
14
+ case "delivered":
15
+ return NO_SETTLEMENT_EVIDENCE;
16
+ default: {
17
+ const unhandled = c;
18
+ throw new Error(`unhandled gate commitment: ${JSON.stringify(unhandled)}`);
19
+ }
20
+ }
21
+ }
22
+ case "replay_matched":
23
+ return settled({});
24
+ case "agent_revived":
25
+ case "card_voided":
26
+ return settled(crossing.gate);
27
+ default: {
28
+ const unhandled = crossing;
29
+ throw new Error(`unhandled settlement crossing: ${JSON.stringify(unhandled)}`);
30
+ }
31
+ }
32
+ }
33
+ export function withoutSettlementEvidence(body) {
34
+ const { decisionCommitted: _committed, checkpointId: _identity, ...rest } = body;
35
+ return rest;
36
+ }
37
+ export function decisionCommittedOf(out) {
38
+ return out.status === 200 && out.body.decisionCommitted === true;
39
+ }
40
+ //# sourceMappingURL=gate-settlement.js.map
@@ -25,6 +25,7 @@ export declare const CLASSIC_STATION_ROLE: {
25
25
  readonly "classic.UserPromptSubmit": "chain";
26
26
  readonly "classic.Stop": "chain";
27
27
  readonly "classic.SubagentStop": "chain";
28
+ readonly "classic.SubagentStart": "chain";
28
29
  readonly "classic.StopFailure": "observe";
29
30
  readonly "classic.PreCompact": "chain";
30
31
  readonly "classic.PostCompact": "observe";
@@ -6,6 +6,7 @@ export const CLASSIC_STATION_ROLE = {
6
6
  "classic.UserPromptSubmit": "chain",
7
7
  "classic.Stop": "chain",
8
8
  "classic.SubagentStop": "chain",
9
+ "classic.SubagentStart": "chain",
9
10
  "classic.StopFailure": "observe",
10
11
  "classic.PreCompact": "chain",
11
12
  "classic.PostCompact": "observe",
@@ -26,7 +26,7 @@ export declare function commandLineChars(entry: {
26
26
  export declare function hookPluginEnv(p: HookPluginContext | undefined): Record<string, string>;
27
27
  /**
28
28
  * 起法:shell 形交回命令串(shell 由调用点落定),exec 形交回可执行名与参数,起不来交回真因(进调用点的 `spawnError`)。
29
- * `env` = 这条 hook 的**有效环境层**({@link composeHookEnvLayer} 的产物;调用点铺在基础 allowlist 之上,不再自己合并任何一层)。
29
+ * `env` = 这条 hook 的**有效环境层**({@link composeHookEnvLayer} 的产物;调用点把它作为授予交给宿主子进程的唯一组合,不再自己合并任何一层)。
30
30
  */
31
31
  export type CommandSpawnPlan = {
32
32
  readonly kind: "shell";
@@ -42,7 +42,7 @@ export type CommandSpawnPlan = {
42
42
  readonly error: string;
43
43
  };
44
44
  /**
45
- * 一条 command hook 的**有效环境层**(基础 allowlist 之上的那一层)—— **唯一铺法**,后铺者赢:`CLAUDE_PROJECT_DIR` 的缺省(`cwd`,hook 的工作目录;请求没写它才取)< 请求 `settings.env`(`requestEnv`;
45
+ * 一条 command hook 的**有效环境层**(继承之上的那一层授予)—— **唯一铺法**,后铺者赢:`CLAUDE_PROJECT_DIR` 的缺省(`cwd`,hook 的工作目录;请求没写它才取)< 请求 `settings.env`(`requestEnv`;
46
46
  * 请求自己写了 `CLAUDE_PROJECT_DIR` 就是它,空串也照用)< 条目自己的插件上下文({@link hookPluginEnv})。键的枚举顺序 = 请求的键在前、`CLAUDE_PROJECT_DIR` 缺省时接后、插件键最后。调用点传**原料**(工作目录与请求 env),不预先取舍 `CLAUDE_PROJECT_DIR`。
47
47
  * 「有没有值」的判据、exec 形的占位替换、shell 形子进程的环境都读这一个对象(见文件头);http 条目的 `$NAME` 插值取环境也走这一只(无插件上下文)。
48
48
  */
@@ -32,20 +32,13 @@ export const MAX_HOOK_CONTEXT_TOTAL_CHARS = 10_000;
32
32
  const MIN_HOOK_CONTEXT_PARTIAL_CHARS = 256;
33
33
  const MAX_TOOL_RESPONSE_CHARS = 32_768;
34
34
  const MAX_TOOL_INPUT_CHARS = 262_144;
35
- const HOOK_ENV_ALLOWLIST = [
36
- "PATH", "HOME", "SHELL", "USER", "LOGNAME", "TERM", "TZ",
37
- "LANG", "LC_ALL", "LC_CTYPE", "LC_MESSAGES", "TMPDIR",
38
- ];
39
- function hookBaseEnv(spawning) {
40
- return spawning
41
- ? hostSubprocessEnv({ site: "server.hooks.spawn-env", policy: [...HOOK_ENV_ALLOWLIST], allow: [] })
42
- : composeHostSubprocessEnv({ policy: [...HOOK_ENV_ALLOWLIST], allow: [] });
43
- }
44
- function hookEnvOverBase(layer, spawning) {
45
- return { ...hookBaseEnv(spawning), ...layer };
35
+ const HOOK_ENV_ALLOWLIST = ["PATH", "HOME", "SHELL", "USER", "LOGNAME", "TERM", "TZ", "LANG", "LC_ALL", "LC_CTYPE", "LC_MESSAGES", "TMPDIR"];
36
+ function hookChildEnv(layer, spawning) {
37
+ const o = { policy: [...HOOK_ENV_ALLOWLIST], allow: [], grants: [layer] };
38
+ return spawning ? hostSubprocessEnv({ site: "server.hooks.spawn-env", ...o }) : composeHostSubprocessEnv(o);
46
39
  }
47
40
  function hookEnv(ctx) {
48
- return hookEnvOverBase(composeHookEnvLayer(ctx.shellEnv, ctx.cwd, undefined), false);
41
+ return hookChildEnv(composeHookEnvLayer(ctx.shellEnv, ctx.cwd, undefined), false);
49
42
  }
50
43
  export const SETTINGS_HOOK_STATIONS = {
51
44
  PreToolUse: ["classic.PreToolUse"],
@@ -754,7 +747,7 @@ function buildHookContextField(parts) {
754
747
  return text === undefined ? {} : { additionalContext: text };
755
748
  }
756
749
  function spawnPlannedCommand(plan, ctx, stdio) {
757
- const env = hookEnvOverBase(plan.env, true);
750
+ const env = hookChildEnv(plan.env, true);
758
751
  if (plan.kind === "exec")
759
752
  return spawn(plan.file, [...plan.args], { cwd: ctx.cwd, env, stdio, detached: true });
760
753
  const sh = hostShell();
@@ -1006,7 +999,14 @@ async function runLlmHook(entry, payload, ctx, extra) {
1006
999
  const MAX_BLANKED_REFERENCES_LOGGED = 16;
1007
1000
  async function runHttpHook(entry, payload, ctx, event) {
1008
1001
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
1009
- const interpolated = interpolateHookEnvVars(entry.headers ?? {}, entry.allowedEnvVars ?? [], hookEnv(ctx));
1002
+ let hookChildVars;
1003
+ try {
1004
+ hookChildVars = hookEnv(ctx);
1005
+ }
1006
+ catch (e) {
1007
+ return { code: null, stdout: "", stderr: "", timedOut: false, spawnError: String(e) };
1008
+ }
1009
+ const interpolated = interpolateHookEnvVars(entry.headers ?? {}, entry.allowedEnvVars ?? [], hookChildVars);
1010
1010
  if (interpolated.blanked.length > 0) {
1011
1011
  ctx.logger.warn("hook_http_header_env_blanked", {
1012
1012
  event,
@@ -743,10 +743,11 @@ export function createAdmission(ctx) {
743
743
  }
744
744
  }
745
745
  const { body: admitted, entry: entryMode } = stampEntryPermissionMode(body, autonomyModeFloor(deps.config.autonomy));
746
+ const lease = deps.peerDesk.lease("fresh");
746
747
  try {
747
748
  const auth = deps.authorize ? await deps.authorize({ req, body: admitted }) : undefined;
748
749
  const engineNotices = [];
749
- const spec = await deps.resolveSpec(admitted, req, auth, { leg: "fresh", ...(legFacts?.liveStream === true ? { liveStream: true } : {}), onNotice: (n) => engineNotices.push(n) });
750
+ const spec = await deps.resolveSpec(admitted, req, auth, { leg: "fresh", ...(legFacts?.liveStream === true ? { liveStream: true } : {}), onNotice: (n) => engineNotices.push(n), lease });
750
751
  const bindingIntent = await resolveDevicePlacementIntent({
751
752
  config: deps.config,
752
753
  req,
@@ -760,9 +761,10 @@ export function createAdmission(ctx) {
760
761
  const clampNotice = buildEntryModeClampNotice(entryMode, spec.sessionId);
761
762
  const pendingNotices = clampNotice !== undefined ? [clampNotice, ...engineNotices] : engineNotices;
762
763
  const warnings = allowSelectorWarningsOf(admitted, spec.tools ?? []);
763
- return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: admitted.jobId, body: admitted, acceptance, ...(bindingIntent === undefined ? {} : { bindingIntent }), ...(pendingNotices.length > 0 ? { engineNotices: pendingNotices } : {}), ...(warnings.length > 0 ? { warnings } : {}) };
764
+ return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: admitted.jobId, body: admitted, acceptance, peerLease: lease, ...(bindingIntent === undefined ? {} : { bindingIntent }), ...(pendingNotices.length > 0 ? { engineNotices: pendingNotices } : {}), ...(warnings.length > 0 ? { warnings } : {}) };
764
765
  }
765
766
  catch (err) {
767
+ lease.release();
766
768
  if (err instanceof HttpError) {
767
769
  if (onTypedFailure) {
768
770
  onTypedFailure(err);
@@ -18,6 +18,10 @@ import type { IncomingMessage, ServerResponse } from "node:http";
18
18
  import type { ServiceConfig } from "../config-types.js";
19
19
  import type { RouteCtxBase, RunRegistry } from "./route-ctx.js";
20
20
  import type { FlatServiceDeps } from "./server.js";
21
+ /** `/health` 的 `mods` 段(运维声明的 mod 包装上了几只;present-iff 装上了)—— 段的键集属主,自铸 wire 值登记表 `healthModsKey` 绑它。 */
22
+ export interface HealthDeploymentModsSection {
23
+ readonly deploymentPackages: number;
24
+ }
21
25
  /**
22
26
  * 🔴 [ref](test T-72 [ref];主会话裁 ①,分诊 [ref])—— 全局服务凭据门**之前**那几族只读路
23
27
  * (`route-table.ts` 里 `position: "read-credential"` 的行)的**唯一**鉴权谓词:「服务凭据 ∨ 已验 principal」。