@astrosheep/pi-context 0.25.2 → 0.26.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 +88 -7
- package/dist/build-info.json +2 -2
- package/dist/extension.js +635 -381
- 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 +51 -28
- 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 +276 -236
- 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/text-match.d.ts +5 -0
- package/dist/src/text-match.js +15 -0
- package/dist/src/tool-output.d.ts +97 -0
- package/dist/src/tool-output.js +1 -15
- 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 +387 -20
- package/dist/test/boot.integration.test.d.ts +1 -0
- package/dist/test/boot.integration.test.js +59 -35
- package/dist/test/budget-settings.integration.test.d.ts +1 -0
- package/dist/test/budget-settings.integration.test.js +28 -12
- 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-test-environment.d.ts +1 -0
- package/dist/test/helpers/extension-test-environment.js +9 -0
- package/dist/test/helpers/extension.d.ts +104 -0
- package/dist/test/helpers/extension.js +7 -81
- 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/history.integration.test.js +17 -19
- package/dist/test/notes-library.test.d.ts +1 -0
- package/dist/test/notes-library.test.js +128 -0
- package/dist/test/notes.integration.test.d.ts +1 -0
- package/dist/test/notes.integration.test.js +51 -55
- package/dist/test/notes.test.d.ts +1 -0
- package/dist/test/notes.test.js +162 -34
- 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 +52 -27
- 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 +289 -238
- 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/src/text-match.ts +13 -0
- package/src/tool-output.ts +2 -14
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { existsSync, renameSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import type { NotesContext } from "../../notes/context.js";
|
|
6
|
+
import { projectKey, slugify } from "../../notes/paths.js";
|
|
7
|
+
|
|
8
|
+
/** Pi's default notes root; environment access stays on the host side of the library boundary. */
|
|
9
|
+
export function notesRoot(): string {
|
|
10
|
+
const override = process.env.PI_NOTES_HOME;
|
|
11
|
+
return override && override.length > 0 ? resolve(override) : join(homedir(), ".agents", "notes");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Translate the current Pi runtime into a fresh, explicit identity snapshot. */
|
|
15
|
+
export function agentSlug(_ctx: ExtensionContext): string {
|
|
16
|
+
return slugify(process.env.PI_NOTES_AGENT ?? "root");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Pi's active model identity is resolved live so a mid-session switch retargets @model. */
|
|
20
|
+
export function modelSlug(ctx: ExtensionContext): string {
|
|
21
|
+
const id = ctx.model?.id;
|
|
22
|
+
return id ? slugify(id.split("/").pop() ?? id) : "default";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Translate the current Pi runtime into one explicit notes identity snapshot. */
|
|
26
|
+
export function notesContextFromPi(ctx: ExtensionContext, home = notesRoot()): NotesContext {
|
|
27
|
+
return {
|
|
28
|
+
home,
|
|
29
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
30
|
+
projectKey: projectKey(ctx.cwd),
|
|
31
|
+
agent: agentSlug(ctx),
|
|
32
|
+
model: modelSlug(ctx),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One-time host activation migration from the legacy personal/ directory. */
|
|
37
|
+
export function migrateLegacyHomes(home = notesRoot()): string | undefined {
|
|
38
|
+
const legacy = join(home, "personal");
|
|
39
|
+
const modern = join(home, "human");
|
|
40
|
+
if (!existsSync(legacy)) return undefined;
|
|
41
|
+
if (existsSync(modern)) return "both personal/ and human/ exist under the notes home; migrate by hand, no automatic merge";
|
|
42
|
+
renameSync(legacy, modern);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { SessionReader } from "
|
|
2
|
-
import { MAX_NOTE_BYTES, NOTE_TYPE } from "
|
|
3
|
-
import { assertVirtualPath } from "
|
|
1
|
+
import type { SessionReader } from "../../session-reader.js";
|
|
2
|
+
import { MAX_NOTE_BYTES, NOTE_TYPE } from "../../protocol.js";
|
|
3
|
+
import { assertVirtualPath } from "../../notes/address.js";
|
|
4
4
|
|
|
5
5
|
export type NoteFile = { text: string; stale: boolean; createdAt: number; updatedAt: number };
|
|
6
6
|
export type NoteOperation = {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import
|
|
2
|
+
import type { NotesContext } from "../../notes/context.js";
|
|
3
|
+
import { createNotesStore, type NoteRow, type NotesQuery, type Scope } from "../../notes/index.js";
|
|
4
|
+
import { notesContextFromPi } from "./adapter.js";
|
|
3
5
|
|
|
4
6
|
const NOTES_HOMES = [
|
|
5
7
|
{ scope: "session", label: "this session" },
|
|
@@ -10,7 +12,7 @@ const NOTES_HOMES = [
|
|
|
10
12
|
] as const satisfies ReadonlyArray<{ scope: Scope; label: string }>;
|
|
11
13
|
|
|
12
14
|
export type NotesHome = (typeof NOTES_HOMES)[number];
|
|
13
|
-
export type NotesLoader = (ctx: ExtensionContext, scope: Scope) => NoteRow[]
|
|
15
|
+
export type NotesLoader = (ctx: ExtensionContext, scope: Scope) => NoteRow[] | Promise<NoteRow[]>;
|
|
14
16
|
export type NotesSnapshot = {
|
|
15
17
|
/** Wall-clock instant captured when this boot began; rendering never consults Date.now(). */
|
|
16
18
|
readonly openedAt: number;
|
|
@@ -18,17 +20,24 @@ export type NotesSnapshot = {
|
|
|
18
20
|
readonly unavailable: readonly NotesHome[];
|
|
19
21
|
};
|
|
20
22
|
|
|
23
|
+
function queryForScope(scope: Scope): NotesQuery {
|
|
24
|
+
if (scope === "agent" || scope === "model") return { scope };
|
|
25
|
+
return { scope };
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
/**
|
|
22
29
|
* Acquire the five homes once for one boot. Only filesystem-style errno failures are isolated;
|
|
23
30
|
* malformed note data and unrelated construction errors remain visible to the caller.
|
|
24
31
|
*/
|
|
25
|
-
export function loadNotesSnapshot(ctx: ExtensionContext, loadHome
|
|
32
|
+
export async function loadNotesSnapshot(ctx: ExtensionContext, loadHome?: NotesLoader, identity: NotesContext = notesContextFromPi(ctx)): Promise<NotesSnapshot> {
|
|
26
33
|
const openedAt = Date.now();
|
|
34
|
+
const store = createNotesStore(identity);
|
|
35
|
+
const load = loadHome ?? ((_context: ExtensionContext, scope: Scope) => store.list(queryForScope(scope)));
|
|
27
36
|
const homes = new Map<Scope, readonly NoteRow[]>();
|
|
28
37
|
const unavailable: NotesHome[] = [];
|
|
29
38
|
for (const home of NOTES_HOMES) {
|
|
30
39
|
try {
|
|
31
|
-
homes.set(home.scope,
|
|
40
|
+
homes.set(home.scope, await load(ctx, home.scope));
|
|
32
41
|
} catch (error) {
|
|
33
42
|
const code = typeof error === "object" && error !== null ? (error as NodeJS.ErrnoException).code : undefined;
|
|
34
43
|
if (typeof code !== "string" || !/^E[A-Z0-9_]+$/.test(code) || code.startsWith("ERR_")) throw error;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
-
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { localIso } from "
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { NoteError, editNote, listNotes, readNote, searchNotes, writeNote } from "./store.js";
|
|
2
|
+
import { defineTool, generateDiffString, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { localIso } from "../../notes/frontmatter.js";
|
|
4
|
+
import { createNotesStore, NoteError, type NoteChange, type NoteReadResult, type NoteRow, type NoteSearchRow, type Origin } from "../../notes/index.js";
|
|
5
|
+
import { DEFAULT_READ_WINDOW_CHARS, MAX_READ_WINDOW_CHARS, middleTruncate, output, outputRaw, page, prefixFit, readCharacterWindow, readWindowBlock, withinTextBudget } from "../../tool-output.js";
|
|
6
|
+
import { cursor, nullableString, positiveInteger, searchQueries, searchQuery } from "../../tool-schema.js";
|
|
7
|
+
import { notesContextFromPi } from "./adapter.js";
|
|
9
8
|
|
|
10
9
|
const ORIGIN = Type.Optional(Type.Union([Type.Literal("user"), Type.Literal("self"), Type.Literal("external")], {
|
|
11
10
|
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.",
|
|
@@ -15,23 +14,27 @@ const ADDRESS_DESCRIPTION = "Address forms are bare `<vpath>` for this session,
|
|
|
15
14
|
function failure(error: unknown) {
|
|
16
15
|
if (error instanceof NoteError) {
|
|
17
16
|
const payload: Record<string, unknown> = { error: error.message };
|
|
18
|
-
if (error.
|
|
19
|
-
if (error.
|
|
17
|
+
if (error.lineNumbers) payload.line_numbers = error.lineNumbers;
|
|
18
|
+
if (error.editIndex !== undefined) payload.edit_index = error.editIndex;
|
|
20
19
|
return output(payload);
|
|
21
20
|
}
|
|
22
21
|
throw error;
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
function renderDiff(change: NoteChange): string {
|
|
25
|
+
if (change.kind === "none") return "";
|
|
26
|
+
return generateDiffString(change.before, change.after).diff;
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
export function registerNotesTools(pi: ExtensionAPI) {
|
|
26
30
|
pi.registerTool(defineTool({
|
|
27
31
|
name: "notes_write", label: "Notes write",
|
|
28
|
-
description: `Create or replace a note as a real markdown file, and name it for what it holds: a fresh window sees only an index entry, never the note itself. ${ADDRESS_DESCRIPTION} Keep notes small and split by topic — by what the note is about, never by who said it (authorship is origin's job); a rewrite replaces the body whole while preserving
|
|
32
|
+
description: `Create or replace a note as a real markdown file, and name it for what it holds: a fresh window sees only an index entry, never the note itself. ${ADDRESS_DESCRIPTION} Keep notes small and split by topic — by what the note is about, never by who said it (authorship is origin's job); a rewrite replaces the body whole while preserving createdAt and every other frontmatter key. stale: true marks the note closed so it leaves the boot index but stays readable and searchable.`,
|
|
29
33
|
parameters: Type.Object({ address: Type.String(), content: Type.String(), origin: ORIGIN, stale: Type.Optional(Type.Boolean()) }, { additionalProperties: false }), executionMode: "sequential",
|
|
30
34
|
async execute(_id, params, _signal, _update, ctx) {
|
|
31
35
|
const content = params.content;
|
|
32
36
|
try {
|
|
33
|
-
|
|
34
|
-
writeNote(ctx, destination.path, content, { scope: destination.scope, who: destination.who, origin: (params.origin ?? "self") as Origin, stale: params.stale });
|
|
37
|
+
await createNotesStore(notesContextFromPi(ctx)).write(params.address, content, { origin: (params.origin ?? "self") as Origin, stale: params.stale });
|
|
35
38
|
return output({ address: params.address, written: true });
|
|
36
39
|
} catch (error) { return failure(error); }
|
|
37
40
|
},
|
|
@@ -43,8 +46,8 @@ export function registerNotesTools(pi: ExtensionAPI) {
|
|
|
43
46
|
parameters: Type.Object({ address: Type.String(), edits: Type.Optional(Type.Array(Type.Object({ oldText: Type.String(), newText: Type.String() }, { additionalProperties: false }))), origin: ORIGIN, stale: Type.Optional(Type.Boolean()), replace_all: Type.Optional(Type.Boolean()) }, { additionalProperties: false }), executionMode: "sequential",
|
|
44
47
|
async execute(_id, params, _signal, _update, ctx) {
|
|
45
48
|
try {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
49
|
+
const { applied, change } = await createNotesStore(notesContextFromPi(ctx)).edit(params.address, params.edits, { origin: params.origin as Origin | undefined, stale: params.stale, replaceAll: params.replace_all });
|
|
50
|
+
const diff = renderDiff(change);
|
|
48
51
|
return output({ address: params.address, applied, diff });
|
|
49
52
|
} catch (error) { return failure(error); }
|
|
50
53
|
},
|
|
@@ -55,10 +58,9 @@ export function registerNotesTools(pi: ExtensionAPI) {
|
|
|
55
58
|
description: `Read a character window of a note file, frontmatter included. ${ADDRESS_DESCRIPTION} offset_chars is the code-point offset to start from (default 0) — a negative value counts back from the end — and limit_chars caps the window (default ${DEFAULT_READ_WINDOW_CHARS}, max ${MAX_READ_WINDOW_CHARS}). Each response delivers the longest fitting prefix of that window in the shared READ WINDOW block: concatenate only the content after the block to reconstruct the note.`,
|
|
56
59
|
parameters: Type.Object({ address: Type.String(), offset_chars: Type.Optional(Type.Integer({ description: "Code-point offset to start from (default 0). A negative value counts back from the end; the response echoes the resolved absolute offset. Pass the previous next_offset_chars back unchanged to continue." })), limit_chars: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_READ_WINDOW_CHARS, description: `Largest requested window in code points (default ${DEFAULT_READ_WINDOW_CHARS}). A window too large for the wire budget is cut short; next_offset_chars names where the next read resumes.` })) }, { additionalProperties: false }),
|
|
57
60
|
async execute(_id, params, _signal, _update, ctx) {
|
|
58
|
-
let note:
|
|
61
|
+
let note: NoteReadResult | undefined;
|
|
59
62
|
try {
|
|
60
|
-
|
|
61
|
-
note = readNote(ctx, destination.path, destination.scope, destination.who);
|
|
63
|
+
note = await createNotesStore(notesContextFromPi(ctx)).read(params.address);
|
|
62
64
|
} catch (error) { return failure(error); }
|
|
63
65
|
if (!note) return output({ error: "note not found", address: params.address });
|
|
64
66
|
const text = note.text;
|
|
@@ -76,9 +78,9 @@ export function registerNotesTools(pi: ExtensionAPI) {
|
|
|
76
78
|
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/.`,
|
|
77
79
|
parameters: Type.Object({ pattern: nullableString(), cursor: cursor(), max_results: positiveInteger() }, { additionalProperties: false }),
|
|
78
80
|
async execute(_id, params, _signal, _update, ctx) {
|
|
79
|
-
let rows:
|
|
80
|
-
try { rows =
|
|
81
|
-
const files: Array<{ address: string; stale: boolean; updated_at: string; address_truncated?: boolean }> = rows.map((row) => ({ address: row.address, stale: row.meta.stale, updated_at: localIso(row.meta.
|
|
81
|
+
let rows: NoteRow[];
|
|
82
|
+
try { rows = await createNotesStore(notesContextFromPi(ctx)).list({ pattern: params.pattern ?? undefined }); } catch (error) { return failure(error); }
|
|
83
|
+
const files: Array<{ address: string; stale: boolean; updated_at: string; address_truncated?: boolean }> = rows.map((row) => ({ address: row.address, stale: row.meta.stale, updated_at: localIso(row.meta.updatedAt) }));
|
|
82
84
|
return output(page(files, params.cursor ?? 0, "files", params.max_results, (file, fits) => {
|
|
83
85
|
if (fits(file)) return file;
|
|
84
86
|
const address = middleTruncate(file.address, (candidate) => fits({ ...file, address: candidate, address_truncated: true }));
|
|
@@ -93,12 +95,12 @@ export function registerNotesTools(pi: ExtensionAPI) {
|
|
|
93
95
|
parameters: Type.Object({ query: searchQuery(), pattern: nullableString(), cursor: cursor(), max_matches_per_file: positiveInteger(), max_files: positiveInteger() }, { additionalProperties: false }),
|
|
94
96
|
async execute(_id, params, _signal, _update, ctx) {
|
|
95
97
|
const queries = searchQueries(params.query);
|
|
96
|
-
let rows:
|
|
97
|
-
try { rows =
|
|
98
|
+
let rows: NoteSearchRow[];
|
|
99
|
+
try { rows = await createNotesStore(notesContextFromPi(ctx)).search(queries, { pattern: params.pattern ?? undefined }); } catch (error) { return failure(error); }
|
|
98
100
|
const maxPerFile = params.max_matches_per_file ?? Number.POSITIVE_INFINITY;
|
|
99
101
|
const result: Array<{ address: string; updated_at: string; stale: boolean; matches_total: number; matches: Array<{ line: number; text: string; truncated: boolean; offset_chars: number }>; address_truncated?: boolean }> = rows.map((row) => {
|
|
100
102
|
const matches = row.matches.map((match) => ({ line: match.line, text: match.text, truncated: false, offset_chars: match.offsetChars }));
|
|
101
|
-
return { address: row.address, updated_at: localIso(row.meta.
|
|
103
|
+
return { address: row.address, updated_at: localIso(row.meta.updatedAt), stale: row.meta.stale, matches_total: matches.length, matches: matches.slice(0, maxPerFile) };
|
|
102
104
|
});
|
|
103
105
|
const fitFile = (file: (typeof result)[number], fits: (candidate: (typeof result)[number]) => boolean) => {
|
|
104
106
|
if (fits(file)) return file;
|
package/src/protocol.ts
CHANGED
|
@@ -4,16 +4,12 @@ export const GUIDANCE_TYPE = "pi-context/guidance";
|
|
|
4
4
|
export const WARNING_TYPE = "pi-context/warning";
|
|
5
5
|
export const RESET_MARKER_TYPE = "pi-context/reset-marker";
|
|
6
6
|
export const CONTINUATION_TYPE = "pi-context/continuation";
|
|
7
|
-
export
|
|
7
|
+
export { MAX_NOTE_BYTES, MAX_NOTE_PATH_BYTES } from "./notes/constants.js";
|
|
8
8
|
export const POCKET_SESSION_LIMIT = 5;
|
|
9
9
|
export const POCKET_PROJECT_LIMIT = 2;
|
|
10
10
|
export const POCKET_HUMAN_LIMIT = 2;
|
|
11
11
|
export const POCKET_AGENT_LIMIT = 1;
|
|
12
12
|
export const POCKET_MODEL_LIMIT = 1;
|
|
13
|
-
// Write-time cap on a virtual note path. Deliberately NOT enforced by assertVirtualPath:
|
|
14
|
-
// notesFromSession replays already-persisted operations, which must keep loading sessions
|
|
15
|
-
// that contain a longer legacy path. Reads and replay stay un-capped.
|
|
16
|
-
export const MAX_NOTE_PATH_BYTES = 512;
|
|
17
13
|
export const CONTEXT_WINDOW_OPEN_TAG = "<context_window>";
|
|
18
14
|
export const CONTEXT_WINDOW_CLOSE_TAG = "</context_window>";
|
|
19
15
|
export const CONTEXT_WINDOW_PROTOCOL_OPEN_TAG = "<context_window_protocol>";
|
|
@@ -57,4 +53,7 @@ Session notes belong to this trip — the goal, the progress, the loose ends. Th
|
|
|
57
53
|
${CONTEXT_WINDOW_PROTOCOL_CLOSE_TAG}`;
|
|
58
54
|
|
|
59
55
|
export const WARNING_PROMPT =
|
|
60
|
-
"Your memory is about to be erased.
|
|
56
|
+
"Your memory is about to be erased. Stop the current task and 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. Use as many note/tool turns as needed to finish the checkpoint. When it is ready, call wipe_memory to reset immediately; otherwise finish normally and the extension will reset at your normal stop. Anything not in the note dies with the window.";
|
|
57
|
+
|
|
58
|
+
/** Identical hidden close-out message for manual and budget-triggered requests. */
|
|
59
|
+
export const WARNING_CONTENT = `${GUIDANCE_OPEN_TAG}\n${WARNING_PROMPT}\n${GUIDANCE_CLOSE_TAG}`;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-point offset of the earliest occurrence of any of `queries` in `text`, or 0 when
|
|
3
|
+
* none occurs. Shared by the two search tools so a match address is computed identically.
|
|
4
|
+
*/
|
|
5
|
+
export function earliestMatchOffsetChars(text: string, queries: string[]): number {
|
|
6
|
+
let earliest = -1;
|
|
7
|
+
for (const query of queries) {
|
|
8
|
+
const index = text.indexOf(query);
|
|
9
|
+
if (index < 0) continue;
|
|
10
|
+
if (earliest < 0 || index < earliest) earliest = index;
|
|
11
|
+
}
|
|
12
|
+
return earliest <= 0 ? 0 : Array.from(text.slice(0, earliest)).length;
|
|
13
|
+
}
|
package/src/tool-output.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { earliestMatchOffsetChars } from "./text-match.js";
|
|
2
|
+
|
|
1
3
|
export const TOOL_OUTPUT_MAX_BYTES = 32 * 1024;
|
|
2
4
|
export const DEFAULT_READ_WINDOW_CHARS = 12000;
|
|
3
5
|
export const MAX_READ_WINDOW_CHARS = 50000;
|
|
@@ -125,20 +127,6 @@ export function readWindowBlock(identity: ReadonlyArray<readonly [string, string
|
|
|
125
127
|
return `--- READ WINDOW ---\n${fields}\nchars: [${window.offset_chars},${end}) of ${window.total_chars}\nnext_offset_chars: ${next}\n`;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
/**
|
|
129
|
-
* Code-point offset of the earliest occurrence of any of `queries` in `text`, or 0 when
|
|
130
|
-
* none occurs. Shared by the two search tools so a match address is computed identically.
|
|
131
|
-
*/
|
|
132
|
-
export function earliestMatchOffsetChars(text: string, queries: string[]): number {
|
|
133
|
-
let earliest = -1;
|
|
134
|
-
for (const query of queries) {
|
|
135
|
-
const index = text.indexOf(query);
|
|
136
|
-
if (index < 0) continue;
|
|
137
|
-
if (earliest < 0 || index < earliest) earliest = index;
|
|
138
|
-
}
|
|
139
|
-
return earliest <= 0 ? 0 : Array.from(text.slice(0, earliest)).length;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
130
|
/** Shrink a single page item to fit; only invoked when that item alone exceeds the budget. */
|
|
143
131
|
export type ItemTruncator<T> = (item: T, fits: (candidate: T) => boolean) => T;
|
|
144
132
|
|