dsh-code 0.7.0 → 0.8.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.
Files changed (44) hide show
  1. package/README.en.md +20 -6
  2. package/README.md +20 -6
  3. package/lib/index.mjs +2685 -622
  4. package/lib/types/app.d.ts +77 -1
  5. package/lib/types/history.d.ts +15 -4
  6. package/lib/types/index.d.ts +48 -0
  7. package/lib/types/kernel-panels.d.ts +7 -0
  8. package/lib/types/permissions.d.ts +37 -0
  9. package/lib/types/presets.d.ts +2 -0
  10. package/lib/types/provider-settings.d.ts +144 -0
  11. package/lib/types/questions.d.ts +2 -0
  12. package/lib/types/render/animations.d.ts +8 -6
  13. package/lib/types/render/lines.d.ts +6 -0
  14. package/lib/types/render/markdown.d.ts +3 -3
  15. package/lib/types/render/projection.d.ts +95 -3
  16. package/lib/types/render/status.d.ts +26 -36
  17. package/lib/types/render/text.d.ts +14 -7
  18. package/lib/types/render/tool-detail.d.ts +3 -1
  19. package/lib/types/render/tool-preview.d.ts +4 -1
  20. package/lib/types/session-directory.d.ts +15 -0
  21. package/lib/types/store.d.ts +13 -2
  22. package/lib/types/version.d.ts +5 -0
  23. package/package.json +1 -1
  24. package/src/app.ts +847 -150
  25. package/src/approval.ts +11 -2
  26. package/src/history.ts +20 -5
  27. package/src/index.ts +402 -159
  28. package/src/kernel-panels.ts +45 -8
  29. package/src/permissions.ts +85 -0
  30. package/src/presets.ts +12 -0
  31. package/src/provider-settings.ts +520 -0
  32. package/src/questions.ts +15 -5
  33. package/src/render/animations.ts +32 -18
  34. package/src/render/lines.ts +21 -6
  35. package/src/render/markdown.ts +302 -4
  36. package/src/render/projection.ts +665 -10
  37. package/src/render/status.ts +68 -162
  38. package/src/render/text.ts +28 -9
  39. package/src/render/tool-detail.ts +81 -40
  40. package/src/render/tool-preview.ts +18 -2
  41. package/src/session-directory.ts +44 -5
  42. package/src/skills.ts +8 -4
  43. package/src/store.ts +26 -8
  44. package/src/version.ts +16 -0
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * @module @deepseek-ai/dsh-tui/render/status
11
11
  */
12
- import type { ContextSegments, TranscriptStats } from './projection.ts';
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' | 'ctxSystem' | 'ctxPrompt' | 'ctxAssistant' | 'ctxThinking' | 'ctxTools';
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 is the identity/state
68
- * row (busy dot, model, cwd, branch, plan, turns, tokens, title; goal,
69
- * sandbox, and permission badges). Row 2 is the run-meters row (mode, the
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
- /** One content-type segment of the context bar (pure data; colors live in app.ts). */
93
- export interface ContextSegmentSpec {
94
- key: keyof ContextSegments;
95
- /** Tone the footer maps to a DeepSeek blue shade. */
96
- tone: StatusTone;
97
- /** Labels longest → shortest; the first one fitting the segment width wins. */
98
- labels: readonly string[];
99
- }
100
- /** The five content types in conversation order, dark light blue. */
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(segments: ContextSegments, usedTokens: number, contextWindow: number, width: number): readonly StatusSpan[];
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 permission preset (folded from 'permission/preset'), empty when unknown. */
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 (identity
189
- * and state badges) degrades in a fixed order cycle hint, then title, token
190
- * figures, turn/step counts, goal, divergent sandbox, permission badge and
191
- * only then ellipsizes the identity cluster, so the row never wraps. Row 2
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
- * and tabs survive; everything else in C0/C1 plus DEL becomes a visible
7
- * `\xNN` escape.
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 drive the
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 control character (except `\n`, `\t`) rendered
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
  */
@@ -48,6 +48,21 @@ export interface SessionRow {
48
48
  readonly preset: string;
49
49
  readonly title?: string;
50
50
  }
51
+ /** True when the header describes a subagent conversation (durable lineage). */
52
+ export declare function isSubagentSession(header: SessionHeader): boolean;
53
+ /** Platform-consistent path equality for session cwd comparisons. */
54
+ export declare function samePath(left: string | undefined, right: string): boolean;
55
+ /**
56
+ * Unique header match by exact id or unique id prefix (root and subagent
57
+ * headers alike); the caller applies any lineage gate.
58
+ * @param headers - the persisted headers.
59
+ * @param wanted - the id or id prefix.
60
+ * @returns the uniquely matched header.
61
+ * @throws when nothing matches or the prefix is ambiguous.
62
+ */
63
+ export declare function matchSessionId(headers: readonly SessionHeader[], wanted: string): SessionHeader;
64
+ /** The newest persisted ROOT session pinned to this cwd, or undefined. */
65
+ export declare function newestRootForCwd(headers: readonly SessionHeader[], cwd: string): SessionHeader | undefined;
51
66
  /** Filter/sort header-only records. No session log is loaded here. */
52
67
  export declare function projectSessionRows(records: readonly SessionRecord[], options: SessionDirectoryOptions): SessionRow[];
53
68
  /** Merge page-local title observations without disturbing directory order. */
@@ -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. The store owns no
5
- * timing — listeners fire synchronously after each applied event.
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,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.7.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "deepseek": "./bin/deepseek.mjs",