@remnic/core 9.3.738 → 9.3.740
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.
- package/dist/access-boundary.d.ts +6 -6
- package/dist/access-boundary.js +13 -13
- package/dist/access-cli.js +35 -35
- package/dist/access-http.d.ts +6 -6
- package/dist/access-http.js +18 -18
- package/dist/access-mcp.d.ts +6 -6
- package/dist/access-mcp.js +17 -17
- package/dist/access-operations-batch.js +15 -15
- package/dist/access-operations.d.ts +6 -6
- package/dist/access-operations.js +16 -16
- package/dist/{access-service-DwQ6BIQw.d.ts → access-service-Cbk-Kv_A.d.ts} +1 -1
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +12 -12
- package/dist/access-surface-catalog.d.ts +6 -6
- package/dist/active-memory-bridge.js +3 -3
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.js +2 -2
- package/dist/buffer.js +2 -2
- package/dist/capabilities.d.ts +97 -1
- package/dist/capabilities.js +7 -1
- package/dist/causal-consolidation.js +4 -3
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-IFCM6OLK.js → chunk-3TUDKDTQ.js} +7 -7
- package/dist/{chunk-A7INTIC5.js → chunk-4J53ZVPQ.js} +3 -3
- package/dist/{chunk-XYUFGHUW.js → chunk-56F6EAQ6.js} +2 -2
- package/dist/{chunk-F6OEXJQQ.js → chunk-6XG3NQRC.js} +2 -2
- package/dist/{chunk-24ZLC5QN.js → chunk-7N7S2RKF.js} +2 -2
- package/dist/{chunk-Y5OOHER4.js → chunk-7Y5KKMIP.js} +12 -11
- package/dist/chunk-7Y5KKMIP.js.map +1 -0
- package/dist/{chunk-VRBBZH4U.js → chunk-AX4TRSRE.js} +3 -3
- package/dist/{chunk-66RZ4EIT.js → chunk-BE6NEQLF.js} +3 -3
- package/dist/{chunk-YF2MMBKA.js → chunk-BJPL5WAL.js} +3 -3
- package/dist/{chunk-EO5QWINU.js → chunk-C3Z5YJSM.js} +5 -2
- package/dist/chunk-C3Z5YJSM.js.map +1 -0
- package/dist/{chunk-GEXI357K.js → chunk-C7C5WIIF.js} +2 -2
- package/dist/{chunk-L3TYGUTH.js → chunk-CMPXLMC3.js} +3 -3
- package/dist/{chunk-W2FBVBQ4.js → chunk-DBVBV2RF.js} +4 -4
- package/dist/{chunk-JQSP7YBJ.js → chunk-ELL5VYXC.js} +5 -4
- package/dist/chunk-ELL5VYXC.js.map +1 -0
- package/dist/{chunk-IQOEW5YV.js → chunk-FCQ7ZYCQ.js} +2 -2
- package/dist/{chunk-IIBFCZA7.js → chunk-FWXLKH2H.js} +3 -3
- package/dist/{chunk-RJBW4EOU.js → chunk-G7Q4D2G3.js} +74 -2
- package/dist/{chunk-RJBW4EOU.js.map → chunk-G7Q4D2G3.js.map} +1 -1
- package/dist/{chunk-WVAEIKUY.js → chunk-GPX5E36D.js} +5 -4
- package/dist/chunk-GPX5E36D.js.map +1 -0
- package/dist/{chunk-23PLOBWT.js → chunk-H3WSZWHK.js} +22 -20
- package/dist/chunk-H3WSZWHK.js.map +1 -0
- package/dist/{chunk-Q6BAA57B.js → chunk-JFCS6PQU.js} +2 -2
- package/dist/chunk-JFCS6PQU.js.map +1 -0
- package/dist/{chunk-CKHPUOAP.js → chunk-K2N6WIZM.js} +3 -3
- package/dist/{chunk-BHJAMSVC.js → chunk-LD6A7XG5.js} +7 -7
- package/dist/{chunk-AKKFHN3I.js → chunk-M4WI23X3.js} +2 -2
- package/dist/{chunk-N2O23WYA.js → chunk-M5KLYHHI.js} +2 -2
- package/dist/{chunk-TNLXCSXV.js → chunk-O4IWVWSW.js} +5 -4
- package/dist/chunk-O4IWVWSW.js.map +1 -0
- package/dist/{chunk-OQQRWPFZ.js → chunk-OS5RKJOH.js} +2 -2
- package/dist/{chunk-MAU7DFCJ.js → chunk-OT5K26NG.js} +5 -4
- package/dist/chunk-OT5K26NG.js.map +1 -0
- package/dist/{chunk-DS3FZ7GS.js → chunk-OUSOMUFV.js} +2 -2
- package/dist/{chunk-7B7IPPN5.js → chunk-OVYBENIJ.js} +3 -3
- package/dist/{chunk-5RZOFIDL.js → chunk-PI4JHLDD.js} +6 -3
- package/dist/chunk-PI4JHLDD.js.map +1 -0
- package/dist/{chunk-EU543UDC.js → chunk-QCO2QFLZ.js} +7 -7
- package/dist/chunk-QCO2QFLZ.js.map +1 -0
- package/dist/{chunk-YXOXAQ5Y.js → chunk-TQQN64J2.js} +5 -4
- package/dist/chunk-TQQN64J2.js.map +1 -0
- package/dist/{chunk-WDJCZQB4.js → chunk-UGFPGWJB.js} +2 -2
- package/dist/{chunk-75PDC7VT.js → chunk-UOVDO6TC.js} +6 -5
- package/dist/chunk-UOVDO6TC.js.map +1 -0
- package/dist/{chunk-BVG2IZOB.js → chunk-USHVHI6J.js} +7 -6
- package/dist/chunk-USHVHI6J.js.map +1 -0
- package/dist/{chunk-56LP72AD.js → chunk-UYYSOMS2.js} +4 -4
- package/dist/{chunk-EKQ3LWBM.js → chunk-VC7QELR6.js} +659 -584
- package/dist/chunk-VC7QELR6.js.map +1 -0
- package/dist/{chunk-XIQAOGAA.js → chunk-WGDE2TJP.js} +7 -6
- package/dist/chunk-WGDE2TJP.js.map +1 -0
- package/dist/{chunk-UFS7OXGL.js → chunk-WHEF76BQ.js} +5 -2
- package/dist/chunk-WHEF76BQ.js.map +1 -0
- package/dist/{chunk-QTJOORGN.js → chunk-WIMKOTNY.js} +2 -2
- package/dist/{chunk-GXTYEG7Q.js → chunk-WO22UEM3.js} +2 -2
- package/dist/{chunk-BHQQVUEQ.js → chunk-X5PMDVY4.js} +6 -5
- package/dist/{chunk-BHQQVUEQ.js.map → chunk-X5PMDVY4.js.map} +1 -1
- package/dist/{chunk-C53JDEH3.js → chunk-Y62QPRKV.js} +2 -2
- package/dist/{chunk-WFNH4UDF.js → chunk-Y6PNYJVO.js} +3 -3
- package/dist/{chunk-6YKIYQU7.js → chunk-YNHRWFAP.js} +5 -2
- package/dist/{chunk-6YKIYQU7.js.map → chunk-YNHRWFAP.js.map} +1 -1
- package/dist/{cli-BlEIWDQw.d.ts → cli-BQW2xKdb.d.ts} +2 -2
- package/dist/cli.d.ts +7 -7
- package/dist/cli.js +29 -29
- package/dist/compounding/engine.js +4 -4
- package/dist/connectors/codex-materialize-runner.js +2 -2
- package/dist/connectors/index.js +4 -3
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-CWO3LIWV.js → contradiction-scan-UERKJESC.js} +3 -3
- package/dist/day-summary.js +2 -1
- package/dist/embedding-fallback.js +2 -2
- package/dist/entity-retrieval.js +3 -3
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction.js +4 -4
- package/dist/{first-start-migration-5DXD3TF2.js → first-start-migration-GQ3UQNIL.js} +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +45 -45
- package/dist/local-llm.js +2 -2
- package/dist/maintenance/memory-governance.js +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/namespaces/migrate.js +7 -7
- package/dist/namespaces/principal.js +2 -2
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +3 -3
- package/dist/operator-toolkit.js +13 -13
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.js +7 -7
- package/dist/{orchestrator-CJZ21ZiB.d.ts → orchestrator-BOAFN_Yr.d.ts} +186 -56
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +29 -29
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +3 -3
- package/dist/semantic-consolidation.js +4 -3
- package/dist/semantic-rule-promotion.js +2 -2
- package/dist/semantic-rule-verifier.js +2 -2
- package/dist/shared-context/manager.js +2 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/{tier-stats-QID73BBB.js → tier-stats-CSKL7BDG.js} +2 -2
- package/dist/verified-recall.js +2 -2
- package/package.json +2 -2
- package/src/access-service.ts +2 -1
- package/src/capabilities.test.ts +166 -0
- package/src/capabilities.ts +267 -0
- package/src/cli/research-status-commands.ts +2 -1
- package/src/cli.ts +3 -2
- package/src/compounding/engine.ts +2 -1
- package/src/connectors/codex-marketplace.ts +2 -1
- package/src/day-summary.ts +2 -1
- package/src/embedding-fallback.ts +2 -1
- package/src/extraction.ts +3 -2
- package/src/local-llm.ts +2 -1
- package/src/maintenance/namespace-maintenance-fanout.ts +2 -1
- package/src/maintenance/namespace-planner.ts +2 -1
- package/src/operator-toolkit.ts +1 -0
- package/src/orchestration/conversation-index-coordinator.ts +344 -0
- package/src/orchestration/recall-rerank-coordinator.ts +425 -0
- package/src/orchestration/semantic-consolidation-coordinator.ts +3 -2
- package/src/orchestrator.ts +89 -598
- package/src/search/embed-helper.ts +2 -1
- package/src/search/factory.ts +2 -1
- package/src/semantic-consolidation.ts +2 -1
- package/src/shared-context/manager.ts +3 -2
- package/src/storage.ts +1 -1
- package/src/summarizer.ts +3 -2
- package/dist/chunk-23PLOBWT.js.map +0 -1
- package/dist/chunk-5RZOFIDL.js.map +0 -1
- package/dist/chunk-75PDC7VT.js.map +0 -1
- package/dist/chunk-BVG2IZOB.js.map +0 -1
- package/dist/chunk-EKQ3LWBM.js.map +0 -1
- package/dist/chunk-EO5QWINU.js.map +0 -1
- package/dist/chunk-EU543UDC.js.map +0 -1
- package/dist/chunk-JQSP7YBJ.js.map +0 -1
- package/dist/chunk-MAU7DFCJ.js.map +0 -1
- package/dist/chunk-Q6BAA57B.js.map +0 -1
- package/dist/chunk-TNLXCSXV.js.map +0 -1
- package/dist/chunk-UFS7OXGL.js.map +0 -1
- package/dist/chunk-WVAEIKUY.js.map +0 -1
- package/dist/chunk-XIQAOGAA.js.map +0 -1
- package/dist/chunk-Y5OOHER4.js.map +0 -1
- package/dist/chunk-YXOXAQ5Y.js.map +0 -1
- /package/dist/{chunk-IFCM6OLK.js.map → chunk-3TUDKDTQ.js.map} +0 -0
- /package/dist/{chunk-A7INTIC5.js.map → chunk-4J53ZVPQ.js.map} +0 -0
- /package/dist/{chunk-XYUFGHUW.js.map → chunk-56F6EAQ6.js.map} +0 -0
- /package/dist/{chunk-F6OEXJQQ.js.map → chunk-6XG3NQRC.js.map} +0 -0
- /package/dist/{chunk-24ZLC5QN.js.map → chunk-7N7S2RKF.js.map} +0 -0
- /package/dist/{chunk-VRBBZH4U.js.map → chunk-AX4TRSRE.js.map} +0 -0
- /package/dist/{chunk-66RZ4EIT.js.map → chunk-BE6NEQLF.js.map} +0 -0
- /package/dist/{chunk-YF2MMBKA.js.map → chunk-BJPL5WAL.js.map} +0 -0
- /package/dist/{chunk-GEXI357K.js.map → chunk-C7C5WIIF.js.map} +0 -0
- /package/dist/{chunk-L3TYGUTH.js.map → chunk-CMPXLMC3.js.map} +0 -0
- /package/dist/{chunk-W2FBVBQ4.js.map → chunk-DBVBV2RF.js.map} +0 -0
- /package/dist/{chunk-IQOEW5YV.js.map → chunk-FCQ7ZYCQ.js.map} +0 -0
- /package/dist/{chunk-IIBFCZA7.js.map → chunk-FWXLKH2H.js.map} +0 -0
- /package/dist/{chunk-CKHPUOAP.js.map → chunk-K2N6WIZM.js.map} +0 -0
- /package/dist/{chunk-BHJAMSVC.js.map → chunk-LD6A7XG5.js.map} +0 -0
- /package/dist/{chunk-AKKFHN3I.js.map → chunk-M4WI23X3.js.map} +0 -0
- /package/dist/{chunk-N2O23WYA.js.map → chunk-M5KLYHHI.js.map} +0 -0
- /package/dist/{chunk-OQQRWPFZ.js.map → chunk-OS5RKJOH.js.map} +0 -0
- /package/dist/{chunk-DS3FZ7GS.js.map → chunk-OUSOMUFV.js.map} +0 -0
- /package/dist/{chunk-7B7IPPN5.js.map → chunk-OVYBENIJ.js.map} +0 -0
- /package/dist/{chunk-WDJCZQB4.js.map → chunk-UGFPGWJB.js.map} +0 -0
- /package/dist/{chunk-56LP72AD.js.map → chunk-UYYSOMS2.js.map} +0 -0
- /package/dist/{chunk-QTJOORGN.js.map → chunk-WIMKOTNY.js.map} +0 -0
- /package/dist/{chunk-GXTYEG7Q.js.map → chunk-WO22UEM3.js.map} +0 -0
- /package/dist/{chunk-C53JDEH3.js.map → chunk-Y62QPRKV.js.map} +0 -0
- /package/dist/{chunk-WFNH4UDF.js.map → chunk-Y6PNYJVO.js.map} +0 -0
- /package/dist/{contradiction-scan-CWO3LIWV.js.map → contradiction-scan-UERKJESC.js.map} +0 -0
- /package/dist/{first-start-migration-5DXD3TF2.js.map → first-start-migration-GQ3UQNIL.js.map} +0 -0
- /package/dist/{tier-stats-QID73BBB.js.map → tier-stats-CSKL7BDG.js.map} +0 -0
package/src/local-llm.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ModelRegistry } from "./model-registry.js";
|
|
|
6
6
|
import { launchProcessSync } from "./runtime/child-process.js";
|
|
7
7
|
import { mergeEnv, readEnvVar } from "./runtime/env.js";
|
|
8
8
|
import { resolveLocalLlmCapabilities } from "./capabilities.js";
|
|
9
|
+
import { resolvePipelineProcessingCapabilities } from "./capabilities.js";
|
|
9
10
|
|
|
10
11
|
/** Trim trailing slash characters without backtracking regex. */
|
|
11
12
|
function trimTrailingSlashes(s: string): string {
|
|
@@ -1186,7 +1187,7 @@ export class LocalLlmClient {
|
|
|
1186
1187
|
: this.estimateTokens(messages, content);
|
|
1187
1188
|
|
|
1188
1189
|
const durationMs = Date.now() - startedAtMs;
|
|
1189
|
-
if (this.config.
|
|
1190
|
+
if (resolvePipelineProcessingCapabilities(this.config).slowLog && durationMs >= this.config.slowLogThresholdMs) {
|
|
1190
1191
|
const promptChars = messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
|
|
1191
1192
|
const op = options.operation ? ` op=${options.operation}` : "";
|
|
1192
1193
|
log.warn(
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { resolveNamespaceCapabilities } from "../capabilities.js";
|
|
28
28
|
import type { NamespaceCatalog } from "../namespaces/catalog.js";
|
|
29
29
|
import type { PluginConfig } from "../types.js";
|
|
30
|
+
import { resolveConversationContextCapabilities } from "../capabilities.js";
|
|
30
31
|
import {
|
|
31
32
|
planNamespaceMaintenance,
|
|
32
33
|
readNamespaceMaintenanceLastRanStatuses,
|
|
@@ -262,7 +263,7 @@ export async function summarizeNamespaceMaintenanceHealth(
|
|
|
262
263
|
|
|
263
264
|
return {
|
|
264
265
|
generatedAt,
|
|
265
|
-
fanoutEnabled: config.
|
|
266
|
+
fanoutEnabled: resolveConversationContextCapabilities(config).maintenanceNamespaceFanout !== false,
|
|
266
267
|
namespacesEnabled: resolveNamespaceCapabilities(config).namespaces,
|
|
267
268
|
maxNamespacesPerCycle: config.maintenanceMaxNamespacesPerCycle,
|
|
268
269
|
jobs,
|
|
@@ -11,6 +11,7 @@ import { resolveNamespaceStorageRoot } from "../namespaces/storage.js";
|
|
|
11
11
|
import { displayErrorDetail } from "../runtime/better-sqlite.js";
|
|
12
12
|
import type { PluginConfig } from "../types.js";
|
|
13
13
|
import { getConfiguredNamespaces } from "../scopes/scope-plan.js";
|
|
14
|
+
import { resolveConversationContextCapabilities } from "../capabilities.js";
|
|
14
15
|
|
|
15
16
|
export type NamespaceMaintenanceJobName = string;
|
|
16
17
|
|
|
@@ -227,7 +228,7 @@ export async function planNamespaceMaintenance(
|
|
|
227
228
|
});
|
|
228
229
|
}
|
|
229
230
|
|
|
230
|
-
if (resolveNamespaceCapabilities(config).namespaces && config.
|
|
231
|
+
if (resolveNamespaceCapabilities(config).namespaces && resolveConversationContextCapabilities(config).maintenanceNamespaceFanout !== false) {
|
|
231
232
|
const configuredSet = new Set(configured);
|
|
232
233
|
try {
|
|
233
234
|
const records = options.catalog?.enabled ? await options.catalog.listNamespaces() : [];
|
package/src/operator-toolkit.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { reportBufferSurpriseDistribution } from "./buffer-surprise-report.js";
|
|
|
56
56
|
import { readJudgeVerdictStats } from "./extraction-judge-telemetry.js";
|
|
57
57
|
import { resolveIndexingCapabilities } from "./capabilities.js";
|
|
58
58
|
|
|
59
|
+
|
|
59
60
|
const OPENCLAW_REMNIC_PLUGIN_IDS = ["openclaw-remnic", "openclaw-engram"] as const;
|
|
60
61
|
|
|
61
62
|
function getOpenClawPluginEntries(raw: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
@@ -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
|
+
}
|