@astrosheep/pi-context 0.25.1 → 0.26.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 (112) hide show
  1. package/README.md +88 -7
  2. package/dist/build-info.json +2 -2
  3. package/dist/extension.js +616 -370
  4. package/dist/src/context/boot.d.ts +24 -0
  5. package/dist/src/context/boot.js +33 -24
  6. package/dist/src/context/budget.d.ts +9 -0
  7. package/dist/src/context/budget.js +19 -15
  8. package/dist/src/context/context-window.d.ts +41 -0
  9. package/dist/src/context/context-window.js +16 -1
  10. package/dist/src/context/prompts.d.ts +20 -0
  11. package/dist/src/context/prompts.js +1 -1
  12. package/dist/src/context/reset-artifacts.d.ts +26 -0
  13. package/dist/src/context/reset-artifacts.js +18 -17
  14. package/dist/src/context/reset-lifecycle.d.ts +89 -0
  15. package/dist/src/context/reset-lifecycle.js +103 -75
  16. package/dist/src/context/runtime.d.ts +3 -0
  17. package/dist/src/context/runtime.js +53 -21
  18. package/dist/src/context/thresholds.d.ts +33 -0
  19. package/dist/src/context/thresholds.js +1 -1
  20. package/dist/src/dream/cli.d.ts +10 -0
  21. package/dist/src/dream/cli.js +1 -1
  22. package/dist/src/dream/doctor.d.ts +2 -0
  23. package/dist/src/dream/doctor.js +6 -2
  24. package/dist/src/dream/gates.d.ts +10 -0
  25. package/dist/src/dream/git.d.ts +21 -0
  26. package/dist/src/dream/lock.d.ts +31 -0
  27. package/dist/src/dream/runner.d.ts +30 -0
  28. package/dist/src/dream/settings.d.ts +16 -0
  29. package/dist/src/history/history-tools.d.ts +2 -0
  30. package/dist/src/history/history.d.ts +57 -0
  31. package/dist/src/index.d.ts +39 -0
  32. package/dist/src/index.js +4 -4
  33. package/dist/src/notes/address.d.ts +26 -0
  34. package/dist/src/notes/address.js +8 -14
  35. package/dist/src/notes/constants.d.ts +3 -0
  36. package/dist/src/notes/constants.js +3 -0
  37. package/dist/src/notes/context.d.ts +10 -0
  38. package/dist/src/notes/context.js +33 -0
  39. package/dist/src/notes/frontmatter.d.ts +46 -0
  40. package/dist/src/notes/frontmatter.js +10 -5
  41. package/dist/src/notes/index.d.ts +4 -0
  42. package/dist/src/notes/index.js +2 -0
  43. package/dist/src/notes/paths.d.ts +21 -0
  44. package/dist/src/notes/paths.js +72 -76
  45. package/dist/src/notes/store.d.ts +94 -0
  46. package/dist/src/notes/store.js +298 -242
  47. package/dist/src/pi/notes/adapter.d.ts +12 -0
  48. package/dist/src/pi/notes/adapter.js +39 -0
  49. package/dist/src/pi/notes/session-replay.d.ts +16 -0
  50. package/dist/src/{notes → pi/notes}/session-replay.js +2 -2
  51. package/dist/src/pi/notes/snapshot.d.ts +33 -0
  52. package/dist/src/{notes/notes-snapshot.js → pi/notes/snapshot.js} +11 -3
  53. package/dist/src/pi/notes/tools.d.ts +2 -0
  54. package/dist/src/{notes → pi/notes}/tools.js +24 -21
  55. package/dist/src/protocol.d.ts +41 -0
  56. package/dist/src/protocol.js +4 -6
  57. package/dist/src/session-reader.d.ts +5 -0
  58. package/dist/src/settings.d.ts +6 -0
  59. package/dist/src/tool-output.d.ts +101 -0
  60. package/dist/src/tool-schema.d.ts +17 -0
  61. package/dist/test/agent-loop.test.d.ts +1 -0
  62. package/dist/test/agent-loop.test.js +318 -19
  63. package/dist/test/boot.integration.test.d.ts +1 -0
  64. package/dist/test/boot.integration.test.js +55 -29
  65. package/dist/test/budget-settings.integration.test.d.ts +1 -0
  66. package/dist/test/budget-settings.integration.test.js +8 -7
  67. package/dist/test/doctor.test.d.ts +1 -0
  68. package/dist/test/doctor.test.js +10 -2
  69. package/dist/test/dream-skill.test.d.ts +1 -0
  70. package/dist/test/dream-skill.test.js +69 -0
  71. package/dist/test/dream.test.d.ts +1 -0
  72. package/dist/test/helpers/extension.d.ts +115 -0
  73. package/dist/test/helpers/extension.js +6 -6
  74. package/dist/test/helpers/notes.d.ts +6 -0
  75. package/dist/test/helpers/notes.js +13 -0
  76. package/dist/test/history.integration.test.d.ts +1 -0
  77. package/dist/test/notes-library.test.d.ts +1 -0
  78. package/dist/test/notes-library.test.js +111 -0
  79. package/dist/test/notes.integration.test.d.ts +1 -0
  80. package/dist/test/notes.integration.test.js +22 -24
  81. package/dist/test/notes.test.d.ts +1 -0
  82. package/dist/test/notes.test.js +137 -7
  83. package/dist/test/reset-lifecycle.test.d.ts +1 -0
  84. package/dist/test/reset-lifecycle.test.js +142 -85
  85. package/docs/architecture.md +8 -8
  86. package/docs/reset-lifecycle.md +63 -79
  87. package/package.json +35 -2
  88. package/playbook.md +33 -32
  89. package/skills/dream/SKILL.md +12 -0
  90. package/src/context/boot.ts +44 -25
  91. package/src/context/budget.ts +25 -17
  92. package/src/context/context-window.ts +16 -1
  93. package/src/context/prompts.ts +2 -2
  94. package/src/context/reset-artifacts.ts +26 -24
  95. package/src/context/reset-lifecycle.ts +117 -111
  96. package/src/context/runtime.ts +50 -22
  97. package/src/context/thresholds.ts +1 -1
  98. package/src/dream/cli.ts +1 -1
  99. package/src/dream/doctor.ts +5 -2
  100. package/src/index.ts +4 -4
  101. package/src/notes/address.ts +9 -15
  102. package/src/notes/constants.ts +3 -0
  103. package/src/notes/context.ts +40 -0
  104. package/src/notes/frontmatter.ts +18 -12
  105. package/src/notes/index.ts +22 -0
  106. package/src/notes/paths.ts +64 -78
  107. package/src/notes/store.ts +308 -244
  108. package/src/pi/notes/adapter.ts +44 -0
  109. package/src/{notes → pi/notes}/session-replay.ts +3 -3
  110. package/src/{notes/notes-snapshot.ts → pi/notes/snapshot.ts} +13 -4
  111. package/src/{notes → pi/notes}/tools.ts +25 -23
  112. package/src/protocol.ts +5 -6
package/README.md CHANGED
@@ -16,9 +16,9 @@ pi -e npm:@astrosheep/pi-context
16
16
 
17
17
  ## What you get
18
18
 
19
- - **`wipe_memory`** — the model can request a fresh context window. The request commits after the complete tool batch; the old conversation stays in the session and remains readable through history_*, but is excluded from the next provider context.
19
+ - **`wipe_memory`** — the model can request a fresh context window after completing a tool batch. Manual `/wipe-memory` and the budget warning instead start a close-out turn: the agent can write notes and use tools over multiple turns, then `wipe_memory` commits the reset or a successful normal stop falls back to one. Abort/error never counts as completion. Raw conversation remains in the session and history_* tools, but is excluded from the next provider context.
20
20
  - **A boot block at every window head** — static once-per-window content (cache-stable) carrying the window identity, the recent-notes index, and a short protocol that teaches the model how to recover: notes for its own bookkeeping, history tools for everything before the reset. The five note homes are read once into that boot's snapshot; a home that is unavailable is omitted without blocking the window, and the boot says that `notes_list` can retry after recovery.
21
- - **Low-budget guidance** — one persisted early warning per window when the estimated remaining budget crosses the reminder line, so the model checkpoints before the lights go out.
21
+ - **Budget close-out** — one early reminder at the configured margin, followed (when automatic compaction is enabled) by a shared hidden warning above Pi's hard reserve. That warning arms the same multi-turn close-out as `/wipe-memory`; the hard reserve remains a separate safety reset.
22
22
  - **`get_context_remaining`** — the live, reserve-adjusted estimate of the context budget left before Pi's compaction reserve.
23
23
  - **Nine history/notes tools** — Codex's History/Notes actions flattened into Pi's single tool namespace; notes are real markdown files under `~/.agents/notes` (`human/`, `project/`, `agents/`, `models/`, `pi/session/`):
24
24
 
@@ -40,9 +40,9 @@ The tool descriptions the model sees are the behavioral documentation: note resu
40
40
 
41
41
  ## Context-window protocol
42
42
 
43
- Each reset appends one `pi-context/reset-marker` custom entry with `{ "windowId": "..." }`, followed by one hidden `pi-context/boot` custom message whose details carry the same `windowId`. The marker is the window boundary and the UUID-like window identity is independent of Pi entry IDs. Native compaction and branch-summary entries are history items inside the current window; they do not create windows.
43
+ Each committed reset first appends a native empty-summary compaction checkpoint with `firstKeptEntryId` set to the checkpoint itself (retain none), then a `pi-context/reset-marker`, hidden `pi-context/boot`, and hidden continuation. The checkpoint lets Pi's persisted canonical projection discard earlier conversation while retaining the system/tool state and empty summary wrapper. The raw session branch and history tools still retain the full conversation. The marker's `windowId` is the durable window identity; legacy marker-only branches remain readable through a narrow compatibility projection.
44
44
 
45
- `/wipe-memory` is the manual reset command. It waits for idle, appends the marker and boot through Pi's public session APIs, and does not call a model. While pi-context is enabled, `/compact` is cancelled with an actionable `/wipe-memory` notice. Automatic resets use the active provider-window usage; mixed tool batches finish before the boundary, and queued steering/follow-up messages are delivered exactly once in the new window. An aborted turn does not manufacture a continuation.
45
+ `/wipe-memory` waits for idle, records the shared hidden close-out warning, and triggers an ordinary model turn. The agent may write notes and use tools over multiple turns; an explicit `wipe_memory` commits after the complete batch, or a successful normal stop commits after queued messages drain. Abort/error does not commit. The budget warning arms this same close-out path; ordinary reminder guidance precedes it, and the hard reserve is a separate safety boundary. Direct `wipe_memory` remains valid without a prior warning. While pi-context is enabled, `/compact` is cancelled with an actionable `/wipe-memory` notice. Close-out requests wait for queued steering/follow-up work in the current window, while explicit tool resets commit after their batch and Pi delivers queued work exactly once. An aborted turn does not manufacture a continuation.
46
46
 
47
47
  History remains available after reset, including earlier windows and raw JSONL. Branch navigation also remains available. If either the source or destination branch contains a reset marker, generated `/tree` summaries are suppressed with a notice because Pi's raw summary generator bypasses the context projection and could reintroduce erased history. Navigation itself is not suppressed; branches without markers retain native summaries.
48
48
 
@@ -57,7 +57,7 @@ The reminder threshold is Pi's compaction reserve plus a margin, configured unde
57
57
  }
58
58
  ```
59
59
 
60
- `reminder = reserveTokens + reminderMarginTokens`; with the defaults the early warning fires 24,576 tokens above Pi's reset line.
60
+ `reminder = reserveTokens + reminderMarginTokens`; with the defaults the early guidance fires 24,576 tokens above Pi's reset line. When automatic compaction is enabled, the shared close-out warning starts at `reserveTokens + 12,288`; the hard reserve is the final safety boundary.
61
61
 
62
62
  The dreamer model is configured under the same key. `--dreamer <model pattern>` on the `dream` CLI wins; otherwise a non-empty `pi-context.dreamer` string from settings applies; otherwise the automatic model is used. An invalid value (empty or not a string) is ignored with one warning.
63
63
 
@@ -67,6 +67,77 @@ The dreamer model is configured under the same key. `--dreamer <model pattern>`
67
67
  }
68
68
  ```
69
69
 
70
+ ## Standalone notes library
71
+
72
+ The same package provides a **Node.js TypeScript library independent of Pi**:
73
+
74
+ ```sh
75
+ npm install @astrosheep/pi-context
76
+ ```
77
+
78
+ ```ts
79
+ import { createNotesStore, type NotesContext } from "@astrosheep/pi-context/notes";
80
+
81
+ const context: NotesContext = {
82
+ home: "/path/to/notes", // explicit filesystem root
83
+ sessionId: "my-session", // safe single directory component
84
+ projectKey: "my-project-a1b2c3d4",
85
+ agent: "my-agent", // canonical lowercase slug
86
+ model: "my-model", // canonical lowercase slug
87
+ };
88
+ const notes = createNotesStore(context);
89
+ await notes.write("@project/decisions.md", "Use a shared notes library.", { origin: "user" });
90
+ await notes.edit("@project/decisions.md", [
91
+ { oldText: "shared", newText: "host-independent" },
92
+ ]);
93
+ const note = await notes.read("@project/decisions.md"); // full text/body/metadata, or undefined
94
+ const files = await notes.list({ pattern: "@project/**" });
95
+ const matches = await notes.search(["library"]);
96
+ ```
97
+
98
+ `/notes` ships JavaScript and TypeScript declarations. It does not import Pi or read `PI_*` environment variables. Pi packages are optional peers: a notes-only installation does not install them. Using the plugin, root SDK entry, or `dream` CLI still requires Pi. This is a filesystem library for Node, not a browser storage API.
99
+
100
+ ### API and identity
101
+
102
+ `createNotesStore(context)` snapshots the five required identity fields; changing the supplied object afterward does not retarget the store. It resolves `home` once, validates identity components, and creates no files until an operation needs to write. Create a new store to change identity. Multiple stores can use independent roots and identities without changing process environment.
103
+
104
+ - `write(address, content, { origin?, stale? }?)` returns `Promise<{ meta }>`. Default origin is `self`; overwriting preserves creation time, existing project ownership, and unknown metadata, and revives stale notes unless `stale: true` is supplied.
105
+ - `read(address)` returns `Promise<{ meta, body, text, resolvedScope } | undefined>`. **Reads update** `lastAccessed` and `accessCount` on disk; `text` includes frontmatter.
106
+ - `edit(address, edits?, { origin?, stale?, replaceAll? }?)` returns `Promise<{ meta, applied, resolvedScope, change }>`. Edits affect the body; metadata-only changes need no edits. Each replacement uses the evolving body in array order; the complete batch is written atomically only after every edit succeeds. `change` is a typed `{ kind, before, after }`: `kind` is `body`, `metadata`, or `file` to identify the diff inputs, or `none` with empty strings when neither body nor origin/stale changed. It is not a rendered diff.
107
+ - `list({ pattern?, scope?, who? }?)` returns `Promise<NoteRow[]>`, sorted by update time descending with address tie-breaking. Rows contain address, scope, virtual path, metadata, body, and body byte size. `scope` narrows the five-home view; `who` names a concrete agent/model home.
108
+ - `search(queries: string[], { pattern?, scope?, who? }?)` returns `Promise<NoteSearchRow[]>`, sorted by address. Matching is case-sensitive literal OR over body lines; matches contain one-based `line`, `text`, and `offsetChars` into the serialized read text. Neither listing nor search increments access metadata.
109
+
110
+ `list` and `search` share the `NotesQuery` type. A merged query uses `{ pattern? }`; a single-home query adds `scope`. Only `scope: "agent" | "model"` accepts `who`. TypeScript rejects combinations such as `{ scope: "project", who: "root" }`, and JavaScript callers receive a runtime refusal.
111
+
112
+ The library returns full data, not tool envelopes or paginated/truncated output. `NoteError` exposes the existing named store refusals through `code`, with `lineNumbers` for ambiguous edits and `editIndex` for a failed edit. Runtime API fields and known persisted note metadata use camelCase; Pi tool wire fields such as `updated_at`, `offset_chars`, and `replace_all` retain their established names. A note carrying a known legacy snake_case metadata key is refused with an explicit manual-migration-required error; import and construction never migrate note data. Invalid addresses/identities and filesystem failures reject; only a missing `read` returns `undefined`. Notes remain markdown files with the existing size limits and same-directory atomic rename. Same-file read/modify/write work is serialized by absolute physical filename across all store instances in this process (including `.md` address aliases); symlink/case aliases and cross-process locking are not guaranteed. `list` and `search` asynchronously traverse homes and serialize each discovered file read against pending mutations, but are not global snapshots and may not discover a file created after traversal. Foreign named homes can be read (including the access-metadata update), but their bodies cannot be written or edited through the store. These are cooperative address rules, not an OS security sandbox.
113
+
114
+ Addresses use bare paths, `@project/`, `@human/`, `@self/`, `@model/`, or explicit `@agents/<slug>/` and `@models/<slug>/`. Relative self/model addresses resolve to the supplied identity; listing renders their concrete names. The disk layout remains `pi/session/<sessionId>/`, `project/<projectKey>/`, `human/`, `agents/<agent>/`, and `models/<model>/`. No data migration happens on library import or construction. Notes already using camelCase metadata retain their metadata; known legacy snake_case keys require the root-coordinated manual migration before use. New session notes record the supplied project key.
115
+
116
+ ### Library and plugin boundary
117
+
118
+ Ownership is explicit in the file tree:
119
+
120
+ ```text
121
+ src/
122
+ notes/ # host-independent library
123
+ index.ts # deliberate public exports
124
+ context.ts # explicit identity validation and snapshot
125
+ store.ts # five storage operations
126
+ address.ts # address parsing and matching
127
+ paths.ts # disk layout and project identity
128
+ frontmatter.ts # persisted metadata codec
129
+ constants.ts # storage limits
130
+ pi/notes/ # Pi integration, not part of /notes
131
+ adapter.ts # live identity, root defaults, activation migration
132
+ tools.ts # schemas, diff rendering, output budgets
133
+ snapshot.ts # boot's five-home snapshot
134
+ session-replay.ts # historical Pi session operations
135
+ ```
136
+
137
+ The public runtime exports are `createNotesStore`, `NoteError`, `projectKey(cwd)`, and `slugify(value)`, alongside the API's TypeScript types. The factory and `projectKey` remain synchronous; `projectKey` provides the existing repository/worktree identity algorithm, while `slugify` normalizes an agent/model name. The five store methods return promises and use asynchronous filesystem operations. Path/glob helpers, serialization, validation internals and constants are implementation details, not exported through `/notes`.
138
+
139
+ The Pi adapter supplies the root and live session/project/agent/model identity on each call. Tools and boot use the same storage implementation. Tool schemas, Pi-style edit diff rendering, wire budgets, pagination, boot selection, legacy activation migration, and session replay stay outside the library. There are no parallel legacy store/path adapters. Internal source paths are not the supported library API.
140
+
70
141
  ## SDK integration
71
142
 
72
143
  SDK hosts that create a session directly can bind pi-context to the exact same public `SettingsManager` authority as the session:
@@ -77,8 +148,7 @@ import {
77
148
  DefaultResourceLoader,
78
149
  SettingsManager,
79
150
  } from "@earendil-works/pi-coding-agent";
80
- // The package currently publishes source/dist files without a package main/exports entry.
81
- import { createPiContext } from "@astrosheep/pi-context/dist/src/index.js";
151
+ import { createPiContext } from "@astrosheep/pi-context";
82
152
 
83
153
  const cwd = process.cwd();
84
154
  const agentDir = "/tmp/my-pi-agent";
@@ -102,6 +172,8 @@ const { session } = await createAgentSession({
102
172
  });
103
173
  ```
104
174
 
175
+ The previous `@astrosheep/pi-context/dist/src/index.js` SDK import remains supported. The root entry is Pi-dependent; notes-only consumers should import `/notes` instead.
176
+
105
177
  The manager must be shared by the resource loader's factory and `createAgentSession`. If the host replaces its settings authority, it must create and bind a new `createPiContext({ settingsManager })` factory together with the replacement manager; an existing factory remains bound to the manager it was created with.
106
178
 
107
179
  The default extension export is file-backed: it reads Pi's standard global settings directory plus the trusted project's `.pi/settings.json`, with project values winning per key. It cannot discover an arbitrary SDK session manager from `cwd`, environment variables, session IDs, or private SDK fields. For an injected manager, compaction settings come from the manager's public `getCompactionSettings(model)` getter, including the active model's `modelOverrides`; pi-context margins are read from the public `getGlobalSettings()` and `getProjectSettings()` scopes. Opaque runtime overrides that those public scope getters do not expose are intentionally not treated as pi-context configuration. Live public manager changes apply on the next policy query/turn, and the extension does not drain the manager's settings I/O diagnostics.
@@ -123,6 +195,14 @@ rm "${PI_NOTES_HOME:-$HOME/.agents/notes}/.dream.lock"
123
195
 
124
196
  Removing a lock while a holder is running is outside the supported cooperative protocol and can let two dreams run at once.
125
197
 
198
+ ## Dream skill
199
+
200
+ The package also provides `/skill:dream` for reviewing notes in the current agent session. The skill reads the same `playbook.md` used by the `dream` CLI; there is only one set of dream instructions.
201
+
202
+ Scope comes from the invocation directory: inside the notes store, dream may inspect across homes; in a project, it extracts from that project's material and all session notes with matching project metadata, and organizes only that project's notes. New session notes record their project key in the frontmatter `project` field. Standard linked Git worktrees resolve to the main checkout's repository root, sharing its project key and `@project/` home. Older metadata and homes remain untouched by the code: there is no automatic migration or backfill; existing data can be migrated manually. Notes without project metadata are skipped in project-only discovery. Changing directories later does not broaden the scope.
203
+
204
+ The skill stays in the current agent session. Its wrapper and shared playbook describe the dreamer's task, not runtime setup: the execution entry point is responsible for supplying readable/writable scope and establishing write permission, including locking and Git audit safeguards. The CLI supplies its own safeguards; the bare skill does not install them. Without an established scope and write permission, the dreamer asks rather than improvising runtime setup. Session records and unapproved homes stay untouched.
205
+
126
206
  ## Documentation
127
207
 
128
208
  Implementation architecture and the reset lifecycle live in [docs/](docs/).
@@ -132,6 +212,7 @@ Implementation architecture and the reset lifecycle live in [docs/](docs/).
132
212
  ```sh
133
213
  npm test # build from a clean dist, then run the suite
134
214
  npm run typecheck
215
+ npm run test:notes-package # pack, install without Pi, typecheck and run a consumer
135
216
  ```
136
217
 
137
218
  The harness runs against the real installed Pi `SessionManager`/`SettingsManager` in temporary directories with fake credentials — no model or network calls, and the real `~/.pi` is never touched.
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.25.1",
3
- "sourceHash": "d9c843b30fa0f1426f40b6a054277e757d64b58ede6cb590aada253f34b75a81"
2
+ "version": "0.26.0",
3
+ "sourceHash": "48dcba30fd66f729c046d9ec89214e69ada06592df8d464f3b8cc70562dd5174"
4
4
  }