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,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall gate — pre-retrieval triage for auto-recall traffic.
|
|
3
|
+
*
|
|
4
|
+
* Borrowed from memory-lancedb-pro's adaptive-retrieval, reshaped per Codex
|
|
5
|
+
* review (2026-07-17) into a three-way verdict instead of a binary SKIP/FORCE:
|
|
6
|
+
*
|
|
7
|
+
* - "full-recall": message explicitly reaches for memory ("记得/上次/my
|
|
8
|
+
* preference") — always run resume + focused search.
|
|
9
|
+
* - "resume-only": bare continuity nudges ("继续", "下一步", "开始吧") —
|
|
10
|
+
* they need checkpoint/continuity, not an embedding search
|
|
11
|
+
* over their own two characters.
|
|
12
|
+
* - "skip-all": greetings, slash commands, heartbeats, whole-message
|
|
13
|
+
* acks, pure emoji, bare CLI invocations — no memory value.
|
|
14
|
+
* - "pass": no rule matched — default full pipeline.
|
|
15
|
+
*
|
|
16
|
+
* Rollout is governed by RECALLNEST_RECALL_GATE (observe-before-enforce,
|
|
17
|
+
* shared-behaviors §5): "observe" (default) computes the verdict and appends a
|
|
18
|
+
* shadow-log line but never changes behavior; "enforce" acts on the verdict;
|
|
19
|
+
* "off" disables even the shadow logging. Shadow entries carry rule id,
|
|
20
|
+
* message length, and decision — never the message text itself.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { dataDir } from "./env-config.js";
|
|
26
|
+
|
|
27
|
+
export type RecallGateDecision = "full-recall" | "resume-only" | "skip-all" | "pass";
|
|
28
|
+
|
|
29
|
+
export interface RecallGateResult {
|
|
30
|
+
decision: RecallGateDecision;
|
|
31
|
+
/** Stable rule identifier, e.g. "force:memory-cue", "skip:ack", "pass:long". */
|
|
32
|
+
ruleId: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type RecallGateMode = "observe" | "enforce" | "off";
|
|
36
|
+
|
|
37
|
+
/** RECALLNEST_RECALL_GATE: unset/invalid → "observe" (shadow-only default). */
|
|
38
|
+
export function resolveRecallGateMode(env: NodeJS.ProcessEnv = process.env): RecallGateMode {
|
|
39
|
+
const raw = (env.RECALLNEST_RECALL_GATE || "").trim().toLowerCase();
|
|
40
|
+
if (raw === "enforce") return "enforce";
|
|
41
|
+
if (raw === "off") return "off";
|
|
42
|
+
return "observe";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Short-text rules below only ever fire on messages up to this length; longer
|
|
46
|
+
// messages carry enough content to always deserve the full pipeline.
|
|
47
|
+
const SHORT_TEXT_MAX_CHARS = 80;
|
|
48
|
+
|
|
49
|
+
// --- full-recall cues (substring, any position, force-first per review) ---
|
|
50
|
+
|
|
51
|
+
const FORCE_CUES_ZH = [
|
|
52
|
+
"记得",
|
|
53
|
+
"回忆",
|
|
54
|
+
"想起",
|
|
55
|
+
"上次",
|
|
56
|
+
"上回",
|
|
57
|
+
"之前讨论",
|
|
58
|
+
"之前说",
|
|
59
|
+
"之前聊",
|
|
60
|
+
"之前提",
|
|
61
|
+
"我的偏好",
|
|
62
|
+
"我说过",
|
|
63
|
+
"你说过",
|
|
64
|
+
"说过吗",
|
|
65
|
+
"存过",
|
|
66
|
+
"记过",
|
|
67
|
+
"提醒过",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Word-bounded and hyphen/underscore-guarded: "recall" must not fire on
|
|
71
|
+
// "auto-recall" or "recall-gate.test.ts" (identifiers, not memory intent).
|
|
72
|
+
const FORCE_CUES_EN = [
|
|
73
|
+
/(?<![\w-])remember(?![\w-])/,
|
|
74
|
+
/(?<![\w-])recall(?![\w-])/,
|
|
75
|
+
/(?<![\w-])last time(?![\w-])/,
|
|
76
|
+
/(?<![\w-])previously(?![\w-])/,
|
|
77
|
+
/(?<![\w-])we discussed(?![\w-])/,
|
|
78
|
+
/(?<![\w-])my preferences?(?![\w-])/,
|
|
79
|
+
/(?<![\w-])you said(?![\w-])/,
|
|
80
|
+
/(?<![\w-])i told you(?![\w-])/,
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// --- resume-only: whole-message continuity nudges ---
|
|
84
|
+
|
|
85
|
+
// Matched against the whole message after trailing particles/punctuation are
|
|
86
|
+
// stripped, so "继续吧" hits but "继续讨论刚才的方案" (real content) passes.
|
|
87
|
+
const RESUME_PHRASES = new Set([
|
|
88
|
+
"继续",
|
|
89
|
+
"接着",
|
|
90
|
+
"接着来",
|
|
91
|
+
"接着做",
|
|
92
|
+
"接着干",
|
|
93
|
+
"接着说",
|
|
94
|
+
"继续做",
|
|
95
|
+
"继续干",
|
|
96
|
+
"下一步",
|
|
97
|
+
"开始",
|
|
98
|
+
"开工",
|
|
99
|
+
"走起",
|
|
100
|
+
"往下",
|
|
101
|
+
"往下走",
|
|
102
|
+
"continue",
|
|
103
|
+
"go on",
|
|
104
|
+
"next",
|
|
105
|
+
"next step",
|
|
106
|
+
"resume",
|
|
107
|
+
"keep going",
|
|
108
|
+
"carry on",
|
|
109
|
+
"proceed",
|
|
110
|
+
"pick up where we left off",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
// --- skip-all: whole-message greetings & acks ---
|
|
114
|
+
|
|
115
|
+
const GREETINGS = new Set([
|
|
116
|
+
"你好",
|
|
117
|
+
"您好",
|
|
118
|
+
"哈喽",
|
|
119
|
+
"嗨",
|
|
120
|
+
"早",
|
|
121
|
+
"早上好",
|
|
122
|
+
"早安",
|
|
123
|
+
"中午好",
|
|
124
|
+
"下午好",
|
|
125
|
+
"晚上好",
|
|
126
|
+
"晚安",
|
|
127
|
+
"在吗",
|
|
128
|
+
"在不在",
|
|
129
|
+
"在么",
|
|
130
|
+
"hi",
|
|
131
|
+
"hello",
|
|
132
|
+
"hey",
|
|
133
|
+
"yo",
|
|
134
|
+
"good morning",
|
|
135
|
+
"good night",
|
|
136
|
+
"good evening",
|
|
137
|
+
"morning",
|
|
138
|
+
"gm",
|
|
139
|
+
"gn",
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
const ACK_WORDS = new Set([
|
|
143
|
+
"好的",
|
|
144
|
+
"好",
|
|
145
|
+
"好嘞",
|
|
146
|
+
"好滴",
|
|
147
|
+
"好呀",
|
|
148
|
+
"嗯",
|
|
149
|
+
"嗯嗯",
|
|
150
|
+
"哦",
|
|
151
|
+
"噢",
|
|
152
|
+
"喔",
|
|
153
|
+
"行",
|
|
154
|
+
"可以",
|
|
155
|
+
"可",
|
|
156
|
+
"没问题",
|
|
157
|
+
"没事",
|
|
158
|
+
"收到",
|
|
159
|
+
"明白",
|
|
160
|
+
"了解",
|
|
161
|
+
"知道了",
|
|
162
|
+
"懂了",
|
|
163
|
+
"对",
|
|
164
|
+
"对的",
|
|
165
|
+
"是",
|
|
166
|
+
"是的",
|
|
167
|
+
"不用",
|
|
168
|
+
"不用了",
|
|
169
|
+
"谢谢",
|
|
170
|
+
"谢了",
|
|
171
|
+
"多谢",
|
|
172
|
+
"辛苦了",
|
|
173
|
+
"辛苦",
|
|
174
|
+
"牛",
|
|
175
|
+
"赞",
|
|
176
|
+
"棒",
|
|
177
|
+
"妙",
|
|
178
|
+
"ok",
|
|
179
|
+
"okay",
|
|
180
|
+
"k",
|
|
181
|
+
"kk",
|
|
182
|
+
"yes",
|
|
183
|
+
"yep",
|
|
184
|
+
"yeah",
|
|
185
|
+
"no",
|
|
186
|
+
"nope",
|
|
187
|
+
"sure",
|
|
188
|
+
"thanks",
|
|
189
|
+
"thank you",
|
|
190
|
+
"thx",
|
|
191
|
+
"ty",
|
|
192
|
+
"cool",
|
|
193
|
+
"nice",
|
|
194
|
+
"great",
|
|
195
|
+
"done",
|
|
196
|
+
"got it",
|
|
197
|
+
"will do",
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
// Bare CLI invocations ("git push", "npm test"). A leading tool name alone is
|
|
201
|
+
// not enough — the rest must look like arguments, not prose: any CJK character
|
|
202
|
+
// or question mark ("git 为什么失败", "why does npm test hang?") disqualifies.
|
|
203
|
+
const CLI_SHAPE =
|
|
204
|
+
/^(git|npm|npx|bun|bunx|yarn|pnpm|node|python3?|pip3?|cargo|make|docker|kubectl|brew|ssh|ls|cd|cat|grep|curl)(\s+[^㐀-鿿??]*)?$/i;
|
|
205
|
+
|
|
206
|
+
// Emoji message test: strip whitespace, then require every remaining unit to
|
|
207
|
+
// be pictographic or an emoji modifier. \p{Extended_Pictographic} deliberately
|
|
208
|
+
// excludes bare digits/#/* (they only render as emoji inside keycap sequences
|
|
209
|
+
// with U+FE0F+U+20E3), so "42" and "#1" fall through to "pass".
|
|
210
|
+
const EMOJI_UNIT =
|
|
211
|
+
/^(?:\p{Extended_Pictographic}|\p{Regional_Indicator}|[\u{1F3FB}-\u{1F3FF}\u{FE0F}\u{200D}\u{20E3}])+$/u;
|
|
212
|
+
|
|
213
|
+
// Trailing tone particles + punctuation tolerated on whole-message matches
|
|
214
|
+
// ("好的吧。", "继续吧!", "开始咯~").
|
|
215
|
+
const TRAILING_FLUFF = /[吧呗啊呀哦嘛喽咯哈嘞捏诶欸了]*[\s。,,.!!??~〜…‥·、;;::'"'"()()]*$/u;
|
|
216
|
+
|
|
217
|
+
function stripTrailingFluff(text: string): string {
|
|
218
|
+
return text.replace(TRAILING_FLUFF, "");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalize(message: string): string {
|
|
222
|
+
return message.replace(/\s+/g, " ").trim();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function isAckMessage(lower: string): boolean {
|
|
226
|
+
const stripped = stripTrailingFluff(lower);
|
|
227
|
+
if (!stripped) return false;
|
|
228
|
+
if (ACK_WORDS.has(stripped)) return true;
|
|
229
|
+
// Multi-token acks ("好的 谢谢", "ok thanks"): every token must be an ack word.
|
|
230
|
+
const tokens = stripped.split(/[\s,,。!!~]+/).filter(Boolean);
|
|
231
|
+
return tokens.length > 1 && tokens.every((t) => ACK_WORDS.has(stripTrailingFluff(t)));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Classify one auto-recall message. Pure and deterministic — call order is
|
|
236
|
+
* force cues → length guard → structural skips → whole-message skips →
|
|
237
|
+
* resume nudges → default pass.
|
|
238
|
+
*/
|
|
239
|
+
export function classifyRecallGate(message: string): RecallGateResult {
|
|
240
|
+
const text = normalize(message);
|
|
241
|
+
if (!text) return { decision: "skip-all", ruleId: "skip:empty" };
|
|
242
|
+
const lower = text.toLowerCase();
|
|
243
|
+
|
|
244
|
+
// 1. Force cues outrank every short-text rule ("可以按上次方案做" must not
|
|
245
|
+
// be swallowed by the ack rule).
|
|
246
|
+
for (const cue of FORCE_CUES_ZH) {
|
|
247
|
+
if (text.includes(cue)) return { decision: "full-recall", ruleId: "force:memory-cue-zh" };
|
|
248
|
+
}
|
|
249
|
+
for (const cue of FORCE_CUES_EN) {
|
|
250
|
+
if (cue.test(lower)) return { decision: "full-recall", ruleId: "force:memory-cue-en" };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 2. Long messages always deserve the full pipeline.
|
|
254
|
+
if (text.length > SHORT_TEXT_MAX_CHARS) return { decision: "pass", ruleId: "pass:long" };
|
|
255
|
+
|
|
256
|
+
// 3. Structural skips.
|
|
257
|
+
if (/^\/\S+/.test(text)) return { decision: "skip-all", ruleId: "skip:slash" };
|
|
258
|
+
if (/^HEARTBEAT/i.test(text)) return { decision: "skip-all", ruleId: "skip:heartbeat" };
|
|
259
|
+
if (EMOJI_UNIT.test(text.replace(/\s+/g, ""))) {
|
|
260
|
+
return { decision: "skip-all", ruleId: "skip:emoji" };
|
|
261
|
+
}
|
|
262
|
+
if (text.length <= 60 && CLI_SHAPE.test(text)) {
|
|
263
|
+
return { decision: "skip-all", ruleId: "skip:cli" };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 4. Whole-message conversational skips.
|
|
267
|
+
const strippedLower = stripTrailingFluff(lower);
|
|
268
|
+
if (GREETINGS.has(strippedLower)) return { decision: "skip-all", ruleId: "skip:greeting" };
|
|
269
|
+
if (isAckMessage(lower)) return { decision: "skip-all", ruleId: "skip:ack" };
|
|
270
|
+
|
|
271
|
+
// 5. Continuity nudges → resume context without a focused search.
|
|
272
|
+
if (RESUME_PHRASES.has(strippedLower)) {
|
|
273
|
+
return { decision: "resume-only", ruleId: "resume:continuity" };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { decision: "pass", ruleId: "pass:default" };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- shadow log (observe-before-enforce evidence trail) ---
|
|
280
|
+
|
|
281
|
+
export interface RecallGateShadowEntry {
|
|
282
|
+
ts: string;
|
|
283
|
+
decision: RecallGateDecision;
|
|
284
|
+
ruleId: string;
|
|
285
|
+
msgLen: number;
|
|
286
|
+
mode: Exclude<RecallGateMode, "off">;
|
|
287
|
+
/** Caller-provided origin, e.g. "api:/v1/auto-recall". Never message text. */
|
|
288
|
+
source?: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const RECALL_GATE_SHADOW_FILE = "recall-gate-shadow.jsonl";
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Append one shadow-log line under dataDir(). Must never block or throw —
|
|
295
|
+
* gate telemetry failing is strictly better than recall failing.
|
|
296
|
+
*/
|
|
297
|
+
export function logRecallGateShadow(entry: RecallGateShadowEntry): void {
|
|
298
|
+
try {
|
|
299
|
+
const dir = dataDir();
|
|
300
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
301
|
+
appendFileSync(join(dir, RECALL_GATE_SHADOW_FILE), `${JSON.stringify(entry)}\n`, "utf8");
|
|
302
|
+
} catch {
|
|
303
|
+
// Shadow logging is best-effort by design.
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { LLMClient } from "./llm-client.js";
|
|
14
14
|
import { logWarn } from "./stderr-log.js";
|
|
15
|
+
import * as envConfig from "./env-config.js";
|
|
15
16
|
|
|
16
17
|
// ============================================================================
|
|
17
18
|
// Configuration
|
|
@@ -38,7 +39,7 @@ export const DEFAULT_SYNTHESIZER_CONFIG: SynthesizerConfig = {
|
|
|
38
39
|
* Check if synthesis is enabled via environment variable.
|
|
39
40
|
*/
|
|
40
41
|
export function isSynthesisEnabled(): boolean {
|
|
41
|
-
return
|
|
42
|
+
return envConfig.synthesize();
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|
package/src/retention-policy.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import * as envConfig from "./env-config.js";
|
|
10
11
|
|
|
11
12
|
export interface RetentionPolicy {
|
|
12
13
|
/** Auto-archive memories older than this many days (0 = disabled) */
|
|
@@ -29,7 +30,7 @@ function scopeHash(scope: string): string {
|
|
|
29
30
|
|
|
30
31
|
function retentionDir(configDir?: string): string {
|
|
31
32
|
return join(
|
|
32
|
-
configDir ?? (
|
|
33
|
+
configDir ?? envConfig.dataDir(),
|
|
33
34
|
"retention",
|
|
34
35
|
);
|
|
35
36
|
}
|
|
@@ -1,7 +1,46 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CATEGORY_MIN_SCORES,
|
|
3
|
+
DEFAULT_RETRIEVAL_CONFIG,
|
|
4
|
+
type RetrievalConfig,
|
|
5
|
+
} from "./retriever.js";
|
|
2
6
|
|
|
3
7
|
export type RetrievalProfileName = "default" | "writing" | "debug" | "fact-check";
|
|
4
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Scale the default per-category thresholds by how loose/tight a profile is overall.
|
|
11
|
+
*
|
|
12
|
+
* Why this exists: a profile used to override `hardMinScore` only, while
|
|
13
|
+
* `Retriever.minScoreFor()` reads `categoryMinScores` first and only falls back to
|
|
14
|
+
* `hardMinScore` for categories not listed. Since the default map lists every
|
|
15
|
+
* category, a profile's looser/tighter intent never reached category admission at
|
|
16
|
+
* all — picking `debug` ("I want episodic detail") still admitted `events` at 0.35
|
|
17
|
+
* and `patterns` at 0.45, exactly as `default` would.
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT: the returned map must stay complete. A partial map would silently send
|
|
20
|
+
* unlisted categories to `hardMinScore` instead of their default threshold.
|
|
21
|
+
*
|
|
22
|
+
* simplified: proportional scaling only — the *relative* ordering across categories
|
|
23
|
+
* is inherited from DEFAULT_CATEGORY_MIN_SCORES unchanged. Re-designing that ordering
|
|
24
|
+
* per profile (e.g. `debug` favouring episodic categories, `writing` favouring
|
|
25
|
+
* semantic ones) is the better end state but needs canary evidence to pick numbers;
|
|
26
|
+
* do that via `bun run eval:canary` before touching the ratios here.
|
|
27
|
+
*/
|
|
28
|
+
function scaledCategoryMinScores(profileHardMinScore: number): Record<string, number> {
|
|
29
|
+
const factor = profileHardMinScore / DEFAULT_RETRIEVAL_CONFIG.hardMinScore;
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
Object.entries(DEFAULT_CATEGORY_MIN_SCORES).map(([category, minScore]) => [
|
|
32
|
+
category,
|
|
33
|
+
Math.round(minScore * factor * 1000) / 1000,
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Kept as named constants so each profile's `hardMinScore` and its scaled category
|
|
39
|
+
// thresholds cannot drift apart.
|
|
40
|
+
const WRITING_HARD_MIN_SCORE = 0.24;
|
|
41
|
+
const DEBUG_HARD_MIN_SCORE = 0.34;
|
|
42
|
+
const FACT_CHECK_HARD_MIN_SCORE = 0.38;
|
|
43
|
+
|
|
5
44
|
export interface RetrievalProfileDefinition {
|
|
6
45
|
name: RetrievalProfileName;
|
|
7
46
|
label: string;
|
|
@@ -27,7 +66,8 @@ export const RETRIEVAL_PROFILES: Record<RetrievalProfileName, RetrievalProfileDe
|
|
|
27
66
|
recencyHalfLifeDays: 90,
|
|
28
67
|
recencyWeight: 0.06,
|
|
29
68
|
timeDecayHalfLifeDays: 365,
|
|
30
|
-
hardMinScore:
|
|
69
|
+
hardMinScore: WRITING_HARD_MIN_SCORE,
|
|
70
|
+
categoryMinScores: scaledCategoryMinScores(WRITING_HARD_MIN_SCORE),
|
|
31
71
|
lengthNormAnchor: 800,
|
|
32
72
|
},
|
|
33
73
|
},
|
|
@@ -42,7 +82,8 @@ export const RETRIEVAL_PROFILES: Record<RetrievalProfileName, RetrievalProfileDe
|
|
|
42
82
|
recencyHalfLifeDays: 7,
|
|
43
83
|
recencyWeight: 0.15,
|
|
44
84
|
timeDecayHalfLifeDays: 30,
|
|
45
|
-
hardMinScore:
|
|
85
|
+
hardMinScore: DEBUG_HARD_MIN_SCORE,
|
|
86
|
+
categoryMinScores: scaledCategoryMinScores(DEBUG_HARD_MIN_SCORE),
|
|
46
87
|
lengthNormAnchor: 420,
|
|
47
88
|
},
|
|
48
89
|
},
|
|
@@ -57,7 +98,8 @@ export const RETRIEVAL_PROFILES: Record<RetrievalProfileName, RetrievalProfileDe
|
|
|
57
98
|
recencyHalfLifeDays: 21,
|
|
58
99
|
recencyWeight: 0.08,
|
|
59
100
|
timeDecayHalfLifeDays: 45,
|
|
60
|
-
hardMinScore:
|
|
101
|
+
hardMinScore: FACT_CHECK_HARD_MIN_SCORE,
|
|
102
|
+
categoryMinScores: scaledCategoryMinScores(FACT_CHECK_HARD_MIN_SCORE),
|
|
61
103
|
lengthNormAnchor: 360,
|
|
62
104
|
},
|
|
63
105
|
},
|