@antst/dashi 0.1.0-alpha.19 → 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.
- package/lib/application.d.ts +45 -0
- package/lib/application.js +273 -0
- package/lib/at-trigger.d.ts +8 -0
- package/lib/at-trigger.js +13 -0
- package/lib/catalogs.d.ts +28 -0
- package/lib/catalogs.js +308 -0
- package/lib/clipboard-image.d.ts +3 -0
- package/lib/clipboard-image.js +74 -0
- package/lib/completion-trigger.d.ts +6 -0
- package/lib/completion-trigger.js +19 -0
- package/lib/diff-view.d.ts +5 -0
- package/lib/diff-view.js +30 -0
- package/lib/external-editor.d.ts +3 -0
- package/lib/external-editor.js +39 -0
- package/lib/help.d.ts +2 -0
- package/lib/help.js +6 -0
- package/lib/history-records.d.ts +4 -0
- package/lib/history-records.js +4 -0
- package/lib/image-input.d.ts +8 -0
- package/lib/image-input.js +37 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +325 -0
- package/lib/input.d.ts +17 -0
- package/lib/input.js +267 -0
- package/lib/memory.d.ts +3 -0
- package/lib/memory.js +27 -0
- package/lib/mouse-input.d.ts +1 -0
- package/lib/mouse-input.js +6 -0
- package/lib/presentation.d.ts +9 -0
- package/lib/presentation.js +54 -0
- package/lib/renderer.d.ts +21 -0
- package/lib/renderer.js +491 -0
- package/lib/rewind.d.ts +31 -0
- package/lib/rewind.js +114 -0
- package/lib/session-export.d.ts +3 -0
- package/lib/session-export.js +27 -0
- package/lib/session-list.d.ts +15 -0
- package/lib/session-list.js +65 -0
- package/lib/session-runtime.d.ts +49 -0
- package/lib/session-runtime.js +1492 -0
- package/lib/shell-command.d.ts +10 -0
- package/lib/shell-command.js +66 -0
- package/lib/state.d.ts +517 -0
- package/lib/state.js +685 -0
- package/lib/terminal-guard.d.ts +14 -0
- package/lib/terminal-guard.js +76 -0
- package/lib/tool-presentation.d.ts +14 -0
- package/lib/tool-presentation.js +156 -0
- package/lib/transcript.d.ts +14 -0
- package/lib/transcript.js +336 -0
- package/lib/tui-root.d.ts +20 -0
- package/lib/tui-root.js +25 -0
- package/package.json +1 -1
package/lib/index.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { exitOnStdinEnd } from '@deepseek-ai/dsh-cmdline';
|
|
5
|
+
import { createTerminalShell } from './application.js';
|
|
6
|
+
import { sessionOverlay } from './catalogs.js';
|
|
7
|
+
import { readClipboardImage } from './clipboard-image.js';
|
|
8
|
+
import { editDraftExternally } from './external-editor.js';
|
|
9
|
+
import { FLAG_HELP, VERSION_LINE } from './help.js';
|
|
10
|
+
import { formatSessionList, isSessionId, parseSessionListArgs, sessionMatches, sessionNotFound, } from './session-list.js';
|
|
11
|
+
import { createSessionRuntime, LaunchArgumentError, } from './session-runtime.js';
|
|
12
|
+
import { TuiRoot } from './tui-root.js';
|
|
13
|
+
export const name = 'dashi';
|
|
14
|
+
export const inject = [
|
|
15
|
+
'agentPresets', 'agents', 'attachments', 'authorization', 'cmdlineArgs', 'commands', 'credentials', 'fs', 'jobs', 'loader', 'permissionPresets', 'subagents',
|
|
16
|
+
'pluginInventory', 'sessionController', 'sandboxPolicy', 'sessionProjections', 'sessionQuery', 'sessions', 'settings', 'shell', 'skills', 'systemPrompt', 'tools',
|
|
17
|
+
];
|
|
18
|
+
function parseArgs(args) {
|
|
19
|
+
let accessible = false;
|
|
20
|
+
let argumentError;
|
|
21
|
+
let agentPreset, sessionId;
|
|
22
|
+
let forkSession = false;
|
|
23
|
+
let inline = true;
|
|
24
|
+
let name;
|
|
25
|
+
let effort;
|
|
26
|
+
let model;
|
|
27
|
+
let permission;
|
|
28
|
+
let provider;
|
|
29
|
+
let tools;
|
|
30
|
+
let disallowedTools;
|
|
31
|
+
let resume;
|
|
32
|
+
let resumeAll = false;
|
|
33
|
+
let resumePicker = false;
|
|
34
|
+
let systemPrompt, appendSystemPrompt;
|
|
35
|
+
let useContinue = false;
|
|
36
|
+
let verbose = false;
|
|
37
|
+
const images = [];
|
|
38
|
+
const prompt = [];
|
|
39
|
+
for (let index = 0; index < args.length; index++) {
|
|
40
|
+
const argument = args[index];
|
|
41
|
+
if (argument === '--accessible')
|
|
42
|
+
accessible = true;
|
|
43
|
+
else if (argument === '--inline')
|
|
44
|
+
inline = true;
|
|
45
|
+
else if (argument === '--fullscreen')
|
|
46
|
+
inline = false;
|
|
47
|
+
else if (['--name', '-n', '--agent', '--image', '--effort', '--model', '--permission', '--provider', '--tools', '--disallowedTools',
|
|
48
|
+
'--system-prompt', '--system-prompt-file', '--append-system-prompt', '--append-system-prompt-file',
|
|
49
|
+
'--session-id'].includes(argument ?? '')) {
|
|
50
|
+
const value = args[++index];
|
|
51
|
+
if (value === undefined || value === '')
|
|
52
|
+
return undefined;
|
|
53
|
+
if (argument === '--name' || argument === '-n')
|
|
54
|
+
name = value;
|
|
55
|
+
else if (argument === '--agent')
|
|
56
|
+
agentPreset = value;
|
|
57
|
+
else if (argument === '--image')
|
|
58
|
+
images.push(value);
|
|
59
|
+
else if (argument === '--effort')
|
|
60
|
+
effort = value;
|
|
61
|
+
else if (argument === '--model')
|
|
62
|
+
model = value;
|
|
63
|
+
else if (argument === '--permission')
|
|
64
|
+
permission = value;
|
|
65
|
+
else if (argument === '--session-id')
|
|
66
|
+
sessionId = value;
|
|
67
|
+
else if (argument === '--tools' || argument === '--disallowedTools') {
|
|
68
|
+
const names = value.split(',').map(name => name.trim());
|
|
69
|
+
if (names.includes(''))
|
|
70
|
+
return undefined;
|
|
71
|
+
if (argument === '--tools')
|
|
72
|
+
tools = names;
|
|
73
|
+
else
|
|
74
|
+
disallowedTools = names;
|
|
75
|
+
}
|
|
76
|
+
else if (argument?.includes('system-prompt') === true) {
|
|
77
|
+
let text = value;
|
|
78
|
+
if (argument.endsWith('-file')) {
|
|
79
|
+
try {
|
|
80
|
+
text = readFileSync(value, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
argumentError = error instanceof Error ? error.message : String(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (argument.startsWith('--append'))
|
|
87
|
+
appendSystemPrompt = text;
|
|
88
|
+
else
|
|
89
|
+
systemPrompt = text;
|
|
90
|
+
}
|
|
91
|
+
else
|
|
92
|
+
provider = value;
|
|
93
|
+
}
|
|
94
|
+
else if (argument === '--resume' || argument === '-r') {
|
|
95
|
+
const value = args[index + 1];
|
|
96
|
+
if (value === '')
|
|
97
|
+
return undefined;
|
|
98
|
+
if (value === undefined || value.startsWith('-'))
|
|
99
|
+
resumePicker = true;
|
|
100
|
+
else
|
|
101
|
+
resume = args[++index];
|
|
102
|
+
}
|
|
103
|
+
else if (argument === '--all')
|
|
104
|
+
resumeAll = true;
|
|
105
|
+
else if (argument === '--continue' || argument === '-c')
|
|
106
|
+
useContinue = true;
|
|
107
|
+
else if (argument === '--fork-session')
|
|
108
|
+
forkSession = true;
|
|
109
|
+
else if (argument === '--verbose')
|
|
110
|
+
verbose = true;
|
|
111
|
+
else if (argument === '--yolo' || argument === '--dangerously-skip-permissions') {
|
|
112
|
+
permission = 'danger-full-access';
|
|
113
|
+
}
|
|
114
|
+
else if (argument?.startsWith('-') === true)
|
|
115
|
+
return undefined;
|
|
116
|
+
else if (argument !== undefined)
|
|
117
|
+
prompt.push(argument);
|
|
118
|
+
}
|
|
119
|
+
if (resume !== undefined && (resumePicker || useContinue) || resumePicker && useContinue
|
|
120
|
+
|| name !== undefined && (resume !== undefined || resumePicker || useContinue)
|
|
121
|
+
|| resumeAll && resume === undefined && !resumePicker || resumeAll && useContinue
|
|
122
|
+
|| provider !== undefined && model === undefined
|
|
123
|
+
|| sessionId !== undefined && (!isSessionId(sessionId) || resume !== undefined || resumePicker || useContinue)
|
|
124
|
+
|| agentPreset !== undefined && (resume !== undefined || resumePicker || useContinue)
|
|
125
|
+
|| forkSession && resume === undefined && !resumePicker && !useContinue)
|
|
126
|
+
return undefined;
|
|
127
|
+
return {
|
|
128
|
+
accessible,
|
|
129
|
+
...(argumentError === undefined ? {} : { argumentError }),
|
|
130
|
+
...(appendSystemPrompt === undefined ? {} : { appendSystemPrompt }),
|
|
131
|
+
continue: useContinue,
|
|
132
|
+
...(agentPreset === undefined ? {} : { agentPreset }), forkSession, inline,
|
|
133
|
+
resumeAll,
|
|
134
|
+
resumePicker,
|
|
135
|
+
...(images.length === 0 ? {} : { images }),
|
|
136
|
+
...(effort === undefined ? {} : { effort }),
|
|
137
|
+
...(model === undefined ? {} : { model }),
|
|
138
|
+
...(name === undefined ? {} : { name }),
|
|
139
|
+
...(permission === undefined ? {} : { permission }),
|
|
140
|
+
...(prompt.length === 0 ? {} : { prompt: prompt.join(' ') }),
|
|
141
|
+
...(provider === undefined ? {} : { provider }),
|
|
142
|
+
...(tools === undefined ? {} : { tools }),
|
|
143
|
+
...(disallowedTools === undefined ? {} : { disallowedTools }),
|
|
144
|
+
...(resume === undefined ? {} : { resume }), ...(sessionId === undefined ? {} : { sessionId }),
|
|
145
|
+
...(systemPrompt === undefined ? {} : { systemPrompt }), verbose,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function packageVersion(path) {
|
|
149
|
+
const manifest = JSON.parse(readFileSync(path, 'utf8'));
|
|
150
|
+
return typeof manifest.version === 'string' ? manifest.version : 'unknown';
|
|
151
|
+
}
|
|
152
|
+
function runningDshVersions() {
|
|
153
|
+
try {
|
|
154
|
+
const entry = realpathSync(process.argv[1] ?? '');
|
|
155
|
+
const manifest = resolve(dirname(entry), '../package.json');
|
|
156
|
+
const version = packageVersion(manifest);
|
|
157
|
+
try {
|
|
158
|
+
return [version, packageVersion(createRequire(manifest).resolve('@deepseek-ai/dsh-base/package.json'))];
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return [version, 'unknown'];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return ['unknown', 'unknown'];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function warnDshVersion() {
|
|
169
|
+
const [version, baseVersion] = runningDshVersions();
|
|
170
|
+
if (baseVersion !== version) {
|
|
171
|
+
process.stderr.write(`dashi: warning: DSH ${version} loads @deepseek-ai/dsh-base ${baseVersion}; versions must match\n`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function installSessionList(ctx, options, ready, exit) {
|
|
175
|
+
const abort = new AbortController();
|
|
176
|
+
let listing;
|
|
177
|
+
const announce = ready.onReady(() => {
|
|
178
|
+
warnDshVersion();
|
|
179
|
+
listing = ctx.sessionController.list({}, abort.signal).then(({ items }) => {
|
|
180
|
+
const cwd = options.all ? undefined : resolve(options.cwd ?? process.cwd());
|
|
181
|
+
process.stdout.write(`${formatSessionList(items, cwd, options.json)}\n`);
|
|
182
|
+
exit(0);
|
|
183
|
+
}).catch((error) => {
|
|
184
|
+
process.stderr.write(`dashi: sessions list failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
185
|
+
exit(1);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
ctx.effect(() => async () => {
|
|
189
|
+
announce();
|
|
190
|
+
abort.abort();
|
|
191
|
+
await listing;
|
|
192
|
+
}, 'dashi.sessions-list');
|
|
193
|
+
}
|
|
194
|
+
export function apply(ctx) {
|
|
195
|
+
const exit = ctx.get('appExit');
|
|
196
|
+
const ready = ctx.get('appReady');
|
|
197
|
+
const cmdline = ctx.get('cmdlineArgs');
|
|
198
|
+
if (exit === undefined || ready === undefined || cmdline === undefined) {
|
|
199
|
+
throw new Error('dashi: the launcher must provide ctx.appExit, ctx.appReady, and ctx.cmdlineArgs');
|
|
200
|
+
}
|
|
201
|
+
const args = cmdline.get();
|
|
202
|
+
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
203
|
+
process.stdout.write(`${VERSION_LINE}\n\nUsage: dashi [options] [prompt]\n\n${FLAG_HELP.join('\n')}\n`);
|
|
204
|
+
exit(0);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (args[0] === 'sessions') {
|
|
208
|
+
const options = parseSessionListArgs(args);
|
|
209
|
+
if (options === undefined) {
|
|
210
|
+
process.stderr.write(`dashi: unsupported arguments: ${args.join(' ')}\n`);
|
|
211
|
+
exit(2);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
installSessionList(ctx, options, ready, exit);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const parsed = parseArgs(args);
|
|
218
|
+
if (parsed === undefined) {
|
|
219
|
+
process.stderr.write(`dashi: unsupported arguments: ${args.join(' ')}\n`);
|
|
220
|
+
exit(2);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const tuiRoot = new TuiRoot(ctx);
|
|
224
|
+
let shell;
|
|
225
|
+
let runtime;
|
|
226
|
+
let boot;
|
|
227
|
+
const stdoutError = (error) => { void shell?.fail(error); };
|
|
228
|
+
process.stdout.on('error', stdoutError);
|
|
229
|
+
const changed = ctx.on('tui/root-changed', (_previous, current) => {
|
|
230
|
+
if (current === undefined)
|
|
231
|
+
shell?.dispatch({ type: 'root-cleared' });
|
|
232
|
+
});
|
|
233
|
+
const announce = ready.onReady(() => {
|
|
234
|
+
warnDshVersion();
|
|
235
|
+
boot = (async () => {
|
|
236
|
+
if (parsed.argumentError !== undefined)
|
|
237
|
+
throw new LaunchArgumentError(parsed.argumentError);
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
const makeRuntime = (options) => createSessionRuntime(ctx, options, tuiRoot, action => { shell?.dispatch(action); }, {
|
|
240
|
+
cancel: id => { shell?.cancelDecision(id); },
|
|
241
|
+
request: decision => shell?.requestDecision(decision)
|
|
242
|
+
?? Promise.reject(new Error('dashi terminal is not ready')),
|
|
243
|
+
});
|
|
244
|
+
const sendPrompt = () => {
|
|
245
|
+
if (parsed.prompt !== undefined)
|
|
246
|
+
shell?.dispatch({ type: 'composer-changed', text: parsed.prompt });
|
|
247
|
+
if (parsed.prompt !== undefined)
|
|
248
|
+
shell?.dispatch({ type: 'submit' });
|
|
249
|
+
};
|
|
250
|
+
let pickerItems = parsed.resumePicker
|
|
251
|
+
? (await ctx.sessionController.list({}, new AbortController().signal)).items : undefined;
|
|
252
|
+
if (parsed.resume !== undefined && !isSessionId(parsed.resume)) {
|
|
253
|
+
const matches = sessionMatches((await ctx.sessionController.list({}, new AbortController().signal)).items, parsed.resume, parsed.resumeAll ? undefined : cwd);
|
|
254
|
+
const [match] = matches;
|
|
255
|
+
if (match === undefined)
|
|
256
|
+
throw sessionNotFound(parsed.resume);
|
|
257
|
+
if (matches.length > 1)
|
|
258
|
+
pickerItems = matches;
|
|
259
|
+
else
|
|
260
|
+
runtime = await makeRuntime({ ...parsed, cwd, resume: String(match.sessionId) });
|
|
261
|
+
}
|
|
262
|
+
else if (!parsed.resumePicker)
|
|
263
|
+
runtime = await makeRuntime({ ...parsed, cwd });
|
|
264
|
+
const initial = runtime;
|
|
265
|
+
shell = createTerminalShell({
|
|
266
|
+
accessible: parsed.accessible,
|
|
267
|
+
activateOverlay: async (value) => {
|
|
268
|
+
if (runtime !== undefined)
|
|
269
|
+
return runtime.activate(value);
|
|
270
|
+
if (value.kind !== 'resume')
|
|
271
|
+
return;
|
|
272
|
+
runtime = await makeRuntime({ ...parsed, continue: false, cwd, resume: value.sessionId });
|
|
273
|
+
shell?.dispatch({ type: 'root-bound', cells: runtime.initialCells, hasMore: runtime.initialHasMore,
|
|
274
|
+
prompts: runtime.initialPrompts, root: runtime.root });
|
|
275
|
+
for (const attachment of runtime.initialAttachments)
|
|
276
|
+
shell?.dispatch({ type: 'attachment-added', attachment });
|
|
277
|
+
runtime.start();
|
|
278
|
+
sendPrompt();
|
|
279
|
+
},
|
|
280
|
+
attach: path => runtime?.attach(path) ?? Promise.resolve(undefined),
|
|
281
|
+
beforeExit: () => runtime?.shutdown() ?? Promise.resolve(),
|
|
282
|
+
complete: (query, caret) => runtime?.complete(query, caret) ?? Promise.resolve([]),
|
|
283
|
+
cwd,
|
|
284
|
+
cyclePermission: () => runtime?.cyclePermission() ?? Promise.resolve(),
|
|
285
|
+
exit,
|
|
286
|
+
externalEdit: editDraftExternally,
|
|
287
|
+
...(initial === undefined ? {} : {
|
|
288
|
+
initialAttachments: initial.initialAttachments, initialCells: initial.initialCells, initialHasMore: initial.initialHasMore,
|
|
289
|
+
initialPrompts: initial.initialPrompts, initialRoot: initial.root,
|
|
290
|
+
}),
|
|
291
|
+
...(parsed.verbose ? { initialToolMode: 'expanded' } : {}),
|
|
292
|
+
inline: parsed.inline || parsed.accessible,
|
|
293
|
+
interrupt: () => { runtime?.interrupt(); },
|
|
294
|
+
loadHistory: rootId => runtime?.loadHistory(rootId) ?? Promise.resolve(),
|
|
295
|
+
onReleased: () => { process.stdout.write(`${runtime?.summary ?? ''}\n`); },
|
|
296
|
+
pasteImage: readClipboardImage,
|
|
297
|
+
search: (query, rootId) => runtime?.search(query, rootId) ?? Promise.resolve(),
|
|
298
|
+
statusLine: () => runtime?.statusLine,
|
|
299
|
+
submit: (text, mode, attachments) => runtime?.submit(text, mode, attachments),
|
|
300
|
+
});
|
|
301
|
+
shell.start();
|
|
302
|
+
if (pickerItems !== undefined) {
|
|
303
|
+
shell.dispatch({ type: 'open-overlay', overlay: sessionOverlay(pickerItems, '', parsed.resumeAll ? undefined : cwd) });
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
runtime?.start();
|
|
307
|
+
sendPrompt();
|
|
308
|
+
}
|
|
309
|
+
})().catch(async (error) => {
|
|
310
|
+
process.stderr.write(`dashi: startup failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
311
|
+
await runtime?.shutdown();
|
|
312
|
+
await shell?.dispose();
|
|
313
|
+
exit(error instanceof LaunchArgumentError ? 2 : 1);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
ctx.effect(() => async () => {
|
|
317
|
+
announce();
|
|
318
|
+
changed();
|
|
319
|
+
await boot;
|
|
320
|
+
await runtime?.shutdown();
|
|
321
|
+
await shell?.dispose();
|
|
322
|
+
process.stdout.off('error', stdoutError);
|
|
323
|
+
}, 'dashi.profile-boot');
|
|
324
|
+
exitOnStdinEnd(ctx, 'dashi.stdin');
|
|
325
|
+
}
|
package/lib/input.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type TUI } from '@earendil-works/pi-tui';
|
|
2
|
+
import { type ComposerCursor, type UiAction, type ViewState } from './state.js';
|
|
3
|
+
export interface InputBindings {
|
|
4
|
+
readonly dispatch: (action: UiAction) => void;
|
|
5
|
+
readonly insertNewline?: () => void;
|
|
6
|
+
readonly insertText?: (text: string) => void;
|
|
7
|
+
readonly readComposerCursor?: () => ComposerCursor;
|
|
8
|
+
readonly readState: () => ViewState;
|
|
9
|
+
}
|
|
10
|
+
export declare function droppedPaths(data: string, cwd: string, inspect?: (path: string) => {
|
|
11
|
+
isDirectory(): boolean;
|
|
12
|
+
}): {
|
|
13
|
+
readonly images: readonly string[];
|
|
14
|
+
readonly text: string;
|
|
15
|
+
} | undefined;
|
|
16
|
+
export declare function caretOffset(text: string, cursor: ComposerCursor): number;
|
|
17
|
+
export declare function installInput(tui: TUI, bindings: InputBindings): () => void;
|
package/lib/input.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { formatFileMention } from '@deepseek-ai/dsh-file-reference/grammar';
|
|
4
|
+
import { isKeyRelease, matchesKey } from '@earendil-works/pi-tui';
|
|
5
|
+
import { isImagePath } from './image-input.js';
|
|
6
|
+
import { PAGE_CELLS } from './state.js';
|
|
7
|
+
export function droppedPaths(data, cwd, inspect = statSync) {
|
|
8
|
+
const [start, end] = ['\u001B[200~', '\u001B[201~'];
|
|
9
|
+
if (!data.startsWith(start) || !data.endsWith(end))
|
|
10
|
+
return undefined;
|
|
11
|
+
const token = /(?:[^\s'\\]+|'[^']*'|\\[\s\S])+/gu;
|
|
12
|
+
const source = data.slice(start.length, -end.length);
|
|
13
|
+
const chunks = source.match(token) ?? [];
|
|
14
|
+
if (chunks.length === 0 || source.replace(token, '').trim() !== '')
|
|
15
|
+
return undefined;
|
|
16
|
+
const paths = chunks.map(chunk => chunk.replace(/'([^']*)'|\\([\s\S])/gu, (_match, quoted, escaped) => quoted ?? escaped ?? ''));
|
|
17
|
+
try {
|
|
18
|
+
const entries = paths.map(value => {
|
|
19
|
+
const absolute = resolve(cwd, value);
|
|
20
|
+
const directory = inspect(absolute).isDirectory();
|
|
21
|
+
if (!directory && isImagePath(absolute))
|
|
22
|
+
return { absolute };
|
|
23
|
+
const local = relative(resolve(cwd), absolute);
|
|
24
|
+
const mention = formatFileMention({ kind: directory ? 'directory' : 'file', path: local !== '' && !isAbsolute(local) && local !== '..' && !local.startsWith(`..${sep}`) ? local : absolute }, false);
|
|
25
|
+
if (mention === undefined)
|
|
26
|
+
throw new Error('path cannot be mentioned');
|
|
27
|
+
return { absolute, mention };
|
|
28
|
+
});
|
|
29
|
+
const mentions = entries.flatMap(entry => entry.mention === undefined ? [] : [entry.mention]);
|
|
30
|
+
return { images: entries.flatMap(entry => entry.mention === undefined ? [entry.absolute] : []), text: mentions.length === 0 ? '' : `${mentions.join(' ')} ` };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function caretOffset(text, cursor) {
|
|
37
|
+
const lines = text.split('\n');
|
|
38
|
+
let offset = 0;
|
|
39
|
+
for (let line = 0; line < cursor.line; line++)
|
|
40
|
+
offset += (lines[line]?.length ?? 0) + 1;
|
|
41
|
+
return Math.min(text.length, offset + cursor.col);
|
|
42
|
+
}
|
|
43
|
+
export function installInput(tui, bindings) {
|
|
44
|
+
return tui.addInputListener((data) => {
|
|
45
|
+
if (data.includes('\u001B[200~')) {
|
|
46
|
+
const state = bindings.readState();
|
|
47
|
+
const decision = state.decisions[0];
|
|
48
|
+
const question = decision?.kind === 'question' ? decision.questions[decision.index] : undefined;
|
|
49
|
+
const customQuestion = question !== undefined && decision?.kind === 'question'
|
|
50
|
+
&& decision.cursor === question.options.length;
|
|
51
|
+
const completion = state.overlay?.kind === 'list' && state.overlay.purpose === 'completion';
|
|
52
|
+
const dropped = decision === undefined && state.overlay === undefined && state.search === undefined && droppedPaths(data, state.root?.cwd ?? state.cwd);
|
|
53
|
+
if (dropped) {
|
|
54
|
+
bindings.insertText?.(dropped.text);
|
|
55
|
+
for (const path of dropped.images)
|
|
56
|
+
bindings.dispatch({ type: 'attachment-path', path });
|
|
57
|
+
return { consume: true };
|
|
58
|
+
}
|
|
59
|
+
return customQuestion || state.search !== undefined || completion
|
|
60
|
+
|| decision === undefined && state.overlay === undefined ? undefined : { consume: true };
|
|
61
|
+
}
|
|
62
|
+
if (isKeyRelease(data))
|
|
63
|
+
return { consume: true };
|
|
64
|
+
const state = bindings.readState();
|
|
65
|
+
const ctrlC = matchesKey(data, 'ctrl+c');
|
|
66
|
+
const ctrlD = matchesKey(data, 'ctrl+d');
|
|
67
|
+
if ((state.rewindArmed && !matchesKey(data, 'escape')) || (state.exitArmed && !ctrlC && !ctrlD)) {
|
|
68
|
+
bindings.dispatch({ type: 'disarm-exit' });
|
|
69
|
+
}
|
|
70
|
+
if (ctrlC) {
|
|
71
|
+
bindings.dispatch({ type: 'ctrl-c' });
|
|
72
|
+
return { consume: true };
|
|
73
|
+
}
|
|
74
|
+
const decision = bindings.readState().decisions[0];
|
|
75
|
+
if (decision !== undefined) {
|
|
76
|
+
const question = decision.kind === 'question' ? decision.questions[decision.index] : undefined;
|
|
77
|
+
const custom = question !== undefined && decision.cursor === question.options.length;
|
|
78
|
+
if (matchesKey(data, 'escape'))
|
|
79
|
+
bindings.dispatch({ type: 'decision-cancel' });
|
|
80
|
+
else if (matchesKey(data, 'up'))
|
|
81
|
+
bindings.dispatch({ type: 'decision-move', offset: -1 });
|
|
82
|
+
else if (matchesKey(data, 'down'))
|
|
83
|
+
bindings.dispatch({ type: 'decision-move', offset: 1 });
|
|
84
|
+
else if (matchesKey(data, 'enter'))
|
|
85
|
+
bindings.dispatch({ type: 'decision-submit' });
|
|
86
|
+
else if (custom)
|
|
87
|
+
return undefined;
|
|
88
|
+
else if (matchesKey(data, 'space'))
|
|
89
|
+
bindings.dispatch({ type: 'decision-toggle' });
|
|
90
|
+
else {
|
|
91
|
+
for (let number = 1; number <= 9; number++) {
|
|
92
|
+
if (!matchesKey(data, String(number)))
|
|
93
|
+
continue;
|
|
94
|
+
bindings.dispatch({ type: 'decision-number', number });
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { consume: true };
|
|
99
|
+
}
|
|
100
|
+
if (matchesKey(data, 'f1')) {
|
|
101
|
+
bindings.dispatch({ type: 'help' });
|
|
102
|
+
return { consume: true };
|
|
103
|
+
}
|
|
104
|
+
const search = bindings.readState().search;
|
|
105
|
+
if (search !== undefined) {
|
|
106
|
+
if (matchesKey(data, 'escape'))
|
|
107
|
+
bindings.dispatch({ type: 'search-close' });
|
|
108
|
+
else if (matchesKey(data, 'enter') || matchesKey(data, 'down')) {
|
|
109
|
+
bindings.dispatch({ type: 'search-move', offset: 1 });
|
|
110
|
+
}
|
|
111
|
+
else if (matchesKey(data, 'shift+enter') || matchesKey(data, 'up')) {
|
|
112
|
+
bindings.dispatch({ type: 'search-move', offset: -1 });
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
return undefined;
|
|
116
|
+
return { consume: true };
|
|
117
|
+
}
|
|
118
|
+
if (matchesKey(data, 'ctrl+f')) {
|
|
119
|
+
bindings.dispatch({ type: 'search-open', scope: 'transcript' });
|
|
120
|
+
return { consume: true };
|
|
121
|
+
}
|
|
122
|
+
if (matchesKey(data, 'ctrl+b')) {
|
|
123
|
+
bindings.dispatch({ type: 'open-details' });
|
|
124
|
+
return { consume: true };
|
|
125
|
+
}
|
|
126
|
+
if (matchesKey(data, 'shift+tab')) {
|
|
127
|
+
bindings.dispatch({ type: 'cycle-permission' });
|
|
128
|
+
return { consume: true };
|
|
129
|
+
}
|
|
130
|
+
const overlay = bindings.readState().overlay;
|
|
131
|
+
if (overlay !== undefined) {
|
|
132
|
+
if (matchesKey(data, 'escape'))
|
|
133
|
+
bindings.dispatch({ type: 'overlay-close' });
|
|
134
|
+
else if (overlay.kind === 'history' && matchesKey(data, 'up'))
|
|
135
|
+
bindings.dispatch({ type: 'history-move', offset: -1 });
|
|
136
|
+
else if (overlay.kind === 'history' && matchesKey(data, 'down'))
|
|
137
|
+
bindings.dispatch({ type: 'history-move', offset: 1 });
|
|
138
|
+
else if (overlay.kind === 'history' && matchesKey(data, '/'))
|
|
139
|
+
bindings.dispatch({ type: 'search-open', scope: 'history' });
|
|
140
|
+
else if (overlay.kind === 'history' && matchesKey(data, 'y'))
|
|
141
|
+
bindings.dispatch({ type: 'history-copy' });
|
|
142
|
+
else if (overlay.kind === 'info' && matchesKey(data, 'pageUp'))
|
|
143
|
+
bindings.dispatch({ type: 'overlay-move', offset: -PAGE_CELLS });
|
|
144
|
+
else if (overlay.kind === 'info' && matchesKey(data, 'pageDown'))
|
|
145
|
+
bindings.dispatch({ type: 'overlay-move', offset: PAGE_CELLS });
|
|
146
|
+
else if (matchesKey(data, 'up'))
|
|
147
|
+
bindings.dispatch({ type: 'overlay-move', offset: -1 });
|
|
148
|
+
else if (matchesKey(data, 'down'))
|
|
149
|
+
bindings.dispatch({ type: 'overlay-move', offset: 1 });
|
|
150
|
+
else if (matchesKey(data, 'ctrl+o'))
|
|
151
|
+
bindings.dispatch({ type: 'toggle-tool-mode' });
|
|
152
|
+
else if (matchesKey(data, 'enter'))
|
|
153
|
+
bindings.dispatch({ type: 'overlay-submit', execute: true });
|
|
154
|
+
else if (matchesKey(data, 'tab'))
|
|
155
|
+
bindings.dispatch({ type: 'overlay-submit' });
|
|
156
|
+
else if (overlay.kind === 'list' && overlay.purpose === 'completion') {
|
|
157
|
+
bindings.dispatch({ type: 'overlay-close' });
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
for (let number = 1; number <= 9; number++) {
|
|
162
|
+
if (!matchesKey(data, String(number)))
|
|
163
|
+
continue;
|
|
164
|
+
bindings.dispatch({ type: 'overlay-number', number });
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { consume: true };
|
|
169
|
+
}
|
|
170
|
+
if (matchesKey(data, 'ctrl+v')) {
|
|
171
|
+
bindings.dispatch({ type: 'clipboard-paste' });
|
|
172
|
+
return { consume: true };
|
|
173
|
+
}
|
|
174
|
+
if (matchesKey(data, 'pageUp')) {
|
|
175
|
+
bindings.dispatch(bindings.readState().inline
|
|
176
|
+
? { type: 'open-history' }
|
|
177
|
+
: { type: 'scroll', direction: 'page-up' });
|
|
178
|
+
return { consume: true };
|
|
179
|
+
}
|
|
180
|
+
if (matchesKey(data, 'pageDown')) {
|
|
181
|
+
bindings.dispatch({ type: 'scroll', direction: 'page-down' });
|
|
182
|
+
return { consume: true };
|
|
183
|
+
}
|
|
184
|
+
if (matchesKey(data, 'ctrl+home')) {
|
|
185
|
+
bindings.dispatch(bindings.readState().inline
|
|
186
|
+
? { type: 'open-history' }
|
|
187
|
+
: { type: 'scroll', direction: 'home' });
|
|
188
|
+
return { consume: true };
|
|
189
|
+
}
|
|
190
|
+
if (matchesKey(data, 'ctrl+end')) {
|
|
191
|
+
bindings.dispatch({ type: 'scroll', direction: 'end' });
|
|
192
|
+
return { consume: true };
|
|
193
|
+
}
|
|
194
|
+
if (matchesKey(data, 'tab')) {
|
|
195
|
+
const state = bindings.readState();
|
|
196
|
+
const cursor = bindings.readComposerCursor?.() ?? { col: state.composer.length, line: 0 };
|
|
197
|
+
bindings.dispatch({ type: 'request-completion', caret: caretOffset(state.composer, cursor) });
|
|
198
|
+
return { consume: true };
|
|
199
|
+
}
|
|
200
|
+
if (matchesKey(data, 'ctrl+r')) {
|
|
201
|
+
bindings.dispatch({ type: 'request-search' });
|
|
202
|
+
return { consume: true };
|
|
203
|
+
}
|
|
204
|
+
if (matchesKey(data, 'up') && !bindings.readState().composer.includes('\n')) {
|
|
205
|
+
bindings.dispatch({ type: 'recall-move', offset: -1 });
|
|
206
|
+
return { consume: true };
|
|
207
|
+
}
|
|
208
|
+
if (matchesKey(data, 'down') && bindings.readState().recall !== undefined) {
|
|
209
|
+
bindings.dispatch({ type: 'recall-move', offset: 1 });
|
|
210
|
+
return { consume: true };
|
|
211
|
+
}
|
|
212
|
+
if (matchesKey(data, 'ctrl+o')) {
|
|
213
|
+
bindings.dispatch({ type: 'toggle-tool-mode' });
|
|
214
|
+
return { consume: true };
|
|
215
|
+
}
|
|
216
|
+
if (matchesKey(data, 'ctrl+t')) {
|
|
217
|
+
bindings.dispatch({ type: 'toggle-send-mode' });
|
|
218
|
+
return { consume: true };
|
|
219
|
+
}
|
|
220
|
+
if (matchesKey(data, 'ctrl+s')) {
|
|
221
|
+
const state = bindings.readState();
|
|
222
|
+
bindings.dispatch({
|
|
223
|
+
type: 'stash-toggle', cursor: bindings.readComposerCursor?.() ?? { col: state.composer.length, line: 0 },
|
|
224
|
+
});
|
|
225
|
+
return { consume: true };
|
|
226
|
+
}
|
|
227
|
+
if (matchesKey(data, 'ctrl+g')) {
|
|
228
|
+
bindings.dispatch({ type: 'external-edit' });
|
|
229
|
+
return { consume: true };
|
|
230
|
+
}
|
|
231
|
+
if (matchesKey(data, 'backspace')) {
|
|
232
|
+
const state = bindings.readState();
|
|
233
|
+
if (state.composer === '' && state.attachments.length > 0) {
|
|
234
|
+
bindings.dispatch({ type: 'attachment-remove', index: state.attachments.length - 1 });
|
|
235
|
+
return { consume: true };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (data === '\n' || matchesKey(data, 'ctrl+j')
|
|
239
|
+
|| matchesKey(data, 'shift+enter') || matchesKey(data, 'alt+enter')) {
|
|
240
|
+
bindings.insertNewline?.();
|
|
241
|
+
return { consume: true };
|
|
242
|
+
}
|
|
243
|
+
if (matchesKey(data, 'enter')) {
|
|
244
|
+
bindings.dispatch({ type: 'submit' });
|
|
245
|
+
return { consume: true };
|
|
246
|
+
}
|
|
247
|
+
if (ctrlD && bindings.readState().composer === '') {
|
|
248
|
+
bindings.dispatch({ type: 'ctrl-d' });
|
|
249
|
+
return { consume: true };
|
|
250
|
+
}
|
|
251
|
+
if (matchesKey(data, 'ctrl+l')) {
|
|
252
|
+
bindings.dispatch({ type: 'redraw' });
|
|
253
|
+
return { consume: true };
|
|
254
|
+
}
|
|
255
|
+
if (matchesKey(data, 'ctrl+z')) {
|
|
256
|
+
bindings.dispatch({ type: 'suspend' });
|
|
257
|
+
return { consume: true };
|
|
258
|
+
}
|
|
259
|
+
if (matchesKey(data, 'escape')) {
|
|
260
|
+
bindings.dispatch({ type: 'escape' });
|
|
261
|
+
return { consume: true };
|
|
262
|
+
}
|
|
263
|
+
if (bindings.readState().exitArmed)
|
|
264
|
+
bindings.dispatch({ type: 'disarm-exit' });
|
|
265
|
+
return undefined;
|
|
266
|
+
});
|
|
267
|
+
}
|
package/lib/memory.d.ts
ADDED
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,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[]>;
|