@wierdbytes/pi-memory 0.1.0 → 0.1.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/index.js +13 -426
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.1] - 2026-11-10
4
+
5
+ ### Removed
6
+
7
+ - `PI_DREAM_CHILD` no longer disables the extension (pi-dream is retired); use `WMEM_DISABLE=1`.
8
+
3
9
  ## [0.1.0] - 2026-11-10
4
10
 
5
11
  ### Added
package/index.js CHANGED
@@ -90,7 +90,7 @@ ${section}
90
90
  }
91
91
 
92
92
  // packages/pi/runtime.ts
93
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
93
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
94
94
  import { join as join3 } from "node:path";
95
95
  // packages/core/src/api.ts
96
96
  var INBOX_KINDS = ["preference", "procedure", "pitfall", "fact", "decision", "reference", "other"];
@@ -100,7 +100,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
100
100
  // packages/core/src/layout.ts
101
101
  import { homedir } from "node:os";
102
102
  import { join, resolve } from "node:path";
103
- var GLOBAL_SCOPE = "global";
104
103
  function expandHome(p) {
105
104
  if (p === "~")
106
105
  return homedir();
@@ -238,244 +237,12 @@ function loadConfig(path = homePaths().config) {
238
237
  function serverUrl(cfg = loadConfig()) {
239
238
  return (process.env.WMEM_URL || `http://127.0.0.1:${cfg.port}`).replace(/\/+$/, "");
240
239
  }
241
- // packages/core/src/notes.ts
242
- import { randomBytes } from "node:crypto";
243
- function newNoteId(taken) {
244
- for (;; ) {
245
- const id = `m-${randomBytes(3).toString("hex")}`;
246
- if (!taken.has(id)) {
247
- taken.add(id);
248
- return id;
249
- }
250
- }
251
- }
252
- function cloneNote(n) {
253
- return {
254
- ...n,
255
- supersedes: [...n.supersedes],
256
- evidence: n.evidence.map((e) => ({ ...e, e: [...e.e] })),
257
- extra: { ...n.extra },
258
- stats: n.stats ? { ...n.stats } : undefined
259
- };
260
- }
261
- function makeNote(p, date) {
262
- return {
263
- id: p.id,
264
- scope: p.scope,
265
- title: p.title,
266
- type: p.type ?? "fact",
267
- status: p.status ?? "active",
268
- index: p.index ?? "none",
269
- when: p.when || undefined,
270
- body: p.body ?? "",
271
- pinned: p.pinned ?? false,
272
- enabled: p.enabled ?? true,
273
- created: p.created || date,
274
- updated: p.updated || p.created || date,
275
- confirmed: p.confirmed || undefined,
276
- validUntil: p.validUntil || undefined,
277
- supersededBy: p.supersededBy || undefined,
278
- supersedes: p.supersedes ?? [],
279
- evidence: p.evidence ?? [],
280
- extra: p.extra ?? {},
281
- stats: p.stats
282
- };
283
- }
284
- var NOTE_COLUMNS = [
285
- "scope",
286
- "title",
287
- "type",
288
- "status",
289
- "index",
290
- "when",
291
- "body",
292
- "pinned",
293
- "enabled",
294
- "created",
295
- "updated",
296
- "confirmed",
297
- "validUntil",
298
- "supersededBy",
299
- "supersedes",
300
- "evidence",
301
- "extra"
302
- ];
303
- function canonical(v) {
304
- if (v === undefined || v === null)
305
- return "null";
306
- if (Array.isArray(v))
307
- return `[${v.map(canonical).join(",")}]`;
308
- if (typeof v === "object") {
309
- const o = v;
310
- return `{${Object.keys(o).filter((k) => o[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(",")}}`;
311
- }
312
- return JSON.stringify(v);
313
- }
314
- function columnValue(n, c) {
315
- const v = n[c];
316
- if (c === "evidence")
317
- return canonical(v.map((e) => ({ s: e.s, e: e.e, date: e.date, at: e.at, kind: e.kind })));
318
- if (v === undefined || v === null || v === "")
319
- return "null";
320
- if (typeof v === "string")
321
- return JSON.stringify(c === "body" || c === "title" ? v.trim() : v);
322
- return canonical(v);
323
- }
324
- function changedColumns(a, b) {
325
- return NOTE_COLUMNS.filter((c) => columnValue(a, c) !== columnValue(b, c));
326
- }
327
- function sameNote(a, b) {
328
- if (!a || !b)
329
- return a === b;
330
- return a.id === b.id && changedColumns(a, b).length === 0;
331
- }
332
240
  // packages/core/src/digest.ts
333
241
  import { homedir as homedir2 } from "node:os";
334
-
335
- // packages/core/src/sessions.ts
336
- import { createReadStream, existsSync as existsSync2, readdirSync, statSync } from "node:fs";
337
- import { createInterface } from "node:readline";
338
- function slim(content) {
339
- if (!Array.isArray(content))
340
- return content;
341
- return content.map((b) => {
342
- if (!b || typeof b !== "object")
343
- return b;
344
- const t = b.type;
345
- if (t === "image")
346
- return { type: "image" };
347
- if (t === "thinking" || t === "redacted_thinking")
348
- return { type: "thinking" };
349
- return b;
350
- });
351
- }
352
- function slimEntry(e) {
353
- if (e.message && typeof e.message === "object") {
354
- const m = e.message;
355
- if (m.role === "system")
356
- e.message = { role: "system" };
357
- else if ("content" in m)
358
- m.content = slim(m.content);
359
- }
360
- if (e.type === "custom_message" && "content" in e)
361
- e.content = slim(e.content);
362
- if (e.type === "compaction")
363
- delete e.systemMessage;
364
- return e;
365
- }
366
- async function parseSession(file) {
367
- const entries = new Map;
368
- let header;
369
- let lastId;
370
- let name;
371
- const rl = createInterface({ input: createReadStream(file, { encoding: "utf8" }), crlfDelay: Infinity });
372
- for await (const line of rl) {
373
- if (!line)
374
- continue;
375
- let e;
376
- try {
377
- e = JSON.parse(line);
378
- } catch {
379
- continue;
380
- }
381
- if (e.type === "session") {
382
- header = e;
383
- continue;
384
- }
385
- if (typeof e.id !== "string")
386
- continue;
387
- entries.set(e.id, slimEntry(e));
388
- lastId = e.id;
389
- if (e.type === "session_info" && typeof e.name === "string")
390
- name = e.name;
391
- }
392
- if (!header)
393
- return;
394
- const branch = [];
395
- const seen = new Set;
396
- let cur = lastId ? entries.get(lastId) : undefined;
397
- while (cur && !seen.has(cur.id)) {
398
- seen.add(cur.id);
399
- branch.push(cur);
400
- cur = cur.parentId ? entries.get(cur.parentId) : undefined;
401
- }
402
- branch.reverse();
403
- return { file, header, branch, name, lastEntryId: lastId };
404
- }
405
- class SessionCache {
406
- max;
407
- map = new Map;
408
- constructor(max = 8) {
409
- this.max = max;
410
- }
411
- get(file) {
412
- let p = this.map.get(file);
413
- if (p) {
414
- this.map.delete(file);
415
- this.map.set(file, p);
416
- return p;
417
- }
418
- p = parseSession(file);
419
- this.map.set(file, p);
420
- while (this.map.size > this.max)
421
- this.map.delete(this.map.keys().next().value);
422
- return p;
423
- }
424
- }
425
-
426
- // packages/core/src/digest.ts
427
242
  var HOME_PATH_RE = new RegExp(`(?:^|[\\s'"=(:])((?:~|/Users/[^/\\s'"]+|/home/[^/\\s'"]+|${homedir2().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})/[^\\s'"\`;|&)<>]+)`, "g");
428
243
  var SESSION_EVENT = "wierd-memory";
429
244
  // packages/core/src/project.ts
430
245
  var cache = new Map;
431
- // packages/core/src/projects.ts
432
- class ProjectMap {
433
- roots;
434
- byKey = new Map;
435
- constructor(entries) {
436
- const byRoot = new Map;
437
- for (const e of entries)
438
- if (e.root && e.key && e.root !== "/")
439
- byRoot.set(e.root.replace(/\/+$/, ""), { key: e.key, project: e.project ?? true });
440
- const roots = [...byRoot.entries()].map(([root, v]) => ({ root, key: v.key, project: v.project, broad: false }));
441
- for (const r of roots)
442
- r.broad = roots.some((o) => o !== r && o.root.startsWith(`${r.root}/`));
443
- roots.sort((a, b) => b.root.length - a.root.length);
444
- this.roots = roots;
445
- for (const r of roots)
446
- if (!this.byKey.has(r.key) || !r.broad)
447
- this.byKey.set(r.key, r);
448
- }
449
- projectOf(path) {
450
- return this.roots.find((r) => path === r.root || path.startsWith(`${r.root}/`));
451
- }
452
- rootOf(key) {
453
- return this.byKey.get(key);
454
- }
455
- isBroad(key) {
456
- return this.byKey.get(key)?.broad ?? false;
457
- }
458
- isProject(key) {
459
- const r = this.byKey.get(key);
460
- return !!r && r.project && !r.broad;
461
- }
462
- touched(paths) {
463
- const counts = new Map;
464
- for (const [p, n] of paths) {
465
- const r = this.projectOf(p);
466
- if (r && !r.broad && r.project)
467
- counts.set(r.key, (counts.get(r.key) ?? 0) + n);
468
- }
469
- return [...counts.entries()].map(([key, n]) => ({ key, n })).sort((a, b) => b.n - a.n);
470
- }
471
- primary(cwdKey, paths) {
472
- const touched = this.touched(paths);
473
- const workspace = this.isBroad(cwdKey);
474
- if (!workspace)
475
- return { key: cwdKey, touched, workspace };
476
- return { key: touched[0]?.key ?? cwdKey, touched, workspace };
477
- }
478
- }
479
246
  // packages/core/src/prompts.ts
480
247
  var OPS_SCHEMA = `Operations — each op has "ref" (unique, e.g. "op1"), "rationale" (why a future session needs this and how we know it is true) and "evidence" (a list of {"s": sessionId | "inbox:<id>", "e": [entryIds]} copied from the input; never invent ids). "scope" is "global", "project" (the project of this pass) or "project:<key>" (another project from the input):
481
248
  - {"op":"add","scope":"…","type":"preference|procedure|pitfall|fact|decision|reference","title":"…","body":"…","when":"…","index":"always|pointer|none", …}
@@ -536,190 +303,10 @@ var MINUTE = 60000;
536
303
  var HOUR = 60 * MINUTE;
537
304
  var DAY = 24 * HOUR;
538
305
  // packages/core/src/search.ts
539
- var RU_PREFIXES = ["пере", "под", "при", "про", "раз", "рас", "за", "вы", "по", "на", "от", "до", "об", "вз", "из"];
540
306
  var STOP = new Set("и в во не что он на я с со как а то все она так его но да ты к у же вы за бы по только ее мне было вот от меня еще нет о из ему теперь когда даже ну вдруг ли если уже или ни быть был него до вас нибудь опять уж вам ведь там потом себя ничего ей может они тут где есть надо ней для мы тебя их чем была сам чтоб без будто чего раз тоже себе под будет ж тогда кто этот того потому этого какой совсем ним здесь этом один почти мой тем чтобы нее сейчас были куда зачем всех никогда можно при наконец два об другой хоть после над больше тот через эти нас про всего них какая много разве три эту моя впрочем хорошо свою этой перед иногда лучше чуть том нельзя такой им более всегда конечно всю между the a an and or of to in on for is it this that with be as at by from are was were has have had not but if then so do does did can will just".split(" "));
541
- function ruForms(word) {
542
- if (word.length <= 4)
543
- return [word];
544
- const out = [word.slice(0, 4)];
545
- if (word.length >= 6)
546
- out.push(word.slice(0, 6));
547
- for (const p of RU_PREFIXES) {
548
- if (word.startsWith(p) && word.length - p.length >= 5) {
549
- const rest = word.slice(p.length);
550
- out.push(rest.slice(0, 4));
551
- if (rest.length >= 6)
552
- out.push(rest.slice(0, 6));
553
- break;
554
- }
555
- }
556
- return out;
557
- }
558
- function enStem(word) {
559
- if (word.length > 4) {
560
- for (const suf of ["ing", "ed", "es", "s"])
561
- if (word.endsWith(suf) && word.length - suf.length >= 3)
562
- return word.slice(0, -suf.length);
563
- }
564
- return word;
565
- }
566
- function tokenize(text) {
567
- const out = [];
568
- for (const raw of text.toLowerCase().replace(/ё/g, "е").split(/[^\p{L}\p{N}_]+/u)) {
569
- if (raw.length < 2 || STOP.has(raw))
570
- continue;
571
- if (/^[а-я]+$/.test(raw))
572
- out.push(...ruForms(raw));
573
- else if (/^[a-z]+$/.test(raw))
574
- out.push(enStem(raw));
575
- else
576
- out.push(raw);
577
- }
578
- return out;
579
- }
580
-
581
- class Bm25 {
582
- docs;
583
- k1;
584
- b;
585
- postings = new Map;
586
- lengths = [];
587
- avg = 0;
588
- constructor(docs, k1 = 1.2, b = 0.75) {
589
- this.docs = docs;
590
- this.k1 = k1;
591
- this.b = b;
592
- let total = 0;
593
- docs.forEach((doc, i) => {
594
- const tf = new Map;
595
- const toks = tokenize(doc.text);
596
- for (const t of toks)
597
- tf.set(t, (tf.get(t) ?? 0) + 1);
598
- for (const [t, n] of tf) {
599
- let list = this.postings.get(t);
600
- if (!list)
601
- this.postings.set(t, list = []);
602
- list.push([i, n]);
603
- }
604
- this.lengths.push(toks.length);
605
- total += toks.length;
606
- });
607
- this.avg = docs.length ? total / docs.length : 0;
608
- }
609
- search(query, limit = 10, filter) {
610
- const terms = [...new Set(tokenize(query))];
611
- if (!terms.length || !this.docs.length)
612
- return [];
613
- const scores = new Map;
614
- const N = this.docs.length;
615
- for (const t of terms) {
616
- const list = this.postings.get(t);
617
- if (!list)
618
- continue;
619
- const idf = Math.log(1 + (N - list.length + 0.5) / (list.length + 0.5));
620
- for (const [i, tf] of list) {
621
- const len = this.lengths[i] || 1;
622
- const s = idf * tf * (this.k1 + 1) / (tf + this.k1 * (1 - this.b + this.b * len / (this.avg || 1)));
623
- scores.set(i, (scores.get(i) ?? 0) + s);
624
- }
625
- }
626
- return [...scores.entries()].filter(([i]) => !filter || filter(this.docs[i])).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([i, score]) => ({ doc: this.docs[i], score }));
627
- }
628
- }
629
- // packages/core/src/workspace.ts
630
- class Workspace {
631
- date;
632
- notes = new Map;
633
- ids = new Set;
634
- pending = new Set;
635
- skills = new Map;
636
- original = new Map;
637
- dirty = new Set;
638
- gone = new Map;
639
- metas = new Map;
640
- known = new Set;
641
- constructor(notes, date, opts = {}) {
642
- this.date = date;
643
- for (const n of notes) {
644
- const copy = cloneNote(n);
645
- this.scope(copy.scope).push(copy);
646
- this.ids.add(copy.id);
647
- this.original.set(copy.id, cloneNote(n));
648
- }
649
- for (const s of opts.scopes ?? []) {
650
- this.known.add(s.id);
651
- this.scope(s.id);
652
- }
653
- for (const id of opts.pending ?? [])
654
- this.pending.add(id);
655
- this.scope(GLOBAL_SCOPE);
656
- }
657
- scope(scope) {
658
- let list = this.notes.get(scope);
659
- if (!list)
660
- this.notes.set(scope, list = []);
661
- return list;
662
- }
663
- ensureScope(scope, meta) {
664
- this.scope(scope);
665
- if (meta && !this.known.has(scope))
666
- this.metas.set(scope, { id: scope, ...meta });
667
- }
668
- all() {
669
- return [...this.notes.values()].flat();
670
- }
671
- find(id) {
672
- for (const list of this.notes.values())
673
- for (const n of list)
674
- if (n.id === id)
675
- return n;
676
- return;
677
- }
678
- createNote(scope, f, evidence) {
679
- const note = makeNote({ id: newNoteId(this.ids), scope, title: f.title, type: f.type, index: f.index, when: f.when, body: f.body ?? "", evidence: [...evidence], stats: { helpful: 0, harmful: 0 } }, this.date);
680
- this.scope(scope).push(note);
681
- this.pending.add(note.id);
682
- this.dirty.add(note);
683
- return note;
684
- }
685
- touch(n) {
686
- this.dirty.add(n);
687
- }
688
- moveNote(n, scope) {
689
- const from = this.scope(n.scope);
690
- const i = from.indexOf(n);
691
- if (i >= 0)
692
- from.splice(i, 1);
693
- n.scope = scope;
694
- this.scope(scope).push(n);
695
- this.dirty.add(n);
696
- }
697
- removeNote(n) {
698
- const list = this.scope(n.scope);
699
- const i = list.indexOf(n);
700
- if (i >= 0)
701
- list.splice(i, 1);
702
- this.dirty.delete(n);
703
- this.pending.delete(n.id);
704
- const before = this.original.get(n.id);
705
- if (before)
706
- this.gone.set(n.id, before);
707
- }
708
- changes() {
709
- const notes = [];
710
- for (const n of this.dirty) {
711
- const before = this.original.get(n.id);
712
- if (before && sameNote(before, n))
713
- continue;
714
- notes.push({ before, after: cloneNote(n) });
715
- }
716
- const scopes = [...this.metas.values()].filter((m) => this.scope(m.id).length > 0 || [...this.skills.values()].some((s) => s.scope === m.id));
717
- return { notes, removed: [...this.gone.values()], scopes, skills: [...this.skills.values()] };
718
- }
719
- }
720
307
  // packages/pi/client.ts
721
308
  import { execFile, spawn } from "node:child_process";
722
- import { accessSync, closeSync, constants, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync } from "node:fs";
309
+ import { accessSync, closeSync, constants, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync } from "node:fs";
723
310
  import { homedir as homedir3 } from "node:os";
724
311
  import { delimiter, dirname, join as join2, resolve as resolve2 } from "node:path";
725
312
  import { fileURLToPath } from "node:url";
@@ -815,7 +402,7 @@ class Client {
815
402
  }
816
403
  }
817
404
  async function launch() {
818
- if (process.platform === "darwin" && !process.env.WMEM_HOME && existsSync3(PLIST)) {
405
+ if (process.platform === "darwin" && !process.env.WMEM_HOME && existsSync2(PLIST)) {
819
406
  const target = `gui/${process.getuid?.() ?? 501}`;
820
407
  if (await run("launchctl", ["kickstart", `${target}/${LABEL}`]) === 0)
821
408
  return "launchctl kickstart";
@@ -867,13 +454,13 @@ function onPath(name) {
867
454
  function checkoutBin() {
868
455
  const repo = resolve2(dirname(fileURLToPath(import.meta.url)), "..", "..");
869
456
  const bin = join2(repo, "bin", "wmem");
870
- return existsSync3(bin) && existsSync3(join2(repo, "packages", "server", "src", "cli.ts")) ? bin : undefined;
457
+ return existsSync2(bin) && existsSync2(join2(repo, "packages", "server", "src", "cli.ts")) ? bin : undefined;
871
458
  }
872
459
 
873
460
  // packages/pi/runtime.ts
874
461
  var EVENT = SESSION_EVENT;
875
462
  function disabledByEnv() {
876
- return process.env.WMEM_DISABLE === "1" || process.env.WMEM_CHILD === "1" || process.env.PI_DREAM_CHILD === "1";
463
+ return process.env.WMEM_DISABLE === "1" || process.env.WMEM_CHILD === "1";
877
464
  }
878
465
  var why2 = (err) => err instanceof Error ? err.message : String(err);
879
466
 
@@ -949,7 +536,7 @@ class Runtime {
949
536
  }
950
537
  async resourcesFor(cwd) {
951
538
  const info = await Promise.race([this.contextFor(cwd), new Promise((r) => setTimeout(() => r(undefined), 2500))]);
952
- return (info?.skills ?? []).filter((d) => existsSync4(d));
539
+ return (info?.skills ?? []).filter((d) => existsSync3(d));
953
540
  }
954
541
  async guarded(fn) {
955
542
  try {
@@ -968,9 +555,9 @@ class Runtime {
968
555
  return `Memory is unavailable: ${why2(err)}`;
969
556
  }
970
557
  }
971
- search(ctx, query, scope, projects2, limit) {
558
+ search(ctx, query, scope, projects, limit) {
972
559
  return this.guarded(async () => {
973
- const p = new URLSearchParams({ q: query, scope, projects: projects2, cwd: ctx.cwd, limit: String(limit), exclude: ctx.sessionManager.getSessionId() });
560
+ const p = new URLSearchParams({ q: query, scope, projects, cwd: ctx.cwd, limit: String(limit), exclude: ctx.sessionManager.getSessionId() });
974
561
  return (await this.client.get(`/api/search?${p}`)).text;
975
562
  });
976
563
  }
@@ -997,11 +584,11 @@ class Runtime {
997
584
  }
998
585
  async dreamLog(lines = 40) {
999
586
  const s = await this.client.get("/api/dreams?limit=1");
1000
- const run2 = s.running ?? s.runs[0];
1001
- if (!run2)
587
+ const run = s.running ?? s.runs[0];
588
+ if (!run)
1002
589
  return "no dreams yet";
1003
- const text = await this.client.get(`/api/dreams/${run2.id}/log?tail=${lines}`);
1004
- return `dream ${run2.id} (${run2.status}):
590
+ const text = await this.client.get(`/api/dreams/${run.id}/log?tail=${lines}`);
591
+ return `dream ${run.id} (${run.status}):
1005
592
  ${text.trimEnd() || "(empty log)"}`;
1006
593
  }
1007
594
  async notifyReview(ctx) {
@@ -1119,7 +706,7 @@ function registerTools(pi, rt) {
1119
706
 
1120
707
  // packages/pi/index.ts
1121
708
  function piWierdMemory(pi) {
1122
- if (process.env.WMEM_CHILD === "1" || process.env.PI_DREAM_CHILD === "1")
709
+ if (process.env.WMEM_CHILD === "1")
1123
710
  return;
1124
711
  pi.registerFlag("no-memory", {
1125
712
  description: "wierd-memory: no memory injection and no memory tools in this run",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wierdbytes/pi-memory",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Long-term memory for pi: injects the approved wierd-memory index into every session and adds memory_search / memory_read / memory_inbox tools and /memory, /dream, /remember commands. Needs the wierd-memory server.",
5
5
  "type": "module",
6
6
  "files": [