@dungle-scrubs/tallow 0.8.26 → 0.8.28
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 +42 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +1 -0
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +40 -1
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/model-metadata-overrides.d.ts +2 -5
- package/dist/model-metadata-overrides.d.ts.map +1 -1
- package/dist/model-metadata-overrides.js +23 -12
- package/dist/model-metadata-overrides.js.map +1 -1
- package/dist/pid-manager.d.ts +2 -9
- package/dist/pid-manager.d.ts.map +1 -1
- package/dist/pid-manager.js +1 -58
- package/dist/pid-manager.js.map +1 -1
- package/dist/pid-schema.d.ts +51 -0
- package/dist/pid-schema.d.ts.map +1 -0
- package/dist/pid-schema.js +70 -0
- package/dist/pid-schema.js.map +1 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +24 -17
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-interactive.d.ts.map +1 -1
- package/dist/workspace-transition-interactive.js +53 -3
- package/dist/workspace-transition-interactive.js.map +1 -1
- package/dist/workspace-transition.d.ts +2 -1
- package/dist/workspace-transition.d.ts.map +1 -1
- package/dist/workspace-transition.js +16 -4
- package/dist/workspace-transition.js.map +1 -1
- package/extensions/__integration__/audit-findings.test.ts +309 -0
- package/extensions/__integration__/cd-tool-guidelines.test.ts +46 -0
- package/extensions/__integration__/tasks-runtime.test.ts +63 -12
- package/extensions/__integration__/welcome-screen.test.ts +240 -0
- package/extensions/_shared/lazy-init.ts +88 -3
- package/extensions/_shared/pid-registry.ts +8 -82
- package/extensions/background-task-tool/index.ts +1 -1
- package/extensions/cd-tool/index.ts +4 -1
- package/extensions/cheatsheet/__tests__/cheatsheet.test.ts +47 -0
- package/extensions/clear/__tests__/clear.test.ts +38 -0
- package/extensions/edit-tool-enhanced/index.ts +3 -1
- package/extensions/git-status/__tests__/git-status.test.ts +32 -0
- package/extensions/health/__tests__/diagnostics.test.ts +25 -0
- package/extensions/health/index.ts +61 -0
- package/extensions/loop/__tests__/loop.test.ts +365 -1
- package/extensions/loop/index.ts +213 -3
- package/extensions/mcp-adapter-tool/index.ts +1 -1
- package/extensions/minimal-skill-display/__tests__/minimal-skill-display.test.ts +20 -0
- package/extensions/permissions/__tests__/permissions.test.ts +213 -0
- package/extensions/progress-indicator/__tests__/progress-indicator.test.ts +104 -0
- package/extensions/prompt-suggestions/__tests__/autocomplete.test.ts +111 -3
- package/extensions/prompt-suggestions/autocomplete.ts +23 -5
- package/extensions/prompt-suggestions/index.ts +62 -3
- package/extensions/random-spinner/__tests__/random-spinner.test.ts +35 -0
- package/extensions/read-tool-enhanced/index.ts +5 -1
- package/extensions/session-memory/index.ts +1 -1
- package/extensions/session-namer/index.ts +1 -1
- package/extensions/show-system-prompt/__tests__/show-system-prompt.test.ts +51 -0
- package/extensions/subagent-tool/__tests__/presentation-rendering.test.ts +9 -8
- package/extensions/subagent-tool/__tests__/process-liveness.test.ts +51 -0
- package/extensions/subagent-tool/__tests__/subprocess-args.test.ts +120 -0
- package/extensions/subagent-tool/formatting.ts +2 -0
- package/extensions/subagent-tool/index.ts +160 -97
- package/extensions/subagent-tool/process.ts +152 -40
- package/extensions/tasks/commands/register-tasks-extension.ts +64 -20
- package/extensions/tasks/extension.json +1 -0
- package/extensions/tasks/index.ts +2 -12
- package/extensions/tasks/state/index.ts +26 -0
- package/extensions/teams-tool/dashboard.ts +13 -1
- package/extensions/teams-tool/sessions/spawn.ts +2 -2
- package/extensions/teams-tool/tools/register-extension.ts +10 -2
- package/extensions/upstream-check/__tests__/upstream-check.test.ts +49 -0
- package/extensions/welcome-screen/__tests__/welcome-screen.test.ts +35 -0
- package/extensions/welcome-screen/extension.json +20 -0
- package/extensions/welcome-screen/index.ts +189 -0
- package/extensions/wezterm-notify/__tests__/index.test.ts +49 -11
- package/extensions/wezterm-notify/index.ts +5 -3
- package/extensions/write-tool-enhanced/__tests__/write-tool-enhanced.test.ts +296 -0
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +2 -2
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.js +2 -2
- package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +309 -25
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +392 -72
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +30 -0
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.js +50 -6
- package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +27 -0
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js +59 -4
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +9 -0
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js +50 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/package.json +1 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +134 -0
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tmux-compat.test.ts +204 -0
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +49 -0
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +2 -0
- package/node_modules/@mariozechner/pi-tui/src/index.ts +11 -0
- package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +478 -140
- package/node_modules/@mariozechner/pi-tui/src/keys.ts +84 -6
- package/node_modules/@mariozechner/pi-tui/src/terminal.ts +69 -4
- package/node_modules/@mariozechner/pi-tui/src/tui.ts +64 -1
- package/package.json +11 -10
- package/runtime/config.ts +7 -0
- package/runtime/model-metadata-overrides.ts +7 -0
- package/runtime/pid-schema.ts +13 -0
- package/skills/tallow-expert/SKILL.md +7 -5
|
@@ -819,7 +819,10 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
819
819
|
if (modifier === 0) {
|
|
820
820
|
return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0);
|
|
821
821
|
}
|
|
822
|
-
return
|
|
822
|
+
return (
|
|
823
|
+
matchesKittySequence(data, CODEPOINTS.tab, modifier) ||
|
|
824
|
+
matchesModifyOtherKeys(data, CODEPOINTS.tab, modifier)
|
|
825
|
+
);
|
|
823
826
|
|
|
824
827
|
case "enter":
|
|
825
828
|
case "return":
|
|
@@ -873,7 +876,8 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
873
876
|
}
|
|
874
877
|
return (
|
|
875
878
|
matchesKittySequence(data, CODEPOINTS.enter, modifier) ||
|
|
876
|
-
matchesKittySequence(data, CODEPOINTS.kpEnter, modifier)
|
|
879
|
+
matchesKittySequence(data, CODEPOINTS.kpEnter, modifier) ||
|
|
880
|
+
matchesModifyOtherKeys(data, CODEPOINTS.enter, modifier)
|
|
877
881
|
);
|
|
878
882
|
|
|
879
883
|
case "backspace":
|
|
@@ -1108,21 +1112,33 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1108
1112
|
if (ctrl && !shift && !alt) {
|
|
1109
1113
|
// Legacy: ctrl+key sends the control character
|
|
1110
1114
|
if (rawCtrl && data === rawCtrl) return true;
|
|
1111
|
-
return
|
|
1115
|
+
return (
|
|
1116
|
+
matchesKittySequence(data, codepoint, MODIFIERS.ctrl) ||
|
|
1117
|
+
matchesModifyOtherKeys(data, codepoint, MODIFIERS.ctrl)
|
|
1118
|
+
);
|
|
1112
1119
|
}
|
|
1113
1120
|
|
|
1114
1121
|
if (ctrl && shift && !alt) {
|
|
1115
|
-
return
|
|
1122
|
+
return (
|
|
1123
|
+
matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||
|
|
1124
|
+
matchesModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)
|
|
1125
|
+
);
|
|
1116
1126
|
}
|
|
1117
1127
|
|
|
1118
1128
|
if (shift && !ctrl && !alt) {
|
|
1119
1129
|
// Legacy: shift+letter produces uppercase
|
|
1120
1130
|
if (data === key.toUpperCase()) return true;
|
|
1121
|
-
return
|
|
1131
|
+
return (
|
|
1132
|
+
matchesKittySequence(data, codepoint, MODIFIERS.shift) ||
|
|
1133
|
+
matchesModifyOtherKeys(data, codepoint, MODIFIERS.shift)
|
|
1134
|
+
);
|
|
1122
1135
|
}
|
|
1123
1136
|
|
|
1124
1137
|
if (modifier !== 0) {
|
|
1125
|
-
return
|
|
1138
|
+
return (
|
|
1139
|
+
matchesKittySequence(data, codepoint, modifier) ||
|
|
1140
|
+
matchesModifyOtherKeys(data, codepoint, modifier)
|
|
1141
|
+
);
|
|
1126
1142
|
}
|
|
1127
1143
|
|
|
1128
1144
|
// Check both raw char and Kitty sequence (needed for release events)
|
|
@@ -1251,3 +1267,65 @@ export function parseKey(data: string): string | undefined {
|
|
|
1251
1267
|
|
|
1252
1268
|
return undefined;
|
|
1253
1269
|
}
|
|
1270
|
+
|
|
1271
|
+
// =============================================================================
|
|
1272
|
+
// Mouse Event Parsing
|
|
1273
|
+
// =============================================================================
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Parsed mouse event from SGR extended format.
|
|
1277
|
+
* SGR format: \x1b[<button;column;row[Mm]
|
|
1278
|
+
* M = press, m = release
|
|
1279
|
+
*/
|
|
1280
|
+
export interface MouseEvent {
|
|
1281
|
+
/** Event type */
|
|
1282
|
+
type: "scroll-up" | "scroll-down" | "press" | "release" | "drag";
|
|
1283
|
+
/** 0=left, 1=middle, 2=right */
|
|
1284
|
+
button: number;
|
|
1285
|
+
/** 1-indexed column */
|
|
1286
|
+
x: number;
|
|
1287
|
+
/** 1-indexed row */
|
|
1288
|
+
y: number;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/** SGR extended mouse format: \x1b[<button;x;y[Mm] */
|
|
1292
|
+
const SGR_MOUSE_RE = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/;
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Parse an SGR mouse event from raw terminal input.
|
|
1296
|
+
*
|
|
1297
|
+
* @param data - Raw terminal input
|
|
1298
|
+
* @returns Parsed mouse event, or null if not a mouse sequence
|
|
1299
|
+
*/
|
|
1300
|
+
export function parseMouseEvent(data: string): MouseEvent | null {
|
|
1301
|
+
const match = data.match(SGR_MOUSE_RE);
|
|
1302
|
+
if (!match) return null;
|
|
1303
|
+
|
|
1304
|
+
const code = parseInt(match[1]!, 10);
|
|
1305
|
+
const x = parseInt(match[2]!, 10);
|
|
1306
|
+
const y = parseInt(match[3]!, 10);
|
|
1307
|
+
const isRelease = match[4] === "m";
|
|
1308
|
+
|
|
1309
|
+
// Scroll wheel: codes 64 (up) and 65 (down)
|
|
1310
|
+
if (code === 64) return { type: "scroll-up", button: 0, x, y };
|
|
1311
|
+
if (code === 65) return { type: "scroll-down", button: 0, x, y };
|
|
1312
|
+
|
|
1313
|
+
// Button number is in the low 2 bits
|
|
1314
|
+
const button = code & 0x03;
|
|
1315
|
+
|
|
1316
|
+
// Bit 5 (32) = motion/drag
|
|
1317
|
+
if (code & 32) return { type: "drag", button, x, y };
|
|
1318
|
+
|
|
1319
|
+
return { type: isRelease ? "release" : "press", button, x, y };
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Fast check: is this data an SGR mouse event?
|
|
1324
|
+
* Avoids regex for non-mouse input.
|
|
1325
|
+
*
|
|
1326
|
+
* @param data - Raw terminal input
|
|
1327
|
+
* @returns true if the input is an SGR mouse sequence
|
|
1328
|
+
*/
|
|
1329
|
+
export function isMouseEvent(data: string): boolean {
|
|
1330
|
+
return data.length >= 9 && data.startsWith("\x1b[<");
|
|
1331
|
+
}
|
|
@@ -30,6 +30,9 @@ export interface Terminal {
|
|
|
30
30
|
// Whether Kitty keyboard protocol is active
|
|
31
31
|
get kittyProtocolActive(): boolean;
|
|
32
32
|
|
|
33
|
+
// Whether running inside tmux (using modifyOtherKeys instead of Kitty protocol)
|
|
34
|
+
get isTmux(): boolean;
|
|
35
|
+
|
|
33
36
|
// Cursor positioning (relative to current position)
|
|
34
37
|
moveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines
|
|
35
38
|
|
|
@@ -42,6 +45,10 @@ export interface Terminal {
|
|
|
42
45
|
clearFromCursor(): void; // Clear from cursor to end of screen
|
|
43
46
|
clearScreen(): void; // Clear entire screen and move cursor to (0,0)
|
|
44
47
|
|
|
48
|
+
// Mouse reporting
|
|
49
|
+
enableMouse(): void; // Enable SGR mouse tracking (scroll, click)
|
|
50
|
+
disableMouse(): void; // Disable mouse tracking
|
|
51
|
+
|
|
45
52
|
// Screen buffer mode
|
|
46
53
|
enterAlternateScreen(): void; // Switch to alternate screen buffer (no scrollback)
|
|
47
54
|
leaveAlternateScreen(): void; // Restore normal screen buffer and scrollback
|
|
@@ -67,11 +74,16 @@ export class ProcessTerminal implements Terminal {
|
|
|
67
74
|
private stdinDataHandler?: (data: string) => void;
|
|
68
75
|
private writeLogPath = process.env.PI_TUI_WRITE_LOG || "";
|
|
69
76
|
private alternateScreenActive = false;
|
|
77
|
+
private mouseActive = false;
|
|
70
78
|
|
|
71
79
|
get kittyProtocolActive(): boolean {
|
|
72
80
|
return this._kittyProtocolActive;
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
get isTmux(): boolean {
|
|
84
|
+
return !!process.env.TMUX;
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
start(onInput: (data: string) => void, onResize: () => void): void {
|
|
76
88
|
this.inputHandler = onInput;
|
|
77
89
|
this.resizeHandler = onResize;
|
|
@@ -97,10 +109,24 @@ export class ProcessTerminal implements Terminal {
|
|
|
97
109
|
process.kill(process.pid, "SIGWINCH");
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
112
|
+
// Mouse tracking (enableMouse/disableMouse) is available but NOT
|
|
113
|
+
// enabled anywhere yet. The TUI has no scroll viewport — content
|
|
114
|
+
// off-screen is only reachable via terminal scrollback (or tmux
|
|
115
|
+
// copy-mode). Enabling mouse tracking steals scroll events without
|
|
116
|
+
// providing scroll handling, making things strictly worse. Enable
|
|
117
|
+
// only after implementing a scroll viewport in the TUI.
|
|
118
|
+
|
|
119
|
+
// Enable keyboard protocol for modified key detection.
|
|
120
|
+
// tmux doesn't support the Kitty keyboard protocol but does support xterm's
|
|
121
|
+
// modifyOtherKeys. Detect tmux and use the appropriate protocol.
|
|
122
|
+
if (process.env.TMUX) {
|
|
123
|
+
this.setupTmuxInput();
|
|
124
|
+
} else {
|
|
125
|
+
// Query and enable Kitty keyboard protocol
|
|
126
|
+
// The query handler intercepts input temporarily, then installs the user's handler
|
|
127
|
+
// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
128
|
+
this.queryAndEnableKittyProtocol();
|
|
129
|
+
}
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
/**
|
|
@@ -154,6 +180,22 @@ export class ProcessTerminal implements Terminal {
|
|
|
154
180
|
};
|
|
155
181
|
}
|
|
156
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Set up stdin handling for tmux without Kitty keyboard protocol.
|
|
185
|
+
*
|
|
186
|
+
* tmux doesn't support the Kitty keyboard protocol. Escape and Ctrl+C
|
|
187
|
+
* arrive as raw bytes (\x1b and \x03) which the key matching handles natively.
|
|
188
|
+
*
|
|
189
|
+
* For Shift+Enter, tmux needs `extended-keys on` and `extended-keys-format csi-u`
|
|
190
|
+
* in tmux.conf. With that config, we request modifyOtherKeys mode 1 so tmux
|
|
191
|
+
* encodes modified keys (Shift+Enter → CSI 13;2 u) while leaving standard
|
|
192
|
+
* keys (Escape, Ctrl+C, regular typing) as raw bytes.
|
|
193
|
+
*/
|
|
194
|
+
private setupTmuxInput(): void {
|
|
195
|
+
this.setupStdinBuffer();
|
|
196
|
+
process.stdin.on("data", this.stdinDataHandler!);
|
|
197
|
+
}
|
|
198
|
+
|
|
157
199
|
/**
|
|
158
200
|
* Query terminal for Kitty keyboard protocol support and enable if available.
|
|
159
201
|
*
|
|
@@ -209,6 +251,9 @@ export class ProcessTerminal implements Terminal {
|
|
|
209
251
|
this.leaveAlternateScreen();
|
|
210
252
|
}
|
|
211
253
|
|
|
254
|
+
// Disable mouse tracking
|
|
255
|
+
this.disableMouse();
|
|
256
|
+
|
|
212
257
|
// Disable bracketed paste mode
|
|
213
258
|
process.stdout.write("\x1b[?2004l");
|
|
214
259
|
|
|
@@ -297,6 +342,26 @@ export class ProcessTerminal implements Terminal {
|
|
|
297
342
|
process.stdout.write("\x1b[2J\x1b[H"); // Clear screen and move to home (1,1)
|
|
298
343
|
}
|
|
299
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Enable SGR mouse tracking.
|
|
347
|
+
* Mode 1000 = button press/release (includes scroll wheel).
|
|
348
|
+
* Mode 1006 = SGR extended format (avoids 223-column limit).
|
|
349
|
+
*/
|
|
350
|
+
enableMouse(): void {
|
|
351
|
+
if (this.mouseActive) return;
|
|
352
|
+
process.stdout.write("\x1b[?1000h\x1b[?1006h");
|
|
353
|
+
this.mouseActive = true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Disable mouse tracking and restore default terminal mouse handling.
|
|
358
|
+
*/
|
|
359
|
+
disableMouse(): void {
|
|
360
|
+
if (!this.mouseActive) return;
|
|
361
|
+
process.stdout.write("\x1b[?1006l\x1b[?1000l");
|
|
362
|
+
this.mouseActive = false;
|
|
363
|
+
}
|
|
364
|
+
|
|
300
365
|
/**
|
|
301
366
|
* Switch to alternate screen buffer and clear it.
|
|
302
367
|
* @returns void
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
isKeyRelease,
|
|
10
|
+
isMouseEvent,
|
|
11
|
+
type MouseEvent,
|
|
12
|
+
matchesKey,
|
|
13
|
+
parseMouseEvent,
|
|
14
|
+
} from "./keys.js";
|
|
9
15
|
import type { Terminal } from "./terminal.js";
|
|
10
16
|
import { getCapabilities, isImageLine, setCellDimensions } from "./terminal-image.js";
|
|
11
17
|
import {
|
|
@@ -209,6 +215,13 @@ export class TUI extends Container {
|
|
|
209
215
|
|
|
210
216
|
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
211
217
|
public onDebug?: () => void;
|
|
218
|
+
/**
|
|
219
|
+
* Callback for mouse events. Called when a mouse event is received.
|
|
220
|
+
* Scroll events are the primary use case (scroll-up, scroll-down).
|
|
221
|
+
* Return value is ignored — mouse events are always consumed and never
|
|
222
|
+
* forwarded to focused components.
|
|
223
|
+
*/
|
|
224
|
+
public onMouse?: (event: MouseEvent) => void;
|
|
212
225
|
private renderRequested = false;
|
|
213
226
|
private pendingRenderHandle?: ReturnType<typeof setTimeout>;
|
|
214
227
|
private cursorRow = 0; // Logical cursor row (end of rendered content)
|
|
@@ -220,6 +233,7 @@ export class TUI extends Container {
|
|
|
220
233
|
private maxLinesRendered = 0; // Track terminal's working area (max lines ever rendered)
|
|
221
234
|
private previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
|
|
222
235
|
private fullRedrawCount = 0;
|
|
236
|
+
private rollingShrinkPeak = 0; // Recent peak line count for gradual shrink detection
|
|
223
237
|
private stopped = false;
|
|
224
238
|
private pendingScrollbackClear = false; // Clear scrollback on next full render (session breaks)
|
|
225
239
|
|
|
@@ -405,6 +419,12 @@ export class TUI extends Container {
|
|
|
405
419
|
if (!getCapabilities().images) {
|
|
406
420
|
return;
|
|
407
421
|
}
|
|
422
|
+
// Skip cell size query inside tmux — tmux doesn't forward CSI 16 t responses,
|
|
423
|
+
// so cellSizeQueryPending would stay true and parseCellSizeResponse would eat
|
|
424
|
+
// bare \x1b (Escape key) as a "partial response", breaking Escape handling.
|
|
425
|
+
if (process.env.TMUX) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
408
428
|
// Query terminal for cell size in pixels: CSI 16 t
|
|
409
429
|
// Response format: CSI 6 ; height ; width t
|
|
410
430
|
this.cellSizeQueryPending = true;
|
|
@@ -442,6 +462,7 @@ export class TUI extends Container {
|
|
|
442
462
|
this.hardwareCursorRow = 0;
|
|
443
463
|
this.maxLinesRendered = 0;
|
|
444
464
|
this.previousViewportTop = 0;
|
|
465
|
+
this.rollingShrinkPeak = 0;
|
|
445
466
|
}
|
|
446
467
|
if (this.renderRequested) return;
|
|
447
468
|
this.scheduleRender();
|
|
@@ -511,6 +532,16 @@ export class TUI extends Container {
|
|
|
511
532
|
data = filtered;
|
|
512
533
|
}
|
|
513
534
|
|
|
535
|
+
// Mouse events — intercept before any key handling.
|
|
536
|
+
// Always consumed: mouse sequences must never reach components as text.
|
|
537
|
+
if (isMouseEvent(data)) {
|
|
538
|
+
const event = parseMouseEvent(data);
|
|
539
|
+
if (event && this.onMouse) {
|
|
540
|
+
this.onMouse(event);
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
514
545
|
// Global debug key handler (Shift+Ctrl+D)
|
|
515
546
|
if (matchesKey(data, "shift+ctrl+d") && this.onDebug) {
|
|
516
547
|
this.onDebug();
|
|
@@ -556,6 +587,19 @@ export class TUI extends Container {
|
|
|
556
587
|
if (isKeyRelease(data) && !this.focusedComponent.wantsKeyRelease) {
|
|
557
588
|
return;
|
|
558
589
|
}
|
|
590
|
+
if (process.env.TALLOW_KEY_DEBUG && (data === "\x1b" || data === "\x03")) {
|
|
591
|
+
const escMatch = matchesKey(data, "escape");
|
|
592
|
+
const ctrlcMatch = matchesKey(data, "ctrl+c");
|
|
593
|
+
const comp = this.focusedComponent as unknown as {
|
|
594
|
+
onEscape?: () => void;
|
|
595
|
+
actionHandlers?: Map<string, unknown>;
|
|
596
|
+
};
|
|
597
|
+
const hasOnEscape = typeof comp.onEscape === "function";
|
|
598
|
+
const actionCount = comp.actionHandlers instanceof Map ? comp.actionHandlers.size : -1;
|
|
599
|
+
process.stderr.write(
|
|
600
|
+
`[key] ${data === "\x1b" ? "ESC" : "C-C"} matchEsc=${escMatch} matchCC=${ctrlcMatch} hasOnEscape=${hasOnEscape} actions=${actionCount}\n`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
559
603
|
this.focusedComponent.handleInput(data);
|
|
560
604
|
this.requestRender();
|
|
561
605
|
}
|
|
@@ -985,6 +1029,7 @@ export class TUI extends Container {
|
|
|
985
1029
|
this.maxLinesRendered = Math.max(this.maxLinesRendered, newLines.length);
|
|
986
1030
|
}
|
|
987
1031
|
this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
|
|
1032
|
+
this.rollingShrinkPeak = newLines.length;
|
|
988
1033
|
this.positionHardwareCursor(cursorPos, newLines.length);
|
|
989
1034
|
this.previousLines = newLines;
|
|
990
1035
|
this.previousWidth = width;
|
|
@@ -1036,6 +1081,21 @@ export class TUI extends Container {
|
|
|
1036
1081
|
return;
|
|
1037
1082
|
}
|
|
1038
1083
|
|
|
1084
|
+
// Rolling shrink detection: catches gradual shrinks where each individual
|
|
1085
|
+
// frame-to-frame delta is ≤5 lines (below the large-shrink threshold) but the
|
|
1086
|
+
// accumulated shrink from a recent peak exceeds it. This happens when
|
|
1087
|
+
// pollStates.clear() collapses tool-result anchors across multiple render
|
|
1088
|
+
// cycles while animations (loader, widget spinners) keep triggering renders.
|
|
1089
|
+
if (newLines.length >= this.rollingShrinkPeak) {
|
|
1090
|
+
this.rollingShrinkPeak = newLines.length;
|
|
1091
|
+
} else if (this.overlayStack.length === 0 && this.rollingShrinkPeak - newLines.length > 5) {
|
|
1092
|
+
logRedraw(
|
|
1093
|
+
`rolling shrink (peak=${this.rollingShrinkPeak}, now=${newLines.length}, delta=${this.rollingShrinkPeak - newLines.length})`
|
|
1094
|
+
);
|
|
1095
|
+
fullRender(true);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1039
1099
|
const previousContentViewportTop = Math.max(0, this.previousLines.length - height);
|
|
1040
1100
|
// Detect viewport basis drift: maxLinesRendered exceeds actual content,
|
|
1041
1101
|
// causing viewportTop to be computed from a stale high-water mark.
|
|
@@ -1306,6 +1366,9 @@ export class TUI extends Container {
|
|
|
1306
1366
|
this.maxLinesRendered = Math.max(this.maxLinesRendered, newLines.length);
|
|
1307
1367
|
}
|
|
1308
1368
|
this.previousViewportTop = Math.max(0, this.maxLinesRendered - height);
|
|
1369
|
+
// Update rolling peak for gradual shrink detection (partial path only —
|
|
1370
|
+
// fullRender paths reset it inside the fullRender closure).
|
|
1371
|
+
this.rollingShrinkPeak = Math.max(this.rollingShrinkPeak, newLines.length);
|
|
1309
1372
|
|
|
1310
1373
|
// Position hardware cursor for IME
|
|
1311
1374
|
this.positionHardwareCursor(cursorPos, newLines.length);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dungle-scrubs/tallow",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.28",
|
|
4
4
|
"description": "An opinionated coding agent. Built on pi.",
|
|
5
5
|
"piConfig": {
|
|
6
6
|
"name": "tallow",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"format:check": "biome format .",
|
|
54
54
|
"bench:startup": "bun scripts/benchmark-startup-fast-path.ts",
|
|
55
55
|
"pre-pr": "bash scripts/pre-pr.sh",
|
|
56
|
-
"prepare": "test -d .git && husky || true"
|
|
56
|
+
"prepare": "test -d .git && husky || true",
|
|
57
|
+
"postinstall": "node scripts/patch-upstream-debug.mjs"
|
|
57
58
|
},
|
|
58
59
|
"lint-staged": {
|
|
59
60
|
"*.{ts,tsx,js,jsx,json,jsonc,css}": [
|
|
@@ -73,25 +74,25 @@
|
|
|
73
74
|
},
|
|
74
75
|
"dependencies": {
|
|
75
76
|
"@clack/prompts": "^1.1.0",
|
|
76
|
-
"@dungle-scrubs/synapse": "0.1.
|
|
77
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
78
|
-
"@mariozechner/pi-tui": "^0.
|
|
77
|
+
"@dungle-scrubs/synapse": "0.1.8",
|
|
78
|
+
"@mariozechner/pi-coding-agent": "^0.64.0",
|
|
79
|
+
"@mariozechner/pi-tui": "^0.62.0",
|
|
79
80
|
"@opentelemetry/api": "^1.9.0",
|
|
80
81
|
"@sinclair/typebox": "0.34.48",
|
|
81
82
|
"ai": "^6.0.116",
|
|
82
83
|
"commander": "^14.0.3",
|
|
83
84
|
"unpdf": "^1.4.0",
|
|
84
|
-
"vscode-jsonrpc": "8.2.1"
|
|
85
|
+
"vscode-jsonrpc": "8.2.1",
|
|
86
|
+
"vscode-languageserver-protocol": "3.17.5"
|
|
85
87
|
},
|
|
86
88
|
"devDependencies": {
|
|
87
89
|
"@biomejs/biome": "2.4.2",
|
|
88
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
89
|
-
"@mariozechner/pi-ai": "^0.
|
|
90
|
+
"@mariozechner/pi-agent-core": "^0.64.0",
|
|
91
|
+
"@mariozechner/pi-ai": "^0.64.0",
|
|
90
92
|
"@types/node": "25.2.3",
|
|
91
93
|
"husky": "^9.1.7",
|
|
92
94
|
"lint-staged": "^16.4.0",
|
|
93
|
-
"typescript": "^5.9.3"
|
|
94
|
-
"vscode-languageserver-protocol": "3.17.5"
|
|
95
|
+
"typescript": "^5.9.3"
|
|
95
96
|
},
|
|
96
97
|
"engines": {
|
|
97
98
|
"node": ">=22"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { resolveRuntimeModuleUrl } from "./resolve-module.js";
|
|
2
|
+
|
|
3
|
+
const mod = (await import(
|
|
4
|
+
resolveRuntimeModuleUrl("model-metadata-overrides.js")
|
|
5
|
+
)) as typeof import("../src/model-metadata-overrides.js");
|
|
6
|
+
|
|
7
|
+
export const applyKnownModelMetadataOverrides = mod.applyKnownModelMetadataOverrides;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveRuntimeModuleUrl } from "./resolve-module.js";
|
|
2
|
+
|
|
3
|
+
const pidSchemaModule = (await import(
|
|
4
|
+
resolveRuntimeModuleUrl("pid-schema.js")
|
|
5
|
+
)) as typeof import("../src/pid-schema.js");
|
|
6
|
+
|
|
7
|
+
export type PidEntry = import("../src/pid-schema.js").PidEntry;
|
|
8
|
+
export type SessionOwner = import("../src/pid-schema.js").SessionOwner;
|
|
9
|
+
export type SessionPidFile = import("../src/pid-schema.js").SessionPidFile;
|
|
10
|
+
|
|
11
|
+
export const isPidEntry = pidSchemaModule.isPidEntry;
|
|
12
|
+
export const isSessionOwner = pidSchemaModule.isSessionOwner;
|
|
13
|
+
export const toOwnerKey = pidSchemaModule.toOwnerKey;
|
|
@@ -33,8 +33,8 @@ Relay that answer to the user.
|
|
|
33
33
|
|
|
34
34
|
| Component | Location |
|
|
35
35
|
|-----------|----------|
|
|
36
|
-
| Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli-auto-rebuild.ts, cli.ts, compaction-cancel-patch.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, model-metadata-overrides.ts, otel.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust-interop.ts, project-trust.ts, runtime-path-provider.ts, runtime-provenance.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts, streaming-yield-patch.ts, workspace-transition-interactive.ts, workspace-transition-relay.ts, workspace-transition.ts, yield-to-io.ts) |
|
|
37
|
-
| Extensions | `extensions/` — extension.json + index.ts each (
|
|
36
|
+
| Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli-auto-rebuild.ts, cli.ts, compaction-cancel-patch.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, model-metadata-overrides.ts, otel.ts, pid-manager.ts, pid-schema.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust-interop.ts, project-trust.ts, runtime-path-provider.ts, runtime-provenance.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts, streaming-yield-patch.ts, workspace-transition-interactive.ts, workspace-transition-relay.ts, workspace-transition.ts, yield-to-io.ts) |
|
|
37
|
+
| Extensions | `extensions/` — extension.json + index.ts each (53 bundled) |
|
|
38
38
|
| Skills | `skills/` — subdirs with SKILL.md |
|
|
39
39
|
| Agents | `agents/` — markdown with YAML frontmatter |
|
|
40
40
|
| Themes | `themes/` — JSON files (34 dark-only themes) |
|
|
@@ -59,8 +59,8 @@ Extensions export a default function receiving `ExtensionAPI` (conventionally na
|
|
|
59
59
|
|
|
60
60
|
#### Registration
|
|
61
61
|
|
|
62
|
-
- `registerTool(tool: ToolDefinition<TParams, TDetails>)` — Register a tool that the LLM can call.
|
|
63
|
-
- `registerCommand(name: string, options: Omit<RegisteredCommand, "name">)` — Register a custom command.
|
|
62
|
+
- `registerTool(tool: ToolDefinition<TParams, TDetails, TState>)` — Register a tool that the LLM can call.
|
|
63
|
+
- `registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "sourceInfo">)` — Register a custom command.
|
|
64
64
|
- `registerFlag(name: string, options: object)` — Register a CLI flag.
|
|
65
65
|
- `registerMessageRenderer(customType: string, renderer: MessageRenderer<T>)` — Register a custom renderer for CustomMessageEntry.
|
|
66
66
|
- `registerProvider(name: string, config: ProviderConfig)` — Register or override a model provider.
|
|
@@ -81,7 +81,7 @@ Extensions export a default function receiving `ExtensionAPI` (conventionally na
|
|
|
81
81
|
- `getFlag(name: string)` — Get the value of a registered CLI flag.
|
|
82
82
|
- `exec(command: string, args: string[], options?: ExecOptions)` — Execute a shell command.
|
|
83
83
|
- `getActiveTools()` — Get the list of currently active tool names.
|
|
84
|
-
- `getAllTools()` — Get all configured tools with
|
|
84
|
+
- `getAllTools()` — Get all configured tools with parameter schema and source metadata.
|
|
85
85
|
- `setActiveTools(toolNames: string[])` — Set the active tools by name.
|
|
86
86
|
- `getCommands()` — Get available slash commands in the current session.
|
|
87
87
|
- `setModel(model: Model<any>)` — Set the current model.
|
|
@@ -154,6 +154,7 @@ Extensions export a default function receiving `ExtensionAPI` (conventionally na
|
|
|
154
154
|
- `modelRegistry` — Model registry for API key resolution
|
|
155
155
|
- `model` — Current model (may be undefined)
|
|
156
156
|
- `isIdle()` — Whether the agent is idle (not streaming)
|
|
157
|
+
- `signal` — The current abort signal, or undefined when the agent is not streaming.
|
|
157
158
|
- `abort()` — Abort the current agent operation
|
|
158
159
|
- `hasPendingMessages()` — Whether there are queued messages waiting
|
|
159
160
|
- `shutdown()` — Gracefully shutdown pi and exit.
|
|
@@ -178,6 +179,7 @@ Extensions export a default function receiving `ExtensionAPI` (conventionally na
|
|
|
178
179
|
- `onTerminalInput(handler: TerminalInputHandler)` — Listen to raw terminal input (interactive mode only).
|
|
179
180
|
- `setStatus(key: string, text: string)` — Set status text in the footer/status bar.
|
|
180
181
|
- `setWorkingMessage(message?: string)` — Set the working/loading message shown during streaming.
|
|
182
|
+
- `setHiddenThinkingLabel(label?: string)` — Set the label shown for hidden thinking blocks.
|
|
181
183
|
- `setWidget(key: string, content: string[], options?: ExtensionWidgetOptions)` — Set a widget to display above or below the editor.
|
|
182
184
|
- `setTitle(title: string)` — Set the terminal window/tab title.
|
|
183
185
|
- `pasteToEditor(text: string)` — Paste text into the editor, triggering paste handling (collapse for large content).
|