@antst/dashi 0.1.0-alpha.10 → 0.1.0-alpha.11
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 +1 -0
- package/lib/application.js +1 -1
- package/lib/catalogs.d.ts +2 -0
- package/lib/catalogs.js +24 -0
- package/lib/help.d.ts +1 -1
- package/lib/help.js +1 -1
- package/lib/index.js +28 -1
- package/lib/session-runtime.d.ts +2 -0
- package/lib/session-runtime.js +155 -18
- package/lib/shell-command.d.ts +1 -1
- package/lib/shell-command.js +3 -2
- package/lib/state.d.ts +4 -1
- package/lib/state.js +5 -4
- package/package.json +4 -1
package/lib/application.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface TerminalShellOptions {
|
|
|
20
20
|
readonly initialHasMore?: boolean;
|
|
21
21
|
readonly initialPrompts?: ViewState['prompts'];
|
|
22
22
|
readonly initialRoot?: ViewState['root'];
|
|
23
|
+
readonly initialToolMode?: ViewState['toolMode'];
|
|
23
24
|
readonly interrupt?: () => void;
|
|
24
25
|
readonly inline: boolean;
|
|
25
26
|
readonly loadHistory?: (rootId: string) => Promise<void>;
|
package/lib/application.js
CHANGED
|
@@ -16,7 +16,7 @@ function suspendProcess() {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
export function createTerminalShell(options) {
|
|
19
|
-
let state = initialViewState(options.cwd, options.inline, options.initialRoot, options.initialCells, options.initialPrompts, options.initialHasMore, options.initialAttachments);
|
|
19
|
+
let state = initialViewState(options.cwd, options.inline, options.initialRoot, options.initialCells, options.initialPrompts, options.initialHasMore, options.initialAttachments, options.initialToolMode);
|
|
20
20
|
let accepting = true;
|
|
21
21
|
let failed = false;
|
|
22
22
|
let queue = Promise.resolve();
|
package/lib/catalogs.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export declare function permissionOverlay(ctx: Context, agent: Agent): Overlay;
|
|
|
18
18
|
export declare function statusOverlay(ctx: Context, agent: Agent, root: RootView): Overlay;
|
|
19
19
|
/** Show DSH's heuristic next-request composition without treating it as an exact total. */
|
|
20
20
|
export declare function contextOverlay(ctx: Context, agent: Agent): Overlay;
|
|
21
|
+
/** Read DSH's whole-log token and timing projections without folding session history. */
|
|
22
|
+
export declare function usageOverlay(ctx: Context, agent: Agent): Overlay;
|
|
21
23
|
/** Build the native agent-preset picker; broken presets are visible elsewhere, not selectable here. */
|
|
22
24
|
export declare function agentPresetOverlay(presets: readonly AgentPreset[], current: string, blank: boolean): Overlay;
|
|
23
25
|
/** Build bounded same-cwd prompt-search rows from DSH query hits. */
|
package/lib/catalogs.js
CHANGED
|
@@ -153,6 +153,9 @@ export function permissionOverlay(ctx, agent) {
|
|
|
153
153
|
function number(value) {
|
|
154
154
|
return typeof value === 'number' ? value.toLocaleString('en-US') : 'unavailable';
|
|
155
155
|
}
|
|
156
|
+
function milliseconds(value) {
|
|
157
|
+
return typeof value === 'number' ? `${number(value)} ms` : 'unavailable';
|
|
158
|
+
}
|
|
156
159
|
/** Read DSH-owned root facts for /status in one projection cut. */
|
|
157
160
|
export function statusOverlay(ctx, agent, root) {
|
|
158
161
|
const values = ctx.sessionProjections.snapshot(agent.session, [
|
|
@@ -196,6 +199,27 @@ export function contextOverlay(ctx, agent) {
|
|
|
196
199
|
title: 'Context',
|
|
197
200
|
};
|
|
198
201
|
}
|
|
202
|
+
/** Read DSH's whole-log token and timing projections without folding session history. */
|
|
203
|
+
export function usageOverlay(ctx, agent) {
|
|
204
|
+
const values = ctx.sessionProjections.snapshot(agent.session, ['tokenUsage', 'sessionStats']).values;
|
|
205
|
+
const tokens = values.tokenUsage;
|
|
206
|
+
const stats = values.sessionStats;
|
|
207
|
+
const measured = stats === undefined ? undefined : stats.llmMs + stats.toolMs;
|
|
208
|
+
return {
|
|
209
|
+
kind: 'info',
|
|
210
|
+
lines: [
|
|
211
|
+
`Input tokens: ${number(tokens?.uncachedInputTokens)}`,
|
|
212
|
+
`Output tokens: ${number(tokens?.outputTokens)}`,
|
|
213
|
+
`Cache read: ${number(tokens?.cacheReadTokens)}`,
|
|
214
|
+
`Cache write: ${number(tokens?.cacheWriteTokens)}`,
|
|
215
|
+
`Turns: ${number(stats?.turns)} · Steps: ${number(stats?.steps)}`,
|
|
216
|
+
`Model time: ${milliseconds(stats?.llmMs)}`,
|
|
217
|
+
`Tool time: ${milliseconds(stats?.toolMs)}`,
|
|
218
|
+
`Measured wall time: ${milliseconds(measured)}`,
|
|
219
|
+
],
|
|
220
|
+
title: 'Usage',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
199
223
|
/** Build the native agent-preset picker; broken presets are visible elsewhere, not selectable here. */
|
|
200
224
|
export function agentPresetOverlay(presets, current, blank) {
|
|
201
225
|
const options = presets.filter(preset => preset.broken === undefined).map(preset => ({
|
package/lib/help.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
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 · --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 · -h, --help"];
|
|
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
CHANGED
|
@@ -2,5 +2,5 @@ import dashi from '../package.json' with { type: 'json' };
|
|
|
2
2
|
import dsh from '@deepseek-ai/dsh-cmdline/package.json' with { type: 'json' };
|
|
3
3
|
export const VERSION_LINE = `dashi ${dashi.version} on DSH ${dsh.version}`;
|
|
4
4
|
export const FLAG_HELP = [
|
|
5
|
-
'Launch flags: --inline | --fullscreen · --accessible · -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 · -h, --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
6
|
];
|
package/lib/index.js
CHANGED
|
@@ -13,11 +13,12 @@ import { TuiRoot } from './tui-root.js';
|
|
|
13
13
|
export const name = 'dashi';
|
|
14
14
|
export const inject = [
|
|
15
15
|
'agentPresets', 'agents', 'attachments', 'authorization', 'cmdlineArgs', 'commands', 'credentials', 'fs', 'jobs', 'loader', 'permissionPresets', 'subagents',
|
|
16
|
-
'pluginInventory', 'sessionController', 'sandboxPolicy', 'sessionProjections', 'sessionQuery', 'sessions', 'settings', 'shell', 'skills', 'tools',
|
|
16
|
+
'pluginInventory', 'sessionController', 'sandboxPolicy', 'sessionProjections', 'sessionQuery', 'sessions', 'settings', 'shell', 'skills', 'systemPrompt', 'tools',
|
|
17
17
|
];
|
|
18
18
|
const validatedVersions = JSON.parse(readFileSync(new URL('../validated-dsh-versions.json', import.meta.url), 'utf8'));
|
|
19
19
|
function parseArgs(args) {
|
|
20
20
|
let accessible = false;
|
|
21
|
+
let argumentError;
|
|
21
22
|
let agentPreset, sessionId;
|
|
22
23
|
let forkSession = false;
|
|
23
24
|
let inline = true;
|
|
@@ -31,7 +32,9 @@ function parseArgs(args) {
|
|
|
31
32
|
let resume;
|
|
32
33
|
let resumeAll = false;
|
|
33
34
|
let resumePicker = false;
|
|
35
|
+
let systemPrompt, appendSystemPrompt;
|
|
34
36
|
let useContinue = false;
|
|
37
|
+
let verbose = false;
|
|
35
38
|
const images = [];
|
|
36
39
|
const prompt = [];
|
|
37
40
|
for (let index = 0; index < args.length; index++) {
|
|
@@ -43,6 +46,7 @@ function parseArgs(args) {
|
|
|
43
46
|
else if (argument === '--fullscreen')
|
|
44
47
|
inline = false;
|
|
45
48
|
else if (['--name', '-n', '--agent', '--image', '--effort', '--model', '--permission', '--provider', '--tools', '--disallowedTools',
|
|
49
|
+
'--system-prompt', '--system-prompt-file', '--append-system-prompt', '--append-system-prompt-file',
|
|
46
50
|
'--session-id'].includes(argument ?? '')) {
|
|
47
51
|
const value = args[++index];
|
|
48
52
|
if (value === undefined || value === '')
|
|
@@ -70,6 +74,21 @@ function parseArgs(args) {
|
|
|
70
74
|
else
|
|
71
75
|
disallowedTools = names;
|
|
72
76
|
}
|
|
77
|
+
else if (argument?.includes('system-prompt') === true) {
|
|
78
|
+
let text = value;
|
|
79
|
+
if (argument.endsWith('-file')) {
|
|
80
|
+
try {
|
|
81
|
+
text = readFileSync(value, 'utf8');
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
argumentError = error instanceof Error ? error.message : String(error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (argument.startsWith('--append'))
|
|
88
|
+
appendSystemPrompt = text;
|
|
89
|
+
else
|
|
90
|
+
systemPrompt = text;
|
|
91
|
+
}
|
|
73
92
|
else
|
|
74
93
|
provider = value;
|
|
75
94
|
}
|
|
@@ -88,6 +107,8 @@ function parseArgs(args) {
|
|
|
88
107
|
useContinue = true;
|
|
89
108
|
else if (argument === '--fork-session')
|
|
90
109
|
forkSession = true;
|
|
110
|
+
else if (argument === '--verbose')
|
|
111
|
+
verbose = true;
|
|
91
112
|
else if (argument === '--yolo' || argument === '--dangerously-skip-permissions') {
|
|
92
113
|
permission = 'danger-full-access';
|
|
93
114
|
}
|
|
@@ -106,6 +127,8 @@ function parseArgs(args) {
|
|
|
106
127
|
return undefined;
|
|
107
128
|
return {
|
|
108
129
|
accessible,
|
|
130
|
+
...(argumentError === undefined ? {} : { argumentError }),
|
|
131
|
+
...(appendSystemPrompt === undefined ? {} : { appendSystemPrompt }),
|
|
109
132
|
continue: useContinue,
|
|
110
133
|
...(agentPreset === undefined ? {} : { agentPreset }), forkSession, inline,
|
|
111
134
|
resumeAll,
|
|
@@ -120,6 +143,7 @@ function parseArgs(args) {
|
|
|
120
143
|
...(tools === undefined ? {} : { tools }),
|
|
121
144
|
...(disallowedTools === undefined ? {} : { disallowedTools }),
|
|
122
145
|
...(resume === undefined ? {} : { resume }), ...(sessionId === undefined ? {} : { sessionId }),
|
|
146
|
+
...(systemPrompt === undefined ? {} : { systemPrompt }), verbose,
|
|
123
147
|
};
|
|
124
148
|
}
|
|
125
149
|
function packageVersion(path) {
|
|
@@ -213,6 +237,8 @@ export function apply(ctx) {
|
|
|
213
237
|
const announce = ready.onReady(() => {
|
|
214
238
|
warnDshVersion();
|
|
215
239
|
boot = (async () => {
|
|
240
|
+
if (parsed.argumentError !== undefined)
|
|
241
|
+
throw new LaunchArgumentError(parsed.argumentError);
|
|
216
242
|
const cwd = process.cwd();
|
|
217
243
|
const makeRuntime = (options) => createSessionRuntime(ctx, options, tuiRoot, action => { shell?.dispatch(action); }, {
|
|
218
244
|
cancel: id => { shell?.cancelDecision(id); },
|
|
@@ -266,6 +292,7 @@ export function apply(ctx) {
|
|
|
266
292
|
initialAttachments: initial.initialAttachments, initialCells: initial.initialCells, initialHasMore: initial.initialHasMore,
|
|
267
293
|
initialPrompts: initial.initialPrompts, initialRoot: initial.root,
|
|
268
294
|
}),
|
|
295
|
+
...(parsed.verbose ? { initialToolMode: 'expanded' } : {}),
|
|
269
296
|
inline: parsed.inline || parsed.accessible,
|
|
270
297
|
interrupt: () => { runtime?.interrupt(); },
|
|
271
298
|
loadHistory: rootId => runtime?.loadHistory(rootId) ?? Promise.resolve(),
|
package/lib/session-runtime.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { TuiRoot } from './tui-root.js';
|
|
|
4
4
|
export interface RootLaunchOptions {
|
|
5
5
|
readonly agentPreset?: string;
|
|
6
6
|
readonly continue: boolean;
|
|
7
|
+
readonly appendSystemPrompt?: string;
|
|
7
8
|
readonly cwd: string;
|
|
8
9
|
readonly effort?: string;
|
|
9
10
|
readonly forkSession: boolean;
|
|
@@ -17,6 +18,7 @@ export interface RootLaunchOptions {
|
|
|
17
18
|
readonly disallowedTools?: readonly string[];
|
|
18
19
|
readonly resume?: string;
|
|
19
20
|
readonly sessionId?: string;
|
|
21
|
+
readonly systemPrompt?: string;
|
|
20
22
|
}
|
|
21
23
|
export declare class LaunchArgumentError extends Error {
|
|
22
24
|
}
|
package/lib/session-runtime.js
CHANGED
|
@@ -2,9 +2,9 @@ import { basename } from 'node:path';
|
|
|
2
2
|
import { AuthorizationDeclinedError } from '@deepseek-ai/dsh-authorization';
|
|
3
3
|
import { parseCredentialKey } from '@deepseek-ai/dsh-credentials';
|
|
4
4
|
import { JobId } from '@deepseek-ai/dsh-jobs';
|
|
5
|
-
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
5
|
+
import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm';
|
|
6
6
|
import { SessionId, SessionSeq } from '@deepseek-ai/dsh-session';
|
|
7
|
-
import { agentPresetOverlay, completionOptions, contextOverlay, currentModel, modelOverlay, permissionOverlay, searchOverlay, sessionOverlay, skillOverlay, statusOverlay, } from './catalogs.js';
|
|
7
|
+
import { agentPresetOverlay, completionOptions, contextOverlay, currentModel, modelOverlay, permissionOverlay, searchOverlay, sessionOverlay, skillOverlay, statusOverlay, usageOverlay, } from './catalogs.js';
|
|
8
8
|
import { diffOverlay } from './diff-view.js';
|
|
9
9
|
import { eventsFromRecords } from './history-records.js';
|
|
10
10
|
import { admitDraftImages, encodeDraftImages, readDraftImages } from './image-input.js';
|
|
@@ -21,6 +21,7 @@ export class LaunchArgumentError extends Error {
|
|
|
21
21
|
function starterAgents(cwd) {
|
|
22
22
|
return `# ${basename(cwd)}\n\n## Working agreement\n\n- Add project-specific architecture guidance.\n- Add required build, test, and lint commands.\n- Add repository conventions and constraints.\n`;
|
|
23
23
|
}
|
|
24
|
+
const RECAP_PROMPT = 'Summarize this conversation so far in ten lines: goal, decisions, open items.';
|
|
24
25
|
function detail(error) {
|
|
25
26
|
return error instanceof Error ? error.message : String(error);
|
|
26
27
|
}
|
|
@@ -82,6 +83,16 @@ async function firstControl(iterator) {
|
|
|
82
83
|
function usage(raw, expected) {
|
|
83
84
|
return raw.trim() === '' ? undefined : { kind: 'error', text: `usage: ${expected}` };
|
|
84
85
|
}
|
|
86
|
+
function loopInterval(seconds) {
|
|
87
|
+
if (seconds % 3_600 === 0)
|
|
88
|
+
return `${String(seconds / 3_600)}h`;
|
|
89
|
+
return seconds % 60 === 0 ? `${String(seconds / 60)}m` : `${String(seconds)}s`;
|
|
90
|
+
}
|
|
91
|
+
function loopLine(schedule) {
|
|
92
|
+
const rule = schedule.kind === 'every' ? `every ${loopInterval(schedule.everySeconds)}`
|
|
93
|
+
: schedule.kind === 'after' ? `after ${String(schedule.afterSeconds)}s` : `at ${schedule.scheduledAt}`;
|
|
94
|
+
return `${String(schedule.id)} · ${rule} · ${schedule.prompt}`;
|
|
95
|
+
}
|
|
85
96
|
function configValue(raw) {
|
|
86
97
|
try {
|
|
87
98
|
return JSON.parse(raw);
|
|
@@ -114,18 +125,26 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
114
125
|
if ('error' in resolved)
|
|
115
126
|
throw resolved.error;
|
|
116
127
|
const agent = resolved.agent;
|
|
117
|
-
|
|
128
|
+
const disposers = [];
|
|
118
129
|
if (options.tools !== undefined || options.disallowedTools !== undefined) {
|
|
119
130
|
try {
|
|
120
|
-
|
|
131
|
+
disposers.push(agent.ctx.tools.restrict({
|
|
121
132
|
...(options.tools === undefined ? {} : { allow: options.tools }),
|
|
122
133
|
...(options.disallowedTools === undefined ? {} : { deny: options.disallowedTools }),
|
|
123
|
-
});
|
|
134
|
+
}));
|
|
124
135
|
}
|
|
125
136
|
catch (error) {
|
|
126
137
|
throw new LaunchArgumentError(detail(error));
|
|
127
138
|
}
|
|
128
139
|
}
|
|
140
|
+
if (options.systemPrompt !== undefined)
|
|
141
|
+
disposers.push(agent.ctx.systemPrompt.section({
|
|
142
|
+
complete: true, name: 'dashi:system-prompt', order: Number.MAX_SAFE_INTEGER, text: options.systemPrompt,
|
|
143
|
+
}));
|
|
144
|
+
if (options.appendSystemPrompt !== undefined)
|
|
145
|
+
disposers.push(agent.ctx.systemPrompt.section({
|
|
146
|
+
name: 'dashi:append-system-prompt', order: Number.MAX_SAFE_INTEGER, text: options.appendSystemPrompt,
|
|
147
|
+
}));
|
|
129
148
|
let title;
|
|
130
149
|
if (name !== undefined)
|
|
131
150
|
title = (await ctx.sessionController.rename({ sessionId, title: name })).title;
|
|
@@ -149,7 +168,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
149
168
|
agent,
|
|
150
169
|
controlIterator,
|
|
151
170
|
cursor: opening.cursor,
|
|
152
|
-
disposers
|
|
171
|
+
disposers,
|
|
153
172
|
events: eventsFromRecords(opening.records),
|
|
154
173
|
hasMore: opening.hasMore,
|
|
155
174
|
iterator,
|
|
@@ -511,6 +530,79 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
511
530
|
const result = ctx.jobs.kill(JobId(match[1]), bound.agent, 'stopped by /tasks');
|
|
512
531
|
return { kind: 'success', text: result === 'requested' ? `stopping ${match[1]}` : `${match[1]} already finished` };
|
|
513
532
|
};
|
|
533
|
+
const loop = async (invocation) => {
|
|
534
|
+
const stale = ensureCurrent(invocation);
|
|
535
|
+
if (stale !== undefined)
|
|
536
|
+
return stale;
|
|
537
|
+
const raw = invocation.rawInput.trim();
|
|
538
|
+
const stop = /^stop\s+(\S+)$/u.exec(raw);
|
|
539
|
+
const create = /^(\d+)([mh])\s+(.+)$/su.exec(raw);
|
|
540
|
+
if (raw !== '' && stop === null && create === null) {
|
|
541
|
+
return { kind: 'error', text: 'usage: /loop [INTERVAL TEXT | stop ID]' };
|
|
542
|
+
}
|
|
543
|
+
const name = raw === '' ? 'schedule_list' : stop === null ? 'schedule_create' : 'schedule_delete';
|
|
544
|
+
const args = raw === '' ? {} : stop === null ? {
|
|
545
|
+
every_seconds: Number(create[1]) * (create[2] === 'h' ? 3_600 : 60), prompt: create[3],
|
|
546
|
+
} : { id: stop[1] };
|
|
547
|
+
const result = await ctx.agents.withInitiator(bound.agent, () => ctx.tools.execute({
|
|
548
|
+
agent: bound.agent, arguments: args, callId: ToolCallId(`${String(invocation.commandId)}:loop`),
|
|
549
|
+
name, signal: invocation.signal,
|
|
550
|
+
}));
|
|
551
|
+
if (result.isError)
|
|
552
|
+
return { kind: 'error', text: result.error.message };
|
|
553
|
+
const value = result.value;
|
|
554
|
+
if (!Array.isArray(value) && 'message' in value)
|
|
555
|
+
return { kind: 'error', text: value.message };
|
|
556
|
+
if (Array.isArray(value))
|
|
557
|
+
return { kind: 'success', text: value.length === 0 ? 'no active schedules' : value.map(loopLine).join('\n') };
|
|
558
|
+
if ('deleted' in value)
|
|
559
|
+
return value.deleted ? { kind: 'success', text: `stopped ${String(value.id)}` }
|
|
560
|
+
: { kind: 'error', text: `schedule ${String(value.id)} not found` };
|
|
561
|
+
return { kind: 'success', text: `scheduled ${loopLine(value)}` };
|
|
562
|
+
};
|
|
563
|
+
const askAside = async (invocation, prompt = invocation.rawInput.trim()) => {
|
|
564
|
+
const stale = ensureCurrent(invocation);
|
|
565
|
+
if (stale !== undefined)
|
|
566
|
+
return stale;
|
|
567
|
+
if (prompt === '')
|
|
568
|
+
return { kind: 'error', text: 'usage: /btw TEXT' };
|
|
569
|
+
const forked = await ctx.sessionController.fork({ sessionId: bound.agent.id });
|
|
570
|
+
const resolved = await ctx.sessionController.resolveAgent(forked.sessionId);
|
|
571
|
+
if ('error' in resolved)
|
|
572
|
+
throw resolved.error;
|
|
573
|
+
const child = resolved.agent;
|
|
574
|
+
const turn = inheritedTurn(child.session.snapshotEvents(), child.session.inheritedEventCount);
|
|
575
|
+
if (turn === undefined)
|
|
576
|
+
throw new Error('the fork contains no completed turn');
|
|
577
|
+
const iterator = ctx.sessionController.follow({
|
|
578
|
+
address: { kind: 'session', sessionId: child.id }, maxMessages: 50,
|
|
579
|
+
}, invocation.signal)[Symbol.asyncIterator]();
|
|
580
|
+
await firstSnapshot(iterator);
|
|
581
|
+
await ctx.sessionController.rename({
|
|
582
|
+
sessionId: child.id, title: `btw · ${prompt.split(/\s+/u).slice(0, 6).join(' ')}`,
|
|
583
|
+
});
|
|
584
|
+
const from = child.session.seq;
|
|
585
|
+
child.steer(createUserMessage({ content: [{ type: 'text', text: prompt }], source: { kind: 'user' } }));
|
|
586
|
+
try {
|
|
587
|
+
for (;;) {
|
|
588
|
+
const next = await iterator.next();
|
|
589
|
+
if (next.done === true)
|
|
590
|
+
throw new Error('DSH session follow ended before the side turn completed');
|
|
591
|
+
if (next.value.type !== 'snapshot' && eventsFromRecords([next.value])[0]?.type === 'turn/end')
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
await iterator.return?.();
|
|
597
|
+
}
|
|
598
|
+
await ctx.sessions.flush(child.session);
|
|
599
|
+
const cells = foldCells(child.session.snapshotEvents(from), createToolPresenter(tool => ctx.tools.get(tool, child)))
|
|
600
|
+
.filter(cell => cell.kind !== 'user');
|
|
601
|
+
dispatch({ type: 'open-overlay', overlay: {
|
|
602
|
+
cells, kind: 'info', lines: [], title: `Btw · turn ${String(turn)}`,
|
|
603
|
+
} });
|
|
604
|
+
return { kind: 'success' };
|
|
605
|
+
};
|
|
514
606
|
const definitions = [
|
|
515
607
|
{
|
|
516
608
|
name: 'help', description: 'Show keyboard and command help',
|
|
@@ -551,6 +643,16 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
551
643
|
return { kind: 'success' };
|
|
552
644
|
},
|
|
553
645
|
},
|
|
646
|
+
{
|
|
647
|
+
name: 'usage', description: 'Show session token and timing totals',
|
|
648
|
+
handler: (invocation) => {
|
|
649
|
+
const invalid = ensureCurrent(invocation) ?? usage(invocation.rawInput, '/usage');
|
|
650
|
+
if (invalid !== undefined)
|
|
651
|
+
return invalid;
|
|
652
|
+
dispatch({ type: 'open-overlay', overlay: usageOverlay(ctx, bound.agent) });
|
|
653
|
+
return { kind: 'success' };
|
|
654
|
+
},
|
|
655
|
+
},
|
|
554
656
|
{
|
|
555
657
|
name: 'memory', description: 'Open an instruction file loaded by DSH',
|
|
556
658
|
handler: (invocation) => {
|
|
@@ -671,7 +773,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
671
773
|
if (invocation.rawInput.trim() === '')
|
|
672
774
|
return { kind: 'error', text: 'usage: /plugin ARGS' };
|
|
673
775
|
const command = `dsh plugin --profile ${quoteShellWord(decodeURIComponent(new URL(ctx.baseUrl).pathname).split('/').at(-2))}${invocation.rawInput}`;
|
|
674
|
-
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'));
|
|
776
|
+
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', false));
|
|
675
777
|
bound.agent.inject(message);
|
|
676
778
|
await ctx.sessions.flush(bound.agent.session);
|
|
677
779
|
publish(bound);
|
|
@@ -679,29 +781,35 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
679
781
|
},
|
|
680
782
|
},
|
|
681
783
|
{
|
|
682
|
-
name: 'new', description: 'Start a new session', input: { hint: '[--name TITLE]' },
|
|
784
|
+
name: 'new', description: 'Start a new session', input: { hint: '[NAME | --name TITLE]' },
|
|
683
785
|
handler: async (invocation) => {
|
|
684
786
|
const stale = ensureCurrent(invocation);
|
|
685
787
|
if (stale !== undefined)
|
|
686
788
|
return stale;
|
|
687
789
|
const raw = invocation.rawInput.trim();
|
|
688
790
|
const match = /^--name\s+(.+)$/su.exec(raw);
|
|
689
|
-
if (raw
|
|
690
|
-
return { kind: 'error', text: 'usage: /new [--name TITLE]' };
|
|
791
|
+
if (raw.startsWith('--') && match === null)
|
|
792
|
+
return { kind: 'error', text: 'usage: /new [NAME | --name TITLE]' };
|
|
691
793
|
const title = match?.[1]?.trim();
|
|
794
|
+
if (raw !== '' && match === null) {
|
|
795
|
+
await ctx.sessionController.rename({ sessionId: bound.agent.id, title: raw });
|
|
796
|
+
await ctx.sessions.flush(bound.agent.session);
|
|
797
|
+
}
|
|
692
798
|
await runRootOperation({ kind: 'new', ...(title === undefined ? {} : { title }) });
|
|
693
799
|
return { kind: 'success' };
|
|
694
800
|
},
|
|
695
801
|
},
|
|
696
802
|
{
|
|
697
|
-
name: 'clear', description: 'Start a new session',
|
|
803
|
+
name: 'clear', description: 'Start a new session', input: { hint: '[NAME]' },
|
|
698
804
|
handler: async (invocation) => {
|
|
699
805
|
const stale = ensureCurrent(invocation);
|
|
700
806
|
if (stale !== undefined)
|
|
701
807
|
return stale;
|
|
702
|
-
const
|
|
703
|
-
if (
|
|
704
|
-
|
|
808
|
+
const title = invocation.rawInput.trim();
|
|
809
|
+
if (title !== '') {
|
|
810
|
+
await ctx.sessionController.rename({ sessionId: bound.agent.id, title });
|
|
811
|
+
await ctx.sessions.flush(bound.agent.session);
|
|
812
|
+
}
|
|
705
813
|
await runRootOperation({ kind: 'new' });
|
|
706
814
|
return { kind: 'success' };
|
|
707
815
|
},
|
|
@@ -787,17 +895,46 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
787
895
|
},
|
|
788
896
|
},
|
|
789
897
|
{
|
|
790
|
-
name: '
|
|
898
|
+
name: 'loop', description: 'List, schedule, or stop a fixed-rate reminder',
|
|
899
|
+
input: { hint: '[INTERVAL TEXT | stop ID]' }, handler: loop,
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
name: 'btw', description: 'Ask a side question in a fork', input: { hint: 'TEXT' }, recordInput: false,
|
|
903
|
+
handler: invocation => askAside(invocation),
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
name: 'recap', description: 'Summarize the conversation in a fork', recordInput: false,
|
|
907
|
+
handler: invocation => usage(invocation.rawInput, '/recap') ?? askAside(invocation, RECAP_PROMPT),
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
name: 'agents', description: 'Choose or author a native DSH agent preset',
|
|
911
|
+
input: { hint: '[new NAME | copy SRC DEST | delete NAME]' },
|
|
791
912
|
handler: async (invocation) => {
|
|
792
913
|
const stale = ensureCurrent(invocation);
|
|
793
914
|
if (stale !== undefined)
|
|
794
915
|
return stale;
|
|
795
|
-
const
|
|
796
|
-
if (invalid !== undefined)
|
|
797
|
-
return invalid;
|
|
916
|
+
const raw = invocation.rawInput.trim();
|
|
798
917
|
const blank = !bound.agent.session.snapshotEvents().some(event => event.type === 'turn/start');
|
|
799
918
|
const selected = ctx.sessionProjections.stateOf(bound.agent.session, 'agentPreset')
|
|
800
919
|
?? ctx.agentPresets.defaultId;
|
|
920
|
+
const create = /^new\s+(\S+)$/u.exec(raw);
|
|
921
|
+
const copy = /^copy\s+(\S+)\s+(\S+)$/u.exec(raw);
|
|
922
|
+
const remove = /^delete\s+(\S+)$/u.exec(raw);
|
|
923
|
+
if (create?.[1] !== undefined) {
|
|
924
|
+
await ctx.agentPresets.copy(selected, create[1]);
|
|
925
|
+
dispatch({ type: 'open-file', path: (await ctx.agentPresets.resolve(create[1])).path });
|
|
926
|
+
return { kind: 'success', text: `created ${create[1]} from ${selected}` };
|
|
927
|
+
}
|
|
928
|
+
if (copy?.[1] !== undefined && copy[2] !== undefined) {
|
|
929
|
+
await ctx.agentPresets.copy(copy[1], copy[2]);
|
|
930
|
+
return { kind: 'success', text: `copied ${copy[1]} to ${copy[2]}` };
|
|
931
|
+
}
|
|
932
|
+
if (remove?.[1] !== undefined) {
|
|
933
|
+
await ctx.agentPresets.remove(remove[1]);
|
|
934
|
+
return { kind: 'success', text: `deleted ${remove[1]}` };
|
|
935
|
+
}
|
|
936
|
+
if (raw !== '')
|
|
937
|
+
return { kind: 'error', text: 'usage: /agents [new NAME | copy SRC DEST | delete NAME]' };
|
|
801
938
|
dispatch({
|
|
802
939
|
type: 'open-overlay',
|
|
803
940
|
overlay: agentPresetOverlay(await ctx.agentPresets.list(), selected, blank),
|
package/lib/shell-command.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export declare const HUMAN_SHELL_STREAM_BYTES: number;
|
|
|
7
7
|
export declare function shellResultText(command: string, result: ShellRunResult): string;
|
|
8
8
|
export declare function quoteShellWord(value: string): string;
|
|
9
9
|
/** Run an explicit human command through DSH and identify its next-step context. */
|
|
10
|
-
export declare function runHumanShell(ctx: Context, agent: Agent, command: string, signal: AbortSignal, success?: string): Promise<readonly [UserMessage, ShellRunResult]>;
|
|
10
|
+
export declare function runHumanShell(ctx: Context, agent: Agent, command: string, signal: AbortSignal, success?: string, useSessionSandbox?: boolean): Promise<readonly [UserMessage, ShellRunResult]>;
|
package/lib/shell-command.js
CHANGED
|
@@ -45,11 +45,12 @@ export function shellResultText(command, result) {
|
|
|
45
45
|
}
|
|
46
46
|
export function quoteShellWord(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
|
|
47
47
|
/** Run an explicit human command through DSH and identify its next-step context. */
|
|
48
|
-
export async function runHumanShell(ctx, agent, command, signal, success) {
|
|
48
|
+
export async function runHumanShell(ctx, agent, command, signal, success, useSessionSandbox = true) {
|
|
49
49
|
const result = await ctx.shell.run(ctx.shell.resolve({
|
|
50
50
|
command,
|
|
51
51
|
env: { DSH_HOME: resolveDshHome() },
|
|
52
|
-
sandboxPolicy: ctx.sandboxPolicy.resolve(
|
|
52
|
+
sandboxPolicy: ctx.sandboxPolicy.resolve(useSessionSandbox
|
|
53
|
+
? { session: agent.session } : { mode: 'danger-full-access' }),
|
|
53
54
|
signal,
|
|
54
55
|
stdoutMaxBytes: HUMAN_SHELL_STREAM_BYTES,
|
|
55
56
|
timeoutMs: HUMAN_SHELL_TIMEOUT_MS,
|
package/lib/state.d.ts
CHANGED
|
@@ -333,6 +333,9 @@ export type UiAction = {
|
|
|
333
333
|
readonly type: 'open-history';
|
|
334
334
|
} | {
|
|
335
335
|
readonly type: 'open-details';
|
|
336
|
+
} | {
|
|
337
|
+
readonly path: string;
|
|
338
|
+
readonly type: 'open-file';
|
|
336
339
|
} | {
|
|
337
340
|
readonly type: 'completion-ready';
|
|
338
341
|
readonly options: readonly OverlayOption[];
|
|
@@ -503,6 +506,6 @@ export type UiEffect = {
|
|
|
503
506
|
} | {
|
|
504
507
|
readonly type: 'suspend';
|
|
505
508
|
};
|
|
506
|
-
export declare function initialViewState(cwd: string, inline: boolean, root?: RootView, cells?: readonly TerminalCell[], prompts?: readonly string[], historyHasMore?: boolean, attachments?: readonly DraftAttachment[]): ViewState;
|
|
509
|
+
export declare function initialViewState(cwd: string, inline: boolean, root?: RootView, cells?: readonly TerminalCell[], prompts?: readonly string[], historyHasMore?: boolean, attachments?: readonly DraftAttachment[], toolMode?: ToolMode): ViewState;
|
|
507
510
|
export declare const TURN_BELL_THRESHOLD_MS = 10000;
|
|
508
511
|
export declare function reduce(state: ViewState, action: UiAction): readonly [ViewState, readonly UiEffect[]];
|
package/lib/state.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { FLAG_HELP, VERSION_LINE } from './help.js';
|
|
2
|
-
export function initialViewState(cwd, inline, root, cells = [], prompts = [], historyHasMore = false, attachments = []) {
|
|
2
|
+
export function initialViewState(cwd, inline, root, cells = [], prompts = [], historyHasMore = false, attachments = [], toolMode = 'collapsed') {
|
|
3
3
|
return {
|
|
4
4
|
attachments, cells, composer: '', cwd, decisions: [], exitArmed: false, historyHasMore, inline,
|
|
5
5
|
newOutput: 0, prompts, rewindArmed: false, scrollOffset: 0,
|
|
6
|
-
sendMode: 'steer', toolMode
|
|
6
|
+
sendMode: 'steer', toolMode,
|
|
7
7
|
...(root === undefined ? {} : { root }),
|
|
8
8
|
};
|
|
9
9
|
}
|
|
@@ -41,8 +41,8 @@ function help() {
|
|
|
41
41
|
'Ctrl+B activity details for jobs and subagents',
|
|
42
42
|
'@ paths require DSH file-reference · Ctrl+V pastes an image · Backspace removes it',
|
|
43
43
|
'Ctrl+S stash · Ctrl+G editor · F1 help · Ctrl+L redraw · Ctrl+Z suspend',
|
|
44
|
-
'/new /clear /reset /resume /continue [--all] [NAME|UUID] /fork /branch /rewind /rename /model /effort /permission /agents /queue /status /context',
|
|
45
|
-
'/init /memory /skills /config /login /logout /diff /plugins /plugin /tasks /bashes /subtask /history /export /copy /exit /quit',
|
|
44
|
+
'/new /clear /reset /resume /continue [--all] [NAME|UUID] /fork /branch /rewind /rename /model /effort /permission /agents /queue /status /usage /context',
|
|
45
|
+
'/init /memory /skills /config /login /logout /diff /plugins /plugin /tasks /bashes /subtask /loop /btw /recap /history /export /copy /exit /quit',
|
|
46
46
|
...FLAG_HELP,
|
|
47
47
|
],
|
|
48
48
|
};
|
|
@@ -366,6 +366,7 @@ export function reduce(state, action) {
|
|
|
366
366
|
return head === undefined
|
|
367
367
|
? [{ ...state, overlay: action.overlay, exitArmed: false, rewindArmed: false }, redraw()]
|
|
368
368
|
: [state, []];
|
|
369
|
+
case 'open-file': return [state, [{ type: 'activate-overlay', value: { kind: 'open-file', path: action.path } }]];
|
|
369
370
|
case 'overlay-close': {
|
|
370
371
|
if (state.overlay === undefined)
|
|
371
372
|
return [state, []];
|
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.11",
|
|
4
4
|
"description": "A terminal UI for DeepSeek Harness",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -57,13 +57,16 @@
|
|
|
57
57
|
"@deepseek-ai/dsh-permission-presets": "0.1.2-rc.1",
|
|
58
58
|
"@deepseek-ai/dsh-plan-mode": "0.1.2-rc.1",
|
|
59
59
|
"@deepseek-ai/dsh-sandbox-policy": "0.1.2-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-schedule": "0.1.2-rc.1",
|
|
60
61
|
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
61
62
|
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
62
63
|
"@deepseek-ai/dsh-session-query": "0.1.2-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-session-stats": "0.1.2-rc.1",
|
|
63
65
|
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
|
64
66
|
"@deepseek-ai/dsh-shell": "0.1.2-rc.1",
|
|
65
67
|
"@deepseek-ai/dsh-skill": "0.1.2-rc.1",
|
|
66
68
|
"@deepseek-ai/dsh-subagent": "0.1.2-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
|
|
67
70
|
"@deepseek-ai/dsh-token-meter": "0.1.2-rc.1",
|
|
68
71
|
"@deepseek-ai/dsh-tool-todo": "0.1.2-rc.1",
|
|
69
72
|
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|