@agi-fans/dsh-tui 0.15.0 → 0.16.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 (70) hide show
  1. package/lib/CHANGELOG.md +30 -1
  2. package/lib/chrome/diff-render.js +12 -0
  3. package/lib/chrome/diff-render.js.map +1 -1
  4. package/lib/chrome/main-screen-renderer.js +21 -5
  5. package/lib/chrome/main-screen-renderer.js.map +1 -1
  6. package/lib/chrome/markdown.js +5 -2
  7. package/lib/chrome/markdown.js.map +1 -1
  8. package/lib/chrome/renderer.d.ts +8 -0
  9. package/lib/chrome/renderer.js.map +1 -1
  10. package/lib/chrome/status-line.d.ts +1 -1
  11. package/lib/chrome/status-line.js +38 -9
  12. package/lib/chrome/status-line.js.map +1 -1
  13. package/lib/chrome/theme.d.ts +9 -2
  14. package/lib/chrome/theme.js +60 -12
  15. package/lib/chrome/theme.js.map +1 -1
  16. package/lib/chrome/tool-renderers.d.ts +2 -0
  17. package/lib/chrome/tool-renderers.js +102 -1
  18. package/lib/chrome/tool-renderers.js.map +1 -1
  19. package/lib/chrome/welcome-tips.js +1 -0
  20. package/lib/chrome/welcome-tips.js.map +1 -1
  21. package/lib/chrome/width.d.ts +19 -1
  22. package/lib/chrome/width.js +135 -41
  23. package/lib/chrome/width.js.map +1 -1
  24. package/lib/commands/session.js +3 -0
  25. package/lib/commands/session.js.map +1 -1
  26. package/lib/definition.d.ts +13 -1
  27. package/lib/runtime/provider-local.d.ts +6 -4
  28. package/lib/runtime/provider-local.js +75 -9
  29. package/lib/runtime/provider-local.js.map +1 -1
  30. package/lib/session/interaction-adapter.d.ts +10 -0
  31. package/lib/session/interaction-adapter.js +12 -1
  32. package/lib/session/interaction-adapter.js.map +1 -1
  33. package/lib/session/session-controller.js +7 -4
  34. package/lib/session/session-controller.js.map +1 -1
  35. package/lib/session/subagent-roster.d.ts +2 -9
  36. package/lib/session/subagent-roster.js +26 -31
  37. package/lib/session/subagent-roster.js.map +1 -1
  38. package/lib/views/agent-hub.d.ts +3 -0
  39. package/lib/views/agent-hub.js +40 -4
  40. package/lib/views/agent-hub.js.map +1 -1
  41. package/lib/views/copy-selector.d.ts +3 -0
  42. package/lib/views/copy-selector.js +26 -6
  43. package/lib/views/copy-selector.js.map +1 -1
  44. package/lib/views/event-views.d.ts +16 -0
  45. package/lib/views/event-views.js +57 -12
  46. package/lib/views/event-views.js.map +1 -1
  47. package/lib/views/history-search.d.ts +3 -0
  48. package/lib/views/history-search.js +40 -4
  49. package/lib/views/history-search.js.map +1 -1
  50. package/lib/views/hotkey-format.d.ts +39 -0
  51. package/lib/views/hotkey-format.js +42 -0
  52. package/lib/views/hotkey-format.js.map +1 -0
  53. package/lib/views/hotkeys.d.ts +3 -0
  54. package/lib/views/hotkeys.js +33 -3
  55. package/lib/views/hotkeys.js.map +1 -1
  56. package/lib/views/prompt-selector.d.ts +3 -0
  57. package/lib/views/prompt-selector.js +46 -7
  58. package/lib/views/prompt-selector.js.map +1 -1
  59. package/lib/views/settings-list.d.ts +3 -0
  60. package/lib/views/settings-list.js +35 -5
  61. package/lib/views/settings-list.js.map +1 -1
  62. package/lib/views/streaming-reveal.js +56 -17
  63. package/lib/views/streaming-reveal.js.map +1 -1
  64. package/lib/views/trajectory.d.ts +15 -5
  65. package/lib/views/trajectory.js +64 -7
  66. package/lib/views/trajectory.js.map +1 -1
  67. package/lib/views/transcript-search.d.ts +3 -0
  68. package/lib/views/transcript-search.js +24 -3
  69. package/lib/views/transcript-search.js.map +1 -1
  70. package/package.json +33 -33
@@ -33,7 +33,8 @@ import { applyTranscriptSearchEvent, createTranscriptSearch, searchBlockIndexes,
33
33
  import { nextRevealStep, revealStreamingAssistant, streamingAssistantKey, streamingAssistantUnits, } from "../views/streaming-reveal.js";
34
34
  import { flushPending, parseKeys } from "../input/keys.js";
35
35
  import { MainScreenRenderer } from "../chrome/main-screen-renderer.js";
36
- import { createTheme, detectTrueColor, parseThemeName } from "../chrome/theme.js";
36
+ import { colorDisabledByEnv, createTheme, detectTrueColor, parseThemeName } from "../chrome/theme.js";
37
+ import { renderTool } from "../chrome/tool-renderers.js";
37
38
  import { TUI_SETTINGS_NAMESPACE, TuiSettingsSchema } from "../session/tui-settings.js";
38
39
  import { defaultStatusBarConfig, resolveStatusBarConfig } from "../chrome/status-config.js";
39
40
  import { HistoryStore } from "../views/history-store.js";
@@ -81,6 +82,18 @@ function detectTerminalProfile() {
81
82
  }
82
83
  return process.platform === 'win32' ? 'conpty' : 'direct';
83
84
  }
85
+ /**
86
+ * Decide whether SGR is emitted. An explicit preference — plugin config or a
87
+ * value the user picked in `/settings` — always wins, so a non-empty `NO_COLOR`
88
+ * can never block someone who asked for color. Without a preference, a
89
+ * non-empty `NO_COLOR` or `FORCE_COLOR=0` suppresses color and the output
90
+ * stream's tty-ness decides.
91
+ */
92
+ function resolveColors(preference, isTty) {
93
+ if (preference !== undefined)
94
+ return preference;
95
+ return isTty && !colorDisabledByEnv();
96
+ }
84
97
  export class LocalTui {
85
98
  #deferInitialRender = false;
86
99
  #terminalProfile;
@@ -187,7 +200,12 @@ export class LocalTui {
187
200
  #subagentLauncherFocused = false;
188
201
  #inspected;
189
202
  #promptDocument;
190
- #trueColor;
203
+ /**
204
+ * 24-bit switch for the current color preference, re-derived whenever that
205
+ * preference changes so a session that starts colorless can still enable
206
+ * hexadecimal palettes without a restart.
207
+ */
208
+ #trueColor = false;
191
209
  #copy;
192
210
  #readClipboard;
193
211
  #readClipboardImage;
@@ -212,7 +230,8 @@ export class LocalTui {
212
230
  /**
213
231
  * @param term - terminal surface (injectable for tests).
214
232
  * @param model - model label for the status line.
215
- * @param colors - SGR styling switch.
233
+ * @param colors - SGR styling switch; `undefined` derives one from the
234
+ * output stream's tty-ness and the environment.
216
235
  * @param themeName - shipped palette.
217
236
  * @param copy - clipboard writer (defaults to the platform tool).
218
237
  * @param paths - cwd/home/listing used by `@` and path autocomplete.
@@ -220,7 +239,7 @@ export class LocalTui {
220
239
  constructor(term, model, colors, themeName = 'dark', copy = copyToClipboard, paths = {}) {
221
240
  this.#term = term;
222
241
  this.#model = model;
223
- this.#colors = colors;
242
+ this.#colors = resolveColors(colors, term.output.isTTY === true);
224
243
  this.#themeName = themeName;
225
244
  this.#copy = copy;
226
245
  this.#readClipboard = paths.readClipboard ?? readFromClipboard;
@@ -245,7 +264,7 @@ export class LocalTui {
245
264
  this.#terminalProfile = paths.terminalProfile ?? detectTerminalProfile();
246
265
  this.#resizeDebounceMs = Math.max(0, paths.resizeDebounceMs ?? 120);
247
266
  this.#streamRenderMs = Math.max(0, paths.streamRenderMs ?? 8);
248
- this.#trueColor = colors && detectTrueColor();
267
+ this.#syncTrueColor();
249
268
  this.#tty = term.input.isTTY === true;
250
269
  this.#pwd = shortenPath(project.root);
251
270
  this.#branch = project.gitLabel;
@@ -354,6 +373,13 @@ export class LocalTui {
354
373
  this.#render();
355
374
  }
356
375
  setSubagents(roster) {
376
+ const previous = this.#subagents?.agents ?? [];
377
+ const incoming = roster?.agents ?? [];
378
+ const statusChanged = previous.length !== incoming.length || incoming.some((agent, index) => {
379
+ const before = previous[index];
380
+ return before === undefined || before.id !== agent.id || before.label !== agent.label
381
+ || before.phase !== agent.phase || before.depth !== agent.depth || before.mode !== agent.mode;
382
+ });
357
383
  this.#subagents = roster === undefined
358
384
  ? undefined
359
385
  : { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })) };
@@ -378,11 +404,17 @@ export class LocalTui {
378
404
  };
379
405
  }
380
406
  }
407
+ if (!statusChanged && this.#agentHub === null)
408
+ return;
381
409
  this.#syncTick();
382
410
  if (this.#tty)
383
- this.#render();
411
+ this.#scheduleStreamRender();
384
412
  }
385
413
  setInspectedSubagent(inspected) {
414
+ if (this.#inspected?.id === inspected?.id && this.#inspected?.label === inspected?.label
415
+ && this.#inspected?.phase === inspected?.phase && this.#inspected?.mode === inspected?.mode
416
+ && this.#inspected?.writable === inspected?.writable)
417
+ return;
386
418
  this.#inspected = inspected === undefined ? undefined : { ...inspected };
387
419
  if (inspected !== undefined) {
388
420
  this.#inspectEscapeFenceUntil = 0;
@@ -433,6 +465,31 @@ export class LocalTui {
433
465
  setImageValidator(validate) {
434
466
  this.#validateImageDraft = validate;
435
467
  }
468
+ toolCallContext(callId) {
469
+ const block = this.#state.blocks.findLast(candidate => candidate.kind === 'tool' && String(candidate.callId) === callId);
470
+ if (block?.kind !== 'tool')
471
+ return undefined;
472
+ // Reuse the card renderer so a decision explains the call the same way the
473
+ // transcript already does. The approval prompt renders its detail on one
474
+ // line, so the parts are joined here rather than handed over as rows.
475
+ const view = renderTool({
476
+ name: block.name,
477
+ arguments: block.args,
478
+ output: block.output,
479
+ status: block.status,
480
+ expanded: false,
481
+ ...(block.presentation === undefined ? {} : { presentation: block.presentation }),
482
+ });
483
+ const detail = view.summary === undefined || view.summary === ''
484
+ ? view.input.join(' ').replace(/\s+/gu, ' ').trim()
485
+ : '';
486
+ // A card with a summary already says what the call is. Without one the raw
487
+ // argument text is all there is, and its pretty-printed form would reduce to
488
+ // a lone brace on a single row, so the whole body is folded into one line.
489
+ return [view.title, view.summary, detail === '' ? undefined : detail]
490
+ .filter((part) => part !== undefined && part !== '')
491
+ .join(' · ');
492
+ }
436
493
  notice(text, options = {}) {
437
494
  const block = {
438
495
  kind: 'notice',
@@ -538,11 +595,19 @@ export class LocalTui {
538
595
  this.#writtenWindowTitle = title;
539
596
  this.#term.output.write(`\x1b]2;${title}\x07`);
540
597
  }
598
+ /**
599
+ * Re-derive the 24-bit switch from the active color preference. Every path
600
+ * that changes `#colors` calls this so `/settings` takes effect immediately.
601
+ */
602
+ #syncTrueColor() {
603
+ this.#trueColor = this.#colors && detectTrueColor();
604
+ }
541
605
  /** Apply prefs loaded from the settings document (does not persist). */
542
606
  applyStoredPrefs(prefs) {
543
607
  const previousMotion = this.#motion;
544
608
  this.#themeName = prefs.theme;
545
609
  this.#colors = prefs.colors;
610
+ this.#syncTrueColor();
546
611
  this.#motion = prefs.motion ?? 'full';
547
612
  this.#terminalProgress = prefs.terminalProgress ?? false;
548
613
  this.#expandTools = prefs.expandTools;
@@ -1745,6 +1810,7 @@ export class LocalTui {
1745
1810
  const expandChanged = prefs.expandTools !== this.#expandTools;
1746
1811
  this.#themeName = prefs.theme;
1747
1812
  this.#colors = prefs.colors;
1813
+ this.#syncTrueColor();
1748
1814
  const previousMotion = this.#motion;
1749
1815
  this.#motion = prefs.motion ?? 'full';
1750
1816
  this.#terminalProgress = prefs.terminalProgress ?? false;
@@ -2315,7 +2381,7 @@ export class LocalTui {
2315
2381
  formatHelpText(this.#commands()),
2316
2382
  '',
2317
2383
  full
2318
- ? `**Keyboard Shortcuts · ${hotkeyCount(this.#keybindings)} bindings**`
2384
+ ? `**Keyboard Shortcuts · ${hotkeyCount(this.#keybindings)} shortcuts**`
2319
2385
  : '**Essential Shortcuts**',
2320
2386
  '',
2321
2387
  full
@@ -2617,7 +2683,7 @@ export function apply(ctx, config) {
2617
2683
  return () => { process.stdout.removeListener('resize', listener); };
2618
2684
  },
2619
2685
  };
2620
- const tui = new LocalTui(term, config.model, config.colors ?? term.output.isTTY === true, parseThemeName(config.theme), copyToClipboard, {
2686
+ const tui = new LocalTui(term, config.model, config.colors, parseThemeName(config.theme), copyToClipboard, {
2621
2687
  deferInitialRender: true,
2622
2688
  terminalProfile,
2623
2689
  alternateScreenOverlays: terminalProfile === 'direct',
@@ -2627,7 +2693,7 @@ export function apply(ctx, config) {
2627
2693
  ctx.provide(TUI_SERVICE, tui);
2628
2694
  ctx.effect(() => () => { tui.dispose(); });
2629
2695
  ctx.inject(['settings'], (settingsCtx) => {
2630
- const scope = settingsCtx.settings.register(TUI_SETTINGS_NAMESPACE, TuiSettingsSchema, { base: { theme: parseThemeName(config.theme), colors: config.colors ?? term.output.isTTY === true, expandTools: false } });
2696
+ const scope = settingsCtx.settings.register(TUI_SETTINGS_NAMESPACE, TuiSettingsSchema, { base: { theme: parseThemeName(config.theme), colors: resolveColors(config.colors, term.output.isTTY === true), expandTools: false } });
2631
2697
  tui.applyStoredPrefs(scope.get());
2632
2698
  tui.setPrefsPersist((prefs) => { void scope.update(prefs); });
2633
2699
  scope.watch((next) => { tui.applyStoredPrefs(next); });