@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
@@ -0,0 +1,166 @@
1
+ # Session migration: import conversations from other coding agents
2
+
3
+ [Documentation index](README.md) · [简体中文](migrate.md)
4
+
5
+ Bring Claude Code, Codex, OMP, zcode, and Grok Build conversation histories
6
+ into the DSH session store. After importing, `/resume` browses and restores
7
+ them by their original working directory — switching agents no longer costs
8
+ your history.
9
+
10
+ ```sh
11
+ dsh-tui migrate # list per-agent scannable file counts (writes nothing)
12
+ dsh-tui migrate claude-code # import every Claude Code conversation
13
+ dsh-tui migrate codex --dry-run # preview what would land, write nothing
14
+ ```
15
+
16
+ In-TUI equivalent: `/migrate`. Bare `/migrate` opens a **multi-select source
17
+ picker** — one row per agent (checkbox + scannable file count + an "active X
18
+ min ago" badge, most recently active first): Space toggles, `a` selects
19
+ all/none, Enter opens the **confirmation layer** (one line per checked
20
+ source with its count and the "existing sessions are skipped automatically,
21
+ safe to re-run" note; Enter imports / `d` dry-runs / Esc returns to the
22
+ picker), Esc closes. `/migrate <agent>` passes through the same layer (single
23
+ source); `/migrate <agent> --dry-run` previews directly since it writes
24
+ nothing. The completion notification carries per-source real counters
25
+ ("imported X · already present Y"). The CLI stays direct for scripts. The import
26
+ runs in a child process so the interface never freezes; results arrive
27
+ through the notification flow and the output lands in a `/migrate` local
28
+ row. Both entry points share the same import logic and idempotency rules.
29
+
30
+ ## Supported sources
31
+
32
+ | Source | Local store | Notes |
33
+ | --- | --- | --- |
34
+ | `claude-code` | `~/.claude/projects/` | Lines split from one response (sharing `message.id`) merge into one step; thinking, tool calls and results, compaction summaries, `/rename` and generated titles all migrate; `isMeta` context joins the next step; `subagents/` and auxiliary transcripts are not sessions |
35
+ | `codex` | `~/.codex/sessions/` | Tool calls with all three output shapes, reasoning summaries (the readable part), `compacted` summaries, interrupted turns and sub-agent reports migrate; the model is recorded per step from `turn_context`; injected blocks and sub-agent rollouts do not |
36
+ | `omp` | `~/.omp/agent/sessions/` | DSH-lineage store, a near-direct mapping (text and thinking) |
37
+ | `zcode` | `~/.zcode/v2/sessions/` | Single-JSON-object format; only user/assistant text is mapped, `meta.title` becomes the title |
38
+ | `grok-build` | `~/.grok/sessions/` (relocatable via `GROK_HOME`) | Reasoning rows attach to the assistant step that follows; tool calls and results (images as placeholders), compaction summaries, interrupted turns and the `session_summary` title migrate; `<user_query>` and similar wrappers keep only the body; synthetic rows and `<user_info>` do not migrate |
39
+
40
+ ## Behavior contract
41
+
42
+ - **Read-only source**: migration only reads the foreign agent's local store;
43
+ artifacts are written through the official `JsonlSessionPersistence` into
44
+ `$DSH_HOME/sessions` — imported sessions are first-class (openable,
45
+ continuable, rewindable).
46
+ - **Idempotent**: one deterministic UUID (v5) per source conversation.
47
+ Re-importing skips what is already present instead of stacking duplicates
48
+ or rewriting existing logs. Both entry points derive identical session ids
49
+ from the same source data.
50
+ - **Structure preserved**: rebuilt turn by turn, one step per model call in
51
+ the source: thinking, text, tool calls and their results sit in the step
52
+ that produced them, so an imported session can pick the work straight up.
53
+ - **Wire-legal tool traffic**: each result pairs back by call id to the step
54
+ that made the call (never to the newest step), in call order within the
55
+ step; an unanswered call gets an empty result, a result with no call is
56
+ dropped and counted, and a call id reused across steps is renamed. On
57
+ resume every tool_call is followed by its tool message. One result keeps
58
+ at most 64KB (the rest is cut and noted); images always become an
59
+ `[image]` placeholder.
60
+ - **Compaction checkpoints**: a context-compaction boundary in the source is
61
+ written as the same native compaction transaction `/compact` produces. The
62
+ raw events stay in the log; the model sees "summary + what followed", the
63
+ context the source agent itself continued with.
64
+ - **Injection filtering**: machine text a harness writes into the user role
65
+ (environment blocks, AGENTS.md instructions, system reminders, local-command
66
+ echoes) opens no turn and never titles a session; `<user_query>`, pasted
67
+ envelopes and similar wrappers keep only their body. Model-visible machine
68
+ context mid-turn (Claude's `isMeta`, Codex sub-agent reports) is kept as
69
+ the next step's input.
70
+ - **Titles**: a title the source owns (`/rename`, generated titles,
71
+ `session_summary`, `meta.title`) is written as `session/title`; without
72
+ one the first real prompt serves as a fallback but is not written, so DSH
73
+ derives it from the first user message itself. Titles are collapsed to one
74
+ line and capped at 80 characters.
75
+ - **Ready to continue**: like a native session, an imported log reserves an
76
+ empty system head in its first step; on resume the system prompt replaces
77
+ it instead of landing after the imported history.
78
+ - **Not migrated**: per-message original timestamps (event times are
79
+ import-time; the session start keeps the source record); the encrypted
80
+ part of Codex reasoning; Grok's provider-side tools
81
+ (`backend_tool_call`).
82
+ - **Robustness**: malformed lines, legal JSON `null` (whole-line or nested),
83
+ and files over 64MB are skipped safely; one failed conversation never
84
+ aborts the batch — failures are listed and reported with exit code 1.
85
+
86
+ ## Measured reference (real data, for scale expectations)
87
+
88
+ | Operation | Scale | Time |
89
+ | --- | --- | --- |
90
+ | List counts, five sources | ~3000 files | 0.7s (name matching, zero parsing) |
91
+ | Import zcode | 60 conversations | 2.3s |
92
+ | Import claude-code | 163 conversations (heavy thinking) | 39s |
93
+ | Import codex + omp | 728 + 1366 conversations | ~2.5 min |
94
+ | Re-import (idempotency) | any | < 1s, all already present |
95
+
96
+ Browse afterwards with `/resume`: sessions land in per-cwd directories
97
+ (named by the official encoding rules); titles and start times are readable;
98
+ conversations with thinking render as collapsible reasoning blocks in the
99
+ TUI.
100
+
101
+ ## Source tabs on the session screen
102
+
103
+ When you do not want a bulk import, browse by source on the session screen
104
+ (`/resume`, `/home`, `/agentview`): the title row shows a tab for every source
105
+ with conversations, and selecting one **imports just that conversation** and
106
+ opens it.
107
+
108
+ - Parsing is exactly `/migrate`'s, and the session id is derived from the source conversation the same way: both entries land a conversation on the same DSH session, and one already imported simply opens.
109
+ - Opening the session screen only checks whether each source has any conversation (each source's walk stops at its first candidate); a source's list is read when its tab is opened, from a summary scan (file heads/tails only). Within one run an unchanged conversation is not read again; no cache file is written.
110
+ - A conversation whose working directory no longer exists is not imported; the screen says so, and `/migrate` still imports it in bulk.
111
+ - The tabs do not change how `/migrate` or `dsh-tui migrate` behave.
112
+
113
+ ## Smart migration hint
114
+
115
+ About 12 seconds after the TUI starts, one background pass checks whether any
116
+ source saw file writes within the last 20 minutes (newest file mtime per
117
+ source) and surfaces a single notification: "Just came from <agent>?
118
+ /migrate imports it quickly". The scan is off the render path (sub-second)
119
+ and fires at most once per session; a source with no data stays silent. While
120
+ the hint is up, Enter (with an empty prompt) jumps straight into the picker
121
+ with that source pre-checked; any other key dismisses it.
122
+
123
+ ## Troubleshooting
124
+
125
+ - **`unknown agent`**: the authoritative source list is what bare
126
+ `dsh-tui migrate` prints.
127
+ - **`needs the profile's compiled copy`**: the profile's compiled output is
128
+ missing or too old — run `dsh-tui update` first.
129
+ - **Imports fewer than the scan count**: the scan count matches candidate
130
+ files by name; import additionally filters unreadable and empty
131
+ conversations, so landing slightly lower is expected.
132
+ - **Fresh `DSH_HOME` first run reports installation rejected**: profile
133
+ bootstrap hit a stale npm tarball; upgrading dsh heals it, or use an
134
+ existing profile meanwhile.
135
+ - **Nothing found at all**: confirm the foreign agent's data directory
136
+ exists under the current home; set `GROK_HOME` when grok-build lives
137
+ elsewhere.
138
+
139
+ ## Design notes (for maintainers and contributors)
140
+
141
+ - Parsing is separate from IO: `adapters/<source>.parse.ts` are pure
142
+ functions (raw text → turn model) over shared rules in `parse/` (jsonl
143
+ bad-line counting, injection recognition and unwrapping, title
144
+ normalization, tool-call pairing); adapters only discover files. OMP still
145
+ produces a role list, folded into turns mechanically by `fromRoleTurns`.
146
+ - grok's `synthetic_reason` filter: only default and explicitly `human`
147
+ user rows migrate; the summary row among `compaction_meta` becomes a
148
+ compaction checkpoint.
149
+ - Claude's legacy (2.0.x) `summary` record is a one-line leaf title at the
150
+ head of a file: it is treated as a title, not a compaction boundary.
151
+ - Codex `custom_tool_call` input is free-form (a patch, a JS snippet); it
152
+ migrates wrapped as `{"input": …}` so call arguments stay JSON.
153
+ - zcode: no sample with tool traffic or reasoning has been available, so
154
+ only the text roles are mapped and no field is guessed at.
155
+ - Timestamp boundary: grok rows carry no per-row time; the session start
156
+ comes from `summary.json`.
157
+ - Terminology: "migrate" here means this feature; it is unrelated to the
158
+ package-rename migration from `dsh-cc-tui` (see
159
+ [Getting started](getting-started.en.md)).
160
+
161
+ Implementation and verification live in `src/dsh-adapter/migrate/`,
162
+ `scripts/verify-migrate-parse.mjs` (per-source parsing rules over synthetic
163
+ fixtures), `scripts/verify-migrate.mjs` (event synthesis and the round trip
164
+ through the official read chain, wire legality and compaction checkpoints
165
+ included) and `scripts/verify-migrate-command.tsx` (the `/migrate` interaction
166
+ regression, mounted against the real Chat screen).
@@ -0,0 +1,132 @@
1
+ # 会话迁移:从其他编程代理导入对话
2
+
3
+ [文档索引](README.md) · [English](migrate.en.md)
4
+
5
+ 把 Claude Code、Codex、OMP、zcode、Grok Build 的本地对话历史导入 DSH
6
+ 会话库。迁移后 `/resume` 按原工作目录浏览并恢复这些对话——换代理不丢
7
+ 历史上下文。
8
+
9
+ ```sh
10
+ dsh-tui migrate # 列出各源可扫描的会话文件数(不写入)
11
+ dsh-tui migrate claude-code # 导入 Claude Code 的全部对话
12
+ dsh-tui migrate codex --dry-run # 只预览将落盘的内容,不写入
13
+ ```
14
+
15
+ TUI 内等效入口:`/migrate`。裸 `/migrate` 弹出**多选源选择器**——每行一个代理
16
+ (勾选框 + 可扫描文件数 + 「X 分钟前刚活动过」标记,最近活跃排最前):
17
+ 空格勾选/取消、`a` 全选/全不选、Enter 进入**二次确认层**(逐行列出将导入
18
+ 的源与数量,注明「已存在的自动跳过,可重复执行」;Enter 导入 / `d` 干跑
19
+ 预览 / Esc 返回选择)、Esc 关闭。`/migrate <agent>` 同样经过确认层(单源);
20
+ `/migrate <agent> --dry-run` 因不写盘直接预览。完成通知带逐源真实计数
21
+ (「导入 X · 已存在 Y」)。CLI 形态保持直达(脚本场景)。导入在
22
+ 子进程中运行,界面不会卡顿;结果经通知流汇报,输出汇入 `/migrate`
23
+ 本地行。两个入口走同一套导入逻辑与同一套幂等规则。
24
+
25
+ ## 支持的源
26
+
27
+ | 源 | 本地存储 | 说明 |
28
+ | --- | --- | --- |
29
+ | `claude-code` | `~/.claude/projects/` | 同一次响应拆成的多行(共用 `message.id`)合并为一步;thinking、工具调用与结果、压缩摘要、`/rename` 与生成标题都迁移;`isMeta` 上下文并入下一步;`subagents/` 与辅助 transcript 不成会话 |
30
+ | `codex` | `~/.codex/sessions/` | 工具调用与三种输出形态、reasoning 摘要(明文部分)、`compacted` 摘要、中断轮、子代理报告都迁移;模型按 `turn_context` 逐步记录;注入块与子代理 rollout 不迁移 |
31
+ | `omp` | `~/.omp/agent/sessions/` | 与 DSH 同源的近直接映射(文本与思考过程) |
32
+ | `zcode` | `~/.zcode/v2/sessions/` | 单 JSON 对象格式;只映射 user/assistant 文本,`meta.title` 作为标题 |
33
+ | `grok-build` | `~/.grok/sessions/`(可用 `GROK_HOME` 重定位) | reasoning 行附着到其后的 assistant 步;工具调用与结果(图片换占位)、压缩摘要、中断轮、`session_summary` 标题都迁移;`<user_query>` 等包装只留正文;合成注入行与 `<user_info>` 不迁移 |
34
+
35
+ ## 行为契约
36
+
37
+ - **只读源**:迁移只读取源代理的本地存储,绝不修改;产物经官方
38
+ `JsonlSessionPersistence` 写入 `$DSH_HOME/sessions`——导入的会话是
39
+ 一等公民(可打开、可续聊、可 rewind)。
40
+ - **幂等**:同一源对话命中同一确定性 UUID(v5)。重复导入跳过已存在
41
+ 项,不堆叠重复、不重写已有日志。两个入口(CLI 与 TUI)对同一源数据
42
+ 生成完全相同的会话 id。
43
+ - **结构保留**:按轮次还原,一步对应源里的一次模型调用:思考过程、正文、
44
+ 工具调用及其结果都在所属的步里,导入的会话可以直接接着做事。
45
+ - **工具调用 wire 合法**:结果按调用 id 挂回发起调用的那一步(不挂最近一步),
46
+ 同一步内按调用顺序排列;没有结果的调用补空结果,找不到调用的结果丢弃并
47
+ 计数,跨步复用的调用 id 改名。续聊时每个 tool_call 都紧跟它的 tool 消息。
48
+ 单条结果上限 64KB(超出截断并注明),图片一律换成 `[image]` 占位。
49
+ - **压缩检查点**:源里的上下文压缩边界写为与 `/compact` 同形的原生压缩事务。
50
+ 原始事件仍在日志里,模型只看到「摘要 + 之后的对话」,与源代理压缩后实际
51
+ 使用的上下文一致。
52
+ - **注入过滤**:harness 写进 user 角色的机器文本(环境块、AGENTS.md 说明、
53
+ system-reminder、本地命令回显等)不开轮、不当标题;`<user_query>` 与粘贴
54
+ 信封等包装只保留正文。轮中途模型可见的机器上下文(Claude 的 `isMeta`、
55
+ Codex 的子代理报告)作为下一步的输入保留。
56
+ - **标题**:源自带的标题(`/rename`、生成标题、`session_summary`、
57
+ `meta.title`)写为 `session/title`;没有时以首个真实提问兜底,但不写入
58
+ 日志,由 DSH 按首条 user 文本自行回退。标题统一折叠空白、截断到 80 字符。
59
+ - **续聊就绪**:导入日志与原生会话一样,在首步预留空的 system head;续聊时
60
+ 系统提示词替换进这个位置,而不是追加在导入历史之后。
61
+ - **不迁移的内容**:逐条消息的原始时间戳(事件时间取导入时刻,会话起始
62
+ 时间保留源记录);Codex reasoning 的加密部分;Grok 的提供方侧工具
63
+ (`backend_tool_call`)。
64
+ - **健壮性**:单条坏行、合法 JSON `null`(整行或子对象)、超 64MB 的
65
+ 文件均安全跳过;单个会话失败不中断整批,失败清单随退出码 1 汇报。
66
+
67
+ ## 实测参考(真实数据,供量级预期)
68
+
69
+ | 操作 | 规模 | 耗时 |
70
+ | --- | --- | --- |
71
+ | 列出五源计数 | ~3000 个文件 | 0.7 秒(名字匹配,零解析) |
72
+ | 导入 zcode | 60 会话 | 2.3 秒 |
73
+ | 导入 claude-code | 163 会话(含大量 thinking) | 39 秒 |
74
+ | 导入 codex + omp | 728 + 1366 会话 | 约 2.5 分钟 |
75
+ | 重复导入(幂等) | 任意 | < 1 秒,全部 already present |
76
+
77
+ 导入后用 `/resume` 浏览:会话按原工作目录分目录存放(目录名按官方
78
+ 编码规则生成),标题、起始时间可读;含思考过程的会话在 TUI 中以
79
+ 推理块呈现,可折叠查看。
80
+
81
+ ## 会话管理界面的来源标签
82
+
83
+ 不想整批导入时,在会话管理界面(`/resume`、`/home`、`/agentview`)里按来源浏览:
84
+ 标题栏右侧为每个有会话的源显示一个标签,选中一条会话即**只导入这一条**并直接打开。
85
+
86
+ - 解析与 `/migrate` 完全相同,会话 id 同样按源会话确定性生成:两个入口导入同一条会话得到同一个 DSH 会话,已导入的直接打开。
87
+ - 打开会话管理界面只探测各来源是否有会话(每个来源找到第一个候选即停);列表在点进标签时才读取,来自摘要扫描(只读文件头/尾)。同一次运行里没有变化的会话不重读;不写任何缓存文件。
88
+ - 会话原工作目录已不存在时不导入,界面给出提示;这时仍可用 `/migrate` 整批导入。
89
+ - 标签页不改变 `/migrate` 与 `dsh-tui migrate` 的行为。
90
+
91
+ ## 智能迁移提示
92
+
93
+ TUI 启动约 12 秒后做一次后台检测:任一源在最近 20 分钟内有文件写入
94
+ (以各源会话文件的最新修改时间为信号)时,弹出一次通知「刚刚从xx过来?
95
+ /migrate 来快速迁移」。检测在后台运行(亚秒级),每会话只提示一次;
96
+ `grok-build` 数据缺失时静默跳过。提示显示期间按 Enter(输入框为空时)
97
+ 直达选择器并自动勾选该源,其他按键照常。
98
+
99
+ ## 故障排查
100
+
101
+ - **`unknown agent`**:源名以 `dsh-tui migrate` 无参输出的名单为准。
102
+ - **`needs the profile's compiled copy`**:profile 内编译产物缺失或过旧,
103
+ 先运行 `dsh-tui update`。
104
+ - **导入数低于扫描计数**:扫描计数是候选文件数(按文件名匹配),
105
+ 导入会过滤解析失败与空对话,略低属正常。
106
+ - **全新 `DSH_HOME` 首跑报 installation rejected**:profile 自举撞上
107
+ npm 上陈旧的 tarball 版本,升级 dsh 后自愈;或先用已有 profile。
108
+ - **一个会话都没找到**:确认源代理的数据目录存在于当前用户家目录;
109
+ `grok-build` 用 `GROK_HOME` 重定位时需在环境变量中设置。
110
+
111
+ ## 设计取舍(维护者与贡献者参考)
112
+
113
+ - 解析与 IO 分离:`adapters/<源>.parse.ts` 是纯函数(原始文本 → 轮模型),
114
+ 共享规则在 `parse/`(jsonl 坏行计数、注入识别与包装剥离、标题归一、
115
+ 工具调用配对);adapter 只做文件发现。OMP 仍产出角色列表,经
116
+ `fromRoleTurns` 机械折成轮。
117
+ - grok 的 `synthetic_reason` 过滤:只迁缺省与显式 `human` 的用户行;
118
+ `compaction_meta` 中的摘要行转为压缩检查点。
119
+ - Claude 旧格式(2.0.x)的 `summary` 记录是写在文件头的一行叶子标题,
120
+ 按标题处理,不当作压缩边界。
121
+ - Codex 的 `custom_tool_call` 输入是自由格式(patch、JS 片段),迁移时包成
122
+ `{"input": …}`,保证调用参数是 JSON。
123
+ - zcode:没有带工具调用或 reasoning 的样本,只映射文本角色,不猜测字段。
124
+ - 时间戳边界:grok 行内无逐条时间,会话起始时间取 `summary.json`。
125
+ - 术语:迁移(migrate)指本功能;与「从 dsh-cc-tui 更名迁移」
126
+ (见[安装与快速开始](getting-started.md))无关。
127
+
128
+ 实现与验证细节见 `src/dsh-adapter/migrate/`、
129
+ `scripts/verify-migrate-parse.mjs`(逐源解析规则,合成 fixture)、
130
+ `scripts/verify-migrate.mjs`(事件合成与官方读取链往返,含 wire 合法性与
131
+ 压缩检查点)与
132
+ `scripts/verify-migrate-command.tsx`(挂真实 Chat 的 `/migrate` 交互回归)。
@@ -0,0 +1,68 @@
1
+ # Plugin Development Guide (merged into spec)
2
+
3
+ [Documentation index](README.md) · [简体中文](plugins.md)
4
+
5
+ > This document has been merged with the ecosystem admission specification:
6
+ > [Plugin Admission and Development Guide](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md)
7
+
8
+ The ecosystem entry points and seam stability reference below are kept here
9
+ for quick reference; the authoritative status and compatibility agreement
10
+ live in the admission & development guide.
11
+
12
+ ## Ecosystem entry points
13
+
14
+ - **Interface & compatibility agreement / Plugin development guide**:
15
+ [Terminal Interactive Ecosystem Plugin Admission and Development Guide](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md)
16
+ (admission spec, seams, contracts, verification checklist).
17
+ - **Organization**:
18
+ [dsh-tui-ecosystem](https://github.com/dsh-tui-ecosystem)
19
+ (home of community plugins and templates).
20
+ - **Template repository**:
21
+ [plugin-template](https://github.com/dsh-tui-ecosystem/plugin-template)
22
+ (start from the template and ship a plugin in minutes).
23
+ - **Reference implementation**: `dsh-working-activity` (live working-status
24
+ line with dual outlets: TUI prompt slot + `workingActivity` session
25
+ projection).
26
+
27
+ ## Seam stability reference
28
+
29
+ An **informal** maturity grading to help plugin authors gauge investment;
30
+ the authoritative status and compatibility agreement live in the
31
+ [admission & development guide](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md):
32
+
33
+ | Tier | Seams |
34
+ | --- | --- |
35
+ | Stable candidate (shape frozen; breaking changes go through a minor-version deprecation warning before removal) | VI settings sections · VIII full-screen scenes · X managed dialogs · XI status line · XII keyboard shortcuts · XIII entry renderers |
36
+ | Experimental (may still shift with dsh-std / admission-spec evolution) | IX decision events · toast notifications (`ctx.tuiToast`, new) |
37
+ | Upstream-tracked (stability owned by the cordis / dsh mechanisms underneath) | I session events · II official prompt slots · III bundled skills · IV themes · V system-prompt sections · VII profile composition |
38
+
39
+ Also an experimental public surface:
40
+
41
+ - `@deepseek-harness-tui/dsh-tui/api` (types-only entry).
42
+ - The `@deepseek-harness-tui/dsh-tui/test-utils` subpath and
43
+ `ctx.tuiPluginHost.grants.corrupt` were removed in the adapter layering
44
+ refactor (#705).
45
+ - `grants` is now the narrower `HostGrantFacade`; see that PR for migration
46
+ details.
47
+ - On `TuiSceneProps.channel` (`ChannelUi`), `minimal` / `setMinimal()` were
48
+ renamed to `minimalUi` / `setMinimalUi()`; the old names stay as
49
+ **deprecated aliases** reading and writing the same Minimal UI switch, so
50
+ existing full-screen scene plugins keep working. That switch only trims
51
+ interface decoration and is unrelated to the kernel `minimal` agent preset
52
+ (极简模式).
53
+ **Removal condition (decidable): v0.13** — the rename and deprecated aliases
54
+ first ship in v0.12.0, leaving one released minor-version deprecation window.
55
+ At that cut, scan the scene-plugin consumption surface (this repo's `src/**`
56
+ re-exports and every plugin on the dsh-tui-ecosystem org that reaches the
57
+ port through `TuiSceneProps.channel`) for `.minimal` / `.setMinimal(`: zero
58
+ callers means both aliases and the `'setMinimal': 'mutate'` row in
59
+ `ui-policy.ts` are deleted in v0.13; any remaining caller is migrated within
60
+ that same release rather than postponing the removal again.
61
+
62
+ - The core repository remains independent; community plugins live in their own
63
+ repos.
64
+ - The organization only maintains the listing and admission rules — it does
65
+ not endorse or warrant the functionality, quality, or safety of community
66
+ plugins.
67
+ - Plugin authors keep full ownership of their repositories and are responsible
68
+ for their maintenance and security.
@@ -0,0 +1,56 @@
1
+ # 插件开发指南(已并入 spec)
2
+
3
+ [文档索引](README.md) · [English](plugins.en.md)
4
+
5
+ > 本文档已与 dsh-ecosystem-spec 的准入规范整合,请阅读:
6
+ > [终端交互生态插件准入与开发指南](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md)
7
+
8
+ 以下生态入口与接缝稳定性分级仅为速览保留;正式状态与兼容性协定
9
+ 以准入与开发指南为准。
10
+
11
+ ## 生态入口
12
+
13
+ - **接口与兼容性协定 / 插件开发指南**:
14
+ [终端交互生态插件准入与开发指南](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md)
15
+ (准入规范、接缝、契约、验证清单)。
16
+ - **生态组织**:
17
+ [dsh-tui-ecosystem](https://github.com/dsh-tui-ecosystem)
18
+ (社区插件与模板的家)。
19
+ - **模板仓库**:
20
+ [plugin-template](https://github.com/dsh-tui-ecosystem/plugin-template)
21
+ (从模板起步,5 分钟出一个插件)。
22
+ - **参考实现**:`dsh-working-activity`(实时工作状态行:TUI 槽位 +
23
+ `workingActivity` 会话投影双出口)。
24
+
25
+ ## 接缝稳定性参考
26
+
27
+ 按当前实现成熟度给出的**非正式**分级,帮助插件作者评估投入;正式
28
+ 状态与兼容性协定以[准入与开发指南](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md)为准:
29
+
30
+ | 分级 | 接缝 |
31
+ | --- | --- |
32
+ | 稳定候选(形态冻结;如有破坏性变更,先在次版本弃用告警再移除) | 六 设置区块 · 八 全屏场景 · 十 托管对话框 · 十一 状态行 · 十二 键盘快捷键 · 十三 条目渲染器 |
33
+ | 实验性(仍可能随 dsh-std / 准入规范演进调整) | 九 决策事件 · toast 通知(`ctx.tuiToast`,新增) |
34
+ | 跟随上游(稳定性由 cordis / dsh 官方机制决定) | 一 会话事件 · 二 官方 prompt 槽位 · 三 技能打包 · 四 主题 · 五 system prompt 段 · 七 profile 组合 |
35
+
36
+ 另:
37
+
38
+ - `@deepseek-harness-tui/dsh-tui/api`(纯类型入口)为实验性公开面。
39
+ - `@deepseek-harness-tui/dsh-tui/test-utils` 子路径与
40
+ `ctx.tuiPluginHost.grants.corrupt` 已随 adapter 分层重构(#705)移除。
41
+ - `grants` 收窄为 `HostGrantFacade`,迁移细节见该 PR。
42
+ - `TuiSceneProps.channel`(`ChannelUi`)上的 `minimal` / `setMinimal()` 已更名为
43
+ `minimalUi` / `setMinimalUi()`;旧名保留为 **deprecated 别名**,读写同一个
44
+ 「极简界面」开关,既有全屏场景插件不受影响。该开关只精简界面装饰,与内核
45
+ Agent preset `minimal`(极简模式)无关。
46
+ **移除条件(可判定):v0.13**——更名与弃用别名首次随 v0.12.0 发布,
47
+ 因此先保留一个已发布的次版本弃用窗口。届时对场景插件消费面
48
+ (本仓库 `src/**` 的再导出,以及 dsh-tui-ecosystem 组织下经
49
+ `TuiSceneProps.channel` 取用该端口的插件)做一次 `.minimal` / `.setMinimal(`
50
+ 扫描:零调用方即在 v0.13 删除这两个别名与 `ui-policy.ts` 里的
51
+ `'setMinimal': 'mutate'` 行;仍有调用方则在同一版本内完成迁移,不再顺延。
52
+
53
+ 核心仓库保持独立,社区插件各居其位。
54
+ 生态组织只维护收录与准入规则——不对社区插件的功能、质量或安全性
55
+ 作任何背书或担保。
56
+ 插件作者对自己的仓库保有完全所有权,并负责其维护与安全。
@@ -0,0 +1,192 @@
1
+ # Themes
2
+
3
+ [Documentation index](README.md) · [简体中文](themes.md)
4
+
5
+ ## Built-in themes
6
+
7
+ dsh-TUI provides three Gentle Mist Blue palettes, plus an `auto` pseudo-theme:
8
+
9
+ | Name | Purpose |
10
+ | --- | --- |
11
+ | `auto` | Pseudo-theme: follows the system/terminal background, resolving to `light` or `dark` |
12
+ | `light` | White panels, ink body text, and mist-blue interaction color |
13
+ | `dark` | Dark-terminal adaptation with warm-gray text and soft blue accents |
14
+ | `dark-ansi` | Compatibility fallback using only the 16 ANSI colors |
15
+
16
+ Without an explicit choice, the TUI queries the terminal background with OSC
17
+ 11 and selects `light` or `dark`. It falls back to `dark` when the terminal
18
+ does not answer.
19
+
20
+ Light-theme panels, tool cards, and image previews use white (`#FFFFFF`)
21
+ surfaces by default; image previews use neutral borders. Dark palettes and
22
+ accent colors are unchanged. This does not modify the terminal's own
23
+ background or wallpaper.
24
+
25
+ `auto` turns that one-shot startup detection into a standing choice:
26
+
27
+ - A valid value for `/theme`, `DSH_TUI_THEME`, and `~/.dsh-tui/theme.json`.
28
+ - Selecting `auto` applies the last detected base immediately and re-queries
29
+ OSC 11 in the background.
30
+ - On terminals that follow the system theme, picking `auto` again (or
31
+ restarting) catches up after a system light/dark switch.
32
+ - `/theme status` shows which palette `auto` currently resolves to.
33
+ - `getTheme('auto')` serves that palette to every consumer.
34
+ - A user theme named `auto` is shadowed by the built-in pseudo-theme (not
35
+ listed in the picker).
36
+
37
+ Selection precedence is:
38
+
39
+ ```text
40
+ DSH_TUI_THEME
41
+ > persisted choice in ~/.dsh-tui/theme.json
42
+ > OSC 11 background detection
43
+ > dark fallback
44
+ ```
45
+
46
+ ## Switching themes
47
+
48
+ - `/theme` opens the picker, with `auto` and the built-ins before static JSON
49
+ and plugin themes.
50
+ - `/theme <name>` switches directly to a static or runtime plugin theme.
51
+ - `/theme status` shows the current theme and persistence location.
52
+
53
+ Confirming a choice hot-switches immediately and writes it to
54
+ `~/.dsh-tui/theme.json`. `DSH_TUI_THEME`, when set, still wins on the next
55
+ launch.
56
+
57
+ ## Custom themes
58
+
59
+ Place JSON files under `~/.dsh-tui/themes/`. Each file starts from one
60
+ built-in palette and overrides a subset of its colors:
61
+
62
+ ```json
63
+ {
64
+ "name": "sakura",
65
+ "displayName": "Sakura",
66
+ "base": "dark",
67
+ "colors": {
68
+ "accent": "#FF9EC7",
69
+ "accentShimmer": "#FFC0D5",
70
+ "activity": "#7DA1DE",
71
+ "activityShimmer": "#ABC2EC",
72
+ "mascotBody": "#D98A63",
73
+ "inputBackground": "#000000",
74
+ "permission": "#FFB3CC",
75
+ "promptBorder": "#B08B99",
76
+ "text": "#E8E6E0",
77
+ "inactive": "#A99BA0",
78
+ "subtle": "#8A7A80",
79
+ "selectionBg": "#5C3A44",
80
+ "success": "#9CC7A8",
81
+ "error": "#E08591",
82
+ "warning": "#E0C08A"
83
+ }
84
+ }
85
+ ```
86
+
87
+ Fields:
88
+
89
+ | Field | Required | Meaning |
90
+ | --- | --- | --- |
91
+ | `base` | Yes | `light`, `dark`, or `dark-ansi`; source for every non-overridden color |
92
+ | `colors` | Yes | Partial override of Theme color keys |
93
+ | `name` | No | Theme ID; defaults to the filename |
94
+ | `displayName` | No | Picker label; defaults to `name` |
95
+
96
+ Available color keys by purpose:
97
+
98
+ - Brand/focus/activity: `accent`, `accentShimmer`, `activity`, `activityShimmer`, `suggestion`, `remember`
99
+ - Panels/borders: `permission`, `permissionShimmer`, `promptBorder`, `promptBorderShimmer`, `bashBorder`, `planMode`, `ide`, `background`
100
+ - Body text: `text`, `inverseText`, `inactive`, `inactiveShimmer`, `subtle`
101
+ - Tool names & status dots: `toolNameMutate`, `toolNameExec`, `toolDotExec`, `toolDotRead`, `toolDotWrite`, `toolDotWeb`, `toolDotTask`
102
+ - Tool card surfaces: `toolCardBackground`, `toolCardBackgroundDim`
103
+ - Status: `autoAccept`, `success`, `error`, `warning`, `warningShimmer`, `merged`
104
+ - Diff: `diffAdded`, `diffRemoved`, `diffAddedDimmed`, `diffRemovedDimmed`, `diffAddedWord`, `diffRemovedWord`
105
+ - Diff syntax highlighting: `syntaxKeyword`, `syntaxString`, `syntaxComment`, `syntaxNumber`, `syntaxFunction`, `syntaxType`
106
+ - Diff syntax highlighting (cont.): `syntaxVariable`, `syntaxOperator`, `syntaxPunctuation`, `syntaxConstant`
107
+ - Badges/accents: `mascotBody`, `inputBackground`, `professionalBlue`, `chromeYellow`
108
+ - Messages & input: `userMessageBackground`, `userMessageBackgroundHover`, `messageActionsBackground`, `selectionBg`, `bashMessageBackgroundColor`
109
+ - Messages & input (cont.): `memoryBackgroundColor`, `rate_limit_fill`, `rate_limit_empty`, `fastMode`, `fastModeShimmer`, `userPromptLabel`
110
+ - Subagent messages: `subagentBullet`, `subagentDescription`, `subagentModel`, `subagentElapsed`, `subagentToolName`, `subagentStatusRunning`
111
+ - Subagent messages (cont.): `subagentStatusCompleted`, `subagentStatusFailed`
112
+
113
+ When the file declares `name`, its filename remains a loading alias. See the
114
+ `Theme` type in [`src/theme.ts`](../src/theme.ts) for every color key.
115
+
116
+ ## npm plugin themes
117
+
118
+ An npm plugin registers a runtime theme through the `tuiThemes` service,
119
+ without writing to `~/.dsh-tui/themes/`. Minimal example:
120
+
121
+ ```ts
122
+ import type { Context } from '@deepseek-ai/cordis'
123
+
124
+ export function apply(ctx: Context): void {
125
+ ctx.get('tuiThemes', false)?.register({
126
+ name: 'my-plugin:night',
127
+ base: 'dark',
128
+ colors: { accent: '#88AAFF' },
129
+ }, ctx)
130
+ }
131
+ ```
132
+
133
+ - Use a lowercase safe ID such as `plugin-id:theme-id`.
134
+ - `auto`, built-in names, and `status` are reserved.
135
+ - Registrations are removed with the plugin activation; the returned disposer
136
+ can remove one early.
137
+ - Plugin themes appear in the `/theme` picker and completion; their names use
138
+ the existing `~/.dsh-tui/theme.json` persistence.
139
+ - Priority: built-ins > static JSON > same-name plugin theme.
140
+ - On an older profile without `tuiThemes`, the plugin degrades silently and
141
+ static themes remain unaffected.
142
+
143
+ See the [Plugin Admission and Development Guide](https://github.com/T-Auto/dsh-ecosystem-spec/blob/main/docs/plugin-admission-and-development.md)
144
+ for the full key list, legacy-key mapping, and registration contract.
145
+
146
+ ## Color formats
147
+
148
+ Accepted forms:
149
+
150
+ - `#rgb`
151
+ - `#rrggbb`
152
+ - `#rrggbbaa`
153
+ - `rgb(r,g,b)`
154
+ - `ansi256(n)`
155
+ - 16-color names such as `ansi:black` and `ansi:redBright`
156
+
157
+ Colors must be concrete values. CSS variables, gradients, and arbitrary CSS
158
+ color names are not accepted.
159
+
160
+ ## Validation and failure behavior
161
+
162
+ - Unknown Theme key: skip that key with a warning and keep the rest.
163
+ - Invalid color: skip that value with a warning.
164
+ - Invalid `base`, malformed JSON, or non-object `colors`: skip the whole file.
165
+ - Missing theme referenced by the environment or preference file: warn and
166
+ continue with background detection.
167
+ - One bad theme never blocks TUI startup or other themes.
168
+
169
+ Theme names are user input. The loader verifies that the resolved path
170
+ remains inside `~/.dsh-tui/themes/`, preventing names from escaping the theme
171
+ directory. Preserve that containment check when changing the implementation.
172
+
173
+ ## Design guidance
174
+
175
+ - Use color keys instead of changing only `text` and `background`. Check at
176
+ least body, inactive, focus, selection, success, warning, error, and diff
177
+ colors.
178
+ - Test light themes in a real light terminal and dark themes in a dark one.
179
+ - Check 16-color, 256-color, and truecolor fallback behavior.
180
+ - Verify narrow layouts, tool diffs, questionnaires, multiline input, and
181
+ selection contrast.
182
+ - Theme files should contain display metadata and color only, never credentials
183
+ or other user data.
184
+
185
+ When developing the theme subsystem, run:
186
+
187
+ ```sh
188
+ node --import tsx/esm scripts/verify-themes.mjs
189
+ ```
190
+
191
+ See [Architecture and limitations](architecture.en.md) for terminal capability
192
+ and renderer details.