@remnic/core 9.3.737 → 9.3.739

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 (144) hide show
  1. package/dist/access-boundary.d.ts +6 -6
  2. package/dist/access-boundary.js +10 -10
  3. package/dist/access-cli.js +26 -26
  4. package/dist/access-http.d.ts +6 -6
  5. package/dist/access-http.js +15 -15
  6. package/dist/access-mcp.d.ts +6 -6
  7. package/dist/access-mcp.js +14 -14
  8. package/dist/access-operations-batch.js +12 -12
  9. package/dist/access-operations.d.ts +6 -6
  10. package/dist/access-operations.js +13 -13
  11. package/dist/{access-service-DwQ6BIQw.d.ts → access-service-Cbk-Kv_A.d.ts} +1 -1
  12. package/dist/access-service.d.ts +6 -6
  13. package/dist/access-service.js +9 -9
  14. package/dist/access-surface-catalog.d.ts +6 -6
  15. package/dist/active-memory-bridge.js +3 -3
  16. package/dist/bootstrap.d.ts +5 -5
  17. package/dist/buffer.js +2 -1
  18. package/dist/capabilities.d.ts +98 -1
  19. package/dist/capabilities.js +7 -1
  20. package/dist/{chunk-G54IDCD3.js → chunk-23PLOBWT.js} +30 -27
  21. package/dist/chunk-23PLOBWT.js.map +1 -0
  22. package/dist/{chunk-K3Y2SJTN.js → chunk-56LP72AD.js} +6 -6
  23. package/dist/{chunk-ZBZ4XULO.js → chunk-66RZ4EIT.js} +3 -3
  24. package/dist/{chunk-IPI45Q4S.js → chunk-75PDC7VT.js} +9 -8
  25. package/dist/chunk-75PDC7VT.js.map +1 -0
  26. package/dist/{chunk-GB2FZOWB.js → chunk-7B7IPPN5.js} +5 -5
  27. package/dist/{chunk-QNTLWH3T.js → chunk-A7INTIC5.js} +3 -3
  28. package/dist/{chunk-FLPKEMSO.js → chunk-AKKFHN3I.js} +2 -2
  29. package/dist/{chunk-AAIHDIMD.js → chunk-BHJAMSVC.js} +7 -7
  30. package/dist/{chunk-BXPG5AVX.js → chunk-BHQQVUEQ.js} +6 -6
  31. package/dist/{chunk-HFXZIF3V.js → chunk-BVG2IZOB.js} +3 -3
  32. package/dist/{chunk-NDKW67ER.js → chunk-C53JDEH3.js} +4 -4
  33. package/dist/{chunk-AESC5OQJ.js → chunk-CKHPUOAP.js} +2 -2
  34. package/dist/{chunk-OYHYBHHI.js → chunk-DS3FZ7GS.js} +4 -4
  35. package/dist/{chunk-UO6MPN77.js → chunk-EU543UDC.js} +10 -9
  36. package/dist/chunk-EU543UDC.js.map +1 -0
  37. package/dist/{chunk-PB5YON2G.js → chunk-IEWUPZ75.js} +659 -584
  38. package/dist/chunk-IEWUPZ75.js.map +1 -0
  39. package/dist/{chunk-E4LYJ7PY.js → chunk-IFCM6OLK.js} +6 -6
  40. package/dist/{chunk-5BOQNB2K.js → chunk-JQSP7YBJ.js} +3 -3
  41. package/dist/{chunk-2T2N7ISV.js → chunk-L3TYGUTH.js} +3 -3
  42. package/dist/{chunk-AB2E5DCN.js → chunk-MAU7DFCJ.js} +2 -2
  43. package/dist/{chunk-P6VLG7KE.js → chunk-N2O23WYA.js} +2 -2
  44. package/dist/{chunk-HYNHLBKA.js → chunk-OQQRWPFZ.js} +5 -2
  45. package/dist/chunk-OQQRWPFZ.js.map +1 -0
  46. package/dist/{chunk-IXGXALRV.js → chunk-QTJOORGN.js} +2 -2
  47. package/dist/{chunk-JWDE7OVJ.js → chunk-RJBW4EOU.js} +46 -2
  48. package/dist/{chunk-JWDE7OVJ.js.map → chunk-RJBW4EOU.js.map} +1 -1
  49. package/dist/{chunk-AE7NTZ75.js → chunk-TNLXCSXV.js} +3 -3
  50. package/dist/{chunk-NNJKEAP2.js → chunk-VRBBZH4U.js} +4 -4
  51. package/dist/{chunk-VOEVVIP3.js → chunk-W2FBVBQ4.js} +4 -4
  52. package/dist/{chunk-WNGXQMRI.js → chunk-WFNH4UDF.js} +5 -2
  53. package/dist/chunk-WFNH4UDF.js.map +1 -0
  54. package/dist/{chunk-CHJIWG2H.js → chunk-WVAEIKUY.js} +2 -2
  55. package/dist/{chunk-FF36YNKY.js → chunk-XIQAOGAA.js} +11 -10
  56. package/dist/chunk-XIQAOGAA.js.map +1 -0
  57. package/dist/{chunk-HLPYL6LC.js → chunk-Y5OOHER4.js} +14 -12
  58. package/dist/chunk-Y5OOHER4.js.map +1 -0
  59. package/dist/{chunk-GYICIICQ.js → chunk-YF2MMBKA.js} +9 -7
  60. package/dist/chunk-YF2MMBKA.js.map +1 -0
  61. package/dist/{chunk-IX4L7E5G.js → chunk-YXOXAQ5Y.js} +2 -2
  62. package/dist/{cli-BlEIWDQw.d.ts → cli-BQW2xKdb.d.ts} +2 -2
  63. package/dist/cli.d.ts +7 -7
  64. package/dist/cli.js +21 -21
  65. package/dist/compounding/engine.js +2 -2
  66. package/dist/contradiction/index.js +2 -2
  67. package/dist/{contradiction-scan-2B4AE4TI.js → contradiction-scan-CWO3LIWV.js} +3 -3
  68. package/dist/embedding-fallback.js +2 -2
  69. package/dist/entity-retrieval.js +2 -2
  70. package/dist/explicit-capture.d.ts +5 -5
  71. package/dist/explicit-capture.js +2 -2
  72. package/dist/extraction.js +3 -3
  73. package/dist/{first-start-migration-2RQYDIZ7.js → first-start-migration-5DXD3TF2.js} +2 -2
  74. package/dist/index.d.ts +8 -8
  75. package/dist/index.js +32 -32
  76. package/dist/local-llm.js +2 -2
  77. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  78. package/dist/namespaces/migrate.js +6 -6
  79. package/dist/namespaces/principal.js +2 -2
  80. package/dist/namespaces/search.js +4 -4
  81. package/dist/namespaces/storage.js +2 -2
  82. package/dist/operator-toolkit.js +11 -11
  83. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  84. package/dist/orchestration/maintenance.js +6 -6
  85. package/dist/{orchestrator-CJZ21ZiB.d.ts → orchestrator-BOAFN_Yr.d.ts} +186 -56
  86. package/dist/orchestrator.d.ts +4 -4
  87. package/dist/orchestrator.js +21 -21
  88. package/dist/search/embed-helper.js +2 -2
  89. package/dist/search/factory.js +3 -3
  90. package/dist/search/index.js +3 -3
  91. package/dist/summarizer.js +3 -3
  92. package/dist/{tier-stats-KPDIISG7.js → tier-stats-QID73BBB.js} +2 -2
  93. package/package.json +2 -2
  94. package/src/access-service.ts +7 -8
  95. package/src/buffer.ts +2 -1
  96. package/src/capabilities.test.ts +137 -0
  97. package/src/capabilities.ts +209 -0
  98. package/src/cli/creation-ledger-commands.ts +3 -3
  99. package/src/cli/research-status-commands.ts +4 -4
  100. package/src/cli.ts +9 -10
  101. package/src/compounding/engine.ts +7 -7
  102. package/src/correction/correction-access-wiring.ts +3 -2
  103. package/src/extraction.ts +8 -10
  104. package/src/operator-toolkit.ts +6 -7
  105. package/src/orchestration/conversation-index-coordinator.ts +344 -0
  106. package/src/orchestration/entity-synthesis-coordinator.ts +2 -1
  107. package/src/orchestration/maintenance.ts +5 -7
  108. package/src/orchestration/recall-rerank-coordinator.ts +425 -0
  109. package/src/orchestration/semantic-consolidation-coordinator.ts +2 -2
  110. package/src/orchestrator.ts +88 -602
  111. package/dist/chunk-FF36YNKY.js.map +0 -1
  112. package/dist/chunk-G54IDCD3.js.map +0 -1
  113. package/dist/chunk-GYICIICQ.js.map +0 -1
  114. package/dist/chunk-HLPYL6LC.js.map +0 -1
  115. package/dist/chunk-HYNHLBKA.js.map +0 -1
  116. package/dist/chunk-IPI45Q4S.js.map +0 -1
  117. package/dist/chunk-PB5YON2G.js.map +0 -1
  118. package/dist/chunk-UO6MPN77.js.map +0 -1
  119. package/dist/chunk-WNGXQMRI.js.map +0 -1
  120. /package/dist/{chunk-K3Y2SJTN.js.map → chunk-56LP72AD.js.map} +0 -0
  121. /package/dist/{chunk-ZBZ4XULO.js.map → chunk-66RZ4EIT.js.map} +0 -0
  122. /package/dist/{chunk-GB2FZOWB.js.map → chunk-7B7IPPN5.js.map} +0 -0
  123. /package/dist/{chunk-QNTLWH3T.js.map → chunk-A7INTIC5.js.map} +0 -0
  124. /package/dist/{chunk-FLPKEMSO.js.map → chunk-AKKFHN3I.js.map} +0 -0
  125. /package/dist/{chunk-AAIHDIMD.js.map → chunk-BHJAMSVC.js.map} +0 -0
  126. /package/dist/{chunk-BXPG5AVX.js.map → chunk-BHQQVUEQ.js.map} +0 -0
  127. /package/dist/{chunk-HFXZIF3V.js.map → chunk-BVG2IZOB.js.map} +0 -0
  128. /package/dist/{chunk-NDKW67ER.js.map → chunk-C53JDEH3.js.map} +0 -0
  129. /package/dist/{chunk-AESC5OQJ.js.map → chunk-CKHPUOAP.js.map} +0 -0
  130. /package/dist/{chunk-OYHYBHHI.js.map → chunk-DS3FZ7GS.js.map} +0 -0
  131. /package/dist/{chunk-E4LYJ7PY.js.map → chunk-IFCM6OLK.js.map} +0 -0
  132. /package/dist/{chunk-5BOQNB2K.js.map → chunk-JQSP7YBJ.js.map} +0 -0
  133. /package/dist/{chunk-2T2N7ISV.js.map → chunk-L3TYGUTH.js.map} +0 -0
  134. /package/dist/{chunk-AB2E5DCN.js.map → chunk-MAU7DFCJ.js.map} +0 -0
  135. /package/dist/{chunk-P6VLG7KE.js.map → chunk-N2O23WYA.js.map} +0 -0
  136. /package/dist/{chunk-IXGXALRV.js.map → chunk-QTJOORGN.js.map} +0 -0
  137. /package/dist/{chunk-AE7NTZ75.js.map → chunk-TNLXCSXV.js.map} +0 -0
  138. /package/dist/{chunk-NNJKEAP2.js.map → chunk-VRBBZH4U.js.map} +0 -0
  139. /package/dist/{chunk-VOEVVIP3.js.map → chunk-W2FBVBQ4.js.map} +0 -0
  140. /package/dist/{chunk-CHJIWG2H.js.map → chunk-WVAEIKUY.js.map} +0 -0
  141. /package/dist/{chunk-IX4L7E5G.js.map → chunk-YXOXAQ5Y.js.map} +0 -0
  142. /package/dist/{contradiction-scan-2B4AE4TI.js.map → contradiction-scan-CWO3LIWV.js.map} +0 -0
  143. /package/dist/{first-start-migration-2RQYDIZ7.js.map → first-start-migration-5DXD3TF2.js.map} +0 -0
  144. /package/dist/{tier-stats-KPDIISG7.js.map → tier-stats-QID73BBB.js.map} +0 -0
@@ -24,12 +24,11 @@ import {
24
24
  type EvalHarnessStatus,
25
25
  } from "./evals.js";
26
26
  import { analyzeGraphHealth, type GraphHealthReport } from "./graph.js";
27
- import {
28
- resolveAccessSetupCapabilities,
27
+ import { resolveAccessSetupCapabilities,
29
28
  resolveCapabilities,
30
29
  resolveGraphConstructionCapabilities,
31
30
  resolveQmdCapabilities,
32
- resolveEvalCapabilities} from "./capabilities.js";
31
+ resolveEvalCapabilities, resolvePresentationCapabilities, resolveConsolidationCapabilities } from "./capabilities.js";
33
32
  import {
34
33
  analyzeSessionIntegrity,
35
34
  applySessionRepair,
@@ -934,7 +933,7 @@ async function buildOperatorConfigReviewReport(input: {
934
933
  config.memoryOsPreset !== "research-max" &&
935
934
  config.memoryOsPreset !== "local-llm-heavy" &&
936
935
  resolveIndexingCapabilities(config).queryAwareIndexing === false &&
937
- config.verbatimArtifactsEnabled === false &&
936
+ resolvePresentationCapabilities(config).verbatimArtifacts === false &&
938
937
  !caps.rerank
939
938
  ) {
940
939
  findings.push(buildConfigReviewFinding({
@@ -1565,11 +1564,11 @@ export async function summarizeBufferSurpriseDistribution(
1565
1564
  return {
1566
1565
  key: "buffer_surprise_distribution",
1567
1566
  status: "ok",
1568
- summary: config.bufferSurpriseTriggerEnabled
1567
+ summary: resolvePresentationCapabilities(config).bufferSurpriseTrigger
1569
1568
  ? "Surprise trigger is enabled but no telemetry has been recorded yet."
1570
1569
  : "Surprise trigger is disabled; no telemetry expected.",
1571
1570
  details: {
1572
- enabled: config.bufferSurpriseTriggerEnabled,
1571
+ enabled: resolvePresentationCapabilities(config).bufferSurpriseTrigger,
1573
1572
  distribution: dist,
1574
1573
  },
1575
1574
  };
@@ -1581,7 +1580,7 @@ export async function summarizeBufferSurpriseDistribution(
1581
1580
  status: "ok",
1582
1581
  summary: `Recent surprise: mean=${dist.mean.toFixed(3)}, median=${dist.median.toFixed(3)}, p90=${dist.p90.toFixed(3)}, triggered=${pct(dist.triggeredRate)}% over ${dist.count} turns (threshold=${dist.currentThreshold ?? config.bufferSurpriseThreshold}).`,
1583
1582
  details: {
1584
- enabled: config.bufferSurpriseTriggerEnabled,
1583
+ enabled: resolvePresentationCapabilities(config).bufferSurpriseTrigger,
1585
1584
  distribution: dist,
1586
1585
  },
1587
1586
  };
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Conversation index coordinator — extracted from the orchestrator (issue #1526).
3
+ *
4
+ * Owns the conversation-index subsystem: semantic recall over past
5
+ * conversations, plus the build / update / rebuild / inspect / health
6
+ * lifecycle of the on-disk chunk store and its pluggable search backend
7
+ * (qmd or faiss). Behavior-preserving move from orchestrator.ts — no logic
8
+ * changes; the orchestrator constructs one instance and keeps thin
9
+ * delegating methods so existing call sites and tests that exercise the
10
+ * private API continue to work.
11
+ *
12
+ * The backend and transcript are read through getters (not captured at
13
+ * construction) so that post-construction reassignment of the orchestrator's
14
+ * live fields — exercised by the conversation-index integration tests and by
15
+ * backend swap-in at deferred-init time — is honored. This mirrors the
16
+ * TierMigrationCoordinator accessor pattern.
17
+ */
18
+
19
+ import { readdir } from "node:fs/promises";
20
+ import path from "node:path";
21
+
22
+ import { resolveIndexingCapabilities } from "../capabilities.js";
23
+ import { cleanupConversationChunks } from "../conversation-index/cleanup.js";
24
+ import { chunkTranscriptEntries } from "../conversation-index/chunker.js";
25
+ import type { ConversationChunk } from "../conversation-index/chunker.js";
26
+ import { writeConversationChunks } from "../conversation-index/indexer.js";
27
+ import type {
28
+ ConversationIndexBackend,
29
+ ConversationIndexBackendHealth,
30
+ ConversationIndexBackendInspection,
31
+ } from "../conversation-index/backend.js";
32
+ import type { ConversationSearchResult } from "../conversation-index/search.js";
33
+ import type { TranscriptManager } from "../transcript.js";
34
+ import type { PluginConfig } from "../types.js";
35
+
36
+ /**
37
+ * Coordinator for the conversation-index subsystem.
38
+ *
39
+ * Holds the per-session last-update timestamps (previously an orchestrator
40
+ * field) and delegates chunk building / persistence / embedding to the
41
+ * configured backend.
42
+ */
43
+ export class ConversationIndexCoordinator {
44
+ private readonly config: PluginConfig;
45
+ private readonly getTranscript: () => TranscriptManager;
46
+ private readonly getBackend: () => ConversationIndexBackend | undefined;
47
+ private readonly indexDir: string;
48
+ private readonly lastUpdateAtMs = new Map<string, number>();
49
+
50
+ constructor(options: {
51
+ config: PluginConfig;
52
+ getTranscript: () => TranscriptManager;
53
+ getBackend: () => ConversationIndexBackend | undefined;
54
+ indexDir: string;
55
+ }) {
56
+ this.config = options.config;
57
+ this.getTranscript = options.getTranscript;
58
+ this.getBackend = options.getBackend;
59
+ this.indexDir = options.indexDir;
60
+ }
61
+
62
+ /** Semantic recall over past-conversation chunks (fail-open: empty on miss). */
63
+ async search(
64
+ retrievalQuery: string,
65
+ topK: number,
66
+ ): Promise<ConversationSearchResult[]> {
67
+ const backend = this.getBackend();
68
+ if (backend) {
69
+ return backend.search(retrievalQuery, topK);
70
+ }
71
+ return [];
72
+ }
73
+
74
+ /** Render conversation-recall search hits as a budgeted markdown section. */
75
+ formatRecallSection(
76
+ results: ConversationSearchResult[],
77
+ maxChars: number,
78
+ ): string | null {
79
+ if (!Array.isArray(results) || results.length === 0) return null;
80
+ const lines: string[] = ["## Semantic Recall (Past Conversations)", ""];
81
+ let used = 0;
82
+ for (const r of results) {
83
+ if (!r?.snippet) continue;
84
+ const chunk =
85
+ `### ${r.path}\n` +
86
+ `Score: ${r.score.toFixed(3)}\n\n` +
87
+ `${r.snippet.trim()}\n`;
88
+ if (used + chunk.length > maxChars) break;
89
+ lines.push(chunk);
90
+ used += chunk.length;
91
+ }
92
+ return used > 0 ? lines.join("\n") : null;
93
+ }
94
+
95
+ /** Recursively count `.md` chunk documents under a directory. */
96
+ async countChunkDocs(dir: string): Promise<number> {
97
+ try {
98
+ const entries = await readdir(dir, { withFileTypes: true });
99
+ let total = 0;
100
+ for (const entry of entries) {
101
+ const fullPath = path.join(dir, entry.name);
102
+ if (entry.isDirectory()) {
103
+ total += await this.countChunkDocs(fullPath);
104
+ continue;
105
+ }
106
+ if (entry.isFile() && entry.name.endsWith(".md")) {
107
+ total += 1;
108
+ }
109
+ }
110
+ return total;
111
+ } catch {
112
+ return 0;
113
+ }
114
+ }
115
+
116
+ /** Read recent transcript entries and chunk them for indexing. */
117
+ async buildChunks(
118
+ sessionKey?: string,
119
+ hours: number = 24,
120
+ ): Promise<ConversationChunk[]> {
121
+ const entries = await this.getTranscript().readRecent(hours, sessionKey);
122
+ const effectiveSessionKey = sessionKey ?? "all-sessions";
123
+ return chunkTranscriptEntries(effectiveSessionKey, entries, {
124
+ maxChars: this.config.conversationRecallMaxChars * 2,
125
+ maxTurns: Math.max(10, this.config.hourlySummariesMaxTurnsPerRun),
126
+ });
127
+ }
128
+
129
+ async getHealth(): Promise<{
130
+ enabled: boolean;
131
+ backend: "qmd" | "faiss";
132
+ status: "ok" | "degraded" | "disabled";
133
+ chunkDocCount: number;
134
+ lastUpdateAt: string | null;
135
+ qmdAvailable?: boolean;
136
+ faiss?: {
137
+ ok: boolean;
138
+ status: "ok" | "degraded" | "error";
139
+ indexPath: string;
140
+ message?: string;
141
+ manifest?: {
142
+ version: number;
143
+ modelId: string;
144
+ normalizedModelId: string;
145
+ dimension: number;
146
+ chunkCount: number;
147
+ updatedAt: string;
148
+ lastSuccessfulRebuildAt: string;
149
+ };
150
+ };
151
+ }> {
152
+ const chunkDocCount = await this.countChunkDocs(this.indexDir);
153
+ const lastUpdateAtMs = Math.max(0, ...this.lastUpdateAtMs.values());
154
+ const lastUpdateAt =
155
+ lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
156
+
157
+ if (!resolveIndexingCapabilities(this.config).conversationIndex) {
158
+ return {
159
+ enabled: false,
160
+ backend: this.config.conversationIndexBackend,
161
+ status: "disabled",
162
+ chunkDocCount,
163
+ lastUpdateAt,
164
+ };
165
+ }
166
+ const backend = this.getBackend();
167
+ const backendHealth: ConversationIndexBackendHealth = backend
168
+ ? await backend.health()
169
+ : {
170
+ backend: this.config.conversationIndexBackend,
171
+ status: "degraded" as const,
172
+ };
173
+ return {
174
+ enabled: true,
175
+ chunkDocCount,
176
+ lastUpdateAt,
177
+ ...backendHealth,
178
+ };
179
+ }
180
+
181
+ async inspect(): Promise<
182
+ ConversationIndexBackendInspection & {
183
+ enabled: boolean;
184
+ chunkDocCount: number;
185
+ lastUpdateAt: string | null;
186
+ }
187
+ > {
188
+ const chunkDocCount = await this.countChunkDocs(this.indexDir);
189
+ const lastUpdateAtMs = Math.max(0, ...this.lastUpdateAtMs.values());
190
+ const lastUpdateAt =
191
+ lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
192
+
193
+ if (!resolveIndexingCapabilities(this.config).conversationIndex) {
194
+ return {
195
+ enabled: false,
196
+ backend: this.config.conversationIndexBackend,
197
+ status: "disabled",
198
+ available: false,
199
+ indexPath: this.indexDir,
200
+ supportsIncrementalUpdate: true,
201
+ message: "Conversation index disabled by config",
202
+ metadata: {
203
+ chunkCount: chunkDocCount,
204
+ },
205
+ chunkDocCount,
206
+ lastUpdateAt,
207
+ };
208
+ }
209
+
210
+ const backend = this.getBackend();
211
+ const inspection: ConversationIndexBackendInspection = backend
212
+ ? await backend.inspect()
213
+ : {
214
+ backend: this.config.conversationIndexBackend,
215
+ status: "degraded" as const,
216
+ available: false,
217
+ indexPath: this.indexDir,
218
+ supportsIncrementalUpdate: true,
219
+ message: "Conversation index backend unavailable",
220
+ metadata: {
221
+ chunkCount: chunkDocCount,
222
+ },
223
+ };
224
+
225
+ return {
226
+ enabled: true,
227
+ chunkDocCount,
228
+ lastUpdateAt,
229
+ ...inspection,
230
+ };
231
+ }
232
+
233
+ async update(
234
+ sessionKey: string,
235
+ hours: number = 24,
236
+ opts?: { embed?: boolean; enforceMinInterval?: boolean },
237
+ ): Promise<{
238
+ chunks: number;
239
+ skipped: boolean;
240
+ reason?: string;
241
+ retryAfterMs?: number;
242
+ embedded?: boolean;
243
+ }> {
244
+ if (!resolveIndexingCapabilities(this.config).conversationIndex) {
245
+ return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
246
+ }
247
+ const enforceMinInterval = opts?.enforceMinInterval !== false;
248
+ if (enforceMinInterval) {
249
+ const minIntervalMs = Math.max(
250
+ 0,
251
+ this.config.conversationIndexMinUpdateIntervalMs,
252
+ );
253
+ const now = Date.now();
254
+ const last = this.lastUpdateAtMs.get(sessionKey) ?? 0;
255
+ const elapsed = now - last;
256
+ if (minIntervalMs > 0 && elapsed < minIntervalMs) {
257
+ return {
258
+ chunks: 0,
259
+ skipped: true,
260
+ reason: "min_interval",
261
+ retryAfterMs: minIntervalMs - elapsed,
262
+ embedded: false,
263
+ };
264
+ }
265
+ }
266
+ const chunks = await this.buildChunks(sessionKey, hours);
267
+ await writeConversationChunks(this.indexDir, chunks);
268
+ const retentionCutoffMs =
269
+ Number.isFinite(this.config.conversationIndexRetentionDays) &&
270
+ this.config.conversationIndexRetentionDays > 0
271
+ ? Date.now() -
272
+ this.config.conversationIndexRetentionDays * 24 * 60 * 60 * 1000
273
+ : undefined;
274
+ await cleanupConversationChunks(
275
+ this.indexDir,
276
+ this.config.conversationIndexRetentionDays,
277
+ );
278
+ const shouldEmbed =
279
+ opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
280
+ let embedded = false;
281
+
282
+ const backend = this.getBackend();
283
+ if (backend) {
284
+ const result = await backend.update(chunks, {
285
+ embed: shouldEmbed,
286
+ ...(retentionCutoffMs !== undefined ? { retentionCutoffMs } : {}),
287
+ });
288
+ embedded = result.embedded;
289
+ }
290
+
291
+ this.lastUpdateAtMs.set(sessionKey, Date.now());
292
+ return { chunks: chunks.length, skipped: false, embedded };
293
+ }
294
+
295
+ async rebuild(
296
+ sessionKey?: string,
297
+ hours: number = 24,
298
+ opts?: { embed?: boolean },
299
+ ): Promise<{
300
+ chunks: number;
301
+ skipped: boolean;
302
+ reason?: string;
303
+ embedded?: boolean;
304
+ rebuilt?: boolean;
305
+ }> {
306
+ if (!resolveIndexingCapabilities(this.config).conversationIndex) {
307
+ return {
308
+ chunks: 0,
309
+ skipped: true,
310
+ reason: "disabled",
311
+ embedded: false,
312
+ rebuilt: false,
313
+ };
314
+ }
315
+
316
+ const chunks = await this.buildChunks(sessionKey, hours);
317
+ await writeConversationChunks(this.indexDir, chunks);
318
+ await cleanupConversationChunks(
319
+ this.indexDir,
320
+ this.config.conversationIndexRetentionDays,
321
+ );
322
+
323
+ const shouldEmbed =
324
+ opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
325
+ let embedded = false;
326
+ let rebuilt = false;
327
+ const backend = this.getBackend();
328
+ if (backend) {
329
+ const result = await backend.rebuild(chunks, {
330
+ embed: shouldEmbed,
331
+ });
332
+ embedded = result.embedded;
333
+ rebuilt = result.rebuilt;
334
+ }
335
+
336
+ const stamp = Date.now();
337
+ if (sessionKey) {
338
+ this.lastUpdateAtMs.set(sessionKey, stamp);
339
+ } else {
340
+ this.lastUpdateAtMs.set("__rebuild__", stamp);
341
+ }
342
+ return { chunks: chunks.length, skipped: false, embedded, rebuilt };
343
+ }
344
+ }
@@ -36,6 +36,7 @@ import type {
36
36
  PluginConfig,
37
37
  } from "../types.js";
38
38
  import { log } from "../logger.js";
39
+ import { resolvePresentationCapabilities } from "../capabilities.js";
39
40
 
40
41
  // ---------------------------------------------------------------------------
41
42
  // Evidence helpers (moved verbatim from orchestrator.ts module scope)
@@ -172,7 +173,7 @@ export class EntitySynthesisCoordinator {
172
173
  ): Promise<number> {
173
174
  const { config } = this.deps;
174
175
  if (
175
- !config.entitySummaryEnabled
176
+ !resolvePresentationCapabilities(config).entitySummary
176
177
  || maxEntities <= 0
177
178
  || config.entitySynthesisMaxTokens <= 0
178
179
  ) return 0;
@@ -19,10 +19,8 @@
19
19
  * `orchestrator.requestQmdMaintenance` etc. continue to work.
20
20
  */
21
21
 
22
- import {
23
- resolveNamespaceCapabilities,
24
- resolveQmdCapabilities,
25
- } from "../capabilities.js";
22
+ import { resolveNamespaceCapabilities,
23
+ resolveQmdCapabilities,resolveConsolidationCapabilities, resolveRecallAuxiliaryCapabilities } from "../capabilities.js";
26
24
  import { existsSync } from "node:fs";
27
25
  import path from "node:path";
28
26
 
@@ -109,7 +107,7 @@ export class MaintenanceScheduler {
109
107
  * `await deferredReady` can rely on jobs.json being current.
110
108
  */
111
109
  async autoRegisterCrons(_signal: AbortSignal): Promise<void> {
112
- if (this.deps.config.daySummaryEnabled) {
110
+ if (resolveRecallAuxiliaryCapabilities(this.deps.config).daySummary) {
113
111
  try {
114
112
  await this.autoRegisterDaySummaryCron();
115
113
  } catch (err) {
@@ -137,14 +135,14 @@ export class MaintenanceScheduler {
137
135
  log.debug(`contradiction scan cron auto-register failed (non-fatal): ${err}`);
138
136
  }
139
137
  }
140
- if (this.deps.config.patternReinforcementEnabled) {
138
+ if (resolveConsolidationCapabilities(this.deps.config).patternReinforcement) {
141
139
  try {
142
140
  await this.autoRegisterPatternReinforcementCron();
143
141
  } catch (err) {
144
142
  log.debug(`pattern reinforcement cron auto-register failed (non-fatal): ${err}`);
145
143
  }
146
144
  }
147
- if (this.deps.config.graphEdgeDecayEnabled) {
145
+ if (resolveConsolidationCapabilities(this.deps.config).graphEdgeDecay) {
148
146
  try {
149
147
  await this.autoRegisterGraphEdgeDecayCron();
150
148
  } catch (err) {