@remnic/core 9.3.621 → 9.3.623

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 (164) hide show
  1. package/dist/access-cli.js +28 -28
  2. package/dist/access-http.js +11 -11
  3. package/dist/access-mcp.js +10 -10
  4. package/dist/access-service.js +9 -9
  5. package/dist/briefing.js +6 -6
  6. package/dist/buffer-surprise.js +3 -3
  7. package/dist/calibration.js +2 -2
  8. package/dist/causal-consolidation.js +10 -10
  9. package/dist/{chunk-GLPBYIXN.js → chunk-2L54V4ZO.js} +3 -3
  10. package/dist/{chunk-PP2JH3GP.js → chunk-2UFQYU5F.js} +2 -2
  11. package/dist/{chunk-XAZOWLW4.js → chunk-3VONWEQB.js} +3 -3
  12. package/dist/{chunk-BF7ZRHH2.js → chunk-66SLUXKM.js} +2 -2
  13. package/dist/{chunk-3HPAPHUK.js → chunk-6KYMPV2O.js} +12 -11
  14. package/dist/chunk-6KYMPV2O.js.map +1 -0
  15. package/dist/{chunk-S53OYO3F.js → chunk-7VFZTJ7K.js} +2 -2
  16. package/dist/{chunk-4RR6ROTB.js → chunk-AGNBY3VG.js} +2 -2
  17. package/dist/{chunk-YEEAADCI.js → chunk-AYHXQR53.js} +2 -2
  18. package/dist/{chunk-IEUU7O4F.js → chunk-BNW5NJJH.js} +2 -2
  19. package/dist/{chunk-6GMPIJAZ.js → chunk-C3IW2F5Z.js} +2 -2
  20. package/dist/{chunk-4EWRLK3C.js → chunk-C4PZTWTG.js} +16 -16
  21. package/dist/{chunk-QVO4YOB7.js → chunk-D2B22JDF.js} +2 -2
  22. package/dist/{chunk-HA5SI4GK.js → chunk-FMGWXIES.js} +4 -4
  23. package/dist/{chunk-B6SU7YSE.js → chunk-GLWW3EJQ.js} +5 -5
  24. package/dist/{chunk-5BTCT236.js → chunk-GYTVOLNX.js} +2 -2
  25. package/dist/{chunk-IMA6GU4Y.js → chunk-H3PHZLMF.js} +3 -3
  26. package/dist/chunk-H3PHZLMF.js.map +1 -0
  27. package/dist/{chunk-TIPYPLLQ.js → chunk-I6UCUHLK.js} +4 -4
  28. package/dist/{chunk-2I2MDQIB.js → chunk-I74SUMNI.js} +2 -2
  29. package/dist/chunk-I74SUMNI.js.map +1 -0
  30. package/dist/{chunk-4H5ZJHEN.js → chunk-J6A3CX5N.js} +8 -3
  31. package/dist/{chunk-4H5ZJHEN.js.map → chunk-J6A3CX5N.js.map} +1 -1
  32. package/dist/{chunk-DEVUWMME.js → chunk-KGIGRNR6.js} +2 -2
  33. package/dist/{chunk-F4QTFIB4.js → chunk-KQFQ3IS5.js} +6 -6
  34. package/dist/{chunk-QSVPYQPG.js → chunk-LDXUBPMO.js} +2 -2
  35. package/dist/chunk-LDXUBPMO.js.map +1 -0
  36. package/dist/{chunk-JFEKNTX7.js → chunk-LN4YGHTM.js} +6 -2
  37. package/dist/chunk-LN4YGHTM.js.map +1 -0
  38. package/dist/{chunk-7XYTQGCC.js → chunk-MAV46GWQ.js} +2 -2
  39. package/dist/{chunk-KILOTVIF.js → chunk-MB5RSUW6.js} +2 -2
  40. package/dist/{chunk-WB3LYXC5.js → chunk-MON3LMO7.js} +3 -3
  41. package/dist/{chunk-APRRL26Q.js → chunk-O4UNM6OR.js} +2 -2
  42. package/dist/{chunk-AZDOWD2L.js → chunk-OZXVGYGZ.js} +2 -2
  43. package/dist/{chunk-WCYKT2DE.js → chunk-P4BC54KI.js} +23 -14
  44. package/dist/chunk-P4BC54KI.js.map +1 -0
  45. package/dist/{chunk-7MLB4NCL.js → chunk-PJGB7XRR.js} +6 -6
  46. package/dist/chunk-PJGB7XRR.js.map +1 -0
  47. package/dist/{chunk-DEPRLVLK.js → chunk-QFQQFX2H.js} +3 -3
  48. package/dist/{chunk-DEPRLVLK.js.map → chunk-QFQQFX2H.js.map} +1 -1
  49. package/dist/{chunk-QPD426WT.js → chunk-R3OQGYOU.js} +2 -2
  50. package/dist/{chunk-UZB5KHKX.js → chunk-RGMVMVMF.js} +2 -2
  51. package/dist/chunk-RGMVMVMF.js.map +1 -0
  52. package/dist/{chunk-O3U5BPUP.js → chunk-RKW6QR7W.js} +23 -19
  53. package/dist/chunk-RKW6QR7W.js.map +1 -0
  54. package/dist/{chunk-C6C7XVKG.js → chunk-UGEBPVNI.js} +3 -3
  55. package/dist/{chunk-4WMCPJWX.js → chunk-UQ7RN5HK.js} +22 -13
  56. package/dist/chunk-UQ7RN5HK.js.map +1 -0
  57. package/dist/{chunk-XQNPGNKK.js → chunk-W3BKVM64.js} +2 -2
  58. package/dist/{chunk-K5O2QY6T.js → chunk-YTWNKQ2G.js} +2 -2
  59. package/dist/chunk-YTWNKQ2G.js.map +1 -0
  60. package/dist/{chunk-2SGJY2UY.js → chunk-Z3CCEP6F.js} +3 -3
  61. package/dist/{chunk-THTIZJZA.js → chunk-ZJSZNTEI.js} +4 -4
  62. package/dist/{chunk-CIOMS6DI.js → chunk-ZZPIJPPD.js} +2 -2
  63. package/dist/chunking.js +1 -1
  64. package/dist/cli.js +23 -23
  65. package/dist/compounding/engine.js +6 -6
  66. package/dist/connectors/codex-materialize-runner.js +7 -7
  67. package/dist/connectors/codex-materialize.js +1 -1
  68. package/dist/connectors/index.js +7 -7
  69. package/dist/contradiction/index.js +2 -2
  70. package/dist/{contradiction-scan-GD7KUFWS.js → contradiction-scan-AZTGFMPY.js} +3 -3
  71. package/dist/entity-retrieval.js +6 -6
  72. package/dist/explicit-capture.js +1 -1
  73. package/dist/extraction-judge.js +3 -3
  74. package/dist/extraction.js +3 -3
  75. package/dist/fallback-llm.js +2 -2
  76. package/dist/identity-continuity.js +1 -1
  77. package/dist/index.js +45 -42
  78. package/dist/index.js.map +1 -1
  79. package/dist/json-extract.js +1 -1
  80. package/dist/lcm/engine.js +3 -3
  81. package/dist/lcm/index.js +3 -3
  82. package/dist/lcm/schema.js +2 -2
  83. package/dist/maintenance/memory-governance.js +6 -6
  84. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -6
  85. package/dist/maintenance/rebuild-memory-projection.js +7 -7
  86. package/dist/memory-projection-store.js +2 -2
  87. package/dist/namespaces/migrate.js +7 -7
  88. package/dist/namespaces/storage.js +6 -6
  89. package/dist/operator-toolkit.js +9 -9
  90. package/dist/orchestrator.js +25 -25
  91. package/dist/peers/index.js +1 -1
  92. package/dist/recall-planner-llm.js +2 -2
  93. package/dist/runtime/better-sqlite.d.ts +2 -1
  94. package/dist/runtime/better-sqlite.js +3 -1
  95. package/dist/schemas.d.ts +22 -22
  96. package/dist/semantic-chunking.js +2 -2
  97. package/dist/semantic-consolidation.js +8 -8
  98. package/dist/semantic-rule-promotion.js +6 -6
  99. package/dist/semantic-rule-verifier.js +6 -6
  100. package/dist/source-attribution.js +1 -1
  101. package/dist/storage.js +5 -5
  102. package/dist/summarizer.js +3 -3
  103. package/dist/temporal-supersession.js +1 -1
  104. package/dist/transfer/export-sqlite.js +2 -2
  105. package/dist/transfer/import-sqlite.js +2 -2
  106. package/dist/transfer/types.d.ts +12 -12
  107. package/dist/verified-recall.js +6 -6
  108. package/package.json +1 -1
  109. package/src/chunking.ts +38 -23
  110. package/src/coding/review-context.ts +7 -1
  111. package/src/connectors/codex-materialize.ts +6 -1
  112. package/src/explicit-capture.ts +7 -2
  113. package/src/identity-continuity.ts +7 -1
  114. package/src/json-extract.ts +4 -1
  115. package/src/orchestrator.ts +5 -1
  116. package/src/peers/profile-reasoner.ts +4 -1
  117. package/src/runtime/better-sqlite.test.ts +29 -0
  118. package/src/runtime/better-sqlite.ts +30 -8
  119. package/src/semantic-chunking.ts +32 -16
  120. package/src/semantic-consolidation.ts +4 -1
  121. package/src/source-attribution.test.ts +21 -0
  122. package/src/source-attribution.ts +17 -2
  123. package/src/storage.ts +11 -2
  124. package/src/temporal-supersession.ts +4 -1
  125. package/dist/chunk-2I2MDQIB.js.map +0 -1
  126. package/dist/chunk-3HPAPHUK.js.map +0 -1
  127. package/dist/chunk-4WMCPJWX.js.map +0 -1
  128. package/dist/chunk-7MLB4NCL.js.map +0 -1
  129. package/dist/chunk-IMA6GU4Y.js.map +0 -1
  130. package/dist/chunk-JFEKNTX7.js.map +0 -1
  131. package/dist/chunk-K5O2QY6T.js.map +0 -1
  132. package/dist/chunk-O3U5BPUP.js.map +0 -1
  133. package/dist/chunk-QSVPYQPG.js.map +0 -1
  134. package/dist/chunk-UZB5KHKX.js.map +0 -1
  135. package/dist/chunk-WCYKT2DE.js.map +0 -1
  136. /package/dist/{chunk-GLPBYIXN.js.map → chunk-2L54V4ZO.js.map} +0 -0
  137. /package/dist/{chunk-PP2JH3GP.js.map → chunk-2UFQYU5F.js.map} +0 -0
  138. /package/dist/{chunk-XAZOWLW4.js.map → chunk-3VONWEQB.js.map} +0 -0
  139. /package/dist/{chunk-BF7ZRHH2.js.map → chunk-66SLUXKM.js.map} +0 -0
  140. /package/dist/{chunk-S53OYO3F.js.map → chunk-7VFZTJ7K.js.map} +0 -0
  141. /package/dist/{chunk-4RR6ROTB.js.map → chunk-AGNBY3VG.js.map} +0 -0
  142. /package/dist/{chunk-YEEAADCI.js.map → chunk-AYHXQR53.js.map} +0 -0
  143. /package/dist/{chunk-IEUU7O4F.js.map → chunk-BNW5NJJH.js.map} +0 -0
  144. /package/dist/{chunk-6GMPIJAZ.js.map → chunk-C3IW2F5Z.js.map} +0 -0
  145. /package/dist/{chunk-4EWRLK3C.js.map → chunk-C4PZTWTG.js.map} +0 -0
  146. /package/dist/{chunk-QVO4YOB7.js.map → chunk-D2B22JDF.js.map} +0 -0
  147. /package/dist/{chunk-HA5SI4GK.js.map → chunk-FMGWXIES.js.map} +0 -0
  148. /package/dist/{chunk-B6SU7YSE.js.map → chunk-GLWW3EJQ.js.map} +0 -0
  149. /package/dist/{chunk-5BTCT236.js.map → chunk-GYTVOLNX.js.map} +0 -0
  150. /package/dist/{chunk-TIPYPLLQ.js.map → chunk-I6UCUHLK.js.map} +0 -0
  151. /package/dist/{chunk-DEVUWMME.js.map → chunk-KGIGRNR6.js.map} +0 -0
  152. /package/dist/{chunk-F4QTFIB4.js.map → chunk-KQFQ3IS5.js.map} +0 -0
  153. /package/dist/{chunk-7XYTQGCC.js.map → chunk-MAV46GWQ.js.map} +0 -0
  154. /package/dist/{chunk-KILOTVIF.js.map → chunk-MB5RSUW6.js.map} +0 -0
  155. /package/dist/{chunk-WB3LYXC5.js.map → chunk-MON3LMO7.js.map} +0 -0
  156. /package/dist/{chunk-APRRL26Q.js.map → chunk-O4UNM6OR.js.map} +0 -0
  157. /package/dist/{chunk-AZDOWD2L.js.map → chunk-OZXVGYGZ.js.map} +0 -0
  158. /package/dist/{chunk-QPD426WT.js.map → chunk-R3OQGYOU.js.map} +0 -0
  159. /package/dist/{chunk-C6C7XVKG.js.map → chunk-UGEBPVNI.js.map} +0 -0
  160. /package/dist/{chunk-XQNPGNKK.js.map → chunk-W3BKVM64.js.map} +0 -0
  161. /package/dist/{chunk-2SGJY2UY.js.map → chunk-Z3CCEP6F.js.map} +0 -0
  162. /package/dist/{chunk-THTIZJZA.js.map → chunk-ZJSZNTEI.js.map} +0 -0
  163. /package/dist/{chunk-CIOMS6DI.js.map → chunk-ZZPIJPPD.js.map} +0 -0
  164. /package/dist/{contradiction-scan-GD7KUFWS.js.map → contradiction-scan-AZTGFMPY.js.map} +0 -0
@@ -12,13 +12,13 @@ import {
12
12
  } from "./chunk-HDDRVXX4.js";
13
13
  import {
14
14
  importSqlite
15
- } from "./chunk-4RR6ROTB.js";
15
+ } from "./chunk-AGNBY3VG.js";
16
16
  import {
17
17
  exportJsonBundle
18
18
  } from "./chunk-UP6MOYCB.js";
19
19
  import {
20
20
  exportSqlite
21
- } from "./chunk-S53OYO3F.js";
21
+ } from "./chunk-7VFZTJ7K.js";
22
22
  import {
23
23
  detectImportFormat
24
24
  } from "./chunk-OZKVVUJB.js";
@@ -53,12 +53,12 @@ import {
53
53
  } from "./chunk-D6RIFG4O.js";
54
54
  import {
55
55
  rebuildMemoryLifecycleLedger
56
- } from "./chunk-APRRL26Q.js";
56
+ } from "./chunk-O4UNM6OR.js";
57
57
  import {
58
58
  rebuildMemoryProjection,
59
59
  repairMemoryProjection,
60
60
  verifyMemoryProjection
61
- } from "./chunk-B6SU7YSE.js";
61
+ } from "./chunk-GLWW3EJQ.js";
62
62
  import {
63
63
  getBulkImportSource,
64
64
  listBulkImportSources,
@@ -70,7 +70,7 @@ import {
70
70
  } from "./chunk-YR6GIWWY.js";
71
71
  import {
72
72
  promoteSemanticRuleFromMemory
73
- } from "./chunk-CIOMS6DI.js";
73
+ } from "./chunk-ZZPIJPPD.js";
74
74
  import {
75
75
  buildResumeBundleFromState,
76
76
  getResumeBundleStatus,
@@ -89,12 +89,12 @@ import {
89
89
  runOperatorInventory,
90
90
  runOperatorRepair,
91
91
  runOperatorSetup
92
- } from "./chunk-HA5SI4GK.js";
92
+ } from "./chunk-FMGWXIES.js";
93
93
  import {
94
94
  listNamespaces,
95
95
  runNamespaceMigration,
96
96
  verifyNamespaces
97
- } from "./chunk-YEEAADCI.js";
97
+ } from "./chunk-AYHXQR53.js";
98
98
  import {
99
99
  collectPatternMemories,
100
100
  explainPatternMemory,
@@ -124,13 +124,13 @@ import {
124
124
  } from "./chunk-TERNBNJB.js";
125
125
  import {
126
126
  searchVerifiedEpisodes
127
- } from "./chunk-QPD426WT.js";
127
+ } from "./chunk-R3OQGYOU.js";
128
128
  import {
129
129
  ThreadingManager
130
130
  } from "./chunk-W4RVMTHR.js";
131
131
  import {
132
132
  searchVerifiedSemanticRules
133
- } from "./chunk-PP2JH3GP.js";
133
+ } from "./chunk-2UFQYU5F.js";
134
134
  import {
135
135
  getWorkProductLedgerStatus,
136
136
  recordWorkProductLedgerEntry,
@@ -200,7 +200,7 @@ import {
200
200
  } from "./chunk-3UXOZBHV.js";
201
201
  import {
202
202
  chunkContent
203
- } from "./chunk-4WMCPJWX.js";
203
+ } from "./chunk-UQ7RN5HK.js";
204
204
  import {
205
205
  parseConfig
206
206
  } from "./chunk-5GOMXHLC.js";
@@ -209,13 +209,13 @@ import {
209
209
  } from "./chunk-OADWQ5CR.js";
210
210
  import {
211
211
  EngramAccessHttpServer
212
- } from "./chunk-THTIZJZA.js";
212
+ } from "./chunk-ZJSZNTEI.js";
213
213
  import {
214
214
  EngramMcpServer
215
- } from "./chunk-TIPYPLLQ.js";
215
+ } from "./chunk-I6UCUHLK.js";
216
216
  import {
217
217
  EngramAccessService
218
- } from "./chunk-F4QTFIB4.js";
218
+ } from "./chunk-KQFQ3IS5.js";
219
219
  import {
220
220
  WorkStorage
221
221
  } from "./chunk-GDB4J2H3.js";
@@ -229,7 +229,7 @@ import {
229
229
  readMemoryGovernanceRunArtifact,
230
230
  restoreMemoryGovernanceRun,
231
231
  runMemoryGovernance
232
- } from "./chunk-5BTCT236.js";
232
+ } from "./chunk-GYTVOLNX.js";
233
233
  import {
234
234
  getTrustZoneStoreStatus,
235
235
  promoteTrustZoneRecord,
@@ -5916,7 +5916,7 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
5916
5916
  });
5917
5917
  reviewCmd.command("scan").description("Run an on-demand contradiction scan").option("--namespace <ns>", "Namespace to scan").action(async (...args) => {
5918
5918
  const options = args[0] ?? {};
5919
- const { runContradictionScan } = await import("./contradiction-scan-GD7KUFWS.js");
5919
+ const { runContradictionScan } = await import("./contradiction-scan-AZTGFMPY.js");
5920
5920
  console.log("Running contradiction scan...");
5921
5921
  const result = await runContradictionScan({
5922
5922
  storage: orchestrator.storage,
@@ -6623,4 +6623,4 @@ export {
6623
6623
  resolveMemoryDirForNamespace,
6624
6624
  registerCli
6625
6625
  };
6626
- //# sourceMappingURL=chunk-4EWRLK3C.js.map
6626
+ //# sourceMappingURL=chunk-C4PZTWTG.js.map
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  ensureLcmStateDir,
12
12
  openLcmDatabase
13
- } from "./chunk-7XYTQGCC.js";
13
+ } from "./chunk-MAV46GWQ.js";
14
14
  import {
15
15
  LcmSummarizer
16
16
  } from "./chunk-5VDJMYTF.js";
@@ -513,4 +513,4 @@ export {
513
513
  extractLcmConfig,
514
514
  LcmEngine
515
515
  };
516
- //# sourceMappingURL=chunk-QVO4YOB7.js.map
516
+ //# sourceMappingURL=chunk-D2B22JDF.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  listNamespaces
3
- } from "./chunk-YEEAADCI.js";
3
+ } from "./chunk-AYHXQR53.js";
4
4
  import {
5
5
  runConsolidationProvenanceCheck
6
6
  } from "./chunk-AL4RAJL5.js";
@@ -36,13 +36,13 @@ import {
36
36
  import {
37
37
  listMemoryGovernanceRuns,
38
38
  readMemoryGovernanceRunArtifact
39
- } from "./chunk-5BTCT236.js";
39
+ } from "./chunk-GYTVOLNX.js";
40
40
  import {
41
41
  analyzeGraphHealth
42
42
  } from "./chunk-XL7FK7PJ.js";
43
43
  import {
44
44
  StorageManager
45
- } from "./chunk-7MLB4NCL.js";
45
+ } from "./chunk-PJGB7XRR.js";
46
46
  import {
47
47
  lintWorkspaceFiles
48
48
  } from "./chunk-DM2T26WE.js";
@@ -1336,4 +1336,4 @@ export {
1336
1336
  runBenchmarkRecall,
1337
1337
  runOperatorRepair
1338
1338
  };
1339
- //# sourceMappingURL=chunk-HA5SI4GK.js.map
1339
+ //# sourceMappingURL=chunk-FMGWXIES.js.map
@@ -10,10 +10,10 @@ import {
10
10
  import {
11
11
  listMemoryGovernanceRuns,
12
12
  readMemoryGovernanceRunArtifact
13
- } from "./chunk-5BTCT236.js";
13
+ } from "./chunk-GYTVOLNX.js";
14
14
  import {
15
15
  StorageManager
16
- } from "./chunk-7MLB4NCL.js";
16
+ } from "./chunk-PJGB7XRR.js";
17
17
  import {
18
18
  MEMORY_LIFECYCLE_EVENT_SORT_ORDER,
19
19
  buildLifecycleEventsForMemory,
@@ -35,10 +35,10 @@ import {
35
35
  readProjectedEntityMentions,
36
36
  readProjectedGovernanceRecord,
37
37
  readProjectedNativeKnowledgeChunks
38
- } from "./chunk-KILOTVIF.js";
38
+ } from "./chunk-MB5RSUW6.js";
39
39
  import {
40
40
  openBetterSqlite3
41
- } from "./chunk-3HPAPHUK.js";
41
+ } from "./chunk-6KYMPV2O.js";
42
42
 
43
43
  // src/maintenance/rebuild-memory-projection.ts
44
44
  import path from "path";
@@ -929,4 +929,4 @@ export {
929
929
  verifyMemoryProjection,
930
930
  repairMemoryProjection
931
931
  };
932
- //# sourceMappingURL=chunk-B6SU7YSE.js.map
932
+ //# sourceMappingURL=chunk-GLWW3EJQ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  StorageManager
3
- } from "./chunk-7MLB4NCL.js";
3
+ } from "./chunk-PJGB7XRR.js";
4
4
  import {
5
5
  decideLifecycleTransition
6
6
  } from "./chunk-TBBDFYXW.js";
@@ -729,4 +729,4 @@ export {
729
729
  listMemoryGovernanceRuns,
730
730
  readMemoryGovernanceRunArtifact
731
731
  };
732
- //# sourceMappingURL=chunk-5BTCT236.js.map
732
+ //# sourceMappingURL=chunk-GYTVOLNX.js.map
@@ -7,8 +7,8 @@ import {
7
7
 
8
8
  // src/explicit-capture.ts
9
9
  import { randomUUID } from "crypto";
10
- var INLINE_NOTE_RE = /<memory_note>\s*([\s\S]*?)\s*<\/memory_note>/gi;
11
- var INLINE_NOTE_MARKUP_RE = /<memory_note>\s*[\s\S]*?\s*<\/memory_note>/i;
10
+ var INLINE_NOTE_RE = /<memory_note>([\s\S]{0,100000}?)<\/memory_note>/gi;
11
+ var INLINE_NOTE_MARKUP_RE = /<memory_note>[\s\S]{0,100000}?<\/memory_note>/i;
12
12
  var INLINE_ALLOWED_CATEGORIES = /* @__PURE__ */ new Set([
13
13
  "fact",
14
14
  "preference",
@@ -435,4 +435,4 @@ export {
435
435
  shouldSkipImplicitExtraction,
436
436
  shouldProcessInlineExplicitCapture
437
437
  };
438
- //# sourceMappingURL=chunk-IMA6GU4Y.js.map
438
+ //# sourceMappingURL=chunk-H3PHZLMF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/explicit-capture.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Orchestrator } from \"./orchestrator.js\";\nimport { isSafeRouteNamespace } from \"./routing/engine.js\";\nimport { sanitizeMemoryContent } from \"./sanitize.js\";\nimport { ContentHashIndex } from \"./storage.js\";\nimport type { CaptureMode, MemoryCategory, MemoryLifecycleEvent, PluginConfig } from \"./types.js\";\n\nexport type ExplicitCaptureInput = {\n content: string;\n category?: string;\n confidence?: number;\n namespace?: string;\n tags?: string[];\n entityRef?: string;\n ttl?: string;\n sourceReason?: string;\n};\n\nexport type ValidExplicitCapture = {\n content: string;\n category: MemoryCategory;\n confidence: number;\n namespace?: string;\n tags: string[];\n entityRef?: string;\n expiresAt?: string;\n sourceReason?: string;\n /**\n * When true, `namespace` was already resolved AND authorized by the caller\n * (the access service's `resolveCodingScopedWriteNamespace`, which auth-checks\n * the base and derives a session-owned `project-*` overlay). The persist /\n * queue layer then routes to it directly instead of re-validating against the\n * static policy allow-list — which would otherwise reject legitimately-derived\n * dynamic project namespaces (#1434). Callers that do NOT pre-authorize the\n * namespace must leave this unset so the allow-list guard still applies.\n */\n namespacePreResolved?: boolean;\n};\n\nexport type ExplicitCaptureSource = \"memory_store\" | \"memory_capture\" | \"suggestion_submit\" | \"inline\";\ntype ExplicitCaptureValidationMode = \"legacy_tool\" | \"strict_explicit\";\n\n// Bounded body {0,100000} instead of an unbounded lazy *? so scanning for the\n// closing tag cannot backtrack polynomially on unterminated <memory_note>\n// markup in hostile turn text (CodeQL js/polynomial-redos). 100 000 chars far\n// exceeds any real inline note, so matching is behavior-preserving; the outer\n// \\s* groups were also dropped (body absorbs whitespace; captures are trimmed).\nconst INLINE_NOTE_RE = /<memory_note>([\\s\\S]{0,100000}?)<\\/memory_note>/gi;\nconst INLINE_NOTE_MARKUP_RE = /<memory_note>[\\s\\S]{0,100000}?<\\/memory_note>/i;\nconst INLINE_ALLOWED_CATEGORIES = new Set<MemoryCategory>([\n \"fact\",\n \"preference\",\n \"correction\",\n \"entity\",\n \"decision\",\n \"relationship\",\n \"principle\",\n \"commitment\",\n \"moment\",\n \"skill\",\n \"rule\",\n \"procedure\",\n \"reasoning_trace\",\n]);\n\nconst SECRET_PATTERNS: RegExp[] = [\n /\\bsk-[A-Za-z0-9]{16,}\\b/,\n /\\bAKIA[0-9A-Z]{16}\\b/,\n /\\bBearer\\s+[A-Za-z0-9._-]{16,}\\b/i,\n /\\b(?:api[_-]?key|secret|token|password|passwd)\\s*[:=]\\s*[^\\s]{8,}\\b/i,\n /\\b(?:authorization)\\s*:\\s*[^\\s]{8,}\\b/i,\n];\nconst SECRET_REDACTION_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [\n { pattern: /\\bsk-[A-Za-z0-9]{16,}\\b/g, replacement: \"[redacted openai key]\" },\n { pattern: /\\bAKIA[0-9A-Z]{16}\\b/g, replacement: \"[redacted aws key]\" },\n { pattern: /\\bBearer\\s+[A-Za-z0-9._-]{16,}\\b/gi, replacement: \"Bearer [redacted token]\" },\n {\n pattern: /\\b(?:api[_-]?key|secret|token|password|passwd)\\s*[:=]\\s*[^\\s]{8,}\\b/gi,\n replacement: \"[redacted credential]\",\n },\n {\n pattern: /\\b(?:authorization)\\s*:\\s*[^\\s]{8,}\\b/gi,\n replacement: \"authorization: [redacted credential]\",\n },\n];\nconst EXPLICIT_CAPTURE_REVIEW_TAGS = [\"explicit-capture\", \"queued-review\"];\n\nfunction explicitCaptureActor(source: ExplicitCaptureSource): string {\n switch (source) {\n case \"inline\":\n return \"inline.memory_note\";\n case \"memory_store\":\n return \"tool.memory_store\";\n case \"suggestion_submit\":\n return \"tool.suggestion_submit\";\n default:\n return \"tool.memory_capture\";\n }\n}\n\nfunction asTrimmed(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed && trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction normalizeCaptureContent(value: string): string {\n return value\n .toLowerCase()\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction redactSecrets(value: string): string {\n let redacted = value;\n for (const { pattern, replacement } of SECRET_REDACTION_PATTERNS) {\n redacted = redacted.replace(pattern, replacement);\n }\n return redacted;\n}\n\nfunction containsSecretLikeValue(value: string): boolean {\n return SECRET_PATTERNS.some((pattern) => pattern.test(value));\n}\n\nfunction assertNoSecretLikeMetadata(field: string, value: string | undefined): void {\n const trimmed = asTrimmed(value);\n if (trimmed && containsSecretLikeValue(trimmed)) {\n throw new Error(`${field} appears to contain a secret or credential`);\n }\n}\n\nfunction assertNoSecretLikeMetadataList(field: string, values: string[] | undefined): void {\n for (const value of values ?? []) {\n assertNoSecretLikeMetadata(field, value);\n }\n}\n\nfunction sanitizeReviewText(value: string | undefined, fallback: string): string {\n const redacted = redactSecrets(asTrimmed(value) ?? fallback);\n const sanitized = sanitizeMemoryContent(redacted);\n const safe = sanitized.text.trim();\n return safe.length > 0 ? safe : fallback;\n}\n\nfunction sanitizeReviewMetadata(value: string | undefined): string | undefined {\n const trimmed = asTrimmed(value);\n if (!trimmed) return undefined;\n return sanitizeReviewText(trimmed, \"[redacted]\");\n}\n\nfunction sanitizeReviewTags(tags: string[] | undefined): string[] {\n return Array.from(new Set((tags ?? [])\n .map((tag) => sanitizeReviewMetadata(tag))\n .filter((tag): tag is string => typeof tag === \"string\" && tag.length > 0)));\n}\n\nfunction normalizeExplicitCaptureError(error: unknown): string {\n if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();\n const rendered = String(error).trim();\n return rendered.length > 0 ? rendered : \"explicit capture failed\";\n}\n\nfunction resolveExplicitCaptureReviewNamespace(\n orchestrator: Orchestrator,\n namespace: string | undefined,\n): string | undefined {\n const normalized = asTrimmed(namespace);\n if (!normalized) return undefined;\n return resolveExplicitCaptureNamespace(orchestrator, normalized);\n}\n\nfunction resolveExplicitCaptureNamespace(\n orchestrator: Orchestrator,\n namespace: string | undefined,\n): string | undefined {\n const normalized = asTrimmed(namespace);\n if (!normalized) return undefined;\n if (!orchestrator.config.namespacesEnabled) {\n if (normalized !== orchestrator.config.defaultNamespace) {\n throw new Error(`unsupported namespace: ${normalized}`);\n }\n return normalized;\n }\n const allowed = new Set([\n orchestrator.config.defaultNamespace,\n orchestrator.config.sharedNamespace,\n ...orchestrator.config.namespacePolicies.map((policy) => policy.name),\n ].map((value) => value.trim()).filter(Boolean));\n if (!allowed.has(normalized)) {\n throw new Error(`unsupported namespace: ${normalized}`);\n }\n return normalized;\n}\n\nfunction parseExplicitCaptureTtl(ttl: string | undefined): string | undefined {\n const raw = asTrimmed(ttl);\n if (!raw) return undefined;\n\n const absoluteMs = Date.parse(raw);\n if (Number.isFinite(absoluteMs)) {\n return new Date(absoluteMs).toISOString();\n }\n\n const relative = raw.match(/^(\\d+)\\s*([mhdw])$/i);\n if (!relative) {\n throw new Error(\"ttl must be an ISO-8601 timestamp or relative duration like 30m, 12h, 7d, or 2w\");\n }\n\n const amount = Number.parseInt(relative[1] ?? \"\", 10);\n const unit = (relative[2] ?? \"\").toLowerCase();\n if (!Number.isFinite(amount) || amount <= 0) {\n throw new Error(\"ttl duration must be a positive integer\");\n }\n\n const multiplier =\n unit === \"m\" ? 60_000\n : unit === \"h\" ? 60 * 60_000\n : unit === \"d\" ? 24 * 60 * 60_000\n : 7 * 24 * 60 * 60_000;\n return new Date(Date.now() + amount * multiplier).toISOString();\n}\n\nfunction parseInlineConfidence(value: string): number {\n const trimmed = value.trim();\n if (!/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?$/i.test(trimmed)) {\n return Number.NaN;\n }\n const parsed = Number(trimmed);\n return Number.isFinite(parsed) ? parsed : Number.NaN;\n}\n\nfunction parseInlineNote(block: string): ExplicitCaptureInput | null {\n const lines = block.replace(/\\r/g, \"\").split(\"\\n\");\n const note: Partial<ExplicitCaptureInput> = {};\n let idx = 0;\n\n while (idx < lines.length) {\n const rawLine = lines[idx] ?? \"\";\n const line = rawLine.trim();\n idx += 1;\n if (line.length === 0) continue;\n const colonIdx = line.indexOf(\":\");\n if (colonIdx < 0) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n\n if (key === \"content\" && value === \"|\") {\n const contentLines: string[] = [];\n while (idx < lines.length) {\n const next = lines[idx] ?? \"\";\n if (next.startsWith(\" \") || next.startsWith(\"\\t\")) {\n contentLines.push(next.replace(/^( |\\t)/, \"\"));\n idx += 1;\n continue;\n }\n if (next.trim().length === 0) {\n contentLines.push(\"\");\n idx += 1;\n continue;\n }\n break;\n }\n note.content = contentLines.join(\"\\n\").trim();\n continue;\n }\n\n switch (key) {\n case \"content\":\n note.content = value;\n break;\n case \"category\":\n note.category = value;\n break;\n case \"confidence\":\n note.confidence = parseInlineConfidence(value);\n break;\n case \"namespace\":\n note.namespace = value;\n break;\n case \"tags\":\n note.tags = value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n break;\n case \"entityRef\":\n note.entityRef = value;\n break;\n case \"ttl\":\n note.ttl = value;\n break;\n case \"sourceReason\":\n note.sourceReason = value;\n break;\n default:\n break;\n }\n }\n\n return asTrimmed(note.content) ? (note as ExplicitCaptureInput) : null;\n}\n\nexport function parseInlineExplicitCaptureNotes(text: string): ExplicitCaptureInput[] {\n const notes: ExplicitCaptureInput[] = [];\n for (const match of text.matchAll(INLINE_NOTE_RE)) {\n const parsed = parseInlineNote(match[1] ?? \"\");\n if (parsed) notes.push(parsed);\n }\n return notes;\n}\n\nexport function hasInlineExplicitCaptureMarkup(text: string): boolean {\n return INLINE_NOTE_MARKUP_RE.test(text);\n}\n\nexport function stripInlineExplicitCaptureNotes(text: string): string {\n return text.replace(INLINE_NOTE_RE, \"\").trim();\n}\n\nexport function validateExplicitCaptureInput(\n input: ExplicitCaptureInput,\n mode: ExplicitCaptureValidationMode = \"strict_explicit\",\n): ValidExplicitCapture {\n const content = asTrimmed(input.content);\n if (!content) throw new Error(\"content is required\");\n if (mode === \"strict_explicit\") {\n if (content.length < 10) throw new Error(\"content must be at least 10 characters\");\n if (content.length > 4000) throw new Error(\"content must be 4000 characters or fewer\");\n }\n if (/<memory_note>/i.test(content) || /<\\/memory_note>/i.test(content)) {\n throw new Error(\"nested memory_note blocks are not allowed\");\n }\n\n const category = (asTrimmed(input.category) ?? \"fact\") as MemoryCategory;\n if (!INLINE_ALLOWED_CATEGORIES.has(category)) {\n throw new Error(`unsupported category: ${input.category ?? category}`);\n }\n\n const sanitized = sanitizeMemoryContent(content);\n if (!sanitized.clean) {\n throw new Error(\"content failed memory sanitization\");\n }\n for (const pattern of SECRET_PATTERNS) {\n if (pattern.test(content)) {\n throw new Error(\"content appears to contain a secret or credential\");\n }\n }\n assertNoSecretLikeMetadata(\"sourceReason\", input.sourceReason);\n assertNoSecretLikeMetadata(\"entityRef\", input.entityRef);\n assertNoSecretLikeMetadata(\"ttl\", input.ttl);\n assertNoSecretLikeMetadataList(\"tags\", input.tags);\n\n if (input.confidence !== undefined && !Number.isFinite(input.confidence)) {\n throw new Error(\"confidence must be a finite number\");\n }\n const confidence = input.confidence === undefined ? 0.95 : Number(input.confidence);\n if (confidence < 0 || confidence > 1) {\n throw new Error(\"confidence must be between 0 and 1\");\n }\n const requestedNamespace = asTrimmed(input.namespace);\n if (requestedNamespace && !isSafeRouteNamespace(requestedNamespace)) {\n throw new Error(`unsafe namespace: ${requestedNamespace}`);\n }\n const expiresAt = parseExplicitCaptureTtl(input.ttl);\n\n return {\n content,\n category,\n confidence,\n namespace: asTrimmed(input.namespace),\n tags: Array.from(new Set((input.tags ?? []).map((tag) => tag.trim()).filter(Boolean))),\n entityRef: asTrimmed(input.entityRef),\n expiresAt,\n sourceReason: asTrimmed(input.sourceReason),\n };\n}\n\nasync function findDuplicateExplicitCapture(\n orchestrator: Orchestrator,\n resolvedNamespace: string | undefined,\n candidate: ValidExplicitCapture,\n): Promise<string | null> {\n const storage = await orchestrator.getStorage(resolvedNamespace);\n if (\n candidate.category === \"fact\"\n && typeof (storage as { hasFactContentHash?: (content: string) => Promise<boolean> }).hasFactContentHash === \"function\"\n ) {\n try {\n const hasHash = await (storage as { hasFactContentHash: (content: string) => Promise<boolean> }).hasFactContentHash(\n candidate.content,\n );\n if (!hasHash) {\n const authoritative =\n typeof (storage as { isFactContentHashAuthoritative?: () => Promise<boolean> | boolean }).isFactContentHashAuthoritative\n === \"function\"\n ? await (storage as { isFactContentHashAuthoritative: () => Promise<boolean> | boolean })\n .isFactContentHashAuthoritative()\n : false;\n if (authoritative) return null;\n }\n } catch (err) {\n // Fail open: hash index is only an optimization, so fall back to the full corpus scan.\n void err;\n }\n }\n const existing = await storage.readAllMemories();\n const normalizedCandidate = normalizeCaptureContent(candidate.content);\n const match = existing.find((memory) => {\n const status = memory.frontmatter.status ?? \"active\";\n if (status !== \"active\") return false;\n if (memory.frontmatter.category !== candidate.category) return false;\n return normalizeCaptureContent(memory.content) === normalizedCandidate;\n });\n return match?.frontmatter.id ?? null;\n}\n\nexport async function persistExplicitCapture(\n orchestrator: Orchestrator,\n candidate: ValidExplicitCapture,\n source: ExplicitCaptureSource,\n): Promise<{ id: string; duplicateOf?: string }> {\n const resolvedNamespace = candidate.namespacePreResolved\n ? asTrimmed(candidate.namespace)\n : resolveExplicitCaptureNamespace(orchestrator, candidate.namespace);\n const duplicateOf = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate);\n if (duplicateOf) {\n return { id: duplicateOf, duplicateOf };\n }\n\n const storage = await orchestrator.getStorage(resolvedNamespace);\n const id = await storage.writeMemory(candidate.category, candidate.content, {\n confidence: candidate.confidence,\n tags: candidate.tags,\n entityRef: candidate.entityRef,\n expiresAt: candidate.expiresAt,\n source: source === \"inline\" ? \"explicit-inline\" : \"explicit\",\n });\n\n const created = new Date().toISOString();\n const event: MemoryLifecycleEvent = {\n eventId: `mle-${randomUUID()}`,\n memoryId: id,\n eventType: \"explicit_capture_accepted\",\n timestamp: created,\n actor: explicitCaptureActor(source),\n reasonCode: candidate.sourceReason,\n ruleVersion: \"explicit-capture.v1\",\n };\n await storage.appendMemoryLifecycleEvents([event]);\n\n return { id };\n}\n\nfunction buildExplicitCaptureReviewContent(input: ExplicitCaptureInput, reason: string): string {\n const requestedContent = asTrimmed(input.content);\n const safeContent = sanitizeReviewText(requestedContent, \"[empty explicit capture]\");\n const safeCategory = sanitizeReviewMetadata(input.category);\n const safeNamespace = sanitizeReviewMetadata(input.namespace);\n const safeEntityRef = sanitizeReviewMetadata(input.entityRef);\n const safeTtl = sanitizeReviewMetadata(input.ttl);\n const safeSourceReason = sanitizeReviewMetadata(input.sourceReason);\n const safeTags = sanitizeReviewTags(input.tags);\n const lines = [\n \"Explicit capture queued for review.\",\n \"\",\n `Reason: ${reason}`,\n \"\",\n \"Submitted content:\",\n safeContent,\n ];\n const metadata = [\n safeCategory ? `Requested category: ${safeCategory}` : undefined,\n safeNamespace ? `Requested namespace: ${safeNamespace}` : undefined,\n safeEntityRef ? `Requested entityRef: ${safeEntityRef}` : undefined,\n safeTtl ? `Requested ttl: ${safeTtl}` : undefined,\n safeSourceReason ? `Requested sourceReason: ${safeSourceReason}` : undefined,\n safeTags.length > 0 ? `Requested tags: ${safeTags.join(\", \")}` : undefined,\n ].filter((entry): entry is string => typeof entry === \"string\" && entry.length > 0);\n if (metadata.length > 0) {\n lines.push(\"\", ...metadata);\n }\n return lines.join(\"\\n\");\n}\n\nasync function findQueuedExplicitCaptureDuplicate(\n orchestrator: Orchestrator,\n namespace: string | undefined,\n content: string,\n): Promise<string | null> {\n const storage = await orchestrator.getStorage(namespace);\n const existing = await storage.readAllMemories();\n const normalized = normalizeCaptureContent(content);\n const match = existing.find((memory) => {\n const status = memory.frontmatter.status ?? \"active\";\n if (status !== \"pending_review\") return false;\n if (!(memory.frontmatter.tags ?? []).includes(\"queued-review\")) return false;\n return normalizeCaptureContent(memory.content) === normalized;\n });\n return match?.frontmatter.id ?? null;\n}\n\nexport async function queueExplicitCaptureForReview(\n orchestrator: Orchestrator,\n input: ExplicitCaptureInput,\n source: ExplicitCaptureSource,\n error: unknown,\n): Promise<{ id: string; duplicateOf?: string }> {\n const reason = sanitizeReviewText(normalizeExplicitCaptureError(error), \"explicit capture failed\");\n const requestedNamespace = asTrimmed(input.namespace);\n // A caller-pre-authorized namespace (e.g. a session-owned project overlay\n // from the access service) routes directly; otherwise apply the static\n // policy allow-list guard (#1434).\n const queueNamespace = (input as { namespacePreResolved?: boolean }).namespacePreResolved\n ? requestedNamespace\n : resolveExplicitCaptureReviewNamespace(orchestrator, requestedNamespace);\n const content = buildExplicitCaptureReviewContent(input, reason);\n const duplicateOf = await findQueuedExplicitCaptureDuplicate(orchestrator, queueNamespace, content);\n if (duplicateOf) {\n return { id: duplicateOf, duplicateOf };\n }\n\n const requestedCategory = asTrimmed(input.category);\n const reviewCategory = requestedCategory && INLINE_ALLOWED_CATEGORIES.has(requestedCategory as MemoryCategory)\n ? requestedCategory as MemoryCategory\n : \"fact\";\n const requestedTags = sanitizeReviewTags(input.tags);\n const storage = await orchestrator.getStorage(queueNamespace);\n const id = await storage.writeMemory(reviewCategory, content, {\n confidence: 0.2,\n tags: Array.from(new Set([...EXPLICIT_CAPTURE_REVIEW_TAGS, ...requestedTags])),\n entityRef: sanitizeReviewMetadata(input.entityRef),\n source: source === \"inline\" ? \"explicit-inline-review\" : \"explicit-review\",\n });\n const created = await storage.getMemoryById(id);\n if (created) {\n await storage.writeMemoryFrontmatter(created, {\n status: \"pending_review\",\n updated: new Date().toISOString(),\n }, {\n actor: explicitCaptureActor(source),\n reasonCode: reason,\n ruleVersion: \"explicit-capture.v1\",\n });\n }\n const event: MemoryLifecycleEvent = {\n eventId: `mle-${randomUUID()}`,\n memoryId: id,\n eventType: \"explicit_capture_queued\",\n timestamp: new Date().toISOString(),\n actor: explicitCaptureActor(source),\n reasonCode: reason,\n ruleVersion: \"explicit-capture.v1\",\n };\n await storage.appendMemoryLifecycleEvents([event]);\n return { id };\n}\n\nexport function shouldSkipImplicitExtraction(cfg: Pick<PluginConfig, \"captureMode\">): boolean {\n return cfg.captureMode === \"explicit\";\n}\n\nexport function shouldProcessInlineExplicitCapture(cfg: Pick<PluginConfig, \"captureMode\">): boolean {\n return cfg.captureMode !== \"implicit\";\n}\n"],"mappings":"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","names":[]}
@@ -11,13 +11,13 @@ import {
11
11
  } from "./chunk-D24OXEPB.js";
12
12
  import {
13
13
  EngramAccessInputError
14
- } from "./chunk-F4QTFIB4.js";
14
+ } from "./chunk-KQFQ3IS5.js";
15
15
  import {
16
16
  projectTagProjectId
17
17
  } from "./chunk-EDQVAMQI.js";
18
18
  import {
19
19
  validateBriefingFormat
20
- } from "./chunk-AZDOWD2L.js";
20
+ } from "./chunk-OZXVGYGZ.js";
21
21
  import {
22
22
  expandTildePath
23
23
  } from "./chunk-EYIEWJNI.js";
@@ -2712,7 +2712,7 @@ ${body}`;
2712
2712
  }
2713
2713
  case "engram.contradiction_scan_run":
2714
2714
  case "remnic.contradiction_scan_run": {
2715
- const { runContradictionScan } = await import("./contradiction-scan-GD7KUFWS.js");
2715
+ const { runContradictionScan } = await import("./contradiction-scan-AZTGFMPY.js");
2716
2716
  return runContradictionScan({
2717
2717
  storage: this.service.storageRef,
2718
2718
  config: this.service.configRef,
@@ -2903,4 +2903,4 @@ ${body}`;
2903
2903
  export {
2904
2904
  EngramMcpServer
2905
2905
  };
2906
- //# sourceMappingURL=chunk-TIPYPLLQ.js.map
2906
+ //# sourceMappingURL=chunk-I6UCUHLK.js.map
@@ -40,7 +40,7 @@ function buildPeerProfileReasonerPrompt(input) {
40
40
  function parsePeerProfileReasonerResponse(raw) {
41
41
  if (typeof raw !== "string" || raw.trim() === "") return [];
42
42
  const trimmed = raw.trim();
43
- const fenced = /^```(?:json)?\s*([\s\S]*?)```\s*$/u.exec(trimmed);
43
+ const fenced = /^```(?:json)?([\s\S]*?)```$/u.exec(trimmed);
44
44
  const payload = fenced ? fenced[1].trim() : trimmed;
45
45
  let parsed;
46
46
  try {
@@ -347,4 +347,4 @@ export {
347
347
  parsePeerProfileReasonerResponse,
348
348
  runPeerProfileReasoner
349
349
  };
350
- //# sourceMappingURL=chunk-2I2MDQIB.js.map
350
+ //# sourceMappingURL=chunk-I74SUMNI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/peers/profile-reasoner.ts"],"sourcesContent":["/**\n * Peer profile reasoner — issue #679 PR 2/5.\n *\n * Pure async function that, for each peer:\n *\n * 1. Reads recent interaction-log entries via `readPeerInteractionLog`.\n * 2. Calls an injected LLM client (same chat shape as\n * `FallbackLlmClient.chatCompletion`) to derive 0..N profile-field\n * proposals with provenance `{observedAt, signal, sourceSessionId,\n * note}`.\n * 3. Merges the proposals into the peer's existing `PeerProfile`,\n * appending provenance entries (never replacing existing\n * provenance — the reasoner is additive by design so the operator\n * retains the full audit trail).\n * 4. Writes via `writePeerProfile`.\n *\n * Gating is handled in two layers:\n *\n * - The orchestrator wires the call behind the\n * `peerProfileReasonerEnabled` config flag (default `false` —\n * opt-in per Gotcha #30/#48). The reasoner ALSO short-circuits\n * when `options.enabled !== true`, so direct callers can't\n * accidentally bypass the flag.\n * - Per-peer, the `peerProfileReasonerMinInteractions` threshold\n * skips peers whose log has fewer entries since the last reasoner\n * run than required.\n *\n * The reasoner is intentionally storage-agnostic — it accepts an LLM\n * client by interface (`PeerProfileReasonerLlm`) so tests can mock the\n * call and the orchestrator can inject either the gateway client or\n * a fast local model. No direct OpenAI imports here.\n */\n\nimport {\n appendInteractionLog,\n listPeers,\n readPeerInteractionLog,\n readPeerProfile,\n writePeerProfile,\n} from \"./storage.js\";\nimport type {\n Peer,\n PeerInteractionLogEntry,\n PeerProfile,\n PeerProfileFieldProvenance,\n} from \"./types.js\";\nimport { PEER_ID_PATTERN } from \"./types.js\";\n\n// ──────────────────────────────────────────────────────────────────────\n// Types\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Minimal chat-completion contract the reasoner depends on. Matches\n * `FallbackLlmClient.chatCompletion` so the orchestrator can pass it\n * through directly. Tests inject a mock that returns canned strings.\n *\n * Returning `null` means the LLM is unavailable / failed — the\n * reasoner treats that as \"no proposals for this peer\" rather than an\n * error so a flaky LLM never aborts the whole pass.\n */\nexport interface PeerProfileReasonerLlm {\n chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options?: { temperature?: number; maxTokens?: number; timeoutMs?: number },\n ): Promise<{ content: string } | null>;\n}\n\n/**\n * One LLM-proposed profile-field update.\n *\n * `value` is the new markdown string to set under `field`. The\n * provenance entry the LLM emits travels alongside it; the reasoner\n * does NOT trust the LLM's `observedAt` — it always overwrites with\n * the run's `now` timestamp so provenance can never claim future or\n * past observation timestamps the operator didn't witness.\n */\nexport interface PeerProfileReasonerProposal {\n /** Stable section key, e.g. \"communication_style\". */\n readonly field: string;\n /** Markdown value to set under that key. */\n readonly value: string;\n /**\n * Short label for the signal that justified the inference,\n * e.g. \"explicit_preference\", \"tool_pattern\", \"topic_recurrence\".\n */\n readonly signal: string;\n /** Optional free-form note explaining the inference. */\n readonly note?: string;\n /**\n * Originating session id, when the LLM can attribute the inference\n * to a specific log line. Reasoner clamps this to a value that\n * actually appeared in the log window so the LLM can't hallucinate.\n */\n readonly sourceSessionId?: string;\n}\n\nexport interface PeerProfileReasonerOptions {\n /** Memory directory containing the peers/ subtree. */\n readonly memoryDir: string;\n /**\n * Master gate. When `false` (the default the orchestrator passes\n * when the config flag is off), the reasoner is a no-op and\n * returns an empty result. Direct callers must explicitly pass\n * `true` so the gate can never be defaulted ON by accident\n * (Gotcha #48 — least-privileged default).\n */\n readonly enabled: boolean;\n /** Injected LLM client. Required when `enabled === true`. */\n readonly llm?: PeerProfileReasonerLlm;\n /** Model name to log for telemetry; not used to dispatch. */\n readonly model?: string;\n /**\n * Minimum new interaction-log entries since last reasoner run\n * before this peer is processed. Peers below the threshold are\n * skipped with `reason: \"below_min_interactions\"`.\n */\n readonly minInteractions: number;\n /**\n * Hard cap on profile fields the reasoner will accept across all\n * peers in a single run. Tracked in insertion order: once the cap\n * is reached, subsequent proposals are dropped with\n * `dropped_due_to_cap` in the per-peer result. Use to bound LLM\n * cost and reviewer load per pass.\n */\n readonly maxFieldsPerRun: number;\n /**\n * Optional restriction to specific peer ids. When omitted, the\n * reasoner enumerates the entire peer registry via `listPeers`.\n */\n readonly peerIds?: ReadonlyArray<string>;\n /**\n * Maximum number of recent log entries to feed the LLM per peer.\n * Defaults to 50. Bounded so a runaway log can't blow the prompt.\n */\n readonly maxLogEntriesPerPeer?: number;\n /**\n * Reasoner run timestamp. Defaults to `new Date()` at call time.\n * Tests inject a deterministic clock; the orchestrator passes\n * `new Date()` so provenance entries reflect actual wall time.\n */\n readonly now?: Date;\n /**\n * Optional logger; defaults to a no-op so the reasoner stays\n * silent in unit tests. The orchestrator wires its `log` here so\n * runs surface in the gateway log under the\n * `[peer-profile-reasoner]` prefix.\n */\n readonly log?: {\n debug?: (msg: string) => void;\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n };\n /**\n * Whether to append a `peer_profile_reasoner_run` entry to the\n * peer's interaction log when the reasoner emits at least one\n * field for that peer. Defaults to `true`. Disable in tests that\n * want to assert the log was untouched.\n */\n readonly appendRunMarkerToLog?: boolean;\n /**\n * Optional abort signal. The reasoner checks between peers and\n * returns the partial result if cancelled mid-run.\n */\n readonly signal?: AbortSignal;\n}\n\nexport interface PeerProfileReasonerPeerResult {\n readonly peerId: string;\n readonly status:\n | \"processed\"\n | \"skipped_below_min_interactions\"\n | \"skipped_no_log\"\n | \"skipped_disabled\"\n | \"skipped_no_llm\"\n | \"skipped_llm_unavailable\"\n | \"skipped_invalid_proposal\"\n | \"skipped_cap_reached\"\n | \"skipped_aborted\"\n | \"error\";\n /** Number of fields actually applied to the peer's profile. */\n readonly fieldsApplied: number;\n /** Number of proposals dropped because the per-run cap was hit. */\n readonly droppedDueToCap: number;\n /** Set of field keys applied; useful for tests and telemetry. */\n readonly fields: ReadonlyArray<string>;\n /** Error message, when `status === \"error\"`. */\n readonly error?: string;\n}\n\nexport interface PeerProfileReasonerResult {\n readonly peersConsidered: number;\n readonly peersProcessed: number;\n readonly fieldsApplied: number;\n readonly perPeer: ReadonlyArray<PeerProfileReasonerPeerResult>;\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Prompt + parser (pure, exported for tests)\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Build the user-facing reasoner prompt. The system message carries\n * the strict-JSON instruction; this function emits the user message\n * with the peer context and the recent log slice.\n *\n * The prompt is intentionally schema-prescriptive — sibling modules\n * (`semantic-consolidation.ts`, `extraction-judge.ts`) demonstrated\n * that letting the LLM improvise field names produces unstable\n * profiles across runs.\n */\nexport function buildPeerProfileReasonerPrompt(input: {\n peer: Peer;\n existingProfile: PeerProfile | null;\n log: ReadonlyArray<PeerInteractionLogEntry>;\n maxFields: number;\n}): string {\n const existingFields = input.existingProfile\n ? Object.keys(input.existingProfile.fields)\n : [];\n const logBlock = input.log\n .map((e) => {\n const session = e.sessionId ? ` session=${e.sessionId}` : \"\";\n return `- [${e.timestamp}] (${e.kind})${session} ${e.summary}`;\n })\n .join(\"\\n\");\n return [\n `You are an async peer-profile reasoner. Your job is to read recent interaction-log entries for one peer and propose 0..${input.maxFields} profile-field updates.`,\n \"\",\n `Peer:`,\n ` id: ${input.peer.id}`,\n ` kind: ${input.peer.kind}`,\n ` displayName: ${input.peer.displayName}`,\n \"\",\n `Existing profile field keys (preserve names when proposing updates that refine an existing field): ${existingFields.length > 0 ? existingFields.join(\", \") : \"(none yet)\"}`,\n \"\",\n `Recent interaction log (oldest first):`,\n logBlock.length > 0 ? logBlock : \"(no entries)\",\n \"\",\n `Output a single JSON object: {\"proposals\": [{\"field\": \"<stable_key>\", \"value\": \"<markdown>\", \"signal\": \"<short_label>\", \"note\": \"<optional>\", \"sourceSessionId\": \"<optional>\"}]}.`,\n \"\",\n `Rules:`,\n `1. Only propose fields supported by evidence in the log. Do not invent.`,\n `2. Keys are short snake_case (e.g. \"communication_style\", \"tool_patterns\").`,\n `3. value is markdown. signal is a short label like \"explicit_preference\" or \"topic_recurrence\".`,\n `4. Omit fields you can't justify. Empty proposals array is valid.`,\n `5. Output JSON ONLY — no prose before or after.`,\n ].join(\"\\n\");\n}\n\n/**\n * Parse the LLM response. Tolerates a fenced code block wrapper.\n * Returns an empty array on any malformed payload — the contract is\n * that flaky LLM output silently produces zero proposals rather than\n * surfacing an error to the caller.\n *\n * Exported so unit tests can verify parser behavior without spinning\n * up the full reasoner.\n */\nexport function parsePeerProfileReasonerResponse(\n raw: string,\n): PeerProfileReasonerProposal[] {\n if (typeof raw !== \"string\" || raw.trim() === \"\") return [];\n const trimmed = raw.trim();\n // Dropped the \\s* groups around the lazy body (they overlapped it and\n // backtracked polynomially — CodeQL js/polynomial-redos). Input is already\n // trimmed and fenced[1] is trimmed below, so matches are identical.\n const fenced = /^```(?:json)?([\\s\\S]*?)```$/u.exec(trimmed);\n const payload = fenced ? fenced[1].trim() : trimmed;\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return [];\n }\n // Gotcha #18: JSON.parse('null') succeeds. Reject non-objects.\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n return [];\n }\n const obj = parsed as { proposals?: unknown };\n if (!Array.isArray(obj.proposals)) return [];\n const out: PeerProfileReasonerProposal[] = [];\n // Gotcha — drop prototype-pollution keys at the field-name layer.\n const RESERVED_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n for (const item of obj.proposals) {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) continue;\n const r = item as Record<string, unknown>;\n if (typeof r.field !== \"string\" || r.field.trim() === \"\") continue;\n if (RESERVED_KEYS.has(r.field)) continue;\n if (typeof r.value !== \"string\" || r.value.trim() === \"\") continue;\n if (typeof r.signal !== \"string\" || r.signal.trim() === \"\") continue;\n const proposal: PeerProfileReasonerProposal = {\n field: r.field,\n value: r.value,\n signal: r.signal,\n ...(typeof r.note === \"string\" && r.note.length > 0 ? { note: r.note } : {}),\n ...(typeof r.sourceSessionId === \"string\" && r.sourceSessionId.length > 0\n ? { sourceSessionId: r.sourceSessionId }\n : {}),\n };\n out.push(proposal);\n }\n return out;\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Reasoner core\n// ──────────────────────────────────────────────────────────────────────\n\nconst SYSTEM_MESSAGE =\n 'You are a peer-profile reasoner. Output ONLY a JSON object of the form {\"proposals\":[{\"field\":\"...\",\"value\":\"...\",\"signal\":\"...\",\"note\":\"...\",\"sourceSessionId\":\"...\"}]}. No prose, no fenced code block, no commentary.';\n\nconst RUN_MARKER_KIND = \"peer_profile_reasoner_run\";\n\n/**\n * Find the most recent reasoner-run marker timestamp in the log.\n * Used to count \"interactions since last run\" so the threshold\n * gate doesn't keep firing on the same dormant log forever.\n */\nfunction lastRunTimestamp(\n log: ReadonlyArray<PeerInteractionLogEntry>,\n): string | undefined {\n let latest: string | undefined;\n for (const entry of log) {\n if (entry.kind !== RUN_MARKER_KIND) continue;\n if (latest === undefined || entry.timestamp > latest) {\n latest = entry.timestamp;\n }\n }\n return latest;\n}\n\nfunction noopLogger(): NonNullable<PeerProfileReasonerOptions[\"log\"]> {\n return { debug: () => {}, info: () => {}, warn: () => {} };\n}\n\n/**\n * Run the reasoner across all (or the requested subset of) peers.\n *\n * Always returns a `PeerProfileReasonerResult` — never throws to the\n * caller — so the orchestrator can wire it as a best-effort\n * post-consolidation hook (Gotcha #13).\n */\nexport async function runPeerProfileReasoner(\n options: PeerProfileReasonerOptions,\n): Promise<PeerProfileReasonerResult> {\n const log = {\n debug: options.log?.debug ?? noopLogger().debug!,\n info: options.log?.info ?? noopLogger().info!,\n warn: options.log?.warn ?? noopLogger().warn!,\n };\n const result: {\n peersConsidered: number;\n peersProcessed: number;\n fieldsApplied: number;\n perPeer: PeerProfileReasonerPeerResult[];\n } = {\n peersConsidered: 0,\n peersProcessed: 0,\n fieldsApplied: 0,\n perPeer: [],\n };\n // Disabled flag is the master gate. Defaults to false in callers'\n // config; we additionally require strict `=== true` here so a\n // stray \"true\" string doesn't silently flip the flag (Gotcha #36).\n if (options.enabled !== true) {\n log.debug(\"[peer-profile-reasoner] disabled — no-op\");\n return result;\n }\n if (!options.llm) {\n log.warn(\"[peer-profile-reasoner] no LLM client supplied — skipping run\");\n return result;\n }\n const minInteractions = Number.isFinite(options.minInteractions)\n ? Math.max(0, Math.floor(options.minInteractions))\n : 0;\n const maxFields = Number.isFinite(options.maxFieldsPerRun)\n ? Math.max(0, Math.floor(options.maxFieldsPerRun))\n : 0;\n if (maxFields === 0) {\n log.debug(\"[peer-profile-reasoner] maxFieldsPerRun=0 — no-op\");\n return result;\n }\n const maxLogPerPeer = Number.isFinite(options.maxLogEntriesPerPeer ?? NaN)\n ? Math.max(1, Math.floor(options.maxLogEntriesPerPeer as number))\n : 50;\n const now = options.now ?? new Date();\n const nowIso = now.toISOString();\n\n let peers: Peer[];\n try {\n if (options.peerIds && options.peerIds.length > 0) {\n // Filter the explicit list against on-disk peers so we never\n // act on an id the operator typed but didn't register.\n const all = await listPeers(options.memoryDir);\n const wanted = new Set(\n options.peerIds.filter(\n (id) => typeof id === \"string\" && PEER_ID_PATTERN.test(id),\n ),\n );\n peers = all.filter((p) => wanted.has(p.id));\n } else {\n peers = await listPeers(options.memoryDir);\n }\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] listPeers failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return result;\n }\n result.peersConsidered = peers.length;\n let fieldsAppliedTotal = 0;\n\n for (const peer of peers) {\n if (options.signal?.aborted) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_aborted\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n try {\n // Codex P2 review on PR #736: the min-interactions threshold\n // must reflect the FULL log of new activity, not the\n // `maxLogPerPeer`-truncated slice. Otherwise a peer with a\n // genuinely active conversation history can be permanently\n // marked `skipped_below_min_interactions` whenever\n // `peerProfileReasonerMinInteractions > maxLogEntriesPerPeer`,\n // because the slice will never include enough new entries.\n // Read the full log first to compute the gate, then truncate\n // for prompt construction below.\n const fullLog = await readPeerInteractionLog(\n options.memoryDir,\n peer.id,\n );\n if (fullLog.length === 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_no_log\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n // Count interactions since the last reasoner-run marker, so\n // dormant peers don't trigger another LLM call until enough\n // new signal accumulates. Run markers themselves don't count.\n const lastRun = lastRunTimestamp(fullLog);\n const sinceLastRunFull = lastRun\n ? fullLog.filter(\n (e) => e.timestamp > lastRun && e.kind !== RUN_MARKER_KIND,\n )\n : fullLog.filter((e) => e.kind !== RUN_MARKER_KIND);\n if (sinceLastRunFull.length < minInteractions) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_below_min_interactions\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n // Truncate ONLY for prompt construction so the LLM context\n // stays bounded. Use the most recent `maxLogPerPeer` entries\n // from the full since-last-run set so the prompt prefers fresh\n // signal over older entries.\n const sinceLastRun =\n sinceLastRunFull.length > maxLogPerPeer\n ? sinceLastRunFull.slice(sinceLastRunFull.length - maxLogPerPeer)\n : sinceLastRunFull;\n\n const existingProfile = await readPeerProfile(options.memoryDir, peer.id);\n\n const remainingBudget = maxFields - fieldsAppliedTotal;\n if (remainingBudget <= 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_cap_reached\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n\n const prompt = buildPeerProfileReasonerPrompt({\n peer,\n existingProfile,\n log: sinceLastRun,\n maxFields: remainingBudget,\n });\n const messages = [\n { role: \"system\" as const, content: SYSTEM_MESSAGE },\n { role: \"user\" as const, content: prompt },\n ];\n let response: { content: string } | null;\n try {\n response = await options.llm.chatCompletion(messages, {\n temperature: 0.2,\n maxTokens: 1500,\n });\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] LLM call failed for \"${peer.id}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_llm_unavailable\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n if (!response || typeof response.content !== \"string\") {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_llm_unavailable\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n\n const proposals = parsePeerProfileReasonerResponse(response.content);\n if (proposals.length === 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"processed\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n\n // Build the merged profile. We never replace existing\n // provenance entries — provenance is append-only so the\n // operator retains a full audit trail.\n const sessionIdsInWindow = new Set(\n sinceLastRun\n .map((e) => e.sessionId)\n .filter((s): s is string => typeof s === \"string\" && s.length > 0),\n );\n const baseFields: Record<string, string> = existingProfile\n ? { ...existingProfile.fields }\n : {};\n const baseProvenance: Record<string, PeerProfileFieldProvenance[]> = {};\n if (existingProfile) {\n for (const [k, list] of Object.entries(existingProfile.provenance)) {\n baseProvenance[k] = [...list];\n }\n }\n\n // Codex P1 review on PR #736: the global `fieldsAppliedTotal`\n // counter must NOT be incremented until the profile write\n // actually succeeds. Otherwise a transient I/O error here\n // poisons the per-run cap for every subsequent peer — they get\n // marked `skipped_cap_reached` for fields that were never\n // persisted. Track the candidate count locally and only\n // commit it to the run-wide budget after `writePeerProfile`\n // returns successfully (Gotcha #25 — don't destroy old state\n // before confirming new state succeeds).\n const appliedFieldsForPeer: string[] = [];\n let droppedDueToCap = 0;\n let invalidProposalSeen = false;\n for (const proposal of proposals) {\n // Use a candidate-budget projection so we never propose more\n // than the run-wide cap allows even before we know the write\n // will succeed.\n const candidateBudget =\n maxFields - fieldsAppliedTotal - appliedFieldsForPeer.length;\n if (candidateBudget <= 0) {\n droppedDueToCap += 1;\n continue;\n }\n // Final defensive guard against prototype keys (parser\n // already drops them, but be redundant for safety).\n if (\n proposal.field === \"__proto__\" ||\n proposal.field === \"constructor\" ||\n proposal.field === \"prototype\"\n ) {\n invalidProposalSeen = true;\n continue;\n }\n // Sanity-check field key matches a conservative pattern so a\n // hostile LLM can't sneak path-traversal-shaped keys through\n // for downstream consumers.\n if (!/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(proposal.field)) {\n invalidProposalSeen = true;\n continue;\n }\n baseFields[proposal.field] = proposal.value;\n const sourceSessionId =\n proposal.sourceSessionId &&\n sessionIdsInWindow.has(proposal.sourceSessionId)\n ? proposal.sourceSessionId\n : undefined;\n const provEntry: PeerProfileFieldProvenance = {\n observedAt: nowIso,\n signal: proposal.signal,\n ...(sourceSessionId ? { sourceSessionId } : {}),\n ...(proposal.note && proposal.note.length > 0\n ? { note: proposal.note }\n : {}),\n };\n const list = baseProvenance[proposal.field] ?? [];\n list.push(provEntry);\n baseProvenance[proposal.field] = list;\n appliedFieldsForPeer.push(proposal.field);\n // NOTE: fieldsAppliedTotal is NOT incremented here — see the\n // P1 comment above. We commit the budget after the write\n // succeeds.\n }\n\n if (appliedFieldsForPeer.length === 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: invalidProposalSeen\n ? \"skipped_invalid_proposal\"\n : droppedDueToCap > 0\n ? \"skipped_cap_reached\"\n : \"processed\",\n fieldsApplied: 0,\n droppedDueToCap,\n fields: [],\n });\n continue;\n }\n\n const merged: PeerProfile = {\n peerId: peer.id,\n updatedAt: nowIso,\n fields: baseFields,\n provenance: baseProvenance,\n };\n await writePeerProfile(options.memoryDir, merged);\n // Write succeeded — NOW commit the budget. A throw above\n // bubbles to the outer catch, where the peer is recorded as\n // `error` and the global cap remains intact for subsequent\n // peers (Codex P1 fix on PR #736).\n fieldsAppliedTotal += appliedFieldsForPeer.length;\n\n // Append a run marker so the next reasoner pass can compute\n // \"interactions since last run\" without a dedicated state\n // file. The marker is best-effort — a write failure here\n // logs but does not roll back the profile (the operator\n // would prefer a slightly noisy threshold over a lost\n // profile update).\n const wantsMarker = options.appendRunMarkerToLog ?? true;\n if (wantsMarker) {\n try {\n await appendInteractionLog(options.memoryDir, peer.id, {\n timestamp: nowIso,\n kind: RUN_MARKER_KIND,\n summary: `applied ${appliedFieldsForPeer.length} field(s) via ${options.model ?? \"unknown-model\"}`,\n });\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] run-marker append failed for \"${peer.id}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n result.perPeer.push({\n peerId: peer.id,\n status: \"processed\",\n fieldsApplied: appliedFieldsForPeer.length,\n droppedDueToCap,\n fields: appliedFieldsForPeer,\n });\n result.peersProcessed += 1;\n result.fieldsApplied = fieldsAppliedTotal;\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] error processing peer \"${peer.id}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n result.perPeer.push({\n peerId: peer.id,\n status: \"error\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,12 @@
2
2
  var DEFAULT_CITATION_FORMAT = "[Source: agent={agent}, session={sessionId}, ts={ts}]";
3
3
  var CITATION_UNKNOWN = "unknown";
4
4
  function defaultCitationMatcher() {
5
- return /\[Source:\s*([^\]\n]+?)\]/gi;
5
+ return /\[Source:([^\]\n]{1,1024})\]/gi;
6
+ }
7
+ function trimTrailingWhitespace(text) {
8
+ let end = text.length;
9
+ while (end > 0 && /\s/u.test(text[end - 1])) end--;
10
+ return text.slice(0, end);
6
11
  }
7
12
  function deriveSessionId(session) {
8
13
  if (!session) return void 0;
@@ -111,7 +116,7 @@ function hasCitationForTemplate(text, template) {
111
116
  function attachCitation(text, ctx, template = DEFAULT_CITATION_FORMAT) {
112
117
  if (typeof text !== "string") return text;
113
118
  if (hasCitationForTemplate(text, template)) return text;
114
- const trimmedEnd = text.replace(/\s+$/u, "");
119
+ const trimmedEnd = trimTrailingWhitespace(text);
115
120
  if (trimmedEnd.length === 0) return text;
116
121
  const citation = formatCitation(ctx, template);
117
122
  const trailing = text.slice(trimmedEnd.length);
@@ -235,4 +240,4 @@ export {
235
240
  stripCitation,
236
241
  stripCitationForTemplate
237
242
  };
238
- //# sourceMappingURL=chunk-4H5ZJHEN.js.map
243
+ //# sourceMappingURL=chunk-J6A3CX5N.js.map