@deepseek-harness-tui/dsh-tui 0.5.2 → 0.5.3
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.
- package/README.md +6 -0
- package/lib/types/approvals.d.ts +80 -0
- package/lib/types/approvals.d.ts.map +1 -0
- package/lib/types/approvals.js +170 -0
- package/lib/types/commands.d.ts +14 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +12 -0
- package/lib/types/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/compat/sessionLog.js +52 -37
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/CommandSuggestions.js +5 -3
- package/lib/types/components/HelpMenu.d.ts +1 -0
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +4 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +16 -15
- package/lib/types/components/approvals/ApprovalPanel.d.ts +21 -0
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -0
- package/lib/types/components/approvals/ApprovalPanel.js +48 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +6 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +9 -2
- package/lib/types/components/questions/PlanReviewPanel.d.ts +39 -0
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -0
- package/lib/types/components/questions/PlanReviewPanel.js +168 -0
- package/lib/types/i18n.d.ts +174 -2
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +82 -1
- package/lib/types/ink/stringWidth.d.ts.map +1 -1
- package/lib/types/ink/stringWidth.js +61 -0
- package/lib/types/plugin.d.ts.map +1 -1
- package/lib/types/plugin.js +15 -0
- package/lib/types/questions.d.ts +3 -0
- package/lib/types/questions.d.ts.map +1 -1
- package/lib/types/questions.js +13 -1
- package/lib/types/screens/Chat.d.ts +8 -13
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +33 -18
- package/lib/types/utils/modifiers.d.ts +19 -0
- package/lib/types/utils/modifiers.d.ts.map +1 -0
- package/lib/types/utils/modifiers.js +17 -0
- package/package.json +2 -1
package/lib/types/i18n.js
CHANGED
|
@@ -168,7 +168,7 @@ const dict = {
|
|
|
168
168
|
'update-check-failed': { zh: '无法确认新版本(网络或 registry 不可达),已尝试直接更新……', en: 'Could not confirm a newer version (network or registry unreachable); attempting the update anyway…' },
|
|
169
169
|
'vim-not-implemented': { zh: 'vim 模式暂未实现', en: 'vim mode not implemented yet' },
|
|
170
170
|
'terminal-setup-hint': { zh: '推荐 Windows Terminal(≥110 列、等宽字体、TrueColor)。', en: 'Recommended: Windows Terminal (≥110 columns, monospace, TrueColor).' },
|
|
171
|
-
'terminal-paste-hint': { zh: '
|
|
171
|
+
'terminal-paste-hint': { zh: '{{mod}}V 粘贴文本/文件路径;Ctrl+Shift+V 终端原生粘贴;右键粘贴同样可用。', en: '{{mod}}V pastes text/file paths; Ctrl+Shift+V is native terminal paste; right-click paste also works.' },
|
|
172
172
|
'connect-none': { zh: 'DSH 暂无远程连接机制(CC 的 /connect 对应能力未适配)。', en: 'DSH has no remote connection mechanism (CC\'s /connect equivalent is not adapted).' },
|
|
173
173
|
'theme-switch-failed': { zh: '主题「{{name}}」切换失败(无法写入 ~/.dsh-cc/theme.json)', en: 'Theme "{{name}}" switch failed (cannot write ~/.dsh-cc/theme.json)' },
|
|
174
174
|
'interrupt-delivered': { zh: '已打断当前回合,{{n}} 条消息立即处理', en: 'Interrupted current turn, {{n}} messages processed immediately' },
|
|
@@ -247,6 +247,74 @@ const dict = {
|
|
|
247
247
|
'question-custom-tab': { zh: '自定义回答', en: 'Custom answer' },
|
|
248
248
|
'question-attached-label': { zh: '(附加:{{label}})', en: '(attached: {{label}})' },
|
|
249
249
|
'question-direct-input': { zh: '直接输入…', en: 'Type directly…' },
|
|
250
|
+
// ── components/approvals/ApprovalPanel.tsx ──────────────────────────
|
|
251
|
+
'approval-waiting': { zh: ' ⏳ 等待审批 · {{tool}} ', en: ' Awaiting approval · {{tool}} ' },
|
|
252
|
+
'approval-proceed': { zh: '要允许这次操作吗?', en: 'Do you want to proceed?' },
|
|
253
|
+
'approval-yes': { zh: '允许(仅本次)', en: 'Yes, allow once' },
|
|
254
|
+
'approval-no': { zh: '拒绝', en: 'No' },
|
|
255
|
+
'approval-hint': { zh: '↑/↓ 选择 · Enter 确认 · Esc 拒绝', en: '↑/↓ select · Enter confirm · Esc reject' },
|
|
256
|
+
// ── components/questions/PlanReviewPanel.tsx ────────────────────────
|
|
257
|
+
'plan-review-fallback-header': { zh: '计划评审', en: 'Plan review' },
|
|
258
|
+
'plan-review-feedback-placeholder': { zh: '输入反馈,告诉模型要改什么…', en: 'Tell the model what to change…' },
|
|
259
|
+
'plan-review-approve-needs-empty': { zh: '请先清空反馈再批准(或在输入行回车提交反馈)', en: 'Clear the feedback to approve (or press Enter on the input row to send it)' },
|
|
260
|
+
'plan-review-hint': { zh: '↑/↓ 选择 · 1/2 快选 · 打字输入反馈 · Enter 提交 · Esc 打断评审', en: '↑/↓ select · 1/2 quick-pick · type feedback · Enter submit · Esc dismiss' },
|
|
261
|
+
// ── commands.ts — slash-command descriptions ─────────────────────────
|
|
262
|
+
// zh-only on purpose: the English text stays in `LOCAL_COMMANDS` (and in
|
|
263
|
+
// the DSH registry for external commands) as the single source of truth,
|
|
264
|
+
// so `localizedDescription` falls back to it whenever the active language
|
|
265
|
+
// has no entry here. `cmd-desc-<name>` keys are resolved at render time,
|
|
266
|
+
// so `/lang` switches apply on the next repaint.
|
|
267
|
+
// Conversation
|
|
268
|
+
'cmd-desc-new': { zh: '新开会话' },
|
|
269
|
+
'cmd-desc-clear': { zh: '清空当前会话' },
|
|
270
|
+
'cmd-desc-compact': { zh: '压缩会话历史' },
|
|
271
|
+
'cmd-desc-resume': { zh: '恢复历史会话' },
|
|
272
|
+
'cmd-desc-rewind': { zh: '回退会话到历史消息' },
|
|
273
|
+
'cmd-desc-export': { zh: '导出会话为 Markdown 文件' },
|
|
274
|
+
// Session / environment
|
|
275
|
+
'cmd-desc-status': { zh: '查看会话状态' },
|
|
276
|
+
'cmd-desc-cost': { zh: '查看会话 token 用量' },
|
|
277
|
+
'cmd-desc-config': { zh: '查看 dsh-cc 配置来源' },
|
|
278
|
+
'cmd-desc-doctor': { zh: '运行环境检查' },
|
|
279
|
+
'cmd-desc-init': { zh: '在工作目录创建 AGENTS.md' },
|
|
280
|
+
'cmd-desc-agents': { zh: '查看本会话的子代理' },
|
|
281
|
+
// Model / display
|
|
282
|
+
'cmd-desc-activity': { zh: '切换工作状态指示器预设' },
|
|
283
|
+
'cmd-desc-preset': { zh: '切换 Agent 预设(standard/code/minimal/cordis)' },
|
|
284
|
+
'cmd-desc-theme': { zh: '切换配色主题(内置或自定义)' },
|
|
285
|
+
'cmd-desc-lang': { zh: '切换界面语言(en / zh)' },
|
|
286
|
+
'cmd-desc-model': { zh: '查看当前模型' },
|
|
287
|
+
'cmd-desc-thinking': { zh: '切换扩展思考显示' },
|
|
288
|
+
'cmd-desc-tokens': { zh: '查看会话 token 用量' },
|
|
289
|
+
// Account / policy
|
|
290
|
+
'cmd-desc-login': { zh: '查看 API 凭证状态' },
|
|
291
|
+
'cmd-desc-logout': { zh: '清除 API 凭证' },
|
|
292
|
+
'cmd-desc-permissions': { zh: '查看权限策略状态' },
|
|
293
|
+
'cmd-desc-add-dir': { zh: '查看文件系统策略范围' },
|
|
294
|
+
'cmd-desc-hooks': { zh: '查看 hooks 状态' },
|
|
295
|
+
'cmd-desc-mcp': { zh: '查看 MCP 状态' },
|
|
296
|
+
'cmd-desc-memory': { zh: '查看记忆状态' },
|
|
297
|
+
'cmd-desc-update': { zh: '更新 dsh-cc-tui 并重启' },
|
|
298
|
+
// Built-in skills
|
|
299
|
+
'cmd-desc-audit': { zh: '对当前项目做全面代码审计' },
|
|
300
|
+
'cmd-desc-bug': { zh: '记录一份 bug 报告' },
|
|
301
|
+
'cmd-desc-practice': { zh: '与 dsh-cc 进行编程练习' },
|
|
302
|
+
'cmd-desc-review': { zh: '对当前项目做全面代码评审' },
|
|
303
|
+
'cmd-desc-pr_comments': { zh: '审查拉取请求评论' },
|
|
304
|
+
'cmd-desc-release-notes': { zh: '生成发布说明' },
|
|
305
|
+
'cmd-desc-vuln-check': { zh: '运行安全漏洞检查' },
|
|
306
|
+
// Misc
|
|
307
|
+
'cmd-desc-vim': { zh: '切换 vim 模式' },
|
|
308
|
+
'cmd-desc-terminal-setup': { zh: '查看终端配置建议' },
|
|
309
|
+
'cmd-desc-connect': { zh: '连接远程机器' },
|
|
310
|
+
// Help / exit
|
|
311
|
+
'cmd-desc-help': { zh: '查看快捷键与命令' },
|
|
312
|
+
'cmd-desc-exit': { zh: '退出 dsh-tui' },
|
|
313
|
+
// Registry-injected (external) commands — zh only; en falls back to the
|
|
314
|
+
// registry's own description, and unlisted externals always fall back.
|
|
315
|
+
'cmd-desc-plan': { zh: '切换计划模式(/plan off 退出)' },
|
|
316
|
+
'cmd-desc-goal': { zh: '设置或查看会话目标' },
|
|
317
|
+
'cmd-desc-feedback': { zh: '提交使用反馈' },
|
|
250
318
|
// ── /lang command ───────────────────────────────────────────────────
|
|
251
319
|
'lang-current': { zh: '当前语言 {{lang}}', en: 'Current language {{lang}}' },
|
|
252
320
|
'lang-switch-hint': { zh: '切换 /lang en | /lang zh', en: 'Switch /lang en | /lang zh' },
|
|
@@ -291,6 +359,19 @@ export function t(key, params = {}) {
|
|
|
291
359
|
const template = entry?.[activeLang] ?? key;
|
|
292
360
|
return template.replace(/\{\{(\w+)\}\}/g, (match, name) => name in params ? String(params[name]) : match);
|
|
293
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Translate a runtime-computed key (e.g. `cmd-desc-${name}`), falling back
|
|
364
|
+
* to the given text when the key is missing or has no entry in the active
|
|
365
|
+
* language — unlike {@link t}, which renders the key itself. Used where the
|
|
366
|
+
* fallback holds the authoritative text (command descriptions: the en copy
|
|
367
|
+
* lives in `LOCAL_COMMANDS` / the DSH registry, the dict carries zh only).
|
|
368
|
+
* @param key - Dictionary key, computed at runtime so it is not type-checked.
|
|
369
|
+
* @param fallback - Text used when no translation exists.
|
|
370
|
+
*/
|
|
371
|
+
export function tOr(key, fallback) {
|
|
372
|
+
const entry = dict[key];
|
|
373
|
+
return entry?.[activeLang] ?? fallback;
|
|
374
|
+
}
|
|
294
375
|
// ── persistence (~/.dsh-cc/lang.json) ──────────────────────────────────
|
|
295
376
|
/**
|
|
296
377
|
* Parse a persisted `{ lang }` value; anything else yields undefined.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stringWidth.d.ts","sourceRoot":"","sources":["../../../src/ink/stringWidth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stringWidth.d.ts","sourceRoot":"","sources":["../../../src/ink/stringWidth.ts"],"names":[],"mappings":"AAuRA;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAElB,CAAA"}
|
|
@@ -114,8 +114,69 @@ function getEmojiWidth(grapheme) {
|
|
|
114
114
|
return 1;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
// Single codepoint without VS16: only Emoji_Presentation=Yes characters
|
|
118
|
+
// actually render as wide emoji. Text-default emoji (✳ U+2733, ⚠ U+26A0,
|
|
119
|
+
// ❤ U+2764, ❄ U+2744, ✂ U+2702 …) render width 1 in terminals
|
|
120
|
+
// (ghostty/kitty/iTerm2/xterm.js all agree) unless VS16 forces emoji
|
|
121
|
+
// presentation. emoji-regex matches BOTH categories, so counting every
|
|
122
|
+
// match as 2 desyncs the layout from the terminal: the spinner glyph ✳
|
|
123
|
+
// measured 2 but painted 1, shifting the whole spinner row 1 column every
|
|
124
|
+
// time the animation cycle hit it — leaving stray cells ("tthinking",
|
|
125
|
+
// orphan "t" residue). VS16 sequences fall through to width 2 below.
|
|
126
|
+
if (!grapheme.includes('\ufe0f')) {
|
|
127
|
+
let codepoints = 0;
|
|
128
|
+
for (const _ of grapheme)
|
|
129
|
+
codepoints++;
|
|
130
|
+
if (codepoints === 1 && !hasEmojiPresentation(first)) {
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
117
134
|
return 2;
|
|
118
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Emoji_Presentation=Yes codepoints (Unicode emoji-data.txt, merged
|
|
138
|
+
* ranges). These render as wide emoji without VS16; everything else
|
|
139
|
+
* emoji-regex matches is text-default and renders narrow.
|
|
140
|
+
*/
|
|
141
|
+
const EMOJI_PRESENTATION_RANGES = [
|
|
142
|
+
[0x231a, 0x231b], [0x23e9, 0x23ec], 0x23f0, 0x23f3, [0x25fd, 0x25fe],
|
|
143
|
+
[0x2614, 0x2615], [0x2648, 0x2653], 0x267f, 0x2693, 0x26a1,
|
|
144
|
+
[0x26aa, 0x26ab], [0x26bd, 0x26be], [0x26c4, 0x26c5], 0x26ce, 0x26d4,
|
|
145
|
+
0x26ea, [0x26f2, 0x26f3], 0x26f5, 0x26fa, 0x26fd, 0x2705,
|
|
146
|
+
[0x270a, 0x270b], 0x2728, 0x274c, 0x274e, [0x2753, 0x2755], 0x2757,
|
|
147
|
+
[0x2795, 0x2797], 0x27b0, 0x27bf, [0x2b1b, 0x2b1c], 0x2b50, 0x2b55,
|
|
148
|
+
0x1f004, 0x1f0cf, 0x1f18e, [0x1f191, 0x1f19a], [0x1f1e6, 0x1f1ff],
|
|
149
|
+
0x1f201, 0x1f21a, 0x1f22f, [0x1f232, 0x1f236], [0x1f238, 0x1f23a],
|
|
150
|
+
[0x1f250, 0x1f251], [0x1f300, 0x1f320], [0x1f32d, 0x1f335],
|
|
151
|
+
[0x1f337, 0x1f37c], [0x1f37e, 0x1f393], [0x1f3a0, 0x1f3ca],
|
|
152
|
+
[0x1f3cf, 0x1f3d3], [0x1f3e0, 0x1f3f0], 0x1f3f4, [0x1f3f8, 0x1f43e],
|
|
153
|
+
0x1f440, [0x1f442, 0x1f4fc], [0x1f4ff, 0x1f53d], [0x1f54b, 0x1f54e],
|
|
154
|
+
[0x1f550, 0x1f567], 0x1f57a, [0x1f595, 0x1f596], 0x1f5a4,
|
|
155
|
+
[0x1f5fb, 0x1f64f], [0x1f680, 0x1f6c5], 0x1f6cc, [0x1f6d0, 0x1f6d2],
|
|
156
|
+
[0x1f6d5, 0x1f6d8], [0x1f6dc, 0x1f6df], [0x1f6eb, 0x1f6ec],
|
|
157
|
+
[0x1f6f4, 0x1f6fc], [0x1f7e0, 0x1f7eb], 0x1f7f0, [0x1f90c, 0x1f93a],
|
|
158
|
+
[0x1f93c, 0x1f945], [0x1f947, 0x1f9ff], [0x1fa70, 0x1fa7c],
|
|
159
|
+
[0x1fa80, 0x1fa8a], [0x1fa8e, 0x1fac6], 0x1fac8, [0x1facd, 0x1fadc],
|
|
160
|
+
[0x1fadf, 0x1faea], [0x1faef, 0x1faf8],
|
|
161
|
+
];
|
|
162
|
+
/** Binary search over the merged, sorted Emoji_Presentation ranges. */
|
|
163
|
+
function hasEmojiPresentation(cp) {
|
|
164
|
+
let lo = 0;
|
|
165
|
+
let hi = EMOJI_PRESENTATION_RANGES.length - 1;
|
|
166
|
+
while (lo <= hi) {
|
|
167
|
+
const mid = (lo + hi) >> 1;
|
|
168
|
+
const entry = EMOJI_PRESENTATION_RANGES[mid];
|
|
169
|
+
const start = typeof entry === 'number' ? entry : entry[0];
|
|
170
|
+
const end = typeof entry === 'number' ? entry : entry[1];
|
|
171
|
+
if (cp < start)
|
|
172
|
+
hi = mid - 1;
|
|
173
|
+
else if (cp > end)
|
|
174
|
+
lo = mid + 1;
|
|
175
|
+
else
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
119
180
|
function isZeroWidth(codePoint) {
|
|
120
181
|
// Fast path for common printable range
|
|
121
182
|
if (codePoint >= 0x20 && codePoint < 0x7f)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAmBnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuTvE;AAyGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA"}
|
package/lib/types/plugin.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createChannel } from './channel.js';
|
|
|
7
7
|
import { createChildStderrReporter, installChildStderrGuard } from './childStderr.js';
|
|
8
8
|
import { logForDebugging } from './utils/debug.js';
|
|
9
9
|
import { QuestionStore } from './questions.js';
|
|
10
|
+
import { ApprovalStore } from './approvals.js';
|
|
10
11
|
import { registerPackagedSkills } from './packaged-skills.js';
|
|
11
12
|
import { readActivityFrames } from './activityPrefs.js';
|
|
12
13
|
import { readModelPref } from './modelPrefs.js';
|
|
@@ -143,6 +144,19 @@ export async function apply(ctx, config) {
|
|
|
143
144
|
agentPreset,
|
|
144
145
|
handle,
|
|
145
146
|
});
|
|
147
|
+
// DSH approval seam: the permission layer asks ApprovalService.request(),
|
|
148
|
+
// which dispatches an `approval/request` waterfall. With no answerer the
|
|
149
|
+
// chain falls through to the fail-closed 'unavailable', so register this
|
|
150
|
+
// TUI as the interactive answerer for the agent it owns; requests for
|
|
151
|
+
// other agents delegate down the chain (next()). Guarded on the service
|
|
152
|
+
// being mounted — a bare composition without the dsh-base approval row
|
|
153
|
+
// has nothing to answer into. channel.agentId tracks agent swaps
|
|
154
|
+
// (/new, /resume, rewind), so ownership is re-evaluated per request.
|
|
155
|
+
const approvalStore = new ApprovalStore();
|
|
156
|
+
if (ctx.get('approval') !== undefined) {
|
|
157
|
+
ctx.on('approval/request', (req, next) => String(req.agent.id) === channel.agentId ? approvalStore.park(req) : next());
|
|
158
|
+
ctx.effect(() => () => approvalStore.settleAll('cancelled'));
|
|
159
|
+
}
|
|
146
160
|
// Attach the stderr reporter to the live channel and flush anything a
|
|
147
161
|
// startup-spawned server produced while the channel didn't exist yet.
|
|
148
162
|
notifyStderr = (text, options) => channel.notify(text, options);
|
|
@@ -241,6 +255,7 @@ export async function apply(ctx, config) {
|
|
|
241
255
|
const chat = React.createElement(Chat, {
|
|
242
256
|
channel,
|
|
243
257
|
questionStore,
|
|
258
|
+
approvalStore,
|
|
244
259
|
onExit: () => handleExit(),
|
|
245
260
|
// Only a `dsh --profile <name>` launch has a profile installation for
|
|
246
261
|
// `/update` to act on; source checkouts and `--config` overlays get the
|
package/lib/types/questions.d.ts
CHANGED
|
@@ -92,6 +92,9 @@ export declare class QuestionStore {
|
|
|
92
92
|
cancelCurrent(): void;
|
|
93
93
|
/** Reject the active and all queued asks (plugin teardown). */
|
|
94
94
|
rejectAll(): void;
|
|
95
|
+
/** User-initiated cancel — the asker learns the user wants to speak. */
|
|
96
|
+
private cancel;
|
|
97
|
+
/** Harness-side interruption — abort signal fired or plugin teardown. */
|
|
95
98
|
private fail;
|
|
96
99
|
}
|
|
97
100
|
//# sourceMappingURL=questions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questions.d.ts","sourceRoot":"","sources":["../../src/questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAA;AAExC;mEACmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAgBD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IACtC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;
|
|
1
|
+
{"version":3,"file":"questions.d.ts","sourceRoot":"","sources":["../../src/questions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAA;AAExC;mEACmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAgBD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IACtC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAiCD;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAI;IACpB,yEAAyE;IACzE,OAAO,CAAC,SAAS,CAAwB;IACzC;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAgC;IAErD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAO3C;;;OAGG;IACH,WAAW,IAAI,gBAAgB,GAAG,IAAI;IAItC;;;OAGG;IACH,aAAa,IAAI,eAAe,EAAE;IAMlC,OAAO,CAAC,IAAI;IAIZ,sEAAsE;IACtE,OAAO,CAAC,eAAe;IAgBvB;;;;;;OAMG;IACH,GAAG,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA6BpE,8CAA8C;IAC9C,OAAO,CAAC,SAAS;IAOjB;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAwBjD,6DAA6D;IAC7D,aAAa,IAAI,IAAI;IAUrB,+DAA+D;IAC/D,SAAS,IAAI,IAAI;IASjB,wEAAwE;IACxE,OAAO,CAAC,MAAM;IAOd,yEAAyE;IACzE,OAAO,CAAC,IAAI;CAMb"}
|
package/lib/types/questions.js
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
import { t } from './i18n.js';
|
|
15
15
|
import { UserQuestionError, } from '@deepseek-ai/dsh-user-questions';
|
|
16
16
|
const ASK_ABORTED = 'ASK_ABORTED';
|
|
17
|
+
/**
|
|
18
|
+
* User-initiated cancel (Esc / Ctrl+C in the panel). dsh-plan-mode keys on
|
|
19
|
+
* this code to report "the user dismissed the plan review to speak instead"
|
|
20
|
+
* rather than the generic abort message, so user cancels must be told apart
|
|
21
|
+
* from harness aborts (signal fired) and teardown rejects.
|
|
22
|
+
*/
|
|
23
|
+
const ASK_CANCELLED = 'ASK_CANCELLED';
|
|
17
24
|
/** Truncate a long answer line for the transcript summary. */
|
|
18
25
|
function clip(text, max = 140) {
|
|
19
26
|
if (text.length <= max)
|
|
@@ -180,7 +187,7 @@ export class QuestionStore {
|
|
|
180
187
|
return;
|
|
181
188
|
this.active = undefined;
|
|
182
189
|
this.rebuildSnapshot();
|
|
183
|
-
this.
|
|
190
|
+
this.cancel(pending);
|
|
184
191
|
this.startNext();
|
|
185
192
|
this.emit();
|
|
186
193
|
}
|
|
@@ -195,6 +202,11 @@ export class QuestionStore {
|
|
|
195
202
|
this.fail(pending);
|
|
196
203
|
this.emit();
|
|
197
204
|
}
|
|
205
|
+
/** User-initiated cancel — the asker learns the user wants to speak. */
|
|
206
|
+
cancel(pending) {
|
|
207
|
+
pending.reject(new UserQuestionError('the user cancelled ask_user_question', ASK_CANCELLED));
|
|
208
|
+
}
|
|
209
|
+
/** Harness-side interruption — abort signal fired or plugin teardown. */
|
|
198
210
|
fail(pending) {
|
|
199
211
|
pending.reject(new UserQuestionError('ask_user_question was interrupted before the user answered', ASK_ABORTED));
|
|
200
212
|
}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Channel } from '../channel.js';
|
|
3
3
|
import type { QuestionStore } from '../questions.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* (with the current turn's prompt pinned above the viewport while scrolled
|
|
7
|
-
* up), transient notifications, the working spinner, the bordered prompt
|
|
8
|
-
* input (with slash-command overlay) and the status line pinned at the
|
|
9
|
-
* bottom.
|
|
10
|
-
*
|
|
11
|
-
* Ctrl+O toggles expanded detail globally; Shift+↑ enters message-selection
|
|
12
|
-
* mode (↑/↓ move, Enter expands the selected row, Esc exits); Ctrl+C
|
|
13
|
-
* interrupts the running turn, or (when idle) asks for a second Ctrl+C to
|
|
14
|
-
* exit; Enter while scrolled up jumps back to the bottom.
|
|
15
|
-
*/
|
|
16
|
-
export declare function Chat({ channel, questionStore, onExit, onUpdate, }: {
|
|
4
|
+
import { ApprovalStore } from '../approvals.js';
|
|
5
|
+
export declare function Chat({ channel, questionStore, approvalStore, onExit, onUpdate, }: {
|
|
17
6
|
channel: Channel;
|
|
18
7
|
questionStore: QuestionStore;
|
|
8
|
+
/**
|
|
9
|
+
* The approval seam's UI store. Optional: hosts without an approval
|
|
10
|
+
* channel (headless scripts, older embeds) render Chat without it and
|
|
11
|
+
* simply never see an approval panel — the question panel keeps its seat.
|
|
12
|
+
*/
|
|
13
|
+
approvalStore?: ApprovalStore;
|
|
19
14
|
onExit: () => void;
|
|
20
15
|
/** Update the installed package and restart the current TUI process. */
|
|
21
16
|
onUpdate?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/screens/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/screens/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,OAAO,EAAyB,MAAM,eAAe,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AA0G/C,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,aAAa,EACb,aAAa,EACb,MAAM,EACN,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,aAAa,CAAA;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,qBAwxCA"}
|
|
@@ -3,8 +3,11 @@ import React from 'react';
|
|
|
3
3
|
import { t, getLang, setLang, isLang, writeLangPref, subscribeLang } from '../i18n.js';
|
|
4
4
|
import { Box, Text, useInput, ScrollBox, useTheme } from '../ui.js';
|
|
5
5
|
import { POINTER } from '../cc/figures.js';
|
|
6
|
+
import { isMod, modLabel } from '../utils/modifiers.js';
|
|
6
7
|
import { formatTokens } from '../cc/format.js';
|
|
8
|
+
import { ApprovalStore } from '../approvals.js';
|
|
7
9
|
import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
|
|
10
|
+
import { ApprovalPanel } from '../components/approvals/ApprovalPanel.js';
|
|
8
11
|
import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js';
|
|
9
12
|
import { useTerminalTitle } from '../ink/hooks/use-terminal-title.js';
|
|
10
13
|
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
@@ -91,7 +94,13 @@ function searchableText(row) {
|
|
|
91
94
|
* interrupts the running turn, or (when idle) asks for a second Ctrl+C to
|
|
92
95
|
* exit; Enter while scrolled up jumps back to the bottom.
|
|
93
96
|
*/
|
|
94
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Shared inert approval store for hosts that render Chat without an
|
|
99
|
+
* approval seam (headless verify scripts). Never parked into, so its
|
|
100
|
+
* snapshot stays null and the approval panel never mounts.
|
|
101
|
+
*/
|
|
102
|
+
let fallbackApprovalStore;
|
|
103
|
+
export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate, }) {
|
|
95
104
|
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
96
105
|
React.useSyncExternalStore(channel.subscribe, () => channel.version);
|
|
97
106
|
// Re-render on language switches so the whole UI hot-swaps its strings.
|
|
@@ -99,6 +108,12 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
99
108
|
// The pending ask-user-question (DSH user-interaction seam): the model's
|
|
100
109
|
// `ask_user_question` tool parks here until the panel is answered.
|
|
101
110
|
const questionSnapshot = React.useSyncExternalStore(listener => questionStore.subscribe(listener), () => questionStore.getSnapshot());
|
|
111
|
+
// The pending tool-approval ask (DSH approval seam): the permission layer
|
|
112
|
+
// parks here until the panel decides; shown with priority over a pending
|
|
113
|
+
// questionnaire since it gates a tool about to run. Hosts that pass no
|
|
114
|
+
// approvalStore share one inert instance that never holds an ask.
|
|
115
|
+
const approvals = approvalStore ?? (fallbackApprovalStore ??= new ApprovalStore());
|
|
116
|
+
const approvalSnapshot = React.useSyncExternalStore(listener => approvals.subscribe(listener), () => approvals.getSnapshot());
|
|
102
117
|
// When a questionnaire batch completes, fold a Q&A summary into the
|
|
103
118
|
// transcript (the tool card itself is hidden from the message list).
|
|
104
119
|
const questionOpenRef = React.useRef(questionSnapshot !== null);
|
|
@@ -616,7 +631,7 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
616
631
|
setHelpOpen(false);
|
|
617
632
|
channel.pushLocal('/terminal-setup', [
|
|
618
633
|
t('terminal-setup-hint'),
|
|
619
|
-
t('terminal-paste-hint'),
|
|
634
|
+
t('terminal-paste-hint', { mod: modLabel }),
|
|
620
635
|
]);
|
|
621
636
|
return true;
|
|
622
637
|
case 'connect':
|
|
@@ -802,10 +817,10 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
802
817
|
rowRefsRef.current.delete(rowId);
|
|
803
818
|
}, []);
|
|
804
819
|
useInput((input, key, event) => {
|
|
805
|
-
// The questionnaire owns the keyboard while
|
|
806
|
-
// panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc;
|
|
807
|
-
// input is unmounted, so nothing else should see these keys).
|
|
808
|
-
if (questionSnapshot !== null)
|
|
820
|
+
// The questionnaire / approval panel owns the keyboard while one is
|
|
821
|
+
// pending (the panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc;
|
|
822
|
+
// the prompt input is unmounted, so nothing else should see these keys).
|
|
823
|
+
if (questionSnapshot !== null || approvalSnapshot !== null)
|
|
809
824
|
return;
|
|
810
825
|
// Mouse wheel scrolls the transcript — in fullscreen there is no
|
|
811
826
|
// terminal scrollback (alt-screen), so this is the only way back.
|
|
@@ -862,7 +877,7 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
862
877
|
else if (key.end) {
|
|
863
878
|
setSearchCursor(searchQuery.length);
|
|
864
879
|
}
|
|
865
|
-
else if (!key.ctrl && !key.meta && input) {
|
|
880
|
+
else if (!key.ctrl && !key.meta && !key.super && input) {
|
|
866
881
|
const next = searchQuery.slice(0, searchCursor) + input + searchQuery.slice(searchCursor);
|
|
867
882
|
setSearchQuery(next);
|
|
868
883
|
setSearchCursor(searchCursor + input.length);
|
|
@@ -873,12 +888,12 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
873
888
|
// After Enter closed the search bar, n/N keep walking the matches
|
|
874
889
|
// (CC: "Query persists across bar open/close so n/N keep working").
|
|
875
890
|
// Transcript mode only — in prompt mode n/N are ordinary input chars.
|
|
876
|
-
if (expanded && input === 'n' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta) {
|
|
891
|
+
if (expanded && input === 'n' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta && !key.super) {
|
|
877
892
|
setSearchCurrent(i => (i >= searchCount - 1 ? 0 : i + 1));
|
|
878
893
|
event.stopImmediatePropagation();
|
|
879
894
|
return;
|
|
880
895
|
}
|
|
881
|
-
if (expanded && input === 'N' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta) {
|
|
896
|
+
if (expanded && input === 'N' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta && !key.super) {
|
|
882
897
|
setSearchCurrent(i => (i <= 0 ? searchCount - 1 : i - 1));
|
|
883
898
|
event.stopImmediatePropagation();
|
|
884
899
|
return;
|
|
@@ -1053,7 +1068,7 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
1053
1068
|
else if (key.upArrow) {
|
|
1054
1069
|
setHistoryFocus(index => historyMatches.length === 0 ? 0 : (index <= 0 ? historyMatches.length - 1 : index - 1));
|
|
1055
1070
|
}
|
|
1056
|
-
else if (key.downArrow || (key
|
|
1071
|
+
else if (key.downArrow || (isMod(key) && input === 'r')) {
|
|
1057
1072
|
// CC's historySearch:next — ↓ and repeat ctrl+r walk to the next match.
|
|
1058
1073
|
setHistoryFocus(index => historyMatches.length === 0 ? 0 : (index >= historyMatches.length - 1 ? 0 : index + 1));
|
|
1059
1074
|
}
|
|
@@ -1083,7 +1098,7 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
1083
1098
|
else if (key.end) {
|
|
1084
1099
|
setHistoryCursor(historyQuery.length);
|
|
1085
1100
|
}
|
|
1086
|
-
else if (!key.ctrl && !key.meta && input) {
|
|
1101
|
+
else if (!key.ctrl && !key.meta && !key.super && input) {
|
|
1087
1102
|
const next = historyQuery.slice(0, historyCursor) + input + historyQuery.slice(historyCursor);
|
|
1088
1103
|
setHistoryQuery(next);
|
|
1089
1104
|
setHistoryCursor(historyCursor + input.length);
|
|
@@ -1122,12 +1137,12 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
1122
1137
|
}
|
|
1123
1138
|
return;
|
|
1124
1139
|
}
|
|
1125
|
-
if (key
|
|
1140
|
+
if (isMod(key) && input === 't') {
|
|
1126
1141
|
// Toggle the startup loaded-context panel (keyboard only — the
|
|
1127
1142
|
// ported ink core handles no mouse clicks).
|
|
1128
1143
|
setLoadedContextOpen(previous => !previous);
|
|
1129
1144
|
}
|
|
1130
|
-
if (key
|
|
1145
|
+
if (isMod(key) && input === 'r' && !helpOpen) {
|
|
1131
1146
|
setHistoryQuery('');
|
|
1132
1147
|
setHistoryCursor(0);
|
|
1133
1148
|
setHistoryFocus(0);
|
|
@@ -1169,11 +1184,11 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
1169
1184
|
}
|
|
1170
1185
|
event.stopImmediatePropagation();
|
|
1171
1186
|
}
|
|
1172
|
-
else if (key
|
|
1187
|
+
else if (isMod(key) && input === 'o') {
|
|
1173
1188
|
// Leaving transcript mode (Ctrl+O) — search was already handled above.
|
|
1174
1189
|
setExpanded(previous => !previous);
|
|
1175
1190
|
}
|
|
1176
|
-
else if (input === '/' && !key.ctrl && !key.meta) {
|
|
1191
|
+
else if (input === '/' && !key.ctrl && !key.meta && !key.super) {
|
|
1177
1192
|
// `/` in transcript mode (Ctrl+O expanded, CC's REPL semantics:
|
|
1178
1193
|
// search is active on the transcript screen where `/` isn't a command).
|
|
1179
1194
|
if (expanded) {
|
|
@@ -1205,11 +1220,11 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
1205
1220
|
requestExit();
|
|
1206
1221
|
}
|
|
1207
1222
|
}
|
|
1208
|
-
else if (key
|
|
1223
|
+
else if (isMod(key) && input === 'l') {
|
|
1209
1224
|
// CC's app:redraw — clear the physical terminal and repaint.
|
|
1210
1225
|
instances.get(process.stdout)?.forceRedraw();
|
|
1211
1226
|
}
|
|
1212
|
-
else if (key
|
|
1227
|
+
else if (isMod(key) && input === 'e') {
|
|
1213
1228
|
setShowAllMessages(previous => !previous);
|
|
1214
1229
|
}
|
|
1215
1230
|
else if (key.return && showPill) {
|
|
@@ -1233,7 +1248,7 @@ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
|
1233
1248
|
(channel.activityEnabled &&
|
|
1234
1249
|
channel.workingActivity !== undefined &&
|
|
1235
1250
|
channel.workingActivity.line !== '' &&
|
|
1236
|
-
channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95, suffix: ` · ↓ ${channel.responseChars} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), resumePickerOpen && resumeSessions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ResumePicker, { sessions: resumeSessions, focusIndex: resumeIndex, currentSessionId: channel.agentId }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent }), _jsx(GoalTodoPanel, { channel: channel }), questionSnapshot !== null
|
|
1251
|
+
channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95, suffix: ` · ↓ ${channel.responseChars} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), resumePickerOpen && resumeSessions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ResumePicker, { sessions: resumeSessions, focusIndex: resumeIndex, currentSessionId: channel.agentId }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent }), _jsx(GoalTodoPanel, { channel: channel }), approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent() }, questionSnapshot.key)) : (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen })] })] }));
|
|
1237
1252
|
}
|
|
1238
1253
|
/**
|
|
1239
1254
|
* The pinned prompt header shown above the ScrollBox while the user has
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform modifier-key helpers. Windows/Linux muscle memory uses
|
|
3
|
+
* Ctrl+<key>; on macOS the same action maps to Cmd+<key>. Terminals deliver
|
|
4
|
+
* Cmd as the `super` flag (kitty CSI-u / xterm modifyOtherKeys — see
|
|
5
|
+
* src/ink/parse-keypress.ts); Ctrl keeps working everywhere, so `isMod`
|
|
6
|
+
* accepts either on the mac and stays Ctrl-only elsewhere.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isMac: boolean;
|
|
9
|
+
/** True when the key event carries the platform's "primary" modifier. */
|
|
10
|
+
export declare function isMod(key: {
|
|
11
|
+
ctrl?: boolean;
|
|
12
|
+
super?: boolean;
|
|
13
|
+
}): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Display prefix for shortcut labels: "⌘" on macOS (Apple style, no "+"),
|
|
16
|
+
* "ctrl+" everywhere else. Pair with the bare key, e.g. `${modLabel}o`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const modLabel: string;
|
|
19
|
+
//# sourceMappingURL=modifiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modifiers.d.ts","sourceRoot":"","sources":["../../../src/utils/modifiers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,KAAK,SAAgC,CAAA;AAElD,yEAAyE;AACzE,wBAAgB,KAAK,CAAC,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAEvE;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,QAAwB,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform modifier-key helpers. Windows/Linux muscle memory uses
|
|
3
|
+
* Ctrl+<key>; on macOS the same action maps to Cmd+<key>. Terminals deliver
|
|
4
|
+
* Cmd as the `super` flag (kitty CSI-u / xterm modifyOtherKeys — see
|
|
5
|
+
* src/ink/parse-keypress.ts); Ctrl keeps working everywhere, so `isMod`
|
|
6
|
+
* accepts either on the mac and stays Ctrl-only elsewhere.
|
|
7
|
+
*/
|
|
8
|
+
export const isMac = process.platform === 'darwin';
|
|
9
|
+
/** True when the key event carries the platform's "primary" modifier. */
|
|
10
|
+
export function isMod(key) {
|
|
11
|
+
return !!key.ctrl || (isMac && !!key.super);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Display prefix for shortcut labels: "⌘" on macOS (Apple style, no "+"),
|
|
15
|
+
* "ctrl+" everywhere else. Pair with the bare key, e.g. `${modLabel}o`.
|
|
16
|
+
*/
|
|
17
|
+
export const modLabel = isMac ? '⌘' : 'ctrl+';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-harness-tui/dsh-tui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Claude Code style interactive TUI front door for DeepSeek Harness agents, built on the ported Ink core.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"@deepseek-ai/dsh-tool-ask-user": "^0.1.0-rc.6",
|
|
70
70
|
"@deepseek-ai/dsh-tool-bash-persistent": "^0.1.0-rc.6",
|
|
71
71
|
"@deepseek-ai/dsh-tool-cordis": "^0.1.0-rc.6",
|
|
72
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.6",
|
|
72
73
|
"@deepseek-ai/dsh-user-questions": "^0.1.0-rc.6",
|
|
73
74
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
74
75
|
"auto-bind": "^4.0.0 || ^5.0.0",
|