@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
@@ -1,54 +1,80 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { Box, Text, useInput, ScrollBox, useTerminalSize, useAnimationFrame } from '../ui.js';
4
4
  import { formatJobDuration } from '../dsh-adapter/jobs.js';
5
+ import { JobProgress } from './Chat/JobCard.js';
6
+ import { Markdown } from './Markdown.js';
5
7
  import { t } from '../i18n.js';
6
8
  import { Divider } from './design-system/Divider.js';
7
9
  import { ExitButton } from './SubagentDashboard.js';
8
10
  import { isPlainReturnInput } from '../utils/modifiers.js';
9
- import { isMinimalMode } from '../minimalMode.js';
10
- import { stringWidth } from '../ink/stringWidth.js';
11
+ import { isMinimalUiMode } from '../minimalUiMode.js';
11
12
  function statusInfo(status) {
12
- const minimal = isMinimalMode();
13
+ const minimalUi = isMinimalUiMode();
13
14
  switch (status) {
14
15
  case 'completed':
15
- return { glyph: minimal ? '✓' : '●', label: t('jobs-status-completed'), color: minimal ? undefined : 'success' };
16
+ return { glyph: minimalUi ? '✓' : '●', label: t('jobs-status-completed'), color: minimalUi ? undefined : 'success' };
16
17
  case 'failed':
17
- return { glyph: minimal ? '×' : '●', label: t('jobs-status-failed'), color: minimal ? undefined : 'error' };
18
+ return { glyph: minimalUi ? '×' : '●', label: t('jobs-status-failed'), color: minimalUi ? undefined : 'error' };
18
19
  case 'killed':
19
- return { glyph: minimal ? '×' : '●', label: t('jobs-status-killed'), color: minimal ? undefined : 'error' };
20
+ return { glyph: minimalUi ? '×' : '●', label: t('jobs-status-killed'), color: minimalUi ? undefined : 'error' };
20
21
  case 'stopping':
21
- return { glyph: minimal ? '·' : '●', label: t('jobs-status-stopping'), color: minimal ? undefined : 'warning' };
22
+ return { glyph: minimalUi ? '·' : '●', label: t('jobs-status-stopping'), color: minimalUi ? undefined : 'warning' };
22
23
  default:
23
- return { glyph: minimal ? '·' : '●', label: t('jobs-status-running'), color: minimal ? undefined : 'warning' };
24
+ return { glyph: minimalUi ? '·' : '●', label: t('jobs-status-running'), color: minimalUi ? undefined : 'warning' };
24
25
  }
25
26
  }
26
- /** Hard single-line clip by display width (shared rule with the job card). */
27
- function clipLine(text, maxWidth) {
28
- if (maxWidth <= 1)
29
- return '';
30
- let width = 0;
31
- let index = 0;
32
- while (index < text.length) {
33
- const next = text.codePointAt(index);
34
- const char = String.fromCodePoint(next);
35
- const charWidth = stringWidth(char);
36
- if (width + charWidth > maxWidth - 1)
37
- break;
38
- width += charWidth;
39
- index += char.length;
27
+ function isTerminalStatus(status) {
28
+ return status === 'completed' || status === 'killed' || status === 'failed';
29
+ }
30
+ /** 12.3 KB / 1.4 MB — byte counter for the detail block. */
31
+ function formatBytes(bytes) {
32
+ if (bytes < 1024)
33
+ return `${bytes} B`;
34
+ if (bytes < 1024 * 1024)
35
+ return `${(bytes / 1024).toFixed(1)} KB`;
36
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
37
+ }
38
+ function renderOutputRuns(job) {
39
+ const runs = [];
40
+ let markdown = [];
41
+ const flush = () => {
42
+ if (markdown.length === 0)
43
+ return;
44
+ runs.push({ kind: 'markdown', text: markdown.join('\n') });
45
+ markdown = [];
46
+ };
47
+ for (const line of job.outputLines) {
48
+ if (line.gapBefore === true) {
49
+ flush();
50
+ runs.push({ kind: 'gap' });
51
+ }
52
+ if (line.channel === 'stderr') {
53
+ flush();
54
+ runs.push({ kind: 'stderr', text: line.text });
55
+ }
56
+ else if (line.channel === 'log') {
57
+ flush();
58
+ runs.push({ kind: 'log', text: line.text });
59
+ }
60
+ else {
61
+ markdown.push(line.text);
62
+ }
40
63
  }
41
- return index < text.length ? `${text.slice(0, index)}…` : text;
64
+ flush();
65
+ return runs;
42
66
  }
43
- function JobRowLine({ job, focused }) {
44
- const { columns } = useTerminalSize();
67
+ function JobRowLine({ job, focused, armed, showProgress, onFocus }) {
45
68
  const info = statusInfo(job.status);
46
69
  const duration = formatJobDuration(job);
47
- const detail = job.detail !== undefined && job.detail !== '' ? job.detail : undefined;
48
- // Reserve: glyph(2) id(~9) kind(~7) duration(~7) status(~6) separators(5×2)
49
- // — the label takes the rest and hard-clips instead of wrapping.
50
- const labelWidth = Math.max(10, (columns ?? 80) - 46);
51
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: focused ? 'accent' : undefined, children: focused ? '❯' : ' ' }), _jsx(Text, { color: info.color, children: info.glyph }), _jsx(Text, { bold: focused, color: focused ? 'accent' : undefined, children: job.id }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: job.kind }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: focused, children: clipLine(job.label, labelWidth) }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: true, children: duration }), detail !== undefined && _jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: detail })] }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: info.color, children: info.label })] }), focused && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: ` ${t('jobs-card-prefix')}${clipLine(job.label, Math.max(10, labelWidth + 24))}` }), job.command !== undefined && (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` ${t('jobs-panel-command')} ${clipLine(job.command, Math.max(10, labelWidth + 24))}` })), _jsxs(Text, { dimColor: true, wrap: "truncate", children: [` ${t('jobs-panel-started')} ${timeOf(job.startedAt)}`, job.finishedAt !== undefined ? ` · ${t('jobs-panel-finished')} ${timeOf(job.finishedAt)}` : '', job.lastOutputAt !== undefined ? ` · ${t('jobs-panel-output-at')} ${timeOf(job.lastOutputAt)}` : ''] }), job.outputLines.length > 0 ? (job.outputLines.map((line, index) => (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${clipLine(line, Math.max(10, labelWidth + 24))}` }, `${job.id}-detail-${index}`)))) : (_jsx(Text, { dimColor: true, wrap: "truncate", children: ` └ ${t('jobs-panel-no-output-yet')}` }))] }))] }));
70
+ const live = !isTerminalStatus(job.status);
71
+ const progress = live && job.progress !== undefined && job.progress !== '' ? job.progress : undefined;
72
+ return (_jsxs(Box, { flexDirection: "column", onClick: onFocus, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsxs(Box, { width: 2, flexShrink: 0, children: [_jsx(Text, { color: focused ? 'accent' : undefined, children: focused ? '❯' : ' ' }), _jsx(Text, { color: info.color, children: info.glyph })] }), _jsx(Box, { width: 9, flexShrink: 0, children: _jsx(Text, { bold: focused, color: focused ? 'accent' : undefined, wrap: "truncate-end", children: job.id }) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { bold: focused, wrap: "truncate-end", children: job.label }) }), showProgress === true && (_jsx(Box, { width: 11, flexShrink: 0, justifyContent: "flex-end", children: progress !== undefined ? _jsx(JobProgress, { progress: progress }) : _jsx(Text, { children: " " }) })), armed === true ? (_jsx(Text, { bold: true, color: "error", wrap: "truncate-end", children: t('jobs-kill-armed') })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: 6, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, children: duration }) }), _jsx(Box, { width: 9, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { color: info.color, wrap: "truncate-end", children: info.label }) })] }))] }), focused && (_jsxs(Box, { flexDirection: "column", paddingLeft: 4, children: [job.command !== undefined && job.command !== '' && job.command !== job.label && (_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Box, { width: 7, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-command') }) }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: job.command })] })), _jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Box, { width: 7, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-started') }) }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: timeOf(job.startedAt)
73
+ + (job.finishedAt !== undefined ? ` · ${t('jobs-panel-finished')} ${timeOf(job.finishedAt)}` : '')
74
+ + (job.lastOutputAt !== undefined ? ` · ${t('jobs-panel-output-at')} ${timeOf(job.lastOutputAt)}` : '') })] }), (job.outputTotalBytes !== undefined || job.outputDropped === true) && (_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Box, { width: 7, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-output') }) }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: (job.outputTotalBytes !== undefined ? formatBytes(job.outputTotalBytes) : '')
75
+ + (job.outputDropped === true
76
+ ? `${job.outputTotalBytes !== undefined ? ' · ' : ''}${t('jobs-output-dropped')}`
77
+ : '') })] })), job.spillPaths !== undefined && job.spillPaths.length > 0 && (_jsx(Box, { paddingLeft: 8, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: t('jobs-output-spill', { path: job.spillPaths[job.spillPaths.length - 1] ?? '' }) }) })), job.outputLines.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: renderOutputRuns(job).map((run, runIndex) => (_jsxs(Box, { flexDirection: "column", marginTop: runIndex === 0 ? 0 : 1, children: [run.kind === 'gap' && (_jsx(Text, { dimColor: true, italic: true, wrap: "truncate-end", children: t('jobs-output-gap') })), run.kind === 'markdown' && (_jsx(Markdown, { cacheTokens: true, children: run.text })), run.kind === 'stderr' && (_jsx(Text, { color: "error", wrap: "truncate-end", children: `│ ${run.text}` })), run.kind === 'log' && (_jsx(Text, { dimColor: true, italic: true, wrap: "truncate-end", children: `│ ${run.text}` }))] }, `${job.id}-run-${runIndex}`))) })) : (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: t('jobs-panel-no-output-yet') }))] }))] }));
52
78
  }
53
79
  /** HH:MM:SS wall-clock of an epoch ms value (locale-independent). */
54
80
  function timeOf(ms) {
@@ -65,11 +91,21 @@ function timeOf(ms) {
65
91
  */
66
92
  export function JobsPanel({ jobs, onClose, onKill }) {
67
93
  const [focusIndex, setFocusIndex] = React.useState(0);
94
+ /** Armed kill: first `k` primes, second within the window confirms; any
95
+ * navigation or other key disarms. Mirrors the web two-press stop. */
96
+ const [killArmed, setKillArmed] = React.useState(undefined);
68
97
  const scrollRef = React.useRef(null);
69
98
  const { rows } = useTerminalSize();
70
99
  // 1s tick keeps live durations counting while the panel is open.
71
100
  const [clockRef] = useAnimationFrame(1000);
72
101
  const focus = Math.min(focusIndex, Math.max(0, jobs.length - 1));
102
+ // The armed confirmation decays after 4s so a stray later `k` never kills.
103
+ React.useEffect(() => {
104
+ if (killArmed === undefined)
105
+ return;
106
+ const timer = setTimeout(() => setKillArmed(undefined), 4000);
107
+ return () => clearTimeout(timer);
108
+ }, [killArmed]);
73
109
  useInput((input, key, event) => {
74
110
  if (key.escape || (key.ctrl && input === 'c')) {
75
111
  event.stopImmediatePropagation();
@@ -78,12 +114,14 @@ export function JobsPanel({ jobs, onClose, onKill }) {
78
114
  }
79
115
  if (key.upArrow) {
80
116
  event.stopImmediatePropagation();
117
+ setKillArmed(undefined);
81
118
  setFocusIndex(i => Math.max(0, i - 1));
82
119
  scrollRef.current?.scrollBy(-1);
83
120
  return;
84
121
  }
85
122
  if (key.downArrow) {
86
123
  event.stopImmediatePropagation();
124
+ setKillArmed(undefined);
87
125
  setFocusIndex(i => Math.min(jobs.length - 1, i + 1));
88
126
  scrollRef.current?.scrollBy(1);
89
127
  return;
@@ -92,7 +130,13 @@ export function JobsPanel({ jobs, onClose, onKill }) {
92
130
  const selected = jobs[focus];
93
131
  if (selected !== undefined && (selected.status === 'running' || selected.status === 'stopping')) {
94
132
  event.stopImmediatePropagation();
95
- onKill(selected.id);
133
+ if (killArmed === selected.id) {
134
+ setKillArmed(undefined);
135
+ onKill(selected.id);
136
+ }
137
+ else {
138
+ setKillArmed(selected.id);
139
+ }
96
140
  }
97
141
  return;
98
142
  }
@@ -105,7 +149,10 @@ export function JobsPanel({ jobs, onClose, onKill }) {
105
149
  event.stopImmediatePropagation();
106
150
  });
107
151
  const running = jobs.filter(job => job.status === 'running' || job.status === 'stopping').length;
152
+ // One live job with a progress line reserves the column on every row, so the
153
+ // right-hand grid does not shift as jobs start and finish.
154
+ const showProgress = jobs.some(job => (job.status === 'running' || job.status === 'stopping') && job.progress !== undefined && job.progress !== '');
108
155
  const completed = jobs.filter(job => job.status === 'completed').length;
109
156
  const failed = jobs.filter(job => job.status === 'failed' || job.status === 'killed').length;
110
- return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, ref: clockRef, children: [_jsx(Divider, { color: "accent", title: t('jobs-panel-title') }), _jsxs(Box, { flexDirection: "row", gap: 3, marginTop: 1, marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "accent", children: running }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-running')] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "success", children: completed }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-completed')] })] }), failed > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: "error", children: failed }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-failed')] })] })), _jsx(Box, { flexGrow: 1 }), _jsx(ExitButton, { onClick: onClose })] }), _jsx(Box, { flexDirection: "column", maxHeight: Math.max(10, rows - 10), marginTop: 1, children: _jsx(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: jobs.length === 0 ? (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: Math.max(2, Math.floor((rows - 16) / 3)), children: [_jsx(Text, { dimColor: true, children: '○' }), _jsx(Text, { dimColor: true, children: t('jobs-panel-empty') }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-empty-hint') }) })] })) : (jobs.map((job, index) => _jsx(JobRowLine, { job: job, focused: index === focus }, job.id))) }) }), _jsx(Divider, { color: "subtle", title: "" }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-hint') }) })] }));
157
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, ref: clockRef, children: [_jsx(Divider, { color: "accent", title: t('jobs-panel-title') }), _jsxs(Box, { flexDirection: "row", gap: 3, marginTop: 1, marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "accent", children: running }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-running')] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "success", children: completed }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-completed')] })] }), failed > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: "error", children: failed }), _jsxs(Text, { dimColor: true, children: [" ", t('jobs-panel-count-failed')] })] })), _jsx(Box, { flexGrow: 1 }), _jsx(ExitButton, { onClick: onClose })] }), _jsx(Box, { flexDirection: "column", maxHeight: Math.max(10, rows - 10), marginTop: 1, children: _jsx(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: jobs.length === 0 ? (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: Math.max(2, Math.floor((rows - 16) / 3)), children: [_jsx(Text, { dimColor: true, children: '○' }), _jsx(Text, { dimColor: true, children: t('jobs-panel-empty') }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-empty-hint') }) })] })) : (jobs.map((job, index) => (_jsx(JobRowLine, { job: job, focused: index === focus, armed: killArmed === job.id, showProgress: showProgress, onFocus: () => { setKillArmed(undefined); setFocusIndex(index); } }, job.id)))) }) }), _jsx(Divider, { color: "subtle", title: "" }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: t('jobs-panel-hint') }) })] }));
111
158
  }
@@ -10,7 +10,7 @@ import { AssistantThinkingMessage } from './messages/AssistantThinkingMessage.js
10
10
  import { AssistantToolUseMessage } from './messages/AssistantToolUseMessage.js';
11
11
  import { SubagentMessage } from './Chat/SubagentMessage.js';
12
12
  import { JobCard } from './Chat/JobCard.js';
13
- import { isMinimalMode } from '../minimalMode.js';
13
+ import { isMinimalUiMode } from '../minimalUiMode.js';
14
14
  import { noteFrameCause, noteListGeometry } from '../ink/geometry-trace.js';
15
15
  import { getTerminalFlushTick } from '../ink/flush-tick.js';
16
16
  import { TurnInterruptedRow } from './TurnInterruptedRow.js';
@@ -1171,9 +1171,9 @@ const MemoRow = React.memo(TranscriptRow);
1171
1171
  * fills the viewport.
1172
1172
  */
1173
1173
  export function LogoHeader({ model, effort, cwd, fontId, whale = true, whaleIdle = true, whaleGirl = false, starred = false, onStarClick, working = false, skipIntro = false, }) {
1174
- // Minimal mode drops the whole splash (whale art AND wordmark) — only the
1174
+ // The minimal UI drops the whole splash (whale art AND wordmark) — only the
1175
1175
  // transcript and a bare status bar remain.
1176
- if (isMinimalMode())
1176
+ if (isMinimalUiMode())
1177
1177
  return null;
1178
1178
  return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(LogoV2, { model: model, effort: effort, cwd: cwd, fontId: fontId, whale: whale, whaleIdle: whaleIdle, whaleGirl: whaleGirl, starred: starred, onStarClick: onStarClick, working: working, skipIntro: skipIntro }) }));
1179
1179
  }
@@ -8,6 +8,10 @@ import type { PresetOption } from '../dsh-adapter/channel.js';
8
8
  * `channel.switchPreset`, Esc cancels. Broken presets are listed (the
9
9
  * roster's discovery contract) but marked with their reason; the roster
10
10
  * default is tagged.
11
+ *
12
+ * The header says "内核 Agent 预设 / Kernel agent preset" (not just "Agent
13
+ * preset"): this screen changes the model-facing tool catalog, and must never
14
+ * be confused with the `/settings → 极简界面` (Minimal UI) display switch.
11
15
  */
12
16
  export declare function PresetPicker({ presets, focusIndex, currentPreset, onPick, }: {
13
17
  presets: readonly PresetOption[];
@@ -1 +1 @@
1
- {"version":3,"file":"PresetPicker.d.ts","sourceRoot":"","sources":["../../../src/components/PresetPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,UAAU,EACV,aAAa,EACb,MAAM,GACP,EAAE;IACD,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC;qDACiD;IACjD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC,GAAG,KAAK,CAAC,SAAS,CA4BlB"}
1
+ {"version":3,"file":"PresetPicker.d.ts","sourceRoot":"","sources":["../../../src/components/PresetPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,UAAU,EACV,aAAa,EACb,MAAM,GACP,EAAE;IACD,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC;qDACiD;IACjD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC,GAAG,KAAK,CAAC,SAAS,CA4BlB"}
@@ -12,9 +12,13 @@ import { HintLine } from './design-system/HintLine.js';
12
12
  * `channel.switchPreset`, Esc cancels. Broken presets are listed (the
13
13
  * roster's discovery contract) but marked with their reason; the roster
14
14
  * default is tagged.
15
+ *
16
+ * The header says "内核 Agent 预设 / Kernel agent preset" (not just "Agent
17
+ * preset"): this screen changes the model-facing tool catalog, and must never
18
+ * be confused with the `/settings → 极简界面` (Minimal UI) display switch.
15
19
  */
16
20
  export function PresetPicker({ presets, focusIndex, currentPreset, onPick, }) {
17
- return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: "Agent preset" }) }), _jsx(Select, { options: presets.map(preset => ({
21
+ return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('preset-picker-title') }) }), _jsx(Select, { options: presets.map(preset => ({
18
22
  value: preset.id,
19
23
  label: (preset.name ?? preset.id) +
20
24
  (preset.isDefault ? t('preset-default-tag') : '') +
@@ -1 +1 @@
1
- {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"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"}
@@ -2063,7 +2063,18 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2063
2063
  // indentation arm so the expanded editor participates in the cycle too —
2064
2064
  // the parser reports backtab as key.tab + key.shift.
2065
2065
  if (key.tab && key.shift) {
2066
- void channel.cycleMode();
2066
+ // The key is consumed either way. `cycleMode` is best-effort inside the
2067
+ // channel, but a dropped rejection here would be an unhandledRejection
2068
+ // (the process guard rethrows everything that is not React #185), and
2069
+ // an unhandled rejection kills the TUI with the session still open —
2070
+ // so the caller keeps a catch of its own rather than `void`ing the
2071
+ // promise (the sibling `/permission` entry point does the same).
2072
+ void channel.cycleMode().catch((error) => {
2073
+ channel.notify(t('mode-switch-failed', { err: error instanceof Error ? error.message : String(error) }), {
2074
+ color: 'error',
2075
+ timeoutMs: 8000,
2076
+ });
2077
+ });
2067
2078
  return;
2068
2079
  }
2069
2080
  // Expanded editor: plain Tab inserts indentation; Shift+Tab was handled
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { StarMilestone } from '../usageStats.js';
3
+ import type { WhaleCouponNotice } from '../dsh-adapter/oauth/bonus.js';
3
4
  /** What a star attempt came back with (the modal renders each outcome). */
4
5
  export type StarAttempt = {
5
6
  readonly kind: 'starred';
@@ -50,4 +51,9 @@ export declare function StarPrompt({ milestone, actions, onClose, initialPhase,
50
51
  * 走这条(只演感谢,不再问一次)。 */
51
52
  readonly initialPhase?: 'ask' | 'done';
52
53
  }): React.ReactNode;
54
+ export declare function WhaleCouponPrompt({ notice, onShown, onClose, }: {
55
+ readonly notice: WhaleCouponNotice;
56
+ readonly onShown: (orderId: WhaleCouponNotice['orderId']) => void;
57
+ readonly onClose: () => void;
58
+ }): React.ReactNode;
53
59
  //# sourceMappingURL=StarPrompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StarPrompt.d.ts","sourceRoot":"","sources":["../../../src/components/StarPrompt.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAWzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAuBrD,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9E;;0DAE0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAChD,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,OAAO,EACP,OAAO,EACP,YAAoB,GACrB,EAAE;IACD,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B;2BACuB;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvC,GAAG,KAAK,CAAC,SAAS,CAsOlB"}
1
+ {"version":3,"file":"StarPrompt.d.ts","sourceRoot":"","sources":["../../../src/components/StarPrompt.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAYzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AAuBtE,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9E;;0DAE0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAChD,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,OAAO,EACP,OAAO,EACP,YAAoB,GACrB,EAAE;IACD,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B;2BACuB;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvC,GAAG,KAAK,CAAC,SAAS,CAsOlB;AAmBD,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,OAAO,EACP,OAAO,GACR,EAAE;IACD,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAA;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,GAAG,KAAK,CAAC,SAAS,CA6DlB"}
@@ -6,6 +6,7 @@ import { Divider } from './design-system/Divider.js';
6
6
  import { HintLine } from './design-system/HintLine.js';
7
7
  import { ListItem } from './design-system/ListItem.js';
8
8
  import { MaidPortrait, useMaidPortraits } from './maidPortrait.js';
9
+ import { WhaleGirlHappyArt } from './WhaleGirl.js';
9
10
  import { useTerminalBackground } from './design-system/ThemeProvider.js';
10
11
  import { STANDARD_FRAME_INDEX, WhaleArt } from './Whale.js';
11
12
  import { OPENING_SEQUENCES, WHALE_FRAME_INDEX } from './whaleFrames.js';
@@ -151,6 +152,50 @@ export function StarPrompt({ milestone, actions, onClose, initialPhase = 'ask',
151
152
  actions.onOpen();
152
153
  }, children: t('star-modal-open') }), phase === 'failed' && (_jsxs(_Fragment, { children: [_jsx(Box, { height: 1 }), _jsx(Text, { color: "error", wrap: "wrap", children: failure })] })), phase !== 'failed' && (_jsxs(_Fragment, { children: [_jsx(Box, { height: 1 }), _jsx(Divider, { width: textColumns })] })), _jsx(Box, { height: 1 }), _jsx(Text, { dimColor: true, wrap: "wrap", children: _jsx(HintLine, { text: t('star-modal-hint') }) })] }))] })] }) })] }));
153
154
  }
155
+ /** The star modal's card and maid art, used only for a confirmed login grant. */
156
+ function couponExpiry(expiresAt) {
157
+ const date = new Date(expiresAt);
158
+ if (Number.isNaN(date.getTime()))
159
+ return null;
160
+ const parts = new Intl.DateTimeFormat('en-US', {
161
+ timeZone: 'Asia/Shanghai', month: 'numeric', day: 'numeric',
162
+ hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
163
+ }).formatToParts(date);
164
+ const part = (type) => parts.find(item => item.type === type)?.value;
165
+ const month = part('month');
166
+ const day = part('day');
167
+ const hour = part('hour');
168
+ const minute = part('minute');
169
+ return month === undefined || day === undefined || hour === undefined || minute === undefined
170
+ ? null : { month, day, time: `${hour}:${minute}` };
171
+ }
172
+ export function WhaleCouponPrompt({ notice, onShown, onClose, }) {
173
+ const lang = React.useSyncExternalStore(subscribeLang, getLang);
174
+ const { columns, rows } = useTerminalSize();
175
+ const imagesAvailable = useTerminalImages();
176
+ const portraits = useMaidPortraits(true);
177
+ const background = useTerminalBackground();
178
+ const withArt = columns >= MIN_ART_COLUMNS && rows >= MIN_ART_ROWS;
179
+ const textColumns = withArt ? TEXT_COLUMNS : Math.max(1, Math.min(TEXT_COLUMNS, columns - 6));
180
+ const cardColumns = withArt ? 96 : Math.min(columns, textColumns + 6);
181
+ const cardRows = withArt ? ART_ROWS + 2 : Math.min(rows, 15);
182
+ const left = Math.max(0, Math.floor((columns - cardColumns) / 2));
183
+ const bottom = Math.max(0, Math.min(Math.floor((rows - cardRows) / 2), rows - cardRows));
184
+ const amount = notice.amount.replace(/(\.\d*?)0+$/u, '$1').replace(/\.$/u, '');
185
+ const unit = lang === 'zh' ? notice.currency === 'CNY' ? '元' : '美元' : notice.currency;
186
+ const expiry = couponExpiry(notice.expiresAt);
187
+ React.useEffect(() => {
188
+ const timer = setTimeout(() => onShown(notice.orderId), 0);
189
+ return () => { clearTimeout(timer); };
190
+ }, [notice.orderId, onShown]);
191
+ useInput((_input, key) => {
192
+ if (key.return || key.escape)
193
+ onClose();
194
+ });
195
+ return (_jsxs(_Fragment, { children: [_jsx(Box, { position: "absolute", bottom: 0, left: 0, width: "100%", height: rows, flexShrink: 0, backdrop: "dim", onClick: onClose }), _jsx(Box, { position: "absolute", left: left, bottom: bottom, width: cardColumns, height: cardRows, flexDirection: "column", flexShrink: 0, overflow: "hidden", backgroundColor: background, opaque: true, onClick: event => { event.stopImmediatePropagation(); }, children: _jsxs(Box, { flexDirection: "row", gap: 2, alignItems: "center", justifyContent: "center", borderStyle: "round", borderColor: "inactive", paddingX: 2, backgroundColor: background, children: [withArt && (_jsxs(Box, { width: ART_COLUMNS, height: ART_ROWS, flexShrink: 0, flexDirection: "column", alignItems: "center", justifyContent: "center", backgroundColor: background, opaque: true, children: [_jsx(Confetti, { width: ART_COLUMNS }), imagesAvailable && portraits?.happy !== undefined
196
+ ? _jsx(MaidPortrait, { source: portraits.happy, maxColumns: ART_COLUMNS, maxRows: ART_ROWS - CONFETTI_ROWS, presentation: "preview" })
197
+ : _jsx(WhaleGirlHappyArt, { source: portraits?.happy, width: ART_COLUMNS })] })), _jsxs(Box, { flexDirection: "column", width: textColumns, ...(withArt ? { height: ART_ROWS } : {}), children: [_jsx(Text, { color: "accent", bold: true, wrap: "truncate-end", children: t('coupon-modal-title') }), _jsx(Box, { height: 1 }), _jsx(Text, { color: "success", bold: true, wrap: "wrap", children: t('coupon-modal-received', { amount, unit }) }), expiry !== null && (_jsxs(_Fragment, { children: [_jsx(Box, { height: 1 }), _jsx(Text, { wrap: "wrap", children: t('coupon-modal-expiry', expiry) })] })), _jsx(Box, { flexGrow: 1 }), _jsx(Divider, { width: textColumns }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: _jsx(HintLine, { text: t('coupon-modal-hint') }) })] })] }) })] }));
198
+ }
154
199
  /**
155
200
  * The card's art column: the raster maid first, the animated pixel whale
156
201
  * otherwise. While celebrating it shows the **happy maid** (raster→raster in
@@ -1 +1 @@
1
- {"version":3,"file":"SubagentCard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAGhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAO9D,MAAM,WAAW,iBAAiB;IAAG,QAAQ,EAAE,aAAa,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAAE;AAEpH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,iBAAiB,GAAG,KAAK,CAAC,SAAS,CAgD/F"}
1
+ {"version":3,"file":"SubagentCard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAEvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAGhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAO9D,MAAM,WAAW,iBAAiB;IAAG,QAAQ,EAAE,aAAa,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAAE;AAEpH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,iBAAiB,GAAG,KAAK,CAAC,SAAS,CAsD/F"}
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Box, Text } from '../ui.js';
4
4
  import { t } from '../i18n.js';
5
- import { isMinimalMode } from '../minimalMode.js';
5
+ import { isMinimalUiMode } from '../minimalUiMode.js';
6
6
  function formatDuration(ms) {
7
7
  const seconds = Math.floor(ms / 1000);
8
8
  return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m${seconds % 60}s`;
@@ -24,16 +24,16 @@ export function SubagentCard({ subagent, focused, onClick }) {
24
24
  // the card mid-gesture and reshuffle the whole dashboard list (user
25
25
  // feedback: hover must never change layout). The hover tint stays.
26
26
  const liveLine = running ? subagent.output[subagent.output.length - 1] : undefined;
27
- const minimal = isMinimalMode();
28
- const glyph = running ? (minimal ? '·' : '🟡')
29
- : subagent.status === 'unknown' ? (minimal ? '·' : '⚪')
30
- : subagent.status === 'failed' || subagent.status === 'cancelled' ? (minimal ? '×' : '🔴')
31
- : (minimal ? '✓' : '🟢');
32
- const glyphColor = minimal ? undefined
27
+ const minimalUi = isMinimalUiMode();
28
+ const glyph = running ? (minimalUi ? '·' : '🟡')
29
+ : subagent.status === 'unknown' ? (minimalUi ? '·' : '⚪')
30
+ : subagent.status === 'failed' || subagent.status === 'cancelled' ? (minimalUi ? '×' : '🔴')
31
+ : (minimalUi ? '✓' : '🟢');
32
+ const glyphColor = minimalUi ? undefined
33
33
  : running ? 'warning'
34
34
  : subagent.status === 'unknown' ? 'subtle'
35
35
  : subagent.status === 'failed' || subagent.status === 'cancelled' ? 'error'
36
36
  : 'success';
37
37
  const hoverTint = onClick !== undefined && hovered && !focused;
38
- return _jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, onClick: onClick, onMouseEnter: onClick !== undefined ? () => setHovered(true) : undefined, onMouseLeave: onClick !== undefined ? () => setHovered(false) : undefined, backgroundColor: hoverTint ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: glyphColor, children: glyph }), _jsx(Text, { bold: true, color: focused ? 'accent' : undefined, children: `${t('subagent-card-prefix')}${subagent.description}` }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }), _jsxs(Text, { dimColor: true, children: [elapsed !== undefined ? ` · ${formatDuration(elapsed)} · ` : ' · ', total || '—', " tok \u00B7 ", subagent.toolCalls.length, " tools"] })] })] }), liveLine && _jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${liveLine}` })] });
38
+ return _jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, onClick: onClick, onMouseEnter: onClick !== undefined ? () => setHovered(true) : undefined, onMouseLeave: onClick !== undefined ? () => setHovered(false) : undefined, backgroundColor: hoverTint ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: glyphColor, children: glyph }), _jsx(Text, { bold: true, color: focused ? 'accent' : undefined, children: `${t('subagent-card-prefix')}${subagent.description}` }), subagent.mode === 'continuable' && (_jsx(Text, { color: focused ? 'accent' : 'warning', children: t('subagent-mode-continuable') })), subagent.mode === 'one-shot' && (_jsx(Text, { dimColor: true, children: t('subagent-mode-one-shot') })), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }), _jsxs(Text, { dimColor: true, children: [elapsed !== undefined ? ` · ${formatDuration(elapsed)} · ` : ' · ', total || '—', " tok \u00B7 ", subagent.toolCalls.length, " tools"] })] })] }), liveLine && _jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${liveLine}` })] });
39
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SubagentDetailScene.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDetailScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAwGhE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,WAAW,GACZ,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CAiN5C"}
1
+ {"version":3,"file":"SubagentDetailScene.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDetailScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAsB,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAqJpF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,WAAW,GACZ,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CA6S5C"}