@remnic/plugin-openclaw 1.0.5 → 1.0.6

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