@firstpick/pi-package-webui 0.4.2 → 0.4.4
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 +10 -6
- package/bin/pi-webui.mjs +100 -15
- package/{WEBUI_TUI_NATIVE_PARITY.json → dev/docs/WEBUI_TUI_NATIVE_PARITY.json} +2 -2
- package/package.json +10 -6
- package/public/app.js +1000 -50
- package/public/index.html +20 -5
- package/public/styles.css +322 -56
- package/tests/http-endpoints-harness.test.mjs +2 -0
- package/tests/mobile-static.test.mjs +90 -19
- package/tests/native-parity-harness.test.mjs +1 -1
- package/tests/native-parity.test.mjs +2 -2
- package/tests/remote-auth-settings-harness.test.mjs +81 -0
- package/start-webui.ps1 +0 -368
- package/start-webui.sh +0 -472
package/public/app.js
CHANGED
|
@@ -58,6 +58,7 @@ const elements = {
|
|
|
58
58
|
skillEditorCancelButton: $("#skillEditorCancelButton"),
|
|
59
59
|
skillEditorSaveButton: $("#skillEditorSaveButton"),
|
|
60
60
|
sendButton: $("#sendButton"),
|
|
61
|
+
btwButton: $("#btwButton"),
|
|
61
62
|
commandSuggest: $("#commandSuggest"),
|
|
62
63
|
attachmentTray: $("#attachmentTray"),
|
|
63
64
|
attachButton: $("#attachButton"),
|
|
@@ -85,6 +86,7 @@ const elements = {
|
|
|
85
86
|
optionsCommandPaletteButton: $("#optionsCommandPaletteButton"),
|
|
86
87
|
optionsResumeButton: $("#optionsResumeButton"),
|
|
87
88
|
optionsReloadButton: $("#optionsReloadButton"),
|
|
89
|
+
optionsRemoteButton: $("#optionsRemoteButton"),
|
|
88
90
|
optionsNameButton: $("#optionsNameButton"),
|
|
89
91
|
optionsCloneButton: $("#optionsCloneButton"),
|
|
90
92
|
optionsSettingsButton: $("#optionsSettingsButton"),
|
|
@@ -197,6 +199,7 @@ const elements = {
|
|
|
197
199
|
commandPaletteInput: $("#commandPaletteInput"),
|
|
198
200
|
commandPaletteList: $("#commandPaletteList"),
|
|
199
201
|
commandPaletteHint: $("#commandPaletteHint"),
|
|
202
|
+
commandPaletteCloseButton: $("#commandPaletteCloseButton"),
|
|
200
203
|
editRetryDialog: $("#editRetryDialog"),
|
|
201
204
|
editRetryMessage: $("#editRetryMessage"),
|
|
202
205
|
editRetryText: $("#editRetryText"),
|
|
@@ -299,6 +302,11 @@ let statsOverlayLastScope = "14";
|
|
|
299
302
|
let statsOverlayCalibrationMessage = "";
|
|
300
303
|
let statsOverlayCalibrationBusy = "";
|
|
301
304
|
let latestStatsOverlayPayload = null;
|
|
305
|
+
let latestBtwWidgetPayload = null;
|
|
306
|
+
let btwWidgetDismissedId = "";
|
|
307
|
+
let btwWidgetComposerOpen = false;
|
|
308
|
+
let btwWidgetInputDraft = "";
|
|
309
|
+
let btwWidgetFocusAfterRender = false;
|
|
302
310
|
let latestWorkspace = null;
|
|
303
311
|
let latestNetwork = null;
|
|
304
312
|
let webuiVersion = "";
|
|
@@ -316,6 +324,8 @@ let updateStatusRefreshTimer = null;
|
|
|
316
324
|
let updateNotificationHideTimer = null;
|
|
317
325
|
let backendOfflineNoticeShown = false;
|
|
318
326
|
let latestMessages = [];
|
|
327
|
+
let latestMessagesSessionKey = "";
|
|
328
|
+
const tabMessagesCache = new Map();
|
|
319
329
|
let promptHistoryByTab = new Map();
|
|
320
330
|
let promptHistoryNavigation = null;
|
|
321
331
|
let transientMessages = [];
|
|
@@ -340,6 +350,8 @@ let toolOutputGloballyExpanded = false;
|
|
|
340
350
|
let agentDoneNotificationPermissionRequested = false;
|
|
341
351
|
let agentDoneNotificationFallbackNoted = false;
|
|
342
352
|
let agentDoneNotificationKeys = new Set();
|
|
353
|
+
let pendingAgentDoneNotificationTimers = new Map();
|
|
354
|
+
let autoRetryingTabs = new Set();
|
|
343
355
|
let availableModels = [];
|
|
344
356
|
let availableThemes = [];
|
|
345
357
|
let currentThemeName = "catppuccin-mocha";
|
|
@@ -374,7 +386,17 @@ let workspaceDashboardCollapsed = false;
|
|
|
374
386
|
let commandPaletteIndex = 0;
|
|
375
387
|
let commandPaletteItems = [];
|
|
376
388
|
let activeEditRetry = null;
|
|
389
|
+
let activePointerActivation = null;
|
|
390
|
+
let pointerActivationTimeout = null;
|
|
391
|
+
let deferredChatFollowScroll = false;
|
|
392
|
+
const deferredUiRenderCallbacks = new Map();
|
|
377
393
|
let abortLongPressTimer = null;
|
|
394
|
+
let abortLongPressTickTimer = null;
|
|
395
|
+
let abortLongPressResetTimer = null;
|
|
396
|
+
let abortLongPressStartedAt = 0;
|
|
397
|
+
let abortLongPressDeadlineAt = 0;
|
|
398
|
+
let abortLongPressSource = "long-press";
|
|
399
|
+
let abortLongPressReleasePending = false;
|
|
378
400
|
let abortLongPressHandled = false;
|
|
379
401
|
const dialogQueue = [];
|
|
380
402
|
const SIDE_PANEL_STORAGE_KEY = "pi-webui-side-panel-collapsed";
|
|
@@ -407,11 +429,21 @@ const GIT_INIT_STACK_STORAGE_KEY = "pi-webui-git-init-stack";
|
|
|
407
429
|
const STATS_WEBUI_STATUS_KEY = "stats-webui";
|
|
408
430
|
const STATS_WEBUI_PAYLOAD_TYPE = "firstpick.pi-extension-stats.overlay";
|
|
409
431
|
const STATS_WEBUI_PAYLOAD_VERSION = 1;
|
|
432
|
+
const BTW_WEBUI_STATUS_KEY = "btw-webui";
|
|
433
|
+
const BTW_OUTPUT_WIDGET_KEY = "btw:output";
|
|
434
|
+
const BTW_FOOTER_WIDGET_KEY = "btw:footer";
|
|
435
|
+
const BTW_WIDGET_PAYLOAD_PREFIX = "BTW_WEBUI_PAYLOAD ";
|
|
436
|
+
const BTW_WEBUI_PAYLOAD_TYPES = new Set(["firstpick.pi-extension-btw.overlay", "firstpick.pi-extension-btw.output"]);
|
|
437
|
+
const WORKFLOW_WIDGET_PAYLOAD_PREFIX = "WORKFLOW_WEBUI_PAYLOAD ";
|
|
438
|
+
const WORKFLOW_SUBPROCESS_PAYLOAD_TYPE = "firstpick.pi-extension-workflows.subprocess";
|
|
439
|
+
const WORKFLOW_SUBPROCESS_PAYLOAD_VERSION = 1;
|
|
410
440
|
const GIT_CHANGES_RENDER_ROW_LIMIT = 4000;
|
|
411
441
|
const LAST_USER_PROMPT_STORAGE_KEY = "pi-webui-last-user-prompts";
|
|
412
442
|
const PROMPT_HISTORY_STORAGE_KEY = "pi-webui-prompt-history";
|
|
413
443
|
const PROMPT_LIST_STORAGE_KEY = "pi-webui-prompt-lists";
|
|
414
444
|
const WORKSPACE_DASHBOARD_STORAGE_KEY = "pi-webui-workspace-dashboard-collapsed";
|
|
445
|
+
const POINTER_ACTIVATION_SELECTOR = "button, a[href], input, select, textarea, summary, [role='button'], [tabindex]:not([tabindex='-1'])";
|
|
446
|
+
const POINTER_ACTIVATION_RENDER_DEFER_MAX_MS = 1200;
|
|
415
447
|
const PROMPT_HISTORY_LIMIT_PER_TAB = 50;
|
|
416
448
|
const ATTACHMENT_MAX_FILES = 12;
|
|
417
449
|
const ATTACHMENT_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
@@ -444,7 +476,9 @@ const UPDATE_STATUS_INITIAL_DELAY_MS = 1800;
|
|
|
444
476
|
const RUN_INDICATOR_TICK_MS = 1000;
|
|
445
477
|
const RUN_INDICATOR_START_GRACE_MS = 2500;
|
|
446
478
|
const RUN_INDICATOR_STATE_RECHECK_MS = 5000;
|
|
447
|
-
const ABORT_LONG_PRESS_MS =
|
|
479
|
+
const ABORT_LONG_PRESS_MS = 3000;
|
|
480
|
+
const ABORT_LONG_PRESS_TICK_MS = 100;
|
|
481
|
+
const ABORT_LONG_PRESS_RELEASE_GRACE_MS = 350;
|
|
448
482
|
const STREAM_OUTPUT_HIDE_DELAY_MS = 300;
|
|
449
483
|
const STREAM_OUTPUT_TOOLCALL_GUARD_MS = 220;
|
|
450
484
|
const STREAM_OUTPUT_MIN_VISIBLE_MS = 900;
|
|
@@ -454,6 +488,7 @@ const TODO_PROGRESS_LINE_REGEX = /^\s*(?:(?:[-*]|\d+[.)])\s*)?\[(?: |x|X|-)\]\s+
|
|
|
454
488
|
const TODO_PROGRESS_PARTIAL_LINE_REGEX = /^\s*(?:(?:[-*]|\d+[.)])\s*)?\[(?: |x|X|-)?\]?\s*.*$/;
|
|
455
489
|
const CHAT_SCROLL_KEYS = new Set(["ArrowDown", "ArrowUp", "End", "Home", "PageDown", "PageUp", " "]);
|
|
456
490
|
const TAB_ACTIVITY_IDLE_RECONCILE_GRACE_MS = 1200;
|
|
491
|
+
const AGENT_DONE_NOTIFICATION_RETRY_GRACE_MS = 1200;
|
|
457
492
|
const FOREGROUND_RECONCILE_DELAY_MS = 120;
|
|
458
493
|
const TAB_GROUP_STATUS_PRIORITY = ["blocked", "done", "working", "idle"];
|
|
459
494
|
const EXTENSION_UI_BLOCKING_METHODS = new Set(["select", "confirm", "input", "editor"]);
|
|
@@ -475,18 +510,28 @@ let liveToolRenderTimer = null;
|
|
|
475
510
|
// commands and live widget events), not npm package folders. This keeps local dev
|
|
476
511
|
// symlinks and independently installed packages working.
|
|
477
512
|
const optionalFeatureAvailability = {
|
|
513
|
+
btwCommand: false,
|
|
478
514
|
gitWorkflow: false,
|
|
479
515
|
releaseNpm: false,
|
|
480
516
|
releaseAur: false,
|
|
517
|
+
workflows: false,
|
|
481
518
|
safetyGuard: false,
|
|
482
519
|
statsCommand: false,
|
|
483
520
|
gitFooterStatus: false,
|
|
484
521
|
tuiSkillsCommand: false,
|
|
485
522
|
todoProgressWidget: false,
|
|
486
523
|
tuiToolsCommand: false,
|
|
524
|
+
remoteWebui: false,
|
|
487
525
|
themeBundle: false,
|
|
488
526
|
};
|
|
489
527
|
const OPTIONAL_FEATURES = [
|
|
528
|
+
{
|
|
529
|
+
id: "btwCommand",
|
|
530
|
+
label: "/btw side questions",
|
|
531
|
+
packageName: "@firstpick/pi-extension-btw",
|
|
532
|
+
capabilityLabel: "/btw or btw:output widget event",
|
|
533
|
+
description: "Ephemeral side-question command with TUI overlay and browser output-widget rendering.",
|
|
534
|
+
},
|
|
490
535
|
{
|
|
491
536
|
id: "gitWorkflow",
|
|
492
537
|
label: "Guided Git workflow",
|
|
@@ -508,6 +553,13 @@ const OPTIONAL_FEATURES = [
|
|
|
508
553
|
capabilityLabel: "/release-aur",
|
|
509
554
|
description: "Publish menu action, setup helpers, skills, and AUR release widgets.",
|
|
510
555
|
},
|
|
556
|
+
{
|
|
557
|
+
id: "workflows",
|
|
558
|
+
label: "Workflows",
|
|
559
|
+
packageName: "@firstpick/pi-extension-workflows",
|
|
560
|
+
capabilityLabel: "/workflow or workflow subprocess widget event",
|
|
561
|
+
description: "Modular workflow runner with live subprocess output shown in a non-blocking Web UI widget.",
|
|
562
|
+
},
|
|
511
563
|
{
|
|
512
564
|
id: "safetyGuard",
|
|
513
565
|
label: "Safety guard",
|
|
@@ -536,6 +588,13 @@ const OPTIONAL_FEATURES = [
|
|
|
536
588
|
capabilityLabel: "RPC /tools from tools extension",
|
|
537
589
|
description: "Terminal-native active-tool manager alongside WebUI-native /tools toggles.",
|
|
538
590
|
},
|
|
591
|
+
{
|
|
592
|
+
id: "remoteWebui",
|
|
593
|
+
label: "Remote WebUI",
|
|
594
|
+
packageName: "@firstpick/pi-package-remote-webui",
|
|
595
|
+
capabilityLabel: "/remote",
|
|
596
|
+
description: "Trusted-LAN QR helper for opening the Web UI from mobile browsers.",
|
|
597
|
+
},
|
|
539
598
|
{
|
|
540
599
|
id: "gitFooterStatus",
|
|
541
600
|
label: "Git footer status",
|
|
@@ -560,20 +619,28 @@ const OPTIONAL_FEATURES = [
|
|
|
560
619
|
];
|
|
561
620
|
const OPTIONAL_FEATURE_BY_ID = new Map(OPTIONAL_FEATURES.map((feature) => [feature.id, feature]));
|
|
562
621
|
const OPTIONAL_COMMAND_FEATURES = new Map([
|
|
622
|
+
["btw", "btwCommand"],
|
|
623
|
+
["btw-transfer", "btwCommand"],
|
|
624
|
+
["btw-status", "btwCommand"],
|
|
563
625
|
["git-staged-msg", "gitWorkflow"],
|
|
564
626
|
["git-branch-name", "gitWorkflow"],
|
|
565
627
|
["pr", "gitWorkflow"],
|
|
566
628
|
["release-npm", "releaseNpm"],
|
|
567
629
|
["release-aur", "releaseAur"],
|
|
630
|
+
["workflow", "workflows"],
|
|
631
|
+
["workflow-clear", "workflows"],
|
|
568
632
|
["safety-guard", "safetyGuard"],
|
|
569
633
|
["skills", "tuiSkillsCommand"],
|
|
570
634
|
["tools", "tuiToolsCommand"],
|
|
635
|
+
["remote", "remoteWebui"],
|
|
571
636
|
["stats", "statsCommand"],
|
|
572
637
|
["git-footer-refresh", "gitFooterStatus"],
|
|
573
638
|
["todo-progress-status", "todoProgressWidget"],
|
|
574
639
|
]);
|
|
575
640
|
const HIDDEN_COMMAND_NAMES = new Set(["webui-tree-navigate", "webui-helper"]);
|
|
576
641
|
HIDDEN_COMMAND_NAMES.add("stats-webui");
|
|
642
|
+
HIDDEN_COMMAND_NAMES.add("btw-status");
|
|
643
|
+
HIDDEN_COMMAND_NAMES.add("btw-transfer");
|
|
577
644
|
const NATIVE_SELECTOR_COMMANDS = new Set(["model", "settings", "theme", "fork", "clone", "name", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"]);
|
|
578
645
|
const SETTINGS_THINKING_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
579
646
|
const SETTINGS_TRANSPORT_OPTIONS = ["sse", "websocket", "websocket-cached", "auto"];
|
|
@@ -597,6 +664,7 @@ const optionalFeatureInstallInProgress = new Set();
|
|
|
597
664
|
const optionalFeaturePackageStatuses = new Map();
|
|
598
665
|
const optionalFeatureInstallMessages = new Map();
|
|
599
666
|
const gitFooterPayloadRefreshInFlightByTab = new Set();
|
|
667
|
+
const gitFooterPiCalibrationInFlightByTab = new Set();
|
|
600
668
|
|
|
601
669
|
function createGitWorkflowActionsDone(patch = {}) {
|
|
602
670
|
return {
|
|
@@ -826,6 +894,82 @@ function delay(ms) {
|
|
|
826
894
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
827
895
|
}
|
|
828
896
|
|
|
897
|
+
function activationControlFromEvent(event) {
|
|
898
|
+
const target = event?.target instanceof Element ? event.target : null;
|
|
899
|
+
const control = target?.closest?.(POINTER_ACTIVATION_SELECTOR);
|
|
900
|
+
if (!control || control === document.body || control === document.documentElement) return null;
|
|
901
|
+
if (control.disabled || control.getAttribute("aria-disabled") === "true") return null;
|
|
902
|
+
return control;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function shouldDeferUiRenderForPointerActivation() {
|
|
906
|
+
return Boolean(
|
|
907
|
+
activePointerActivation
|
|
908
|
+
&& performance.now() - activePointerActivation.startedAt <= POINTER_ACTIVATION_RENDER_DEFER_MAX_MS,
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function deferUiRenderDuringPointerActivation(key, callback) {
|
|
913
|
+
if (!shouldDeferUiRenderForPointerActivation()) return false;
|
|
914
|
+
deferredUiRenderCallbacks.set(key, callback);
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function deferChatFollowScrollDuringPointerActivation({ force = false } = {}) {
|
|
919
|
+
if (force || !shouldDeferUiRenderForPointerActivation()) return false;
|
|
920
|
+
deferredChatFollowScroll = true;
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function flushDeferredUiRenders() {
|
|
925
|
+
const callbacks = [...deferredUiRenderCallbacks.values()];
|
|
926
|
+
deferredUiRenderCallbacks.clear();
|
|
927
|
+
const shouldScroll = deferredChatFollowScroll;
|
|
928
|
+
deferredChatFollowScroll = false;
|
|
929
|
+
|
|
930
|
+
for (const callback of callbacks) {
|
|
931
|
+
try {
|
|
932
|
+
callback();
|
|
933
|
+
} catch (error) {
|
|
934
|
+
console.error("deferred Web UI render failed", error);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (shouldScroll) scrollChatToBottom();
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function beginPointerActivation(event) {
|
|
941
|
+
if (event?.button !== undefined && event.button !== 0) return;
|
|
942
|
+
const control = activationControlFromEvent(event);
|
|
943
|
+
if (!control) return;
|
|
944
|
+
clearTimeout(pointerActivationTimeout);
|
|
945
|
+
const activation = { pointerId: event.pointerId, startedAt: performance.now(), control };
|
|
946
|
+
activePointerActivation = activation;
|
|
947
|
+
pointerActivationTimeout = setTimeout(() => {
|
|
948
|
+
if (activePointerActivation === activation) activePointerActivation = null;
|
|
949
|
+
pointerActivationTimeout = null;
|
|
950
|
+
flushDeferredUiRenders();
|
|
951
|
+
}, POINTER_ACTIVATION_RENDER_DEFER_MAX_MS);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function finishPointerActivation(event) {
|
|
955
|
+
if (!activePointerActivation) return;
|
|
956
|
+
if (event?.pointerId !== undefined && activePointerActivation.pointerId !== event.pointerId) return;
|
|
957
|
+
const activation = activePointerActivation;
|
|
958
|
+
clearTimeout(pointerActivationTimeout);
|
|
959
|
+
pointerActivationTimeout = null;
|
|
960
|
+
setTimeout(() => {
|
|
961
|
+
if (activePointerActivation === activation) activePointerActivation = null;
|
|
962
|
+
flushDeferredUiRenders();
|
|
963
|
+
}, 0);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function cancelPointerActivation() {
|
|
967
|
+
clearTimeout(pointerActivationTimeout);
|
|
968
|
+
pointerActivationTimeout = null;
|
|
969
|
+
activePointerActivation = null;
|
|
970
|
+
flushDeferredUiRenders();
|
|
971
|
+
}
|
|
972
|
+
|
|
829
973
|
function isMobileView() {
|
|
830
974
|
return mobileViewMedia?.matches || false;
|
|
831
975
|
}
|
|
@@ -834,6 +978,36 @@ function isSidePanelOverlayView() {
|
|
|
834
978
|
return sidePanelOverlayMedia?.matches || false;
|
|
835
979
|
}
|
|
836
980
|
|
|
981
|
+
function mobileDropdownViewportHeight() {
|
|
982
|
+
return window.visualViewport?.height || window.innerHeight || document.documentElement.clientHeight || 0;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function mobileDropdownConfigs() {
|
|
986
|
+
return [
|
|
987
|
+
{ menu: elements.publishButton?.parentElement, button: elements.publishButton, panel: elements.publishButton?.parentElement?.querySelector(".composer-publish-menu-panel") },
|
|
988
|
+
{ menu: elements.nativeCommandMenuButton?.parentElement, button: elements.nativeCommandMenuButton, panel: elements.nativeCommandMenuButton?.parentElement?.querySelector(".composer-publish-menu-panel") },
|
|
989
|
+
{ menu: elements.optionsMenuButton?.parentElement, button: elements.optionsMenuButton, panel: elements.optionsMenu },
|
|
990
|
+
{ menu: elements.appRunnerMenu, button: elements.appRunnerMenuButton, panel: elements.appRunnerMenuPanel },
|
|
991
|
+
];
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function updateMobileDropdownScrollBounds() {
|
|
995
|
+
const viewportHeight = mobileDropdownViewportHeight();
|
|
996
|
+
for (const { menu, button, panel } of mobileDropdownConfigs()) {
|
|
997
|
+
if (!panel) continue;
|
|
998
|
+
panel.style.removeProperty("--mobile-dropdown-max-height");
|
|
999
|
+
if (!isMobileView() || !menu?.classList.contains("open") || !viewportHeight) continue;
|
|
1000
|
+
const anchorRect = (button || menu).getBoundingClientRect();
|
|
1001
|
+
const availableAbove = Math.floor(anchorRect.top - 8);
|
|
1002
|
+
const boundedHeight = Math.max(72, Math.min(viewportHeight - 16, availableAbove));
|
|
1003
|
+
panel.style.setProperty("--mobile-dropdown-max-height", `${boundedHeight}px`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function scheduleMobileDropdownScrollBoundsUpdate() {
|
|
1008
|
+
requestAnimationFrame(updateMobileDropdownScrollBounds);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
837
1011
|
function readStoredSidePanelCollapsed() {
|
|
838
1012
|
try {
|
|
839
1013
|
const stored = localStorage.getItem(SIDE_PANEL_STORAGE_KEY);
|
|
@@ -1592,6 +1766,7 @@ function setComposerActionsOpen(open) {
|
|
|
1592
1766
|
setOptionsMenuOpen(false);
|
|
1593
1767
|
setBusyPromptBehaviorMenuOpen(false);
|
|
1594
1768
|
}
|
|
1769
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
1595
1770
|
}
|
|
1596
1771
|
|
|
1597
1772
|
function isUserBashActive(tabId = activeTabId) {
|
|
@@ -1643,11 +1818,17 @@ function updateComposerModeButtons() {
|
|
|
1643
1818
|
button.hidden = !runActive;
|
|
1644
1819
|
button.disabled = !runActive;
|
|
1645
1820
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
elements.abortButton.
|
|
1649
|
-
elements.abortButton.
|
|
1650
|
-
|
|
1821
|
+
const abortHoldActive = isAbortLongPressActive();
|
|
1822
|
+
if (!abortAvailable && !abortHoldActive) resetAbortLongPressAffordance();
|
|
1823
|
+
elements.abortButton.hidden = !abortAvailable && !abortHoldActive;
|
|
1824
|
+
elements.abortButton.disabled = (!abortAvailable && !abortHoldActive) || abortRequestInFlight;
|
|
1825
|
+
if (abortHoldActive) {
|
|
1826
|
+
renderAbortLongPressAffordance();
|
|
1827
|
+
} else {
|
|
1828
|
+
elements.abortButton.textContent = abortRequestInFlight ? "Aborting…" : "Abort";
|
|
1829
|
+
elements.abortButton.title = abortAvailable ? abortButtonReadyTitle() : "Abort is available while Pi is running";
|
|
1830
|
+
elements.abortButton.setAttribute("aria-label", elements.abortButton.title);
|
|
1831
|
+
}
|
|
1651
1832
|
renderBusyPromptBehaviorTag();
|
|
1652
1833
|
document.body.classList.toggle("pi-run-active", runActive || abortAvailable);
|
|
1653
1834
|
}
|
|
@@ -1851,6 +2032,7 @@ function updateVisualViewportVars() {
|
|
|
1851
2032
|
syncMobileChatToBottomForInput();
|
|
1852
2033
|
}
|
|
1853
2034
|
updateFooterModelPickerPosition();
|
|
2035
|
+
updateMobileDropdownScrollBounds();
|
|
1854
2036
|
}
|
|
1855
2037
|
|
|
1856
2038
|
function installViewportHandlers() {
|
|
@@ -3367,6 +3549,15 @@ function setOptionalFeatureDisabled(featureId, disabled) {
|
|
|
3367
3549
|
statusEntries.delete(GIT_FOOTER_WEBUI_STATUS_KEY);
|
|
3368
3550
|
clearGitFooterWebuiPayloadCache();
|
|
3369
3551
|
}
|
|
3552
|
+
if (featureId === "btwCommand") {
|
|
3553
|
+
statusEntries.delete(BTW_WEBUI_STATUS_KEY);
|
|
3554
|
+
widgets.delete(BTW_OUTPUT_WIDGET_KEY);
|
|
3555
|
+
widgets.delete(BTW_FOOTER_WIDGET_KEY);
|
|
3556
|
+
latestBtwWidgetPayload = null;
|
|
3557
|
+
btwWidgetDismissedId = "";
|
|
3558
|
+
btwWidgetComposerOpen = false;
|
|
3559
|
+
btwWidgetInputDraft = "";
|
|
3560
|
+
}
|
|
3370
3561
|
storeDisabledOptionalFeatures();
|
|
3371
3562
|
renderOptionalFeatureDependentDisplays();
|
|
3372
3563
|
const tabContext = activeTabContext();
|
|
@@ -3726,11 +3917,17 @@ function syncTabMetadata(nextTabs = []) {
|
|
|
3726
3917
|
if (!liveIds.has(tabId)) {
|
|
3727
3918
|
tabActivities.delete(tabId);
|
|
3728
3919
|
tabSeenCompletionSerials.delete(tabId);
|
|
3920
|
+
autoRetryingTabs.delete(tabId);
|
|
3921
|
+
suppressPendingAgentDoneNotificationsForTab(tabId, { markSeen: false });
|
|
3729
3922
|
actionFeedbackByTab.delete(tabId);
|
|
3730
3923
|
skillUsageByTab.delete(tabId);
|
|
3924
|
+
tabMessagesCache.delete(tabId);
|
|
3731
3925
|
clearGitWorkflowForTab(tabId);
|
|
3732
3926
|
}
|
|
3733
3927
|
}
|
|
3928
|
+
for (const tabId of tabMessagesCache.keys()) {
|
|
3929
|
+
if (!liveIds.has(tabId)) tabMessagesCache.delete(tabId);
|
|
3930
|
+
}
|
|
3734
3931
|
pruneSkillUsageForKnownTabs(liveIds);
|
|
3735
3932
|
}
|
|
3736
3933
|
|
|
@@ -3894,6 +4091,18 @@ function ingestEventTabActivity(event) {
|
|
|
3894
4091
|
if (changed) renderTabs();
|
|
3895
4092
|
}
|
|
3896
4093
|
|
|
4094
|
+
function trackAutoRetryStateFromEvent(event) {
|
|
4095
|
+
const tabId = event?.tabId || activeTabId;
|
|
4096
|
+
if (!tabId) return;
|
|
4097
|
+
if (event.type === "auto_retry_start") {
|
|
4098
|
+
autoRetryingTabs.add(tabId);
|
|
4099
|
+
suppressPendingAgentDoneNotificationsForTab(tabId);
|
|
4100
|
+
markTabWorkingLocally(tabId);
|
|
4101
|
+
} else if (event.type === "auto_retry_end") {
|
|
4102
|
+
autoRetryingTabs.delete(tabId);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
|
|
3897
4106
|
function rememberActiveTab() {
|
|
3898
4107
|
try {
|
|
3899
4108
|
if (activeTabId) localStorage.setItem(TAB_STORAGE_KEY, activeTabId);
|
|
@@ -3983,8 +4192,13 @@ function resetActiveTabUi() {
|
|
|
3983
4192
|
currentState = null;
|
|
3984
4193
|
latestStats = null;
|
|
3985
4194
|
latestStatsOverlayPayload = null;
|
|
4195
|
+
latestBtwWidgetPayload = null;
|
|
4196
|
+
btwWidgetDismissedId = "";
|
|
4197
|
+
btwWidgetComposerOpen = false;
|
|
4198
|
+
btwWidgetInputDraft = "";
|
|
3986
4199
|
latestWorkspace = null;
|
|
3987
4200
|
latestMessages = [];
|
|
4201
|
+
latestMessagesSessionKey = "";
|
|
3988
4202
|
clearRunIndicatorActivity({ render: false });
|
|
3989
4203
|
statusEntries.clear();
|
|
3990
4204
|
widgets.clear();
|
|
@@ -4016,8 +4230,10 @@ function resetActiveTabUi() {
|
|
|
4016
4230
|
renderAppRunnerControls();
|
|
4017
4231
|
renderWidgets();
|
|
4018
4232
|
renderGitWorkflow();
|
|
4019
|
-
|
|
4020
|
-
|
|
4233
|
+
if (!restoreCachedMessagesForActiveTab()) {
|
|
4234
|
+
renderFooter();
|
|
4235
|
+
renderFeedbackTray();
|
|
4236
|
+
}
|
|
4021
4237
|
}
|
|
4022
4238
|
|
|
4023
4239
|
function tabGroupStatusRank(state) {
|
|
@@ -4256,6 +4472,7 @@ function moveNewTabMenuFocus(delta) {
|
|
|
4256
4472
|
}
|
|
4257
4473
|
|
|
4258
4474
|
function renderTabs() {
|
|
4475
|
+
if (deferUiRenderDuringPointerActivation("tabs", renderTabs)) return;
|
|
4259
4476
|
const active = activeTab();
|
|
4260
4477
|
const activeIndicator = active ? tabIndicator(active) : null;
|
|
4261
4478
|
elements.terminalTabsToggleButton.textContent = active ? `${activeIndicator.glyph} ${active.title}${tabs.length > 1 ? ` · ${tabs.length}` : ""}` : "Tabs";
|
|
@@ -4280,7 +4497,7 @@ function renderTabs() {
|
|
|
4280
4497
|
updateDocumentTitle();
|
|
4281
4498
|
renderWorkspaceDashboard();
|
|
4282
4499
|
renderContextMeter();
|
|
4283
|
-
if (elements.commandPaletteDialog?.open) renderCommandPalette();
|
|
4500
|
+
if (elements.commandPaletteDialog?.open) renderCommandPalette({ preserveScroll: true });
|
|
4284
4501
|
syncTabPolling();
|
|
4285
4502
|
}
|
|
4286
4503
|
|
|
@@ -4311,6 +4528,7 @@ async function switchTab(tabId) {
|
|
|
4311
4528
|
footerBranchPickerOpen = false;
|
|
4312
4529
|
footerBranchPickerRequestSerial += 1;
|
|
4313
4530
|
saveActiveDraft();
|
|
4531
|
+
cacheMessagesForTab(activeTabId);
|
|
4314
4532
|
const tabContext = setActiveTabId(tabId, { remember: true });
|
|
4315
4533
|
resetActiveTabUi();
|
|
4316
4534
|
renderTabs();
|
|
@@ -4379,6 +4597,20 @@ function tabHasActiveAgent(tab) {
|
|
|
4379
4597
|
return !!activity.isWorking || indicator.state === "working" || indicator.state === "blocked";
|
|
4380
4598
|
}
|
|
4381
4599
|
|
|
4600
|
+
function activeTabHasConversationMessages(tab = activeTab()) {
|
|
4601
|
+
const tabId = tab?.id || activeTabId;
|
|
4602
|
+
if (!tabId) return false;
|
|
4603
|
+
if (tabId !== activeTabId && !latestMessagesSessionKey.startsWith(`${tabId}|`)) return false;
|
|
4604
|
+
return latestMessages.some((message) => ["user", "assistant"].includes(message?.role));
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
function shouldOpenCwdChangeInNewTab(tab) {
|
|
4608
|
+
return !!tab?.conversationStarted
|
|
4609
|
+
|| activeTabHasConversationMessages(tab)
|
|
4610
|
+
|| stateHasVisibleWork(currentState)
|
|
4611
|
+
|| tabHasActiveAgent(tab);
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4382
4614
|
function confirmCloseTerminalTabs(targetTabs, label) {
|
|
4383
4615
|
const count = targetTabs.length;
|
|
4384
4616
|
const noun = count === 1 ? "tab" : "tabs";
|
|
@@ -4423,6 +4655,7 @@ async function closeTerminalTabs(tabIds, { label = "selected terminal tabs" } =
|
|
|
4423
4655
|
clearAttachments(id);
|
|
4424
4656
|
clearGitWorkflowForTab(id);
|
|
4425
4657
|
appRunnerDataByTab.delete(id);
|
|
4658
|
+
tabMessagesCache.delete(id);
|
|
4426
4659
|
}
|
|
4427
4660
|
clearOpenTerminalTabGroup(null, { force: true });
|
|
4428
4661
|
|
|
@@ -4689,6 +4922,36 @@ function agentDoneNotificationKey(tabId, activity = {}) {
|
|
|
4689
4922
|
return `${tabId}:${Number.isFinite(serial) && serial > 0 ? serial : "done"}`;
|
|
4690
4923
|
}
|
|
4691
4924
|
|
|
4925
|
+
function isAutoRetryingTab(tabId) {
|
|
4926
|
+
return !!tabId && autoRetryingTabs.has(tabId);
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4929
|
+
function clearPendingAgentDoneNotification(key, { markSeen = false } = {}) {
|
|
4930
|
+
const pending = pendingAgentDoneNotificationTimers.get(key);
|
|
4931
|
+
if (!pending) return false;
|
|
4932
|
+
clearTimeout(pending.timer);
|
|
4933
|
+
pendingAgentDoneNotificationTimers.delete(key);
|
|
4934
|
+
if (markSeen) agentDoneNotificationKeys.add(key);
|
|
4935
|
+
return true;
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
function suppressPendingAgentDoneNotificationsForTab(tabId, { markSeen = true } = {}) {
|
|
4939
|
+
if (!tabId) return;
|
|
4940
|
+
for (const [key, pending] of pendingAgentDoneNotificationTimers) {
|
|
4941
|
+
if (pending.tabId === tabId) clearPendingAgentDoneNotification(key, { markSeen });
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4945
|
+
function queueAgentDoneBrowserNotification({ key, tabId, title, body }) {
|
|
4946
|
+
clearPendingAgentDoneNotification(key);
|
|
4947
|
+
const timer = setTimeout(() => {
|
|
4948
|
+
pendingAgentDoneNotificationTimers.delete(key);
|
|
4949
|
+
if (isAutoRetryingTab(tabId)) return;
|
|
4950
|
+
showAgentDoneBrowserNotification({ tabId, title, body });
|
|
4951
|
+
}, AGENT_DONE_NOTIFICATION_RETRY_GRACE_MS);
|
|
4952
|
+
pendingAgentDoneNotificationTimers.set(key, { tabId, timer });
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4692
4955
|
function notifyAgentDone(tabOrId, { activity = null, tabTitle = "" } = {}) {
|
|
4693
4956
|
if (!agentDoneNotificationsEnabled) return;
|
|
4694
4957
|
const tabId = typeof tabOrId === "string" ? tabOrId : tabOrId?.id || activeTabId;
|
|
@@ -4699,9 +4962,11 @@ function notifyAgentDone(tabOrId, { activity = null, tabTitle = "" } = {}) {
|
|
|
4699
4962
|
const key = agentDoneNotificationKey(tabId, normalizedActivity);
|
|
4700
4963
|
if (agentDoneNotificationKeys.has(key)) return;
|
|
4701
4964
|
agentDoneNotificationKeys.add(key);
|
|
4965
|
+
if (isAutoRetryingTab(tabId)) return;
|
|
4702
4966
|
|
|
4703
4967
|
const displayTitle = tabTitle || tab?.title || "terminal";
|
|
4704
|
-
|
|
4968
|
+
queueAgentDoneBrowserNotification({
|
|
4969
|
+
key,
|
|
4705
4970
|
tabId,
|
|
4706
4971
|
title: "Pi finished work",
|
|
4707
4972
|
body: `${displayTitle} finished its agent run.`,
|
|
@@ -5068,6 +5333,58 @@ async function toggleFooterAutoCompaction(tabContext = activeTabContext()) {
|
|
|
5068
5333
|
}
|
|
5069
5334
|
}
|
|
5070
5335
|
|
|
5336
|
+
function scheduleGitFooterPiCalibrationRefresh(tabContext, delays = [600, 1600]) {
|
|
5337
|
+
for (const delayMs of delays) {
|
|
5338
|
+
setTimeout(() => {
|
|
5339
|
+
if (isCurrentTabContext(tabContext)) requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
5340
|
+
}, delayMs);
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5344
|
+
async function runGitFooterPiCalibration(mode = "current", tabContext = activeTabContext()) {
|
|
5345
|
+
if (!tabContext.tabId) return;
|
|
5346
|
+
if (gitFooterPiCalibrationInFlightByTab.has(tabContext.tabId)) return;
|
|
5347
|
+
if (currentState?.isStreaming || currentState?.isCompacting) {
|
|
5348
|
+
addEvent("PI calibration can run after the active agent work finishes.", "warn");
|
|
5349
|
+
return;
|
|
5350
|
+
}
|
|
5351
|
+
|
|
5352
|
+
const commandName = resolveAvailableCommandName("calibrate", { rpcOnly: true });
|
|
5353
|
+
if (!commandName) {
|
|
5354
|
+
addEvent("PI calibration unavailable: /calibrate is not loaded in this Pi tab.", "warn");
|
|
5355
|
+
return;
|
|
5356
|
+
}
|
|
5357
|
+
if (mode === "probe" && !confirm("Start an isolated PI calibration probe? This sends one tiny model request and may incur provider token usage.")) return;
|
|
5358
|
+
|
|
5359
|
+
const command = mode === "probe" ? `/${commandName}` : `/${commandName} current`;
|
|
5360
|
+
gitFooterPiCalibrationInFlightByTab.add(tabContext.tabId);
|
|
5361
|
+
renderFooter();
|
|
5362
|
+
try {
|
|
5363
|
+
await sendPrompt("prompt", command, { targetTabId: tabContext.tabId, throwOnError: true });
|
|
5364
|
+
if (!isCurrentTabContext(tabContext)) return;
|
|
5365
|
+
addEvent(mode === "probe" ? "PI calibration probe started; refreshing git footer value after it records…" : "PI calibration requested; refreshing git footer value…", "info");
|
|
5366
|
+
scheduleGitFooterPiCalibrationRefresh(tabContext, mode === "probe" ? [5000, 14000] : [600, 1600]);
|
|
5367
|
+
} catch (error) {
|
|
5368
|
+
if (isCurrentTabContext(tabContext)) addEvent(error.message || String(error), "error");
|
|
5369
|
+
} finally {
|
|
5370
|
+
gitFooterPiCalibrationInFlightByTab.delete(tabContext.tabId);
|
|
5371
|
+
if (isCurrentTabContext(tabContext)) renderFooter();
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
|
|
5375
|
+
function applyGitFooterPiCalibrationOptions(chip, options) {
|
|
5376
|
+
if (chip?.key !== "pi" || !FOOTER_PAYLOAD_ACTIONS.has(chip?.action)) return "";
|
|
5377
|
+
const tabContext = activeTabContext();
|
|
5378
|
+
const busy = !!tabContext.tabId && gitFooterPiCalibrationInFlightByTab.has(tabContext.tabId);
|
|
5379
|
+
const mode = chip.action === "calibrate-probe" ? "probe" : "current";
|
|
5380
|
+
options.onClick = () => runGitFooterPiCalibration(mode);
|
|
5381
|
+
if (busy) options.ariaBusy = true;
|
|
5382
|
+
if (busy) return "Calibrating PI estimate and refreshing this value…";
|
|
5383
|
+
return mode === "probe"
|
|
5384
|
+
? "Click to start an isolated PI calibration probe, then refresh this value."
|
|
5385
|
+
: "Click to calibrate this uncalibrated PI estimate from the current session, then refresh this value.";
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5071
5388
|
function applyGitFooterContextToggleOptions(chip, options) {
|
|
5072
5389
|
if (chip?.key !== "context") return "";
|
|
5073
5390
|
options.onClick = () => toggleFooterAutoCompaction();
|
|
@@ -5249,6 +5566,7 @@ function footerMeta(label, value, className = "", options = {}) {
|
|
|
5249
5566
|
}
|
|
5250
5567
|
|
|
5251
5568
|
const FOOTER_PAYLOAD_TONES = new Set(["pink", "blue", "mauve", "yellow", "green", "teal"]);
|
|
5569
|
+
const FOOTER_PAYLOAD_ACTIONS = new Set(["calibrate-current", "calibrate-probe"]);
|
|
5252
5570
|
const FOOTER_CHANGED_FILE_KINDS = new Set(["modified", "staged", "untracked", "conflicted"]);
|
|
5253
5571
|
const FOOTER_CHANGED_FILE_KIND_ORDER = ["modified", "staged", "untracked", "conflicted"];
|
|
5254
5572
|
const FOOTER_CHANGED_FILE_KIND_LABELS = {
|
|
@@ -5318,6 +5636,7 @@ function normalizeFooterPayloadChip(value, index) {
|
|
|
5318
5636
|
tone: FOOTER_PAYLOAD_TONES.has(value.tone) ? value.tone : "",
|
|
5319
5637
|
title: cleanFooterPayloadText(value.title, "", 4000),
|
|
5320
5638
|
};
|
|
5639
|
+
if (FOOTER_PAYLOAD_ACTIONS.has(value.action)) chip.action = value.action;
|
|
5321
5640
|
if (Array.isArray(value.files)) {
|
|
5322
5641
|
const files = value.files.map(normalizeFooterPayloadChangedFile).filter(Boolean).slice(0, 80);
|
|
5323
5642
|
if (files.length) chip.files = files;
|
|
@@ -5552,7 +5871,7 @@ function applyFooterChangedFilesDropdown(node, chip) {
|
|
|
5552
5871
|
|
|
5553
5872
|
function renderGitFooterPayloadMetric(chip) {
|
|
5554
5873
|
const options = { tooltipAlign: gitFooterTooltipAlign(chip) };
|
|
5555
|
-
const action = applyGitFooterContextToggleOptions(chip, options);
|
|
5874
|
+
const action = applyGitFooterPiCalibrationOptions(chip, options) || applyGitFooterContextToggleOptions(chip, options);
|
|
5556
5875
|
options.title = gitFooterPayloadTooltip(chip, { action });
|
|
5557
5876
|
const node = footerMetric(chip.icon || "•", chip.label, chip.value, chip.tone ? `tone-${chip.tone}` : "", options);
|
|
5558
5877
|
return chip.contextUsage ? applyFooterContextUsage(node, chip.contextUsage) : node;
|
|
@@ -6183,6 +6502,7 @@ async function requestManualCompaction({ triggerButton = null } = {}) {
|
|
|
6183
6502
|
}
|
|
6184
6503
|
|
|
6185
6504
|
function renderContextMeter() {
|
|
6505
|
+
if (deferUiRenderDuringPointerActivation("context-meter", renderContextMeter)) return;
|
|
6186
6506
|
const root = elements.contextMeterBar;
|
|
6187
6507
|
if (!root) return;
|
|
6188
6508
|
const tab = activeTab();
|
|
@@ -6239,6 +6559,7 @@ function dashboardAction(label, handler, className = "") {
|
|
|
6239
6559
|
}
|
|
6240
6560
|
|
|
6241
6561
|
function renderWorkspaceDashboard() {
|
|
6562
|
+
if (deferUiRenderDuringPointerActivation("workspace-dashboard", renderWorkspaceDashboard)) return;
|
|
6242
6563
|
const root = elements.workspaceDashboard;
|
|
6243
6564
|
if (!root) return;
|
|
6244
6565
|
const tab = activeTab();
|
|
@@ -7010,6 +7331,12 @@ async function changeActiveTabCwd() {
|
|
|
7010
7331
|
const currentCwd = latestWorkspace?.cwd || tab.cwd || "";
|
|
7011
7332
|
const cwd = await pickCwd(tab, currentCwd);
|
|
7012
7333
|
if (!isCurrentTabContext(tabContext) || !cwd || cwd === currentCwd) return;
|
|
7334
|
+
|
|
7335
|
+
if (shouldOpenCwdChangeInNewTab(tab)) {
|
|
7336
|
+
await createTerminalTab(cwd, { triggerButton: null });
|
|
7337
|
+
return;
|
|
7338
|
+
}
|
|
7339
|
+
|
|
7013
7340
|
if (!window.confirm(`Restart ${tab.title} in:\n${cwd}\n\nCurrent in-flight work in this tab will be stopped. The conversation continues in the new directory.`)) return;
|
|
7014
7341
|
|
|
7015
7342
|
saveActiveDraft();
|
|
@@ -7037,6 +7364,7 @@ async function changeActiveTabCwd() {
|
|
|
7037
7364
|
}
|
|
7038
7365
|
|
|
7039
7366
|
function renderFooter() {
|
|
7367
|
+
if (deferUiRenderDuringPointerActivation("footer", renderFooter)) return;
|
|
7040
7368
|
const gitFooterPayload = parseGitFooterWebuiPayload();
|
|
7041
7369
|
if (gitFooterPayload) {
|
|
7042
7370
|
renderGitFooterPayload(footerPayloadWithLiveModel(gitFooterPayload));
|
|
@@ -7269,6 +7597,7 @@ function initializeCodexUsage() {
|
|
|
7269
7597
|
}
|
|
7270
7598
|
|
|
7271
7599
|
function renderStatus() {
|
|
7600
|
+
if (deferUiRenderDuringPointerActivation("status", renderStatus)) return;
|
|
7272
7601
|
const state = currentState;
|
|
7273
7602
|
updateComposerModeButtons();
|
|
7274
7603
|
const running = state?.isStreaming ? "running" : "idle";
|
|
@@ -7511,6 +7840,10 @@ function releaseNpmLineTone(line) {
|
|
|
7511
7840
|
if (/^(WARN|warning)\b/i.test(clean)) return "warn";
|
|
7512
7841
|
if (/^(INFO|npm notice|notice)\b/i.test(clean)) return "info";
|
|
7513
7842
|
if (/^RELEASE_NPM_EVENT\b/.test(clean)) return "event";
|
|
7843
|
+
if (/^\[[0-9:]+\]\s+\[[^\]]+\]\s+\$/.test(clean)) return "command";
|
|
7844
|
+
if (/\b(STDERR|failed|error|exited with code)\b/i.test(clean)) return "fail";
|
|
7845
|
+
if (/\b(completed|succeeded|agent completed|tool completed)\b/i.test(clean)) return "pass";
|
|
7846
|
+
if (/\b(started|running|auto retry|compaction)\b/i.test(clean)) return "info";
|
|
7514
7847
|
return "";
|
|
7515
7848
|
}
|
|
7516
7849
|
|
|
@@ -7720,6 +8053,74 @@ function renderReleaseAurLogWidget() {
|
|
|
7720
8053
|
return node;
|
|
7721
8054
|
}
|
|
7722
8055
|
|
|
8056
|
+
function parseWorkflowSubprocessPayload(lines) {
|
|
8057
|
+
const raw = String(lines?.[0] || "").trim();
|
|
8058
|
+
if (!raw) return null;
|
|
8059
|
+
const json = raw.startsWith(WORKFLOW_WIDGET_PAYLOAD_PREFIX) ? raw.slice(WORKFLOW_WIDGET_PAYLOAD_PREFIX.length) : raw;
|
|
8060
|
+
try {
|
|
8061
|
+
const payload = JSON.parse(json);
|
|
8062
|
+
if (payload?.type !== WORKFLOW_SUBPROCESS_PAYLOAD_TYPE || payload.version !== WORKFLOW_SUBPROCESS_PAYLOAD_VERSION) return null;
|
|
8063
|
+
return payload;
|
|
8064
|
+
} catch {
|
|
8065
|
+
return null;
|
|
8066
|
+
}
|
|
8067
|
+
}
|
|
8068
|
+
|
|
8069
|
+
function workflowSubprocessIsLive(payload) {
|
|
8070
|
+
return payload?.status === "queued" || payload?.status === "running" || Number(payload?.taskCounts?.running || 0) > 0;
|
|
8071
|
+
}
|
|
8072
|
+
|
|
8073
|
+
function workflowTaskCountLabel(payload) {
|
|
8074
|
+
const counts = payload?.taskCounts || {};
|
|
8075
|
+
const done = Number(counts.completed || 0);
|
|
8076
|
+
const total = Number(counts.total || 0);
|
|
8077
|
+
const failed = Number(counts.failed || 0);
|
|
8078
|
+
const cancelled = Number(counts.cancelled || 0);
|
|
8079
|
+
return `${done}/${total} done${failed ? ` · ${failed} failed` : ""}${cancelled ? ` · ${cancelled} cancelled` : ""}`;
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
function renderWorkflowSubprocessWidget() {
|
|
8083
|
+
if (!isOptionalFeatureEnabled("workflows")) return null;
|
|
8084
|
+
const payload = parseWorkflowSubprocessPayload(getWidgetLines("workflow:subprocess"));
|
|
8085
|
+
if (!payload) return null;
|
|
8086
|
+
|
|
8087
|
+
const live = workflowSubprocessIsLive(payload);
|
|
8088
|
+
const node = make("section", `widget release-npm-widget workflow-widget ${live ? "workflow-live-widget" : "workflow-log-widget"}`);
|
|
8089
|
+
node.setAttribute("aria-label", "workflow subprocess output");
|
|
8090
|
+
|
|
8091
|
+
const header = make("div", "release-npm-header");
|
|
8092
|
+
const titleWrap = make("div", "release-npm-title-wrap");
|
|
8093
|
+
titleWrap.append(
|
|
8094
|
+
make("span", "release-npm-kicker", "workflow subprocesses"),
|
|
8095
|
+
make("strong", "release-npm-title", payload.workflowName || payload.workflowKey || "workflow"),
|
|
8096
|
+
);
|
|
8097
|
+
|
|
8098
|
+
const meta = make("div", "release-npm-meta");
|
|
8099
|
+
meta.append(make("span", `release-npm-pill workflow-status ${payload.status || "unknown"}`, payload.status || "unknown"));
|
|
8100
|
+
if (payload.activePhase) meta.append(make("span", "release-npm-pill", payload.activePhase));
|
|
8101
|
+
meta.append(make("span", "release-npm-pill elapsed", workflowTaskCountLabel(payload)));
|
|
8102
|
+
if (payload.truncated) meta.append(make("span", "release-npm-pill workflow-truncated", "truncated"));
|
|
8103
|
+
|
|
8104
|
+
const actions = make("div", "release-npm-actions");
|
|
8105
|
+
actions.append(releaseNpmActionButton("Status", "/workflow status"));
|
|
8106
|
+
if (live) actions.append(releaseNpmActionButton("Abort", "/workflow abort", "danger"));
|
|
8107
|
+
actions.append(releaseNpmActionButton("Clear", "/workflow-clear"));
|
|
8108
|
+
header.append(titleWrap, meta, actions);
|
|
8109
|
+
|
|
8110
|
+
const lines = Array.isArray(payload.lines) && payload.lines.length ? payload.lines : ["Waiting for workflow subprocess output..."];
|
|
8111
|
+
const streamHeader = releaseNpmStreamHeader(live ? "Live subprocess output" : "Subprocess output", lines.length, { live });
|
|
8112
|
+
const terminal = make("div", "release-npm-terminal");
|
|
8113
|
+
terminal.setAttribute("role", "log");
|
|
8114
|
+
terminal.setAttribute("aria-live", live ? "polite" : "off");
|
|
8115
|
+
for (const line of lines) appendReleaseNpmTerminalLine(terminal, line);
|
|
8116
|
+
|
|
8117
|
+
const controls = make("div", "release-npm-controls", "Workflow subprocess output is shown as a non-blocking Web UI widget. Use /workflow abort to stop an active run.");
|
|
8118
|
+
const outputDetails = renderReleaseNpmOutputDetails("workflow:subprocess", streamHeader, terminal, controls);
|
|
8119
|
+
node.append(header, outputDetails);
|
|
8120
|
+
requestAnimationFrame(() => { if (outputDetails.open) terminal.scrollTop = terminal.scrollHeight; });
|
|
8121
|
+
return node;
|
|
8122
|
+
}
|
|
8123
|
+
|
|
7723
8124
|
function activeAppRunnerData() {
|
|
7724
8125
|
return activeTabId ? appRunnerDataByTab.get(activeTabId) || { runners: [], activeRun: null } : { runners: [], activeRun: null };
|
|
7725
8126
|
}
|
|
@@ -8710,7 +9111,290 @@ function handleStatsWebuiStatus(statusText) {
|
|
|
8710
9111
|
if (payload.open || elements.statsOverlayDialog?.open) renderStatsOverlay();
|
|
8711
9112
|
}
|
|
8712
9113
|
|
|
9114
|
+
function parseBtwWebuiPayloadRaw(raw) {
|
|
9115
|
+
if (!raw) return null;
|
|
9116
|
+
const text = String(raw || "");
|
|
9117
|
+
const json = text.startsWith(BTW_WIDGET_PAYLOAD_PREFIX) ? text.slice(BTW_WIDGET_PAYLOAD_PREFIX.length) : text;
|
|
9118
|
+
try {
|
|
9119
|
+
const parsed = JSON.parse(json);
|
|
9120
|
+
if (!BTW_WEBUI_PAYLOAD_TYPES.has(parsed?.type)) return null;
|
|
9121
|
+
return parsed;
|
|
9122
|
+
} catch {
|
|
9123
|
+
return null;
|
|
9124
|
+
}
|
|
9125
|
+
}
|
|
9126
|
+
|
|
9127
|
+
function parseBtwWidgetPayload(lines = []) {
|
|
9128
|
+
const first = Array.isArray(lines) ? lines[0] : "";
|
|
9129
|
+
return parseBtwWebuiPayloadRaw(first);
|
|
9130
|
+
}
|
|
9131
|
+
|
|
9132
|
+
function currentBtwWidgetPayload() {
|
|
9133
|
+
if (isOptionalFeatureDisabled("btwCommand")) return null;
|
|
9134
|
+
const outputLines = widgets.get(BTW_OUTPUT_WIDGET_KEY)?.widgetLines || [];
|
|
9135
|
+
const payload = parseBtwWidgetPayload(outputLines) || parseBtwWebuiPayloadRaw(statusEntries.get(BTW_WEBUI_STATUS_KEY)) || latestBtwWidgetPayload;
|
|
9136
|
+
if (payload?.id && payload.id === btwWidgetDismissedId) return null;
|
|
9137
|
+
return payload;
|
|
9138
|
+
}
|
|
9139
|
+
|
|
9140
|
+
function btwStatusLabel(payload) {
|
|
9141
|
+
switch (payload?.status) {
|
|
9142
|
+
case "done": return "Done";
|
|
9143
|
+
case "error": return "Error";
|
|
9144
|
+
case "aborted": return "Aborted";
|
|
9145
|
+
case "streaming": return "Answering…";
|
|
9146
|
+
default: return "Starting…";
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
|
|
9150
|
+
function btwAnswerLines(payload) {
|
|
9151
|
+
const text = payload?.error || payload?.answer || (payload?.status === "loading" ? "Starting side request…" : "Waiting for model output…");
|
|
9152
|
+
return String(text || "").replace(/\r\n?/g, "\n").split("\n");
|
|
9153
|
+
}
|
|
9154
|
+
|
|
9155
|
+
function focusBtwWidgetInput() {
|
|
9156
|
+
const input = document.querySelector(".btw-widget-input");
|
|
9157
|
+
if (!input) return;
|
|
9158
|
+
try {
|
|
9159
|
+
input.focus({ preventScroll: true });
|
|
9160
|
+
} catch {
|
|
9161
|
+
input.focus();
|
|
9162
|
+
}
|
|
9163
|
+
}
|
|
9164
|
+
|
|
9165
|
+
function openBtwComposerWidget() {
|
|
9166
|
+
btwWidgetComposerOpen = true;
|
|
9167
|
+
btwWidgetDismissedId = "";
|
|
9168
|
+
btwWidgetFocusAfterRender = true;
|
|
9169
|
+
setComposerActionsOpen(false);
|
|
9170
|
+
setPublishMenuOpen(false);
|
|
9171
|
+
setNativeCommandMenuOpen(false);
|
|
9172
|
+
setAppRunnerMenuOpen(false);
|
|
9173
|
+
setOptionsMenuOpen(false);
|
|
9174
|
+
renderWidgets();
|
|
9175
|
+
}
|
|
9176
|
+
|
|
9177
|
+
function closeBtwOutputWidget() {
|
|
9178
|
+
const payload = currentBtwWidgetPayload();
|
|
9179
|
+
if (payload?.id) btwWidgetDismissedId = payload.id;
|
|
9180
|
+
widgets.delete(BTW_OUTPUT_WIDGET_KEY);
|
|
9181
|
+
widgets.delete(BTW_FOOTER_WIDGET_KEY);
|
|
9182
|
+
statusEntries.delete(BTW_WEBUI_STATUS_KEY);
|
|
9183
|
+
latestBtwWidgetPayload = null;
|
|
9184
|
+
btwWidgetComposerOpen = false;
|
|
9185
|
+
btwWidgetInputDraft = "";
|
|
9186
|
+
renderWidgets();
|
|
9187
|
+
renderStatus();
|
|
9188
|
+
}
|
|
9189
|
+
|
|
9190
|
+
async function copyBtwWidgetAnswer(button) {
|
|
9191
|
+
const payload = currentBtwWidgetPayload();
|
|
9192
|
+
const answer = String(payload?.answer || payload?.error || "").trim();
|
|
9193
|
+
if (!answer) return;
|
|
9194
|
+
const original = button?.textContent || "Copy";
|
|
9195
|
+
try {
|
|
9196
|
+
await navigator.clipboard.writeText(answer);
|
|
9197
|
+
if (button) button.textContent = "Copied";
|
|
9198
|
+
setTimeout(() => { if (button) button.textContent = original; }, 1600);
|
|
9199
|
+
} catch (error) {
|
|
9200
|
+
addEvent(`copy /btw answer failed: ${error.message || String(error)}`, "error");
|
|
9201
|
+
}
|
|
9202
|
+
}
|
|
9203
|
+
|
|
9204
|
+
function base64UrlEncodeUtf8(value) {
|
|
9205
|
+
const bytes = new TextEncoder().encode(String(value || ""));
|
|
9206
|
+
let binary = "";
|
|
9207
|
+
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
|
|
9208
|
+
binary += String.fromCharCode(...bytes.slice(offset, offset + 0x8000));
|
|
9209
|
+
}
|
|
9210
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
9211
|
+
}
|
|
9212
|
+
|
|
9213
|
+
function btwTransferPayload(payload) {
|
|
9214
|
+
return {
|
|
9215
|
+
question: payload?.question || "",
|
|
9216
|
+
answer: payload?.answer || payload?.error || "",
|
|
9217
|
+
status: payload?.status || "done",
|
|
9218
|
+
model: payload?.model || "",
|
|
9219
|
+
generatedAt: payload?.generatedAt || 0,
|
|
9220
|
+
updatedAt: payload?.updatedAt || Date.now(),
|
|
9221
|
+
};
|
|
9222
|
+
}
|
|
9223
|
+
|
|
9224
|
+
function makeBtwTransferIcon() {
|
|
9225
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
9226
|
+
const svg = document.createElementNS(ns, "svg");
|
|
9227
|
+
svg.setAttribute("class", "btw-transfer-icon");
|
|
9228
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
9229
|
+
svg.setAttribute("aria-hidden", "true");
|
|
9230
|
+
svg.setAttribute("focusable", "false");
|
|
9231
|
+
const bubble = document.createElementNS(ns, "path");
|
|
9232
|
+
bubble.setAttribute("d", "M4.5 5.75h8.75a2 2 0 0 1 2 2v3.5a2 2 0 0 1-2 2H9.8L6.5 15.6v-2.35h-2a2 2 0 0 1-2-2v-3.5a2 2 0 0 1 2-2Z");
|
|
9233
|
+
bubble.setAttribute("fill", "none");
|
|
9234
|
+
bubble.setAttribute("stroke", "currentColor");
|
|
9235
|
+
bubble.setAttribute("stroke-width", "1.9");
|
|
9236
|
+
bubble.setAttribute("stroke-linecap", "round");
|
|
9237
|
+
bubble.setAttribute("stroke-linejoin", "round");
|
|
9238
|
+
const arrow = document.createElementNS(ns, "path");
|
|
9239
|
+
arrow.setAttribute("d", "M13 17h7m0 0-2.8-2.8M20 17l-2.8 2.8");
|
|
9240
|
+
arrow.setAttribute("fill", "none");
|
|
9241
|
+
arrow.setAttribute("stroke", "currentColor");
|
|
9242
|
+
arrow.setAttribute("stroke-width", "2.15");
|
|
9243
|
+
arrow.setAttribute("stroke-linecap", "round");
|
|
9244
|
+
arrow.setAttribute("stroke-linejoin", "round");
|
|
9245
|
+
const line = document.createElementNS(ns, "path");
|
|
9246
|
+
line.setAttribute("d", "M6 9.4h5.6");
|
|
9247
|
+
line.setAttribute("fill", "none");
|
|
9248
|
+
line.setAttribute("stroke", "currentColor");
|
|
9249
|
+
line.setAttribute("stroke-width", "1.9");
|
|
9250
|
+
line.setAttribute("stroke-linecap", "round");
|
|
9251
|
+
svg.append(bubble, line, arrow);
|
|
9252
|
+
return svg;
|
|
9253
|
+
}
|
|
9254
|
+
|
|
9255
|
+
async function transferBtwContextToMain(button) {
|
|
9256
|
+
const payload = currentBtwWidgetPayload();
|
|
9257
|
+
const transferPayload = btwTransferPayload(payload);
|
|
9258
|
+
if (!transferPayload.question && !transferPayload.answer) return;
|
|
9259
|
+
const targetTabId = activeTabId;
|
|
9260
|
+
const liveSteer = !!currentState?.isStreaming;
|
|
9261
|
+
const original = button?.querySelector("span")?.textContent || "Transfer Context";
|
|
9262
|
+
const encoded = base64UrlEncodeUtf8(JSON.stringify(transferPayload));
|
|
9263
|
+
try {
|
|
9264
|
+
await sendPrompt("prompt", `/btw-transfer ${encoded}`, { targetTabId, throwOnError: true, streamingBehavior: liveSteer ? "steer" : undefined });
|
|
9265
|
+
const label = button?.querySelector("span");
|
|
9266
|
+
if (label) label.textContent = liveSteer ? "Steered" : "Transferred";
|
|
9267
|
+
addEvent(liveSteer
|
|
9268
|
+
? "/btw context sent as live steering; it will be injected after the next agent action"
|
|
9269
|
+
: "/btw context transferred into the main conversation", "info");
|
|
9270
|
+
setTimeout(() => { if (label) label.textContent = original; }, 1800);
|
|
9271
|
+
} catch {
|
|
9272
|
+
// sendPrompt already reports the error.
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
|
|
9276
|
+
function btwWidgetActionButton(label, handler, className = "") {
|
|
9277
|
+
const button = make("button", `release-npm-action ${className}`.trim(), label);
|
|
9278
|
+
button.type = "button";
|
|
9279
|
+
button.addEventListener("click", () => handler(button));
|
|
9280
|
+
return button;
|
|
9281
|
+
}
|
|
9282
|
+
|
|
9283
|
+
function renderBtwComposerForm() {
|
|
9284
|
+
const form = make("form", "btw-widget-composer");
|
|
9285
|
+
const input = make("textarea", "btw-widget-input");
|
|
9286
|
+
input.rows = 1;
|
|
9287
|
+
input.placeholder = "Ask a /btw side question…";
|
|
9288
|
+
input.value = btwWidgetInputDraft;
|
|
9289
|
+
input.setAttribute("aria-label", "Ask a /btw side question");
|
|
9290
|
+
input.addEventListener("input", () => { btwWidgetInputDraft = input.value; });
|
|
9291
|
+
input.addEventListener("keydown", (event) => {
|
|
9292
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
9293
|
+
event.preventDefault();
|
|
9294
|
+
form.requestSubmit();
|
|
9295
|
+
}
|
|
9296
|
+
});
|
|
9297
|
+
|
|
9298
|
+
const submit = make("button", "release-npm-action btw-widget-send", "Ask /btw");
|
|
9299
|
+
submit.type = "submit";
|
|
9300
|
+
form.append(input, submit);
|
|
9301
|
+
form.addEventListener("submit", async (event) => {
|
|
9302
|
+
event.preventDefault();
|
|
9303
|
+
const question = input.value.trim();
|
|
9304
|
+
if (!question) {
|
|
9305
|
+
input.focus();
|
|
9306
|
+
return;
|
|
9307
|
+
}
|
|
9308
|
+
submit.disabled = true;
|
|
9309
|
+
const sent = await sendBtwQuestion(question);
|
|
9310
|
+
submit.disabled = false;
|
|
9311
|
+
if (!sent) return;
|
|
9312
|
+
btwWidgetInputDraft = "";
|
|
9313
|
+
input.value = "";
|
|
9314
|
+
input.focus({ preventScroll: true });
|
|
9315
|
+
});
|
|
9316
|
+
return form;
|
|
9317
|
+
}
|
|
9318
|
+
|
|
9319
|
+
function renderBtwOutputWidget() {
|
|
9320
|
+
const payload = currentBtwWidgetPayload();
|
|
9321
|
+
if (!payload && !btwWidgetComposerOpen) return null;
|
|
9322
|
+
|
|
9323
|
+
if (payload) latestBtwWidgetPayload = payload;
|
|
9324
|
+
const running = payload?.status === "loading" || payload?.status === "streaming";
|
|
9325
|
+
const lineCount = payload ? btwAnswerLines(payload).length : 0;
|
|
9326
|
+
const node = make("section", `widget release-npm-widget btw-widget${running ? " btw-live-widget" : " btw-done-widget"}`);
|
|
9327
|
+
node.setAttribute("aria-label", "/btw side-question output");
|
|
9328
|
+
|
|
9329
|
+
const header = make("div", "release-npm-header");
|
|
9330
|
+
const titleWrap = make("div", "release-npm-title-wrap");
|
|
9331
|
+
titleWrap.append(make("span", "release-npm-kicker", "/btw"), make("strong", "release-npm-title", payload ? btwStatusLabel(payload) : "Ready"));
|
|
9332
|
+
|
|
9333
|
+
const meta = make("div", "release-npm-meta");
|
|
9334
|
+
meta.append(make("span", `release-npm-pill btw-status ${payload?.status || "ready"}`.trim(), payload?.status || "ready"));
|
|
9335
|
+
if (payload?.model) meta.append(make("span", "release-npm-pill", payload.model));
|
|
9336
|
+
|
|
9337
|
+
const actions = make("div", "release-npm-actions");
|
|
9338
|
+
const transferButton = btwWidgetActionButton("", transferBtwContextToMain, "btw-transfer-action");
|
|
9339
|
+
transferButton.title = currentState?.isStreaming
|
|
9340
|
+
? "Transfer this /btw question and answer as live steering after the next agent action"
|
|
9341
|
+
: "Transfer this /btw question and answer into the main conversation context";
|
|
9342
|
+
transferButton.append(makeBtwTransferIcon(), make("span", undefined, "Transfer Context"));
|
|
9343
|
+
transferButton.disabled = !payload || !(payload.answer || payload.error || payload.question);
|
|
9344
|
+
actions.append(
|
|
9345
|
+
transferButton,
|
|
9346
|
+
btwWidgetActionButton("Copy", copyBtwWidgetAnswer),
|
|
9347
|
+
btwWidgetActionButton("Close", closeBtwOutputWidget),
|
|
9348
|
+
);
|
|
9349
|
+
header.append(titleWrap, meta, actions);
|
|
9350
|
+
|
|
9351
|
+
const question = make("div", "btw-widget-question");
|
|
9352
|
+
question.append(make("span", "btw-widget-question-label", "Question"), make("span", "btw-widget-question-text", payload?.question || "Start or continue with the /btw input below."));
|
|
9353
|
+
|
|
9354
|
+
const streamHeader = releaseNpmStreamHeader(running ? "Live side answer" : "Side answer", lineCount, { live: running });
|
|
9355
|
+
const terminal = make("div", "release-npm-terminal btw-terminal");
|
|
9356
|
+
terminal.setAttribute("role", "log");
|
|
9357
|
+
terminal.setAttribute("aria-live", running ? "polite" : "off");
|
|
9358
|
+
for (const line of (payload ? btwAnswerLines(payload) : ["Type a side question below and press Enter to run it as /btw."])) appendReleaseNpmTerminalLine(terminal, line);
|
|
9359
|
+
|
|
9360
|
+
const note = payload?.status === "error"
|
|
9361
|
+
? "The side request failed. The main conversation was not changed."
|
|
9362
|
+
: "Ephemeral answer · every message in this input is sent as /btw · not appended to the main transcript.";
|
|
9363
|
+
const controls = make("div", "release-npm-controls btw-controls", note);
|
|
9364
|
+
const outputDetails = renderReleaseNpmOutputDetails(`btw:${payload?.id || "composer"}`, streamHeader, terminal, controls);
|
|
9365
|
+
node.append(header, question, outputDetails, renderBtwComposerForm());
|
|
9366
|
+
requestAnimationFrame(() => {
|
|
9367
|
+
if (outputDetails.open) terminal.scrollTop = terminal.scrollHeight;
|
|
9368
|
+
if (btwWidgetFocusAfterRender) {
|
|
9369
|
+
btwWidgetFocusAfterRender = false;
|
|
9370
|
+
focusBtwWidgetInput();
|
|
9371
|
+
}
|
|
9372
|
+
});
|
|
9373
|
+
return node;
|
|
9374
|
+
}
|
|
9375
|
+
|
|
9376
|
+
function handleBtwWebuiStatus(statusText) {
|
|
9377
|
+
const payload = parseBtwWebuiPayloadRaw(statusText);
|
|
9378
|
+
if (payload) latestBtwWidgetPayload = payload;
|
|
9379
|
+
renderWidgets();
|
|
9380
|
+
}
|
|
9381
|
+
|
|
9382
|
+
function remoteWebuiWidgetLines(lines = []) {
|
|
9383
|
+
return (Array.isArray(lines) ? lines : [])
|
|
9384
|
+
.map(stripAnsi)
|
|
9385
|
+
.map((line) => String(line ?? ""))
|
|
9386
|
+
.filter((line, index, array) => line.trim() || (index > 0 && index < array.length - 1));
|
|
9387
|
+
}
|
|
9388
|
+
|
|
9389
|
+
function mirrorRemoteWebuiWidgetToTranscript(widgetKey, lines = [], request = {}) {
|
|
9390
|
+
if (widgetKey !== "pi-remote-webui" || request.replayed) return;
|
|
9391
|
+
const content = remoteWebuiWidgetLines(lines).join("\n").trimEnd();
|
|
9392
|
+
if (!content) return;
|
|
9393
|
+
addTransientMessage({ role: "extension", title: "/remote", content, level: "info", widgetKey });
|
|
9394
|
+
}
|
|
9395
|
+
|
|
8713
9396
|
function renderWidgets() {
|
|
9397
|
+
if (deferUiRenderDuringPointerActivation("widgets", renderWidgets)) return;
|
|
8714
9398
|
elements.widgetArea.replaceChildren();
|
|
8715
9399
|
const releaseOutput = renderReleaseNpmOutputWidget();
|
|
8716
9400
|
if (releaseOutput) elements.widgetArea.append(releaseOutput);
|
|
@@ -8720,14 +9404,19 @@ function renderWidgets() {
|
|
|
8720
9404
|
if (releaseAurOutput) elements.widgetArea.append(releaseAurOutput);
|
|
8721
9405
|
const releaseAurLog = renderReleaseAurLogWidget();
|
|
8722
9406
|
if (releaseAurLog) elements.widgetArea.append(releaseAurLog);
|
|
9407
|
+
const workflowSubprocessWidget = renderWorkflowSubprocessWidget();
|
|
9408
|
+
if (workflowSubprocessWidget) elements.widgetArea.append(workflowSubprocessWidget);
|
|
8723
9409
|
const appRunnerWidget = renderAppRunnerWidget();
|
|
8724
9410
|
if (appRunnerWidget) elements.widgetArea.append(appRunnerWidget);
|
|
9411
|
+
const btwWidget = renderBtwOutputWidget();
|
|
9412
|
+
if (btwWidget) elements.widgetArea.append(btwWidget);
|
|
8725
9413
|
|
|
8726
9414
|
for (const [key, value] of widgets) {
|
|
8727
9415
|
const widgetFeatureId = optionalFeatureWidgetFeatureId(key);
|
|
8728
9416
|
if (widgetFeatureId && !isOptionalFeatureEnabled(widgetFeatureId)) continue;
|
|
8729
|
-
if (widgetFeatureId && key
|
|
9417
|
+
if (widgetFeatureId && optionalFeatureWidgetHasSpecializedRenderer(key)) continue;
|
|
8730
9418
|
const lines = Array.isArray(value.widgetLines) ? value.widgetLines : [];
|
|
9419
|
+
if (key === "pi-remote-webui") continue;
|
|
8731
9420
|
const specialized = key === "todo-progress" && isOptionalFeatureEnabled("todoProgressWidget") ? renderTodoProgressWidget(key, lines) : null;
|
|
8732
9421
|
if (specialized) {
|
|
8733
9422
|
elements.widgetArea.append(specialized);
|
|
@@ -10330,6 +11019,7 @@ function renderQueueGroup(label, items, tone) {
|
|
|
10330
11019
|
}
|
|
10331
11020
|
|
|
10332
11021
|
function renderQueue(event) {
|
|
11022
|
+
if (deferUiRenderDuringPointerActivation("queue", () => renderQueue(event))) return;
|
|
10333
11023
|
const snapshot = normalizeQueuedMessages(event);
|
|
10334
11024
|
const tabId = event?.tabId || activeTabId;
|
|
10335
11025
|
if (tabId) latestQueuedMessagesByTab.set(tabId, snapshot);
|
|
@@ -11281,6 +11971,7 @@ function renderActionFeedbackControls(bubble, message, messageIndex) {
|
|
|
11281
11971
|
}
|
|
11282
11972
|
|
|
11283
11973
|
function renderFeedbackTray() {
|
|
11974
|
+
if (deferUiRenderDuringPointerActivation("feedback-tray", renderFeedbackTray)) return;
|
|
11284
11975
|
const items = queuedActionFeedback();
|
|
11285
11976
|
const hasItems = items.length > 0;
|
|
11286
11977
|
elements.feedbackTray.hidden = !hasItems;
|
|
@@ -11753,6 +12444,7 @@ function findStickyUserPromptTarget(targets = userPromptTargets()) {
|
|
|
11753
12444
|
}
|
|
11754
12445
|
|
|
11755
12446
|
function updateStickyUserPromptButton() {
|
|
12447
|
+
if (deferUiRenderDuringPointerActivation("sticky-user-prompt", updateStickyUserPromptButton)) return;
|
|
11756
12448
|
const button = elements.stickyUserPromptButton;
|
|
11757
12449
|
if (!button) return;
|
|
11758
12450
|
const targets = userPromptTargets();
|
|
@@ -12902,6 +13594,7 @@ function pruneDisconnectedLiveToolCards() {
|
|
|
12902
13594
|
}
|
|
12903
13595
|
|
|
12904
13596
|
function renderAllMessages({ preserveScroll = false, forceRebuild = false } = {}) {
|
|
13597
|
+
if (deferUiRenderDuringPointerActivation("messages", () => renderAllMessages({ preserveScroll, forceRebuild }))) return;
|
|
12905
13598
|
const shouldFollow = !preserveScroll && (autoFollowChat || isChatNearBottom());
|
|
12906
13599
|
const previousScrollTop = elements.chat.scrollTop;
|
|
12907
13600
|
const transcriptItems = orderedTranscriptItems();
|
|
@@ -13073,6 +13766,7 @@ function scheduleChatFollowScroll() {
|
|
|
13073
13766
|
}
|
|
13074
13767
|
|
|
13075
13768
|
function scrollChatToBottom({ force = false } = {}) {
|
|
13769
|
+
if (deferChatFollowScrollDuringPointerActivation({ force })) return;
|
|
13076
13770
|
if (force) autoFollowChat = true;
|
|
13077
13771
|
if (!autoFollowChat) {
|
|
13078
13772
|
updateJumpToLatestButton();
|
|
@@ -13128,11 +13822,47 @@ function sendPromptFromModeButton(kind, button) {
|
|
|
13128
13822
|
sendPrompt(kind);
|
|
13129
13823
|
}
|
|
13130
13824
|
|
|
13825
|
+
async function sendBtwQuestion(question, { clearComposerDraft = false } = {}) {
|
|
13826
|
+
const cleanQuestion = String(question || "").trim();
|
|
13827
|
+
if (!cleanQuestion) return false;
|
|
13828
|
+
const message = /^\/btw(?:\s|$)/.test(cleanQuestion) ? cleanQuestion : `/btw ${cleanQuestion}`;
|
|
13829
|
+
const targetTabId = activeTabId;
|
|
13830
|
+
btwWidgetComposerOpen = true;
|
|
13831
|
+
btwWidgetDismissedId = "";
|
|
13832
|
+
try {
|
|
13833
|
+
await sendPrompt("prompt", message, { targetTabId, throwOnError: true });
|
|
13834
|
+
} catch {
|
|
13835
|
+
return false;
|
|
13836
|
+
}
|
|
13837
|
+
if (!targetTabId) return true;
|
|
13838
|
+
if (clearComposerDraft) {
|
|
13839
|
+
if (targetTabId === activeTabId) {
|
|
13840
|
+
elements.promptInput.value = "";
|
|
13841
|
+
resizePromptInput();
|
|
13842
|
+
hideCommandSuggestions();
|
|
13843
|
+
saveActiveDraft();
|
|
13844
|
+
} else {
|
|
13845
|
+
tabDrafts.set(targetTabId, "");
|
|
13846
|
+
}
|
|
13847
|
+
}
|
|
13848
|
+
return true;
|
|
13849
|
+
}
|
|
13850
|
+
|
|
13851
|
+
async function sendBtwPromptFromButton() {
|
|
13852
|
+
const question = String(elements.promptInput.value || "").trim();
|
|
13853
|
+
if (!question) {
|
|
13854
|
+
openBtwComposerWidget();
|
|
13855
|
+
return;
|
|
13856
|
+
}
|
|
13857
|
+
await sendBtwQuestion(question, { clearComposerDraft: true });
|
|
13858
|
+
}
|
|
13859
|
+
|
|
13131
13860
|
function setPublishMenuOpen(open) {
|
|
13132
13861
|
publishMenuOpen = !!open;
|
|
13133
13862
|
elements.publishButton.setAttribute("aria-expanded", publishMenuOpen ? "true" : "false");
|
|
13134
13863
|
elements.publishButton.classList.toggle("menu-open", publishMenuOpen);
|
|
13135
13864
|
elements.publishButton.parentElement?.classList.toggle("open", publishMenuOpen);
|
|
13865
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13136
13866
|
}
|
|
13137
13867
|
|
|
13138
13868
|
function setNativeCommandMenuOpen(open) {
|
|
@@ -13140,6 +13870,7 @@ function setNativeCommandMenuOpen(open) {
|
|
|
13140
13870
|
elements.nativeCommandMenuButton.setAttribute("aria-expanded", nativeCommandMenuOpen ? "true" : "false");
|
|
13141
13871
|
elements.nativeCommandMenuButton.classList.toggle("menu-open", nativeCommandMenuOpen);
|
|
13142
13872
|
elements.nativeCommandMenuButton.parentElement?.classList.toggle("open", nativeCommandMenuOpen);
|
|
13873
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13143
13874
|
}
|
|
13144
13875
|
|
|
13145
13876
|
function setAppRunnerMenuOpen(open) {
|
|
@@ -13147,6 +13878,7 @@ function setAppRunnerMenuOpen(open) {
|
|
|
13147
13878
|
elements.appRunnerMenuButton?.setAttribute("aria-expanded", appRunnerMenuOpen ? "true" : "false");
|
|
13148
13879
|
elements.appRunnerMenuButton?.classList.toggle("menu-open", appRunnerMenuOpen);
|
|
13149
13880
|
elements.appRunnerMenuButton?.parentElement?.classList.toggle("open", appRunnerMenuOpen);
|
|
13881
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13150
13882
|
}
|
|
13151
13883
|
|
|
13152
13884
|
function setOptionsMenuOpen(open) {
|
|
@@ -13154,6 +13886,7 @@ function setOptionsMenuOpen(open) {
|
|
|
13154
13886
|
elements.optionsMenuButton.setAttribute("aria-expanded", optionsMenuOpen ? "true" : "false");
|
|
13155
13887
|
elements.optionsMenuButton.classList.toggle("menu-open", optionsMenuOpen);
|
|
13156
13888
|
elements.optionsMenuButton.parentElement?.classList.toggle("open", optionsMenuOpen);
|
|
13889
|
+
scheduleMobileDropdownScrollBoundsUpdate();
|
|
13157
13890
|
}
|
|
13158
13891
|
|
|
13159
13892
|
function optionalFeatureIdForCommand(name) {
|
|
@@ -13309,15 +14042,18 @@ function requestGitFooterWebuiPayload(tabContext = activeTabContext(), { force =
|
|
|
13309
14042
|
}
|
|
13310
14043
|
|
|
13311
14044
|
function updateOptionalFeatureAvailability() {
|
|
14045
|
+
optionalFeatureAvailability.btwCommand = hasAvailableCommand("btw") || optionalFeatureAvailability.btwCommand || statusEntries.has(BTW_WEBUI_STATUS_KEY) || widgets.has(BTW_OUTPUT_WIDGET_KEY);
|
|
13312
14046
|
optionalFeatureAvailability.gitWorkflow = hasAvailableCommand("git-staged-msg");
|
|
13313
14047
|
optionalFeatureAvailability.releaseNpm = hasAvailableCommand("release-npm");
|
|
13314
14048
|
optionalFeatureAvailability.releaseAur = hasAvailableCommand("release-aur");
|
|
14049
|
+
optionalFeatureAvailability.workflows = hasAvailableCommand("workflow") || hasAvailableCommand("workflow-clear") || optionalFeatureAvailability.workflows || widgets.has("workflow") || widgets.has("workflow:subprocess");
|
|
13315
14050
|
optionalFeatureAvailability.safetyGuard = hasAvailableCommand("safety-guard") || optionalFeatureAvailability.safetyGuard || statusEntries.has("safety-guard");
|
|
13316
14051
|
optionalFeatureAvailability.statsCommand = hasAvailableCommand("stats");
|
|
13317
14052
|
optionalFeatureAvailability.gitFooterStatus = hasAvailableCommand("git-footer-refresh") || optionalFeatureAvailability.gitFooterStatus || statusEntries.has("git-footer") || statusEntries.has(GIT_FOOTER_WEBUI_STATUS_KEY);
|
|
13318
14053
|
optionalFeatureAvailability.tuiSkillsCommand = hasLoadedRpcCommand("skills");
|
|
13319
14054
|
optionalFeatureAvailability.todoProgressWidget = hasAvailableCommand("todo-progress-status") || optionalFeatureAvailability.todoProgressWidget || widgets.has("todo-progress");
|
|
13320
14055
|
optionalFeatureAvailability.tuiToolsCommand = hasLoadedRpcCommand("tools");
|
|
14056
|
+
optionalFeatureAvailability.remoteWebui = hasAvailableCommand("remote") || optionalFeatureAvailability.remoteWebui || statusEntries.has("pi-remote-webui") || widgets.has("pi-remote-webui");
|
|
13321
14057
|
optionalFeatureAvailability.themeBundle = availableThemes.length > 0;
|
|
13322
14058
|
requestGitFooterWebuiPayload();
|
|
13323
14059
|
renderOptionalFeatureControls();
|
|
@@ -13339,12 +14075,19 @@ function optionalFeatureStatus(featureId) {
|
|
|
13339
14075
|
}
|
|
13340
14076
|
|
|
13341
14077
|
function optionalFeatureWidgetFeatureId(key) {
|
|
14078
|
+
if (key.startsWith("btw:")) return "btwCommand";
|
|
13342
14079
|
if (key.startsWith("release-npm:")) return "releaseNpm";
|
|
13343
14080
|
if (key.startsWith("release-aur:")) return "releaseAur";
|
|
14081
|
+
if (key === "workflow" || key.startsWith("workflow:")) return "workflows";
|
|
13344
14082
|
if (key === "todo-progress") return "todoProgressWidget";
|
|
14083
|
+
if (key === "pi-remote-webui") return "remoteWebui";
|
|
13345
14084
|
return null;
|
|
13346
14085
|
}
|
|
13347
14086
|
|
|
14087
|
+
function optionalFeatureWidgetHasSpecializedRenderer(key) {
|
|
14088
|
+
return key.startsWith("btw:") || key.startsWith("release-npm:") || key.startsWith("release-aur:") || key === "workflow:subprocess";
|
|
14089
|
+
}
|
|
14090
|
+
|
|
13348
14091
|
function renderOptionalFeaturePanel() {
|
|
13349
14092
|
if (!elements.optionalFeaturesBox) return;
|
|
13350
14093
|
elements.optionalFeaturesBox.replaceChildren();
|
|
@@ -13405,6 +14148,16 @@ function renderOptionalFeaturePanel() {
|
|
|
13405
14148
|
}
|
|
13406
14149
|
|
|
13407
14150
|
function renderOptionalFeatureControls() {
|
|
14151
|
+
const hasBtwCommand = isOptionalFeatureEnabled("btwCommand");
|
|
14152
|
+
if (elements.btwButton) {
|
|
14153
|
+
elements.btwButton.hidden = !hasBtwCommand;
|
|
14154
|
+
setOptionalControlState(
|
|
14155
|
+
elements.btwButton,
|
|
14156
|
+
hasBtwCommand,
|
|
14157
|
+
optionalFeatureUnavailableMessage("btwCommand"),
|
|
14158
|
+
);
|
|
14159
|
+
}
|
|
14160
|
+
|
|
13408
14161
|
const hasGitWorkflow = isOptionalFeatureEnabled("gitWorkflow");
|
|
13409
14162
|
elements.gitWorkflowButton.hidden = !hasGitWorkflow;
|
|
13410
14163
|
setOptionalControlState(
|
|
@@ -13449,6 +14202,16 @@ function renderOptionalFeatureControls() {
|
|
|
13449
14202
|
);
|
|
13450
14203
|
}
|
|
13451
14204
|
|
|
14205
|
+
const hasRemoteWebuiCommand = isOptionalFeatureEnabled("remoteWebui") && hasAvailableCommand("remote");
|
|
14206
|
+
if (elements.optionsRemoteButton) {
|
|
14207
|
+
elements.optionsRemoteButton.hidden = !hasRemoteWebuiCommand;
|
|
14208
|
+
setOptionalControlState(
|
|
14209
|
+
elements.optionsRemoteButton,
|
|
14210
|
+
hasRemoteWebuiCommand,
|
|
14211
|
+
optionalFeatureUnavailableMessage("remoteWebui"),
|
|
14212
|
+
);
|
|
14213
|
+
}
|
|
14214
|
+
|
|
13452
14215
|
renderOptionalFeaturePanel();
|
|
13453
14216
|
}
|
|
13454
14217
|
|
|
@@ -14751,6 +15514,10 @@ async function refreshState(tabContext = activeTabContext()) {
|
|
|
14751
15514
|
if (!isCurrentTabContext(tabContext)) return;
|
|
14752
15515
|
const previousState = currentState;
|
|
14753
15516
|
currentState = response.data || null;
|
|
15517
|
+
if (latestMessages.length) {
|
|
15518
|
+
latestMessagesSessionKey = resolveMessagesSessionKey(tabContext.tabId);
|
|
15519
|
+
cacheMessagesForTab(tabContext.tabId, latestMessages, latestMessagesSessionKey);
|
|
15520
|
+
}
|
|
14754
15521
|
const shouldRefreshGitFooter = gitFooterRelevantStateChanged(previousState, currentState);
|
|
14755
15522
|
syncActiveTabActivityFromState(currentState);
|
|
14756
15523
|
syncRunIndicatorFromState(currentState);
|
|
@@ -14904,7 +15671,32 @@ async function refreshFooterData(tabContext = activeTabContext()) {
|
|
|
14904
15671
|
|
|
14905
15672
|
// Session key of the last applied transcript fetch; deltas are only
|
|
14906
15673
|
// attempted while the tab+session is unchanged.
|
|
14907
|
-
|
|
15674
|
+
function resolveMessagesSessionKey(tabId = activeTabId) {
|
|
15675
|
+
if (!tabId) return "";
|
|
15676
|
+
const stateSessionId = tabId === activeTabId ? currentState?.sessionId : null;
|
|
15677
|
+
if (stateSessionId) return `${tabId}|${stateSessionId}`;
|
|
15678
|
+
if (latestMessagesSessionKey.startsWith(`${tabId}|`)) return latestMessagesSessionKey;
|
|
15679
|
+
const cached = tabMessagesCache.get(tabId);
|
|
15680
|
+
if (cached?.sessionKey?.startsWith(`${tabId}|`)) return cached.sessionKey;
|
|
15681
|
+
return `${tabId}|`;
|
|
15682
|
+
}
|
|
15683
|
+
|
|
15684
|
+
function cacheMessagesForTab(tabId = activeTabId, messages = latestMessages, sessionKey = latestMessagesSessionKey) {
|
|
15685
|
+
if (!tabId || !Array.isArray(messages)) return;
|
|
15686
|
+
const stateSessionKey = tabId === activeTabId && currentState?.sessionId ? `${tabId}|${currentState.sessionId}` : "";
|
|
15687
|
+
const resolvedSessionKey = stateSessionKey || (sessionKey?.startsWith(`${tabId}|`) ? sessionKey : resolveMessagesSessionKey(tabId));
|
|
15688
|
+
tabMessagesCache.set(tabId, { messages, sessionKey: resolvedSessionKey });
|
|
15689
|
+
}
|
|
15690
|
+
|
|
15691
|
+
function restoreCachedMessagesForActiveTab() {
|
|
15692
|
+
if (!activeTabId) return false;
|
|
15693
|
+
const cached = tabMessagesCache.get(activeTabId);
|
|
15694
|
+
if (!cached || !Array.isArray(cached.messages)) return false;
|
|
15695
|
+
latestMessages = cached.messages;
|
|
15696
|
+
latestMessagesSessionKey = cached.sessionKey || resolveMessagesSessionKey(activeTabId);
|
|
15697
|
+
renderMessages(latestMessages);
|
|
15698
|
+
return true;
|
|
15699
|
+
}
|
|
14908
15700
|
|
|
14909
15701
|
function messagesLookEqual(a, b) {
|
|
14910
15702
|
return !!a && !!b && a.role === b.role && String(a.timestamp || "") === String(b.timestamp || "")
|
|
@@ -14933,7 +15725,7 @@ function mergeMessagesDelta(previous, data) {
|
|
|
14933
15725
|
async function refreshMessages(tabContext = activeTabContext()) {
|
|
14934
15726
|
if (!tabContext.tabId) return;
|
|
14935
15727
|
const previousMessages = latestMessages;
|
|
14936
|
-
const sessionKey =
|
|
15728
|
+
const sessionKey = resolveMessagesSessionKey(tabContext.tabId);
|
|
14937
15729
|
let nextMessages = null;
|
|
14938
15730
|
if (previousMessages.length > 1 && sessionKey === latestMessagesSessionKey) {
|
|
14939
15731
|
// Delta fetch with a one-message overlap: the last known message is
|
|
@@ -14949,6 +15741,7 @@ async function refreshMessages(tabContext = activeTabContext()) {
|
|
|
14949
15741
|
}
|
|
14950
15742
|
latestMessages = nextMessages;
|
|
14951
15743
|
latestMessagesSessionKey = sessionKey;
|
|
15744
|
+
cacheMessagesForTab(tabContext.tabId, latestMessages, latestMessagesSessionKey);
|
|
14952
15745
|
const preserveLiveStream = liveStreamRenderActive();
|
|
14953
15746
|
if (!preserveLiveStream) resetStreamBubble();
|
|
14954
15747
|
renderMessages(latestMessages);
|
|
@@ -14989,7 +15782,7 @@ async function refreshModels(tabContext = activeTabContext()) {
|
|
|
14989
15782
|
syncModelSelectToState();
|
|
14990
15783
|
renderFooter();
|
|
14991
15784
|
renderFeedbackTray();
|
|
14992
|
-
if (elements.commandPaletteDialog?.open) renderCommandPalette();
|
|
15785
|
+
if (elements.commandPaletteDialog?.open) renderCommandPalette({ preserveScroll: true });
|
|
14993
15786
|
}
|
|
14994
15787
|
|
|
14995
15788
|
function syncModelSelectToState() {
|
|
@@ -15466,7 +16259,7 @@ async function refreshCommands(tabContext = activeTabContext()) {
|
|
|
15466
16259
|
availableCommands = normalizeCommands(response.data?.commands || []);
|
|
15467
16260
|
updateOptionalFeatureAvailability();
|
|
15468
16261
|
renderCommands();
|
|
15469
|
-
if (elements.commandPaletteDialog?.open) renderCommandPalette();
|
|
16262
|
+
if (elements.commandPaletteDialog?.open) renderCommandPalette({ preserveScroll: true });
|
|
15470
16263
|
}
|
|
15471
16264
|
|
|
15472
16265
|
function paletteText(value) {
|
|
@@ -15563,12 +16356,14 @@ function setCommandPaletteIndex(index) {
|
|
|
15563
16356
|
renderCommandPaletteList();
|
|
15564
16357
|
}
|
|
15565
16358
|
|
|
15566
|
-
function renderCommandPaletteList() {
|
|
16359
|
+
function renderCommandPaletteList({ preserveScroll = false } = {}) {
|
|
15567
16360
|
const list = elements.commandPaletteList;
|
|
15568
16361
|
if (!list) return;
|
|
16362
|
+
const scrollTop = preserveScroll ? list.scrollTop : 0;
|
|
15569
16363
|
list.replaceChildren();
|
|
15570
16364
|
if (!commandPaletteItems.length) {
|
|
15571
16365
|
list.append(make("div", "command-palette-empty muted", "No matching actions."));
|
|
16366
|
+
if (preserveScroll) list.scrollTop = scrollTop;
|
|
15572
16367
|
return;
|
|
15573
16368
|
}
|
|
15574
16369
|
commandPaletteItems.forEach((item, index) => {
|
|
@@ -15584,14 +16379,18 @@ function renderCommandPaletteList() {
|
|
|
15584
16379
|
);
|
|
15585
16380
|
list.append(button);
|
|
15586
16381
|
});
|
|
16382
|
+
if (preserveScroll) {
|
|
16383
|
+
list.scrollTop = scrollTop;
|
|
16384
|
+
return;
|
|
16385
|
+
}
|
|
15587
16386
|
const active = list.children[commandPaletteIndex];
|
|
15588
16387
|
active?.scrollIntoView({ block: "nearest" });
|
|
15589
16388
|
}
|
|
15590
16389
|
|
|
15591
|
-
function renderCommandPalette() {
|
|
16390
|
+
function renderCommandPalette({ preserveScroll = false } = {}) {
|
|
15592
16391
|
commandPaletteItems = filteredCommandPaletteItems();
|
|
15593
16392
|
if (commandPaletteIndex >= commandPaletteItems.length) commandPaletteIndex = 0;
|
|
15594
|
-
renderCommandPaletteList();
|
|
16393
|
+
renderCommandPaletteList({ preserveScroll });
|
|
15595
16394
|
}
|
|
15596
16395
|
|
|
15597
16396
|
function openCommandPalette(initialQuery = "") {
|
|
@@ -16068,7 +16867,7 @@ async function sendUserBashCommand(parsed, { usesPromptInput = false, targetTabI
|
|
|
16068
16867
|
await runUserBashCommand(parsed, { usesPromptInput, targetTabId });
|
|
16069
16868
|
}
|
|
16070
16869
|
|
|
16071
|
-
async function sendPrompt(kind = "prompt", explicitMessage, { targetTabId = activeTabId, throwOnError = false } = {}) {
|
|
16870
|
+
async function sendPrompt(kind = "prompt", explicitMessage, { targetTabId = activeTabId, throwOnError = false, streamingBehavior } = {}) {
|
|
16072
16871
|
const usesPromptInput = explicitMessage === undefined;
|
|
16073
16872
|
const rawMessage = usesPromptInput ? elements.promptInput.value : explicitMessage;
|
|
16074
16873
|
const originalMessage = String(rawMessage || "").trim();
|
|
@@ -16114,7 +16913,7 @@ async function sendPrompt(kind = "prompt", explicitMessage, { targetTabId = acti
|
|
|
16114
16913
|
response = await api("/api/follow-up", { method: "POST", body: bodyBase, tabId: targetTabId });
|
|
16115
16914
|
} else {
|
|
16116
16915
|
const body = { ...bodyBase };
|
|
16117
|
-
if (targetWasStreaming) body.streamingBehavior = busyBehavior;
|
|
16916
|
+
if (targetWasStreaming) body.streamingBehavior = streamingBehavior || busyBehavior;
|
|
16118
16917
|
response = await api("/api/prompt", { method: "POST", body, tabId: targetTabId });
|
|
16119
16918
|
}
|
|
16120
16919
|
applyResponseTab(response);
|
|
@@ -16167,7 +16966,7 @@ function hasQueuedDialogRequest(id) {
|
|
|
16167
16966
|
|
|
16168
16967
|
function removeQueuedDialogRequests(ids = []) {
|
|
16169
16968
|
const idSet = new Set(ids.map((id) => String(id)).filter(Boolean));
|
|
16170
|
-
if (idSet.size === 0) return;
|
|
16969
|
+
if (idSet.size === 0) return false;
|
|
16171
16970
|
for (let i = dialogQueue.length - 1; i >= 0; i -= 1) {
|
|
16172
16971
|
if (idSet.has(String(dialogQueue[i]?.id || ""))) dialogQueue.splice(i, 1);
|
|
16173
16972
|
}
|
|
@@ -16175,7 +16974,9 @@ function removeQueuedDialogRequests(ids = []) {
|
|
|
16175
16974
|
if (elements.dialog.open) elements.dialog.close();
|
|
16176
16975
|
activeDialog = null;
|
|
16177
16976
|
showNextDialog();
|
|
16977
|
+
return true;
|
|
16178
16978
|
}
|
|
16979
|
+
return false;
|
|
16179
16980
|
}
|
|
16180
16981
|
|
|
16181
16982
|
function handleExtensionUiRequest(request) {
|
|
@@ -16197,16 +16998,25 @@ function handleExtensionUiRequest(request) {
|
|
|
16197
16998
|
statusEntries.delete(statusKey);
|
|
16198
16999
|
}
|
|
16199
17000
|
if (statusKey === STATS_WEBUI_STATUS_KEY) handleStatsWebuiStatus(request.statusText);
|
|
17001
|
+
if (statusKey === BTW_WEBUI_STATUS_KEY) handleBtwWebuiStatus(request.statusText);
|
|
16200
17002
|
updateOptionalFeatureAvailability();
|
|
16201
17003
|
renderStatus();
|
|
16202
17004
|
return;
|
|
16203
17005
|
}
|
|
16204
|
-
case "setWidget":
|
|
16205
|
-
|
|
16206
|
-
|
|
17006
|
+
case "setWidget": {
|
|
17007
|
+
const widgetKey = request.widgetKey || request.id;
|
|
17008
|
+
if (widgetKey === "pi-remote-webui") {
|
|
17009
|
+
widgets.delete(widgetKey);
|
|
17010
|
+
if (Array.isArray(request.widgetLines)) mirrorRemoteWebuiWidgetToTranscript(widgetKey, request.widgetLines, request);
|
|
17011
|
+
} else if (Array.isArray(request.widgetLines)) {
|
|
17012
|
+
widgets.set(widgetKey, request);
|
|
17013
|
+
} else {
|
|
17014
|
+
widgets.delete(widgetKey);
|
|
17015
|
+
}
|
|
16207
17016
|
updateOptionalFeatureAvailability();
|
|
16208
17017
|
renderWidgets();
|
|
16209
17018
|
return;
|
|
17019
|
+
}
|
|
16210
17020
|
case "setTitle":
|
|
16211
17021
|
if (request.title) document.title = request.title;
|
|
16212
17022
|
return;
|
|
@@ -16239,6 +17049,7 @@ function handleExtensionUiRequest(request) {
|
|
|
16239
17049
|
async function sendDialogResponse(payload) {
|
|
16240
17050
|
const { tabId = activeTabId, ...body } = payload;
|
|
16241
17051
|
const tabContext = activeTabContext(tabId);
|
|
17052
|
+
const responseId = String(body.id || "");
|
|
16242
17053
|
try {
|
|
16243
17054
|
const response = await api("/api/extension-ui-response", { method: "POST", body, tabId });
|
|
16244
17055
|
if (!applyResponseTab(response) && decrementTabPendingBlockerCount(tabId)) renderTabs();
|
|
@@ -16246,6 +17057,7 @@ async function sendDialogResponse(payload) {
|
|
|
16246
17057
|
if (isCurrentTabContext(tabContext)) addEvent(error.message, "error");
|
|
16247
17058
|
} finally {
|
|
16248
17059
|
if (!isCurrentTabContext(tabContext)) return;
|
|
17060
|
+
if (responseId && activeDialog && String(activeDialog.id || "") !== responseId) return;
|
|
16249
17061
|
if (elements.dialog.open) elements.dialog.close();
|
|
16250
17062
|
activeDialog = null;
|
|
16251
17063
|
if (runIndicatorIsActive()) setRunIndicatorActivity("Continuing after your response…");
|
|
@@ -16336,6 +17148,7 @@ function handleInactiveTabEvent(event) {
|
|
|
16336
17148
|
|
|
16337
17149
|
function handleEvent(event) {
|
|
16338
17150
|
ingestEventTabActivity(event);
|
|
17151
|
+
trackAutoRetryStateFromEvent(event);
|
|
16339
17152
|
trackSkillsFromEvent(event);
|
|
16340
17153
|
if (!eventTargetsActiveTab(event)) {
|
|
16341
17154
|
handleInactiveTabEvent(event);
|
|
@@ -16375,6 +17188,10 @@ function handleEvent(event) {
|
|
|
16375
17188
|
clearContextUsageUnknownAfterCompaction(event.tabId || activeTabId);
|
|
16376
17189
|
statusEntries.clear();
|
|
16377
17190
|
widgets.clear();
|
|
17191
|
+
latestBtwWidgetPayload = null;
|
|
17192
|
+
btwWidgetDismissedId = "";
|
|
17193
|
+
btwWidgetComposerOpen = false;
|
|
17194
|
+
btwWidgetInputDraft = "";
|
|
16378
17195
|
resetOptionalFeatureAvailability();
|
|
16379
17196
|
renderStatus();
|
|
16380
17197
|
renderWidgets();
|
|
@@ -16390,6 +17207,14 @@ function handleEvent(event) {
|
|
|
16390
17207
|
removeQueuedDialogRequests(event.ids || []);
|
|
16391
17208
|
addEvent(`cancelled ${event.ids?.length || 0} pending extension UI request(s)`, "warn");
|
|
16392
17209
|
break;
|
|
17210
|
+
case "webui_extension_ui_resolved": {
|
|
17211
|
+
const closedActiveDialog = removeQueuedDialogRequests([event.id]);
|
|
17212
|
+
if (closedActiveDialog) {
|
|
17213
|
+
addEvent("extension UI request resolved");
|
|
17214
|
+
if (runIndicatorIsActive() && !activeDialog) setRunIndicatorActivity("Continuing after extension UI response…");
|
|
17215
|
+
}
|
|
17216
|
+
break;
|
|
17217
|
+
}
|
|
16393
17218
|
case "webui_app_runner_update":
|
|
16394
17219
|
setAppRunnerData(event.tabId || activeTabId, { cwd: event.cwd, activeRun: event.activeRun });
|
|
16395
17220
|
renderAppRunnerControls();
|
|
@@ -16719,6 +17544,7 @@ elements.busyPromptBehaviorMenu?.addEventListener("keydown", (event) => {
|
|
|
16719
17544
|
});
|
|
16720
17545
|
elements.steerButton.addEventListener("click", () => sendPromptFromModeButton("steer", elements.steerButton));
|
|
16721
17546
|
elements.followUpButton.addEventListener("click", () => sendPromptFromModeButton("follow-up", elements.followUpButton));
|
|
17547
|
+
elements.btwButton?.addEventListener("click", () => sendBtwPromptFromButton());
|
|
16722
17548
|
elements.terminalTabsToggleButton.addEventListener("click", () => {
|
|
16723
17549
|
setMobileTabsExpanded(!document.body.classList.contains("mobile-tabs-expanded"));
|
|
16724
17550
|
});
|
|
@@ -16894,6 +17720,7 @@ elements.nativeToolsButton.addEventListener("click", () => runNativeCommandMenu(
|
|
|
16894
17720
|
elements.optionsCommandPaletteButton.addEventListener("click", () => openCommandPalette());
|
|
16895
17721
|
elements.optionsResumeButton.addEventListener("click", () => runNativeCommandMenu("/resume"));
|
|
16896
17722
|
elements.optionsReloadButton.addEventListener("click", () => runNativeCommandMenu("/reload"));
|
|
17723
|
+
elements.optionsRemoteButton.addEventListener("click", () => runNativeCommandMenu("/remote"));
|
|
16897
17724
|
elements.optionsNameButton.addEventListener("click", () => runNativeCommandMenu("/name"));
|
|
16898
17725
|
elements.optionsCloneButton.addEventListener("click", () => runNativeCommandMenu("/clone"));
|
|
16899
17726
|
elements.optionsSettingsButton.addEventListener("click", () => runNativeCommandMenu("/settings"));
|
|
@@ -16954,6 +17781,7 @@ elements.commandPaletteDialog?.addEventListener("cancel", (event) => {
|
|
|
16954
17781
|
event.preventDefault();
|
|
16955
17782
|
closeCommandPalette();
|
|
16956
17783
|
});
|
|
17784
|
+
elements.commandPaletteCloseButton?.addEventListener("click", closeCommandPalette);
|
|
16957
17785
|
elements.commandPaletteInput?.addEventListener("input", () => {
|
|
16958
17786
|
commandPaletteIndex = 0;
|
|
16959
17787
|
renderCommandPalette();
|
|
@@ -16986,11 +17814,104 @@ elements.editRetryCancelButton?.addEventListener("click", closeEditRetryDialog);
|
|
|
16986
17814
|
elements.editRetryForkButton?.addEventListener("click", () => submitEditRetry({ send: false }));
|
|
16987
17815
|
elements.editRetrySendButton?.addEventListener("click", () => submitEditRetry({ send: true }));
|
|
16988
17816
|
|
|
16989
|
-
function
|
|
17817
|
+
function abortButtonHoldSeconds() {
|
|
17818
|
+
return String(Math.round(ABORT_LONG_PRESS_MS / 1000));
|
|
17819
|
+
}
|
|
17820
|
+
|
|
17821
|
+
function abortButtonReadyTitle() {
|
|
17822
|
+
return `Hold Esc or the Abort button for ${abortButtonHoldSeconds()} seconds to abort the active Pi run`;
|
|
17823
|
+
}
|
|
17824
|
+
|
|
17825
|
+
function clearAbortLongPressResetTimer() {
|
|
17826
|
+
clearTimeout(abortLongPressResetTimer);
|
|
17827
|
+
abortLongPressResetTimer = null;
|
|
17828
|
+
}
|
|
17829
|
+
|
|
17830
|
+
function clearAbortLongPressCompletionTimers() {
|
|
16990
17831
|
clearTimeout(abortLongPressTimer);
|
|
17832
|
+
clearInterval(abortLongPressTickTimer);
|
|
16991
17833
|
abortLongPressTimer = null;
|
|
17834
|
+
abortLongPressTickTimer = null;
|
|
17835
|
+
}
|
|
17836
|
+
|
|
17837
|
+
function isAbortLongPressActive() {
|
|
17838
|
+
return abortLongPressStartedAt > 0;
|
|
17839
|
+
}
|
|
17840
|
+
|
|
17841
|
+
function abortLongPressRemainingMs() {
|
|
17842
|
+
if (!abortLongPressStartedAt || !abortLongPressDeadlineAt) return ABORT_LONG_PRESS_MS;
|
|
17843
|
+
return Math.max(0, abortLongPressDeadlineAt - performance.now());
|
|
17844
|
+
}
|
|
17845
|
+
|
|
17846
|
+
function formatAbortLongPressRemaining(ms) {
|
|
17847
|
+
return (Math.ceil(Math.max(0, ms) / 100) / 10).toFixed(1);
|
|
17848
|
+
}
|
|
17849
|
+
|
|
17850
|
+
function abortLongPressLabel() {
|
|
17851
|
+
const remaining = formatAbortLongPressRemaining(abortLongPressRemainingMs());
|
|
17852
|
+
return abortLongPressSource === "escape" ? `Hold Esc ${remaining}s` : `Hold ${remaining}s`;
|
|
17853
|
+
}
|
|
17854
|
+
|
|
17855
|
+
function renderAbortLongPressAffordance() {
|
|
17856
|
+
const label = abortLongPressLabel();
|
|
17857
|
+
elements.abortButton.textContent = label;
|
|
17858
|
+
elements.abortButton.title = `${label} more to abort the active Pi run`;
|
|
17859
|
+
elements.abortButton.setAttribute("aria-label", elements.abortButton.title);
|
|
17860
|
+
}
|
|
17861
|
+
|
|
17862
|
+
function completeAbortLongPress() {
|
|
17863
|
+
if (!isAbortLongPressActive()) return;
|
|
17864
|
+
if (abortLongPressReleasePending) return;
|
|
17865
|
+
const source = abortLongPressSource;
|
|
17866
|
+
clearAbortLongPressResetTimer();
|
|
17867
|
+
clearAbortLongPressCompletionTimers();
|
|
17868
|
+
abortLongPressHandled = true;
|
|
17869
|
+
if (isAbortAvailable()) abortActiveRun({ source });
|
|
17870
|
+
else {
|
|
17871
|
+
resetAbortLongPressAffordance();
|
|
17872
|
+
updateComposerModeButtons();
|
|
17873
|
+
}
|
|
17874
|
+
}
|
|
17875
|
+
|
|
17876
|
+
function tickAbortLongPressAffordance() {
|
|
17877
|
+
if (!isAbortLongPressActive()) return;
|
|
17878
|
+
renderAbortLongPressAffordance();
|
|
17879
|
+
if (abortLongPressRemainingMs() <= 0) completeAbortLongPress();
|
|
17880
|
+
}
|
|
17881
|
+
|
|
17882
|
+
function resumeAbortLongPressAffordance() {
|
|
17883
|
+
if (!isAbortLongPressActive()) return;
|
|
17884
|
+
clearAbortLongPressResetTimer();
|
|
17885
|
+
abortLongPressReleasePending = false;
|
|
17886
|
+
tickAbortLongPressAffordance();
|
|
17887
|
+
}
|
|
17888
|
+
|
|
17889
|
+
function scheduleAbortLongPressReleaseReset() {
|
|
17890
|
+
if (!isAbortLongPressActive()) return;
|
|
17891
|
+
abortLongPressReleasePending = true;
|
|
17892
|
+
clearAbortLongPressResetTimer();
|
|
17893
|
+
abortLongPressResetTimer = setTimeout(() => {
|
|
17894
|
+
abortLongPressResetTimer = null;
|
|
17895
|
+
if (!abortLongPressReleasePending) return;
|
|
17896
|
+
resetAbortLongPressAffordance();
|
|
17897
|
+
updateComposerModeButtons();
|
|
17898
|
+
}, ABORT_LONG_PRESS_RELEASE_GRACE_MS);
|
|
17899
|
+
}
|
|
17900
|
+
|
|
17901
|
+
function resetAbortLongPressAffordance() {
|
|
17902
|
+
clearAbortLongPressResetTimer();
|
|
17903
|
+
clearAbortLongPressCompletionTimers();
|
|
17904
|
+
abortLongPressStartedAt = 0;
|
|
17905
|
+
abortLongPressDeadlineAt = 0;
|
|
17906
|
+
abortLongPressSource = "long-press";
|
|
17907
|
+
abortLongPressReleasePending = false;
|
|
16992
17908
|
elements.abortButton.classList.remove("long-pressing");
|
|
16993
|
-
|
|
17909
|
+
elements.abortButton.style.removeProperty("--abort-long-press-duration");
|
|
17910
|
+
if (!abortRequestInFlight) {
|
|
17911
|
+
elements.abortButton.textContent = "Abort";
|
|
17912
|
+
elements.abortButton.title = isAbortAvailable() ? abortButtonReadyTitle() : "Abort is available while Pi is running";
|
|
17913
|
+
elements.abortButton.setAttribute("aria-label", elements.abortButton.title);
|
|
17914
|
+
}
|
|
16994
17915
|
}
|
|
16995
17916
|
|
|
16996
17917
|
async function abortActiveRun({ source = "button" } = {}) {
|
|
@@ -17026,31 +17947,41 @@ async function abortActiveRun({ source = "button" } = {}) {
|
|
|
17026
17947
|
}
|
|
17027
17948
|
}
|
|
17028
17949
|
|
|
17029
|
-
function startAbortLongPress(event) {
|
|
17030
|
-
if (!isAbortAvailable() || abortRequestInFlight) return;
|
|
17031
|
-
if (event
|
|
17950
|
+
function startAbortLongPress(event, { source = "long-press" } = {}) {
|
|
17951
|
+
if (!isAbortAvailable() || abortRequestInFlight) return false;
|
|
17952
|
+
if (source !== "escape" && event?.button !== undefined && event.button !== 0) return false;
|
|
17953
|
+
if (isAbortLongPressActive()) {
|
|
17954
|
+
resumeAbortLongPressAffordance();
|
|
17955
|
+
return true;
|
|
17956
|
+
}
|
|
17032
17957
|
resetAbortLongPressAffordance();
|
|
17033
17958
|
abortLongPressHandled = false;
|
|
17959
|
+
abortLongPressReleasePending = false;
|
|
17960
|
+
abortLongPressSource = source;
|
|
17961
|
+
abortLongPressStartedAt = performance.now();
|
|
17962
|
+
abortLongPressDeadlineAt = abortLongPressStartedAt + ABORT_LONG_PRESS_MS;
|
|
17963
|
+
elements.abortButton.style.setProperty("--abort-long-press-duration", `${ABORT_LONG_PRESS_MS}ms`);
|
|
17034
17964
|
elements.abortButton.classList.add("long-pressing");
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
abortActiveRun({ source: "long-press" });
|
|
17040
|
-
}, ABORT_LONG_PRESS_MS);
|
|
17965
|
+
renderAbortLongPressAffordance();
|
|
17966
|
+
abortLongPressTickTimer = setInterval(tickAbortLongPressAffordance, ABORT_LONG_PRESS_TICK_MS);
|
|
17967
|
+
abortLongPressTimer = setTimeout(tickAbortLongPressAffordance, ABORT_LONG_PRESS_MS + 10);
|
|
17968
|
+
return true;
|
|
17041
17969
|
}
|
|
17042
17970
|
|
|
17043
17971
|
elements.abortButton.addEventListener("pointerdown", startAbortLongPress);
|
|
17044
17972
|
for (const eventName of ["pointerup", "pointerleave", "pointercancel", "blur"]) {
|
|
17045
17973
|
elements.abortButton.addEventListener(eventName, resetAbortLongPressAffordance);
|
|
17046
17974
|
}
|
|
17975
|
+
elements.abortButton.addEventListener("keydown", (event) => {
|
|
17976
|
+
if (event.key !== " " && event.key !== "Enter") return;
|
|
17977
|
+
if (startAbortLongPress(event)) event.preventDefault();
|
|
17978
|
+
});
|
|
17979
|
+
elements.abortButton.addEventListener("keyup", (event) => {
|
|
17980
|
+
if (event.key === " " || event.key === "Enter") resetAbortLongPressAffordance();
|
|
17981
|
+
});
|
|
17047
17982
|
elements.abortButton.addEventListener("click", (event) => {
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
abortLongPressHandled = false;
|
|
17051
|
-
return;
|
|
17052
|
-
}
|
|
17053
|
-
abortActiveRun({ source: "button" });
|
|
17983
|
+
event.preventDefault();
|
|
17984
|
+
if (abortLongPressHandled) abortLongPressHandled = false;
|
|
17054
17985
|
});
|
|
17055
17986
|
elements.newSessionButton.addEventListener("click", async () => {
|
|
17056
17987
|
setComposerActionsOpen(false);
|
|
@@ -17166,6 +18097,10 @@ elements.chat.addEventListener("scroll", () => {
|
|
|
17166
18097
|
markTabOutputSeen();
|
|
17167
18098
|
updateStickyUserPromptButton();
|
|
17168
18099
|
}, { passive: true });
|
|
18100
|
+
document.addEventListener("pointerdown", beginPointerActivation, { capture: true, passive: true });
|
|
18101
|
+
document.addEventListener("pointerup", finishPointerActivation, { capture: true, passive: true });
|
|
18102
|
+
document.addEventListener("pointercancel", cancelPointerActivation, { capture: true, passive: true });
|
|
18103
|
+
window.addEventListener("blur", cancelPointerActivation, { passive: true });
|
|
17169
18104
|
document.addEventListener("pointerdown", (event) => {
|
|
17170
18105
|
if (openTerminalTabGroupKey && !event.target?.closest?.(".terminal-tab-group")) {
|
|
17171
18106
|
clearOpenTerminalTabGroup(openTerminalTabGroupKey);
|
|
@@ -17386,12 +18321,14 @@ window.addEventListener("keydown", (event) => {
|
|
|
17386
18321
|
window.addEventListener("keydown", handleNativeAppShortcut, { capture: true });
|
|
17387
18322
|
document.addEventListener("visibilitychange", () => {
|
|
17388
18323
|
if (document.visibilityState === "visible") scheduleForegroundReconcile("visibility resume", 0);
|
|
18324
|
+
else resetAbortLongPressAffordance();
|
|
17389
18325
|
});
|
|
17390
18326
|
window.addEventListener("pageshow", () => scheduleForegroundReconcile("page show", 0));
|
|
17391
18327
|
window.addEventListener("focus", () => scheduleForegroundReconcile("window focus"));
|
|
17392
18328
|
window.addEventListener("online", () => scheduleForegroundReconcile("network online", 0));
|
|
17393
18329
|
window.addEventListener("keydown", (event) => {
|
|
17394
18330
|
if (event.key !== "Escape") return;
|
|
18331
|
+
if (event.defaultPrevented) return;
|
|
17395
18332
|
if (elements.dialog?.open || elements.pathPickerDialog?.open || elements.gitChangesDialog?.open || elements.commandPaletteDialog?.open || elements.editRetryDialog?.open) return;
|
|
17396
18333
|
if (publishMenuOpen) {
|
|
17397
18334
|
setPublishMenuOpen(false);
|
|
@@ -17437,6 +18374,20 @@ window.addEventListener("keydown", (event) => {
|
|
|
17437
18374
|
hideCommandSuggestions();
|
|
17438
18375
|
return;
|
|
17439
18376
|
}
|
|
18377
|
+
if (isSidePanelOverlayView() && !document.body.classList.contains("side-panel-collapsed")) {
|
|
18378
|
+
setSidePanelCollapsed(true);
|
|
18379
|
+
return;
|
|
18380
|
+
}
|
|
18381
|
+
if (isAbortAvailable()) {
|
|
18382
|
+
event.preventDefault();
|
|
18383
|
+
if (abortLongPressSource === "escape" && isAbortLongPressActive()) resumeAbortLongPressAffordance();
|
|
18384
|
+
else if (!event.repeat) startAbortLongPress(event, { source: "escape" });
|
|
18385
|
+
return;
|
|
18386
|
+
}
|
|
18387
|
+
if (event.repeat) {
|
|
18388
|
+
event.preventDefault();
|
|
18389
|
+
return;
|
|
18390
|
+
}
|
|
17440
18391
|
if (document.activeElement === elements.promptInput && !elements.promptInput.value.trim() && doubleEscapeAction !== "none") {
|
|
17441
18392
|
const now = Date.now();
|
|
17442
18393
|
if (now - lastEmptyPromptEscapeTime < 500) {
|
|
@@ -17447,14 +18398,13 @@ window.addEventListener("keydown", (event) => {
|
|
|
17447
18398
|
}
|
|
17448
18399
|
lastEmptyPromptEscapeTime = now;
|
|
17449
18400
|
}
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
}
|
|
18401
|
+
});
|
|
18402
|
+
window.addEventListener("keyup", (event) => {
|
|
18403
|
+
if (event.key === "Escape" && abortLongPressSource === "escape") scheduleAbortLongPressReleaseReset();
|
|
18404
|
+
}, { capture: true });
|
|
18405
|
+
window.addEventListener("blur", () => {
|
|
18406
|
+
if (abortLongPressSource === "escape") scheduleAbortLongPressReleaseReset();
|
|
18407
|
+
else resetAbortLongPressAffordance();
|
|
17458
18408
|
});
|
|
17459
18409
|
|
|
17460
18410
|
elements.gitChangesRefreshButton?.addEventListener("click", refreshGitChangesDialog);
|