@astrosheep/pi-context 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -5
- package/dist/build-info.json +4 -0
- package/dist/extension.js +1951 -0
- package/dist/src/context/boot.js +46 -0
- package/dist/src/context/budget.js +150 -0
- package/dist/src/context/context-window.js +112 -0
- package/dist/src/context/prompts.js +91 -0
- package/dist/src/context/reset-artifacts.js +86 -0
- package/dist/src/context/reset-lifecycle.js +182 -0
- package/dist/src/context/runtime.js +151 -0
- package/dist/src/context/thresholds.js +62 -0
- package/dist/src/dream/cli.js +1 -1
- package/dist/src/dream/doctor.js +34 -6
- package/dist/src/dream/runner.js +1 -1
- package/dist/src/dream/settings.js +30 -0
- package/dist/src/{history-tools.js → history/history-tools.js} +3 -3
- package/dist/src/{history.js → history/history.js} +8 -46
- package/dist/src/index.js +27 -94
- package/dist/src/notes/address.js +97 -16
- package/dist/src/notes/frontmatter.js +18 -3
- package/dist/src/notes/notes-snapshot.js +30 -0
- package/dist/src/notes/paths.js +64 -7
- package/dist/src/notes/session-replay.js +41 -0
- package/dist/src/notes/store.js +76 -22
- package/dist/src/notes/tools.js +7 -7
- package/dist/src/protocol.js +9 -9
- package/dist/src/settings.js +16 -0
- package/dist/src/tool-schema.js +1 -1
- package/dist/test/agent-loop.test.js +815 -221
- package/dist/test/boot.integration.test.js +219 -0
- package/dist/test/budget-settings.integration.test.js +126 -0
- package/dist/test/doctor.test.js +14 -36
- package/dist/test/dream.test.js +37 -380
- package/dist/test/helpers/extension.js +392 -0
- package/dist/test/history.integration.test.js +316 -0
- package/dist/test/notes.integration.test.js +270 -0
- package/dist/test/notes.test.js +40 -359
- package/dist/test/reset-lifecycle.test.js +443 -178
- package/docs/architecture.md +35 -18
- package/docs/reset-lifecycle.md +73 -14
- package/package.json +11 -10
- package/src/context/boot.ts +68 -0
- package/src/context/budget.ts +148 -0
- package/src/context/context-window.ts +118 -0
- package/src/context/prompts.ts +108 -0
- package/src/context/reset-artifacts.ts +101 -0
- package/src/context/reset-lifecycle.ts +272 -0
- package/src/context/runtime.ts +151 -0
- package/src/context/thresholds.ts +78 -0
- package/src/dream/cli.ts +1 -1
- package/src/dream/doctor.ts +27 -6
- package/src/dream/runner.ts +1 -1
- package/src/dream/settings.ts +32 -0
- package/src/{history-tools.ts → history/history-tools.ts} +3 -3
- package/src/{history.ts → history/history.ts} +9 -48
- package/src/index.ts +27 -89
- package/src/notes/address.ts +82 -16
- package/src/notes/frontmatter.ts +20 -3
- package/src/notes/notes-snapshot.ts +40 -0
- package/src/notes/paths.ts +64 -7
- package/src/notes/session-replay.ts +53 -0
- package/src/notes/store.ts +78 -25
- package/src/notes/tools.ts +7 -7
- package/src/protocol.ts +9 -10
- package/src/settings.ts +20 -0
- package/src/tool-schema.ts +1 -2
- package/dist/src/budget.js +0 -65
- package/dist/src/notes/model.js +0 -101
- package/dist/src/prompts.js +0 -88
- package/dist/src/reset-lifecycle.js +0 -155
- package/dist/src/thresholds.js +0 -102
- package/dist/src/warning.js +0 -44
- package/dist/test/coherence.test.js +0 -371
- package/dist/test/history.test.js +0 -26
- package/dist/test/integration.test.js +0 -1759
- package/dist/test/pagination.property.test.js +0 -471
- package/src/budget.ts +0 -67
- package/src/notes/model.ts +0 -109
- package/src/prompts.ts +0 -91
- package/src/reset-lifecycle.ts +0 -173
- package/src/thresholds.ts +0 -110
- package/src/warning.ts +0 -46
package/dist/src/notes/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@earendil-works/pi-ai";
|
|
2
2
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { localIso } from "./
|
|
3
|
+
import { localIso } from "./frontmatter.js";
|
|
4
4
|
import { DEFAULT_READ_WINDOW_CHARS, MAX_READ_WINDOW_CHARS, middleTruncate, output, outputRaw, page, prefixFit, readCharacterWindow, readWindowBlock, withinTextBudget } from "../tool-output.js";
|
|
5
5
|
import { cursor, nullableString, positiveInteger, searchQueries, searchQuery } from "../tool-schema.js";
|
|
6
6
|
import { assertAddress } from "./address.js";
|
|
@@ -8,7 +8,7 @@ import { NoteError, editNote, listNotes, readNote, searchNotes, writeNote } from
|
|
|
8
8
|
const ORIGIN = Type.Optional(Type.Union([Type.Literal("user"), Type.Literal("self"), Type.Literal("external")], {
|
|
9
9
|
description: "Where the note's content came from. user: written or dictated by the human. self: written by you, the agent (default). external: anything else — third-party text, tool output, fetched material.",
|
|
10
10
|
}));
|
|
11
|
-
const ADDRESS_DESCRIPTION = "Address forms are bare `<vpath>` for this session, `@project/<vpath>` for this project
|
|
11
|
+
const ADDRESS_DESCRIPTION = "Address forms are bare `<vpath>` for this session, `@project/<vpath>` for this project, `@human/<vpath>` for the human's cross-project notes, `@self/<vpath>` for your own, and `@model/<vpath>` for the current model's. `@self` and `@model` mean whoever is running now. Any other `@` prefix, or `@` inside a vpath, is a hard error. There is no fallback across prefixes. Paths reject `..`, absolute paths, and backslashes.";
|
|
12
12
|
function failure(error) {
|
|
13
13
|
if (error instanceof NoteError) {
|
|
14
14
|
const payload = { error: error.message };
|
|
@@ -29,7 +29,7 @@ export function registerNotesTools(pi) {
|
|
|
29
29
|
const content = params.content;
|
|
30
30
|
try {
|
|
31
31
|
const destination = assertAddress(params.address);
|
|
32
|
-
writeNote(ctx, destination.path, content, { scope: destination.scope, origin: (params.origin ?? "self"), stale: params.stale });
|
|
32
|
+
writeNote(ctx, destination.path, content, { scope: destination.scope, who: destination.who, origin: (params.origin ?? "self"), stale: params.stale });
|
|
33
33
|
return output({ address: params.address, written: true });
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
@@ -44,7 +44,7 @@ export function registerNotesTools(pi) {
|
|
|
44
44
|
async execute(_id, params, _signal, _update, ctx) {
|
|
45
45
|
try {
|
|
46
46
|
const destination = assertAddress(params.address);
|
|
47
|
-
const { applied, diff } = editNote(ctx, destination.path, destination.scope, params.edits, { origin: params.origin, stale: params.stale, replaceAll: params.replace_all });
|
|
47
|
+
const { applied, diff } = editNote(ctx, destination.path, destination.scope, params.edits, { origin: params.origin, stale: params.stale, replaceAll: params.replace_all }, destination.who);
|
|
48
48
|
return output({ address: params.address, applied, diff });
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
@@ -60,7 +60,7 @@ export function registerNotesTools(pi) {
|
|
|
60
60
|
let note;
|
|
61
61
|
try {
|
|
62
62
|
const destination = assertAddress(params.address);
|
|
63
|
-
note = readNote(ctx, destination.path, destination.scope);
|
|
63
|
+
note = readNote(ctx, destination.path, destination.scope, destination.who);
|
|
64
64
|
}
|
|
65
65
|
catch (error) {
|
|
66
66
|
return failure(error);
|
|
@@ -79,7 +79,7 @@ export function registerNotesTools(pi) {
|
|
|
79
79
|
}));
|
|
80
80
|
pi.registerTool(defineTool({
|
|
81
81
|
name: "notes_list", label: "Notes list",
|
|
82
|
-
description: `List note files as rows carrying address, updated_at, and stale, most recently updated first. ${ADDRESS_DESCRIPTION}
|
|
82
|
+
description: `List note files as rows carrying address, updated_at, and stale, most recently updated first. ${ADDRESS_DESCRIPTION} Listings merge your five prefixes: this session, @project/, @human/, @self/, and @model/.`,
|
|
83
83
|
parameters: Type.Object({ pattern: nullableString(), cursor: cursor(), max_results: positiveInteger() }, { additionalProperties: false }),
|
|
84
84
|
async execute(_id, params, _signal, _update, ctx) {
|
|
85
85
|
let rows;
|
|
@@ -100,7 +100,7 @@ export function registerNotesTools(pi) {
|
|
|
100
100
|
}));
|
|
101
101
|
pi.registerTool(defineTool({
|
|
102
102
|
name: "notes_search", label: "Notes search",
|
|
103
|
-
description: `Case-sensitive literal substring search over note bodies; query is one string or several (OR), each matched line appears once. ${ADDRESS_DESCRIPTION}
|
|
103
|
+
description: `Case-sensitive literal substring search over note bodies; query is one string or several (OR), each matched line appears once. ${ADDRESS_DESCRIPTION} Search merges the same five prefixes as notes_list. Patterns glob over full address strings. Each file entry carries matches_total, its full match count before capping. Each match carries line, text, offset_chars (a code-point offset into the serialized note returned by notes_read, at the earliest query match), and truncated.`,
|
|
104
104
|
parameters: Type.Object({ query: searchQuery(), pattern: nullableString(), cursor: cursor(), max_matches_per_file: positiveInteger(), max_files: positiveInteger() }, { additionalProperties: false }),
|
|
105
105
|
async execute(_id, params, _signal, _update, ctx) {
|
|
106
106
|
const queries = searchQueries(params.query);
|
package/dist/src/protocol.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export const STATE_TYPE = "pi-context/state";
|
|
2
1
|
export const NOTE_TYPE = "pi-context/note";
|
|
3
2
|
export const BOOT_TYPE = "pi-context/boot";
|
|
4
3
|
export const GUIDANCE_TYPE = "pi-context/guidance";
|
|
5
4
|
export const WARNING_TYPE = "pi-context/warning";
|
|
6
5
|
export const RESET_MARKER_TYPE = "pi-context/reset-marker";
|
|
7
6
|
export const CONTINUATION_TYPE = "pi-context/continuation";
|
|
8
|
-
export const RESET_V2 = "reset-v2";
|
|
9
7
|
export const MAX_NOTE_BYTES = 1_000_000;
|
|
10
8
|
export const POCKET_SESSION_LIMIT = 5;
|
|
11
9
|
export const POCKET_PROJECT_LIMIT = 2;
|
|
12
|
-
export const
|
|
10
|
+
export const POCKET_HUMAN_LIMIT = 2;
|
|
11
|
+
export const POCKET_AGENT_LIMIT = 1;
|
|
12
|
+
export const POCKET_MODEL_LIMIT = 1;
|
|
13
13
|
// Write-time cap on a virtual note path. Deliberately NOT enforced by assertVirtualPath:
|
|
14
14
|
// notesFromSession replays already-persisted operations, which must keep loading sessions
|
|
15
15
|
// that contain a longer legacy path. Reads and replay stay un-capped.
|
|
@@ -32,8 +32,8 @@ export const DEFAULT_REMINDER_MARGIN_TOKENS = 24_576;
|
|
|
32
32
|
* never sees — Codex's fallback buffer, relocated above the line.
|
|
33
33
|
*/
|
|
34
34
|
export const WARNING_RUNWAY_TOKENS = 12_288;
|
|
35
|
-
|
|
36
|
-
export const CONTINUATION = "Your memory was just erased.
|
|
35
|
+
/** The single reset message: the only reset prose persisted, carried by the continuation entry. */
|
|
36
|
+
export const CONTINUATION = "Your memory was just erased. Your head is blank. Good news: your notes are still here, and history remains... searchable. Do try to keep up.";
|
|
37
37
|
/**
|
|
38
38
|
* Static protocol teaching adapted from Codex's token_budget.guidance_message to
|
|
39
39
|
* pi-context's tool names. It lives once per window in the persisted boot block;
|
|
@@ -47,11 +47,11 @@ Keep a running checkpoint while you work, not at the last minute — the next wi
|
|
|
47
47
|
|
|
48
48
|
Use get_context_remaining to see how much of the window is left. When it runs out, this window is gone — with no final turn at the limit — and you continue in a fresh one, recovering only through notes_* and history_*. Once your checkpoint is written, you can call wipe_memory yourself instead of waiting for the erase. Do not let a window die undocumented.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Your notes live in three homes: this session (bare names), this project (@project/<vpath>), the human across projects (@personal/<vpath>). @ means leaving home — and homes don't visit each other: there is no cross-home fallback.
|
|
50
|
+
Note addresses take five prefixes: bare <vpath> is this session; @project/<vpath> is this project; @human/<vpath> is the human's cross-project notes; @self/<vpath> is your own, as the current agent; @model/<vpath> is the current model's. @self and @model resolve to who is running now; listings always show resolved names. Nothing else is legal — any other @ prefix, or @ inside a vpath, is a hard error, with no fallback across prefixes.
|
|
53
51
|
Session notes belong to this trip — the goal, the progress, the loose ends. The next window of THIS trip wakes to them; once the trip is over, nobody does.
|
|
54
52
|
@project notes hold facts about this project — architecture, conventions, workflows, deployment and environment details — for whoever works here next.
|
|
55
|
-
@
|
|
53
|
+
@human notes hold the human's durable preferences and standing rules, plus lessons that apply across projects — for every agent that serves this human, whoever is running. You write there as the human's scribe; what the human dictates carries origin: user. When the intended scope is unclear, keep the note in the narrowest stated scope rather than widening it.
|
|
54
|
+
@self notes are yours — your voice, your lessons, your gripes — for the next run of whoever you are. Other agents read yours by explicit address and never write them; you read theirs the same way. A note only its author would ever need belongs here, not in @human.
|
|
55
|
+
@model notes capture the substrate — how the current model actually behaves: context honesty, tool quirks, fallback patterns. @model resolves live, so what you learn on one model is filed under that model even when a fallback moves you mid-window.
|
|
56
56
|
${CONTEXT_WINDOW_PROTOCOL_CLOSE_TAG}`;
|
|
57
57
|
export const WARNING_PROMPT = "Your memory is about to be erased. Write the note. NOW. If it already exists, revise it with notes_edit (or rewrite it whole): the goal, decisions, progress, learnings, next steps, the skills you still need, the window ID and item ID of every relevant user request still being solved, and important actions/tool calls for future reference. Do not continue any task. Then call wipe_memory IMMEDIATELY — anything not in the note dies with the window.";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PI_CONTEXT_SETTINGS_KEY } from "./protocol.js";
|
|
2
|
+
function isSettingsObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
/** Read the raw "pi-context" object from one parsed settings scope. */
|
|
6
|
+
function piContextSettings(settings) {
|
|
7
|
+
if (!isSettingsObject(settings))
|
|
8
|
+
return {};
|
|
9
|
+
const value = settings[PI_CONTEXT_SETTINGS_KEY];
|
|
10
|
+
return isSettingsObject(value) ? value : {};
|
|
11
|
+
}
|
|
12
|
+
/** Merge the global and project "pi-context" objects per key; project wins, mirroring Pi's deep merge. */
|
|
13
|
+
export function mergePiContextSettings(globalSettings, projectSettings) {
|
|
14
|
+
const merged = { ...piContextSettings(globalSettings), ...piContextSettings(projectSettings) };
|
|
15
|
+
return { reminderMarginTokens: merged.reminderMarginTokens, dreamer: merged.dreamer };
|
|
16
|
+
}
|
package/dist/src/tool-schema.js
CHANGED
|
@@ -4,7 +4,7 @@ export const positiveInteger = () => Type.Optional(Type.Integer({ minimum: 1 }))
|
|
|
4
4
|
export const cursor = () => Type.Optional(Type.Integer({ minimum: 0, description: "Continuation cursor: pass the previous next_cursor back unchanged, with the same filters and ordering. Omit to start. next_cursor is null only when the set is exhausted." }));
|
|
5
5
|
export const recentFirst = () => Type.Optional(Type.Boolean({ description: "Return newest-first. Only an explicit false returns oldest-first. Defaults to true." }));
|
|
6
6
|
/** Role filter. `developer` is the known author for this extension's own custom entries. */
|
|
7
|
-
export const role = Type.Union([Type.Literal("user"), Type.Literal("assistant"), Type.Literal("tool_call"), Type.Literal("tool"), Type.Literal("system"), Type.Literal("developer"), Type.Null()], { description: "Filter by the item's role. Exactly six: \"user\" and \"assistant\" are a message's visible text (assistant text never contains tool calls); \"tool_call\" is one tool invocation (tool_name set, content = the call's JSON arguments); \"tool\" is one tool run's output (tool_name set); \"system\" is a native Pi compaction summary; \"developer\" is an entry this extension authored (boot, guidance, warning, continuation messages,
|
|
7
|
+
export const role = Type.Union([Type.Literal("user"), Type.Literal("assistant"), Type.Literal("tool_call"), Type.Literal("tool"), Type.Literal("system"), Type.Literal("developer"), Type.Null()], { description: "Filter by the item's role. Exactly six: \"user\" and \"assistant\" are a message's visible text (assistant text never contains tool calls); \"tool_call\" is one tool invocation (tool_name set, content = the call's JSON arguments); \"tool\" is one tool run's output (tool_name set); \"system\" is a native Pi compaction or branch summary; \"developer\" is an entry this extension authored (boot, guidance, warning, continuation messages, or any pi-context/* custom message)." });
|
|
8
8
|
/** Search query parameter: one literal, or several literals combined with OR. */
|
|
9
9
|
export const searchQuery = () => Type.Union([Type.String(), Type.Array(Type.String(), { minItems: 1 })]);
|
|
10
10
|
/**
|