@antst/dashi 0.1.0-alpha.17 → 0.1.0-alpha.19
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/package.json +38 -39
- package/lib/application.d.ts +0 -45
- package/lib/application.js +0 -273
- package/lib/at-trigger.d.ts +0 -8
- package/lib/at-trigger.js +0 -13
- package/lib/catalogs.d.ts +0 -28
- package/lib/catalogs.js +0 -308
- package/lib/clipboard-image.d.ts +0 -3
- package/lib/clipboard-image.js +0 -74
- package/lib/completion-trigger.d.ts +0 -6
- package/lib/completion-trigger.js +0 -19
- package/lib/diff-view.d.ts +0 -5
- package/lib/diff-view.js +0 -24
- package/lib/external-editor.d.ts +0 -3
- package/lib/external-editor.js +0 -39
- package/lib/help.d.ts +0 -2
- package/lib/help.js +0 -6
- package/lib/history-records.d.ts +0 -4
- package/lib/history-records.js +0 -14
- package/lib/image-input.d.ts +0 -8
- package/lib/image-input.js +0 -37
- package/lib/index.d.ts +0 -4
- package/lib/index.js +0 -329
- package/lib/input.d.ts +0 -17
- package/lib/input.js +0 -267
- package/lib/memory.d.ts +0 -3
- package/lib/memory.js +0 -27
- package/lib/mouse-input.d.ts +0 -1
- package/lib/mouse-input.js +0 -6
- package/lib/presentation.d.ts +0 -9
- package/lib/presentation.js +0 -54
- package/lib/renderer.d.ts +0 -21
- package/lib/renderer.js +0 -491
- package/lib/rewind.d.ts +0 -26
- package/lib/rewind.js +0 -107
- package/lib/session-export.d.ts +0 -3
- package/lib/session-export.js +0 -27
- package/lib/session-list.d.ts +0 -15
- package/lib/session-list.js +0 -65
- package/lib/session-runtime.d.ts +0 -49
- package/lib/session-runtime.js +0 -1455
- package/lib/shell-command.d.ts +0 -10
- package/lib/shell-command.js +0 -66
- package/lib/state.d.ts +0 -517
- package/lib/state.js +0 -678
- package/lib/terminal-guard.d.ts +0 -14
- package/lib/terminal-guard.js +0 -76
- package/lib/tool-presentation.d.ts +0 -14
- package/lib/tool-presentation.js +0 -156
- package/lib/transcript.d.ts +0 -12
- package/lib/transcript.js +0 -323
- package/lib/tui-root.d.ts +0 -20
- package/lib/tui-root.js +0 -25
- package/validated-dsh-versions.json +0 -3
package/lib/catalogs.js
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { formatFileMention } from '@deepseek-ai/dsh-file-reference/grammar';
|
|
5
|
-
import { isUserInvocable } from '@deepseek-ai/dsh-skill';
|
|
6
|
-
import { detectAtTrigger } from './at-trigger.js';
|
|
7
|
-
import { pluginCompletionQuery, slashCompletionQuery } from './completion-trigger.js';
|
|
8
|
-
import { isImagePath } from './image-input.js';
|
|
9
|
-
import { sessionTitle } from './session-list.js';
|
|
10
|
-
function matchRank(name, query) {
|
|
11
|
-
const candidate = name.toLocaleLowerCase();
|
|
12
|
-
const needle = query.toLocaleLowerCase();
|
|
13
|
-
return candidate.startsWith(needle) ? 0 : candidate.includes(needle) ? 1 : 2;
|
|
14
|
-
}
|
|
15
|
-
function selectedModel(ctx, agent) {
|
|
16
|
-
const selection = ctx.sessionProjections.snapshot(agent.session, ['modelSelection']).values.modelSelection;
|
|
17
|
-
return selection?.next ?? selection?.lastUsed ?? undefined;
|
|
18
|
-
}
|
|
19
|
-
/** Build command and skill completion rows for one exact root. */
|
|
20
|
-
export async function completionOptions(ctx, agent, input, caret = input.length, signal) {
|
|
21
|
-
const plugin = pluginCompletionQuery(input, caret);
|
|
22
|
-
if (plugin !== undefined) {
|
|
23
|
-
let candidates = ['add', 'remove', 'update', 'outdated', 'list', 'why', 'exec', 'licenses'];
|
|
24
|
-
if (plugin.command !== undefined) {
|
|
25
|
-
const profile = fileURLToPath(new URL('.', ctx.baseUrl));
|
|
26
|
-
if (plugin.command === 'exec') {
|
|
27
|
-
candidates = (await readdir(join(profile, 'node_modules', '.bin')).catch((error) => error.code === 'ENOENT' ? [] : Promise.reject(error))).sort();
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
const manifest = JSON.parse(await readFile(join(profile, 'package.json'), 'utf8'));
|
|
31
|
-
candidates = Object.keys(manifest.dependencies ?? {}).sort();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return candidates.filter(name => matchRank(name, plugin.query) < 2).map(name => ({
|
|
35
|
-
group: 'Commands', label: name,
|
|
36
|
-
value: { kind: 'insert', text: `/plugin ${plugin.command === undefined ? '' : `${plugin.command} `}${name} ` },
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
const at = detectAtTrigger(input, caret);
|
|
40
|
-
if (at !== undefined) {
|
|
41
|
-
const service = ctx.get('fileReferences');
|
|
42
|
-
if (service === undefined)
|
|
43
|
-
return [];
|
|
44
|
-
const candidates = await service.list(agent, at.query, signal ?? new AbortController().signal);
|
|
45
|
-
return candidates.flatMap((candidate) => {
|
|
46
|
-
const mention = formatFileMention(candidate, at.quoted);
|
|
47
|
-
if (mention === undefined)
|
|
48
|
-
return [];
|
|
49
|
-
const text = `${input.slice(0, at.start)}${mention}${input.slice(at.end)}`;
|
|
50
|
-
const image = candidate.kind === 'file' && isImagePath(candidate.path);
|
|
51
|
-
return [{
|
|
52
|
-
detail: candidate.kind === 'directory' ? 'directory' : image ? 'attach image' : 'file',
|
|
53
|
-
group: 'Files',
|
|
54
|
-
label: `${candidate.path}${candidate.kind === 'directory' ? '/' : ''}`,
|
|
55
|
-
value: image
|
|
56
|
-
? {
|
|
57
|
-
kind: 'attach',
|
|
58
|
-
path: candidate.path,
|
|
59
|
-
source: input,
|
|
60
|
-
text: `${input.slice(0, at.start)}${input.slice(at.end)}`,
|
|
61
|
-
}
|
|
62
|
-
: { kind: 'insert', text },
|
|
63
|
-
}];
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
const query = slashCompletionQuery(input);
|
|
67
|
-
if (query === undefined)
|
|
68
|
-
return [];
|
|
69
|
-
const commands = ctx.commands.list(agent)
|
|
70
|
-
.filter(command => matchRank(command.name, query) < 2)
|
|
71
|
-
.sort((left, right) => matchRank(left.name, query) - matchRank(right.name, query)
|
|
72
|
-
|| left.name.localeCompare(right.name))
|
|
73
|
-
.map(command => ({
|
|
74
|
-
detail: command.description,
|
|
75
|
-
group: 'Commands',
|
|
76
|
-
label: `/${command.name}${command.input?.hint === undefined ? '' : ` ${command.input.hint}`}`,
|
|
77
|
-
...(command.input?.hint === undefined ? { submitOnEnter: true } : {}),
|
|
78
|
-
value: { kind: 'insert', text: `/${command.name} ` },
|
|
79
|
-
}));
|
|
80
|
-
const skills = (await ctx.skills.list({ cwd: agent.session.header.cwd, scope: agent, signal }))
|
|
81
|
-
.filter(isUserInvocable)
|
|
82
|
-
.filter(skill => matchRank(skill.name, query) < 2)
|
|
83
|
-
.sort((left, right) => matchRank(left.name, query) - matchRank(right.name, query)
|
|
84
|
-
|| left.name.localeCompare(right.name))
|
|
85
|
-
.map(skill => ({
|
|
86
|
-
detail: skill.description,
|
|
87
|
-
group: 'Skills',
|
|
88
|
-
label: `/${skill.name}`,
|
|
89
|
-
value: { kind: 'insert', text: `/${skill.name} ` },
|
|
90
|
-
}));
|
|
91
|
-
return [...commands, ...skills];
|
|
92
|
-
}
|
|
93
|
-
/** Build a filtered view of the skills resolved for one live session. */
|
|
94
|
-
export async function skillOverlay(ctx, agent, filter, signal) {
|
|
95
|
-
const lookup = { cwd: agent.session.header.cwd, scope: agent, signal };
|
|
96
|
-
const needle = filter.trim().toLocaleLowerCase();
|
|
97
|
-
const skills = (await ctx.skills.list(lookup)).filter(skill => needle === ''
|
|
98
|
-
|| skill.name.toLocaleLowerCase().includes(needle)
|
|
99
|
-
|| skill.description.toLocaleLowerCase().includes(needle));
|
|
100
|
-
const definitions = await Promise.all(skills.map(skill => ctx.skills.get(skill.name, lookup)));
|
|
101
|
-
return {
|
|
102
|
-
cursor: 0, kind: 'list', purpose: 'skills', title: needle === '' ? 'Skills' : `Skills · ${filter.trim()}`,
|
|
103
|
-
options: skills.map((skill, index) => ({
|
|
104
|
-
detail: `${skill.description} · user ${skill.invocation.userInvocable ? 'yes' : 'no'} · model ${skill.invocation.modelInvocable ? 'yes' : 'no'} · source ${skill.source} · provider ${skill.provider} · path ${definitions[index]?.path ?? 'unavailable'}`,
|
|
105
|
-
label: `/${skill.name}`,
|
|
106
|
-
value: { kind: 'insert', text: `/${skill.name} ` },
|
|
107
|
-
})),
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/** Build the cold session picker without adopting any candidate. */
|
|
111
|
-
export function sessionOverlay(items, currentId, cwd) {
|
|
112
|
-
return {
|
|
113
|
-
cursor: 0,
|
|
114
|
-
kind: 'list',
|
|
115
|
-
options: items.filter(item => item.origin === undefined && (cwd === undefined || item.cwd === cwd)).map(item => ({
|
|
116
|
-
active: String(item.sessionId) === currentId,
|
|
117
|
-
detail: `${item.cwd ?? 'cwd unavailable'} · ${new Date(item.updatedAt).toISOString()}`,
|
|
118
|
-
label: `${sessionTitle(item) ?? 'Untitled'} · ${String(item.sessionId)}`,
|
|
119
|
-
value: { kind: 'resume', sessionId: String(item.sessionId) },
|
|
120
|
-
})),
|
|
121
|
-
purpose: 'resume',
|
|
122
|
-
title: 'Resume session',
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
/** Flatten DSH's provider/model/effort catalog into one terminal picker. */
|
|
126
|
-
export function modelOverlay(catalog, selected) {
|
|
127
|
-
const options = [];
|
|
128
|
-
for (const group of catalog.groups) {
|
|
129
|
-
for (const model of group.models) {
|
|
130
|
-
const efforts = model.reasoning?.efforts ?? [];
|
|
131
|
-
if (efforts.length === 0) {
|
|
132
|
-
options.push({
|
|
133
|
-
active: selected?.provider === group.id && selected.model === model.id,
|
|
134
|
-
label: `${group.name} · ${model.name}`,
|
|
135
|
-
value: { kind: 'model', model: model.id, provider: group.id },
|
|
136
|
-
...(model.description === undefined ? {} : { detail: model.description }),
|
|
137
|
-
});
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
for (const effort of efforts) {
|
|
141
|
-
options.push({
|
|
142
|
-
active: selected?.provider === group.id && selected.model === model.id
|
|
143
|
-
&& (selected.reasoningEffort ?? model.reasoning?.defaultEffort) === effort.id,
|
|
144
|
-
label: `${group.name} · ${model.name} · ${effort.name}`,
|
|
145
|
-
value: { kind: 'model', effort: effort.id, model: model.id, provider: group.id },
|
|
146
|
-
...(effort.description ?? model.description) === undefined
|
|
147
|
-
? {} : { detail: effort.description ?? model.description },
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return { cursor: Math.max(0, options.findIndex(option => option.active === true)), kind: 'list', options, purpose: 'model', title: 'Model' };
|
|
153
|
-
}
|
|
154
|
-
/** Build the preset picker from the native preset service. */
|
|
155
|
-
export function permissionOverlay(ctx, agent) {
|
|
156
|
-
const current = ctx.permissionPresets.current(agent.session);
|
|
157
|
-
const options = ctx.permissionPresets.names.map((preset) => {
|
|
158
|
-
const option = ctx.permissionPresets.optionOf(preset);
|
|
159
|
-
const spec = ctx.permissionPresets.resolve(preset);
|
|
160
|
-
return {
|
|
161
|
-
active: preset === current,
|
|
162
|
-
confirmDetail: [`Sandbox: ${spec.sandbox}`, `Approval: ${spec.approval}`],
|
|
163
|
-
danger: spec.approval === 'never',
|
|
164
|
-
label: option.name,
|
|
165
|
-
value: { kind: 'permission', preset },
|
|
166
|
-
...(option.description === undefined ? {} : { detail: option.description }),
|
|
167
|
-
};
|
|
168
|
-
});
|
|
169
|
-
return {
|
|
170
|
-
cursor: Math.max(0, options.findIndex(option => option.active)),
|
|
171
|
-
kind: 'list', options, purpose: 'permission', title: 'Permission preset',
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function number(value) {
|
|
175
|
-
return typeof value === 'number' ? value.toLocaleString('en-US') : 'unavailable';
|
|
176
|
-
}
|
|
177
|
-
function milliseconds(value) {
|
|
178
|
-
return typeof value === 'number' ? `${number(value)} ms` : 'unavailable';
|
|
179
|
-
}
|
|
180
|
-
function compactTokens(value) {
|
|
181
|
-
return Intl.NumberFormat('en-US', { maximumFractionDigits: 1, notation: 'compact' }).format(value).replace('K', 'k');
|
|
182
|
-
}
|
|
183
|
-
export function sessionStatusLine(ctx, agent, root, repo) {
|
|
184
|
-
const { modelSelection, permissions, tokenUsage, contextPressure } = ctx.sessionProjections
|
|
185
|
-
.snapshot(agent.session, ['modelSelection', 'permissions', 'tokenUsage', 'contextPressure']).values;
|
|
186
|
-
const model = modelSelection?.next ?? modelSelection?.lastUsed;
|
|
187
|
-
const input = tokenUsage === undefined ? 0 : tokenUsage.uncachedInputTokens + tokenUsage.cacheReadTokens + tokenUsage.cacheWriteTokens;
|
|
188
|
-
const total = input + (tokenUsage?.outputTokens ?? 0);
|
|
189
|
-
const used = contextPressure?.projectedTokens ?? contextPressure?.pressureTokens;
|
|
190
|
-
const context = used === undefined ? undefined
|
|
191
|
-
: contextPressure?.contextWindow === undefined ? `ctx ${compactTokens(used)}`
|
|
192
|
-
: `ctx ${compactTokens(used)}/${compactTokens(contextPressure.contextWindow)} ${String(Math.min(100, Math.round(used / contextPressure.contextWindow * 100)))}%`;
|
|
193
|
-
const agents = root.subagents?.filter(item => item.state === 'running').length ?? 0;
|
|
194
|
-
const jobs = root.jobs?.filter(item => item.status === 'running' || item.status === 'stopping').length ?? 0;
|
|
195
|
-
return [
|
|
196
|
-
model?.model ?? root.model,
|
|
197
|
-
...(model?.reasoningEffort === undefined ? [] : [model.reasoningEffort]),
|
|
198
|
-
permissions?.currentValue ?? root.permission ?? 'unknown',
|
|
199
|
-
...(context === undefined ? [] : [context]),
|
|
200
|
-
...(input === 0 ? [] : [`cache ${String(Math.min(tokenUsage?.cacheReadTokens === input ? 100 : 99, Math.round((tokenUsage?.cacheReadTokens ?? 0) / input * 100)))}%`]),
|
|
201
|
-
...(tokenUsage === undefined ? [] : [`${compactTokens(total)} tok`]),
|
|
202
|
-
...(agents === 0 ? [] : [`${String(agents)} agents`]),
|
|
203
|
-
...(jobs === 0 ? [] : [`${String(jobs)} jobs`]),
|
|
204
|
-
...(root.title === undefined ? [] : [root.title]),
|
|
205
|
-
...(repo === undefined ? [] : [repo]),
|
|
206
|
-
].join(' · ');
|
|
207
|
-
}
|
|
208
|
-
/** Read DSH-owned root facts for /status in one projection cut. */
|
|
209
|
-
export function statusOverlay(ctx, agent, root) {
|
|
210
|
-
const values = ctx.sessionProjections.snapshot(agent.session, [
|
|
211
|
-
'modelSelection', 'permissions', 'tokenUsage', 'contextPressure', 'contextBreakdown',
|
|
212
|
-
]).values;
|
|
213
|
-
const model = values.modelSelection?.next ?? values.modelSelection?.lastUsed;
|
|
214
|
-
const usage = values.tokenUsage;
|
|
215
|
-
const pressure = values.contextPressure;
|
|
216
|
-
const breakdown = values.contextBreakdown;
|
|
217
|
-
return {
|
|
218
|
-
kind: 'info',
|
|
219
|
-
lines: [
|
|
220
|
-
`UUID: ${root.id}`,
|
|
221
|
-
`Title: ${root.title ?? 'Untitled'}`,
|
|
222
|
-
`Cwd: ${root.cwd}`,
|
|
223
|
-
root.parent === undefined
|
|
224
|
-
? 'Lineage: root session'
|
|
225
|
-
: `Lineage: forked from ${root.parent} at turn ${String(root.parentTurn ?? 'unknown')}`,
|
|
226
|
-
`Model: ${model === null || model === undefined ? root.model : `${model.provider}/${model.model}`}`,
|
|
227
|
-
`Effort: ${model?.reasoningEffort ?? root.effort ?? 'default'}`,
|
|
228
|
-
`Permission: ${ctx.permissionPresets.current(agent.session)}`,
|
|
229
|
-
`Usage: input ${number(usage?.uncachedInputTokens)} · output ${number(usage?.outputTokens)} · cache read ${number(usage?.cacheReadTokens)} · cache write ${number(usage?.cacheWriteTokens)}`,
|
|
230
|
-
`Context: ${number(pressure?.projectedTokens)} / ${number(pressure?.contextWindow)} tokens`,
|
|
231
|
-
`Composition: system ${number(breakdown?.systemTokens)} · tools ${number(breakdown?.toolsTokens)} · messages ${number(breakdown?.messageTokens)}`,
|
|
232
|
-
],
|
|
233
|
-
title: 'Status',
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
/** Show DSH's heuristic next-request composition without treating it as an exact total. */
|
|
237
|
-
export function contextOverlay(ctx, agent) {
|
|
238
|
-
const breakdown = ctx.sessionProjections.snapshot(agent.session, ['contextBreakdown'])
|
|
239
|
-
.values.contextBreakdown;
|
|
240
|
-
return {
|
|
241
|
-
kind: 'info',
|
|
242
|
-
lines: [
|
|
243
|
-
`System prompt: ${number(breakdown?.systemTokens)} tokens`,
|
|
244
|
-
`Tool schemas: ${number(breakdown?.toolsTokens)} tokens`,
|
|
245
|
-
`Messages: ${number(breakdown?.messageTokens)} tokens`,
|
|
246
|
-
'These are DSH heuristic estimates for the next request, not a provider-reported total.',
|
|
247
|
-
],
|
|
248
|
-
title: 'Context',
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
/** Read DSH's whole-log token and timing projections without folding session history. */
|
|
252
|
-
export function usageOverlay(ctx, agent) {
|
|
253
|
-
const values = ctx.sessionProjections.snapshot(agent.session, ['tokenUsage', 'sessionStats']).values;
|
|
254
|
-
const tokens = values.tokenUsage;
|
|
255
|
-
const stats = values.sessionStats;
|
|
256
|
-
const measured = stats === undefined ? undefined : stats.llmMs + stats.toolMs;
|
|
257
|
-
return {
|
|
258
|
-
kind: 'info',
|
|
259
|
-
lines: [
|
|
260
|
-
`Input tokens: ${number(tokens?.uncachedInputTokens)}`,
|
|
261
|
-
`Output tokens: ${number(tokens?.outputTokens)}`,
|
|
262
|
-
`Cache read: ${number(tokens?.cacheReadTokens)}`,
|
|
263
|
-
`Cache write: ${number(tokens?.cacheWriteTokens)}`,
|
|
264
|
-
`Turns: ${number(stats?.turns)} · Steps: ${number(stats?.steps)}`,
|
|
265
|
-
`Model time: ${milliseconds(stats?.llmMs)}`,
|
|
266
|
-
`Tool time: ${milliseconds(stats?.toolMs)}`,
|
|
267
|
-
`Measured wall time: ${milliseconds(measured)}`,
|
|
268
|
-
],
|
|
269
|
-
title: 'Usage',
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
/** Build the native agent-preset picker; broken presets are visible elsewhere, not selectable here. */
|
|
273
|
-
export function agentPresetOverlay(presets, current, blank) {
|
|
274
|
-
const options = presets.filter(preset => preset.broken === undefined).map(preset => ({
|
|
275
|
-
active: preset.id === current,
|
|
276
|
-
label: preset.name ?? preset.id,
|
|
277
|
-
value: { kind: 'agent-preset', preset: preset.id },
|
|
278
|
-
...(preset.description === undefined ? {} : { detail: preset.description }),
|
|
279
|
-
}));
|
|
280
|
-
return {
|
|
281
|
-
cursor: Math.max(0, options.findIndex(option => option.active)),
|
|
282
|
-
kind: 'list',
|
|
283
|
-
notice: blank ? 'The current session is blank.' : 'Choosing a preset starts a new session.',
|
|
284
|
-
options,
|
|
285
|
-
purpose: 'agents',
|
|
286
|
-
title: 'Agent preset',
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
/** Build bounded same-cwd prompt-search rows from DSH query hits. */
|
|
290
|
-
export function searchOverlay(items, rootId) {
|
|
291
|
-
return {
|
|
292
|
-
cursor: 0,
|
|
293
|
-
kind: 'list',
|
|
294
|
-
options: items.map(item => ({
|
|
295
|
-
detail: `${String(item.header.id)} · ${item.header.cwd ?? 'cwd unavailable'}`,
|
|
296
|
-
label: item.bestMatch.snippet.replaceAll(/\s+/gu, ' ').trim(),
|
|
297
|
-
value: {
|
|
298
|
-
kind: 'search-result', rootId, seq: item.bestMatch.seq,
|
|
299
|
-
sessionId: String(item.header.id),
|
|
300
|
-
},
|
|
301
|
-
})),
|
|
302
|
-
purpose: 'search',
|
|
303
|
-
title: 'Prompt search',
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
export function currentModel(ctx, agent) {
|
|
307
|
-
return selectedModel(ctx, agent);
|
|
308
|
-
}
|
package/lib/clipboard-image.d.ts
DELETED
package/lib/clipboard-image.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { draftImage } from './image-input.js';
|
|
3
|
-
const APPLE_SCRIPT = `
|
|
4
|
-
ObjC.import('AppKit')
|
|
5
|
-
const pasteboard = $.NSPasteboard.generalPasteboard
|
|
6
|
-
let data = pasteboard.dataForType('public.png')
|
|
7
|
-
if (!data) {
|
|
8
|
-
const tiff = pasteboard.dataForType('public.tiff')
|
|
9
|
-
if (tiff) {
|
|
10
|
-
const image = $.NSBitmapImageRep.imageRepWithData(tiff)
|
|
11
|
-
if (image) data = image.representationUsingTypeProperties($.NSBitmapImageFileTypePNG, $())
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
if (!data) $.exit(2)
|
|
15
|
-
console.log(ObjC.unwrap(data.base64EncodedStringWithOptions(0)))
|
|
16
|
-
`.trim();
|
|
17
|
-
function capture(command, args, base64, path) {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
const child = spawn(command, args, {
|
|
20
|
-
...(path === undefined ? {} : { env: { ...process.env, PATH: path } }),
|
|
21
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
22
|
-
});
|
|
23
|
-
const chunks = [];
|
|
24
|
-
child.stdout.on('data', (chunk) => { chunks.push(chunk); });
|
|
25
|
-
child.once('error', error => {
|
|
26
|
-
if (error.code === 'ENOENT')
|
|
27
|
-
resolve({ found: false });
|
|
28
|
-
else
|
|
29
|
-
reject(error);
|
|
30
|
-
});
|
|
31
|
-
child.once('close', code => {
|
|
32
|
-
if (code !== 0)
|
|
33
|
-
return resolve({ found: true });
|
|
34
|
-
const output = Buffer.concat(chunks);
|
|
35
|
-
if (output.length === 0)
|
|
36
|
-
return resolve({ found: true });
|
|
37
|
-
if (!base64)
|
|
38
|
-
return resolve({ found: true, output: new Uint8Array(output) });
|
|
39
|
-
try {
|
|
40
|
-
const decoded = Buffer.from(output.toString('utf8').trim(), 'base64');
|
|
41
|
-
resolve({ found: true, ...(decoded.length === 0 ? {} : { output: new Uint8Array(decoded) }) });
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
reject(error);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
/** Read a PNG from an installed platform clipboard helper. */
|
|
50
|
-
export async function readClipboardImage(platform = process.platform, path) {
|
|
51
|
-
const attempts = platform === 'darwin'
|
|
52
|
-
? [
|
|
53
|
-
['pngpaste', ['-'], false],
|
|
54
|
-
['osascript', ['-l', 'JavaScript', '-e', APPLE_SCRIPT], true],
|
|
55
|
-
]
|
|
56
|
-
: platform === 'linux'
|
|
57
|
-
? [
|
|
58
|
-
['wl-paste', ['--no-newline', '--type', 'image/png'], false],
|
|
59
|
-
['xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o'], false],
|
|
60
|
-
]
|
|
61
|
-
: [];
|
|
62
|
-
let helperFound = false;
|
|
63
|
-
for (const [command, args, base64] of attempts) {
|
|
64
|
-
const result = await capture(command, args, base64, path);
|
|
65
|
-
helperFound ||= result.found;
|
|
66
|
-
if (result.output !== undefined)
|
|
67
|
-
return draftImage(result.output, 'image/png', 'clipboard.png', 'clipboard');
|
|
68
|
-
}
|
|
69
|
-
if (attempts.length === 0)
|
|
70
|
-
throw new Error('Clipboard image paste is supported on Linux and macOS.');
|
|
71
|
-
throw new Error(helperFound
|
|
72
|
-
? 'The clipboard does not contain a PNG image.'
|
|
73
|
-
: `Clipboard image paste needs ${platform === 'darwin' ? 'pngpaste or osascript' : 'wl-paste or xclip'}.`);
|
|
74
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare function slashCompletionQuery(input: string): string | undefined;
|
|
2
|
-
export declare function pluginCompletionQuery(input: string, caret: number): {
|
|
3
|
-
readonly command?: string;
|
|
4
|
-
readonly query: string;
|
|
5
|
-
} | undefined;
|
|
6
|
-
export declare function hasCompletionTrigger(input: string, caret: number): boolean;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { detectAtTrigger } from './at-trigger.js';
|
|
2
|
-
export function slashCompletionQuery(input) {
|
|
3
|
-
return /^\/([a-z0-9_-]*)$/u.exec(input)?.[1];
|
|
4
|
-
}
|
|
5
|
-
export function pluginCompletionQuery(input, caret) {
|
|
6
|
-
if (caret !== input.length)
|
|
7
|
-
return undefined;
|
|
8
|
-
const match = /^\/plugin +([^\s]*)(?: +([^\s]*))?$/u.exec(input);
|
|
9
|
-
if (match === null)
|
|
10
|
-
return undefined;
|
|
11
|
-
const command = match[1] ?? '';
|
|
12
|
-
if (match[2] === undefined)
|
|
13
|
-
return { query: command };
|
|
14
|
-
return /^(?:exec|remove|update|why)$/u.test(command) ? { command, query: match[2] } : undefined;
|
|
15
|
-
}
|
|
16
|
-
export function hasCompletionTrigger(input, caret) {
|
|
17
|
-
return slashCompletionQuery(input) !== undefined || pluginCompletionQuery(input, caret) !== undefined
|
|
18
|
-
|| detectAtTrigger(input, caret) !== undefined;
|
|
19
|
-
}
|
package/lib/diff-view.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
-
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
3
|
-
import type { Overlay } from './state.js';
|
|
4
|
-
import { type ToolPresenter } from './tool-presentation.js';
|
|
5
|
-
export declare function diffOverlay(ctx: Context, agent: Agent, presenter: ToolPresenter, mode: '' | 'turn', signal: AbortSignal): Promise<Overlay>;
|
package/lib/diff-view.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { HUMAN_SHELL_STREAM_BYTES } from './shell-command.js';
|
|
2
|
-
import { boundedBody } from './tool-presentation.js';
|
|
3
|
-
import { foldCells } from './transcript.js';
|
|
4
|
-
export async function diffOverlay(ctx, agent, presenter, mode, signal) {
|
|
5
|
-
if (mode === 'turn') {
|
|
6
|
-
const events = agent.session.snapshotEvents();
|
|
7
|
-
const start = events.map(event => event.type).lastIndexOf('turn/start');
|
|
8
|
-
const cells = (start < 0 ? [] : foldCells(events.slice(start), presenter)).filter(cell => cell.pending !== true && cell.tool?.card === 'diff' && (cell.tool.diffs?.length ?? 0) > 0);
|
|
9
|
-
return { cells, kind: 'info', lines: cells.length === 0 ? ['No write/edit changes in the last turn.'] : [], title: 'Last turn diff' };
|
|
10
|
-
}
|
|
11
|
-
const command = 'git diff --no-ext-diff --no-color HEAD --';
|
|
12
|
-
const result = await ctx.shell.run(ctx.shell.resolve({
|
|
13
|
-
command, sandboxPolicy: ctx.sandboxPolicy.resolve({ session: agent.session }), signal,
|
|
14
|
-
stdoutMaxBytes: HUMAN_SHELL_STREAM_BYTES, timeoutMs: 30_000, workdir: agent.session.header.cwd,
|
|
15
|
-
}));
|
|
16
|
-
const output = [result.stdout.text, result.stderr.text].filter(Boolean).join('\n');
|
|
17
|
-
const cell = {
|
|
18
|
-
key: 'diff:working', kind: result.exitCode === 0 ? 'tool' : 'error', text: 'git diff',
|
|
19
|
-
tool: { card: 'terminal', title: 'git diff', status: result.exitCode === 0 ? output === '' ? 'clean' : 'changed'
|
|
20
|
-
: result.exitCode === null ? result.signal ?? 'failed' : `exit ${String(result.exitCode)}`,
|
|
21
|
-
...(output === '' ? {} : { body: boundedBody(output) }) },
|
|
22
|
-
};
|
|
23
|
-
return { cells: [cell], kind: 'info', lines: [], title: 'Working tree diff' };
|
|
24
|
-
}
|
package/lib/external-editor.d.ts
DELETED
package/lib/external-editor.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { mkdtemp, readFile, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
export function runEditor(command, file, cwd) {
|
|
6
|
-
return new Promise((resolve, reject) => {
|
|
7
|
-
const child = spawn('/bin/sh', ['-c', `exec ${command} "$1"`, 'dashi-editor', file], {
|
|
8
|
-
cwd, stdio: 'inherit',
|
|
9
|
-
});
|
|
10
|
-
child.once('error', reject);
|
|
11
|
-
child.once('exit', (code, signal) => {
|
|
12
|
-
if (code === 0)
|
|
13
|
-
resolve();
|
|
14
|
-
else
|
|
15
|
-
reject(new Error(`editor exited with ${code === null ? signal ?? 'unknown status' : `code ${String(code)}`}`));
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
/** Edit one draft through the user's terminal editor and always remove its file. */
|
|
20
|
-
export async function editDraftExternally(text, cwd) {
|
|
21
|
-
const command = process.env.VISUAL?.trim() || process.env.EDITOR?.trim();
|
|
22
|
-
if (command === undefined || command === '')
|
|
23
|
-
throw new Error('set $VISUAL or $EDITOR to use Ctrl+G');
|
|
24
|
-
const directory = await mkdtemp(join(tmpdir(), 'dashi-editor-'));
|
|
25
|
-
const file = join(directory, 'draft.md');
|
|
26
|
-
try {
|
|
27
|
-
await writeFile(file, text, { encoding: 'utf8', mode: 0o600 });
|
|
28
|
-
await runEditor(command, file, cwd);
|
|
29
|
-
return await readFile(file, 'utf8');
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
try {
|
|
33
|
-
await rm(file, { force: true });
|
|
34
|
-
}
|
|
35
|
-
finally {
|
|
36
|
-
await rmdir(directory);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
package/lib/help.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const VERSION_LINE: string;
|
|
2
|
-
export declare const FLAG_HELP: readonly ["Launch flags: --inline | --fullscreen · --accessible · --verbose · -r, --resume [NAME|UUID] · -c, --continue · --fork-session · --all", " -n, --name TITLE · --agent PRESET · --session-id UUID · --model ID [--provider ID] · --effort LEVEL", " --permission PRESET · --tools NAMES · --disallowedTools NAMES · --yolo | --dangerously-skip-permissions · --image PATH", " --system-prompt TEXT | --system-prompt-file PATH · --append-system-prompt TEXT | --append-system-prompt-file PATH · -h, --help"];
|
package/lib/help.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import dashi from '../package.json' with { type: 'json' };
|
|
2
|
-
import dsh from '@deepseek-ai/dsh-cmdline/package.json' with { type: 'json' };
|
|
3
|
-
export const VERSION_LINE = `dashi ${dashi.version} on DSH ${dsh.version}`;
|
|
4
|
-
export const FLAG_HELP = [
|
|
5
|
-
'Launch flags: --inline | --fullscreen · --accessible · --verbose · -r, --resume [NAME|UUID] · -c, --continue · --fork-session · --all', ' -n, --name TITLE · --agent PRESET · --session-id UUID · --model ID [--provider ID] · --effort LEVEL', ' --permission PRESET · --tools NAMES · --disallowedTools NAMES · --yolo | --dangerously-skip-permissions · --image PATH', ' --system-prompt TEXT | --system-prompt-file PATH · --append-system-prompt TEXT | --append-system-prompt-file PATH · -h, --help'
|
|
6
|
-
];
|
package/lib/history-records.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { SessionHistoryRecord } from '@deepseek-ai/dsh-api-session-controller';
|
|
2
|
-
import { type SessionEvent } from '@deepseek-ai/dsh-session';
|
|
3
|
-
/** Expand the Controller's compact history wire records to canonical Session events. */
|
|
4
|
-
export declare function eventsFromRecords(records: readonly SessionHistoryRecord[]): SessionEvent[];
|
package/lib/history-records.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { decodeStorageRecord } from '@deepseek-ai/dsh-session';
|
|
2
|
-
/** Expand the Controller's compact history wire records to canonical Session events. */
|
|
3
|
-
export function eventsFromRecords(records) {
|
|
4
|
-
return records.flatMap((record) => {
|
|
5
|
-
if (record.type === 'event')
|
|
6
|
-
return [record.event];
|
|
7
|
-
const encoded = record.event.type === 'chunkrow/text-chunks'
|
|
8
|
-
? { type: 'text-chunks', seq0: record.event.seq, time0: record.event.time, data: record.event.data }
|
|
9
|
-
: record.event.type === 'chunkrow/reasoning-chunks'
|
|
10
|
-
? { type: 'reasoning-chunks', seq0: record.event.seq, time0: record.event.time, data: record.event.data }
|
|
11
|
-
: { type: 'tool-call-chunks', seq0: record.event.seq, time0: record.event.time, data: record.event.data };
|
|
12
|
-
return decodeStorageRecord(encoded);
|
|
13
|
-
});
|
|
14
|
-
}
|
package/lib/image-input.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { AttachmentStore, EncodedImageAttachment, ImageAttachmentRef, ImageMediaType } from '@deepseek-ai/dsh-attachment';
|
|
2
|
-
import type { DraftAttachment } from './state.js';
|
|
3
|
-
export declare function isImagePath(path: string): boolean;
|
|
4
|
-
export declare function draftImage(data: Uint8Array, mediaType: ImageMediaType, name: string, path: string): DraftAttachment;
|
|
5
|
-
/** Snapshot explicit user paths into one disposable composer draft. */
|
|
6
|
-
export declare function readDraftImages(cwd: string, paths: readonly string[]): Promise<DraftAttachment[]>;
|
|
7
|
-
export declare function encodeDraftImages(images: readonly DraftAttachment[]): EncodedImageAttachment[];
|
|
8
|
-
export declare function admitDraftImages(attachments: AttachmentStore, images: readonly DraftAttachment[]): Promise<readonly ImageAttachmentRef[]>;
|
package/lib/image-input.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { basename, extname, resolve } from 'node:path';
|
|
3
|
-
const MEDIA_TYPES = {
|
|
4
|
-
'.gif': 'image/gif',
|
|
5
|
-
'.jpeg': 'image/jpeg',
|
|
6
|
-
'.jpg': 'image/jpeg',
|
|
7
|
-
'.png': 'image/png',
|
|
8
|
-
'.webp': 'image/webp',
|
|
9
|
-
};
|
|
10
|
-
export function isImagePath(path) {
|
|
11
|
-
return MEDIA_TYPES[extname(path).toLocaleLowerCase()] !== undefined;
|
|
12
|
-
}
|
|
13
|
-
export function draftImage(data, mediaType, name, path) {
|
|
14
|
-
return { bytes: data.byteLength, data, mediaType, name, path };
|
|
15
|
-
}
|
|
16
|
-
/** Snapshot explicit user paths into one disposable composer draft. */
|
|
17
|
-
export async function readDraftImages(cwd, paths) {
|
|
18
|
-
return Promise.all(paths.map(async (path) => {
|
|
19
|
-
const absolute = resolve(cwd, path);
|
|
20
|
-
const mediaType = MEDIA_TYPES[extname(absolute).toLocaleLowerCase()];
|
|
21
|
-
if (mediaType === undefined) {
|
|
22
|
-
throw new Error(`unsupported image extension for ${path}; accepted: .png, .jpg, .jpeg, .webp, .gif`);
|
|
23
|
-
}
|
|
24
|
-
const data = new Uint8Array(await readFile(absolute));
|
|
25
|
-
return draftImage(data, mediaType, basename(absolute), absolute);
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
export function encodeDraftImages(images) {
|
|
29
|
-
return images.map(image => ({
|
|
30
|
-
data: Buffer.from(image.data).toString('base64'), mediaType: image.mediaType, name: image.name,
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
export function admitDraftImages(attachments, images) {
|
|
34
|
-
return attachments.saveImages(images.map(image => ({
|
|
35
|
-
data: image.data, mediaType: image.mediaType, name: image.name,
|
|
36
|
-
})));
|
|
37
|
-
}
|
package/lib/index.d.ts
DELETED