@aiwayds/dsh-tui-pi 0.1.1 → 0.4.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 (62) hide show
  1. package/README.md +175 -41
  2. package/lib/agent-manager.d.ts +125 -0
  3. package/lib/agent-manager.js +298 -0
  4. package/lib/agent-manager.js.map +1 -0
  5. package/lib/agents.d.ts +30 -0
  6. package/lib/agents.js +489 -0
  7. package/lib/agents.js.map +1 -0
  8. package/lib/append-system.d.ts +2 -0
  9. package/lib/append-system.js +1 -1
  10. package/lib/append-system.js.map +1 -1
  11. package/lib/dsh-events.d.ts +12 -0
  12. package/lib/editor.d.ts +58 -0
  13. package/lib/editor.js +92 -0
  14. package/lib/editor.js.map +1 -1
  15. package/lib/footer.d.ts +46 -0
  16. package/lib/footer.js +65 -0
  17. package/lib/footer.js.map +1 -1
  18. package/lib/hotkeys.d.ts +93 -0
  19. package/lib/hotkeys.js +305 -0
  20. package/lib/hotkeys.js.map +1 -0
  21. package/lib/index.js +342 -61
  22. package/lib/index.js.map +1 -1
  23. package/lib/keymap.d.ts +243 -0
  24. package/lib/keymap.js +245 -0
  25. package/lib/keymap.js.map +1 -0
  26. package/lib/live-widgets.d.ts +59 -49
  27. package/lib/live-widgets.js +174 -117
  28. package/lib/live-widgets.js.map +1 -1
  29. package/lib/panels.d.ts +177 -0
  30. package/lib/panels.js +331 -0
  31. package/lib/panels.js.map +1 -0
  32. package/lib/selectors.d.ts +12 -1
  33. package/lib/selectors.js +1 -1
  34. package/lib/selectors.js.map +1 -1
  35. package/lib/session.d.ts +25 -0
  36. package/lib/session.js +173 -12
  37. package/lib/session.js.map +1 -1
  38. package/lib/settings.d.ts +1 -2
  39. package/lib/subagent-policy.d.ts +88 -0
  40. package/lib/subagent-policy.js +189 -0
  41. package/lib/subagent-policy.js.map +1 -0
  42. package/lib/subagent-viewer.d.ts +49 -0
  43. package/lib/subagent-viewer.js +523 -0
  44. package/lib/subagent-viewer.js.map +1 -0
  45. package/lib/theme/index.js +4 -0
  46. package/lib/theme/index.js.map +1 -1
  47. package/lib/theme/palette.d.ts +21 -3
  48. package/lib/theme/palette.js +27 -6
  49. package/lib/theme/palette.js.map +1 -1
  50. package/lib/theme-settings.d.ts +71 -9
  51. package/lib/theme-settings.js +145 -21
  52. package/lib/theme-settings.js.map +1 -1
  53. package/lib/tui.d.ts +23 -9
  54. package/lib/tui.js +110 -7
  55. package/lib/tui.js.map +1 -1
  56. package/package.json +3 -4
  57. package/patches/@earendil-works__pi-tui.patch +206 -2
  58. package/scripts/dev-install.mjs +87 -0
  59. package/scripts/link-dsh-closure.mjs +85 -0
  60. package/templates/agents/ArtyDuck.md +11 -0
  61. package/templates/agents/oldfox.md +33 -0
  62. package/templates/agents/workhorse.md +34 -0
package/lib/tui.js CHANGED
@@ -20,13 +20,25 @@
20
20
  */
21
21
  import { Container, matchesKey, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, } from '@earendil-works/pi-tui';
22
22
  import { CwdBorderEditor } from "./editor.js";
23
- import { ansiFg, RESET, resolveTheme } from "./theme/index.js";
23
+ import { mergeKeyBindings, resolveKeyAction } from "./keymap.js";
24
+ import { ansiBg, ansiFg, RESET, resolveTheme } from "./theme/index.js";
24
25
  export function startTui(options = {}) {
25
26
  const terminal = new ProcessTerminal();
26
27
  const tui = new TuiAltScreen(terminal, true);
27
28
  // Mutable theme ref: `applyTheme` swaps it and every later read (handle
28
29
  // getter, baked closures below) observes the new bundle on the next call.
29
30
  let themeRef = resolveTheme(process.env, options.themePreference ?? 'auto');
31
+ // App-owned canvas: every rendered row is painted with the palette's
32
+ // canvas background (patched pi-tui — see setCanvasBackground), so a theme
33
+ // switch recolors the WHOLE screen, not only the surfaces that paint their
34
+ // own bg. Without it the terminal default background shows through the
35
+ // unpainted rows and "freezes" on switch — most visible inside cmux/gostty,
36
+ // where the pane background belongs to the multiplexer. DSH_TUI_TRANSPARENT=1
37
+ // opts back into the old see-through canvas for users who want their
38
+ // terminal theme to stay visible.
39
+ const paintCanvas = process.env.DSH_TUI_TRANSPARENT !== '1';
40
+ if (paintCanvas)
41
+ tui.setCanvasBackground(ansiBg(themeRef.palette.canvas));
30
42
  // ------------------------------------------------------------- component tree --
31
43
  // Live Todos widget, pinned ABOVE the chat input: a plain Container with
32
44
  // auto height — it renders zero rows while empty and grows to its
@@ -45,6 +57,9 @@ export function startTui(options = {}) {
45
57
  let editor = new CwdBorderEditor(tui, themeRef.editor, process.cwd(), {
46
58
  infoColor: text => ansiFg(themeRef.palette.fgMuted) + text + RESET,
47
59
  });
60
+ // User keybindings overrides — mutable so `/hotkeys` can live-apply a
61
+ // write without restarting the TUI (see `setKeyBindings`).
62
+ let keyBindingsRef = options.keyBindings;
48
63
  // External wiring that must survive editor rebuilds.
49
64
  let autocompleteProvider;
50
65
  let branchProvider;
@@ -74,6 +89,10 @@ export function startTui(options = {}) {
74
89
  editor.onSubmit = (text) => {
75
90
  if (text.trim() === '')
76
91
  return;
92
+ // Record the submitted message for Up/Down browse before dispatching —
93
+ // covers both the real path and the smoke-test fallback echo path. The
94
+ // editor's own `submitValue()` never calls `addToHistory` in pi-tui 0.84.2.
95
+ editor.addToHistory(text);
77
96
  if (options.onSubmit !== undefined) {
78
97
  options.onSubmit(text);
79
98
  return;
@@ -94,12 +113,23 @@ export function startTui(options = {}) {
94
113
  */
95
114
  function rebuildEditor() {
96
115
  const text = editor.getText();
116
+ // Preserve the Up/Down browse history AND the mid-browse cursor/draft
117
+ // across a theme-swap rebuild: the rebuild fires asynchronously (settings
118
+ // watch, CSI 997 terminal-follow) — precisely while the user may be sitting
119
+ // mid-browse with a pre-browse draft saved in the base. Copies `history`
120
+ // and `browse` so the swap restores exactly where the user was.
121
+ const history = editor.getHistory();
122
+ const browse = editor.getBrowseState();
97
123
  const hadFocus = editor.focused;
98
124
  const next = new CwdBorderEditor(tui, themeRef.editor, process.cwd(), {
99
125
  infoColor: text => ansiFg(themeRef.palette.fgMuted) + text + RESET,
100
126
  });
101
127
  next.setText(text);
102
128
  next.onSubmit = editor.onSubmit;
129
+ // `addToHistory` unshifts, so reseed oldest→newest to land in the same
130
+ // order on the new instance; then restore the browse cursor/draft.
131
+ next.reseedHistory(history);
132
+ next.restoreBrowseState(browse);
103
133
  if (autocompleteProvider !== undefined)
104
134
  next.setAutocompleteProvider(autocompleteProvider);
105
135
  if (branchProvider !== undefined)
@@ -140,6 +170,8 @@ export function startTui(options = {}) {
140
170
  if (theme === themeRef)
141
171
  return;
142
172
  themeRef = theme;
173
+ if (paintCanvas)
174
+ tui.setCanvasBackground(ansiBg(theme.palette.canvas));
143
175
  rebuildEditor();
144
176
  tui.requestRender();
145
177
  },
@@ -155,21 +187,92 @@ export function startTui(options = {}) {
155
187
  permissionProvider = provider;
156
188
  editor.setPermissionProvider(provider);
157
189
  },
190
+ setKeyBindings(bindings) {
191
+ keyBindingsRef = bindings;
192
+ },
158
193
  };
159
194
  // ------------------------------------------------------------- input & focus --
160
195
  tui.setFocus(editor);
196
+ /**
197
+ * App-level key dispatch — the pi interrupt chain (see keymap.ts):
198
+ * popup/overlay first (a popup closes itself and never stops the running
199
+ * task), then the editor's autocomplete, then the running agent — where a
200
+ * DOUBLE Esc within `DOUBLE_PRESS_MS` stops the whole task (parent +
201
+ * subagents; the first press only arms the stop window) — and finally the
202
+ * empty-editor double-press windows. Every decision is delegated to the
203
+ * pure `resolveKeyAction`; the listener only composes live state, advances
204
+ * the double-press timestamps, and reports whether the key was consumed.
205
+ * `matchesKey('escape')` is true for the lone ESC byte `\x1b` — and,
206
+ * because legacy terminal Ctrl+[ sends the same byte, for Ctrl+[ too
207
+ * (pi-tui keys.js normalizes both to `escape`).
208
+ */
209
+ let lastEscPress = 0;
210
+ let lastRunningEscPress = 0;
211
+ let lastCtrlCPress = 0;
212
+ let lastOverlayEscPress = 0;
213
+ const isRunning = options.isRunning ?? (() => false);
214
+ const getRunningAgents = options.getRunningAgents ?? (() => 0);
161
215
  tui.addInputListener((data) => {
162
- if (matchesKey(data, 'ctrl+c')) {
163
- if (options.onInterrupt !== undefined) {
164
- options.onInterrupt();
216
+ const now = Date.now();
217
+ const action = resolveKeyAction(data, {
218
+ running: isRunning(),
219
+ overlayOpen: tui.hasOverlay(),
220
+ editorHasText: editor.getText() !== '',
221
+ autocompleteOpen: editor.isShowingAutocomplete(),
222
+ runningAgents: getRunningAgents(),
223
+ lastEscPress,
224
+ lastRunningEscPress,
225
+ lastCtrlCPress,
226
+ lastOverlayEscPress,
227
+ }, now, keyBindingsRef);
228
+ // Advance the double-press windows for the keys this chain owns. Two
229
+ // separate Esc windows keep their own clocks: the running-stop arm
230
+ // (`interrupt-arm-stop` -> `interrupt-cancel`) and the idle double-Esc
231
+ // (`interrupt-arm` -> `interrupt-double`), so an Esc that armed a stop
232
+ // never pops /session once the turn settles inside the window, and vice
233
+ // versa. A fired stop re-arms the running clock at the cancel timestamp:
234
+ // lashes of held-key Esc after the cancel land <80ms later and resolve as
235
+ // swallowable `key-repeat` instead of re-noticing another arm. Esc
236
+ // `key-repeat` never advances an Esc window - a held key must not arm or
237
+ // complete an Esc double press. A popup-owned Esc (`overlay-esc`) arms
238
+ // ONLY the post-popup guard clock - never the stop/idle windows.
239
+ if (action.kind === 'interrupt-arm' || action.kind === 'interrupt-double') {
240
+ lastEscPress = now;
241
+ }
242
+ else if (action.kind === 'interrupt-arm-stop' || action.kind === 'interrupt-cancel') {
243
+ lastRunningEscPress = now;
244
+ }
245
+ else if (action.kind === 'overlay-esc') {
246
+ lastOverlayEscPress = now;
247
+ }
248
+ // The ctrl+c clock advances on EVERY arrival (press, swallowed repeat,
249
+ // popup-owned - anything but a key-release) so the quit gap measures
250
+ // CONSECUTIVE key events: a held key's repeats keep the gap small and can
251
+ // never reach the QUIT_MIN_GAP_MS floor (see resolveCtrlC in keymap.ts).
252
+ if (action.kind !== 'key-release' && matchesKey(data, mergeKeyBindings(keyBindingsRef).ctrlC)) {
253
+ lastCtrlCPress = now;
254
+ }
255
+ // Pass-through outcomes (popup, autocomplete, unbound keys) let the
256
+ // focused component handle the key; inert swallows (key-release,
257
+ // overlay-esc, esc-after-overlay) go nowhere; actionable ones
258
+ // (key-repeat included, so the executor can abort a pending quit
259
+ // confirmation on it) go to the caller.
260
+ if (action.kind !== 'overlay' && action.kind !== 'autocomplete-close' && action.kind !== 'noop'
261
+ && action.kind !== 'key-release' && action.kind !== 'overlay-esc' && action.kind !== 'esc-after-overlay') {
262
+ if (options.onKeyAction !== undefined) {
263
+ options.onKeyAction(action);
165
264
  }
166
- else {
265
+ else if (action.kind === 'ctrl-c-cancel' || action.kind === 'ctrl-c-clear'
266
+ || action.kind === 'ctrl-c-quit' || action.kind === 'ctrl-d-quit') {
267
+ // Smoke-test fallback: any Ctrl+C family press, or an empty-editor
268
+ // Ctrl+D, exits - Ctrl+C behaves like the pre-keymap build. (key-repeat
269
+ // deliberately excluded: no terminal is attached in smoke mode, but a
270
+ // repeat must never be an exit path by itself.)
167
271
  handle.dispose();
168
272
  process.exit(130);
169
273
  }
170
- return { consume: true };
171
274
  }
172
- return undefined;
275
+ return action.consumes ? { consume: true } : undefined;
173
276
  });
174
277
  tui.setLayoutRoot(root);
175
278
  try {
package/lib/tui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwayds/dsh-tui-pi",
3
- "version": "0.1.1",
3
+ "version": "0.4.0",
4
4
  "description": "pi-style terminal UI for DeepSeek Harness (dsh) — pi-tui look & feel, dsh slash commands, GitHub light/dark themes, powerline footer",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -18,23 +18,22 @@
18
18
  "cordis.patch.yml",
19
19
  "patches",
20
20
  "pnpm-workspace.yaml",
21
+ "scripts",
21
22
  "templates",
22
23
  "README.md",
23
24
  "LICENSE"
24
25
  ],
25
26
  "scripts": {
27
+ "postinstall": "node scripts/link-dsh-closure.mjs",
26
28
  "build": "tsc -p tsconfig.json",
27
29
  "check": "tsc --noEmit -p tsconfig.json",
28
30
  "pretest": "npm run build",
29
31
  "test": "node --test test/*.test.mjs"
30
32
  },
31
33
  "dependencies": {
32
- "@deepseek-ai/dsh-client-schema-form": "0.1.0-rc.6",
33
34
  "@earendil-works/pi-tui": "0.84.2"
34
35
  },
35
36
  "devDependencies": {
36
- "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
37
- "@deepseek-ai/schemastery": "3.18.1",
38
37
  "@types/node": "^24.0.0",
39
38
  "typescript": "~5.9.0"
40
39
  },
@@ -1,8 +1,54 @@
1
+ diff --git a/dist/components/editor.d.ts b/dist/components/editor.d.ts
2
+ index a6fedc9e3b36d066e34860d040db6df47d88c432..f4d9afd8bc64f5ce5aafa06bb1aa8db3338c4f7d 100644
3
+ --- a/dist/components/editor.d.ts
4
+ +++ b/dist/components/editor.d.ts
5
+ @@ -24,6 +24,8 @@ export interface TextChunk {
6
+ export declare function wordWrapLine(line: string, maxWidth: number, preSegmented?: Intl.SegmentData[]): TextChunk[];
7
+ export interface EditorTheme {
8
+ borderColor: (str: string) => string;
9
+ + /** Optional style for the editor's input text rows. Absent → the terminal default foreground is used (invisible on an app-painted dark canvas). */
10
+ + textColor?: (str: string) => string;
11
+ selectList: SelectListTheme;
12
+ }
13
+ export interface EditorOptions {
1
14
  diff --git a/dist/components/editor.js b/dist/components/editor.js
2
- index 4786b46932b65785cfbd2304610ca77ccead910a..e0e4e39c3be8e5d4d828621ad2d3d8768984add8 100644
15
+ index 4786b46932b65785cfbd2304610ca77ccead910a..93c5f7e1596a0b567cf9fc59600c16596a60e567 100644
3
16
  --- a/dist/components/editor.js
4
17
  +++ b/dist/components/editor.js
5
- @@ -463,14 +463,20 @@ export class Editor {
18
+ @@ -433,13 +433,13 @@ export class Editor {
19
+ const afterGraphemes = [...this.segment(after, "grapheme")];
20
+ const firstGrapheme = afterGraphemes[0]?.segment || "";
21
+ const restAfter = after.slice(firstGrapheme.length);
22
+ - const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
23
+ + const cursor = `\x1b[7m${firstGrapheme}\x1b[27m`;
24
+ displayText = before + marker + cursor + restAfter;
25
+ // lineVisibleWidth stays the same - we're replacing, not adding
26
+ }
27
+ else {
28
+ // Cursor is at the end - add highlighted space
29
+ - const cursor = "\x1b[7m \x1b[0m";
30
+ + const cursor = "\x1b[7m \x1b[27m";
31
+ displayText = before + marker + cursor;
32
+ lineVisibleWidth = lineVisibleWidth + 1;
33
+ // If cursor overflows content width into the padding, flag it
34
+ @@ -451,8 +451,14 @@ export class Editor {
35
+ // Calculate padding based on actual visible width
36
+ const padding = " ".repeat(Math.max(0, contentWidth - lineVisibleWidth));
37
+ const lineRightPadding = cursorInPadding ? rightPadding.slice(1) : rightPadding;
38
+ - // Render the line (no side borders, just horizontal lines above and below)
39
+ - result.push(`${leftPadding}${displayText}${padding}${lineRightPadding}`);
40
+ + // Render the line (no side borders, just horizontal lines above and below).
41
+ + // The theme's optional textColor styles the input rows — without it they
42
+ + // fall back to the terminal default foreground, which is invisible on an
43
+ + // app-painted dark canvas. Applied per line; the cursor's reverse-video
44
+ + // closes with ESC[27m (reverse off, not full reset) so the themed
45
+ + // foreground survives past a mid-text cursor.
46
+ + const styledText = this.theme.textColor ? this.theme.textColor(displayText) : displayText;
47
+ + result.push(`${leftPadding}${styledText}${padding}${lineRightPadding}`);
48
+ }
49
+ // Render bottom border (with scroll indicator if more content below)
50
+ const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
51
+ @@ -463,14 +469,20 @@ export class Editor {
6
52
  else {
7
53
  result.push(horizontal.repeat(width));
8
54
  }
@@ -70,3 +116,161 @@ index 2e9231150951c3d3f3e13ac8ae8d623f06d38d5d..d5c8859ecd08fe4eccdc3cfca8ac7a32
70
116
  }
71
117
  getPrimaryColumnWidth() {
72
118
  const { min, max } = this.getPrimaryColumnBounds();
119
+ diff --git a/dist/index.d.ts b/dist/index.d.ts
120
+ index 50b5aeb61f12011642978bc2c31d8a2eb36d17d3..28b70df8001dc6b78056c408bdd6016f34f44e46 100644
121
+ --- a/dist/index.d.ts
122
+ +++ b/dist/index.d.ts
123
+ @@ -25,7 +25,7 @@ export { ProcessTerminal, type Terminal } from "./terminal.ts";
124
+ export { parseOsc11BackgroundColor, parseTerminalColorSchemeReport, type RgbColor, type TerminalColorScheme, } from "./terminal-colors.ts";
125
+ export { allocateImageId, type CellDimensions, calculateImageRows, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getWebpDimensions, hyperlink, type ImageDimensions, type ImageProtocol, type ImageRenderOptions, imageFallback, renderImage, resetCapabilitiesCache, setCapabilities, setCellDimensions, type TerminalCapabilities, } from "./terminal-image.ts";
126
+ export { type Component, Container, CURSOR_MARKER, compositeTuiLine, type Focusable, isFocusable, isViewportTUI, type OverlayAnchor, type OverlayHandle, type OverlayMargin, type OverlayOptions, type OverlayUnfocusOptions, type SizeValue, type TUI, type TuiInputListener, type TuiInputListenerResult, type TuiMode, type TuiStopOptions, type ViewportTUI, } from "./tui.ts";
127
+ -export { TuiAltScreen, type TuiAltScreenOptions } from "./tui-alt-screen.ts";
128
+ +export { paintCanvasRow, TuiAltScreen, type TuiAltScreenOptions } from "./tui-alt-screen.ts";
129
+ export { TuiMainScreen, type TuiMainScreenRenderState } from "./tui-main-screen.ts";
130
+ export { getOsc8LinkAtColumn, sliceByColumn, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "./utils.ts";
131
+ //# sourceMappingURL=index.d.ts.map
132
+
133
+ diff --git a/dist/index.js b/dist/index.js
134
+ index 97acf774699dded9c5e490f2441a93ec05b73be5..80a1abf6ac515c4df22e3bf78fc0504d50ad6dd4 100644
135
+ --- a/dist/index.js
136
+ +++ b/dist/index.js
137
+ @@ -35,7 +35,7 @@ export { parseOsc11BackgroundColor, parseTerminalColorSchemeReport, } from "./te
138
+ // Terminal image support
139
+ export { allocateImageId, calculateImageRows, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getWebpDimensions, hyperlink, imageFallback, renderImage, resetCapabilitiesCache, setCapabilities, setCellDimensions, } from "./terminal-image.js";
140
+ export { Container, CURSOR_MARKER, compositeTuiLine, isFocusable, isViewportTUI, } from "./tui.js";
141
+ -export { TuiAltScreen } from "./tui-alt-screen.js";
142
+ +export { paintCanvasRow, TuiAltScreen } from "./tui-alt-screen.js";
143
+ export { TuiMainScreen } from "./tui-main-screen.js";
144
+ // Utilities
145
+ export { getOsc8LinkAtColumn, sliceByColumn, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "./utils.js";
146
+ diff --git a/dist/tui-alt-screen.d.ts b/dist/tui-alt-screen.d.ts
147
+ index 848119411c1ff2885b8d1efe97f2f297bd4cd470..96422839632a71e6382a1fc6fcd9b43e7b435609 100644
148
+ --- a/dist/tui-alt-screen.d.ts
149
+ +++ b/dist/tui-alt-screen.d.ts
150
+ @@ -1,5 +1,11 @@
151
+ import type { Terminal } from "./terminal.ts";
152
+ import { type Component, TuiBase, type TuiStopOptions, VIEWPORT_TUI, type ViewportTUI } from "./tui.ts";
153
+ +/**
154
+ + * Paint one screen row with a canvas background SGR: prefix the row with the
155
+ + * bg code, re-inject it after every SGR that clears the background, and pad
156
+ + * to the full row width so the erase-line remainder carries the color too.
157
+ + */
158
+ +export declare function paintCanvasRow(line: string, background: string, width: number): string;
159
+ export interface TuiAltScreenOptions {
160
+ /** Number of logical lines moved for each mouse-wheel event. */
161
+ wheelScrollLines?: number;
162
+ @@ -57,11 +63,20 @@ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
163
+ private readonly openUrl?;
164
+ private readonly onRightClickPaste?;
165
+ private readonly copySelection?;
166
+ + private canvasBackground?;
167
+ constructor(terminal: Terminal, showHardwareCursor?: boolean, logDirectory?: string, options?: TuiAltScreenOptions);
168
+ get viewportTop(): number;
169
+ get isFollowingOutput(): boolean;
170
+ setLayoutRoot(component: Component | undefined): void;
171
+ render(width: number): string[];
172
+ + /**
173
+ + * Paint every rendered row with a canvas background SGR (e.g.
174
+ + * "\x1b[48;2;13;17;23m"). `undefined` restores the transparent default
175
+ + * (the terminal background shows through). The code is re-injected after
176
+ + * every SGR that clears the background and rows are padded to the full
177
+ + * width, so the whole screen carries the color — image rows excepted.
178
+ + */
179
+ + setCanvasBackground(background: string | undefined): void;
180
+ protected getMountedRoots(): readonly Component[];
181
+ private getPrimaryScrollView;
182
+ protected beforeTerminalStart(): void;
183
+ diff --git a/dist/tui-alt-screen.js b/dist/tui-alt-screen.js
184
+ index 4edcf845ba7866782a8ec7219508a2dce0073a91..338f2b29e7ba926399f3d663b0b79669c8ac6c13 100644
185
+ --- a/dist/tui-alt-screen.js
186
+ +++ b/dist/tui-alt-screen.js
187
+ @@ -21,6 +21,51 @@ const END_SYNCHRONIZED_OUTPUT = "\x1b[?2026l";
188
+ const OSC133_ZONE_PREFIX = /^(?:\x1b\]133;[ABC](?:\x07|\x1b\\))+/;
189
+ const OSC133_PROMPT_START = /^\x1b\]133;A(?:\x07|\x1b\\)/;
190
+ const PAGE_SCROLL_OVERLAP = 4;
191
+ +const CSI_PATTERN = /\x1b\[[0-9;:]*[A-Za-z]/g;
192
+ +/**
193
+ + * True when an SGR sequence resets the background to the terminal default:
194
+ + * a full reset (empty params or param 0) or the explicit default-bg param 49.
195
+ + * Intentional background sets (48/4…, 48;2;…) do not clear, so they win.
196
+ + */
197
+ +function sgrClearsBackground(sequence) {
198
+ + const params = sequence.slice(2, -1);
199
+ + if (params === "") return true;
200
+ + // A color-set (3x foreground / 4x background — ANSI 30-37/40-47, truecolor
201
+ + // 38;2/48;2, 256-color 38;5/48;5) never clears the background, even when a
202
+ + // channel is 0: 48;2;0;121;107 (teal) would otherwise read the 0 as the
203
+ + // reset param and re-inject the canvas over the segment. (49 alone — bg →
204
+ + // default — IS a clear and falls through to the loop below.)
205
+ + if (/^(?:3[0-7]|38;|4[0-8]|48;)/.test(params)) return false;
206
+ + for (const param of params.split(";")) {
207
+ + const code = param === "" ? 0 : Number.parseInt(param, 10);
208
+ + if (Number.isNaN(code)) continue;
209
+ + if (code === 0 || code === 49) return true;
210
+ + }
211
+ + return false;
212
+ +}
213
+ +/**
214
+ + * Paint one screen row with a canvas background SGR: prefix the row with the
215
+ + * bg code, re-inject it after every SGR that clears the background, and pad
216
+ + * to the full row width so the erase-line remainder carries the color too.
217
+ + * Image rows are the caller's responsibility (they must stay untouched).
218
+ + */
219
+ +export function paintCanvasRow(line, background, width) {
220
+ + let painted = background;
221
+ + let last = 0;
222
+ + for (const match of line.matchAll(CSI_PATTERN)) {
223
+ + const sequence = match[0];
224
+ + painted += line.slice(last, match.index);
225
+ + painted += sequence;
226
+ + if (sequence.endsWith("m") && sgrClearsBackground(sequence)) {
227
+ + painted += background;
228
+ + }
229
+ + last = match.index + sequence.length;
230
+ + }
231
+ + painted += line.slice(last);
232
+ + const pad = width - visibleWidth(line);
233
+ + if (pad > 0) painted += " ".repeat(pad);
234
+ + return `${painted}\x1b[0m`;
235
+ +}
236
+ const MAX_CACHED_OFFSCREEN_KITTY_IMAGES = 16;
237
+ const MAX_CACHED_OFFSCREEN_KITTY_TRANSMISSION_BYTES = 32 * 1024 * 1024;
238
+ const MAX_CACHED_OFFSCREEN_KITTY_DECODED_BYTES = 64 * 1024 * 1024;
239
+ @@ -64,6 +109,7 @@ export class TuiAltScreen extends TuiBase {
240
+ openUrl;
241
+ onRightClickPaste;
242
+ copySelection;
243
+ + canvasBackground;
244
+ constructor(terminal, showHardwareCursor, logDirectory, options = {}) {
245
+ super(terminal, showHardwareCursor, logDirectory);
246
+ this.implicitDocument = {
247
+ @@ -100,6 +146,18 @@ export class TuiAltScreen extends TuiBase {
248
+ render(width) {
249
+ return this.layoutRoot?.render(width) ?? super.render(width);
250
+ }
251
+ + /**
252
+ + * Paint every rendered row with a canvas background SGR (e.g.
253
+ + * "\x1b[48;2;13;17;23m"). `undefined` restores the transparent default
254
+ + * (the terminal background shows through). The code is re-injected after
255
+ + * every SGR that clears the background and rows are padded to the full
256
+ + * width, so the whole screen carries the color — image rows excepted.
257
+ + */
258
+ + setCanvasBackground(background) {
259
+ + if (this.canvasBackground === background) return;
260
+ + this.canvasBackground = background;
261
+ + this.requestRender();
262
+ + }
263
+ getMountedRoots() {
264
+ return this.layoutRoot ? [this.layoutRoot] : this.children;
265
+ }
266
+ @@ -1099,6 +1157,10 @@ export class TuiAltScreen extends TuiBase {
267
+ return line;
268
+ return sliceByColumn(line, 0, width, true);
269
+ });
270
+ + if (this.canvasBackground !== undefined) {
271
+ + const canvasBackground = this.canvasBackground;
272
+ + screen = screen.map((line) => (isImageLine(line) ? line : paintCanvasRow(line, canvasBackground, width)));
273
+ + }
274
+ const fullRedraw = this.previousScreen.length === 0 || this.previousScreenWidth !== width || this.previousScreenHeight !== height;
275
+ const imagesNeedRedraw = screen.some((line, row) => line !== this.previousScreen[row] && (isImageLine(line) || isImageLine(this.previousScreen[row] ?? "")));
276
+ const redrawImages = fullRedraw || imagesNeedRedraw;
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Dev loop: build → pack → install into a dsh profile — the same flow a real
4
+ * user gets (`dsh plugin add <tarball>`), no `link:` mount, no live-repo
5
+ * resolution at runtime.
6
+ *
7
+ * Usage: node scripts/dev-install.mjs [profile]
8
+ * (profile defaults to "tui")
9
+ *
10
+ * Steps:
11
+ * 1. `pnpm build` — emit lib/ from src/.
12
+ * 2. `pnpm pack` — produce <name>-<version>.tgz in the repo root.
13
+ * 3. In ~/.dsh/profiles/<profile>: remove the two installed copies of the
14
+ * plugin (bundle name `dsh-tui-pi` + scoped `@aiwayds/dsh-tui-pi`), then
15
+ * `pnpm install`. The file: tarball deps are reinstalled from the fresh
16
+ * tarball (pnpm does NOT re-read a changed tarball while the node_modules
17
+ * entry exists — the remove is what forces the refresh).
18
+ *
19
+ * The profile's package.json must declare BOTH keys pointing at the tarball:
20
+ * "dsh-tui-pi": "file:<repo>/<name>-<version>.tgz" (bundle resolution)
21
+ * "@aiwayds/dsh-tui-pi": "file:<repo>/<name>-<version>.tgz" (loader import)
22
+ * and pnpm-workspace.yaml must carry the pi-tui patchedDependencies (see
23
+ * ~/.dsh/profiles/tui/pnpm-workspace.yaml).
24
+ *
25
+ * After installing: restart dsh (or /reload inside the TUI) to load the new
26
+ * copy.
27
+ */
28
+
29
+ import { execFileSync } from 'node:child_process'
30
+ import { existsSync, readFileSync, rmSync } from 'node:fs'
31
+ import { homedir } from 'node:os'
32
+ import { dirname, join } from 'node:path'
33
+ import { fileURLToPath } from 'node:url'
34
+
35
+ const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)))
36
+ const profileName = process.argv[2] ?? 'tui'
37
+ const profileDir = join(homedir(), '.dsh', 'profiles', profileName)
38
+
39
+ function run(cmd, args, cwd) {
40
+ console.log(`$ ${cmd} ${args.join(' ')}`)
41
+ execFileSync(cmd, args, { cwd, stdio: 'inherit' })
42
+ }
43
+
44
+ if (!existsSync(join(profileDir, 'package.json'))) {
45
+ console.error(`[dev-install] profile not found: ${profileDir}`)
46
+ process.exit(1)
47
+ }
48
+
49
+ // 1. build
50
+ run('pnpm', ['build'], repoRoot)
51
+
52
+ // 2. pack
53
+ run('pnpm', ['pack'], repoRoot)
54
+ const manifest = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8'))
55
+ // pnpm packs scoped packages as <scope>-<name>-<version>.tgz (@aiwayds/dsh-tui-pi
56
+ // → aiwayds-dsh-tui-pi-0.2.0.tgz).
57
+ const tarballName = `${manifest.name.replace(/^@/, '').replace('/', '-')}-${manifest.version}.tgz`
58
+ const tarball = join(repoRoot, tarballName)
59
+ if (!existsSync(tarball)) {
60
+ console.error(`[dev-install] tarball not produced: ${tarball}`)
61
+ process.exit(1)
62
+ }
63
+ console.log(`[dev-install] tarball: ${tarball}`)
64
+
65
+ // B3: the profile's file: deps must reference THIS tarball. After a version
66
+ // bump the profile still points at the old <name>-<oldversion>.tgz — installing
67
+ // would silently load the stale package. Fail loudly instead.
68
+ const profileManifest = JSON.parse(readFileSync(join(profileDir, 'package.json'), 'utf8'))
69
+ for (const key of ['dsh-tui-pi', '@aiwayds/dsh-tui-pi']) {
70
+ const ref = profileManifest.dependencies?.[key]
71
+ if (typeof ref !== 'string' || !ref.includes(tarballName)) {
72
+ console.error(
73
+ `[dev-install] profile ${profileName} dependency "${key}" does not reference ${tarballName} ` +
74
+ `(got: ${ref}); update ~/.dsh/profiles/${profileName}/package.json first.`,
75
+ )
76
+ process.exit(1)
77
+ }
78
+ }
79
+
80
+ // 3. refresh the profile's installed copies (remove forces reinstall of the
81
+ // changed file: tarball — pnpm skips re-reading it while the entry exists)
82
+ for (const dir of ['dsh-tui-pi', join('@aiwayds', 'dsh-tui-pi')]) {
83
+ rmSync(join(profileDir, 'node_modules', dir), { recursive: true, force: true })
84
+ }
85
+ run('pnpm', ['install'], profileDir)
86
+
87
+ console.log(`[dev-install] installed into ${profileName} — restart dsh (or /reload) to load the new copy.`)
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Postinstall linker: point every `node_modules/@deepseek-ai/*` entry at the
3
+ * global dsh closure.
4
+ *
5
+ * Why this exists: dsh-tui-pi is a plugin that runs *inside* the installed
6
+ * dsh CLI, and its source imports the `@deepseek-ai/*` packages (cordis,
7
+ * dsh-session, dsh-settings, schemastery, …). Those packages are **not**
8
+ * resolvable from the public npm registry in a usable way (their rc.6
9
+ * versions live only in the dsh install's own node_modules), so the repo
10
+ * resolves them from the *global dsh closure* —
11
+ * `$(realpath $(which dsh))/node_modules/@deepseek-ai` — via plain symlinks.
12
+ *
13
+ * The contract: ALL `@deepseek-ai/*` resolve to that single closure instance,
14
+ * so there is exactly one `@deepseek-ai/cordis` in the type graph. Declaring
15
+ * any of them in package.json made `pnpm install` create a second local copy
16
+ * in `.pnpm`, which broke the cordis `declare module` augmentation
17
+ * (`Property 'settings' does not exist on type 'Context'`), so they must stay
18
+ * *undeclared*. pnpm then treats the closure links as extraneous — and, as
19
+ * it prunes entries it once managed, the links can still disappear after an
20
+ * install. This script re-creates them on every `pnpm install` (postinstall).
21
+ *
22
+ * It is a no-op (exit 0) when no global dsh install is found — a dev machine
23
+ * without dsh simply cannot typecheck against dsh types.
24
+ */
25
+
26
+ import { execFileSync } from 'node:child_process'
27
+ import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, symlinkSync } from 'node:fs'
28
+ import { dirname, join } from 'node:path'
29
+ import { fileURLToPath } from 'node:url'
30
+
31
+ const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)))
32
+ // Guard: this postinstall is a dev-machine convenience for THIS repo. A
33
+ // published tarball carries scripts/ but no src/, and must never touch a
34
+ // consumer's node_modules (it would delete their @deepseek-ai packages and
35
+ // replace them with symlinks into their global dsh closure). pnpm ≥10 blocks
36
+ // dependency lifecycle scripts by default, but npm would run this — exit
37
+ // silently outside the repo.
38
+ if (!existsSync(join(repoRoot, 'src'))) {
39
+ process.exit(0)
40
+ }
41
+ const scopeDir = join(repoRoot, 'node_modules', '@deepseek-ai')
42
+
43
+ /** The global dsh package's own `node_modules/@deepseek-ai` closure dir. */
44
+ function findDshClosure() {
45
+ // 1) Follow the `dsh` bin — the most faithful pointer to the installed CLI
46
+ // (`/opt/homebrew/bin/dsh` → …/lib/bin.js → pkg dir → its node_modules).
47
+ try {
48
+ const bin = execFileSync('which', ['dsh'], { encoding: 'utf8' }).trim()
49
+ if (bin !== '') {
50
+ const real = realpathSync(bin)
51
+ const closure = join(dirname(dirname(real)), 'node_modules', '@deepseek-ai')
52
+ if (existsSync(join(closure, 'cordis'))) return closure
53
+ }
54
+ } catch { /* dsh not on PATH */ }
55
+ // 2) Fall back to the global node_modules root.
56
+ try {
57
+ const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8' }).trim()
58
+ const closure = join(root, '@deepseek-ai', 'dsh', 'node_modules', '@deepseek-ai')
59
+ if (existsSync(join(closure, 'cordis'))) return closure
60
+ } catch { /* npm unavailable */ }
61
+ return undefined
62
+ }
63
+
64
+ const closure = findDshClosure()
65
+ if (closure === undefined) {
66
+ console.warn('[link-dsh-closure] global dsh not found — skipping @deepseek-ai links (dev without dsh)')
67
+ process.exit(0)
68
+ }
69
+
70
+ mkdirSync(scopeDir, { recursive: true })
71
+ let linked = 0
72
+ for (const name of readdirSync(closure)) {
73
+ const target = join(scopeDir, name)
74
+ const source = join(closure, name)
75
+ try {
76
+ // Replace any existing entry (stale symlink, or a local .pnpm copy a
77
+ // previous install created) with the closure link.
78
+ rmSync(target, { recursive: true, force: true })
79
+ symlinkSync(source, target, 'junction')
80
+ linked++
81
+ } catch (error) {
82
+ console.warn(`[link-dsh-closure] failed to link ${name}: ${(error instanceof Error ? error.message : String(error))}`)
83
+ }
84
+ }
85
+ console.log(`[link-dsh-closure] linked ${linked} @deepseek-ai/* packages from ${closure}`)
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: ArtyDuck
3
+ display_name: 艺术鸭
4
+ description: "多模态识别"
5
+ color: yellow
6
+ model: minimax-cn/MiniMax-M3
7
+ deep: 0
8
+ thinking: high
9
+ ---
10
+
11
+ 你是艺术家,支持多模态模型,理解世界.