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/ingest.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { readFileSync, readdirSync, existsSync, statSync, writeFileSync } from "
|
|
|
12
12
|
import { join, basename, resolve, dirname } from "node:path";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { detectLang, tokenizeFts } from "./language-hook.js";
|
|
15
|
+
import { redactSecrets } from "./pii-detector.js";
|
|
15
16
|
import type { MemoryStore, MemoryEntry } from "./store.js";
|
|
16
17
|
import type { Embedder } from "./embedder.js";
|
|
17
18
|
import { chunkDocument, type ChunkerConfig } from "./chunker.js";
|
|
@@ -20,8 +21,10 @@ import { isConnectorOutputV1 } from "./connector-types.js";
|
|
|
20
21
|
import { isObsidianVault, scanVault } from "./obsidian-connector.js";
|
|
21
22
|
import { segmentEvents, type EventSegmenterConfig, DEFAULT_EVENT_SEGMENTER_CONFIG } from "./event-segmenter.js";
|
|
22
23
|
import { isProcessed, markProcessed } from "./tracker.js";
|
|
24
|
+
import * as envConfig from "./env-config.js";
|
|
23
25
|
import type { LLMClient, SmartExtraction } from "./llm-client.js";
|
|
24
26
|
import { resolveIngestBoundary } from "./memory-boundaries.js";
|
|
27
|
+
import { isActiveMemory } from "./memory-evolution.js";
|
|
25
28
|
import { isNoise } from "./noise-filter.js";
|
|
26
29
|
import {
|
|
27
30
|
inferReplyStylePreferenceSlot,
|
|
@@ -287,8 +290,12 @@ export async function dedupCheck(
|
|
|
287
290
|
llm?: LLMClient | null,
|
|
288
291
|
): Promise<DedupCheckResult> {
|
|
289
292
|
try {
|
|
290
|
-
// Stage 1: vector similarity check
|
|
291
|
-
|
|
293
|
+
// Stage 1: vector similarity check. store.vectorSearch() has no status filter, so
|
|
294
|
+
// superseded belief-history rows surface here — dedupe against one and incoming
|
|
295
|
+
// content gets dropped as a duplicate of a belief that is no longer held (returning
|
|
296
|
+
// to an earlier position would be silently discarded).
|
|
297
|
+
const results = (await store.vectorSearch(vector, DEDUP_CANDIDATE_LIMIT, DEDUP_SOFT_THRESHOLD))
|
|
298
|
+
.filter((result) => isActiveMemory(result.entry.metadata));
|
|
292
299
|
if (results.length === 0) {
|
|
293
300
|
return { action: "store", reason: "unique" }; // Clearly unique
|
|
294
301
|
}
|
|
@@ -507,7 +514,7 @@ async function smartExtractBatch(
|
|
|
507
514
|
* Tier 3.1: Check if core summary generation is enabled.
|
|
508
515
|
*/
|
|
509
516
|
function isCoreSummaryEnabled(): boolean {
|
|
510
|
-
return
|
|
517
|
+
return envConfig.coreSummary();
|
|
511
518
|
}
|
|
512
519
|
|
|
513
520
|
/**
|
|
@@ -625,7 +632,8 @@ export async function drainPendingQueue(
|
|
|
625
632
|
let processed = 0, errors = 0;
|
|
626
633
|
for (let i = 0; i < pending.length; i += 20) {
|
|
627
634
|
const batch = pending.slice(i, i + 20);
|
|
628
|
-
|
|
635
|
+
// F-3b: scrub before the LLM extraction call and downstream embedding
|
|
636
|
+
const texts = batch.map(c => redactSecrets(c.text).text);
|
|
629
637
|
const extractions = await smartExtractBatch(texts, llm);
|
|
630
638
|
const embeddingTexts = extractions.map(e => e.l1 || e.l0);
|
|
631
639
|
const vectors = await embedder.embedBatchPassage(embeddingTexts);
|
|
@@ -860,7 +868,7 @@ function filterNoiseTurns(turns: ConversationTurn[]): ConversationTurn[] {
|
|
|
860
868
|
return turns.filter((turn) => !isNoise(turn.text));
|
|
861
869
|
}
|
|
862
870
|
|
|
863
|
-
function groupTurnsIntoChunks(turns: ConversationTurn[]): Array<{
|
|
871
|
+
export function groupTurnsIntoChunks(turns: ConversationTurn[]): Array<{
|
|
864
872
|
text: string;
|
|
865
873
|
timestamp: string;
|
|
866
874
|
sessionId: string;
|
|
@@ -933,7 +941,18 @@ function groupTurnsIntoChunks(turns: ConversationTurn[]): Array<{
|
|
|
933
941
|
}
|
|
934
942
|
}
|
|
935
943
|
|
|
936
|
-
|
|
944
|
+
// P1 defence-in-depth: re-filter AFTER splitting. Long turns pass the
|
|
945
|
+
// turn-level filterNoiseTurns gate, but chunking can peel their tail into
|
|
946
|
+
// severed context-JSON fragments that the turn-level check never saw.
|
|
947
|
+
// F-3b: scrub secrets at the chunk boundary — this is the shared text
|
|
948
|
+
// finalization point for all session-ingest paths (CC/Codex/Gemini), before
|
|
949
|
+
// any chunk reaches embedding, LLM extraction, or storage.
|
|
950
|
+
return chunks
|
|
951
|
+
.filter((c) => !isNoise(c.text))
|
|
952
|
+
.map((c) => {
|
|
953
|
+
const r = redactSecrets(c.text);
|
|
954
|
+
return r.redacted > 0 ? { ...c, text: r.text } : c;
|
|
955
|
+
});
|
|
937
956
|
}
|
|
938
957
|
|
|
939
958
|
// ============================================================================
|
|
@@ -1027,9 +1046,14 @@ export async function ingestCodexSessions(
|
|
|
1027
1046
|
errors: [],
|
|
1028
1047
|
};
|
|
1029
1048
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1049
|
+
// 同时扫 sessions(活跃)+ archived_sessions(App 内归档会把文件移到这里,否则会漏)
|
|
1050
|
+
const baseDirs = [
|
|
1051
|
+
expandHome("~/.codex/sessions"),
|
|
1052
|
+
expandHome("~/.codex/archived_sessions"),
|
|
1053
|
+
];
|
|
1054
|
+
const existingDirs = baseDirs.filter((d) => existsSync(d));
|
|
1055
|
+
if (existingDirs.length === 0) {
|
|
1056
|
+
result.errors.push(`Codex sessions directory not found: ${baseDirs.join(", ")}`);
|
|
1033
1057
|
return result;
|
|
1034
1058
|
}
|
|
1035
1059
|
|
|
@@ -1048,7 +1072,7 @@ export async function ingestCodexSessions(
|
|
|
1048
1072
|
}
|
|
1049
1073
|
}
|
|
1050
1074
|
}
|
|
1051
|
-
walk(
|
|
1075
|
+
for (const dir of existingDirs) walk(dir);
|
|
1052
1076
|
allFiles.sort();
|
|
1053
1077
|
|
|
1054
1078
|
// --recent: only keep files modified within N hours
|
|
@@ -1171,6 +1195,276 @@ export async function ingestCodexSessions(
|
|
|
1171
1195
|
return result;
|
|
1172
1196
|
}
|
|
1173
1197
|
|
|
1198
|
+
// ============================================================================
|
|
1199
|
+
// Kimi Code Session Parser
|
|
1200
|
+
// ============================================================================
|
|
1201
|
+
// Kimi Code 的 wire.jsonl 是事件流(非 role/content 行),对话内容只在三类事件里:
|
|
1202
|
+
// turn.prompt (origin.kind="user") → 用户输入,.input 是 text part 数组
|
|
1203
|
+
// context.append_message → 消息;用户消息只收 origin.kind="user"
|
|
1204
|
+
// (injection/skill_activation 等是系统注入,跳过)
|
|
1205
|
+
// context.append_loop_event content.part → 助手文本(part.type==="text";think 跳过)
|
|
1206
|
+
// 其余类型(llm.tools_snapshot / mcp.tools_discovered / config.update / llm.request /
|
|
1207
|
+
// usage.record / permission.* 等,约占 86% 体积)是运行事件,一律跳过。
|
|
1208
|
+
function parseKimiSession(filePath: string): ConversationTurn[] {
|
|
1209
|
+
const turns: ConversationTurn[] = [];
|
|
1210
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1211
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1212
|
+
|
|
1213
|
+
// sessionId 从路径取:.../sessions/<workDirKey>/<sessionId>/agents/main/wire.jsonl
|
|
1214
|
+
// 去掉 "session_" 前缀,否则 scope 统一变成 kimi:session_
|
|
1215
|
+
const pathParts = filePath.split("/");
|
|
1216
|
+
const rawId = pathParts.length >= 4 ? pathParts[pathParts.length - 4] : basename(filePath, ".jsonl");
|
|
1217
|
+
const sessionId = rawId.replace(/^session_/, "");
|
|
1218
|
+
|
|
1219
|
+
const pushTurn = (role: "user" | "assistant", text: string, timestamp: string) => {
|
|
1220
|
+
const trimmed = text.trim();
|
|
1221
|
+
// 中文 8 字符已是完整句;用户阈值与 codex/cc 对齐
|
|
1222
|
+
if (trimmed.length <= (role === "user" ? 10 : 8)) return;
|
|
1223
|
+
// turn.prompt 与随后的 context.append_message 重复记录同一用户输入(同 codex 相邻去重)
|
|
1224
|
+
const lastTurn = turns[turns.length - 1];
|
|
1225
|
+
if (lastTurn && lastTurn.role === role && lastTurn.text === trimmed) return;
|
|
1226
|
+
turns.push({ role, text: trimmed, timestamp, sessionId });
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
for (const line of lines) {
|
|
1230
|
+
try {
|
|
1231
|
+
const obj = JSON.parse(line);
|
|
1232
|
+
const timestamp = typeof obj.time === "number" ? new Date(obj.time).toISOString() : "";
|
|
1233
|
+
|
|
1234
|
+
if (obj.type === "turn.prompt") {
|
|
1235
|
+
if (obj.origin?.kind !== "user") continue;
|
|
1236
|
+
const parts = Array.isArray(obj.input) ? obj.input : [];
|
|
1237
|
+
const text = parts
|
|
1238
|
+
.filter((c: any) => c?.type === "text")
|
|
1239
|
+
.map((c: any) => c.text || "")
|
|
1240
|
+
.join("\n");
|
|
1241
|
+
if (text) pushTurn("user", text, timestamp);
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if (obj.type === "context.append_message") {
|
|
1246
|
+
const msg = obj.message;
|
|
1247
|
+
if (!msg || (msg.role !== "user" && msg.role !== "assistant")) continue;
|
|
1248
|
+
// 系统注入(system-reminder、skill 激活、后台任务通知)不是用户对话内容
|
|
1249
|
+
if (msg.role === "user" && msg.origin?.kind !== "user") continue;
|
|
1250
|
+
let text = "";
|
|
1251
|
+
if (typeof msg.content === "string") {
|
|
1252
|
+
text = msg.content;
|
|
1253
|
+
} else if (Array.isArray(msg.content)) {
|
|
1254
|
+
text = msg.content
|
|
1255
|
+
.filter((c: any) => c.type === "text")
|
|
1256
|
+
.map((c: any) => c.text || "")
|
|
1257
|
+
.join("\n");
|
|
1258
|
+
}
|
|
1259
|
+
if (text) pushTurn(msg.role, text, timestamp);
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (obj.type === "context.append_loop_event") {
|
|
1264
|
+
const part = obj.event?.type === "content.part" ? obj.event.part : null;
|
|
1265
|
+
if (part?.type === "text" && typeof part.text === "string") {
|
|
1266
|
+
pushTurn("assistant", part.text, timestamp);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
} catch {
|
|
1270
|
+
// Skip malformed lines
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
return turns;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export async function ingestKimiSessions(
|
|
1278
|
+
store: MemoryStore,
|
|
1279
|
+
embedder: Embedder,
|
|
1280
|
+
options: { limit?: number; verbose?: boolean; noDedup?: boolean; llm?: LLMClient | null; recentHours?: number } = {},
|
|
1281
|
+
): Promise<IngestResult> {
|
|
1282
|
+
const result: IngestResult = {
|
|
1283
|
+
source: "kimi",
|
|
1284
|
+
filesProcessed: 0,
|
|
1285
|
+
chunksIngested: 0,
|
|
1286
|
+
chunksSkipped: 0,
|
|
1287
|
+
chunksDeduped: 0,
|
|
1288
|
+
dedupReasonCounts: createDedupReasonCounts(),
|
|
1289
|
+
errors: [],
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
const baseDir = expandHome("~/.kimi-code/sessions");
|
|
1293
|
+
const indexPath = expandHome("~/.kimi-code/session_index.jsonl");
|
|
1294
|
+
|
|
1295
|
+
// 文件发现:优先 session_index.jsonl(精确),再 walk sessions 目录兜底(索引缺漏时)。
|
|
1296
|
+
// 只收 agents/main/wire.jsonl,子 agent 的 agents/agent-N/wire.jsonl 与主对话重复,不采。
|
|
1297
|
+
const seen = new Set<string>();
|
|
1298
|
+
const allFiles: string[] = [];
|
|
1299
|
+
const addFile = (p: string) => {
|
|
1300
|
+
if (!seen.has(p) && existsSync(p)) {
|
|
1301
|
+
seen.add(p);
|
|
1302
|
+
allFiles.push(p);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
if (existsSync(indexPath)) {
|
|
1307
|
+
try {
|
|
1308
|
+
for (const line of readFileSync(indexPath, "utf-8").split("\n")) {
|
|
1309
|
+
if (!line.trim()) continue;
|
|
1310
|
+
try {
|
|
1311
|
+
const entry = JSON.parse(line);
|
|
1312
|
+
if (typeof entry.sessionDir === "string" && entry.sessionDir) {
|
|
1313
|
+
addFile(join(entry.sessionDir, "agents", "main", "wire.jsonl"));
|
|
1314
|
+
}
|
|
1315
|
+
} catch {
|
|
1316
|
+
// Skip malformed index lines
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
} catch {
|
|
1320
|
+
// 索引读不出来就走下面的目录扫描
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
if (existsSync(baseDir)) {
|
|
1325
|
+
const mainWireSuffix = join("agents", "main", "wire.jsonl");
|
|
1326
|
+
const walk = (dir: string) => {
|
|
1327
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1328
|
+
const full = join(dir, entry.name);
|
|
1329
|
+
if (entry.isDirectory()) {
|
|
1330
|
+
walk(full);
|
|
1331
|
+
} else if (full.endsWith(mainWireSuffix)) {
|
|
1332
|
+
addFile(full);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
walk(baseDir);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (allFiles.length === 0) {
|
|
1340
|
+
result.errors.push(`Kimi Code sessions not found: ${baseDir} (index: ${indexPath})`);
|
|
1341
|
+
return result;
|
|
1342
|
+
}
|
|
1343
|
+
allFiles.sort();
|
|
1344
|
+
|
|
1345
|
+
const recentMode = options.recentHours !== undefined;
|
|
1346
|
+
const cutoffMs = recentMode ? Date.now() - options.recentHours! * 3600_000 : 0;
|
|
1347
|
+
|
|
1348
|
+
// --recent: only keep files modified within N hours
|
|
1349
|
+
const filteredFiles = recentMode
|
|
1350
|
+
? allFiles.filter((f) => { try { return statSync(f).mtimeMs >= cutoffMs; } catch { return false; } })
|
|
1351
|
+
: allFiles;
|
|
1352
|
+
|
|
1353
|
+
const filesToProcess = options.limit ? filteredFiles.slice(0, options.limit) : filteredFiles;
|
|
1354
|
+
const total = filesToProcess.length;
|
|
1355
|
+
|
|
1356
|
+
for (let fi = 0; fi < filesToProcess.length; fi++) {
|
|
1357
|
+
const filePath = filesToProcess[fi];
|
|
1358
|
+
|
|
1359
|
+
try {
|
|
1360
|
+
const stat = statSync(filePath);
|
|
1361
|
+
if (stat.size < 200) {
|
|
1362
|
+
result.chunksSkipped++;
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// In --recent mode, skip ingested-files check — files may have new content appended
|
|
1367
|
+
if (!recentMode && isProcessed(filePath, stat.size, stat.mtimeMs)) {
|
|
1368
|
+
result.chunksSkipped++;
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
const turns = parseKimiSession(filePath);
|
|
1373
|
+
if (turns.length === 0) {
|
|
1374
|
+
result.chunksSkipped++;
|
|
1375
|
+
markProcessed(filePath, stat.size, 0, stat.mtimeMs);
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const chunks = groupTurnsIntoChunks(turns);
|
|
1380
|
+
const texts = chunks.map((c) => c.text);
|
|
1381
|
+
const batchSize = 32;
|
|
1382
|
+
let fileChunks = 0;
|
|
1383
|
+
|
|
1384
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
1385
|
+
const batch = texts.slice(i, i + batchSize);
|
|
1386
|
+
const batchChunks = chunks.slice(i, i + batchSize);
|
|
1387
|
+
|
|
1388
|
+
try {
|
|
1389
|
+
const vectors = await embedder.embedBatchPassage(batch);
|
|
1390
|
+
const toStore: Array<{text: string; vector: number[]; category: string; scope: string; importance: number; metadata: string}> = [];
|
|
1391
|
+
|
|
1392
|
+
// Dedup + L0 batch
|
|
1393
|
+
const dedupedTexts: string[] = [];
|
|
1394
|
+
const dedupedVectors: number[][] = [];
|
|
1395
|
+
const dedupedChunks: typeof batchChunks = [];
|
|
1396
|
+
|
|
1397
|
+
for (let j = 0; j < batch.length; j++) {
|
|
1398
|
+
const vector = vectors[j];
|
|
1399
|
+
if (!vector || vector.length === 0) {
|
|
1400
|
+
result.chunksSkipped++;
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
if (!options.noDedup) {
|
|
1404
|
+
const decision = await dedupCheck(store, vector, batch[j], options.llm);
|
|
1405
|
+
recordDedupDecision(result, decision);
|
|
1406
|
+
if (decision.secondaryDeletes?.length) {
|
|
1407
|
+
await executeSecondaryDeletes(store, decision.secondaryDeletes);
|
|
1408
|
+
}
|
|
1409
|
+
if (decision.action === "skip") continue;
|
|
1410
|
+
}
|
|
1411
|
+
dedupedTexts.push(batch[j]);
|
|
1412
|
+
dedupedVectors.push(vector);
|
|
1413
|
+
dedupedChunks.push(batchChunks[j]);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (dedupedTexts.length > 0) {
|
|
1417
|
+
if (!options.llm) {
|
|
1418
|
+
queueForLaterExtraction(
|
|
1419
|
+
dedupedChunks.map(c => ({ text: c.text, scope: `kimi:${c.sessionId.slice(0, 8)}` }))
|
|
1420
|
+
);
|
|
1421
|
+
result.chunksSkipped += dedupedTexts.length;
|
|
1422
|
+
} else {
|
|
1423
|
+
const extractions = await smartExtractBatch(dedupedTexts, options.llm);
|
|
1424
|
+
const coreSummaries = await generateCoreSummaries(dedupedTexts, options.llm);
|
|
1425
|
+
for (let j = 0; j < dedupedTexts.length; j++) {
|
|
1426
|
+
const chunk = dedupedChunks[j];
|
|
1427
|
+
const ext = extractions[j];
|
|
1428
|
+
toStore.push(buildIngestedEntry({
|
|
1429
|
+
source: "kimi",
|
|
1430
|
+
scope: `kimi:${chunk.sessionId.slice(0, 8)}`,
|
|
1431
|
+
text: dedupedTexts[j],
|
|
1432
|
+
vector: dedupedVectors[j],
|
|
1433
|
+
extraction: ext,
|
|
1434
|
+
sessionId: chunk.sessionId,
|
|
1435
|
+
file: basename(filePath),
|
|
1436
|
+
coreSummary: coreSummaries[j],
|
|
1437
|
+
}));
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
if (toStore.length > 0) {
|
|
1443
|
+
await store.storeBatch(toStore);
|
|
1444
|
+
result.chunksIngested += toStore.length;
|
|
1445
|
+
fileChunks += toStore.length;
|
|
1446
|
+
}
|
|
1447
|
+
} catch (err: any) {
|
|
1448
|
+
result.errors.push(`Embedding batch error: ${err.message}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
markProcessed(filePath, stat.size, fileChunks, stat.mtimeMs);
|
|
1453
|
+
result.filesProcessed++;
|
|
1454
|
+
|
|
1455
|
+
if ((fi + 1) % 10 === 0 || fi + 1 === total) {
|
|
1456
|
+
console.log(
|
|
1457
|
+
` Kimi: ${fi + 1}/${total} files, ${result.chunksIngested} chunks ingested`,
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
} catch (err: any) {
|
|
1461
|
+
result.errors.push(`Error processing ${basename(filePath)}: ${err.message}`);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1174
1468
|
// ============================================================================
|
|
1175
1469
|
// Gemini Session Parser
|
|
1176
1470
|
// ============================================================================
|
|
@@ -1650,7 +1944,8 @@ export async function ingestMarkdownFiles(
|
|
|
1650
1944
|
continue;
|
|
1651
1945
|
}
|
|
1652
1946
|
|
|
1653
|
-
|
|
1947
|
+
// F-3b: scrub before embedding and storage
|
|
1948
|
+
const texts = sections.map((s) => redactSecrets(s.text).text);
|
|
1654
1949
|
const batchSize = 32;
|
|
1655
1950
|
let fileChunks = 0;
|
|
1656
1951
|
|
|
@@ -1788,7 +2083,8 @@ export async function ingestGenericText(
|
|
|
1788
2083
|
|
|
1789
2084
|
const chunkResult = chunkDocument(textToChunk, CONVERSATION_CHUNK_CONFIG);
|
|
1790
2085
|
|
|
1791
|
-
|
|
2086
|
+
// F-3b: scrub before embedding and storage
|
|
2087
|
+
const texts = chunkResult.chunks.map((t) => redactSecrets(t).text);
|
|
1792
2088
|
const batchSize = 32;
|
|
1793
2089
|
|
|
1794
2090
|
for (let i = 0; i < texts.length; i += batchSize) {
|
package/src/kg-extractor.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { LLMClient } from "./llm-client.js";
|
|
|
8
8
|
import type { KGStore } from "./kg-store.js";
|
|
9
9
|
import { logInfo, logWarn } from "./stderr-log.js";
|
|
10
10
|
import { detectLang, getKgPromptHook } from "./language-hook.js";
|
|
11
|
+
import * as envConfig from "./env-config.js";
|
|
11
12
|
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// Types
|
|
@@ -39,7 +40,7 @@ interface LlmExtractionResponse {
|
|
|
39
40
|
|
|
40
41
|
/** Check if KG mode is enabled (env var) */
|
|
41
42
|
export function isKGModeEnabled(): boolean {
|
|
42
|
-
return
|
|
43
|
+
return envConfig.kgMode();
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// ============================================================================
|
|
@@ -95,7 +96,13 @@ const DEFAULT_KG_SYSTEM = `You are a knowledge graph extraction assistant. Extra
|
|
|
95
96
|
const DEFAULT_KG_USER_TEMPLATE = `Extract knowledge graph triples from the following text.
|
|
96
97
|
Rules:
|
|
97
98
|
- Use simple predicates: uses, created_by, works_with, is_a, part_of, has, located_in, belongs_to, depends_on, implements, extends, related_to, caused_by, results_in, precedes, follows
|
|
98
|
-
-
|
|
99
|
+
- confidence must be EXACTLY one of these five values (models collapse continuous ranges — pick the one matching the evidence):
|
|
100
|
+
1.0 = stated explicitly in the text
|
|
101
|
+
0.85 = strong inference (clearly implied, never stated)
|
|
102
|
+
0.75 = reasonable inference (requires interpretation)
|
|
103
|
+
0.65 = weak inference (thematic association only)
|
|
104
|
+
0.55 = speculative (surface co-occurrence)
|
|
105
|
+
Never use 0.5 or any value outside this list. If unsure between two tiers, pick the lower.
|
|
99
106
|
- Extract 3-8 triples
|
|
100
107
|
- Return JSON: { "triples": [{ "subject": "", "predicate": "", "object": "", "confidence": 0.0 }] }
|
|
101
108
|
|
|
@@ -163,8 +170,11 @@ export class KGExtractor {
|
|
|
163
170
|
prompt,
|
|
164
171
|
);
|
|
165
172
|
} catch (err) {
|
|
173
|
+
// Transport/API failure is NOT "zero triples" — propagate so callers can
|
|
174
|
+
// distinguish (backfill must not journal it as processed). The capture
|
|
175
|
+
// path's fire-and-forget .catch() keeps memory writes unaffected.
|
|
166
176
|
logWarn(`[KG] LLM extraction failed: ${String(err)}`);
|
|
167
|
-
|
|
177
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
if (!response?.triples || !Array.isArray(response.triples)) {
|