@antst/dashi 0.1.0-alpha.12 → 0.1.0-alpha.13
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/catalogs.js +22 -1
- package/lib/completion-trigger.d.ts +4 -0
- package/lib/completion-trigger.js +13 -1
- package/lib/rewind.d.ts +4 -2
- package/lib/rewind.js +28 -14
- package/lib/session-runtime.js +6 -6
- package/package.json +1 -1
package/lib/catalogs.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
1
4
|
import { formatFileMention } from '@deepseek-ai/dsh-file-reference/grammar';
|
|
2
5
|
import { isUserInvocable } from '@deepseek-ai/dsh-skill';
|
|
3
6
|
import { detectAtTrigger } from './at-trigger.js';
|
|
4
|
-
import { slashCompletionQuery } from './completion-trigger.js';
|
|
7
|
+
import { pluginCompletionQuery, slashCompletionQuery } from './completion-trigger.js';
|
|
5
8
|
import { isImagePath } from './image-input.js';
|
|
6
9
|
import { sessionTitle } from './session-list.js';
|
|
7
10
|
function matchRank(name, query) {
|
|
@@ -15,6 +18,24 @@ function selectedModel(ctx, agent) {
|
|
|
15
18
|
}
|
|
16
19
|
/** Build command and skill completion rows for one exact root. */
|
|
17
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
|
+
}
|
|
18
39
|
const at = detectAtTrigger(input, caret);
|
|
19
40
|
if (at !== undefined) {
|
|
20
41
|
const service = ctx.get('fileReferences');
|
|
@@ -1,2 +1,6 @@
|
|
|
1
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;
|
|
2
6
|
export declare function hasCompletionTrigger(input: string, caret: number): boolean;
|
|
@@ -2,6 +2,18 @@ import { detectAtTrigger } from './at-trigger.js';
|
|
|
2
2
|
export function slashCompletionQuery(input) {
|
|
3
3
|
return /^\/([a-z0-9_-]*)$/u.exec(input)?.[1];
|
|
4
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
|
+
}
|
|
5
16
|
export function hasCompletionTrigger(input, caret) {
|
|
6
|
-
return slashCompletionQuery(input) !== undefined ||
|
|
17
|
+
return slashCompletionQuery(input) !== undefined || pluginCompletionQuery(input, caret) !== undefined
|
|
18
|
+
|| detectAtTrigger(input, caret) !== undefined;
|
|
7
19
|
}
|
package/lib/rewind.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ export interface RewindBoundary {
|
|
|
7
7
|
}
|
|
8
8
|
/** Return the text of a direct human message, excluding injected context. */
|
|
9
9
|
export declare function humanPrompt(event: SessionEvent): string | undefined;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/** Recover one composer input from DSH's durable log. */
|
|
11
|
+
export declare function historyInput(event: SessionEvent): string | undefined;
|
|
12
|
+
export declare function historyInputs(events: readonly SessionEvent[]): readonly string[];
|
|
13
|
+
/** Derive each recalled input and its controller-valid preceding-turn cut. */
|
|
12
14
|
export declare function rewindBoundaries(events: readonly SessionEvent[]): readonly RewindBoundary[];
|
|
13
15
|
export declare function rewindOverlay(events: readonly SessionEvent[], roller: boolean): Overlay;
|
|
14
16
|
export declare function rewindActionOverlay(boundary: Extract<OverlayValue, {
|
package/lib/rewind.js
CHANGED
|
@@ -7,10 +7,24 @@ export function humanPrompt(event) {
|
|
|
7
7
|
.map(block => block.text).join('');
|
|
8
8
|
return text === '' ? undefined : text;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/** Recover one composer input from DSH's durable log. */
|
|
11
|
+
export function historyInput(event) {
|
|
12
|
+
const prompt = humanPrompt(event);
|
|
13
|
+
if (prompt !== undefined)
|
|
14
|
+
return prompt;
|
|
15
|
+
if (event.type === 'command/run' && event.data.args !== undefined) {
|
|
16
|
+
return `/${event.data.name}${event.data.args}`;
|
|
17
|
+
}
|
|
18
|
+
if (event.type !== 'user/message' || event.data.source.kind !== 'plugin'
|
|
19
|
+
|| event.data.source.plugin !== 'dashi' || event.data.source.form !== 'notice')
|
|
20
|
+
return undefined;
|
|
21
|
+
const first = event.data.content.find(block => block.type === 'text')?.text.split(/\r?\n/u)[0];
|
|
22
|
+
return first?.startsWith('$ ') === true ? `!${first.slice(2)}` : undefined;
|
|
12
23
|
}
|
|
13
|
-
|
|
24
|
+
export function historyInputs(events) {
|
|
25
|
+
return events.flatMap(event => historyInput(event) ?? []);
|
|
26
|
+
}
|
|
27
|
+
/** Derive each recalled input and its controller-valid preceding-turn cut. */
|
|
14
28
|
export function rewindBoundaries(events) {
|
|
15
29
|
const boundaries = [];
|
|
16
30
|
let previousEnd;
|
|
@@ -21,22 +35,22 @@ export function rewindBoundaries(events) {
|
|
|
21
35
|
openTurn = event.data.turn;
|
|
22
36
|
prompted = false;
|
|
23
37
|
}
|
|
24
|
-
else if (event.type === 'user/message' && openTurn !== undefined) {
|
|
25
|
-
const prompt = humanPrompt(event);
|
|
26
|
-
if (prompt !== undefined) {
|
|
27
|
-
boundaries.push({
|
|
28
|
-
...(previousEnd === undefined ? {} : { atSeq: previousEnd.seq }),
|
|
29
|
-
label: `${prompt.replaceAll(/\s+/gu, ' ')}${prompted ? ' · mid-turn' : ''}`,
|
|
30
|
-
prompt,
|
|
31
|
-
});
|
|
32
|
-
prompted = true;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
38
|
else if (event.type === 'turn/end') {
|
|
36
39
|
previousEnd = event;
|
|
37
40
|
openTurn = undefined;
|
|
38
41
|
prompted = false;
|
|
39
42
|
}
|
|
43
|
+
const prompt = historyInput(event);
|
|
44
|
+
if (prompt === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
const human = humanPrompt(event) !== undefined;
|
|
47
|
+
boundaries.push({
|
|
48
|
+
...(previousEnd === undefined ? {} : { atSeq: previousEnd.seq }),
|
|
49
|
+
label: `${prompt.replaceAll(/\s+/gu, ' ')}${human && prompted ? ' · mid-turn' : ''}`,
|
|
50
|
+
prompt,
|
|
51
|
+
});
|
|
52
|
+
if (human && openTurn !== undefined)
|
|
53
|
+
prompted = true;
|
|
40
54
|
}
|
|
41
55
|
return boundaries;
|
|
42
56
|
}
|
package/lib/session-runtime.js
CHANGED
|
@@ -9,7 +9,7 @@ import { diffOverlay } from './diff-view.js';
|
|
|
9
9
|
import { eventsFromRecords } from './history-records.js';
|
|
10
10
|
import { admitDraftImages, encodeDraftImages, readDraftImages } from './image-input.js';
|
|
11
11
|
import { contextPercent, jobViews, subagentViews } from './presentation.js';
|
|
12
|
-
import {
|
|
12
|
+
import { historyInput, historyInputs, humanPrompt, inheritedTurn, rewindActionOverlay, rewindOverlay, } from './rewind.js';
|
|
13
13
|
import { createToolPresenter } from './tool-presentation.js';
|
|
14
14
|
import { quoteShellWord, runHumanShell } from './shell-command.js';
|
|
15
15
|
import { markdownTranscript } from './session-export.js';
|
|
@@ -327,7 +327,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
327
327
|
...(model.reasoningEffort === undefined ? {} : { effort: model.reasoningEffort }),
|
|
328
328
|
});
|
|
329
329
|
}
|
|
330
|
-
const prompt =
|
|
330
|
+
const prompt = historyInput(event);
|
|
331
331
|
if (prompt !== undefined)
|
|
332
332
|
dispatch({ type: 'prompt-recorded', rootId: bound.root.id, text: prompt });
|
|
333
333
|
schedule(bound);
|
|
@@ -406,7 +406,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
406
406
|
tuiRoot.bind(candidate.agent);
|
|
407
407
|
dispatch({
|
|
408
408
|
type: 'root-bound', cells: transcriptCells(candidate),
|
|
409
|
-
hasMore: candidate.hasMore, prompts:
|
|
409
|
+
hasMore: candidate.hasMore, prompts: historyInputs(candidate.events), root: candidate.root,
|
|
410
410
|
});
|
|
411
411
|
if (started) {
|
|
412
412
|
candidate.pump = follow(candidate);
|
|
@@ -505,7 +505,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
505
505
|
const roller = ctx.commands.list(bound.agent).some(command => command.name === 'roller-restore');
|
|
506
506
|
const overlay = rewindOverlay(bound.agent.session.snapshotEvents(), roller);
|
|
507
507
|
if (overlay.kind !== 'list' || overlay.options.length === 0) {
|
|
508
|
-
throw new Error('the current session has no
|
|
508
|
+
throw new Error('the current session has no input to rewind');
|
|
509
509
|
}
|
|
510
510
|
dispatch({ type: 'open-overlay', overlay });
|
|
511
511
|
return;
|
|
@@ -795,7 +795,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
795
795
|
if (invocation.rawInput.trim() === '')
|
|
796
796
|
return { kind: 'error', text: 'usage: /plugin ARGS' };
|
|
797
797
|
const command = `dsh plugin --profile ${quoteShellWord(decodeURIComponent(new URL(ctx.baseUrl).pathname).split('/').at(-2))}${invocation.rawInput}`;
|
|
798
|
-
const [message, result] = await bound.agent.runMaintenance(signal => runHumanShell(ctx, bound.agent, command, signal, '
|
|
798
|
+
const [message, result] = await bound.agent.runMaintenance(signal => runHumanShell(ctx, bound.agent, command, signal, 'bundles load on the next launch; plain plugins need a patch row', false));
|
|
799
799
|
bound.agent.inject(message);
|
|
800
800
|
await ctx.sessions.flush(bound.agent.session);
|
|
801
801
|
publish(bound);
|
|
@@ -1277,7 +1277,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
|
|
|
1277
1277
|
initialAttachments,
|
|
1278
1278
|
get initialCells() { return transcriptCells(current()); },
|
|
1279
1279
|
get initialHasMore() { return current().hasMore; },
|
|
1280
|
-
get initialPrompts() { return
|
|
1280
|
+
get initialPrompts() { return historyInputs(current().events); },
|
|
1281
1281
|
get root() { return current().root; },
|
|
1282
1282
|
get statusLine() { const bound = current(); return sessionStatusLine(ctx, bound.agent, bound.root, bound.branch); },
|
|
1283
1283
|
get summary() { return `Resume with: dsh --profile dashi --resume ${current().root.id}`; },
|