dsh-code 0.9.0 → 1.0.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 (59) hide show
  1. package/README.en.md +31 -8
  2. package/README.md +264 -241
  3. package/bin/deepseek.mjs +100 -6
  4. package/cordis.patch.yml +29 -1
  5. package/lib/index.mjs +2317 -708
  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 +66 -14
  9. package/lib/types/attachments.d.ts +7 -0
  10. package/lib/types/editor.d.ts +6 -0
  11. package/lib/types/fork.d.ts +8 -0
  12. package/lib/types/git-workflow.d.ts +23 -0
  13. package/lib/types/index.d.ts +6 -0
  14. package/lib/types/kernel-panels.d.ts +39 -0
  15. package/lib/types/keyboard.d.ts +43 -0
  16. package/lib/types/mentions.d.ts +28 -38
  17. package/lib/types/presets.d.ts +1 -3
  18. package/lib/types/provider-settings.d.ts +16 -0
  19. package/lib/types/render/animations.d.ts +24 -41
  20. package/lib/types/render/editor.d.ts +137 -0
  21. package/lib/types/render/export.d.ts +1 -1
  22. package/lib/types/render/lines.d.ts +6 -2
  23. package/lib/types/render/markdown.d.ts +3 -1
  24. package/lib/types/render/projection.d.ts +29 -3
  25. package/lib/types/render/status.d.ts +5 -12
  26. package/lib/types/session-directory.d.ts +1 -3
  27. package/lib/types/startup.d.ts +14 -11
  28. package/lib/types/store.d.ts +11 -9
  29. package/lib/types/subagents.d.ts +3 -3
  30. package/lib/types/theme.d.ts +14 -1
  31. package/lib/types/version.d.ts +15 -2
  32. package/package.json +153 -117
  33. package/src/app.ts +4455 -3904
  34. package/src/attachments.ts +44 -0
  35. package/src/editor.ts +51 -0
  36. package/src/fork.ts +31 -0
  37. package/src/git-workflow.ts +87 -0
  38. package/src/index.ts +1510 -1374
  39. package/src/internals.ts +14 -1
  40. package/src/kernel-panels.ts +914 -798
  41. package/src/keyboard.ts +125 -0
  42. package/src/mentions.ts +72 -117
  43. package/src/presets.ts +1 -4
  44. package/src/provider-settings.ts +94 -0
  45. package/src/render/animations.ts +74 -60
  46. package/src/render/editor.ts +398 -0
  47. package/src/render/export.ts +79 -79
  48. package/src/render/lines.ts +342 -236
  49. package/src/render/markdown.ts +99 -26
  50. package/src/render/projection.ts +102 -19
  51. package/src/render/status.ts +713 -650
  52. package/src/render/text.ts +150 -150
  53. package/src/render/tool-detail.ts +3 -1
  54. package/src/session-directory.ts +3 -3
  55. package/src/startup.ts +136 -119
  56. package/src/store.ts +23 -11
  57. package/src/subagents.ts +13 -5
  58. package/src/theme.ts +214 -206
  59. 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 };
@@ -21,15 +21,17 @@ import { type TranscriptEntry } from './render/projection.ts';
21
21
  import type { ApprovalStore } from './approval.ts';
22
22
  import type { CommandsView } from './commands.ts';
23
23
  import type { ModelDirectory, ModelRow } from './models.ts';
24
- import type { ProviderSettingsDirectory, ProviderTargetView } from './provider-settings.ts';
24
+ import type { ProviderConfiguration, ProviderSettingsDirectory, ProviderTargetView } from './provider-settings.ts';
25
25
  import type { QuestionStore } from './questions.ts';
26
26
  import type { SkillsView, SkillRow } from './skills.ts';
27
27
  import type { MentionCandidate } from './mentions.ts';
28
28
  import type { SubagentFeedView } from './subagents.ts';
29
+ import { type JobRow } from './kernel-panels.ts';
29
30
  import type { PresetRow } from './presets.ts';
30
31
  import type { PermissionRow } from './permissions.ts';
31
32
  import type { PluginRow } from './plugin-inventory.ts';
32
33
  import type { SessionDirectoryOptions, SessionRow } from './session-directory.ts';
34
+ import type { GitDiffView } from './git-workflow.ts';
33
35
  /** Visual priority for one bounded local notice. */
34
36
  export type NoticeTone = 'info' | 'warning' | 'error';
35
37
  /** Props the runner hands the app; callbacks stay owned by the runner. */
@@ -96,6 +98,8 @@ export interface AppProps {
96
98
  unsetModelProviderCredential?(target: ProviderTargetView): Promise<void>;
97
99
  /** Remove one user-owned provider profile and its page-managed credential. */
98
100
  removeModelProvider?(target: ProviderTargetView): Promise<void>;
101
+ /** Save endpoint and explicit model capacities through the provider profile. */
102
+ saveModelProviderConfiguration?(target: ProviderTargetView, configuration: ProviderConfiguration): Promise<void>;
99
103
  /** Cycle to the next permission preset (Shift+Tab); returns the new label. */
100
104
  cyclePermission(): string;
101
105
  /** Select or inspect a permission preset without requiring a pre-existing session. */
@@ -104,12 +108,20 @@ export interface AppProps {
104
108
  exportTranscript(argument: string): Promise<void>;
105
109
  /** Rename the session (/title <text>); returns the outcome line for the notice. */
106
110
  renameTitle(argument: string): string;
111
+ /** Copy the latest complete assistant response; resolves to notice text. */
112
+ copyLastResponse(): Promise<string>;
113
+ /** Load a complete read-only Git diff for the file-oriented viewport. */
114
+ loadGitDiff(argument: string): Promise<GitDiffView>;
115
+ /** Start a model review after applying the read-only permission preset. */
116
+ reviewChanges(argument: string): void;
107
117
  /** Preset/session/plugin kernel operations. */
108
118
  loadPresets(): Promise<readonly PresetRow[]>;
109
119
  switchMode(id: string): Promise<string>;
110
120
  /** Load the switchable permission presets for the /permission panel. */
111
121
  loadPermissions(): Promise<readonly PermissionRow[]>;
112
122
  createSession(mode?: string): void;
123
+ /** Fork the active session at a completed-turn boundary. */
124
+ forkSession(argument: string): void;
113
125
  loadSessions(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
114
126
  loadSessionTranscript(id: string, signal?: AbortSignal): Promise<string>;
115
127
  /** Load this session's subagent conversations (children by lineage). */
@@ -117,6 +129,8 @@ export interface AppProps {
117
129
  switchSession(row: SessionRow): void;
118
130
  cancelSessionSwitch(): boolean;
119
131
  loadPlugins(): readonly PluginRow[];
132
+ /** Caller-visible background jobs (the host jobs registry, read-only). */
133
+ loadJobs(): readonly JobRow[];
120
134
  /** Registers the app's notice channel with the runner (called once on mount). */
121
135
  onBridgeReady(bridge: {
122
136
  notify(text: string, tone?: NoticeTone): void;
@@ -134,6 +148,17 @@ export interface AppProps {
134
148
  /** Cancel one queued inbox message by identity (Delete on the empty composer). */
135
149
  cancelQueued(messageId: string): void;
136
150
  }
151
+ /**
152
+ * One-row editor window keeping the logical cursor visible in long drafts.
153
+ * The caret and its surroundings slice at grapheme boundaries: splitting a
154
+ * star-plane surrogate pair would render an isolated half under the block
155
+ * caret with a width the terminal never draws.
156
+ */
157
+ export declare function editorWindow(value: string, cursor: number, columns: number): {
158
+ before: string;
159
+ caret: string;
160
+ after: string;
161
+ };
137
162
  /** One completion candidate row. */
138
163
  interface CompletionCandidate {
139
164
  /** Insertion text for the command name (with leading slash). */
@@ -141,7 +166,7 @@ interface CompletionCandidate {
141
166
  /** Human-readable description shown beside the label. */
142
167
  description: string;
143
168
  /** Candidate origin; skills land the same literal text but route through the prompt. */
144
- origin: 'command' | 'skill' | 'mention' | 'path';
169
+ origin: 'command' | 'skill' | 'mention';
145
170
  }
146
171
  /**
147
172
  * Resolve completion candidates for the current input: TUI-local commands,
@@ -163,14 +188,14 @@ interface SettledRowRecord {
163
188
  before: ReactElement | undefined;
164
189
  /** The roomy-prompt spacer AFTER the row, or undefined. */
165
190
  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;
191
+ /** Physical rows this record contributes (row body plus spacers) — the
192
+ * unit of the rendered-history cap. */
193
+ rows: number;
170
194
  }
171
195
  /** The incremental settled-history cache (see `computeSettledRows`). */
172
196
  interface SettledRowsCache {
173
- /** The exact settled entries the cache covers (`view.entries[0..entries.length)`). */
197
+ /** The exact settled entries the cache covers (the WINDOW: the newest
198
+ * `entries.length` settled entries, oldest dropped entries excluded). */
174
199
  entries: TranscriptEntry[];
175
200
  /** Records keyed by entry identity; mutated in place so the append path
176
201
  * never copies the whole map. */
@@ -181,10 +206,21 @@ interface SettledRowsCache {
181
206
  resumed: boolean;
182
207
  /** The toggle state the rows were built with. */
183
208
  showReasoning: boolean;
184
- /** The refreshEpoch the rows were built for; a bump forces a full rebuild. */
209
+ /** The refreshEpoch the rows was built for; a bump forces a full rebuild. */
185
210
  epoch: number;
186
- /** The flat row list (header + per-entry before/box/after). */
211
+ /** The terminal width the rows were wrapped for; a change forces a rebuild. */
212
+ columns: number;
213
+ /** The flat row list (header + optional hint + per-entry before/box/after). */
187
214
  flat: ReactElement[];
215
+ /** Settled entries dropped from the window's head (rendering only — the
216
+ * event log keeps everything; Ctrl+O and /export read it directly). */
217
+ droppedEntries: number;
218
+ /** Physical rows the window's entries contribute (excludes header/hint). */
219
+ totalRows: number;
220
+ /** The window overflowed the trim hysteresis; one source-backed replay
221
+ * (epoch bump) will re-window the cache. The append path never mutates
222
+ * flat's head, so <Static> only ever sees tail appends between remounts. */
223
+ needsTrim: boolean;
188
224
  }
189
225
  /** One step of `computeSettledRows`. */
190
226
  interface SettledRowsResult {
@@ -205,13 +241,29 @@ interface SettledRowsResult {
205
241
  * rebuild of rows, Map, or MarkdownBody parses). `records` is mutated in place
206
242
  * on the append/toggle paths to stay O(delta).
207
243
  *
244
+ * RENDERED-HISTORY CAP: the window holds at most `rowCap` physical rows of
245
+ * settled transcript (header and hint reserved on top). The cap exists only
246
+ * here — the event log, the store projection, /export, Ctrl+O, and /resume
247
+ * keep the full history. Ink 5's <Static> is a consumption counter
248
+ * (items.slice(index) keyed on length): deleting head items mid-stream while
249
+ * appending tail items can permanently swallow new rows, so the append branch
250
+ * NEVER drops the head — it only accounts rows and flags `needsTrim` once the
251
+ * window overflows cap + margin. The flag fires one source-backed replay
252
+ * (epoch bump = the existing clear + <Static> remount), whose rebuild branch
253
+ * walks the settled entries BACKWARD from the newest, keeps whole entries
254
+ * until the cap, and counts everything older as `droppedEntries` (those
255
+ * entries never even reach settledEntryLines). Hysteresis bounds replays to
256
+ * at most one per 25% growth; resize / Ctrl+L / idle Ctrl+R replays re-window
257
+ * for free on the same path.
258
+ *
208
259
  * 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.
260
+ * source-backed replay (`epoch` bump: resize / Ctrl+L / an idle Ctrl+R fold
261
+ * toggle / a cap trim remounts `<Static>` and must re-flush the CURRENT rows
262
+ * at the CURRENT fold state), a `resumed` change, or a shrink (`store.reset`).
263
+ * While a turn is busy or streaming, Ctrl+R only flips the live region; rows
264
+ * already emitted to native scrollback change exclusively through rebuilds.
213
265
  */
214
- export declare function computeSettledRows(previous: SettledRowsCache | undefined, entries: readonly TranscriptEntry[], settled: number, showReasoning: boolean, resumed: boolean, epoch: number): SettledRowsResult;
266
+ export declare function computeSettledRows(previous: SettledRowsCache | undefined, entries: readonly TranscriptEntry[], settled: number, showReasoning: boolean, resumed: boolean, epoch: number, columns?: number, rowCap?: number): SettledRowsResult;
215
267
  /** The whole terminal app; state arrives via the store, output via Ink. */
216
268
  export declare function App(props: AppProps): ReactElement;
217
269
  export {};
@@ -0,0 +1,7 @@
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
+ /** Detect the supported encoded raster formats from bytes, never from a path suffix. */
5
+ export declare function detectImageMediaType(data: Uint8Array): ImageMediaType | undefined;
6
+ /** Read, validate, and persist an ordered image path list as model content blocks. */
7
+ export declare function saveImagePaths(paths: readonly string[], attachments: AttachmentStore | undefined): Promise<readonly ImageBlock[]>;
@@ -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,43 @@
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`), which makes terminals
7
+ * report Shift+Enter as `CSI 13;2u` instead of a bare CR. Event types are
8
+ * deliberately NOT requested: Ink 5's parser cannot decode the
9
+ * `:event-type` suffix, and repeat/release reporting buys this surface
10
+ * nothing.
11
+ *
12
+ * Ink 5 also cannot parse most CSI-u forms at all — they fall through its
13
+ * regex as unnamed sequences and get INSERTED AS DRAFT TEXT. The composer's
14
+ * stdin read patch therefore rewrites every CSI-u form it can decode back
15
+ * to the legacy byte or canonical sequence the existing key handling
16
+ * already understands, before Ink ever parses the chunk.
17
+ *
18
+ * @module @deepseek-ai/dsh-code/keyboard
19
+ */
20
+ /** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
21
+ export declare const KEYBOARD_ENHANCE_ENABLE = "\u001B[>4;0m\u001B[>5u";
22
+ /** Pop the enhancement stack and reset modifyOtherKeys (exit path). */
23
+ export declare const KEYBOARD_ENHANCE_DISABLE = "\u001B[<u\u001B[>4;0m";
24
+ /** Enable bracketed paste reporting. */
25
+ export declare const BRACKETED_PASTE_ENABLE = "\u001B[?2004h";
26
+ /** Disable bracketed paste reporting. */
27
+ export declare const BRACKETED_PASTE_DISABLE = "\u001B[?2004l";
28
+ /** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
29
+ export declare const PASTE_START_MARKER = "[200~";
30
+ export declare const PASTE_END_MARKER = "[201~";
31
+ /**
32
+ * Remove bracketed paste markers from one input chunk. Panel drafts accept raw
33
+ * `input` text, where an unhandled paste would otherwise persist the literal
34
+ * "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
35
+ */
36
+ export declare function stripPasteMarkers(text: string): string;
37
+ /**
38
+ * Rewrite every decodable kitty CSI-u sequence in one stdin chunk to the
39
+ * legacy form the input layer already handles. Undecodable or non-key
40
+ * sequences pass through untouched, so terminals without the protocol are
41
+ * unaffected.
42
+ */
43
+ export declare function normalizeKeyboardChunk(chunk: string): string;
@@ -1,17 +1,21 @@
1
1
  /**
2
- * Workspace @mention support: file and directory candidates from a bounded
3
- * async scan of the session cwd, session candidates from the opt-in
4
- * `sessionReferenceResolver` service, and submission preparation through its
5
- * `prepare()` API. Picked session mentions land as canonical
6
- * `@[label](dsh-session:…)` tokens; on submit the text is parsed back into
7
- * readable `@label` text plus structured references, snapshots are injected
8
- * via `agent.inject()` before the readable message wakes the driver
2
+ * Workspace @mention support: file and directory candidates from the
3
+ * `fileReferences` service (dsh-file-reference-local), session candidates
4
+ * from the opt-in `sessionReferenceResolver` service, and submission
5
+ * preparation through its `prepare()` API. Picked session mentions land as
6
+ * canonical `@[label](dsh-session:…)` tokens; on submit the text is parsed
7
+ * back into readable `@label` text plus structured references, snapshots are
8
+ * injected via `agent.inject()` before the readable message wakes the driver
9
9
  * (`followup` idle, `steer` running) — exactly the upstream README's wiring.
10
10
  *
11
- * Harness exposes no workspace-file mention service (only session references
12
- * plus a post-hoc produced-file linker), so the file index is the lightweight
13
- * bounded scan below, kept deliberately smaller than Codex's streaming
14
- * gitignore-aware walker.
11
+ * File discovery lives entirely in the Harness service (per-agent bounded
12
+ * index, `@dir/` listing, symlink guards, tool/result invalidation); this
13
+ * module only maps candidates to menu rows and never re-implements scanning.
14
+ * The service is agent-scoped (the agent supplies the session cwd and the
15
+ * cache key), so before the first session creates an agent the SAME official
16
+ * search class runs against the launch cwd — @ file completion works on a
17
+ * bare launch, model- and session-independent, and the agent-scoped service
18
+ * takes over once a session exists.
15
19
  *
16
20
  * @module @deepseek-ai/dsh-code/mentions
17
21
  */
@@ -20,13 +24,6 @@ import type { Agent } from '@deepseek-ai/dsh-agent';
20
24
  import { parseSessionReferenceText, type SessionReferenceCandidate, type SessionReferenceInput } from '@deepseek-ai/dsh-session-reference';
21
25
  /** Parsed submission text: readable text plus structured references. */
22
26
  type ParsedSessionReferenceText = ReturnType<typeof parseSessionReferenceText>;
23
- /** One filesystem entry the @ menu can complete. */
24
- export interface FileCandidate {
25
- /** Workspace-relative path with forward slashes. */
26
- path: string;
27
- /** Entry kind; directories insert with a trailing slash. */
28
- kind: 'file' | 'directory';
29
- }
30
27
  /** One merged menu candidate (files and sessions, already ranked). */
31
28
  export interface MentionCandidate {
32
29
  /** Text inserted after the `@` (directories carry a trailing slash). */
@@ -45,18 +42,8 @@ export interface PreparedMention {
45
42
  /** Aggregated snapshot for `agent.inject()`, undefined without references. */
46
43
  additionalContext?: import('@deepseek-ai/dsh-session').UserMessage;
47
44
  }
48
- /**
49
- * Bounded async BFS scan of a workspace; unreadable entries are skipped.
50
- * Both files and directories are indexed (directories insert with a trailing
51
- * slash), mirroring Codex's `MatchType::{File,Directory}` index. Dotfiles and
52
- * the {@link SKIP_DIRS} list are excluded, which is a coarser filter than
53
- * Codex's gitignore-aware walker but stays dependency-free and bounded.
54
- */
55
- export declare function scanWorkspaceFiles(root: string, signal?: AbortSignal): Promise<readonly FileCandidate[]>;
56
45
  /** The mention API the input editor and the runner share. */
57
46
  export interface MentionsApi {
58
- /** Scanned workspace files and directories, cached across one session. */
59
- files(): Promise<readonly FileCandidate[]>;
60
47
  /** Ranked menu candidates for the typed `@` query. */
61
48
  candidates(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
62
49
  /** Parse submission text into readable text plus structured references. */
@@ -71,17 +58,20 @@ export interface MentionsApi {
71
58
  }
72
59
  /**
73
60
  * Create the mention API for one agent's workspace. A missing
74
- * session-reference service degrades to file mentions only (the scan still
75
- * works); `prepare` then passes text through untouched. An undefined agent
76
- * (a bare launch before any session exists) also degrades to file-only
77
- * mentions, so `@` file completion works before the first message.
61
+ * `fileReferences` service (with an agent present) or `sessionReferenceResolver`
62
+ * degrades that half to empty rows; `prepare` passes text through untouched
63
+ * without references. An undefined agent (a bare launch before any session
64
+ * exists) runs the official WorkspaceFileSearch over the launch cwd — the
65
+ * same class the mounted service uses per agent — so `@` file completion
66
+ * works from the first keystroke; session references wait for the session.
78
67
  *
79
- * `files` is hoisted into the closure so `candidates` never reaches for
80
- * `this` the runner hands `mentions.candidates` to the input editor as a
81
- * detached callback, and a `this`-bound method would throw on every `@` key.
82
- * @param ctx - context carrying the optional `sessionReferenceResolver`.
83
- * @param agent - the session owner; excluded from its own candidates.
84
- * @param cwd - workspace root to scan.
68
+ * `candidates` never reaches for `this` — the runner hands it to the input
69
+ * editor as a detached callback, and a `this`-bound method would throw on
70
+ * every `@` key.
71
+ * @param ctx - context carrying the optional `fileReferences` and
72
+ * `sessionReferenceResolver` services.
73
+ * @param agent - the session owner; excluded from its own session candidates.
74
+ * @param cwd - launch working directory; bounds the pre-session search.
85
75
  */
86
76
  export declare function createMentions(ctx: Context, agent: Agent | undefined, cwd: string): MentionsApi;
87
77
  export {};
@@ -1,6 +1,6 @@
1
1
  /** Agent-preset policy kept independent from the Ink surface. */
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
- import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent';
3
+ import type { Agent } from '@deepseek-ai/dsh-agent';
4
4
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
5
5
  /** One discoverable agent composition. */
6
6
  export interface PresetRow {
@@ -30,5 +30,3 @@ export declare function resolvePreset(session: Pick<Session, 'header' | 'events'
30
30
  export declare function selectPreset(service: AgentPresetsService, agent: Agent | undefined, presetId: string): Promise<PresetRow>;
31
31
  /** Recompose atomically from the caller's perspective, logging only success. */
32
32
  export declare function switchPreset(service: AgentPresetsService, agent: Agent, presetId: string): Promise<PresetRow>;
33
- /** Minimal handle shape used by lifecycle tests without exposing Agent internals. */
34
- export type OwnedAgent = Pick<AgentHandle, 'agent' | 'dispose'>;
@@ -42,6 +42,18 @@ export type ProviderCredentialView = ({
42
42
  readonly kind: 'error';
43
43
  readonly message: string;
44
44
  };
45
+ /** One explicit model enabled for a provider profile. */
46
+ export interface ProviderModelSettings {
47
+ readonly id: string;
48
+ readonly name?: string;
49
+ readonly contextWindow?: number;
50
+ readonly maxTokens?: number;
51
+ }
52
+ /** The small, portable subset of a provider profile the terminal edits. */
53
+ export interface ProviderConfiguration {
54
+ readonly baseURL?: string;
55
+ readonly models: readonly ProviderModelSettings[];
56
+ }
45
57
  /**
46
58
  * One provider row in the TUI provider-management panel: the configurable
47
59
  * directory entry joined with its settings profile and credential facts.
@@ -71,6 +83,8 @@ export interface ProviderTargetView {
71
83
  readonly suggestedRef: string;
72
84
  /** Credential facts, a bounded describe error, or undefined when there is no ref to describe. */
73
85
  readonly credential: ProviderCredentialView | undefined;
86
+ /** Endpoint and explicit model overrides visible to the provider editor. */
87
+ readonly configuration: ProviderConfiguration;
74
88
  /** The owning adapter reports this route as hand-declared (absent when it draws no distinction). */
75
89
  readonly declared?: boolean;
76
90
  }
@@ -120,6 +134,8 @@ export declare function loadProviderSettings(ctx: Context): Promise<ProviderSett
120
134
  * @throws {@link ProviderSettingsError} with a single-line, key-free message.
121
135
  */
122
136
  export declare function saveProviderCredential(ctx: Context, target: ProviderTargetView, rawKey: string): Promise<void>;
137
+ /** Save the endpoint and an explicit model allow-list without rebuilding the profile. */
138
+ export declare function saveProviderConfiguration(ctx: Context, target: ProviderTargetView, configuration: ProviderConfiguration): Promise<void>;
123
139
  /**
124
140
  * Remove the currently named credential without touching the provider
125
141
  * profile. Only the resolved profile's own reference is unset; a dormant or