@wierdbytes/pi-memory 0.1.0 → 0.1.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.
- package/CHANGELOG.md +12 -0
- package/index.js +27 -434
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.2] - 2026-11-10
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `/dream status`: shows what waits for a decision in the one review (no review ids); needs a wierd-memory server with the `dreams` branch.
|
|
8
|
+
|
|
9
|
+
## [0.1.1] - 2026-11-10
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- `PI_DREAM_CHILD` no longer disables the extension (pi-dream is retired); use `WMEM_DISABLE=1`.
|
|
14
|
+
|
|
3
15
|
## [0.1.0] - 2026-11-10
|
|
4
16
|
|
|
5
17
|
### Added
|
package/index.js
CHANGED
|
@@ -90,7 +90,7 @@ ${section}
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// packages/pi/runtime.ts
|
|
93
|
-
import { existsSync as
|
|
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();
|
|
@@ -123,7 +122,8 @@ function homePaths(home = wmemHome(), memoryDir) {
|
|
|
123
122
|
local,
|
|
124
123
|
state: join(local, "state.json"),
|
|
125
124
|
inbox: join(local, "inbox"),
|
|
126
|
-
|
|
125
|
+
review: join(local, "review.json"),
|
|
126
|
+
decisions: join(local, "decisions.jsonl"),
|
|
127
127
|
runs: join(local, "runs"),
|
|
128
128
|
dreamLogs: join(local, "logs"),
|
|
129
129
|
digests: join(local, "digests"),
|
|
@@ -139,7 +139,7 @@ var STAGES = ["cards", "analyze", "verify"];
|
|
|
139
139
|
function defaults() {
|
|
140
140
|
return {
|
|
141
141
|
port: 7433,
|
|
142
|
-
memory: { dir: "", branch: "main", remote: "origin", sync: true },
|
|
142
|
+
memory: { dir: "", branch: "main", reviewBranch: "dreams", remote: "origin", sync: true },
|
|
143
143
|
language: "Russian",
|
|
144
144
|
inject: true,
|
|
145
145
|
models: {
|
|
@@ -181,10 +181,15 @@ function sanitize(raw) {
|
|
|
181
181
|
cfg.port = Math.round(num(raw.port, 1, 65535, d.port));
|
|
182
182
|
if (isObj(raw.memory)) {
|
|
183
183
|
const m = raw.memory;
|
|
184
|
-
const
|
|
184
|
+
const branchName = (v, fallback) => typeof v === "string" && /^[A-Za-z0-9._/-]{1,100}$/.test(v.trim()) && !v.includes("..") ? v.trim() : fallback;
|
|
185
|
+
const branch = branchName(m.branch, d.memory.branch);
|
|
186
|
+
let reviewBranch = branchName(m.reviewBranch, d.memory.reviewBranch);
|
|
187
|
+
if (reviewBranch === branch)
|
|
188
|
+
reviewBranch = branch === d.memory.reviewBranch ? `${branch}-proposed` : d.memory.reviewBranch;
|
|
185
189
|
cfg.memory = {
|
|
186
190
|
dir: typeof m.dir === "string" ? m.dir.trim() : d.memory.dir,
|
|
187
191
|
branch,
|
|
192
|
+
reviewBranch,
|
|
188
193
|
remote: typeof m.remote === "string" && /^[A-Za-z0-9._-]*$/.test(m.remote.trim()) ? m.remote.trim() : d.memory.remote,
|
|
189
194
|
sync: bool(m.sync, d.memory.sync)
|
|
190
195
|
};
|
|
@@ -238,244 +243,12 @@ function loadConfig(path = homePaths().config) {
|
|
|
238
243
|
function serverUrl(cfg = loadConfig()) {
|
|
239
244
|
return (process.env.WMEM_URL || `http://127.0.0.1:${cfg.port}`).replace(/\/+$/, "");
|
|
240
245
|
}
|
|
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
246
|
// packages/core/src/digest.ts
|
|
333
247
|
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
248
|
var HOME_PATH_RE = new RegExp(`(?:^|[\\s'"=(:])((?:~|/Users/[^/\\s'"]+|/home/[^/\\s'"]+|${homedir2().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})/[^\\s'"\`;|&)<>]+)`, "g");
|
|
428
249
|
var SESSION_EVENT = "wierd-memory";
|
|
429
250
|
// packages/core/src/project.ts
|
|
430
251
|
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
252
|
// packages/core/src/prompts.ts
|
|
480
253
|
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
254
|
- {"op":"add","scope":"…","type":"preference|procedure|pitfall|fact|decision|reference","title":"…","body":"…","when":"…","index":"always|pointer|none", …}
|
|
@@ -523,11 +296,11 @@ var REVIEW = `You are the memory curator of wierd-memory, an offline memory syst
|
|
|
523
296
|
${OPS_SCHEMA}
|
|
524
297
|
|
|
525
298
|
For these operations:
|
|
526
|
-
- Notes marked "pending": true are proposed
|
|
299
|
+
- Notes marked "pending": true are proposed, not approved yet: to drop one use deprecate (the proposal is withdrawn), to change one use update.
|
|
527
300
|
- To move a note to another scope use update with "scope": "global", "project:<key>" (key from the scope titles in the input) or a scope id; the note keeps its id and evidence.
|
|
528
|
-
- A comment with "note" refers to that note; without it, to
|
|
301
|
+
- A comment with "note" refers to that note; without it, to what is proposed as a whole.
|
|
529
302
|
- "scope" may also be a scope id from the input (e.g. "projects/github.com__owner__repo").
|
|
530
|
-
- evidence is the comment: {"s": "
|
|
303
|
+
- evidence is the comment: {"s": "comment:<commentId>", "e": []}.
|
|
531
304
|
- Follow the same writing rules: titles are one self-contained statement in {{language}} (≤ 160 chars), bodies hold the specifics (≤ 120 words).
|
|
532
305
|
- A comment that asks for something you cannot express as an operation goes to "unhandled" with a short reason.
|
|
533
306
|
Reply with JSON only: {"ops": [...], "unhandled": [{"comment": "<commentId>", "reason": "…"}]}`;
|
|
@@ -536,190 +309,10 @@ var MINUTE = 60000;
|
|
|
536
309
|
var HOUR = 60 * MINUTE;
|
|
537
310
|
var DAY = 24 * HOUR;
|
|
538
311
|
// packages/core/src/search.ts
|
|
539
|
-
var RU_PREFIXES = ["пере", "под", "при", "про", "раз", "рас", "за", "вы", "по", "на", "от", "до", "об", "вз", "из"];
|
|
540
312
|
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
313
|
// packages/pi/client.ts
|
|
721
314
|
import { execFile, spawn } from "node:child_process";
|
|
722
|
-
import { accessSync, closeSync, constants, existsSync as
|
|
315
|
+
import { accessSync, closeSync, constants, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync } from "node:fs";
|
|
723
316
|
import { homedir as homedir3 } from "node:os";
|
|
724
317
|
import { delimiter, dirname, join as join2, resolve as resolve2 } from "node:path";
|
|
725
318
|
import { fileURLToPath } from "node:url";
|
|
@@ -815,7 +408,7 @@ class Client {
|
|
|
815
408
|
}
|
|
816
409
|
}
|
|
817
410
|
async function launch() {
|
|
818
|
-
if (process.platform === "darwin" && !process.env.WMEM_HOME &&
|
|
411
|
+
if (process.platform === "darwin" && !process.env.WMEM_HOME && existsSync2(PLIST)) {
|
|
819
412
|
const target = `gui/${process.getuid?.() ?? 501}`;
|
|
820
413
|
if (await run("launchctl", ["kickstart", `${target}/${LABEL}`]) === 0)
|
|
821
414
|
return "launchctl kickstart";
|
|
@@ -867,13 +460,13 @@ function onPath(name) {
|
|
|
867
460
|
function checkoutBin() {
|
|
868
461
|
const repo = resolve2(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
869
462
|
const bin = join2(repo, "bin", "wmem");
|
|
870
|
-
return
|
|
463
|
+
return existsSync2(bin) && existsSync2(join2(repo, "packages", "server", "src", "cli.ts")) ? bin : undefined;
|
|
871
464
|
}
|
|
872
465
|
|
|
873
466
|
// packages/pi/runtime.ts
|
|
874
467
|
var EVENT = SESSION_EVENT;
|
|
875
468
|
function disabledByEnv() {
|
|
876
|
-
return process.env.WMEM_DISABLE === "1" || process.env.WMEM_CHILD === "1"
|
|
469
|
+
return process.env.WMEM_DISABLE === "1" || process.env.WMEM_CHILD === "1";
|
|
877
470
|
}
|
|
878
471
|
var why2 = (err) => err instanceof Error ? err.message : String(err);
|
|
879
472
|
|
|
@@ -949,7 +542,7 @@ class Runtime {
|
|
|
949
542
|
}
|
|
950
543
|
async resourcesFor(cwd) {
|
|
951
544
|
const info = await Promise.race([this.contextFor(cwd), new Promise((r) => setTimeout(() => r(undefined), 2500))]);
|
|
952
|
-
return (info?.skills ?? []).filter((d) =>
|
|
545
|
+
return (info?.skills ?? []).filter((d) => existsSync3(d));
|
|
953
546
|
}
|
|
954
547
|
async guarded(fn) {
|
|
955
548
|
try {
|
|
@@ -968,9 +561,9 @@ class Runtime {
|
|
|
968
561
|
return `Memory is unavailable: ${why2(err)}`;
|
|
969
562
|
}
|
|
970
563
|
}
|
|
971
|
-
search(ctx, query, scope,
|
|
564
|
+
search(ctx, query, scope, projects, limit) {
|
|
972
565
|
return this.guarded(async () => {
|
|
973
|
-
const p = new URLSearchParams({ q: query, scope, projects
|
|
566
|
+
const p = new URLSearchParams({ q: query, scope, projects, cwd: ctx.cwd, limit: String(limit), exclude: ctx.sessionManager.getSessionId() });
|
|
974
567
|
return (await this.client.get(`/api/search?${p}`)).text;
|
|
975
568
|
});
|
|
976
569
|
}
|
|
@@ -997,11 +590,11 @@ class Runtime {
|
|
|
997
590
|
}
|
|
998
591
|
async dreamLog(lines = 40) {
|
|
999
592
|
const s = await this.client.get("/api/dreams?limit=1");
|
|
1000
|
-
const
|
|
1001
|
-
if (!
|
|
593
|
+
const run = s.running ?? s.runs[0];
|
|
594
|
+
if (!run)
|
|
1002
595
|
return "no dreams yet";
|
|
1003
|
-
const text = await this.client.get(`/api/dreams/${
|
|
1004
|
-
return `dream ${
|
|
596
|
+
const text = await this.client.get(`/api/dreams/${run.id}/log?tail=${lines}`);
|
|
597
|
+
return `dream ${run.id} (${run.status}):
|
|
1005
598
|
${text.trimEnd() || "(empty log)"}`;
|
|
1006
599
|
}
|
|
1007
600
|
async notifyReview(ctx) {
|
|
@@ -1024,7 +617,7 @@ ${text.trimEnd() || "(empty log)"}`;
|
|
|
1024
617
|
} catch {}
|
|
1025
618
|
if (last.status === "error")
|
|
1026
619
|
ctx.ui.notify(`wierd-memory: the last dream failed — ${last.error ?? ""} (/dream log)`, "warning");
|
|
1027
|
-
else if (s.review
|
|
620
|
+
else if (s.review.pending)
|
|
1028
621
|
ctx.ui.notify(`wierd-memory: ${s.review.pending} memory change(s) wait for your review — ${s.url}/#/review`, "info");
|
|
1029
622
|
}
|
|
1030
623
|
async status(ctx) {
|
|
@@ -1045,7 +638,7 @@ cwd: ${ctx.cwd}`;
|
|
|
1045
638
|
`inbox: ${s.inbox} pending`,
|
|
1046
639
|
`dream: ${d.running ? `running (${d.running.id})` : `${d.due.due ? "due" : "not due"} (${d.due.reason})`}`,
|
|
1047
640
|
`last: ${d.last ? `${d.last.status} ${d.last.finishedAt ?? d.last.startedAt}${d.last.error ? ` — ${d.last.error}` : ""}` : "never"}`,
|
|
1048
|
-
`review: ${s.review ? `${s.review.pending} to decide — ${s.url}/#/review` : "nothing to
|
|
641
|
+
`review: ${s.review.pending ? `${s.review.pending} to decide — ${s.url}/#/review` : "nothing to decide"}`,
|
|
1049
642
|
`cwd: ${ctx.cwd}`
|
|
1050
643
|
].join(`
|
|
1051
644
|
`);
|
|
@@ -1119,7 +712,7 @@ function registerTools(pi, rt) {
|
|
|
1119
712
|
|
|
1120
713
|
// packages/pi/index.ts
|
|
1121
714
|
function piWierdMemory(pi) {
|
|
1122
|
-
if (process.env.WMEM_CHILD === "1"
|
|
715
|
+
if (process.env.WMEM_CHILD === "1")
|
|
1123
716
|
return;
|
|
1124
717
|
pi.registerFlag("no-memory", {
|
|
1125
718
|
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.
|
|
3
|
+
"version": "0.1.2",
|
|
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": [
|