dsh-code 1.0.6 → 1.2.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 (86) hide show
  1. package/README.en.md +123 -26
  2. package/README.md +124 -27
  3. package/bin/deepseek.mjs +283 -35
  4. package/cordis.patch.yml +97 -0
  5. package/lib/index.mjs +5008 -881
  6. package/lib/session-query.mjs +150 -0
  7. package/lib/startup.mjs +4 -4
  8. package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
  9. package/lib/types/app.d.ts +106 -62
  10. package/lib/types/authorization-panel.d.ts +3 -3
  11. package/lib/types/git-workflow.d.ts +91 -2
  12. package/lib/types/i18n.d.ts +39 -0
  13. package/lib/types/index.d.ts +100 -1
  14. package/lib/types/input-split.d.ts +1 -1
  15. package/lib/types/kernel-panels.d.ts +107 -29
  16. package/lib/types/language-panel.d.ts +12 -0
  17. package/lib/types/locales/en.d.ts +450 -0
  18. package/lib/types/locales/zh.d.ts +9 -0
  19. package/lib/types/mentions.d.ts +7 -3
  20. package/lib/types/models.d.ts +14 -0
  21. package/lib/types/panel-accent.d.ts +28 -0
  22. package/lib/types/rainbow.d.ts +69 -0
  23. package/lib/types/render/animations.d.ts +42 -0
  24. package/lib/types/render/editor.d.ts +4 -3
  25. package/lib/types/render/ime-cursor.d.ts +60 -0
  26. package/lib/types/render/inspector.d.ts +26 -0
  27. package/lib/types/render/lines.d.ts +21 -1
  28. package/lib/types/render/markdown.d.ts +1 -1
  29. package/lib/types/render/projection.d.ts +130 -4
  30. package/lib/types/render/status.d.ts +9 -9
  31. package/lib/types/render/text.d.ts +6 -0
  32. package/lib/types/render/usage.d.ts +113 -0
  33. package/lib/types/session-directory.d.ts +17 -0
  34. package/lib/types/session-query.d.ts +92 -0
  35. package/lib/types/startup.d.ts +1 -1
  36. package/lib/types/terminal-title.d.ts +66 -0
  37. package/lib/types/theme-panel.d.ts +2 -2
  38. package/lib/types/theme.d.ts +271 -52
  39. package/lib/types/update-panel.d.ts +49 -0
  40. package/lib/types/update.d.ts +75 -0
  41. package/lib/types/version.d.ts +4 -3
  42. package/package.json +246 -90
  43. package/src/app.ts +1369 -509
  44. package/src/approval.ts +166 -166
  45. package/src/authorization-panel.ts +19 -16
  46. package/src/editor-keys.ts +371 -371
  47. package/src/git-workflow.ts +229 -3
  48. package/src/i18n.ts +68 -0
  49. package/src/index.ts +534 -80
  50. package/src/input-split.ts +3 -3
  51. package/src/internals.ts +5 -0
  52. package/src/kernel-panels.ts +554 -86
  53. package/src/keyboard.ts +5 -4
  54. package/src/language-panel.ts +53 -0
  55. package/src/locales/en.ts +489 -0
  56. package/src/locales/zh.ts +488 -0
  57. package/src/mentions.ts +8 -4
  58. package/src/models.ts +264 -212
  59. package/src/panel-accent.ts +41 -0
  60. package/src/presets.ts +1 -1
  61. package/src/provider-settings.ts +1 -1
  62. package/src/rainbow.ts +208 -0
  63. package/src/render/animations.ts +104 -6
  64. package/src/render/editor.ts +25 -24
  65. package/src/render/export.ts +116 -95
  66. package/src/render/ime-cursor.ts +147 -0
  67. package/src/render/inspector.ts +42 -0
  68. package/src/render/lines.ts +628 -415
  69. package/src/render/markdown.ts +15 -3
  70. package/src/render/projection.ts +572 -21
  71. package/src/render/status.ts +59 -39
  72. package/src/render/text.ts +14 -0
  73. package/src/render/tool-preview.ts +77 -77
  74. package/src/render/usage.ts +430 -0
  75. package/src/render/width.ts +2 -2
  76. package/src/session-directory.ts +8 -6
  77. package/src/session-query.ts +239 -0
  78. package/src/startup.ts +3 -3
  79. package/src/subagents.ts +229 -229
  80. package/src/terminal-title.ts +190 -0
  81. package/src/theme-panel.ts +17 -21
  82. package/src/theme.ts +281 -33
  83. package/src/update-panel.ts +256 -0
  84. package/src/update.ts +126 -0
  85. package/src/version.ts +58 -20
  86. package/src/whale-glyph.ts +23 -23
package/src/keyboard.ts CHANGED
@@ -129,9 +129,10 @@ function legacyForKey(key: CsiUKey): string | undefined {
129
129
  const alt = (bits & 2) !== 0
130
130
  const ctrl = (bits & 4) !== 0
131
131
  if (key.code === 13) {
132
- // Modified Enter has no dedicated composer behavior. Preserve the legacy
133
- // Ctrl/Alt bytes and collapse every other form to ordinary Enter.
134
- if (ctrl) return '\n'
132
+ // Modified Enter is the composer's newline family (Codex insert_newline):
133
+ // Ctrl+Enter and Shift+Enter collapse to the LF byte Ctrl+J already sends,
134
+ // Alt keeps its legacy escape form, and plain Enter stays the submit key.
135
+ if (ctrl || shift) return '\n'
135
136
  if (alt) return '\x1b\r'
136
137
  return '\r'
137
138
  }
@@ -268,7 +269,7 @@ export function tokenizeRawEditorChunk(chunk: string): readonly RawEditorToken[]
268
269
  offset += functional.length
269
270
  continue
270
271
  }
271
- const char = chunk[offset]!
272
+ const char = chunk[offset]
272
273
  if (char === '\x7f' || char === '\b') {
273
274
  flushText()
274
275
  tokens.push({ kind: 'delete-backward' })
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The /language picker: one row per interface language, the active one
3
+ * marked, arrow selection, Enter applies (persisting through the runner and
4
+ * repainting the whole screen so Static-region text switches too).
5
+ */
6
+
7
+ import { createElement, useState, type ReactElement } from 'react'
8
+ import { Box, Text, useInput, useStdout } from 'ink'
9
+ import { panelViewport } from './render/inspector.ts'
10
+ import { truncateColumns } from './render/text.ts'
11
+ import { LANGUAGES, t, type LanguageName } from './i18n.ts'
12
+ import { getPalette, inkColor } from './theme.ts'
13
+
14
+ export function LanguagePanel({ current, select, close }: {
15
+ current: LanguageName
16
+ select: (name: LanguageName) => void
17
+ close: () => void
18
+ }): ReactElement {
19
+ const [cursor, setCursor] = useState(() => {
20
+ const index = LANGUAGES.findIndex(language => language.id === current)
21
+ return index < 0 ? 0 : index
22
+ })
23
+ const stdout = useStdout().stdout
24
+ const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30)
25
+ useInput((input, key) => {
26
+ if (key.escape || input === 'q') return close()
27
+ if (key.upArrow) return setCursor(value => (value + LANGUAGES.length - 1) % LANGUAGES.length)
28
+ if (key.downArrow) return setCursor(value => (value + 1) % LANGUAGES.length)
29
+ if (key.return) return select(LANGUAGES[cursor].id)
30
+ })
31
+ if (viewport.maxHeight === 0 || viewport.compact) {
32
+ return createElement(Text, { wrap: 'truncate-end' }, truncateColumns('/language · esc/q close', viewport.contentColumns))
33
+ }
34
+ return createElement(
35
+ Box,
36
+ { width: viewport.outerColumns, borderStyle: 'round', borderColor: inkColor(getPalette().dim), flexDirection: 'column', paddingX: 1 },
37
+ createElement(Text, { color: inkColor(getPalette().brandBright), wrap: 'truncate-end' }, truncateColumns(t('language.title'), viewport.contentColumns)),
38
+ ...LANGUAGES.map((language, index) => {
39
+ const selected = index === cursor
40
+ const active = language.id === current
41
+ return createElement(
42
+ Text,
43
+ {
44
+ key: language.id,
45
+ color: selected ? inkColor(getPalette().brandBright) : undefined,
46
+ wrap: 'truncate-end',
47
+ },
48
+ truncateColumns(`${selected ? '› ' : ' '}${language.label}${active ? ' ●' : ''}`, viewport.contentColumns),
49
+ )
50
+ }),
51
+ createElement(Text, { dimColor: true, wrap: 'truncate-end' }, truncateColumns(t('language.hint'), viewport.contentColumns)),
52
+ )
53
+ }
@@ -0,0 +1,489 @@
1
+ /**
2
+ * The English message catalog: every user-visible UI string the terminal
3
+ * renders, keyed for {@link t}. The catalog is a plain record so missing
4
+ * keys fail typecheck against the shared key union, not at runtime.
5
+ *
6
+ * @module @deepseek-ai/dsh-tui/locales/en
7
+ */
8
+
9
+ export const en = {
10
+ // Shared panel chrome
11
+ 'panel.close': 'esc/q close',
12
+ 'panel.moveEnterSelect': '↑↓ move · enter select · esc close',
13
+ 'panel.moveEnterSelectBack': '↑↓ move · enter select · esc back · q close',
14
+ 'panel.scrollEnds': '↑↓ scroll · pgup/pgdn page · g/G ends · esc/q close',
15
+ 'panel.loading': 'loading…',
16
+ 'panel.loadFailed': 'load failed',
17
+ 'panel.noMatch': 'no matching entries',
18
+ 'panel.emptyClose': 'esc close',
19
+ 'panel.searchIdle': 'search: type to filter',
20
+ 'panel.searchIdleStop': 'search: type to filter · esc stops',
21
+ 'panel.searchPrefix': 'search: ',
22
+
23
+ // /language panel
24
+ 'language.title': '/language — interface language',
25
+ 'language.hint': '↑↓ move · enter apply · esc/q close',
26
+
27
+ // /theme panel
28
+ 'theme.title': '/theme — color palette',
29
+ 'theme.compact': '/theme · esc close',
30
+ 'theme.current': 'current',
31
+ 'theme.dark.label': 'dark',
32
+ 'theme.dark.description': 'DeepSeek dark palette (default)',
33
+ 'theme.light.label': 'light',
34
+ 'theme.light.description': 'light palette for bright terminals',
35
+ 'theme.prismatic.label': 'prismatic',
36
+ 'theme.prismatic.description': 'neon synthwave palette (violet/magenta/cyan)',
37
+ 'theme.rainbow.label': 'rainbow',
38
+ 'theme.rainbow.description': 'randomized carnival palette (/rainbow to reroll)',
39
+ 'theme.auto.label': 'auto',
40
+ 'theme.auto.description': 'follow the terminal; dark until detection lands',
41
+
42
+ // /history panel
43
+ 'history.title.prompts': '/history · {count} prompts · type to filter',
44
+ 'history.title.match': "/history · {matches} of {count} match '{query}'",
45
+ 'history.filterHint': 'filter {state}, enter fills the composer',
46
+ 'history.filterEmpty': '· type to search prompts',
47
+ 'history.filterQuery': '· {query}',
48
+ 'history.empty': 'no matching prompts',
49
+ 'history.compact.none': 'no matching prompts',
50
+ 'history.footer': '↑↓ move · g/G ends · enter fill · esc close',
51
+
52
+ // /search panel
53
+ 'search.title.type': '/search · type a query and press enter',
54
+ 'search.title.hits': "/search · {count} hit for '{query}'",
55
+ 'search.title.hitsPlural': "/search · {count} hits for '{query}'",
56
+ 'search.hint': '{state} · enter searches or resumes',
57
+ 'search.hintEmpty': 'type to search sessions',
58
+ 'search.empty.idle': 'full-text search across every persisted session',
59
+ 'search.empty.noHits': 'no matching sessions{loading}',
60
+ 'search.compact.noResults': 'no results yet',
61
+ 'search.compact.searching': 'searching…',
62
+ 'search.compact.error': 'error: {message}',
63
+ 'search.footer': '↑↓ move · enter search/resume · esc close',
64
+
65
+ // /statusline panel
66
+ 'statusline.title': '/statusline · items apply to the live status line below',
67
+
68
+ // /schedule panel
69
+ 'schedule.title': '/schedule · {count} active reminder',
70
+ 'schedule.titlePlural': '/schedule · {count} active reminders',
71
+ 'schedule.empty': 'no active reminders — the model creates them with schedule_create',
72
+
73
+ // Transcript document panel (/resume details)
74
+ 'document.loading': 'loading transcript…',
75
+ 'document.compact': 'transcript · esc close',
76
+ 'document.footer': 'lines {from}-{to}/{total} · ↑↓/pg/g/G · t/esc close',
77
+
78
+ // /review picker
79
+ 'review.picker.title': '/review · choose a target',
80
+ 'review.picker.uncommitted': 'Review uncommitted changes (staged / unstaged / new files)',
81
+ 'review.picker.branch': 'Compare against a base branch (lists local branches)',
82
+ 'review.picker.commit': 'Review a commit on the current branch (lists recent commits)',
83
+ 'review.picker.custom': 'Custom review focus (type it)',
84
+ 'review.picker.customHint': 'type your focus, then enter to start the review',
85
+ 'review.picker.branches': '{filtered} of {total} branches',
86
+ 'review.picker.commits': '{filtered} of {total} commits',
87
+ 'review.picker.loadingBranches': 'loading branches…',
88
+ 'review.picker.loadingCommits': 'loading commits…',
89
+ 'review.picker.empty': 'no candidates',
90
+ 'review.picker.noMatch': 'no matches',
91
+ 'review.picker.loading': 'loading…',
92
+ 'review.picker.loadFailed': 'load failed',
93
+ 'review.picker.compact': 'pick a review target',
94
+ 'review.picker.footer': '↑↓ move · enter select · esc back · q close',
95
+
96
+ // Review result summary
97
+ 'review.summary.untagged': 'untagged\u00d7{n}',
98
+ 'review.summary.incorrect': 'patch incorrect',
99
+ 'review.summary.correctClean': 'no issues found',
100
+ 'review.summary.correct': 'patch correct',
101
+ 'review.summary.noConclusion': 'review finished: no structured conclusion',
102
+ 'review.summary.zero': 'review finished: 0 findings · {verdict}',
103
+ 'review.summary.count': 'review finished: {count} findings ({parts})',
104
+ 'review.summary.countVerdict': 'review finished: {count} findings ({parts}) · {verdict}',
105
+
106
+ // Review runner notices
107
+ 'review.notice.started': 'review started under read-only permissions',
108
+ 'review.notice.failed': 'review failed: {message}',
109
+ 'review.notice.unavailable': 'review unavailable: {message}',
110
+
111
+ // LOCAL_COMMAND descriptions
112
+ 'cmd.help': 'show this overlay',
113
+ 'cmd.model': 'switch the model and manage providers',
114
+ 'cmd.effort': 'adjust reasoning effort for the current model',
115
+ 'cmd.mode': 'inspect or select the agent preset (/mode [preset])',
116
+ 'cmd.permission': 'inspect or select the permission preset (/permission [preset])',
117
+ 'cmd.new': 'create and switch to a fresh session (/new [preset])',
118
+ 'cmd.fork': 'fork at the latest completed turn (/fork [event-seq])',
119
+ 'cmd.resume': 'browse or switch root sessions (/resume [id|prefix])',
120
+ 'cmd.search': 'full-text search across persisted sessions (/search [query])',
121
+ 'cmd.plugin': 'inspect the live plugin composition',
122
+ 'cmd.update': 'update dsh-code, the harness host, and profile plugins in one aligned step',
123
+ 'cmd.jobs': 'inspect background jobs',
124
+ 'cmd.schedule': 'inspect active reminders (created through schedule tools)',
125
+ 'cmd.statusline': 'customize the status line items',
126
+ 'cmd.theme': 'switch the color theme',
127
+ 'cmd.language': 'switch the interface language (/language [en|zh])',
128
+ 'cmd.rainbow': 'reroll or pin the rainbow palette (/rainbow [seed])',
129
+ 'cmd.animation': 'toggle timed animations (/animation [on|off])',
130
+ 'cmd.history': 'search and recall past prompts',
131
+ 'cmd.queue': 'manage messages queued for the next turn',
132
+ 'cmd.usage': 'show this session token usage',
133
+ 'cmd.agents': 'inspect subagent sessions of this conversation',
134
+ 'cmd.todos': 'inspect the full todo list',
135
+ 'cmd.subagent': 'choose the model delegated subagents run on',
136
+ 'cmd.vscode-keys': 'pass ctrl+r through the vs code terminal',
137
+ 'cmd.delete': 'delete a session and its subagent threads',
138
+ 'cmd.clear': 'clear the screen',
139
+ 'cmd.export': 'export the transcript to markdown (/export [path])',
140
+ 'cmd.title': 'rename this session (/title <text>)',
141
+ 'cmd.copy': 'copy the latest assistant response',
142
+ 'cmd.diff': 'inspect Git changes (/diff [--staged|ref])',
143
+ 'cmd.review': 'review Git changes with the diff pasted in this session (/review [note])',
144
+ 'cmd.quit': 'exit',
145
+
146
+ // Help overlay
147
+ 'help.keysTitle': ' keys',
148
+ 'help.key.submit': 'enter submit · ctrl+j / alt+enter newline · up/down history · tab complete',
149
+ 'help.key.mentions': '@ mentions workspace files and sessions',
150
+ 'help.key.inspector': 'ctrl+o history details · ctrl/alt+r thinking · shift+tab permission preset',
151
+ 'help.key.cancel': 'esc interrupt the running turn · ctrl+c cancel / clear / quit · ctrl+d exit',
152
+ 'help.key.queue': 'empty tab picks queue or steer · /queue manages queued messages · delete cancels the newest one',
153
+ 'help.key.edit': 'ctrl+k cut to end of line · ctrl+u clear line · ctrl+a / ctrl+e line ends',
154
+ 'help.commandsTitle': ' commands',
155
+ 'help.skillsTitle': ' skills',
156
+ 'help.footer': '↑↓ scroll · pgup/pgdn page · g/G ends · esc/q close',
157
+ 'help.compact': '/help · esc/q close',
158
+ 'panel.queue.title': '/queue — {count} queued · rows {from}-{to}',
159
+ 'panel.queue.empty': ' no messages queued for the next turn',
160
+ 'panel.queue.attachments': '[attachments: read-only]',
161
+ 'panel.queue.footerBusy': '↑↓ select · enter steer · e edit text · d remove · esc/q close',
162
+ 'panel.queue.footerIdle': '↑↓ select · e edit text · d remove · esc/q close',
163
+ 'panel.queue.editFooter': 'type · ←→ move · enter save · esc cancel',
164
+ 'panel.queue.compact': '/queue · esc/q close',
165
+ 'panel.usage.title': '/usage — usage of this session',
166
+ 'panel.usage.compact': '/usage · esc/q close',
167
+ 'panel.usage.footer': '↑↓ scroll · pgup/pgdn page · g/G ends · esc/q close',
168
+ 'panel.usage.totals': 'Totals (whole session)',
169
+ 'panel.usage.uncached': 'uncached in',
170
+ 'panel.usage.cacheWrite': 'cache write',
171
+ 'panel.usage.cacheRead': 'cache read',
172
+ 'panel.usage.output': 'out',
173
+ 'panel.usage.total': 'total',
174
+ 'panel.usage.cacheHit': 'cache hit',
175
+ 'panel.usage.turns': 'Per turn',
176
+ 'panel.usage.byModel': 'By model',
177
+ 'panel.usage.colTurns': 'turns',
178
+ 'panel.usage.unknownModel': 'unattributed',
179
+ 'panel.usage.colTurn': 'turn',
180
+ 'panel.usage.colTotal': 'total',
181
+ 'panel.usage.colUncached': 'uncached',
182
+ 'panel.usage.colCacheRead': 'cache R',
183
+ 'panel.usage.colCacheHit': 'hit',
184
+ 'panel.usage.colCacheWrite': 'cache W',
185
+ 'panel.usage.colOut': 'out',
186
+ 'panel.usage.colThink': 'think',
187
+ 'panel.usage.colModel': 'model',
188
+ 'panel.usage.partialNote': '+ marks a group where some turns did not report that bucket; only reported turns are counted',
189
+ 'panel.usage.unavailable': 'token projections are not mounted in this deployment (dsh-token-meter is missing)',
190
+ 'panel.usage.noTurns': 'no completed turn yet',
191
+ 'status.cycleHint': '(shift+tab to cycle)',
192
+ 'approval.compact': 'approval{queued} · enter/y allow · esc/n reject',
193
+ 'approval.overflow': '… +{count} more lines · ctrl+o shows the full call in the transcript',
194
+ 'question.compact.plan': 'plan review · esc cancel',
195
+ 'question.compact.normal': 'question · esc cancel',
196
+ 'approval.submitted': 'submitted…',
197
+ 'approval.footer': '↑↓ choose · enter confirm · y/n/d quick · esc reject',
198
+ 'question.footer': '↑↓ choose · 1-9 select · space toggle · enter submit · esc cancel',
199
+ 'question.custom': 'type a custom answer',
200
+ 'question.submitted': 'submitted…',
201
+ 'question.customNoOptions': '↑↓/pgup/pgdn scroll · type answer · enter submit · esc interrupt',
202
+ 'question.customOptions': '↑↓/pgup/pgdn scroll · type answer · enter submit · tab/esc or empty backspace: options',
203
+ 'question.multiOptions': '↑↓ choose · pgup/pgdn scroll · space/1-9 toggle · enter submit · c custom · esc interrupt',
204
+ 'question.singleOptions': '↑↓ choose · pgup/pgdn scroll · 1-9 pick · enter submit · c custom · esc interrupt',
205
+ 'question.switch': ' · ←→/ctrl+p/n switch question',
206
+ 'question.title.plan': '📋 plan review',
207
+ 'question.title.normal': '❓ question',
208
+
209
+
210
+ // Composer and status chrome
211
+ 'composer.placeholder': 'type a message · / commands · @ mentions · tab steers',
212
+ 'composer.placeholderSteer': 'steer into this turn · tab queues for the next one',
213
+ 'notice.submitMode.queue': 'send \u2192 queue for the next turn',
214
+ 'notice.submitMode.steer': 'send \u2192 steer into this turn (only while one runs; idle it sends normally)',
215
+ // Transcript markers on a settled prompt that was not submitted idle.
216
+ 'entry.delivery.queued': 'queued',
217
+ 'entry.delivery.steered': 'steered',
218
+ 'status.shiftTabHint': '(shift+tab to cycle)',
219
+ 'status.label.turns': 'turns',
220
+ 'status.label.steps': 'steps',
221
+ 'status.label.modelTime': 'model',
222
+ 'status.label.latency': 'latency',
223
+ 'status.label.tokensPerSec': ' tokens/s',
224
+ 'status.label.tool': 'tool',
225
+ 'status.label.cache': 'cache',
226
+ 'status.label.in': 'in',
227
+ 'status.label.out': 'out',
228
+ 'status.label.mode': '/mode',
229
+ 'status.label.context': 'context',
230
+
231
+ // Frozen band
232
+ 'frozen.keysGoTo': 'keys go to {owner} · esc {action}',
233
+ 'frozen.action.rejects': 'rejects',
234
+ 'frozen.action.cancels': 'cancels',
235
+ 'frozen.action.closes': 'closes',
236
+
237
+ // Relative time (panel list timestamps)
238
+ 'time.justNow': 'now',
239
+ 'time.minutesAgo': '{n}m ago',
240
+ 'time.hoursAgo': '{n}h ago',
241
+ 'time.daysAgo': '{n}d ago',
242
+
243
+ // Common notices
244
+ 'notice.themeSaved': 'theme \u2192 {name}',
245
+ 'notice.themeRainbow': 'theme \u2192 rainbow · seed {seed} · /rainbow to reroll',
246
+ 'notice.rainbowRolled': 'rainbow · seed {seed} · /rainbow to reroll · /rainbow {seed} to pin',
247
+ 'notice.languageSaved': 'language \u2192 {name}',
248
+ 'notice.themeSaveFailed': 'theme save failed: {message}',
249
+ 'notice.languageSaveFailed': 'language save failed: {message}',
250
+ 'notice.alreadyActive': 'that session is already active',
251
+ 'notice.queueCancelled': 'queued message cancelled',
252
+ 'notice.queueActionFailed': 'queue action failed: {message}',
253
+ 'notice.queueUnavailable': 'queued message is no longer pending',
254
+ 'notice.queueSteerUnavailable': 'steering is available only while the turn is running',
255
+ 'notice.queueEditEmpty': 'queued message cannot be empty',
256
+ 'notice.queueEdited': 'queued message edited',
257
+ 'notice.queueSteered': 'queued message moved into this turn',
258
+ 'notice.historySaveFailed': 'history save failed: {message}',
259
+ 'notice.turnCancelled': 'turn cancelled \u2014 Ctrl+C or /quit to exit',
260
+ 'notice.turnCancelledKeepQueue': 'turn cancelled \u2014 pending messages are preserved \u00b7 Ctrl+C or /quit to exit',
261
+ 'notice.noSessionYet': 'no session yet \u2014 submit a message to start',
262
+ 'notice.usage.animation': 'usage: /animation [on|off]',
263
+ 'notice.usage.language': 'usage: /language [en|zh]',
264
+ 'notice.usage.rainbow': 'usage: /rainbow [seed]',
265
+ 'notice.imageCancelled': 'image submission cancelled',
266
+ 'notice.themeConfigUnreadable': 'theme config unreadable, using dark: {message}',
267
+ 'notice.languageConfigUnreadable': 'language config unreadable, using english: {message}',
268
+ 'notice.animationsConfigUnreadable': 'animations config unreadable, animations stay on: {message}',
269
+ 'notice.statuslineConfigUnreadable': 'statusline config unreadable, using defaults: {message}',
270
+ 'notice.sessionSearchUnavailable': 'session search is unavailable in this deployment',
271
+ 'notice.subagentsReadOnly': 'subagent conversations are read-only',
272
+ 'notice.statuslineSaveFailed': 'statusline save failed: {message}',
273
+ 'notice.animationsSaveFailed': 'animations save failed: {message}',
274
+ 'notice.commandRegistryMissing': 'no command registry is mounted in this composition',
275
+ 'notice.commandFallbackFailed': 'command fallback failed: {message}',
276
+ 'notice.commandFailed': 'command failed: {message}',
277
+ 'notice.invalidReference': 'invalid session reference: {message}',
278
+ 'notice.messageFailed': '{kind} failed: {message}',
279
+ 'notice.referenceFailed': 'session reference failed: {message}',
280
+ 'notice.activationFailed': 'session activation failed: {message}',
281
+ 'notice.creationFailed': 'session creation failed: {message}',
282
+ 'notice.modeChanged': 'mode → {value}',
283
+ 'notice.modeSwitchFailed': 'mode switch failed: {message}',
284
+ 'notice.permissionChanged': 'permission → {value}',
285
+ 'notice.permissionChangeFailed': 'permission change failed: {message}',
286
+ 'notice.cancelFailed': 'cancel failed: {message}',
287
+ 'notice.modeChangeFailed': 'mode change failed: {message}',
288
+ 'notice.modelNotDefault': 'model switch applies to this session but was not saved as the default: {message}',
289
+ 'notice.modelSelectionRejected': 'model selection rejected: {message} — reopen /model to pick again',
290
+ 'notice.exported': 'exported to {path}',
291
+ 'notice.exportFailed': 'export failed: {message}',
292
+ 'notice.resumeFailed': 'resume failed: {message}',
293
+ 'notice.forkFailed': 'fork failed: {message}',
294
+ 'notice.sessionSwitchFailed': 'session switch failed: {message}',
295
+ 'notice.switchQueued': 'will switch to {label} when the current turn finishes · /resume cancel to abort',
296
+ 'notice.reviewStarted': 'review started under read-only permissions',
297
+ 'notice.reviewFailed': 'review failed: {message}',
298
+ 'notice.reviewUnavailable': 'review unavailable: {message}',
299
+ 'notice.startupImages': 'processing {count} startup image{plural}…',
300
+ 'notice.startupImagesAttached': '{count} startup image{plural} attached',
301
+ 'notice.initialPromptFailed': 'initial prompt failed: {message}',
302
+ 'notice.rejected': 'rejected — type below what it should do differently (it steers the next step)',
303
+ 'notice.attachmentAlready': '{name} is already attached',
304
+ 'notice.attachmentsChecking': 'checking {count} attachment{plural}…',
305
+ 'notice.attachmentsAlready': 'those attachments are already attached',
306
+ 'notice.attachmentDraftChanged': 'draft changed at the attachment drop point; drop the files again',
307
+ 'notice.attachmentsReady': '{count} attachment{plural} ready for the next message',
308
+ 'notice.attachmentFailed': 'attachment failed: {message}',
309
+ 'notice.imageChecking': 'checking image {name}…',
310
+ 'notice.imageDraftChanged': 'draft changed around the image mention; select it again',
311
+ 'notice.imageReady': '{name} ready for the next message',
312
+ 'notice.imageFailed': 'image attachment failed: {message}',
313
+ 'notice.cancelBeforeExit': 'cancel the running turn before exiting (Esc or Ctrl+C)',
314
+ 'notice.commandAttachments': 'commands cannot carry attachments; the line will be sent to the model as a prompt',
315
+ 'notice.copyFailed': 'copy failed: {message}',
316
+ 'notice.vscodeKeysFailed': 'vscode-keys failed: {message}',
317
+ 'notice.animationState': 'animations {state}',
318
+ 'notice.deleteFailed': 'delete failed: {message}',
319
+ 'notice.modelChangedPlaceholder': 'model → {model} · image history will be sent as text placeholders',
320
+ 'notice.modelNextStep': 'model → next step uses {model}',
321
+ 'notice.modelSwitchFailed': 'model switch failed: {message}',
322
+ 'notice.loggedIn': 'logged in to {provider}; select a model',
323
+ 'notice.loggedOut': 'logged out from {provider}',
324
+ 'notice.providerSaved': 'provider configuration saved: {provider}{suffix}',
325
+ 'notice.apiKeyRemoved': 'API key removed for {provider}',
326
+ 'notice.providerRemoved': 'provider removed: {provider}',
327
+ 'notice.providerUnavailable': 'provider configuration is unavailable in this profile',
328
+ 'notice.apiKeyUnavailable': 'API key removal is unavailable in this profile',
329
+ 'notice.providerRemovalUnavailable': 'provider removal is unavailable in this profile',
330
+ 'notice.loginIdleOnly': 'provider login is available only while the agent is idle',
331
+ 'notice.loginUnavailable': 'provider login is unavailable in this profile',
332
+ 'notice.logoutUnavailable': 'provider logout is unavailable in this profile',
333
+ 'notice.modeChangedSimple': 'mode → {value}',
334
+ 'notice.permissionChangedSimple': 'permission → {value}',
335
+ 'notice.subagentsChanged': 'subagents → {value}',
336
+ 'notice.subagentsInherited': 'subagents → inherit current model',
337
+ 'notice.subagentChangeFailed': 'subagent model change failed: {message}',
338
+ 'notice.modelMissing': 'current model is not in the catalog',
339
+ 'notice.effortUnavailable': 'reasoning levels temporarily unavailable (capability lookup failed) — try again',
340
+ 'notice.modelLookupFailed': 'model lookup failed: {message}',
341
+ 'notice.diffFailed': 'diff failed: {message}',
342
+
343
+ // Additional panel chrome
344
+ 'panel.footer.chooseSwitch': '↑↓ choose · enter switch · r refresh · esc close',
345
+ 'panel.footer.chooseSelect': '↑↓ choose · enter select · r refresh · esc close',
346
+ 'panel.footer.inspectDetails': '↑↓ inspect · enter details · r refresh · esc close',
347
+ 'panel.footer.inspectRefresh': '↑↓ inspect · r refresh · esc close',
348
+ 'panel.footer.resume': 'tab/←→ filters · ↑↓/pg navigate · ctrl+f search · e details · t transcript · d delete · enter resume',
349
+ 'panel.footer.transcript': 'lines {from}-{to}/{total} · ↑↓/pg/g/G · t/esc close',
350
+ 'panel.footer.history': '↑↓ move · g/G ends · enter fill · esc close',
351
+ 'panel.footer.review': '↑↓ move · enter select · esc back · q close',
352
+ 'panel.footer.statusline': '↑↓ move · space toggle · ←→ reorder · d default · esc close',
353
+ 'panel.footer.schedule': 'esc/q close{more} · the model schedules via schedule_create',
354
+ 'panel.mode.title': '/mode · current {current}',
355
+ 'panel.permission.title': '/permission · current {current}',
356
+ 'panel.plugin.title': '/plugin · loader inspector',
357
+ 'panel.jobs.title': '/jobs · background tasks · {count}',
358
+ 'panel.resume.deleteTitle': 'permanently delete {target}? this cannot be undone · subagent threads go too',
359
+ 'panel.resume.title': '/resume{mode}{search} · {toolbar}',
360
+ 'panel.noActiveReminders': 'no active reminders — the model schedules via schedule_create',
361
+ 'panel.notMounted': 'not mounted',
362
+ 'panel.broken': 'broken: {message}',
363
+ 'panel.live': 'live',
364
+ 'panel.parent': 'parent {id}',
365
+ 'panel.deleteMode': 'delete mode',
366
+ 'panel.searching': 'searching',
367
+ 'panel.noPresets': 'no presets',
368
+ 'panel.noPermissions': 'no permission presets',
369
+ 'panel.noJobs': 'no background jobs',
370
+ 'panel.diff.compact': '{title} · {files} files · esc/q close',
371
+ 'panel.diff.noFiles': 'no files',
372
+ 'panel.diff.footer': '↑/↓ scroll · g/G ends · esc/q close',
373
+ 'panel.todos.compact': 'todos · esc/q close',
374
+ 'panel.todos.title': 'todos · {done}/{total} done · {active} active · {pending} pending · rows {from}-{to}/{rows}',
375
+ 'panel.todos.empty': 'no todos yet',
376
+ 'panel.todos.footer': '↑↓ scroll · pgup/pgdn page · g/G ends · esc/q close',
377
+ 'panel.verbose.compact': 'history details · ctrl+o / esc / q close',
378
+ 'panel.verbose.footer': '←→ entry · ↑↓ scroll · pgup/pgdn page · g/G ends · ctrl+o/esc/q close',
379
+ 'panel.model.compact': '/model · {state}{providers} · {tail}',
380
+ 'panel.model.noMatch': "no models match '{query}'",
381
+ 'panel.model.compactNoMatch': "no match for '{query}'",
382
+ 'panel.model.loading': 'loading…',
383
+ 'panel.model.error': 'error',
384
+ 'panel.model.noModels': 'no models available',
385
+ 'panel.model.selectTitle': '/model — select model',
386
+ 'panel.model.matches': "{filtered} of {total} match '{query}'",
387
+ 'panel.model.footer.filter': 'type to filter · r retry · esc/q close',
388
+ 'panel.model.footer.filtered': 'backspace edits · esc close',
389
+ 'panel.model.title': '/model — select model',
390
+ 'panel.model.titleCount': '/model — select model · {index}/{total}',
391
+ 'panel.model.titleMatches': "/model — select model · {filtered} of {total} match '{query}'",
392
+ 'panel.model.footer.select': 'type to filter · ↑↓ move · pgup/pgdn page · enter select{providers} · r retry · esc/q close',
393
+ 'panel.model.footer.filteredSelect': '↑↓ move · pgup/pgdn page · enter select · backspace edits · esc close',
394
+ 'panel.model.providers': 'tab providers',
395
+ 'panel.provider.failure': 'unavailable providers: {providers}',
396
+ 'panel.provider.loading': 'loading providers…',
397
+ 'panel.provider.empty': 'no configurable providers',
398
+ 'panel.provider.notConfigured': 'not configured',
399
+ 'panel.provider.configured': 'configured',
400
+ 'panel.provider.keyMissing': 'key missing',
401
+ 'panel.provider.keyStatusUnavailable': 'key status unavailable',
402
+ 'panel.provider.auth': 'provider auth',
403
+ 'panel.provider.active': 'active',
404
+ 'panel.provider.dormant': 'dormant',
405
+ 'panel.provider.readOnly': 'read-only',
406
+ 'panel.provider.custom': 'custom',
407
+ 'panel.provider.notConfiguredDivider': '── not configured ──',
408
+ 'panel.provider.title': '/model — providers',
409
+ 'panel.provider.titleCount': '/model — providers · {index}/{total}',
410
+ 'panel.provider.state': '{route} · {value}',
411
+ 'panel.provider.key': 'key {value}',
412
+ 'panel.provider.authConfigured': 'provider auth',
413
+ 'panel.provider.noKey': 'key missing',
414
+ 'panel.provider.noLogin': 'not configured',
415
+ 'panel.provider.loginStatusUnavailable': 'login status unavailable: {message}',
416
+ 'panel.provider.noConfiguredKey': 'this provider has no configured API key to remove',
417
+ 'panel.provider.readOnlyKey': 'this API key is supplied read-only by the environment',
418
+ 'panel.provider.notRemovable': 'this provider profile is not removable',
419
+ 'panel.provider.noLoginFlow': 'this provider offers no interactive login flow',
420
+ 'panel.provider.loginRunning': 'a login attempt is already running for this provider',
421
+ 'panel.provider.noLoginRecord': 'this provider has no login record to remove',
422
+ 'panel.provider.readOnlyLogin': 'this login record is read-only',
423
+ 'panel.provider.notManaged': 'this provider is not managed by Harness settings',
424
+ 'panel.providers.compact': '/model providers · enter configure · d remove key · esc back',
425
+ 'panel.providers.footer': '↑↓ move · enter configure · l login · o logout · d remove key · x remove provider · r retry · esc back',
426
+ 'panel.update.compact': '/update · {summary}',
427
+ 'panel.update.checking': 'checking…',
428
+ 'panel.update.probeFailed': 'probe failed',
429
+ 'panel.update.updating': 'updating…',
430
+ 'panel.update.installed': 'installed — restart to activate',
431
+ 'panel.update.failed': 'failed',
432
+ 'panel.update.upToDate': 'up to date',
433
+ 'panel.update.aheadOfNpm': 'newer than npm',
434
+ 'panel.update.enter': 'enter updates',
435
+ 'panel.update.blocked': 'blocked',
436
+ 'panel.update.title': '/update · aligned upgrade',
437
+ 'panel.auth.methodsFooter': '↑↓ choose · enter continue · esc/q back',
438
+ 'panel.auth.answerFooter': 'enter answer · c copy URL/code · esc cancel login',
439
+ 'panel.auth.title': '/model · login {provider}',
440
+ 'panel.auth.waiting': 'waiting for provider…',
441
+ 'panel.auth.compact': 'provider login · esc cancel',
442
+ 'panel.auth.logoutCompact': 'y logout · n/esc back',
443
+ 'panel.auth.logoutTitle': '/model · logout provider',
444
+ 'panel.auth.removeRecord': 'remove {provider} login record',
445
+ 'panel.auth.unchanged': 'provider endpoint and model configuration stay unchanged',
446
+ 'panel.auth.working': 'working…',
447
+ 'panel.auth.logoutFooter': 'y confirm · n/esc back',
448
+ 'panel.setup.compact': 'provider setup · terminal too small · esc back',
449
+ 'panel.setup.noDonors': 'no model with declared efforts yet; declare one with e, or hand-write settings',
450
+ 'panel.setup.copyInto': 'copy verbatim into {id}',
451
+ 'panel.setup.copyTitle': '/model — copy efforts',
452
+ 'panel.setup.copyFooter': '↑↓ move · enter copy · esc back',
453
+ 'panel.setup.title': '/model — configure {provider}',
454
+ 'panel.setup.footer': '↑↓ move · ←→ in/out · space remove · e efforts · c copy efforts · tab discover · enter save · esc back',
455
+ 'panel.discovery.compact': 'model discovery · terminal too small · esc back',
456
+ 'panel.discovery.loading': 'discovering models…',
457
+ 'panel.discovery.empty': 'the endpoint advertised no models; add ids by hand on the setup page',
458
+ 'panel.discovery.summary': '{advertised} advertised · {newCount} new · {checked} checked',
459
+ 'panel.discovery.title': '/model — discover {provider}',
460
+ 'panel.discovery.footer': '↑↓ move · space check · enter adopt · f refetch · esc back',
461
+ 'panel.update.footer.probe': 'checking… · esc close',
462
+ 'panel.update.footer.error': 'r recheck · esc close',
463
+ 'panel.update.footer.apply': 'updating… · ↑↓ scroll · esc waits',
464
+ 'panel.update.footer.current': 'up to date · r recheck · esc close',
465
+ 'panel.update.footer.ahead': 'newer than npm — not downgrading · r recheck · esc close',
466
+ 'panel.update.footer.update': 'enter update · r recheck · esc close',
467
+ 'panel.update.footer.blocked': 'blocked · r recheck · esc close',
468
+ 'panel.footer.effortEmpty': 'esc back',
469
+ 'panel.footer.effort': '↑↓ choose · enter apply · esc/q back',
470
+ 'panel.effort.title': '/model — effort for {provider} · {model}',
471
+ 'panel.effort.empty': 'this model advertises no reasoning effort levels — the provider default applies',
472
+ 'panel.default': 'default',
473
+ 'panel.footer.agents': '↑↓ choose · enter/t transcript · r refresh · esc close',
474
+ 'panel.agents.title': '/agents · {live} live · {total} total',
475
+ 'panel.document.title': 'transcript · {id}',
476
+ 'panel.subagent.transcriptTitle': 'subagent · {id}',
477
+ 'panel.footer.subagent': '↑↓ choose · enter apply · r refresh · esc close',
478
+ 'panel.subagent.title': '/subagent — model for delegated agents',
479
+ 'panel.subagent.override': ' · override {value}',
480
+ 'panel.subagent.inherit': 'inherit — follow the current model (/model switches apply)',
481
+ 'panel.statusline.compact': '/statusline · esc close',
482
+ 'panel.schedule.compact': '/schedule · {summary}',
483
+ 'panel.schedule.none': 'no active reminders',
484
+ 'panel.schedule.more': ' · +{count} more',
485
+ 'panel.schedule.footer': 'esc/q close{more} · the model schedules via schedule_create',
486
+ } as const
487
+
488
+ export type MessageKey = keyof typeof en
489
+ export type MessageCatalog = Record<MessageKey, string>