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/access-tracker.ts
CHANGED
|
@@ -38,10 +38,18 @@ export const DEFAULT_ACCESS_TRACKER_CONFIG: AccessTrackerConfig = {
|
|
|
38
38
|
reinforcementFactor: 0.5,
|
|
39
39
|
maxMultiplier: 3.0,
|
|
40
40
|
accessFreshnessHalfLifeDays: 30,
|
|
41
|
-
|
|
41
|
+
// 0 = novelty gate off. 生产实测(2026-07-23):0.35 要求命中的 cosine ≤0.65 才打点,
|
|
42
|
+
// 真实相关命中几乎都 >0.65,导致 accessCount 在生产库全空——"最被用到"的记忆
|
|
43
|
+
// 恰好全被排除,方向与"用进废退"相反。防刷由 cooldown 承担;要恢复旧行为显式传 0.35。
|
|
44
|
+
noveltyThreshold: 0,
|
|
42
45
|
cooldownMs: 300_000, // 5 minutes
|
|
43
46
|
};
|
|
44
47
|
|
|
48
|
+
/** readerIds 在 metadata 里的存储上限。distinctReaderCount 到达上限后饱和不再涨。 */
|
|
49
|
+
// simplified: capped array + saturating count instead of Artel's exact memory_reads
|
|
50
|
+
// ledger table — promotion thresholds are ≤4 distinct readers, so saturation at 8 is harmless.
|
|
51
|
+
export const READER_ID_CAP = 8;
|
|
52
|
+
|
|
45
53
|
// ============================================================================
|
|
46
54
|
// Core
|
|
47
55
|
// ============================================================================
|
|
@@ -52,11 +60,36 @@ export class AccessTracker {
|
|
|
52
60
|
private flushPromise: Promise<void> | null = null;
|
|
53
61
|
/** Cooldown map: entryId → last reinforced timestamp */
|
|
54
62
|
private cooldownMap = new Map<string, number>();
|
|
63
|
+
/** Distinct-reader identity: one stdio MCP server process ≈ one CC session.
|
|
64
|
+
* Used to maintain readerIds / distinctReaderCount on flush (Artel-style read fan-out). */
|
|
65
|
+
readonly readerId: string;
|
|
66
|
+
private exitFlushRegistered = false;
|
|
55
67
|
|
|
56
68
|
constructor(
|
|
57
69
|
private store: MemoryStore,
|
|
58
70
|
private config: AccessTrackerConfig = DEFAULT_ACCESS_TRACKER_CONFIG,
|
|
59
|
-
|
|
71
|
+
readerId?: string,
|
|
72
|
+
) {
|
|
73
|
+
this.readerId = readerId ?? `r-${crypto.randomUUID().slice(0, 8)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Best-effort flush on normal process exit (stdin close → clean shutdown).
|
|
78
|
+
* Covers the "retrieval happened <flushIntervalMs before exit" window where the
|
|
79
|
+
* debounce timer would otherwise drop pending deltas. Opt-in so tests creating
|
|
80
|
+
* many trackers don't leak process listeners. Not wired to SIGTERM on purpose —
|
|
81
|
+
* an async handler there isn't guaranteed to complete and we don't want to own
|
|
82
|
+
* the server's exit semantics.
|
|
83
|
+
*/
|
|
84
|
+
registerExitFlush(): void {
|
|
85
|
+
if (this.exitFlushRegistered) return;
|
|
86
|
+
this.exitFlushRegistered = true;
|
|
87
|
+
process.once("beforeExit", () => {
|
|
88
|
+
this.flush().catch(err => {
|
|
89
|
+
logWarn("AccessTracker exit flush failed:", err);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
60
93
|
|
|
61
94
|
/**
|
|
62
95
|
* Determine whether a retrieval result should trigger access reinforcement.
|
|
@@ -163,43 +196,66 @@ export class AccessTracker {
|
|
|
163
196
|
|
|
164
197
|
for (const [id, delta] of batch) {
|
|
165
198
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
199
|
+
// Read-modify-write goes through the store's per-id patch queue so a
|
|
200
|
+
// concurrent usage/confidence patch can't overwrite this delta (or
|
|
201
|
+
// vice versa). patchFn stays pure — logging happens after settle.
|
|
202
|
+
let tierChange: { from: MemoryTier; to: MemoryTier; newCount: number } | null = null;
|
|
203
|
+
const result = await this.store.patchMetadata(id, (meta, entry) => {
|
|
204
|
+
const prevCount = typeof meta.accessCount === "number" ? meta.accessCount : 0;
|
|
205
|
+
const newCount = prevCount + delta;
|
|
206
|
+
const importance = entry.importance ?? 0.6;
|
|
207
|
+
const lastAccessedAt = now;
|
|
208
|
+
|
|
209
|
+
const currentTier = resolveTier(entry.metadata, importance);
|
|
210
|
+
const newTier = evaluateTierChange(
|
|
211
|
+
currentTier,
|
|
212
|
+
newCount,
|
|
213
|
+
importance,
|
|
214
|
+
lastAccessedAt,
|
|
215
|
+
);
|
|
216
|
+
tierChange = newTier !== currentTier
|
|
217
|
+
? { from: currentTier, to: newTier, newCount }
|
|
218
|
+
: null;
|
|
219
|
+
|
|
220
|
+
// Distinct-reader bookkeeping (Artel read fan-out, lightweight form):
|
|
221
|
+
// dedup by readerId in a capped array; count saturates at cap (under-
|
|
222
|
+
// counting is safer than over-counting for promotion gating).
|
|
223
|
+
const rawReaders = Array.isArray(meta.readerIds)
|
|
224
|
+
? (meta.readerIds as unknown[]).filter((x): x is string => typeof x === "string")
|
|
225
|
+
: [];
|
|
226
|
+
let readerIds = rawReaders;
|
|
227
|
+
let distinctReaderCount = typeof meta.distinctReaderCount === "number"
|
|
228
|
+
? meta.distinctReaderCount
|
|
229
|
+
: rawReaders.length;
|
|
230
|
+
if (!rawReaders.includes(this.readerId) && rawReaders.length < READER_ID_CAP) {
|
|
231
|
+
readerIds = [...rawReaders, this.readerId];
|
|
232
|
+
distinctReaderCount = readerIds.length;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
...meta,
|
|
237
|
+
accessCount: newCount,
|
|
238
|
+
lastAccessedAt,
|
|
239
|
+
readerIds,
|
|
240
|
+
distinctReaderCount,
|
|
241
|
+
...(tierChange ? { tier: tierChange.to } : {}),
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Entry vanished mid-flight → drop the delta (same as the old
|
|
246
|
+
// getById-null path). A patch failure throws and is re-queued below.
|
|
247
|
+
if (!result) continue;
|
|
248
|
+
|
|
249
|
+
if (tierChange !== null) {
|
|
250
|
+
const change: { from: MemoryTier; to: MemoryTier; newCount: number } = tierChange;
|
|
251
|
+
if (TIER_ORDER[change.to] > TIER_ORDER[change.from]) {
|
|
187
252
|
promotions++;
|
|
188
|
-
logInfo(`[INFO] Tier promotion: ${id.slice(0, 8)} ${
|
|
253
|
+
logInfo(`[INFO] Tier promotion: ${id.slice(0, 8)} ${change.from} → ${change.to} (access=${change.newCount})`);
|
|
189
254
|
} else {
|
|
190
255
|
demotions++;
|
|
191
|
-
logInfo(`[INFO] Tier demotion: ${id.slice(0, 8)} ${
|
|
256
|
+
logInfo(`[INFO] Tier demotion: ${id.slice(0, 8)} ${change.from} → ${change.to}`);
|
|
192
257
|
}
|
|
193
258
|
}
|
|
194
|
-
|
|
195
|
-
const updatedMeta = {
|
|
196
|
-
...meta,
|
|
197
|
-
accessCount: newCount,
|
|
198
|
-
lastAccessedAt,
|
|
199
|
-
...(tierChanged ? { tier: newTier } : {}),
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
await this.store.update(id, { metadata: JSON.stringify(updatedMeta) });
|
|
203
259
|
} catch {
|
|
204
260
|
// Re-queue failed entries for next flush
|
|
205
261
|
this.pending.set(id, (this.pending.get(id) || 0) + delta);
|
package/src/activity-counter.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
mkdirSync,
|
|
12
12
|
} from "node:fs";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
|
+
import * as envConfig from "./env-config.js";
|
|
14
15
|
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Config
|
|
@@ -29,7 +30,7 @@ export interface ActivityCounterConfig {
|
|
|
29
30
|
|
|
30
31
|
export const DEFAULT_ACTIVITY_CONFIG: ActivityCounterConfig = {
|
|
31
32
|
statsPath: join(
|
|
32
|
-
|
|
33
|
+
envConfig.dataDir(),
|
|
33
34
|
"activity-stats.json",
|
|
34
35
|
),
|
|
35
36
|
lightThreshold: 15,
|
|
@@ -44,8 +45,8 @@ export type DistillTier = "none" | "light" | "standard" | "deep";
|
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
45
46
|
|
|
46
47
|
interface ActivityStats {
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
/** Per-scope write counts since that scope's last dream/distill reset. */
|
|
49
|
+
scopes: Record<string, number>;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function resolveConfig(
|
|
@@ -56,12 +57,24 @@ function resolveConfig(
|
|
|
56
57
|
|
|
57
58
|
function readStats(statsPath: string): ActivityStats {
|
|
58
59
|
if (!existsSync(statsPath)) {
|
|
59
|
-
return {
|
|
60
|
+
return { scopes: {} };
|
|
60
61
|
}
|
|
61
62
|
try {
|
|
62
|
-
|
|
63
|
+
const parsed: unknown = JSON.parse(readFileSync(statsPath, "utf-8"));
|
|
64
|
+
// Robust against the legacy global format ({writesSinceLastDistill}) — no migration,
|
|
65
|
+
// just treat anything without a `scopes` map as empty (counts restart per scope).
|
|
66
|
+
if (
|
|
67
|
+
parsed !== null &&
|
|
68
|
+
typeof parsed === "object" &&
|
|
69
|
+
"scopes" in parsed &&
|
|
70
|
+
typeof (parsed as { scopes?: unknown }).scopes === "object" &&
|
|
71
|
+
(parsed as { scopes?: unknown }).scopes !== null
|
|
72
|
+
) {
|
|
73
|
+
return { scopes: (parsed as { scopes: Record<string, number> }).scopes };
|
|
74
|
+
}
|
|
75
|
+
return { scopes: {} };
|
|
63
76
|
} catch {
|
|
64
|
-
return {
|
|
77
|
+
return { scopes: {} };
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
|
|
@@ -74,34 +87,49 @@ function writeStats(statsPath: string, stats: ActivityStats): void {
|
|
|
74
87
|
// Public API
|
|
75
88
|
// ---------------------------------------------------------------------------
|
|
76
89
|
|
|
77
|
-
/** Increment write counter by n (default 1). Returns new count. */
|
|
90
|
+
/** Increment a scope's write counter by n (default 1). Returns the scope's new count. */
|
|
78
91
|
export function incrementWriteCount(
|
|
92
|
+
scope: string,
|
|
79
93
|
n = 1,
|
|
80
94
|
cfg?: Partial<ActivityCounterConfig>,
|
|
81
95
|
): number {
|
|
82
96
|
const { statsPath } = resolveConfig(cfg);
|
|
83
97
|
const stats = readStats(statsPath);
|
|
84
|
-
stats.
|
|
98
|
+
stats.scopes[scope] = (stats.scopes[scope] ?? 0) + n;
|
|
85
99
|
writeStats(statsPath, stats);
|
|
86
|
-
return stats.
|
|
100
|
+
return stats.scopes[scope];
|
|
87
101
|
}
|
|
88
102
|
|
|
89
|
-
/** Read current write count without modifying. */
|
|
90
|
-
export function getWriteCount(cfg?: Partial<ActivityCounterConfig>): number {
|
|
103
|
+
/** Read a scope's current write count without modifying (0 if never written). */
|
|
104
|
+
export function getWriteCount(scope: string, cfg?: Partial<ActivityCounterConfig>): number {
|
|
91
105
|
const { statsPath } = resolveConfig(cfg);
|
|
92
|
-
return readStats(statsPath).
|
|
106
|
+
return readStats(statsPath).scopes[scope] ?? 0;
|
|
93
107
|
}
|
|
94
108
|
|
|
95
|
-
/** Reset counter after successful distill. */
|
|
96
|
-
export function resetWriteCount(cfg?: Partial<ActivityCounterConfig>): void {
|
|
109
|
+
/** Reset one scope's counter after its successful dream/distill (leaves other scopes untouched). */
|
|
110
|
+
export function resetWriteCount(scope: string, cfg?: Partial<ActivityCounterConfig>): void {
|
|
97
111
|
const { statsPath } = resolveConfig(cfg);
|
|
98
|
-
|
|
112
|
+
const stats = readStats(statsPath);
|
|
113
|
+
delete stats.scopes[scope];
|
|
114
|
+
writeStats(statsPath, stats);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Scopes whose write count is at or above `threshold` — the dream scheduler's work list. */
|
|
118
|
+
export function listScopesAboveThreshold(
|
|
119
|
+
threshold: number,
|
|
120
|
+
cfg?: Partial<ActivityCounterConfig>,
|
|
121
|
+
): string[] {
|
|
122
|
+
const { statsPath } = resolveConfig(cfg);
|
|
123
|
+
const stats = readStats(statsPath);
|
|
124
|
+
return Object.entries(stats.scopes)
|
|
125
|
+
.filter(([, count]) => count >= threshold)
|
|
126
|
+
.map(([scope]) => scope);
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
/** Determine which distill tier
|
|
102
|
-
export function getDistillTier(cfg?: Partial<ActivityCounterConfig>): DistillTier {
|
|
129
|
+
/** Determine which distill tier a scope's write count warrants. */
|
|
130
|
+
export function getDistillTier(scope: string, cfg?: Partial<ActivityCounterConfig>): DistillTier {
|
|
103
131
|
const config = resolveConfig(cfg);
|
|
104
|
-
const count = getWriteCount(cfg);
|
|
132
|
+
const count = getWriteCount(scope, cfg);
|
|
105
133
|
if (count >= config.deepThreshold) return "deep";
|
|
106
134
|
if (count >= config.standardThreshold) return "standard";
|
|
107
135
|
if (count >= config.lightThreshold) return "light";
|
|
@@ -55,8 +55,19 @@ export function shouldSkipRetrieval(query: string, minLength?: number): boolean
|
|
|
55
55
|
|
|
56
56
|
const effectiveMinLength = minLength ?? (hasCJK ? 2 : 15);
|
|
57
57
|
|
|
58
|
+
// 【本地分叉点 · 2026-08-08 · 勿在同步上游时覆盖回去】
|
|
59
|
+
// 单 token(不含空格)= 实体查找意图,与问号一样豁免长度闸。
|
|
60
|
+
// 上游的 ASCII 阈值 15 是按「英文自然语句」定的(挡 "hello there" 这类闲聊),
|
|
61
|
+
// 但项目名/工具名几乎都短于 15:cobbler=7 / slidesmith=10 / hippo-wiki=10 / recallnest=10,
|
|
62
|
+
// 于是「甩一个名字过去」这种最自然的查法一律返回空——库里明明有。
|
|
63
|
+
// 红绿实测:`cobbler` → No results;`cobbler?` → 100% 命中 12 条,只差一个问号豁免。
|
|
64
|
+
// 中文不受影响(阈值 2),所以该缺陷只在英文实体名上暴露,一直没被发现。
|
|
65
|
+
// 安全性:本条排在 SKIP_PATTERNS 之后,hi / ok / ping / test / ls / git 等单词仍被挡住。
|
|
66
|
+
const isSingleToken = !/\s/.test(trimmed);
|
|
67
|
+
|
|
58
68
|
// Short non-question messages are skipped; questions (? ?) are always worth checking
|
|
59
69
|
if (trimmed.length < effectiveMinLength &&
|
|
70
|
+
!isSingleToken &&
|
|
60
71
|
!trimmed.includes('?') && !trimmed.includes('?')) {
|
|
61
72
|
return true;
|
|
62
73
|
}
|
package/src/aliases.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alias dictionary for query rewriting.
|
|
3
|
+
*
|
|
4
|
+
* Manually maintained — do NOT use LLM expansion (risks precision pollution).
|
|
5
|
+
* Each rule appends entity tokens to the query when the pattern matches,
|
|
6
|
+
* giving BM25/vector search more anchor points without losing the original wording.
|
|
7
|
+
*
|
|
8
|
+
* Why expand (append) rather than replace:
|
|
9
|
+
* - Preserves the user's exact wording for vector embedding fidelity.
|
|
10
|
+
* - BM25 gets extra keyword anchors to find the right entity.
|
|
11
|
+
* - Reversible via inspecting the expanded vs. original query.
|
|
12
|
+
*
|
|
13
|
+
* Add new rules sparingly. Each entry should solve a real failure
|
|
14
|
+
* documented in FAILURES.md, not a hypothetical.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface AliasRule {
|
|
18
|
+
pattern: RegExp;
|
|
19
|
+
expansion: string;
|
|
20
|
+
comment?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ALIAS_RULES: AliasRule[] = [
|
|
24
|
+
// Entity aliases — colloquial → canonical name
|
|
25
|
+
{
|
|
26
|
+
pattern: /(?:我的)?记忆项目/,
|
|
27
|
+
expansion: "recallnest",
|
|
28
|
+
comment: "FAILURES 2026-04-25: '我的记忆项目' → recallnest",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
pattern: /我的桥/,
|
|
32
|
+
expansion: "telegram-ai-bridge",
|
|
33
|
+
comment: "Alice 用'我的桥'指 telegram-ai-bridge",
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Operations vocabulary — operator wording → canonical entities
|
|
37
|
+
{
|
|
38
|
+
pattern: /aws\s*(?:bot|ssh|podcast)/i,
|
|
39
|
+
expansion: "aws-bot hermes-aws aws-podcast-daily",
|
|
40
|
+
comment: "FAILURES 2026-03-06: aws bot/ssh/podcast → aws-bot hermes-aws entities",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
pattern: /bot\s*(?:挂|崩|不能用|跑不起|失联|down)/i,
|
|
44
|
+
expansion: "bot-doctor hermes docker launchd 排查 重启",
|
|
45
|
+
comment: "Operator bot-crash debugging vocabulary",
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Cross-window / continuity hints — these strengthen recall of session state.
|
|
49
|
+
// Pattern 必须明确出现"隔壁/另一窗口/session"才扩;纯"之前说过"太宽会污染
|
|
50
|
+
// 普通话题(如 fuzzy_ai_feelings 的 "之前聊过 AI 感受" 被错误归类成 cross-window)。
|
|
51
|
+
{
|
|
52
|
+
pattern: /(?:刚刚|刚才|之前)(?:.{0,10})(?:隔壁|另一个|其他)(?:.{0,10})(?:窗口|session)/,
|
|
53
|
+
expansion: "checkpoint session resume_context 跨窗口",
|
|
54
|
+
comment: "FAILURES 2026-05-10: cross-window real-time sync hint",
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Tool provenance — common ambiguous tool references
|
|
58
|
+
{
|
|
59
|
+
pattern: /taobao.{0,5}(?:mcp|官方)/i,
|
|
60
|
+
expansion: "MCP server provenance source",
|
|
61
|
+
comment: "FAILURES 2026-05-01: taobao MCP official-vs-third-party",
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// RecallNest self-introspection — capability queries
|
|
65
|
+
{
|
|
66
|
+
pattern: /recallnest.{0,15}(?:有哪些|工具|能做|功能|tool)/i,
|
|
67
|
+
expansion: "search_memory store_memory resume_context checkpoint_session MCP",
|
|
68
|
+
comment: "FAILURES 2026-04-20: RN self-introspection capability digest",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: /recallnest.{0,10}(?:ingest|跑了|跑过|状态|telemetry)/i,
|
|
72
|
+
expansion: "import_conversations weekly-distill ingest telemetry 成功 失败",
|
|
73
|
+
comment: "FAILURES 2026-04-20: RN ingest telemetry queries",
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Personal-life recall — subscription / friends provenance
|
|
77
|
+
{
|
|
78
|
+
pattern: /(?:chatgpt|订阅).{0,10}(?:谁|怎么|哪里|帮|顶)/i,
|
|
79
|
+
expansion: "ChatGPT Plus 群友 加拿大 堂姐 澳洲 代订 aliceljyalice",
|
|
80
|
+
comment: "FAILURES 2026-05-12: ChatGPT subscription provenance chain",
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/** 超过此长度的 query 不做别名扩展——长 query 是粘贴文档场景,扩展只添噪声 (P1-B)。 */
|
|
85
|
+
export const ALIAS_QUERY_MAX_LENGTH = 100;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Expand a query by appending alias tokens for matching patterns.
|
|
89
|
+
* Returns the original query unchanged if no rules match.
|
|
90
|
+
*
|
|
91
|
+
* 注意分工:本模块只承载**静态 builtin 规则**,作用于 retrieve() 全通道
|
|
92
|
+
* (vector + BM25)。**用户级** alias(data/alias-map.json)由
|
|
93
|
+
* query-expander.ts 加载,只作用于 BM25 通道(hybridRetrieval 内部)——
|
|
94
|
+
* 用户别名不进 vector embedding,避免随手加的别名造成语义向量漂移。
|
|
95
|
+
*
|
|
96
|
+
* Example:
|
|
97
|
+
* expandQueryWithAliases("帮我看我的记忆项目")
|
|
98
|
+
* → "帮我看我的记忆项目 recallnest"
|
|
99
|
+
* expandQueryWithAliases("aws ssh 怎么连")
|
|
100
|
+
* → "aws ssh 怎么连 aws-bot hermes-aws aws-podcast-daily"
|
|
101
|
+
*/
|
|
102
|
+
export function expandQueryWithAliases(query: string): string {
|
|
103
|
+
if (!query || typeof query !== "string") return query;
|
|
104
|
+
if (query.length > ALIAS_QUERY_MAX_LENGTH) return query;
|
|
105
|
+
|
|
106
|
+
const additions: string[] = [];
|
|
107
|
+
const seen = new Set<string>();
|
|
108
|
+
|
|
109
|
+
for (const rule of ALIAS_RULES) {
|
|
110
|
+
if (rule.pattern.test(query)) {
|
|
111
|
+
for (const token of rule.expansion.split(/\s+/)) {
|
|
112
|
+
if (token && !seen.has(token) && !query.includes(token)) {
|
|
113
|
+
additions.push(token);
|
|
114
|
+
seen.add(token);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return additions.length > 0 ? `${query} ${additions.join(" ")}` : query;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Inspection helper — return which builtin rules matched a query, for debugging.
|
|
125
|
+
* 用户级规则(BM25-only)的检视走 query-expander.ts 的 explainUserAliases。
|
|
126
|
+
*/
|
|
127
|
+
export function explainAliasExpansion(query: string): Array<{ pattern: string; expansion: string; comment?: string }> {
|
|
128
|
+
return ALIAS_RULES.filter((rule) => rule.pattern.test(query)).map((rule) => ({
|
|
129
|
+
pattern: rule.pattern.source,
|
|
130
|
+
expansion: rule.expansion,
|
|
131
|
+
comment: rule.comment,
|
|
132
|
+
}));
|
|
133
|
+
}
|
package/src/api-server.ts
CHANGED
|
@@ -17,19 +17,25 @@ import { ConflictStatusSchema } from "./conflict-schema.js";
|
|
|
17
17
|
import { resolveConflictCandidate } from "./conflict-engine.js";
|
|
18
18
|
import { escalateConflicts } from "./conflict-escalation.js";
|
|
19
19
|
import { ConflictCandidateStore } from "./conflict-store.js";
|
|
20
|
+
import { createAuditLogger } from "./audit-log.js";
|
|
20
21
|
import { parseConflictAttention, summarizeConflictLifecycle } from "./conflict-lifecycle.js";
|
|
21
22
|
import { buildConflictAuditSummary, clusterConflicts, summarizeConflictAdvice } from "./conflict-advisor.js";
|
|
22
23
|
import { buildWorkflowEvidence, buildWorkflowObservationRecord, inspectWorkflowDashboard, inspectWorkflowHealth } from "./workflow-observation-engine.js";
|
|
24
|
+
import { recordSkillOutcome } from "./skill-engine.js";
|
|
23
25
|
import { WorkflowObservationStore } from "./workflow-observation-store.js";
|
|
24
26
|
import { buildManagedCheckpointObservation, buildManagedResumeObservation } from "./workflow-observation-managed.js";
|
|
25
27
|
import { runAutoRecall } from "./auto-recall.js";
|
|
26
28
|
import { buildRetrievalContext, resolveScopeSelection } from "./scope-policy.js";
|
|
27
29
|
import { runMemoryLint } from "./memory-lint.js";
|
|
30
|
+
import { enforceLocalHttpRequestPolicy, LOCAL_HTTP_HOSTNAME } from "./http-request-policy.js";
|
|
31
|
+
import * as envConfig from "./env-config.js";
|
|
32
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
28
33
|
|
|
29
34
|
const config = (loadDotEnv(), loadConfig());
|
|
30
35
|
const getComponents = createComponentResolver(config);
|
|
31
36
|
const checkpointStore = new SessionCheckpointStore();
|
|
32
37
|
const conflictStore = new ConflictCandidateStore();
|
|
38
|
+
const auditLogger = createAuditLogger();
|
|
33
39
|
const workflowObservationStore = new WorkflowObservationStore();
|
|
34
40
|
|
|
35
41
|
// ============================================================================
|
|
@@ -235,7 +241,7 @@ async function handleStore(request: Request): Promise<Response> {
|
|
|
235
241
|
const body = await readJson(request);
|
|
236
242
|
try {
|
|
237
243
|
const { store, embedder } = getComponents();
|
|
238
|
-
const stored = await persistMemory({ store, embedder, conflictStore }, body);
|
|
244
|
+
const stored = await persistMemory({ store, embedder, conflictStore, auditLogger }, body);
|
|
239
245
|
|
|
240
246
|
return jsonResponse({
|
|
241
247
|
id: stored.id,
|
|
@@ -284,7 +290,7 @@ async function handlePattern(request: Request): Promise<Response> {
|
|
|
284
290
|
const body = await readJson(request);
|
|
285
291
|
try {
|
|
286
292
|
const { store, embedder } = getComponents();
|
|
287
|
-
const stored = await persistWorkflowPattern({ store, embedder, conflictStore }, body);
|
|
293
|
+
const stored = await persistWorkflowPattern({ store, embedder, conflictStore, auditLogger }, body);
|
|
288
294
|
return jsonResponse({
|
|
289
295
|
id: stored.id,
|
|
290
296
|
stored: stored.disposition !== "conflict",
|
|
@@ -308,7 +314,7 @@ async function handleCase(request: Request): Promise<Response> {
|
|
|
308
314
|
const body = await readJson(request);
|
|
309
315
|
try {
|
|
310
316
|
const { store, embedder } = getComponents();
|
|
311
|
-
const stored = await persistCaseMemory({ store, embedder, conflictStore }, body);
|
|
317
|
+
const stored = await persistCaseMemory({ store, embedder, conflictStore, auditLogger }, body);
|
|
312
318
|
return jsonResponse({
|
|
313
319
|
id: stored.id,
|
|
314
320
|
stored: stored.disposition !== "conflict",
|
|
@@ -332,7 +338,7 @@ async function handlePromote(request: Request): Promise<Response> {
|
|
|
332
338
|
const body = await readJson(request);
|
|
333
339
|
try {
|
|
334
340
|
const { store, embedder } = getComponents();
|
|
335
|
-
const stored = await promoteMemory({ store, embedder, conflictStore }, body);
|
|
341
|
+
const stored = await promoteMemory({ store, embedder, conflictStore, auditLogger }, body);
|
|
336
342
|
return jsonResponse({
|
|
337
343
|
id: stored.id,
|
|
338
344
|
stored: stored.disposition !== "conflict",
|
|
@@ -455,13 +461,29 @@ async function handleCheckpoint(request: Request): Promise<Response> {
|
|
|
455
461
|
}
|
|
456
462
|
}
|
|
457
463
|
|
|
458
|
-
/** POST /v1/workflow-observe — persist a workflow observation outside durable memory */
|
|
464
|
+
/** POST /v1/workflow-observe — persist a workflow observation outside durable memory; bumps skill counters when `skillId` is present */
|
|
459
465
|
async function handleWorkflowObserve(request: Request): Promise<Response> {
|
|
460
466
|
const body = await readJson(request);
|
|
461
467
|
try {
|
|
462
468
|
const record = buildWorkflowObservationRecord(body);
|
|
463
469
|
const stored = await workflowObservationStore.save(record);
|
|
464
|
-
|
|
470
|
+
|
|
471
|
+
let skillOutcome: unknown = undefined;
|
|
472
|
+
if (record.skillId) {
|
|
473
|
+
try {
|
|
474
|
+
const { store } = getComponents();
|
|
475
|
+
const result = await recordSkillOutcome(store, record.skillId, record.outcome);
|
|
476
|
+
skillOutcome = result;
|
|
477
|
+
if (!result.updated) {
|
|
478
|
+
console.warn(`[api /v1/workflow-observe] skillId=${record.skillId} update skipped: ${result.reason}`);
|
|
479
|
+
}
|
|
480
|
+
} catch (err) {
|
|
481
|
+
console.warn(`[api /v1/workflow-observe] recordSkillOutcome threw for skillId=${record.skillId}:`, err);
|
|
482
|
+
skillOutcome = { updated: false, reason: "exception" };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return jsonResponse({ observation: stored, ...(skillOutcome !== undefined ? { skillOutcome } : {}) }, 201);
|
|
465
487
|
} catch (err) {
|
|
466
488
|
const message = err instanceof Error ? err.message : String(err);
|
|
467
489
|
return errorResponse(400, message);
|
|
@@ -605,7 +627,7 @@ async function handleHealth(): Promise<Response> {
|
|
|
605
627
|
const stats = await store.stats();
|
|
606
628
|
return jsonResponse({
|
|
607
629
|
status: "ok",
|
|
608
|
-
version:
|
|
630
|
+
version: PACKAGE_VERSION,
|
|
609
631
|
totalMemories: stats.totalCount,
|
|
610
632
|
timestamp: new Date().toISOString(),
|
|
611
633
|
});
|
|
@@ -625,12 +647,17 @@ async function handleHealth(): Promise<Response> {
|
|
|
625
647
|
// Server
|
|
626
648
|
// ============================================================================
|
|
627
649
|
|
|
628
|
-
const port = clampInt(
|
|
650
|
+
const port = clampInt(envConfig.apiPortRaw(), 4318, 1, 65535);
|
|
629
651
|
|
|
630
652
|
const server = Bun.serve({
|
|
631
653
|
port,
|
|
632
|
-
hostname:
|
|
654
|
+
hostname: LOCAL_HTTP_HOSTNAME,
|
|
633
655
|
async fetch(request) {
|
|
656
|
+
const policy = await enforceLocalHttpRequestPolicy(request);
|
|
657
|
+
if (!policy.allowed) {
|
|
658
|
+
return errorResponse(policy.status, policy.message);
|
|
659
|
+
}
|
|
660
|
+
|
|
634
661
|
const url = new URL(request.url);
|
|
635
662
|
const { pathname } = url;
|
|
636
663
|
const method = request.method;
|
package/src/audit-log.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
|
+
import * as envConfig from "./env-config.js";
|
|
10
11
|
|
|
11
12
|
export type AuditOperation =
|
|
12
13
|
| "store"
|
|
@@ -17,7 +18,8 @@ export type AuditOperation =
|
|
|
17
18
|
| "supersede"
|
|
18
19
|
| "consolidate"
|
|
19
20
|
| "forget"
|
|
20
|
-
| "cascade_forget"
|
|
21
|
+
| "cascade_forget"
|
|
22
|
+
| "reject";
|
|
21
23
|
|
|
22
24
|
export interface AuditEntry {
|
|
23
25
|
timestamp: string; // ISO 8601
|
|
@@ -59,7 +61,7 @@ function parseLines(raw: string): AuditEntry[] {
|
|
|
59
61
|
export function createAuditLogger(logPath?: string): AuditLogger {
|
|
60
62
|
const resolvedPath =
|
|
61
63
|
logPath ??
|
|
62
|
-
join(
|
|
64
|
+
join(envConfig.dataDir(), "audit.jsonl");
|
|
63
65
|
|
|
64
66
|
// Ensure directory exists (silent on failure)
|
|
65
67
|
try {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* reason is operational hygiene.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type {
|
|
13
|
+
import type { MemoryEntry } from "./store.js";
|
|
14
|
+
import type { MemoryStorePort } from "./memory-store-port.js";
|
|
14
15
|
import { ConsolidationEngine, type ConsolidationConfig, type ConsolidationResult, DEFAULT_CONSOLIDATION_CONFIG } from "./consolidation-engine.js";
|
|
15
16
|
import type { LLMClient } from "./llm-client.js";
|
|
16
17
|
import { isLLMConsolidationEnabled, evaluateCluster, executeMergeDecisions } from "./llm-consolidation.js";
|
|
@@ -54,16 +55,20 @@ export interface AutoConsolidationResult {
|
|
|
54
55
|
/**
|
|
55
56
|
* Check conditions and run consolidation if thresholds are met.
|
|
56
57
|
* Returns immediately if conditions not met (no-op).
|
|
58
|
+
*
|
|
59
|
+
* @deprecated Prefer dream-pipeline.ts for full maintenance. This module is
|
|
60
|
+
* kept for compatibility with callers that only need count/time-gated
|
|
61
|
+
* consolidation.
|
|
57
62
|
*/
|
|
58
63
|
export async function maybeConsolidate(
|
|
59
|
-
store:
|
|
64
|
+
store: MemoryStorePort,
|
|
60
65
|
scope: string,
|
|
61
66
|
config: AutoConsolidationConfig = DEFAULT_AUTO_CONSOLIDATION_CONFIG,
|
|
62
67
|
/** Tier 3.7: Optional LLM client for semantic consolidation decisions */
|
|
63
68
|
llm?: LLMClient | null,
|
|
64
69
|
): Promise<AutoConsolidationResult> {
|
|
65
70
|
const stats = await store.stats([scope]);
|
|
66
|
-
const currentCount = stats.
|
|
71
|
+
const currentCount = stats.totalCount ?? 0;
|
|
67
72
|
|
|
68
73
|
// Condition 1: enough new memories since last run
|
|
69
74
|
const newSinceLastRun = currentCount - lastRunMemoryCount;
|