dsh-code 0.7.0 → 0.9.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.en.md +30 -7
- package/README.md +30 -7
- package/lib/index.mjs +3791 -853
- package/lib/types/app.d.ts +90 -1
- package/lib/types/approval.d.ts +3 -1
- package/lib/types/history.d.ts +15 -4
- package/lib/types/index.d.ts +48 -0
- package/lib/types/kernel-panels.d.ts +65 -8
- package/lib/types/models.d.ts +15 -1
- package/lib/types/permissions.d.ts +37 -0
- package/lib/types/presets.d.ts +2 -0
- package/lib/types/provider-settings.d.ts +144 -0
- package/lib/types/questions.d.ts +2 -0
- package/lib/types/render/animations.d.ts +8 -6
- package/lib/types/render/lines.d.ts +6 -0
- package/lib/types/render/markdown.d.ts +3 -3
- package/lib/types/render/projection.d.ts +97 -3
- package/lib/types/render/status.d.ts +26 -36
- package/lib/types/render/text.d.ts +14 -7
- package/lib/types/render/tool-detail.d.ts +3 -1
- package/lib/types/render/tool-preview.d.ts +14 -1
- package/lib/types/session-directory.d.ts +61 -2
- package/lib/types/store.d.ts +13 -2
- package/lib/types/subagents.d.ts +60 -0
- package/lib/types/version.d.ts +5 -0
- package/package.json +1 -1
- package/src/app.ts +1200 -219
- package/src/approval.ts +161 -126
- package/src/history.ts +20 -5
- package/src/index.ts +577 -167
- package/src/kernel-panels.ts +354 -37
- package/src/models.ts +26 -0
- package/src/permissions.ts +85 -0
- package/src/presets.ts +12 -0
- package/src/provider-settings.ts +520 -0
- package/src/questions.ts +15 -5
- package/src/render/animations.ts +32 -18
- package/src/render/lines.ts +236 -218
- package/src/render/markdown.ts +302 -4
- package/src/render/projection.ts +670 -11
- package/src/render/status.ts +68 -162
- package/src/render/text.ts +28 -9
- package/src/render/tool-detail.ts +81 -40
- package/src/render/tool-preview.ts +77 -34
- package/src/session-directory.ts +171 -10
- package/src/skills.ts +8 -4
- package/src/store.ts +26 -8
- package/src/subagents.ts +165 -0
- package/src/version.ts +16 -0
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* block/inline parser producing styled line segments the Ink renderer maps
|
|
4
4
|
* to colored text. No ANSI here — the app owns color mapping, tests own the
|
|
5
5
|
* structure. The subset mirrors what agent replies actually emit: headings,
|
|
6
|
-
* emphasis, inline/fenced code, flat lists, blockquotes, links, rules,
|
|
7
|
-
* wrapped paragraphs. Unknown syntax degrades to plain text
|
|
6
|
+
* emphasis, inline/fenced code, flat lists, blockquotes, links, rules, GFM
|
|
7
|
+
* tables, and wrapped paragraphs. Unknown syntax degrades to plain text.
|
|
8
8
|
*
|
|
9
9
|
* @module @deepseek-ai/dsh-code/render/markdown
|
|
10
10
|
*/
|
|
11
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';
|
|
12
|
+
export type MdStyle = 'plain' | 'bold' | 'italic' | 'boldItalic' | 'code' | 'accent' | 'accentBold' | 'dim' | 'strike';
|
|
13
13
|
/** One styled run of text. */
|
|
14
14
|
export interface MdSegment {
|
|
15
15
|
/** Visible text (no ANSI). */
|
|
@@ -47,6 +47,8 @@ export interface ToolEntry {
|
|
|
47
47
|
arguments: string;
|
|
48
48
|
/** Bounded human-meaningful arguments preview for the tool card. */
|
|
49
49
|
preview: string;
|
|
50
|
+
/** Bounded delegation prompt (subagent cards' second row), '' when none. */
|
|
51
|
+
prompt: string;
|
|
50
52
|
/** Execution state; `running` until the paired result lands. */
|
|
51
53
|
state: 'running' | 'done' | 'error';
|
|
52
54
|
/** Bounded first text block of the result, empty until it lands. */
|
|
@@ -236,7 +238,10 @@ export interface TranscriptView {
|
|
|
236
238
|
/**
|
|
237
239
|
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
238
240
|
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
239
|
-
* against. Keyed `turn:step` and by call id.
|
|
241
|
+
* against. Keyed `turn:step` and by call id. `turnSteps`/`turnTools`
|
|
242
|
+
* track which step/tool anchors still belong to the open turn so
|
|
243
|
+
* `turn/end` (and a superseding `step/start`) can sweep anchors an
|
|
244
|
+
* interruption left behind; `turnFiles` keys mutated paths by turn.
|
|
240
245
|
*/
|
|
241
246
|
readonly anchors: {
|
|
242
247
|
stepStart: Map<string, number>;
|
|
@@ -245,6 +250,8 @@ export interface TranscriptView {
|
|
|
245
250
|
compactionTokens: Map<string, number>;
|
|
246
251
|
lastPruneTokens: number;
|
|
247
252
|
turnFiles: Map<number, Set<string>>;
|
|
253
|
+
turnSteps: Map<number, string>;
|
|
254
|
+
turnTools: Map<number, Set<string>>;
|
|
248
255
|
};
|
|
249
256
|
}
|
|
250
257
|
/** A fresh, empty transcript view. */
|
|
@@ -256,8 +263,92 @@ export declare function createTranscriptView(): TranscriptView;
|
|
|
256
263
|
* @returns the view after the event; the input view is never mutated.
|
|
257
264
|
*/
|
|
258
265
|
export declare function projectEvent(view: TranscriptView, event: SessionEvent): TranscriptView;
|
|
266
|
+
/**
|
|
267
|
+
* Mutable replay accumulator: folds a persisted log into the identical view
|
|
268
|
+
* `projectEvent` would produce, but in near-linear time. Where `projectEvent`
|
|
269
|
+
* is copy-on-write — every append/scan rebuilds the whole `entries` array, so
|
|
270
|
+
* folding a full log costs O(N²) — the accumulator appends by push, resolves
|
|
271
|
+
* id-keyed updates (tool/result, command/done, retry-started) through index
|
|
272
|
+
* maps, and tombstones retired pending rows, so the whole log folds in O(N)
|
|
273
|
+
* plus one compaction pass when tombstones exist.
|
|
274
|
+
*
|
|
275
|
+
* Index maps never delete: every appended row registers its index, so an id
|
|
276
|
+
* lookup miss provably means no matching row exists and the update is an O(1)
|
|
277
|
+
* no-op (a malicious/orphan-heavy log cannot force per-orphan full-array
|
|
278
|
+
* scans). Each id maps to ALL of its indices, so a duplicate id updates every
|
|
279
|
+
* matching row exactly like the copy-on-write reducer.
|
|
280
|
+
*
|
|
281
|
+
* @internal Exported only so tests can (a) prove replay ≡ sequential
|
|
282
|
+
* `projectEvent` folds and (b) assert the linear complexity deterministically
|
|
283
|
+
* via {@link ReplayAccumulator.ops}, which counts entry-level container work
|
|
284
|
+
* instead of relying on wall-clock thresholds. No public consumer.
|
|
285
|
+
*/
|
|
286
|
+
export interface ReplayAccumulator {
|
|
287
|
+
/** Working entry list; `undefined` marks a retired pending row (tombstone). */
|
|
288
|
+
entries: (TranscriptEntry | undefined)[];
|
|
289
|
+
/** callId → every index into `entries` holding a `tool` row with that id. */
|
|
290
|
+
toolIndex: Map<string, number[]>;
|
|
291
|
+
/** commandId → every index into `entries` holding a `command` row with that id. */
|
|
292
|
+
commandIndex: Map<string, number[]>;
|
|
293
|
+
/** retryId → every index into `entries` holding a `retry` row with that id. */
|
|
294
|
+
retryIndex: Map<string, number[]>;
|
|
295
|
+
/** messageId → every index into `entries` holding a `pending` row with that id. */
|
|
296
|
+
pendingIndex: Map<string, number[]>;
|
|
297
|
+
/** Tombstone count; zero means `entries` is already the final array. */
|
|
298
|
+
removedCount: number;
|
|
299
|
+
/** Mutable inbox id lists, mirroring `view.pending` order per target. */
|
|
300
|
+
pendingTurn: string[];
|
|
301
|
+
pendingStep: string[];
|
|
302
|
+
streaming: string;
|
|
303
|
+
streamingReasoning: string;
|
|
304
|
+
todos: readonly TodoItem[];
|
|
305
|
+
busy: boolean;
|
|
306
|
+
busySince: number;
|
|
307
|
+
model: string;
|
|
308
|
+
plan: boolean;
|
|
309
|
+
permission: string;
|
|
310
|
+
title: string;
|
|
311
|
+
sandbox: string;
|
|
312
|
+
goal: GoalFold | undefined;
|
|
313
|
+
stats: TranscriptStats;
|
|
314
|
+
stepStart: Map<string, number>;
|
|
315
|
+
toolStart: Map<string, number>;
|
|
316
|
+
firstChunkAt: Map<string, number>;
|
|
317
|
+
compactionTokens: Map<string, number>;
|
|
318
|
+
lastPruneTokens: number;
|
|
319
|
+
turnFiles: Map<number, Set<string>>;
|
|
320
|
+
turnSteps: Map<number, string>;
|
|
321
|
+
turnTools: Map<number, Set<string>>;
|
|
322
|
+
/** Entry-level container operations performed so far (test instrumentation). */
|
|
323
|
+
ops: number;
|
|
324
|
+
}
|
|
325
|
+
/** @internal A fresh replay accumulator whose state mirrors `createTranscriptView()`. */
|
|
326
|
+
export declare function createReplayAccumulator(): ReplayAccumulator;
|
|
327
|
+
/**
|
|
328
|
+
* Fold one session event into a replay accumulator. This mirrors
|
|
329
|
+
* {@link projectEvent} case for case — same stats arithmetic, same anchor
|
|
330
|
+
* set/delete behavior, same entry shapes — so the finished view is identical
|
|
331
|
+
* to a sequential fold; only the `entries` container operations are mutable.
|
|
332
|
+
*
|
|
333
|
+
* @internal Test-instrumentation path; `projectEvents` is the public entry.
|
|
334
|
+
*/
|
|
335
|
+
export declare function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent): void;
|
|
336
|
+
/**
|
|
337
|
+
* Materialize the accumulated fold as a `TranscriptView`, compacting any
|
|
338
|
+
* retired tombstones. The anchors maps are handed through as-is (their
|
|
339
|
+
* content is identical to a sequential fold's).
|
|
340
|
+
*
|
|
341
|
+
* @internal Test-instrumentation path; `projectEvents` is the public entry.
|
|
342
|
+
*/
|
|
343
|
+
export declare function finishReplay(acc: ReplayAccumulator): TranscriptView;
|
|
259
344
|
/**
|
|
260
345
|
* Fold a replayed event history into one view.
|
|
346
|
+
*
|
|
347
|
+
* Folding is near-linear in the log size: the mutable replay accumulator
|
|
348
|
+
* appends in place and resolves id-keyed updates through index maps, so a
|
|
349
|
+
* long persisted session replays without the O(N²) copy-on-write rebuilds a
|
|
350
|
+
* naive sequential fold would incur. The result is identical to folding
|
|
351
|
+
* {@link projectEvent} per event in order.
|
|
261
352
|
* @param events - events in `seq` order.
|
|
262
353
|
* @returns the folded view.
|
|
263
354
|
*/
|
|
@@ -271,8 +362,11 @@ export declare function projectEvents(events: readonly SessionEvent[]): Transcri
|
|
|
271
362
|
* the inbox claims or cancels them durably (`agent/inbox/spliced` removals,
|
|
272
363
|
* `user/message` retirement), and an append-only `<Static>` flush cannot
|
|
273
364
|
* erase a row that vanishes from the view — the retired row would ghost on
|
|
274
|
-
* screen until the next source-backed replay.
|
|
275
|
-
*
|
|
365
|
+
* screen until the next source-backed replay. Running commands join the
|
|
366
|
+
* mutable boundary for the same reason in reverse: `command/done` mutates the
|
|
367
|
+
* row's state/summary, so a flushed row would keep its stale running mark
|
|
368
|
+
* until a resize-triggered replay. Everything else (including a completed
|
|
369
|
+
* tail) is final: later events only APPEND new rows.
|
|
276
370
|
* @param entries - the view's transcript entries in order.
|
|
277
371
|
* @returns the count of entries safe to flush (0 for an empty transcript).
|
|
278
372
|
*/
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module @deepseek-ai/dsh-tui/render/status
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { TranscriptStats } from './projection.ts';
|
|
13
13
|
/**
|
|
14
14
|
* Compact token count: 517 / 12.2K / 517K / 1.2M (one decimal under three
|
|
15
15
|
* digits), mirroring the web composer's StatsLine format.
|
|
@@ -40,7 +40,7 @@ export declare function cacheHitPercent(usage: TranscriptStats['usage']): number
|
|
|
40
40
|
* Presentation tones for status spans; the footer maps each to a theme color
|
|
41
41
|
* (Codex status-line accents: model/path/branch/state/usage categories).
|
|
42
42
|
*/
|
|
43
|
-
export type StatusTone = 'model' | 'live' | 'path' | 'branch' | 'value' | 'label' | 'meta' | 'accent' | 'success' | 'warn' | 'error' | '
|
|
43
|
+
export type StatusTone = 'model' | 'live' | 'path' | 'branch' | 'value' | 'label' | 'meta' | 'accent' | 'success' | 'warn' | 'error' | 'ctxFill';
|
|
44
44
|
/** One colored run inside the status bar. */
|
|
45
45
|
export interface StatusSpan {
|
|
46
46
|
text: string;
|
|
@@ -64,11 +64,9 @@ export interface StatusRow {
|
|
|
64
64
|
hint: boolean;
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
|
-
* The footer layout: two stacked physical rows. Row 1
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* context progress bar, cache, and duration figures) and degrades to empty
|
|
71
|
-
* before any row-1 content is touched.
|
|
67
|
+
* The footer layout: two stacked physical rows. Row 1 keeps the primary
|
|
68
|
+
* controls in model, cwd, mode, branch, context, permission order. Row 2
|
|
69
|
+
* carries every secondary session/run figure and degrades independently.
|
|
72
70
|
*/
|
|
73
71
|
export interface StatusLayout {
|
|
74
72
|
row1: StatusRow;
|
|
@@ -89,33 +87,21 @@ export declare const STATUS_CYCLE_HINT = " (shift+tab to cycle)";
|
|
|
89
87
|
export declare const CONTEXT_BAR_WIDTH = 24;
|
|
90
88
|
/** Occupancy at which the usage readout flips from brand blue to amber. */
|
|
91
89
|
export declare const CONTEXT_WARN_PERCENT = 90;
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
export declare const CONTEXT_SEGMENTS: readonly ContextSegmentSpec[];
|
|
102
|
-
/**
|
|
103
|
-
* Render context occupancy as a segmented bar: one DeepSeek-blue run per
|
|
104
|
-
* content type (system/prompt/assistant/thinking/tools), column widths
|
|
105
|
-
* proportional to their estimated token share, each with a centered label
|
|
106
|
-
* that shortens to fit (system→sys→s). The remaining free tail is a dim
|
|
107
|
-
* track whose right edge carries the usage readout (`12.3K/1.0M 25%`,
|
|
108
|
-
* shrinking to the bare percent as the tail narrows). The readout flips to
|
|
109
|
-
* amber once occupancy reaches the warning threshold; the segment blues stay
|
|
110
|
-
* untouched so the composition remains readable at full context. The used
|
|
111
|
-
* total comes from the reported `lastPromptTokens`, never from the estimates.
|
|
112
|
-
* @param segments - estimated used tokens per content type.
|
|
90
|
+
/**
|
|
91
|
+
* Render context occupancy as ONE stepless bar: a solid DeepSeek-blue fill
|
|
92
|
+
* run, a dim dotted free track, and the usage readout riding the track's
|
|
93
|
+
* right edge (`12.3K/1.0M 25%`, shrinking to the bare percent as the track
|
|
94
|
+
* narrows). No per-content-type segmentation. Column split is deterministic:
|
|
95
|
+
* the free share is `Math.round(free/window*width)` clamped to at least
|
|
96
|
+
* CONTEXT_MIN_FREE columns and at most the full width; the fill takes every
|
|
97
|
+
* remaining column, so a given occupancy always renders the identical bar.
|
|
98
|
+
* The readout flips to amber once occupancy reaches the warning threshold.
|
|
113
99
|
* @param usedTokens - reported used tokens (drives the readout and percent).
|
|
114
100
|
* @param contextWindow - route capacity.
|
|
115
101
|
* @param width - total bar interior columns.
|
|
116
102
|
* @returns tone-split spans for the footer to paint.
|
|
117
103
|
*/
|
|
118
|
-
export declare function contextBar(
|
|
104
|
+
export declare function contextBar(usedTokens: number, contextWindow: number, width: number): readonly StatusSpan[];
|
|
119
105
|
/**
|
|
120
106
|
* One customizable status item (the Codex /statusline picker contract).
|
|
121
107
|
* 'left' items render as pipe-separated clusters after the identity dot;
|
|
@@ -149,6 +135,12 @@ export declare const DEFAULT_STATUSLINE_ITEMS: readonly StatusItemId[];
|
|
|
149
135
|
* @returns the normalized ordered item list.
|
|
150
136
|
*/
|
|
151
137
|
export declare function parseStatuslineItems(value: unknown): readonly StatusItemId[];
|
|
138
|
+
/**
|
|
139
|
+
* Extra left padding on the secondary row so its content aligns with the
|
|
140
|
+
* model name's left edge on the primary row (padding 2 + busy dot 2). The
|
|
141
|
+
* layout subtracts it from row 2's budget so the indent can never wrap it.
|
|
142
|
+
*/
|
|
143
|
+
export declare const STATUS_ROW2_INDENT = 2;
|
|
152
144
|
/** Identity facts the runner resolves once at mount; empty strings drop out. */
|
|
153
145
|
export interface StatusFacts {
|
|
154
146
|
/** 'provider/model' selection serving this session. */
|
|
@@ -173,7 +165,7 @@ export interface StatusFacts {
|
|
|
173
165
|
} | undefined;
|
|
174
166
|
/** Whether plan mode is active (folded from 'plan/mode'). */
|
|
175
167
|
plan: boolean;
|
|
176
|
-
/** Active
|
|
168
|
+
/** Active or pending permission preset; empty only when the service is unavailable. */
|
|
177
169
|
permission: string;
|
|
178
170
|
}
|
|
179
171
|
/**
|
|
@@ -185,12 +177,10 @@ export interface StatusFacts {
|
|
|
185
177
|
*/
|
|
186
178
|
export declare function permissionTone(permission: string): StatusTone;
|
|
187
179
|
/**
|
|
188
|
-
* Compose the two-row footer layout under a column budget. Row 1
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* (mode, context bar, cache, duration figures) fits its own budget and
|
|
193
|
-
* degrades to empty before any row-1 content is touched.
|
|
180
|
+
* Compose the two-row footer layout under a column budget. Row 1 keeps model,
|
|
181
|
+
* cwd, mode, branch, context, then the right-pinned permission badge and cycle
|
|
182
|
+
* hint. It drops hint, context, and permission before ellipsizing identity.
|
|
183
|
+
* Row 2 fits all secondary figures and state within its own budget.
|
|
194
184
|
* @param facts - identity facts resolved by the runner.
|
|
195
185
|
* @param stats - session figures folded from the durable log.
|
|
196
186
|
* @param columns - usable columns for each row (before their left padding).
|
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
* tool payloads, skill descriptions). Control characters — including ANSI
|
|
4
4
|
* CSI/OSC escape sequences — would otherwise pass through Ink into the
|
|
5
5
|
* terminal, letting output rewrite the screen or inject prompts. Newlines
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* survive; everything else in C0/C1 plus DEL becomes a visible `\xNN`
|
|
7
|
+
* escape, and bidi overrides / invisible format controls / Unicode line and
|
|
8
|
+
* paragraph separators become a visible `\uXXXX` escape (terminal emulators
|
|
9
|
+
* that render bidirectional text would otherwise reorder the displayed
|
|
10
|
+
* glyphs and let a command read as something it is not).
|
|
8
11
|
*
|
|
9
12
|
* @module @deepseek-ai/dsh-code/render/text
|
|
10
13
|
*/
|
|
11
14
|
/**
|
|
12
|
-
* Escape control characters so externally sourced text cannot
|
|
13
|
-
* terminal.
|
|
15
|
+
* Escape control and deceptive characters so externally sourced text cannot
|
|
16
|
+
* drive the terminal. C0/C1/DEL render as a literal `\xNN` escape; bidi,
|
|
17
|
+
* invisible-format, and separator controls render as a literal `\uXXXX`
|
|
18
|
+
* escape. Newlines and tabs survive (budgeted callers normalize tabs).
|
|
14
19
|
* @param text - raw text from a session event, tool payload, or catalog.
|
|
15
|
-
* @returns text with every
|
|
16
|
-
* as a literal `\xNN` escape.
|
|
20
|
+
* @returns display-safe text with every injectable character made visible.
|
|
17
21
|
*/
|
|
18
22
|
export declare function displayText(text: string): string;
|
|
19
23
|
/** Collapse external text to one terminal-safe logical row. */
|
|
@@ -36,7 +40,10 @@ export interface DisplayTail {
|
|
|
36
40
|
* Keep only the newest display-safe text that fits a terminal rectangle.
|
|
37
41
|
* The scan walks backward and stops as soon as the suffix is full, so a long
|
|
38
42
|
* reasoning stream does not rescan its entire accumulated prefix per chunk.
|
|
39
|
-
* Explicit newlines and terminal wrapping both consume rows
|
|
43
|
+
* Explicit newlines and terminal wrapping both consume rows; tabs expand to
|
|
44
|
+
* two spaces so terminal tab stops (which render at contextual column 8
|
|
45
|
+
* boundaries, not at the budgeted cell count) cannot inflate the physical
|
|
46
|
+
* row count of the live region.
|
|
40
47
|
* @param text - raw externally sourced text.
|
|
41
48
|
* @param columns - available terminal columns.
|
|
42
49
|
* @param rows - available terminal rows.
|
|
@@ -70,7 +70,9 @@ export type ToolDetail = {
|
|
|
70
70
|
* Render one change as removed-then-added rows, hunked by common prefix and
|
|
71
71
|
* suffix. A null before-image (file create) renders as pure additions. The
|
|
72
72
|
* budget caps emitted rows and reports the cut, so a whole-file overwrite
|
|
73
|
-
* never floods the transcript.
|
|
73
|
+
* never floods the transcript. Inputs are hard-capped before line splitting
|
|
74
|
+
* and the row list is built incrementally up to the budget — a crafted or
|
|
75
|
+
* replayed giant diff cannot force a full intermediate rows array.
|
|
74
76
|
* @param oldText - prior content, or null for a create.
|
|
75
77
|
* @param newText - content after the change.
|
|
76
78
|
* @param budget - maximum rows to emit.
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Bounded preview line for a tool invocation's raw JSON arguments: the first
|
|
3
3
|
* human-meaningful string among the well-known keys (command, path, query, …)
|
|
4
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.
|
|
5
|
+
* transcript and the approval bar's command preview. Arguments longer than
|
|
6
|
+
* {@link MAX_PARSE_CHARS} are never parsed: the preview is a display concern,
|
|
7
|
+
* and a synchronous `JSON.parse` plus string copies of an unbounded model
|
|
8
|
+
* payload must not run on the approval or projection paths.
|
|
6
9
|
*
|
|
7
10
|
* @module @deepseek-ai/dsh-code/render/tool-preview
|
|
8
11
|
*/
|
|
@@ -13,3 +16,13 @@
|
|
|
13
16
|
* @returns the preview line; empty when nothing useful resolves.
|
|
14
17
|
*/
|
|
15
18
|
export declare function toolArgumentsPreview(args: string, toolName: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Bounded prompt preview for delegation-style tools (`subagent`): the
|
|
21
|
+
* `prompt` argument rendered as the card's second row, so the transcript
|
|
22
|
+
* shows what the child agent was asked — not just its description label —
|
|
23
|
+
* while it runs (Codex's SpawnAgent card preview). Anything else returns ''.
|
|
24
|
+
* @param toolName - the tool the arguments belong to.
|
|
25
|
+
* @param args - raw JSON arguments string as the model produced it.
|
|
26
|
+
* @returns the one-line prompt preview, or '' when none applies.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toolPromptPreview(toolName: string, args: string): string;
|
|
@@ -38,6 +38,8 @@ export interface SessionDirectoryOptions {
|
|
|
38
38
|
export interface SessionRow {
|
|
39
39
|
readonly id: string;
|
|
40
40
|
readonly createdAt: number;
|
|
41
|
+
/** Last-activity timestamp: artifact mtime when known, else createdAt. */
|
|
42
|
+
readonly updatedAt: number;
|
|
41
43
|
readonly cwd: string;
|
|
42
44
|
readonly workspace: string;
|
|
43
45
|
readonly parent?: string;
|
|
@@ -48,7 +50,64 @@ export interface SessionRow {
|
|
|
48
50
|
readonly preset: string;
|
|
49
51
|
readonly title?: string;
|
|
50
52
|
}
|
|
51
|
-
/**
|
|
52
|
-
export declare function
|
|
53
|
+
/** True when the header describes a subagent conversation (durable lineage). */
|
|
54
|
+
export declare function isSubagentSession(header: SessionHeader): boolean;
|
|
55
|
+
/** Platform-consistent path equality for session cwd comparisons. */
|
|
56
|
+
export declare function samePath(left: string | undefined, right: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Unique header match by exact id or unique id prefix (root and subagent
|
|
59
|
+
* headers alike); the caller applies any lineage gate.
|
|
60
|
+
* @param headers - the persisted headers.
|
|
61
|
+
* @param wanted - the id or id prefix.
|
|
62
|
+
* @returns the uniquely matched header.
|
|
63
|
+
* @throws when nothing matches or the prefix is ambiguous.
|
|
64
|
+
*/
|
|
65
|
+
export declare function matchSessionId(headers: readonly SessionHeader[], wanted: string): SessionHeader;
|
|
66
|
+
/** The newest persisted ROOT session pinned to this cwd, or undefined. */
|
|
67
|
+
export declare function newestRootForCwd(headers: readonly SessionHeader[], cwd: string): SessionHeader | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Filter/sort header-only records. No session log is loaded here. Sorting is
|
|
70
|
+
* by LAST ACTIVITY (`updated` — artifact mtime when the caller resolved one,
|
|
71
|
+
* else createdAt), matching the codex resume picker's default UpdatedAt
|
|
72
|
+
* ordering: a session you kept talking in outranks one created later but idle.
|
|
73
|
+
* @param records - the header-only records.
|
|
74
|
+
* @param options - filter/sort options.
|
|
75
|
+
* @param updated - per-session last-activity timestamps, when resolved.
|
|
76
|
+
*/
|
|
77
|
+
export declare function projectSessionRows(records: readonly SessionRecord[], options: SessionDirectoryOptions, updated?: ReadonlyMap<string, number>): SessionRow[];
|
|
53
78
|
/** Merge page-local title observations without disturbing directory order. */
|
|
54
79
|
export declare function mergeSessionTitles(rows: readonly SessionRow[], observations: readonly TitleObservationResult[]): SessionRow[];
|
|
80
|
+
/**
|
|
81
|
+
* Encode a session id the way the JSONL backend does for its on-disk layout
|
|
82
|
+
* (`encodeSegment`: safe units literal, everything else `~XXXX`). Used ONLY to
|
|
83
|
+
* validate that a `locate()` path really is this session's directory before
|
|
84
|
+
* any deletion touches the filesystem — a local copy of the pure upstream
|
|
85
|
+
* contract, kept in sync with `session-persistence-jsonl/src/format.ts`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function encodeSessionSegment(raw: string): string;
|
|
88
|
+
/** The session-log artifact names the JSONL backend may create. */
|
|
89
|
+
export declare const SESSION_ARTIFACT_NAMES: readonly string[];
|
|
90
|
+
/**
|
|
91
|
+
* Guard one `locate()` artifact path before deletion (codex's scoped-path
|
|
92
|
+
* check, adapted to the JSONL layout): the file must be a `session.jsonl`
|
|
93
|
+
* artifact sitting in the directory named exactly `encodeSegment(id)`.
|
|
94
|
+
* @param artifact - the path the persistence backend located.
|
|
95
|
+
* @param id - the session id the artifact claims to belong to.
|
|
96
|
+
* @returns the owning session directory, or undefined when the layout is unexpected.
|
|
97
|
+
*/
|
|
98
|
+
export declare function sessionArtifactDirectory(artifact: string, id: string): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Collect one session's deletion subtree: the id plus every record whose
|
|
101
|
+
* parent chain leads to it (codex deletes subagent threads with their root).
|
|
102
|
+
* @param records - the full directory listing.
|
|
103
|
+
* @param id - the root session id to delete.
|
|
104
|
+
* @returns the ids to delete, root first.
|
|
105
|
+
*/
|
|
106
|
+
export declare function collectDeletionSubtree(records: readonly SessionRecord[], id: string): string[];
|
|
107
|
+
/**
|
|
108
|
+
* Codex-style relative time for session rows ("now", "5m ago", "3h ago",
|
|
109
|
+
* "2d ago"; older than a week falls back to the local date).
|
|
110
|
+
* @param timestamp - epoch milliseconds of the last activity.
|
|
111
|
+
* @param now - the pinned reference clock (one value per list render).
|
|
112
|
+
*/
|
|
113
|
+
export declare function formatRelativeTime(timestamp: number, now: number): string;
|
package/lib/types/store.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Observable transcript store: folds session events into the projection view
|
|
3
3
|
* and notifies subscribers. The renderer subscribes through
|
|
4
|
-
* `useSyncExternalStore`; the runner owns event feeding.
|
|
5
|
-
*
|
|
4
|
+
* `useSyncExternalStore`; the runner owns event feeding.
|
|
5
|
+
*
|
|
6
|
+
* Notification coalescing: the fold stays synchronous — `getView()` always
|
|
7
|
+
* returns the latest state the moment `apply` returns — but listener
|
|
8
|
+
* notification is scheduled on a microtask and deduplicated, so N events
|
|
9
|
+
* delivered inside one synchronous drain (the zai/GLM adapter drains its
|
|
10
|
+
* token buffer in sub-millisecond bursts) produce ONE React re-render.
|
|
11
|
+
* Synchronous per-event notification instead cascades one
|
|
12
|
+
* `useSyncExternalStore` force-update per token inside a single flush; the
|
|
13
|
+
* reconciler counts those as nested passive updates and floods React's
|
|
14
|
+
* "Maximum update depth exceeded" warning past 50 events, besides rendering
|
|
15
|
+
* the whole live tree once per token. A microtask keeps latency within the
|
|
16
|
+
* same macrotask, before Ink's throttled paint.
|
|
6
17
|
*
|
|
7
18
|
* @module @deepseek-ai/dsh-tui/store
|
|
8
19
|
*/
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live subagent activity feed: a bounded, display-only projection of CHILD
|
|
3
|
+
* session events. The transcript store folds only the root session (the
|
|
4
|
+
* durable truth this TUI renders); subagent conversations are their own
|
|
5
|
+
* sessions, and before this module their events were dropped entirely —
|
|
6
|
+
* a running subagent was invisible until its parent tool call settled.
|
|
7
|
+
*
|
|
8
|
+
* This is NOT a second transcript: each child folds to ONE row (label,
|
|
9
|
+
* running state, bounded last-activity text), capped at
|
|
10
|
+
* {@link MAX_SUBAGENT_ROWS}. Rows are advisory display state, rebuilt from
|
|
11
|
+
* live events; nothing here persists or replays. Notification is coalesced
|
|
12
|
+
* to one microtask per delivery burst, mirroring the transcript store's
|
|
13
|
+
* contract (per-token synchronous notify once cascaded past React's nested
|
|
14
|
+
* update limit on the GLM thinking path).
|
|
15
|
+
*
|
|
16
|
+
* @module @deepseek-ai/dsh-code/subagents
|
|
17
|
+
*/
|
|
18
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
19
|
+
/** Hard row cap: a fan-out larger than this stays summarized by the head. */
|
|
20
|
+
export declare const MAX_SUBAGENT_ROWS = 8;
|
|
21
|
+
/** One live subagent row in the feed. */
|
|
22
|
+
export interface SubagentRow {
|
|
23
|
+
/** Child session id. */
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/** Display label (session title when observed, else a short id form). */
|
|
26
|
+
readonly label: string;
|
|
27
|
+
/** Coarse lifecycle state folded from the child's events. */
|
|
28
|
+
readonly state: 'running' | 'idle' | 'done';
|
|
29
|
+
/** Bounded last-activity text for the status line. */
|
|
30
|
+
readonly activity: string;
|
|
31
|
+
/** Last fold time (ms, event clock) — newest-first ordering key. */
|
|
32
|
+
readonly updatedAt: number;
|
|
33
|
+
}
|
|
34
|
+
/** The read-only snapshot surface the renderer subscribes to. */
|
|
35
|
+
export interface SubagentFeedView {
|
|
36
|
+
/** Subscribe to feed changes; returns the unsubscribe function. */
|
|
37
|
+
subscribe(listener: () => void): () => void;
|
|
38
|
+
/** Read the current rows (identity-stable between changes). */
|
|
39
|
+
getSnapshot(): readonly SubagentRow[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fold one child-session event into its feed row (pure).
|
|
43
|
+
* Unknown event kinds leave the row untouched.
|
|
44
|
+
* @param previous - the row's current state, when any.
|
|
45
|
+
* @param sessionId - the child session id.
|
|
46
|
+
* @param event - the child session event.
|
|
47
|
+
* @returns the next row state.
|
|
48
|
+
*/
|
|
49
|
+
export declare function foldSubagentRow(previous: SubagentRow | undefined, sessionId: string, event: SessionEvent): SubagentRow;
|
|
50
|
+
/**
|
|
51
|
+
* Create one subagent feed. `apply` folds a child event (the caller gates
|
|
52
|
+
* which sessions are children); `reset` clears on a session switch. Row
|
|
53
|
+
* order is first-seen; the snapshot array is frozen and only replaced when
|
|
54
|
+
* a row actually changed.
|
|
55
|
+
* @returns the mutable feed handle plus its `SubagentFeedView`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createSubagentFeed(): SubagentFeedView & {
|
|
58
|
+
apply(sessionId: string, event: SessionEvent): void;
|
|
59
|
+
reset(): void;
|
|
60
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Installed dsh-code version exposed by the terminal header. */
|
|
2
|
+
/** Read one package manifest version without making terminal startup depend on it. */
|
|
3
|
+
export declare function readPackageVersion(manifest?: import("url").URL): string;
|
|
4
|
+
/** Version of the installed dsh-code package. */
|
|
5
|
+
export declare const DSH_CODE_VERSION: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code",
|
|
3
3
|
"description": "Claude-Code-style interactive TUI bundle for DeepSeek Harness (dsh): DeepSeek-blue whale banner, live session transcript, and a blended status line",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"deepseek": "./bin/deepseek.mjs",
|