@astrosheep/pi-context 0.26.0 → 0.26.2
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 +2 -2
- package/dist/build-info.json +2 -2
- package/dist/extension.js +69 -64
- package/dist/src/context/budget.js +37 -18
- package/dist/src/context/prompts.js +3 -3
- package/dist/src/dream/doctor.js +0 -4
- package/dist/src/notes/frontmatter.d.ts +1 -2
- package/dist/src/notes/frontmatter.js +1 -6
- package/dist/src/notes/store.js +24 -40
- package/dist/src/protocol.d.ts +4 -4
- package/dist/src/protocol.js +4 -4
- package/dist/src/text-match.d.ts +5 -0
- package/dist/src/text-match.js +15 -0
- package/dist/src/tool-output.d.ts +1 -5
- package/dist/src/tool-output.js +1 -15
- package/dist/test/agent-loop.test.js +78 -10
- package/dist/test/boot.integration.test.js +5 -7
- package/dist/test/budget-settings.integration.test.js +20 -5
- package/dist/test/doctor.test.js +4 -5
- 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 +0 -11
- package/dist/test/helpers/extension.js +1 -75
- package/dist/test/history.integration.test.js +17 -19
- package/dist/test/notes-library.test.js +17 -0
- package/dist/test/notes.integration.test.js +47 -31
- package/dist/test/notes.test.js +51 -44
- package/docs/architecture.md +1 -1
- package/package.json +1 -1
- package/src/context/budget.ts +35 -18
- package/src/context/prompts.ts +3 -3
- package/src/dream/doctor.ts +0 -3
- package/src/notes/frontmatter.ts +1 -5
- package/src/notes/store.ts +24 -37
- package/src/protocol.ts +4 -4
- package/src/text-match.ts +13 -0
- package/src/tool-output.ts +2 -14
package/src/notes/store.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import type { Dirent } from "node:fs";
|
|
3
3
|
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { earliestMatchOffsetChars } from "../text-match.js";
|
|
5
6
|
import { assertAddress, assertGlobPattern, addressFor, globToRegExp } from "./address.js";
|
|
6
7
|
import { snapshotNotesContext, type NotesContext } from "./context.js";
|
|
7
8
|
import { MAX_NOTE_BYTES, MAX_NOTE_PATH_BYTES } from "./constants.js";
|
|
@@ -179,17 +180,6 @@ function matchLineNumbers(body: string, needle: string): number[] {
|
|
|
179
180
|
return lines;
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
/** Code-point offset of the earliest query occurrence, matching the serialized read text. */
|
|
183
|
-
function earliestMatchOffsetChars(text: string, queries: string[]): number {
|
|
184
|
-
let earliest = -1;
|
|
185
|
-
for (const query of queries) {
|
|
186
|
-
const index = text.indexOf(query);
|
|
187
|
-
if (index < 0) continue;
|
|
188
|
-
if (earliest < 0 || index < earliest) earliest = index;
|
|
189
|
-
}
|
|
190
|
-
return earliest <= 0 ? 0 : Array.from(text.slice(0, earliest)).length;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
183
|
/** Every mutation uses a tmp file renamed into place in the same directory. */
|
|
194
184
|
async function atomicWrite(path: string, content: string): Promise<void> {
|
|
195
185
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -348,11 +338,10 @@ export function createNotesStore(input: NotesContext): NotesStore {
|
|
|
348
338
|
});
|
|
349
339
|
}
|
|
350
340
|
|
|
351
|
-
async function
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
for (const home of await homesFor(context, stableOptions)) {
|
|
341
|
+
async function* scan(options: NotesQuery): AsyncGenerator<Omit<NoteRow, "sizeBytes">> {
|
|
342
|
+
const matcher = matcherFor(normalizePattern(options.pattern, context));
|
|
343
|
+
const homes = await homesFor(context, options);
|
|
344
|
+
for (const home of homes) {
|
|
356
345
|
const scope = home.scope;
|
|
357
346
|
const root = scopeDir(scope, context, home.who);
|
|
358
347
|
for (const path of await walkMarkdown(root)) {
|
|
@@ -362,9 +351,17 @@ export function createNotesStore(input: NotesContext): NotesStore {
|
|
|
362
351
|
const raw = await withPathQueue(fullPath, () => readFile(fullPath, "utf8"));
|
|
363
352
|
const { meta, body } = parseNote(raw);
|
|
364
353
|
meta.scope = scope;
|
|
365
|
-
|
|
354
|
+
yield { address, scope, path, meta, body };
|
|
366
355
|
}
|
|
367
356
|
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function list(options: NotesQuery = {}): Promise<NoteRow[]> {
|
|
360
|
+
const stableOptions = { ...options } as NotesQuery;
|
|
361
|
+
const rows: NoteRow[] = [];
|
|
362
|
+
for await (const row of scan(stableOptions)) {
|
|
363
|
+
rows.push({ ...row, sizeBytes: Buffer.byteLength(row.body, "utf8") });
|
|
364
|
+
}
|
|
368
365
|
rows.sort((a, b) => b.meta.updatedAt - a.meta.updatedAt || a.address.localeCompare(b.address));
|
|
369
366
|
return rows;
|
|
370
367
|
}
|
|
@@ -372,29 +369,19 @@ export function createNotesStore(input: NotesContext): NotesStore {
|
|
|
372
369
|
async function search(queries: string[], options: NotesQuery = {}): Promise<NoteSearchRow[]> {
|
|
373
370
|
const stableQueries = [...queries];
|
|
374
371
|
const stableOptions = { ...options } as NotesQuery;
|
|
375
|
-
const matcher = matcherFor(normalizePattern(stableOptions.pattern, context));
|
|
376
372
|
const rows: NoteSearchRow[] = [];
|
|
377
|
-
for (const
|
|
378
|
-
const scope =
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const { meta, body } = parseNote(raw);
|
|
386
|
-
meta.scope = scope;
|
|
387
|
-
const serializedBodyOffset = Array.from(serializeNote(accessedMeta(meta, scope, Date.now()), "")).length;
|
|
388
|
-
let baseChars = 0;
|
|
389
|
-
const matches: NoteMatch[] = [];
|
|
390
|
-
for (const [index, line] of body.split("\n").entries()) {
|
|
391
|
-
if (stableQueries.some((query) => line.includes(query))) {
|
|
392
|
-
matches.push({ line: index + 1, text: line, offsetChars: serializedBodyOffset + baseChars + earliestMatchOffsetChars(line, stableQueries) });
|
|
393
|
-
}
|
|
394
|
-
baseChars += Array.from(line).length + 1;
|
|
373
|
+
for await (const note of scan(stableOptions)) {
|
|
374
|
+
const { address, path, scope, meta, body } = note;
|
|
375
|
+
const serializedBodyOffset = Array.from(serializeNote(accessedMeta(meta, scope, Date.now()), "")).length;
|
|
376
|
+
let baseChars = 0;
|
|
377
|
+
const matches: NoteMatch[] = [];
|
|
378
|
+
for (const [index, line] of body.split("\n").entries()) {
|
|
379
|
+
if (stableQueries.some((query) => line.includes(query))) {
|
|
380
|
+
matches.push({ line: index + 1, text: line, offsetChars: serializedBodyOffset + baseChars + earliestMatchOffsetChars(line, stableQueries) });
|
|
395
381
|
}
|
|
396
|
-
|
|
382
|
+
baseChars += Array.from(line).length + 1;
|
|
397
383
|
}
|
|
384
|
+
if (matches.length > 0) rows.push({ address, path, scope, meta, matches });
|
|
398
385
|
}
|
|
399
386
|
rows.sort((a, b) => a.address.localeCompare(b.address));
|
|
400
387
|
return rows;
|
package/src/protocol.ts
CHANGED
|
@@ -6,10 +6,10 @@ export const RESET_MARKER_TYPE = "pi-context/reset-marker";
|
|
|
6
6
|
export const CONTINUATION_TYPE = "pi-context/continuation";
|
|
7
7
|
export { MAX_NOTE_BYTES, MAX_NOTE_PATH_BYTES } from "./notes/constants.js";
|
|
8
8
|
export const POCKET_SESSION_LIMIT = 5;
|
|
9
|
-
export const POCKET_PROJECT_LIMIT =
|
|
10
|
-
export const POCKET_HUMAN_LIMIT =
|
|
11
|
-
export const POCKET_AGENT_LIMIT =
|
|
12
|
-
export const POCKET_MODEL_LIMIT =
|
|
9
|
+
export const POCKET_PROJECT_LIMIT = 5;
|
|
10
|
+
export const POCKET_HUMAN_LIMIT = 5;
|
|
11
|
+
export const POCKET_AGENT_LIMIT = 5;
|
|
12
|
+
export const POCKET_MODEL_LIMIT = 3;
|
|
13
13
|
export const CONTEXT_WINDOW_OPEN_TAG = "<context_window>";
|
|
14
14
|
export const CONTEXT_WINDOW_CLOSE_TAG = "</context_window>";
|
|
15
15
|
export const CONTEXT_WINDOW_PROTOCOL_OPEN_TAG = "<context_window_protocol>";
|
|
@@ -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
|
|