@antst/dashi 0.1.0-alpha.6 → 0.1.0-alpha.8
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.js +9 -1
- package/lib/diff-view.d.ts +5 -0
- package/lib/diff-view.js +23 -0
- package/lib/external-editor.d.ts +1 -0
- package/lib/external-editor.js +1 -1
- package/lib/help.d.ts +2 -0
- package/lib/help.js +6 -0
- package/lib/index.js +32 -5
- package/lib/input.js +2 -0
- package/lib/memory.d.ts +3 -0
- package/lib/memory.js +27 -0
- package/lib/renderer.js +9 -2
- package/lib/session-runtime.d.ts +4 -0
- package/lib/session-runtime.js +120 -2
- package/lib/shell-command.d.ts +2 -1
- package/lib/shell-command.js +11 -8
- package/lib/state.d.ts +5 -1
- package/lib/state.js +5 -2
- package/lib/transcript.js +20 -1
- package/package.json +6 -1
package/lib/application.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRenderer } from './renderer.js';
|
|
2
|
+
import { runEditor } from './external-editor.js';
|
|
2
3
|
import { initialViewState, reduce, } from './state.js';
|
|
3
4
|
import { createTerminalGuard } from './terminal-guard.js';
|
|
4
5
|
function suspendProcess() {
|
|
@@ -44,7 +45,14 @@ export function createTerminalShell(options) {
|
|
|
44
45
|
async function run(effect) {
|
|
45
46
|
switch (effect.type) {
|
|
46
47
|
case 'activate-overlay':
|
|
47
|
-
|
|
48
|
+
if (effect.value.kind === 'open-file') {
|
|
49
|
+
const path = effect.value.path;
|
|
50
|
+
await guard.withCookedTerminal(() => runEditor(process.env.EDITOR?.trim() || 'vi', path, options.cwd)).catch(error => {
|
|
51
|
+
dispatch({ type: 'runtime-error', message: error instanceof Error ? error.message : String(error) });
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else
|
|
55
|
+
await options.activateOverlay?.(effect.value);
|
|
48
56
|
return;
|
|
49
57
|
case 'attach': {
|
|
50
58
|
try {
|
|
@@ -0,0 +1,5 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { boundedBody } from './tool-presentation.js';
|
|
2
|
+
import { foldCells } from './transcript.js';
|
|
3
|
+
export async function diffOverlay(ctx, agent, presenter, mode, signal) {
|
|
4
|
+
if (mode === 'turn') {
|
|
5
|
+
const events = agent.session.snapshotEvents();
|
|
6
|
+
const start = events.map(event => event.type).lastIndexOf('turn/start');
|
|
7
|
+
const cells = (start < 0 ? [] : foldCells(events.slice(start), presenter)).filter(cell => cell.pending !== true && cell.tool?.card === 'diff' && (cell.tool.diffs?.length ?? 0) > 0);
|
|
8
|
+
return { cells, kind: 'info', lines: cells.length === 0 ? ['No write/edit changes in the last turn.'] : [], title: 'Last turn diff' };
|
|
9
|
+
}
|
|
10
|
+
const command = 'git diff --no-ext-diff --no-color HEAD --';
|
|
11
|
+
const result = await ctx.shell.run(ctx.shell.resolve({
|
|
12
|
+
command, sandboxPolicy: ctx.sandboxPolicy.resolve({ session: agent.session }), signal,
|
|
13
|
+
stdoutMaxBytes: 64 * 1024, timeoutMs: 30_000, workdir: agent.session.header.cwd,
|
|
14
|
+
}));
|
|
15
|
+
const output = [result.stdout.text, result.stderr.text].filter(Boolean).join('\n');
|
|
16
|
+
const cell = {
|
|
17
|
+
key: 'diff:working', kind: result.exitCode === 0 ? 'tool' : 'error', text: 'git diff',
|
|
18
|
+
tool: { card: 'terminal', title: 'git diff', status: result.exitCode === 0 ? output === '' ? 'clean' : 'changed'
|
|
19
|
+
: result.exitCode === null ? result.signal ?? 'failed' : `exit ${String(result.exitCode)}`,
|
|
20
|
+
...(output === '' ? {} : { body: boundedBody(output) }) },
|
|
21
|
+
};
|
|
22
|
+
return { cells: [cell], kind: 'info', lines: [], title: 'Working tree diff' };
|
|
23
|
+
}
|
package/lib/external-editor.d.ts
CHANGED
package/lib/external-editor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { mkdtemp, readFile, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
function runEditor(command, file, cwd) {
|
|
5
|
+
export function runEditor(command, file, cwd) {
|
|
6
6
|
return new Promise((resolve, reject) => {
|
|
7
7
|
const child = spawn('/bin/sh', ['-c', `exec ${command} "$1"`, 'dashi-editor', file], {
|
|
8
8
|
cwd, stdio: 'inherit',
|
package/lib/help.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const VERSION_LINE: string;
|
|
2
|
+
export declare const FLAG_HELP: readonly ["Launch flags: --inline | --fullscreen · --accessible · -r, --resume [NAME|UUID] · -c, --continue · --all · --name TITLE", " --model ID [--provider ID] · --effort LEVEL · --permission PRESET", " --yolo | --dangerously-skip-permissions · --image PATH · -h, --help"];
|
package/lib/help.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
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 · -r, --resume [NAME|UUID] · -c, --continue · --all · --name TITLE', ' --model ID [--provider ID] · --effort LEVEL · --permission PRESET', ' --yolo | --dangerously-skip-permissions · --image PATH · -h, --help'
|
|
6
|
+
];
|
package/lib/index.js
CHANGED
|
@@ -6,19 +6,24 @@ import { createTerminalShell } from './application.js';
|
|
|
6
6
|
import { sessionOverlay } from './catalogs.js';
|
|
7
7
|
import { readClipboardImage } from './clipboard-image.js';
|
|
8
8
|
import { editDraftExternally } from './external-editor.js';
|
|
9
|
+
import { FLAG_HELP, VERSION_LINE } from './help.js';
|
|
9
10
|
import { formatSessionList, isSessionId, parseSessionListArgs, sessionMatches, sessionNotFound, } from './session-list.js';
|
|
10
11
|
import { createSessionRuntime } from './session-runtime.js';
|
|
11
12
|
import { TuiRoot } from './tui-root.js';
|
|
12
13
|
export const name = 'dashi';
|
|
13
14
|
export const inject = [
|
|
14
|
-
'agentPresets', 'agents', 'attachments', 'cmdlineArgs', 'commands', 'fs', '
|
|
15
|
-
'sandboxPolicy', 'sessionProjections', 'sessionQuery', 'sessions', 'shell', 'skills', 'tools',
|
|
15
|
+
'agentPresets', 'agents', 'attachments', 'cmdlineArgs', 'commands', 'fs', 'loader', 'permissionPresets',
|
|
16
|
+
'pluginInventory', 'sessionController', 'sandboxPolicy', 'sessionProjections', 'sessionQuery', 'sessions', 'settings', 'shell', 'skills', 'tools',
|
|
16
17
|
];
|
|
17
18
|
const validatedVersions = JSON.parse(readFileSync(new URL('../validated-dsh-versions.json', import.meta.url), 'utf8'));
|
|
18
19
|
function parseArgs(args) {
|
|
19
20
|
let accessible = false;
|
|
20
21
|
let inline = true;
|
|
21
22
|
let name;
|
|
23
|
+
let effort;
|
|
24
|
+
let model;
|
|
25
|
+
let permission;
|
|
26
|
+
let provider;
|
|
22
27
|
let resume;
|
|
23
28
|
let resumeAll = false;
|
|
24
29
|
let resumePicker = false;
|
|
@@ -33,14 +38,23 @@ function parseArgs(args) {
|
|
|
33
38
|
inline = true;
|
|
34
39
|
else if (argument === '--fullscreen')
|
|
35
40
|
inline = false;
|
|
36
|
-
else if (argument === '--name' || argument === '--image'
|
|
41
|
+
else if (argument === '--name' || argument === '--image' || argument === '--effort'
|
|
42
|
+
|| argument === '--model' || argument === '--permission' || argument === '--provider') {
|
|
37
43
|
const value = args[++index];
|
|
38
44
|
if (value === undefined || value === '')
|
|
39
45
|
return undefined;
|
|
40
46
|
if (argument === '--name')
|
|
41
47
|
name = value;
|
|
42
|
-
else
|
|
48
|
+
else if (argument === '--image')
|
|
43
49
|
images.push(value);
|
|
50
|
+
else if (argument === '--effort')
|
|
51
|
+
effort = value;
|
|
52
|
+
else if (argument === '--model')
|
|
53
|
+
model = value;
|
|
54
|
+
else if (argument === '--permission')
|
|
55
|
+
permission = value;
|
|
56
|
+
else
|
|
57
|
+
provider = value;
|
|
44
58
|
}
|
|
45
59
|
else if (argument === '--resume' || argument === '-r') {
|
|
46
60
|
const value = args[index + 1];
|
|
@@ -55,6 +69,9 @@ function parseArgs(args) {
|
|
|
55
69
|
resumeAll = true;
|
|
56
70
|
else if (argument === '--continue' || argument === '-c')
|
|
57
71
|
useContinue = true;
|
|
72
|
+
else if (argument === '--yolo' || argument === '--dangerously-skip-permissions') {
|
|
73
|
+
permission = 'danger-full-access';
|
|
74
|
+
}
|
|
58
75
|
else if (argument?.startsWith('-') === true)
|
|
59
76
|
return undefined;
|
|
60
77
|
else if (argument !== undefined)
|
|
@@ -62,7 +79,8 @@ function parseArgs(args) {
|
|
|
62
79
|
}
|
|
63
80
|
if (resume !== undefined && (resumePicker || useContinue) || resumePicker && useContinue
|
|
64
81
|
|| name !== undefined && (resume !== undefined || resumePicker || useContinue)
|
|
65
|
-
|| resumeAll && resume === undefined && !resumePicker || resumeAll && useContinue
|
|
82
|
+
|| resumeAll && resume === undefined && !resumePicker || resumeAll && useContinue
|
|
83
|
+
|| provider !== undefined && model === undefined)
|
|
66
84
|
return undefined;
|
|
67
85
|
return {
|
|
68
86
|
accessible,
|
|
@@ -71,8 +89,12 @@ function parseArgs(args) {
|
|
|
71
89
|
resumeAll,
|
|
72
90
|
resumePicker,
|
|
73
91
|
...(images.length === 0 ? {} : { images }),
|
|
92
|
+
...(effort === undefined ? {} : { effort }),
|
|
93
|
+
...(model === undefined ? {} : { model }),
|
|
74
94
|
...(name === undefined ? {} : { name }),
|
|
95
|
+
...(permission === undefined ? {} : { permission }),
|
|
75
96
|
...(prompt.length === 0 ? {} : { prompt: prompt.join(' ') }),
|
|
97
|
+
...(provider === undefined ? {} : { provider }),
|
|
76
98
|
...(resume === undefined ? {} : { resume }),
|
|
77
99
|
};
|
|
78
100
|
}
|
|
@@ -133,6 +155,11 @@ export function apply(ctx) {
|
|
|
133
155
|
throw new Error('dashi: the launcher must provide ctx.appExit, ctx.appReady, and ctx.cmdlineArgs');
|
|
134
156
|
}
|
|
135
157
|
const args = cmdline.get();
|
|
158
|
+
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
159
|
+
process.stdout.write(`${VERSION_LINE}\n\nUsage: dashi [options] [prompt]\n\n${FLAG_HELP.join('\n')}\n`);
|
|
160
|
+
exit(0);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
136
163
|
if (args[0] === 'sessions') {
|
|
137
164
|
const options = parseSessionListArgs(args);
|
|
138
165
|
if (options === undefined) {
|
package/lib/input.js
CHANGED
|
@@ -102,6 +102,8 @@ export function installInput(tui, bindings) {
|
|
|
102
102
|
bindings.dispatch({ type: 'overlay-move', offset: -1 });
|
|
103
103
|
else if (matchesKey(data, 'down'))
|
|
104
104
|
bindings.dispatch({ type: 'overlay-move', offset: 1 });
|
|
105
|
+
else if (matchesKey(data, 'ctrl+o'))
|
|
106
|
+
bindings.dispatch({ type: 'toggle-tool-mode' });
|
|
105
107
|
else if (matchesKey(data, 'enter'))
|
|
106
108
|
bindings.dispatch({ type: 'overlay-submit', execute: true });
|
|
107
109
|
else if (matchesKey(data, 'tab'))
|
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
|
+
}
|
package/lib/renderer.js
CHANGED
|
@@ -65,7 +65,12 @@ function cellText(cell, toolMode, accessible, now = Date.now()) {
|
|
|
65
65
|
: accessible ? '[done]' : '◆';
|
|
66
66
|
return `${cell.pending === true ? YELLOW : DIM}${marker} ${safe}${elapsed === undefined ? '' : ` · ${duration(elapsed)}`}${RESET}`;
|
|
67
67
|
}
|
|
68
|
-
case 'context':
|
|
68
|
+
case 'context': {
|
|
69
|
+
if (cell.collapsed !== true || cell.detail === undefined)
|
|
70
|
+
return `${DIM}Context · ${safe}${RESET}`;
|
|
71
|
+
const head = `${DIM}Context · ${plain(cell.detail, true)}${RESET}`;
|
|
72
|
+
return toolMode === 'expanded' ? `${head}\n${safe}` : head;
|
|
73
|
+
}
|
|
69
74
|
case 'shell': return `${DIM}Shell${RESET}\n${safe}`;
|
|
70
75
|
case 'compaction': return `${DIM}${accessible ? '[compacted]' : '↻'} ${safe}${RESET}`;
|
|
71
76
|
case 'reasoning': return cell.collapsed
|
|
@@ -263,9 +268,11 @@ export function createRenderer(options) {
|
|
|
263
268
|
if (overlay.kind === 'details')
|
|
264
269
|
return detailsPanel(overlay, state, width, rows);
|
|
265
270
|
if (overlay.kind === 'info') {
|
|
271
|
+
const cards = overlay.cells?.flatMap(cell => wrapTextWithAnsi(cellText(cell, state.toolMode, accessible), width)) ?? [];
|
|
266
272
|
return [
|
|
267
|
-
`${BOLD_CYAN}${announcement(plain(overlay.title), overlay.lines.length)}${RESET}`,
|
|
273
|
+
`${BOLD_CYAN}${announcement(plain(overlay.title), overlay.lines.length + (overlay.cells?.length ?? 0))}${RESET}`,
|
|
268
274
|
...overlay.lines.flatMap(value => wrapTextWithAnsi(plain(value, true), width)),
|
|
275
|
+
...cards,
|
|
269
276
|
`${DIM}Enter or Esc to close${RESET}`,
|
|
270
277
|
].slice(0, Math.max(2, rows - 3));
|
|
271
278
|
}
|
package/lib/session-runtime.d.ts
CHANGED
|
@@ -4,9 +4,13 @@ import type { TuiRoot } from './tui-root.js';
|
|
|
4
4
|
export interface RootLaunchOptions {
|
|
5
5
|
readonly continue: boolean;
|
|
6
6
|
readonly cwd: string;
|
|
7
|
+
readonly effort?: string;
|
|
7
8
|
readonly name?: string;
|
|
8
9
|
readonly images?: readonly string[];
|
|
10
|
+
readonly model?: string;
|
|
11
|
+
readonly permission?: string;
|
|
9
12
|
readonly prompt?: string;
|
|
13
|
+
readonly provider?: string;
|
|
10
14
|
readonly resume?: string;
|
|
11
15
|
}
|
|
12
16
|
export interface SessionRuntime {
|
package/lib/session-runtime.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import { SessionId, SessionSeq } from '@deepseek-ai/dsh-session';
|
|
3
3
|
import { agentPresetOverlay, completionOptions, contextOverlay, currentModel, modelOverlay, permissionOverlay, searchOverlay, sessionOverlay, statusOverlay, } from './catalogs.js';
|
|
4
|
+
import { diffOverlay } from './diff-view.js';
|
|
4
5
|
import { eventsFromRecords } from './history-records.js';
|
|
5
6
|
import { admitDraftImages, encodeDraftImages, readDraftImages } from './image-input.js';
|
|
6
7
|
import { contextPercent, jobViews, subagentViews } from './presentation.js';
|
|
7
8
|
import { humanPrompt, humanPrompts, inheritedTurn, rewindActionOverlay, rewindOverlay, } from './rewind.js';
|
|
8
9
|
import { createToolPresenter } from './tool-presentation.js';
|
|
9
|
-
import { runHumanShell } from './shell-command.js';
|
|
10
|
+
import { quoteShellWord, runHumanShell } from './shell-command.js';
|
|
10
11
|
import { markdownTranscript } from './session-export.js';
|
|
11
12
|
import { isSessionId, sessionMatches, sessionNotFound } from './session-list.js';
|
|
13
|
+
import { memoryOverlay } from './memory.js';
|
|
12
14
|
import { foldCells, pendingShellCells } from './transcript.js';
|
|
13
15
|
function detail(error) {
|
|
14
16
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -71,6 +73,17 @@ async function firstControl(iterator) {
|
|
|
71
73
|
function usage(raw, expected) {
|
|
72
74
|
return raw.trim() === '' ? undefined : { kind: 'error', text: `usage: ${expected}` };
|
|
73
75
|
}
|
|
76
|
+
function configValue(raw) {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(raw);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function configLayer(value) {
|
|
85
|
+
return value === undefined ? '—' : JSON.stringify(value);
|
|
86
|
+
}
|
|
74
87
|
/** Create or resume the exact current root and own its replaceable terminal binding. */
|
|
75
88
|
export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, decisions) {
|
|
76
89
|
const lifetime = new AbortController();
|
|
@@ -491,6 +504,82 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
491
504
|
return { kind: 'success' };
|
|
492
505
|
},
|
|
493
506
|
},
|
|
507
|
+
{
|
|
508
|
+
name: 'memory', description: 'Open an instruction file loaded by DSH',
|
|
509
|
+
handler: (invocation) => {
|
|
510
|
+
const invalid = ensureCurrent(invocation) ?? usage(invocation.rawInput, '/memory');
|
|
511
|
+
if (invalid !== undefined)
|
|
512
|
+
return invalid;
|
|
513
|
+
dispatch({ type: 'open-overlay', overlay: memoryOverlay(bound.agent) });
|
|
514
|
+
return { kind: 'success' };
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: 'config', description: 'Show or update native DSH settings', input: { hint: '[NAMESPACE KEY=VALUE]' },
|
|
519
|
+
handler: async (invocation) => {
|
|
520
|
+
const stale = ensureCurrent(invocation);
|
|
521
|
+
if (stale !== undefined)
|
|
522
|
+
return stale;
|
|
523
|
+
const raw = invocation.rawInput.trim();
|
|
524
|
+
if (raw === '')
|
|
525
|
+
return { kind: 'success', text: ctx.settings.describe({ redactSecrets: true }).map(item => `${String(item.ns)}\n value: ${configLayer(item.value)}\n base: ${configLayer(item.base)}\n user: ${configLayer(item.user)}`).join('\n') };
|
|
526
|
+
const match = /^(\S+)\s+([^\s=]+)=(.*)$/su.exec(raw);
|
|
527
|
+
if (match?.[1] === undefined || match[2] === undefined || match[3] === undefined) {
|
|
528
|
+
return { kind: 'error', text: 'usage: /config [NAMESPACE KEY=VALUE]' };
|
|
529
|
+
}
|
|
530
|
+
const value = configValue(match[3]);
|
|
531
|
+
if (match[2].includes('.'))
|
|
532
|
+
await ctx.settings.mutate(match[1], [{ op: 'set', path: match[2].split('.'), value }]);
|
|
533
|
+
else
|
|
534
|
+
await ctx.settings.update(match[1], { [match[2]]: value });
|
|
535
|
+
return { kind: 'success', text: `updated ${match[1]}.${match[2]}` };
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'diff', description: 'Show working tree or last-turn file changes', input: { hint: '[turn]' }, handler: async (invocation) => {
|
|
540
|
+
const invalid = ensureCurrent(invocation);
|
|
541
|
+
if (invalid !== undefined)
|
|
542
|
+
return invalid;
|
|
543
|
+
const mode = invocation.rawInput.trim();
|
|
544
|
+
if (mode !== '' && mode !== 'turn')
|
|
545
|
+
return { kind: 'error', text: 'usage: /diff [turn]' };
|
|
546
|
+
dispatch({ type: 'open-overlay', overlay: await diffOverlay(ctx, bound.agent, bound.presenter, mode, lifetime.signal) });
|
|
547
|
+
return { kind: 'success' };
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: 'plugins', description: 'List the running profile plugins',
|
|
552
|
+
handler: async (invocation) => {
|
|
553
|
+
const stale = ensureCurrent(invocation);
|
|
554
|
+
if (stale !== undefined)
|
|
555
|
+
return stale;
|
|
556
|
+
if (invocation.rawInput.trim() !== '')
|
|
557
|
+
return { kind: 'error', text: 'usage: /plugins' };
|
|
558
|
+
const servers = new Map();
|
|
559
|
+
for (const entry of ctx.loader.entries())
|
|
560
|
+
if (entry.options.name === '@deepseek-ai/dsh-mcp-client'
|
|
561
|
+
&& typeof entry.options.config?.serverName === 'string')
|
|
562
|
+
servers.set(entry.id, entry.options.config.serverName);
|
|
563
|
+
const snapshot = await ctx.get('pluginInventory').list();
|
|
564
|
+
return { kind: 'success', text: snapshot.entries.map(entry => `${String(entry.entryId)} · ${entry.moduleName}${servers.has(String(entry.entryId)) ? ` · ${servers.get(String(entry.entryId))}` : ''} · ${entry.enabled ? 'enabled' : 'disabled'} · ${entry.fiberPhase ?? 'inactive'}`).join('\n') };
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: 'plugin', description: 'Run DSH profile plugin management', input: { hint: 'ARGS' },
|
|
569
|
+
handler: async (invocation) => {
|
|
570
|
+
const stale = ensureCurrent(invocation);
|
|
571
|
+
if (stale !== undefined)
|
|
572
|
+
return stale;
|
|
573
|
+
if (invocation.rawInput.trim() === '')
|
|
574
|
+
return { kind: 'error', text: 'usage: /plugin ARGS' };
|
|
575
|
+
const command = `dsh plugin --profile ${quoteShellWord(decodeURIComponent(new URL(ctx.baseUrl).pathname).split('/').at(-2))}${invocation.rawInput}`;
|
|
576
|
+
const [message, result] = await bound.agent.runMaintenance(signal => runHumanShell(ctx, bound.agent, command, signal, 'changes load on the next launch; exit and run dashi again'));
|
|
577
|
+
bound.agent.inject(message);
|
|
578
|
+
await ctx.sessions.flush(bound.agent.session);
|
|
579
|
+
publish(bound);
|
|
580
|
+
return result.exitCode === 0 ? { kind: 'success' } : { kind: 'error', text: `dsh plugin exited ${String(result.exitCode ?? result.signal ?? 'without status')}` };
|
|
581
|
+
},
|
|
582
|
+
},
|
|
494
583
|
{
|
|
495
584
|
name: 'new', description: 'Start a new session', input: { hint: '[--name TITLE]' },
|
|
496
585
|
handler: async (invocation) => {
|
|
@@ -775,6 +864,34 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
775
864
|
await bound.controlPump;
|
|
776
865
|
}
|
|
777
866
|
await install(binding);
|
|
867
|
+
if (options.model !== undefined || options.effort !== undefined) {
|
|
868
|
+
const catalog = await ctx.sessionController.modelCatalog();
|
|
869
|
+
const currentSelection = currentModel(ctx, binding.agent) ?? catalog.default;
|
|
870
|
+
const model = options.model ?? currentSelection.model;
|
|
871
|
+
const candidates = catalog.groups.filter(group => group.models.some(item => item.id === model));
|
|
872
|
+
if (options.provider === undefined && candidates.length > 1) {
|
|
873
|
+
throw new Error(`model "${model}" is available from multiple providers: ${candidates.map(group => group.id).join(', ')}; pass --provider`);
|
|
874
|
+
}
|
|
875
|
+
const result = await ctx.sessionController.selectModel({
|
|
876
|
+
sessionId: binding.agent.id,
|
|
877
|
+
model,
|
|
878
|
+
provider: options.provider ?? candidates[0]?.id ?? currentSelection.provider,
|
|
879
|
+
...(options.effort === undefined ? {} : { reasoningEffort: options.effort }),
|
|
880
|
+
});
|
|
881
|
+
const { effort: _previousEffort, ...root } = binding.root;
|
|
882
|
+
binding.root = {
|
|
883
|
+
...root, model: result.selected.model, provider: result.selected.provider,
|
|
884
|
+
...(result.selected.reasoningEffort === undefined ? {} : { effort: result.selected.reasoningEffort }),
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
if (options.permission !== undefined) {
|
|
888
|
+
const execution = await ctx.commands.execute(binding.agent, `/permission ${options.permission}`, [], lifetime.signal);
|
|
889
|
+
if (execution === undefined)
|
|
890
|
+
throw new Error('DSH supplied no /permission command');
|
|
891
|
+
if (execution.result.kind === 'error')
|
|
892
|
+
throw new Error(execution.result.text ?? 'permission change failed');
|
|
893
|
+
binding.root = { ...binding.root, permission: options.permission };
|
|
894
|
+
}
|
|
778
895
|
tuiRoot.bind(binding.agent);
|
|
779
896
|
const activate = async (value) => {
|
|
780
897
|
try {
|
|
@@ -999,7 +1116,8 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
999
1116
|
if (attachments.length > 0)
|
|
1000
1117
|
throw new Error('!command does not accept image attachments');
|
|
1001
1118
|
await bound.agent.runMaintenance(async (signal) => {
|
|
1002
|
-
|
|
1119
|
+
const [message] = await runHumanShell(ctx, bound.agent, command, signal);
|
|
1120
|
+
bound.agent.inject(message);
|
|
1003
1121
|
await ctx.sessions.flush(bound.agent.session);
|
|
1004
1122
|
});
|
|
1005
1123
|
publish(bound);
|
package/lib/shell-command.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ import type { ShellRunResult } from '@deepseek-ai/dsh-shell';
|
|
|
5
5
|
export declare const HUMAN_SHELL_TIMEOUT_MS = 30000;
|
|
6
6
|
export declare const HUMAN_SHELL_STREAM_BYTES: number;
|
|
7
7
|
export declare function shellResultText(command: string, result: ShellRunResult): string;
|
|
8
|
+
export declare function quoteShellWord(value: string): string;
|
|
8
9
|
/** Run an explicit human command through DSH and identify its next-step context. */
|
|
9
|
-
export declare function runHumanShell(ctx: Context, agent: Agent, command: string, signal: AbortSignal): Promise<UserMessage>;
|
|
10
|
+
export declare function runHumanShell(ctx: Context, agent: Agent, command: string, signal: AbortSignal, success?: string): Promise<readonly [UserMessage, ShellRunResult]>;
|
package/lib/shell-command.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { boundContextSummary, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
|
|
2
3
|
export const HUMAN_SHELL_TIMEOUT_MS = 30_000;
|
|
3
4
|
export const HUMAN_SHELL_STREAM_BYTES = 32 * 1024;
|
|
4
5
|
function tail(output) {
|
|
@@ -42,21 +43,23 @@ export function shellResultText(command, result) {
|
|
|
42
43
|
}
|
|
43
44
|
return parts.join('\n');
|
|
44
45
|
}
|
|
46
|
+
export function quoteShellWord(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
|
|
45
47
|
/** Run an explicit human command through DSH and identify its next-step context. */
|
|
46
|
-
export async function runHumanShell(ctx, agent, command, signal) {
|
|
48
|
+
export async function runHumanShell(ctx, agent, command, signal, success) {
|
|
47
49
|
const result = await ctx.shell.run(ctx.shell.resolve({
|
|
48
50
|
command,
|
|
51
|
+
env: { DSH_HOME: resolveDshHome() },
|
|
49
52
|
sandboxPolicy: ctx.sandboxPolicy.resolve({ session: agent.session }),
|
|
50
53
|
signal,
|
|
51
54
|
stdoutMaxBytes: HUMAN_SHELL_STREAM_BYTES,
|
|
52
55
|
timeoutMs: HUMAN_SHELL_TIMEOUT_MS,
|
|
53
56
|
workdir: agent.session.header.cwd,
|
|
54
57
|
}));
|
|
55
|
-
return createUserMessage({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
return [createUserMessage({
|
|
59
|
+
content: [{ type: 'text', text: shellResultText(command, result) + (result.exitCode === 0 && success ? `\n${success}` : '') }],
|
|
60
|
+
source: {
|
|
61
|
+
form: 'notice', kind: 'plugin', plugin: 'dashi',
|
|
62
|
+
summary: boundContextSummary(`Shell: ${command.replace(/\s+/gu, ' ').trim()}`),
|
|
63
|
+
},
|
|
64
|
+
}), result];
|
|
62
65
|
}
|
package/lib/state.d.ts
CHANGED
|
@@ -137,6 +137,9 @@ export type OverlayValue = {
|
|
|
137
137
|
readonly effort?: string;
|
|
138
138
|
readonly model: string;
|
|
139
139
|
readonly provider: string;
|
|
140
|
+
} | {
|
|
141
|
+
readonly kind: 'open-file';
|
|
142
|
+
readonly path: string;
|
|
140
143
|
} | {
|
|
141
144
|
readonly interrupt?: true;
|
|
142
145
|
readonly kind: 'fork';
|
|
@@ -197,9 +200,10 @@ export type Overlay = {
|
|
|
197
200
|
readonly kind: 'list';
|
|
198
201
|
readonly notice?: string;
|
|
199
202
|
readonly options: readonly OverlayOption[];
|
|
200
|
-
readonly purpose: 'agents' | 'completion' | 'model' | 'permission' | 'resume' | 'rewind' | 'rewind-action' | 'search';
|
|
203
|
+
readonly purpose: 'agents' | 'completion' | 'memory' | 'model' | 'permission' | 'resume' | 'rewind' | 'rewind-action' | 'search';
|
|
201
204
|
readonly title: string;
|
|
202
205
|
} | {
|
|
206
|
+
readonly cells?: readonly TerminalCell[];
|
|
203
207
|
readonly kind: 'info';
|
|
204
208
|
readonly lines: readonly string[];
|
|
205
209
|
readonly title: string;
|
package/lib/state.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FLAG_HELP, VERSION_LINE } from './help.js';
|
|
1
2
|
export function initialViewState(cwd, inline, root, cells = [], prompts = [], historyHasMore = false, attachments = []) {
|
|
2
3
|
return {
|
|
3
4
|
attachments, cells, composer: '', cwd, decisions: [], exitArmed: false, historyHasMore, inline,
|
|
@@ -31,6 +32,7 @@ function help() {
|
|
|
31
32
|
kind: 'info',
|
|
32
33
|
title: 'Help',
|
|
33
34
|
lines: [
|
|
35
|
+
VERSION_LINE,
|
|
34
36
|
'Enter send · Ctrl+J newline · Ctrl+T steer/next turn',
|
|
35
37
|
'Esc interrupt · Ctrl+C clear/interrupt · Ctrl+C/D again within 2s to exit',
|
|
36
38
|
'/ and @ complete live · Tab insert · Shift+Tab permission · Ctrl+O cards',
|
|
@@ -39,8 +41,9 @@ function help() {
|
|
|
39
41
|
'Ctrl+B activity details for jobs and subagents',
|
|
40
42
|
'@ paths require DSH file-reference · Ctrl+V pastes an image · Backspace removes it',
|
|
41
43
|
'Ctrl+S stash · Ctrl+G editor · F1 help · Ctrl+L redraw · Ctrl+Z suspend',
|
|
42
|
-
'/new /clear /resume [--all] [NAME|UUID] /fork /rewind /rename /model /permission /agents /queue
|
|
43
|
-
'/status /context /tasks /history /export /copy /exit',
|
|
44
|
+
'/new /clear /resume [--all] [NAME|UUID] /fork /rewind /rename /model /permission /agents /queue',
|
|
45
|
+
'/status /context /memory /config /diff /plugins /plugin /tasks /history /export /copy /exit',
|
|
46
|
+
...FLAG_HELP,
|
|
44
47
|
],
|
|
45
48
|
};
|
|
46
49
|
}
|
package/lib/transcript.js
CHANGED
|
@@ -26,6 +26,24 @@ function shellCell(message, pending) {
|
|
|
26
26
|
key: `shell:${String(message.id)}`, kind: 'shell', pending, text: messageText(message.content),
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
function collapsedContextCell(message, key) {
|
|
30
|
+
const source = message.source;
|
|
31
|
+
const form = source.form;
|
|
32
|
+
if (source.kind !== 'agent-instructions'
|
|
33
|
+
&& (source.kind !== 'plugin' || !['instructions', 'catalog', 'snapshot', 'notice', 'recall'].includes(String(form)))) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const text = messageText(message.content);
|
|
37
|
+
const lines = text.split(/\r?\n/u);
|
|
38
|
+
const first = form === 'notice' && typeof source.summary === 'string' ? source.summary : lines[0] ?? '';
|
|
39
|
+
return {
|
|
40
|
+
collapsed: true,
|
|
41
|
+
detail: `${String(form)} · ${first.trim()} · ${String(lines.length)} lines`,
|
|
42
|
+
key,
|
|
43
|
+
kind: 'context',
|
|
44
|
+
text,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
29
47
|
/** Project DSH-owned pending injections beside the durable transcript. */
|
|
30
48
|
export function pendingShellCells(messages) {
|
|
31
49
|
return messages.filter(isDashiShell).map(message => shellCell(message, true));
|
|
@@ -132,9 +150,10 @@ export function foldCells(events, presenter = createToolPresenter(() => undefine
|
|
|
132
150
|
break;
|
|
133
151
|
}
|
|
134
152
|
const text = messageText(event.data.content);
|
|
153
|
+
const context = collapsedContextCell(event.data, `${event.seq}:context`);
|
|
135
154
|
cells.push(isDashiShell(event.data)
|
|
136
155
|
? shellCell(event.data, false)
|
|
137
|
-
: {
|
|
156
|
+
: context ?? {
|
|
138
157
|
key: `${event.seq}:user`,
|
|
139
158
|
kind: event.data.source.kind === 'user' ? 'user' : 'context',
|
|
140
159
|
text,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antst/dashi",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
4
4
|
"description": "A terminal UI for DeepSeek Harness",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "4.0.2",
|
|
40
|
+
"@deepseek-ai/cordis-plugin-loader": "1.0.3",
|
|
40
41
|
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-agent-instructions": "0.1.2-rc.1",
|
|
41
43
|
"@deepseek-ai/dsh-agent-presets": "0.1.2-rc.1",
|
|
42
44
|
"@deepseek-ai/dsh-api-session-controller": "0.1.2-rc.1",
|
|
43
45
|
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
|
@@ -46,6 +48,8 @@
|
|
|
46
48
|
"@deepseek-ai/dsh-compaction": "0.1.2-rc.1",
|
|
47
49
|
"@deepseek-ai/dsh-file-reference": "0.1.2-rc.1",
|
|
48
50
|
"@deepseek-ai/dsh-fs": "0.1.2-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-host-plugin-inventory": "0.1.2-rc.1",
|
|
49
53
|
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
50
54
|
"@deepseek-ai/dsh-permission-presets": "0.1.2-rc.1",
|
|
51
55
|
"@deepseek-ai/dsh-plan-mode": "0.1.2-rc.1",
|
|
@@ -53,6 +57,7 @@
|
|
|
53
57
|
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
54
58
|
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
55
59
|
"@deepseek-ai/dsh-session-query": "0.1.2-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
|
56
61
|
"@deepseek-ai/dsh-shell": "0.1.2-rc.1",
|
|
57
62
|
"@deepseek-ai/dsh-skill": "0.1.2-rc.1",
|
|
58
63
|
"@deepseek-ai/dsh-subagent": "0.1.2-rc.1",
|