@narumitw/pi-btw 0.57.0 → 0.58.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/README.md +57 -141
- package/dist/index.ts +466 -41
- package/dist/index.ts.map +4 -4
- package/docs/workflows.md +74 -0
- package/package.json +2 -1
- package/src/btw.ts +15 -1
- package/src/fullscreen-ui.ts +32 -2
- package/src/keybindings.ts +330 -0
- package/src/menu.ts +136 -19
- package/src/settings.ts +32 -0
- package/src/side-thread.ts +57 -5
- package/src/transcript-pager.ts +52 -30
package/dist/index.ts
CHANGED
|
@@ -481,15 +481,23 @@ import {
|
|
|
481
481
|
copyToClipboard as copyToHostClipboard
|
|
482
482
|
} from "@earendil-works/pi-coding-agent";
|
|
483
483
|
import {
|
|
484
|
-
isKeyRelease,
|
|
485
|
-
isKittyProtocolActive,
|
|
484
|
+
isKeyRelease as isKeyRelease2,
|
|
485
|
+
isKittyProtocolActive as isKittyProtocolActive2,
|
|
486
486
|
Key as Key2,
|
|
487
|
-
matchesKey as matchesKey2,
|
|
488
487
|
parseKey,
|
|
489
488
|
TuiAltScreen,
|
|
490
489
|
truncateToWidth as truncateToWidth2
|
|
491
490
|
} from "@earendil-works/pi-tui";
|
|
492
491
|
|
|
492
|
+
// src/keybindings.ts
|
|
493
|
+
import {
|
|
494
|
+
isKeyRelease,
|
|
495
|
+
isKittyProtocolActive,
|
|
496
|
+
KeybindingsManager,
|
|
497
|
+
matchesKey as matchesKey2,
|
|
498
|
+
TUI_KEYBINDINGS
|
|
499
|
+
} from "@earendil-works/pi-tui";
|
|
500
|
+
|
|
493
501
|
// src/text.ts
|
|
494
502
|
function sanitizeSingleLine(text) {
|
|
495
503
|
return [...text.replace(/[\r\n\t]/gu, " ")].filter((character) => {
|
|
@@ -510,6 +518,234 @@ function formatKeyLabel2(key) {
|
|
|
510
518
|
}).join("+");
|
|
511
519
|
}
|
|
512
520
|
|
|
521
|
+
// src/keybindings.ts
|
|
522
|
+
var BTW_SHORTCUT_ACTIONS = ["exit", "cycleThinkingLevel", "bringToMain"];
|
|
523
|
+
var MODIFIERS = ["shift", "alt", "ctrl", "super"];
|
|
524
|
+
var SYMBOLS = "`-=[]\\;',./!@#$%^&*()_|~{}:<>?";
|
|
525
|
+
var SPECIAL = {
|
|
526
|
+
escape: 27,
|
|
527
|
+
tab: 9,
|
|
528
|
+
enter: 13,
|
|
529
|
+
space: 32,
|
|
530
|
+
backspace: 127,
|
|
531
|
+
insert: 57425,
|
|
532
|
+
delete: 57426,
|
|
533
|
+
home: 57423,
|
|
534
|
+
end: 57424,
|
|
535
|
+
pageup: 57421,
|
|
536
|
+
pagedown: 57422,
|
|
537
|
+
left: 57417,
|
|
538
|
+
right: 57418,
|
|
539
|
+
up: 57419,
|
|
540
|
+
down: 57420
|
|
541
|
+
};
|
|
542
|
+
var FUNCTION_INPUTS = [
|
|
543
|
+
"OP",
|
|
544
|
+
"OQ",
|
|
545
|
+
"OR",
|
|
546
|
+
"OS",
|
|
547
|
+
"[15~",
|
|
548
|
+
"[17~",
|
|
549
|
+
"[18~",
|
|
550
|
+
"[19~",
|
|
551
|
+
"[20~",
|
|
552
|
+
"[21~",
|
|
553
|
+
"[23~",
|
|
554
|
+
"[24~"
|
|
555
|
+
];
|
|
556
|
+
var LEGACY_INPUTS = [
|
|
557
|
+
...Array.from({ length: 128 }, (_, code) => String.fromCharCode(code)),
|
|
558
|
+
...Array.from({ length: 128 }, (_, code) => `\x1B${String.fromCharCode(code)}`),
|
|
559
|
+
"\x1B[Z",
|
|
560
|
+
"\x1BOM",
|
|
561
|
+
"\x1B[E",
|
|
562
|
+
"\x1B[e",
|
|
563
|
+
"\x1BOe",
|
|
564
|
+
...FUNCTION_INPUTS.map((suffix) => `\x1B${suffix}`)
|
|
565
|
+
];
|
|
566
|
+
function normalizeBtwKey(value) {
|
|
567
|
+
if (typeof value !== "string" || value.length > 80 || /[\s\p{Cc}]/u.test(value)) return void 0;
|
|
568
|
+
const parts = value.toLowerCase().split("+");
|
|
569
|
+
let base = parts.pop();
|
|
570
|
+
if (!base) return void 0;
|
|
571
|
+
if (base === "esc") base = "escape";
|
|
572
|
+
if (base === "return") base = "enter";
|
|
573
|
+
if (new Set(parts).size !== parts.length || parts.some((part) => !MODIFIERS.includes(part)))
|
|
574
|
+
return void 0;
|
|
575
|
+
if (!Object.hasOwn(SPECIAL, base) && base !== "clear" && !/^f(?:[1-9]|1[0-2])$/u.test(base) && !(base.length === 1 && (/^[a-z0-9]$/u.test(base) || SYMBOLS.includes(base))))
|
|
576
|
+
return void 0;
|
|
577
|
+
if ((base === "escape" || base.startsWith("f") && base.length > 1) && parts.length)
|
|
578
|
+
return void 0;
|
|
579
|
+
if (base === "clear" && (parts.length > 1 || parts.length === 1 && !["ctrl", "shift"].includes(parts[0] ?? "")))
|
|
580
|
+
return void 0;
|
|
581
|
+
return [...MODIFIERS.filter((part) => parts.includes(part)), base].join("+");
|
|
582
|
+
}
|
|
583
|
+
function inputsFor(key) {
|
|
584
|
+
const parts = key.split("+");
|
|
585
|
+
const base = parts.pop() ?? "";
|
|
586
|
+
const modifier = MODIFIERS.reduce(
|
|
587
|
+
(mask, part, bit) => mask | (parts.includes(part) ? 1 << bit : 0),
|
|
588
|
+
0
|
|
589
|
+
);
|
|
590
|
+
const code = SPECIAL[base] ?? (base.length === 1 ? base.charCodeAt(0) : void 0);
|
|
591
|
+
const inputs = code === void 0 ? LEGACY_INPUTS : [...LEGACY_INPUTS, `\x1B[${code};${modifier + 1}u`];
|
|
592
|
+
return inputs.filter((input) => matchesKey2(input, key));
|
|
593
|
+
}
|
|
594
|
+
function btwKeysOverlap(first, second) {
|
|
595
|
+
const normalized = normalizeBtwKey(first);
|
|
596
|
+
return normalized !== void 0 && inputsFor(normalized).some((input) => matchesKey2(input, second));
|
|
597
|
+
}
|
|
598
|
+
function reservedKeys(keybindings, copyOnSelect) {
|
|
599
|
+
return [
|
|
600
|
+
"ctrl+c",
|
|
601
|
+
"shift+backspace",
|
|
602
|
+
"shift+delete",
|
|
603
|
+
"shift+space",
|
|
604
|
+
// Exact-range review uses these fixed selection actions even if Editor is remapped.
|
|
605
|
+
"shift+left",
|
|
606
|
+
"shift+right",
|
|
607
|
+
"shift+up",
|
|
608
|
+
"shift+down",
|
|
609
|
+
"left",
|
|
610
|
+
"right",
|
|
611
|
+
"enter",
|
|
612
|
+
"alt+enter",
|
|
613
|
+
"ctrl+j",
|
|
614
|
+
"pageUp",
|
|
615
|
+
"pageDown",
|
|
616
|
+
...Object.keys(TUI_KEYBINDINGS).flatMap(
|
|
617
|
+
(id) => keybindings.getKeys(id)
|
|
618
|
+
),
|
|
619
|
+
...!copyOnSelect ? keybindings.getKeys("app.message.copy") : []
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
function isTextKey(key) {
|
|
623
|
+
const parts = key.split("+");
|
|
624
|
+
const base = parts.at(-1) ?? "";
|
|
625
|
+
return (base.length === 1 || base === "space") && !parts.some((part) => ["ctrl", "alt", "super"].includes(part));
|
|
626
|
+
}
|
|
627
|
+
function resolveBtwShortcuts(overrides = {}, keybindings, copyOnSelect = true) {
|
|
628
|
+
let mode = isKittyProtocolActive();
|
|
629
|
+
let snapshot = resolveShortcutSnapshot(overrides, keybindings, copyOnSelect);
|
|
630
|
+
const current = () => {
|
|
631
|
+
if (mode !== isKittyProtocolActive()) {
|
|
632
|
+
mode = isKittyProtocolActive();
|
|
633
|
+
snapshot = resolveShortcutSnapshot(overrides, keybindings, copyOnSelect);
|
|
634
|
+
}
|
|
635
|
+
return snapshot;
|
|
636
|
+
};
|
|
637
|
+
return {
|
|
638
|
+
get keys() {
|
|
639
|
+
return current().keys;
|
|
640
|
+
},
|
|
641
|
+
get warnings() {
|
|
642
|
+
return current().warnings;
|
|
643
|
+
},
|
|
644
|
+
matches: (data, action) => current().matches(data, action),
|
|
645
|
+
label: (action) => current().label(action)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function resolveShortcutSnapshot(overrides = {}, keybindings, copyOnSelect = true) {
|
|
649
|
+
const reserved = reservedKeys(keybindings, copyOnSelect);
|
|
650
|
+
const keys = { exit: ["ctrl+c"], cycleThinkingLevel: [], bringToMain: [] };
|
|
651
|
+
const warnings = [];
|
|
652
|
+
const defaults = {
|
|
653
|
+
exit: ["ctrl+c"],
|
|
654
|
+
cycleThinkingLevel: keybindings.getKeys("app.thinking.cycle"),
|
|
655
|
+
bringToMain: ["ctrl+r"]
|
|
656
|
+
};
|
|
657
|
+
const usable = (action, candidate, inherited = false) => {
|
|
658
|
+
const key = normalizeBtwKey(candidate);
|
|
659
|
+
if (!key || !inherited && isTextKey(key)) return void 0;
|
|
660
|
+
const inputs = inputsFor(key);
|
|
661
|
+
if (!inputs.length) return void 0;
|
|
662
|
+
if (action === "exit" && key === "ctrl+c") return key;
|
|
663
|
+
if (reserved.some(
|
|
664
|
+
(other) => typeof other === "string" && inputs.some((input) => matchesKey2(input, other))
|
|
665
|
+
))
|
|
666
|
+
return void 0;
|
|
667
|
+
return key;
|
|
668
|
+
};
|
|
669
|
+
const candidates = {};
|
|
670
|
+
const availableDefaults = { ...defaults };
|
|
671
|
+
for (const action of BTW_SHORTCUT_ACTIONS) {
|
|
672
|
+
availableDefaults[action] = defaults[action].map((key) => usable(action, key, action === "cycleThinkingLevel")).filter((key) => key !== void 0);
|
|
673
|
+
const override = overrides[action];
|
|
674
|
+
if (override !== void 0) candidates[action] = usable(action, override);
|
|
675
|
+
}
|
|
676
|
+
for (; ; ) {
|
|
677
|
+
const rejected = BTW_SHORTCUT_ACTIONS.filter((action) => {
|
|
678
|
+
const candidate = candidates[action];
|
|
679
|
+
return candidate !== void 0 && BTW_SHORTCUT_ACTIONS.some(
|
|
680
|
+
(other) => other !== action && (candidates[other] ? [candidates[other]] : availableDefaults[other]).some(
|
|
681
|
+
(key) => btwKeysOverlap(candidate, key)
|
|
682
|
+
)
|
|
683
|
+
);
|
|
684
|
+
});
|
|
685
|
+
if (!rejected.length) break;
|
|
686
|
+
for (const action of rejected) delete candidates[action];
|
|
687
|
+
}
|
|
688
|
+
for (const action of BTW_SHORTCUT_ACTIONS) {
|
|
689
|
+
const override = overrides[action];
|
|
690
|
+
const selected = candidates[action];
|
|
691
|
+
if (override !== void 0 && !selected)
|
|
692
|
+
warnings.push(
|
|
693
|
+
`${action}: configured shortcut is invalid or conflicts with a reserved action; using an available default.`
|
|
694
|
+
);
|
|
695
|
+
const effective = selected ? [selected] : availableDefaults[action].filter(
|
|
696
|
+
(key) => !BTW_SHORTCUT_ACTIONS.some(
|
|
697
|
+
(other) => other !== action && keys[other].some((used) => btwKeysOverlap(key, used))
|
|
698
|
+
)
|
|
699
|
+
);
|
|
700
|
+
keys[action] = action === "exit" ? [.../* @__PURE__ */ new Set([...effective, "ctrl+c"])] : effective;
|
|
701
|
+
if (!keys[action].length && (override !== void 0 || defaults[action].length > 0))
|
|
702
|
+
warnings.push(`${action}: no usable shortcut; change Pi BTW Settings or Pi keybindings.`);
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
keys,
|
|
706
|
+
warnings,
|
|
707
|
+
matches: (data, action) => !isKeyRelease(data) && keys[action].some((key) => matchesKey2(data, key)),
|
|
708
|
+
label: (action) => keys[action].length ? formatKeyLabel2(keys[action][0] ?? "") : "Unavailable"
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function validateBtwShortcutEdit(action, value, overrides, keybindings, copyOnSelect) {
|
|
712
|
+
if (value === void 0) return void 0;
|
|
713
|
+
if (!normalizeBtwKey(value))
|
|
714
|
+
return "Invalid key combination. Use a Pi key name such as ctrl+q or f6.";
|
|
715
|
+
const next = { ...overrides, [action]: value };
|
|
716
|
+
const resolved = resolveBtwShortcuts(next, keybindings, copyOnSelect);
|
|
717
|
+
const previous = resolveBtwShortcuts(overrides, keybindings, copyOnSelect);
|
|
718
|
+
for (const item of BTW_SHORTCUT_ACTIONS) {
|
|
719
|
+
if (item === action && !resolved.keys[item].includes(normalizeBtwKey(value) ?? "") || item !== action && previous.keys[item].some((key) => !resolved.keys[item].includes(key))) {
|
|
720
|
+
return `${item} conflicts with another BTW shortcut, editing, selection, search, scrolling, or copying. Choose a different key.`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return void 0;
|
|
724
|
+
}
|
|
725
|
+
var bindingsByTui = /* @__PURE__ */ new WeakMap();
|
|
726
|
+
function setBtwShortcuts(tui, shortcuts) {
|
|
727
|
+
bindingsByTui.set(tui, shortcuts);
|
|
728
|
+
}
|
|
729
|
+
function getBtwShortcuts(tui, keybindings) {
|
|
730
|
+
return bindingsByTui.get(tui) ?? resolveBtwShortcuts(
|
|
731
|
+
{},
|
|
732
|
+
keybindings ?? new KeybindingsManager({
|
|
733
|
+
...TUI_KEYBINDINGS,
|
|
734
|
+
"app.thinking.cycle": { defaultKeys: "shift+tab" }
|
|
735
|
+
})
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
var BtwPasteGuard = class {
|
|
739
|
+
active = false;
|
|
740
|
+
consume(data) {
|
|
741
|
+
const wasActive = this.active;
|
|
742
|
+
const starts = data.includes("\x1B[200~");
|
|
743
|
+
if (starts) this.active = true;
|
|
744
|
+
if (this.active && data.includes("\x1B[201~")) this.active = false;
|
|
745
|
+
return wasActive || starts;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
513
749
|
// src/fullscreen-ui.ts
|
|
514
750
|
var FullscreenUiDisposedError = class extends Error {
|
|
515
751
|
constructor() {
|
|
@@ -690,7 +926,7 @@ function legacyRawInput(key) {
|
|
|
690
926
|
const parts = key.split("+");
|
|
691
927
|
const base = parts.at(-1) ?? "";
|
|
692
928
|
if (parts.length === 2 && parts[0] === "ctrl") return rawCtrlInput(base);
|
|
693
|
-
if (
|
|
929
|
+
if (isKittyProtocolActive2()) return void 0;
|
|
694
930
|
if (parts.length === 2 && parts[0] === "alt" && base.length === 1) return `\x1B${base}`;
|
|
695
931
|
if (parts.length === 3 && parts[0] === "ctrl" && parts[1] === "alt") {
|
|
696
932
|
const input = rawCtrlInput(base);
|
|
@@ -761,7 +997,7 @@ function createBtwFullscreenTui(parent, theme, keybindings, copyOnSelect, manual
|
|
|
761
997
|
if (isInBracketedPaste && data.includes(BRACKETED_PASTE_END)) {
|
|
762
998
|
isInBracketedPaste = false;
|
|
763
999
|
}
|
|
764
|
-
if (wasInBracketedPaste || startsBracketedPaste || fullscreen.hasFocusedOverlay() ||
|
|
1000
|
+
if (wasInBracketedPaste || startsBracketedPaste || fullscreen.hasFocusedOverlay() || isKeyRelease2(data) || !keybindings.matches(data, "app.message.copy")) {
|
|
765
1001
|
return void 0;
|
|
766
1002
|
}
|
|
767
1003
|
if (!fullscreen.hasActiveSelection()) {
|
|
@@ -839,9 +1075,30 @@ var BtwFullscreenHost = class {
|
|
|
839
1075
|
this.fullscreen = this.createTui(this.parent, this.theme, this.keybindings, this.options);
|
|
840
1076
|
this.fullscreenCreated = true;
|
|
841
1077
|
this.fullscreen.start();
|
|
1078
|
+
const shortcuts = resolveBtwShortcuts(
|
|
1079
|
+
this.options.keybindings,
|
|
1080
|
+
this.keybindings,
|
|
1081
|
+
this.options.copyOnSelect ?? true
|
|
1082
|
+
);
|
|
1083
|
+
setBtwShortcuts(this.fullscreen, shortcuts);
|
|
1084
|
+
let previousWarnings = [];
|
|
1085
|
+
const reportWarnings = () => {
|
|
1086
|
+
const warnings = shortcuts.warnings;
|
|
1087
|
+
for (const warning of warnings) {
|
|
1088
|
+
if (previousWarnings.includes(warning)) continue;
|
|
1089
|
+
try {
|
|
1090
|
+
this.ctx.ui.notify(`Pi BTW: ${warning}`, "warning");
|
|
1091
|
+
} catch {
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
previousWarnings = warnings;
|
|
1095
|
+
};
|
|
1096
|
+
const pasteGuard = new BtwPasteGuard();
|
|
842
1097
|
const addHardCancelListener = this.fullscreen.addInputListenerBeforeAll?.bind(this.fullscreen) ?? this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ?? this.fullscreen.addInputListener.bind(this.fullscreen);
|
|
843
1098
|
this.removeHardCancelListener = addHardCancelListener((data) => {
|
|
844
|
-
|
|
1099
|
+
reportWarnings();
|
|
1100
|
+
if (pasteGuard.consume(data) || !shortcuts.matches(data, "exit")) return void 0;
|
|
1101
|
+
this.disposed = true;
|
|
845
1102
|
try {
|
|
846
1103
|
this.hardCancelActiveCustom?.();
|
|
847
1104
|
} finally {
|
|
@@ -1125,14 +1382,15 @@ async function completeSideThreadTurn({
|
|
|
1125
1382
|
thinkingLevel,
|
|
1126
1383
|
auth,
|
|
1127
1384
|
signal,
|
|
1128
|
-
completeSimple
|
|
1385
|
+
completeSimple,
|
|
1386
|
+
sessionId
|
|
1129
1387
|
}) {
|
|
1130
1388
|
if (signal?.aborted) return { kind: "aborted" };
|
|
1131
1389
|
try {
|
|
1132
1390
|
const response = await completeSimple(
|
|
1133
1391
|
model,
|
|
1134
1392
|
{ systemPrompt: SYSTEM_PROMPT, messages: buildSideThreadMessages(thread, question) },
|
|
1135
|
-
buildStreamOptions(auth, thinkingLevel, signal)
|
|
1393
|
+
buildStreamOptions(auth, { thinkingLevel, signal, model, sessionId })
|
|
1136
1394
|
);
|
|
1137
1395
|
if (signal?.aborted || response?.stopReason === "aborted") return { kind: "aborted" };
|
|
1138
1396
|
if (!isAssistantMessage(response)) {
|
|
@@ -1191,10 +1449,31 @@ function createUserMessage(text) {
|
|
|
1191
1449
|
timestamp: Date.now()
|
|
1192
1450
|
};
|
|
1193
1451
|
}
|
|
1194
|
-
|
|
1452
|
+
var OPENCODE_HOST = "opencode.ai";
|
|
1453
|
+
function matchesOpencodeHost(baseUrl) {
|
|
1454
|
+
if (!baseUrl) return false;
|
|
1455
|
+
try {
|
|
1456
|
+
return new URL(baseUrl).hostname === OPENCODE_HOST;
|
|
1457
|
+
} catch {
|
|
1458
|
+
return false;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function getOpencodeSessionHeaders(model, sessionId) {
|
|
1462
|
+
if (!sessionId) return void 0;
|
|
1463
|
+
if (model.provider !== "opencode" && model.provider !== "opencode-go" && !matchesOpencodeHost(model.baseUrl)) {
|
|
1464
|
+
return void 0;
|
|
1465
|
+
}
|
|
1466
|
+
return { "x-opencode-session": sessionId, "x-opencode-client": "pi" };
|
|
1467
|
+
}
|
|
1468
|
+
function mergeSessionHeaders(authHeaders, sessionHeaders) {
|
|
1469
|
+
if (!sessionHeaders && !authHeaders) return void 0;
|
|
1470
|
+
return { ...sessionHeaders, ...authHeaders };
|
|
1471
|
+
}
|
|
1472
|
+
function buildStreamOptions(auth, { thinkingLevel, signal, model, sessionId }) {
|
|
1473
|
+
const sessionHeaders = model ? getOpencodeSessionHeaders(model, sessionId) : void 0;
|
|
1195
1474
|
const options = {
|
|
1196
1475
|
apiKey: auth.apiKey,
|
|
1197
|
-
headers: auth.headers,
|
|
1476
|
+
headers: mergeSessionHeaders(auth.headers, sessionHeaders),
|
|
1198
1477
|
env: auth.env,
|
|
1199
1478
|
signal
|
|
1200
1479
|
};
|
|
@@ -1220,6 +1499,17 @@ function btwSettingsPath() {
|
|
|
1220
1499
|
function normalizeBtwSettings(value) {
|
|
1221
1500
|
if (!isSettingsDocument(value)) return void 0;
|
|
1222
1501
|
const settings = {};
|
|
1502
|
+
if (Object.hasOwn(value, "keybindings")) {
|
|
1503
|
+
const keys = value.keybindings;
|
|
1504
|
+
if (!isSettingsDocument(keys)) return void 0;
|
|
1505
|
+
settings.keybindings = {};
|
|
1506
|
+
for (const action of BTW_SHORTCUT_ACTIONS) {
|
|
1507
|
+
if (!Object.hasOwn(keys, action)) continue;
|
|
1508
|
+
const key = normalizeBtwKey(keys[action]);
|
|
1509
|
+
if (!key) return void 0;
|
|
1510
|
+
settings.keybindings[action] = key;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1223
1513
|
if (Object.hasOwn(value, "model")) {
|
|
1224
1514
|
const model = Reflect.get(value, "model");
|
|
1225
1515
|
if (typeof model !== "string" || !parseBtwModelReference(model)) return void 0;
|
|
@@ -1263,6 +1553,8 @@ function updateBtwSettings(patch, options = {}) {
|
|
|
1263
1553
|
return enqueueMutation(settingsPath, async () => {
|
|
1264
1554
|
options.signal?.throwIfAborted();
|
|
1265
1555
|
const current = await readSettingsDocumentForUpdate(settingsPath);
|
|
1556
|
+
options.signal?.throwIfAborted();
|
|
1557
|
+
options.validateCurrent?.(normalizeBtwSettings(current) ?? {});
|
|
1266
1558
|
const updated = applyBtwSettingsPatch(current, patch);
|
|
1267
1559
|
const settings = normalizeBtwSettings(updated);
|
|
1268
1560
|
if (!settings) throw invalidSettingsError(settingsPath, "invalid settings shape");
|
|
@@ -1381,6 +1673,16 @@ async function publishSettings(settingsPath, document, signal, beforeRename) {
|
|
|
1381
1673
|
}
|
|
1382
1674
|
function applyBtwSettingsPatch(current, patch) {
|
|
1383
1675
|
const updated = { ...current };
|
|
1676
|
+
if (patch.keybindings) {
|
|
1677
|
+
const keys = isSettingsDocument(current.keybindings) ? { ...current.keybindings } : {};
|
|
1678
|
+
for (const action of BTW_SHORTCUT_ACTIONS) {
|
|
1679
|
+
if (!Object.hasOwn(patch.keybindings, action)) continue;
|
|
1680
|
+
if (patch.keybindings[action] === void 0) delete keys[action];
|
|
1681
|
+
else keys[action] = patch.keybindings[action];
|
|
1682
|
+
}
|
|
1683
|
+
if (Object.keys(keys).length) updated.keybindings = keys;
|
|
1684
|
+
else delete updated.keybindings;
|
|
1685
|
+
}
|
|
1384
1686
|
if (Object.hasOwn(patch, "thinkingLevel")) {
|
|
1385
1687
|
if (patch.thinkingLevel === void 0) delete updated.thinkingLevel;
|
|
1386
1688
|
else updated.thinkingLevel = patch.thinkingLevel;
|
|
@@ -1425,6 +1727,64 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1425
1727
|
let startSelected = false;
|
|
1426
1728
|
let treeSelected = false;
|
|
1427
1729
|
let resumedThreadId;
|
|
1730
|
+
let keybindings;
|
|
1731
|
+
let shortcut = "exit";
|
|
1732
|
+
const shortcutLabels = {
|
|
1733
|
+
exit: "Exit shortcut",
|
|
1734
|
+
cycleThinkingLevel: "Cycle thinking level shortcut",
|
|
1735
|
+
bringToMain: "Bring to main shortcut"
|
|
1736
|
+
};
|
|
1737
|
+
const shortcutValue = (settings, action) => {
|
|
1738
|
+
if (!keybindings) return "Default";
|
|
1739
|
+
const effective = resolveBtwShortcuts(
|
|
1740
|
+
settings.keybindings,
|
|
1741
|
+
keybindings,
|
|
1742
|
+
effectiveFullscreenCopyOnSelect(settings)
|
|
1743
|
+
);
|
|
1744
|
+
const configured = settings.keybindings?.[action];
|
|
1745
|
+
if (configured !== void 0 && !effective.keys[action].includes(configured)) {
|
|
1746
|
+
return `Fallback (${effective.label(action)}; saved ${formatKeyLabel2(configured)})`;
|
|
1747
|
+
}
|
|
1748
|
+
const source = configured === void 0 ? action === "cycleThinkingLevel" ? "Inherit Pi" : "Default" : "Custom";
|
|
1749
|
+
return `${source} (${effective.label(action)})`;
|
|
1750
|
+
};
|
|
1751
|
+
const saveShortcut = async (state, value, signal) => {
|
|
1752
|
+
if (!keybindings || state.kind !== "valid" || signal.aborted)
|
|
1753
|
+
return { kind: "rejected" };
|
|
1754
|
+
const action = shortcut;
|
|
1755
|
+
const manager = keybindings;
|
|
1756
|
+
const validate = (settings) => validateBtwShortcutEdit(
|
|
1757
|
+
action,
|
|
1758
|
+
value,
|
|
1759
|
+
settings.keybindings ?? {},
|
|
1760
|
+
manager,
|
|
1761
|
+
effectiveFullscreenCopyOnSelect(settings)
|
|
1762
|
+
);
|
|
1763
|
+
const error = validate(state.settings);
|
|
1764
|
+
if (error) {
|
|
1765
|
+
notifySafely(ctx, error, "error");
|
|
1766
|
+
return { kind: "rejected" };
|
|
1767
|
+
}
|
|
1768
|
+
try {
|
|
1769
|
+
await updateSettings(
|
|
1770
|
+
{ keybindings: { [action]: value === void 0 ? void 0 : normalizeBtwKey(value) } },
|
|
1771
|
+
{
|
|
1772
|
+
settingsPath,
|
|
1773
|
+
signal,
|
|
1774
|
+
validateCurrent: (settings) => {
|
|
1775
|
+
const conflict = validate(settings);
|
|
1776
|
+
if (conflict) throw new Error(conflict);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
);
|
|
1780
|
+
if (signal.aborted) return { kind: "rejected" };
|
|
1781
|
+
notifySafely(ctx, "Pi BTW shortcut saved; applies when opening or resuming BTW.", "info");
|
|
1782
|
+
return { kind: "back" };
|
|
1783
|
+
} catch (error2) {
|
|
1784
|
+
if (!signal.aborted) notifySaveFailure(ctx, error2);
|
|
1785
|
+
return { kind: "rejected" };
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1428
1788
|
const loadState = async () => {
|
|
1429
1789
|
const loaded = await readSettings(settingsPath);
|
|
1430
1790
|
if (loaded.kind === "invalid") {
|
|
@@ -1476,7 +1836,7 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1476
1836
|
{
|
|
1477
1837
|
id: "settings",
|
|
1478
1838
|
label: "Settings",
|
|
1479
|
-
description: "Choose thinking,
|
|
1839
|
+
description: "Choose thinking, keybindings, and selection copying",
|
|
1480
1840
|
to: state.kind === "invalid" ? "invalid" : "settings"
|
|
1481
1841
|
}
|
|
1482
1842
|
],
|
|
@@ -1523,9 +1883,34 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1523
1883
|
currentValue: effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off",
|
|
1524
1884
|
values: ["On", "Off"],
|
|
1525
1885
|
action: "set-fullscreen-copy"
|
|
1526
|
-
}
|
|
1886
|
+
},
|
|
1887
|
+
...BTW_SHORTCUT_ACTIONS.map((action) => ({
|
|
1888
|
+
id: action,
|
|
1889
|
+
label: shortcutLabels[action],
|
|
1890
|
+
description: "Edit a BTW-only key combination or restore its default. Ctrl+C always hard-cancels.",
|
|
1891
|
+
currentValue: shortcutValue(state.settings, action),
|
|
1892
|
+
action: "edit-shortcut"
|
|
1893
|
+
}))
|
|
1527
1894
|
]
|
|
1528
1895
|
}),
|
|
1896
|
+
shortcut: ({ state }) => ({
|
|
1897
|
+
kind: "actions",
|
|
1898
|
+
title: shortcutLabels[shortcut],
|
|
1899
|
+
lines: [shortcutValue(state.settings, shortcut), "Ctrl+C always hard-cancels BTW."],
|
|
1900
|
+
items: [
|
|
1901
|
+
{ id: "edit", label: "Edit key combination\u2026", to: "shortcut-input" },
|
|
1902
|
+
{ id: "reset", label: "Restore default", action: "reset-shortcut" }
|
|
1903
|
+
],
|
|
1904
|
+
hint: "back"
|
|
1905
|
+
}),
|
|
1906
|
+
"shortcut-input": () => ({
|
|
1907
|
+
kind: "input",
|
|
1908
|
+
title: shortcutLabels[shortcut],
|
|
1909
|
+
lines: ["Type a key name, not the shortcut itself. For example: ctrl+q or f6."],
|
|
1910
|
+
placeholder: "Key combination",
|
|
1911
|
+
action: "save-shortcut",
|
|
1912
|
+
hint: "back"
|
|
1913
|
+
}),
|
|
1529
1914
|
invalid: ({ state }) => ({
|
|
1530
1915
|
kind: "detail",
|
|
1531
1916
|
title: "Pi BTW Settings \xB7 Read only",
|
|
@@ -1537,6 +1922,14 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1537
1922
|
})
|
|
1538
1923
|
},
|
|
1539
1924
|
actions: {
|
|
1925
|
+
"edit-shortcut": ({ itemId }) => {
|
|
1926
|
+
if (!BTW_SHORTCUT_ACTIONS.includes(itemId))
|
|
1927
|
+
return { kind: "rejected" };
|
|
1928
|
+
shortcut = itemId;
|
|
1929
|
+
return { kind: "to", screen: "shortcut" };
|
|
1930
|
+
},
|
|
1931
|
+
"save-shortcut": ({ state, value, signal }) => saveShortcut(state, value?.trim() ?? "", signal),
|
|
1932
|
+
"reset-shortcut": ({ state, signal }) => saveShortcut(state, void 0, signal),
|
|
1540
1933
|
start: async () => {
|
|
1541
1934
|
startSelected = true;
|
|
1542
1935
|
return { kind: "close" };
|
|
@@ -1600,7 +1993,10 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1600
1993
|
});
|
|
1601
1994
|
const result = await runBtwMenuPreservingEditor(
|
|
1602
1995
|
ctx,
|
|
1603
|
-
(menuContext) => runMenu(menuContext, menu, { getState: loadState })
|
|
1996
|
+
(menuContext) => runMenu(menuContext, menu, { getState: loadState }),
|
|
1997
|
+
(manager) => {
|
|
1998
|
+
keybindings = manager;
|
|
1999
|
+
}
|
|
1604
2000
|
);
|
|
1605
2001
|
if (result.kind !== "closed" || result.reason !== "close") return "closed";
|
|
1606
2002
|
if (resumedThreadId) return { kind: "resume", threadId: resumedThreadId };
|
|
@@ -1628,23 +2024,23 @@ async function showBtwCustomPreservingEditor(ctx, factory) {
|
|
|
1628
2024
|
}
|
|
1629
2025
|
return result;
|
|
1630
2026
|
}
|
|
1631
|
-
async function runBtwMenuPreservingEditor(ctx, run) {
|
|
2027
|
+
async function runBtwMenuPreservingEditor(ctx, run, onKeybindings) {
|
|
1632
2028
|
let liveEditorText = ctx.ui.getEditorText();
|
|
1633
2029
|
let completed = false;
|
|
1634
2030
|
const ui = new Proxy(ctx.ui, {
|
|
1635
2031
|
get(target, property) {
|
|
1636
2032
|
if (property === "custom") {
|
|
1637
|
-
return (factory, customOptions) => target.custom(
|
|
1638
|
-
(
|
|
2033
|
+
return (factory, customOptions) => target.custom((tui, theme, keybindings, done) => {
|
|
2034
|
+
onKeybindings?.(keybindings);
|
|
2035
|
+
return factory(tui, theme, keybindings, (value2) => {
|
|
1639
2036
|
try {
|
|
1640
2037
|
liveEditorText = target.getEditorText();
|
|
1641
2038
|
} catch {
|
|
1642
2039
|
}
|
|
1643
2040
|
completed = true;
|
|
1644
2041
|
done(value2);
|
|
1645
|
-
})
|
|
1646
|
-
|
|
1647
|
-
);
|
|
2042
|
+
});
|
|
2043
|
+
}, customOptions);
|
|
1648
2044
|
}
|
|
1649
2045
|
const value = Reflect.get(target, property, target);
|
|
1650
2046
|
return typeof value === "function" ? value.bind(target) : value;
|
|
@@ -2030,6 +2426,7 @@ var BtwTranscriptPager = class {
|
|
|
2030
2426
|
this.theme = theme;
|
|
2031
2427
|
this.onAction = onAction;
|
|
2032
2428
|
this.options = options;
|
|
2429
|
+
this.shortcuts = getBtwShortcuts(tui, options.thinking?.keybindings);
|
|
2033
2430
|
this.transcriptComponents = buildTranscriptComponents(turns, this.theme);
|
|
2034
2431
|
this.canBringToMain = turns.some((turn) => turn.kind === "answered");
|
|
2035
2432
|
this.thinkingLevel = options.thinking?.level;
|
|
@@ -2073,6 +2470,8 @@ var BtwTranscriptPager = class {
|
|
|
2073
2470
|
theme;
|
|
2074
2471
|
onAction;
|
|
2075
2472
|
options;
|
|
2473
|
+
shortcuts;
|
|
2474
|
+
pasteGuard = new BtwPasteGuard();
|
|
2076
2475
|
transcriptComponents;
|
|
2077
2476
|
editor;
|
|
2078
2477
|
canBringToMain;
|
|
@@ -2094,6 +2493,7 @@ var BtwTranscriptPager = class {
|
|
|
2094
2493
|
return this.layoutRoot;
|
|
2095
2494
|
}
|
|
2096
2495
|
render(width) {
|
|
2496
|
+
if (width <= 0) return [];
|
|
2097
2497
|
const safeWidth = Math.max(1, width);
|
|
2098
2498
|
const editorLines = this.editor.render(safeWidth);
|
|
2099
2499
|
const availableRows = Math.max(1, this.tui.terminal.rows - RESERVED_APP_LINES);
|
|
@@ -2114,22 +2514,27 @@ var BtwTranscriptPager = class {
|
|
|
2114
2514
|
this.renderFooter(safeWidth),
|
|
2115
2515
|
editorLines,
|
|
2116
2516
|
availableRows
|
|
2117
|
-
);
|
|
2517
|
+
).map((line) => truncateToWidth3(line, safeWidth));
|
|
2118
2518
|
}
|
|
2119
2519
|
handleInput(data) {
|
|
2120
2520
|
if (this.finished) return;
|
|
2121
|
-
if (
|
|
2521
|
+
if (this.pasteGuard.consume(data)) {
|
|
2522
|
+
this.editor.handleInput(data);
|
|
2523
|
+
this.tui.requestRender();
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
if (this.shortcuts.matches(data, "exit")) {
|
|
2122
2527
|
this.finished = true;
|
|
2123
2528
|
this.onAction({ kind: "close" });
|
|
2124
2529
|
return;
|
|
2125
2530
|
}
|
|
2126
|
-
if (this.canBringToMain &&
|
|
2531
|
+
if (this.canBringToMain && this.shortcuts.matches(data, "bringToMain")) {
|
|
2127
2532
|
this.finished = true;
|
|
2128
2533
|
this.onAction({ kind: "bringToMain", questionDraft: this.editor.getExpandedText() });
|
|
2129
2534
|
return;
|
|
2130
2535
|
}
|
|
2131
2536
|
const thinking = this.options.thinking;
|
|
2132
|
-
if (thinking && thinking.levels.length > 1 &&
|
|
2537
|
+
if (thinking && thinking.levels.length > 1 && this.shortcuts.matches(data, "cycleThinkingLevel")) {
|
|
2133
2538
|
const currentIndex = thinking.levels.indexOf(this.thinkingLevel ?? thinking.level);
|
|
2134
2539
|
const nextLevel = thinking.levels[(currentIndex + 1) % thinking.levels.length];
|
|
2135
2540
|
if (nextLevel) {
|
|
@@ -2162,23 +2567,26 @@ var BtwTranscriptPager = class {
|
|
|
2162
2567
|
this.onAction({ kind: "close" });
|
|
2163
2568
|
}
|
|
2164
2569
|
renderFooter(width) {
|
|
2570
|
+
const exit = this.shortcuts.label("exit");
|
|
2571
|
+
const bring = this.canBringToMain && this.shortcuts.keys.bringToMain.length > 0;
|
|
2572
|
+
const bringKey = this.shortcuts.label("bringToMain");
|
|
2165
2573
|
if (this.warning) {
|
|
2166
|
-
const warning = width < 32 ?
|
|
2574
|
+
const warning = width < 32 ? `Empty \u2022 ${exit}` : `${this.warning} \u2022 ${exit} exit`;
|
|
2167
2575
|
return truncateToWidth3(this.theme.fg("warning", warning), width);
|
|
2168
2576
|
}
|
|
2169
2577
|
const scrollable = this.getMaxScrollOffset() > 0;
|
|
2170
2578
|
const thinking = this.options.thinking;
|
|
2171
|
-
const cycleHint = thinking && thinking.levels.length > 1 && this.thinkingLevel ? ` \u2022 thinking ${this.thinkingLevel} \u2022 ${
|
|
2172
|
-
const base =
|
|
2579
|
+
const cycleHint = thinking && thinking.levels.length > 1 && this.thinkingLevel && this.shortcuts.keys.cycleThinkingLevel.length ? ` \u2022 thinking ${this.thinkingLevel} \u2022 ${this.shortcuts.label("cycleThinkingLevel")} cycle` : "";
|
|
2580
|
+
const base = bring ? `btw \u2022 Enter send \u2022 ${bringKey} bring to main \u2022 ${exit} exit` : `btw \u2022 Enter send \u2022 ${exit} exit`;
|
|
2173
2581
|
const fullBase = `${base}${cycleHint}`;
|
|
2174
|
-
const fallbackBase =
|
|
2175
|
-
const compactBase =
|
|
2582
|
+
const fallbackBase = `btw \u2022 Enter \u2022 ${exit}`;
|
|
2583
|
+
const compactBase = bring ? `btw \u2022 Enter \u2022 ${bringKey} \u2022 ${exit}` : fallbackBase;
|
|
2176
2584
|
const compactWithThinking = `${compactBase}${cycleHint}`;
|
|
2177
2585
|
let hints = visibleWidth2(fullBase) <= width ? fullBase : visibleWidth2(compactWithThinking) <= width ? compactWithThinking : visibleWidth2(compactBase) <= width ? compactBase : fallbackBase;
|
|
2178
2586
|
if (scrollable) {
|
|
2179
2587
|
const history = ` \u2022 ${this.scrollView.scrollTop > 0 ? "\u2191 older" : "\u2193 newer"} \u2022 PgUp/PgDn history`;
|
|
2180
2588
|
const compactHistory = " \u2022 PgUp/PgDn";
|
|
2181
|
-
const compactScrollable =
|
|
2589
|
+
const compactScrollable = bring ? `Enter \u2022 ${bringKey} \u2022 ${exit} \u2022 PgUp/PgDn` : `${fallbackBase}${compactHistory}`;
|
|
2182
2590
|
if (visibleWidth2(`${hints}${history}`) <= width) {
|
|
2183
2591
|
hints += history;
|
|
2184
2592
|
} else if (visibleWidth2(`${compactBase}${history}`) <= width) {
|
|
@@ -2229,6 +2637,7 @@ var BtwAnsweringView = class {
|
|
|
2229
2637
|
this.theme = theme;
|
|
2230
2638
|
this.onCancel = onCancel;
|
|
2231
2639
|
this.options = options;
|
|
2640
|
+
this.shortcuts = getBtwShortcuts(tui, options.steering?.thinking?.keybindings);
|
|
2232
2641
|
this.transcriptComponents = buildTranscriptComponents(turns, this.theme, pendingQuestion);
|
|
2233
2642
|
this.thinkingLevel = options.steering?.thinking?.level ?? thinkingLevel;
|
|
2234
2643
|
this.loader = new Loader(
|
|
@@ -2282,6 +2691,8 @@ var BtwAnsweringView = class {
|
|
|
2282
2691
|
theme;
|
|
2283
2692
|
onCancel;
|
|
2284
2693
|
options;
|
|
2694
|
+
shortcuts;
|
|
2695
|
+
pasteGuard = new BtwPasteGuard();
|
|
2285
2696
|
transcriptComponents;
|
|
2286
2697
|
loader;
|
|
2287
2698
|
editor;
|
|
@@ -2307,6 +2718,7 @@ var BtwAnsweringView = class {
|
|
|
2307
2718
|
return this.layoutRoot;
|
|
2308
2719
|
}
|
|
2309
2720
|
render(width) {
|
|
2721
|
+
if (width <= 0) return [];
|
|
2310
2722
|
const safeWidth = Math.max(1, width);
|
|
2311
2723
|
const availableRows = Math.max(1, this.tui.terminal.rows - RESERVED_APP_LINES);
|
|
2312
2724
|
const editorLines = this.editor?.render(safeWidth) ?? [];
|
|
@@ -2338,11 +2750,16 @@ var BtwAnsweringView = class {
|
|
|
2338
2750
|
editorLines,
|
|
2339
2751
|
availableRows,
|
|
2340
2752
|
steeringLines
|
|
2341
|
-
);
|
|
2753
|
+
).map((line) => truncateToWidth3(line, safeWidth));
|
|
2342
2754
|
}
|
|
2343
2755
|
handleInput(data) {
|
|
2344
2756
|
if (this.finished) return;
|
|
2345
|
-
if (
|
|
2757
|
+
if (this.pasteGuard.consume(data)) {
|
|
2758
|
+
this.editor?.handleInput(data);
|
|
2759
|
+
this.tui.requestRender();
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
if (this.shortcuts.matches(data, "exit")) {
|
|
2346
2763
|
this.finished = true;
|
|
2347
2764
|
this.loader.stop();
|
|
2348
2765
|
this.controller.abort();
|
|
@@ -2350,7 +2767,7 @@ var BtwAnsweringView = class {
|
|
|
2350
2767
|
return;
|
|
2351
2768
|
}
|
|
2352
2769
|
const thinking = this.options.steering?.thinking;
|
|
2353
|
-
if (thinking && thinking.levels.length > 1 &&
|
|
2770
|
+
if (thinking && thinking.levels.length > 1 && this.shortcuts.matches(data, "cycleThinkingLevel")) {
|
|
2354
2771
|
const currentIndex = thinking.levels.indexOf(this.thinkingLevel ?? thinking.level);
|
|
2355
2772
|
const nextLevel = thinking.levels[(currentIndex + 1) % thinking.levels.length];
|
|
2356
2773
|
if (nextLevel) {
|
|
@@ -2393,16 +2810,17 @@ var BtwAnsweringView = class {
|
|
|
2393
2810
|
this.onCancel();
|
|
2394
2811
|
}
|
|
2395
2812
|
renderFooter(width) {
|
|
2813
|
+
const exit = this.shortcuts.label("exit");
|
|
2396
2814
|
if (this.warning) {
|
|
2397
|
-
const warning = width < 32 ?
|
|
2815
|
+
const warning = width < 32 ? `Empty \u2022 ${exit}` : `${this.warning} \u2022 ${exit} cancel`;
|
|
2398
2816
|
return truncateToWidth3(this.theme.fg("warning", warning), width);
|
|
2399
2817
|
}
|
|
2400
|
-
const baseHint = this.editor ?
|
|
2818
|
+
const baseHint = this.editor ? `Enter steer \u2022 ${exit} cancel` : `${exit} cancel`;
|
|
2401
2819
|
const thinking = this.options.steering?.thinking;
|
|
2402
|
-
const cycleHint = thinking && thinking.levels.length > 1 && this.thinkingLevel ? ` \u2022 thinking ${this.thinkingLevel} \u2022 ${
|
|
2820
|
+
const cycleHint = thinking && thinking.levels.length > 1 && this.thinkingLevel && this.shortcuts.keys.cycleThinkingLevel.length ? ` \u2022 thinking ${this.thinkingLevel} \u2022 ${this.shortcuts.label("cycleThinkingLevel")} cycle` : "";
|
|
2403
2821
|
const scrollHint = this.getMaxScrollOffset() > 0 ? " \u2022 PgUp/PgDn history" : "";
|
|
2404
2822
|
const hints = `${baseHint}${cycleHint}${scrollHint}`;
|
|
2405
|
-
const compactHints = this.editor ?
|
|
2823
|
+
const compactHints = this.editor ? `Enter \u2022 ${exit}` : exit;
|
|
2406
2824
|
const selectedHints = visibleWidth2(hints) <= width ? hints : compactHints;
|
|
2407
2825
|
const loaderWidth = Math.max(1, width - visibleWidth2(selectedHints) - 3);
|
|
2408
2826
|
const loaderLine = this.loader.render(loaderWidth).at(-1) ?? "Answering\u2026";
|
|
@@ -2490,9 +2908,6 @@ function renderSideThreadHeader(width, theme, thinkingLevel) {
|
|
|
2490
2908
|
const ruleWidth = Math.max(0, width - visibleWidth2(title));
|
|
2491
2909
|
return theme.fg("muted", `${title}${"\u2500".repeat(ruleWidth)}`);
|
|
2492
2910
|
}
|
|
2493
|
-
function thinkingKeyLabel(keybindings) {
|
|
2494
|
-
return formatKeyLabel2(String(keybindings.getKeys("app.thinking.cycle")[0] ?? "shift+tab")) || "Shift+Tab";
|
|
2495
|
-
}
|
|
2496
2911
|
function fitComposerLayout(header, contentLines, footer, editorLines, availableRows, statusLines = []) {
|
|
2497
2912
|
const lines = [header, ...contentLines, ...statusLines, footer, ...editorLines];
|
|
2498
2913
|
if (lines.length <= availableRows) return lines;
|
|
@@ -2615,6 +3030,12 @@ function providerHeadersHaveValue(headers) {
|
|
|
2615
3030
|
function formatError5(error) {
|
|
2616
3031
|
return error instanceof Error ? error.message : String(error);
|
|
2617
3032
|
}
|
|
3033
|
+
function readBtwSessionId(ctx) {
|
|
3034
|
+
const getSessionId = ctx.sessionManager.getSessionId;
|
|
3035
|
+
if (typeof getSessionId !== "function") return void 0;
|
|
3036
|
+
const sessionId = getSessionId.call(ctx.sessionManager);
|
|
3037
|
+
return sessionId.length > 0 ? sessionId : void 0;
|
|
3038
|
+
}
|
|
2618
3039
|
function notifySafely3(ctx, message, level) {
|
|
2619
3040
|
try {
|
|
2620
3041
|
ctx.ui.notify(sanitizeSingleLine(message), level);
|
|
@@ -2720,7 +3141,10 @@ function btw(pi, dependencies = {}) {
|
|
|
2720
3141
|
ctx: fullscreenCtx
|
|
2721
3142
|
});
|
|
2722
3143
|
},
|
|
2723
|
-
{
|
|
3144
|
+
{
|
|
3145
|
+
copyOnSelect: effectiveFullscreenCopyOnSelect(settings),
|
|
3146
|
+
...settings.keybindings ? { keybindings: settings.keybindings } : {}
|
|
3147
|
+
}
|
|
2724
3148
|
);
|
|
2725
3149
|
} finally {
|
|
2726
3150
|
if (state?.title && state.thread.turns.length > 0) {
|
|
@@ -3149,7 +3573,8 @@ async function askThreadQuestion(thread, question, selected, thinkingLevel, ctx,
|
|
|
3149
3573
|
thinkingLevel,
|
|
3150
3574
|
auth: selected.auth,
|
|
3151
3575
|
signal: view.signal,
|
|
3152
|
-
completeSimple: createModelRegistryCompleteSimple(ctx.modelRegistry)
|
|
3576
|
+
completeSimple: createModelRegistryCompleteSimple(ctx.modelRegistry),
|
|
3577
|
+
sessionId: readBtwSessionId(ctx)
|
|
3153
3578
|
}).then((result) => {
|
|
3154
3579
|
if (settled) return;
|
|
3155
3580
|
settled = true;
|