@maxiaochao/pi-toolkit 0.2.1 → 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 +4 -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/package.json +2 -2
package/CHANGELOG.md
CHANGED
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/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",
|