@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,6 +4,12 @@ import { dirname, join } from "node:path";
4
4
  import { encodeScopeSegment, syncMemoryScope, } from "@sema-agent/core";
5
5
  import { replicationCovers } from "./memory-scope.js";
6
6
  import { parseProjectLayerKey } from "./project-layer-key.js";
7
+ const CURSOR_MISMATCH_CODE = "config.memory_sync_cursor_mismatch";
8
+ const CURSOR_PREDATES_STORE_NOTICE = "memory_sync_cursor_predates_store";
9
+ const CURSOR_PREDATES_STORE_OUTS = "this layer's sync baseline was written before sync cursors carried a store binding, so the layer does not sync until the operator picks one of two ways out: " +
10
+ "(1) run one sync round on the previous server version so deletions made since the last round reach the other side, then delete this file; " +
11
+ "(2) delete this file now to accept re-convergence from an empty baseline — entries changed on both sides are reported as conflicts, " +
12
+ "and entries deleted on one side since the last round come back from the other side";
7
13
  export const DEFAULT_MEMORY_SYNC_PEER = "central";
8
14
  export function createMemorySyncTransport(opts) {
9
15
  const base = opts.url.replace(/\/+$/, "");
@@ -25,32 +31,56 @@ export function createMemorySyncTransport(opts) {
25
31
  export function memorySyncCursorPath(memoryRoot, peer, scope) {
26
32
  return join(memoryRoot, "sync-cursors", encodeScopeSegment(peer), `${createHash("sha256").update(scope, "utf8").digest("hex").slice(0, 32)}.json`);
27
33
  }
28
- function legacyMemorySyncCursorPath(memoryRoot, peer) {
34
+ function perPeerCursorPath(memoryRoot, peer) {
29
35
  return join(memoryRoot, `sync-cursor-${encodeScopeSegment(peer)}.json`);
30
36
  }
31
- async function loadCursor(path, scope, peer, log) {
37
+ async function readCursorFile(path) {
32
38
  let raw;
33
39
  try {
34
40
  raw = await readFile(path, "utf8");
35
41
  }
36
42
  catch (err) {
37
- if (err.code !== "ENOENT")
38
- log.warn("memory_sync_cursor_read_failed", { path, err: String(err), note: "treating as first round" });
39
- return undefined;
43
+ return err.code === "ENOENT" ? { kind: "absent" } : { kind: "unreadable", err: String(err) };
40
44
  }
41
- let parsed;
42
45
  try {
43
- parsed = JSON.parse(raw);
46
+ const value = JSON.parse(raw);
47
+ return { kind: "parsed", value };
44
48
  }
45
49
  catch (err) {
46
- log.warn("memory_sync_cursor_corrupt", { path, err: String(err), note: "treating as first round" });
47
- return undefined;
50
+ return { kind: "corrupt", err: String(err) };
51
+ }
52
+ }
53
+ function predatesStoreBinding(parsed, scope, peer) {
54
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
55
+ return false;
56
+ const c = parsed;
57
+ return c["scope"] === scope && c["peer"] === peer && c["store"] === undefined;
58
+ }
59
+ async function loadLayerBaseline(path, perPeer, scope, peer, log) {
60
+ const read = await readCursorFile(path);
61
+ switch (read.kind) {
62
+ case "absent": {
63
+ if (perPeer === undefined)
64
+ return { kind: "none" };
65
+ const legacy = await readCursorFile(perPeer);
66
+ return legacy.kind === "parsed" && predatesStoreBinding(legacy.value, scope, peer) ? { kind: "predates-store", path: perPeer } : { kind: "none" };
67
+ }
68
+ case "unreadable":
69
+ log.warn("memory_sync_cursor_read_failed", { path, err: read.err, note: "treating as first round" });
70
+ return { kind: "none" };
71
+ case "corrupt":
72
+ log.warn("memory_sync_cursor_corrupt", { path, err: read.err, note: "treating as first round" });
73
+ return { kind: "none" };
74
+ case "parsed": {
75
+ if (predatesStoreBinding(read.value, scope, peer))
76
+ return { kind: "predates-store", path };
77
+ const cursor = cursorOf(read.value, scope, peer);
78
+ if (cursor !== undefined)
79
+ return { kind: "cursor", cursor };
80
+ log.warn("memory_sync_cursor_invalid", { path, scope, peer, note: "shape/identity mismatch — treating as first round" });
81
+ return { kind: "none" };
82
+ }
48
83
  }
49
- const cursor = cursorOf(parsed, scope, peer);
50
- if (cursor !== undefined)
51
- return cursor;
52
- log.warn("memory_sync_cursor_invalid", { path, scope, peer, note: "shape/identity mismatch — treating as first round" });
53
- return undefined;
54
84
  }
55
85
  function cursorOf(parsed, scope, peer) {
56
86
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -58,43 +88,18 @@ function cursorOf(parsed, scope, peer) {
58
88
  const baseRevsRaw = c["baseRevs"];
59
89
  if (c["scope"] === scope &&
60
90
  c["peer"] === peer &&
91
+ typeof c["store"] === "string" &&
61
92
  typeof c["updatedAtMs"] === "number" &&
62
93
  Number.isFinite(c["updatedAtMs"]) &&
63
94
  baseRevsRaw !== null &&
64
95
  typeof baseRevsRaw === "object" &&
65
96
  !Array.isArray(baseRevsRaw) &&
66
97
  Object.values(baseRevsRaw).every((v) => typeof v === "string" && v.length > 0)) {
67
- return { scope, peer, baseRevs: { ...baseRevsRaw }, updatedAtMs: c["updatedAtMs"] };
98
+ return { scope, peer, store: c["store"], baseRevs: { ...baseRevsRaw }, updatedAtMs: c["updatedAtMs"] };
68
99
  }
69
100
  }
70
101
  return undefined;
71
102
  }
72
- async function adoptLegacyCursor(memoryRoot, scope, peer, log) {
73
- const target = memorySyncCursorPath(memoryRoot, peer, scope);
74
- const legacy = legacyMemorySyncCursorPath(memoryRoot, peer);
75
- const facts = await Promise.all([readFile(target, "utf8").then(() => "present", (err) => (err.code === "ENOENT" ? "absent" : "present")), readFile(legacy, "utf8").then((text) => ({ text }), (err) => ({ err }))]);
76
- const [targetFact, legacyRead] = facts;
77
- if (targetFact === "present")
78
- return;
79
- if ("err" in legacyRead) {
80
- if (legacyRead.err.code !== "ENOENT")
81
- log.warn("memory_sync_cursor_read_failed", { path: legacy, err: String(legacyRead.err), note: "legacy cursor not adopted — this layer starts from its own cursor" });
82
- return;
83
- }
84
- let parsed;
85
- try {
86
- parsed = JSON.parse(legacyRead.text);
87
- }
88
- catch (err) {
89
- log.warn("memory_sync_cursor_corrupt", { path: legacy, err: String(err), note: "legacy cursor not adopted" });
90
- return;
91
- }
92
- if (cursorOf(parsed, scope, peer) === undefined)
93
- return;
94
- await mkdir(dirname(target), { recursive: true });
95
- await rename(legacy, target);
96
- log.info("memory_sync_cursor_adopted", { from: legacy, to: target, scope, peer });
97
- }
98
103
  async function saveCursor(path, cursor) {
99
104
  await mkdir(dirname(path), { recursive: true });
100
105
  const tmp = `${path}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
@@ -110,7 +115,6 @@ export function createMemorySyncRunner(opts) {
110
115
  const peer = opts.peer ?? DEFAULT_MEMORY_SYNC_PEER;
111
116
  const coverage = { kind: "layers", layers: [opts.scope] };
112
117
  const tracked = new Set();
113
- let legacyCursorChecked = false;
114
118
  let enumerationWarned = false;
115
119
  const isCoveredProjectLayer = (scope) => scope !== opts.scope && parseProjectLayerKey(scope) !== undefined && replicationCovers(coverage, scope);
116
120
  const layersOfRound = () => {
@@ -124,21 +128,36 @@ export function createMemorySyncRunner(opts) {
124
128
  };
125
129
  const syncLayer = async (scope, reason) => {
126
130
  const cursorPath = memorySyncCursorPath(opts.memoryRoot, peer, scope);
127
- let cursor = await loadCursor(cursorPath, scope, peer, opts.log);
131
+ const baseline = await loadLayerBaseline(cursorPath, scope === opts.scope ? perPeerCursorPath(opts.memoryRoot, peer) : undefined, scope, peer, opts.log);
132
+ if (baseline.kind === "predates-store") {
133
+ opts.log.warn(CURSOR_PREDATES_STORE_NOTICE, { scope, peer, path: baseline.path, note: CURSOR_PREDATES_STORE_OUTS });
134
+ throw new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — its baseline ${baseline.path} predates the store binding (see warn ${CURSOR_PREDATES_STORE_NOTICE})`);
135
+ }
136
+ let cursor = baseline.kind === "cursor" ? baseline.cursor : undefined;
128
137
  let result;
129
138
  let subRound = 0;
130
139
  for (;;) {
131
140
  subRound += 1;
132
- result = await syncMemoryScope({
133
- backend: opts.backend,
134
- scope,
135
- transport: opts.transport,
136
- peer,
137
- ...(cursor !== undefined ? { cursor } : {}),
138
- ...(opts.now !== undefined ? { now: opts.now } : {}),
139
- ...(opts.maxPushEntries !== undefined ? { maxPushEntries: opts.maxPushEntries } : {}),
140
- ...(opts.maxPullEntries !== undefined ? { maxPullEntries: opts.maxPullEntries } : {}),
141
- });
141
+ try {
142
+ result = await syncMemoryScope({
143
+ backend: opts.backend,
144
+ scope,
145
+ transport: opts.transport,
146
+ peer,
147
+ ...(cursor !== undefined ? { cursor } : {}),
148
+ ...(opts.now !== undefined ? { now: opts.now } : {}),
149
+ ...(opts.maxPushEntries !== undefined ? { maxPushEntries: opts.maxPushEntries } : {}),
150
+ ...(opts.maxPullEntries !== undefined ? { maxPullEntries: opts.maxPullEntries } : {}),
151
+ });
152
+ }
153
+ catch (err) {
154
+ if (cursor === undefined || err?.code !== CURSOR_MISMATCH_CODE)
155
+ throw err;
156
+ opts.log.warn("memory_sync_cursor_rebound", { scope, peer, subRound, note: "the persisted cursor is bound to another store — running this round from no cursor and replacing it" });
157
+ cursor = undefined;
158
+ subRound -= 1;
159
+ continue;
160
+ }
142
161
  for (const w of result.warnings ?? [])
143
162
  opts.log.warn("memory_sync_warning", { scope, peer, subRound, warning: w });
144
163
  if (!(result.pushTruncated === true || result.pullTruncated === true))
@@ -192,10 +211,6 @@ export function createMemorySyncRunner(opts) {
192
211
  return result;
193
212
  };
194
213
  const round = async (reason) => {
195
- if (!legacyCursorChecked) {
196
- legacyCursorChecked = true;
197
- await adoptLegacyCursor(opts.memoryRoot, opts.scope, peer, opts.log);
198
- }
199
214
  const layers = [];
200
215
  const failures = [];
201
216
  for (const scope of layersOfRound()) {
@@ -2,7 +2,7 @@ import { type MemoryBackend, type MemoryEntry, type PatchReport } from "@sema-ag
2
2
  import type { MemorySyncStore } from "./plugins/memory-sync-store-pg.js";
3
3
  /** 一轮同步请求(验型后的干净形状——`parseMemorySyncRequest` 是唯一铸造点)。 */
4
4
  export interface MemorySyncRequest {
5
- /** 对端标识(MemorySyncCursor.peer,per-(scope,peer) 游标分区键)。 */
5
+ /** 对端标识(`CentralSyncCursor.peer`,per-(scope,peer) 游标分区键)。 */
6
6
  peer: string;
7
7
  /** 客户端游标的共同基线:id → rev at last completed sync round(空 map = 首轮)。 */
8
8
  baseRevs: Record<string, string>;
@@ -1,23 +1,3 @@
1
- /**
2
- * **env 模型路由读数的唯一读口**:`readModelRouteEnv(env)`、env 腿的条目表 `envLaneTable`,与车道值级端口 `laneAndEndpointFor`。
3
- *
4
- * ## 形(一只读口、一处调用)
5
- * 「env 里哪几把键是模型路由、空串 = 未设、provider 的显式 / 推断 / 缺省、env 腿铸哪几个条目槽」的读法只在这里:
6
- * · {@link readModelRouteEnv}:`parseModelDomain`(`config.ts`,起服铸 `ServiceConfig` 的路由字段)吃它的读数(`process.env` 与测试快照是同一个入参位);
7
- * · {@link envLaneTable}:env 腿的条目名与键序(`parseModelDomain` 铸真 `Model` 表经它);
8
- * · {@link laneAndEndpointFor}:值级端口 —— 「这只模型在这张路由表上走哪条车道、这条车道的端点 / 凭据取哪一对 env 键」。车道只经 `brainLaneFor`
9
- * (core `brainLaneOf` 对路由表的答案)取,键名取 {@link MODEL_ROUTE_ENV_KEYS};**只交键名不交值**。
10
- * 路由判据本身(「将要服务的那张表里每条 openai 兼容路由是否解析得到端点」)不在这里:唯一属主是 `model-route-endpoint.ts` 的 `judgeModelRouteEndpoint`,
11
- * 起服段与 `GET /v1/models.modelPlane` 经 `judgeServedModelRoute` 问它 —— 它们判的是**将要服务的那张表**(env 表或目录表),不是本文件读出的 env 视图。
12
- *
13
- * ## 对外
14
- * 本模块的精选再导出是子路径 `@sema-agent/server/model-route-env`(`src/model-route-env.ts`):引擎 core 是 BYOM(只吃调用方给的路由表,不读这些键),
15
- * 别仓要在引擎起之前回答「走哪条车道、端点 / 凭据取哪一对键」,读这一只,不各抄一份键名与车道判。
16
- *
17
- * ## 纯
18
- * 不读 `process.env`、不读文件、不枚举快照 —— 只按 {@link MODEL_ROUTE_ENV_KEYS} 点名取值(依赖闭包里没有 `node:*` / `process`,
19
- * `test/model-route-env-reading.test.ts` 钉)。同入同出;返回值是新对象。provider 词域外的值在这里按引擎派发的口径读作网关腿(`brainLaneFor`:域外词的路由表里没有那个键 ⇒ 引擎路由器落网关 fallback),不在此拒(域外拒启是 `model-provider-word` 那条不变量的事)。
20
- */
21
1
  import { type ModelBrainLane } from "./model-provider.js";
22
2
  /**
23
3
  * 路由读数读的九把 env 键(**闭表**;{@link readModelRouteEnv} 只碰这几把,测试用代理钉「读集 ⊆ 本表」)。
@@ -59,8 +39,9 @@ export interface ModelRouteEnvReading {
59
39
  /** `MODEL_GATEWAY_FALLBACK_URLS` 逐项 trim、丢空项。 */
60
40
  readonly gatewayFallbackUrls: string[];
61
41
  /**
62
- * `MODEL_API_KEY`(网关腿的部署级凭据):**原样**读 —— 与其余各把不同,空串**不**折成未设。brain 装配以**真值**判这把凭据发不发(空串部署一个凭据字节都不发),
42
+ * `MODEL_API_KEY`(网关腿的部署级凭据):读数**原样**交出 —— 与其余各把不同,空串**不**折成未设:
63
43
  * 起服铸的 `ServiceConfig.gatewayApiKey` 逐字保留空串(compose 的 `${VAR:-}` 模板与空 Secret 挂载天天产它);读数改折叠会改变那一位。
44
+ * 「这把凭据设了没有」的问法不在读数里折,而是消费方问 {@link declaredCredential}(引擎的 `isBlankCredential`:空串与只含空白都 = 没有)。
64
45
  * 它只与网关端点配对(没有 {@link gatewayBaseUrl} 就不装载),配对由消费方判。
65
46
  */
66
47
  readonly gatewayApiKey: string | undefined;
@@ -85,6 +66,9 @@ export interface ModelRouteEnvReading {
85
66
  /** cheap 槽铸不铸(见 {@link EnvCheapSlot})。 */
86
67
  readonly cheapSlot: EnvCheapSlot;
87
68
  }
69
+ /** 一把凭据键「设了没有」的唯一问法(两把 anthropic 键与网关键 `MODEL_API_KEY` 同读):引擎的 `isBlankCredential`(整串 trim 后为空 ⇒ 没有凭据)—— 与引擎对同一个值的读法同一只,
70
+ * 不自拼第二种「空」:只含空白的 key 旁边的 token 是 bearer(引擎让那只头作凭据照发),不是同设冲突;两把都空白 = 缺席、路由不挂;只含空白的网关 key = 没有部署凭据。 */
71
+ export declare const declaredCredential: (v: string | undefined) => string | undefined;
88
72
  /** 逗号串读法的归一点:逐项 trim、丢空项(缺席 = 空串 = `[]`)。本文件的 CSV 路由键与 `config.ts` 的逗号串旋钮(按名薄壳 `csv(name)`)同读这一只;
89
73
  * 目录模块的回显分词与 `SERVICE_AUTH_TOKENS` 的 `k=v` 对分词是同形的另两处,尚未改调(清账版收,本函数届时挪到中性模块)。 */
90
74
  export declare const csvOf: (raw: string | undefined) => string[];
@@ -98,7 +82,7 @@ export declare function readModelRouteEnv(env: ModelRouteEnv): ModelRouteEnvRead
98
82
  * · `ANTHROPIC_API_KEY` ⇒ `x-api-key`(只发它);
99
83
  * · `ANTHROPIC_AUTH_TOKEN` ⇒ `Authorization: Bearer`(只发它);
100
84
  * · 两个都设 ⇒ `conflict`:两个名字说的是两种头,哪个赢都是替运维猜(7.135.0 及以前是「AUTH_TOKEN 优先」,x-api-key 被静默丢弃)⇒ 起服拒启,拒句 {@link anthropicCredentialRefusal}。
101
- * 空串 = 未设(与 {@link readModelRouteEnv} 同尺)。入参是两个名字各自的值 —— env 读数的两位或 `config.anthropic` 的 `apiKey` / `authToken` —— 不读 `process.env`。
85
+ * 「设了没有」按引擎的 `isBlankCredential` 读(空串与只含空白都 = 未设;与 {@link declaredCredential} 同一只)。入参是两个名字各自的值 —— env 读数的两位或 `config.anthropic` 的 `apiKey` / `authToken` —— 不读 `process.env`。
102
86
  * 读者:起服(`config.ts` 的拒启锚与跨键不变量表 `anthropic-credential-exclusive` 同一句)· brain 装配(交引擎哪种头)· hook 直连腿(哪种头、交引擎配对判官的那份值)·
103
87
  * 车道端口 {@link laneAndEndpointFor}(哪把键)· 起服的 Bearer 部署诊断(`boot/web-admin.ts`)。
104
88
  */
@@ -1,3 +1,4 @@
1
+ import { isBlankCredential } from "@sema-agent/core";
1
2
  import { brainLaneFor, declaredRootUrl } from "./model-provider.js";
2
3
  import { DEFAULT_MODEL_REF } from "./model-select.js";
3
4
  export const MODEL_ROUTE_ENV_KEYS = {
@@ -22,14 +23,15 @@ export function mintedCheapId(slot) {
22
23
  }
23
24
  }
24
25
  const declaredValue = (v) => (v !== undefined && v !== "" ? v : undefined);
26
+ export const declaredCredential = (v) => (v !== undefined && !isBlankCredential(v) ? v : undefined);
25
27
  export const csvOf = (raw) => (raw ?? "")
26
28
  .split(",")
27
29
  .map((s) => s.trim())
28
30
  .filter(Boolean);
29
31
  export function readModelRouteEnv(env) {
30
32
  const K = MODEL_ROUTE_ENV_KEYS;
31
- const anthropicApiKey = declaredValue(env[K.anthropicApiKey]);
32
- const anthropicAuthToken = declaredValue(env[K.anthropicAuthToken]);
33
+ const anthropicApiKey = declaredCredential(env[K.anthropicApiKey]);
34
+ const anthropicAuthToken = declaredCredential(env[K.anthropicAuthToken]);
33
35
  const anthropicBaseUrl = declaredValue(env[K.anthropicBaseUrl]);
34
36
  const anthropicRouteMounted = anthropicApiKey !== undefined || anthropicAuthToken !== undefined;
35
37
  const explicitProvider = declaredValue(env[K.provider]);
@@ -58,8 +60,8 @@ export function readModelRouteEnv(env) {
58
60
  }
59
61
  export function judgeAnthropicCredential(names) {
60
62
  const K = MODEL_ROUTE_ENV_KEYS;
61
- const apiKey = declaredValue(names.apiKey);
62
- const authToken = declaredValue(names.authToken);
63
+ const apiKey = declaredCredential(names.apiKey);
64
+ const authToken = declaredCredential(names.authToken);
63
65
  if (apiKey !== undefined && authToken !== undefined)
64
66
  return { kind: "conflict" };
65
67
  if (apiKey !== undefined)
@@ -111,7 +113,7 @@ export function laneAndEndpointFor(env, model, routes) {
111
113
  };
112
114
  case "gateway": {
113
115
  const endpointDeclared = declaredRootUrl(reading.gatewayBaseUrl) !== undefined;
114
- const credentialLoaded = endpointDeclared && reading.gatewayApiKey !== undefined && reading.gatewayApiKey !== "";
116
+ const credentialLoaded = endpointDeclared && declaredCredential(reading.gatewayApiKey) !== undefined;
115
117
  return { lane, endpointEnvName: K.gatewayBaseUrl, endpointDeclared, apiKeyEnvName: credentialLoaded ? K.gatewayApiKey : undefined };
116
118
  }
117
119
  }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * 部署声明的 mod 包 —— `MODS_ROOT` + `MODS_PACKAGES` 的**唯一读法**,以及部署座装配(`boot/deployment-mods.ts`)全部失败的**唯一词表与拒句**。
3
+ *
4
+ * ## 规则(一句)
5
+ * mod 包只从运维的部署声明装载;声明只说四件宿主的事实 —— 装哪个目录(`dir`)、叫什么(`name`,须与包自己的名逐字同)、站哪一档(`tier`)、
6
+ * 交给它什么(`options`);包里的三件文件只有引擎一只读者(`loadModPackage`),本仓**不读包内任何文件、不算摘要**。
7
+ *
8
+ * ## 声明的形
9
+ * · `MODS_ROOT` —— 绝对路径;声明的每只包解析真实路径之后必须**严格**落在它(的真实路径)之下(按路径分量判,不是前缀;根本身不是包目录)。
10
+ * 单设它而不声明包 = 无事(形照判)。
11
+ * · `MODS_PACKAGES` —— JSON 数组,每项闭集四键 `{ name, dir, tier, options? }`:`name` 非空串;`dir` 绝对路径;`tier` ∈ `prepend | user | append`
12
+ * (宿主必填 —— 包不自报档;站不站得住由引擎的座 × 档表判,部署座三档都许);`options` 任意 JSON 值,原样交包的 `register`。
13
+ * 缺席 / 空串 = 不声明(部署座只有本仓自铸的注册,与没有这两根旋钮的版本逐字节同)。
14
+ * · `MODS_PACKAGES` 是一份声明、不是凭据值:配置目录只回在场位与长度(不回显);`options` 不是凭据通道 —— 本服务不替包扣凭据(声明与包自己读的环境变量
15
+ * 都不在自有凭据声明里,宿主车道 `inheritEnv: "all"` 下模型的子进程照样继承)。
16
+ *
17
+ * ## 失败(两类,一张闭集词表,一种拒句)
18
+ * · **声明 / 文件系统**(本仓判;任何一只包被装载之前,整张声明全部判完):
19
+ * `config_invalid`(某一席的形不对 —— 整段 JSON、某一项、`MODS_ROOT`)· `root_missing`(声明了包而 `MODS_ROOT` 没设)·
20
+ * `name_missing` · `duplicate_name` · `root_unreadable` · `dir_unreadable` · `outside_root` · `not_a_directory`;
21
+ * · **读包者 / 准入**(引擎判,本仓只透传):`package_refused`(读包者拒了这只包)· `name_mismatch`(包自己的名与声明的不同)·
22
+ * `admission_refused`(整列交引擎准入被拒)。
23
+ * 任一失败 = 拒启。拒句一行:`deployment mods refused: <词>(<主语>)[: <细节>]`,整行经出口座 `boot-refusal-line`(剥控制与双向字符、凭据形换记号、
24
+ * ≤ 512 码元含截断记号)—— 读包者 / 准入的机器码原样留在主语括号里;本仓写的句子不回显声明的值(路径、`options`),主语只点名是哪一席(`MODS_PACKAGES[2]`)
25
+ * 或哪只包;读包者 / 准入 / 包自己交回的话原样透传(过出口座),其中可能点名包目录 —— 那是运维要的诊断,包本身是受信代码。
26
+ */
27
+ import type { ModRegistrationTier } from "@sema-agent/core";
28
+ /** 声明的一只包(宿主的四件事实)。 */
29
+ export interface DeclaredModPackage {
30
+ readonly name: string;
31
+ readonly dir: string;
32
+ readonly tier: ModRegistrationTier;
33
+ /** 缺席 = 声明里没写这一键(交给 `register` 的就是 `undefined`)。 */
34
+ readonly options?: unknown;
35
+ }
36
+ /**
37
+ * 部署声明(`loadConfig` 读一次;两只起服入口的装配都读这一份)。两种形,形即律:
38
+ * · **没声明包** —— 没有 `packages` 这一席(变量缺席 / 空串 / 显式 `[]` 同一形);`root` 只在单设了 `MODS_ROOT` 时在(形照判、无事,配置目录照回);
39
+ * · **声明了包** —— 至少一只,且**必有根**:「有包无根」「有根空表」都不是可表示的值(`root_missing` 只在本模块的读法里判,装配口没有这一臂)。
40
+ * 读者按 `d.packages === undefined` 分两形(`packages` 是判别位:没声明那一形上它只能缺席)。
41
+ */
42
+ export type ModsDeclaration = {
43
+ readonly root?: string;
44
+ readonly packages?: never;
45
+ } | {
46
+ readonly root: string;
47
+ readonly packages: readonly [DeclaredModPackage, ...DeclaredModPackage[]];
48
+ };
49
+ /** 声明里的一席:整根旋钮,或 `MODS_PACKAGES` 的第几项。 */
50
+ export type DeclarationSeat = "MODS_ROOT" | "MODS_PACKAGES" | {
51
+ readonly entry: number;
52
+ };
53
+ /** 一次失败(词 + 主语 + 细节)。细节只有两种来源:本仓写死的句子,或读包者 / 准入交回的话(过出口座)。**失败词的唯一来源是本联合各成员的 `word`。** */
54
+ export type DeploymentModsRefusal = {
55
+ readonly word: "config_invalid";
56
+ readonly seat: DeclarationSeat;
57
+ readonly why: string;
58
+ } | {
59
+ readonly word: "root_missing";
60
+ } | {
61
+ readonly word: "name_missing";
62
+ readonly entry: number;
63
+ } | {
64
+ readonly word: "duplicate_name";
65
+ readonly entry: number;
66
+ readonly first: number;
67
+ } | {
68
+ readonly word: "root_unreadable";
69
+ readonly cause: string;
70
+ } | {
71
+ readonly word: "dir_unreadable";
72
+ readonly entry: number;
73
+ readonly cause: string;
74
+ } | {
75
+ readonly word: "outside_root";
76
+ readonly entry: number;
77
+ } | {
78
+ readonly word: "not_a_directory";
79
+ readonly entry: number;
80
+ } | {
81
+ readonly word: "package_refused";
82
+ readonly name: string;
83
+ readonly code: string | undefined;
84
+ readonly reason: string;
85
+ } | {
86
+ readonly word: "name_mismatch";
87
+ readonly name: string;
88
+ readonly packageName: string;
89
+ } | {
90
+ readonly word: "admission_refused";
91
+ readonly code: string | undefined;
92
+ readonly reason: string;
93
+ };
94
+ /** 失败词(拒句的 `<词>`)= 联合成员的 `word`,不另写一份。渲染是穷尽 `switch`(加成员不加臂 = 编译红)。 */
95
+ export type DeploymentModsRefusalWord = DeploymentModsRefusal["word"];
96
+ /** 失败词闭集(运行期全集;「每词至少一格」的钉读它)。 */
97
+ export declare const DEPLOYMENT_MODS_REFUSALS: readonly DeploymentModsRefusalWord[];
98
+ /** 一次失败的拒句(一行;整行经出口座 `boot-refusal-line`:剥控制与双向字符、凭据形换记号、≤ 512 码元含截断记号)。 */
99
+ export declare function buildDeploymentModsRefusalLine(r: DeploymentModsRefusal): string;
100
+ /** 部署座装配的拒启(两只起服入口同一只类型;`refusal` 是机读的那一份,`message` 是它的拒句)。 */
101
+ export declare class DeploymentModsRefusedError extends Error {
102
+ readonly refusal: DeploymentModsRefusal;
103
+ constructor(refusal: DeploymentModsRefusal);
104
+ }
105
+ /**
106
+ * 声明的形(纯函数,`loadConfig` 调;文件系统那一半在装配里):`MODS_ROOT` 的形 → `MODS_PACKAGES` 是不是 JSON 数组 → 逐项(序号升序;每项:是对象 →
107
+ * 键闭集 → `name` → `dir` → `tier` → 与前面的项重名)→ 声明了包而没设根。第一处不过即拒,拒句不回显声明的值。
108
+ */
109
+ export declare function parseModsDeclaration(rootRaw: string | undefined, packagesRaw: string | undefined): ModsDeclaration;
110
+ //# sourceMappingURL=mods-declaration.d.ts.map
@@ -0,0 +1,109 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { seatText } from "./trace/exit-seat.js";
3
+ const DECLARABLE_TIERS = { prepend: true, user: true, append: true };
4
+ const TIER_WORDS = Object.keys(DECLARABLE_TIERS);
5
+ const isDeclarableTier = (v) => typeof v === "string" && Object.hasOwn(DECLARABLE_TIERS, v);
6
+ const ENTRY_KEYS = new Set(["name", "dir", "tier", "options"]);
7
+ const REFUSAL_WORDS = {
8
+ config_invalid: true,
9
+ root_missing: true,
10
+ name_missing: true,
11
+ duplicate_name: true,
12
+ root_unreadable: true,
13
+ dir_unreadable: true,
14
+ outside_root: true,
15
+ not_a_directory: true,
16
+ package_refused: true,
17
+ name_mismatch: true,
18
+ admission_refused: true,
19
+ };
20
+ export const DEPLOYMENT_MODS_REFUSALS = Object.keys(REFUSAL_WORDS);
21
+ const seatLabel = (seat) => (typeof seat === "string" ? seat : `MODS_PACKAGES[${seat.entry}]`);
22
+ const entryLabel = (entry) => seatLabel({ entry });
23
+ const nameLabel = (name) => JSON.stringify(name);
24
+ function refusalText(r) {
25
+ const line = (subject, detail) => `deployment mods refused: ${r.word}${subject !== undefined ? `(${subject})` : ""}${detail !== undefined ? `: ${detail}` : ""}`;
26
+ switch (r.word) {
27
+ case "config_invalid":
28
+ return line(seatLabel(r.seat), r.why);
29
+ case "root_missing":
30
+ return line("MODS_ROOT", "MODS_PACKAGES declares packages but MODS_ROOT is not set — every declared package must resolve under that root");
31
+ case "name_missing":
32
+ return line(entryLabel(r.entry), "name must be a non-empty string — the package's own name (its .claude-plugin/plugin.json `name`), spelled exactly");
33
+ case "duplicate_name":
34
+ return line(entryLabel(r.entry), `same name as ${entryLabel(r.first)} — each declared package is one registration and a name is its identity`);
35
+ case "root_unreadable":
36
+ return line("MODS_ROOT", `its real path cannot be resolved (${r.cause})`);
37
+ case "dir_unreadable":
38
+ return line(entryLabel(r.entry), `dir's real path cannot be resolved (${r.cause})`);
39
+ case "outside_root":
40
+ return line(entryLabel(r.entry), "dir does not resolve strictly under MODS_ROOT (real paths, compared by path components; the root itself is not a package directory)");
41
+ case "not_a_directory":
42
+ return line(entryLabel(r.entry), "dir does not resolve to a directory");
43
+ case "package_refused":
44
+ return line(r.code !== undefined ? `${nameLabel(r.name)}, ${r.code}` : nameLabel(r.name), r.reason);
45
+ case "name_mismatch":
46
+ return line(nameLabel(r.name), `the package names itself ${nameLabel(r.packageName)} — the declared name must be the package's own name, spelled exactly`);
47
+ case "admission_refused":
48
+ return line(r.code, r.reason);
49
+ }
50
+ }
51
+ export function buildDeploymentModsRefusalLine(r) {
52
+ return seatText("boot-refusal-line", refusalText(r));
53
+ }
54
+ export class DeploymentModsRefusedError extends Error {
55
+ refusal;
56
+ constructor(refusal) {
57
+ super(buildDeploymentModsRefusalLine(refusal));
58
+ this.refusal = refusal;
59
+ }
60
+ }
61
+ function refuse(r) {
62
+ throw new DeploymentModsRefusedError(r);
63
+ }
64
+ const isPlainRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
65
+ export function parseModsDeclaration(rootRaw, packagesRaw) {
66
+ const root = rootRaw === undefined || rootRaw === "" ? undefined : rootRaw;
67
+ if (root !== undefined && !isAbsolute(root))
68
+ refuse({ word: "config_invalid", seat: "MODS_ROOT", why: "must be an absolute path" });
69
+ const undeclared = root !== undefined ? { root } : {};
70
+ if (packagesRaw === undefined || packagesRaw.trim() === "")
71
+ return undeclared;
72
+ let parsed;
73
+ try {
74
+ parsed = JSON.parse(packagesRaw);
75
+ }
76
+ catch {
77
+ refuse({ word: "config_invalid", seat: "MODS_PACKAGES", why: "is not valid JSON" });
78
+ }
79
+ if (!Array.isArray(parsed))
80
+ refuse({ word: "config_invalid", seat: "MODS_PACKAGES", why: "must be a JSON array of { name, dir, tier, options? }" });
81
+ const packages = [];
82
+ const firstIndexOf = new Map();
83
+ for (const [entry, item] of parsed.entries()) {
84
+ if (!isPlainRecord(item))
85
+ refuse({ word: "config_invalid", seat: { entry }, why: "must be an object { name, dir, tier, options? }" });
86
+ const unknown = Object.keys(item).filter((k) => !ENTRY_KEYS.has(k));
87
+ if (unknown.length > 0)
88
+ refuse({ word: "config_invalid", seat: { entry }, why: `has keys outside { name, dir, tier, options }: ${unknown.map((k) => JSON.stringify(k)).join(", ")}` });
89
+ const { name, dir, tier } = item;
90
+ if (typeof name !== "string" || name === "")
91
+ refuse({ word: "name_missing", entry });
92
+ if (typeof dir !== "string" || !isAbsolute(dir))
93
+ refuse({ word: "config_invalid", seat: { entry }, why: "dir must be an absolute path" });
94
+ if (!isDeclarableTier(tier))
95
+ refuse({ word: "config_invalid", seat: { entry }, why: `tier must be one of ${TIER_WORDS.join(", ")} — the host declares it, a package never does` });
96
+ const first = firstIndexOf.get(name);
97
+ if (first !== undefined)
98
+ refuse({ word: "duplicate_name", entry, first });
99
+ firstIndexOf.set(name, entry);
100
+ packages.push({ name, dir, tier, ...(Object.hasOwn(item, "options") ? { options: item.options } : {}) });
101
+ }
102
+ const [head, ...tail] = packages;
103
+ if (head === undefined)
104
+ return undeclared;
105
+ if (root === undefined)
106
+ refuse({ word: "root_missing" });
107
+ return { root, packages: [head, ...tail] };
108
+ }
109
+ //# sourceMappingURL=mods-declaration.js.map
@@ -9,4 +9,9 @@ export declare function errorText(e: unknown): string;
9
9
  * `name` 的 getter 抛、或它交回的不是字符串(拼进模板串会抛的 Symbol 等)⇒ `<unreadable>`。
10
10
  */
11
11
  export declare function errorKindOf(err: unknown): string;
12
+ /**
13
+ * 错误自带的机器码(`err.code` 串:Node 的 `ENOENT`、引擎的 `config.mods_invalid` …)—— 第三只读口,同一处兜底 {@link readOr}:`code` 的 getter 抛 / 代理抛 /
14
+ * 不是串 ⇒ `undefined`(没有码,不编一个)。调用方按自己的形判(什么样的码配进一句拒句)再用它。
15
+ */
16
+ export declare function errorCodeOf(err: unknown): string | undefined;
12
17
  //# sourceMappingURL=err-text.d.ts.map
@@ -21,4 +21,13 @@ export function errorKindOf(err) {
21
21
  return typeof name === "string" ? name : KIND_UNREADABLE;
22
22
  }, KIND_UNREADABLE);
23
23
  }
24
+ export function errorCodeOf(err) {
25
+ const code = readOr(() => {
26
+ if (!(err instanceof Error) || !("code" in err))
27
+ return "";
28
+ const c = err.code;
29
+ return typeof c === "string" ? c : "";
30
+ }, "");
31
+ return code === "" ? undefined : code;
32
+ }
24
33
  //# sourceMappingURL=err-text.js.map
@@ -193,7 +193,7 @@ export declare const FAIL_OPEN_TAGS: {
193
193
  };
194
194
  readonly "server.approvals.durable-row-checkpoint-id-missing": {
195
195
  readonly cls: "F";
196
- readonly note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。";
196
+ readonly note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。 第五面:回决门族的 200 体(`/decide` / `/resume` / `/plan_review` 执行之前从选中的那一行捕获一次,`http/resume-legs.ts` 三处;detail `face=decision-receipt`)—— 选中那一行的门身份没过信任边界 ⇒ 结清证据照铸、体上 `checkpointId` 键缺席(决断方只是对不上身份;位、决定、续跑都照常)。每次走到捕获点的决断计一次(计的是选中那一行的事实,与这次决断随后走哪条车道无关)。";
197
197
  };
198
198
  readonly "server.approvals.queue-row-gate-kind-missing": {
199
199
  readonly cls: "F";
@@ -279,6 +279,10 @@ export declare const FAIL_OPEN_TAGS: {
279
279
  readonly cls: "F";
280
280
  readonly note: "S-690(`instruction-source-notice.ts announceInstructionSource`,项目指令换源通告的唯一铸点;detail 的 `op` 点名哪一步):基线**读**失败或超期(会话店抖动 / 列形坏 / 2s 期限到,detail 带 `timeout=`)⇒ 本 run 不比也不写;基线**写**失败或超期 ⇒ 本 run 的换源帧照发(它对本 run 为真),下一 run 可能再说一遍同一次换源(超期后才落地的写由 CAS 挡住,盖不掉更新的基线);通告**出口**抛 ⇒ 本 run 缺这一帧。放行的最坏后果 = 一次换源没被说出来或被多说一次 —— 通告是**披露**面,不参与任何门 / 裁决,模型读到的指令本身不受影响。不放行的代价严重不对称:铸点跑在 core 的 `loadProjectMemory` 席内,抛回去 core 会把这次 prepare 的项目记忆整块丢掉(不注入),为一条通告赔上指令本身。计数让「某台部署的会话店一直读不出基线」与「这台部署从来没换过源」在遥测上分得开。";
281
281
  };
282
+ readonly "server.session-workspace.record-degraded": {
283
+ readonly cls: "F";
284
+ readonly note: "会话行的位置与项目那一格(`session_meta.workspace` / local `session-workspaces.json`;唯一写者 `session-workspace.ts` 的腿记录座,唯一列属主 `plugins/session-workspace-cell.ts`;detail 的 `op` 点名哪一步):`op=write` 一次写失败(库抖 / 盘满 / 锁等待超时)⇒ 行停在上一帧,下一帧照写;`op=read` 一格读不出(被带外改坏)⇒ 读作没有记录,下一帧整格重写;`op=frame` 一帧投不成存储形(引擎不会铸出这种帧)⇒ 这一帧不记。这一格是宿主对**引擎判过的答案**的记录,不是判据 —— 引擎每条腿都重判项目(有记号就判记号,声明不等响亮拒)。放行的最坏后果 = 这只会话的下一条不带 `projectId` 的提交续声明的是旧的项目或不声明(没有记号的检出里这只会话少一次续声明);不放行的代价是把一条在跑的腿、或这只会话往后的每一次提交,为一份记录摔掉。计数非零 = 这台部署的会话店写不动 / 有人改过库。";
285
+ };
282
286
  readonly "server.engine-notice.wire-sink-threw": {
283
287
  readonly cls: "F";
284
288
  readonly note: "#310:`engine_notice` 分流器把一条白名单通告投给某条 run 腿的登记口时,那只口抛了(live 口写向已断/已撕裂的 SSE socket,或 durable 口的账本写同步抛)。放行的最坏后果 = **这一条通告的这一个终点**缺席:①日志终点在分流之前已经逐字打过(事实一条不丢);②两个终点注册成两只独立 sink ⇒ live 抛不牵连 durable(断连后仍看得见的那半保住);③同会话其余口照投。故 F 类。不放行的代价是把异常回抛给 core 的 `deliverEngineNotice` —— 它会吞掉,于是同一次失败**既没有留痕也没有第二只口**,正是本 tag 要根除的形。留痕是承重的:静默吞掉之后「wire 腿为什么总有几条通告不到」在遥测里与「core 本来就没发」同形。";
@@ -174,7 +174,7 @@ export const FAIL_OPEN_TAGS = {
174
174
  },
175
175
  "server.approvals.durable-row-checkpoint-id-missing": {
176
176
  cls: "F",
177
- note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。",
177
+ note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。 第五面:回决门族的 200 体(`/decide` / `/resume` / `/plan_review` 执行之前从选中的那一行捕获一次,`http/resume-legs.ts` 三处;detail `face=decision-receipt`)—— 选中那一行的门身份没过信任边界 ⇒ 结清证据照铸、体上 `checkpointId` 键缺席(决断方只是对不上身份;位、决定、续跑都照常)。每次走到捕获点的决断计一次(计的是选中那一行的事实,与这次决断随后走哪条车道无关)。",
178
178
  },
179
179
  "server.approvals.queue-row-gate-kind-missing": {
180
180
  cls: "F",
@@ -260,6 +260,10 @@ export const FAIL_OPEN_TAGS = {
260
260
  cls: "F",
261
261
  note: "S-690(`instruction-source-notice.ts announceInstructionSource`,项目指令换源通告的唯一铸点;detail 的 `op` 点名哪一步):基线**读**失败或超期(会话店抖动 / 列形坏 / 2s 期限到,detail 带 `timeout=`)⇒ 本 run 不比也不写;基线**写**失败或超期 ⇒ 本 run 的换源帧照发(它对本 run 为真),下一 run 可能再说一遍同一次换源(超期后才落地的写由 CAS 挡住,盖不掉更新的基线);通告**出口**抛 ⇒ 本 run 缺这一帧。放行的最坏后果 = 一次换源没被说出来或被多说一次 —— 通告是**披露**面,不参与任何门 / 裁决,模型读到的指令本身不受影响。不放行的代价严重不对称:铸点跑在 core 的 `loadProjectMemory` 席内,抛回去 core 会把这次 prepare 的项目记忆整块丢掉(不注入),为一条通告赔上指令本身。计数让「某台部署的会话店一直读不出基线」与「这台部署从来没换过源」在遥测上分得开。",
262
262
  },
263
+ "server.session-workspace.record-degraded": {
264
+ cls: "F",
265
+ note: "会话行的位置与项目那一格(`session_meta.workspace` / local `session-workspaces.json`;唯一写者 `session-workspace.ts` 的腿记录座,唯一列属主 `plugins/session-workspace-cell.ts`;detail 的 `op` 点名哪一步):`op=write` 一次写失败(库抖 / 盘满 / 锁等待超时)⇒ 行停在上一帧,下一帧照写;`op=read` 一格读不出(被带外改坏)⇒ 读作没有记录,下一帧整格重写;`op=frame` 一帧投不成存储形(引擎不会铸出这种帧)⇒ 这一帧不记。这一格是宿主对**引擎判过的答案**的记录,不是判据 —— 引擎每条腿都重判项目(有记号就判记号,声明不等响亮拒)。放行的最坏后果 = 这只会话的下一条不带 `projectId` 的提交续声明的是旧的项目或不声明(没有记号的检出里这只会话少一次续声明);不放行的代价是把一条在跑的腿、或这只会话往后的每一次提交,为一份记录摔掉。计数非零 = 这台部署的会话店写不动 / 有人改过库。",
266
+ },
263
267
  "server.engine-notice.wire-sink-threw": {
264
268
  cls: "F",
265
269
  note: "#310:`engine_notice` 分流器把一条白名单通告投给某条 run 腿的登记口时,那只口抛了(live 口写向已断/已撕裂的 SSE socket,或 durable 口的账本写同步抛)。放行的最坏后果 = **这一条通告的这一个终点**缺席:①日志终点在分流之前已经逐字打过(事实一条不丢);②两个终点注册成两只独立 sink ⇒ live 抛不牵连 durable(断连后仍看得见的那半保住);③同会话其余口照投。故 F 类。不放行的代价是把异常回抛给 core 的 `deliverEngineNotice` —— 它会吞掉,于是同一次失败**既没有留痕也没有第二只口**,正是本 tag 要根除的形。留痕是承重的:静默吞掉之后「wire 腿为什么总有几条通告不到」在遥测里与「core 本来就没发」同形。",
@@ -62,7 +62,7 @@ export declare const SECRET_ENV_SCRUB_SITES: {
62
62
  readonly remedy: null;
63
63
  };
64
64
  readonly "server.hooks.spawn-env": {
65
- readonly note: "settings hooks 的命令腿(请求带来的命令跑在宿主上)的 env —— 严格名单(`PATH` / `HOME` / 终端 / locale 等十二名)经引擎组合,名单外一律不交;请求显式给的 `settings.env` 另叠在上面";
65
+ readonly note: "settings hooks 的命令腿(请求带来的命令跑在宿主上)的 env —— 严格名单(`PATH` / `HOME` / 终端 / locale 等十二名)经引擎的 `hostChildEnv` 组合:名单外一律不交,本服务自有凭据任何情况都不交;请求的有效环境层(`settings.env`)作授予叠在上面;请求给工具 shell 的继承词对它不作用";
66
66
  readonly remedy: null;
67
67
  };
68
68
  readonly "server.project-memory.git-subprocess": {