@agi-fans/dsh-tui 0.18.0 → 0.20.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 (227) hide show
  1. package/lib/CHANGELOG.md +115 -1
  2. package/lib/chrome/box.d.ts +2 -0
  3. package/lib/chrome/box.js +6 -12
  4. package/lib/chrome/box.js.map +1 -1
  5. package/lib/chrome/code-highlight.d.ts +26 -18
  6. package/lib/chrome/code-highlight.js +114 -39
  7. package/lib/chrome/code-highlight.js.map +1 -1
  8. package/lib/chrome/diff-render.d.ts +1 -2
  9. package/lib/chrome/diff-render.js +41 -90
  10. package/lib/chrome/diff-render.js.map +1 -1
  11. package/lib/chrome/main-screen-renderer.d.ts +52 -30
  12. package/lib/chrome/main-screen-renderer.js +1730 -79
  13. package/lib/chrome/main-screen-renderer.js.map +1 -1
  14. package/lib/chrome/markdown.js +144 -101
  15. package/lib/chrome/markdown.js.map +1 -1
  16. package/lib/chrome/math.d.ts +4 -0
  17. package/lib/chrome/math.js +340 -0
  18. package/lib/chrome/math.js.map +1 -0
  19. package/lib/chrome/md-style.d.ts +3 -0
  20. package/lib/chrome/md-style.js.map +1 -1
  21. package/lib/chrome/mermaid.d.ts +6 -0
  22. package/lib/chrome/mermaid.js +56 -0
  23. package/lib/chrome/mermaid.js.map +1 -0
  24. package/lib/chrome/renderer.d.ts +78 -0
  25. package/lib/chrome/renderer.js.map +1 -1
  26. package/lib/chrome/status-config.d.ts +5 -0
  27. package/lib/chrome/status-config.js +3 -0
  28. package/lib/chrome/status-config.js.map +1 -1
  29. package/lib/chrome/status-line.d.ts +0 -21
  30. package/lib/chrome/status-line.js +8 -65
  31. package/lib/chrome/status-line.js.map +1 -1
  32. package/lib/chrome/terminal-images.d.ts +31 -0
  33. package/lib/chrome/terminal-images.js +34 -0
  34. package/lib/chrome/terminal-images.js.map +1 -0
  35. package/lib/chrome/theme.d.ts +23 -6
  36. package/lib/chrome/theme.js +217 -109
  37. package/lib/chrome/theme.js.map +1 -1
  38. package/lib/chrome/tool-args.d.ts +24 -1
  39. package/lib/chrome/tool-args.js +40 -1
  40. package/lib/chrome/tool-args.js.map +1 -1
  41. package/lib/chrome/tool-renderers.d.ts +11 -0
  42. package/lib/chrome/tool-renderers.js +55 -5
  43. package/lib/chrome/tool-renderers.js.map +1 -1
  44. package/lib/chrome/welcome-tips.js +2 -2
  45. package/lib/chrome/welcome-tips.js.map +1 -1
  46. package/lib/chrome/width.d.ts +7 -3
  47. package/lib/chrome/width.js +78 -14
  48. package/lib/chrome/width.js.map +1 -1
  49. package/lib/commands/diff.js +49 -3
  50. package/lib/commands/diff.js.map +1 -1
  51. package/lib/commands/export.js +28 -5
  52. package/lib/commands/export.js.map +1 -1
  53. package/lib/commands/files.d.ts +8 -0
  54. package/lib/commands/files.js +137 -0
  55. package/lib/commands/files.js.map +1 -0
  56. package/lib/commands/plugins.d.ts +8 -0
  57. package/lib/commands/plugins.js +158 -0
  58. package/lib/commands/plugins.js.map +1 -0
  59. package/lib/commands/session-configuration.d.ts +4 -0
  60. package/lib/commands/session-configuration.js +39 -8
  61. package/lib/commands/session-configuration.js.map +1 -1
  62. package/lib/commands/session.js +47 -15
  63. package/lib/commands/session.js.map +1 -1
  64. package/lib/commands/steer.js +2 -2
  65. package/lib/commands/steer.js.map +1 -1
  66. package/lib/commands/terminal.d.ts +7 -0
  67. package/lib/commands/terminal.js +99 -0
  68. package/lib/commands/terminal.js.map +1 -0
  69. package/lib/definition.d.ts +180 -14
  70. package/lib/definition.js.map +1 -1
  71. package/lib/input/clipboard.d.ts +12 -7
  72. package/lib/input/clipboard.js +79 -20
  73. package/lib/input/clipboard.js.map +1 -1
  74. package/lib/input/composer-files.d.ts +26 -0
  75. package/lib/input/composer-files.js +50 -0
  76. package/lib/input/composer-files.js.map +1 -0
  77. package/lib/input/editor-discovery.d.ts +21 -0
  78. package/lib/input/editor-discovery.js +99 -0
  79. package/lib/input/editor-discovery.js.map +1 -0
  80. package/lib/input/editor.d.ts +26 -1
  81. package/lib/input/editor.js +126 -12
  82. package/lib/input/editor.js.map +1 -1
  83. package/lib/input/external-editor.d.ts +5 -2
  84. package/lib/input/external-editor.js +14 -12
  85. package/lib/input/external-editor.js.map +1 -1
  86. package/lib/input/fuzzy-search.d.ts +9 -0
  87. package/lib/input/fuzzy-search.js +113 -0
  88. package/lib/input/fuzzy-search.js.map +1 -0
  89. package/lib/input/keybindings-config.d.ts +1 -1
  90. package/lib/input/keybindings-config.js +7 -1
  91. package/lib/input/keybindings-config.js.map +1 -1
  92. package/lib/input/keys.d.ts +17 -0
  93. package/lib/input/keys.js +21 -5
  94. package/lib/input/keys.js.map +1 -1
  95. package/lib/input/mouse-policy.d.ts +3 -0
  96. package/lib/input/mouse-policy.js +14 -0
  97. package/lib/input/mouse-policy.js.map +1 -0
  98. package/lib/input/paste-burst.d.ts +16 -0
  99. package/lib/input/paste-burst.js +61 -0
  100. package/lib/input/paste-burst.js.map +1 -0
  101. package/lib/input/text-selection.d.ts +20 -0
  102. package/lib/input/text-selection.js +195 -0
  103. package/lib/input/text-selection.js.map +1 -0
  104. package/lib/runtime/composer-images.d.ts +1 -0
  105. package/lib/runtime/composer-images.js +11 -3
  106. package/lib/runtime/composer-images.js.map +1 -1
  107. package/lib/runtime/file-open.d.ts +2 -0
  108. package/lib/runtime/file-open.js +12 -0
  109. package/lib/runtime/file-open.js.map +1 -0
  110. package/lib/runtime/file-review.d.ts +24 -0
  111. package/lib/runtime/file-review.js +188 -0
  112. package/lib/runtime/file-review.js.map +1 -0
  113. package/lib/runtime/herdr-agent.d.ts +7 -15
  114. package/lib/runtime/herdr-agent.js +151 -79
  115. package/lib/runtime/herdr-agent.js.map +1 -1
  116. package/lib/runtime/hook-settings.d.ts +17 -0
  117. package/lib/runtime/hook-settings.js +57 -0
  118. package/lib/runtime/hook-settings.js.map +1 -0
  119. package/lib/runtime/human-interaction.js +1 -1
  120. package/lib/runtime/human-interaction.js.map +1 -1
  121. package/lib/runtime/image-preview.d.ts +8 -0
  122. package/lib/runtime/image-preview.js +119 -0
  123. package/lib/runtime/image-preview.js.map +1 -0
  124. package/lib/runtime/plain-tui.d.ts +2 -0
  125. package/lib/runtime/plain-tui.js +2 -2
  126. package/lib/runtime/plain-tui.js.map +1 -1
  127. package/lib/runtime/plugin-settings.d.ts +17 -0
  128. package/lib/runtime/plugin-settings.js +154 -0
  129. package/lib/runtime/plugin-settings.js.map +1 -0
  130. package/lib/runtime/provider-local.d.ts +58 -7
  131. package/lib/runtime/provider-local.js +1394 -137
  132. package/lib/runtime/provider-local.js.map +1 -1
  133. package/lib/runtime/runner.js +3 -3
  134. package/lib/runtime/runner.js.map +1 -1
  135. package/lib/runtime/session-archive.d.ts +5 -0
  136. package/lib/runtime/session-archive.js +114 -0
  137. package/lib/runtime/session-archive.js.map +1 -0
  138. package/lib/runtime/subagent-model-selection.d.ts +5 -0
  139. package/lib/runtime/subagent-model-selection.js +15 -0
  140. package/lib/runtime/subagent-model-selection.js.map +1 -0
  141. package/lib/runtime/terminal-notifications.d.ts +8 -0
  142. package/lib/runtime/terminal-notifications.js +16 -0
  143. package/lib/runtime/terminal-notifications.js.map +1 -1
  144. package/lib/runtime/terminal-reader.d.ts +11 -0
  145. package/lib/runtime/terminal-reader.js +87 -0
  146. package/lib/runtime/terminal-reader.js.map +1 -0
  147. package/lib/runtime/tool-presentation.js +43 -30
  148. package/lib/runtime/tool-presentation.js.map +1 -1
  149. package/lib/session/composer-drafts.d.ts +20 -0
  150. package/lib/session/composer-drafts.js +103 -0
  151. package/lib/session/composer-drafts.js.map +1 -0
  152. package/lib/session/conversation-turns.d.ts +16 -0
  153. package/lib/session/conversation-turns.js +33 -0
  154. package/lib/session/conversation-turns.js.map +1 -0
  155. package/lib/session/feature-toggles.d.ts +79 -0
  156. package/lib/session/feature-toggles.js +139 -0
  157. package/lib/session/feature-toggles.js.map +1 -0
  158. package/lib/session/fold-policy.d.ts +4 -0
  159. package/lib/session/fold-policy.js +4 -0
  160. package/lib/session/fold-policy.js.map +1 -0
  161. package/lib/session/interaction-adapter.d.ts +2 -2
  162. package/lib/session/interaction-adapter.js +156 -25
  163. package/lib/session/interaction-adapter.js.map +1 -1
  164. package/lib/session/job-notice.d.ts +4 -0
  165. package/lib/session/job-notice.js +44 -3
  166. package/lib/session/job-notice.js.map +1 -1
  167. package/lib/session/message-queue.d.ts +22 -0
  168. package/lib/session/message-queue.js +157 -0
  169. package/lib/session/message-queue.js.map +1 -0
  170. package/lib/session/project-file-search.js +3 -21
  171. package/lib/session/project-file-search.js.map +1 -1
  172. package/lib/session/session-controller.d.ts +11 -18
  173. package/lib/session/session-controller.js +246 -100
  174. package/lib/session/session-controller.js.map +1 -1
  175. package/lib/session/session-library.d.ts +11 -3
  176. package/lib/session/session-library.js +48 -8
  177. package/lib/session/session-library.js.map +1 -1
  178. package/lib/session/session-tree.d.ts +26 -0
  179. package/lib/session/session-tree.js +112 -0
  180. package/lib/session/session-tree.js.map +1 -0
  181. package/lib/session/steering.d.ts +3 -0
  182. package/lib/session/steering.js +9 -0
  183. package/lib/session/steering.js.map +1 -0
  184. package/lib/session/tui-settings.d.ts +23 -1
  185. package/lib/session/tui-settings.js +15 -2
  186. package/lib/session/tui-settings.js.map +1 -1
  187. package/lib/session/workflow-projection.d.ts +9 -0
  188. package/lib/session/workflow-projection.js +44 -0
  189. package/lib/session/workflow-projection.js.map +1 -0
  190. package/lib/views/agent-hub.js +9 -4
  191. package/lib/views/agent-hub.js.map +1 -1
  192. package/lib/views/autocomplete.js +5 -25
  193. package/lib/views/autocomplete.js.map +1 -1
  194. package/lib/views/document-reader.d.ts +34 -0
  195. package/lib/views/document-reader.js +145 -0
  196. package/lib/views/document-reader.js.map +1 -0
  197. package/lib/views/event-views.d.ts +26 -2
  198. package/lib/views/event-views.js +196 -42
  199. package/lib/views/event-views.js.map +1 -1
  200. package/lib/views/hotkeys.d.ts +2 -0
  201. package/lib/views/hotkeys.js +20 -8
  202. package/lib/views/hotkeys.js.map +1 -1
  203. package/lib/views/prompt-selector.d.ts +33 -3
  204. package/lib/views/prompt-selector.js +287 -43
  205. package/lib/views/prompt-selector.js.map +1 -1
  206. package/lib/views/prompt-tree.d.ts +11 -0
  207. package/lib/views/prompt-tree.js +74 -0
  208. package/lib/views/prompt-tree.js.map +1 -0
  209. package/lib/views/session-files.d.ts +7 -0
  210. package/lib/views/session-files.js +62 -0
  211. package/lib/views/session-files.js.map +1 -0
  212. package/lib/views/settings-list.d.ts +32 -4
  213. package/lib/views/settings-list.js +148 -37
  214. package/lib/views/settings-list.js.map +1 -1
  215. package/lib/views/transcript-render.d.ts +117 -18
  216. package/lib/views/transcript-render.js +1084 -126
  217. package/lib/views/transcript-render.js.map +1 -1
  218. package/lib/views/transcript-types.d.ts +80 -2
  219. package/lib/views/transcript-types.js +11 -0
  220. package/lib/views/transcript-types.js.map +1 -1
  221. package/lib/views/workspace-changes.d.ts +27 -0
  222. package/lib/views/workspace-changes.js +128 -0
  223. package/lib/views/workspace-changes.js.map +1 -0
  224. package/package.json +77 -41
  225. package/lib/chrome/style.d.ts +0 -22
  226. package/lib/chrome/style.js +0 -23
  227. package/lib/chrome/style.js.map +0 -1
@@ -19,6 +19,8 @@ import type { SessionStatsProjection } from '@deepseek-ai/dsh-session-stats/type
19
19
  import type { ContextBreakdownProjection, ContextPressureProjection, TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client';
20
20
  import { type SessionEvent } from '@deepseek-ai/dsh-session';
21
21
  import type { GoalProjection } from '@deepseek-ai/dsh-goal/types';
22
+ import type { UserQuestionProjectionView } from '@deepseek-ai/dsh-user-questions';
23
+ export { conversationTurns, type ConversationTurn } from './conversation-turns.ts';
22
24
  import type { TuiCommand, TuiRecentSession, TuiService, TuiSessionControls, TuiSessionStats, TuiSubmission, TuiInputImage } from '../definition.ts';
23
25
  import type { ContextDiagnostics } from './context-diagnostics.ts';
24
26
  /** Projection values consumed as one consistent snapshot when the units exist. */
@@ -30,6 +32,7 @@ export interface TuiStatsProjection {
30
32
  plan?: PlanProjection;
31
33
  permissions?: PermissionSelection;
32
34
  goal?: GoalProjection | null;
35
+ userQuestions?: UserQuestionProjectionView;
33
36
  /** Last logged sandbox-mode override, or null before one; outranks the preset while set. */
34
37
  sandboxMode?: 'read-only' | 'workspace-write' | 'danger-full-access' | null;
35
38
  }
@@ -53,21 +56,6 @@ export declare function sessionStats(events: readonly SessionEvent[], contextWin
53
56
  * fallback fold when projections are absent.
54
57
  */
55
58
  export declare function shouldRefreshSessionInfoAfter(event: SessionEvent): boolean;
56
- /** One direct human turn that can become a safe fork boundary. */
57
- export interface ConversationTurn {
58
- /** Harness turn number shown to the user. */
59
- turn: number;
60
- /** Index of the direct user/message event in the immutable log. */
61
- messageIndex: number;
62
- /** Balanced seed boundary immediately before this turn starts. */
63
- branchIndex: number;
64
- /** Single-line selector preview. */
65
- preview: string;
66
- /** Number of image blocks in the selected message. */
67
- imageCount: number;
68
- }
69
- /** Find direct human messages whose preceding log prefix is safe to seed into a fork. */
70
- export declare function conversationTurns(events: readonly SessionEvent[]): ConversationTurn[];
71
59
  /** Title and latest-human-message preview for durable session discovery. */
72
60
  export declare function recentSessionContent(events: readonly SessionEvent[]): {
73
61
  title: string;
@@ -86,6 +74,9 @@ export declare function createSubmissionMessage(submission: TuiSubmission, attac
86
74
  content: ({
87
75
  type: "image";
88
76
  attachment: ImageAttachmentRef;
77
+ } | {
78
+ type: "file";
79
+ attachment: import("@deepseek-ai/dsh-attachment").FileAttachmentRef;
89
80
  } | {
90
81
  type: "text";
91
82
  text: string;
@@ -113,12 +104,15 @@ export declare class SessionRuntime {
113
104
  start(): Promise<void>;
114
105
  /** Submit one human composer value; active turns retain it as a later follow-up. */
115
106
  send(input: string | TuiSubmission, agent?: Agent): Promise<void>;
107
+ openQueue(signal: AbortSignal, agent?: Agent): Promise<void>;
116
108
  /** Remove and rehydrate the newest durable human follow-up for queue browsing. */
117
109
  editLatestFollowup(agent?: Agent): Promise<TuiSubmission | undefined>;
118
110
  /** Execute a plugin-owned slash command, falling back to user-invocable skills. */
119
111
  execute(line: string, signal: AbortSignal, images?: readonly TuiInputImage[]): Promise<boolean>;
120
112
  /** Fail when a command invocation targets a stale or background Agent. */
121
113
  assertActive(agent: Agent): void;
114
+ /** Shared local metadata document for Session Library and Tree actions. */
115
+ get sessionLibraryPath(): string;
122
116
  /** Immutable recent-session view used by the resume command. */
123
117
  get recentSessions(): readonly TuiRecentSession[];
124
118
  /** Current model selection for the active Agent. */
@@ -133,8 +127,8 @@ export declare class SessionRuntime {
133
127
  resumeSession(agent: Agent, id: string, signal: AbortSignal): Promise<void>;
134
128
  /** Rename a live or durable top-level session through the public log-backed title service. */
135
129
  renameSession(agent: Agent, id: string, title: string, signal: AbortSignal): Promise<void>;
136
- /** Fork before a selected human turn and restore that message as an editable draft. */
137
- rewindToTurn(signal: AbortSignal): Promise<void>;
130
+ /** Browse conversation forks and preview turns before editing or continuing a branch. */
131
+ openSessionTree(signal: AbortSignal): Promise<void>;
138
132
  /** Whole-session figures for the active Agent. */
139
133
  stats(agent?: Agent): TuiSessionStats;
140
134
  /** Current client-visible context projections, read as one consistent snapshot. */
@@ -152,4 +146,3 @@ export declare class SessionRuntime {
152
146
  refreshRecent(): Promise<void>;
153
147
  dispose(): Promise<void>;
154
148
  }
155
- export {};
@@ -7,6 +7,9 @@
7
7
  * @module @agi-fans/dsh-tui/session-controller
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
+ import { steerAgent } from "./steering.js";
11
+ import { openMessageQueue } from "./message-queue.js";
12
+ import { WorkflowProjection } from "./workflow-projection.js";
10
13
  import { join } from 'node:path';
11
14
  import { installModelSelection, } from '@deepseek-ai/dsh-agent';
12
15
  import { createUserMessage, } from '@deepseek-ai/dsh-llm';
@@ -18,9 +21,12 @@ import { queueHostSubagentPrompt } from '@deepseek-ai/dsh-subagent/internal';
18
21
  import { firstVisibleStreamTime } from "../views/stream-time.js";
19
22
  import { LiveAttemptTracker } from "./live-attempt-tracker.js";
20
23
  import { blocksText } from "./content-text.js";
24
+ import { conversationTurns } from "./conversation-turns.js";
25
+ import { buildSessionTree, markedSessionTree, sessionTreeFamily } from "./session-tree.js";
26
+ export { conversationTurns } from "./conversation-turns.js";
21
27
  import { jobNoticeFor } from "./job-notice.js";
22
28
  import { descendantDepth, isSteerableSubagent } from "./subagent-roster.js";
23
- import { readRecentRows, writeRecentRows } from "./session-library.js";
29
+ import { readRecentRows, writeRecentRows, readSessionLibrary, sessionLibraryPath, setSessionLabel } from "./session-library.js";
24
30
  import { readJsonFile, writeJsonAtomic } from "./json-file.js";
25
31
  import { SubagentTracker } from "./subagent-tracker.js";
26
32
  import { commandPermission } from "../commands/permission.js";
@@ -55,6 +61,9 @@ export function sessionControls(projection) {
55
61
  ? (projection?.permissions === undefined ? {} : { permission: projection.permissions.currentValue })
56
62
  : { permission: override }),
57
63
  ...goalControl(projection?.goal),
64
+ ...(projection?.userQuestions === undefined ? {} : {
65
+ pendingQuestions: projection.userQuestions.active.filter(question => question.state === 'continued').length,
66
+ }),
58
67
  };
59
68
  }
60
69
  /** A completed goal renders nothing, mirroring the projection's own visibility rule. */
@@ -252,37 +261,6 @@ function humanMessageText(event) {
252
261
  const text = blocksText(event.data.content, { collapse: true });
253
262
  return text === '' ? undefined : text;
254
263
  }
255
- /** Find direct human messages whose preceding log prefix is safe to seed into a fork. */
256
- export function conversationTurns(events) {
257
- const turns = [];
258
- let open;
259
- for (let index = 0; index < events.length; index += 1) {
260
- const event = events[index];
261
- if (event.type === 'turn/start') {
262
- open = { turn: event.data.turn, branchIndex: index };
263
- continue;
264
- }
265
- if (event.type === 'turn/end') {
266
- if (open?.turn === event.data.turn)
267
- open = undefined;
268
- continue;
269
- }
270
- if (open === undefined || event.type !== 'user/message' || event.data.source.kind !== 'user')
271
- continue;
272
- const text = blocksText(event.data.content, { collapse: true });
273
- const imageCount = event.data.content.filter(block => block.type === 'image').length;
274
- if (text === '' && imageCount === 0)
275
- continue;
276
- turns.push({
277
- turn: open.turn,
278
- messageIndex: index,
279
- branchIndex: open.branchIndex,
280
- preview: text === '' ? (imageCount === 1 ? 'Image' : `${imageCount} images`) : text,
281
- imageCount,
282
- });
283
- }
284
- return turns;
285
- }
286
264
  /** Title and latest-human-message preview for durable session discovery. */
287
265
  export function recentSessionContent(events) {
288
266
  const generatedTitle = explicitSessionTitle(events);
@@ -342,6 +320,7 @@ export async function createSubmissionMessage(submission, attachments) {
342
320
  const content = [
343
321
  ...(submission.text === '' ? [] : [{ type: 'text', text: submission.text }]),
344
322
  ...refs.map(attachment => ({ type: 'image', attachment })),
323
+ ...(submission.files ?? []).map(attachment => ({ type: 'file', attachment })),
345
324
  ];
346
325
  if (content.length === 0)
347
326
  throw new Error('Cannot submit an empty message.');
@@ -377,24 +356,29 @@ export async function restoreSubmissionMessage(message, attachments) {
377
356
  height: stored.ref.height,
378
357
  };
379
358
  }));
380
- return { text, images };
359
+ const files = message.content.filter((block) => block.type === 'file').map(block => block.attachment);
360
+ return { text, images, ...(files.length === 0 ? {} : { files }) };
381
361
  }
382
362
  /** Own one switchable top-level Agent and project it onto a TuiService. */
383
363
  export class SessionRuntime {
364
+ #workflows = new WorkflowProjection();
384
365
  #ctx;
385
366
  #tui;
386
367
  #active;
387
368
  #recent = [];
369
+ #archivedSessions = new Set();
388
370
  #skillCommands = [];
389
371
  #started = false;
390
372
  #retired = [];
391
373
  // Weak: deduplication must not retain disposed session graphs.
392
374
  #releasedHandles = new WeakSet();
393
375
  #disposed = false;
376
+ #queueAbort;
394
377
  #off = [];
395
378
  #tracker;
396
379
  #streamAttempts = new LiveAttemptTracker();
397
380
  #recentCache = new Map();
381
+ #libraryPath;
398
382
  #recentRowsPath;
399
383
  #upgradeMarkerPath;
400
384
  #recentRowsLoaded = false;
@@ -406,6 +390,7 @@ export class SessionRuntime {
406
390
  constructor(ctx, tui, options = {}) {
407
391
  this.#ctx = ctx;
408
392
  this.#tui = tui;
393
+ this.#libraryPath = sessionLibraryPath(options.stateDir);
409
394
  this.#recentRowsPath = options.stateDir === undefined ? undefined : join(options.stateDir, 'recent-sessions.json');
410
395
  this.#upgradeMarkerPath = options.stateDir === undefined ? undefined : join(options.stateDir, 'sessions-upgraded.json');
411
396
  this.#tracker = new SubagentTracker({
@@ -418,12 +403,14 @@ export class SessionRuntime {
418
403
  sessions: () => this.#ctx.get('sessions')?.list() ?? [],
419
404
  listChildren: rootId => this.#ctx.get('subagents')?.listChildren(SessionId(rootId)),
420
405
  publish: (roster) => {
421
- this.#tui.setSubagents(roster);
406
+ this.#tui.setSubagents(this.#workflows.roster(roster));
422
407
  if (this.#inspectedId === undefined)
423
408
  return;
424
409
  this.#tui.setInspectedSubagent(this.#inspectView(this.#inspectedId));
425
410
  },
426
411
  });
412
+ this.#off.push(tui.setSteerHandler(text => { steerAgent(this.agent, text); }));
413
+ this.#off.push(tui.setQueueHandler(signal => this.openQueue(signal)));
427
414
  this.#off.push(tui.onInspectSubagent(id => { void this.#inspectSubagent(id); }));
428
415
  this.#off.push(tui.onInspectClose(() => { this.#closeInspect(); }));
429
416
  this.#off.push(tui.onInspectSubmit(submission => { void this.#steerInspected(submission); }));
@@ -460,14 +447,21 @@ export class SessionRuntime {
460
447
  return;
461
448
  const owner = event.job.owner === undefined ? undefined : this.#ctx.get('agents')?.get(event.job.owner);
462
449
  const notice = jobNoticeFor(event.job, owner, this.#active?.handle.agent);
463
- if (notice !== undefined)
464
- tui.notice(notice);
450
+ if (notice !== undefined) {
451
+ const job = event.job;
452
+ tui.notice(notice, {
453
+ process: true, processSource: `job:${job.id}`,
454
+ ...(/^(bash|pwsh)$/u.test(job.kind) && (job.status === 'completed' || job.status === 'failed' || job.status === 'killed')
455
+ ? { job: { id: job.id, label: job.label, status: job.status, startedAt: job.startedAt, ...(job.detail === undefined ? {} : { detail: job.detail }) } }
456
+ : {}),
457
+ ...(job.status === 'failed' || job.status === 'killed' ? { level: 'error' } : {}),
458
+ });
459
+ }
465
460
  }));
466
461
  }
467
462
  this.#off.push(ctx.on('agent/status', (payload) => {
468
463
  if (payload.agent === this.#active?.handle.agent) {
469
- if (this.#inspectedId === undefined)
470
- tui.setStatus(payload.status);
464
+ tui.setStatus(payload.status, { root: true });
471
465
  return;
472
466
  }
473
467
  if (payload.agent.id === this.#inspectedId)
@@ -491,9 +485,13 @@ export class SessionRuntime {
491
485
  return;
492
486
  }
493
487
  if (session === active.handle.agent.session) {
488
+ if (this.#workflows.apply(event))
489
+ tui.setSubagents(this.#workflows.roster(this.#tracker.snapshot()));
494
490
  if (this.#inspectedId === undefined) {
495
491
  tui.event(event, ctx.get('tuiToolPresentation')?.event(active.handle.agent, event));
496
492
  }
493
+ if (event.type === 'workspace/changes')
494
+ this.#serveWorkspaceSummary(ctx, tui, session, event);
497
495
  if (event.type === 'subagent/catalog')
498
496
  this.#tracker.observeCatalog(session, [event]);
499
497
  if (shouldRefreshSessionInfoAfter(event))
@@ -543,7 +541,7 @@ export class SessionRuntime {
543
541
  if (session !== this.#active?.handle.agent.session)
544
542
  return;
545
543
  if (key === 'sessionStats' || key === 'tokenUsage' || key === 'contextPressure' || key === 'contextBreakdown'
546
- || key === 'plan' || key === 'permissions' || key === 'goal')
544
+ || key === 'plan' || key === 'permissions' || key === 'goal' || key === 'userQuestions')
547
545
  this.#pushSessionInfo();
548
546
  }));
549
547
  }
@@ -579,7 +577,7 @@ export class SessionRuntime {
579
577
  if (defaults === undefined)
580
578
  throw new Error('agent default model is unavailable');
581
579
  await this.refreshRecent();
582
- await this.#activate(await this.#create(defaults));
580
+ await this.#activate(await this.#create(defaults), 'initial');
583
581
  }
584
582
  /** Submit one human composer value; active turns retain it as a later follow-up. */
585
583
  async send(input, agent = this.#requiredAgent()) {
@@ -589,24 +587,37 @@ export class SessionRuntime {
589
587
  this.assertActive(agent);
590
588
  agent.followup(message);
591
589
  }
590
+ async openQueue(signal, agent = this.#requiredAgent()) {
591
+ this.assertActive(agent);
592
+ if (this.#inspectedId !== undefined)
593
+ throw new Error('Return to the parent session to manage queued messages.');
594
+ if (this.#queueAbort !== undefined)
595
+ throw new Error('Message Queue is already open.');
596
+ const abort = new AbortController();
597
+ this.#queueAbort = abort;
598
+ const onAbort = () => { abort.abort(signal.reason); };
599
+ signal.addEventListener('abort', onAbort, { once: true });
600
+ if (signal.aborted)
601
+ onAbort();
602
+ try {
603
+ await openMessageQueue({ agent, tui: this.#tui, signal: abort.signal, assertActive: () => { this.assertActive(agent); },
604
+ restore: message => restoreSubmissionMessage(message, this.#ctx.get('attachments')) });
605
+ }
606
+ finally {
607
+ signal.removeEventListener('abort', onAbort);
608
+ if (this.#queueAbort === abort)
609
+ this.#queueAbort = undefined;
610
+ }
611
+ }
592
612
  /** Remove and rehydrate the newest durable human follow-up for queue browsing. */
593
613
  async editLatestFollowup(agent = this.#requiredAgent()) {
594
614
  this.assertActive(agent);
595
615
  const message = agent.inbox.nextTurn.findLast(candidate => candidate.source.kind === 'user');
596
- if (message === undefined || !agent.inbox.remove(message.id))
616
+ if (message === undefined)
597
617
  return undefined;
598
- try {
599
- const submission = await restoreSubmissionMessage(message, this.#ctx.get('attachments'));
600
- this.assertActive(agent);
601
- return submission;
602
- }
603
- catch (error) {
604
- try {
605
- agent.inbox.append('next-turn', message);
606
- }
607
- catch { /* agent retired or message was restored elsewhere */ }
608
- throw error;
609
- }
618
+ const submission = await restoreSubmissionMessage(message, this.#ctx.get('attachments'));
619
+ this.assertActive(agent);
620
+ return agent.inbox.remove(message.id) ? { ...submission, literal: true } : undefined;
610
621
  }
611
622
  /** Execute a plugin-owned slash command, falling back to user-invocable skills. */
612
623
  async execute(line, signal, images = []) {
@@ -640,9 +651,9 @@ export class SessionRuntime {
640
651
  this.#tui.restoreInput({ text: line, images });
641
652
  if (result.text !== undefined) {
642
653
  if (result.kind === 'error')
643
- this.#tui.notice(result.text, { level: 'error' });
654
+ this.#tui.notice(result.text, { level: 'error', focus: true });
644
655
  else
645
- this.#tui.commandOutput(parsed.name, result.text);
656
+ this.#tui.commandOutput(parsed.name, result.text, { focus: true });
646
657
  }
647
658
  return true;
648
659
  }
@@ -651,6 +662,8 @@ export class SessionRuntime {
651
662
  if (agent !== this.#requiredAgent())
652
663
  throw new Error('the command does not target the active omdsh session');
653
664
  }
665
+ /** Shared local metadata document for Session Library and Tree actions. */
666
+ get sessionLibraryPath() { return this.#libraryPath; }
654
667
  /** Immutable recent-session view used by the resume command. */
655
668
  get recentSessions() {
656
669
  return this.#recent;
@@ -681,7 +694,7 @@ export class SessionRuntime {
681
694
  /** Start a new top-level session with the current model selection. */
682
695
  async newSession(agent) {
683
696
  this.assertActive(agent);
684
- await this.#activate(await this.#create(this.selection(agent)));
697
+ await this.#activate(await this.#create(this.selection(agent)), 'new');
685
698
  await this.refreshRecent();
686
699
  }
687
700
  /** Replace the active top-level session with one durable session. */
@@ -734,44 +747,143 @@ export class SessionRuntime {
734
747
  }
735
748
  await this.refreshRecent();
736
749
  }
737
- /** Fork before a selected human turn and restore that message as an editable draft. */
738
- async rewindToTurn(signal) {
750
+ /** Browse conversation forks and preview turns before editing or continuing a branch. */
751
+ async openSessionTree(signal) {
739
752
  const agent = this.#requiredAgent();
740
753
  if (agent.status !== 'idle')
741
754
  return;
742
- const events = agent.session.snapshotEvents();
743
- const turns = conversationTurns(events);
744
- if (turns.length === 0) {
745
- this.#tui.notice('No conversation turns are available to rewind.');
746
- return;
755
+ const persistence = this.#ctx.get('sessionPersistence');
756
+ const snapshots = persistence === undefined ? [] : await persistence.list({ signal });
757
+ const candidates = snapshots.filter(item => item.header.origin !== 'subagent'
758
+ && item.header.cwd === agent.session.header.cwd).map(item => ({
759
+ id: String(item.header.id),
760
+ ...(item.header.isSeeded && item.header.parentSession !== undefined ? { parentId: String(item.header.parentSession) } : {}),
761
+ }));
762
+ const current = { id: String(agent.id),
763
+ ...(agent.session.header.isSeeded && agent.session.header.parentSession !== undefined
764
+ ? { parentId: String(agent.session.header.parentSession) } : {}) };
765
+ const headers = [...candidates.filter(item => item.id !== current.id), current];
766
+ const family = sessionTreeFamily(headers, current.id);
767
+ const branches = [];
768
+ for (const header of headers) {
769
+ if (!family.has(header.id))
770
+ continue;
771
+ signal.throwIfAborted();
772
+ if (header.id === current.id) {
773
+ const events = agent.session.snapshotEvents();
774
+ const title = explicitSessionTitle(events.slice(header.parentId === undefined ? 0 : Number(agent.session.inheritedEventCount)));
775
+ branches.push({ ...header, events, inheritedEventCount: Number(agent.session.inheritedEventCount), ...(title === undefined ? {} : { title }),
776
+ ...(agent.session.header.agentPreset === undefined ? {} : { agentPreset: agent.session.header.agentPreset }) });
777
+ }
778
+ else if (persistence !== undefined) {
779
+ const log = await readColdSessionLog(persistence, SessionId(header.id), signal);
780
+ const title = explicitSessionTitle(log.events.slice(header.parentId === undefined ? 0 : Number(log.inheritedEventCount)));
781
+ branches.push({ ...header, events: log.events, inheritedEventCount: Number(log.inheritedEventCount), ...(title === undefined ? {} : { title }),
782
+ ...(log.header.agentPreset === undefined ? {} : { agentPreset: log.header.agentPreset }) });
783
+ }
747
784
  }
748
- const newestFirst = [...turns].reverse();
749
- const answer = await this.#tui.prompt({
750
- title: 'Rewind Conversation',
751
- question: '',
752
- detail: 'original session preserved',
753
- options: newestFirst.map(turn => ({
754
- label: `Turn ${turn.turn}`,
755
- value: String(turn.messageIndex),
756
- preview: turn.preview,
757
- description: turn.imageCount === 0
758
- ? 'Branch before this message'
759
- : `Branch before this message · ${turn.imageCount} ${turn.imageCount === 1 ? 'image' : 'images'}`,
760
- })),
761
- initialValue: String(newestFirst[0]?.messageIndex),
762
- presentation: 'fullscreen-list',
763
- optionLayout: 'spacious',
764
- filterable: true,
765
- allowCustom: false,
766
- submitLabel: 'rewind',
767
- signal,
768
- });
769
- if (answer === null)
770
- return;
771
785
  this.assertActive(agent);
772
786
  if (agent.status !== 'idle')
773
- throw new Error('Finish or interrupt the active turn before rewinding.');
774
- const selected = turns.find(turn => String(turn.messageIndex) === answer);
787
+ return;
788
+ const nodes = buildSessionTree(branches, current.id);
789
+ if (!nodes.some(node => node.kind === 'turn')) {
790
+ this.#tui.notice('No conversation turns are available in the Session Tree.');
791
+ return;
792
+ }
793
+ const target = nodes.findLast(node => node.sessionId === current.id && node.kind === 'turn')
794
+ ?? nodes.find(node => node.sessionId === current.id);
795
+ let markedOnly = false;
796
+ let selectedId = target?.id;
797
+ let answer;
798
+ while (true) {
799
+ const library = readSessionLibrary(this.#libraryPath);
800
+ const visible = markedOnly ? markedSessionTree(nodes, library.labels) : nodes;
801
+ answer = await this.#tui.prompt({
802
+ title: markedOnly ? 'Session Tree · Marked' : 'Session Tree', question: '', notify: false,
803
+ emptyText: 'No marked nodes. Alt+B shows the full tree.',
804
+ options: visible.map(node => {
805
+ const label = library.labels[node.id];
806
+ return {
807
+ label: label === undefined ? node.label : `◆ ${label} · ${node.label}`,
808
+ value: node.id, ...(node.parentId === undefined ? {} : { parentValue: node.parentId }),
809
+ preview: node.preview, description: node.description,
810
+ submitLabel: node.kind === 'turn' ? 'edit from here' : 'continue branch',
811
+ ...(node.current ? { badge: { label: 'Current', tone: 'success' } } : {}),
812
+ };
813
+ }),
814
+ actions: [
815
+ { key: 'Alt+Enter', label: 'continue branch', valuePrefix: 'continue:' },
816
+ { key: 'Alt+L', label: 'label', valuePrefix: 'label:' },
817
+ { key: 'Alt+U', label: 'clear label', valuePrefix: 'unlabel:' },
818
+ { key: 'Alt+B', label: markedOnly ? 'full tree' : 'marked nodes', valuePrefix: 'marked:', scope: 'list' },
819
+ ],
820
+ ...(selectedId === undefined ? {} : { initialValue: selectedId }), presentation: 'fullscreen-tree', filterable: true, allowCustom: false, signal,
821
+ });
822
+ if (answer === null)
823
+ return;
824
+ this.assertActive(agent);
825
+ signal.throwIfAborted();
826
+ if (agent.status !== 'idle')
827
+ throw new Error('Finish or interrupt the active turn before navigating the Session Tree.');
828
+ if (answer === 'marked:') {
829
+ markedOnly = !markedOnly;
830
+ continue;
831
+ }
832
+ const clear = answer.startsWith('unlabel:');
833
+ if (clear || answer.startsWith('label:')) {
834
+ const id = answer.slice(clear ? 'unlabel:'.length : 'label:'.length);
835
+ const node = visible.find(item => item.id === id);
836
+ if (node === undefined)
837
+ continue;
838
+ selectedId = id;
839
+ if (clear) {
840
+ setSessionLabel(this.#libraryPath, id, '');
841
+ continue;
842
+ }
843
+ let draft = library.labels[id] ?? '';
844
+ let error;
845
+ while (true) {
846
+ const label = await this.#tui.prompt({
847
+ title: 'Label Conversation Node', question: `Label for ${node.label}`,
848
+ initialInput: draft, detail: error ?? 'Up to 120 characters. Alt+U in the tree clears a label.',
849
+ allowCustom: true, notify: false, signal,
850
+ });
851
+ if (label === null)
852
+ break;
853
+ this.assertActive(agent);
854
+ signal.throwIfAborted();
855
+ try {
856
+ setSessionLabel(this.#libraryPath, id, label);
857
+ break;
858
+ }
859
+ catch (failure) {
860
+ error = failure instanceof Error ? failure.message : String(failure);
861
+ draft = label;
862
+ }
863
+ }
864
+ continue;
865
+ }
866
+ break;
867
+ }
868
+ const continuing = answer.startsWith('continue:');
869
+ const node = nodes.find(item => item.id === (continuing ? answer.slice('continue:'.length) : answer));
870
+ if (node === undefined)
871
+ throw new Error('The selected conversation node is no longer available.');
872
+ if (continuing || node.kind === 'branch') {
873
+ if (node.sessionId === current.id) {
874
+ this.#tui.notice('This branch is already active.');
875
+ return;
876
+ }
877
+ await this.resumeSession(agent, node.sessionId, signal);
878
+ this.#tui.notice('Continued the selected conversation branch.');
879
+ return;
880
+ }
881
+ // Draft preparation forks the selected branch; browsing itself never activates it.
882
+ const source = branches.find(branch => branch.id === node.sessionId);
883
+ if (source === undefined)
884
+ throw new Error('The selected conversation branch is no longer available.');
885
+ const events = source.events;
886
+ const selected = conversationTurns(events).find(turn => turn.messageIndex === node.messageIndex);
775
887
  if (selected === undefined)
776
888
  throw new Error('The selected conversation turn is no longer available.');
777
889
  const message = events[selected.messageIndex];
@@ -808,9 +920,9 @@ export class SessionRuntime {
808
920
  seed: events.slice(0, selected.branchIndex),
809
921
  meta: {
810
922
  ...(agent.session.header.cwd === undefined ? {} : { cwd: agent.session.header.cwd }),
811
- parentSession: agent.id,
923
+ parentSession: SessionId(source.id),
812
924
  isSeeded: true,
813
- agentPreset: active.agentPreset,
925
+ agentPreset: source.agentPreset ?? active.agentPreset,
814
926
  },
815
927
  inheritedEventCount: SessionLogOffset(selected.branchIndex),
816
928
  agentOptions: { provider: selection.provider, model: selection.model },
@@ -836,8 +948,9 @@ export class SessionRuntime {
836
948
  reasoningEffort: undefined,
837
949
  agentPreset: configuration.agentPreset,
838
950
  });
839
- this.#tui.restoreInput({ text, images });
840
- this.#tui.notice(`Rewound to before turn ${selected.turn}. The original session remains available in /resume.`);
951
+ const files = message.data.content.filter((block) => block.type === 'file').map(block => block.attachment);
952
+ this.#tui.restoreInput({ text, images, ...(files.length === 0 ? {} : { files }) });
953
+ this.#tui.notice(`Editing from before turn ${selected.turn}. The original branch remains available in the Session Tree.`);
841
954
  await this.#disposeRetired();
842
955
  await this.refreshRecent();
843
956
  }
@@ -901,6 +1014,34 @@ export class SessionRuntime {
901
1014
  this.#pushSessionInfo();
902
1015
  return outcome;
903
1016
  }
1017
+ /**
1018
+ * Resolve and attach the changed-file summary a `workspace/changes` event
1019
+ * announces.
1020
+ *
1021
+ * The Harness appends the event and only then files the summary under its
1022
+ * sequence, so a listener running inside that append always reads
1023
+ * `undefined`. The lookup is therefore deferred by one microtask, which runs
1024
+ * after the recorder's own synchronous store, and enriches the turn-only
1025
+ * block the transcript already rendered. A session without the plugin, or a
1026
+ * recorder that has already been disposed, simply keeps the fallback.
1027
+ *
1028
+ * @param ctx - host context carrying the optional service.
1029
+ * @param tui - the mounted presentation service.
1030
+ * @param session - the Session that appended the event.
1031
+ * @param event - the `workspace/changes` event.
1032
+ */
1033
+ #serveWorkspaceSummary(ctx, tui, session, event) {
1034
+ const service = ctx.get('workspaceChanges');
1035
+ if (service === undefined)
1036
+ return;
1037
+ const { id } = session;
1038
+ const { seq, data } = event;
1039
+ queueMicrotask(() => {
1040
+ const summary = service.summary(id, seq);
1041
+ if (summary !== undefined)
1042
+ tui.setWorkspaceSummary(data.turn, summary);
1043
+ });
1044
+ }
904
1045
  /**
905
1046
  * Publish a current-format generation for every stored session an earlier
906
1047
  * release wrote. A read open never upgrades a log, so without this pass
@@ -966,6 +1107,7 @@ export class SessionRuntime {
966
1107
  return run;
967
1108
  }
968
1109
  async #refreshRecentNow() {
1110
+ this.#archivedSessions = new Set(readSessionLibrary(this.#libraryPath).archived);
969
1111
  const persistence = this.#ctx.get('sessionPersistence');
970
1112
  if (persistence === undefined) {
971
1113
  this.#recent = [];
@@ -1030,6 +1172,7 @@ export class SessionRuntime {
1030
1172
  this.#pushSessionInfo();
1031
1173
  }
1032
1174
  async dispose() {
1175
+ this.#queueAbort?.abort(new Error('session runtime disposed'));
1033
1176
  if (this.#disposed)
1034
1177
  return;
1035
1178
  this.#disposed = true;
@@ -1106,7 +1249,7 @@ export class SessionRuntime {
1106
1249
  return undefined;
1107
1250
  }
1108
1251
  }
1109
- async #activate(next) {
1252
+ async #activate(next, reason = 'open') {
1110
1253
  // Reject a late create before it publishes UI on a disposed tree.
1111
1254
  if (this.#disposed) {
1112
1255
  this.#releaseHandle(next.handle);
@@ -1114,10 +1257,11 @@ export class SessionRuntime {
1114
1257
  }
1115
1258
  const previous = this.#active;
1116
1259
  const epoch = ++this.#activationEpoch;
1260
+ this.#queueAbort?.abort(new Error('active session changed'));
1117
1261
  this.#active = next;
1118
1262
  void this.#claimWorkspace(next);
1119
1263
  try {
1120
- this.#presentAgent(next);
1264
+ this.#presentAgent(next, reason);
1121
1265
  const selected = this.selection(next.handle.agent);
1122
1266
  const info = await this.#resolveModelInfo(selected);
1123
1267
  if (this.#disposed || this.#activationEpoch !== epoch) {
@@ -1178,16 +1322,18 @@ export class SessionRuntime {
1178
1322
  this.#releaseHandle(previous.handle);
1179
1323
  }
1180
1324
  /** Show one active session on the terminal: transcript, tools, controls, model. */
1181
- #presentAgent(active) {
1325
+ #presentAgent(active, reason = 'open') {
1326
+ this.#workflows.reset(active.handle.agent.session.snapshotEvents());
1182
1327
  const agent = active.handle.agent;
1183
1328
  this.#inspectedId = undefined;
1184
1329
  this.#tui.setInspectedSubagent(undefined);
1185
1330
  this.#tui.setStatus(agent.status);
1186
1331
  this.#tracker.sync();
1332
+ this.#tui.setSubagents(this.#workflows.roster(this.#tracker.snapshot()));
1187
1333
  // Seed welcome metadata before replaceSession commits the startup header to
1188
1334
  // native scrollback; later updates cannot rewrite that frozen first frame.
1189
1335
  this.#pushSessionInfo();
1190
- this.#replaceTranscript(agent);
1336
+ this.#replaceTranscript(agent, reason);
1191
1337
  this.#pushTools();
1192
1338
  this.#pushCommands();
1193
1339
  const selected = this.selection(agent);
@@ -1304,15 +1450,15 @@ export class SessionRuntime {
1304
1450
  this.#tui.setSession({
1305
1451
  id: agent.id,
1306
1452
  ...(title === undefined ? {} : { title }),
1307
- recent: this.#recent.filter(row => row.id !== agent.id),
1453
+ recent: this.#recent.filter(row => row.id !== agent.id && !this.#archivedSessions.has(row.id)),
1308
1454
  stats: sessionStats(events, active.contextWindow, projection),
1309
1455
  controls: this.#sessionControls(active, projection),
1310
1456
  });
1311
1457
  }
1312
- #replaceTranscript(agent) {
1458
+ #replaceTranscript(agent, reason = 'refresh') {
1313
1459
  const events = agent.session.snapshotEvents();
1314
1460
  this.#tracker.observeCatalog(agent.session, events);
1315
- this.#tui.replaceSession(events, this.#ctx.get('tuiToolPresentation')?.session(agent, events), agent.status);
1461
+ this.#tui.replaceSession(events, this.#ctx.get('tuiToolPresentation')?.session(agent, events), agent.status, reason);
1316
1462
  this.#replayLivePrefix(agent.session.id);
1317
1463
  }
1318
1464
  #replaceVisibleTranscript() {
@@ -1367,7 +1513,7 @@ export class SessionRuntime {
1367
1513
  return;
1368
1514
  this.#inspectedId = id;
1369
1515
  const child = this.#ctx.get('agents')?.get(SessionId(id));
1370
- this.#tui.replaceSession(events, child === undefined ? undefined : this.#ctx.get('tuiToolPresentation')?.session(child, events), child?.status ?? 'idle');
1516
+ this.#tui.replaceSession(events, child === undefined ? undefined : this.#ctx.get('tuiToolPresentation')?.session(child, events), child?.status ?? 'idle', 'refresh');
1371
1517
  this.#replayLivePrefix(id);
1372
1518
  this.#tui.setInspectedSubagent(this.#inspectView(id, child?.status === 'running' ? 'running' : 'waiting'));
1373
1519
  this.#tui.setStatus(child?.status ?? 'idle');