@maxiaochao/pi-toolkit 0.2.0 → 0.2.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/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/extensions/btw-scroll.ts +9 -0
- package/extensions/btw.ts +81 -3
- package/extensions/tests/btw-history.mjs +14 -0
- package/global/AGENTS.md +5 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 2026-09-03 - v0.2.2
|
|
2
|
+
|
|
3
|
+
- let the focused `/btw` overlay scroll its full transcript with Up/Down and PageUp/PageDown
|
|
4
|
+
|
|
5
|
+
## 2026-09-02 - v0.2.1
|
|
6
|
+
|
|
7
|
+
- update global agent instructions for harness-native file editing
|
|
8
|
+
- remove the HTML artifact output preference from the global instructions
|
|
9
|
+
|
|
1
10
|
## 2026-09-02 - v0.2.0
|
|
2
11
|
|
|
3
12
|
- bundle the Apache-2.0 `web-browser` skill for Chrome/Chromium CDP automation
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ To synchronize the file directly from a checkout instead:
|
|
|
38
38
|
bash scripts/install.sh
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
The `/btw` side chat is a separate agent session. It can inspect the main conversation and repository, and its thread stays out of the main context until you close it and choose to inject a summary. Use `/btw` to open it or `/btw <question>` to ask directly.
|
|
41
|
+
The `/btw` side chat is a separate agent session. It can inspect the main conversation and repository, and its thread stays out of the main context until you close it and choose to inject a summary. Use `/btw` to open it or `/btw <question>` to ask directly. While the BTW overlay has focus, use `Up` / `Down` to scroll its transcript one line at a time and `PageUp` / `PageDown` to move by one transcript page.
|
|
42
42
|
|
|
43
43
|
The bundled `nightowl` theme is available to Pi through the package manifest and is selected automatically on npm installation only when no theme is already configured.
|
|
44
44
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function getBtwPageScrollSize(viewportHeight: number): number {
|
|
2
|
+
return Math.max(1, Math.floor(viewportHeight));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function clampBtwScrollTop(scrollTop: number, contentHeight: number, viewportHeight: number): number {
|
|
6
|
+
const maxScrollTop = Math.max(0, Math.floor(contentHeight) - Math.floor(viewportHeight));
|
|
7
|
+
const requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : 0;
|
|
8
|
+
return Math.max(0, Math.min(maxScrollTop, requested));
|
|
9
|
+
}
|
package/extensions/btw.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type OverlayHandle,
|
|
24
24
|
type TUI,
|
|
25
25
|
} from "@earendil-works/pi-tui";
|
|
26
|
+
import { clampBtwScrollTop, getBtwPageScrollSize } from "./btw-scroll.ts";
|
|
26
27
|
|
|
27
28
|
const BTW_ENTRY_TYPE = "btw-thread-entry";
|
|
28
29
|
const BTW_RESET_TYPE = "btw-thread-reset";
|
|
@@ -59,6 +60,7 @@ type OverlayRuntime = {
|
|
|
59
60
|
close?: () => void;
|
|
60
61
|
finish?: () => void;
|
|
61
62
|
setDraft?: (value: string) => void;
|
|
63
|
+
scrollToEnd?: () => void;
|
|
62
64
|
closed?: boolean;
|
|
63
65
|
};
|
|
64
66
|
|
|
@@ -201,6 +203,10 @@ class BtwOverlay extends Container implements Focusable {
|
|
|
201
203
|
private readonly getStatus: () => string;
|
|
202
204
|
private readonly onSubmitCallback: (value: string) => void;
|
|
203
205
|
private readonly onDismissCallback: () => void;
|
|
206
|
+
private transcriptScrollTop = 0;
|
|
207
|
+
private transcriptFollowingEnd = true;
|
|
208
|
+
private transcriptContentHeight = 0;
|
|
209
|
+
private transcriptViewportHeight = 0;
|
|
204
210
|
private _focused = false;
|
|
205
211
|
|
|
206
212
|
get focused(): boolean {
|
|
@@ -245,9 +251,65 @@ class BtwOverlay extends Container implements Focusable {
|
|
|
245
251
|
return;
|
|
246
252
|
}
|
|
247
253
|
|
|
254
|
+
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
255
|
+
this.scrollTranscriptBy(-1);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (this.keybindings.matches(data, "tui.select.down")) {
|
|
260
|
+
this.scrollTranscriptBy(1);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const pageSize = getBtwPageScrollSize(this.transcriptViewportHeight);
|
|
265
|
+
if (this.keybindings.matches(data, "tui.select.pageUp")) {
|
|
266
|
+
this.scrollTranscriptBy(-pageSize);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (this.keybindings.matches(data, "tui.select.pageDown")) {
|
|
271
|
+
this.scrollTranscriptBy(pageSize);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
248
275
|
this.input.handleInput(data);
|
|
249
276
|
}
|
|
250
277
|
|
|
278
|
+
private scrollTranscriptBy(lines: number): void {
|
|
279
|
+
const maxScrollTop = Math.max(0, this.transcriptContentHeight - this.transcriptViewportHeight);
|
|
280
|
+
const currentScrollTop = this.transcriptFollowingEnd
|
|
281
|
+
? maxScrollTop
|
|
282
|
+
: clampBtwScrollTop(this.transcriptScrollTop, this.transcriptContentHeight, this.transcriptViewportHeight);
|
|
283
|
+
const nextScrollTop = clampBtwScrollTop(
|
|
284
|
+
currentScrollTop + Math.trunc(lines),
|
|
285
|
+
this.transcriptContentHeight,
|
|
286
|
+
this.transcriptViewportHeight,
|
|
287
|
+
);
|
|
288
|
+
const nextFollowingEnd = nextScrollTop === maxScrollTop;
|
|
289
|
+
if (nextScrollTop === currentScrollTop && nextFollowingEnd === this.transcriptFollowingEnd) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.transcriptScrollTop = nextScrollTop;
|
|
294
|
+
this.transcriptFollowingEnd = nextFollowingEnd;
|
|
295
|
+
this.tui.requestRender();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
scrollTranscriptToEnd(): void {
|
|
299
|
+
const nextScrollTop = clampBtwScrollTop(
|
|
300
|
+
this.transcriptContentHeight,
|
|
301
|
+
this.transcriptContentHeight,
|
|
302
|
+
this.transcriptViewportHeight,
|
|
303
|
+
);
|
|
304
|
+
if (this.transcriptFollowingEnd && this.transcriptScrollTop === nextScrollTop) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.transcriptScrollTop = nextScrollTop;
|
|
309
|
+
this.transcriptFollowingEnd = true;
|
|
310
|
+
this.tui.requestRender();
|
|
311
|
+
}
|
|
312
|
+
|
|
251
313
|
setDraft(value: string): void {
|
|
252
314
|
this.input.setValue(value);
|
|
253
315
|
this.tui.requestRender();
|
|
@@ -279,7 +341,19 @@ class BtwOverlay extends Container implements Focusable {
|
|
|
279
341
|
|
|
280
342
|
// Markdown renders to innerWidth already — no manual wrapping needed
|
|
281
343
|
const transcript = this.getTranscript(innerWidth, this.theme);
|
|
282
|
-
|
|
344
|
+
this.transcriptContentHeight = transcript.length;
|
|
345
|
+
this.transcriptViewportHeight = transcriptHeight;
|
|
346
|
+
const maxScrollTop = Math.max(0, this.transcriptContentHeight - this.transcriptViewportHeight);
|
|
347
|
+
if (this.transcriptFollowingEnd) {
|
|
348
|
+
this.transcriptScrollTop = maxScrollTop;
|
|
349
|
+
} else {
|
|
350
|
+
this.transcriptScrollTop = clampBtwScrollTop(
|
|
351
|
+
this.transcriptScrollTop,
|
|
352
|
+
this.transcriptContentHeight,
|
|
353
|
+
this.transcriptViewportHeight,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const visibleTranscript = transcript.slice(this.transcriptScrollTop, this.transcriptScrollTop + transcriptHeight);
|
|
283
357
|
const transcriptPadding = Math.max(0, transcriptHeight - visibleTranscript.length);
|
|
284
358
|
|
|
285
359
|
const status = this.getStatus();
|
|
@@ -308,7 +382,7 @@ class BtwOverlay extends Container implements Focusable {
|
|
|
308
382
|
lines.push(
|
|
309
383
|
`${this.theme.fg("borderMuted", "│")}${inputLine}${this.theme.fg("borderMuted", "│")}`,
|
|
310
384
|
);
|
|
311
|
-
lines.push(this.frameLine(this.theme.fg("dim", "Enter submit · Esc close"), innerWidth));
|
|
385
|
+
lines.push(this.frameLine(this.theme.fg("dim", "↑/↓ scroll · PgUp/PgDn page · Enter submit · Esc close"), innerWidth));
|
|
312
386
|
lines.push(this.borderLine(innerWidth, "bottom"));
|
|
313
387
|
|
|
314
388
|
return lines;
|
|
@@ -396,7 +470,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
396
470
|
}
|
|
397
471
|
|
|
398
472
|
const lines: string[] = [];
|
|
399
|
-
for (const item of thread
|
|
473
|
+
for (const item of thread) {
|
|
400
474
|
// User message
|
|
401
475
|
const userText = item.question.trim().split("\n")[0];
|
|
402
476
|
lines.push(theme.fg("accent", theme.bold("You: ")) + truncateToWidth(userText, width - 5, "…"));
|
|
@@ -509,6 +583,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
509
583
|
setOverlayDraft("");
|
|
510
584
|
setOverlayStatus("Ready");
|
|
511
585
|
await disposeSideSession();
|
|
586
|
+
overlayRuntime?.scrollToEnd?.();
|
|
512
587
|
if (persist) {
|
|
513
588
|
const details: BtwResetDetails = { timestamp: Date.now() };
|
|
514
589
|
pi.appendEntry(BTW_RESET_TYPE, details);
|
|
@@ -546,6 +621,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
546
621
|
thread.push(details);
|
|
547
622
|
}
|
|
548
623
|
|
|
624
|
+
overlayRuntime?.scrollToEnd?.();
|
|
549
625
|
syncOverlay();
|
|
550
626
|
}
|
|
551
627
|
|
|
@@ -691,6 +767,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
691
767
|
overlay.focused = true;
|
|
692
768
|
overlay.setDraft(overlayDraft);
|
|
693
769
|
runtime.setDraft = (value) => overlay.setDraft(value);
|
|
770
|
+
runtime.scrollToEnd = () => overlay.scrollTranscriptToEnd();
|
|
694
771
|
runtime.refresh = () => {
|
|
695
772
|
overlay.focused = runtime.handle?.isFocused() ?? false;
|
|
696
773
|
tui.requestRender();
|
|
@@ -836,6 +913,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
836
913
|
return;
|
|
837
914
|
}
|
|
838
915
|
|
|
916
|
+
overlayRuntime?.scrollToEnd?.();
|
|
839
917
|
const side = await ensureSideSession(ctx);
|
|
840
918
|
if (!side) {
|
|
841
919
|
notify(ctx, "Unable to create BTW side session.", "error");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { clampBtwScrollTop, getBtwPageScrollSize } from "../btw-scroll.ts";
|
|
3
|
+
|
|
4
|
+
assert.equal(getBtwPageScrollSize(18), 18, "page scrolling uses the transcript viewport height");
|
|
5
|
+
assert.equal(getBtwPageScrollSize(8.9), 8, "page scrolling uses a whole number of lines");
|
|
6
|
+
assert.equal(getBtwPageScrollSize(0), 1, "page scrolling always moves at least one line");
|
|
7
|
+
|
|
8
|
+
assert.equal(clampBtwScrollTop(-3, 20, 8), 0, "scroll top clamps at the start");
|
|
9
|
+
assert.equal(clampBtwScrollTop(4, 20, 8), 4, "scroll top remains inside the range");
|
|
10
|
+
assert.equal(clampBtwScrollTop(99, 20, 8), 12, "scroll top clamps at the end");
|
|
11
|
+
assert.equal(clampBtwScrollTop(5, 4, 8), 0, "short content cannot scroll");
|
|
12
|
+
assert.equal(clampBtwScrollTop(Number.NaN, 20, 8), 0, "invalid scroll input falls back to the start");
|
|
13
|
+
|
|
14
|
+
console.log("BTW history scrolling tests passed");
|
package/global/AGENTS.md
CHANGED
|
@@ -100,11 +100,10 @@ After each significant step:
|
|
|
100
100
|
2. Does the standard library cover it? Use it.
|
|
101
101
|
3. Only then: write the minimum code that works.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
## 9. Use Available Editing Tools
|
|
104
104
|
|
|
105
|
+
**Follow the current harness's tool surface; do not assume a shell patch helper exists.**
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
When using HTML, create a self-contained, responsive artifact in the project's established output location, or `.cache/html/<descriptive-name>.html` when no convention exists. Use semantic HTML, accessible controls, escaped code blocks, and small useful interactions. Return a concise Markdown summary with a clickable local file path; if a preview/server tool is available, start it and return the preview URL only after verifying the page is not blank and its assets load.
|
|
107
|
+
- Use the native file-editing tools exposed by the current environment. In pi, use `edit` for precise replacements and `write` for new files or complete rewrites.
|
|
108
|
+
- Do not run `apply_patch` as a shell command unless `command -v apply_patch` confirms that executable is available.
|
|
109
|
+
- If the harness exposes `apply_patch` as a dedicated tool rather than a command, invoke it only through that tool interface.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maxiaochao/pi-toolkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Personal pi coding agent toolkit: /todos, /btw, /cache_export extensions, browser automation, and bundled theme/skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"ws": "^8.19.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
-
"test": "cd extensions/cache-export && node --experimental-strip-types tests/run-tests.mjs",
|
|
46
|
+
"test": "cd extensions/cache-export && node --experimental-strip-types tests/run-tests.mjs && node --experimental-strip-types ../tests/btw-history.mjs",
|
|
47
47
|
"load-test": "pi -ne -e ./extensions/todo.ts && pi -ne -e ./extensions/cache-export/index.ts && pi -ne -e ./extensions/btw.ts",
|
|
48
48
|
"eval:agents": "node evaluations/agents-md/run.mjs",
|
|
49
49
|
"eval:agents:test": "node evaluations/agents-md/tests/run-tests.mjs",
|