@remnic/core 9.3.717 → 9.3.719

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/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +9 -9
  3. package/dist/access-cli.js +23 -23
  4. package/dist/access-http.d.ts +5 -5
  5. package/dist/access-http.js +13 -13
  6. package/dist/access-mcp.d.ts +5 -5
  7. package/dist/access-mcp.js +12 -12
  8. package/dist/access-operations-batch.js +10 -10
  9. package/dist/access-operations.d.ts +5 -5
  10. package/dist/access-operations.js +11 -11
  11. package/dist/{access-service-d4Tk-Dlr.d.ts → access-service-CZbnt0t2.d.ts} +3 -3
  12. package/dist/access-service.d.ts +5 -5
  13. package/dist/access-service.js +8 -8
  14. package/dist/access-surface-catalog.d.ts +5 -5
  15. package/dist/action-confidence.d.ts +1 -1
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-recall.d.ts +1 -1
  18. package/dist/active-recall.js +1 -1
  19. package/dist/behavior-learner.d.ts +1 -1
  20. package/dist/behavior-signals.d.ts +1 -1
  21. package/dist/bootstrap.d.ts +4 -4
  22. package/dist/briefing.d.ts +3 -1
  23. package/dist/briefing.js +3 -3
  24. package/dist/buffer-surprise-report.d.ts +1 -1
  25. package/dist/buffer.d.ts +1 -1
  26. package/dist/calibration.d.ts +1 -1
  27. package/dist/capabilities.d.ts +1 -1
  28. package/dist/{catalog-CbR0CsUw.d.ts → catalog-CEYlMs9X.d.ts} +1 -1
  29. package/dist/causal-behavior.d.ts +1 -1
  30. package/dist/causal-consolidation.d.ts +1 -1
  31. package/dist/causal-consolidation.js +4 -4
  32. package/dist/{chunk-JEIROMLX.js → chunk-2COL35PK.js} +95 -9
  33. package/dist/chunk-2COL35PK.js.map +1 -0
  34. package/dist/{chunk-XZTUMWKQ.js → chunk-2PTBLS2S.js} +2 -2
  35. package/dist/{chunk-2Y6LMOQQ.js → chunk-2VHMVWPU.js} +2 -2
  36. package/dist/{chunk-6UU43ZGD.js → chunk-4KZGLDJJ.js} +2 -2
  37. package/dist/{chunk-K44MIN6D.js → chunk-5RHM43BJ.js} +3 -3
  38. package/dist/{chunk-2B43BYYZ.js → chunk-72VDBALG.js} +18 -18
  39. package/dist/{chunk-UY4JMEAK.js → chunk-7EEVXW7X.js} +2 -2
  40. package/dist/{chunk-XCTDHF7U.js → chunk-A5TUPZ3N.js} +33 -1
  41. package/dist/chunk-A5TUPZ3N.js.map +1 -0
  42. package/dist/{chunk-WI4QX7GH.js → chunk-A66YRSOS.js} +2 -2
  43. package/dist/{chunk-EM22PCQR.js → chunk-ANI5DUAE.js} +8 -6
  44. package/dist/{chunk-EM22PCQR.js.map → chunk-ANI5DUAE.js.map} +1 -1
  45. package/dist/{chunk-VZQXJNBD.js → chunk-APUJIMDM.js} +2 -2
  46. package/dist/{chunk-MPHDVHIB.js → chunk-DIFU33GU.js} +2 -2
  47. package/dist/{chunk-DXGS5YOQ.js → chunk-EH27LHML.js} +2 -2
  48. package/dist/{chunk-YAINYEM3.js → chunk-F3PVXKHP.js} +25 -1398
  49. package/dist/chunk-F3PVXKHP.js.map +1 -0
  50. package/dist/{chunk-DZYARLJ7.js → chunk-HLIR3RJ7.js} +2 -2
  51. package/dist/{chunk-6FUJ2VYY.js → chunk-JAKYZGV4.js} +3 -3
  52. package/dist/{chunk-ZAVTEQFM.js → chunk-JIZGYQYH.js} +3 -3
  53. package/dist/{chunk-QOIONSOI.js → chunk-KRGC4EDX.js} +2 -2
  54. package/dist/{chunk-AJOIM6UR.js → chunk-L5ASZA2F.js} +2 -2
  55. package/dist/{chunk-I56LKMF7.js → chunk-LXSG4L55.js} +2 -2
  56. package/dist/{chunk-MSVSP5VO.js → chunk-N3DUH6OO.js} +1 -1
  57. package/dist/chunk-N3DUH6OO.js.map +1 -0
  58. package/dist/{chunk-CTUWGJRI.js → chunk-NFRBADNR.js} +166 -28
  59. package/dist/chunk-NFRBADNR.js.map +1 -0
  60. package/dist/chunk-OJU73BUX.js +1688 -0
  61. package/dist/chunk-OJU73BUX.js.map +1 -0
  62. package/dist/{chunk-JVEMUX6L.js → chunk-OZ2KQ7HF.js} +2 -2
  63. package/dist/{chunk-OUDAU3T5.js → chunk-P5IPFZVH.js} +4 -4
  64. package/dist/{chunk-KHQ433G5.js → chunk-REWQAFSA.js} +2 -2
  65. package/dist/{chunk-V7GDTQ6I.js → chunk-S35FX6MU.js} +2 -2
  66. package/dist/{chunk-MTHNJCOJ.js → chunk-S3OPGGZB.js} +2 -2
  67. package/dist/{chunk-ZXLSNG3T.js → chunk-U5H7WHTS.js} +6 -4
  68. package/dist/{chunk-ZXLSNG3T.js.map → chunk-U5H7WHTS.js.map} +1 -1
  69. package/dist/{chunk-BZAK3RBN.js → chunk-UCJ5CDTK.js} +6 -6
  70. package/dist/{chunk-BF2426TN.js → chunk-URM3HW2R.js} +2 -2
  71. package/dist/{chunk-VO4UICII.js → chunk-YTZLBBEB.js} +2 -2
  72. package/dist/{chunk-OC56UWV5.js → chunk-YYOK4GVF.js} +2 -2
  73. package/dist/{cli-insNLYs6.d.ts → cli-1orcdXOY.d.ts} +3 -3
  74. package/dist/cli.d.ts +6 -6
  75. package/dist/cli.js +27 -27
  76. package/dist/compounding/engine.d.ts +1 -1
  77. package/dist/compounding/engine.js +3 -3
  78. package/dist/compounding/preference-consolidator.d.ts +1 -1
  79. package/dist/compression-optimizer.d.ts +1 -1
  80. package/dist/config.d.ts +1 -1
  81. package/dist/config.js +1 -1
  82. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  83. package/dist/connectors/codex-materialize-runner.js +3 -3
  84. package/dist/connectors/codex-materialize.d.ts +1 -1
  85. package/dist/connectors/index.d.ts +1 -1
  86. package/dist/connectors/index.js +3 -3
  87. package/dist/consolidation-provenance-check.d.ts +1 -1
  88. package/dist/consolidation-undo.d.ts +1 -1
  89. package/dist/contradiction/index.d.ts +1 -1
  90. package/dist/conversation-index/backend.d.ts +1 -1
  91. package/dist/conversation-index/chunker.d.ts +1 -1
  92. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  93. package/dist/conversation-index/indexer.d.ts +1 -1
  94. package/dist/conversation-index/search.d.ts +1 -1
  95. package/dist/day-summary.d.ts +1 -1
  96. package/dist/delinearize.d.ts +1 -1
  97. package/dist/direct-answer-wiring.d.ts +1 -1
  98. package/dist/direct-answer.d.ts +1 -1
  99. package/dist/embedding-fallback.d.ts +1 -1
  100. package/dist/enrichment/index.d.ts +1 -1
  101. package/dist/entity-retrieval.d.ts +1 -1
  102. package/dist/entity-retrieval.js +3 -3
  103. package/dist/entity-schema.d.ts +1 -1
  104. package/dist/explicit-capture.d.ts +4 -4
  105. package/dist/extraction-faithfulness.d.ts +1 -1
  106. package/dist/extraction-judge-telemetry.d.ts +1 -1
  107. package/dist/extraction-judge-training.d.ts +1 -1
  108. package/dist/extraction-judge.d.ts +1 -1
  109. package/dist/extraction.d.ts +1 -1
  110. package/dist/fallback-llm.d.ts +1 -1
  111. package/dist/identity-continuity.d.ts +1 -1
  112. package/dist/importance.d.ts +1 -1
  113. package/dist/index.d.ts +9 -9
  114. package/dist/index.js +33 -33
  115. package/dist/intent.d.ts +1 -1
  116. package/dist/lcm/engine.d.ts +1 -1
  117. package/dist/lcm/index.d.ts +1 -1
  118. package/dist/lcm/tools.d.ts +1 -1
  119. package/dist/lifecycle.d.ts +1 -1
  120. package/dist/live-connectors-runner.d.ts +1 -1
  121. package/dist/local-llm.d.ts +1 -1
  122. package/dist/maintenance/memory-governance.d.ts +1 -1
  123. package/dist/maintenance/memory-governance.js +3 -3
  124. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  125. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  126. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  127. package/dist/memory-action-policy.d.ts +1 -1
  128. package/dist/memory-cache.d.ts +1 -1
  129. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  130. package/dist/memory-projection-store.d.ts +1 -1
  131. package/dist/memory-provenance.d.ts +1 -1
  132. package/dist/memory-worth-outcomes.d.ts +1 -1
  133. package/dist/models-json.d.ts +1 -1
  134. package/dist/namespaces/migrate.d.ts +2 -2
  135. package/dist/namespaces/migrate.js +4 -4
  136. package/dist/namespaces/principal.d.ts +1 -1
  137. package/dist/namespaces/search.d.ts +1 -1
  138. package/dist/namespaces/storage.d.ts +2 -2
  139. package/dist/namespaces/storage.js +3 -3
  140. package/dist/native-knowledge.d.ts +1 -1
  141. package/dist/operator-toolkit.d.ts +1 -1
  142. package/dist/operator-toolkit.js +9 -9
  143. package/dist/orchestration/maintenance.d.ts +2 -2
  144. package/dist/orchestration/maintenance.js +5 -5
  145. package/dist/{orchestrator-XPKhY2OV.d.ts → orchestrator-kOxredSr.d.ts} +20 -3
  146. package/dist/orchestrator.d.ts +4 -4
  147. package/dist/orchestrator.js +16 -16
  148. package/dist/patterns-cli.d.ts +1 -1
  149. package/dist/policy-runtime.d.ts +1 -1
  150. package/dist/provenance.d.ts +1 -1
  151. package/dist/qmd-recall-cache.d.ts +1 -1
  152. package/dist/qmd.d.ts +1 -1
  153. package/dist/recall-disclosure-escalation.d.ts +1 -1
  154. package/dist/recall-explain-renderer.d.ts +1 -1
  155. package/dist/recall-explain-renderer.js +3 -3
  156. package/dist/recall-planner-llm.d.ts +1 -1
  157. package/dist/recall-state.d.ts +1 -1
  158. package/dist/recall-tag-filter.d.ts +1 -1
  159. package/dist/recall-xray-cli.d.ts +1 -1
  160. package/dist/recall-xray-cli.js +4 -4
  161. package/dist/recall-xray-renderer.d.ts +1 -1
  162. package/dist/recall-xray-renderer.js +3 -3
  163. package/dist/recall-xray.d.ts +1 -1
  164. package/dist/recall-xray.js +2 -2
  165. package/dist/resolve-auth-token.d.ts +1 -1
  166. package/dist/resume-bundles.js +2 -2
  167. package/dist/retrieval-agents.d.ts +1 -1
  168. package/dist/retrieval-tiers.d.ts +1 -1
  169. package/dist/routing/engine.d.ts +1 -1
  170. package/dist/routing/store.d.ts +1 -1
  171. package/dist/schemas.d.ts +22 -22
  172. package/dist/search/embed-helper.d.ts +1 -1
  173. package/dist/search/factory.d.ts +1 -1
  174. package/dist/search/index.d.ts +1 -1
  175. package/dist/search/lancedb-backend.d.ts +1 -1
  176. package/dist/search/meilisearch-backend.d.ts +1 -1
  177. package/dist/search/noop-backend.d.ts +1 -1
  178. package/dist/search/orama-backend.d.ts +1 -1
  179. package/dist/search/port.d.ts +1 -1
  180. package/dist/search/remote-backend.d.ts +1 -1
  181. package/dist/{semantic-consolidation-DSjMDZ1u.d.ts → semantic-consolidation-B58v3S-a.d.ts} +1 -1
  182. package/dist/semantic-consolidation.d.ts +2 -2
  183. package/dist/semantic-consolidation.js +4 -4
  184. package/dist/semantic-rule-promotion.js +3 -3
  185. package/dist/semantic-rule-verifier.d.ts +1 -1
  186. package/dist/semantic-rule-verifier.js +3 -3
  187. package/dist/session-observer-bands.d.ts +1 -1
  188. package/dist/session-observer-state.d.ts +1 -1
  189. package/dist/shared-context/manager.d.ts +1 -1
  190. package/dist/signal.d.ts +1 -1
  191. package/dist/storage.d.ts +1 -1
  192. package/dist/storage.js +2 -2
  193. package/dist/summarizer.d.ts +1 -1
  194. package/dist/summary-snapshot.d.ts +1 -1
  195. package/dist/temporal-supersession.d.ts +1 -1
  196. package/dist/temporal-validity.d.ts +1 -1
  197. package/dist/threading.d.ts +1 -1
  198. package/dist/tier-migration.d.ts +1 -1
  199. package/dist/tier-routing.d.ts +1 -1
  200. package/dist/topics.d.ts +1 -1
  201. package/dist/transcript.d.ts +1 -1
  202. package/dist/transfer/types.d.ts +12 -12
  203. package/dist/{types-D9dwzU_x.d.ts → types-ACmDOScJ.d.ts} +36 -0
  204. package/dist/types.d.ts +1 -1
  205. package/dist/types.js +1 -1
  206. package/dist/utility-runtime.d.ts +1 -1
  207. package/dist/verified-recall.js +3 -3
  208. package/package.json +2 -2
  209. package/src/briefing.ts +32 -0
  210. package/src/config.ts +32 -0
  211. package/src/correction/index.ts +24 -0
  212. package/src/correction/passive-capture.test.ts +500 -0
  213. package/src/correction/passive-capture.ts +335 -0
  214. package/src/correction/passive-correction-detector.test.ts +263 -0
  215. package/src/correction/passive-correction-detector.ts +325 -0
  216. package/src/correction/passive-correction-notifications.ts +123 -0
  217. package/src/orchestrator.ts +196 -12
  218. package/src/types.ts +36 -0
  219. package/dist/chunk-2KF4HDG2.js +0 -24
  220. package/dist/chunk-2KF4HDG2.js.map +0 -1
  221. package/dist/chunk-CTUWGJRI.js.map +0 -1
  222. package/dist/chunk-JEIROMLX.js.map +0 -1
  223. package/dist/chunk-MSVSP5VO.js.map +0 -1
  224. package/dist/chunk-XCTDHF7U.js.map +0 -1
  225. package/dist/chunk-YAINYEM3.js.map +0 -1
  226. /package/dist/{chunk-XZTUMWKQ.js.map → chunk-2PTBLS2S.js.map} +0 -0
  227. /package/dist/{chunk-2Y6LMOQQ.js.map → chunk-2VHMVWPU.js.map} +0 -0
  228. /package/dist/{chunk-6UU43ZGD.js.map → chunk-4KZGLDJJ.js.map} +0 -0
  229. /package/dist/{chunk-K44MIN6D.js.map → chunk-5RHM43BJ.js.map} +0 -0
  230. /package/dist/{chunk-2B43BYYZ.js.map → chunk-72VDBALG.js.map} +0 -0
  231. /package/dist/{chunk-UY4JMEAK.js.map → chunk-7EEVXW7X.js.map} +0 -0
  232. /package/dist/{chunk-WI4QX7GH.js.map → chunk-A66YRSOS.js.map} +0 -0
  233. /package/dist/{chunk-VZQXJNBD.js.map → chunk-APUJIMDM.js.map} +0 -0
  234. /package/dist/{chunk-MPHDVHIB.js.map → chunk-DIFU33GU.js.map} +0 -0
  235. /package/dist/{chunk-DXGS5YOQ.js.map → chunk-EH27LHML.js.map} +0 -0
  236. /package/dist/{chunk-DZYARLJ7.js.map → chunk-HLIR3RJ7.js.map} +0 -0
  237. /package/dist/{chunk-6FUJ2VYY.js.map → chunk-JAKYZGV4.js.map} +0 -0
  238. /package/dist/{chunk-ZAVTEQFM.js.map → chunk-JIZGYQYH.js.map} +0 -0
  239. /package/dist/{chunk-QOIONSOI.js.map → chunk-KRGC4EDX.js.map} +0 -0
  240. /package/dist/{chunk-AJOIM6UR.js.map → chunk-L5ASZA2F.js.map} +0 -0
  241. /package/dist/{chunk-I56LKMF7.js.map → chunk-LXSG4L55.js.map} +0 -0
  242. /package/dist/{chunk-JVEMUX6L.js.map → chunk-OZ2KQ7HF.js.map} +0 -0
  243. /package/dist/{chunk-OUDAU3T5.js.map → chunk-P5IPFZVH.js.map} +0 -0
  244. /package/dist/{chunk-KHQ433G5.js.map → chunk-REWQAFSA.js.map} +0 -0
  245. /package/dist/{chunk-V7GDTQ6I.js.map → chunk-S35FX6MU.js.map} +0 -0
  246. /package/dist/{chunk-MTHNJCOJ.js.map → chunk-S3OPGGZB.js.map} +0 -0
  247. /package/dist/{chunk-BZAK3RBN.js.map → chunk-UCJ5CDTK.js.map} +0 -0
  248. /package/dist/{chunk-BF2426TN.js.map → chunk-URM3HW2R.js.map} +0 -0
  249. /package/dist/{chunk-VO4UICII.js.map → chunk-YTZLBBEB.js.map} +0 -0
  250. /package/dist/{chunk-OC56UWV5.js.map → chunk-YYOK4GVF.js.map} +0 -0
@@ -24,6 +24,10 @@ import { SmartBuffer } from "./buffer.js";
24
24
  import { chunkContent, type ChunkingConfig } from "./chunking.js";
25
25
  import { semanticChunkContent, type SemanticChunkResult } from "./semantic-chunking.js";
26
26
  import { ExtractionEngine } from "./extraction.js";
27
+ import { detectPassiveCorrections } from "./correction/passive-correction-detector.js";
28
+ import { capturePassiveCorrections, type PassiveCaptureConfig } from "./correction/passive-capture.js";
29
+ import { createCorrectionService } from "./correction/correction-access-wiring.js";
30
+ import type { CorrectionService } from "./correction/correction-service.js";
27
31
  import { isAboveImportanceThreshold, scoreImportance } from "./importance.js";
28
32
  import {
29
33
  judgeFactDurability,
@@ -338,6 +342,7 @@ import {
338
342
  } from "./namespaces/identity.js";
339
343
  import {
340
344
  canReadNamespace,
345
+ canWriteNamespace,
341
346
  defaultNamespaceForPrincipal,
342
347
  recallNamespacesForPrincipal,
343
348
  resolvePrincipal,
@@ -1980,6 +1985,16 @@ export class Orchestrator {
1980
1985
  { count: number; lastAccessed: string }
1981
1986
  > = new Map();
1982
1987
 
1988
+ // Passive correction capture (issue #1581) — dedup state + lazy service.
1989
+ private passiveCorrectionDedup: Set<string> = new Set();
1990
+ private _passiveCorrectionService: CorrectionService | null = null;
1991
+ private passiveCorrectionTelemetry: {
1992
+ detected: number;
1993
+ queued: number;
1994
+ autoApplied: number;
1995
+ suppressedReasonCounts: Record<string, number>;
1996
+ } = { detected: 0, queued: 0, autoApplied: 0, suppressedReasonCounts: {} };
1997
+
1983
1998
  // Background serial queue for extractions (agent_end optimization)
1984
1999
  // Queue stores promises that resolve when extraction should run
1985
2000
  private extractionQueue: Array<() => Promise<void>> = [];
@@ -13012,6 +13027,139 @@ export class Orchestrator {
13012
13027
  }
13013
13028
  }
13014
13029
 
13030
+ /**
13031
+ * Passive correction capture (issue #1581) — detects corrections expressed
13032
+ * passively in conversation turns and routes them to the Correction Contract
13033
+ * (#1580). Called from `runExtraction` after persistence completes.
13034
+ *
13035
+ * Thin wiring: delegates ALL correction logic to the detector + capture
13036
+ * modules + the CorrectionService. This method only checks gates, calls the
13037
+ * detector, and routes results. Fail-open: capture errors never block the
13038
+ * extraction return path.
13039
+ */
13040
+ private async maybeCapturePassiveCorrections(
13041
+ turns: readonly BufferTurn[],
13042
+ opts: {
13043
+ sessionKey: string;
13044
+ principal?: string;
13045
+ namespace: string;
13046
+ bufferKey: string;
13047
+ isLiveSession: boolean;
13048
+ },
13049
+ ): Promise<void> {
13050
+ const mode = this.config.correctionCaptureMode;
13051
+ if (mode === "off") return;
13052
+ if (!this.config.correctionEnabled) return;
13053
+
13054
+ try {
13055
+ const corrections = detectPassiveCorrections(
13056
+ turns.map((t) => ({ role: t.role, content: t.content })),
13057
+ );
13058
+ if (corrections.length === 0) return;
13059
+
13060
+ // Replay/import: force queue-only mode even if config says auto.
13061
+ const effectiveMode = opts.isLiveSession ? mode : "queue";
13062
+ const captureConfig: PassiveCaptureConfig = {
13063
+ mode: effectiveMode,
13064
+ confidenceFloor: this.config.correctionCaptureConfidenceFloor,
13065
+ autoApplyMaxAffected: this.config.correctionCaptureAutoApplyMaxAffected,
13066
+ };
13067
+
13068
+ const service = this.passiveCorrectionService();
13069
+ const result = await capturePassiveCorrections(
13070
+ corrections,
13071
+ {
13072
+ correctionEnabled: this.config.correctionEnabled,
13073
+ isLiveSession: opts.isLiveSession,
13074
+ bufferKey: opts.bufferKey,
13075
+ sessionKey: opts.sessionKey,
13076
+ principal: opts.principal,
13077
+ namespace: opts.namespace,
13078
+ },
13079
+ captureConfig,
13080
+ {
13081
+ planCorrection: (req) => service.plan(req),
13082
+ applyCorrection: (planId, applyOpts) => service.apply(planId, applyOpts),
13083
+ storageDir: async (ns) => (await this.getStorage(ns)).dir,
13084
+ // Resolve `[m:xxxx]` handles to concrete memory ids via the single
13085
+ // shared helper (#1582). Returns null on miss/ambiguity so the
13086
+ // capture loop drops the handle and the planner falls back to text
13087
+ // search (review: "memory handles not resolved").
13088
+ resolveHandle: (ref, sessionKey) => {
13089
+ try {
13090
+ return this.resolveMemoryIdOrHandle(ref, sessionKey);
13091
+ } catch {
13092
+ return null;
13093
+ }
13094
+ },
13095
+ },
13096
+ this.passiveCorrectionDedup,
13097
+ );
13098
+
13099
+ // Accumulate telemetry
13100
+ this.passiveCorrectionTelemetry.detected += result.telemetry.detected;
13101
+ this.passiveCorrectionTelemetry.queued += result.telemetry.queued;
13102
+ this.passiveCorrectionTelemetry.autoApplied += result.telemetry.autoApplied;
13103
+ for (const [reason, count] of Object.entries(result.telemetry.suppressedReasons)) {
13104
+ this.passiveCorrectionTelemetry.suppressedReasonCounts[reason] =
13105
+ (this.passiveCorrectionTelemetry.suppressedReasonCounts[reason] ?? 0) + count;
13106
+ }
13107
+ } catch (err) {
13108
+ // Fail-open: passive capture never blocks extraction.
13109
+ log.debug(
13110
+ `passive-correction: capture failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`,
13111
+ );
13112
+ }
13113
+ }
13114
+
13115
+ /** Lazily construct the CorrectionService for passive capture. Stateless
13116
+ * across requests (per #1580 design); cached for the orchestrator's life. */
13117
+ private passiveCorrectionService(): CorrectionService {
13118
+ if (this._passiveCorrectionService) return this._passiveCorrectionService;
13119
+ this._passiveCorrectionService = createCorrectionService({
13120
+ orchestrator: this,
13121
+ // Session-scoped write ACL (review: "passive capture bypasses write
13122
+ // ACL"). A correction detected in session S plans only against S readable
13123
+ // namespaces and applies only to writable ones (rule 42) — passive
13124
+ // capture never becomes a cross-tenant mutation vector. Mirrors the
13125
+ // access-service createCorrectionService wiring rather than bypassing it.
13126
+ resolveAuthorizedNamespace: async (req) => {
13127
+ const principal = req.principal || resolvePrincipal(req.sessionKey, this.config);
13128
+ const ns = req.namespace ?? defaultNamespaceForPrincipal(principal, this.config);
13129
+ if (!canWriteNamespace(principal, ns, this.config)) {
13130
+ throw new Error(
13131
+ `passive correction: namespace "${ns}" is not writable for principal ${principal ?? "(none)"}`,
13132
+ );
13133
+ }
13134
+ return ns;
13135
+ },
13136
+ resolveReadableNamespaces: (req) => {
13137
+ const principal = req.principal || resolvePrincipal(req.sessionKey, this.config);
13138
+ return recallNamespacesForPrincipal(principal, this.config);
13139
+ },
13140
+ canWriteNamespace: async (req) => {
13141
+ const principal = req.principal || resolvePrincipal(req.sessionKey, this.config);
13142
+ return canWriteNamespace(principal, req.namespace, this.config);
13143
+ },
13144
+ llmComplete: async ({ system, user }) => {
13145
+ const llmResult = await this.localLlm.chatCompletion(
13146
+ [
13147
+ { role: "system", content: system },
13148
+ { role: "user", content: user },
13149
+ ],
13150
+ { operation: "correction-classify", priority: "background" },
13151
+ );
13152
+ if (!llmResult) {
13153
+ throw new Error(
13154
+ "passive correction classify+draft: local LLM unavailable (disabled or in cooldown)",
13155
+ );
13156
+ }
13157
+ return llmResult.content;
13158
+ },
13159
+ });
13160
+ return this._passiveCorrectionService;
13161
+ }
13162
+
13015
13163
  private async runExtraction(
13016
13164
  turns: BufferTurn[],
13017
13165
  options: {
@@ -13097,13 +13245,13 @@ export class Orchestrator {
13097
13245
  );
13098
13246
  if (targetTurns.length === 0) {
13099
13247
  log.debug("skipping extraction: no non-context turns after normalization");
13248
+ // Context-only turns may still contain corrections (review: "context-only
13249
+ // turns skip capture"). Scan normalizedTurns before clearing the buffer.
13250
+ if (normalizedTurns.length > 0) {
13251
+ await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], { sessionKey, principal: resolvePrincipal(sessionKey, this.config), namespace: this.resolveSelfNamespace(sessionKey), bufferKey, isLiveSession: clearBufferAfterExtraction });
13252
+ }
13100
13253
  await clearBuffer();
13101
- return {
13102
- status: "skipped",
13103
- reason: "empty_normalized_turns",
13104
- persistedCount: 0,
13105
- durableOutputCount: 0,
13106
- };
13254
+ return { status: "skipped", reason: "empty_normalized_turns", persistedCount: 0, durableOutputCount: 0 };
13107
13255
  }
13108
13256
  const sourceValidAt = latestSourceValidAtFromTurns(targetTurns);
13109
13257
  throwIfDeadlineExceeded("before_extract");
@@ -13123,6 +13271,30 @@ export class Orchestrator {
13123
13271
  `skipping extraction: below threshold (totalChars=${totalChars}, userTurns=${userTurns.length})`,
13124
13272
  );
13125
13273
  await clearBuffer();
13274
+ // Passive correction capture runs even when extraction is skipped for
13275
+ // being below the char/user-turn threshold (review: "skipped extraction
13276
+ // skips capture"). A short correction like "stop using Vim" is under
13277
+ // extractionMinChars but is exactly the high-value case this feature
13278
+ // exists for. The write namespace resolves through the SAME scope-profile
13279
+ // plan as the full extraction path so corrections target the active
13280
+ // profile's write layer, not just resolveSelfNamespace (review:
13281
+ // "below-threshold wrong namespace"). The ACL in passiveCorrectionService
13282
+ // authorizes the actual plan/apply.
13283
+ {
13284
+ const capturePrincipal =
13285
+ typeof options.principalOverride === "string" && options.principalOverride.length > 0
13286
+ ? options.principalOverride
13287
+ : resolvePrincipal(sessionKey, this.config);
13288
+ const captureWO = typeof options.writeNamespaceOverride === "string" && options.writeNamespaceOverride.length > 0
13289
+ ? options.writeNamespaceOverride : undefined;
13290
+ const captureCodingCtx = sessionKey ? this.getCodingContextForSession(sessionKey) : null;
13291
+ const captureCodingOv = resolveCodingNamespaceOverlay(captureCodingCtx, this.config.codingMode, this.config.defaultNamespace);
13292
+ const captureScopePlan = resolveScopeProfilePlan({ config: this.config, principal: capturePrincipal, codingContext: captureCodingCtx, codingOverlay: captureCodingOv });
13293
+ const captureNamespace = captureWO ?? captureScopePlan?.writeNamespace ?? this.resolveSelfNamespace(sessionKey);
13294
+ await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], {
13295
+ sessionKey, principal: capturePrincipal, namespace: captureNamespace, bufferKey, isLiveSession: clearBufferAfterExtraction,
13296
+ });
13297
+ }
13126
13298
  return {
13127
13299
  status: "skipped",
13128
13300
  reason: "below_threshold",
@@ -13322,13 +13494,12 @@ export class Orchestrator {
13322
13494
  meta.lastExtractionAt = new Date().toISOString();
13323
13495
  await storage.saveMeta(meta);
13324
13496
  }
13497
+ // Correction-only turns that meet char/user-turn thresholds but yield
13498
+ // zero facts still need passive capture (review: "empty extraction skips
13499
+ // capture"). selfNamespace/principal already resolved above.
13500
+ await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], { sessionKey, principal, namespace: selfNamespace, bufferKey, isLiveSession: clearBufferAfterExtraction });
13325
13501
  await clearBuffer();
13326
- return {
13327
- status: "skipped",
13328
- reason: "empty_extraction_result",
13329
- persistedCount: 0,
13330
- durableOutputCount: 0,
13331
- };
13502
+ return { status: "skipped", reason: "empty_extraction_result", persistedCount: 0, durableOutputCount: 0 };
13332
13503
  }
13333
13504
 
13334
13505
  let threadIdForExtraction: string | null = null;
@@ -13445,6 +13616,19 @@ export class Orchestrator {
13445
13616
 
13446
13617
  await clearBuffer({ ignoreAbort: true });
13447
13618
 
13619
+ // Passive correction capture (issue #1581) — detect corrections expressed
13620
+ // passively in the extracted turns and route to the Correction Contract.
13621
+ // Runs AFTER persistence + buffer clear so a capture failure never blocks
13622
+ // the extraction return. `clearBufferAfterExtraction` gates live-session
13623
+ // auto-apply (replay/import → queue-only).
13624
+ await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], {
13625
+ sessionKey,
13626
+ principal,
13627
+ namespace: selfNamespace,
13628
+ bufferKey,
13629
+ isLiveSession: clearBufferAfterExtraction,
13630
+ });
13631
+
13448
13632
  // Build memory box from this extraction (v8.0 Phase 2A)
13449
13633
  // Topics are derived from the current extraction's facts and entities only —
13450
13634
  // not from readAllMemories() — so box topics accurately reflect the current
package/src/types.ts CHANGED
@@ -862,6 +862,29 @@ export interface PluginConfig {
862
862
  * `24`. Parsed from `correction.planTtlHours` / `correctionPlanTtlHours`.
863
863
  */
864
864
  correctionPlanTtlHours: number;
865
+ /**
866
+ * Passive correction capture (issue #1581) — detects corrections expressed
867
+ * passively in conversation turns during extraction and routes them to the
868
+ * Correction Contract (#1580). `"off"` disables detection entirely; `"queue"`
869
+ * plans corrections for human review (conservative); `"auto"` applies
870
+ * immediately when all safety guards pass, otherwise falls back to queue.
871
+ * Default `"off"` — operators opt in (rule 48). Parsed from
872
+ * `correctionCapture.mode` / `correctionCaptureMode`.
873
+ */
874
+ correctionCaptureMode: "off" | "queue" | "auto";
875
+ /**
876
+ * Minimum planner confidence for auto-apply (issue #1581). Plans below this
877
+ * floor are queued even in `"auto"` mode. Default `0.85`. Parsed from
878
+ * `correctionCapture.confidenceFloor` / `correctionCaptureConfidenceFloor`.
879
+ */
880
+ correctionCaptureConfidenceFloor: number;
881
+ /**
882
+ * Maximum affected memories for auto-apply (issue #1581). Plans touching more
883
+ * memories than this are queued even in `"auto"` mode — a blast-radius cap.
884
+ * Default `2`. Parsed from `correctionCapture.autoApplyMaxAffected` /
885
+ * `correctionCaptureAutoApplyMaxAffected`.
886
+ */
887
+ correctionCaptureAutoApplyMaxAffected: number;
865
888
  // Tombstones — non-resurrection invariant (issue #1579).
866
889
  /**
867
890
  * Master gate for the tombstone non-resurrection invariant. When `true`
@@ -2178,6 +2201,19 @@ export interface BriefingResult {
2178
2201
  * Allows callers to distinguish "no events today" from "source unavailable".
2179
2202
  */
2180
2203
  calendarSourceErrors?: BriefingCalendarSourceError[];
2204
+ /**
2205
+ * Auto-applied passive corrections drained from the notification queue at
2206
+ * briefing time (issue #1581). Each carries a one-line summary and the undo
2207
+ * command. Drained exactly once — the queue file is cleared on read, so a
2208
+ * notification appears in at most one briefing. Absent when the correction
2209
+ * capture feature is off or no corrections were auto-applied.
2210
+ */
2211
+ correctionNotifications?: ReadonlyArray<{
2212
+ planId: string;
2213
+ summary: string;
2214
+ undoCommand: string;
2215
+ createdAt: string;
2216
+ }>;
2181
2217
  }
2182
2218
 
2183
2219
  /**
@@ -1,24 +0,0 @@
1
- // src/procedural/reinforcement-core.ts
2
- function clusterByKey(items, keyFn) {
3
- const clusters = /* @__PURE__ */ new Map();
4
- for (const item of items) {
5
- const key = keyFn(item);
6
- if (typeof key !== "string") {
7
- throw new TypeError(
8
- `clusterByKey: keyFn must return a string, got ${key === null ? "null" : typeof key}`
9
- );
10
- }
11
- const existing = clusters.get(key);
12
- if (existing) {
13
- existing.push(item);
14
- } else {
15
- clusters.set(key, [item]);
16
- }
17
- }
18
- return clusters;
19
- }
20
-
21
- export {
22
- clusterByKey
23
- };
24
- //# sourceMappingURL=chunk-2KF4HDG2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/procedural/reinforcement-core.ts"],"sourcesContent":["/**\n * Generic reinforcement-core primitives extracted from `procedure-miner.ts`\n * (issue #687 PR 1/4). Procedure-specific scoring (success rate, step\n * normalization) intentionally stays in the miner — this module only\n * exposes category-agnostic clustering and cluster summarization helpers\n * so future PRs can run reinforcement across non-procedural categories.\n *\n * Pure refactor — no behavior change.\n */\n\n/**\n * Group `items` into clusters keyed by `keyFn(item)`.\n *\n * - Preserves the original input order within each cluster's array.\n * - The returned `Map` insertion order matches first-seen key order, so\n * downstream iteration is deterministic for a given input.\n * - Throws `TypeError` if `keyFn` returns a non-string (e.g. `undefined`,\n * `null`, or a number). Callers must produce a stable string key.\n */\nexport function clusterByKey<T>(items: readonly T[], keyFn: (item: T) => string): Map<string, T[]> {\n const clusters = new Map<string, T[]>();\n for (const item of items) {\n const key = keyFn(item);\n if (typeof key !== \"string\") {\n throw new TypeError(\n `clusterByKey: keyFn must return a string, got ${key === null ? \"null\" : typeof key}`,\n );\n }\n const existing = clusters.get(key);\n if (existing) {\n existing.push(item);\n } else {\n clusters.set(key, [item]);\n }\n }\n return clusters;\n}\n\nexport interface ClusterSummary {\n /** Number of items in the cluster. */\n count: number;\n /** Earliest timestamp seen in the cluster (string min via `localeCompare`). */\n firstSeen: string;\n /** Latest timestamp seen in the cluster (string max via `localeCompare`). */\n lastSeen: string;\n}\n\n/**\n * Summarize a cluster by counting items and tracking earliest/latest\n * timestamps. Timestamp comparison uses `String#localeCompare`, which is\n * correct for ISO-8601 strings (lexicographic order matches chronological\n * order).\n *\n * - Throws `RangeError` on empty clusters — `firstSeen`/`lastSeen` are not\n * meaningful without at least one item.\n * - When all timestamps are equal, `firstSeen === lastSeen`.\n */\nexport function summarizeCluster<T>(\n cluster: readonly T[],\n extractTimestamp: (item: T) => string,\n): ClusterSummary {\n if (cluster.length === 0) {\n throw new RangeError(\"summarizeCluster: cluster must contain at least one item\");\n }\n let firstSeen = extractTimestamp(cluster[0]);\n let lastSeen = firstSeen;\n for (let i = 1; i < cluster.length; i += 1) {\n const ts = extractTimestamp(cluster[i]);\n if (ts.localeCompare(firstSeen) < 0) firstSeen = ts;\n if (ts.localeCompare(lastSeen) > 0) lastSeen = ts;\n }\n return { count: cluster.length, firstSeen, lastSeen };\n}\n"],"mappings":";AAmBO,SAAS,aAAgB,OAAqB,OAA8C;AACjG,QAAM,WAAW,oBAAI,IAAiB;AACtC,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,MAAM,IAAI;AACtB,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,IAAI;AAAA,QACR,iDAAiD,QAAQ,OAAO,SAAS,OAAO,GAAG;AAAA,MACrF;AAAA,IACF;AACA,UAAM,WAAW,SAAS,IAAI,GAAG;AACjC,QAAI,UAAU;AACZ,eAAS,KAAK,IAAI;AAAA,IACpB,OAAO;AACL,eAAS,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;","names":[]}