@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/README.md
CHANGED
|
@@ -95,6 +95,12 @@ TUI 启动后会在后台检查 npm 是否有新版本;发现更新时会提
|
|
|
95
95
|
| `?` | 快捷键菜单 |
|
|
96
96
|
| `Shift+↑` | 消息选择模式(Enter 展开单条) |
|
|
97
97
|
|
|
98
|
+
**macOS 修饰键**:上表中 Windows/Linux 的 `Ctrl+<键>` 在 macOS 上同时可用 `⌘<键>`
|
|
99
|
+
(如 `⌘V` 粘贴、`⌘O` 展开详情、`⌘Enter` 立即发送);仅 `Ctrl+C` / `Ctrl+D`
|
|
100
|
+
(中断/退出)保持 Ctrl 不变,避免与 macOS 系统级 `⌘C` 复制等肌肉记忆冲突。
|
|
101
|
+
`⌘` 需终端支持扩展键盘协议(iTerm2 / kitty / WezTerm / ghostty / tmux);
|
|
102
|
+
macOS 自带 Terminal.app 会自行消费 `⌘` 快捷键,请继续使用 `Ctrl`。
|
|
103
|
+
|
|
98
104
|
**鼠标(`fullscreen: true` 全屏模式;默认关,profile 补丁层覆盖开启)**
|
|
99
105
|
|
|
100
106
|
| 操作 | 功能 |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval store — the UI-side answerer half of the DSH approval seam
|
|
3
|
+
* (`ctx.approval`). The harness's permission layer asks
|
|
4
|
+
* `ApprovalService.request()`, which dispatches an `approval/request`
|
|
5
|
+
* waterfall; the listener registered in plugin.ts parks the request here,
|
|
6
|
+
* surfaces one ask at a time to the TUI (Claude Code style permission
|
|
7
|
+
* prompt), and settles the harness promise when the user decides, the
|
|
8
|
+
* asker's abort signal fires, or the plugin tears down.
|
|
9
|
+
*
|
|
10
|
+
* Queue semantics mirror QuestionStore: parallel tool calls can trigger
|
|
11
|
+
* several asks before any answer is given, so asks are drained FIFO.
|
|
12
|
+
* Outcomes are the protocol's closed set — `'allowed-once'` and
|
|
13
|
+
* `'rejected'` from the panel, `'cancelled'` on abort/teardown; there is
|
|
14
|
+
* no allow-always or feedback channel in the protocol.
|
|
15
|
+
*/
|
|
16
|
+
import type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval';
|
|
17
|
+
/** What the TUI renders while an approval is pending. */
|
|
18
|
+
export interface ApprovalSnapshot {
|
|
19
|
+
/** Stable key so the panel remounts (fresh focus state) per request. */
|
|
20
|
+
readonly key: string;
|
|
21
|
+
readonly toolName: string;
|
|
22
|
+
/** The asker's human-readable explanation, when given. */
|
|
23
|
+
readonly reason?: string;
|
|
24
|
+
/** The gated command, recovered from the paired tool/call event. */
|
|
25
|
+
readonly command?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Approval store: parks asks from the harness's approval seam, surfaces
|
|
29
|
+
* one at a time to the TUI, and settles each ask when the user decides or
|
|
30
|
+
* the ask is withdrawn. The TUI subscribes for re-renders and decides via
|
|
31
|
+
* {@link ApprovalStore.decide}.
|
|
32
|
+
*/
|
|
33
|
+
export declare class ApprovalStore {
|
|
34
|
+
private readonly queue;
|
|
35
|
+
private active;
|
|
36
|
+
private readonly listeners;
|
|
37
|
+
private seq;
|
|
38
|
+
/**
|
|
39
|
+
* Cached snapshot: useSyncExternalStore requires a stable reference while
|
|
40
|
+
* nothing changed (a fresh object per call would loop re-renders).
|
|
41
|
+
*/
|
|
42
|
+
private snapshotCache;
|
|
43
|
+
/**
|
|
44
|
+
* Subscribe to store changes (useSyncExternalStore contract).
|
|
45
|
+
* @param listener - Called after every mutation that changes the snapshot.
|
|
46
|
+
* @returns An unsubscribe function removing the listener.
|
|
47
|
+
*/
|
|
48
|
+
subscribe(listener: () => void): () => void;
|
|
49
|
+
/**
|
|
50
|
+
* The approval the TUI should render now, or null when idle.
|
|
51
|
+
* @returns The cached snapshot; the reference is stable between mutations.
|
|
52
|
+
*/
|
|
53
|
+
getSnapshot(): ApprovalSnapshot | null;
|
|
54
|
+
private emit;
|
|
55
|
+
/** Rebuild the cached snapshot after any mutation of active. */
|
|
56
|
+
private rebuildSnapshot;
|
|
57
|
+
/**
|
|
58
|
+
* Answerer entry point — called by the `approval/request` waterfall
|
|
59
|
+
* listener when the ask concerns the agent this TUI owns.
|
|
60
|
+
* @param req - The approval request (agent, tool, callId, reason, signal).
|
|
61
|
+
* @returns A promise settling with the user's decision, or `'cancelled'`
|
|
62
|
+
* when the ask is withdrawn or the plugin tears down.
|
|
63
|
+
*/
|
|
64
|
+
park(req: ApprovalRequest): Promise<ApprovalOutcome>;
|
|
65
|
+
/** Advance to the next queued ask, if any. */
|
|
66
|
+
private startNext;
|
|
67
|
+
/**
|
|
68
|
+
* The user decided on the current approval; settles it and drains the
|
|
69
|
+
* next queued ask if any. No-op when nothing is pending.
|
|
70
|
+
* @param outcome - `'allowed-once'` or `'rejected'`.
|
|
71
|
+
*/
|
|
72
|
+
decide(outcome: 'allowed-once' | 'rejected'): void;
|
|
73
|
+
/**
|
|
74
|
+
* Settle the active and all queued asks (plugin teardown). The panel
|
|
75
|
+
* unmounts as the snapshot clears.
|
|
76
|
+
* @param outcome - The outcome every pending ask resolves with.
|
|
77
|
+
*/
|
|
78
|
+
settleAll(outcome: ApprovalOutcome): void;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=approvals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../src/approvals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAGtF,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AA0CD;;;;;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,GAAG,CAAI;IACf;;;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,OAAO,CAAC,IAAI;IAIZ,gEAAgE;IAChE,OAAO,CAAC,eAAe;IAOvB;;;;;;OAMG;IACH,IAAI,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IA8BpD,8CAA8C;IAC9C,OAAO,CAAC,SAAS;IAOjB;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,GAAG,IAAI;IAUlD;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;CAQ1C"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval store — the UI-side answerer half of the DSH approval seam
|
|
3
|
+
* (`ctx.approval`). The harness's permission layer asks
|
|
4
|
+
* `ApprovalService.request()`, which dispatches an `approval/request`
|
|
5
|
+
* waterfall; the listener registered in plugin.ts parks the request here,
|
|
6
|
+
* surfaces one ask at a time to the TUI (Claude Code style permission
|
|
7
|
+
* prompt), and settles the harness promise when the user decides, the
|
|
8
|
+
* asker's abort signal fires, or the plugin tears down.
|
|
9
|
+
*
|
|
10
|
+
* Queue semantics mirror QuestionStore: parallel tool calls can trigger
|
|
11
|
+
* several asks before any answer is given, so asks are drained FIFO.
|
|
12
|
+
* Outcomes are the protocol's closed set — `'allowed-once'` and
|
|
13
|
+
* `'rejected'` from the panel, `'cancelled'` on abort/teardown; there is
|
|
14
|
+
* no allow-always or feedback channel in the protocol.
|
|
15
|
+
*/
|
|
16
|
+
const COMMAND_CLIP = 500;
|
|
17
|
+
/**
|
|
18
|
+
* Recover the gated command from the session log: the approval request
|
|
19
|
+
* links to an already-presented tool call via `callId`, so the arguments
|
|
20
|
+
* are not duplicated on the request. Mirrors the web client's `commandOf`.
|
|
21
|
+
* @param req - The pending approval request.
|
|
22
|
+
* @returns The `command` argument when present, else the raw arguments
|
|
23
|
+
* string (clipped), else undefined when the call cannot be found.
|
|
24
|
+
*/
|
|
25
|
+
function commandOf(req) {
|
|
26
|
+
if (req.callId === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
const events = req.agent.session.events;
|
|
29
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
30
|
+
const event = events[i];
|
|
31
|
+
if (event.type !== 'tool/call')
|
|
32
|
+
continue;
|
|
33
|
+
if (String(event.data.callId) !== String(req.callId))
|
|
34
|
+
continue;
|
|
35
|
+
const raw = event.data.arguments;
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
if (parsed !== null && typeof parsed === 'object' && 'command' in parsed) {
|
|
39
|
+
const command = parsed.command;
|
|
40
|
+
if (typeof command === 'string')
|
|
41
|
+
return command;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Not JSON — fall through to the raw string.
|
|
46
|
+
}
|
|
47
|
+
return raw.length <= COMMAND_CLIP ? raw : `${raw.slice(0, COMMAND_CLIP)}…`;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Approval store: parks asks from the harness's approval seam, surfaces
|
|
53
|
+
* one at a time to the TUI, and settles each ask when the user decides or
|
|
54
|
+
* the ask is withdrawn. The TUI subscribes for re-renders and decides via
|
|
55
|
+
* {@link ApprovalStore.decide}.
|
|
56
|
+
*/
|
|
57
|
+
export class ApprovalStore {
|
|
58
|
+
queue = [];
|
|
59
|
+
active;
|
|
60
|
+
listeners = new Set();
|
|
61
|
+
seq = 0;
|
|
62
|
+
/**
|
|
63
|
+
* Cached snapshot: useSyncExternalStore requires a stable reference while
|
|
64
|
+
* nothing changed (a fresh object per call would loop re-renders).
|
|
65
|
+
*/
|
|
66
|
+
snapshotCache = null;
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe to store changes (useSyncExternalStore contract).
|
|
69
|
+
* @param listener - Called after every mutation that changes the snapshot.
|
|
70
|
+
* @returns An unsubscribe function removing the listener.
|
|
71
|
+
*/
|
|
72
|
+
subscribe(listener) {
|
|
73
|
+
this.listeners.add(listener);
|
|
74
|
+
return () => {
|
|
75
|
+
this.listeners.delete(listener);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The approval the TUI should render now, or null when idle.
|
|
80
|
+
* @returns The cached snapshot; the reference is stable between mutations.
|
|
81
|
+
*/
|
|
82
|
+
getSnapshot() {
|
|
83
|
+
return this.snapshotCache;
|
|
84
|
+
}
|
|
85
|
+
emit() {
|
|
86
|
+
for (const listener of this.listeners)
|
|
87
|
+
listener();
|
|
88
|
+
}
|
|
89
|
+
/** Rebuild the cached snapshot after any mutation of active. */
|
|
90
|
+
rebuildSnapshot() {
|
|
91
|
+
const pending = this.active;
|
|
92
|
+
this.snapshotCache = pending === undefined
|
|
93
|
+
? null
|
|
94
|
+
: { key: pending.key, ...pending.snapshot };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Answerer entry point — called by the `approval/request` waterfall
|
|
98
|
+
* listener when the ask concerns the agent this TUI owns.
|
|
99
|
+
* @param req - The approval request (agent, tool, callId, reason, signal).
|
|
100
|
+
* @returns A promise settling with the user's decision, or `'cancelled'`
|
|
101
|
+
* when the ask is withdrawn or the plugin tears down.
|
|
102
|
+
*/
|
|
103
|
+
park(req) {
|
|
104
|
+
return new Promise(resolve => {
|
|
105
|
+
const command = commandOf(req);
|
|
106
|
+
const pending = {
|
|
107
|
+
key: String(++this.seq),
|
|
108
|
+
snapshot: {
|
|
109
|
+
toolName: req.toolName,
|
|
110
|
+
...(req.reason !== undefined ? { reason: req.reason } : {}),
|
|
111
|
+
...(command !== undefined ? { command } : {}),
|
|
112
|
+
},
|
|
113
|
+
resolve,
|
|
114
|
+
onAbort: () => {
|
|
115
|
+
if (this.active === pending) {
|
|
116
|
+
this.active = undefined;
|
|
117
|
+
this.rebuildSnapshot();
|
|
118
|
+
pending.resolve('cancelled');
|
|
119
|
+
this.startNext();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const at = this.queue.indexOf(pending);
|
|
123
|
+
if (at >= 0)
|
|
124
|
+
this.queue.splice(at, 1);
|
|
125
|
+
pending.resolve('cancelled');
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
req.signal?.addEventListener('abort', pending.onAbort, { once: true });
|
|
129
|
+
this.queue.push(pending);
|
|
130
|
+
this.startNext();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** Advance to the next queued ask, if any. */
|
|
134
|
+
startNext() {
|
|
135
|
+
if (this.active !== undefined || this.queue.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
this.active = this.queue.shift();
|
|
138
|
+
this.rebuildSnapshot();
|
|
139
|
+
this.emit();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The user decided on the current approval; settles it and drains the
|
|
143
|
+
* next queued ask if any. No-op when nothing is pending.
|
|
144
|
+
* @param outcome - `'allowed-once'` or `'rejected'`.
|
|
145
|
+
*/
|
|
146
|
+
decide(outcome) {
|
|
147
|
+
const pending = this.active;
|
|
148
|
+
if (pending === undefined)
|
|
149
|
+
return;
|
|
150
|
+
this.active = undefined;
|
|
151
|
+
this.rebuildSnapshot();
|
|
152
|
+
pending.resolve(outcome);
|
|
153
|
+
this.startNext();
|
|
154
|
+
this.emit();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Settle the active and all queued asks (plugin teardown). The panel
|
|
158
|
+
* unmounts as the snapshot clears.
|
|
159
|
+
* @param outcome - The outcome every pending ask resolves with.
|
|
160
|
+
*/
|
|
161
|
+
settleAll(outcome) {
|
|
162
|
+
const active = this.active;
|
|
163
|
+
this.active = undefined;
|
|
164
|
+
this.rebuildSnapshot();
|
|
165
|
+
active?.resolve(outcome);
|
|
166
|
+
for (const pending of this.queue.splice(0))
|
|
167
|
+
pending.resolve(outcome);
|
|
168
|
+
this.emit();
|
|
169
|
+
}
|
|
170
|
+
}
|
package/lib/types/commands.d.ts
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
export interface LocalCommand {
|
|
10
10
|
/** The command name without the slash, e.g. `clear`. */
|
|
11
11
|
name: string;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* One-line description shown in the suggestion overlay — the English text
|
|
14
|
+
* and the fallback for languages without a `cmd-desc-<name>` dict entry
|
|
15
|
+
* (see {@link localizedDescription}).
|
|
16
|
+
*/
|
|
13
17
|
description: string;
|
|
14
18
|
/** Optional bracket tag shown between name and description. */
|
|
15
19
|
tag?: string;
|
|
@@ -21,6 +25,15 @@ export interface LocalCommand {
|
|
|
21
25
|
* commands merge in at runtime; locals win on name collisions.
|
|
22
26
|
*/
|
|
23
27
|
export declare const LOCAL_COMMANDS: LocalCommand[];
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a command's description in the active UI language. The en text in
|
|
30
|
+
* `LOCAL_COMMANDS` (and the registry's own text for external commands) is
|
|
31
|
+
* the fallback; zh translations live in the i18n dict under
|
|
32
|
+
* `cmd-desc-<name>`. Resolved at call time — components call this during
|
|
33
|
+
* render, so a `/lang` switch repaints descriptions immediately.
|
|
34
|
+
* @param command - The command whose description to localize.
|
|
35
|
+
*/
|
|
36
|
+
export declare function localizedDescription(command: LocalCommand): string;
|
|
24
37
|
/**
|
|
25
38
|
* Parse a slash-command line into its name and the verbatim input following
|
|
26
39
|
* the name (separator whitespace included) — the same split the DSH command
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,EA+CxC,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAElE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GACX;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,YAAY,EAAE,CAKhB"}
|
package/lib/types/commands.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `runCommand` in the Chat screen dispatches either kind, with the registry
|
|
7
7
|
* handler winning for names both sides declare.
|
|
8
8
|
*/
|
|
9
|
+
import { tOr } from './i18n.js';
|
|
9
10
|
/**
|
|
10
11
|
* The built-in slash commands (name + description pairs). Plugin-registered
|
|
11
12
|
* commands merge in at runtime; locals win on name collisions.
|
|
@@ -58,6 +59,17 @@ export const LOCAL_COMMANDS = [
|
|
|
58
59
|
{ name: 'help', description: 'Show shortcuts and commands' },
|
|
59
60
|
{ name: 'exit', description: 'Exit dsh-tui' },
|
|
60
61
|
];
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a command's description in the active UI language. The en text in
|
|
64
|
+
* `LOCAL_COMMANDS` (and the registry's own text for external commands) is
|
|
65
|
+
* the fallback; zh translations live in the i18n dict under
|
|
66
|
+
* `cmd-desc-<name>`. Resolved at call time — components call this during
|
|
67
|
+
* render, so a `/lang` switch repaints descriptions immediately.
|
|
68
|
+
* @param command - The command whose description to localize.
|
|
69
|
+
*/
|
|
70
|
+
export function localizedDescription(command) {
|
|
71
|
+
return tOr(`cmd-desc-${command.name}`, command.description);
|
|
72
|
+
}
|
|
61
73
|
/**
|
|
62
74
|
* Parse a slash-command line into its name and the verbatim input following
|
|
63
75
|
* the name (separator whitespace included) — the same split the DSH command
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionLog.d.ts","sourceRoot":"","sources":["../../../src/compat/sessionLog.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessionLog.d.ts","sourceRoot":"","sources":["../../../src/compat/sessionLog.ts"],"names":[],"mappings":"AAiCA,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,OAAO,GACP,aAAa,CAAA;AA4EjB;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAsChF;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9E"}
|
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
* marked, and already-known events are never touched.
|
|
17
17
|
*
|
|
18
18
|
* Storage notes: the jsonl persistence flushes by APPENDING zstd frames, so
|
|
19
|
-
* the file is a concatenation of frames
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* the file is a concatenation of frames. Frame layout is load-bearing: the
|
|
20
|
+
* backend asserts frame 0 holds EXACTLY the header line (listings read only
|
|
21
|
+
* that frame; `assertZstdHeaderFrame`), so the repair re-encodes each frame
|
|
22
|
+
* with its original line set — frame boundaries are preserved 1:1, and any
|
|
23
|
+
* frame whose lines were untouched is copied verbatim. Any decode/parse
|
|
24
|
+
* anomaly aborts the repair and resume proceeds unpatched — the failure
|
|
25
|
+
* mode degrades to the pre-patch behavior.
|
|
23
26
|
* @module @deepseek-harness-tui/dsh-tui/compat/sessionLog
|
|
24
27
|
*/
|
|
25
28
|
import { KNOWN_SESSION_EVENT_TYPES } from '@deepseek-ai/dsh-session';
|
|
@@ -61,11 +64,12 @@ function findSessionLogFile(sessionId) {
|
|
|
61
64
|
return undefined;
|
|
62
65
|
}
|
|
63
66
|
/**
|
|
64
|
-
* Decode a (possibly multi-frame) zstd jsonl log
|
|
65
|
-
* scan; any frame failing to decode or any line
|
|
66
|
-
* callers abort instead of rewriting a log they
|
|
67
|
+
* Decode a (possibly multi-frame) zstd jsonl log, keeping frames separate.
|
|
68
|
+
* Frames are split by magic scan; any frame failing to decode or any line
|
|
69
|
+
* failing to parse throws, so callers abort instead of rewriting a log they
|
|
70
|
+
* did not fully understand.
|
|
67
71
|
* @param buf - Raw file bytes.
|
|
68
|
-
* @returns
|
|
72
|
+
* @returns Per-frame byte spans and parsed event envelopes, in log order.
|
|
69
73
|
*/
|
|
70
74
|
function decodeFrames(buf) {
|
|
71
75
|
const offsets = [];
|
|
@@ -75,20 +79,21 @@ function decodeFrames(buf) {
|
|
|
75
79
|
}
|
|
76
80
|
if (offsets.length === 0)
|
|
77
81
|
throw new Error('no zstd frame found');
|
|
78
|
-
|
|
79
|
-
for (let i = 0; i < offsets.length; i++) {
|
|
82
|
+
return offsets.map((start, i) => {
|
|
80
83
|
const end = i + 1 < offsets.length ? offsets[i + 1] : buf.length;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
const raw = buf.subarray(start, end);
|
|
85
|
+
const text = zstdDecompressSync(raw).toString('utf8');
|
|
86
|
+
const events = text
|
|
87
|
+
.split('\n')
|
|
88
|
+
.filter((line) => line.length > 0)
|
|
89
|
+
.map((line) => {
|
|
90
|
+
const parsed = JSON.parse(line);
|
|
91
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
92
|
+
throw new Error('session log line is not an event envelope');
|
|
93
|
+
}
|
|
94
|
+
return parsed;
|
|
95
|
+
});
|
|
96
|
+
return { raw, events };
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
99
|
/**
|
|
@@ -103,25 +108,35 @@ export function repairSessionLogForResume(sessionId) {
|
|
|
103
108
|
const file = findSessionLogFile(sessionId);
|
|
104
109
|
if (file === undefined)
|
|
105
110
|
return 'unavailable';
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
const frames = decodeFrames(readFileSync(file));
|
|
112
|
+
// Mark unknown types in place, tracking which frames actually changed:
|
|
113
|
+
// untouched frames are copied back verbatim, so the header frame keeps
|
|
114
|
+
// its exact original bytes (and the one-header-line invariant with it).
|
|
115
|
+
const dirty = new Set();
|
|
116
|
+
frames.forEach((frame, index) => {
|
|
117
|
+
for (const event of frame.events) {
|
|
118
|
+
const type = event['type'];
|
|
119
|
+
// Only real log entries carry a numeric seq — the seq-less header row
|
|
120
|
+
// is parsed by a separate path that must not see an extra field.
|
|
121
|
+
if (typeof type === 'string' &&
|
|
122
|
+
typeof event['seq'] === 'number' &&
|
|
123
|
+
!KNOWN_SESSION_EVENT_TYPES.has(type) &&
|
|
124
|
+
event['ignorable'] === undefined) {
|
|
125
|
+
event['ignorable'] = true;
|
|
126
|
+
dirty.add(index);
|
|
127
|
+
}
|
|
118
128
|
}
|
|
119
|
-
}
|
|
120
|
-
if (
|
|
129
|
+
});
|
|
130
|
+
if (dirty.size === 0)
|
|
121
131
|
return 'clean';
|
|
122
|
-
const
|
|
132
|
+
const parts = frames.map((frame, index) => {
|
|
133
|
+
if (!dirty.has(index))
|
|
134
|
+
return frame.raw;
|
|
135
|
+
const payload = frame.events.map((event) => JSON.stringify(event)).join('\n') + '\n';
|
|
136
|
+
return zstdCompressSync(Buffer.from(payload, 'utf8'));
|
|
137
|
+
});
|
|
123
138
|
const tmp = `${file}.compat-${randomUUID()}.tmp`;
|
|
124
|
-
writeFileSync(tmp,
|
|
139
|
+
writeFileSync(tmp, Buffer.concat(parts));
|
|
125
140
|
renameSync(tmp, file);
|
|
126
141
|
return 'repaired';
|
|
127
142
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandSuggestions.d.ts","sourceRoot":"","sources":["../../../src/components/CommandSuggestions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"CommandSuggestions.d.ts","sourceRoot":"","sources":["../../../src/components/CommandSuggestions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAGlD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,aAAa,EACb,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAA;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,KAAK,CAAC,SAAS,CAqDlB"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from '../ui.js';
|
|
3
3
|
import { stringWidth } from '../ink/stringWidth.js';
|
|
4
4
|
import { truncateToWidth } from '../ink/truncateToWidth.js';
|
|
5
|
+
import { localizedDescription } from '../commands.js';
|
|
5
6
|
/**
|
|
6
7
|
* The slash-command suggestion overlay, ported from the leak's
|
|
7
8
|
* `PromptInputFooterSuggestions.tsx` (command layout only): a name column
|
|
@@ -25,9 +26,10 @@ export function CommandSuggestions({ commands, selectedIndex, columns, }) {
|
|
|
25
26
|
const tagText = command.tag ? `[${command.tag}] ` : '';
|
|
26
27
|
const tagWidth = stringWidth(tagText);
|
|
27
28
|
const descriptionWidth = Math.max(0, columns - nameWidth - tagWidth - 4);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const rawDescription = localizedDescription(command);
|
|
30
|
+
const description = stringWidth(rawDescription) > descriptionWidth
|
|
31
|
+
? truncateToWidth(rawDescription, descriptionWidth - 1) + '…'
|
|
32
|
+
: rawDescription;
|
|
31
33
|
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: isSelected ? 'suggestion' : undefined, dimColor: !isSelected, children: padded }), tagText ? _jsx(Text, { dimColor: true, children: tagText }) : null, _jsx(Text, { color: isSelected ? 'suggestion' : undefined, dimColor: !isSelected, children: description })] }, command.name));
|
|
32
34
|
}) }));
|
|
33
35
|
}
|
|
@@ -5,6 +5,7 @@ import type { LocalCommand } from '../commands.js';
|
|
|
5
5
|
* (three-column shortcut layout, trimmed to the keys dsh-tui actually binds).
|
|
6
6
|
* The command column lists the merged slash-command surface: built-in
|
|
7
7
|
* commands plus plugin-registered ones from the DSH registry (plan/goal/…).
|
|
8
|
+
* Modifier labels follow the platform convention: ⌘ on macOS, ctrl elsewhere.
|
|
8
9
|
*/
|
|
9
10
|
export declare function HelpMenu({ commands, }: {
|
|
10
11
|
commands: readonly LocalCommand[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HelpMenu.d.ts","sourceRoot":"","sources":["../../../src/components/HelpMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"HelpMenu.d.ts","sourceRoot":"","sources":["../../../src/components/HelpMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAIlD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAA;CAClC,GAAG,KAAK,CAAC,SAAS,CA6DlB"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import Box from '../ink/components/Box.js';
|
|
3
3
|
import Text from '../ink/components/Text.js';
|
|
4
|
+
import { localizedDescription } from '../commands.js';
|
|
5
|
+
import { modLabel } from '../utils/modifiers.js';
|
|
4
6
|
/**
|
|
5
7
|
* The `?` help menu, ported from the leak's `PromptInputHelpMenu.tsx`
|
|
6
8
|
* (three-column shortcut layout, trimmed to the keys dsh-tui actually binds).
|
|
7
9
|
* The command column lists the merged slash-command surface: built-in
|
|
8
10
|
* commands plus plugin-registered ones from the DSH registry (plan/goal/…).
|
|
11
|
+
* Modifier labels follow the platform convention: ⌘ on macOS, ctrl elsewhere.
|
|
9
12
|
*/
|
|
10
13
|
export function HelpMenu({ commands, }) {
|
|
11
|
-
return (_jsxs(Box, { paddingX: 2, flexDirection: "row", gap: 4, children: [_jsxs(Box, { flexDirection: "column", width: 26, flexShrink: 0, children: [_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "/ for commands" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "? for this help" }) }), _jsx(Box, { children:
|
|
14
|
+
return (_jsxs(Box, { paddingX: 2, flexDirection: "row", gap: 4, children: [_jsxs(Box, { flexDirection: "column", width: 26, flexShrink: 0, children: [_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "/ for commands" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "? for this help" }) }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [modLabel, "o for verbose output"] }) }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [modLabel, "t to toggle context"] }) }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [modLabel, "r to search history"] }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "ctrl+c to interrupt" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "ctrl+d to exit" }) }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [modLabel, "l to redraw"] }) })] }), _jsxs(Box, { flexDirection: "column", width: 24, flexShrink: 0, children: [_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "esc to clear input" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 for history" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "\u2190/\u2192 to move cursor" }) }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [modLabel, "\u2190/\u2192 for word jumps"] }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "tab to complete command" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "shift+tab to cycle effort" }) })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [_jsx(Text, { dimColor: true, children: "commands:" }), commands.map(command => (_jsx(Box, { children: _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["/", command.name, " \u2014 ", localizedDescription(command)] }) }, command.name)))] })] }));
|
|
12
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAyC5C;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACrD,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,aAAa,GACd,EAAE,gBAAgB,qBA+xBlB"}
|
|
@@ -8,6 +8,7 @@ import { formatClipboardInsert, readClipboard } from '../utils/clipboard.js';
|
|
|
8
8
|
import { filterCommands, parseCommandName } from '../commands.js';
|
|
9
9
|
import { appendHistory } from '../history.js';
|
|
10
10
|
import { mentionAtCaret } from '../utils/mentions.js';
|
|
11
|
+
import { isMod } from '../utils/modifiers.js';
|
|
11
12
|
import { CommandSuggestions } from './CommandSuggestions.js';
|
|
12
13
|
import { FileSuggestions } from './FileSuggestions.js';
|
|
13
14
|
import { HelpMenu } from './HelpMenu.js';
|
|
@@ -347,10 +348,10 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
347
348
|
insertAtCaret(input.replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
|
|
348
349
|
return;
|
|
349
350
|
}
|
|
350
|
-
// Ctrl+V: raw mode hands the key to the app, so the clipboard is
|
|
351
|
-
// here — text, or file paths when Explorer copied files (pasted
|
|
352
|
-
// insert their paths).
|
|
353
|
-
if (key
|
|
351
|
+
// Ctrl+V / Cmd+V: raw mode hands the key to the app, so the clipboard is
|
|
352
|
+
// read here — text, or file paths when Explorer copied files (pasted
|
|
353
|
+
// files insert their paths).
|
|
354
|
+
if (isMod(key) && input === 'v') {
|
|
354
355
|
if (clipboardBusyRef.current)
|
|
355
356
|
return;
|
|
356
357
|
clipboardBusyRef.current = true;
|
|
@@ -425,9 +426,9 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
425
426
|
submitText(line);
|
|
426
427
|
return;
|
|
427
428
|
}
|
|
428
|
-
if (key.return && key
|
|
429
|
-
// Ctrl+Enter: interrupt the running turn and process this
|
|
430
|
-
// immediately (Windows Terminal sends CSI 13;5u / 13;1;5u).
|
|
429
|
+
if (key.return && isMod(key)) {
|
|
430
|
+
// Ctrl+Enter / Cmd+Enter: interrupt the running turn and process this
|
|
431
|
+
// message immediately (Windows Terminal sends CSI 13;5u / 13;1;5u).
|
|
431
432
|
interruptSend();
|
|
432
433
|
return;
|
|
433
434
|
}
|
|
@@ -537,12 +538,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
537
538
|
setCursor(previous => Math.min(value.length, previous + 1));
|
|
538
539
|
return;
|
|
539
540
|
}
|
|
540
|
-
if (key
|
|
541
|
+
if (isMod(key) && key.leftArrow) {
|
|
541
542
|
// Jump to the previous word boundary (readline alt+b).
|
|
542
543
|
setCursor(previous => wordBoundaryLeft(value, previous));
|
|
543
544
|
return;
|
|
544
545
|
}
|
|
545
|
-
if (key
|
|
546
|
+
if (isMod(key) && key.rightArrow) {
|
|
546
547
|
// Jump to the next word boundary (readline alt+f).
|
|
547
548
|
setCursor(previous => wordBoundaryRight(value, previous));
|
|
548
549
|
return;
|
|
@@ -572,31 +573,31 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
572
573
|
setCursor(nextLine === -1 ? value.length : nextLine);
|
|
573
574
|
return;
|
|
574
575
|
}
|
|
575
|
-
if (key
|
|
576
|
+
if (isMod(key) && input === 'a') {
|
|
576
577
|
const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
|
|
577
578
|
setCursor(lineStart);
|
|
578
579
|
return;
|
|
579
580
|
}
|
|
580
|
-
if (key
|
|
581
|
+
if (isMod(key) && input === 'e') {
|
|
581
582
|
const nextLine = value.indexOf('\n', cursor);
|
|
582
583
|
setCursor(nextLine === -1 ? value.length : nextLine);
|
|
583
584
|
return;
|
|
584
585
|
}
|
|
585
|
-
if (key
|
|
586
|
+
if (isMod(key) && input === 'u') {
|
|
586
587
|
// Delete to start of line.
|
|
587
588
|
const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
|
|
588
589
|
setValue(value.slice(0, lineStart) + value.slice(cursor));
|
|
589
590
|
setCursor(lineStart);
|
|
590
591
|
return;
|
|
591
592
|
}
|
|
592
|
-
if (key
|
|
593
|
+
if (isMod(key) && input === 'k') {
|
|
593
594
|
// Delete to end of line.
|
|
594
595
|
const nextLine = value.indexOf('\n', cursor);
|
|
595
596
|
const end = nextLine === -1 ? value.length : nextLine;
|
|
596
597
|
setValue(value.slice(0, cursor) + value.slice(end));
|
|
597
598
|
return;
|
|
598
599
|
}
|
|
599
|
-
if (key
|
|
600
|
+
if (isMod(key) && input === 'w') {
|
|
600
601
|
// Delete the word before the cursor (CC/readline behavior): skip
|
|
601
602
|
// trailing whitespace, then the whitespace-delimited word.
|
|
602
603
|
const before = value.slice(0, cursor);
|
|
@@ -678,7 +679,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
678
679
|
onToggleHelp();
|
|
679
680
|
return;
|
|
680
681
|
}
|
|
681
|
-
if (input && !key.ctrl && !key.meta && !key.tab && !key.escape) {
|
|
682
|
+
if (input && !key.ctrl && !key.meta && !key.super && !key.tab && !key.escape) {
|
|
682
683
|
// Typing anything else dismisses the help menu (CC behavior).
|
|
683
684
|
if (helpOpen)
|
|
684
685
|
onToggleHelp();
|