@firstpick/pi-package-webui 0.4.3 → 0.4.5
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 +4 -2
- package/bin/pi-webui.mjs +28 -8
- package/package.json +6 -4
- package/public/app.js +528 -4
- package/public/index.html +11 -2
- package/public/styles.css +146 -1
- package/tests/fixtures/fake-pi.mjs +0 -0
- package/tests/mobile-static.test.mjs +34 -9
- package/tests/native-parity-harness.test.mjs +1 -1
- package/tests/native-parity.test.mjs +2 -2
- package/start-webui.ps1 +0 -368
- package/start-webui.sh +0 -472
- /package/{WEBUI_TUI_NATIVE_PARITY.json → dev/docs/WEBUI_TUI_NATIVE_PARITY.json} +0 -0
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"),
|
|
@@ -301,6 +302,11 @@ let statsOverlayLastScope = "14";
|
|
|
301
302
|
let statsOverlayCalibrationMessage = "";
|
|
302
303
|
let statsOverlayCalibrationBusy = "";
|
|
303
304
|
let latestStatsOverlayPayload = null;
|
|
305
|
+
let latestBtwWidgetPayload = null;
|
|
306
|
+
let btwWidgetDismissedId = "";
|
|
307
|
+
let btwWidgetComposerOpen = false;
|
|
308
|
+
let btwWidgetInputDraft = "";
|
|
309
|
+
let btwWidgetFocusAfterRender = false;
|
|
304
310
|
let latestWorkspace = null;
|
|
305
311
|
let latestNetwork = null;
|
|
306
312
|
let webuiVersion = "";
|
|
@@ -423,6 +429,14 @@ const GIT_INIT_STACK_STORAGE_KEY = "pi-webui-git-init-stack";
|
|
|
423
429
|
const STATS_WEBUI_STATUS_KEY = "stats-webui";
|
|
424
430
|
const STATS_WEBUI_PAYLOAD_TYPE = "firstpick.pi-extension-stats.overlay";
|
|
425
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;
|
|
426
440
|
const GIT_CHANGES_RENDER_ROW_LIMIT = 4000;
|
|
427
441
|
const LAST_USER_PROMPT_STORAGE_KEY = "pi-webui-last-user-prompts";
|
|
428
442
|
const PROMPT_HISTORY_STORAGE_KEY = "pi-webui-prompt-history";
|
|
@@ -496,9 +510,11 @@ let liveToolRenderTimer = null;
|
|
|
496
510
|
// commands and live widget events), not npm package folders. This keeps local dev
|
|
497
511
|
// symlinks and independently installed packages working.
|
|
498
512
|
const optionalFeatureAvailability = {
|
|
513
|
+
btwCommand: false,
|
|
499
514
|
gitWorkflow: false,
|
|
500
515
|
releaseNpm: false,
|
|
501
516
|
releaseAur: false,
|
|
517
|
+
workflows: false,
|
|
502
518
|
safetyGuard: false,
|
|
503
519
|
statsCommand: false,
|
|
504
520
|
gitFooterStatus: false,
|
|
@@ -509,6 +525,13 @@ const optionalFeatureAvailability = {
|
|
|
509
525
|
themeBundle: false,
|
|
510
526
|
};
|
|
511
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
|
+
},
|
|
512
535
|
{
|
|
513
536
|
id: "gitWorkflow",
|
|
514
537
|
label: "Guided Git workflow",
|
|
@@ -530,6 +553,13 @@ const OPTIONAL_FEATURES = [
|
|
|
530
553
|
capabilityLabel: "/release-aur",
|
|
531
554
|
description: "Publish menu action, setup helpers, skills, and AUR release widgets.",
|
|
532
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
|
+
},
|
|
533
563
|
{
|
|
534
564
|
id: "safetyGuard",
|
|
535
565
|
label: "Safety guard",
|
|
@@ -589,11 +619,16 @@ const OPTIONAL_FEATURES = [
|
|
|
589
619
|
];
|
|
590
620
|
const OPTIONAL_FEATURE_BY_ID = new Map(OPTIONAL_FEATURES.map((feature) => [feature.id, feature]));
|
|
591
621
|
const OPTIONAL_COMMAND_FEATURES = new Map([
|
|
622
|
+
["btw", "btwCommand"],
|
|
623
|
+
["btw-transfer", "btwCommand"],
|
|
624
|
+
["btw-status", "btwCommand"],
|
|
592
625
|
["git-staged-msg", "gitWorkflow"],
|
|
593
626
|
["git-branch-name", "gitWorkflow"],
|
|
594
627
|
["pr", "gitWorkflow"],
|
|
595
628
|
["release-npm", "releaseNpm"],
|
|
596
629
|
["release-aur", "releaseAur"],
|
|
630
|
+
["workflow", "workflows"],
|
|
631
|
+
["workflow-clear", "workflows"],
|
|
597
632
|
["safety-guard", "safetyGuard"],
|
|
598
633
|
["skills", "tuiSkillsCommand"],
|
|
599
634
|
["tools", "tuiToolsCommand"],
|
|
@@ -604,6 +639,8 @@ const OPTIONAL_COMMAND_FEATURES = new Map([
|
|
|
604
639
|
]);
|
|
605
640
|
const HIDDEN_COMMAND_NAMES = new Set(["webui-tree-navigate", "webui-helper"]);
|
|
606
641
|
HIDDEN_COMMAND_NAMES.add("stats-webui");
|
|
642
|
+
HIDDEN_COMMAND_NAMES.add("btw-status");
|
|
643
|
+
HIDDEN_COMMAND_NAMES.add("btw-transfer");
|
|
607
644
|
const NATIVE_SELECTOR_COMMANDS = new Set(["model", "settings", "theme", "fork", "clone", "name", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"]);
|
|
608
645
|
const SETTINGS_THINKING_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
609
646
|
const SETTINGS_TRANSPORT_OPTIONS = ["sse", "websocket", "websocket-cached", "auto"];
|
|
@@ -627,6 +664,7 @@ const optionalFeatureInstallInProgress = new Set();
|
|
|
627
664
|
const optionalFeaturePackageStatuses = new Map();
|
|
628
665
|
const optionalFeatureInstallMessages = new Map();
|
|
629
666
|
const gitFooterPayloadRefreshInFlightByTab = new Set();
|
|
667
|
+
const gitFooterPiCalibrationInFlightByTab = new Set();
|
|
630
668
|
|
|
631
669
|
function createGitWorkflowActionsDone(patch = {}) {
|
|
632
670
|
return {
|
|
@@ -3511,6 +3549,15 @@ function setOptionalFeatureDisabled(featureId, disabled) {
|
|
|
3511
3549
|
statusEntries.delete(GIT_FOOTER_WEBUI_STATUS_KEY);
|
|
3512
3550
|
clearGitFooterWebuiPayloadCache();
|
|
3513
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
|
+
}
|
|
3514
3561
|
storeDisabledOptionalFeatures();
|
|
3515
3562
|
renderOptionalFeatureDependentDisplays();
|
|
3516
3563
|
const tabContext = activeTabContext();
|
|
@@ -4145,6 +4192,10 @@ function resetActiveTabUi() {
|
|
|
4145
4192
|
currentState = null;
|
|
4146
4193
|
latestStats = null;
|
|
4147
4194
|
latestStatsOverlayPayload = null;
|
|
4195
|
+
latestBtwWidgetPayload = null;
|
|
4196
|
+
btwWidgetDismissedId = "";
|
|
4197
|
+
btwWidgetComposerOpen = false;
|
|
4198
|
+
btwWidgetInputDraft = "";
|
|
4148
4199
|
latestWorkspace = null;
|
|
4149
4200
|
latestMessages = [];
|
|
4150
4201
|
latestMessagesSessionKey = "";
|
|
@@ -4546,6 +4597,20 @@ function tabHasActiveAgent(tab) {
|
|
|
4546
4597
|
return !!activity.isWorking || indicator.state === "working" || indicator.state === "blocked";
|
|
4547
4598
|
}
|
|
4548
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
|
+
|
|
4549
4614
|
function confirmCloseTerminalTabs(targetTabs, label) {
|
|
4550
4615
|
const count = targetTabs.length;
|
|
4551
4616
|
const noun = count === 1 ? "tab" : "tabs";
|
|
@@ -5268,6 +5333,58 @@ async function toggleFooterAutoCompaction(tabContext = activeTabContext()) {
|
|
|
5268
5333
|
}
|
|
5269
5334
|
}
|
|
5270
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
|
+
|
|
5271
5388
|
function applyGitFooterContextToggleOptions(chip, options) {
|
|
5272
5389
|
if (chip?.key !== "context") return "";
|
|
5273
5390
|
options.onClick = () => toggleFooterAutoCompaction();
|
|
@@ -5449,6 +5566,7 @@ function footerMeta(label, value, className = "", options = {}) {
|
|
|
5449
5566
|
}
|
|
5450
5567
|
|
|
5451
5568
|
const FOOTER_PAYLOAD_TONES = new Set(["pink", "blue", "mauve", "yellow", "green", "teal"]);
|
|
5569
|
+
const FOOTER_PAYLOAD_ACTIONS = new Set(["calibrate-current", "calibrate-probe"]);
|
|
5452
5570
|
const FOOTER_CHANGED_FILE_KINDS = new Set(["modified", "staged", "untracked", "conflicted"]);
|
|
5453
5571
|
const FOOTER_CHANGED_FILE_KIND_ORDER = ["modified", "staged", "untracked", "conflicted"];
|
|
5454
5572
|
const FOOTER_CHANGED_FILE_KIND_LABELS = {
|
|
@@ -5518,6 +5636,7 @@ function normalizeFooterPayloadChip(value, index) {
|
|
|
5518
5636
|
tone: FOOTER_PAYLOAD_TONES.has(value.tone) ? value.tone : "",
|
|
5519
5637
|
title: cleanFooterPayloadText(value.title, "", 4000),
|
|
5520
5638
|
};
|
|
5639
|
+
if (FOOTER_PAYLOAD_ACTIONS.has(value.action)) chip.action = value.action;
|
|
5521
5640
|
if (Array.isArray(value.files)) {
|
|
5522
5641
|
const files = value.files.map(normalizeFooterPayloadChangedFile).filter(Boolean).slice(0, 80);
|
|
5523
5642
|
if (files.length) chip.files = files;
|
|
@@ -5752,7 +5871,7 @@ function applyFooterChangedFilesDropdown(node, chip) {
|
|
|
5752
5871
|
|
|
5753
5872
|
function renderGitFooterPayloadMetric(chip) {
|
|
5754
5873
|
const options = { tooltipAlign: gitFooterTooltipAlign(chip) };
|
|
5755
|
-
const action = applyGitFooterContextToggleOptions(chip, options);
|
|
5874
|
+
const action = applyGitFooterPiCalibrationOptions(chip, options) || applyGitFooterContextToggleOptions(chip, options);
|
|
5756
5875
|
options.title = gitFooterPayloadTooltip(chip, { action });
|
|
5757
5876
|
const node = footerMetric(chip.icon || "•", chip.label, chip.value, chip.tone ? `tone-${chip.tone}` : "", options);
|
|
5758
5877
|
return chip.contextUsage ? applyFooterContextUsage(node, chip.contextUsage) : node;
|
|
@@ -7212,6 +7331,12 @@ async function changeActiveTabCwd() {
|
|
|
7212
7331
|
const currentCwd = latestWorkspace?.cwd || tab.cwd || "";
|
|
7213
7332
|
const cwd = await pickCwd(tab, currentCwd);
|
|
7214
7333
|
if (!isCurrentTabContext(tabContext) || !cwd || cwd === currentCwd) return;
|
|
7334
|
+
|
|
7335
|
+
if (shouldOpenCwdChangeInNewTab(tab)) {
|
|
7336
|
+
await createTerminalTab(cwd, { triggerButton: null });
|
|
7337
|
+
return;
|
|
7338
|
+
}
|
|
7339
|
+
|
|
7215
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;
|
|
7216
7341
|
|
|
7217
7342
|
saveActiveDraft();
|
|
@@ -7715,6 +7840,10 @@ function releaseNpmLineTone(line) {
|
|
|
7715
7840
|
if (/^(WARN|warning)\b/i.test(clean)) return "warn";
|
|
7716
7841
|
if (/^(INFO|npm notice|notice)\b/i.test(clean)) return "info";
|
|
7717
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";
|
|
7718
7847
|
return "";
|
|
7719
7848
|
}
|
|
7720
7849
|
|
|
@@ -7924,6 +8053,74 @@ function renderReleaseAurLogWidget() {
|
|
|
7924
8053
|
return node;
|
|
7925
8054
|
}
|
|
7926
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
|
+
|
|
7927
8124
|
function activeAppRunnerData() {
|
|
7928
8125
|
return activeTabId ? appRunnerDataByTab.get(activeTabId) || { runners: [], activeRun: null } : { runners: [], activeRun: null };
|
|
7929
8126
|
}
|
|
@@ -8914,6 +9111,274 @@ function handleStatsWebuiStatus(statusText) {
|
|
|
8914
9111
|
if (payload.open || elements.statsOverlayDialog?.open) renderStatsOverlay();
|
|
8915
9112
|
}
|
|
8916
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
|
+
|
|
8917
9382
|
function remoteWebuiWidgetLines(lines = []) {
|
|
8918
9383
|
return (Array.isArray(lines) ? lines : [])
|
|
8919
9384
|
.map(stripAnsi)
|
|
@@ -8939,8 +9404,12 @@ function renderWidgets() {
|
|
|
8939
9404
|
if (releaseAurOutput) elements.widgetArea.append(releaseAurOutput);
|
|
8940
9405
|
const releaseAurLog = renderReleaseAurLogWidget();
|
|
8941
9406
|
if (releaseAurLog) elements.widgetArea.append(releaseAurLog);
|
|
9407
|
+
const workflowSubprocessWidget = renderWorkflowSubprocessWidget();
|
|
9408
|
+
if (workflowSubprocessWidget) elements.widgetArea.append(workflowSubprocessWidget);
|
|
8942
9409
|
const appRunnerWidget = renderAppRunnerWidget();
|
|
8943
9410
|
if (appRunnerWidget) elements.widgetArea.append(appRunnerWidget);
|
|
9411
|
+
const btwWidget = renderBtwOutputWidget();
|
|
9412
|
+
if (btwWidget) elements.widgetArea.append(btwWidget);
|
|
8944
9413
|
|
|
8945
9414
|
for (const [key, value] of widgets) {
|
|
8946
9415
|
const widgetFeatureId = optionalFeatureWidgetFeatureId(key);
|
|
@@ -13353,6 +13822,41 @@ function sendPromptFromModeButton(kind, button) {
|
|
|
13353
13822
|
sendPrompt(kind);
|
|
13354
13823
|
}
|
|
13355
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
|
+
|
|
13356
13860
|
function setPublishMenuOpen(open) {
|
|
13357
13861
|
publishMenuOpen = !!open;
|
|
13358
13862
|
elements.publishButton.setAttribute("aria-expanded", publishMenuOpen ? "true" : "false");
|
|
@@ -13538,9 +14042,11 @@ function requestGitFooterWebuiPayload(tabContext = activeTabContext(), { force =
|
|
|
13538
14042
|
}
|
|
13539
14043
|
|
|
13540
14044
|
function updateOptionalFeatureAvailability() {
|
|
14045
|
+
optionalFeatureAvailability.btwCommand = hasAvailableCommand("btw") || optionalFeatureAvailability.btwCommand || statusEntries.has(BTW_WEBUI_STATUS_KEY) || widgets.has(BTW_OUTPUT_WIDGET_KEY);
|
|
13541
14046
|
optionalFeatureAvailability.gitWorkflow = hasAvailableCommand("git-staged-msg");
|
|
13542
14047
|
optionalFeatureAvailability.releaseNpm = hasAvailableCommand("release-npm");
|
|
13543
14048
|
optionalFeatureAvailability.releaseAur = hasAvailableCommand("release-aur");
|
|
14049
|
+
optionalFeatureAvailability.workflows = hasAvailableCommand("workflow") || hasAvailableCommand("workflow-clear") || optionalFeatureAvailability.workflows || widgets.has("workflow") || widgets.has("workflow:subprocess");
|
|
13544
14050
|
optionalFeatureAvailability.safetyGuard = hasAvailableCommand("safety-guard") || optionalFeatureAvailability.safetyGuard || statusEntries.has("safety-guard");
|
|
13545
14051
|
optionalFeatureAvailability.statsCommand = hasAvailableCommand("stats");
|
|
13546
14052
|
optionalFeatureAvailability.gitFooterStatus = hasAvailableCommand("git-footer-refresh") || optionalFeatureAvailability.gitFooterStatus || statusEntries.has("git-footer") || statusEntries.has(GIT_FOOTER_WEBUI_STATUS_KEY);
|
|
@@ -13569,15 +14075,17 @@ function optionalFeatureStatus(featureId) {
|
|
|
13569
14075
|
}
|
|
13570
14076
|
|
|
13571
14077
|
function optionalFeatureWidgetFeatureId(key) {
|
|
14078
|
+
if (key.startsWith("btw:")) return "btwCommand";
|
|
13572
14079
|
if (key.startsWith("release-npm:")) return "releaseNpm";
|
|
13573
14080
|
if (key.startsWith("release-aur:")) return "releaseAur";
|
|
14081
|
+
if (key === "workflow" || key.startsWith("workflow:")) return "workflows";
|
|
13574
14082
|
if (key === "todo-progress") return "todoProgressWidget";
|
|
13575
14083
|
if (key === "pi-remote-webui") return "remoteWebui";
|
|
13576
14084
|
return null;
|
|
13577
14085
|
}
|
|
13578
14086
|
|
|
13579
14087
|
function optionalFeatureWidgetHasSpecializedRenderer(key) {
|
|
13580
|
-
return key.startsWith("release-npm:") || key.startsWith("release-aur:");
|
|
14088
|
+
return key.startsWith("btw:") || key.startsWith("release-npm:") || key.startsWith("release-aur:") || key === "workflow:subprocess";
|
|
13581
14089
|
}
|
|
13582
14090
|
|
|
13583
14091
|
function renderOptionalFeaturePanel() {
|
|
@@ -13640,6 +14148,16 @@ function renderOptionalFeaturePanel() {
|
|
|
13640
14148
|
}
|
|
13641
14149
|
|
|
13642
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
|
+
|
|
13643
14161
|
const hasGitWorkflow = isOptionalFeatureEnabled("gitWorkflow");
|
|
13644
14162
|
elements.gitWorkflowButton.hidden = !hasGitWorkflow;
|
|
13645
14163
|
setOptionalControlState(
|
|
@@ -16349,7 +16867,7 @@ async function sendUserBashCommand(parsed, { usesPromptInput = false, targetTabI
|
|
|
16349
16867
|
await runUserBashCommand(parsed, { usesPromptInput, targetTabId });
|
|
16350
16868
|
}
|
|
16351
16869
|
|
|
16352
|
-
async function sendPrompt(kind = "prompt", explicitMessage, { targetTabId = activeTabId, throwOnError = false } = {}) {
|
|
16870
|
+
async function sendPrompt(kind = "prompt", explicitMessage, { targetTabId = activeTabId, throwOnError = false, streamingBehavior } = {}) {
|
|
16353
16871
|
const usesPromptInput = explicitMessage === undefined;
|
|
16354
16872
|
const rawMessage = usesPromptInput ? elements.promptInput.value : explicitMessage;
|
|
16355
16873
|
const originalMessage = String(rawMessage || "").trim();
|
|
@@ -16395,7 +16913,7 @@ async function sendPrompt(kind = "prompt", explicitMessage, { targetTabId = acti
|
|
|
16395
16913
|
response = await api("/api/follow-up", { method: "POST", body: bodyBase, tabId: targetTabId });
|
|
16396
16914
|
} else {
|
|
16397
16915
|
const body = { ...bodyBase };
|
|
16398
|
-
if (targetWasStreaming) body.streamingBehavior = busyBehavior;
|
|
16916
|
+
if (targetWasStreaming) body.streamingBehavior = streamingBehavior || busyBehavior;
|
|
16399
16917
|
response = await api("/api/prompt", { method: "POST", body, tabId: targetTabId });
|
|
16400
16918
|
}
|
|
16401
16919
|
applyResponseTab(response);
|
|
@@ -16480,6 +16998,7 @@ function handleExtensionUiRequest(request) {
|
|
|
16480
16998
|
statusEntries.delete(statusKey);
|
|
16481
16999
|
}
|
|
16482
17000
|
if (statusKey === STATS_WEBUI_STATUS_KEY) handleStatsWebuiStatus(request.statusText);
|
|
17001
|
+
if (statusKey === BTW_WEBUI_STATUS_KEY) handleBtwWebuiStatus(request.statusText);
|
|
16483
17002
|
updateOptionalFeatureAvailability();
|
|
16484
17003
|
renderStatus();
|
|
16485
17004
|
return;
|
|
@@ -16669,6 +17188,10 @@ function handleEvent(event) {
|
|
|
16669
17188
|
clearContextUsageUnknownAfterCompaction(event.tabId || activeTabId);
|
|
16670
17189
|
statusEntries.clear();
|
|
16671
17190
|
widgets.clear();
|
|
17191
|
+
latestBtwWidgetPayload = null;
|
|
17192
|
+
btwWidgetDismissedId = "";
|
|
17193
|
+
btwWidgetComposerOpen = false;
|
|
17194
|
+
btwWidgetInputDraft = "";
|
|
16672
17195
|
resetOptionalFeatureAvailability();
|
|
16673
17196
|
renderStatus();
|
|
16674
17197
|
renderWidgets();
|
|
@@ -17021,6 +17544,7 @@ elements.busyPromptBehaviorMenu?.addEventListener("keydown", (event) => {
|
|
|
17021
17544
|
});
|
|
17022
17545
|
elements.steerButton.addEventListener("click", () => sendPromptFromModeButton("steer", elements.steerButton));
|
|
17023
17546
|
elements.followUpButton.addEventListener("click", () => sendPromptFromModeButton("follow-up", elements.followUpButton));
|
|
17547
|
+
elements.btwButton?.addEventListener("click", () => sendBtwPromptFromButton());
|
|
17024
17548
|
elements.terminalTabsToggleButton.addEventListener("click", () => {
|
|
17025
17549
|
setMobileTabsExpanded(!document.body.classList.contains("mobile-tabs-expanded"));
|
|
17026
17550
|
});
|