dsh-code 1.0.7 → 1.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.
Files changed (89) hide show
  1. package/README.en.md +70 -24
  2. package/README.md +71 -25
  3. package/bin/deepseek.mjs +202 -39
  4. package/cordis.patch.yml +13 -4
  5. package/lib/index.mjs +5002 -1061
  6. package/lib/session-query.mjs +3 -2
  7. package/lib/startup.mjs +4 -4
  8. package/lib/{theme-DCT8Y2xf.mjs → theme-B3orFUYz.mjs} +665 -20
  9. package/lib/types/app.d.ts +120 -63
  10. package/lib/types/attachments.d.ts +16 -7
  11. package/lib/types/authorization-panel.d.ts +3 -3
  12. package/lib/types/git-workflow.d.ts +91 -2
  13. package/lib/types/history.d.ts +10 -0
  14. package/lib/types/i18n.d.ts +39 -0
  15. package/lib/types/index.d.ts +74 -2
  16. package/lib/types/input-split.d.ts +1 -1
  17. package/lib/types/kernel-panels.d.ts +89 -32
  18. package/lib/types/language-panel.d.ts +12 -0
  19. package/lib/types/locales/en.d.ts +498 -0
  20. package/lib/types/locales/zh.d.ts +9 -0
  21. package/lib/types/mentions.d.ts +7 -3
  22. package/lib/types/models.d.ts +14 -0
  23. package/lib/types/panel-accent.d.ts +28 -0
  24. package/lib/types/rainbow.d.ts +69 -0
  25. package/lib/types/render/animations.d.ts +42 -0
  26. package/lib/types/render/inspector.d.ts +26 -0
  27. package/lib/types/render/lines.d.ts +21 -1
  28. package/lib/types/render/markdown.d.ts +1 -1
  29. package/lib/types/render/projection.d.ts +95 -4
  30. package/lib/types/render/status.d.ts +12 -9
  31. package/lib/types/render/text.d.ts +6 -0
  32. package/lib/types/render/usage.d.ts +113 -0
  33. package/lib/types/session-directory.d.ts +42 -1
  34. package/lib/types/session-switch.d.ts +8 -0
  35. package/lib/types/startup.d.ts +1 -1
  36. package/lib/types/terminal-title.d.ts +8 -0
  37. package/lib/types/theme-panel.d.ts +2 -2
  38. package/lib/types/theme.d.ts +271 -52
  39. package/lib/types/update-panel.d.ts +27 -6
  40. package/lib/types/update.d.ts +10 -1
  41. package/lib/types/version.d.ts +6 -3
  42. package/package.json +26 -7
  43. package/src/app.ts +1426 -627
  44. package/src/approval.ts +166 -166
  45. package/src/attachments.ts +65 -19
  46. package/src/authorization-panel.ts +24 -18
  47. package/src/editor-keys.ts +371 -371
  48. package/src/fork.ts +11 -7
  49. package/src/git-workflow.ts +229 -3
  50. package/src/history.ts +14 -0
  51. package/src/i18n.ts +68 -0
  52. package/src/index.ts +503 -128
  53. package/src/input-split.ts +27 -7
  54. package/src/kernel-panels.ts +528 -113
  55. package/src/keyboard.ts +5 -4
  56. package/src/language-panel.ts +53 -0
  57. package/src/locales/en.ts +538 -0
  58. package/src/locales/zh.ts +537 -0
  59. package/src/mentions.ts +8 -4
  60. package/src/models.ts +264 -212
  61. package/src/panel-accent.ts +41 -0
  62. package/src/presets.ts +1 -1
  63. package/src/provider-settings.ts +1 -1
  64. package/src/rainbow.ts +218 -0
  65. package/src/render/animations.ts +104 -6
  66. package/src/render/editor.ts +20 -20
  67. package/src/render/export.ts +116 -95
  68. package/src/render/inspector.ts +42 -0
  69. package/src/render/lines.ts +628 -415
  70. package/src/render/markdown.ts +15 -3
  71. package/src/render/projection.ts +429 -19
  72. package/src/render/status.ts +119 -62
  73. package/src/render/text.ts +15 -0
  74. package/src/render/tool-preview.ts +77 -77
  75. package/src/render/usage.ts +430 -0
  76. package/src/render/width.ts +2 -2
  77. package/src/session-directory.ts +90 -9
  78. package/src/session-query.ts +8 -4
  79. package/src/session-switch.ts +14 -0
  80. package/src/startup.ts +3 -3
  81. package/src/store.ts +19 -1
  82. package/src/subagents.ts +229 -229
  83. package/src/terminal-title.ts +22 -5
  84. package/src/theme-panel.ts +17 -21
  85. package/src/theme.ts +281 -33
  86. package/src/update-panel.ts +148 -31
  87. package/src/update.ts +19 -3
  88. package/src/version.ts +63 -20
  89. package/src/whale-glyph.ts +23 -23
@@ -18,6 +18,7 @@ import type { CommandDescriptor } from '@deepseek-ai/dsh-commands';
18
18
  import type { ContentBlock, FileBlock, ImageBlock } from '@deepseek-ai/dsh-llm';
19
19
  import type { AuthorizationInteraction, AuthorizationStatus } from '@deepseek-ai/dsh-authorization';
20
20
  import { type ThemeName } from './theme.ts';
21
+ import { type LanguageName } from './i18n.ts';
21
22
  import type { LauncherUpdateStatus } from './update.ts';
22
23
  import type { TranscriptStore } from './store.ts';
23
24
  import { type TranscriptEntry } from './render/projection.ts';
@@ -29,16 +30,26 @@ import type { QuestionStore } from './questions.ts';
29
30
  import type { SkillsView, SkillRow } from './skills.ts';
30
31
  import { type MentionCandidate } from './mentions.ts';
31
32
  import type { SubagentFeedView } from './subagents.ts';
32
- import { type JobRow } from './kernel-panels.ts';
33
+ import type { UsageView } from './render/usage.ts';
34
+ import { type JobRow, type SearchRow } from './kernel-panels.ts';
33
35
  import type { PresetRow } from './presets.ts';
34
36
  import type { PermissionRow } from './permissions.ts';
35
37
  import type { PluginRow } from './plugin-inventory.ts';
36
38
  import type { SessionDirectoryOptions, SessionRow } from './session-directory.ts';
37
- import type { GitDiffView } from './git-workflow.ts';
39
+ import { type GitDiffView, type ReviewBranch, type ReviewCommit, type ReviewSelection } from './git-workflow.ts';
38
40
  import { type ProviderAuthorizationDirectory, type ProviderAuthorizationRow } from './authorization.ts';
39
41
  import { type FilePathInspection, type ImagePathInspection } from './attachments.ts';
40
42
  /** Visual priority for one bounded local notice. */
41
43
  export type NoticeTone = 'info' | 'warning' | 'error';
44
+ /** One mutation the terminal may request for a pending next-turn inbox item. */
45
+ export type QueueMutation = {
46
+ readonly kind: 'remove';
47
+ } | {
48
+ readonly kind: 'edit';
49
+ readonly text: string;
50
+ } | {
51
+ readonly kind: 'steer';
52
+ };
42
53
  /** Props the runner hands the app; callbacks stay owned by the runner. */
43
54
  export interface AppProps {
44
55
  /** Event-fed transcript store for the live session. */
@@ -77,130 +88,170 @@ export interface AppProps {
77
88
  * an attachment prepare resolves after the app remounted onto another
78
89
  * session, and the runner drops the stale delivery then.
79
90
  */
80
- dispatch(text: string, attachments?: readonly ContentBlock[], origin?: string): void;
81
- /** Submit steering, with the same stale-delivery guard as {@link dispatch}. */
82
- steer(text: string, attachments?: readonly ContentBlock[], origin?: string): void;
91
+ dispatch: (text: string, attachments?: readonly ContentBlock[], origin?: string) => void;
92
+ /**
93
+ * Submit one line as steering: it joins the turn already running at its next
94
+ * step boundary instead of waiting for the next turn. Same stale-delivery
95
+ * guard as {@link dispatch}.
96
+ */
97
+ steer: (text: string, attachments?: readonly ContentBlock[], origin?: string) => void;
83
98
  /**
84
99
  * The FULL current session identity ('' while the first session is pending)
85
100
  * — the stale-delivery origin above. Distinct from the short display id.
86
101
  */
87
102
  sessionKey: string;
88
103
  /** Interrupt the running turn (Esc); true when a turn was cancelled. */
89
- interrupt(): boolean;
104
+ interrupt: () => boolean;
90
105
  /** Quit: unmount, flush, and request process exit. */
91
- quit(): void;
106
+ quit: () => void;
92
107
  /** Load the selectable model directory (called when /model opens). */
93
- loadModels(): Promise<ModelDirectory>;
108
+ loadModels: () => Promise<ModelDirectory>;
94
109
  /** Load @mention candidates for the typed query (files + sessions). */
95
- loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
110
+ loadMentions: (query: string, signal?: AbortSignal) => Promise<readonly MentionCandidate[]>;
96
111
  /** Validate draft image paths without committing attachment objects. */
97
- inspectImages(paths: readonly string[]): Promise<readonly ImagePathInspection[]>;
112
+ inspectImages: (paths: readonly string[]) => Promise<readonly ImagePathInspection[]>;
98
113
  /** Validate, normalize and persist images immediately before submission. */
99
- prepareImages(paths: readonly string[], signal?: AbortSignal): Promise<readonly ImageBlock[]>;
114
+ prepareImages: (paths: readonly string[], signal?: AbortSignal) => Promise<readonly ImageBlock[]>;
100
115
  /** Validate draft non-image file paths without committing attachment objects. */
101
- inspectFiles(paths: readonly string[]): Promise<readonly FilePathInspection[]>;
116
+ inspectFiles: (paths: readonly string[]) => Promise<readonly FilePathInspection[]>;
102
117
  /** Persist non-image files immediately before submission as durable file blocks. */
103
- prepareFiles(paths: readonly string[], signal?: AbortSignal): Promise<readonly FileBlock[]>;
118
+ prepareFiles: (paths: readonly string[], signal?: AbortSignal) => Promise<readonly FileBlock[]>;
104
119
  /** Apply one /model selection (with an advertised reasoning effort, when picked); returns the display label. */
105
- selectModel(row: ModelRow, effortId?: string): string;
120
+ selectModel: (row: ModelRow, effortId?: string) => string;
106
121
  /** The /subagent override label, '' when delegated agents follow the current model. */
107
122
  subagentModel: string;
108
123
  /** Apply one /subagent model pick; returns the override label. */
109
- setSubagentModel(row: ModelRow, effortId?: string): string;
124
+ setSubagentModel: (row: ModelRow, effortId?: string) => string;
110
125
  /** Drop the /subagent override (delegated agents follow the current model). */
111
- clearSubagentModel(): void;
126
+ clearSubagentModel: () => void;
112
127
  /** Delete one session subtree; resolves with the outcome line. */
113
- deleteSession(id: string): Promise<string>;
128
+ deleteSession: (id: string) => Promise<string>;
114
129
  /** Load provider/settings/credential facts for the optional /model provider stage. */
115
- loadModelProviders?(): Promise<ProviderSettingsDirectory>;
130
+ loadModelProviders?: () => Promise<ProviderSettingsDirectory>;
116
131
  /** Subscribe to Harness credential/settings/adapter invalidations while /model is open. */
117
- subscribeModelProviders?(listener: () => void): () => void;
132
+ subscribeModelProviders?: (listener: () => void) => () => void;
118
133
  /** Store or rotate one provider credential through the Harness credential service. */
119
- saveModelProviderCredential?(target: ProviderTargetView, key: string): Promise<void>;
134
+ saveModelProviderCredential?: (target: ProviderTargetView, key: string) => Promise<void>;
120
135
  /** Remove one writable provider credential without removing its settings profile. */
121
- unsetModelProviderCredential?(target: ProviderTargetView): Promise<void>;
136
+ unsetModelProviderCredential?: (target: ProviderTargetView) => Promise<void>;
122
137
  /** Remove one user-owned provider profile and its page-managed credential. */
123
- removeModelProvider?(target: ProviderTargetView): Promise<void>;
138
+ removeModelProvider?: (target: ProviderTargetView) => Promise<void>;
124
139
  /** Save endpoint and explicit model capacities through the provider profile. */
125
- saveModelProviderConfiguration?(target: ProviderTargetView, configuration: ProviderConfiguration): Promise<void>;
140
+ saveModelProviderConfiguration?: (target: ProviderTargetView, configuration: ProviderConfiguration) => Promise<void>;
126
141
  /**
127
142
  * Interrogate the provider's real endpoint (typed key wins over the stored
128
143
  * credential) for the models it actually serves — the discovery stage of
129
144
  * the provider setup page.
130
145
  */
131
- discoverModelProvider?(target: ProviderTargetView, request: {
146
+ discoverModelProvider?: (target: ProviderTargetView, request: {
132
147
  readonly apiKey?: string;
133
148
  readonly baseURL?: string;
134
- }, signal?: AbortSignal): Promise<readonly DiscoveredModelView[]>;
149
+ }, signal?: AbortSignal) => Promise<readonly DiscoveredModelView[]>;
135
150
  /** Provider authorization flows and value-free stored-record facts. */
136
- loadProviderAuthorizations?(): Promise<ProviderAuthorizationDirectory>;
137
- subscribeProviderAuthorizations?(listener: () => void): () => void;
138
- beginProviderAuthorization?(row: ProviderAuthorizationRow, method: string, interaction: AuthorizationInteraction, signal: AbortSignal): Promise<AuthorizationStatus>;
139
- cancelProviderAuthorization?(row: ProviderAuthorizationRow): void;
140
- logoutProviderAuthorization?(row: ProviderAuthorizationRow): Promise<void>;
141
- openAuthorizationUrl?(url: string): boolean;
142
- copyTextValue?(text: string): Promise<void>;
151
+ loadProviderAuthorizations?: () => Promise<ProviderAuthorizationDirectory>;
152
+ subscribeProviderAuthorizations?: (listener: () => void) => () => void;
153
+ beginProviderAuthorization?: (row: ProviderAuthorizationRow, method: string, interaction: AuthorizationInteraction, signal: AbortSignal) => Promise<AuthorizationStatus>;
154
+ cancelProviderAuthorization?: (row: ProviderAuthorizationRow) => void;
155
+ logoutProviderAuthorization?: (row: ProviderAuthorizationRow) => Promise<void>;
156
+ openAuthorizationUrl?: (url: string) => boolean;
157
+ copyTextValue?: (text: string) => Promise<void>;
143
158
  /** Cycle to the next mode station (Shift+Tab): a permission preset or a plan switch; returns the notice label. */
144
- cycleMode(): string;
159
+ cycleMode: () => string;
145
160
  /** Pre-session plan choice: shows the plan badge before the first session exists. */
146
161
  pendingPlan?: boolean;
147
162
  /** Select or inspect a permission preset without requiring a pre-existing session. */
148
- setPermission(id: string): string;
163
+ setPermission: (id: string) => string;
149
164
  /** Export the transcript to a markdown file (/export [path]); reports via notices. */
150
- exportTranscript(argument: string): Promise<void>;
165
+ exportTranscript: (argument: string) => Promise<void>;
151
166
  /** Rename the session (/title <text>); returns the outcome line for the notice. */
152
- renameTitle(argument: string): string;
167
+ renameTitle: (argument: string) => string;
153
168
  /** Copy the latest complete assistant response; resolves to notice text. */
154
- copyLastResponse(): Promise<string>;
169
+ copyLastResponse: () => Promise<string>;
155
170
  /** Load a complete read-only Git diff for the file-oriented viewport. */
156
- loadGitDiff(argument: string): Promise<GitDiffView>;
171
+ loadGitDiff: (argument: string) => Promise<GitDiffView>;
172
+ /** Local branches for the /review picker (absent: the picker hides the branch phase's list). */
173
+ listReviewBranches?: (signal?: AbortSignal) => Promise<readonly ReviewBranch[]>;
174
+ /** Recent commits on the current branch for the /review picker. */
175
+ listReviewCommits?: (signal?: AbortSignal) => Promise<readonly ReviewCommit[]>;
157
176
  /** Start a model review after applying the read-only permission preset. */
158
- reviewChanges(argument: string): void;
177
+ reviewChanges: (selection: ReviewSelection) => void;
159
178
  /** Preset/session/plugin kernel operations. */
160
- loadPresets(): Promise<readonly PresetRow[]>;
161
- switchMode(id: string): Promise<string>;
179
+ loadPresets: () => Promise<readonly PresetRow[]>;
180
+ switchMode: (id: string) => Promise<string>;
162
181
  /** Load the switchable permission presets for the /permission panel. */
163
- loadPermissions(): Promise<readonly PermissionRow[]>;
164
- createSession(mode?: string): void;
182
+ loadPermissions: () => Promise<readonly PermissionRow[]>;
183
+ createSession: (mode?: string) => void;
165
184
  /** Fork the active session at a completed-turn boundary. */
166
- forkSession(argument: string): void;
167
- loadSessions(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
168
- loadSessionTranscript(id: string, signal?: AbortSignal): Promise<string>;
185
+ forkSession: (argument: string) => void;
186
+ loadSessions: (options: SessionDirectoryOptions, signal?: AbortSignal) => Promise<readonly SessionRow[]>;
187
+ loadSessionTranscript: (id: string, signal?: AbortSignal) => Promise<string>;
188
+ /** Read the current session's usage blocks (projections plus per-turn fold). */
189
+ loadUsage: () => Promise<UsageView>;
190
+ /**
191
+ * Full-text search over every persisted session (the in-process
192
+ * session-query engine). Absent when the deployment disabled the row;
193
+ * /search degrades to a notice instead of opening the panel.
194
+ */
195
+ searchSessions?: (query: string, signal?: AbortSignal) => Promise<readonly SearchRow[]>;
169
196
  /** Load this session's subagent conversations (children by lineage). */
170
- loadSubagents(): Promise<readonly SessionRow[]>;
171
- switchSession(row: SessionRow): void;
172
- cancelSessionSwitch(): boolean;
173
- loadPlugins(): readonly PluginRow[];
197
+ loadSubagents: () => Promise<readonly SessionRow[]>;
198
+ switchSession: (row: SessionRow) => void;
199
+ cancelSessionSwitch: () => boolean;
200
+ loadPlugins: () => readonly PluginRow[];
174
201
  /** Caller-visible background jobs (the host jobs registry, read-only). */
175
- loadJobs(): readonly JobRow[];
202
+ loadJobs: () => readonly JobRow[];
176
203
  /** Probe the launcher's aligned update plan (read-only; never installs). */
177
- probeUpdate(): Promise<LauncherUpdateStatus>;
204
+ probeUpdate: () => Promise<LauncherUpdateStatus>;
178
205
  /** Run the launcher's aligned update; streams sanitized lines; resolves with the exit code. */
179
- applyUpdate(onLine: (line: string) => void): Promise<number>;
206
+ applyUpdate: (onLine: (line: string) => void, plan?: {
207
+ readonly dshSpec: string;
208
+ readonly codeSpec: string;
209
+ readonly pluginSpecs: readonly string[];
210
+ }) => Promise<number>;
180
211
  /** Registers the app's notice channel with the runner (called once on mount). */
181
- onBridgeReady(bridge: {
182
- notify(text: string, tone?: NoticeTone): void;
183
- }): void;
212
+ onBridgeReady: (bridge: {
213
+ notify: (text: string, tone?: NoticeTone) => void;
214
+ }) => void;
184
215
  /** Ordered enabled status items (/statusline config); the runner owns persistence. */
185
216
  statusline: readonly string[];
186
217
  /** Persist a new statusline item set; the runner surfaces IO failures as notices. */
187
- saveStatusline(items: readonly string[]): void;
218
+ saveStatusline: (items: readonly string[]) => void;
219
+ /** Apply and persist one /language selection; the runner owns the language.json file. */
220
+ saveLanguage: (name: LanguageName) => void;
188
221
  /** Apply and persist one /theme selection; the runner owns the theme.json file. */
189
- saveTheme?(name: ThemeName): void;
222
+ saveTheme?: (name: ThemeName) => void;
190
223
  /** Whether timed animations run at startup (animations.json; on by default
191
224
  * — like parseAnimationsPref, only an explicit false disables them). */
192
225
  animations?: boolean;
193
226
  /** Apply and persist one /animation toggle; the runner owns the file. */
194
- saveAnimations?(enabled: boolean): void;
227
+ saveAnimations?: (enabled: boolean) => void;
195
228
  /** Persistent cross-session input history (oldest first); the runner owns the file. */
196
229
  history: readonly string[];
197
230
  /** Persist one submitted prompt to the global history file. */
198
- recordHistory(text: string): void;
199
- /** Cancel one queued inbox message by identity (Delete on the empty composer). */
200
- cancelQueued(messageId: string): void;
231
+ recordHistory: (text: string) => void;
232
+ /** Mutate one next-turn inbox message; durable inbox splices reconcile the result. */
233
+ updateQueued?: (messageId: string, action: QueueMutation) => void;
201
234
  /** Apply the Ctrl+R terminal passthrough to the detected editor (/vscode-keys); resolves to a one-line summary. */
202
- applyEditorKeys(): Promise<string>;
235
+ applyEditorKeys: () => Promise<string>;
203
236
  }
237
+ /**
238
+ * The streaming buffer rendered with a hard size cap: the live region must
239
+ * ALWAYS fit the terminal, or Ink's erase/rewrite of a dynamic tree taller
240
+ * than the screen freezes (cursor-up past the top, garbage, no scroll). The
241
+ * cap counts explicit newlines and terminal wrapping, slicing from the END so
242
+ * the freshest tokens stay visible while a long reply streams; the complete
243
+ * text lands in the flushed scrollback once the turn assembles it.
244
+ *
245
+ * Body wrap width for a streaming tail. `rowColumns` is the same width
246
+ * passed to `transcriptEntryLines` (terminal minus the last-column safety);
247
+ * the hanging prefix then shrinks the body so streamed text and settled
248
+ * markdown wrap on the same column.
249
+ */
250
+ export declare function streamTailBodyColumns(rowColumns: number, prefix: string, continuationPrefix?: string): number;
251
+ /** Rows in the exact next-turn inbox order, never transcript append order. */
252
+ export declare function queuedInboxRows(entries: readonly TranscriptEntry[], ids: readonly string[]): readonly Extract<TranscriptEntry, {
253
+ kind: 'pending';
254
+ }>[];
204
255
  /** One completion candidate row. */
205
256
  interface CompletionCandidate {
206
257
  /** Insertion text for the command name (with leading slash). */
@@ -210,6 +261,12 @@ interface CompletionCandidate {
210
261
  /** Candidate origin; skills land the same literal text but route through the prompt. */
211
262
  origin: 'command' | 'skill' | 'mention';
212
263
  }
264
+ /**
265
+ * Wrap one completion-menu cursor step. An empty menu keeps the index at 0:
266
+ * `% 0` is NaN, and a NaN index silently disables the highlight, the accept
267
+ * key, and any later Enter that runs through the menu.
268
+ */
269
+ export declare function stepCompletionIndex(index: number, delta: number, count: number): number;
213
270
  /**
214
271
  * Resolve completion candidates for the current input: TUI-local commands,
215
272
  * the live registry descriptors, and user-invocable skills, filtered by the
@@ -26,17 +26,26 @@ export declare const MAX_FILES_PER_MESSAGE = 8;
26
26
  export declare function detectImageMediaType(data: Uint8Array): ImageMediaType | undefined;
27
27
  /** Whether a path-like token is worth probing as an image attachment. */
28
28
  export declare function looksLikeImagePath(path: string): boolean;
29
+ /** Strip one layer of ASCII or Unicode quotes and shell-escaped spaces. */
30
+ export declare function unwrapDroppedPath(token: string): string;
31
+ /** Absolute/relative drop with a dotted leaf — including unquoted spaces. */
32
+ export declare function looksLikeFilesystemDrop(path: string): boolean;
29
33
  /**
30
- * Parse a paste/drop into its image and file paths: image-suffixed tokens
31
- * stay images, other path-shaped tokens ride as file attachments (0.1.5
32
- * file blocks), and anything that is neither leaves both empty — the caller
33
- * then treats the paste as plain text.
34
+ * A composer draft that is a filesystem path, not a slash command.
35
+ * `/usage` stays a command; `/Users/foo.png` and `C:\temp\a.png` are drops.
36
+ */
37
+ export declare function looksLikePathDraft(value: string): boolean;
38
+ /**
39
+ * Parse a paste or file-drop into image and file paths: image-suffixed
40
+ * tokens stay images, other path-shaped tokens ride as file attachments
41
+ * (0.1.5 file blocks), and anything that is neither leaves both empty —
42
+ * the caller then treats the paste as plain text.
34
43
  *
35
44
  * File tokens are held to an absolute-path-with-shape bar (drive/backslash
36
- * or a dot-suffixed leaf after a separator): a dropped terminal path always
45
+ * or a dotted leaf after a separator): a dropped terminal path always
37
46
  * carries one of those, while prose, slash commands, and option flags never
38
- * do. A POSIX absolute path without any dot-suffixed leaf falls through as
39
- * text — the @ mention route still attaches such files deliberately.
47
+ * do. A POSIX absolute path without a dotted leaf falls through as text —
48
+ * the @ mention route still attaches such files deliberately.
40
49
  */
41
50
  export declare function parsePastedAttachmentPaths(input: string): {
42
51
  readonly images: readonly string[];
@@ -16,7 +16,7 @@ export interface ProviderAuthorizationPanelProps {
16
16
  export declare function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProps): ReactElement;
17
17
  export declare function ProviderAuthorizationLogoutPanel({ row, confirm, done, back }: {
18
18
  row: ProviderAuthorizationRow;
19
- confirm(row: ProviderAuthorizationRow): Promise<void>;
20
- done(): void;
21
- back(): void;
19
+ confirm: (row: ProviderAuthorizationRow) => Promise<void>;
20
+ done: () => void;
21
+ back: () => void;
22
22
  }): ReactElement;
@@ -28,5 +28,94 @@ export declare function parseGitDiffSpec(argument: string): GitDiffSpec;
28
28
  * git subprocess instead of letting a stale repository's diff land later.
29
29
  */
30
30
  export declare function loadGitDiff(cwd: string, argument: string, signal?: AbortSignal): Promise<GitDiffView>;
31
- /** Review prompt capped before it reaches a provider context window. */
32
- export declare function buildReviewPrompt(diff: string, label: string, maxChars?: number): string;
31
+ /** One /review run's selection: what to review plus an optional user note. */
32
+ export type ReviewSelection = {
33
+ readonly kind: 'uncommitted';
34
+ } | {
35
+ readonly kind: 'base-branch';
36
+ readonly branch: string;
37
+ readonly mergeBase?: string;
38
+ } | {
39
+ readonly kind: 'commit';
40
+ readonly sha: string;
41
+ } | {
42
+ readonly kind: 'custom';
43
+ readonly instructions: string;
44
+ };
45
+ /**
46
+ * The /review argument is always a free-form note applied to the uncommitted
47
+ * working tree (`/review 使用中文` reviews the uncommitted diff in Chinese);
48
+ * branch and commit targets come from the candidate picker, never from
49
+ * argument guessing.
50
+ */
51
+ export declare function parseReviewArgument(argument: string): ReviewSelection;
52
+ /** The merge base of HEAD and one branch, or undefined when git cannot compute one. */
53
+ export declare function mergeBaseWith(cwd: string, branch: string, signal?: AbortSignal): Promise<string | undefined>;
54
+ /** One commit's own patch (parent..commit), falling back to `git show` for root commits. */
55
+ export declare function loadCommitDiff(cwd: string, sha: string, signal?: AbortSignal): Promise<GitDiffView>;
56
+ /**
57
+ * Build the in-session review prompt: the diff is pasted whole (truncated at
58
+ * the character cap with an explicit marker the model can see), the rubric's
59
+ * essentials ride along — P0-P3 priorities, file/line anchors, only defects
60
+ * this change introduced — and an optional user note (language, focus)
61
+ * prefixes everything as the user's explicit instruction.
62
+ */
63
+ export declare function buildReviewPrompt(diff: string, label: string, note?: string, maxChars?: number): string;
64
+ /** One branch candidate for the review picker. */
65
+ export interface ReviewBranch {
66
+ readonly name: string;
67
+ }
68
+ /** One commit candidate for the review picker. */
69
+ export interface ReviewCommit {
70
+ readonly sha: string;
71
+ readonly title: string;
72
+ /** Committer timestamp in Unix epoch milliseconds. */
73
+ readonly at: number;
74
+ }
75
+ /**
76
+ * Local branch names for the review picker, newest activity first and with
77
+ * the current branch excluded (reviewing against it is always empty).
78
+ */
79
+ export declare function listReviewBranches(cwd: string, signal?: AbortSignal): Promise<readonly ReviewBranch[]>;
80
+ /** Recent commits on the current branch for the review picker. */
81
+ export declare function listReviewCommits(cwd: string, signal?: AbortSignal, limit?: number): Promise<readonly ReviewCommit[]>;
82
+ /** One finished review, ready for the terminal's result panel. */
83
+ export interface ReviewResultView {
84
+ /** Panel header naming what was reviewed. */
85
+ readonly title: string;
86
+ /** The reviewer's reply markdown (the findings list). */
87
+ readonly body: string;
88
+ /** Compact summary: finding counts per priority plus the verdict. */
89
+ readonly summary: string;
90
+ /** The review session id, so follow-ups can @-reference its context. */
91
+ readonly sessionId: string;
92
+ }
93
+ /** One parsed review finding from the reviewer's closing JSON block. */
94
+ export interface ReviewFinding {
95
+ /** Priority 0-3 for P0-P3; undefined when the reviewer omitted it. */
96
+ readonly priority?: number;
97
+ /** Finding title, already free of the [P#] prefix when present. */
98
+ readonly title: string;
99
+ /** File path the finding anchors to, when stated. */
100
+ readonly path?: string;
101
+ /** Line range as written, when stated. */
102
+ readonly range?: string;
103
+ }
104
+ /** The parsed conclusion of one review reply. */
105
+ export interface ReviewConclusion {
106
+ readonly findings: readonly ReviewFinding[];
107
+ readonly overall?: 'correct' | 'incorrect';
108
+ readonly explanation?: string;
109
+ }
110
+ /**
111
+ * Parse the reviewer's reply into a conclusion: whole-text JSON first, then
112
+ * the first balanced JSON object anywhere in the text, then undefined (the
113
+ * reply renders as plain markdown with no summary line). Malformed fields
114
+ * are dropped, never thrown.
115
+ */
116
+ export declare function parseReviewConclusion(text: string): ReviewConclusion | undefined;
117
+ /**
118
+ * One compact summary line for a finished review: finding counts per
119
+ * priority plus the overall verdict, or a no-findings phrasing.
120
+ */
121
+ export declare function reviewSummaryLine(conclusion: ReviewConclusion): string;
@@ -78,6 +78,16 @@ export interface RecallState {
78
78
  }
79
79
  /** Fresh navigation state over one recall space. */
80
80
  export declare function beginRecall(entries: readonly string[], draft: string): RecallState;
81
+ /**
82
+ * Join a panel-recalled entry onto the draft already in the composer: the
83
+ * draft is extended, never replaced, so picking a history row cannot discard
84
+ * work in progress. An empty draft takes the entry as-is; otherwise the entry
85
+ * starts on its own line unless the draft already ends one.
86
+ * @param draft - the composer text before the recall.
87
+ * @param entry - the sanitized text of the accepted row.
88
+ * @returns the text to place in the composer.
89
+ */
90
+ export declare function appendRecall(draft: string, entry: string): string;
81
91
  /** The outcome of one recall step. */
82
92
  export interface RecallStep {
83
93
  state: RecallState;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Interface language for the TUI: a module-level current language with a
3
+ * message accessor, mirroring the theme module's setTheme/getPalette shape
4
+ * so a language switch re-renders every translated surface on the next
5
+ * render without touching call sites. English is the default; the persisted
6
+ * choice lives in language.json next to theme.json (see the runner's
7
+ * persistence block).
8
+ *
9
+ * @module @deepseek-ai/dsh-tui/i18n
10
+ */
11
+ import { type MessageKey } from './locales/en.ts';
12
+ export type { MessageKey } from './locales/en.ts';
13
+ /** Selectable interface languages: English (default) and Chinese. */
14
+ export type LanguageName = 'en' | 'zh';
15
+ /** Valid language names for argument parsing. */
16
+ export declare const LANGUAGE_NAMES: readonly LanguageName[];
17
+ /** One language's picker row. */
18
+ export interface LanguageDescriptor {
19
+ readonly id: LanguageName;
20
+ readonly label: string;
21
+ }
22
+ /** The /language picker rows in canonical order. */
23
+ export declare const LANGUAGES: readonly LanguageDescriptor[];
24
+ /**
25
+ * Parse a persisted or typed language name: only 'en' and 'zh' survive;
26
+ * anything else falls back to English.
27
+ */
28
+ export declare function parseLanguageName(value: unknown): LanguageName;
29
+ /** The language name in force. */
30
+ export declare function getLanguage(): LanguageName;
31
+ /** Switch the active language; the next render paints with the new catalog. */
32
+ export declare function setLanguage(name: LanguageName): void;
33
+ /**
34
+ * One message from the active catalog, with `{n}`-style placeholders filled
35
+ * from the params record. Unknown placeholders stay literal; a missing key
36
+ * falls back to the English entry so a catalog gap degrades visibly but
37
+ * never crashes.
38
+ */
39
+ export declare function t(key: MessageKey, params?: Readonly<Record<string, string | number>>): string;
@@ -10,10 +10,13 @@
10
10
  */
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
12
  import z from '@deepseek-ai/schemastery';
13
+ import type { Agent, AgentStatus, Inbox } from '@deepseek-ai/dsh-agent';
13
14
  import { type ContentBlock } from '@deepseek-ai/dsh-llm';
14
- import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session';
15
- import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
15
+ import { SessionId, type SessionEvent, type SessionHeader, type UserMessage } from '@deepseek-ai/dsh-session';
16
+ import { type SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
17
+ import { type QueueMutation } from './app.ts';
16
18
  import type { TuiStartup } from './startup.ts';
19
+ import type { SearchRow } from './kernel-panels.ts';
17
20
  /** Stable Cordis plugin name. */
18
21
  export declare const name = "tui-runner";
19
22
  /** Core services required before the interactive session can start. */
@@ -39,6 +42,8 @@ interface Target {
39
42
  cwd?: string;
40
43
  seed?: readonly SessionEvent[];
41
44
  parentSession?: SessionId;
45
+ /** Marks the session as a subagent conversation in the durable header. */
46
+ origin?: 'subagent';
42
47
  seedLength?: number;
43
48
  }
44
49
  /**
@@ -73,9 +78,48 @@ export declare function runQuitSequence(steps: readonly QuitCleanupStep[], exit:
73
78
  /** One composer submission waiting behind the startup delivery. */
74
79
  export interface QueuedSubmission {
75
80
  readonly text: string;
81
+ /** `steer` inserts into the running turn; `followup` waits for the next one. */
76
82
  readonly mode: 'followup' | 'steer';
77
83
  readonly images: readonly ContentBlock[];
78
84
  }
85
+ /** What one requested queue mutation did; the runner maps it to one notice. */
86
+ export type QueueMutationOutcome = 'removed' | 'edited' | 'steered' | 'unavailable' | 'empty' | 'steerUnavailable';
87
+ /**
88
+ * Replace one queued message's text while keeping its attachments. A queue
89
+ * edit rewrites what the user typed, not what they attached: image and file
90
+ * blocks ride through in delivery order (text first, then attachments, the
91
+ * shape {@link deliverLine} submits). Dropping them here would silently strip
92
+ * an attachment the user already confirmed, so this is the edit's single
93
+ * definition and the panel's read-only marker only mirrors it.
94
+ */
95
+ export declare function queueEditContent(content: readonly ContentBlock[], text: string): ContentBlock[];
96
+ /**
97
+ * Apply one terminal queue mutation to the live inbox. The decision and the
98
+ * inbox change are pure over the supplied handles so every branch is testable
99
+ * without an agent; steering itself is injected because it wakes the driver
100
+ * rather than mutating the inbox. The durable inbox splices remain the UI's
101
+ * single source of truth — this helper never reports a state the inbox did not
102
+ * actually reach.
103
+ * @param inbox - the live agent inbox (pending lists plus its mutators).
104
+ * @param status - the agent's lifecycle status; steering needs `running`.
105
+ * @param messageId - identity of the queued message to mutate.
106
+ * @param action - the requested mutation.
107
+ * @param steer - submits the removed message as next-step steering.
108
+ * @returns the outcome the caller reports.
109
+ */
110
+ export declare function applyQueueMutation(inbox: Pick<Inbox, 'nextTurn' | 'append' | 'remove' | 'replace'>, status: AgentStatus, messageId: string, action: QueueMutation, steer: (message: UserMessage) => void): QueueMutationOutcome;
111
+ /**
112
+ * Cancel the active turn while keeping the next-turn queue, then wake the
113
+ * driver again so the preserved messages actually run. `cancel` clears
114
+ * pending work by default and never wakes the driver on its own, so the queue
115
+ * is captured first and re-submitted afterwards: a waking submission latches
116
+ * the wake while the aborted activity converges to idle, which is what turns
117
+ * "preserved" into "sent next" instead of "parked forever". Next-step
118
+ * steering is deliberately dropped — it belonged to the cancelled turn.
119
+ * @param agent - the live agent handle.
120
+ * @returns how many queued messages were preserved across the abort.
121
+ */
122
+ export declare function cancelPreservingQueue(agent: Pick<Agent, 'inbox' | 'cancel' | 'followup'>): number;
79
123
  /**
80
124
  * Whether a tagged submission still belongs to the active session. Attachment
81
125
  * prepares resolve on the microtask timeline, while a queued session switch
@@ -85,6 +129,25 @@ export interface QueuedSubmission {
85
129
  * An untagged (synchronous) or pending-session ('') submission always passes.
86
130
  */
87
131
  export declare function submissionBelongsToSession(origin: string | undefined, activeSessionId: string | undefined): boolean;
132
+ /**
133
+ * Root-log catalog facts a resumed session must replay into the subagent
134
+ * feed: constructor seeds never fire on the live bus, so without this the
135
+ * children of a resumed session vanish behind a restart. The empty-child
136
+ * placeholder row (childId '') is a placeholder, not a child, and stays out.
137
+ */
138
+ export declare function subagentCatalogSeed(events: readonly SessionEvent[]): readonly SessionEvent<'subagent/catalog'>[];
139
+ /**
140
+ * Map one cross-session full-text hit onto the /search panel's row (pure).
141
+ * Labels fall back to the short id form — the engine's hit carries the
142
+ * strongest matching event, not the title observation.
143
+ */
144
+ export declare function searchHitToRow(hit: {
145
+ header: SessionHeader;
146
+ bestMatch: {
147
+ snippet: string;
148
+ time: number;
149
+ };
150
+ }): SearchRow;
88
151
  /** One Shift+Tab station decision for the mode cycle. */
89
152
  export type ModeCycleDecision = {
90
153
  readonly kind: 'permission';
@@ -105,12 +168,21 @@ export type ModeCycleDecision = {
105
168
  * parked on read-only), and leaving it lands on the next preset after the
106
169
  * most restrictive one. Without the /plan command the cycle is exactly the
107
170
  * preset table.
171
+ *
172
+ * `planIntent` covers the committed fold's commit lag: upstream queues a
173
+ * plan switch during an open turn (and the command pipeline is async even
174
+ * idle), so the durable plan/mode event lands AFTER the press that chose
175
+ * it. While an intent from an earlier press is in flight it — not the
176
+ * stale committed fold — decides the station, so repeated presses advance
177
+ * the cycle instead of re-issuing the same plan transition (the stuck
178
+ * plan-on/plan-off toggle). Undefined falls back to the committed fold.
108
179
  */
109
180
  export declare function planCycleDecision(input: {
110
181
  readonly names: readonly string[];
111
182
  readonly current: string;
112
183
  readonly inPlan: boolean;
113
184
  readonly planAvailable: boolean;
185
+ readonly planIntent?: boolean;
114
186
  }): ModeCycleDecision | undefined;
115
187
  /**
116
188
  * Order-preserving gate for composer input while the startup prompt/images
@@ -50,5 +50,5 @@ export interface TuiStdin extends PassThrough {
50
50
  */
51
51
  export declare function createSplitStdin(source: NodeJS.ReadStream): {
52
52
  stdin: TuiStdin;
53
- dispose(): void;
53
+ dispose: () => void;
54
54
  };