@antst/dashi 0.1.0-alpha.20 → 0.1.0-alpha.21

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 (53) hide show
  1. package/lib/application.d.ts +45 -0
  2. package/lib/application.js +273 -0
  3. package/lib/at-trigger.d.ts +8 -0
  4. package/lib/at-trigger.js +13 -0
  5. package/lib/catalogs.d.ts +28 -0
  6. package/lib/catalogs.js +308 -0
  7. package/lib/clipboard-image.d.ts +3 -0
  8. package/lib/clipboard-image.js +74 -0
  9. package/lib/completion-trigger.d.ts +6 -0
  10. package/lib/completion-trigger.js +19 -0
  11. package/lib/diff-view.d.ts +5 -0
  12. package/lib/diff-view.js +30 -0
  13. package/lib/external-editor.d.ts +3 -0
  14. package/lib/external-editor.js +39 -0
  15. package/lib/help.d.ts +2 -0
  16. package/lib/help.js +6 -0
  17. package/lib/history-records.d.ts +4 -0
  18. package/lib/history-records.js +4 -0
  19. package/lib/image-input.d.ts +8 -0
  20. package/lib/image-input.js +37 -0
  21. package/lib/index.d.ts +4 -0
  22. package/lib/index.js +325 -0
  23. package/lib/input.d.ts +17 -0
  24. package/lib/input.js +267 -0
  25. package/lib/memory.d.ts +3 -0
  26. package/lib/memory.js +27 -0
  27. package/lib/mouse-input.d.ts +1 -0
  28. package/lib/mouse-input.js +6 -0
  29. package/lib/presentation.d.ts +9 -0
  30. package/lib/presentation.js +54 -0
  31. package/lib/renderer.d.ts +21 -0
  32. package/lib/renderer.js +491 -0
  33. package/lib/rewind.d.ts +31 -0
  34. package/lib/rewind.js +114 -0
  35. package/lib/session-export.d.ts +3 -0
  36. package/lib/session-export.js +27 -0
  37. package/lib/session-list.d.ts +15 -0
  38. package/lib/session-list.js +65 -0
  39. package/lib/session-runtime.d.ts +49 -0
  40. package/lib/session-runtime.js +1492 -0
  41. package/lib/shell-command.d.ts +10 -0
  42. package/lib/shell-command.js +66 -0
  43. package/lib/state.d.ts +517 -0
  44. package/lib/state.js +685 -0
  45. package/lib/terminal-guard.d.ts +14 -0
  46. package/lib/terminal-guard.js +76 -0
  47. package/lib/tool-presentation.d.ts +14 -0
  48. package/lib/tool-presentation.js +156 -0
  49. package/lib/transcript.d.ts +14 -0
  50. package/lib/transcript.js +336 -0
  51. package/lib/tui-root.d.ts +20 -0
  52. package/lib/tui-root.js +25 -0
  53. package/package.json +1 -1
package/lib/rewind.js ADDED
@@ -0,0 +1,114 @@
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
+ }
11
+ /** Return the text of a direct human message, excluding injected context. */
12
+ export function humanPrompt(event) {
13
+ if (event.type !== 'user/message' || event.data.source.kind !== 'user')
14
+ return undefined;
15
+ const text = event.data.content
16
+ .filter(block => block.type === 'text')
17
+ .map(block => block.text).join('');
18
+ return text === '' ? undefined : text;
19
+ }
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);
38
+ }
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. */
43
+ export function rewindBoundaries(events) {
44
+ const boundaries = [];
45
+ let previousEnd;
46
+ let openTurn;
47
+ let prompted = false;
48
+ for (const event of events) {
49
+ if (event.type === 'turn/start') {
50
+ openTurn = event.data.turn;
51
+ prompted = false;
52
+ }
53
+ else if (event.type === 'turn/end' && event.data.reason.kind !== 'interrupted') {
54
+ previousEnd = event;
55
+ openTurn = undefined;
56
+ prompted = false;
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;
69
+ }
70
+ return boundaries;
71
+ }
72
+ export function rewindOverlay(events, roller) {
73
+ const boundaries = rewindBoundaries(events);
74
+ const trailing = latestCompletedTurn(events);
75
+ return {
76
+ cursor: Math.max(0, boundaries.length - 1),
77
+ kind: 'list',
78
+ options: boundaries.map(boundary => ({
79
+ label: boundary.label,
80
+ value: {
81
+ kind: 'rewind-boundary', prompt: boundary.prompt, roller,
82
+ ...(boundary.atSeq === undefined ? {} : { atSeq: boundary.atSeq }),
83
+ },
84
+ })),
85
+ purpose: 'rewind',
86
+ title: `Rewind to a prompt${trailing.open ? ' · trailing turn open; completed boundaries only' : ''}`,
87
+ };
88
+ }
89
+ export function rewindActionOverlay(boundary) {
90
+ const base = {
91
+ prompt: boundary.prompt,
92
+ ...(boundary.atSeq === undefined ? {} : { atSeq: boundary.atSeq }),
93
+ };
94
+ const options = [
95
+ ...(boundary.roller ? [{
96
+ label: 'Restore code and conversation', value: { ...base, kind: 'rewind', mode: 'both' },
97
+ }] : []),
98
+ { label: 'Restore conversation', value: { ...base, kind: 'rewind', mode: 'conversation' } },
99
+ ...(boundary.roller ? [{
100
+ label: 'Restore code', value: { ...base, kind: 'rewind', mode: 'code' },
101
+ }] : []),
102
+ { label: 'Never mind', value: { kind: 'open-rewind' } },
103
+ ];
104
+ return { cursor: 0, kind: 'list', options, purpose: 'rewind-action', title: boundary.prompt };
105
+ }
106
+ /** Read a fork's durable source boundary without storing lineage separately. */
107
+ export function inheritedTurn(events, inheritedCount) {
108
+ for (let index = Math.min(inheritedCount, events.length) - 1; index >= 0; index--) {
109
+ const event = events[index];
110
+ if (event?.type === 'turn/end')
111
+ return event.data.turn;
112
+ }
113
+ return undefined;
114
+ }
@@ -0,0 +1,3 @@
1
+ import type { TerminalCell } from './state.js';
2
+ /** Format the existing terminal projection as a portable Markdown transcript. */
3
+ export declare function markdownTranscript(sessionId: string, cwd: string, title: string | undefined, cells: readonly TerminalCell[]): string;
@@ -0,0 +1,27 @@
1
+ function heading(kind) {
2
+ return kind === 'user' ? 'User'
3
+ : kind === 'assistant' ? 'Assistant'
4
+ : kind === 'reasoning' ? 'Reasoning'
5
+ : kind[0]?.toUpperCase() + kind.slice(1);
6
+ }
7
+ function body(cell) {
8
+ const parts = [cell.text];
9
+ if (cell.detail !== undefined && cell.detail !== cell.text)
10
+ parts.push(cell.detail);
11
+ if (cell.tool?.body !== undefined)
12
+ parts.push(cell.tool.body);
13
+ for (const diff of cell.tool?.diffs ?? []) {
14
+ parts.push(`${diff.path} (+${String(diff.added)} -${String(diff.removed)})`, ...diff.lines);
15
+ }
16
+ return parts.filter(Boolean).join('\n\n');
17
+ }
18
+ /** Format the existing terminal projection as a portable Markdown transcript. */
19
+ export function markdownTranscript(sessionId, cwd, title, cells) {
20
+ const sections = cells.filter(cell => cell.pending !== true).map(cell => `## ${heading(cell.kind)}\n\n${body(cell)}`);
21
+ return [
22
+ `# ${title ?? 'Untitled DSH session'}`,
23
+ `Session: \`${sessionId}\` `,
24
+ `Working directory: \`${cwd}\``,
25
+ ...sections,
26
+ ].join('\n\n').trimEnd() + '\n';
27
+ }
@@ -0,0 +1,15 @@
1
+ import type { SessionSummary } from '@deepseek-ai/dsh-api-session-controller';
2
+ export interface SessionListOptions {
3
+ readonly all: boolean;
4
+ readonly cwd?: string;
5
+ readonly json: boolean;
6
+ }
7
+ /** Parse only the profile-provided `sessions list` grammar. */
8
+ export declare function parseSessionListArgs(args: readonly string[]): SessionListOptions | undefined;
9
+ export declare function sessionTitle(item: SessionSummary): string | undefined;
10
+ export declare function isSessionId(value: string): boolean;
11
+ /** Resolve one native title exactly, then by case-insensitive substring. */
12
+ export declare function sessionMatches(items: readonly SessionSummary[], value: string, cwd?: string): readonly SessionSummary[];
13
+ export declare function sessionNotFound(value: string): Error;
14
+ /** Format ordinary roots from DSH's native catalog without adding live-process facts. */
15
+ export declare function formatSessionList(items: readonly SessionSummary[], cwd: string | undefined, json: boolean): string;
@@ -0,0 +1,65 @@
1
+ /** Parse only the profile-provided `sessions list` grammar. */
2
+ export function parseSessionListArgs(args) {
3
+ if (args[0] !== 'sessions' || args[1] !== 'list')
4
+ return undefined;
5
+ let all = false;
6
+ let cwd;
7
+ let json = false;
8
+ for (let index = 2; index < args.length; index++) {
9
+ const argument = args[index];
10
+ if (argument === '--all' && !all && cwd === undefined)
11
+ all = true;
12
+ else if (argument === '--json' && !json)
13
+ json = true;
14
+ else if (argument === '--cwd' && cwd === undefined && !all) {
15
+ cwd = args[++index];
16
+ if (cwd === undefined || cwd === '')
17
+ return undefined;
18
+ }
19
+ else
20
+ return undefined;
21
+ }
22
+ return { all, json, ...(cwd === undefined ? {} : { cwd }) };
23
+ }
24
+ export function sessionTitle(item) {
25
+ const title = item.projections?.values.title;
26
+ return typeof title === 'string' && title !== '' ? title : undefined;
27
+ }
28
+ export function isSessionId(value) {
29
+ return /^session-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(value);
30
+ }
31
+ /** Resolve one native title exactly, then by case-insensitive substring. */
32
+ export function sessionMatches(items, value, cwd) {
33
+ const scoped = items.filter(item => item.origin === undefined && (cwd === undefined || item.cwd === cwd));
34
+ const exact = scoped.filter(item => sessionTitle(item) === value);
35
+ return exact.length > 0 ? exact : scoped.filter(item => sessionTitle(item)?.toLocaleLowerCase().includes(value.toLocaleLowerCase()));
36
+ }
37
+ export function sessionNotFound(value) {
38
+ return new Error(`no session named ${JSON.stringify(value)}`);
39
+ }
40
+ function terminalJson(value, space) {
41
+ return JSON.stringify(value, undefined, space).replace(/[\u0080-\u009f]/gu, character => `\\u${(character.codePointAt(0) ?? 0).toString(16).padStart(4, '0')}`);
42
+ }
43
+ function terminalField(value) {
44
+ return terminalJson(value).slice(1, -1);
45
+ }
46
+ /** Format ordinary roots from DSH's native catalog without adding live-process facts. */
47
+ export function formatSessionList(items, cwd, json) {
48
+ const sessions = items
49
+ .filter(item => item.origin === undefined && (cwd === undefined || item.cwd === cwd))
50
+ .map(item => ({
51
+ sessionId: String(item.sessionId),
52
+ title: sessionTitle(item) ?? null,
53
+ ...(item.cwd === undefined ? {} : { cwd: item.cwd }),
54
+ updatedAt: new Date(item.updatedAt).toISOString(),
55
+ }));
56
+ if (json)
57
+ return terminalJson({ version: 1, sessions }, 2);
58
+ const records = sessions.map(item => [
59
+ terminalField(item.sessionId), terminalField(item.title ?? 'Untitled'),
60
+ terminalField(item.cwd ?? 'cwd unavailable'), item.updatedAt,
61
+ ]);
62
+ const headers = ['UUID', 'TITLE', 'CWD', 'UPDATED'];
63
+ const widths = headers.map((header, index) => Math.max(header.length, ...records.map(row => row[index]?.length ?? 0)));
64
+ return [headers, ...records].map(row => row.map((value, index) => index === row.length - 1 ? value : value.padEnd(widths[index] ?? 0)).join(' ')).join('\n');
65
+ }
@@ -0,0 +1,49 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { ActivatableOverlayValue, DecisionAnswer, DraftAttachment, OverlayOption, PendingDecision, RootView, SendMode, TerminalCell, UiAction } from './state.js';
3
+ import type { TuiRoot } from './tui-root.js';
4
+ export interface RootLaunchOptions {
5
+ readonly agentPreset?: string;
6
+ readonly continue: boolean;
7
+ readonly appendSystemPrompt?: string;
8
+ readonly cwd: string;
9
+ readonly effort?: string;
10
+ readonly forkSession: boolean;
11
+ readonly name?: string;
12
+ readonly images?: readonly string[];
13
+ readonly model?: string;
14
+ readonly permission?: string;
15
+ readonly prompt?: string;
16
+ readonly provider?: string;
17
+ readonly tools?: readonly string[];
18
+ readonly disallowedTools?: readonly string[];
19
+ readonly resume?: string;
20
+ readonly sessionId?: string;
21
+ readonly systemPrompt?: string;
22
+ }
23
+ export declare class LaunchArgumentError extends Error {
24
+ }
25
+ export interface SessionRuntime {
26
+ readonly initialAttachments: readonly DraftAttachment[];
27
+ readonly initialCells: readonly TerminalCell[];
28
+ readonly initialHasMore: boolean;
29
+ readonly initialPrompts: readonly string[];
30
+ readonly root: RootView;
31
+ readonly statusLine: string;
32
+ readonly summary: string;
33
+ activate(value: ActivatableOverlayValue): Promise<void>;
34
+ attach(path: string): Promise<DraftAttachment | undefined>;
35
+ complete(query: string, caret: number): Promise<readonly OverlayOption[]>;
36
+ cyclePermission(): Promise<void>;
37
+ interrupt(): void;
38
+ loadHistory(rootId: string): Promise<void>;
39
+ search(query: string, rootId: string): Promise<void>;
40
+ shutdown(): Promise<void>;
41
+ start(): void;
42
+ submit(text: string, mode: SendMode, attachments: readonly DraftAttachment[]): Promise<void>;
43
+ }
44
+ export interface DecisionBindings {
45
+ readonly cancel: (id: string) => void;
46
+ readonly request: (decision: PendingDecision) => Promise<DecisionAnswer>;
47
+ }
48
+ /** Create or resume the exact current root and own its replaceable terminal binding. */
49
+ export declare function createSessionRuntime(ctx: Context, options: RootLaunchOptions, tuiRoot: TuiRoot, dispatch: (action: UiAction) => void, decisions?: DecisionBindings): Promise<SessionRuntime>;