@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/README.md +6 -6
- package/lib/application.d.ts +2 -0
- package/lib/application.js +8 -3
- package/lib/catalogs.d.ts +5 -0
- package/lib/catalogs.js +91 -1
- package/lib/completion-trigger.d.ts +4 -0
- package/lib/completion-trigger.js +13 -1
- package/lib/diff-view.js +11 -4
- package/lib/help.d.ts +1 -1
- package/lib/help.js +1 -1
- package/lib/history-records.d.ts +2 -2
- package/lib/history-records.js +2 -12
- package/lib/index.js +62 -14
- package/lib/input.d.ts +8 -1
- package/lib/input.js +45 -0
- package/lib/renderer.d.ts +2 -0
- package/lib/renderer.js +90 -20
- package/lib/rewind.d.ts +15 -2
- package/lib/rewind.js +46 -16
- package/lib/session-runtime.d.ts +10 -0
- package/lib/session-runtime.js +390 -43
- package/lib/shell-command.d.ts +1 -1
- package/lib/shell-command.js +3 -2
- package/lib/state.d.ts +23 -3
- package/lib/state.js +64 -23
- package/lib/transcript.d.ts +3 -1
- package/lib/transcript.js +57 -44
- package/package.json +38 -33
- package/validated-dsh-versions.json +0 -3
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
|
|
163
|
-
|
|
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
|
|
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 +
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
...
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, ...
|
|
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, ...
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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 === '
|
|
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:
|
|
86
|
+
title: `Rewind to a prompt${trailing.open ? ' · trailing turn open; completed boundaries only' : ''}`,
|
|
57
87
|
};
|
|
58
88
|
}
|
|
59
89
|
export function rewindActionOverlay(boundary) {
|
package/lib/session-runtime.d.ts
CHANGED
|
@@ -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>;
|