@remnic/plugin-openclaw 1.0.4 → 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-62IFBWHC.js → causal-consolidation-EBLROS42.js} +5 -5
- package/dist/{causal-retrieval-5UPIKZ4I.js → causal-retrieval-3BKBXVXD.js} +1 -1
- package/dist/{chunk-J7VGZNH4.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-BEV4BHEH.js → engine-BU6GNUJ5.js} +2 -2
- package/dist/index.js +1964 -538
- package/dist/{storage-HW6SRQCK.js → storage-BA6OBLMK.js} +1 -1
- package/openclaw.plugin.json +168 -4
- package/package.json +5 -2
- package/dist/chunk-NJG4HPAL.js +0 -7099
- package/dist/openai-53ZQ46RZ.js +0 -44
package/dist/index.js
CHANGED
|
@@ -3,17 +3,16 @@ 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";
|
|
14
|
-
import {
|
|
15
|
-
OpenAI
|
|
16
|
-
} from "./chunk-NJG4HPAL.js";
|
|
17
16
|
import {
|
|
18
17
|
GraphIndex,
|
|
19
18
|
analyzeGraphHealth
|
|
@@ -27,16 +26,7 @@ import {
|
|
|
27
26
|
parseConsolidationResponse,
|
|
28
27
|
renderExtensionsFooter,
|
|
29
28
|
resolveExtensionsRoot
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import {
|
|
32
|
-
FallbackLlmClient,
|
|
33
|
-
buildChatCompletionTokenLimit,
|
|
34
|
-
extractJsonCandidates,
|
|
35
|
-
mergeEnv,
|
|
36
|
-
readEnvVar,
|
|
37
|
-
resolveHomeDir,
|
|
38
|
-
shouldAssumeOpenAiChatCompletions
|
|
39
|
-
} from "./chunk-3SA5F4WT.js";
|
|
29
|
+
} from "./chunk-QHMR3D7U.js";
|
|
40
30
|
import {
|
|
41
31
|
ContentHashIndex,
|
|
42
32
|
MEMORY_LIFECYCLE_EVENT_SORT_ORDER,
|
|
@@ -78,7 +68,7 @@ import {
|
|
|
78
68
|
sortMemoryLifecycleEvents,
|
|
79
69
|
stripCitationForTemplate,
|
|
80
70
|
toMemoryPathRel
|
|
81
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-KPMXWORS.js";
|
|
82
72
|
import {
|
|
83
73
|
BoxBuilder,
|
|
84
74
|
assertIsoRecordedAt,
|
|
@@ -92,6 +82,15 @@ import {
|
|
|
92
82
|
recordStoreDay,
|
|
93
83
|
validateStringRecord
|
|
94
84
|
} from "./chunk-YHH3SXKD.js";
|
|
85
|
+
import {
|
|
86
|
+
FallbackLlmClient,
|
|
87
|
+
buildChatCompletionTokenLimit,
|
|
88
|
+
extractJsonCandidates,
|
|
89
|
+
mergeEnv,
|
|
90
|
+
readEnvVar,
|
|
91
|
+
resolveHomeDir,
|
|
92
|
+
shouldAssumeOpenAiChatCompletions
|
|
93
|
+
} from "./chunk-3SA5F4WT.js";
|
|
95
94
|
import {
|
|
96
95
|
listJsonFiles,
|
|
97
96
|
listNamedFiles,
|
|
@@ -207,6 +206,7 @@ async function buildExtensionsFooterForSummary(config) {
|
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
// ../../src/index.ts
|
|
209
|
+
import OpenAI2 from "openai";
|
|
210
210
|
import { createRequire as createRequire3 } from "module";
|
|
211
211
|
|
|
212
212
|
// ../remnic-core/src/config.ts
|
|
@@ -235,6 +235,18 @@ function coerceBool(value) {
|
|
|
235
235
|
function coerceInstallExtension(value) {
|
|
236
236
|
return coerceBool(value);
|
|
237
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
|
+
}
|
|
238
250
|
|
|
239
251
|
// ../remnic-core/src/config.ts
|
|
240
252
|
var DEFAULT_MEMORY_DIR = path2.join(
|
|
@@ -329,7 +341,8 @@ var VALID_MEMORY_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
329
341
|
"commitment",
|
|
330
342
|
"moment",
|
|
331
343
|
"skill",
|
|
332
|
-
"rule"
|
|
344
|
+
"rule",
|
|
345
|
+
"procedure"
|
|
333
346
|
]);
|
|
334
347
|
var DEFAULT_BEHAVIOR_LOOP_PROTECTED_PARAMS = [
|
|
335
348
|
"maxMemoryTokens",
|
|
@@ -496,6 +509,19 @@ function parseConfig(raw) {
|
|
|
496
509
|
) ? rawCodexCompat.compactionFlushMode : "auto",
|
|
497
510
|
fingerprintDedup: rawCodexCompat.fingerprintDedup !== false
|
|
498
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
|
+
};
|
|
499
525
|
const memoryDir = typeof cfg.memoryDir === "string" && cfg.memoryDir.length > 0 ? cfg.memoryDir : DEFAULT_MEMORY_DIR;
|
|
500
526
|
const rawIdentityInjectionMode = cfg.identityInjectionMode;
|
|
501
527
|
const identityInjectionMode = rawIdentityInjectionMode && VALID_IDENTITY_INJECTION_MODES.includes(rawIdentityInjectionMode) ? rawIdentityInjectionMode : "recovery_only";
|
|
@@ -683,6 +709,25 @@ function parseConfig(raw) {
|
|
|
683
709
|
// On by default
|
|
684
710
|
temporalSupersessionIncludeInRecall: cfg.temporalSupersessionIncludeInRecall === true,
|
|
685
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"],
|
|
686
731
|
// Memory Linking (Phase 3A)
|
|
687
732
|
memoryLinkingEnabled: cfg.memoryLinkingEnabled === true,
|
|
688
733
|
// Off by default initially
|
|
@@ -758,6 +803,7 @@ function parseConfig(raw) {
|
|
|
758
803
|
activeRecallAttachRecallExplain: cfg.activeRecallAttachRecallExplain === true,
|
|
759
804
|
activeRecallAllowChainedActiveMemory: cfg.activeRecallAllowChainedActiveMemory === true,
|
|
760
805
|
dreaming,
|
|
806
|
+
procedural,
|
|
761
807
|
heartbeat,
|
|
762
808
|
slotBehavior,
|
|
763
809
|
codexCompat,
|
|
@@ -827,7 +873,7 @@ function parseConfig(raw) {
|
|
|
827
873
|
semanticConsolidationMinClusterSize: typeof cfg.semanticConsolidationMinClusterSize === "number" ? Math.max(2, Math.floor(cfg.semanticConsolidationMinClusterSize)) : 3,
|
|
828
874
|
semanticConsolidationExcludeCategories: Array.isArray(cfg.semanticConsolidationExcludeCategories) ? cfg.semanticConsolidationExcludeCategories.filter(
|
|
829
875
|
(c) => typeof c === "string" && c.length > 0
|
|
830
|
-
) : ["correction", "commitment"],
|
|
876
|
+
) : ["correction", "commitment", "procedure"],
|
|
831
877
|
semanticConsolidationIntervalHours: typeof cfg.semanticConsolidationIntervalHours === "number" ? Math.max(1, Math.floor(cfg.semanticConsolidationIntervalHours)) : 168,
|
|
832
878
|
semanticConsolidationMaxPerRun: typeof cfg.semanticConsolidationMaxPerRun === "number" ? Math.max(0, Math.floor(cfg.semanticConsolidationMaxPerRun)) : 100,
|
|
833
879
|
creationMemoryEnabled: cfg.creationMemoryEnabled === true,
|
|
@@ -1077,7 +1123,7 @@ function parseConfig(raw) {
|
|
|
1077
1123
|
factArchivalAgeDays: typeof cfg.factArchivalAgeDays === "number" ? cfg.factArchivalAgeDays : 90,
|
|
1078
1124
|
factArchivalMaxImportance: typeof cfg.factArchivalMaxImportance === "number" ? cfg.factArchivalMaxImportance : 0.3,
|
|
1079
1125
|
factArchivalMaxAccessCount: typeof cfg.factArchivalMaxAccessCount === "number" ? cfg.factArchivalMaxAccessCount : 2,
|
|
1080
|
-
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"],
|
|
1081
1127
|
// v8.3 lifecycle policy engine (default off)
|
|
1082
1128
|
lifecyclePolicyEnabled: cfg.lifecyclePolicyEnabled === true,
|
|
1083
1129
|
lifecycleFilterStaleEnabled: cfg.lifecycleFilterStaleEnabled === true,
|
|
@@ -1086,7 +1132,7 @@ function parseConfig(raw) {
|
|
|
1086
1132
|
lifecycleArchiveDecayThreshold: typeof cfg.lifecycleArchiveDecayThreshold === "number" ? Math.min(1, Math.max(0, cfg.lifecycleArchiveDecayThreshold)) : 0.85,
|
|
1087
1133
|
lifecycleProtectedCategories: Array.isArray(cfg.lifecycleProtectedCategories) ? cfg.lifecycleProtectedCategories.filter(
|
|
1088
1134
|
(c) => typeof c === "string" && VALID_MEMORY_CATEGORIES.has(c)
|
|
1089
|
-
) : ["decision", "principle", "commitment", "preference"],
|
|
1135
|
+
) : ["decision", "principle", "commitment", "preference", "procedure"],
|
|
1090
1136
|
lifecycleMetricsEnabled: typeof cfg.lifecycleMetricsEnabled === "boolean" ? cfg.lifecycleMetricsEnabled : cfg.lifecyclePolicyEnabled === true,
|
|
1091
1137
|
// v8.3 proactive + policy learning (default off)
|
|
1092
1138
|
proactiveExtractionEnabled: cfg.proactiveExtractionEnabled === true,
|
|
@@ -1358,6 +1404,11 @@ function buildDefaultRecallPipeline(cfg) {
|
|
|
1358
1404
|
maxEntities: typeof cfg.knowledgeIndexMaxEntities === "number" ? Math.max(0, Math.floor(cfg.knowledgeIndexMaxEntities)) : 40
|
|
1359
1405
|
},
|
|
1360
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
|
+
},
|
|
1361
1412
|
{ id: "memory-boxes", enabled: cfg.memoryBoxesEnabled === true },
|
|
1362
1413
|
{ id: "temporal-memory-tree", enabled: cfg.temporalMemoryTreeEnabled === true },
|
|
1363
1414
|
{ id: "lcm-compressed-history", enabled: cfg.lcmEnabled === true },
|
|
@@ -1483,7 +1534,7 @@ function detectSdkCapabilities(api) {
|
|
|
1483
1534
|
// ../remnic-core/src/orchestrator.ts
|
|
1484
1535
|
import path43 from "path";
|
|
1485
1536
|
import os3 from "os";
|
|
1486
|
-
import { createHash as createHash9 } from "crypto";
|
|
1537
|
+
import { createHash as createHash9, randomBytes } from "crypto";
|
|
1487
1538
|
import { existsSync as existsSync8 } from "fs";
|
|
1488
1539
|
import {
|
|
1489
1540
|
mkdir as mkdir30,
|
|
@@ -2144,6 +2195,15 @@ var SmartBuffer = class {
|
|
|
2144
2195
|
this.state = this.normalizeState(await this.storage.loadBuffer());
|
|
2145
2196
|
this.loaded = true;
|
|
2146
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
|
+
}
|
|
2147
2207
|
async save() {
|
|
2148
2208
|
await this.storage.saveBuffer(this.state);
|
|
2149
2209
|
}
|
|
@@ -2627,6 +2687,9 @@ function buildRecursiveFallback(content, cfg) {
|
|
|
2627
2687
|
};
|
|
2628
2688
|
}
|
|
2629
2689
|
|
|
2690
|
+
// ../remnic-core/src/extraction.ts
|
|
2691
|
+
import OpenAI from "openai";
|
|
2692
|
+
|
|
2630
2693
|
// ../remnic-core/src/delinearize.ts
|
|
2631
2694
|
var PRONOUN_MAP = {
|
|
2632
2695
|
"he": { types: ["person"], possessive: false, group: "masc" },
|
|
@@ -3709,14 +3772,40 @@ function parseMemoryActionEligibilityContext(value) {
|
|
|
3709
3772
|
source: "unknown"
|
|
3710
3773
|
};
|
|
3711
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
|
+
});
|
|
3712
3785
|
var ExtractedFactSchema = external_exports.object({
|
|
3713
|
-
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
|
+
]),
|
|
3714
3800
|
content: external_exports.string().describe("The memory content \u2014 a clear, standalone statement"),
|
|
3715
3801
|
confidence: external_exports.number().min(0).max(1).describe("How confident are you this is correct (0-1)"),
|
|
3716
3802
|
tags: external_exports.array(external_exports.string()).describe("Relevant tags for categorization"),
|
|
3717
3803
|
entityRef: external_exports.string().optional().nullable().describe("If about an entity, its normalized name (e.g. person-jane-doe)"),
|
|
3718
3804
|
promptedByQuestion: external_exports.string().optional().nullable().describe("Optional proactive follow-up question that surfaced this fact."),
|
|
3719
|
-
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
|
+
)
|
|
3720
3809
|
});
|
|
3721
3810
|
var EntityMentionSchema = external_exports.object({
|
|
3722
3811
|
name: external_exports.string().describe("Normalized entity name (e.g. jane-doe, acme-corp, my-project)"),
|
|
@@ -4464,6 +4553,67 @@ function parallelEfficiency(group) {
|
|
|
4464
4553
|
return Math.round(idealMs / group.wallMs * 100);
|
|
4465
4554
|
}
|
|
4466
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
|
+
|
|
4467
4617
|
// ../remnic-core/src/extraction.ts
|
|
4468
4618
|
var PROACTIVE_MIN_CONFIDENCE = 0.8;
|
|
4469
4619
|
function normalizeQuestion(question) {
|
|
@@ -4554,8 +4704,9 @@ var ExtractionEngine = class {
|
|
|
4554
4704
|
return shouldAssumeOpenAiChatCompletions(this.config.openaiBaseUrl);
|
|
4555
4705
|
}
|
|
4556
4706
|
sanitizeExtractionResult(result, messageTimestamp) {
|
|
4707
|
+
const proceduralOn = this.config.procedural?.enabled === true;
|
|
4557
4708
|
const ts = messageTimestamp ?? /* @__PURE__ */ new Date();
|
|
4558
|
-
const facts = result.facts.map((fact) => {
|
|
4709
|
+
const facts = result.facts.filter((fact) => proceduralOn || fact.category !== "procedure").map((fact) => {
|
|
4559
4710
|
const sanitized = sanitizeMemoryContent(fact.content);
|
|
4560
4711
|
if (!sanitized.clean) {
|
|
4561
4712
|
log.warn(`extraction fact sanitized; violations=${sanitized.violations.join(", ")}`);
|
|
@@ -4582,7 +4733,8 @@ var ExtractionEngine = class {
|
|
|
4582
4733
|
promptedByQuestion: typeof f?.promptedByQuestion === "string" ? f.promptedByQuestion : void 0,
|
|
4583
4734
|
structuredAttributes: f?.structuredAttributes && typeof f.structuredAttributes === "object" && !Array.isArray(f.structuredAttributes) ? Object.fromEntries(
|
|
4584
4735
|
Object.entries(f.structuredAttributes).filter(([k, v]) => typeof k === "string" && typeof v === "string")
|
|
4585
|
-
) : void 0
|
|
4736
|
+
) : void 0,
|
|
4737
|
+
procedureSteps: Array.isArray(f?.procedureSteps) ? normalizeProcedureSteps(f.procedureSteps) : void 0
|
|
4586
4738
|
})).filter((f) => f.content.length > 0) : [];
|
|
4587
4739
|
const questions = Array.isArray(parsed?.questions) ? parsed.questions.map((q) => {
|
|
4588
4740
|
if (typeof q === "string") return { question: q, context: "", priority: 0.5 };
|
|
@@ -5425,7 +5577,9 @@ Respond with valid JSON matching this schema:
|
|
|
5425
5577
|
"principle",
|
|
5426
5578
|
"commitment",
|
|
5427
5579
|
"moment",
|
|
5428
|
-
"skill"
|
|
5580
|
+
"skill",
|
|
5581
|
+
"rule",
|
|
5582
|
+
"procedure"
|
|
5429
5583
|
]);
|
|
5430
5584
|
const allowedEntityTypes = /* @__PURE__ */ new Set([
|
|
5431
5585
|
"person",
|
|
@@ -5482,6 +5636,7 @@ Memory categories:
|
|
|
5482
5636
|
- moment: Emotionally significant events or milestones (e.g., "first successful deployment of engram")
|
|
5483
5637
|
- skill: Capabilities the user or agent has demonstrated (e.g., "user is proficient with Kubernetes")${this.config.causalRuleExtractionEnabled ? `
|
|
5484
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.
|
|
5485
5640
|
|
|
5486
5641
|
Rules:
|
|
5487
5642
|
- Only extract genuinely NEW information worth remembering across sessions
|
|
@@ -6691,6 +6846,8 @@ var CATEGORY_BOOSTS = {
|
|
|
6691
6846
|
// Durable rules/values
|
|
6692
6847
|
rule: 0.11,
|
|
6693
6848
|
// Causal IF→THEN rules
|
|
6849
|
+
procedure: 0.1,
|
|
6850
|
+
// Repeatable workflows (issue #519)
|
|
6694
6851
|
preference: 0.1,
|
|
6695
6852
|
// User preferences matter
|
|
6696
6853
|
commitment: 0.1,
|
|
@@ -6996,6 +7153,18 @@ function enforceMaxCacheSize(cache) {
|
|
|
6996
7153
|
}
|
|
6997
7154
|
}
|
|
6998
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
|
+
}
|
|
6999
7168
|
async function judgeFactDurability(candidates, config, localLlm, fallbackLlm, cache) {
|
|
7000
7169
|
const startMs = Date.now();
|
|
7001
7170
|
const verdicts = /* @__PURE__ */ new Map();
|
|
@@ -7176,6 +7345,156 @@ function createVerdictCache() {
|
|
|
7176
7345
|
return /* @__PURE__ */ new Map();
|
|
7177
7346
|
}
|
|
7178
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
|
+
|
|
7179
7498
|
// ../remnic-core/src/reconstruct.ts
|
|
7180
7499
|
function findUnresolvedEntityRefs(recalledSnippets, recalledEntityRefs, knownEntities) {
|
|
7181
7500
|
const refSet = new Set(recalledEntityRefs.map((r) => r.toLowerCase()));
|
|
@@ -8713,6 +9032,26 @@ var QmdDaemonSession = class {
|
|
|
8713
9032
|
this.buffer = "";
|
|
8714
9033
|
}
|
|
8715
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
|
+
}
|
|
8716
9055
|
function parseMcpSearchResult(result, transport = "daemon") {
|
|
8717
9056
|
const resultObj = result;
|
|
8718
9057
|
if (!resultObj) return [];
|
|
@@ -8745,6 +9084,15 @@ function parseMcpSearchResult(result, transport = "daemon") {
|
|
|
8745
9084
|
const textResults = parsed?.results ?? parsed?.documents;
|
|
8746
9085
|
if (Array.isArray(textResults)) pushDocs(textResults);
|
|
8747
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
|
+
}
|
|
8748
9096
|
}
|
|
8749
9097
|
}
|
|
8750
9098
|
}
|
|
@@ -8800,9 +9148,22 @@ var QmdClient = class _QmdClient {
|
|
|
8800
9148
|
collection;
|
|
8801
9149
|
maxResults;
|
|
8802
9150
|
available = null;
|
|
8803
|
-
|
|
9151
|
+
_lastUpdateFailAtMs = null;
|
|
8804
9152
|
lastEmbedFailAtMs = null;
|
|
8805
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
|
+
}
|
|
8806
9167
|
updateTimeoutMs;
|
|
8807
9168
|
updateMinIntervalMs;
|
|
8808
9169
|
slowLog;
|
|
@@ -9419,13 +9780,13 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9419
9780
|
return [];
|
|
9420
9781
|
}
|
|
9421
9782
|
}
|
|
9422
|
-
async update() {
|
|
9423
|
-
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false });
|
|
9783
|
+
async update(signal) {
|
|
9784
|
+
await this.runUpdateForCollection(this.collection, { perCollectionThrottle: false }, signal);
|
|
9424
9785
|
}
|
|
9425
9786
|
async updateCollection(collection) {
|
|
9426
9787
|
await this.runUpdateForCollection(collection, { perCollectionThrottle: true });
|
|
9427
9788
|
}
|
|
9428
|
-
async runUpdateForCollection(collection, options) {
|
|
9789
|
+
async runUpdateForCollection(collection, options, signal) {
|
|
9429
9790
|
if (this.available === false) return;
|
|
9430
9791
|
const name = collection.trim();
|
|
9431
9792
|
if (!name) return;
|
|
@@ -9451,7 +9812,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9451
9812
|
log.debug("QMD update: suppressed due to min-interval gate");
|
|
9452
9813
|
return;
|
|
9453
9814
|
}
|
|
9454
|
-
if (this.
|
|
9815
|
+
if (this._lastUpdateFailAtMs && now - this._lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS) {
|
|
9455
9816
|
log.debug("QMD update: suppressed due to recent failures (backoff)");
|
|
9456
9817
|
return;
|
|
9457
9818
|
}
|
|
@@ -9472,7 +9833,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9472
9833
|
);
|
|
9473
9834
|
}
|
|
9474
9835
|
const startedAtMs = Date.now();
|
|
9475
|
-
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs);
|
|
9836
|
+
await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs, signal);
|
|
9476
9837
|
const durationMs = Date.now() - startedAtMs;
|
|
9477
9838
|
if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
|
|
9478
9839
|
log.warn(`SLOW QMD update: durationMs=${durationMs}`);
|
|
@@ -9492,7 +9853,7 @@ ${stderr}`.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
|
9492
9853
|
globalState.lastUpdateFailByCollectionMs[name] = at;
|
|
9493
9854
|
globalState.lastGlobalUpdateFailAtMs = at;
|
|
9494
9855
|
} else {
|
|
9495
|
-
this.
|
|
9856
|
+
this._lastUpdateFailAtMs = at;
|
|
9496
9857
|
globalState.lastGlobalUpdateFailAtMs = at;
|
|
9497
9858
|
}
|
|
9498
9859
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -12621,6 +12982,7 @@ import { mkdir as mkdir8, readFile as readFile10, rename, rm as rm2, stat as sta
|
|
|
12621
12982
|
import path15 from "path";
|
|
12622
12983
|
var DAY_SUMMARY_CRON_ID = "engram-day-summary";
|
|
12623
12984
|
var GOVERNANCE_CRON_ID = "engram-nightly-governance";
|
|
12985
|
+
var PROCEDURAL_MINING_CRON_ID = "engram-procedural-mining";
|
|
12624
12986
|
async function acquireCronJobsLock(jobsPath) {
|
|
12625
12987
|
const lockPath2 = `${jobsPath}.lock`;
|
|
12626
12988
|
const start = Date.now();
|
|
@@ -12733,13 +13095,37 @@ async function ensureNightlyGovernanceCron(jobsPath, options) {
|
|
|
12733
13095
|
delivery: { mode: "none" }
|
|
12734
13096
|
}));
|
|
12735
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
|
+
}
|
|
12736
13122
|
|
|
12737
13123
|
// ../remnic-core/src/lifecycle.ts
|
|
12738
13124
|
var DEFAULT_POLICY = {
|
|
12739
13125
|
promoteHeatThreshold: 0.55,
|
|
12740
13126
|
staleDecayThreshold: 0.65,
|
|
12741
13127
|
archiveDecayThreshold: 0.85,
|
|
12742
|
-
protectedCategories: ["decision", "principle", "commitment", "preference"]
|
|
13128
|
+
protectedCategories: ["decision", "principle", "commitment", "preference", "procedure"]
|
|
12743
13129
|
};
|
|
12744
13130
|
function clamp01(value) {
|
|
12745
13131
|
if (!Number.isFinite(value)) return 0;
|
|
@@ -18167,97 +18553,6 @@ async function readRecentEntityTranscriptEntries(transcriptEntriesPromise, recen
|
|
|
18167
18553
|
}
|
|
18168
18554
|
var entityRecentTranscriptLookbackHours = RECENT_TRANSCRIPT_LOOKBACK_HOURS;
|
|
18169
18555
|
|
|
18170
|
-
// ../remnic-core/src/intent.ts
|
|
18171
|
-
var GOAL_PATTERNS = [
|
|
18172
|
-
{ re: /\b(debug(?:s|ged|ging)?|fix(?:es|ed|ing)?|error(?:s)?|incident(?:s)?|outage(?:s)?|failure(?:s)?)\b/i, goal: "stabilize" },
|
|
18173
|
-
{ re: /\b(deploy(?:s|ed|ing)?|release(?:s|d|ing)?|ship(?:s|ped|ping)?|publish(?:es|ed|ing)?)\b/i, goal: "release" },
|
|
18174
|
-
{ re: /\b(plan(?:s|ned|ning)?|roadmap(?:s)?|strateg(?:y|ies)|design(?:s|ed|ing)?)\b/i, goal: "plan" },
|
|
18175
|
-
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|security|hardening)\b/i, goal: "review" },
|
|
18176
|
-
{ re: /\b(sales|deal|customer|client|prospect)\b/i, goal: "close_deal" }
|
|
18177
|
-
];
|
|
18178
|
-
var ACTION_PATTERNS = [
|
|
18179
|
-
{ re: /\b(review(?:s|ed|ing)?|audit(?:s|ed|ing)?|inspect(?:s|ed|ing)?|check(?:s|ed|ing)?)\b/i, action: "review" },
|
|
18180
|
-
{ re: /\b(plan(?:s|ned|ning)?|design(?:s|ed|ing)?|brainstorm(?:s|ed|ing)?|spec(?:s)?)\b/i, action: "plan" },
|
|
18181
|
-
{ 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" },
|
|
18182
|
-
{ re: /\b(summariz(?:e|es|ed|ing)|recap(?:s|ped|ping)?|what happened|timeline)\b/i, action: "summarize" },
|
|
18183
|
-
{ re: /\b(decid(?:e|es|ed|ing)|decision(?:s)?|cho(?:ose|oses|osing)|chose|chosen)\b/i, action: "decide" }
|
|
18184
|
-
];
|
|
18185
|
-
var ENTITY_PATTERNS = [
|
|
18186
|
-
{ re: /\b(pr|pull request|branch|repo|github|ci|workflow)\b/i, entityType: "repo" },
|
|
18187
|
-
{ re: /\b(discord|slack|channel|gateway|agent)\b/i, entityType: "ops" },
|
|
18188
|
-
{ re: /\b(customer|client|deal|lead|account)\b/i, entityType: "client" },
|
|
18189
|
-
{ re: /\b(model|llm|qmd|embedding|retrieval|memory)\b/i, entityType: "ai" },
|
|
18190
|
-
{ re: /\b(doc|readme|docs|changelog)\b/i, entityType: "docs" }
|
|
18191
|
-
];
|
|
18192
|
-
function normalizeTextInput(input) {
|
|
18193
|
-
return typeof input === "string" ? input : "";
|
|
18194
|
-
}
|
|
18195
|
-
function inferIntentFromText(text) {
|
|
18196
|
-
const safeText = normalizeTextInput(text);
|
|
18197
|
-
const goal = GOAL_PATTERNS.find((p) => p.re.test(safeText))?.goal ?? "unknown";
|
|
18198
|
-
const actionType = ACTION_PATTERNS.find((p) => p.re.test(safeText))?.action ?? "unknown";
|
|
18199
|
-
const entityTypes = Array.from(
|
|
18200
|
-
new Set(ENTITY_PATTERNS.filter((p) => p.re.test(safeText)).map((p) => p.entityType))
|
|
18201
|
-
);
|
|
18202
|
-
return {
|
|
18203
|
-
goal,
|
|
18204
|
-
actionType,
|
|
18205
|
-
entityTypes
|
|
18206
|
-
};
|
|
18207
|
-
}
|
|
18208
|
-
function intentCompatibilityScore(queryIntent, memoryIntent) {
|
|
18209
|
-
const queryHasSignal = queryIntent.goal !== "unknown" || queryIntent.actionType !== "unknown" || queryIntent.entityTypes.length > 0;
|
|
18210
|
-
const memoryHasSignal = memoryIntent.goal !== "unknown" || memoryIntent.actionType !== "unknown" || memoryIntent.entityTypes.length > 0;
|
|
18211
|
-
if (!queryHasSignal || !memoryHasSignal) return 0;
|
|
18212
|
-
let score = 0;
|
|
18213
|
-
if (queryIntent.goal !== "unknown" && memoryIntent.goal !== "unknown" && queryIntent.goal === memoryIntent.goal) {
|
|
18214
|
-
score += 0.5;
|
|
18215
|
-
}
|
|
18216
|
-
if (queryIntent.actionType !== "unknown" && memoryIntent.actionType !== "unknown" && queryIntent.actionType === memoryIntent.actionType) {
|
|
18217
|
-
score += 0.3;
|
|
18218
|
-
}
|
|
18219
|
-
const overlap = queryIntent.entityTypes.filter((et) => memoryIntent.entityTypes.includes(et)).length;
|
|
18220
|
-
if (overlap > 0) {
|
|
18221
|
-
const denom = Math.max(queryIntent.entityTypes.length, memoryIntent.entityTypes.length, 1);
|
|
18222
|
-
score += 0.2 * (overlap / denom);
|
|
18223
|
-
}
|
|
18224
|
-
return Math.max(0, Math.min(1, score));
|
|
18225
|
-
}
|
|
18226
|
-
function planRecallMode(prompt) {
|
|
18227
|
-
const p = normalizeTextInput(prompt).trim();
|
|
18228
|
-
let ackCandidate = p;
|
|
18229
|
-
while (ackCandidate.length > 0) {
|
|
18230
|
-
const ch = ackCandidate.charCodeAt(ackCandidate.length - 1);
|
|
18231
|
-
const isDigit = ch >= 48 && ch <= 57;
|
|
18232
|
-
const isUpper = ch >= 65 && ch <= 90;
|
|
18233
|
-
const isLower = ch >= 97 && ch <= 122;
|
|
18234
|
-
if (isDigit || isUpper || isLower) break;
|
|
18235
|
-
ackCandidate = ackCandidate.slice(0, -1);
|
|
18236
|
-
}
|
|
18237
|
-
ackCandidate = ackCandidate.trim();
|
|
18238
|
-
if (p.length === 0) return "no_recall";
|
|
18239
|
-
if (/\b(timeline|sequence|history|what happened|chain of events|root cause)\b/i.test(p)) {
|
|
18240
|
-
return "graph_mode";
|
|
18241
|
-
}
|
|
18242
|
-
if (p.length <= 18 && /^(ok|okay|kk|thanks|thx|got it|sounds good|yep|yes|nope|no|done|cool|works)$/i.test(ackCandidate)) {
|
|
18243
|
-
return "no_recall";
|
|
18244
|
-
}
|
|
18245
|
-
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())) {
|
|
18246
|
-
return "full";
|
|
18247
|
-
}
|
|
18248
|
-
if (p.length <= 100 && /^(check|reload|restart|run|verify|show|status|sync|update|open|close|set|enable|disable|fix|patch)\b/i.test(p)) {
|
|
18249
|
-
return "minimal";
|
|
18250
|
-
}
|
|
18251
|
-
return "full";
|
|
18252
|
-
}
|
|
18253
|
-
function hasBroadGraphIntent(prompt) {
|
|
18254
|
-
const p = normalizeTextInput(prompt).trim().toLowerCase();
|
|
18255
|
-
if (!p) return false;
|
|
18256
|
-
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(
|
|
18257
|
-
p
|
|
18258
|
-
);
|
|
18259
|
-
}
|
|
18260
|
-
|
|
18261
18556
|
// ../remnic-core/src/recall-query-policy.ts
|
|
18262
18557
|
var DEFAULT_STOPWORDS = /* @__PURE__ */ new Set([
|
|
18263
18558
|
"the",
|
|
@@ -18552,11 +18847,11 @@ function computeCompressionGuidelineCandidate(events, options = {}) {
|
|
|
18552
18847
|
notes
|
|
18553
18848
|
};
|
|
18554
18849
|
}
|
|
18555
|
-
const
|
|
18850
|
+
const successRate2 = summary.outcomes.applied / summary.total;
|
|
18556
18851
|
const failureRate = summary.outcomes.failed / summary.total;
|
|
18557
18852
|
const qualitySeen = summary.quality.good + summary.quality.poor;
|
|
18558
18853
|
const qualitySignal = qualitySeen > 0 ? (summary.quality.good - summary.quality.poor) / qualitySeen : 0;
|
|
18559
|
-
const rawDelta = clamp((
|
|
18854
|
+
const rawDelta = clamp((successRate2 - failureRate) * 0.12 + qualitySignal * 0.06, -MAX_DELTA, MAX_DELTA);
|
|
18560
18855
|
const delta = roundDelta(rawDelta);
|
|
18561
18856
|
const direction = directionForDelta(delta);
|
|
18562
18857
|
if (direction === "decrease" && summary.outcomes.failed > summary.outcomes.applied) {
|
|
@@ -20872,14 +21167,75 @@ async function getWorkProductLedgerStatus(options) {
|
|
|
20872
21167
|
};
|
|
20873
21168
|
}
|
|
20874
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
|
+
|
|
20875
21235
|
// ../remnic-core/src/replay/types.ts
|
|
20876
21236
|
var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
|
|
20877
21237
|
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
20878
|
-
var ISO_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
20879
21238
|
var REPLAY_UNKNOWN_SESSION_KEY = "replay:unknown";
|
|
20880
|
-
function normalizeIsoForComparison(value) {
|
|
20881
|
-
return value.includes(".") ? value : value.replace("Z", ".000Z");
|
|
20882
|
-
}
|
|
20883
21239
|
function isReplaySource(value) {
|
|
20884
21240
|
return typeof value === "string" && VALID_SOURCES.has(value);
|
|
20885
21241
|
}
|
|
@@ -20892,12 +21248,7 @@ function normalizeReplaySessionKey(value) {
|
|
|
20892
21248
|
return trimmed.length > 0 ? trimmed : REPLAY_UNKNOWN_SESSION_KEY;
|
|
20893
21249
|
}
|
|
20894
21250
|
function parseIsoTimestamp(value) {
|
|
20895
|
-
|
|
20896
|
-
const ts = Date.parse(value);
|
|
20897
|
-
if (!Number.isFinite(ts)) return null;
|
|
20898
|
-
const roundTrip = new Date(ts).toISOString();
|
|
20899
|
-
if (roundTrip !== normalizeIsoForComparison(value)) return null;
|
|
20900
|
-
return ts;
|
|
21251
|
+
return parseIsoUtcTimestamp(value);
|
|
20901
21252
|
}
|
|
20902
21253
|
function validateReplayTurn(turn, index) {
|
|
20903
21254
|
const issues = [];
|
|
@@ -24960,7 +25311,8 @@ var DEFAULT_CATEGORIES = [
|
|
|
24960
25311
|
"commitment",
|
|
24961
25312
|
"moment",
|
|
24962
25313
|
"skill",
|
|
24963
|
-
"rule"
|
|
25314
|
+
"rule",
|
|
25315
|
+
"procedure"
|
|
24964
25316
|
];
|
|
24965
25317
|
function normalizeNamespace(namespace) {
|
|
24966
25318
|
return namespace.trim();
|
|
@@ -25065,7 +25417,8 @@ var INLINE_ALLOWED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
25065
25417
|
"commitment",
|
|
25066
25418
|
"moment",
|
|
25067
25419
|
"skill",
|
|
25068
|
-
"rule"
|
|
25420
|
+
"rule",
|
|
25421
|
+
"procedure"
|
|
25069
25422
|
]);
|
|
25070
25423
|
var SECRET_PATTERNS = [
|
|
25071
25424
|
/\bsk-[A-Za-z0-9]{16,}\b/,
|
|
@@ -25664,15 +26017,24 @@ var NamespaceSearchRouter = class {
|
|
|
25664
26017
|
);
|
|
25665
26018
|
return mergeNamespaceSearchResults(resultsByNamespace, maxResults);
|
|
25666
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
|
+
*/
|
|
25667
26027
|
async updateNamespaces(namespaces) {
|
|
25668
26028
|
const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
|
|
25669
|
-
await Promise.all(
|
|
26029
|
+
const results = await Promise.all(
|
|
25670
26030
|
unique.map(async (namespace) => {
|
|
25671
26031
|
const record = await this.backendRecordFor(namespace);
|
|
25672
|
-
if (!record.available || record.collectionState === "missing") return;
|
|
26032
|
+
if (!record.available || record.collectionState === "missing") return 0;
|
|
25673
26033
|
await record.backend.update();
|
|
26034
|
+
return 1;
|
|
25674
26035
|
})
|
|
25675
26036
|
);
|
|
26037
|
+
return results.reduce((sum, v) => sum + v, 0);
|
|
25676
26038
|
}
|
|
25677
26039
|
async embedNamespaces(namespaces) {
|
|
25678
26040
|
const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
|
|
@@ -25688,6 +26050,10 @@ var NamespaceSearchRouter = class {
|
|
|
25688
26050
|
const record = await this.backendRecordFor(namespace);
|
|
25689
26051
|
return record.collectionState;
|
|
25690
26052
|
}
|
|
26053
|
+
/** Clear cached backend records so the next access re-probes availability. */
|
|
26054
|
+
clearCache() {
|
|
26055
|
+
this.cache.clear();
|
|
26056
|
+
}
|
|
25691
26057
|
async backendRecordFor(namespace) {
|
|
25692
26058
|
const key = namespace.trim() || this.config.defaultNamespace;
|
|
25693
26059
|
const existing = this.cache.get(key);
|
|
@@ -27107,7 +27473,8 @@ function parseMemoryIntentSnapshot(value) {
|
|
|
27107
27473
|
actionType: typeof candidate.actionType === "string" ? candidate.actionType : "unknown",
|
|
27108
27474
|
entityTypes: Array.isArray(candidate.entityTypes) ? candidate.entityTypes.filter(
|
|
27109
27475
|
(item) => typeof item === "string"
|
|
27110
|
-
) : []
|
|
27476
|
+
) : [],
|
|
27477
|
+
taskInitiation: candidate.taskInitiation === true
|
|
27111
27478
|
};
|
|
27112
27479
|
}
|
|
27113
27480
|
function buildQmdIntentHint(intent) {
|
|
@@ -27121,6 +27488,9 @@ function buildQmdIntentHint(intent) {
|
|
|
27121
27488
|
if (intent.entityTypes.length > 0) {
|
|
27122
27489
|
parts.push(`entities:${intent.entityTypes.join(",")}`);
|
|
27123
27490
|
}
|
|
27491
|
+
if (intent.taskInitiation === true) {
|
|
27492
|
+
parts.push("task_initiation");
|
|
27493
|
+
}
|
|
27124
27494
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
27125
27495
|
}
|
|
27126
27496
|
function parseQmdRecallResults(value) {
|
|
@@ -27296,6 +27666,41 @@ var Orchestrator = class _Orchestrator {
|
|
|
27296
27666
|
// Initialization gate: recall() awaits this before proceeding
|
|
27297
27667
|
initPromise = null;
|
|
27298
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
|
+
}
|
|
27299
27704
|
/** Set per-session workspace for the next recall() call (compaction reset). @internal */
|
|
27300
27705
|
setRecallWorkspaceOverride(sessionKey, dir) {
|
|
27301
27706
|
this._recallWorkspaceOverrides.set(sessionKey, dir);
|
|
@@ -27686,100 +28091,173 @@ var Orchestrator = class _Orchestrator {
|
|
|
27686
28091
|
return this.fastLlm;
|
|
27687
28092
|
}
|
|
27688
28093
|
async initialize() {
|
|
27689
|
-
|
|
27690
|
-
|
|
27691
|
-
logger: (message) => log.info(message)
|
|
28094
|
+
this.deferredReady = new Promise((resolve) => {
|
|
28095
|
+
this.resolveDeferredReady = resolve;
|
|
27692
28096
|
});
|
|
27693
|
-
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
|
|
27701
|
-
|
|
27702
|
-
|
|
27703
|
-
|
|
27704
|
-
|
|
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
|
+
);
|
|
27705
28134
|
}
|
|
27706
|
-
|
|
27707
|
-
|
|
27708
|
-
|
|
27709
|
-
|
|
27710
|
-
|
|
27711
|
-
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
|
|
27719
|
-
const stateDir2 = path43.join(this.config.memoryDir, "state");
|
|
27720
|
-
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
27721
|
-
await this.contentHashIndex.load();
|
|
27722
|
-
log.info(
|
|
27723
|
-
`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`
|
|
27724
|
-
);
|
|
27725
|
-
}
|
|
27726
|
-
await this.transcript.initialize();
|
|
27727
|
-
await this.summarizer.initialize();
|
|
27728
|
-
if (this.sharedContext) {
|
|
27729
|
-
await this.sharedContext.ensureStructure();
|
|
27730
|
-
}
|
|
27731
|
-
if (this.compounding) {
|
|
27732
|
-
await this.compounding.ensureDirs();
|
|
27733
|
-
}
|
|
27734
|
-
if (this.resolveInit) {
|
|
27735
|
-
this.resolveInit();
|
|
27736
|
-
this.resolveInit = null;
|
|
27737
|
-
log.info("init gate opened (essential state loaded)");
|
|
27738
|
-
}
|
|
27739
|
-
{
|
|
27740
|
-
const available = await this.qmd.probe();
|
|
27741
|
-
if (available) {
|
|
27742
|
-
log.info(`Search backend: available ${this.qmd.debugStatus()}`);
|
|
27743
|
-
const namespaces = this.config.namespacesEnabled ? this.configuredNamespaces() : [this.config.defaultNamespace];
|
|
27744
|
-
const states = await Promise.all(
|
|
27745
|
-
namespaces.map(async (namespace) => ({
|
|
27746
|
-
namespace,
|
|
27747
|
-
state: this.config.namespacesEnabled ? await this.namespaceSearchRouter.ensureNamespaceCollection(
|
|
27748
|
-
namespace
|
|
27749
|
-
) : await this.qmd.ensureCollection(this.config.memoryDir)
|
|
27750
|
-
}))
|
|
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}`
|
|
27751
28148
|
);
|
|
27752
|
-
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
27756
|
-
this.
|
|
27757
|
-
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27761
|
-
|
|
27762
|
-
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
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);
|
|
27768
28167
|
}
|
|
27769
|
-
|
|
27770
|
-
|
|
27771
|
-
|
|
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();
|
|
27772
28187
|
log.warn(
|
|
27773
|
-
|
|
28188
|
+
"Search collection missing for Remnic memory store; disabling search retrieval for this runtime (fallback retrieval remains enabled)"
|
|
28189
|
+
);
|
|
28190
|
+
} else if (defaultState2 === "unknown") {
|
|
28191
|
+
log.warn(
|
|
28192
|
+
"Search collection check unavailable; keeping search retrieval enabled for fail-open behavior"
|
|
28193
|
+
);
|
|
28194
|
+
} else if (defaultState2 === "skipped") {
|
|
28195
|
+
log.debug(
|
|
28196
|
+
"Search collection check skipped (remote or daemon-only mode)"
|
|
27774
28197
|
);
|
|
27775
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
|
+
}
|
|
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()}`);
|
|
27776
28211
|
}
|
|
27777
|
-
}
|
|
27778
|
-
log.
|
|
27779
|
-
}
|
|
27780
|
-
|
|
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;
|
|
28232
|
+
}
|
|
28233
|
+
if (this.resolveDeferredReady) {
|
|
28234
|
+
this.resolveDeferredReady();
|
|
28235
|
+
this.resolveDeferredReady = null;
|
|
27781
28236
|
}
|
|
28237
|
+
throw err;
|
|
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;
|
|
27782
28259
|
}
|
|
28260
|
+
if (signal.aborted) return;
|
|
27783
28261
|
const warmupPromises = [];
|
|
27784
28262
|
if (this.qmd.isAvailable()) {
|
|
27785
28263
|
const warmupNs = this.config.defaultNamespace;
|
|
@@ -27804,68 +28282,173 @@ var Orchestrator = class _Orchestrator {
|
|
|
27804
28282
|
);
|
|
27805
28283
|
}
|
|
27806
28284
|
await Promise.all(warmupPromises);
|
|
28285
|
+
if (signal.aborted) return;
|
|
28286
|
+
const cacheWarmups = [];
|
|
27807
28287
|
if (this.config.knowledgeIndexEnabled) {
|
|
27808
|
-
(
|
|
27809
|
-
|
|
27810
|
-
|
|
27811
|
-
|
|
27812
|
-
|
|
27813
|
-
|
|
27814
|
-
|
|
27815
|
-
|
|
27816
|
-
|
|
27817
|
-
|
|
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
|
+
);
|
|
27818
28299
|
}
|
|
27819
|
-
this.storage.readAllMemories().
|
|
27820
|
-
})
|
|
27821
|
-
|
|
27822
|
-
|
|
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;
|
|
27823
28308
|
if (this.config.conversationIndexEnabled && this.conversationIndexBackend) {
|
|
27824
|
-
|
|
27825
|
-
|
|
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}`);
|
|
27826
28323
|
this.config.conversationIndexEnabled = false;
|
|
27827
28324
|
}
|
|
27828
|
-
if (init.logLevel === "info") {
|
|
27829
|
-
log.info(init.message);
|
|
27830
|
-
} else if (init.logLevel === "warn") {
|
|
27831
|
-
log.warn(init.message);
|
|
27832
|
-
} else {
|
|
27833
|
-
log.debug(init.message);
|
|
27834
|
-
}
|
|
27835
28325
|
}
|
|
27836
|
-
|
|
28326
|
+
if (signal.aborted) return;
|
|
27837
28327
|
if (this.config.localLlmEnabled) {
|
|
27838
|
-
await this.validateLocalLlmModel();
|
|
27839
|
-
}
|
|
27840
|
-
if (this.config.compactionResetEnabled) {
|
|
27841
28328
|
try {
|
|
27842
|
-
|
|
27843
|
-
const files = await readdir14(wsDir).catch(() => []);
|
|
27844
|
-
for (const f of files) {
|
|
27845
|
-
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
27846
|
-
const fp = path43.join(wsDir, f);
|
|
27847
|
-
const s = await stat10(fp).catch(() => null);
|
|
27848
|
-
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
27849
|
-
await unlink7(fp).catch(() => {
|
|
27850
|
-
});
|
|
27851
|
-
log.debug(`initialize: removed stale compaction signal ${f}`);
|
|
27852
|
-
}
|
|
27853
|
-
}
|
|
28329
|
+
await this.validateLocalLlmModel();
|
|
27854
28330
|
} catch (err) {
|
|
27855
|
-
log.
|
|
28331
|
+
log.error(`Local LLM validation failed (non-fatal): ${err}`);
|
|
27856
28332
|
}
|
|
27857
28333
|
}
|
|
27858
|
-
|
|
28334
|
+
if (signal.aborted) return;
|
|
27859
28335
|
if (this.config.daySummaryEnabled) {
|
|
27860
|
-
|
|
28336
|
+
try {
|
|
28337
|
+
await this.autoRegisterDaySummaryCron();
|
|
28338
|
+
} catch (err) {
|
|
27861
28339
|
log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);
|
|
27862
|
-
}
|
|
28340
|
+
}
|
|
27863
28341
|
}
|
|
27864
28342
|
if (this.config.nightlyGovernanceCronAutoRegister) {
|
|
27865
|
-
|
|
28343
|
+
try {
|
|
28344
|
+
await this.autoRegisterNightlyGovernanceCron();
|
|
28345
|
+
} catch (err) {
|
|
27866
28346
|
log.debug(`nightly governance cron auto-register failed (non-fatal): ${err}`);
|
|
27867
|
-
}
|
|
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
|
+
}
|
|
27868
28450
|
}
|
|
28451
|
+
return true;
|
|
27869
28452
|
}
|
|
27870
28453
|
/**
|
|
27871
28454
|
* Auto-register the engram-day-summary cron job in OpenClaw if it doesn't exist.
|
|
@@ -27917,6 +28500,26 @@ var Orchestrator = class _Orchestrator {
|
|
|
27917
28500
|
log.debug(`nightly governance cron auto-register error: ${err}`);
|
|
27918
28501
|
}
|
|
27919
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
|
+
}
|
|
27920
28523
|
async applyBehaviorRuntimePolicy(state) {
|
|
27921
28524
|
const result = await this.policyRuntime.applyFromBehaviorState(state);
|
|
27922
28525
|
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
@@ -30380,7 +30983,7 @@ ${trimmedBody}`;
|
|
|
30380
30983
|
return null;
|
|
30381
30984
|
}
|
|
30382
30985
|
try {
|
|
30383
|
-
const { retrieveCausalChains } = await import("./causal-retrieval-
|
|
30986
|
+
const { retrieveCausalChains } = await import("./causal-retrieval-3BKBXVXD.js");
|
|
30384
30987
|
const section = await retrieveCausalChains({
|
|
30385
30988
|
memoryDir: this.config.memoryDir,
|
|
30386
30989
|
causalTrajectoryStoreDir: this.config.causalTrajectoryStoreDir,
|
|
@@ -31316,6 +31919,22 @@ ${formatted}`;
|
|
|
31316
31919
|
});
|
|
31317
31920
|
return section;
|
|
31318
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
|
+
})();
|
|
31319
31938
|
const compoundingPromise = observeEnrichmentPromise(
|
|
31320
31939
|
(async () => {
|
|
31321
31940
|
const t0 = Date.now();
|
|
@@ -31380,6 +31999,7 @@ ${formatted}`;
|
|
|
31380
31999
|
causalTrajectorySection,
|
|
31381
32000
|
cmcCausalChainsSection,
|
|
31382
32001
|
calibrationSection,
|
|
32002
|
+
procedureRecallSection,
|
|
31383
32003
|
trustZoneSection,
|
|
31384
32004
|
verifiedRecallSection,
|
|
31385
32005
|
verifiedRulesSection,
|
|
@@ -31401,6 +32021,7 @@ ${formatted}`;
|
|
|
31401
32021
|
["causalTraj", causalTrajectoryPromise],
|
|
31402
32022
|
["cmc", cmcRetrievalPromise],
|
|
31403
32023
|
["calibration", calibrationPromise],
|
|
32024
|
+
["procedureRecall", procedureRecallPromise],
|
|
31404
32025
|
["trustZone", trustZonePromise],
|
|
31405
32026
|
["verifiedRecall", verifiedRecallPromise],
|
|
31406
32027
|
["verifiedRules", verifiedRulesPromise],
|
|
@@ -31506,6 +32127,13 @@ ${profile}`
|
|
|
31506
32127
|
calibrationSection
|
|
31507
32128
|
);
|
|
31508
32129
|
}
|
|
32130
|
+
if (procedureRecallSection) {
|
|
32131
|
+
this.appendRecallSection(
|
|
32132
|
+
sectionBuckets,
|
|
32133
|
+
"procedure-recall",
|
|
32134
|
+
procedureRecallSection
|
|
32135
|
+
);
|
|
32136
|
+
}
|
|
31509
32137
|
if (identityContinuity) {
|
|
31510
32138
|
this.appendRecallSection(
|
|
31511
32139
|
sectionBuckets,
|
|
@@ -32550,6 +33178,87 @@ _Context: ${topQuestion.context}_`
|
|
|
32550
33178
|
}
|
|
32551
33179
|
}
|
|
32552
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
|
+
}
|
|
32553
33262
|
async observeSessionHeartbeat(sessionKey, options = {}) {
|
|
32554
33263
|
if (this.config.sessionObserverEnabled !== true) return;
|
|
32555
33264
|
if (!sessionKey || sessionKey.length === 0) return;
|
|
@@ -32616,7 +33325,8 @@ _Context: ${topQuestion.context}_`
|
|
|
32616
33325
|
skipCharThreshold: options.skipCharThreshold ?? false,
|
|
32617
33326
|
deadlineMs: options.extractionDeadlineMs,
|
|
32618
33327
|
bufferKey,
|
|
32619
|
-
abortSignal: options.abortSignal
|
|
33328
|
+
abortSignal: options.abortSignal,
|
|
33329
|
+
writeNamespaceOverride: options.writeNamespaceOverride
|
|
32620
33330
|
});
|
|
32621
33331
|
options.onTaskSettled?.();
|
|
32622
33332
|
} catch (err) {
|
|
@@ -32742,7 +33452,7 @@ ${normalized}`).digest("hex");
|
|
|
32742
33452
|
return;
|
|
32743
33453
|
}
|
|
32744
33454
|
const principal = resolvePrincipal(sessionKey, this.config);
|
|
32745
|
-
const selfNamespace = defaultNamespaceForPrincipal(principal, this.config);
|
|
33455
|
+
const selfNamespace = typeof options.writeNamespaceOverride === "string" && options.writeNamespaceOverride.length > 0 ? options.writeNamespaceOverride : defaultNamespaceForPrincipal(principal, this.config);
|
|
32746
33456
|
const storage = await this.storageRouter.storageFor(selfNamespace);
|
|
32747
33457
|
const shouldPersistProcessedFingerprint = normalizedTurns.some(
|
|
32748
33458
|
(turn) => turn.persistProcessedFingerprint === true
|
|
@@ -33426,6 +34136,9 @@ ${normalized}`).digest("hex");
|
|
|
33426
34136
|
continue;
|
|
33427
34137
|
}
|
|
33428
34138
|
const judgeCategory = preRoutedCategories[fi] ?? f.category;
|
|
34139
|
+
if (judgeCategory === "procedure") {
|
|
34140
|
+
continue;
|
|
34141
|
+
}
|
|
33429
34142
|
const tags = Array.isArray(f.tags) ? f.tags : [];
|
|
33430
34143
|
const imp = scoreImportance(
|
|
33431
34144
|
f.content,
|
|
@@ -33520,6 +34233,10 @@ ${normalized}`).digest("hex");
|
|
|
33520
34233
|
writeCategory,
|
|
33521
34234
|
fact.tags
|
|
33522
34235
|
);
|
|
34236
|
+
if (writeCategory === "procedure" && this.config.procedural?.enabled !== true) {
|
|
34237
|
+
log.debug("persistExtraction: skip procedure memory (procedural.enabled is false)");
|
|
34238
|
+
continue;
|
|
34239
|
+
}
|
|
33523
34240
|
if (!isAboveImportanceThreshold(
|
|
33524
34241
|
importance.level,
|
|
33525
34242
|
this.config.extractionMinImportanceLevel
|
|
@@ -33548,6 +34265,24 @@ ${normalized}`).digest("hex");
|
|
|
33548
34265
|
}
|
|
33549
34266
|
}
|
|
33550
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
|
+
}
|
|
33551
34286
|
let pendingSemanticSkip = null;
|
|
33552
34287
|
if (this.config.semanticDedupEnabled) {
|
|
33553
34288
|
let semanticDecision;
|
|
@@ -33627,7 +34362,7 @@ ${normalized}`).digest("hex");
|
|
|
33627
34362
|
dedupedCount++;
|
|
33628
34363
|
continue;
|
|
33629
34364
|
}
|
|
33630
|
-
if (this.config.chunkingEnabled) {
|
|
34365
|
+
if (this.config.chunkingEnabled && writeCategory !== "procedure") {
|
|
33631
34366
|
let chunkResult;
|
|
33632
34367
|
if (this.config.semanticChunkingEnabled) {
|
|
33633
34368
|
try {
|
|
@@ -33822,8 +34557,9 @@ ${normalized}`).digest("hex");
|
|
|
33822
34557
|
links.push(...suggestedLinks);
|
|
33823
34558
|
}
|
|
33824
34559
|
}
|
|
33825
|
-
const memoryKind = this.config.episodeNoteModeEnabled ? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory) : void 0;
|
|
33826
|
-
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);
|
|
33827
34563
|
const memoryId = await targetStorage.writeMemory(
|
|
33828
34564
|
writeCategory,
|
|
33829
34565
|
citedFactContent,
|
|
@@ -33840,7 +34576,7 @@ ${normalized}`).digest("hex");
|
|
|
33840
34576
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
33841
34577
|
memoryKind,
|
|
33842
34578
|
structuredAttributes: fact.structuredAttributes,
|
|
33843
|
-
contentHashSource: fact.content
|
|
34579
|
+
contentHashSource: writeCategory === "fact" ? fact.content : void 0
|
|
33844
34580
|
}
|
|
33845
34581
|
);
|
|
33846
34582
|
if (routedRuleId) {
|
|
@@ -39064,8 +39800,8 @@ Best for:
|
|
|
39064
39800
|
),
|
|
39065
39801
|
category: Type.Optional(
|
|
39066
39802
|
Type.String({
|
|
39067
|
-
description: 'Category: "fact", "preference", "correction", "entity", "decision", "relationship", "principle", "commitment", "moment", "skill", "rule" (default: "fact")',
|
|
39068
|
-
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"]
|
|
39069
39805
|
})
|
|
39070
39806
|
),
|
|
39071
39807
|
tags: Type.Optional(
|
|
@@ -39123,7 +39859,7 @@ Best for:
|
|
|
39123
39859
|
category: Type.Optional(
|
|
39124
39860
|
Type.String({
|
|
39125
39861
|
description: "Memory category.",
|
|
39126
|
-
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"]
|
|
39127
39863
|
})
|
|
39128
39864
|
),
|
|
39129
39865
|
tags: Type.Optional(
|
|
@@ -40192,8 +40928,8 @@ Returns: Performance trace data with timing breakdown`,
|
|
|
40192
40928
|
}
|
|
40193
40929
|
|
|
40194
40930
|
// ../remnic-core/src/cli.ts
|
|
40195
|
-
import
|
|
40196
|
-
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";
|
|
40197
40933
|
import { createHash as createHash14 } from "crypto";
|
|
40198
40934
|
|
|
40199
40935
|
// ../remnic-core/src/transfer/export-json.ts
|
|
@@ -41070,8 +41806,8 @@ function gatherCandidates(input, warnings) {
|
|
|
41070
41806
|
const record = rec;
|
|
41071
41807
|
const content = typeof record.content === "string" ? record.content : null;
|
|
41072
41808
|
if (!content) continue;
|
|
41073
|
-
const
|
|
41074
|
-
if (!
|
|
41809
|
+
const path100 = typeof record.path === "string" ? record.path : "";
|
|
41810
|
+
if (!path100.startsWith("transcripts/") && !path100.includes("/transcripts/")) continue;
|
|
41075
41811
|
rows.push(...parseJsonl(content, warnings));
|
|
41076
41812
|
}
|
|
41077
41813
|
return rows;
|
|
@@ -41126,6 +41862,158 @@ var openclawReplayNormalizer = {
|
|
|
41126
41862
|
}
|
|
41127
41863
|
};
|
|
41128
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
|
+
|
|
41129
42017
|
// ../remnic-core/src/maintenance/archive-observations.ts
|
|
41130
42018
|
import path55 from "path";
|
|
41131
42019
|
import { mkdir as mkdir40, readdir as readdir19, readFile as readFile33, unlink as unlink8, writeFile as writeFile37 } from "fs/promises";
|
|
@@ -44474,6 +45362,98 @@ function sleep2(ms) {
|
|
|
44474
45362
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44475
45363
|
}
|
|
44476
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
|
+
|
|
44477
45457
|
// ../remnic-core/src/briefing.ts
|
|
44478
45458
|
import { readFile as readFile41 } from "fs/promises";
|
|
44479
45459
|
import path67 from "path";
|
|
@@ -45113,10 +46093,10 @@ async function loadTodayCalendar(source, now) {
|
|
|
45113
46093
|
}
|
|
45114
46094
|
function buildOpenAiFollowupGenerator(cfg) {
|
|
45115
46095
|
return async ({ sections, windowLabel, maxFollowups }) => {
|
|
45116
|
-
const { OpenAI:
|
|
46096
|
+
const { OpenAI: OpenAI3 } = await import("openai");
|
|
45117
46097
|
const clientOpts = { apiKey: cfg.apiKey };
|
|
45118
46098
|
if (cfg.baseURL) clientOpts.baseURL = cfg.baseURL;
|
|
45119
|
-
const client = new
|
|
46099
|
+
const client = new OpenAI3(clientOpts);
|
|
45120
46100
|
const prompt = buildFollowupPrompt(sections, windowLabel, maxFollowups);
|
|
45121
46101
|
const response = await client.responses.create({
|
|
45122
46102
|
model: cfg.model,
|
|
@@ -46185,6 +47165,25 @@ var EngramAccessService = class {
|
|
|
46185
47165
|
reportPath: result.reportPath
|
|
46186
47166
|
};
|
|
46187
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
|
+
}
|
|
46188
47187
|
async trustZoneStatus(namespace, principal) {
|
|
46189
47188
|
const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
|
|
46190
47189
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
@@ -47213,6 +48212,17 @@ var EngramMcpServer = class {
|
|
|
47213
48212
|
additionalProperties: false
|
|
47214
48213
|
}
|
|
47215
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
|
+
},
|
|
47216
48226
|
{
|
|
47217
48227
|
name: "engram.memory_get",
|
|
47218
48228
|
description: "Fetch one Remnic memory by id.",
|
|
@@ -48092,6 +49102,14 @@ ${body}`;
|
|
|
48092
49102
|
batchSize: typeof args.batchSize === "number" && Number.isFinite(args.batchSize) ? args.batchSize : void 0,
|
|
48093
49103
|
authenticatedPrincipal: effectivePrincipal
|
|
48094
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
|
+
);
|
|
48095
49113
|
case "engram.memory_get":
|
|
48096
49114
|
return this.service.memoryGet(
|
|
48097
49115
|
typeof args.memoryId === "string" ? args.memoryId : "",
|
|
@@ -48457,7 +49475,8 @@ var categorySchema = external_exports.enum([
|
|
|
48457
49475
|
"commitment",
|
|
48458
49476
|
"moment",
|
|
48459
49477
|
"skill",
|
|
48460
|
-
"rule"
|
|
49478
|
+
"rule",
|
|
49479
|
+
"procedure"
|
|
48461
49480
|
]).optional();
|
|
48462
49481
|
var confidenceSchema = external_exports.number().min(0).max(1).optional();
|
|
48463
49482
|
var tagsSchema = external_exports.array(external_exports.string().max(256)).max(50).optional();
|
|
@@ -48656,8 +49675,8 @@ var HermesAdapter = class {
|
|
|
48656
49675
|
// ../remnic-core/src/adapters/registry.ts
|
|
48657
49676
|
var AdapterRegistry = class {
|
|
48658
49677
|
adapters;
|
|
48659
|
-
constructor(
|
|
48660
|
-
this.adapters =
|
|
49678
|
+
constructor(adapters2) {
|
|
49679
|
+
this.adapters = adapters2 ?? [
|
|
48661
49680
|
new HermesAdapter(),
|
|
48662
49681
|
new ReplitAdapter(),
|
|
48663
49682
|
new CodexAdapter(),
|
|
@@ -50129,6 +51148,10 @@ async function promoteSemanticRuleFromMemory(options) {
|
|
|
50129
51148
|
return report;
|
|
50130
51149
|
}
|
|
50131
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
|
+
|
|
50132
51155
|
// ../remnic-core/src/cli.ts
|
|
50133
51156
|
function rankCandidateForKeep(a, b) {
|
|
50134
51157
|
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
@@ -50319,7 +51342,7 @@ async function runRepairMemoryProjectionCliCommand(options) {
|
|
|
50319
51342
|
});
|
|
50320
51343
|
}
|
|
50321
51344
|
async function runMemoryTimelineCliCommand(options) {
|
|
50322
|
-
const storage = new (await import("./storage-
|
|
51345
|
+
const storage = new (await import("./storage-BA6OBLMK.js")).StorageManager(options.memoryDir);
|
|
50323
51346
|
return storage.getMemoryTimeline(options.memoryId, options.limit);
|
|
50324
51347
|
}
|
|
50325
51348
|
async function runMemoryGovernanceCliCommand(options) {
|
|
@@ -50347,7 +51370,7 @@ async function runMemoryGovernanceRestoreCliCommand(options) {
|
|
|
50347
51370
|
});
|
|
50348
51371
|
}
|
|
50349
51372
|
async function runMemoryReviewDispositionCliCommand(options) {
|
|
50350
|
-
const storage = new (await import("./storage-
|
|
51373
|
+
const storage = new (await import("./storage-BA6OBLMK.js")).StorageManager(options.memoryDir);
|
|
50351
51374
|
const memory = await storage.getMemoryById(options.memoryId);
|
|
50352
51375
|
if (!memory) throw new Error(`memory not found: ${options.memoryId}`);
|
|
50353
51376
|
const updated = await storage.writeMemoryFrontmatter(memory, {
|
|
@@ -50513,7 +51536,7 @@ async function runSemanticRulePromoteCliCommand(options) {
|
|
|
50513
51536
|
});
|
|
50514
51537
|
}
|
|
50515
51538
|
async function runCompoundingPromoteCliCommand(options) {
|
|
50516
|
-
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-
|
|
51539
|
+
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-BU6GNUJ5.js");
|
|
50517
51540
|
const config = parseConfig({
|
|
50518
51541
|
memoryDir: options.memoryDir,
|
|
50519
51542
|
qmdEnabled: false,
|
|
@@ -50930,7 +51953,7 @@ function policyVersionForValues(values, config) {
|
|
|
50930
51953
|
return createHash14("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
50931
51954
|
}
|
|
50932
51955
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
50933
|
-
const filePath =
|
|
51956
|
+
const filePath = path72.join(config.memoryDir, "state", fileName);
|
|
50934
51957
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
50935
51958
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
50936
51959
|
});
|
|
@@ -51486,7 +52509,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
|
51486
52509
|
}
|
|
51487
52510
|
async function runReplayCliCommand(orchestrator, options) {
|
|
51488
52511
|
const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
|
|
51489
|
-
const inputRaw = await
|
|
52512
|
+
const inputRaw = await readFile46(options.inputPath, "utf-8");
|
|
51490
52513
|
const registry = buildReplayNormalizerRegistry([
|
|
51491
52514
|
openclawReplayNormalizer,
|
|
51492
52515
|
claudeReplayNormalizer,
|
|
@@ -51548,10 +52571,101 @@ async function runReplayCliCommand(orchestrator, options) {
|
|
|
51548
52571
|
}
|
|
51549
52572
|
return summary;
|
|
51550
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
|
+
}
|
|
51551
52665
|
async function getPluginVersion() {
|
|
51552
52666
|
try {
|
|
51553
52667
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
51554
|
-
const raw = await
|
|
52668
|
+
const raw = await readFile46(pkgPath, "utf-8");
|
|
51555
52669
|
const parsed = JSON.parse(raw);
|
|
51556
52670
|
return parsed.version ?? "unknown";
|
|
51557
52671
|
} catch {
|
|
@@ -51575,59 +52689,71 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace, options) {
|
|
|
51575
52689
|
}
|
|
51576
52690
|
return orchestrator.config.memoryDir;
|
|
51577
52691
|
}
|
|
51578
|
-
const candidate =
|
|
52692
|
+
const candidate = path72.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
51579
52693
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
51580
52694
|
return await exists3(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
51581
52695
|
}
|
|
51582
52696
|
return candidate;
|
|
51583
52697
|
}
|
|
51584
|
-
async function
|
|
51585
|
-
const roots = [
|
|
51586
|
-
const out = [];
|
|
52698
|
+
async function walkMemoryMarkdownFiles(memoryDir, visit) {
|
|
52699
|
+
const roots = [path72.join(memoryDir, "facts"), path72.join(memoryDir, "corrections")];
|
|
51587
52700
|
const walk = async (dir) => {
|
|
51588
52701
|
let entries;
|
|
51589
52702
|
try {
|
|
51590
|
-
entries = await
|
|
52703
|
+
entries = await readdir26(dir, { withFileTypes: true });
|
|
51591
52704
|
} catch {
|
|
51592
52705
|
return;
|
|
51593
52706
|
}
|
|
51594
52707
|
for (const entry of entries) {
|
|
51595
52708
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
51596
|
-
const fullPath =
|
|
52709
|
+
const fullPath = path72.join(dir, entryName);
|
|
51597
52710
|
if (entry.isDirectory()) {
|
|
51598
52711
|
await walk(fullPath);
|
|
51599
52712
|
continue;
|
|
51600
52713
|
}
|
|
51601
52714
|
if (!entry.isFile() || !entryName.endsWith(".md")) continue;
|
|
51602
|
-
|
|
51603
|
-
const raw = await readFile45(fullPath, "utf-8");
|
|
51604
|
-
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
51605
|
-
if (!parsed) continue;
|
|
51606
|
-
const fmRaw = parsed[1];
|
|
51607
|
-
const body = parsed[2] ?? "";
|
|
51608
|
-
const get = (key) => {
|
|
51609
|
-
const match = fmRaw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
51610
|
-
return match ? match[1].trim() : "";
|
|
51611
|
-
};
|
|
51612
|
-
const confidenceRaw = get("confidence");
|
|
51613
|
-
const confidence = confidenceRaw.length > 0 ? Number(confidenceRaw) : void 0;
|
|
51614
|
-
out.push({
|
|
51615
|
-
path: fullPath,
|
|
51616
|
-
content: body,
|
|
51617
|
-
frontmatter: {
|
|
51618
|
-
id: get("id") || void 0,
|
|
51619
|
-
confidence: Number.isFinite(confidence) ? confidence : void 0,
|
|
51620
|
-
updated: get("updated") || void 0,
|
|
51621
|
-
created: get("created") || void 0
|
|
51622
|
-
}
|
|
51623
|
-
});
|
|
51624
|
-
} catch {
|
|
51625
|
-
}
|
|
52715
|
+
await visit(fullPath);
|
|
51626
52716
|
}
|
|
51627
52717
|
};
|
|
51628
52718
|
for (const root of roots) {
|
|
51629
52719
|
await walk(root);
|
|
51630
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
|
+
});
|
|
51631
52757
|
return out;
|
|
51632
52758
|
}
|
|
51633
52759
|
function formatContinuityIncidentCli(incident) {
|
|
@@ -51936,7 +53062,7 @@ function registerCli(api, orchestrator) {
|
|
|
51936
53062
|
if (plan.moved.length > 0) {
|
|
51937
53063
|
console.log("\nEntries:");
|
|
51938
53064
|
for (const move of plan.moved) {
|
|
51939
|
-
console.log(`- ${
|
|
53065
|
+
console.log(`- ${path72.basename(move.from)}`);
|
|
51940
53066
|
}
|
|
51941
53067
|
}
|
|
51942
53068
|
if (dryRun) {
|
|
@@ -52134,6 +53260,64 @@ function registerCli(api, orchestrator) {
|
|
|
52134
53260
|
}
|
|
52135
53261
|
console.log("OK");
|
|
52136
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
|
+
});
|
|
52137
53321
|
cmd.command("benchmark-status").description("Show benchmark/evaluation harness status, benchmark packs, and latest run summary").action(async () => {
|
|
52138
53322
|
const status = await runBenchmarkStatusCliCommand({
|
|
52139
53323
|
memoryDir: orchestrator.config.memoryDir,
|
|
@@ -53586,7 +54770,7 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53586
54770
|
}
|
|
53587
54771
|
});
|
|
53588
54772
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
53589
|
-
const workspaceDir =
|
|
54773
|
+
const workspaceDir = path72.join(resolveHomeDir(), ".openclaw", "workspace");
|
|
53590
54774
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
53591
54775
|
if (!identity) {
|
|
53592
54776
|
console.log("No identity file found.");
|
|
@@ -53809,8 +54993,8 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
53809
54993
|
const options = args[0] ?? {};
|
|
53810
54994
|
const threadId = options.thread;
|
|
53811
54995
|
const top = parseInt(options.top ?? "10", 10);
|
|
53812
|
-
const memoryDir =
|
|
53813
|
-
const threading = new ThreadingManager(
|
|
54996
|
+
const memoryDir = path72.join(resolveHomeDir(), ".openclaw", "workspace", "memory", "local");
|
|
54997
|
+
const threading = new ThreadingManager(path72.join(memoryDir, "threads"));
|
|
53814
54998
|
if (threadId) {
|
|
53815
54999
|
const thread = await threading.loadThread(threadId);
|
|
53816
55000
|
if (!thread) {
|
|
@@ -54286,19 +55470,19 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
54286
55470
|
}
|
|
54287
55471
|
|
|
54288
55472
|
// ../../src/index.ts
|
|
54289
|
-
import { readFile as
|
|
55473
|
+
import { readFile as readFile52, realpath as realpath5, writeFile as writeFile46 } from "fs/promises";
|
|
54290
55474
|
import { readFileSync as readFileSync6 } from "fs";
|
|
54291
|
-
import
|
|
55475
|
+
import path98 from "path";
|
|
54292
55476
|
import os6 from "os";
|
|
54293
55477
|
|
|
54294
55478
|
// ../remnic-core/src/opik-exporter.ts
|
|
54295
|
-
import { createHash as createHash15, randomBytes } from "crypto";
|
|
55479
|
+
import { createHash as createHash15, randomBytes as randomBytes2 } from "crypto";
|
|
54296
55480
|
import { readFileSync as readFileSync4 } from "fs";
|
|
54297
|
-
import
|
|
55481
|
+
import path73 from "path";
|
|
54298
55482
|
var OPIK_EXPORTER_SLOT = "__openclawOpikExporter";
|
|
54299
55483
|
function readOpikOpenclawConfig(log2) {
|
|
54300
55484
|
try {
|
|
54301
|
-
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");
|
|
54302
55486
|
const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
54303
55487
|
const entry = raw?.plugins?.entries?.["opik-openclaw"];
|
|
54304
55488
|
if (!entry?.enabled || !entry?.config) return {};
|
|
@@ -54316,7 +55500,7 @@ function readOpikOpenclawConfig(log2) {
|
|
|
54316
55500
|
}
|
|
54317
55501
|
function uuidV7() {
|
|
54318
55502
|
const now = Date.now();
|
|
54319
|
-
const bytes =
|
|
55503
|
+
const bytes = randomBytes2(16);
|
|
54320
55504
|
bytes[0] = now / 2 ** 40 & 255;
|
|
54321
55505
|
bytes[1] = now / 2 ** 32 & 255;
|
|
54322
55506
|
bytes[2] = now / 2 ** 24 & 255;
|
|
@@ -54660,8 +55844,8 @@ function cleanUserMessage(content) {
|
|
|
54660
55844
|
}
|
|
54661
55845
|
|
|
54662
55846
|
// src/public-artifacts.ts
|
|
54663
|
-
import { readdir as
|
|
54664
|
-
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";
|
|
54665
55849
|
var PUBLIC_DIRS = [
|
|
54666
55850
|
{ dir: "facts", kind: "fact", contentType: "markdown" },
|
|
54667
55851
|
{ dir: "entities", kind: "entity", contentType: "markdown" },
|
|
@@ -54673,9 +55857,9 @@ var PUBLIC_FILES = [
|
|
|
54673
55857
|
];
|
|
54674
55858
|
async function isContainedWithin(target, boundary) {
|
|
54675
55859
|
try {
|
|
54676
|
-
const resolvedTarget = await
|
|
54677
|
-
const resolvedBoundary = await
|
|
54678
|
-
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);
|
|
54679
55863
|
} catch {
|
|
54680
55864
|
return false;
|
|
54681
55865
|
}
|
|
@@ -54684,7 +55868,7 @@ async function listMarkdownFilesRecursive(rootDir, boundary, ancestorRealPaths)
|
|
|
54684
55868
|
const boundaryDir = boundary ?? rootDir;
|
|
54685
55869
|
let resolvedRoot;
|
|
54686
55870
|
try {
|
|
54687
|
-
resolvedRoot = await
|
|
55871
|
+
resolvedRoot = await realpath4(rootDir);
|
|
54688
55872
|
} catch {
|
|
54689
55873
|
return [];
|
|
54690
55874
|
}
|
|
@@ -54693,18 +55877,18 @@ async function listMarkdownFilesRecursive(rootDir, boundary, ancestorRealPaths)
|
|
|
54693
55877
|
nextAncestors.add(resolvedRoot);
|
|
54694
55878
|
let entries;
|
|
54695
55879
|
try {
|
|
54696
|
-
entries = await
|
|
55880
|
+
entries = await readdir27(rootDir, { withFileTypes: true });
|
|
54697
55881
|
} catch {
|
|
54698
55882
|
return [];
|
|
54699
55883
|
}
|
|
54700
55884
|
entries.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
54701
55885
|
const files = [];
|
|
54702
55886
|
for (const entry of entries) {
|
|
54703
|
-
const fullPath =
|
|
55887
|
+
const fullPath = path74.join(rootDir, String(entry.name));
|
|
54704
55888
|
let isDir = entry.isDirectory();
|
|
54705
55889
|
let isFile = entry.isFile();
|
|
54706
55890
|
try {
|
|
54707
|
-
const linkStat = await
|
|
55891
|
+
const linkStat = await lstat3(fullPath);
|
|
54708
55892
|
if (linkStat.isSymbolicLink()) {
|
|
54709
55893
|
if (!await isContainedWithin(fullPath, boundaryDir)) {
|
|
54710
55894
|
continue;
|
|
@@ -54738,21 +55922,21 @@ async function listRemnicPublicArtifacts(params) {
|
|
|
54738
55922
|
const { memoryDir, workspaceDir, agentIds } = params;
|
|
54739
55923
|
const artifacts = [];
|
|
54740
55924
|
for (const spec of PUBLIC_DIRS) {
|
|
54741
|
-
const dirPath =
|
|
55925
|
+
const dirPath = path74.join(memoryDir, spec.dir);
|
|
54742
55926
|
if (!await pathExists2(dirPath)) continue;
|
|
54743
55927
|
if (!await isContainedWithin(dirPath, memoryDir)) continue;
|
|
54744
55928
|
try {
|
|
54745
|
-
const resolvedDir = await
|
|
54746
|
-
const expectedParent = await
|
|
54747
|
-
const resolvedName =
|
|
55929
|
+
const resolvedDir = await realpath4(dirPath);
|
|
55930
|
+
const expectedParent = await realpath4(memoryDir);
|
|
55931
|
+
const resolvedName = path74.basename(resolvedDir);
|
|
54748
55932
|
if (resolvedName !== spec.dir) continue;
|
|
54749
|
-
if (
|
|
55933
|
+
if (path74.dirname(resolvedDir) !== expectedParent) continue;
|
|
54750
55934
|
} catch {
|
|
54751
55935
|
continue;
|
|
54752
55936
|
}
|
|
54753
55937
|
const files = await listMarkdownFilesRecursive(dirPath, dirPath);
|
|
54754
55938
|
for (const absolutePath of files) {
|
|
54755
|
-
const relativePath =
|
|
55939
|
+
const relativePath = path74.relative(memoryDir, absolutePath).replace(/\\/g, "/");
|
|
54756
55940
|
artifacts.push({
|
|
54757
55941
|
kind: spec.kind,
|
|
54758
55942
|
workspaceDir,
|
|
@@ -54764,16 +55948,16 @@ async function listRemnicPublicArtifacts(params) {
|
|
|
54764
55948
|
}
|
|
54765
55949
|
}
|
|
54766
55950
|
for (const spec of PUBLIC_FILES) {
|
|
54767
|
-
const absolutePath =
|
|
55951
|
+
const absolutePath = path74.join(memoryDir, spec.relativePath);
|
|
54768
55952
|
if (!await pathExists2(absolutePath)) continue;
|
|
54769
55953
|
if (!await isContainedWithin(absolutePath, memoryDir)) continue;
|
|
54770
55954
|
try {
|
|
54771
|
-
const linkStat = await
|
|
55955
|
+
const linkStat = await lstat3(absolutePath);
|
|
54772
55956
|
if (linkStat.isSymbolicLink()) {
|
|
54773
|
-
const resolvedPath = await
|
|
54774
|
-
const expectedParent = await
|
|
54775
|
-
if (
|
|
54776
|
-
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;
|
|
54777
55961
|
}
|
|
54778
55962
|
} catch {
|
|
54779
55963
|
continue;
|
|
@@ -54924,28 +56108,28 @@ var DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024;
|
|
|
54924
56108
|
// ../remnic-core/src/binary-lifecycle/backend.ts
|
|
54925
56109
|
import fs3 from "fs";
|
|
54926
56110
|
import fsp2 from "fs/promises";
|
|
54927
|
-
import
|
|
56111
|
+
import path75 from "path";
|
|
54928
56112
|
|
|
54929
56113
|
// ../remnic-core/src/binary-lifecycle/scanner.ts
|
|
54930
56114
|
import fsp3 from "fs/promises";
|
|
54931
|
-
import
|
|
56115
|
+
import path76 from "path";
|
|
54932
56116
|
|
|
54933
56117
|
// ../remnic-core/src/binary-lifecycle/manifest.ts
|
|
54934
56118
|
import fsp4 from "fs/promises";
|
|
54935
|
-
import
|
|
56119
|
+
import path77 from "path";
|
|
54936
56120
|
import crypto3 from "crypto";
|
|
54937
56121
|
|
|
54938
56122
|
// ../remnic-core/src/binary-lifecycle/pipeline.ts
|
|
54939
56123
|
import fsp5 from "fs/promises";
|
|
54940
|
-
import
|
|
56124
|
+
import path78 from "path";
|
|
54941
56125
|
import crypto4 from "crypto";
|
|
54942
56126
|
|
|
54943
56127
|
// ../remnic-core/src/projection/index.ts
|
|
54944
56128
|
import fs4 from "fs";
|
|
54945
|
-
import
|
|
56129
|
+
import path80 from "path";
|
|
54946
56130
|
|
|
54947
56131
|
// ../remnic-core/src/utils/category-dir.ts
|
|
54948
|
-
import
|
|
56132
|
+
import path79 from "path";
|
|
54949
56133
|
var CATEGORY_DIR_MAP = {
|
|
54950
56134
|
correction: "corrections",
|
|
54951
56135
|
question: "questions",
|
|
@@ -54956,7 +56140,8 @@ var CATEGORY_DIR_MAP = {
|
|
|
54956
56140
|
principle: "principles",
|
|
54957
56141
|
rule: "rules",
|
|
54958
56142
|
skill: "skills",
|
|
54959
|
-
relationship: "relationships"
|
|
56143
|
+
relationship: "relationships",
|
|
56144
|
+
procedure: "procedures"
|
|
54960
56145
|
};
|
|
54961
56146
|
var ALL_CATEGORY_DIRS = [
|
|
54962
56147
|
"facts",
|
|
@@ -54969,30 +56154,30 @@ var ALL_CATEGORY_KEYS = [
|
|
|
54969
56154
|
|
|
54970
56155
|
// ../remnic-core/src/onboarding/index.ts
|
|
54971
56156
|
import fs5 from "fs";
|
|
54972
|
-
import
|
|
56157
|
+
import path81 from "path";
|
|
54973
56158
|
|
|
54974
56159
|
// ../remnic-core/src/curation/index.ts
|
|
54975
56160
|
import fs6 from "fs";
|
|
54976
|
-
import
|
|
56161
|
+
import path82 from "path";
|
|
54977
56162
|
import crypto5 from "crypto";
|
|
54978
56163
|
|
|
54979
56164
|
// ../remnic-core/src/dedup/index.ts
|
|
54980
56165
|
import fs7 from "fs";
|
|
54981
|
-
import
|
|
56166
|
+
import path83 from "path";
|
|
54982
56167
|
import crypto6 from "crypto";
|
|
54983
56168
|
|
|
54984
56169
|
// ../remnic-core/src/review/index.ts
|
|
54985
56170
|
import fs8 from "fs";
|
|
54986
|
-
import
|
|
56171
|
+
import path84 from "path";
|
|
54987
56172
|
|
|
54988
56173
|
// ../remnic-core/src/sync/index.ts
|
|
54989
56174
|
import fs9 from "fs";
|
|
54990
|
-
import
|
|
56175
|
+
import path85 from "path";
|
|
54991
56176
|
import crypto7 from "crypto";
|
|
54992
56177
|
|
|
54993
56178
|
// ../remnic-core/src/connectors/index.ts
|
|
54994
56179
|
import fs11 from "fs";
|
|
54995
|
-
import
|
|
56180
|
+
import path88 from "path";
|
|
54996
56181
|
import os4 from "os";
|
|
54997
56182
|
import { spawnSync } from "child_process";
|
|
54998
56183
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -55000,31 +56185,31 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
55000
56185
|
|
|
55001
56186
|
// ../remnic-core/src/tokens.ts
|
|
55002
56187
|
import fs10 from "fs";
|
|
55003
|
-
import
|
|
55004
|
-
import { randomBytes as
|
|
56188
|
+
import path86 from "path";
|
|
56189
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
55005
56190
|
|
|
55006
56191
|
// ../remnic-core/src/connectors/codex-marketplace.ts
|
|
55007
56192
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
55008
|
-
import
|
|
56193
|
+
import path87 from "path";
|
|
55009
56194
|
|
|
55010
56195
|
// ../remnic-core/src/spaces/index.ts
|
|
55011
56196
|
import fs12 from "fs";
|
|
55012
|
-
import
|
|
56197
|
+
import path89 from "path";
|
|
55013
56198
|
import crypto8 from "crypto";
|
|
55014
56199
|
|
|
55015
56200
|
// ../remnic-core/src/memory-extension/codex-publisher.ts
|
|
55016
56201
|
import fs13 from "fs";
|
|
55017
56202
|
import os5 from "os";
|
|
55018
|
-
import
|
|
56203
|
+
import path90 from "path";
|
|
55019
56204
|
|
|
55020
56205
|
// ../remnic-core/src/taxonomy/taxonomy-loader.ts
|
|
55021
|
-
import { readFile as
|
|
55022
|
-
import
|
|
56206
|
+
import { readFile as readFile47, mkdir as mkdir49, writeFile as writeFile43 } from "fs/promises";
|
|
56207
|
+
import path91 from "path";
|
|
55023
56208
|
|
|
55024
56209
|
// ../remnic-core/src/enrichment/audit.ts
|
|
55025
|
-
import { mkdir as mkdir50, readFile as
|
|
56210
|
+
import { mkdir as mkdir50, readFile as readFile48, appendFile as appendFile4 } from "fs/promises";
|
|
55026
56211
|
import { existsSync as existsSync11 } from "fs";
|
|
55027
|
-
import
|
|
56212
|
+
import path92 from "path";
|
|
55028
56213
|
|
|
55029
56214
|
// src/openclaw-tools/shapes.ts
|
|
55030
56215
|
var MemorySearchInputSchema = Type.Object({
|
|
@@ -55438,7 +56623,7 @@ async function syncHeartbeatOutcomeLinks(params) {
|
|
|
55438
56623
|
}
|
|
55439
56624
|
return { created: 0, updated: 0, linked };
|
|
55440
56625
|
}
|
|
55441
|
-
function
|
|
56626
|
+
function parseIsoTimestamp3(value) {
|
|
55442
56627
|
if (!value) return null;
|
|
55443
56628
|
const parsed = Date.parse(value);
|
|
55444
56629
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -55447,7 +56632,7 @@ function planDreamEntryFromConsolidation(params) {
|
|
|
55447
56632
|
const now = params.now ?? /* @__PURE__ */ new Date();
|
|
55448
56633
|
const latestDreamAt = Math.max(
|
|
55449
56634
|
-1,
|
|
55450
|
-
...params.existingDreams.map((entry) =>
|
|
56635
|
+
...params.existingDreams.map((entry) => parseIsoTimestamp3(entry.timestamp)).filter((value) => value !== null)
|
|
55451
56636
|
);
|
|
55452
56637
|
if (latestDreamAt > 0 && now.getTime() - latestDreamAt < params.minIntervalMinutes * 6e4) {
|
|
55453
56638
|
return null;
|
|
@@ -55591,88 +56776,100 @@ function resolveSession(commandCtx) {
|
|
|
55591
56776
|
};
|
|
55592
56777
|
}
|
|
55593
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(", ");
|
|
55594
56857
|
return [
|
|
55595
56858
|
{
|
|
55596
56859
|
name: "remnic",
|
|
56860
|
+
description: `Remnic memory controls (${subcommandNames})`,
|
|
55597
56861
|
category: "memory",
|
|
55598
56862
|
pluginId,
|
|
55599
|
-
|
|
55600
|
-
|
|
55601
|
-
|
|
55602
|
-
|
|
55603
|
-
|
|
55604
|
-
|
|
55605
|
-
|
|
55606
|
-
await runtime.toggles.setDisabled(sessionKey, agentId, true);
|
|
55607
|
-
return `Remnic recall disabled for session ${sessionKey}.`;
|
|
55608
|
-
}
|
|
55609
|
-
},
|
|
55610
|
-
{
|
|
55611
|
-
name: "on",
|
|
55612
|
-
description: "Re-enable Remnic recall for this session",
|
|
55613
|
-
args: [],
|
|
55614
|
-
handler: async (commandCtx = {}) => {
|
|
55615
|
-
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
55616
|
-
await runtime.toggles.setDisabled(sessionKey, agentId, false);
|
|
55617
|
-
return `Remnic recall re-enabled for session ${sessionKey}.`;
|
|
55618
|
-
}
|
|
55619
|
-
},
|
|
55620
|
-
{
|
|
55621
|
-
name: "status",
|
|
55622
|
-
description: "Show Remnic recall status and last injected summary",
|
|
55623
|
-
args: [],
|
|
55624
|
-
handler: async (commandCtx = {}) => {
|
|
55625
|
-
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
55626
|
-
const resolved = await runtime.toggles.resolve(sessionKey, agentId);
|
|
55627
|
-
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
55628
|
-
const summaryText = runtime.getLastRecallSummary(sessionKey);
|
|
55629
|
-
const summary = summaryText && summaryText.length > 0 ? summaryText : lastRecall && lastRecall.memoryIds.length > 0 ? `${lastRecall.memoryIds.length} memory item(s), latency ${lastRecall.latencyMs ?? "?"}ms` : "NONE";
|
|
55630
|
-
return [
|
|
55631
|
-
`Remnic recall is ${resolved.disabled ? "disabled" : "enabled"} for session ${sessionKey}.`,
|
|
55632
|
-
`Source: ${describeToggleSource(resolved.source)}.`,
|
|
55633
|
-
`Last recall: ${summary}.`
|
|
55634
|
-
].join(" ");
|
|
55635
|
-
}
|
|
55636
|
-
},
|
|
55637
|
-
{
|
|
55638
|
-
name: "clear",
|
|
55639
|
-
description: "Clear the session override and use global config again",
|
|
55640
|
-
args: [],
|
|
55641
|
-
handler: async (commandCtx = {}) => {
|
|
55642
|
-
const { sessionKey, agentId } = resolveSession(commandCtx);
|
|
55643
|
-
await runtime.toggles.clear(sessionKey, agentId);
|
|
55644
|
-
return `Cleared the Remnic session override for ${sessionKey}.`;
|
|
55645
|
-
}
|
|
55646
|
-
},
|
|
55647
|
-
{
|
|
55648
|
-
name: "stats",
|
|
55649
|
-
description: "Show Remnic extraction and recall stats for this session",
|
|
55650
|
-
args: [],
|
|
55651
|
-
handler: async (commandCtx = {}) => {
|
|
55652
|
-
const { sessionKey } = resolveSession(commandCtx);
|
|
55653
|
-
const lastRecall = runtime.getLastRecall(sessionKey);
|
|
55654
|
-
if (!lastRecall) {
|
|
55655
|
-
return `No Remnic recall stats are available for session ${sessionKey} yet.`;
|
|
55656
|
-
}
|
|
55657
|
-
return [
|
|
55658
|
-
`Session ${sessionKey}.`,
|
|
55659
|
-
`Planner mode: ${lastRecall.plannerMode ?? "unknown"}.`,
|
|
55660
|
-
`Latency: ${lastRecall.latencyMs ?? "?"}ms.`,
|
|
55661
|
-
`Memories: ${lastRecall.memoryIds.length}.`
|
|
55662
|
-
].join(" ");
|
|
55663
|
-
}
|
|
55664
|
-
},
|
|
55665
|
-
{
|
|
55666
|
-
name: "flush",
|
|
55667
|
-
description: "Force-flush the extraction buffer now",
|
|
55668
|
-
args: [],
|
|
55669
|
-
handler: async (commandCtx = {}) => {
|
|
55670
|
-
const { sessionKey } = resolveSession(commandCtx);
|
|
55671
|
-
await runtime.flushSession(sessionKey);
|
|
55672
|
-
return `Flushed the Remnic buffer for session ${sessionKey}.`;
|
|
55673
|
-
}
|
|
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}.`;
|
|
55674
56870
|
}
|
|
55675
|
-
|
|
56871
|
+
return match.handler(commandCtx);
|
|
56872
|
+
}
|
|
55676
56873
|
}
|
|
55677
56874
|
];
|
|
55678
56875
|
}
|
|
@@ -55772,8 +56969,8 @@ function validateSlotSelection(ctx) {
|
|
|
55772
56969
|
}
|
|
55773
56970
|
|
|
55774
56971
|
// ../remnic-core/src/session-toggles.ts
|
|
55775
|
-
import { mkdir as mkdir51, readFile as
|
|
55776
|
-
import
|
|
56972
|
+
import { mkdir as mkdir51, readFile as readFile49, writeFile as writeFile44 } from "fs/promises";
|
|
56973
|
+
import path93 from "path";
|
|
55777
56974
|
function encodeToggleKey(sessionKey, agentId) {
|
|
55778
56975
|
return `${encodeURIComponent(sessionKey)}::${encodeURIComponent(agentId)}`;
|
|
55779
56976
|
}
|
|
@@ -55787,7 +56984,7 @@ function decodeToggleKey(key) {
|
|
|
55787
56984
|
}
|
|
55788
56985
|
async function safeReadToggleFile(filePath) {
|
|
55789
56986
|
try {
|
|
55790
|
-
const raw = await
|
|
56987
|
+
const raw = await readFile49(filePath, "utf8");
|
|
55791
56988
|
const parsed = JSON.parse(raw);
|
|
55792
56989
|
if (!parsed || typeof parsed !== "object" || typeof parsed.entries !== "object") {
|
|
55793
56990
|
return { version: 1, entries: {} };
|
|
@@ -55812,7 +57009,7 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
55812
57009
|
await run;
|
|
55813
57010
|
}
|
|
55814
57011
|
async function writeToggleFile(next) {
|
|
55815
|
-
await mkdir51(
|
|
57012
|
+
await mkdir51(path93.dirname(filePath), { recursive: true });
|
|
55816
57013
|
await writeFile44(filePath, JSON.stringify(next, null, 2), "utf8");
|
|
55817
57014
|
}
|
|
55818
57015
|
async function readPrimary() {
|
|
@@ -55885,8 +57082,8 @@ function createFileToggleStore(filePath, options = {}) {
|
|
|
55885
57082
|
}
|
|
55886
57083
|
|
|
55887
57084
|
// ../remnic-core/src/recall-audit.ts
|
|
55888
|
-
import { appendFile as appendFile5, mkdir as mkdir52, readdir as
|
|
55889
|
-
import
|
|
57085
|
+
import { appendFile as appendFile5, mkdir as mkdir52, readdir as readdir28, rm as rm10 } from "fs/promises";
|
|
57086
|
+
import path94 from "path";
|
|
55890
57087
|
function formatIsoDate(ts) {
|
|
55891
57088
|
const normalized = new Date(ts);
|
|
55892
57089
|
if (Number.isNaN(normalized.getTime())) {
|
|
@@ -55896,24 +57093,24 @@ function formatIsoDate(ts) {
|
|
|
55896
57093
|
}
|
|
55897
57094
|
function buildRecallAuditPath(rootDir, ts, sessionKey) {
|
|
55898
57095
|
const safeSessionKey = encodeURIComponent(sessionKey);
|
|
55899
|
-
return
|
|
57096
|
+
return path94.join(rootDir, "transcripts", formatIsoDate(ts), `${safeSessionKey}.jsonl`);
|
|
55900
57097
|
}
|
|
55901
57098
|
async function appendRecallAuditEntry(rootDir, entry) {
|
|
55902
57099
|
const filePath = buildRecallAuditPath(rootDir, entry.ts, entry.sessionKey);
|
|
55903
|
-
await mkdir52(
|
|
57100
|
+
await mkdir52(path94.dirname(filePath), { recursive: true });
|
|
55904
57101
|
await appendFile5(filePath, `${JSON.stringify(entry)}
|
|
55905
57102
|
`, "utf8");
|
|
55906
57103
|
return filePath;
|
|
55907
57104
|
}
|
|
55908
57105
|
async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE__ */ new Date()) {
|
|
55909
|
-
const transcriptsDir =
|
|
57106
|
+
const transcriptsDir = path94.join(rootDir, "transcripts");
|
|
55910
57107
|
const removed = [];
|
|
55911
57108
|
const cutoff = new Date(now);
|
|
55912
57109
|
cutoff.setUTCHours(0, 0, 0, 0);
|
|
55913
57110
|
cutoff.setUTCDate(cutoff.getUTCDate() - Math.max(1, Math.floor(retentionDays)));
|
|
55914
57111
|
let entries;
|
|
55915
57112
|
try {
|
|
55916
|
-
entries = await
|
|
57113
|
+
entries = await readdir28(transcriptsDir, { withFileTypes: true });
|
|
55917
57114
|
} catch {
|
|
55918
57115
|
return removed;
|
|
55919
57116
|
}
|
|
@@ -55922,7 +57119,7 @@ async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE_
|
|
|
55922
57119
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.name)) continue;
|
|
55923
57120
|
const day = /* @__PURE__ */ new Date(`${entry.name}T00:00:00.000Z`);
|
|
55924
57121
|
if (Number.isNaN(day.getTime()) || day >= cutoff) continue;
|
|
55925
|
-
const dirPath =
|
|
57122
|
+
const dirPath = path94.join(transcriptsDir, entry.name);
|
|
55926
57123
|
await rm10(dirPath, { recursive: true, force: true });
|
|
55927
57124
|
removed.push(dirPath);
|
|
55928
57125
|
}
|
|
@@ -55931,7 +57128,7 @@ async function pruneRecallAuditEntries(rootDir, retentionDays, now = /* @__PURE_
|
|
|
55931
57128
|
|
|
55932
57129
|
// ../remnic-core/src/active-recall.ts
|
|
55933
57130
|
import { appendFile as appendFile6, mkdir as mkdir53 } from "fs/promises";
|
|
55934
|
-
import
|
|
57131
|
+
import path95 from "path";
|
|
55935
57132
|
var ACTIVE_RECALL_CACHE_MAX_ENTRIES = 256;
|
|
55936
57133
|
var NONE_SET = /* @__PURE__ */ new Set([
|
|
55937
57134
|
"",
|
|
@@ -56074,14 +57271,14 @@ ${params.recallExplain}` : null,
|
|
|
56074
57271
|
}
|
|
56075
57272
|
async function appendActiveRecallTranscript(transcriptRoot, input, config, result, queryBundle) {
|
|
56076
57273
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
56077
|
-
const filePath =
|
|
57274
|
+
const filePath = path95.join(
|
|
56078
57275
|
transcriptRoot,
|
|
56079
57276
|
"agents",
|
|
56080
57277
|
sanitizeTranscriptPathSegment(input.agentId),
|
|
56081
57278
|
date,
|
|
56082
57279
|
`${sanitizeTranscriptPathSegment(input.sessionKey)}.jsonl`
|
|
56083
57280
|
);
|
|
56084
|
-
await mkdir53(
|
|
57281
|
+
await mkdir53(path95.dirname(filePath), { recursive: true });
|
|
56085
57282
|
await appendFile6(
|
|
56086
57283
|
filePath,
|
|
56087
57284
|
`${JSON.stringify({
|
|
@@ -56313,8 +57510,8 @@ import { resolvePrincipal as resolvePrincipal2 } from "@remnic/core";
|
|
|
56313
57510
|
// ../remnic-core/src/surfaces/dreams.ts
|
|
56314
57511
|
import { createHash as createHash16 } from "crypto";
|
|
56315
57512
|
import { statSync, watch as watch2 } from "fs";
|
|
56316
|
-
import { mkdir as mkdir54, readFile as
|
|
56317
|
-
import
|
|
57513
|
+
import { mkdir as mkdir54, readFile as readFile50, writeFile as writeFile45 } from "fs/promises";
|
|
57514
|
+
import path96 from "path";
|
|
56318
57515
|
var DIARY_START_MARKER = "<!-- openclaw:dreaming:diary:start -->";
|
|
56319
57516
|
var DIARY_END_MARKER = "<!-- openclaw:dreaming:diary:end -->";
|
|
56320
57517
|
function stableDreamId(params) {
|
|
@@ -56479,7 +57676,7 @@ function createDreamsSurface() {
|
|
|
56479
57676
|
return {
|
|
56480
57677
|
async read(filePath) {
|
|
56481
57678
|
try {
|
|
56482
|
-
const content = await
|
|
57679
|
+
const content = await readFile50(filePath, "utf8");
|
|
56483
57680
|
return parseDreamEntries(content);
|
|
56484
57681
|
} catch (error) {
|
|
56485
57682
|
if (error.code === "ENOENT") {
|
|
@@ -56489,10 +57686,10 @@ function createDreamsSurface() {
|
|
|
56489
57686
|
}
|
|
56490
57687
|
},
|
|
56491
57688
|
async append(filePath, entry) {
|
|
56492
|
-
await mkdir54(
|
|
57689
|
+
await mkdir54(path96.dirname(filePath), { recursive: true });
|
|
56493
57690
|
let content = "";
|
|
56494
57691
|
try {
|
|
56495
|
-
content = await
|
|
57692
|
+
content = await readFile50(filePath, "utf8");
|
|
56496
57693
|
} catch (error) {
|
|
56497
57694
|
if (error.code !== "ENOENT") throw error;
|
|
56498
57695
|
}
|
|
@@ -56510,22 +57707,22 @@ ${ensured.slice(endIndex)}` : `${ensureDiary("")}${block}`;
|
|
|
56510
57707
|
let fileWatcher = null;
|
|
56511
57708
|
let parentWatcher = null;
|
|
56512
57709
|
let timer = null;
|
|
56513
|
-
const watchedName =
|
|
56514
|
-
const watchedDir =
|
|
57710
|
+
const watchedName = path96.basename(filePath);
|
|
57711
|
+
const watchedDir = path96.dirname(filePath);
|
|
56515
57712
|
const resolveParentWatchTarget = () => {
|
|
56516
57713
|
let candidateDir = watchedDir;
|
|
56517
57714
|
while (true) {
|
|
56518
57715
|
try {
|
|
56519
57716
|
if (statSync(candidateDir).isDirectory()) {
|
|
56520
|
-
const relative =
|
|
57717
|
+
const relative = path96.relative(candidateDir, watchedDir);
|
|
56521
57718
|
return {
|
|
56522
57719
|
dir: candidateDir,
|
|
56523
|
-
expectedName: relative.length === 0 ? watchedName : relative.split(
|
|
57720
|
+
expectedName: relative.length === 0 ? watchedName : relative.split(path96.sep)[0] ?? watchedName
|
|
56524
57721
|
};
|
|
56525
57722
|
}
|
|
56526
57723
|
} catch {
|
|
56527
57724
|
}
|
|
56528
|
-
const parentDir =
|
|
57725
|
+
const parentDir = path96.dirname(candidateDir);
|
|
56529
57726
|
if (parentDir === candidateDir) {
|
|
56530
57727
|
return null;
|
|
56531
57728
|
}
|
|
@@ -56590,8 +57787,8 @@ ${ensured.slice(endIndex)}` : `${ensureDiary("")}${block}`;
|
|
|
56590
57787
|
// ../remnic-core/src/surfaces/heartbeat.ts
|
|
56591
57788
|
import { createHash as createHash17 } from "crypto";
|
|
56592
57789
|
import { statSync as statSync2, watch as watch3 } from "fs";
|
|
56593
|
-
import { readFile as
|
|
56594
|
-
import
|
|
57790
|
+
import { readFile as readFile51 } from "fs/promises";
|
|
57791
|
+
import path97 from "path";
|
|
56595
57792
|
function stableHeartbeatId(params) {
|
|
56596
57793
|
const digest = createHash17("sha1").update(
|
|
56597
57794
|
JSON.stringify({
|
|
@@ -56754,7 +57951,7 @@ function createHeartbeatSurface() {
|
|
|
56754
57951
|
return {
|
|
56755
57952
|
async read(filePath) {
|
|
56756
57953
|
try {
|
|
56757
|
-
const content = await
|
|
57954
|
+
const content = await readFile51(filePath, "utf8");
|
|
56758
57955
|
return parseHeartbeatEntries(content);
|
|
56759
57956
|
} catch (error) {
|
|
56760
57957
|
if (error.code === "ENOENT") {
|
|
@@ -56767,22 +57964,22 @@ function createHeartbeatSurface() {
|
|
|
56767
57964
|
let fileWatcher = null;
|
|
56768
57965
|
let parentWatcher = null;
|
|
56769
57966
|
let timer = null;
|
|
56770
|
-
const watchedName =
|
|
56771
|
-
const watchedDir =
|
|
57967
|
+
const watchedName = path97.basename(filePath);
|
|
57968
|
+
const watchedDir = path97.dirname(filePath);
|
|
56772
57969
|
const resolveParentWatchTarget = () => {
|
|
56773
57970
|
let candidateDir = watchedDir;
|
|
56774
57971
|
while (true) {
|
|
56775
57972
|
try {
|
|
56776
57973
|
if (statSync2(candidateDir).isDirectory()) {
|
|
56777
|
-
const relative =
|
|
57974
|
+
const relative = path97.relative(candidateDir, watchedDir);
|
|
56778
57975
|
return {
|
|
56779
57976
|
dir: candidateDir,
|
|
56780
|
-
expectedName: relative.length === 0 ? watchedName : relative.split(
|
|
57977
|
+
expectedName: relative.length === 0 ? watchedName : relative.split(path97.sep)[0] ?? watchedName
|
|
56781
57978
|
};
|
|
56782
57979
|
}
|
|
56783
57980
|
} catch {
|
|
56784
57981
|
}
|
|
56785
|
-
const parentDir =
|
|
57982
|
+
const parentDir = path97.dirname(candidateDir);
|
|
56786
57983
|
if (parentDir === candidateDir) {
|
|
56787
57984
|
return null;
|
|
56788
57985
|
}
|
|
@@ -56868,7 +58065,7 @@ function loadPluginEntryFromFile(pluginId) {
|
|
|
56868
58065
|
try {
|
|
56869
58066
|
const explicitConfigPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH");
|
|
56870
58067
|
const homeDir = resolveHomeDir();
|
|
56871
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
58068
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path98.join(homeDir, ".openclaw", "openclaw.json");
|
|
56872
58069
|
const content = readFileSync6(configPath, "utf-8");
|
|
56873
58070
|
const config = JSON.parse(content);
|
|
56874
58071
|
return resolveRemnicPluginEntry(config, pluginId);
|
|
@@ -56884,7 +58081,7 @@ function loadRawConfigFromFile() {
|
|
|
56884
58081
|
try {
|
|
56885
58082
|
const explicitConfigPath = readEnvVar("OPENCLAW_ENGRAM_CONFIG_PATH") || readEnvVar("OPENCLAW_CONFIG_PATH");
|
|
56886
58083
|
const homeDir = resolveHomeDir();
|
|
56887
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
58084
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path98.join(homeDir, ".openclaw", "openclaw.json");
|
|
56888
58085
|
const content = readFileSync6(configPath, "utf-8");
|
|
56889
58086
|
const config = JSON.parse(content);
|
|
56890
58087
|
return config && typeof config === "object" ? config : void 0;
|
|
@@ -57118,15 +58315,15 @@ var pluginDefinition = {
|
|
|
57118
58315
|
citationsAutoDetect: cfg.citationsAutoDetect
|
|
57119
58316
|
});
|
|
57120
58317
|
globalThis[keys.ACCESS_HTTP_SERVER] = accessHttpServer;
|
|
57121
|
-
const pluginStateDir =
|
|
57122
|
-
const togglePrimaryPath =
|
|
57123
|
-
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;
|
|
57124
58321
|
const sessionToggleStore = createFileToggleStore(togglePrimaryPath, {
|
|
57125
58322
|
secondaryReadOnlyPath: toggleSecondaryPath
|
|
57126
58323
|
});
|
|
57127
58324
|
const dreamsSurface = createDreamsSurface();
|
|
57128
58325
|
const heartbeatSurface = createHeartbeatSurface();
|
|
57129
|
-
const dreamNarrativeClient = cfg.openaiApiKey ? new
|
|
58326
|
+
const dreamNarrativeClient = cfg.openaiApiKey ? new OpenAI2({
|
|
57130
58327
|
apiKey: cfg.openaiApiKey,
|
|
57131
58328
|
...cfg.openaiBaseUrl ? { baseURL: cfg.openaiBaseUrl } : {}
|
|
57132
58329
|
}) : null;
|
|
@@ -57142,11 +58339,11 @@ var pluginDefinition = {
|
|
|
57142
58339
|
}
|
|
57143
58340
|
function resolveDreamJournalPath(runtimeWorkspaceDir) {
|
|
57144
58341
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
57145
|
-
return
|
|
58342
|
+
return path98.isAbsolute(cfg.dreaming.journalPath) ? cfg.dreaming.journalPath : path98.join(workspaceRoot, cfg.dreaming.journalPath);
|
|
57146
58343
|
}
|
|
57147
58344
|
function resolveHeartbeatJournalPath(runtimeWorkspaceDir) {
|
|
57148
58345
|
const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
|
|
57149
|
-
return
|
|
58346
|
+
return path98.isAbsolute(cfg.heartbeat.journalPath) ? cfg.heartbeat.journalPath : path98.join(workspaceRoot, cfg.heartbeat.journalPath);
|
|
57150
58347
|
}
|
|
57151
58348
|
function queueDreamSurfaceSync(runtimeWorkspaceDir) {
|
|
57152
58349
|
if (!cfg.dreaming.enabled) return Promise.resolve();
|
|
@@ -57304,7 +58501,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
57304
58501
|
timeoutMs: cfg.activeRecallTimeoutMs,
|
|
57305
58502
|
cacheTtlMs: cfg.activeRecallCacheTtlMs,
|
|
57306
58503
|
persistTranscripts: cfg.activeRecallPersistTranscripts,
|
|
57307
|
-
transcriptDir:
|
|
58504
|
+
transcriptDir: path98.isAbsolute(cfg.activeRecallTranscriptDir) ? cfg.activeRecallTranscriptDir : path98.join(pluginStateDir, cfg.activeRecallTranscriptDir),
|
|
57308
58505
|
entityGraphDepth: cfg.activeRecallEntityGraphDepth,
|
|
57309
58506
|
includeCausalTrajectories: cfg.activeRecallIncludeCausalTrajectories,
|
|
57310
58507
|
includeDaySummary: cfg.activeRecallIncludeDaySummary,
|
|
@@ -58172,6 +59369,219 @@ Keep the reflection grounded in the evidence below.
|
|
|
58172
59369
|
const runtimeAgentId = typeof runtimeAgent?.id === "string" && runtimeAgent.id.length > 0 ? runtimeAgent.id : void 0;
|
|
58173
59370
|
const capabilityAgentIds = runtimeAgentId ? [runtimeAgentId] : ["generalist"];
|
|
58174
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
|
+
};
|
|
58175
59585
|
const memoryCapability = {
|
|
58176
59586
|
// Include the promptBuilder so runtimes that treat unified capability
|
|
58177
59587
|
// registration as authoritative (SDK >=2026.4.5) continue to inject
|
|
@@ -58179,6 +59589,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58179
59589
|
// Respect promptInjectionAllowed policy — omit promptBuilder if injection
|
|
58180
59590
|
// is disabled, so the capability only provides publicArtifacts.
|
|
58181
59591
|
...promptInjectionAllowed ? { promptBuilder: capabilityPromptBuilder } : {},
|
|
59592
|
+
runtime: remnicMemoryRuntime,
|
|
58182
59593
|
publicArtifacts: {
|
|
58183
59594
|
listArtifacts: async (_params) => {
|
|
58184
59595
|
try {
|
|
@@ -58196,7 +59607,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58196
59607
|
};
|
|
58197
59608
|
api.registerMemoryCapability(memoryCapability);
|
|
58198
59609
|
const builderDesc = !promptInjectionAllowed ? " (promptBuilder omitted \u2014 injection disabled by policy)" : memoryPromptBuilder ? " and promptBuilder (from registerMemoryPromptSection)" : " and promptBuilder (capability-only fallback)";
|
|
58199
|
-
log.info(`registered memory capability with publicArtifacts provider${builderDesc}`);
|
|
59610
|
+
log.info(`registered memory capability with runtime and publicArtifacts provider${builderDesc}`);
|
|
58200
59611
|
}
|
|
58201
59612
|
api.on(
|
|
58202
59613
|
"agent_end",
|
|
@@ -58493,7 +59904,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58493
59904
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
58494
59905
|
);
|
|
58495
59906
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
58496
|
-
const signalPath =
|
|
59907
|
+
const signalPath = path98.join(
|
|
58497
59908
|
workspaceDir,
|
|
58498
59909
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
58499
59910
|
);
|
|
@@ -58745,7 +60156,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58745
60156
|
}
|
|
58746
60157
|
async function ensureHourlySummaryCron(api2) {
|
|
58747
60158
|
const jobId = "engram-hourly-summary";
|
|
58748
|
-
const cronFilePath =
|
|
60159
|
+
const cronFilePath = path98.join(
|
|
58749
60160
|
os6.homedir(),
|
|
58750
60161
|
".openclaw",
|
|
58751
60162
|
"cron",
|
|
@@ -58757,7 +60168,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58757
60168
|
jobs: []
|
|
58758
60169
|
};
|
|
58759
60170
|
try {
|
|
58760
|
-
const content = await
|
|
60171
|
+
const content = await readFile52(cronFilePath, "utf-8");
|
|
58761
60172
|
jobsData = JSON.parse(content);
|
|
58762
60173
|
} catch {
|
|
58763
60174
|
}
|
|
@@ -58772,7 +60183,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58772
60183
|
id: jobId,
|
|
58773
60184
|
agentId: "generalist",
|
|
58774
60185
|
model,
|
|
58775
|
-
name: "
|
|
60186
|
+
name: "Remnic Hourly Summary",
|
|
58776
60187
|
enabled: true,
|
|
58777
60188
|
createdAtMs: Date.now(),
|
|
58778
60189
|
updatedAtMs: Date.now(),
|
|
@@ -58788,7 +60199,7 @@ Keep the reflection grounded in the evidence below.
|
|
|
58788
60199
|
kind: "agentTurn",
|
|
58789
60200
|
timeoutSeconds: 120,
|
|
58790
60201
|
thinking: "off",
|
|
58791
|
-
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"
|
|
58792
60203
|
},
|
|
58793
60204
|
delivery: { mode: "none" },
|
|
58794
60205
|
state: {}
|
|
@@ -58958,39 +60369,33 @@ Keep the reflection grounded in the evidence below.
|
|
|
58958
60369
|
stop: async () => {
|
|
58959
60370
|
if (!didCountStart) return;
|
|
58960
60371
|
didCountStart = false;
|
|
60372
|
+
const initPromiseAtStopEntry = globalThis[keys.INIT_PROMISE];
|
|
60373
|
+
try {
|
|
60374
|
+
await orchestrator.deferredReady;
|
|
60375
|
+
} catch {
|
|
60376
|
+
}
|
|
58961
60377
|
const remainingServices = Math.max(
|
|
58962
60378
|
0,
|
|
58963
60379
|
(globalThis[CLI_ACTIVE_SERVICE_COUNT] || 0) - 1
|
|
58964
60380
|
);
|
|
58965
60381
|
globalThis[CLI_ACTIVE_SERVICE_COUNT] = remainingServices;
|
|
58966
|
-
|
|
58967
|
-
activeOpikExporter?.unsubscribe();
|
|
58968
|
-
} catch (err) {
|
|
58969
|
-
log.debug(`engram opik exporter unsubscribe failed: ${err}`);
|
|
58970
|
-
}
|
|
58971
|
-
activeOpikExporter = null;
|
|
58972
|
-
try {
|
|
58973
|
-
await accessHttpServer.stop();
|
|
58974
|
-
} catch (err) {
|
|
58975
|
-
log.debug(`engram access HTTP stop failed: ${err}`);
|
|
58976
|
-
}
|
|
58977
|
-
stopDreamWatcher?.();
|
|
58978
|
-
stopDreamWatcher = null;
|
|
58979
|
-
stopHeartbeatWatcher?.();
|
|
58980
|
-
stopHeartbeatWatcher = null;
|
|
58981
|
-
removeDreamingObserver?.();
|
|
58982
|
-
removeDreamingObserver = null;
|
|
58983
|
-
delete globalThis[keys.ACCESS_HTTP_SERVER];
|
|
58984
|
-
delete globalThis[keys.ACCESS_SERVICE];
|
|
58985
|
-
const currentInitPromise = globalThis[keys.INIT_PROMISE];
|
|
60382
|
+
const currentInitPromise = initPromiseAtStopEntry;
|
|
58986
60383
|
let secondaryTookOver = false;
|
|
60384
|
+
if (!currentInitPromise && globalThis[keys.INIT_PROMISE]) {
|
|
60385
|
+
secondaryTookOver = true;
|
|
60386
|
+
}
|
|
58987
60387
|
if (!currentInitPromise) {
|
|
58988
|
-
|
|
58989
|
-
|
|
58990
|
-
|
|
58991
|
-
|
|
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
|
+
}
|
|
58992
60394
|
}
|
|
58993
60395
|
} else {
|
|
60396
|
+
if (globalThis[keys.INIT_PROMISE] && globalThis[keys.INIT_PROMISE] !== currentInitPromise) {
|
|
60397
|
+
secondaryTookOver = true;
|
|
60398
|
+
}
|
|
58994
60399
|
try {
|
|
58995
60400
|
await currentInitPromise;
|
|
58996
60401
|
} catch {
|
|
@@ -59000,6 +60405,27 @@ Keep the reflection grounded in the evidence below.
|
|
|
59000
60405
|
secondaryTookOver = true;
|
|
59001
60406
|
}
|
|
59002
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
|
+
}
|
|
59003
60429
|
if (!secondaryTookOver) {
|
|
59004
60430
|
globalThis[keys.HOOK_APIS] = /* @__PURE__ */ new WeakSet();
|
|
59005
60431
|
}
|
|
@@ -59035,7 +60461,7 @@ function extractTextContent2(msg) {
|
|
|
59035
60461
|
// src/bridge.ts
|
|
59036
60462
|
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
59037
60463
|
import * as childProcess from "child_process";
|
|
59038
|
-
import
|
|
60464
|
+
import path99 from "path";
|
|
59039
60465
|
|
|
59040
60466
|
// src/service-candidates.ts
|
|
59041
60467
|
function firstSuccessfulResult(candidates, attempt) {
|
|
@@ -59062,17 +60488,17 @@ function readCompatEnv(primary, legacy) {
|
|
|
59062
60488
|
function configPathCandidates() {
|
|
59063
60489
|
const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
|
|
59064
60490
|
return [
|
|
59065
|
-
...envPath ? [
|
|
59066
|
-
|
|
59067
|
-
|
|
59068
|
-
|
|
59069
|
-
|
|
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")
|
|
59070
60496
|
];
|
|
59071
60497
|
}
|
|
59072
60498
|
function isDaemonRunning() {
|
|
59073
60499
|
for (const pidFile of [
|
|
59074
|
-
|
|
59075
|
-
|
|
60500
|
+
path99.join(resolveHomeDir2(), ".remnic", "server.pid"),
|
|
60501
|
+
path99.join(resolveHomeDir2(), ".engram", "server.pid")
|
|
59076
60502
|
]) {
|
|
59077
60503
|
try {
|
|
59078
60504
|
const pid = parseInt(readFileSync7(pidFile, "utf8").trim(), 10);
|
|
@@ -59145,8 +60571,8 @@ function detectBridgeMode() {
|
|
|
59145
60571
|
}
|
|
59146
60572
|
function loadAnyToken() {
|
|
59147
60573
|
const tokenPaths = [
|
|
59148
|
-
|
|
59149
|
-
|
|
60574
|
+
path99.join(resolveHomeDir2(), ".remnic", "tokens.json"),
|
|
60575
|
+
path99.join(resolveHomeDir2(), ".engram", "tokens.json")
|
|
59150
60576
|
];
|
|
59151
60577
|
for (const tokensPath of tokenPaths) {
|
|
59152
60578
|
if (!existsSync12(tokensPath)) continue;
|