@wrongstack/webui 0.3.3 → 0.3.7
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/dist/assets/index-B-u6omip.css +1 -0
- package/dist/assets/{index-CJmc6zwr.js → index-Do84Z2Fv.js} +33 -33
- package/dist/index.css +3 -0
- package/dist/index.css.map +1 -1
- package/dist/index.html +2 -2
- package/dist/index.js +54 -16
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +54 -19
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +54 -19
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/assets/index-TCwASaz8.css +0 -1
package/dist/index.js
CHANGED
|
@@ -108,6 +108,11 @@ function playCompletionChime() {
|
|
|
108
108
|
tone(659.25, 0, 0.18);
|
|
109
109
|
tone(880, 0.12, 0.24);
|
|
110
110
|
}
|
|
111
|
+
function playPermissionChime() {
|
|
112
|
+
tone(523.25, 0, 0.15);
|
|
113
|
+
tone(659.25, 0.1, 0.15);
|
|
114
|
+
tone(783.99, 0.2, 0.25);
|
|
115
|
+
}
|
|
111
116
|
|
|
112
117
|
// src/lib/favicon.ts
|
|
113
118
|
var BASE_BG = "#4f46e5";
|
|
@@ -119,6 +124,8 @@ function buildSvg(status) {
|
|
|
119
124
|
return '<circle cx="50" cy="14" r="14" fill="#ef4444" stroke="#fff" stroke-width="3" />';
|
|
120
125
|
if (status === "running")
|
|
121
126
|
return '<circle cx="50" cy="14" r="14" fill="#f59e0b" stroke="#fff" stroke-width="3" />';
|
|
127
|
+
if (status === "attention")
|
|
128
|
+
return '<circle cx="50" cy="14" r="14" fill="#eab308" stroke="#fff" stroke-width="3"><animate attributeName="opacity" values="1;0.3;1" dur="1s" repeatCount="indefinite"/></circle>';
|
|
122
129
|
return "";
|
|
123
130
|
})();
|
|
124
131
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
@@ -153,7 +160,7 @@ function installFaviconVisibilityReset() {
|
|
|
153
160
|
if (visibilityHookInstalled || typeof document === "undefined") return;
|
|
154
161
|
visibilityHookInstalled = true;
|
|
155
162
|
document.addEventListener("visibilitychange", () => {
|
|
156
|
-
if (!document.hidden && (currentStatus === "ready" || currentStatus === "error")) {
|
|
163
|
+
if (!document.hidden && (currentStatus === "ready" || currentStatus === "error" || currentStatus === "attention")) {
|
|
157
164
|
setFaviconStatus("idle");
|
|
158
165
|
}
|
|
159
166
|
});
|
|
@@ -178,7 +185,7 @@ async function ensureNotificationPermission() {
|
|
|
178
185
|
return "denied";
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
|
-
function notifyIfHidden(title, body) {
|
|
188
|
+
function notifyIfHidden(title, body, tag) {
|
|
182
189
|
if (typeof document === "undefined" || !document.hidden) return;
|
|
183
190
|
if (permissionState !== "granted") return;
|
|
184
191
|
try {
|
|
@@ -186,10 +193,13 @@ function notifyIfHidden(title, body) {
|
|
|
186
193
|
body,
|
|
187
194
|
icon: "/favicon.ico",
|
|
188
195
|
// Tag-collapse: if multiple notifications stack while the tab is
|
|
189
|
-
// hidden, only the latest
|
|
190
|
-
// litter the OS notification center.
|
|
191
|
-
tag
|
|
192
|
-
|
|
196
|
+
// hidden, only the latest with the same tag shows up so we don't
|
|
197
|
+
// litter the OS notification center. Permission alerts use a
|
|
198
|
+
// separate tag so they don't get swallowed by run-completion.
|
|
199
|
+
tag: tag ?? "wrongstack-run",
|
|
200
|
+
// Require interaction for permission alerts — the agent is stuck
|
|
201
|
+
// until the user responds, so auto-dismiss would be harmful.
|
|
202
|
+
requireInteraction: tag === "wrongstack-confirm"
|
|
193
203
|
});
|
|
194
204
|
n.onclick = () => {
|
|
195
205
|
window.focus();
|
|
@@ -212,6 +222,9 @@ var WrongStackWebSocketClient = class {
|
|
|
212
222
|
messageQueue = [];
|
|
213
223
|
pendingConfirms = /* @__PURE__ */ new Map();
|
|
214
224
|
sessionId = null;
|
|
225
|
+
/** Auth token received from server in session.start payload.
|
|
226
|
+
* Used for reconnection so the client doesn't need to know the token upfront. */
|
|
227
|
+
wsToken = null;
|
|
215
228
|
/** Stored last close reason / error message so the UI can show "what
|
|
216
229
|
* went wrong" while reconnecting instead of a generic spinner. */
|
|
217
230
|
lastErrorText;
|
|
@@ -245,7 +258,8 @@ var WrongStackWebSocketClient = class {
|
|
|
245
258
|
}
|
|
246
259
|
this.setStatus({ state: "connecting" });
|
|
247
260
|
try {
|
|
248
|
-
this.
|
|
261
|
+
const wsUrl = this.wsToken ? `${this.url}${this.url.includes("?") ? "&" : "?"}token=${this.wsToken}` : this.url;
|
|
262
|
+
this.ws = new WebSocket(wsUrl);
|
|
249
263
|
this.ws.binaryType = "arraybuffer";
|
|
250
264
|
const connectTimeout = setTimeout(() => {
|
|
251
265
|
reject(new Error("Connection timeout"));
|
|
@@ -363,6 +377,9 @@ var WrongStackWebSocketClient = class {
|
|
|
363
377
|
if (msg.type === "session.start") {
|
|
364
378
|
const payload = msg.payload;
|
|
365
379
|
this.sessionId = payload.sessionId;
|
|
380
|
+
if (payload.wsToken) {
|
|
381
|
+
this.wsToken = payload.wsToken;
|
|
382
|
+
}
|
|
366
383
|
}
|
|
367
384
|
this.emit(msg);
|
|
368
385
|
}
|
|
@@ -1165,6 +1182,19 @@ function installHandlers(ws) {
|
|
|
1165
1182
|
input: payload.input,
|
|
1166
1183
|
suggestedPattern: payload.suggestedPattern
|
|
1167
1184
|
});
|
|
1185
|
+
try {
|
|
1186
|
+
playPermissionChime();
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
void ensureNotificationPermission();
|
|
1190
|
+
notifyIfHidden(
|
|
1191
|
+
"WrongStack needs approval",
|
|
1192
|
+
`Tool "${payload.toolName}" is waiting for your decision.`,
|
|
1193
|
+
"wrongstack-confirm"
|
|
1194
|
+
);
|
|
1195
|
+
if (typeof document !== "undefined" && document.hidden) {
|
|
1196
|
+
setFaviconStatus("attention");
|
|
1197
|
+
}
|
|
1168
1198
|
});
|
|
1169
1199
|
on("run.result", (msg) => {
|
|
1170
1200
|
const payload = msg.payload;
|
|
@@ -5377,7 +5407,7 @@ function ChatView() {
|
|
|
5377
5407
|
|
|
5378
5408
|
// src/components/ConfirmDialog.tsx
|
|
5379
5409
|
import { AlertTriangle as AlertTriangle2, FileEdit, Globe, ShieldAlert, Terminal as Terminal3, Wrench as Wrench3 } from "lucide-react";
|
|
5380
|
-
import { useEffect as useEffect13 } from "react";
|
|
5410
|
+
import { useEffect as useEffect13, useRef as useRef10 } from "react";
|
|
5381
5411
|
|
|
5382
5412
|
// src/components/ui/dialog.tsx
|
|
5383
5413
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
@@ -5505,6 +5535,7 @@ function SmartInputPreview({
|
|
|
5505
5535
|
function ConfirmDialog() {
|
|
5506
5536
|
const { showConfirmDialog, confirmInfo, hideConfirm } = useUIStore();
|
|
5507
5537
|
const { sendConfirm } = useWebSocket();
|
|
5538
|
+
const dialogRef = useRef10(null);
|
|
5508
5539
|
const handleConfirm = (decision) => {
|
|
5509
5540
|
if (confirmInfo) {
|
|
5510
5541
|
sendConfirm(confirmInfo.id, decision);
|
|
@@ -5526,9 +5557,13 @@ function ConfirmDialog() {
|
|
|
5526
5557
|
} else if (e.key === "a" || e.key === "A") {
|
|
5527
5558
|
e.preventDefault();
|
|
5528
5559
|
handleConfirm("always");
|
|
5560
|
+
} else if (e.key === "d" || e.key === "D") {
|
|
5561
|
+
e.preventDefault();
|
|
5562
|
+
handleConfirm("deny");
|
|
5529
5563
|
}
|
|
5530
5564
|
};
|
|
5531
5565
|
window.addEventListener("keydown", onKey);
|
|
5566
|
+
dialogRef.current?.focus();
|
|
5532
5567
|
return () => window.removeEventListener("keydown", onKey);
|
|
5533
5568
|
}, [showConfirmDialog, confirmInfo?.id]);
|
|
5534
5569
|
if (!confirmInfo) {
|
|
@@ -5536,11 +5571,11 @@ function ConfirmDialog() {
|
|
|
5536
5571
|
}
|
|
5537
5572
|
const Icon2 = pickToolIcon(confirmInfo.toolName);
|
|
5538
5573
|
const isEdit = /edit|write/i.test(confirmInfo.toolName);
|
|
5539
|
-
return /* @__PURE__ */ jsx19(Dialog, { open: showConfirmDialog, onOpenChange: () => hideConfirm(), children: /* @__PURE__ */ jsxs18(DialogContent, { className: "sm:max-w-2xl", children: [
|
|
5574
|
+
return /* @__PURE__ */ jsx19(Dialog, { open: showConfirmDialog, onOpenChange: () => hideConfirm(), children: /* @__PURE__ */ jsxs18(DialogContent, { className: "sm:max-w-2xl border-yellow-500/50", ref: dialogRef, tabIndex: -1, children: [
|
|
5540
5575
|
/* @__PURE__ */ jsxs18(DialogHeader, { children: [
|
|
5541
5576
|
/* @__PURE__ */ jsxs18(DialogTitle, { className: "flex items-center gap-2", children: [
|
|
5542
|
-
/* @__PURE__ */ jsx19(ShieldAlert, { className: "h-5 w-5 text-yellow-500" }),
|
|
5543
|
-
"
|
|
5577
|
+
/* @__PURE__ */ jsx19(ShieldAlert, { className: "h-5 w-5 text-yellow-500 animate-pulse" }),
|
|
5578
|
+
"Approval required: ",
|
|
5544
5579
|
confirmInfo.toolName
|
|
5545
5580
|
] }),
|
|
5546
5581
|
/* @__PURE__ */ jsxs18(DialogDescription, { children: [
|
|
@@ -5575,14 +5610,17 @@ function ConfirmDialog() {
|
|
|
5575
5610
|
] })
|
|
5576
5611
|
] }),
|
|
5577
5612
|
/* @__PURE__ */ jsxs18(DialogFooter, { className: "gap-2 sm:gap-2 flex-wrap", children: [
|
|
5578
|
-
/* @__PURE__ */
|
|
5613
|
+
/* @__PURE__ */ jsxs18(
|
|
5579
5614
|
Button,
|
|
5580
5615
|
{
|
|
5581
5616
|
variant: "outline",
|
|
5582
5617
|
size: "sm",
|
|
5583
5618
|
onClick: () => handleConfirm("deny"),
|
|
5584
|
-
title: "Reject this and all future calls matching the pattern",
|
|
5585
|
-
children:
|
|
5619
|
+
title: "Reject this and all future calls matching the pattern (d)",
|
|
5620
|
+
children: [
|
|
5621
|
+
"Deny always ",
|
|
5622
|
+
/* @__PURE__ */ jsx19("kbd", { className: "ml-1 text-[10px] border rounded px-1 bg-background", children: "d" })
|
|
5623
|
+
]
|
|
5586
5624
|
}
|
|
5587
5625
|
),
|
|
5588
5626
|
/* @__PURE__ */ jsxs18(
|
|
@@ -5733,7 +5771,7 @@ var ErrorBoundary = class extends Component {
|
|
|
5733
5771
|
|
|
5734
5772
|
// src/components/QuickModelSwitcher.tsx
|
|
5735
5773
|
import { ArrowRight as ArrowRight2, Cpu as Cpu3, Search as Search4 } from "lucide-react";
|
|
5736
|
-
import { useEffect as useEffect15, useMemo as useMemo5, useRef as
|
|
5774
|
+
import { useEffect as useEffect15, useMemo as useMemo5, useRef as useRef11, useState as useState15 } from "react";
|
|
5737
5775
|
import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
5738
5776
|
function QuickModelSwitcher() {
|
|
5739
5777
|
const open = useUIStore((s) => s.modelSwitcherOpen);
|
|
@@ -5742,7 +5780,7 @@ function QuickModelSwitcher() {
|
|
|
5742
5780
|
const [selected, setSelected] = useState15(0);
|
|
5743
5781
|
const [saved, setSaved] = useState15([]);
|
|
5744
5782
|
const [modelsByProvider, setModelsByProvider] = useState15({});
|
|
5745
|
-
const inputRef =
|
|
5783
|
+
const inputRef = useRef11(null);
|
|
5746
5784
|
const wsUrl = useConfigStore((s) => s.wsUrl);
|
|
5747
5785
|
const currentProvider = useConfigStore((s) => s.provider);
|
|
5748
5786
|
const currentModel = useConfigStore((s) => s.model);
|