@deepseek-harness-tui/dsh-tui 0.11.2 → 0.12.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 (168) hide show
  1. package/README.md +21 -5
  2. package/bin/dsh-tui.js +30 -3
  3. package/cordis.patch.yml +24 -5
  4. package/guide/dsh-tui-guide/SKILL.md +37 -0
  5. package/guide/dsh-tui-guide/configuration.en.md +410 -0
  6. package/guide/dsh-tui-guide/configuration.md +349 -0
  7. package/guide/dsh-tui-guide/getting-started.en.md +398 -0
  8. package/guide/dsh-tui-guide/getting-started.md +397 -0
  9. package/guide/dsh-tui-guide/interaction.en.md +674 -0
  10. package/guide/dsh-tui-guide/interaction.md +747 -0
  11. package/guide/dsh-tui-guide/migrate.en.md +166 -0
  12. package/guide/dsh-tui-guide/migrate.md +132 -0
  13. package/guide/dsh-tui-guide/plugins.en.md +68 -0
  14. package/guide/dsh-tui-guide/plugins.md +56 -0
  15. package/guide/dsh-tui-guide/themes.en.md +192 -0
  16. package/guide/dsh-tui-guide/themes.md +185 -0
  17. package/guide/dsh-tui-guide/user-guide.en.md +608 -0
  18. package/guide/dsh-tui-guide/user-guide.md +586 -0
  19. package/guide/dsh-tui-guide/vscode.en.md +301 -0
  20. package/guide/dsh-tui-guide/vscode.md +272 -0
  21. package/lib/settings.json +5 -5
  22. package/lib/types/adapter/channel/ui-policy.d.ts +3 -2
  23. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  24. package/lib/types/adapter/channel/ui-policy.js +4 -0
  25. package/lib/types/adapter/ports/channel-settings.d.ts +2 -1
  26. package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
  27. package/lib/types/adapter/ports/channel-ui.d.ts +29 -2
  28. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  29. package/lib/types/adapter/ports/channel-view.d.ts +43 -9
  30. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  31. package/lib/types/components/Chat/JobCard.d.ts +9 -0
  32. package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
  33. package/lib/types/components/Chat/JobCard.js +42 -15
  34. package/lib/types/components/Chat/SubagentMessage.js +6 -6
  35. package/lib/types/components/JobsPanel.d.ts.map +1 -1
  36. package/lib/types/components/JobsPanel.js +80 -33
  37. package/lib/types/components/MessageList.js +3 -3
  38. package/lib/types/components/PresetPicker.d.ts +4 -0
  39. package/lib/types/components/PresetPicker.d.ts.map +1 -1
  40. package/lib/types/components/PresetPicker.js +5 -1
  41. package/lib/types/components/PromptInput.d.ts.map +1 -1
  42. package/lib/types/components/PromptInput.js +12 -1
  43. package/lib/types/components/StarPrompt.d.ts +6 -0
  44. package/lib/types/components/StarPrompt.d.ts.map +1 -1
  45. package/lib/types/components/StarPrompt.js +45 -0
  46. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  47. package/lib/types/components/SubagentCard.js +8 -8
  48. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  49. package/lib/types/components/SubagentDetailScene.js +108 -10
  50. package/lib/types/components/WhaleGirl.d.ts +6 -0
  51. package/lib/types/components/WhaleGirl.d.ts.map +1 -1
  52. package/lib/types/components/WhaleGirl.js +18 -0
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +4 -4
  54. package/lib/types/components/trajectory/MiniWake.d.ts +11 -1
  55. package/lib/types/components/trajectory/MiniWake.d.ts.map +1 -1
  56. package/lib/types/components/trajectory/MiniWake.js +9 -3
  57. package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
  58. package/lib/types/dsh-adapter/channel/external-commands.js +8 -2
  59. package/lib/types/dsh-adapter/channel/job-projection.d.ts +1 -0
  60. package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel/job-projection.js +88 -3
  62. package/lib/types/dsh-adapter/channel/mode-actions.d.ts +13 -0
  63. package/lib/types/dsh-adapter/channel/mode-actions.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/channel/mode-actions.js +48 -7
  65. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/channel/mode-permission-actions.js +25 -9
  67. package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
  68. package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
  69. package/lib/types/dsh-adapter/channel/preferences.js +17 -7
  70. package/lib/types/dsh-adapter/channel/state.d.ts +4 -2
  71. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  72. package/lib/types/dsh-adapter/channel/state.js +1 -1
  73. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  74. package/lib/types/dsh-adapter/channel/subagent-projection.js +5 -0
  75. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  76. package/lib/types/dsh-adapter/channel.js +16 -1
  77. package/lib/types/dsh-adapter/contract.d.ts +5 -4
  78. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/contract.js +9 -3
  80. package/lib/types/dsh-adapter/jobs.d.ts +82 -16
  81. package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/jobs.js +104 -9
  83. package/lib/types/dsh-adapter/oauth/bonus.d.ts +27 -0
  84. package/lib/types/dsh-adapter/oauth/bonus.d.ts.map +1 -0
  85. package/lib/types/dsh-adapter/oauth/bonus.js +69 -0
  86. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.d.ts +2 -1
  87. package/lib/types/dsh-adapter/oauth/command.d.ts.map +1 -0
  88. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.js +7 -4
  89. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.d.ts +13 -10
  90. package/lib/types/dsh-adapter/oauth/credentials.d.ts.map +1 -0
  91. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.js +61 -23
  92. package/lib/types/dsh-adapter/oauth/deepseek.d.ts +39 -0
  93. package/lib/types/dsh-adapter/oauth/deepseek.d.ts.map +1 -0
  94. package/lib/types/dsh-adapter/oauth/deepseek.js +189 -0
  95. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.d.ts +24 -16
  96. package/lib/types/dsh-adapter/oauth/index.d.ts.map +1 -0
  97. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.js +34 -20
  98. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/interaction.d.ts +14 -12
  99. package/lib/types/dsh-adapter/oauth/interaction.d.ts.map +1 -0
  100. package/lib/types/dsh-adapter/oauth/interaction.js +283 -0
  101. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.d.ts +2 -1
  102. package/lib/types/dsh-adapter/oauth/opener.d.ts.map +1 -0
  103. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.js +1 -1
  104. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.d.ts +10 -3
  105. package/lib/types/dsh-adapter/oauth/pi-ai.d.ts.map +1 -0
  106. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.js +8 -3
  107. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.d.ts +6 -3
  108. package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -0
  109. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.js +11 -3
  110. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.d.ts +14 -7
  111. package/lib/types/dsh-adapter/oauth/service.d.ts.map +1 -0
  112. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.js +82 -17
  113. package/lib/types/dsh-adapter/plugin.d.ts +8 -1
  114. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  115. package/lib/types/dsh-adapter/plugin.js +72 -12
  116. package/lib/types/dsh-adapter/presets.d.ts +6 -4
  117. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  118. package/lib/types/dsh-adapter/presets.js +6 -4
  119. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  120. package/lib/types/dsh-adapter/providerWizard.js +6 -2
  121. package/lib/types/dsh-adapter/subagents.d.ts +1 -0
  122. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  123. package/lib/types/dsh-adapter/subagents.js +10 -1
  124. package/lib/types/i18n.d.ts +174 -18
  125. package/lib/types/i18n.d.ts.map +1 -1
  126. package/lib/types/i18n.js +53 -11
  127. package/lib/types/ink/components/App.d.ts.map +1 -1
  128. package/lib/types/ink/components/App.js +10 -1
  129. package/lib/types/ink/parse-keypress.d.ts +35 -0
  130. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  131. package/lib/types/ink/parse-keypress.js +611 -83
  132. package/lib/types/ink/terminal-querier.d.ts +25 -0
  133. package/lib/types/ink/terminal-querier.d.ts.map +1 -1
  134. package/lib/types/ink/terminal-querier.js +42 -0
  135. package/lib/types/ink/update-overflow-guard.d.ts +24 -0
  136. package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
  137. package/lib/types/ink/update-overflow-guard.js +25 -3
  138. package/lib/types/minimalUiMode.d.ts +19 -0
  139. package/lib/types/minimalUiMode.d.ts.map +1 -0
  140. package/lib/types/minimalUiMode.js +23 -0
  141. package/lib/types/oauth.d.ts +5 -10
  142. package/lib/types/oauth.d.ts.map +1 -1
  143. package/lib/types/oauth.js +5 -10
  144. package/lib/types/screens/Chat.d.ts +4 -1
  145. package/lib/types/screens/Chat.d.ts.map +1 -1
  146. package/lib/types/screens/Chat.js +27 -7
  147. package/lib/types/screens/StatusLine.d.ts +4 -0
  148. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  149. package/lib/types/screens/StatusLine.js +12 -12
  150. package/lib/types/settings/definitions.js +4 -4
  151. package/lib/types/tips.d.ts.map +1 -1
  152. package/lib/types/tips.js +10 -4
  153. package/lib/types/update.js +2 -2
  154. package/lib/types/utils/pastedImagePath.d.ts +8 -0
  155. package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
  156. package/lib/types/utils/pastedImagePath.js +29 -5
  157. package/package.json +111 -100
  158. package/presets/liangshen/agent.cordis.yml +4 -1
  159. package/presets/liangshen/preset.yml +1 -1
  160. package/lib/types/minimalMode.d.ts +0 -13
  161. package/lib/types/minimalMode.d.ts.map +0 -1
  162. package/lib/types/minimalMode.js +0 -17
  163. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +0 -21
  164. package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +0 -143
  165. package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +0 -11
  166. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +0 -44
  167. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +0 -193
  168. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +0 -69
package/README.md CHANGED
@@ -35,6 +35,8 @@
35
35
  - **Session workflow** — `/new` `/compact` `/export` `/btw`, model hot-switch, fork, rewind, vim, fullscreen draft editor.
36
36
  - **IDE selection channel** — a VS Code selection lands in the prompt.
37
37
  - **DSH integrations** — presets, skills, MCP, goals, todos, subagents, questionnaires.
38
+ - **Account sign-in** — the standard profile offers pi-ai OAuth for ChatGPT/Codex, Claude, and Grok (plus OpenAI direct and Meta Muse when available), and Host-owned DeepSeek browser sign-in as `deepseek-account` on DSH 0.2.0-rc.1+. Use `/provider` or `/auth` without another plugin.
39
+ A profile-only update from a global TUI patch that already mounts `dsh-tui-auth` can still start the official loopback callback listener on demand; fixed-port SSH forwarding still requires the global package to be aligned.
38
40
  - **Extensions** — browser interaction, computer use and more.
39
41
  - **Built for long sessions** — event-driven projection, virtualization, bounded caches.
40
42
 
@@ -80,10 +82,13 @@ daily** (TypeScript).
80
82
  ## Quick Start
81
83
 
82
84
  Prerequisites: [Node.js](https://nodejs.org/en) and
83
- [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness), with
84
- `DEEPSEEK_API_KEY` configured.
85
+ [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness).
86
+ The `deepseek-official` API-key route needs `DEEPSEEK_API_KEY`. On DSH
87
+ 0.2.0-rc.1+, the standard profile can instead use `/auth login deepseek-account`
88
+ and select the separate account route through `/model`. Other supported
89
+ accounts can sign in through `/provider` or `/auth` after startup.
85
90
 
86
- The primary compatibility target is DSH `0.2.0-rc.1`. This adapter supports its
91
+ The primary compatibility target is DSH `0.2.0-rc.2`. This adapter supports its
87
92
  Shell API, V4 session messages, declarative presets, and profile-backed settings;
88
93
  older supported hosts retain their compatibility paths. See [configuration](docs/configuration.en.md).
89
94
 
@@ -173,15 +178,25 @@ Incomplete records are held for a bounded recovery window (1 second from first c
173
178
 
174
179
  A session's very first record can still leave residue if it is split before its record-specific shape forms; once any record has been decoded, every split position is covered. Inside the recovery window, literal input starting with `[digit;…` cannot be told apart from a protocol prefix — it may be held, or re-joined to a preceding `Esc`. To type it, wait for the window to close, or avoid that shape right after `Esc`.
175
180
 
181
+ Terminal replies that arrive split are reassembled the same way (native Windows ConPTY is the common source): while the app still has a query awaiting its answer, an unfinished DA1 / DA2 / DSR / DECRPM / XTVERSION tail — even one split again after the introducer `Esc` was flushed — is held across input delays, but only while its shape can still complete into the response type that query expects. It is then consumed as the reply it completes instead of entering the prompt as protocol text.
182
+
183
+ That claim is evidence-gated, and this is the difference from earlier builds: no query awaiting an answer means nothing is claimed, so a literal `[?61;4c` typed right after `Esc` still enters the prompt exactly as before.
184
+
185
+ The window is bounded like the record hold (about a second, never extended by later input; 64 bytes max); past either bound it ends, and bytes still shaped like an unfinished reply prefix are dropped rather than shown.
186
+
187
+ Inside that window, with a query of the matching response type outstanding, same-shaped literal input can still be claimed as a reply; to type it, wait for the window to close (about a second), or avoid that shape while a query is outstanding.
188
+
176
189
  Mouse (fullscreen): drag to select and copy, double/triple click to select a word or line, click tool cards, timeline ticks and `[Image #N]` previews.
177
190
 
178
191
  **Pasting**: native and bracketed paste keeps ordinary text and newlines, and never submits itself on arrival. On Windows terminals that deliver a paste as win32-input-mode key records, the residue is stripped at the entry point (a multi-line paste no longer leaves stray `_`) and pasted CRLF collapses to a single newline; genuine underscores and bracketed-paste text are untouched.
179
192
 
193
+ **Dropped files**: a native Windows desktop drop (Windows Terminal / OpenConsole) arrives as an OSC 8 hyperlink; the parser restores its `file://` URI to a decoded local path before paste hygiene runs, so the `]8;id=…;` parameter bytes never reach the draft. Image paths enter the existing image staging pipeline; other files are inserted as a referenceable path (a path containing whitespace arrives in the composer's quoted `"…"` single-token form). Only `file://` URIs are restored, and it is fail-closed: a remote authority/UNC, a payload carrying several distinct URIs, or a URI that carries several tokens is refused and stays literal text rather than guessed.
194
+
180
195
  Full reference: [Interaction and commands](docs/interaction.en.md).
181
196
 
182
197
  ## Built-in Commands
183
198
 
184
- `/resume` · `/home` · `/agentview` · `/bg` · `⌸` open the same session manager: workspace rail, live state, filter, ★ pins. Also `/model` `/new` `/compact` `/export` `/btw` `/tree` `/fork` `/rewind` `/settings` `/status` `/cost` `/jobs` `/skills` `/mcp` `/login` `/update`.
199
+ `/resume` · `/home` · `/agentview` · `/bg` · `⌸` open the same session manager: workspace rail, live state, filter, ★ pins. Also `/model` `/new` `/compact` `/export` `/btw` `/tree` `/fork` `/rewind` `/settings` `/status` `/cost` `/jobs` `/skills` `/mcp` `/provider` `/auth` `/login` `/update`.
185
200
 
186
201
  The session manager paints the last successful list immediately while it checks the persistence store for changes. Titles that require a deeper log scan appear first with a fallback name and update in place when recovery finishes.
187
202
 
@@ -209,8 +224,9 @@ Runtime path, module boundaries, performance notes and persistence locations: [A
209
224
  - Injected plugin context has no standalone display; it counts into the context segments.
210
225
  - `/model` switches by forking the session; the old session stays in `/resume` (a session nobody has typed into records no branch, so your first prompt there still gets a generated title).
211
226
  - `Ctrl+V` needs platform clipboard tools; unsupported bitmap formats are rejected.
227
+ - A dropped file is restored from its OSC 8 `file://` URI alone: multi-file drops, non-Windows terminal drop encodings and terminator-less truncated frames are not covered, and the hyperlink's own display name is never used.
212
228
  - A background session lives inside this process and stops when the TUI exits.
213
- - `/thinking` is not persisted; `/compact` is unavailable under the `minimal` preset; `/update` needs a `dsh --profile` launch and is refused while a turn is running.
229
+ - `/thinking` is not persisted; `/compact` is unavailable under the kernel's `minimal` agent preset (极简模式, one persistent-shell tool) — a different thing from the `/settings → Minimal UI` (极简界面) display switch; `/update` needs a `dsh --profile` launch and is refused while a turn is running.
214
230
  - The status-bar `≈¥` and `/cost` are session estimates that include subagent usage (priced per each agent's model × peak/idle × cache components); unofficial or unlisted models show tokens only and are marked unpriced. **The platform bill is authoritative.**
215
231
 
216
232
  Full list: [Architecture and limitations → Known limitations](docs/architecture.en.md#known-limitations).
package/bin/dsh-tui.js CHANGED
@@ -62,6 +62,16 @@ const ownPackage = readJson(join(ownDir, 'package.json'))
62
62
  const ownVersion = ownPackage?.name === '@deepseek-harness-tui/dsh-tui' ? ownPackage.version : undefined
63
63
  const PACKAGE = '@deepseek-harness-tui/dsh-tui'
64
64
  const PROFILE = 'dsh-tui'
65
+
66
+ // 随包用户手册(guide/,见 scripts/build-guide.mjs):交给 dsh 当内核
67
+ // dsh-skill-filesystem 的随包技能根(rank 600 的 bundledSkillDir 默认取这个
68
+ // 环境变量)。会话里模型常驻只多一行技能目录,用户真问到 dsh-tui 时才按需
69
+ // 读手册。用户自己设过就不覆盖;包里没有 guide/(旧安装/裁剪包)时保持原样。
70
+ const withGuideSkillDir = env => {
71
+ if (env.DSH_BUNDLED_SKILL_DIR !== undefined) return env
72
+ const guideDir = join(ownDir, 'guide')
73
+ return existsSync(guideDir) ? { ...env, DSH_BUNDLED_SKILL_DIR: guideDir } : env
74
+ }
65
75
  // 救援 profile(最小可用):同 home 下的空白 profile(仅 base+TUI,无第三方
66
76
  // 插件),是主 profile 装炸时的干净启动通道——创建走官方 dsh plugin add
67
77
  // (钉当前版本,同 bootstrap 语义)。
@@ -728,7 +738,7 @@ const startDshSession = (dshArgs, profile = PROFILE, env = process.env) =>
728
738
  new Promise(resolve => {
729
739
  const child = spawn(...cmd('dsh', ['--profile', profile, ...dshArgs]), {
730
740
  stdio: 'inherit',
731
- env,
741
+ env: withGuideSkillDir(env),
732
742
  ...shellOpt,
733
743
  })
734
744
  child.on('error', err => resolve({ kind: 'error', error: err }))
@@ -751,6 +761,23 @@ const rescueEnv = () => {
751
761
  return env
752
762
  }
753
763
 
764
+ // 安全模式「重试正常启动」的环境(菜单选项 1,首启 fallback 与 `safe` 共用):
765
+ // 崩溃往往发生在 TUI 的退出漏斗之前,此时 `~/.dsh-tui/resume.txt` 是"用户上
766
+ // 一刻在哪个会话"的唯一线索(TUI 侧崩溃分支也会写它,见
767
+ // src/dsh-adapter/plugin.ts 的退出漏斗)。不带这个变量重试等于开一个新的空会话
768
+ // ——正是「会话丢了」的观感。已经显式设了该变量(用户自己 `--resume`)则不覆盖;
769
+ // 指针缺失/不可读时保持冷启动语义。
770
+ const resumeEnvForRetry = () => {
771
+ if (process.env.DSH_TUI_RESUME_SESSION !== undefined) return process.env
772
+ let target = ''
773
+ try {
774
+ target = readFileSync(join(homedir(), '.dsh-tui', 'resume.txt'), 'utf8').trim()
775
+ } catch {
776
+ // 没有历史会话可恢复——静默冷启动。
777
+ }
778
+ return target === '' ? process.env : { ...process.env, DSH_TUI_RESUME_SESSION: target }
779
+ }
780
+
754
781
  // TTY 判定:询问与菜单都要求 stdin/stdout 均可交互(readline 需要 stdin,
755
782
  // 菜单可读需要 stdout);任一非 TTY(脚本/管道/headless 宿主)走降级。
756
783
  const isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY)
@@ -1071,7 +1098,7 @@ const settleFirstResult = async (result, firstArgs) => {
1071
1098
  if (result.kind === 'error') {
1072
1099
  console.error(msg('launchFailed')(result.error))
1073
1100
  if (isInteractive()) {
1074
- if (await askSafeEntry(1)) process.exit(await runSafeSession({ pendingExitCode: 1, retryDsh: () => startDshSession(firstArgs) }))
1101
+ if (await askSafeEntry(1)) process.exit(await runSafeSession({ pendingExitCode: 1, retryDsh: () => startDshSession(firstArgs, PROFILE, resumeEnvForRetry()) }))
1075
1102
  } else {
1076
1103
  console.error(msg('safeHint')(1))
1077
1104
  }
@@ -1082,7 +1109,7 @@ const settleFirstResult = async (result, firstArgs) => {
1082
1109
  console.error(msg('profileExited')(result.code))
1083
1110
  if (isInteractive()) {
1084
1111
  if (await askSafeEntry(result.code)) {
1085
- process.exit(await runSafeSession({ pendingExitCode: result.code, retryDsh: () => startDshSession(firstArgs) }))
1112
+ process.exit(await runSafeSession({ pendingExitCode: result.code, retryDsh: () => startDshSession(firstArgs, PROFILE, resumeEnvForRetry()) }))
1086
1113
  }
1087
1114
  } else {
1088
1115
  console.error(msg('safeHint')(result.code))
package/cordis.patch.yml CHANGED
@@ -409,11 +409,30 @@
409
409
  name: '@deepseek-ai/dsh-cordis-host-runner'
410
410
  disabled: !!js "[...ctx.loader.entries()].some(entry => entry.options.id === 'cordis-host-runner' && entry.options.name === '@deepseek-ai/dsh-cordis-host-runner' && !entry.disabled)"
411
411
 
412
- # Subscription OAuth sign-in (bundled dsh-auth dependency): mounts the
413
- # ChatGPT/Codex, Claude Pro/Max and SuperGrok provider routes plus the
414
- # /auth command; the TUI's /provider wizard gains its OAuth branch from
415
- # the ctx.dshAuth service this row provides. Entry-level inject only —
416
- # the plugin module's own inject is empty so a stale patch cannot
412
+ # DeepSeek account sign-in uses the Host-owned /oauth/callback route. A
413
+ # TUI-only profile needs a loopback listener; a mixed Web+TUI profile
414
+ # reuses Web's official webserver instead of opening a second one. Hosts
415
+ # without an enabled account service row leave this listener disabled.
416
+ - id: dsh-tui-webserver
417
+ name: '@deepseek-ai/dsh-host-webserver'
418
+ disabled: !!js >-
419
+ (() => {
420
+ const require = process.getBuiltinModule('node:module').createRequire(ctx.baseUrl)
421
+ try { require.resolve('@deepseek-ai/dsh-host-webserver/package.json') } catch { return true }
422
+ const entries = [...ctx.loader.entries()]
423
+ return !entries.some(entry => entry.options.name === '@deepseek-ai/dsh-deepseek-account-platform' && !entry.disabled)
424
+ || entries.some(entry => entry.options.id === 'webserver' && entry.options.name === '@deepseek-ai/dsh-host-webserver' && !entry.disabled)
425
+ })()
426
+ config:
427
+ host: 127.0.0.1
428
+ port: 0
429
+
430
+ # Built-in subscription OAuth sign-in: mounts available pi-ai routes
431
+ # (ChatGPT/Codex, Claude, Grok; newer pi-ai adds OpenAI direct and Meta)
432
+ # plus the /auth command. Its facade also delegates DeepSeek sign-in to
433
+ # the Host-owned account service when available. The TUI's /provider
434
+ # wizard gains its OAuth branch from ctx.dshAuth. Entry-level inject only —
435
+ # the internal module's own inject is empty so a stale patch cannot
417
436
  # deadlock a composition lacking these services (#183 pattern).
418
437
  - id: dsh-tui-auth
419
438
  name: '@deepseek-harness-tui/dsh-tui/oauth'
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: dsh-tui-guide
3
+ description: Use when the user asks about dsh-tui itself (usage, shortcuts, config, themes, migration, VS Code). Load before answering; skip for normal coding tasks.
4
+ ---
5
+
6
+ # dsh-tui 用户手册(索引)
7
+
8
+ 用户手册全文就在**本文件所在目录**——`skill` 工具返回的 `resourceBase`。共 8 篇,
9
+ 每篇中英各一份:中文文件名无后缀,英文加 `.en.md`。拿不到绝对路径时,用
10
+ `glob` 在 dsh-tui 包里搜 `guide/dsh-tui-guide`。
11
+
12
+ ## 怎么用
13
+
14
+ 1. **跟用户同语言**:中文提问读无后缀文件,英文提问读加后缀的那份;回答也用同一种语言。
15
+ 2. **先定位再读**:16 个文件合计约 290 KB,整篇读既慢又挤上下文。先 `grep` 关键词拿到命中行,
16
+ 再用 `read` 带 `offset`/`limit` 只展开需要的那几段。
17
+ 3. **一次一到两篇**:按下面的路由表挑最贴近的;跨主题时再补第二篇。
18
+ 4. **给出处**:回答末尾写清来源(文件名 + 小节标题),用户能直接翻回去核对。
19
+ 5. **别编**:手册没写的直说"手册没写",再给保守建议;不要拿记忆里的旧行为顶替。
20
+
21
+ ## 路由表
22
+
23
+ | 用户问的 | 文件 |
24
+ | --- | --- |
25
+ | 安装、首次启动、启动失败、排障 | `getting-started.md` |
26
+ | 日常使用、键位、斜杠命令、会话与工作流、设置 | `user-guide.md` |
27
+ | 输入框、鼠标、选中复制、粘贴、图片、问卷与审批 | `interaction.md` |
28
+ | 配置覆盖层、TUI 开关、agent preset、模型路由、MCP | `configuration.md` |
29
+ | 主题、配色、自动检测、自定义主题 | `themes.md` |
30
+ | 从 Claude Code / Codex / OMP / zcode / Grok 迁移会话 | `migrate.md` |
31
+ | VS Code 集成、扩展、IDE 选区 | `vscode.md` |
32
+ | 插件生态、接缝稳定性分级 | `plugins.md` |
33
+
34
+ 英文提问时把上表文件名接上后缀(例如 `user-guide.en.md`)。
35
+
36
+ > 除本文件外,这些副本由 `scripts/build-guide.mjs` 从仓库 `docs/` 逐字节同步生成,
37
+ > 不要手改;改文档请改 `docs/` 再跑一次构建。
@@ -0,0 +1,410 @@
1
+ # Configuration
2
+
3
+ [Documentation index](README.md) · [简体中文](configuration.md)
4
+
5
+ ## Profiles and patch layers
6
+
7
+ After an npm/profile installation, user configuration lives at:
8
+
9
+ ```text
10
+ $DSH_HOME/profiles/dsh-tui/cordis.patch.yml
11
+ ```
12
+
13
+ When `DSH_HOME` is unset, it normally defaults to `~/.dsh`. The file is a
14
+ top-level YAML array and may use the `!!js` expressions supported by DSH.
15
+
16
+ Profile startup layers, in order:
17
+
18
+ - `dsh-base`
19
+ - Installed bundles
20
+ - The package's `cordis.patch.yml`
21
+ - The user patch (applied last)
22
+
23
+ A user configuration normally overrides an existing row by `id`; use `insert`
24
+ only for a genuinely new service.
25
+
26
+ > When a row is overridden, its `config` block is replaced as a whole. It is
27
+ > not deep-merged, so repeat every key that must remain active.
28
+
29
+ ## TUI configuration
30
+
31
+ On DSH 0.1.7, `/settings` writes plugin Config fields to the active profile's
32
+ `cordis.patch.yml`. Older hosts still use `~/.dsh/settings.yaml`; that file is
33
+ not the new settings entry point. Language and layout preferences update live;
34
+ fullscreen and image previews require `/restart`.
35
+
36
+ A complete common override looks like this:
37
+
38
+ ```yaml
39
+ - id: dsh-tui
40
+ config:
41
+ provider: deepseek-official
42
+ model: deepseek-flash
43
+ # Prefer leaving cwd unset — the default resolves to the git worktree
44
+ # root containing the launch directory. To pin a fixed workspace, use an
45
+ # absolute path (e.g. cwd: /repo/packages/app), NOT `!!js process.cwd()`
46
+ # (that pins the workspace to the launch subdirectory, issue #96).
47
+ effort: max
48
+ activity: true
49
+ activityFrames: moon8
50
+ contextBar: true
51
+ fullscreen: false
52
+ terminalImages: true
53
+ preset: !!js process.env.DSH_TUI_PRESET ?? undefined
54
+ workspace: !!js process.env.DSH_TUI_WORKSPACE_TARGET ?? undefined
55
+ sessionId: !!js process.env.DSH_TUI_RESUME_SESSION ?? undefined
56
+ ```
57
+
58
+ | Field | Default/source | Meaning |
59
+ | --- | --- | --- |
60
+ | `provider` | Harness `agentDefaultModel`; bare compositions fall back to `deepseek-official` | DSH model route; provider and model must both be set to form an explicit route |
61
+ | `model` | Harness `agentDefaultModel`; bare compositions fall back to `deepseek-flash` | Startup model; `/model` can switch through a session fork |
62
+ | `cwd` | git worktree root containing the launch directory (`process.cwd()` when outside any worktree; a dotfiles repo at `$HOME` does not count) | TUI-side session workspace: agent meta, `@` completion/mention expansion, /resume filtering, statusline; resuming an existing session adopts that session's persisted cwd. Note the bash/fs-policy/sandbox roots are still owned by the composition layer's cordis config (default: the launch directory, governed by dsh-base) and may differ from this session-side cwd |
63
+ | `workspace` | unset | Startup workspace target: a local path, `file://` URL, or plugin-provided URI; takes precedence over `cwd` |
64
+ | `effort` | normally `max` in the bundle | Reasoning effort applied to every request (validated against the runtime model's levels; invalid levels silently fall back to the adapter default), also shown in the header at startup. Precedence: /settings `effortDefault` (`auto` defers) > this field > the persisted `/effort` choice (`~/.dsh-tui/effort.json`) > the model default |
65
+ | `effortDefault` | unset | Default reasoning effort for new sessions; `auto` defers to `effort`; editable through `/settings` |
66
+ | `whale` / `whaleIdle` | `true` / `true` | Header whale and welcome-page idle animation |
67
+ | `splashFont` | `daily` | Big-text face on the header splash: `daily` rotates by local date (the default), any other value is a face id (`bold` / `square` / `bevel` / `wide` / `dot` / `stencil` / `classic` / `slab`) pinning that one; an unknown value falls back to `daily`. Also editable through `/settings` |
68
+ | `whaleGirl` | `false` | Swap the header's pixel whale for the maid: real raster FIRST (Kitty/Sixel); falls back to the character-art maid without them |
69
+ | `minimal` | `false` | Minimal UI (极简界面): reduce header decoration and colors. **A display switch only** — a different thing from the kernel's `minimal` agent preset under `preset` below (that one decides which tools the model can use) |
70
+ | `modes` | built-in trio | Shift+Tab session-mode cycle (plan/sandbox/approval atom bundles); defaults to default → plan → full-access |
71
+ | `activity` | `true` | Show the live activity row |
72
+ | `activityFrames` | `moon8` | Activity animation preset; `/activity` changes it at runtime. A legacy saved value of `claude` is read as `moon8`, and the picker no longer offers that legacy preset |
73
+ | `contextBar` | `true` | Segmented context-usage bar below the input box; `false` hides the row. Both this and `/settings → statusBar.contextBar` (also on by default) must be on for it to render |
74
+ | `fullscreen` | `true` (factory default since 0.9.0) | `true` uses the alternate screen, app scrolling, and mouse selection; `false` uses inline mode |
75
+ | `terminalImages` | `true` | Allow previews in supported terminals; `false` keeps text metadata and skips image probing and preview decoding. Restart to apply changes |
76
+ | `preset` | roster default `standard` | Agent preset for new sessions; explicit configuration wins over persisted preference |
77
+ | `sessionId` | unset | Session to resume, normally injected by the Windows `--resume` launcher |
78
+
79
+ ### Precedence and force-off
80
+
81
+ - `/settings → Terminal image previews` overrides `config.terminalImages`.
82
+ - Without a saved choice, the config value applies and defaults to on.
83
+ - Enabling still needs Kitty graphics support and a display mode that allows
84
+ image rendering.
85
+ - `DSH_TUI_DISABLE_TERMINAL_IMAGES=1` always forces previews off.
86
+ - Disabled previews do not read or decode image data or send image rendering
87
+ commands; sending images to the model is unaffected.
88
+ - The checkbox edits the preview preference; an environment override is shown
89
+ separately as “Image previews (forced off)” in the settings list.
90
+
91
+ ### Restart
92
+
93
+ - This switch is read at startup.
94
+ - Use `/restart` after changing it to restart the TUI and resume the current
95
+ session; `/reload` does not apply it.
96
+ - If a turn is running, wait for it to finish or stop it with `Ctrl+C` before
97
+ restarting.
98
+
99
+ ## Diagnostic environment variables
100
+
101
+ The following variables are for diagnostics or experimental terminal
102
+ integration. They are all off by default and take effect only when explicitly
103
+ set:
104
+
105
+ | Variable | Purpose |
106
+ | --- | --- |
107
+ | `DSH_TUI_DEBUG_REPAINTS=1` | Record repaint diagnostics |
108
+ | `DSH_TUI_COMMIT_LOG=1` | Record render-commit diagnostics |
109
+ | `DSH_TUI_ACCESSIBILITY=1` | Enable accessibility related display paths |
110
+ | `DSH_TUI_TMUX_TRUECOLOR=1` | Enable the truecolor detection path in tmux |
111
+ | `DSH_TUI_TAB_STATUS=1` | Experimental terminal tab-status opt-in; off by default, with no guarantee of support in every terminal |
112
+
113
+ Diagnostic output does not change session events or model routing. Enable
114
+ only the variable needed for the terminal or rendering issue being
115
+ investigated.
116
+
117
+ ## Live activity row
118
+
119
+ `dsh-working-activity` is installed with the package and inserted by its
120
+ patch. The working line reads the plugin's `workingActivity` session
121
+ projection, which requires `dsh-working-activity` ≥ 0.5.0 — that release
122
+ replaced the old `activity/status` event outlet with the projection, so
123
+ older plugin versions produce no working line. Override only the existing
124
+ ID when tuning it:
125
+
126
+ ```yaml
127
+ - id: working-activity
128
+ config:
129
+ publishIntervalMs: 500
130
+ ```
131
+
132
+ Do not insert a second row and do not separately run
133
+ `dsh plugin ... add dsh-working-activity` for the same profile.
134
+
135
+ ## Agent presets
136
+
137
+ Each session composes its model-visible tools and prompt through the official
138
+ preset registry: `@deepseek-ai/dsh-agent-preset-registry` on 0.1.7, or
139
+ `@deepseek-ai/dsh-agent-presets` on older hosts:
140
+
141
+ | ID | Name | Capability |
142
+ | --- | --- | --- |
143
+ | `standard` | Standard (default) | Editing, shell, search, skills, planning, goals, subagents, and workflows |
144
+ | `ptc` (0.1.2) / `code` (legacy 0.1.1) | PTC | Standard plus the PTC SDK presentation for composing operations in TypeScript; both names resolve compatibly across versions |
145
+ | `minimal` | Minimal | Kernel agent preset: a single persistent-shell tool (bash on POSIX, pwsh on Windows), with no compaction, no plan mode and no runtime context. `str_replace_editor` has been opt-in since 0.1.3-alpha.2, so this preset does not include it |
146
+ | `cordis` | Creation | Standard plus runtime inspection and plugin-experimentation tools |
147
+ | `liangshen` | Liangshen mode | Minimal's minimal tool surface first for root and delegated agents, the full catalog after the first tool call, and a fresh anchor after compaction |
148
+
149
+ > ⚠️ Do not confuse the 「极简模式」 (Minimal) preset here with `/settings → Appearance → 极简界面`
150
+ > (Minimal UI, config key `dsh-tui.minimal`): the preset is a **kernel agent preset** and changes
151
+ > which tools the model can see and call, while Minimal UI only trims interface decoration
152
+ > (header splash, emoji glyphs, decorative colors, footer fields) and has no effect on capability.
153
+
154
+ ### Selecting and switching
155
+
156
+ - `/preset` opens the picker.
157
+ - `/preset <id>` selects directly; `/preset status` reports the current state.
158
+ - Picker names and descriptions come from registry declarations (from
159
+ `preset.yml` on older hosts).
160
+ - Under the `en` UI language (`/lang en`), the built-in presets show localized
161
+ English names and descriptions.
162
+ - Built-in presets: `standard` / `minimal` / `ptc` (legacy `code`) / `cordis` / `liangshen`;
163
+ custom presets are shown as-is.
164
+ - A blank session can switch in place. Once a conversation has started, the
165
+ official blank-only rule stores the choice as the new default for `/new` or
166
+ the next launch.
167
+
168
+ ### Default and precedence
169
+
170
+ - The default is stored in `~/.dsh-tui/agent-preset.json`.
171
+ - Precedence: explicit `config.preset` or `DSH_TUI_PRESET`, then persisted
172
+ preference, then the roster default `standard`.
173
+ - A legacy `code` preference resolves to `ptc` when the active roster no
174
+ longer provides `code`, then migrates after that successful resolution;
175
+ rc rosters keep their real `code` id, and session logs are never rewritten.
176
+ - Resuming a session restores the preset recorded in that session's log and
177
+ does not overwrite it with the current default.
178
+
179
+ ### Liangshen mode
180
+
181
+ - Liangshen mode ships with dsh-tui. On 0.1.7 it registers with the official
182
+ registry; an existing profile declaration with the same id takes precedence.
183
+ Older hosts install it into the user preset root, preserving unmanaged directories.
184
+ - The first-round `bash` on Windows runs an auto-discovered Git Bash, trying
185
+ in order:
186
+ - The installation tree of a `git.exe` found on PATH (covers installer,
187
+ portable, and Scoop layouts; Scoop shims are followed)
188
+ - Conventional install roots and Scoop's conventional directories
189
+ - Bare `bash` on PATH (final fallback)
190
+ - It never accepts the System32 WSL launcher as Git Bash
191
+ - Set `DSH_TUI_LIANGSHEN_BASH_PATH` to an absolute `bash.exe` path to pin it.
192
+ - The pin is the only candidate; a miss warns and skips registration, exposing
193
+ the full tool catalog on the first round.
194
+
195
+ ### Custom presets
196
+
197
+ On 0.1.7, declare `@deepseek-ai/dsh-agent-preset` through a profile/bundle with
198
+ `id`, `name`, and `plugins` in its config. Migrate old directory presets to
199
+ bundles using the upstream workflow; TUI no longer scans directories itself.
200
+ Older hosts still discover `$DSH_HOME/.agent-presets/<name>/agent.cordis.yml`.
201
+
202
+ Since 0.3, model-side tools, planning, compaction, and delegation are owned by
203
+ the preset. Profile mode no longer uses the old `DSH_TUI_COMPACT_RATIO`,
204
+ `DSH_TUI_COMPACT_RETAIN`, or the former TUI's subagent-depth customization; configure
205
+ those policies in the preset instead.
206
+
207
+ ## MCP
208
+
209
+ The official `@deepseek-ai/dsh-mcp-client` supports both stdio and streamable
210
+ HTTP. Mounted tools are registered as `mcp__<server>__<tool>` and enter the
211
+ model tool set automatically.
212
+
213
+ Insert servers in the user `cordis.patch.yml`:
214
+
215
+ ```yaml
216
+ - insert:
217
+ - id: mcp-context7
218
+ name: '@deepseek-ai/dsh-mcp-client'
219
+ config:
220
+ transport: stdio
221
+ serverName: context7
222
+ command: npx
223
+ args: ['-y', '@upstash/context7-mcp']
224
+
225
+ - id: mcp-remote
226
+ name: '@deepseek-ai/dsh-mcp-client'
227
+ config:
228
+ transport: streamable-http
229
+ serverName: remote
230
+ url: https://example.com/mcp
231
+ headers:
232
+ Authorization: !!js process.env.MCP_TOKEN
233
+ ```
234
+
235
+ Run `/mcp` to inspect connected servers and tool counts. Consult the
236
+ [DeepSeek Harness configuration catalog](https://deepseek-harness.github.io/deepseek-harness/reference/config-catalog#deepseek-ai-dsh-mcp-client)
237
+ for the complete field reference.
238
+
239
+ ## Environment variables
240
+
241
+ | Variable | Purpose |
242
+ | --- | --- |
243
+ | `VISUAL` / `EDITOR` | External editor opened by `Ctrl+G` (`VISUAL` wins; arguments like `code --wait` are allowed; with neither set the TUI prompts you to configure one — no `vi` fallback) |
244
+ | `DEEPSEEK_API_KEY` | Required DeepSeek credential |
245
+ | `DEEPSEEK_BASE_URL` | Override the compatible DeepSeek API endpoint |
246
+ | `DSH_HOME` | Harness home (profiles, sessions, credentials, attachments); falls back to the upstream default `~/.dsh` |
247
+ | `DSH_TUI_PERSONA` | Override the Agent persona injected by the composition |
248
+ | `DSH_TUI_PRESET` | Override the default Agent preset for new sessions |
249
+ | `DSH_TUI_THEME` | Pin a built-in (`auto`/`light`/`dark`/`dark-ansi`), static theme, or registered plugin theme ahead of persisted selection |
250
+ | `DSH_TUI_DISABLE_MOUSE` | Temporarily disable mouse handling in fullscreen mode |
251
+ | `DSH_TUI_DISABLE_TERMINAL_IMAGES` | Set to `1` to force Kitty/Sixel probing, preview reads/decoding, and terminal image rendering off, overriding config and /settings; text metadata remains visible |
252
+ | `DSH_TUI_IMAGE_PROTOCOL` | `auto` (default), `kitty`, `sixel`, or `none`; override protocol selection without bypassing the preview preference, disable switch, non-fullscreen, accessibility or multiplexer guards |
253
+ | `DSH_TUI_RESUME_SESSION` | Resume a session at startup, normally set by a launcher |
254
+ | `DSH_TUI_WORKSPACE_TARGET` | Workspace path or URI resolved at startup, normally set by `dsh-tui <target>` |
255
+ | `DSH_TUI_SESSION_ROOT` | Override the JSONL session root; profile default `$DSH_HOME/sessions`, bare `cordis.yml` default `~/.dsh-tui/sessions` |
256
+ | `DSH_PERMISSION_MODE` | Override non-Windows sandbox policy, such as `workspace-write` or `danger-full-access` |
257
+ | `DSH_TUI_WORKSPACE` | Working directory used by the Windows `dsh-tui.cmd` launcher |
258
+ | `DSH_TUI_DEBUG` | Enable dsh-tui diagnostics on stderr |
259
+ | `DSH_TUI_RENDER_LOG` | File path for raw ANSI frame capture |
260
+
261
+ The old `CC_TUI_*` and `DSH_CC_*` names come from earlier release naming and
262
+ are no longer read as of this release; use the `DSH_TUI_*` prefix.
263
+
264
+ Two directories are involved and neither substitutes for the other:
265
+
266
+ - **Harness home**: `$DSH_HOME`, falling back to the upstream default `~/.dsh`.
267
+ Holds profiles, sessions, credentials, and attachments. Early releases pinned
268
+ it to `~/.dsh-cc`.
269
+ - **TUI data directory**: `~/.dsh-tui` (a fixed path, independent of
270
+ `$DSH_HOME`). Holds `/model` (persisted at `~/.dsh-tui/model.json`, surviving
271
+ restart and `/new`), `/lang`, `/theme` and similar preferences plus
272
+ `resume.txt`. Early releases wrote these under `$DSH_HOME` instead.
273
+
274
+ `DSH_TUI_RENDER_LOG` may capture visible prompts, tool arguments, and output.
275
+ Do not attach it to a public issue without reviewing and redacting it.
276
+
277
+ ## `/provider`: manage model providers at runtime
278
+
279
+ `/provider` opens an interactive wizard to add, edit, or delete model
280
+ providers without a restart.
281
+
282
+ - Sources: built-in catalog routes or custom API endpoints.
283
+ - Only providers written by the **user settings layer** can be edited or
284
+ deleted; ones inherited from the composition base cannot be removed.
285
+ - Keys are written to `~/.dsh/.credentials.yaml` (mode 0600) and render as
286
+ `••••••`.
287
+ - Only non-environment keys are written to the store; a key shared with
288
+ another provider is kept on delete.
289
+ - Editing the model list re-fetches the candidates: a built-in catalog route
290
+ starts from the installed catalog snapshot and, whenever the route stores a
291
+ baseURL (or the add flow just collected one), also probes
292
+ `GET {baseURL}/models` live with the stored key and merges the listings —
293
+ ids only the endpoint advertises are tagged as new and carry their
294
+ disclosed capacities once enabled; already-enabled models are pre-checked
295
+ and only an explicit un-check removes one. A catalog route without a
296
+ baseURL shows the snapshot only, with the origin noted in the question
297
+ detail. Catalog routes with no single known protocol or custom request
298
+ headers also explain the limitation and stay on the snapshot, rather than
299
+ saving endpoint-only models into an unverifiable profile.
300
+
301
+ Where it writes:
302
+
303
+ | Artifact | Location |
304
+ | --- | --- |
305
+ | Provider profile | `llm-pi-ai.providers.<route>` in the active profile config on 0.1.7, or `~/.dsh/settings.yaml` on older hosts; the route registers on write and unregisters on delete |
306
+ | API key | `~/.dsh/.credentials.yaml` (mode 0600), referenced as `<ROUTE>_API_KEY` |
307
+
308
+ ### Built-in subscription OAuth
309
+
310
+ The standard profile's `dsh-tui-auth` row loads this package's `./oauth`
311
+ entry; it **no longer depends on a separate `dsh-auth` plugin package**.
312
+ The `/provider` add branch signs in to ChatGPT/Codex (`openai-codex`), Claude
313
+ (`anthropic`), or Grok (`xai`) subscriptions. When the installed host's pi-ai
314
+ catalog ships the corresponding flows, it also offers OpenAI direct ChatGPT sign-in
315
+ (`openai`, using the OpenAI API rather than the legacy Codex backend) and Meta
316
+ Muse (`meta`). The primary validated DSH `0.2.0-rc.2` fixture carries pi-ai
317
+ `0.87.1`, which includes Meta Muse but not OpenAI direct sign-in.
318
+ `/auth status`, `/auth login [provider]`, and `/auth logout <provider>` use
319
+ that credential store for these pi-ai routes. Browser authorization tries to open the page
320
+ automatically and offers the authorization link and manual callback URL/code
321
+ input on one question;
322
+ device-code flows show a copyable code while polling. A host without an
323
+ interactive question surface refuses sign-in explicitly.
324
+
325
+ The `dsh-tui-auth` row accepts `providers` (default: all supported flows present
326
+ in the installed pi-ai catalog; an explicit non-empty subset must exist in
327
+ that catalog), `credentialsFile` (custom file path), and
328
+ `modelOverrides.<provider>.<model>` (optional `contextWindow` and `maxTokens`).
329
+ A profile override replaces the whole `config` block, so retain every field
330
+ you need. The flow implementation comes from the host's `dsh-llm-pi-ai` / pi-ai
331
+ installation. Subscription authentication uses its subscription backend; it
332
+ is not a general-purpose API key.
333
+
334
+ The default credential file remains `$DSH_HOME/dsh-auth/credentials.json`
335
+ (`~/.dsh/dsh-auth/credentials.json` when `DSH_HOME` is unset), with
336
+ `DSH_AUTH_CREDENTIALS` as an override. Existing sign-ins need no migration or
337
+ re-login. The `ctx.dshAuth` service name and `dsh-auth` log prefix remain for
338
+ compatibility; they do not imply a separate installed package. The file
339
+ contains long-lived refresh tokens; directory/file modes are best-effort
340
+ 0700/0600. OpenAI direct login lazily creates a stable UUID in a sibling
341
+ `device-id` file (0600); pi-ai uses it as the installation's agent-host ID.
342
+ Status surfaces show only sign-in and expiry metadata. Models appear in the
343
+ picker after sign-in. If another `llm-pi-ai` profile already owns the same
344
+ provider route, the OAuth route refuses duplicate registration.
345
+
346
+ ### DeepSeek account authorization
347
+
348
+ DSH `0.2.0-rc.1+` separately provides the Host-owned `deepseekAccount` service
349
+ and `deepseek-account` model route. The standard profile includes it in the
350
+ same `/provider` account sign-in branch and the `/auth status` and `/login`
351
+ lists. You can also run `/auth login deepseek-account` directly, then select
352
+ a `deepseek-account` model via `/model`. This is a **different route** from
353
+ `deepseek-official`, which still needs `DEEPSEEK_API_KEY`. The command
354
+ `/auth logout deepseek-account` delegates sign-out to the Host: it removes the Host's local
355
+ grant, while the Host handles remote revocation in the background. The TUI
356
+ does not implement PKCE, read the account token, or put the grant in the
357
+ `dsh-auth/credentials.json` file above. DeepSeek account grants have no pi-ai
358
+ token expiry/refresh timestamp, so status surfaces show only signed in/out.
359
+ `dsh-tui-auth.config.providers` and `modelOverrides` still control pi-ai
360
+ routes only; do not put `deepseek-account` in them.
361
+
362
+ The browser callback uses the Host `webServer` service. A TUI-only profile's
363
+ `dsh-tui-webserver` row listens on `127.0.0.1` with an OS-assigned port; a
364
+ mixed Web+TUI profile reuses the official `webserver` row instead of opening a
365
+ second listener. If a profile-only update leaves an older global TUI patch
366
+ that mounts `dsh-tui-auth` but lacks `dsh-tui-webserver`, the built-in OAuth
367
+ entry starts the same official Host listener on `127.0.0.1` at the first
368
+ DeepSeek sign-in, using an OS-assigned port. It reuses an existing listener
369
+ and closes only its own fallback when the OAuth module unmounts; a declared
370
+ but disabled or failed webserver row is not bypassed. The question
371
+ panel tries to open the authorization URL and offers the full link plus copy,
372
+ reopen, and cancel actions. The DeepSeek flow **requires the callback**; it
373
+ has no manual-code fallback. If a remote TUI
374
+ uses your local browser over SSH, override `dsh-tui-webserver.port` to a fixed
375
+ port and forward that same port (for example,
376
+ `ssh -L 43123:127.0.0.1:43123 ...`):
377
+
378
+ ```yaml
379
+ - id: dsh-tui-webserver
380
+ config:
381
+ host: 127.0.0.1
382
+ port: 43123
383
+ ```
384
+
385
+ In a mixed Web profile, override the official `webserver` row. A stale global
386
+ patch cannot apply a fixed-port override to a row it lacks, so SSH users must
387
+ also update the global TUI package before forwarding a fixed port. Missing or
388
+ unstartable callback listeners fail sign-in clearly without affecting the
389
+ other pi-ai OAuth routes.
390
+
391
+ ## Composition constraints
392
+
393
+ - `user-interaction` normally comes from `dsh-base`. The plugin creates a
394
+ fallback in a bare composition, but the profile patch must not insert a
395
+ duplicate.
396
+ - When manually inserting a subagent provider, mount the core `subagent`
397
+ service first.
398
+ - A custom `plan-mode` override requires a non-empty `section`.
399
+ - Profile mode uses the base JSONL persistence row rooted at the shared
400
+ `~/.dsh/sessions`, allowing TUI and Web to read the same history.
401
+ - `cordis.yml` is a bare-composition example and may have a different service
402
+ topology. Normal installation and user overrides should follow
403
+ `cordis.patch.yml`.
404
+
405
+ `DSH_TUI_SESSION_ROOT` always names a JSONL root. `dsh --profile dsh-tui`
406
+ defaults to `$DSH_HOME/sessions` (normally `~/.dsh/sessions/`); direct
407
+ `dsh --config cordis.yml` defaults to `~/.dsh-tui/sessions/`.
408
+
409
+ See [Architecture and limitations](architecture.en.md#permissions-and-security-boundary)
410
+ for permission behavior and platform differences.