dsh-neotui 0.2.0 → 0.2.2
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 +32 -0
- package/package.json +1 -1
- package/src/panels.js +9 -23
- package/src/views.js +97 -58
- package/src/widgets.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 — 2026-08-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- 底栏在子代理状态后显示“有 x 条命令正在排队 Ctrl+N 查看详情”。
|
|
8
|
+
- Ctrl+N 打开精简排队命令面板;每条命令一行,支持 `dd` 删除。
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- AskUser 窗口按实际内容自适应高度,长内容才启用滚动。
|
|
13
|
+
- 选项标签与描述只显示一次,完整描述按终端宽度换行。
|
|
14
|
+
- 自定义答案常驻在选项下一行并支持最多六行自动换行。
|
|
15
|
+
- 长正文使用 Ctrl+↑/Ctrl+↓、PgUp/PgDn、Home/End 和滚轮翻页。
|
|
16
|
+
- 权限审批窗口保留完整原因和命令,明确显示滚动提示与位置。
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- AskUser 自定义回答、真实跳过项、精确鼠标 hitbox 和文本光标编辑。
|
|
21
|
+
- 权限申请窗口支持 ↑/↓ 逐行滚动,不改变安全默认“拒绝”。
|
|
22
|
+
- 超长审批命令不再只保留前六行。
|
|
23
|
+
- Queue 面板移除未要求的编辑与 steering 操作,`dd` 同时兼容 text/key 事件。
|
|
24
|
+
|
|
25
|
+
## 0.2.1 — 2026-08-16
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- AskUser 问题始终显示“输入自己的回答”和常驻输入栏。
|
|
30
|
+
- “跳过此问题”改为真实、可导航的列表项,不再使用重复的底部按钮。
|
|
31
|
+
- 自定义回答支持左右键、Home、End、Backspace、Delete 和光标位置插入。
|
|
32
|
+
- 鼠标只在选项实际文字区域内生效,同行空白区域不再误提交。
|
|
33
|
+
- 自定义输入栏固定在自定义回答项下一行,跳过项固定在输入栏下方。
|
|
34
|
+
|
|
3
35
|
## 0.2.0 — 2026-08-16
|
|
4
36
|
|
|
5
37
|
### Added
|
package/package.json
CHANGED
package/src/panels.js
CHANGED
|
@@ -1751,18 +1751,17 @@ export class QueuePanel extends Popup {
|
|
|
1751
1751
|
const items = app.queueItems ?? [];
|
|
1752
1752
|
const w = Math.max(24, Math.min(84, app.screen.w - 4));
|
|
1753
1753
|
const h = Math.max(7, Math.min(24, app.screen.h - 4));
|
|
1754
|
-
super({ x: Math.max(0, Math.floor((app.screen.w - w) / 2)), y: Math.max(0, Math.floor((app.screen.h - h) / 2)), w, h, title: "
|
|
1755
|
-
this.app = app; this.items = items; this.sel = 0; this.pending = false; this.
|
|
1754
|
+
super({ x: Math.max(0, Math.floor((app.screen.w - w) / 2)), y: Math.max(0, Math.floor((app.screen.h - h) / 2)), w, h, title: "排队命令 · ↑↓ 选择 · dd 删除 · Esc 关闭", lines: [], buttons: [], scrollable: true });
|
|
1755
|
+
this.app = app; this.items = items; this.sel = 0; this.pending = false; this.dArmed=false; this.rebuild();
|
|
1756
1756
|
}
|
|
1757
1757
|
rebuild() {
|
|
1758
|
-
const lines = [
|
|
1758
|
+
const lines = [];
|
|
1759
1759
|
for (let i = 0; i < this.items.length; i++) {
|
|
1760
1760
|
const item = this.items[i];
|
|
1761
1761
|
const text = partsText(item.message?.content).replace(/\s+/g, " ");
|
|
1762
1762
|
lines.push([{ t: `${i === this.sel ? "▸" : " "} ${item.placement === "queued" ? "⏳" : item.placement === "steering" ? "↪" : "ℹ"} ${truncate(text || item.id, this.w - 8)}`, fg: i === this.sel ? T.SELFG : K.TXT, bg: i === this.sel ? T.MENUSEL : -1 }]);
|
|
1763
1763
|
}
|
|
1764
1764
|
if (!this.items.length) lines.push([{ t: " (队列为空)", fg: K.FAINT }]);
|
|
1765
|
-
if (this.editInput) lines.push([{ t: ` 编辑: ${truncate(this.editInput.value, this.w - 8)}`, fg: K.ACCENT }]);
|
|
1766
1765
|
this.lines = lines;
|
|
1767
1766
|
}
|
|
1768
1767
|
syncItems(items) {
|
|
@@ -1782,7 +1781,6 @@ export class QueuePanel extends Popup {
|
|
|
1782
1781
|
const action = kind === "edit" ? { kind, content: [{ type: "text", text: content }] } : { kind };
|
|
1783
1782
|
await this.app.api.call("session.updateQueue", { sessionId: this.app.currentSession, itemId: item.id, action });
|
|
1784
1783
|
if (kind === "remove") this.syncItems(this.items.filter((row) => row.id !== item.id));
|
|
1785
|
-
this.editInput = null;
|
|
1786
1784
|
} catch (e) {
|
|
1787
1785
|
const code = this.#errorCode(e);
|
|
1788
1786
|
if (code === "queue-item-not-found" || /queue-item-not-found/.test(e.message ?? "")) {
|
|
@@ -1793,31 +1791,19 @@ export class QueuePanel extends Popup {
|
|
|
1793
1791
|
} else this.app.toast(`队列操作失败: ${e.message}`);
|
|
1794
1792
|
} finally { this.pending = false; this.rebuild(); this.app.redraw(); }
|
|
1795
1793
|
}
|
|
1796
|
-
#beginEdit() {
|
|
1797
|
-
const item = this.items[this.sel];
|
|
1798
|
-
if (!item || item.placement !== "queued") { this.app.toast("只有排队消息可以编辑"); return; }
|
|
1799
|
-
this.editInput = new Input({ x: this.x + 2, y: this.y + this.h - 3, w: this.w - 4, h: 1, prompt: "编辑: ", allowEmptyEnter: false, onEnter: (value) => this.#mutate("edit", value) });
|
|
1800
|
-
this.editInput.setValue(partsText(item.message?.content));
|
|
1801
|
-
this.rebuild();
|
|
1802
|
-
}
|
|
1803
1794
|
onKey(ev) {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
if (this.editInput.onKey(ev)) { this.rebuild(); this.app.redraw(); return true; }
|
|
1807
|
-
}
|
|
1795
|
+
const ch=ev.type==="text"?ev.text:ev.type==="key"&&ev.name==="char"?ev.key:null;
|
|
1796
|
+
if(ch==="d"){if(this.dArmed){this.dArmed=false;this.#mutate("remove");}else{this.dArmed=true;this.app.toast("再按 d 删除这条排队命令");}return true;}
|
|
1808
1797
|
if (ev.type === "key") {
|
|
1809
1798
|
if (ev.name === "escape") { this.app.closeOverlay(); return true; }
|
|
1810
|
-
if (ev.name === "up") { this.sel = Math.max(0, this.sel - 1); this.rebuild(); return true; }
|
|
1811
|
-
if (ev.name === "down") { this.sel = Math.min(this.items.length - 1, this.sel + 1); this.rebuild(); return true; }
|
|
1812
|
-
if (ev.name === "char" && ev.key === "e" && !ev.ctrl) { this.#beginEdit(); return true; }
|
|
1813
|
-
if (ev.name === "char" && ev.key === "s" && !ev.ctrl) { this.#mutate("steer"); return true; }
|
|
1814
|
-
if (ev.name === "char" && ev.key === "d" && !ev.ctrl) { this.#mutate("remove"); return true; }
|
|
1799
|
+
if (ev.name === "up") { this.dArmed=false;this.sel = Math.max(0, this.sel - 1); this.rebuild(); return true; }
|
|
1800
|
+
if (ev.name === "down") { this.dArmed=false;this.sel = Math.min(this.items.length - 1, this.sel + 1); this.rebuild(); return true; }
|
|
1815
1801
|
}
|
|
1816
|
-
return
|
|
1802
|
+
this.dArmed=false;return false;
|
|
1817
1803
|
}
|
|
1818
1804
|
onMouse(ev) {
|
|
1819
1805
|
if (ev.kind === "press" && ev.button === 0) {
|
|
1820
|
-
const idx = ev.y - this.y -
|
|
1806
|
+
const idx = ev.y - this.y - 1;
|
|
1821
1807
|
if (idx >= 0 && idx < this.items.length) { this.sel = idx; this.rebuild(); this.app.redraw(); return true; }
|
|
1822
1808
|
}
|
|
1823
1809
|
return super.onMouse(ev);
|
package/src/views.js
CHANGED
|
@@ -2344,22 +2344,36 @@ function truncateText(s, n) {
|
|
|
2344
2344
|
return s.length > n ? s.slice(0, n) + "\n…(截断)" : s;
|
|
2345
2345
|
}
|
|
2346
2346
|
|
|
2347
|
+
function wrapDisplayText(value, width) {
|
|
2348
|
+
const out = [];
|
|
2349
|
+
for (const raw of String(value ?? "").split("\n")) {
|
|
2350
|
+
if (!raw) { out.push(""); continue; }
|
|
2351
|
+
let line = "", used = 0;
|
|
2352
|
+
for (const g of graphemes(raw)) {
|
|
2353
|
+
const gw = graphemeWidth(g);
|
|
2354
|
+
if (line && used + gw > width) { out.push(line); line = ""; used = 0; }
|
|
2355
|
+
line += g; used += gw;
|
|
2356
|
+
}
|
|
2357
|
+
out.push(line);
|
|
2358
|
+
}
|
|
2359
|
+
return out;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2347
2362
|
// ---- Question popup ----
|
|
2348
2363
|
|
|
2349
2364
|
export class QuestionPopup extends Popup {
|
|
2350
2365
|
constructor({ app, frame }) {
|
|
2351
2366
|
const questions = frame.questions ?? [];
|
|
2352
2367
|
const planReview = questions.length === 1 && questions[0]?.intent?.kind === "plan-review";
|
|
2353
|
-
const w = Math.max(12, Math.min(planReview ?
|
|
2354
|
-
const
|
|
2368
|
+
const w = Math.max(12, Math.min(planReview ? 96 : 92, app.screen.w - 2));
|
|
2369
|
+
const wrapCount=(value,width)=>wrapDisplayText(value,width).length;
|
|
2370
|
+
const estimated=questions.reduce((n,q)=>n+1+wrapCount(q.question??"",w-4)+(q.detail?wrapCount(q.detail,w-4):0)+(q.options??[]).reduce((m,o)=>m+1+(o.description?wrapCount(o.description,w-10):0),0)+(q.intent?.kind==="plan-review"?0:4),0)+2;
|
|
2371
|
+
const h=Math.max(10,Math.min(app.screen.h-2,estimated));
|
|
2355
2372
|
super({
|
|
2356
2373
|
x: Math.max(0, Math.floor((app.screen.w - w) / 2)), y: Math.max(0, Math.floor((app.screen.h - h) / 2)),
|
|
2357
2374
|
w, h, title: planReview ? "✎ 计划审阅" : "❓ 需要你的回答",
|
|
2358
2375
|
lines: ["", ...questions.map((q) => q.question ?? q.id)],
|
|
2359
|
-
buttons:
|
|
2360
|
-
{ label: "回答…", action: "custom" },
|
|
2361
|
-
{ label: "跳过", action: "cancel" },
|
|
2362
|
-
],
|
|
2376
|
+
buttons: [],
|
|
2363
2377
|
});
|
|
2364
2378
|
this.app = app;
|
|
2365
2379
|
this.frame = frame;
|
|
@@ -2368,14 +2382,13 @@ export class QuestionPopup extends Popup {
|
|
|
2368
2382
|
this.detailScrollY = 0;
|
|
2369
2383
|
this.detailPage = 1;
|
|
2370
2384
|
this.optionRows = [];
|
|
2385
|
+
this.optionHitboxes = [];
|
|
2371
2386
|
this.questionIdx = 0;
|
|
2372
2387
|
this.drafts = questions.map(() => ({ selected: [], custom: "", skipped: false }));
|
|
2373
2388
|
this.selIdx = 0;
|
|
2374
|
-
this.
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
else if (btn.action === "__cancel__") this.#cancel();
|
|
2378
|
-
};
|
|
2389
|
+
this.customEditing = false;
|
|
2390
|
+
this.customCursor = 0;
|
|
2391
|
+
this.onAction = (btn) => { if (btn.action === "__cancel__") this.#cancel(); };
|
|
2379
2392
|
this.#layout();
|
|
2380
2393
|
}
|
|
2381
2394
|
|
|
@@ -2391,35 +2404,45 @@ export class QuestionPopup extends Popup {
|
|
|
2391
2404
|
const q = this.questions[this.questionIdx];
|
|
2392
2405
|
if (!q) return;
|
|
2393
2406
|
const draft = this.drafts[this.questionIdx];
|
|
2394
|
-
let ly = this.y + 1;
|
|
2395
|
-
screen.text(this.x + 2, ly++, truncate(`▎ ${q.header ?? `问题 ${this.questionIdx + 1}/${this.questions.length}`}`, this.w - 4), { fg: K.ACCENT, bold: true });
|
|
2396
|
-
screen.text(this.x + 2, ly++, truncate(q.question ?? "", this.w - 4), { fg: K.TXT });
|
|
2397
|
-
if (this.planReview) screen.text(this.x + 2, ly++, "Enter 执行计划 · ↓ 继续规划 · Esc 取消审阅", { fg: K.FAINT });
|
|
2398
2407
|
const opts = q.options ?? [];
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
this.
|
|
2405
|
-
this.
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
}
|
|
2413
|
-
this.
|
|
2414
|
-
|
|
2408
|
+
const optionDescriptionRows=opts.reduce((n,o)=>n+(o.description?wrapDisplayText(o.description,this.w-10).length:0),0);
|
|
2409
|
+
const actionRows = opts.length + optionDescriptionRows + (this.planReview ? 0 : Math.min(6,Math.max(3,wrapDisplayText(draft.custom||"在此输入…",this.w-10).length))+2);
|
|
2410
|
+
const actionTop = Math.max(this.y + 5, this.y + this.h - 1 - Math.min(actionRows,this.h-6));
|
|
2411
|
+
const doc = [
|
|
2412
|
+
{ text: `▎ ${q.header ?? `问题 ${this.questionIdx + 1}/${this.questions.length}`}`, fg: K.ACCENT, bold: true },
|
|
2413
|
+
...wrapDisplayText(q.question ?? "", this.w - 4).map((text) => ({ text, fg: K.TXT })),
|
|
2414
|
+
...(q.detail ? wrapDisplayText(q.detail, this.w - 4).map((text) => ({ text, fg: K.DIM })) : []),
|
|
2415
|
+
];
|
|
2416
|
+
|
|
2417
|
+
const room = Math.max(1, actionTop - (this.y + 1));
|
|
2418
|
+
const maxScroll = Math.max(0, doc.length - room);
|
|
2419
|
+
this.detailScrollY = Math.max(0, Math.min(this.detailScrollY, maxScroll)); this.detailPage = room; this.detailTotal = doc.length;
|
|
2420
|
+
let ly = this.y + 1;
|
|
2421
|
+
for (const line of doc.slice(this.detailScrollY, this.detailScrollY + room)) screen.text(this.x + 2, ly++, truncate(line.text, this.w - 4), { fg: line.fg, attrs: line.bold ? 1 : 0 });
|
|
2422
|
+
if(maxScroll>0){screen.text(this.x+this.w-26,this.y,`正文 Ctrl+↑↓ 翻页 ${this.detailScrollY+1}-${Math.min(doc.length,this.detailScrollY+room)}/${doc.length}`,{fg:K.ACCENT,bg:T.BG2});}
|
|
2423
|
+
screen.hline(this.x+1,this.x+this.w-2,actionTop-1,"─",{fg:T.BORDER2,bg:T.BG2});screen.text(this.x+3,actionTop-1," 回答(↑/↓ 选择) ",{fg:K.ACCENT,bg:T.BG2});
|
|
2424
|
+
ly=actionTop;
|
|
2425
|
+
this.optionRows = []; this.optionHitboxes = [];
|
|
2426
|
+
for (let i = 0; i < opts.length; i++) {
|
|
2415
2427
|
this.optionRows[i] = ly;
|
|
2416
2428
|
const chosen = draft.selected.includes(opts[i].label);
|
|
2417
2429
|
const cursor = this.selIdx === i;
|
|
2418
2430
|
const glyph = q.multiSelect ? (chosen ? "☑" : "☐") : (chosen ? "●" : "○");
|
|
2419
|
-
|
|
2420
|
-
|
|
2431
|
+
const optionText=truncate(` ${cursor ? "▸" : " "} ${glyph} ${opts[i].label}`, this.w - 6);
|
|
2432
|
+
const descLines=opts[i].description?wrapDisplayText(opts[i].description,this.w-10):[];
|
|
2433
|
+
this.optionHitboxes[i]={x1:this.x+2,x2:this.x+2+strWidth(optionText)-1,y1:ly,y2:ly+descLines.length};
|
|
2434
|
+
screen.text(this.x + 2, ly++, optionText, { fg: cursor ? T.SELFG : K.TXT, bg: cursor ? T.MENUSEL : -1 });
|
|
2435
|
+
for(const line of descLines)screen.text(this.x+7,ly++,line,{fg:K.FAINT,bg:cursor?T.MENUSEL:-1});
|
|
2436
|
+
}
|
|
2437
|
+
if (!this.planReview) {
|
|
2438
|
+
this.optionRows[opts.length] = ly;
|
|
2439
|
+
const cursor = this.selIdx === opts.length;
|
|
2440
|
+
const customText=truncate(` ${cursor ? "▸" : " "} ✎ 输入自己的回答`, this.w - 6);
|
|
2441
|
+
this.optionHitboxes[opts.length]={x1:this.x+2,x2:this.x+2+strWidth(customText)-1,y1:ly,y2:ly};
|
|
2442
|
+
screen.text(this.x + 2, ly++, customText, { fg: cursor ? T.SELFG : K.ACCENT, bg: cursor ? T.MENUSEL : -1 });
|
|
2443
|
+
{const chars=Array.from(draft.custom),shown=this.customEditing?[...chars.slice(0,this.customCursor),"▏",...chars.slice(this.customCursor)].join(""):draft.custom;const inputLines=wrapDisplayText(shown||"在此输入…",this.w-10).slice(-6);for(let i=0;i<inputLines.length;i++)screen.text(this.x+4,ly++,`${i===0?"> ":" "}${inputLines[i]}`,{fg:this.customEditing?K.TXT:K.FAINT,bg:this.customEditing?T.BG2:-1});}
|
|
2444
|
+
{this.optionRows[opts.length+1]=ly;const skip=this.selIdx===opts.length+1,skipText=` ${skip?"▸":" "} ↷ 跳过此问题`;this.optionHitboxes[opts.length+1]={x1:this.x+2,x2:this.x+2+strWidth(skipText)-1,y1:ly,y2:ly};screen.text(this.x+2,ly++,skipText,{fg:skip?T.SELFG:K.FAINT,bg:skip?T.MENUSEL:-1});}
|
|
2421
2445
|
}
|
|
2422
|
-
if (draft.custom) screen.text(this.x + 2, Math.min(ly, this.y + this.h - 3), truncate(`自定义: ${draft.custom}`, this.w - 4), { fg: K.ACCENT });
|
|
2423
2446
|
}
|
|
2424
2447
|
|
|
2425
2448
|
#choose(i) {
|
|
@@ -2435,20 +2458,24 @@ export class QuestionPopup extends Popup {
|
|
|
2435
2458
|
}
|
|
2436
2459
|
|
|
2437
2460
|
onMouse(ev) {
|
|
2438
|
-
if (
|
|
2439
|
-
|
|
2461
|
+
if (ev.kind === "wheel-up" || ev.kind === "wheel-down") {
|
|
2462
|
+
const max=Math.max(0,(this.detailTotal??0)-(this.detailPage??1));
|
|
2463
|
+
this.detailScrollY = Math.max(0,Math.min(max,this.detailScrollY + (ev.kind === "wheel-up" ? -3 : 3)));
|
|
2440
2464
|
this.app.redraw(); return true;
|
|
2441
2465
|
}
|
|
2442
2466
|
if (ev.kind === "press" && ev.button === 0) {
|
|
2443
2467
|
const q = this.questions[this.questionIdx];
|
|
2444
2468
|
for (let i = 0; i < (q?.options?.length ?? 0); i++) {
|
|
2445
|
-
const
|
|
2446
|
-
if (
|
|
2447
|
-
this.selIdx = i; this.#choose(i);
|
|
2469
|
+
const box=this.optionHitboxes[i];
|
|
2470
|
+
if (box && ev.x>=box.x1 && ev.x<=box.x2 && ev.y>=box.y1 && ev.y<=box.y2) {
|
|
2471
|
+
this.selIdx = i; this.customEditing = false; this.#choose(i);
|
|
2448
2472
|
if (!q.multiSelect) this.#continueCurrent();
|
|
2449
2473
|
return true;
|
|
2450
2474
|
}
|
|
2451
2475
|
}
|
|
2476
|
+
const count=q?.options?.length??0,customBox=this.optionHitboxes[count];
|
|
2477
|
+
if(customBox&&ev.x>=customBox.x1&&ev.x<=customBox.x2&&ev.y===customBox.y1){this.selIdx=count;this.customEditing=true;this.customCursor=Array.from(this.drafts[this.questionIdx].custom).length;this.drafts[this.questionIdx].selected=[];this.app.redraw();return true;}
|
|
2478
|
+
const skipBox=this.optionHitboxes[count+1];if(skipBox&&ev.x>=skipBox.x1&&ev.x<=skipBox.x2&&ev.y===skipBox.y1){this.selIdx=count+1;this.#skipCurrent();return true;}
|
|
2452
2479
|
}
|
|
2453
2480
|
return super.onMouse(ev);
|
|
2454
2481
|
}
|
|
@@ -2456,22 +2483,33 @@ export class QuestionPopup extends Popup {
|
|
|
2456
2483
|
onKey(ev) {
|
|
2457
2484
|
const q = this.questions[this.questionIdx];
|
|
2458
2485
|
const draft = this.drafts[this.questionIdx];
|
|
2459
|
-
if (ev.type === "text") { draft.custom += ev.text; draft.skipped = false; return true; }
|
|
2460
|
-
if (ev.type !== "key") return false;
|
|
2461
2486
|
const count = q?.options?.length ?? 0;
|
|
2462
|
-
|
|
2463
|
-
|
|
2487
|
+
const choices = count + (this.planReview ? 0 : 2);
|
|
2488
|
+
if (ev.type === "text") { if(this.customEditing||this.selIdx===count){this.customEditing=true;draft.selected=[];const chars=Array.from(draft.custom);chars.splice(this.customCursor,0,...Array.from(ev.text));draft.custom=chars.join("");this.customCursor+=Array.from(ev.text).length;draft.skipped=false;return true;} return false; }
|
|
2489
|
+
if (ev.type !== "key") return false;
|
|
2490
|
+
if (["pageup", "pagedown", "home", "end"].includes(ev.name)) {
|
|
2491
|
+
const total=this.detailTotal??0,max=Math.max(0,total-this.detailPage);
|
|
2464
2492
|
if (ev.name === "pageup") this.detailScrollY = Math.max(0, this.detailScrollY - this.detailPage);
|
|
2465
|
-
else if (ev.name === "pagedown") this.detailScrollY = Math.min(
|
|
2493
|
+
else if (ev.name === "pagedown") this.detailScrollY = Math.min(max, this.detailScrollY + this.detailPage);
|
|
2466
2494
|
else if (ev.name === "home") this.detailScrollY = 0;
|
|
2467
|
-
else this.detailScrollY =
|
|
2495
|
+
else this.detailScrollY = max;
|
|
2468
2496
|
this.app.redraw(); return true;
|
|
2469
2497
|
}
|
|
2470
|
-
if
|
|
2471
|
-
if
|
|
2472
|
-
if
|
|
2473
|
-
if
|
|
2498
|
+
if(!this.customEditing&&ev.ctrl&&ev.name==="up"){this.detailScrollY=Math.max(0,this.detailScrollY-this.detailPage);return true;}
|
|
2499
|
+
if(!this.customEditing&&ev.ctrl&&ev.name==="down"){this.detailScrollY=Math.min(Math.max(0,(this.detailTotal??0)-this.detailPage),this.detailScrollY+this.detailPage);return true;}
|
|
2500
|
+
if(this.customEditing&&ev.name==="left"){this.customCursor=Math.max(0,this.customCursor-1);return true;}
|
|
2501
|
+
if(this.customEditing&&ev.name==="right"){this.customCursor=Math.min(Array.from(draft.custom).length,this.customCursor+1);return true;}
|
|
2502
|
+
if(this.customEditing&&ev.name==="home"){this.customCursor=0;return true;}
|
|
2503
|
+
if(this.customEditing&&ev.name==="end"){this.customCursor=Array.from(draft.custom).length;return true;}
|
|
2504
|
+
if (ev.name === "up") { this.customEditing=false;this.selIdx = Math.max(0, this.selIdx - 1); return true; }
|
|
2505
|
+
if (ev.name === "down") { this.customEditing=false;this.selIdx = Math.min(Math.max(0, choices - 1), this.selIdx + 1); return true; }
|
|
2506
|
+
if (ev.name === "char" && ev.key === " " && count && this.selIdx<count) { this.#choose(this.selIdx); return true; }
|
|
2507
|
+
if (ev.name === "backspace" && this.customEditing && this.customCursor>0) {const chars=Array.from(draft.custom);chars.splice(this.customCursor-1,1);draft.custom=chars.join("");this.customCursor--;return true;}
|
|
2508
|
+
if(ev.name==="delete"&&this.customEditing){const chars=Array.from(draft.custom);if(this.customCursor<chars.length){chars.splice(this.customCursor,1);draft.custom=chars.join("");}return true;}
|
|
2474
2509
|
if (ev.name === "enter") {
|
|
2510
|
+
if(!this.planReview&&this.selIdx===count+1){this.#skipCurrent();return true;}
|
|
2511
|
+
if(!this.planReview&&this.selIdx===count&&!this.customEditing){this.customEditing=true;this.customCursor=Array.from(draft.custom).length;draft.selected=[];return true;}
|
|
2512
|
+
if(this.customEditing){if(!draft.custom.trim()){this.app.toast("请输入自己的回答");return true;}this.#continueCurrent();return true;}
|
|
2475
2513
|
if (count && draft.selected.length === 0 && !draft.custom) this.#choose(this.selIdx);
|
|
2476
2514
|
this.#continueCurrent(); return true;
|
|
2477
2515
|
}
|
|
@@ -2491,7 +2529,7 @@ export class QuestionPopup extends Popup {
|
|
|
2491
2529
|
}
|
|
2492
2530
|
|
|
2493
2531
|
#advanceOrSubmit() {
|
|
2494
|
-
if (this.questionIdx < this.questions.length - 1) { this.questionIdx++; this.selIdx = 0; this.detailScrollY = 0; this.#layout(); this.app.redraw(); return; }
|
|
2532
|
+
if (this.questionIdx < this.questions.length - 1) { this.questionIdx++; this.selIdx = 0; this.customEditing=false;this.customCursor=0;this.detailScrollY = 0; this.#layout(); this.app.redraw(); return; }
|
|
2495
2533
|
this.#submit();
|
|
2496
2534
|
}
|
|
2497
2535
|
|
|
@@ -2520,13 +2558,14 @@ export class ApprovalPopup extends Popup {
|
|
|
2520
2558
|
const maxW = Math.max(12, app.screen.w - 2);
|
|
2521
2559
|
const w = Math.min(72, maxW);
|
|
2522
2560
|
const command = app.chat?.toolCommandForCall?.(frame.callId) ?? null;
|
|
2523
|
-
const
|
|
2524
|
-
|
|
2525
|
-
];
|
|
2526
|
-
if (command) lines.push([{ t: "
|
|
2561
|
+
const wrap=(value,width)=>{const out=[];for(const raw of String(value??"").split("\n")){const gs=graphemes(raw);let line="",used=0;for(const g of gs){const gw=graphemeWidth(g);if(used+gw>width&&line){out.push(line);line="";used=0;}line+=g;used+=gw;}out.push(line);}return out;};
|
|
2562
|
+
const reason=frame.reason ?? `工具 ${frame.toolName ?? "tool"} 请求越权执行`;
|
|
2563
|
+
const lines = [[{t:" 请求原因",fg:K.DIM,underline:true}],...wrap(reason,w-6).map(line=>[{t:` ${line}`,fg:K.WARN}])];
|
|
2564
|
+
if (command) lines.push([{ t: " 将执行", fg: K.DIM, underline: true }], ...wrap(command,w-6).map((line) => [{ t: " " + line, fg: K.TXT }]));
|
|
2565
|
+
lines.push([{t:" 导航:↑/↓ 逐行 · PgUp/PgDn 翻页 · 滚轮 · ←/→ 选择",fg:K.FAINT}]);
|
|
2527
2566
|
super({
|
|
2528
|
-
x: Math.max(0, Math.floor((app.screen.w - w) / 2)), y: Math.max(0, Math.floor(app.screen.h
|
|
2529
|
-
w, h: Math.min(app.screen.h
|
|
2567
|
+
x: Math.max(0, Math.floor((app.screen.w - w) / 2)), y: Math.max(0, Math.floor((app.screen.h - Math.min(app.screen.h - 2, Math.max(10, Math.min(24, lines.length + 4)))) / 2)),
|
|
2568
|
+
w, h: Math.min(app.screen.h - 2, Math.max(10, Math.min(24, lines.length + 4))), title: "⚠ 工具需要授权 · 可滚动",
|
|
2530
2569
|
lines,
|
|
2531
2570
|
buttons: [
|
|
2532
2571
|
{ label: "允许一次", action: "allowed-once" },
|
|
@@ -3819,7 +3858,7 @@ export class App {
|
|
|
3819
3858
|
if (ev.ctrl && ev.key === "t") { this.setMode("trajectory"); return; }
|
|
3820
3859
|
if (ev.ctrl && ev.key === "e") { this.quickJumpStep(); return; }
|
|
3821
3860
|
if (ev.ctrl && ev.key === "j") { this.showJobs(); return; }
|
|
3822
|
-
if (ev.ctrl && ev.key === "
|
|
3861
|
+
if (ev.ctrl && ev.key === "n") { this.showQueue(); return; }
|
|
3823
3862
|
if (ev.ctrl && ev.key === "y") {
|
|
3824
3863
|
const next = busyEnter() === "queue" ? "steer" : "queue";
|
|
3825
3864
|
saveTuiConfig({ busyEnter: next });
|
|
@@ -4013,7 +4052,6 @@ export class App {
|
|
|
4013
4052
|
if (rawGoal && !["complete", "completed", "cleared"].includes(rawGoal.phase)) {
|
|
4014
4053
|
row0.left.push({ t: ` 🎯 ${truncate(rawGoal.objective ?? "目标", 14)} · Ctrl+G `, fg: T.SELFG, bg: T.WARN, bold: true });
|
|
4015
4054
|
}
|
|
4016
|
-
if (this.queueItems.length) row0.left.push({ t: ` ⏳队列 ${this.queueItems.length} (Ctrl+U) `, fg: T.SELFG, bg: T.WARN, bold: true });
|
|
4017
4055
|
if (this.sidebarVisible) row0.left.push({ t: " " + truncate(t || "(未选择会话)", 40) + " ", fg: T.TXT, bg: T.STATUSBG });
|
|
4018
4056
|
else row0.left.push({ t: " " + truncate(t || "(未选择会话)", 40) + " ", fg: T.TXT, bg: T.STATUSBG });
|
|
4019
4057
|
if (cur?.running) row0.left.push({ t: " ●运行 ", fg: T.OK, bg: T.STATUSBG });
|
|
@@ -4100,6 +4138,7 @@ export class App {
|
|
|
4100
4138
|
row2.left.push({ t: ` ${done}已完成${failed > 0 ? ` ${failed}失败` : ""} `, fg: done > 0 ? T.OK : T.FAINT, bg: T.STATUSBG });
|
|
4101
4139
|
row2.left.push({ t: ` ${subStats.running > 0 ? `${subStats.running} 个子代理运行中` : "没有子代理运行"} ${subStats.completed}已完成 `, fg: subStats.running > 0 ? T.PURPLE : T.FAINT, bg: T.STATUSBG, bold: subStats.running > 0 });
|
|
4102
4140
|
if (sub) row2.left.push({ t: ` 🛰 ${truncate(sub.label ?? sub.mode ?? "子代理", 20)} `, fg: T.PURPLE, bg: T.STATUSBG });
|
|
4141
|
+
if(this.queueItems.length)row2.left.push({t:` 有${this.queueItems.length}条命令正在排队 Ctrl+N查看详情 `,fg:T.SELFG,bg:T.WARN,bold:true});
|
|
4103
4142
|
row2.right.push({ t: " Ctrl+J 任务/子代理 ", fg: T.DIM, bg: T.STATUSBG });
|
|
4104
4143
|
rows.push(row2);
|
|
4105
4144
|
}
|
package/src/widgets.js
CHANGED
|
@@ -788,8 +788,8 @@ export class Popup extends Widget {
|
|
|
788
788
|
}
|
|
789
789
|
if (this.maxScroll() > 0) {
|
|
790
790
|
// "↑N" / "↓N" overflow indicators on the border row
|
|
791
|
-
if (this.scrollY > 0) screen.text(this.x + this.w -
|
|
792
|
-
if (this.scrollY < this.maxScroll()) screen.text(this.x + this.w -
|
|
791
|
+
if (this.scrollY > 0) screen.text(this.x + this.w - 10, this.y, ` ↑ ${this.scrollY}/${this.lines.length}`, { fg: T.ACCENT, bg: T.BG2 });
|
|
792
|
+
if (this.scrollY < this.maxScroll()) screen.text(this.x + this.w - 12, this.y + this.h - 1, ` ↓ ${this.scrollY + this.contentRows()}/${this.lines.length}`, { fg: T.ACCENT, bg: T.BG2 });
|
|
793
793
|
}
|
|
794
794
|
} else {
|
|
795
795
|
for (const line of this.lines) draw(line);
|
|
@@ -832,6 +832,8 @@ export class Popup extends Widget {
|
|
|
832
832
|
onKey(ev) {
|
|
833
833
|
if (ev.type !== "key") return false;
|
|
834
834
|
if (this.scrollable) {
|
|
835
|
+
if (ev.name === "up") { this.scrollY = Math.max(0, this.scrollY - 1); return true; }
|
|
836
|
+
if (ev.name === "down") { this.scrollY = Math.min(this.maxScroll(), this.scrollY + 1); return true; }
|
|
835
837
|
if (ev.name === "pgup") { this.scrollY = Math.max(0, this.scrollY - this.contentRows()); return true; }
|
|
836
838
|
if (ev.name === "pgdn") { this.scrollY = Math.min(this.maxScroll(), this.scrollY + this.contentRows()); return true; }
|
|
837
839
|
}
|