@remnic/plugin-openclaw 1.0.5 → 1.0.7
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/README.md +36 -0
- package/dist/{calibration-3JHF25QT.js → calibration-BAC7KNKR.js} +2 -1
- package/dist/{causal-consolidation-Z3PHIFTL.js → causal-consolidation-S6M7UTZG.js} +4 -3
- package/dist/{causal-retrieval-5UPIKZ4I.js → causal-retrieval-3BKBXVXD.js} +1 -1
- package/dist/chunk-3A5ELHTT.js +61 -0
- package/dist/chunk-DIZW6H5J.js +136 -0
- package/dist/{chunk-5VTGFKKU.js → chunk-KPMXWORS.js} +17 -0
- package/dist/{chunk-RMFPW4VK.js → chunk-LN5UZQVG.js} +10 -0
- package/dist/{chunk-3SA5F4WT.js → chunk-NXLHSCLU.js} +125 -69
- package/dist/{chunk-J2FCINY7.js → chunk-QHMR3D7U.js} +1 -1
- package/dist/{chunk-Y65XJVI3.js → chunk-SVGN3ACY.js} +3 -3
- package/dist/contradiction-review-SVGBS3V5.js +21 -0
- package/dist/contradiction-scan-LRRLWUOS.js +376 -0
- package/dist/{engine-2TLD4YSC.js → engine-WGNTTFYE.js} +2 -2
- package/dist/{fallback-llm-HJRCHKSA.js → fallback-llm-QEAPMDW7.js} +2 -1
- package/dist/index.js +2439 -591
- package/dist/resolution-YITUVUTH.js +100 -0
- package/dist/{storage-HW6SRQCK.js → storage-BA6OBLMK.js} +1 -1
- package/openclaw.plugin.json +194 -4
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
SharedContextManager,
|
|
4
4
|
defaultTierMigrationCycleBudget,
|
|
5
5
|
external_exports
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SVGN3ACY.js";
|
|
7
7
|
import {
|
|
8
|
+
filterTrajectoriesByLookbackDays,
|
|
8
9
|
getCausalTrajectoryStoreStatus,
|
|
10
|
+
readCausalTrajectoryRecords,
|
|
9
11
|
searchCausalTrajectories
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LN5UZQVG.js";
|
|
11
13
|
import {
|
|
12
14
|
compareVersions
|
|
13
15
|
} from "./chunk-GUSMRW4H.js";
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
parseConsolidationResponse,
|
|
25
27
|
renderExtensionsFooter,
|
|
26
28
|
resolveExtensionsRoot
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-QHMR3D7U.js";
|
|
28
30
|
import {
|
|
29
31
|
ContentHashIndex,
|
|
30
32
|
MEMORY_LIFECYCLE_EVENT_SORT_ORDER,
|
|
@@ -66,7 +68,7 @@ import {
|
|
|
66
68
|
sortMemoryLifecycleEvents,
|
|
67
69
|
stripCitationForTemplate,
|
|
68
70
|
toMemoryPathRel
|
|
69
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-KPMXWORS.js";
|
|
70
72
|
import {
|
|
71
73
|
BoxBuilder,
|
|
72
74
|
assertIsoRecordedAt,
|
|
@@ -83,12 +85,14 @@ import {
|
|
|
83
85
|
import {
|
|
84
86
|
FallbackLlmClient,
|
|
85
87
|
buildChatCompletionTokenLimit,
|
|
86
|
-
extractJsonCandidates,
|
|
87
88
|
mergeEnv,
|
|
88
89
|
readEnvVar,
|
|
89
90
|
resolveHomeDir,
|
|
90
91
|
shouldAssumeOpenAiChatCompletions
|
|
91
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-NXLHSCLU.js";
|
|
93
|
+
import {
|
|
94
|
+
extractJsonCandidates
|
|
95
|
+
} from "./chunk-3A5ELHTT.js";
|
|
92
96
|
import {
|
|
93
97
|
listJsonFiles,
|
|
94
98
|
listNamedFiles,
|
|
@@ -233,6 +237,18 @@ function coerceBool(value) {
|
|
|
233
237
|
function coerceInstallExtension(value) {
|
|
234
238
|
return coerceBool(value);
|
|
235
239
|
}
|
|
240
|
+
function coerceNumber(value) {
|
|
241
|
+
if (typeof value === "number") {
|
|
242
|
+
return Number.isFinite(value) ? value : void 0;
|
|
243
|
+
}
|
|
244
|
+
if (typeof value === "string") {
|
|
245
|
+
const trimmed = value.trim();
|
|
246
|
+
if (trimmed.length === 0) return void 0;
|
|
247
|
+
const n = Number(trimmed);
|
|
248
|
+
return Number.isFinite(n) ? n : void 0;
|
|
249
|
+
}
|
|
250
|
+
return void 0;
|
|
251
|
+
}
|
|
236
252
|
|
|
237
253
|
// ../remnic-core/src/config.ts
|
|
238
254
|
var DEFAULT_MEMORY_DIR = path2.join(
|
|
@@ -292,6 +308,31 @@ function normalizeMemoryRelativeDir(raw, fallback) {
|
|
|
292
308
|
const normalized = trimmed.replace(/\\/g, "/").split("/").filter((segment) => segment.length > 0 && segment !== "." && segment !== "..").join("/");
|
|
293
309
|
return normalized.length > 0 ? normalized : fallback;
|
|
294
310
|
}
|
|
311
|
+
function parseContradictionScanConfig(raw) {
|
|
312
|
+
if (!raw || typeof raw !== "object") {
|
|
313
|
+
return {
|
|
314
|
+
enabled: false,
|
|
315
|
+
similarityFloor: 0.82,
|
|
316
|
+
topicOverlapFloor: 0.4,
|
|
317
|
+
maxPairsPerRun: 500,
|
|
318
|
+
cooldownDays: 14,
|
|
319
|
+
autoMergeDuplicates: false
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const src = raw;
|
|
323
|
+
const simFloor = coerceNumber(src.similarityFloor) ?? 0.82;
|
|
324
|
+
const topicFloor = coerceNumber(src.topicOverlapFloor) ?? 0.4;
|
|
325
|
+
const maxPairs = coerceNumber(src.maxPairsPerRun) ?? 500;
|
|
326
|
+
const cooldown = coerceNumber(src.cooldownDays) ?? 14;
|
|
327
|
+
return {
|
|
328
|
+
enabled: coerceBool(src.enabled) === true,
|
|
329
|
+
similarityFloor: Math.min(1, Math.max(0, simFloor)),
|
|
330
|
+
topicOverlapFloor: Math.min(1, Math.max(0, topicFloor)),
|
|
331
|
+
maxPairsPerRun: Math.max(1, maxPairs),
|
|
332
|
+
cooldownDays: Math.max(0, cooldown),
|
|
333
|
+
autoMergeDuplicates: coerceBool(src.autoMergeDuplicates) === true
|
|
334
|
+
};
|
|
335
|
+
}
|
|
295
336
|
function parseSemanticChunkingConfig(raw) {
|
|
296
337
|
if (!raw || typeof raw !== "object") return {};
|
|
297
338
|
const src = raw;
|
|
@@ -327,7 +368,8 @@ var VALID_MEMORY_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
327
368
|
"commitment",
|
|
328
369
|
"moment",
|
|
329
370
|
"skill",
|
|
330
|
-
"rule"
|
|
371
|
+
"rule",
|
|
372
|
+
"procedure"
|
|
331
373
|
]);
|
|
332
374
|
var DEFAULT_BEHAVIOR_LOOP_PROTECTED_PARAMS = [
|
|
333
375
|
"maxMemoryTokens",
|
|
@@ -494,6 +536,28 @@ function parseConfig(raw) {
|
|
|
494
536
|
) ? rawCodexCompat.compactionFlushMode : "auto",
|
|
495
537
|
fingerprintDedup: rawCodexCompat.fingerprintDedup !== false
|
|
496
538
|
};
|
|
539
|
+
const rawProcedural = cfg.procedural && typeof cfg.procedural === "object" && !Array.isArray(cfg.procedural) ? cfg.procedural : {};
|
|
540
|
+
const proceduralMinCoerced = coerceNumber(rawProcedural.minOccurrences);
|
|
541
|
+
const proceduralMinRaw = proceduralMinCoerced !== void 0 ? Math.floor(proceduralMinCoerced) : 3;
|
|
542
|
+
const successFloorRaw = coerceNumber(rawProcedural.successFloor);
|
|
543
|
+
const successFloor = successFloorRaw !== void 0 && successFloorRaw >= 0 && successFloorRaw <= 1 ? successFloorRaw : 0.7;
|
|
544
|
+
const autoPromoteOccRaw = coerceNumber(rawProcedural.autoPromoteOccurrences);
|
|
545
|
+
const autoPromoteOccurrences = autoPromoteOccRaw !== void 0 && Number.isFinite(autoPromoteOccRaw) ? autoPromoteOccRaw <= 0 ? 0 : Math.min(1e4, Math.max(1, Math.floor(autoPromoteOccRaw))) : 8;
|
|
546
|
+
const lookbackCoerced = coerceNumber(rawProcedural.lookbackDays);
|
|
547
|
+
const lookbackDays = lookbackCoerced !== void 0 && Number.isFinite(lookbackCoerced) ? Math.min(3650, Math.max(1, Math.floor(lookbackCoerced))) : 30;
|
|
548
|
+
const recallMaxCoerced = coerceNumber(rawProcedural.recallMaxProcedures);
|
|
549
|
+
const recallMaxProcedures = recallMaxCoerced !== void 0 && Number.isFinite(recallMaxCoerced) ? Math.min(10, Math.max(1, Math.floor(recallMaxCoerced))) : 3;
|
|
550
|
+
const procedural = {
|
|
551
|
+
enabled: coerceBool(rawProcedural.enabled) === true,
|
|
552
|
+
/** `0` skips all mining (`minOccurrences_zero`); otherwise clusters need at least this many members. */
|
|
553
|
+
minOccurrences: Math.min(1e3, Math.max(0, proceduralMinRaw)),
|
|
554
|
+
successFloor,
|
|
555
|
+
autoPromoteOccurrences,
|
|
556
|
+
autoPromoteEnabled: coerceBool(rawProcedural.autoPromoteEnabled) === true,
|
|
557
|
+
lookbackDays,
|
|
558
|
+
proceduralMiningCronAutoRegister: coerceBool(rawProcedural.proceduralMiningCronAutoRegister) === true,
|
|
559
|
+
recallMaxProcedures
|
|
560
|
+
};
|
|
497
561
|
const memoryDir = typeof cfg.memoryDir === "string" && cfg.memoryDir.length > 0 ? cfg.memoryDir : DEFAULT_MEMORY_DIR;
|
|
498
562
|
const rawIdentityInjectionMode = cfg.identityInjectionMode;
|
|
499
563
|
const identityInjectionMode = rawIdentityInjectionMode && VALID_IDENTITY_INJECTION_MODES.includes(rawIdentityInjectionMode) ? rawIdentityInjectionMode : "recovery_only";
|
|
@@ -676,11 +740,36 @@ function parseConfig(raw) {
|
|
|
676
740
|
contradictionSimilarityThreshold: typeof cfg.contradictionSimilarityThreshold === "number" ? cfg.contradictionSimilarityThreshold : 0.7,
|
|
677
741
|
contradictionMinConfidence: typeof cfg.contradictionMinConfidence === "number" ? cfg.contradictionMinConfidence : 0.9,
|
|
678
742
|
contradictionAutoResolve: cfg.contradictionAutoResolve !== false,
|
|
743
|
+
// Contradiction Scan cron (issue #520)
|
|
744
|
+
contradictionScan: parseContradictionScanConfig(cfg.contradictionScan),
|
|
679
745
|
// Temporal Supersession (issue #375)
|
|
680
746
|
temporalSupersessionEnabled: cfg.temporalSupersessionEnabled !== false,
|
|
681
747
|
// On by default
|
|
682
748
|
temporalSupersessionIncludeInRecall: cfg.temporalSupersessionIncludeInRecall === true,
|
|
683
749
|
// Off by default
|
|
750
|
+
// Direct-answer retrieval tier (issue #518). Default on — the
|
|
751
|
+
// tier runs in observation mode: it annotates
|
|
752
|
+
// LastRecallSnapshot.tierExplain but never short-circuits the
|
|
753
|
+
// QMD path. Operators can opt out with
|
|
754
|
+
// recallDirectAnswerEnabled=false.
|
|
755
|
+
recallDirectAnswerEnabled: coerceBool(cfg.recallDirectAnswerEnabled) ?? true,
|
|
756
|
+
recallDirectAnswerTokenOverlapFloor: (() => {
|
|
757
|
+
const n = coerceNumber(cfg.recallDirectAnswerTokenOverlapFloor);
|
|
758
|
+
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.55;
|
|
759
|
+
})(),
|
|
760
|
+
recallDirectAnswerImportanceFloor: (() => {
|
|
761
|
+
const n = coerceNumber(cfg.recallDirectAnswerImportanceFloor);
|
|
762
|
+
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.7;
|
|
763
|
+
})(),
|
|
764
|
+
recallDirectAnswerAmbiguityMargin: (() => {
|
|
765
|
+
const n = coerceNumber(cfg.recallDirectAnswerAmbiguityMargin);
|
|
766
|
+
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.15;
|
|
767
|
+
})(),
|
|
768
|
+
recallDirectAnswerEligibleTaxonomyBuckets: Array.isArray(
|
|
769
|
+
cfg.recallDirectAnswerEligibleTaxonomyBuckets
|
|
770
|
+
) ? cfg.recallDirectAnswerEligibleTaxonomyBuckets.filter(
|
|
771
|
+
(v) => typeof v === "string" && v.length > 0
|
|
772
|
+
) : ["decisions", "principles", "conventions", "runbooks", "entities"],
|
|
684
773
|
// Memory Linking (Phase 3A)
|
|
685
774
|
memoryLinkingEnabled: cfg.memoryLinkingEnabled === true,
|
|
686
775
|
// Off by default initially
|
|
@@ -756,6 +845,7 @@ function parseConfig(raw) {
|
|
|
756
845
|
activeRecallAttachRecallExplain: cfg.activeRecallAttachRecallExplain === true,
|
|
757
846
|
activeRecallAllowChainedActiveMemory: cfg.activeRecallAllowChainedActiveMemory === true,
|
|
758
847
|
dreaming,
|
|
848
|
+
procedural,
|
|
759
849
|
heartbeat,
|
|
760
850
|
slotBehavior,
|
|
761
851
|
codexCompat,
|
|
@@ -825,7 +915,7 @@ function parseConfig(raw) {
|
|
|
825
915
|
semanticConsolidationMinClusterSize: typeof cfg.semanticConsolidationMinClusterSize === "number" ? Math.max(2, Math.floor(cfg.semanticConsolidationMinClusterSize)) : 3,
|
|
826
916
|
semanticConsolidationExcludeCategories: Array.isArray(cfg.semanticConsolidationExcludeCategories) ? cfg.semanticConsolidationExcludeCategories.filter(
|
|
827
917
|
(c) => typeof c === "string" && c.length > 0
|
|
828
|
-
) : ["correction", "commitment"],
|
|
918
|
+
) : ["correction", "commitment", "procedure"],
|
|
829
919
|
semanticConsolidationIntervalHours: typeof cfg.semanticConsolidationIntervalHours === "number" ? Math.max(1, Math.floor(cfg.semanticConsolidationIntervalHours)) : 168,
|
|
830
920
|
semanticConsolidationMaxPerRun: typeof cfg.semanticConsolidationMaxPerRun === "number" ? Math.max(0, Math.floor(cfg.semanticConsolidationMaxPerRun)) : 100,
|
|
831
921
|
creationMemoryEnabled: cfg.creationMemoryEnabled === true,
|
|
@@ -924,6 +1014,22 @@ function parseConfig(raw) {
|
|
|
924
1014
|
localLlmFastModel: typeof cfg.localLlmFastModel === "string" && cfg.localLlmFastModel.length > 0 ? cfg.localLlmFastModel : "",
|
|
925
1015
|
localLlmFastUrl: typeof cfg.localLlmFastUrl === "string" && cfg.localLlmFastUrl.length > 0 ? cfg.localLlmFastUrl : typeof cfg.localLlmUrl === "string" && cfg.localLlmUrl.length > 0 ? cfg.localLlmUrl : "http://localhost:1234/v1",
|
|
926
1016
|
localLlmFastTimeoutMs: typeof cfg.localLlmFastTimeoutMs === "number" ? cfg.localLlmFastTimeoutMs : 15e3,
|
|
1017
|
+
// Thinking-mode suppression on the main local LLM (issue #548).
|
|
1018
|
+
// Default true — extraction / consolidation produce structured
|
|
1019
|
+
// JSON and gain nothing from chain-of-thought; thinking-capable
|
|
1020
|
+
// models burn their token budget on reasoning and blow the
|
|
1021
|
+
// default 60s timeout. Operators who need thinking on the main
|
|
1022
|
+
// client (e.g. for narrative tasks) can set this to false via
|
|
1023
|
+
// config or --config CLI flag. The fast-tier `fastLlm` always
|
|
1024
|
+
// disables thinking and is unaffected by this flag.
|
|
1025
|
+
//
|
|
1026
|
+
// Injection is backend-gated inside LocalLlmClient: the
|
|
1027
|
+
// `chat_template_kwargs` field is only sent when the detected
|
|
1028
|
+
// backend is in `THINKING_COMPATIBLE_BACKENDS` (LM Studio, vLLM).
|
|
1029
|
+
// Strict OpenAI-compatible backends reject unknown request
|
|
1030
|
+
// fields with 400, so the client fails open on unknown backends
|
|
1031
|
+
// rather than tripping the 400 cooldown (Codex P1 on PR #550).
|
|
1032
|
+
localLlmDisableThinking: coerceBool(cfg.localLlmDisableThinking) ?? true,
|
|
927
1033
|
// Gateway config (passed from index.ts for fallback AI)
|
|
928
1034
|
gatewayConfig: cfg.gatewayConfig,
|
|
929
1035
|
// Gateway model source (v9.2) — route LLM calls through gateway agent model chain
|
|
@@ -1075,7 +1181,7 @@ function parseConfig(raw) {
|
|
|
1075
1181
|
factArchivalAgeDays: typeof cfg.factArchivalAgeDays === "number" ? cfg.factArchivalAgeDays : 90,
|
|
1076
1182
|
factArchivalMaxImportance: typeof cfg.factArchivalMaxImportance === "number" ? cfg.factArchivalMaxImportance : 0.3,
|
|
1077
1183
|
factArchivalMaxAccessCount: typeof cfg.factArchivalMaxAccessCount === "number" ? cfg.factArchivalMaxAccessCount : 2,
|
|
1078
|
-
factArchivalProtectedCategories: Array.isArray(cfg.factArchivalProtectedCategories) ? cfg.factArchivalProtectedCategories.filter((c) => typeof c === "string") : ["commitment", "preference", "decision", "principle"],
|
|
1184
|
+
factArchivalProtectedCategories: Array.isArray(cfg.factArchivalProtectedCategories) ? cfg.factArchivalProtectedCategories.filter((c) => typeof c === "string") : ["commitment", "preference", "decision", "principle", "procedure"],
|
|
1079
1185
|
// v8.3 lifecycle policy engine (default off)
|
|
1080
1186
|
lifecyclePolicyEnabled: cfg.lifecyclePolicyEnabled === true,
|
|
1081
1187
|
lifecycleFilterStaleEnabled: cfg.lifecycleFilterStaleEnabled === true,
|
|
@@ -1084,7 +1190,7 @@ function parseConfig(raw) {
|
|
|
1084
1190
|
lifecycleArchiveDecayThreshold: typeof cfg.lifecycleArchiveDecayThreshold === "number" ? Math.min(1, Math.max(0, cfg.lifecycleArchiveDecayThreshold)) : 0.85,
|
|
1085
1191
|
lifecycleProtectedCategories: Array.isArray(cfg.lifecycleProtectedCategories) ? cfg.lifecycleProtectedCategories.filter(
|
|
1086
1192
|
(c) => typeof c === "string" && VALID_MEMORY_CATEGORIES.has(c)
|
|
1087
|
-
) : ["decision", "principle", "commitment", "preference"],
|
|
1193
|
+
) : ["decision", "principle", "commitment", "preference", "procedure"],
|
|
1088
1194
|
lifecycleMetricsEnabled: typeof cfg.lifecycleMetricsEnabled === "boolean" ? cfg.lifecycleMetricsEnabled : cfg.lifecyclePolicyEnabled === true,
|
|
1089
1195
|
// v8.3 proactive + policy learning (default off)
|
|
1090
1196
|
proactiveExtractionEnabled: cfg.proactiveExtractionEnabled === true,
|
|
@@ -1356,6 +1462,11 @@ function buildDefaultRecallPipeline(cfg) {
|
|
|
1356
1462
|
maxEntities: typeof cfg.knowledgeIndexMaxEntities === "number" ? Math.max(0, Math.floor(cfg.knowledgeIndexMaxEntities)) : 40
|
|
1357
1463
|
},
|
|
1358
1464
|
{ id: "verbatim-artifacts", enabled: cfg.verbatimArtifactsEnabled === true },
|
|
1465
|
+
{
|
|
1466
|
+
id: "procedure-recall",
|
|
1467
|
+
enabled: typeof cfg.procedural === "object" && cfg.procedural !== null && !Array.isArray(cfg.procedural) && cfg.procedural.enabled === true,
|
|
1468
|
+
maxChars: 2400
|
|
1469
|
+
},
|
|
1359
1470
|
{ id: "memory-boxes", enabled: cfg.memoryBoxesEnabled === true },
|
|
1360
1471
|
{ id: "temporal-memory-tree", enabled: cfg.temporalMemoryTreeEnabled === true },
|
|
1361
1472
|
{ id: "lcm-compressed-history", enabled: cfg.lcmEnabled === true },
|
|
@@ -1481,7 +1592,7 @@ function detectSdkCapabilities(api) {
|
|
|
1481
1592
|
// ../remnic-core/src/orchestrator.ts
|
|
1482
1593
|
import path43 from "path";
|
|
1483
1594
|
import os3 from "os";
|
|
1484
|
-
import { createHash as createHash9 } from "crypto";
|
|
1595
|
+
import { createHash as createHash9, randomBytes } from "crypto";
|
|
1485
1596
|
import { existsSync as existsSync8 } from "fs";
|
|
1486
1597
|
import {
|
|
1487
1598
|
mkdir as mkdir30,
|
|
@@ -2142,6 +2253,15 @@ var SmartBuffer = class {
|
|
|
2142
2253
|
this.state = this.normalizeState(await this.storage.loadBuffer());
|
|
2143
2254
|
this.loaded = true;
|
|
2144
2255
|
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Reset the buffer to an empty, usable state.
|
|
2258
|
+
* Called when the persisted buffer file is corrupt and load() fails,
|
|
2259
|
+
* so the buffer can still accept new turns for the rest of the session.
|
|
2260
|
+
*/
|
|
2261
|
+
resetToEmpty() {
|
|
2262
|
+
this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };
|
|
2263
|
+
this.loaded = true;
|
|
2264
|
+
}
|
|
2145
2265
|
async save() {
|
|
2146
2266
|
await this.storage.saveBuffer(this.state);
|
|
2147
2267
|
}
|
|
@@ -2782,6 +2902,10 @@ function trimTrailingSlashes(s) {
|
|
|
2782
2902
|
while (end > 0 && s[end - 1] === "/") end--;
|
|
2783
2903
|
return s.substring(0, end);
|
|
2784
2904
|
}
|
|
2905
|
+
var THINKING_COMPATIBLE_BACKENDS = /* @__PURE__ */ new Set([
|
|
2906
|
+
"lmstudio",
|
|
2907
|
+
"vllm"
|
|
2908
|
+
]);
|
|
2785
2909
|
var LOCAL_SERVERS = [
|
|
2786
2910
|
{
|
|
2787
2911
|
type: "ollama",
|
|
@@ -2840,9 +2964,16 @@ var LocalLlmClient = class _LocalLlmClient {
|
|
|
2840
2964
|
this.modelRegistry = modelRegistry;
|
|
2841
2965
|
}
|
|
2842
2966
|
/**
|
|
2843
|
-
*
|
|
2844
|
-
*
|
|
2845
|
-
*
|
|
2967
|
+
* Request thinking/reasoning suppression on the next chat completion.
|
|
2968
|
+
*
|
|
2969
|
+
* When `true`, the client will inject
|
|
2970
|
+
* `chat_template_kwargs: { enable_thinking: false }` into the request
|
|
2971
|
+
* body — **but only when the detected backend is known to support it**
|
|
2972
|
+
* (LM Studio, vLLM; see `THINKING_COMPATIBLE_BACKENDS`). Strict
|
|
2973
|
+
* OpenAI-compat backends reject unknown fields with 400; on those the
|
|
2974
|
+
* client fails open (thinking runs normally). This is the safe
|
|
2975
|
+
* default for Remnic extraction / consolidation: measurable latency
|
|
2976
|
+
* win on thinking-capable backends, zero risk on others. Issue #548.
|
|
2846
2977
|
*/
|
|
2847
2978
|
set disableThinking(value) {
|
|
2848
2979
|
this._disableThinking = value;
|
|
@@ -3340,7 +3471,7 @@ var LocalLlmClient = class _LocalLlmClient {
|
|
|
3340
3471
|
if (options.responseFormat?.type === "json_schema") {
|
|
3341
3472
|
requestBody.response_format = options.responseFormat;
|
|
3342
3473
|
}
|
|
3343
|
-
if (this._disableThinking) {
|
|
3474
|
+
if (this._disableThinking && this.detectedType !== null && THINKING_COMPATIBLE_BACKENDS.has(this.detectedType)) {
|
|
3344
3475
|
requestBody.chat_template_kwargs = { enable_thinking: false };
|
|
3345
3476
|
}
|
|
3346
3477
|
const baseUrl = trimTrailingSlashes(
|
|
@@ -3710,14 +3841,40 @@ function parseMemoryActionEligibilityContext(value) {
|
|
|
3710
3841
|
source: "unknown"
|
|
3711
3842
|
};
|
|
3712
3843
|
}
|
|
3844
|
+
var ProcedureStepExtractSchema = external_exports.object({
|
|
3845
|
+
order: external_exports.number(),
|
|
3846
|
+
intent: external_exports.string(),
|
|
3847
|
+
toolCall: external_exports.object({
|
|
3848
|
+
kind: external_exports.string(),
|
|
3849
|
+
signature: external_exports.string()
|
|
3850
|
+
}).optional().nullable(),
|
|
3851
|
+
expectedOutcome: external_exports.string().optional().nullable(),
|
|
3852
|
+
optional: external_exports.boolean().optional().nullable()
|
|
3853
|
+
});
|
|
3713
3854
|
var ExtractedFactSchema = external_exports.object({
|
|
3714
|
-
category: external_exports.enum([
|
|
3855
|
+
category: external_exports.enum([
|
|
3856
|
+
"fact",
|
|
3857
|
+
"preference",
|
|
3858
|
+
"correction",
|
|
3859
|
+
"entity",
|
|
3860
|
+
"decision",
|
|
3861
|
+
"relationship",
|
|
3862
|
+
"principle",
|
|
3863
|
+
"commitment",
|
|
3864
|
+
"moment",
|
|
3865
|
+
"skill",
|
|
3866
|
+
"rule",
|
|
3867
|
+
"procedure"
|
|
3868
|
+
]),
|
|
3715
3869
|
content: external_exports.string().describe("The memory content \u2014 a clear, standalone statement"),
|
|
3716
3870
|
confidence: external_exports.number().min(0).max(1).describe("How confident are you this is correct (0-1)"),
|
|
3717
3871
|
tags: external_exports.array(external_exports.string()).describe("Relevant tags for categorization"),
|
|
3718
3872
|
entityRef: external_exports.string().optional().nullable().describe("If about an entity, its normalized name (e.g. person-jane-doe)"),
|
|
3719
3873
|
promptedByQuestion: external_exports.string().optional().nullable().describe("Optional proactive follow-up question that surfaced this fact."),
|
|
3720
|
-
structuredAttributes: external_exports.record(external_exports.string(), external_exports.string()).optional().nullable().describe('Structured key-value attributes when the fact contains measurable or categorical data (e.g., {"price": "29.99", "color": "blue", "date": "2024-03-15"}).')
|
|
3874
|
+
structuredAttributes: external_exports.record(external_exports.string(), external_exports.string()).optional().nullable().describe('Structured key-value attributes when the fact contains measurable or categorical data (e.g., {"price": "29.99", "color": "blue", "date": "2024-03-15"}).'),
|
|
3875
|
+
procedureSteps: external_exports.array(ProcedureStepExtractSchema).optional().nullable().describe(
|
|
3876
|
+
'For category "procedure" only: ordered steps (intent per step). At least two steps; include explicit trigger phrasing in content (e.g. "When you deploy\u2026").'
|
|
3877
|
+
)
|
|
3721
3878
|
});
|
|
3722
3879
|
var EntityMentionSchema = external_exports.object({
|
|
3723
3880
|
name: external_exports.string().describe("Normalized entity name (e.g. jane-doe, acme-corp, my-project)"),
|
|
@@ -4465,6 +4622,67 @@ function parallelEfficiency(group) {
|
|
|
4465
4622
|
return Math.round(idealMs / group.wallMs * 100);
|
|
4466
4623
|
}
|
|
4467
4624
|
|
|
4625
|
+
// ../remnic-core/src/procedural/procedure-types.ts
|
|
4626
|
+
function normalizeProcedureSteps(raw) {
|
|
4627
|
+
if (!Array.isArray(raw)) return [];
|
|
4628
|
+
const out = [];
|
|
4629
|
+
for (let i = 0; i < raw.length; i++) {
|
|
4630
|
+
const s = raw[i];
|
|
4631
|
+
if (!s || typeof s !== "object") continue;
|
|
4632
|
+
const o = s;
|
|
4633
|
+
const intent = typeof o.intent === "string" ? o.intent.trim() : "";
|
|
4634
|
+
if (!intent) continue;
|
|
4635
|
+
const orderRaw = o.order;
|
|
4636
|
+
const order = typeof orderRaw === "number" && Number.isFinite(orderRaw) ? Math.max(1, Math.floor(orderRaw)) : i + 1;
|
|
4637
|
+
let toolCall;
|
|
4638
|
+
const tc = o.toolCall;
|
|
4639
|
+
if (tc && typeof tc === "object" && !Array.isArray(tc)) {
|
|
4640
|
+
const t = tc;
|
|
4641
|
+
const kind = typeof t.kind === "string" ? t.kind.trim() : "";
|
|
4642
|
+
const signature = typeof t.signature === "string" ? t.signature.trim() : "";
|
|
4643
|
+
if (kind && signature) {
|
|
4644
|
+
toolCall = { kind, signature };
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
const expectedOutcome = typeof o.expectedOutcome === "string" && o.expectedOutcome.trim() ? o.expectedOutcome.trim() : void 0;
|
|
4648
|
+
const optional = o.optional === true ? true : void 0;
|
|
4649
|
+
out.push({ order, intent, toolCall, expectedOutcome, optional });
|
|
4650
|
+
}
|
|
4651
|
+
return out;
|
|
4652
|
+
}
|
|
4653
|
+
function buildProcedurePersistBody(title, procedureSteps) {
|
|
4654
|
+
const head = typeof title === "string" ? title.trim() : "";
|
|
4655
|
+
const steps = normalizeProcedureSteps(procedureSteps);
|
|
4656
|
+
if (steps.length === 0) return head;
|
|
4657
|
+
return `${head}
|
|
4658
|
+
|
|
4659
|
+
${buildProcedureMarkdownBody(steps)}`.trimEnd() + "\n";
|
|
4660
|
+
}
|
|
4661
|
+
function buildProcedureMarkdownBody(steps) {
|
|
4662
|
+
const sorted = [...steps].sort((a, b) => a.order - b.order);
|
|
4663
|
+
const lines = [];
|
|
4664
|
+
for (const step of sorted) {
|
|
4665
|
+
const n = Number.isFinite(step.order) ? Math.max(1, Math.floor(step.order)) : 1;
|
|
4666
|
+
lines.push(`## Step ${n}`);
|
|
4667
|
+
lines.push("");
|
|
4668
|
+
lines.push(step.intent.trim());
|
|
4669
|
+
if (step.toolCall?.kind && step.toolCall.signature) {
|
|
4670
|
+
lines.push("");
|
|
4671
|
+
lines.push(`- Tool: \`${step.toolCall.kind}\` \u2014 ${step.toolCall.signature}`);
|
|
4672
|
+
}
|
|
4673
|
+
if (step.expectedOutcome?.trim()) {
|
|
4674
|
+
lines.push("");
|
|
4675
|
+
lines.push(`- Expected: ${step.expectedOutcome.trim()}`);
|
|
4676
|
+
}
|
|
4677
|
+
if (step.optional === true) {
|
|
4678
|
+
lines.push("");
|
|
4679
|
+
lines.push("- Optional: true");
|
|
4680
|
+
}
|
|
4681
|
+
lines.push("");
|
|
4682
|
+
}
|
|
4683
|
+
return lines.join("\n").trimEnd() + "\n";
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4468
4686
|
// ../remnic-core/src/extraction.ts
|
|
4469
4687
|
var PROACTIVE_MIN_CONFIDENCE = 0.8;
|
|
4470
4688
|
function normalizeQuestion(question) {
|
|
@@ -4555,8 +4773,9 @@ var ExtractionEngine = class {
|
|
|
4555
4773
|
return shouldAssumeOpenAiChatCompletions(this.config.openaiBaseUrl);
|
|
4556
4774
|
}
|
|
4557
4775
|
sanitizeExtractionResult(result, messageTimestamp) {
|
|
4776
|
+
const proceduralOn = this.config.procedural?.enabled === true;
|
|
4558
4777
|
const ts = messageTimestamp ?? /* @__PURE__ */ new Date();
|
|
4559
|
-
const facts = result.facts.map((fact) => {
|
|
4778
|
+
const facts = result.facts.filter((fact) => proceduralOn || fact.category !== "procedure").map((fact) => {
|
|
4560
4779
|
const sanitized = sanitizeMemoryContent(fact.content);
|
|
4561
4780
|
if (!sanitized.clean) {
|
|
4562
4781
|
log.warn(`extraction fact sanitized; violations=${sanitized.violations.join(", ")}`);
|
|
@@ -4583,7 +4802,8 @@ var ExtractionEngine = class {
|
|
|
4583
4802
|
promptedByQuestion: typeof f?.promptedByQuestion === "string" ? f.promptedByQuestion : void 0,
|
|
4584
4803
|
structuredAttributes: f?.structuredAttributes && typeof f.structuredAttributes === "object" && !Array.isArray(f.structuredAttributes) ? Object.fromEntries(
|
|
4585
4804
|
Object.entries(f.structuredAttributes).filter(([k, v]) => typeof k === "string" && typeof v === "string")
|
|
4586
|
-
) : void 0
|
|
4805
|
+
) : void 0,
|
|
4806
|
+
procedureSteps: Array.isArray(f?.procedureSteps) ? normalizeProcedureSteps(f.procedureSteps) : void 0
|
|
4587
4807
|
})).filter((f) => f.content.length > 0) : [];
|
|
4588
4808
|
const questions = Array.isArray(parsed?.questions) ? parsed.questions.map((q) => {
|
|
4589
4809
|
if (typeof q === "string") return { question: q, context: "", priority: 0.5 };
|
|
@@ -5244,6 +5464,8 @@ Memory categories \u2014 use the MOST SPECIFIC category that fits:
|
|
|
5244
5464
|
- commitment: Promises, obligations, deadlines
|
|
5245
5465
|
- moment: Emotionally significant events
|
|
5246
5466
|
- skill: Demonstrated capabilities
|
|
5467
|
+
- rule: Explicit operational rules or constraints
|
|
5468
|
+
- procedure: Repeatable workflows \u2014 use when the user describes a multi-step play (\u22652 ordered steps). Put the human-readable trigger/context in "content" (e.g. "When you deploy\u2026") and list steps in "procedureSteps" as [{"order":1,"intent":"\u2026"}, \u2026] mirroring the gateway extraction schema.
|
|
5247
5469
|
|
|
5248
5470
|
IMPORTANT: Do NOT label everything as "fact". Use "decision" for architectural choices, "commitment" for deadlines/promises, "principle" for reusable rules, "correction" for when the user rejects a suggestion, etc.
|
|
5249
5471
|
|
|
@@ -5305,7 +5527,7 @@ Also generate:
|
|
|
5305
5527
|
|
|
5306
5528
|
Output JSON:
|
|
5307
5529
|
{
|
|
5308
|
-
"facts": [{"category": "decision", "content": "Chose PostgreSQL over MongoDB for the user service", "importance": 8, "confidence": 0.9, "structuredAttributes": {"chosen": "PostgreSQL", "rejected": "MongoDB"}}, {"category": "commitment", "content": "Must ship v2.0 API by end of March", "importance": 10, "confidence": 1.0, "structuredAttributes": {"deadline": "end of March", "deliverable": "v2.0 API"}}, {"category": "fact", "content": "The store backend uses Redis for session caching", "importance": 6, "confidence": 0.95, "entityRef": "project-acme-store"}, {"category": "principle", "content": "Always run migrations in a transaction to avoid partial schema updates", "importance": 8, "confidence": 0.9}],
|
|
5530
|
+
"facts": [{"category": "decision", "content": "Chose PostgreSQL over MongoDB for the user service", "importance": 8, "confidence": 0.9, "structuredAttributes": {"chosen": "PostgreSQL", "rejected": "MongoDB"}}, {"category": "procedure", "content": "When you cut a hotfix release, follow the checklist", "importance": 8, "confidence": 0.9, "procedureSteps": [{"order": 1, "intent": "Branch from main and cherry-pick the fix"}, {"order": 2, "intent": "Run CI and tag the release"}]}, {"category": "commitment", "content": "Must ship v2.0 API by end of March", "importance": 10, "confidence": 1.0, "structuredAttributes": {"deadline": "end of March", "deliverable": "v2.0 API"}}, {"category": "fact", "content": "The store backend uses Redis for session caching", "importance": 6, "confidence": 0.95, "entityRef": "project-acme-store"}, {"category": "principle", "content": "Always run migrations in a transaction to avoid partial schema updates", "importance": 8, "confidence": 0.9}],
|
|
5309
5531
|
"entities": [{"name": "person-jane-doe", "type": "person", "facts": ["Works at Acme Corp", "Prefers Python over JavaScript"], "structuredSections": [{"key": "beliefs", "title": "Beliefs", "facts": ["Python is a better fit than JavaScript for backend work."]}]}, {"name": "project-acme-store", "type": "project", "facts": ["Built with Next.js", "Deployed on Vercel"]}],
|
|
5310
5532
|
"profileUpdates": ["User prefers dark mode in all editors"],
|
|
5311
5533
|
"questions": [{"question": "Which cloud provider hosts the staging environment?", "context": "Came up during deployment discussion", "priority": 0.5}],
|
|
@@ -5426,7 +5648,9 @@ Respond with valid JSON matching this schema:
|
|
|
5426
5648
|
"principle",
|
|
5427
5649
|
"commitment",
|
|
5428
5650
|
"moment",
|
|
5429
|
-
"skill"
|
|
5651
|
+
"skill",
|
|
5652
|
+
"rule",
|
|
5653
|
+
"procedure"
|
|
5430
5654
|
]);
|
|
5431
5655
|
const allowedEntityTypes = /* @__PURE__ */ new Set([
|
|
5432
5656
|
"person",
|
|
@@ -5483,6 +5707,7 @@ Memory categories:
|
|
|
5483
5707
|
- moment: Emotionally significant events or milestones (e.g., "first successful deployment of engram")
|
|
5484
5708
|
- skill: Capabilities the user or agent has demonstrated (e.g., "user is proficient with Kubernetes")${this.config.causalRuleExtractionEnabled ? `
|
|
5485
5709
|
- rule: Causal rules discovered through experience (format: "IF <condition> THEN <action/outcome>", e.g., "IF Shopify API returns 401 THEN the admin token is missing read_products scope")` : ""}
|
|
5710
|
+
- procedure: A reusable workflow the user wants remembered the same way across sessions. Set category to "procedure". Use "content" for a short title that includes explicit trigger phrasing (e.g. "When you deploy to production\u2026", "Whenever you ship a release\u2026"). Add "procedureSteps": an array of at least two objects {"order": number, "intent": "concrete step description"} in execution order. Optional per-step "toolCall": {"kind": "\u2026", "signature": "\u2026"}, "expectedOutcome", "optional": true.
|
|
5486
5711
|
|
|
5487
5712
|
Rules:
|
|
5488
5713
|
- Only extract genuinely NEW information worth remembering across sessions
|
|
@@ -6692,6 +6917,8 @@ var CATEGORY_BOOSTS = {
|
|
|
6692
6917
|
// Durable rules/values
|
|
6693
6918
|
rule: 0.11,
|
|
6694
6919
|
// Causal IF→THEN rules
|
|
6920
|
+
procedure: 0.1,
|
|
6921
|
+
// Repeatable workflows (issue #519)
|
|
6695
6922
|
preference: 0.1,
|
|
6696
6923
|
// User preferences matter
|
|
6697
6924
|
commitment: 0.1,
|
|
@@ -6997,6 +7224,18 @@ function enforceMaxCacheSize(cache) {
|
|
|
6997
7224
|
}
|
|
6998
7225
|
}
|
|
6999
7226
|
var AUTO_APPROVE_CATEGORIES = /* @__PURE__ */ new Set(["correction", "principle"]);
|
|
7227
|
+
var PROCEDURE_TRIGGER_RE = /(when you|whenever|before you|before running|always\s|first\b.*\bthen|to deploy|to ship|run these steps|follow these steps|how (i|we)\s|recipe for|workflow|each time you)/i;
|
|
7228
|
+
function validateProcedureExtraction(input) {
|
|
7229
|
+
const steps = normalizeProcedureSteps(input.procedureSteps);
|
|
7230
|
+
if (steps.length < 2) {
|
|
7231
|
+
return { durable: false, reason: "Procedure requires at least two steps with intents" };
|
|
7232
|
+
}
|
|
7233
|
+
const combined = [input.content, ...steps.map((s) => s.intent)].join(" ").toLowerCase();
|
|
7234
|
+
if (!PROCEDURE_TRIGGER_RE.test(combined)) {
|
|
7235
|
+
return { durable: false, reason: "Procedure missing explicit trigger phrasing" };
|
|
7236
|
+
}
|
|
7237
|
+
return { durable: true, reason: "Procedure structure validated" };
|
|
7238
|
+
}
|
|
7000
7239
|
async function judgeFactDurability(candidates, config, localLlm, fallbackLlm, cache) {
|
|
7001
7240
|
const startMs = Date.now();
|
|
7002
7241
|
const verdicts = /* @__PURE__ */ new Map();
|
|
@@ -7177,6 +7416,156 @@ function createVerdictCache() {
|
|
|
7177
7416
|
return /* @__PURE__ */ new Map();
|
|
7178
7417
|
}
|
|
7179
7418
|
|
|
7419
|
+
// ../remnic-core/src/intent.ts
|
|
7420
|
+
var GOAL_PATTERNS = [
|
|
7421
|
+
{ re: /\b(debug(?:s|ged|ging)?|fix(?:es|ed|ing)?|error(?:s)?|incident(?:s)?|outage(?:s)?|failure(?:s)?)\b/i, goal: "stabilize" },
|
|
7422
|
+
{ re: /\b(deploy(?:s|ed|ing)?|release(?:s|d|ing)?|ship(?:s|ped|ping)?|publish(?:es|ed|ing)?)\b/i, goal: "release" },
|
|
7423
|
+
{ re: /\b(plan(?:s|ned|ning)?|roadmap(?:s)?|strateg(?:y|ies)|design(?:s|ed|ing)?)\b/i, goal: "plan" },
|
|
7424
|
+
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|security|hardening)\b/i, goal: "review" },
|
|
7425
|
+
{ re: /\b(sales|deal|customer|client|prospect)\b/i, goal: "close_deal" }
|
|
7426
|
+
];
|
|
7427
|
+
var ACTION_PATTERNS = [
|
|
7428
|
+
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|inspect(?:s|ed|ing)?|check(?:s|ed|ing)?)\b/i, action: "review" },
|
|
7429
|
+
{ re: /\b(plan(?:s|ned|ning)?|design(?:s|ed|ing)?|brainstorm(?:s|ed|ing)?|spec(?:s)?)\b/i, action: "plan" },
|
|
7430
|
+
{ re: /\b(implement(?:s|ed|ing)?|build(?:s|ing)?|built|code(?:s|d|ing)?|patch(?:es|ed|ing)?|fix(?:es|ed|ing)?)\b/i, action: "execute" },
|
|
7431
|
+
{ re: /\b(summariz(?:e|es|ed|ing)|recap(?:s|ped|ping)?|what happened|timeline)\b/i, action: "summarize" },
|
|
7432
|
+
{ re: /\b(decid(?:e|es|ed|ing)|decision(?:s)?|cho(?:ose|oses|osing)|chose|chosen)\b/i, action: "decide" }
|
|
7433
|
+
];
|
|
7434
|
+
var ENTITY_PATTERNS = [
|
|
7435
|
+
{ re: /\b(pr|pull request|branch|repo|github|ci|workflow)\b/i, entityType: "repo" },
|
|
7436
|
+
{ re: /\b(discord|slack|channel|gateway|agent)\b/i, entityType: "ops" },
|
|
7437
|
+
{ re: /\b(customer|client|deal|lead|account)\b/i, entityType: "client" },
|
|
7438
|
+
{ re: /\b(model|llm|qmd|embedding|retrieval|memory)\b/i, entityType: "ai" },
|
|
7439
|
+
{ re: /\b(doc|readme|docs|changelog)\b/i, entityType: "docs" }
|
|
7440
|
+
];
|
|
7441
|
+
var TASK_INITIATION_RE = /\b(ship(?:ping|ped)?|deploy(?:ing|ed)?|release|publish|open(?:ing)?\s+(?:a\s+)?(?:pr|pull\s+request)|merge(?:ing)?\s+(?:the\s+)?(?:pr|pull\s+request)|run\s+(?:the\s+)?tests?|start(?:ing)?\s+(?:work|on|the)|kick\s+off|implement(?:ing|ed)?|let's\s+(?:ship|deploy|release|publish|open|run|merge|implement|fix|patch|build|start|do|get|put|wire|hook|land|roll)\b|going\s+to\s+(?:ship|deploy|release|open|run|merge)|need\s+to\s+(?:ship|deploy|run|open|merge|test)|fix(?:ing|ed)?\s+(?:(?:the|a)\s+)?(?:\w+\s+){0,4}(?:bug|build)\b|patch(?:ing|ed)?|build(?:ing)?\s+(?:and\s+)?(?:ship|deploy))\b/i;
|
|
7442
|
+
function normalizeTextInput(input) {
|
|
7443
|
+
return typeof input === "string" ? input : "";
|
|
7444
|
+
}
|
|
7445
|
+
function inferIntentFromText(text) {
|
|
7446
|
+
const safeText = normalizeTextInput(text);
|
|
7447
|
+
const goal = GOAL_PATTERNS.find((p) => p.re.test(safeText))?.goal ?? "unknown";
|
|
7448
|
+
const actionType = ACTION_PATTERNS.find((p) => p.re.test(safeText))?.action ?? "unknown";
|
|
7449
|
+
const entityTypes = Array.from(
|
|
7450
|
+
new Set(ENTITY_PATTERNS.filter((p) => p.re.test(safeText)).map((p) => p.entityType))
|
|
7451
|
+
);
|
|
7452
|
+
const taskInitiation = TASK_INITIATION_RE.test(safeText);
|
|
7453
|
+
return {
|
|
7454
|
+
goal,
|
|
7455
|
+
actionType,
|
|
7456
|
+
entityTypes,
|
|
7457
|
+
taskInitiation
|
|
7458
|
+
};
|
|
7459
|
+
}
|
|
7460
|
+
function isTaskInitiationIntent(intent) {
|
|
7461
|
+
return intent.taskInitiation === true;
|
|
7462
|
+
}
|
|
7463
|
+
function intentCompatibilityScore(queryIntent, memoryIntent) {
|
|
7464
|
+
const queryHasSignal = queryIntent.goal !== "unknown" || queryIntent.actionType !== "unknown" || queryIntent.entityTypes.length > 0;
|
|
7465
|
+
const memoryHasSignal = memoryIntent.goal !== "unknown" || memoryIntent.actionType !== "unknown" || memoryIntent.entityTypes.length > 0;
|
|
7466
|
+
if (!queryHasSignal || !memoryHasSignal) return 0;
|
|
7467
|
+
let score = 0;
|
|
7468
|
+
if (queryIntent.goal !== "unknown" && memoryIntent.goal !== "unknown" && queryIntent.goal === memoryIntent.goal) {
|
|
7469
|
+
score += 0.5;
|
|
7470
|
+
}
|
|
7471
|
+
if (queryIntent.actionType !== "unknown" && memoryIntent.actionType !== "unknown" && queryIntent.actionType === memoryIntent.actionType) {
|
|
7472
|
+
score += 0.3;
|
|
7473
|
+
}
|
|
7474
|
+
const overlap = queryIntent.entityTypes.filter((et) => memoryIntent.entityTypes.includes(et)).length;
|
|
7475
|
+
if (overlap > 0) {
|
|
7476
|
+
const denom = Math.max(queryIntent.entityTypes.length, memoryIntent.entityTypes.length, 1);
|
|
7477
|
+
score += 0.2 * (overlap / denom);
|
|
7478
|
+
}
|
|
7479
|
+
return Math.max(0, Math.min(1, score));
|
|
7480
|
+
}
|
|
7481
|
+
function planRecallMode(prompt) {
|
|
7482
|
+
const p = normalizeTextInput(prompt).trim();
|
|
7483
|
+
let ackCandidate = p;
|
|
7484
|
+
while (ackCandidate.length > 0) {
|
|
7485
|
+
const ch = ackCandidate.charCodeAt(ackCandidate.length - 1);
|
|
7486
|
+
const isDigit = ch >= 48 && ch <= 57;
|
|
7487
|
+
const isUpper = ch >= 65 && ch <= 90;
|
|
7488
|
+
const isLower = ch >= 97 && ch <= 122;
|
|
7489
|
+
if (isDigit || isUpper || isLower) break;
|
|
7490
|
+
ackCandidate = ackCandidate.slice(0, -1);
|
|
7491
|
+
}
|
|
7492
|
+
ackCandidate = ackCandidate.trim();
|
|
7493
|
+
if (p.length === 0) return "no_recall";
|
|
7494
|
+
if (/\b(timeline|sequence|history|what happened|chain of events|root cause)\b/i.test(p)) {
|
|
7495
|
+
return "graph_mode";
|
|
7496
|
+
}
|
|
7497
|
+
if (p.length <= 18 && /^(ok|okay|kk|thanks|thx|got it|sounds good|yep|yes|nope|no|done|cool|works)$/i.test(ackCandidate)) {
|
|
7498
|
+
return "no_recall";
|
|
7499
|
+
}
|
|
7500
|
+
if (/\b(previous|earlier|remember|last time|did we|what did we decide|context|summarize|summary|recap|key points|decision)\b/i.test(p) || /\?$/.test(p) || /^(what|why|how|when|where|who|which)\b/i.test(p.toLowerCase())) {
|
|
7501
|
+
return "full";
|
|
7502
|
+
}
|
|
7503
|
+
if (p.length <= 100 && /^(check|reload|restart|run|verify|show|status|sync|update|open|close|set|enable|disable|fix|patch)\b/i.test(p)) {
|
|
7504
|
+
return "minimal";
|
|
7505
|
+
}
|
|
7506
|
+
return "full";
|
|
7507
|
+
}
|
|
7508
|
+
function hasBroadGraphIntent(prompt) {
|
|
7509
|
+
const p = normalizeTextInput(prompt).trim().toLowerCase();
|
|
7510
|
+
if (!p) return false;
|
|
7511
|
+
return /\b(what changed|how did we get here|why did this happen|what led to|cause chain|dependency chain|regression chain|failure chain)\b/i.test(
|
|
7512
|
+
p
|
|
7513
|
+
);
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7516
|
+
// ../remnic-core/src/procedural/procedure-recall.ts
|
|
7517
|
+
function tokenOverlapScore(prompt, memoryText) {
|
|
7518
|
+
const norm = (s) => s.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2);
|
|
7519
|
+
const promptTokens = new Set(norm(prompt));
|
|
7520
|
+
const memTokens = new Set(norm(memoryText));
|
|
7521
|
+
if (promptTokens.size === 0 || memTokens.size === 0) return 0;
|
|
7522
|
+
let inter = 0;
|
|
7523
|
+
for (const t of promptTokens) {
|
|
7524
|
+
if (memTokens.has(t)) inter++;
|
|
7525
|
+
}
|
|
7526
|
+
const union = /* @__PURE__ */ new Set([...promptTokens, ...memTokens]);
|
|
7527
|
+
return inter / Math.max(1, union.size);
|
|
7528
|
+
}
|
|
7529
|
+
function scoreProcedureForPrompt(m, prompt, queryIntent) {
|
|
7530
|
+
const memText = `${m.content}
|
|
7531
|
+
${(m.frontmatter.tags ?? []).join(" ")}`;
|
|
7532
|
+
const jaccard = tokenOverlapScore(prompt, memText);
|
|
7533
|
+
const memIntent = inferIntentFromText(m.content.slice(0, 2e3));
|
|
7534
|
+
const intentScore = intentCompatibilityScore(queryIntent, memIntent);
|
|
7535
|
+
return jaccard * 0.55 + intentScore * 0.45;
|
|
7536
|
+
}
|
|
7537
|
+
async function buildProcedureRecallSection(storage, prompt, config) {
|
|
7538
|
+
if (config.procedural?.enabled !== true) return null;
|
|
7539
|
+
const trimmed = typeof prompt === "string" ? prompt.trim() : "";
|
|
7540
|
+
if (!trimmed) return null;
|
|
7541
|
+
const queryIntent = inferIntentFromText(trimmed);
|
|
7542
|
+
if (!isTaskInitiationIntent(queryIntent)) return null;
|
|
7543
|
+
const maxN = Math.min(
|
|
7544
|
+
10,
|
|
7545
|
+
Math.max(
|
|
7546
|
+
1,
|
|
7547
|
+
typeof config.procedural.recallMaxProcedures === "number" && Number.isFinite(config.procedural.recallMaxProcedures) ? Math.floor(config.procedural.recallMaxProcedures) : 3
|
|
7548
|
+
)
|
|
7549
|
+
);
|
|
7550
|
+
const all = await storage.readAllMemories();
|
|
7551
|
+
const scored = all.filter(
|
|
7552
|
+
(m) => m.frontmatter.category === "procedure" && m.frontmatter.status !== "pending_review" && m.frontmatter.status !== "rejected" && m.frontmatter.status !== "quarantined" && m.frontmatter.status !== "superseded" && m.frontmatter.status !== "archived"
|
|
7553
|
+
).map((m) => ({ m, score: scoreProcedureForPrompt(m, trimmed, queryIntent) })).filter((x) => x.score > 0.04).sort((a, b) => b.score - a.score).slice(0, maxN);
|
|
7554
|
+
if (scored.length === 0) return null;
|
|
7555
|
+
const blocks = scored.map(({ m, score }) => {
|
|
7556
|
+
const id = m.frontmatter.id;
|
|
7557
|
+
const flat = m.content.replace(/\s+/g, " ").trim();
|
|
7558
|
+
const preview = flat.slice(0, 320);
|
|
7559
|
+
const suffix = flat.length > 320 ? "\u2026" : "";
|
|
7560
|
+
return `### ${id} (match ${score.toFixed(2)})
|
|
7561
|
+
|
|
7562
|
+
${preview}${suffix}`;
|
|
7563
|
+
});
|
|
7564
|
+
return `## Relevant procedures
|
|
7565
|
+
|
|
7566
|
+
${blocks.join("\n\n")}`;
|
|
7567
|
+
}
|
|
7568
|
+
|
|
7180
7569
|
// ../remnic-core/src/reconstruct.ts
|
|
7181
7570
|
function findUnresolvedEntityRefs(recalledSnippets, recalledEntityRefs, knownEntities) {
|
|
7182
7571
|
const refSet = new Set(recalledEntityRefs.map((r) => r.toLowerCase()));
|
|
@@ -7380,8 +7769,13 @@ async function scanDir(dir) {
|
|
|
7380
7769
|
async function scanMemoryDir(memoryDir) {
|
|
7381
7770
|
const factsDir = path4.join(memoryDir, "facts");
|
|
7382
7771
|
const correctionsDir = path4.join(memoryDir, "corrections");
|
|
7383
|
-
const
|
|
7384
|
-
|
|
7772
|
+
const proceduresDir = path4.join(memoryDir, "procedures");
|
|
7773
|
+
const [facts, corrections, procedures] = await Promise.all([
|
|
7774
|
+
scanDir(factsDir),
|
|
7775
|
+
scanDir(correctionsDir),
|
|
7776
|
+
scanDir(proceduresDir)
|
|
7777
|
+
]);
|
|
7778
|
+
return [...facts, ...corrections, ...procedures];
|
|
7385
7779
|
}
|
|
7386
7780
|
|
|
7387
7781
|
// ../remnic-core/src/search/lancedb-backend.ts
|
|
@@ -8216,6 +8610,23 @@ var EmbedHelper = class {
|
|
|
8216
8610
|
import { createHash as createHash3 } from "crypto";
|
|
8217
8611
|
import os2 from "os";
|
|
8218
8612
|
import path6 from "path";
|
|
8613
|
+
|
|
8614
|
+
// ../remnic-core/src/abort-error.ts
|
|
8615
|
+
function abortError(message) {
|
|
8616
|
+
const err = new Error(message);
|
|
8617
|
+
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
8618
|
+
return err;
|
|
8619
|
+
}
|
|
8620
|
+
function isAbortError(err) {
|
|
8621
|
+
return err instanceof Error && err.name === "AbortError";
|
|
8622
|
+
}
|
|
8623
|
+
function throwIfAborted(signal, message = "operation aborted") {
|
|
8624
|
+
if (signal?.aborted) {
|
|
8625
|
+
throw abortError(message);
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8629
|
+
// ../remnic-core/src/qmd.ts
|
|
8219
8630
|
var QMD_TIMEOUT_MS = 3e4;
|
|
8220
8631
|
var QMD_DAEMON_TIMEOUT_MS = 8e3;
|
|
8221
8632
|
var QMD_PROBE_TIMEOUT_MS = 8e3;
|
|
@@ -8246,14 +8657,6 @@ function getGlobalQmdState() {
|
|
|
8246
8657
|
}
|
|
8247
8658
|
return g[QMD_GLOBAL_STATE_KEY];
|
|
8248
8659
|
}
|
|
8249
|
-
function abortError(message) {
|
|
8250
|
-
const err = new Error(message);
|
|
8251
|
-
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
8252
|
-
return err;
|
|
8253
|
-
}
|
|
8254
|
-
function isAbortError(err) {
|
|
8255
|
-
return err instanceof Error && err.name === "AbortError";
|
|
8256
|
-
}
|
|
8257
8660
|
function errorMessage(err) {
|
|
8258
8661
|
if (typeof err === "string") return err;
|
|
8259
8662
|
if (err instanceof Error) return err.message;
|
|
@@ -8274,11 +8677,6 @@ function isCallerCancellation(err, signal) {
|
|
|
8274
8677
|
function isDaemonTimeoutError(err) {
|
|
8275
8678
|
return /timed out/i.test(errorMessage(err));
|
|
8276
8679
|
}
|
|
8277
|
-
function throwIfAborted(signal, message = "operation aborted") {
|
|
8278
|
-
if (signal?.aborted) {
|
|
8279
|
-
throw abortError(message);
|
|
8280
|
-
}
|
|
8281
|
-
}
|
|
8282
8680
|
function sleepWithSignal(ms, signal) {
|
|
8283
8681
|
return new Promise((resolve, reject) => {
|
|
8284
8682
|
throwIfAborted(signal);
|
|
@@ -8714,6 +9112,26 @@ var QmdDaemonSession = class {
|
|
|
8714
9112
|
this.buffer = "";
|
|
8715
9113
|
}
|
|
8716
9114
|
};
|
|
9115
|
+
var QMD_RESULT_LINE_RE = /^#([0-9a-fA-F]+)\s+(\d+)%\s+(.+)/;
|
|
9116
|
+
var QMD_PATH_TITLE_RE = /^(.+?\.[a-zA-Z]{2,10})\s+-\s+(.*)$/;
|
|
9117
|
+
function parseQmdMarkdownResultText(text, transport) {
|
|
9118
|
+
const results = [];
|
|
9119
|
+
for (const line of text.split("\n")) {
|
|
9120
|
+
const m = QMD_RESULT_LINE_RE.exec(line.trim());
|
|
9121
|
+
if (!m) continue;
|
|
9122
|
+
const rest = m[3];
|
|
9123
|
+
const pathTitleSplit = QMD_PATH_TITLE_RE.exec(rest);
|
|
9124
|
+
if (!pathTitleSplit) continue;
|
|
9125
|
+
results.push({
|
|
9126
|
+
docid: m[1],
|
|
9127
|
+
path: pathTitleSplit[1] ?? "unknown",
|
|
9128
|
+
snippet: "",
|
|
9129
|
+
score: parseInt(m[2], 10) / 100,
|
|
9130
|
+
transport
|
|
9131
|
+
});
|
|
9132
|
+
}
|
|
9133
|
+
return results;
|
|
9134
|
+
}
|
|
8717
9135
|
function parseMcpSearchResult(result, transport = "daemon") {
|
|
8718
9136
|
const resultObj = result;
|
|
8719
9137
|
if (!resultObj) return [];
|
|
@@ -8746,6 +9164,15 @@ function parseMcpSearchResult(result, transport = "daemon") {
|
|
|
8746
9164
|
const textResults = parsed?.results ?? parsed?.documents;
|
|
8747
9165
|
if (Array.isArray(textResults)) pushDocs(textResults);
|
|
8748
9166
|
} catch {
|
|
9167
|
+
const existingKeys = new Set(results.map((r) => `${r.docid.toLowerCase()}|${r.path}`));
|
|
9168
|
+
const parsed = parseQmdMarkdownResultText(item.text, transport);
|
|
9169
|
+
for (const p of parsed) {
|
|
9170
|
+
const key = `${p.docid.toLowerCase()}|${p.path}`;
|
|
9171
|
+
if (!existingKeys.has(key)) {
|
|
9172
|
+
results.push(p);
|
|
9173
|
+
existingKeys.add(key);
|
|
9174
|
+
}
|
|
9175
|
+
}
|
|
8749
9176
|
}
|
|
8750
9177
|
}
|
|
8751
9178
|
}
|
|
@@ -8801,9 +9228,22 @@ var QmdClient = class _QmdClient {
|
|
|
8801
9228
|
collection;
|
|
8802
9229
|
maxResults;
|
|
8803
9230
|
available = null;
|
|
8804
|
-
|
|
9231
|
+
_lastUpdateFailAtMs = null;
|
|
8805
9232
|
lastEmbedFailAtMs = null;
|
|
8806
9233
|
lastUpdateRunAtMs = null;
|
|
9234
|
+
get lastUpdateFailedAtMs() {
|
|
9235
|
+
return this._lastUpdateFailAtMs;
|
|
9236
|
+
}
|
|
9237
|
+
get lastUpdateRanAtMs() {
|
|
9238
|
+
return this.lastUpdateRunAtMs;
|
|
9239
|
+
}
|
|
9240
|
+
resetUpdateThrottles() {
|
|
9241
|
+
this._lastUpdateFailAtMs = null;
|
|
9242
|
+
this.lastUpdateRunAtMs = null;
|
|
9243
|
+
const gs = getGlobalQmdState();
|
|
9244
|
+
gs.lastGlobalUpdateRunAtMs = null;
|
|
9245
|
+
gs.lastGlobalUpdateFailAtMs = null;
|
|
9246
|
+
}
|
|
8807
9247
|
updateTimeoutMs;
|
|
8808
9248
|
updateMinIntervalMs;
|
|
8809
9249
|
slowLog;
|
|
@@ -9420,13 +9860,13 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9420
9860
|
return [];
|
|
9421
9861
|
}
|
|
9422
9862
|
}
|
|
9423
|
-
async update() {
|
|
9424
|
-
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false });
|
|
9863
|
+
async update(signal) {
|
|
9864
|
+
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false }, signal);
|
|
9425
9865
|
}
|
|
9426
9866
|
async updateCollection(collection) {
|
|
9427
9867
|
await this.runUpdateForCollection(collection, { perCollectionThrottle: true });
|
|
9428
9868
|
}
|
|
9429
|
-
async runUpdateForCollection(collection, options) {
|
|
9869
|
+
async runUpdateForCollection(collection, options, signal) {
|
|
9430
9870
|
if (this.available === false) return;
|
|
9431
9871
|
const name = collection.trim();
|
|
9432
9872
|
if (!name) return;
|
|
@@ -9452,7 +9892,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9452
9892
|
log.debug("QMD update: suppressed due to min-interval gate");
|
|
9453
9893
|
return;
|
|
9454
9894
|
}
|
|
9455
|
-
if (this.
|
|
9895
|
+
if (this._lastUpdateFailAtMs && now - this._lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
9456
9896
|
log.debug("QMD update: suppressed due to recent failures (backoff)");
|
|
9457
9897
|
return;
|
|
9458
9898
|
}
|
|
@@ -9473,7 +9913,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9473
9913
|
);
|
|
9474
9914
|
}
|
|
9475
9915
|
const startedAtMs = Date.now();
|
|
9476
|
-
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs);
|
|
9916
|
+
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs, signal);
|
|
9477
9917
|
const durationMs = Date.now() - startedAtMs;
|
|
9478
9918
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
9479
9919
|
log.warn(`SLOW QMD update: durationMs=${durationMs}`);
|
|
@@ -9493,7 +9933,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9493
9933
|
globalState.lastUpdateFailByCollectionMs[name] = at;
|
|
9494
9934
|
globalState.lastGlobalUpdateFailAtMs = at;
|
|
9495
9935
|
} else {
|
|
9496
|
-
this.
|
|
9936
|
+
this._lastUpdateFailAtMs = at;
|
|
9497
9937
|
globalState.lastGlobalUpdateFailAtMs = at;
|
|
9498
9938
|
}
|
|
9499
9939
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -12622,6 +13062,8 @@ import { mkdir as mkdir8, readFile as readFile10, rename, rm as rm2, stat as sta
|
|
|
12622
13062
|
import path15 from "path";
|
|
12623
13063
|
var DAY_SUMMARY_CRON_ID = "engram-day-summary";
|
|
12624
13064
|
var GOVERNANCE_CRON_ID = "engram-nightly-governance";
|
|
13065
|
+
var PROCEDURAL_MINING_CRON_ID = "engram-procedural-mining";
|
|
13066
|
+
var CONTRADICTION_SCAN_CRON_ID = "engram-contradiction-scan";
|
|
12625
13067
|
async function acquireCronJobsLock(jobsPath) {
|
|
12626
13068
|
const lockPath2 = `${jobsPath}.lock`;
|
|
12627
13069
|
const start = Date.now();
|
|
@@ -12734,13 +13176,61 @@ async function ensureNightlyGovernanceCron(jobsPath, options) {
|
|
|
12734
13176
|
delivery: { mode: "none" }
|
|
12735
13177
|
}));
|
|
12736
13178
|
}
|
|
13179
|
+
async function ensureProceduralMiningCron(jobsPath, options) {
|
|
13180
|
+
const scheduleExpr = typeof options.scheduleExpr === "string" && options.scheduleExpr.trim().length > 0 ? options.scheduleExpr.trim() : "17 3 * * *";
|
|
13181
|
+
const agentId = typeof options.agentId === "string" && options.agentId.trim().length > 0 ? options.agentId.trim() : "main";
|
|
13182
|
+
return ensureCronJob(jobsPath, PROCEDURAL_MINING_CRON_ID, () => ({
|
|
13183
|
+
id: PROCEDURAL_MINING_CRON_ID,
|
|
13184
|
+
agentId,
|
|
13185
|
+
name: "Remnic Procedural Mining (nightly)",
|
|
13186
|
+
enabled: true,
|
|
13187
|
+
schedule: {
|
|
13188
|
+
kind: "cron",
|
|
13189
|
+
expr: scheduleExpr,
|
|
13190
|
+
tz: options.timezone
|
|
13191
|
+
},
|
|
13192
|
+
sessionTarget: "isolated",
|
|
13193
|
+
wakeMode: "now",
|
|
13194
|
+
payload: {
|
|
13195
|
+
kind: "agentTurn",
|
|
13196
|
+
timeoutSeconds: 900,
|
|
13197
|
+
thinking: "off",
|
|
13198
|
+
message: "You are OpenClaw automation. Call tool `engram.procedure_mining_run` with empty params. If successful output exactly NO_REPLY. On error output one concise line. Do NOT use message tool."
|
|
13199
|
+
},
|
|
13200
|
+
delivery: { mode: "none" }
|
|
13201
|
+
}));
|
|
13202
|
+
}
|
|
13203
|
+
async function ensureContradictionScanCron(jobsPath, options) {
|
|
13204
|
+
const scheduleExpr = typeof options.scheduleExpr === "string" && options.scheduleExpr.trim().length > 0 ? options.scheduleExpr.trim() : "37 3 * * *";
|
|
13205
|
+
const agentId = typeof options.agentId === "string" && options.agentId.trim().length > 0 ? options.agentId.trim() : "main";
|
|
13206
|
+
return ensureCronJob(jobsPath, CONTRADICTION_SCAN_CRON_ID, () => ({
|
|
13207
|
+
id: CONTRADICTION_SCAN_CRON_ID,
|
|
13208
|
+
agentId,
|
|
13209
|
+
name: "Remnic Contradiction Scan (nightly)",
|
|
13210
|
+
enabled: true,
|
|
13211
|
+
schedule: {
|
|
13212
|
+
kind: "cron",
|
|
13213
|
+
expr: scheduleExpr,
|
|
13214
|
+
tz: options.timezone
|
|
13215
|
+
},
|
|
13216
|
+
sessionTarget: "isolated",
|
|
13217
|
+
wakeMode: "now",
|
|
13218
|
+
payload: {
|
|
13219
|
+
kind: "agentTurn",
|
|
13220
|
+
timeoutSeconds: 900,
|
|
13221
|
+
thinking: "off",
|
|
13222
|
+
message: "You are OpenClaw automation. Call tool `engram.contradiction_scan_run` with empty params. If successful output exactly NO_REPLY. On error output one concise line. Do NOT use message tool."
|
|
13223
|
+
},
|
|
13224
|
+
delivery: { mode: "none" }
|
|
13225
|
+
}));
|
|
13226
|
+
}
|
|
12737
13227
|
|
|
12738
13228
|
// ../remnic-core/src/lifecycle.ts
|
|
12739
13229
|
var DEFAULT_POLICY = {
|
|
12740
13230
|
promoteHeatThreshold: 0.55,
|
|
12741
13231
|
staleDecayThreshold: 0.65,
|
|
12742
13232
|
archiveDecayThreshold: 0.85,
|
|
12743
|
-
protectedCategories: ["decision", "principle", "commitment", "preference"]
|
|
13233
|
+
protectedCategories: ["decision", "principle", "commitment", "preference", "procedure"]
|
|
12744
13234
|
};
|
|
12745
13235
|
function clamp01(value) {
|
|
12746
13236
|
if (!Number.isFinite(value)) return 0;
|
|
@@ -14497,6 +14987,14 @@ function clampGraphRecallExpandedEntries(entries, maxEntries = 64) {
|
|
|
14497
14987
|
};
|
|
14498
14988
|
}).filter((item) => item.path.length > 0 && item.namespace.length > 0).slice(0, limit);
|
|
14499
14989
|
}
|
|
14990
|
+
function cloneTierExplain(tierExplain) {
|
|
14991
|
+
if (!tierExplain) return void 0;
|
|
14992
|
+
return structuredClone(tierExplain);
|
|
14993
|
+
}
|
|
14994
|
+
function cloneLastRecallSnapshot(snapshot) {
|
|
14995
|
+
if (!snapshot) return null;
|
|
14996
|
+
return structuredClone(snapshot);
|
|
14997
|
+
}
|
|
14500
14998
|
var DEFAULT_TIER_MIGRATION_STATUS = {
|
|
14501
14999
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
14502
15000
|
lastCycle: null,
|
|
@@ -14527,13 +15025,17 @@ var LastRecallStore = class {
|
|
|
14527
15025
|
}
|
|
14528
15026
|
}
|
|
14529
15027
|
get(sessionKey) {
|
|
14530
|
-
return this.state[sessionKey] ?? null;
|
|
15028
|
+
return cloneLastRecallSnapshot(this.state[sessionKey] ?? null);
|
|
14531
15029
|
}
|
|
14532
15030
|
getMostRecent() {
|
|
14533
15031
|
const snapshots = Object.values(this.state);
|
|
14534
15032
|
if (snapshots.length === 0) return null;
|
|
14535
|
-
snapshots.sort((a, b) =>
|
|
14536
|
-
|
|
15033
|
+
snapshots.sort((a, b) => {
|
|
15034
|
+
const byTime = b.recordedAt.localeCompare(a.recordedAt);
|
|
15035
|
+
if (byTime !== 0) return byTime;
|
|
15036
|
+
return a.sessionKey.localeCompare(b.sessionKey);
|
|
15037
|
+
});
|
|
15038
|
+
return cloneLastRecallSnapshot(snapshots[0] ?? null);
|
|
14537
15039
|
}
|
|
14538
15040
|
/**
|
|
14539
15041
|
* Persist last-recall snapshot and append an impression log entry.
|
|
@@ -14542,7 +15044,7 @@ var LastRecallStore = class {
|
|
|
14542
15044
|
async record(opts) {
|
|
14543
15045
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14544
15046
|
const queryHash = createHash4("sha256").update(opts.query).digest("hex");
|
|
14545
|
-
const
|
|
15047
|
+
const liveSnapshot = {
|
|
14546
15048
|
sessionKey: opts.sessionKey,
|
|
14547
15049
|
recordedAt: now,
|
|
14548
15050
|
queryHash,
|
|
@@ -14554,15 +15056,17 @@ var LastRecallStore = class {
|
|
|
14554
15056
|
requestedMode: opts.requestedMode,
|
|
14555
15057
|
source: opts.source,
|
|
14556
15058
|
fallbackUsed: opts.fallbackUsed,
|
|
14557
|
-
sourcesUsed: opts.sourcesUsed
|
|
14558
|
-
budgetsApplied: opts.budgetsApplied
|
|
15059
|
+
sourcesUsed: opts.sourcesUsed,
|
|
15060
|
+
budgetsApplied: opts.budgetsApplied,
|
|
14559
15061
|
latencyMs: opts.latencyMs,
|
|
14560
|
-
resultPaths: opts.resultPaths
|
|
15062
|
+
resultPaths: opts.resultPaths,
|
|
14561
15063
|
policyVersion: opts.policyVersion,
|
|
14562
15064
|
identityInjectionMode: opts.identityInjection?.mode,
|
|
14563
15065
|
identityInjectedChars: opts.identityInjection?.injectedChars,
|
|
14564
|
-
identityInjectionTruncated: opts.identityInjection?.truncated
|
|
15066
|
+
identityInjectionTruncated: opts.identityInjection?.truncated,
|
|
15067
|
+
tierExplain: opts.tierExplain
|
|
14565
15068
|
};
|
|
15069
|
+
const snapshot = cloneLastRecallSnapshot(liveSnapshot);
|
|
14566
15070
|
this.state[opts.sessionKey] = snapshot;
|
|
14567
15071
|
const keys = Object.keys(this.state);
|
|
14568
15072
|
if (keys.length > 50) {
|
|
@@ -14586,6 +15090,31 @@ var LastRecallStore = class {
|
|
|
14586
15090
|
}
|
|
14587
15091
|
}
|
|
14588
15092
|
}
|
|
15093
|
+
/**
|
|
15094
|
+
* Attach a RecallTierExplain block to the existing snapshot for a
|
|
15095
|
+
* session without rewriting the entire snapshot. Used by the
|
|
15096
|
+
* post-recall direct-answer annotation path (issue #518 slice 3c):
|
|
15097
|
+
* recallInternal records the snapshot first, then the orchestrator
|
|
15098
|
+
* fires the direct-answer tier in observation mode and annotates
|
|
15099
|
+
* the stored snapshot with whichever tier served the query.
|
|
15100
|
+
*
|
|
15101
|
+
* No-op when no snapshot exists for the given session; callers do
|
|
15102
|
+
* not need to guard on existence.
|
|
15103
|
+
*/
|
|
15104
|
+
async annotateTierExplain(sessionKey, tierExplain) {
|
|
15105
|
+
const current = this.state[sessionKey];
|
|
15106
|
+
if (!current) return;
|
|
15107
|
+
this.state[sessionKey] = {
|
|
15108
|
+
...current,
|
|
15109
|
+
tierExplain: cloneTierExplain(tierExplain)
|
|
15110
|
+
};
|
|
15111
|
+
try {
|
|
15112
|
+
await mkdir11(path20.dirname(this.statePath), { recursive: true });
|
|
15113
|
+
await writeFile12(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
15114
|
+
} catch (err) {
|
|
15115
|
+
log.debug(`last recall tier-explain annotate failed: ${err}`);
|
|
15116
|
+
}
|
|
15117
|
+
}
|
|
14589
15118
|
};
|
|
14590
15119
|
var TierMigrationStatusStore = class {
|
|
14591
15120
|
statePath;
|
|
@@ -18168,97 +18697,6 @@ async function readRecentEntityTranscriptEntries(transcriptEntriesPromise, recen
|
|
|
18168
18697
|
}
|
|
18169
18698
|
var entityRecentTranscriptLookbackHours = RECENT_TRANSCRIPT_LOOKBACK_HOURS;
|
|
18170
18699
|
|
|
18171
|
-
// ../remnic-core/src/intent.ts
|
|
18172
|
-
var GOAL_PATTERNS = [
|
|
18173
|
-
{ re: /\b(debug(?:s|ged|ging)?|fix(?:es|ed|ing)?|error(?:s)?|incident(?:s)?|outage(?:s)?|failure(?:s)?)\b/i, goal: "stabilize" },
|
|
18174
|
-
{ re: /\b(deploy(?:s|ed|ing)?|release(?:s|d|ing)?|ship(?:s|ped|ping)?|publish(?:es|ed|ing)?)\b/i, goal: "release" },
|
|
18175
|
-
{ re: /\b(plan(?:s|ned|ning)?|roadmap(?:s)?|strateg(?:y|ies)|design(?:s|ed|ing)?)\b/i, goal: "plan" },
|
|
18176
|
-
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|security|hardening)\b/i, goal: "review" },
|
|
18177
|
-
{ re: /\b(sales|deal|customer|client|prospect)\b/i, goal: "close_deal" }
|
|
18178
|
-
];
|
|
18179
|
-
var ACTION_PATTERNS = [
|
|
18180
|
-
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|inspect(?:s|ed|ing)?|check(?:s|ed|ing)?)\b/i, action: "review" },
|
|
18181
|
-
{ re: /\b(plan(?:s|ned|ning)?|design(?:s|ed|ing)?|brainstorm(?:s|ed|ing)?|spec(?:s)?)\b/i, action: "plan" },
|
|
18182
|
-
{ re: /\b(implement(?:s|ed|ing)?|build(?:s|ing)?|built|code(?:s|d|ing)?|patch(?:es|ed|ing)?|fix(?:es|ed|ing)?)\b/i, action: "execute" },
|
|
18183
|
-
{ re: /\b(summariz(?:e|es|ed|ing)|recap(?:s|ped|ping)?|what happened|timeline)\b/i, action: "summarize" },
|
|
18184
|
-
{ re: /\b(decid(?:e|es|ed|ing)|decision(?:s)?|cho(?:ose|oses|osing)|chose|chosen)\b/i, action: "decide" }
|
|
18185
|
-
];
|
|
18186
|
-
var ENTITY_PATTERNS = [
|
|
18187
|
-
{ re: /\b(pr|pull request|branch|repo|github|ci|workflow)\b/i, entityType: "repo" },
|
|
18188
|
-
{ re: /\b(discord|slack|channel|gateway|agent)\b/i, entityType: "ops" },
|
|
18189
|
-
{ re: /\b(customer|client|deal|lead|account)\b/i, entityType: "client" },
|
|
18190
|
-
{ re: /\b(model|llm|qmd|embedding|retrieval|memory)\b/i, entityType: "ai" },
|
|
18191
|
-
{ re: /\b(doc|readme|docs|changelog)\b/i, entityType: "docs" }
|
|
18192
|
-
];
|
|
18193
|
-
function normalizeTextInput(input) {
|
|
18194
|
-
return typeof input === "string" ? input : "";
|
|
18195
|
-
}
|
|
18196
|
-
function inferIntentFromText(text) {
|
|
18197
|
-
const safeText = normalizeTextInput(text);
|
|
18198
|
-
const goal = GOAL_PATTERNS.find((p) => p.re.test(safeText))?.goal ?? "unknown";
|
|
18199
|
-
const actionType = ACTION_PATTERNS.find((p) => p.re.test(safeText))?.action ?? "unknown";
|
|
18200
|
-
const entityTypes = Array.from(
|
|
18201
|
-
new Set(ENTITY_PATTERNS.filter((p) => p.re.test(safeText)).map((p) => p.entityType))
|
|
18202
|
-
);
|
|
18203
|
-
return {
|
|
18204
|
-
goal,
|
|
18205
|
-
actionType,
|
|
18206
|
-
entityTypes
|
|
18207
|
-
};
|
|
18208
|
-
}
|
|
18209
|
-
function intentCompatibilityScore(queryIntent, memoryIntent) {
|
|
18210
|
-
const queryHasSignal = queryIntent.goal !== "unknown" || queryIntent.actionType !== "unknown" || queryIntent.entityTypes.length > 0;
|
|
18211
|
-
const memoryHasSignal = memoryIntent.goal !== "unknown" || memoryIntent.actionType !== "unknown" || memoryIntent.entityTypes.length > 0;
|
|
18212
|
-
if (!queryHasSignal || !memoryHasSignal) return 0;
|
|
18213
|
-
let score = 0;
|
|
18214
|
-
if (queryIntent.goal !== "unknown" && memoryIntent.goal !== "unknown" && queryIntent.goal === memoryIntent.goal) {
|
|
18215
|
-
score += 0.5;
|
|
18216
|
-
}
|
|
18217
|
-
if (queryIntent.actionType !== "unknown" && memoryIntent.actionType !== "unknown" && queryIntent.actionType === memoryIntent.actionType) {
|
|
18218
|
-
score += 0.3;
|
|
18219
|
-
}
|
|
18220
|
-
const overlap = queryIntent.entityTypes.filter((et) => memoryIntent.entityTypes.includes(et)).length;
|
|
18221
|
-
if (overlap > 0) {
|
|
18222
|
-
const denom = Math.max(queryIntent.entityTypes.length, memoryIntent.entityTypes.length, 1);
|
|
18223
|
-
score += 0.2 * (overlap / denom);
|
|
18224
|
-
}
|
|
18225
|
-
return Math.max(0, Math.min(1, score));
|
|
18226
|
-
}
|
|
18227
|
-
function planRecallMode(prompt) {
|
|
18228
|
-
const p = normalizeTextInput(prompt).trim();
|
|
18229
|
-
let ackCandidate = p;
|
|
18230
|
-
while (ackCandidate.length > 0) {
|
|
18231
|
-
const ch = ackCandidate.charCodeAt(ackCandidate.length - 1);
|
|
18232
|
-
const isDigit = ch >= 48 && ch <= 57;
|
|
18233
|
-
const isUpper = ch >= 65 && ch <= 90;
|
|
18234
|
-
const isLower = ch >= 97 && ch <= 122;
|
|
18235
|
-
if (isDigit || isUpper || isLower) break;
|
|
18236
|
-
ackCandidate = ackCandidate.slice(0, -1);
|
|
18237
|
-
}
|
|
18238
|
-
ackCandidate = ackCandidate.trim();
|
|
18239
|
-
if (p.length === 0) return "no_recall";
|
|
18240
|
-
if (/\b(timeline|sequence|history|what happened|chain of events|root cause)\b/i.test(p)) {
|
|
18241
|
-
return "graph_mode";
|
|
18242
|
-
}
|
|
18243
|
-
if (p.length <= 18 && /^(ok|okay|kk|thanks|thx|got it|sounds good|yep|yes|nope|no|done|cool|works)$/i.test(ackCandidate)) {
|
|
18244
|
-
return "no_recall";
|
|
18245
|
-
}
|
|
18246
|
-
if (/\b(previous|earlier|remember|last time|did we|what did we decide|context|summarize|summary|recap|key points|decision)\b/i.test(p) || /\?$/.test(p) || /^(what|why|how|when|where|who|which)\b/i.test(p.toLowerCase())) {
|
|
18247
|
-
return "full";
|
|
18248
|
-
}
|
|
18249
|
-
if (p.length <= 100 && /^(check|reload|restart|run|verify|show|status|sync|update|open|close|set|enable|disable|fix|patch)\b/i.test(p)) {
|
|
18250
|
-
return "minimal";
|
|
18251
|
-
}
|
|
18252
|
-
return "full";
|
|
18253
|
-
}
|
|
18254
|
-
function hasBroadGraphIntent(prompt) {
|
|
18255
|
-
const p = normalizeTextInput(prompt).trim().toLowerCase();
|
|
18256
|
-
if (!p) return false;
|
|
18257
|
-
return /\b(what changed|how did we get here|why did this happen|what led to|cause chain|dependency chain|regression chain|failure chain)\b/i.test(
|
|
18258
|
-
p
|
|
18259
|
-
);
|
|
18260
|
-
}
|
|
18261
|
-
|
|
18262
18700
|
// ../remnic-core/src/recall-query-policy.ts
|
|
18263
18701
|
var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
|
|
18264
18702
|
"the",
|
|
@@ -18553,11 +18991,11 @@ function computeCompressionGuidelineCandidate(events, options = {}) {
|
|
|
18553
18991
|
notes
|
|
18554
18992
|
};
|
|
18555
18993
|
}
|
|
18556
|
-
const
|
|
18994
|
+
const successRate2 = summary.outcomes.applied / summary.total;
|
|
18557
18995
|
const failureRate = summary.outcomes.failed / summary.total;
|
|
18558
18996
|
const qualitySeen = summary.quality.good + summary.quality.poor;
|
|
18559
18997
|
const qualitySignal = qualitySeen > 0 ? (summary.quality.good - summary.quality.poor) / qualitySeen : 0;
|
|
18560
|
-
const rawDelta = clamp((
|
|
18998
|
+
const rawDelta = clamp((successRate2 - failureRate) * 0.12 + qualitySignal * 0.06, -MAX_DELTA, MAX_DELTA);
|
|
18561
18999
|
const delta = roundDelta(rawDelta);
|
|
18562
19000
|
const direction = directionForDelta(delta);
|
|
18563
19001
|
if (direction === "decrease" && summary.outcomes.failed > summary.outcomes.applied) {
|
|
@@ -20261,13 +20699,13 @@ async function readCueAnchors(options) {
|
|
|
20261
20699
|
return anchors;
|
|
20262
20700
|
}
|
|
20263
20701
|
async function searchHarmonicRetrieval(options) {
|
|
20264
|
-
|
|
20702
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20265
20703
|
const queryTokens = new Set(normalizeRecallTokens(options.query, ["what", "which"]));
|
|
20266
20704
|
if (queryTokens.size === 0 || options.maxResults <= 0) return [];
|
|
20267
20705
|
const nodes = await readAbstractionNodes(options);
|
|
20268
20706
|
const candidates = /* @__PURE__ */ new Map();
|
|
20269
20707
|
for (const node of nodes) {
|
|
20270
|
-
|
|
20708
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20271
20709
|
const { score, matchedFields } = scoreNode(node, queryTokens);
|
|
20272
20710
|
if (score <= 0) continue;
|
|
20273
20711
|
candidates.set(node.nodeId, {
|
|
@@ -20279,11 +20717,11 @@ async function searchHarmonicRetrieval(options) {
|
|
|
20279
20717
|
});
|
|
20280
20718
|
}
|
|
20281
20719
|
if (options.anchorsEnabled) {
|
|
20282
|
-
|
|
20720
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20283
20721
|
const anchors = await readCueAnchors(options);
|
|
20284
20722
|
const nodeIndex = new Map(nodes.map((node) => [node.nodeId, node]));
|
|
20285
20723
|
for (const anchor of anchors) {
|
|
20286
|
-
|
|
20724
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20287
20725
|
const { score, matchedFields } = scoreAnchor(anchor, queryTokens);
|
|
20288
20726
|
if (score <= 0) continue;
|
|
20289
20727
|
for (const nodeRef of anchor.nodeRefs) {
|
|
@@ -20325,12 +20763,6 @@ async function searchHarmonicRetrieval(options) {
|
|
|
20325
20763
|
(left, right) => right.score - left.score || right.anchorScore - left.anchorScore || right.node.recordedAt.localeCompare(left.node.recordedAt)
|
|
20326
20764
|
).slice(0, options.maxResults);
|
|
20327
20765
|
}
|
|
20328
|
-
function throwIfAborted2(signal) {
|
|
20329
|
-
if (!signal?.aborted) return;
|
|
20330
|
-
const err = new Error("harmonic retrieval aborted");
|
|
20331
|
-
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
20332
|
-
throw err;
|
|
20333
|
-
}
|
|
20334
20766
|
|
|
20335
20767
|
// ../remnic-core/src/verified-recall.ts
|
|
20336
20768
|
function createReadOnlyBoxBuilder(memoryDir) {
|
|
@@ -20873,14 +21305,75 @@ async function getWorkProductLedgerStatus(options) {
|
|
|
20873
21305
|
};
|
|
20874
21306
|
}
|
|
20875
21307
|
|
|
21308
|
+
// ../remnic-core/src/utils/iso-timestamp.ts
|
|
21309
|
+
var ISO_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
21310
|
+
var ISO_OFFSET_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
21311
|
+
function validateDateComponents(isoString) {
|
|
21312
|
+
const match = isoString.match(
|
|
21313
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/
|
|
21314
|
+
);
|
|
21315
|
+
if (!match) return false;
|
|
21316
|
+
const [, yStr, mStr, dStr, hStr, minStr, sStr] = match;
|
|
21317
|
+
const y = Number(yStr);
|
|
21318
|
+
const m = Number(mStr);
|
|
21319
|
+
const d = Number(dStr);
|
|
21320
|
+
const h = Number(hStr);
|
|
21321
|
+
const min = Number(minStr);
|
|
21322
|
+
const s = Number(sStr);
|
|
21323
|
+
if (m < 1 || m > 12) return false;
|
|
21324
|
+
if (d < 1 || d > 31) return false;
|
|
21325
|
+
if (h > 23 || min > 59 || s > 59) return false;
|
|
21326
|
+
const daysInMonth = new Date(y, m, 0).getDate();
|
|
21327
|
+
if (d > daysInMonth) return false;
|
|
21328
|
+
return true;
|
|
21329
|
+
}
|
|
21330
|
+
function validateOffset(isoString) {
|
|
21331
|
+
const offsetMatch = isoString.match(/([+-])(\d{2}):(\d{2})$/);
|
|
21332
|
+
if (!offsetMatch) return true;
|
|
21333
|
+
const oh = Number(offsetMatch[2]);
|
|
21334
|
+
const om = Number(offsetMatch[3]);
|
|
21335
|
+
if (oh > 14 || om > 59) return false;
|
|
21336
|
+
if (oh === 14 && om > 0) return false;
|
|
21337
|
+
return true;
|
|
21338
|
+
}
|
|
21339
|
+
function normalizeUtcForComparison(value) {
|
|
21340
|
+
const fracMatch = value.match(/\.(\d+)Z$/);
|
|
21341
|
+
if (fracMatch) {
|
|
21342
|
+
const ms = (fracMatch[1] + "000").slice(0, 3);
|
|
21343
|
+
return value.replace(/\.\d+Z$/, `.${ms}Z`);
|
|
21344
|
+
}
|
|
21345
|
+
return value.replace(/Z$/, ".000Z");
|
|
21346
|
+
}
|
|
21347
|
+
function parseIsoUtcTimestamp(value) {
|
|
21348
|
+
if (typeof value !== "string" || !ISO_UTC_TIMESTAMP_RE.test(value)) {
|
|
21349
|
+
return null;
|
|
21350
|
+
}
|
|
21351
|
+
const ts = Date.parse(value);
|
|
21352
|
+
if (!Number.isFinite(ts)) return null;
|
|
21353
|
+
if (!validateDateComponents(value)) return null;
|
|
21354
|
+
const roundTrip = new Date(ts).toISOString();
|
|
21355
|
+
if (roundTrip !== normalizeUtcForComparison(value)) return null;
|
|
21356
|
+
return ts;
|
|
21357
|
+
}
|
|
21358
|
+
function parseIsoOffsetTimestamp(value) {
|
|
21359
|
+
if (typeof value !== "string" || !ISO_OFFSET_TIMESTAMP_RE.test(value)) {
|
|
21360
|
+
return null;
|
|
21361
|
+
}
|
|
21362
|
+
const ts = Date.parse(value);
|
|
21363
|
+
if (!Number.isFinite(ts)) return null;
|
|
21364
|
+
if (!validateDateComponents(value)) return null;
|
|
21365
|
+
if (!validateOffset(value)) return null;
|
|
21366
|
+
if (value.endsWith("Z")) {
|
|
21367
|
+
const roundTrip = new Date(ts).toISOString();
|
|
21368
|
+
if (roundTrip !== normalizeUtcForComparison(value)) return null;
|
|
21369
|
+
}
|
|
21370
|
+
return ts;
|
|
21371
|
+
}
|
|
21372
|
+
|
|
20876
21373
|
// ../remnic-core/src/replay/types.ts
|
|
20877
21374
|
var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
|
|
20878
21375
|
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
20879
|
-
var ISO_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
20880
21376
|
var REPLAY_UNKNOWN_SESSION_KEY = "replay:unknown";
|
|
20881
|
-
function normalizeIsoForComparison(value) {
|
|
20882
|
-
return value.includes(".") ? value : value.replace("Z", ".000Z");
|
|
20883
|
-
}
|
|
20884
21377
|
function isReplaySource(value) {
|
|
20885
21378
|
return typeof value === "string" && VALID_SOURCES.has(value);
|
|
20886
21379
|
}
|
|
@@ -20893,12 +21386,7 @@ function normalizeReplaySessionKey(value) {
|
|
|
20893
21386
|
return trimmed.length > 0 ? trimmed : REPLAY_UNKNOWN_SESSION_KEY;
|
|
20894
21387
|
}
|
|
20895
21388
|
function parseIsoTimestamp(value) {
|
|
20896
|
-
|
|
20897
|
-
const ts = Date.parse(value);
|
|
20898
|
-
if (!Number.isFinite(ts)) return null;
|
|
20899
|
-
const roundTrip = new Date(ts).toISOString();
|
|
20900
|
-
if (roundTrip !== normalizeIsoForComparison(value)) return null;
|
|
20901
|
-
return ts;
|
|
21389
|
+
return parseIsoUtcTimestamp(value);
|
|
20902
21390
|
}
|
|
20903
21391
|
function validateReplayTurn(turn, index) {
|
|
20904
21392
|
const issues = [];
|
|
@@ -24961,7 +25449,8 @@ var DEFAULT_CATEGORIES = [
|
|
|
24961
25449
|
"commitment",
|
|
24962
25450
|
"moment",
|
|
24963
25451
|
"skill",
|
|
24964
|
-
"rule"
|
|
25452
|
+
"rule",
|
|
25453
|
+
"procedure"
|
|
24965
25454
|
];
|
|
24966
25455
|
function normalizeNamespace(namespace) {
|
|
24967
25456
|
return namespace.trim();
|
|
@@ -25066,7 +25555,8 @@ var INLINE_ALLOWED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
25066
25555
|
"commitment",
|
|
25067
25556
|
"moment",
|
|
25068
25557
|
"skill",
|
|
25069
|
-
"rule"
|
|
25558
|
+
"rule",
|
|
25559
|
+
"procedure"
|
|
25070
25560
|
]);
|
|
25071
25561
|
var SECRET_PATTERNS = [
|
|
25072
25562
|
/\bsk-[A-Za-z0-9]{16,}\b/,
|
|
@@ -25665,15 +26155,24 @@ var NamespaceSearchRouter = class {
|
|
|
25665
26155
|
);
|
|
25666
26156
|
return mergeNamespaceSearchResults(resultsByNamespace, maxResults);
|
|
25667
26157
|
}
|
|
26158
|
+
/**
|
|
26159
|
+
* Update all namespace backends.
|
|
26160
|
+
* Returns the number of backends for which an update was attempted
|
|
26161
|
+
* (i.e., available and collection present). Callers can treat 0 as a
|
|
26162
|
+
* signal that no backend was eligible — useful for success-verification in
|
|
26163
|
+
* startup-sync when namespacesEnabled is true.
|
|
26164
|
+
*/
|
|
25668
26165
|
async updateNamespaces(namespaces) {
|
|
25669
26166
|
const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
|
|
25670
|
-
await Promise.all(
|
|
26167
|
+
const results = await Promise.all(
|
|
25671
26168
|
unique.map(async (namespace) => {
|
|
25672
26169
|
const record = await this.backendRecordFor(namespace);
|
|
25673
|
-
if (!record.available || record.collectionState === "missing") return;
|
|
26170
|
+
if (!record.available || record.collectionState === "missing") return 0;
|
|
25674
26171
|
await record.backend.update();
|
|
26172
|
+
return 1;
|
|
25675
26173
|
})
|
|
25676
26174
|
);
|
|
26175
|
+
return results.reduce((sum, v) => sum + v, 0);
|
|
25677
26176
|
}
|
|
25678
26177
|
async embedNamespaces(namespaces) {
|
|
25679
26178
|
const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
|
|
@@ -25689,6 +26188,10 @@ var NamespaceSearchRouter = class {
|
|
|
25689
26188
|
const record = await this.backendRecordFor(namespace);
|
|
25690
26189
|
return record.collectionState;
|
|
25691
26190
|
}
|
|
26191
|
+
/** Clear cached backend records so the next access re-probes availability. */
|
|
26192
|
+
clearCache() {
|
|
26193
|
+
this.cache.clear();
|
|
26194
|
+
}
|
|
25692
26195
|
async backendRecordFor(namespace) {
|
|
25693
26196
|
const key = namespace.trim() || this.config.defaultNamespace;
|
|
25694
26197
|
const existing = this.cache.get(key);
|
|
@@ -26846,15 +27349,9 @@ function fingerprintEntitySynthesisEvidence(entity) {
|
|
|
26846
27349
|
fingerprint.update(fingerprintEntityStructuredFacts(entity) ?? "");
|
|
26847
27350
|
return fingerprint.digest("hex");
|
|
26848
27351
|
}
|
|
26849
|
-
|
|
26850
|
-
const err = new Error(message);
|
|
26851
|
-
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
26852
|
-
return err;
|
|
26853
|
-
}
|
|
27352
|
+
var abortRecallError = abortError;
|
|
26854
27353
|
function throwIfRecallAborted(signal, message = "recall aborted") {
|
|
26855
|
-
|
|
26856
|
-
throw abortRecallError(message);
|
|
26857
|
-
}
|
|
27354
|
+
throwIfAborted(signal, message);
|
|
26858
27355
|
}
|
|
26859
27356
|
async function raceRecallAbort(promise, signal, message = "recall aborted") {
|
|
26860
27357
|
throwIfRecallAborted(signal, message);
|
|
@@ -27108,7 +27605,8 @@ function parseMemoryIntentSnapshot(value) {
|
|
|
27108
27605
|
actionType: typeof candidate.actionType === "string" ? candidate.actionType : "unknown",
|
|
27109
27606
|
entityTypes: Array.isArray(candidate.entityTypes) ? candidate.entityTypes.filter(
|
|
27110
27607
|
(item) => typeof item === "string"
|
|
27111
|
-
) : []
|
|
27608
|
+
) : [],
|
|
27609
|
+
taskInitiation: candidate.taskInitiation === true
|
|
27112
27610
|
};
|
|
27113
27611
|
}
|
|
27114
27612
|
function buildQmdIntentHint(intent) {
|
|
@@ -27122,6 +27620,9 @@ function buildQmdIntentHint(intent) {
|
|
|
27122
27620
|
if (intent.entityTypes.length > 0) {
|
|
27123
27621
|
parts.push(`entities:${intent.entityTypes.join(",")}`);
|
|
27124
27622
|
}
|
|
27623
|
+
if (intent.taskInitiation === true) {
|
|
27624
|
+
parts.push("task_initiation");
|
|
27625
|
+
}
|
|
27125
27626
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
27126
27627
|
}
|
|
27127
27628
|
function parseQmdRecallResults(value) {
|
|
@@ -27297,6 +27798,41 @@ var Orchestrator = class _Orchestrator {
|
|
|
27297
27798
|
// Initialization gate: recall() awaits this before proceeding
|
|
27298
27799
|
initPromise = null;
|
|
27299
27800
|
resolveInit = null;
|
|
27801
|
+
/**
|
|
27802
|
+
* Resolves when deferred initialization (QMD probe, warmup, caches, cron)
|
|
27803
|
+
* completes. CLI and http-serve callers that need `qmd.isAvailable()` to
|
|
27804
|
+
* reflect reality should `await orchestrator.deferredReady` after
|
|
27805
|
+
* `initialize()`. Gateway callers can ignore it — recall() degrades
|
|
27806
|
+
* gracefully when QMD isn't ready yet.
|
|
27807
|
+
*
|
|
27808
|
+
* Also resolves (without error) when `initialize()` throws before reaching
|
|
27809
|
+
* the deferred-init phase, so callers never hang on a permanently-pending
|
|
27810
|
+
* promise.
|
|
27811
|
+
*
|
|
27812
|
+
* Host adapters that need to tie deferred init to their stop() lifecycle
|
|
27813
|
+
* should `await orchestrator.deferredReady` before proceeding with teardown
|
|
27814
|
+
* to prevent background QMD/warmup/cron tasks from racing with shutdown.
|
|
27815
|
+
*/
|
|
27816
|
+
deferredReady = Promise.resolve();
|
|
27817
|
+
resolveDeferredReady = null;
|
|
27818
|
+
deferredInitAbort = null;
|
|
27819
|
+
/**
|
|
27820
|
+
* Whether the deferred init's QMD startup sync completed successfully.
|
|
27821
|
+
* When false after deferredReady resolves, the server retry loop should
|
|
27822
|
+
* attempt startupSearchSync() even if `qmd.isAvailable()` is true —
|
|
27823
|
+
* availability only means probe succeeded, not that the index is current.
|
|
27824
|
+
*/
|
|
27825
|
+
deferredSyncSucceeded = false;
|
|
27826
|
+
/**
|
|
27827
|
+
* Abort deferred initialization so background QMD sync/warmup stops
|
|
27828
|
+
* promptly on shutdown. Safe to call multiple times or before init.
|
|
27829
|
+
*/
|
|
27830
|
+
abortDeferredInit() {
|
|
27831
|
+
if (this.deferredInitAbort) {
|
|
27832
|
+
this.deferredInitAbort.abort();
|
|
27833
|
+
this.deferredInitAbort = null;
|
|
27834
|
+
}
|
|
27835
|
+
}
|
|
27300
27836
|
/** Set per-session workspace for the next recall() call (compaction reset). @internal */
|
|
27301
27837
|
setRecallWorkspaceOverride(sessionKey, dir) {
|
|
27302
27838
|
this._recallWorkspaceOverrides.set(sessionKey, dir);
|
|
@@ -27449,6 +27985,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
27449
27985
|
);
|
|
27450
27986
|
this.judgeVerdictCache = createVerdictCache();
|
|
27451
27987
|
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
27988
|
+
this.localLlm.disableThinking = config.localLlmDisableThinking;
|
|
27452
27989
|
this.fastLlm = config.localLlmFastEnabled ? (() => {
|
|
27453
27990
|
const client = new LocalLlmClient(
|
|
27454
27991
|
{
|
|
@@ -27687,100 +28224,173 @@ var Orchestrator = class _Orchestrator {
|
|
|
27687
28224
|
return this.fastLlm;
|
|
27688
28225
|
}
|
|
27689
28226
|
async initialize() {
|
|
27690
|
-
|
|
27691
|
-
|
|
27692
|
-
logger: (message) => log.info(message)
|
|
28227
|
+
this.deferredReady = new Promise((resolve) => {
|
|
28228
|
+
this.resolveDeferredReady = resolve;
|
|
27693
28229
|
});
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
|
|
27701
|
-
|
|
27702
|
-
|
|
27703
|
-
|
|
27704
|
-
|
|
27705
|
-
|
|
28230
|
+
try {
|
|
28231
|
+
await migrateFromEngram({
|
|
28232
|
+
quiet: true,
|
|
28233
|
+
logger: (message) => log.info(message)
|
|
28234
|
+
});
|
|
28235
|
+
await this.storage.ensureDirectories();
|
|
28236
|
+
await this.storage.loadAliases();
|
|
28237
|
+
if (this.config.namespacesEnabled) {
|
|
28238
|
+
const namespaces = /* @__PURE__ */ new Set([
|
|
28239
|
+
this.config.defaultNamespace,
|
|
28240
|
+
this.config.sharedNamespace,
|
|
28241
|
+
...this.config.namespacePolicies.map((p) => p.name)
|
|
28242
|
+
]);
|
|
28243
|
+
for (const ns of namespaces) {
|
|
28244
|
+
const sm = await this.storageRouter.storageFor(ns);
|
|
28245
|
+
await sm.ensureDirectories();
|
|
28246
|
+
await sm.loadAliases().catch(() => void 0);
|
|
28247
|
+
}
|
|
28248
|
+
}
|
|
28249
|
+
await this.relevance.load();
|
|
28250
|
+
await this.negatives.load();
|
|
28251
|
+
await this.lastRecall.load();
|
|
28252
|
+
await this.tierMigrationStatus.load();
|
|
28253
|
+
await this.sessionObserver.load();
|
|
28254
|
+
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
28255
|
+
this.utilityRuntimeValues = await loadUtilityRuntimeValues({
|
|
28256
|
+
memoryDir: this.config.memoryDir,
|
|
28257
|
+
memoryUtilityLearningEnabled: this.config.memoryUtilityLearningEnabled,
|
|
28258
|
+
promotionByOutcomeEnabled: this.config.promotionByOutcomeEnabled
|
|
28259
|
+
});
|
|
28260
|
+
if (this.config.factDeduplicationEnabled) {
|
|
28261
|
+
const stateDir2 = path43.join(this.config.memoryDir, "state");
|
|
28262
|
+
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
28263
|
+
await this.contentHashIndex.load();
|
|
28264
|
+
log.info(
|
|
28265
|
+
`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`
|
|
28266
|
+
);
|
|
27706
28267
|
}
|
|
27707
|
-
|
|
27708
|
-
|
|
27709
|
-
|
|
27710
|
-
|
|
27711
|
-
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
|
|
27719
|
-
|
|
27720
|
-
const stateDir2 = path43.join(this.config.memoryDir, "state");
|
|
27721
|
-
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
27722
|
-
await this.contentHashIndex.load();
|
|
27723
|
-
log.info(
|
|
27724
|
-
`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`
|
|
27725
|
-
);
|
|
27726
|
-
}
|
|
27727
|
-
await this.transcript.initialize();
|
|
27728
|
-
await this.summarizer.initialize();
|
|
27729
|
-
if (this.sharedContext) {
|
|
27730
|
-
await this.sharedContext.ensureStructure();
|
|
27731
|
-
}
|
|
27732
|
-
if (this.compounding) {
|
|
27733
|
-
await this.compounding.ensureDirs();
|
|
27734
|
-
}
|
|
27735
|
-
if (this.resolveInit) {
|
|
27736
|
-
this.resolveInit();
|
|
27737
|
-
this.resolveInit = null;
|
|
27738
|
-
log.info("init gate opened (essential state loaded)");
|
|
27739
|
-
}
|
|
27740
|
-
{
|
|
27741
|
-
const available = await this.qmd.probe();
|
|
27742
|
-
if (available) {
|
|
27743
|
-
log.info(`Search backend: available ${this.qmd.debugStatus()}`);
|
|
27744
|
-
const namespaces = this.config.namespacesEnabled ? this.configuredNamespaces() : [this.config.defaultNamespace];
|
|
27745
|
-
const states = await Promise.all(
|
|
27746
|
-
namespaces.map(async (namespace) => ({
|
|
27747
|
-
namespace,
|
|
27748
|
-
state: this.config.namespacesEnabled ? await this.namespaceSearchRouter.ensureNamespaceCollection(
|
|
27749
|
-
namespace
|
|
27750
|
-
) : await this.qmd.ensureCollection(this.config.memoryDir)
|
|
27751
|
-
}))
|
|
28268
|
+
await this.transcript.initialize();
|
|
28269
|
+
await this.summarizer.initialize();
|
|
28270
|
+
if (this.sharedContext) {
|
|
28271
|
+
await this.sharedContext.ensureStructure();
|
|
28272
|
+
}
|
|
28273
|
+
if (this.compounding) {
|
|
28274
|
+
await this.compounding.ensureDirs();
|
|
28275
|
+
}
|
|
28276
|
+
try {
|
|
28277
|
+
await this.buffer.load();
|
|
28278
|
+
} catch (bufErr) {
|
|
28279
|
+
log.error(
|
|
28280
|
+
`buffer.load() failed (init gate will still open): ${bufErr}`
|
|
27752
28281
|
);
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
27756
|
-
|
|
27757
|
-
this.
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27761
|
-
|
|
27762
|
-
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
|
|
28282
|
+
this.buffer.resetToEmpty();
|
|
28283
|
+
}
|
|
28284
|
+
if (this.config.compactionResetEnabled) {
|
|
28285
|
+
try {
|
|
28286
|
+
const wsDir = this.config.workspaceDir || defaultWorkspaceDir();
|
|
28287
|
+
const files = await readdir14(wsDir).catch(() => []);
|
|
28288
|
+
for (const f of files) {
|
|
28289
|
+
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
28290
|
+
const fp = path43.join(wsDir, f);
|
|
28291
|
+
const s = await stat10(fp).catch(() => null);
|
|
28292
|
+
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
28293
|
+
await unlink7(fp).catch(() => {
|
|
28294
|
+
});
|
|
28295
|
+
log.debug(`initialize: removed stale compaction signal ${f}`);
|
|
28296
|
+
}
|
|
28297
|
+
}
|
|
28298
|
+
} catch (err) {
|
|
28299
|
+
log.debug("initialize: stale signal sweep failed:", err);
|
|
27769
28300
|
}
|
|
27770
|
-
|
|
27771
|
-
|
|
27772
|
-
|
|
28301
|
+
}
|
|
28302
|
+
try {
|
|
28303
|
+
const available = await this.qmd.probe();
|
|
28304
|
+
if (available) {
|
|
28305
|
+
log.info(`Search backend: available ${this.qmd.debugStatus()}`);
|
|
28306
|
+
const namespaces = this.config.namespacesEnabled ? this.configuredNamespaces() : [this.config.defaultNamespace];
|
|
28307
|
+
const states = await Promise.all(
|
|
28308
|
+
namespaces.map(async (namespace) => ({
|
|
28309
|
+
namespace,
|
|
28310
|
+
state: this.config.namespacesEnabled ? await this.namespaceSearchRouter.ensureNamespaceCollection(
|
|
28311
|
+
namespace
|
|
28312
|
+
) : await this.qmd.ensureCollection(this.config.memoryDir)
|
|
28313
|
+
}))
|
|
28314
|
+
);
|
|
28315
|
+
const defaultState2 = states.find(
|
|
28316
|
+
(entry) => entry.namespace === this.config.defaultNamespace
|
|
28317
|
+
)?.state ?? "unknown";
|
|
28318
|
+
if (defaultState2 === "missing") {
|
|
28319
|
+
this.qmd = new NoopSearchBackend();
|
|
27773
28320
|
log.warn(
|
|
27774
|
-
|
|
28321
|
+
"Search collection missing for Remnic memory store; disabling search retrieval for this runtime (fallback retrieval remains enabled)"
|
|
27775
28322
|
);
|
|
28323
|
+
} else if (defaultState2 === "unknown") {
|
|
28324
|
+
log.warn(
|
|
28325
|
+
"Search collection check unavailable; keeping search retrieval enabled for fail-open behavior"
|
|
28326
|
+
);
|
|
28327
|
+
} else if (defaultState2 === "skipped") {
|
|
28328
|
+
log.debug(
|
|
28329
|
+
"Search collection check skipped (remote or daemon-only mode)"
|
|
28330
|
+
);
|
|
28331
|
+
}
|
|
28332
|
+
for (const entry of states) {
|
|
28333
|
+
if (entry.namespace === this.config.defaultNamespace) continue;
|
|
28334
|
+
if (entry.state === "missing") {
|
|
28335
|
+
log.warn(
|
|
28336
|
+
`Search collection missing for namespace '${entry.namespace}'; namespace retrieval will fail open to non-search paths`
|
|
28337
|
+
);
|
|
28338
|
+
}
|
|
27776
28339
|
}
|
|
28340
|
+
} else if (this.qmd instanceof NoopSearchBackend) {
|
|
28341
|
+
log.debug(`Search backend: noop (search intentionally disabled)`);
|
|
28342
|
+
} else {
|
|
28343
|
+
log.warn(`Search backend: not available ${this.qmd.debugStatus()}`);
|
|
27777
28344
|
}
|
|
27778
|
-
}
|
|
27779
|
-
log.
|
|
27780
|
-
}
|
|
27781
|
-
|
|
28345
|
+
} catch (err) {
|
|
28346
|
+
log.error(`QMD probe/collection check failed (non-fatal): ${err}`);
|
|
28347
|
+
}
|
|
28348
|
+
if (this.resolveInit) {
|
|
28349
|
+
this.resolveInit();
|
|
28350
|
+
this.resolveInit = null;
|
|
28351
|
+
log.info("init gate opened (essential state + QMD state loaded)");
|
|
28352
|
+
}
|
|
28353
|
+
const resolveDeferred = this.resolveDeferredReady;
|
|
28354
|
+
this.resolveDeferredReady = null;
|
|
28355
|
+
this.deferredInitAbort = new AbortController();
|
|
28356
|
+
this.deferredInitialize(this.deferredInitAbort.signal).catch((err) => {
|
|
28357
|
+
log.error(`deferred initialization failed (non-fatal): ${err}`);
|
|
28358
|
+
}).finally(() => {
|
|
28359
|
+
resolveDeferred?.();
|
|
28360
|
+
});
|
|
28361
|
+
} catch (err) {
|
|
28362
|
+
if (this.resolveInit) {
|
|
28363
|
+
this.resolveInit();
|
|
28364
|
+
this.resolveInit = null;
|
|
28365
|
+
}
|
|
28366
|
+
if (this.resolveDeferredReady) {
|
|
28367
|
+
this.resolveDeferredReady();
|
|
28368
|
+
this.resolveDeferredReady = null;
|
|
28369
|
+
}
|
|
28370
|
+
throw err;
|
|
28371
|
+
}
|
|
28372
|
+
}
|
|
28373
|
+
async deferredInitialize(signal) {
|
|
28374
|
+
if (this.qmd.isAvailable() && this.config.qmdMaintenanceEnabled) {
|
|
28375
|
+
try {
|
|
28376
|
+
log.info("QMD startup sync: updating index to match current disk state");
|
|
28377
|
+
if (this.config.namespacesEnabled) {
|
|
28378
|
+
await this.namespaceSearchRouter.updateNamespaces(
|
|
28379
|
+
this.configuredNamespaces()
|
|
28380
|
+
);
|
|
28381
|
+
} else {
|
|
28382
|
+
await this.qmd.update();
|
|
28383
|
+
}
|
|
28384
|
+
log.info("QMD startup sync: complete");
|
|
28385
|
+
this.deferredSyncSucceeded = true;
|
|
28386
|
+
} catch (err) {
|
|
28387
|
+
log.warn(`QMD startup sync failed (non-fatal): ${err}`);
|
|
27782
28388
|
}
|
|
28389
|
+
} else if (!this.qmd.isAvailable()) {
|
|
28390
|
+
} else {
|
|
28391
|
+
this.deferredSyncSucceeded = true;
|
|
27783
28392
|
}
|
|
28393
|
+
if (signal.aborted) return;
|
|
27784
28394
|
const warmupPromises = [];
|
|
27785
28395
|
if (this.qmd.isAvailable()) {
|
|
27786
28396
|
const warmupNs = this.config.defaultNamespace;
|
|
@@ -27805,68 +28415,180 @@ var Orchestrator = class _Orchestrator {
|
|
|
27805
28415
|
);
|
|
27806
28416
|
}
|
|
27807
28417
|
await Promise.all(warmupPromises);
|
|
28418
|
+
if (signal.aborted) return;
|
|
28419
|
+
const cacheWarmups = [];
|
|
27808
28420
|
if (this.config.knowledgeIndexEnabled) {
|
|
27809
|
-
(
|
|
27810
|
-
|
|
27811
|
-
|
|
27812
|
-
|
|
27813
|
-
|
|
27814
|
-
|
|
27815
|
-
|
|
27816
|
-
|
|
27817
|
-
|
|
27818
|
-
|
|
28421
|
+
cacheWarmups.push(
|
|
28422
|
+
(async () => {
|
|
28423
|
+
try {
|
|
28424
|
+
const t0 = Date.now();
|
|
28425
|
+
await this.storage.buildKnowledgeIndex(this.config);
|
|
28426
|
+
log.info(`Knowledge Index warmup: complete in ${Date.now() - t0}ms`);
|
|
28427
|
+
} catch (err) {
|
|
28428
|
+
log.debug(`Knowledge Index warmup failed (non-fatal): ${err}`);
|
|
28429
|
+
}
|
|
28430
|
+
})()
|
|
28431
|
+
);
|
|
27819
28432
|
}
|
|
27820
|
-
this.storage.readAllMemories().
|
|
27821
|
-
})
|
|
27822
|
-
|
|
27823
|
-
|
|
28433
|
+
cacheWarmups.push(this.storage.readAllMemories().then(() => {
|
|
28434
|
+
}).catch(() => {
|
|
28435
|
+
}));
|
|
28436
|
+
cacheWarmups.push(this.storage.readAllEntityFiles().then(() => {
|
|
28437
|
+
}).catch(() => {
|
|
28438
|
+
}));
|
|
28439
|
+
await Promise.all(cacheWarmups);
|
|
28440
|
+
if (signal.aborted) return;
|
|
27824
28441
|
if (this.config.conversationIndexEnabled && this.conversationIndexBackend) {
|
|
27825
|
-
|
|
27826
|
-
|
|
28442
|
+
try {
|
|
28443
|
+
const init = await this.conversationIndexBackend.initialize();
|
|
28444
|
+
if (!init.enabled) {
|
|
28445
|
+
this.config.conversationIndexEnabled = false;
|
|
28446
|
+
}
|
|
28447
|
+
if (init.logLevel === "info") {
|
|
28448
|
+
log.info(init.message);
|
|
28449
|
+
} else if (init.logLevel === "warn") {
|
|
28450
|
+
log.warn(init.message);
|
|
28451
|
+
} else {
|
|
28452
|
+
log.debug(init.message);
|
|
28453
|
+
}
|
|
28454
|
+
} catch (err) {
|
|
28455
|
+
log.error(`Conversation index initialization failed (non-fatal): ${err}`);
|
|
27827
28456
|
this.config.conversationIndexEnabled = false;
|
|
27828
28457
|
}
|
|
27829
|
-
if (init.logLevel === "info") {
|
|
27830
|
-
log.info(init.message);
|
|
27831
|
-
} else if (init.logLevel === "warn") {
|
|
27832
|
-
log.warn(init.message);
|
|
27833
|
-
} else {
|
|
27834
|
-
log.debug(init.message);
|
|
27835
|
-
}
|
|
27836
28458
|
}
|
|
27837
|
-
|
|
28459
|
+
if (signal.aborted) return;
|
|
27838
28460
|
if (this.config.localLlmEnabled) {
|
|
27839
|
-
await this.validateLocalLlmModel();
|
|
27840
|
-
}
|
|
27841
|
-
if (this.config.compactionResetEnabled) {
|
|
27842
28461
|
try {
|
|
27843
|
-
|
|
27844
|
-
const files = await readdir14(wsDir).catch(() => []);
|
|
27845
|
-
for (const f of files) {
|
|
27846
|
-
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
27847
|
-
const fp = path43.join(wsDir, f);
|
|
27848
|
-
const s = await stat10(fp).catch(() => null);
|
|
27849
|
-
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
27850
|
-
await unlink7(fp).catch(() => {
|
|
27851
|
-
});
|
|
27852
|
-
log.debug(`initialize: removed stale compaction signal ${f}`);
|
|
27853
|
-
}
|
|
27854
|
-
}
|
|
28462
|
+
await this.validateLocalLlmModel();
|
|
27855
28463
|
} catch (err) {
|
|
27856
|
-
log.
|
|
28464
|
+
log.error(`Local LLM validation failed (non-fatal): ${err}`);
|
|
27857
28465
|
}
|
|
27858
28466
|
}
|
|
27859
|
-
|
|
28467
|
+
if (signal.aborted) return;
|
|
27860
28468
|
if (this.config.daySummaryEnabled) {
|
|
27861
|
-
|
|
28469
|
+
try {
|
|
28470
|
+
await this.autoRegisterDaySummaryCron();
|
|
28471
|
+
} catch (err) {
|
|
27862
28472
|
log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);
|
|
27863
|
-
}
|
|
28473
|
+
}
|
|
27864
28474
|
}
|
|
27865
28475
|
if (this.config.nightlyGovernanceCronAutoRegister) {
|
|
27866
|
-
|
|
28476
|
+
try {
|
|
28477
|
+
await this.autoRegisterNightlyGovernanceCron();
|
|
28478
|
+
} catch (err) {
|
|
27867
28479
|
log.debug(`nightly governance cron auto-register failed (non-fatal): ${err}`);
|
|
27868
|
-
}
|
|
28480
|
+
}
|
|
28481
|
+
}
|
|
28482
|
+
if (this.config.procedural?.proceduralMiningCronAutoRegister) {
|
|
28483
|
+
try {
|
|
28484
|
+
await this.autoRegisterProceduralMiningCron();
|
|
28485
|
+
} catch (err) {
|
|
28486
|
+
log.debug(`procedural mining cron auto-register failed (non-fatal): ${err}`);
|
|
28487
|
+
}
|
|
28488
|
+
}
|
|
28489
|
+
if (this.config.contradictionScan?.enabled) {
|
|
28490
|
+
try {
|
|
28491
|
+
await this.autoRegisterContradictionScanCron();
|
|
28492
|
+
} catch (err) {
|
|
28493
|
+
log.debug(`contradiction scan cron auto-register failed (non-fatal): ${err}`);
|
|
28494
|
+
}
|
|
28495
|
+
}
|
|
28496
|
+
log.info("orchestrator initialized (full \u2014 deferred steps complete)");
|
|
28497
|
+
}
|
|
28498
|
+
/**
|
|
28499
|
+
* Namespace-aware startup search sync. Re-probes QMD, ensures collections
|
|
28500
|
+
* (namespace-aware when namespacesEnabled), runs update, and warms up search.
|
|
28501
|
+
* Designed for server retry paths that run after the deferred init completes
|
|
28502
|
+
* when QMD was not available during initial startup.
|
|
28503
|
+
*
|
|
28504
|
+
* Accepts an optional AbortSignal so callers can interrupt the sync during
|
|
28505
|
+
* shutdown. The signal is checked between phases and forwarded into the QMD
|
|
28506
|
+
* update and warmup search calls so a long-running `qmd update` subprocess
|
|
28507
|
+
* is killed promptly rather than left in flight after `httpServer.stop()`.
|
|
28508
|
+
*
|
|
28509
|
+
* Returns true if the sync succeeded (QMD now available), false otherwise.
|
|
28510
|
+
*/
|
|
28511
|
+
async startupSearchSync(signal) {
|
|
28512
|
+
if (signal?.aborted) return false;
|
|
28513
|
+
const available = await this.qmd.probe();
|
|
28514
|
+
if (!available) return false;
|
|
28515
|
+
if (signal?.aborted) {
|
|
28516
|
+
log.debug("startupSearchSync: aborted after probe");
|
|
28517
|
+
return false;
|
|
27869
28518
|
}
|
|
28519
|
+
log.info(`startupSearchSync: backend now available ${this.qmd.debugStatus()}`);
|
|
28520
|
+
if (this.config.namespacesEnabled) {
|
|
28521
|
+
this.namespaceSearchRouter.clearCache();
|
|
28522
|
+
}
|
|
28523
|
+
const namespaces = this.config.namespacesEnabled ? this.configuredNamespaces() : [this.config.defaultNamespace];
|
|
28524
|
+
const states = await Promise.all(
|
|
28525
|
+
namespaces.map(async (namespace) => ({
|
|
28526
|
+
namespace,
|
|
28527
|
+
state: this.config.namespacesEnabled ? await this.namespaceSearchRouter.ensureNamespaceCollection(namespace) : await this.qmd.ensureCollection(this.config.memoryDir)
|
|
28528
|
+
}))
|
|
28529
|
+
);
|
|
28530
|
+
if (signal?.aborted) {
|
|
28531
|
+
log.debug("startupSearchSync: aborted after ensureCollection");
|
|
28532
|
+
return false;
|
|
28533
|
+
}
|
|
28534
|
+
const defaultState2 = states.find((e) => e.namespace === this.config.defaultNamespace)?.state ?? "unknown";
|
|
28535
|
+
if (defaultState2 === "missing") {
|
|
28536
|
+
if ("available" in this.qmd) {
|
|
28537
|
+
this.qmd.available = false;
|
|
28538
|
+
}
|
|
28539
|
+
this.qmd = new NoopSearchBackend();
|
|
28540
|
+
log.warn("startupSearchSync: search collection missing; disabling search (fallback retrieval remains enabled)");
|
|
28541
|
+
return false;
|
|
28542
|
+
}
|
|
28543
|
+
if (this.config.qmdMaintenanceEnabled) {
|
|
28544
|
+
try {
|
|
28545
|
+
const failTsBefore = "lastUpdateFailedAtMs" in this.qmd ? this.qmd.lastUpdateFailedAtMs : null;
|
|
28546
|
+
const hasRunTs = "lastUpdateRanAtMs" in this.qmd;
|
|
28547
|
+
if ("resetUpdateThrottles" in this.qmd) {
|
|
28548
|
+
this.qmd.resetUpdateThrottles();
|
|
28549
|
+
}
|
|
28550
|
+
log.info("startupSearchSync: updating index to match current disk state");
|
|
28551
|
+
let namespacesUpdated = 0;
|
|
28552
|
+
if (this.config.namespacesEnabled) {
|
|
28553
|
+
namespacesUpdated = await this.namespaceSearchRouter.updateNamespaces(namespaces);
|
|
28554
|
+
} else {
|
|
28555
|
+
await this.qmd.update(signal);
|
|
28556
|
+
}
|
|
28557
|
+
if (signal?.aborted) {
|
|
28558
|
+
log.debug("startupSearchSync: aborted after update");
|
|
28559
|
+
return false;
|
|
28560
|
+
}
|
|
28561
|
+
const failTsAfter = "lastUpdateFailedAtMs" in this.qmd ? this.qmd.lastUpdateFailedAtMs : null;
|
|
28562
|
+
const runTsAfter = hasRunTs ? this.qmd.lastUpdateRanAtMs : null;
|
|
28563
|
+
if (failTsAfter !== null && failTsAfter !== failTsBefore) {
|
|
28564
|
+
log.warn("startupSearchSync: update silently failed (detected via fail timestamp)");
|
|
28565
|
+
return false;
|
|
28566
|
+
}
|
|
28567
|
+
if (this.config.namespacesEnabled) {
|
|
28568
|
+
if (namespacesUpdated === 0) {
|
|
28569
|
+
log.warn("startupSearchSync: no namespace backends were eligible for update (all unavailable or collections missing)");
|
|
28570
|
+
return false;
|
|
28571
|
+
}
|
|
28572
|
+
log.info(`startupSearchSync: namespace updates succeeded (${namespacesUpdated}/${namespaces.length} namespaces updated)`);
|
|
28573
|
+
} else if (hasRunTs && runTsAfter === null) {
|
|
28574
|
+
log.warn("startupSearchSync: update was throttled/skipped (run timestamp is null after reset + update)");
|
|
28575
|
+
return false;
|
|
28576
|
+
}
|
|
28577
|
+
log.info("startupSearchSync: sync complete");
|
|
28578
|
+
} catch (err) {
|
|
28579
|
+
log.warn(`startupSearchSync: update failed: ${err}`);
|
|
28580
|
+
return false;
|
|
28581
|
+
}
|
|
28582
|
+
}
|
|
28583
|
+
if (!signal?.aborted) {
|
|
28584
|
+
try {
|
|
28585
|
+
await this.qmd.search("warmup", this.config.defaultNamespace, 1, void 0, { signal });
|
|
28586
|
+
log.info("startupSearchSync: warmup complete");
|
|
28587
|
+
} catch (err) {
|
|
28588
|
+
log.debug(`startupSearchSync: warmup search failed (non-fatal): ${err}`);
|
|
28589
|
+
}
|
|
28590
|
+
}
|
|
28591
|
+
return true;
|
|
27870
28592
|
}
|
|
27871
28593
|
/**
|
|
27872
28594
|
* Auto-register the engram-day-summary cron job in OpenClaw if it doesn't exist.
|
|
@@ -27918,6 +28640,46 @@ var Orchestrator = class _Orchestrator {
|
|
|
27918
28640
|
log.debug(`nightly governance cron auto-register error: ${err}`);
|
|
27919
28641
|
}
|
|
27920
28642
|
}
|
|
28643
|
+
async autoRegisterProceduralMiningCron() {
|
|
28644
|
+
const home = resolveHomeDir();
|
|
28645
|
+
const jobsPath = path43.join(home, ".openclaw", "cron", "jobs.json");
|
|
28646
|
+
try {
|
|
28647
|
+
if (!existsSync8(jobsPath)) {
|
|
28648
|
+
log.debug("procedural mining cron: jobs.json not found, skipping auto-register");
|
|
28649
|
+
return;
|
|
28650
|
+
}
|
|
28651
|
+
const created = await ensureProceduralMiningCron(jobsPath, {
|
|
28652
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
28653
|
+
});
|
|
28654
|
+
if (created.created) {
|
|
28655
|
+
log.info(`procedural mining cron auto-registered (${created.jobId})`);
|
|
28656
|
+
} else {
|
|
28657
|
+
log.debug("procedural mining cron already exists, skipping auto-register");
|
|
28658
|
+
}
|
|
28659
|
+
} catch (err) {
|
|
28660
|
+
log.debug(`procedural mining cron auto-register error: ${err}`);
|
|
28661
|
+
}
|
|
28662
|
+
}
|
|
28663
|
+
async autoRegisterContradictionScanCron() {
|
|
28664
|
+
const home = resolveHomeDir();
|
|
28665
|
+
const jobsPath = path43.join(home, ".openclaw", "cron", "jobs.json");
|
|
28666
|
+
try {
|
|
28667
|
+
if (!existsSync8(jobsPath)) {
|
|
28668
|
+
log.debug("contradiction scan cron: jobs.json not found, skipping auto-register");
|
|
28669
|
+
return;
|
|
28670
|
+
}
|
|
28671
|
+
const created = await ensureContradictionScanCron(jobsPath, {
|
|
28672
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
28673
|
+
});
|
|
28674
|
+
if (created.created) {
|
|
28675
|
+
log.info(`contradiction scan cron auto-registered (${created.jobId})`);
|
|
28676
|
+
} else {
|
|
28677
|
+
log.debug("contradiction scan cron already exists, skipping auto-register");
|
|
28678
|
+
}
|
|
28679
|
+
} catch (err) {
|
|
28680
|
+
log.debug(`contradiction scan cron auto-register error: ${err}`);
|
|
28681
|
+
}
|
|
28682
|
+
}
|
|
27921
28683
|
async applyBehaviorRuntimePolicy(state) {
|
|
27922
28684
|
const result = await this.policyRuntime.applyFromBehaviorState(state);
|
|
27923
28685
|
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
@@ -28045,7 +28807,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
28045
28807
|
);
|
|
28046
28808
|
return result;
|
|
28047
28809
|
}
|
|
28048
|
-
const { FallbackLlmClient: FallbackLlmClient2 } = await import("./fallback-llm-
|
|
28810
|
+
const { FallbackLlmClient: FallbackLlmClient2 } = await import("./fallback-llm-QEAPMDW7.js");
|
|
28049
28811
|
const useGateway = this.config.modelSource === "gateway";
|
|
28050
28812
|
const modelSetting = this.config.semanticConsolidationModel;
|
|
28051
28813
|
if (modelSetting === "fast" && this.fastLlm && !useGateway) {
|
|
@@ -30381,7 +31143,7 @@ ${trimmedBody}`;
|
|
|
30381
31143
|
return null;
|
|
30382
31144
|
}
|
|
30383
31145
|
try {
|
|
30384
|
-
const { retrieveCausalChains } = await import("./causal-retrieval-
|
|
31146
|
+
const { retrieveCausalChains } = await import("./causal-retrieval-3BKBXVXD.js");
|
|
30385
31147
|
const section = await retrieveCausalChains({
|
|
30386
31148
|
memoryDir: this.config.memoryDir,
|
|
30387
31149
|
causalTrajectoryStoreDir: this.config.causalTrajectoryStoreDir,
|
|
@@ -30434,7 +31196,7 @@ ${trimmedBody}`;
|
|
|
30434
31196
|
return null;
|
|
30435
31197
|
}
|
|
30436
31198
|
try {
|
|
30437
|
-
const { getCalibrationRulesForRecall, buildCalibrationRecallSection } = await import("./calibration-
|
|
31199
|
+
const { getCalibrationRulesForRecall, buildCalibrationRecallSection } = await import("./calibration-BAC7KNKR.js");
|
|
30438
31200
|
const rules = await getCalibrationRulesForRecall(this.config.memoryDir);
|
|
30439
31201
|
if (rules.length === 0) {
|
|
30440
31202
|
recordRecallSectionMetric({
|
|
@@ -31317,6 +32079,22 @@ ${formatted}`;
|
|
|
31317
32079
|
});
|
|
31318
32080
|
return section;
|
|
31319
32081
|
})();
|
|
32082
|
+
const procedureRecallPromise = (async () => {
|
|
32083
|
+
if (this.config.procedural?.enabled !== true) return null;
|
|
32084
|
+
if (!this.isRecallSectionEnabled("procedure-recall", true)) return null;
|
|
32085
|
+
try {
|
|
32086
|
+
return await buildProcedureRecallSection(
|
|
32087
|
+
profileStorage,
|
|
32088
|
+
retrievalQuery,
|
|
32089
|
+
this.config
|
|
32090
|
+
);
|
|
32091
|
+
} catch (err) {
|
|
32092
|
+
log.debug(
|
|
32093
|
+
`procedure-recall: failed open: ${err instanceof Error ? err.message : String(err)}`
|
|
32094
|
+
);
|
|
32095
|
+
return null;
|
|
32096
|
+
}
|
|
32097
|
+
})();
|
|
31320
32098
|
const compoundingPromise = observeEnrichmentPromise(
|
|
31321
32099
|
(async () => {
|
|
31322
32100
|
const t0 = Date.now();
|
|
@@ -31381,6 +32159,7 @@ ${formatted}`;
|
|
|
31381
32159
|
causalTrajectorySection,
|
|
31382
32160
|
cmcCausalChainsSection,
|
|
31383
32161
|
calibrationSection,
|
|
32162
|
+
procedureRecallSection,
|
|
31384
32163
|
trustZoneSection,
|
|
31385
32164
|
verifiedRecallSection,
|
|
31386
32165
|
verifiedRulesSection,
|
|
@@ -31402,6 +32181,7 @@ ${formatted}`;
|
|
|
31402
32181
|
["causalTraj", causalTrajectoryPromise],
|
|
31403
32182
|
["cmc", cmcRetrievalPromise],
|
|
31404
32183
|
["calibration", calibrationPromise],
|
|
32184
|
+
["procedureRecall", procedureRecallPromise],
|
|
31405
32185
|
["trustZone", trustZonePromise],
|
|
31406
32186
|
["verifiedRecall", verifiedRecallPromise],
|
|
31407
32187
|
["verifiedRules", verifiedRulesPromise],
|
|
@@ -31507,6 +32287,13 @@ ${profile}`
|
|
|
31507
32287
|
calibrationSection
|
|
31508
32288
|
);
|
|
31509
32289
|
}
|
|
32290
|
+
if (procedureRecallSection) {
|
|
32291
|
+
this.appendRecallSection(
|
|
32292
|
+
sectionBuckets,
|
|
32293
|
+
"procedure-recall",
|
|
32294
|
+
procedureRecallSection
|
|
32295
|
+
);
|
|
32296
|
+
}
|
|
31510
32297
|
if (identityContinuity) {
|
|
31511
32298
|
this.appendRecallSection(
|
|
31512
32299
|
sectionBuckets,
|
|
@@ -32551,6 +33338,87 @@ _Context: ${topQuestion.context}_`
|
|
|
32551
33338
|
}
|
|
32552
33339
|
}
|
|
32553
33340
|
}
|
|
33341
|
+
/**
|
|
33342
|
+
* Return the namespace that `ingestBulkImportBatch` writes into (#460).
|
|
33343
|
+
*
|
|
33344
|
+
* Exposed so host CLIs can snapshot the same storage root that extraction
|
|
33345
|
+
* actually writes to, avoiding the "CLI counts files at namespace A while
|
|
33346
|
+
* writes land in namespace B" footgun that a naïve
|
|
33347
|
+
* `config.defaultNamespace` snapshot could hit when a namespace policy
|
|
33348
|
+
* named `"default"` also exists.
|
|
33349
|
+
*
|
|
33350
|
+
* Today bulk-import is pinned to `config.defaultNamespace`; future
|
|
33351
|
+
* per-invocation namespace routing would thread an explicit target here
|
|
33352
|
+
* and through `ingestBulkImportBatch`.
|
|
33353
|
+
*/
|
|
33354
|
+
bulkImportWriteNamespace() {
|
|
33355
|
+
return this.config.defaultNamespace;
|
|
33356
|
+
}
|
|
33357
|
+
/**
|
|
33358
|
+
* Ingest a batch of bulk-import turns (#460). Like ingestReplayBatch, this
|
|
33359
|
+
* normalizes user/assistant turns into the extraction buffer and awaits
|
|
33360
|
+
* settlement, but it intentionally bypasses the captureMode="explicit"
|
|
33361
|
+
* gate because bulk-import is itself an explicit user action — the user
|
|
33362
|
+
* ran `bulk-import --source <name> --file ...` and would be surprised to
|
|
33363
|
+
* see the command silently no-op when capture is otherwise restricted.
|
|
33364
|
+
*
|
|
33365
|
+
* Turns with role="other" are skipped (not supported by the extraction
|
|
33366
|
+
* pipeline).
|
|
33367
|
+
*
|
|
33368
|
+
* Two design decisions worth calling out:
|
|
33369
|
+
*
|
|
33370
|
+
* - **sessionKey is truthy and per-batch-unique.**
|
|
33371
|
+
* `ThreadingManager.shouldStartNewThread` only applies the session-key
|
|
33372
|
+
* boundary check when `turn.sessionKey` is truthy (threading.ts:82);
|
|
33373
|
+
* with an empty string, imported turns could attach to the current
|
|
33374
|
+
* live thread or merge across unrelated import batches. A unique
|
|
33375
|
+
* `bulk-import:batch:<timestamp>-<rand>` key forces a fresh thread per
|
|
33376
|
+
* batch without matching common prefix/map rules in
|
|
33377
|
+
* `principalFromSessionKeyRules`. (Catch-all regex rules could still
|
|
33378
|
+
* remap the principal, but that only affects metadata provenance —
|
|
33379
|
+
* see the next point for why write routing is unaffected.)
|
|
33380
|
+
*
|
|
33381
|
+
* - **writeNamespaceOverride pins the storage target.**
|
|
33382
|
+
* We pass `writeNamespaceOverride: this.bulkImportWriteNamespace()` to
|
|
33383
|
+
* `queueBufferedExtraction`, which tells `runExtraction` to skip
|
|
33384
|
+
* `defaultNamespaceForPrincipal` and write directly into the
|
|
33385
|
+
* orchestrator's declared bulk-import write namespace. This keeps
|
|
33386
|
+
* writes deterministic even when namespace policies named `"default"`
|
|
33387
|
+
* exist alongside a different `config.defaultNamespace`, and also
|
|
33388
|
+
* guards against regex-catch-all principal rules steering bulk-import
|
|
33389
|
+
* into an unexpected tenant.
|
|
33390
|
+
*
|
|
33391
|
+
* Per-invocation namespace routing (letting callers target a namespace
|
|
33392
|
+
* other than `bulkImportWriteNamespace()`) is a separate feature tracked
|
|
33393
|
+
* as a follow-up — the hook is the `writeNamespaceOverride` option, but
|
|
33394
|
+
* the CLI surface does not yet expose a `--namespace` flag.
|
|
33395
|
+
*/
|
|
33396
|
+
async ingestBulkImportBatch(turns, options = {}) {
|
|
33397
|
+
if (!Array.isArray(turns) || turns.length === 0) return;
|
|
33398
|
+
const sessionKey = `bulk-import:batch:${Date.now().toString(36)}-` + randomBytes(6).toString("hex");
|
|
33399
|
+
const sessionTurns = [];
|
|
33400
|
+
for (const turn of turns) {
|
|
33401
|
+
if (turn.role !== "user" && turn.role !== "assistant") continue;
|
|
33402
|
+
sessionTurns.push({
|
|
33403
|
+
role: turn.role,
|
|
33404
|
+
content: turn.content,
|
|
33405
|
+
timestamp: turn.timestamp,
|
|
33406
|
+
sessionKey
|
|
33407
|
+
});
|
|
33408
|
+
}
|
|
33409
|
+
if (sessionTurns.length === 0) return;
|
|
33410
|
+
await new Promise((resolve, reject) => {
|
|
33411
|
+
void this.queueBufferedExtraction(sessionTurns, "trigger_mode", {
|
|
33412
|
+
skipDedupeCheck: true,
|
|
33413
|
+
clearBufferAfterExtraction: false,
|
|
33414
|
+
skipCharThreshold: true,
|
|
33415
|
+
bufferKey: sessionKey,
|
|
33416
|
+
extractionDeadlineMs: options.deadlineMs,
|
|
33417
|
+
writeNamespaceOverride: this.bulkImportWriteNamespace(),
|
|
33418
|
+
onTaskSettled: (err) => err ? reject(err) : resolve()
|
|
33419
|
+
}).catch(reject);
|
|
33420
|
+
});
|
|
33421
|
+
}
|
|
32554
33422
|
async observeSessionHeartbeat(sessionKey, options = {}) {
|
|
32555
33423
|
if (this.config.sessionObserverEnabled !== true) return;
|
|
32556
33424
|
if (!sessionKey || sessionKey.length === 0) return;
|
|
@@ -32617,7 +33485,8 @@ _Context: ${topQuestion.context}_`
|
|
|
32617
33485
|
skipCharThreshold: options.skipCharThreshold ?? false,
|
|
32618
33486
|
deadlineMs: options.extractionDeadlineMs,
|
|
32619
33487
|
bufferKey,
|
|
32620
|
-
abortSignal: options.abortSignal
|
|
33488
|
+
abortSignal: options.abortSignal,
|
|
33489
|
+
writeNamespaceOverride: options.writeNamespaceOverride
|
|
32621
33490
|
});
|
|
32622
33491
|
options.onTaskSettled?.();
|
|
32623
33492
|
} catch (err) {
|
|
@@ -32628,7 +33497,7 @@ _Context: ${topQuestion.context}_`
|
|
|
32628
33497
|
if (!this.queueProcessing) {
|
|
32629
33498
|
this.queueProcessing = true;
|
|
32630
33499
|
this.processQueue().catch((err) => {
|
|
32631
|
-
|
|
33500
|
+
this.logExtractionQueueFailure(err, "processor");
|
|
32632
33501
|
this.queueProcessing = false;
|
|
32633
33502
|
});
|
|
32634
33503
|
}
|
|
@@ -32686,12 +33555,38 @@ ${normalized}`).digest("hex");
|
|
|
32686
33555
|
try {
|
|
32687
33556
|
await task();
|
|
32688
33557
|
} catch (err) {
|
|
32689
|
-
|
|
33558
|
+
this.logExtractionQueueFailure(err, "task");
|
|
32690
33559
|
}
|
|
32691
33560
|
}
|
|
32692
33561
|
}
|
|
32693
33562
|
this.queueProcessing = false;
|
|
32694
33563
|
}
|
|
33564
|
+
/**
|
|
33565
|
+
* Classify + log a failure from either the per-task catch inside
|
|
33566
|
+
* `processQueue()` or the outer `processQueue().catch(...)` in
|
|
33567
|
+
* `queueBufferedExtraction()`. Issue #549: `throwIfRecallAborted`
|
|
33568
|
+
* (used throughout `runExtraction`) raises an Error whose `name` is
|
|
33569
|
+
* `"AbortError"`. That path fires when `before_reset` aborts a
|
|
33570
|
+
* queued task to avoid duplicate extraction — it is intentional
|
|
33571
|
+
* cancellation, not a failure. Downgrading the log to debug
|
|
33572
|
+
* prevents spurious `error`-level lines that routinely appear
|
|
33573
|
+
* right next to a successful `persisted: N facts, M entities` log
|
|
33574
|
+
* and that confuse operators into thinking extraction is broken.
|
|
33575
|
+
* Genuine extraction failures (network, parse, I/O) still log at
|
|
33576
|
+
* `error`.
|
|
33577
|
+
*
|
|
33578
|
+
* Source differentiates the two call sites so the log message
|
|
33579
|
+
* names the right layer (`task` vs `processor`).
|
|
33580
|
+
*/
|
|
33581
|
+
logExtractionQueueFailure(err, source) {
|
|
33582
|
+
const aborted = source === "task" ? "background extraction task aborted (session transition)" : "background extraction queue processor aborted (session transition)";
|
|
33583
|
+
const failed = source === "task" ? "background extraction task failed" : "background extraction queue processor failed";
|
|
33584
|
+
if (isAbortError(err)) {
|
|
33585
|
+
log.debug(aborted);
|
|
33586
|
+
} else {
|
|
33587
|
+
log.error(failed, err);
|
|
33588
|
+
}
|
|
33589
|
+
}
|
|
32695
33590
|
async runExtraction(turns, options = {}) {
|
|
32696
33591
|
log.debug(`running extraction on ${turns.length} turns`);
|
|
32697
33592
|
const clearBufferAfterExtraction = options.clearBufferAfterExtraction ?? true;
|
|
@@ -32703,12 +33598,12 @@ ${normalized}`).digest("hex");
|
|
|
32703
33598
|
throw new Error(`replay extraction deadline exceeded (${stage})`);
|
|
32704
33599
|
}
|
|
32705
33600
|
};
|
|
32706
|
-
const
|
|
33601
|
+
const throwIfAborted2 = (stage) => {
|
|
32707
33602
|
throwIfRecallAborted(options.abortSignal, `extraction aborted (${stage})`);
|
|
32708
33603
|
};
|
|
32709
33604
|
const clearBuffer = async (options2) => {
|
|
32710
33605
|
if (options2?.ignoreAbort !== true) {
|
|
32711
|
-
|
|
33606
|
+
throwIfAborted2("before_clear_buffer");
|
|
32712
33607
|
}
|
|
32713
33608
|
if (clearBufferAfterExtraction) {
|
|
32714
33609
|
await this.buffer.clearAfterExtraction(bufferKey);
|
|
@@ -32727,7 +33622,7 @@ ${normalized}`).digest("hex");
|
|
|
32727
33622
|
content: t.content.trim().slice(0, this.config.extractionMaxTurnChars)
|
|
32728
33623
|
})).filter((t) => t.content.length > 0);
|
|
32729
33624
|
throwIfDeadlineExceeded("before_extract");
|
|
32730
|
-
|
|
33625
|
+
throwIfAborted2("before_extract");
|
|
32731
33626
|
const userTurns = normalizedTurns.filter((t) => t.role === "user");
|
|
32732
33627
|
const totalChars = normalizedTurns.reduce(
|
|
32733
33628
|
(sum, t) => sum + t.content.length,
|
|
@@ -32743,7 +33638,7 @@ ${normalized}`).digest("hex");
|
|
|
32743
33638
|
return;
|
|
32744
33639
|
}
|
|
32745
33640
|
const principal = resolvePrincipal(sessionKey, this.config);
|
|
32746
|
-
const selfNamespace = defaultNamespaceForPrincipal(principal, this.config);
|
|
33641
|
+
const selfNamespace = typeof options.writeNamespaceOverride === "string" && options.writeNamespaceOverride.length > 0 ? options.writeNamespaceOverride : defaultNamespaceForPrincipal(principal, this.config);
|
|
32747
33642
|
const storage = await this.storageRouter.storageFor(selfNamespace);
|
|
32748
33643
|
const shouldPersistProcessedFingerprint = normalizedTurns.some(
|
|
32749
33644
|
(turn) => turn.persistProcessedFingerprint === true
|
|
@@ -32772,7 +33667,7 @@ ${normalized}`).digest("hex");
|
|
|
32772
33667
|
"extraction aborted (during_extract)"
|
|
32773
33668
|
);
|
|
32774
33669
|
throwIfDeadlineExceeded("before_persist");
|
|
32775
|
-
|
|
33670
|
+
throwIfAborted2("before_persist");
|
|
32776
33671
|
if (!result) {
|
|
32777
33672
|
log.warn("runExtraction: extraction returned null/undefined");
|
|
32778
33673
|
await clearBuffer();
|
|
@@ -33427,6 +34322,9 @@ ${normalized}`).digest("hex");
|
|
|
33427
34322
|
continue;
|
|
33428
34323
|
}
|
|
33429
34324
|
const judgeCategory = preRoutedCategories[fi] ?? f.category;
|
|
34325
|
+
if (judgeCategory === "procedure") {
|
|
34326
|
+
continue;
|
|
34327
|
+
}
|
|
33430
34328
|
const tags = Array.isArray(f.tags) ? f.tags : [];
|
|
33431
34329
|
const imp = scoreImportance(
|
|
33432
34330
|
f.content,
|
|
@@ -33482,16 +34380,6 @@ ${normalized}`).digest("hex");
|
|
|
33482
34380
|
}
|
|
33483
34381
|
fact.tags = Array.isArray(fact.tags) ? fact.tags.filter((t) => typeof t === "string") : [];
|
|
33484
34382
|
fact.confidence = typeof fact.confidence === "number" ? fact.confidence : 0.7;
|
|
33485
|
-
if (this.contentHashIndex) {
|
|
33486
|
-
const canonicalContent = citationEnabled && hasCitationForTemplate(fact.content, citationTemplate) ? stripCitationForTemplate(fact.content, citationTemplate) : fact.content;
|
|
33487
|
-
if (this.contentHashIndex.has(canonicalContent)) {
|
|
33488
|
-
log.debug(
|
|
33489
|
-
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}\u2026"`
|
|
33490
|
-
);
|
|
33491
|
-
dedupedCount++;
|
|
33492
|
-
continue;
|
|
33493
|
-
}
|
|
33494
|
-
}
|
|
33495
34383
|
let writeCategory = fact.category;
|
|
33496
34384
|
let targetStorage = storage;
|
|
33497
34385
|
let routedRuleId;
|
|
@@ -33516,11 +34404,24 @@ ${normalized}`).digest("hex");
|
|
|
33516
34404
|
);
|
|
33517
34405
|
}
|
|
33518
34406
|
}
|
|
34407
|
+
const canonicalContentForHash = citationEnabled && hasCitationForTemplate(fact.content, citationTemplate) ? stripCitationForTemplate(fact.content, citationTemplate) : fact.content;
|
|
34408
|
+
const contentHashDedupKey = writeCategory === "procedure" ? buildProcedurePersistBody(fact.content, fact.procedureSteps) : canonicalContentForHash;
|
|
34409
|
+
if (this.contentHashIndex && this.contentHashIndex.has(contentHashDedupKey)) {
|
|
34410
|
+
log.debug(
|
|
34411
|
+
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}\u2026"`
|
|
34412
|
+
);
|
|
34413
|
+
dedupedCount++;
|
|
34414
|
+
continue;
|
|
34415
|
+
}
|
|
33519
34416
|
const importance = scoreImportance(
|
|
33520
34417
|
fact.content,
|
|
33521
34418
|
writeCategory,
|
|
33522
34419
|
fact.tags
|
|
33523
34420
|
);
|
|
34421
|
+
if (writeCategory === "procedure" && this.config.procedural?.enabled !== true) {
|
|
34422
|
+
log.debug("persistExtraction: skip procedure memory (procedural.enabled is false)");
|
|
34423
|
+
continue;
|
|
34424
|
+
}
|
|
33524
34425
|
if (!isAboveImportanceThreshold(
|
|
33525
34426
|
importance.level,
|
|
33526
34427
|
this.config.extractionMinImportanceLevel
|
|
@@ -33549,6 +34450,18 @@ ${normalized}`).digest("hex");
|
|
|
33549
34450
|
}
|
|
33550
34451
|
}
|
|
33551
34452
|
}
|
|
34453
|
+
if (writeCategory === "procedure") {
|
|
34454
|
+
const procGate = validateProcedureExtraction({
|
|
34455
|
+
content: fact.content,
|
|
34456
|
+
procedureSteps: fact.procedureSteps
|
|
34457
|
+
});
|
|
34458
|
+
if (!procGate.durable) {
|
|
34459
|
+
log.debug(
|
|
34460
|
+
`extraction-procedure-gate: rejected "${fact.content.slice(0, 60)}\u2026" reason="${procGate.reason}"`
|
|
34461
|
+
);
|
|
34462
|
+
continue;
|
|
34463
|
+
}
|
|
34464
|
+
}
|
|
33552
34465
|
let pendingSemanticSkip = null;
|
|
33553
34466
|
if (this.config.semanticDedupEnabled) {
|
|
33554
34467
|
let semanticDecision;
|
|
@@ -33628,7 +34541,7 @@ ${normalized}`).digest("hex");
|
|
|
33628
34541
|
dedupedCount++;
|
|
33629
34542
|
continue;
|
|
33630
34543
|
}
|
|
33631
|
-
if (this.config.chunkingEnabled) {
|
|
34544
|
+
if (this.config.chunkingEnabled && writeCategory !== "procedure") {
|
|
33632
34545
|
let chunkResult;
|
|
33633
34546
|
if (this.config.semanticChunkingEnabled) {
|
|
33634
34547
|
try {
|
|
@@ -33823,8 +34736,9 @@ ${normalized}`).digest("hex");
|
|
|
33823
34736
|
links.push(...suggestedLinks);
|
|
33824
34737
|
}
|
|
33825
34738
|
}
|
|
33826
|
-
const memoryKind = this.config.episodeNoteModeEnabled ? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory) : void 0;
|
|
33827
|
-
const
|
|
34739
|
+
const memoryKind = writeCategory === "procedure" ? void 0 : this.config.episodeNoteModeEnabled ? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory) : void 0;
|
|
34740
|
+
const rawPersistBody = writeCategory === "procedure" ? buildProcedurePersistBody(fact.content, fact.procedureSteps) : fact.content;
|
|
34741
|
+
const citedFactContent = applyInlineCitation(rawPersistBody);
|
|
33828
34742
|
const memoryId = await targetStorage.writeMemory(
|
|
33829
34743
|
writeCategory,
|
|
33830
34744
|
citedFactContent,
|
|
@@ -33841,7 +34755,7 @@ ${normalized}`).digest("hex");
|
|
|
33841
34755
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
33842
34756
|
memoryKind,
|
|
33843
34757
|
structuredAttributes: fact.structuredAttributes,
|
|
33844
|
-
contentHashSource: fact.content
|
|
34758
|
+
contentHashSource: writeCategory === "fact" ? fact.content : void 0
|
|
33845
34759
|
}
|
|
33846
34760
|
);
|
|
33847
34761
|
if (routedRuleId) {
|
|
@@ -33942,7 +34856,8 @@ ${normalized}`).digest("hex");
|
|
|
33942
34856
|
}
|
|
33943
34857
|
if (this.contentHashIndex) {
|
|
33944
34858
|
const canonicalFactContent = citationEnabled && hasCitationForTemplate(fact.content, citationTemplate) ? stripCitationForTemplate(fact.content, citationTemplate) : fact.content;
|
|
33945
|
-
|
|
34859
|
+
const hashRegisterKey = writeCategory === "procedure" ? buildProcedurePersistBody(fact.content, fact.procedureSteps) : canonicalFactContent;
|
|
34860
|
+
this.contentHashIndex.add(hashRegisterKey);
|
|
33946
34861
|
}
|
|
33947
34862
|
}
|
|
33948
34863
|
for (const entity of entities) {
|
|
@@ -39065,8 +39980,8 @@ Best for:
|
|
|
39065
39980
|
),
|
|
39066
39981
|
category: Type.Optional(
|
|
39067
39982
|
Type.String({
|
|
39068
|
-
description: 'Category: "fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule" (default: "fact")',
|
|
39069
|
-
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule"]
|
|
39983
|
+
description: 'Category: "fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule", "procedure" (default: "fact")',
|
|
39984
|
+
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule", "procedure"]
|
|
39070
39985
|
})
|
|
39071
39986
|
),
|
|
39072
39987
|
tags: Type.Optional(
|
|
@@ -39124,7 +40039,7 @@ Best for:
|
|
|
39124
40039
|
category: Type.Optional(
|
|
39125
40040
|
Type.String({
|
|
39126
40041
|
description: "Memory category.",
|
|
39127
|
-
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule"]
|
|
40042
|
+
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule", "procedure"]
|
|
39128
40043
|
})
|
|
39129
40044
|
),
|
|
39130
40045
|
tags: Type.Optional(
|
|
@@ -40193,8 +41108,8 @@ Returns: Performance trace data with timing breakdown`,
|
|
|
40193
41108
|
}
|
|
40194
41109
|
|
|
40195
41110
|
// ../remnic-core/src/cli.ts
|
|
40196
|
-
import
|
|
40197
|
-
import { access as access5, readFile as
|
|
41111
|
+
import path72 from "path";
|
|
41112
|
+
import { access as access5, readFile as readFile46, readdir as readdir26, unlink as unlink11 } from "fs/promises";
|
|
40198
41113
|
import { createHash as createHash14 } from "crypto";
|
|
40199
41114
|
|
|
40200
41115
|
// ../remnic-core/src/transfer/export-json.ts
|
|
@@ -41071,8 +41986,8 @@ function gatherCandidates(input, warnings) {
|
|
|
41071
41986
|
const record = rec;
|
|
41072
41987
|
const content = typeof record.content === "string" ? record.content : null;
|
|
41073
41988
|
if (!content) continue;
|
|
41074
|
-
const
|
|
41075
|
-
if (!
|
|
41989
|
+
const path100 = typeof record.path === "string" ? record.path : "";
|
|
41990
|
+
if (!path100.startsWith("transcripts/") && !path100.includes("/transcripts/")) continue;
|
|
41076
41991
|
rows.push(...parseJsonl(content, warnings));
|
|
41077
41992
|
}
|
|
41078
41993
|
return rows;
|
|
@@ -41127,6 +42042,158 @@ var openclawReplayNormalizer = {
|
|
|
41127
42042
|
}
|
|
41128
42043
|
};
|
|
41129
42044
|
|
|
42045
|
+
// ../remnic-core/src/bulk-import/types.ts
|
|
42046
|
+
var VALID_ROLES2 = /* @__PURE__ */ new Set(["user", "assistant", "other"]);
|
|
42047
|
+
function isImportRole(value) {
|
|
42048
|
+
return typeof value === "string" && VALID_ROLES2.has(value);
|
|
42049
|
+
}
|
|
42050
|
+
function parseIsoTimestamp2(value) {
|
|
42051
|
+
return parseIsoOffsetTimestamp(value);
|
|
42052
|
+
}
|
|
42053
|
+
function validateImportTurn(turn, index) {
|
|
42054
|
+
const issues = [];
|
|
42055
|
+
if (!turn || typeof turn !== "object") {
|
|
42056
|
+
issues.push({
|
|
42057
|
+
code: "turn.invalid",
|
|
42058
|
+
message: "Import turn must be an object.",
|
|
42059
|
+
index
|
|
42060
|
+
});
|
|
42061
|
+
return issues;
|
|
42062
|
+
}
|
|
42063
|
+
if (!isImportRole(turn.role)) {
|
|
42064
|
+
issues.push({
|
|
42065
|
+
code: "turn.role.invalid",
|
|
42066
|
+
message: `Import turn role must be 'user', 'assistant', or 'other', received '${String(turn.role)}'.`,
|
|
42067
|
+
index
|
|
42068
|
+
});
|
|
42069
|
+
}
|
|
42070
|
+
if (!turn.content || typeof turn.content !== "string" || turn.content.trim().length === 0) {
|
|
42071
|
+
issues.push({
|
|
42072
|
+
code: "turn.content.invalid",
|
|
42073
|
+
message: "Import turn content must be a non-empty string.",
|
|
42074
|
+
index
|
|
42075
|
+
});
|
|
42076
|
+
}
|
|
42077
|
+
if (!turn.timestamp || typeof turn.timestamp !== "string" || parseIsoTimestamp2(turn.timestamp) === null) {
|
|
42078
|
+
issues.push({
|
|
42079
|
+
code: "turn.timestamp.invalid",
|
|
42080
|
+
message: `Import turn timestamp must be a valid ISO timestamp, received '${String(turn.timestamp)}'.`,
|
|
42081
|
+
index
|
|
42082
|
+
});
|
|
42083
|
+
}
|
|
42084
|
+
return issues;
|
|
42085
|
+
}
|
|
42086
|
+
|
|
42087
|
+
// ../remnic-core/src/bulk-import/registry.ts
|
|
42088
|
+
var adapters = /* @__PURE__ */ new Map();
|
|
42089
|
+
function registerBulkImportSource(adapter) {
|
|
42090
|
+
if (!adapter || typeof adapter !== "object") {
|
|
42091
|
+
throw new Error("bulk-import adapter must be an object");
|
|
42092
|
+
}
|
|
42093
|
+
if (!adapter.name || typeof adapter.name !== "string" || adapter.name.trim().length === 0) {
|
|
42094
|
+
throw new Error("bulk-import adapter name must be a non-empty string");
|
|
42095
|
+
}
|
|
42096
|
+
if (typeof adapter.parse !== "function") {
|
|
42097
|
+
throw new Error(
|
|
42098
|
+
`bulk-import adapter '${adapter.name}' must have a parse function`
|
|
42099
|
+
);
|
|
42100
|
+
}
|
|
42101
|
+
const key = adapter.name.trim();
|
|
42102
|
+
if (adapters.has(key)) {
|
|
42103
|
+
throw new Error(
|
|
42104
|
+
`bulk-import source adapter '${key}' is already registered`
|
|
42105
|
+
);
|
|
42106
|
+
}
|
|
42107
|
+
const normalized = adapter.name === key ? adapter : { ...adapter, name: key };
|
|
42108
|
+
adapters.set(key, normalized);
|
|
42109
|
+
}
|
|
42110
|
+
function getBulkImportSource(name) {
|
|
42111
|
+
if (typeof name !== "string") return void 0;
|
|
42112
|
+
const key = name.trim();
|
|
42113
|
+
if (key.length === 0) return void 0;
|
|
42114
|
+
return adapters.get(key);
|
|
42115
|
+
}
|
|
42116
|
+
function listBulkImportSources() {
|
|
42117
|
+
return [...adapters.keys()];
|
|
42118
|
+
}
|
|
42119
|
+
|
|
42120
|
+
// ../remnic-core/src/bulk-import/pipeline.ts
|
|
42121
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
42122
|
+
var MIN_BATCH_SIZE = 1;
|
|
42123
|
+
var MAX_BATCH_SIZE = 1e3;
|
|
42124
|
+
function validateBatchSize(value) {
|
|
42125
|
+
if (value === void 0) return DEFAULT_BATCH_SIZE;
|
|
42126
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
42127
|
+
throw new Error(
|
|
42128
|
+
`batchSize must be a finite number, received ${String(value)}`
|
|
42129
|
+
);
|
|
42130
|
+
}
|
|
42131
|
+
if (!Number.isInteger(value)) {
|
|
42132
|
+
throw new Error(
|
|
42133
|
+
`batchSize must be an integer, received ${value}`
|
|
42134
|
+
);
|
|
42135
|
+
}
|
|
42136
|
+
if (value < MIN_BATCH_SIZE || value > MAX_BATCH_SIZE) {
|
|
42137
|
+
throw new Error(
|
|
42138
|
+
`batchSize must be between ${MIN_BATCH_SIZE} and ${MAX_BATCH_SIZE}, received ${value}`
|
|
42139
|
+
);
|
|
42140
|
+
}
|
|
42141
|
+
return value;
|
|
42142
|
+
}
|
|
42143
|
+
async function runBulkImportPipeline(source, options = {}, processBatch) {
|
|
42144
|
+
const batchSize = validateBatchSize(options.batchSize);
|
|
42145
|
+
const dryRun = options.dryRun === true;
|
|
42146
|
+
const result = {
|
|
42147
|
+
memoriesCreated: 0,
|
|
42148
|
+
duplicatesSkipped: 0,
|
|
42149
|
+
entitiesCreated: 0,
|
|
42150
|
+
turnsProcessed: 0,
|
|
42151
|
+
batchesProcessed: 0,
|
|
42152
|
+
errors: []
|
|
42153
|
+
};
|
|
42154
|
+
const turns = source.turns;
|
|
42155
|
+
if (!turns || turns.length === 0) {
|
|
42156
|
+
return result;
|
|
42157
|
+
}
|
|
42158
|
+
const validTurns = [];
|
|
42159
|
+
for (let i = 0; i < turns.length; i += 1) {
|
|
42160
|
+
const issues = validateImportTurn(turns[i], i);
|
|
42161
|
+
if (issues.length > 0) {
|
|
42162
|
+
const error = {
|
|
42163
|
+
batchIndex: -1,
|
|
42164
|
+
message: issues.map((iss) => iss.message).join("; ")
|
|
42165
|
+
};
|
|
42166
|
+
result.errors.push(error);
|
|
42167
|
+
} else {
|
|
42168
|
+
validTurns.push(turns[i]);
|
|
42169
|
+
}
|
|
42170
|
+
}
|
|
42171
|
+
if (dryRun) {
|
|
42172
|
+
result.turnsProcessed = validTurns.length;
|
|
42173
|
+
result.batchesProcessed = validTurns.length > 0 ? Math.ceil(validTurns.length / batchSize) : 0;
|
|
42174
|
+
return result;
|
|
42175
|
+
}
|
|
42176
|
+
let batchIndex = 0;
|
|
42177
|
+
for (let i = 0; i < validTurns.length; i += batchSize) {
|
|
42178
|
+
const batch = validTurns.slice(i, i + batchSize);
|
|
42179
|
+
try {
|
|
42180
|
+
const batchResult = await processBatch(batch);
|
|
42181
|
+
result.memoriesCreated += batchResult.memoriesCreated;
|
|
42182
|
+
result.duplicatesSkipped += batchResult.duplicatesSkipped;
|
|
42183
|
+
if (typeof batchResult.entitiesCreated === "number") {
|
|
42184
|
+
result.entitiesCreated += batchResult.entitiesCreated;
|
|
42185
|
+
}
|
|
42186
|
+
} catch (err) {
|
|
42187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42188
|
+
result.errors.push({ batchIndex, message });
|
|
42189
|
+
}
|
|
42190
|
+
result.turnsProcessed += batch.length;
|
|
42191
|
+
result.batchesProcessed += 1;
|
|
42192
|
+
batchIndex += 1;
|
|
42193
|
+
}
|
|
42194
|
+
return result;
|
|
42195
|
+
}
|
|
42196
|
+
|
|
41130
42197
|
// ../remnic-core/src/maintenance/archive-observations.ts
|
|
41131
42198
|
import path55 from "path";
|
|
41132
42199
|
import { mkdir as mkdir40, readdir as readdir19, readFile as readFile33, unlink as unlink8, writeFile as writeFile37 } from "fs/promises";
|
|
@@ -44475,6 +45542,102 @@ function sleep2(ms) {
|
|
|
44475
45542
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44476
45543
|
}
|
|
44477
45544
|
|
|
45545
|
+
// ../remnic-core/src/procedural/procedure-miner.ts
|
|
45546
|
+
var PROCEDURE_CLUSTER_ATTR_MAX = 500;
|
|
45547
|
+
function clusterKey(record) {
|
|
45548
|
+
const goal = record.goal.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 120);
|
|
45549
|
+
const refs = [...record.entityRefs ?? []].map((r) => r.trim().toLowerCase()).sort();
|
|
45550
|
+
return `${goal}|${refs.join(",")}`;
|
|
45551
|
+
}
|
|
45552
|
+
function successRate(group) {
|
|
45553
|
+
if (group.length === 0) return 0;
|
|
45554
|
+
const ok = group.filter((g) => g.outcomeKind === "success" || g.outcomeKind === "partial").length;
|
|
45555
|
+
return ok / group.length;
|
|
45556
|
+
}
|
|
45557
|
+
function pseudoStepsFromCluster(group) {
|
|
45558
|
+
const sentences = [];
|
|
45559
|
+
const pushUnique = (raw) => {
|
|
45560
|
+
const t = raw.trim();
|
|
45561
|
+
if (t.length < 8) return;
|
|
45562
|
+
if (!sentences.includes(t)) sentences.push(t);
|
|
45563
|
+
};
|
|
45564
|
+
for (const g of group) {
|
|
45565
|
+
const parts = [g.actionSummary, g.observationSummary, g.outcomeSummary].join(" ").split(/[.!?]\s+|;|\n+/).map((s) => s.trim()).filter((s) => s.length > 12);
|
|
45566
|
+
for (const p of parts) pushUnique(p);
|
|
45567
|
+
if (sentences.length >= 5) break;
|
|
45568
|
+
}
|
|
45569
|
+
if (sentences.length < 2 && group[0]) {
|
|
45570
|
+
pushUnique(`${group[0].goal.trim()} \u2014 confirm prerequisites and context.`);
|
|
45571
|
+
pushUnique("Execute the planned actions, then record the outcome.");
|
|
45572
|
+
}
|
|
45573
|
+
return sentences.slice(0, 6).map((intent, i) => ({
|
|
45574
|
+
order: i + 1,
|
|
45575
|
+
intent
|
|
45576
|
+
}));
|
|
45577
|
+
}
|
|
45578
|
+
async function hasExistingClusterWrite(storage, cluster) {
|
|
45579
|
+
const clusterKey2 = cluster.slice(0, PROCEDURE_CLUSTER_ATTR_MAX);
|
|
45580
|
+
const memories = await storage.readAllMemories();
|
|
45581
|
+
for (const m of memories) {
|
|
45582
|
+
if (m.frontmatter.category !== "procedure") continue;
|
|
45583
|
+
const c = m.frontmatter.structuredAttributes?.procedure_cluster;
|
|
45584
|
+
if (c === clusterKey2) return true;
|
|
45585
|
+
}
|
|
45586
|
+
return false;
|
|
45587
|
+
}
|
|
45588
|
+
async function runProcedureMining(options) {
|
|
45589
|
+
const cfg = options.config.procedural;
|
|
45590
|
+
if (!cfg?.enabled) {
|
|
45591
|
+
return { clustersProcessed: 0, proceduresWritten: 0, skippedReason: "procedural_disabled" };
|
|
45592
|
+
}
|
|
45593
|
+
if (cfg.minOccurrences <= 0) {
|
|
45594
|
+
return { clustersProcessed: 0, proceduresWritten: 0, skippedReason: "minOccurrences_zero" };
|
|
45595
|
+
}
|
|
45596
|
+
const trajectoryDir = typeof options.config.causalTrajectoryStoreDir === "string" && options.config.causalTrajectoryStoreDir.trim().length > 0 ? options.config.causalTrajectoryStoreDir.trim() : void 0;
|
|
45597
|
+
const { trajectories } = await readCausalTrajectoryRecords({
|
|
45598
|
+
memoryDir: options.memoryDir,
|
|
45599
|
+
causalTrajectoryStoreDir: trajectoryDir
|
|
45600
|
+
});
|
|
45601
|
+
const recent = filterTrajectoriesByLookbackDays(trajectories, cfg.lookbackDays);
|
|
45602
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
45603
|
+
for (const t of recent) {
|
|
45604
|
+
const key = clusterKey(t);
|
|
45605
|
+
const arr = clusters.get(key) ?? [];
|
|
45606
|
+
arr.push(t);
|
|
45607
|
+
clusters.set(key, arr);
|
|
45608
|
+
}
|
|
45609
|
+
let clustersProcessed = 0;
|
|
45610
|
+
let proceduresWritten = 0;
|
|
45611
|
+
for (const [key, group] of clusters) {
|
|
45612
|
+
if (group.length < cfg.minOccurrences) continue;
|
|
45613
|
+
const rate = successRate(group);
|
|
45614
|
+
if (rate < cfg.successFloor) continue;
|
|
45615
|
+
clustersProcessed += 1;
|
|
45616
|
+
if (await hasExistingClusterWrite(options.storage, key)) {
|
|
45617
|
+
log.debug(`procedure-miner: skip duplicate cluster key=${key.slice(0, 40)}\u2026`);
|
|
45618
|
+
continue;
|
|
45619
|
+
}
|
|
45620
|
+
const steps = normalizeProcedureSteps(pseudoStepsFromCluster(group));
|
|
45621
|
+
if (steps.length < 2) continue;
|
|
45622
|
+
const title = `When you work on goals like: ${group[0].goal.trim().slice(0, 140)}`;
|
|
45623
|
+
const body = buildProcedurePersistBody(title, steps);
|
|
45624
|
+
const promote = cfg.autoPromoteEnabled === true && group.length >= cfg.autoPromoteOccurrences && rate >= cfg.successFloor;
|
|
45625
|
+
await options.storage.writeMemory("procedure", body, {
|
|
45626
|
+
source: "procedure-miner",
|
|
45627
|
+
status: promote ? "active" : "pending_review",
|
|
45628
|
+
tags: ["procedure-miner", "causal-trajectory"],
|
|
45629
|
+
structuredAttributes: {
|
|
45630
|
+
procedure_cluster: key.slice(0, PROCEDURE_CLUSTER_ATTR_MAX),
|
|
45631
|
+
trajectory_ids: group.map((g) => g.trajectoryId).join(",").slice(0, 1900),
|
|
45632
|
+
trajectory_count: String(group.length),
|
|
45633
|
+
success_rate: rate.toFixed(4)
|
|
45634
|
+
}
|
|
45635
|
+
});
|
|
45636
|
+
proceduresWritten += 1;
|
|
45637
|
+
}
|
|
45638
|
+
return { clustersProcessed, proceduresWritten };
|
|
45639
|
+
}
|
|
45640
|
+
|
|
44478
45641
|
// ../remnic-core/src/briefing.ts
|
|
44479
45642
|
import { readFile as readFile41 } from "fs/promises";
|
|
44480
45643
|
import path67 from "path";
|
|
@@ -46186,6 +47349,25 @@ var EngramAccessService = class {
|
|
|
46186
47349
|
reportPath: result.reportPath
|
|
46187
47350
|
};
|
|
46188
47351
|
}
|
|
47352
|
+
async procedureMiningRun(request, principal) {
|
|
47353
|
+
const resolvedNamespace = this.resolveWritableNamespace(
|
|
47354
|
+
request.namespace,
|
|
47355
|
+
void 0,
|
|
47356
|
+
request.authenticatedPrincipal ?? principal
|
|
47357
|
+
);
|
|
47358
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
47359
|
+
const result = await runProcedureMining({
|
|
47360
|
+
memoryDir: storage.dir,
|
|
47361
|
+
storage,
|
|
47362
|
+
config: this.orchestrator.config
|
|
47363
|
+
});
|
|
47364
|
+
return {
|
|
47365
|
+
namespace: resolvedNamespace,
|
|
47366
|
+
clustersProcessed: result.clustersProcessed,
|
|
47367
|
+
proceduresWritten: result.proceduresWritten,
|
|
47368
|
+
skippedReason: result.skippedReason
|
|
47369
|
+
};
|
|
47370
|
+
}
|
|
46189
47371
|
async trustZoneStatus(namespace, principal) {
|
|
46190
47372
|
const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
|
|
46191
47373
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
@@ -47066,6 +48248,25 @@ ${next}`);
|
|
|
47066
48248
|
}
|
|
47067
48249
|
return { submitted: memoryIds.length, matched: matchedIds.length };
|
|
47068
48250
|
}
|
|
48251
|
+
// ── Contradiction Review (issue #520) ──────────────────────────────────────
|
|
48252
|
+
get memoryDir() {
|
|
48253
|
+
return this.orchestrator.config.memoryDir;
|
|
48254
|
+
}
|
|
48255
|
+
get storageRef() {
|
|
48256
|
+
return this.orchestrator.storage;
|
|
48257
|
+
}
|
|
48258
|
+
get configRef() {
|
|
48259
|
+
return this.orchestrator.config;
|
|
48260
|
+
}
|
|
48261
|
+
get localLlmRef() {
|
|
48262
|
+
return this.orchestrator.localLlm ?? null;
|
|
48263
|
+
}
|
|
48264
|
+
get fallbackLlmRef() {
|
|
48265
|
+
return null;
|
|
48266
|
+
}
|
|
48267
|
+
get embeddingLookupRef() {
|
|
48268
|
+
return void 0;
|
|
48269
|
+
}
|
|
47069
48270
|
};
|
|
47070
48271
|
|
|
47071
48272
|
// ../remnic-core/src/access-http.ts
|
|
@@ -47214,6 +48415,17 @@ var EngramMcpServer = class {
|
|
|
47214
48415
|
additionalProperties: false
|
|
47215
48416
|
}
|
|
47216
48417
|
},
|
|
48418
|
+
{
|
|
48419
|
+
name: "engram.procedure_mining_run",
|
|
48420
|
+
description: "Run procedural memory mining from causal trajectories (issue #519). Respects procedural.enabled; writes under procedures/ when clusters qualify.",
|
|
48421
|
+
inputSchema: {
|
|
48422
|
+
type: "object",
|
|
48423
|
+
properties: {
|
|
48424
|
+
namespace: { type: "string" }
|
|
48425
|
+
},
|
|
48426
|
+
additionalProperties: false
|
|
48427
|
+
}
|
|
48428
|
+
},
|
|
47217
48429
|
{
|
|
47218
48430
|
name: "engram.memory_get",
|
|
47219
48431
|
description: "Fetch one Remnic memory by id.",
|
|
@@ -47803,7 +49015,45 @@ var EngramMcpServer = class {
|
|
|
47803
49015
|
},
|
|
47804
49016
|
additionalProperties: false
|
|
47805
49017
|
}
|
|
47806
|
-
}] : []
|
|
49018
|
+
}] : [],
|
|
49019
|
+
// ── Contradiction Review (issue #520) ────────────────────────────────
|
|
49020
|
+
{
|
|
49021
|
+
name: "engram.review_list",
|
|
49022
|
+
description: "List contradiction review items pending user resolution.",
|
|
49023
|
+
inputSchema: {
|
|
49024
|
+
type: "object",
|
|
49025
|
+
properties: {
|
|
49026
|
+
filter: { type: "string", enum: ["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"], description: "Filter by verdict type. Default: unresolved." },
|
|
49027
|
+
namespace: { type: "string" },
|
|
49028
|
+
limit: { type: "number", description: "Max items to return (default 50)." }
|
|
49029
|
+
},
|
|
49030
|
+
additionalProperties: false
|
|
49031
|
+
}
|
|
49032
|
+
},
|
|
49033
|
+
{
|
|
49034
|
+
name: "engram.review_resolve",
|
|
49035
|
+
description: "Resolve a contradiction pair with a chosen verb.",
|
|
49036
|
+
inputSchema: {
|
|
49037
|
+
type: "object",
|
|
49038
|
+
properties: {
|
|
49039
|
+
pairId: { type: "string", description: "The contradiction pair ID to resolve." },
|
|
49040
|
+
verb: { type: "string", enum: ["keep-a", "keep-b", "merge", "both-valid", "needs-more-context"], description: "Resolution action." }
|
|
49041
|
+
},
|
|
49042
|
+
required: ["pairId", "verb"],
|
|
49043
|
+
additionalProperties: false
|
|
49044
|
+
}
|
|
49045
|
+
},
|
|
49046
|
+
{
|
|
49047
|
+
name: "engram.contradiction_scan_run",
|
|
49048
|
+
description: "Run an on-demand contradiction scan over the memory corpus.",
|
|
49049
|
+
inputSchema: {
|
|
49050
|
+
type: "object",
|
|
49051
|
+
properties: {
|
|
49052
|
+
namespace: { type: "string" }
|
|
49053
|
+
},
|
|
49054
|
+
additionalProperties: false
|
|
49055
|
+
}
|
|
49056
|
+
}
|
|
47807
49057
|
].flatMap((tool) => withToolAliases(tool));
|
|
47808
49058
|
}
|
|
47809
49059
|
service;
|
|
@@ -48093,6 +49343,14 @@ ${body}`;
|
|
|
48093
49343
|
batchSize: typeof args.batchSize === "number" && Number.isFinite(args.batchSize) ? args.batchSize : void 0,
|
|
48094
49344
|
authenticatedPrincipal: effectivePrincipal
|
|
48095
49345
|
}, effectivePrincipal);
|
|
49346
|
+
case "engram.procedure_mining_run":
|
|
49347
|
+
return this.service.procedureMiningRun(
|
|
49348
|
+
{
|
|
49349
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
49350
|
+
authenticatedPrincipal: effectivePrincipal
|
|
49351
|
+
},
|
|
49352
|
+
effectivePrincipal
|
|
49353
|
+
);
|
|
48096
49354
|
case "engram.memory_get":
|
|
48097
49355
|
return this.service.memoryGet(
|
|
48098
49356
|
typeof args.memoryId === "string" ? args.memoryId : "",
|
|
@@ -48402,6 +49660,39 @@ ${body}`;
|
|
|
48402
49660
|
principal: effectivePrincipal
|
|
48403
49661
|
});
|
|
48404
49662
|
}
|
|
49663
|
+
// ── Contradiction Review (issue #520) ──────────────────────────────────
|
|
49664
|
+
case "engram.review_list":
|
|
49665
|
+
case "remnic.review_list": {
|
|
49666
|
+
const { listPairs } = await import("./contradiction-review-SVGBS3V5.js");
|
|
49667
|
+
const filter = typeof args.filter === "string" ? args.filter : "unresolved";
|
|
49668
|
+
const ns = typeof args.namespace === "string" ? args.namespace : void 0;
|
|
49669
|
+
const limit = typeof args.limit === "number" ? args.limit : 50;
|
|
49670
|
+
return listPairs(this.service.memoryDir, { filter, namespace: ns, limit });
|
|
49671
|
+
}
|
|
49672
|
+
case "engram.review_resolve":
|
|
49673
|
+
case "remnic.review_resolve": {
|
|
49674
|
+
const pairId = typeof args.pairId === "string" ? args.pairId : "";
|
|
49675
|
+
const verb = typeof args.verb === "string" ? args.verb : "";
|
|
49676
|
+
if (!pairId) throw new Error("pairId is required");
|
|
49677
|
+
if (!verb) throw new Error("verb is required");
|
|
49678
|
+
const { isValidResolutionVerb } = await import("./resolution-YITUVUTH.js");
|
|
49679
|
+
if (!isValidResolutionVerb(verb)) throw new Error(`Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context`);
|
|
49680
|
+
const { executeResolution } = await import("./resolution-YITUVUTH.js");
|
|
49681
|
+
return executeResolution(this.service.memoryDir, this.service.storageRef, pairId, verb);
|
|
49682
|
+
}
|
|
49683
|
+
case "engram.contradiction_scan_run":
|
|
49684
|
+
case "remnic.contradiction_scan_run": {
|
|
49685
|
+
const { runContradictionScan } = await import("./contradiction-scan-LRRLWUOS.js");
|
|
49686
|
+
return runContradictionScan({
|
|
49687
|
+
storage: this.service.storageRef,
|
|
49688
|
+
config: this.service.configRef,
|
|
49689
|
+
memoryDir: this.service.memoryDir,
|
|
49690
|
+
embeddingLookup: this.service.embeddingLookupRef,
|
|
49691
|
+
localLlm: this.service.localLlmRef,
|
|
49692
|
+
fallbackLlm: this.service.fallbackLlmRef,
|
|
49693
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0
|
|
49694
|
+
});
|
|
49695
|
+
}
|
|
48405
49696
|
default:
|
|
48406
49697
|
throw new Error(`unknown tool: ${name}`);
|
|
48407
49698
|
}
|
|
@@ -48458,7 +49749,8 @@ var categorySchema = external_exports.enum([
|
|
|
48458
49749
|
"commitment",
|
|
48459
49750
|
"moment",
|
|
48460
49751
|
"skill",
|
|
48461
|
-
"rule"
|
|
49752
|
+
"rule",
|
|
49753
|
+
"procedure"
|
|
48462
49754
|
]).optional();
|
|
48463
49755
|
var confidenceSchema = external_exports.number().min(0).max(1).optional();
|
|
48464
49756
|
var tagsSchema = external_exports.array(external_exports.string().max(256)).max(50).optional();
|
|
@@ -48657,8 +49949,8 @@ var HermesAdapter = class {
|
|
|
48657
49949
|
// ../remnic-core/src/adapters/registry.ts
|
|
48658
49950
|
var AdapterRegistry = class {
|
|
48659
49951
|
adapters;
|
|
48660
|
-
constructor(
|
|
48661
|
-
this.adapters =
|
|
49952
|
+
constructor(adapters2) {
|
|
49953
|
+
this.adapters = adapters2 ?? [
|
|
48662
49954
|
new HermesAdapter(),
|
|
48663
49955
|
new ReplitAdapter(),
|
|
48664
49956
|
new CodexAdapter(),
|
|
@@ -49248,6 +50540,67 @@ var EngramAccessHttpServer = class {
|
|
|
49248
50540
|
});
|
|
49249
50541
|
return;
|
|
49250
50542
|
}
|
|
50543
|
+
if (req.method === "GET" && pathname === "/engram/v1/review/contradictions") {
|
|
50544
|
+
const VALID_FILTERS = /* @__PURE__ */ new Set(["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"]);
|
|
50545
|
+
const rawFilter = parsed.searchParams.get("filter") ?? "unresolved";
|
|
50546
|
+
if (!VALID_FILTERS.has(rawFilter)) {
|
|
50547
|
+
this.respondJson(res, 400, { error: `Invalid filter '${rawFilter}'. Valid: ${[...VALID_FILTERS].join(", ")}` });
|
|
50548
|
+
return;
|
|
50549
|
+
}
|
|
50550
|
+
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
50551
|
+
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
|
|
50552
|
+
const { listPairs } = await import("./contradiction-review-SVGBS3V5.js");
|
|
50553
|
+
const result = listPairs(this.service.memoryDir, {
|
|
50554
|
+
filter: rawFilter,
|
|
50555
|
+
namespace,
|
|
50556
|
+
limit: Number.isFinite(limitRaw) ? limitRaw : 50
|
|
50557
|
+
});
|
|
50558
|
+
this.respondJson(res, 200, result);
|
|
50559
|
+
return;
|
|
50560
|
+
}
|
|
50561
|
+
if (req.method === "GET" && pathname.startsWith("/engram/v1/review/contradictions/")) {
|
|
50562
|
+
const pairId = pathname.split("/").pop() ?? "";
|
|
50563
|
+
const { readPair } = await import("./contradiction-review-SVGBS3V5.js");
|
|
50564
|
+
const pair = readPair(this.service.memoryDir, pairId);
|
|
50565
|
+
if (!pair) {
|
|
50566
|
+
this.respondJson(res, 404, { error: "pair_not_found" });
|
|
50567
|
+
return;
|
|
50568
|
+
}
|
|
50569
|
+
this.respondJson(res, 200, pair);
|
|
50570
|
+
return;
|
|
50571
|
+
}
|
|
50572
|
+
if (req.method === "POST" && pathname === "/engram/v1/review/resolve") {
|
|
50573
|
+
const body = await this.readJsonBody(req);
|
|
50574
|
+
const pairId = typeof body.pairId === "string" ? body.pairId : "";
|
|
50575
|
+
const verb = typeof body.verb === "string" ? body.verb : "";
|
|
50576
|
+
if (!pairId || !verb) {
|
|
50577
|
+
this.respondJson(res, 400, { error: "pairId and verb are required" });
|
|
50578
|
+
return;
|
|
50579
|
+
}
|
|
50580
|
+
const { isValidResolutionVerb, executeResolution } = await import("./resolution-YITUVUTH.js");
|
|
50581
|
+
if (!isValidResolutionVerb(verb)) {
|
|
50582
|
+
this.respondJson(res, 400, { error: `Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context` });
|
|
50583
|
+
return;
|
|
50584
|
+
}
|
|
50585
|
+
const result = await executeResolution(this.service.memoryDir, this.service.storageRef, pairId, verb);
|
|
50586
|
+
this.respondJson(res, 200, result);
|
|
50587
|
+
return;
|
|
50588
|
+
}
|
|
50589
|
+
if (req.method === "POST" && pathname === "/engram/v1/contradiction-scan") {
|
|
50590
|
+
const body = await this.readJsonBody(req);
|
|
50591
|
+
const { runContradictionScan } = await import("./contradiction-scan-LRRLWUOS.js");
|
|
50592
|
+
const result = await runContradictionScan({
|
|
50593
|
+
storage: this.service.storageRef,
|
|
50594
|
+
config: this.service.configRef,
|
|
50595
|
+
memoryDir: this.service.memoryDir,
|
|
50596
|
+
embeddingLookup: this.service.embeddingLookupRef,
|
|
50597
|
+
localLlm: this.service.localLlmRef,
|
|
50598
|
+
fallbackLlm: this.service.fallbackLlmRef,
|
|
50599
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0
|
|
50600
|
+
});
|
|
50601
|
+
this.respondJson(res, 200, result);
|
|
50602
|
+
return;
|
|
50603
|
+
}
|
|
49251
50604
|
this.respondJson(res, 404, { error: "not_found", code: "not_found" });
|
|
49252
50605
|
}
|
|
49253
50606
|
async handleMcpRequest(req, res) {
|
|
@@ -50130,6 +51483,10 @@ async function promoteSemanticRuleFromMemory(options) {
|
|
|
50130
51483
|
return report;
|
|
50131
51484
|
}
|
|
50132
51485
|
|
|
51486
|
+
// ../remnic-core/src/training-export/converter.ts
|
|
51487
|
+
import { lstat as lstat2, readdir as readdir25, readFile as readFile45, realpath as realpath3 } from "fs/promises";
|
|
51488
|
+
import path71 from "path";
|
|
51489
|
+
|
|
50133
51490
|
// ../remnic-core/src/cli.ts
|
|
50134
51491
|
function rankCandidateForKeep(a, b) {
|
|
50135
51492
|
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
@@ -50320,7 +51677,7 @@ async function runRepairMemoryProjectionCliCommand(options) {
|
|
|
50320
51677
|
});
|
|
50321
51678
|
}
|
|
50322
51679
|
async function runMemoryTimelineCliCommand(options) {
|
|
50323
|
-
const storage = new (await import("./storage-
|
|
51680
|
+
const storage = new (await import("./storage-BA6OBLMK.js")).StorageManager(options.memoryDir);
|
|
50324
51681
|
return storage.getMemoryTimeline(options.memoryId, options.limit);
|
|
50325
51682
|
}
|
|
50326
51683
|
async function runMemoryGovernanceCliCommand(options) {
|
|
@@ -50348,7 +51705,7 @@ async function runMemoryGovernanceRestoreCliCommand(options) {
|
|
|
50348
51705
|
});
|
|
50349
51706
|
}
|
|
50350
51707
|
async function runMemoryReviewDispositionCliCommand(options) {
|
|
50351
|
-
const storage = new (await import("./storage-
|
|
51708
|
+
const storage = new (await import("./storage-BA6OBLMK.js")).StorageManager(options.memoryDir);
|
|
50352
51709
|
const memory = await storage.getMemoryById(options.memoryId);
|
|
50353
51710
|
if (!memory) throw new Error(`memory not found: ${options.memoryId}`);
|
|
50354
51711
|
const updated = await storage.writeMemoryFrontmatter(memory, {
|
|
@@ -50514,7 +51871,7 @@ async function runSemanticRulePromoteCliCommand(options) {
|
|
|
50514
51871
|
});
|
|
50515
51872
|
}
|
|
50516
51873
|
async function runCompoundingPromoteCliCommand(options) {
|
|
50517
|
-
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-
|
|
51874
|
+
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-WGNTTFYE.js");
|
|
50518
51875
|
const config = parseConfig({
|
|
50519
51876
|
memoryDir: options.memoryDir,
|
|
50520
51877
|
qmdEnabled: false,
|
|
@@ -50931,7 +52288,7 @@ function policyVersionForValues(values, config) {
|
|
|
50931
52288
|
return createHash14("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
50932
52289
|
}
|
|
50933
52290
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
50934
|
-
const filePath =
|
|
52291
|
+
const filePath = path72.join(config.memoryDir, "state", fileName);
|
|
50935
52292
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
50936
52293
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
50937
52294
|
});
|
|
@@ -51487,7 +52844,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
|
51487
52844
|
}
|
|
51488
52845
|
async function runReplayCliCommand(orchestrator, options) {
|
|
51489
52846
|
const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
|
|
51490
|
-
const inputRaw = await
|
|
52847
|
+
const inputRaw = await readFile46(options.inputPath, "utf-8");
|
|
51491
52848
|
const registry = buildReplayNormalizerRegistry([
|
|
51492
52849
|
openclawReplayNormalizer,
|
|
51493
52850
|
claudeReplayNormalizer,
|
|
@@ -51549,10 +52906,101 @@ async function runReplayCliCommand(orchestrator, options) {
|
|
|
51549
52906
|
}
|
|
51550
52907
|
return summary;
|
|
51551
52908
|
}
|
|
52909
|
+
async function ensureBuiltInBulkImportAdapters() {
|
|
52910
|
+
if (!getBulkImportSource("weclone")) {
|
|
52911
|
+
const wecloneSpecifier = "@remnic/import-weclone";
|
|
52912
|
+
try {
|
|
52913
|
+
const mod = await import(wecloneSpecifier);
|
|
52914
|
+
if (mod.wecloneImportAdapter) {
|
|
52915
|
+
try {
|
|
52916
|
+
registerBulkImportSource(mod.wecloneImportAdapter);
|
|
52917
|
+
} catch {
|
|
52918
|
+
}
|
|
52919
|
+
}
|
|
52920
|
+
} catch {
|
|
52921
|
+
}
|
|
52922
|
+
}
|
|
52923
|
+
}
|
|
52924
|
+
async function runBulkImportCliCommand(opts) {
|
|
52925
|
+
await ensureBuiltInBulkImportAdapters();
|
|
52926
|
+
const adapter = getBulkImportSource(opts.source);
|
|
52927
|
+
if (!adapter) {
|
|
52928
|
+
const registered = listBulkImportSources();
|
|
52929
|
+
const list = registered.length > 0 ? registered.map((n) => `'${n}'`).join(", ") : "(none registered)";
|
|
52930
|
+
throw new Error(
|
|
52931
|
+
`Unknown bulk-import source '${opts.source}'. Valid sources: ${list}`
|
|
52932
|
+
);
|
|
52933
|
+
}
|
|
52934
|
+
if (opts.dryRun !== true && typeof opts.ingestBatch !== "function") {
|
|
52935
|
+
throw new Error(
|
|
52936
|
+
"Bulk import persistence is not wired: no ingestBatch callback was provided by the host CLI. Use --dry-run to validate without persisting, or invoke via `openclaw engram bulk-import` which supplies the orchestrator-backed ingestion path."
|
|
52937
|
+
);
|
|
52938
|
+
}
|
|
52939
|
+
const inputRaw = await readFile46(opts.file, "utf-8");
|
|
52940
|
+
let inputParsed;
|
|
52941
|
+
try {
|
|
52942
|
+
inputParsed = JSON.parse(inputRaw);
|
|
52943
|
+
} catch (err) {
|
|
52944
|
+
throw new Error(
|
|
52945
|
+
`Failed to parse import file as JSON: ${err.message}`
|
|
52946
|
+
);
|
|
52947
|
+
}
|
|
52948
|
+
if (typeof inputParsed !== "object" || inputParsed === null) {
|
|
52949
|
+
throw new Error(
|
|
52950
|
+
"Import file must contain a JSON object or array, got " + (inputParsed === null ? "null" : typeof inputParsed)
|
|
52951
|
+
);
|
|
52952
|
+
}
|
|
52953
|
+
const parsed = await adapter.parse(inputParsed, {
|
|
52954
|
+
strict: opts.strict === true,
|
|
52955
|
+
platform: opts.platform
|
|
52956
|
+
});
|
|
52957
|
+
const processBatch = opts.ingestBatch ?? (async () => {
|
|
52958
|
+
throw new Error(
|
|
52959
|
+
"Bulk import persistence is not wired: no ingestBatch callback was provided by the host CLI."
|
|
52960
|
+
);
|
|
52961
|
+
});
|
|
52962
|
+
const result = await runBulkImportPipeline(
|
|
52963
|
+
parsed,
|
|
52964
|
+
{
|
|
52965
|
+
batchSize: opts.batchSize,
|
|
52966
|
+
dryRun: opts.dryRun,
|
|
52967
|
+
dedup: true,
|
|
52968
|
+
trustLevel: "import"
|
|
52969
|
+
},
|
|
52970
|
+
processBatch
|
|
52971
|
+
);
|
|
52972
|
+
const out = opts.stdout;
|
|
52973
|
+
out.write(`Bulk import complete (source: ${opts.source})
|
|
52974
|
+
`);
|
|
52975
|
+
out.write(` Turns processed: ${result.turnsProcessed}
|
|
52976
|
+
`);
|
|
52977
|
+
out.write(` Batches processed: ${result.batchesProcessed}
|
|
52978
|
+
`);
|
|
52979
|
+
out.write(` Memories created: ${result.memoriesCreated}
|
|
52980
|
+
`);
|
|
52981
|
+
out.write(` Duplicates skipped: ${result.duplicatesSkipped}
|
|
52982
|
+
`);
|
|
52983
|
+
if (result.errors.length > 0) {
|
|
52984
|
+
out.write(` Errors: ${result.errors.length}
|
|
52985
|
+
`);
|
|
52986
|
+
if (opts.verbose) {
|
|
52987
|
+
for (const err of result.errors) {
|
|
52988
|
+
opts.stderr.write(
|
|
52989
|
+
` [batch ${err.batchIndex}] ${err.message}
|
|
52990
|
+
`
|
|
52991
|
+
);
|
|
52992
|
+
}
|
|
52993
|
+
}
|
|
52994
|
+
}
|
|
52995
|
+
if (opts.dryRun) {
|
|
52996
|
+
out.write(" (dry run \u2014 no memories were stored)\n");
|
|
52997
|
+
}
|
|
52998
|
+
return result;
|
|
52999
|
+
}
|
|
51552
53000
|
async function getPluginVersion() {
|
|
51553
53001
|
try {
|
|
51554
53002
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
51555
|
-
const raw = await
|
|
53003
|
+
const raw = await readFile46(pkgPath, "utf-8");
|
|
51556
53004
|
const parsed = JSON.parse(raw);
|
|
51557
53005
|
return parsed.version ?? "unknown";
|
|
51558
53006
|
} catch {
|
|
@@ -51576,59 +53024,71 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace, options) {
|
|
|
51576
53024
|
}
|
|
51577
53025
|
return orchestrator.config.memoryDir;
|
|
51578
53026
|
}
|
|
51579
|
-
const candidate =
|
|
53027
|
+
const candidate = path72.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
51580
53028
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
51581
53029
|
return await exists3(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
51582
53030
|
}
|
|
51583
53031
|
return candidate;
|
|
51584
53032
|
}
|
|
51585
|
-
async function
|
|
51586
|
-
const roots = [
|
|
51587
|
-
const out = [];
|
|
53033
|
+
async function walkMemoryMarkdownFiles(memoryDir, visit) {
|
|
53034
|
+
const roots = [path72.join(memoryDir, "facts"), path72.join(memoryDir, "corrections")];
|
|
51588
53035
|
const walk = async (dir) => {
|
|
51589
53036
|
let entries;
|
|
51590
53037
|
try {
|
|
51591
|
-
entries = await
|
|
53038
|
+
entries = await readdir26(dir, { withFileTypes: true });
|
|
51592
53039
|
} catch {
|
|
51593
53040
|
return;
|
|
51594
53041
|
}
|
|
51595
53042
|
for (const entry of entries) {
|
|
51596
53043
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
51597
|
-
const fullPath =
|
|
53044
|
+
const fullPath = path72.join(dir, entryName);
|
|
51598
53045
|
if (entry.isDirectory()) {
|
|
51599
53046
|
await walk(fullPath);
|
|
51600
53047
|
continue;
|
|
51601
53048
|
}
|
|
51602
53049
|
if (!entry.isFile() || !entryName.endsWith(".md")) continue;
|
|
51603
|
-
|
|
51604
|
-
const raw = await readFile45(fullPath, "utf-8");
|
|
51605
|
-
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
51606
|
-
if (!parsed) continue;
|
|
51607
|
-
const fmRaw = parsed[1];
|
|
51608
|
-
const body = parsed[2] ?? "";
|
|
51609
|
-
const get = (key) => {
|
|
51610
|
-
const match = fmRaw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
51611
|
-
return match ? match[1].trim() : "";
|
|
51612
|
-
};
|
|
51613
|
-
const confidenceRaw = get("confidence");
|
|
51614
|
-
const confidence = confidenceRaw.length > 0 ? Number(confidenceRaw) : void 0;
|
|
51615
|
-
out.push({
|
|
51616
|
-
path: fullPath,
|
|
51617
|
-
content: body,
|
|
51618
|
-
frontmatter: {
|
|
51619
|
-
id: get("id") || void 0,
|
|
51620
|
-
confidence: Number.isFinite(confidence) ? confidence : void 0,
|
|
51621
|
-
updated: get("updated") || void 0,
|
|
51622
|
-
created: get("created") || void 0
|
|
51623
|
-
}
|
|
51624
|
-
});
|
|
51625
|
-
} catch {
|
|
51626
|
-
}
|
|
53050
|
+
await visit(fullPath);
|
|
51627
53051
|
}
|
|
51628
53052
|
};
|
|
51629
53053
|
for (const root of roots) {
|
|
51630
53054
|
await walk(root);
|
|
51631
53055
|
}
|
|
53056
|
+
}
|
|
53057
|
+
async function listMemoryMarkdownFilePaths(memoryDir) {
|
|
53058
|
+
const paths = [];
|
|
53059
|
+
await walkMemoryMarkdownFiles(memoryDir, (fullPath) => {
|
|
53060
|
+
paths.push(fullPath);
|
|
53061
|
+
});
|
|
53062
|
+
return paths;
|
|
53063
|
+
}
|
|
53064
|
+
async function readAllMemoryFiles(memoryDir) {
|
|
53065
|
+
const out = [];
|
|
53066
|
+
await walkMemoryMarkdownFiles(memoryDir, async (fullPath) => {
|
|
53067
|
+
try {
|
|
53068
|
+
const raw = await readFile46(fullPath, "utf-8");
|
|
53069
|
+
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
53070
|
+
if (!parsed) return;
|
|
53071
|
+
const fmRaw = parsed[1];
|
|
53072
|
+
const body = parsed[2] ?? "";
|
|
53073
|
+
const get = (key) => {
|
|
53074
|
+
const match = fmRaw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
53075
|
+
return match ? match[1].trim() : "";
|
|
53076
|
+
};
|
|
53077
|
+
const confidenceRaw = get("confidence");
|
|
53078
|
+
const confidence = confidenceRaw.length > 0 ? Number(confidenceRaw) : void 0;
|
|
53079
|
+
out.push({
|
|
53080
|
+
path: fullPath,
|
|
53081
|
+
content: body,
|
|
53082
|
+
frontmatter: {
|
|
53083
|
+
id: get("id") || void 0,
|
|
53084
|
+
confidence: Number.isFinite(confidence) ? confidence : void 0,
|
|
53085
|
+
updated: get("updated") || void 0,
|
|
53086
|
+
created: get("created") || void 0
|
|
53087
|
+
}
|
|
53088
|
+
});
|
|
53089
|
+
} catch {
|
|
53090
|
+
}
|
|
53091
|
+
});
|
|
51632
53092
|
return out;
|
|
51633
53093
|
}
|
|
51634
53094
|
function formatContinuityIncidentCli(incident) {
|
|
@@ -51937,7 +53397,7 @@ function registerCli(api, orchestrator) {
|
|
|
51937
53397
|
if (plan.moved.length > 0) {
|
|
51938
53398
|
console.log("\nEntries:");
|
|
51939
53399
|
for (const move of plan.moved) {
|
|
51940
|
-
console.log(`- ${
|
|
53400
|
+
console.log(`- ${path72.basename(move.from)}`);
|
|
51941
53401
|
}
|
|
51942
53402
|
}
|
|
51943
53403
|
if (dryRun) {
|
|
@@ -52135,6 +53595,64 @@ function registerCli(api, orchestrator) {
|
|
|
52135
53595
|
}
|
|
52136
53596
|
console.log("OK");
|
|
52137
53597
|
});
|
|
53598
|
+
cmd.command("bulk-import").description(
|
|
53599
|
+
"Bulk-import chat history via a registered source adapter (e.g. --source weclone)."
|
|
53600
|
+
).option("--source <source>", "Bulk-import source adapter name (e.g. weclone)").option("--file <path>", "Path to the import file (JSON)").option("--platform <platform>", "Optional platform override forwarded to the adapter").option("--batch-size <n>", "Turns per batch", "50").option("--dry-run", "Parse and validate only; do not persist").option("--strict", "Fail on any invalid source row").option("--verbose", "Print per-batch error details").action(async (...args) => {
|
|
53601
|
+
const options = args[0] ?? {};
|
|
53602
|
+
const sourceRaw = typeof options.source === "string" ? options.source.trim() : "";
|
|
53603
|
+
const filePathRaw = typeof options.file === "string" ? options.file.trim() : "";
|
|
53604
|
+
if (sourceRaw.length === 0) {
|
|
53605
|
+
console.log("Missing --source. Example: openclaw engram bulk-import --source weclone --file /tmp/export.json");
|
|
53606
|
+
return;
|
|
53607
|
+
}
|
|
53608
|
+
if (filePathRaw.length === 0) {
|
|
53609
|
+
console.log("Missing --file. Example: openclaw engram bulk-import --source weclone --file /tmp/export.json");
|
|
53610
|
+
return;
|
|
53611
|
+
}
|
|
53612
|
+
const batchSizeRaw = parseInt(String(options.batchSize ?? "50"), 10);
|
|
53613
|
+
const batchSize = Number.isFinite(batchSizeRaw) && batchSizeRaw > 0 ? batchSizeRaw : 50;
|
|
53614
|
+
const platformRaw = typeof options.platform === "string" ? options.platform.trim() : "";
|
|
53615
|
+
const writeNamespace = orchestrator.bulkImportWriteNamespace();
|
|
53616
|
+
const writeStorage = await orchestrator.getStorageForNamespace(
|
|
53617
|
+
writeNamespace
|
|
53618
|
+
);
|
|
53619
|
+
const writeRoot = writeStorage.dir;
|
|
53620
|
+
const ingestBatch = async (turns) => {
|
|
53621
|
+
const before = new Set(await listMemoryMarkdownFilePaths(writeRoot));
|
|
53622
|
+
await orchestrator.ingestBulkImportBatch(turns, {});
|
|
53623
|
+
const after = await listMemoryMarkdownFilePaths(writeRoot);
|
|
53624
|
+
let memoriesCreated = 0;
|
|
53625
|
+
for (const p of after) {
|
|
53626
|
+
if (!before.has(p)) memoriesCreated += 1;
|
|
53627
|
+
}
|
|
53628
|
+
return { memoriesCreated, duplicatesSkipped: 0 };
|
|
53629
|
+
};
|
|
53630
|
+
try {
|
|
53631
|
+
const result = await runBulkImportCliCommand({
|
|
53632
|
+
memoryDir: writeRoot,
|
|
53633
|
+
source: sourceRaw,
|
|
53634
|
+
file: filePathRaw,
|
|
53635
|
+
platform: platformRaw.length > 0 ? platformRaw : void 0,
|
|
53636
|
+
batchSize,
|
|
53637
|
+
dryRun: options.dryRun === true,
|
|
53638
|
+
verbose: options.verbose === true,
|
|
53639
|
+
strict: options.strict === true,
|
|
53640
|
+
ingestBatch,
|
|
53641
|
+
stdout: process.stdout,
|
|
53642
|
+
stderr: process.stderr
|
|
53643
|
+
});
|
|
53644
|
+
if (result.errors.length > 0) {
|
|
53645
|
+
console.error(`Bulk import completed with ${result.errors.length} batch error(s).`);
|
|
53646
|
+
process.exitCode = 1;
|
|
53647
|
+
} else {
|
|
53648
|
+
console.log("OK");
|
|
53649
|
+
}
|
|
53650
|
+
} catch (err) {
|
|
53651
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53652
|
+
console.error(`Bulk import failed: ${message}`);
|
|
53653
|
+
process.exitCode = 1;
|
|
53654
|
+
}
|
|
53655
|
+
});
|
|
52138
53656
|
cmd.command("benchmark-status").description("Show benchmark/evaluation harness status, benchmark packs, and latest run summary").action(async () => {
|
|
52139
53657
|
const status = await runBenchmarkStatusCliCommand({
|
|
52140
53658
|
memoryDir: orchestrator.config.memoryDir,
|
|
@@ -53587,7 +55105,7 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53587
55105
|
}
|
|
53588
55106
|
});
|
|
53589
55107
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
53590
|
-
const workspaceDir =
|
|
55108
|
+
const workspaceDir = path72.join(resolveHomeDir(), ".openclaw", "workspace");
|
|
53591
55109
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
53592
55110
|
if (!identity) {
|
|
53593
55111
|
console.log("No identity file found.");
|
|
@@ -53810,8 +55328,8 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53810
55328
|
const options = args[0] ?? {};
|
|
53811
55329
|
const threadId = options.thread;
|
|
53812
55330
|
const top = parseInt(options.top ?? "10", 10);
|
|
53813
|
-
const memoryDir =
|
|
53814
|
-
const threading = new ThreadingManager(
|
|
55331
|
+
const memoryDir = path72.join(resolveHomeDir(), ".openclaw", "workspace", "memory", "local");
|
|
55332
|
+
const threading = new ThreadingManager(path72.join(memoryDir, "threads"));
|
|
53815
55333
|
if (threadId) {
|
|
53816
55334
|
const thread = await threading.loadThread(threadId);
|
|
53817
55335
|
if (!thread) {
|
|
@@ -53963,6 +55481,94 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53963
55481
|
}
|
|
53964
55482
|
console.log(orchestrator.summarizer.formatForRecall(summaries, summaries.length));
|
|
53965
55483
|
});
|
|
55484
|
+
const reviewCmd = cmd.command("review").description("Manage contradiction review queue");
|
|
55485
|
+
reviewCmd.command("list").description("List contradiction review items").option("--filter <type>", "Filter: all, unresolved, contradicts, independent, duplicates, needs-user", "unresolved").option("--namespace <ns>", "Filter by namespace").option("--limit <n>", "Max items (default 50)", "50").action(async (...args) => {
|
|
55486
|
+
const options = args[0] ?? {};
|
|
55487
|
+
const filter = options.filter ?? "unresolved";
|
|
55488
|
+
const validFilters = ["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"];
|
|
55489
|
+
if (!validFilters.includes(filter)) {
|
|
55490
|
+
console.error(`Invalid filter: ${filter}. Must be one of: ${validFilters.join(", ")}`);
|
|
55491
|
+
process.exit(1);
|
|
55492
|
+
}
|
|
55493
|
+
const limit = parseInt(options.limit ?? "50", 10);
|
|
55494
|
+
const { listPairs } = await import("./contradiction-review-SVGBS3V5.js");
|
|
55495
|
+
const result = listPairs(orchestrator.config.memoryDir, {
|
|
55496
|
+
filter,
|
|
55497
|
+
namespace: options.namespace,
|
|
55498
|
+
limit: Number.isFinite(limit) ? limit : 50
|
|
55499
|
+
});
|
|
55500
|
+
if (result.pairs.length === 0) {
|
|
55501
|
+
console.log("No review items found.");
|
|
55502
|
+
return;
|
|
55503
|
+
}
|
|
55504
|
+
console.log(`Found ${result.total} item(s) (${result.durationMs}ms):
|
|
55505
|
+
`);
|
|
55506
|
+
for (const pair of result.pairs) {
|
|
55507
|
+
console.log(` [${pair.verdict}] ${pair.pairId}`);
|
|
55508
|
+
console.log(` Memories: ${pair.memoryIds.join(", ")}`);
|
|
55509
|
+
console.log(` Rationale: ${pair.rationale}`);
|
|
55510
|
+
console.log(` Confidence: ${pair.confidence.toFixed(2)}`);
|
|
55511
|
+
if (pair.resolution) console.log(` Resolution: ${pair.resolution}`);
|
|
55512
|
+
console.log();
|
|
55513
|
+
}
|
|
55514
|
+
});
|
|
55515
|
+
reviewCmd.command("show <pairId>").description("Show details of a contradiction pair").action(async (...args) => {
|
|
55516
|
+
const pairId = typeof args[0] === "string" ? args[0] : "";
|
|
55517
|
+
if (!pairId) {
|
|
55518
|
+
console.error("pairId is required");
|
|
55519
|
+
process.exit(1);
|
|
55520
|
+
}
|
|
55521
|
+
const { readPair } = await import("./contradiction-review-SVGBS3V5.js");
|
|
55522
|
+
const pair = readPair(orchestrator.config.memoryDir, pairId);
|
|
55523
|
+
if (!pair) {
|
|
55524
|
+
console.error(`Pair ${pairId} not found.`);
|
|
55525
|
+
process.exit(1);
|
|
55526
|
+
}
|
|
55527
|
+
console.log(JSON.stringify(pair, null, 2));
|
|
55528
|
+
});
|
|
55529
|
+
reviewCmd.command("resolve <pairId>").description("Resolve a contradiction pair").requiredOption("--verb <verb>", "Resolution verb: keep-a, keep-b, merge, both-valid, needs-more-context").action(async (...args) => {
|
|
55530
|
+
const pairId = typeof args[0] === "string" ? args[0] : "";
|
|
55531
|
+
const cmdOpts = args[1] ?? {};
|
|
55532
|
+
if (!pairId) {
|
|
55533
|
+
console.error("pairId is required");
|
|
55534
|
+
process.exit(1);
|
|
55535
|
+
}
|
|
55536
|
+
const verb = cmdOpts.verb;
|
|
55537
|
+
if (!verb) {
|
|
55538
|
+
console.error("--verb is required. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context");
|
|
55539
|
+
process.exit(1);
|
|
55540
|
+
}
|
|
55541
|
+
const { isValidResolutionVerb, executeResolution } = await import("./resolution-YITUVUTH.js");
|
|
55542
|
+
if (!isValidResolutionVerb(verb)) {
|
|
55543
|
+
console.error(`Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context`);
|
|
55544
|
+
process.exit(1);
|
|
55545
|
+
}
|
|
55546
|
+
const result = await executeResolution(orchestrator.config.memoryDir, orchestrator.storage, pairId, verb);
|
|
55547
|
+
console.log(result.message);
|
|
55548
|
+
if (result.affectedIds.length > 0) {
|
|
55549
|
+
console.log(`Affected: ${result.affectedIds.join(", ")}`);
|
|
55550
|
+
}
|
|
55551
|
+
});
|
|
55552
|
+
reviewCmd.command("scan").description("Run an on-demand contradiction scan").option("--namespace <ns>", "Namespace to scan").action(async (...args) => {
|
|
55553
|
+
const options = args[0] ?? {};
|
|
55554
|
+
const { runContradictionScan } = await import("./contradiction-scan-LRRLWUOS.js");
|
|
55555
|
+
console.log("Running contradiction scan...");
|
|
55556
|
+
const result = await runContradictionScan({
|
|
55557
|
+
storage: orchestrator.storage,
|
|
55558
|
+
config: orchestrator.config,
|
|
55559
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
55560
|
+
embeddingLookup: void 0,
|
|
55561
|
+
localLlm: orchestrator.localLlm ?? null,
|
|
55562
|
+
fallbackLlm: null,
|
|
55563
|
+
namespace: options.namespace
|
|
55564
|
+
});
|
|
55565
|
+
console.log(`Scan complete in ${result.elapsedMs}ms:`);
|
|
55566
|
+
console.log(` Scanned: ${result.scanned} memories`);
|
|
55567
|
+
console.log(` Candidates: ${result.candidates} pairs`);
|
|
55568
|
+
console.log(` Judged: ${result.judged}`);
|
|
55569
|
+
console.log(` Queued: ${result.queued}`);
|
|
55570
|
+
console.log(` Cooled down: ${result.cooledDown}`);
|
|
55571
|
+
});
|
|
53966
55572
|
},
|
|
53967
55573
|
{ commands: ["engram"] }
|
|
53968
55574
|
);
|
|
@@ -54287,19 +55893,19 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
54287
55893
|
}
|
|
54288
55894
|
|
|
54289
55895
|
// ../../src/index.ts
|
|
54290
|
-
import { readFile as
|
|
55896
|
+
import { readFile as readFile52, realpath as realpath5, writeFile as writeFile46 } from "fs/promises";
|
|
54291
55897
|
import { readFileSync as readFileSync6 } from "fs";
|
|
54292
|
-
import
|
|
55898
|
+
import path98 from "path";
|
|
54293
55899
|
import os6 from "os";
|
|
54294
55900
|
|
|
54295
55901
|
// ../remnic-core/src/opik-exporter.ts
|
|
54296
|
-
import { createHash as createHash15, randomBytes } from "crypto";
|
|
55902
|
+
import { createHash as createHash15, randomBytes as randomBytes2 } from "crypto";
|
|
54297
55903
|
import { readFileSync as readFileSync4 } from "fs";
|
|
54298
|
-
import
|
|
55904
|
+
import path73 from "path";
|
|
54299
55905
|
var OPIK_EXPORTER_SLOT = "__openclawOpikExporter";
|
|
54300
55906
|
function readOpikOpenclawConfig(log2) {
|
|
54301
55907
|
try {
|
|
54302
|
-
const configPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH") ||
|
|
55908
|
+
const configPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH") || path73.join(resolveHomeDir(), ".openclaw", "openclaw.json");
|
|
54303
55909
|
const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
54304
55910
|
const entry = raw?.plugins?.entries?.["opik-openclaw"];
|
|
54305
55911
|
if (!entry?.enabled || !entry?.config) return {};
|
|
@@ -54317,7 +55923,7 @@ function readOpikOpenclawConfig(log2) {
|
|
|
54317
55923
|
}
|
|
54318
55924
|
function uuidV7() {
|
|
54319
55925
|
const now = Date.now();
|
|
54320
|
-
const bytes =
|
|
55926
|
+
const bytes = randomBytes2(16);
|
|
54321
55927
|
bytes[0] = now / 2 ** 40 & 255;
|
|
54322
55928
|
bytes[1] = now / 2 ** 32 & 255;
|
|
54323
55929
|
bytes[2] = now / 2 ** 24 & 255;
|
|
@@ -54661,8 +56267,8 @@ function cleanUserMessage(content) {
|
|
|
54661
56267
|
}
|
|
54662
56268
|
|
|
54663
56269
|
// src/public-artifacts.ts
|
|
54664
|
-
import { readdir as
|
|
54665
|
-
import
|
|
56270
|
+
import { readdir as readdir27, access as access6, stat as stat20, lstat as lstat3, realpath as realpath4 } from "fs/promises";
|
|
56271
|
+
import path74 from "path";
|
|
54666
56272
|
var PUBLIC_DIRS = [
|
|
54667
56273
|
{ dir: "facts", kind: "fact", contentType: "markdown" },
|
|
54668
56274
|
{ dir: "entities", kind: "entity", contentType: "markdown" },
|
|
@@ -54674,9 +56280,9 @@ var PUBLIC_FILES = [
|
|
|
54674
56280
|
];
|
|
54675
56281
|
async function isContainedWithin(target, boundary) {
|
|
54676
56282
|
try {
|
|
54677
|
-
const resolvedTarget = await
|
|
54678
|
-
const resolvedBoundary = await
|
|
54679
|
-
return resolvedTarget === resolvedBoundary || resolvedTarget.startsWith(resolvedBoundary +
|
|
56283
|
+
const resolvedTarget = await realpath4(target);
|
|
56284
|
+
const resolvedBoundary = await realpath4(boundary);
|
|
56285
|
+
return resolvedTarget === resolvedBoundary || resolvedTarget.startsWith(resolvedBoundary + path74.sep);
|
|
54680
56286
|
} catch {
|
|
54681
56287
|
return false;
|
|
54682
56288
|
}
|
|
@@ -54685,7 +56291,7 @@ async function listMarkdownFilesRecursive(rootDir, boundary, ancestorRealPaths)
|
|
|
54685
56291
|
const boundaryDir = boundary ?? rootDir;
|
|
54686
56292
|
let resolvedRoot;
|
|
54687
56293
|
try {
|
|
54688
|
-
resolvedRoot = await
|
|
56294
|
+
resolvedRoot = await realpath4(rootDir);
|
|
54689
56295
|
} catch {
|
|
54690
56296
|
return [];
|
|
54691
56297
|
}
|
|
@@ -54694,18 +56300,18 @@ async function listMarkdownFilesRecursive(rootDir, boundary, ancestorRealPaths)
|
|
|
54694
56300
|
nextAncestors.add(resolvedRoot);
|
|
54695
56301
|
let entries;
|
|
54696
56302
|
try {
|
|
54697
|
-
entries = await
|
|
56303
|
+
entries = await readdir27(rootDir, { withFileTypes: true });
|
|
54698
56304
|
} catch {
|
|
54699
56305
|
return [];
|
|
54700
56306
|
}
|
|
54701
56307
|
entries.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
54702
56308
|
const files = [];
|
|
54703
56309
|
for (const entry of entries) {
|
|
54704
|
-
const fullPath =
|
|
56310
|
+
const fullPath = path74.join(rootDir, String(entry.name));
|
|
54705
56311
|
let isDir = entry.isDirectory();
|
|
54706
56312
|
let isFile = entry.isFile();
|
|
54707
56313
|
try {
|
|
54708
|
-
const linkStat = await
|
|
56314
|
+
const linkStat = await lstat3(fullPath);
|
|
54709
56315
|
if (linkStat.isSymbolicLink()) {
|
|
54710
56316
|
if (!await isContainedWithin(fullPath, boundaryDir)) {
|
|
54711
56317
|
continue;
|
|
@@ -54739,21 +56345,21 @@ async function listRemnicPublicArtifacts(params) {
|
|
|
54739
56345
|
const { memoryDir, workspaceDir, agentIds } = params;
|
|
54740
56346
|
const artifacts = [];
|
|
54741
56347
|
for (const spec of PUBLIC_DIRS) {
|
|
54742
|
-
const dirPath =
|
|
56348
|
+
const dirPath = path74.join(memoryDir, spec.dir);
|
|
54743
56349
|
if (!await pathExists2(dirPath)) continue;
|
|
54744
56350
|
if (!await isContainedWithin(dirPath, memoryDir)) continue;
|
|
54745
56351
|
try {
|
|
54746
|
-
const resolvedDir = await
|
|
54747
|
-
const expectedParent = await
|
|
54748
|
-
const resolvedName =
|
|
56352
|
+
const resolvedDir = await realpath4(dirPath);
|
|
56353
|
+
const expectedParent = await realpath4(memoryDir);
|
|
56354
|
+
const resolvedName = path74.basename(resolvedDir);
|
|
54749
56355
|
if (resolvedName !== spec.dir) continue;
|
|
54750
|
-
if (
|
|
56356
|
+
if (path74.dirname(resolvedDir) !== expectedParent) continue;
|
|
54751
56357
|
} catch {
|
|
54752
56358
|
continue;
|
|
54753
56359
|
}
|
|
54754
56360
|
const files = await listMarkdownFilesRecursive(dirPath, dirPath);
|
|
54755
56361
|
for (const absolutePath of files) {
|
|
54756
|
-
const relativePath =
|
|
56362
|
+
const relativePath = path74.relative(memoryDir, absolutePath).replace(/\\/g, "/");
|
|
54757
56363
|
artifacts.push({
|
|
54758
56364
|
kind: spec.kind,
|
|
54759
56365
|
workspaceDir,
|
|
@@ -54765,16 +56371,16 @@ async function listRemnicPublicArtifacts(params) {
|
|
|
54765
56371
|
}
|
|
54766
56372
|
}
|
|
54767
56373
|
for (const spec of PUBLIC_FILES) {
|
|
54768
|
-
const absolutePath =
|
|
56374
|
+
const absolutePath = path74.join(memoryDir, spec.relativePath);
|
|
54769
56375
|
if (!await pathExists2(absolutePath)) continue;
|
|
54770
56376
|
if (!await isContainedWithin(absolutePath, memoryDir)) continue;
|
|
54771
56377
|
try {
|
|
54772
|
-
const linkStat = await
|
|
56378
|
+
const linkStat = await lstat3(absolutePath);
|
|
54773
56379
|
if (linkStat.isSymbolicLink()) {
|
|
54774
|
-
const resolvedPath = await
|
|
54775
|
-
const expectedParent = await
|
|
54776
|
-
if (
|
|
54777
|
-
if (
|
|
56380
|
+
const resolvedPath = await realpath4(absolutePath);
|
|
56381
|
+
const expectedParent = await realpath4(memoryDir);
|
|
56382
|
+
if (path74.dirname(resolvedPath) !== expectedParent) continue;
|
|
56383
|
+
if (path74.basename(resolvedPath) !== path74.basename(spec.relativePath)) continue;
|
|
54778
56384
|
}
|
|
54779
56385
|
} catch {
|
|
54780
56386
|
continue;
|
|
@@ -54925,28 +56531,28 @@ var DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024;
|
|
|
54925
56531
|
// ../remnic-core/src/binary-lifecycle/backend.ts
|
|
54926
56532
|
import fs3 from "fs";
|
|
54927
56533
|
import fsp2 from "fs/promises";
|
|
54928
|
-
import
|
|
56534
|
+
import path75 from "path";
|
|
54929
56535
|
|
|
54930
56536
|
// ../remnic-core/src/binary-lifecycle/scanner.ts
|
|
54931
56537
|
import fsp3 from "fs/promises";
|
|
54932
|
-
import
|
|
56538
|
+
import path76 from "path";
|
|
54933
56539
|
|
|
54934
56540
|
// ../remnic-core/src/binary-lifecycle/manifest.ts
|
|
54935
56541
|
import fsp4 from "fs/promises";
|
|
54936
|
-
import
|
|
56542
|
+
import path77 from "path";
|
|
54937
56543
|
import crypto3 from "crypto";
|
|
54938
56544
|
|
|
54939
56545
|
// ../remnic-core/src/binary-lifecycle/pipeline.ts
|
|
54940
56546
|
import fsp5 from "fs/promises";
|
|
54941
|
-
import
|
|
56547
|
+
import path78 from "path";
|
|
54942
56548
|
import crypto4 from "crypto";
|
|
54943
56549
|
|
|
54944
56550
|
// ../remnic-core/src/projection/index.ts
|
|
54945
56551
|
import fs4 from "fs";
|
|
54946
|
-
import
|
|
56552
|
+
import path80 from "path";
|
|
54947
56553
|
|
|
54948
56554
|
// ../remnic-core/src/utils/category-dir.ts
|
|
54949
|
-
import
|
|
56555
|
+
import path79 from "path";
|
|
54950
56556
|
var CATEGORY_DIR_MAP = {
|
|
54951
56557
|
correction: "corrections",
|
|
54952
56558
|
question: "questions",
|
|
@@ -54957,7 +56563,8 @@ var CATEGORY_DIR_MAP = {
|
|
|
54957
56563
|
principle: "principles",
|
|
54958
56564
|
rule: "rules",
|
|
54959
56565
|
skill: "skills",
|
|
54960
|
-
relationship: "relationships"
|
|
56566
|
+
relationship: "relationships",
|
|
56567
|
+
procedure: "procedures"
|
|
54961
56568
|
};
|
|
54962
56569
|
var ALL_CATEGORY_DIRS = [
|
|
54963
56570
|
"facts",
|
|
@@ -54970,30 +56577,30 @@ var ALL_CATEGORY_KEYS = [
|
|
|
54970
56577
|
|
|
54971
56578
|
// ../remnic-core/src/onboarding/index.ts
|
|
54972
56579
|
import fs5 from "fs";
|
|
54973
|
-
import
|
|
56580
|
+
import path81 from "path";
|
|
54974
56581
|
|
|
54975
56582
|
// ../remnic-core/src/curation/index.ts
|
|
54976
56583
|
import fs6 from "fs";
|
|
54977
|
-
import
|
|
56584
|
+
import path82 from "path";
|
|
54978
56585
|
import crypto5 from "crypto";
|
|
54979
56586
|
|
|
54980
56587
|
// ../remnic-core/src/dedup/index.ts
|
|
54981
56588
|
import fs7 from "fs";
|
|
54982
|
-
import
|
|
56589
|
+
import path83 from "path";
|
|
54983
56590
|
import crypto6 from "crypto";
|
|
54984
56591
|
|
|
54985
56592
|
// ../remnic-core/src/review/index.ts
|
|
54986
56593
|
import fs8 from "fs";
|
|
54987
|
-
import
|
|
56594
|
+
import path84 from "path";
|
|
54988
56595
|
|
|
54989
56596
|
// ../remnic-core/src/sync/index.ts
|
|
54990
56597
|
import fs9 from "fs";
|
|
54991
|
-
import
|
|
56598
|
+
import path85 from "path";
|
|
54992
56599
|
import crypto7 from "crypto";
|
|
54993
56600
|
|
|
54994
56601
|
// ../remnic-core/src/connectors/index.ts
|
|
54995
56602
|
import fs11 from "fs";
|
|
54996
|
-
import
|
|
56603
|
+
import path88 from "path";
|
|
54997
56604
|
import os4 from "os";
|
|
54998
56605
|
import { spawnSync } from "child_process";
|
|
54999
56606
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -55001,31 +56608,31 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
55001
56608
|
|
|
55002
56609
|
// ../remnic-core/src/tokens.ts
|
|
55003
56610
|
import fs10 from "fs";
|
|
55004
|
-
import
|
|
55005
|
-
import { randomBytes as
|
|
56611
|
+
import path86 from "path";
|
|
56612
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
55006
56613
|
|
|
55007
56614
|
// ../remnic-core/src/connectors/codex-marketplace.ts
|
|
55008
56615
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
55009
|
-
import
|
|
56616
|
+
import path87 from "path";
|
|
55010
56617
|
|
|
55011
56618
|
// ../remnic-core/src/spaces/index.ts
|
|
55012
56619
|
import fs12 from "fs";
|
|
55013
|
-
import
|
|
56620
|
+
import path89 from "path";
|
|
55014
56621
|
import crypto8 from "crypto";
|
|
55015
56622
|
|
|
55016
56623
|
// ../remnic-core/src/memory-extension/codex-publisher.ts
|
|
55017
56624
|
import fs13 from "fs";
|
|
55018
56625
|
import os5 from "os";
|
|
55019
|
-
import
|
|
56626
|
+
import path90 from "path";
|
|
55020
56627
|
|
|
55021
56628
|
// ../remnic-core/src/taxonomy/taxonomy-loader.ts
|
|
55022
|
-
import { readFile as
|
|
55023
|
-
import
|
|
56629
|
+
import { readFile as readFile47, mkdir as mkdir49, writeFile as writeFile43 } from "fs/promises";
|
|
56630
|
+
import path91 from "path";
|
|
55024
56631
|
|
|
55025
56632
|
// ../remnic-core/src/enrichment/audit.ts
|
|
55026
|
-
import { mkdir as mkdir50, readFile as
|
|
56633
|
+
import { mkdir as mkdir50, readFile as readFile48, appendFile as appendFile4 } from "fs/promises";
|
|
55027
56634
|
import { existsSync as existsSync11 } from "fs";
|
|
55028
|
-
import
|
|
56635
|
+
import path92 from "path";
|
|
55029
56636
|
|
|
55030
56637
|
// src/openclaw-tools/shapes.ts
|
|
55031
56638
|
var MemorySearchInputSchema = Type.Object({
|
|
@@ -55439,7 +57046,7 @@ async function syncHeartbeatOutcomeLinks(params) {
|
|
|
55439
57046
|
}
|
|
55440
57047
|
return { created: 0, updated: 0, linked };
|
|
55441
57048
|
}
|
|
55442
|
-
function
|
|
57049
|
+
function parseIsoTimestamp3(value) {
|
|
55443
57050
|
if (!value) return null;
|
|
55444
57051
|
const parsed = Date.parse(value);
|
|
55445
57052
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -55448,7 +57055,7 @@ function planDreamEntryFromConsolidation(params) {
|
|
|
55448
57055
|
const now = params.now ?? /* @__PURE__ */ new Date();
|
|
55449
57056
|
const latestDreamAt = Math.max(
|
|
55450
57057
|
-1,
|
|
55451
|
-
...params.existingDreams.map((entry) =>
|
|
57058
|
+
...params.existingDreams.map((entry) => parseIsoTimestamp3(entry.timestamp)).filter((value) => value !== null)
|
|
55452
57059
|
);
|
|
55453
57060
|
if (latestDreamAt > 0 && now.getTime() - latestDreamAt < params.minIntervalMinutes * 6e4) {
|
|
55454
57061
|
return null;
|
|
@@ -55592,88 +57199,100 @@ function resolveSession(commandCtx) {
|
|
|
55592
57199
|
};
|
|
55593
57200
|
}
|
|
55594
57201
|
function buildSessionCommandDescriptors(pluginId, runtime) {
|
|
57202
|
+
const subcommands = [
|
|
57203
|
+
{
|
|
57204
|
+
name: "off",
|
|
57205
|
+
description: "Disable Remnic recall for this session",
|
|
57206
|
+
args: [],
|
|
57207
|
+
handler: async (commandCtx = {}) => {
|
|
57208
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
57209
|
+
await runtime.toggles.setDisabled(sessionKey, agentId, true);
|
|
57210
|
+
return `Remnic recall disabled for session ${sessionKey}.`;
|
|
57211
|
+
}
|
|
57212
|
+
},
|
|
57213
|
+
{
|
|
57214
|
+
name: "on",
|
|
57215
|
+
description: "Re-enable Remnic recall for this session",
|
|
57216
|
+
args: [],
|
|
57217
|
+
handler: async (commandCtx = {}) => {
|
|
57218
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
57219
|
+
await runtime.toggles.setDisabled(sessionKey, agentId, false);
|
|
57220
|
+
return `Remnic recall re-enabled for session ${sessionKey}.`;
|
|
57221
|
+
}
|
|
57222
|
+
},
|
|
57223
|
+
{
|
|
57224
|
+
name: "status",
|
|
57225
|
+
description: "Show Remnic recall status and last injected summary",
|
|
57226
|
+
args: [],
|
|
57227
|
+
handler: async (commandCtx = {}) => {
|
|
57228
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
57229
|
+
const resolved = await runtime.toggles.resolve(sessionKey, agentId);
|
|
57230
|
+
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
57231
|
+
const summaryText = runtime.getLastRecallSummary(sessionKey);
|
|
57232
|
+
const summary = summaryText && summaryText.length > 0 ? summaryText : lastRecall && lastRecall.memoryIds.length > 0 ? `${lastRecall.memoryIds.length} memory item(s), latency ${lastRecall.latencyMs ?? "?"}ms` : "NONE";
|
|
57233
|
+
return [
|
|
57234
|
+
`Remnic recall is ${resolved.disabled ? "disabled" : "enabled"} for session ${sessionKey}.`,
|
|
57235
|
+
`Source: ${describeToggleSource(resolved.source)}.`,
|
|
57236
|
+
`Last recall: ${summary}.`
|
|
57237
|
+
].join(" ");
|
|
57238
|
+
}
|
|
57239
|
+
},
|
|
57240
|
+
{
|
|
57241
|
+
name: "clear",
|
|
57242
|
+
description: "Clear the session override and use global config again",
|
|
57243
|
+
args: [],
|
|
57244
|
+
handler: async (commandCtx = {}) => {
|
|
57245
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
57246
|
+
await runtime.toggles.clear(sessionKey, agentId);
|
|
57247
|
+
return `Cleared the Remnic session override for ${sessionKey}.`;
|
|
57248
|
+
}
|
|
57249
|
+
},
|
|
57250
|
+
{
|
|
57251
|
+
name: "stats",
|
|
57252
|
+
description: "Show Remnic extraction and recall stats for this session",
|
|
57253
|
+
args: [],
|
|
57254
|
+
handler: async (commandCtx = {}) => {
|
|
57255
|
+
const { sessionKey } = resolveSession(commandCtx);
|
|
57256
|
+
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
57257
|
+
if (!lastRecall) {
|
|
57258
|
+
return `No Remnic recall stats are available for session ${sessionKey} yet.`;
|
|
57259
|
+
}
|
|
57260
|
+
return [
|
|
57261
|
+
`Session ${sessionKey}.`,
|
|
57262
|
+
`Planner mode: ${lastRecall.plannerMode ?? "unknown"}.`,
|
|
57263
|
+
`Latency: ${lastRecall.latencyMs ?? "?"}ms.`,
|
|
57264
|
+
`Memories: ${lastRecall.memoryIds.length}.`
|
|
57265
|
+
].join(" ");
|
|
57266
|
+
}
|
|
57267
|
+
},
|
|
57268
|
+
{
|
|
57269
|
+
name: "flush",
|
|
57270
|
+
description: "Force-flush the extraction buffer now",
|
|
57271
|
+
args: [],
|
|
57272
|
+
handler: async (commandCtx = {}) => {
|
|
57273
|
+
const { sessionKey } = resolveSession(commandCtx);
|
|
57274
|
+
await runtime.flushSession(sessionKey);
|
|
57275
|
+
return `Flushed the Remnic buffer for session ${sessionKey}.`;
|
|
57276
|
+
}
|
|
57277
|
+
}
|
|
57278
|
+
];
|
|
57279
|
+
const subcommandNames = subcommands.map((entry) => entry.name).join(", ");
|
|
55595
57280
|
return [
|
|
55596
57281
|
{
|
|
55597
57282
|
name: "remnic",
|
|
57283
|
+
description: `Remnic memory controls (${subcommandNames})`,
|
|
55598
57284
|
category: "memory",
|
|
55599
57285
|
pluginId,
|
|
55600
|
-
|
|
55601
|
-
|
|
55602
|
-
|
|
55603
|
-
|
|
55604
|
-
|
|
55605
|
-
|
|
55606
|
-
|
|
55607
|
-
await runtime.toggles.setDisabled(sessionKey, agentId, true);
|
|
55608
|
-
return `Remnic recall disabled for session ${sessionKey}.`;
|
|
55609
|
-
}
|
|
55610
|
-
},
|
|
55611
|
-
{
|
|
55612
|
-
name: "on",
|
|
55613
|
-
description: "Re-enable Remnic recall for this session",
|
|
55614
|
-
args: [],
|
|
55615
|
-
handler: async (commandCtx = {}) => {
|
|
55616
|
-
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
55617
|
-
await runtime.toggles.setDisabled(sessionKey, agentId, false);
|
|
55618
|
-
return `Remnic recall re-enabled for session ${sessionKey}.`;
|
|
55619
|
-
}
|
|
55620
|
-
},
|
|
55621
|
-
{
|
|
55622
|
-
name: "status",
|
|
55623
|
-
description: "Show Remnic recall status and last injected summary",
|
|
55624
|
-
args: [],
|
|
55625
|
-
handler: async (commandCtx = {}) => {
|
|
55626
|
-
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
55627
|
-
const resolved = await runtime.toggles.resolve(sessionKey, agentId);
|
|
55628
|
-
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
55629
|
-
const summaryText = runtime.getLastRecallSummary(sessionKey);
|
|
55630
|
-
const summary = summaryText && summaryText.length > 0 ? summaryText : lastRecall && lastRecall.memoryIds.length > 0 ? `${lastRecall.memoryIds.length} memory item(s), latency ${lastRecall.latencyMs ?? "?"}ms` : "NONE";
|
|
55631
|
-
return [
|
|
55632
|
-
`Remnic recall is ${resolved.disabled ? "disabled" : "enabled"} for session ${sessionKey}.`,
|
|
55633
|
-
`Source: ${describeToggleSource(resolved.source)}.`,
|
|
55634
|
-
`Last recall: ${summary}.`
|
|
55635
|
-
].join(" ");
|
|
55636
|
-
}
|
|
55637
|
-
},
|
|
55638
|
-
{
|
|
55639
|
-
name: "clear",
|
|
55640
|
-
description: "Clear the session override and use global config again",
|
|
55641
|
-
args: [],
|
|
55642
|
-
handler: async (commandCtx = {}) => {
|
|
55643
|
-
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
55644
|
-
await runtime.toggles.clear(sessionKey, agentId);
|
|
55645
|
-
return `Cleared the Remnic session override for ${sessionKey}.`;
|
|
55646
|
-
}
|
|
55647
|
-
},
|
|
55648
|
-
{
|
|
55649
|
-
name: "stats",
|
|
55650
|
-
description: "Show Remnic extraction and recall stats for this session",
|
|
55651
|
-
args: [],
|
|
55652
|
-
handler: async (commandCtx = {}) => {
|
|
55653
|
-
const { sessionKey } = resolveSession(commandCtx);
|
|
55654
|
-
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
55655
|
-
if (!lastRecall) {
|
|
55656
|
-
return `No Remnic recall stats are available for session ${sessionKey} yet.`;
|
|
55657
|
-
}
|
|
55658
|
-
return [
|
|
55659
|
-
`Session ${sessionKey}.`,
|
|
55660
|
-
`Planner mode: ${lastRecall.plannerMode ?? "unknown"}.`,
|
|
55661
|
-
`Latency: ${lastRecall.latencyMs ?? "?"}ms.`,
|
|
55662
|
-
`Memories: ${lastRecall.memoryIds.length}.`
|
|
55663
|
-
].join(" ");
|
|
55664
|
-
}
|
|
55665
|
-
},
|
|
55666
|
-
{
|
|
55667
|
-
name: "flush",
|
|
55668
|
-
description: "Force-flush the extraction buffer now",
|
|
55669
|
-
args: [],
|
|
55670
|
-
handler: async (commandCtx = {}) => {
|
|
55671
|
-
const { sessionKey } = resolveSession(commandCtx);
|
|
55672
|
-
await runtime.flushSession(sessionKey);
|
|
55673
|
-
return `Flushed the Remnic buffer for session ${sessionKey}.`;
|
|
55674
|
-
}
|
|
57286
|
+
acceptsArgs: true,
|
|
57287
|
+
subcommands,
|
|
57288
|
+
handler: async (commandCtx = {}) => {
|
|
57289
|
+
const requested = commandCtx.args?.[0]?.trim().toLowerCase() ?? "status";
|
|
57290
|
+
const match = subcommands.find((entry) => entry.name === requested);
|
|
57291
|
+
if (!match) {
|
|
57292
|
+
return `Unknown Remnic subcommand "${requested}". Try one of: ${subcommandNames}.`;
|
|
55675
57293
|
}
|
|
55676
|
-
|
|
57294
|
+
return match.handler(commandCtx);
|
|
57295
|
+
}
|
|
55677
57296
|
}
|
|
55678
57297
|
];
|
|
55679
57298
|
}
|
|
@@ -55773,8 +57392,8 @@ function validateSlotSelection(ctx) {
|
|
|
55773
57392
|
}
|
|
55774
57393
|
|
|
55775
57394
|
// ../remnic-core/src/session-toggles.ts
|
|
55776
|
-
import { mkdir as mkdir51, readFile as
|
|
55777
|
-
import
|
|
57395
|
+
import { mkdir as mkdir51, readFile as readFile49, writeFile as writeFile44 } from "fs/promises";
|
|
57396
|
+
import path93 from "path";
|
|
55778
57397
|
function encodeToggleKey(sessionKey, agentId) {
|
|
55779
57398
|
return `${encodeURIComponent(sessionKey)}::${encodeURIComponent(agentId)}`;
|
|
55780
57399
|
}
|
|
@@ -55788,7 +57407,7 @@ function decodeToggleKey(key) {
|
|
|
55788
57407
|
}
|
|
55789
57408
|
async function safeReadToggleFile(filePath) {
|
|
55790
57409
|
try {
|
|
55791
|
-
const raw = await
|
|
57410
|
+
const raw = await readFile49(filePath, "utf8");
|
|
55792
57411
|
const parsed = JSON.parse(raw);
|
|
55793
57412
|
if (!parsed || typeof parsed !== "object" || typeof parsed.entries !== "object") {
|
|
55794
57413
|
return { version: 1, entries: {} };
|
|
@@ -55813,7 +57432,7 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
55813
57432
|
await run;
|
|
55814
57433
|
}
|
|
55815
57434
|
async function writeToggleFile(next) {
|
|
55816
|
-
await mkdir51(
|
|
57435
|
+
await mkdir51(path93.dirname(filePath), { recursive: true });
|
|
55817
57436
|
await writeFile44(filePath, JSON.stringify(next, null, 2), "utf8");
|
|
55818
57437
|
}
|
|
55819
57438
|
async function readPrimary() {
|
|
@@ -55886,8 +57505,8 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
55886
57505
|
}
|
|
55887
57506
|
|
|
55888
57507
|
// ../remnic-core/src/recall-audit.ts
|
|
55889
|
-
import { appendFile as appendFile5, mkdir as mkdir52, readdir as
|
|
55890
|
-
import
|
|
57508
|
+
import { appendFile as appendFile5, mkdir as mkdir52, readdir as readdir28, rm as rm10 } from "fs/promises";
|
|
57509
|
+
import path94 from "path";
|
|
55891
57510
|
function formatIsoDate(ts) {
|
|
55892
57511
|
const normalized = new Date(ts);
|
|
55893
57512
|
if (Number.isNaN(normalized.getTime())) {
|
|
@@ -55897,24 +57516,24 @@ function formatIsoDate(ts) {
|
|
|
55897
57516
|
}
|
|
55898
57517
|
function buildRecallAuditPath(rootDir, ts, sessionKey) {
|
|
55899
57518
|
const safeSessionKey = encodeURIComponent(sessionKey);
|
|
55900
|
-
return
|
|
57519
|
+
return path94.join(rootDir, "transcripts", formatIsoDate(ts), `${safeSessionKey}.jsonl`);
|
|
55901
57520
|
}
|
|
55902
57521
|
async function appendRecallAuditEntry(rootDir, entry) {
|
|
55903
57522
|
const filePath = buildRecallAuditPath(rootDir, entry.ts, entry.sessionKey);
|
|
55904
|
-
await mkdir52(
|
|
57523
|
+
await mkdir52(path94.dirname(filePath), { recursive: true });
|
|
55905
57524
|
await appendFile5(filePath, `${JSON.stringify(entry)}
|
|
55906
57525
|
`, "utf8");
|
|
55907
57526
|
return filePath;
|
|
55908
57527
|
}
|
|
55909
57528
|
async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE__ */ new Date()) {
|
|
55910
|
-
const transcriptsDir =
|
|
57529
|
+
const transcriptsDir = path94.join(rootDir, "transcripts");
|
|
55911
57530
|
const removed = [];
|
|
55912
57531
|
const cutoff = new Date(now);
|
|
55913
57532
|
cutoff.setUTCHours(0, 0, 0, 0);
|
|
55914
57533
|
cutoff.setUTCDate(cutoff.getUTCDate() - Math.max(1, Math.floor(retentionDays)));
|
|
55915
57534
|
let entries;
|
|
55916
57535
|
try {
|
|
55917
|
-
entries = await
|
|
57536
|
+
entries = await readdir28(transcriptsDir, { withFileTypes: true });
|
|
55918
57537
|
} catch {
|
|
55919
57538
|
return removed;
|
|
55920
57539
|
}
|
|
@@ -55923,7 +57542,7 @@ async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE_
|
|
|
55923
57542
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.name)) continue;
|
|
55924
57543
|
const day = /* @__PURE__ */ new Date(`${entry.name}T00:00:00.000Z`);
|
|
55925
57544
|
if (Number.isNaN(day.getTime()) || day >= cutoff) continue;
|
|
55926
|
-
const dirPath =
|
|
57545
|
+
const dirPath = path94.join(transcriptsDir, entry.name);
|
|
55927
57546
|
await rm10(dirPath, { recursive: true, force: true });
|
|
55928
57547
|
removed.push(dirPath);
|
|
55929
57548
|
}
|
|
@@ -55932,7 +57551,7 @@ async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE_
|
|
|
55932
57551
|
|
|
55933
57552
|
// ../remnic-core/src/active-recall.ts
|
|
55934
57553
|
import { appendFile as appendFile6, mkdir as mkdir53 } from "fs/promises";
|
|
55935
|
-
import
|
|
57554
|
+
import path95 from "path";
|
|
55936
57555
|
var ACTIVE_RECALL_CACHE_MAX_ENTRIES = 256;
|
|
55937
57556
|
var NONE_SET = /* @__PURE__ */ new Set([
|
|
55938
57557
|
"",
|
|
@@ -56075,14 +57694,14 @@ ${params.recallExplain}` : null,
|
|
|
56075
57694
|
}
|
|
56076
57695
|
async function appendActiveRecallTranscript(transcriptRoot, input, config, result, queryBundle) {
|
|
56077
57696
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
56078
|
-
const filePath =
|
|
57697
|
+
const filePath = path95.join(
|
|
56079
57698
|
transcriptRoot,
|
|
56080
57699
|
"agents",
|
|
56081
57700
|
sanitizeTranscriptPathSegment(input.agentId),
|
|
56082
57701
|
date,
|
|
56083
57702
|
`${sanitizeTranscriptPathSegment(input.sessionKey)}.jsonl`
|
|
56084
57703
|
);
|
|
56085
|
-
await mkdir53(
|
|
57704
|
+
await mkdir53(path95.dirname(filePath), { recursive: true });
|
|
56086
57705
|
await appendFile6(
|
|
56087
57706
|
filePath,
|
|
56088
57707
|
`${JSON.stringify({
|
|
@@ -56314,8 +57933,8 @@ import { resolvePrincipal as resolvePrincipal2 } from "@remnic/core";
|
|
|
56314
57933
|
// ../remnic-core/src/surfaces/dreams.ts
|
|
56315
57934
|
import { createHash as createHash16 } from "crypto";
|
|
56316
57935
|
import { statSync, watch as watch2 } from "fs";
|
|
56317
|
-
import { mkdir as mkdir54, readFile as
|
|
56318
|
-
import
|
|
57936
|
+
import { mkdir as mkdir54, readFile as readFile50, writeFile as writeFile45 } from "fs/promises";
|
|
57937
|
+
import path96 from "path";
|
|
56319
57938
|
var DIARY_START_MARKER = "<!-- openclaw:dreaming:diary:start -->";
|
|
56320
57939
|
var DIARY_END_MARKER = "<!-- openclaw:dreaming:diary:end -->";
|
|
56321
57940
|
function stableDreamId(params) {
|
|
@@ -56480,7 +58099,7 @@ function createDreamsSurface() {
|
|
|
56480
58099
|
return {
|
|
56481
58100
|
async read(filePath) {
|
|
56482
58101
|
try {
|
|
56483
|
-
const content = await
|
|
58102
|
+
const content = await readFile50(filePath, "utf8");
|
|
56484
58103
|
return parseDreamEntries(content);
|
|
56485
58104
|
} catch (error) {
|
|
56486
58105
|
if (error.code === "ENOENT") {
|
|
@@ -56490,10 +58109,10 @@ function createDreamsSurface() {
|
|
|
56490
58109
|
}
|
|
56491
58110
|
},
|
|
56492
58111
|
async append(filePath, entry) {
|
|
56493
|
-
await mkdir54(
|
|
58112
|
+
await mkdir54(path96.dirname(filePath), { recursive: true });
|
|
56494
58113
|
let content = "";
|
|
56495
58114
|
try {
|
|
56496
|
-
content = await
|
|
58115
|
+
content = await readFile50(filePath, "utf8");
|
|
56497
58116
|
} catch (error) {
|
|
56498
58117
|
if (error.code !== "ENOENT") throw error;
|
|
56499
58118
|
}
|
|
@@ -56511,22 +58130,22 @@ ${ensured.slice(endIndex)}` : `${ensureDiary("")}${block}`;
|
|
|
56511
58130
|
let fileWatcher = null;
|
|
56512
58131
|
let parentWatcher = null;
|
|
56513
58132
|
let timer = null;
|
|
56514
|
-
const watchedName =
|
|
56515
|
-
const watchedDir =
|
|
58133
|
+
const watchedName = path96.basename(filePath);
|
|
58134
|
+
const watchedDir = path96.dirname(filePath);
|
|
56516
58135
|
const resolveParentWatchTarget = () => {
|
|
56517
58136
|
let candidateDir = watchedDir;
|
|
56518
58137
|
while (true) {
|
|
56519
58138
|
try {
|
|
56520
58139
|
if (statSync(candidateDir).isDirectory()) {
|
|
56521
|
-
const relative =
|
|
58140
|
+
const relative = path96.relative(candidateDir, watchedDir);
|
|
56522
58141
|
return {
|
|
56523
58142
|
dir: candidateDir,
|
|
56524
|
-
expectedName: relative.length === 0 ? watchedName : relative.split(
|
|
58143
|
+
expectedName: relative.length === 0 ? watchedName : relative.split(path96.sep)[0] ?? watchedName
|
|
56525
58144
|
};
|
|
56526
58145
|
}
|
|
56527
58146
|
} catch {
|
|
56528
58147
|
}
|
|
56529
|
-
const parentDir =
|
|
58148
|
+
const parentDir = path96.dirname(candidateDir);
|
|
56530
58149
|
if (parentDir === candidateDir) {
|
|
56531
58150
|
return null;
|
|
56532
58151
|
}
|
|
@@ -56591,8 +58210,8 @@ ${ensured.slice(endIndex)}` : `${ensureDiary("")}${block}`;
|
|
|
56591
58210
|
// ../remnic-core/src/surfaces/heartbeat.ts
|
|
56592
58211
|
import { createHash as createHash17 } from "crypto";
|
|
56593
58212
|
import { statSync as statSync2, watch as watch3 } from "fs";
|
|
56594
|
-
import { readFile as
|
|
56595
|
-
import
|
|
58213
|
+
import { readFile as readFile51 } from "fs/promises";
|
|
58214
|
+
import path97 from "path";
|
|
56596
58215
|
function stableHeartbeatId(params) {
|
|
56597
58216
|
const digest = createHash17("sha1").update(
|
|
56598
58217
|
JSON.stringify({
|
|
@@ -56755,7 +58374,7 @@ function createHeartbeatSurface() {
|
|
|
56755
58374
|
return {
|
|
56756
58375
|
async read(filePath) {
|
|
56757
58376
|
try {
|
|
56758
|
-
const content = await
|
|
58377
|
+
const content = await readFile51(filePath, "utf8");
|
|
56759
58378
|
return parseHeartbeatEntries(content);
|
|
56760
58379
|
} catch (error) {
|
|
56761
58380
|
if (error.code === "ENOENT") {
|
|
@@ -56768,22 +58387,22 @@ function createHeartbeatSurface() {
|
|
|
56768
58387
|
let fileWatcher = null;
|
|
56769
58388
|
let parentWatcher = null;
|
|
56770
58389
|
let timer = null;
|
|
56771
|
-
const watchedName =
|
|
56772
|
-
const watchedDir =
|
|
58390
|
+
const watchedName = path97.basename(filePath);
|
|
58391
|
+
const watchedDir = path97.dirname(filePath);
|
|
56773
58392
|
const resolveParentWatchTarget = () => {
|
|
56774
58393
|
let candidateDir = watchedDir;
|
|
56775
58394
|
while (true) {
|
|
56776
58395
|
try {
|
|
56777
58396
|
if (statSync2(candidateDir).isDirectory()) {
|
|
56778
|
-
const relative =
|
|
58397
|
+
const relative = path97.relative(candidateDir, watchedDir);
|
|
56779
58398
|
return {
|
|
56780
58399
|
dir: candidateDir,
|
|
56781
|
-
expectedName: relative.length === 0 ? watchedName : relative.split(
|
|
58400
|
+
expectedName: relative.length === 0 ? watchedName : relative.split(path97.sep)[0] ?? watchedName
|
|
56782
58401
|
};
|
|
56783
58402
|
}
|
|
56784
58403
|
} catch {
|
|
56785
58404
|
}
|
|
56786
|
-
const parentDir =
|
|
58405
|
+
const parentDir = path97.dirname(candidateDir);
|
|
56787
58406
|
if (parentDir === candidateDir) {
|
|
56788
58407
|
return null;
|
|
56789
58408
|
}
|
|
@@ -56869,7 +58488,7 @@ function loadPluginEntryFromFile(pluginId) {
|
|
|
56869
58488
|
try {
|
|
56870
58489
|
const explicitConfigPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH");
|
|
56871
58490
|
const homeDir = resolveHomeDir();
|
|
56872
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
58491
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path98.join(homeDir, ".openclaw", "openclaw.json");
|
|
56873
58492
|
const content = readFileSync6(configPath, "utf-8");
|
|
56874
58493
|
const config = JSON.parse(content);
|
|
56875
58494
|
return resolveRemnicPluginEntry(config, pluginId);
|
|
@@ -56885,7 +58504,7 @@ function loadRawConfigFromFile() {
|
|
|
56885
58504
|
try {
|
|
56886
58505
|
const explicitConfigPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH");
|
|
56887
58506
|
const homeDir = resolveHomeDir();
|
|
56888
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
58507
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path98.join(homeDir, ".openclaw", "openclaw.json");
|
|
56889
58508
|
const content = readFileSync6(configPath, "utf-8");
|
|
56890
58509
|
const config = JSON.parse(content);
|
|
56891
58510
|
return config && typeof config === "object" ? config : void 0;
|
|
@@ -57119,9 +58738,9 @@ var pluginDefinition = {
|
|
|
57119
58738
|
citationsAutoDetect: cfg.citationsAutoDetect
|
|
57120
58739
|
});
|
|
57121
58740
|
globalThis[keys.ACCESS_HTTP_SERVER] = accessHttpServer;
|
|
57122
|
-
const pluginStateDir =
|
|
57123
|
-
const togglePrimaryPath =
|
|
57124
|
-
const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ?
|
|
58741
|
+
const pluginStateDir = path98.join(cfg.memoryDir, "state", "plugins", serviceId);
|
|
58742
|
+
const togglePrimaryPath = path98.join(pluginStateDir, "session-toggles.json");
|
|
58743
|
+
const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ? path98.join(cfg.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0;
|
|
57125
58744
|
const sessionToggleStore = createFileToggleStore(togglePrimaryPath, {
|
|
57126
58745
|
secondaryReadOnlyPath: toggleSecondaryPath
|
|
57127
58746
|
});
|
|
@@ -57143,11 +58762,11 @@ var pluginDefinition = {
|
|
|
57143
58762
|
}
|
|
57144
58763
|
function resolveDreamJournalPath(runtimeWorkspaceDir) {
|
|
57145
58764
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
57146
|
-
return
|
|
58765
|
+
return path98.isAbsolute(cfg.dreaming.journalPath) ? cfg.dreaming.journalPath : path98.join(workspaceRoot, cfg.dreaming.journalPath);
|
|
57147
58766
|
}
|
|
57148
58767
|
function resolveHeartbeatJournalPath(runtimeWorkspaceDir) {
|
|
57149
58768
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
57150
|
-
return
|
|
58769
|
+
return path98.isAbsolute(cfg.heartbeat.journalPath) ? cfg.heartbeat.journalPath : path98.join(workspaceRoot, cfg.heartbeat.journalPath);
|
|
57151
58770
|
}
|
|
57152
58771
|
function queueDreamSurfaceSync(runtimeWorkspaceDir) {
|
|
57153
58772
|
if (!cfg.dreaming.enabled) return Promise.resolve();
|
|
@@ -57305,7 +58924,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
57305
58924
|
timeoutMs: cfg.activeRecallTimeoutMs,
|
|
57306
58925
|
cacheTtlMs: cfg.activeRecallCacheTtlMs,
|
|
57307
58926
|
persistTranscripts: cfg.activeRecallPersistTranscripts,
|
|
57308
|
-
transcriptDir:
|
|
58927
|
+
transcriptDir: path98.isAbsolute(cfg.activeRecallTranscriptDir) ? cfg.activeRecallTranscriptDir : path98.join(pluginStateDir, cfg.activeRecallTranscriptDir),
|
|
57309
58928
|
entityGraphDepth: cfg.activeRecallEntityGraphDepth,
|
|
57310
58929
|
includeCausalTrajectories: cfg.activeRecallIncludeCausalTrajectories,
|
|
57311
58930
|
includeDaySummary: cfg.activeRecallIncludeDaySummary,
|
|
@@ -58173,6 +59792,219 @@ Keep the reflection grounded in the evidence below.
|
|
|
58173
59792
|
const runtimeAgentId = typeof runtimeAgent?.id === "string" && runtimeAgent.id.length > 0 ? runtimeAgent.id : void 0;
|
|
58174
59793
|
const capabilityAgentIds = runtimeAgentId ? [runtimeAgentId] : ["generalist"];
|
|
58175
59794
|
const capabilityWorkspaceDir = (typeof runtimeAgent?.workspaceDir === "string" && runtimeAgent.workspaceDir.length > 0 ? runtimeAgent.workspaceDir : void 0) ?? orchestrator.config.workspaceDir ?? defaultWorkspaceDir();
|
|
59795
|
+
const remnicUsesQmd = (orchestrator.config.searchBackend ?? "qmd") === "qmd" && orchestrator.config.qmdEnabled !== false;
|
|
59796
|
+
const remnicQmdCommand = typeof orchestrator.config.qmdPath === "string" && orchestrator.config.qmdPath.trim().length > 0 ? orchestrator.config.qmdPath.trim() : "qmd";
|
|
59797
|
+
const readAllowedRoots = [
|
|
59798
|
+
orchestrator.config.memoryDir,
|
|
59799
|
+
capabilityWorkspaceDir ? path98.join(capabilityWorkspaceDir, "memory") : void 0
|
|
59800
|
+
].filter((root) => typeof root === "string" && root.length > 0);
|
|
59801
|
+
const canonicalizeRootForContainment = async (rawPath) => {
|
|
59802
|
+
const resolved = path98.resolve(rawPath);
|
|
59803
|
+
try {
|
|
59804
|
+
return path98.normalize(await realpath5(resolved));
|
|
59805
|
+
} catch {
|
|
59806
|
+
return path98.normalize(resolved);
|
|
59807
|
+
}
|
|
59808
|
+
};
|
|
59809
|
+
const canonicalizeForRead = async (rawPath) => {
|
|
59810
|
+
const resolved = path98.resolve(rawPath);
|
|
59811
|
+
const real = await realpath5(resolved);
|
|
59812
|
+
return path98.normalize(real);
|
|
59813
|
+
};
|
|
59814
|
+
const readAllowedCanonicalRootsPromise = Promise.all(
|
|
59815
|
+
readAllowedRoots.map((root) => canonicalizeRootForContainment(root))
|
|
59816
|
+
);
|
|
59817
|
+
const isWithinAllowedRoot = async (candidatePath) => {
|
|
59818
|
+
let canonicalCandidatePath;
|
|
59819
|
+
try {
|
|
59820
|
+
canonicalCandidatePath = await canonicalizeForRead(candidatePath);
|
|
59821
|
+
} catch {
|
|
59822
|
+
return false;
|
|
59823
|
+
}
|
|
59824
|
+
const canonicalRoots = await readAllowedCanonicalRootsPromise;
|
|
59825
|
+
return canonicalRoots.some((root) => {
|
|
59826
|
+
const relative = path98.relative(root, canonicalCandidatePath);
|
|
59827
|
+
return relative === "" || !relative.startsWith("..") && !path98.isAbsolute(relative);
|
|
59828
|
+
});
|
|
59829
|
+
};
|
|
59830
|
+
const normalizeWorkspacePath = (rawPath) => {
|
|
59831
|
+
if (!rawPath || typeof rawPath !== "string") return "memory";
|
|
59832
|
+
const resolved = path98.isAbsolute(rawPath) ? path98.resolve(rawPath) : path98.resolve(capabilityWorkspaceDir, rawPath);
|
|
59833
|
+
const relative = path98.relative(capabilityWorkspaceDir, resolved);
|
|
59834
|
+
return relative && !relative.startsWith("..") && !path98.isAbsolute(relative) ? relative : rawPath;
|
|
59835
|
+
};
|
|
59836
|
+
const relativizeToMemoryRoot = (rawPath) => {
|
|
59837
|
+
if (!rawPath || typeof rawPath !== "string") return "memory";
|
|
59838
|
+
const resolved = path98.isAbsolute(rawPath) ? path98.resolve(rawPath) : path98.resolve(capabilityWorkspaceDir, rawPath);
|
|
59839
|
+
for (const root of readAllowedRoots) {
|
|
59840
|
+
const relative = path98.relative(root, resolved);
|
|
59841
|
+
if (relative !== "" && !relative.startsWith("..") && !path98.isAbsolute(relative)) {
|
|
59842
|
+
return relative;
|
|
59843
|
+
}
|
|
59844
|
+
}
|
|
59845
|
+
return normalizeWorkspacePath(rawPath);
|
|
59846
|
+
};
|
|
59847
|
+
const resolveReadablePath = async (requestedPath) => {
|
|
59848
|
+
const candidateAbsolutePaths = path98.isAbsolute(requestedPath) ? [path98.resolve(requestedPath)] : readAllowedRoots.map((root) => path98.resolve(root, requestedPath));
|
|
59849
|
+
let canonicalPath;
|
|
59850
|
+
let lastError;
|
|
59851
|
+
for (const absolutePath of candidateAbsolutePaths) {
|
|
59852
|
+
try {
|
|
59853
|
+
canonicalPath = await canonicalizeForRead(absolutePath);
|
|
59854
|
+
break;
|
|
59855
|
+
} catch (err) {
|
|
59856
|
+
lastError = err;
|
|
59857
|
+
}
|
|
59858
|
+
}
|
|
59859
|
+
if (canonicalPath === void 0) {
|
|
59860
|
+
throw new Error(
|
|
59861
|
+
`memory read rejected (path unresolvable): ${requestedPath}`
|
|
59862
|
+
);
|
|
59863
|
+
}
|
|
59864
|
+
const canonicalRoots = await readAllowedCanonicalRootsPromise;
|
|
59865
|
+
const contained = canonicalRoots.some((root) => {
|
|
59866
|
+
const relative = path98.relative(root, canonicalPath);
|
|
59867
|
+
return relative === "" || !relative.startsWith("..") && !path98.isAbsolute(relative);
|
|
59868
|
+
});
|
|
59869
|
+
if (!contained) {
|
|
59870
|
+
throw new Error(`memory read outside allowed roots: ${requestedPath}`);
|
|
59871
|
+
}
|
|
59872
|
+
if (!canonicalPath.toLowerCase().endsWith(".md")) {
|
|
59873
|
+
throw new Error(
|
|
59874
|
+
`memory read restricted to .md files: ${requestedPath}`
|
|
59875
|
+
);
|
|
59876
|
+
}
|
|
59877
|
+
return canonicalPath;
|
|
59878
|
+
};
|
|
59879
|
+
const remnicMemoryRuntime = {
|
|
59880
|
+
async getMemorySearchManager(_params) {
|
|
59881
|
+
return {
|
|
59882
|
+
manager: {
|
|
59883
|
+
async search(query, opts) {
|
|
59884
|
+
const namespace = typeof orchestrator.resolveSelfNamespace === "function" ? orchestrator.resolveSelfNamespace(opts?.sessionKey) : void 0;
|
|
59885
|
+
const resolvedMode = opts?.qmdSearchModeOverride === "vsearch" ? "vector" : opts?.qmdSearchModeOverride === "query" ? "search" : opts?.qmdSearchModeOverride ?? "search";
|
|
59886
|
+
const rawResults = await orchestrator.searchAcrossNamespaces({
|
|
59887
|
+
query,
|
|
59888
|
+
maxResults: opts?.maxResults,
|
|
59889
|
+
namespaces: namespace ? [namespace] : void 0,
|
|
59890
|
+
mode: resolvedMode
|
|
59891
|
+
});
|
|
59892
|
+
const isArtifactPath2 = (p) => /(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(p);
|
|
59893
|
+
return rawResults.filter((result) => {
|
|
59894
|
+
const candidate = result;
|
|
59895
|
+
const p = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : "";
|
|
59896
|
+
return !isArtifactPath2(p);
|
|
59897
|
+
}).map((result, index) => {
|
|
59898
|
+
const candidate = result;
|
|
59899
|
+
const rawPath = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : `memory-${index + 1}`;
|
|
59900
|
+
const absolutePath = path98.isAbsolute(rawPath) ? path98.resolve(rawPath) : (() => {
|
|
59901
|
+
for (const root of readAllowedRoots) {
|
|
59902
|
+
const candidateAbs = path98.resolve(root, rawPath);
|
|
59903
|
+
const relative = path98.relative(root, candidateAbs);
|
|
59904
|
+
if (!relative.startsWith("..") && !path98.isAbsolute(relative)) {
|
|
59905
|
+
return candidateAbs;
|
|
59906
|
+
}
|
|
59907
|
+
}
|
|
59908
|
+
return path98.resolve(capabilityWorkspaceDir, rawPath);
|
|
59909
|
+
})();
|
|
59910
|
+
const normalizedPath = relativizeToMemoryRoot(rawPath);
|
|
59911
|
+
const startLine = typeof candidate.startLine === "number" && Number.isFinite(candidate.startLine) ? Math.max(1, Math.floor(candidate.startLine)) : 1;
|
|
59912
|
+
const endLine = typeof candidate.endLine === "number" && Number.isFinite(candidate.endLine) ? Math.max(startLine, Math.floor(candidate.endLine)) : startLine;
|
|
59913
|
+
return {
|
|
59914
|
+
path: absolutePath,
|
|
59915
|
+
startLine,
|
|
59916
|
+
endLine,
|
|
59917
|
+
score: typeof candidate.score === "number" && Number.isFinite(candidate.score) ? candidate.score : 0,
|
|
59918
|
+
snippet: typeof candidate.snippet === "string" ? candidate.snippet : typeof candidate.text === "string" ? candidate.text : "",
|
|
59919
|
+
source: normalizedPath.includes("sessions/") ? "sessions" : "memory",
|
|
59920
|
+
citation: normalizedPath
|
|
59921
|
+
};
|
|
59922
|
+
}).filter(
|
|
59923
|
+
(result) => typeof opts?.minScore === "number" && Number.isFinite(opts.minScore) ? result.score >= opts.minScore : true
|
|
59924
|
+
);
|
|
59925
|
+
},
|
|
59926
|
+
async readFile(params) {
|
|
59927
|
+
const requestedPath = normalizeWorkspacePath(params.relPath);
|
|
59928
|
+
const absolutePath = await resolveReadablePath(params.relPath);
|
|
59929
|
+
const text = await readFile52(absolutePath, "utf-8");
|
|
59930
|
+
const allLines = text.split(/\r?\n/);
|
|
59931
|
+
const from = typeof params.from === "number" ? Math.max(1, Math.floor(params.from)) : 1;
|
|
59932
|
+
const lines = typeof params.lines === "number" && Number.isFinite(params.lines) ? Math.max(1, Math.floor(params.lines)) : void 0;
|
|
59933
|
+
const startIndex = from - 1;
|
|
59934
|
+
const endIndex = typeof lines === "number" ? startIndex + lines : allLines.length;
|
|
59935
|
+
const slice = allLines.slice(startIndex, endIndex);
|
|
59936
|
+
const truncated = endIndex < allLines.length;
|
|
59937
|
+
return {
|
|
59938
|
+
text: slice.join("\n"),
|
|
59939
|
+
path: requestedPath,
|
|
59940
|
+
truncated: truncated || void 0,
|
|
59941
|
+
from,
|
|
59942
|
+
lines,
|
|
59943
|
+
nextFrom: truncated ? endIndex + 1 : void 0
|
|
59944
|
+
};
|
|
59945
|
+
},
|
|
59946
|
+
status() {
|
|
59947
|
+
const qmdAvailable = remnicUsesQmd && typeof orchestrator.qmd?.isAvailable === "function" ? Boolean(orchestrator.qmd.isAvailable()) : !remnicUsesQmd;
|
|
59948
|
+
const qmdDebug = typeof orchestrator.qmd?.debugStatus === "function" ? orchestrator.qmd.debugStatus() : void 0;
|
|
59949
|
+
return {
|
|
59950
|
+
backend: remnicUsesQmd ? "qmd" : "builtin",
|
|
59951
|
+
provider: remnicUsesQmd ? "qmd" : "builtin",
|
|
59952
|
+
requestedProvider: remnicUsesQmd ? "qmd" : "builtin",
|
|
59953
|
+
model: remnicUsesQmd ? remnicQmdCommand : "builtin",
|
|
59954
|
+
dirty: false,
|
|
59955
|
+
workspaceDir: capabilityWorkspaceDir,
|
|
59956
|
+
dbPath: orchestrator.config.memoryDir,
|
|
59957
|
+
sources: ["memory"],
|
|
59958
|
+
sourceCounts: [],
|
|
59959
|
+
vector: remnicUsesQmd ? {
|
|
59960
|
+
enabled: true,
|
|
59961
|
+
available: qmdAvailable
|
|
59962
|
+
} : {
|
|
59963
|
+
enabled: false
|
|
59964
|
+
},
|
|
59965
|
+
fts: {
|
|
59966
|
+
enabled: true,
|
|
59967
|
+
available: qmdAvailable
|
|
59968
|
+
},
|
|
59969
|
+
custom: {
|
|
59970
|
+
remnic: {
|
|
59971
|
+
qmdAvailable,
|
|
59972
|
+
qmdDebug,
|
|
59973
|
+
memoryDir: orchestrator.config.memoryDir
|
|
59974
|
+
}
|
|
59975
|
+
}
|
|
59976
|
+
};
|
|
59977
|
+
},
|
|
59978
|
+
async sync(_params2) {
|
|
59979
|
+
if (remnicUsesQmd && typeof orchestrator.qmd?.update === "function") {
|
|
59980
|
+
await orchestrator.qmd.update();
|
|
59981
|
+
}
|
|
59982
|
+
},
|
|
59983
|
+
async probeEmbeddingAvailability() {
|
|
59984
|
+
if (!remnicUsesQmd) return { ok: true };
|
|
59985
|
+
const qmdAvailable = typeof orchestrator.qmd?.isAvailable === "function" ? Boolean(orchestrator.qmd.isAvailable()) : false;
|
|
59986
|
+
if (qmdAvailable) return { ok: true };
|
|
59987
|
+
const qmdDebug = typeof orchestrator.qmd?.debugStatus === "function" ? orchestrator.qmd.debugStatus() : void 0;
|
|
59988
|
+
return {
|
|
59989
|
+
ok: false,
|
|
59990
|
+
error: qmdDebug ?? "Remnic QMD backend unavailable"
|
|
59991
|
+
};
|
|
59992
|
+
},
|
|
59993
|
+
async probeVectorAvailability() {
|
|
59994
|
+
if (!remnicUsesQmd) return false;
|
|
59995
|
+
return typeof orchestrator.qmd?.isAvailable === "function" ? Boolean(orchestrator.qmd.isAvailable()) : false;
|
|
59996
|
+
},
|
|
59997
|
+
async close() {
|
|
59998
|
+
}
|
|
59999
|
+
}
|
|
60000
|
+
};
|
|
60001
|
+
},
|
|
60002
|
+
resolveMemoryBackendConfig(_params) {
|
|
60003
|
+
return remnicUsesQmd ? { backend: "qmd", qmd: { command: remnicQmdCommand } } : { backend: "builtin" };
|
|
60004
|
+
},
|
|
60005
|
+
async closeAllMemorySearchManagers() {
|
|
60006
|
+
}
|
|
60007
|
+
};
|
|
58176
60008
|
const memoryCapability = {
|
|
58177
60009
|
// Include the promptBuilder so runtimes that treat unified capability
|
|
58178
60010
|
// registration as authoritative (SDK >=2026.4.5) continue to inject
|
|
@@ -58180,6 +60012,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58180
60012
|
// Respect promptInjectionAllowed policy — omit promptBuilder if injection
|
|
58181
60013
|
// is disabled, so the capability only provides publicArtifacts.
|
|
58182
60014
|
...promptInjectionAllowed ? { promptBuilder: capabilityPromptBuilder } : {},
|
|
60015
|
+
runtime: remnicMemoryRuntime,
|
|
58183
60016
|
publicArtifacts: {
|
|
58184
60017
|
listArtifacts: async (_params) => {
|
|
58185
60018
|
try {
|
|
@@ -58197,7 +60030,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58197
60030
|
};
|
|
58198
60031
|
api.registerMemoryCapability(memoryCapability);
|
|
58199
60032
|
const builderDesc = !promptInjectionAllowed ? " (promptBuilder omitted \u2014 injection disabled by policy)" : memoryPromptBuilder ? " and promptBuilder (from registerMemoryPromptSection)" : " and promptBuilder (capability-only fallback)";
|
|
58200
|
-
log.info(`registered memory capability with publicArtifacts provider${builderDesc}`);
|
|
60033
|
+
log.info(`registered memory capability with runtime and publicArtifacts provider${builderDesc}`);
|
|
58201
60034
|
}
|
|
58202
60035
|
api.on(
|
|
58203
60036
|
"agent_end",
|
|
@@ -58494,7 +60327,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58494
60327
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
58495
60328
|
);
|
|
58496
60329
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
58497
|
-
const signalPath =
|
|
60330
|
+
const signalPath = path98.join(
|
|
58498
60331
|
workspaceDir,
|
|
58499
60332
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
58500
60333
|
);
|
|
@@ -58746,7 +60579,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58746
60579
|
}
|
|
58747
60580
|
async function ensureHourlySummaryCron(api2) {
|
|
58748
60581
|
const jobId = "engram-hourly-summary";
|
|
58749
|
-
const cronFilePath =
|
|
60582
|
+
const cronFilePath = path98.join(
|
|
58750
60583
|
os6.homedir(),
|
|
58751
60584
|
".openclaw",
|
|
58752
60585
|
"cron",
|
|
@@ -58758,7 +60591,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58758
60591
|
jobs: []
|
|
58759
60592
|
};
|
|
58760
60593
|
try {
|
|
58761
|
-
const content = await
|
|
60594
|
+
const content = await readFile52(cronFilePath, "utf-8");
|
|
58762
60595
|
jobsData = JSON.parse(content);
|
|
58763
60596
|
} catch {
|
|
58764
60597
|
}
|
|
@@ -58773,7 +60606,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58773
60606
|
id: jobId,
|
|
58774
60607
|
agentId: "generalist",
|
|
58775
60608
|
model,
|
|
58776
|
-
name: "
|
|
60609
|
+
name: "Remnic Hourly Summary",
|
|
58777
60610
|
enabled: true,
|
|
58778
60611
|
createdAtMs: Date.now(),
|
|
58779
60612
|
updatedAtMs: Date.now(),
|
|
@@ -58789,7 +60622,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58789
60622
|
kind: "agentTurn",
|
|
58790
60623
|
timeoutSeconds: 120,
|
|
58791
60624
|
thinking: "off",
|
|
58792
|
-
message: "You are OpenClaw automation.\n\nTask: Generate
|
|
60625
|
+
message: "You are OpenClaw automation.\n\nTask: Generate Remnic hourly summaries.\n\nCall the tool `memory_summarize_hourly` with empty params.\n\nOutput policy:\n- If you generated summaries successfully: output exactly NO_REPLY.\n- If there is an error: output one concise line describing it.\n\nRules:\n- Do NOT send anything to Discord.\n- Never print secrets.\n"
|
|
58793
60626
|
},
|
|
58794
60627
|
delivery: { mode: "none" },
|
|
58795
60628
|
state: {}
|
|
@@ -58959,39 +60792,33 @@ Keep the reflection grounded in the evidence below.
|
|
|
58959
60792
|
stop: async () => {
|
|
58960
60793
|
if (!didCountStart) return;
|
|
58961
60794
|
didCountStart = false;
|
|
60795
|
+
const initPromiseAtStopEntry = globalThis[keys.INIT_PROMISE];
|
|
60796
|
+
try {
|
|
60797
|
+
await orchestrator.deferredReady;
|
|
60798
|
+
} catch {
|
|
60799
|
+
}
|
|
58962
60800
|
const remainingServices = Math.max(
|
|
58963
60801
|
0,
|
|
58964
60802
|
(globalThis[CLI_ACTIVE_SERVICE_COUNT] || 0) - 1
|
|
58965
60803
|
);
|
|
58966
60804
|
globalThis[CLI_ACTIVE_SERVICE_COUNT] = remainingServices;
|
|
58967
|
-
|
|
58968
|
-
activeOpikExporter?.unsubscribe();
|
|
58969
|
-
} catch (err) {
|
|
58970
|
-
log.debug(`engram opik exporter unsubscribe failed: ${err}`);
|
|
58971
|
-
}
|
|
58972
|
-
activeOpikExporter = null;
|
|
58973
|
-
try {
|
|
58974
|
-
await accessHttpServer.stop();
|
|
58975
|
-
} catch (err) {
|
|
58976
|
-
log.debug(`engram access HTTP stop failed: ${err}`);
|
|
58977
|
-
}
|
|
58978
|
-
stopDreamWatcher?.();
|
|
58979
|
-
stopDreamWatcher = null;
|
|
58980
|
-
stopHeartbeatWatcher?.();
|
|
58981
|
-
stopHeartbeatWatcher = null;
|
|
58982
|
-
removeDreamingObserver?.();
|
|
58983
|
-
removeDreamingObserver = null;
|
|
58984
|
-
delete globalThis[keys.ACCESS_HTTP_SERVER];
|
|
58985
|
-
delete globalThis[keys.ACCESS_SERVICE];
|
|
58986
|
-
const currentInitPromise = globalThis[keys.INIT_PROMISE];
|
|
60805
|
+
const currentInitPromise = initPromiseAtStopEntry;
|
|
58987
60806
|
let secondaryTookOver = false;
|
|
60807
|
+
if (!currentInitPromise && globalThis[keys.INIT_PROMISE]) {
|
|
60808
|
+
secondaryTookOver = true;
|
|
60809
|
+
}
|
|
58988
60810
|
if (!currentInitPromise) {
|
|
58989
|
-
|
|
58990
|
-
|
|
58991
|
-
|
|
58992
|
-
|
|
60811
|
+
if (!secondaryTookOver) {
|
|
60812
|
+
globalThis[keys.REGISTERED_GUARD] = false;
|
|
60813
|
+
if (remainingServices === 0) {
|
|
60814
|
+
globalThis[CLI_REGISTERED_GUARD] = false;
|
|
60815
|
+
globalThis[SESSION_COMMANDS_REGISTERED_GUARD] = false;
|
|
60816
|
+
}
|
|
58993
60817
|
}
|
|
58994
60818
|
} else {
|
|
60819
|
+
if (globalThis[keys.INIT_PROMISE] && globalThis[keys.INIT_PROMISE] !== currentInitPromise) {
|
|
60820
|
+
secondaryTookOver = true;
|
|
60821
|
+
}
|
|
58995
60822
|
try {
|
|
58996
60823
|
await currentInitPromise;
|
|
58997
60824
|
} catch {
|
|
@@ -59001,6 +60828,27 @@ Keep the reflection grounded in the evidence below.
|
|
|
59001
60828
|
secondaryTookOver = true;
|
|
59002
60829
|
}
|
|
59003
60830
|
}
|
|
60831
|
+
if (!secondaryTookOver) {
|
|
60832
|
+
try {
|
|
60833
|
+
activeOpikExporter?.unsubscribe();
|
|
60834
|
+
} catch (err) {
|
|
60835
|
+
log.debug(`engram opik exporter unsubscribe failed: ${err}`);
|
|
60836
|
+
}
|
|
60837
|
+
activeOpikExporter = null;
|
|
60838
|
+
try {
|
|
60839
|
+
await accessHttpServer.stop();
|
|
60840
|
+
} catch (err) {
|
|
60841
|
+
log.debug(`engram access HTTP stop failed: ${err}`);
|
|
60842
|
+
}
|
|
60843
|
+
stopDreamWatcher?.();
|
|
60844
|
+
stopDreamWatcher = null;
|
|
60845
|
+
stopHeartbeatWatcher?.();
|
|
60846
|
+
stopHeartbeatWatcher = null;
|
|
60847
|
+
removeDreamingObserver?.();
|
|
60848
|
+
removeDreamingObserver = null;
|
|
60849
|
+
delete globalThis[keys.ACCESS_HTTP_SERVER];
|
|
60850
|
+
delete globalThis[keys.ACCESS_SERVICE];
|
|
60851
|
+
}
|
|
59004
60852
|
if (!secondaryTookOver) {
|
|
59005
60853
|
globalThis[keys.HOOK_APIS] = /* @__PURE__ */ new WeakSet();
|
|
59006
60854
|
}
|
|
@@ -59036,7 +60884,7 @@ function extractTextContent2(msg) {
|
|
|
59036
60884
|
// src/bridge.ts
|
|
59037
60885
|
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
59038
60886
|
import * as childProcess from "child_process";
|
|
59039
|
-
import
|
|
60887
|
+
import path99 from "path";
|
|
59040
60888
|
|
|
59041
60889
|
// src/service-candidates.ts
|
|
59042
60890
|
function firstSuccessfulResult(candidates, attempt) {
|
|
@@ -59063,17 +60911,17 @@ function readCompatEnv(primary, legacy) {
|
|
|
59063
60911
|
function configPathCandidates() {
|
|
59064
60912
|
const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
|
|
59065
60913
|
return [
|
|
59066
|
-
...envPath ? [
|
|
59067
|
-
|
|
59068
|
-
|
|
59069
|
-
|
|
59070
|
-
|
|
60914
|
+
...envPath ? [path99.resolve(envPath)] : [],
|
|
60915
|
+
path99.join(resolveHomeDir2(), ".config", "remnic", "config.json"),
|
|
60916
|
+
path99.join(resolveHomeDir2(), ".config", "engram", "config.json"),
|
|
60917
|
+
path99.join(process.cwd(), "remnic.config.json"),
|
|
60918
|
+
path99.join(process.cwd(), "engram.config.json")
|
|
59071
60919
|
];
|
|
59072
60920
|
}
|
|
59073
60921
|
function isDaemonRunning() {
|
|
59074
60922
|
for (const pidFile of [
|
|
59075
|
-
|
|
59076
|
-
|
|
60923
|
+
path99.join(resolveHomeDir2(), ".remnic", "server.pid"),
|
|
60924
|
+
path99.join(resolveHomeDir2(), ".engram", "server.pid")
|
|
59077
60925
|
]) {
|
|
59078
60926
|
try {
|
|
59079
60927
|
const pid = parseInt(readFileSync7(pidFile, "utf8").trim(), 10);
|
|
@@ -59146,8 +60994,8 @@ function detectBridgeMode() {
|
|
|
59146
60994
|
}
|
|
59147
60995
|
function loadAnyToken() {
|
|
59148
60996
|
const tokenPaths = [
|
|
59149
|
-
|
|
59150
|
-
|
|
60997
|
+
path99.join(resolveHomeDir2(), ".remnic", "tokens.json"),
|
|
60998
|
+
path99.join(resolveHomeDir2(), ".engram", "tokens.json")
|
|
59151
60999
|
];
|
|
59152
61000
|
for (const tokensPath of tokenPaths) {
|
|
59153
61001
|
if (!existsSync12(tokensPath)) continue;
|