@sema-agent/server 7.137.0 → 7.139.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 (328) hide show
  1. package/MIGRATION.md +600 -142
  2. package/README.md +7 -3
  3. package/README.zh-CN.md +6 -3
  4. package/USAGE.md +132 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/wire.d.ts +3 -3
  8. package/dist/approval-card.d.ts +23 -16
  9. package/dist/approval-card.js +1 -0
  10. package/dist/audit.js +1 -0
  11. package/dist/background-agent-name.d.ts +5 -0
  12. package/dist/background-agent-name.js +5 -0
  13. package/dist/bench/s1/arms.d.ts +1 -1
  14. package/dist/bench/s1/arms.js +1 -3
  15. package/dist/bench/s1/run-firm.js +2 -2
  16. package/dist/boot/budget-tracing.d.ts +1 -12
  17. package/dist/boot/deployment-mods.d.ts +46 -16
  18. package/dist/boot/deployment-mods.js +82 -6
  19. package/dist/boot/device-lane.d.ts +8 -3
  20. package/dist/boot/device-lane.js +7 -2
  21. package/dist/boot/execution-env.d.ts +4 -2
  22. package/dist/boot/execution-env.js +16 -24
  23. package/dist/boot/execution-lane.d.ts +1 -1
  24. package/dist/boot/governance-seams.d.ts +12 -18
  25. package/dist/boot/governance-seams.js +10 -24
  26. package/dist/boot/leader.d.ts +3 -0
  27. package/dist/boot/leader.js +1 -0
  28. package/dist/boot/memory-consolidation.js +2 -2
  29. package/dist/boot/org-delivery.js +15 -2
  30. package/dist/boot/resolve-spec.d.ts +4 -1
  31. package/dist/boot/resolve-spec.js +29 -19
  32. package/dist/boot/retention-lane.d.ts +14 -8
  33. package/dist/boot/retention-lane.js +2 -8
  34. package/dist/boot/runner-deps.d.ts +10 -4
  35. package/dist/boot/runner-deps.js +2 -0
  36. package/dist/boot/session-faces.d.ts +0 -3
  37. package/dist/boot/session-faces.js +15 -8
  38. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -12
  40. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  41. package/dist/boot/stage-05-execution-env.js +8 -5
  42. package/dist/boot/stage-06-runners.d.ts +3 -12
  43. package/dist/boot/stage-06-runners.js +5 -3
  44. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  45. package/dist/boot/stage-07-capability-layer.js +2 -2
  46. package/dist/boot/stage-08-reapers.d.ts +3 -12
  47. package/dist/boot/stage-09-leader.d.ts +3 -12
  48. package/dist/boot/stage-09-leader.js +2 -3
  49. package/dist/boot/stage-10-http-server.d.ts +3 -12
  50. package/dist/boot/stage-10-http-server.js +5 -3
  51. package/dist/boot/stores.js +4 -4
  52. package/dist/boot/web-admin.d.ts +5 -2
  53. package/dist/boot/web-admin.js +5 -5
  54. package/dist/brain.js +5 -5
  55. package/dist/budget.d.ts +17 -15
  56. package/dist/budget.js +2 -2
  57. package/dist/capabilities/mcp-probe.d.ts +36 -11
  58. package/dist/capabilities/mcp-probe.js +44 -5
  59. package/dist/capabilities/scenarios.d.ts +1 -1
  60. package/dist/capability-facts.d.ts +21 -7
  61. package/dist/capability-facts.js +13 -3
  62. package/dist/checkpoint-identity.d.ts +13 -6
  63. package/dist/config-catalog.js +14 -8
  64. package/dist/config-invariants.d.ts +23 -3
  65. package/dist/config-invariants.js +42 -0
  66. package/dist/config-provider.d.ts +19 -24
  67. package/dist/config-provider.js +27 -12
  68. package/dist/config-types.d.ts +25 -12
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +52 -20
  71. package/dist/delegated-session-owner.d.ts +2 -2
  72. package/dist/deployment-shape.d.ts +7 -7
  73. package/dist/device-enrollment.d.ts +41 -2
  74. package/dist/device-enrollment.js +27 -12
  75. package/dist/device-store.d.ts +58 -85
  76. package/dist/device-store.js +19 -49
  77. package/dist/device-ws-hub.js +2 -1
  78. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  79. package/dist/exec-cut.d.ts +60 -0
  80. package/dist/exec-cut.js +36 -0
  81. package/dist/execution-lane-caps.d.ts +19 -0
  82. package/dist/execution-lane-caps.js +10 -0
  83. package/dist/execution-placement.d.ts +259 -0
  84. package/dist/execution-placement.js +199 -0
  85. package/dist/field-conflict.d.ts +3 -2
  86. package/dist/field-conflict.js +1 -0
  87. package/dist/gate-settlement.d.ts +71 -0
  88. package/dist/gate-settlement.js +40 -0
  89. package/dist/hooks/classic-stations.d.ts +1 -0
  90. package/dist/hooks/classic-stations.js +1 -0
  91. package/dist/hooks/command-spawn.d.ts +2 -2
  92. package/dist/hooks/hook-runner.js +14 -14
  93. package/dist/hosted-posture.d.ts +2 -2
  94. package/dist/http/admission.js +10 -19
  95. package/dist/http/checkpoint-ctx.d.ts +21 -12
  96. package/dist/http/checkpoint-ctx.js +4 -2
  97. package/dist/http/claimed-run.d.ts +1 -1
  98. package/dist/http/dispatch.d.ts +4 -0
  99. package/dist/http/dispatch.js +3 -0
  100. package/dist/http/durable-run-leg.js +12 -6
  101. package/dist/http/execution-context.d.ts +218 -0
  102. package/dist/http/execution-context.js +330 -0
  103. package/dist/http/memory-compliance-failure-reply.js +3 -0
  104. package/dist/http/resume-legs.d.ts +5 -11
  105. package/dist/http/resume-legs.js +179 -104
  106. package/dist/http/route-ctx.d.ts +44 -59
  107. package/dist/http/route-ctx.js +2 -5
  108. package/dist/http/routes/a2a-serve.js +10 -4
  109. package/dist/http/routes/agents-roster.d.ts +1 -1
  110. package/dist/http/routes/agents-roster.js +5 -1
  111. package/dist/http/routes/approvals-assistant.js +6 -6
  112. package/dist/http/routes/background.d.ts +29 -1
  113. package/dist/http/routes/background.js +29 -2
  114. package/dist/http/routes/capabilities.js +12 -10
  115. package/dist/http/routes/devices.d.ts +13 -19
  116. package/dist/http/routes/devices.js +21 -26
  117. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  118. package/dist/http/routes/mcp-reconnect.js +7 -5
  119. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  120. package/dist/http/routes/memory-origin.d.ts +1 -1
  121. package/dist/http/routes/rules.js +3 -3
  122. package/dist/http/routes/runs.js +11 -7
  123. package/dist/http/routes/session-sync.js +110 -15
  124. package/dist/http/routes/sessions.d.ts +6 -0
  125. package/dist/http/routes/sessions.js +119 -13
  126. package/dist/http/routes/side-query.d.ts +12 -4
  127. package/dist/http/routes/side-query.js +6 -8
  128. package/dist/http/routes/tasks.js +800 -782
  129. package/dist/http/server.d.ts +61 -39
  130. package/dist/http/server.js +11 -0
  131. package/dist/http/session-inherit-env.d.ts +3 -4
  132. package/dist/http/session-inherit-env.js +1 -1
  133. package/dist/http/wire-types.d.ts +22 -18
  134. package/dist/identity-source.d.ts +1 -1
  135. package/dist/index.d.ts +3 -0
  136. package/dist/index.js +1 -0
  137. package/dist/leader/wire.d.ts +7 -0
  138. package/dist/leader/wire.js +5 -4
  139. package/dist/mcp-assertion-header.d.ts +51 -40
  140. package/dist/mcp-assertion-header.js +10 -11
  141. package/dist/mcp-authorization-face.d.ts +27 -0
  142. package/dist/mcp-authorization-face.js +15 -0
  143. package/dist/memory-operator-faces.d.ts +13 -4
  144. package/dist/memory-operator-faces.js +1 -17
  145. package/dist/memory-sync-client.d.ts +53 -14
  146. package/dist/memory-sync-client.js +187 -92
  147. package/dist/memory-sync.d.ts +1 -1
  148. package/dist/memory-sync.js +3 -2
  149. package/dist/model-route-gap.d.ts +6 -22
  150. package/dist/model-route-gap.js +7 -5
  151. package/dist/mods-declaration.d.ts +110 -0
  152. package/dist/mods-declaration.js +109 -0
  153. package/dist/observability/cost-taxonomy.d.ts +2 -2
  154. package/dist/observability/err-text.d.ts +5 -0
  155. package/dist/observability/err-text.js +9 -0
  156. package/dist/observability/fail-open.d.ts +17 -1
  157. package/dist/observability/fail-open.js +17 -1
  158. package/dist/observability/run-terminal.js +0 -1
  159. package/dist/observability/secret-env-scrub.d.ts +1 -1
  160. package/dist/observability/secret-env-scrub.js +1 -1
  161. package/dist/orchestration/workflow-notify-journal.js +4 -4
  162. package/dist/org-memory-admission.d.ts +4 -3
  163. package/dist/org-memory-admission.js +6 -6
  164. package/dist/parked-decide.d.ts +49 -11
  165. package/dist/parked-decide.js +25 -26
  166. package/dist/parked-revive-surface.d.ts +12 -15
  167. package/dist/parked-revive-surface.js +3 -6
  168. package/dist/peer-seat.d.ts +156 -0
  169. package/dist/peer-seat.js +273 -0
  170. package/dist/permission-rule-vocab.d.ts +7 -7
  171. package/dist/permission-rule-vocab.js +3 -3
  172. package/dist/plain-string-record.d.ts +38 -0
  173. package/dist/plain-string-record.js +38 -0
  174. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  175. package/dist/plugins/approval-ask-store-file.js +11 -0
  176. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  177. package/dist/plugins/approval-ask-store-memory.js +12 -0
  178. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  179. package/dist/plugins/approval-ask-store-sql.js +30 -12
  180. package/dist/plugins/background-agent-store-sql.js +6 -3
  181. package/dist/plugins/caching-session-store.d.ts +11 -2
  182. package/dist/plugins/caching-session-store.js +15 -3
  183. package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
  184. package/dist/plugins/checkpoint-store-sql.js +13 -15
  185. package/dist/plugins/device-store-sql.d.ts +25 -51
  186. package/dist/plugins/device-store-sql.js +49 -103
  187. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  188. package/dist/plugins/execution-placement-sql.js +223 -0
  189. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  190. package/dist/plugins/file-history-store-sql.js +8 -7
  191. package/dist/plugins/file-run-store.d.ts +6 -1
  192. package/dist/plugins/file-run-store.js +4 -0
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  194. package/dist/plugins/fork-routing-session-store.js +16 -0
  195. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  196. package/dist/plugins/k8s-pod-reaper.js +3 -3
  197. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  198. package/dist/plugins/local-checkpoint-store.js +0 -1
  199. package/dist/plugins/local-session-store.d.ts +183 -45
  200. package/dist/plugins/local-session-store.js +412 -105
  201. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  202. package/dist/plugins/mailbox-store-sql.js +103 -15
  203. package/dist/plugins/memory-key-guards.d.ts +4 -0
  204. package/dist/plugins/memory-key-guards.js +1 -0
  205. package/dist/plugins/memory-run-store.d.ts +1 -1
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +117 -6
  209. package/dist/plugins/one-time-migrations.js +383 -14
  210. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  212. package/dist/plugins/permission-rule-store-sql.js +34 -29
  213. package/dist/plugins/pg-pool.d.ts +7 -3
  214. package/dist/plugins/pg-session-storage.d.ts +41 -22
  215. package/dist/plugins/pg-session-storage.js +136 -55
  216. package/dist/plugins/remote-env-adb.d.ts +22 -4
  217. package/dist/plugins/remote-env-adb.js +70 -62
  218. package/dist/plugins/remote-env-device.d.ts +32 -12
  219. package/dist/plugins/remote-env-device.js +18 -17
  220. package/dist/plugins/remote-env-e2b.d.ts +19 -3
  221. package/dist/plugins/remote-env-e2b.js +41 -42
  222. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  223. package/dist/plugins/remote-env-file-error.js +1 -1
  224. package/dist/plugins/remote-env-host.d.ts +23 -8
  225. package/dist/plugins/remote-env-host.js +66 -70
  226. package/dist/plugins/remote-env-k8s.d.ts +13 -16
  227. package/dist/plugins/remote-env-k8s.js +61 -54
  228. package/dist/plugins/remote-env-local-docker.d.ts +30 -5
  229. package/dist/plugins/remote-env-local-docker.js +46 -59
  230. package/dist/plugins/remote-env-ssh.d.ts +18 -4
  231. package/dist/plugins/remote-env-ssh.js +72 -56
  232. package/dist/plugins/remote-shell.d.ts +100 -3
  233. package/dist/plugins/remote-shell.js +166 -2
  234. package/dist/plugins/retention-store-sql.d.ts +23 -8
  235. package/dist/plugins/retention-store-sql.js +13 -1
  236. package/dist/plugins/run-store-sql.d.ts +13 -3
  237. package/dist/plugins/run-store-sql.js +5 -0
  238. package/dist/plugins/schema-consistency.d.ts +15 -5
  239. package/dist/plugins/schema-consistency.js +14 -8
  240. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  241. package/dist/plugins/schema-drift-sentence.js +38 -0
  242. package/dist/plugins/session-fork.d.ts +63 -12
  243. package/dist/plugins/session-fork.js +38 -21
  244. package/dist/plugins/session-ownership.d.ts +7 -0
  245. package/dist/plugins/session-ownership.js +5 -0
  246. package/dist/plugins/session-row-sql.d.ts +22 -0
  247. package/dist/plugins/session-row-sql.js +81 -0
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  250. package/dist/plugins/session-workspace-cell.js +38 -0
  251. package/dist/plugins/store-backend.d.ts +1 -1
  252. package/dist/plugins/store-backend.js +26 -14
  253. package/dist/plugins/store-contracts.d.ts +25 -5
  254. package/dist/plugins/tidb-pool.d.ts +12 -7
  255. package/dist/plugins/tidb-pool.js +15 -3
  256. package/dist/plugins/tidb-session-store.d.ts +74 -28
  257. package/dist/plugins/tidb-session-store.js +132 -53
  258. package/dist/plugins/tool-result-store-sql.d.ts +16 -8
  259. package/dist/plugins/tool-result-store-sql.js +26 -17
  260. package/dist/plugins/worktree-isolation.d.ts +3 -2
  261. package/dist/plugins/worktree-isolation.js +1 -7
  262. package/dist/project-identity.d.ts +1 -14
  263. package/dist/project-identity.js +0 -4
  264. package/dist/project-memory.d.ts +9 -15
  265. package/dist/project-memory.js +5 -4
  266. package/dist/request-key-closure.js +1 -1
  267. package/dist/request-value-closure.d.ts +6 -7
  268. package/dist/request-value-closure.js +1 -3
  269. package/dist/rules-consent.js +6 -4
  270. package/dist/run-local.d.ts +6 -0
  271. package/dist/run-local.js +36 -26
  272. package/dist/runs.d.ts +9 -2
  273. package/dist/runs.js +9 -2
  274. package/dist/sandbox-git-plane.d.ts +2 -2
  275. package/dist/security.d.ts +38 -12
  276. package/dist/security.js +8 -2
  277. package/dist/server-secret-env.d.ts +15 -8
  278. package/dist/server-secret-env.js +4 -2
  279. package/dist/session-children-retire.d.ts +27 -9
  280. package/dist/session-children-retire.js +9 -5
  281. package/dist/session-incarnation.d.ts +19 -10
  282. package/dist/session-incarnation.js +5 -3
  283. package/dist/session-row.d.ts +114 -5
  284. package/dist/session-row.js +55 -5
  285. package/dist/session-titler.d.ts +2 -2
  286. package/dist/session-workspace.d.ts +130 -0
  287. package/dist/session-workspace.js +83 -0
  288. package/dist/spec-fields.d.ts +1 -1
  289. package/dist/standing-answer-grant.d.ts +2 -13
  290. package/dist/standing-answer-grant.js +0 -7
  291. package/dist/task-a2a.d.ts +44 -14
  292. package/dist/task-a2a.js +46 -20
  293. package/dist/task-cwd.d.ts +6 -1
  294. package/dist/task-mcp.d.ts +33 -20
  295. package/dist/task-mcp.js +46 -22
  296. package/dist/tool-approval.d.ts +38 -8
  297. package/dist/tool-approval.js +57 -13
  298. package/dist/trace/core-keyset-guard.d.ts +33 -18
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +2 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +3 -0
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/project.d.ts +26 -7
  306. package/dist/trace/project.js +30 -3
  307. package/dist/trace/redact.js +42 -13
  308. package/dist/trace/self-minted-wire.d.ts +240 -12
  309. package/dist/trace/self-minted-wire.js +100 -4
  310. package/dist/trace/wire-projection-faces.d.ts +6 -6
  311. package/dist/trace/wire-projection-faces.js +6 -5
  312. package/dist/url-refusal.d.ts +1 -1
  313. package/dist/user-authored-text.d.ts +2 -2
  314. package/dist/user-authored-text.js +16 -1
  315. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  316. package/dist/web-admin-client/apply-effective.js +20 -10
  317. package/dist/web-admin-client/center-request.d.ts +3 -2
  318. package/dist/web-admin-client/center-request.js +2 -3
  319. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  320. package/dist/web-admin-client/skills-mcp.js +32 -18
  321. package/dist/web-admin-client/types.d.ts +11 -0
  322. package/package.json +3 -3
  323. package/wire-changes/server@7.138.0.json +446 -0
  324. package/wire-changes/server@7.139.0.json +348 -0
  325. package/dist/http/device-placement.d.ts +0 -175
  326. package/dist/http/device-placement.js +0 -145
  327. package/dist/http/mcp-remote-text.d.ts +0 -5
  328. package/dist/http/mcp-remote-text.js +0 -7
@@ -1,99 +1,97 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, readdir, rename, rmdir, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
- import { encodeScopeSegment, syncMemoryScope, } from "@sema-agent/core";
4
+ import { carriesCredential, syncMemoryScope, } from "@sema-agent/core";
5
5
  import { replicationCovers } from "./memory-scope.js";
6
6
  import { parseProjectLayerKey } from "./project-layer-key.js";
7
- export const DEFAULT_MEMORY_SYNC_PEER = "central";
7
+ import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
8
+ import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./plugins/one-time-migrations.js";
9
+ import { pathBaseUrlRefusal } from "./web-admin-client/center-request.js";
10
+ import { buildDisplaySafeUrl } from "./trace/redact.js";
11
+ export function memorySyncPeerOf(url) {
12
+ const refusal = pathBaseUrlRefusal("memory sync: MEMORY_SYNC_URL", url);
13
+ if (refusal !== undefined)
14
+ throw new Error(refusal);
15
+ const u = new URL(url);
16
+ const identity = `${u.origin}${u.pathname.replace(/\/+$/, "")}`;
17
+ if (carriesCredential(identity)) {
18
+ throw new Error("memory sync: MEMORY_SYNC_URL holds a credential (a token-shaped part of its path) — the sync peer identity travels verbatim in every request body and " +
19
+ "cursor, so it is refused, never cleaned: put the token in MEMORY_SYNC_TOKEN and set MEMORY_SYNC_URL to the central base address itself");
20
+ }
21
+ if (identity.length > MEMORY_SYNC_PEER_COLUMN_CHARS) {
22
+ throw new Error(`memory sync: MEMORY_SYNC_URL normalizes to ${identity.length} characters — the peer identity is the central request's peer, which holds at most ${MEMORY_SYNC_PEER_COLUMN_CHARS}: use a shorter base URL`);
23
+ }
24
+ return identity;
25
+ }
26
+ export function buildMemorySyncEnabledFields(peer, scope, cursorPath) {
27
+ return { peer: buildDisplaySafeUrl(peer), scope, cursorPath };
28
+ }
8
29
  export function createMemorySyncTransport(opts) {
9
- const base = opts.url.replace(/\/+$/, "");
30
+ const peer = memorySyncPeerOf(opts.url);
10
31
  const fetchImpl = opts.fetchImpl ?? fetch;
11
32
  const timeoutMs = opts.timeoutMs ?? 60_000;
12
- return async (path, body) => {
13
- const res = await fetchImpl(`${base}${path}`, {
14
- method: "POST",
15
- headers: { authorization: `Bearer ${opts.token}`, "content-type": "application/json" },
16
- body: JSON.stringify(body),
17
- signal: AbortSignal.timeout(timeoutMs),
18
- });
19
- const text = await res.text();
20
- if (!res.ok)
21
- throw new Error(`memory sync POST ${path}: HTTP ${res.status} ${text.slice(0, 200)}`);
22
- return JSON.parse(text);
23
- };
33
+ return Object.freeze({
34
+ peer,
35
+ async post(path, body) {
36
+ const res = await fetchImpl(`${peer}${path}`, {
37
+ method: "POST",
38
+ headers: { authorization: `Bearer ${opts.token}`, "content-type": "application/json" },
39
+ body: JSON.stringify(body),
40
+ signal: AbortSignal.timeout(timeoutMs),
41
+ });
42
+ const text = await res.text();
43
+ if (!res.ok)
44
+ throw new Error(`memory sync POST ${path}: HTTP ${res.status} ${text.slice(0, 200)}`);
45
+ return JSON.parse(text);
46
+ },
47
+ });
24
48
  }
25
- export function memorySyncCursorPath(memoryRoot, peer, scope) {
26
- return join(memoryRoot, "sync-cursors", encodeScopeSegment(peer), `${createHash("sha256").update(scope, "utf8").digest("hex").slice(0, 32)}.json`);
49
+ const SYNC_CURSOR_DIR = "sync-cursors";
50
+ function scopeDigest(scope) {
51
+ return createHash("sha256").update(scope, "utf8").digest("hex").slice(0, 32);
27
52
  }
28
- function legacyMemorySyncCursorPath(memoryRoot, peer) {
29
- return join(memoryRoot, `sync-cursor-${encodeScopeSegment(peer)}.json`);
53
+ export function memorySyncCursorPath(memoryRoot, scope) {
54
+ return join(memoryRoot, SYNC_CURSOR_DIR, `${scopeDigest(scope)}.json`);
30
55
  }
31
- async function loadCursor(path, scope, peer, log) {
56
+ function unusableCursor(scope, path, why) {
57
+ return new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — the cursor file ${path} cannot be read as a sync cursor (${why}); the layer stays unsynced until the operator picks one of two ways out: ` +
58
+ "(1) restore the file from a backup; (2) delete it to accept re-convergence from an empty baseline — entries changed on both sides are reported as conflicts, " +
59
+ "and entries deleted on one side since the last round come back from the other side");
60
+ }
61
+ function cursorOf(parsed) {
62
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
63
+ return "not a JSON object";
64
+ const c = parsed;
65
+ const baseRevs = c["baseRevs"];
66
+ if (baseRevs === null || typeof baseRevs !== "object" || Array.isArray(baseRevs) || !Object.values(baseRevs).every((v) => typeof v === "string" && v.length > 0)) {
67
+ return "baseRevs is not a map of entry id to a non-empty rev";
68
+ }
69
+ const updatedAtMs = c["updatedAtMs"];
70
+ if (typeof updatedAtMs !== "number" || !Number.isFinite(updatedAtMs))
71
+ return "updatedAtMs is not a finite number";
72
+ return { scope: c["scope"], peer: c["peer"], store: c["store"], baseRevs: { ...baseRevs }, updatedAtMs };
73
+ }
74
+ async function loadLayerBaseline(path, scope) {
32
75
  let raw;
33
76
  try {
34
77
  raw = await readFile(path, "utf8");
35
78
  }
36
79
  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;
80
+ if (err.code === "ENOENT")
81
+ return { kind: "none" };
82
+ throw new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — cannot read the cursor file ${path} (${String(err)}); the next round reads it again once it is readable`);
40
83
  }
41
84
  let parsed;
42
85
  try {
43
86
  parsed = JSON.parse(raw);
44
87
  }
45
- catch (err) {
46
- log.warn("memory_sync_cursor_corrupt", { path, err: String(err), note: "treating as first round" });
47
- return undefined;
88
+ catch {
89
+ throw unusableCursor(scope, path, "not JSON");
48
90
  }
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
- }
55
- function cursorOf(parsed, scope, peer) {
56
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
57
- const c = parsed;
58
- const baseRevsRaw = c["baseRevs"];
59
- if (c["scope"] === scope &&
60
- c["peer"] === peer &&
61
- typeof c["updatedAtMs"] === "number" &&
62
- Number.isFinite(c["updatedAtMs"]) &&
63
- baseRevsRaw !== null &&
64
- typeof baseRevsRaw === "object" &&
65
- !Array.isArray(baseRevsRaw) &&
66
- Object.values(baseRevsRaw).every((v) => typeof v === "string" && v.length > 0)) {
67
- return { scope, peer, baseRevs: { ...baseRevsRaw }, updatedAtMs: c["updatedAtMs"] };
68
- }
69
- }
70
- return undefined;
71
- }
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 });
91
+ const cursor = cursorOf(parsed);
92
+ if (typeof cursor === "string")
93
+ throw unusableCursor(scope, path, cursor);
94
+ return { kind: "cursor", cursor };
97
95
  }
98
96
  async function saveCursor(path, cursor) {
99
97
  await mkdir(dirname(path), { recursive: true });
@@ -101,30 +99,129 @@ async function saveCursor(path, cursor) {
101
99
  await writeFile(tmp, JSON.stringify(cursor), "utf8");
102
100
  await rename(tmp, path);
103
101
  }
102
+ const LEGACY_PEER_SEGMENT = "central";
103
+ const LEGACY_ROOT_CURSOR_FILE = "sync-cursor-central.json";
104
+ function m31Window() {
105
+ const row = ONE_TIME_MIGRATIONS.find((w) => w.id === "M31");
106
+ if (row === undefined)
107
+ throw new Error("memory-sync-client: the one-time migration registry has no M31 row — this migration outlived its registry row; delete it with that row");
108
+ return row;
109
+ }
110
+ function scopeOfLegacyCursor(raw) {
111
+ let parsed;
112
+ try {
113
+ parsed = JSON.parse(raw);
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
119
+ return undefined;
120
+ const scope = parsed["scope"];
121
+ return typeof scope === "string" && scope.length > 0 ? scope : undefined;
122
+ }
123
+ export async function relocateSyncCursorsM31(memoryRoot, log, opts) {
124
+ if (!migrationWindowOpen(opts.window ?? m31Window(), opts.today))
125
+ return { window: "closed", moved: 0, left: 0 };
126
+ let moved = 0;
127
+ let left = 0;
128
+ const codeOf = (err) => err?.code;
129
+ const exists = async (path) => {
130
+ try {
131
+ await stat(path);
132
+ return true;
133
+ }
134
+ catch (err) {
135
+ if (codeOf(err) === "ENOENT")
136
+ return false;
137
+ throw err;
138
+ }
139
+ };
140
+ const move = async (from, to) => {
141
+ if (await exists(to)) {
142
+ left += 1;
143
+ log.warn("memory_sync_cursor_not_relocated", { path: from, to, note: "this layer already has a cursor at its path, so the file written by an earlier version stays where it is and no round reads it; delete it" });
144
+ return;
145
+ }
146
+ await mkdir(dirname(to), { recursive: true });
147
+ try {
148
+ await rename(from, to);
149
+ }
150
+ catch (err) {
151
+ if (codeOf(err) === "ENOENT")
152
+ return;
153
+ throw err;
154
+ }
155
+ moved += 1;
156
+ log.info("memory_sync_cursor_relocated", { from, to });
157
+ };
158
+ const legacyDir = join(memoryRoot, SYNC_CURSOR_DIR, LEGACY_PEER_SEGMENT);
159
+ let names = [];
160
+ try {
161
+ names = await readdir(legacyDir);
162
+ }
163
+ catch (err) {
164
+ if (codeOf(err) !== "ENOENT")
165
+ throw err;
166
+ }
167
+ for (const name of [...names].sort()) {
168
+ if (/^[0-9a-f]{32}\.json$/.test(name))
169
+ await move(join(legacyDir, name), join(memoryRoot, SYNC_CURSOR_DIR, name));
170
+ }
171
+ if (names.length > 0) {
172
+ try {
173
+ await rmdir(legacyDir);
174
+ }
175
+ catch (err) {
176
+ if (codeOf(err) !== "ENOENT" && codeOf(err) !== "ENOTEMPTY" && codeOf(err) !== "EEXIST")
177
+ throw err;
178
+ }
179
+ }
180
+ const legacyFile = join(memoryRoot, LEGACY_ROOT_CURSOR_FILE);
181
+ let raw;
182
+ try {
183
+ raw = await readFile(legacyFile, "utf8");
184
+ }
185
+ catch (err) {
186
+ if (codeOf(err) !== "ENOENT")
187
+ throw err;
188
+ }
189
+ if (raw !== undefined) {
190
+ const scope = scopeOfLegacyCursor(raw);
191
+ if (scope === undefined) {
192
+ left += 1;
193
+ log.warn("memory_sync_cursor_not_relocated", { path: legacyFile, note: "a cursor file written by an earlier version that is not a JSON object naming its scope, so no layer can take it as its baseline; restore it from a backup and restart, or delete it" });
194
+ }
195
+ else {
196
+ await move(legacyFile, memorySyncCursorPath(memoryRoot, scope));
197
+ }
198
+ }
199
+ return { window: "open", moved, left };
200
+ }
104
201
  export const MAX_SYNC_ROUNDS = 32;
105
202
  function enumerateScopes(backend) {
106
203
  const face = backend.listScopes;
107
204
  return typeof face === "function" ? face.call(backend) : { supported: false, reason: "the memory backend has no listScopes face" };
108
205
  }
109
206
  export function createMemorySyncRunner(opts) {
110
- const peer = opts.peer ?? DEFAULT_MEMORY_SYNC_PEER;
207
+ const now = opts.now ?? Date.now;
111
208
  const coverage = { kind: "layers", layers: [opts.scope] };
112
209
  const tracked = new Set();
113
- let legacyCursorChecked = false;
114
210
  let enumerationWarned = false;
115
211
  const isCoveredProjectLayer = (scope) => scope !== opts.scope && parseProjectLayerKey(scope) !== undefined && replicationCovers(coverage, scope);
116
212
  const layersOfRound = () => {
117
213
  const local = enumerateScopes(opts.backend);
118
214
  if (!local.supported && !enumerationWarned) {
119
215
  enumerationWarned = true;
120
- opts.log.warn("memory_sync_layer_enumeration_unsupported", { scope: opts.scope, peer, reason: local.reason, note: "only the sync layer and tracked project layers are synced" });
216
+ opts.log.warn("memory_sync_layer_enumeration_unsupported", { scope: opts.scope, reason: local.reason, note: "only the sync layer and tracked project layers are synced" });
121
217
  }
122
218
  const projectLayers = new Set([...(local.supported ? local.scopes : []), ...tracked].filter(isCoveredProjectLayer));
123
219
  return [opts.scope, ...[...projectLayers].sort()];
124
220
  };
125
221
  const syncLayer = async (scope, reason) => {
126
- const cursorPath = memorySyncCursorPath(opts.memoryRoot, peer, scope);
127
- let cursor = await loadCursor(cursorPath, scope, peer, opts.log);
222
+ const cursorPath = memorySyncCursorPath(opts.memoryRoot, scope);
223
+ const baseline = await loadLayerBaseline(cursorPath, scope);
224
+ let cursor = baseline.kind === "cursor" ? baseline.cursor : undefined;
128
225
  let result;
129
226
  let subRound = 0;
130
227
  for (;;) {
@@ -133,18 +230,17 @@ export function createMemorySyncRunner(opts) {
133
230
  backend: opts.backend,
134
231
  scope,
135
232
  transport: opts.transport,
136
- peer,
137
233
  ...(cursor !== undefined ? { cursor } : {}),
138
234
  ...(opts.now !== undefined ? { now: opts.now } : {}),
139
235
  ...(opts.maxPushEntries !== undefined ? { maxPushEntries: opts.maxPushEntries } : {}),
140
236
  ...(opts.maxPullEntries !== undefined ? { maxPullEntries: opts.maxPullEntries } : {}),
141
237
  });
142
238
  for (const w of result.warnings ?? [])
143
- opts.log.warn("memory_sync_warning", { scope, peer, subRound, warning: w });
239
+ opts.log.warn("memory_sync_warning", { scope, subRound, warning: w });
144
240
  if (!(result.pushTruncated === true || result.pullTruncated === true))
145
241
  break;
146
242
  if (subRound >= MAX_SYNC_ROUNDS) {
147
- opts.log.warn("memory_sync_rounds_exhausted", { scope, peer, rounds: subRound, note: "truncation flag still set at the round cap — remaining items ride the next trigger" });
243
+ opts.log.warn("memory_sync_rounds_exhausted", { scope, rounds: subRound, note: "truncation flag still set at the round cap — remaining items ride the next trigger" });
148
244
  break;
149
245
  }
150
246
  if (result.cursorAdvanced && result.cursor !== undefined) {
@@ -156,12 +252,11 @@ export function createMemorySyncRunner(opts) {
156
252
  }
157
253
  }
158
254
  cursor = result.cursor ?? cursor;
159
- opts.log.info("memory_sync_continuation", { reason, scope, peer, subRound, pushTruncated: result.pushTruncated === true, pullTruncated: result.pullTruncated === true });
255
+ opts.log.info("memory_sync_continuation", { reason, scope, subRound, pushTruncated: result.pushTruncated === true, pullTruncated: result.pullTruncated === true });
160
256
  }
161
257
  for (const c of result.conflicts) {
162
258
  opts.log.warn("memory_sync_conflict", {
163
259
  scope,
164
- peer,
165
260
  side: c.side,
166
261
  id: c.id,
167
262
  reason: c.reason,
@@ -180,7 +275,6 @@ export function createMemorySyncRunner(opts) {
180
275
  opts.log.info("memory_sync_round", {
181
276
  reason,
182
277
  scope,
183
- peer,
184
278
  ok: result.ok,
185
279
  cursorAdvanced: result.cursorAdvanced,
186
280
  pushedEntries: result.pushed.entries.length,
@@ -191,10 +285,11 @@ export function createMemorySyncRunner(opts) {
191
285
  });
192
286
  return result;
193
287
  };
288
+ let relocated = false;
194
289
  const round = async (reason) => {
195
- if (!legacyCursorChecked) {
196
- legacyCursorChecked = true;
197
- await adoptLegacyCursor(opts.memoryRoot, opts.scope, peer, opts.log);
290
+ if (!relocated) {
291
+ await relocateSyncCursorsM31(opts.memoryRoot, opts.log, { today: new Date(now()).toISOString().slice(0, 10) });
292
+ relocated = true;
198
293
  }
199
294
  const layers = [];
200
295
  const failures = [];
@@ -204,7 +299,7 @@ export function createMemorySyncRunner(opts) {
204
299
  }
205
300
  catch (err) {
206
301
  failures.push({ scope, err });
207
- opts.log.warn("memory_sync_layer_failed", { reason, scope, peer, err: String(err) });
302
+ opts.log.warn("memory_sync_layer_failed", { reason, scope, cursorPath: memorySyncCursorPath(opts.memoryRoot, scope), err: String(err) });
208
303
  }
209
304
  }
210
305
  if (failures.length > 0) {
@@ -227,7 +322,7 @@ export function createMemorySyncRunner(opts) {
227
322
  const syncOnce = (reason) => exclusive(() => round(reason));
228
323
  const queueRound = (reason) => {
229
324
  void syncOnce(reason).catch((err) => {
230
- opts.log.warn("memory_sync_failed", { reason, scope: opts.scope, peer, err: String(err) });
325
+ opts.log.warn("memory_sync_failed", { reason, scope: opts.scope, err: String(err) });
231
326
  });
232
327
  };
233
328
  const trigger = (reason) => {
@@ -244,6 +339,6 @@ export function createMemorySyncRunner(opts) {
244
339
  tracked.add(scope);
245
340
  queueRound("track");
246
341
  };
247
- return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(opts.memoryRoot, peer, scope), pending: () => pending };
342
+ return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(opts.memoryRoot, scope), pending: () => pending };
248
343
  }
249
344
  //# sourceMappingURL=memory-sync-client.js.map
@@ -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) 游标分区键)= 客户端 transport 的身份(本仓的客户端腿:规范化的中心基址,`memory-sync-client.ts` `memorySyncPeerOf`)。 */
6
6
  peer: string;
7
7
  /** 客户端游标的共同基线:id → rev at last completed sync round(空 map = 首轮)。 */
8
8
  baseRevs: Record<string, string>;
@@ -1,5 +1,6 @@
1
1
  import { computeEntryRev, MEMORY_ORIGIN_CAUSES, nextSyncBaseline, reconcileMemoryEntries, screenInboundEntries, } from "@sema-agent/core";
2
2
  import { exportMemoryScope } from "./memory-export.js";
3
+ import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
3
4
  export const MEMORY_SYNC_MAX_ENTRIES = 500;
4
5
  export const MEMORY_SYNC_MAX_DELETES = 2000;
5
6
  export function parseMemorySyncRequest(body, scope) {
@@ -7,8 +8,8 @@ export function parseMemorySyncRequest(body, scope) {
7
8
  return { ok: false, error: "body must be a JSON object { peer, baseRevs, entries, deletes }" };
8
9
  const b = body;
9
10
  const peer = b["peer"];
10
- if (typeof peer !== "string" || peer.length === 0 || peer.length > 190)
11
- return { ok: false, error: "peer must be a non-empty string of at most 190 characters" };
11
+ if (typeof peer !== "string" || peer.length === 0 || peer.length > MEMORY_SYNC_PEER_COLUMN_CHARS)
12
+ return { ok: false, error: `peer must be a non-empty string of at most ${MEMORY_SYNC_PEER_COLUMN_CHARS} characters` };
12
13
  const rawBase = b["baseRevs"];
13
14
  if (rawBase === null || typeof rawBase !== "object" || Array.isArray(rawBase))
14
15
  return { ok: false, error: "baseRevs must be an object mapping entry id → rev (may be empty on the first round)" };
@@ -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