cogmemory 0.0.1-beta.2

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.
@@ -0,0 +1,1068 @@
1
+ import { ofetch as x, FetchError as E } from "ofetch";
2
+ class T extends Error {
3
+ /**
4
+ * Set as a real value, because a minifier renames the class and that silently
5
+ * changes what monitoring groups on. `error.name` is correct either way — the
6
+ * constructor assigns it — but `SomeError.name` and `error.constructor.name`
7
+ * are how a reporter identifies a class, and a minified `"h"` breaks that.
8
+ */
9
+ static name = "CognitiveMemoryError";
10
+ /** The service's own tag: `Unauthorized`, `Forbidden`, `InvalidRequest`, … */
11
+ code;
12
+ status;
13
+ /** Present on a 403: the scope the key would have needed. */
14
+ requiredScope;
15
+ /** Present on a 400: which field, and why. */
16
+ issues;
17
+ body;
18
+ constructor(e, t) {
19
+ super(e), this.name = "CognitiveMemoryError", this.status = t.status, this.code = t.code, this.body = t.body, this.requiredScope = t.requiredScope, this.issues = t.issues, Error.captureStackTrace && Error.captureStackTrace(this, T);
20
+ }
21
+ /** The key is missing, malformed, expired or revoked. */
22
+ isAuthError() {
23
+ return this.status === 401;
24
+ }
25
+ /** The key is valid but lacks a scope. `requiredScope` says which. */
26
+ isScopeError() {
27
+ return this.status === 403;
28
+ }
29
+ /** The request body did not match the schema. `issues` says where. */
30
+ isValidationError() {
31
+ return this.status === 400;
32
+ }
33
+ /** Nothing exists at that id — or it belongs to another organisation. */
34
+ isNotFoundError() {
35
+ return this.status === 404;
36
+ }
37
+ isRateLimitError() {
38
+ return this.status === 429;
39
+ }
40
+ /** A dependency of ours failed, not the request. Safe to retry. */
41
+ isServerError() {
42
+ return this.status >= 500;
43
+ }
44
+ }
45
+ function L(i) {
46
+ const {
47
+ apiKey: e,
48
+ baseUrl: t = D(),
49
+ timeout: s = 3e4,
50
+ retry: o = 2,
51
+ debug: n = !1,
52
+ headers: r = {}
53
+ } = i;
54
+ if (typeof e != "string" || e.trim() === "")
55
+ throw new Error(
56
+ "cognitive-memory: apiKey is required. Issue one in the dashboard — the secret is shown only once."
57
+ );
58
+ const a = x.create({
59
+ baseURL: `${t.replace(/\/$/, "")}/api/v1`,
60
+ timeout: s,
61
+ // Retries are for transport hiccups, not for a 400. ofetch already limits
62
+ // this to idempotent-looking methods, and the service is idempotent for the
63
+ // reads that matter.
64
+ retry: o,
65
+ headers: {
66
+ accept: "application/json",
67
+ ...r
68
+ },
69
+ onRequest({ options: h, request: u }) {
70
+ h.headers.set("authorization", `Bearer ${e}`), h.headers.set("content-type", "application/json"), n && console.log("[cognitive-memory]", h.method ?? "GET", `${h.baseURL ?? ""}${u ?? ""}`);
71
+ },
72
+ onResponse({ response: h, options: u, request: c }) {
73
+ n && console.log("[cognitive-memory]", h.status, u.method ?? "GET", c ?? "");
74
+ }
75
+ });
76
+ return async (h, u) => {
77
+ try {
78
+ return await a(h, u);
79
+ } catch (c) {
80
+ if (!(c instanceof E) || !c.response) throw c;
81
+ const d = c.response, m = d._data ?? void 0;
82
+ throw new T(m?.message ?? `cognitive-memory: request failed with ${d.status}`, {
83
+ status: d.status,
84
+ code: m?.error ?? "UnknownError",
85
+ ...m === void 0 ? {} : { body: m },
86
+ ...m?.requiredScope === void 0 ? {} : { requiredScope: m.requiredScope },
87
+ ...m?.issues === void 0 ? {} : { issues: m.issues }
88
+ });
89
+ }
90
+ };
91
+ }
92
+ function D() {
93
+ return typeof window < "u" ? window.location.origin : "http://localhost:3000";
94
+ }
95
+ class R {
96
+ constructor(e) {
97
+ this.client = e;
98
+ }
99
+ build(e = {}) {
100
+ return this.client("/context", { method: "POST", body: e });
101
+ }
102
+ }
103
+ class I {
104
+ constructor(e) {
105
+ this.client = e;
106
+ }
107
+ /**
108
+ * The envelope is unwrapped here rather than passed on.
109
+ *
110
+ * The service replies with `{ memories, stats }` so a dashboard can get both in
111
+ * one call, but an SDK caller who asked for a list wants a list. Deciding that
112
+ * in one place here is better than making every integration know the shape.
113
+ */
114
+ async list(e = {}) {
115
+ return (await this.client("/memories", {
116
+ method: "GET",
117
+ query: e
118
+ })).memories;
119
+ }
120
+ async get(e) {
121
+ return (await this.client(`/memories/${encodeURIComponent(e)}`, {
122
+ method: "GET"
123
+ })).memory;
124
+ }
125
+ /**
126
+ * State facts outright.
127
+ *
128
+ * Restatements of something already held are folded in rather than stored
129
+ * twice, and the result says which ones were folded and into what — a write
130
+ * that silently did nothing is indistinguishable from a bug.
131
+ */
132
+ create(e) {
133
+ return this.client("/memories", { method: "POST", body: e });
134
+ }
135
+ /** Move a memory between tiers. L1 is pre-staged into every context build. */
136
+ async promote(e, t) {
137
+ return (await this.client(`/memories/${encodeURIComponent(e)}`, {
138
+ method: "PATCH",
139
+ body: { tier: t }
140
+ })).memory;
141
+ }
142
+ remove(e) {
143
+ return this.client(`/memories/${encodeURIComponent(e)}`, { method: "DELETE" });
144
+ }
145
+ }
146
+ class O {
147
+ constructor(e) {
148
+ this.client = e;
149
+ }
150
+ search(e) {
151
+ return this.client("/recall", { method: "POST", body: e });
152
+ }
153
+ }
154
+ class j {
155
+ constructor(e) {
156
+ this.client = e;
157
+ }
158
+ get() {
159
+ return this.client("/self-model", { method: "GET" });
160
+ }
161
+ record(e) {
162
+ return this.client("/self-model/outcome", { method: "POST", body: e });
163
+ }
164
+ }
165
+ class H {
166
+ constructor(e) {
167
+ this.client = e;
168
+ }
169
+ /** Needs only `stats:read`, so monitoring can watch without reading memories. */
170
+ get() {
171
+ return this.client("/stats", { method: "GET" });
172
+ }
173
+ }
174
+ const P = async (i) => {
175
+ const e = i.replace(/\/$/, ""), t = await fetch(`${e}/api/v1/health`, { headers: { accept: "application/json" } });
176
+ if (!t.ok)
177
+ throw new Error(`cognitive-memory: health check failed with ${t.status}`);
178
+ return await t.json();
179
+ };
180
+ class z {
181
+ constructor(e) {
182
+ this.client = e;
183
+ }
184
+ async list(e) {
185
+ return (await this.client("/tensions", {
186
+ method: "GET",
187
+ query: e === void 0 ? {} : { status: e }
188
+ })).tensions;
189
+ }
190
+ async create(e) {
191
+ return (await this.client("/tensions", {
192
+ method: "POST",
193
+ body: e
194
+ })).tension;
195
+ }
196
+ async resolve(e, t) {
197
+ return (await this.client(`/tensions/${encodeURIComponent(e)}`, {
198
+ method: "POST",
199
+ body: t
200
+ })).tension;
201
+ }
202
+ }
203
+ class N {
204
+ constructor(e) {
205
+ this.client = e;
206
+ }
207
+ learn(e) {
208
+ return this.client("/turns", { method: "POST", body: e });
209
+ }
210
+ }
211
+ class U {
212
+ client;
213
+ baseUrl;
214
+ memories;
215
+ context;
216
+ recall;
217
+ turns;
218
+ tensions;
219
+ selfModel;
220
+ stats;
221
+ constructor(e) {
222
+ this.baseUrl = (e.baseUrl ?? "http://localhost:3000").replace(/\/$/, ""), this.client = L(e), this.memories = new I(this.client), this.context = new R(this.client), this.recall = new O(this.client), this.turns = new N(this.client), this.tensions = new z(this.client), this.selfModel = new j(this.client), this.stats = new H(this.client);
223
+ }
224
+ /**
225
+ * Liveness and limits, without spending a request on the configured key.
226
+ *
227
+ * Useful in a readiness probe: if `extractor` is `rules-only` the service is up
228
+ * but is not doing model-backed extraction, and `problems` is non-empty when a
229
+ * setting is present but unusable.
230
+ */
231
+ health() {
232
+ return P(this.baseUrl);
233
+ }
234
+ }
235
+ function he(i) {
236
+ return new U(i);
237
+ }
238
+ async function de(i, e, t = {}) {
239
+ const s = await i.context.build({
240
+ userMessage: e.userMessage,
241
+ ...t.sessionId === void 0 ? {} : { sessionId: t.sessionId }
242
+ }), o = await e.run(s.text);
243
+ if (t.learn === !1)
244
+ return { context: s, learning: null, learningSkipped: "learning disabled for this turn" };
245
+ let n = null;
246
+ try {
247
+ n = await i.turns.learn({
248
+ userMessage: e.userMessage,
249
+ assistantResponse: o,
250
+ ...t.sessionId === void 0 ? {} : { sessionId: t.sessionId }
251
+ }), t.onLearn?.(n);
252
+ } catch (r) {
253
+ return console.warn("[cognitive-memory] could not learn from this turn", r), { context: s, learning: null, learningSkipped: String(r) };
254
+ }
255
+ if (t.domain !== void 0)
256
+ try {
257
+ await i.selfModel.record({
258
+ domain: t.domain,
259
+ success: t.failurePattern === void 0,
260
+ ...t.failurePattern === void 0 ? {} : { failurePattern: t.failurePattern },
261
+ ...t.strategy === void 0 ? {} : { strategy: t.strategy }
262
+ });
263
+ } catch (r) {
264
+ console.warn("[cognitive-memory] could not record the domain outcome", r);
265
+ }
266
+ return { context: s, learning: n };
267
+ }
268
+ async function me(i, e, t = {}) {
269
+ const s = await i.recall.search({
270
+ query: e,
271
+ ...t.limit === void 0 ? {} : { limit: t.limit }
272
+ });
273
+ if (s.empty)
274
+ return t.emptyMessage ?? `Nothing in memory matches "${e}". If you were not told, say so rather than guessing.`;
275
+ const o = s.results.map((n) => {
276
+ const r = n.memory.domains.length > 0 ? ` (${n.memory.domains.join(", ")})` : "";
277
+ return `- ${n.memory.content}${r} [${n.memory.tier}, relevance ${n.score.toFixed(2)}]`;
278
+ });
279
+ return `Remembered (${s.results.length} match${s.results.length === 1 ? "" : "es"}):
280
+ ${o.join(`
281
+ `)}`;
282
+ }
283
+ async function ue(i, e) {
284
+ const t = await i.memories.create({
285
+ items: e.facts.map((s) => ({ content: s })),
286
+ ...e.sessionId === void 0 ? {} : { sessionId: e.sessionId }
287
+ });
288
+ return { stored: t.stored, merged: t.mergedInto, rejected: t.rejected };
289
+ }
290
+ const B = [
291
+ /actually,?\s+(?:we|i)\s+(?:switched|changed|moved|migrated|replaced|use|want|have)/i,
292
+ /that(?:'s|\s+is)\s+(?:wrong|incorrect|outdated|false|not\s+right|no\s+longer)/i,
293
+ /no,?\s+(?:it(?:'s|\s+is)|we\s+use|we\s+switched|it\s+should\s+be|don(?:'t|\s+not)\s+use)/i,
294
+ /stop\s+using\s+/i,
295
+ /instead\s+of\s+/i,
296
+ /disregard\s+(?:prior|previous|what\s+i\s+said)/i
297
+ ], W = {
298
+ auth: [/auth/i, /login/i, /session/i, /jwt/i, /oauth/i, /token/i, /password/i, /permission/i],
299
+ database: [/postgres/i, /sqlite/i, /sql/i, /schema/i, /migration/i, /prisma/i, /drizzle/i, /mongo/i, /redis/i],
300
+ frontend: [/css/i, /tailwind/i, /component/i, /jsx/i, /tsx/i, /layout/i, /ui/i, /render/i, /html/i],
301
+ build: [/vite/i, /webpack/i, /turbo/i, /tsconfig/i, /pnpm/i, /package\.json/i, /bundle/i],
302
+ api: [/rest/i, /graphql/i, /endpoint/i, /route/i, /fetch/i, /request/i, /response/i],
303
+ git: [/branch/i, /commit/i, /merge/i, /rebase/i, /worktree/i, /conflict/i]
304
+ }, F = (i) => {
305
+ if (!i || i.trim().length === 0)
306
+ return { action: "proceed" };
307
+ for (const e of B)
308
+ if (e.test(i))
309
+ return {
310
+ action: "inject_caution",
311
+ cautionNote: "User appears to be explicitly correcting a prior premise or configuration. Verify current facts before proceeding with assumptions.",
312
+ detectedDomains: k(i)
313
+ };
314
+ return {
315
+ action: "proceed",
316
+ detectedDomains: k(i)
317
+ };
318
+ }, k = (i) => {
319
+ const e = [];
320
+ for (const [t, s] of Object.entries(W))
321
+ s.some((o) => o.test(i)) && e.push(t);
322
+ return e;
323
+ }, _ = /* @__PURE__ */ new Set([
324
+ "the",
325
+ "and",
326
+ "for",
327
+ "this",
328
+ "that",
329
+ "with",
330
+ "from",
331
+ "you",
332
+ "are",
333
+ "was",
334
+ "has",
335
+ "have",
336
+ "what",
337
+ "which",
338
+ "when",
339
+ "were",
340
+ "will",
341
+ "your",
342
+ "our",
343
+ "its",
344
+ "not",
345
+ "but",
346
+ "all",
347
+ "any",
348
+ "can",
349
+ "did",
350
+ "does",
351
+ "how",
352
+ "into",
353
+ "out",
354
+ "use",
355
+ "used",
356
+ "using",
357
+ "one",
358
+ "two",
359
+ "get",
360
+ "new",
361
+ "now",
362
+ "then",
363
+ "than",
364
+ "them",
365
+ "they",
366
+ "his",
367
+ "her",
368
+ "she",
369
+ "him",
370
+ "been",
371
+ "being",
372
+ "there",
373
+ "here",
374
+ "also",
375
+ "just",
376
+ "like",
377
+ "make",
378
+ "made",
379
+ "need",
380
+ "want",
381
+ "about",
382
+ "after",
383
+ "tell",
384
+ "know",
385
+ "give",
386
+ "show",
387
+ "please",
388
+ "would",
389
+ "could",
390
+ "should",
391
+ "shall"
392
+ ]), q = /* @__PURE__ */ new Set([
393
+ "this",
394
+ "that",
395
+ "these",
396
+ "those",
397
+ "there",
398
+ "here",
399
+ "with",
400
+ "from",
401
+ "into",
402
+ "must",
403
+ "should",
404
+ "always",
405
+ "never",
406
+ "under",
407
+ "over",
408
+ "about",
409
+ "after",
410
+ "before",
411
+ "when",
412
+ "where",
413
+ "which",
414
+ "what",
415
+ "your",
416
+ "their",
417
+ "them",
418
+ "they",
419
+ "then",
420
+ "than",
421
+ "also",
422
+ "just",
423
+ "only",
424
+ "each",
425
+ "every",
426
+ "some",
427
+ "such",
428
+ "very",
429
+ "more",
430
+ "most",
431
+ "same",
432
+ "file",
433
+ "files",
434
+ "name",
435
+ "names",
436
+ "project",
437
+ "repository",
438
+ "repo",
439
+ "note"
440
+ ]), g = (i) => new Set(
441
+ i.toLowerCase().split(/[^a-z0-9]+/).filter((e) => e.length >= 3 && !_.has(e))
442
+ ), v = (i, e) => {
443
+ if (i.size === 0 || e.size === 0) return 0;
444
+ let t = 0;
445
+ for (const s of i) e.has(s) && (t += 1);
446
+ return t / Math.min(i.size, e.size);
447
+ }, G = 0.3, Z = 0.12, Q = 8, K = 0.8, p = (i) => new Set(
448
+ i.toLowerCase().replace(/['']/g, "").split(/[^a-z0-9]+/).filter(Boolean).filter((e) => /\d/.test(e) || e.length >= 4).filter((e) => !q.has(e))
449
+ ), b = (i, e) => {
450
+ const t = (o) => new Set([...o].map((n) => n.length >= 4 && n.endsWith("s") ? n.slice(0, -1) : n)), s = t(p(e));
451
+ for (const o of t(p(i)))
452
+ if (!s.has(o)) return !0;
453
+ return !1;
454
+ }, V = /\b[a-z0-9][a-z0-9-]{1,}(?:\.[a-z0-9-]+)*\.[a-z]{2,}\b/gi, J = /* @__PURE__ */ new Set([
455
+ "eg",
456
+ "ie",
457
+ "etc",
458
+ "vs",
459
+ "approx",
460
+ "inc",
461
+ "ltd",
462
+ "corp",
463
+ "dept",
464
+ "est",
465
+ "fig",
466
+ "no",
467
+ "al"
468
+ ]), fe = (i) => {
469
+ const e = /* @__PURE__ */ new Set();
470
+ for (const t of i.match(/\bhttps?:\/\/[^\s<>()[\]"'`]+/g) ?? []) e.add(t);
471
+ for (const t of i.match(V) ?? [])
472
+ J.has(t.toLowerCase()) || e.add(t);
473
+ for (const t of i.match(/\b(?:\.{0,2}\/)?[\w-]+(?:\/[\w.-]+)+\/?/g) ?? [])
474
+ t.length > 3 && e.add(t);
475
+ for (const t of i.match(/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b/g) ?? []) e.add(t);
476
+ for (const t of i.match(/\b[A-Z]{2,}[0-9][A-Z0-9-]*\b/g) ?? []) e.add(t);
477
+ for (const t of i.match(/\b[a-z]+(?:[A-Z][a-z0-9]+){1,}\b/g) ?? []) e.add(t);
478
+ for (const t of i.match(/\b[a-z]+(?:-[a-z0-9]+){2,}\b/g) ?? []) e.add(t);
479
+ for (const t of i.match(/\b[0-9a-f]{6,}\b/gi) ?? []) e.add(t);
480
+ return [...e];
481
+ }, Y = [
482
+ /\b(?:do not|don'?t|never|no need to)\s+(?:verify|check|confirm|look\s?up|search|investigate|browse|resolve)\b/i,
483
+ /\bjust\s+(?:remember|note|acknowledge|retain|treat)\b/i,
484
+ /\b(?:held|noted|stored|remembered)\s+(?:in|for)\s+(?:this|the)\s+conversation\b/i,
485
+ /\bnot\s+verified\b/i,
486
+ /\bwithout\s+verifying\b/i,
487
+ /\bfor\s+this\s+(?:conversation|session|turn|reply|response)\s+only\b/i,
488
+ /^(?:ok|okay|noted|got it|sure|thanks)\b[.!]?$/i
489
+ ], A = (i) => Y.some((e) => e.test(i)), X = (i, e) => {
490
+ const t = p(i), s = p(e);
491
+ if (t.size === 0 || s.size === 0) return 0;
492
+ let o = 0;
493
+ for (const n of t) s.has(n) && (o += 1);
494
+ return o / Math.min(t.size, s.size);
495
+ }, pe = (i) => i.trim().toLowerCase(), ee = (i) => Math.ceil(i.length / 4), $ = (i) => {
496
+ if (i.gist && i.gist.trim().length > 0) return i.gist.trim();
497
+ const t = (i.content.split(/(?<=[.!?])\s/)[0] ?? i.content).trim();
498
+ return t.length > 90 ? `${t.slice(0, 90)}…` : t;
499
+ }, te = 5;
500
+ class ge {
501
+ // L0: Pinned core state (identity, self-model, active tensions)
502
+ activeTensions = /* @__PURE__ */ new Map();
503
+ selfModel;
504
+ activeTaskTrace = "";
505
+ // L1: Hot Cache (pre-staged for upcoming turn)
506
+ l1HotCache = /* @__PURE__ */ new Map();
507
+ // L2: Warm Storage (indexed candidates ready for promotion)
508
+ l2WarmStore = /* @__PURE__ */ new Map();
509
+ // L3: Cold Archive (historical, loaded on demand)
510
+ l3ColdArchive = /* @__PURE__ */ new Map();
511
+ maxL0Tokens;
512
+ maxL1Tokens;
513
+ /** Ceiling on everything injected into one prompt, index and bodies together. */
514
+ maxTotalTokens;
515
+ arbiter = null;
516
+ autoExtractMemories;
517
+ /** Model-backed turn extractor; the regex fallback runs when this is absent. */
518
+ reconcile;
519
+ extract;
520
+ onPersist;
521
+ stats = {
522
+ totalTurnsProcessed: 0,
523
+ predictionsHit: 0,
524
+ predictionsTotal: 0,
525
+ tensionsDetected: 0
526
+ };
527
+ constructor(e = {}) {
528
+ this.maxL0Tokens = e.maxL0Tokens ?? 2e3, this.maxL1Tokens = e.maxL1Tokens ?? 8e3, this.maxTotalTokens = e.maxTotalTokens ?? 2e3, this.arbiter = e.arbiter ?? null, this.autoExtractMemories = e.autoExtractMemories ?? !0, this.extract = e.extract, this.reconcile = e.reconcile, this.onPersist = e.onPersist, this.selfModel = {
529
+ domains: e.initialSelfModel?.domains ?? {},
530
+ calibrationFactor: e.initialSelfModel?.calibrationFactor ?? 1,
531
+ activeDomains: e.initialSelfModel?.activeDomains ?? []
532
+ };
533
+ }
534
+ /**
535
+ * Sub-1ms synchronous call to produce the prompt context block.
536
+ * Concatenates L0 (active tensions + self-model) and pre-staged L1.
537
+ * Adds zero latency to TTFT.
538
+ */
539
+ /**
540
+ * Decide what goes into this turn's prompt, and record why.
541
+ *
542
+ * Follows the index/body split that Claude Code's `MEMORY.md` and Letta's
543
+ * progressive disclosure both converged on: a short index of every memory is
544
+ * always present, and a body is only spent where a deterministic signal earned
545
+ * it. Always injecting bodies costs an order of magnitude more tokens and, per
546
+ * Chroma's context-rot work, injects distractors by construction.
547
+ *
548
+ * @param forceFull memory ids whose body should be included regardless of tier.
549
+ */
550
+ planInjection(e = {}) {
551
+ const t = new Set(e.forceFull ?? []), s = [], o = [];
552
+ let n = 0, r = !1;
553
+ const a = (c, d, m) => {
554
+ const f = $(c), S = m ? c.content : void 0, C = c.metadata.domains.slice(0, 3), M = C.length > 0 ? ` (${C.join(", ")})` : "", y = ee(`${f}${M}${S ?? ""}`);
555
+ if (n + y > this.maxTotalTokens) {
556
+ r = !0;
557
+ return;
558
+ }
559
+ n += y, o.push({ id: c.id, tier: c.tier, reason: d, gist: f, body: S, tokens: y });
560
+ };
561
+ if (e.userMessage) {
562
+ const c = F(e.userMessage);
563
+ c.action === "inject_caution" && c.cautionNote && s.push(`### ⚠️ Correction Detected In This Message
564
+ ${c.cautionNote}`);
565
+ }
566
+ const l = this.selfModel.activeDomains.map((c) => ({ d: c, capability: this.selfModel.domains[c] })).filter((c) => c.capability && c.capability.reliabilityScore < 0.75);
567
+ if (l.length > 0) {
568
+ const c = l.map(({ d, capability: m }) => {
569
+ const f = m;
570
+ return a(
571
+ {
572
+ id: `guardrail-${d}`,
573
+ content: `${d}: reliability ${Math.round(f.reliabilityScore * 100)}% over ${f.sampleCount} tasks.${f.knownFailurePatterns.length > 0 ? ` Known pitfalls: ${f.knownFailurePatterns.join("; ")}` : ""}${f.recommendedStrategies.length > 0 ? ` Approach: ${f.recommendedStrategies.join("; ")}` : ""}`,
574
+ tier: "L0",
575
+ metadata: { domains: [d], createdAt: Date.now(), lastAccessedAt: Date.now(), accessCount: 0 }
576
+ },
577
+ "guardrail",
578
+ !0
579
+ ), `- ${d} (${Math.round(f.reliabilityScore * 100)}% reliable): ${f.knownFailurePatterns.join("; ") || "be careful"}`;
580
+ });
581
+ s.push(`### Weak Domains — Under 75% Reliability
582
+ ${c.join(`
583
+ `)}`);
584
+ }
585
+ const h = [];
586
+ for (const c of this.l1HotCache.values()) {
587
+ const d = c.metadata.domains.slice(0, 3), m = d.length > 0 ? ` (${d.join(", ")})` : "";
588
+ t.has(c.id) ? (a(c, "trigger", !0), h.push(`- ${c.content}${m}`)) : (a(c, "index", !1), h.push(`- ${$(c)}${m}`));
589
+ }
590
+ h.length > 0 && s.push(
591
+ [
592
+ "### Memory index — established earlier in this project",
593
+ "One line per remembered item. Use `recall` to pull a full item, then treat it as true.",
594
+ ...h
595
+ ].join(`
596
+ `)
597
+ );
598
+ const u = [...this.activeTensions.values()].filter((c) => c.status === "active");
599
+ if (u.length > 0) {
600
+ const c = u.map((d) => (a(
601
+ {
602
+ id: d.id,
603
+ content: `${d.claimA.statement} vs ${d.claimB.statement} — ${d.actionableQuestion}`,
604
+ bookmark: d.claimA.statement,
605
+ tier: "L0",
606
+ metadata: { domains: [], createdAt: d.claimA.timestamp, lastAccessedAt: Date.now(), accessCount: 0 }
607
+ },
608
+ "tension",
609
+ !0
610
+ ), `- [${d.impact.toUpperCase()}] "${d.claimA.statement}" conflicts with "${d.claimB.statement}". Ask: ${d.actionableQuestion}`));
611
+ s.push(`### Unresolved Contradictions
612
+ ${c.join(`
613
+ `)}`);
614
+ }
615
+ return {
616
+ text: s.length > 0 ? `
617
+ ## Cognitive Memory State
618
+ ${s.join(`
619
+
620
+ `)}
621
+ ` : "",
622
+ entries: o,
623
+ totalTokens: n,
624
+ truncated: r
625
+ };
626
+ }
627
+ /** Prompt text only. Prefer `planInjection` when you want to log what went in. */
628
+ getPromptContext(e) {
629
+ return this.planInjection({ userMessage: e }).text;
630
+ }
631
+ /**
632
+ * Search every tier for memories relevant to `query`.
633
+ *
634
+ * This is the on-demand path, exposed to the agent as a tool. Pre-staging into
635
+ * the prompt is a best-effort optimisation; a model that does not read the
636
+ * block, or whose question shares no words with it, can still ask. Ranking is
637
+ * deterministic — no model involved — so recall does not depend on model
638
+ * quality.
639
+ */
640
+ search(e, t = 8) {
641
+ const s = g(e);
642
+ if (s.size === 0) return [];
643
+ const n = [
644
+ ...this.l1HotCache.values(),
645
+ ...this.l2WarmStore.values(),
646
+ ...this.l3ColdArchive.values()
647
+ ].map((l) => ({
648
+ item: l,
649
+ score: v(s, g(`${l.content} ${l.metadata.domains.join(" ")}`))
650
+ })).filter((l) => l.score > 0).sort((l, h) => h.score - l.score || l.item.content.length - h.item.content.length), r = [], a = [];
651
+ for (const l of n)
652
+ if (!r.some((h) => X(h, l.item.content) >= K) && (r.push(l.item.content), a.push(l), a.length >= Math.max(1, t)))
653
+ break;
654
+ return a;
655
+ }
656
+ /**
657
+ * Post-turn asynchronous execution.
658
+ * Fired when the agent finishes streaming its response to the user.
659
+ * Evaluates turn, manages L1 cache, detects tensions, updates self-model.
660
+ */
661
+ async postTurnAsync(e) {
662
+ this.stats.totalTurnsProcessed += 1, e.detectedDomains && e.detectedDomains.length > 0 && (this.selfModel.activeDomains = Array.from(
663
+ /* @__PURE__ */ new Set([...this.selfModel.activeDomains, ...e.detectedDomains])
664
+ ));
665
+ const t = Array.from(this.l2WarmStore.values()).slice(0, 20).map((n) => ({
666
+ id: n.id,
667
+ bookmark: n.bookmark,
668
+ domains: n.metadata.domains,
669
+ hasTension: !1
670
+ })), s = Array.from(this.l1HotCache.values()).map((n) => ({
671
+ id: n.id,
672
+ bookmark: n.bookmark,
673
+ domains: n.metadata.domains
674
+ })), o = this.arbiter ? await this.arbiter({
675
+ turnText: e.userMessage,
676
+ assistantReply: e.assistantResponse,
677
+ l0Prompt: this.activeTaskTrace,
678
+ l1Summaries: s,
679
+ candidates: t
680
+ }) : this.runHeuristicArbiter(e, t);
681
+ if (this.applyArbiterDecision(o), this.autoExtractMemories)
682
+ if (this.extract)
683
+ try {
684
+ await this.applyExtraction(
685
+ await this.extract({ userMessage: e.userMessage, assistantResponse: e.assistantResponse })
686
+ );
687
+ } catch {
688
+ this.autoExtractTurnMemory(e.userMessage, e.assistantResponse);
689
+ }
690
+ else e.assistantResponse.length > 50 && this.autoExtractTurnMemory(e.userMessage, e.assistantResponse);
691
+ if (this.enforceBudgets(), this.onPersist)
692
+ try {
693
+ await this.onPersist(this.getSnapshot());
694
+ } catch {
695
+ }
696
+ }
697
+ /**
698
+ * Fold extracted memories and tensions in, skipping anything already known.
699
+ *
700
+ * Extracted items land in L2 rather than L1: they are candidates, and the
701
+ * arbiter decides what is worth pre-staging. That is what gives the tiering
702
+ * something to actually do — previously nothing ever wrote to L2, so every
703
+ * arbiter run evaluated an empty candidate set.
704
+ */
705
+ async applyExtraction(e) {
706
+ const t = [];
707
+ for (const s of e.memories ?? []) {
708
+ const o = s.content.trim();
709
+ if (o.length < 8 || o.length > 600 || A(o)) continue;
710
+ let n = o;
711
+ const r = this.recallSimilar(o);
712
+ if (r.length > 0) {
713
+ let h = { action: "add" };
714
+ if (this.reconcile ? t.push({ content: o, candidates: r }) : r.some((u) => u.content.trim().toLowerCase() === o.trim().toLowerCase()) && (h = { action: "merge" }), h.action === "reject" || h.action === "merge" && r[0] && this.mergeInto(r[0], h.content, { text: o, stored: !1 }))
715
+ continue;
716
+ h.action === "replace" && this.supersede(r.map((u) => u.content)), n = h.content?.trim() || o;
717
+ }
718
+ const a = Date.now(), l = `mem-${a}-${Math.random().toString(36).slice(2, 7)}`;
719
+ this.addMemory(
720
+ {
721
+ id: l,
722
+ content: n,
723
+ bookmark: n.slice(0, 80),
724
+ tier: "L1",
725
+ metadata: {
726
+ domains: s.domains ?? [],
727
+ createdAt: a,
728
+ lastAccessedAt: a,
729
+ accessCount: 1
730
+ }
731
+ },
732
+ "L1"
733
+ );
734
+ }
735
+ if (t.length > 0 && this.reconcile) {
736
+ let s = [];
737
+ try {
738
+ s = await this.reconcile({
739
+ items: t.map((o) => ({ candidate: o.content, remember: o.candidates.map((n) => n.content) }))
740
+ });
741
+ } catch {
742
+ s = [];
743
+ }
744
+ for (const [o, n] of t.entries()) {
745
+ const r = s[o];
746
+ if (r)
747
+ if (r.action === "reject") this.removeByContent(n.content);
748
+ else if (r.action === "merge") {
749
+ const a = n.candidates[0];
750
+ if (a && this.mergeInto(a, r.content, { text: n.content, stored: !0 }))
751
+ continue;
752
+ } else r.action === "replace" && (this.supersede(n.candidates.map((a) => a.content)), r.content && this.replaceByContent(n.content, r.content));
753
+ }
754
+ }
755
+ for (const s of e.tensions ?? []) {
756
+ const o = `${s.claimA}::${s.claimB}`.toLowerCase();
757
+ this.activeTensions.has(o) || this.addTension({
758
+ id: o,
759
+ status: "active",
760
+ claimA: { source: "user", statement: s.claimA, timestamp: Date.now() },
761
+ claimB: { source: "conversation", statement: s.claimB, timestamp: Date.now() },
762
+ impact: s.impact,
763
+ taskRelevance: 1,
764
+ actionableQuestion: s.actionableQuestion
765
+ });
766
+ }
767
+ }
768
+ /** Drop an entry by its exact content. */
769
+ removeByContent(e) {
770
+ const t = e.trim().toLowerCase();
771
+ for (const s of [this.l1HotCache, this.l2WarmStore, this.l3ColdArchive])
772
+ for (const [o, n] of s)
773
+ n.content.trim().toLowerCase() === t && s.delete(o);
774
+ }
775
+ /** Rewrite one entry's text in place, matched on its old value. */
776
+ replaceByContent(e, t) {
777
+ const s = e.trim().toLowerCase();
778
+ for (const o of [this.l1HotCache, this.l2WarmStore, this.l3ColdArchive])
779
+ for (const n of o.values())
780
+ n.content.trim().toLowerCase() === s && !b(n.content, t) && this.enrich(n, t);
781
+ }
782
+ /** Replace an entry's content, keeping its identity and tags. */
783
+ enrich(e, t) {
784
+ e.content = t, e.bookmark = t.slice(0, 80), e.metadata.lastAccessedAt = Date.now();
785
+ }
786
+ /**
787
+ * Fold a duplicate into its survivor, refusing rewrites that drop anything.
788
+ *
789
+ * `merge` returns the model's idea of the fuller statement, and overwriting
790
+ * with it deletes whatever the model happened to leave out. The under-merge
791
+ * bias covers merging the *wrong* pair, but nothing covered a lossy rewrite
792
+ * of the right pair, so containment is checked first.
793
+ *
794
+ * @returns false when the rewrite would lose information, in which case the
795
+ * caller keeps both entries.
796
+ */
797
+ mergeInto(e, t, s) {
798
+ const o = t?.trim();
799
+ return o && (b(e.content, o) || b(s.text, o)) ? !1 : (s.stored && this.removeByContent(s.text), o && this.enrich(e, o), !0);
800
+ }
801
+ /** Retire the entries a replacement supersedes, keeping them out of retrieval. */
802
+ supersede(e) {
803
+ const t = new Set(e.map((s) => s.trim().toLowerCase()));
804
+ for (const s of [this.l1HotCache, this.l2WarmStore, this.l3ColdArchive])
805
+ for (const [o, n] of s)
806
+ t.has(n.content.trim().toLowerCase()) && s.delete(o);
807
+ }
808
+ /**
809
+ * Existing memories worth adjudicating a candidate against.
810
+ *
811
+ * Cheap and deliberately over-inclusive. An identical string short-circuits
812
+ * because it is never worth a model call; anything else above the recall floor
813
+ * is offered to the adjudicator, which decides.
814
+ */
815
+ recallSimilar(e) {
816
+ const t = p(e);
817
+ return t.size === 0 ? [] : [
818
+ ...this.l1HotCache.values(),
819
+ ...this.l2WarmStore.values(),
820
+ ...this.l3ColdArchive.values()
821
+ ].map((n) => {
822
+ const r = p(n.content);
823
+ let a = 0;
824
+ for (const l of t) r.has(l) && (a += 1);
825
+ return { item: n, exact: n.content.trim().toLowerCase() === e.trim().toLowerCase(), score: r.size ? a / Math.min(t.size, r.size) : 0 };
826
+ }).filter((n) => n.exact || n.score >= G).sort((n, r) => Number(r.exact) - Number(n.exact) || r.score - n.score).slice(0, Q).map((n) => n.item);
827
+ }
828
+ /**
829
+ * Keep the pinned tiers within budget.
830
+ *
831
+ * `maxL0Tokens`/`maxL1Tokens` were stored but never read, so L1 grew for the
832
+ * lifetime of the process. Evict least-recently-accessed first, and demote to
833
+ * L2 rather than dropping, so nothing is lost.
834
+ */
835
+ enforceBudgets() {
836
+ ((t, s) => {
837
+ if (s <= 0) return;
838
+ let o = 0;
839
+ for (const r of t.values()) o += r.content.length / 4;
840
+ if (o <= s) return;
841
+ const n = [...t.values()].sort(
842
+ (r, a) => r.metadata.lastAccessedAt - a.metadata.lastAccessedAt
843
+ );
844
+ for (const r of n) {
845
+ if (o <= s) break;
846
+ t.delete(r.id), o -= r.content.length / 4, this.l2WarmStore.set(r.id, { ...r, tier: "L2" });
847
+ }
848
+ })(this.l1HotCache, this.maxL1Tokens);
849
+ }
850
+ /**
851
+ * Add a known tension manually or from an external source.
852
+ */
853
+ addTension(e) {
854
+ this.activeTensions.set(e.id, e), this.stats.tensionsDetected += 1;
855
+ }
856
+ /**
857
+ * Mark a tension resolved with an optional reusable pattern.
858
+ */
859
+ resolveTension(e, t) {
860
+ const s = this.activeTensions.get(e);
861
+ return s ? (s.status = "resolved", s.resolution = {
862
+ resolvedAt: Date.now(),
863
+ resolvedBy: t.resolvedBy,
864
+ pattern: t.pattern
865
+ }, !0) : !1;
866
+ }
867
+ /**
868
+ * Add a memory directly to L2 warm storage or promote to L1.
869
+ */
870
+ addMemory(e, t = "L2") {
871
+ e.tier = t, t === "L1" ? this.l1HotCache.set(e.id, e) : t === "L2" ? this.l2WarmStore.set(e.id, e) : t === "L3" && this.l3ColdArchive.set(e.id, e);
872
+ }
873
+ /**
874
+ * Drop one memory by id, from whichever tier holds it.
875
+ *
876
+ * The counterpart to `addMemory`, and the only way to retire a fact that has
877
+ * turned out to be wrong. One map is not enough to look in: a promotion or a
878
+ * demotion moves an id between them, so an id that is absent from the hot
879
+ * cache may still be in the warm store or the archive. Short-circuiting on the
880
+ * first hit is therefore safe — an id lives in exactly one of the three.
881
+ */
882
+ removeMemory(e) {
883
+ return this.l1HotCache.delete(e) || this.l2WarmStore.delete(e) || this.l3ColdArchive.delete(e);
884
+ }
885
+ /**
886
+ * Update the proprioceptive capability record for a domain.
887
+ */
888
+ recordDomainOutcome(e, t, s) {
889
+ const o = this.selfModel.domains[e] ?? {
890
+ reliabilityScore: 0.8,
891
+ sampleCount: 0,
892
+ knownFailurePatterns: [],
893
+ recommendedStrategies: []
894
+ }, n = o.sampleCount + 1, r = 1 / Math.min(n, 10), a = t ? 1 : 0, l = o.reliabilityScore * (1 - r) + a * r;
895
+ !t && s && !o.knownFailurePatterns.includes(s) && o.knownFailurePatterns.push(s), this.selfModel.domains[e] = {
896
+ reliabilityScore: Number(l.toFixed(3)),
897
+ sampleCount: n,
898
+ knownFailurePatterns: o.knownFailurePatterns,
899
+ recommendedStrategies: o.recommendedStrategies
900
+ };
901
+ }
902
+ getSnapshot() {
903
+ return {
904
+ l0: {
905
+ tensions: Array.from(this.activeTensions.values()),
906
+ selfModel: JSON.parse(JSON.stringify(this.selfModel)),
907
+ activeTaskTrace: this.activeTaskTrace
908
+ },
909
+ l1: Array.from(this.l1HotCache.values()),
910
+ l2: Array.from(this.l2WarmStore.values()),
911
+ l3: Array.from(this.l3ColdArchive.values()),
912
+ stats: { ...this.stats }
913
+ };
914
+ }
915
+ loadSnapshot(e) {
916
+ this.activeTensions.clear();
917
+ for (const t of e.l0.tensions)
918
+ this.activeTensions.set(t.id, t);
919
+ this.selfModel = e.l0.selfModel, this.activeTaskTrace = e.l0.activeTaskTrace, this.l1HotCache.clear();
920
+ for (const t of e.l1)
921
+ this.l1HotCache.set(t.id, t);
922
+ this.l2WarmStore.clear();
923
+ for (const t of e.l2)
924
+ this.l2WarmStore.set(t.id, t);
925
+ this.l3ColdArchive.clear();
926
+ for (const t of e.l3)
927
+ this.l3ColdArchive.set(t.id, t);
928
+ this.stats = { ...e.stats };
929
+ }
930
+ applyArbiterDecision(e) {
931
+ for (const t of e.promotions) {
932
+ const s = this.l2WarmStore.get(t.memoryId) ?? this.l3ColdArchive.get(t.memoryId);
933
+ s && (s.tier = "L1", s.metadata.lastAccessedAt = Date.now(), s.metadata.accessCount += 1, this.l1HotCache.set(s.id, s), this.l2WarmStore.delete(s.id), this.l3ColdArchive.delete(s.id));
934
+ }
935
+ for (const t of e.demotions) {
936
+ const s = this.l1HotCache.get(t.memoryId);
937
+ s && (s.tier = "L2", this.l2WarmStore.set(s.id, s), this.l1HotCache.delete(s.id));
938
+ }
939
+ for (const t of e.detectedTensions) {
940
+ const s = `tension-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
941
+ this.addTension({
942
+ id: s,
943
+ status: "active",
944
+ claimA: { source: "current conversation", statement: t.claimA, timestamp: Date.now() },
945
+ claimB: { source: "known state / files", statement: t.claimB, timestamp: Date.now() },
946
+ impact: t.impact,
947
+ taskRelevance: 1,
948
+ actionableQuestion: t.actionableQuestion
949
+ });
950
+ }
951
+ if (e.selfModelUpdate) {
952
+ const { domain: t, success: s, failurePatternObserved: o } = e.selfModelUpdate;
953
+ s !== void 0 && this.recordDomainOutcome(t, s, o);
954
+ }
955
+ }
956
+ /**
957
+ * Fast default heuristic arbiter when an LLM arbiter model is not supplied.
958
+ */
959
+ runHeuristicArbiter(e, t) {
960
+ const s = [], o = [], n = g(`${e.userMessage} ${e.assistantResponse}`), r = t.map((a) => ({
961
+ candidate: a,
962
+ score: v(
963
+ n,
964
+ g(`${a.bookmark} ${a.domains.join(" ")}`)
965
+ )
966
+ })).filter((a) => a.score > Z).sort((a, l) => l.score - a.score).slice(0, 3);
967
+ for (const { candidate: a, score: l } of r)
968
+ s.push({
969
+ memoryId: a.id,
970
+ targetTier: "L1",
971
+ signalType: "anticipatory",
972
+ urgency: Math.min(1, l)
973
+ });
974
+ for (const a of this.l1HotCache.values())
975
+ !(v(n, g(`${a.bookmark} ${a.metadata.domains.join(" ")}`)) > 0) && this.l1HotCache.size > te && o.push({
976
+ memoryId: a.id,
977
+ targetTier: "L2",
978
+ reason: "Not referenced in recent turns"
979
+ });
980
+ return process.env.NAH_MEMORY_DEBUG && console.log(
981
+ ` [arbiter] scored=${JSON.stringify(r.map((a) => ({ id: a.candidate.id, score: Number(a.score.toFixed(2)) })))} promotions=${s.length} demotions=${o.length}`
982
+ ), { promotions: s, demotions: o, pins: [], detectedTensions: [] };
983
+ }
984
+ autoExtractTurnMemory(e, t) {
985
+ const s = e.match(/(?:always|never|make sure to|remember to|use)\s+([^\.\n]+)/i);
986
+ if (s && s[1]) {
987
+ const o = `mem-pref-${Date.now()}`, n = s[1].trim();
988
+ if (A(`User preference: ${n}`)) return;
989
+ this.addMemory({
990
+ id: o,
991
+ content: `User preference: ${n}`,
992
+ bookmark: `User preference: ${n.slice(0, 80)}`,
993
+ tier: "L1",
994
+ // Pre-stage directly into hot cache
995
+ metadata: {
996
+ domains: this.selfModel.activeDomains,
997
+ createdAt: Date.now(),
998
+ lastAccessedAt: Date.now(),
999
+ accessCount: 1
1000
+ }
1001
+ }, "L1");
1002
+ }
1003
+ }
1004
+ }
1005
+ const se = /\bhttps?:\/\/[^\s<>()[\]"'`]+/g, oe = /\b(always|never|must|should|make sure to|remember to)\b[\s:]+([^.?!\n]{4,160})/gi, ne = /\b([A-Za-z][\w ()-]{2,60}?)\s+(?:is|are)\s+([^\n?!]{3,200})/g, ie = /[A-Z0-9_./:-]/, re = (i) => {
1006
+ const e = i.trim();
1007
+ return e.length < 3 || e.length > 200 || /^(not|nothing|unclear|unknown|the same|that|this|it)\b/i.test(e) ? !1 : ie.test(e);
1008
+ }, ae = (i) => {
1009
+ const e = /\.(?=\s+[A-Z(])/.exec(i);
1010
+ return e ? i.slice(0, e.index) : i;
1011
+ }, w = (i) => ae(i).replace(/\s+/g, " ").replace(/^(?:that|the|a|an|it|this)\s+/i, "").replace(/[,;:\s.。]+$/, "").trim(), we = (i) => {
1012
+ const e = [], t = (o) => {
1013
+ const n = w(o);
1014
+ n.length < 8 || n.length > 300 || /^(yes|no|ok|okay|sure|thanks|thank you|got it|noted)\b/i.test(n) || e.some((r) => r.content.toLowerCase() === n.toLowerCase()) || e.push({ content: n, domains: [] });
1015
+ };
1016
+ for (const o of i.match(se) ?? [])
1017
+ t(`User-provided URL: ${o}`);
1018
+ for (const o of i.matchAll(oe)) {
1019
+ const n = (o[1] ?? "").toLowerCase(), r = w(o[2] ?? "");
1020
+ r && t(`User requirement (${n}): ${r}`);
1021
+ }
1022
+ for (const o of i.matchAll(ne)) {
1023
+ const n = w(o[1] ?? ""), r = w(o[2] ?? "");
1024
+ !n || !re(r) || t(`${n} is ${r}`);
1025
+ }
1026
+ return e.filter(
1027
+ (o, n) => !e.some((r, a) => a === n || r.content.length <= o.content.length ? !1 : ce(o.content, r.content))
1028
+ ).slice(0, 6);
1029
+ }, ce = (i, e) => {
1030
+ const t = new Set(e.toLowerCase().split(/[^a-z0-9]+/).filter((o) => o.length >= 4)), s = i.toLowerCase().split(/[^a-z0-9]+/).filter((o) => o.length >= 4);
1031
+ return s.length > 0 && s.every((o) => t.has(o));
1032
+ };
1033
+ export {
1034
+ G as CANDIDATE_FLOOR,
1035
+ K as COLLAPSE_THRESHOLD,
1036
+ U as Cogmem,
1037
+ ge as CognitiveMemory,
1038
+ T as CognitiveMemoryError,
1039
+ R as ContextResource,
1040
+ Q as MAX_CANDIDATES,
1041
+ I as MemoriesResource,
1042
+ Z as PROMOTE_THRESHOLD,
1043
+ O as RecallResource,
1044
+ j as SelfModelResource,
1045
+ H as StatsResource,
1046
+ z as TensionsResource,
1047
+ N as TurnsResource,
1048
+ he as createClient,
1049
+ L as createHttpClient,
1050
+ p as distinctiveTokens,
1051
+ ee as estimateTokens,
1052
+ we as extractDeterministic,
1053
+ k as extractDomains,
1054
+ fe as extractIdentifiers,
1055
+ P as fetchHealth,
1056
+ $ as gistOf,
1057
+ A as isInteractionScoped,
1058
+ b as isLossyRewrite,
1059
+ pe as normalise,
1060
+ v as overlapScore,
1061
+ me as recallOrExplain,
1062
+ g as relevanceTokens,
1063
+ F as runFastGate,
1064
+ de as runTurn,
1065
+ ue as seedMemories,
1066
+ X as similarity
1067
+ };
1068
+ //# sourceMappingURL=cognitive-memory.js.map