@remnic/core 10.0.66 → 10.0.68

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 (240) hide show
  1. package/dist/access-admin-ops-surface.js +16 -16
  2. package/dist/access-authorization-probe.js +17 -17
  3. package/dist/access-boundary.js +16 -16
  4. package/dist/access-cli.js +60 -60
  5. package/dist/access-extraction-force-flush.js +16 -16
  6. package/dist/access-http-briefing.js +17 -17
  7. package/dist/access-http-query.js +18 -18
  8. package/dist/access-http.js +33 -33
  9. package/dist/access-identity-continuity-surface.js +16 -16
  10. package/dist/access-lcm-surface.js +16 -16
  11. package/dist/access-mcp.js +26 -26
  12. package/dist/access-namespace-preflight.js +16 -16
  13. package/dist/access-observe-write-surface.js +16 -16
  14. package/dist/access-operations-batch.js +17 -17
  15. package/dist/access-operations.js +24 -24
  16. package/dist/access-recall-concurrency.js +16 -16
  17. package/dist/access-recall-diagnostics.js +2 -2
  18. package/dist/access-recall-response.js +16 -16
  19. package/dist/access-recall-surface.js +16 -16
  20. package/dist/access-service.js +16 -16
  21. package/dist/access-surface-catalog.js +18 -18
  22. package/dist/active-recall.js +3 -3
  23. package/dist/briefing.js +3 -3
  24. package/dist/buffer.js +2 -2
  25. package/dist/causal-consolidation.js +4 -4
  26. package/dist/{chunk-KISOMGY2.js → chunk-3RHU5MD7.js} +2 -2
  27. package/dist/{chunk-3H25IRJJ.js → chunk-4MPAKYZU.js} +2 -2
  28. package/dist/{chunk-IPCSZLLF.js → chunk-4NV7O76Q.js} +2 -2
  29. package/dist/{chunk-BH7NVSEC.js → chunk-4SOWMA55.js} +3 -3
  30. package/dist/{chunk-GROHOWR5.js → chunk-5MGKGEL7.js} +2 -2
  31. package/dist/{chunk-ZPLQ27MY.js → chunk-5UM4H64E.js} +2 -2
  32. package/dist/{chunk-2DNFRLLL.js → chunk-5Y6VA6X2.js} +2 -2
  33. package/dist/{chunk-RAU25Q6J.js → chunk-6WKWL53V.js} +13 -13
  34. package/dist/{chunk-KFPJIKUW.js → chunk-75DCOS6I.js} +23 -23
  35. package/dist/{chunk-25QK3LR2.js → chunk-7O2MC42R.js} +2 -2
  36. package/dist/{chunk-7ETWIBTO.js → chunk-ADYP7HXE.js} +3 -3
  37. package/dist/{chunk-FUDKGJDN.js → chunk-AVGDYYMA.js} +2 -2
  38. package/dist/{chunk-ECF627KC.js → chunk-AYJQ644F.js} +2 -2
  39. package/dist/{chunk-T763ADEK.js → chunk-B3MWELZ3.js} +2 -2
  40. package/dist/{chunk-DRTSPRWL.js → chunk-B4DXREWC.js} +2 -2
  41. package/dist/{chunk-5UEGL5BF.js → chunk-CI3MAAIY.js} +2 -2
  42. package/dist/{chunk-JYXFIILI.js → chunk-CTH2CQCB.js} +2 -2
  43. package/dist/{chunk-UVHXLVFJ.js → chunk-EURPSF4Y.js} +2 -2
  44. package/dist/{chunk-VPEA63FC.js → chunk-EWJZZDRG.js} +2 -2
  45. package/dist/{chunk-BOZQAUAJ.js → chunk-EXQJMFRF.js} +2 -2
  46. package/dist/{chunk-XMBDI725.js → chunk-FAF4A76Q.js} +684 -529
  47. package/dist/chunk-FAF4A76Q.js.map +1 -0
  48. package/dist/{chunk-VC65GNUS.js → chunk-GGD2YDS6.js} +2 -2
  49. package/dist/{chunk-5JABRT5G.js → chunk-GHHQLSSY.js} +3 -3
  50. package/dist/{chunk-FMYAU5MP.js → chunk-GJ4HU6LK.js} +2 -2
  51. package/dist/{chunk-3NJJQAL3.js → chunk-H5DIRCZP.js} +12 -12
  52. package/dist/{chunk-434E4BTO.js → chunk-HH73M65H.js} +3 -3
  53. package/dist/{chunk-LIGZO7XN.js → chunk-HPB5TF55.js} +2 -2
  54. package/dist/{chunk-E4QIX2OB.js → chunk-IAGVM7EY.js} +2 -2
  55. package/dist/{chunk-VC7GKR2V.js → chunk-IG3EZ3TF.js} +2 -2
  56. package/dist/{chunk-QAN5ZDRS.js → chunk-IVUVQPTQ.js} +2 -2
  57. package/dist/{chunk-XOFIJGB2.js → chunk-IZBIDPMY.js} +2 -2
  58. package/dist/{chunk-HFEGRMFG.js → chunk-JLGBJ4AX.js} +7 -7
  59. package/dist/{chunk-BBHKN2CN.js → chunk-JLMOH5JA.js} +2 -2
  60. package/dist/{chunk-TPCRDPUY.js → chunk-KZSGBWOJ.js} +2 -2
  61. package/dist/{chunk-SHHZWSSE.js → chunk-KZZ5EZ7O.js} +2 -2
  62. package/dist/{chunk-A5FOXI3B.js → chunk-LN4X2NDA.js} +2 -2
  63. package/dist/{chunk-5ZHFPUBC.js → chunk-MOIJWWNA.js} +8 -8
  64. package/dist/{chunk-KO224R36.js → chunk-NCZGGGWI.js} +2 -2
  65. package/dist/{chunk-XVQVF2TK.js → chunk-NHWDTOBN.js} +269 -230
  66. package/dist/chunk-NHWDTOBN.js.map +1 -0
  67. package/dist/{chunk-7ZTGMEXF.js → chunk-O5ZINWW3.js} +2 -2
  68. package/dist/{chunk-WWH7GJ42.js → chunk-OCK4D5PC.js} +2 -2
  69. package/dist/{chunk-G6L3REL3.js → chunk-OPFVXM2D.js} +2 -2
  70. package/dist/{chunk-FR7PK7ED.js → chunk-P4TV3TQ2.js} +2 -2
  71. package/dist/{chunk-ZGHPELZ6.js → chunk-PMDOGXA2.js} +2 -2
  72. package/dist/{chunk-NM2II57L.js → chunk-PRKNAIGZ.js} +2 -1
  73. package/dist/chunk-PRKNAIGZ.js.map +1 -0
  74. package/dist/{chunk-F32JRMX2.js → chunk-QGFR73SN.js} +2 -2
  75. package/dist/{chunk-QNZPYH36.js → chunk-R2BIVU2Z.js} +352 -290
  76. package/dist/chunk-R2BIVU2Z.js.map +1 -0
  77. package/dist/{chunk-HG3CG557.js → chunk-RLVI2M5Q.js} +2 -2
  78. package/dist/{chunk-SH6ZYUTX.js → chunk-SXTVZ6JL.js} +2 -2
  79. package/dist/{chunk-CLSKGTAT.js → chunk-TECK5IVY.js} +2 -2
  80. package/dist/{chunk-SNS3DWG6.js → chunk-TVE7W67N.js} +2 -2
  81. package/dist/{chunk-KYSSUY66.js → chunk-U2CAGMUD.js} +2 -2
  82. package/dist/{chunk-XLCEFUAU.js → chunk-UVVFPCPX.js} +2 -2
  83. package/dist/{chunk-PL75KQGF.js → chunk-UW6LBFYN.js} +5 -5
  84. package/dist/{chunk-SEA56G6S.js → chunk-V4XE263I.js} +1 -1
  85. package/dist/chunk-V4XE263I.js.map +1 -0
  86. package/dist/{chunk-VSZNPAGL.js → chunk-V6SBA5O7.js} +3 -3
  87. package/dist/{chunk-B7VD36TI.js → chunk-WHROVQ5Q.js} +3 -3
  88. package/dist/{chunk-LDHR465K.js → chunk-Y3XPSGNF.js} +2 -2
  89. package/dist/{chunk-E6VQJ75T.js → chunk-ZORS5BG5.js} +4 -4
  90. package/dist/{chunk-KXY4HW2Y.js → chunk-ZXF3O7P4.js} +2 -2
  91. package/dist/cli.js +46 -46
  92. package/dist/coding/export-okf-codegraph.js +2 -2
  93. package/dist/compounding/engine.js +4 -4
  94. package/dist/config.d.ts.map +1 -1
  95. package/dist/config.js +3 -3
  96. package/dist/connectors/codex-materialize-runner.js +3 -3
  97. package/dist/connectors/index.js +3 -3
  98. package/dist/convergence-refresh.js +5 -5
  99. package/dist/corpus-watermark.js +5 -5
  100. package/dist/entity-retrieval.js +3 -3
  101. package/dist/external-wiki-access.js +17 -17
  102. package/dist/extraction.js +6 -6
  103. package/dist/harmonic-retrieval.js +3 -3
  104. package/dist/index.js +60 -60
  105. package/dist/location/index.js +5 -5
  106. package/dist/maintenance/memory-governance.js +3 -3
  107. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  108. package/dist/maintenance/rebuild-memory-projection-runner.js +5 -5
  109. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  110. package/dist/memory-browse-operations.js +17 -17
  111. package/dist/namespaces/migrate.js +4 -4
  112. package/dist/namespaces/storage.js +3 -3
  113. package/dist/operator-doctor-corpus.js +6 -6
  114. package/dist/operator-toolkit.js +12 -12
  115. package/dist/orchestration/buffer-drain-runner.js +60 -60
  116. package/dist/orchestration/consolidation-run.d.ts.map +1 -1
  117. package/dist/orchestration/extraction-drain.d.ts +4 -4
  118. package/dist/orchestration/extraction-drain.d.ts.map +1 -1
  119. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  120. package/dist/orchestration/extraction-queue-coordinator.d.ts +67 -33
  121. package/dist/orchestration/extraction-queue-coordinator.d.ts.map +1 -1
  122. package/dist/orchestration/extraction-run.d.ts +1 -8
  123. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  124. package/dist/orchestration/fact-dedup-window.d.ts +49 -0
  125. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -0
  126. package/dist/orchestration/maintenance.js +13 -13
  127. package/dist/orchestration/provider-breaker.d.ts +58 -0
  128. package/dist/orchestration/provider-breaker.d.ts.map +1 -0
  129. package/dist/orchestrator.js +60 -60
  130. package/dist/recall-explain-renderer.js +3 -3
  131. package/dist/recall-navigate-access.js +2 -2
  132. package/dist/recall-navigate-disclosure.js +2 -2
  133. package/dist/recall-navigate.js +2 -2
  134. package/dist/recall-navigation-operations.js +17 -17
  135. package/dist/recall-navigation.js +16 -16
  136. package/dist/recall-xray-cli.js +4 -4
  137. package/dist/recall-xray-renderer.js +3 -3
  138. package/dist/recall-xray.js +2 -2
  139. package/dist/resume-bundles.js +4 -4
  140. package/dist/semantic-consolidation.js +4 -4
  141. package/dist/semantic-rule-promotion.js +3 -3
  142. package/dist/semantic-rule-verifier.js +3 -3
  143. package/dist/source-agent-qualifier.js +5 -5
  144. package/dist/standup/build.js +3 -3
  145. package/dist/storage/profile-mutation.d.ts +25 -0
  146. package/dist/storage/profile-mutation.d.ts.map +1 -0
  147. package/dist/storage.d.ts +4 -6
  148. package/dist/storage.d.ts.map +1 -1
  149. package/dist/storage.js +2 -2
  150. package/dist/support-passport/index.js +19 -19
  151. package/dist/transfer/export-okf.js +3 -3
  152. package/dist/types.d.ts +2 -0
  153. package/dist/types.d.ts.map +1 -1
  154. package/dist/types.js +1 -1
  155. package/dist/verified-recall.js +3 -3
  156. package/dist/who-knows-cli.js +4 -4
  157. package/dist/who-knows.js +3 -3
  158. package/package.json +2 -2
  159. package/src/buffer-drain-handover.test.ts +3 -3
  160. package/src/buffer-drop-extraction-chunk.test.ts +0 -1
  161. package/src/config.test.ts +12 -0
  162. package/src/config.ts +2 -0
  163. package/src/extraction-parallel-dedup.test.ts +185 -0
  164. package/src/fact-dedup-window.test.ts +116 -0
  165. package/src/orchestration/consolidation-profile-race.test.ts +178 -0
  166. package/src/orchestration/consolidation-run.ts +19 -7
  167. package/src/orchestration/extraction-drain.ts +10 -3
  168. package/src/orchestration/extraction-persist.ts +156 -161
  169. package/src/orchestration/extraction-queue-coordinator.ts +167 -56
  170. package/src/orchestration/extraction-run.ts +50 -93
  171. package/src/orchestration/fact-dedup-window.ts +122 -0
  172. package/src/orchestration/provider-breaker.test.ts +111 -0
  173. package/src/orchestration/provider-breaker.ts +158 -0
  174. package/src/orchestration/turn-ingestion.ts +5 -5
  175. package/src/orchestrator-extraction-queue.test.ts +179 -8
  176. package/src/orchestrator.ts +1 -1
  177. package/src/storage/profile-mutation.ts +119 -0
  178. package/src/storage-append-profile.test.ts +114 -0
  179. package/src/storage.ts +17 -48
  180. package/src/types.ts +2 -0
  181. package/dist/chunk-NM2II57L.js.map +0 -1
  182. package/dist/chunk-QNZPYH36.js.map +0 -1
  183. package/dist/chunk-SEA56G6S.js.map +0 -1
  184. package/dist/chunk-XMBDI725.js.map +0 -1
  185. package/dist/chunk-XVQVF2TK.js.map +0 -1
  186. /package/dist/{chunk-KISOMGY2.js.map → chunk-3RHU5MD7.js.map} +0 -0
  187. /package/dist/{chunk-3H25IRJJ.js.map → chunk-4MPAKYZU.js.map} +0 -0
  188. /package/dist/{chunk-IPCSZLLF.js.map → chunk-4NV7O76Q.js.map} +0 -0
  189. /package/dist/{chunk-BH7NVSEC.js.map → chunk-4SOWMA55.js.map} +0 -0
  190. /package/dist/{chunk-GROHOWR5.js.map → chunk-5MGKGEL7.js.map} +0 -0
  191. /package/dist/{chunk-ZPLQ27MY.js.map → chunk-5UM4H64E.js.map} +0 -0
  192. /package/dist/{chunk-2DNFRLLL.js.map → chunk-5Y6VA6X2.js.map} +0 -0
  193. /package/dist/{chunk-RAU25Q6J.js.map → chunk-6WKWL53V.js.map} +0 -0
  194. /package/dist/{chunk-KFPJIKUW.js.map → chunk-75DCOS6I.js.map} +0 -0
  195. /package/dist/{chunk-25QK3LR2.js.map → chunk-7O2MC42R.js.map} +0 -0
  196. /package/dist/{chunk-7ETWIBTO.js.map → chunk-ADYP7HXE.js.map} +0 -0
  197. /package/dist/{chunk-FUDKGJDN.js.map → chunk-AVGDYYMA.js.map} +0 -0
  198. /package/dist/{chunk-ECF627KC.js.map → chunk-AYJQ644F.js.map} +0 -0
  199. /package/dist/{chunk-T763ADEK.js.map → chunk-B3MWELZ3.js.map} +0 -0
  200. /package/dist/{chunk-DRTSPRWL.js.map → chunk-B4DXREWC.js.map} +0 -0
  201. /package/dist/{chunk-5UEGL5BF.js.map → chunk-CI3MAAIY.js.map} +0 -0
  202. /package/dist/{chunk-JYXFIILI.js.map → chunk-CTH2CQCB.js.map} +0 -0
  203. /package/dist/{chunk-UVHXLVFJ.js.map → chunk-EURPSF4Y.js.map} +0 -0
  204. /package/dist/{chunk-VPEA63FC.js.map → chunk-EWJZZDRG.js.map} +0 -0
  205. /package/dist/{chunk-BOZQAUAJ.js.map → chunk-EXQJMFRF.js.map} +0 -0
  206. /package/dist/{chunk-VC65GNUS.js.map → chunk-GGD2YDS6.js.map} +0 -0
  207. /package/dist/{chunk-5JABRT5G.js.map → chunk-GHHQLSSY.js.map} +0 -0
  208. /package/dist/{chunk-FMYAU5MP.js.map → chunk-GJ4HU6LK.js.map} +0 -0
  209. /package/dist/{chunk-3NJJQAL3.js.map → chunk-H5DIRCZP.js.map} +0 -0
  210. /package/dist/{chunk-434E4BTO.js.map → chunk-HH73M65H.js.map} +0 -0
  211. /package/dist/{chunk-LIGZO7XN.js.map → chunk-HPB5TF55.js.map} +0 -0
  212. /package/dist/{chunk-E4QIX2OB.js.map → chunk-IAGVM7EY.js.map} +0 -0
  213. /package/dist/{chunk-VC7GKR2V.js.map → chunk-IG3EZ3TF.js.map} +0 -0
  214. /package/dist/{chunk-QAN5ZDRS.js.map → chunk-IVUVQPTQ.js.map} +0 -0
  215. /package/dist/{chunk-XOFIJGB2.js.map → chunk-IZBIDPMY.js.map} +0 -0
  216. /package/dist/{chunk-HFEGRMFG.js.map → chunk-JLGBJ4AX.js.map} +0 -0
  217. /package/dist/{chunk-BBHKN2CN.js.map → chunk-JLMOH5JA.js.map} +0 -0
  218. /package/dist/{chunk-TPCRDPUY.js.map → chunk-KZSGBWOJ.js.map} +0 -0
  219. /package/dist/{chunk-SHHZWSSE.js.map → chunk-KZZ5EZ7O.js.map} +0 -0
  220. /package/dist/{chunk-A5FOXI3B.js.map → chunk-LN4X2NDA.js.map} +0 -0
  221. /package/dist/{chunk-5ZHFPUBC.js.map → chunk-MOIJWWNA.js.map} +0 -0
  222. /package/dist/{chunk-KO224R36.js.map → chunk-NCZGGGWI.js.map} +0 -0
  223. /package/dist/{chunk-7ZTGMEXF.js.map → chunk-O5ZINWW3.js.map} +0 -0
  224. /package/dist/{chunk-WWH7GJ42.js.map → chunk-OCK4D5PC.js.map} +0 -0
  225. /package/dist/{chunk-G6L3REL3.js.map → chunk-OPFVXM2D.js.map} +0 -0
  226. /package/dist/{chunk-FR7PK7ED.js.map → chunk-P4TV3TQ2.js.map} +0 -0
  227. /package/dist/{chunk-ZGHPELZ6.js.map → chunk-PMDOGXA2.js.map} +0 -0
  228. /package/dist/{chunk-F32JRMX2.js.map → chunk-QGFR73SN.js.map} +0 -0
  229. /package/dist/{chunk-HG3CG557.js.map → chunk-RLVI2M5Q.js.map} +0 -0
  230. /package/dist/{chunk-SH6ZYUTX.js.map → chunk-SXTVZ6JL.js.map} +0 -0
  231. /package/dist/{chunk-CLSKGTAT.js.map → chunk-TECK5IVY.js.map} +0 -0
  232. /package/dist/{chunk-SNS3DWG6.js.map → chunk-TVE7W67N.js.map} +0 -0
  233. /package/dist/{chunk-KYSSUY66.js.map → chunk-U2CAGMUD.js.map} +0 -0
  234. /package/dist/{chunk-XLCEFUAU.js.map → chunk-UVVFPCPX.js.map} +0 -0
  235. /package/dist/{chunk-PL75KQGF.js.map → chunk-UW6LBFYN.js.map} +0 -0
  236. /package/dist/{chunk-VSZNPAGL.js.map → chunk-V6SBA5O7.js.map} +0 -0
  237. /package/dist/{chunk-B7VD36TI.js.map → chunk-WHROVQ5Q.js.map} +0 -0
  238. /package/dist/{chunk-LDHR465K.js.map → chunk-Y3XPSGNF.js.map} +0 -0
  239. /package/dist/{chunk-E6VQJ75T.js.map → chunk-ZORS5BG5.js.map} +0 -0
  240. /package/dist/{chunk-KXY4HW2Y.js.map → chunk-ZXF3O7P4.js.map} +0 -0
@@ -274,6 +274,104 @@ function classifyExtractionOrigin(sourceContext) {
274
274
  });
275
275
  }
276
276
 
277
+ // src/orchestration/provider-breaker.ts
278
+ var ProviderCircuitBreaker = class {
279
+ consecutiveFailures = 0;
280
+ openUntilMs = 0;
281
+ state = "closed";
282
+ lastReason = "";
283
+ probeHeld = false;
284
+ probeToken = null;
285
+ acquire(now) {
286
+ if (now < this.openUntilMs) return { suppressed: true, probe: false };
287
+ if (this.state === "open") {
288
+ this.state = "half_open";
289
+ this.probeHeld = true;
290
+ const token = { __probeOwner: true };
291
+ this.probeToken = token;
292
+ this.logTransition();
293
+ return { suppressed: false, probe: true, token };
294
+ }
295
+ if (this.state === "half_open") {
296
+ if (this.probeHeld) return { suppressed: true, probe: false };
297
+ this.probeHeld = true;
298
+ const token = { __probeOwner: true };
299
+ this.probeToken = token;
300
+ return { suppressed: false, probe: true, token };
301
+ }
302
+ return { suppressed: false, probe: false };
303
+ }
304
+ /** Release an owned probe on an attempt that exits without a terminal
305
+ * outcome (deadline, abort, throw). Only the matching owner releases;
306
+ * idempotent; state stays half_open so the next attempt re-probes. */
307
+ releaseProbe(token) {
308
+ if (token === void 0 || this.probeToken !== token) return;
309
+ this.probeToken = null;
310
+ this.probeHeld = false;
311
+ }
312
+ failure(cls, now, config, probeToken) {
313
+ const owned = probeToken !== void 0 && this.probeToken === probeToken;
314
+ if (owned) {
315
+ this.probeToken = null;
316
+ this.probeHeld = false;
317
+ }
318
+ this.consecutiveFailures += 1;
319
+ const trip = (
320
+ // An unowned auth_config failure (forced extraction) must not reopen
321
+ // over a held half-open probe — same ownership rule as the threshold
322
+ // term; the auth cooldown still applies once the probe settles.
323
+ cls === "auth_config" && (this.state !== "half_open" || owned) || // A failed half-open probe re-opens immediately (standard breaker
324
+ // semantics). Without this, an auth_config-opened breaker (which trips
325
+ // below the threshold) would get stuck half_open after a transient
326
+ // probe failure and stop suppressing (cursor review).
327
+ owned && this.state === "half_open" || // PRRT_kwDORJXyws6zxG6V: an unowned (forced-extraction) failure must
328
+ // not cross the threshold while another attempt owns the half-open
329
+ // probe — reopening here would strand the owner's token and let the
330
+ // next cooldown mint a second concurrent probe. The failure count and
331
+ // retry bookkeeping still accumulate for when the state leaves
332
+ // half_open.
333
+ this.state !== "half_open" && this.consecutiveFailures >= config.failureThreshold
334
+ );
335
+ if (!trip) return;
336
+ const cooldown = cls === "auth_config" ? config.authCooldownMs : config.cooldownMs;
337
+ this.openUntilMs = now + Math.max(0, cooldown);
338
+ this.lastReason = cls;
339
+ if (this.state !== "open") {
340
+ this.state = "open";
341
+ this.logTransition();
342
+ }
343
+ }
344
+ success(probeToken) {
345
+ const wasDisturbed = this.state !== "closed" || this.consecutiveFailures > 0 || this.openUntilMs > 0;
346
+ if (!wasDisturbed) return;
347
+ if (this.probeHeld && !(probeToken !== void 0 && this.probeToken === probeToken)) return;
348
+ const wasOpen = this.state !== "closed";
349
+ this.probeToken = null;
350
+ this.probeHeld = false;
351
+ this.consecutiveFailures = 0;
352
+ this.openUntilMs = 0;
353
+ this.lastReason = "";
354
+ this.state = "closed";
355
+ if (wasOpen) this.logTransition();
356
+ }
357
+ snapshot() {
358
+ return {
359
+ state: this.state,
360
+ openUntilMs: this.openUntilMs,
361
+ consecutiveFailures: this.consecutiveFailures,
362
+ lastReason: this.lastReason
363
+ };
364
+ }
365
+ logTransition() {
366
+ log.warn("extraction: provider circuit breaker transition", {
367
+ state: this.state,
368
+ consecutiveFailures: this.consecutiveFailures,
369
+ lastReason: this.lastReason,
370
+ openUntilMs: this.openUntilMs
371
+ });
372
+ }
373
+ };
374
+
277
375
  // src/orchestration/extraction-run.ts
278
376
  var ExtractionDeadlineError = class extends Error {
279
377
  stage;
@@ -298,7 +396,7 @@ var ExtractionRunCoordinator = class {
298
396
  // backoff still throttles, so a restart can't resurrect the hot loop.
299
397
  extractionRetryState = /* @__PURE__ */ new Map();
300
398
  hydratedRetryNamespaces = /* @__PURE__ */ new Set();
301
- providerBreaker = { consecutiveFailures: 0, openUntilMs: 0, state: "closed", lastReason: "" };
399
+ providerBreaker = new ProviderCircuitBreaker();
302
400
  get config() {
303
401
  return this.deps.config;
304
402
  }
@@ -398,51 +496,6 @@ ${normalized}`).digest("hex");
398
496
  }
399
497
  this.extractionRetryState.set(namespace, nsMap);
400
498
  }
401
- logBreakerTransition(state) {
402
- log.warn("extraction: provider circuit breaker transition", {
403
- state,
404
- consecutiveFailures: this.providerBreaker.consecutiveFailures,
405
- lastReason: this.providerBreaker.lastReason,
406
- openUntilMs: this.providerBreaker.openUntilMs
407
- });
408
- }
409
- /** True while the breaker suppresses non-forced attempts. Flips an expired
410
- * `open` breaker to `half_open`, which allows exactly one probe (the next
411
- * non-forced attempt) because the extraction queue is a serial drain. */
412
- isProviderBreakerOpen(now) {
413
- if (now < this.providerBreaker.openUntilMs) return true;
414
- if (this.providerBreaker.state === "open") {
415
- this.providerBreaker.state = "half_open";
416
- this.logBreakerTransition("half_open");
417
- }
418
- return false;
419
- }
420
- onProviderFailure(cls, now) {
421
- this.providerBreaker.consecutiveFailures += 1;
422
- const trip = cls === "auth_config" || // A failed half-open probe re-opens immediately (standard breaker
423
- // semantics). Without this, an auth_config-opened breaker (which trips
424
- // below the threshold) would get stuck half_open after a transient
425
- // probe failure and stop suppressing (cursor review).
426
- this.providerBreaker.state === "half_open" || this.providerBreaker.consecutiveFailures >= this.config.extractionBreakerFailureThreshold;
427
- if (!trip) return;
428
- const cooldown = cls === "auth_config" ? this.config.extractionBreakerAuthCooldownMs : this.config.extractionBreakerCooldownMs;
429
- this.providerBreaker.openUntilMs = now + Math.max(0, cooldown);
430
- this.providerBreaker.lastReason = cls;
431
- if (this.providerBreaker.state !== "open") {
432
- this.providerBreaker.state = "open";
433
- this.logBreakerTransition("open");
434
- }
435
- }
436
- resetProviderBreakerOnSuccess() {
437
- const wasDisturbed = this.providerBreaker.state !== "closed" || this.providerBreaker.consecutiveFailures > 0 || this.providerBreaker.openUntilMs > 0;
438
- if (!wasDisturbed) return;
439
- const wasOpen = this.providerBreaker.state !== "closed";
440
- this.providerBreaker.consecutiveFailures = 0;
441
- this.providerBreaker.openUntilMs = 0;
442
- this.providerBreaker.lastReason = "";
443
- this.providerBreaker.state = "closed";
444
- if (wasOpen) this.logBreakerTransition("closed");
445
- }
446
499
  /** Rebuild `meta.extractionRetryState` from the in-memory namespace mirror,
447
500
  * bounded to the newest entries, and prune the mirror to match. */
448
501
  persistRetryStateToMeta(namespace, meta) {
@@ -464,10 +517,10 @@ ${normalized}`).digest("hex");
464
517
  }
465
518
  }
466
519
  /** Record an extraction failure into per-fingerprint backoff state + breaker, persisting to namespace meta. Fail-open. */
467
- async recordExtractionFailure(namespace, fingerprint, cls, meta, now) {
520
+ async recordExtractionFailure(namespace, fingerprint, cls, meta, now, probeToken) {
468
521
  try {
469
522
  this.hydrateRetryStateFromMeta(namespace, meta);
470
- this.applyExtractionFailureToMemory(namespace, fingerprint, cls, now);
523
+ this.applyExtractionFailureToMemory(namespace, fingerprint, cls, now, probeToken);
471
524
  this.persistRetryStateToMeta(namespace, meta);
472
525
  } catch (err) {
473
526
  log.warn("runExtraction: failed to record extraction retry state (non-fatal)", err);
@@ -475,7 +528,7 @@ ${normalized}`).digest("hex");
475
528
  }
476
529
  /** Failure backoff into the in-memory mirror + breaker WITHOUT touching meta — the fallback
477
530
  * when the locked meta write cannot run: the retry gate reads this mirror, so it stays shut. */
478
- applyExtractionFailureToMemory(namespace, fingerprint, cls, now) {
531
+ applyExtractionFailureToMemory(namespace, fingerprint, cls, now, probeToken) {
479
532
  const nsMap = this.extractionRetryState.get(namespace) ?? /* @__PURE__ */ new Map();
480
533
  const prev = nsMap.get(fingerprint);
481
534
  const attempts = (prev?.attempts ?? 0) + 1;
@@ -494,7 +547,11 @@ ${normalized}`).digest("hex");
494
547
  }
495
548
  nsMap.set(fingerprint, { attempts, nextEligibleAtMs, firstFailedAtMs, lastFailureClass: cls });
496
549
  this.extractionRetryState.set(namespace, nsMap);
497
- this.onProviderFailure(cls, now);
550
+ this.providerBreaker.failure(cls, now, {
551
+ failureThreshold: this.config.extractionBreakerFailureThreshold,
552
+ cooldownMs: this.config.extractionBreakerCooldownMs,
553
+ authCooldownMs: this.config.extractionBreakerAuthCooldownMs
554
+ }, probeToken);
498
555
  }
499
556
  /** Delete a fingerprint's retry entry from the mirror + meta. Returns whether
500
557
  * meta changed (so the caller can decide to persist). Does not touch the
@@ -513,12 +570,7 @@ ${normalized}`).digest("hex");
513
570
  backoffFingerprintCount += nsMap.size;
514
571
  }
515
572
  return {
516
- breaker: {
517
- state: this.providerBreaker.state,
518
- openUntilMs: this.providerBreaker.openUntilMs,
519
- consecutiveFailures: this.providerBreaker.consecutiveFailures,
520
- lastReason: this.providerBreaker.lastReason
521
- },
573
+ breaker: this.providerBreaker.snapshot(),
522
574
  backoffFingerprintCount
523
575
  };
524
576
  }
@@ -774,13 +826,18 @@ ${normalized}`).digest("hex");
774
826
  }
775
827
  const forcedExtractionAttempt = options.failOnExtractionFailure === true || options.forceExtractionAttempt === true;
776
828
  const extractionRetryEnabled = resolveMemoryLifecycleCapabilities(this.config).extractionRetry;
829
+ let extractionProbeOwned = false;
830
+ let extractionProbeToken;
777
831
  if (extractionFingerprint && !forcedExtractionAttempt && extractionRetryEnabled) {
778
832
  try {
779
833
  meta ??= await runDeadlineAware(() => storage.loadMeta(), "during_retry_gate");
780
834
  this.hydrateRetryStateFromMeta(selfNamespace, meta);
781
835
  const nowMs = Date.now();
836
+ const probeAttempt = this.providerBreaker.acquire(nowMs);
837
+ extractionProbeOwned = probeAttempt.probe;
838
+ extractionProbeToken = probeAttempt.token;
782
839
  let suppressReason = null;
783
- if (this.isProviderBreakerOpen(nowMs)) {
840
+ if (probeAttempt.suppressed) {
784
841
  suppressReason = "provider_circuit_open";
785
842
  } else {
786
843
  const st = this.extractionRetryState.get(selfNamespace)?.get(extractionFingerprint);
@@ -803,6 +860,7 @@ ${normalized}`).digest("hex");
803
860
  log.warn("runExtraction: passive correction capture failed on suppressed attempt (non-fatal)", captureErr);
804
861
  }
805
862
  clearExtractionDeadlineTimer();
863
+ if (extractionProbeOwned) this.providerBreaker.releaseProbe(extractionProbeToken);
806
864
  return { status: "skipped", reason: suppressReason, persistedCount: 0, durableOutputCount: 0 };
807
865
  }
808
866
  } catch (err) {
@@ -810,268 +868,272 @@ ${normalized}`).digest("hex");
810
868
  log.warn("runExtraction: extraction retry gate check failed; proceeding (fail-open)", err);
811
869
  }
812
870
  }
813
- const existingEntities = await runDeadlineAware(
814
- () => storage.listEntityNames(),
815
- "during_list_entity_names"
816
- );
817
- let result;
818
871
  try {
819
- result = await raceRecallAbort(
820
- this.deps.getExtraction().extract(normalizedTurns, existingEntities, extractionAbortSignal),
821
- extractionAbortSignal,
822
- "extraction aborted (during_extract)"
872
+ const existingEntities = await runDeadlineAware(
873
+ () => storage.listEntityNames(),
874
+ "during_list_entity_names"
823
875
  );
824
- } catch (error) {
825
- clearExtractionDeadlineTimer();
826
- if (typeof deadlineMs === "number" && Date.now() >= deadlineMs) {
827
- throw new ExtractionDeadlineError("during_extract");
828
- }
829
- throw error;
830
- }
831
- try {
832
- throwIfDeadlineExceeded("before_persist");
833
- throwIfAborted("before_persist");
834
- if (!result) {
835
- log.warn("runExtraction: extraction returned null/undefined");
836
- if (options.failOnExtractionFailure) {
837
- throw new Error("extraction failed: invalid_extraction_result");
876
+ let result;
877
+ try {
878
+ result = await raceRecallAbort(
879
+ this.deps.getExtraction().extract(normalizedTurns, existingEntities, extractionAbortSignal),
880
+ extractionAbortSignal,
881
+ "extraction aborted (during_extract)"
882
+ );
883
+ } catch (error) {
884
+ clearExtractionDeadlineTimer();
885
+ if (deadlineMs !== void 0 && Date.now() >= deadlineMs) {
886
+ throw new ExtractionDeadlineError("during_extract");
838
887
  }
839
- await clearBuffer();
840
- return {
841
- status: "skipped",
842
- reason: "invalid_extraction_result",
843
- persistedCount: 0,
844
- durableOutputCount: 0
845
- };
888
+ throw error;
846
889
  }
847
- const invalidExtractionResultFields = [
848
- ["facts", result.facts],
849
- ["entities", result.entities],
850
- ["questions", result.questions],
851
- ["profileUpdates", result.profileUpdates]
852
- ].filter(([, value]) => !Array.isArray(value)).map(([field]) => field);
853
- if (invalidExtractionResultFields.length > 0) {
854
- log.warn("runExtraction: extraction returned invalid collection fields", {
855
- invalidFields: invalidExtractionResultFields,
856
- resultKeys: typeof result === "object" && result !== null ? Object.keys(result) : []
857
- });
858
- if (options.failOnExtractionFailure) {
859
- throw new Error("extraction failed: invalid_extraction_result");
890
+ try {
891
+ throwIfDeadlineExceeded("before_persist");
892
+ throwIfAborted("before_persist");
893
+ if (!result) {
894
+ log.warn("runExtraction: extraction returned null/undefined");
895
+ if (options.failOnExtractionFailure) {
896
+ throw new Error("extraction failed: invalid_extraction_result");
897
+ }
898
+ await clearBuffer();
899
+ return {
900
+ status: "skipped",
901
+ reason: "invalid_extraction_result",
902
+ persistedCount: 0,
903
+ durableOutputCount: 0
904
+ };
860
905
  }
861
- await clearBuffer();
862
- return {
863
- status: "skipped",
864
- reason: "invalid_extraction_result",
865
- persistedCount: 0,
866
- durableOutputCount: 0
867
- };
868
- }
869
- const extractionFailure = typeof result.extractionFailure === "string" && result.extractionFailure.trim().length > 0 ? result.extractionFailure : void 0;
870
- if (extractionFailure) {
871
- recordExtractionLlmFailure(extractionFailure);
872
- emitExtractionLlmFailureError(extractionFailure, result.extractionFailureClass);
873
- } else if (result.extractionSkippedReason !== void 0) {
874
- } else {
875
- recordExtractionLlmSuccess();
876
- }
877
- let recordedRetryFailure = false;
878
- if (extractionFingerprint && extractionRetryEnabled) {
906
+ const invalidExtractionResultFields = [
907
+ ["facts", result.facts],
908
+ ["entities", result.entities],
909
+ ["questions", result.questions],
910
+ ["profileUpdates", result.profileUpdates]
911
+ ].filter(([, value]) => !Array.isArray(value)).map(([field]) => field);
912
+ if (invalidExtractionResultFields.length > 0) {
913
+ log.warn("runExtraction: extraction returned invalid collection fields", {
914
+ invalidFields: invalidExtractionResultFields,
915
+ resultKeys: typeof result === "object" && result !== null ? Object.keys(result) : []
916
+ });
917
+ if (options.failOnExtractionFailure) {
918
+ throw new Error("extraction failed: invalid_extraction_result");
919
+ }
920
+ await clearBuffer();
921
+ return {
922
+ status: "skipped",
923
+ reason: "invalid_extraction_result",
924
+ persistedCount: 0,
925
+ durableOutputCount: 0
926
+ };
927
+ }
928
+ const extractionFailure = typeof result.extractionFailure === "string" && result.extractionFailure.trim().length > 0 ? result.extractionFailure : void 0;
879
929
  if (extractionFailure) {
880
- const failureClass = result.extractionFailureClass ?? "provider_retryable";
881
- try {
882
- await runDeadlineAware(
883
- () => storage.updateMeta(
884
- (freshMeta) => this.recordExtractionFailure(selfNamespace, extractionFingerprint, failureClass, freshMeta, Date.now())
885
- ),
886
- "during_retry_bookkeeping_save",
887
- false
930
+ recordExtractionLlmFailure(extractionFailure);
931
+ emitExtractionLlmFailureError(extractionFailure, result.extractionFailureClass);
932
+ } else if (result.extractionSkippedReason !== void 0) {
933
+ } else {
934
+ recordExtractionLlmSuccess();
935
+ }
936
+ let recordedRetryFailure = false;
937
+ if (extractionFingerprint && extractionRetryEnabled) {
938
+ if (extractionFailure) {
939
+ const failureClass = result.extractionFailureClass ?? "provider_retryable";
940
+ try {
941
+ await runDeadlineAware(
942
+ () => storage.updateMeta(
943
+ (freshMeta) => this.recordExtractionFailure(selfNamespace, extractionFingerprint, failureClass, freshMeta, Date.now(), extractionProbeToken)
944
+ ),
945
+ "during_retry_bookkeeping_save",
946
+ false
947
+ );
948
+ recordedRetryFailure = true;
949
+ } catch (err) {
950
+ if (err instanceof ExtractionDeadlineError) throw err;
951
+ if (err instanceof Error && err.message.includes("meta.json.lock not acquired"))
952
+ this.applyExtractionFailureToMemory(selfNamespace, extractionFingerprint, failureClass, Date.now(), extractionProbeToken);
953
+ recordedRetryFailure = true;
954
+ log.warn("runExtraction: retry state kept in memory; meta persist will catch up", err);
955
+ }
956
+ } else if (result.extractionSkippedReason === void 0) {
957
+ this.providerBreaker.success(extractionProbeToken);
958
+ try {
959
+ await runDeadlineAware(
960
+ () => storage.updateMeta((freshMeta) => {
961
+ this.hydrateRetryStateFromMeta(selfNamespace, freshMeta);
962
+ return this.clearExtractionRetryEntry(selfNamespace, extractionFingerprint, freshMeta);
963
+ }),
964
+ "during_retry_bookkeeping_save",
965
+ false
966
+ );
967
+ } catch (err) {
968
+ if (err instanceof ExtractionDeadlineError) throw err;
969
+ log.warn("runExtraction: failed to persist cleared retry state (non-fatal)", err);
970
+ }
971
+ }
972
+ }
973
+ if (options.failOnExtractionFailure && extractionFailure) {
974
+ throw new Error(`extraction failed: ${extractionFailure}`);
975
+ }
976
+ if (result.facts.length === 0 && result.entities.length === 0 && result.questions.length === 0 && result.profileUpdates.length === 0) {
977
+ log.debug("runExtraction: extraction produced no durable outputs; skipping persistence");
978
+ if (extractionFailure) {
979
+ log.warn(
980
+ "runExtraction: extraction reported failure with no durable outputs; not marking fingerprint processed",
981
+ { extractionFailure }
888
982
  );
889
- recordedRetryFailure = true;
890
- } catch (err) {
891
- if (err instanceof ExtractionDeadlineError) throw err;
892
- if (err instanceof Error && err.message.includes("meta.json.lock not acquired"))
893
- this.applyExtractionFailureToMemory(selfNamespace, extractionFingerprint, failureClass, Date.now());
894
- recordedRetryFailure = true;
895
- log.warn("runExtraction: retry state kept in memory; meta persist will catch up", err);
896
983
  }
897
- } else if (result.extractionSkippedReason === void 0) {
898
- this.resetProviderBreakerOnSuccess();
984
+ const emptySuccessCommit = {
985
+ storage,
986
+ extractionFingerprint,
987
+ shouldPersistProcessedFingerprint,
988
+ hasExtractionFailure: Boolean(extractionFailure),
989
+ extractionSkippedReason: result.extractionSkippedReason,
990
+ recordProcessedExtractionFingerprint: this.deps.recordProcessedExtractionFingerprint,
991
+ runDeadlineAware,
992
+ isDeadlineError: (error) => error instanceof ExtractionDeadlineError
993
+ };
994
+ if (shouldPersistProcessedFingerprint) {
995
+ await commitEmptySuccessExtraction(emptySuccessCommit);
996
+ }
997
+ await runPassiveCapture(normalizedTurns, {
998
+ sessionKey,
999
+ principal,
1000
+ namespace: selfNamespace,
1001
+ bufferKey,
1002
+ isLiveSession: clearBufferAfterExtraction
1003
+ });
1004
+ if (recordedRetryFailure) {
1005
+ log.debug("runExtraction: retaining buffer for backoff retry after failure");
1006
+ } else {
1007
+ await clearBuffer();
1008
+ }
1009
+ if (!shouldPersistProcessedFingerprint) {
1010
+ await commitEmptySuccessExtraction(emptySuccessCommit);
1011
+ }
1012
+ return emptyExtractionResult(recordedRetryFailure, result.extractionFailureClass);
1013
+ }
1014
+ let threadIdForExtraction = null;
1015
+ if (resolvePresentationCapabilities(this.config).threading && turns.length > 0) {
1016
+ const lastTurn = turns[turns.length - 1];
899
1017
  try {
900
- await runDeadlineAware(
901
- () => storage.updateMeta((freshMeta) => {
902
- this.hydrateRetryStateFromMeta(selfNamespace, freshMeta);
903
- return this.clearExtractionRetryEntry(selfNamespace, extractionFingerprint, freshMeta);
904
- }),
905
- "during_retry_bookkeeping_save",
906
- false
1018
+ threadIdForExtraction = await runDeadlineAware(
1019
+ () => this.deps.getThreading().processTurn(lastTurn, []),
1020
+ "during_threading"
907
1021
  );
908
1022
  } catch (err) {
909
1023
  if (err instanceof ExtractionDeadlineError) throw err;
910
- log.warn("runExtraction: failed to persist cleared retry state (non-fatal)", err);
1024
+ log.warn("[threading] processTurn failed before persistence (non-fatal)", err);
911
1025
  }
912
1026
  }
913
- }
914
- if (options.failOnExtractionFailure && extractionFailure) {
915
- throw new Error(`extraction failed: ${extractionFailure}`);
916
- }
917
- if (result.facts.length === 0 && result.entities.length === 0 && result.questions.length === 0 && result.profileUpdates.length === 0) {
918
- log.debug("runExtraction: extraction produced no durable outputs; skipping persistence");
919
- if (extractionFailure) {
920
- log.warn(
921
- "runExtraction: extraction reported failure with no durable outputs; not marking fingerprint processed",
922
- { extractionFailure }
923
- );
1027
+ const originContext = deriveExtractionOriginContext(targetTurns, result.sourceConnector);
1028
+ options.onBeforePersist?.();
1029
+ throwIfDeadlineExceeded("before_persist");
1030
+ throwIfAborted("before_persist");
1031
+ const { persistedIds, deferredCount, pendingReviewIds } = await this.deps.persistExtraction(
1032
+ result,
1033
+ storage,
1034
+ threadIdForExtraction,
1035
+ { sessionKey, principal, validAt: sourceValidAt, ...originContext },
1036
+ // Pass the KNOWN base namespace (NHIdx) so the catalog write touch records the
1037
+ // real namespace rather than a guess decoded from the storage dir.
1038
+ selfNamespace,
1039
+ scopeProfileWritePlan,
1040
+ // Verbatim source turns for the faithfulness gate (#1576) so it can
1041
+ // locate a quote per fact when #1575 spans are absent.
1042
+ normalizedTurns.map((t) => t.content).join("\n\n")
1043
+ );
1044
+ const durableOutputCount = result.facts.length + result.entities.length + result.questions.length + result.profileUpdates.length;
1045
+ try {
1046
+ options.onDurableCommit?.();
1047
+ } catch (error) {
1048
+ log.warn("runExtraction: durable-commit callback failed", error);
924
1049
  }
925
- const emptySuccessCommit = {
1050
+ const runPostPersistBestEffort = runExtractionPostPersistBestEffort.bind(null, runDeadlineAware);
1051
+ await tagPersistedMemories(storage, persistedIds, this.config);
1052
+ const postPersist = await runExtractionPostPersist({
1053
+ result,
926
1054
  storage,
927
- extractionFingerprint,
1055
+ extractionFingerprint: extractionFingerprint ?? void 0,
928
1056
  shouldPersistProcessedFingerprint,
929
- hasExtractionFailure: Boolean(extractionFailure),
930
- extractionSkippedReason: result.extractionSkippedReason,
931
1057
  recordProcessedExtractionFingerprint: this.deps.recordProcessedExtractionFingerprint,
932
1058
  runDeadlineAware,
933
- isDeadlineError: (error) => error instanceof ExtractionDeadlineError
934
- };
935
- if (shouldPersistProcessedFingerprint) {
936
- await commitEmptySuccessExtraction(emptySuccessCommit);
937
- }
938
- await runPassiveCapture(normalizedTurns, {
939
- sessionKey,
940
- principal,
941
- namespace: selfNamespace,
1059
+ isDeadlineError: (error) => error instanceof ExtractionDeadlineError,
1060
+ createDeadlineError: (stage) => new ExtractionDeadlineError(stage),
1061
+ deadlineMs,
1062
+ clearBufferAfterExtraction,
1063
+ runPostPersistBestEffort,
1064
+ extractionJudgeShadow: this.config.extractionJudgeShadow,
1065
+ deferredCount,
1066
+ normalizedTurns,
942
1067
  bufferKey,
943
- isLiveSession: clearBufferAfterExtraction
1068
+ retainDeferredTurns: (key, retainedTurns, max) => this.deps.getBuffer().retainDeferredTurns(key, retainedTurns, max, options.retentionWindow ?? turns),
1069
+ clearBuffer,
1070
+ failOnExtractionFailure: options.failOnExtractionFailure === true
944
1071
  });
945
- if (recordedRetryFailure) {
946
- log.debug("runExtraction: retaining buffer for backoff retry after failure");
947
- } else {
948
- await clearBuffer();
1072
+ const postPersistMetadataFailed = postPersist.metadataFailed;
1073
+ await runPostPersistBestEffort(
1074
+ "during_post_persist_passive_capture",
1075
+ () => runPassiveCapture(normalizedTurns, {
1076
+ sessionKey,
1077
+ principal,
1078
+ namespace: selfNamespace,
1079
+ bufferKey,
1080
+ isLiveSession: clearBufferAfterExtraction,
1081
+ abortSignal: extractionAbortSignal
1082
+ })
1083
+ );
1084
+ if (resolvePresentationCapabilities(this.config).memoryBoxes && persistedIds.length > 0) {
1085
+ const extractionTopics = deriveTopicsFromExtraction(result);
1086
+ const firstUserTurn = turns.find((t) => t.role === "user");
1087
+ const boxGoal = firstUserTurn?.content?.slice(0, 100)?.trim() || void 0;
1088
+ await runPostPersistBestEffort(
1089
+ "during_memory_box",
1090
+ () => this.deps.boxBuilderFor(storage).onExtraction({
1091
+ topics: extractionTopics,
1092
+ memoryIds: persistedIds,
1093
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1094
+ goal: boxGoal
1095
+ })
1096
+ );
1097
+ }
1098
+ if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction && persistedIds.length > 0) {
1099
+ await runPostPersistBestEffort(
1100
+ "during_thread_episode_append",
1101
+ () => this.deps.appendPersistedThreadEpisodes(threadIdForExtraction, persistedIds, pendingReviewIds)
1102
+ );
949
1103
  }
950
- if (!shouldPersistProcessedFingerprint) {
951
- await commitEmptySuccessExtraction(emptySuccessCommit);
1104
+ if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction) {
1105
+ const conversationContent = turns.map((t) => t.content).join(" ");
1106
+ await runPostPersistBestEffort(
1107
+ "during_thread_title_update",
1108
+ () => this.deps.getThreading().updateThreadTitle(threadIdForExtraction, conversationContent)
1109
+ );
952
1110
  }
953
- return emptyExtractionResult(recordedRetryFailure, result.extractionFailureClass);
954
- }
955
- let threadIdForExtraction = null;
956
- if (resolvePresentationCapabilities(this.config).threading && turns.length > 0) {
957
- const lastTurn = turns[turns.length - 1];
1111
+ const nonZeroExtraction = durableOutputCount > 0;
958
1112
  try {
959
- threadIdForExtraction = await runDeadlineAware(
960
- () => this.deps.getThreading().processTurn(lastTurn, []),
961
- "during_threading"
962
- );
1113
+ this.deps.maybeScheduleConsolidation(nonZeroExtraction);
963
1114
  } catch (err) {
964
- if (err instanceof ExtractionDeadlineError) throw err;
965
- log.warn("[threading] processTurn failed before persistence (non-fatal)", err);
1115
+ log.warn("runExtraction: consolidation scheduling failed after persistence (non-fatal)", err);
1116
+ }
1117
+ try {
1118
+ this.deps.requestQmdMaintenance();
1119
+ } catch (err) {
1120
+ log.warn("runExtraction: QMD maintenance scheduling failed after persistence (non-fatal)", err);
966
1121
  }
967
- }
968
- const originContext = deriveExtractionOriginContext(targetTurns, result.sourceConnector);
969
- options.onBeforePersist?.();
970
- throwIfDeadlineExceeded("before_persist");
971
- throwIfAborted("before_persist");
972
- const { persistedIds, deferredCount, pendingReviewIds } = await this.deps.persistExtraction(
973
- result,
974
- storage,
975
- threadIdForExtraction,
976
- { sessionKey, principal, validAt: sourceValidAt, ...originContext },
977
- // Pass the KNOWN base namespace (NHIdx) so the catalog write touch records the
978
- // real namespace rather than a guess decoded from the storage dir.
979
- selfNamespace,
980
- scopeProfileWritePlan,
981
- // Verbatim source turns for the faithfulness gate (#1576) so it can
982
- // locate a quote per fact when #1575 spans are absent.
983
- normalizedTurns.map((t) => t.content).join("\n\n")
984
- );
985
- const durableOutputCount = result.facts.length + result.entities.length + result.questions.length + result.profileUpdates.length;
986
- try {
987
- options.onDurableCommit?.();
988
- } catch (error) {
989
- log.warn("runExtraction: durable-commit callback failed", error);
990
- }
991
- const runPostPersistBestEffort = runExtractionPostPersistBestEffort.bind(null, runDeadlineAware);
992
- await tagPersistedMemories(storage, persistedIds, this.config);
993
- const postPersist = await runExtractionPostPersist({
994
- result,
995
- storage,
996
- extractionFingerprint: extractionFingerprint ?? void 0,
997
- shouldPersistProcessedFingerprint,
998
- recordProcessedExtractionFingerprint: this.deps.recordProcessedExtractionFingerprint,
999
- runDeadlineAware,
1000
- isDeadlineError: (error) => error instanceof ExtractionDeadlineError,
1001
- createDeadlineError: (stage) => new ExtractionDeadlineError(stage),
1002
- deadlineMs,
1003
- clearBufferAfterExtraction,
1004
- runPostPersistBestEffort,
1005
- extractionJudgeShadow: this.config.extractionJudgeShadow,
1006
- deferredCount,
1007
- normalizedTurns,
1008
- bufferKey,
1009
- retainDeferredTurns: (key, retainedTurns, max) => this.deps.getBuffer().retainDeferredTurns(key, retainedTurns, max, options.retentionWindow ?? turns),
1010
- clearBuffer,
1011
- failOnExtractionFailure: options.failOnExtractionFailure === true
1012
- });
1013
- const postPersistMetadataFailed = postPersist.metadataFailed;
1014
- await runPostPersistBestEffort(
1015
- "during_post_persist_passive_capture",
1016
- () => runPassiveCapture(normalizedTurns, {
1017
- sessionKey,
1018
- principal,
1019
- namespace: selfNamespace,
1020
- bufferKey,
1021
- isLiveSession: clearBufferAfterExtraction,
1022
- abortSignal: extractionAbortSignal
1023
- })
1024
- );
1025
- if (resolvePresentationCapabilities(this.config).memoryBoxes && persistedIds.length > 0) {
1026
- const extractionTopics = deriveTopicsFromExtraction(result);
1027
- const firstUserTurn = turns.find((t) => t.role === "user");
1028
- const boxGoal = firstUserTurn?.content?.slice(0, 100)?.trim() || void 0;
1029
- await runPostPersistBestEffort(
1030
- "during_memory_box",
1031
- () => this.deps.boxBuilderFor(storage).onExtraction({
1032
- topics: extractionTopics,
1033
- memoryIds: persistedIds,
1034
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1035
- goal: boxGoal
1036
- })
1037
- );
1038
- }
1039
- if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction && persistedIds.length > 0) {
1040
- await runPostPersistBestEffort(
1041
- "during_thread_episode_append",
1042
- () => this.deps.appendPersistedThreadEpisodes(threadIdForExtraction, persistedIds, pendingReviewIds)
1043
- );
1044
- }
1045
- if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction) {
1046
- const conversationContent = turns.map((t) => t.content).join(" ");
1047
1122
  await runPostPersistBestEffort(
1048
- "during_thread_title_update",
1049
- () => this.deps.getThreading().updateThreadTitle(threadIdForExtraction, conversationContent)
1123
+ "during_tier_migration",
1124
+ () => this.deps.runTierMigrationCycle(storage, "extraction")
1050
1125
  );
1126
+ return {
1127
+ status: "completed",
1128
+ persistedCount: persistedIds.length,
1129
+ durableOutputCount,
1130
+ postPersistMetadataFailed
1131
+ };
1132
+ } finally {
1133
+ clearExtractionDeadlineTimer();
1051
1134
  }
1052
- const nonZeroExtraction = durableOutputCount > 0;
1053
- try {
1054
- this.deps.maybeScheduleConsolidation(nonZeroExtraction);
1055
- } catch (err) {
1056
- log.warn("runExtraction: consolidation scheduling failed after persistence (non-fatal)", err);
1057
- }
1058
- try {
1059
- this.deps.requestQmdMaintenance();
1060
- } catch (err) {
1061
- log.warn("runExtraction: QMD maintenance scheduling failed after persistence (non-fatal)", err);
1062
- }
1063
- await runPostPersistBestEffort(
1064
- "during_tier_migration",
1065
- () => this.deps.runTierMigrationCycle(storage, "extraction")
1066
- );
1067
- return {
1068
- status: "completed",
1069
- persistedCount: persistedIds.length,
1070
- durableOutputCount,
1071
- postPersistMetadataFailed
1072
- };
1073
1135
  } finally {
1074
- clearExtractionDeadlineTimer();
1136
+ if (extractionProbeOwned) this.providerBreaker.releaseProbe(extractionProbeToken);
1075
1137
  }
1076
1138
  }
1077
1139
  };
@@ -1083,4 +1145,4 @@ export {
1083
1145
  ExtractionDeadlineError,
1084
1146
  ExtractionRunCoordinator
1085
1147
  };
1086
- //# sourceMappingURL=chunk-QNZPYH36.js.map
1148
+ //# sourceMappingURL=chunk-R2BIVU2Z.js.map