@astrosheep/pi-context 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/src/budget.js +63 -0
  2. package/dist/src/dream/apply.js +87 -0
  3. package/dist/src/dream/cli.js +82 -0
  4. package/dist/src/dream/gates.js +21 -0
  5. package/dist/src/dream/lock.js +58 -0
  6. package/dist/src/dream/manifest.js +16 -0
  7. package/dist/src/dream/runner.js +56 -0
  8. package/dist/src/history-tools.js +105 -0
  9. package/dist/src/history.js +210 -0
  10. package/dist/src/index.js +99 -0
  11. package/dist/src/memory/frontmatter.js +134 -0
  12. package/dist/src/memory/paths.js +54 -0
  13. package/dist/src/memory/store.js +297 -0
  14. package/dist/src/memory/tools.js +175 -0
  15. package/dist/src/notes.js +101 -0
  16. package/dist/src/prompts.js +79 -0
  17. package/dist/src/protocol.js +52 -0
  18. package/dist/src/reset-lifecycle.js +101 -0
  19. package/dist/src/session-reader.js +1 -0
  20. package/dist/src/thresholds.js +72 -0
  21. package/dist/src/tool-output.js +172 -0
  22. package/dist/src/tool-schema.js +26 -0
  23. package/dist/src/warning.js +44 -0
  24. package/dist/test/agent-loop.test.js +212 -0
  25. package/dist/test/coherence.test.js +371 -0
  26. package/dist/test/dream.test.js +43 -0
  27. package/dist/test/history.test.js +21 -0
  28. package/dist/test/integration.test.js +1716 -0
  29. package/dist/test/memory.test.js +370 -0
  30. package/dist/test/pagination.property.test.js +476 -0
  31. package/dist/test/reset-lifecycle.test.js +199 -0
  32. package/package.json +9 -3
  33. package/playbook.md +5 -0
  34. package/src/dream/apply.ts +47 -0
  35. package/src/dream/cli.ts +33 -0
  36. package/src/dream/gates.ts +19 -0
  37. package/src/dream/lock.ts +39 -0
  38. package/src/dream/manifest.ts +21 -0
  39. package/src/dream/runner.ts +53 -0
  40. package/src/memory/store.ts +15 -0
  41. package/src/memory/tools.ts +12 -3
  42. package/src/protocol.ts +2 -2
@@ -0,0 +1,43 @@
1
+ import test from "node:test";
2
+ import nodeAssert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, readFileSync, readdirSync, statSync, utimesSync, writeFileSync, existsSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ import { defaultDreamerSessionFactory, runDreamer, READ_ONLY_TOOLS } from "../src/dream/runner.js";
8
+ const cli = join(process.cwd(), "dist/src/dream/cli.js");
9
+ const assert = nodeAssert;
10
+ assert.match = (value, expected) => nodeAssert.ok(typeof expected === "string" ? value.includes(expected) : expected.test(value));
11
+ function fixture() { const home = mkdtempSync(join(tmpdir(), "dream-")); mkdirSync(join(home, "pi/session"), { recursive: true }); return home; }
12
+ function note(home, session, name, body, window) { const dir = join(home, "pi/session", session); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, name), `---\nscope: session\norigin: self\nstatus: active\nsource_window: ${window}\ncreated_at: 2020-01-01T00:00:00.000Z\nupdated_at: 2020-01-01T00:00:00.000Z\nlast_accessed: 2020-01-01T00:00:00.000Z\naccess_count: 0\n---\n\n${body}`); }
13
+ function dreamer(home, manifest, sentinel) { const file = join(home, "dreamer.mjs"); writeFileSync(file, `import {writeFileSync} from 'node:fs'; ${sentinel ? `writeFileSync(${JSON.stringify(sentinel)}, 'spawned');` : ""} process.stdin.resume(); process.stdin.on('end',()=>process.stdout.write(${JSON.stringify(JSON.stringify(manifest))}));`); return `node ${file}`; }
14
+ function run(home, extra = []) { return spawnSync(process.execPath, [cli, "--notes-home", home, ...extra], { encoding: "utf8" }); }
15
+ function old(path) { const d = new Date(Date.now() - 48 * 3600_000); utimesSync(path, d, d); }
16
+ test("dream time gate skips first, names reason, and does not spawn", () => { const h = fixture(); const sentinel = join(h, "sentinel"); const lock = join(h, ".dream.lock"); writeFileSync(lock, "999999"); const fresh = new Date(); utimesSync(lock, fresh, fresh); const r = run(h, ["--dreamer", dreamer(h, { report: "x" }, sentinel)]); assert.equal(r.status, 0); assert.match(r.stdout, "time gate"); assert.equal(existsSync(sentinel), false); });
17
+ test("material gate skips when too few session directories changed", () => { const h = fixture(); const lock = join(h, ".dream.lock"); writeFileSync(lock, "999999"); old(lock); note(h, "only", "a.md", "a", "w"); const r = run(h, ["--min-sessions", "3", "--dreamer", dreamer(h, { report: "x" })]); assert.equal(r.status, 0); assert.match(r.stdout, "material gate"); });
18
+ test("force bypasses time and material gates and external dreamer runs", () => { const h = fixture(); const r = run(h, ["--force", "--dreamer", dreamer(h, { report: "forced" })]); assert.equal(r.status, 0); assert.match(readFileSync(readdirSync(join(h, "dreams")).map(x => join(h, "dreams", x))[0], "utf8"), "forced"); });
19
+ test("live PID lock excludes and dead PID is reclaimed", () => { const h = fixture(); const lock = join(h, ".dream.lock"); writeFileSync(lock, String(process.pid)); const r = run(h, ["--force", "--dreamer", dreamer(h, { report: "no" })]); assert.equal(r.status, 0); assert.match(r.stdout, "lock gate"); writeFileSync(lock, "999999"); old(lock); const ok = run(h, ["--force", "--dreamer", dreamer(h, { report: "reclaimed" })]); assert.equal(ok.status, 0); });
20
+ test("external dreamer manifest merges recurrence, proposes global promotion, trashes, and reports", () => { const h = fixture(); note(h, "dream", "a.md", "one", "window-a"); note(h, "dream", "b.md", "two", "window-b"); note(h, "dream", "trash.md", "gone", "window-c"); const m = { merge: [{ into: "a.md", from: ["b.md"] }], promote: [{ path: "a.md", to: "global", reason: "shared" }], trash: [{ path: "trash.md", reason: "obsolete" }], report: "I dreamed on 2026-09-19." }; const r = run(h, ["--force", "--dreamer", dreamer(h, m)]); assert.equal(r.status, 0, r.stderr); const merged = readFileSync(join(h, "pi/session/dream/a.md"), "utf8"); assert.match(merged, "one"); assert.match(merged, "two"); assert.match(merged, "recurrence_count: 1"); assert.match(readFileSync(join(h, "pi/session/dream/b.md"), "utf8"), "status: superseded"); assert.equal(existsSync(join(h, "pi/session/dream/trash.md")), false); const trash = readdirSync(join(h, "trash"))[0]; assert.equal(readFileSync(join(h, "trash", trash, "session", "trash.md"), "utf8").includes("gone"), true); const reports = readdirSync(join(h, "dreams")); const report = readFileSync(join(h, "dreams", reports[0]), "utf8"); assert.match(report, "I dreamed"); assert.match(report, "proposal: promote"); assert.match(report, "trashed"); });
21
+ test("invalid manifest path makes zero writes and exits nonzero", () => { const h = fixture(); note(h, "s1", "a.md", "one", "w"); const before = readFileSync(join(h, "pi/session/s1/a.md"), "utf8"); const r = run(h, ["--force", "--dreamer", dreamer(h, { merge: [{ into: "missing.md", from: ["a.md"] }], report: "bad" })]); assert.notEqual(r.status, 0); assert.match(r.stderr, "missing.md"); assert.equal(readFileSync(join(h, "pi/session/s1/a.md"), "utf8"), before); assert.equal(existsSync(join(h, "dreams")), false); });
22
+ test("failed run restores prior lock mtime", () => { const h = fixture(); const lock = join(h, ".dream.lock"); writeFileSync(lock, "999999"); const prior = Date.now() - 48 * 3600_000; utimesSync(lock, new Date(prior), new Date(prior)); const r = run(h, ["--force", "--dreamer", dreamer(h, { merge: [{ into: "missing", from: [] }], report: "x" })]); assert.notEqual(r.status, 0); assert.ok(Math.abs(statSync(lock).mtimeMs - prior) < 2000); });
23
+ test("SDK dreamer captures assistant event and receives read-only allowlist", async () => {
24
+ let configured = [];
25
+ const session = { subscribe(handler) { this.handler = handler; return () => { }; }, handler: (_event) => { }, async prompt(_text) { this.handler({ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: '{"report":"stub"}' }] } }); }, dispose() { } };
26
+ const manifest = await runDreamer("playbook", "/tmp/notes", { sessionFactory: async (options) => { configured = options.tools; return session; } });
27
+ assert.deepEqual(manifest, { report: "stub" });
28
+ assert.deepEqual(configured, READ_ONLY_TOOLS);
29
+ assert.equal(configured.includes("notes_write"), false);
30
+ assert.equal(configured.includes("notes_edit"), false);
31
+ });
32
+ test("SDK dreamer surfaces provider stopReason error instead of manifest-parse lie", async () => {
33
+ const session = { subscribe(handler) { this.handler = handler; return () => { }; }, handler: (_event) => { }, async prompt(_text) { this.handler({ type: "message_end", message: { role: "assistant", content: [], stopReason: "error", errorMessage: "402: {\"error\":{\"message\":\"Insufficient Balance\"}}" } }); }, dispose() { } };
34
+ await assert.rejects(() => runDreamer("playbook", "/tmp/notes", { sessionFactory: async () => session }), /Insufficient Balance/);
35
+ await assert.rejects(() => runDreamer("playbook", "/tmp/notes", { sessionFactory: async () => session }), /402/);
36
+ });
37
+ test("SDK dreamer parse failure keeps existing manifest message without provider error", async () => {
38
+ const session = { subscribe(handler) { this.handler = handler; return () => { }; }, handler: (_event) => { }, async prompt(_text) { this.handler({ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "sorry, I could not do that" }] } }); }, dispose() { } };
39
+ await assert.rejects(() => runDreamer("playbook", "/tmp/notes", { sessionFactory: async () => session }), /did not return a valid JSON manifest/);
40
+ });
41
+ test("default dreamer rejects an unresolvable model pattern", async () => {
42
+ await assert.rejects(() => defaultDreamerSessionFactory({ cwd: "/tmp/notes", modelPattern: "definitely-not-a-real-model", tools: READ_ONLY_TOOLS }), /definitely-not-a-real-model/);
43
+ });
@@ -0,0 +1,21 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { resetV2WindowId, visibleItem } from "../src/history.js";
4
+ test("visibleItem reports a plain fitting prefix and names the full length", () => {
5
+ const item = visibleItem({ windowId: "w", itemId: "i", role: "user", content: "abcdef", createdAt: undefined }, 4);
6
+ assert.equal(Array.from(item.truncated_content).length, 4);
7
+ assert.equal(item.truncated_content, "abcd");
8
+ assert.equal(item.truncated_content.includes("…"), false, "no marker is appended to the payload");
9
+ assert.equal(item.truncated, true);
10
+ assert.equal(item.total_chars, 6);
11
+ const whole = visibleItem({ windowId: "w", itemId: "i", role: "user", content: "abcdef", createdAt: undefined }, 6);
12
+ assert.equal(whole.truncated, false);
13
+ assert.equal(whole.total_chars, 6);
14
+ assert.equal(whole.truncated_content, "abcdef");
15
+ });
16
+ test("persisted reset IDs are opaque within the supported protocol version", () => {
17
+ assert.equal(resetV2WindowId({ piContext: "reset-v2", windowId: "opaque-window-id" }), "opaque-window-id");
18
+ assert.equal(resetV2WindowId({ piContext: "reset-v1", windowId: "opaque-window-id" }), undefined);
19
+ assert.equal(resetV2WindowId({ piContext: "reset-v2", windowId: 123 }), undefined);
20
+ assert.equal(resetV2WindowId(null), undefined);
21
+ });