recallnest 2.3.0 → 2.6.0
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/CHANGELOG.md +187 -0
- package/README.md +49 -38
- package/README_CN.md +46 -31
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +25 -4
- package/src/__tests__/access-tracker-novelty.test.ts +304 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/api-server-security.test.ts +102 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/belief-history.test.ts +220 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1328 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +116 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +945 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +418 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +430 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +262 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +129 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +780 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/freshness.test.ts +186 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +283 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-client-pivot.test.ts +628 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +189 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +282 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/noise-prototype-bank.test.ts +280 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +333 -0
- package/src/__tests__/pivot-apply.test.ts +373 -0
- package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
- package/src/__tests__/pivot-distill.test.ts +1178 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +282 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-gate.test.ts +266 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/resolve-db-path.test.ts +130 -0
- package/src/__tests__/resolve-tier.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-eval.test.ts +163 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +654 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/stable-context-length-contract.test.ts +62 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-read-consistency.test.ts +122 -0
- package/src/__tests__/store-schema-migration.test.ts +87 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +201 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/ui-server-security.test.ts +93 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +90 -34
- package/src/activity-counter.ts +46 -18
- package/src/adaptive-retrieval.ts +11 -0
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +36 -9
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/auto-recall.ts +61 -1
- package/src/belief-history.ts +164 -0
- package/src/capture-engine.ts +142 -32
- package/src/cli.ts +268 -41
- package/src/confidence-tracker.ts +36 -18
- package/src/conflict-engine.ts +24 -3
- package/src/consolidation-engine.ts +255 -23
- package/src/context-composer-stable-selection.ts +7 -1
- package/src/context-composer.ts +8 -1
- package/src/data-checkup.ts +96 -7
- package/src/decay-engine.ts +59 -7
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +13 -4
- package/src/dream-pipeline.ts +473 -41
- package/src/env-config.ts +118 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +402 -14
- package/src/freshness.ts +192 -0
- package/src/http-request-policy.ts +148 -0
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +308 -12
- package/src/kg-extractor.ts +13 -3
- package/src/kg-store.ts +236 -44
- package/src/llm-client.ts +106 -11
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +87 -1800
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +954 -0
- package/src/mcp-tools-core.ts +637 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +26 -3
- package/src/memory-evolution.ts +73 -1
- package/src/memory-health-rebalance.ts +4 -0
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +129 -4
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +46 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/noise-prototype-bank.ts +408 -0
- package/src/pii-detector.ts +207 -5
- package/src/pivot-apply.ts +399 -0
- package/src/preference-matcher.ts +37 -3
- package/src/query-expander.ts +90 -9
- package/src/recall-gate.ts +305 -0
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retrieval-profiles.ts +46 -4
- package/src/retriever.ts +354 -157
- package/src/retroactive-boost.ts +6 -2
- package/src/runtime-config.ts +78 -6
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +1 -0
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +304 -48
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/store.ts +321 -57
- package/src/term-registry.ts +35 -0
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +9 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/version.ts +19 -0
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -60
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/ingest-2026-04-13.log +0 -87
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
package/src/cli.ts
CHANGED
|
@@ -11,16 +11,19 @@
|
|
|
11
11
|
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
14
|
-
import { join, resolve } from "node:path";
|
|
14
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
16
|
|
|
17
17
|
import { metaDir } from "./compat.js";
|
|
18
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
18
19
|
import type { RetrievalResult } from "./retriever.js";
|
|
19
20
|
import { applyRetrievalProfile, listRetrievalProfiles } from "./retrieval-profiles.js";
|
|
20
21
|
import { distillResults, formatExplainResults, formatSearchResults, selectBriefSeedResults, summarizeResults } from "./memory-output.js";
|
|
21
22
|
import { archiveDirtyBriefAsset, assetSummaryLine, buildBriefAsset, buildPinAsset, getExportsDir, listDirtyBriefAssets, listMemoryAssets, listPinAssets, pinSummaryLine, saveBriefAsset, savePinAsset, writeExportArtifact } from "./memory-assets.js";
|
|
22
23
|
import { indexAsset, indexPinnedAsset } from "./asset-sync.js";
|
|
23
|
-
import { createComponents, createStoreOnly, expandHome, loadConfig, loadDotEnv, type LocalMemoryConfig } from "./runtime-config.js";
|
|
24
|
+
import { createComponents, createStoreOnly, expandHome, loadConfig, loadDotEnv, resolveDbPath, type LocalMemoryConfig } from "./runtime-config.js";
|
|
25
|
+
import { KGStore } from "./kg-store.js";
|
|
26
|
+
import { isKGModeEnabled } from "./kg-extractor.js";
|
|
24
27
|
import {
|
|
25
28
|
formatDedupReasonSummary,
|
|
26
29
|
getDedupSkipRate,
|
|
@@ -28,12 +31,16 @@ import {
|
|
|
28
31
|
ingestCCTranscripts,
|
|
29
32
|
ingestCodexSessions,
|
|
30
33
|
ingestGeminiSessions,
|
|
34
|
+
ingestKimiSessions,
|
|
31
35
|
ingestMarkdownFiles,
|
|
32
36
|
ingestObsidianVault,
|
|
33
37
|
ingestConnectorFile,
|
|
34
38
|
} from "./ingest.js";
|
|
35
39
|
import { runDoctor, formatDoctorResults } from "./doctor.js";
|
|
36
40
|
import { persistCaseMemory, persistMemory, persistWorkflowPattern } from "./capture-engine.js";
|
|
41
|
+
import { scanMemoryPromotions, buildPromoteScanDeps, formatPromoteScanResult } from "./memory-promotion.js";
|
|
42
|
+
import { runDream, formatDreamResult, formatDreamMetrics, DEFAULT_DREAM_CONFIG, classifyDreamFailure, shouldBlockDreamRun, partitionAutoDreamScopes } from "./dream-pipeline.js";
|
|
43
|
+
import { listScopesAboveThreshold } from "./activity-counter.js";
|
|
37
44
|
import {
|
|
38
45
|
CaseMemoryInputSchema,
|
|
39
46
|
type CaseMemoryInput,
|
|
@@ -47,6 +54,7 @@ import { ConflictStatusSchema } from "./conflict-schema.js";
|
|
|
47
54
|
import { resolveConflictCandidate } from "./conflict-engine.js";
|
|
48
55
|
import { escalateConflicts } from "./conflict-escalation.js";
|
|
49
56
|
import { ConflictCandidateStore } from "./conflict-store.js";
|
|
57
|
+
import { createAuditLogger } from "./audit-log.js";
|
|
50
58
|
import { formatConflictAudit, formatConflictAuditMarkdown, formatConflictClusters, formatConflictEscalation, formatConflictList, formatConflictRecord, formatConflictResolution } from "./conflict-output.js";
|
|
51
59
|
import { CONFLICT_ATTENTION_LEVELS, parseConflictAttention, summarizeConflictLifecycle } from "./conflict-lifecycle.js";
|
|
52
60
|
import { buildConflictAuditSummary, clusterConflicts, summarizeConflictAdvice } from "./conflict-advisor.js";
|
|
@@ -63,49 +71,66 @@ import {
|
|
|
63
71
|
} from "./workflow-observation-scope-review.js";
|
|
64
72
|
import { collectScopeInventory, formatScopeInventoryReport } from "./scope-inventory.js";
|
|
65
73
|
import { buildRetrievalContext, resolveScopeSelection } from "./scope-policy.js";
|
|
74
|
+
import { createScopeSuggester } from "./scope-suggester.js";
|
|
66
75
|
|
|
67
76
|
/**
|
|
68
|
-
* Auto-detect Claude Code transcript
|
|
69
|
-
* Scans ~/.claude/projects/ for subdirectories containing .jsonl files.
|
|
77
|
+
* Auto-detect Claude Code transcript directories.
|
|
78
|
+
* Scans ~/.claude/projects/ for ALL subdirectories containing .jsonl files.
|
|
79
|
+
*
|
|
80
|
+
* CC 按 cwd 编码 project 子目录,凡是不在 ~ 下跑的会话(Codex spawn 的子进程 / cd 到 repo 跑 /
|
|
81
|
+
* bridge 临时工作目录)会落到 ~/.claude/projects/-XXX/ 别的子目录。旧版只挑 jsonl 最多的根
|
|
82
|
+
* 目录返回,导致 28+ 个子目录全失声。修复:返回所有有 jsonl 的子目录,调用方循环 ingest。
|
|
70
83
|
*/
|
|
71
|
-
function detectCCPath(): string
|
|
84
|
+
function detectCCPath(): string[] {
|
|
72
85
|
const projectsDir = join(homedir(), ".claude", "projects");
|
|
73
|
-
if (!existsSync(projectsDir)) return
|
|
86
|
+
if (!existsSync(projectsDir)) return [];
|
|
74
87
|
try {
|
|
75
|
-
|
|
88
|
+
return readdirSync(projectsDir, { withFileTypes: true })
|
|
76
89
|
.filter(d => d.isDirectory())
|
|
77
|
-
.map(d => join(projectsDir, d.name))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const jsonlCount = readdirSync(dir).filter(f => f.endsWith(".jsonl")).length;
|
|
83
|
-
if (jsonlCount > bestCount) {
|
|
84
|
-
bestCount = jsonlCount;
|
|
85
|
-
best = dir;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return best || null;
|
|
90
|
+
.map(d => join(projectsDir, d.name))
|
|
91
|
+
.filter(dir => {
|
|
92
|
+
try { return readdirSync(dir).some(f => f.endsWith(".jsonl")); }
|
|
93
|
+
catch { return false; }
|
|
94
|
+
});
|
|
89
95
|
} catch {
|
|
90
|
-
return
|
|
96
|
+
return [];
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Pick the CC project subdirectory with the most jsonl files (heuristic: user's primary cwd).
|
|
102
|
+
* Used by memory source which still expects a single directory.
|
|
103
|
+
*/
|
|
104
|
+
function pickPrimaryCCPath(detected: string[]): string {
|
|
105
|
+
let best = detected[0];
|
|
106
|
+
let bestCount = 0;
|
|
107
|
+
for (const dir of detected) {
|
|
108
|
+
try {
|
|
109
|
+
const c = readdirSync(dir).filter(f => f.endsWith(".jsonl")).length;
|
|
110
|
+
if (c > bestCount) { bestCount = c; best = dir; }
|
|
111
|
+
} catch { /* skip */ }
|
|
112
|
+
}
|
|
113
|
+
return best;
|
|
114
|
+
}
|
|
115
|
+
|
|
94
116
|
/**
|
|
95
117
|
* Resolve "auto" paths in config to actual directories.
|
|
118
|
+
*
|
|
119
|
+
* Note: cc 分支只返回第一个 detect 到的目录(fallback),ingest 入口已改为直接调
|
|
120
|
+
* detectCCPath() 拿全量子目录循环处理。这里保留是为兼容潜在的其它调用方。
|
|
96
121
|
*/
|
|
97
122
|
function resolveSourcePath(source: string, key: string): string {
|
|
98
123
|
if (source !== "auto") return expandHome(source);
|
|
99
124
|
|
|
100
125
|
if (key === "cc") {
|
|
101
126
|
const detected = detectCCPath();
|
|
102
|
-
if (
|
|
103
|
-
return detected;
|
|
127
|
+
if (detected.length === 0) throw new Error("Could not auto-detect Claude Code transcript path. Set sources.cc.path in config.json.");
|
|
128
|
+
return pickPrimaryCCPath(detected);
|
|
104
129
|
}
|
|
105
130
|
if (key === "memory") {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
return join(
|
|
131
|
+
const detected = detectCCPath();
|
|
132
|
+
if (detected.length === 0) throw new Error("Could not auto-detect memory path. Set sources.memory.path in config.json.");
|
|
133
|
+
return join(pickPrimaryCCPath(detected), "memory");
|
|
109
134
|
}
|
|
110
135
|
throw new Error(`Cannot auto-detect path for source "${key}". Set it manually in config.json.`);
|
|
111
136
|
}
|
|
@@ -130,7 +155,7 @@ const program = new Command();
|
|
|
130
155
|
program
|
|
131
156
|
.name("recallnest")
|
|
132
157
|
.description("本地优先 AI 对话记忆搜索与蒸馏层")
|
|
133
|
-
.version(
|
|
158
|
+
.version(PACKAGE_VERSION);
|
|
134
159
|
|
|
135
160
|
async function runRetrievalView(
|
|
136
161
|
view: "search" | "explain" | "distill",
|
|
@@ -147,7 +172,7 @@ async function runRetrievalView(
|
|
|
147
172
|
},
|
|
148
173
|
) {
|
|
149
174
|
const config = loadConfig();
|
|
150
|
-
const { retriever, profile } = createComponents(config, options.profile);
|
|
175
|
+
const { retriever, profile, store } = createComponents(config, options.profile);
|
|
151
176
|
|
|
152
177
|
const limit = parseLimitOption(options.limit, 5, 1, 20);
|
|
153
178
|
|
|
@@ -206,6 +231,17 @@ async function runRetrievalView(
|
|
|
206
231
|
: formatSearchResults(results, context);
|
|
207
232
|
|
|
208
233
|
console.log(rendered);
|
|
234
|
+
|
|
235
|
+
// P1-A: 显式 scope 0 命中 → 相近 scope 提示打到 stderr(不污染 stdout)
|
|
236
|
+
if (results.length === 0 && options.scope) {
|
|
237
|
+
try {
|
|
238
|
+
const suggest = createScopeSuggester(async () => (await store.stats()).scopeCounts);
|
|
239
|
+
const suggestions = (await suggest(options.scope)).filter(s => s !== options.scope);
|
|
240
|
+
if (suggestions.length > 0) {
|
|
241
|
+
console.error(`⚠️ scope '${options.scope}' 命中 0 条,相近 scope: [${suggestions.join(", ")}]`);
|
|
242
|
+
}
|
|
243
|
+
} catch { /* best-effort */ }
|
|
244
|
+
}
|
|
209
245
|
}
|
|
210
246
|
|
|
211
247
|
function parseLimitOption(value: string | undefined, fallback: number, min = 1, max = 50): number {
|
|
@@ -644,6 +680,28 @@ program
|
|
|
644
680
|
].join("\n"));
|
|
645
681
|
});
|
|
646
682
|
|
|
683
|
+
program
|
|
684
|
+
.command("promote-scan")
|
|
685
|
+
.description("扫描 transcript 降级的 profile/preferences evidence,聚类高频高 importance 项晋升回 durable")
|
|
686
|
+
.requiredOption("--scope <scope>", "要扫描的 scope,如 cc:project:recallnest")
|
|
687
|
+
.option("--apply", "执行晋升;默认 dry-run 只预览")
|
|
688
|
+
.option("--min-occurrences <n>", "聚类最小成员数(默认 3)", "3")
|
|
689
|
+
.option("--min-importance <n>", "聚类最小平均 importance(默认 0.6)", "0.6")
|
|
690
|
+
.action(async (options) => {
|
|
691
|
+
const config = loadConfig();
|
|
692
|
+
const { store, embedder } = createComponents(config);
|
|
693
|
+
const conflictStore = new ConflictCandidateStore();
|
|
694
|
+
const deps = buildPromoteScanDeps({ store, embedder, conflictStore });
|
|
695
|
+
const minOccurrences = Number(options.minOccurrences);
|
|
696
|
+
const minImportance = Number(options.minImportance);
|
|
697
|
+
const result = await scanMemoryPromotions(deps, options.scope, {
|
|
698
|
+
dryRun: !options.apply,
|
|
699
|
+
...(Number.isFinite(minOccurrences) ? { minOccurrences } : {}),
|
|
700
|
+
...(Number.isFinite(minImportance) ? { minImportance } : {}),
|
|
701
|
+
});
|
|
702
|
+
console.log(formatPromoteScanResult(result));
|
|
703
|
+
});
|
|
704
|
+
|
|
647
705
|
program
|
|
648
706
|
.command("export <query>")
|
|
649
707
|
.description("导出检索与蒸馏结果到 markdown/json")
|
|
@@ -1054,7 +1112,7 @@ program
|
|
|
1054
1112
|
continue;
|
|
1055
1113
|
}
|
|
1056
1114
|
|
|
1057
|
-
const record = await persistWorkflowPattern({ store, embedder, conflictStore }, input);
|
|
1115
|
+
const record = await persistWorkflowPattern({ store, embedder, conflictStore, auditLogger: createAuditLogger() }, input);
|
|
1058
1116
|
existingKeys.add(key);
|
|
1059
1117
|
stored.push({ title: record.title, id: record.id, scope: record.resolvedScope });
|
|
1060
1118
|
}
|
|
@@ -1130,7 +1188,7 @@ program
|
|
|
1130
1188
|
continue;
|
|
1131
1189
|
}
|
|
1132
1190
|
|
|
1133
|
-
const record = await persistCaseMemory({ store, embedder, conflictStore }, input);
|
|
1191
|
+
const record = await persistCaseMemory({ store, embedder, conflictStore, auditLogger: createAuditLogger() }, input);
|
|
1134
1192
|
existingKeys.add(key);
|
|
1135
1193
|
stored.push({ title: record.title, id: record.id, scope: record.resolvedScope });
|
|
1136
1194
|
}
|
|
@@ -1208,7 +1266,7 @@ program
|
|
|
1208
1266
|
continue;
|
|
1209
1267
|
}
|
|
1210
1268
|
|
|
1211
|
-
const record = await persistMemory({ store, embedder, conflictStore }, input);
|
|
1269
|
+
const record = await persistMemory({ store, embedder, conflictStore, auditLogger: createAuditLogger() }, input);
|
|
1212
1270
|
existingKeys.add(storeMemoryIdentity({
|
|
1213
1271
|
category: input.category,
|
|
1214
1272
|
scope: record.resolvedScope,
|
|
@@ -1302,7 +1360,7 @@ program
|
|
|
1302
1360
|
program
|
|
1303
1361
|
.command("ingest")
|
|
1304
1362
|
.description("导入对话记录到索引")
|
|
1305
|
-
.option("-s, --source <source>", "数据源: cc / codex / memory / desktop / all", "all")
|
|
1363
|
+
.option("-s, --source <source>", "数据源: cc / codex / gemini / kimi / memory / desktop / all", "all")
|
|
1306
1364
|
.option("-l, --limit <n>", "限制处理文件数(调试用)")
|
|
1307
1365
|
.option("-v, --verbose", "详细输出")
|
|
1308
1366
|
.option("--no-dedup", "跳过向量去重")
|
|
@@ -1345,8 +1403,10 @@ program
|
|
|
1345
1403
|
console.log(` ✅ ${config.embedding.model} (${testResult.dimensions}d)`);
|
|
1346
1404
|
|
|
1347
1405
|
// LLM status — respect --no-llm flag
|
|
1348
|
-
|
|
1349
|
-
|
|
1406
|
+
// commander 把 --no-llm 解析成 options.llm=false(不产生 options.noLlm),对齐 --no-dedup 的取反键写法
|
|
1407
|
+
const noLlm = options.llm === false;
|
|
1408
|
+
const effectiveLlm = noLlm ? null : llm;
|
|
1409
|
+
if (noLlm) {
|
|
1350
1410
|
console.log(" ⚠️ LLM: 已通过 --no-llm 禁用,原始对话将跳过不存入");
|
|
1351
1411
|
} else if (llm) {
|
|
1352
1412
|
const llmTest = await llm.test();
|
|
@@ -1376,15 +1436,23 @@ program
|
|
|
1376
1436
|
const ingestOpts = { limit, verbose, noDedup, llm: effectiveLlm, recentHours };
|
|
1377
1437
|
|
|
1378
1438
|
// CC Transcripts
|
|
1439
|
+
// CC 按 cwd 编码 project 子目录,所有有 jsonl 的子目录都要 ingest(不只是 jsonl 最多的那个)
|
|
1379
1440
|
if (source === "all" || source === "cc") {
|
|
1380
1441
|
console.log("📝 导入 Claude Code 对话...");
|
|
1381
1442
|
const ccSource = config.sources.cc;
|
|
1382
1443
|
if (ccSource) {
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1444
|
+
const ccPaths = ccSource.path === "auto"
|
|
1445
|
+
? detectCCPath()
|
|
1446
|
+
: [expandHome(ccSource.path)];
|
|
1447
|
+
if (ccPaths.length === 0) {
|
|
1448
|
+
throw new Error("Could not auto-detect Claude Code transcript path. Set sources.cc.path in config.json.");
|
|
1449
|
+
}
|
|
1450
|
+
for (const ccPath of ccPaths) {
|
|
1451
|
+
const r = await ingestCCTranscripts(store, embedder, ccPath, ingestOpts);
|
|
1452
|
+
results.push(r);
|
|
1453
|
+
console.log(` ✅ CC [${basename(ccPath)}]: ${formatIngestSummary(r)}`);
|
|
1454
|
+
maybeWarnHighCCDedupRate(r);
|
|
1455
|
+
}
|
|
1388
1456
|
}
|
|
1389
1457
|
}
|
|
1390
1458
|
|
|
@@ -1396,6 +1464,14 @@ program
|
|
|
1396
1464
|
console.log(` ✅ Codex: ${formatIngestSummary(r)}`);
|
|
1397
1465
|
}
|
|
1398
1466
|
|
|
1467
|
+
// Kimi Code Sessions (wire.jsonl 事件流,~/.kimi-code/sessions/)
|
|
1468
|
+
if (source === "all" || source === "kimi") {
|
|
1469
|
+
console.log("🌙 导入 Kimi Code 对话...");
|
|
1470
|
+
const r = await ingestKimiSessions(store, embedder, ingestOpts);
|
|
1471
|
+
results.push(r);
|
|
1472
|
+
console.log(` ✅ Kimi: ${formatIngestSummary(r)}`);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1399
1475
|
// Gemini Sessions (JSON format under ~/.gemini/tmp/*/chats/)
|
|
1400
1476
|
if (source === "all" || source === "gemini") {
|
|
1401
1477
|
console.log("💎 导入 Gemini 对话...");
|
|
@@ -1403,7 +1479,7 @@ program
|
|
|
1403
1479
|
limit,
|
|
1404
1480
|
verbose,
|
|
1405
1481
|
noDedup,
|
|
1406
|
-
llm,
|
|
1482
|
+
llm: effectiveLlm,
|
|
1407
1483
|
recentHours,
|
|
1408
1484
|
});
|
|
1409
1485
|
results.push(r);
|
|
@@ -1436,7 +1512,7 @@ program
|
|
|
1436
1512
|
const r = await ingestMarkdownFiles(store, embedder, memPath, "memory", {
|
|
1437
1513
|
verbose,
|
|
1438
1514
|
noDedup,
|
|
1439
|
-
llm,
|
|
1515
|
+
llm: effectiveLlm,
|
|
1440
1516
|
recentHours,
|
|
1441
1517
|
});
|
|
1442
1518
|
results.push(r);
|
|
@@ -1502,6 +1578,155 @@ program
|
|
|
1502
1578
|
console.log();
|
|
1503
1579
|
});
|
|
1504
1580
|
|
|
1581
|
+
// ─── dream ───────────────────────────────────────────────────────────────────
|
|
1582
|
+
// P3 调度化入口:launchd 经 scripts/dream-consolidation.sh 调用本命令,
|
|
1583
|
+
// 输出 [[DREAM_STATUS]] ok|skip|blocked 标记供脚本判定(同 weekly-distill 的
|
|
1584
|
+
// [[DISTILL_STATUS]] 模式)。dream 是 auto-gc / usageStatus 快照 / consolidation
|
|
1585
|
+
// 的唯一生产触发点——没有它们整条维护链就是死代码(2026-06-11 临床重审实锤)。
|
|
1586
|
+
|
|
1587
|
+
program
|
|
1588
|
+
.command("dream")
|
|
1589
|
+
.description("运行 dream 离线整固管线 (Orient/Gather/Consolidate/Prune)")
|
|
1590
|
+
.option("--scope <scope>", "目标 scope(前缀或精确)", "cc")
|
|
1591
|
+
.option("--auto", "扫描 activity-counter 中写计数达标的所有 scope,逐个跑 dream(单 scope 失败不阻断其他)")
|
|
1592
|
+
.option("--force", "跳过 min-writes 门槛强制运行")
|
|
1593
|
+
.action(async (options: { scope: string; auto?: boolean; force?: boolean }) => {
|
|
1594
|
+
const config = loadConfig();
|
|
1595
|
+
const { store, embedder, llm } = createComponents(config);
|
|
1596
|
+
// Same activity-stats file the store writes per-scope counts to (beside the LanceDB dir).
|
|
1597
|
+
const activityStatsPath = join(dirname(store.dbPath), "activity-stats.json");
|
|
1598
|
+
// KG evidence for triple-overlap merging — same gate as the MCP server
|
|
1599
|
+
const kgStore = isKGModeEnabled() ? new KGStore({ dbPath: store.dbPath }) : null;
|
|
1600
|
+
|
|
1601
|
+
if (options.auto) {
|
|
1602
|
+
const eligible = listScopesAboveThreshold(DEFAULT_DREAM_CONFIG.minWritesForDream, { statsPath: activityStatsPath });
|
|
1603
|
+
// 巨型 scope 交给专用 schedule,不占日常轮次的 wall-clock 预算(理由见
|
|
1604
|
+
// DreamConfig.autoExcludeScopes:07-30 那轮 6h 只跑了 15/507,而 memory 独占 79% 的条数)。
|
|
1605
|
+
const { run: scopes, deferred } = partitionAutoDreamScopes(eligible, DEFAULT_DREAM_CONFIG.autoExcludeScopes);
|
|
1606
|
+
if (deferred.length > 0) {
|
|
1607
|
+
// 不静默:被挪走的 scope 必须在日志里看得见,否则"今天怎么没跑 memory"只能靠翻代码回答。
|
|
1608
|
+
// 前缀刻意用 `[dream]` 而不是 `dream --auto:`——后者是 dream-checkup.sh 抓「达标 scope 数」
|
|
1609
|
+
// 的锚点,多一行同前缀的会混进它的取数。
|
|
1610
|
+
console.log(`[dream] ${deferred.length} 个 scope 交给专用 schedule,本轮不跑 → ${deferred.join(", ")}`);
|
|
1611
|
+
}
|
|
1612
|
+
if (scopes.length === 0) {
|
|
1613
|
+
// 两种空:一种是真没人达标,一种是达标的全被挪走了——混成一句会让排障从这里开始跑偏。
|
|
1614
|
+
console.log(
|
|
1615
|
+
eligible.length === 0
|
|
1616
|
+
? "dream --auto: 无达标 scope(写计数均低于门槛)"
|
|
1617
|
+
: "dream --auto: 达标 scope 全部交给专用 schedule,本轮无事可做",
|
|
1618
|
+
);
|
|
1619
|
+
console.log("[[DREAM_STATUS]] skip");
|
|
1620
|
+
process.exit(0);
|
|
1621
|
+
}
|
|
1622
|
+
console.log(`dream --auto: ${scopes.length} 个达标 scope → ${scopes.join(", ")}`);
|
|
1623
|
+
|
|
1624
|
+
// Wall-clock 预算(2026-07-29 补):防单轮跑成跨天。07-24 那轮跑了 4 天 15 小时,
|
|
1625
|
+
// 把 07-25~27 三天的调度全堵死(上一次没结束,launchd 起不了新的)。
|
|
1626
|
+
// 天花板见 DreamConfig.autoRunBudgetMs 的注释——截断会让靠后 scope 系统性饿死,
|
|
1627
|
+
// 真解是 session scope TTL,不是把预算调大。
|
|
1628
|
+
const budgetMs = Number(process.env.RECALLNEST_DREAM_BUDGET_MS ?? DEFAULT_DREAM_CONFIG.autoRunBudgetMs);
|
|
1629
|
+
const deadline = Date.now() + budgetMs;
|
|
1630
|
+
|
|
1631
|
+
let fatalFailures = 0;
|
|
1632
|
+
let transientFailures = 0;
|
|
1633
|
+
let processed = 0;
|
|
1634
|
+
// 2026-08-12 产出计量:`[[DREAM_STATUS]] ok` 只证明「没崩」,不证明「产出了东西」。
|
|
1635
|
+
// 2026-07-23 查出的语义阈值卡在真实 p99 之上,就是这样静默跑了 2716 次零 insight
|
|
1636
|
+
// 而无人察觉 —— 当时日志里每一轮都是 ok。
|
|
1637
|
+
const tally = { produced: 0, noop: 0, partial: 0, skipped: 0 };
|
|
1638
|
+
let effectsTotal = 0;
|
|
1639
|
+
const degradedScopes: string[] = [];
|
|
1640
|
+
for (const scope of scopes) {
|
|
1641
|
+
if (Date.now() > deadline) {
|
|
1642
|
+
console.warn(
|
|
1643
|
+
`[dream] wall-clock 预算 ${Math.round(budgetMs / 60_000)} 分钟耗尽:已处理 ` +
|
|
1644
|
+
`${processed}/${scopes.length},剩余 ${scopes.length - processed} 个 scope 本轮跳过`,
|
|
1645
|
+
);
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
processed++;
|
|
1649
|
+
try {
|
|
1650
|
+
const result = await runDream({ store, llm, embedder, scope, force: Boolean(options.force), activityStatsPath, kgStore });
|
|
1651
|
+
tally[result.output.kind]++;
|
|
1652
|
+
effectsTotal += result.output.effectsWritten;
|
|
1653
|
+
if (result.output.degraded) degradedScopes.push(`${scope}=${result.output.degraded}`);
|
|
1654
|
+
console.log(`[scope=${scope}] ${formatDreamResult(result)}`);
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
// 单 scope 失败不阻断其他 scope。
|
|
1657
|
+
// 打完整 stack 而不只是 message:像「Array.from requires an array-like object」
|
|
1658
|
+
// 这种原生错误,只有 message 时根本定位不到是哪一行抛的——2026-07-18/19 连着两天
|
|
1659
|
+
// 因此 blocked,回头查日志除了这一句什么线索都没有,只能靠猜。
|
|
1660
|
+
// 分类基于 message(不是 stack):stack 里的文件路径不该参与判定。
|
|
1661
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1662
|
+
if (classifyDreamFailure(message) === "transient") transientFailures++;
|
|
1663
|
+
else fatalFailures++;
|
|
1664
|
+
console.error(`[scope=${scope}] dream failed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// 旧语义是「任一 scope 抛异常即 blocked」,在 475 个 scope 的规模下等于「有一个撞上
|
|
1669
|
+
// store-write 锁就整轮红」,而整轮红会让 shell 脚本重试 → 重跑全量。改为:代码/数据
|
|
1670
|
+
// 缺陷一票否决,环境性失败要成规模才算整轮失败。比例按已处理数算,不按总数。
|
|
1671
|
+
// 结构性降级一票否决:它不是「今天数据不成簇」(那是合法的 noop),而是某条路径
|
|
1672
|
+
// 整段断了 —— 向量管线空、或压根没配 LLM。判据只认确定性零值,不含任何阈值,
|
|
1673
|
+
// 所以不会因为数据分布变化而误伤(详见 assertDreamSweepHealth 的注释)。
|
|
1674
|
+
const structurallyBroken = degradedScopes.length > 0;
|
|
1675
|
+
if (structurallyBroken) {
|
|
1676
|
+
console.error(`[dream] ⚠ 结构性降级 ${degradedScopes.length} 个 scope:${degradedScopes.join(", ")}`);
|
|
1677
|
+
}
|
|
1678
|
+
const blocked = shouldBlockDreamRun({ totalScopes: processed, fatalFailures, transientFailures })
|
|
1679
|
+
|| structurallyBroken;
|
|
1680
|
+
if (fatalFailures > 0 || transientFailures > 0) {
|
|
1681
|
+
console.log(`[dream] 失败统计:fatal=${fatalFailures} transient=${transientFailures}(已处理 ${processed})`);
|
|
1682
|
+
}
|
|
1683
|
+
// 这一行是 dream-consolidation.sh 存在性闸的锚点:STATUS=ok 却没有它 = 判失败。
|
|
1684
|
+
// 它防的是「断言本身被某次重构悄悄删掉」—— 一个不需要调参的自检。
|
|
1685
|
+
console.log(formatDreamMetrics({
|
|
1686
|
+
tally,
|
|
1687
|
+
degraded: degradedScopes.length,
|
|
1688
|
+
effects: effectsTotal,
|
|
1689
|
+
processed,
|
|
1690
|
+
total: scopes.length,
|
|
1691
|
+
}));
|
|
1692
|
+
console.log(blocked ? "[[DREAM_STATUS]] blocked" : "[[DREAM_STATUS]] ok");
|
|
1693
|
+
process.exit(blocked ? 1 : 0);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
try {
|
|
1697
|
+
const result = await runDream({
|
|
1698
|
+
store,
|
|
1699
|
+
llm,
|
|
1700
|
+
embedder,
|
|
1701
|
+
scope: options.scope,
|
|
1702
|
+
force: Boolean(options.force),
|
|
1703
|
+
activityStatsPath,
|
|
1704
|
+
kgStore,
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
console.log(formatDreamResult(result));
|
|
1708
|
+
// 单 scope 也必须打产出计量:dream-consolidation.sh 的存在性闸对 STATUS=ok
|
|
1709
|
+
// 一视同仁地要求这行,而本分支此前从不打 —— 08-12 那道闸上线后,周日的
|
|
1710
|
+
// memory 轮次即使跑成功也会被判"产出断言缺失"。tally 在这里只有一格是 1。
|
|
1711
|
+
const tally = { produced: 0, noop: 0, partial: 0, skipped: 0 };
|
|
1712
|
+
tally[result.output.kind] = 1;
|
|
1713
|
+
console.log(formatDreamMetrics({
|
|
1714
|
+
tally,
|
|
1715
|
+
degraded: result.output.degraded ? 1 : 0,
|
|
1716
|
+
effects: result.output.effectsWritten,
|
|
1717
|
+
processed: 1,
|
|
1718
|
+
total: 1,
|
|
1719
|
+
}));
|
|
1720
|
+
console.log(result.ran ? "[[DREAM_STATUS]] ok" : "[[DREAM_STATUS]] skip");
|
|
1721
|
+
process.exit(0);
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
// 同 --auto 分支:带 stack,否则原生错误无从定位。
|
|
1724
|
+
console.error(`dream failed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
1725
|
+
console.log("[[DREAM_STATUS]] blocked");
|
|
1726
|
+
process.exit(1);
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1505
1730
|
// ─── stats ───────────────────────────────────────────────────────────────────
|
|
1506
1731
|
|
|
1507
1732
|
program
|
|
@@ -1611,7 +1836,7 @@ program
|
|
|
1611
1836
|
}
|
|
1612
1837
|
|
|
1613
1838
|
const config = loadConfig();
|
|
1614
|
-
const dbPath =
|
|
1839
|
+
const dbPath = resolveDbPath(config);
|
|
1615
1840
|
|
|
1616
1841
|
if (existsSync(dbPath)) {
|
|
1617
1842
|
const { rmSync } = await import("node:fs");
|
|
@@ -1723,6 +1948,7 @@ program
|
|
|
1723
1948
|
.option("--task <task>", "相关任务")
|
|
1724
1949
|
.option("--tags <tags>", "逗号分隔 tags")
|
|
1725
1950
|
.option("--tools <tools>", "逗号分隔 tools")
|
|
1951
|
+
.option("--idempotency-key <key>", "同一请求重试时用于替换旧 observation 的稳定键")
|
|
1726
1952
|
.option("--json", "输出 JSON")
|
|
1727
1953
|
.action(async (workflowId, summary, options) => {
|
|
1728
1954
|
const store = new WorkflowObservationStore();
|
|
@@ -1736,6 +1962,7 @@ program
|
|
|
1736
1962
|
task: options.task,
|
|
1737
1963
|
tags: splitCsvOption(options.tags),
|
|
1738
1964
|
tools: splitCsvOption(options.tools),
|
|
1965
|
+
idempotencyKey: options.idempotencyKey,
|
|
1739
1966
|
});
|
|
1740
1967
|
const stored = await store.save(record);
|
|
1741
1968
|
if (options.json) {
|
|
@@ -261,50 +261,68 @@ export function buildContradictPatch(
|
|
|
261
261
|
// Convenience wrappers (for callers who DO have a store)
|
|
262
262
|
// ---------------------------------------------------------------------------
|
|
263
263
|
|
|
264
|
+
/** 单写通道端口:读改写经 store.patchMetadata per-id 队列,防并发覆盖。 */
|
|
265
|
+
type ConfidenceStorePort = {
|
|
266
|
+
patchMetadata(
|
|
267
|
+
id: string,
|
|
268
|
+
patchFn: (meta: Record<string, unknown>, entry: MemoryEntry) => Record<string, unknown>,
|
|
269
|
+
scopeFilter?: string[],
|
|
270
|
+
): Promise<MemoryEntry | null>;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
function applyConfidencePatch(
|
|
274
|
+
store: ConfidenceStorePort,
|
|
275
|
+
entryId: string,
|
|
276
|
+
scope: string,
|
|
277
|
+
build: (entry: MemoryEntry) => ConfidencePatch | null,
|
|
278
|
+
): Promise<ConfidenceUpdate | null> {
|
|
279
|
+
let update: ConfidenceUpdate | null = null;
|
|
280
|
+
return store
|
|
281
|
+
.patchMetadata(
|
|
282
|
+
entryId,
|
|
283
|
+
(meta, entry) => {
|
|
284
|
+
const patch = build(entry);
|
|
285
|
+
if (!patch) return meta;
|
|
286
|
+
update = patch.update;
|
|
287
|
+
return patch.metadata;
|
|
288
|
+
},
|
|
289
|
+
[scope],
|
|
290
|
+
)
|
|
291
|
+
.then(result => (result === null ? null : update));
|
|
292
|
+
}
|
|
293
|
+
|
|
264
294
|
/**
|
|
265
295
|
* Confirm a memory and write back to store. Convenience wrapper.
|
|
266
296
|
*/
|
|
267
297
|
export async function confirmMemory(
|
|
268
|
-
store:
|
|
298
|
+
store: ConfidenceStorePort,
|
|
269
299
|
entryId: string,
|
|
270
300
|
scope: string,
|
|
271
301
|
): Promise<ConfidenceUpdate | null> {
|
|
272
|
-
|
|
273
|
-
const patch = buildConfirmPatch(entry);
|
|
274
|
-
if (!patch) return null;
|
|
275
|
-
await store.update(entryId, { metadata: JSON.stringify(patch.metadata) }, [scope]);
|
|
276
|
-
return patch.update;
|
|
302
|
+
return applyConfidencePatch(store, entryId, scope, entry => buildConfirmPatch(entry));
|
|
277
303
|
}
|
|
278
304
|
|
|
279
305
|
/**
|
|
280
306
|
* Correct a memory and write back to store. Convenience wrapper.
|
|
281
307
|
*/
|
|
282
308
|
export async function correctMemory(
|
|
283
|
-
store:
|
|
309
|
+
store: ConfidenceStorePort,
|
|
284
310
|
entryId: string,
|
|
285
311
|
scope: string,
|
|
286
312
|
correctedById?: string,
|
|
287
313
|
): Promise<ConfidenceUpdate | null> {
|
|
288
|
-
|
|
289
|
-
const patch = buildCorrectPatch(entry, correctedById);
|
|
290
|
-
if (!patch) return null;
|
|
291
|
-
await store.update(entryId, { metadata: JSON.stringify(patch.metadata) }, [scope]);
|
|
292
|
-
return patch.update;
|
|
314
|
+
return applyConfidencePatch(store, entryId, scope, entry => buildCorrectPatch(entry, correctedById));
|
|
293
315
|
}
|
|
294
316
|
|
|
295
317
|
/**
|
|
296
318
|
* Contradict a memory and write back to store. Convenience wrapper.
|
|
297
319
|
*/
|
|
298
320
|
export async function contradictMemory(
|
|
299
|
-
store:
|
|
321
|
+
store: ConfidenceStorePort,
|
|
300
322
|
entryId: string,
|
|
301
323
|
scope: string,
|
|
302
324
|
): Promise<ConfidenceUpdate | null> {
|
|
303
|
-
|
|
304
|
-
const patch = buildContradictPatch(entry);
|
|
305
|
-
if (!patch) return null;
|
|
306
|
-
await store.update(entryId, { metadata: JSON.stringify(patch.metadata) }, [scope]);
|
|
307
|
-
return patch.update;
|
|
325
|
+
return applyConfidencePatch(store, entryId, scope, entry => buildContradictPatch(entry));
|
|
308
326
|
}
|
|
309
327
|
|
|
310
328
|
// ---------------------------------------------------------------------------
|
package/src/conflict-engine.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
parseMetadataObject,
|
|
10
10
|
} from "./memory-boundaries.js";
|
|
11
11
|
import type { MemoryStore } from "./store.js";
|
|
12
|
+
import { archiveBeliefVersion, buildSupersedingBeliefMetadata } from "./belief-history.js";
|
|
13
|
+
import { parseEvolution } from "./memory-evolution.js";
|
|
12
14
|
import {
|
|
13
15
|
type ConflictCandidateInput,
|
|
14
16
|
ConflictCandidateInputSchema,
|
|
@@ -95,7 +97,8 @@ function buildMergedConflictMetadata(
|
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
export interface ResolveConflictDeps {
|
|
98
|
-
store
|
|
100
|
+
// `store`/`upsert` are needed to archive the belief being replaced before overwriting it.
|
|
101
|
+
store: Pick<MemoryStore, "getById" | "update" | "store"> & Partial<Pick<MemoryStore, "upsert">>;
|
|
99
102
|
embedder: Pick<Embedder, "embedPassage">;
|
|
100
103
|
conflictStore: Pick<ConflictCandidateStore, "getById" | "replace">;
|
|
101
104
|
}
|
|
@@ -144,13 +147,31 @@ export async function resolveConflictCandidate(
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
const vector = await deps.embedder.embedPassage(nextText);
|
|
150
|
+
|
|
151
|
+
// Conflict resolution is the most deliberate belief change there is — accepting the
|
|
152
|
+
// incoming version or merging both used to overwrite the existing row in place, losing
|
|
153
|
+
// precisely the "before" side of the decision. Archive it first.
|
|
154
|
+
const resolvedAtMs = Date.now();
|
|
155
|
+
const previousEvo = parseEvolution(existing.metadata, existing.timestamp);
|
|
156
|
+
const { historyId } = await archiveBeliefVersion(
|
|
157
|
+
{ store: deps.store, embedder: deps.embedder },
|
|
158
|
+
existing,
|
|
159
|
+
{ now: resolvedAtMs },
|
|
160
|
+
);
|
|
161
|
+
|
|
147
162
|
const updated = await deps.store.update(existing.id, {
|
|
148
163
|
text: nextText,
|
|
149
164
|
vector,
|
|
150
165
|
importance: nextImportance,
|
|
151
166
|
category: nextCategory,
|
|
152
|
-
metadata:
|
|
153
|
-
|
|
167
|
+
metadata: buildSupersedingBeliefMetadata(
|
|
168
|
+
nextMetadata,
|
|
169
|
+
previousEvo,
|
|
170
|
+
historyId,
|
|
171
|
+
resolvedAtMs,
|
|
172
|
+
`conflict:${input.resolution}`,
|
|
173
|
+
),
|
|
174
|
+
timestamp: resolvedAtMs,
|
|
154
175
|
});
|
|
155
176
|
if (!updated) {
|
|
156
177
|
throw new Error(`Failed to update durable memory ${existing.id}`);
|