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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized RECALLNEST_* environment variable accessors (P3-C config 收口).
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for every fixed `RECALLNEST_*` env flag read across `src/`:
|
|
5
|
+
* the env NAME, its default, and its parsing all live here. Consumers call these
|
|
6
|
+
* accessors instead of reading `process.env` directly.
|
|
7
|
+
*
|
|
8
|
+
* This is a PURE-MOVEMENT refactor — each accessor preserves the exact original
|
|
9
|
+
* parsing. Do not "improve" them; the following invariants are load-bearing:
|
|
10
|
+
*
|
|
11
|
+
* - Boolean flags use strict `=== "true"` — no trim, no case-folding, no truthy
|
|
12
|
+
* coercion. `"True"` / `" true "` / `"1"` must NOT enable a flag.
|
|
13
|
+
* - String defaults use `||`, NOT `??` — an empty string falls through to the
|
|
14
|
+
* default (matches the original inline reads).
|
|
15
|
+
* - Raw-value accessors (recall mode, ports) return the UNPARSED env value so the
|
|
16
|
+
* caller keeps its existing validation / clamping / config-fallback logic. In
|
|
17
|
+
* particular `recallModeRaw()` must not default to "summary" here, or it would
|
|
18
|
+
* bypass the `config.recallMode` fallback in resolveRecallMode().
|
|
19
|
+
* - `mcpTier()` keeps only the type assertion with NO runtime validation — an
|
|
20
|
+
* illegal non-empty value is preserved as-is (downstream shouldRegisterTool()
|
|
21
|
+
* relies on this), so do not coerce unknown values back to the default.
|
|
22
|
+
*
|
|
23
|
+
* Accessors are FUNCTIONS (lazy): they read `process.env` at call time. Never
|
|
24
|
+
* freeze them into module-load constants — 100+ tests toggle `process.env` at
|
|
25
|
+
* runtime. Consumers must substitute in place and never relocate a read across
|
|
26
|
+
* the `loadDotEnv()` boundary or into a different evaluation phase: five reads are
|
|
27
|
+
* intentionally module-init-time eager (mcp-server tier, api/ui ports,
|
|
28
|
+
* activity-counter / distill-lock data dir) and must stay eager.
|
|
29
|
+
*
|
|
30
|
+
* Intentionally NOT centralized here:
|
|
31
|
+
* - scope-policy.ts's RECALLNEST_DEFAULT_SCOPE / _SCOPE / _PROJECT_SCOPE /
|
|
32
|
+
* _SESSION_ID — an injectable `options.env || process.env` policy entry that is
|
|
33
|
+
* already consolidated in that module with caller-injection semantics.
|
|
34
|
+
* - store.ts's `RECALLNEST_NS` — a local hash-namespace constant, not an env var.
|
|
35
|
+
* - config-template `${VAR}` expansion in runtime-config / embedder / llm-client.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// --- Boolean feature flags (strict === "true") ---
|
|
39
|
+
|
|
40
|
+
export const multiVector = (): boolean => process.env.RECALLNEST_MULTI_VECTOR === "true";
|
|
41
|
+
|
|
42
|
+
export const emotionScoring = (): boolean => process.env.RECALLNEST_EMOTION_SCORING === "true";
|
|
43
|
+
|
|
44
|
+
export const predictiveMemory = (): boolean => process.env.RECALLNEST_PREDICTIVE_MEMORY === "true";
|
|
45
|
+
|
|
46
|
+
export const synthesize = (): boolean => process.env.RECALLNEST_SYNTHESIZE === "true";
|
|
47
|
+
|
|
48
|
+
export const llmConsolidation = (): boolean => process.env.RECALLNEST_LLM_CONSOLIDATION === "true";
|
|
49
|
+
|
|
50
|
+
export const constructiveRetrieval = (): boolean =>
|
|
51
|
+
process.env.RECALLNEST_CONSTRUCTIVE_RETRIEVAL === "true";
|
|
52
|
+
|
|
53
|
+
export const narrativeMode = (): boolean => process.env.RECALLNEST_NARRATIVE_MODE === "true";
|
|
54
|
+
|
|
55
|
+
export const kgMode = (): boolean => process.env.RECALLNEST_KG_MODE === "true";
|
|
56
|
+
|
|
57
|
+
export const coreSummary = (): boolean => process.env.RECALLNEST_CORE_SUMMARY === "true";
|
|
58
|
+
|
|
59
|
+
export const errorSignatureBoost = (): boolean =>
|
|
60
|
+
process.env.RECALLNEST_ERROR_SIGNATURE_BOOST === "true";
|
|
61
|
+
|
|
62
|
+
export const usageDecay = (): boolean => process.env.RECALLNEST_USAGE_DECAY === "true";
|
|
63
|
+
|
|
64
|
+
// --- LA-1: Layer admission (tri-state, defaults to off) ---
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 资格层准入模式:默认检索是否只召回 durable 层(提炼结论),把 evidence
|
|
68
|
+
* (transcript 碎片)留给 deja / 显式溯源。
|
|
69
|
+
*
|
|
70
|
+
* off — 默认,完全不生效(现网行为不变)
|
|
71
|
+
* observe — 只计算并记录"会过滤掉什么",返回结果不变(影子期)
|
|
72
|
+
* on — 真正过滤
|
|
73
|
+
*/
|
|
74
|
+
export const layerAdmission = (): "off" | "observe" | "on" => {
|
|
75
|
+
const v = process.env.RECALLNEST_LAYER_ADMISSION;
|
|
76
|
+
return v === "on" || v === "observe" ? v : "off";
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** durable 层命中数低于此值时回退到全量候选池,避免"宁缺毋滥"变成"什么都没有"。 */
|
|
80
|
+
export const layerAdmissionMin = (): number => {
|
|
81
|
+
const n = Number(process.env.RECALLNEST_LAYER_ADMISSION_MIN);
|
|
82
|
+
return Number.isFinite(n) && n > 0 ? n : 3;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// --- String settings with `||` default (empty string falls through) ---
|
|
86
|
+
|
|
87
|
+
export const dataDir = (): string => process.env.RECALLNEST_DATA_DIR || "data";
|
|
88
|
+
|
|
89
|
+
export const mcpTier = (): "core" | "advanced" | "full" =>
|
|
90
|
+
(process.env.RECALLNEST_MCP_TIER || "advanced") as "core" | "advanced" | "full";
|
|
91
|
+
|
|
92
|
+
// --- LanceDB read consistency (cross-process visibility) ---
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Read consistency interval (seconds) resolved for lancedb.connect().
|
|
96
|
+
* Without it a long-lived table handle pins its manifest version and never
|
|
97
|
+
* sees writes committed by other processes (CLI ingest vs resident MCP/API/UI
|
|
98
|
+
* servers). Explicit StoreConfig.readConsistencyInterval wins over this env.
|
|
99
|
+
*
|
|
100
|
+
* Unset / empty → 0 (strong consistency: check for external commits per read)
|
|
101
|
+
* "off" | "none" → undefined (legacy unchecked-handle behavior, escape hatch)
|
|
102
|
+
* number ≥ 0 → bounded staleness window in seconds (invalid values → 0)
|
|
103
|
+
*/
|
|
104
|
+
export const readConsistencyInterval = (): number | undefined => {
|
|
105
|
+
const raw = process.env.RECALLNEST_READ_CONSISTENCY_INTERVAL;
|
|
106
|
+
if (raw === undefined || raw === "") return 0;
|
|
107
|
+
if (raw === "off" || raw === "none") return undefined;
|
|
108
|
+
const n = Number(raw);
|
|
109
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// --- Raw env values (caller validates / clamps / falls back to config) ---
|
|
113
|
+
|
|
114
|
+
export const recallModeRaw = (): string | undefined => process.env.RECALLNEST_RECALL_MODE;
|
|
115
|
+
|
|
116
|
+
export const uiPortRaw = (): string | undefined => process.env.RECALLNEST_UI_PORT;
|
|
117
|
+
|
|
118
|
+
export const apiPortRaw = (): string | undefined => process.env.RECALLNEST_API_PORT;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A1 · Error-signature extractor — zero-LLM 结构化错误指纹(debug case 精确召回用)。
|
|
3
|
+
*
|
|
4
|
+
* 借鉴 MemOS local-plugin `core/capture/error-signature.ts`(repo-insight 审计 2026-06-13),
|
|
5
|
+
* 针对 RecallNest 本地化适配(Codex read-only 验证指出的三处不适配):
|
|
6
|
+
* - case 没有 toolCalls,错误指纹藏在 problem/context 自由文本里(MemOS 面向 tool output)
|
|
7
|
+
* - 补裸 `X not found` / `No such file or directory` / 中文错误模式(MemOS 原 6 正则抽不到
|
|
8
|
+
* 裸的 `xmlsec1 not found`,也会被英文 alpha 检查滤掉中文)
|
|
9
|
+
* - normaliseFragment 放宽长度/字母检查以保留中文短片段
|
|
10
|
+
*
|
|
11
|
+
* **读写对称**:纯函数。写入端(persistCaseMemory)抽指纹存 metadata.error_signature;
|
|
12
|
+
* 未来 P3-A 检索精确召回通道复用同一个 extractErrorSignatures,保证读写抽取规则一致。
|
|
13
|
+
* 零 LLM、确定性——每次 case 写入都跑,必须便宜。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** 每条 case 最多保留的指纹数(控制 hot-path 边界)。 */
|
|
17
|
+
export const MAX_SIGNATURES = 4;
|
|
18
|
+
const MIN_FRAGMENT_LEN = 6;
|
|
19
|
+
const MAX_FRAGMENT_LEN = 160;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* verbatim 抽取模式——顺序即优先级,整段 match(m[0])作为 fragment。
|
|
23
|
+
*/
|
|
24
|
+
const ERROR_PATTERNS: RegExp[] = [
|
|
25
|
+
// <Name>Error: / <Name>Exception: body
|
|
26
|
+
/\b([A-Z][A-Za-z0-9]*(?:Error|Exception)):\s*([^\n]{4,160})/g,
|
|
27
|
+
// error: / fatal: / ERROR: body
|
|
28
|
+
/\b(?:error|Error|fatal|FATAL|ERROR)\s*:\s*([^\n]{4,160})/g,
|
|
29
|
+
// <cmd>: ... not found / no such file / permission denied(带冒号)
|
|
30
|
+
/\b([A-Za-z0-9_\-./]+):\s*[^\n]{0,40}\b(not found|no such (?:file|directory)|permission denied|undefined reference|command not found)\b[^\n]*/g,
|
|
31
|
+
// 裸 <thing> [command] not found(无冒号——MemOS 原版抽不到 "xmlsec1 not found")
|
|
32
|
+
/\b([A-Za-z0-9_.\-/]{2,60})\s+(?:command )?not found\b/gi,
|
|
33
|
+
// No such file or directory(独立出现)
|
|
34
|
+
/\bno such file or directory\b/gi,
|
|
35
|
+
// <thing> is required / must be / cannot / could not / failed to
|
|
36
|
+
/\b([A-Za-z0-9_]{3,40})\s+(is required|must be|cannot|could not|failed to)\s+[^\n]{3,120}/g,
|
|
37
|
+
// exit code / status N
|
|
38
|
+
/\bexit (?:code|status)\s*[:=]?\s*(\d{1,4})\b[^\n]{0,80}/g,
|
|
39
|
+
// HTTP 4xx/5xx + 标准状态短语(收紧:避免 "500 ms" / "404 rows" 这类裸数字误抽)
|
|
40
|
+
/\b(4\d\d|5\d\d)\s+(Bad Request|Unauthorized|Payment Required|Forbidden|Not Found|Method Not Allowed|Not Acceptable|Request Timeout|Conflict|Gone|Payload Too Large|Unprocessable Entity|Too Many Requests|Internal Server Error|Internal|Not Implemented|Bad Gateway|Service Unavailable|Gateway Timeout)\b/g,
|
|
41
|
+
// 中文错误现象 + 后续短语
|
|
42
|
+
/(找不到|没有找到|不存在|无法|没有权限|权限被拒|拒绝访问|命令不存在|没有那个文件|连接失败|连接超时|超时|崩溃|段错误|空指针|未定义|报错|异常|失败)[^\n]{0,24}/g,
|
|
43
|
+
// 中文 退出码/状态码/错误码/返回码 N
|
|
44
|
+
/(退出码|状态码|错误码|返回码)\s*[:=:]?\s*\d{1,4}/g,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const STOP_WORDS = new Set([
|
|
48
|
+
"the", "for", "this", "that", "your", "from", "with", "have", "has",
|
|
49
|
+
"not", "a", "an", "of", "to", "is", "in", "on", "by", "was", "were",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 过泛中文错误词——单独出现(裸词 + 语气助词、无具体锚点)会在检索端过度匹配。
|
|
54
|
+
* 具体短语(找不到 / 权限被拒 / 连接失败 / 段错误 / 空指针)不在此列、照常保留。
|
|
55
|
+
* 已有 cjk<3 长度门挡掉 2 字裸词;本表治的是 3+ 字「裸词+助词」(失败了 / 超时了)。
|
|
56
|
+
*/
|
|
57
|
+
const GENERIC_CN_ERROR = /^(?:报错|出错|错误|异常|失败|超时|崩溃|无法|未定义|不能)[了的过着吧呢啊啦哦呀嘛。,!?!?,\s]*$/;
|
|
58
|
+
|
|
59
|
+
/** 失败信号词——outcome/solutionSteps 仅在命中时才纳入语料(避免成功态反向噪声)。 */
|
|
60
|
+
const FAILURE_HINT = /(error|fail|failed|failure|exception|not found|cannot|denied|timeout|拒绝|失败|报错|错误|异常|不能|无法|超时|崩溃)/i;
|
|
61
|
+
|
|
62
|
+
export interface ExtractInput {
|
|
63
|
+
/** 主信号:case 的 problem 字段("What problem happened")。 */
|
|
64
|
+
problem: string;
|
|
65
|
+
/** case 的 context(原始 stderr / 触发条件常在此)。 */
|
|
66
|
+
context?: string;
|
|
67
|
+
/** case 的 outcome(解决后状态——偏成功态,仅含失败词时参与)。 */
|
|
68
|
+
outcome?: string;
|
|
69
|
+
/** solution 步骤(修复命令噪声多,仅含失败词时参与)。 */
|
|
70
|
+
solutionSteps?: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 抽取至多 MAX_SIGNATURES 条规范化错误指纹,按 specificity 降序。
|
|
75
|
+
* problem + context 是主语料;outcome / solutionSteps 仅当包含失败词时纳入
|
|
76
|
+
* (outcome 多为成功态,否则会抽到 "exit code 0 / 不再报错" 这类反向信号)。
|
|
77
|
+
*/
|
|
78
|
+
export function extractErrorSignatures(input: ExtractInput): string[] {
|
|
79
|
+
const corpus: string[] = [input.problem];
|
|
80
|
+
if (input.context) corpus.push(input.context);
|
|
81
|
+
const tail = [input.outcome ?? "", ...(input.solutionSteps ?? [])].join("\n");
|
|
82
|
+
if (tail.trim() && FAILURE_HINT.test(tail)) corpus.push(tail);
|
|
83
|
+
|
|
84
|
+
// dedup by lowercased/collapsed key(重叠正则不产近似重复);first-seen casing 胜出。
|
|
85
|
+
const candidates = new Map<string, { frag: string; freq: number }>();
|
|
86
|
+
for (const text of corpus) {
|
|
87
|
+
for (const frag of extractFragments(text)) {
|
|
88
|
+
const normalised = normaliseFragment(frag);
|
|
89
|
+
if (!normalised) continue;
|
|
90
|
+
const key = normalised.toLowerCase().replace(/\s+/g, " ");
|
|
91
|
+
const existing = candidates.get(key);
|
|
92
|
+
if (existing) existing.freq++;
|
|
93
|
+
else candidates.set(key, { frag: normalised, freq: 1 });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const scored = Array.from(candidates.values()).map(({ frag, freq }) => ({
|
|
98
|
+
frag,
|
|
99
|
+
score: specificityScore(frag, freq),
|
|
100
|
+
}));
|
|
101
|
+
scored.sort((a, b) => b.score - a.score);
|
|
102
|
+
return scored.slice(0, MAX_SIGNATURES).map((s) => s.frag);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractFragments(text: string): string[] {
|
|
106
|
+
if (!text) return [];
|
|
107
|
+
const out: string[] = [];
|
|
108
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
109
|
+
pattern.lastIndex = 0;
|
|
110
|
+
let m: RegExpExecArray | null;
|
|
111
|
+
while ((m = pattern.exec(text))) {
|
|
112
|
+
out.push(m[0]);
|
|
113
|
+
if (m.index === pattern.lastIndex) pattern.lastIndex++; // 防零宽匹配死循环
|
|
114
|
+
if (out.length >= 32) break; // 每个 pattern 硬上限
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normaliseFragment(frag: string): string | null {
|
|
121
|
+
const collapsed = frag.replace(/\s+/g, " ").trim();
|
|
122
|
+
const cjk = collapsed.replace(/[^一-鿿]/g, "");
|
|
123
|
+
// 长度门:英文片段 ≥MIN_FRAGMENT_LEN,或中文 ≥3 字(中文短片段如"找不到文件"也保留)
|
|
124
|
+
if (collapsed.length < MIN_FRAGMENT_LEN && cjk.length < 3) return null;
|
|
125
|
+
const truncated = collapsed.length > MAX_FRAGMENT_LEN ? collapsed.slice(0, MAX_FRAGMENT_LEN) : collapsed;
|
|
126
|
+
// 信息量门:英文字母 ≥4 或 中文 ≥2 字(MemOS 原版只看 alpha≥4,会滤掉纯中文)
|
|
127
|
+
const alpha = truncated.replace(/[^A-Za-z]/g, "");
|
|
128
|
+
if (alpha.length < 4 && cjk.length < 2) return null;
|
|
129
|
+
// 纯英文停用词片段才拒;纯中文(words 为空)不走这条
|
|
130
|
+
const words = truncated.toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean);
|
|
131
|
+
if (words.length > 0 && words.every((w) => STOP_WORDS.has(w))) return null;
|
|
132
|
+
// P3-A 收紧:裸过泛中文词(报错/失败/超时…+语气助词)且无具体锚点(latin≥3 / 多位数字 / 路径)→ 拒,
|
|
133
|
+
// 避免检索端 error-signature 精确通道被这类词过度匹配成噪声。
|
|
134
|
+
const hasSpecificAnchor = /[A-Za-z]{3,}|\d{2,}|\//.test(truncated);
|
|
135
|
+
if (!hasSpecificAnchor && GENERIC_CN_ERROR.test(truncated)) return null;
|
|
136
|
+
return truncated;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** specificity:不寻常 token(PascalCase Error / 路径 / 错误码 / 中文错误)得分更高。 */
|
|
140
|
+
function specificityScore(frag: string, freq: number): number {
|
|
141
|
+
let score = 0;
|
|
142
|
+
if (/\b[A-Z][a-zA-Z]*Error\b/.test(frag)) score += 3;
|
|
143
|
+
if (/\b[A-Z][a-zA-Z]*Exception\b/.test(frag)) score += 3;
|
|
144
|
+
if (/(\b|_)E[A-Z]{3,}\b/.test(frag)) score += 2; // ENOENT, EACCES
|
|
145
|
+
if (/\/[a-zA-Z0-9._\-/]+/.test(frag)) score += 2; // path
|
|
146
|
+
if (/\bcode\s*=\s*\d+/.test(frag)) score += 1;
|
|
147
|
+
if (/\b\d{3}\b/.test(frag)) score += 1; // status
|
|
148
|
+
if (/_/.test(frag)) score += 1; // snake_case id
|
|
149
|
+
if (/[一-鿿]/.test(frag)) score += 1; // 中文错误片段值得保留
|
|
150
|
+
score += Math.min(2, freq - 1); // 重复片段小幅加权
|
|
151
|
+
if (frag.length > 120) score -= 1; // 过长片段降权
|
|
152
|
+
return score;
|
|
153
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 错误分类学 — 借鉴 RepoPrompt CE 的 error taxonomy。
|
|
3
|
+
*
|
|
4
|
+
* 问题:RecallNest 43 个 MCP 工具的错误现在三种风格混着(fail-open 空结果 /
|
|
5
|
+
* 错误当正常 content / 局部 catch 返回文本),agent 拿到错误无法判断该重试、
|
|
6
|
+
* 改参数还是上报用户。
|
|
7
|
+
*
|
|
8
|
+
* 方案:给每种失败标三元组 {reasonCode, retryable, responsibility},在 registerTool
|
|
9
|
+
* 外层统一兜底分类,返回带机器可读 block 的 isError result。
|
|
10
|
+
*
|
|
11
|
+
* 范围(第一阶段):只兜底「抛到 handler 顶层、未被内部 catch」的错误。各 handler
|
|
12
|
+
* 内部的 fail-open / 文本错误风格统一是后续第二阶段,不在此处改(避免破坏客户端兼容)。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** 责任方:host=运行环境/配置,app=RecallNest 自身逻辑,peer=调用方输入或上游 API,transport=网络层。 */
|
|
16
|
+
export type ErrorResponsibility = "host" | "app" | "peer" | "transport";
|
|
17
|
+
|
|
18
|
+
export interface ErrorClassification {
|
|
19
|
+
/** 稳定错误码(机器可读,跨版本稳定,供 agent 分支判断)。 */
|
|
20
|
+
reasonCode: string;
|
|
21
|
+
/** 是否值得重试(transient vs permanent)。 */
|
|
22
|
+
retryable: boolean;
|
|
23
|
+
/** 责任方,决定 agent 是重试 / 改参数 / 上报用户。 */
|
|
24
|
+
responsibility: ErrorResponsibility;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 失败模式表:按 error message 模式分类。
|
|
29
|
+
* 未匹配 → internal_error / app / non-retryable(保守:不误导 agent 去重试一个未知错误)。
|
|
30
|
+
*/
|
|
31
|
+
export function classifyError(err: unknown): ErrorClassification {
|
|
32
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
33
|
+
|
|
34
|
+
// transport:网络/连接层,可重试
|
|
35
|
+
if (/econnreset|etimedout|enotfound|econnrefused|socket|network|fetch failed|timed out|timeout/.test(msg)) {
|
|
36
|
+
return { reasonCode: "transport_error", retryable: true, responsibility: "transport" };
|
|
37
|
+
}
|
|
38
|
+
// 上游 API 过载/限流,可重试(退避后)
|
|
39
|
+
if (/rate.?limit|\b429\b|\b529\b|overloaded|too many requests|quota exceeded/.test(msg)) {
|
|
40
|
+
return { reasonCode: "upstream_overloaded", retryable: true, responsibility: "peer" };
|
|
41
|
+
}
|
|
42
|
+
// 上游 API 鉴权/密钥,不可重试(需修 host 配置)
|
|
43
|
+
if (/\b401\b|\b403\b|unauthorized|invalid api key|authentication failed|missing api key/.test(msg)) {
|
|
44
|
+
return { reasonCode: "upstream_auth", retryable: false, responsibility: "host" };
|
|
45
|
+
}
|
|
46
|
+
// 运行环境/配置缺失(host):缺 env var、未配置(Codex 复审 P2-3,关联 lazy-init 失败 P2-1)
|
|
47
|
+
if (/environment variable .* not set|not configured|missing required (config|environment)/.test(msg)) {
|
|
48
|
+
return { reasonCode: "config_missing", retryable: false, responsibility: "host" };
|
|
49
|
+
}
|
|
50
|
+
// 调用方未给 scope(resolveScopeSelection 抛出)
|
|
51
|
+
if (/requires a scope|scope is required|pass scope/.test(msg)) {
|
|
52
|
+
return { reasonCode: "scope_required", retryable: false, responsibility: "peer" };
|
|
53
|
+
}
|
|
54
|
+
// 调用方给的 memory 引用无效:ID 格式错 / 前缀歧义 / 不在可访问 scope(Codex 复审 P2-3)。
|
|
55
|
+
// 指引 agent 给有效 ID / 更长前缀 / 正确 scope,而非误以为 RecallNest 内部故障。
|
|
56
|
+
if (/invalid memory id|ambiguous prefix|invalid .* id format|outside accessible scope|not accessible/.test(msg)) {
|
|
57
|
+
return { reasonCode: "invalid_memory_ref", retryable: false, responsibility: "peer" };
|
|
58
|
+
}
|
|
59
|
+
// 调用方输入非法(zod/validation)
|
|
60
|
+
if (/validation|invalid input|expected .* received|must be a|is required|not a valid|failed to parse/.test(msg)) {
|
|
61
|
+
return { reasonCode: "invalid_input", retryable: false, responsibility: "peer" };
|
|
62
|
+
}
|
|
63
|
+
// 存储层(LanceDB / 向量 / 索引)
|
|
64
|
+
if (/lancedb|database|\btable\b|vector ?search|embedding failed|index/.test(msg)) {
|
|
65
|
+
return { reasonCode: "store_error", retryable: false, responsibility: "app" };
|
|
66
|
+
}
|
|
67
|
+
// 默认:保守归为 app 内部错误,不可重试
|
|
68
|
+
return { reasonCode: "internal_error", retryable: false, responsibility: "app" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** MCP tool error content item 类型(与 handler 成功路径的 content 同构)。 */
|
|
72
|
+
interface ErrorResult {
|
|
73
|
+
content: { type: "text"; text: string }[];
|
|
74
|
+
isError: true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 把任意错误转成带机器可读分类 block 的 MCP isError result。
|
|
79
|
+
* agent 可解析 reason_code / retryable / responsibility 决定下一步。
|
|
80
|
+
*/
|
|
81
|
+
export function toErrorResult(toolName: string, err: unknown): ErrorResult {
|
|
82
|
+
const cls = classifyError(err);
|
|
83
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
84
|
+
return {
|
|
85
|
+
content: [{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: [
|
|
88
|
+
`❌ Tool "${toolName}" failed`,
|
|
89
|
+
`reason_code: ${cls.reasonCode}`,
|
|
90
|
+
`retryable: ${cls.retryable}`,
|
|
91
|
+
`responsibility: ${cls.responsibility}`,
|
|
92
|
+
`detail: ${detail}`,
|
|
93
|
+
].join("\n"),
|
|
94
|
+
}],
|
|
95
|
+
isError: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 安全执行 tool 逻辑:捕获任意抛错(含 lazy-init / handler 执行),分类 + 结构化
|
|
101
|
+
* stderr 日志 + 返回 isError result。提取成独立函数以便单测锁定回归(Codex 复审建议)。
|
|
102
|
+
* registerTool 的 lazyHandler 把「ensureComponents + handler 调用」整体交给它。
|
|
103
|
+
*/
|
|
104
|
+
export async function runToolSafely<T>(toolName: string, fn: () => Promise<T>): Promise<T | ErrorResult> {
|
|
105
|
+
try {
|
|
106
|
+
return await fn();
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const cls = classifyError(err);
|
|
109
|
+
console.error(
|
|
110
|
+
`[recallnest] tool ${toolName} failed [${cls.reasonCode}/${cls.responsibility}/retryable=${cls.retryable}]:`,
|
|
111
|
+
err instanceof Error ? err.stack || err.message : String(err),
|
|
112
|
+
);
|
|
113
|
+
return toErrorResult(toolName, err);
|
|
114
|
+
}
|
|
115
|
+
}
|