memwarden 0.0.1
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/LICENSE +202 -0
- package/README.md +402 -0
- package/dist/bundle/bundle.d.ts +28 -0
- package/dist/bundle/bundle.js +85 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +593 -0
- package/dist/cli/connect.d.ts +63 -0
- package/dist/cli/connect.js +121 -0
- package/dist/cli/hook.d.ts +24 -0
- package/dist/cli/hook.js +186 -0
- package/dist/cli/tools.d.ts +47 -0
- package/dist/cli/tools.js +246 -0
- package/dist/daemon/ensure.d.ts +12 -0
- package/dist/daemon/ensure.js +54 -0
- package/dist/daemon/service.d.ts +15 -0
- package/dist/daemon/service.js +210 -0
- package/dist/embedding/index.d.ts +10 -0
- package/dist/embedding/index.js +33 -0
- package/dist/embedding/local-embedding.d.ts +14 -0
- package/dist/embedding/local-embedding.js +80 -0
- package/dist/functions/access-tracker.d.ts +13 -0
- package/dist/functions/access-tracker.js +92 -0
- package/dist/functions/audit.d.ts +46 -0
- package/dist/functions/audit.js +0 -0
- package/dist/functions/cjk-segmenter.d.ts +6 -0
- package/dist/functions/cjk-segmenter.js +120 -0
- package/dist/functions/compress-synthetic.d.ts +2 -0
- package/dist/functions/compress-synthetic.js +104 -0
- package/dist/functions/config.d.ts +68 -0
- package/dist/functions/config.js +231 -0
- package/dist/functions/conflicts.d.ts +19 -0
- package/dist/functions/conflicts.js +328 -0
- package/dist/functions/context.d.ts +3 -0
- package/dist/functions/context.js +155 -0
- package/dist/functions/dedup.d.ts +11 -0
- package/dist/functions/dedup.js +51 -0
- package/dist/functions/dejafix.d.ts +96 -0
- package/dist/functions/dejafix.js +356 -0
- package/dist/functions/doctor.d.ts +29 -0
- package/dist/functions/doctor.js +137 -0
- package/dist/functions/forget.d.ts +3 -0
- package/dist/functions/forget.js +87 -0
- package/dist/functions/hybrid-search.d.ts +17 -0
- package/dist/functions/hybrid-search.js +205 -0
- package/dist/functions/index.d.ts +32 -0
- package/dist/functions/index.js +44 -0
- package/dist/functions/keyed-mutex.d.ts +1 -0
- package/dist/functions/keyed-mutex.js +21 -0
- package/dist/functions/logger.d.ts +6 -0
- package/dist/functions/logger.js +37 -0
- package/dist/functions/memory-utils.d.ts +2 -0
- package/dist/functions/memory-utils.js +29 -0
- package/dist/functions/observe.d.ts +5 -0
- package/dist/functions/observe.js +326 -0
- package/dist/functions/paths.d.ts +1 -0
- package/dist/functions/paths.js +38 -0
- package/dist/functions/privacy.d.ts +1 -0
- package/dist/functions/privacy.js +30 -0
- package/dist/functions/provenance.d.ts +9 -0
- package/dist/functions/provenance.js +57 -0
- package/dist/functions/quantized-vector-index.d.ts +60 -0
- package/dist/functions/quantized-vector-index.js +275 -0
- package/dist/functions/receipt.d.ts +31 -0
- package/dist/functions/receipt.js +95 -0
- package/dist/functions/search-index.d.ts +27 -0
- package/dist/functions/search-index.js +217 -0
- package/dist/functions/search.d.ts +25 -0
- package/dist/functions/search.js +523 -0
- package/dist/functions/stemmer.d.ts +1 -0
- package/dist/functions/stemmer.js +110 -0
- package/dist/functions/synonyms.d.ts +1 -0
- package/dist/functions/synonyms.js +69 -0
- package/dist/functions/turboquant.d.ts +53 -0
- package/dist/functions/turboquant.js +278 -0
- package/dist/functions/types.d.ts +217 -0
- package/dist/functions/types.js +8 -0
- package/dist/functions/vector-index.d.ts +25 -0
- package/dist/functions/vector-index.js +125 -0
- package/dist/functions/vector-persistence.d.ts +14 -0
- package/dist/functions/vector-persistence.js +75 -0
- package/dist/functions/verify.d.ts +13 -0
- package/dist/functions/verify.js +104 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +219 -0
- package/dist/kernel/http.d.ts +24 -0
- package/dist/kernel/http.js +261 -0
- package/dist/kernel/index.d.ts +19 -0
- package/dist/kernel/index.js +21 -0
- package/dist/kernel/kernel.d.ts +80 -0
- package/dist/kernel/kernel.js +297 -0
- package/dist/kernel/pubsub.d.ts +21 -0
- package/dist/kernel/pubsub.js +38 -0
- package/dist/kernel/types.d.ts +139 -0
- package/dist/kernel/types.js +20 -0
- package/dist/mcp/bin.d.ts +2 -0
- package/dist/mcp/bin.js +27 -0
- package/dist/mcp/server.d.ts +34 -0
- package/dist/mcp/server.js +377 -0
- package/dist/observability/metrics.d.ts +26 -0
- package/dist/observability/metrics.js +104 -0
- package/dist/proxy/server.d.ts +30 -0
- package/dist/proxy/server.js +331 -0
- package/dist/state/kv.d.ts +41 -0
- package/dist/state/kv.js +50 -0
- package/dist/state/oplog.d.ts +25 -0
- package/dist/state/oplog.js +57 -0
- package/dist/state/schema.d.ts +60 -0
- package/dist/state/schema.js +88 -0
- package/dist/state/store-libsql.d.ts +46 -0
- package/dist/state/store-libsql.js +263 -0
- package/dist/state/store-memory.d.ts +23 -0
- package/dist/state/store-memory.js +121 -0
- package/dist/state/store.d.ts +87 -0
- package/dist/state/store.js +58 -0
- package/dist/triggers/api.d.ts +14 -0
- package/dist/triggers/api.js +510 -0
- package/dist/triggers/auth.d.ts +1 -0
- package/dist/triggers/auth.js +13 -0
- package/package.json +58 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Reciprocal Rank Fusion (RRF) hybrid retrieval. Ported from the original
|
|
3
|
+
// src/state/hybrid-search.ts, preserving the load-bearing constants and the
|
|
4
|
+
// exact fusion math:
|
|
5
|
+
//
|
|
6
|
+
// RRF_K = 60
|
|
7
|
+
// default weights: bm25Weight = 0.4, vectorWeight = 0.6, graphWeight = 0.3
|
|
8
|
+
// combinedScore = Σ effectiveW_stream * (1 / (RRF_K + rank_stream))
|
|
9
|
+
// missing-stream rank = Infinity (so its term is 1/(60+Inf) = 0)
|
|
10
|
+
// weights are RENORMALIZED over only the streams that returned hits.
|
|
11
|
+
//
|
|
12
|
+
// PHASE-0 SCOPE: the vector and graph streams are stubbed empty (no
|
|
13
|
+
// embedding provider, no knowledge graph wired yet). Because the
|
|
14
|
+
// renormalization divides by the sum of only the streams that returned
|
|
15
|
+
// hits, with both vector and graph empty the BM25 stream is renormalized
|
|
16
|
+
// to weight 1.0 — identical to the original behavior when those streams
|
|
17
|
+
// return nothing. When the vector provider lands in Phase 0b, pass a real
|
|
18
|
+
// VectorIndex + EmbeddingProvider into the constructor and the second
|
|
19
|
+
// stream lights up with no fusion-math change. The graph stream stays a
|
|
20
|
+
// no-op until the graph layer is ported.
|
|
21
|
+
import { memoryToObservation } from "./memory-utils.js";
|
|
22
|
+
import { KV } from "../state/schema.js";
|
|
23
|
+
const RRF_K = 60;
|
|
24
|
+
export class HybridSearch {
|
|
25
|
+
bm25;
|
|
26
|
+
vector;
|
|
27
|
+
embeddingProvider;
|
|
28
|
+
kv;
|
|
29
|
+
bm25Weight;
|
|
30
|
+
vectorWeight;
|
|
31
|
+
graphWeight;
|
|
32
|
+
constructor(bm25, vector, embeddingProvider, kv, bm25Weight = 0.4, vectorWeight = 0.6, graphWeight = 0.3) {
|
|
33
|
+
this.bm25 = bm25;
|
|
34
|
+
this.vector = vector;
|
|
35
|
+
this.embeddingProvider = embeddingProvider;
|
|
36
|
+
this.kv = kv;
|
|
37
|
+
this.bm25Weight = bm25Weight;
|
|
38
|
+
this.vectorWeight = vectorWeight;
|
|
39
|
+
this.graphWeight = graphWeight;
|
|
40
|
+
}
|
|
41
|
+
async search(query, limit = 20) {
|
|
42
|
+
return this.tripleStreamSearch(query, limit);
|
|
43
|
+
}
|
|
44
|
+
async tripleStreamSearch(query, limit) {
|
|
45
|
+
const bm25Results = this.bm25.search(query, limit * 2);
|
|
46
|
+
// Vector stream. Stubbed empty in Phase 0 (no provider / empty index);
|
|
47
|
+
// the try/catch + size-guard mirror the original implementation so lighting up the
|
|
48
|
+
// provider later needs no change here.
|
|
49
|
+
let vectorResults = [];
|
|
50
|
+
if (this.vector && this.embeddingProvider && this.vector.size > 0) {
|
|
51
|
+
try {
|
|
52
|
+
const queryEmbedding = await this.embeddingProvider.embed(query);
|
|
53
|
+
vectorResults = this.vector.search(queryEmbedding, limit * 2);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// fall through to BM25-only
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Graph stream. Not ported in Phase 0; always empty.
|
|
60
|
+
const graphResults = [];
|
|
61
|
+
const scores = new Map();
|
|
62
|
+
bm25Results.forEach((r, i) => {
|
|
63
|
+
scores.set(r.obsId, {
|
|
64
|
+
bm25Rank: i + 1,
|
|
65
|
+
vectorRank: Infinity,
|
|
66
|
+
graphRank: Infinity,
|
|
67
|
+
sessionId: r.sessionId,
|
|
68
|
+
bm25Score: r.score,
|
|
69
|
+
vectorScore: 0,
|
|
70
|
+
graphScore: 0,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
vectorResults.forEach((r, i) => {
|
|
74
|
+
const existing = scores.get(r.obsId);
|
|
75
|
+
if (existing) {
|
|
76
|
+
existing.vectorRank = i + 1;
|
|
77
|
+
existing.vectorScore = r.score;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
scores.set(r.obsId, {
|
|
81
|
+
bm25Rank: Infinity,
|
|
82
|
+
vectorRank: i + 1,
|
|
83
|
+
graphRank: Infinity,
|
|
84
|
+
sessionId: r.sessionId,
|
|
85
|
+
bm25Score: 0,
|
|
86
|
+
vectorScore: r.score,
|
|
87
|
+
graphScore: 0,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
graphResults.forEach((r, i) => {
|
|
92
|
+
const existing = scores.get(r.obsId);
|
|
93
|
+
if (existing) {
|
|
94
|
+
existing.graphRank = Math.min(existing.graphRank, i + 1);
|
|
95
|
+
existing.graphScore = Math.max(existing.graphScore, r.score);
|
|
96
|
+
if (r.graphContext && !existing.graphContext) {
|
|
97
|
+
existing.graphContext = r.graphContext;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
scores.set(r.obsId, {
|
|
102
|
+
bm25Rank: Infinity,
|
|
103
|
+
vectorRank: Infinity,
|
|
104
|
+
graphRank: i + 1,
|
|
105
|
+
sessionId: r.sessionId,
|
|
106
|
+
bm25Score: 0,
|
|
107
|
+
vectorScore: 0,
|
|
108
|
+
graphScore: r.score,
|
|
109
|
+
...(r.graphContext !== undefined
|
|
110
|
+
? { graphContext: r.graphContext }
|
|
111
|
+
: {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const hasVector = vectorResults.length > 0;
|
|
116
|
+
const hasGraph = graphResults.length > 0;
|
|
117
|
+
let effectiveBm25W = this.bm25Weight;
|
|
118
|
+
let effectiveVectorW = hasVector ? this.vectorWeight : 0;
|
|
119
|
+
let effectiveGraphW = hasGraph ? this.graphWeight : 0;
|
|
120
|
+
// Renormalize over only the streams that returned hits. With both the
|
|
121
|
+
// vector and graph streams empty (Phase 0) this leaves BM25 at 1.0.
|
|
122
|
+
const totalW = effectiveBm25W + effectiveVectorW + effectiveGraphW;
|
|
123
|
+
if (totalW > 0) {
|
|
124
|
+
effectiveBm25W /= totalW;
|
|
125
|
+
effectiveVectorW /= totalW;
|
|
126
|
+
effectiveGraphW /= totalW;
|
|
127
|
+
}
|
|
128
|
+
const combined = Array.from(scores.entries()).map(([obsId, s]) => ({
|
|
129
|
+
obsId,
|
|
130
|
+
sessionId: s.sessionId,
|
|
131
|
+
bm25Score: s.bm25Score,
|
|
132
|
+
vectorScore: s.vectorScore,
|
|
133
|
+
graphScore: s.graphScore,
|
|
134
|
+
graphContext: s.graphContext,
|
|
135
|
+
combinedScore: effectiveBm25W * (1 / (RRF_K + s.bm25Rank)) +
|
|
136
|
+
effectiveVectorW * (1 / (RRF_K + s.vectorRank)) +
|
|
137
|
+
effectiveGraphW * (1 / (RRF_K + s.graphRank)),
|
|
138
|
+
}));
|
|
139
|
+
combined.sort((a, b) => b.combinedScore - a.combinedScore);
|
|
140
|
+
const retrievalDepth = Math.max(limit, 20);
|
|
141
|
+
const diversified = this.diversifyBySession(combined, retrievalDepth);
|
|
142
|
+
const enriched = await this.enrichResults(diversified, retrievalDepth);
|
|
143
|
+
return enriched.slice(0, limit);
|
|
144
|
+
}
|
|
145
|
+
diversifyBySession(results, limit, maxPerSession = 3) {
|
|
146
|
+
const selected = [];
|
|
147
|
+
const sessionCounts = new Map();
|
|
148
|
+
for (const r of results) {
|
|
149
|
+
const count = sessionCounts.get(r.sessionId) || 0;
|
|
150
|
+
if (count >= maxPerSession)
|
|
151
|
+
continue;
|
|
152
|
+
selected.push(r);
|
|
153
|
+
sessionCounts.set(r.sessionId, count + 1);
|
|
154
|
+
if (selected.length >= limit)
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (selected.length < limit) {
|
|
158
|
+
for (const r of results) {
|
|
159
|
+
if (selected.length >= limit)
|
|
160
|
+
break;
|
|
161
|
+
if (!selected.some((s) => s.obsId === r.obsId)) {
|
|
162
|
+
selected.push(r);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return selected;
|
|
167
|
+
}
|
|
168
|
+
async enrichResults(results, limit) {
|
|
169
|
+
const sliced = results.slice(0, limit);
|
|
170
|
+
const observations = await Promise.all(sliced.map(async (r) => {
|
|
171
|
+
const obs = await this.kv
|
|
172
|
+
.get(KV.observations(r.sessionId), r.obsId)
|
|
173
|
+
.catch(() => null);
|
|
174
|
+
if (obs)
|
|
175
|
+
return obs;
|
|
176
|
+
// Fallback: indexed entry may originate from mem::remember, which
|
|
177
|
+
// writes to KV.memories with a synthetic sessionId. Coerce the
|
|
178
|
+
// Memory record into a CompressedObservation so saved memories
|
|
179
|
+
// still surface.
|
|
180
|
+
const mem = await this.kv
|
|
181
|
+
.get(KV.memories, r.obsId)
|
|
182
|
+
.catch(() => null);
|
|
183
|
+
return mem ? memoryToObservation(mem) : null;
|
|
184
|
+
}));
|
|
185
|
+
const enriched = [];
|
|
186
|
+
for (let i = 0; i < sliced.length; i++) {
|
|
187
|
+
const obs = observations[i];
|
|
188
|
+
const src = sliced[i];
|
|
189
|
+
if (obs) {
|
|
190
|
+
enriched.push({
|
|
191
|
+
observation: obs,
|
|
192
|
+
bm25Score: src.bm25Score,
|
|
193
|
+
vectorScore: src.vectorScore,
|
|
194
|
+
graphScore: src.graphScore,
|
|
195
|
+
combinedScore: src.combinedScore,
|
|
196
|
+
sessionId: src.sessionId,
|
|
197
|
+
...(src.graphContext !== undefined
|
|
198
|
+
? { graphContext: src.graphContext }
|
|
199
|
+
: {}),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return enriched;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ISdk } from "../kernel/index.js";
|
|
2
|
+
import { StateKV } from "../state/kv.js";
|
|
3
|
+
import { DedupMap } from "./dedup.js";
|
|
4
|
+
export { registerObserveFunction } from "./observe.js";
|
|
5
|
+
export { registerContextFunction } from "./context.js";
|
|
6
|
+
export { registerSearchFunction } from "./search.js";
|
|
7
|
+
export { registerForgetFunction } from "./forget.js";
|
|
8
|
+
export { registerDoctorFunction } from "./doctor.js";
|
|
9
|
+
export type { DoctorReport } from "./doctor.js";
|
|
10
|
+
export { registerReceiptFunction } from "./receipt.js";
|
|
11
|
+
export type { DeleteReceipt, ForgetResult } from "./receipt.js";
|
|
12
|
+
export { registerDejaFixFunctions, recordFix, lookupFix, errorSignature, looksLikeResolvedFix, DEJAFIX_SCOPE, } from "./dejafix.js";
|
|
13
|
+
export type { FixMemory, VerifiedFix, RecordFixInput } from "./dejafix.js";
|
|
14
|
+
export { getSearchIndex, getVectorIndex, setVectorIndex, getEmbeddingProvider, setEmbeddingProvider, rebuildIndex, vectorIndexAddGuarded, makeVectorIndex, } from "./search.js";
|
|
15
|
+
export { QuantizedVectorIndex } from "./quantized-vector-index.js";
|
|
16
|
+
export type { QuantParams } from "./quantized-vector-index.js";
|
|
17
|
+
export { persistVectorIndex, loadVectorIndex } from "./vector-persistence.js";
|
|
18
|
+
export { DedupMap } from "./dedup.js";
|
|
19
|
+
export interface RegisterCoreOptions {
|
|
20
|
+
/** Per-request context token budget. Defaults to config. */
|
|
21
|
+
tokenBudget?: number;
|
|
22
|
+
/** Max observations per session. Defaults to config. */
|
|
23
|
+
maxObservationsPerSession?: number;
|
|
24
|
+
/** Dedup map for the observe write path. Defaults to a fresh DedupMap. */
|
|
25
|
+
dedupMap?: DedupMap;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register the three core functions against the kernel. The kernel
|
|
29
|
+
* routes the five state::* ids to its StateStore, so StateKV — constructed
|
|
30
|
+
* over the kernel here — is the persistence chokepoint all three share.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerCoreFunctions(sdk: ISdk, kv?: StateKV, opts?: RegisterCoreOptions): StateKV;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Barrel + single registration entrypoint for the core functions
|
|
3
|
+
// (mem::observe, mem::context, mem::search). The boot path (src/index.ts)
|
|
4
|
+
// constructs the StateKV over the kernel and calls registerCoreFunctions to
|
|
5
|
+
// wire all three against the kernel's function registry in one step.
|
|
6
|
+
import { StateKV } from "../state/kv.js";
|
|
7
|
+
import { registerObserveFunction } from "./observe.js";
|
|
8
|
+
import { registerContextFunction } from "./context.js";
|
|
9
|
+
import { registerSearchFunction } from "./search.js";
|
|
10
|
+
import { registerForgetFunction } from "./forget.js";
|
|
11
|
+
import { registerDoctorFunction } from "./doctor.js";
|
|
12
|
+
import { registerDejaFixFunctions } from "./dejafix.js";
|
|
13
|
+
import { registerReceiptFunction } from "./receipt.js";
|
|
14
|
+
import { DedupMap } from "./dedup.js";
|
|
15
|
+
import { getTokenBudget, getMaxObservationsPerSession } from "./config.js";
|
|
16
|
+
export { registerObserveFunction } from "./observe.js";
|
|
17
|
+
export { registerContextFunction } from "./context.js";
|
|
18
|
+
export { registerSearchFunction } from "./search.js";
|
|
19
|
+
export { registerForgetFunction } from "./forget.js";
|
|
20
|
+
export { registerDoctorFunction } from "./doctor.js";
|
|
21
|
+
export { registerReceiptFunction } from "./receipt.js";
|
|
22
|
+
export { registerDejaFixFunctions, recordFix, lookupFix, errorSignature, looksLikeResolvedFix, DEJAFIX_SCOPE, } from "./dejafix.js";
|
|
23
|
+
export { getSearchIndex, getVectorIndex, setVectorIndex, getEmbeddingProvider, setEmbeddingProvider, rebuildIndex, vectorIndexAddGuarded, makeVectorIndex, } from "./search.js";
|
|
24
|
+
export { QuantizedVectorIndex } from "./quantized-vector-index.js";
|
|
25
|
+
export { persistVectorIndex, loadVectorIndex } from "./vector-persistence.js";
|
|
26
|
+
export { DedupMap } from "./dedup.js";
|
|
27
|
+
/**
|
|
28
|
+
* Register the three core functions against the kernel. The kernel
|
|
29
|
+
* routes the five state::* ids to its StateStore, so StateKV — constructed
|
|
30
|
+
* over the kernel here — is the persistence chokepoint all three share.
|
|
31
|
+
*/
|
|
32
|
+
export function registerCoreFunctions(sdk, kv = new StateKV(sdk), opts = {}) {
|
|
33
|
+
const tokenBudget = opts.tokenBudget ?? getTokenBudget();
|
|
34
|
+
const maxObs = opts.maxObservationsPerSession ?? getMaxObservationsPerSession();
|
|
35
|
+
const dedupMap = opts.dedupMap ?? new DedupMap();
|
|
36
|
+
registerObserveFunction(sdk, kv, dedupMap, maxObs);
|
|
37
|
+
registerContextFunction(sdk, kv, tokenBudget);
|
|
38
|
+
registerSearchFunction(sdk, kv);
|
|
39
|
+
registerForgetFunction(sdk, kv);
|
|
40
|
+
registerDoctorFunction(sdk, kv);
|
|
41
|
+
registerDejaFixFunctions(sdk, kv);
|
|
42
|
+
registerReceiptFunction(sdk, kv);
|
|
43
|
+
return kv;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function withKeyedLock<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Per-key serialization. Calls that share a key run one at a time, chained on
|
|
3
|
+
// a promise so each waits for the previous to settle (whether it resolved or
|
|
4
|
+
// threw). Used to serialize the observe write path per session so counters and
|
|
5
|
+
// rollbacks stay race-free. Correct only for a single in-process instance; a
|
|
6
|
+
// multi-process memwarden would need a real distributed lock.
|
|
7
|
+
const chains = new Map();
|
|
8
|
+
export function withKeyedLock(key, fn) {
|
|
9
|
+
const prior = chains.get(key) ?? Promise.resolve();
|
|
10
|
+
// Run after the prior call settles either way (pass fn for both branches).
|
|
11
|
+
const run = prior.then(fn, fn);
|
|
12
|
+
// A branch that never rejects, used both to keep the chain going past a
|
|
13
|
+
// failure and to release the key once nothing is queued behind it.
|
|
14
|
+
const settled = run.then(() => undefined, () => undefined);
|
|
15
|
+
chains.set(key, settled);
|
|
16
|
+
void settled.then(() => {
|
|
17
|
+
if (chains.get(key) === settled)
|
|
18
|
+
chains.delete(key);
|
|
19
|
+
});
|
|
20
|
+
return run;
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const logger: {
|
|
2
|
+
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
3
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
4
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
5
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
6
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Minimal structured logger: the info/warn/error/debug surface the rest of
|
|
3
|
+
// the code calls, writing JSON lines to stderr so log output never corrupts a
|
|
4
|
+
// stdout protocol stream. Level is gated by MEMWARDEN_LOG_LEVEL (default
|
|
5
|
+
// "info").
|
|
6
|
+
const LEVELS = {
|
|
7
|
+
debug: 10,
|
|
8
|
+
info: 20,
|
|
9
|
+
warn: 30,
|
|
10
|
+
error: 40,
|
|
11
|
+
};
|
|
12
|
+
function threshold() {
|
|
13
|
+
const raw = (process.env.MEMWARDEN_LOG_LEVEL ?? "info").toLowerCase();
|
|
14
|
+
return LEVELS[raw] ?? LEVELS.info;
|
|
15
|
+
}
|
|
16
|
+
function emit(level, message, meta) {
|
|
17
|
+
if (LEVELS[level] < threshold())
|
|
18
|
+
return;
|
|
19
|
+
const line = {
|
|
20
|
+
ts: new Date().toISOString(),
|
|
21
|
+
level,
|
|
22
|
+
message,
|
|
23
|
+
...(meta ?? {}),
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
process.stderr.write(`${JSON.stringify(line)}\n`);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Logging must never throw into the call path.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const logger = {
|
|
33
|
+
debug: (message, meta) => emit("debug", message, meta),
|
|
34
|
+
info: (message, meta) => emit("info", message, meta),
|
|
35
|
+
warn: (message, meta) => emit("warn", message, meta),
|
|
36
|
+
error: (message, meta) => emit("error", message, meta),
|
|
37
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Adapts a stored Memory into the CompressedObservation shape the search and
|
|
3
|
+
// enrichment paths expect. A Memory has no real session, so it borrows its
|
|
4
|
+
// first sessionId (or the literal "memory") as a synthetic key that
|
|
5
|
+
// KV.memories lookups fall back on.
|
|
6
|
+
export function memoryToObservation(memory) {
|
|
7
|
+
const obs = {
|
|
8
|
+
id: memory.id,
|
|
9
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
10
|
+
timestamp: memory.createdAt,
|
|
11
|
+
type: "decision",
|
|
12
|
+
title: memory.title,
|
|
13
|
+
facts: [memory.content],
|
|
14
|
+
narrative: memory.content,
|
|
15
|
+
concepts: memory.concepts,
|
|
16
|
+
files: memory.files,
|
|
17
|
+
importance: memory.strength,
|
|
18
|
+
};
|
|
19
|
+
// Carry provenance so Memory records go through Verified Recall too. If a
|
|
20
|
+
// record has no provenance but does reference files, synthesize a minimal
|
|
21
|
+
// one so at least deletion is detected (content drift needs captured hashes).
|
|
22
|
+
if (memory.provenance) {
|
|
23
|
+
obs.provenance = memory.provenance;
|
|
24
|
+
}
|
|
25
|
+
else if (memory.files && memory.files.length > 0) {
|
|
26
|
+
obs.provenance = { files: memory.files, command: "memory" };
|
|
27
|
+
}
|
|
28
|
+
return obs;
|
|
29
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ISdk } from "../kernel/index.js";
|
|
2
|
+
import type { StateKV } from "../state/kv.js";
|
|
3
|
+
import { DedupMap } from "./dedup.js";
|
|
4
|
+
export declare function extractImage(d: unknown): string | undefined;
|
|
5
|
+
export declare function registerObserveFunction(sdk: ISdk, kv: StateKV, dedupMap?: DedupMap, maxObservationsPerSession?: number): void;
|