@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,66 +1,11 @@
1
- /**
2
- * #264 v2-c —— `POST /v1/memory/{export,import}` 两口的**引擎接线**(core 5.40.0 design/178 v2-c)。
3
- *
4
- * ## 为什么需要这个模块(而不是在 main.ts 里直接 new)
5
- *
6
- * 本仓**没有**一个长命的 `MemoryEngine` 实例:`boot/stores.ts` 装配出来的 `memoryEngine` 是
7
- * `{ backend, root }` 二元组,真正的引擎由 **core 的 Runner 每任务现构**
8
- * (`core/runner/prepare-memory.js`),而且构出来的 `memoryDir` 是**按任务的 scope 平面**决定的
9
- * (personal / project / dual-root 三形,还要看仓根、项目标记、pinned 目录)。
10
- * 也就是说「这个部署的那一个记忆引擎」这个东西**在本仓不存在** —— HTTP 面若要一个引擎,必须自己构。
11
- *
12
- * ## 为什么自己构是安全的:这两个面是 **backend-scoped**,不碰 `memoryDir`
13
- *
14
- * 亲读 core 5.40.0 的 `memory-engine/engine.js`(不信 JSDoc,读实现):
15
- * · `exportMemoryScopes` 全身只用 `this.backend.exportSnapshotOf` 与 `this.now()`;
16
- * · `importMemoryBundle` 全身只用 `this.backend.importBundleCommit`(校验段是纯函数 `memoryBundleInvalid`)。
17
- * 两条路径**都不读** `memoryDir` / `controlDir` —— 它们是**店级**的单锁复合面(一个 epoch 里把五张
18
- * 店面围起来),不是「某个任务挂载的那棵树」上的操作。因此本模块传给构造器的 `memoryDir` 只是**构造器
19
- * 的必填座**,对这两条路径无影响。
20
- *
21
- * 🔴 这条判断是承重的,所以它有**机器钉**而不是只有这段注释:`test/memory-bundle-engine.test.ts` 用一只
22
- * 假 backend 断言两条路径确实落到 `exportSnapshotOf` / `importBundleCommit`,且给一个**不存在的**
23
- * `memoryDir` 也照样工作。core 哪天让这两条路径去读挂载树,那道钉当场红 —— 而不是在某个部署上静默
24
- * 读错一棵树。
25
- *
26
- * ## 能力缺席在**构造期**判定(不挂一个注定失败的口)
27
- *
28
- * backend 没有那两个可选面(本仓两只 SQL 记忆孪生就没有)⇒ 本工厂返回 **undefined**,两口整个不挂载,
29
- * HTTP 面诚实 501。
30
- *
31
- * 🔴 首版把这件事当成**运行期**事实(照常挂载,让 core 每次抛 `memory.export_incomplete` / 422),
32
- * 被 codex 对抗复审驳倒(#277 提货批 [medium],验真后修):`typeof backend.exportSnapshotOf === "function"`
33
- * 是一次**属性检查**,boot 期就判得出;而且它是**永久**缺席(换后端才会变),不是瞬时状态。照旧挂着
34
- * 就成了「能力位说 yes、每一次调用都确定性失败」——本仓「says yes ⟺ route works」是结构性承诺,
35
- * 一个恒假的 yes 会让壳给用户开出一条走不通的迁移流程。
36
- *
37
- * ⚠️ 两面是**一个**能力,判据取合取:只有一半也不挂。半挂的形是「导得出、导不进」,而这条产品面的
38
- * 全部意义是那趟往返。
39
- *
40
- * ⚠️ 与**运行期**拒不是一回事,别合并:面齐备但店自陈说不清(chain-degraded)时,core 照样抛
41
- * `memory.export_incomplete` —— 那是诚实的运行期拒,附录 A 单列一条。绝不在这里回落到「用通用读面拼
42
- * 一个包」:core 明写那正是它拒绝生产的形(a governance-less export is the laundering shape)。
43
- */
44
1
  import { MemoryEngine } from "@sema-agent/core";
45
- /**
46
- * 在**已装配的记忆后端**上建 bundle 两面。命名照 CLAUDE.md 工厂律:返回的是带行为的活对象 ⇒ `create*`。
47
- *
48
- * `memoryDir` = `boot/stores.ts` 的 `memoryEngine.root`(该部署的记忆工作根)。见文件头:这两条路径
49
- * 不读它,传它是为了不发明第二个「引擎根」的概念 —— 万一 core 日后真用上,它拿到的也是本部署唯一的
50
- * 那个根,而不是一个本模块凭空编出来的路径。
51
- */
52
2
  export function createMemoryBundleFaces(store) {
53
- // 构造期能力判定(见文件头):两个可选面缺任一 ⇒ 整个不挂。判据直接读 backend 的面,不看后端方言名
54
- // ——「认能力不认实现名」是本仓的既有教训(按部署名判「组件已装」认不出 helm 装的那次)。
55
3
  const b = store.backend;
56
4
  if (typeof b.exportSnapshotOf !== "function" || typeof b.importBundleCommit !== "function")
57
5
  return undefined;
58
6
  const engine = new MemoryEngine({ backend: store.backend, memoryDir: store.root });
59
7
  return {
60
8
  exportScopes: (scopes) => engine.exportMemoryScopes(scopes),
61
- // 唯一一次窄化,就发生在**引擎边界上**:core 的 d.ts 要 MemoryExportBundle,而它的实装第一件事
62
- // 就是 JSON 深拷 + `memoryBundleInvalid` 整包校验 —— 也就是说这个形参在运行期本就接受任意值,
63
- // 由它自己裁。把窄化收在这一行(而不是让它一路污染 HTTP 层的类型)是「谁裁决谁承型」。
64
9
  importBundle: (bundle, opts) => engine.importMemoryBundle(bundle, opts),
65
10
  };
66
11
  }
@@ -1,12 +1,8 @@
1
- /** All entries of one scope, deterministic id order (both backend read faces ORDER BY id). */
2
1
  export async function exportMemoryScope(backend, scope) {
3
2
  const headers = await backend.listHeaders([scope]);
4
3
  if (headers.length === 0)
5
4
  return [];
6
5
  const entries = await backend.getByIds(headers.map((h) => h.id));
7
- // Defense-in-depth: getByIds is id-keyed (no scope filter). The ids came from THIS scope's own
8
- // headers, but a header-read/entry-read race (concurrent cross-scope re-add) must never leak a
9
- // foreign scope's entry into an owner-gated export — filter honestly.
10
6
  return entries.filter((e) => e.scope === scope);
11
7
  }
12
8
  //# sourceMappingURL=memory-export.js.map
@@ -1,7 +1,3 @@
1
- /**
2
- * 纯推导(`build*`:产纯数据)。成因的判序是**显式关停 > 租户隔离 > 车道分裂**:
3
- * operator 自己关的那一形必须报成 `engine_off`,报成隔离/车道会把人指向一个改了也没用的旋钮。
4
- */
5
1
  export function buildMemoryPosture(facts) {
6
2
  const { config, engineWired, vectorMode, remoteLaneDark } = facts;
7
3
  const embedder = config.memoryEmbedder;
@@ -13,8 +9,7 @@ export function buildMemoryPosture(facts) {
13
9
  ? "multi_tenant_file_backend"
14
10
  : remoteLaneDark
15
11
  ? "remote_lane_split"
16
- : // 到不了的第四形只可能是「装配拒启路径上」(DB 腿半配 fail-loud 已 throw),那时这个对象根本
17
- // 不会被建出来。仍然给一个词而不是 null:点亮态为假时 `darkReason` 必须有值,才谈得上机器可读。
12
+ :
18
13
  "engine_off";
19
14
  return {
20
15
  enabled: engineWired,
@@ -23,19 +18,10 @@ export function buildMemoryPosture(facts) {
23
18
  darkReason,
24
19
  multiTenant: config.requirePrincipal === true,
25
20
  embedder: embedder !== undefined ? { model: embedder.model, dimensions: embedder.dimensions } : null,
26
- // #299:键缺席 ⇒ `null`(「本部署没设」),不折成 core 的缺省词 —— 见字段 JSDoc 的诚实纪律。
27
21
  delegationEvidence: config.memoryDelegationEvidence ?? null,
28
- // #307 件4:同一条诚实纪律 —— 键缺席 ⇒ `null`(「本部署没设」),不折成 core 的缺省词 `carry`。
29
22
  provenance: config.memoryProvenance ?? null,
30
23
  };
31
24
  }
32
- /**
33
- * 能力面(`GET /v1/capabilities` 的 `memoryEngine` 位)的**窄投影**:点亮 ⇒ 后端形 + 档位;
34
- * 暗 ⇒ `false`(位在场且假 = 「这台认识这一位、本部署没点亮」,与位整个缺席 = 老 server 两回事)。
35
- *
36
- * 刻意**不**外泄 `darkReason` / `multiTenant` / embedder 身份:能力面是任何持凭证调用方都能读的面,
37
- * 它只需要回答「我能不能用记忆、用的是哪一档」;成因与身份是运维诊断料,归 operator-only 那一面。
38
- */
39
25
  export function projectMemoryEngineCapability(posture) {
40
26
  if (posture === undefined || !posture.enabled)
41
27
  return false;
@@ -1,124 +1,43 @@
1
- // design/158 N17: the memory-plane resolvers moved VERBATIM out of security.ts (lens4 §4.1 — "budget.ts"'s
2
- // sibling finding: security.ts's name says auth, but ~110 of its 765 lines were memory-scope config
3
- // resolution with nothing to do with authentication). security.ts imports memoryScopeFor for its own
4
- // createAuthorizer; consumers import all four from HERE ([2354] 兼容面全清 — the security.ts compat re-export is gone); index.ts's npm-facing re-export is unaffected (package.json
5
- // "exports" only ever exposed "." and "./main" — dist/security.js was never a reachable deep-import path).
6
1
  import { join } from "node:path";
7
2
  import { FileMemoryEngineBackend, resolveMemoryEngineRoot, deriveControlPlaneDir, formatUserScope, formatProjScope } from "@sema-agent/core";
8
- import { assertMemoryScopeWidth } from "./plugins/memory-key-guards.js"; // #271 件1:列宽单一属主(本文件再导出)
9
- /**
10
- * design/142 §1.2 的 v2 typed scope-key 前缀表 —— **单一属主**(A-002.4:`config.ts` 的
11
- * MEMORY_SYNC_SCOPE 派生处曾有一份逐字副本,两处咬合用途不同[盖 v2 契约标 vs 判要不要再包一层
12
- * `formatUserScope`],但判据必须是同一份,否则 core 加第五种 typed 前缀时两处各自漂)。
13
- *
14
- * 🔴 表按 core 的 {@link ParsedScopeKey} 判别词**穷举**:`Record<Exclude<kind,"legacy">, …>` 少一格
15
- * 编译红、多一格也编译红 —— core 新增/改名一种 typed kind,本表在编译期就先红,而不是等某个运行期
16
- * 分支静默走错。(`legacy` 排除在外正是本谓词要判的反面:无可识别前缀 = 不透明旧键。)
17
- */
3
+ import { assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
18
4
  const V2_SCOPE_PREFIX_BY_KIND = {
19
5
  user: "user:",
20
6
  org: "org:",
21
7
  proj: "proj:",
22
8
  userproj: "userproj:",
23
9
  };
24
- /** 前缀词表本体(供门/诊断读;判定一律走 {@link isV2ScopeKey},别在别处重写 startsWith 链)。 */
25
10
  export const V2_SCOPE_PREFIXES = Object.freeze(Object.values(V2_SCOPE_PREFIX_BY_KIND));
26
- /**
27
- * 一个 scope 键是否带 v2 typed 前缀。
28
- *
29
- * ⚠️ 判据刻意停在**前缀**层,不升级成 core 的 `parseScopeKey`:后者对「带可识别前缀但结构坏」的键
30
- * 是 fail-loud 抛错,而本谓词的两个调用点都只是在决定「要不要挂 v2 标 / 要不要再包一层」——把抛点
31
- * 挪到这里会把 core 的校验时机整体前移(行为面改动,不在本条案的射程内)。坏键仍由 core 在它自己的
32
- * 校验点 fail-loud。
33
- */
34
11
  export function isV2ScopeKey(key) {
35
12
  return V2_SCOPE_PREFIXES.some((prefix) => key.startsWith(prefix));
36
13
  }
37
- /** Resolve the memory scope for a request (design/138 S1 — the file-based memory engine is the only plane).
38
- *
39
- * SINGLE-USER (REQUIRE_PRINCIPAL !== "true"): ONE shared scope — `config.memoryScope` (default "local"),
40
- * regardless of any principal label the caller self-reports (a single-user worker's `anon:*` / ad-hoc
41
- * principals are surface labels, not isolation identities — keying memory by them would split the one
42
- * user's memory across surfaces). Explicit `MEMORY_SCOPE` wins (it IS config.memoryScope).
43
- *
44
- * MULTI-TENANT (REQUIRE_PRINCIPAL=true): always `undefined` ⇒ no `spec.memory` ⇒ memory is DARK,
45
- * fail-closed. The engine's file basement carries NO tenant isolation (one shared directory tree), so
46
- * handing tenants per-principal scopes over it would be a cross-tenant bleed surface; until a
47
- * tenant-isolated backend exists, multi-tenant memory is off by construction. */
48
- /** R5(批γ):memory scope 列宽(两方言 VARCHAR(190) 同宽)。principal 上限 190 **字符**在
49
- * security.ts 把身份轴守住了,但 `formatUserScope`/`formatProjScope` 的段编码(百分号转义)会
50
- * **膨胀**——非 ASCII principal 编码后可超列宽,此前落裸 SQL 错。产出处响亮拒,错误可分类。
51
- *
52
- * #271 件1:定义**搬去** `plugins/memory-key-guards.ts`(列宽与 store 写口同源单一属主),此处
53
- * 只再导出——旧 import 路径(本文件)逐字不变,`memoryScopeFor` 的产出处消费点也不变。 */
54
14
  export { MEMORY_SCOPE_COLUMN_CHARS, assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
55
15
  export function memoryScopeFor(config, principal, projectId) {
56
16
  if (config.requirePrincipal === true) {
57
- // S3-TOB(设计 §1.3):multi-tenant lights up ONLY on a DB backend (scope column = tenant
58
- // partition) — the scope is the VERIFIED principal, never MEMORY_SCOPE (a shared-env scope here would
59
- // be a cross-tenant bleed, 复审 F6) and never absent-principal (anonymous run ⇒ no memory).
60
- // NOTE (复审 F-07): the scope column is data PARTITIONING; tenant AUTHORIZATION is exactly this
61
- // binding — verified principal in, one tenant's scope out. File backend stays dark (no isolation).
62
- // 142-S1.5(core 1.268.0 到货即切,免费迁移窗=现网点亮前存量 0):bare principal → the v2 typed
63
- // key `user:<tenant>` via core's formatUserScope (encodeScopeSegment inside — principal chars are
64
- // segment-encoded, never truncated/collided). Paired with scopeContract:"v2" on the spec face below
65
- // (opt-in is EXPLICIT per design/142 §1.2 — no prefix sniffing anywhere).
66
- // 142-S4(派生 v1=项目盘):a SHAPE-VALIDATED projectId(authorizer 的 PROJECT_ID_REGEX 门)
67
- // 在场 ⇒ 项目盘 v2 键 `proj:<tenant>/<projectId>`(core formatProjScope;tenant 段 = verified
68
- // principal,身份永远不来自 body — projectId 只挑盘)。铁律:proj: 键经我方 DB 的场景=无仓
69
- // project(有仓 project 权威=git,根本不到这条派生线)。userproj: 派生不在 v1(待拍板)。
70
17
  if (config.memoryEngineBackend === "pg" || config.memoryEngineBackend === "tidb") {
71
18
  if (!principal)
72
19
  return undefined;
73
20
  const scope = projectId ? formatProjScope(principal, projectId) : formatUserScope(principal);
74
- assertMemoryScopeWidth(scope); // R5:编码膨胀在产出处接住,不落 store 裸错
21
+ assertMemoryScopeWidth(scope);
75
22
  return scope;
76
23
  }
77
- return undefined; // file backend: dark, fail-closed (no tenant isolation in the file engine)
24
+ return undefined;
78
25
  }
79
- return config.memoryScope; // single-user: one shared scope — principal labels never partition memory
26
+ return config.memoryScope;
80
27
  }
81
- /**
82
- * N0 (通宵测试 2026-07-09,三重坐实 e2b+kata+代码): the file memory engine (core design/138) materializes and
83
- * harvests via `node:fs` on the WORKER's local fs, while a remote exec lane routes the model's file tools to
84
- * the SANDBOX fs — the planes never meet: materialized memory is invisible inside the sandbox (the injected
85
- * instructions burn failing Write turns) and sandbox writes are never harvested (add=0). Until core defines
86
- * the boundary (engine routed through the ExecutionEnv seam, or host-only by contract — relayed on the team
87
- * channel), a remote lane forces memory DARK unless the operator explicitly vouches both lanes share one fs
88
- * (`MEMORY_ENGINE_REMOTE_LANE=allow`, e.g. ssh to a host on the same NFS mount).
89
- *
90
- * Pure posture probe (memoryEngineBackendFor consumes it; main.ts logs it): `undefined` = no plane split
91
- * (no remote lane, or the `host` lane), `dark` = split + fail-closed, `forced` = split + operator override.
92
- */
93
28
  export function memoryEngineRemoteLanePosture(config) {
94
29
  const lane = config.remoteExec?.provider;
95
30
  if (lane === undefined || lane === "host")
96
- return undefined; // hands work THIS machine's fs — same plane
31
+ return undefined;
97
32
  return { lane, posture: config.memoryEngineRemoteLaneAllowed === true ? "forced" : "dark" };
98
33
  }
99
- /**
100
- * N0 启动告警的**文案**(纯,可单测;boot/stores.ts 的装配点只负责喂「引擎这一腿真的接上了吗」并打日志)。
101
- *
102
- * 为什么要按引擎真身分腿(2026-08-12 复扫,已核真):{@link memoryEngineRemoteLanePosture} 的 `dark` 只对
103
- * **file** 引擎腿有裁决权 —— 车道门的唯一消费者是 {@link memoryEngineBackendFor}。`MEMORY_ENGINE_BACKEND=pg|tidb`
104
- * 的两条腿在 boot/stores.ts 里**先于**任何车道判断就被点亮(库是持久真身,与手的文件平面无关),于是同一次
105
- * 启动会先打 `memory_engine_enabled {enabled:true, backend:"pg"}`,几行之后再打一句「memory dark
106
- * (fail-closed) … Set MEMORY_ENGINE_REMOTE_LANE=allow」—— 两句直接对撞,而且把运维指向一个在该腿上**不接线**
107
- * 的旋钮(`memoryEngineRemoteLaneAllowed` 的读者只有本文件与那条日志)。日志是运维唯一能看见的部署事实,
108
- * 一句谎比没有这句更贵。
109
- *
110
- * 返回 `undefined` = 本部署没什么可说的(无平面分裂车道 / 记忆整体关 / 多租户 file 腿的 dark 成因是租户隔离,
111
- * 已由 `memory_engine_enabled` 的 reason 位报因,不在这里重复)。
112
- */
113
- export function buildMemoryRemoteLaneWarn(config,
114
- /** 记忆引擎这一腿**真的**接上了吗(`memoryEngine !== undefined`)——装配结果,不在本函数里重算。 */
115
- engineWired) {
34
+ export function buildMemoryRemoteLaneWarn(config, engineWired) {
116
35
  const posture = memoryEngineRemoteLanePosture(config);
117
36
  if (posture === undefined || !config.memoryEngineEnabled)
118
37
  return undefined;
119
38
  if (config.memoryEngineBackend !== "file") {
120
39
  if (!engineWired)
121
- return undefined; // DB 腿没接上只可能是拒启路径(半配 fail-loud),不在这里猜
40
+ return undefined;
122
41
  return {
123
42
  lane: posture.lane,
124
43
  effect: `memory ON over a remote lane: the "${config.memoryEngineBackend}" memory plane is durable in the DB and is NOT gated by ` +
@@ -128,7 +47,7 @@ engineWired) {
128
47
  };
129
48
  }
130
49
  if (config.requirePrincipal === true)
131
- return undefined; // 多租户 file 腿:dark 的成因是隔离,不是车道
50
+ return undefined;
132
51
  return posture.posture === "dark"
133
52
  ? {
134
53
  lane: posture.lane,
@@ -141,77 +60,22 @@ engineWired) {
141
60
  "WORKER fs; verify the lane really shares it.",
142
61
  };
143
62
  }
144
- /**
145
- * design/138 S1 wiring gate (pure — unit-testable without booting main): build the file-based memory-engine
146
- * backend for this deployment, or `undefined` when memory must stay dark.
147
- *
148
- * - SINGLE-USER TURNKEY ONLY (`requirePrincipal !== true`): the engine's file basement
149
- * (`FileMemoryEngineBackend` over one directory tree) has NO tenant isolation, so a multi-tenant
150
- * deployment gets `undefined` — memory dark, fail-closed (never a shared cross-tenant store).
151
- * - `MEMORY_ENGINE=off` (`config.memoryEngineEnabled === false`) is the explicit operator kill-switch.
152
- * - REMOTE EXEC LANE (N0): a plane-splitting lane (any non-`host` remoteExec) forces `undefined` — memory
153
- * dark, fail-closed — unless `MEMORY_ENGINE_REMOTE_LANE=allow` (see memoryEngineRemoteLanePosture above).
154
- * - Directory: `<root>/memory` where root = `resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot)`
155
- * (explicit `MEMORY_ENGINE_DIR` → caller's data root → `AGENT_DATA_DIR` env → `~/.ai-agent`). The
156
- * FileBackend PINS this directory (core §2.7: `directoryRoot` wins over per-repo derivation), so the
157
- * single user has ONE memory home across repos/tasks — the CC `~/.claude` posture.
158
- *
159
- * Returns the backend + the resolved config root (the root also rides on `RunnerDeps.memoryEngineDir` so
160
- * core derives the B3 control plane beside it).
161
- */
162
63
  export function memoryEngineBackendFor(config, fallbackRoot) {
163
64
  if (config.requirePrincipal === true || !config.memoryEngineEnabled)
164
65
  return undefined;
165
66
  if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
166
- return undefined; // N0: worker/sandbox file planes split
67
+ return undefined;
167
68
  const root = resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
168
69
  const dir = join(root, "memory");
169
- // 🔴 controlDir EXPLICIT: the backend's DEFAULT control plane derives from the ARGLESS
170
- // resolveMemoryEngineRoot() — i.e. AGENT_DATA_DIR/~/.ai-agent, IGNORING an explicit MEMORY_ENGINE_DIR. With a
171
- // custom dir the sidecars (ledger/shadow/quarantine) would land under the DEFAULT root while
172
- // RunnerDeps.memoryEngineDir points core's engine at the custom root — a split control plane. Pin it beside
173
- // OUR root using core's own derivation (key = the memory dir, the same shape as the backend's default).
174
70
  return { backend: new FileMemoryEngineBackend(dir, { controlDir: deriveControlPlaneDir(root, dir) }), root };
175
71
  }
176
- /**
177
- * MF-30 memory PAUSE (option B, per-request — clay 2026-06-27): build the `TaskSpec.memory` spec for ONE run from
178
- * the resolved memory `scope` + the request's `memoryWrite` toggle. `memoryWrite === false` ⇒ `writeScope: null`
179
- * (design/138 engine read-only: normalizeMemorySpec's writeScope:null — materialize/read still works, harvest
180
- * commits nothing). Absent/true ⇒ normal read+write. No scope (engine off / multi-tenant dark) ⇒ undefined
181
- * (memory feature off for this run). Pure (testable in isolation) — main.ts's resolveSpec composes it with the
182
- * engine-backend presence guard.
183
- */
184
- export function memorySpecForRequest(scope, memoryWrite, defaultScopes,
185
- /** design/170 件A(#148 件3④):origin 盖章的部署形态维(N2)。`multiTenant=true`(requirePrincipal)
186
- * ⇒ 登记簿 defaultScopes 的 org 键按 **request** 盖章(条目由调用方 projectId 选定=caller 可影响的
187
- * 选择器,core 准入门据此过目录判决);单用户 ⇒ 一律 deployment(operator 登记簿条目归 deployment,
188
- * 否则单用户部署被自家规则整拒——v4 §0 伤害①的成立前提)。缺参=旧调用形,不盖章(整键缺席=
189
- * core legacy 语义,零迁移)。 */
190
- originPolicy) {
72
+ export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy) {
191
73
  if (!scope)
192
74
  return undefined;
193
- // 142-S1.5: a scope carrying a v2 typed prefix rides WITH the explicit contract marker — core validates
194
- // the key fail-loud under "v2" (design/142 §1.2). Detection here is NOT sniffing: `user:`/`proj:` keys are
195
- // OUR memoryScopeFor's own mint (formatUserScope/formatProjScope) and `defaultScopes` entries are the
196
- // OPERATOR's center-registry declaration (config.projects — 登记簿说明其形态=v2 scope 键串),so the
197
- // prefix is a self/operator-produced marker, never a guess about caller data; a legacy opaque scope
198
- // (single-user config.memoryScope) with no defaultScopes stays contract-less byte-identical.
199
- const isV2 = isV2ScopeKey; // A-002.4:词表单一属主(见文件顶部 V2_SCOPE_PREFIX_BY_KIND)
200
- // 142-S4 defaultScopes 种子(config.projects[projectId].defaultScopes):额外的 READ 层(去重、去 scope
201
- // 自身、去空串;保 center 声明顺序)。写路由不变:core normalizeMemorySpec 的 writeScope 缺省=scopes 的
202
- // 最后一层,所以带种子时必须 EXPLICIT 钉 writeScope=派生 scope(否则 harvest 会写进登记簿的最后一个默认
203
- // scope — 一个共享层,错)。无种子 = 形状逐字节等价旧版(additive 锁)。
75
+ const isV2 = isV2ScopeKey;
204
76
  const extras = (defaultScopes ?? []).filter((s, i, arr) => typeof s === "string" && s.length > 0 && s !== scope && arr.indexOf(s) === i);
205
- // core 5.1.0([2390]②)删单数 `scope` 键:normalizeMemorySpec 只迭代 `scopes`,单数形 ⇒ undefined ⇒
206
- // memory 静默整体关闭(全窗复审 D7-F1,HIGH——返回值位不触发多余属性检查,旧形编译全绿)。生产者恒产
207
- // `scopes: [scope, ...extras]`;writeScope 只在带种子时显式钉(core 默认取 scopes 最后一层,无种子时
208
- // 最后一层就是派生 scope=正确,不钉保持最小形)。
209
77
  const layered = extras.length > 0 ? { scopes: [scope, ...extras], writeScope: scope } : { scopes: [scope] };
210
78
  const v2 = isV2(scope) || extras.some(isV2) ? { scopeContract: "v2" } : {};
211
- // 件A origin 盖章(core 5.13.0 `memory.scopeOrigins` 两格语义):只盖 org 键(非 org 不入准入判决,
212
- // v4 §1);派生/env 位的 `scope` org 形只可能来自 operator 显式 MEMORY_SCOPE(memoryScopeFor 多租户
213
- // 恒铸 user: 形)⇒ deployment;登记簿种子按部署形态维分格(见参数注)。零 org 键 ⇒ 整键缺席
214
- // (core legacy 零迁移;非 org 部署形状逐字节不变=additive 锁)。
215
79
  const stamped = {};
216
80
  if (originPolicy !== undefined) {
217
81
  if (scope.startsWith("org:"))
@@ -222,7 +86,6 @@ originPolicy) {
222
86
  }
223
87
  }
224
88
  const origins = Object.keys(stamped).length > 0 ? { scopeOrigins: stamped } : {};
225
- // memoryWrite === false(MF-30 pause)wins over the layered writeScope pin — 只读语义在有种子时同样成立。
226
89
  return memoryWrite === false ? { ...layered, writeScope: null, ...v2, ...origins } : { ...layered, ...v2, ...origins };
227
90
  }
228
91
  //# sourceMappingURL=memory-scope.js.map
@@ -1,29 +1,8 @@
1
- // 142-S2.5-W1 —— TOC 同步 client 腿:包 core `syncMemoryScope`(1.275.1 导出,一轮 =
2
- // 一 RTT 双向,cursor 保留纪律见 core sync-client.js 头注)。本模块只补 core 刻意留白的部署半场:
3
- // ① transport:fetch POST `{MEMORY_SYNC_URL 去尾斜杠}{path}`(path 由 core 铸,scope 已
4
- // encodeURIComponent),headers = Bearer + JSON;非 2xx throw(带 status + body 前 200 字节)。
5
- // core 契约:transport 在任何本地写之前跑,throw = 本轮零本地副作用。
6
- // ② cursor 持久化:`join(memoryRoot, "sync-cursor-" + encodeScopeSegment(peer) + ".json")`——
7
- // 读:不存在 ⇒ undefined(首轮);坏 JSON/形状不符/scope-peer 不匹配 ⇒ warn + 当首轮(core 对
8
- // 错配 cursor 是 fail-loud throw,这里预检降为首轮:空基线重收敛是协议的幂等安全面)。
9
- // 写:仅 cursorAdvanced 才写(core 保留纪律的落盘半场),tmp+rename 原子。
10
- // ③ 串行化 + 节流:syncOnce 按 promise 链串行(两轮并发会在同一文件面上交错写);trigger =
11
- // fire-and-forget,上一轮在飞(排队中/执行中)则跳过(设计:简单 inflight 布尔)。
12
- // conflicts = logger.warn 逐条上报(不自动 ladder——败者铸 sibling 的解决动作留给 operator/后续单)。
13
1
  import { randomBytes } from "node:crypto";
14
2
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
15
3
  import { dirname, join } from "node:path";
16
4
  import { encodeScopeSegment, syncMemoryScope, } from "@sema-agent/core";
17
- /** 缺省对端名(core `syncMemoryScope` 同款缺省;cursor 按 (scope,peer) 分区)。 */
18
5
  export const DEFAULT_MEMORY_SYNC_PEER = "central";
19
- /**
20
- * fetch transport(core `MemorySyncTransport` 的部署实现)。`url` = 中心 server 基址(尾斜杠剥除,
21
- * core 传来的 path 以 `/` 开头);`token` 走 Authorization: Bearer(与中心侧 gatedPrincipal 的
22
- * verified-principal 门对齐)。非 2xx throw(status + body 前 200 字节——中心 4xx/5xx 的 typed error
23
- * body 就是最好的诊断线索);2xx 返回解码后的 JSON(形状校验在 core parseMemorySyncResponse,职责
24
- * 不重复)。`timeoutMs`(缺省 60s,AbortSignal.timeout)是活性保护:fire-and-forget 轮挂死会把
25
- * inflight 节流永久卡住(undici 默认 headers 超时 300s 太钝)。
26
- */
27
6
  export function createMemorySyncTransport(opts) {
28
7
  const base = opts.url.replace(/\/+$/, "");
29
8
  const fetchImpl = opts.fetchImpl ?? fetch;
@@ -41,13 +20,9 @@ export function createMemorySyncTransport(opts) {
41
20
  return JSON.parse(text);
42
21
  };
43
22
  }
44
- /** cursor 文件路径:`memoryRoot`(= RunnerDeps.memoryEngineDir 的 resolved config root,memory/ 目录
45
- * 旁)下的 per-peer sidecar。peer 段走 core encodeScopeSegment(文件名安全字符纪律与 scope 目录同)。 */
46
23
  export function memorySyncCursorPath(memoryRoot, peer) {
47
24
  return join(memoryRoot, `sync-cursor-${encodeScopeSegment(peer)}.json`);
48
25
  }
49
- /** 读 cursor:不存在 ⇒ undefined(首轮);坏 JSON / 形状不符 / (scope,peer) 错配 ⇒ warn + undefined
50
- * (core 对错配 cursor fail-loud throw——这里预检降为首轮,空基线重收敛幂等安全)。 */
51
26
  async function loadCursor(path, scope, peer, log) {
52
27
  let raw;
53
28
  try {
@@ -83,22 +58,12 @@ async function loadCursor(path, scope, peer, log) {
83
58
  log.warn("memory_sync_cursor_invalid", { path, scope, peer, note: "shape/identity mismatch — treating as first round" });
84
59
  return undefined;
85
60
  }
86
- /** 写 cursor:tmp + rename 原子(半写的 cursor = 下轮坏 JSON 当首轮,重收敛而非错基线)。 */
87
61
  async function saveCursor(path, cursor) {
88
62
  await mkdir(dirname(path), { recursive: true });
89
- // #131-3:staging 名带熵(pid+random)——共享 memoryRoot 的跨进程形(server + run-local 同 data 根)
90
- // 用固定 `.tmp` 会互抢 staging(一侧 rename 走对方的 tmp ⇒ 对方 ENOENT / 写出对方字节);同仓其余
91
- // 原子写(config-lkg / local-session-store / skills-mcp / workflow-completion-inbox)全带熵,此处对齐。
92
63
  const tmp = `${path}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
93
64
  await writeFile(tmp, JSON.stringify(cursor), "utf8");
94
65
  await rename(tmp, path);
95
66
  }
96
- /**
97
- * 组装一个 per-(scope,peer) 的同步执行器:cursor 持久化 + core `syncMemoryScope` + conflicts 上报 +
98
- * 串行化/节流。`backend` = 本地 File authority plane(memoryEngineBackendFor 的产物);`memoryRoot` =
99
- * resolved config root(cursor sidecar 落在 memory/ 目录旁)。
100
- */
101
- /** S2.5 分批 loop 的轮数上限(fail-loud 警戒,防协议异常时死循环;正常收敛远低于此)。 */
102
67
  export const MAX_SYNC_ROUNDS = 32;
103
68
  export function createMemorySyncRunner(opts) {
104
69
  const peer = opts.peer ?? DEFAULT_MEMORY_SYNC_PEER;
@@ -107,8 +72,6 @@ export function createMemorySyncRunner(opts) {
107
72
  let cursor = await loadCursor(cursorPath, opts.scope, peer, opts.log);
108
73
  let result;
109
74
  let subRound = 0;
110
- // S2.5 分批续轮:任一半场截断 ⇒ 立即续跑下一子轮(用上一轮返回的 cursor 作基线,协议幂等收敛);
111
- // MAX_SYNC_ROUNDS 警戒 fail-loud(截断标志异常粘死时不无限烧请求,余量下轮 trigger 自然补)。
112
75
  for (;;) {
113
76
  subRound += 1;
114
77
  result = await syncMemoryScope({
@@ -131,7 +94,7 @@ export function createMemorySyncRunner(opts) {
131
94
  }
132
95
  if (result.cursorAdvanced && result.cursor !== undefined) {
133
96
  try {
134
- await saveCursor(cursorPath, result.cursor); // 子轮间也持久化(崩溃不回退基线)
97
+ await saveCursor(cursorPath, result.cursor);
135
98
  }
136
99
  catch (err) {
137
100
  opts.log.warn("memory_sync_cursor_write_failed", { path: cursorPath, err: String(err) });
@@ -140,7 +103,6 @@ export function createMemorySyncRunner(opts) {
140
103
  cursor = result.cursor ?? cursor;
141
104
  opts.log.info("memory_sync_continuation", { reason, scope: opts.scope, peer, subRound, pushTruncated: result.pushTruncated === true, pullTruncated: result.pullTruncated === true });
142
105
  }
143
- // conflicts 逐条 warn(server 半场 + 本地半场聚合,core 绝不隐藏;解决 ladder 不自动跑)。
144
106
  for (const c of result.conflicts) {
145
107
  opts.log.warn("memory_sync_conflict", {
146
108
  scope: opts.scope,
@@ -152,8 +114,6 @@ export function createMemorySyncRunner(opts) {
152
114
  ...(c.currentRev !== undefined ? { currentRev: c.currentRev } : {}),
153
115
  });
154
116
  }
155
- // 保留纪律的落盘半场:仅 cursorAdvanced 才写。写失败只 warn(数据已按 CAS 落定,丢的是基线推进——
156
- // 下轮按旧基线重放即幂等自愈,与中心侧 putCursor 失败的收敛论证同款),绝不把成功轮报成失败。
157
117
  if (result.cursorAdvanced && result.cursor !== undefined) {
158
118
  try {
159
119
  await saveCursor(cursorPath, result.cursor);
@@ -176,8 +136,6 @@ export function createMemorySyncRunner(opts) {
176
136
  });
177
137
  return result;
178
138
  };
179
- // 串行链:两轮并发会在同一 File plane / cursor 文件上交错(push 半场读集与 pull 半场落盘互踩)。
180
- // `pending` = 排队+在飞的轮数(设计的“简单 inflight 布尔”推广到可排队形态:syncOnce 排队,trigger 跳过)。
181
139
  let chain = Promise.resolve();
182
140
  let pending = 0;
183
141
  const syncOnce = (reason) => {
@@ -192,7 +150,7 @@ export function createMemorySyncRunner(opts) {
192
150
  };
193
151
  const trigger = (reason) => {
194
152
  if (pending > 0)
195
- return; // 上一轮在飞 ⇒ 跳过(harvest 高频时的节流;漏掉的变更下一轮全量补上)
153
+ return;
196
154
  void syncOnce(reason).catch((err) => {
197
155
  opts.log.warn("memory_sync_failed", { reason, scope: opts.scope, peer, err: String(err) });
198
156
  });
@@ -1,49 +1,7 @@
1
- // 142-S2.5 中心侧同步 API 的执行内核(设计 sema-internal server/docs/design/142-S4-DATAPLANE.md §2.5;裁定:
2
- // **plan 随数据走,core 零运行时半场**——core 的 `reconcileMemoryEntries`/`nextSyncBaseline`/
3
- // `screenInboundEntries` 是纯函数导出,持数据的一侧(本 server=DB 权威+per-(scope,peer) 游标)直接
4
- // import 调用算 plan 并执行 CAS;API 面全在 server,core 的承诺=纯函数语义稳定。
5
- //
6
- // 协议(POST /v1/memory/sync/:scope,http/server.ts 持路由/鉴权/验型;本模块只吃已验型请求):
7
- // 请求 = { peer, baseRevs, entries[], deletes[] } —— TOC 本地引擎按**自己的**游标把 push 半场打过来:
8
- // entries = 自 baseRevs 以来本地新增/变更的条目全量;deletes = 本地删除(tombstone)的 id
9
- // (CAS 基线 = baseRevs[id],所以 deletes ⊆ baseRevs 键——验型层已保证)。
10
- // 响应 = { applied[], conflicts[], serverEntries[], serverDeletes[], cursor } —— 一个 RTT 双向:
11
- // serverEntries/serverDeletes = 中心侧自 baseRevs 以来变过/删过的条目 = 本地的 pull 半场。
12
- //
13
- // 与 §2.5 草案的两处**诚实偏离**(按 core JSDoc 实况,不硬凑草案):
14
- // ① 响应加 `serverDeletes[]`:core MemorySyncPlan.deleteRemote—— 没有删除传播腿,
15
- // 中心侧删除永远过不去、每轮重摆同一状态 = livelock;草案响应形状缺这条腿。
16
- // ② 响应加 `cursor`(本轮落库的 per-(scope,peer) 新基线):客户端应用完 pull 半场后持久化为
17
- // 自己的游标——它自己算不出(不持有中心全集)。
18
- //
19
- // 游标推进纪律(nextSyncBaseline 的 JSDoc 假设「POST-round 双方都已应用」,但响应发出时客户端还
20
- // **没有**应用 pull 半场——直接乐观推进会在客户端应用失败时把它的旧条目误判为「客户端改过」而回灌
21
- // 中心 = 数据回退):对 pull 半场(plan.push/plan.deleteRemote)的 id **保留旧基线**。两种客户端结局
22
- // 都收敛且零回退:客户端应用了 ⇒ 下轮 identical revs → no-op+基线推进;没应用 ⇒ 下轮干净重推。
23
- //
24
- // 执行序(任务书①-⑤):
25
- // ① screenInboundEntries 入站门(拒 = 该条入 conflicts 上报,不落库不入队——门拒是终态)+
26
- // rev 诚实校验(rev = core computeEntryRev 的内容哈希契约;谎报的 rev 会破坏 identical-revs
27
- // 收敛判定,按条拒入 conflicts,批内其余照常——applyPatches 的 per-patch 非致命姿势);
28
- // ② 过门条目按 baseRevs[id] CAS applyPatches(基线有 ⇒ update+baseRev;基线无 ⇒ add——core
29
- // MemorySyncPlan JSDoc 的 CAS 纪律原文:absent from the baseline ⇒ plain add);
30
- // ③ deletes 同 CAS(plan.deleteLocal 的 {id, baseRev} → delete patch);
31
- // ④ reconcileMemoryEntries(baseRevs, 中心集, 对端视图) 算双向 plan(对端视图 = push 条目 +
32
- // 「基线未提及 id 钉在基线 rev」的合成 stub——stub 永远不会被 pull/delete 判定选中,只参与
33
- // rev 对比);⑤ nextSyncBaseline + 保留纪律 → putCursor。
34
- //
35
- // putCursor 失败于 applyPatches 之后 = 补丁已落、游标陈旧:向上抛(路由 500),客户端按同一
36
- // baseRevs 重放即幂等自愈(identical revs 全程 no-op)——游标写不是本轮正确性的一部分。
37
1
  import { computeEntryRev, MEMORY_ORIGIN_CAUSES, nextSyncBaseline, reconcileMemoryEntries, screenInboundEntries, } from "@sema-agent/core";
38
2
  import { exportMemoryScope } from "./memory-export.js";
39
- // ── 验型(HTTP 面的 422 判据;返回 error 字符串而非 throw——validateUserSkills 姿势) ──────────
40
- /** 一轮 push 的条目数上限 = core REMOTE_HARVEST_MAX_FILES 同款口径(一个 scope 的文件面上限 500;
41
- * 同步一轮打满全集也在此内)。deletes 上限同基数放宽(全集删除也要能一轮表达)。 */
42
3
  export const MEMORY_SYNC_MAX_ENTRIES = 500;
43
4
  export const MEMORY_SYNC_MAX_DELETES = 2000;
44
- /** Shape-validate + NORMALIZE the request body (picked-not-spread:只挑认识的字段重建,body 上的
45
- * 多余键绝不透传进数据面)。`scope` = 路径上已过 owner 门的 scope——每个条目的 scope 必须逐字
46
- * 等于它(否则一个 owner-gated 路由就成了跨租户写门)。 */
47
5
  export function parseMemorySyncRequest(body, scope) {
48
6
  if (body === null || typeof body !== "object" || Array.isArray(body))
49
7
  return { ok: false, error: "body must be a JSON object { peer, baseRevs, entries, deletes }" };
@@ -99,12 +57,6 @@ export function parseMemorySyncRequest(body, scope) {
99
57
  if (fm["extra"] !== undefined && !(Array.isArray(fm["extra"]) && fm["extra"].every((s) => typeof s === "string"))) {
100
58
  return { ok: false, error: "entry.frontmatter.extra must be an array of strings when present" };
101
59
  }
102
- // #323([4769]):三只 provenance 载体入白名单——修前这里静默剥掉 origin/trust/provenance,
103
- // 带标条目过 sync wire 即丢标(污染追踪断链)、rev 恒假冲突(client 以含标载体算 rev),且
104
- // 「typed origin + 冲突 extra 块」的歧义表示被选择性改写成非歧义形绕过 core 的
105
- // ambiguousOriginRepresentation 拒绝门(applyPatches 直调路径拒、sync 路径放行=同法两答)。
106
- // 修=pick+闭集验形(照 core MemoryEntryFrontmatter 单源形),坏形响亮拒;歧义**判定**刻意不在
107
- // 这里重复——保真透传后 core 受理门(clause 31)与 sync 路径同答([4770] core 已确认零改动)。
108
60
  const rawOrigin = fm["origin"];
109
61
  if (rawOrigin !== undefined) {
110
62
  if (rawOrigin === null || typeof rawOrigin !== "object" || Array.isArray(rawOrigin))
@@ -120,9 +72,6 @@ export function parseMemorySyncRequest(body, scope) {
120
72
  }
121
73
  if (fm["trust"] !== undefined && fm["trust"] !== "untrusted")
122
74
  return { ok: false, error: 'entry.frontmatter.trust must be the literal "untrusted" when present (single-value union — there is no trusted spelling)' };
123
- // core 5.55.0 第 9 键 distilled(consolidation 产物块):受理为**不透明块**浅验过境——成员级判据
124
- // 不在这里手抄(F1b 温床),真闸=store add/update 臂的 distilledWhitewashRefusal(committedDistilledOf/
125
- // distilledEquals);sync 剥掉它=洗白(#323 origin 同病),深验它=第二份 core 结构。
126
75
  const rawDistilled = fm["distilled"];
127
76
  if (rawDistilled !== undefined && (rawDistilled === null || typeof rawDistilled !== "object" || Array.isArray(rawDistilled))) {
128
77
  return { ok: false, error: "entry.frontmatter.distilled must be an object (opaque consolidation block) when present" };
@@ -152,7 +101,6 @@ export function parseMemorySyncRequest(body, scope) {
152
101
  ...(fm["description"] !== undefined ? { description: fm["description"] } : {}),
153
102
  ...(fm["type"] !== undefined ? { type: fm["type"] } : {}),
154
103
  ...(fm["deleted"] !== undefined ? { deleted: fm["deleted"] } : {}),
155
- // #323:三载体保真重建(pick,不 spread——上面已闭集验形;origin 逐成员重建防原对象带私货键)
156
104
  ...(rawOrigin !== undefined
157
105
  ? {
158
106
  origin: {
@@ -203,21 +151,12 @@ export function parseMemorySyncRequest(body, scope) {
203
151
  }
204
152
  return { ok: true, value: { peer, baseRevs, entries, deletes, ...(pull !== undefined ? { pull } : {}) } };
205
153
  }
206
- // ── 执行内核 ─────────────────────────────────────────────────────────────────────────────────
207
- /** 基线 stub:对端在基线上、本轮未提及(或被门拒钉回基线)的 id 的合成占位。只有 id/rev 参与
208
- * reconcile 判定;rev === baseRevs[id] ⇒ remoteChanged 恒 false ⇒ 永不被 pull/deleteLocal 选中,
209
- * 身体永不落任何盘。 */
210
154
  function baselineStub(id, rev, scope, slug) {
211
155
  return { id, rev, scope, slug: slug ?? `sync-baseline-stub/${id}`, frontmatter: {}, body: "" };
212
156
  }
213
- /**
214
- * One full sync round for one (scope, peer). `backend` = 中心侧 entry plane(DB 权威),`cursors` =
215
- * per-(scope,peer) 游标面(sync_cursors)。纯数据参数进(路由已完成鉴权/owner 门/验型),响应形状出。
216
- */
217
157
  export async function performMemorySync(backend, cursors, scope, req, now = Date.now) {
218
158
  const conflicts = [];
219
- // 入站门 + rev 诚实校验(拒 = 终态:上报 conflicts,不落库不入队;per-entry 非致命,批内其余照常)
220
- const rejected = new Map(); // id → reason
159
+ const rejected = new Map();
221
160
  for (const e of req.entries) {
222
161
  const computed = computeEntryRev(e);
223
162
  if (computed !== e.rev)
@@ -233,23 +172,16 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
233
172
  conflicts.push({ id, reason, ...(base !== undefined ? { baseRev: base } : {}) });
234
173
  }
235
174
  const clean = req.entries.filter((e) => !rejected.has(e.id));
236
- // 中心侧全集(export 面同款读姿势:listHeaders → getByIds → scope 过滤纵深)。
237
175
  const central = await exportMemoryScope(backend, scope);
238
176
  const centralById = new Map(central.map((e) => [e.id, e]));
239
- // 对端视图:push 条目 + 基线 stub。门拒的 id 钉回基线(有基线时)——绝不能让「拒收」被误判成
240
- // 「对端删除」而触发 deleteLocal(中心副本会被误删);新条目被拒 = 视图里干脆没有它。
241
177
  const peerView = [...clean];
242
178
  const pushedOrDeleted = new Set([...clean.map((e) => e.id), ...req.deletes]);
243
179
  for (const [id, rev] of Object.entries(req.baseRevs)) {
244
180
  if (pushedOrDeleted.has(id))
245
181
  continue;
246
- // 未提及 = 对端仍在基线(含被门拒钉回的);stub 只有 rev 参与判定。
247
182
  peerView.push(baselineStub(id, rev, scope, centralById.get(id)?.slug));
248
183
  }
249
- // ④(判定先行)三方 reconcile:local = 中心,remote = 对端视图。
250
184
  const plan = reconcileMemoryEntries(req.baseRevs, central, peerView);
251
- // ②③ 中心半场执行:pull(采纳对端 push)按 baseRevs CAS;deleteLocal 同 CAS。backend.applyPatches
252
- // 的 per-id CAS 是并发纵深——本函数读集与落库之间挤进来的写者会让 baseRev 失配 = 冲突上报,绝不盲写。
253
185
  const patches = [
254
186
  ...plan.pull.map((e) => {
255
187
  const base = req.baseRevs[e.id];
@@ -261,8 +193,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
261
193
  for (const c of report.conflicts) {
262
194
  conflicts.push({ id: c.id, reason: c.reason, ...(c.baseRev !== undefined ? { baseRev: c.baseRev } : {}), ...(c.currentRev !== undefined ? { currentRev: c.currentRev } : {}) });
263
195
  }
264
- // plan 冲突(双边自基线各自变过 / 删改相撞):currentRev = 中心当前 rev(中心已删则缺席),
265
- // 客户端拿它走 §3 ladder(败者铸 sibling)。
266
196
  for (const c of plan.conflicts) {
267
197
  conflicts.push({
268
198
  id: c.id,
@@ -275,7 +205,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
275
205
  ...(c.local !== undefined ? { currentRev: c.local.rev } : {}),
276
206
  });
277
207
  }
278
- // ⑤ 游标:POST-round 中心集(内存推演,只按 applied 口径——CAS 输掉的补丁不算)× 对端视图。
279
208
  const appliedByKey = new Set(report.applied.map((a) => `${a.op}\x1f${a.id}`));
280
209
  const appliedPatch = new Map();
281
210
  for (const p of patches)
@@ -292,9 +221,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
292
221
  centralPost.push(p.entry);
293
222
  }
294
223
  const nextBase = nextSyncBaseline(centralPost, peerView);
295
- // 保留纪律(头注):pull 半场的 id 保留旧基线——客户端此刻还没应用 serverEntries/serverDeletes,
296
- // 乐观推进会在「客户端没应用」的下一轮把它的旧条目误判成新改动回灌中心(数据回退)。保留旧基线
297
- // 两种结局都收敛:应用了 ⇒ identical revs no-op+推进;没应用 ⇒ 干净重推/重删。
298
224
  for (const e of plan.push) {
299
225
  const base = req.baseRevs[e.id];
300
226
  if (base !== undefined)
@@ -302,11 +228,6 @@ export async function performMemorySync(backend, cursors, scope, req, now = Date
302
228
  }
303
229
  for (const d of plan.deleteRemote)
304
230
  nextBase[d.id] = d.baseRev;
305
- // ⑥ S2.5 pull 分批(契约 settled 2026-07-14):serverEntries 稳定序(id 升序)截 limit;deletes 轻不截。
306
- // cursor 精确化约束(core 回执):**不得预填未交付条目的 rev**——有基线的 pull id 上面的保留纪律已经
307
- // 钉回旧值;无基线的新条目若未交付,必须从 nextBase 整个剔除(预填=客户端应用交付子集后持久化整张
308
- // cursor,下轮把未交付 id 误判「baseline 已知」⇒ 该条目永久漏拉)。已交付的新条目保持 nextSyncBaseline
309
- // 值(客户端按纪律应用完交付集才持久化 cursor,安全)。
310
231
  let serverEntries = plan.push;
311
232
  let pullTruncated = false;
312
233
  if (req.pull !== undefined && plan.push.length > req.pull.limit) {