common-memory-core 0.2.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 (284) hide show
  1. package/.env.sample +3 -0
  2. package/CHANGELOG.md +39 -0
  3. package/LICENSE +21 -0
  4. package/README.md +120 -0
  5. package/SECURITY.md +48 -0
  6. package/dist/cli/codex/transcript-0.153.4.d.ts +20 -0
  7. package/dist/cli/codex/transcript-0.153.4.d.ts.map +1 -0
  8. package/dist/cli/codex/transcript-0.153.4.js +78 -0
  9. package/dist/cli/codex/transcript-0.153.4.js.map +1 -0
  10. package/dist/cli/codex-config.d.ts +2 -0
  11. package/dist/cli/codex-config.d.ts.map +1 -0
  12. package/dist/cli/codex-config.js +5 -0
  13. package/dist/cli/codex-config.js.map +1 -0
  14. package/dist/cli/codex-hook.d.ts +18 -0
  15. package/dist/cli/codex-hook.d.ts.map +1 -0
  16. package/dist/cli/codex-hook.js +140 -0
  17. package/dist/cli/codex-hook.js.map +1 -0
  18. package/dist/cli/codex-session.d.ts +3 -0
  19. package/dist/cli/codex-session.d.ts.map +1 -0
  20. package/dist/cli/codex-session.js +3 -0
  21. package/dist/cli/codex-session.js.map +1 -0
  22. package/dist/cli/flush-command.d.ts +6 -0
  23. package/dist/cli/flush-command.d.ts.map +1 -0
  24. package/dist/cli/flush-command.js +30 -0
  25. package/dist/cli/flush-command.js.map +1 -0
  26. package/dist/cli/host-launch.d.ts +28 -0
  27. package/dist/cli/host-launch.d.ts.map +1 -0
  28. package/dist/cli/host-launch.js +16 -0
  29. package/dist/cli/host-launch.js.map +1 -0
  30. package/dist/cli/host-process.d.ts +3 -0
  31. package/dist/cli/host-process.d.ts.map +1 -0
  32. package/dist/cli/host-process.js +35 -0
  33. package/dist/cli/host-process.js.map +1 -0
  34. package/dist/cli/host-session.d.ts +22 -0
  35. package/dist/cli/host-session.d.ts.map +1 -0
  36. package/dist/cli/host-session.js +204 -0
  37. package/dist/cli/host-session.js.map +1 -0
  38. package/dist/cli/import-command.d.ts +20 -0
  39. package/dist/cli/import-command.d.ts.map +1 -0
  40. package/dist/cli/import-command.js +98 -0
  41. package/dist/cli/import-command.js.map +1 -0
  42. package/dist/cli/interactive-process.d.ts +3 -0
  43. package/dist/cli/interactive-process.d.ts.map +1 -0
  44. package/dist/cli/interactive-process.js +15 -0
  45. package/dist/cli/interactive-process.js.map +1 -0
  46. package/dist/cli/main.d.ts +3 -0
  47. package/dist/cli/main.d.ts.map +1 -0
  48. package/dist/cli/main.js +151 -0
  49. package/dist/cli/main.js.map +1 -0
  50. package/dist/cli/mcp-config.d.ts +16 -0
  51. package/dist/cli/mcp-config.d.ts.map +1 -0
  52. package/dist/cli/mcp-config.js +108 -0
  53. package/dist/cli/mcp-config.js.map +1 -0
  54. package/dist/cli/network-test.d.ts +4 -0
  55. package/dist/cli/network-test.d.ts.map +1 -0
  56. package/dist/cli/network-test.js +28 -0
  57. package/dist/cli/network-test.js.map +1 -0
  58. package/dist/cli/operations.d.ts +26 -0
  59. package/dist/cli/operations.d.ts.map +1 -0
  60. package/dist/cli/operations.js +55 -0
  61. package/dist/cli/operations.js.map +1 -0
  62. package/dist/cli/session-drain.d.ts +3 -0
  63. package/dist/cli/session-drain.d.ts.map +1 -0
  64. package/dist/cli/session-drain.js +34 -0
  65. package/dist/cli/session-drain.js.map +1 -0
  66. package/dist/cli/storage-paths.d.ts +4 -0
  67. package/dist/cli/storage-paths.d.ts.map +1 -0
  68. package/dist/cli/storage-paths.js +15 -0
  69. package/dist/cli/storage-paths.js.map +1 -0
  70. package/dist/cli/tui-integrations.d.ts +4 -0
  71. package/dist/cli/tui-integrations.d.ts.map +1 -0
  72. package/dist/cli/tui-integrations.js +143 -0
  73. package/dist/cli/tui-integrations.js.map +1 -0
  74. package/dist/cli/tui-prompts.d.ts +22 -0
  75. package/dist/cli/tui-prompts.d.ts.map +1 -0
  76. package/dist/cli/tui-prompts.js +64 -0
  77. package/dist/cli/tui-prompts.js.map +1 -0
  78. package/dist/cli/tui-settings.d.ts +21 -0
  79. package/dist/cli/tui-settings.d.ts.map +1 -0
  80. package/dist/cli/tui-settings.js +198 -0
  81. package/dist/cli/tui-settings.js.map +1 -0
  82. package/dist/cli/tui.d.ts +4 -0
  83. package/dist/cli/tui.d.ts.map +1 -0
  84. package/dist/cli/tui.js +292 -0
  85. package/dist/cli/tui.js.map +1 -0
  86. package/dist/cli/work-config.d.ts +24 -0
  87. package/dist/cli/work-config.d.ts.map +1 -0
  88. package/dist/cli/work-config.js +147 -0
  89. package/dist/cli/work-config.js.map +1 -0
  90. package/dist/config/config.d.ts +41 -0
  91. package/dist/config/config.d.ts.map +1 -0
  92. package/dist/config/config.js +193 -0
  93. package/dist/config/config.js.map +1 -0
  94. package/dist/config/private-env.d.ts +8 -0
  95. package/dist/config/private-env.d.ts.map +1 -0
  96. package/dist/config/private-env.js +38 -0
  97. package/dist/config/private-env.js.map +1 -0
  98. package/dist/config/runtime.d.ts +32 -0
  99. package/dist/config/runtime.d.ts.map +1 -0
  100. package/dist/config/runtime.js +85 -0
  101. package/dist/config/runtime.js.map +1 -0
  102. package/dist/core/contracts/errors.d.ts +10 -0
  103. package/dist/core/contracts/errors.d.ts.map +1 -0
  104. package/dist/core/contracts/errors.js +23 -0
  105. package/dist/core/contracts/errors.js.map +1 -0
  106. package/dist/core/safety/external-preflight.d.ts +7 -0
  107. package/dist/core/safety/external-preflight.d.ts.map +1 -0
  108. package/dist/core/safety/external-preflight.js +47 -0
  109. package/dist/core/safety/external-preflight.js.map +1 -0
  110. package/dist/core/safety/redaction.d.ts +2 -0
  111. package/dist/core/safety/redaction.d.ts.map +1 -0
  112. package/dist/core/safety/redaction.js +4 -0
  113. package/dist/core/safety/redaction.js.map +1 -0
  114. package/dist/core/safety/rules.d.ts +6 -0
  115. package/dist/core/safety/rules.d.ts.map +1 -0
  116. package/dist/core/safety/rules.js +14 -0
  117. package/dist/core/safety/rules.js.map +1 -0
  118. package/dist/core/safety/scanner.d.ts +11 -0
  119. package/dist/core/safety/scanner.d.ts.map +1 -0
  120. package/dist/core/safety/scanner.js +18 -0
  121. package/dist/core/safety/scanner.js.map +1 -0
  122. package/dist/core/transaction/fsync.d.ts +6 -0
  123. package/dist/core/transaction/fsync.d.ts.map +1 -0
  124. package/dist/core/transaction/fsync.js +41 -0
  125. package/dist/core/transaction/fsync.js.map +1 -0
  126. package/dist/index.d.ts +16 -0
  127. package/dist/index.d.ts.map +1 -0
  128. package/dist/index.js +13 -0
  129. package/dist/index.js.map +1 -0
  130. package/dist/mcp/ingress.d.ts +61 -0
  131. package/dist/mcp/ingress.d.ts.map +1 -0
  132. package/dist/mcp/ingress.js +134 -0
  133. package/dist/mcp/ingress.js.map +1 -0
  134. package/dist/mcp/server.d.ts +4 -0
  135. package/dist/mcp/server.d.ts.map +1 -0
  136. package/dist/mcp/server.js +92 -0
  137. package/dist/mcp/server.js.map +1 -0
  138. package/dist/mcp/stdio.d.ts +5 -0
  139. package/dist/mcp/stdio.d.ts.map +1 -0
  140. package/dist/mcp/stdio.js +115 -0
  141. package/dist/mcp/stdio.js.map +1 -0
  142. package/dist/memory-manager/contracts/diagnostic.d.ts +14 -0
  143. package/dist/memory-manager/contracts/diagnostic.d.ts.map +1 -0
  144. package/dist/memory-manager/contracts/diagnostic.js +17 -0
  145. package/dist/memory-manager/contracts/diagnostic.js.map +1 -0
  146. package/dist/memory-manager/contracts/disclosure.d.ts +12 -0
  147. package/dist/memory-manager/contracts/disclosure.d.ts.map +1 -0
  148. package/dist/memory-manager/contracts/disclosure.js +8 -0
  149. package/dist/memory-manager/contracts/disclosure.js.map +1 -0
  150. package/dist/memory-manager/contracts/errors.d.ts +15 -0
  151. package/dist/memory-manager/contracts/errors.d.ts.map +1 -0
  152. package/dist/memory-manager/contracts/errors.js +25 -0
  153. package/dist/memory-manager/contracts/errors.js.map +1 -0
  154. package/dist/memory-manager/contracts/model-port.d.ts +38 -0
  155. package/dist/memory-manager/contracts/model-port.d.ts.map +1 -0
  156. package/dist/memory-manager/contracts/model-port.js +2 -0
  157. package/dist/memory-manager/contracts/model-port.js.map +1 -0
  158. package/dist/memory-manager/network/client.d.ts +13 -0
  159. package/dist/memory-manager/network/client.d.ts.map +1 -0
  160. package/dist/memory-manager/network/client.js +122 -0
  161. package/dist/memory-manager/network/client.js.map +1 -0
  162. package/dist/memory-manager/network/route.d.ts +31 -0
  163. package/dist/memory-manager/network/route.d.ts.map +1 -0
  164. package/dist/memory-manager/network/route.js +148 -0
  165. package/dist/memory-manager/network/route.js.map +1 -0
  166. package/dist/memory-manager/openai/abort.d.ts +4 -0
  167. package/dist/memory-manager/openai/abort.d.ts.map +1 -0
  168. package/dist/memory-manager/openai/abort.js +19 -0
  169. package/dist/memory-manager/openai/abort.js.map +1 -0
  170. package/dist/memory-manager/openai/bounded-body.d.ts +2 -0
  171. package/dist/memory-manager/openai/bounded-body.d.ts.map +1 -0
  172. package/dist/memory-manager/openai/bounded-body.js +49 -0
  173. package/dist/memory-manager/openai/bounded-body.js.map +1 -0
  174. package/dist/memory-manager/openai/openai-chat-adapter.d.ts +16 -0
  175. package/dist/memory-manager/openai/openai-chat-adapter.d.ts.map +1 -0
  176. package/dist/memory-manager/openai/openai-chat-adapter.js +51 -0
  177. package/dist/memory-manager/openai/openai-chat-adapter.js.map +1 -0
  178. package/dist/memory-manager/openai/openai-responses-adapter.d.ts +15 -0
  179. package/dist/memory-manager/openai/openai-responses-adapter.d.ts.map +1 -0
  180. package/dist/memory-manager/openai/openai-responses-adapter.js +14 -0
  181. package/dist/memory-manager/openai/openai-responses-adapter.js.map +1 -0
  182. package/dist/memory-manager/openai/options.d.ts +14 -0
  183. package/dist/memory-manager/openai/options.d.ts.map +1 -0
  184. package/dist/memory-manager/openai/options.js +29 -0
  185. package/dist/memory-manager/openai/options.js.map +1 -0
  186. package/dist/memory-manager/openai/remote-http.d.ts +33 -0
  187. package/dist/memory-manager/openai/remote-http.d.ts.map +1 -0
  188. package/dist/memory-manager/openai/remote-http.js +207 -0
  189. package/dist/memory-manager/openai/remote-http.js.map +1 -0
  190. package/dist/memory-manager/openai/response-decoder.d.ts +3 -0
  191. package/dist/memory-manager/openai/response-decoder.d.ts.map +1 -0
  192. package/dist/memory-manager/openai/response-decoder.js +57 -0
  193. package/dist/memory-manager/openai/response-decoder.js.map +1 -0
  194. package/dist/memory-manager/openai/retry.d.ts +8 -0
  195. package/dist/memory-manager/openai/retry.d.ts.map +1 -0
  196. package/dist/memory-manager/openai/retry.js +18 -0
  197. package/dist/memory-manager/openai/retry.js.map +1 -0
  198. package/dist/pi-extension/extraction-runtime.d.ts +51 -0
  199. package/dist/pi-extension/extraction-runtime.d.ts.map +1 -0
  200. package/dist/pi-extension/extraction-runtime.js +87 -0
  201. package/dist/pi-extension/extraction-runtime.js.map +1 -0
  202. package/dist/pi-extension/index.d.ts +19 -0
  203. package/dist/pi-extension/index.d.ts.map +1 -0
  204. package/dist/pi-extension/index.js +172 -0
  205. package/dist/pi-extension/index.js.map +1 -0
  206. package/dist/v2/canonical.d.ts +53 -0
  207. package/dist/v2/canonical.d.ts.map +1 -0
  208. package/dist/v2/canonical.js +321 -0
  209. package/dist/v2/canonical.js.map +1 -0
  210. package/dist/v2/contract.d.ts +34 -0
  211. package/dist/v2/contract.d.ts.map +1 -0
  212. package/dist/v2/contract.js +53 -0
  213. package/dist/v2/contract.js.map +1 -0
  214. package/dist/v2/document-import.d.ts +100 -0
  215. package/dist/v2/document-import.d.ts.map +1 -0
  216. package/dist/v2/document-import.js +259 -0
  217. package/dist/v2/document-import.js.map +1 -0
  218. package/dist/v2/errors.d.ts +6 -0
  219. package/dist/v2/errors.d.ts.map +1 -0
  220. package/dist/v2/errors.js +29 -0
  221. package/dist/v2/errors.js.map +1 -0
  222. package/dist/v2/import.d.ts +26 -0
  223. package/dist/v2/import.d.ts.map +1 -0
  224. package/dist/v2/import.js +48 -0
  225. package/dist/v2/import.js.map +1 -0
  226. package/dist/v2/lock.d.ts +3 -0
  227. package/dist/v2/lock.d.ts.map +1 -0
  228. package/dist/v2/lock.js +47 -0
  229. package/dist/v2/lock.js.map +1 -0
  230. package/dist/v2/memory-maintainer.md +38 -0
  231. package/dist/v2/read-guidance.d.ts +3 -0
  232. package/dist/v2/read-guidance.d.ts.map +1 -0
  233. package/dist/v2/read-guidance.js +3 -0
  234. package/dist/v2/read-guidance.js.map +1 -0
  235. package/dist/v2/reader.d.ts +25 -0
  236. package/dist/v2/reader.d.ts.map +1 -0
  237. package/dist/v2/reader.js +38 -0
  238. package/dist/v2/reader.js.map +1 -0
  239. package/dist/v2/registry.d.ts +14 -0
  240. package/dist/v2/registry.d.ts.map +1 -0
  241. package/dist/v2/registry.js +46 -0
  242. package/dist/v2/registry.js.map +1 -0
  243. package/dist/v2/runtime.d.ts +133 -0
  244. package/dist/v2/runtime.d.ts.map +1 -0
  245. package/dist/v2/runtime.js +326 -0
  246. package/dist/v2/runtime.js.map +1 -0
  247. package/dist/v2/session-drain.d.ts +14 -0
  248. package/dist/v2/session-drain.d.ts.map +1 -0
  249. package/dist/v2/session-drain.js +16 -0
  250. package/dist/v2/session-drain.js.map +1 -0
  251. package/dist/v2/session.d.ts +54 -0
  252. package/dist/v2/session.d.ts.map +1 -0
  253. package/dist/v2/session.js +158 -0
  254. package/dist/v2/session.js.map +1 -0
  255. package/dist/v2/writer.d.ts +40 -0
  256. package/dist/v2/writer.d.ts.map +1 -0
  257. package/dist/v2/writer.js +327 -0
  258. package/dist/v2/writer.js.map +1 -0
  259. package/docs/00-index.md +19 -0
  260. package/docs/03-target-architecture.md +49 -0
  261. package/docs/init-v0.1-closeout.md +152 -0
  262. package/docs/init-v0.1-design.md +237 -0
  263. package/docs/init-v0.1-verification.md +303 -0
  264. package/docs/outbound-network-design.md +100 -0
  265. package/docs/outbound-network-verification.json +300 -0
  266. package/docs/provider-verification.md +94 -0
  267. package/docs/releasing.md +138 -0
  268. package/docs/session-integration.md +134 -0
  269. package/docs/tui-workbench.md +173 -0
  270. package/docs/usage.md +815 -0
  271. package/docs/v2-ablation-results.json +12035 -0
  272. package/docs/v2-ablation.md +117 -0
  273. package/docs/v2-evaluation-repeat-results.json +7 -0
  274. package/docs/v2-evaluation-scripted-results.json +67 -0
  275. package/docs/v2-evaluation.md +35 -0
  276. package/docs/v2-optimization-plan.md +23 -0
  277. package/docs/v2-performance-baseline-runtime.js.txt +231 -0
  278. package/docs/v2-performance-baseline.json +338 -0
  279. package/docs/v2-performance-behavior-equivalence.json +17 -0
  280. package/docs/v2-performance-results.json +585 -0
  281. package/docs/v2-performance.md +88 -0
  282. package/docs/v2-replacement-test-map.md +14 -0
  283. package/docs/v2-verification.md +49 -0
  284. package/package.json +87 -0
@@ -0,0 +1,117 @@
1
+ # V2 调度机制消融实验
2
+
3
+ ## 结论范围
4
+
5
+ 本次是**真实 RuntimeStore 的调度层单因素消融**,不是完整 Writer 或真实模型的语义消融。
6
+ 未修改生产实现、未移除生产安全机制、未调用远程模型、未读取实际用户 dataRoot。
7
+ Prompt、context-only、Section patch、语义 admission 等组件的质量贡献**尚未测量**;不能从脚本 oracle 推断它们无效。
8
+
9
+ ## 方法与控制变量
10
+
11
+ - 40 个场景 × 6 个组 × 3 次重复 = **720 次隔离 SQLite 实验**;重复必须得到完全相同的去 ID 轨迹,否则脚本失败。
12
+ - 组别:完整 Hybrid、分别移除数量/体积/空闲/最长等待/生命周期触发;一次仅移除一项。
13
+ - 默认基线:6 Turn、16 KiB、120 秒 idle、600 秒 oldest wait。
14
+ - **独立固定批容量为 6**,调用实际 `claim({maxTurns:6})`。生产默认批容量取 turnThreshold;直接调大该阈值而不固定容量会混入批大小效应。
15
+ - 移除阈值用安全最大整数实现;所有实验轮数、字节数和虚拟时间远小于此值。
16
+ - 同场景各组共享输入、Scope、busy/stable、关闭/重启时间与观察截止点。
17
+ - 每秒检查一次、每次最多领取一批;同刻顺序固定为 **enqueue → lifecycle → 稳定检查**。新输入与 idle deadline 同刻时先重置 idle。
18
+ - 领取后立即 no-op 完成,不模拟模型耗时、失败、重试和内容写入。
19
+ - 生命周期用持久 `requestFlush()`,不是 `force:true`;关闭仅排队,重开数据库后才检查。
20
+ - **截止时不额外强制 flush**,否则会掩盖尾批差异。pending 是窗口内未处理,不是永久丢失。
21
+ - 均值按 Turn 加权,不将三次确定性重复视为三倍独立统计样本,不提供虚假的显著性或置信区间。
22
+
23
+ 原始 30 条轨迹来自已有 fixture,但末轮后 1 秒的 lifecycle flush 是本实验合成的宿主事件,
24
+ 不是实际采集证据;统一观察到末轮后 60 秒。截止选择影响 pending 数,不能外推生产发生率。
25
+ 另有 9 个默认参数诊断场景,以及 1 个单独分层的高数量阈值敏感性场景。
26
+
27
+ ## 指标
28
+
29
+ - **维护批次**:实际成功领取并消费的队列批次,不是真实模型调用或 Token 成本。
30
+ - **已处理延迟**:消费时刻减投递时刻;JSON 记录均值、P95 与逐轮值。
31
+ - **截尾平均等待**:每轮取 `min(消费时刻, 观察截止)-投递时刻` 后求均值,避免忽略 pending 产生幸存者偏差。
32
+ - pending 另报数量和年龄;截尾平均等待是有限窗口指标,不是未完成轮次的最终延迟估计。
33
+ - 模型调用数为 0,Token 未测量。原始时序、分层汇总与代码/fixture 摘要在 [JSON](v2-ablation-results.json)。
34
+
35
+ ## 结果 A:已有 30 条轨迹(210 Turn)
36
+
37
+ | 消融组 | 已处理 / 总轮数 | 截尾待处理 | 维护批次 | 截尾平均等待(秒) | 相对基线(秒) |
38
+ |---|---:|---:|---:|---:|---:|
39
+ | 完整 Hybrid | 210 / 210 | 0 | 60 | 47.89 | 0.00 |
40
+ | 移除数量触发 | 210 / 210 | 0 | 60 | 56.20 | 8.31 |
41
+ | 移除体积触发 | 210 / 210 | 0 | 60 | 47.89 | 0.00 |
42
+ | 移除空闲触发 | 210 / 210 | 0 | 60 | 58.00 | 10.11 |
43
+ | 移除最长等待 | 210 / 210 | 0 | 60 | 47.89 | 0.00 |
44
+ | 移除生命周期 flush | 144 / 210 | 66 | 30 | 66.43 | 18.54 |
45
+
46
+ 数量与空闲触发影响等待,即使总批次数仍为 60。移除生命周期后有 66 条在截止前尚未处理;
47
+ 这不表示正文丢失。体积和最长等待在此样本无边际效应,不能据此判断为冗余。
48
+
49
+ ## 结果 B:默认参数诊断场景(9 场景、39 Turn)
50
+
51
+ | 消融组 | 已处理 / 总轮数 | 截尾待处理 | 维护批次 | 截尾平均等待(秒) | 相对基线(秒) |
52
+ |---|---:|---:|---:|---:|---:|
53
+ | 完整 Hybrid | 39 / 39 | 0 | 15 | 153.85 | 0.00 |
54
+ | 移除数量触发 | 33 / 39 | 6 | 14 | 197.05 | 43.21 |
55
+ | 移除体积触发 | 38 / 39 | 1 | 14 | 155.38 | 1.54 |
56
+ | 移除空闲触发 | 32 / 39 | 7 | 9 | 161.79 | 7.95 |
57
+ | 移除最长等待 | 37 / 39 | 2 | 14 | 155.90 | 2.05 |
58
+ | 移除生命周期 flush | 35 / 39 | 4 | 13 | 156.92 | 3.08 |
59
+
60
+ 每个机制都有能被独立识别的场景:
61
+
62
+ | 机制 | 场景与证据 |
63
+ |---|---|
64
+ | 数量 | 6 条短消息在 50 秒内抵达:基线第 50 秒消费;去数量组第 110 秒仍待处理 6 条。 |
65
+ | 体积 | 单条完整 20 KiB Turn:基线立即消费;去体积组第 60 秒仍待处理。 |
66
+ | 空闲 | 两条短消息后不结束 Session:基线第 150 秒消费;去 idle 组第 210 秒仍待处理 2 条。 |
67
+ | 生命周期 | 第 20 秒请求 flush;或第 20 秒关闭、第 40 秒重开,持久 flush 才触发。去生命周期组到第 60 秒均未消费。 |
68
+ | 最长等待 | 第 0 秒与第 650 秒投递两条,宿主 busy 到第 660 秒。稳定后只有 oldest wait 满足;基线消费两条,去 maxWait 组第 700 秒仍未处理。 |
69
+
70
+ **最长等待不是严格的墙钟完成 SLA。** 模型工作仍须等稳定边界;上例首轮实际等了 660 秒。
71
+ 跨 Scope 交错和 busy 边界也在 fixture 中,避免把全 global、始终空闲的结果误推到全部宿主。
72
+
73
+ ## 结果 C:高阈值敏感性(单独分层)
74
+
75
+ 数量阈值 100、字节阈值 1 GB;连续每 100 秒输入,共 12 条,观察到第 1150 秒。
76
+
77
+ | 消融组 | 已处理 / 总轮数 | 截尾待处理 | 维护批次 | 截尾平均等待(秒) | 相对基线(秒) |
78
+ |---|---:|---:|---:|---:|---:|
79
+ | 完整 Hybrid | 6 / 12 | 6 | 1 | 325.00 | 0.00 |
80
+ | 移除数量触发 | 6 / 12 | 6 | 1 | 325.00 | 0.00 |
81
+ | 移除体积触发 | 6 / 12 | 6 | 1 | 325.00 | 0.00 |
82
+ | 移除空闲触发 | 6 / 12 | 6 | 1 | 325.00 | 0.00 |
83
+ | 移除最长等待 | 0 / 12 | 12 | 0 | 600.00 | 275.00 |
84
+ | 移除生命周期 flush | 6 / 12 | 6 | 1 | 325.00 | 0.00 |
85
+
86
+ 去掉 maxWait 后一直被新输入重置 idle,窗口内零维护。基线处理 6 条、余下 6 条被截尾;
87
+ 这是窗口与固定批容量的结果,不是失败或永久积压证明。
88
+
89
+ ## 判断与下一步
90
+
91
+ 1. 在这些受控场景中,五种触发都有窗口内及时性贡献,**没有依据直接删除某一机制**。
92
+ 2. original-30 零效应属于覆盖不足;busy-aged-backlog 使默认 maxWait 的独立作用可见。
93
+ 3. 结果支持保留多触发设计,但不证明参数成本最优、语义更好或线上用户收益。
94
+ 4. 后续真实模型消融应固定模型、调用预算和对话集,分别测 current-state/context/维护指令,评审保留、更新、遗忘、过度泛化和 Scope 错误;本轮未执行该付费实验。
95
+
96
+ ## 复现与验证
97
+
98
+ ```sh
99
+ npm run ablate:v2
100
+ npm test -- --run tests/v2/ablation.test.ts
101
+ npm run lint
102
+ npm test
103
+ ```
104
+
105
+ 仅新增实验脚本、fixture、测试、npm 命令与文档,生产 Writer/Runtime/Pi 行为不变。
106
+ 独立实验设计审查记录见本文件末尾。
107
+
108
+ ### 验证记录
109
+
110
+ - `npm run ablate:v2`:build 与 720 次隔离执行通过;三次重复逐场景轨迹完全一致。
111
+ - `npm run lint`:typecheck 与静态边界检查通过。
112
+ - `npm test`:13 个测试文件,104 项测试通过(包含 3 项新增实验回归)。
113
+ - `git diff --check`:通过。
114
+ - 独立 `critic` 实验设计审查:**OKAY,无阻断项**;核对脚本、场景、结果与报告,并独立运行新增 3 项测试通过。
115
+
116
+ 实验文件:`scripts/ablate-v2.mjs`、`fixtures/v2-ablation-scenarios.json`、
117
+ `tests/v2/ablation.test.ts`、`docs/v2-ablation-results.json`。
@@ -0,0 +1,7 @@
1
+ {
2
+ "runs": 10,
3
+ "policyRuns": 30,
4
+ "trajectoryRuns": 900,
5
+ "failures": [],
6
+ "semanticQualityVerified": false
7
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "mode": "scripted-executor",
3
+ "semanticQualityVerified": false,
4
+ "trajectories": 30,
5
+ "results": [
6
+ {
7
+ "policy": "every-turn",
8
+ "cases": 30,
9
+ "correctFinalState": 30,
10
+ "scopeErrors": 0,
11
+ "calls": 210,
12
+ "inputBytes": 1455570,
13
+ "inputTokens": 0,
14
+ "outputTokens": 0,
15
+ "tokensMeasured": false,
16
+ "retries": 0,
17
+ "failureCodes": {},
18
+ "maxLatencyMs": 0,
19
+ "meanLatencyMs": 0,
20
+ "outcomes": {
21
+ "committed": 35,
22
+ "ignored": 175,
23
+ "idle": 30
24
+ }
25
+ },
26
+ {
27
+ "policy": "fixed-six",
28
+ "cases": 30,
29
+ "correctFinalState": 30,
30
+ "scopeErrors": 0,
31
+ "calls": 60,
32
+ "inputBytes": 430525,
33
+ "inputTokens": 0,
34
+ "outputTokens": 0,
35
+ "tokensMeasured": false,
36
+ "retries": 0,
37
+ "failureCodes": {},
38
+ "maxLatencyMs": 225000,
39
+ "meanLatencyMs": 58000,
40
+ "outcomes": {
41
+ "idle": 210,
42
+ "committed": 35,
43
+ "ignored": 25
44
+ }
45
+ },
46
+ {
47
+ "policy": "hybrid",
48
+ "cases": 30,
49
+ "correctFinalState": 30,
50
+ "scopeErrors": 0,
51
+ "calls": 60,
52
+ "inputBytes": 430525,
53
+ "inputTokens": 0,
54
+ "outputTokens": 0,
55
+ "tokensMeasured": false,
56
+ "retries": 0,
57
+ "failureCodes": {},
58
+ "maxLatencyMs": 165000,
59
+ "meanLatencyMs": 85286,
60
+ "outcomes": {
61
+ "idle": 222,
62
+ "committed": 35,
63
+ "ignored": 25
64
+ }
65
+ }
66
+ ]
67
+ }
@@ -0,0 +1,35 @@
1
+ # V2 bounded evaluation
2
+
3
+ `npm run build && node scripts/evaluate-v2.mjs` runs all 30 synthetic trajectories
4
+ through the actual Writer, runtime queue, protocol validator and Markdown commit path
5
+ under every-turn, fixed-six and hybrid scheduling. All policies flush their tail batch.
6
+ There are five retain, update, forget, ignore, conditional preference and project-scope
7
+ cases each. Variable pauses exercise idle maintenance. Fixtures contain explicit oracle
8
+ actions used only by the scripted model, never by production semantic admission.
9
+
10
+ The JSON report records final-state correctness, project-to-global scope errors, model
11
+ calls, serialized input bytes, real token usage when available, failed/retry outcomes,
12
+ and capture-to-model latency. Scripted token counts are zero (unknown/not measured),
13
+ not estimates. These comparisons measure execution and scheduling; they do not show
14
+ that any real model correctly interprets natural-language evidence. In particular,
15
+ conditional wording and ignore correctness are supplied by the oracle.
16
+
17
+ Real-model quality is separate, paid and opt-in:
18
+
19
+ ```sh
20
+ COMMON_MEMORY_EVAL_CONFIRM=paid-remote-disclosure node scripts/evaluate-v2.mjs --real --limit=3
21
+ ```
22
+
23
+ This uses configured credentials but creates isolated temporary runtime/canonical
24
+ roots, never writes production memory, and discloses synthetic fixture text only.
25
+ `--limit` is 1–30; total model calls have a hard cap of 630. No automatic provider
26
+ fallback is used. Final-state exact-match is deliberately strict and can count a
27
+ semantically correct paraphrase as a failure: inspect outputs before drawing quality
28
+ conclusions. Broader human-reviewed over-memory/generalization precision/recall and
29
+ real provider token/cost results remain unverified until this opt-in run is performed.
30
+
31
+ The final scripted evaluation is repeated ten times with freshly generated host IDs.
32
+ `v2-evaluation-repeat-results.json` records all 900 trajectory-policy executions.
33
+ Failure codes are retained in metrics rather than hiding failed calls behind a final
34
+ state match. A prior UUID/card-scanner false positive was reproduced and fixed; see
35
+ `v2-verification.md` for the regression record.
@@ -0,0 +1,23 @@
1
+ # V2 本轮性能优化计划
2
+
3
+ ## 目标与不变项
4
+
5
+ 仅降低 Runtime SQLite 空闲轮询与积压领取开销。保留消融验证过的五种触发、默认阈值、完整 Turn、跨 Scope FIFO、持久 flush、lease fencing、隐私清理与文件回执恢复。无新依赖、无新增数据库字段、无模型调用。不以删功能或调大延迟换取速度。
6
+
7
+ ## 基线与执行顺序
8
+
9
+ 1. 先冻结本轮修改前的 dist,并保存 `v2-performance-baseline.json`;脚本在独立临时 dataRoot 测量,造数和 warmup 不计入耗时。
10
+ 2. 编辑实现前新增行为回归:Unicode/NUL 字节计数、跨 Scope 阈值及 FIFO、时钟回拨时按 ID 而非时间极值取首尾、flush 跨 Scope 尾批、maxTurns 极值、已清空正文不重复更新。
11
+ 3. 仅加查询索引和局部读取:空闲/待处理 state 索引、jobId 索引、活跃 job 部分索引、可清理 processed 正文部分索引;清理排除已 NULL 正文。
12
+ 4. claim 仅读取当前批容量的队首,count 在全队列判断,必要时 SQL UTF-8 BLOB 字节聚合;idle/maxWait 仍用末/首 ID 的入队时间。更新后查询剩余 pending 来决定是否清除 flush。
13
+ 5. 跑全量回归与 720 次消融;调度记录必须与优化前完全相同。最后进行新旧配对性能测试与独立复审。
14
+
15
+ ## 不做的优化
16
+
17
+ 不缓存队列总量,不迁移正文 schema,不跳过 lease/CAS/来源检查,不缓存或跳过永久回执验证。回执恢复虽然也是测量项,但本轮保持其安全行为,避免用弱化恢复校验换性能。
18
+
19
+ ## 验收
20
+
21
+ 新旧相同固定数据、相同批次与余量;至少 9 个实测样本并报告 median/P95 和小样本开销,不在 CI 对墙钟设脆弱阈值。大积压和大量历史的主要路径应有明确提升;若没有则撤回无收益改动。机器、Node、代码摘要及噪声边界写入结果,不把本机微基准当线上 SLA 或语义质量证据。
22
+
23
+ 本计划由主代理撰写,独立 critic lane 在实施前审查约束,实施后复核行为和证据。
@@ -0,0 +1,231 @@
1
+ import { failureCode } from './errors.js';
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { lstatSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { randomUUID, createHash } from "node:crypto";
6
+ import { safeDirectory } from "./lock.js";
7
+ /** Durable queue, not a reconstructible index. All mutating operations are synchronous. */
8
+ export class RuntimeStore {
9
+ db;
10
+ #now;
11
+ #options;
12
+ #depth = 0;
13
+ constructor(dataRoot, options = {}) {
14
+ safeDirectory(dataRoot);
15
+ const path = join(dataRoot, "runtime.sqlite");
16
+ for (const suffix of ["", "-journal", "-wal", "-shm"]) {
17
+ try {
18
+ const stat = lstatSync(path + suffix);
19
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
20
+ throw new Error("Unsafe runtime path");
21
+ }
22
+ catch (error) {
23
+ if (error.code !== "ENOENT")
24
+ throw error;
25
+ }
26
+ }
27
+ this.#now = options.now ?? Date.now;
28
+ this.#options = { turnThreshold: options.turnThreshold ?? 6, byteThreshold: options.byteThreshold ?? 16384, idleMs: options.idleMs ?? 120000, maxWaitMs: options.maxWaitMs ?? 600000, leaseMs: options.leaseMs ?? 120000, maxAttempts: options.maxAttempts ?? 5 };
29
+ for (const value of Object.values(this.#options))
30
+ if (!Number.isSafeInteger(value) || value <= 0)
31
+ throw new Error("Invalid runtime limit");
32
+ this.db = new DatabaseSync(path);
33
+ this.db.exec(`PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;
34
+ CREATE TABLE IF NOT EXISTS observations(id INTEGER PRIMARY KEY AUTOINCREMENT, sessionId TEXT NOT NULL, entryId TEXT NOT NULL, text TEXT, digest TEXT NOT NULL, scope TEXT NOT NULL, observedAt TEXT NOT NULL, source TEXT NOT NULL, state TEXT NOT NULL, enqueuedAt INTEGER NOT NULL, processedAt INTEGER, jobId TEXT, issue TEXT, UNIQUE(sessionId,entryId));
35
+ CREATE TABLE IF NOT EXISTS jobs(id TEXT PRIMARY KEY, token TEXT NOT NULL, generation INTEGER NOT NULL, state TEXT NOT NULL, expires INTEGER NOT NULL, attempts INTEGER NOT NULL, available INTEGER NOT NULL, issue TEXT);
36
+ CREATE TABLE IF NOT EXISTS document_versions(target TEXT PRIMARY KEY, hash TEXT NOT NULL);
37
+ CREATE TABLE IF NOT EXISTS associations(target TEXT NOT NULL, sourceId INTEGER NOT NULL, PRIMARY KEY(target,sourceId));
38
+ CREATE TABLE IF NOT EXISTS receipts(id TEXT PRIMARY KEY);
39
+ CREATE TABLE IF NOT EXISTS inputs(id TEXT PRIMARY KEY, sessionId TEXT NOT NULL, text TEXT NOT NULL, source TEXT NOT NULL, scope TEXT NOT NULL, queueKind TEXT NOT NULL, parentEntryId TEXT, used INTEGER NOT NULL DEFAULT 0);
40
+ CREATE TABLE IF NOT EXISTS deliveries(id INTEGER PRIMARY KEY AUTOINCREMENT, sessionId TEXT NOT NULL, text TEXT NOT NULL, digest TEXT NOT NULL, timestamp INTEGER NOT NULL, scope TEXT NOT NULL, source TEXT NOT NULL, state TEXT NOT NULL, UNIQUE(sessionId,digest,timestamp));
41
+ CREATE TABLE IF NOT EXISTS settings(key TEXT PRIMARY KEY,value INTEGER NOT NULL);`);
42
+ }
43
+ transaction(fn) {
44
+ if (this.#depth > 0)
45
+ return fn();
46
+ this.db.exec("BEGIN IMMEDIATE");
47
+ this.#depth++;
48
+ try {
49
+ const result = fn();
50
+ if (result instanceof Promise)
51
+ throw new Error("Runtime transaction must be synchronous");
52
+ this.db.exec("COMMIT");
53
+ return result;
54
+ }
55
+ catch (error) {
56
+ this.db.exec("ROLLBACK");
57
+ throw error;
58
+ }
59
+ finally {
60
+ this.#depth--;
61
+ }
62
+ }
63
+ enqueue(input) {
64
+ const digest = createHash("sha256").update(input.text).digest("hex");
65
+ return this.transaction(() => {
66
+ const existing = this.db.prepare("SELECT * FROM observations WHERE sessionId=? AND entryId=?").get(input.sessionId, input.entryId);
67
+ if (existing) {
68
+ if (existing.digest !== digest || existing.scope !== input.scope || existing.source !== input.source)
69
+ throw new Error("Conflicting observation identity");
70
+ return existing;
71
+ }
72
+ const state = ["interactive", "rpc"].includes(input.source) ? "pending" : "quarantined";
73
+ const result = this.db.prepare("INSERT INTO observations(sessionId,entryId,text,digest,scope,observedAt,source,state,enqueuedAt) VALUES(?,?,?,?,?,?,?,?,?)").run(input.sessionId, input.entryId, input.text, digest, input.scope, input.observedAt, input.source, state, this.#now());
74
+ return this.db.prepare("SELECT * FROM observations WHERE id=?").get(result.lastInsertRowid);
75
+ });
76
+ }
77
+ hasWork() { return Boolean(this.db.prepare("SELECT 1 FROM observations WHERE state IN ('pending','claimed') LIMIT 1").get()); }
78
+ pending() { return this.db.prepare("SELECT * FROM observations WHERE state='pending' ORDER BY id").all(); }
79
+ context(observation) {
80
+ return this.db.prepare("SELECT * FROM observations WHERE id<? AND sessionId=? AND scope=? AND state='processed' AND text IS NOT NULL ORDER BY id DESC LIMIT 2").all(observation.id, observation.sessionId, observation.scope).reverse();
81
+ }
82
+ requestFlush() { this.db.prepare("INSERT INTO settings VALUES('flush',1) ON CONFLICT(key) DO UPDATE SET value=1").run(); }
83
+ claim(options = {}) {
84
+ if (options.maxTurns !== undefined && (!Number.isSafeInteger(options.maxTurns) || options.maxTurns < 1))
85
+ throw new Error("Invalid batch size");
86
+ return this.transaction(() => {
87
+ const now = this.#now();
88
+ const active = this.db.prepare("SELECT * FROM jobs WHERE state IN ('running','retry') ORDER BY rowid LIMIT 1").get();
89
+ if (active) {
90
+ if (active.state === "running" && Number(active.expires) > now || Number(active.available) > now)
91
+ return null;
92
+ if (Number(active.attempts) >= this.#options.maxAttempts) {
93
+ this.db.prepare("UPDATE jobs SET state='dead' WHERE id=?").run(active.id);
94
+ this.db.prepare("UPDATE observations SET state='dead' WHERE jobId=?").run(active.id);
95
+ }
96
+ else {
97
+ const token = randomUUID(), generation = Number(active.generation) + 1;
98
+ this.db.prepare("UPDATE jobs SET token=?,generation=?,state='running',expires=?,attempts=attempts+1 WHERE id=?").run(token, generation, now + this.#options.leaseMs, active.id);
99
+ return { id: String(active.id), token, generation, observations: this.db.prepare("SELECT * FROM observations WHERE jobId=? ORDER BY id").all(active.id) };
100
+ }
101
+ }
102
+ const pending = this.pending();
103
+ if (!pending.length) {
104
+ this.db.prepare("DELETE FROM settings WHERE key='flush'").run();
105
+ return null;
106
+ }
107
+ const flush = this.db.prepare("SELECT value FROM settings WHERE key='flush'").get();
108
+ if (!options.force && !flush?.value && pending.length < this.#options.turnThreshold && pending.reduce((n, o) => n + Buffer.byteLength(o.text ?? ""), 0) < this.#options.byteThreshold && now - pending[pending.length - 1].enqueuedAt < this.#options.idleMs && now - pending[0].enqueuedAt < this.#options.maxWaitMs)
109
+ return null;
110
+ const firstScope = pending[0].scope;
111
+ const observations = [];
112
+ for (const observation of pending) {
113
+ if (observation.scope !== firstScope || observations.length >= (options.maxTurns ?? this.#options.turnThreshold))
114
+ break;
115
+ observations.push(observation);
116
+ }
117
+ const id = randomUUID(), token = randomUUID();
118
+ this.db.prepare("INSERT INTO jobs VALUES(?,?,1,'running',?,1,0,NULL)").run(id, token, now + this.#options.leaseMs);
119
+ for (const o of observations)
120
+ this.db.prepare("UPDATE observations SET state='claimed',jobId=? WHERE id=?").run(id, o.id);
121
+ if (observations.length === pending.length)
122
+ this.db.prepare("DELETE FROM settings WHERE key='flush'").run();
123
+ return { id, token, generation: 1, observations };
124
+ });
125
+ }
126
+ assertLease(job) {
127
+ const row = this.db.prepare("SELECT * FROM jobs WHERE id=? AND token=? AND generation=? AND state='running' AND expires>?").get(job.id, job.token, job.generation, this.#now());
128
+ if (!row)
129
+ throw new Error("STALE_LEASE");
130
+ }
131
+ trim(job, count) {
132
+ return this.transaction(() => { this.assertLease(job); if (!Number.isSafeInteger(count) || count < 1 || count > job.observations.length)
133
+ throw new Error("Invalid batch size"); for (const o of job.observations.slice(count))
134
+ this.db.prepare("UPDATE observations SET state='pending',jobId=NULL WHERE id=? AND jobId=?").run(o.id, job.id); return { ...job, observations: job.observations.slice(0, count) }; });
135
+ }
136
+ renew(job) { this.transaction(() => { this.assertLease(job); this.db.prepare("UPDATE jobs SET expires=? WHERE id=?").run(this.#now() + this.#options.leaseMs, job.id); }); }
137
+ finish(job, receipt) { this.transaction(() => { this.assertLease(job); if (receipt) {
138
+ if (receipt.jobId !== job.id || JSON.stringify([...receipt.observationIds].sort()) !== JSON.stringify(job.observations.map(o => o.id).sort()))
139
+ throw new Error("Receipt batch mismatch");
140
+ this.recoverReceipt(receipt);
141
+ }
142
+ else
143
+ this.#consume(job.id); }); }
144
+ #consume(id) { this.db.prepare("UPDATE observations SET state='processed',processedAt=? WHERE jobId=?").run(this.#now(), id); this.db.prepare("UPDATE jobs SET state='done' WHERE id=?").run(id); }
145
+ recoverReceipt(receipt) {
146
+ this.transaction(() => {
147
+ if (this.db.prepare("SELECT id FROM receipts WHERE id=?").get(receipt.jobId))
148
+ return;
149
+ const actual = this.db.prepare("SELECT id FROM observations WHERE jobId=? ORDER BY id").all(receipt.jobId).map(row => Number(row.id));
150
+ if (!actual.length || JSON.stringify(actual) !== JSON.stringify([...receipt.observationIds].sort((a, b) => a - b)))
151
+ throw new Error("Receipt batch mismatch");
152
+ for (const link of receipt.associations ?? [])
153
+ for (const sourceId of link.sourceIds)
154
+ if (!this.db.prepare("SELECT id FROM observations WHERE id=?").get(sourceId))
155
+ throw new Error("Receipt source unknown");
156
+ for (const id of receipt.observationIds) {
157
+ const row = this.db.prepare("SELECT jobId FROM observations WHERE id=?").get(id);
158
+ if (!row || row.jobId !== receipt.jobId)
159
+ throw new Error("Receipt references unknown batch");
160
+ }
161
+ this.#consume(receipt.jobId);
162
+ for (const doc of receipt.documents ?? [])
163
+ this.db.prepare("INSERT INTO document_versions VALUES(?,?) ON CONFLICT(target) DO UPDATE SET hash=excluded.hash").run(doc.target, doc.after);
164
+ for (const target of receipt.removeTargets ?? [])
165
+ this.db.prepare("DELETE FROM associations WHERE target=?").run(target);
166
+ for (const link of receipt.associations ?? [])
167
+ for (const id of link.sourceIds)
168
+ this.db.prepare("INSERT OR IGNORE INTO associations VALUES(?,?)").run(link.target, id);
169
+ for (const id of receipt.forgetSourceIds ?? []) {
170
+ this.db.prepare("UPDATE observations SET text=NULL WHERE id=? AND state='processed'").run(id);
171
+ }
172
+ this.db.prepare("INSERT INTO receipts VALUES(?)").run(receipt.jobId);
173
+ });
174
+ }
175
+ documentVersion(target) { const row = this.db.prepare("SELECT hash FROM document_versions WHERE target=?").get(target); return row ? String(row.hash) : null; }
176
+ documentSourceKeys(target) { return this.db.prepare("SELECT DISTINCT target FROM associations WHERE substr(target,1,?)=?").all(target.length + 1, target + ':').map(row => String(row.target)); }
177
+ sources(target) { return this.db.prepare("SELECT sourceId FROM associations WHERE target=? ORDER BY sourceId").all(target).map(row => Number(row.sourceId)); }
178
+ fail(job, error) { this.transaction(() => { this.assertLease(job); const row = this.db.prepare("SELECT attempts FROM jobs WHERE id=?").get(job.id); const dead = Number(row.attempts) >= this.#options.maxAttempts; this.db.prepare("UPDATE jobs SET state=?,available=?,issue=? WHERE id=?").run(dead ? "dead" : "retry", this.#now() + Math.min(600000, 1000 * 2 ** (Number(row.attempts) - 1)), failureCode(error), job.id); if (dead)
179
+ this.db.prepare("UPDATE observations SET state='dead' WHERE jobId=?").run(job.id); }); }
180
+ quarantine(job, observationId, issue) { this.transaction(() => { this.assertLease(job); if (!job.observations.some(o => o.id === observationId))
181
+ throw new Error("Unknown observation"); this.db.prepare("UPDATE observations SET state='quarantined',jobId=NULL,issue=? WHERE id=?").run(issue, observationId); this.db.prepare("UPDATE observations SET state='pending',jobId=NULL WHERE jobId=?").run(job.id); this.db.prepare("UPDATE jobs SET state='done' WHERE id=?").run(job.id); }); }
182
+ retry(jobId) { this.transaction(() => { this.db.prepare("UPDATE observations SET state='pending',jobId=NULL WHERE jobId=? AND state='dead'").run(jobId); this.db.prepare("UPDATE jobs SET state='done' WHERE id=? AND state='dead'").run(jobId); }); }
183
+ pruneProcessed(retentionMs = 7 * 86400000) { this.db.prepare("UPDATE observations SET text=NULL WHERE state='processed' AND processedAt<?").run(this.#now() - retentionMs); }
184
+ cancelInputs(sessionId) { this.db.prepare("DELETE FROM inputs WHERE sessionId=?").run(sessionId); }
185
+ stageInput(input) {
186
+ return this.transaction(() => {
187
+ const id = randomUUID(), kind = input.streamingBehavior ?? "direct";
188
+ const prior = kind === "direct" ? this.db.prepare("SELECT text FROM inputs WHERE sessionId=? AND queueKind='direct'").get(input.sessionId) : undefined;
189
+ // Direct preflight may be cancelled; queued steer/followUp are legal FIFO lists.
190
+ if (kind === "direct")
191
+ this.db.prepare("DELETE FROM inputs WHERE sessionId=? AND queueKind='direct'").run(input.sessionId);
192
+ let source = input.hasUnsupportedContent ? "unsupported_content" : prior?.text === input.text ? "ambiguous" : input.source;
193
+ const competing = this.db.prepare("SELECT source,scope FROM inputs WHERE sessionId=?").all(input.sessionId);
194
+ if (competing.some(row => row.source !== source || row.scope !== input.scope)) {
195
+ source = "ambiguous";
196
+ this.db.prepare("UPDATE inputs SET source='ambiguous' WHERE sessionId=?").run(input.sessionId);
197
+ }
198
+ this.db.prepare("INSERT INTO inputs(id,sessionId,text,source,scope,queueKind,parentEntryId) VALUES(?,?,?,?,?,?,?)").run(id, input.sessionId, input.text, source, input.scope, kind, input.parentEntryId ?? null);
199
+ return id;
200
+ });
201
+ }
202
+ delivered(sessionId, text, timestamp, hasUnsupportedContent = false) {
203
+ const digest = createHash("sha256").update(text).digest("hex");
204
+ this.transaction(() => {
205
+ if (this.db.prepare("SELECT id FROM deliveries WHERE sessionId=? AND digest=? AND timestamp=?").get(sessionId, digest, timestamp))
206
+ return;
207
+ // Pi drains direct delivery, steering, then follow-up queues. Never search old
208
+ // inputs by text: a cancelled global input must not authenticate project text.
209
+ const expected = this.db.prepare("SELECT * FROM inputs WHERE sessionId=? ORDER BY CASE queueKind WHEN 'direct' THEN 0 WHEN 'steer' THEN 1 ELSE 2 END, rowid LIMIT 1").get(sessionId);
210
+ const matches = expected?.text === text;
211
+ const source = hasUnsupportedContent ? "unsupported_content" : matches ? String(expected.source) : "ambiguous";
212
+ this.db.prepare("INSERT OR IGNORE INTO deliveries(sessionId,text,digest,timestamp,scope,source,state) VALUES(?,?,?,?,?,?,'unbound')").run(sessionId, text, digest, timestamp, expected?.scope ?? "global", source);
213
+ if (expected)
214
+ this.db.prepare("DELETE FROM inputs WHERE id=?").run(expected.id);
215
+ });
216
+ }
217
+ bind(sessionId, entries) { this.transaction(() => { const deliveries = this.db.prepare("SELECT * FROM deliveries WHERE sessionId=? AND state='unbound' ORDER BY id").all(sessionId); for (const delivery of deliveries) {
218
+ const matches = entries.filter(e => e.text === delivery.text && e.timestamp === delivery.timestamp);
219
+ if (matches.length > 1) {
220
+ this.db.prepare("UPDATE deliveries SET state='quarantined' WHERE id=?").run(delivery.id);
221
+ continue;
222
+ }
223
+ if (matches.length === 0)
224
+ continue;
225
+ this.enqueue({ sessionId, entryId: matches[0].id, text: String(delivery.text), scope: String(delivery.scope), source: String(delivery.source), observedAt: new Date(Number(delivery.timestamp)).toISOString() });
226
+ this.db.prepare("UPDATE deliveries SET state='bound',text='' WHERE id=?").run(delivery.id);
227
+ } }); }
228
+ status() { return { observations: this.db.prepare("SELECT state,COUNT(*) AS count FROM observations GROUP BY state").all(), jobs: this.db.prepare("SELECT id,state,attempts,issue FROM jobs ORDER BY rowid").all(), quarantinedDeliveries: Number(this.db.prepare("SELECT COUNT(*) AS n FROM deliveries WHERE state='quarantined'").get().n), unbound: Number(this.db.prepare("SELECT COUNT(*) AS n FROM deliveries WHERE state='unbound'").get().n) }; }
229
+ close() { this.db.close(); }
230
+ }
231
+ //# sourceMappingURL=runtime.js.map