@antst/dashi 0.1.0-alpha.8 → 0.1.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.
package/lib/renderer.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Editor, TuiAltScreen, TuiMainScreen, stripTerminalSequences, truncateToWidth, wrapTextWithAnsi, } from '@earendil-works/pi-tui';
1
+ import { Editor, Markdown, TuiAltScreen, TuiMainScreen, stripTerminalSequences, truncateToWidth, wrapTextWithAnsi, } from '@earendil-works/pi-tui';
2
2
  import { caretOffset, installInput } from './input.js';
3
3
  import { hasCompletionTrigger } from './completion-trigger.js';
4
4
  import { createProcessTerminal } from './terminal-guard.js';
@@ -14,6 +14,23 @@ const OVERSCAN_CELLS = 8;
14
14
  const THEME_GENERATION = 1;
15
15
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
16
16
  const SPINNER_INTERVAL_MS = 80;
17
+ const MASK_TOKEN = new RegExp(String.raw `\u001B(?:\[[0-?]*[ -/]*[@-~]|[_P^X][^\u0007]*(?:\u0007|\u001B\\))|[^\s]`, 'gu');
18
+ const markdownTheme = {
19
+ heading: text => `\u001B[36m${text}${RESET}`,
20
+ link: text => `\u001B[36m${text}${RESET}`,
21
+ linkUrl: text => `${DIM}${text}${RESET}`,
22
+ code: text => `${YELLOW}${text}${RESET}`,
23
+ codeBlock: text => `${GREEN}${text}${RESET}`,
24
+ codeBlockBorder: text => `${DIM}${text}${RESET}`,
25
+ quote: text => `${DIM}${text}${RESET}`,
26
+ quoteBorder: text => `${DIM}${text}${RESET}`,
27
+ hr: text => `${DIM}${text}${RESET}`,
28
+ listBullet: text => `\u001B[36m${text}${RESET}`,
29
+ bold: text => `\u001B[1m${text}\u001B[22m`,
30
+ italic: text => `\u001B[3m${text}\u001B[23m`,
31
+ strikethrough: text => `\u001B[9m${text}\u001B[29m`,
32
+ underline: text => `\u001B[4m${text}\u001B[24m`,
33
+ };
17
34
  export function supportsOsc52(terminalType) {
18
35
  return terminalType?.toLocaleLowerCase() !== 'dumb';
19
36
  }
@@ -137,6 +154,22 @@ function plain(text, preserveNewlines = false) {
137
154
  function line(text, width) {
138
155
  return truncateToWidth(text, Math.max(1, width), '…');
139
156
  }
157
+ function windowRows(values, size, position, follow) {
158
+ if (values.length <= size)
159
+ return [...values];
160
+ if (size < 3)
161
+ return [values[Math.max(0, Math.min(values.length - 1, position))] ?? ''];
162
+ const start = Math.max(0, Math.min(values.length - Math.max(1, size - 1), follow ? position - Math.floor((size - 2) / 2) : position));
163
+ const above = start > 0;
164
+ let end = Math.min(values.length, start + size - Number(above));
165
+ if (end < values.length)
166
+ end--;
167
+ return [
168
+ ...(above ? [`${DIM}↑ ${String(start)} more${RESET}`] : []),
169
+ ...values.slice(start, end),
170
+ ...(end < values.length ? [`${DIM}↓ ${String(values.length - end)} more${RESET}`] : []),
171
+ ];
172
+ }
140
173
  function theme() {
141
174
  const identity = (text) => text;
142
175
  return {
@@ -159,15 +192,25 @@ export function createRenderer(options) {
159
192
  const osc52 = supportsOsc52(terminalType);
160
193
  const tui = options.inline
161
194
  ? new TuiMainScreen(terminal, true)
162
- : new TuiAltScreen(terminal, true, undefined, { mouse: true });
163
- const editor = new Editor(tui, theme(), { paddingX: 1 });
195
+ : new class extends TuiAltScreen {
196
+ isOverlayFocused() { return options.readState().overlay !== undefined || super.isOverlayFocused(); }
197
+ }(terminal, true, undefined, { mouse: true });
198
+ let editor = new Editor(tui, theme(), { paddingX: 1 });
164
199
  editor.disableSubmit = true;
165
200
  editor.onChange = text => {
201
+ const decision = options.readState().decisions[0];
166
202
  const caret = caretOffset(text, editor.getCursor());
167
203
  options.dispatch({
168
- type: 'composer-changed', text, caret, completion: hasCompletionTrigger(text, caret),
204
+ type: 'composer-changed', text: decision?.kind === 'question' && decision.questions[decision.index]?.secret === true ? editor.getExpandedText() : text, caret, completion: hasCompletionTrigger(text, caret),
169
205
  });
170
206
  };
207
+ const replaceEditor = () => {
208
+ const discarded = editor;
209
+ editor = new Editor(tui, theme(), { paddingX: 1 });
210
+ editor.disableSubmit = true;
211
+ editor.onChange = discarded.onChange;
212
+ tui.setFocus(editor);
213
+ };
171
214
  const lineCache = new Map();
172
215
  let animationTimer;
173
216
  const cellLines = (cell, width, toolMode, now) => {
@@ -175,7 +218,11 @@ export function createRenderer(options) {
175
218
  const cached = cell.pending === true ? undefined : lineCache.get(key);
176
219
  if (cached !== undefined)
177
220
  return cached;
178
- const rendered = wrapTextWithAnsi(cellText(cell, toolMode, accessible, now), Math.max(1, width - 2)).map(value => ` ${value}`);
221
+ const cellWidth = Math.max(1, width - 2);
222
+ const lines = !accessible && cell.kind === 'assistant' && cell.pending !== true
223
+ ? [`${BOLD_CYAN}Assistant${RESET}`, ...new Markdown(plain(cell.text, true), 0, 0, markdownTheme).render(cellWidth).map(value => value.trimEnd())]
224
+ : wrapTextWithAnsi(cellText(cell, toolMode, accessible, now), cellWidth);
225
+ const rendered = lines.map(value => ` ${value}`);
179
226
  if (cell.pending !== true) {
180
227
  lineCache.set(key, rendered);
181
228
  if (lineCache.size > 512)
@@ -203,14 +250,18 @@ export function createRenderer(options) {
203
250
  return [`${RED}Invalid empty question batch${RESET}`];
204
251
  const planReview = question.intent?.kind === 'plan-review';
205
252
  const heading = planReview ? 'Plan review' : question.header ?? `Question ${String(decision.index + 1)}/${String(decision.questions.length)}`;
253
+ const customAllowed = question.allowCustom !== false;
206
254
  const selected = question.options[decision.cursor]?.label ?? 'Other (type an answer)';
207
- const announced = announcement(plain(heading), question.options.length + 1, selected, decision.cursor);
255
+ const announced = announcement(plain(heading), question.options.length + Number(customAllowed), selected, decision.cursor);
208
256
  const prompt = wrapTextWithAnsi(`${BOLD_CYAN}${announced}${RESET}\n${plain(question.question, true)}`, width);
209
257
  const choiceRows = question.options.map((option, index) => optionLine(option.description === undefined ? option.label : `${option.label} — ${option.description}`, index + 1, decision.cursor === index, decision.selected.includes(option.label)));
210
- choiceRows.push(optionLine('Other (type an answer)', question.options.length + 1, decision.cursor === question.options.length, false));
258
+ if (customAllowed)
259
+ choiceRows.push(optionLine('Other (type an answer)', question.options.length + 1, decision.cursor === question.options.length, false));
211
260
  const detailLimit = planReview ? Math.max(3, rows - prompt.length - choiceRows.length - 5) : 3;
212
261
  const detail = question.detail === undefined ? [] : wrapTextWithAnsi(`${DIM}${plain(question.detail, true)}${RESET}`, width).slice(0, detailLimit);
213
- const custom = decision.cursor === question.options.length ? editor.render(width) : [];
262
+ const editorLines = decision.cursor === question.options.length ? editor.render(width) : [];
263
+ const custom = question.secret === true
264
+ ? editorLines.map((value, index) => index === 0 || index === editorLines.length - 1 ? value : value.replace(MASK_TOKEN, token => token.startsWith('\u001B') ? token : '•')) : editorLines;
214
265
  const rendered = [...prompt, ...detail, ...choiceRows, ...custom];
215
266
  return rows < 12 ? rendered.slice(0, Math.max(2, rows - 4)) : rendered;
216
267
  };
@@ -262,19 +313,19 @@ export function createRenderer(options) {
262
313
  result.push(`${DIM}↑↓ select · Enter details · Esc close${RESET}`);
263
314
  return result.slice(0, Math.max(2, rows - 3));
264
315
  };
316
+ const infoRows = (overlay, state, width) => overlay.lines.flatMap(value => wrapTextWithAnsi(plain(value, true), width)).concat(overlay.cells?.flatMap(cell => wrapTextWithAnsi(cellText(cell, state.toolMode, accessible), width)) ?? []);
265
317
  const overlayPanel = (overlay, state, width, rows) => {
266
318
  if (overlay.kind === 'history')
267
319
  return historyPanel(overlay, state, width, rows);
268
320
  if (overlay.kind === 'details')
269
321
  return detailsPanel(overlay, state, width, rows);
270
322
  if (overlay.kind === 'info') {
271
- const cards = overlay.cells?.flatMap(cell => wrapTextWithAnsi(cellText(cell, state.toolMode, accessible), width)) ?? [];
323
+ const body = infoRows(overlay, state, width);
272
324
  return [
273
325
  `${BOLD_CYAN}${announcement(plain(overlay.title), overlay.lines.length + (overlay.cells?.length ?? 0))}${RESET}`,
274
- ...overlay.lines.flatMap(value => wrapTextWithAnsi(plain(value, true), width)),
275
- ...cards,
276
- `${DIM}Enter or Esc to close${RESET}`,
277
- ].slice(0, Math.max(2, rows - 3));
326
+ ...windowRows(body, Math.max(1, rows - 5), overlay.scrollOffset ?? 0, false),
327
+ `${DIM}↑↓ scroll · PageUp/PageDown · Enter or Esc close${RESET}`,
328
+ ];
278
329
  }
279
330
  if (overlay.kind === 'confirm') {
280
331
  const choices = [overlay.acceptLabel, 'Cancel'];
@@ -290,18 +341,23 @@ export function createRenderer(options) {
290
341
  line(`${BOLD_CYAN}${announcement(plain(overlay.title), overlay.options.length, selected === undefined ? undefined : plain(selected.label), overlay.cursor)}${RESET}`, width),
291
342
  ...(overlay.notice === undefined ? [] : [`${DIM}${plain(overlay.notice)}${RESET}`]),
292
343
  ];
344
+ const body = [];
345
+ let selectedRow = 0;
293
346
  let group;
294
347
  for (const [index, option] of overlay.options.entries()) {
295
348
  if (option.group !== undefined && option.group !== group) {
296
349
  group = option.group;
297
- result.push(`${DIM}${plain(group)}${RESET}`);
350
+ body.push(`${DIM}${plain(group)}${RESET}`);
298
351
  }
299
352
  const detail = option.detail === undefined ? '' : ` — ${plain(option.detail)}`;
300
353
  const active = option.active === true ? accessible ? ' [active]' : ' ●' : '';
301
354
  const danger = option.danger === true ? ` ${YELLOW}${accessible ? '[danger]' : '!'}${RESET}` : '';
302
- result.push(line(`${index === overlay.cursor ? BOLD_CYAN : DIM}${index === overlay.cursor ? accessible ? '>' : '›' : ' '} ${String(index + 1)} ${plain(option.label)}${active}${detail}${RESET}${danger}`, width));
355
+ if (index === overlay.cursor)
356
+ selectedRow = body.length;
357
+ body.push(line(`${index === overlay.cursor ? BOLD_CYAN : DIM}${index === overlay.cursor ? accessible ? '>' : '›' : ' '} ${String(index + 1)} ${plain(option.label)}${active}${detail}${RESET}${danger}`, width));
303
358
  }
304
- return result.slice(0, Math.max(2, rows - (overlay.purpose === 'completion' ? 6 : 3)));
359
+ const limit = Math.max(2, rows - (overlay.purpose === 'completion' ? 6 : 3));
360
+ return [...result, ...windowRows(body, Math.max(1, limit - result.length), selectedRow, true)];
305
361
  };
306
362
  const document = {
307
363
  invalidate() { editor.invalidate(); },
@@ -345,8 +401,8 @@ export function createRenderer(options) {
345
401
  : root === undefined
346
402
  ? `${DIM}no session · cards ${state.toolMode} · Ctrl+J newline · Ctrl+L redraw${RESET}`
347
403
  : root.status === 'running'
348
- ? `${DIM}running · ${plain(root.model)} · Enter ${state.sendMode === 'steer' ? 'steer' : 'queue next turn'} · Ctrl+T toggle · cards ${state.toolMode} · Esc interrupt${RESET}`
349
- : `${DIM}idle · ${plain(root.model)} · Enter send · cards ${state.toolMode}${RESET}`;
404
+ ? `${DIM}running${accessible ? ` · ${plain(root.model)}` : ''} · Enter ${state.sendMode === 'steer' ? 'steer' : 'queue next turn'} · Ctrl+T toggle · cards ${state.toolMode} · Esc interrupt${RESET}`
405
+ : `${DIM}idle${accessible ? ` · ${plain(root.model)}` : ''} · Enter send · cards ${state.toolMode}${RESET}`;
350
406
  const activityCount = activities(state).length;
351
407
  const baseStatus = `${baseStatusText}${root?.contextPercent === undefined ? '' : ` · ${String(root.contextPercent)}% context`}${activityCount === 0 ? '' : ' · Ctrl+B activity'}`;
352
408
  const status = state.newOutput === 0 ? baseStatus
@@ -355,6 +411,11 @@ export function createRenderer(options) {
355
411
  const attachmentRows = state.attachments.map((attachment, index) => line(`${YELLOW}[image ${String(index + 1)}]${RESET} ${plain(attachment.name)} · ${attachment.mediaType} · ${String(attachment.bytes)} B · ${plain(attachment.path)}`, width));
356
412
  const activityRows = decision !== undefined || state.search !== undefined || state.overlay !== undefined
357
413
  ? [] : activities(state).map(item => line(`${DIM}${activityText(item, now)}${RESET}`, width));
414
+ const persistentStatus = accessible || state.exitArmed ? undefined : options.statusLine?.();
415
+ const composer = () => [
416
+ ...(persistentStatus === undefined ? [] : [line(`${DIM}${plain(persistentStatus)}${RESET}`, width)]),
417
+ ...editor.render(width),
418
+ ];
358
419
  const control = decision !== undefined
359
420
  ? decisionPanel(decision, width, rows)
360
421
  : state.search !== undefined
@@ -366,11 +427,11 @@ export function createRenderer(options) {
366
427
  `${DIM}${state.search.matches.length === 0 ? 'no matches' : `${String(state.search.cursor + 1)}/${String(state.search.matches.length)}`} · Enter next · Shift+Enter previous${RESET}`,
367
428
  ]
368
429
  : state.overlay === undefined
369
- ? [...activityRows, ...attachmentRows, ...editor.render(width)]
430
+ ? [...activityRows, ...attachmentRows, ...composer()]
370
431
  : [
371
432
  ...overlayPanel(state.overlay, state, width, rows),
372
433
  ...(state.overlay.kind === 'list' && state.overlay.purpose === 'completion'
373
- ? [...attachmentRows, ...editor.render(width)]
434
+ ? [...attachmentRows, ...composer()]
374
435
  : []),
375
436
  ];
376
437
  const fixedRows = header.length + control.length + 2;
@@ -392,7 +453,15 @@ export function createRenderer(options) {
392
453
  tui.setFocus(editor);
393
454
  installInput(tui, {
394
455
  ...options,
456
+ dispatch: action => {
457
+ const overlay = options.readState().overlay;
458
+ const size = Math.max(1, terminal.rows - 5);
459
+ options.dispatch(action.type === 'overlay-move' && overlay?.kind === 'info'
460
+ ? { ...action, limit: Math.max(0, infoRows(overlay, options.readState(), terminal.columns).length - Math.max(1, size - 1)) }
461
+ : action);
462
+ },
395
463
  insertNewline: () => { editor.insertTextAtCursor('\n'); },
464
+ insertText: text => { editor.insertTextAtCursor(text); },
396
465
  readComposerCursor: () => editor.getCursor(),
397
466
  });
398
467
  return {
@@ -406,6 +475,7 @@ export function createRenderer(options) {
406
475
  terminal.write(`\u001B]52;c;${Buffer.from(safe).toString('base64')}\u0007`);
407
476
  return 'ok';
408
477
  },
478
+ discardSecretComposer: replaceEditor,
409
479
  drainInput: () => terminal.drainInput(),
410
480
  materializedCells: () => materializedCount,
411
481
  render: force => { tui.renderNow(force); },
package/lib/rewind.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller';
1
2
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
2
3
  import type { Overlay, OverlayValue } from './state.js';
3
4
  export interface RewindBoundary {
@@ -5,10 +6,22 @@ export interface RewindBoundary {
5
6
  readonly label: string;
6
7
  readonly prompt: string;
7
8
  }
9
+ export declare function latestCompletedTurn(events: readonly SessionEvent[]): {
10
+ readonly atSeq?: number;
11
+ readonly open: boolean;
12
+ readonly turn?: number;
13
+ };
14
+ /** Return only a selection that DSH recorded for the source session. */
15
+ export declare function rewindModelSelection(projection: {
16
+ readonly lastUsed: ModelSelection | null;
17
+ readonly next: ModelSelection | null;
18
+ } | undefined): ModelSelection | undefined;
8
19
  /** Return the text of a direct human message, excluding injected context. */
9
20
  export declare function humanPrompt(event: SessionEvent): string | undefined;
10
- export declare function humanPrompts(events: readonly SessionEvent[]): readonly string[];
11
- /** Derive each human prompt and its controller-valid containing-turn cut. */
21
+ /** Recover one composer input from DSH's durable log. */
22
+ export declare function historyInput(event: SessionEvent): string | undefined;
23
+ export declare function historyInputs(events: readonly SessionEvent[]): readonly string[];
24
+ /** Derive each recalled input and its controller-valid preceding-turn cut. */
12
25
  export declare function rewindBoundaries(events: readonly SessionEvent[]): readonly RewindBoundary[];
13
26
  export declare function rewindOverlay(events: readonly SessionEvent[], roller: boolean): Overlay;
14
27
  export declare function rewindActionOverlay(boundary: Extract<OverlayValue, {
package/lib/rewind.js CHANGED
@@ -1,3 +1,13 @@
1
+ export function latestCompletedTurn(events) {
2
+ const end = events.filter((event) => event.type === 'turn/end' && event.data.reason.kind !== 'interrupted').at(-1);
3
+ const start = events.filter((event) => event.type === 'turn/start').at(-1);
4
+ return { open: start !== undefined && (end === undefined || start.seq > end.seq),
5
+ ...(end === undefined ? {} : { atSeq: end.seq, turn: end.data.turn }) };
6
+ }
7
+ /** Return only a selection that DSH recorded for the source session. */
8
+ export function rewindModelSelection(projection) {
9
+ return projection?.next ?? projection?.lastUsed ?? undefined;
10
+ }
1
11
  /** Return the text of a direct human message, excluding injected context. */
2
12
  export function humanPrompt(event) {
3
13
  if (event.type !== 'user/message' || event.data.source.kind !== 'user')
@@ -7,10 +17,29 @@ export function humanPrompt(event) {
7
17
  .map(block => block.text).join('');
8
18
  return text === '' ? undefined : text;
9
19
  }
10
- export function humanPrompts(events) {
11
- return events.flatMap(event => humanPrompt(event) ?? []);
20
+ function shellInput(message) {
21
+ if (message.source.kind !== 'plugin' || message.source.plugin !== 'dashi'
22
+ || message.source.form !== 'notice')
23
+ return undefined;
24
+ const first = message.content.find(block => block.type === 'text')?.text.split(/\r?\n/u)[0];
25
+ return first?.startsWith('$ ') === true ? `!${first.slice(2)}` : undefined;
26
+ }
27
+ /** Recover one composer input from DSH's durable log. */
28
+ export function historyInput(event) {
29
+ const prompt = humanPrompt(event);
30
+ if (prompt !== undefined)
31
+ return prompt;
32
+ if (event.type === 'command/run' && event.data.args !== undefined) {
33
+ return `/${event.data.name}${event.data.args}`;
34
+ }
35
+ if (event.type !== 'agent/inbox/spliced')
36
+ return undefined;
37
+ return event.data.inserted.map(shellInput).find(input => input !== undefined);
12
38
  }
13
- /** Derive each human prompt and its controller-valid containing-turn cut. */
39
+ export function historyInputs(events) {
40
+ return events.flatMap(event => historyInput(event) ?? []);
41
+ }
42
+ /** Derive each recalled input and its controller-valid preceding-turn cut. */
14
43
  export function rewindBoundaries(events) {
15
44
  const boundaries = [];
16
45
  let previousEnd;
@@ -21,27 +50,28 @@ export function rewindBoundaries(events) {
21
50
  openTurn = event.data.turn;
22
51
  prompted = false;
23
52
  }
24
- else if (event.type === 'user/message' && openTurn !== undefined) {
25
- const prompt = humanPrompt(event);
26
- if (prompt !== undefined) {
27
- boundaries.push({
28
- ...(previousEnd === undefined ? {} : { atSeq: previousEnd.seq }),
29
- label: `${prompt.replaceAll(/\s+/gu, ' ')}${prompted ? ' · mid-turn' : ''}`,
30
- prompt,
31
- });
32
- prompted = true;
33
- }
34
- }
35
- else if (event.type === 'turn/end') {
53
+ else if (event.type === 'turn/end' && event.data.reason.kind !== 'interrupted') {
36
54
  previousEnd = event;
37
55
  openTurn = undefined;
38
56
  prompted = false;
39
57
  }
58
+ const prompt = historyInput(event);
59
+ if (prompt === undefined)
60
+ continue;
61
+ const human = humanPrompt(event) !== undefined;
62
+ boundaries.push({
63
+ ...(previousEnd === undefined ? {} : { atSeq: previousEnd.seq }),
64
+ label: `${prompt.replaceAll(/\s+/gu, ' ')}${human && prompted ? ' · mid-turn' : ''}`,
65
+ prompt,
66
+ });
67
+ if (human && openTurn !== undefined)
68
+ prompted = true;
40
69
  }
41
70
  return boundaries;
42
71
  }
43
72
  export function rewindOverlay(events, roller) {
44
73
  const boundaries = rewindBoundaries(events);
74
+ const trailing = latestCompletedTurn(events);
45
75
  return {
46
76
  cursor: Math.max(0, boundaries.length - 1),
47
77
  kind: 'list',
@@ -53,7 +83,7 @@ export function rewindOverlay(events, roller) {
53
83
  },
54
84
  })),
55
85
  purpose: 'rewind',
56
- title: 'Rewind to a prompt',
86
+ title: `Rewind to a prompt${trailing.open ? ' · trailing turn open; completed boundaries only' : ''}`,
57
87
  };
58
88
  }
59
89
  export function rewindActionOverlay(boundary) {
@@ -2,16 +2,25 @@ import type { Context } from '@deepseek-ai/cordis';
2
2
  import type { ActivatableOverlayValue, DecisionAnswer, DraftAttachment, OverlayOption, PendingDecision, RootView, SendMode, TerminalCell, UiAction } from './state.js';
3
3
  import type { TuiRoot } from './tui-root.js';
4
4
  export interface RootLaunchOptions {
5
+ readonly agentPreset?: string;
5
6
  readonly continue: boolean;
7
+ readonly appendSystemPrompt?: string;
6
8
  readonly cwd: string;
7
9
  readonly effort?: string;
10
+ readonly forkSession: boolean;
8
11
  readonly name?: string;
9
12
  readonly images?: readonly string[];
10
13
  readonly model?: string;
11
14
  readonly permission?: string;
12
15
  readonly prompt?: string;
13
16
  readonly provider?: string;
17
+ readonly tools?: readonly string[];
18
+ readonly disallowedTools?: readonly string[];
14
19
  readonly resume?: string;
20
+ readonly sessionId?: string;
21
+ readonly systemPrompt?: string;
22
+ }
23
+ export declare class LaunchArgumentError extends Error {
15
24
  }
16
25
  export interface SessionRuntime {
17
26
  readonly initialAttachments: readonly DraftAttachment[];
@@ -19,6 +28,7 @@ export interface SessionRuntime {
19
28
  readonly initialHasMore: boolean;
20
29
  readonly initialPrompts: readonly string[];
21
30
  readonly root: RootView;
31
+ readonly statusLine: string;
22
32
  readonly summary: string;
23
33
  activate(value: ActivatableOverlayValue): Promise<void>;
24
34
  attach(path: string): Promise<DraftAttachment | undefined>;