@antst/dashi 0.1.0-alpha.10

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/lib/application.d.ts +43 -0
  4. package/lib/application.js +273 -0
  5. package/lib/at-trigger.d.ts +8 -0
  6. package/lib/at-trigger.js +13 -0
  7. package/lib/catalogs.d.ts +25 -0
  8. package/lib/catalogs.js +235 -0
  9. package/lib/clipboard-image.d.ts +3 -0
  10. package/lib/clipboard-image.js +74 -0
  11. package/lib/completion-trigger.d.ts +2 -0
  12. package/lib/completion-trigger.js +7 -0
  13. package/lib/diff-view.d.ts +5 -0
  14. package/lib/diff-view.js +24 -0
  15. package/lib/external-editor.d.ts +3 -0
  16. package/lib/external-editor.js +39 -0
  17. package/lib/help.d.ts +2 -0
  18. package/lib/help.js +6 -0
  19. package/lib/history-records.d.ts +4 -0
  20. package/lib/history-records.js +14 -0
  21. package/lib/image-input.d.ts +8 -0
  22. package/lib/image-input.js +37 -0
  23. package/lib/index.d.ts +4 -0
  24. package/lib/index.js +301 -0
  25. package/lib/input.d.ts +10 -0
  26. package/lib/input.js +222 -0
  27. package/lib/memory.d.ts +3 -0
  28. package/lib/memory.js +27 -0
  29. package/lib/mouse-input.d.ts +1 -0
  30. package/lib/mouse-input.js +6 -0
  31. package/lib/presentation.d.ts +9 -0
  32. package/lib/presentation.js +54 -0
  33. package/lib/renderer.d.ts +20 -0
  34. package/lib/renderer.js +435 -0
  35. package/lib/rewind.d.ts +18 -0
  36. package/lib/rewind.js +84 -0
  37. package/lib/session-export.d.ts +3 -0
  38. package/lib/session-export.js +27 -0
  39. package/lib/session-list.d.ts +15 -0
  40. package/lib/session-list.js +65 -0
  41. package/lib/session-runtime.d.ts +46 -0
  42. package/lib/session-runtime.js +1293 -0
  43. package/lib/shell-command.d.ts +10 -0
  44. package/lib/shell-command.js +65 -0
  45. package/lib/state.d.ts +508 -0
  46. package/lib/state.js +667 -0
  47. package/lib/terminal-guard.d.ts +14 -0
  48. package/lib/terminal-guard.js +76 -0
  49. package/lib/tool-presentation.d.ts +14 -0
  50. package/lib/tool-presentation.js +156 -0
  51. package/lib/transcript.d.ts +12 -0
  52. package/lib/transcript.js +323 -0
  53. package/lib/tui-root.d.ts +20 -0
  54. package/lib/tui-root.js +25 -0
  55. package/package.json +73 -0
  56. package/validated-dsh-versions.json +3 -0
package/lib/input.js ADDED
@@ -0,0 +1,222 @@
1
+ import { isKeyRelease, matchesKey } from '@earendil-works/pi-tui';
2
+ export function caretOffset(text, cursor) {
3
+ const lines = text.split('\n');
4
+ let offset = 0;
5
+ for (let line = 0; line < cursor.line; line++)
6
+ offset += (lines[line]?.length ?? 0) + 1;
7
+ return Math.min(text.length, offset + cursor.col);
8
+ }
9
+ export function installInput(tui, bindings) {
10
+ return tui.addInputListener((data) => {
11
+ if (data.includes('\u001B[200~')) {
12
+ const state = bindings.readState();
13
+ const decision = state.decisions[0];
14
+ const question = decision?.kind === 'question' ? decision.questions[decision.index] : undefined;
15
+ const customQuestion = question !== undefined && decision?.kind === 'question'
16
+ && decision.cursor === question.options.length;
17
+ const completion = state.overlay?.kind === 'list' && state.overlay.purpose === 'completion';
18
+ return customQuestion || state.search !== undefined || completion
19
+ || decision === undefined && state.overlay === undefined ? undefined : { consume: true };
20
+ }
21
+ if (isKeyRelease(data))
22
+ return { consume: true };
23
+ const state = bindings.readState();
24
+ const ctrlC = matchesKey(data, 'ctrl+c');
25
+ const ctrlD = matchesKey(data, 'ctrl+d');
26
+ if ((state.rewindArmed && !matchesKey(data, 'escape')) || (state.exitArmed && !ctrlC && !ctrlD)) {
27
+ bindings.dispatch({ type: 'disarm-exit' });
28
+ }
29
+ if (ctrlC) {
30
+ bindings.dispatch({ type: 'ctrl-c' });
31
+ return { consume: true };
32
+ }
33
+ const decision = bindings.readState().decisions[0];
34
+ if (decision !== undefined) {
35
+ const question = decision.kind === 'question' ? decision.questions[decision.index] : undefined;
36
+ const custom = question !== undefined && decision.cursor === question.options.length;
37
+ if (matchesKey(data, 'escape'))
38
+ bindings.dispatch({ type: 'decision-cancel' });
39
+ else if (matchesKey(data, 'up'))
40
+ bindings.dispatch({ type: 'decision-move', offset: -1 });
41
+ else if (matchesKey(data, 'down'))
42
+ bindings.dispatch({ type: 'decision-move', offset: 1 });
43
+ else if (matchesKey(data, 'enter'))
44
+ bindings.dispatch({ type: 'decision-submit' });
45
+ else if (custom)
46
+ return undefined;
47
+ else if (matchesKey(data, 'space'))
48
+ bindings.dispatch({ type: 'decision-toggle' });
49
+ else {
50
+ for (let number = 1; number <= 9; number++) {
51
+ if (!matchesKey(data, String(number)))
52
+ continue;
53
+ bindings.dispatch({ type: 'decision-number', number });
54
+ break;
55
+ }
56
+ }
57
+ return { consume: true };
58
+ }
59
+ if (matchesKey(data, 'f1')) {
60
+ bindings.dispatch({ type: 'help' });
61
+ return { consume: true };
62
+ }
63
+ const search = bindings.readState().search;
64
+ if (search !== undefined) {
65
+ if (matchesKey(data, 'escape'))
66
+ bindings.dispatch({ type: 'search-close' });
67
+ else if (matchesKey(data, 'enter') || matchesKey(data, 'down')) {
68
+ bindings.dispatch({ type: 'search-move', offset: 1 });
69
+ }
70
+ else if (matchesKey(data, 'shift+enter') || matchesKey(data, 'up')) {
71
+ bindings.dispatch({ type: 'search-move', offset: -1 });
72
+ }
73
+ else
74
+ return undefined;
75
+ return { consume: true };
76
+ }
77
+ if (matchesKey(data, 'ctrl+f')) {
78
+ bindings.dispatch({ type: 'search-open', scope: 'transcript' });
79
+ return { consume: true };
80
+ }
81
+ if (matchesKey(data, 'ctrl+b')) {
82
+ bindings.dispatch({ type: 'open-details' });
83
+ return { consume: true };
84
+ }
85
+ if (matchesKey(data, 'shift+tab')) {
86
+ bindings.dispatch({ type: 'cycle-permission' });
87
+ return { consume: true };
88
+ }
89
+ const overlay = bindings.readState().overlay;
90
+ if (overlay !== undefined) {
91
+ if (matchesKey(data, 'escape'))
92
+ bindings.dispatch({ type: 'overlay-close' });
93
+ else if (overlay.kind === 'history' && matchesKey(data, 'up'))
94
+ bindings.dispatch({ type: 'history-move', offset: -1 });
95
+ else if (overlay.kind === 'history' && matchesKey(data, 'down'))
96
+ bindings.dispatch({ type: 'history-move', offset: 1 });
97
+ else if (overlay.kind === 'history' && matchesKey(data, '/'))
98
+ bindings.dispatch({ type: 'search-open', scope: 'history' });
99
+ else if (overlay.kind === 'history' && matchesKey(data, 'y'))
100
+ bindings.dispatch({ type: 'history-copy' });
101
+ else if (matchesKey(data, 'up'))
102
+ bindings.dispatch({ type: 'overlay-move', offset: -1 });
103
+ else if (matchesKey(data, 'down'))
104
+ bindings.dispatch({ type: 'overlay-move', offset: 1 });
105
+ else if (matchesKey(data, 'ctrl+o'))
106
+ bindings.dispatch({ type: 'toggle-tool-mode' });
107
+ else if (matchesKey(data, 'enter'))
108
+ bindings.dispatch({ type: 'overlay-submit', execute: true });
109
+ else if (matchesKey(data, 'tab'))
110
+ bindings.dispatch({ type: 'overlay-submit' });
111
+ else if (overlay.kind === 'list' && overlay.purpose === 'completion') {
112
+ bindings.dispatch({ type: 'overlay-close' });
113
+ return undefined;
114
+ }
115
+ else {
116
+ for (let number = 1; number <= 9; number++) {
117
+ if (!matchesKey(data, String(number)))
118
+ continue;
119
+ bindings.dispatch({ type: 'overlay-number', number });
120
+ break;
121
+ }
122
+ }
123
+ return { consume: true };
124
+ }
125
+ if (matchesKey(data, 'ctrl+v')) {
126
+ bindings.dispatch({ type: 'clipboard-paste' });
127
+ return { consume: true };
128
+ }
129
+ if (matchesKey(data, 'pageUp')) {
130
+ bindings.dispatch(bindings.readState().inline
131
+ ? { type: 'open-history' }
132
+ : { type: 'scroll', direction: 'page-up' });
133
+ return { consume: true };
134
+ }
135
+ if (matchesKey(data, 'pageDown')) {
136
+ bindings.dispatch({ type: 'scroll', direction: 'page-down' });
137
+ return { consume: true };
138
+ }
139
+ if (matchesKey(data, 'ctrl+home')) {
140
+ bindings.dispatch(bindings.readState().inline
141
+ ? { type: 'open-history' }
142
+ : { type: 'scroll', direction: 'home' });
143
+ return { consume: true };
144
+ }
145
+ if (matchesKey(data, 'ctrl+end')) {
146
+ bindings.dispatch({ type: 'scroll', direction: 'end' });
147
+ return { consume: true };
148
+ }
149
+ if (matchesKey(data, 'tab')) {
150
+ const state = bindings.readState();
151
+ const cursor = bindings.readComposerCursor?.() ?? { col: state.composer.length, line: 0 };
152
+ bindings.dispatch({ type: 'request-completion', caret: caretOffset(state.composer, cursor) });
153
+ return { consume: true };
154
+ }
155
+ if (matchesKey(data, 'ctrl+r')) {
156
+ bindings.dispatch({ type: 'request-search' });
157
+ return { consume: true };
158
+ }
159
+ if (matchesKey(data, 'up') && !bindings.readState().composer.includes('\n')) {
160
+ bindings.dispatch({ type: 'recall-move', offset: -1 });
161
+ return { consume: true };
162
+ }
163
+ if (matchesKey(data, 'down') && bindings.readState().recall !== undefined) {
164
+ bindings.dispatch({ type: 'recall-move', offset: 1 });
165
+ return { consume: true };
166
+ }
167
+ if (matchesKey(data, 'ctrl+o')) {
168
+ bindings.dispatch({ type: 'toggle-tool-mode' });
169
+ return { consume: true };
170
+ }
171
+ if (matchesKey(data, 'ctrl+t')) {
172
+ bindings.dispatch({ type: 'toggle-send-mode' });
173
+ return { consume: true };
174
+ }
175
+ if (matchesKey(data, 'ctrl+s')) {
176
+ const state = bindings.readState();
177
+ bindings.dispatch({
178
+ type: 'stash-toggle', cursor: bindings.readComposerCursor?.() ?? { col: state.composer.length, line: 0 },
179
+ });
180
+ return { consume: true };
181
+ }
182
+ if (matchesKey(data, 'ctrl+g')) {
183
+ bindings.dispatch({ type: 'external-edit' });
184
+ return { consume: true };
185
+ }
186
+ if (matchesKey(data, 'backspace')) {
187
+ const state = bindings.readState();
188
+ if (state.composer === '' && state.attachments.length > 0) {
189
+ bindings.dispatch({ type: 'attachment-remove', index: state.attachments.length - 1 });
190
+ return { consume: true };
191
+ }
192
+ }
193
+ if (data === '\n' || matchesKey(data, 'ctrl+j')
194
+ || matchesKey(data, 'shift+enter') || matchesKey(data, 'alt+enter')) {
195
+ bindings.insertNewline?.();
196
+ return { consume: true };
197
+ }
198
+ if (matchesKey(data, 'enter')) {
199
+ bindings.dispatch({ type: 'submit' });
200
+ return { consume: true };
201
+ }
202
+ if (ctrlD && bindings.readState().composer === '') {
203
+ bindings.dispatch({ type: 'ctrl-d' });
204
+ return { consume: true };
205
+ }
206
+ if (matchesKey(data, 'ctrl+l')) {
207
+ bindings.dispatch({ type: 'redraw' });
208
+ return { consume: true };
209
+ }
210
+ if (matchesKey(data, 'ctrl+z')) {
211
+ bindings.dispatch({ type: 'suspend' });
212
+ return { consume: true };
213
+ }
214
+ if (matchesKey(data, 'escape')) {
215
+ bindings.dispatch({ type: 'escape' });
216
+ return { consume: true };
217
+ }
218
+ if (bindings.readState().exitArmed)
219
+ bindings.dispatch({ type: 'disarm-exit' });
220
+ return undefined;
221
+ });
222
+ }
@@ -0,0 +1,3 @@
1
+ import type { Agent } from '@deepseek-ai/dsh-agent';
2
+ import type { Overlay } from './state.js';
3
+ export declare function memoryOverlay(agent: Agent): Overlay;
package/lib/memory.js ADDED
@@ -0,0 +1,27 @@
1
+ import { resolve } from 'node:path';
2
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
3
+ export function memoryOverlay(agent) {
4
+ const cwd = agent.session.header.cwd ?? process.cwd();
5
+ let root = cwd;
6
+ const active = new Map();
7
+ for (const seq of agent.session.surface.nodes) {
8
+ const event = agent.session.eventAt(seq);
9
+ if (event?.type !== 'user/message' || event.data.source.kind !== 'agent-instructions')
10
+ continue;
11
+ const source = event.data.source, identity = source.baselineIdentity === undefined
12
+ ? undefined : JSON.parse(source.baselineIdentity);
13
+ if (typeof identity?.projectRoot === 'string')
14
+ root = resolve(cwd, identity.projectRoot);
15
+ for (const change of source.changes) {
16
+ if (change.action === 'remove')
17
+ active.delete(change.scope);
18
+ else
19
+ active.set(change.scope, { path: change.path, scope: change.scope.split('\0')[0] ?? change.scope });
20
+ }
21
+ }
22
+ return { cursor: 0, kind: 'list', options: [...active.values()].map(item => {
23
+ const homePath = /^(?:~\/\.dsh|\$DSH_HOME)\/(.*)$/u.exec(item.path)?.[1];
24
+ return { detail: `scope ${item.scope}`, label: item.path,
25
+ value: { kind: 'open-file', path: resolve(homePath === undefined ? root : resolveDshHome(), homePath ?? item.path) } };
26
+ }), purpose: 'memory', title: 'Memory' };
27
+ }
@@ -0,0 +1 @@
1
+ export declare function filterWheelInput(data: string): -3 | 3 | string;
@@ -0,0 +1,6 @@
1
+ export function filterWheelInput(data) {
2
+ if (!data.startsWith('\u001B[<'))
3
+ return data;
4
+ const match = /^(64|65);\d+;\d+M$/u.exec(data.slice(3));
5
+ return match?.[1] === '64' ? 3 : match?.[1] === '65' ? -3 : data;
6
+ }
@@ -0,0 +1,9 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { SessionJob } from '@deepseek-ai/dsh-api-session-controller';
3
+ import type { SessionId } from '@deepseek-ai/dsh-session';
4
+ import type { JobView, SubagentView } from './state.js';
5
+ /** Web-equivalent context occupancy; undefined until both DSH projection fields exist. */
6
+ export declare function contextPercent(value: unknown): number | undefined;
7
+ export declare function jobViews(jobs: readonly SessionJob[]): readonly JobView[];
8
+ /** Read DSH's direct-child catalog and projection-backed summary facts. */
9
+ export declare function subagentViews(ctx: Context, parentId: SessionId, signal: AbortSignal): Promise<readonly SubagentView[]>;
@@ -0,0 +1,54 @@
1
+ /** Web-equivalent context occupancy; undefined until both DSH projection fields exist. */
2
+ export function contextPercent(value) {
3
+ if (typeof value !== 'object' || value === null)
4
+ return undefined;
5
+ const pressure = value;
6
+ const used = typeof pressure.projectedTokens === 'number' ? pressure.projectedTokens : pressure.pressureTokens;
7
+ return typeof used !== 'number' || typeof pressure.contextWindow !== 'number'
8
+ ? undefined
9
+ : Math.min(100, Math.round(used / pressure.contextWindow * 100));
10
+ }
11
+ export function jobViews(jobs) {
12
+ return jobs.map(job => ({
13
+ id: String(job.id), kind: job.kind, label: job.label, startedAt: job.startedAt, status: job.status,
14
+ ...(job.detail === undefined ? {} : { detail: job.detail }),
15
+ ...(job.finishedAt === undefined ? {} : { finishedAt: job.finishedAt }),
16
+ }));
17
+ }
18
+ function summaryTitle(summary) {
19
+ const title = summary?.projections?.values.title;
20
+ return typeof title === 'string' && title !== '' ? title : undefined;
21
+ }
22
+ function tokenTotal(summary) {
23
+ const usage = summary?.projections?.values.tokenUsage;
24
+ return usage === undefined ? undefined
25
+ : usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
26
+ }
27
+ /** Read DSH's direct-child catalog and projection-backed summary facts. */
28
+ export async function subagentViews(ctx, parentId, signal) {
29
+ const subagents = ctx.get('subagents');
30
+ if (subagents === undefined)
31
+ return [];
32
+ const [catalog, listed] = await Promise.all([
33
+ subagents.remoteExportList(parentId, signal),
34
+ ctx.sessionController.list({}, signal),
35
+ ]);
36
+ const summaries = new Map(listed.items.map(summary => [String(summary.sessionId), summary]));
37
+ return catalog.entries.flatMap((entry) => {
38
+ if (entry.kind !== 'child')
39
+ return [];
40
+ const summary = summaries.get(String(entry.id));
41
+ const timing = summary?.projections?.values.subagentTiming;
42
+ const title = summaryTitle(summary);
43
+ const tokens = tokenTotal(summary);
44
+ return [{
45
+ id: String(entry.id), label: entry.label ?? String(entry.id), mode: entry.mode, state: entry.activity,
46
+ ...(timing === undefined ? {} : {
47
+ elapsedMs: timing.settledMs,
48
+ ...(timing.active === undefined ? {} : { active: timing.active }),
49
+ }),
50
+ ...(title === undefined ? {} : { summary: title }),
51
+ ...(tokens === undefined ? {} : { tokens }),
52
+ }];
53
+ });
54
+ }
@@ -0,0 +1,20 @@
1
+ import { type Terminal } from '@earendil-works/pi-tui';
2
+ import { type InputBindings } from './input.js';
3
+ import type { ComposerCursor } from './state.js';
4
+ import { type TerminalSession } from './terminal-guard.js';
5
+ export type CopyResult = 'ok' | 'too-large' | 'unsupported';
6
+ export interface Renderer extends TerminalSession {
7
+ bell(): void;
8
+ copy(text: string): CopyResult;
9
+ discardSecretComposer(): void;
10
+ materializedCells(): number;
11
+ setComposer(text: string, cursor?: ComposerCursor): void;
12
+ }
13
+ export interface RendererOptions extends InputBindings {
14
+ readonly accessible?: boolean;
15
+ readonly inline: boolean;
16
+ readonly terminal?: Terminal;
17
+ readonly terminalType?: string;
18
+ }
19
+ export declare function supportsOsc52(terminalType: string | undefined): boolean;
20
+ export declare function createRenderer(options: RendererOptions): Renderer;