@remnic/plugin-openclaw 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{causal-consolidation-Z3PHIFTL.js → causal-consolidation-EBLROS42.js} +2 -2
- package/dist/{causal-retrieval-5UPIKZ4I.js → causal-retrieval-3BKBXVXD.js} +1 -1
- package/dist/{chunk-Y65XJVI3.js → chunk-GUKYM4XZ.js} +2 -2
- package/dist/{chunk-5VTGFKKU.js → chunk-KPMXWORS.js} +17 -0
- package/dist/{chunk-RMFPW4VK.js → chunk-LN5UZQVG.js} +10 -0
- package/dist/{chunk-J2FCINY7.js → chunk-QHMR3D7U.js} +1 -1
- package/dist/{engine-2TLD4YSC.js → engine-BU6GNUJ5.js} +2 -2
- package/dist/index.js +1948 -523
- package/dist/{storage-HW6SRQCK.js → storage-BA6OBLMK.js} +1 -1
- package/openclaw.plugin.json +168 -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-GUKYM4XZ.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,
|
|
@@ -233,6 +235,18 @@ function coerceBool(value) {
|
|
|
233
235
|
function coerceInstallExtension(value) {
|
|
234
236
|
return coerceBool(value);
|
|
235
237
|
}
|
|
238
|
+
function coerceNumber(value) {
|
|
239
|
+
if (typeof value === "number") {
|
|
240
|
+
return Number.isFinite(value) ? value : void 0;
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "string") {
|
|
243
|
+
const trimmed = value.trim();
|
|
244
|
+
if (trimmed.length === 0) return void 0;
|
|
245
|
+
const n = Number(trimmed);
|
|
246
|
+
return Number.isFinite(n) ? n : void 0;
|
|
247
|
+
}
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
236
250
|
|
|
237
251
|
// ../remnic-core/src/config.ts
|
|
238
252
|
var DEFAULT_MEMORY_DIR = path2.join(
|
|
@@ -327,7 +341,8 @@ var VALID_MEMORY_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
327
341
|
"commitment",
|
|
328
342
|
"moment",
|
|
329
343
|
"skill",
|
|
330
|
-
"rule"
|
|
344
|
+
"rule",
|
|
345
|
+
"procedure"
|
|
331
346
|
]);
|
|
332
347
|
var DEFAULT_BEHAVIOR_LOOP_PROTECTED_PARAMS = [
|
|
333
348
|
"maxMemoryTokens",
|
|
@@ -494,6 +509,19 @@ function parseConfig(raw) {
|
|
|
494
509
|
) ? rawCodexCompat.compactionFlushMode : "auto",
|
|
495
510
|
fingerprintDedup: rawCodexCompat.fingerprintDedup !== false
|
|
496
511
|
};
|
|
512
|
+
const rawProcedural = cfg.procedural && typeof cfg.procedural === "object" && !Array.isArray(cfg.procedural) ? cfg.procedural : {};
|
|
513
|
+
const proceduralMinRaw = typeof rawProcedural.minOccurrences === "number" && Number.isFinite(rawProcedural.minOccurrences) ? Math.floor(rawProcedural.minOccurrences) : 3;
|
|
514
|
+
const procedural = {
|
|
515
|
+
enabled: coerceBool(rawProcedural.enabled) === true,
|
|
516
|
+
/** 0 disables miner emission threshold (no clusters qualify). */
|
|
517
|
+
minOccurrences: Math.min(1e3, Math.max(0, proceduralMinRaw)),
|
|
518
|
+
successFloor: typeof rawProcedural.successFloor === "number" && Number.isFinite(rawProcedural.successFloor) && rawProcedural.successFloor >= 0 && rawProcedural.successFloor <= 1 ? rawProcedural.successFloor : 0.7,
|
|
519
|
+
autoPromoteOccurrences: typeof rawProcedural.autoPromoteOccurrences === "number" && Number.isFinite(rawProcedural.autoPromoteOccurrences) ? rawProcedural.autoPromoteOccurrences <= 0 ? 0 : Math.min(1e4, Math.max(1, Math.floor(rawProcedural.autoPromoteOccurrences))) : 8,
|
|
520
|
+
autoPromoteEnabled: coerceBool(rawProcedural.autoPromoteEnabled) === true,
|
|
521
|
+
lookbackDays: typeof rawProcedural.lookbackDays === "number" && Number.isFinite(rawProcedural.lookbackDays) ? Math.min(3650, Math.max(1, Math.floor(rawProcedural.lookbackDays))) : 30,
|
|
522
|
+
proceduralMiningCronAutoRegister: coerceBool(rawProcedural.proceduralMiningCronAutoRegister) === true,
|
|
523
|
+
recallMaxProcedures: typeof rawProcedural.recallMaxProcedures === "number" && Number.isFinite(rawProcedural.recallMaxProcedures) ? Math.min(10, Math.max(1, Math.floor(rawProcedural.recallMaxProcedures))) : 3
|
|
524
|
+
};
|
|
497
525
|
const memoryDir = typeof cfg.memoryDir === "string" && cfg.memoryDir.length > 0 ? cfg.memoryDir : DEFAULT_MEMORY_DIR;
|
|
498
526
|
const rawIdentityInjectionMode = cfg.identityInjectionMode;
|
|
499
527
|
const identityInjectionMode = rawIdentityInjectionMode && VALID_IDENTITY_INJECTION_MODES.includes(rawIdentityInjectionMode) ? rawIdentityInjectionMode : "recovery_only";
|
|
@@ -681,6 +709,25 @@ function parseConfig(raw) {
|
|
|
681
709
|
// On by default
|
|
682
710
|
temporalSupersessionIncludeInRecall: cfg.temporalSupersessionIncludeInRecall === true,
|
|
683
711
|
// Off by default
|
|
712
|
+
// Direct-answer retrieval tier (issue #518)
|
|
713
|
+
recallDirectAnswerEnabled: coerceBool(cfg.recallDirectAnswerEnabled) ?? false,
|
|
714
|
+
recallDirectAnswerTokenOverlapFloor: (() => {
|
|
715
|
+
const n = coerceNumber(cfg.recallDirectAnswerTokenOverlapFloor);
|
|
716
|
+
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.55;
|
|
717
|
+
})(),
|
|
718
|
+
recallDirectAnswerImportanceFloor: (() => {
|
|
719
|
+
const n = coerceNumber(cfg.recallDirectAnswerImportanceFloor);
|
|
720
|
+
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.7;
|
|
721
|
+
})(),
|
|
722
|
+
recallDirectAnswerAmbiguityMargin: (() => {
|
|
723
|
+
const n = coerceNumber(cfg.recallDirectAnswerAmbiguityMargin);
|
|
724
|
+
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.15;
|
|
725
|
+
})(),
|
|
726
|
+
recallDirectAnswerEligibleTaxonomyBuckets: Array.isArray(
|
|
727
|
+
cfg.recallDirectAnswerEligibleTaxonomyBuckets
|
|
728
|
+
) ? cfg.recallDirectAnswerEligibleTaxonomyBuckets.filter(
|
|
729
|
+
(v) => typeof v === "string" && v.length > 0
|
|
730
|
+
) : ["decisions", "principles", "conventions", "runbooks", "entities"],
|
|
684
731
|
// Memory Linking (Phase 3A)
|
|
685
732
|
memoryLinkingEnabled: cfg.memoryLinkingEnabled === true,
|
|
686
733
|
// Off by default initially
|
|
@@ -756,6 +803,7 @@ function parseConfig(raw) {
|
|
|
756
803
|
activeRecallAttachRecallExplain: cfg.activeRecallAttachRecallExplain === true,
|
|
757
804
|
activeRecallAllowChainedActiveMemory: cfg.activeRecallAllowChainedActiveMemory === true,
|
|
758
805
|
dreaming,
|
|
806
|
+
procedural,
|
|
759
807
|
heartbeat,
|
|
760
808
|
slotBehavior,
|
|
761
809
|
codexCompat,
|
|
@@ -825,7 +873,7 @@ function parseConfig(raw) {
|
|
|
825
873
|
semanticConsolidationMinClusterSize: typeof cfg.semanticConsolidationMinClusterSize === "number" ? Math.max(2, Math.floor(cfg.semanticConsolidationMinClusterSize)) : 3,
|
|
826
874
|
semanticConsolidationExcludeCategories: Array.isArray(cfg.semanticConsolidationExcludeCategories) ? cfg.semanticConsolidationExcludeCategories.filter(
|
|
827
875
|
(c) => typeof c === "string" && c.length > 0
|
|
828
|
-
) : ["correction", "commitment"],
|
|
876
|
+
) : ["correction", "commitment", "procedure"],
|
|
829
877
|
semanticConsolidationIntervalHours: typeof cfg.semanticConsolidationIntervalHours === "number" ? Math.max(1, Math.floor(cfg.semanticConsolidationIntervalHours)) : 168,
|
|
830
878
|
semanticConsolidationMaxPerRun: typeof cfg.semanticConsolidationMaxPerRun === "number" ? Math.max(0, Math.floor(cfg.semanticConsolidationMaxPerRun)) : 100,
|
|
831
879
|
creationMemoryEnabled: cfg.creationMemoryEnabled === true,
|
|
@@ -1075,7 +1123,7 @@ function parseConfig(raw) {
|
|
|
1075
1123
|
factArchivalAgeDays: typeof cfg.factArchivalAgeDays === "number" ? cfg.factArchivalAgeDays : 90,
|
|
1076
1124
|
factArchivalMaxImportance: typeof cfg.factArchivalMaxImportance === "number" ? cfg.factArchivalMaxImportance : 0.3,
|
|
1077
1125
|
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"],
|
|
1126
|
+
factArchivalProtectedCategories: Array.isArray(cfg.factArchivalProtectedCategories) ? cfg.factArchivalProtectedCategories.filter((c) => typeof c === "string") : ["commitment", "preference", "decision", "principle", "procedure"],
|
|
1079
1127
|
// v8.3 lifecycle policy engine (default off)
|
|
1080
1128
|
lifecyclePolicyEnabled: cfg.lifecyclePolicyEnabled === true,
|
|
1081
1129
|
lifecycleFilterStaleEnabled: cfg.lifecycleFilterStaleEnabled === true,
|
|
@@ -1084,7 +1132,7 @@ function parseConfig(raw) {
|
|
|
1084
1132
|
lifecycleArchiveDecayThreshold: typeof cfg.lifecycleArchiveDecayThreshold === "number" ? Math.min(1, Math.max(0, cfg.lifecycleArchiveDecayThreshold)) : 0.85,
|
|
1085
1133
|
lifecycleProtectedCategories: Array.isArray(cfg.lifecycleProtectedCategories) ? cfg.lifecycleProtectedCategories.filter(
|
|
1086
1134
|
(c) => typeof c === "string" && VALID_MEMORY_CATEGORIES.has(c)
|
|
1087
|
-
) : ["decision", "principle", "commitment", "preference"],
|
|
1135
|
+
) : ["decision", "principle", "commitment", "preference", "procedure"],
|
|
1088
1136
|
lifecycleMetricsEnabled: typeof cfg.lifecycleMetricsEnabled === "boolean" ? cfg.lifecycleMetricsEnabled : cfg.lifecyclePolicyEnabled === true,
|
|
1089
1137
|
// v8.3 proactive + policy learning (default off)
|
|
1090
1138
|
proactiveExtractionEnabled: cfg.proactiveExtractionEnabled === true,
|
|
@@ -1356,6 +1404,11 @@ function buildDefaultRecallPipeline(cfg) {
|
|
|
1356
1404
|
maxEntities: typeof cfg.knowledgeIndexMaxEntities === "number" ? Math.max(0, Math.floor(cfg.knowledgeIndexMaxEntities)) : 40
|
|
1357
1405
|
},
|
|
1358
1406
|
{ id: "verbatim-artifacts", enabled: cfg.verbatimArtifactsEnabled === true },
|
|
1407
|
+
{
|
|
1408
|
+
id: "procedure-recall",
|
|
1409
|
+
enabled: typeof cfg.procedural === "object" && cfg.procedural !== null && !Array.isArray(cfg.procedural) && cfg.procedural.enabled === true,
|
|
1410
|
+
maxChars: 2400
|
|
1411
|
+
},
|
|
1359
1412
|
{ id: "memory-boxes", enabled: cfg.memoryBoxesEnabled === true },
|
|
1360
1413
|
{ id: "temporal-memory-tree", enabled: cfg.temporalMemoryTreeEnabled === true },
|
|
1361
1414
|
{ id: "lcm-compressed-history", enabled: cfg.lcmEnabled === true },
|
|
@@ -1481,7 +1534,7 @@ function detectSdkCapabilities(api) {
|
|
|
1481
1534
|
// ../remnic-core/src/orchestrator.ts
|
|
1482
1535
|
import path43 from "path";
|
|
1483
1536
|
import os3 from "os";
|
|
1484
|
-
import { createHash as createHash9 } from "crypto";
|
|
1537
|
+
import { createHash as createHash9, randomBytes } from "crypto";
|
|
1485
1538
|
import { existsSync as existsSync8 } from "fs";
|
|
1486
1539
|
import {
|
|
1487
1540
|
mkdir as mkdir30,
|
|
@@ -2142,6 +2195,15 @@ var SmartBuffer = class {
|
|
|
2142
2195
|
this.state = this.normalizeState(await this.storage.loadBuffer());
|
|
2143
2196
|
this.loaded = true;
|
|
2144
2197
|
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Reset the buffer to an empty, usable state.
|
|
2200
|
+
* Called when the persisted buffer file is corrupt and load() fails,
|
|
2201
|
+
* so the buffer can still accept new turns for the rest of the session.
|
|
2202
|
+
*/
|
|
2203
|
+
resetToEmpty() {
|
|
2204
|
+
this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };
|
|
2205
|
+
this.loaded = true;
|
|
2206
|
+
}
|
|
2145
2207
|
async save() {
|
|
2146
2208
|
await this.storage.saveBuffer(this.state);
|
|
2147
2209
|
}
|
|
@@ -3710,14 +3772,40 @@ function parseMemoryActionEligibilityContext(value) {
|
|
|
3710
3772
|
source: "unknown"
|
|
3711
3773
|
};
|
|
3712
3774
|
}
|
|
3775
|
+
var ProcedureStepExtractSchema = external_exports.object({
|
|
3776
|
+
order: external_exports.number(),
|
|
3777
|
+
intent: external_exports.string(),
|
|
3778
|
+
toolCall: external_exports.object({
|
|
3779
|
+
kind: external_exports.string(),
|
|
3780
|
+
signature: external_exports.string()
|
|
3781
|
+
}).optional().nullable(),
|
|
3782
|
+
expectedOutcome: external_exports.string().optional().nullable(),
|
|
3783
|
+
optional: external_exports.boolean().optional().nullable()
|
|
3784
|
+
});
|
|
3713
3785
|
var ExtractedFactSchema = external_exports.object({
|
|
3714
|
-
category: external_exports.enum([
|
|
3786
|
+
category: external_exports.enum([
|
|
3787
|
+
"fact",
|
|
3788
|
+
"preference",
|
|
3789
|
+
"correction",
|
|
3790
|
+
"entity",
|
|
3791
|
+
"decision",
|
|
3792
|
+
"relationship",
|
|
3793
|
+
"principle",
|
|
3794
|
+
"commitment",
|
|
3795
|
+
"moment",
|
|
3796
|
+
"skill",
|
|
3797
|
+
"rule",
|
|
3798
|
+
"procedure"
|
|
3799
|
+
]),
|
|
3715
3800
|
content: external_exports.string().describe("The memory content \u2014 a clear, standalone statement"),
|
|
3716
3801
|
confidence: external_exports.number().min(0).max(1).describe("How confident are you this is correct (0-1)"),
|
|
3717
3802
|
tags: external_exports.array(external_exports.string()).describe("Relevant tags for categorization"),
|
|
3718
3803
|
entityRef: external_exports.string().optional().nullable().describe("If about an entity, its normalized name (e.g. person-jane-doe)"),
|
|
3719
3804
|
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"}).')
|
|
3805
|
+
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"}).'),
|
|
3806
|
+
procedureSteps: external_exports.array(ProcedureStepExtractSchema).optional().nullable().describe(
|
|
3807
|
+
'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").'
|
|
3808
|
+
)
|
|
3721
3809
|
});
|
|
3722
3810
|
var EntityMentionSchema = external_exports.object({
|
|
3723
3811
|
name: external_exports.string().describe("Normalized entity name (e.g. jane-doe, acme-corp, my-project)"),
|
|
@@ -4465,6 +4553,67 @@ function parallelEfficiency(group) {
|
|
|
4465
4553
|
return Math.round(idealMs / group.wallMs * 100);
|
|
4466
4554
|
}
|
|
4467
4555
|
|
|
4556
|
+
// ../remnic-core/src/procedural/procedure-types.ts
|
|
4557
|
+
function normalizeProcedureSteps(raw) {
|
|
4558
|
+
if (!Array.isArray(raw)) return [];
|
|
4559
|
+
const out = [];
|
|
4560
|
+
for (let i = 0; i < raw.length; i++) {
|
|
4561
|
+
const s = raw[i];
|
|
4562
|
+
if (!s || typeof s !== "object") continue;
|
|
4563
|
+
const o = s;
|
|
4564
|
+
const intent = typeof o.intent === "string" ? o.intent.trim() : "";
|
|
4565
|
+
if (!intent) continue;
|
|
4566
|
+
const orderRaw = o.order;
|
|
4567
|
+
const order = typeof orderRaw === "number" && Number.isFinite(orderRaw) ? Math.max(1, Math.floor(orderRaw)) : i + 1;
|
|
4568
|
+
let toolCall;
|
|
4569
|
+
const tc = o.toolCall;
|
|
4570
|
+
if (tc && typeof tc === "object" && !Array.isArray(tc)) {
|
|
4571
|
+
const t = tc;
|
|
4572
|
+
const kind = typeof t.kind === "string" ? t.kind.trim() : "";
|
|
4573
|
+
const signature = typeof t.signature === "string" ? t.signature.trim() : "";
|
|
4574
|
+
if (kind && signature) {
|
|
4575
|
+
toolCall = { kind, signature };
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
const expectedOutcome = typeof o.expectedOutcome === "string" && o.expectedOutcome.trim() ? o.expectedOutcome.trim() : void 0;
|
|
4579
|
+
const optional = o.optional === true ? true : void 0;
|
|
4580
|
+
out.push({ order, intent, toolCall, expectedOutcome, optional });
|
|
4581
|
+
}
|
|
4582
|
+
return out;
|
|
4583
|
+
}
|
|
4584
|
+
function buildProcedurePersistBody(title, procedureSteps) {
|
|
4585
|
+
const head = typeof title === "string" ? title.trim() : "";
|
|
4586
|
+
const steps = normalizeProcedureSteps(procedureSteps);
|
|
4587
|
+
if (steps.length === 0) return head;
|
|
4588
|
+
return `${head}
|
|
4589
|
+
|
|
4590
|
+
${buildProcedureMarkdownBody(steps)}`.trimEnd() + "\n";
|
|
4591
|
+
}
|
|
4592
|
+
function buildProcedureMarkdownBody(steps) {
|
|
4593
|
+
const sorted = [...steps].sort((a, b) => a.order - b.order);
|
|
4594
|
+
const lines = [];
|
|
4595
|
+
for (const step of sorted) {
|
|
4596
|
+
const n = Number.isFinite(step.order) ? Math.max(1, Math.floor(step.order)) : 1;
|
|
4597
|
+
lines.push(`## Step ${n}`);
|
|
4598
|
+
lines.push("");
|
|
4599
|
+
lines.push(step.intent.trim());
|
|
4600
|
+
if (step.toolCall?.kind && step.toolCall.signature) {
|
|
4601
|
+
lines.push("");
|
|
4602
|
+
lines.push(`- Tool: \`${step.toolCall.kind}\` \u2014 ${step.toolCall.signature}`);
|
|
4603
|
+
}
|
|
4604
|
+
if (step.expectedOutcome?.trim()) {
|
|
4605
|
+
lines.push("");
|
|
4606
|
+
lines.push(`- Expected: ${step.expectedOutcome.trim()}`);
|
|
4607
|
+
}
|
|
4608
|
+
if (step.optional === true) {
|
|
4609
|
+
lines.push("");
|
|
4610
|
+
lines.push("- Optional: true");
|
|
4611
|
+
}
|
|
4612
|
+
lines.push("");
|
|
4613
|
+
}
|
|
4614
|
+
return lines.join("\n").trimEnd() + "\n";
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4468
4617
|
// ../remnic-core/src/extraction.ts
|
|
4469
4618
|
var PROACTIVE_MIN_CONFIDENCE = 0.8;
|
|
4470
4619
|
function normalizeQuestion(question) {
|
|
@@ -4555,8 +4704,9 @@ var ExtractionEngine = class {
|
|
|
4555
4704
|
return shouldAssumeOpenAiChatCompletions(this.config.openaiBaseUrl);
|
|
4556
4705
|
}
|
|
4557
4706
|
sanitizeExtractionResult(result, messageTimestamp) {
|
|
4707
|
+
const proceduralOn = this.config.procedural?.enabled === true;
|
|
4558
4708
|
const ts = messageTimestamp ?? /* @__PURE__ */ new Date();
|
|
4559
|
-
const facts = result.facts.map((fact) => {
|
|
4709
|
+
const facts = result.facts.filter((fact) => proceduralOn || fact.category !== "procedure").map((fact) => {
|
|
4560
4710
|
const sanitized = sanitizeMemoryContent(fact.content);
|
|
4561
4711
|
if (!sanitized.clean) {
|
|
4562
4712
|
log.warn(`extraction fact sanitized; violations=${sanitized.violations.join(", ")}`);
|
|
@@ -4583,7 +4733,8 @@ var ExtractionEngine = class {
|
|
|
4583
4733
|
promptedByQuestion: typeof f?.promptedByQuestion === "string" ? f.promptedByQuestion : void 0,
|
|
4584
4734
|
structuredAttributes: f?.structuredAttributes && typeof f.structuredAttributes === "object" && !Array.isArray(f.structuredAttributes) ? Object.fromEntries(
|
|
4585
4735
|
Object.entries(f.structuredAttributes).filter(([k, v]) => typeof k === "string" && typeof v === "string")
|
|
4586
|
-
) : void 0
|
|
4736
|
+
) : void 0,
|
|
4737
|
+
procedureSteps: Array.isArray(f?.procedureSteps) ? normalizeProcedureSteps(f.procedureSteps) : void 0
|
|
4587
4738
|
})).filter((f) => f.content.length > 0) : [];
|
|
4588
4739
|
const questions = Array.isArray(parsed?.questions) ? parsed.questions.map((q) => {
|
|
4589
4740
|
if (typeof q === "string") return { question: q, context: "", priority: 0.5 };
|
|
@@ -5426,7 +5577,9 @@ Respond with valid JSON matching this schema:
|
|
|
5426
5577
|
"principle",
|
|
5427
5578
|
"commitment",
|
|
5428
5579
|
"moment",
|
|
5429
|
-
"skill"
|
|
5580
|
+
"skill",
|
|
5581
|
+
"rule",
|
|
5582
|
+
"procedure"
|
|
5430
5583
|
]);
|
|
5431
5584
|
const allowedEntityTypes = /* @__PURE__ */ new Set([
|
|
5432
5585
|
"person",
|
|
@@ -5483,6 +5636,7 @@ Memory categories:
|
|
|
5483
5636
|
- moment: Emotionally significant events or milestones (e.g., "first successful deployment of engram")
|
|
5484
5637
|
- skill: Capabilities the user or agent has demonstrated (e.g., "user is proficient with Kubernetes")${this.config.causalRuleExtractionEnabled ? `
|
|
5485
5638
|
- 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")` : ""}
|
|
5639
|
+
- 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
5640
|
|
|
5487
5641
|
Rules:
|
|
5488
5642
|
- Only extract genuinely NEW information worth remembering across sessions
|
|
@@ -6692,6 +6846,8 @@ var CATEGORY_BOOSTS = {
|
|
|
6692
6846
|
// Durable rules/values
|
|
6693
6847
|
rule: 0.11,
|
|
6694
6848
|
// Causal IF→THEN rules
|
|
6849
|
+
procedure: 0.1,
|
|
6850
|
+
// Repeatable workflows (issue #519)
|
|
6695
6851
|
preference: 0.1,
|
|
6696
6852
|
// User preferences matter
|
|
6697
6853
|
commitment: 0.1,
|
|
@@ -6997,6 +7153,18 @@ function enforceMaxCacheSize(cache) {
|
|
|
6997
7153
|
}
|
|
6998
7154
|
}
|
|
6999
7155
|
var AUTO_APPROVE_CATEGORIES = /* @__PURE__ */ new Set(["correction", "principle"]);
|
|
7156
|
+
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;
|
|
7157
|
+
function validateProcedureExtraction(input) {
|
|
7158
|
+
const steps = normalizeProcedureSteps(input.procedureSteps);
|
|
7159
|
+
if (steps.length < 2) {
|
|
7160
|
+
return { durable: false, reason: "Procedure requires at least two steps with intents" };
|
|
7161
|
+
}
|
|
7162
|
+
const combined = [input.content, ...steps.map((s) => s.intent)].join(" ").toLowerCase();
|
|
7163
|
+
if (!PROCEDURE_TRIGGER_RE.test(combined)) {
|
|
7164
|
+
return { durable: false, reason: "Procedure missing explicit trigger phrasing" };
|
|
7165
|
+
}
|
|
7166
|
+
return { durable: true, reason: "Procedure structure validated" };
|
|
7167
|
+
}
|
|
7000
7168
|
async function judgeFactDurability(candidates, config, localLlm, fallbackLlm, cache) {
|
|
7001
7169
|
const startMs = Date.now();
|
|
7002
7170
|
const verdicts = /* @__PURE__ */ new Map();
|
|
@@ -7177,6 +7345,156 @@ function createVerdictCache() {
|
|
|
7177
7345
|
return /* @__PURE__ */ new Map();
|
|
7178
7346
|
}
|
|
7179
7347
|
|
|
7348
|
+
// ../remnic-core/src/intent.ts
|
|
7349
|
+
var GOAL_PATTERNS = [
|
|
7350
|
+
{ re: /\b(debug(?:s|ged|ging)?|fix(?:es|ed|ing)?|error(?:s)?|incident(?:s)?|outage(?:s)?|failure(?:s)?)\b/i, goal: "stabilize" },
|
|
7351
|
+
{ re: /\b(deploy(?:s|ed|ing)?|release(?:s|d|ing)?|ship(?:s|ped|ping)?|publish(?:es|ed|ing)?)\b/i, goal: "release" },
|
|
7352
|
+
{ re: /\b(plan(?:s|ned|ning)?|roadmap(?:s)?|strateg(?:y|ies)|design(?:s|ed|ing)?)\b/i, goal: "plan" },
|
|
7353
|
+
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|security|hardening)\b/i, goal: "review" },
|
|
7354
|
+
{ re: /\b(sales|deal|customer|client|prospect)\b/i, goal: "close_deal" }
|
|
7355
|
+
];
|
|
7356
|
+
var ACTION_PATTERNS = [
|
|
7357
|
+
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|inspect(?:s|ed|ing)?|check(?:s|ed|ing)?)\b/i, action: "review" },
|
|
7358
|
+
{ re: /\b(plan(?:s|ned|ning)?|design(?:s|ed|ing)?|brainstorm(?:s|ed|ing)?|spec(?:s)?)\b/i, action: "plan" },
|
|
7359
|
+
{ 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" },
|
|
7360
|
+
{ re: /\b(summariz(?:e|es|ed|ing)|recap(?:s|ped|ping)?|what happened|timeline)\b/i, action: "summarize" },
|
|
7361
|
+
{ re: /\b(decid(?:e|es|ed|ing)|decision(?:s)?|cho(?:ose|oses|osing)|chose|chosen)\b/i, action: "decide" }
|
|
7362
|
+
];
|
|
7363
|
+
var ENTITY_PATTERNS = [
|
|
7364
|
+
{ re: /\b(pr|pull request|branch|repo|github|ci|workflow)\b/i, entityType: "repo" },
|
|
7365
|
+
{ re: /\b(discord|slack|channel|gateway|agent)\b/i, entityType: "ops" },
|
|
7366
|
+
{ re: /\b(customer|client|deal|lead|account)\b/i, entityType: "client" },
|
|
7367
|
+
{ re: /\b(model|llm|qmd|embedding|retrieval|memory)\b/i, entityType: "ai" },
|
|
7368
|
+
{ re: /\b(doc|readme|docs|changelog)\b/i, entityType: "docs" }
|
|
7369
|
+
];
|
|
7370
|
+
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+|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\s+)?(?:bug|build)|patch(?:ing|ed)?|build(?:ing)?\s+(?:and\s+)?(?:ship|deploy))\b/i;
|
|
7371
|
+
function normalizeTextInput(input) {
|
|
7372
|
+
return typeof input === "string" ? input : "";
|
|
7373
|
+
}
|
|
7374
|
+
function inferIntentFromText(text) {
|
|
7375
|
+
const safeText = normalizeTextInput(text);
|
|
7376
|
+
const goal = GOAL_PATTERNS.find((p) => p.re.test(safeText))?.goal ?? "unknown";
|
|
7377
|
+
const actionType = ACTION_PATTERNS.find((p) => p.re.test(safeText))?.action ?? "unknown";
|
|
7378
|
+
const entityTypes = Array.from(
|
|
7379
|
+
new Set(ENTITY_PATTERNS.filter((p) => p.re.test(safeText)).map((p) => p.entityType))
|
|
7380
|
+
);
|
|
7381
|
+
const taskInitiation = TASK_INITIATION_RE.test(safeText);
|
|
7382
|
+
return {
|
|
7383
|
+
goal,
|
|
7384
|
+
actionType,
|
|
7385
|
+
entityTypes,
|
|
7386
|
+
taskInitiation
|
|
7387
|
+
};
|
|
7388
|
+
}
|
|
7389
|
+
function isTaskInitiationIntent(intent) {
|
|
7390
|
+
return intent.taskInitiation === true;
|
|
7391
|
+
}
|
|
7392
|
+
function intentCompatibilityScore(queryIntent, memoryIntent) {
|
|
7393
|
+
const queryHasSignal = queryIntent.goal !== "unknown" || queryIntent.actionType !== "unknown" || queryIntent.entityTypes.length > 0;
|
|
7394
|
+
const memoryHasSignal = memoryIntent.goal !== "unknown" || memoryIntent.actionType !== "unknown" || memoryIntent.entityTypes.length > 0;
|
|
7395
|
+
if (!queryHasSignal || !memoryHasSignal) return 0;
|
|
7396
|
+
let score = 0;
|
|
7397
|
+
if (queryIntent.goal !== "unknown" && memoryIntent.goal !== "unknown" && queryIntent.goal === memoryIntent.goal) {
|
|
7398
|
+
score += 0.5;
|
|
7399
|
+
}
|
|
7400
|
+
if (queryIntent.actionType !== "unknown" && memoryIntent.actionType !== "unknown" && queryIntent.actionType === memoryIntent.actionType) {
|
|
7401
|
+
score += 0.3;
|
|
7402
|
+
}
|
|
7403
|
+
const overlap = queryIntent.entityTypes.filter((et) => memoryIntent.entityTypes.includes(et)).length;
|
|
7404
|
+
if (overlap > 0) {
|
|
7405
|
+
const denom = Math.max(queryIntent.entityTypes.length, memoryIntent.entityTypes.length, 1);
|
|
7406
|
+
score += 0.2 * (overlap / denom);
|
|
7407
|
+
}
|
|
7408
|
+
return Math.max(0, Math.min(1, score));
|
|
7409
|
+
}
|
|
7410
|
+
function planRecallMode(prompt) {
|
|
7411
|
+
const p = normalizeTextInput(prompt).trim();
|
|
7412
|
+
let ackCandidate = p;
|
|
7413
|
+
while (ackCandidate.length > 0) {
|
|
7414
|
+
const ch = ackCandidate.charCodeAt(ackCandidate.length - 1);
|
|
7415
|
+
const isDigit = ch >= 48 && ch <= 57;
|
|
7416
|
+
const isUpper = ch >= 65 && ch <= 90;
|
|
7417
|
+
const isLower = ch >= 97 && ch <= 122;
|
|
7418
|
+
if (isDigit || isUpper || isLower) break;
|
|
7419
|
+
ackCandidate = ackCandidate.slice(0, -1);
|
|
7420
|
+
}
|
|
7421
|
+
ackCandidate = ackCandidate.trim();
|
|
7422
|
+
if (p.length === 0) return "no_recall";
|
|
7423
|
+
if (/\b(timeline|sequence|history|what happened|chain of events|root cause)\b/i.test(p)) {
|
|
7424
|
+
return "graph_mode";
|
|
7425
|
+
}
|
|
7426
|
+
if (p.length <= 18 && /^(ok|okay|kk|thanks|thx|got it|sounds good|yep|yes|nope|no|done|cool|works)$/i.test(ackCandidate)) {
|
|
7427
|
+
return "no_recall";
|
|
7428
|
+
}
|
|
7429
|
+
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())) {
|
|
7430
|
+
return "full";
|
|
7431
|
+
}
|
|
7432
|
+
if (p.length <= 100 && /^(check|reload|restart|run|verify|show|status|sync|update|open|close|set|enable|disable|fix|patch)\b/i.test(p)) {
|
|
7433
|
+
return "minimal";
|
|
7434
|
+
}
|
|
7435
|
+
return "full";
|
|
7436
|
+
}
|
|
7437
|
+
function hasBroadGraphIntent(prompt) {
|
|
7438
|
+
const p = normalizeTextInput(prompt).trim().toLowerCase();
|
|
7439
|
+
if (!p) return false;
|
|
7440
|
+
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(
|
|
7441
|
+
p
|
|
7442
|
+
);
|
|
7443
|
+
}
|
|
7444
|
+
|
|
7445
|
+
// ../remnic-core/src/procedural/procedure-recall.ts
|
|
7446
|
+
function tokenOverlapScore(prompt, memoryText) {
|
|
7447
|
+
const norm = (s) => s.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2);
|
|
7448
|
+
const promptTokens = new Set(norm(prompt));
|
|
7449
|
+
const memTokens = new Set(norm(memoryText));
|
|
7450
|
+
if (promptTokens.size === 0 || memTokens.size === 0) return 0;
|
|
7451
|
+
let inter = 0;
|
|
7452
|
+
for (const t of promptTokens) {
|
|
7453
|
+
if (memTokens.has(t)) inter++;
|
|
7454
|
+
}
|
|
7455
|
+
const union = /* @__PURE__ */ new Set([...promptTokens, ...memTokens]);
|
|
7456
|
+
return inter / Math.max(1, union.size);
|
|
7457
|
+
}
|
|
7458
|
+
function scoreProcedureForPrompt(m, prompt, queryIntent) {
|
|
7459
|
+
const memText = `${m.content}
|
|
7460
|
+
${(m.frontmatter.tags ?? []).join(" ")}`;
|
|
7461
|
+
const jaccard = tokenOverlapScore(prompt, memText);
|
|
7462
|
+
const memIntent = inferIntentFromText(m.content.slice(0, 2e3));
|
|
7463
|
+
const intentScore = intentCompatibilityScore(queryIntent, memIntent);
|
|
7464
|
+
return jaccard * 0.55 + intentScore * 0.45;
|
|
7465
|
+
}
|
|
7466
|
+
async function buildProcedureRecallSection(storage, prompt, config) {
|
|
7467
|
+
if (config.procedural?.enabled !== true) return null;
|
|
7468
|
+
const trimmed = typeof prompt === "string" ? prompt.trim() : "";
|
|
7469
|
+
if (!trimmed) return null;
|
|
7470
|
+
const queryIntent = inferIntentFromText(trimmed);
|
|
7471
|
+
if (!isTaskInitiationIntent(queryIntent)) return null;
|
|
7472
|
+
const maxN = Math.min(
|
|
7473
|
+
10,
|
|
7474
|
+
Math.max(
|
|
7475
|
+
1,
|
|
7476
|
+
typeof config.procedural.recallMaxProcedures === "number" && Number.isFinite(config.procedural.recallMaxProcedures) ? Math.floor(config.procedural.recallMaxProcedures) : 3
|
|
7477
|
+
)
|
|
7478
|
+
);
|
|
7479
|
+
const all = await storage.readAllMemories();
|
|
7480
|
+
const scored = all.filter(
|
|
7481
|
+
(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"
|
|
7482
|
+
).map((m) => ({ m, score: scoreProcedureForPrompt(m, trimmed, queryIntent) })).filter((x) => x.score > 0.04).sort((a, b) => b.score - a.score).slice(0, maxN);
|
|
7483
|
+
if (scored.length === 0) return null;
|
|
7484
|
+
const blocks = scored.map(({ m, score }) => {
|
|
7485
|
+
const id = m.frontmatter.id;
|
|
7486
|
+
const flat = m.content.replace(/\s+/g, " ").trim();
|
|
7487
|
+
const preview = flat.slice(0, 320);
|
|
7488
|
+
const suffix = flat.length > 320 ? "\u2026" : "";
|
|
7489
|
+
return `### ${id} (match ${score.toFixed(2)})
|
|
7490
|
+
|
|
7491
|
+
${preview}${suffix}`;
|
|
7492
|
+
});
|
|
7493
|
+
return `## Relevant procedures
|
|
7494
|
+
|
|
7495
|
+
${blocks.join("\n\n")}`;
|
|
7496
|
+
}
|
|
7497
|
+
|
|
7180
7498
|
// ../remnic-core/src/reconstruct.ts
|
|
7181
7499
|
function findUnresolvedEntityRefs(recalledSnippets, recalledEntityRefs, knownEntities) {
|
|
7182
7500
|
const refSet = new Set(recalledEntityRefs.map((r) => r.toLowerCase()));
|
|
@@ -8714,6 +9032,26 @@ var QmdDaemonSession = class {
|
|
|
8714
9032
|
this.buffer = "";
|
|
8715
9033
|
}
|
|
8716
9034
|
};
|
|
9035
|
+
var QMD_RESULT_LINE_RE = /^#([0-9a-fA-F]+)\s+(\d+)%\s+(.+)/;
|
|
9036
|
+
var QMD_PATH_TITLE_RE = /^(.+?\.[a-zA-Z]{2,10})\s+-\s+(.*)$/;
|
|
9037
|
+
function parseQmdMarkdownResultText(text, transport) {
|
|
9038
|
+
const results = [];
|
|
9039
|
+
for (const line of text.split("\n")) {
|
|
9040
|
+
const m = QMD_RESULT_LINE_RE.exec(line.trim());
|
|
9041
|
+
if (!m) continue;
|
|
9042
|
+
const rest = m[3];
|
|
9043
|
+
const pathTitleSplit = QMD_PATH_TITLE_RE.exec(rest);
|
|
9044
|
+
if (!pathTitleSplit) continue;
|
|
9045
|
+
results.push({
|
|
9046
|
+
docid: m[1],
|
|
9047
|
+
path: pathTitleSplit[1] ?? "unknown",
|
|
9048
|
+
snippet: "",
|
|
9049
|
+
score: parseInt(m[2], 10) / 100,
|
|
9050
|
+
transport
|
|
9051
|
+
});
|
|
9052
|
+
}
|
|
9053
|
+
return results;
|
|
9054
|
+
}
|
|
8717
9055
|
function parseMcpSearchResult(result, transport = "daemon") {
|
|
8718
9056
|
const resultObj = result;
|
|
8719
9057
|
if (!resultObj) return [];
|
|
@@ -8746,6 +9084,15 @@ function parseMcpSearchResult(result, transport = "daemon") {
|
|
|
8746
9084
|
const textResults = parsed?.results ?? parsed?.documents;
|
|
8747
9085
|
if (Array.isArray(textResults)) pushDocs(textResults);
|
|
8748
9086
|
} catch {
|
|
9087
|
+
const existingKeys = new Set(results.map((r) => `${r.docid.toLowerCase()}|${r.path}`));
|
|
9088
|
+
const parsed = parseQmdMarkdownResultText(item.text, transport);
|
|
9089
|
+
for (const p of parsed) {
|
|
9090
|
+
const key = `${p.docid.toLowerCase()}|${p.path}`;
|
|
9091
|
+
if (!existingKeys.has(key)) {
|
|
9092
|
+
results.push(p);
|
|
9093
|
+
existingKeys.add(key);
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
8749
9096
|
}
|
|
8750
9097
|
}
|
|
8751
9098
|
}
|
|
@@ -8801,9 +9148,22 @@ var QmdClient = class _QmdClient {
|
|
|
8801
9148
|
collection;
|
|
8802
9149
|
maxResults;
|
|
8803
9150
|
available = null;
|
|
8804
|
-
|
|
9151
|
+
_lastUpdateFailAtMs = null;
|
|
8805
9152
|
lastEmbedFailAtMs = null;
|
|
8806
9153
|
lastUpdateRunAtMs = null;
|
|
9154
|
+
get lastUpdateFailedAtMs() {
|
|
9155
|
+
return this._lastUpdateFailAtMs;
|
|
9156
|
+
}
|
|
9157
|
+
get lastUpdateRanAtMs() {
|
|
9158
|
+
return this.lastUpdateRunAtMs;
|
|
9159
|
+
}
|
|
9160
|
+
resetUpdateThrottles() {
|
|
9161
|
+
this._lastUpdateFailAtMs = null;
|
|
9162
|
+
this.lastUpdateRunAtMs = null;
|
|
9163
|
+
const gs = getGlobalQmdState();
|
|
9164
|
+
gs.lastGlobalUpdateRunAtMs = null;
|
|
9165
|
+
gs.lastGlobalUpdateFailAtMs = null;
|
|
9166
|
+
}
|
|
8807
9167
|
updateTimeoutMs;
|
|
8808
9168
|
updateMinIntervalMs;
|
|
8809
9169
|
slowLog;
|
|
@@ -9420,13 +9780,13 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9420
9780
|
return [];
|
|
9421
9781
|
}
|
|
9422
9782
|
}
|
|
9423
|
-
async update() {
|
|
9424
|
-
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false });
|
|
9783
|
+
async update(signal) {
|
|
9784
|
+
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false }, signal);
|
|
9425
9785
|
}
|
|
9426
9786
|
async updateCollection(collection) {
|
|
9427
9787
|
await this.runUpdateForCollection(collection, { perCollectionThrottle: true });
|
|
9428
9788
|
}
|
|
9429
|
-
async runUpdateForCollection(collection, options) {
|
|
9789
|
+
async runUpdateForCollection(collection, options, signal) {
|
|
9430
9790
|
if (this.available === false) return;
|
|
9431
9791
|
const name = collection.trim();
|
|
9432
9792
|
if (!name) return;
|
|
@@ -9452,7 +9812,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9452
9812
|
log.debug("QMD update: suppressed due to min-interval gate");
|
|
9453
9813
|
return;
|
|
9454
9814
|
}
|
|
9455
|
-
if (this.
|
|
9815
|
+
if (this._lastUpdateFailAtMs && now - this._lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
9456
9816
|
log.debug("QMD update: suppressed due to recent failures (backoff)");
|
|
9457
9817
|
return;
|
|
9458
9818
|
}
|
|
@@ -9473,7 +9833,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9473
9833
|
);
|
|
9474
9834
|
}
|
|
9475
9835
|
const startedAtMs = Date.now();
|
|
9476
|
-
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs);
|
|
9836
|
+
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs, signal);
|
|
9477
9837
|
const durationMs = Date.now() - startedAtMs;
|
|
9478
9838
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
9479
9839
|
log.warn(`SLOW QMD update: durationMs=${durationMs}`);
|
|
@@ -9493,7 +9853,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9493
9853
|
globalState.lastUpdateFailByCollectionMs[name] = at;
|
|
9494
9854
|
globalState.lastGlobalUpdateFailAtMs = at;
|
|
9495
9855
|
} else {
|
|
9496
|
-
this.
|
|
9856
|
+
this._lastUpdateFailAtMs = at;
|
|
9497
9857
|
globalState.lastGlobalUpdateFailAtMs = at;
|
|
9498
9858
|
}
|
|
9499
9859
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -12622,6 +12982,7 @@ import { mkdir as mkdir8, readFile as readFile10, rename, rm as rm2, stat as sta
|
|
|
12622
12982
|
import path15 from "path";
|
|
12623
12983
|
var DAY_SUMMARY_CRON_ID = "engram-day-summary";
|
|
12624
12984
|
var GOVERNANCE_CRON_ID = "engram-nightly-governance";
|
|
12985
|
+
var PROCEDURAL_MINING_CRON_ID = "engram-procedural-mining";
|
|
12625
12986
|
async function acquireCronJobsLock(jobsPath) {
|
|
12626
12987
|
const lockPath2 = `${jobsPath}.lock`;
|
|
12627
12988
|
const start = Date.now();
|
|
@@ -12734,13 +13095,37 @@ async function ensureNightlyGovernanceCron(jobsPath, options) {
|
|
|
12734
13095
|
delivery: { mode: "none" }
|
|
12735
13096
|
}));
|
|
12736
13097
|
}
|
|
13098
|
+
async function ensureProceduralMiningCron(jobsPath, options) {
|
|
13099
|
+
const scheduleExpr = typeof options.scheduleExpr === "string" && options.scheduleExpr.trim().length > 0 ? options.scheduleExpr.trim() : "17 3 * * *";
|
|
13100
|
+
const agentId = typeof options.agentId === "string" && options.agentId.trim().length > 0 ? options.agentId.trim() : "main";
|
|
13101
|
+
return ensureCronJob(jobsPath, PROCEDURAL_MINING_CRON_ID, () => ({
|
|
13102
|
+
id: PROCEDURAL_MINING_CRON_ID,
|
|
13103
|
+
agentId,
|
|
13104
|
+
name: "Remnic Procedural Mining (nightly)",
|
|
13105
|
+
enabled: true,
|
|
13106
|
+
schedule: {
|
|
13107
|
+
kind: "cron",
|
|
13108
|
+
expr: scheduleExpr,
|
|
13109
|
+
tz: options.timezone
|
|
13110
|
+
},
|
|
13111
|
+
sessionTarget: "isolated",
|
|
13112
|
+
wakeMode: "now",
|
|
13113
|
+
payload: {
|
|
13114
|
+
kind: "agentTurn",
|
|
13115
|
+
timeoutSeconds: 900,
|
|
13116
|
+
thinking: "off",
|
|
13117
|
+
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."
|
|
13118
|
+
},
|
|
13119
|
+
delivery: { mode: "none" }
|
|
13120
|
+
}));
|
|
13121
|
+
}
|
|
12737
13122
|
|
|
12738
13123
|
// ../remnic-core/src/lifecycle.ts
|
|
12739
13124
|
var DEFAULT_POLICY = {
|
|
12740
13125
|
promoteHeatThreshold: 0.55,
|
|
12741
13126
|
staleDecayThreshold: 0.65,
|
|
12742
13127
|
archiveDecayThreshold: 0.85,
|
|
12743
|
-
protectedCategories: ["decision", "principle", "commitment", "preference"]
|
|
13128
|
+
protectedCategories: ["decision", "principle", "commitment", "preference", "procedure"]
|
|
12744
13129
|
};
|
|
12745
13130
|
function clamp01(value) {
|
|
12746
13131
|
if (!Number.isFinite(value)) return 0;
|
|
@@ -18168,97 +18553,6 @@ async function readRecentEntityTranscriptEntries(transcriptEntriesPromise, recen
|
|
|
18168
18553
|
}
|
|
18169
18554
|
var entityRecentTranscriptLookbackHours = RECENT_TRANSCRIPT_LOOKBACK_HOURS;
|
|
18170
18555
|
|
|
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
18556
|
// ../remnic-core/src/recall-query-policy.ts
|
|
18263
18557
|
var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
|
|
18264
18558
|
"the",
|
|
@@ -18553,11 +18847,11 @@ function computeCompressionGuidelineCandidate(events, options = {}) {
|
|
|
18553
18847
|
notes
|
|
18554
18848
|
};
|
|
18555
18849
|
}
|
|
18556
|
-
const
|
|
18850
|
+
const successRate2 = summary.outcomes.applied / summary.total;
|
|
18557
18851
|
const failureRate = summary.outcomes.failed / summary.total;
|
|
18558
18852
|
const qualitySeen = summary.quality.good + summary.quality.poor;
|
|
18559
18853
|
const qualitySignal = qualitySeen > 0 ? (summary.quality.good - summary.quality.poor) / qualitySeen : 0;
|
|
18560
|
-
const rawDelta = clamp((
|
|
18854
|
+
const rawDelta = clamp((successRate2 - failureRate) * 0.12 + qualitySignal * 0.06, -MAX_DELTA, MAX_DELTA);
|
|
18561
18855
|
const delta = roundDelta(rawDelta);
|
|
18562
18856
|
const direction = directionForDelta(delta);
|
|
18563
18857
|
if (direction === "decrease" && summary.outcomes.failed > summary.outcomes.applied) {
|
|
@@ -20873,14 +21167,75 @@ async function getWorkProductLedgerStatus(options) {
|
|
|
20873
21167
|
};
|
|
20874
21168
|
}
|
|
20875
21169
|
|
|
21170
|
+
// ../remnic-core/src/utils/iso-timestamp.ts
|
|
21171
|
+
var ISO_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
21172
|
+
var ISO_OFFSET_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
21173
|
+
function validateDateComponents(isoString) {
|
|
21174
|
+
const match = isoString.match(
|
|
21175
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/
|
|
21176
|
+
);
|
|
21177
|
+
if (!match) return false;
|
|
21178
|
+
const [, yStr, mStr, dStr, hStr, minStr, sStr] = match;
|
|
21179
|
+
const y = Number(yStr);
|
|
21180
|
+
const m = Number(mStr);
|
|
21181
|
+
const d = Number(dStr);
|
|
21182
|
+
const h = Number(hStr);
|
|
21183
|
+
const min = Number(minStr);
|
|
21184
|
+
const s = Number(sStr);
|
|
21185
|
+
if (m < 1 || m > 12) return false;
|
|
21186
|
+
if (d < 1 || d > 31) return false;
|
|
21187
|
+
if (h > 23 || min > 59 || s > 59) return false;
|
|
21188
|
+
const daysInMonth = new Date(y, m, 0).getDate();
|
|
21189
|
+
if (d > daysInMonth) return false;
|
|
21190
|
+
return true;
|
|
21191
|
+
}
|
|
21192
|
+
function validateOffset(isoString) {
|
|
21193
|
+
const offsetMatch = isoString.match(/([+-])(\d{2}):(\d{2})$/);
|
|
21194
|
+
if (!offsetMatch) return true;
|
|
21195
|
+
const oh = Number(offsetMatch[2]);
|
|
21196
|
+
const om = Number(offsetMatch[3]);
|
|
21197
|
+
if (oh > 14 || om > 59) return false;
|
|
21198
|
+
if (oh === 14 && om > 0) return false;
|
|
21199
|
+
return true;
|
|
21200
|
+
}
|
|
21201
|
+
function normalizeUtcForComparison(value) {
|
|
21202
|
+
const fracMatch = value.match(/\.(\d+)Z$/);
|
|
21203
|
+
if (fracMatch) {
|
|
21204
|
+
const ms = (fracMatch[1] + "000").slice(0, 3);
|
|
21205
|
+
return value.replace(/\.\d+Z$/, `.${ms}Z`);
|
|
21206
|
+
}
|
|
21207
|
+
return value.replace(/Z$/, ".000Z");
|
|
21208
|
+
}
|
|
21209
|
+
function parseIsoUtcTimestamp(value) {
|
|
21210
|
+
if (typeof value !== "string" || !ISO_UTC_TIMESTAMP_RE.test(value)) {
|
|
21211
|
+
return null;
|
|
21212
|
+
}
|
|
21213
|
+
const ts = Date.parse(value);
|
|
21214
|
+
if (!Number.isFinite(ts)) return null;
|
|
21215
|
+
if (!validateDateComponents(value)) return null;
|
|
21216
|
+
const roundTrip = new Date(ts).toISOString();
|
|
21217
|
+
if (roundTrip !== normalizeUtcForComparison(value)) return null;
|
|
21218
|
+
return ts;
|
|
21219
|
+
}
|
|
21220
|
+
function parseIsoOffsetTimestamp(value) {
|
|
21221
|
+
if (typeof value !== "string" || !ISO_OFFSET_TIMESTAMP_RE.test(value)) {
|
|
21222
|
+
return null;
|
|
21223
|
+
}
|
|
21224
|
+
const ts = Date.parse(value);
|
|
21225
|
+
if (!Number.isFinite(ts)) return null;
|
|
21226
|
+
if (!validateDateComponents(value)) return null;
|
|
21227
|
+
if (!validateOffset(value)) return null;
|
|
21228
|
+
if (value.endsWith("Z")) {
|
|
21229
|
+
const roundTrip = new Date(ts).toISOString();
|
|
21230
|
+
if (roundTrip !== normalizeUtcForComparison(value)) return null;
|
|
21231
|
+
}
|
|
21232
|
+
return ts;
|
|
21233
|
+
}
|
|
21234
|
+
|
|
20876
21235
|
// ../remnic-core/src/replay/types.ts
|
|
20877
21236
|
var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
|
|
20878
21237
|
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
21238
|
var REPLAY_UNKNOWN_SESSION_KEY = "replay:unknown";
|
|
20881
|
-
function normalizeIsoForComparison(value) {
|
|
20882
|
-
return value.includes(".") ? value : value.replace("Z", ".000Z");
|
|
20883
|
-
}
|
|
20884
21239
|
function isReplaySource(value) {
|
|
20885
21240
|
return typeof value === "string" && VALID_SOURCES.has(value);
|
|
20886
21241
|
}
|
|
@@ -20893,12 +21248,7 @@ function normalizeReplaySessionKey(value) {
|
|
|
20893
21248
|
return trimmed.length > 0 ? trimmed : REPLAY_UNKNOWN_SESSION_KEY;
|
|
20894
21249
|
}
|
|
20895
21250
|
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;
|
|
21251
|
+
return parseIsoUtcTimestamp(value);
|
|
20902
21252
|
}
|
|
20903
21253
|
function validateReplayTurn(turn, index) {
|
|
20904
21254
|
const issues = [];
|
|
@@ -24961,7 +25311,8 @@ var DEFAULT_CATEGORIES = [
|
|
|
24961
25311
|
"commitment",
|
|
24962
25312
|
"moment",
|
|
24963
25313
|
"skill",
|
|
24964
|
-
"rule"
|
|
25314
|
+
"rule",
|
|
25315
|
+
"procedure"
|
|
24965
25316
|
];
|
|
24966
25317
|
function normalizeNamespace(namespace) {
|
|
24967
25318
|
return namespace.trim();
|
|
@@ -25066,7 +25417,8 @@ var INLINE_ALLOWED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
25066
25417
|
"commitment",
|
|
25067
25418
|
"moment",
|
|
25068
25419
|
"skill",
|
|
25069
|
-
"rule"
|
|
25420
|
+
"rule",
|
|
25421
|
+
"procedure"
|
|
25070
25422
|
]);
|
|
25071
25423
|
var SECRET_PATTERNS = [
|
|
25072
25424
|
/\bsk-[A-Za-z0-9]{16,}\b/,
|
|
@@ -25665,15 +26017,24 @@ var NamespaceSearchRouter = class {
|
|
|
25665
26017
|
);
|
|
25666
26018
|
return mergeNamespaceSearchResults(resultsByNamespace, maxResults);
|
|
25667
26019
|
}
|
|
26020
|
+
/**
|
|
26021
|
+
* Update all namespace backends.
|
|
26022
|
+
* Returns the number of backends for which an update was attempted
|
|
26023
|
+
* (i.e., available and collection present). Callers can treat 0 as a
|
|
26024
|
+
* signal that no backend was eligible — useful for success-verification in
|
|
26025
|
+
* startup-sync when namespacesEnabled is true.
|
|
26026
|
+
*/
|
|
25668
26027
|
async updateNamespaces(namespaces) {
|
|
25669
26028
|
const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
|
|
25670
|
-
await Promise.all(
|
|
26029
|
+
const results = await Promise.all(
|
|
25671
26030
|
unique.map(async (namespace) => {
|
|
25672
26031
|
const record = await this.backendRecordFor(namespace);
|
|
25673
|
-
if (!record.available || record.collectionState === "missing") return;
|
|
26032
|
+
if (!record.available || record.collectionState === "missing") return 0;
|
|
25674
26033
|
await record.backend.update();
|
|
26034
|
+
return 1;
|
|
25675
26035
|
})
|
|
25676
26036
|
);
|
|
26037
|
+
return results.reduce((sum, v) => sum + v, 0);
|
|
25677
26038
|
}
|
|
25678
26039
|
async embedNamespaces(namespaces) {
|
|
25679
26040
|
const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
|
|
@@ -25689,6 +26050,10 @@ var NamespaceSearchRouter = class {
|
|
|
25689
26050
|
const record = await this.backendRecordFor(namespace);
|
|
25690
26051
|
return record.collectionState;
|
|
25691
26052
|
}
|
|
26053
|
+
/** Clear cached backend records so the next access re-probes availability. */
|
|
26054
|
+
clearCache() {
|
|
26055
|
+
this.cache.clear();
|
|
26056
|
+
}
|
|
25692
26057
|
async backendRecordFor(namespace) {
|
|
25693
26058
|
const key = namespace.trim() || this.config.defaultNamespace;
|
|
25694
26059
|
const existing = this.cache.get(key);
|
|
@@ -27108,7 +27473,8 @@ function parseMemoryIntentSnapshot(value) {
|
|
|
27108
27473
|
actionType: typeof candidate.actionType === "string" ? candidate.actionType : "unknown",
|
|
27109
27474
|
entityTypes: Array.isArray(candidate.entityTypes) ? candidate.entityTypes.filter(
|
|
27110
27475
|
(item) => typeof item === "string"
|
|
27111
|
-
) : []
|
|
27476
|
+
) : [],
|
|
27477
|
+
taskInitiation: candidate.taskInitiation === true
|
|
27112
27478
|
};
|
|
27113
27479
|
}
|
|
27114
27480
|
function buildQmdIntentHint(intent) {
|
|
@@ -27122,6 +27488,9 @@ function buildQmdIntentHint(intent) {
|
|
|
27122
27488
|
if (intent.entityTypes.length > 0) {
|
|
27123
27489
|
parts.push(`entities:${intent.entityTypes.join(",")}`);
|
|
27124
27490
|
}
|
|
27491
|
+
if (intent.taskInitiation === true) {
|
|
27492
|
+
parts.push("task_initiation");
|
|
27493
|
+
}
|
|
27125
27494
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
27126
27495
|
}
|
|
27127
27496
|
function parseQmdRecallResults(value) {
|
|
@@ -27297,6 +27666,41 @@ var Orchestrator = class _Orchestrator {
|
|
|
27297
27666
|
// Initialization gate: recall() awaits this before proceeding
|
|
27298
27667
|
initPromise = null;
|
|
27299
27668
|
resolveInit = null;
|
|
27669
|
+
/**
|
|
27670
|
+
* Resolves when deferred initialization (QMD probe, warmup, caches, cron)
|
|
27671
|
+
* completes. CLI and http-serve callers that need `qmd.isAvailable()` to
|
|
27672
|
+
* reflect reality should `await orchestrator.deferredReady` after
|
|
27673
|
+
* `initialize()`. Gateway callers can ignore it — recall() degrades
|
|
27674
|
+
* gracefully when QMD isn't ready yet.
|
|
27675
|
+
*
|
|
27676
|
+
* Also resolves (without error) when `initialize()` throws before reaching
|
|
27677
|
+
* the deferred-init phase, so callers never hang on a permanently-pending
|
|
27678
|
+
* promise.
|
|
27679
|
+
*
|
|
27680
|
+
* Host adapters that need to tie deferred init to their stop() lifecycle
|
|
27681
|
+
* should `await orchestrator.deferredReady` before proceeding with teardown
|
|
27682
|
+
* to prevent background QMD/warmup/cron tasks from racing with shutdown.
|
|
27683
|
+
*/
|
|
27684
|
+
deferredReady = Promise.resolve();
|
|
27685
|
+
resolveDeferredReady = null;
|
|
27686
|
+
deferredInitAbort = null;
|
|
27687
|
+
/**
|
|
27688
|
+
* Whether the deferred init's QMD startup sync completed successfully.
|
|
27689
|
+
* When false after deferredReady resolves, the server retry loop should
|
|
27690
|
+
* attempt startupSearchSync() even if `qmd.isAvailable()` is true —
|
|
27691
|
+
* availability only means probe succeeded, not that the index is current.
|
|
27692
|
+
*/
|
|
27693
|
+
deferredSyncSucceeded = false;
|
|
27694
|
+
/**
|
|
27695
|
+
* Abort deferred initialization so background QMD sync/warmup stops
|
|
27696
|
+
* promptly on shutdown. Safe to call multiple times or before init.
|
|
27697
|
+
*/
|
|
27698
|
+
abortDeferredInit() {
|
|
27699
|
+
if (this.deferredInitAbort) {
|
|
27700
|
+
this.deferredInitAbort.abort();
|
|
27701
|
+
this.deferredInitAbort = null;
|
|
27702
|
+
}
|
|
27703
|
+
}
|
|
27300
27704
|
/** Set per-session workspace for the next recall() call (compaction reset). @internal */
|
|
27301
27705
|
setRecallWorkspaceOverride(sessionKey, dir) {
|
|
27302
27706
|
this._recallWorkspaceOverrides.set(sessionKey, dir);
|
|
@@ -27687,100 +28091,173 @@ var Orchestrator = class _Orchestrator {
|
|
|
27687
28091
|
return this.fastLlm;
|
|
27688
28092
|
}
|
|
27689
28093
|
async initialize() {
|
|
27690
|
-
|
|
27691
|
-
|
|
27692
|
-
logger: (message) => log.info(message)
|
|
28094
|
+
this.deferredReady = new Promise((resolve) => {
|
|
28095
|
+
this.resolveDeferredReady = resolve;
|
|
27693
28096
|
});
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
|
|
27701
|
-
|
|
27702
|
-
|
|
27703
|
-
|
|
27704
|
-
|
|
27705
|
-
|
|
28097
|
+
try {
|
|
28098
|
+
await migrateFromEngram({
|
|
28099
|
+
quiet: true,
|
|
28100
|
+
logger: (message) => log.info(message)
|
|
28101
|
+
});
|
|
28102
|
+
await this.storage.ensureDirectories();
|
|
28103
|
+
await this.storage.loadAliases();
|
|
28104
|
+
if (this.config.namespacesEnabled) {
|
|
28105
|
+
const namespaces = /* @__PURE__ */ new Set([
|
|
28106
|
+
this.config.defaultNamespace,
|
|
28107
|
+
this.config.sharedNamespace,
|
|
28108
|
+
...this.config.namespacePolicies.map((p) => p.name)
|
|
28109
|
+
]);
|
|
28110
|
+
for (const ns of namespaces) {
|
|
28111
|
+
const sm = await this.storageRouter.storageFor(ns);
|
|
28112
|
+
await sm.ensureDirectories();
|
|
28113
|
+
await sm.loadAliases().catch(() => void 0);
|
|
28114
|
+
}
|
|
28115
|
+
}
|
|
28116
|
+
await this.relevance.load();
|
|
28117
|
+
await this.negatives.load();
|
|
28118
|
+
await this.lastRecall.load();
|
|
28119
|
+
await this.tierMigrationStatus.load();
|
|
28120
|
+
await this.sessionObserver.load();
|
|
28121
|
+
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
28122
|
+
this.utilityRuntimeValues = await loadUtilityRuntimeValues({
|
|
28123
|
+
memoryDir: this.config.memoryDir,
|
|
28124
|
+
memoryUtilityLearningEnabled: this.config.memoryUtilityLearningEnabled,
|
|
28125
|
+
promotionByOutcomeEnabled: this.config.promotionByOutcomeEnabled
|
|
28126
|
+
});
|
|
28127
|
+
if (this.config.factDeduplicationEnabled) {
|
|
28128
|
+
const stateDir2 = path43.join(this.config.memoryDir, "state");
|
|
28129
|
+
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
28130
|
+
await this.contentHashIndex.load();
|
|
28131
|
+
log.info(
|
|
28132
|
+
`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`
|
|
28133
|
+
);
|
|
27706
28134
|
}
|
|
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
|
-
}))
|
|
28135
|
+
await this.transcript.initialize();
|
|
28136
|
+
await this.summarizer.initialize();
|
|
28137
|
+
if (this.sharedContext) {
|
|
28138
|
+
await this.sharedContext.ensureStructure();
|
|
28139
|
+
}
|
|
28140
|
+
if (this.compounding) {
|
|
28141
|
+
await this.compounding.ensureDirs();
|
|
28142
|
+
}
|
|
28143
|
+
try {
|
|
28144
|
+
await this.buffer.load();
|
|
28145
|
+
} catch (bufErr) {
|
|
28146
|
+
log.error(
|
|
28147
|
+
`buffer.load() failed (init gate will still open): ${bufErr}`
|
|
27752
28148
|
);
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
27756
|
-
|
|
27757
|
-
this.
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27761
|
-
|
|
27762
|
-
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
|
|
28149
|
+
this.buffer.resetToEmpty();
|
|
28150
|
+
}
|
|
28151
|
+
if (this.config.compactionResetEnabled) {
|
|
28152
|
+
try {
|
|
28153
|
+
const wsDir = this.config.workspaceDir || defaultWorkspaceDir();
|
|
28154
|
+
const files = await readdir14(wsDir).catch(() => []);
|
|
28155
|
+
for (const f of files) {
|
|
28156
|
+
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
28157
|
+
const fp = path43.join(wsDir, f);
|
|
28158
|
+
const s = await stat10(fp).catch(() => null);
|
|
28159
|
+
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
28160
|
+
await unlink7(fp).catch(() => {
|
|
28161
|
+
});
|
|
28162
|
+
log.debug(`initialize: removed stale compaction signal ${f}`);
|
|
28163
|
+
}
|
|
28164
|
+
}
|
|
28165
|
+
} catch (err) {
|
|
28166
|
+
log.debug("initialize: stale signal sweep failed:", err);
|
|
27769
28167
|
}
|
|
27770
|
-
|
|
27771
|
-
|
|
27772
|
-
|
|
28168
|
+
}
|
|
28169
|
+
try {
|
|
28170
|
+
const available = await this.qmd.probe();
|
|
28171
|
+
if (available) {
|
|
28172
|
+
log.info(`Search backend: available ${this.qmd.debugStatus()}`);
|
|
28173
|
+
const namespaces = this.config.namespacesEnabled ? this.configuredNamespaces() : [this.config.defaultNamespace];
|
|
28174
|
+
const states = await Promise.all(
|
|
28175
|
+
namespaces.map(async (namespace) => ({
|
|
28176
|
+
namespace,
|
|
28177
|
+
state: this.config.namespacesEnabled ? await this.namespaceSearchRouter.ensureNamespaceCollection(
|
|
28178
|
+
namespace
|
|
28179
|
+
) : await this.qmd.ensureCollection(this.config.memoryDir)
|
|
28180
|
+
}))
|
|
28181
|
+
);
|
|
28182
|
+
const defaultState2 = states.find(
|
|
28183
|
+
(entry) => entry.namespace === this.config.defaultNamespace
|
|
28184
|
+
)?.state ?? "unknown";
|
|
28185
|
+
if (defaultState2 === "missing") {
|
|
28186
|
+
this.qmd = new NoopSearchBackend();
|
|
28187
|
+
log.warn(
|
|
28188
|
+
"Search collection missing for Remnic memory store; disabling search retrieval for this runtime (fallback retrieval remains enabled)"
|
|
28189
|
+
);
|
|
28190
|
+
} else if (defaultState2 === "unknown") {
|
|
27773
28191
|
log.warn(
|
|
27774
|
-
|
|
28192
|
+
"Search collection check unavailable; keeping search retrieval enabled for fail-open behavior"
|
|
27775
28193
|
);
|
|
28194
|
+
} else if (defaultState2 === "skipped") {
|
|
28195
|
+
log.debug(
|
|
28196
|
+
"Search collection check skipped (remote or daemon-only mode)"
|
|
28197
|
+
);
|
|
28198
|
+
}
|
|
28199
|
+
for (const entry of states) {
|
|
28200
|
+
if (entry.namespace === this.config.defaultNamespace) continue;
|
|
28201
|
+
if (entry.state === "missing") {
|
|
28202
|
+
log.warn(
|
|
28203
|
+
`Search collection missing for namespace '${entry.namespace}'; namespace retrieval will fail open to non-search paths`
|
|
28204
|
+
);
|
|
28205
|
+
}
|
|
27776
28206
|
}
|
|
28207
|
+
} else if (this.qmd instanceof NoopSearchBackend) {
|
|
28208
|
+
log.debug(`Search backend: noop (search intentionally disabled)`);
|
|
28209
|
+
} else {
|
|
28210
|
+
log.warn(`Search backend: not available ${this.qmd.debugStatus()}`);
|
|
27777
28211
|
}
|
|
27778
|
-
}
|
|
27779
|
-
log.
|
|
27780
|
-
}
|
|
27781
|
-
|
|
28212
|
+
} catch (err) {
|
|
28213
|
+
log.error(`QMD probe/collection check failed (non-fatal): ${err}`);
|
|
28214
|
+
}
|
|
28215
|
+
if (this.resolveInit) {
|
|
28216
|
+
this.resolveInit();
|
|
28217
|
+
this.resolveInit = null;
|
|
28218
|
+
log.info("init gate opened (essential state + QMD state loaded)");
|
|
28219
|
+
}
|
|
28220
|
+
const resolveDeferred = this.resolveDeferredReady;
|
|
28221
|
+
this.resolveDeferredReady = null;
|
|
28222
|
+
this.deferredInitAbort = new AbortController();
|
|
28223
|
+
this.deferredInitialize(this.deferredInitAbort.signal).catch((err) => {
|
|
28224
|
+
log.error(`deferred initialization failed (non-fatal): ${err}`);
|
|
28225
|
+
}).finally(() => {
|
|
28226
|
+
resolveDeferred?.();
|
|
28227
|
+
});
|
|
28228
|
+
} catch (err) {
|
|
28229
|
+
if (this.resolveInit) {
|
|
28230
|
+
this.resolveInit();
|
|
28231
|
+
this.resolveInit = null;
|
|
27782
28232
|
}
|
|
28233
|
+
if (this.resolveDeferredReady) {
|
|
28234
|
+
this.resolveDeferredReady();
|
|
28235
|
+
this.resolveDeferredReady = null;
|
|
28236
|
+
}
|
|
28237
|
+
throw err;
|
|
27783
28238
|
}
|
|
28239
|
+
}
|
|
28240
|
+
async deferredInitialize(signal) {
|
|
28241
|
+
if (this.qmd.isAvailable() && this.config.qmdMaintenanceEnabled) {
|
|
28242
|
+
try {
|
|
28243
|
+
log.info("QMD startup sync: updating index to match current disk state");
|
|
28244
|
+
if (this.config.namespacesEnabled) {
|
|
28245
|
+
await this.namespaceSearchRouter.updateNamespaces(
|
|
28246
|
+
this.configuredNamespaces()
|
|
28247
|
+
);
|
|
28248
|
+
} else {
|
|
28249
|
+
await this.qmd.update();
|
|
28250
|
+
}
|
|
28251
|
+
log.info("QMD startup sync: complete");
|
|
28252
|
+
this.deferredSyncSucceeded = true;
|
|
28253
|
+
} catch (err) {
|
|
28254
|
+
log.warn(`QMD startup sync failed (non-fatal): ${err}`);
|
|
28255
|
+
}
|
|
28256
|
+
} else if (!this.qmd.isAvailable()) {
|
|
28257
|
+
} else {
|
|
28258
|
+
this.deferredSyncSucceeded = true;
|
|
28259
|
+
}
|
|
28260
|
+
if (signal.aborted) return;
|
|
27784
28261
|
const warmupPromises = [];
|
|
27785
28262
|
if (this.qmd.isAvailable()) {
|
|
27786
28263
|
const warmupNs = this.config.defaultNamespace;
|
|
@@ -27805,68 +28282,173 @@ var Orchestrator = class _Orchestrator {
|
|
|
27805
28282
|
);
|
|
27806
28283
|
}
|
|
27807
28284
|
await Promise.all(warmupPromises);
|
|
28285
|
+
if (signal.aborted) return;
|
|
28286
|
+
const cacheWarmups = [];
|
|
27808
28287
|
if (this.config.knowledgeIndexEnabled) {
|
|
27809
|
-
(
|
|
27810
|
-
|
|
27811
|
-
|
|
27812
|
-
|
|
27813
|
-
|
|
27814
|
-
|
|
27815
|
-
|
|
27816
|
-
|
|
27817
|
-
|
|
27818
|
-
|
|
28288
|
+
cacheWarmups.push(
|
|
28289
|
+
(async () => {
|
|
28290
|
+
try {
|
|
28291
|
+
const t0 = Date.now();
|
|
28292
|
+
await this.storage.buildKnowledgeIndex(this.config);
|
|
28293
|
+
log.info(`Knowledge Index warmup: complete in ${Date.now() - t0}ms`);
|
|
28294
|
+
} catch (err) {
|
|
28295
|
+
log.debug(`Knowledge Index warmup failed (non-fatal): ${err}`);
|
|
28296
|
+
}
|
|
28297
|
+
})()
|
|
28298
|
+
);
|
|
27819
28299
|
}
|
|
27820
|
-
this.storage.readAllMemories().
|
|
27821
|
-
})
|
|
27822
|
-
|
|
27823
|
-
|
|
28300
|
+
cacheWarmups.push(this.storage.readAllMemories().then(() => {
|
|
28301
|
+
}).catch(() => {
|
|
28302
|
+
}));
|
|
28303
|
+
cacheWarmups.push(this.storage.readAllEntityFiles().then(() => {
|
|
28304
|
+
}).catch(() => {
|
|
28305
|
+
}));
|
|
28306
|
+
await Promise.all(cacheWarmups);
|
|
28307
|
+
if (signal.aborted) return;
|
|
27824
28308
|
if (this.config.conversationIndexEnabled && this.conversationIndexBackend) {
|
|
27825
|
-
|
|
27826
|
-
|
|
28309
|
+
try {
|
|
28310
|
+
const init = await this.conversationIndexBackend.initialize();
|
|
28311
|
+
if (!init.enabled) {
|
|
28312
|
+
this.config.conversationIndexEnabled = false;
|
|
28313
|
+
}
|
|
28314
|
+
if (init.logLevel === "info") {
|
|
28315
|
+
log.info(init.message);
|
|
28316
|
+
} else if (init.logLevel === "warn") {
|
|
28317
|
+
log.warn(init.message);
|
|
28318
|
+
} else {
|
|
28319
|
+
log.debug(init.message);
|
|
28320
|
+
}
|
|
28321
|
+
} catch (err) {
|
|
28322
|
+
log.error(`Conversation index initialization failed (non-fatal): ${err}`);
|
|
27827
28323
|
this.config.conversationIndexEnabled = false;
|
|
27828
28324
|
}
|
|
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
28325
|
}
|
|
27837
|
-
|
|
28326
|
+
if (signal.aborted) return;
|
|
27838
28327
|
if (this.config.localLlmEnabled) {
|
|
27839
|
-
await this.validateLocalLlmModel();
|
|
27840
|
-
}
|
|
27841
|
-
if (this.config.compactionResetEnabled) {
|
|
27842
28328
|
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
|
-
}
|
|
28329
|
+
await this.validateLocalLlmModel();
|
|
27855
28330
|
} catch (err) {
|
|
27856
|
-
log.
|
|
28331
|
+
log.error(`Local LLM validation failed (non-fatal): ${err}`);
|
|
27857
28332
|
}
|
|
27858
28333
|
}
|
|
27859
|
-
|
|
28334
|
+
if (signal.aborted) return;
|
|
27860
28335
|
if (this.config.daySummaryEnabled) {
|
|
27861
|
-
|
|
28336
|
+
try {
|
|
28337
|
+
await this.autoRegisterDaySummaryCron();
|
|
28338
|
+
} catch (err) {
|
|
27862
28339
|
log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);
|
|
27863
|
-
}
|
|
28340
|
+
}
|
|
27864
28341
|
}
|
|
27865
28342
|
if (this.config.nightlyGovernanceCronAutoRegister) {
|
|
27866
|
-
|
|
28343
|
+
try {
|
|
28344
|
+
await this.autoRegisterNightlyGovernanceCron();
|
|
28345
|
+
} catch (err) {
|
|
27867
28346
|
log.debug(`nightly governance cron auto-register failed (non-fatal): ${err}`);
|
|
27868
|
-
}
|
|
28347
|
+
}
|
|
28348
|
+
}
|
|
28349
|
+
if (this.config.procedural?.proceduralMiningCronAutoRegister) {
|
|
28350
|
+
try {
|
|
28351
|
+
await this.autoRegisterProceduralMiningCron();
|
|
28352
|
+
} catch (err) {
|
|
28353
|
+
log.debug(`procedural mining cron auto-register failed (non-fatal): ${err}`);
|
|
28354
|
+
}
|
|
28355
|
+
}
|
|
28356
|
+
log.info("orchestrator initialized (full \u2014 deferred steps complete)");
|
|
28357
|
+
}
|
|
28358
|
+
/**
|
|
28359
|
+
* Namespace-aware startup search sync. Re-probes QMD, ensures collections
|
|
28360
|
+
* (namespace-aware when namespacesEnabled), runs update, and warms up search.
|
|
28361
|
+
* Designed for server retry paths that run after the deferred init completes
|
|
28362
|
+
* when QMD was not available during initial startup.
|
|
28363
|
+
*
|
|
28364
|
+
* Accepts an optional AbortSignal so callers can interrupt the sync during
|
|
28365
|
+
* shutdown. The signal is checked between phases and forwarded into the QMD
|
|
28366
|
+
* update and warmup search calls so a long-running `qmd update` subprocess
|
|
28367
|
+
* is killed promptly rather than left in flight after `httpServer.stop()`.
|
|
28368
|
+
*
|
|
28369
|
+
* Returns true if the sync succeeded (QMD now available), false otherwise.
|
|
28370
|
+
*/
|
|
28371
|
+
async startupSearchSync(signal) {
|
|
28372
|
+
if (signal?.aborted) return false;
|
|
28373
|
+
const available = await this.qmd.probe();
|
|
28374
|
+
if (!available) return false;
|
|
28375
|
+
if (signal?.aborted) {
|
|
28376
|
+
log.debug("startupSearchSync: aborted after probe");
|
|
28377
|
+
return false;
|
|
28378
|
+
}
|
|
28379
|
+
log.info(`startupSearchSync: backend now available ${this.qmd.debugStatus()}`);
|
|
28380
|
+
if (this.config.namespacesEnabled) {
|
|
28381
|
+
this.namespaceSearchRouter.clearCache();
|
|
28382
|
+
}
|
|
28383
|
+
const namespaces = this.config.namespacesEnabled ? this.configuredNamespaces() : [this.config.defaultNamespace];
|
|
28384
|
+
const states = await Promise.all(
|
|
28385
|
+
namespaces.map(async (namespace) => ({
|
|
28386
|
+
namespace,
|
|
28387
|
+
state: this.config.namespacesEnabled ? await this.namespaceSearchRouter.ensureNamespaceCollection(namespace) : await this.qmd.ensureCollection(this.config.memoryDir)
|
|
28388
|
+
}))
|
|
28389
|
+
);
|
|
28390
|
+
if (signal?.aborted) {
|
|
28391
|
+
log.debug("startupSearchSync: aborted after ensureCollection");
|
|
28392
|
+
return false;
|
|
28393
|
+
}
|
|
28394
|
+
const defaultState2 = states.find((e) => e.namespace === this.config.defaultNamespace)?.state ?? "unknown";
|
|
28395
|
+
if (defaultState2 === "missing") {
|
|
28396
|
+
if ("available" in this.qmd) {
|
|
28397
|
+
this.qmd.available = false;
|
|
28398
|
+
}
|
|
28399
|
+
this.qmd = new NoopSearchBackend();
|
|
28400
|
+
log.warn("startupSearchSync: search collection missing; disabling search (fallback retrieval remains enabled)");
|
|
28401
|
+
return false;
|
|
28402
|
+
}
|
|
28403
|
+
if (this.config.qmdMaintenanceEnabled) {
|
|
28404
|
+
try {
|
|
28405
|
+
const failTsBefore = "lastUpdateFailedAtMs" in this.qmd ? this.qmd.lastUpdateFailedAtMs : null;
|
|
28406
|
+
const hasRunTs = "lastUpdateRanAtMs" in this.qmd;
|
|
28407
|
+
if ("resetUpdateThrottles" in this.qmd) {
|
|
28408
|
+
this.qmd.resetUpdateThrottles();
|
|
28409
|
+
}
|
|
28410
|
+
log.info("startupSearchSync: updating index to match current disk state");
|
|
28411
|
+
let namespacesUpdated = 0;
|
|
28412
|
+
if (this.config.namespacesEnabled) {
|
|
28413
|
+
namespacesUpdated = await this.namespaceSearchRouter.updateNamespaces(namespaces);
|
|
28414
|
+
} else {
|
|
28415
|
+
await this.qmd.update(signal);
|
|
28416
|
+
}
|
|
28417
|
+
if (signal?.aborted) {
|
|
28418
|
+
log.debug("startupSearchSync: aborted after update");
|
|
28419
|
+
return false;
|
|
28420
|
+
}
|
|
28421
|
+
const failTsAfter = "lastUpdateFailedAtMs" in this.qmd ? this.qmd.lastUpdateFailedAtMs : null;
|
|
28422
|
+
const runTsAfter = hasRunTs ? this.qmd.lastUpdateRanAtMs : null;
|
|
28423
|
+
if (failTsAfter !== null && failTsAfter !== failTsBefore) {
|
|
28424
|
+
log.warn("startupSearchSync: update silently failed (detected via fail timestamp)");
|
|
28425
|
+
return false;
|
|
28426
|
+
}
|
|
28427
|
+
if (this.config.namespacesEnabled) {
|
|
28428
|
+
if (namespacesUpdated === 0) {
|
|
28429
|
+
log.warn("startupSearchSync: no namespace backends were eligible for update (all unavailable or collections missing)");
|
|
28430
|
+
return false;
|
|
28431
|
+
}
|
|
28432
|
+
log.info(`startupSearchSync: namespace updates succeeded (${namespacesUpdated}/${namespaces.length} namespaces updated)`);
|
|
28433
|
+
} else if (hasRunTs && runTsAfter === null) {
|
|
28434
|
+
log.warn("startupSearchSync: update was throttled/skipped (run timestamp is null after reset + update)");
|
|
28435
|
+
return false;
|
|
28436
|
+
}
|
|
28437
|
+
log.info("startupSearchSync: sync complete");
|
|
28438
|
+
} catch (err) {
|
|
28439
|
+
log.warn(`startupSearchSync: update failed: ${err}`);
|
|
28440
|
+
return false;
|
|
28441
|
+
}
|
|
28442
|
+
}
|
|
28443
|
+
if (!signal?.aborted) {
|
|
28444
|
+
try {
|
|
28445
|
+
await this.qmd.search("warmup", this.config.defaultNamespace, 1, void 0, { signal });
|
|
28446
|
+
log.info("startupSearchSync: warmup complete");
|
|
28447
|
+
} catch (err) {
|
|
28448
|
+
log.debug(`startupSearchSync: warmup search failed (non-fatal): ${err}`);
|
|
28449
|
+
}
|
|
27869
28450
|
}
|
|
28451
|
+
return true;
|
|
27870
28452
|
}
|
|
27871
28453
|
/**
|
|
27872
28454
|
* Auto-register the engram-day-summary cron job in OpenClaw if it doesn't exist.
|
|
@@ -27918,6 +28500,26 @@ var Orchestrator = class _Orchestrator {
|
|
|
27918
28500
|
log.debug(`nightly governance cron auto-register error: ${err}`);
|
|
27919
28501
|
}
|
|
27920
28502
|
}
|
|
28503
|
+
async autoRegisterProceduralMiningCron() {
|
|
28504
|
+
const home = resolveHomeDir();
|
|
28505
|
+
const jobsPath = path43.join(home, ".openclaw", "cron", "jobs.json");
|
|
28506
|
+
try {
|
|
28507
|
+
if (!existsSync8(jobsPath)) {
|
|
28508
|
+
log.debug("procedural mining cron: jobs.json not found, skipping auto-register");
|
|
28509
|
+
return;
|
|
28510
|
+
}
|
|
28511
|
+
const created = await ensureProceduralMiningCron(jobsPath, {
|
|
28512
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
28513
|
+
});
|
|
28514
|
+
if (created.created) {
|
|
28515
|
+
log.info(`procedural mining cron auto-registered (${created.jobId})`);
|
|
28516
|
+
} else {
|
|
28517
|
+
log.debug("procedural mining cron already exists, skipping auto-register");
|
|
28518
|
+
}
|
|
28519
|
+
} catch (err) {
|
|
28520
|
+
log.debug(`procedural mining cron auto-register error: ${err}`);
|
|
28521
|
+
}
|
|
28522
|
+
}
|
|
27921
28523
|
async applyBehaviorRuntimePolicy(state) {
|
|
27922
28524
|
const result = await this.policyRuntime.applyFromBehaviorState(state);
|
|
27923
28525
|
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
@@ -30381,7 +30983,7 @@ ${trimmedBody}`;
|
|
|
30381
30983
|
return null;
|
|
30382
30984
|
}
|
|
30383
30985
|
try {
|
|
30384
|
-
const { retrieveCausalChains } = await import("./causal-retrieval-
|
|
30986
|
+
const { retrieveCausalChains } = await import("./causal-retrieval-3BKBXVXD.js");
|
|
30385
30987
|
const section = await retrieveCausalChains({
|
|
30386
30988
|
memoryDir: this.config.memoryDir,
|
|
30387
30989
|
causalTrajectoryStoreDir: this.config.causalTrajectoryStoreDir,
|
|
@@ -31317,6 +31919,22 @@ ${formatted}`;
|
|
|
31317
31919
|
});
|
|
31318
31920
|
return section;
|
|
31319
31921
|
})();
|
|
31922
|
+
const procedureRecallPromise = (async () => {
|
|
31923
|
+
if (this.config.procedural?.enabled !== true) return null;
|
|
31924
|
+
if (!this.isRecallSectionEnabled("procedure-recall", true)) return null;
|
|
31925
|
+
try {
|
|
31926
|
+
return await buildProcedureRecallSection(
|
|
31927
|
+
this.storage,
|
|
31928
|
+
retrievalQuery,
|
|
31929
|
+
this.config
|
|
31930
|
+
);
|
|
31931
|
+
} catch (err) {
|
|
31932
|
+
log.debug(
|
|
31933
|
+
`procedure-recall: failed open: ${err instanceof Error ? err.message : String(err)}`
|
|
31934
|
+
);
|
|
31935
|
+
return null;
|
|
31936
|
+
}
|
|
31937
|
+
})();
|
|
31320
31938
|
const compoundingPromise = observeEnrichmentPromise(
|
|
31321
31939
|
(async () => {
|
|
31322
31940
|
const t0 = Date.now();
|
|
@@ -31381,6 +31999,7 @@ ${formatted}`;
|
|
|
31381
31999
|
causalTrajectorySection,
|
|
31382
32000
|
cmcCausalChainsSection,
|
|
31383
32001
|
calibrationSection,
|
|
32002
|
+
procedureRecallSection,
|
|
31384
32003
|
trustZoneSection,
|
|
31385
32004
|
verifiedRecallSection,
|
|
31386
32005
|
verifiedRulesSection,
|
|
@@ -31402,6 +32021,7 @@ ${formatted}`;
|
|
|
31402
32021
|
["causalTraj", causalTrajectoryPromise],
|
|
31403
32022
|
["cmc", cmcRetrievalPromise],
|
|
31404
32023
|
["calibration", calibrationPromise],
|
|
32024
|
+
["procedureRecall", procedureRecallPromise],
|
|
31405
32025
|
["trustZone", trustZonePromise],
|
|
31406
32026
|
["verifiedRecall", verifiedRecallPromise],
|
|
31407
32027
|
["verifiedRules", verifiedRulesPromise],
|
|
@@ -31507,6 +32127,13 @@ ${profile}`
|
|
|
31507
32127
|
calibrationSection
|
|
31508
32128
|
);
|
|
31509
32129
|
}
|
|
32130
|
+
if (procedureRecallSection) {
|
|
32131
|
+
this.appendRecallSection(
|
|
32132
|
+
sectionBuckets,
|
|
32133
|
+
"procedure-recall",
|
|
32134
|
+
procedureRecallSection
|
|
32135
|
+
);
|
|
32136
|
+
}
|
|
31510
32137
|
if (identityContinuity) {
|
|
31511
32138
|
this.appendRecallSection(
|
|
31512
32139
|
sectionBuckets,
|
|
@@ -32551,6 +33178,87 @@ _Context: ${topQuestion.context}_`
|
|
|
32551
33178
|
}
|
|
32552
33179
|
}
|
|
32553
33180
|
}
|
|
33181
|
+
/**
|
|
33182
|
+
* Return the namespace that `ingestBulkImportBatch` writes into (#460).
|
|
33183
|
+
*
|
|
33184
|
+
* Exposed so host CLIs can snapshot the same storage root that extraction
|
|
33185
|
+
* actually writes to, avoiding the "CLI counts files at namespace A while
|
|
33186
|
+
* writes land in namespace B" footgun that a naïve
|
|
33187
|
+
* `config.defaultNamespace` snapshot could hit when a namespace policy
|
|
33188
|
+
* named `"default"` also exists.
|
|
33189
|
+
*
|
|
33190
|
+
* Today bulk-import is pinned to `config.defaultNamespace`; future
|
|
33191
|
+
* per-invocation namespace routing would thread an explicit target here
|
|
33192
|
+
* and through `ingestBulkImportBatch`.
|
|
33193
|
+
*/
|
|
33194
|
+
bulkImportWriteNamespace() {
|
|
33195
|
+
return this.config.defaultNamespace;
|
|
33196
|
+
}
|
|
33197
|
+
/**
|
|
33198
|
+
* Ingest a batch of bulk-import turns (#460). Like ingestReplayBatch, this
|
|
33199
|
+
* normalizes user/assistant turns into the extraction buffer and awaits
|
|
33200
|
+
* settlement, but it intentionally bypasses the captureMode="explicit"
|
|
33201
|
+
* gate because bulk-import is itself an explicit user action — the user
|
|
33202
|
+
* ran `bulk-import --source <name> --file ...` and would be surprised to
|
|
33203
|
+
* see the command silently no-op when capture is otherwise restricted.
|
|
33204
|
+
*
|
|
33205
|
+
* Turns with role="other" are skipped (not supported by the extraction
|
|
33206
|
+
* pipeline).
|
|
33207
|
+
*
|
|
33208
|
+
* Two design decisions worth calling out:
|
|
33209
|
+
*
|
|
33210
|
+
* - **sessionKey is truthy and per-batch-unique.**
|
|
33211
|
+
* `ThreadingManager.shouldStartNewThread` only applies the session-key
|
|
33212
|
+
* boundary check when `turn.sessionKey` is truthy (threading.ts:82);
|
|
33213
|
+
* with an empty string, imported turns could attach to the current
|
|
33214
|
+
* live thread or merge across unrelated import batches. A unique
|
|
33215
|
+
* `bulk-import:batch:<timestamp>-<rand>` key forces a fresh thread per
|
|
33216
|
+
* batch without matching common prefix/map rules in
|
|
33217
|
+
* `principalFromSessionKeyRules`. (Catch-all regex rules could still
|
|
33218
|
+
* remap the principal, but that only affects metadata provenance —
|
|
33219
|
+
* see the next point for why write routing is unaffected.)
|
|
33220
|
+
*
|
|
33221
|
+
* - **writeNamespaceOverride pins the storage target.**
|
|
33222
|
+
* We pass `writeNamespaceOverride: this.bulkImportWriteNamespace()` to
|
|
33223
|
+
* `queueBufferedExtraction`, which tells `runExtraction` to skip
|
|
33224
|
+
* `defaultNamespaceForPrincipal` and write directly into the
|
|
33225
|
+
* orchestrator's declared bulk-import write namespace. This keeps
|
|
33226
|
+
* writes deterministic even when namespace policies named `"default"`
|
|
33227
|
+
* exist alongside a different `config.defaultNamespace`, and also
|
|
33228
|
+
* guards against regex-catch-all principal rules steering bulk-import
|
|
33229
|
+
* into an unexpected tenant.
|
|
33230
|
+
*
|
|
33231
|
+
* Per-invocation namespace routing (letting callers target a namespace
|
|
33232
|
+
* other than `bulkImportWriteNamespace()`) is a separate feature tracked
|
|
33233
|
+
* as a follow-up — the hook is the `writeNamespaceOverride` option, but
|
|
33234
|
+
* the CLI surface does not yet expose a `--namespace` flag.
|
|
33235
|
+
*/
|
|
33236
|
+
async ingestBulkImportBatch(turns, options = {}) {
|
|
33237
|
+
if (!Array.isArray(turns) || turns.length === 0) return;
|
|
33238
|
+
const sessionKey = `bulk-import:batch:${Date.now().toString(36)}-` + randomBytes(6).toString("hex");
|
|
33239
|
+
const sessionTurns = [];
|
|
33240
|
+
for (const turn of turns) {
|
|
33241
|
+
if (turn.role !== "user" && turn.role !== "assistant") continue;
|
|
33242
|
+
sessionTurns.push({
|
|
33243
|
+
role: turn.role,
|
|
33244
|
+
content: turn.content,
|
|
33245
|
+
timestamp: turn.timestamp,
|
|
33246
|
+
sessionKey
|
|
33247
|
+
});
|
|
33248
|
+
}
|
|
33249
|
+
if (sessionTurns.length === 0) return;
|
|
33250
|
+
await new Promise((resolve, reject) => {
|
|
33251
|
+
void this.queueBufferedExtraction(sessionTurns, "trigger_mode", {
|
|
33252
|
+
skipDedupeCheck: true,
|
|
33253
|
+
clearBufferAfterExtraction: false,
|
|
33254
|
+
skipCharThreshold: true,
|
|
33255
|
+
bufferKey: sessionKey,
|
|
33256
|
+
extractionDeadlineMs: options.deadlineMs,
|
|
33257
|
+
writeNamespaceOverride: this.bulkImportWriteNamespace(),
|
|
33258
|
+
onTaskSettled: (err) => err ? reject(err) : resolve()
|
|
33259
|
+
}).catch(reject);
|
|
33260
|
+
});
|
|
33261
|
+
}
|
|
32554
33262
|
async observeSessionHeartbeat(sessionKey, options = {}) {
|
|
32555
33263
|
if (this.config.sessionObserverEnabled !== true) return;
|
|
32556
33264
|
if (!sessionKey || sessionKey.length === 0) return;
|
|
@@ -32617,7 +33325,8 @@ _Context: ${topQuestion.context}_`
|
|
|
32617
33325
|
skipCharThreshold: options.skipCharThreshold ?? false,
|
|
32618
33326
|
deadlineMs: options.extractionDeadlineMs,
|
|
32619
33327
|
bufferKey,
|
|
32620
|
-
abortSignal: options.abortSignal
|
|
33328
|
+
abortSignal: options.abortSignal,
|
|
33329
|
+
writeNamespaceOverride: options.writeNamespaceOverride
|
|
32621
33330
|
});
|
|
32622
33331
|
options.onTaskSettled?.();
|
|
32623
33332
|
} catch (err) {
|
|
@@ -32743,7 +33452,7 @@ ${normalized}`).digest("hex");
|
|
|
32743
33452
|
return;
|
|
32744
33453
|
}
|
|
32745
33454
|
const principal = resolvePrincipal(sessionKey, this.config);
|
|
32746
|
-
const selfNamespace = defaultNamespaceForPrincipal(principal, this.config);
|
|
33455
|
+
const selfNamespace = typeof options.writeNamespaceOverride === "string" && options.writeNamespaceOverride.length > 0 ? options.writeNamespaceOverride : defaultNamespaceForPrincipal(principal, this.config);
|
|
32747
33456
|
const storage = await this.storageRouter.storageFor(selfNamespace);
|
|
32748
33457
|
const shouldPersistProcessedFingerprint = normalizedTurns.some(
|
|
32749
33458
|
(turn) => turn.persistProcessedFingerprint === true
|
|
@@ -33427,6 +34136,9 @@ ${normalized}`).digest("hex");
|
|
|
33427
34136
|
continue;
|
|
33428
34137
|
}
|
|
33429
34138
|
const judgeCategory = preRoutedCategories[fi] ?? f.category;
|
|
34139
|
+
if (judgeCategory === "procedure") {
|
|
34140
|
+
continue;
|
|
34141
|
+
}
|
|
33430
34142
|
const tags = Array.isArray(f.tags) ? f.tags : [];
|
|
33431
34143
|
const imp = scoreImportance(
|
|
33432
34144
|
f.content,
|
|
@@ -33521,6 +34233,10 @@ ${normalized}`).digest("hex");
|
|
|
33521
34233
|
writeCategory,
|
|
33522
34234
|
fact.tags
|
|
33523
34235
|
);
|
|
34236
|
+
if (writeCategory === "procedure" && this.config.procedural?.enabled !== true) {
|
|
34237
|
+
log.debug("persistExtraction: skip procedure memory (procedural.enabled is false)");
|
|
34238
|
+
continue;
|
|
34239
|
+
}
|
|
33524
34240
|
if (!isAboveImportanceThreshold(
|
|
33525
34241
|
importance.level,
|
|
33526
34242
|
this.config.extractionMinImportanceLevel
|
|
@@ -33549,6 +34265,24 @@ ${normalized}`).digest("hex");
|
|
|
33549
34265
|
}
|
|
33550
34266
|
}
|
|
33551
34267
|
}
|
|
34268
|
+
if (writeCategory === "procedure") {
|
|
34269
|
+
const procGate = validateProcedureExtraction({
|
|
34270
|
+
content: fact.content,
|
|
34271
|
+
procedureSteps: fact.procedureSteps
|
|
34272
|
+
});
|
|
34273
|
+
if (!procGate.durable) {
|
|
34274
|
+
if (this.config.extractionJudgeShadow) {
|
|
34275
|
+
log.info(
|
|
34276
|
+
`extraction-procedure-gate[shadow]: would reject "${fact.content.slice(0, 60)}\u2026" reason="${procGate.reason}"`
|
|
34277
|
+
);
|
|
34278
|
+
} else {
|
|
34279
|
+
log.debug(
|
|
34280
|
+
`extraction-procedure-gate: rejected "${fact.content.slice(0, 60)}\u2026" reason="${procGate.reason}"`
|
|
34281
|
+
);
|
|
34282
|
+
continue;
|
|
34283
|
+
}
|
|
34284
|
+
}
|
|
34285
|
+
}
|
|
33552
34286
|
let pendingSemanticSkip = null;
|
|
33553
34287
|
if (this.config.semanticDedupEnabled) {
|
|
33554
34288
|
let semanticDecision;
|
|
@@ -33628,7 +34362,7 @@ ${normalized}`).digest("hex");
|
|
|
33628
34362
|
dedupedCount++;
|
|
33629
34363
|
continue;
|
|
33630
34364
|
}
|
|
33631
|
-
if (this.config.chunkingEnabled) {
|
|
34365
|
+
if (this.config.chunkingEnabled && writeCategory !== "procedure") {
|
|
33632
34366
|
let chunkResult;
|
|
33633
34367
|
if (this.config.semanticChunkingEnabled) {
|
|
33634
34368
|
try {
|
|
@@ -33823,8 +34557,9 @@ ${normalized}`).digest("hex");
|
|
|
33823
34557
|
links.push(...suggestedLinks);
|
|
33824
34558
|
}
|
|
33825
34559
|
}
|
|
33826
|
-
const memoryKind = this.config.episodeNoteModeEnabled ? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory) : void 0;
|
|
33827
|
-
const
|
|
34560
|
+
const memoryKind = writeCategory === "procedure" ? void 0 : this.config.episodeNoteModeEnabled ? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory) : void 0;
|
|
34561
|
+
const rawPersistBody = writeCategory === "procedure" ? buildProcedurePersistBody(fact.content, fact.procedureSteps) : fact.content;
|
|
34562
|
+
const citedFactContent = applyInlineCitation(rawPersistBody);
|
|
33828
34563
|
const memoryId = await targetStorage.writeMemory(
|
|
33829
34564
|
writeCategory,
|
|
33830
34565
|
citedFactContent,
|
|
@@ -33841,7 +34576,7 @@ ${normalized}`).digest("hex");
|
|
|
33841
34576
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
33842
34577
|
memoryKind,
|
|
33843
34578
|
structuredAttributes: fact.structuredAttributes,
|
|
33844
|
-
contentHashSource: fact.content
|
|
34579
|
+
contentHashSource: writeCategory === "fact" ? fact.content : void 0
|
|
33845
34580
|
}
|
|
33846
34581
|
);
|
|
33847
34582
|
if (routedRuleId) {
|
|
@@ -39065,8 +39800,8 @@ Best for:
|
|
|
39065
39800
|
),
|
|
39066
39801
|
category: Type.Optional(
|
|
39067
39802
|
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"]
|
|
39803
|
+
description: 'Category: "fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule", "procedure" (default: "fact")',
|
|
39804
|
+
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule", "procedure"]
|
|
39070
39805
|
})
|
|
39071
39806
|
),
|
|
39072
39807
|
tags: Type.Optional(
|
|
@@ -39124,7 +39859,7 @@ Best for:
|
|
|
39124
39859
|
category: Type.Optional(
|
|
39125
39860
|
Type.String({
|
|
39126
39861
|
description: "Memory category.",
|
|
39127
|
-
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule"]
|
|
39862
|
+
enum: ["fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule", "procedure"]
|
|
39128
39863
|
})
|
|
39129
39864
|
),
|
|
39130
39865
|
tags: Type.Optional(
|
|
@@ -40193,8 +40928,8 @@ Returns: Performance trace data with timing breakdown`,
|
|
|
40193
40928
|
}
|
|
40194
40929
|
|
|
40195
40930
|
// ../remnic-core/src/cli.ts
|
|
40196
|
-
import
|
|
40197
|
-
import { access as access5, readFile as
|
|
40931
|
+
import path72 from "path";
|
|
40932
|
+
import { access as access5, readFile as readFile46, readdir as readdir26, unlink as unlink11 } from "fs/promises";
|
|
40198
40933
|
import { createHash as createHash14 } from "crypto";
|
|
40199
40934
|
|
|
40200
40935
|
// ../remnic-core/src/transfer/export-json.ts
|
|
@@ -41071,8 +41806,8 @@ function gatherCandidates(input, warnings) {
|
|
|
41071
41806
|
const record = rec;
|
|
41072
41807
|
const content = typeof record.content === "string" ? record.content : null;
|
|
41073
41808
|
if (!content) continue;
|
|
41074
|
-
const
|
|
41075
|
-
if (!
|
|
41809
|
+
const path100 = typeof record.path === "string" ? record.path : "";
|
|
41810
|
+
if (!path100.startsWith("transcripts/") && !path100.includes("/transcripts/")) continue;
|
|
41076
41811
|
rows.push(...parseJsonl(content, warnings));
|
|
41077
41812
|
}
|
|
41078
41813
|
return rows;
|
|
@@ -41127,6 +41862,158 @@ var openclawReplayNormalizer = {
|
|
|
41127
41862
|
}
|
|
41128
41863
|
};
|
|
41129
41864
|
|
|
41865
|
+
// ../remnic-core/src/bulk-import/types.ts
|
|
41866
|
+
var VALID_ROLES2 = /* @__PURE__ */ new Set(["user", "assistant", "other"]);
|
|
41867
|
+
function isImportRole(value) {
|
|
41868
|
+
return typeof value === "string" && VALID_ROLES2.has(value);
|
|
41869
|
+
}
|
|
41870
|
+
function parseIsoTimestamp2(value) {
|
|
41871
|
+
return parseIsoOffsetTimestamp(value);
|
|
41872
|
+
}
|
|
41873
|
+
function validateImportTurn(turn, index) {
|
|
41874
|
+
const issues = [];
|
|
41875
|
+
if (!turn || typeof turn !== "object") {
|
|
41876
|
+
issues.push({
|
|
41877
|
+
code: "turn.invalid",
|
|
41878
|
+
message: "Import turn must be an object.",
|
|
41879
|
+
index
|
|
41880
|
+
});
|
|
41881
|
+
return issues;
|
|
41882
|
+
}
|
|
41883
|
+
if (!isImportRole(turn.role)) {
|
|
41884
|
+
issues.push({
|
|
41885
|
+
code: "turn.role.invalid",
|
|
41886
|
+
message: `Import turn role must be 'user', 'assistant', or 'other', received '${String(turn.role)}'.`,
|
|
41887
|
+
index
|
|
41888
|
+
});
|
|
41889
|
+
}
|
|
41890
|
+
if (!turn.content || typeof turn.content !== "string" || turn.content.trim().length === 0) {
|
|
41891
|
+
issues.push({
|
|
41892
|
+
code: "turn.content.invalid",
|
|
41893
|
+
message: "Import turn content must be a non-empty string.",
|
|
41894
|
+
index
|
|
41895
|
+
});
|
|
41896
|
+
}
|
|
41897
|
+
if (!turn.timestamp || typeof turn.timestamp !== "string" || parseIsoTimestamp2(turn.timestamp) === null) {
|
|
41898
|
+
issues.push({
|
|
41899
|
+
code: "turn.timestamp.invalid",
|
|
41900
|
+
message: `Import turn timestamp must be a valid ISO timestamp, received '${String(turn.timestamp)}'.`,
|
|
41901
|
+
index
|
|
41902
|
+
});
|
|
41903
|
+
}
|
|
41904
|
+
return issues;
|
|
41905
|
+
}
|
|
41906
|
+
|
|
41907
|
+
// ../remnic-core/src/bulk-import/registry.ts
|
|
41908
|
+
var adapters = /* @__PURE__ */ new Map();
|
|
41909
|
+
function registerBulkImportSource(adapter) {
|
|
41910
|
+
if (!adapter || typeof adapter !== "object") {
|
|
41911
|
+
throw new Error("bulk-import adapter must be an object");
|
|
41912
|
+
}
|
|
41913
|
+
if (!adapter.name || typeof adapter.name !== "string" || adapter.name.trim().length === 0) {
|
|
41914
|
+
throw new Error("bulk-import adapter name must be a non-empty string");
|
|
41915
|
+
}
|
|
41916
|
+
if (typeof adapter.parse !== "function") {
|
|
41917
|
+
throw new Error(
|
|
41918
|
+
`bulk-import adapter '${adapter.name}' must have a parse function`
|
|
41919
|
+
);
|
|
41920
|
+
}
|
|
41921
|
+
const key = adapter.name.trim();
|
|
41922
|
+
if (adapters.has(key)) {
|
|
41923
|
+
throw new Error(
|
|
41924
|
+
`bulk-import source adapter '${key}' is already registered`
|
|
41925
|
+
);
|
|
41926
|
+
}
|
|
41927
|
+
const normalized = adapter.name === key ? adapter : { ...adapter, name: key };
|
|
41928
|
+
adapters.set(key, normalized);
|
|
41929
|
+
}
|
|
41930
|
+
function getBulkImportSource(name) {
|
|
41931
|
+
if (typeof name !== "string") return void 0;
|
|
41932
|
+
const key = name.trim();
|
|
41933
|
+
if (key.length === 0) return void 0;
|
|
41934
|
+
return adapters.get(key);
|
|
41935
|
+
}
|
|
41936
|
+
function listBulkImportSources() {
|
|
41937
|
+
return [...adapters.keys()];
|
|
41938
|
+
}
|
|
41939
|
+
|
|
41940
|
+
// ../remnic-core/src/bulk-import/pipeline.ts
|
|
41941
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
41942
|
+
var MIN_BATCH_SIZE = 1;
|
|
41943
|
+
var MAX_BATCH_SIZE = 1e3;
|
|
41944
|
+
function validateBatchSize(value) {
|
|
41945
|
+
if (value === void 0) return DEFAULT_BATCH_SIZE;
|
|
41946
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
41947
|
+
throw new Error(
|
|
41948
|
+
`batchSize must be a finite number, received ${String(value)}`
|
|
41949
|
+
);
|
|
41950
|
+
}
|
|
41951
|
+
if (!Number.isInteger(value)) {
|
|
41952
|
+
throw new Error(
|
|
41953
|
+
`batchSize must be an integer, received ${value}`
|
|
41954
|
+
);
|
|
41955
|
+
}
|
|
41956
|
+
if (value < MIN_BATCH_SIZE || value > MAX_BATCH_SIZE) {
|
|
41957
|
+
throw new Error(
|
|
41958
|
+
`batchSize must be between ${MIN_BATCH_SIZE} and ${MAX_BATCH_SIZE}, received ${value}`
|
|
41959
|
+
);
|
|
41960
|
+
}
|
|
41961
|
+
return value;
|
|
41962
|
+
}
|
|
41963
|
+
async function runBulkImportPipeline(source, options = {}, processBatch) {
|
|
41964
|
+
const batchSize = validateBatchSize(options.batchSize);
|
|
41965
|
+
const dryRun = options.dryRun === true;
|
|
41966
|
+
const result = {
|
|
41967
|
+
memoriesCreated: 0,
|
|
41968
|
+
duplicatesSkipped: 0,
|
|
41969
|
+
entitiesCreated: 0,
|
|
41970
|
+
turnsProcessed: 0,
|
|
41971
|
+
batchesProcessed: 0,
|
|
41972
|
+
errors: []
|
|
41973
|
+
};
|
|
41974
|
+
const turns = source.turns;
|
|
41975
|
+
if (!turns || turns.length === 0) {
|
|
41976
|
+
return result;
|
|
41977
|
+
}
|
|
41978
|
+
const validTurns = [];
|
|
41979
|
+
for (let i = 0; i < turns.length; i += 1) {
|
|
41980
|
+
const issues = validateImportTurn(turns[i], i);
|
|
41981
|
+
if (issues.length > 0) {
|
|
41982
|
+
const error = {
|
|
41983
|
+
batchIndex: -1,
|
|
41984
|
+
message: issues.map((iss) => iss.message).join("; ")
|
|
41985
|
+
};
|
|
41986
|
+
result.errors.push(error);
|
|
41987
|
+
} else {
|
|
41988
|
+
validTurns.push(turns[i]);
|
|
41989
|
+
}
|
|
41990
|
+
}
|
|
41991
|
+
if (dryRun) {
|
|
41992
|
+
result.turnsProcessed = validTurns.length;
|
|
41993
|
+
result.batchesProcessed = validTurns.length > 0 ? Math.ceil(validTurns.length / batchSize) : 0;
|
|
41994
|
+
return result;
|
|
41995
|
+
}
|
|
41996
|
+
let batchIndex = 0;
|
|
41997
|
+
for (let i = 0; i < validTurns.length; i += batchSize) {
|
|
41998
|
+
const batch = validTurns.slice(i, i + batchSize);
|
|
41999
|
+
try {
|
|
42000
|
+
const batchResult = await processBatch(batch);
|
|
42001
|
+
result.memoriesCreated += batchResult.memoriesCreated;
|
|
42002
|
+
result.duplicatesSkipped += batchResult.duplicatesSkipped;
|
|
42003
|
+
if (typeof batchResult.entitiesCreated === "number") {
|
|
42004
|
+
result.entitiesCreated += batchResult.entitiesCreated;
|
|
42005
|
+
}
|
|
42006
|
+
} catch (err) {
|
|
42007
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42008
|
+
result.errors.push({ batchIndex, message });
|
|
42009
|
+
}
|
|
42010
|
+
result.turnsProcessed += batch.length;
|
|
42011
|
+
result.batchesProcessed += 1;
|
|
42012
|
+
batchIndex += 1;
|
|
42013
|
+
}
|
|
42014
|
+
return result;
|
|
42015
|
+
}
|
|
42016
|
+
|
|
41130
42017
|
// ../remnic-core/src/maintenance/archive-observations.ts
|
|
41131
42018
|
import path55 from "path";
|
|
41132
42019
|
import { mkdir as mkdir40, readdir as readdir19, readFile as readFile33, unlink as unlink8, writeFile as writeFile37 } from "fs/promises";
|
|
@@ -44475,6 +45362,98 @@ function sleep2(ms) {
|
|
|
44475
45362
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44476
45363
|
}
|
|
44477
45364
|
|
|
45365
|
+
// ../remnic-core/src/procedural/procedure-miner.ts
|
|
45366
|
+
function clusterKey(record) {
|
|
45367
|
+
const goal = record.goal.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 120);
|
|
45368
|
+
const refs = [...record.entityRefs ?? []].map((r) => r.trim().toLowerCase()).sort();
|
|
45369
|
+
return `${goal}|${refs.join(",")}`;
|
|
45370
|
+
}
|
|
45371
|
+
function successRate(group) {
|
|
45372
|
+
if (group.length === 0) return 0;
|
|
45373
|
+
const ok = group.filter((g) => g.outcomeKind === "success" || g.outcomeKind === "partial").length;
|
|
45374
|
+
return ok / group.length;
|
|
45375
|
+
}
|
|
45376
|
+
function pseudoStepsFromCluster(group) {
|
|
45377
|
+
const sentences = [];
|
|
45378
|
+
const pushUnique = (raw) => {
|
|
45379
|
+
const t = raw.trim();
|
|
45380
|
+
if (t.length < 8) return;
|
|
45381
|
+
if (!sentences.includes(t)) sentences.push(t);
|
|
45382
|
+
};
|
|
45383
|
+
for (const g of group) {
|
|
45384
|
+
const parts = [g.actionSummary, g.observationSummary, g.outcomeSummary].join(" ").split(/[.!?]\s+|;|\n+/).map((s) => s.trim()).filter((s) => s.length > 12);
|
|
45385
|
+
for (const p of parts) pushUnique(p);
|
|
45386
|
+
if (sentences.length >= 5) break;
|
|
45387
|
+
}
|
|
45388
|
+
if (sentences.length < 2 && group[0]) {
|
|
45389
|
+
pushUnique(`${group[0].goal.trim()} \u2014 confirm prerequisites and context.`);
|
|
45390
|
+
pushUnique("Execute the planned actions, then record the outcome.");
|
|
45391
|
+
}
|
|
45392
|
+
return sentences.slice(0, 6).map((intent, i) => ({
|
|
45393
|
+
order: i + 1,
|
|
45394
|
+
intent
|
|
45395
|
+
}));
|
|
45396
|
+
}
|
|
45397
|
+
async function hasExistingClusterWrite(storage, cluster) {
|
|
45398
|
+
const memories = await storage.readAllMemories();
|
|
45399
|
+
for (const m of memories) {
|
|
45400
|
+
if (m.frontmatter.category !== "procedure") continue;
|
|
45401
|
+
const c = m.frontmatter.structuredAttributes?.procedure_cluster;
|
|
45402
|
+
if (c === cluster) return true;
|
|
45403
|
+
}
|
|
45404
|
+
return false;
|
|
45405
|
+
}
|
|
45406
|
+
async function runProcedureMining(options) {
|
|
45407
|
+
const cfg = options.config.procedural;
|
|
45408
|
+
if (!cfg?.enabled) {
|
|
45409
|
+
return { clustersProcessed: 0, proceduresWritten: 0, skippedReason: "procedural_disabled" };
|
|
45410
|
+
}
|
|
45411
|
+
if (cfg.minOccurrences <= 0) {
|
|
45412
|
+
return { clustersProcessed: 0, proceduresWritten: 0, skippedReason: "minOccurrences_zero" };
|
|
45413
|
+
}
|
|
45414
|
+
const { trajectories } = await readCausalTrajectoryRecords({
|
|
45415
|
+
memoryDir: options.memoryDir
|
|
45416
|
+
});
|
|
45417
|
+
const recent = filterTrajectoriesByLookbackDays(trajectories, cfg.lookbackDays);
|
|
45418
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
45419
|
+
for (const t of recent) {
|
|
45420
|
+
const key = clusterKey(t);
|
|
45421
|
+
const arr = clusters.get(key) ?? [];
|
|
45422
|
+
arr.push(t);
|
|
45423
|
+
clusters.set(key, arr);
|
|
45424
|
+
}
|
|
45425
|
+
let clustersProcessed = 0;
|
|
45426
|
+
let proceduresWritten = 0;
|
|
45427
|
+
for (const [key, group] of clusters) {
|
|
45428
|
+
if (group.length < cfg.minOccurrences) continue;
|
|
45429
|
+
const rate = successRate(group);
|
|
45430
|
+
if (rate < cfg.successFloor) continue;
|
|
45431
|
+
clustersProcessed += 1;
|
|
45432
|
+
if (await hasExistingClusterWrite(options.storage, key)) {
|
|
45433
|
+
log.debug(`procedure-miner: skip duplicate cluster key=${key.slice(0, 40)}\u2026`);
|
|
45434
|
+
continue;
|
|
45435
|
+
}
|
|
45436
|
+
const steps = normalizeProcedureSteps(pseudoStepsFromCluster(group));
|
|
45437
|
+
if (steps.length < 2) continue;
|
|
45438
|
+
const title = `When you work on goals like: ${group[0].goal.trim().slice(0, 140)}`;
|
|
45439
|
+
const body = buildProcedurePersistBody(title, steps);
|
|
45440
|
+
const promote = cfg.autoPromoteEnabled === true && group.length >= cfg.autoPromoteOccurrences && rate >= cfg.successFloor;
|
|
45441
|
+
await options.storage.writeMemory("procedure", body, {
|
|
45442
|
+
source: "procedure-miner",
|
|
45443
|
+
status: promote ? "active" : "pending_review",
|
|
45444
|
+
tags: ["procedure-miner", "causal-trajectory"],
|
|
45445
|
+
structuredAttributes: {
|
|
45446
|
+
procedure_cluster: key.slice(0, 500),
|
|
45447
|
+
trajectory_ids: group.map((g) => g.trajectoryId).join(",").slice(0, 1900),
|
|
45448
|
+
trajectory_count: String(group.length),
|
|
45449
|
+
success_rate: rate.toFixed(4)
|
|
45450
|
+
}
|
|
45451
|
+
});
|
|
45452
|
+
proceduresWritten += 1;
|
|
45453
|
+
}
|
|
45454
|
+
return { clustersProcessed, proceduresWritten };
|
|
45455
|
+
}
|
|
45456
|
+
|
|
44478
45457
|
// ../remnic-core/src/briefing.ts
|
|
44479
45458
|
import { readFile as readFile41 } from "fs/promises";
|
|
44480
45459
|
import path67 from "path";
|
|
@@ -46186,6 +47165,25 @@ var EngramAccessService = class {
|
|
|
46186
47165
|
reportPath: result.reportPath
|
|
46187
47166
|
};
|
|
46188
47167
|
}
|
|
47168
|
+
async procedureMiningRun(request, principal) {
|
|
47169
|
+
const resolvedNamespace = this.resolveWritableNamespace(
|
|
47170
|
+
request.namespace,
|
|
47171
|
+
void 0,
|
|
47172
|
+
request.authenticatedPrincipal ?? principal
|
|
47173
|
+
);
|
|
47174
|
+
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
47175
|
+
const result = await runProcedureMining({
|
|
47176
|
+
memoryDir: storage.dir,
|
|
47177
|
+
storage,
|
|
47178
|
+
config: this.orchestrator.config
|
|
47179
|
+
});
|
|
47180
|
+
return {
|
|
47181
|
+
namespace: resolvedNamespace,
|
|
47182
|
+
clustersProcessed: result.clustersProcessed,
|
|
47183
|
+
proceduresWritten: result.proceduresWritten,
|
|
47184
|
+
skippedReason: result.skippedReason
|
|
47185
|
+
};
|
|
47186
|
+
}
|
|
46189
47187
|
async trustZoneStatus(namespace, principal) {
|
|
46190
47188
|
const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
|
|
46191
47189
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
@@ -47214,6 +48212,17 @@ var EngramMcpServer = class {
|
|
|
47214
48212
|
additionalProperties: false
|
|
47215
48213
|
}
|
|
47216
48214
|
},
|
|
48215
|
+
{
|
|
48216
|
+
name: "engram.procedure_mining_run",
|
|
48217
|
+
description: "Run procedural memory mining from causal trajectories (issue #519). Respects procedural.enabled; writes under procedures/ when clusters qualify.",
|
|
48218
|
+
inputSchema: {
|
|
48219
|
+
type: "object",
|
|
48220
|
+
properties: {
|
|
48221
|
+
namespace: { type: "string" }
|
|
48222
|
+
},
|
|
48223
|
+
additionalProperties: false
|
|
48224
|
+
}
|
|
48225
|
+
},
|
|
47217
48226
|
{
|
|
47218
48227
|
name: "engram.memory_get",
|
|
47219
48228
|
description: "Fetch one Remnic memory by id.",
|
|
@@ -48093,6 +49102,14 @@ ${body}`;
|
|
|
48093
49102
|
batchSize: typeof args.batchSize === "number" && Number.isFinite(args.batchSize) ? args.batchSize : void 0,
|
|
48094
49103
|
authenticatedPrincipal: effectivePrincipal
|
|
48095
49104
|
}, effectivePrincipal);
|
|
49105
|
+
case "engram.procedure_mining_run":
|
|
49106
|
+
return this.service.procedureMiningRun(
|
|
49107
|
+
{
|
|
49108
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
49109
|
+
authenticatedPrincipal: effectivePrincipal
|
|
49110
|
+
},
|
|
49111
|
+
effectivePrincipal
|
|
49112
|
+
);
|
|
48096
49113
|
case "engram.memory_get":
|
|
48097
49114
|
return this.service.memoryGet(
|
|
48098
49115
|
typeof args.memoryId === "string" ? args.memoryId : "",
|
|
@@ -48458,7 +49475,8 @@ var categorySchema = external_exports.enum([
|
|
|
48458
49475
|
"commitment",
|
|
48459
49476
|
"moment",
|
|
48460
49477
|
"skill",
|
|
48461
|
-
"rule"
|
|
49478
|
+
"rule",
|
|
49479
|
+
"procedure"
|
|
48462
49480
|
]).optional();
|
|
48463
49481
|
var confidenceSchema = external_exports.number().min(0).max(1).optional();
|
|
48464
49482
|
var tagsSchema = external_exports.array(external_exports.string().max(256)).max(50).optional();
|
|
@@ -48657,8 +49675,8 @@ var HermesAdapter = class {
|
|
|
48657
49675
|
// ../remnic-core/src/adapters/registry.ts
|
|
48658
49676
|
var AdapterRegistry = class {
|
|
48659
49677
|
adapters;
|
|
48660
|
-
constructor(
|
|
48661
|
-
this.adapters =
|
|
49678
|
+
constructor(adapters2) {
|
|
49679
|
+
this.adapters = adapters2 ?? [
|
|
48662
49680
|
new HermesAdapter(),
|
|
48663
49681
|
new ReplitAdapter(),
|
|
48664
49682
|
new CodexAdapter(),
|
|
@@ -50130,6 +51148,10 @@ async function promoteSemanticRuleFromMemory(options) {
|
|
|
50130
51148
|
return report;
|
|
50131
51149
|
}
|
|
50132
51150
|
|
|
51151
|
+
// ../remnic-core/src/training-export/converter.ts
|
|
51152
|
+
import { lstat as lstat2, readdir as readdir25, readFile as readFile45, realpath as realpath3 } from "fs/promises";
|
|
51153
|
+
import path71 from "path";
|
|
51154
|
+
|
|
50133
51155
|
// ../remnic-core/src/cli.ts
|
|
50134
51156
|
function rankCandidateForKeep(a, b) {
|
|
50135
51157
|
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
@@ -50320,7 +51342,7 @@ async function runRepairMemoryProjectionCliCommand(options) {
|
|
|
50320
51342
|
});
|
|
50321
51343
|
}
|
|
50322
51344
|
async function runMemoryTimelineCliCommand(options) {
|
|
50323
|
-
const storage = new (await import("./storage-
|
|
51345
|
+
const storage = new (await import("./storage-BA6OBLMK.js")).StorageManager(options.memoryDir);
|
|
50324
51346
|
return storage.getMemoryTimeline(options.memoryId, options.limit);
|
|
50325
51347
|
}
|
|
50326
51348
|
async function runMemoryGovernanceCliCommand(options) {
|
|
@@ -50348,7 +51370,7 @@ async function runMemoryGovernanceRestoreCliCommand(options) {
|
|
|
50348
51370
|
});
|
|
50349
51371
|
}
|
|
50350
51372
|
async function runMemoryReviewDispositionCliCommand(options) {
|
|
50351
|
-
const storage = new (await import("./storage-
|
|
51373
|
+
const storage = new (await import("./storage-BA6OBLMK.js")).StorageManager(options.memoryDir);
|
|
50352
51374
|
const memory = await storage.getMemoryById(options.memoryId);
|
|
50353
51375
|
if (!memory) throw new Error(`memory not found: ${options.memoryId}`);
|
|
50354
51376
|
const updated = await storage.writeMemoryFrontmatter(memory, {
|
|
@@ -50514,7 +51536,7 @@ async function runSemanticRulePromoteCliCommand(options) {
|
|
|
50514
51536
|
});
|
|
50515
51537
|
}
|
|
50516
51538
|
async function runCompoundingPromoteCliCommand(options) {
|
|
50517
|
-
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-
|
|
51539
|
+
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-BU6GNUJ5.js");
|
|
50518
51540
|
const config = parseConfig({
|
|
50519
51541
|
memoryDir: options.memoryDir,
|
|
50520
51542
|
qmdEnabled: false,
|
|
@@ -50931,7 +51953,7 @@ function policyVersionForValues(values, config) {
|
|
|
50931
51953
|
return createHash14("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
50932
51954
|
}
|
|
50933
51955
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
50934
|
-
const filePath =
|
|
51956
|
+
const filePath = path72.join(config.memoryDir, "state", fileName);
|
|
50935
51957
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
50936
51958
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
50937
51959
|
});
|
|
@@ -51487,7 +52509,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
|
51487
52509
|
}
|
|
51488
52510
|
async function runReplayCliCommand(orchestrator, options) {
|
|
51489
52511
|
const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
|
|
51490
|
-
const inputRaw = await
|
|
52512
|
+
const inputRaw = await readFile46(options.inputPath, "utf-8");
|
|
51491
52513
|
const registry = buildReplayNormalizerRegistry([
|
|
51492
52514
|
openclawReplayNormalizer,
|
|
51493
52515
|
claudeReplayNormalizer,
|
|
@@ -51549,10 +52571,101 @@ async function runReplayCliCommand(orchestrator, options) {
|
|
|
51549
52571
|
}
|
|
51550
52572
|
return summary;
|
|
51551
52573
|
}
|
|
52574
|
+
async function ensureBuiltInBulkImportAdapters() {
|
|
52575
|
+
if (!getBulkImportSource("weclone")) {
|
|
52576
|
+
const wecloneSpecifier = "@remnic/import-weclone";
|
|
52577
|
+
try {
|
|
52578
|
+
const mod = await import(wecloneSpecifier);
|
|
52579
|
+
if (mod.wecloneImportAdapter) {
|
|
52580
|
+
try {
|
|
52581
|
+
registerBulkImportSource(mod.wecloneImportAdapter);
|
|
52582
|
+
} catch {
|
|
52583
|
+
}
|
|
52584
|
+
}
|
|
52585
|
+
} catch {
|
|
52586
|
+
}
|
|
52587
|
+
}
|
|
52588
|
+
}
|
|
52589
|
+
async function runBulkImportCliCommand(opts) {
|
|
52590
|
+
await ensureBuiltInBulkImportAdapters();
|
|
52591
|
+
const adapter = getBulkImportSource(opts.source);
|
|
52592
|
+
if (!adapter) {
|
|
52593
|
+
const registered = listBulkImportSources();
|
|
52594
|
+
const list = registered.length > 0 ? registered.map((n) => `'${n}'`).join(", ") : "(none registered)";
|
|
52595
|
+
throw new Error(
|
|
52596
|
+
`Unknown bulk-import source '${opts.source}'. Valid sources: ${list}`
|
|
52597
|
+
);
|
|
52598
|
+
}
|
|
52599
|
+
if (opts.dryRun !== true && typeof opts.ingestBatch !== "function") {
|
|
52600
|
+
throw new Error(
|
|
52601
|
+
"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."
|
|
52602
|
+
);
|
|
52603
|
+
}
|
|
52604
|
+
const inputRaw = await readFile46(opts.file, "utf-8");
|
|
52605
|
+
let inputParsed;
|
|
52606
|
+
try {
|
|
52607
|
+
inputParsed = JSON.parse(inputRaw);
|
|
52608
|
+
} catch (err) {
|
|
52609
|
+
throw new Error(
|
|
52610
|
+
`Failed to parse import file as JSON: ${err.message}`
|
|
52611
|
+
);
|
|
52612
|
+
}
|
|
52613
|
+
if (typeof inputParsed !== "object" || inputParsed === null) {
|
|
52614
|
+
throw new Error(
|
|
52615
|
+
"Import file must contain a JSON object or array, got " + (inputParsed === null ? "null" : typeof inputParsed)
|
|
52616
|
+
);
|
|
52617
|
+
}
|
|
52618
|
+
const parsed = await adapter.parse(inputParsed, {
|
|
52619
|
+
strict: opts.strict === true,
|
|
52620
|
+
platform: opts.platform
|
|
52621
|
+
});
|
|
52622
|
+
const processBatch = opts.ingestBatch ?? (async () => {
|
|
52623
|
+
throw new Error(
|
|
52624
|
+
"Bulk import persistence is not wired: no ingestBatch callback was provided by the host CLI."
|
|
52625
|
+
);
|
|
52626
|
+
});
|
|
52627
|
+
const result = await runBulkImportPipeline(
|
|
52628
|
+
parsed,
|
|
52629
|
+
{
|
|
52630
|
+
batchSize: opts.batchSize,
|
|
52631
|
+
dryRun: opts.dryRun,
|
|
52632
|
+
dedup: true,
|
|
52633
|
+
trustLevel: "import"
|
|
52634
|
+
},
|
|
52635
|
+
processBatch
|
|
52636
|
+
);
|
|
52637
|
+
const out = opts.stdout;
|
|
52638
|
+
out.write(`Bulk import complete (source: ${opts.source})
|
|
52639
|
+
`);
|
|
52640
|
+
out.write(` Turns processed: ${result.turnsProcessed}
|
|
52641
|
+
`);
|
|
52642
|
+
out.write(` Batches processed: ${result.batchesProcessed}
|
|
52643
|
+
`);
|
|
52644
|
+
out.write(` Memories created: ${result.memoriesCreated}
|
|
52645
|
+
`);
|
|
52646
|
+
out.write(` Duplicates skipped: ${result.duplicatesSkipped}
|
|
52647
|
+
`);
|
|
52648
|
+
if (result.errors.length > 0) {
|
|
52649
|
+
out.write(` Errors: ${result.errors.length}
|
|
52650
|
+
`);
|
|
52651
|
+
if (opts.verbose) {
|
|
52652
|
+
for (const err of result.errors) {
|
|
52653
|
+
opts.stderr.write(
|
|
52654
|
+
` [batch ${err.batchIndex}] ${err.message}
|
|
52655
|
+
`
|
|
52656
|
+
);
|
|
52657
|
+
}
|
|
52658
|
+
}
|
|
52659
|
+
}
|
|
52660
|
+
if (opts.dryRun) {
|
|
52661
|
+
out.write(" (dry run \u2014 no memories were stored)\n");
|
|
52662
|
+
}
|
|
52663
|
+
return result;
|
|
52664
|
+
}
|
|
51552
52665
|
async function getPluginVersion() {
|
|
51553
52666
|
try {
|
|
51554
52667
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
51555
|
-
const raw = await
|
|
52668
|
+
const raw = await readFile46(pkgPath, "utf-8");
|
|
51556
52669
|
const parsed = JSON.parse(raw);
|
|
51557
52670
|
return parsed.version ?? "unknown";
|
|
51558
52671
|
} catch {
|
|
@@ -51576,59 +52689,71 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace, options) {
|
|
|
51576
52689
|
}
|
|
51577
52690
|
return orchestrator.config.memoryDir;
|
|
51578
52691
|
}
|
|
51579
|
-
const candidate =
|
|
52692
|
+
const candidate = path72.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
51580
52693
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
51581
52694
|
return await exists3(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
51582
52695
|
}
|
|
51583
52696
|
return candidate;
|
|
51584
52697
|
}
|
|
51585
|
-
async function
|
|
51586
|
-
const roots = [
|
|
51587
|
-
const out = [];
|
|
52698
|
+
async function walkMemoryMarkdownFiles(memoryDir, visit) {
|
|
52699
|
+
const roots = [path72.join(memoryDir, "facts"), path72.join(memoryDir, "corrections")];
|
|
51588
52700
|
const walk = async (dir) => {
|
|
51589
52701
|
let entries;
|
|
51590
52702
|
try {
|
|
51591
|
-
entries = await
|
|
52703
|
+
entries = await readdir26(dir, { withFileTypes: true });
|
|
51592
52704
|
} catch {
|
|
51593
52705
|
return;
|
|
51594
52706
|
}
|
|
51595
52707
|
for (const entry of entries) {
|
|
51596
52708
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
51597
|
-
const fullPath =
|
|
52709
|
+
const fullPath = path72.join(dir, entryName);
|
|
51598
52710
|
if (entry.isDirectory()) {
|
|
51599
52711
|
await walk(fullPath);
|
|
51600
52712
|
continue;
|
|
51601
52713
|
}
|
|
51602
52714
|
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
|
-
}
|
|
52715
|
+
await visit(fullPath);
|
|
51627
52716
|
}
|
|
51628
52717
|
};
|
|
51629
52718
|
for (const root of roots) {
|
|
51630
52719
|
await walk(root);
|
|
51631
52720
|
}
|
|
52721
|
+
}
|
|
52722
|
+
async function listMemoryMarkdownFilePaths(memoryDir) {
|
|
52723
|
+
const paths = [];
|
|
52724
|
+
await walkMemoryMarkdownFiles(memoryDir, (fullPath) => {
|
|
52725
|
+
paths.push(fullPath);
|
|
52726
|
+
});
|
|
52727
|
+
return paths;
|
|
52728
|
+
}
|
|
52729
|
+
async function readAllMemoryFiles(memoryDir) {
|
|
52730
|
+
const out = [];
|
|
52731
|
+
await walkMemoryMarkdownFiles(memoryDir, async (fullPath) => {
|
|
52732
|
+
try {
|
|
52733
|
+
const raw = await readFile46(fullPath, "utf-8");
|
|
52734
|
+
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
52735
|
+
if (!parsed) return;
|
|
52736
|
+
const fmRaw = parsed[1];
|
|
52737
|
+
const body = parsed[2] ?? "";
|
|
52738
|
+
const get = (key) => {
|
|
52739
|
+
const match = fmRaw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
52740
|
+
return match ? match[1].trim() : "";
|
|
52741
|
+
};
|
|
52742
|
+
const confidenceRaw = get("confidence");
|
|
52743
|
+
const confidence = confidenceRaw.length > 0 ? Number(confidenceRaw) : void 0;
|
|
52744
|
+
out.push({
|
|
52745
|
+
path: fullPath,
|
|
52746
|
+
content: body,
|
|
52747
|
+
frontmatter: {
|
|
52748
|
+
id: get("id") || void 0,
|
|
52749
|
+
confidence: Number.isFinite(confidence) ? confidence : void 0,
|
|
52750
|
+
updated: get("updated") || void 0,
|
|
52751
|
+
created: get("created") || void 0
|
|
52752
|
+
}
|
|
52753
|
+
});
|
|
52754
|
+
} catch {
|
|
52755
|
+
}
|
|
52756
|
+
});
|
|
51632
52757
|
return out;
|
|
51633
52758
|
}
|
|
51634
52759
|
function formatContinuityIncidentCli(incident) {
|
|
@@ -51937,7 +53062,7 @@ function registerCli(api, orchestrator) {
|
|
|
51937
53062
|
if (plan.moved.length > 0) {
|
|
51938
53063
|
console.log("\nEntries:");
|
|
51939
53064
|
for (const move of plan.moved) {
|
|
51940
|
-
console.log(`- ${
|
|
53065
|
+
console.log(`- ${path72.basename(move.from)}`);
|
|
51941
53066
|
}
|
|
51942
53067
|
}
|
|
51943
53068
|
if (dryRun) {
|
|
@@ -52135,6 +53260,64 @@ function registerCli(api, orchestrator) {
|
|
|
52135
53260
|
}
|
|
52136
53261
|
console.log("OK");
|
|
52137
53262
|
});
|
|
53263
|
+
cmd.command("bulk-import").description(
|
|
53264
|
+
"Bulk-import chat history via a registered source adapter (e.g. --source weclone)."
|
|
53265
|
+
).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) => {
|
|
53266
|
+
const options = args[0] ?? {};
|
|
53267
|
+
const sourceRaw = typeof options.source === "string" ? options.source.trim() : "";
|
|
53268
|
+
const filePathRaw = typeof options.file === "string" ? options.file.trim() : "";
|
|
53269
|
+
if (sourceRaw.length === 0) {
|
|
53270
|
+
console.log("Missing --source. Example: openclaw engram bulk-import --source weclone --file /tmp/export.json");
|
|
53271
|
+
return;
|
|
53272
|
+
}
|
|
53273
|
+
if (filePathRaw.length === 0) {
|
|
53274
|
+
console.log("Missing --file. Example: openclaw engram bulk-import --source weclone --file /tmp/export.json");
|
|
53275
|
+
return;
|
|
53276
|
+
}
|
|
53277
|
+
const batchSizeRaw = parseInt(String(options.batchSize ?? "50"), 10);
|
|
53278
|
+
const batchSize = Number.isFinite(batchSizeRaw) && batchSizeRaw > 0 ? batchSizeRaw : 50;
|
|
53279
|
+
const platformRaw = typeof options.platform === "string" ? options.platform.trim() : "";
|
|
53280
|
+
const writeNamespace = orchestrator.bulkImportWriteNamespace();
|
|
53281
|
+
const writeStorage = await orchestrator.getStorageForNamespace(
|
|
53282
|
+
writeNamespace
|
|
53283
|
+
);
|
|
53284
|
+
const writeRoot = writeStorage.dir;
|
|
53285
|
+
const ingestBatch = async (turns) => {
|
|
53286
|
+
const before = new Set(await listMemoryMarkdownFilePaths(writeRoot));
|
|
53287
|
+
await orchestrator.ingestBulkImportBatch(turns, {});
|
|
53288
|
+
const after = await listMemoryMarkdownFilePaths(writeRoot);
|
|
53289
|
+
let memoriesCreated = 0;
|
|
53290
|
+
for (const p of after) {
|
|
53291
|
+
if (!before.has(p)) memoriesCreated += 1;
|
|
53292
|
+
}
|
|
53293
|
+
return { memoriesCreated, duplicatesSkipped: 0 };
|
|
53294
|
+
};
|
|
53295
|
+
try {
|
|
53296
|
+
const result = await runBulkImportCliCommand({
|
|
53297
|
+
memoryDir: writeRoot,
|
|
53298
|
+
source: sourceRaw,
|
|
53299
|
+
file: filePathRaw,
|
|
53300
|
+
platform: platformRaw.length > 0 ? platformRaw : void 0,
|
|
53301
|
+
batchSize,
|
|
53302
|
+
dryRun: options.dryRun === true,
|
|
53303
|
+
verbose: options.verbose === true,
|
|
53304
|
+
strict: options.strict === true,
|
|
53305
|
+
ingestBatch,
|
|
53306
|
+
stdout: process.stdout,
|
|
53307
|
+
stderr: process.stderr
|
|
53308
|
+
});
|
|
53309
|
+
if (result.errors.length > 0) {
|
|
53310
|
+
console.error(`Bulk import completed with ${result.errors.length} batch error(s).`);
|
|
53311
|
+
process.exitCode = 1;
|
|
53312
|
+
} else {
|
|
53313
|
+
console.log("OK");
|
|
53314
|
+
}
|
|
53315
|
+
} catch (err) {
|
|
53316
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53317
|
+
console.error(`Bulk import failed: ${message}`);
|
|
53318
|
+
process.exitCode = 1;
|
|
53319
|
+
}
|
|
53320
|
+
});
|
|
52138
53321
|
cmd.command("benchmark-status").description("Show benchmark/evaluation harness status, benchmark packs, and latest run summary").action(async () => {
|
|
52139
53322
|
const status = await runBenchmarkStatusCliCommand({
|
|
52140
53323
|
memoryDir: orchestrator.config.memoryDir,
|
|
@@ -53587,7 +54770,7 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53587
54770
|
}
|
|
53588
54771
|
});
|
|
53589
54772
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
53590
|
-
const workspaceDir =
|
|
54773
|
+
const workspaceDir = path72.join(resolveHomeDir(), ".openclaw", "workspace");
|
|
53591
54774
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
53592
54775
|
if (!identity) {
|
|
53593
54776
|
console.log("No identity file found.");
|
|
@@ -53810,8 +54993,8 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53810
54993
|
const options = args[0] ?? {};
|
|
53811
54994
|
const threadId = options.thread;
|
|
53812
54995
|
const top = parseInt(options.top ?? "10", 10);
|
|
53813
|
-
const memoryDir =
|
|
53814
|
-
const threading = new ThreadingManager(
|
|
54996
|
+
const memoryDir = path72.join(resolveHomeDir(), ".openclaw", "workspace", "memory", "local");
|
|
54997
|
+
const threading = new ThreadingManager(path72.join(memoryDir, "threads"));
|
|
53815
54998
|
if (threadId) {
|
|
53816
54999
|
const thread = await threading.loadThread(threadId);
|
|
53817
55000
|
if (!thread) {
|
|
@@ -54287,19 +55470,19 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
54287
55470
|
}
|
|
54288
55471
|
|
|
54289
55472
|
// ../../src/index.ts
|
|
54290
|
-
import { readFile as
|
|
55473
|
+
import { readFile as readFile52, realpath as realpath5, writeFile as writeFile46 } from "fs/promises";
|
|
54291
55474
|
import { readFileSync as readFileSync6 } from "fs";
|
|
54292
|
-
import
|
|
55475
|
+
import path98 from "path";
|
|
54293
55476
|
import os6 from "os";
|
|
54294
55477
|
|
|
54295
55478
|
// ../remnic-core/src/opik-exporter.ts
|
|
54296
|
-
import { createHash as createHash15, randomBytes } from "crypto";
|
|
55479
|
+
import { createHash as createHash15, randomBytes as randomBytes2 } from "crypto";
|
|
54297
55480
|
import { readFileSync as readFileSync4 } from "fs";
|
|
54298
|
-
import
|
|
55481
|
+
import path73 from "path";
|
|
54299
55482
|
var OPIK_EXPORTER_SLOT = "__openclawOpikExporter";
|
|
54300
55483
|
function readOpikOpenclawConfig(log2) {
|
|
54301
55484
|
try {
|
|
54302
|
-
const configPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH") ||
|
|
55485
|
+
const configPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH") || path73.join(resolveHomeDir(), ".openclaw", "openclaw.json");
|
|
54303
55486
|
const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
54304
55487
|
const entry = raw?.plugins?.entries?.["opik-openclaw"];
|
|
54305
55488
|
if (!entry?.enabled || !entry?.config) return {};
|
|
@@ -54317,7 +55500,7 @@ function readOpikOpenclawConfig(log2) {
|
|
|
54317
55500
|
}
|
|
54318
55501
|
function uuidV7() {
|
|
54319
55502
|
const now = Date.now();
|
|
54320
|
-
const bytes =
|
|
55503
|
+
const bytes = randomBytes2(16);
|
|
54321
55504
|
bytes[0] = now / 2 ** 40 & 255;
|
|
54322
55505
|
bytes[1] = now / 2 ** 32 & 255;
|
|
54323
55506
|
bytes[2] = now / 2 ** 24 & 255;
|
|
@@ -54661,8 +55844,8 @@ function cleanUserMessage(content) {
|
|
|
54661
55844
|
}
|
|
54662
55845
|
|
|
54663
55846
|
// src/public-artifacts.ts
|
|
54664
|
-
import { readdir as
|
|
54665
|
-
import
|
|
55847
|
+
import { readdir as readdir27, access as access6, stat as stat20, lstat as lstat3, realpath as realpath4 } from "fs/promises";
|
|
55848
|
+
import path74 from "path";
|
|
54666
55849
|
var PUBLIC_DIRS = [
|
|
54667
55850
|
{ dir: "facts", kind: "fact", contentType: "markdown" },
|
|
54668
55851
|
{ dir: "entities", kind: "entity", contentType: "markdown" },
|
|
@@ -54674,9 +55857,9 @@ var PUBLIC_FILES = [
|
|
|
54674
55857
|
];
|
|
54675
55858
|
async function isContainedWithin(target, boundary) {
|
|
54676
55859
|
try {
|
|
54677
|
-
const resolvedTarget = await
|
|
54678
|
-
const resolvedBoundary = await
|
|
54679
|
-
return resolvedTarget === resolvedBoundary || resolvedTarget.startsWith(resolvedBoundary +
|
|
55860
|
+
const resolvedTarget = await realpath4(target);
|
|
55861
|
+
const resolvedBoundary = await realpath4(boundary);
|
|
55862
|
+
return resolvedTarget === resolvedBoundary || resolvedTarget.startsWith(resolvedBoundary + path74.sep);
|
|
54680
55863
|
} catch {
|
|
54681
55864
|
return false;
|
|
54682
55865
|
}
|
|
@@ -54685,7 +55868,7 @@ async function listMarkdownFilesRecursive(rootDir, boundary, ancestorRealPaths)
|
|
|
54685
55868
|
const boundaryDir = boundary ?? rootDir;
|
|
54686
55869
|
let resolvedRoot;
|
|
54687
55870
|
try {
|
|
54688
|
-
resolvedRoot = await
|
|
55871
|
+
resolvedRoot = await realpath4(rootDir);
|
|
54689
55872
|
} catch {
|
|
54690
55873
|
return [];
|
|
54691
55874
|
}
|
|
@@ -54694,18 +55877,18 @@ async function listMarkdownFilesRecursive(rootDir, boundary, ancestorRealPaths)
|
|
|
54694
55877
|
nextAncestors.add(resolvedRoot);
|
|
54695
55878
|
let entries;
|
|
54696
55879
|
try {
|
|
54697
|
-
entries = await
|
|
55880
|
+
entries = await readdir27(rootDir, { withFileTypes: true });
|
|
54698
55881
|
} catch {
|
|
54699
55882
|
return [];
|
|
54700
55883
|
}
|
|
54701
55884
|
entries.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
54702
55885
|
const files = [];
|
|
54703
55886
|
for (const entry of entries) {
|
|
54704
|
-
const fullPath =
|
|
55887
|
+
const fullPath = path74.join(rootDir, String(entry.name));
|
|
54705
55888
|
let isDir = entry.isDirectory();
|
|
54706
55889
|
let isFile = entry.isFile();
|
|
54707
55890
|
try {
|
|
54708
|
-
const linkStat = await
|
|
55891
|
+
const linkStat = await lstat3(fullPath);
|
|
54709
55892
|
if (linkStat.isSymbolicLink()) {
|
|
54710
55893
|
if (!await isContainedWithin(fullPath, boundaryDir)) {
|
|
54711
55894
|
continue;
|
|
@@ -54739,21 +55922,21 @@ async function listRemnicPublicArtifacts(params) {
|
|
|
54739
55922
|
const { memoryDir, workspaceDir, agentIds } = params;
|
|
54740
55923
|
const artifacts = [];
|
|
54741
55924
|
for (const spec of PUBLIC_DIRS) {
|
|
54742
|
-
const dirPath =
|
|
55925
|
+
const dirPath = path74.join(memoryDir, spec.dir);
|
|
54743
55926
|
if (!await pathExists2(dirPath)) continue;
|
|
54744
55927
|
if (!await isContainedWithin(dirPath, memoryDir)) continue;
|
|
54745
55928
|
try {
|
|
54746
|
-
const resolvedDir = await
|
|
54747
|
-
const expectedParent = await
|
|
54748
|
-
const resolvedName =
|
|
55929
|
+
const resolvedDir = await realpath4(dirPath);
|
|
55930
|
+
const expectedParent = await realpath4(memoryDir);
|
|
55931
|
+
const resolvedName = path74.basename(resolvedDir);
|
|
54749
55932
|
if (resolvedName !== spec.dir) continue;
|
|
54750
|
-
if (
|
|
55933
|
+
if (path74.dirname(resolvedDir) !== expectedParent) continue;
|
|
54751
55934
|
} catch {
|
|
54752
55935
|
continue;
|
|
54753
55936
|
}
|
|
54754
55937
|
const files = await listMarkdownFilesRecursive(dirPath, dirPath);
|
|
54755
55938
|
for (const absolutePath of files) {
|
|
54756
|
-
const relativePath =
|
|
55939
|
+
const relativePath = path74.relative(memoryDir, absolutePath).replace(/\\/g, "/");
|
|
54757
55940
|
artifacts.push({
|
|
54758
55941
|
kind: spec.kind,
|
|
54759
55942
|
workspaceDir,
|
|
@@ -54765,16 +55948,16 @@ async function listRemnicPublicArtifacts(params) {
|
|
|
54765
55948
|
}
|
|
54766
55949
|
}
|
|
54767
55950
|
for (const spec of PUBLIC_FILES) {
|
|
54768
|
-
const absolutePath =
|
|
55951
|
+
const absolutePath = path74.join(memoryDir, spec.relativePath);
|
|
54769
55952
|
if (!await pathExists2(absolutePath)) continue;
|
|
54770
55953
|
if (!await isContainedWithin(absolutePath, memoryDir)) continue;
|
|
54771
55954
|
try {
|
|
54772
|
-
const linkStat = await
|
|
55955
|
+
const linkStat = await lstat3(absolutePath);
|
|
54773
55956
|
if (linkStat.isSymbolicLink()) {
|
|
54774
|
-
const resolvedPath = await
|
|
54775
|
-
const expectedParent = await
|
|
54776
|
-
if (
|
|
54777
|
-
if (
|
|
55957
|
+
const resolvedPath = await realpath4(absolutePath);
|
|
55958
|
+
const expectedParent = await realpath4(memoryDir);
|
|
55959
|
+
if (path74.dirname(resolvedPath) !== expectedParent) continue;
|
|
55960
|
+
if (path74.basename(resolvedPath) !== path74.basename(spec.relativePath)) continue;
|
|
54778
55961
|
}
|
|
54779
55962
|
} catch {
|
|
54780
55963
|
continue;
|
|
@@ -54925,28 +56108,28 @@ var DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024;
|
|
|
54925
56108
|
// ../remnic-core/src/binary-lifecycle/backend.ts
|
|
54926
56109
|
import fs3 from "fs";
|
|
54927
56110
|
import fsp2 from "fs/promises";
|
|
54928
|
-
import
|
|
56111
|
+
import path75 from "path";
|
|
54929
56112
|
|
|
54930
56113
|
// ../remnic-core/src/binary-lifecycle/scanner.ts
|
|
54931
56114
|
import fsp3 from "fs/promises";
|
|
54932
|
-
import
|
|
56115
|
+
import path76 from "path";
|
|
54933
56116
|
|
|
54934
56117
|
// ../remnic-core/src/binary-lifecycle/manifest.ts
|
|
54935
56118
|
import fsp4 from "fs/promises";
|
|
54936
|
-
import
|
|
56119
|
+
import path77 from "path";
|
|
54937
56120
|
import crypto3 from "crypto";
|
|
54938
56121
|
|
|
54939
56122
|
// ../remnic-core/src/binary-lifecycle/pipeline.ts
|
|
54940
56123
|
import fsp5 from "fs/promises";
|
|
54941
|
-
import
|
|
56124
|
+
import path78 from "path";
|
|
54942
56125
|
import crypto4 from "crypto";
|
|
54943
56126
|
|
|
54944
56127
|
// ../remnic-core/src/projection/index.ts
|
|
54945
56128
|
import fs4 from "fs";
|
|
54946
|
-
import
|
|
56129
|
+
import path80 from "path";
|
|
54947
56130
|
|
|
54948
56131
|
// ../remnic-core/src/utils/category-dir.ts
|
|
54949
|
-
import
|
|
56132
|
+
import path79 from "path";
|
|
54950
56133
|
var CATEGORY_DIR_MAP = {
|
|
54951
56134
|
correction: "corrections",
|
|
54952
56135
|
question: "questions",
|
|
@@ -54957,7 +56140,8 @@ var CATEGORY_DIR_MAP = {
|
|
|
54957
56140
|
principle: "principles",
|
|
54958
56141
|
rule: "rules",
|
|
54959
56142
|
skill: "skills",
|
|
54960
|
-
relationship: "relationships"
|
|
56143
|
+
relationship: "relationships",
|
|
56144
|
+
procedure: "procedures"
|
|
54961
56145
|
};
|
|
54962
56146
|
var ALL_CATEGORY_DIRS = [
|
|
54963
56147
|
"facts",
|
|
@@ -54970,30 +56154,30 @@ var ALL_CATEGORY_KEYS = [
|
|
|
54970
56154
|
|
|
54971
56155
|
// ../remnic-core/src/onboarding/index.ts
|
|
54972
56156
|
import fs5 from "fs";
|
|
54973
|
-
import
|
|
56157
|
+
import path81 from "path";
|
|
54974
56158
|
|
|
54975
56159
|
// ../remnic-core/src/curation/index.ts
|
|
54976
56160
|
import fs6 from "fs";
|
|
54977
|
-
import
|
|
56161
|
+
import path82 from "path";
|
|
54978
56162
|
import crypto5 from "crypto";
|
|
54979
56163
|
|
|
54980
56164
|
// ../remnic-core/src/dedup/index.ts
|
|
54981
56165
|
import fs7 from "fs";
|
|
54982
|
-
import
|
|
56166
|
+
import path83 from "path";
|
|
54983
56167
|
import crypto6 from "crypto";
|
|
54984
56168
|
|
|
54985
56169
|
// ../remnic-core/src/review/index.ts
|
|
54986
56170
|
import fs8 from "fs";
|
|
54987
|
-
import
|
|
56171
|
+
import path84 from "path";
|
|
54988
56172
|
|
|
54989
56173
|
// ../remnic-core/src/sync/index.ts
|
|
54990
56174
|
import fs9 from "fs";
|
|
54991
|
-
import
|
|
56175
|
+
import path85 from "path";
|
|
54992
56176
|
import crypto7 from "crypto";
|
|
54993
56177
|
|
|
54994
56178
|
// ../remnic-core/src/connectors/index.ts
|
|
54995
56179
|
import fs11 from "fs";
|
|
54996
|
-
import
|
|
56180
|
+
import path88 from "path";
|
|
54997
56181
|
import os4 from "os";
|
|
54998
56182
|
import { spawnSync } from "child_process";
|
|
54999
56183
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -55001,31 +56185,31 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
55001
56185
|
|
|
55002
56186
|
// ../remnic-core/src/tokens.ts
|
|
55003
56187
|
import fs10 from "fs";
|
|
55004
|
-
import
|
|
55005
|
-
import { randomBytes as
|
|
56188
|
+
import path86 from "path";
|
|
56189
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
55006
56190
|
|
|
55007
56191
|
// ../remnic-core/src/connectors/codex-marketplace.ts
|
|
55008
56192
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
55009
|
-
import
|
|
56193
|
+
import path87 from "path";
|
|
55010
56194
|
|
|
55011
56195
|
// ../remnic-core/src/spaces/index.ts
|
|
55012
56196
|
import fs12 from "fs";
|
|
55013
|
-
import
|
|
56197
|
+
import path89 from "path";
|
|
55014
56198
|
import crypto8 from "crypto";
|
|
55015
56199
|
|
|
55016
56200
|
// ../remnic-core/src/memory-extension/codex-publisher.ts
|
|
55017
56201
|
import fs13 from "fs";
|
|
55018
56202
|
import os5 from "os";
|
|
55019
|
-
import
|
|
56203
|
+
import path90 from "path";
|
|
55020
56204
|
|
|
55021
56205
|
// ../remnic-core/src/taxonomy/taxonomy-loader.ts
|
|
55022
|
-
import { readFile as
|
|
55023
|
-
import
|
|
56206
|
+
import { readFile as readFile47, mkdir as mkdir49, writeFile as writeFile43 } from "fs/promises";
|
|
56207
|
+
import path91 from "path";
|
|
55024
56208
|
|
|
55025
56209
|
// ../remnic-core/src/enrichment/audit.ts
|
|
55026
|
-
import { mkdir as mkdir50, readFile as
|
|
56210
|
+
import { mkdir as mkdir50, readFile as readFile48, appendFile as appendFile4 } from "fs/promises";
|
|
55027
56211
|
import { existsSync as existsSync11 } from "fs";
|
|
55028
|
-
import
|
|
56212
|
+
import path92 from "path";
|
|
55029
56213
|
|
|
55030
56214
|
// src/openclaw-tools/shapes.ts
|
|
55031
56215
|
var MemorySearchInputSchema = Type.Object({
|
|
@@ -55439,7 +56623,7 @@ async function syncHeartbeatOutcomeLinks(params) {
|
|
|
55439
56623
|
}
|
|
55440
56624
|
return { created: 0, updated: 0, linked };
|
|
55441
56625
|
}
|
|
55442
|
-
function
|
|
56626
|
+
function parseIsoTimestamp3(value) {
|
|
55443
56627
|
if (!value) return null;
|
|
55444
56628
|
const parsed = Date.parse(value);
|
|
55445
56629
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -55448,7 +56632,7 @@ function planDreamEntryFromConsolidation(params) {
|
|
|
55448
56632
|
const now = params.now ?? /* @__PURE__ */ new Date();
|
|
55449
56633
|
const latestDreamAt = Math.max(
|
|
55450
56634
|
-1,
|
|
55451
|
-
...params.existingDreams.map((entry) =>
|
|
56635
|
+
...params.existingDreams.map((entry) => parseIsoTimestamp3(entry.timestamp)).filter((value) => value !== null)
|
|
55452
56636
|
);
|
|
55453
56637
|
if (latestDreamAt > 0 && now.getTime() - latestDreamAt < params.minIntervalMinutes * 6e4) {
|
|
55454
56638
|
return null;
|
|
@@ -55592,88 +56776,100 @@ function resolveSession(commandCtx) {
|
|
|
55592
56776
|
};
|
|
55593
56777
|
}
|
|
55594
56778
|
function buildSessionCommandDescriptors(pluginId, runtime) {
|
|
56779
|
+
const subcommands = [
|
|
56780
|
+
{
|
|
56781
|
+
name: "off",
|
|
56782
|
+
description: "Disable Remnic recall for this session",
|
|
56783
|
+
args: [],
|
|
56784
|
+
handler: async (commandCtx = {}) => {
|
|
56785
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
56786
|
+
await runtime.toggles.setDisabled(sessionKey, agentId, true);
|
|
56787
|
+
return `Remnic recall disabled for session ${sessionKey}.`;
|
|
56788
|
+
}
|
|
56789
|
+
},
|
|
56790
|
+
{
|
|
56791
|
+
name: "on",
|
|
56792
|
+
description: "Re-enable Remnic recall for this session",
|
|
56793
|
+
args: [],
|
|
56794
|
+
handler: async (commandCtx = {}) => {
|
|
56795
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
56796
|
+
await runtime.toggles.setDisabled(sessionKey, agentId, false);
|
|
56797
|
+
return `Remnic recall re-enabled for session ${sessionKey}.`;
|
|
56798
|
+
}
|
|
56799
|
+
},
|
|
56800
|
+
{
|
|
56801
|
+
name: "status",
|
|
56802
|
+
description: "Show Remnic recall status and last injected summary",
|
|
56803
|
+
args: [],
|
|
56804
|
+
handler: async (commandCtx = {}) => {
|
|
56805
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
56806
|
+
const resolved = await runtime.toggles.resolve(sessionKey, agentId);
|
|
56807
|
+
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
56808
|
+
const summaryText = runtime.getLastRecallSummary(sessionKey);
|
|
56809
|
+
const summary = summaryText && summaryText.length > 0 ? summaryText : lastRecall && lastRecall.memoryIds.length > 0 ? `${lastRecall.memoryIds.length} memory item(s), latency ${lastRecall.latencyMs ?? "?"}ms` : "NONE";
|
|
56810
|
+
return [
|
|
56811
|
+
`Remnic recall is ${resolved.disabled ? "disabled" : "enabled"} for session ${sessionKey}.`,
|
|
56812
|
+
`Source: ${describeToggleSource(resolved.source)}.`,
|
|
56813
|
+
`Last recall: ${summary}.`
|
|
56814
|
+
].join(" ");
|
|
56815
|
+
}
|
|
56816
|
+
},
|
|
56817
|
+
{
|
|
56818
|
+
name: "clear",
|
|
56819
|
+
description: "Clear the session override and use global config again",
|
|
56820
|
+
args: [],
|
|
56821
|
+
handler: async (commandCtx = {}) => {
|
|
56822
|
+
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
56823
|
+
await runtime.toggles.clear(sessionKey, agentId);
|
|
56824
|
+
return `Cleared the Remnic session override for ${sessionKey}.`;
|
|
56825
|
+
}
|
|
56826
|
+
},
|
|
56827
|
+
{
|
|
56828
|
+
name: "stats",
|
|
56829
|
+
description: "Show Remnic extraction and recall stats for this session",
|
|
56830
|
+
args: [],
|
|
56831
|
+
handler: async (commandCtx = {}) => {
|
|
56832
|
+
const { sessionKey } = resolveSession(commandCtx);
|
|
56833
|
+
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
56834
|
+
if (!lastRecall) {
|
|
56835
|
+
return `No Remnic recall stats are available for session ${sessionKey} yet.`;
|
|
56836
|
+
}
|
|
56837
|
+
return [
|
|
56838
|
+
`Session ${sessionKey}.`,
|
|
56839
|
+
`Planner mode: ${lastRecall.plannerMode ?? "unknown"}.`,
|
|
56840
|
+
`Latency: ${lastRecall.latencyMs ?? "?"}ms.`,
|
|
56841
|
+
`Memories: ${lastRecall.memoryIds.length}.`
|
|
56842
|
+
].join(" ");
|
|
56843
|
+
}
|
|
56844
|
+
},
|
|
56845
|
+
{
|
|
56846
|
+
name: "flush",
|
|
56847
|
+
description: "Force-flush the extraction buffer now",
|
|
56848
|
+
args: [],
|
|
56849
|
+
handler: async (commandCtx = {}) => {
|
|
56850
|
+
const { sessionKey } = resolveSession(commandCtx);
|
|
56851
|
+
await runtime.flushSession(sessionKey);
|
|
56852
|
+
return `Flushed the Remnic buffer for session ${sessionKey}.`;
|
|
56853
|
+
}
|
|
56854
|
+
}
|
|
56855
|
+
];
|
|
56856
|
+
const subcommandNames = subcommands.map((entry) => entry.name).join(", ");
|
|
55595
56857
|
return [
|
|
55596
56858
|
{
|
|
55597
56859
|
name: "remnic",
|
|
56860
|
+
description: `Remnic memory controls (${subcommandNames})`,
|
|
55598
56861
|
category: "memory",
|
|
55599
56862
|
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
|
-
}
|
|
56863
|
+
acceptsArgs: true,
|
|
56864
|
+
subcommands,
|
|
56865
|
+
handler: async (commandCtx = {}) => {
|
|
56866
|
+
const requested = commandCtx.args?.[0]?.trim().toLowerCase() ?? "status";
|
|
56867
|
+
const match = subcommands.find((entry) => entry.name === requested);
|
|
56868
|
+
if (!match) {
|
|
56869
|
+
return `Unknown Remnic subcommand "${requested}". Try one of: ${subcommandNames}.`;
|
|
55675
56870
|
}
|
|
55676
|
-
|
|
56871
|
+
return match.handler(commandCtx);
|
|
56872
|
+
}
|
|
55677
56873
|
}
|
|
55678
56874
|
];
|
|
55679
56875
|
}
|
|
@@ -55773,8 +56969,8 @@ function validateSlotSelection(ctx) {
|
|
|
55773
56969
|
}
|
|
55774
56970
|
|
|
55775
56971
|
// ../remnic-core/src/session-toggles.ts
|
|
55776
|
-
import { mkdir as mkdir51, readFile as
|
|
55777
|
-
import
|
|
56972
|
+
import { mkdir as mkdir51, readFile as readFile49, writeFile as writeFile44 } from "fs/promises";
|
|
56973
|
+
import path93 from "path";
|
|
55778
56974
|
function encodeToggleKey(sessionKey, agentId) {
|
|
55779
56975
|
return `${encodeURIComponent(sessionKey)}::${encodeURIComponent(agentId)}`;
|
|
55780
56976
|
}
|
|
@@ -55788,7 +56984,7 @@ function decodeToggleKey(key) {
|
|
|
55788
56984
|
}
|
|
55789
56985
|
async function safeReadToggleFile(filePath) {
|
|
55790
56986
|
try {
|
|
55791
|
-
const raw = await
|
|
56987
|
+
const raw = await readFile49(filePath, "utf8");
|
|
55792
56988
|
const parsed = JSON.parse(raw);
|
|
55793
56989
|
if (!parsed || typeof parsed !== "object" || typeof parsed.entries !== "object") {
|
|
55794
56990
|
return { version: 1, entries: {} };
|
|
@@ -55813,7 +57009,7 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
55813
57009
|
await run;
|
|
55814
57010
|
}
|
|
55815
57011
|
async function writeToggleFile(next) {
|
|
55816
|
-
await mkdir51(
|
|
57012
|
+
await mkdir51(path93.dirname(filePath), { recursive: true });
|
|
55817
57013
|
await writeFile44(filePath, JSON.stringify(next, null, 2), "utf8");
|
|
55818
57014
|
}
|
|
55819
57015
|
async function readPrimary() {
|
|
@@ -55886,8 +57082,8 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
55886
57082
|
}
|
|
55887
57083
|
|
|
55888
57084
|
// ../remnic-core/src/recall-audit.ts
|
|
55889
|
-
import { appendFile as appendFile5, mkdir as mkdir52, readdir as
|
|
55890
|
-
import
|
|
57085
|
+
import { appendFile as appendFile5, mkdir as mkdir52, readdir as readdir28, rm as rm10 } from "fs/promises";
|
|
57086
|
+
import path94 from "path";
|
|
55891
57087
|
function formatIsoDate(ts) {
|
|
55892
57088
|
const normalized = new Date(ts);
|
|
55893
57089
|
if (Number.isNaN(normalized.getTime())) {
|
|
@@ -55897,24 +57093,24 @@ function formatIsoDate(ts) {
|
|
|
55897
57093
|
}
|
|
55898
57094
|
function buildRecallAuditPath(rootDir, ts, sessionKey) {
|
|
55899
57095
|
const safeSessionKey = encodeURIComponent(sessionKey);
|
|
55900
|
-
return
|
|
57096
|
+
return path94.join(rootDir, "transcripts", formatIsoDate(ts), `${safeSessionKey}.jsonl`);
|
|
55901
57097
|
}
|
|
55902
57098
|
async function appendRecallAuditEntry(rootDir, entry) {
|
|
55903
57099
|
const filePath = buildRecallAuditPath(rootDir, entry.ts, entry.sessionKey);
|
|
55904
|
-
await mkdir52(
|
|
57100
|
+
await mkdir52(path94.dirname(filePath), { recursive: true });
|
|
55905
57101
|
await appendFile5(filePath, `${JSON.stringify(entry)}
|
|
55906
57102
|
`, "utf8");
|
|
55907
57103
|
return filePath;
|
|
55908
57104
|
}
|
|
55909
57105
|
async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE__ */ new Date()) {
|
|
55910
|
-
const transcriptsDir =
|
|
57106
|
+
const transcriptsDir = path94.join(rootDir, "transcripts");
|
|
55911
57107
|
const removed = [];
|
|
55912
57108
|
const cutoff = new Date(now);
|
|
55913
57109
|
cutoff.setUTCHours(0, 0, 0, 0);
|
|
55914
57110
|
cutoff.setUTCDate(cutoff.getUTCDate() - Math.max(1, Math.floor(retentionDays)));
|
|
55915
57111
|
let entries;
|
|
55916
57112
|
try {
|
|
55917
|
-
entries = await
|
|
57113
|
+
entries = await readdir28(transcriptsDir, { withFileTypes: true });
|
|
55918
57114
|
} catch {
|
|
55919
57115
|
return removed;
|
|
55920
57116
|
}
|
|
@@ -55923,7 +57119,7 @@ async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE_
|
|
|
55923
57119
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.name)) continue;
|
|
55924
57120
|
const day = /* @__PURE__ */ new Date(`${entry.name}T00:00:00.000Z`);
|
|
55925
57121
|
if (Number.isNaN(day.getTime()) || day >= cutoff) continue;
|
|
55926
|
-
const dirPath =
|
|
57122
|
+
const dirPath = path94.join(transcriptsDir, entry.name);
|
|
55927
57123
|
await rm10(dirPath, { recursive: true, force: true });
|
|
55928
57124
|
removed.push(dirPath);
|
|
55929
57125
|
}
|
|
@@ -55932,7 +57128,7 @@ async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE_
|
|
|
55932
57128
|
|
|
55933
57129
|
// ../remnic-core/src/active-recall.ts
|
|
55934
57130
|
import { appendFile as appendFile6, mkdir as mkdir53 } from "fs/promises";
|
|
55935
|
-
import
|
|
57131
|
+
import path95 from "path";
|
|
55936
57132
|
var ACTIVE_RECALL_CACHE_MAX_ENTRIES = 256;
|
|
55937
57133
|
var NONE_SET = /* @__PURE__ */ new Set([
|
|
55938
57134
|
"",
|
|
@@ -56075,14 +57271,14 @@ ${params.recallExplain}` : null,
|
|
|
56075
57271
|
}
|
|
56076
57272
|
async function appendActiveRecallTranscript(transcriptRoot, input, config, result, queryBundle) {
|
|
56077
57273
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
56078
|
-
const filePath =
|
|
57274
|
+
const filePath = path95.join(
|
|
56079
57275
|
transcriptRoot,
|
|
56080
57276
|
"agents",
|
|
56081
57277
|
sanitizeTranscriptPathSegment(input.agentId),
|
|
56082
57278
|
date,
|
|
56083
57279
|
`${sanitizeTranscriptPathSegment(input.sessionKey)}.jsonl`
|
|
56084
57280
|
);
|
|
56085
|
-
await mkdir53(
|
|
57281
|
+
await mkdir53(path95.dirname(filePath), { recursive: true });
|
|
56086
57282
|
await appendFile6(
|
|
56087
57283
|
filePath,
|
|
56088
57284
|
`${JSON.stringify({
|
|
@@ -56314,8 +57510,8 @@ import { resolvePrincipal as resolvePrincipal2 } from "@remnic/core";
|
|
|
56314
57510
|
// ../remnic-core/src/surfaces/dreams.ts
|
|
56315
57511
|
import { createHash as createHash16 } from "crypto";
|
|
56316
57512
|
import { statSync, watch as watch2 } from "fs";
|
|
56317
|
-
import { mkdir as mkdir54, readFile as
|
|
56318
|
-
import
|
|
57513
|
+
import { mkdir as mkdir54, readFile as readFile50, writeFile as writeFile45 } from "fs/promises";
|
|
57514
|
+
import path96 from "path";
|
|
56319
57515
|
var DIARY_START_MARKER = "<!-- openclaw:dreaming:diary:start -->";
|
|
56320
57516
|
var DIARY_END_MARKER = "<!-- openclaw:dreaming:diary:end -->";
|
|
56321
57517
|
function stableDreamId(params) {
|
|
@@ -56480,7 +57676,7 @@ function createDreamsSurface() {
|
|
|
56480
57676
|
return {
|
|
56481
57677
|
async read(filePath) {
|
|
56482
57678
|
try {
|
|
56483
|
-
const content = await
|
|
57679
|
+
const content = await readFile50(filePath, "utf8");
|
|
56484
57680
|
return parseDreamEntries(content);
|
|
56485
57681
|
} catch (error) {
|
|
56486
57682
|
if (error.code === "ENOENT") {
|
|
@@ -56490,10 +57686,10 @@ function createDreamsSurface() {
|
|
|
56490
57686
|
}
|
|
56491
57687
|
},
|
|
56492
57688
|
async append(filePath, entry) {
|
|
56493
|
-
await mkdir54(
|
|
57689
|
+
await mkdir54(path96.dirname(filePath), { recursive: true });
|
|
56494
57690
|
let content = "";
|
|
56495
57691
|
try {
|
|
56496
|
-
content = await
|
|
57692
|
+
content = await readFile50(filePath, "utf8");
|
|
56497
57693
|
} catch (error) {
|
|
56498
57694
|
if (error.code !== "ENOENT") throw error;
|
|
56499
57695
|
}
|
|
@@ -56511,22 +57707,22 @@ ${ensured.slice(endIndex)}` : `${ensureDiary("")}${block}`;
|
|
|
56511
57707
|
let fileWatcher = null;
|
|
56512
57708
|
let parentWatcher = null;
|
|
56513
57709
|
let timer = null;
|
|
56514
|
-
const watchedName =
|
|
56515
|
-
const watchedDir =
|
|
57710
|
+
const watchedName = path96.basename(filePath);
|
|
57711
|
+
const watchedDir = path96.dirname(filePath);
|
|
56516
57712
|
const resolveParentWatchTarget = () => {
|
|
56517
57713
|
let candidateDir = watchedDir;
|
|
56518
57714
|
while (true) {
|
|
56519
57715
|
try {
|
|
56520
57716
|
if (statSync(candidateDir).isDirectory()) {
|
|
56521
|
-
const relative =
|
|
57717
|
+
const relative = path96.relative(candidateDir, watchedDir);
|
|
56522
57718
|
return {
|
|
56523
57719
|
dir: candidateDir,
|
|
56524
|
-
expectedName: relative.length === 0 ? watchedName : relative.split(
|
|
57720
|
+
expectedName: relative.length === 0 ? watchedName : relative.split(path96.sep)[0] ?? watchedName
|
|
56525
57721
|
};
|
|
56526
57722
|
}
|
|
56527
57723
|
} catch {
|
|
56528
57724
|
}
|
|
56529
|
-
const parentDir =
|
|
57725
|
+
const parentDir = path96.dirname(candidateDir);
|
|
56530
57726
|
if (parentDir === candidateDir) {
|
|
56531
57727
|
return null;
|
|
56532
57728
|
}
|
|
@@ -56591,8 +57787,8 @@ ${ensured.slice(endIndex)}` : `${ensureDiary("")}${block}`;
|
|
|
56591
57787
|
// ../remnic-core/src/surfaces/heartbeat.ts
|
|
56592
57788
|
import { createHash as createHash17 } from "crypto";
|
|
56593
57789
|
import { statSync as statSync2, watch as watch3 } from "fs";
|
|
56594
|
-
import { readFile as
|
|
56595
|
-
import
|
|
57790
|
+
import { readFile as readFile51 } from "fs/promises";
|
|
57791
|
+
import path97 from "path";
|
|
56596
57792
|
function stableHeartbeatId(params) {
|
|
56597
57793
|
const digest = createHash17("sha1").update(
|
|
56598
57794
|
JSON.stringify({
|
|
@@ -56755,7 +57951,7 @@ function createHeartbeatSurface() {
|
|
|
56755
57951
|
return {
|
|
56756
57952
|
async read(filePath) {
|
|
56757
57953
|
try {
|
|
56758
|
-
const content = await
|
|
57954
|
+
const content = await readFile51(filePath, "utf8");
|
|
56759
57955
|
return parseHeartbeatEntries(content);
|
|
56760
57956
|
} catch (error) {
|
|
56761
57957
|
if (error.code === "ENOENT") {
|
|
@@ -56768,22 +57964,22 @@ function createHeartbeatSurface() {
|
|
|
56768
57964
|
let fileWatcher = null;
|
|
56769
57965
|
let parentWatcher = null;
|
|
56770
57966
|
let timer = null;
|
|
56771
|
-
const watchedName =
|
|
56772
|
-
const watchedDir =
|
|
57967
|
+
const watchedName = path97.basename(filePath);
|
|
57968
|
+
const watchedDir = path97.dirname(filePath);
|
|
56773
57969
|
const resolveParentWatchTarget = () => {
|
|
56774
57970
|
let candidateDir = watchedDir;
|
|
56775
57971
|
while (true) {
|
|
56776
57972
|
try {
|
|
56777
57973
|
if (statSync2(candidateDir).isDirectory()) {
|
|
56778
|
-
const relative =
|
|
57974
|
+
const relative = path97.relative(candidateDir, watchedDir);
|
|
56779
57975
|
return {
|
|
56780
57976
|
dir: candidateDir,
|
|
56781
|
-
expectedName: relative.length === 0 ? watchedName : relative.split(
|
|
57977
|
+
expectedName: relative.length === 0 ? watchedName : relative.split(path97.sep)[0] ?? watchedName
|
|
56782
57978
|
};
|
|
56783
57979
|
}
|
|
56784
57980
|
} catch {
|
|
56785
57981
|
}
|
|
56786
|
-
const parentDir =
|
|
57982
|
+
const parentDir = path97.dirname(candidateDir);
|
|
56787
57983
|
if (parentDir === candidateDir) {
|
|
56788
57984
|
return null;
|
|
56789
57985
|
}
|
|
@@ -56869,7 +58065,7 @@ function loadPluginEntryFromFile(pluginId) {
|
|
|
56869
58065
|
try {
|
|
56870
58066
|
const explicitConfigPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH");
|
|
56871
58067
|
const homeDir = resolveHomeDir();
|
|
56872
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
58068
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path98.join(homeDir, ".openclaw", "openclaw.json");
|
|
56873
58069
|
const content = readFileSync6(configPath, "utf-8");
|
|
56874
58070
|
const config = JSON.parse(content);
|
|
56875
58071
|
return resolveRemnicPluginEntry(config, pluginId);
|
|
@@ -56885,7 +58081,7 @@ function loadRawConfigFromFile() {
|
|
|
56885
58081
|
try {
|
|
56886
58082
|
const explicitConfigPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH");
|
|
56887
58083
|
const homeDir = resolveHomeDir();
|
|
56888
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
58084
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path98.join(homeDir, ".openclaw", "openclaw.json");
|
|
56889
58085
|
const content = readFileSync6(configPath, "utf-8");
|
|
56890
58086
|
const config = JSON.parse(content);
|
|
56891
58087
|
return config && typeof config === "object" ? config : void 0;
|
|
@@ -57119,9 +58315,9 @@ var pluginDefinition = {
|
|
|
57119
58315
|
citationsAutoDetect: cfg.citationsAutoDetect
|
|
57120
58316
|
});
|
|
57121
58317
|
globalThis[keys.ACCESS_HTTP_SERVER] = accessHttpServer;
|
|
57122
|
-
const pluginStateDir =
|
|
57123
|
-
const togglePrimaryPath =
|
|
57124
|
-
const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ?
|
|
58318
|
+
const pluginStateDir = path98.join(cfg.memoryDir, "state", "plugins", serviceId);
|
|
58319
|
+
const togglePrimaryPath = path98.join(pluginStateDir, "session-toggles.json");
|
|
58320
|
+
const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ? path98.join(cfg.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0;
|
|
57125
58321
|
const sessionToggleStore = createFileToggleStore(togglePrimaryPath, {
|
|
57126
58322
|
secondaryReadOnlyPath: toggleSecondaryPath
|
|
57127
58323
|
});
|
|
@@ -57143,11 +58339,11 @@ var pluginDefinition = {
|
|
|
57143
58339
|
}
|
|
57144
58340
|
function resolveDreamJournalPath(runtimeWorkspaceDir) {
|
|
57145
58341
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
57146
|
-
return
|
|
58342
|
+
return path98.isAbsolute(cfg.dreaming.journalPath) ? cfg.dreaming.journalPath : path98.join(workspaceRoot, cfg.dreaming.journalPath);
|
|
57147
58343
|
}
|
|
57148
58344
|
function resolveHeartbeatJournalPath(runtimeWorkspaceDir) {
|
|
57149
58345
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
57150
|
-
return
|
|
58346
|
+
return path98.isAbsolute(cfg.heartbeat.journalPath) ? cfg.heartbeat.journalPath : path98.join(workspaceRoot, cfg.heartbeat.journalPath);
|
|
57151
58347
|
}
|
|
57152
58348
|
function queueDreamSurfaceSync(runtimeWorkspaceDir) {
|
|
57153
58349
|
if (!cfg.dreaming.enabled) return Promise.resolve();
|
|
@@ -57305,7 +58501,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
57305
58501
|
timeoutMs: cfg.activeRecallTimeoutMs,
|
|
57306
58502
|
cacheTtlMs: cfg.activeRecallCacheTtlMs,
|
|
57307
58503
|
persistTranscripts: cfg.activeRecallPersistTranscripts,
|
|
57308
|
-
transcriptDir:
|
|
58504
|
+
transcriptDir: path98.isAbsolute(cfg.activeRecallTranscriptDir) ? cfg.activeRecallTranscriptDir : path98.join(pluginStateDir, cfg.activeRecallTranscriptDir),
|
|
57309
58505
|
entityGraphDepth: cfg.activeRecallEntityGraphDepth,
|
|
57310
58506
|
includeCausalTrajectories: cfg.activeRecallIncludeCausalTrajectories,
|
|
57311
58507
|
includeDaySummary: cfg.activeRecallIncludeDaySummary,
|
|
@@ -58173,6 +59369,219 @@ Keep the reflection grounded in the evidence below.
|
|
|
58173
59369
|
const runtimeAgentId = typeof runtimeAgent?.id === "string" && runtimeAgent.id.length > 0 ? runtimeAgent.id : void 0;
|
|
58174
59370
|
const capabilityAgentIds = runtimeAgentId ? [runtimeAgentId] : ["generalist"];
|
|
58175
59371
|
const capabilityWorkspaceDir = (typeof runtimeAgent?.workspaceDir === "string" && runtimeAgent.workspaceDir.length > 0 ? runtimeAgent.workspaceDir : void 0) ?? orchestrator.config.workspaceDir ?? defaultWorkspaceDir();
|
|
59372
|
+
const remnicUsesQmd = (orchestrator.config.searchBackend ?? "qmd") === "qmd" && orchestrator.config.qmdEnabled !== false;
|
|
59373
|
+
const remnicQmdCommand = typeof orchestrator.config.qmdPath === "string" && orchestrator.config.qmdPath.trim().length > 0 ? orchestrator.config.qmdPath.trim() : "qmd";
|
|
59374
|
+
const readAllowedRoots = [
|
|
59375
|
+
orchestrator.config.memoryDir,
|
|
59376
|
+
capabilityWorkspaceDir ? path98.join(capabilityWorkspaceDir, "memory") : void 0
|
|
59377
|
+
].filter((root) => typeof root === "string" && root.length > 0);
|
|
59378
|
+
const canonicalizeRootForContainment = async (rawPath) => {
|
|
59379
|
+
const resolved = path98.resolve(rawPath);
|
|
59380
|
+
try {
|
|
59381
|
+
return path98.normalize(await realpath5(resolved));
|
|
59382
|
+
} catch {
|
|
59383
|
+
return path98.normalize(resolved);
|
|
59384
|
+
}
|
|
59385
|
+
};
|
|
59386
|
+
const canonicalizeForRead = async (rawPath) => {
|
|
59387
|
+
const resolved = path98.resolve(rawPath);
|
|
59388
|
+
const real = await realpath5(resolved);
|
|
59389
|
+
return path98.normalize(real);
|
|
59390
|
+
};
|
|
59391
|
+
const readAllowedCanonicalRootsPromise = Promise.all(
|
|
59392
|
+
readAllowedRoots.map((root) => canonicalizeRootForContainment(root))
|
|
59393
|
+
);
|
|
59394
|
+
const isWithinAllowedRoot = async (candidatePath) => {
|
|
59395
|
+
let canonicalCandidatePath;
|
|
59396
|
+
try {
|
|
59397
|
+
canonicalCandidatePath = await canonicalizeForRead(candidatePath);
|
|
59398
|
+
} catch {
|
|
59399
|
+
return false;
|
|
59400
|
+
}
|
|
59401
|
+
const canonicalRoots = await readAllowedCanonicalRootsPromise;
|
|
59402
|
+
return canonicalRoots.some((root) => {
|
|
59403
|
+
const relative = path98.relative(root, canonicalCandidatePath);
|
|
59404
|
+
return relative === "" || !relative.startsWith("..") && !path98.isAbsolute(relative);
|
|
59405
|
+
});
|
|
59406
|
+
};
|
|
59407
|
+
const normalizeWorkspacePath = (rawPath) => {
|
|
59408
|
+
if (!rawPath || typeof rawPath !== "string") return "memory";
|
|
59409
|
+
const resolved = path98.isAbsolute(rawPath) ? path98.resolve(rawPath) : path98.resolve(capabilityWorkspaceDir, rawPath);
|
|
59410
|
+
const relative = path98.relative(capabilityWorkspaceDir, resolved);
|
|
59411
|
+
return relative && !relative.startsWith("..") && !path98.isAbsolute(relative) ? relative : rawPath;
|
|
59412
|
+
};
|
|
59413
|
+
const relativizeToMemoryRoot = (rawPath) => {
|
|
59414
|
+
if (!rawPath || typeof rawPath !== "string") return "memory";
|
|
59415
|
+
const resolved = path98.isAbsolute(rawPath) ? path98.resolve(rawPath) : path98.resolve(capabilityWorkspaceDir, rawPath);
|
|
59416
|
+
for (const root of readAllowedRoots) {
|
|
59417
|
+
const relative = path98.relative(root, resolved);
|
|
59418
|
+
if (relative !== "" && !relative.startsWith("..") && !path98.isAbsolute(relative)) {
|
|
59419
|
+
return relative;
|
|
59420
|
+
}
|
|
59421
|
+
}
|
|
59422
|
+
return normalizeWorkspacePath(rawPath);
|
|
59423
|
+
};
|
|
59424
|
+
const resolveReadablePath = async (requestedPath) => {
|
|
59425
|
+
const candidateAbsolutePaths = path98.isAbsolute(requestedPath) ? [path98.resolve(requestedPath)] : readAllowedRoots.map((root) => path98.resolve(root, requestedPath));
|
|
59426
|
+
let canonicalPath;
|
|
59427
|
+
let lastError;
|
|
59428
|
+
for (const absolutePath of candidateAbsolutePaths) {
|
|
59429
|
+
try {
|
|
59430
|
+
canonicalPath = await canonicalizeForRead(absolutePath);
|
|
59431
|
+
break;
|
|
59432
|
+
} catch (err) {
|
|
59433
|
+
lastError = err;
|
|
59434
|
+
}
|
|
59435
|
+
}
|
|
59436
|
+
if (canonicalPath === void 0) {
|
|
59437
|
+
throw new Error(
|
|
59438
|
+
`memory read rejected (path unresolvable): ${requestedPath}`
|
|
59439
|
+
);
|
|
59440
|
+
}
|
|
59441
|
+
const canonicalRoots = await readAllowedCanonicalRootsPromise;
|
|
59442
|
+
const contained = canonicalRoots.some((root) => {
|
|
59443
|
+
const relative = path98.relative(root, canonicalPath);
|
|
59444
|
+
return relative === "" || !relative.startsWith("..") && !path98.isAbsolute(relative);
|
|
59445
|
+
});
|
|
59446
|
+
if (!contained) {
|
|
59447
|
+
throw new Error(`memory read outside allowed roots: ${requestedPath}`);
|
|
59448
|
+
}
|
|
59449
|
+
if (!canonicalPath.toLowerCase().endsWith(".md")) {
|
|
59450
|
+
throw new Error(
|
|
59451
|
+
`memory read restricted to .md files: ${requestedPath}`
|
|
59452
|
+
);
|
|
59453
|
+
}
|
|
59454
|
+
return canonicalPath;
|
|
59455
|
+
};
|
|
59456
|
+
const remnicMemoryRuntime = {
|
|
59457
|
+
async getMemorySearchManager(_params) {
|
|
59458
|
+
return {
|
|
59459
|
+
manager: {
|
|
59460
|
+
async search(query, opts) {
|
|
59461
|
+
const namespace = typeof orchestrator.resolveSelfNamespace === "function" ? orchestrator.resolveSelfNamespace(opts?.sessionKey) : void 0;
|
|
59462
|
+
const resolvedMode = opts?.qmdSearchModeOverride === "vsearch" ? "vector" : opts?.qmdSearchModeOverride === "query" ? "search" : opts?.qmdSearchModeOverride ?? "search";
|
|
59463
|
+
const rawResults = await orchestrator.searchAcrossNamespaces({
|
|
59464
|
+
query,
|
|
59465
|
+
maxResults: opts?.maxResults,
|
|
59466
|
+
namespaces: namespace ? [namespace] : void 0,
|
|
59467
|
+
mode: resolvedMode
|
|
59468
|
+
});
|
|
59469
|
+
const isArtifactPath2 = (p) => /(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(p);
|
|
59470
|
+
return rawResults.filter((result) => {
|
|
59471
|
+
const candidate = result;
|
|
59472
|
+
const p = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : "";
|
|
59473
|
+
return !isArtifactPath2(p);
|
|
59474
|
+
}).map((result, index) => {
|
|
59475
|
+
const candidate = result;
|
|
59476
|
+
const rawPath = typeof candidate.path === "string" ? candidate.path : typeof candidate.id === "string" ? candidate.id : `memory-${index + 1}`;
|
|
59477
|
+
const absolutePath = path98.isAbsolute(rawPath) ? path98.resolve(rawPath) : (() => {
|
|
59478
|
+
for (const root of readAllowedRoots) {
|
|
59479
|
+
const candidateAbs = path98.resolve(root, rawPath);
|
|
59480
|
+
const relative = path98.relative(root, candidateAbs);
|
|
59481
|
+
if (!relative.startsWith("..") && !path98.isAbsolute(relative)) {
|
|
59482
|
+
return candidateAbs;
|
|
59483
|
+
}
|
|
59484
|
+
}
|
|
59485
|
+
return path98.resolve(capabilityWorkspaceDir, rawPath);
|
|
59486
|
+
})();
|
|
59487
|
+
const normalizedPath = relativizeToMemoryRoot(rawPath);
|
|
59488
|
+
const startLine = typeof candidate.startLine === "number" && Number.isFinite(candidate.startLine) ? Math.max(1, Math.floor(candidate.startLine)) : 1;
|
|
59489
|
+
const endLine = typeof candidate.endLine === "number" && Number.isFinite(candidate.endLine) ? Math.max(startLine, Math.floor(candidate.endLine)) : startLine;
|
|
59490
|
+
return {
|
|
59491
|
+
path: absolutePath,
|
|
59492
|
+
startLine,
|
|
59493
|
+
endLine,
|
|
59494
|
+
score: typeof candidate.score === "number" && Number.isFinite(candidate.score) ? candidate.score : 0,
|
|
59495
|
+
snippet: typeof candidate.snippet === "string" ? candidate.snippet : typeof candidate.text === "string" ? candidate.text : "",
|
|
59496
|
+
source: normalizedPath.includes("sessions/") ? "sessions" : "memory",
|
|
59497
|
+
citation: normalizedPath
|
|
59498
|
+
};
|
|
59499
|
+
}).filter(
|
|
59500
|
+
(result) => typeof opts?.minScore === "number" && Number.isFinite(opts.minScore) ? result.score >= opts.minScore : true
|
|
59501
|
+
);
|
|
59502
|
+
},
|
|
59503
|
+
async readFile(params) {
|
|
59504
|
+
const requestedPath = normalizeWorkspacePath(params.relPath);
|
|
59505
|
+
const absolutePath = await resolveReadablePath(params.relPath);
|
|
59506
|
+
const text = await readFile52(absolutePath, "utf-8");
|
|
59507
|
+
const allLines = text.split(/\r?\n/);
|
|
59508
|
+
const from = typeof params.from === "number" ? Math.max(1, Math.floor(params.from)) : 1;
|
|
59509
|
+
const lines = typeof params.lines === "number" && Number.isFinite(params.lines) ? Math.max(1, Math.floor(params.lines)) : void 0;
|
|
59510
|
+
const startIndex = from - 1;
|
|
59511
|
+
const endIndex = typeof lines === "number" ? startIndex + lines : allLines.length;
|
|
59512
|
+
const slice = allLines.slice(startIndex, endIndex);
|
|
59513
|
+
const truncated = endIndex < allLines.length;
|
|
59514
|
+
return {
|
|
59515
|
+
text: slice.join("\n"),
|
|
59516
|
+
path: requestedPath,
|
|
59517
|
+
truncated: truncated || void 0,
|
|
59518
|
+
from,
|
|
59519
|
+
lines,
|
|
59520
|
+
nextFrom: truncated ? endIndex + 1 : void 0
|
|
59521
|
+
};
|
|
59522
|
+
},
|
|
59523
|
+
status() {
|
|
59524
|
+
const qmdAvailable = remnicUsesQmd && typeof orchestrator.qmd?.isAvailable === "function" ? Boolean(orchestrator.qmd.isAvailable()) : !remnicUsesQmd;
|
|
59525
|
+
const qmdDebug = typeof orchestrator.qmd?.debugStatus === "function" ? orchestrator.qmd.debugStatus() : void 0;
|
|
59526
|
+
return {
|
|
59527
|
+
backend: remnicUsesQmd ? "qmd" : "builtin",
|
|
59528
|
+
provider: remnicUsesQmd ? "qmd" : "builtin",
|
|
59529
|
+
requestedProvider: remnicUsesQmd ? "qmd" : "builtin",
|
|
59530
|
+
model: remnicUsesQmd ? remnicQmdCommand : "builtin",
|
|
59531
|
+
dirty: false,
|
|
59532
|
+
workspaceDir: capabilityWorkspaceDir,
|
|
59533
|
+
dbPath: orchestrator.config.memoryDir,
|
|
59534
|
+
sources: ["memory"],
|
|
59535
|
+
sourceCounts: [],
|
|
59536
|
+
vector: remnicUsesQmd ? {
|
|
59537
|
+
enabled: true,
|
|
59538
|
+
available: qmdAvailable
|
|
59539
|
+
} : {
|
|
59540
|
+
enabled: false
|
|
59541
|
+
},
|
|
59542
|
+
fts: {
|
|
59543
|
+
enabled: true,
|
|
59544
|
+
available: qmdAvailable
|
|
59545
|
+
},
|
|
59546
|
+
custom: {
|
|
59547
|
+
remnic: {
|
|
59548
|
+
qmdAvailable,
|
|
59549
|
+
qmdDebug,
|
|
59550
|
+
memoryDir: orchestrator.config.memoryDir
|
|
59551
|
+
}
|
|
59552
|
+
}
|
|
59553
|
+
};
|
|
59554
|
+
},
|
|
59555
|
+
async sync(_params2) {
|
|
59556
|
+
if (remnicUsesQmd && typeof orchestrator.qmd?.update === "function") {
|
|
59557
|
+
await orchestrator.qmd.update();
|
|
59558
|
+
}
|
|
59559
|
+
},
|
|
59560
|
+
async probeEmbeddingAvailability() {
|
|
59561
|
+
if (!remnicUsesQmd) return { ok: true };
|
|
59562
|
+
const qmdAvailable = typeof orchestrator.qmd?.isAvailable === "function" ? Boolean(orchestrator.qmd.isAvailable()) : false;
|
|
59563
|
+
if (qmdAvailable) return { ok: true };
|
|
59564
|
+
const qmdDebug = typeof orchestrator.qmd?.debugStatus === "function" ? orchestrator.qmd.debugStatus() : void 0;
|
|
59565
|
+
return {
|
|
59566
|
+
ok: false,
|
|
59567
|
+
error: qmdDebug ?? "Remnic QMD backend unavailable"
|
|
59568
|
+
};
|
|
59569
|
+
},
|
|
59570
|
+
async probeVectorAvailability() {
|
|
59571
|
+
if (!remnicUsesQmd) return false;
|
|
59572
|
+
return typeof orchestrator.qmd?.isAvailable === "function" ? Boolean(orchestrator.qmd.isAvailable()) : false;
|
|
59573
|
+
},
|
|
59574
|
+
async close() {
|
|
59575
|
+
}
|
|
59576
|
+
}
|
|
59577
|
+
};
|
|
59578
|
+
},
|
|
59579
|
+
resolveMemoryBackendConfig(_params) {
|
|
59580
|
+
return remnicUsesQmd ? { backend: "qmd", qmd: { command: remnicQmdCommand } } : { backend: "builtin" };
|
|
59581
|
+
},
|
|
59582
|
+
async closeAllMemorySearchManagers() {
|
|
59583
|
+
}
|
|
59584
|
+
};
|
|
58176
59585
|
const memoryCapability = {
|
|
58177
59586
|
// Include the promptBuilder so runtimes that treat unified capability
|
|
58178
59587
|
// registration as authoritative (SDK >=2026.4.5) continue to inject
|
|
@@ -58180,6 +59589,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58180
59589
|
// Respect promptInjectionAllowed policy — omit promptBuilder if injection
|
|
58181
59590
|
// is disabled, so the capability only provides publicArtifacts.
|
|
58182
59591
|
...promptInjectionAllowed ? { promptBuilder: capabilityPromptBuilder } : {},
|
|
59592
|
+
runtime: remnicMemoryRuntime,
|
|
58183
59593
|
publicArtifacts: {
|
|
58184
59594
|
listArtifacts: async (_params) => {
|
|
58185
59595
|
try {
|
|
@@ -58197,7 +59607,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58197
59607
|
};
|
|
58198
59608
|
api.registerMemoryCapability(memoryCapability);
|
|
58199
59609
|
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}`);
|
|
59610
|
+
log.info(`registered memory capability with runtime and publicArtifacts provider${builderDesc}`);
|
|
58201
59611
|
}
|
|
58202
59612
|
api.on(
|
|
58203
59613
|
"agent_end",
|
|
@@ -58494,7 +59904,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58494
59904
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
58495
59905
|
);
|
|
58496
59906
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
58497
|
-
const signalPath =
|
|
59907
|
+
const signalPath = path98.join(
|
|
58498
59908
|
workspaceDir,
|
|
58499
59909
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
58500
59910
|
);
|
|
@@ -58746,7 +60156,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58746
60156
|
}
|
|
58747
60157
|
async function ensureHourlySummaryCron(api2) {
|
|
58748
60158
|
const jobId = "engram-hourly-summary";
|
|
58749
|
-
const cronFilePath =
|
|
60159
|
+
const cronFilePath = path98.join(
|
|
58750
60160
|
os6.homedir(),
|
|
58751
60161
|
".openclaw",
|
|
58752
60162
|
"cron",
|
|
@@ -58758,7 +60168,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58758
60168
|
jobs: []
|
|
58759
60169
|
};
|
|
58760
60170
|
try {
|
|
58761
|
-
const content = await
|
|
60171
|
+
const content = await readFile52(cronFilePath, "utf-8");
|
|
58762
60172
|
jobsData = JSON.parse(content);
|
|
58763
60173
|
} catch {
|
|
58764
60174
|
}
|
|
@@ -58773,7 +60183,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58773
60183
|
id: jobId,
|
|
58774
60184
|
agentId: "generalist",
|
|
58775
60185
|
model,
|
|
58776
|
-
name: "
|
|
60186
|
+
name: "Remnic Hourly Summary",
|
|
58777
60187
|
enabled: true,
|
|
58778
60188
|
createdAtMs: Date.now(),
|
|
58779
60189
|
updatedAtMs: Date.now(),
|
|
@@ -58789,7 +60199,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58789
60199
|
kind: "agentTurn",
|
|
58790
60200
|
timeoutSeconds: 120,
|
|
58791
60201
|
thinking: "off",
|
|
58792
|
-
message: "You are OpenClaw automation.\n\nTask: Generate
|
|
60202
|
+
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
60203
|
},
|
|
58794
60204
|
delivery: { mode: "none" },
|
|
58795
60205
|
state: {}
|
|
@@ -58959,39 +60369,33 @@ Keep the reflection grounded in the evidence below.
|
|
|
58959
60369
|
stop: async () => {
|
|
58960
60370
|
if (!didCountStart) return;
|
|
58961
60371
|
didCountStart = false;
|
|
60372
|
+
const initPromiseAtStopEntry = globalThis[keys.INIT_PROMISE];
|
|
60373
|
+
try {
|
|
60374
|
+
await orchestrator.deferredReady;
|
|
60375
|
+
} catch {
|
|
60376
|
+
}
|
|
58962
60377
|
const remainingServices = Math.max(
|
|
58963
60378
|
0,
|
|
58964
60379
|
(globalThis[CLI_ACTIVE_SERVICE_COUNT] || 0) - 1
|
|
58965
60380
|
);
|
|
58966
60381
|
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];
|
|
60382
|
+
const currentInitPromise = initPromiseAtStopEntry;
|
|
58987
60383
|
let secondaryTookOver = false;
|
|
60384
|
+
if (!currentInitPromise && globalThis[keys.INIT_PROMISE]) {
|
|
60385
|
+
secondaryTookOver = true;
|
|
60386
|
+
}
|
|
58988
60387
|
if (!currentInitPromise) {
|
|
58989
|
-
|
|
58990
|
-
|
|
58991
|
-
|
|
58992
|
-
|
|
60388
|
+
if (!secondaryTookOver) {
|
|
60389
|
+
globalThis[keys.REGISTERED_GUARD] = false;
|
|
60390
|
+
if (remainingServices === 0) {
|
|
60391
|
+
globalThis[CLI_REGISTERED_GUARD] = false;
|
|
60392
|
+
globalThis[SESSION_COMMANDS_REGISTERED_GUARD] = false;
|
|
60393
|
+
}
|
|
58993
60394
|
}
|
|
58994
60395
|
} else {
|
|
60396
|
+
if (globalThis[keys.INIT_PROMISE] && globalThis[keys.INIT_PROMISE] !== currentInitPromise) {
|
|
60397
|
+
secondaryTookOver = true;
|
|
60398
|
+
}
|
|
58995
60399
|
try {
|
|
58996
60400
|
await currentInitPromise;
|
|
58997
60401
|
} catch {
|
|
@@ -59001,6 +60405,27 @@ Keep the reflection grounded in the evidence below.
|
|
|
59001
60405
|
secondaryTookOver = true;
|
|
59002
60406
|
}
|
|
59003
60407
|
}
|
|
60408
|
+
if (!secondaryTookOver) {
|
|
60409
|
+
try {
|
|
60410
|
+
activeOpikExporter?.unsubscribe();
|
|
60411
|
+
} catch (err) {
|
|
60412
|
+
log.debug(`engram opik exporter unsubscribe failed: ${err}`);
|
|
60413
|
+
}
|
|
60414
|
+
activeOpikExporter = null;
|
|
60415
|
+
try {
|
|
60416
|
+
await accessHttpServer.stop();
|
|
60417
|
+
} catch (err) {
|
|
60418
|
+
log.debug(`engram access HTTP stop failed: ${err}`);
|
|
60419
|
+
}
|
|
60420
|
+
stopDreamWatcher?.();
|
|
60421
|
+
stopDreamWatcher = null;
|
|
60422
|
+
stopHeartbeatWatcher?.();
|
|
60423
|
+
stopHeartbeatWatcher = null;
|
|
60424
|
+
removeDreamingObserver?.();
|
|
60425
|
+
removeDreamingObserver = null;
|
|
60426
|
+
delete globalThis[keys.ACCESS_HTTP_SERVER];
|
|
60427
|
+
delete globalThis[keys.ACCESS_SERVICE];
|
|
60428
|
+
}
|
|
59004
60429
|
if (!secondaryTookOver) {
|
|
59005
60430
|
globalThis[keys.HOOK_APIS] = /* @__PURE__ */ new WeakSet();
|
|
59006
60431
|
}
|
|
@@ -59036,7 +60461,7 @@ function extractTextContent2(msg) {
|
|
|
59036
60461
|
// src/bridge.ts
|
|
59037
60462
|
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
59038
60463
|
import * as childProcess from "child_process";
|
|
59039
|
-
import
|
|
60464
|
+
import path99 from "path";
|
|
59040
60465
|
|
|
59041
60466
|
// src/service-candidates.ts
|
|
59042
60467
|
function firstSuccessfulResult(candidates, attempt) {
|
|
@@ -59063,17 +60488,17 @@ function readCompatEnv(primary, legacy) {
|
|
|
59063
60488
|
function configPathCandidates() {
|
|
59064
60489
|
const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
|
|
59065
60490
|
return [
|
|
59066
|
-
...envPath ? [
|
|
59067
|
-
|
|
59068
|
-
|
|
59069
|
-
|
|
59070
|
-
|
|
60491
|
+
...envPath ? [path99.resolve(envPath)] : [],
|
|
60492
|
+
path99.join(resolveHomeDir2(), ".config", "remnic", "config.json"),
|
|
60493
|
+
path99.join(resolveHomeDir2(), ".config", "engram", "config.json"),
|
|
60494
|
+
path99.join(process.cwd(), "remnic.config.json"),
|
|
60495
|
+
path99.join(process.cwd(), "engram.config.json")
|
|
59071
60496
|
];
|
|
59072
60497
|
}
|
|
59073
60498
|
function isDaemonRunning() {
|
|
59074
60499
|
for (const pidFile of [
|
|
59075
|
-
|
|
59076
|
-
|
|
60500
|
+
path99.join(resolveHomeDir2(), ".remnic", "server.pid"),
|
|
60501
|
+
path99.join(resolveHomeDir2(), ".engram", "server.pid")
|
|
59077
60502
|
]) {
|
|
59078
60503
|
try {
|
|
59079
60504
|
const pid = parseInt(readFileSync7(pidFile, "utf8").trim(), 10);
|
|
@@ -59146,8 +60571,8 @@ function detectBridgeMode() {
|
|
|
59146
60571
|
}
|
|
59147
60572
|
function loadAnyToken() {
|
|
59148
60573
|
const tokenPaths = [
|
|
59149
|
-
|
|
59150
|
-
|
|
60574
|
+
path99.join(resolveHomeDir2(), ".remnic", "tokens.json"),
|
|
60575
|
+
path99.join(resolveHomeDir2(), ".engram", "tokens.json")
|
|
59151
60576
|
];
|
|
59152
60577
|
for (const tokensPath of tokenPaths) {
|
|
59153
60578
|
if (!existsSync12(tokensPath)) continue;
|