@agi-fans/dsh-tui 0.9.0 → 0.10.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 (61) hide show
  1. package/lib/CHANGELOG.md +21 -1
  2. package/lib/chrome/welcome-tips.js +1 -0
  3. package/lib/chrome/welcome-tips.js.map +1 -1
  4. package/lib/chrome/width.js +36 -0
  5. package/lib/chrome/width.js.map +1 -1
  6. package/lib/commands/export.js +11 -6
  7. package/lib/commands/export.js.map +1 -1
  8. package/lib/commands/model.js +111 -4
  9. package/lib/commands/model.js.map +1 -1
  10. package/lib/commands/session.js +79 -36
  11. package/lib/commands/session.js.map +1 -1
  12. package/lib/commands/trajectory.d.ts +5 -0
  13. package/lib/commands/trajectory.js +20 -0
  14. package/lib/commands/trajectory.js.map +1 -0
  15. package/lib/definition.d.ts +12 -0
  16. package/lib/input/keybindings-config.d.ts +1 -1
  17. package/lib/input/keybindings-config.js +7 -1
  18. package/lib/input/keybindings-config.js.map +1 -1
  19. package/lib/runtime/provider-local.d.ts +1 -0
  20. package/lib/runtime/provider-local.js +110 -19
  21. package/lib/runtime/provider-local.js.map +1 -1
  22. package/lib/runtime/terminal-notifications.d.ts +24 -0
  23. package/lib/runtime/terminal-notifications.js +61 -0
  24. package/lib/runtime/terminal-notifications.js.map +1 -0
  25. package/lib/session/context-diagnostics.d.ts +8 -0
  26. package/lib/session/context-diagnostics.js +50 -0
  27. package/lib/session/context-diagnostics.js.map +1 -0
  28. package/lib/session/model-favorites.d.ts +11 -0
  29. package/lib/session/model-favorites.js +74 -0
  30. package/lib/session/model-favorites.js.map +1 -0
  31. package/lib/session/session-controller.d.ts +7 -1
  32. package/lib/session/session-controller.js +39 -6
  33. package/lib/session/session-controller.js.map +1 -1
  34. package/lib/session/session-library.d.ts +8 -0
  35. package/lib/session/session-library.js +43 -0
  36. package/lib/session/session-library.js.map +1 -0
  37. package/lib/session/subagent-roster.js +11 -1
  38. package/lib/session/subagent-roster.js.map +1 -1
  39. package/lib/session/tui-settings.d.ts +2 -0
  40. package/lib/session/tui-settings.js +2 -0
  41. package/lib/session/tui-settings.js.map +1 -1
  42. package/lib/views/agent-hub.d.ts +29 -0
  43. package/lib/views/agent-hub.js +165 -0
  44. package/lib/views/agent-hub.js.map +1 -0
  45. package/lib/views/event-views.d.ts +6 -0
  46. package/lib/views/event-views.js +18 -0
  47. package/lib/views/event-views.js.map +1 -1
  48. package/lib/views/hotkeys.js +3 -0
  49. package/lib/views/hotkeys.js.map +1 -1
  50. package/lib/views/prompt-selector.js +2 -1
  51. package/lib/views/prompt-selector.js.map +1 -1
  52. package/lib/views/settings-list.d.ts +2 -0
  53. package/lib/views/settings-list.js +21 -1
  54. package/lib/views/settings-list.js.map +1 -1
  55. package/lib/views/trajectory.d.ts +64 -0
  56. package/lib/views/trajectory.js +550 -0
  57. package/lib/views/trajectory.js.map +1 -0
  58. package/lib/views/transcript-export.d.ts +2 -0
  59. package/lib/views/transcript-export.js +34 -0
  60. package/lib/views/transcript-export.js.map +1 -1
  61. package/package.json +5 -1
@@ -0,0 +1,20 @@
1
+ /** Full-screen session Trajectory command registered through dsh-commands. */
2
+ import { registerCommands } from "./registration.js";
3
+ export const name = 'omdsh-command-trajectory';
4
+ export const inject = ['commands', 'tui'];
5
+ function openTrajectory(ctx, invocation) {
6
+ if (invocation.rawInput.trim() !== '')
7
+ return { kind: 'error', text: 'Usage: /trajectory' };
8
+ if (!ctx.tui.openTrajectory(invocation.agent.session.events)) {
9
+ return { kind: 'error', text: 'Trajectory requires an interactive terminal.' };
10
+ }
11
+ return { kind: 'success' };
12
+ }
13
+ export function apply(ctx) {
14
+ registerCommands(ctx, [{
15
+ name: 'trajectory',
16
+ description: 'Inspect the session event trajectory',
17
+ handler: invocation => openTrajectory(ctx, invocation),
18
+ }], 'omdsh trajectory command');
19
+ }
20
+ //# sourceMappingURL=trajectory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trajectory.js","sourceRoot":"","sources":["../../src/commands/trajectory.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAI9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEpD,MAAM,CAAC,MAAM,IAAI,GAAG,0BAA0B,CAAA;AAC9C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;AAEzC,SAAS,cAAc,CAAC,GAAY,EAAE,UAA6B;IACjE,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAA;IAC3F,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAA;IAChF,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,gBAAgB,CAAC,GAAG,EAAE,CAAC;YACrB,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,sCAAsC;YACnD,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,cAAc,CAAC,GAAG,EAAE,UAAU,CAAC;SACvD,CAAC,EAAE,0BAA0B,CAAC,CAAA;AACjC,CAAC"}
@@ -48,6 +48,13 @@ export interface TuiPrompt {
48
48
  tone: 'success' | 'warning' | 'error' | 'muted';
49
49
  };
50
50
  }[];
51
+ /** Optional single-key actions applied to the active option while the filter is empty. */
52
+ actions?: readonly {
53
+ key: string;
54
+ label: string;
55
+ /** Prefix returned before the selected option value. */
56
+ valuePrefix: string;
57
+ }[];
51
58
  multiSelect?: boolean;
52
59
  allowCustom?: boolean;
53
60
  /** Mask custom input while retaining the real value only in the prompt editor. */
@@ -110,6 +117,9 @@ export interface TuiSubagentView {
110
117
  readonly mode?: 'one-shot' | 'continuable';
111
118
  readonly phase: TuiSubagentPhase;
112
119
  readonly activity: readonly TuiSubagentActivity[];
120
+ /** First and latest durable event timestamps for elapsed-time presentation. */
121
+ readonly startedAt?: number;
122
+ readonly updatedAt?: number;
113
123
  }
114
124
  /** Live descendant roster for the active root session. */
115
125
  export interface TuiSubagentRoster {
@@ -196,6 +206,8 @@ export interface TuiService {
196
206
  }[]): void;
197
207
  /** Replace commands contributed by the active agent's Harness scope. */
198
208
  setCommands(commands: readonly TuiCommand[]): void;
209
+ /** Open a keyboard-driven event ledger for the active session. */
210
+ openTrajectory(events: readonly SessionEvent[]): boolean;
199
211
  /** Append a direct UI/command result without fabricating a session event. */
200
212
  notice(text: string, options?: TuiNoticeOptions): void;
201
213
  /** Append one successful plugin command result using the command-output surface. */
@@ -1,5 +1,5 @@
1
1
  /** User-overridable application keybindings loaded from one JSON document. */
2
- export type TuiAction = 'external-editor' | 'retry' | 'paste-clipboard' | 'copy-prompt' | 'copy-line' | 'inspect-subagent';
2
+ export type TuiAction = 'external-editor' | 'retry' | 'paste-clipboard' | 'copy-prompt' | 'copy-line' | 'inspect-subagent' | 'cycle-model-forward' | 'cycle-model-backward' | 'cycle-reasoning';
3
3
  export declare const DEFAULT_KEYBINDINGS: Readonly<Record<string, TuiAction>>;
4
4
  /** Load `{ "key-id": "action" }`; invalid rows are ignored independently. */
5
5
  export declare function loadKeybindings(path: string | undefined): Record<string, TuiAction>;
@@ -7,8 +7,14 @@ export const DEFAULT_KEYBINDINGS = Object.freeze({
7
7
  'alt+c': 'copy-prompt',
8
8
  'ctrl+alt+c': 'copy-line',
9
9
  'alt+a': 'inspect-subagent',
10
+ 'ctrl+p': 'cycle-model-forward',
11
+ 'alt+p': 'cycle-model-backward',
12
+ 'ctrl+t': 'cycle-reasoning',
10
13
  });
11
- const ACTIONS = ['external-editor', 'retry', 'paste-clipboard', 'copy-prompt', 'copy-line', 'inspect-subagent'];
14
+ const ACTIONS = [
15
+ 'external-editor', 'retry', 'paste-clipboard', 'copy-prompt', 'copy-line', 'inspect-subagent',
16
+ 'cycle-model-forward', 'cycle-model-backward', 'cycle-reasoning',
17
+ ];
12
18
  /** Load `{ "key-id": "action" }`; invalid rows are ignored independently. */
13
19
  export function loadKeybindings(path) {
14
20
  const result = { ...DEFAULT_KEYBINDINGS };
@@ -1 +1 @@
1
- {"version":3,"file":"keybindings-config.js","sourceRoot":"","sources":["../../src/input/keybindings-config.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAItC,MAAM,CAAC,MAAM,mBAAmB,GAAwC,MAAM,CAAC,MAAM,CAAC;IACpF,QAAQ,EAAE,iBAAiB;IAC3B,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,iBAAiB;IAC3B,OAAO,EAAE,aAAa;IACtB,YAAY,EAAE,WAAW;IACzB,OAAO,EAAE,kBAAkB;CAC5B,CAAC,CAAA;AAEF,MAAM,OAAO,GAAyB,CAAC,iBAAiB,EAAE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAA;AAErI,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAAC,IAAwB;IACtD,MAAM,MAAM,GAA8B,EAAE,GAAG,mBAAmB,EAAE,CAAA;IACpE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QAC9D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAA;QAChE,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAmB,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,MAAmB,CAAA;QAC1H,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,kEAAkE,CAAC,CAAC;IAC9E,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"keybindings-config.js","sourceRoot":"","sources":["../../src/input/keybindings-config.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAKtC,MAAM,CAAC,MAAM,mBAAmB,GAAwC,MAAM,CAAC,MAAM,CAAC;IACpF,QAAQ,EAAE,iBAAiB;IAC3B,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,iBAAiB;IAC3B,OAAO,EAAE,aAAa;IACtB,YAAY,EAAE,WAAW;IACzB,OAAO,EAAE,kBAAkB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,OAAO,EAAE,sBAAsB;IAC/B,QAAQ,EAAE,iBAAiB;CAC5B,CAAC,CAAA;AAEF,MAAM,OAAO,GAAyB;IACpC,iBAAiB,EAAE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,kBAAkB;IAC7F,qBAAqB,EAAE,sBAAsB,EAAE,iBAAiB;CACjE,CAAA;AAED,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAAC,IAAwB;IACtD,MAAM,MAAM,GAA8B,EAAE,GAAG,mBAAmB,EAAE,CAAA;IACpE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QAC9D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAA;QAChE,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAmB,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,MAAmB,CAAA;QAC1H,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,kEAAkE,CAAC,CAAC;IAC9E,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -92,6 +92,7 @@ export declare class LocalTui implements TuiService {
92
92
  setInspectedSubagent(inspected: TuiInspectedSubagent | undefined): void;
93
93
  setTools(tools: readonly ToolInfo[]): void;
94
94
  setCommands(commands: readonly TuiCommand[]): void;
95
+ openTrajectory(events: readonly SessionEvent[]): boolean;
95
96
  setSessionSearch(search?: SessionSearcher): void;
96
97
  setFileSearch(search?: FileSearcher): void;
97
98
  setImageValidator(validate?: (image: TuiInputImage) => Promise<void>): void;
@@ -10,6 +10,7 @@
10
10
  * printing of settled blocks.
11
11
  * @module @agi-fans/dsh-tui
12
12
  */
13
+ import { TerminalNotificationController, terminalNotificationSequence } from "./terminal-notifications.js";
13
14
  import { homedir } from 'node:os';
14
15
  import { join } from 'node:path';
15
16
  import { createInterface } from 'node:readline';
@@ -24,6 +25,8 @@ import { buildCopyTargets, extractCopyTarget, parseCopyKind } from "../views/cop
24
25
  import { applyHistorySearchEvent, createHistorySearch, } from "../views/history-search.js";
25
26
  import { InputEditor, lineEnd, lineStart } from "../input/editor.js";
26
27
  import { applySettingsEvent, createSettings, } from "../views/settings-list.js";
28
+ import { appendTrajectoryEvent, applyTrajectoryEvent, createTrajectory, } from "../views/trajectory.js";
29
+ import { applyAgentHubEvent, createAgentHub, updateAgentHub, } from "../views/agent-hub.js";
27
30
  import { applyEvent, blockLines, initialTranscript, replayEvents, renderView, TRANSCRIPT_FAST_SCROLL, } from "../views/event-views.js";
28
31
  import { flushPending, parseKeys } from "../input/keys.js";
29
32
  import { MainScreenRenderer } from "../chrome/main-screen-renderer.js";
@@ -83,6 +86,8 @@ export class LocalTui {
83
86
  #search = null;
84
87
  #settings = null;
85
88
  #copySelector = null;
89
+ #trajectory = null;
90
+ #agentHub = null;
86
91
  #pending = null;
87
92
  #queuedSubmissions = [];
88
93
  /** Newer queue entries temporarily detached while Up browses backward. */
@@ -128,6 +133,9 @@ export class LocalTui {
128
133
  #expandTools = false;
129
134
  #checkUpdates = true;
130
135
  #startupChangelog = 'summary';
136
+ #notifications = new TerminalNotificationController();
137
+ #notificationPolicy = 'off';
138
+ #notificationThreshold = '30s';
131
139
  #statusBar = defaultStatusBarConfig();
132
140
  #toolsExpanded = false;
133
141
  #expandedToolCalls = new Set();
@@ -238,6 +246,9 @@ export class LocalTui {
238
246
  }
239
247
  event(event, presentation) {
240
248
  this.#state = applyEvent(this.#state, event, presentation);
249
+ this.#emitNotification(this.#notifications.event({ type: event.type, ...('reason' in event && typeof event.reason === 'string' ? { reason: event.reason } : {}) }));
250
+ if (this.#trajectory !== null)
251
+ this.#trajectory = appendTrajectoryEvent(this.#trajectory, event);
241
252
  this.#syncTick();
242
253
  if (this.#tty) {
243
254
  if (event.type === 'assistant/chunk' && this.#streamRenderMs > 0) {
@@ -277,6 +288,12 @@ export class LocalTui {
277
288
  : { agents: roster.agents.map(agent => ({ ...agent, activity: [...agent.activity] })) };
278
289
  if ((this.#subagents?.agents.length ?? 0) === 0)
279
290
  this.#subagentLauncherFocused = false;
291
+ if (this.#agentHub !== null) {
292
+ if (this.#subagents === undefined)
293
+ this.#agentHub = null;
294
+ else
295
+ this.#agentHub = updateAgentHub(this.#agentHub, this.#subagents);
296
+ }
280
297
  const inspected = this.#inspected;
281
298
  if (inspected !== undefined) {
282
299
  const current = this.#subagents?.agents.find(agent => agent.id === inspected.id);
@@ -315,6 +332,19 @@ export class LocalTui {
315
332
  if (this.#tty)
316
333
  this.#render();
317
334
  }
335
+ openTrajectory(events) {
336
+ if (!this.#tty)
337
+ return false;
338
+ this.#trajectory = createTrajectory(events);
339
+ this.#agentHub = null;
340
+ this.#prompt = null;
341
+ this.#settings = null;
342
+ this.#copySelector = null;
343
+ this.#search = null;
344
+ this.#ac = null;
345
+ this.#render();
346
+ return true;
347
+ }
318
348
  setSessionSearch(search) {
319
349
  this.#searchSessions = search;
320
350
  this.#refreshAutocomplete();
@@ -355,6 +385,7 @@ export class LocalTui {
355
385
  return Promise.reject(new Error('omdsh-tui: prompt already in flight'));
356
386
  if (this.#disposed || request.signal?.aborted === true)
357
387
  return Promise.resolve(null);
388
+ this.#emitNotification(this.#notifications.humanPrompt());
358
389
  this.#editor.setText('');
359
390
  this.#ac = null;
360
391
  return new Promise((resolve) => {
@@ -386,6 +417,7 @@ export class LocalTui {
386
417
  });
387
418
  }
388
419
  replaceSession(events, presentations, status = 'idle') {
420
+ this.#trajectory = null;
389
421
  const state = replayEvents(events, presentations);
390
422
  this.#state = { ...state, status, compactCommandId: undefined };
391
423
  this.#plainPrinted = 0;
@@ -417,6 +449,9 @@ export class LocalTui {
417
449
  this.#expandTools = prefs.expandTools;
418
450
  this.#checkUpdates = prefs.checkUpdates ?? true;
419
451
  this.#startupChangelog = prefs.startupChangelog ?? 'summary';
452
+ this.#notificationPolicy = prefs.notifications ?? 'off';
453
+ this.#notificationThreshold = prefs.notificationThreshold ?? '30s';
454
+ this.#configureNotifications();
420
455
  this.#statusBar = resolveStatusBarConfig(prefs.statusBar, prefs.statusPreset);
421
456
  this.#toolsExpanded = prefs.expandTools;
422
457
  if (this.#settings !== null)
@@ -714,6 +749,8 @@ export class LocalTui {
714
749
  ...(this.#subagents === undefined ? {} : { subagents: this.#subagents }),
715
750
  subagentLauncherFocused: this.#subagentLauncherFocused,
716
751
  ...(this.#inspected === undefined ? {} : { inspected: this.#inspected }),
752
+ ...(this.#trajectory === null ? {} : { trajectory: this.#trajectory }),
753
+ ...(this.#agentHub === null ? {} : { agentHub: this.#agentHub }),
717
754
  statusBar: this.#statusBar,
718
755
  ...(this.#prompt === null ? {} : { promptSelector: this.#prompt }),
719
756
  ...(this.#settings !== null
@@ -1026,6 +1063,28 @@ export class LocalTui {
1026
1063
  this.#lastEscapeTime = 0;
1027
1064
  if (this.#handlePrompt(event))
1028
1065
  return;
1066
+ if (this.#trajectory !== null) {
1067
+ const command = applyTrajectoryEvent(this.#trajectory, event);
1068
+ if ('close' in command)
1069
+ this.#trajectory = null;
1070
+ else
1071
+ this.#trajectory = command.state;
1072
+ this.#render();
1073
+ return;
1074
+ }
1075
+ if (this.#agentHub !== null) {
1076
+ const command = applyAgentHubEvent(this.#agentHub, event);
1077
+ if ('close' in command)
1078
+ this.#agentHub = null;
1079
+ else if ('open' in command) {
1080
+ this.#agentHub = null;
1081
+ this.#openInspect(command.open);
1082
+ }
1083
+ else
1084
+ this.#agentHub = command.state;
1085
+ this.#render();
1086
+ return;
1087
+ }
1029
1088
  if (event.type === 'key') {
1030
1089
  const action = this.#keybindings[event.id];
1031
1090
  if (action !== undefined) {
@@ -1184,6 +1243,15 @@ export class LocalTui {
1184
1243
  return false;
1185
1244
  if (prompt.request.presentation === 'plan-review')
1186
1245
  return this.#handlePlanReview(event, prompt);
1246
+ if (event.type === 'text' && this.#editor.text === '') {
1247
+ const action = prompt.request.actions?.find(item => item.key === event.value);
1248
+ const selected = filteredPromptOptions(prompt.request, '')[prompt.selected];
1249
+ if (action !== undefined && selected !== undefined) {
1250
+ this.#finishPrompt(action.valuePrefix + (selected.value ?? selected.label));
1251
+ this.#render();
1252
+ return true;
1253
+ }
1254
+ }
1187
1255
  if (event.type === 'text' && event.value === ' ' && prompt.request.multiSelect === true && this.#editor.text === '') {
1188
1256
  this.#prompt = togglePromptSelection(prompt);
1189
1257
  this.#render();
@@ -1391,31 +1459,21 @@ export class LocalTui {
1391
1459
  for (const listener of this.#inspectCloses)
1392
1460
  listener();
1393
1461
  }
1394
- async #pickSubagent() {
1462
+ #pickSubagent() {
1395
1463
  this.#subagentLauncherFocused = false;
1396
1464
  const agents = this.#subagents?.agents ?? [];
1397
1465
  if (agents.length === 0) {
1398
1466
  this.notice('No subagents are available in this session.');
1399
1467
  return;
1400
1468
  }
1401
- const initialValue = this.#inspected?.id ?? agents[0]?.id;
1402
- const answer = await this.prompt({
1403
- title: 'Agent Hub',
1404
- question: 'Open a subagent transcript',
1405
- options: agents.map(agent => ({
1406
- label: agent.label,
1407
- value: agent.id,
1408
- description: agent.activity.at(-1)?.text ?? agent.phase,
1409
- })),
1410
- ...(initialValue === undefined ? {} : { initialValue }),
1411
- allowCustom: false,
1412
- presentation: 'fullscreen-list',
1413
- filterable: true,
1414
- submitLabel: 'open',
1415
- });
1416
- if (answer === null || answer === '')
1417
- return;
1418
- this.#openInspect(answer);
1469
+ this.#prompt = null;
1470
+ this.#settings = null;
1471
+ this.#copySelector = null;
1472
+ this.#search = null;
1473
+ this.#trajectory = null;
1474
+ this.#ac = null;
1475
+ this.#agentHub = createAgentHub({ agents });
1476
+ this.#render();
1419
1477
  }
1420
1478
  #prefs() {
1421
1479
  return {
@@ -1424,6 +1482,8 @@ export class LocalTui {
1424
1482
  expandTools: this.#expandTools,
1425
1483
  checkUpdates: this.#checkUpdates,
1426
1484
  startupChangelog: this.#startupChangelog,
1485
+ notifications: this.#notificationPolicy,
1486
+ notificationThreshold: this.#notificationThreshold,
1427
1487
  statusBar: {
1428
1488
  ...this.#statusBar,
1429
1489
  groups: [...this.#statusBar.groups],
@@ -1442,6 +1502,9 @@ export class LocalTui {
1442
1502
  this.#expandTools = prefs.expandTools;
1443
1503
  this.#checkUpdates = prefs.checkUpdates ?? true;
1444
1504
  this.#startupChangelog = prefs.startupChangelog ?? 'summary';
1505
+ this.#notificationPolicy = prefs.notifications ?? 'off';
1506
+ this.#notificationThreshold = prefs.notificationThreshold ?? '30s';
1507
+ this.#configureNotifications();
1445
1508
  this.#statusBar = resolveStatusBarConfig(prefs.statusBar, prefs.statusPreset);
1446
1509
  if (expandChanged)
1447
1510
  this.#toolsExpanded = prefs.expandTools;
@@ -1827,6 +1890,8 @@ export class LocalTui {
1827
1890
  this.#search = null;
1828
1891
  this.#settings = null;
1829
1892
  this.#copySelector = null;
1893
+ this.#trajectory = null;
1894
+ this.#agentHub = null;
1830
1895
  this.#followTail();
1831
1896
  // Image placeholders are TUI-owned. Mixed image+slash drafts stay a
1832
1897
  // submission so restore can keep the original markers; the runner strips
@@ -2031,6 +2096,20 @@ export class LocalTui {
2031
2096
  blocks: [...this.#state.blocks, { kind: 'notice', level: 'info', text }],
2032
2097
  };
2033
2098
  }
2099
+ #configureNotifications() {
2100
+ const thresholds = { '15s': 15_000, '30s': 30_000, '1m': 60_000, '2m': 120_000 };
2101
+ this.#notifications.configure({
2102
+ policy: this.#notificationPolicy,
2103
+ thresholdMs: thresholds[this.#notificationThreshold],
2104
+ });
2105
+ }
2106
+ #emitNotification(notification) {
2107
+ if (notification === undefined || !this.#tty)
2108
+ return;
2109
+ const sequence = terminalNotificationSequence(notification);
2110
+ if (sequence !== '')
2111
+ this.#term.output.write(sequence);
2112
+ }
2034
2113
  #toolCatalog() {
2035
2114
  this.#state = {
2036
2115
  ...this.#state,
@@ -2053,6 +2132,18 @@ export class LocalTui {
2053
2132
  this.#submit('/retry');
2054
2133
  return;
2055
2134
  }
2135
+ if (action === 'cycle-model-forward') {
2136
+ this.#submit('/model next');
2137
+ return;
2138
+ }
2139
+ if (action === 'cycle-model-backward') {
2140
+ this.#submit('/model previous');
2141
+ return;
2142
+ }
2143
+ if (action === 'cycle-reasoning') {
2144
+ this.#submit('/model reasoning');
2145
+ return;
2146
+ }
2056
2147
  if (action === 'copy-prompt') {
2057
2148
  void this.#copyPicked(this.#editor.text, 'current prompt');
2058
2149
  return;