@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.
- package/README.md +88 -7
- package/dist/build-info.json +2 -2
- package/dist/extension.js +616 -370
- package/dist/src/context/boot.d.ts +24 -0
- package/dist/src/context/boot.js +33 -24
- package/dist/src/context/budget.d.ts +9 -0
- package/dist/src/context/budget.js +19 -15
- package/dist/src/context/context-window.d.ts +41 -0
- package/dist/src/context/context-window.js +16 -1
- package/dist/src/context/prompts.d.ts +20 -0
- package/dist/src/context/prompts.js +1 -1
- package/dist/src/context/reset-artifacts.d.ts +26 -0
- package/dist/src/context/reset-artifacts.js +18 -17
- package/dist/src/context/reset-lifecycle.d.ts +89 -0
- package/dist/src/context/reset-lifecycle.js +103 -75
- package/dist/src/context/runtime.d.ts +3 -0
- package/dist/src/context/runtime.js +53 -21
- package/dist/src/context/thresholds.d.ts +33 -0
- package/dist/src/context/thresholds.js +1 -1
- package/dist/src/dream/cli.d.ts +10 -0
- package/dist/src/dream/cli.js +1 -1
- package/dist/src/dream/doctor.d.ts +2 -0
- package/dist/src/dream/doctor.js +6 -2
- package/dist/src/dream/gates.d.ts +10 -0
- package/dist/src/dream/git.d.ts +21 -0
- package/dist/src/dream/lock.d.ts +31 -0
- package/dist/src/dream/runner.d.ts +30 -0
- package/dist/src/dream/settings.d.ts +16 -0
- package/dist/src/history/history-tools.d.ts +2 -0
- package/dist/src/history/history.d.ts +57 -0
- package/dist/src/index.d.ts +39 -0
- package/dist/src/index.js +4 -4
- package/dist/src/notes/address.d.ts +26 -0
- package/dist/src/notes/address.js +8 -14
- package/dist/src/notes/constants.d.ts +3 -0
- package/dist/src/notes/constants.js +3 -0
- package/dist/src/notes/context.d.ts +10 -0
- package/dist/src/notes/context.js +33 -0
- package/dist/src/notes/frontmatter.d.ts +46 -0
- package/dist/src/notes/frontmatter.js +10 -5
- package/dist/src/notes/index.d.ts +4 -0
- package/dist/src/notes/index.js +2 -0
- package/dist/src/notes/paths.d.ts +21 -0
- package/dist/src/notes/paths.js +72 -76
- package/dist/src/notes/store.d.ts +94 -0
- package/dist/src/notes/store.js +298 -242
- package/dist/src/pi/notes/adapter.d.ts +12 -0
- package/dist/src/pi/notes/adapter.js +39 -0
- package/dist/src/pi/notes/session-replay.d.ts +16 -0
- package/dist/src/{notes → pi/notes}/session-replay.js +2 -2
- package/dist/src/pi/notes/snapshot.d.ts +33 -0
- package/dist/src/{notes/notes-snapshot.js → pi/notes/snapshot.js} +11 -3
- package/dist/src/pi/notes/tools.d.ts +2 -0
- package/dist/src/{notes → pi/notes}/tools.js +24 -21
- package/dist/src/protocol.d.ts +41 -0
- package/dist/src/protocol.js +4 -6
- package/dist/src/session-reader.d.ts +5 -0
- package/dist/src/settings.d.ts +6 -0
- package/dist/src/tool-output.d.ts +101 -0
- package/dist/src/tool-schema.d.ts +17 -0
- package/dist/test/agent-loop.test.d.ts +1 -0
- package/dist/test/agent-loop.test.js +318 -19
- package/dist/test/boot.integration.test.d.ts +1 -0
- package/dist/test/boot.integration.test.js +55 -29
- package/dist/test/budget-settings.integration.test.d.ts +1 -0
- package/dist/test/budget-settings.integration.test.js +8 -7
- package/dist/test/doctor.test.d.ts +1 -0
- package/dist/test/doctor.test.js +10 -2
- package/dist/test/dream-skill.test.d.ts +1 -0
- package/dist/test/dream-skill.test.js +69 -0
- package/dist/test/dream.test.d.ts +1 -0
- package/dist/test/helpers/extension.d.ts +115 -0
- package/dist/test/helpers/extension.js +6 -6
- package/dist/test/helpers/notes.d.ts +6 -0
- package/dist/test/helpers/notes.js +13 -0
- package/dist/test/history.integration.test.d.ts +1 -0
- package/dist/test/notes-library.test.d.ts +1 -0
- package/dist/test/notes-library.test.js +111 -0
- package/dist/test/notes.integration.test.d.ts +1 -0
- package/dist/test/notes.integration.test.js +22 -24
- package/dist/test/notes.test.d.ts +1 -0
- package/dist/test/notes.test.js +137 -7
- package/dist/test/reset-lifecycle.test.d.ts +1 -0
- package/dist/test/reset-lifecycle.test.js +142 -85
- package/docs/architecture.md +8 -8
- package/docs/reset-lifecycle.md +63 -79
- package/package.json +35 -2
- package/playbook.md +33 -32
- package/skills/dream/SKILL.md +12 -0
- package/src/context/boot.ts +44 -25
- package/src/context/budget.ts +25 -17
- package/src/context/context-window.ts +16 -1
- package/src/context/prompts.ts +2 -2
- package/src/context/reset-artifacts.ts +26 -24
- package/src/context/reset-lifecycle.ts +117 -111
- package/src/context/runtime.ts +50 -22
- package/src/context/thresholds.ts +1 -1
- package/src/dream/cli.ts +1 -1
- package/src/dream/doctor.ts +5 -2
- package/src/index.ts +4 -4
- package/src/notes/address.ts +9 -15
- package/src/notes/constants.ts +3 -0
- package/src/notes/context.ts +40 -0
- package/src/notes/frontmatter.ts +18 -12
- package/src/notes/index.ts +22 -0
- package/src/notes/paths.ts +64 -78
- package/src/notes/store.ts +308 -244
- package/src/pi/notes/adapter.ts +44 -0
- package/src/{notes → pi/notes}/session-replay.ts +3 -3
- package/src/{notes/notes-snapshot.ts → pi/notes/snapshot.ts} +13 -4
- package/src/{notes → pi/notes}/tools.ts +25 -23
- package/src/protocol.ts +5 -6
package/docs/reset-lifecycle.md
CHANGED
|
@@ -1,83 +1,67 @@
|
|
|
1
1
|
# Reset lifecycle
|
|
2
2
|
|
|
3
|
-
`src/context/reset-lifecycle.ts` owns
|
|
3
|
+
`src/context/reset-lifecycle.ts` owns the close-out request phases, turn-end batching, bounded overflow recovery, and reset scheduling. `src/context/budget.ts` owns budget policy and stages the early reminder/final warning; `src/context/reset-artifacts.ts` builds the checkpoint, marker, boot, and continuation drafts; `src/context/context-window.ts` validates and projects the active window. `src/context/runtime.ts` wires these parts to Pi's public lifecycle hooks and `/wipe-memory` command.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Lifecycle at a glance
|
|
6
|
+
|
|
7
|
+
| Trigger | Behavior |
|
|
6
8
|
| --- | --- |
|
|
7
|
-
| `wipe_memory` |
|
|
8
|
-
| `
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
| `/compact` while
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- **Normal reset.** A completed, enabled turn whose explicit request is pending or whose usage is due returns `commit-boundary`. The adapter drains budget drafts, keeps incoming drafts in order, appends `marker -> boot -> continuation`, and requests continuation. A second request in the same batch returns `already-requested`; the next turn commits it alone.
|
|
67
|
-
- **Overflow recovery.** An overflow-like `turn_end` with no queued message, enabled mode, and automatic reset arms `pending`; a `before_settle` with no queued message returns `recover-overflow` and spends the attempt to `spent`. A repeated settle is a no-op, and a later overflow failure re-arms only `pending-spent`.
|
|
68
|
-
- **Abort.** An aborted `turn_end` returns only the drafts already collected for that boundary, clears both axes, and never appends reset drafts.
|
|
69
|
-
- **Queued success.** A queued message defers `before_settle`; the queued turn's successful `turn_end` clears the stale overflow chain, so no recovery fires.
|
|
70
|
-
- **Process interruption.** If Pi stops between the marker and the later reset messages, the marker remains the authoritative boundary. On the next start, startup repair may append only the missing boot and continuation when the tail is otherwise repairable; it never moves or reinterprets the boundary.
|
|
71
|
-
- **Startup tail repair.** On `session_start` / `session_tree`, `ensureBoot` asks `repairResetTail` to inspect the marker tail and emit only the missing artifacts in the closed order: the boot, then the continuation. A boot/continuation sequence that is already complete, or a tail containing real conversation, a foreign message, a later marker, or a misordered/duplicate artifact, refuses repair and leaves `/wipe-memory` as the explicit recovery path.
|
|
72
|
-
|
|
73
|
-
The budget owner stages the early guidance and final checkpoint warning from active-window usage. The warning is visible in the current provider request, while both drafts are committed only by the lifecycle composer and are discarded on abort, settlement without a `turn_end`, transition, or window mismatch. After the warning, the model either writes its note and calls `wipe_memory`, or runtime recovery requests the same marker/boot boundary. Guidance and warning drafts precede reset drafts so stale reminders cannot be queued into the new window; durable entries remain the authority for redelivery. Their UI notices are emitted at the next turn start or settlement only after the matching reminder is committed in the active window, so aborted requests and retries cannot repeat an uncommitted reminder's notification.
|
|
74
|
-
|
|
75
|
-
The turn-end commit is the scheduling boundary: Pi receives the finished tool batch and then the marker/boot drafts as one append operation. Pi owns queue scheduling and deduplication of the next request; the extension does not run a parallel compaction state machine or use a compaction completion callback.
|
|
76
|
-
|
|
77
|
-
Boot construction reads the five note homes into one closed snapshot before rendering the hidden boot. An absent home is empty; a real read failure removes only that home's MAP/pocket rows. The boot retains its identity, reset line, notes-home instructions, and recovery protocol, and includes a concise `notes_list` retry notice. The human receives one incomplete-index notification per boot. This notes failure isolation does not bypass the marker/boot commit, alter raw history, or turn a partial boot into an empty fallback.
|
|
78
|
-
|
|
79
|
-
The hidden boot is selected by its durable `details.windowId`, not by timestamp or content equality. The context hook folds only the dropped system prefix before that boot and preserves later prompt patches and messages in order. If the boot is missing, the hook aborts with a safe head and notice rather than sending raw history. Startup/tree repair is deliberately narrow: it appends a boot only when the marker tail is otherwise incomplete metadata; later conversation or an authoritative raw boot causes safe refusal. `/wipe-memory` is the recovery path for that refused branch. A fork/clone creates a new session ID while copying branch entries, so startup refreshes the root boot identity while retaining copied root messages.
|
|
80
|
-
|
|
81
|
-
Public APIs let a mixed tool batch finish before the marker/boot boundary. Queued steering/follow-up messages are delivered exactly once in the new window, neither dropped nor replayed. `/tree` navigation remains available, but when either source or destination branch contains a reset marker, Pi's raw summary generator is bypassed: the summary is empty and a notice explains why, preventing erased history from re-entering through a path outside the context hook. With no marker on either branch, native summaries remain unchanged.
|
|
82
|
-
|
|
83
|
-
Validation uses a reduced set of persisted-data integration tests, isolated lifecycle event tests, and scripted SDK tests running Pi's actual agent loop. Representative cases check reset-window history retention, resumable reads, native-compaction cancellation, mixed-tool completion, steering/follow-up delivery, bounded overflow recovery, and settings authority. Tree-summary suppression no longer has a dedicated retained test. The separate coherence and pagination property suites have been removed; the retained cases do not cover the previous full matrix of branch, cancellation, malformed-input, and pagination edges.
|
|
9
|
+
| `wipe_memory` tool | Mark the current window tool-requested. At `turn_end`, after the complete tool batch and any accepted budget drafts, append the reset boundary and ask Pi to continue. Repeated requests for that window deduplicate. |
|
|
10
|
+
| `/wipe-memory` | Capture the current window, wait for idle, and stop if another invocation has already moved the branch to a new window. Arm a manual close-out, persist the shared hidden warning, and trigger an ordinary model turn. The agent can write notes/use tools over several turns. A `wipe_memory` call commits at `turn_end`; otherwise a successful normal stop commits at `agent_before_settle` after queued messages drain. |
|
|
11
|
+
| Budget warning | When automatic compaction is enabled and remaining active-window budget reaches reserve plus the warning runway, put the same hidden warning in the request and arm an automatic close-out. An explicit `wipe_memory` commits the boundary; a successful normal stop is the fallback. |
|
|
12
|
+
| Hard reserve | Independent safety path: if automatic resets are enabled and completed-turn usage reaches Pi's reserve, commit a boundary at `turn_end`. |
|
|
13
|
+
| Abort or error | Never count as successful close-out. Abort clears lifecycle state. An ordinary close-out error is dropped without reset; provider-overflow recovery remains separately bounded. |
|
|
14
|
+
| `/compact` while active | Cancel native compaction, which could otherwise summarize pre-reset history back into the active window. A manual attempt receives an actionable `/wipe-memory` notice. |
|
|
15
|
+
|
|
16
|
+
A new boundary is ordered as:
|
|
17
|
+
|
|
18
|
+
1. A native `compaction` entry with `summary: ""` and `firstKeptEntryId` set to its own ID. This is Pi's retain-none canonical checkpoint; it is not a generated summary request.
|
|
19
|
+
2. A `pi-context/reset-marker` custom entry with `{ windowId }`.
|
|
20
|
+
3. A hidden `pi-context/boot` custom message with matching `details.windowId`.
|
|
21
|
+
4. A hidden continuation message.
|
|
22
|
+
|
|
23
|
+
The raw session branch is preserved for history. New checkpoint-backed branches use Pi's canonical retain-none projection, which preserves the empty summary wrapper and system/tool state while excluding earlier conversational context. Older marker-only sessions use marker slicing as a narrow compatibility fallback. A marker is trusted as checkpoint-backed only when it directly follows an empty native compaction whose `firstKeptEntryId` is the compaction's own ID; a generic preceding compaction is not enough.
|
|
24
|
+
|
|
25
|
+
`turn_end` is the sole composer for completed turns. It preserves incoming entries, consumes current-window budget drafts, and appends the ordered reset boundary only when the reducer requests one. `agent_before_settle` handles successful close-out fallback after Pi has drained queued work. Pi owns the ensuing continuation and request scheduling. Reset construction awaits the asynchronous notes snapshot; the handler captures its session/window generation before awaiting, then rechecks generation, session, window, enabled state, and abort status before returning drafts. A stale completion is discarded while incoming/budget entries survive, and success notices are staged only after that guard. Construction failures notify only while the initiating lifecycle is still current and preserve already-collected entries without claiming a continuation.
|
|
26
|
+
|
|
27
|
+
## Reducer state
|
|
28
|
+
|
|
29
|
+
`reduceResetControl` is pure: the adapter supplies all lifecycle facts, and the reducer returns the next state plus a named effect. Its state combines a request phase with a bounded overflow phase:
|
|
30
|
+
|
|
31
|
+
| State | Meaning |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `{ phase: "none" }` | No close-out/tool request is pending. |
|
|
34
|
+
| `{ phase: "close-out", windowId, source }` | Manual or automatic warning close-out remains armed across ordinary note/tool turns. |
|
|
35
|
+
| `{ phase: "tool-requested", windowId }` | A direct `wipe_memory` request will commit after its complete turn batch. |
|
|
36
|
+
| `overflow: "idle"` | No overflow recovery is pending. |
|
|
37
|
+
| `overflow: "pending"` | An overflow failure may recover once at pre-settlement. |
|
|
38
|
+
| `overflow: "pending-spent"` | Recovery was already spent in this failure chain. |
|
|
39
|
+
| `overflow: "spent"` | The one recovery has been used until the chain settles. |
|
|
40
|
+
|
|
41
|
+
### Transition rules
|
|
42
|
+
|
|
43
|
+
- `close_out(windowId, source)` arms a close-out for that window. Repeating an already-pending request deduplicates; a manual request can upgrade an automatic request for the same window.
|
|
44
|
+
- `tool_request(windowId)` marks that window for a turn-end reset. A matching duplicate is reported as already pending.
|
|
45
|
+
- `turn_end` first clears everything on abort. Overflow-like errors disarm the reset request and arm overflow recovery only when there is no queued work, the extension is enabled, and automatic reset is enabled. Other failures drop the request but preserve the overflow chain. A successful, enabled turn commits for a tool request or hard-reserve condition; a close-out alone remains armed.
|
|
46
|
+
- `agent_before_settle` clears state on actual abort. Pending overflow recovery gets its one bounded attempt when enabled and unqueued. Otherwise a close-out commits only if the outcome is successful, the request belongs to the current window, the extension is enabled, and no queued work remains. Automatic close-outs also require automatic resets to remain enabled.
|
|
47
|
+
- `agent_settled`, session start/tree navigation/shutdown, `/pi-context off`, and abort clear transient request state. Durable checkpoint/marker history remains authoritative after transient state is gone.
|
|
48
|
+
|
|
49
|
+
This separation is intentional: a note write or ordinary tool turn during a close-out must not consume it. Direct `wipe_memory` is not armed by the shared warning and remains valid without one. A user abort, an assistant message with a synthetic `stopReason: "aborted"`, and a generic provider error are distinct cases; none commits an ordinary close-out, while only Pi's actual operation cancellation is a user abort.
|
|
50
|
+
|
|
51
|
+
## Budget staging and notifications
|
|
52
|
+
|
|
53
|
+
The early reminder is staged once per window when remaining budget reaches `reserve + reminderMarginTokens`. The final close-out warning is staged/attached at `reserve + WARNING_RUNWAY_TOKENS`, only when automatic compaction is enabled. The hard reserve itself remains a separate safety cutoff. The warning text is shared with `/wipe-memory`; after it is durable, the extension does not repeat it in that window. Warning persistence deduplicates only injection: each eligible low-budget request re-arms transient automatic close-out before checking for the durable warning, so an abort/error can clear its attempt and the next request can retry without another warning.
|
|
54
|
+
|
|
55
|
+
Budget drafts are instance-local and are consumed at turn end. They are discarded on abort, failed turn, transition, or window mismatch. UI reminder notices are emitted only after the matching hidden entry is durable, so failed requests do not report an uncommitted warning. Active-window usage and the exact `SettingsManager` authority drive threshold checks; default file-backed policy is cached per extension instance, while injected managers are read through their public API.
|
|
56
|
+
|
|
57
|
+
## Projection, repair, and history
|
|
58
|
+
|
|
59
|
+
`context_with_system` selects the active boot by durable `details.windowId`. For a verified native checkpoint, Pi's canonical projection is used directly; for a legacy marker-only reset, the compatibility projection slices at the matching boot. The projection preserves system/tool state and later prompt patches. If the boot is absent, the runtime aborts safely instead of sending raw history. Usage estimation uses the same checkpoint-aware projection decision.
|
|
60
|
+
|
|
61
|
+
Startup/tree repair is narrow: it may complete a repairable marker tail when later conversation does not make the missing metadata ambiguous. It awaits the notes snapshot and rechecks lifecycle/window identity before sending a repaired boot or continuation. Legacy marker-only tails remain supported; repair never moves a boundary or promotes an arbitrary compaction to a retain-none checkpoint. `/tree` summary generation is suppressed with an empty summary when either branch crosses a reset, because the raw summary generator bypasses the provider projection.
|
|
62
|
+
|
|
63
|
+
## Validation and known limits
|
|
64
|
+
|
|
65
|
+
The test suite combines isolated reducer tests with scripted SDK tests executing Pi's real agent loop without model/network calls. The agent-loop coverage includes manual warning plus multi-turn note writes and explicit wipe, direct wipe without warning, normal-stop fallback, same-window concurrent command deduplication, actual and synthetic abort/error cleanup, queued steering/follow-up delivery, hard-reserve and bounded-overflow recovery, successive resets, and real SessionManager file reopen after two retain-none checkpoints. It also checks raw-history preservation, canonical projection, empty-summary retention, system/tool state, warning/marker order, and fresh-window continuation.
|
|
66
|
+
|
|
67
|
+
The broader coherence and pagination property suites were removed during test reduction; retained cases are representative rather than exhaustive. Tree-summary suppression has no dedicated retained test, and external-provider behavior, every malformed persisted shape, and every filesystem failure mode are not established by this suite.
|
package/package.json
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/pi-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"main": "./dist/src/index.js",
|
|
6
|
+
"types": "./dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"import": "./dist/src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./notes": {
|
|
13
|
+
"types": "./dist/src/notes/index.d.ts",
|
|
14
|
+
"import": "./dist/src/notes/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./dist/src/index.js": {
|
|
17
|
+
"types": "./dist/src/index.d.ts",
|
|
18
|
+
"import": "./dist/src/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
5
22
|
"description": "Codex-style context windows for Pi: durable reset windows, session history tools, and persistent notes.",
|
|
6
23
|
"license": "MIT",
|
|
7
24
|
"keywords": [
|
|
@@ -16,6 +33,9 @@
|
|
|
16
33
|
"pi": {
|
|
17
34
|
"extensions": [
|
|
18
35
|
"./dist/extension.js"
|
|
36
|
+
],
|
|
37
|
+
"skills": [
|
|
38
|
+
"./skills"
|
|
19
39
|
]
|
|
20
40
|
},
|
|
21
41
|
"files": [
|
|
@@ -24,7 +44,8 @@
|
|
|
24
44
|
"docs",
|
|
25
45
|
"LICENSE",
|
|
26
46
|
"README.md",
|
|
27
|
-
"playbook.md"
|
|
47
|
+
"playbook.md",
|
|
48
|
+
"skills"
|
|
28
49
|
],
|
|
29
50
|
"bin": {
|
|
30
51
|
"dream": "dist/src/dream/cli.js"
|
|
@@ -33,6 +54,7 @@
|
|
|
33
54
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && node scripts/build-extension.mjs && node -e \"require('node:fs').chmodSync('dist/src/dream/cli.js', 0o755)\"",
|
|
34
55
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
35
56
|
"test": "npm run build && node --test dist/test/*.test.js",
|
|
57
|
+
"test:notes-package": "npm run build && node scripts/check-notes-package.mjs",
|
|
36
58
|
"prepublishOnly": "npm run typecheck",
|
|
37
59
|
"prepack": "npm run build"
|
|
38
60
|
},
|
|
@@ -41,6 +63,17 @@
|
|
|
41
63
|
"@earendil-works/pi-ai": "^0.87.0",
|
|
42
64
|
"@earendil-works/pi-coding-agent": "^0.87.0"
|
|
43
65
|
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@earendil-works/pi-agent-core": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@earendil-works/pi-ai": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@earendil-works/pi-coding-agent": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
44
77
|
"devDependencies": {
|
|
45
78
|
"@earendil-works/pi-agent-core": "^0.87.0",
|
|
46
79
|
"@earendil-works/pi-ai": "^0.87.0",
|
package/playbook.md
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
# Dream
|
|
2
|
+
|
|
3
|
+
I am reviewing my notes: keeping what is useful, connecting what belongs together, and leaving a clearer record for whoever comes next. Notes are record, not memory.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
Work within the readable sources and writable homes established for this run. Do not broaden that scope while exploring. If a boundary is unclear, ask before crossing it.
|
|
8
|
+
|
|
9
|
+
A project dream draws from that project's material, project notes, and session notes identified as belonging to the project; it organizes only that project's notes. A store-wide dream may inspect across homes, but authorship and write permissions still apply. Unknown ownership is a coverage gap to report, not a reason to guess or repair ownership.
|
|
10
|
+
|
|
11
|
+
The homes serve different readers:
|
|
12
|
+
|
|
13
|
+
- Session notes are a trip's working record. Read them as sources where permitted; never edit them, even after promoting a useful finding.
|
|
14
|
+
- Project notes hold knowledge for whoever next works on that project.
|
|
15
|
+
- Human notes hold the human's cross-project preferences and standing rules. Do not turn project facts or your own lessons into rules in the human's voice.
|
|
16
|
+
- Agent notes belong to their author. Other agents' notes are read-only unless the human authorizes revision.
|
|
17
|
+
- Model notes describe the substrate's behavior. Other models' notes are read-only unless the human authorizes revision.
|
|
18
|
+
|
|
19
|
+
## Review and consolidate
|
|
20
|
+
|
|
21
|
+
1. **Leave stable notes alone.** Change a note to incorporate new evidence, resolve a verified error, merge a genuine duplicate, or split an oversized file—not merely to shorten or rephrase it. No change is a valid outcome.
|
|
22
|
+
2. **Verify only what the proposed change depends on.** Before correcting or promoting a conclusion, check the relevant evidence as needed. Do not revalidate every stable note or turn dream into a source-code audit. An unavailable referent is unverified, not proof that it is dead; do not promote an unsupported claim as current fact.
|
|
23
|
+
3. **Merge only genuine duplicates.** Supersede a note only when both notes have the same topic and kind (checkpoint, design, log…), and the survivor is strictly newer or more specific. Name the shared topic in the survivor. Otherwise keep both and record any unresolved conflict.
|
|
24
|
+
4. **Preserve meaning and provenance.** Keep facts, conditions, exceptions, uncertainty, authorship, and sources. When promoting a finding, cite its source. Do not relabel agent-authored material as human-authored. If its destination or authority is unclear, propose the promotion instead.
|
|
25
|
+
5. **Keep notes manageable.** Aim below about 200 lines / 8KB per note. Split oversized notes by topic with one-line cross-links. Checkpoints may exceed the budget: trim prose, never facts.
|
|
26
|
+
6. **Keep maps useful.** Each writable home's MAP.md lists its durable notes, one line per entry, with an unambiguous address and a short gist in your own words. Add promoted notes and drop stale entries. Session notes are never mapped. Maps follow the same size budget.
|
|
27
|
+
7. **Preserve the record.** Never physically delete notes. After merging, mark the absorbed note stale only if it is writable. Preserve existing metadata and provenance. Map entry lines are yours to maintain within the writable scope.
|
|
28
|
+
|
|
29
|
+
## Report
|
|
30
|
+
|
|
31
|
+
Give a concise report grouped by home: what you inspected, changed, promoted, and left unresolved. Mention coverage gaps and partial failures. If no notes changed, say so. Distinguish actual note changes from execution or audit artifacts; do not claim success for an incomplete run.
|
|
32
|
+
|
|
33
|
+
Put skill ideas and unresolved questions in the final message as proposals for the human, not new skill files.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dream
|
|
3
|
+
description: Review and consolidate durable notes using pi-context's shared dream playbook. Use when the human asks to dream over notes, merge genuine duplicates, update maps, or promote durable learning.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dream
|
|
7
|
+
|
|
8
|
+
Read `../../playbook.md` completely, resolving the path from this skill's directory. It is the shared guide to reviewing and organizing notes.
|
|
9
|
+
|
|
10
|
+
Dream in this agent's current session. Do not start another agent or model unless the human asks.
|
|
11
|
+
|
|
12
|
+
Work within the supplied readable and writable scope. If the scope or permission to write has not been established, ask rather than inventing it or setting up execution machinery yourself.
|
package/src/context/boot.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { notesContextFromPi } from "../pi/notes/adapter.js";
|
|
3
|
+
import { loadNotesSnapshot, type NotesSnapshot } from "../pi/notes/snapshot.js";
|
|
4
4
|
import { BOOT_TYPE } from "../protocol.js";
|
|
5
5
|
import { renderBootBlock } from "./prompts.js";
|
|
6
|
-
import { currentReset, isWindowBoot, rootWindowId } from "./context-window.js";
|
|
6
|
+
import { currentReset, currentWindowId, isWindowBoot, rootWindowId } from "./context-window.js";
|
|
7
7
|
import { repairResetTail } from "./reset-artifacts.js";
|
|
8
8
|
|
|
9
9
|
export type IncompleteNotesNotifier = (ctx: ExtensionContext, windowId: string, snapshot: NotesSnapshot) => void;
|
|
@@ -16,31 +16,38 @@ export type BootMessage = {
|
|
|
16
16
|
readonly details: { windowId: string };
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
/** Render the boot block from
|
|
20
|
-
function bootContent(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
19
|
+
/** Render the boot block from one pre-await identity and acquired notes snapshot. */
|
|
20
|
+
function bootContent(
|
|
21
|
+
agentName: string,
|
|
22
|
+
modelName: string,
|
|
23
|
+
firstWindowId: string,
|
|
24
|
+
currentId: string,
|
|
25
|
+
previousId: string | undefined,
|
|
26
|
+
notes: NotesSnapshot,
|
|
27
|
+
): string {
|
|
28
|
+
return renderBootBlock({ agentName, modelName, firstWindowId, currentWindowId: currentId, previousWindowId: previousId, notes });
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Acquire one notes snapshot and build the boot custom message for a window.
|
|
33
|
-
*
|
|
32
|
+
* Acquire one notes snapshot and build the boot custom message for a window. All identity
|
|
33
|
+
* fields are captured synchronously before the first filesystem await.
|
|
34
34
|
*/
|
|
35
|
-
export function buildBootMessage(
|
|
35
|
+
export async function buildBootMessage(
|
|
36
36
|
ctx: ExtensionContext,
|
|
37
37
|
windowId: string,
|
|
38
38
|
previousId: string | undefined,
|
|
39
39
|
notifyIncompleteNotes?: IncompleteNotesNotifier,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
isCurrent: () => boolean = () => true,
|
|
41
|
+
): Promise<BootMessage> {
|
|
42
|
+
const identity = notesContextFromPi(ctx);
|
|
43
|
+
const notes = await loadNotesSnapshot(ctx, undefined, identity);
|
|
44
|
+
if (isCurrent()) notifyIncompleteNotes?.(ctx, windowId, notes);
|
|
45
|
+
return {
|
|
46
|
+
customType: BOOT_TYPE,
|
|
47
|
+
content: bootContent(identity.agent, identity.model, rootWindowId(identity.sessionId), windowId, previousId, notes),
|
|
48
|
+
display: false,
|
|
49
|
+
details: { windowId },
|
|
50
|
+
};
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
/** Persist one hidden boot message without triggering a model turn. */
|
|
@@ -54,15 +61,27 @@ export function sendBoot(pi: ExtensionAPI, boot: BootMessage): void {
|
|
|
54
61
|
/**
|
|
55
62
|
* Boot entry point for `session_start` / `session_tree`. Ordinary startup ensures one root
|
|
56
63
|
* boot; a reset marker instead asks reset-artifact repair to complete its persisted tail.
|
|
57
|
-
* Boot idempotence lives here: an already-projected root boot or a complete reset tail emits nothing.
|
|
58
64
|
*/
|
|
59
|
-
export function ensureBoot(
|
|
65
|
+
export async function ensureBoot(
|
|
66
|
+
pi: ExtensionAPI,
|
|
67
|
+
ctx: ExtensionContext,
|
|
68
|
+
notifyIncompleteNotes?: IncompleteNotesNotifier,
|
|
69
|
+
isCurrent: () => boolean = () => true,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
60
72
|
const reset = currentReset(ctx);
|
|
73
|
+
const windowId = reset?.data.windowId ?? rootWindowId(sessionId);
|
|
74
|
+
const stillCurrent = () => isCurrent() &&
|
|
75
|
+
ctx.signal?.aborted !== true &&
|
|
76
|
+
ctx.sessionManager.getSessionId() === sessionId &&
|
|
77
|
+
currentWindowId(ctx) === windowId;
|
|
61
78
|
if (reset) {
|
|
62
|
-
repairResetTail(pi, ctx, reset, notifyIncompleteNotes);
|
|
79
|
+
await repairResetTail(pi, ctx, reset, notifyIncompleteNotes, stillCurrent);
|
|
63
80
|
return;
|
|
64
81
|
}
|
|
65
|
-
const windowId = rootWindowId(ctx.sessionManager.getSessionId());
|
|
66
82
|
if (ctx.sessionManager.buildSessionProjection().messages.some((message) => isWindowBoot(message, windowId))) return;
|
|
67
|
-
|
|
83
|
+
const boot = await buildBootMessage(ctx, windowId, undefined, notifyIncompleteNotes, stillCurrent);
|
|
84
|
+
if (!stillCurrent()) return;
|
|
85
|
+
if (ctx.sessionManager.buildSessionProjection().messages.some((message) => isWindowBoot(message, windowId))) return;
|
|
86
|
+
sendBoot(pi, boot);
|
|
68
87
|
}
|
package/src/context/budget.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@earendil-works/pi-ai";
|
|
2
2
|
import { defineTool, type ExtensionAPI, type ExtensionContext, type SessionBoundaryDraft, type SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import {
|
|
3
|
+
import { GUIDANCE_TYPE, WARNING_CONTENT, WARNING_TYPE } from "../protocol.js";
|
|
4
4
|
import { readThresholdSettings, type ResolvedThresholds, type ThresholdSettingsResolution } from "./thresholds.js";
|
|
5
5
|
import { currentWindowId, hasWindowMessage, windowUsage } from "./context-window.js";
|
|
6
6
|
import { tokenBudgetGuidance } from "./prompts.js";
|
|
@@ -12,7 +12,12 @@ export function remainingTokens(ctx: Pick<ExtensionContext, "sessionManager" | "
|
|
|
12
12
|
return !usage || usage.tokens === null ? null : Math.max(0, usage.contextWindow - usage.tokens);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function registerBudget(
|
|
15
|
+
export function registerBudget(
|
|
16
|
+
pi: ExtensionAPI,
|
|
17
|
+
isEnabled: () => boolean,
|
|
18
|
+
settingsManager?: SettingsManager,
|
|
19
|
+
onCloseOut: (windowId: string) => void = () => {},
|
|
20
|
+
) {
|
|
16
21
|
let cachedPolicy: { thresholds: ResolvedThresholds; automatic: boolean } | undefined;
|
|
17
22
|
const notifiedWarnings = new Set<string>();
|
|
18
23
|
const resolvePolicy = (ctx: ExtensionContext): ThresholdSettingsResolution => {
|
|
@@ -32,22 +37,22 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
|
|
|
32
37
|
const automaticResetEnabled = (ctx: ExtensionContext): boolean => {
|
|
33
38
|
return resolvePolicy(ctx).automatic;
|
|
34
39
|
};
|
|
35
|
-
const
|
|
40
|
+
const hardReserveDue = (ctx: ExtensionContext): boolean => {
|
|
36
41
|
if (!automaticResetEnabled(ctx)) return false;
|
|
37
42
|
const usage = windowUsage(ctx);
|
|
38
43
|
return usage !== undefined && usage.tokens !== null && usage.contextWindow - usage.tokens <= thresholdsFor(ctx).reserve;
|
|
39
44
|
};
|
|
40
45
|
const invalidateThresholds = () => { cachedPolicy = undefined; };
|
|
41
|
-
let pendingGuidance: { windowId: string; content: string } | undefined;
|
|
42
|
-
let pendingWarning: { windowId: string; content: string } | undefined;
|
|
43
|
-
let pendingNotices: Array<{ windowId: string; customType: string }> = [];
|
|
46
|
+
let pendingGuidance: { windowId: string; content: string; remaining: number } | undefined;
|
|
47
|
+
let pendingWarning: { windowId: string; content: string; remaining: number } | undefined;
|
|
48
|
+
let pendingNotices: Array<{ windowId: string; customType: string; remaining: number }> = [];
|
|
44
49
|
const notifyCommittedReminders = (ctx: ExtensionContext) => {
|
|
45
50
|
const windowId = currentWindowId(ctx);
|
|
46
51
|
for (const notice of pendingNotices) {
|
|
47
52
|
if (notice.windowId !== windowId || !hasWindowMessage(ctx, notice.customType)) continue;
|
|
48
53
|
ctx.ui.notify(notice.customType === WARNING_TYPE
|
|
49
|
-
? "pi-context:
|
|
50
|
-
: "pi-context:
|
|
54
|
+
? "pi-context: Context almost full; close out the current memory window."
|
|
55
|
+
: "pi-context: Context running low; checkpoint your notes soon.", "warning");
|
|
51
56
|
}
|
|
52
57
|
pendingNotices = [];
|
|
53
58
|
};
|
|
@@ -71,7 +76,7 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
|
|
|
71
76
|
clearStaged();
|
|
72
77
|
const windowId = currentWindowId(ctx);
|
|
73
78
|
const drafts = staged.filter((draft): draft is NonNullable<typeof draft> => draft !== undefined && draft.windowId === windowId);
|
|
74
|
-
pendingNotices = drafts.map(({ windowId, customType }) => ({ windowId, customType }));
|
|
79
|
+
pendingNotices = drafts.map(({ windowId, customType, remaining }) => ({ windowId, customType, remaining }));
|
|
75
80
|
return drafts.map((draft) => ({
|
|
76
81
|
type: "custom_message" as const,
|
|
77
82
|
customType: draft.customType,
|
|
@@ -88,7 +93,6 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
|
|
|
88
93
|
// lifecycle point that must discard an uncommitted draft before the next prompt.
|
|
89
94
|
// UI notices follow committed reminders. Aborted requests can retry their drafts
|
|
90
95
|
// without showing the same low-budget notification twice.
|
|
91
|
-
pi.on("turn_start", (_event, ctx) => notifyCommittedReminders(ctx));
|
|
92
96
|
pi.on("agent_settled", (_event, ctx) => {
|
|
93
97
|
notifyCommittedReminders(ctx);
|
|
94
98
|
clearStaged();
|
|
@@ -101,12 +105,15 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
|
|
|
101
105
|
if (remaining === null) return undefined;
|
|
102
106
|
const windowId = currentWindowId(ctx);
|
|
103
107
|
const { reminder, warning } = thresholdsFor(ctx);
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
//
|
|
108
|
+
if (remaining <= warning && automaticResetEnabled(ctx)) {
|
|
109
|
+
// Re-arm close-out on each eligible request. A prior request may have aborted
|
|
110
|
+
// after persisting the warning, so warning deduplication must not own this state.
|
|
111
|
+
onCloseOut(windowId);
|
|
112
|
+
if (hasWindowMessage(ctx, WARNING_TYPE) || pendingWarning?.windowId === windowId) return undefined;
|
|
113
|
+
// The critical close-out starts at reserve + runway, not at the hard reserve.
|
|
107
114
|
pendingGuidance = undefined;
|
|
108
|
-
const content =
|
|
109
|
-
pendingWarning = { windowId, content };
|
|
115
|
+
const content = WARNING_CONTENT;
|
|
116
|
+
pendingWarning = { windowId, content, remaining };
|
|
110
117
|
const warningMessage = {
|
|
111
118
|
role: "custom" as const,
|
|
112
119
|
customType: WARNING_TYPE,
|
|
@@ -116,12 +123,13 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
|
|
|
116
123
|
};
|
|
117
124
|
return { messages: [..._event.messages, warningMessage] };
|
|
118
125
|
}
|
|
126
|
+
if (hasWindowMessage(ctx, WARNING_TYPE) || pendingWarning?.windowId === windowId) return undefined;
|
|
119
127
|
if (hasWindowMessage(ctx, GUIDANCE_TYPE) || pendingGuidance?.windowId === windowId) return undefined;
|
|
120
128
|
if (remaining <= reminder) {
|
|
121
129
|
// Persist at turn_end, before any reset drafts. A queued sendMessage could
|
|
122
130
|
// otherwise cross the marker and leak the old window's reminder forward.
|
|
123
131
|
const left = Math.max(0, remaining - warning);
|
|
124
|
-
pendingGuidance = { windowId, content: tokenBudgetGuidance(left) };
|
|
132
|
+
pendingGuidance = { windowId, content: tokenBudgetGuidance(left), remaining };
|
|
125
133
|
}
|
|
126
134
|
return undefined;
|
|
127
135
|
});
|
|
@@ -141,7 +149,7 @@ export function registerBudget(pi: ExtensionAPI, isEnabled: () => boolean, setti
|
|
|
141
149
|
|
|
142
150
|
return {
|
|
143
151
|
automaticResetEnabled,
|
|
144
|
-
|
|
152
|
+
hardReserveDue,
|
|
145
153
|
consumeTurnEnd,
|
|
146
154
|
clear: () => { clearStaged(); pendingNotices = []; },
|
|
147
155
|
};
|
|
@@ -24,6 +24,16 @@ export function currentReset(ctx: SessionReader): WindowMarker | undefined {
|
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** A reset is checkpoint-backed only when its marker directly follows a native compaction. */
|
|
28
|
+
export function isCheckpointBackedReset(ctx: SessionReader, marker = currentReset(ctx)): boolean {
|
|
29
|
+
if (!marker?.parentId) return false;
|
|
30
|
+
const branch = ctx.sessionManager.getBranch();
|
|
31
|
+
const markerIndex = branch.findIndex((entry) => entry.id === marker.id);
|
|
32
|
+
const checkpoint = branch[markerIndex - 1];
|
|
33
|
+
return markerIndex > 0 && checkpoint?.id === marker.parentId && checkpoint.type === "compaction" &&
|
|
34
|
+
checkpoint.summary === "" && checkpoint.firstKeptEntryId === checkpoint.id;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
/** Mint the durable identity of a session's root history window. */
|
|
28
38
|
export function rootWindowId(sessionId: string): string {
|
|
29
39
|
return `pcw:${sessionId.slice(0, 8)}:root`;
|
|
@@ -106,7 +116,12 @@ export function windowUsage(ctx: Pick<ExtensionContext, "sessionManager" | "getC
|
|
|
106
116
|
if (!contextWindow) return undefined;
|
|
107
117
|
const windowId = reset.data.windowId;
|
|
108
118
|
try {
|
|
109
|
-
const
|
|
119
|
+
const canonicalMessages = ctx.sessionManager.buildSessionProjection().messages;
|
|
120
|
+
// New resets already cut the durable Pi projection. Marker slicing is retained only
|
|
121
|
+
// for sessions written before native reset checkpoints existed.
|
|
122
|
+
const messages: AgentMessage[] = isCheckpointBackedReset(ctx, reset)
|
|
123
|
+
? canonicalMessages
|
|
124
|
+
: projectWindow(canonicalMessages, windowId);
|
|
110
125
|
const { tokens } = estimateContextTokens(convertToLlm(messages));
|
|
111
126
|
return { tokens, contextWindow, percent: tokens / contextWindow * 100 };
|
|
112
127
|
} catch {
|
package/src/context/prompts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NotesHome, NotesSnapshot } from "../notes/
|
|
1
|
+
import type { NotesHome, NotesSnapshot } from "../pi/notes/snapshot.js";
|
|
2
2
|
import { CONTEXT_WINDOW_OPEN_TAG, CONTEXT_WINDOW_CLOSE_TAG, POCKET_AGENT_LIMIT, POCKET_HUMAN_LIMIT, POCKET_MODEL_LIMIT, POCKET_PROJECT_LIMIT, POCKET_SESSION_LIMIT, PROTOCOL_BLOCK, GUIDANCE_OPEN_TAG, GUIDANCE_CLOSE_TAG } from "../protocol.js";
|
|
3
3
|
|
|
4
4
|
/** Codex-style <context_window> identity block: the resolved agent and model names plus first/current/previous window ids. */
|
|
@@ -62,7 +62,7 @@ function notesIndex(snapshot: NotesSnapshot): string {
|
|
|
62
62
|
if (recentNotes.length > 0) {
|
|
63
63
|
const lines = [`You find ${recentNotes.length} crumpled note${recentNotes.length === 1 ? "" : "s"} in your pocket (by prefix, most recent first within each: up to ${POCKET_SESSION_LIMIT} from this session, ${POCKET_PROJECT_LIMIT} from @project, ${POCKET_HUMAN_LIMIT} from @human, ${POCKET_AGENT_LIMIT} from @self, ${POCKET_MODEL_LIMIT} from @model). A note's content never appears here, so its name has to say what the note is about:`];
|
|
64
64
|
for (const row of recentNotes) {
|
|
65
|
-
lines.push(`- ${row.address} (${row.body.split("\n").length} lines, ${row.sizeBytes} UTF-8 bytes, updated ${relativeTime(row.meta.
|
|
65
|
+
lines.push(`- ${row.address} (${row.body.split("\n").length} lines, ${row.sizeBytes} UTF-8 bytes, updated ${relativeTime(row.meta.updatedAt, snapshot.openedAt)})`);
|
|
66
66
|
}
|
|
67
67
|
sections.push(lines.join("\n"));
|
|
68
68
|
}
|