@antst/dashi 0.1.0-alpha.11 → 0.1.0-alpha.12
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/application.d.ts +1 -0
- package/lib/application.js +1 -1
- package/lib/catalogs.d.ts +1 -0
- package/lib/catalogs.js +14 -0
- package/lib/index.js +1 -0
- package/lib/input.d.ts +1 -1
- package/lib/input.js +5 -0
- package/lib/renderer.d.ts +1 -0
- package/lib/renderer.js +68 -13
- package/lib/session-runtime.d.ts +1 -0
- package/lib/session-runtime.js +25 -2
- package/lib/state.d.ts +3 -0
- package/lib/state.js +8 -2
- package/package.json +1 -1
package/lib/application.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface TerminalShellOptions {
|
|
|
27
27
|
readonly onReleased?: () => void;
|
|
28
28
|
readonly pasteImage?: () => Promise<DraftAttachment>;
|
|
29
29
|
readonly search?: (query: string, rootId: string) => Promise<void>;
|
|
30
|
+
readonly statusLine?: () => string | undefined;
|
|
30
31
|
readonly submit?: (text: string, mode: 'next-turn' | 'steer', attachments: readonly DraftAttachment[]) => Promise<void> | void;
|
|
31
32
|
readonly suspend?: () => Promise<void>;
|
|
32
33
|
readonly writeError?: (message: string) => void;
|
package/lib/application.js
CHANGED
|
@@ -26,7 +26,7 @@ export function createTerminalShell(options) {
|
|
|
26
26
|
readState: () => state,
|
|
27
27
|
};
|
|
28
28
|
const renderer = options.createView?.(bindings) ?? createRenderer({
|
|
29
|
-
...bindings, ...(options.accessible === undefined ? {} : { accessible: options.accessible }), inline: options.inline,
|
|
29
|
+
...bindings, ...(options.accessible === undefined ? {} : { accessible: options.accessible }), ...(options.statusLine === undefined ? {} : { statusLine: options.statusLine }), inline: options.inline,
|
|
30
30
|
});
|
|
31
31
|
const guard = createTerminalGuard(renderer);
|
|
32
32
|
const writeError = options.writeError ?? (message => { process.stderr.write(message); });
|
package/lib/catalogs.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare function sessionOverlay(items: readonly SessionSummary[], current
|
|
|
14
14
|
export declare function modelOverlay(catalog: ModelCatalog, selected?: ModelSelection): Overlay;
|
|
15
15
|
/** Build the preset picker from the native preset service. */
|
|
16
16
|
export declare function permissionOverlay(ctx: Context, agent: Agent): Overlay;
|
|
17
|
+
export declare function sessionStatusLine(ctx: Context, agent: Agent, root: RootView, branch?: string): string;
|
|
17
18
|
/** Read DSH-owned root facts for /status in one projection cut. */
|
|
18
19
|
export declare function statusOverlay(ctx: Context, agent: Agent, root: RootView): Overlay;
|
|
19
20
|
/** Show DSH's heuristic next-request composition without treating it as an exact total. */
|
package/lib/catalogs.js
CHANGED
|
@@ -156,6 +156,20 @@ function number(value) {
|
|
|
156
156
|
function milliseconds(value) {
|
|
157
157
|
return typeof value === 'number' ? `${number(value)} ms` : 'unavailable';
|
|
158
158
|
}
|
|
159
|
+
export function sessionStatusLine(ctx, agent, root, branch) {
|
|
160
|
+
const { modelSelection, permissions, tokenUsage, contextPressure } = ctx.sessionProjections
|
|
161
|
+
.snapshot(agent.session, ['modelSelection', 'permissions', 'tokenUsage', 'contextPressure']).values;
|
|
162
|
+
const model = modelSelection?.next ?? modelSelection?.lastUsed;
|
|
163
|
+
const input = tokenUsage === undefined ? 0 : tokenUsage.uncachedInputTokens + tokenUsage.cacheReadTokens + tokenUsage.cacheWriteTokens;
|
|
164
|
+
const used = contextPressure?.projectedTokens ?? contextPressure?.pressureTokens;
|
|
165
|
+
return [
|
|
166
|
+
`model ${model == null ? `${root.provider === undefined ? '' : `${root.provider}/`}${root.model}` : `${model.provider}/${model.model}`}`,
|
|
167
|
+
`permission ${permissions?.currentValue ?? root.permission ?? 'unknown'}`,
|
|
168
|
+
...(used === undefined || contextPressure?.contextWindow === undefined ? [] : [`context ${number(used)}/${number(contextPressure.contextWindow)}`]),
|
|
169
|
+
...(input === 0 ? [] : [`cache ${String(Math.min(tokenUsage?.cacheReadTokens === input ? 100 : 99, Math.round((tokenUsage?.cacheReadTokens ?? 0) / input * 100)))}%`]),
|
|
170
|
+
...(branch === undefined ? [] : [`branch ${branch}`]),
|
|
171
|
+
].join(' · ');
|
|
172
|
+
}
|
|
159
173
|
/** Read DSH-owned root facts for /status in one projection cut. */
|
|
160
174
|
export function statusOverlay(ctx, agent, root) {
|
|
161
175
|
const values = ctx.sessionProjections.snapshot(agent.session, [
|
package/lib/index.js
CHANGED
|
@@ -299,6 +299,7 @@ export function apply(ctx) {
|
|
|
299
299
|
onReleased: () => { process.stdout.write(`${runtime?.summary ?? ''}\n`); },
|
|
300
300
|
pasteImage: readClipboardImage,
|
|
301
301
|
search: (query, rootId) => runtime?.search(query, rootId) ?? Promise.resolve(),
|
|
302
|
+
statusLine: () => runtime?.statusLine,
|
|
302
303
|
submit: (text, mode, attachments) => runtime?.submit(text, mode, attachments),
|
|
303
304
|
});
|
|
304
305
|
shell.start();
|
package/lib/input.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TUI } from '@earendil-works/pi-tui';
|
|
2
|
-
import type
|
|
2
|
+
import { type ComposerCursor, type UiAction, type ViewState } from './state.js';
|
|
3
3
|
export interface InputBindings {
|
|
4
4
|
readonly dispatch: (action: UiAction) => void;
|
|
5
5
|
readonly insertNewline?: () => void;
|
package/lib/input.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isKeyRelease, matchesKey } from '@earendil-works/pi-tui';
|
|
2
|
+
import { PAGE_CELLS } from './state.js';
|
|
2
3
|
export function caretOffset(text, cursor) {
|
|
3
4
|
const lines = text.split('\n');
|
|
4
5
|
let offset = 0;
|
|
@@ -98,6 +99,10 @@ export function installInput(tui, bindings) {
|
|
|
98
99
|
bindings.dispatch({ type: 'search-open', scope: 'history' });
|
|
99
100
|
else if (overlay.kind === 'history' && matchesKey(data, 'y'))
|
|
100
101
|
bindings.dispatch({ type: 'history-copy' });
|
|
102
|
+
else if (overlay.kind === 'info' && matchesKey(data, 'pageUp'))
|
|
103
|
+
bindings.dispatch({ type: 'overlay-move', offset: -PAGE_CELLS });
|
|
104
|
+
else if (overlay.kind === 'info' && matchesKey(data, 'pageDown'))
|
|
105
|
+
bindings.dispatch({ type: 'overlay-move', offset: PAGE_CELLS });
|
|
101
106
|
else if (matchesKey(data, 'up'))
|
|
102
107
|
bindings.dispatch({ type: 'overlay-move', offset: -1 });
|
|
103
108
|
else if (matchesKey(data, 'down'))
|
package/lib/renderer.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface Renderer extends TerminalSession {
|
|
|
13
13
|
export interface RendererOptions extends InputBindings {
|
|
14
14
|
readonly accessible?: boolean;
|
|
15
15
|
readonly inline: boolean;
|
|
16
|
+
readonly statusLine?: () => string | undefined;
|
|
16
17
|
readonly terminal?: Terminal;
|
|
17
18
|
readonly terminalType?: string;
|
|
18
19
|
}
|
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';
|
|
@@ -15,6 +15,22 @@ const THEME_GENERATION = 1;
|
|
|
15
15
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
16
16
|
const SPINNER_INTERVAL_MS = 80;
|
|
17
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
|
+
};
|
|
18
34
|
export function supportsOsc52(terminalType) {
|
|
19
35
|
return terminalType?.toLocaleLowerCase() !== 'dumb';
|
|
20
36
|
}
|
|
@@ -138,6 +154,22 @@ function plain(text, preserveNewlines = false) {
|
|
|
138
154
|
function line(text, width) {
|
|
139
155
|
return truncateToWidth(text, Math.max(1, width), '…');
|
|
140
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
|
+
}
|
|
141
173
|
function theme() {
|
|
142
174
|
const identity = (text) => text;
|
|
143
175
|
return {
|
|
@@ -160,7 +192,9 @@ export function createRenderer(options) {
|
|
|
160
192
|
const osc52 = supportsOsc52(terminalType);
|
|
161
193
|
const tui = options.inline
|
|
162
194
|
? new TuiMainScreen(terminal, true)
|
|
163
|
-
: new
|
|
195
|
+
: new class extends TuiAltScreen {
|
|
196
|
+
isOverlayFocused() { return options.readState().overlay !== undefined || super.isOverlayFocused(); }
|
|
197
|
+
}(terminal, true, undefined, { mouse: true });
|
|
164
198
|
let editor = new Editor(tui, theme(), { paddingX: 1 });
|
|
165
199
|
editor.disableSubmit = true;
|
|
166
200
|
editor.onChange = text => {
|
|
@@ -184,7 +218,11 @@ export function createRenderer(options) {
|
|
|
184
218
|
const cached = cell.pending === true ? undefined : lineCache.get(key);
|
|
185
219
|
if (cached !== undefined)
|
|
186
220
|
return cached;
|
|
187
|
-
const
|
|
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}`);
|
|
188
226
|
if (cell.pending !== true) {
|
|
189
227
|
lineCache.set(key, rendered);
|
|
190
228
|
if (lineCache.size > 512)
|
|
@@ -275,19 +313,19 @@ export function createRenderer(options) {
|
|
|
275
313
|
result.push(`${DIM}↑↓ select · Enter details · Esc close${RESET}`);
|
|
276
314
|
return result.slice(0, Math.max(2, rows - 3));
|
|
277
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)) ?? []);
|
|
278
317
|
const overlayPanel = (overlay, state, width, rows) => {
|
|
279
318
|
if (overlay.kind === 'history')
|
|
280
319
|
return historyPanel(overlay, state, width, rows);
|
|
281
320
|
if (overlay.kind === 'details')
|
|
282
321
|
return detailsPanel(overlay, state, width, rows);
|
|
283
322
|
if (overlay.kind === 'info') {
|
|
284
|
-
const
|
|
323
|
+
const body = infoRows(overlay, state, width);
|
|
285
324
|
return [
|
|
286
325
|
`${BOLD_CYAN}${announcement(plain(overlay.title), overlay.lines.length + (overlay.cells?.length ?? 0))}${RESET}`,
|
|
287
|
-
...
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
].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
|
+
];
|
|
291
329
|
}
|
|
292
330
|
if (overlay.kind === 'confirm') {
|
|
293
331
|
const choices = [overlay.acceptLabel, 'Cancel'];
|
|
@@ -303,18 +341,23 @@ export function createRenderer(options) {
|
|
|
303
341
|
line(`${BOLD_CYAN}${announcement(plain(overlay.title), overlay.options.length, selected === undefined ? undefined : plain(selected.label), overlay.cursor)}${RESET}`, width),
|
|
304
342
|
...(overlay.notice === undefined ? [] : [`${DIM}${plain(overlay.notice)}${RESET}`]),
|
|
305
343
|
];
|
|
344
|
+
const body = [];
|
|
345
|
+
let selectedRow = 0;
|
|
306
346
|
let group;
|
|
307
347
|
for (const [index, option] of overlay.options.entries()) {
|
|
308
348
|
if (option.group !== undefined && option.group !== group) {
|
|
309
349
|
group = option.group;
|
|
310
|
-
|
|
350
|
+
body.push(`${DIM}${plain(group)}${RESET}`);
|
|
311
351
|
}
|
|
312
352
|
const detail = option.detail === undefined ? '' : ` — ${plain(option.detail)}`;
|
|
313
353
|
const active = option.active === true ? accessible ? ' [active]' : ' ●' : '';
|
|
314
354
|
const danger = option.danger === true ? ` ${YELLOW}${accessible ? '[danger]' : '!'}${RESET}` : '';
|
|
315
|
-
|
|
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));
|
|
316
358
|
}
|
|
317
|
-
|
|
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)];
|
|
318
361
|
};
|
|
319
362
|
const document = {
|
|
320
363
|
invalidate() { editor.invalidate(); },
|
|
@@ -368,6 +411,11 @@ export function createRenderer(options) {
|
|
|
368
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));
|
|
369
412
|
const activityRows = decision !== undefined || state.search !== undefined || state.overlay !== undefined
|
|
370
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
|
+
];
|
|
371
419
|
const control = decision !== undefined
|
|
372
420
|
? decisionPanel(decision, width, rows)
|
|
373
421
|
: state.search !== undefined
|
|
@@ -379,11 +427,11 @@ export function createRenderer(options) {
|
|
|
379
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}`,
|
|
380
428
|
]
|
|
381
429
|
: state.overlay === undefined
|
|
382
|
-
? [...activityRows, ...attachmentRows, ...
|
|
430
|
+
? [...activityRows, ...attachmentRows, ...composer()]
|
|
383
431
|
: [
|
|
384
432
|
...overlayPanel(state.overlay, state, width, rows),
|
|
385
433
|
...(state.overlay.kind === 'list' && state.overlay.purpose === 'completion'
|
|
386
|
-
? [...attachmentRows, ...
|
|
434
|
+
? [...attachmentRows, ...composer()]
|
|
387
435
|
: []),
|
|
388
436
|
];
|
|
389
437
|
const fixedRows = header.length + control.length + 2;
|
|
@@ -405,6 +453,13 @@ export function createRenderer(options) {
|
|
|
405
453
|
tui.setFocus(editor);
|
|
406
454
|
installInput(tui, {
|
|
407
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
|
+
},
|
|
408
463
|
insertNewline: () => { editor.insertTextAtCursor('\n'); },
|
|
409
464
|
readComposerCursor: () => editor.getCursor(),
|
|
410
465
|
});
|
package/lib/session-runtime.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export interface SessionRuntime {
|
|
|
28
28
|
readonly initialHasMore: boolean;
|
|
29
29
|
readonly initialPrompts: readonly string[];
|
|
30
30
|
readonly root: RootView;
|
|
31
|
+
readonly statusLine: string;
|
|
31
32
|
readonly summary: string;
|
|
32
33
|
activate(value: ActivatableOverlayValue): Promise<void>;
|
|
33
34
|
attach(path: string): Promise<DraftAttachment | undefined>;
|
package/lib/session-runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseCredentialKey } from '@deepseek-ai/dsh-credentials';
|
|
|
4
4
|
import { JobId } from '@deepseek-ai/dsh-jobs';
|
|
5
5
|
import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm';
|
|
6
6
|
import { SessionId, SessionSeq } from '@deepseek-ai/dsh-session';
|
|
7
|
-
import { agentPresetOverlay, completionOptions, contextOverlay, currentModel, modelOverlay, permissionOverlay, searchOverlay, sessionOverlay, skillOverlay, statusOverlay, usageOverlay, } from './catalogs.js';
|
|
7
|
+
import { agentPresetOverlay, completionOptions, contextOverlay, currentModel, modelOverlay, permissionOverlay, searchOverlay, sessionStatusLine, sessionOverlay, skillOverlay, statusOverlay, usageOverlay, } from './catalogs.js';
|
|
8
8
|
import { diffOverlay } from './diff-view.js';
|
|
9
9
|
import { eventsFromRecords } from './history-records.js';
|
|
10
10
|
import { admitDraftImages, encodeDraftImages, readDraftImages } from './image-input.js';
|
|
@@ -80,6 +80,18 @@ async function firstControl(iterator) {
|
|
|
80
80
|
}
|
|
81
81
|
return first.value;
|
|
82
82
|
}
|
|
83
|
+
async function gitBranch(ctx, agent, signal) {
|
|
84
|
+
try {
|
|
85
|
+
const result = await ctx.shell.run(ctx.shell.resolve({
|
|
86
|
+
command: 'git branch --show-current', sandboxPolicy: ctx.sandboxPolicy.resolve({ mode: 'read-only' }),
|
|
87
|
+
signal, stdoutMaxBytes: 512, timeoutMs: 2_000, workdir: agent.session.header.cwd,
|
|
88
|
+
}));
|
|
89
|
+
return result.exitCode === 0 && result.stdout.text.trim() !== '' ? result.stdout.text.trim() : undefined;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
83
95
|
function usage(raw, expected) {
|
|
84
96
|
return raw.trim() === '' ? undefined : { kind: 'error', text: `usage: ${expected}` };
|
|
85
97
|
}
|
|
@@ -153,8 +165,9 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
153
165
|
address: { kind: 'session', sessionId }, maxMessages: 50,
|
|
154
166
|
}, abort.signal)[Symbol.asyncIterator]();
|
|
155
167
|
const controlIterator = ctx.sessionController.control(abort.signal)[Symbol.asyncIterator]();
|
|
156
|
-
const [opening, controlOpening, subagents] = await Promise.all([
|
|
168
|
+
const [opening, controlOpening, subagents, branch] = await Promise.all([
|
|
157
169
|
firstSnapshot(iterator), firstControl(controlIterator), subagentViews(ctx, sessionId, abort.signal),
|
|
170
|
+
gitBranch(ctx, agent, abort.signal),
|
|
158
171
|
]);
|
|
159
172
|
title ??= projectionString(opening.projections, 'title');
|
|
160
173
|
const selected = projectionModel(opening.projections);
|
|
@@ -166,6 +179,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
166
179
|
return {
|
|
167
180
|
abort,
|
|
168
181
|
agent,
|
|
182
|
+
branch,
|
|
169
183
|
controlIterator,
|
|
170
184
|
cursor: opening.cursor,
|
|
171
185
|
disposers,
|
|
@@ -275,12 +289,20 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
275
289
|
});
|
|
276
290
|
}, 34);
|
|
277
291
|
};
|
|
292
|
+
const refreshBranch = async (bound) => {
|
|
293
|
+
const branch = await gitBranch(ctx, bound.agent, bound.abort.signal);
|
|
294
|
+
if (bound.abort.signal.aborted || current() !== bound || bound.branch === branch)
|
|
295
|
+
return;
|
|
296
|
+
bound.branch = branch;
|
|
297
|
+
dispatch({ type: 'redraw' });
|
|
298
|
+
};
|
|
278
299
|
const consume = (bound, frame) => {
|
|
279
300
|
const [event] = eventsFromRecords([frame]);
|
|
280
301
|
if (event === undefined)
|
|
281
302
|
return;
|
|
282
303
|
bound.events.push(event);
|
|
283
304
|
if (event.type === 'turn/end') {
|
|
305
|
+
void refreshBranch(bound);
|
|
284
306
|
const startedAt = turnStartTime(bound.events, event.data.turn);
|
|
285
307
|
if (startedAt !== undefined) {
|
|
286
308
|
dispatch({
|
|
@@ -1257,6 +1279,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
1257
1279
|
get initialHasMore() { return current().hasMore; },
|
|
1258
1280
|
get initialPrompts() { return humanPrompts(current().events); },
|
|
1259
1281
|
get root() { return current().root; },
|
|
1282
|
+
get statusLine() { const bound = current(); return sessionStatusLine(ctx, bound.agent, bound.root, bound.branch); },
|
|
1260
1283
|
get summary() { return `Resume with: dsh --profile dashi --resume ${current().root.id}`; },
|
|
1261
1284
|
activate,
|
|
1262
1285
|
async attach(path) {
|
package/lib/state.d.ts
CHANGED
|
@@ -216,6 +216,7 @@ export type Overlay = {
|
|
|
216
216
|
readonly cells?: readonly TerminalCell[];
|
|
217
217
|
readonly kind: 'info';
|
|
218
218
|
readonly lines: readonly string[];
|
|
219
|
+
readonly scrollOffset?: number;
|
|
219
220
|
readonly title: string;
|
|
220
221
|
} | {
|
|
221
222
|
readonly cursor: number;
|
|
@@ -346,6 +347,7 @@ export type UiAction = {
|
|
|
346
347
|
} | {
|
|
347
348
|
readonly type: 'overlay-close';
|
|
348
349
|
} | {
|
|
350
|
+
readonly limit?: number;
|
|
349
351
|
readonly type: 'overlay-move';
|
|
350
352
|
readonly offset: number;
|
|
351
353
|
} | {
|
|
@@ -508,4 +510,5 @@ export type UiEffect = {
|
|
|
508
510
|
};
|
|
509
511
|
export declare function initialViewState(cwd: string, inline: boolean, root?: RootView, cells?: readonly TerminalCell[], prompts?: readonly string[], historyHasMore?: boolean, attachments?: readonly DraftAttachment[], toolMode?: ToolMode): ViewState;
|
|
510
512
|
export declare const TURN_BELL_THRESHOLD_MS = 10000;
|
|
513
|
+
export declare const PAGE_CELLS = 8;
|
|
511
514
|
export declare function reduce(state: ViewState, action: UiAction): readonly [ViewState, readonly UiEffect[]];
|
package/lib/state.js
CHANGED
|
@@ -55,7 +55,7 @@ function moveOverlay(overlay, cursor, detailsCount = 0) {
|
|
|
55
55
|
: Math.max(0, overlay.options.length - 1);
|
|
56
56
|
return { ...overlay, cursor: Math.max(0, Math.min(limit, cursor)) };
|
|
57
57
|
}
|
|
58
|
-
const PAGE_CELLS = 8;
|
|
58
|
+
export const PAGE_CELLS = 8;
|
|
59
59
|
function matches(cells, query) {
|
|
60
60
|
const needle = query.trim().toLocaleLowerCase();
|
|
61
61
|
if (needle === '')
|
|
@@ -376,8 +376,14 @@ export function reduce(state, action) {
|
|
|
376
376
|
case 'overlay-move': {
|
|
377
377
|
if (state.overlay === undefined)
|
|
378
378
|
return [state, []];
|
|
379
|
+
if (state.overlay.kind === 'info') {
|
|
380
|
+
const current = state.overlay.scrollOffset ?? 0;
|
|
381
|
+
const scrollOffset = Math.max(0, Math.min(action.limit ?? current, current + action.offset));
|
|
382
|
+
return scrollOffset === current ? [state, []]
|
|
383
|
+
: [{ ...state, overlay: { ...state.overlay, scrollOffset } }, redraw()];
|
|
384
|
+
}
|
|
379
385
|
const count = (state.root?.jobs?.length ?? 0) + (state.root?.subagents?.length ?? 0);
|
|
380
|
-
const overlay = moveOverlay(state.overlay, state.overlay.
|
|
386
|
+
const overlay = moveOverlay(state.overlay, state.overlay.cursor + action.offset, count);
|
|
381
387
|
return overlay === state.overlay ? [state, []] : [{ ...state, overlay }, redraw()];
|
|
382
388
|
}
|
|
383
389
|
case 'overlay-number': {
|