@dungle-scrubs/tallow 0.8.27 → 0.9.0

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.
Files changed (99) hide show
  1. package/README.md +42 -1
  2. package/dist/cli.js +7 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +1 -1
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +1 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/install.d.ts.map +1 -1
  9. package/dist/install.js +2 -9
  10. package/dist/install.js.map +1 -1
  11. package/dist/interactive-mode-patch.d.ts.map +1 -1
  12. package/dist/interactive-mode-patch.js +20 -9
  13. package/dist/interactive-mode-patch.js.map +1 -1
  14. package/dist/model-metadata-overrides.d.ts +2 -5
  15. package/dist/model-metadata-overrides.d.ts.map +1 -1
  16. package/dist/model-metadata-overrides.js +23 -12
  17. package/dist/model-metadata-overrides.js.map +1 -1
  18. package/dist/sdk.d.ts.map +1 -1
  19. package/dist/sdk.js +20 -9
  20. package/dist/sdk.js.map +1 -1
  21. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  22. package/dist/workspace-transition-interactive.js +53 -3
  23. package/dist/workspace-transition-interactive.js.map +1 -1
  24. package/dist/workspace-transition.d.ts +2 -1
  25. package/dist/workspace-transition.d.ts.map +1 -1
  26. package/dist/workspace-transition.js +16 -4
  27. package/dist/workspace-transition.js.map +1 -1
  28. package/extensions/__integration__/cd-tool-guidelines.test.ts +46 -0
  29. package/extensions/__integration__/welcome-screen.test.ts +240 -0
  30. package/extensions/_icons/__tests__/icons.test.ts +0 -1
  31. package/extensions/_icons/index.ts +0 -2
  32. package/extensions/_shared/pid-registry.ts +5 -5
  33. package/extensions/background-task-tool/index.ts +1 -1
  34. package/extensions/cd-tool/index.ts +4 -1
  35. package/extensions/context-fork/__tests__/context-fork.test.ts +9 -0
  36. package/extensions/edit-tool-enhanced/index.ts +3 -1
  37. package/extensions/health/__tests__/diagnostics.test.ts +25 -0
  38. package/extensions/health/index.ts +62 -1
  39. package/extensions/loop/__tests__/loop.test.ts +365 -1
  40. package/extensions/loop/index.ts +213 -3
  41. package/extensions/prompt-suggestions/__tests__/autocomplete.test.ts +111 -3
  42. package/extensions/prompt-suggestions/autocomplete.ts +23 -5
  43. package/extensions/prompt-suggestions/index.ts +62 -3
  44. package/extensions/read-tool-enhanced/index.ts +5 -1
  45. package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +42 -0
  46. package/extensions/render-stabilizer/extension.json +5 -0
  47. package/extensions/render-stabilizer/index.ts +66 -0
  48. package/extensions/session-memory/index.ts +1 -1
  49. package/extensions/session-namer/index.ts +1 -1
  50. package/extensions/subagent-tool/__tests__/auto-cheap-model.test.ts +66 -6
  51. package/extensions/subagent-tool/__tests__/model-router-explicit-resolution.test.ts +79 -5
  52. package/extensions/subagent-tool/__tests__/presentation-rendering.test.ts +4 -4
  53. package/extensions/subagent-tool/index.ts +4 -2
  54. package/extensions/subagent-tool/process.ts +26 -8
  55. package/extensions/teams-tool/sessions/spawn.ts +2 -2
  56. package/extensions/welcome-screen/__tests__/welcome-screen.test.ts +35 -0
  57. package/extensions/welcome-screen/extension.json +20 -0
  58. package/extensions/welcome-screen/index.ts +189 -0
  59. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +2 -2
  60. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  61. package/node_modules/@mariozechner/pi-tui/dist/index.js +2 -2
  62. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  63. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +309 -25
  64. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  65. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +392 -72
  66. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  67. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +30 -0
  68. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  69. package/node_modules/@mariozechner/pi-tui/dist/keys.js +50 -6
  70. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  71. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +27 -0
  72. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  73. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +59 -4
  74. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  75. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +56 -0
  76. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  77. package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -5
  78. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  79. package/node_modules/@mariozechner/pi-tui/package.json +1 -1
  80. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +134 -0
  81. package/node_modules/@mariozechner/pi-tui/src/__tests__/tmux-compat.test.ts +204 -0
  82. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +49 -0
  83. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +2 -0
  84. package/node_modules/@mariozechner/pi-tui/src/index.ts +11 -0
  85. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +478 -140
  86. package/node_modules/@mariozechner/pi-tui/src/keys.ts +84 -6
  87. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +69 -4
  88. package/node_modules/@mariozechner/pi-tui/src/tui.ts +205 -5
  89. package/package.json +9 -9
  90. package/runtime/config.ts +7 -0
  91. package/runtime/model-metadata-overrides.ts +7 -0
  92. package/schemas/settings.schema.json +0 -5
  93. package/skills/tallow-expert/SKILL.md +6 -4
  94. package/extensions/plan-mode-tool/__tests__/e2e.mjs +0 -350
  95. package/extensions/plan-mode-tool/__tests__/index.test.ts +0 -213
  96. package/extensions/plan-mode-tool/__tests__/utils.test.ts +0 -381
  97. package/extensions/plan-mode-tool/extension.json +0 -22
  98. package/extensions/plan-mode-tool/index.ts +0 -583
  99. package/extensions/plan-mode-tool/utils.ts +0 -257
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Tests for tmux compatibility: modifyOtherKeys key matching and protocol detection.
3
+ *
4
+ * tmux uses xterm's modifyOtherKeys protocol (not Kitty keyboard protocol).
5
+ * Format: \x1b[27;<modifier>;<keycode>~
6
+ * CSI u format: \x1b[<keycode>;<modifier>u
7
+ *
8
+ * Modifier values are 1-indexed:
9
+ * 1 = no modifier, 2 = Shift, 3 = Alt, 4 = Shift+Alt,
10
+ * 5 = Ctrl, 6 = Ctrl+Shift, 7 = Ctrl+Alt, 8 = Ctrl+Shift+Alt
11
+ */
12
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
13
+ import { isKittyProtocolActive, matchesKey, parseKey, setKittyProtocolActive } from "../keys.js";
14
+
15
+ // ── modifyOtherKeys format (xterm) ──────────────────────────────────────────
16
+ // tmux with `extended-keys-format xterm` sends: \x1b[27;<mod>;<keycode>~
17
+
18
+ describe("modifyOtherKeys xterm format", () => {
19
+ beforeEach(() => setKittyProtocolActive(false));
20
+ afterEach(() => setKittyProtocolActive(false));
21
+
22
+ it("matches Shift+Enter as shift+enter", () => {
23
+ // mod=2 → Shift, keycode=13 → Enter
24
+ expect(matchesKey("\x1b[27;2;13~", "shift+enter")).toBe(true);
25
+ });
26
+
27
+ it("does not match Shift+Enter as plain enter", () => {
28
+ expect(matchesKey("\x1b[27;2;13~", "enter")).toBe(false);
29
+ });
30
+
31
+ it("matches unmodified Escape via modifyOtherKeys", () => {
32
+ // mod=1 → no modifier, keycode=27 → Escape
33
+ expect(matchesKey("\x1b[27;1;27~", "escape")).toBe(true);
34
+ });
35
+
36
+ it("matches Ctrl+Shift+A", () => {
37
+ // mod=6 → Ctrl+Shift, keycode=65 → 'A' (uppercase)
38
+ // matchesKey expects lowercase key: "ctrl+shift+a"
39
+ expect(matchesKey("\x1b[27;6;97~", "ctrl+shift+a")).toBe(true);
40
+ });
41
+
42
+ it("matches Ctrl+Enter", () => {
43
+ // mod=5 → Ctrl, keycode=13 → Enter
44
+ expect(matchesKey("\x1b[27;5;13~", "ctrl+enter")).toBe(true);
45
+ });
46
+
47
+ it("matches Alt+Enter", () => {
48
+ // mod=3 → Alt, keycode=13 → Enter
49
+ expect(matchesKey("\x1b[27;3;13~", "alt+enter")).toBe(true);
50
+ });
51
+
52
+ it("matches Shift+Space", () => {
53
+ // mod=2 → Shift, keycode=32 → Space
54
+ expect(matchesKey("\x1b[27;2;32~", "shift+space")).toBe(true);
55
+ });
56
+
57
+ it("matches Shift+Backspace", () => {
58
+ // mod=2 → Shift, keycode=127 → Backspace
59
+ expect(matchesKey("\x1b[27;2;127~", "shift+backspace")).toBe(true);
60
+ });
61
+
62
+ it("matches Shift+Tab via standard legacy sequence", () => {
63
+ // Shift+Tab has its own legacy sequence, not modifyOtherKeys
64
+ expect(matchesKey("\x1b[Z", "shift+tab")).toBe(true);
65
+ });
66
+ });
67
+
68
+ // ── CSI u format (tmux with extended-keys-format csi-u) ─────────────────────
69
+ // tmux with `extended-keys-format csi-u` sends: \x1b[<keycode>;<modifier>u
70
+
71
+ describe("CSI u format (tmux csi-u)", () => {
72
+ beforeEach(() => setKittyProtocolActive(false));
73
+ afterEach(() => setKittyProtocolActive(false));
74
+
75
+ it("matches Shift+Enter", () => {
76
+ // \x1b[13;2u → keycode=13 (Enter), mod=2 (Shift)
77
+ expect(matchesKey("\x1b[13;2u", "shift+enter")).toBe(true);
78
+ });
79
+
80
+ it("does not match CSI u Shift+Enter as plain enter", () => {
81
+ expect(matchesKey("\x1b[13;2u", "enter")).toBe(false);
82
+ });
83
+
84
+ it("matches unmodified Enter via CSI u", () => {
85
+ // \x1b[13u → keycode=13 (Enter), no modifier
86
+ expect(matchesKey("\x1b[13u", "enter")).toBe(true);
87
+ });
88
+
89
+ it("matches unmodified Escape via CSI u", () => {
90
+ // \x1b[27u → keycode=27 (Escape), no modifier
91
+ expect(matchesKey("\x1b[27u", "escape")).toBe(true);
92
+ });
93
+
94
+ it("matches Ctrl+Shift+Enter", () => {
95
+ // \x1b[13;6u → keycode=13, mod=6 (Ctrl+Shift)
96
+ expect(matchesKey("\x1b[13;6u", "ctrl+shift+enter")).toBe(true);
97
+ });
98
+
99
+ it("matches Shift+Space via CSI u", () => {
100
+ // \x1b[32;2u → keycode=32 (Space), mod=2 (Shift)
101
+ expect(matchesKey("\x1b[32;2u", "shift+space")).toBe(true);
102
+ });
103
+
104
+ it("matches Shift+Backspace via CSI u", () => {
105
+ // \x1b[127;2u → keycode=127 (Backspace), mod=2 (Shift)
106
+ expect(matchesKey("\x1b[127;2u", "shift+backspace")).toBe(true);
107
+ });
108
+ });
109
+
110
+ // ── Legacy fallbacks (no protocol) ──────────────────────────────────────────
111
+ // When tmux has extended-keys off, only legacy sequences arrive
112
+
113
+ describe("legacy mode (no extended-keys)", () => {
114
+ beforeEach(() => setKittyProtocolActive(false));
115
+ afterEach(() => setKittyProtocolActive(false));
116
+
117
+ it("matches Escape as raw 0x1b", () => {
118
+ expect(matchesKey("\x1b", "escape")).toBe(true);
119
+ });
120
+
121
+ it("matches Enter as raw \\r", () => {
122
+ expect(matchesKey("\r", "enter")).toBe(true);
123
+ });
124
+
125
+ it("matches Enter as \\n in legacy mode", () => {
126
+ expect(matchesKey("\n", "enter")).toBe(true);
127
+ });
128
+
129
+ it("does NOT match \\r as shift+enter", () => {
130
+ // In legacy mode, Shift+Enter is indistinguishable from Enter
131
+ expect(matchesKey("\r", "shift+enter")).toBe(false);
132
+ });
133
+
134
+ it("matches Ctrl+C", () => {
135
+ expect(matchesKey("\x03", "ctrl+c")).toBe(true);
136
+ });
137
+
138
+ it("matches Alt+Enter as ESC CR in legacy mode", () => {
139
+ expect(matchesKey("\x1b\r", "alt+enter")).toBe(true);
140
+ });
141
+ });
142
+
143
+ // ── Kitty protocol active should NOT be set in tmux ─────────────────────────
144
+
145
+ describe("Kitty protocol state isolation", () => {
146
+ afterEach(() => setKittyProtocolActive(false));
147
+
148
+ it("starts with Kitty protocol inactive", () => {
149
+ setKittyProtocolActive(false);
150
+ expect(isKittyProtocolActive()).toBe(false);
151
+ });
152
+
153
+ it("modifyOtherKeys sequences work regardless of Kitty state", () => {
154
+ // These should work whether Kitty protocol is active or not
155
+ setKittyProtocolActive(false);
156
+ expect(matchesKey("\x1b[27;2;13~", "shift+enter")).toBe(true);
157
+
158
+ setKittyProtocolActive(true);
159
+ expect(matchesKey("\x1b[27;2;13~", "shift+enter")).toBe(true);
160
+ });
161
+
162
+ it("CSI u sequences work regardless of Kitty state", () => {
163
+ setKittyProtocolActive(false);
164
+ expect(matchesKey("\x1b[13;2u", "shift+enter")).toBe(true);
165
+
166
+ setKittyProtocolActive(true);
167
+ expect(matchesKey("\x1b[13;2u", "shift+enter")).toBe(true);
168
+ });
169
+
170
+ it("legacy Escape works regardless of Kitty state", () => {
171
+ setKittyProtocolActive(false);
172
+ expect(matchesKey("\x1b", "escape")).toBe(true);
173
+
174
+ setKittyProtocolActive(true);
175
+ expect(matchesKey("\x1b", "escape")).toBe(true);
176
+ });
177
+ });
178
+
179
+ // ── parseKey with modifyOtherKeys ───────────────────────────────────────────
180
+
181
+ describe("parseKey with modifyOtherKeys", () => {
182
+ beforeEach(() => setKittyProtocolActive(false));
183
+ afterEach(() => setKittyProtocolActive(false));
184
+
185
+ it("parses CSI u Shift+Enter", () => {
186
+ expect(parseKey("\x1b[13;2u")).toBe("shift+enter");
187
+ });
188
+
189
+ it("parses CSI u unmodified Enter", () => {
190
+ expect(parseKey("\x1b[13u")).toBe("enter");
191
+ });
192
+
193
+ it("parses CSI u unmodified Escape", () => {
194
+ expect(parseKey("\x1b[27u")).toBe("escape");
195
+ });
196
+
197
+ it("parses CSI u Ctrl+Enter", () => {
198
+ expect(parseKey("\x1b[13;5u")).toBe("ctrl+enter");
199
+ });
200
+
201
+ it("parses CSI u Shift+Space", () => {
202
+ expect(parseKey("\x1b[32;2u")).toBe("shift+space");
203
+ });
204
+ });
@@ -48,6 +48,8 @@ class MockTerminal implements Terminal {
48
48
 
49
49
  clearScreen(): void {}
50
50
 
51
+ enableMouse(): void {}
52
+ disableMouse(): void {}
51
53
  enterAlternateScreen(): void {}
52
54
 
53
55
  leaveAlternateScreen(): void {}
@@ -480,6 +482,53 @@ describe("TUI differential rendering shrink regression", () => {
480
482
  expect(terminal.writes.some((w) => w.includes("\x1b[3J"))).toBe(false);
481
483
  });
482
484
 
485
+ test("gradual shrink across multiple frames triggers full redraw", () => {
486
+ const width = 40;
487
+ const height = 10;
488
+ const terminal = new MockTerminal(width, height);
489
+ const tui = new TUI(terminal);
490
+ const component = new MutableLinesComponent(Array.from({ length: 20 }, (_, i) => `line ${i}`));
491
+ tui.addChild(component);
492
+ renderNow(tui); // Establish peak at 20 lines
493
+
494
+ const redraws = () => tui.fullRedraws;
495
+
496
+ // Shrink by 2 lines per frame — each frame delta is ≤5 (not caught by
497
+ // single-frame guard). The rolling peak should catch the accumulated drop.
498
+ const base = redraws();
499
+ component.setLines(Array.from({ length: 18 }, (_, i) => `line ${i}`)); // -2
500
+ renderNow(tui);
501
+ component.setLines(Array.from({ length: 16 }, (_, i) => `line ${i}`)); // -4 total
502
+ renderNow(tui);
503
+ // Still within threshold — partial redraws only
504
+ expect(redraws() - base).toBe(0);
505
+
506
+ component.setLines(Array.from({ length: 14 }, (_, i) => `line ${i}`)); // -6 total from peak
507
+ renderNow(tui);
508
+ // Accumulated shrink exceeds threshold — should have triggered full redraw
509
+ expect(redraws() - base).toBeGreaterThanOrEqual(1);
510
+ });
511
+
512
+ test("rolling shrink peak resets after full redraw", () => {
513
+ const width = 40;
514
+ const height = 10;
515
+ const terminal = new MockTerminal(width, height);
516
+ const tui = new TUI(terminal);
517
+ const component = new MutableLinesComponent(Array.from({ length: 20 }, (_, i) => `line ${i}`));
518
+ tui.addChild(component);
519
+ renderNow(tui); // Peak = 20
520
+
521
+ // Trigger rolling shrink detection
522
+ component.setLines(Array.from({ length: 13 }, (_, i) => `line ${i}`)); // -7
523
+ renderNow(tui);
524
+ const afterFirstRedraw = tui.fullRedraws;
525
+
526
+ // Now shrink by only 2 from the new peak (13) — should NOT trigger
527
+ component.setLines(Array.from({ length: 11 }, (_, i) => `line ${i}`)); // -2 from reset peak
528
+ renderNow(tui);
529
+ expect(tui.fullRedraws).toBe(afterFirstRedraw);
530
+ });
531
+
483
532
  test("requestScrollbackClear has no effect on partial (differential) renders", () => {
484
533
  const width = 40;
485
534
  const height = 10;
@@ -85,6 +85,8 @@ class ControlledTerminal implements Terminal {
85
85
 
86
86
  clearScreen(): void {}
87
87
 
88
+ enableMouse(): void {}
89
+ disableMouse(): void {}
88
90
  enterAlternateScreen(): void {}
89
91
 
90
92
  leaveAlternateScreen(): void {}
@@ -50,18 +50,29 @@ export {
50
50
  type EditorKeybindingsConfig,
51
51
  EditorKeybindingsManager,
52
52
  getEditorKeybindings,
53
+ getKeybindings,
54
+ type Keybinding,
55
+ type KeybindingDefinition,
56
+ type Keybindings,
57
+ type KeybindingsConfig,
58
+ KeybindingsManager,
53
59
  setEditorKeybindings,
60
+ setKeybindings,
61
+ TUI_KEYBINDINGS,
54
62
  } from "./keybindings.js";
55
63
  // Keyboard input handling
56
64
  export {
57
65
  isKeyRelease,
58
66
  isKeyRepeat,
59
67
  isKittyProtocolActive,
68
+ isMouseEvent,
60
69
  Key,
61
70
  type KeyEventType,
62
71
  type KeyId,
72
+ type MouseEvent,
63
73
  matchesKey,
64
74
  parseKey,
75
+ parseMouseEvent,
65
76
  setKittyProtocolActive,
66
77
  } from "./keys.js";
67
78
  // Input buffering for batch splitting