deepseek-cli-mode 1.2.0 → 1.4.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 +21 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/runner/line-editor.js +113 -5
- package/runner/runner.js +178 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.0 — reasoning effort levels in the model picker
|
|
4
|
+
|
|
5
|
+
- `/model` now asks for the reasoning effort after you pick a model, listing the adapter's real levels
|
|
6
|
+
(`low`, `medium`, `high`, `max`) plus `default`, with the current value preselected.
|
|
7
|
+
- `/reasoning` lists the same adapter-provided levels (falling back to `low`/`medium`/`high`/`max` when the
|
|
8
|
+
adapter exposes no metadata).
|
|
9
|
+
- The footer and banner show the active model **and** effort, e.g. `deepseek-flash · high`.
|
|
10
|
+
- Direct arguments still work: `/reasoning low`, `/model deepseek-v4-pro`.
|
|
11
|
+
|
|
12
|
+
## 1.3.0 — pickers, Shift+Tab permission cycling, permanent mode footer
|
|
13
|
+
|
|
14
|
+
- `/model`, `/reasoning`, `/permissions`, and `/resume` now open an interactive picker (`↑`/`↓`, `Enter`,
|
|
15
|
+
`Ctrl+C` to cancel) instead of requiring an argument. `/model` lists the models discovered from the provider.
|
|
16
|
+
- **Shift+Tab** cycles the permission mode (read-only → workspace-write → danger-full-access) without losing the
|
|
17
|
+
typed line, with a transient hint.
|
|
18
|
+
- A permanent dim footer above the prompt shows the active permission mode, model, and the Shift+Tab hint:
|
|
19
|
+
`⏵⏵ workspace-write · deepseek-official/deepseek-chat · shift+tab to cycle`.
|
|
20
|
+
- Direct arguments still work (`/model <id>`, `/reasoning high`, `/permissions read-only`, `/resume <id>`).
|
|
21
|
+
- Security: picker labels/descriptions and the footer pass through the sanitizer; permission changes still require a
|
|
22
|
+
user keystroke (the model cannot trigger them).
|
|
23
|
+
|
|
3
24
|
## 1.2.0 — live slash-command menu
|
|
4
25
|
|
|
5
26
|
- 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 and its reasoning effort (low/medium/high/max)
|
|
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,100 @@ 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
|
+
/** Adapter-owned reasoning levels for the selected model, when exposed. */
|
|
1055
|
+
async function reasoningInfo() {
|
|
1056
|
+
try {
|
|
1057
|
+
if (llm === undefined) return null;
|
|
1058
|
+
const resolved = await llm.resolveModel(selectionRef.current.provider, selectionRef.current.model);
|
|
1059
|
+
return resolved.reasoning ?? null;
|
|
1060
|
+
} catch {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async function applyModelSelection(modelId, pickEffort = true) {
|
|
1066
|
+
const next = { ...selectionRef.current, model: modelId };
|
|
1067
|
+
try {
|
|
1068
|
+
await defaultModel.saveSelection(next);
|
|
1069
|
+
selectionRef.current = next;
|
|
1070
|
+
out(paint(ansi.green, ` ✓ model set: ${sanitize(next.provider)}/${sanitize(next.model)} (next message)\n`));
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (!pickEffort) return;
|
|
1076
|
+
const info = await reasoningInfo();
|
|
1077
|
+
const effortOptions =
|
|
1078
|
+
info !== null && info.efforts.length > 0
|
|
1079
|
+
? info.efforts.map((effort) => ({ label: effort.name, description: effort.id, value: effort.id }))
|
|
1080
|
+
: ["low", "medium", "high", "max"].map((effort) => ({ label: effort, description: `effort: ${effort}`, value: effort }));
|
|
1081
|
+
const options = [...effortOptions, { label: "default", description: "provider default", value: "default" }];
|
|
1082
|
+
const defaultEffort = info !== null && info.defaultEffort !== undefined ? info.defaultEffort : "default";
|
|
1083
|
+
// Offer the effort choice for the newly selected model, keeping the current
|
|
1084
|
+
// value when it is still valid.
|
|
1085
|
+
const currentEffort = selectionRef.current.reasoningEffort ?? "default";
|
|
1086
|
+
const preferred = options.some((option) => option.value === currentEffort) ? currentEffort : defaultEffort;
|
|
1087
|
+
const initial = Math.max(0, options.findIndex((option) => option.value === preferred));
|
|
1088
|
+
const chosen = await selectOption(`Reasoning effort for ${modelId}`, options, initial);
|
|
1089
|
+
if (chosen !== null) await applyReasoningSelection(chosen);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
async function applyReasoningSelection(effort) {
|
|
1093
|
+
const next = { ...selectionRef.current, reasoningEffort: effort === "default" ? undefined : effort };
|
|
1094
|
+
try {
|
|
1095
|
+
await defaultModel.saveSelection(next);
|
|
1096
|
+
selectionRef.current = next;
|
|
1097
|
+
out(paint(ansi.green, ` ✓ reasoning set: ${sanitize(effort)} (next message)\n`));
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
999
1103
|
// ---- parity helpers -----------------------------------------------------
|
|
1000
1104
|
const transcript = [];
|
|
1001
1105
|
const pendingImages = [];
|
|
@@ -1211,6 +1315,7 @@ async function run(ctx, options, exit) {
|
|
|
1211
1315
|
}
|
|
1212
1316
|
|
|
1213
1317
|
while (!quitting) {
|
|
1318
|
+
printStatusBar();
|
|
1214
1319
|
if (editor) editor.setCommands(commandMenu(agent, commands));
|
|
1215
1320
|
const line = await ask(paint(ansi.bold, "> "), true);
|
|
1216
1321
|
if (line === null) break;
|
|
@@ -1243,13 +1348,27 @@ async function run(ctx, options, exit) {
|
|
|
1243
1348
|
printSessionList(listSessions(), write);
|
|
1244
1349
|
continue;
|
|
1245
1350
|
case "/resume": {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1351
|
+
let target = argument;
|
|
1352
|
+
if (target === "") {
|
|
1353
|
+
const sessions = listSessions();
|
|
1354
|
+
if (sessions.length === 0) {
|
|
1355
|
+
out(paint(ansi.yellow, " no sessions in this directory yet\n"));
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
target = await selectOption(
|
|
1359
|
+
"Resume session",
|
|
1360
|
+
sessions.slice(0, 10).map((session) => ({
|
|
1361
|
+
label: session.id,
|
|
1362
|
+
description: timeAgo(session.mtime) + (session.openTurn ? " · running" : ""),
|
|
1363
|
+
value: session.id,
|
|
1364
|
+
})),
|
|
1365
|
+
0
|
|
1366
|
+
);
|
|
1367
|
+
if (target === null) continue;
|
|
1249
1368
|
}
|
|
1250
1369
|
try {
|
|
1251
|
-
out(paint(ansi.dim, ` ↻ resuming ${
|
|
1252
|
-
agent = await startAgent(undefined,
|
|
1370
|
+
out(paint(ansi.dim, ` ↻ resuming ${sanitize(target)}\n`));
|
|
1371
|
+
agent = await startAgent(undefined, target);
|
|
1253
1372
|
} catch (error) {
|
|
1254
1373
|
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1255
1374
|
}
|
|
@@ -1273,33 +1392,50 @@ async function run(ctx, options, exit) {
|
|
|
1273
1392
|
continue;
|
|
1274
1393
|
}
|
|
1275
1394
|
case "/model": {
|
|
1276
|
-
if (argument
|
|
1277
|
-
|
|
1395
|
+
if (argument !== "") {
|
|
1396
|
+
await applyModelSelection(argument);
|
|
1278
1397
|
continue;
|
|
1279
1398
|
}
|
|
1280
|
-
|
|
1399
|
+
let models = [];
|
|
1281
1400
|
try {
|
|
1282
|
-
await
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
out(paint(ansi.
|
|
1401
|
+
models = llm === undefined ? [] : await llm.listModels(selectionRef.current.provider);
|
|
1402
|
+
} catch {}
|
|
1403
|
+
if (models.length === 0) {
|
|
1404
|
+
out(paint(ansi.dim, ` model: ${sanitize(modelDesc())}`) + "\n");
|
|
1405
|
+
out(paint(ansi.dim, " no model list is discoverable here — use /model <id>\n"));
|
|
1406
|
+
continue;
|
|
1287
1407
|
}
|
|
1408
|
+
const currentIndex = Math.max(0, models.findIndex((model) => model.id === selectionRef.current.model));
|
|
1409
|
+
const chosen = await selectOption(
|
|
1410
|
+
"Select model",
|
|
1411
|
+
models.map((model) => ({ label: model.name || model.id, description: model.id, value: model.id })),
|
|
1412
|
+
currentIndex
|
|
1413
|
+
);
|
|
1414
|
+
if (chosen !== null) await applyModelSelection(chosen);
|
|
1288
1415
|
continue;
|
|
1289
1416
|
}
|
|
1290
1417
|
case "/reasoning": {
|
|
1291
|
-
if (argument
|
|
1292
|
-
|
|
1418
|
+
if (argument !== "") {
|
|
1419
|
+
await applyReasoningSelection(argument);
|
|
1293
1420
|
continue;
|
|
1294
1421
|
}
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1422
|
+
const info = await reasoningInfo();
|
|
1423
|
+
const efforts =
|
|
1424
|
+
info !== null && info.efforts.length > 0
|
|
1425
|
+
? info.efforts.map((effort) => ({
|
|
1426
|
+
label: effort.name,
|
|
1427
|
+
description: effort.description ?? effort.id,
|
|
1428
|
+
value: effort.id,
|
|
1429
|
+
}))
|
|
1430
|
+
: ["low", "medium", "high", "max"].map((effort) => ({
|
|
1431
|
+
label: effort,
|
|
1432
|
+
description: `effort: ${effort}`,
|
|
1433
|
+
value: effort,
|
|
1434
|
+
}));
|
|
1435
|
+
efforts.push({ label: "default", description: "provider default", value: "default" });
|
|
1436
|
+
const current = selectionRef.current.reasoningEffort ?? "default";
|
|
1437
|
+
const chosen = await selectOption("Select reasoning effort", efforts, Math.max(0, efforts.findIndex((option) => option.value === current)));
|
|
1438
|
+
if (chosen !== null) await applyReasoningSelection(chosen);
|
|
1303
1439
|
continue;
|
|
1304
1440
|
}
|
|
1305
1441
|
case "/thinking": {
|
|
@@ -1381,19 +1517,25 @@ async function run(ctx, options, exit) {
|
|
|
1381
1517
|
continue;
|
|
1382
1518
|
}
|
|
1383
1519
|
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 {
|
|
1520
|
+
if (argument !== "") {
|
|
1392
1521
|
permissionPresets.set(agent.session, argument);
|
|
1393
|
-
out(paint(ansi.green, ` ✓
|
|
1522
|
+
out(paint(ansi.green, ` ✓ permission mode: ${sanitize(argument)}\n`));
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
const catalog = permissionPresets.catalog();
|
|
1526
|
+
const options = catalog.options.map((option) => ({
|
|
1527
|
+
label: option.value === catalog.defaultPreset ? `${option.value} (default)` : option.value,
|
|
1528
|
+
description: option.description || option.name,
|
|
1529
|
+
value: option.value,
|
|
1530
|
+
}));
|
|
1531
|
+
const initial = Math.max(0, options.findIndex((option) => option.value === currentPermission()));
|
|
1532
|
+
const chosen = await selectOption("Permission mode", options, initial);
|
|
1533
|
+
if (chosen !== null) {
|
|
1534
|
+
permissionPresets.set(agent.session, chosen);
|
|
1535
|
+
out(paint(ansi.green, ` ✓ permission mode: ${sanitize(chosen)}\n`));
|
|
1394
1536
|
}
|
|
1395
1537
|
} catch (error) {
|
|
1396
|
-
out(paint(ansi.red, ` ✘ ${describeError(error)}\n`));
|
|
1538
|
+
out(paint(ansi.red, ` ✘ ${sanitize(describeError(error))}\n`));
|
|
1397
1539
|
}
|
|
1398
1540
|
continue;
|
|
1399
1541
|
}
|