@remnic/plugin-openclaw 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/{calibration-3JHF25QT.js → calibration-BAC7KNKR.js} +2 -1
- package/dist/{causal-consolidation-EBLROS42.js → causal-consolidation-S6M7UTZG.js} +2 -1
- package/dist/chunk-3A5ELHTT.js +61 -0
- package/dist/chunk-DIZW6H5J.js +136 -0
- package/dist/{chunk-3SA5F4WT.js → chunk-NXLHSCLU.js} +125 -69
- package/dist/{chunk-GUKYM4XZ.js → chunk-SVGN3ACY.js} +2 -2
- package/dist/contradiction-review-SVGBS3V5.js +21 -0
- package/dist/contradiction-scan-LRRLWUOS.js +376 -0
- package/dist/{engine-BU6GNUJ5.js → engine-WGNTTFYE.js} +1 -1
- package/dist/{fallback-llm-HJRCHKSA.js → fallback-llm-QEAPMDW7.js} +2 -1
- package/dist/index.js +516 -93
- package/dist/resolution-YITUVUTH.js +100 -0
- package/openclaw.plugin.json +28 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
SharedContextManager,
|
|
4
4
|
defaultTierMigrationCycleBudget,
|
|
5
5
|
external_exports
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SVGN3ACY.js";
|
|
7
7
|
import {
|
|
8
8
|
filterTrajectoriesByLookbackDays,
|
|
9
9
|
getCausalTrajectoryStoreStatus,
|
|
@@ -85,12 +85,14 @@ import {
|
|
|
85
85
|
import {
|
|
86
86
|
FallbackLlmClient,
|
|
87
87
|
buildChatCompletionTokenLimit,
|
|
88
|
-
extractJsonCandidates,
|
|
89
88
|
mergeEnv,
|
|
90
89
|
readEnvVar,
|
|
91
90
|
resolveHomeDir,
|
|
92
91
|
shouldAssumeOpenAiChatCompletions
|
|
93
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-NXLHSCLU.js";
|
|
93
|
+
import {
|
|
94
|
+
extractJsonCandidates
|
|
95
|
+
} from "./chunk-3A5ELHTT.js";
|
|
94
96
|
import {
|
|
95
97
|
listJsonFiles,
|
|
96
98
|
listNamedFiles,
|
|
@@ -306,6 +308,31 @@ function normalizeMemoryRelativeDir(raw, fallback) {
|
|
|
306
308
|
const normalized = trimmed.replace(/\\/g, "/").split("/").filter((segment) => segment.length > 0 && segment !== "." && segment !== "..").join("/");
|
|
307
309
|
return normalized.length > 0 ? normalized : fallback;
|
|
308
310
|
}
|
|
311
|
+
function parseContradictionScanConfig(raw) {
|
|
312
|
+
if (!raw || typeof raw !== "object") {
|
|
313
|
+
return {
|
|
314
|
+
enabled: false,
|
|
315
|
+
similarityFloor: 0.82,
|
|
316
|
+
topicOverlapFloor: 0.4,
|
|
317
|
+
maxPairsPerRun: 500,
|
|
318
|
+
cooldownDays: 14,
|
|
319
|
+
autoMergeDuplicates: false
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const src = raw;
|
|
323
|
+
const simFloor = coerceNumber(src.similarityFloor) ?? 0.82;
|
|
324
|
+
const topicFloor = coerceNumber(src.topicOverlapFloor) ?? 0.4;
|
|
325
|
+
const maxPairs = coerceNumber(src.maxPairsPerRun) ?? 500;
|
|
326
|
+
const cooldown = coerceNumber(src.cooldownDays) ?? 14;
|
|
327
|
+
return {
|
|
328
|
+
enabled: coerceBool(src.enabled) === true,
|
|
329
|
+
similarityFloor: Math.min(1, Math.max(0, simFloor)),
|
|
330
|
+
topicOverlapFloor: Math.min(1, Math.max(0, topicFloor)),
|
|
331
|
+
maxPairsPerRun: Math.max(1, maxPairs),
|
|
332
|
+
cooldownDays: Math.max(0, cooldown),
|
|
333
|
+
autoMergeDuplicates: coerceBool(src.autoMergeDuplicates) === true
|
|
334
|
+
};
|
|
335
|
+
}
|
|
309
336
|
function parseSemanticChunkingConfig(raw) {
|
|
310
337
|
if (!raw || typeof raw !== "object") return {};
|
|
311
338
|
const src = raw;
|
|
@@ -510,17 +537,26 @@ function parseConfig(raw) {
|
|
|
510
537
|
fingerprintDedup: rawCodexCompat.fingerprintDedup !== false
|
|
511
538
|
};
|
|
512
539
|
const rawProcedural = cfg.procedural && typeof cfg.procedural === "object" && !Array.isArray(cfg.procedural) ? cfg.procedural : {};
|
|
513
|
-
const
|
|
540
|
+
const proceduralMinCoerced = coerceNumber(rawProcedural.minOccurrences);
|
|
541
|
+
const proceduralMinRaw = proceduralMinCoerced !== void 0 ? Math.floor(proceduralMinCoerced) : 3;
|
|
542
|
+
const successFloorRaw = coerceNumber(rawProcedural.successFloor);
|
|
543
|
+
const successFloor = successFloorRaw !== void 0 && successFloorRaw >= 0 && successFloorRaw <= 1 ? successFloorRaw : 0.7;
|
|
544
|
+
const autoPromoteOccRaw = coerceNumber(rawProcedural.autoPromoteOccurrences);
|
|
545
|
+
const autoPromoteOccurrences = autoPromoteOccRaw !== void 0 && Number.isFinite(autoPromoteOccRaw) ? autoPromoteOccRaw <= 0 ? 0 : Math.min(1e4, Math.max(1, Math.floor(autoPromoteOccRaw))) : 8;
|
|
546
|
+
const lookbackCoerced = coerceNumber(rawProcedural.lookbackDays);
|
|
547
|
+
const lookbackDays = lookbackCoerced !== void 0 && Number.isFinite(lookbackCoerced) ? Math.min(3650, Math.max(1, Math.floor(lookbackCoerced))) : 30;
|
|
548
|
+
const recallMaxCoerced = coerceNumber(rawProcedural.recallMaxProcedures);
|
|
549
|
+
const recallMaxProcedures = recallMaxCoerced !== void 0 && Number.isFinite(recallMaxCoerced) ? Math.min(10, Math.max(1, Math.floor(recallMaxCoerced))) : 3;
|
|
514
550
|
const procedural = {
|
|
515
551
|
enabled: coerceBool(rawProcedural.enabled) === true,
|
|
516
|
-
/** 0
|
|
552
|
+
/** `0` skips all mining (`minOccurrences_zero`); otherwise clusters need at least this many members. */
|
|
517
553
|
minOccurrences: Math.min(1e3, Math.max(0, proceduralMinRaw)),
|
|
518
|
-
successFloor
|
|
519
|
-
autoPromoteOccurrences
|
|
554
|
+
successFloor,
|
|
555
|
+
autoPromoteOccurrences,
|
|
520
556
|
autoPromoteEnabled: coerceBool(rawProcedural.autoPromoteEnabled) === true,
|
|
521
|
-
lookbackDays
|
|
557
|
+
lookbackDays,
|
|
522
558
|
proceduralMiningCronAutoRegister: coerceBool(rawProcedural.proceduralMiningCronAutoRegister) === true,
|
|
523
|
-
recallMaxProcedures
|
|
559
|
+
recallMaxProcedures
|
|
524
560
|
};
|
|
525
561
|
const memoryDir = typeof cfg.memoryDir === "string" && cfg.memoryDir.length > 0 ? cfg.memoryDir : DEFAULT_MEMORY_DIR;
|
|
526
562
|
const rawIdentityInjectionMode = cfg.identityInjectionMode;
|
|
@@ -704,13 +740,19 @@ function parseConfig(raw) {
|
|
|
704
740
|
contradictionSimilarityThreshold: typeof cfg.contradictionSimilarityThreshold === "number" ? cfg.contradictionSimilarityThreshold : 0.7,
|
|
705
741
|
contradictionMinConfidence: typeof cfg.contradictionMinConfidence === "number" ? cfg.contradictionMinConfidence : 0.9,
|
|
706
742
|
contradictionAutoResolve: cfg.contradictionAutoResolve !== false,
|
|
743
|
+
// Contradiction Scan cron (issue #520)
|
|
744
|
+
contradictionScan: parseContradictionScanConfig(cfg.contradictionScan),
|
|
707
745
|
// Temporal Supersession (issue #375)
|
|
708
746
|
temporalSupersessionEnabled: cfg.temporalSupersessionEnabled !== false,
|
|
709
747
|
// On by default
|
|
710
748
|
temporalSupersessionIncludeInRecall: cfg.temporalSupersessionIncludeInRecall === true,
|
|
711
749
|
// Off by default
|
|
712
|
-
// Direct-answer retrieval tier (issue #518)
|
|
713
|
-
|
|
750
|
+
// Direct-answer retrieval tier (issue #518). Default on — the
|
|
751
|
+
// tier runs in observation mode: it annotates
|
|
752
|
+
// LastRecallSnapshot.tierExplain but never short-circuits the
|
|
753
|
+
// QMD path. Operators can opt out with
|
|
754
|
+
// recallDirectAnswerEnabled=false.
|
|
755
|
+
recallDirectAnswerEnabled: coerceBool(cfg.recallDirectAnswerEnabled) ?? true,
|
|
714
756
|
recallDirectAnswerTokenOverlapFloor: (() => {
|
|
715
757
|
const n = coerceNumber(cfg.recallDirectAnswerTokenOverlapFloor);
|
|
716
758
|
return n !== void 0 && n >= 0 && n <= 1 ? n : 0.55;
|
|
@@ -972,6 +1014,22 @@ function parseConfig(raw) {
|
|
|
972
1014
|
localLlmFastModel: typeof cfg.localLlmFastModel === "string" && cfg.localLlmFastModel.length > 0 ? cfg.localLlmFastModel : "",
|
|
973
1015
|
localLlmFastUrl: typeof cfg.localLlmFastUrl === "string" && cfg.localLlmFastUrl.length > 0 ? cfg.localLlmFastUrl : typeof cfg.localLlmUrl === "string" && cfg.localLlmUrl.length > 0 ? cfg.localLlmUrl : "http://localhost:1234/v1",
|
|
974
1016
|
localLlmFastTimeoutMs: typeof cfg.localLlmFastTimeoutMs === "number" ? cfg.localLlmFastTimeoutMs : 15e3,
|
|
1017
|
+
// Thinking-mode suppression on the main local LLM (issue #548).
|
|
1018
|
+
// Default true — extraction / consolidation produce structured
|
|
1019
|
+
// JSON and gain nothing from chain-of-thought; thinking-capable
|
|
1020
|
+
// models burn their token budget on reasoning and blow the
|
|
1021
|
+
// default 60s timeout. Operators who need thinking on the main
|
|
1022
|
+
// client (e.g. for narrative tasks) can set this to false via
|
|
1023
|
+
// config or --config CLI flag. The fast-tier `fastLlm` always
|
|
1024
|
+
// disables thinking and is unaffected by this flag.
|
|
1025
|
+
//
|
|
1026
|
+
// Injection is backend-gated inside LocalLlmClient: the
|
|
1027
|
+
// `chat_template_kwargs` field is only sent when the detected
|
|
1028
|
+
// backend is in `THINKING_COMPATIBLE_BACKENDS` (LM Studio, vLLM).
|
|
1029
|
+
// Strict OpenAI-compatible backends reject unknown request
|
|
1030
|
+
// fields with 400, so the client fails open on unknown backends
|
|
1031
|
+
// rather than tripping the 400 cooldown (Codex P1 on PR #550).
|
|
1032
|
+
localLlmDisableThinking: coerceBool(cfg.localLlmDisableThinking) ?? true,
|
|
975
1033
|
// Gateway config (passed from index.ts for fallback AI)
|
|
976
1034
|
gatewayConfig: cfg.gatewayConfig,
|
|
977
1035
|
// Gateway model source (v9.2) — route LLM calls through gateway agent model chain
|
|
@@ -2844,6 +2902,10 @@ function trimTrailingSlashes(s) {
|
|
|
2844
2902
|
while (end > 0 && s[end - 1] === "/") end--;
|
|
2845
2903
|
return s.substring(0, end);
|
|
2846
2904
|
}
|
|
2905
|
+
var THINKING_COMPATIBLE_BACKENDS = /* @__PURE__ */ new Set([
|
|
2906
|
+
"lmstudio",
|
|
2907
|
+
"vllm"
|
|
2908
|
+
]);
|
|
2847
2909
|
var LOCAL_SERVERS = [
|
|
2848
2910
|
{
|
|
2849
2911
|
type: "ollama",
|
|
@@ -2902,9 +2964,16 @@ var LocalLlmClient = class _LocalLlmClient {
|
|
|
2902
2964
|
this.modelRegistry = modelRegistry;
|
|
2903
2965
|
}
|
|
2904
2966
|
/**
|
|
2905
|
-
*
|
|
2906
|
-
*
|
|
2907
|
-
*
|
|
2967
|
+
* Request thinking/reasoning suppression on the next chat completion.
|
|
2968
|
+
*
|
|
2969
|
+
* When `true`, the client will inject
|
|
2970
|
+
* `chat_template_kwargs: { enable_thinking: false }` into the request
|
|
2971
|
+
* body — **but only when the detected backend is known to support it**
|
|
2972
|
+
* (LM Studio, vLLM; see `THINKING_COMPATIBLE_BACKENDS`). Strict
|
|
2973
|
+
* OpenAI-compat backends reject unknown fields with 400; on those the
|
|
2974
|
+
* client fails open (thinking runs normally). This is the safe
|
|
2975
|
+
* default for Remnic extraction / consolidation: measurable latency
|
|
2976
|
+
* win on thinking-capable backends, zero risk on others. Issue #548.
|
|
2908
2977
|
*/
|
|
2909
2978
|
set disableThinking(value) {
|
|
2910
2979
|
this._disableThinking = value;
|
|
@@ -3402,7 +3471,7 @@ var LocalLlmClient = class _LocalLlmClient {
|
|
|
3402
3471
|
if (options.responseFormat?.type === "json_schema") {
|
|
3403
3472
|
requestBody.response_format = options.responseFormat;
|
|
3404
3473
|
}
|
|
3405
|
-
if (this._disableThinking) {
|
|
3474
|
+
if (this._disableThinking && this.detectedType !== null && THINKING_COMPATIBLE_BACKENDS.has(this.detectedType)) {
|
|
3406
3475
|
requestBody.chat_template_kwargs = { enable_thinking: false };
|
|
3407
3476
|
}
|
|
3408
3477
|
const baseUrl = trimTrailingSlashes(
|
|
@@ -5395,6 +5464,8 @@ Memory categories \u2014 use the MOST SPECIFIC category that fits:
|
|
|
5395
5464
|
- commitment: Promises, obligations, deadlines
|
|
5396
5465
|
- moment: Emotionally significant events
|
|
5397
5466
|
- skill: Demonstrated capabilities
|
|
5467
|
+
- rule: Explicit operational rules or constraints
|
|
5468
|
+
- procedure: Repeatable workflows \u2014 use when the user describes a multi-step play (\u22652 ordered steps). Put the human-readable trigger/context in "content" (e.g. "When you deploy\u2026") and list steps in "procedureSteps" as [{"order":1,"intent":"\u2026"}, \u2026] mirroring the gateway extraction schema.
|
|
5398
5469
|
|
|
5399
5470
|
IMPORTANT: Do NOT label everything as "fact". Use "decision" for architectural choices, "commitment" for deadlines/promises, "principle" for reusable rules, "correction" for when the user rejects a suggestion, etc.
|
|
5400
5471
|
|
|
@@ -5456,7 +5527,7 @@ Also generate:
|
|
|
5456
5527
|
|
|
5457
5528
|
Output JSON:
|
|
5458
5529
|
{
|
|
5459
|
-
"facts": [{"category": "decision", "content": "Chose PostgreSQL over MongoDB for the user service", "importance": 8, "confidence": 0.9, "structuredAttributes": {"chosen": "PostgreSQL", "rejected": "MongoDB"}}, {"category": "commitment", "content": "Must ship v2.0 API by end of March", "importance": 10, "confidence": 1.0, "structuredAttributes": {"deadline": "end of March", "deliverable": "v2.0 API"}}, {"category": "fact", "content": "The store backend uses Redis for session caching", "importance": 6, "confidence": 0.95, "entityRef": "project-acme-store"}, {"category": "principle", "content": "Always run migrations in a transaction to avoid partial schema updates", "importance": 8, "confidence": 0.9}],
|
|
5530
|
+
"facts": [{"category": "decision", "content": "Chose PostgreSQL over MongoDB for the user service", "importance": 8, "confidence": 0.9, "structuredAttributes": {"chosen": "PostgreSQL", "rejected": "MongoDB"}}, {"category": "procedure", "content": "When you cut a hotfix release, follow the checklist", "importance": 8, "confidence": 0.9, "procedureSteps": [{"order": 1, "intent": "Branch from main and cherry-pick the fix"}, {"order": 2, "intent": "Run CI and tag the release"}]}, {"category": "commitment", "content": "Must ship v2.0 API by end of March", "importance": 10, "confidence": 1.0, "structuredAttributes": {"deadline": "end of March", "deliverable": "v2.0 API"}}, {"category": "fact", "content": "The store backend uses Redis for session caching", "importance": 6, "confidence": 0.95, "entityRef": "project-acme-store"}, {"category": "principle", "content": "Always run migrations in a transaction to avoid partial schema updates", "importance": 8, "confidence": 0.9}],
|
|
5460
5531
|
"entities": [{"name": "person-jane-doe", "type": "person", "facts": ["Works at Acme Corp", "Prefers Python over JavaScript"], "structuredSections": [{"key": "beliefs", "title": "Beliefs", "facts": ["Python is a better fit than JavaScript for backend work."]}]}, {"name": "project-acme-store", "type": "project", "facts": ["Built with Next.js", "Deployed on Vercel"]}],
|
|
5461
5532
|
"profileUpdates": ["User prefers dark mode in all editors"],
|
|
5462
5533
|
"questions": [{"question": "Which cloud provider hosts the staging environment?", "context": "Came up during deployment discussion", "priority": 0.5}],
|
|
@@ -7367,7 +7438,7 @@ var ENTITY_PATTERNS = [
|
|
|
7367
7438
|
{ re: /\b(model|llm|qmd|embedding|retrieval|memory)\b/i, entityType: "ai" },
|
|
7368
7439
|
{ re: /\b(doc|readme|docs|changelog)\b/i, entityType: "docs" }
|
|
7369
7440
|
];
|
|
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
|
|
7441
|
+
var TASK_INITIATION_RE = /\b(ship(?:ping|ped)?|deploy(?:ing|ed)?|release|publish|open(?:ing)?\s+(?:a\s+)?(?:pr|pull\s+request)|merge(?:ing)?\s+(?:the\s+)?(?:pr|pull\s+request)|run\s+(?:the\s+)?tests?|start(?:ing)?\s+(?:work|on|the)|kick\s+off|implement(?:ing|ed)?|let's\s+(?:ship|deploy|release|publish|open|run|merge|implement|fix|patch|build|start|do|get|put|wire|hook|land|roll)\b|going\s+to\s+(?:ship|deploy|release|open|run|merge)|need\s+to\s+(?:ship|deploy|run|open|merge|test)|fix(?:ing|ed)?\s+(?:(?:the|a)\s+)?(?:\w+\s+){0,4}(?:bug|build)\b|patch(?:ing|ed)?|build(?:ing)?\s+(?:and\s+)?(?:ship|deploy))\b/i;
|
|
7371
7442
|
function normalizeTextInput(input) {
|
|
7372
7443
|
return typeof input === "string" ? input : "";
|
|
7373
7444
|
}
|
|
@@ -7698,8 +7769,13 @@ async function scanDir(dir) {
|
|
|
7698
7769
|
async function scanMemoryDir(memoryDir) {
|
|
7699
7770
|
const factsDir = path4.join(memoryDir, "facts");
|
|
7700
7771
|
const correctionsDir = path4.join(memoryDir, "corrections");
|
|
7701
|
-
const
|
|
7702
|
-
|
|
7772
|
+
const proceduresDir = path4.join(memoryDir, "procedures");
|
|
7773
|
+
const [facts, corrections, procedures] = await Promise.all([
|
|
7774
|
+
scanDir(factsDir),
|
|
7775
|
+
scanDir(correctionsDir),
|
|
7776
|
+
scanDir(proceduresDir)
|
|
7777
|
+
]);
|
|
7778
|
+
return [...facts, ...corrections, ...procedures];
|
|
7703
7779
|
}
|
|
7704
7780
|
|
|
7705
7781
|
// ../remnic-core/src/search/lancedb-backend.ts
|
|
@@ -8534,6 +8610,23 @@ var EmbedHelper = class {
|
|
|
8534
8610
|
import { createHash as createHash3 } from "crypto";
|
|
8535
8611
|
import os2 from "os";
|
|
8536
8612
|
import path6 from "path";
|
|
8613
|
+
|
|
8614
|
+
// ../remnic-core/src/abort-error.ts
|
|
8615
|
+
function abortError(message) {
|
|
8616
|
+
const err = new Error(message);
|
|
8617
|
+
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
8618
|
+
return err;
|
|
8619
|
+
}
|
|
8620
|
+
function isAbortError(err) {
|
|
8621
|
+
return err instanceof Error && err.name === "AbortError";
|
|
8622
|
+
}
|
|
8623
|
+
function throwIfAborted(signal, message = "operation aborted") {
|
|
8624
|
+
if (signal?.aborted) {
|
|
8625
|
+
throw abortError(message);
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8629
|
+
// ../remnic-core/src/qmd.ts
|
|
8537
8630
|
var QMD_TIMEOUT_MS = 3e4;
|
|
8538
8631
|
var QMD_DAEMON_TIMEOUT_MS = 8e3;
|
|
8539
8632
|
var QMD_PROBE_TIMEOUT_MS = 8e3;
|
|
@@ -8564,14 +8657,6 @@ function getGlobalQmdState() {
|
|
|
8564
8657
|
}
|
|
8565
8658
|
return g[QMD_GLOBAL_STATE_KEY];
|
|
8566
8659
|
}
|
|
8567
|
-
function abortError(message) {
|
|
8568
|
-
const err = new Error(message);
|
|
8569
|
-
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
8570
|
-
return err;
|
|
8571
|
-
}
|
|
8572
|
-
function isAbortError(err) {
|
|
8573
|
-
return err instanceof Error && err.name === "AbortError";
|
|
8574
|
-
}
|
|
8575
8660
|
function errorMessage(err) {
|
|
8576
8661
|
if (typeof err === "string") return err;
|
|
8577
8662
|
if (err instanceof Error) return err.message;
|
|
@@ -8592,11 +8677,6 @@ function isCallerCancellation(err, signal) {
|
|
|
8592
8677
|
function isDaemonTimeoutError(err) {
|
|
8593
8678
|
return /timed out/i.test(errorMessage(err));
|
|
8594
8679
|
}
|
|
8595
|
-
function throwIfAborted(signal, message = "operation aborted") {
|
|
8596
|
-
if (signal?.aborted) {
|
|
8597
|
-
throw abortError(message);
|
|
8598
|
-
}
|
|
8599
|
-
}
|
|
8600
8680
|
function sleepWithSignal(ms, signal) {
|
|
8601
8681
|
return new Promise((resolve, reject) => {
|
|
8602
8682
|
throwIfAborted(signal);
|
|
@@ -12983,6 +13063,7 @@ import path15 from "path";
|
|
|
12983
13063
|
var DAY_SUMMARY_CRON_ID = "engram-day-summary";
|
|
12984
13064
|
var GOVERNANCE_CRON_ID = "engram-nightly-governance";
|
|
12985
13065
|
var PROCEDURAL_MINING_CRON_ID = "engram-procedural-mining";
|
|
13066
|
+
var CONTRADICTION_SCAN_CRON_ID = "engram-contradiction-scan";
|
|
12986
13067
|
async function acquireCronJobsLock(jobsPath) {
|
|
12987
13068
|
const lockPath2 = `${jobsPath}.lock`;
|
|
12988
13069
|
const start = Date.now();
|
|
@@ -13119,6 +13200,30 @@ async function ensureProceduralMiningCron(jobsPath, options) {
|
|
|
13119
13200
|
delivery: { mode: "none" }
|
|
13120
13201
|
}));
|
|
13121
13202
|
}
|
|
13203
|
+
async function ensureContradictionScanCron(jobsPath, options) {
|
|
13204
|
+
const scheduleExpr = typeof options.scheduleExpr === "string" && options.scheduleExpr.trim().length > 0 ? options.scheduleExpr.trim() : "37 3 * * *";
|
|
13205
|
+
const agentId = typeof options.agentId === "string" && options.agentId.trim().length > 0 ? options.agentId.trim() : "main";
|
|
13206
|
+
return ensureCronJob(jobsPath, CONTRADICTION_SCAN_CRON_ID, () => ({
|
|
13207
|
+
id: CONTRADICTION_SCAN_CRON_ID,
|
|
13208
|
+
agentId,
|
|
13209
|
+
name: "Remnic Contradiction Scan (nightly)",
|
|
13210
|
+
enabled: true,
|
|
13211
|
+
schedule: {
|
|
13212
|
+
kind: "cron",
|
|
13213
|
+
expr: scheduleExpr,
|
|
13214
|
+
tz: options.timezone
|
|
13215
|
+
},
|
|
13216
|
+
sessionTarget: "isolated",
|
|
13217
|
+
wakeMode: "now",
|
|
13218
|
+
payload: {
|
|
13219
|
+
kind: "agentTurn",
|
|
13220
|
+
timeoutSeconds: 900,
|
|
13221
|
+
thinking: "off",
|
|
13222
|
+
message: "You are OpenClaw automation. Call tool `engram.contradiction_scan_run` with empty params. If successful output exactly NO_REPLY. On error output one concise line. Do NOT use message tool."
|
|
13223
|
+
},
|
|
13224
|
+
delivery: { mode: "none" }
|
|
13225
|
+
}));
|
|
13226
|
+
}
|
|
13122
13227
|
|
|
13123
13228
|
// ../remnic-core/src/lifecycle.ts
|
|
13124
13229
|
var DEFAULT_POLICY = {
|
|
@@ -14882,6 +14987,14 @@ function clampGraphRecallExpandedEntries(entries, maxEntries = 64) {
|
|
|
14882
14987
|
};
|
|
14883
14988
|
}).filter((item) => item.path.length > 0 && item.namespace.length > 0).slice(0, limit);
|
|
14884
14989
|
}
|
|
14990
|
+
function cloneTierExplain(tierExplain) {
|
|
14991
|
+
if (!tierExplain) return void 0;
|
|
14992
|
+
return structuredClone(tierExplain);
|
|
14993
|
+
}
|
|
14994
|
+
function cloneLastRecallSnapshot(snapshot) {
|
|
14995
|
+
if (!snapshot) return null;
|
|
14996
|
+
return structuredClone(snapshot);
|
|
14997
|
+
}
|
|
14885
14998
|
var DEFAULT_TIER_MIGRATION_STATUS = {
|
|
14886
14999
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
14887
15000
|
lastCycle: null,
|
|
@@ -14912,13 +15025,17 @@ var LastRecallStore = class {
|
|
|
14912
15025
|
}
|
|
14913
15026
|
}
|
|
14914
15027
|
get(sessionKey) {
|
|
14915
|
-
return this.state[sessionKey] ?? null;
|
|
15028
|
+
return cloneLastRecallSnapshot(this.state[sessionKey] ?? null);
|
|
14916
15029
|
}
|
|
14917
15030
|
getMostRecent() {
|
|
14918
15031
|
const snapshots = Object.values(this.state);
|
|
14919
15032
|
if (snapshots.length === 0) return null;
|
|
14920
|
-
snapshots.sort((a, b) =>
|
|
14921
|
-
|
|
15033
|
+
snapshots.sort((a, b) => {
|
|
15034
|
+
const byTime = b.recordedAt.localeCompare(a.recordedAt);
|
|
15035
|
+
if (byTime !== 0) return byTime;
|
|
15036
|
+
return a.sessionKey.localeCompare(b.sessionKey);
|
|
15037
|
+
});
|
|
15038
|
+
return cloneLastRecallSnapshot(snapshots[0] ?? null);
|
|
14922
15039
|
}
|
|
14923
15040
|
/**
|
|
14924
15041
|
* Persist last-recall snapshot and append an impression log entry.
|
|
@@ -14927,7 +15044,7 @@ var LastRecallStore = class {
|
|
|
14927
15044
|
async record(opts) {
|
|
14928
15045
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14929
15046
|
const queryHash = createHash4("sha256").update(opts.query).digest("hex");
|
|
14930
|
-
const
|
|
15047
|
+
const liveSnapshot = {
|
|
14931
15048
|
sessionKey: opts.sessionKey,
|
|
14932
15049
|
recordedAt: now,
|
|
14933
15050
|
queryHash,
|
|
@@ -14939,15 +15056,17 @@ var LastRecallStore = class {
|
|
|
14939
15056
|
requestedMode: opts.requestedMode,
|
|
14940
15057
|
source: opts.source,
|
|
14941
15058
|
fallbackUsed: opts.fallbackUsed,
|
|
14942
|
-
sourcesUsed: opts.sourcesUsed
|
|
14943
|
-
budgetsApplied: opts.budgetsApplied
|
|
15059
|
+
sourcesUsed: opts.sourcesUsed,
|
|
15060
|
+
budgetsApplied: opts.budgetsApplied,
|
|
14944
15061
|
latencyMs: opts.latencyMs,
|
|
14945
|
-
resultPaths: opts.resultPaths
|
|
15062
|
+
resultPaths: opts.resultPaths,
|
|
14946
15063
|
policyVersion: opts.policyVersion,
|
|
14947
15064
|
identityInjectionMode: opts.identityInjection?.mode,
|
|
14948
15065
|
identityInjectedChars: opts.identityInjection?.injectedChars,
|
|
14949
|
-
identityInjectionTruncated: opts.identityInjection?.truncated
|
|
15066
|
+
identityInjectionTruncated: opts.identityInjection?.truncated,
|
|
15067
|
+
tierExplain: opts.tierExplain
|
|
14950
15068
|
};
|
|
15069
|
+
const snapshot = cloneLastRecallSnapshot(liveSnapshot);
|
|
14951
15070
|
this.state[opts.sessionKey] = snapshot;
|
|
14952
15071
|
const keys = Object.keys(this.state);
|
|
14953
15072
|
if (keys.length > 50) {
|
|
@@ -14971,6 +15090,31 @@ var LastRecallStore = class {
|
|
|
14971
15090
|
}
|
|
14972
15091
|
}
|
|
14973
15092
|
}
|
|
15093
|
+
/**
|
|
15094
|
+
* Attach a RecallTierExplain block to the existing snapshot for a
|
|
15095
|
+
* session without rewriting the entire snapshot. Used by the
|
|
15096
|
+
* post-recall direct-answer annotation path (issue #518 slice 3c):
|
|
15097
|
+
* recallInternal records the snapshot first, then the orchestrator
|
|
15098
|
+
* fires the direct-answer tier in observation mode and annotates
|
|
15099
|
+
* the stored snapshot with whichever tier served the query.
|
|
15100
|
+
*
|
|
15101
|
+
* No-op when no snapshot exists for the given session; callers do
|
|
15102
|
+
* not need to guard on existence.
|
|
15103
|
+
*/
|
|
15104
|
+
async annotateTierExplain(sessionKey, tierExplain) {
|
|
15105
|
+
const current = this.state[sessionKey];
|
|
15106
|
+
if (!current) return;
|
|
15107
|
+
this.state[sessionKey] = {
|
|
15108
|
+
...current,
|
|
15109
|
+
tierExplain: cloneTierExplain(tierExplain)
|
|
15110
|
+
};
|
|
15111
|
+
try {
|
|
15112
|
+
await mkdir11(path20.dirname(this.statePath), { recursive: true });
|
|
15113
|
+
await writeFile12(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
15114
|
+
} catch (err) {
|
|
15115
|
+
log.debug(`last recall tier-explain annotate failed: ${err}`);
|
|
15116
|
+
}
|
|
15117
|
+
}
|
|
14974
15118
|
};
|
|
14975
15119
|
var TierMigrationStatusStore = class {
|
|
14976
15120
|
statePath;
|
|
@@ -20555,13 +20699,13 @@ async function readCueAnchors(options) {
|
|
|
20555
20699
|
return anchors;
|
|
20556
20700
|
}
|
|
20557
20701
|
async function searchHarmonicRetrieval(options) {
|
|
20558
|
-
|
|
20702
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20559
20703
|
const queryTokens = new Set(normalizeRecallTokens(options.query, ["what", "which"]));
|
|
20560
20704
|
if (queryTokens.size === 0 || options.maxResults <= 0) return [];
|
|
20561
20705
|
const nodes = await readAbstractionNodes(options);
|
|
20562
20706
|
const candidates = /* @__PURE__ */ new Map();
|
|
20563
20707
|
for (const node of nodes) {
|
|
20564
|
-
|
|
20708
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20565
20709
|
const { score, matchedFields } = scoreNode(node, queryTokens);
|
|
20566
20710
|
if (score <= 0) continue;
|
|
20567
20711
|
candidates.set(node.nodeId, {
|
|
@@ -20573,11 +20717,11 @@ async function searchHarmonicRetrieval(options) {
|
|
|
20573
20717
|
});
|
|
20574
20718
|
}
|
|
20575
20719
|
if (options.anchorsEnabled) {
|
|
20576
|
-
|
|
20720
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20577
20721
|
const anchors = await readCueAnchors(options);
|
|
20578
20722
|
const nodeIndex = new Map(nodes.map((node) => [node.nodeId, node]));
|
|
20579
20723
|
for (const anchor of anchors) {
|
|
20580
|
-
|
|
20724
|
+
throwIfAborted(options.abortSignal, "harmonic retrieval aborted");
|
|
20581
20725
|
const { score, matchedFields } = scoreAnchor(anchor, queryTokens);
|
|
20582
20726
|
if (score <= 0) continue;
|
|
20583
20727
|
for (const nodeRef of anchor.nodeRefs) {
|
|
@@ -20619,12 +20763,6 @@ async function searchHarmonicRetrieval(options) {
|
|
|
20619
20763
|
(left, right) => right.score - left.score || right.anchorScore - left.anchorScore || right.node.recordedAt.localeCompare(left.node.recordedAt)
|
|
20620
20764
|
).slice(0, options.maxResults);
|
|
20621
20765
|
}
|
|
20622
|
-
function throwIfAborted2(signal) {
|
|
20623
|
-
if (!signal?.aborted) return;
|
|
20624
|
-
const err = new Error("harmonic retrieval aborted");
|
|
20625
|
-
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
20626
|
-
throw err;
|
|
20627
|
-
}
|
|
20628
20766
|
|
|
20629
20767
|
// ../remnic-core/src/verified-recall.ts
|
|
20630
20768
|
function createReadOnlyBoxBuilder(memoryDir) {
|
|
@@ -27211,15 +27349,9 @@ function fingerprintEntitySynthesisEvidence(entity) {
|
|
|
27211
27349
|
fingerprint.update(fingerprintEntityStructuredFacts(entity) ?? "");
|
|
27212
27350
|
return fingerprint.digest("hex");
|
|
27213
27351
|
}
|
|
27214
|
-
|
|
27215
|
-
const err = new Error(message);
|
|
27216
|
-
Object.defineProperty(err, "name", { value: "AbortError" });
|
|
27217
|
-
return err;
|
|
27218
|
-
}
|
|
27352
|
+
var abortRecallError = abortError;
|
|
27219
27353
|
function throwIfRecallAborted(signal, message = "recall aborted") {
|
|
27220
|
-
|
|
27221
|
-
throw abortRecallError(message);
|
|
27222
|
-
}
|
|
27354
|
+
throwIfAborted(signal, message);
|
|
27223
27355
|
}
|
|
27224
27356
|
async function raceRecallAbort(promise, signal, message = "recall aborted") {
|
|
27225
27357
|
throwIfRecallAborted(signal, message);
|
|
@@ -27853,6 +27985,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
27853
27985
|
);
|
|
27854
27986
|
this.judgeVerdictCache = createVerdictCache();
|
|
27855
27987
|
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
27988
|
+
this.localLlm.disableThinking = config.localLlmDisableThinking;
|
|
27856
27989
|
this.fastLlm = config.localLlmFastEnabled ? (() => {
|
|
27857
27990
|
const client = new LocalLlmClient(
|
|
27858
27991
|
{
|
|
@@ -28353,6 +28486,13 @@ var Orchestrator = class _Orchestrator {
|
|
|
28353
28486
|
log.debug(`procedural mining cron auto-register failed (non-fatal): ${err}`);
|
|
28354
28487
|
}
|
|
28355
28488
|
}
|
|
28489
|
+
if (this.config.contradictionScan?.enabled) {
|
|
28490
|
+
try {
|
|
28491
|
+
await this.autoRegisterContradictionScanCron();
|
|
28492
|
+
} catch (err) {
|
|
28493
|
+
log.debug(`contradiction scan cron auto-register failed (non-fatal): ${err}`);
|
|
28494
|
+
}
|
|
28495
|
+
}
|
|
28356
28496
|
log.info("orchestrator initialized (full \u2014 deferred steps complete)");
|
|
28357
28497
|
}
|
|
28358
28498
|
/**
|
|
@@ -28520,6 +28660,26 @@ var Orchestrator = class _Orchestrator {
|
|
|
28520
28660
|
log.debug(`procedural mining cron auto-register error: ${err}`);
|
|
28521
28661
|
}
|
|
28522
28662
|
}
|
|
28663
|
+
async autoRegisterContradictionScanCron() {
|
|
28664
|
+
const home = resolveHomeDir();
|
|
28665
|
+
const jobsPath = path43.join(home, ".openclaw", "cron", "jobs.json");
|
|
28666
|
+
try {
|
|
28667
|
+
if (!existsSync8(jobsPath)) {
|
|
28668
|
+
log.debug("contradiction scan cron: jobs.json not found, skipping auto-register");
|
|
28669
|
+
return;
|
|
28670
|
+
}
|
|
28671
|
+
const created = await ensureContradictionScanCron(jobsPath, {
|
|
28672
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
28673
|
+
});
|
|
28674
|
+
if (created.created) {
|
|
28675
|
+
log.info(`contradiction scan cron auto-registered (${created.jobId})`);
|
|
28676
|
+
} else {
|
|
28677
|
+
log.debug("contradiction scan cron already exists, skipping auto-register");
|
|
28678
|
+
}
|
|
28679
|
+
} catch (err) {
|
|
28680
|
+
log.debug(`contradiction scan cron auto-register error: ${err}`);
|
|
28681
|
+
}
|
|
28682
|
+
}
|
|
28523
28683
|
async applyBehaviorRuntimePolicy(state) {
|
|
28524
28684
|
const result = await this.policyRuntime.applyFromBehaviorState(state);
|
|
28525
28685
|
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
@@ -28647,7 +28807,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
28647
28807
|
);
|
|
28648
28808
|
return result;
|
|
28649
28809
|
}
|
|
28650
|
-
const { FallbackLlmClient: FallbackLlmClient2 } = await import("./fallback-llm-
|
|
28810
|
+
const { FallbackLlmClient: FallbackLlmClient2 } = await import("./fallback-llm-QEAPMDW7.js");
|
|
28651
28811
|
const useGateway = this.config.modelSource === "gateway";
|
|
28652
28812
|
const modelSetting = this.config.semanticConsolidationModel;
|
|
28653
28813
|
if (modelSetting === "fast" && this.fastLlm && !useGateway) {
|
|
@@ -31036,7 +31196,7 @@ ${trimmedBody}`;
|
|
|
31036
31196
|
return null;
|
|
31037
31197
|
}
|
|
31038
31198
|
try {
|
|
31039
|
-
const { getCalibrationRulesForRecall, buildCalibrationRecallSection } = await import("./calibration-
|
|
31199
|
+
const { getCalibrationRulesForRecall, buildCalibrationRecallSection } = await import("./calibration-BAC7KNKR.js");
|
|
31040
31200
|
const rules = await getCalibrationRulesForRecall(this.config.memoryDir);
|
|
31041
31201
|
if (rules.length === 0) {
|
|
31042
31202
|
recordRecallSectionMetric({
|
|
@@ -31924,7 +32084,7 @@ ${formatted}`;
|
|
|
31924
32084
|
if (!this.isRecallSectionEnabled("procedure-recall", true)) return null;
|
|
31925
32085
|
try {
|
|
31926
32086
|
return await buildProcedureRecallSection(
|
|
31927
|
-
|
|
32087
|
+
profileStorage,
|
|
31928
32088
|
retrievalQuery,
|
|
31929
32089
|
this.config
|
|
31930
32090
|
);
|
|
@@ -33337,7 +33497,7 @@ _Context: ${topQuestion.context}_`
|
|
|
33337
33497
|
if (!this.queueProcessing) {
|
|
33338
33498
|
this.queueProcessing = true;
|
|
33339
33499
|
this.processQueue().catch((err) => {
|
|
33340
|
-
|
|
33500
|
+
this.logExtractionQueueFailure(err, "processor");
|
|
33341
33501
|
this.queueProcessing = false;
|
|
33342
33502
|
});
|
|
33343
33503
|
}
|
|
@@ -33395,12 +33555,38 @@ ${normalized}`).digest("hex");
|
|
|
33395
33555
|
try {
|
|
33396
33556
|
await task();
|
|
33397
33557
|
} catch (err) {
|
|
33398
|
-
|
|
33558
|
+
this.logExtractionQueueFailure(err, "task");
|
|
33399
33559
|
}
|
|
33400
33560
|
}
|
|
33401
33561
|
}
|
|
33402
33562
|
this.queueProcessing = false;
|
|
33403
33563
|
}
|
|
33564
|
+
/**
|
|
33565
|
+
* Classify + log a failure from either the per-task catch inside
|
|
33566
|
+
* `processQueue()` or the outer `processQueue().catch(...)` in
|
|
33567
|
+
* `queueBufferedExtraction()`. Issue #549: `throwIfRecallAborted`
|
|
33568
|
+
* (used throughout `runExtraction`) raises an Error whose `name` is
|
|
33569
|
+
* `"AbortError"`. That path fires when `before_reset` aborts a
|
|
33570
|
+
* queued task to avoid duplicate extraction — it is intentional
|
|
33571
|
+
* cancellation, not a failure. Downgrading the log to debug
|
|
33572
|
+
* prevents spurious `error`-level lines that routinely appear
|
|
33573
|
+
* right next to a successful `persisted: N facts, M entities` log
|
|
33574
|
+
* and that confuse operators into thinking extraction is broken.
|
|
33575
|
+
* Genuine extraction failures (network, parse, I/O) still log at
|
|
33576
|
+
* `error`.
|
|
33577
|
+
*
|
|
33578
|
+
* Source differentiates the two call sites so the log message
|
|
33579
|
+
* names the right layer (`task` vs `processor`).
|
|
33580
|
+
*/
|
|
33581
|
+
logExtractionQueueFailure(err, source) {
|
|
33582
|
+
const aborted = source === "task" ? "background extraction task aborted (session transition)" : "background extraction queue processor aborted (session transition)";
|
|
33583
|
+
const failed = source === "task" ? "background extraction task failed" : "background extraction queue processor failed";
|
|
33584
|
+
if (isAbortError(err)) {
|
|
33585
|
+
log.debug(aborted);
|
|
33586
|
+
} else {
|
|
33587
|
+
log.error(failed, err);
|
|
33588
|
+
}
|
|
33589
|
+
}
|
|
33404
33590
|
async runExtraction(turns, options = {}) {
|
|
33405
33591
|
log.debug(`running extraction on ${turns.length} turns`);
|
|
33406
33592
|
const clearBufferAfterExtraction = options.clearBufferAfterExtraction ?? true;
|
|
@@ -33412,12 +33598,12 @@ ${normalized}`).digest("hex");
|
|
|
33412
33598
|
throw new Error(`replay extraction deadline exceeded (${stage})`);
|
|
33413
33599
|
}
|
|
33414
33600
|
};
|
|
33415
|
-
const
|
|
33601
|
+
const throwIfAborted2 = (stage) => {
|
|
33416
33602
|
throwIfRecallAborted(options.abortSignal, `extraction aborted (${stage})`);
|
|
33417
33603
|
};
|
|
33418
33604
|
const clearBuffer = async (options2) => {
|
|
33419
33605
|
if (options2?.ignoreAbort !== true) {
|
|
33420
|
-
|
|
33606
|
+
throwIfAborted2("before_clear_buffer");
|
|
33421
33607
|
}
|
|
33422
33608
|
if (clearBufferAfterExtraction) {
|
|
33423
33609
|
await this.buffer.clearAfterExtraction(bufferKey);
|
|
@@ -33436,7 +33622,7 @@ ${normalized}`).digest("hex");
|
|
|
33436
33622
|
content: t.content.trim().slice(0, this.config.extractionMaxTurnChars)
|
|
33437
33623
|
})).filter((t) => t.content.length > 0);
|
|
33438
33624
|
throwIfDeadlineExceeded("before_extract");
|
|
33439
|
-
|
|
33625
|
+
throwIfAborted2("before_extract");
|
|
33440
33626
|
const userTurns = normalizedTurns.filter((t) => t.role === "user");
|
|
33441
33627
|
const totalChars = normalizedTurns.reduce(
|
|
33442
33628
|
(sum, t) => sum + t.content.length,
|
|
@@ -33481,7 +33667,7 @@ ${normalized}`).digest("hex");
|
|
|
33481
33667
|
"extraction aborted (during_extract)"
|
|
33482
33668
|
);
|
|
33483
33669
|
throwIfDeadlineExceeded("before_persist");
|
|
33484
|
-
|
|
33670
|
+
throwIfAborted2("before_persist");
|
|
33485
33671
|
if (!result) {
|
|
33486
33672
|
log.warn("runExtraction: extraction returned null/undefined");
|
|
33487
33673
|
await clearBuffer();
|
|
@@ -34194,16 +34380,6 @@ ${normalized}`).digest("hex");
|
|
|
34194
34380
|
}
|
|
34195
34381
|
fact.tags = Array.isArray(fact.tags) ? fact.tags.filter((t) => typeof t === "string") : [];
|
|
34196
34382
|
fact.confidence = typeof fact.confidence === "number" ? fact.confidence : 0.7;
|
|
34197
|
-
if (this.contentHashIndex) {
|
|
34198
|
-
const canonicalContent = citationEnabled && hasCitationForTemplate(fact.content, citationTemplate) ? stripCitationForTemplate(fact.content, citationTemplate) : fact.content;
|
|
34199
|
-
if (this.contentHashIndex.has(canonicalContent)) {
|
|
34200
|
-
log.debug(
|
|
34201
|
-
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}\u2026"`
|
|
34202
|
-
);
|
|
34203
|
-
dedupedCount++;
|
|
34204
|
-
continue;
|
|
34205
|
-
}
|
|
34206
|
-
}
|
|
34207
34383
|
let writeCategory = fact.category;
|
|
34208
34384
|
let targetStorage = storage;
|
|
34209
34385
|
let routedRuleId;
|
|
@@ -34228,6 +34404,15 @@ ${normalized}`).digest("hex");
|
|
|
34228
34404
|
);
|
|
34229
34405
|
}
|
|
34230
34406
|
}
|
|
34407
|
+
const canonicalContentForHash = citationEnabled && hasCitationForTemplate(fact.content, citationTemplate) ? stripCitationForTemplate(fact.content, citationTemplate) : fact.content;
|
|
34408
|
+
const contentHashDedupKey = writeCategory === "procedure" ? buildProcedurePersistBody(fact.content, fact.procedureSteps) : canonicalContentForHash;
|
|
34409
|
+
if (this.contentHashIndex && this.contentHashIndex.has(contentHashDedupKey)) {
|
|
34410
|
+
log.debug(
|
|
34411
|
+
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}\u2026"`
|
|
34412
|
+
);
|
|
34413
|
+
dedupedCount++;
|
|
34414
|
+
continue;
|
|
34415
|
+
}
|
|
34231
34416
|
const importance = scoreImportance(
|
|
34232
34417
|
fact.content,
|
|
34233
34418
|
writeCategory,
|
|
@@ -34271,16 +34456,10 @@ ${normalized}`).digest("hex");
|
|
|
34271
34456
|
procedureSteps: fact.procedureSteps
|
|
34272
34457
|
});
|
|
34273
34458
|
if (!procGate.durable) {
|
|
34274
|
-
|
|
34275
|
-
|
|
34276
|
-
|
|
34277
|
-
|
|
34278
|
-
} else {
|
|
34279
|
-
log.debug(
|
|
34280
|
-
`extraction-procedure-gate: rejected "${fact.content.slice(0, 60)}\u2026" reason="${procGate.reason}"`
|
|
34281
|
-
);
|
|
34282
|
-
continue;
|
|
34283
|
-
}
|
|
34459
|
+
log.debug(
|
|
34460
|
+
`extraction-procedure-gate: rejected "${fact.content.slice(0, 60)}\u2026" reason="${procGate.reason}"`
|
|
34461
|
+
);
|
|
34462
|
+
continue;
|
|
34284
34463
|
}
|
|
34285
34464
|
}
|
|
34286
34465
|
let pendingSemanticSkip = null;
|
|
@@ -34677,7 +34856,8 @@ ${normalized}`).digest("hex");
|
|
|
34677
34856
|
}
|
|
34678
34857
|
if (this.contentHashIndex) {
|
|
34679
34858
|
const canonicalFactContent = citationEnabled && hasCitationForTemplate(fact.content, citationTemplate) ? stripCitationForTemplate(fact.content, citationTemplate) : fact.content;
|
|
34680
|
-
|
|
34859
|
+
const hashRegisterKey = writeCategory === "procedure" ? buildProcedurePersistBody(fact.content, fact.procedureSteps) : canonicalFactContent;
|
|
34860
|
+
this.contentHashIndex.add(hashRegisterKey);
|
|
34681
34861
|
}
|
|
34682
34862
|
}
|
|
34683
34863
|
for (const entity of entities) {
|
|
@@ -45363,6 +45543,7 @@ function sleep2(ms) {
|
|
|
45363
45543
|
}
|
|
45364
45544
|
|
|
45365
45545
|
// ../remnic-core/src/procedural/procedure-miner.ts
|
|
45546
|
+
var PROCEDURE_CLUSTER_ATTR_MAX = 500;
|
|
45366
45547
|
function clusterKey(record) {
|
|
45367
45548
|
const goal = record.goal.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 120);
|
|
45368
45549
|
const refs = [...record.entityRefs ?? []].map((r) => r.trim().toLowerCase()).sort();
|
|
@@ -45395,11 +45576,12 @@ function pseudoStepsFromCluster(group) {
|
|
|
45395
45576
|
}));
|
|
45396
45577
|
}
|
|
45397
45578
|
async function hasExistingClusterWrite(storage, cluster) {
|
|
45579
|
+
const clusterKey2 = cluster.slice(0, PROCEDURE_CLUSTER_ATTR_MAX);
|
|
45398
45580
|
const memories = await storage.readAllMemories();
|
|
45399
45581
|
for (const m of memories) {
|
|
45400
45582
|
if (m.frontmatter.category !== "procedure") continue;
|
|
45401
45583
|
const c = m.frontmatter.structuredAttributes?.procedure_cluster;
|
|
45402
|
-
if (c ===
|
|
45584
|
+
if (c === clusterKey2) return true;
|
|
45403
45585
|
}
|
|
45404
45586
|
return false;
|
|
45405
45587
|
}
|
|
@@ -45411,8 +45593,10 @@ async function runProcedureMining(options) {
|
|
|
45411
45593
|
if (cfg.minOccurrences <= 0) {
|
|
45412
45594
|
return { clustersProcessed: 0, proceduresWritten: 0, skippedReason: "minOccurrences_zero" };
|
|
45413
45595
|
}
|
|
45596
|
+
const trajectoryDir = typeof options.config.causalTrajectoryStoreDir === "string" && options.config.causalTrajectoryStoreDir.trim().length > 0 ? options.config.causalTrajectoryStoreDir.trim() : void 0;
|
|
45414
45597
|
const { trajectories } = await readCausalTrajectoryRecords({
|
|
45415
|
-
memoryDir: options.memoryDir
|
|
45598
|
+
memoryDir: options.memoryDir,
|
|
45599
|
+
causalTrajectoryStoreDir: trajectoryDir
|
|
45416
45600
|
});
|
|
45417
45601
|
const recent = filterTrajectoriesByLookbackDays(trajectories, cfg.lookbackDays);
|
|
45418
45602
|
const clusters = /* @__PURE__ */ new Map();
|
|
@@ -45443,7 +45627,7 @@ async function runProcedureMining(options) {
|
|
|
45443
45627
|
status: promote ? "active" : "pending_review",
|
|
45444
45628
|
tags: ["procedure-miner", "causal-trajectory"],
|
|
45445
45629
|
structuredAttributes: {
|
|
45446
|
-
procedure_cluster: key.slice(0,
|
|
45630
|
+
procedure_cluster: key.slice(0, PROCEDURE_CLUSTER_ATTR_MAX),
|
|
45447
45631
|
trajectory_ids: group.map((g) => g.trajectoryId).join(",").slice(0, 1900),
|
|
45448
45632
|
trajectory_count: String(group.length),
|
|
45449
45633
|
success_rate: rate.toFixed(4)
|
|
@@ -48064,6 +48248,25 @@ ${next}`);
|
|
|
48064
48248
|
}
|
|
48065
48249
|
return { submitted: memoryIds.length, matched: matchedIds.length };
|
|
48066
48250
|
}
|
|
48251
|
+
// ── Contradiction Review (issue #520) ──────────────────────────────────────
|
|
48252
|
+
get memoryDir() {
|
|
48253
|
+
return this.orchestrator.config.memoryDir;
|
|
48254
|
+
}
|
|
48255
|
+
get storageRef() {
|
|
48256
|
+
return this.orchestrator.storage;
|
|
48257
|
+
}
|
|
48258
|
+
get configRef() {
|
|
48259
|
+
return this.orchestrator.config;
|
|
48260
|
+
}
|
|
48261
|
+
get localLlmRef() {
|
|
48262
|
+
return this.orchestrator.localLlm ?? null;
|
|
48263
|
+
}
|
|
48264
|
+
get fallbackLlmRef() {
|
|
48265
|
+
return null;
|
|
48266
|
+
}
|
|
48267
|
+
get embeddingLookupRef() {
|
|
48268
|
+
return void 0;
|
|
48269
|
+
}
|
|
48067
48270
|
};
|
|
48068
48271
|
|
|
48069
48272
|
// ../remnic-core/src/access-http.ts
|
|
@@ -48812,7 +49015,45 @@ var EngramMcpServer = class {
|
|
|
48812
49015
|
},
|
|
48813
49016
|
additionalProperties: false
|
|
48814
49017
|
}
|
|
48815
|
-
}] : []
|
|
49018
|
+
}] : [],
|
|
49019
|
+
// ── Contradiction Review (issue #520) ────────────────────────────────
|
|
49020
|
+
{
|
|
49021
|
+
name: "engram.review_list",
|
|
49022
|
+
description: "List contradiction review items pending user resolution.",
|
|
49023
|
+
inputSchema: {
|
|
49024
|
+
type: "object",
|
|
49025
|
+
properties: {
|
|
49026
|
+
filter: { type: "string", enum: ["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"], description: "Filter by verdict type. Default: unresolved." },
|
|
49027
|
+
namespace: { type: "string" },
|
|
49028
|
+
limit: { type: "number", description: "Max items to return (default 50)." }
|
|
49029
|
+
},
|
|
49030
|
+
additionalProperties: false
|
|
49031
|
+
}
|
|
49032
|
+
},
|
|
49033
|
+
{
|
|
49034
|
+
name: "engram.review_resolve",
|
|
49035
|
+
description: "Resolve a contradiction pair with a chosen verb.",
|
|
49036
|
+
inputSchema: {
|
|
49037
|
+
type: "object",
|
|
49038
|
+
properties: {
|
|
49039
|
+
pairId: { type: "string", description: "The contradiction pair ID to resolve." },
|
|
49040
|
+
verb: { type: "string", enum: ["keep-a", "keep-b", "merge", "both-valid", "needs-more-context"], description: "Resolution action." }
|
|
49041
|
+
},
|
|
49042
|
+
required: ["pairId", "verb"],
|
|
49043
|
+
additionalProperties: false
|
|
49044
|
+
}
|
|
49045
|
+
},
|
|
49046
|
+
{
|
|
49047
|
+
name: "engram.contradiction_scan_run",
|
|
49048
|
+
description: "Run an on-demand contradiction scan over the memory corpus.",
|
|
49049
|
+
inputSchema: {
|
|
49050
|
+
type: "object",
|
|
49051
|
+
properties: {
|
|
49052
|
+
namespace: { type: "string" }
|
|
49053
|
+
},
|
|
49054
|
+
additionalProperties: false
|
|
49055
|
+
}
|
|
49056
|
+
}
|
|
48816
49057
|
].flatMap((tool) => withToolAliases(tool));
|
|
48817
49058
|
}
|
|
48818
49059
|
service;
|
|
@@ -49419,6 +49660,39 @@ ${body}`;
|
|
|
49419
49660
|
principal: effectivePrincipal
|
|
49420
49661
|
});
|
|
49421
49662
|
}
|
|
49663
|
+
// ── Contradiction Review (issue #520) ──────────────────────────────────
|
|
49664
|
+
case "engram.review_list":
|
|
49665
|
+
case "remnic.review_list": {
|
|
49666
|
+
const { listPairs } = await import("./contradiction-review-SVGBS3V5.js");
|
|
49667
|
+
const filter = typeof args.filter === "string" ? args.filter : "unresolved";
|
|
49668
|
+
const ns = typeof args.namespace === "string" ? args.namespace : void 0;
|
|
49669
|
+
const limit = typeof args.limit === "number" ? args.limit : 50;
|
|
49670
|
+
return listPairs(this.service.memoryDir, { filter, namespace: ns, limit });
|
|
49671
|
+
}
|
|
49672
|
+
case "engram.review_resolve":
|
|
49673
|
+
case "remnic.review_resolve": {
|
|
49674
|
+
const pairId = typeof args.pairId === "string" ? args.pairId : "";
|
|
49675
|
+
const verb = typeof args.verb === "string" ? args.verb : "";
|
|
49676
|
+
if (!pairId) throw new Error("pairId is required");
|
|
49677
|
+
if (!verb) throw new Error("verb is required");
|
|
49678
|
+
const { isValidResolutionVerb } = await import("./resolution-YITUVUTH.js");
|
|
49679
|
+
if (!isValidResolutionVerb(verb)) throw new Error(`Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context`);
|
|
49680
|
+
const { executeResolution } = await import("./resolution-YITUVUTH.js");
|
|
49681
|
+
return executeResolution(this.service.memoryDir, this.service.storageRef, pairId, verb);
|
|
49682
|
+
}
|
|
49683
|
+
case "engram.contradiction_scan_run":
|
|
49684
|
+
case "remnic.contradiction_scan_run": {
|
|
49685
|
+
const { runContradictionScan } = await import("./contradiction-scan-LRRLWUOS.js");
|
|
49686
|
+
return runContradictionScan({
|
|
49687
|
+
storage: this.service.storageRef,
|
|
49688
|
+
config: this.service.configRef,
|
|
49689
|
+
memoryDir: this.service.memoryDir,
|
|
49690
|
+
embeddingLookup: this.service.embeddingLookupRef,
|
|
49691
|
+
localLlm: this.service.localLlmRef,
|
|
49692
|
+
fallbackLlm: this.service.fallbackLlmRef,
|
|
49693
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0
|
|
49694
|
+
});
|
|
49695
|
+
}
|
|
49422
49696
|
default:
|
|
49423
49697
|
throw new Error(`unknown tool: ${name}`);
|
|
49424
49698
|
}
|
|
@@ -50266,6 +50540,67 @@ var EngramAccessHttpServer = class {
|
|
|
50266
50540
|
});
|
|
50267
50541
|
return;
|
|
50268
50542
|
}
|
|
50543
|
+
if (req.method === "GET" && pathname === "/engram/v1/review/contradictions") {
|
|
50544
|
+
const VALID_FILTERS = /* @__PURE__ */ new Set(["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"]);
|
|
50545
|
+
const rawFilter = parsed.searchParams.get("filter") ?? "unresolved";
|
|
50546
|
+
if (!VALID_FILTERS.has(rawFilter)) {
|
|
50547
|
+
this.respondJson(res, 400, { error: `Invalid filter '${rawFilter}'. Valid: ${[...VALID_FILTERS].join(", ")}` });
|
|
50548
|
+
return;
|
|
50549
|
+
}
|
|
50550
|
+
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
50551
|
+
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
|
|
50552
|
+
const { listPairs } = await import("./contradiction-review-SVGBS3V5.js");
|
|
50553
|
+
const result = listPairs(this.service.memoryDir, {
|
|
50554
|
+
filter: rawFilter,
|
|
50555
|
+
namespace,
|
|
50556
|
+
limit: Number.isFinite(limitRaw) ? limitRaw : 50
|
|
50557
|
+
});
|
|
50558
|
+
this.respondJson(res, 200, result);
|
|
50559
|
+
return;
|
|
50560
|
+
}
|
|
50561
|
+
if (req.method === "GET" && pathname.startsWith("/engram/v1/review/contradictions/")) {
|
|
50562
|
+
const pairId = pathname.split("/").pop() ?? "";
|
|
50563
|
+
const { readPair } = await import("./contradiction-review-SVGBS3V5.js");
|
|
50564
|
+
const pair = readPair(this.service.memoryDir, pairId);
|
|
50565
|
+
if (!pair) {
|
|
50566
|
+
this.respondJson(res, 404, { error: "pair_not_found" });
|
|
50567
|
+
return;
|
|
50568
|
+
}
|
|
50569
|
+
this.respondJson(res, 200, pair);
|
|
50570
|
+
return;
|
|
50571
|
+
}
|
|
50572
|
+
if (req.method === "POST" && pathname === "/engram/v1/review/resolve") {
|
|
50573
|
+
const body = await this.readJsonBody(req);
|
|
50574
|
+
const pairId = typeof body.pairId === "string" ? body.pairId : "";
|
|
50575
|
+
const verb = typeof body.verb === "string" ? body.verb : "";
|
|
50576
|
+
if (!pairId || !verb) {
|
|
50577
|
+
this.respondJson(res, 400, { error: "pairId and verb are required" });
|
|
50578
|
+
return;
|
|
50579
|
+
}
|
|
50580
|
+
const { isValidResolutionVerb, executeResolution } = await import("./resolution-YITUVUTH.js");
|
|
50581
|
+
if (!isValidResolutionVerb(verb)) {
|
|
50582
|
+
this.respondJson(res, 400, { error: `Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context` });
|
|
50583
|
+
return;
|
|
50584
|
+
}
|
|
50585
|
+
const result = await executeResolution(this.service.memoryDir, this.service.storageRef, pairId, verb);
|
|
50586
|
+
this.respondJson(res, 200, result);
|
|
50587
|
+
return;
|
|
50588
|
+
}
|
|
50589
|
+
if (req.method === "POST" && pathname === "/engram/v1/contradiction-scan") {
|
|
50590
|
+
const body = await this.readJsonBody(req);
|
|
50591
|
+
const { runContradictionScan } = await import("./contradiction-scan-LRRLWUOS.js");
|
|
50592
|
+
const result = await runContradictionScan({
|
|
50593
|
+
storage: this.service.storageRef,
|
|
50594
|
+
config: this.service.configRef,
|
|
50595
|
+
memoryDir: this.service.memoryDir,
|
|
50596
|
+
embeddingLookup: this.service.embeddingLookupRef,
|
|
50597
|
+
localLlm: this.service.localLlmRef,
|
|
50598
|
+
fallbackLlm: this.service.fallbackLlmRef,
|
|
50599
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0
|
|
50600
|
+
});
|
|
50601
|
+
this.respondJson(res, 200, result);
|
|
50602
|
+
return;
|
|
50603
|
+
}
|
|
50269
50604
|
this.respondJson(res, 404, { error: "not_found", code: "not_found" });
|
|
50270
50605
|
}
|
|
50271
50606
|
async handleMcpRequest(req, res) {
|
|
@@ -51536,7 +51871,7 @@ async function runSemanticRulePromoteCliCommand(options) {
|
|
|
51536
51871
|
});
|
|
51537
51872
|
}
|
|
51538
51873
|
async function runCompoundingPromoteCliCommand(options) {
|
|
51539
|
-
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-
|
|
51874
|
+
const { CompoundingEngine: CompoundingEngine2 } = await import("./engine-WGNTTFYE.js");
|
|
51540
51875
|
const config = parseConfig({
|
|
51541
51876
|
memoryDir: options.memoryDir,
|
|
51542
51877
|
qmdEnabled: false,
|
|
@@ -55146,6 +55481,94 @@ Semantic consolidation complete. clusters=${result.clustersFound}, consolidated=
|
|
|
55146
55481
|
}
|
|
55147
55482
|
console.log(orchestrator.summarizer.formatForRecall(summaries, summaries.length));
|
|
55148
55483
|
});
|
|
55484
|
+
const reviewCmd = cmd.command("review").description("Manage contradiction review queue");
|
|
55485
|
+
reviewCmd.command("list").description("List contradiction review items").option("--filter <type>", "Filter: all, unresolved, contradicts, independent, duplicates, needs-user", "unresolved").option("--namespace <ns>", "Filter by namespace").option("--limit <n>", "Max items (default 50)", "50").action(async (...args) => {
|
|
55486
|
+
const options = args[0] ?? {};
|
|
55487
|
+
const filter = options.filter ?? "unresolved";
|
|
55488
|
+
const validFilters = ["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"];
|
|
55489
|
+
if (!validFilters.includes(filter)) {
|
|
55490
|
+
console.error(`Invalid filter: ${filter}. Must be one of: ${validFilters.join(", ")}`);
|
|
55491
|
+
process.exit(1);
|
|
55492
|
+
}
|
|
55493
|
+
const limit = parseInt(options.limit ?? "50", 10);
|
|
55494
|
+
const { listPairs } = await import("./contradiction-review-SVGBS3V5.js");
|
|
55495
|
+
const result = listPairs(orchestrator.config.memoryDir, {
|
|
55496
|
+
filter,
|
|
55497
|
+
namespace: options.namespace,
|
|
55498
|
+
limit: Number.isFinite(limit) ? limit : 50
|
|
55499
|
+
});
|
|
55500
|
+
if (result.pairs.length === 0) {
|
|
55501
|
+
console.log("No review items found.");
|
|
55502
|
+
return;
|
|
55503
|
+
}
|
|
55504
|
+
console.log(`Found ${result.total} item(s) (${result.durationMs}ms):
|
|
55505
|
+
`);
|
|
55506
|
+
for (const pair of result.pairs) {
|
|
55507
|
+
console.log(` [${pair.verdict}] ${pair.pairId}`);
|
|
55508
|
+
console.log(` Memories: ${pair.memoryIds.join(", ")}`);
|
|
55509
|
+
console.log(` Rationale: ${pair.rationale}`);
|
|
55510
|
+
console.log(` Confidence: ${pair.confidence.toFixed(2)}`);
|
|
55511
|
+
if (pair.resolution) console.log(` Resolution: ${pair.resolution}`);
|
|
55512
|
+
console.log();
|
|
55513
|
+
}
|
|
55514
|
+
});
|
|
55515
|
+
reviewCmd.command("show <pairId>").description("Show details of a contradiction pair").action(async (...args) => {
|
|
55516
|
+
const pairId = typeof args[0] === "string" ? args[0] : "";
|
|
55517
|
+
if (!pairId) {
|
|
55518
|
+
console.error("pairId is required");
|
|
55519
|
+
process.exit(1);
|
|
55520
|
+
}
|
|
55521
|
+
const { readPair } = await import("./contradiction-review-SVGBS3V5.js");
|
|
55522
|
+
const pair = readPair(orchestrator.config.memoryDir, pairId);
|
|
55523
|
+
if (!pair) {
|
|
55524
|
+
console.error(`Pair ${pairId} not found.`);
|
|
55525
|
+
process.exit(1);
|
|
55526
|
+
}
|
|
55527
|
+
console.log(JSON.stringify(pair, null, 2));
|
|
55528
|
+
});
|
|
55529
|
+
reviewCmd.command("resolve <pairId>").description("Resolve a contradiction pair").requiredOption("--verb <verb>", "Resolution verb: keep-a, keep-b, merge, both-valid, needs-more-context").action(async (...args) => {
|
|
55530
|
+
const pairId = typeof args[0] === "string" ? args[0] : "";
|
|
55531
|
+
const cmdOpts = args[1] ?? {};
|
|
55532
|
+
if (!pairId) {
|
|
55533
|
+
console.error("pairId is required");
|
|
55534
|
+
process.exit(1);
|
|
55535
|
+
}
|
|
55536
|
+
const verb = cmdOpts.verb;
|
|
55537
|
+
if (!verb) {
|
|
55538
|
+
console.error("--verb is required. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context");
|
|
55539
|
+
process.exit(1);
|
|
55540
|
+
}
|
|
55541
|
+
const { isValidResolutionVerb, executeResolution } = await import("./resolution-YITUVUTH.js");
|
|
55542
|
+
if (!isValidResolutionVerb(verb)) {
|
|
55543
|
+
console.error(`Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context`);
|
|
55544
|
+
process.exit(1);
|
|
55545
|
+
}
|
|
55546
|
+
const result = await executeResolution(orchestrator.config.memoryDir, orchestrator.storage, pairId, verb);
|
|
55547
|
+
console.log(result.message);
|
|
55548
|
+
if (result.affectedIds.length > 0) {
|
|
55549
|
+
console.log(`Affected: ${result.affectedIds.join(", ")}`);
|
|
55550
|
+
}
|
|
55551
|
+
});
|
|
55552
|
+
reviewCmd.command("scan").description("Run an on-demand contradiction scan").option("--namespace <ns>", "Namespace to scan").action(async (...args) => {
|
|
55553
|
+
const options = args[0] ?? {};
|
|
55554
|
+
const { runContradictionScan } = await import("./contradiction-scan-LRRLWUOS.js");
|
|
55555
|
+
console.log("Running contradiction scan...");
|
|
55556
|
+
const result = await runContradictionScan({
|
|
55557
|
+
storage: orchestrator.storage,
|
|
55558
|
+
config: orchestrator.config,
|
|
55559
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
55560
|
+
embeddingLookup: void 0,
|
|
55561
|
+
localLlm: orchestrator.localLlm ?? null,
|
|
55562
|
+
fallbackLlm: null,
|
|
55563
|
+
namespace: options.namespace
|
|
55564
|
+
});
|
|
55565
|
+
console.log(`Scan complete in ${result.elapsedMs}ms:`);
|
|
55566
|
+
console.log(` Scanned: ${result.scanned} memories`);
|
|
55567
|
+
console.log(` Candidates: ${result.candidates} pairs`);
|
|
55568
|
+
console.log(` Judged: ${result.judged}`);
|
|
55569
|
+
console.log(` Queued: ${result.queued}`);
|
|
55570
|
+
console.log(` Cooled down: ${result.cooledDown}`);
|
|
55571
|
+
});
|
|
55149
55572
|
},
|
|
55150
55573
|
{ commands: ["engram"] }
|
|
55151
55574
|
);
|