dsh-code 0.2.0 → 0.4.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 +13 -2
- package/README.zh.md +11 -2
- package/cordis.patch.yml +6 -0
- package/lib/index.mjs +2793 -394
- package/lib/types/app.d.ts +12 -0
- package/lib/types/mentions.d.ts +70 -0
- package/lib/types/questions.d.ts +48 -0
- package/lib/types/render/animations.d.ts +15 -0
- package/lib/types/render/export.d.ts +15 -0
- package/lib/types/render/inspector.d.ts +30 -0
- package/lib/types/render/lines.d.ts +31 -0
- package/lib/types/render/markdown.d.ts +27 -0
- package/lib/types/render/projection.d.ts +107 -2
- package/lib/types/render/status.d.ts +21 -0
- package/lib/types/render/text.d.ts +18 -0
- package/lib/types/render/tool-detail.d.ts +92 -0
- package/lib/types/render/tool-preview.d.ts +15 -0
- package/lib/types/store.d.ts +2 -0
- package/lib/types/theme.d.ts +4 -0
- package/package.json +20 -2
- package/src/app.ts +1507 -149
- package/src/index.ts +156 -46
- package/src/mentions.ts +193 -0
- package/src/pictures/1.png +0 -0
- package/src/questions.ts +143 -0
- package/src/render/animations.ts +22 -0
- package/src/render/export.ts +81 -0
- package/src/render/inspector.ts +79 -0
- package/src/render/lines.ts +207 -0
- package/src/render/markdown.ts +235 -0
- package/src/render/projection.ts +322 -18
- package/src/render/status.ts +62 -3
- package/src/render/text.ts +79 -0
- package/src/render/tool-detail.ts +197 -0
- package/src/render/tool-preview.ts +34 -0
- package/src/store.ts +8 -0
- package/src/theme.ts +4 -0
package/lib/types/app.d.ts
CHANGED
|
@@ -18,13 +18,17 @@ import type { TranscriptStore } from './store.ts';
|
|
|
18
18
|
import type { ApprovalStore } from './approval.ts';
|
|
19
19
|
import type { CommandsView } from './commands.ts';
|
|
20
20
|
import type { ModelDirectory, ModelRow } from './models.ts';
|
|
21
|
+
import type { QuestionStore } from './questions.ts';
|
|
21
22
|
import type { SkillsView } from './skills.ts';
|
|
23
|
+
import type { MentionCandidate } from './mentions.ts';
|
|
22
24
|
/** Props the runner hands the app; callbacks stay owned by the runner. */
|
|
23
25
|
export interface AppProps {
|
|
24
26
|
/** Event-fed transcript store for the live session. */
|
|
25
27
|
store: TranscriptStore;
|
|
26
28
|
/** Approval-question store fed by the answerer listener. */
|
|
27
29
|
approval: ApprovalStore;
|
|
30
|
+
/** ask_user_question store fed by the single UI provider. */
|
|
31
|
+
questions: QuestionStore;
|
|
28
32
|
/** Live slash-command descriptor list (completion candidates). */
|
|
29
33
|
commands: CommandsView;
|
|
30
34
|
/** Live user-invocable skill catalog (completion candidates). */
|
|
@@ -49,8 +53,16 @@ export interface AppProps {
|
|
|
49
53
|
quit(): void;
|
|
50
54
|
/** Load the selectable model directory (called when /model opens). */
|
|
51
55
|
loadModels(): Promise<ModelDirectory>;
|
|
56
|
+
/** Load @mention candidates for the typed query (files + sessions). */
|
|
57
|
+
loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
|
|
52
58
|
/** Apply one /model selection; returns the display label. */
|
|
53
59
|
selectModel(row: ModelRow): string;
|
|
60
|
+
/** Cycle to the next permission preset (Shift+Tab); returns the new label. */
|
|
61
|
+
cyclePermission(): string;
|
|
62
|
+
/** Export the transcript to a markdown file (/export [path]); reports via notices. */
|
|
63
|
+
exportTranscript(argument: string): Promise<void>;
|
|
64
|
+
/** Rename the session (/title <text>); returns the outcome line for the notice. */
|
|
65
|
+
renameTitle(argument: string): string;
|
|
54
66
|
/** Registers the app's notice channel with the runner (called once on mount). */
|
|
55
67
|
onBridgeReady(bridge: {
|
|
56
68
|
notify(text: string): void;
|
|
@@ -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,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,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown export of one transcript view: the /export command's pure
|
|
3
|
+
* formatter. Deterministic and side-effect free — the runner owns the file
|
|
4
|
+
* write, so tests drive the builder with folded views directly.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-code/render/export
|
|
7
|
+
*/
|
|
8
|
+
import type { TranscriptView } from './projection.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Render the transcript as a standalone markdown document.
|
|
11
|
+
* @param view - the folded transcript view to export.
|
|
12
|
+
* @param sessionId - the full session identity for the header.
|
|
13
|
+
* @returns the complete markdown text.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildExportMarkdown(view: TranscriptView, sessionId: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Pure viewport, selection, and scrolling rules for exclusive TUI panels. */
|
|
2
|
+
/** Terminal-space allocation for the inspector's one dynamic screen. */
|
|
3
|
+
export interface InspectorViewport {
|
|
4
|
+
/** Maximum dynamic rows, kept strictly below the terminal height. */
|
|
5
|
+
maxHeight: number;
|
|
6
|
+
/** Rows available to the selected entry after border, title, and footer. */
|
|
7
|
+
bodyRows: number;
|
|
8
|
+
/** Columns available inside the horizontal border and padding. */
|
|
9
|
+
contentColumns: number;
|
|
10
|
+
/** Tiny terminals use a borderless one-line close hint. */
|
|
11
|
+
compact: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Keep the inspector plus its persistent status/composer chrome below
|
|
15
|
+
* `stdout.rows`: at equality Ink clears the terminal and rewrites all
|
|
16
|
+
* accumulated `<Static>` output on every frame.
|
|
17
|
+
*/
|
|
18
|
+
export declare function panelViewport(columns: number, rows: number): InspectorViewport;
|
|
19
|
+
/** Backward-compatible name for the Ctrl+O-specific caller and tests. */
|
|
20
|
+
export declare function inspectorViewport(columns: number, rows: number): InspectorViewport;
|
|
21
|
+
/** Clamp a first-visible row to the range representable by one viewport. */
|
|
22
|
+
export declare function clampScroll(offset: number, totalRows: number, visibleRows: number): number;
|
|
23
|
+
/** Move a viewport by a signed row delta without escaping its content. */
|
|
24
|
+
export declare function moveScroll(offset: number, delta: number, totalRows: number, visibleRows: number): number;
|
|
25
|
+
/** Keep one focused row visible while preserving the current window when possible. */
|
|
26
|
+
export declare function revealRow(offset: number, row: number, totalRows: number, visibleRows: number): number;
|
|
27
|
+
/** Center a selected list row where possible, clamped at both ends. */
|
|
28
|
+
export declare function selectionWindow(cursor: number, totalRows: number, visibleRows: number): number;
|
|
29
|
+
/** Follow appended history only while the inspector cursor was at the tail. */
|
|
30
|
+
export declare function followInspectorCursor(cursor: number, previousLength: number, nextLength: number): number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Width-safe styled physical rows for bounded terminal panels. */
|
|
2
|
+
import type { TranscriptEntry } from './projection.ts';
|
|
3
|
+
import { type MdStyle } from './markdown.ts';
|
|
4
|
+
/** Presentation classes mapped to Ink colors by the app boundary. */
|
|
5
|
+
export type LineStyle = MdStyle | 'brand' | 'success' | 'error' | 'warn' | 'dimItalic';
|
|
6
|
+
/** One styled run within a physical terminal row. */
|
|
7
|
+
export interface StyledSegment {
|
|
8
|
+
text: string;
|
|
9
|
+
style: LineStyle;
|
|
10
|
+
}
|
|
11
|
+
/** One row guaranteed not to exceed the requested terminal width. */
|
|
12
|
+
export interface StyledLine {
|
|
13
|
+
segments: readonly StyledSegment[];
|
|
14
|
+
}
|
|
15
|
+
/** Construct one segment without leaking mutable objects into cached rows. */
|
|
16
|
+
export declare function lineSegment(text: string, style?: LineStyle): StyledSegment;
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize and hard-wrap styled content into exact physical rows.
|
|
19
|
+
* Tabs become two visible spaces because terminal tab stops are contextual
|
|
20
|
+
* and therefore cannot participate in a deterministic row budget.
|
|
21
|
+
*/
|
|
22
|
+
export declare function styledLines(segments: readonly StyledSegment[], columns: number): readonly StyledLine[];
|
|
23
|
+
/** Plain/dim text convenience over {@link styledLines}. */
|
|
24
|
+
export declare function textLines(text: string, columns: number, style?: LineStyle): readonly StyledLine[];
|
|
25
|
+
/** Markdown rows re-hardened so a single long word cannot escape the budget. */
|
|
26
|
+
export declare function markdownLines(text: string, columns: number): readonly StyledLine[];
|
|
27
|
+
/**
|
|
28
|
+
* Convert one durable transcript entry to its complete scrollable row model.
|
|
29
|
+
* The source entry stays intact; only the caller's visible slice is rendered.
|
|
30
|
+
*/
|
|
31
|
+
export declare function transcriptEntryLines(entry: TranscriptEntry, columns: number): readonly StyledLine[];
|
|
@@ -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[];
|
|
@@ -7,17 +7,23 @@
|
|
|
7
7
|
* @module @deepseek-ai/dsh-tui/render/projection
|
|
8
8
|
*/
|
|
9
9
|
import type { SessionEvent, TodoItem } from '@deepseek-ai/dsh-session';
|
|
10
|
+
import { type ToolDetail } from './tool-detail.ts';
|
|
10
11
|
/** One user prompt line. */
|
|
11
12
|
export interface UserEntry {
|
|
12
13
|
kind: 'user';
|
|
13
14
|
/** Joined text blocks of the user message. */
|
|
14
15
|
text: string;
|
|
16
|
+
/** True for collapsed injected context (plugin/continuation notices), which
|
|
17
|
+
* the renderer marks with a dim ↳ instead of the user ❯ prompt. */
|
|
18
|
+
notice: boolean;
|
|
15
19
|
}
|
|
16
20
|
/** One assembled assistant reply. */
|
|
17
21
|
export interface AssistantEntry {
|
|
18
22
|
kind: 'assistant';
|
|
19
23
|
/** Joined text blocks of the assistant message. */
|
|
20
24
|
text: string;
|
|
25
|
+
/** Joined reasoning blocks of the same message, empty when the model thought out loud. */
|
|
26
|
+
reasoning: string;
|
|
21
27
|
}
|
|
22
28
|
/** One model-requested tool invocation and its settled state. */
|
|
23
29
|
export interface ToolEntry {
|
|
@@ -28,10 +34,18 @@ export interface ToolEntry {
|
|
|
28
34
|
name: string;
|
|
29
35
|
/** Raw arguments JSON string exactly as the model produced it. */
|
|
30
36
|
arguments: string;
|
|
37
|
+
/** Bounded human-meaningful arguments preview for the tool card. */
|
|
38
|
+
preview: string;
|
|
31
39
|
/** Execution state; `running` until the paired result lands. */
|
|
32
40
|
state: 'running' | 'done' | 'error';
|
|
33
41
|
/** Bounded first text block of the result, empty until it lands. */
|
|
34
42
|
summary: string;
|
|
43
|
+
/**
|
|
44
|
+
* Bounded expansion payload for the verbose transcript (Ctrl+O), derived
|
|
45
|
+
* from the tool's persisted presentation metadata; undefined until the
|
|
46
|
+
* result lands and only when something renderable exists.
|
|
47
|
+
*/
|
|
48
|
+
detail: ToolDetail | undefined;
|
|
35
49
|
}
|
|
36
50
|
/** One slash-command execution dispatched through `ctx.commands`. */
|
|
37
51
|
export interface CommandEntry {
|
|
@@ -53,8 +67,57 @@ export interface ErrorEntry {
|
|
|
53
67
|
/** `code: message` of the failure. */
|
|
54
68
|
text: string;
|
|
55
69
|
}
|
|
70
|
+
/** One non-error turn outcome surfaced from `turn/end`. */
|
|
71
|
+
export interface TurnMarkerEntry {
|
|
72
|
+
kind: 'turn-marker';
|
|
73
|
+
/** Human-readable outcome line, dim-rendered. */
|
|
74
|
+
text: string;
|
|
75
|
+
}
|
|
76
|
+
/** One completed compaction lifecycle surfaced from `compaction/end`. */
|
|
77
|
+
export interface CompactionEntry {
|
|
78
|
+
kind: 'compaction';
|
|
79
|
+
/** True when the compaction completed, false when it failed. */
|
|
80
|
+
ok: boolean;
|
|
81
|
+
/** Heuristic tokens shadowed by the compaction (summary or prune price). */
|
|
82
|
+
tokens: number;
|
|
83
|
+
/** Failure text when `ok` is false, empty otherwise. */
|
|
84
|
+
error: string;
|
|
85
|
+
}
|
|
86
|
+
/** One provider-routed model-request retry (the `llm/retry` pair). */
|
|
87
|
+
export interface RetryEntry {
|
|
88
|
+
kind: 'retry';
|
|
89
|
+
/** Correlation id shared with the matching `llm/retry-started`. */
|
|
90
|
+
retryId: string;
|
|
91
|
+
/** Attempt ordinal and its cap. */
|
|
92
|
+
attempt: number;
|
|
93
|
+
max: number;
|
|
94
|
+
/** Failure code that triggered the retry. */
|
|
95
|
+
code: string;
|
|
96
|
+
/** Backoff wait before the next attempt, in ms. */
|
|
97
|
+
delayMs: number;
|
|
98
|
+
/** `running` while the backoff waits, `done` once the attempt started. */
|
|
99
|
+
state: 'running' | 'done';
|
|
100
|
+
}
|
|
101
|
+
/** Turn-tail deliverables: files mutated by the turn's diff-bearing tools. */
|
|
102
|
+
export interface FilesEntry {
|
|
103
|
+
kind: 'files';
|
|
104
|
+
/** Unique mutated paths in call order, bounded. */
|
|
105
|
+
paths: readonly string[];
|
|
106
|
+
}
|
|
56
107
|
/** Ordered transcript items the renderer draws. */
|
|
57
|
-
export type TranscriptEntry = UserEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry;
|
|
108
|
+
export type TranscriptEntry = UserEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry;
|
|
109
|
+
/** The live goal the status line badges, folded from `goal/change`. */
|
|
110
|
+
export interface GoalFold {
|
|
111
|
+
/** Human-requested completion objective. */
|
|
112
|
+
objective: string;
|
|
113
|
+
/** Durable lifecycle phase. */
|
|
114
|
+
phase: 'active' | 'paused' | 'blocked' | 'complete';
|
|
115
|
+
/** Highest admitted continuation round and its cap. */
|
|
116
|
+
rounds: number;
|
|
117
|
+
max: number;
|
|
118
|
+
/** Blocked explanation, empty outside the blocked phase. */
|
|
119
|
+
blocked: string;
|
|
120
|
+
}
|
|
58
121
|
/** Cumulative token accounting folded from `assistant/message` usage reports. */
|
|
59
122
|
export interface UsageTotals {
|
|
60
123
|
/** Prompt-side billed tokens: `inputTokens` plus both cache buckets. */
|
|
@@ -76,17 +139,33 @@ export interface TranscriptStats {
|
|
|
76
139
|
toolMs: number;
|
|
77
140
|
/** Cumulative token accounting; input stays 0 until a report lands. */
|
|
78
141
|
usage: UsageTotals;
|
|
142
|
+
/** Prompt-side size of the most recent reported request (context pressure). */
|
|
143
|
+
lastPromptTokens: number;
|
|
144
|
+
/** Newest advertised route capacity, 0 when no adapter ever advertised one. */
|
|
145
|
+
contextWindow: number;
|
|
146
|
+
/** Summed first-token waits: `step/start` → first non-empty chunk, in ms. */
|
|
147
|
+
ttftMs: number;
|
|
148
|
+
/** Steps that produced a first chunk (the TTFT average's denominator). */
|
|
149
|
+
ttftSteps: number;
|
|
150
|
+
/** Summed decode spans: first chunk → `assistant/message`, in ms. */
|
|
151
|
+
decodeMs: number;
|
|
152
|
+
/** Completion tokens over timed decode spans (the tok/s numerator). */
|
|
153
|
+
decodeTokens: number;
|
|
79
154
|
}
|
|
80
155
|
/** The complete TUI transcript view for one session. */
|
|
81
156
|
export interface TranscriptView {
|
|
82
157
|
/** Settled entries in log order. */
|
|
83
158
|
entries: readonly TranscriptEntry[];
|
|
84
|
-
/**
|
|
159
|
+
/** Bounded text tail accumulated from `assistant/chunk` deltas since the last flush. */
|
|
85
160
|
streaming: string;
|
|
161
|
+
/** Bounded thinking tail accumulated from reasoning deltas since the last flush. */
|
|
162
|
+
streamingReasoning: string;
|
|
86
163
|
/** Latest whole-list todo snapshot from `todo/write`, empty when none. */
|
|
87
164
|
todos: readonly TodoItem[];
|
|
88
165
|
/** True while a durable turn is open (`turn/start` … `turn/end`). */
|
|
89
166
|
busy: boolean;
|
|
167
|
+
/** `turn/start` time of the open turn (0 while idle) — the web TurnStatus clock anchor. */
|
|
168
|
+
busySince: number;
|
|
90
169
|
/** Figures the status line renders. */
|
|
91
170
|
stats: TranscriptStats;
|
|
92
171
|
/**
|
|
@@ -96,6 +175,16 @@ export interface TranscriptView {
|
|
|
96
175
|
* Empty before the session's first request.
|
|
97
176
|
*/
|
|
98
177
|
model: string;
|
|
178
|
+
/** Plan mode state folded from the last `plan/mode` event. */
|
|
179
|
+
plan: boolean;
|
|
180
|
+
/** Active permission preset folded from the last `permission/preset` event, empty before one. */
|
|
181
|
+
permission: string;
|
|
182
|
+
/** Latest session title folded from the last `session/title` event, empty before one. */
|
|
183
|
+
title: string;
|
|
184
|
+
/** Sandbox-mode override folded from the last `sandbox/mode` event, empty when never switched. */
|
|
185
|
+
sandbox: string;
|
|
186
|
+
/** Current long-running goal folded from the last `goal/change`, undefined when cleared. */
|
|
187
|
+
goal: GoalFold | undefined;
|
|
99
188
|
/**
|
|
100
189
|
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
101
190
|
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
@@ -104,6 +193,10 @@ export interface TranscriptView {
|
|
|
104
193
|
readonly anchors: {
|
|
105
194
|
stepStart: Map<string, number>;
|
|
106
195
|
toolStart: Map<string, number>;
|
|
196
|
+
firstChunkAt: Map<string, number>;
|
|
197
|
+
compactionTokens: Map<string, number>;
|
|
198
|
+
lastPruneTokens: number;
|
|
199
|
+
turnFiles: Map<number, Set<string>>;
|
|
107
200
|
};
|
|
108
201
|
}
|
|
109
202
|
/** A fresh, empty transcript view. */
|
|
@@ -121,3 +214,15 @@ export declare function projectEvent(view: TranscriptView, event: SessionEvent):
|
|
|
121
214
|
* @returns the folded view.
|
|
122
215
|
*/
|
|
123
216
|
export declare function projectEvents(events: readonly SessionEvent[]): TranscriptView;
|
|
217
|
+
/**
|
|
218
|
+
* How many leading transcript entries can never change again: only a
|
|
219
|
+
* `running` tool or retry can still mutate in place — everything before the
|
|
220
|
+
* first one (including a completed tail: later events only APPEND new rows)
|
|
221
|
+
* is final. The renderer currently draws the whole transcript dynamically
|
|
222
|
+
* (a `<Static>` flush proved unstable with CJK wrapping on real terminals);
|
|
223
|
+
* this boundary stays as the append-only contract for when flushing is
|
|
224
|
+
* reintroduced.
|
|
225
|
+
* @param entries - the view's transcript entries in order.
|
|
226
|
+
* @returns the count of entries safe to flush (0 for an empty transcript).
|
|
227
|
+
*/
|
|
228
|
+
export declare function settledEntryCount(entries: readonly TranscriptEntry[]): number;
|
|
@@ -21,6 +21,13 @@ export declare function formatTokens(n: number): string;
|
|
|
21
21
|
* @returns display string.
|
|
22
22
|
*/
|
|
23
23
|
export declare function formatDuration(ms: number): string;
|
|
24
|
+
/**
|
|
25
|
+
* Compact decode rate: one decimal under a hundred, whole below a thousand,
|
|
26
|
+
* then thousands (15.3 / 124 / 1.2K).
|
|
27
|
+
* @param n - tokens per second.
|
|
28
|
+
* @returns display string.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatRate(n: number): string;
|
|
24
31
|
/**
|
|
25
32
|
* Cache-hit share of billed prompt-side input.
|
|
26
33
|
* @param usage - cumulative token totals.
|
|
@@ -37,6 +44,20 @@ export interface StatusFacts {
|
|
|
37
44
|
branch: string;
|
|
38
45
|
/** Short session identifier (last dash-separated segment or tail). */
|
|
39
46
|
sessionId: string;
|
|
47
|
+
/** Latest session title (folded from `session/title`); shown in place of the id. */
|
|
48
|
+
title: string;
|
|
49
|
+
/** Sandbox-mode override (folded from `sandbox/mode`), empty when never switched. */
|
|
50
|
+
sandbox: string;
|
|
51
|
+
/** Live goal summary (folded from `goal/change`), undefined when none. */
|
|
52
|
+
goal: {
|
|
53
|
+
phase: string;
|
|
54
|
+
rounds: number;
|
|
55
|
+
max: number;
|
|
56
|
+
} | undefined;
|
|
57
|
+
/** Whether plan mode is active (folded from `plan/mode`). */
|
|
58
|
+
plan: boolean;
|
|
59
|
+
/** Active permission preset (folded from `permission/preset`), empty when unknown. */
|
|
60
|
+
permission: string;
|
|
40
61
|
}
|
|
41
62
|
/**
|
|
42
63
|
* Build the footer's display groups; a group with no data drops out whole.
|
|
@@ -16,3 +16,21 @@
|
|
|
16
16
|
* as a literal `\xNN` escape.
|
|
17
17
|
*/
|
|
18
18
|
export declare function displayText(text: string): string;
|
|
19
|
+
/** A display-safe suffix bounded by terminal rows and columns. */
|
|
20
|
+
export interface DisplayTail {
|
|
21
|
+
/** Sanitized suffix suitable for direct terminal rendering. */
|
|
22
|
+
text: string;
|
|
23
|
+
/** Whether content before the returned suffix was omitted. */
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Keep only the newest display-safe text that fits a terminal rectangle.
|
|
28
|
+
* The scan walks backward and stops as soon as the suffix is full, so a long
|
|
29
|
+
* reasoning stream does not rescan its entire accumulated prefix per chunk.
|
|
30
|
+
* Explicit newlines and terminal wrapping both consume rows.
|
|
31
|
+
* @param text - raw externally sourced text.
|
|
32
|
+
* @param columns - available terminal columns.
|
|
33
|
+
* @param rows - available terminal rows.
|
|
34
|
+
* @returns a sanitized bounded suffix and whether an earlier prefix was cut.
|
|
35
|
+
*/
|
|
36
|
+
export declare function displayTail(text: string, columns: number, rows: number): DisplayTail;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expansion payloads for tool cards (the Ctrl+O verbose transcript): the
|
|
3
|
+
* TUI-side consumption of the harness presentation contract. Mutation and
|
|
4
|
+
* read tools persist a structured `tool/result.meta` (`diffs`, read
|
|
5
|
+
* windows, web sources) exactly so a capable UI can replay richer cards than
|
|
6
|
+
* the model-facing text; this module narrows that opaque JSON defensively —
|
|
7
|
+
* mirroring the upstream validators — and pre-formats bounded, render-ready
|
|
8
|
+
* rows. Malformed or absent metadata always degrades to the bounded raw
|
|
9
|
+
* result text, never throws during replay.
|
|
10
|
+
*
|
|
11
|
+
* @module @deepseek-ai/dsh-code/render/tool-detail
|
|
12
|
+
*/
|
|
13
|
+
/** One rendered diff row: removed, added, or shared context. */
|
|
14
|
+
export interface DiffLine {
|
|
15
|
+
/** '-' removed, '+' added, ' ' context. */
|
|
16
|
+
mark: '-' | '+' | ' ';
|
|
17
|
+
/** The line text, truncated to the column budget. */
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
/** One file's bounded inline diff. */
|
|
21
|
+
export interface ToolDiff {
|
|
22
|
+
/** File path the change belongs to. */
|
|
23
|
+
path: string;
|
|
24
|
+
/** Rendered rows in order; '-' block before the '+' block. */
|
|
25
|
+
lines: readonly DiffLine[];
|
|
26
|
+
/** True when the line budget cut the hunk. */
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** One numbered line of a read window. */
|
|
30
|
+
export interface ToolReadLine {
|
|
31
|
+
/** 1-based file line number. */
|
|
32
|
+
number: number;
|
|
33
|
+
/** The line text, truncated to the column budget. */
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
/** One web-search source row. */
|
|
37
|
+
export interface ToolWebSource {
|
|
38
|
+
/** Source URL. */
|
|
39
|
+
url: string;
|
|
40
|
+
/** Source title, when the provider returned one. */
|
|
41
|
+
title: string | undefined;
|
|
42
|
+
/** Short excerpt, truncated to the column budget. */
|
|
43
|
+
snippet: string;
|
|
44
|
+
}
|
|
45
|
+
/** The expansion payload a verbose tool card renders; a discriminated union. */
|
|
46
|
+
export type ToolDetail = {
|
|
47
|
+
kind: 'diff';
|
|
48
|
+
diffs: readonly ToolDiff[];
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'read';
|
|
51
|
+
path: string;
|
|
52
|
+
offset: number;
|
|
53
|
+
lines: readonly ToolReadLine[];
|
|
54
|
+
totalLines: number;
|
|
55
|
+
truncated: boolean;
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'web-search';
|
|
58
|
+
sources: readonly ToolWebSource[];
|
|
59
|
+
truncated: boolean;
|
|
60
|
+
} | {
|
|
61
|
+
kind: 'web-fetch';
|
|
62
|
+
url: string;
|
|
63
|
+
statusCode: number;
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'raw';
|
|
66
|
+
text: string;
|
|
67
|
+
truncated: boolean;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Render one change as removed-then-added rows, hunked by common prefix and
|
|
71
|
+
* suffix. A null before-image (file create) renders as pure additions. The
|
|
72
|
+
* budget caps emitted rows and reports the cut, so a whole-file overwrite
|
|
73
|
+
* never floods the transcript.
|
|
74
|
+
* @param oldText - prior content, or null for a create.
|
|
75
|
+
* @param newText - content after the change.
|
|
76
|
+
* @param budget - maximum rows to emit.
|
|
77
|
+
* @returns the bounded rows and whether they were cut.
|
|
78
|
+
*/
|
|
79
|
+
export declare function diffRows(oldText: string | null, newText: string, budget: number): {
|
|
80
|
+
lines: readonly DiffLine[];
|
|
81
|
+
truncated: boolean;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Narrow the opaque `tool/result.meta` into one bounded expansion payload,
|
|
85
|
+
* mirroring the upstream presenters' degradation ladder: diffs (write/edit),
|
|
86
|
+
* read windows (read), sources (web_search), fetch summaries (web_fetch), and
|
|
87
|
+
* the bounded raw result text as the universal fallback.
|
|
88
|
+
* @param meta - the persisted presentation metadata, when the tool attached one.
|
|
89
|
+
* @param rawText - the joined text blocks of the result message.
|
|
90
|
+
* @returns the expansion payload, or undefined when nothing renderable exists.
|
|
91
|
+
*/
|
|
92
|
+
export declare function toolResultDetail(meta: unknown, rawText: string): ToolDetail | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded preview line for a tool invocation's raw JSON arguments: the first
|
|
3
|
+
* human-meaningful string among the well-known keys (command, path, query, …)
|
|
4
|
+
* with a fallback to the bounded raw JSON. Shared by the tool card in the
|
|
5
|
+
* transcript and the approval bar's command preview.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-code/render/tool-preview
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve one bounded preview for raw tool arguments.
|
|
11
|
+
* @param args - raw JSON arguments string as the model produced it.
|
|
12
|
+
* @param toolName - the tool the arguments belong to (fallback label).
|
|
13
|
+
* @returns the preview line; empty when nothing useful resolves.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toolArgumentsPreview(args: string, toolName: string): string;
|
package/lib/types/store.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface TranscriptStore {
|
|
|
16
16
|
subscribe(listener: () => void): () => void;
|
|
17
17
|
/** Fold one session event; ignored events change nothing and notify nobody. */
|
|
18
18
|
apply(event: SessionEvent): void;
|
|
19
|
+
/** Drop the folded view entirely (/clear): the next event starts a fresh one. */
|
|
20
|
+
reset(): void;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Create one transcript store, optionally seeded with replayed history. The
|
package/lib/types/theme.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ export declare const TUI_RGB: {
|
|
|
25
25
|
readonly error: readonly [239, 68, 68];
|
|
26
26
|
/** Warning amber — `--dsw-static-amber-500`. */
|
|
27
27
|
readonly warn: readonly [245, 158, 11];
|
|
28
|
+
/** Default foreground text — `--dsw-static-neutral-50`. */
|
|
29
|
+
readonly text: readonly [236, 240, 246];
|
|
30
|
+
/** Inline/fenced code — soft sky blue, distinct from brand accents. */
|
|
31
|
+
readonly code: readonly [125, 211, 252];
|
|
28
32
|
};
|
|
29
33
|
/** Paint with the primary brand blue: whale, wordmark, tool names, accents. */
|
|
30
34
|
export declare function brand(text: string): string;
|