dsh-code 0.9.1 → 1.0.1

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 (67) hide show
  1. package/README.en.md +278 -249
  2. package/README.md +131 -102
  3. package/bin/deepseek.mjs +100 -6
  4. package/cordis.patch.yml +36 -1
  5. package/lib/index.mjs +3055 -819
  6. package/lib/startup.mjs +21 -11
  7. package/lib/{theme-BEi4i_aN.mjs → theme-DCT8Y2xf.mjs} +13 -9
  8. package/lib/types/app.d.ts +84 -16
  9. package/lib/types/attachments.d.ts +20 -0
  10. package/lib/types/authorization-panel.d.ts +22 -0
  11. package/lib/types/authorization.d.ts +36 -0
  12. package/lib/types/editor.d.ts +6 -0
  13. package/lib/types/fork.d.ts +8 -0
  14. package/lib/types/git-workflow.d.ts +23 -0
  15. package/lib/types/index.d.ts +6 -0
  16. package/lib/types/kernel-panels.d.ts +39 -0
  17. package/lib/types/keyboard.d.ts +41 -0
  18. package/lib/types/mentions.d.ts +30 -38
  19. package/lib/types/models.d.ts +3 -1
  20. package/lib/types/permissions.d.ts +4 -14
  21. package/lib/types/presets.d.ts +5 -20
  22. package/lib/types/provider-settings.d.ts +16 -0
  23. package/lib/types/render/animations.d.ts +10 -39
  24. package/lib/types/render/editor.d.ts +137 -0
  25. package/lib/types/render/export.d.ts +1 -1
  26. package/lib/types/render/lines.d.ts +6 -2
  27. package/lib/types/render/markdown.d.ts +3 -1
  28. package/lib/types/render/projection.d.ts +29 -3
  29. package/lib/types/render/status.d.ts +6 -13
  30. package/lib/types/session-directory.d.ts +1 -3
  31. package/lib/types/startup.d.ts +14 -11
  32. package/lib/types/store.d.ts +11 -9
  33. package/lib/types/subagents.d.ts +3 -3
  34. package/lib/types/theme.d.ts +14 -1
  35. package/lib/types/version.d.ts +15 -2
  36. package/package.json +159 -141
  37. package/src/app.ts +1490 -663
  38. package/src/attachments.ts +128 -0
  39. package/src/authorization-panel.ts +285 -0
  40. package/src/authorization.ts +147 -0
  41. package/src/editor.ts +51 -0
  42. package/src/fork.ts +31 -0
  43. package/src/git-workflow.ts +87 -0
  44. package/src/index.ts +1523 -1374
  45. package/src/internals.ts +14 -1
  46. package/src/kernel-panels.ts +914 -798
  47. package/src/keyboard.ts +126 -0
  48. package/src/mentions.ts +78 -117
  49. package/src/models.ts +20 -14
  50. package/src/permissions.ts +5 -13
  51. package/src/presets.ts +6 -22
  52. package/src/provider-settings.ts +95 -1
  53. package/src/render/animations.ts +420 -450
  54. package/src/render/editor.ts +398 -0
  55. package/src/render/export.ts +79 -79
  56. package/src/render/lines.ts +342 -236
  57. package/src/render/markdown.ts +99 -26
  58. package/src/render/projection.ts +106 -19
  59. package/src/render/status.ts +713 -650
  60. package/src/render/text.ts +150 -150
  61. package/src/render/tool-detail.ts +3 -1
  62. package/src/session-directory.ts +4 -4
  63. package/src/startup.ts +136 -119
  64. package/src/store.ts +23 -11
  65. package/src/subagents.ts +13 -5
  66. package/src/theme.ts +214 -206
  67. package/src/version.ts +58 -1
package/lib/startup.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import { t as THEME_NAMES } from "./theme-BEi4i_aN.mjs";
1
+ import { t as THEME_NAMES } from "./theme-DCT8Y2xf.mjs";
2
2
  import { Command } from "commander";
3
3
  import { parseCmdline } from "@deepseek-ai/dsh-cmdline";
4
4
  //#region src/startup.ts
5
5
  /**
6
6
  * The interactive terminal app's command-line provider: parses `--resume`,
7
- * `--continue`, `--session`, `--mode`, `--theme`, and `--help`, then
7
+ * `--continue`, `--session`, `--mode`, `--theme`, `--image`, an optional
8
+ * initial prompt, and `--help`, then
8
9
  * publishes {@link TUI_STARTUP_SERVICE} for the runner to consume lazily.
9
10
  * Follows the headless bundle's startup shape (a commander action publishing
10
11
  * a service through {@link parseCmdline}).
@@ -41,23 +42,27 @@ function resolveTuiStartup(options) {
41
42
  if (options.mode === "") throw new Error("--mode needs a preset id");
42
43
  if (options.mode !== void 0 && (options.resume !== void 0 || options.continue === true)) throw new Error("--mode applies only to a new session; it cannot be combined with --resume or --continue");
43
44
  if (options.theme !== void 0 && !THEME_NAMES.includes(options.theme)) throw new Error("--theme must be dark, light, or auto");
44
- const theme = options.theme === void 0 ? {} : { theme: options.theme };
45
+ const input = {
46
+ ...options.theme === void 0 ? {} : { theme: options.theme },
47
+ ...options.prompt === void 0 || options.prompt.trim() === "" ? {} : { prompt: options.prompt.trim() },
48
+ ...options.images === void 0 || options.images.length === 0 ? {} : { images: [...options.images] }
49
+ };
45
50
  return options.resume !== void 0 ? {
46
51
  kind: "resume",
47
52
  sessionId: options.resume,
48
- ...theme
53
+ ...input
49
54
  } : options.continue === true ? {
50
55
  kind: "latest",
51
- ...theme
56
+ ...input
52
57
  } : options.session !== void 0 ? {
53
58
  kind: "named",
54
59
  sessionId: options.session,
55
60
  ...options.mode === void 0 ? {} : { mode: options.mode },
56
- ...theme
61
+ ...input
57
62
  } : {
58
63
  kind: "fresh",
59
64
  ...options.mode === void 0 ? {} : { mode: options.mode },
60
- ...theme
65
+ ...input
61
66
  };
62
67
  }
63
68
  /**
@@ -66,13 +71,15 @@ function resolveTuiStartup(options) {
66
71
  * @returns a fresh program, so one process can parse more than once (tests).
67
72
  */
68
73
  function tuiCommand() {
69
- return new Command().name("dsh --profile cli").description("Claude-Code-style interactive terminal for DeepSeek Harness.").helpOption("-h, --help", "show this help").option("-r, --resume <session>", "resume the persisted session with this id (or unique id prefix)").option("-c, --continue", "resume the most recent persisted session for this working directory").option("--session <id>", "create a new session under this explicit id").option("--mode <preset>", "agent preset for a newly created session").option("--theme <name>", "color theme: dark (default), light, or auto").addHelpText("after", `
74
+ return new Command().name("dsh --profile cli").description("DeepSeek Harness CLI core: the interactive coding terminal.").helpOption("-h, --help", "show this help").option("-r, --resume <session>", "resume the persisted session with this id (or unique id prefix)").option("-c, --continue", "resume the most recent persisted session for this working directory").option("--session <id>", "create a new session under this explicit id").option("--mode <preset>", "agent preset for a newly created session").option("--theme <name>", "color theme: dark (default), light, or auto").option("-i, --image <path>", "attach an image to the initial prompt (repeatable)", (path, paths) => [...paths, path], []).argument("[prompt...]", "initial prompt; sends immediately after startup").addHelpText("after", `
70
75
  Examples:
71
76
  dsh --profile cli fresh session, minted id
72
77
  dsh --profile cli --resume abc123 resume session by id prefix
73
78
  dsh --profile cli --continue resume the latest local session
74
79
  dsh --profile cli --mode minimal fresh session using the minimal preset
75
80
  dsh --profile cli --theme light light palette for bright terminals
81
+ dsh --profile cli "explain this repo" start and send an initial prompt
82
+ dsh --profile cli -i diagram.png "review this diagram"
76
83
  `);
77
84
  }
78
85
  /**
@@ -82,8 +89,11 @@ Examples:
82
89
  */
83
90
  function apply(ctx) {
84
91
  const program = tuiCommand();
85
- program.action(() => {
86
- const options = program.opts();
92
+ program.action((prompt) => {
93
+ const options = {
94
+ ...program.opts(),
95
+ prompt: prompt.join(" ")
96
+ };
87
97
  let startup;
88
98
  try {
89
99
  startup = resolveTuiStartup(options);
@@ -96,4 +106,4 @@ function apply(ctx) {
96
106
  parseCmdline(ctx, program);
97
107
  }
98
108
  //#endregion
99
- export { TUI_STARTUP_SERVICE, apply, inject, name, resolveTuiStartup };
109
+ export { apply, inject, name, resolveTuiStartup };
@@ -491,6 +491,12 @@ const DARK_PALETTE = {
491
491
  125,
492
492
  211,
493
493
  252
494
+ ],
495
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
496
+ composerBand: [
497
+ 46,
498
+ 48,
499
+ 52
494
500
  ]
495
501
  };
496
502
  /** Every palette by theme name; auto resolves through {@link resolveTheme}. */
@@ -556,6 +562,12 @@ const PALETTES = {
556
562
  14,
557
563
  116,
558
564
  144
565
+ ],
566
+ /** Composer three-row band base — neutral light gray, hue-free so wave tints read on it. */
567
+ composerBand: [
568
+ 229,
569
+ 231,
570
+ 235
559
571
  ]
560
572
  }
561
573
  };
@@ -608,17 +620,9 @@ function parseThemeName(value) {
608
620
  function inkColor(triple) {
609
621
  return `rgb(${triple[0]}, ${triple[1]}, ${triple[2]})`;
610
622
  }
611
- /** Paint with the primary brand blue: whale, wordmark, tool names, accents. */
612
- function brand(text) {
613
- return chalk.rgb(...activePalette.brand)(text);
614
- }
615
623
  /** Paint muted captions, hints, and meta lines. */
616
624
  function dim(text) {
617
625
  return chalk.rgb(...activePalette.dim)(text);
618
626
  }
619
- /** Paint failures and error entries. */
620
- function error(text) {
621
- return chalk.rgb(...activePalette.error)(text);
622
- }
623
627
  //#endregion
624
- export { getPalette as a, parseThemeName as c, error as i, setTheme as l, brand as n, getTheme as o, dim as r, inkColor as s, THEME_NAMES as t, chalk as u };
628
+ export { inkColor as a, chalk as c, getTheme as i, dim as n, parseThemeName as o, getPalette as r, setTheme as s, THEME_NAMES as t };
@@ -15,21 +15,27 @@
15
15
  */
16
16
  import { type ReactElement } from 'react';
17
17
  import type { CommandDescriptor } from '@deepseek-ai/dsh-commands';
18
+ import type { ImageBlock } from '@deepseek-ai/dsh-llm';
19
+ import type { AuthorizationInteraction, AuthorizationStatus } from '@deepseek-ai/dsh-authorization';
18
20
  import { type ThemeName } from './theme.ts';
19
21
  import type { TranscriptStore } from './store.ts';
20
22
  import { type TranscriptEntry } from './render/projection.ts';
21
23
  import type { ApprovalStore } from './approval.ts';
22
24
  import type { CommandsView } from './commands.ts';
23
25
  import type { ModelDirectory, ModelRow } from './models.ts';
24
- import type { ProviderSettingsDirectory, ProviderTargetView } from './provider-settings.ts';
26
+ import type { ProviderConfiguration, ProviderSettingsDirectory, ProviderTargetView } from './provider-settings.ts';
25
27
  import type { QuestionStore } from './questions.ts';
26
28
  import type { SkillsView, SkillRow } from './skills.ts';
27
29
  import type { MentionCandidate } from './mentions.ts';
28
30
  import type { SubagentFeedView } from './subagents.ts';
31
+ import { type JobRow } from './kernel-panels.ts';
29
32
  import type { PresetRow } from './presets.ts';
30
33
  import type { PermissionRow } from './permissions.ts';
31
34
  import type { PluginRow } from './plugin-inventory.ts';
32
35
  import type { SessionDirectoryOptions, SessionRow } from './session-directory.ts';
36
+ import type { GitDiffView } from './git-workflow.ts';
37
+ import { type ProviderAuthorizationDirectory, type ProviderAuthorizationRow } from './authorization.ts';
38
+ import { type ImagePathInspection } from './attachments.ts';
33
39
  /** Visual priority for one bounded local notice. */
34
40
  export type NoticeTone = 'info' | 'warning' | 'error';
35
41
  /** Props the runner hands the app; callbacks stay owned by the runner. */
@@ -65,9 +71,9 @@ export interface AppProps {
65
71
  /** Permission preset selected for the current or pending first session. */
66
72
  permission: string;
67
73
  /** Submit one line: slash commands to the registry, other text to the agent. */
68
- dispatch(text: string): void;
74
+ dispatch(text: string, images?: readonly ImageBlock[]): void;
69
75
  /** Submit steering: consumed at the running turn's next step boundary. */
70
- steer(text: string): void;
76
+ steer(text: string, images?: readonly ImageBlock[]): void;
71
77
  /** Interrupt the running turn (Esc); true when a turn was cancelled. */
72
78
  interrupt(): boolean;
73
79
  /** Quit: unmount, flush, and request process exit. */
@@ -76,6 +82,10 @@ export interface AppProps {
76
82
  loadModels(): Promise<ModelDirectory>;
77
83
  /** Load @mention candidates for the typed query (files + sessions). */
78
84
  loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
85
+ /** Validate draft image paths without committing attachment objects. */
86
+ inspectImages(paths: readonly string[]): Promise<readonly ImagePathInspection[]>;
87
+ /** Validate, normalize and persist images immediately before submission. */
88
+ prepareImages(paths: readonly string[]): Promise<readonly ImageBlock[]>;
79
89
  /** Apply one /model selection (with an advertised reasoning effort, when picked); returns the display label. */
80
90
  selectModel(row: ModelRow, effortId?: string): string;
81
91
  /** The /subagent override label, '' when delegated agents follow the current model. */
@@ -96,6 +106,16 @@ export interface AppProps {
96
106
  unsetModelProviderCredential?(target: ProviderTargetView): Promise<void>;
97
107
  /** Remove one user-owned provider profile and its page-managed credential. */
98
108
  removeModelProvider?(target: ProviderTargetView): Promise<void>;
109
+ /** Save endpoint and explicit model capacities through the provider profile. */
110
+ saveModelProviderConfiguration?(target: ProviderTargetView, configuration: ProviderConfiguration): Promise<void>;
111
+ /** Provider authorization flows and value-free stored-record facts. */
112
+ loadProviderAuthorizations?(): Promise<ProviderAuthorizationDirectory>;
113
+ subscribeProviderAuthorizations?(listener: () => void): () => void;
114
+ beginProviderAuthorization?(row: ProviderAuthorizationRow, method: string, interaction: AuthorizationInteraction, signal: AbortSignal): Promise<AuthorizationStatus>;
115
+ cancelProviderAuthorization?(row: ProviderAuthorizationRow): void;
116
+ logoutProviderAuthorization?(row: ProviderAuthorizationRow): Promise<void>;
117
+ openAuthorizationUrl?(url: string): boolean;
118
+ copyTextValue?(text: string): Promise<void>;
99
119
  /** Cycle to the next permission preset (Shift+Tab); returns the new label. */
100
120
  cyclePermission(): string;
101
121
  /** Select or inspect a permission preset without requiring a pre-existing session. */
@@ -104,12 +124,20 @@ export interface AppProps {
104
124
  exportTranscript(argument: string): Promise<void>;
105
125
  /** Rename the session (/title <text>); returns the outcome line for the notice. */
106
126
  renameTitle(argument: string): string;
127
+ /** Copy the latest complete assistant response; resolves to notice text. */
128
+ copyLastResponse(): Promise<string>;
129
+ /** Load a complete read-only Git diff for the file-oriented viewport. */
130
+ loadGitDiff(argument: string): Promise<GitDiffView>;
131
+ /** Start a model review after applying the read-only permission preset. */
132
+ reviewChanges(argument: string): void;
107
133
  /** Preset/session/plugin kernel operations. */
108
134
  loadPresets(): Promise<readonly PresetRow[]>;
109
135
  switchMode(id: string): Promise<string>;
110
136
  /** Load the switchable permission presets for the /permission panel. */
111
137
  loadPermissions(): Promise<readonly PermissionRow[]>;
112
138
  createSession(mode?: string): void;
139
+ /** Fork the active session at a completed-turn boundary. */
140
+ forkSession(argument: string): void;
113
141
  loadSessions(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
114
142
  loadSessionTranscript(id: string, signal?: AbortSignal): Promise<string>;
115
143
  /** Load this session's subagent conversations (children by lineage). */
@@ -117,6 +145,8 @@ export interface AppProps {
117
145
  switchSession(row: SessionRow): void;
118
146
  cancelSessionSwitch(): boolean;
119
147
  loadPlugins(): readonly PluginRow[];
148
+ /** Caller-visible background jobs (the host jobs registry, read-only). */
149
+ loadJobs(): readonly JobRow[];
120
150
  /** Registers the app's notice channel with the runner (called once on mount). */
121
151
  onBridgeReady(bridge: {
122
152
  notify(text: string, tone?: NoticeTone): void;
@@ -134,6 +164,17 @@ export interface AppProps {
134
164
  /** Cancel one queued inbox message by identity (Delete on the empty composer). */
135
165
  cancelQueued(messageId: string): void;
136
166
  }
167
+ /**
168
+ * One-row editor window keeping the logical cursor visible in long drafts.
169
+ * The caret and its surroundings slice at grapheme boundaries: splitting a
170
+ * star-plane surrogate pair would render an isolated half under the block
171
+ * caret with a width the terminal never draws.
172
+ */
173
+ export declare function editorWindow(value: string, cursor: number, columns: number): {
174
+ before: string;
175
+ caret: string;
176
+ after: string;
177
+ };
137
178
  /** One completion candidate row. */
138
179
  interface CompletionCandidate {
139
180
  /** Insertion text for the command name (with leading slash). */
@@ -141,7 +182,7 @@ interface CompletionCandidate {
141
182
  /** Human-readable description shown beside the label. */
142
183
  description: string;
143
184
  /** Candidate origin; skills land the same literal text but route through the prompt. */
144
- origin: 'command' | 'skill' | 'mention' | 'path';
185
+ origin: 'command' | 'skill' | 'mention';
145
186
  }
146
187
  /**
147
188
  * Resolve completion candidates for the current input: TUI-local commands,
@@ -163,14 +204,14 @@ interface SettledRowRecord {
163
204
  before: ReactElement | undefined;
164
205
  /** The roomy-prompt spacer AFTER the row, or undefined. */
165
206
  after: ReactElement | undefined;
166
- /** Whether the row's text depends on the reasoning toggle (Ctrl+R). */
167
- reasonSensitive: boolean;
168
- /** The toggle state the row was built with. */
169
- showReasoning: boolean;
207
+ /** Physical rows this record contributes (row body plus spacers) — the
208
+ * unit of the rendered-history cap. */
209
+ rows: number;
170
210
  }
171
211
  /** The incremental settled-history cache (see `computeSettledRows`). */
172
212
  interface SettledRowsCache {
173
- /** The exact settled entries the cache covers (`view.entries[0..entries.length)`). */
213
+ /** The exact settled entries the cache covers (the WINDOW: the newest
214
+ * `entries.length` settled entries, oldest dropped entries excluded). */
174
215
  entries: TranscriptEntry[];
175
216
  /** Records keyed by entry identity; mutated in place so the append path
176
217
  * never copies the whole map. */
@@ -181,10 +222,21 @@ interface SettledRowsCache {
181
222
  resumed: boolean;
182
223
  /** The toggle state the rows were built with. */
183
224
  showReasoning: boolean;
184
- /** The refreshEpoch the rows were built for; a bump forces a full rebuild. */
225
+ /** The refreshEpoch the rows was built for; a bump forces a full rebuild. */
185
226
  epoch: number;
186
- /** The flat row list (header + per-entry before/box/after). */
227
+ /** The terminal width the rows were wrapped for; a change forces a rebuild. */
228
+ columns: number;
229
+ /** The flat row list (header + optional hint + per-entry before/box/after). */
187
230
  flat: ReactElement[];
231
+ /** Settled entries dropped from the window's head (rendering only — the
232
+ * event log keeps everything; Ctrl+O and /export read it directly). */
233
+ droppedEntries: number;
234
+ /** Physical rows the window's entries contribute (excludes header/hint). */
235
+ totalRows: number;
236
+ /** The window overflowed the trim hysteresis; one source-backed replay
237
+ * (epoch bump) will re-window the cache. The append path never mutates
238
+ * flat's head, so <Static> only ever sees tail appends between remounts. */
239
+ needsTrim: boolean;
188
240
  }
189
241
  /** One step of `computeSettledRows`. */
190
242
  interface SettledRowsResult {
@@ -205,13 +257,29 @@ interface SettledRowsResult {
205
257
  * rebuild of rows, Map, or MarkdownBody parses). `records` is mutated in place
206
258
  * on the append/toggle paths to stay O(delta).
207
259
  *
260
+ * RENDERED-HISTORY CAP: the window holds at most `rowCap` physical rows of
261
+ * settled transcript (header and hint reserved on top). The cap exists only
262
+ * here — the event log, the store projection, /export, Ctrl+O, and /resume
263
+ * keep the full history. Ink 5's <Static> is a consumption counter
264
+ * (items.slice(index) keyed on length): deleting head items mid-stream while
265
+ * appending tail items can permanently swallow new rows, so the append branch
266
+ * NEVER drops the head — it only accounts rows and flags `needsTrim` once the
267
+ * window overflows cap + margin. The flag fires one source-backed replay
268
+ * (epoch bump = the existing clear + <Static> remount), whose rebuild branch
269
+ * walks the settled entries BACKWARD from the newest, keeps whole entries
270
+ * until the cap, and counts everything older as `droppedEntries` (those
271
+ * entries never even reach settledEntryLines). Hysteresis bounds replays to
272
+ * at most one per 25% growth; resize / Ctrl+L / idle Ctrl+R replays re-window
273
+ * for free on the same path.
274
+ *
208
275
  * Full rebuilds run only on the rare, deliberate paths: no cache yet, a
209
- * source-backed replay (`epoch` bump: resize / Ctrl+L / Ctrl+R remounts
210
- * `<Static>` and must re-flush the CURRENT rows), a `resumed` change, or a shrink
211
- * (`store.reset`). A reasoning toggle rebuilds only the rows whose text
212
- * depends on it, preserving the other rows' element identity.
276
+ * source-backed replay (`epoch` bump: resize / Ctrl+L / an idle Ctrl+R fold
277
+ * toggle / a cap trim remounts `<Static>` and must re-flush the CURRENT rows
278
+ * at the CURRENT fold state), a `resumed` change, or a shrink (`store.reset`).
279
+ * While a turn is busy or streaming, Ctrl+R only flips the live region; rows
280
+ * already emitted to native scrollback change exclusively through rebuilds.
213
281
  */
214
- export declare function computeSettledRows(previous: SettledRowsCache | undefined, entries: readonly TranscriptEntry[], settled: number, showReasoning: boolean, resumed: boolean, epoch: number): SettledRowsResult;
282
+ export declare function computeSettledRows(previous: SettledRowsCache | undefined, entries: readonly TranscriptEntry[], settled: number, showReasoning: boolean, resumed: boolean, epoch: number, columns?: number, rowCap?: number): SettledRowsResult;
215
283
  /** The whole terminal app; state arrives via the store, output via Ink. */
216
284
  export declare function App(props: AppProps): ReactElement;
217
285
  export {};
@@ -0,0 +1,20 @@
1
+ /** Terminal image-file adapter over the Harness durable attachment service. */
2
+ import type { AttachmentStore, ImageMediaType } from '@deepseek-ai/dsh-attachment';
3
+ import type { ImageBlock } from '@deepseek-ai/dsh-llm';
4
+ /** A validated path retained in the editor until submission persists it. */
5
+ export interface ImagePathInspection {
6
+ readonly path: string;
7
+ readonly name: string;
8
+ readonly mediaType: ImageMediaType;
9
+ readonly bytes: number;
10
+ }
11
+ /** Detect the supported encoded raster formats from bytes, never from a path suffix. */
12
+ export declare function detectImageMediaType(data: Uint8Array): ImageMediaType | undefined;
13
+ /** Whether a path-like token is worth probing as an image attachment. */
14
+ export declare function looksLikeImagePath(path: string): boolean;
15
+ /** Parse a terminal paste/drop containing only one or more image paths. */
16
+ export declare function parsePastedImagePaths(input: string): readonly string[];
17
+ /** Validate path, byte size and encoded signature without writing an attachment object. */
18
+ export declare function inspectImagePaths(paths: readonly string[], attachments: AttachmentStore | undefined, cwd?: string): Promise<readonly ImagePathInspection[]>;
19
+ /** Read, validate, and persist an ordered image path list as model content blocks. */
20
+ export declare function saveImagePaths(paths: readonly string[], attachments: AttachmentStore | undefined): Promise<readonly ImageBlock[]>;
@@ -0,0 +1,22 @@
1
+ /** Bounded Ink surfaces for provider login and logout. */
2
+ import { type ReactElement } from 'react';
3
+ import { type AuthorizationInteraction, type AuthorizationStatus } from '@deepseek-ai/dsh-authorization';
4
+ import type { CredentialKey } from '@deepseek-ai/dsh-credentials';
5
+ import type { ProviderAuthorizationRow } from './authorization.ts';
6
+ export interface ProviderAuthorizationPanelProps {
7
+ readonly row: ProviderAuthorizationRow;
8
+ begin(row: ProviderAuthorizationRow, method: string, interaction: AuthorizationInteraction, signal: AbortSignal): Promise<AuthorizationStatus>;
9
+ cancel(key: CredentialKey): void;
10
+ openUrl(url: string): boolean;
11
+ copy(text: string): Promise<void>;
12
+ done(): void;
13
+ back(): void;
14
+ }
15
+ /** Run one upstream authorization flow without letting notices or prompts exceed the panel budget. */
16
+ export declare function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProps): ReactElement;
17
+ export declare function ProviderAuthorizationLogoutPanel({ row, confirm, done, back }: {
18
+ row: ProviderAuthorizationRow;
19
+ confirm(row: ProviderAuthorizationRow): Promise<void>;
20
+ done(): void;
21
+ back(): void;
22
+ }): ReactElement;
@@ -0,0 +1,36 @@
1
+ /** Terminal adapter over the Harness provider-authorization and credential-record seams. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { AuthorizationEntry, AuthorizationInteraction, AuthorizationMethod, AuthorizationStatus } from '@deepseek-ai/dsh-authorization';
4
+ import { type CredentialKey, type CredentialRecordInfo } from '@deepseek-ai/dsh-credentials';
5
+ /** One provider login flow joined with its value-free stored-record facts. */
6
+ export interface ProviderAuthorizationRow {
7
+ readonly key: CredentialKey;
8
+ readonly provider: string;
9
+ readonly label: string;
10
+ readonly methods: readonly AuthorizationMethod[];
11
+ readonly inFlight: boolean;
12
+ readonly record: CredentialRecordInfo;
13
+ }
14
+ /** The provider-login directory plus non-fatal record lookup failures. */
15
+ export interface ProviderAuthorizationDirectory {
16
+ readonly rows: readonly ProviderAuthorizationRow[];
17
+ readonly failures: readonly string[];
18
+ }
19
+ /** Load only model-provider flows; unrelated future authorization domains stay out of `/model`. */
20
+ export declare function loadProviderAuthorizations(ctx: Context): Promise<ProviderAuthorizationDirectory>;
21
+ /** Subscribe to login settlement and credential-record changes. */
22
+ export declare function subscribeProviderAuthorizations(ctx: Context, listener: () => void): () => void;
23
+ /** Begin one provider login through the interaction surface owned by the caller. */
24
+ export declare function beginProviderAuthorization(ctx: Context, row: Pick<ProviderAuthorizationRow, 'key'>, method: string, interaction: AuthorizationInteraction, signal?: AbortSignal): Promise<AuthorizationStatus>;
25
+ /** Cancel the attempt currently serving this provider, if any. */
26
+ export declare function cancelProviderAuthorization(ctx: Context, key: CredentialKey): void;
27
+ /** Remove an authorization record without changing the provider's settings profile. */
28
+ export declare function logoutProviderAuthorization(ctx: Context, row: ProviderAuthorizationRow): Promise<void>;
29
+ /** Open an authorization URL with the platform default browser, without invoking a shell. */
30
+ export declare function openAuthorizationUrl(raw: string): boolean;
31
+ /** Compact value-free status for the provider list. */
32
+ export declare function providerAuthorizationStatus(row: ProviderAuthorizationRow | undefined): string;
33
+ /** Find a provider's login flow from a previously loaded directory. */
34
+ export declare function authorizationForProvider(directory: ProviderAuthorizationDirectory | undefined, provider: string): ProviderAuthorizationRow | undefined;
35
+ /** Preserve the upstream entry type in declarations without leaking service internals into the TUI. */
36
+ export type { AuthorizationEntry };
@@ -0,0 +1,6 @@
1
+ /** Host editor and clipboard adapters used by the terminal surface. */
2
+ import type { TranscriptView } from './render/projection.ts';
3
+ /** Copy UTF-8 text through the platform clipboard command. */
4
+ export declare function copyText(text: string): Promise<void>;
5
+ /** Latest complete assistant text, excluding streaming and reasoning. */
6
+ export declare function latestAssistantText(view: TranscriptView): string | undefined;
@@ -0,0 +1,8 @@
1
+ /** Pure session-fork boundary policy shared by the TUI command and tests. */
2
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
3
+ export interface ForkSeed {
4
+ readonly boundarySeq: number;
5
+ readonly events: readonly SessionEvent[];
6
+ }
7
+ /** Select a completed turn and trailing between-turn metadata. */
8
+ export declare function selectForkSeed(events: readonly SessionEvent[], atSeq?: number): ForkSeed;
@@ -0,0 +1,23 @@
1
+ /** Read-only Git inspection used by /diff and /review. */
2
+ export interface GitDiffSpec {
3
+ readonly label: string;
4
+ readonly args: readonly string[];
5
+ }
6
+ /** One file section from a unified diff, retained in source order. */
7
+ export interface GitDiffFile {
8
+ readonly path: string;
9
+ readonly lines: readonly string[];
10
+ }
11
+ /** A parsed diff ready for a file-oriented terminal viewport. */
12
+ export interface GitDiffView {
13
+ readonly title: string;
14
+ readonly files: readonly GitDiffFile[];
15
+ }
16
+ /** Split Git's stable `diff --git` framing without interpreting patch content. */
17
+ export declare function parseGitDiffFiles(text: string): readonly GitDiffFile[];
18
+ /** Parse the intentionally small, option-safe /diff argument vocabulary. */
19
+ export declare function parseGitDiffSpec(argument: string): GitDiffSpec;
20
+ /** Load one complete textual diff without invoking external diff drivers. */
21
+ export declare function loadGitDiff(cwd: string, argument: string): Promise<GitDiffView>;
22
+ /** Review prompt capped before it reaches a provider context window. */
23
+ export declare function buildReviewPrompt(diff: string, label: string, maxChars?: number): string;
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
12
  import z from '@deepseek-ai/schemastery';
13
+ import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session';
13
14
  import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
14
15
  import type { TuiStartup } from './startup.ts';
15
16
  /** Stable Cordis plugin name. */
@@ -24,6 +25,8 @@ export interface Config {
24
25
  sessionId?: string;
25
26
  mode?: string;
26
27
  theme?: string;
28
+ prompt?: string;
29
+ images?: string[];
27
30
  };
28
31
  }
29
32
  export declare const Config: z<Config>;
@@ -33,6 +36,9 @@ interface Target {
33
36
  resume: boolean;
34
37
  mode?: string;
35
38
  cwd?: string;
39
+ seed?: readonly SessionEvent[];
40
+ parentSession?: SessionId;
41
+ seedLength?: number;
36
42
  }
37
43
  /**
38
44
  * Reduce a session id to a filename-safe /export default-name suffix. Session
@@ -7,6 +7,15 @@ import type { PresetRow } from './presets.ts';
7
7
  import type { PluginRow } from './plugin-inventory.ts';
8
8
  import type { SessionDirectoryOptions, SessionRow } from './session-directory.ts';
9
9
  import { type StatusItemId } from './render/status.ts';
10
+ /**
11
+ * Apply one keystroke to a panel search query. IME commits arrive as one
12
+ * multi-character chunk, so the whole printable run is appended; paste
13
+ * markers are stripped and control-laden chunks are ignored.
14
+ */
15
+ export declare function editQuery(query: string, input: string, key: {
16
+ backspace?: boolean;
17
+ delete?: boolean;
18
+ }): string | undefined;
10
19
  export declare function ModePanel({ current, load, select, close }: {
11
20
  current: string;
12
21
  load(): Promise<readonly PresetRow[]>;
@@ -24,6 +33,36 @@ export declare function PluginPanel({ load, close, initialQuery }: {
24
33
  close(): void;
25
34
  initialQuery?: string;
26
35
  }): ReactElement;
36
+ /** One background job snapshot for the /jobs panel (the registry's read-only view). */
37
+ export interface JobRow {
38
+ /** The registry-issued id (`<kind>-N`). */
39
+ readonly id: string;
40
+ /** Producer kind (bash, subagent, …). */
41
+ readonly kind: string;
42
+ /** One-line model-facing label (the command; the delegation description). */
43
+ readonly label: string;
44
+ /** Lifecycle state. */
45
+ readonly status: 'running' | 'stopping' | 'completed' | 'killed' | 'failed';
46
+ /** Kind-specific status detail once the producer supplied one. */
47
+ readonly detail?: string;
48
+ /** Epoch ms when the job was registered. */
49
+ readonly startedAt: number;
50
+ /** Epoch ms when the job settled; absent while running/stopping. */
51
+ readonly finishedAt?: number;
52
+ }
53
+ /** Web TurnStatus elapsed format: `45s` under a minute, `2m03s` beyond. */
54
+ export declare function runClock(ms: number): string;
55
+ /**
56
+ * The read-only background-job panel: caller-owned and unowned jobs from the
57
+ * host `jobs` registry in registration order, with a local second-hand while
58
+ * the panel is open (elapsed clocks advance and the snapshot re-reads; the
59
+ * interval dies with the panel). Cancel stays upstream-only; an absent
60
+ * registry renders as the plain empty state (a harmless missing service).
61
+ */
62
+ export declare function JobsPanel({ load, close }: {
63
+ load(): readonly JobRow[];
64
+ close(): void;
65
+ }): ReactElement;
27
66
  export declare function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete, deleteConfirmId, reloadToken, deleteMode, close }: {
28
67
  currentCwd: string;
29
68
  load(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Keyboard enhancement protocol (Codex `keyboard_modes` parity) and the
3
+ * kitty CSI-u normalization layer.
4
+ *
5
+ * The TUI pushes the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES
6
+ * and REPORT_ALTERNATE_KEYS (flags 1|4 = `\x1b[>5u`). Event types are
7
+ * deliberately NOT requested: Ink 5's parser cannot decode the
8
+ * `:event-type` suffix, and repeat/release reporting buys this surface
9
+ * nothing.
10
+ *
11
+ * Ink 5 also cannot parse most CSI-u forms at all — they fall through its
12
+ * regex as unnamed sequences and get INSERTED AS DRAFT TEXT. The composer's
13
+ * stdin read patch therefore rewrites every CSI-u form it can decode back
14
+ * to the legacy byte or canonical sequence the existing key handling
15
+ * already understands, before Ink ever parses the chunk.
16
+ * @module @deepseek-ai/dsh-code/keyboard
17
+ */
18
+ /** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
19
+ export declare const KEYBOARD_ENHANCE_ENABLE = "\u001B[>4;0m\u001B[>5u";
20
+ /** Pop the enhancement stack and reset modifyOtherKeys (exit path). */
21
+ export declare const KEYBOARD_ENHANCE_DISABLE = "\u001B[<u\u001B[>4;0m";
22
+ /** Enable bracketed paste reporting. */
23
+ export declare const BRACKETED_PASTE_ENABLE = "\u001B[?2004h";
24
+ /** Disable bracketed paste reporting. */
25
+ export declare const BRACKETED_PASTE_DISABLE = "\u001B[?2004l";
26
+ /** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
27
+ export declare const PASTE_START_MARKER = "[200~";
28
+ export declare const PASTE_END_MARKER = "[201~";
29
+ /**
30
+ * Remove bracketed paste markers from one input chunk. Panel drafts accept raw
31
+ * `input` text, where an unhandled paste would otherwise persist the literal
32
+ * "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
33
+ */
34
+ export declare function stripPasteMarkers(text: string): string;
35
+ /**
36
+ * Rewrite every decodable kitty CSI-u sequence in one stdin chunk to the
37
+ * legacy form the input layer already handles. Undecodable or non-key
38
+ * sequences pass through untouched, so terminals without the protocol are
39
+ * unaffected.
40
+ */
41
+ export declare function normalizeKeyboardChunk(chunk: string): string;