deepseek-cli-mode 1.2.0 → 1.3.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.
- package/CHANGELOG.md +12 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/runner/line-editor.js +113 -5
- package/runner/runner.js +146 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0 — pickers, Shift+Tab permission cycling, permanent mode footer
|
|
4
|
+
|
|
5
|
+
- `/model`, `/reasoning`, `/permissions`, and `/resume` now open an interactive picker (`↑`/`↓`, `Enter`,
|
|
6
|
+
`Ctrl+C` to cancel) instead of requiring an argument. `/model` lists the models discovered from the provider.
|
|
7
|
+
- **Shift+Tab** cycles the permission mode (read-only → workspace-write → danger-full-access) without losing the
|
|
8
|
+
typed line, with a transient hint.
|
|
9
|
+
- A permanent dim footer above the prompt shows the active permission mode, model, and the Shift+Tab hint:
|
|
10
|
+
`⏵⏵ workspace-write · deepseek-official/deepseek-chat · shift+tab to cycle`.
|
|
11
|
+
- Direct arguments still work (`/model <id>`, `/reasoning high`, `/permissions read-only`, `/resume <id>`).
|
|
12
|
+
- Security: picker labels/descriptions and the footer pass through the sanitizer; permission changes still require a
|
|
13
|
+
user keystroke (the model cannot trigger them).
|
|
14
|
+
|
|
3
15
|
## 1.2.0 — live slash-command menu
|
|
4
16
|
|
|
5
17
|
- Typing `/` at the prompt now opens a live command menu (like Claude Code): matching commands and descriptions,
|
package/README.md
CHANGED
|
@@ -144,6 +144,14 @@ Typing `/` opens a live command menu, like Claude Code:
|
|
|
144
144
|
|
|
145
145
|
`↑`/`↓` moves the selection, `Tab` completes it, `Enter` runs it. The menu filters as you type.
|
|
146
146
|
|
|
147
|
+
`/model`, `/reasoning`, `/permissions`, and `/resume` open the same kind of picker when called without an argument.
|
|
148
|
+
**Shift+Tab** cycles the permission mode, and a dim footer above the prompt always shows the active one:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
⏵⏵ workspace-write · deepseek-official/deepseek-chat · shift+tab to cycle
|
|
152
|
+
>
|
|
153
|
+
```
|
|
154
|
+
|
|
147
155
|
| Command | What it does |
|
|
148
156
|
|---|---|
|
|
149
157
|
| `/help` | command list, including the live Harness command registry |
|
package/package.json
CHANGED
package/runner/line-editor.js
CHANGED
|
@@ -28,12 +28,13 @@ export class LineEditor {
|
|
|
28
28
|
* @param {() => void} [options.onInterrupt] called for Ctrl+C while no question is pending
|
|
29
29
|
* @param {() => void} [options.onEnd] called when stdin ends
|
|
30
30
|
*/
|
|
31
|
-
constructor({ input, output, columns, onInterrupt, onEnd }) {
|
|
31
|
+
constructor({ input, output, columns, onInterrupt, onEnd, onShiftTab }) {
|
|
32
32
|
this.input = input;
|
|
33
33
|
this.output = output;
|
|
34
34
|
this.columns = columns ?? (() => this.output.columns || 80);
|
|
35
35
|
this.onInterrupt = onInterrupt ?? (() => {});
|
|
36
36
|
this.onEnd = onEnd ?? (() => {});
|
|
37
|
+
this.onShiftTab = onShiftTab ?? (() => undefined);
|
|
37
38
|
this.commands = [];
|
|
38
39
|
this.buffer = "";
|
|
39
40
|
this.cursor = 0;
|
|
@@ -50,6 +51,10 @@ export class LineEditor {
|
|
|
50
51
|
this.paste = false;
|
|
51
52
|
this.escape = null;
|
|
52
53
|
this.started = false;
|
|
54
|
+
this.selectMode = false;
|
|
55
|
+
this.selectOptions = [];
|
|
56
|
+
this.selectIndex = 0;
|
|
57
|
+
this.hint = "";
|
|
53
58
|
this._onData = (chunk) => this.handleData(chunk);
|
|
54
59
|
this._onEnd = () => {
|
|
55
60
|
this.onEnd();
|
|
@@ -113,10 +118,60 @@ export class LineEditor {
|
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Ask the user to pick one option from a list.
|
|
123
|
+
* @param {string} title prompt line
|
|
124
|
+
* @param {{label: string, description?: string, value: string}[]} options
|
|
125
|
+
* @param {number} [initial]
|
|
126
|
+
* @returns {Promise<string|null>}
|
|
127
|
+
*/
|
|
128
|
+
askSelect(title, options, initial = 0) {
|
|
129
|
+
this.selectMode = true;
|
|
130
|
+
this.selectOptions = Array.isArray(options) ? options : [];
|
|
131
|
+
this.selectIndex = Math.max(0, Math.min(initial, this.selectOptions.length - 1));
|
|
132
|
+
this.prompt = title;
|
|
133
|
+
this.menuEnabled = false;
|
|
134
|
+
this.hint = "";
|
|
135
|
+
this.buffer = "";
|
|
136
|
+
this.cursor = 0;
|
|
137
|
+
this.active = true;
|
|
138
|
+
this.renderedRows = 0;
|
|
139
|
+
this.output.write("\x1b[?25l");
|
|
140
|
+
this.render();
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
this.resolve = resolve;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
chooseSelect() {
|
|
147
|
+
const option = this.selectOptions[this.selectIndex];
|
|
148
|
+
this.selectMode = false;
|
|
149
|
+
this.selectOptions = [];
|
|
150
|
+
this.settle(option === undefined ? null : option.value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
cancelSelect() {
|
|
154
|
+
if (!this.selectMode) return;
|
|
155
|
+
this.selectMode = false;
|
|
156
|
+
this.selectOptions = [];
|
|
157
|
+
this.settle(null);
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
// ---- input --------------------------------------------------------------
|
|
117
161
|
handleData(chunk) {
|
|
118
162
|
for (const char of String(chunk)) {
|
|
119
163
|
if (this.escape !== null) {
|
|
164
|
+
if (this.escape === "\x1b" && char !== "[" && char !== "O") {
|
|
165
|
+
// Lone Esc (not a CSI/SS3 sequence).
|
|
166
|
+
this.escape = null;
|
|
167
|
+
if (this.selectMode) this.cancelSelect();
|
|
168
|
+
else if (this.active) {
|
|
169
|
+
this.hint = "";
|
|
170
|
+
this.menu = [];
|
|
171
|
+
this.render();
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
120
175
|
this.escape += char;
|
|
121
176
|
if (/[A-Za-z~]$/.test(char)) {
|
|
122
177
|
const sequence = this.escape;
|
|
@@ -139,11 +194,17 @@ export class LineEditor {
|
|
|
139
194
|
}
|
|
140
195
|
|
|
141
196
|
handleChar(char) {
|
|
197
|
+
if (this.selectMode) {
|
|
198
|
+
if (char === "\r" || char === "\n") this.chooseSelect();
|
|
199
|
+
else if (char === "\x03" || char === "\x04") this.cancelSelect();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
142
202
|
if (this.paste) {
|
|
143
203
|
if (char === "\x1b") this.escape = "\x1b";
|
|
144
204
|
else this.insert(char === "\n" ? " " : char);
|
|
145
205
|
return;
|
|
146
206
|
}
|
|
207
|
+
this.hint = "";
|
|
147
208
|
switch (char) {
|
|
148
209
|
case "\r":
|
|
149
210
|
case "\n":
|
|
@@ -183,6 +244,20 @@ export class LineEditor {
|
|
|
183
244
|
return;
|
|
184
245
|
}
|
|
185
246
|
if (this.paste) return;
|
|
247
|
+
if (sequence === "\x1b[Z") {
|
|
248
|
+
// Shift+Tab: cycle a mode (permission preset) while keeping the line.
|
|
249
|
+
if (this.selectMode) return;
|
|
250
|
+
this.hint = this.onShiftTab?.() ?? "";
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (this.selectMode) {
|
|
254
|
+
if (sequence === "\x1b[A") {
|
|
255
|
+
this.selectIndex = (this.selectIndex - 1 + this.selectOptions.length) % Math.max(1, this.selectOptions.length);
|
|
256
|
+
} else if (sequence === "\x1b[B") {
|
|
257
|
+
this.selectIndex = (this.selectIndex + 1) % Math.max(1, this.selectOptions.length);
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
186
261
|
switch (sequence) {
|
|
187
262
|
case "\x1b[A":
|
|
188
263
|
this.up();
|
|
@@ -293,8 +368,12 @@ export class LineEditor {
|
|
|
293
368
|
const resolve = this.resolve;
|
|
294
369
|
this.resolve = null;
|
|
295
370
|
this.active = false;
|
|
371
|
+
this.selectMode = false;
|
|
372
|
+
this.selectOptions = [];
|
|
373
|
+
this.hint = "";
|
|
296
374
|
this.clearRender();
|
|
297
375
|
this.menu = [];
|
|
376
|
+
this.output.write("\x1b[?25h");
|
|
298
377
|
if (echo) this.output.write("\n");
|
|
299
378
|
resolve(value);
|
|
300
379
|
}
|
|
@@ -321,6 +400,10 @@ export class LineEditor {
|
|
|
321
400
|
|
|
322
401
|
render() {
|
|
323
402
|
if (!this.active) return;
|
|
403
|
+
if (this.selectMode) {
|
|
404
|
+
this.renderSelect();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
324
407
|
this.clearRender();
|
|
325
408
|
const columns = Math.max(40, this.columns() || 80);
|
|
326
409
|
const prompt = this.prompt;
|
|
@@ -334,10 +417,10 @@ export class LineEditor {
|
|
|
334
417
|
|
|
335
418
|
this.menu = this.computeMenu();
|
|
336
419
|
if (this.menuIndex >= this.menu.length) this.menuIndex = 0;
|
|
337
|
-
|
|
338
|
-
if (
|
|
420
|
+
let rows = 0;
|
|
421
|
+
if (this.menu.length > 0) {
|
|
339
422
|
const nameWidth = Math.min(18, Math.max(...this.menu.map((item) => item.name.length)) + 1);
|
|
340
|
-
for (let index = 0; index <
|
|
423
|
+
for (let index = 0; index < this.menu.length; index += 1) {
|
|
341
424
|
const item = this.menu[index];
|
|
342
425
|
const selected = index === this.menuIndex;
|
|
343
426
|
const marker = selected ? "❯" : " ";
|
|
@@ -346,9 +429,34 @@ export class LineEditor {
|
|
|
346
429
|
const line = ` ${marker} ${selected ? `\x1b[36m${name}\x1b[39m` : name} ${description}`;
|
|
347
430
|
this.output.write("\n" + line);
|
|
348
431
|
}
|
|
349
|
-
this.
|
|
432
|
+
rows = this.menu.length;
|
|
433
|
+
}
|
|
434
|
+
if (this.hint !== "") {
|
|
435
|
+
this.output.write(`\n \x1b[2m${clean(this.hint)}\x1b[22m`);
|
|
436
|
+
rows += 1;
|
|
350
437
|
}
|
|
438
|
+
if (rows > 0) this.output.write(`\x1b[${rows}A`);
|
|
351
439
|
this.renderedRows = rows;
|
|
352
440
|
this.output.write(`\x1b[${column + 1}G`);
|
|
353
441
|
}
|
|
442
|
+
|
|
443
|
+
renderSelect() {
|
|
444
|
+
this.clearRender();
|
|
445
|
+
const columns = Math.max(40, this.columns() || 80);
|
|
446
|
+
this.output.write(this.prompt);
|
|
447
|
+
const options = this.selectOptions;
|
|
448
|
+
const rows = options.length;
|
|
449
|
+
for (let index = 0; index < rows; index += 1) {
|
|
450
|
+
const option = options[index];
|
|
451
|
+
const selected = index === this.selectIndex;
|
|
452
|
+
const marker = selected ? "❯" : " ";
|
|
453
|
+
const label = clean(option.label ?? option.value ?? "");
|
|
454
|
+
const description = option.description ? ` \x1b[2m${clean(option.description)}\x1b[22m` : "";
|
|
455
|
+
const line = ` ${marker} ${selected ? `\x1b[36m${label}\x1b[39m` : label}${description}`;
|
|
456
|
+
this.output.write("\n" + line.slice(0, columns + 16));
|
|
457
|
+
}
|
|
458
|
+
if (rows > 0) this.output.write(`\x1b[${rows}A`);
|
|
459
|
+
this.renderedRows = rows;
|
|
460
|
+
this.output.write(`\x1b[${visibleWidth(this.prompt) + 1}G`);
|
|
461
|
+
}
|
|
354
462
|
}
|
package/runner/runner.js
CHANGED
|
@@ -547,9 +547,9 @@ Interactive commands:
|
|
|
547
547
|
/new start a fresh session
|
|
548
548
|
/sessions list recent sessions in this directory
|
|
549
549
|
/resume <id> switch to another session
|
|
550
|
-
/model [id]
|
|
551
|
-
/reasoning [off|high|max]
|
|
552
|
-
/permissions [p]
|
|
550
|
+
/model [id] pick a model, or pass an id directly
|
|
551
|
+
/reasoning [off|high|max] pick a reasoning effort
|
|
552
|
+
/permissions [p] pick a permission mode (native: /permission; Shift+Tab cycles)
|
|
553
553
|
/skills list the skills discovered in this workspace
|
|
554
554
|
/jobs list this session's background jobs
|
|
555
555
|
/todos show the current todo list
|
|
@@ -564,6 +564,12 @@ Interactive commands:
|
|
|
564
564
|
Harness commands (shared with the Web and Desktop apps):
|
|
565
565
|
/compact, /goal, /plan, and any command a plugin registers.
|
|
566
566
|
Type /help inside a session to see the live list.
|
|
567
|
+
|
|
568
|
+
Keyboard:
|
|
569
|
+
Shift+Tab cycle the permission mode (workspace-write, read-only, …)
|
|
570
|
+
Ctrl+C interrupt a running turn; twice exits
|
|
571
|
+
↑/↓ history (or move in the / command menu)
|
|
572
|
+
Tab complete the highlighted / command
|
|
567
573
|
`);
|
|
568
574
|
}
|
|
569
575
|
|
|
@@ -637,6 +643,7 @@ async function run(ctx, options, exit) {
|
|
|
637
643
|
const permissionPresets = ctx.get("permissionPresets");
|
|
638
644
|
const attachments = ctx.get("attachments");
|
|
639
645
|
const tokenMeter = ctx.get("tokenMeter");
|
|
646
|
+
const llm = ctx.get("llm");
|
|
640
647
|
|
|
641
648
|
if (options.list) {
|
|
642
649
|
printSessionList(listSessions());
|
|
@@ -652,6 +659,7 @@ async function run(ctx, options, exit) {
|
|
|
652
659
|
const autoYes = options.yes || process.env.DSH_CLI_YES === "1";
|
|
653
660
|
|
|
654
661
|
let handle = null;
|
|
662
|
+
let activeAgent = null;
|
|
655
663
|
let busy = false;
|
|
656
664
|
let quitting = false;
|
|
657
665
|
let cancelSent = false;
|
|
@@ -676,6 +684,7 @@ async function run(ctx, options, exit) {
|
|
|
676
684
|
output: process.stdout,
|
|
677
685
|
columns: () => process.stdout.columns || 80,
|
|
678
686
|
onInterrupt: () => handleInterrupt(),
|
|
687
|
+
onShiftTab: () => cyclePermission(),
|
|
679
688
|
onEnd: () => {
|
|
680
689
|
stdinClosed = true;
|
|
681
690
|
if (ready && !busy) shutdown(0);
|
|
@@ -949,6 +958,7 @@ async function run(ctx, options, exit) {
|
|
|
949
958
|
setup,
|
|
950
959
|
});
|
|
951
960
|
handle = created;
|
|
961
|
+
activeAgent = created.agent;
|
|
952
962
|
wire(created.agent);
|
|
953
963
|
await created.agent.whenIdle();
|
|
954
964
|
return created.agent;
|
|
@@ -996,6 +1006,73 @@ async function run(ctx, options, exit) {
|
|
|
996
1006
|
return `${current.provider}/${current.model}${current.reasoningEffort ? ` · ${current.reasoningEffort}` : ""}`;
|
|
997
1007
|
};
|
|
998
1008
|
|
|
1009
|
+
// ---- permission state, pickers, status bar ------------------------------
|
|
1010
|
+
function currentPermission() {
|
|
1011
|
+
try {
|
|
1012
|
+
if (permissionPresets !== undefined && activeAgent !== null) return permissionPresets.current(activeAgent.session);
|
|
1013
|
+
} catch {}
|
|
1014
|
+
return "workspace-write";
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** Shift+Tab: cycle the sandbox/approval preset and return a hint label. */
|
|
1018
|
+
function cyclePermission() {
|
|
1019
|
+
try {
|
|
1020
|
+
if (permissionPresets === undefined || activeAgent === null) return "";
|
|
1021
|
+
const options = permissionPresets.catalog().options.map((option) => option.value);
|
|
1022
|
+
if (options.length === 0) return "";
|
|
1023
|
+
const current = currentPermission();
|
|
1024
|
+
const next = options[(Math.max(0, options.indexOf(current)) + 1) % options.length];
|
|
1025
|
+
permissionPresets.set(activeAgent.session, next);
|
|
1026
|
+
return `⏵⏵ ${next} (shift+tab to cycle)`;
|
|
1027
|
+
} catch {
|
|
1028
|
+
return "";
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/** Permanent one-line footer above the prompt. */
|
|
1033
|
+
function printStatusBar() {
|
|
1034
|
+
if (!tty || !interactive) return;
|
|
1035
|
+
out(paint(ansi.dim, ` ⏵⏵ ${sanitize(currentPermission())} · ${sanitize(modelDesc())} · shift+tab to cycle`) + "\n");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/** One list picker shared by /model, /reasoning, /permissions, /resume. */
|
|
1039
|
+
async function selectOption(title, options, initial = 0) {
|
|
1040
|
+
if (options.length === 0) return null;
|
|
1041
|
+
if (editor) {
|
|
1042
|
+
printStatusBar();
|
|
1043
|
+
return editor.askSelect(paint(ansi.bold, ` ${title}`), options, initial);
|
|
1044
|
+
}
|
|
1045
|
+
out(`\n ${sanitize(title)}\n`);
|
|
1046
|
+
options.forEach((option, index) => {
|
|
1047
|
+
out(` ${index + 1}) ${sanitize(option.label)}${option.description ? paint(ansi.dim, ` — ${sanitize(option.description)}`) : ""}\n`);
|
|
1048
|
+
});
|
|
1049
|
+
const answer = await ask(paint(ansi.green, " choose: "));
|
|
1050
|
+
const index = Number.parseInt(answer ?? "", 10) - 1;
|
|
1051
|
+
return options[index]?.value ?? null;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
async function applyModelSelection(modelId) {
|
|
1055
|
+
const next = { ...selectionRef.current, model: modelId };
|
|
1056
|
+
try {
|
|
1057
|
+
await defaultModel.saveSelection(next);
|
|
1058
|
+
selectionRef.current = next;
|
|
1059
|
+
out(paint(ansi.green, ` ✓ model set: ${sanitize(next.provider)}/${sanitize(next.model)} (next message)\n`));
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async function applyReasoningSelection(effort) {
|
|
1066
|
+
const next = { ...selectionRef.current, reasoningEffort: effort === "default" ? undefined : effort };
|
|
1067
|
+
try {
|
|
1068
|
+
await defaultModel.saveSelection(next);
|
|
1069
|
+
selectionRef.current = next;
|
|
1070
|
+
out(paint(ansi.green, ` ✓ reasoning set: ${sanitize(effort)} (next message)\n`));
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
999
1076
|
// ---- parity helpers -----------------------------------------------------
|
|
1000
1077
|
const transcript = [];
|
|
1001
1078
|
const pendingImages = [];
|
|
@@ -1211,6 +1288,7 @@ async function run(ctx, options, exit) {
|
|
|
1211
1288
|
}
|
|
1212
1289
|
|
|
1213
1290
|
while (!quitting) {
|
|
1291
|
+
printStatusBar();
|
|
1214
1292
|
if (editor) editor.setCommands(commandMenu(agent, commands));
|
|
1215
1293
|
const line = await ask(paint(ansi.bold, "> "), true);
|
|
1216
1294
|
if (line === null) break;
|
|
@@ -1243,13 +1321,27 @@ async function run(ctx, options, exit) {
|
|
|
1243
1321
|
printSessionList(listSessions(), write);
|
|
1244
1322
|
continue;
|
|
1245
1323
|
case "/resume": {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1324
|
+
let target = argument;
|
|
1325
|
+
if (target === "") {
|
|
1326
|
+
const sessions = listSessions();
|
|
1327
|
+
if (sessions.length === 0) {
|
|
1328
|
+
out(paint(ansi.yellow, " no sessions in this directory yet\n"));
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
target = await selectOption(
|
|
1332
|
+
"Resume session",
|
|
1333
|
+
sessions.slice(0, 10).map((session) => ({
|
|
1334
|
+
label: session.id,
|
|
1335
|
+
description: timeAgo(session.mtime) + (session.openTurn ? " · running" : ""),
|
|
1336
|
+
value: session.id,
|
|
1337
|
+
})),
|
|
1338
|
+
0
|
|
1339
|
+
);
|
|
1340
|
+
if (target === null) continue;
|
|
1249
1341
|
}
|
|
1250
1342
|
try {
|
|
1251
|
-
out(paint(ansi.dim, ` ↻ resuming ${
|
|
1252
|
-
agent = await startAgent(undefined,
|
|
1343
|
+
out(paint(ansi.dim, ` ↻ resuming ${sanitize(target)}\n`));
|
|
1344
|
+
agent = await startAgent(undefined, target);
|
|
1253
1345
|
} catch (error) {
|
|
1254
1346
|
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1255
1347
|
}
|
|
@@ -1273,33 +1365,45 @@ async function run(ctx, options, exit) {
|
|
|
1273
1365
|
continue;
|
|
1274
1366
|
}
|
|
1275
1367
|
case "/model": {
|
|
1276
|
-
if (argument
|
|
1277
|
-
|
|
1368
|
+
if (argument !== "") {
|
|
1369
|
+
await applyModelSelection(argument);
|
|
1278
1370
|
continue;
|
|
1279
1371
|
}
|
|
1280
|
-
|
|
1372
|
+
let models = [];
|
|
1281
1373
|
try {
|
|
1282
|
-
await
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
out(paint(ansi.
|
|
1374
|
+
models = llm === undefined ? [] : await llm.listModels(selectionRef.current.provider);
|
|
1375
|
+
} catch {}
|
|
1376
|
+
if (models.length === 0) {
|
|
1377
|
+
out(paint(ansi.dim, ` model: ${sanitize(modelDesc())}`) + "\n");
|
|
1378
|
+
out(paint(ansi.dim, " no model list is discoverable here — use /model <id>\n"));
|
|
1379
|
+
continue;
|
|
1287
1380
|
}
|
|
1381
|
+
const currentIndex = Math.max(0, models.findIndex((model) => model.id === selectionRef.current.model));
|
|
1382
|
+
const chosen = await selectOption(
|
|
1383
|
+
"Select model",
|
|
1384
|
+
models.map((model) => ({ label: model.name || model.id, description: model.id, value: model.id })),
|
|
1385
|
+
currentIndex
|
|
1386
|
+
);
|
|
1387
|
+
if (chosen !== null) await applyModelSelection(chosen);
|
|
1288
1388
|
continue;
|
|
1289
1389
|
}
|
|
1290
1390
|
case "/reasoning": {
|
|
1291
|
-
if (argument
|
|
1292
|
-
|
|
1391
|
+
if (argument !== "") {
|
|
1392
|
+
await applyReasoningSelection(argument);
|
|
1293
1393
|
continue;
|
|
1294
1394
|
}
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1395
|
+
const efforts = ["off", "high", "max", "default"];
|
|
1396
|
+
const current = selectionRef.current.reasoningEffort ?? "default";
|
|
1397
|
+
const chosen = await selectOption(
|
|
1398
|
+
"Select reasoning effort",
|
|
1399
|
+
efforts.map((effort) => ({
|
|
1400
|
+
label: effort,
|
|
1401
|
+
description: effort === "default" ? "provider default" : `effort: ${effort}`,
|
|
1402
|
+
value: effort,
|
|
1403
|
+
})),
|
|
1404
|
+
Math.max(0, efforts.indexOf(current))
|
|
1405
|
+
);
|
|
1406
|
+
if (chosen !== null) await applyReasoningSelection(chosen);
|
|
1303
1407
|
continue;
|
|
1304
1408
|
}
|
|
1305
1409
|
case "/thinking": {
|
|
@@ -1381,19 +1485,25 @@ async function run(ctx, options, exit) {
|
|
|
1381
1485
|
continue;
|
|
1382
1486
|
}
|
|
1383
1487
|
try {
|
|
1384
|
-
|
|
1385
|
-
if (argument === "") {
|
|
1386
|
-
out(paint(ansi.bold, "Permission presets:") + "\n");
|
|
1387
|
-
for (const option of catalog.options) {
|
|
1388
|
-
out(` ${paint(ansi.cyan, option.value.padEnd(18))} ${option.name}${option.description ? paint(ansi.dim, ` — ${option.description}`) : ""}\n`);
|
|
1389
|
-
}
|
|
1390
|
-
out(paint(ansi.dim, ` default: ${catalog.defaultPreset}`) + "\n");
|
|
1391
|
-
} else {
|
|
1488
|
+
if (argument !== "") {
|
|
1392
1489
|
permissionPresets.set(agent.session, argument);
|
|
1393
|
-
out(paint(ansi.green, ` ✓
|
|
1490
|
+
out(paint(ansi.green, ` ✓ permission mode: ${sanitize(argument)}\n`));
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
const catalog = permissionPresets.catalog();
|
|
1494
|
+
const options = catalog.options.map((option) => ({
|
|
1495
|
+
label: option.value === catalog.defaultPreset ? `${option.value} (default)` : option.value,
|
|
1496
|
+
description: option.description || option.name,
|
|
1497
|
+
value: option.value,
|
|
1498
|
+
}));
|
|
1499
|
+
const initial = Math.max(0, options.findIndex((option) => option.value === currentPermission()));
|
|
1500
|
+
const chosen = await selectOption("Permission mode", options, initial);
|
|
1501
|
+
if (chosen !== null) {
|
|
1502
|
+
permissionPresets.set(agent.session, chosen);
|
|
1503
|
+
out(paint(ansi.green, ` ✓ permission mode: ${sanitize(chosen)}\n`));
|
|
1394
1504
|
}
|
|
1395
1505
|
} catch (error) {
|
|
1396
|
-
out(paint(ansi.red, ` ✘ ${describeError(error)}\n`));
|
|
1506
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1397
1507
|
}
|
|
1398
1508
|
continue;
|
|
1399
1509
|
}
|