@xopcai/xopc 0.0.140 → 0.0.142

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 (250) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DtONoxBN.js → agent-avatar-dicebear-BawPxiu5.js} +1 -1
  4. package/dist/gateway/static/root/assets/agents-CyvCIHnB.js +2 -0
  5. package/dist/gateway/static/root/assets/{app-management-settings-panel-BRBxLECY.js → app-management-settings-panel-81xd1p9R.js} +1 -1
  6. package/dist/gateway/static/root/assets/{appearance-settings-ChvyuSEt.js → appearance-settings-DYeQsYU8.js} +1 -1
  7. package/dist/gateway/static/root/assets/{apps-page-DgHWTnjs.js → apps-page-DK_Vx6nl.js} +1 -1
  8. package/dist/gateway/static/root/assets/{archive-plugin-CdIoagem.js → archive-plugin-D_oa7oti.js} +1 -1
  9. package/dist/gateway/static/root/assets/{attachment-load-Ch411Kht.js → attachment-load-Cd-bwI2N.js} +1 -1
  10. package/dist/gateway/static/root/assets/{automation-suggestion-card-SVq27yw2.js → automation-suggestion-card-B1LV3YYv.js} +1 -1
  11. package/dist/gateway/static/root/assets/automations-page-fzrovqhv.js +4 -0
  12. package/dist/gateway/static/root/assets/{binary-plugins-D8cV-emn.js → binary-plugins-O8svLKWw.js} +1 -1
  13. package/dist/gateway/static/root/assets/{block-editor-CXDkby6y.js → block-editor-Dhj5f4Bx.js} +47 -47
  14. package/dist/gateway/static/root/assets/browser-settings-page-BDn-n-UB.js +8 -0
  15. package/dist/gateway/static/root/assets/{capability-presets-api-DLxI--Ge.js → capability-presets-api-dJy1Eqgx.js} +1 -1
  16. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-DKyRbnlH.js → capability-presets-settings-panel-Co5ipo_Q.js} +1 -1
  17. package/dist/gateway/static/root/assets/{channel-recipient-api-DllrPmp-.js → channel-recipient-api-Bfuhp2ty.js} +1 -1
  18. package/dist/gateway/static/root/assets/channels-settings-BWlpaxg9.js +1 -0
  19. package/dist/gateway/static/root/assets/channels-status-swr-D_jkIZLV.js +1 -0
  20. package/dist/gateway/static/root/assets/connectors-page-BcekbW4q.js +2 -0
  21. package/dist/gateway/static/root/assets/{desktop-pet-Bz9ommjr.js → desktop-pet-DFMfIb5F.js} +1 -1
  22. package/dist/gateway/static/root/assets/{desktop-pet-settings-BnLgxgoR.js → desktop-pet-settings-BJCUuL8Y.js} +1 -1
  23. package/dist/gateway/static/root/assets/{directory-picker-path-field-DLsCwFaf.js → directory-picker-path-field-BrYt1gAr.js} +1 -1
  24. package/dist/gateway/static/root/assets/{dreaming-settings-BzkF1-XT.js → dreaming-settings-BN1Z2mIo.js} +3 -3
  25. package/dist/gateway/static/root/assets/{extension-debug-page-CdERvf3L.js → extension-debug-page-Bh630pLD.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-D1fz474Z.js → extension-page-B-1KxvVy.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-settings-page-DoQMkNOK.js → extension-settings-page-BF9NACI3.js} +1 -1
  28. package/dist/gateway/static/root/assets/{fetch-BID89uCH.js → fetch-B1WtuprY.js} +1 -1
  29. package/dist/gateway/static/root/assets/{gateway-config-api-Rygo8UAs.js → gateway-config-api-De2N821l.js} +1 -1
  30. package/dist/gateway/static/root/assets/{gateway-settings-kb8KIeQL.js → gateway-settings-DN9sNREz.js} +1 -1
  31. package/dist/gateway/static/root/assets/{gateway-startup-retry-BkrLLlpE.js → gateway-startup-retry-nEujFSDj.js} +1 -1
  32. package/dist/gateway/static/root/assets/goal-create-dialog-BpoQW8cD.js +1 -0
  33. package/dist/gateway/static/root/assets/goal-detail-page-CBRSPd0b.js +4 -0
  34. package/dist/gateway/static/root/assets/{goals-config-api-BuY6-l0a.js → goals-config-api-DUy3eEse.js} +1 -1
  35. package/dist/gateway/static/root/assets/goals-page-BCcBi407.js +2 -0
  36. package/dist/gateway/static/root/assets/{goals-settings-DoH5fZWo.js → goals-settings-pkTXzXkh.js} +1 -1
  37. package/dist/gateway/static/root/assets/{heartbeat-settings-BqDs48B-.js → heartbeat-settings-ezsufgLQ.js} +1 -1
  38. package/dist/gateway/static/root/assets/{index-CrPRlGMg.js → index-DcUaXYeb.js} +65 -65
  39. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-CVIwwM15.js +1 -0
  40. package/dist/gateway/static/root/assets/logs-page-DQtTRXtj.js +7 -0
  41. package/dist/gateway/static/root/assets/{markdown-split-CSt32FWE.js → markdown-split-B_6us9j7.js} +1 -1
  42. package/dist/gateway/static/root/assets/{media-plugins-ByItHxBj.js → media-plugins-D_fryIyj.js} +1 -1
  43. package/dist/gateway/static/root/assets/messages-BXNELNBa.js +3 -0
  44. package/dist/gateway/static/root/assets/models-hub-panel-BP2M9n6Q.js +2 -0
  45. package/dist/gateway/static/root/assets/notes-page-CspveYaO.js +1 -0
  46. package/dist/gateway/static/root/assets/notes-workbench-BHwzJ0OC.js +2 -0
  47. package/dist/gateway/static/root/assets/{page-tabs-DOfMaq_L.js → page-tabs-D_8jGlqx.js} +1 -1
  48. package/dist/gateway/static/root/assets/{preview-open-alternatives-BArxASu7.js → preview-open-alternatives-BBLMK8Mp.js} +1 -1
  49. package/dist/gateway/static/root/assets/project-detail-page-Dy3rsobs.js +1 -0
  50. package/dist/gateway/static/root/assets/projects-page-CWKpPE0L.js +1 -0
  51. package/dist/gateway/static/root/assets/remote-access-hub-CqNRyXEo.js +1 -0
  52. package/dist/gateway/static/root/assets/{save-bar-controls-BRxlkhG8.js → save-bar-controls-DVS3FR5r.js} +1 -1
  53. package/dist/gateway/static/root/assets/{schedule-field-BzM1iyOc.js → schedule-field-D6vKd6-T.js} +1 -1
  54. package/dist/gateway/static/root/assets/{schema-form-ChVcm0DX.js → schema-form-BPy4lbIh.js} +1 -1
  55. package/dist/gateway/static/root/assets/sessions-page-MEME7ivF.js +1 -0
  56. package/dist/gateway/static/root/assets/settings-advanced-gate-JVd0qaSF.js +1 -0
  57. package/dist/gateway/static/root/assets/{settings-collapsible-section-BcD9xrvY.js → settings-collapsible-section-WzdikxPt.js} +1 -1
  58. package/dist/gateway/static/root/assets/{settings-form-section-Cu30CXVl.js → settings-form-section-CvfzhBV-.js} +1 -1
  59. package/dist/gateway/static/root/assets/{settings-loading-skeleton-Cfd37IYb.js → settings-loading-skeleton-BxR-Ulhi.js} +1 -1
  60. package/dist/gateway/static/root/assets/{settings-page-EINHyzLo.js → settings-page-DYEbB1EV.js} +1 -1
  61. package/dist/gateway/static/root/assets/{settings-page-layout-C15zixct.js → settings-page-layout-CKefvbYi.js} +1 -1
  62. package/dist/gateway/static/root/assets/{setup-status-panel-Bu7SdpD4.js → setup-status-panel-dWN8BJ3z.js} +1 -1
  63. package/dist/gateway/static/root/assets/{share-preview-page-CjjAFV-v.js → share-preview-page-D8foINzP.js} +1 -1
  64. package/dist/gateway/static/root/assets/shares-settings-Tgbv1s7G.js +1 -0
  65. package/dist/gateway/static/root/assets/skill-reload-api-D_YK0b_Z.js +1 -0
  66. package/dist/gateway/static/root/assets/skills-page-DGytfoZK.js +2 -0
  67. package/dist/gateway/static/root/assets/{system-settings-panel-DlQMtzPf.js → system-settings-panel-BokgAgf7.js} +1 -1
  68. package/dist/gateway/static/root/assets/{typed-models-lib-BS1BwExE.js → typed-models-lib-BwHlNL3L.js} +1 -1
  69. package/dist/gateway/static/root/assets/{use-channel-catalog-EA4hfbsP.js → use-channel-catalog-DR11ap3A.js} +1 -1
  70. package/dist/gateway/static/root/assets/{user-profile-settings-panel-B7RCdmSp.js → user-profile-settings-panel-8YtygcJp.js} +1 -1
  71. package/dist/gateway/static/root/assets/{utils-BrhEgVaa.js → utils-BKUWB-TT.js} +1 -1
  72. package/dist/gateway/static/root/assets/voice-api-key-field-BVH71EFf.js +1 -0
  73. package/dist/gateway/static/root/assets/work-item-detail-page-JeTE7XNM.js +1 -0
  74. package/dist/gateway/static/root/assets/workflow-run-setup-panel-CL_rnAUB.js +3 -0
  75. package/dist/gateway/static/root/assets/{workflow-start-dialog-BZCY7N42.js → workflow-start-dialog-_u8Uc1ze.js} +1 -1
  76. package/dist/gateway/static/root/assets/workflows-page-ixgk_y1l.js +18 -0
  77. package/dist/gateway/static/root/index.html +4 -4
  78. package/dist/package.js +1 -1
  79. package/dist/src/agent/agent-manager.js +13 -21
  80. package/dist/src/agent/agent-manager.js.map +1 -1
  81. package/dist/src/agent/background-review/run-background-review.js +1 -0
  82. package/dist/src/agent/background-review/run-background-review.js.map +1 -1
  83. package/dist/src/agent/embedded/session-runner.js +5 -5
  84. package/dist/src/agent/embedded/session-runner.js.map +1 -1
  85. package/dist/src/agent/embedded/xopc-auth-storage.d.ts +4 -3
  86. package/dist/src/agent/embedded/xopc-auth-storage.js +6 -6
  87. package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
  88. package/dist/src/agent/embedded/xopc-stream-bridge.js +1 -0
  89. package/dist/src/agent/embedded/xopc-stream-bridge.js.map +1 -1
  90. package/dist/src/agent/goals/checklist-judge.js +3 -3
  91. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  92. package/dist/src/agent/goals/judge.js +2 -2
  93. package/dist/src/agent/goals/judge.js.map +1 -1
  94. package/dist/src/agent/image/understanding/pi-ai-provider.js +2 -2
  95. package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
  96. package/dist/src/agent/memory/access-policy.d.ts +12 -0
  97. package/dist/src/agent/memory/access-policy.js +44 -0
  98. package/dist/src/agent/memory/access-policy.js.map +1 -0
  99. package/dist/src/agent/memory/builtin-provider.d.ts +4 -1
  100. package/dist/src/agent/memory/builtin-provider.js +51 -16
  101. package/dist/src/agent/memory/builtin-provider.js.map +1 -1
  102. package/dist/src/agent/memory/compaction.js +2 -2
  103. package/dist/src/agent/memory/compaction.js.map +1 -1
  104. package/dist/src/agent/memory/context/planner.d.ts +1 -0
  105. package/dist/src/agent/memory/context/planner.js +9 -5
  106. package/dist/src/agent/memory/context/planner.js.map +1 -1
  107. package/dist/src/agent/memory/create-memory-manager.d.ts +1 -1
  108. package/dist/src/agent/memory/create-memory-manager.js +8 -5
  109. package/dist/src/agent/memory/create-memory-manager.js.map +1 -1
  110. package/dist/src/agent/memory/dreaming/config.d.ts +2 -0
  111. package/dist/src/agent/memory/dreaming/config.js +13 -1
  112. package/dist/src/agent/memory/dreaming/config.js.map +1 -1
  113. package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +2 -0
  114. package/dist/src/agent/memory/dreaming/deep-promotion.js +24 -3
  115. package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -1
  116. package/dist/src/agent/memory/dreaming/light-sweep.d.ts +1 -0
  117. package/dist/src/agent/memory/dreaming/light-sweep.js +184 -16
  118. package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -1
  119. package/dist/src/agent/memory/dreaming/promotion-lifecycle.d.ts +30 -0
  120. package/dist/src/agent/memory/dreaming/promotion-lifecycle.js +85 -0
  121. package/dist/src/agent/memory/dreaming/promotion-lifecycle.js.map +1 -0
  122. package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +4 -0
  123. package/dist/src/agent/memory/dreaming/rem-patterns.js +76 -53
  124. package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -1
  125. package/dist/src/agent/memory/dreaming/short-term-store.d.ts +10 -0
  126. package/dist/src/agent/memory/dreaming/short-term-store.js +157 -79
  127. package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
  128. package/dist/src/agent/memory/manager.d.ts +7 -0
  129. package/dist/src/agent/memory/manager.js +31 -6
  130. package/dist/src/agent/memory/manager.js.map +1 -1
  131. package/dist/src/agent/memory/memory-config.d.ts +1 -1
  132. package/dist/src/agent/memory/memory-config.js +3 -3
  133. package/dist/src/agent/memory/memory-config.js.map +1 -1
  134. package/dist/src/agent/memory/sensitivity.d.ts +3 -0
  135. package/dist/src/agent/memory/sensitivity.js +13 -0
  136. package/dist/src/agent/memory/sensitivity.js.map +1 -0
  137. package/dist/src/agent/memory/understanding/service.js +10 -14
  138. package/dist/src/agent/memory/understanding/service.js.map +1 -1
  139. package/dist/src/agent/memory/user-context-coordinator.d.ts +1 -0
  140. package/dist/src/agent/memory/user-context-coordinator.js +2 -0
  141. package/dist/src/agent/memory/user-context-coordinator.js.map +1 -1
  142. package/dist/src/agent/orchestration/agent-orchestrator.js +8 -1
  143. package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
  144. package/dist/src/agent/service/btw-query.d.ts +4 -0
  145. package/dist/src/agent/service/btw-query.js +94 -13
  146. package/dist/src/agent/service/btw-query.js.map +1 -1
  147. package/dist/src/agent/session/session-inspector.js +9 -1
  148. package/dist/src/agent/session/session-inspector.js.map +1 -1
  149. package/dist/src/agent/tools/dreaming-tool.js +3 -12
  150. package/dist/src/agent/tools/dreaming-tool.js.map +1 -1
  151. package/dist/src/agent/tools/factory.js +14 -2
  152. package/dist/src/agent/tools/factory.js.map +1 -1
  153. package/dist/src/agent/tools/memory-tool.d.ts +3 -0
  154. package/dist/src/agent/tools/memory-tool.js +6 -3
  155. package/dist/src/agent/tools/memory-tool.js.map +1 -1
  156. package/dist/src/agent/tools/session-search-tool.js +2 -2
  157. package/dist/src/agent/tools/session-search-tool.js.map +1 -1
  158. package/dist/src/agent/tools/web-extract.js +2 -2
  159. package/dist/src/agent/tools/web-extract.js.map +1 -1
  160. package/dist/src/agent/workspace-runtime/registry.d.ts +6 -6
  161. package/dist/src/agent/workspace-runtime/registry.js +19 -12
  162. package/dist/src/agent/workspace-runtime/registry.js.map +1 -1
  163. package/dist/src/agent-manifest/schema.d.ts +4 -0
  164. package/dist/src/agent-manifest/schema.js +2 -1
  165. package/dist/src/agent-manifest/schema.js.map +1 -1
  166. package/dist/src/agent-runtime/index.js +1 -1
  167. package/dist/src/agent-runtime/runtime-profile.js +1 -1
  168. package/dist/src/ai-assist/text-assist.d.ts +2 -1
  169. package/dist/src/ai-assist/text-assist.js +10 -23
  170. package/dist/src/ai-assist/text-assist.js.map +1 -1
  171. package/dist/src/auth/credentials.js +5 -2
  172. package/dist/src/auth/credentials.js.map +1 -1
  173. package/dist/src/auth/oauth/github-copilot.d.ts +3 -4
  174. package/dist/src/auth/oauth/github-copilot.js +66 -2
  175. package/dist/src/auth/oauth/github-copilot.js.map +1 -0
  176. package/dist/src/auth/oauth/index.js +2 -2
  177. package/dist/src/automations/draft/automation-draft-service.js +3 -3
  178. package/dist/src/automations/draft/automation-draft-service.js.map +1 -1
  179. package/dist/src/chat-commands/builtins/review.js +22 -5
  180. package/dist/src/chat-commands/builtins/review.js.map +1 -1
  181. package/dist/src/chat-commands/types.d.ts +4 -0
  182. package/dist/src/config/schema.d.ts +4 -0
  183. package/dist/src/gateway/hono/lib/config-payload.d.ts +2 -0
  184. package/dist/src/gateway/hono/routes/dreaming.js +28 -21
  185. package/dist/src/gateway/hono/routes/dreaming.js.map +1 -1
  186. package/dist/src/gateway/hono/routes/memory.js +16 -4
  187. package/dist/src/gateway/hono/routes/memory.js.map +1 -1
  188. package/dist/src/gateway/hono/routes/models.js +1 -0
  189. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  190. package/dist/src/gateway/hono/routes/voice.js +2 -2
  191. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  192. package/dist/src/goals/goal-contract-draft.js +2 -2
  193. package/dist/src/goals/goal-contract-draft.js.map +1 -1
  194. package/dist/src/goals/goal-evidence-review.js +2 -2
  195. package/dist/src/goals/goal-evidence-review.js.map +1 -1
  196. package/dist/src/notes/service.js +2 -2
  197. package/dist/src/notes/service.js.map +1 -1
  198. package/dist/src/providers/constants.d.ts +2 -0
  199. package/dist/src/providers/constants.js +11 -0
  200. package/dist/src/providers/constants.js.map +1 -0
  201. package/dist/src/providers/extension-stream-bridge.js +1 -0
  202. package/dist/src/providers/extension-stream-bridge.js.map +1 -1
  203. package/dist/src/providers/index.d.ts +3 -3
  204. package/dist/src/providers/index.js +5 -4
  205. package/dist/src/providers/index.js.map +1 -1
  206. package/dist/src/providers/model-call.d.ts +17 -0
  207. package/dist/src/providers/model-call.js +60 -0
  208. package/dist/src/providers/model-call.js.map +1 -0
  209. package/dist/src/session/session-title.js +2 -2
  210. package/dist/src/session/session-title.js.map +1 -1
  211. package/dist/src/storage/sqlite/fts.d.ts +6 -0
  212. package/dist/src/storage/sqlite/fts.js +70 -1
  213. package/dist/src/storage/sqlite/fts.js.map +1 -1
  214. package/dist/src/storage/sqlite/memory-index-repository.js +16 -7
  215. package/dist/src/storage/sqlite/memory-index-repository.js.map +1 -1
  216. package/dist/src/storage/sqlite/memory-records-repository.d.ts +18 -0
  217. package/dist/src/storage/sqlite/memory-records-repository.js +55 -8
  218. package/dist/src/storage/sqlite/memory-records-repository.js.map +1 -1
  219. package/dist/src/voice/tts/summarize.js +2 -2
  220. package/dist/src/voice/tts/summarize.js.map +1 -1
  221. package/dist/src/workflows/draft/workflow-draft-service.js +2 -2
  222. package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -1
  223. package/package.json +5 -5
  224. package/dist/gateway/static/root/assets/agents-Dl9EWnUh.js +0 -2
  225. package/dist/gateway/static/root/assets/automations-page-CFt5Ik4q.js +0 -4
  226. package/dist/gateway/static/root/assets/browser-settings-page-ColJPwer.js +0 -8
  227. package/dist/gateway/static/root/assets/channels-settings-BDLHV3Jx.js +0 -1
  228. package/dist/gateway/static/root/assets/channels-status-swr-EUwWYx8l.js +0 -1
  229. package/dist/gateway/static/root/assets/connectors-page-CA752gY8.js +0 -2
  230. package/dist/gateway/static/root/assets/goal-create-dialog-DVAHnifk.js +0 -1
  231. package/dist/gateway/static/root/assets/goal-detail-page-BuOEQZn_.js +0 -4
  232. package/dist/gateway/static/root/assets/goals-page-WV4cHvD-.js +0 -2
  233. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-D51yogNZ.js +0 -1
  234. package/dist/gateway/static/root/assets/logs-page-CL22jleU.js +0 -7
  235. package/dist/gateway/static/root/assets/messages-DbNQuFdA.js +0 -3
  236. package/dist/gateway/static/root/assets/models-hub-panel-BkV9hDc-.js +0 -2
  237. package/dist/gateway/static/root/assets/notes-page-BK9yydrL.js +0 -1
  238. package/dist/gateway/static/root/assets/notes-workbench-Dd0v-hK2.js +0 -2
  239. package/dist/gateway/static/root/assets/project-detail-page-DSPhB3do.js +0 -1
  240. package/dist/gateway/static/root/assets/projects-page-C-fZk1f7.js +0 -1
  241. package/dist/gateway/static/root/assets/remote-access-hub-BGaVc3t7.js +0 -1
  242. package/dist/gateway/static/root/assets/sessions-page-DbnzgQOP.js +0 -1
  243. package/dist/gateway/static/root/assets/settings-advanced-gate-CuDBoz5k.js +0 -1
  244. package/dist/gateway/static/root/assets/shares-settings-06IYvwDt.js +0 -1
  245. package/dist/gateway/static/root/assets/skill-reload-api-0ODH0DXl.js +0 -1
  246. package/dist/gateway/static/root/assets/skills-page-3W2-wDyO.js +0 -2
  247. package/dist/gateway/static/root/assets/voice-api-key-field-BPn1ZEga.js +0 -1
  248. package/dist/gateway/static/root/assets/work-item-detail-page-BQGTTsz1.js +0 -1
  249. package/dist/gateway/static/root/assets/workflow-run-setup-panel-na-N4jZ6.js +0 -3
  250. package/dist/gateway/static/root/assets/workflows-page-CjeVDs4h.js +0 -18
@@ -2,8 +2,10 @@ import { createLogger } from "../../../utils/logger/index.js";
2
2
  import { init_logger } from "../../../utils/logger.js";
3
3
  import { DREAMING_DIR_RELATIVE, DREAMS_MD_FILENAME, MS_PER_DAY } from "./constants.js";
4
4
  import { isoDay } from "./utils.js";
5
- import { bumpEntryPhaseSignal, loadDreamingStore, saveDreamingStore } from "./short-term-store.js";
5
+ import { bumpEntryPhaseSignal, loadDreamingStore, saveDreamingStore, withDreamingStoreLock } from "./short-term-store.js";
6
+ import { inferMemorySensitivity } from "../sensitivity.js";
6
7
  import "./last-run.js";
8
+ import { activateRemInsight, remPatternKey } from "./promotion-lifecycle.js";
7
9
  import path from "node:path";
8
10
  import fs from "node:fs/promises";
9
11
  //#region src/agent/memory/dreaming/rem-patterns.ts
@@ -56,71 +58,89 @@ async function runRemPatterns(params) {
56
58
  };
57
59
  }
58
60
  try {
59
- const { store } = await loadDreamingStore({ dreamingRoot: params.dreamingRoot });
60
- const cutoffMs = nowMs - cfg.lookbackDays * MS_PER_DAY;
61
- const recentEntries = Object.values(store.entries ?? {}).filter((entry) => {
62
- if (!entry || typeof entry !== "object") return false;
63
- if (!entry.lastRecalledAt) return false;
64
- const lastMs = Date.parse(entry.lastRecalledAt);
65
- return Number.isFinite(lastMs) && lastMs >= cutoffMs;
66
- });
67
- if (recentEntries.length < 2) {
61
+ return await withDreamingStoreLock(params.dreamingRoot, async () => {
62
+ const { store } = await loadDreamingStore({ dreamingRoot: params.dreamingRoot });
63
+ const cutoffMs = nowMs - cfg.lookbackDays * MS_PER_DAY;
64
+ const recentEntries = Object.values(store.entries ?? {}).filter((entry) => {
65
+ if (!entry || typeof entry !== "object") return false;
66
+ if (!entry.lastRecalledAt) return false;
67
+ const lastMs = Date.parse(entry.lastRecalledAt);
68
+ return Number.isFinite(lastMs) && lastMs >= cutoffMs;
69
+ });
70
+ if (recentEntries.length < 2) {
71
+ await writeLastRun(params.dreamingRoot, {
72
+ runId,
73
+ startedAt,
74
+ cfg,
75
+ ok: true,
76
+ reason: "not enough recent entries for pattern analysis",
77
+ startMs,
78
+ rem: {
79
+ patternsDiscovered: 0,
80
+ entriesAnalyzed: recentEntries.length
81
+ }
82
+ });
83
+ return {
84
+ ok: true,
85
+ reason: "not enough recent entries for pattern analysis",
86
+ patternsDiscovered: 0,
87
+ entriesAnalyzed: recentEntries.length
88
+ };
89
+ }
90
+ const topClusters = discoverPatternClusters(recentEntries, cfg.minPatternStrength).filter((cluster) => {
91
+ return inferMemorySensitivity(cluster.members.map((member) => member.snippet).join("\n")) === "normal" || params.sensitiveWritePolicy === "allow";
92
+ }).slice(0, cfg.limit);
93
+ const touchedKeys = /* @__PURE__ */ new Set();
94
+ for (const cluster of topClusters) for (const member of cluster.members) if (!touchedKeys.has(member.key)) {
95
+ touchedKeys.add(member.key);
96
+ const storeEntry = store.entries[member.key];
97
+ if (storeEntry) bumpEntryPhaseSignal(storeEntry, "remHits");
98
+ }
99
+ store.updatedAt = now.toISOString();
100
+ await saveDreamingStore({
101
+ dreamingRoot: params.dreamingRoot,
102
+ store
103
+ });
104
+ if (params.promotionWritePolicy === "allow") for (const cluster of topClusters) {
105
+ const sensitivity = inferMemorySensitivity(cluster.members.map((member) => member.snippet).join("\n"));
106
+ activateRemInsight({
107
+ agentId: params.agentId,
108
+ workspaceId: params.workspaceDir,
109
+ memberKeys: cluster.members.map((member) => member.key),
110
+ representative: cluster.representative.snippet,
111
+ distinctPaths: cluster.distinctPaths,
112
+ strength: cluster.strength,
113
+ observedAt: now.toISOString(),
114
+ evidence: cluster.members.map((member) => member.snippet),
115
+ sensitivity
116
+ });
117
+ }
118
+ if (topClusters.length > 0) await appendPatternSummary(params.dreamingRoot, topClusters, now);
119
+ log.info({
120
+ dreamingRoot: params.dreamingRoot,
121
+ patterns: topClusters.length,
122
+ entriesAnalyzed: recentEntries.length,
123
+ touched: touchedKeys.size
124
+ }, "REM pattern discovery complete");
68
125
  await writeLastRun(params.dreamingRoot, {
69
126
  runId,
70
127
  startedAt,
71
128
  cfg,
72
129
  ok: true,
73
- reason: "not enough recent entries for pattern analysis",
130
+ reason: "REM patterns complete",
74
131
  startMs,
75
132
  rem: {
76
- patternsDiscovered: 0,
133
+ patternsDiscovered: topClusters.length,
77
134
  entriesAnalyzed: recentEntries.length
78
135
  }
79
136
  });
80
137
  return {
81
138
  ok: true,
82
- reason: "not enough recent entries for pattern analysis",
83
- patternsDiscovered: 0,
84
- entriesAnalyzed: recentEntries.length
85
- };
86
- }
87
- const topClusters = discoverPatternClusters(recentEntries, cfg.minPatternStrength).slice(0, cfg.limit);
88
- const touchedKeys = /* @__PURE__ */ new Set();
89
- for (const cluster of topClusters) for (const member of cluster.members) if (!touchedKeys.has(member.key)) {
90
- touchedKeys.add(member.key);
91
- const storeEntry = store.entries[member.key];
92
- if (storeEntry) bumpEntryPhaseSignal(storeEntry, "remHits");
93
- }
94
- store.updatedAt = now.toISOString();
95
- await saveDreamingStore({
96
- dreamingRoot: params.dreamingRoot,
97
- store
98
- });
99
- if (topClusters.length > 0) await appendPatternSummary(params.dreamingRoot, topClusters, now);
100
- log.info({
101
- dreamingRoot: params.dreamingRoot,
102
- patterns: topClusters.length,
103
- entriesAnalyzed: recentEntries.length,
104
- touched: touchedKeys.size
105
- }, "REM pattern discovery complete");
106
- await writeLastRun(params.dreamingRoot, {
107
- runId,
108
- startedAt,
109
- cfg,
110
- ok: true,
111
- reason: "REM patterns complete",
112
- startMs,
113
- rem: {
139
+ reason: "REM patterns complete",
114
140
  patternsDiscovered: topClusters.length,
115
141
  entriesAnalyzed: recentEntries.length
116
- }
142
+ };
117
143
  });
118
- return {
119
- ok: true,
120
- reason: "REM patterns complete",
121
- patternsDiscovered: topClusters.length,
122
- entriesAnalyzed: recentEntries.length
123
- };
124
144
  } catch (err) {
125
145
  const errorMessage = err instanceof Error ? err.message : String(err);
126
146
  log.error({
@@ -238,11 +258,14 @@ async function appendPatternSummary(dreamingRoot, clusters, now) {
238
258
  });
239
259
  const day = isoDay(now);
240
260
  const lines = [];
261
+ const newClusters = clusters.filter((cluster) => !existing.includes(`xopc-rem-pattern id="${remPatternKey(cluster.members.map((member) => member.key))}"`));
262
+ if (newClusters.length === 0) return;
241
263
  if (existing.trim().length === 0) lines.push("# Dream Diary", "");
242
264
  lines.push(`## REM Pattern Discovery — ${day}`, "");
243
265
  lines.push(`*${now.toISOString()}*`, "");
244
- for (let i = 0; i < clusters.length; i++) {
245
- const cluster = clusters[i];
266
+ for (let i = 0; i < newClusters.length; i++) {
267
+ const cluster = newClusters[i];
268
+ lines.push(`<!-- xopc-rem-pattern id="${remPatternKey(cluster.members.map((member) => member.key))}" -->`);
246
269
  lines.push(`### Pattern ${i + 1}: ${cluster.distinctPaths.length} files, strength=${cluster.strength.toFixed(2)}`);
247
270
  lines.push("");
248
271
  lines.push(`**Files involved:** ${cluster.distinctPaths.join(", ")}`);
@@ -1 +1 @@
1
- {"version":3,"file":"rem-patterns.js","names":[],"sources":["../../../../../src/agent/memory/dreaming/rem-patterns.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { DREAMING_DIR_RELATIVE, DREAMS_MD_FILENAME, MS_PER_DAY } from './constants.js';\nimport type { DreamingRemConfig } from './config.js';\nimport {\n bumpEntryPhaseSignal,\n loadDreamingStore,\n saveDreamingStore,\n type DreamingStoreEntry,\n} from './short-term-store.js';\nimport { isoDay } from './utils.js';\nimport {\n DREAMING_LAST_RUN_FORMAT_VERSION,\n type DreamingRemLastRun,\n} from './last-run.js';\n\nconst log = createLogger('Dreaming:REM');\n\n// ── Pattern types ──────────────────────────────────────────────────────\n\ntype PatternCluster = {\n representative: DreamingStoreEntry;\n members: DreamingStoreEntry[];\n strength: number;\n /** Shared query hashes across members. */\n sharedQueries: string[];\n /** Distinct source files involved. */\n distinctPaths: string[];\n};\n\n// ── Config defaults ────────────────────────────────────────────────────\n\nfunction resolveConfig(overrides?: Partial<DreamingRemConfig>): DreamingRemConfig {\n return {\n enabled: overrides?.enabled === true,\n cron: typeof overrides?.cron === 'string' ? overrides.cron : '0 5 * * 0',\n lookbackDays: Math.max(1, Math.floor(Number(overrides?.lookbackDays) || 7)),\n limit: Math.max(0, Math.floor(Number(overrides?.limit) || 10)),\n minPatternStrength: Math.max(0, Math.min(1, Number(overrides?.minPatternStrength) || 0.75)),\n };\n}\n\n// ── Core REM pattern discovery ─────────────────────────────────────────\n\n/**\n * REM phase: cross-session pattern discovery.\n *\n * Scans the short-term store for entries that share query hashes or\n * appear across multiple indexed memory sources, then clusters them to identify\n * recurring themes/patterns. Bumps `remHits` on touched entries and\n * optionally writes a pattern summary to DREAMS.md.\n *\n * Runs weekly; expensive but insightful.\n */\nexport async function runRemPatterns(params: {\n dreamingRoot: string;\n config?: Partial<DreamingRemConfig>;\n now?: Date;\n}): Promise<{\n ok: boolean;\n reason: string;\n patternsDiscovered: number;\n entriesAnalyzed: number;\n}> {\n const cfg = resolveConfig(params.config);\n const now = params.now ?? new Date();\n const startedAt = now.toISOString();\n const runId = `rem:${startedAt}:${process.pid}`;\n const startMs = Date.now();\n const nowMs = now.getTime();\n\n if (!cfg.enabled) {\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: true, reason: 'REM patterns disabled', startMs,\n rem: { patternsDiscovered: 0, entriesAnalyzed: 0 },\n });\n return { ok: true, reason: 'REM patterns disabled', patternsDiscovered: 0, entriesAnalyzed: 0 };\n }\n\n try {\n const { store } = await loadDreamingStore({ dreamingRoot: params.dreamingRoot });\n\n // Filter entries within the lookback window.\n const cutoffMs = nowMs - cfg.lookbackDays * MS_PER_DAY;\n const recentEntries = Object.values(store.entries ?? {}).filter(\n (entry): entry is DreamingStoreEntry => {\n if (!entry || typeof entry !== 'object') return false;\n if (!entry.lastRecalledAt) return false;\n const lastMs = Date.parse(entry.lastRecalledAt);\n return Number.isFinite(lastMs) && lastMs >= cutoffMs;\n },\n );\n\n if (recentEntries.length < 2) {\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: true, reason: 'not enough recent entries for pattern analysis', startMs,\n rem: { patternsDiscovered: 0, entriesAnalyzed: recentEntries.length },\n });\n return {\n ok: true,\n reason: 'not enough recent entries for pattern analysis',\n patternsDiscovered: 0,\n entriesAnalyzed: recentEntries.length,\n };\n }\n\n // Discover patterns by clustering entries that share query hashes.\n const clusters = discoverPatternClusters(recentEntries, cfg.minPatternStrength);\n const topClusters = clusters.slice(0, cfg.limit);\n\n // Bump remHits on all entries that belong to a discovered pattern.\n const touchedKeys = new Set<string>();\n for (const cluster of topClusters) {\n for (const member of cluster.members) {\n if (!touchedKeys.has(member.key)) {\n touchedKeys.add(member.key);\n const storeEntry = store.entries[member.key];\n if (storeEntry) {\n bumpEntryPhaseSignal(storeEntry, 'remHits');\n }\n }\n }\n }\n\n store.updatedAt = now.toISOString();\n await saveDreamingStore({ dreamingRoot: params.dreamingRoot, store });\n\n // Write pattern summary to DREAMS.md (append).\n if (topClusters.length > 0) {\n await appendPatternSummary(params.dreamingRoot, topClusters, now);\n }\n\n log.info(\n {\n dreamingRoot: params.dreamingRoot,\n patterns: topClusters.length,\n entriesAnalyzed: recentEntries.length,\n touched: touchedKeys.size,\n },\n 'REM pattern discovery complete',\n );\n\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: true, reason: 'REM patterns complete', startMs,\n rem: { patternsDiscovered: topClusters.length, entriesAnalyzed: recentEntries.length },\n });\n\n return {\n ok: true,\n reason: 'REM patterns complete',\n patternsDiscovered: topClusters.length,\n entriesAnalyzed: recentEntries.length,\n };\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage, dreamingRoot: params.dreamingRoot }, `REM pattern discovery failed: ${errorMessage}`);\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: false, reason: `REM error: ${errorMessage}`, startMs,\n rem: { patternsDiscovered: 0, entriesAnalyzed: 0 }, errorMessage,\n }).catch(() => undefined);\n return { ok: false, reason: errorMessage, patternsDiscovered: 0, entriesAnalyzed: 0 };\n }\n}\n\n// ── Pattern clustering ─────────────────────────────────────────────────\n\n/**\n * Build an inverted index of queryHash → entries, then form clusters\n * where multiple entries share overlapping query hashes. Each cluster's\n * \"strength\" is the ratio of shared queries to total unique queries.\n */\nfunction discoverPatternClusters(\n entries: DreamingStoreEntry[],\n minStrength: number,\n): PatternCluster[] {\n // Build inverted index: queryHash → entry keys.\n const hashToEntries = new Map<string, DreamingStoreEntry[]>();\n for (const entry of entries) {\n for (const queryHash of entry.queryHashes ?? []) {\n const group = hashToEntries.get(queryHash);\n if (group) {\n group.push(entry);\n } else {\n hashToEntries.set(queryHash, [entry]);\n }\n }\n }\n\n // Find query hashes that appear in 2+ distinct entries from different paths.\n const significantHashes: Array<{ hash: string; entries: DreamingStoreEntry[] }> = [];\n for (const [hash, group] of hashToEntries) {\n const uniquePaths = new Set(group.map((e) => e.path));\n if (group.length >= 2 && uniquePaths.size >= 2) {\n significantHashes.push({ hash, entries: group });\n }\n }\n\n if (significantHashes.length === 0) return [];\n\n // Merge overlapping groups into clusters using union-find.\n const keyToCluster = new Map<string, Set<string>>();\n const keyToEntry = new Map<string, DreamingStoreEntry>();\n\n for (const entry of entries) {\n keyToEntry.set(entry.key, entry);\n }\n\n for (const { entries: groupEntries } of significantHashes) {\n const keys = groupEntries.map((e) => e.key);\n // Find or create the cluster for the first key.\n let mergedCluster = keyToCluster.get(keys[0]!);\n if (!mergedCluster) {\n mergedCluster = new Set<string>();\n mergedCluster.add(keys[0]!);\n keyToCluster.set(keys[0]!, mergedCluster);\n }\n // Merge all other keys into this cluster.\n for (const key of keys.slice(1)) {\n const existingCluster = keyToCluster.get(key);\n if (existingCluster && existingCluster !== mergedCluster) {\n for (const existingKey of existingCluster) {\n mergedCluster.add(existingKey);\n keyToCluster.set(existingKey, mergedCluster);\n }\n } else {\n mergedCluster.add(key);\n keyToCluster.set(key, mergedCluster);\n }\n }\n }\n\n // Deduplicate cluster sets.\n const seenClusters = new Set<Set<string>>();\n const rawClusters: Set<string>[] = [];\n for (const cluster of keyToCluster.values()) {\n if (!seenClusters.has(cluster) && cluster.size >= 2) {\n seenClusters.add(cluster);\n rawClusters.push(cluster);\n }\n }\n\n // Score each cluster.\n const scoredClusters: PatternCluster[] = [];\n for (const clusterKeys of rawClusters) {\n const members: DreamingStoreEntry[] = [];\n for (const key of clusterKeys) {\n const entry = keyToEntry.get(key);\n if (entry) members.push(entry);\n }\n if (members.length < 2) continue;\n\n // Shared queries: hashes that appear in 2+ members.\n const hashCounts = new Map<string, number>();\n for (const member of members) {\n for (const hash of member.queryHashes ?? []) {\n hashCounts.set(hash, (hashCounts.get(hash) ?? 0) + 1);\n }\n }\n const sharedQueries = [...hashCounts.entries()]\n .filter(([, count]) => count >= 2)\n .map(([hash]) => hash);\n\n const allUniqueHashes = new Set<string>();\n for (const member of members) {\n for (const hash of member.queryHashes ?? []) allUniqueHashes.add(hash);\n }\n\n const strength = allUniqueHashes.size > 0 ? sharedQueries.length / allUniqueHashes.size : 0;\n if (strength < minStrength) continue;\n\n const distinctPaths = [...new Set(members.map((m) => m.path))];\n\n // Representative: the member with the highest totalSignalCount.\n const representative = members.reduce((best, current) =>\n (current.totalSignalCount ?? 0) > (best.totalSignalCount ?? 0) ? current : best,\n );\n\n scoredClusters.push({\n representative,\n members,\n strength,\n sharedQueries,\n distinctPaths,\n });\n }\n\n // Sort by strength descending, then by member count.\n scoredClusters.sort((a, b) => {\n if (b.strength !== a.strength) return b.strength - a.strength;\n return b.members.length - a.members.length;\n });\n\n return scoredClusters;\n}\n\n// ── DREAMS.md writer ───────────────────────────────────────────────────\n\nasync function appendPatternSummary(\n dreamingRoot: string,\n clusters: PatternCluster[],\n now: Date,\n): Promise<void> {\n const dreamsPath = path.join(dreamingRoot, DREAMS_MD_FILENAME);\n const existing = await fs.readFile(dreamsPath, 'utf-8').catch((err: unknown) => {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return '';\n throw err;\n });\n\n const day = isoDay(now);\n const lines: string[] = [];\n\n if (existing.trim().length === 0) {\n lines.push('# Dream Diary', '');\n }\n\n lines.push(`## REM Pattern Discovery — ${day}`, '');\n lines.push(`*${now.toISOString()}*`, '');\n\n for (let i = 0; i < clusters.length; i++) {\n const cluster = clusters[i]!;\n lines.push(\n `### Pattern ${i + 1}: ${cluster.distinctPaths.length} files, strength=${cluster.strength.toFixed(2)}`,\n );\n lines.push('');\n lines.push(`**Files involved:** ${cluster.distinctPaths.join(', ')}`);\n lines.push(`**Shared query themes:** ${cluster.sharedQueries.length} overlapping queries`);\n lines.push(`**Members:** ${cluster.members.length} snippets`);\n lines.push('');\n // Include the representative snippet.\n const rep = cluster.representative;\n lines.push(`> ${rep.snippet?.slice(0, 200) ?? '(no snippet)'}`);\n lines.push(`> — ${rep.path}:${rep.startLine}-${rep.endLine}`);\n lines.push('');\n }\n\n lines.push('---', '');\n\n const separator = existing.trim().length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n const next = `${existing}${separator}${lines.join('\\n')}`;\n await fs.writeFile(dreamsPath, next, 'utf-8');\n}\n\n// ── Last-run writer ────────────────────────────────────────────────────\n\nasync function writeLastRun(\n dreamingRoot: string,\n params: {\n runId: string;\n startedAt: string;\n cfg: DreamingRemConfig;\n ok: boolean;\n reason: string;\n startMs: number;\n rem: DreamingRemLastRun['rem'];\n errorMessage?: string;\n },\n): Promise<void> {\n const finishedAt = new Date().toISOString();\n const durationMs = Math.max(0, Date.now() - params.startMs);\n\n const lastRun: DreamingRemLastRun = {\n version: DREAMING_LAST_RUN_FORMAT_VERSION,\n phase: 'rem',\n runId: params.runId,\n startedAt: params.startedAt,\n finishedAt,\n durationMs,\n ok: params.ok,\n reason: params.reason,\n config: params.cfg,\n rem: params.rem,\n ...(params.errorMessage ? { errorMessage: params.errorMessage } : {}),\n };\n\n const lastRunPath = path.join(dreamingRoot, DREAMING_DIR_RELATIVE, 'last-run-rem.json');\n await fs.mkdir(path.dirname(lastRunPath), { recursive: true });\n const tmp = `${lastRunPath}.${process.pid}.${Date.now()}.tmp`;\n await fs.writeFile(tmp, `${JSON.stringify(lastRun, null, 2)}\\n`, 'utf-8');\n await fs.rename(tmp, lastRunPath);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rem-patterns.js","names":[],"sources":["../../../../../src/agent/memory/dreaming/rem-patterns.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { DREAMING_DIR_RELATIVE, DREAMS_MD_FILENAME, MS_PER_DAY } from './constants.js';\nimport type { DreamingRemConfig } from './config.js';\nimport {\n bumpEntryPhaseSignal,\n loadDreamingStore,\n saveDreamingStore,\n withDreamingStoreLock,\n type DreamingStoreEntry,\n} from './short-term-store.js';\nimport { isoDay } from './utils.js';\nimport { inferMemorySensitivity } from '../sensitivity.js';\nimport { activateRemInsight, remPatternKey } from './promotion-lifecycle.js';\nimport {\n DREAMING_LAST_RUN_FORMAT_VERSION,\n type DreamingRemLastRun,\n} from './last-run.js';\n\nconst log = createLogger('Dreaming:REM');\n\n// ── Pattern types ──────────────────────────────────────────────────────\n\ntype PatternCluster = {\n representative: DreamingStoreEntry;\n members: DreamingStoreEntry[];\n strength: number;\n /** Shared query hashes across members. */\n sharedQueries: string[];\n /** Distinct source files involved. */\n distinctPaths: string[];\n};\n\n// ── Config defaults ────────────────────────────────────────────────────\n\nfunction resolveConfig(overrides?: Partial<DreamingRemConfig>): DreamingRemConfig {\n return {\n enabled: overrides?.enabled === true,\n cron: typeof overrides?.cron === 'string' ? overrides.cron : '0 5 * * 0',\n lookbackDays: Math.max(1, Math.floor(Number(overrides?.lookbackDays) || 7)),\n limit: Math.max(0, Math.floor(Number(overrides?.limit) || 10)),\n minPatternStrength: Math.max(0, Math.min(1, Number(overrides?.minPatternStrength) || 0.75)),\n };\n}\n\n// ── Core REM pattern discovery ─────────────────────────────────────────\n\n/**\n * REM phase: cross-session pattern discovery.\n *\n * Scans the short-term store for entries that share query hashes or\n * appear across multiple indexed memory sources, then clusters them to identify\n * recurring themes/patterns. Bumps `remHits` on touched entries and\n * optionally writes a pattern summary to DREAMS.md.\n *\n * Runs weekly; expensive but insightful.\n */\nexport async function runRemPatterns(params: {\n agentId: string;\n workspaceDir: string;\n dreamingRoot: string;\n config?: Partial<DreamingRemConfig>;\n sensitiveWritePolicy?: 'deny' | 'confirm' | 'allow';\n promotionWritePolicy?: 'deny' | 'confirm' | 'allow';\n now?: Date;\n}): Promise<{\n ok: boolean;\n reason: string;\n patternsDiscovered: number;\n entriesAnalyzed: number;\n}> {\n const cfg = resolveConfig(params.config);\n const now = params.now ?? new Date();\n const startedAt = now.toISOString();\n const runId = `rem:${startedAt}:${process.pid}`;\n const startMs = Date.now();\n const nowMs = now.getTime();\n\n if (!cfg.enabled) {\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: true, reason: 'REM patterns disabled', startMs,\n rem: { patternsDiscovered: 0, entriesAnalyzed: 0 },\n });\n return { ok: true, reason: 'REM patterns disabled', patternsDiscovered: 0, entriesAnalyzed: 0 };\n }\n\n try {\n return await withDreamingStoreLock(params.dreamingRoot, async () => {\n const { store } = await loadDreamingStore({ dreamingRoot: params.dreamingRoot });\n\n // Filter entries within the lookback window.\n const cutoffMs = nowMs - cfg.lookbackDays * MS_PER_DAY;\n const recentEntries = Object.values(store.entries ?? {}).filter(\n (entry): entry is DreamingStoreEntry => {\n if (!entry || typeof entry !== 'object') return false;\n if (!entry.lastRecalledAt) return false;\n const lastMs = Date.parse(entry.lastRecalledAt);\n return Number.isFinite(lastMs) && lastMs >= cutoffMs;\n },\n );\n\n if (recentEntries.length < 2) {\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: true, reason: 'not enough recent entries for pattern analysis', startMs,\n rem: { patternsDiscovered: 0, entriesAnalyzed: recentEntries.length },\n });\n return {\n ok: true,\n reason: 'not enough recent entries for pattern analysis',\n patternsDiscovered: 0,\n entriesAnalyzed: recentEntries.length,\n };\n }\n\n // Discover patterns by clustering entries that share query hashes.\n const clusters = discoverPatternClusters(recentEntries, cfg.minPatternStrength);\n const topClusters = clusters\n .filter((cluster) => {\n const sensitivity = inferMemorySensitivity(cluster.members.map((member) => member.snippet).join('\\n'));\n return sensitivity === 'normal' || params.sensitiveWritePolicy === 'allow';\n })\n .slice(0, cfg.limit);\n\n // Bump remHits on all entries that belong to a discovered pattern.\n const touchedKeys = new Set<string>();\n for (const cluster of topClusters) {\n for (const member of cluster.members) {\n if (!touchedKeys.has(member.key)) {\n touchedKeys.add(member.key);\n const storeEntry = store.entries[member.key];\n if (storeEntry) {\n bumpEntryPhaseSignal(storeEntry, 'remHits');\n }\n }\n }\n }\n\n store.updatedAt = now.toISOString();\n await saveDreamingStore({ dreamingRoot: params.dreamingRoot, store });\n\n if (params.promotionWritePolicy === 'allow') {\n for (const cluster of topClusters) {\n const sensitivity = inferMemorySensitivity(cluster.members.map((member) => member.snippet).join('\\n'));\n activateRemInsight({\n agentId: params.agentId,\n workspaceId: params.workspaceDir,\n memberKeys: cluster.members.map((member) => member.key),\n representative: cluster.representative.snippet,\n distinctPaths: cluster.distinctPaths,\n strength: cluster.strength,\n observedAt: now.toISOString(),\n evidence: cluster.members.map((member) => member.snippet),\n sensitivity,\n });\n }\n }\n\n // Write pattern summary to DREAMS.md (append).\n if (topClusters.length > 0) {\n await appendPatternSummary(params.dreamingRoot, topClusters, now);\n }\n\n log.info(\n {\n dreamingRoot: params.dreamingRoot,\n patterns: topClusters.length,\n entriesAnalyzed: recentEntries.length,\n touched: touchedKeys.size,\n },\n 'REM pattern discovery complete',\n );\n\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: true, reason: 'REM patterns complete', startMs,\n rem: { patternsDiscovered: topClusters.length, entriesAnalyzed: recentEntries.length },\n });\n\n return {\n ok: true,\n reason: 'REM patterns complete',\n patternsDiscovered: topClusters.length,\n entriesAnalyzed: recentEntries.length,\n };\n });\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage, dreamingRoot: params.dreamingRoot }, `REM pattern discovery failed: ${errorMessage}`);\n await writeLastRun(params.dreamingRoot, {\n runId, startedAt, cfg, ok: false, reason: `REM error: ${errorMessage}`, startMs,\n rem: { patternsDiscovered: 0, entriesAnalyzed: 0 }, errorMessage,\n }).catch(() => undefined);\n return { ok: false, reason: errorMessage, patternsDiscovered: 0, entriesAnalyzed: 0 };\n }\n}\n\n// ── Pattern clustering ─────────────────────────────────────────────────\n\n/**\n * Build an inverted index of queryHash → entries, then form clusters\n * where multiple entries share overlapping query hashes. Each cluster's\n * \"strength\" is the ratio of shared queries to total unique queries.\n */\nfunction discoverPatternClusters(\n entries: DreamingStoreEntry[],\n minStrength: number,\n): PatternCluster[] {\n // Build inverted index: queryHash → entry keys.\n const hashToEntries = new Map<string, DreamingStoreEntry[]>();\n for (const entry of entries) {\n for (const queryHash of entry.queryHashes ?? []) {\n const group = hashToEntries.get(queryHash);\n if (group) {\n group.push(entry);\n } else {\n hashToEntries.set(queryHash, [entry]);\n }\n }\n }\n\n // Find query hashes that appear in 2+ distinct entries from different paths.\n const significantHashes: Array<{ hash: string; entries: DreamingStoreEntry[] }> = [];\n for (const [hash, group] of hashToEntries) {\n const uniquePaths = new Set(group.map((e) => e.path));\n if (group.length >= 2 && uniquePaths.size >= 2) {\n significantHashes.push({ hash, entries: group });\n }\n }\n\n if (significantHashes.length === 0) return [];\n\n // Merge overlapping groups into clusters using union-find.\n const keyToCluster = new Map<string, Set<string>>();\n const keyToEntry = new Map<string, DreamingStoreEntry>();\n\n for (const entry of entries) {\n keyToEntry.set(entry.key, entry);\n }\n\n for (const { entries: groupEntries } of significantHashes) {\n const keys = groupEntries.map((e) => e.key);\n // Find or create the cluster for the first key.\n let mergedCluster = keyToCluster.get(keys[0]!);\n if (!mergedCluster) {\n mergedCluster = new Set<string>();\n mergedCluster.add(keys[0]!);\n keyToCluster.set(keys[0]!, mergedCluster);\n }\n // Merge all other keys into this cluster.\n for (const key of keys.slice(1)) {\n const existingCluster = keyToCluster.get(key);\n if (existingCluster && existingCluster !== mergedCluster) {\n for (const existingKey of existingCluster) {\n mergedCluster.add(existingKey);\n keyToCluster.set(existingKey, mergedCluster);\n }\n } else {\n mergedCluster.add(key);\n keyToCluster.set(key, mergedCluster);\n }\n }\n }\n\n // Deduplicate cluster sets.\n const seenClusters = new Set<Set<string>>();\n const rawClusters: Set<string>[] = [];\n for (const cluster of keyToCluster.values()) {\n if (!seenClusters.has(cluster) && cluster.size >= 2) {\n seenClusters.add(cluster);\n rawClusters.push(cluster);\n }\n }\n\n // Score each cluster.\n const scoredClusters: PatternCluster[] = [];\n for (const clusterKeys of rawClusters) {\n const members: DreamingStoreEntry[] = [];\n for (const key of clusterKeys) {\n const entry = keyToEntry.get(key);\n if (entry) members.push(entry);\n }\n if (members.length < 2) continue;\n\n // Shared queries: hashes that appear in 2+ members.\n const hashCounts = new Map<string, number>();\n for (const member of members) {\n for (const hash of member.queryHashes ?? []) {\n hashCounts.set(hash, (hashCounts.get(hash) ?? 0) + 1);\n }\n }\n const sharedQueries = [...hashCounts.entries()]\n .filter(([, count]) => count >= 2)\n .map(([hash]) => hash);\n\n const allUniqueHashes = new Set<string>();\n for (const member of members) {\n for (const hash of member.queryHashes ?? []) allUniqueHashes.add(hash);\n }\n\n const strength = allUniqueHashes.size > 0 ? sharedQueries.length / allUniqueHashes.size : 0;\n if (strength < minStrength) continue;\n\n const distinctPaths = [...new Set(members.map((m) => m.path))];\n\n // Representative: the member with the highest totalSignalCount.\n const representative = members.reduce((best, current) =>\n (current.totalSignalCount ?? 0) > (best.totalSignalCount ?? 0) ? current : best,\n );\n\n scoredClusters.push({\n representative,\n members,\n strength,\n sharedQueries,\n distinctPaths,\n });\n }\n\n // Sort by strength descending, then by member count.\n scoredClusters.sort((a, b) => {\n if (b.strength !== a.strength) return b.strength - a.strength;\n return b.members.length - a.members.length;\n });\n\n return scoredClusters;\n}\n\n// ── DREAMS.md writer ───────────────────────────────────────────────────\n\nasync function appendPatternSummary(\n dreamingRoot: string,\n clusters: PatternCluster[],\n now: Date,\n): Promise<void> {\n const dreamsPath = path.join(dreamingRoot, DREAMS_MD_FILENAME);\n const existing = await fs.readFile(dreamsPath, 'utf-8').catch((err: unknown) => {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return '';\n throw err;\n });\n\n const day = isoDay(now);\n const lines: string[] = [];\n const newClusters = clusters.filter((cluster) =>\n !existing.includes(`xopc-rem-pattern id=\"${remPatternKey(cluster.members.map((member) => member.key))}\"`),\n );\n if (newClusters.length === 0) return;\n\n if (existing.trim().length === 0) {\n lines.push('# Dream Diary', '');\n }\n\n lines.push(`## REM Pattern Discovery — ${day}`, '');\n lines.push(`*${now.toISOString()}*`, '');\n\n for (let i = 0; i < newClusters.length; i++) {\n const cluster = newClusters[i]!;\n lines.push(`<!-- xopc-rem-pattern id=\"${remPatternKey(cluster.members.map((member) => member.key))}\" -->`);\n lines.push(\n `### Pattern ${i + 1}: ${cluster.distinctPaths.length} files, strength=${cluster.strength.toFixed(2)}`,\n );\n lines.push('');\n lines.push(`**Files involved:** ${cluster.distinctPaths.join(', ')}`);\n lines.push(`**Shared query themes:** ${cluster.sharedQueries.length} overlapping queries`);\n lines.push(`**Members:** ${cluster.members.length} snippets`);\n lines.push('');\n // Include the representative snippet.\n const rep = cluster.representative;\n lines.push(`> ${rep.snippet?.slice(0, 200) ?? '(no snippet)'}`);\n lines.push(`> — ${rep.path}:${rep.startLine}-${rep.endLine}`);\n lines.push('');\n }\n\n lines.push('---', '');\n\n const separator = existing.trim().length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n const next = `${existing}${separator}${lines.join('\\n')}`;\n await fs.writeFile(dreamsPath, next, 'utf-8');\n}\n\n// ── Last-run writer ────────────────────────────────────────────────────\n\nasync function writeLastRun(\n dreamingRoot: string,\n params: {\n runId: string;\n startedAt: string;\n cfg: DreamingRemConfig;\n ok: boolean;\n reason: string;\n startMs: number;\n rem: DreamingRemLastRun['rem'];\n errorMessage?: string;\n },\n): Promise<void> {\n const finishedAt = new Date().toISOString();\n const durationMs = Math.max(0, Date.now() - params.startMs);\n\n const lastRun: DreamingRemLastRun = {\n version: DREAMING_LAST_RUN_FORMAT_VERSION,\n phase: 'rem',\n runId: params.runId,\n startedAt: params.startedAt,\n finishedAt,\n durationMs,\n ok: params.ok,\n reason: params.reason,\n config: params.cfg,\n rem: params.rem,\n ...(params.errorMessage ? { errorMessage: params.errorMessage } : {}),\n };\n\n const lastRunPath = path.join(dreamingRoot, DREAMING_DIR_RELATIVE, 'last-run-rem.json');\n await fs.mkdir(path.dirname(lastRunPath), { recursive: true });\n const tmp = `${lastRunPath}.${process.pid}.${Date.now()}.tmp`;\n await fs.writeFile(tmp, `${JSON.stringify(lastRun, null, 2)}\\n`, 'utf-8');\n await fs.rename(tmp, lastRunPath);\n}\n"],"mappings":"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"}
@@ -25,6 +25,7 @@ export type DreamingStoreEntry = {
25
25
  recallDays: string[];
26
26
  firstRecalledAt: string;
27
27
  lastRecalledAt: string;
28
+ lastObservedAt?: string;
28
29
  promotedAt?: string;
29
30
  };
30
31
  export type DreamingStore = {
@@ -44,6 +45,9 @@ export declare function recordDreamingRecalls(params: {
44
45
  skipped: number;
45
46
  storePath: string;
46
47
  }>;
48
+ /** Serialize all read-modify-write operations against the shared Dreaming store. */
49
+ export declare function withDreamingStoreLock<T>(dreamingRoot: string, task: () => Promise<T>): Promise<T>;
50
+ /** @deprecated Use the store-level name; retained for callers outside this module. */
47
51
  export declare function withDreamingPromotionLock<T>(dreamingRoot: string, task: () => Promise<T>): Promise<T>;
48
52
  export declare function loadDreamingStore(params: {
49
53
  dreamingRoot: string;
@@ -55,6 +59,12 @@ export declare function saveDreamingStore(params: {
55
59
  dreamingRoot: string;
56
60
  store: DreamingStore;
57
61
  }): Promise<void>;
62
+ export declare function resetDreamingStore(params: {
63
+ dreamingRoot: string;
64
+ now?: Date;
65
+ }): Promise<number>;
66
+ /** Remove only a stale lock whose owning process is no longer alive. */
67
+ export declare function clearStaleDreamingLock(dreamingRoot: string): Promise<boolean>;
58
68
  type PhaseSignalField = 'sourceCount' | 'groundedCount' | 'lightHits' | 'remHits';
59
69
  /**
60
70
  * Increment a phase-specific signal counter on an existing store entry.
@@ -8,6 +8,10 @@ import fs from "node:fs/promises";
8
8
  //#region src/agent/memory/dreaming/short-term-store.ts
9
9
  init_logger();
10
10
  const log = createLogger("Dreaming:Store");
11
+ const STORE_MAX_ENTRIES = 2e3;
12
+ const STORE_MAX_AGE_MS = 2160 * 60 * 60 * 1e3;
13
+ const PROMOTED_MAX_AGE_MS = 720 * 60 * 60 * 1e3;
14
+ const LOCK_STALE_MS = 6e4;
11
15
  function isRecordableMemoryPath(rel) {
12
16
  const p = normalizeMemoryPath(rel);
13
17
  return p.length > 0 && !p.startsWith("../") && !path.isAbsolute(p);
@@ -41,9 +45,9 @@ async function readStore(dreamingRoot, nowIso) {
41
45
  try {
42
46
  const raw = await fs.readFile(storePath, "utf-8");
43
47
  const parsed = JSON.parse(raw);
44
- if (!parsed || typeof parsed !== "object") return emptyStore(nowIso);
48
+ if (!parsed || typeof parsed !== "object") throw new Error("Dreaming store root must be an object");
45
49
  const rec = parsed;
46
- if (rec.version !== 1 || !rec.entries || typeof rec.entries !== "object") return emptyStore(nowIso);
50
+ if (rec.version !== 1 || !rec.entries || typeof rec.entries !== "object") throw new Error("Dreaming store has an unsupported or invalid shape");
47
51
  return {
48
52
  version: 1,
49
53
  updatedAt: typeof rec.updatedAt === "string" ? rec.updatedAt : nowIso,
@@ -51,14 +55,35 @@ async function readStore(dreamingRoot, nowIso) {
51
55
  };
52
56
  } catch (err) {
53
57
  if (err?.code === "ENOENT") return emptyStore(nowIso);
58
+ const quarantinePath = `${storePath}.corrupt-${Date.now()}`;
59
+ await fs.rename(storePath, quarantinePath).catch(() => void 0);
54
60
  log.warn({
55
61
  err,
56
- dreamingRoot
57
- }, "Failed to read dreaming store; resetting");
62
+ dreamingRoot,
63
+ quarantinePath
64
+ }, "Failed to read dreaming store; quarantined corrupt data");
58
65
  return emptyStore(nowIso);
59
66
  }
60
67
  }
68
+ function pruneStore(store, nowMs = Date.now()) {
69
+ for (const [key, entry] of Object.entries(store.entries)) {
70
+ const lastSeenMs = Math.max(Date.parse(entry.lastRecalledAt || entry.firstRecalledAt), entry.lastObservedAt ? Date.parse(entry.lastObservedAt) : 0);
71
+ const promotedMs = entry.promotedAt ? Date.parse(entry.promotedAt) : NaN;
72
+ if (Number.isFinite(promotedMs) && nowMs - promotedMs > PROMOTED_MAX_AGE_MS || Number.isFinite(lastSeenMs) && nowMs - lastSeenMs > STORE_MAX_AGE_MS) delete store.entries[key];
73
+ }
74
+ const entries = Object.values(store.entries);
75
+ if (entries.length <= STORE_MAX_ENTRIES) return;
76
+ entries.sort((left, right) => {
77
+ if (Boolean(left.promotedAt) !== Boolean(right.promotedAt)) return left.promotedAt ? 1 : -1;
78
+ const signalDelta = (right.totalSignalCount ?? 0) - (left.totalSignalCount ?? 0);
79
+ if (signalDelta !== 0) return signalDelta;
80
+ return Date.parse(right.lastRecalledAt) - Date.parse(left.lastRecalledAt);
81
+ });
82
+ const keep = new Set(entries.slice(0, STORE_MAX_ENTRIES).map((entry) => entry.key));
83
+ for (const key of Object.keys(store.entries)) if (!keep.has(key)) delete store.entries[key];
84
+ }
61
85
  async function writeStore(dreamingRoot, store) {
86
+ pruneStore(store);
62
87
  await ensureDreamDir(dreamingRoot);
63
88
  const storePath = path.join(dreamingRoot, SHORT_TERM_RECALL_STORE_RELATIVE);
64
89
  const tmp = `${storePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
@@ -73,84 +98,87 @@ async function recordDreamingRecalls(params) {
73
98
  skipped: params.matches.length,
74
99
  storePath: SHORT_TERM_RECALL_STORE_RELATIVE
75
100
  };
76
- const now = params.now ?? /* @__PURE__ */ new Date();
77
- const nowIso = now.toISOString();
78
- const dayBucket = isoDay(now);
79
- const qHash = hashQuery(query);
80
- const store = await readStore(dreamingRoot, nowIso);
81
- let recorded = 0;
82
- let skipped = 0;
83
- for (const match of params.matches) {
84
- const file = typeof match?.file === "string" ? match.file : "";
85
- if (!file || !isRecordableMemoryPath(file)) {
86
- skipped += 1;
87
- continue;
101
+ return withDreamingStoreLock(dreamingRoot, async () => {
102
+ const now = params.now ?? /* @__PURE__ */ new Date();
103
+ const nowIso = now.toISOString();
104
+ const dayBucket = isoDay(now);
105
+ const qHash = hashQuery(query);
106
+ const store = await readStore(dreamingRoot, nowIso);
107
+ let recorded = 0;
108
+ let skipped = 0;
109
+ for (const match of params.matches) {
110
+ const file = typeof match?.file === "string" ? match.file : "";
111
+ if (!file || !isRecordableMemoryPath(file)) {
112
+ skipped += 1;
113
+ continue;
114
+ }
115
+ const lines = typeof match?.lines === "string" ? match.lines.trim() : "";
116
+ const lineNumbers = Array.isArray(match?.lineNumbers) ? match.lineNumbers : [];
117
+ const startLine = Math.max(1, Math.min(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
118
+ const endLine = Math.max(startLine, Math.max(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
119
+ if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) {
120
+ skipped += 1;
121
+ continue;
122
+ }
123
+ const score = clamp01(Number(match.score));
124
+ const key = buildEntryKey({
125
+ path: file,
126
+ startLine,
127
+ endLine
128
+ });
129
+ const existing = store.entries[key];
130
+ const snippet = lines.length > 0 ? lines.slice(0, 360) : file;
131
+ const next = existing ? {
132
+ ...existing,
133
+ snippet,
134
+ recallCount: Math.max(0, Math.floor(existing.recallCount + 1)),
135
+ sourceCount: existing.sourceCount ?? 0,
136
+ groundedCount: existing.groundedCount ?? 0,
137
+ lightHits: existing.lightHits ?? 0,
138
+ remHits: existing.remHits ?? 0,
139
+ phaseHitCount: existing.phaseHitCount ?? 0,
140
+ totalSignalCount: Math.max(0, (existing.totalSignalCount ?? existing.recallCount ?? 0) + 1),
141
+ totalScore: Math.max(0, existing.totalScore + score),
142
+ maxScore: Math.max(existing.maxScore, score),
143
+ queryHashes: mergeQueryHashes(existing.queryHashes ?? [], qHash),
144
+ recallDays: mergeRecentDistinct(existing.recallDays ?? [], dayBucket, 16),
145
+ lastRecalledAt: nowIso
146
+ } : {
147
+ key,
148
+ path: normalizeMemoryPath(file),
149
+ startLine,
150
+ endLine,
151
+ snippet,
152
+ recallCount: 1,
153
+ sourceCount: 0,
154
+ groundedCount: 0,
155
+ lightHits: 0,
156
+ remHits: 0,
157
+ phaseHitCount: 0,
158
+ totalSignalCount: 1,
159
+ totalScore: score,
160
+ maxScore: score,
161
+ queryHashes: [qHash],
162
+ recallDays: [dayBucket],
163
+ firstRecalledAt: nowIso,
164
+ lastRecalledAt: nowIso
165
+ };
166
+ store.entries[key] = next;
167
+ recorded += 1;
88
168
  }
89
- const lines = typeof match?.lines === "string" ? match.lines.trim() : "";
90
- const lineNumbers = Array.isArray(match?.lineNumbers) ? match.lineNumbers : [];
91
- const startLine = Math.max(1, Math.min(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
92
- const endLine = Math.max(startLine, Math.max(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
93
- if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) {
94
- skipped += 1;
95
- continue;
169
+ if (recorded > 0) {
170
+ store.updatedAt = nowIso;
171
+ await writeStore(dreamingRoot, store);
96
172
  }
97
- const score = clamp01(Number(match.score));
98
- const key = buildEntryKey({
99
- path: file,
100
- startLine,
101
- endLine
102
- });
103
- const existing = store.entries[key];
104
- const snippet = lines.length > 0 ? lines.slice(0, 360) : file;
105
- const next = existing ? {
106
- ...existing,
107
- snippet,
108
- recallCount: Math.max(0, Math.floor(existing.recallCount + 1)),
109
- sourceCount: existing.sourceCount ?? 0,
110
- groundedCount: existing.groundedCount ?? 0,
111
- lightHits: existing.lightHits ?? 0,
112
- remHits: existing.remHits ?? 0,
113
- phaseHitCount: existing.phaseHitCount ?? 0,
114
- totalSignalCount: Math.max(0, (existing.totalSignalCount ?? existing.recallCount ?? 0) + 1),
115
- totalScore: Math.max(0, existing.totalScore + score),
116
- maxScore: Math.max(existing.maxScore, score),
117
- queryHashes: mergeQueryHashes(existing.queryHashes ?? [], qHash),
118
- recallDays: mergeRecentDistinct(existing.recallDays ?? [], dayBucket, 16),
119
- lastRecalledAt: nowIso
120
- } : {
121
- key,
122
- path: normalizeMemoryPath(file),
123
- startLine,
124
- endLine,
125
- snippet,
126
- recallCount: 1,
127
- sourceCount: 0,
128
- groundedCount: 0,
129
- lightHits: 0,
130
- remHits: 0,
131
- phaseHitCount: 0,
132
- totalSignalCount: 1,
133
- totalScore: score,
134
- maxScore: score,
135
- queryHashes: [qHash],
136
- recallDays: [dayBucket],
137
- firstRecalledAt: nowIso,
138
- lastRecalledAt: nowIso
173
+ return {
174
+ recorded,
175
+ skipped,
176
+ storePath: SHORT_TERM_RECALL_STORE_RELATIVE
139
177
  };
140
- store.entries[key] = next;
141
- recorded += 1;
142
- }
143
- if (recorded > 0) {
144
- store.updatedAt = nowIso;
145
- await writeStore(dreamingRoot, store);
146
- }
147
- return {
148
- recorded,
149
- skipped,
150
- storePath: SHORT_TERM_RECALL_STORE_RELATIVE
151
- };
178
+ });
152
179
  }
153
- async function withDreamingPromotionLock(dreamingRoot, task) {
180
+ /** Serialize all read-modify-write operations against the shared Dreaming store. */
181
+ async function withDreamingStoreLock(dreamingRoot, task) {
154
182
  const lockPath = path.join(dreamingRoot, SHORT_TERM_PROMOTION_LOCK_RELATIVE);
155
183
  await ensureDreamDir(dreamingRoot);
156
184
  const startedAt = Date.now();
@@ -167,10 +195,34 @@ async function withDreamingPromotionLock(dreamingRoot, task) {
167
195
  }
168
196
  } catch (err) {
169
197
  if (err?.code !== "EEXIST") throw err;
198
+ const lockStat = await fs.stat(lockPath).catch(() => null);
199
+ if (lockStat && Date.now() - lockStat.mtimeMs > LOCK_STALE_MS && await lockOwnerIsDead(lockPath)) {
200
+ await fs.unlink(lockPath).catch(() => void 0);
201
+ log.warn({
202
+ lockPath,
203
+ ageMs: Date.now() - lockStat.mtimeMs
204
+ }, "Removed stale dreaming lock");
205
+ continue;
206
+ }
170
207
  if (Date.now() - startedAt >= timeoutMs) throw new Error(`Timed out waiting for dreaming promotion lock at ${lockPath}`);
171
208
  await new Promise((r) => setTimeout(r, retryDelayMs));
172
209
  }
173
210
  }
211
+ /** @deprecated Use the store-level name; retained for callers outside this module. */
212
+ async function withDreamingPromotionLock(dreamingRoot, task) {
213
+ return withDreamingStoreLock(dreamingRoot, task);
214
+ }
215
+ async function lockOwnerIsDead(lockPath) {
216
+ const raw = await fs.readFile(lockPath, "utf-8").catch(() => "");
217
+ const pid = Number.parseInt(raw.split(":", 1)[0] ?? "", 10);
218
+ if (!Number.isFinite(pid) || pid <= 0) return true;
219
+ try {
220
+ process.kill(pid, 0);
221
+ return false;
222
+ } catch (error) {
223
+ return error?.code === "ESRCH";
224
+ }
225
+ }
174
226
  async function loadDreamingStore(params) {
175
227
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
176
228
  return {
@@ -181,6 +233,32 @@ async function loadDreamingStore(params) {
181
233
  async function saveDreamingStore(params) {
182
234
  await writeStore(params.dreamingRoot, params.store);
183
235
  }
236
+ async function resetDreamingStore(params) {
237
+ return withDreamingStoreLock(params.dreamingRoot, async () => {
238
+ const nowIso = (params.now ?? /* @__PURE__ */ new Date()).toISOString();
239
+ const store = await readStore(params.dreamingRoot, nowIso);
240
+ const removed = Object.keys(store.entries).length;
241
+ await writeStore(params.dreamingRoot, emptyStore(nowIso));
242
+ return removed;
243
+ });
244
+ }
245
+ /** Remove only a stale lock whose owning process is no longer alive. */
246
+ async function clearStaleDreamingLock(dreamingRoot) {
247
+ const lockPath = path.join(dreamingRoot, SHORT_TERM_PROMOTION_LOCK_RELATIVE);
248
+ const lockStat = await fs.stat(lockPath).catch((error) => {
249
+ if (error?.code === "ENOENT") return null;
250
+ throw error;
251
+ });
252
+ if (!lockStat) return false;
253
+ const ageMs = Date.now() - lockStat.mtimeMs;
254
+ if (ageMs <= LOCK_STALE_MS || !await lockOwnerIsDead(lockPath)) throw new Error(`Dreaming lock is active and cannot be cleared: ${lockPath}`);
255
+ await fs.unlink(lockPath);
256
+ log.warn({
257
+ lockPath,
258
+ ageMs
259
+ }, "Cleared stale dreaming lock");
260
+ return true;
261
+ }
184
262
  /**
185
263
  * Increment a phase-specific signal counter on an existing store entry.
186
264
  * Also bumps `phaseHitCount` and `totalSignalCount`.
@@ -192,6 +270,6 @@ function bumpEntryPhaseSignal(entry, field, increment = 1) {
192
270
  entry.totalSignalCount = (entry.totalSignalCount ?? 0) + increment;
193
271
  }
194
272
  //#endregion
195
- export { bumpEntryPhaseSignal, loadDreamingStore, recordDreamingRecalls, saveDreamingStore, withDreamingPromotionLock };
273
+ export { bumpEntryPhaseSignal, clearStaleDreamingLock, loadDreamingStore, recordDreamingRecalls, resetDreamingStore, saveDreamingStore, withDreamingPromotionLock, withDreamingStoreLock };
196
274
 
197
275
  //# sourceMappingURL=short-term-store.js.map