dsh-code 0.1.0 → 0.3.0
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 +17 -4
- package/README.zh.md +17 -4
- package/cordis.patch.yml +22 -5
- package/lib/index.mjs +1888 -100
- package/lib/invariant.mjs +1 -1
- package/lib/startup.mjs +70 -0
- package/lib/types/app.d.ts +64 -9
- package/lib/types/approval.d.ts +57 -0
- package/lib/types/commands.d.ts +37 -0
- package/lib/types/index.d.ts +19 -7
- package/lib/types/invariant.d.ts +2 -2
- package/lib/types/mentions.d.ts +70 -0
- package/lib/types/models.d.ts +37 -0
- package/lib/types/questions.d.ts +48 -0
- package/lib/types/render/animations.d.ts +15 -0
- package/lib/types/render/markdown.d.ts +27 -0
- package/lib/types/render/projection.d.ts +37 -3
- package/lib/types/render/status.d.ts +4 -0
- package/lib/types/render/text.d.ts +18 -0
- package/lib/types/render/tool-preview.d.ts +15 -0
- package/lib/types/skills.d.ts +45 -0
- package/lib/types/startup.d.ts +44 -0
- package/lib/types/store.d.ts +8 -2
- package/lib/types/theme.d.ts +4 -0
- package/package.json +36 -3
- package/src/app.ts +971 -57
- package/src/approval.ts +126 -0
- package/src/commands.ts +71 -0
- package/src/index.ts +353 -40
- package/src/invariant.ts +3 -3
- package/src/mentions.ts +193 -0
- package/src/models.ts +66 -0
- package/src/questions.ts +143 -0
- package/src/render/animations.ts +22 -0
- package/src/render/markdown.ts +235 -0
- package/src/render/projection.ts +117 -10
- package/src/render/status.ts +14 -2
- package/src/render/text.ts +24 -0
- package/src/render/tool-preview.ts +34 -0
- package/src/skills.ts +104 -0
- package/src/startup.ts +91 -0
- package/src/store.ts +10 -4
- package/src/theme.ts +4 -0
package/lib/invariant.mjs
CHANGED
package/lib/startup.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { parseCmdline } from "@deepseek-ai/dsh-cmdline";
|
|
3
|
+
//#region src/startup.ts
|
|
4
|
+
/**
|
|
5
|
+
* The interactive terminal app's command-line provider: parses `--resume`,
|
|
6
|
+
* `--continue`, `--session`, and `--help`, then publishes
|
|
7
|
+
* {@link TUI_STARTUP_SERVICE} for the runner to consume lazily. Follows the
|
|
8
|
+
* headless bundle's startup shape (a commander action publishing a service
|
|
9
|
+
* through {@link parseCmdline}).
|
|
10
|
+
*
|
|
11
|
+
* Semantics:
|
|
12
|
+
* - `--resume <id|prefix>` — continue the persisted session whose id or unique
|
|
13
|
+
* id-prefix matches; the TUI replays its transcript and appends to the same
|
|
14
|
+
* durable log.
|
|
15
|
+
* - `--continue` / `-c` — resume the most recently modified persisted session
|
|
16
|
+
* whose project directory matches the current working directory.
|
|
17
|
+
* - `--session <id>` — create a new session under an explicit identity (the
|
|
18
|
+
* id must not exist yet).
|
|
19
|
+
* - no flags — a fresh session with a minted id.
|
|
20
|
+
*
|
|
21
|
+
* @module @deepseek-ai/dsh-tui/startup
|
|
22
|
+
*/
|
|
23
|
+
/** Stable Cordis plugin name. */
|
|
24
|
+
const name = "tui-startup";
|
|
25
|
+
/** Services required before the invocation can be resolved. */
|
|
26
|
+
const inject = ["cmdlineArgs"];
|
|
27
|
+
/** Service provided by this plugin and injected by the terminal runner. */
|
|
28
|
+
const TUI_STARTUP_SERVICE = "tuiStartup";
|
|
29
|
+
/**
|
|
30
|
+
* This app's command: the launcher's flags this app owns, its description,
|
|
31
|
+
* and its help text.
|
|
32
|
+
* @returns a fresh program, so one process can parse more than once (tests).
|
|
33
|
+
*/
|
|
34
|
+
function tuiCommand() {
|
|
35
|
+
return new Command().name("dsh --profile cli").description("Claude-Code-style interactive terminal for DeepSeek Harness.").helpOption("-h, --help", "show this help").option("-r, --resume <session>", "resume the persisted session with this id (or unique id prefix)").option("-c, --continue", "resume the most recent persisted session for this working directory").option("--session <id>", "create a new session under this explicit id").addHelpText("after", `
|
|
36
|
+
Examples:
|
|
37
|
+
dsh --profile cli fresh session, minted id
|
|
38
|
+
dsh --profile cli --resume abc123 resume session by id prefix
|
|
39
|
+
dsh --profile cli --continue resume the latest local session
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse the invocation and publish the startup service. Mutual exclusions are
|
|
44
|
+
* usage errors rejected from the action before anything is provided.
|
|
45
|
+
* @param ctx - plugin context carrying the command line and exit request.
|
|
46
|
+
*/
|
|
47
|
+
function apply(ctx) {
|
|
48
|
+
const program = tuiCommand();
|
|
49
|
+
program.action(() => {
|
|
50
|
+
const options = program.opts();
|
|
51
|
+
if ([
|
|
52
|
+
options.resume !== void 0,
|
|
53
|
+
options.continue === true,
|
|
54
|
+
options.session !== void 0
|
|
55
|
+
].filter(Boolean).length > 1) program.error("error: --resume, --continue, and --session are mutually exclusive");
|
|
56
|
+
if (options.session !== void 0 && options.session === "") program.error("error: --session needs an id");
|
|
57
|
+
if (options.resume !== void 0 && options.resume === "") program.error("error: --resume needs a session id or id prefix");
|
|
58
|
+
const startup = options.resume !== void 0 ? {
|
|
59
|
+
kind: "resume",
|
|
60
|
+
sessionId: options.resume
|
|
61
|
+
} : options.continue === true ? { kind: "latest" } : options.session !== void 0 ? {
|
|
62
|
+
kind: "named",
|
|
63
|
+
sessionId: options.session
|
|
64
|
+
} : { kind: "fresh" };
|
|
65
|
+
ctx.provide(TUI_STARTUP_SERVICE, { startup });
|
|
66
|
+
});
|
|
67
|
+
parseCmdline(ctx, program);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { TUI_STARTUP_SERVICE, apply, inject, name };
|
package/lib/types/app.d.ts
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Ink terminal app: whale-and-wordmark header in DeepSeek blue, the live
|
|
3
|
-
* transcript, the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* transcript, the todo panel, the streaming line, the approval bar, the model
|
|
4
|
+
* panel, local notices, and the input box with history and slash-command
|
|
5
|
+
* completion. All state arrives through the transcript store (derived from
|
|
6
|
+
* the durable session log) plus local input state; the app owns no session
|
|
7
|
+
* mutation of its own.
|
|
6
8
|
*
|
|
7
9
|
* Element construction uses `createElement` (not JSX): the `dsh` source launch
|
|
8
10
|
* compiles this file through tsx's ESM-only hook, which does not adopt this
|
|
9
11
|
* package's `jsx: react-jsx` compiler option, and the classic JSX runtime
|
|
10
12
|
* would demand a React global.
|
|
11
13
|
*
|
|
12
|
-
* @module @deepseek-ai/dsh-
|
|
14
|
+
* @module @deepseek-ai/dsh-code/app
|
|
13
15
|
*/
|
|
14
16
|
import { type ReactElement } from 'react';
|
|
15
17
|
import type { TranscriptStore } from './store.ts';
|
|
18
|
+
import type { ApprovalStore } from './approval.ts';
|
|
19
|
+
import type { CommandsView } from './commands.ts';
|
|
20
|
+
import type { ModelDirectory, ModelRow } from './models.ts';
|
|
21
|
+
import type { QuestionStore } from './questions.ts';
|
|
22
|
+
import type { SkillsView } from './skills.ts';
|
|
23
|
+
import type { MentionCandidate } from './mentions.ts';
|
|
16
24
|
/** Props the runner hands the app; callbacks stay owned by the runner. */
|
|
17
25
|
export interface AppProps {
|
|
18
26
|
/** Event-fed transcript store for the live session. */
|
|
19
27
|
store: TranscriptStore;
|
|
20
|
-
/**
|
|
28
|
+
/** Approval-question store fed by the answerer listener. */
|
|
29
|
+
approval: ApprovalStore;
|
|
30
|
+
/** ask_user_question store fed by the single UI provider. */
|
|
31
|
+
questions: QuestionStore;
|
|
32
|
+
/** Live slash-command descriptor list (completion candidates). */
|
|
33
|
+
commands: CommandsView;
|
|
34
|
+
/** Live user-invocable skill catalog (completion candidates). */
|
|
35
|
+
skills: SkillsView;
|
|
36
|
+
/** `provider/model` selection serving this session (updated on /model). */
|
|
21
37
|
model: string;
|
|
22
38
|
/** Working-directory basename the session serves. */
|
|
23
39
|
cwd: string;
|
|
@@ -25,10 +41,49 @@ export interface AppProps {
|
|
|
25
41
|
branch: string;
|
|
26
42
|
/** Short session identifier. */
|
|
27
43
|
sessionId: string;
|
|
28
|
-
/**
|
|
29
|
-
|
|
44
|
+
/** Whether this session was resumed from persistence. */
|
|
45
|
+
resumed: boolean;
|
|
46
|
+
/** Submit one line: slash commands to the registry, other text to the agent. */
|
|
47
|
+
dispatch(text: string): void;
|
|
48
|
+
/** Submit steering: consumed at the running turn's next step boundary. */
|
|
49
|
+
steer(text: string): void;
|
|
50
|
+
/** Interrupt the running turn (Esc); true when a turn was cancelled. */
|
|
51
|
+
interrupt(): boolean;
|
|
30
52
|
/** Quit: unmount, flush, and request process exit. */
|
|
31
|
-
|
|
53
|
+
quit(): void;
|
|
54
|
+
/** Load the selectable model directory (called when /model opens). */
|
|
55
|
+
loadModels(): Promise<ModelDirectory>;
|
|
56
|
+
/** Load @mention candidates for the typed query (files + sessions). */
|
|
57
|
+
loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
|
|
58
|
+
/** Apply one /model selection; returns the display label. */
|
|
59
|
+
selectModel(row: ModelRow): string;
|
|
60
|
+
/** Cycle to the next permission preset (Shift+Tab); returns the new label. */
|
|
61
|
+
cyclePermission(): string;
|
|
62
|
+
/** Registers the app's notice channel with the runner (called once on mount). */
|
|
63
|
+
onBridgeReady(bridge: {
|
|
64
|
+
notify(text: string): void;
|
|
65
|
+
}): void;
|
|
66
|
+
}
|
|
67
|
+
/** One completion candidate row. */
|
|
68
|
+
interface CompletionCandidate {
|
|
69
|
+
/** Insertion text for the command name (with leading slash). */
|
|
70
|
+
label: string;
|
|
71
|
+
/** Human-readable description shown beside the label. */
|
|
72
|
+
description: string;
|
|
73
|
+
/** Candidate origin; skills land the same literal text but route through the prompt. */
|
|
74
|
+
origin: 'command' | 'skill' | 'mention';
|
|
75
|
+
}
|
|
76
|
+
/** The completion menu snapshot the input editor publishes to the app. */
|
|
77
|
+
export interface MenuState {
|
|
78
|
+
/** Whether the menu is on screen (slash or @mention). */
|
|
79
|
+
active: boolean;
|
|
80
|
+
/** Whether the menu is driven by an @mention token. */
|
|
81
|
+
mention: boolean;
|
|
82
|
+
/** Highlighted candidate index (wraps by row count). */
|
|
83
|
+
index: number;
|
|
84
|
+
/** Rendered rows in display order. */
|
|
85
|
+
rows: readonly CompletionCandidate[];
|
|
32
86
|
}
|
|
33
87
|
/** The whole terminal app; state arrives via the store, output via Ink. */
|
|
34
|
-
export declare function App(
|
|
88
|
+
export declare function App(props: AppProps): ReactElement;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal approval answerer: one `approval/request` waterfall listener
|
|
3
|
+
* that renders the pending question as a y/n bar and resolves the decision
|
|
4
|
+
* back into the waterfall. Mirrors the web host's composer takeover — the
|
|
5
|
+
* service (audit pair, policy gate, fail-closed defaults) all live in
|
|
6
|
+
* dsh-base; this module only answers for agents this TUI owns.
|
|
7
|
+
*
|
|
8
|
+
* Vocabulary note: a client answerer may only ever resolve `'allowed-once'`
|
|
9
|
+
* or `'rejected'`; `'cancelled'` belongs to the request signal and
|
|
10
|
+
* `'unavailable'` to the fail-closed waterfall default.
|
|
11
|
+
*
|
|
12
|
+
* @module @deepseek-ai/dsh-code/approval
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
16
|
+
import type { ApprovalRequest } from '@deepseek-ai/dsh-user-approval';
|
|
17
|
+
/** The answer values a client answerer may resolve with. */
|
|
18
|
+
export type ApprovalAnswer = 'allowed-once' | 'rejected';
|
|
19
|
+
/** One pending approval question, derived from the request for rendering. */
|
|
20
|
+
export interface PendingApproval {
|
|
21
|
+
/** The asker's human-readable explanation, or a generic fallback. */
|
|
22
|
+
headline: string;
|
|
23
|
+
/** The tool the question is about. */
|
|
24
|
+
toolName: string;
|
|
25
|
+
/** Command-line preview resolved from the paired streaming tool call. */
|
|
26
|
+
command: string;
|
|
27
|
+
/** Resolve the ask; calling twice is inert (one-shot latch). */
|
|
28
|
+
answer(outcome: ApprovalAnswer): void;
|
|
29
|
+
}
|
|
30
|
+
/** The pending-question snapshot the renderer subscribes to. */
|
|
31
|
+
export interface ApprovalSnapshot {
|
|
32
|
+
/** The pending question, or undefined when none is being asked. */
|
|
33
|
+
pending: PendingApproval | undefined;
|
|
34
|
+
/** Presentational: an answer was submitted, the ask has not settled yet. */
|
|
35
|
+
answered: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Store the pending question lands in; the renderer reads, the answerer writes. */
|
|
38
|
+
export interface ApprovalStore {
|
|
39
|
+
/** Subscribe to pending-state changes; returns the unsubscribe function. */
|
|
40
|
+
subscribe(listener: () => void): () => void;
|
|
41
|
+
/** Read the current snapshot (identity-stable between changes). */
|
|
42
|
+
getSnapshot(): ApprovalSnapshot;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create the approval store and mount the answerer listener on the context.
|
|
46
|
+
* The listener claims only requests for `owns`-owned agents and defers every
|
|
47
|
+
* other request back into the waterfall (`next()`), so sibling answerers stay
|
|
48
|
+
* usable. An aborted ask never reaches the human. Plugin teardown removes the
|
|
49
|
+
* listener; the service then fails its own question closed.
|
|
50
|
+
* @param ctx - plugin context whose event bus carries `approval/request`.
|
|
51
|
+
* @param owns - agents this terminal answers for.
|
|
52
|
+
* @param preview - resolves a tool-call preview for a pending request (the
|
|
53
|
+
* request contract carries no arguments; the UI self-serves from the
|
|
54
|
+
* transcript projection via `callId`).
|
|
55
|
+
* @returns the store the renderer subscribes to.
|
|
56
|
+
*/
|
|
57
|
+
export declare function mountApprovalAnswerer(ctx: Context, owns: (agent: Agent) => boolean, preview: (request: ApprovalRequest) => string): ApprovalStore;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash-command bridge: forwards terminal command lines into the shared
|
|
3
|
+
* `ctx.commands` registry (the same surface the web composer dispatches
|
|
4
|
+
* through) and exposes the live descriptor list as completion candidates.
|
|
5
|
+
* The runner keeps only its own TUI-local commands (`/help`, `/quit`,
|
|
6
|
+
* `/clear`, `/model`) ahead of the registry dispatch.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-tui/commands
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
12
|
+
import type { CommandDescriptor } from '@deepseek-ai/dsh-commands';
|
|
13
|
+
/** Descriptor list snapshot the completion menu renders from. */
|
|
14
|
+
export interface CommandsView {
|
|
15
|
+
/** Name-sorted descriptors after scoped shadowing. */
|
|
16
|
+
readonly descriptors: readonly CommandDescriptor[];
|
|
17
|
+
/** Subscribe to list changes (`commands/change`); returns the unsubscribe function. */
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
/** Retarget the agent whose scoped view the list is read through. */
|
|
20
|
+
setAgent(agent: Agent): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Watch the live command registry. Reads the current list immediately and
|
|
24
|
+
* re-reads on every registry mutation or agent retarget; notification
|
|
25
|
+
* failures are contained by the registry itself, so this watcher only ever
|
|
26
|
+
* re-reads. Without a `commands` service the view stays empty and all lines
|
|
27
|
+
* fall through to normal prompts.
|
|
28
|
+
* @param ctx - context carrying the `commands` service (optional).
|
|
29
|
+
* @returns the view the completion menu subscribes to.
|
|
30
|
+
*/
|
|
31
|
+
export declare function watchCommands(ctx: Context): CommandsView;
|
|
32
|
+
/**
|
|
33
|
+
* Whether one command line is a syntactically valid slash command.
|
|
34
|
+
* @param line - the complete candidate line.
|
|
35
|
+
* @returns true when the line parses as `/name` or `/name input`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isSlashLine(line: string): boolean;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @deepseek-ai/dsh-
|
|
2
|
+
* @deepseek-ai/dsh-code — the interactive terminal driver. The bundle patch
|
|
3
3
|
* rides over dsh-base without Host, HTTP, or browser plugins; this runner
|
|
4
|
-
* creates one Agent through the core registry, mounts the Ink
|
|
5
|
-
* blue, whale wordmark), folds submitted prompts into the same
|
|
6
|
-
* session,
|
|
7
|
-
* and
|
|
4
|
+
* creates (or resumes) one Agent through the core registry, mounts the Ink
|
|
5
|
+
* app (DeepSeek blue, whale wordmark), folds submitted prompts into the same
|
|
6
|
+
* durable session, answers approval asks with a y/n bar, dispatches slash
|
|
7
|
+
* commands through the shared registry, and on quit flushes and requests
|
|
8
|
+
* process exit.
|
|
8
9
|
*
|
|
9
|
-
* @module @deepseek-ai/dsh-
|
|
10
|
+
* @module @deepseek-ai/dsh-code
|
|
10
11
|
*/
|
|
11
12
|
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
+
import z from '@deepseek-ai/schemastery';
|
|
12
14
|
/** Stable Cordis plugin name. */
|
|
13
15
|
export declare const name = "tui-runner";
|
|
14
16
|
/** Core services required before the interactive session can start. */
|
|
15
17
|
export declare const inject: string[];
|
|
18
|
+
/** Plugin config: the startup resolved from this app's injected provider service. */
|
|
19
|
+
export interface Config {
|
|
20
|
+
/** How this invocation obtains its session identity (validated loosely; narrowed in {@link apply}). */
|
|
21
|
+
startup: {
|
|
22
|
+
kind: string;
|
|
23
|
+
sessionId?: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare const Config: z<Config>;
|
|
16
27
|
/**
|
|
17
28
|
* Mount the interactive terminal driver.
|
|
18
29
|
* @param ctx - plugin context carrying core services and the launcher-provided exit request.
|
|
30
|
+
* @param config - validated startup config resolved from the tuiStartup provider.
|
|
19
31
|
*/
|
|
20
|
-
export declare function apply(ctx: Context): void;
|
|
32
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/types/invariant.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Package-owned invariant companion for
|
|
3
|
-
* @module
|
|
2
|
+
* Package-owned invariant companion for `dsh-code`.
|
|
3
|
+
* @module dsh-code/invariant
|
|
4
4
|
*/
|
|
5
5
|
import type { Context } from '@deepseek-ai/cordis';
|
|
6
6
|
/** Cordis companion plugin name. */
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace @mention support: file candidates from a bounded async scan of
|
|
3
|
+
* the session cwd, session candidates from the opt-in `sessionReferenceResolver`
|
|
4
|
+
* service, and submission preparation through its `prepare()` API. Picked
|
|
5
|
+
* session mentions land as canonical `@[label](dsh-session:…)` tokens; on
|
|
6
|
+
* submit the text is parsed back into readable `@label` text plus structured
|
|
7
|
+
* references, snapshots are injected via `agent.inject()` before the readable
|
|
8
|
+
* message wakes the driver (`followup` idle, `steer` running) — exactly the
|
|
9
|
+
* upstream README's wiring.
|
|
10
|
+
*
|
|
11
|
+
* @module @deepseek-ai/dsh-code/mentions
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
15
|
+
import { parseSessionReferenceText, type SessionReferenceCandidate, type SessionReferenceInput } from '@deepseek-ai/dsh-session-reference';
|
|
16
|
+
/** Parsed submission text: readable text plus structured references. */
|
|
17
|
+
type ParsedSessionReferenceText = ReturnType<typeof parseSessionReferenceText>;
|
|
18
|
+
/** One filesystem entry the @ menu can complete. */
|
|
19
|
+
export interface FileCandidate {
|
|
20
|
+
/** Workspace-relative path with forward slashes. */
|
|
21
|
+
path: string;
|
|
22
|
+
/** Entry kind; directories insert with a trailing slash. */
|
|
23
|
+
kind: 'file' | 'directory';
|
|
24
|
+
}
|
|
25
|
+
/** One merged menu candidate (files and sessions, already ranked). */
|
|
26
|
+
export interface MentionCandidate {
|
|
27
|
+
/** Text inserted after the `@` (directories carry a trailing slash). */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Human-readable origin shown beside the label. */
|
|
30
|
+
description: string;
|
|
31
|
+
/** Origin kind for icon/coloring decisions. */
|
|
32
|
+
kind: 'file' | 'directory' | 'session';
|
|
33
|
+
}
|
|
34
|
+
/** Prepared submission: readable content plus optional injected context. */
|
|
35
|
+
export interface PreparedMention {
|
|
36
|
+
/** Readable text with mention tokens normalized to `@label`. */
|
|
37
|
+
text: string;
|
|
38
|
+
/** Structured source sessions in appearance order (empty when none). */
|
|
39
|
+
references: SessionReferenceInput[];
|
|
40
|
+
/** Aggregated snapshot for `agent.inject()`, undefined without references. */
|
|
41
|
+
additionalContext?: import('@deepseek-ai/dsh-session').UserMessage;
|
|
42
|
+
}
|
|
43
|
+
/** Bounded async BFS scan of a workspace; unreadable entries are skipped. */
|
|
44
|
+
export declare function scanWorkspaceFiles(root: string, signal?: AbortSignal): Promise<readonly FileCandidate[]>;
|
|
45
|
+
/** The mention API the input editor and the runner share. */
|
|
46
|
+
export interface MentionsApi {
|
|
47
|
+
/** Scanned workspace files, cached across one session. */
|
|
48
|
+
files(): Promise<readonly FileCandidate[]>;
|
|
49
|
+
/** Ranked menu candidates for the typed `@` query. */
|
|
50
|
+
candidates(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
|
|
51
|
+
/** Parse submission text into readable text plus structured references. */
|
|
52
|
+
parse(text: string): ParsedSessionReferenceText;
|
|
53
|
+
/**
|
|
54
|
+
* Snapshot references and build the injected context. Throws the service's
|
|
55
|
+
* typed error on failure — the caller restores the draft and notifies.
|
|
56
|
+
*/
|
|
57
|
+
prepare(parsed: ParsedSessionReferenceText, signal?: AbortSignal): Promise<PreparedMention>;
|
|
58
|
+
/** Canonical mention token for a picked session candidate. */
|
|
59
|
+
sessionMention(candidate: SessionReferenceCandidate): string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create the mention API for one agent's workspace. A missing
|
|
63
|
+
* session-reference service degrades to file mentions only (the scan still
|
|
64
|
+
* works); `prepare` then passes text through untouched.
|
|
65
|
+
* @param ctx - context carrying the optional `sessionReferenceResolver`.
|
|
66
|
+
* @param agent - the session owner; excluded from its own candidates.
|
|
67
|
+
* @param cwd - workspace root to scan.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createMentions(ctx: Context, agent: Agent, cwd: string): MentionsApi;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model directory for the `/model` panel: the in-process equivalent of the
|
|
3
|
+
* web host's model catalog (`buildModelCatalog`), reading the advisory
|
|
4
|
+
* `ctx.llm` registry directly. Catalog membership is advisory — a route
|
|
5
|
+
* serving a model it stopped advertising stays usable — so selection never
|
|
6
|
+
* fails on catalog absence alone.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-tui/models
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
/** One selectable row in the `/model` panel. */
|
|
12
|
+
export interface ModelRow {
|
|
13
|
+
/** Registered provider route. */
|
|
14
|
+
provider: string;
|
|
15
|
+
/** Display name of the provider route. */
|
|
16
|
+
providerName: string;
|
|
17
|
+
/** Provider-owned model id. */
|
|
18
|
+
model: string;
|
|
19
|
+
/** Human-readable model name. */
|
|
20
|
+
modelName: string;
|
|
21
|
+
}
|
|
22
|
+
/** The resolved directory: rows plus per-provider discovery failures. */
|
|
23
|
+
export interface ModelDirectory {
|
|
24
|
+
/** Advisory rows, provider-major in registry order. */
|
|
25
|
+
rows: readonly ModelRow[];
|
|
26
|
+
/** Provider ids whose model listing failed; those providers contribute no rows. */
|
|
27
|
+
failures: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load the selectable model directory from the live `ctx.llm` registry.
|
|
31
|
+
* Providers are listed synchronously; each provider's models are discovered
|
|
32
|
+
* with a bounded parallel fan-out whose failures degrade to that provider
|
|
33
|
+
* contributing no rows (mirrors the web catalog's per-provider failures).
|
|
34
|
+
* @param ctx - context carrying the `llm` service.
|
|
35
|
+
* @returns the resolved directory; empty rows when `llm` is unavailable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadModelDirectory(ctx: Context): Promise<ModelDirectory>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal ask_user_question provider: registers the single UI provider
|
|
3
|
+
* on `ctx.userQuestions` and drives it with a FIFO queue — one question
|
|
4
|
+
* request on screen at a time, everything else waiting — then resolves the
|
|
5
|
+
* collected answers back into the tool's promise. The community TUI proved
|
|
6
|
+
* this exact pipeline shape; here the dialog is an Ink bar instead of a
|
|
7
|
+
* pi-tui inline modal.
|
|
8
|
+
*
|
|
9
|
+
* Plan reviews (`exit_plan_mode`) arrive through the same service with an
|
|
10
|
+
* `intent: { kind: 'plan-review' }` — the renderer highlights the approve
|
|
11
|
+
* option; the answer encoding is identical either way.
|
|
12
|
+
*
|
|
13
|
+
* @module @deepseek-ai/dsh-code/questions
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
import { type AskUserQuestionAnswer, type AskUserQuestionRequest } from '@deepseek-ai/dsh-user-questions';
|
|
17
|
+
/** One question request waiting on the human, with its settle channels. */
|
|
18
|
+
export interface PendingQuestion {
|
|
19
|
+
/** The request the renderer walks through question by question. */
|
|
20
|
+
request: AskUserQuestionRequest;
|
|
21
|
+
/** Resolve the provider promise with the collected answers. */
|
|
22
|
+
resolve(answers: AskUserQuestionAnswer): void;
|
|
23
|
+
/** Reject the provider promise as aborted (also used for Esc cancel). */
|
|
24
|
+
reject(error: Error): void;
|
|
25
|
+
}
|
|
26
|
+
/** The pending-question snapshot the renderer subscribes to. */
|
|
27
|
+
export interface QuestionSnapshot {
|
|
28
|
+
/** The active request, or undefined when nothing is being asked. */
|
|
29
|
+
pending: PendingQuestion | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Store the pending question lands in; the renderer reads, the provider writes. */
|
|
32
|
+
export interface QuestionStore {
|
|
33
|
+
/** Subscribe to pending-state changes; returns the unsubscribe function. */
|
|
34
|
+
subscribe(listener: () => void): () => void;
|
|
35
|
+
/** Read the current snapshot (identity-stable between changes). */
|
|
36
|
+
getSnapshot(): QuestionSnapshot;
|
|
37
|
+
/** Submit the collected answers for the active request and advance the queue. */
|
|
38
|
+
submit(pending: PendingQuestion, answers: AskUserQuestionAnswer): void;
|
|
39
|
+
/** Cancel the active request (Esc) — rejects ASK_ABORTED and advances the queue. */
|
|
40
|
+
cancel(pending: PendingQuestion): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Mount the single `ctx.userQuestions` UI provider over a FIFO queue.
|
|
44
|
+
* @param ctx - context carrying the `userQuestions` service (dsh-base).
|
|
45
|
+
* @returns the store the renderer subscribes to; a context without the
|
|
46
|
+
* service yields a permanently empty store.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mountQuestionProvider(ctx: Context): QuestionStore;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal animation frame tables derived from the web design language:
|
|
3
|
+
* the StateDot "ongoing" pixel chase (3×3 ring, 125ms flat-hold brightness
|
|
4
|
+
* steps, 1s cycle) becomes the single-cell stepped pulse below, and the
|
|
5
|
+
* streaming caret blink is the Claude-Code convention. Pure functions only —
|
|
6
|
+
* the Ink layer owns timers and colors.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-code/render/animations
|
|
9
|
+
*/
|
|
10
|
+
/** Single-cell stepped pulse: flat holds mirroring the web's 125ms keyframes. */
|
|
11
|
+
export declare const PULSE_FRAMES: readonly ["█", "█", "▆", "▃", "▁", "▃", "▆", "█"];
|
|
12
|
+
/** Pulse frame for a monotonic tick. */
|
|
13
|
+
export declare function pulseFrame(tick: number): string;
|
|
14
|
+
/** Caret visibility: half the ticks on, half off (530ms blink). */
|
|
15
|
+
export declare function caretVisible(tick: number): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal markdown renderer for assistant replies: a pure GFM-subset
|
|
3
|
+
* block/inline parser producing styled line segments the Ink renderer maps
|
|
4
|
+
* to colored text. No ANSI here — the app owns color mapping, tests own the
|
|
5
|
+
* structure. The subset mirrors what agent replies actually emit: headings,
|
|
6
|
+
* emphasis, inline/fenced code, flat lists, blockquotes, links, rules, and
|
|
7
|
+
* wrapped paragraphs. Unknown syntax degrades to plain text (never throws).
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/render/markdown
|
|
10
|
+
*/
|
|
11
|
+
/** Style classes the renderer emits; the app maps them to colors/props. */
|
|
12
|
+
export type MdStyle = 'plain' | 'bold' | 'italic' | 'boldItalic' | 'code' | 'accent' | 'dim' | 'strike';
|
|
13
|
+
/** One styled run of text. */
|
|
14
|
+
export interface MdSegment {
|
|
15
|
+
/** Visible text (no ANSI). */
|
|
16
|
+
text: string;
|
|
17
|
+
/** Presentation class for the app's color map. */
|
|
18
|
+
style: MdStyle;
|
|
19
|
+
}
|
|
20
|
+
/** One rendered line: a sequence of styled runs. */
|
|
21
|
+
export interface MdLine {
|
|
22
|
+
segments: readonly MdSegment[];
|
|
23
|
+
}
|
|
24
|
+
/** Visible width of a run in columns (CJK counts double). */
|
|
25
|
+
export declare function visibleColumns(text: string): number;
|
|
26
|
+
/** Render markdown text into styled lines of at most `width` columns. */
|
|
27
|
+
export declare function renderMarkdown(text: string, width: number): readonly MdLine[];
|
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module @deepseek-ai/dsh-tui/render/projection
|
|
8
8
|
*/
|
|
9
|
-
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
9
|
+
import type { SessionEvent, TodoItem } from '@deepseek-ai/dsh-session';
|
|
10
10
|
/** One user prompt line. */
|
|
11
11
|
export interface UserEntry {
|
|
12
12
|
kind: 'user';
|
|
13
13
|
/** Joined text blocks of the user message. */
|
|
14
14
|
text: string;
|
|
15
|
+
/** True for collapsed injected context (plugin/continuation notices), which
|
|
16
|
+
* the renderer marks with a dim ↳ instead of the user ❯ prompt. */
|
|
17
|
+
notice: boolean;
|
|
15
18
|
}
|
|
16
19
|
/** One assembled assistant reply. */
|
|
17
20
|
export interface AssistantEntry {
|
|
18
21
|
kind: 'assistant';
|
|
19
22
|
/** Joined text blocks of the assistant message. */
|
|
20
23
|
text: string;
|
|
24
|
+
/** Joined reasoning blocks of the same message, empty when the model thought out loud. */
|
|
25
|
+
reasoning: string;
|
|
21
26
|
}
|
|
22
27
|
/** One model-requested tool invocation and its settled state. */
|
|
23
28
|
export interface ToolEntry {
|
|
@@ -28,11 +33,27 @@ export interface ToolEntry {
|
|
|
28
33
|
name: string;
|
|
29
34
|
/** Raw arguments JSON string exactly as the model produced it. */
|
|
30
35
|
arguments: string;
|
|
36
|
+
/** Bounded human-meaningful arguments preview for the tool card. */
|
|
37
|
+
preview: string;
|
|
31
38
|
/** Execution state; `running` until the paired result lands. */
|
|
32
39
|
state: 'running' | 'done' | 'error';
|
|
33
40
|
/** Bounded first text block of the result, empty until it lands. */
|
|
34
41
|
summary: string;
|
|
35
42
|
}
|
|
43
|
+
/** One slash-command execution dispatched through `ctx.commands`. */
|
|
44
|
+
export interface CommandEntry {
|
|
45
|
+
kind: 'command';
|
|
46
|
+
/** Pairing id shared with the matching `command/done`. */
|
|
47
|
+
commandId: string;
|
|
48
|
+
/** Lowercase command name without the leading slash. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Verbatim text following the command name. */
|
|
51
|
+
args: string;
|
|
52
|
+
/** Execution state; `running` until the paired lifecycle event lands. */
|
|
53
|
+
state: 'running' | 'done' | 'error';
|
|
54
|
+
/** Handler outcome text, empty until it lands. */
|
|
55
|
+
summary: string;
|
|
56
|
+
}
|
|
36
57
|
/** One turn-level failure surfaced from `turn/end`. */
|
|
37
58
|
export interface ErrorEntry {
|
|
38
59
|
kind: 'error';
|
|
@@ -40,7 +61,7 @@ export interface ErrorEntry {
|
|
|
40
61
|
text: string;
|
|
41
62
|
}
|
|
42
63
|
/** Ordered transcript items the renderer draws. */
|
|
43
|
-
export type TranscriptEntry = UserEntry | AssistantEntry | ToolEntry | ErrorEntry;
|
|
64
|
+
export type TranscriptEntry = UserEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry;
|
|
44
65
|
/** Cumulative token accounting folded from `assistant/message` usage reports. */
|
|
45
66
|
export interface UsageTotals {
|
|
46
67
|
/** Prompt-side billed tokens: `inputTokens` plus both cache buckets. */
|
|
@@ -69,12 +90,25 @@ export interface TranscriptView {
|
|
|
69
90
|
entries: readonly TranscriptEntry[];
|
|
70
91
|
/** Text accumulated from `assistant/chunk` deltas since the last flush. */
|
|
71
92
|
streaming: string;
|
|
93
|
+
/** Thinking accumulated from `assistant/chunk` reasoning deltas since the last flush. */
|
|
94
|
+
streamingReasoning: string;
|
|
72
95
|
/** Latest whole-list todo snapshot from `todo/write`, empty when none. */
|
|
73
|
-
todos:
|
|
96
|
+
todos: readonly TodoItem[];
|
|
74
97
|
/** True while a durable turn is open (`turn/start` … `turn/end`). */
|
|
75
98
|
busy: boolean;
|
|
76
99
|
/** Figures the status line renders. */
|
|
77
100
|
stats: TranscriptStats;
|
|
101
|
+
/**
|
|
102
|
+
* The `provider/model` pair of the last `request/header` snapshot — the
|
|
103
|
+
* session's own model record, which a resumed TUI prefers over the
|
|
104
|
+
* deployment default (mirrors the web host's resume selection order).
|
|
105
|
+
* Empty before the session's first request.
|
|
106
|
+
*/
|
|
107
|
+
model: string;
|
|
108
|
+
/** Plan mode state folded from the last `plan/mode` event. */
|
|
109
|
+
plan: boolean;
|
|
110
|
+
/** Active permission preset folded from the last `permission/preset` event, empty before one. */
|
|
111
|
+
permission: string;
|
|
78
112
|
/**
|
|
79
113
|
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
80
114
|
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
@@ -37,6 +37,10 @@ export interface StatusFacts {
|
|
|
37
37
|
branch: string;
|
|
38
38
|
/** Short session identifier (last dash-separated segment or tail). */
|
|
39
39
|
sessionId: string;
|
|
40
|
+
/** Whether plan mode is active (folded from `plan/mode`). */
|
|
41
|
+
plan: boolean;
|
|
42
|
+
/** Active permission preset (folded from `permission/preset`), empty when unknown. */
|
|
43
|
+
permission: string;
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
42
46
|
* Build the footer's display groups; a group with no data drops out whole.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-boundary sanitization for externally sourced text (model output,
|
|
3
|
+
* tool payloads, skill descriptions). Control characters — including ANSI
|
|
4
|
+
* CSI/OSC escape sequences — would otherwise pass through Ink into the
|
|
5
|
+
* terminal, letting output rewrite the screen or inject prompts. Newlines
|
|
6
|
+
* and tabs survive; everything else in C0/C1 plus DEL becomes a visible
|
|
7
|
+
* `\xNN` escape.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/render/text
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Escape control characters so externally sourced text cannot drive the
|
|
13
|
+
* terminal.
|
|
14
|
+
* @param text - raw text from a session event, tool payload, or catalog.
|
|
15
|
+
* @returns text with every control character (except `\n`, `\t`) rendered
|
|
16
|
+
* as a literal `\xNN` escape.
|
|
17
|
+
*/
|
|
18
|
+
export declare function displayText(text: string): string;
|