@agi-fans/dsh-tui 0.20.0 → 0.22.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 (319) hide show
  1. package/lib/CHANGELOG.md +249 -3
  2. package/lib/chrome/box.d.ts +47 -16
  3. package/lib/chrome/box.js +172 -130
  4. package/lib/chrome/box.js.map +1 -1
  5. package/lib/chrome/code-highlight.d.ts +15 -0
  6. package/lib/chrome/code-highlight.js +60 -4
  7. package/lib/chrome/code-highlight.js.map +1 -1
  8. package/lib/chrome/command-output.d.ts +29 -2
  9. package/lib/chrome/command-output.js +100 -4
  10. package/lib/chrome/command-output.js.map +1 -1
  11. package/lib/chrome/main-screen-renderer.d.ts +18 -0
  12. package/lib/chrome/main-screen-renderer.js +58 -5
  13. package/lib/chrome/main-screen-renderer.js.map +1 -1
  14. package/lib/chrome/markdown.js +30 -10
  15. package/lib/chrome/markdown.js.map +1 -1
  16. package/lib/chrome/relative-time.d.ts +17 -0
  17. package/lib/chrome/relative-time.js +36 -0
  18. package/lib/chrome/relative-time.js.map +1 -1
  19. package/lib/chrome/renderer.d.ts +9 -0
  20. package/lib/chrome/renderer.js +1 -1
  21. package/lib/chrome/renderer.js.map +1 -1
  22. package/lib/chrome/secret-mask.d.ts +35 -0
  23. package/lib/chrome/secret-mask.js +180 -0
  24. package/lib/chrome/secret-mask.js.map +1 -0
  25. package/lib/chrome/status-config.d.ts +161 -51
  26. package/lib/chrome/status-config.js +237 -126
  27. package/lib/chrome/status-config.js.map +1 -1
  28. package/lib/chrome/status-line.d.ts +3 -2
  29. package/lib/chrome/status-line.js +105 -63
  30. package/lib/chrome/status-line.js.map +1 -1
  31. package/lib/chrome/terminal-colors.d.ts +85 -0
  32. package/lib/chrome/terminal-colors.js +230 -0
  33. package/lib/chrome/terminal-colors.js.map +1 -0
  34. package/lib/chrome/terminal-images.d.ts +9 -1
  35. package/lib/chrome/terminal-images.js +27 -4
  36. package/lib/chrome/terminal-images.js.map +1 -1
  37. package/lib/chrome/theme.d.ts +32 -9
  38. package/lib/chrome/theme.js +616 -520
  39. package/lib/chrome/theme.js.map +1 -1
  40. package/lib/chrome/tool-args.d.ts +1 -1
  41. package/lib/chrome/tool-args.js +3 -1
  42. package/lib/chrome/tool-args.js.map +1 -1
  43. package/lib/chrome/tool-renderers.d.ts +2 -0
  44. package/lib/chrome/tool-renderers.js +69 -3
  45. package/lib/chrome/tool-renderers.js.map +1 -1
  46. package/lib/chrome/tools-list.d.ts +1 -4
  47. package/lib/chrome/tools-list.js +1 -41
  48. package/lib/chrome/tools-list.js.map +1 -1
  49. package/lib/chrome/welcome-tips.d.ts +20 -7
  50. package/lib/chrome/welcome-tips.js +22 -41
  51. package/lib/chrome/welcome-tips.js.map +1 -1
  52. package/lib/commands/auth-ids.d.ts +25 -0
  53. package/lib/commands/auth-ids.js +27 -0
  54. package/lib/commands/auth-ids.js.map +1 -0
  55. package/lib/commands/auth-input.d.ts +34 -0
  56. package/lib/commands/auth-input.js +96 -0
  57. package/lib/commands/auth-input.js.map +1 -0
  58. package/lib/commands/auth-providers.d.ts +39 -0
  59. package/lib/commands/auth-providers.js +125 -0
  60. package/lib/commands/auth-providers.js.map +1 -0
  61. package/lib/commands/auth.d.ts +16 -22
  62. package/lib/commands/auth.js +346 -299
  63. package/lib/commands/auth.js.map +1 -1
  64. package/lib/commands/btw.d.ts +27 -0
  65. package/lib/commands/btw.js +59 -0
  66. package/lib/commands/btw.js.map +1 -0
  67. package/lib/commands/diff.js +30 -5
  68. package/lib/commands/diff.js.map +1 -1
  69. package/lib/commands/export.d.ts +1 -1
  70. package/lib/commands/export.js +5 -3
  71. package/lib/commands/export.js.map +1 -1
  72. package/lib/commands/files.d.ts +17 -1
  73. package/lib/commands/files.js +69 -61
  74. package/lib/commands/files.js.map +1 -1
  75. package/lib/commands/integrations.js +18 -6
  76. package/lib/commands/integrations.js.map +1 -1
  77. package/lib/commands/jobs.d.ts +19 -4
  78. package/lib/commands/jobs.js +50 -7
  79. package/lib/commands/jobs.js.map +1 -1
  80. package/lib/commands/model.js +109 -76
  81. package/lib/commands/model.js.map +1 -1
  82. package/lib/commands/permission.js +46 -13
  83. package/lib/commands/permission.js.map +1 -1
  84. package/lib/commands/plugins.js +196 -42
  85. package/lib/commands/plugins.js.map +1 -1
  86. package/lib/commands/schedule.d.ts +29 -0
  87. package/lib/commands/schedule.js +228 -0
  88. package/lib/commands/schedule.js.map +1 -0
  89. package/lib/commands/session-configuration.d.ts +1 -1
  90. package/lib/commands/session-configuration.js +29 -28
  91. package/lib/commands/session-configuration.js.map +1 -1
  92. package/lib/commands/session.js +75 -80
  93. package/lib/commands/session.js.map +1 -1
  94. package/lib/commands/terminal.d.ts +24 -4
  95. package/lib/commands/terminal.js +38 -94
  96. package/lib/commands/terminal.js.map +1 -1
  97. package/lib/commands/trajectory.js +4 -1
  98. package/lib/commands/trajectory.js.map +1 -1
  99. package/lib/definition.d.ts +150 -11
  100. package/lib/index.d.ts +1 -0
  101. package/lib/index.js +1 -0
  102. package/lib/index.js.map +1 -1
  103. package/lib/input/actions.d.ts +51 -0
  104. package/lib/input/actions.js +120 -0
  105. package/lib/input/actions.js.map +1 -0
  106. package/lib/input/editor-discovery.js +2 -2
  107. package/lib/input/editor-discovery.js.map +1 -1
  108. package/lib/input/editor.d.ts +0 -10
  109. package/lib/input/editor.js +11 -9
  110. package/lib/input/editor.js.map +1 -1
  111. package/lib/input/image-paste.d.ts +19 -1
  112. package/lib/input/image-paste.js +82 -1
  113. package/lib/input/image-paste.js.map +1 -1
  114. package/lib/input/keybindings-config.d.ts +15 -4
  115. package/lib/input/keybindings-config.js +57 -35
  116. package/lib/input/keybindings-config.js.map +1 -1
  117. package/lib/input/keys.d.ts +18 -0
  118. package/lib/input/keys.js +48 -3
  119. package/lib/input/keys.js.map +1 -1
  120. package/lib/input/wheel-scroll.d.ts +46 -0
  121. package/lib/input/wheel-scroll.js +69 -0
  122. package/lib/input/wheel-scroll.js.map +1 -0
  123. package/lib/prompt-intents.d.ts +182 -0
  124. package/lib/prompt-intents.js +127 -0
  125. package/lib/prompt-intents.js.map +1 -0
  126. package/lib/runtime/agent-profile.d.ts +5 -0
  127. package/lib/runtime/agent-profile.js +12 -0
  128. package/lib/runtime/agent-profile.js.map +1 -1
  129. package/lib/runtime/approval-rules.d.ts +59 -0
  130. package/lib/runtime/approval-rules.js +179 -0
  131. package/lib/runtime/approval-rules.js.map +1 -0
  132. package/lib/runtime/btw-policy.d.ts +7 -0
  133. package/lib/runtime/btw-policy.js +18 -0
  134. package/lib/runtime/btw-policy.js.map +1 -0
  135. package/lib/runtime/btw.d.ts +47 -0
  136. package/lib/runtime/btw.js +200 -0
  137. package/lib/runtime/btw.js.map +1 -0
  138. package/lib/runtime/cancellable.d.ts +75 -0
  139. package/lib/runtime/cancellable.js +76 -0
  140. package/lib/runtime/cancellable.js.map +1 -0
  141. package/lib/runtime/composer-images.js +5 -4
  142. package/lib/runtime/composer-images.js.map +1 -1
  143. package/lib/runtime/composer-shell.d.ts +30 -0
  144. package/lib/runtime/composer-shell.js +150 -0
  145. package/lib/runtime/composer-shell.js.map +1 -0
  146. package/lib/runtime/file-recovery-local.d.ts +73 -0
  147. package/lib/runtime/file-recovery-local.js +351 -0
  148. package/lib/runtime/file-recovery-local.js.map +1 -0
  149. package/lib/runtime/file-recovery-recording.d.ts +23 -0
  150. package/lib/runtime/file-recovery-recording.js +131 -0
  151. package/lib/runtime/file-recovery-recording.js.map +1 -0
  152. package/lib/runtime/file-recovery-review.d.ts +3 -0
  153. package/lib/runtime/file-recovery-review.js +130 -0
  154. package/lib/runtime/file-recovery-review.js.map +1 -0
  155. package/lib/runtime/file-recovery-store.d.ts +83 -0
  156. package/lib/runtime/file-recovery-store.js +329 -0
  157. package/lib/runtime/file-recovery-store.js.map +1 -0
  158. package/lib/runtime/file-recovery-worker.d.ts +22 -0
  159. package/lib/runtime/file-recovery-worker.js +153 -0
  160. package/lib/runtime/file-recovery-worker.js.map +1 -0
  161. package/lib/runtime/file-review.d.ts +6 -3
  162. package/lib/runtime/file-review.js +44 -7
  163. package/lib/runtime/file-review.js.map +1 -1
  164. package/lib/runtime/herdr-agent.d.ts +4 -13
  165. package/lib/runtime/herdr-agent.js +19 -63
  166. package/lib/runtime/herdr-agent.js.map +1 -1
  167. package/lib/runtime/plain-tui.d.ts +2 -0
  168. package/lib/runtime/plain-tui.js +7 -1
  169. package/lib/runtime/plain-tui.js.map +1 -1
  170. package/lib/runtime/plugin-settings.d.ts +25 -2
  171. package/lib/runtime/plugin-settings.js +329 -31
  172. package/lib/runtime/plugin-settings.js.map +1 -1
  173. package/lib/runtime/provider-local.d.ts +81 -24
  174. package/lib/runtime/provider-local.js +1807 -608
  175. package/lib/runtime/provider-local.js.map +1 -1
  176. package/lib/runtime/runner.js +52 -12
  177. package/lib/runtime/runner.js.map +1 -1
  178. package/lib/runtime/session-archive.d.ts +6 -0
  179. package/lib/runtime/session-archive.js +129 -3
  180. package/lib/runtime/session-archive.js.map +1 -1
  181. package/lib/runtime/session-runtime.js +1 -1
  182. package/lib/runtime/session-runtime.js.map +1 -1
  183. package/lib/runtime/settings-preferences.d.ts +5 -0
  184. package/lib/runtime/settings-preferences.js +34 -0
  185. package/lib/runtime/settings-preferences.js.map +1 -0
  186. package/lib/runtime/startup-notices.js +2 -0
  187. package/lib/runtime/startup-notices.js.map +1 -1
  188. package/lib/runtime/terminal-modes.d.ts +47 -0
  189. package/lib/runtime/terminal-modes.js +86 -0
  190. package/lib/runtime/terminal-modes.js.map +1 -0
  191. package/lib/runtime/terminal-notifications.d.ts +3 -5
  192. package/lib/runtime/terminal-notifications.js +4 -8
  193. package/lib/runtime/terminal-notifications.js.map +1 -1
  194. package/lib/runtime/terminal-probe.d.ts +36 -0
  195. package/lib/runtime/terminal-probe.js +37 -0
  196. package/lib/runtime/terminal-probe.js.map +1 -0
  197. package/lib/runtime/terminal-session.d.ts +57 -0
  198. package/lib/runtime/terminal-session.js +164 -0
  199. package/lib/runtime/terminal-session.js.map +1 -0
  200. package/lib/runtime/terminal-state.d.ts +29 -0
  201. package/lib/runtime/terminal-state.js +62 -0
  202. package/lib/runtime/terminal-state.js.map +1 -0
  203. package/lib/runtime/tool-presentation.js +50 -14
  204. package/lib/runtime/tool-presentation.js.map +1 -1
  205. package/lib/session/external-agents.d.ts +5 -0
  206. package/lib/session/external-agents.js +27 -0
  207. package/lib/session/external-agents.js.map +1 -0
  208. package/lib/session/feature-toggles.d.ts +1 -1
  209. package/lib/session/feature-toggles.js +1 -1
  210. package/lib/session/interaction-adapter.d.ts +1 -6
  211. package/lib/session/interaction-adapter.js +95 -18
  212. package/lib/session/interaction-adapter.js.map +1 -1
  213. package/lib/session/message-queue.js +1 -1
  214. package/lib/session/message-queue.js.map +1 -1
  215. package/lib/session/model-readiness.d.ts +16 -0
  216. package/lib/session/model-readiness.js +43 -0
  217. package/lib/session/model-readiness.js.map +1 -0
  218. package/lib/session/package-metadata.d.ts +8 -0
  219. package/lib/session/package-metadata.js +8 -0
  220. package/lib/session/package-metadata.js.map +1 -1
  221. package/lib/session/project-context.d.ts +5 -0
  222. package/lib/session/project-context.js +36 -0
  223. package/lib/session/project-context.js.map +1 -1
  224. package/lib/session/session-controller.d.ts +10 -13
  225. package/lib/session/session-controller.js +183 -98
  226. package/lib/session/session-controller.js.map +1 -1
  227. package/lib/session/session-library.js +2 -0
  228. package/lib/session/session-library.js.map +1 -1
  229. package/lib/session/session-tree.d.ts +3 -0
  230. package/lib/session/session-tree.js +3 -1
  231. package/lib/session/session-tree.js.map +1 -1
  232. package/lib/session/subagent-roster.d.ts +2 -1
  233. package/lib/session/subagent-roster.js +24 -3
  234. package/lib/session/subagent-roster.js.map +1 -1
  235. package/lib/session/subagent-tracker.d.ts +6 -2
  236. package/lib/session/subagent-tracker.js +49 -2
  237. package/lib/session/subagent-tracker.js.map +1 -1
  238. package/lib/session/tui-settings.d.ts +47 -13
  239. package/lib/session/tui-settings.js +76 -34
  240. package/lib/session/tui-settings.js.map +1 -1
  241. package/lib/session/working-directory.d.ts +4 -0
  242. package/lib/session/working-directory.js +4 -0
  243. package/lib/session/working-directory.js.map +1 -0
  244. package/lib/session/worktrees.d.ts +13 -0
  245. package/lib/session/worktrees.js +49 -0
  246. package/lib/session/worktrees.js.map +1 -0
  247. package/lib/views/approval-rule.d.ts +3 -0
  248. package/lib/views/approval-rule.js +4 -0
  249. package/lib/views/approval-rule.js.map +1 -0
  250. package/lib/views/at-complete.d.ts +8 -0
  251. package/lib/views/at-complete.js +64 -2
  252. package/lib/views/at-complete.js.map +1 -1
  253. package/lib/views/autocomplete.d.ts +22 -2
  254. package/lib/views/autocomplete.js +70 -40
  255. package/lib/views/autocomplete.js.map +1 -1
  256. package/lib/views/copy-targets.d.ts +13 -14
  257. package/lib/views/copy-targets.js +42 -54
  258. package/lib/views/copy-targets.js.map +1 -1
  259. package/lib/views/document-reader.js +18 -5
  260. package/lib/views/document-reader.js.map +1 -1
  261. package/lib/views/event-views.d.ts +1 -1
  262. package/lib/views/event-views.js +60 -6
  263. package/lib/views/event-views.js.map +1 -1
  264. package/lib/views/file-actions.d.ts +12 -0
  265. package/lib/views/file-actions.js +72 -0
  266. package/lib/views/file-actions.js.map +1 -0
  267. package/lib/views/history-search.js +15 -21
  268. package/lib/views/history-search.js.map +1 -1
  269. package/lib/views/hotkeys.d.ts +8 -1
  270. package/lib/views/hotkeys.js +53 -26
  271. package/lib/views/hotkeys.js.map +1 -1
  272. package/lib/views/list-navigation.d.ts +62 -0
  273. package/lib/views/list-navigation.js +73 -0
  274. package/lib/views/list-navigation.js.map +1 -0
  275. package/lib/views/list-view.d.ts +99 -0
  276. package/lib/views/list-view.js +165 -0
  277. package/lib/views/list-view.js.map +1 -0
  278. package/lib/views/prompt-selector.d.ts +34 -4
  279. package/lib/views/prompt-selector.js +270 -134
  280. package/lib/views/prompt-selector.js.map +1 -1
  281. package/lib/views/session-files.d.ts +1 -2
  282. package/lib/views/session-files.js +1 -11
  283. package/lib/views/session-files.js.map +1 -1
  284. package/lib/views/settings-list.d.ts +51 -10
  285. package/lib/views/settings-list.js +267 -315
  286. package/lib/views/settings-list.js.map +1 -1
  287. package/lib/views/trajectory-timeline.d.ts +72 -0
  288. package/lib/views/trajectory-timeline.js +133 -0
  289. package/lib/views/trajectory-timeline.js.map +1 -0
  290. package/lib/views/trajectory.d.ts +15 -4
  291. package/lib/views/trajectory.js +557 -123
  292. package/lib/views/trajectory.js.map +1 -1
  293. package/lib/views/transcript-export.js +9 -0
  294. package/lib/views/transcript-export.js.map +1 -1
  295. package/lib/views/transcript-render.d.ts +97 -25
  296. package/lib/views/transcript-render.js +542 -170
  297. package/lib/views/transcript-render.js.map +1 -1
  298. package/lib/views/transcript-search.d.ts +39 -6
  299. package/lib/views/transcript-search.js +46 -11
  300. package/lib/views/transcript-search.js.map +1 -1
  301. package/lib/views/transcript-types.d.ts +63 -5
  302. package/lib/views/transcript-types.js +23 -11
  303. package/lib/views/transcript-types.js.map +1 -1
  304. package/package.json +95 -65
  305. package/lib/commands/loop.d.ts +0 -52
  306. package/lib/commands/loop.js +0 -307
  307. package/lib/commands/loop.js.map +0 -1
  308. package/lib/commands/steer.d.ts +0 -5
  309. package/lib/commands/steer.js +0 -24
  310. package/lib/commands/steer.js.map +0 -1
  311. package/lib/runtime/terminal-reader.d.ts +0 -11
  312. package/lib/runtime/terminal-reader.js +0 -87
  313. package/lib/runtime/terminal-reader.js.map +0 -1
  314. package/lib/views/agent-hub.d.ts +0 -32
  315. package/lib/views/agent-hub.js +0 -206
  316. package/lib/views/agent-hub.js.map +0 -1
  317. package/lib/views/copy-selector.d.ts +0 -51
  318. package/lib/views/copy-selector.js +0 -139
  319. package/lib/views/copy-selector.js.map +0 -1
@@ -101,7 +101,13 @@ export class PlainTui {
101
101
  let out = '';
102
102
  for (const block of blocks.slice(this.#printed)) {
103
103
  // Pipe / CI output is not a viewport: print the full tool body.
104
- for (const line of blockLines(block, theme, width, { toolsExpanded: true, mathMode: this.#deps.mathMode?.() ?? 'auto', mermaidMode: this.#deps.mermaidMode?.() ?? 'auto' }))
104
+ for (const line of blockLines(block, theme, width, {
105
+ toolsExpanded: true,
106
+ mathMode: this.#deps.mathMode?.() ?? 'auto',
107
+ mermaidMode: this.#deps.mermaidMode?.() ?? 'auto',
108
+ hideThinking: this.#deps.hideThinking?.() === true,
109
+ thinkingOpen: this.#deps.hideThinking?.() !== true,
110
+ }))
105
111
  out += line + '\n';
106
112
  }
107
113
  this.#printed = blocks.length;
@@ -1 +1 @@
1
- {"version":3,"file":"plain-tui.js","sourceRoot":"","sources":["../../src/runtime/plain-tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plain-tui.js","sourceRoot":"","sources":["../../src/runtime/plain-tui.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,21 @@
1
1
  /** Schema-driven configuration using path edits and revision-checked writes. */
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
+ import type { BundleInfo } from '@deepseek-ai/dsh-plugin-manager';
3
4
  import z from '@deepseek-ai/schemastery';
4
5
  import type { PluginSettingsEntry } from '../views/settings-list.ts';
6
+ import type { TuiService } from '../definition.ts';
7
+ interface ProductForm {
8
+ label: string;
9
+ section: NonNullable<PluginSettingsEntry['section']>;
10
+ fields: Record<string, string>;
11
+ inline?: string;
12
+ effect?: string;
13
+ }
14
+ export declare function nativeIntegrationId(bundle: string): string | undefined;
15
+ /** A failed user action needs acknowledgement before its covering form reopens. */
16
+ export declare function showSettingsError(tui: Pick<TuiService, 'notice' | 'prompt'>, detail: string, signal?: AbortSignal): Promise<void>;
17
+ /** Diagnostics are a result: they go to the transcript, folded when long. */
18
+ export declare function showIntegrationStatus(ctx: Context, bundle: string, signal: AbortSignal): Promise<void>;
5
19
  export interface ConfigField {
6
20
  path: string[];
7
21
  schema: z;
@@ -13,5 +27,14 @@ export interface ConfigField {
13
27
  export declare function configFields(schema: z, path?: string[]): ConfigField[];
14
28
  export declare function configValue(value: unknown, path: readonly string[]): unknown;
15
29
  export declare function parseConfigValue(field: ConfigField, text: string): unknown;
16
- export declare function pluginSettingsEntries(ctx: Context): PluginSettingsEntry[];
17
- export declare function editPluginSettings(ctx: Context, ns: string, signal: AbortSignal): Promise<void>;
30
+ /** Human units are presentation only; owner schemas still validate the stored numbers. */
31
+ export declare function fieldDisplay(field: ConfigField, value: unknown): string;
32
+ export declare function pluginSettingsEntries(ctx: Context, bundles?: readonly BundleInfo[]): PluginSettingsEntry[];
33
+ /** Write the displayed snapshot's revision; a concurrent change must never be silently overwritten. */
34
+ export declare function setSettingsEntry(ctx: Context, entry: PluginSettingsEntry, choice: string, signal: AbortSignal): Promise<void>;
35
+ /** Route a product row to its original owner; installation remains in /plugins. */
36
+ export declare function editSettingsEntry(ctx: Context, id: string, signal: AbortSignal, resetOnly?: boolean): Promise<void>;
37
+ /** Restore only the chosen owner fields, fenced to the configuration shown by the confirmation. */
38
+ export declare function resetSettingsControl(ctx: Context, id: string, entry: PluginSettingsEntry | undefined, signal: AbortSignal, label?: string): Promise<void>;
39
+ export declare function editPluginSettings(ctx: Context, ns: string, signal: AbortSignal, allowedFields?: readonly string[], presentation?: Pick<ProductForm, 'label' | 'fields'>, resetOnly?: boolean): Promise<void>;
40
+ export {};
@@ -2,6 +2,67 @@
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { credentialRef } from '@deepseek-ai/dsh-credentials';
4
4
  import z from '@deepseek-ai/schemastery';
5
+ import { externalAgentDiagnostic, externalAgentGuide } from "../session/external-agents.js";
6
+ import { preferencePaths } from "./settings-preferences.js";
7
+ import { resolveStatusBarConfig } from "../chrome/status-config.js";
8
+ const SHELL_FORM = { label: 'Shell', section: 'tools', fields: { timeoutMs: 'Default timeout', maxTimeoutMs: 'Maximum timeout', maxOutputBytes: 'Output limit', maxSpillBytes: 'Retained output limit', graceMs: 'Shutdown grace' } };
9
+ // Presentation only: values, validation, inheritance and writes belong to each live schema.
10
+ const PRODUCT_FORMS = {
11
+ 'agent-loop': { label: 'Tool parallelism', section: 'tools', fields: { maxParallelToolCalls: 'Parallel tool calls' } },
12
+ subagent: { label: 'Subagents', section: 'agent', fields: { maxDepth: 'Delegation depth', maxActiveSubagents: 'Concurrent subagents' } },
13
+ 'subagent-model-selection-settings': { label: 'Subagent models', section: 'agent', fields: { enabled: 'Model selection', allowedModels: 'Allowed models' }, inline: 'enabled', effect: 'Applies to new sessions; existing sessions keep their routes.' },
14
+ bash: SHELL_FORM,
15
+ pwsh: SHELL_FORM,
16
+ 'file-recovery': { label: 'File recovery snapshots', section: 'tools', fields: { enabled: 'Snapshots' }, inline: 'enabled', effect: 'Keeps private file contents. Changes apply to the next Turn; existing snapshots remain readable.' },
17
+ 'hook-settings': { label: 'Command hooks', section: 'integrations', fields: { bridge: 'Hook compatibility', configPath: 'Configuration file' }, inline: 'bridge' },
18
+ web: { label: 'Web providers', section: 'integrations', fields: { searchProvider: 'Search provider', fetchProvider: 'Fetch provider' },
19
+ effect: 'Default: deepseek-search-api and deepseek-fetch-api. Fallbacks: deepseek-official (Messages) and http (local). With more than one provider installed, an empty value is ambiguous.' },
20
+ 'web-deepseek-api': { label: 'Web search · API', section: 'integrations',
21
+ fields: { apiKey: 'API key', apiKeyEnv: 'API key reference', baseURL: 'Endpoint', count: 'Results per search', sendOriginalQuestion: 'Send original question' },
22
+ effect: 'Default search and fetch provider: one direct DeepSeek API request without an auxiliary model turn. /login stores the official key under this reference.' },
23
+ 'web-search-deepseek': { label: 'Web search · Messages', section: 'integrations',
24
+ fields: { maxTokens: 'Token limit', maxUses: 'Search limit', apiKey: 'API key', apiKeyEnv: 'API key reference', baseURL: 'Endpoint', model: 'Model', apiVersion: 'API version' },
25
+ effect: 'Fallback search provider: one auxiliary Messages turn. Select it under Web providers.' },
26
+ };
27
+ // Already represented by dedicated controls, or accepted solely for migration.
28
+ const DEDICATED_FIELDS = {
29
+ tui: ['theme', 'colors', 'motion', 'mathMode', 'mermaidMode', 'editor', 'terminalTitle', 'terminalState', 'pasteProtection', 'copyOnSelect', 'mouseInteraction', 'checkUpdates', 'startupChangelog', 'notifications', 'notificationFocus', 'notificationThreshold', 'statusBar', 'foldDensity', 'expandTools', 'statusPreset'],
30
+ 'agent-behavior': ['language'],
31
+ // A deployment without the working-directory service may still set these in
32
+ // its profile; normal sessions always run in the Session's current directory.
33
+ bash: ['cwd'],
34
+ pwsh: ['cwd'],
35
+ 'subagent-codex': ['model', 'permissionMode', 'env'],
36
+ 'subagent-claude-code': ['model', 'permissionMode', 'env'],
37
+ };
38
+ const NATIVE_INTEGRATIONS = [
39
+ { id: 'integration:codex', label: 'Codex', bundle: '@deepseek-ai/dsh-subagent-codex' },
40
+ { id: 'integration:claude', label: 'Claude Code', bundle: '@deepseek-ai/dsh-subagent-claude-code' },
41
+ ];
42
+ export function nativeIntegrationId(bundle) {
43
+ return NATIVE_INTEGRATIONS.find(item => item.bundle === bundle)?.id;
44
+ }
45
+ /** A failed user action needs acknowledgement before its covering form reopens. */
46
+ export async function showSettingsError(tui, detail, signal) {
47
+ if (signal?.aborted)
48
+ return;
49
+ tui.notice(detail, { level: 'error' });
50
+ await tui.prompt({ title: 'Settings', question: 'Could not change configuration', detail,
51
+ options: [{ label: 'Return to settings' }], allowCustom: false, notify: false,
52
+ ...(signal === undefined ? {} : { signal }) });
53
+ }
54
+ /** Diagnostics are a result: they go to the transcript, folded when long. */
55
+ export async function showIntegrationStatus(ctx, bundle, signal) {
56
+ const manager = ctx.get('pluginManager');
57
+ if (manager === undefined || signal.aborted)
58
+ return;
59
+ const row = (await manager.listBundles()).find(row => row.name === bundle);
60
+ const text = row === undefined ? 'This integration is no longer registered in this profile.'
61
+ : externalAgentDiagnostic(row, await manager.listPlugins());
62
+ if (signal.aborted)
63
+ return;
64
+ ctx.tui.commandOutput(`${NATIVE_INTEGRATIONS.find(item => item.bundle === bundle)?.label ?? bundle} · Status`, text);
65
+ }
5
66
  function containsSecret(schema, seen = new Set()) {
6
67
  if (seen.has(schema))
7
68
  return false;
@@ -11,7 +72,7 @@ function containsSecret(schema, seen = new Set()) {
11
72
  }
12
73
  /** Flatten ordinary objects while retaining arrays and unions as atomic fields. */
13
74
  export function configFields(schema, path = []) {
14
- if (schema.meta.hidden || schema.meta.disabled)
75
+ if (schema.meta.hidden || schema.meta.disabled || schema.meta.badges?.some(badge => badge.text === 'deprecated'))
15
76
  return [];
16
77
  if (schema.type === 'object')
17
78
  return Object.entries(schema.dict ?? {}).flatMap(([key, child]) => configFields(child, [...path, key]));
@@ -45,12 +106,12 @@ function credentialPath(field, fields) {
45
106
  const path = [...field.path.slice(0, -1), 'apiKeyEnv'];
46
107
  return field.secret && field.path.at(-1) === 'apiKey' && fields.some(item => item.path.join('.') === path.join('.')) ? path : undefined;
47
108
  }
48
- function fieldDescription(section, field, fields) {
109
+ function fieldDescription(section, field, fields, human = false) {
49
110
  const refPath = credentialPath(field, fields);
50
111
  const reference = refPath === undefined ? undefined : configValue(section.value, refPath);
51
112
  const inline = section.secrets?.find(secret => secret.path.join('.') === field.path.join('.'))?.set;
52
113
  const value = field.secret ? `secret · ${inline ? 'configured' : typeof reference === 'string' && reference !== '' ? 'credential reference configured' : 'not set'}`
53
- : field.protected ? 'protected · use /auth or the owner configuration' : display(configValue(section.value, field.path));
114
+ : field.protected ? 'protected · use /login or the owner configuration' : human ? fieldDisplay(field, configValue(section.value, field.path)) : display(configValue(section.value, field.path));
54
115
  const override = hasOverride(section.user, field.path) || refPath !== undefined && hasOverride(section.user, refPath);
55
116
  const help = field.schema.meta.description;
56
117
  return `${value} · ${override ? 'profile override' : 'inherited'}${typeof help === 'string' && help !== '' ? ` · ${help}` : ''}`;
@@ -60,39 +121,221 @@ export function parseConfigValue(field, text) {
60
121
  field.schema(value);
61
122
  return value;
62
123
  }
63
- const list = { presentation: 'fullscreen-list', optionLayout: 'compact', filterable: true, allowCustom: false };
64
- export function pluginSettingsEntries(ctx) {
65
- return ctx.settings.describe({ redactSecrets: true }).flatMap(section => {
124
+ const list = { presentation: 'card', filterable: true, allowCustom: false, notify: false };
125
+ function constantValues(field) {
126
+ return field.schema.type === 'boolean' ? [true, false] : field.schema.type === 'union'
127
+ && field.schema.list?.every(child => child.type === 'const') ? field.schema.list.map(child => child.value) : undefined;
128
+ }
129
+ function choiceLabel(value) {
130
+ return typeof value === 'boolean' ? value ? 'On' : 'Off' : display(value);
131
+ }
132
+ /** Human units are presentation only; owner schemas still validate the stored numbers. */
133
+ export function fieldDisplay(field, value) {
134
+ if (value === undefined || value === null)
135
+ return '—';
136
+ if (typeof value === 'boolean')
137
+ return choiceLabel(value);
138
+ if (typeof value !== 'number')
139
+ return display(value);
140
+ const name = field.path.at(-1) ?? '';
141
+ if (name.endsWith('Ms'))
142
+ return value !== 0 && value % 60_000 === 0 ? `${value / 60_000}m` : value !== 0 && value % 1000 === 0 ? `${value / 1000}s` : `${value}ms`;
143
+ if (name.endsWith('Bytes')) {
144
+ for (const [unit, factor] of [['GiB', 1024 ** 3], ['MiB', 1024 ** 2], ['KiB', 1024]])
145
+ if (value !== 0 && value % factor === 0)
146
+ return `${value / factor} ${unit}`;
147
+ return `${value} B`;
148
+ }
149
+ return display(value);
150
+ }
151
+ function parseProductValue(field, text) {
152
+ if (field.schema.type === 'number') {
153
+ const match = /^\s*(\d+(?:\.\d+)?)\s*([a-z]+)\s*$/iu.exec(text);
154
+ const factors = (field.path.at(-1) ?? '').endsWith('Ms') ? { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }
155
+ : (field.path.at(-1) ?? '').endsWith('Bytes') ? { b: 1, kb: 1000, mb: 1000 ** 2, gb: 1000 ** 3, kib: 1024, mib: 1024 ** 2, gib: 1024 ** 3 } : {};
156
+ const factor = match === null ? undefined : factors[match[2].toLowerCase()];
157
+ if (match !== null && factor !== undefined) {
158
+ const value = Number(match[1]) * factor;
159
+ field.schema(value);
160
+ return value;
161
+ }
162
+ }
163
+ return parseConfigValue(field, text);
164
+ }
165
+ export function pluginSettingsEntries(ctx, bundles) {
166
+ const entries = ctx.settings.describe({ redactSecrets: true }).flatMap(section => {
66
167
  const fields = configFields(new z(section.schema));
67
- const label = String(section.ns).replace(/[-_]+/gu, ' ').replace(/^./u, first => first.toUpperCase());
68
- return fields.length === 0 ? [] : [{ id: String(section.ns), label,
69
- description: `${section.ns} · ${fields.length} fields · ${ctx.settings.writable ? 'Edits apply live to this profile; Esc returns to Settings.' : 'This profile is read-only.'}` }];
168
+ const ns = String(section.ns), form = Object.hasOwn(PRODUCT_FORMS, ns) ? PRODUCT_FORMS[ns] : undefined;
169
+ const paths = fields.map(field => field.path.join('.'));
170
+ const claimed = [...(Object.hasOwn(DEDICATED_FIELDS, ns) ? DEDICATED_FIELDS[ns] ?? [] : []), ...Object.keys(form?.fields ?? {})];
171
+ const owned = paths.filter(path => Object.hasOwn(form?.fields ?? {}, path));
172
+ const advanced = section.autoGenerate === false ? [] : paths.filter(path => !claimed.includes(path));
173
+ const writable = ctx.settings.writable ? 'Edits apply live to this profile.' : 'This profile is read-only.';
174
+ const summary = fields.filter(field => owned.includes(field.path.join('.')) && !field.secret && !field.protected
175
+ && configValue(section.value, field.path) !== undefined).slice(0, 2)
176
+ .map(field => `${form?.fields[field.path.join('.')]}: ${Array.isArray(configValue(section.value, field.path)) ? `${configValue(section.value, field.path).length} selected` : fieldDisplay(field, configValue(section.value, field.path))}`).join(' · ');
177
+ const field = fields.find(field => field.path.join('.') === form?.inline);
178
+ const values = field === undefined || field.secret || field.protected ? undefined : constantValues(field);
179
+ // A field with a schema default is already in effect, so the row shows that
180
+ // value rather than "not set": the reader's choice is what the schema says it
181
+ // is, and an unset override is not a third state.
182
+ const effective = field === undefined ? undefined
183
+ : configValue(section.value, field.path) ?? field.schema.meta.default;
184
+ const inline = field === undefined || values === undefined || values.length === 0 ? undefined : {
185
+ path: field.path, revision: section.revision, value: choiceLabel(effective),
186
+ choices: ctx.settings.writable ? values.map(value => ({ label: choiceLabel(value), value: JSON.stringify(value) })) : [],
187
+ };
188
+ return [
189
+ ...(form === undefined || owned.length === 0 ? [] : [{ id: ns, namespace: ns, label: form.label, section: form.section, fields: owned,
190
+ ...(inline === undefined ? {} : { inline }), summary, keywords: [...owned, ...Object.values(form.fields)],
191
+ description: `${form.effect === undefined ? '' : `${form.effect} `}${field === undefined ? '' : `${fieldDescription(section, field, fields)} · `}${writable}` }]),
192
+ ...(advanced.length === 0 ? [] : [{ id: `advanced:${ns}`, namespace: ns,
193
+ label: String(section.ns).replace(/[-_]+/gu, ' ').replace(/^./u, first => first.toUpperCase()), section: 'advanced', fields: advanced,
194
+ summary: `${advanced.length} fields`, keywords: advanced, description: `${ns} · ${advanced.length} fields · ${writable}` }]),
195
+ ];
70
196
  });
197
+ if (ctx.get('pluginManager') !== undefined)
198
+ entries.push(...NATIVE_INTEGRATIONS.map(item => {
199
+ const bundle = bundles?.find(bundle => bundle.name === item.bundle);
200
+ return { id: item.id, label: item.label, section: 'integrations', fields: [],
201
+ summary: bundles === undefined ? 'Details' : bundle === undefined || bundle.availability === 'missing' ? 'Unavailable' : bundle.error !== undefined ? 'Load error' : bundle.enabled ? 'Enabled' : 'Disabled',
202
+ description: 'Provider model, permissions and profile enablement. Native login has not been checked; Installation status reports registration.' };
203
+ }));
204
+ return entries;
205
+ }
206
+ /** Write the displayed snapshot's revision; a concurrent change must never be silently overwritten. */
207
+ export async function setSettingsEntry(ctx, entry, choice, signal) {
208
+ signal.throwIfAborted();
209
+ const inline = entry.inline;
210
+ if (!ctx.settings.writable)
211
+ throw new Error('This profile is read-only.');
212
+ if (entry.namespace === undefined || inline === undefined || !inline.choices.some(item => item.value === choice))
213
+ throw new Error('This setting is no longer editable. Refresh and retry.');
214
+ const section = ctx.settings.describe({ redactSecrets: true }).find(item => item.ns === entry.namespace);
215
+ if (section === undefined || section.revision !== inline.revision)
216
+ throw new Error('Configuration changed. Refresh and retry.');
217
+ const form = Object.hasOwn(PRODUCT_FORMS, entry.namespace) ? PRODUCT_FORMS[entry.namespace] : undefined;
218
+ const field = configFields(new z(section.schema)).find(field => field.path.join('.') === inline.path.join('.') && field.path.join('.') === form?.inline);
219
+ if (field === undefined || field.secret || field.protected)
220
+ throw new Error('This setting is no longer editable. Refresh and retry.');
221
+ await saveValue(ctx, section, field, parseConfigValue(field, choice));
222
+ ctx.tui.notice(`${form?.fields[field.path.join('.')] ?? entry.label}: saved.`);
223
+ }
224
+ /** Route a product row to its original owner; installation remains in /plugins. */
225
+ export async function editSettingsEntry(ctx, id, signal, resetOnly = false) {
226
+ const native = NATIVE_INTEGRATIONS.find(item => item.id === id);
227
+ if (native !== undefined) {
228
+ const manager = ctx.get('pluginManager');
229
+ if (manager === undefined)
230
+ return;
231
+ while (!signal.aborted) {
232
+ const row = (await manager.listBundles()).find(row => row.name === native.bundle);
233
+ if (row === undefined || row.availability === 'missing') {
234
+ await showSettingsError(ctx.tui, `${native.label} is unavailable. Open /plugins → Bundles to install or repair its package.`, signal);
235
+ return;
236
+ }
237
+ const ns = row.rows.find(item => item.moduleName === row.name)?.rowId;
238
+ const configurable = ns !== undefined && ctx.get('settings')?.describe({ redactSecrets: true }).some(section => section.ns === ns);
239
+ const action = await ctx.tui.prompt({ ...list, title: native.label, question: row.enabled ? 'Enabled' : 'Disabled',
240
+ detail: [row.error === undefined ? undefined : `Load error: ${row.error.diagnostic ?? row.error.code}`, externalAgentGuide(row.name)].filter(Boolean).join('\n\n'), options: [{ label: row.enabled ? 'Disable integration' : 'Enable integration', value: 'toggle' },
241
+ ...(configurable ? [{ label: 'Model', value: 'field:model' }, { label: 'Permissions', value: 'field:permissionMode' }] : []), ...(configurable ? [{ label: 'Restore inherited model or permissions…', value: 'restore' }] : []), { label: 'Installation status', value: 'check' }], notify: false, signal });
242
+ if (action === null || signal.aborted)
243
+ return;
244
+ if (action === 'toggle') {
245
+ const result = await manager.setBundleEnabled(row.name, !row.enabled);
246
+ if (result.error !== undefined || result.application === 'failed')
247
+ await showSettingsError(ctx.tui, result.error?.diagnostic ?? result.error?.code ?? 'Could not change integration.', signal);
248
+ else
249
+ ctx.tui.notice(`${native.label}: ${result.application}${result.enabled === undefined ? '' : result.enabled ? ' · enabled' : ' · disabled'}.`);
250
+ }
251
+ if (action.startsWith('field:') && configurable && ns !== undefined)
252
+ await editPluginSettings(ctx, ns, signal, [action.slice(6)], { label: native.label, fields: { model: 'Model', permissionMode: 'Permissions' } });
253
+ if (action === 'restore' && configurable && ns !== undefined)
254
+ await editPluginSettings(ctx, ns, signal, ['model', 'permissionMode'], { label: native.label, fields: { model: 'Model', permissionMode: 'Permissions' } }, true);
255
+ if (action === 'check')
256
+ await showIntegrationStatus(ctx, row.name, signal);
257
+ }
258
+ return;
259
+ }
260
+ const entry = pluginSettingsEntries(ctx).find(entry => entry.id === id);
261
+ if (entry?.namespace === undefined)
262
+ return;
263
+ await editPluginSettings(ctx, entry.namespace, signal, entry.fields, entry.section === 'advanced' ? undefined : PRODUCT_FORMS[entry.namespace], resetOnly);
264
+ }
265
+ /** Restore only the chosen owner fields, fenced to the configuration shown by the confirmation. */
266
+ export async function resetSettingsControl(ctx, id, entry, signal, label = entry?.label ?? id) {
267
+ if (entry !== undefined && entry.inline === undefined) {
268
+ await editSettingsEntry(ctx, entry.id, signal, true);
269
+ return;
270
+ }
271
+ const ns = entry?.namespace ?? (id === 'agentLanguage' ? 'agent-behavior' : 'tui');
272
+ const paths = entry?.inline === undefined ? id === 'agentLanguage' ? [['language']] : preferencePaths(id) : [[...entry.inline.path]];
273
+ if (paths === undefined || !ctx.settings.writable)
274
+ throw new Error('This setting cannot be restored in this profile.');
275
+ const section = ctx.settings.describe({ redactSecrets: true }).find(section => section.ns === ns);
276
+ if (section === undefined || entry?.inline !== undefined && section.revision !== entry.inline.revision)
277
+ throw new Error('Configuration changed. Refresh and retry.');
278
+ const revision = section.revision;
279
+ const fields = configFields(new z(section.schema));
280
+ const status = resolveStatusBarConfig(configValue(section.base, ['statusBar']), configValue(section.base, ['statusPreset']));
281
+ const inherited = id === 'statusReset' ? `Context: ${status.contextStyle}\nTelemetry: ${status.groups.length} items\nHeader: ${status.meta.length} items` : paths.map(path => {
282
+ let value = configValue(section.base, path);
283
+ if (path[0] === 'statusBar')
284
+ value = configValue(status, path.slice(1));
285
+ const field = fields.find(field => field.path.join('.') === path.join('.'));
286
+ if (value === undefined)
287
+ value = field?.schema.meta.default;
288
+ return `${path[0] === 'statusBar' && path.length === 1 ? 'Layout' : label}: ${display(value)}`;
289
+ }).join('\n');
290
+ const choice = await ctx.tui.prompt({ title: 'Restore inherited setting', question: id.startsWith('statusItem:') ? `${label} color` : label,
291
+ detail: `${inherited}\nOnly these Profile overrides will be removed.`, options: [{ label: 'Restore inherited value', value: 'restore' }], allowCustom: false, notify: false, signal });
292
+ if (choice !== 'restore' || signal.aborted)
293
+ return;
294
+ if (entry?.inline !== undefined) {
295
+ const field = configFields(new z(section.schema)).find(field => field.path.join('.') === entry.inline.path.join('.'));
296
+ if (field === undefined || field.secret || field.protected)
297
+ throw new Error('This setting is no longer editable.');
298
+ validateModelSelection(section, field, configValue(section.base, field.path));
299
+ }
300
+ await ctx.settings.mutate(ns, paths.map(path => ({ op: 'unset', path })), revision);
301
+ ctx.tui.notice('Inherited setting restored.');
71
302
  }
72
- export async function editPluginSettings(ctx, ns, signal) {
303
+ export async function editPluginSettings(ctx, ns, signal, allowedFields, presentation, resetOnly = false) {
304
+ let focusedPath;
305
+ const directField = presentation !== undefined && allowedFields?.length === 1 && ['subagent-codex', 'subagent-claude-code'].includes(ns);
73
306
  while (!signal.aborted) {
74
307
  // Re-read after every write; never carry a stale editor into another field.
75
- const section = ctx.settings.describe({ redactSecrets: true }).find(item => item.ns === ns);
76
- if (section === undefined) {
77
- ctx.tui.notice(`No editable configuration for ${ns}.`, { level: 'error' });
308
+ const descriptor = ctx.settings.describe({ redactSecrets: true }).find(item => item.ns === ns);
309
+ if (descriptor === undefined) {
310
+ await showSettingsError(ctx.tui, `No editable configuration for ${ns}.`, signal);
78
311
  return;
79
312
  }
80
- const fields = configFields(new z(section.schema));
81
- const selected = await ctx.tui.prompt({ ...list, title: String(section.ns), question: 'Choose a field',
82
- options: fields.map((field, index) => ({ label: field.path.join('.'), value: String(index),
83
- description: fieldDescription(section, field, fields) })), signal });
313
+ const section = { ...descriptor };
314
+ const fields = configFields(new z(section.schema)).filter(field => allowedFields === undefined || allowedFields.includes(field.path.join('.')));
315
+ const currentIndex = fields.findIndex(field => field.path.join('.') === focusedPath);
316
+ let selected = directField && !resetOnly ? '0' : await ctx.tui.prompt({ ...list, ...(currentIndex < 0 ? {} : { initialValue: String(currentIndex) }), title: presentation?.label ?? String(section.ns), question: resetOnly ? 'Choose a setting to restore' : 'Choose a setting',
317
+ options: fields.map((field, index) => ({ label: presentation?.fields[field.path.join('.')] ?? field.path.join('.'), value: String(index),
318
+ description: fieldDescription(section, field, fields, presentation !== undefined) })).concat(presentation !== undefined && !resetOnly ? [{ label: 'Restore inherited setting…', value: 'restore', description: 'Remove a selected Profile override.' }] : []), signal });
84
319
  if (selected === null)
85
320
  break;
321
+ const restore = resetOnly || selected === 'restore';
322
+ if (selected === 'restore')
323
+ selected = await ctx.tui.prompt({ ...list, title: presentation?.label ?? ns, question: 'Choose a setting to restore',
324
+ options: fields.map((field, index) => ({ label: presentation?.fields[field.path.join('.')] ?? field.path.join('.'), value: String(index), description: fieldDescription(section, field, fields, presentation !== undefined) })), signal });
325
+ if (selected === null || signal.aborted)
326
+ continue;
86
327
  const field = fields[Number(selected)];
87
328
  if (field === undefined || field.protected)
88
329
  continue;
330
+ focusedPath = field.path.join('.');
89
331
  if (!ctx.settings.writable) {
90
- ctx.tui.notice('This profile is read-only.');
332
+ await showSettingsError(ctx.tui, 'This profile is read-only.', signal);
91
333
  continue;
92
334
  }
93
- const action = await ctx.tui.prompt({ title: field.path.join('.'), question: 'Set a value or restore inheritance',
94
- detail: field.secret ? 'Secret values are never shown. Reset removes the profile value and its paired credential reference; inherited credentials may still apply.' : `Current: ${display(configValue(section.value, field.path))}\nInherited: ${display(configValue(section.base, field.path))}`,
95
- options: [{ label: 'Set value', value: 'set' }, { label: 'Reset to inherited', value: 'reset' }], allowCustom: false, signal });
335
+ const label = presentation?.fields[field.path.join('.')] ?? field.path.join('.');
336
+ const action = presentation !== undefined && !restore ? 'set' : await ctx.tui.prompt({ title: label, question: restore ? 'Restore inherited value?' : 'Set a value or restore inheritance',
337
+ detail: field.secret ? 'Secret values are never shown. Reset removes the profile value and its paired credential reference; inherited credentials may still apply.' : `Current: ${presentation === undefined ? display(configValue(section.value, field.path)) : fieldDisplay(field, configValue(section.value, field.path))}\nInherited: ${presentation === undefined ? display(configValue(section.base, field.path)) : fieldDisplay(field, configValue(section.base, field.path))}`,
338
+ options: [...(restore ? [] : [{ label: 'Set value', value: 'set' }]), { label: 'Reset to inherited', value: 'reset' }], allowCustom: false, notify: false, signal });
96
339
  if (action === null || signal.aborted)
97
340
  continue;
98
341
  try {
@@ -101,31 +344,86 @@ export async function editPluginSettings(ctx, ns, signal) {
101
344
  const refPath = credentialPath(field, fields);
102
345
  await ctx.settings.mutate(ns, [{ op: 'unset', path: field.path }, ...(refPath === undefined ? [] : [{ op: 'unset', path: refPath }])], section.revision);
103
346
  }
347
+ else if (section.ns === 'subagent-model-selection-settings' && field.path.join('.') === 'allowedModels') {
348
+ const routes = await chooseSubagentModels(ctx, configValue(section.value, field.path), signal);
349
+ if (routes === null)
350
+ continue;
351
+ field.schema(routes);
352
+ await saveValue(ctx, section, field, routes);
353
+ }
104
354
  else {
105
- const constants = field.schema.type === 'boolean' ? [true, false] : field.schema.type === 'union'
106
- && field.schema.list?.every(child => child.type === 'const') ? field.schema.list.map(child => child.value) : undefined;
107
- const text = await ctx.tui.prompt({ title: field.path.join('.'), question: field.schema.type === 'string' ? 'Enter a value' : 'Enter a JSON value',
108
- ...(field.secret ? {} : { detail: field.schema.toString() }), secret: field.secret,
109
- ...(constants === undefined ? { allowCustom: true } : { allowCustom: false, options: constants.map(value => ({ label: display(value), value: JSON.stringify(value) })) }), signal });
110
- if (text === null || signal.aborted)
355
+ const constants = constantValues(field);
356
+ const current = configValue(section.value, field.path);
357
+ const unitHelp = field.schema.type !== 'number' ? '' : (field.path.at(-1) ?? '').endsWith('Ms') ? 'Use milliseconds, or units such as 30s and 2m.'
358
+ : (field.path.at(-1) ?? '').endsWith('Bytes') ? 'Use bytes, or units such as 512 KiB and 2 MiB.' : '';
359
+ const text = await ctx.tui.prompt({ title: label, question: field.schema.type === 'string' || presentation !== undefined && field.schema.type === 'number' ? 'Enter a value' : constants === undefined ? 'Enter a JSON value' : 'Choose a value',
360
+ ...(field.secret ? {} : { detail: presentation === undefined ? field.schema.toString() : [`Current: ${fieldDisplay(field, current)}`, field.schema.meta.description, unitHelp].filter(Boolean).join('\n') }), secret: field.secret,
361
+ ...(field.secret ? {} : constants === undefined ? { initialInput: current === undefined ? '' : presentation !== undefined && field.schema.type === 'number' ? fieldDisplay(field, current) : field.schema.type === 'string' ? String(current) : JSON.stringify(current) }
362
+ : { initialValue: JSON.stringify(current) }),
363
+ ...(constants === undefined ? { allowCustom: true } : { allowCustom: false, options: constants.map(value => ({ label: presentation !== undefined && typeof value === 'boolean' ? value ? 'On' : 'Off' : display(value), value: JSON.stringify(value) })) }), notify: false, signal });
364
+ if (text === null || signal.aborted) {
365
+ if (directField)
366
+ return;
111
367
  continue;
112
- const value = parseConfigValue(field, text);
368
+ }
369
+ const value = presentation === undefined ? parseConfigValue(field, text) : parseProductValue(field, text);
113
370
  await saveValue(ctx, section, field, value);
114
371
  }
115
- ctx.tui.notice(`${ns}.${field.path.join('.')}: ${action === 'reset' ? 'inherited value restored' : 'saved'}.`);
372
+ ctx.tui.notice(`${presentation === undefined ? `${ns}.${field.path.join('.')}` : label}: ${action === 'reset' ? 'inherited value restored' : 'saved'}.`);
373
+ if (directField && !resetOnly)
374
+ return;
116
375
  }
117
376
  catch (error) {
118
377
  // Secret validation messages may echo their input.
119
- ctx.tui.notice(field.secret ? 'Could not save the secret. Refresh the field and check its format or credential storage.' : error instanceof Error ? error.message : String(error), { level: 'error' });
378
+ await showSettingsError(ctx.tui, field.secret ? 'Could not save the secret. Refresh the field and check its format or credential storage.' : error instanceof Error ? error.message : String(error), signal);
120
379
  }
121
380
  }
122
381
  }
382
+ async function chooseSubagentModels(ctx, current, signal) {
383
+ const routes = [];
384
+ const key = (route) => JSON.stringify([route.provider, route.model]);
385
+ const existing = Array.isArray(current) ? current.filter((route) => route !== null && typeof route === 'object' && typeof route.provider === 'string' && typeof route.model === 'string') : [];
386
+ const llm = ctx.get('llm');
387
+ const unavailable = [];
388
+ if (llm !== undefined) {
389
+ const providers = llm.listProviders();
390
+ const catalogs = await Promise.allSettled(providers.map(provider => llm.listModels(provider.id)));
391
+ signal.throwIfAborted();
392
+ catalogs.forEach((catalog, index) => {
393
+ const provider = providers[index];
394
+ if (catalog.status === 'rejected') {
395
+ unavailable.push(provider.id);
396
+ return;
397
+ }
398
+ for (const model of catalog.value)
399
+ routes.push({ provider: provider.id, model: model.id, label: `${model.name ?? model.id} · ${provider.id}`, description: model.description ?? model.id });
400
+ });
401
+ }
402
+ for (const route of existing)
403
+ if (!routes.some(item => key(item) === key(route)))
404
+ routes.push({ ...route, label: `${route.model} · ${route.provider}`, description: 'Configured route · currently outside the model catalog' });
405
+ const unique = routes.filter((route, index) => routes.findIndex(item => key(item) === key(route)) === index);
406
+ const answer = await ctx.tui.prompt({ ...list, title: 'Allowed subagent models', question: 'Choose models for new sessions', detail: `Space toggles a model. Enter saves the selection. Existing sessions keep their routes.${unavailable.length === 0 ? '' : ` Catalog unavailable: ${unavailable.join(', ')}; configured routes are retained.`}`,
407
+ emptyText: 'No models are available. Configure a provider through /login or cancel to keep the current setting.',
408
+ multiSelect: true, allowEmptySelection: true, initialValues: unique.flatMap((route, index) => existing.some(item => key(item) === key(route)) ? [String(index)] : []),
409
+ options: unique.map((route, index) => ({ label: route.label, ...(route.description === undefined ? {} : { description: route.description }), value: String(index) })), signal, notify: false });
410
+ if (answer === null)
411
+ return null;
412
+ if (answer === '')
413
+ return [];
414
+ return answer.split(', ').map(value => {
415
+ const route = unique[Number(value)];
416
+ if (route === undefined || !/^\d+$/u.test(value))
417
+ throw new Error('Model selection changed. Refresh and retry.');
418
+ return { provider: route.provider, model: route.model };
419
+ });
420
+ }
123
421
  function validateModelSelection(section, field, value) {
124
422
  if (section.ns === 'subagent-model-selection-settings') {
125
423
  const enabled = field.path[0] === 'enabled' ? value : configValue(section.value, ['enabled']);
126
424
  const models = field.path[0] === 'allowedModels' ? value : configValue(section.value, ['allowedModels']);
127
425
  if (enabled === true && (!Array.isArray(models) || models.length === 0))
128
- throw new Error('Set at least one allowedModels route before enabling subagent model selection.');
426
+ throw new Error('Choose at least one allowed model before enabling subagent model selection.');
129
427
  }
130
428
  }
131
429
  async function saveValue(ctx, section, field, value) {