dsh-code 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.en.md +20 -6
  2. package/README.md +20 -6
  3. package/lib/index.mjs +3952 -1315
  4. package/lib/startup.mjs +21 -9
  5. package/lib/theme-BEi4i_aN.mjs +624 -0
  6. package/lib/types/app.d.ts +108 -4
  7. package/lib/types/history.d.ts +15 -4
  8. package/lib/types/index.d.ts +49 -0
  9. package/lib/types/kernel-panels.d.ts +28 -0
  10. package/lib/types/mentions.d.ts +29 -12
  11. package/lib/types/models.d.ts +66 -0
  12. package/lib/types/permissions.d.ts +37 -0
  13. package/lib/types/presets.d.ts +2 -0
  14. package/lib/types/provider-settings.d.ts +144 -0
  15. package/lib/types/questions.d.ts +2 -0
  16. package/lib/types/render/animations.d.ts +177 -2
  17. package/lib/types/render/lines.d.ts +6 -0
  18. package/lib/types/render/markdown.d.ts +3 -3
  19. package/lib/types/render/projection.d.ts +123 -3
  20. package/lib/types/render/status.d.ts +35 -24
  21. package/lib/types/render/text.d.ts +14 -7
  22. package/lib/types/render/tool-detail.d.ts +3 -1
  23. package/lib/types/render/tool-preview.d.ts +4 -1
  24. package/lib/types/session-directory.d.ts +15 -0
  25. package/lib/types/startup.d.ts +12 -4
  26. package/lib/types/store.d.ts +13 -2
  27. package/lib/types/theme-panel.d.ts +24 -0
  28. package/lib/types/theme.d.ts +158 -2
  29. package/lib/types/version.d.ts +5 -0
  30. package/package.json +1 -1
  31. package/src/app.ts +1283 -206
  32. package/src/approval.ts +11 -2
  33. package/src/history.ts +20 -5
  34. package/src/index.ts +1207 -905
  35. package/src/kernel-panels.ts +518 -419
  36. package/src/mentions.ts +57 -27
  37. package/src/models.ts +200 -66
  38. package/src/permissions.ts +85 -0
  39. package/src/presets.ts +12 -0
  40. package/src/provider-settings.ts +520 -0
  41. package/src/questions.ts +15 -5
  42. package/src/render/animations.ts +373 -2
  43. package/src/render/lines.ts +21 -6
  44. package/src/render/markdown.ts +302 -4
  45. package/src/render/projection.ts +1419 -659
  46. package/src/render/status.ts +650 -603
  47. package/src/render/text.ts +28 -9
  48. package/src/render/tool-detail.ts +81 -40
  49. package/src/render/tool-preview.ts +18 -2
  50. package/src/session-directory.ts +44 -5
  51. package/src/skills.ts +8 -4
  52. package/src/startup.ts +119 -109
  53. package/src/store.ts +26 -8
  54. package/src/theme-panel.ts +72 -0
  55. package/src/theme.ts +206 -70
  56. package/src/version.ts +16 -0
@@ -14,14 +14,19 @@
14
14
  * @module @deepseek-ai/dsh-code/app
15
15
  */
16
16
  import { type ReactElement } from 'react';
17
+ import type { CommandDescriptor } from '@deepseek-ai/dsh-commands';
18
+ import { type ThemeName } from './theme.ts';
17
19
  import type { TranscriptStore } from './store.ts';
20
+ import { type TranscriptEntry } from './render/projection.ts';
18
21
  import type { ApprovalStore } from './approval.ts';
19
22
  import type { CommandsView } from './commands.ts';
20
23
  import type { ModelDirectory, ModelRow } from './models.ts';
24
+ import type { ProviderSettingsDirectory, ProviderTargetView } from './provider-settings.ts';
21
25
  import type { QuestionStore } from './questions.ts';
22
- import type { SkillsView } from './skills.ts';
26
+ import type { SkillsView, SkillRow } from './skills.ts';
23
27
  import type { MentionCandidate } from './mentions.ts';
24
28
  import type { PresetRow } from './presets.ts';
29
+ import type { PermissionRow } from './permissions.ts';
25
30
  import type { PluginRow } from './plugin-inventory.ts';
26
31
  import type { SessionDirectoryOptions, SessionRow } from './session-directory.ts';
27
32
  /** Visual priority for one bounded local notice. */
@@ -40,6 +45,8 @@ export interface AppProps {
40
45
  skills: SkillsView;
41
46
  /** `provider/model` selection serving this session (updated on /model). */
42
47
  model: string;
48
+ /** Effective reasoning effort in force ('' when none), for the /model picker mark. */
49
+ effort?: string;
43
50
  /** Working-directory basename the session serves. */
44
51
  cwd: string;
45
52
  /** Absolute working directory used by session filters and references. */
@@ -50,8 +57,10 @@ export interface AppProps {
50
57
  sessionId: string;
51
58
  /** Whether this session was resumed from persistence. */
52
59
  resumed: boolean;
53
- /** Agent preset currently composing the session. */
60
+ /** Agent preset selected for the current or pending first session. */
54
61
  mode: string;
62
+ /** Permission preset selected for the current or pending first session. */
63
+ permission: string;
55
64
  /** Submit one line: slash commands to the registry, other text to the agent. */
56
65
  dispatch(text: string): void;
57
66
  /** Submit steering: consumed at the running turn's next step boundary. */
@@ -64,10 +73,22 @@ export interface AppProps {
64
73
  loadModels(): Promise<ModelDirectory>;
65
74
  /** Load @mention candidates for the typed query (files + sessions). */
66
75
  loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
67
- /** Apply one /model selection; returns the display label. */
68
- selectModel(row: ModelRow): string;
76
+ /** Apply one /model selection (with an advertised reasoning effort, when picked); returns the display label. */
77
+ selectModel(row: ModelRow, effortId?: string): string;
78
+ /** Load provider/settings/credential facts for the optional /model provider stage. */
79
+ loadModelProviders?(): Promise<ProviderSettingsDirectory>;
80
+ /** Subscribe to Harness credential/settings/adapter invalidations while /model is open. */
81
+ subscribeModelProviders?(listener: () => void): () => void;
82
+ /** Store or rotate one provider credential through the Harness credential service. */
83
+ saveModelProviderCredential?(target: ProviderTargetView, key: string): Promise<void>;
84
+ /** Remove one writable provider credential without removing its settings profile. */
85
+ unsetModelProviderCredential?(target: ProviderTargetView): Promise<void>;
86
+ /** Remove one user-owned provider profile and its page-managed credential. */
87
+ removeModelProvider?(target: ProviderTargetView): Promise<void>;
69
88
  /** Cycle to the next permission preset (Shift+Tab); returns the new label. */
70
89
  cyclePermission(): string;
90
+ /** Select or inspect a permission preset without requiring a pre-existing session. */
91
+ setPermission(id: string): string;
71
92
  /** Export the transcript to a markdown file (/export [path]); reports via notices. */
72
93
  exportTranscript(argument: string): Promise<void>;
73
94
  /** Rename the session (/title <text>); returns the outcome line for the notice. */
@@ -75,6 +96,8 @@ export interface AppProps {
75
96
  /** Preset/session/plugin kernel operations. */
76
97
  loadPresets(): Promise<readonly PresetRow[]>;
77
98
  switchMode(id: string): Promise<string>;
99
+ /** Load the switchable permission presets for the /permission panel. */
100
+ loadPermissions(): Promise<readonly PermissionRow[]>;
78
101
  createSession(mode?: string): void;
79
102
  loadSessions(options: SessionDirectoryOptions, signal?: AbortSignal): Promise<readonly SessionRow[]>;
80
103
  loadSessionTranscript(id: string, signal?: AbortSignal): Promise<string>;
@@ -89,6 +112,8 @@ export interface AppProps {
89
112
  statusline: readonly string[];
90
113
  /** Persist a new statusline item set; the runner surfaces IO failures as notices. */
91
114
  saveStatusline(items: readonly string[]): void;
115
+ /** Apply and persist one /theme selection; the runner owns the theme.json file. */
116
+ saveTheme?(name: ThemeName): void;
92
117
  /** Persistent cross-session input history (oldest first); the runner owns the file. */
93
118
  history: readonly string[];
94
119
  /** Persist one submitted prompt to the global history file. */
@@ -96,5 +121,84 @@ export interface AppProps {
96
121
  /** Cancel one queued inbox message by identity (Delete on the empty composer). */
97
122
  cancelQueued(messageId: string): void;
98
123
  }
124
+ /** One completion candidate row. */
125
+ interface CompletionCandidate {
126
+ /** Insertion text for the command name (with leading slash). */
127
+ label: string;
128
+ /** Human-readable description shown beside the label. */
129
+ description: string;
130
+ /** Candidate origin; skills land the same literal text but route through the prompt. */
131
+ origin: 'command' | 'skill' | 'mention' | 'path';
132
+ }
133
+ /**
134
+ * Resolve completion candidates for the current input: TUI-local commands,
135
+ * the live registry descriptors, and user-invocable skills, filtered by the
136
+ * typed prefix. Command names win collisions (the dispatch tries the
137
+ * registry first and only then falls through to the skill gesture), and a
138
+ * later duplicate name never renders twice.
139
+ *
140
+ * A bare `/` returns the FULL merged list — Codex's command popup shows every
141
+ * command inside a scroll window on an empty filter, and the menu's own
142
+ * selection window bounds the visible rows, so no slice cap is needed.
143
+ */
144
+ export declare function completionCandidates(value: string, descriptors: readonly CommandDescriptor[], skills: readonly SkillRow[]): readonly CompletionCandidate[];
145
+ /** One cached settled row: the row Box plus its roomy-prompt spacers. */
146
+ interface SettledRowRecord {
147
+ /** The row Box element (keyed by the entry's settled index). */
148
+ box: ReactElement;
149
+ /** The roomy-prompt spacer BEFORE the row, or undefined. */
150
+ before: ReactElement | undefined;
151
+ /** The roomy-prompt spacer AFTER the row, or undefined. */
152
+ after: ReactElement | undefined;
153
+ /** Whether the row's text depends on the reasoning toggle (Ctrl+R). */
154
+ reasonSensitive: boolean;
155
+ /** The toggle state the row was built with. */
156
+ showReasoning: boolean;
157
+ }
158
+ /** The incremental settled-history cache (see `computeSettledRows`). */
159
+ interface SettledRowsCache {
160
+ /** The exact settled entries the cache covers (`view.entries[0..entries.length)`). */
161
+ entries: TranscriptEntry[];
162
+ /** Records keyed by entry identity; mutated in place so the append path
163
+ * never copies the whole map. */
164
+ records: Map<TranscriptEntry, SettledRowRecord>;
165
+ /** The header element (depends only on `resumed`). */
166
+ header: ReactElement;
167
+ /** The `resumed` the header was built with. */
168
+ resumed: boolean;
169
+ /** The toggle state the rows were built with. */
170
+ showReasoning: boolean;
171
+ /** The refreshEpoch the rows were built for; a bump forces a full rebuild. */
172
+ epoch: number;
173
+ /** The flat row list (header + per-entry before/box/after). */
174
+ flat: ReactElement[];
175
+ }
176
+ /** One step of `computeSettledRows`. */
177
+ interface SettledRowsResult {
178
+ cache: SettledRowsCache;
179
+ /** How many rows had to be BUILT by this step (0 = pure reuse). */
180
+ built: number;
181
+ }
182
+ /**
183
+ * The settled `<Static>` row set as a PURE incremental state machine (App
184
+ * drives it from the memo; tests drive it directly and read `built`).
185
+ *
186
+ * The settled prefix is permanently final: the projection only APPENDS below
187
+ * the flush boundary, removes pending rows at or beyond it, and replaces
188
+ * running tool/retry/command rows there too. So extending the cache never
189
+ * rescans the old prefix — a grown boundary builds ONLY the newly settled
190
+ * suffix and reuses every cached element, letting React bail out of unchanged
191
+ * rows and keeping long histories out of the per-durable-event path (no O(N)
192
+ * rebuild of rows, Map, or MarkdownBody parses). `records` is mutated in place
193
+ * on the append/toggle paths to stay O(delta).
194
+ *
195
+ * Full rebuilds run only on the rare, deliberate paths: no cache yet, a
196
+ * source-backed replay (`epoch` bump: resize / Ctrl+L / Ctrl+R remounts
197
+ * `<Static>` and must re-flush the CURRENT rows), a `resumed` change, or a shrink
198
+ * (`store.reset`). A reasoning toggle rebuilds only the rows whose text
199
+ * depends on it, preserving the other rows' element identity.
200
+ */
201
+ export declare function computeSettledRows(previous: SettledRowsCache | undefined, entries: readonly TranscriptEntry[], settled: number, showReasoning: boolean, resumed: boolean, epoch: number): SettledRowsResult;
99
202
  /** The whole terminal app; state arrives via the store, output via Ink. */
100
203
  export declare function App(props: AppProps): ReactElement;
204
+ export {};
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * @module @deepseek-ai/dsh-tui/history
9
9
  */
10
- /** Maximum entries retained in the persistent history file. */
11
- export declare const HISTORY_MAX_ENTRIES = 500;
10
+ /** Maximum entries retained in the persistent history file and the local recall pool. */
11
+ export declare const HISTORY_MAX_ENTRIES = 100;
12
12
  /** Encode one entry for the history file (JSON keeps multi-line drafts intact). */
13
13
  export declare function serializeHistoryEntry(text: string): string;
14
14
  /**
@@ -31,12 +31,23 @@ export declare function parseHistoryFile(raw: string, max?: number): readonly st
31
31
  export declare function appendHistoryContent(current: string, text: string, max?: number): string;
32
32
  /**
33
33
  * Record one in-session submission: empty text is ignored and an adjacent
34
- * duplicate collapses (Codex `record_local_submission` semantics).
34
+ * duplicate collapses (Codex `record_local_submission` semantics). The local
35
+ * pool shares the persistent pool's cap so the recall space stays bounded.
35
36
  * @param local - current in-session entries, oldest first.
36
37
  * @param text - the submitted prompt.
38
+ * @param max - the local pool cap.
37
39
  * @returns the updated local list.
38
40
  */
39
- export declare function recordLocalEntry(local: readonly string[], text: string): readonly string[];
41
+ export declare function recordLocalEntry(local: readonly string[], text: string, max?: number): readonly string[];
42
+ /**
43
+ * Serialize a capped entry list to the history file format (one JSON line per
44
+ * entry, trailing newline). The runner writes the in-memory list as the whole
45
+ * file, so rapid same-process submissions cannot lose entries to a
46
+ * read-modify-write race (the file is never read back before writing).
47
+ * @param entries - the entries to persist, oldest first.
48
+ * @returns the file content, '' for an empty list.
49
+ */
50
+ export declare function serializeHistoryList(entries: readonly string[]): string;
40
51
  /**
41
52
  * Build the recall space, newest first: local entries, then persistent
42
53
  * entries whose text is not duplicated locally (the local copy wins and the
@@ -10,6 +10,8 @@
10
10
  */
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
12
  import z from '@deepseek-ai/schemastery';
13
+ import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
14
+ import type { TuiStartup } from './startup.ts';
13
15
  /** Stable Cordis plugin name. */
14
16
  export declare const name = "tui-runner";
15
17
  /** Core services required before the interactive session can start. */
@@ -21,12 +23,59 @@ export interface Config {
21
23
  kind: string;
22
24
  sessionId?: string;
23
25
  mode?: string;
26
+ theme?: string;
24
27
  };
25
28
  }
26
29
  export declare const Config: z<Config>;
30
+ /** The session identity this invocation will run, plus whether it is resumed. */
31
+ interface Target {
32
+ sessionId: string;
33
+ resume: boolean;
34
+ mode?: string;
35
+ cwd?: string;
36
+ }
37
+ /**
38
+ * Reduce a session id to a filename-safe /export default-name suffix. Session
39
+ * ids are normally minted `session-<uuid>`, but `--session` accepts arbitrary
40
+ * user text: path separators must never leak into the default export filename
41
+ * (which would escape the session cwd).
42
+ * @param id - the session id.
43
+ * @returns at most the last 8 filename-safe characters.
44
+ */
45
+ export declare function exportSessionIdSuffix(id: string): string;
46
+ /** One ordered step of the terminal quit cleanup. */
47
+ export interface QuitCleanupStep {
48
+ /** Step label used in diagnostics and tests. */
49
+ readonly name: string;
50
+ /** The step's async work; a rejection is contained by the sequence. */
51
+ readonly run: () => Promise<void>;
52
+ }
53
+ /**
54
+ * Run the ordered quit cleanup, then request exit. Every step rejection is
55
+ * contained (reported through `onError`) so a failed flush or dispose never
56
+ * skips the remaining cleanup; the exit request is always reached exactly
57
+ * once.
58
+ * @param steps - the cleanup steps in dependency order (settle the visible
59
+ * session, await the final in-flight composition, await durable recall).
60
+ * @param exit - the terminal exit request (code 0).
61
+ * @param onError - optional failure sink; called once per failing step and
62
+ * itself contained, so a throwing sink cannot abort the sequence.
63
+ * @returns the names of the steps that started, in order (for tests).
64
+ */
65
+ export declare function runQuitSequence(steps: readonly QuitCleanupStep[], exit: (code: number) => void, onError?: (name: string, error: unknown) => void): Promise<readonly string[]>;
66
+ /**
67
+ * Resolve the invocation's target session against the persisted headers.
68
+ * @param startup - the parsed startup flags.
69
+ * @param persistence - the persistence service; required for resume/latest.
70
+ * @param cwd - the working directory `--continue` filters by.
71
+ * @returns the target identity.
72
+ * @throws with a user-facing message when the flags name nothing resolvable.
73
+ */
74
+ export declare function resolveTarget(startup: TuiStartup, persistence: SessionPersistence | undefined, cwd: string): Promise<Target>;
27
75
  /**
28
76
  * Mount the interactive terminal driver.
29
77
  * @param ctx - plugin context carrying core services and the launcher-provided exit request.
30
78
  * @param config - validated startup config resolved from the tuiStartup provider.
31
79
  */
32
80
  export declare function apply(ctx: Context, config: Config): void;
81
+ export {};
@@ -1,5 +1,7 @@
1
1
  /** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
2
2
  import { type ReactElement } from 'react';
3
+ import type { ModelRow } from './models.ts';
4
+ import type { PermissionRow } from './permissions.ts';
3
5
  import type { PresetRow } from './presets.ts';
4
6
  import type { PluginRow } from './plugin-inventory.ts';
5
7
  import type { SessionDirectoryOptions, SessionRow } from './session-directory.ts';
@@ -10,6 +12,12 @@ export declare function ModePanel({ current, load, select, close }: {
10
12
  select(id: string): void;
11
13
  close(): void;
12
14
  }): ReactElement;
15
+ export declare function PermissionPanel({ current, load, select, close }: {
16
+ current: string;
17
+ load(): Promise<readonly PermissionRow[]>;
18
+ select(id: string): void;
19
+ close(): void;
20
+ }): ReactElement;
13
21
  export declare function PluginPanel({ load, close, initialQuery }: {
14
22
  load(): readonly PluginRow[];
15
23
  close(): void;
@@ -46,3 +54,23 @@ export declare function StatuslinePanel({ enabled, change, close }: {
46
54
  change(items: readonly StatusItemId[]): void;
47
55
  close(): void;
48
56
  }): ReactElement;
57
+ /**
58
+ * The `/model` reasoning-effort stage (the Codex model → reasoning popup
59
+ * contract): one bounded list over the selected model's adapter-advertised
60
+ * effort levels, with the effective effort and the model default marked.
61
+ * A model WITHOUT an adapter-declared default leads with a "Default"
62
+ * (provider-default) row — the web effort pane's first entry — so the user
63
+ * can clear a picked level back to provider behavior instead of being forced
64
+ * to choose an advertised one. Enter applies one level; Esc returns to the
65
+ * model list without applying.
66
+ */
67
+ export declare function EffortPanel({ row, current, select, back }: {
68
+ /** The model row whose advertised levels this stage lists. */
69
+ row: ModelRow;
70
+ /** Effective effort currently in force ('' when none), for the ● mark. */
71
+ current: string | undefined;
72
+ /** Accept one advertised effort id, or '' for the provider default. */
73
+ select(effortId: string): void;
74
+ /** Return to the model list without applying. */
75
+ back(): void;
76
+ }): ReactElement;
@@ -1,12 +1,17 @@
1
1
  /**
2
- * Workspace @mention support: file candidates from a bounded async scan of
3
- * the session cwd, session candidates from the opt-in `sessionReferenceResolver`
4
- * service, and submission preparation through its `prepare()` API. Picked
5
- * session mentions land as canonical `@[label](dsh-session:…)` tokens; on
6
- * submit the text is parsed back into readable `@label` text plus structured
7
- * references, snapshots are injected via `agent.inject()` before the readable
8
- * message wakes the driver (`followup` idle, `steer` running) exactly the
9
- * upstream README's wiring.
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
9
+ * (`followup` idle, `steer` running) — exactly the upstream README's wiring.
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.
10
15
  *
11
16
  * @module @deepseek-ai/dsh-code/mentions
12
17
  */
@@ -40,11 +45,17 @@ export interface PreparedMention {
40
45
  /** Aggregated snapshot for `agent.inject()`, undefined without references. */
41
46
  additionalContext?: import('@deepseek-ai/dsh-session').UserMessage;
42
47
  }
43
- /** Bounded async BFS scan of a workspace; unreadable entries are skipped. */
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
+ */
44
55
  export declare function scanWorkspaceFiles(root: string, signal?: AbortSignal): Promise<readonly FileCandidate[]>;
45
56
  /** The mention API the input editor and the runner share. */
46
57
  export interface MentionsApi {
47
- /** Scanned workspace files, cached across one session. */
58
+ /** Scanned workspace files and directories, cached across one session. */
48
59
  files(): Promise<readonly FileCandidate[]>;
49
60
  /** Ranked menu candidates for the typed `@` query. */
50
61
  candidates(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
@@ -61,10 +72,16 @@ export interface MentionsApi {
61
72
  /**
62
73
  * Create the mention API for one agent's workspace. A missing
63
74
  * session-reference service degrades to file mentions only (the scan still
64
- * works); `prepare` then passes text through untouched.
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.
78
+ *
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.
65
82
  * @param ctx - context carrying the optional `sessionReferenceResolver`.
66
83
  * @param agent - the session owner; excluded from its own candidates.
67
84
  * @param cwd - workspace root to scan.
68
85
  */
69
- export declare function createMentions(ctx: Context, agent: Agent, cwd: string): MentionsApi;
86
+ export declare function createMentions(ctx: Context, agent: Agent | undefined, cwd: string): MentionsApi;
70
87
  export {};
@@ -8,6 +8,24 @@
8
8
  * @module @deepseek-ai/dsh-tui/models
9
9
  */
10
10
  import type { Context } from '@deepseek-ai/cordis';
11
+ import type { ModelSelection } from '@deepseek-ai/dsh-agent';
12
+ import { type LlmModelReasoningInfo } from '@deepseek-ai/dsh-llm';
13
+ /** Display metadata for one adapter-owned reasoning effort (mirrors `LlmReasoningEffortInfo`). */
14
+ export interface ModelReasoningEffort {
15
+ /** Opaque value accepted by the model's `GenerateOptions.reasoningEffort`. */
16
+ id: string;
17
+ /** Human-readable effort name for selectors. */
18
+ name: string;
19
+ /** Optional user-facing distinction from otherwise similar efforts. */
20
+ description?: string;
21
+ }
22
+ /** Selectable reasoning efforts for one model (mirrors `LlmModelReasoningInfo`). */
23
+ export interface ModelReasoning {
24
+ /** Supported efforts in adapter-preferred display order. */
25
+ efforts: readonly ModelReasoningEffort[];
26
+ /** Adapter-configured default materialized when callers omit an effort. */
27
+ defaultEffort?: string;
28
+ }
11
29
  /** One selectable row in the `/model` panel. */
12
30
  export interface ModelRow {
13
31
  /** Registered provider route. */
@@ -18,6 +36,8 @@ export interface ModelRow {
18
36
  model: string;
19
37
  /** Human-readable model name. */
20
38
  modelName: string;
39
+ /** Adapter-owned selectable reasoning levels when the model exposes any. */
40
+ reasoning?: ModelReasoning;
21
41
  }
22
42
  /** The resolved directory: rows plus per-provider discovery failures. */
23
43
  export interface ModelDirectory {
@@ -25,12 +45,58 @@ export interface ModelDirectory {
25
45
  rows: readonly ModelRow[];
26
46
  /** Provider ids whose model listing failed; those providers contribute no rows. */
27
47
  failures: readonly string[];
48
+ /**
49
+ * `provider/model` labels whose per-model capability lookup failed. Those
50
+ * rows still appear (advisory degrade, mirroring the web catalog), but
51
+ * without an effort picker — a picker caller must not misread the absence
52
+ * as "this model exposes no reasoning" (e.g. deepseek-v4-flash always
53
+ * advertises off/high/max unless thinking is disabled). Optional for
54
+ * callers that shape a directory by hand; {@link loadModelDirectory}
55
+ * always populates it (empty when nothing failed).
56
+ */
57
+ reasoningFailures?: readonly string[];
28
58
  }
59
+ /** Map an adapter's reasoning capability onto the panel's plain-id shape. */
60
+ export declare function mapReasoning(reasoning: LlmModelReasoningInfo): ModelReasoning;
61
+ /**
62
+ * Resolve the effective model selection for one live session, in the
63
+ * documented precedence: the in-process explicit pick, then the session's
64
+ * last `request/header` config, then the deployment default.
65
+ * @param picked - the explicit selection made in this process, when any.
66
+ * @param logged - the last logged request header config, when any.
67
+ * @param defaults - the deployment default selection.
68
+ * @returns the effective selection, carrying a reasoning effort when one is in force.
69
+ */
70
+ export declare function resolveEffectiveSelection(picked: ModelSelection | undefined, logged: {
71
+ provider: string;
72
+ model: string;
73
+ reasoningEffort?: string;
74
+ } | undefined, defaults: ModelSelection): ModelSelection;
75
+ /**
76
+ * Build the selection one `/model` pick applies, rejecting an effort the
77
+ * row does not advertise. The row is the picker's source of truth, so a
78
+ * stale directory cannot smuggle an unsupported effort into the next step
79
+ * (the request pipeline would reject it before network I/O regardless). The
80
+ * empty string is the picker's "provider default" sentinel — an explicit
81
+ * choice to leave the effort to the model's own default, exactly like an
82
+ * absent effort.
83
+ * @param row - the picked model row.
84
+ * @param effortId - the chosen advertised effort, '' or undefined for the model default.
85
+ * @returns the selection the runner records for the next assembled step.
86
+ */
87
+ export declare function buildModelSelection(row: ModelRow, effortId?: string): ModelSelection;
88
+ /** Display label for one applied selection: `provider/model` or `provider/model@effort`. */
89
+ export declare function modelSelectionLabel(selection: ModelSelection): string;
29
90
  /**
30
91
  * Load the selectable model directory from the live `ctx.llm` registry.
31
92
  * Providers are listed synchronously; each provider's models are discovered
32
93
  * with a bounded parallel fan-out whose failures degrade to that provider
33
94
  * contributing no rows (mirrors the web catalog's per-provider failures).
95
+ * Each row's reasoning levels are resolved per exact model like the web
96
+ * catalog (`buildModelCatalog`); a single model's capability lookup failure
97
+ * degrades to that row having no effort picker rather than hiding the model,
98
+ * and the failure rides `reasoningFailures` so the caller can tell "no
99
+ * advertised reasoning" from "capability lookup failed".
34
100
  * @param ctx - context carrying the `llm` service.
35
101
  * @returns the resolved directory; empty rows when `llm` is unavailable.
36
102
  */
@@ -0,0 +1,37 @@
1
+ /** Permission-preset policy for pending and active TUI sessions. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
4
+ /** One selectable permission preset row for the /permission panel. */
5
+ export interface PermissionRow {
6
+ readonly id: string;
7
+ readonly description?: string;
8
+ }
9
+ /** Structural boundary over Harness permission presets; values stay service-owned. */
10
+ export interface PermissionPresetsService {
11
+ readonly names: readonly string[];
12
+ readonly defaultPreset: string;
13
+ resolve(name: string): unknown;
14
+ current(events: readonly SessionEvent[]): string;
15
+ set(session: Session, preset: string): void;
16
+ /** Client presentation metadata for one preset; may reject unknown names. */
17
+ optionOf?(name: string): {
18
+ name: string;
19
+ description?: string;
20
+ } | undefined;
21
+ }
22
+ /** Read the optional Harness service without importing its runtime package. */
23
+ export declare function permissionPresetsFrom(ctx: Context): PermissionPresetsService | undefined;
24
+ /** Effective label for either an active session or the not-yet-created first one. */
25
+ export declare function effectivePermission(service: PermissionPresetsService, session: Session | undefined, pending: string | undefined): string;
26
+ /** Validate a preset and write it only when a durable session already exists. */
27
+ export declare function selectPermission(service: PermissionPresetsService, session: Session | undefined, preset: string): string;
28
+ /** Cycle table order from the active, pending, or configured-default value. */
29
+ export declare function cyclePermission(service: PermissionPresetsService, session: Session | undefined, pending: string | undefined): string;
30
+ /** Materialize a pre-session choice after Harness creates the first session. */
31
+ export declare function applyPendingPermission(service: PermissionPresetsService, session: Session, pending: string | undefined): void;
32
+ /**
33
+ * List every switchable preset for the /permission panel, table order kept.
34
+ * Description lookup failures degrade to an undocumented row, never a failed
35
+ * panel load — `optionOf` rejects names its table no longer knows.
36
+ */
37
+ export declare function listPermissionRows(service: PermissionPresetsService): readonly PermissionRow[];
@@ -26,6 +26,8 @@ export declare function agentPresetsFrom(ctx: Context): AgentPresetsService | un
26
26
  export declare function isBlankSession(events: readonly SessionEvent[]): boolean;
27
27
  /** Latest logged selection wins; legacy sessions deliberately fall back to standard. */
28
28
  export declare function resolvePreset(session: Pick<Session, 'header' | 'events'>): string;
29
+ /** Resolve a pre-session choice, or recompose an active blank Agent. */
30
+ export declare function selectPreset(service: AgentPresetsService, agent: Agent | undefined, presetId: string): Promise<PresetRow>;
29
31
  /** Recompose atomically from the caller's perspective, logging only success. */
30
32
  export declare function switchPreset(service: AgentPresetsService, agent: Agent, presetId: string): Promise<PresetRow>;
31
33
  /** Minimal handle shape used by lifecycle tests without exposing Agent internals. */