@co0ontty/wand 1.1.7 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-pty-bridge.js +3 -6
- package/dist/process-manager.d.ts +1 -1
- package/dist/process-manager.js +19 -192
- package/dist/pty-text-utils.d.ts +2 -0
- package/dist/pty-text-utils.js +20 -0
- package/dist/resume-policy.d.ts +80 -0
- package/dist/resume-policy.js +178 -0
- package/dist/server-session-routes.d.ts +6 -0
- package/dist/server-session-routes.js +359 -0
- package/dist/server.js +20 -331
- package/dist/web-ui/content/scripts.js +435 -45
- package/dist/web-ui/content/styles.css +143 -18
- package/dist/ws-broadcast.d.ts +1 -1
- package/package.json +3 -2
|
@@ -257,6 +257,24 @@
|
|
|
257
257
|
}
|
|
258
258
|
startPolling();
|
|
259
259
|
refreshAll();
|
|
260
|
+
// Request browser notification permission after login
|
|
261
|
+
requestNotificationPermission();
|
|
262
|
+
// Show update bubble if server reports an available update
|
|
263
|
+
if (config.updateAvailable && config.latestVersion) {
|
|
264
|
+
showNotificationBubble({
|
|
265
|
+
title: "\u53d1\u73b0\u65b0\u7248\u672c",
|
|
266
|
+
body: "\u5f53\u524d " + (config.currentVersion || "-") + " \u2192 \u6700\u65b0 " + config.latestVersion,
|
|
267
|
+
type: "info",
|
|
268
|
+
icon: "\u2191",
|
|
269
|
+
duration: 0,
|
|
270
|
+
actionLabel: "\u53bb\u66f4\u65b0",
|
|
271
|
+
action: function() {
|
|
272
|
+
var settingsBtn = document.getElementById("open-settings-btn") || document.querySelector("[data-action='settings']");
|
|
273
|
+
if (settingsBtn) settingsBtn.click();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
sendBrowserNotification("Wand \u53d1\u73b0\u65b0\u7248\u672c", "\u5f53\u524d " + (config.currentVersion || "-") + " \u2192 \u6700\u65b0 " + config.latestVersion, { tag: "wand-update" });
|
|
277
|
+
}
|
|
260
278
|
// Auto-load claude history since section defaults to expanded
|
|
261
279
|
if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
|
|
262
280
|
loadClaudeHistory();
|
|
@@ -331,22 +349,25 @@
|
|
|
331
349
|
}
|
|
332
350
|
}
|
|
333
351
|
|
|
352
|
+
function renderShortcutKeys() {
|
|
353
|
+
return '<button class="shortcut-key' + (state.modifiers.ctrl ? ' active' : '') + '" data-key="ctrl" type="button">Ctrl</button>' +
|
|
354
|
+
'<button class="shortcut-key' + (state.modifiers.alt ? ' active' : '') + '" data-key="alt" type="button">Alt</button>' +
|
|
355
|
+
'<span class="shortcut-sep">·</span>' +
|
|
356
|
+
'<button class="shortcut-key shortcut-dir" data-key="up" type="button">↑</button>' +
|
|
357
|
+
'<button class="shortcut-key shortcut-dir" data-key="down" type="button">↓</button>' +
|
|
358
|
+
'<button class="shortcut-key shortcut-dir" data-key="left" type="button">←</button>' +
|
|
359
|
+
'<button class="shortcut-key shortcut-dir" data-key="right" type="button">→</button>' +
|
|
360
|
+
'<span class="shortcut-sep">·</span>' +
|
|
361
|
+
'<button class="shortcut-key" data-key="enter" type="button">↵</button>' +
|
|
362
|
+
'<button class="shortcut-key" data-key="ctrl_enter" type="button">C-↵</button>' +
|
|
363
|
+
'<button class="shortcut-key" data-key="escape" type="button">Esc</button>';
|
|
364
|
+
}
|
|
365
|
+
|
|
334
366
|
function renderInlineKeyboard() {
|
|
335
367
|
if (!state.selectedId) return "";
|
|
336
368
|
var isTerminal = state.currentView === "terminal";
|
|
337
369
|
if (!isTerminal) return "";
|
|
338
|
-
var keys =
|
|
339
|
-
'<button class="shortcut-key' + (state.modifiers.ctrl ? ' active' : '') + '" data-key="ctrl" type="button">Ctrl</button>' +
|
|
340
|
-
'<button class="shortcut-key' + (state.modifiers.alt ? ' active' : '') + '" data-key="alt" type="button">Alt</button>' +
|
|
341
|
-
'<span class="shortcut-sep">·</span>' +
|
|
342
|
-
'<button class="shortcut-key shortcut-dir" data-key="up" type="button">↑</button>' +
|
|
343
|
-
'<button class="shortcut-key shortcut-dir" data-key="down" type="button">↓</button>' +
|
|
344
|
-
'<button class="shortcut-key shortcut-dir" data-key="left" type="button">←</button>' +
|
|
345
|
-
'<button class="shortcut-key shortcut-dir" data-key="right" type="button">→</button>' +
|
|
346
|
-
'<span class="shortcut-sep">·</span>' +
|
|
347
|
-
'<button class="shortcut-key" data-key="enter" type="button">↵</button>' +
|
|
348
|
-
'<button class="shortcut-key" data-key="ctrl_enter" type="button">C-↵</button>' +
|
|
349
|
-
'<button class="shortcut-key" data-key="escape" type="button">Esc</button>';
|
|
370
|
+
var keys = renderShortcutKeys();
|
|
350
371
|
var arrow = state.shortcutsExpanded ? '›' : '‹';
|
|
351
372
|
return '<div class="inline-shortcuts-wrap' + (state.shortcutsExpanded ? ' expanded' : '') + '">' +
|
|
352
373
|
'<button class="shortcuts-toggle' + (state.shortcutsExpanded ? ' active' : '') + '" type="button" title="快捷键">' + arrow + '</button>' +
|
|
@@ -355,9 +376,11 @@
|
|
|
355
376
|
'</div>';
|
|
356
377
|
}
|
|
357
378
|
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
379
|
+
function renderExpandedShortcutsRow() {
|
|
380
|
+
if (!state.selectedId) return "";
|
|
381
|
+
var isTerminal = state.currentView === "terminal";
|
|
382
|
+
if (!isTerminal) return "";
|
|
383
|
+
return '<div class="inline-shortcuts-expanded-row' + (state.shortcutsExpanded ? ' visible' : '') + '">' + renderShortcutKeys() + '</div>';
|
|
361
384
|
}
|
|
362
385
|
|
|
363
386
|
function renderLogin() {
|
|
@@ -555,6 +578,7 @@
|
|
|
555
578
|
'</button>' +
|
|
556
579
|
'</div>' +
|
|
557
580
|
'</div>' +
|
|
581
|
+
renderExpandedShortcutsRow() +
|
|
558
582
|
// Session info bar at bottom
|
|
559
583
|
'<div class="input-session-info-bar">' +
|
|
560
584
|
'<span id="session-cwd-display" class="session-cwd-display">' + (selectedSession && selectedSession.cwd ? escapeHtml(selectedSession.cwd) : '未设置目录') + '</span>' +
|
|
@@ -631,6 +655,18 @@
|
|
|
631
655
|
'</div>' +
|
|
632
656
|
'<p id="update-message" class="hint hidden"></p>' +
|
|
633
657
|
'</div>' +
|
|
658
|
+
'<div class="settings-notification-section">' +
|
|
659
|
+
'<div class="settings-section-title">\u901a\u77e5\u72b6\u6001</div>' +
|
|
660
|
+
'<div class="settings-about-row">' +
|
|
661
|
+
'<span class="settings-label">\u6d4f\u89c8\u5668\u901a\u77e5</span>' +
|
|
662
|
+
'<span class="settings-value" id="notification-permission-status">-</span>' +
|
|
663
|
+
'</div>' +
|
|
664
|
+
'<div class="settings-update-actions">' +
|
|
665
|
+
'<button id="notification-request-btn" class="btn btn-ghost btn-sm hidden">\u6388\u6743\u901a\u77e5</button>' +
|
|
666
|
+
'<button id="notification-test-btn" class="btn btn-ghost btn-sm">\u53d1\u9001\u6d4b\u8bd5\u901a\u77e5</button>' +
|
|
667
|
+
'</div>' +
|
|
668
|
+
'<p id="notification-test-message" class="hint hidden"></p>' +
|
|
669
|
+
'</div>' +
|
|
634
670
|
'</div>' +
|
|
635
671
|
|
|
636
672
|
// General config tab
|
|
@@ -1886,6 +1922,16 @@
|
|
|
1886
1922
|
if (checkUpdateBtn) checkUpdateBtn.addEventListener("click", checkForUpdate);
|
|
1887
1923
|
var doUpdateBtn = document.getElementById("do-update-button");
|
|
1888
1924
|
if (doUpdateBtn) doUpdateBtn.addEventListener("click", performUpdate);
|
|
1925
|
+
// Notification test section
|
|
1926
|
+
var notifRequestBtn = document.getElementById("notification-request-btn");
|
|
1927
|
+
if (notifRequestBtn) notifRequestBtn.addEventListener("click", function() {
|
|
1928
|
+
if (typeof Notification !== "undefined") {
|
|
1929
|
+
Notification.requestPermission().then(function() { updateNotificationStatus(); });
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
var notifTestBtn = document.getElementById("notification-test-btn");
|
|
1933
|
+
if (notifTestBtn) notifTestBtn.addEventListener("click", testNotification);
|
|
1934
|
+
updateNotificationStatus();
|
|
1889
1935
|
var newSessBtn = document.getElementById("topbar-new-session-button");
|
|
1890
1936
|
if (newSessBtn) newSessBtn.addEventListener("click", openSessionModal);
|
|
1891
1937
|
var drawerNewSessBtn = document.getElementById("drawer-new-session-button");
|
|
@@ -1945,6 +1991,8 @@
|
|
|
1945
1991
|
// Inline shortcuts click handler
|
|
1946
1992
|
var inlineShortcutsWrap = document.querySelector(".inline-shortcuts-wrap");
|
|
1947
1993
|
if (inlineShortcutsWrap) inlineShortcutsWrap.addEventListener("click", handleInlineKeyboardClick);
|
|
1994
|
+
var expandedShortcutsRow = document.querySelector(".inline-shortcuts-expanded-row");
|
|
1995
|
+
if (expandedShortcutsRow) expandedShortcutsRow.addEventListener("click", handleInlineKeyboardClick);
|
|
1948
1996
|
// Shortcuts toggle (mobile fold/unfold)
|
|
1949
1997
|
var shortcutsToggleBtn = document.querySelector(".shortcuts-toggle");
|
|
1950
1998
|
if (shortcutsToggleBtn) shortcutsToggleBtn.addEventListener("click", function(e) {
|
|
@@ -1952,7 +2000,9 @@
|
|
|
1952
2000
|
state.shortcutsExpanded = !state.shortcutsExpanded;
|
|
1953
2001
|
var wrap = document.querySelector(".inline-shortcuts-wrap");
|
|
1954
2002
|
var toggle = document.querySelector(".shortcuts-toggle");
|
|
2003
|
+
var row = document.querySelector(".inline-shortcuts-expanded-row");
|
|
1955
2004
|
if (wrap) wrap.classList.toggle("expanded", state.shortcutsExpanded);
|
|
2005
|
+
if (row) row.classList.toggle("visible", state.shortcutsExpanded);
|
|
1956
2006
|
if (toggle) {
|
|
1957
2007
|
toggle.classList.toggle("active", state.shortcutsExpanded);
|
|
1958
2008
|
toggle.textContent = state.shortcutsExpanded ? "\u203a" : "\u2039";
|
|
@@ -1962,9 +2012,12 @@
|
|
|
1962
2012
|
document.addEventListener("click", function(e) {
|
|
1963
2013
|
if (!state.shortcutsExpanded) return;
|
|
1964
2014
|
var wrap = document.querySelector(".inline-shortcuts-wrap");
|
|
1965
|
-
|
|
2015
|
+
var expandedRow = document.querySelector(".inline-shortcuts-expanded-row");
|
|
2016
|
+
var clickedInsideRow = expandedRow && expandedRow.contains(e.target);
|
|
2017
|
+
if (wrap && !wrap.contains(e.target) && !clickedInsideRow) {
|
|
1966
2018
|
state.shortcutsExpanded = false;
|
|
1967
2019
|
wrap.classList.remove("expanded");
|
|
2020
|
+
if (expandedRow) expandedRow.classList.remove("visible");
|
|
1968
2021
|
var toggle = document.querySelector(".shortcuts-toggle");
|
|
1969
2022
|
if (toggle) {
|
|
1970
2023
|
toggle.classList.remove("active");
|
|
@@ -3270,6 +3323,58 @@
|
|
|
3270
3323
|
}
|
|
3271
3324
|
}
|
|
3272
3325
|
|
|
3326
|
+
function subscribeToSession(sessionId) {
|
|
3327
|
+
if (!sessionId || !state.ws || state.ws.readyState !== WebSocket.OPEN) return;
|
|
3328
|
+
state.ws.send(JSON.stringify({ type: "subscribe", sessionId: sessionId }));
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
function mergeServerSession(localSession, serverSession) {
|
|
3332
|
+
if (!localSession) return serverSession;
|
|
3333
|
+
|
|
3334
|
+
var merged = Object.assign({}, localSession, serverSession);
|
|
3335
|
+
var localOutput = localSession.output || "";
|
|
3336
|
+
var serverOutput = serverSession.output || "";
|
|
3337
|
+
var keepLocalOutput = localOutput.length > serverOutput.length;
|
|
3338
|
+
|
|
3339
|
+
if (keepLocalOutput) {
|
|
3340
|
+
merged.output = localOutput;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
if (localSession.id === state.selectedId) {
|
|
3344
|
+
if (localSession.permissionBlocked && serverSession.permissionBlocked === false) {
|
|
3345
|
+
// server explicitly resolved it; keep resolved state
|
|
3346
|
+
} else if (localSession.permissionBlocked && !serverSession.permissionBlocked) {
|
|
3347
|
+
merged.permissionBlocked = true;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
if (localSession.pendingEscalation && !serverSession.pendingEscalation && serverSession.permissionBlocked !== false) {
|
|
3351
|
+
merged.pendingEscalation = localSession.pendingEscalation;
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
if (localSession.messages && localSession.messages.length > 0 && (!serverSession.messages || serverSession.messages.length === 0)) {
|
|
3355
|
+
merged.messages = localSession.messages;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
return merged;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
function getPreferredMessages(session, fallbackOutput, allowFallback) {
|
|
3363
|
+
if (session && session.messages && session.messages.length > 0) {
|
|
3364
|
+
return session.messages;
|
|
3365
|
+
}
|
|
3366
|
+
if (!allowFallback) {
|
|
3367
|
+
return [];
|
|
3368
|
+
}
|
|
3369
|
+
var output = typeof fallbackOutput === "string"
|
|
3370
|
+
? fallbackOutput
|
|
3371
|
+
: (session && session.output) || "";
|
|
3372
|
+
if (!output) {
|
|
3373
|
+
return [];
|
|
3374
|
+
}
|
|
3375
|
+
return parseMessages(output, session && session.command);
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3273
3378
|
function getPreferredSessionId(sessions) {
|
|
3274
3379
|
if (!sessions || !sessions.length) return null;
|
|
3275
3380
|
// Keep currently selected session as long as it still exists
|
|
@@ -3304,10 +3409,7 @@
|
|
|
3304
3409
|
|
|
3305
3410
|
state.sessions = serverSessions.map(function(serverSession) {
|
|
3306
3411
|
var localSession = state.sessions.find(function(s) { return s.id === serverSession.id; });
|
|
3307
|
-
|
|
3308
|
-
return localSession;
|
|
3309
|
-
}
|
|
3310
|
-
return serverSession;
|
|
3412
|
+
return mergeServerSession(localSession, serverSession);
|
|
3311
3413
|
});
|
|
3312
3414
|
|
|
3313
3415
|
state.selectedId = getPreferredSessionId(state.sessions);
|
|
@@ -3431,7 +3533,7 @@
|
|
|
3431
3533
|
updateShellChrome();
|
|
3432
3534
|
|
|
3433
3535
|
var selectedSession = state.sessions.find(function(s) { return s.id === id; });
|
|
3434
|
-
state.currentMessages = data.
|
|
3536
|
+
state.currentMessages = getPreferredMessages(selectedSession, data.output, false);
|
|
3435
3537
|
|
|
3436
3538
|
if (state.terminal) {
|
|
3437
3539
|
syncTerminalBuffer(id, data.output || "", { mode: "replace" });
|
|
@@ -3466,6 +3568,7 @@
|
|
|
3466
3568
|
refreshFileExplorer();
|
|
3467
3569
|
}
|
|
3468
3570
|
loadOutput(id).then(function() { focusInputBox(true); });
|
|
3571
|
+
subscribeToSession(id);
|
|
3469
3572
|
}
|
|
3470
3573
|
|
|
3471
3574
|
function updateDrawerState() {
|
|
@@ -3913,6 +4016,101 @@
|
|
|
3913
4016
|
});
|
|
3914
4017
|
}
|
|
3915
4018
|
|
|
4019
|
+
// ── Notification Settings Helpers ──
|
|
4020
|
+
|
|
4021
|
+
function updateNotificationStatus() {
|
|
4022
|
+
var statusEl = document.getElementById("notification-permission-status");
|
|
4023
|
+
var requestBtn = document.getElementById("notification-request-btn");
|
|
4024
|
+
var testMsgEl = document.getElementById("notification-test-message");
|
|
4025
|
+
if (!statusEl) return;
|
|
4026
|
+
|
|
4027
|
+
if (typeof Notification === "undefined") {
|
|
4028
|
+
statusEl.textContent = "\u4e0d\u652f\u6301";
|
|
4029
|
+
statusEl.style.color = "var(--fg-muted)";
|
|
4030
|
+
if (requestBtn) requestBtn.classList.add("hidden");
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
var perm = Notification.permission;
|
|
4035
|
+
if (perm === "granted") {
|
|
4036
|
+
statusEl.textContent = "\u5df2\u6388\u6743 \u2713";
|
|
4037
|
+
statusEl.style.color = "var(--success)";
|
|
4038
|
+
if (requestBtn) requestBtn.classList.add("hidden");
|
|
4039
|
+
} else if (perm === "denied") {
|
|
4040
|
+
statusEl.textContent = "\u5df2\u62d2\u7edd";
|
|
4041
|
+
statusEl.style.color = "var(--danger)";
|
|
4042
|
+
if (requestBtn) requestBtn.classList.add("hidden");
|
|
4043
|
+
if (testMsgEl) {
|
|
4044
|
+
testMsgEl.textContent = "\u6d4f\u89c8\u5668\u5df2\u62d2\u7edd\u901a\u77e5\u6743\u9650\uff0c\u8bf7\u5728\u6d4f\u89c8\u5668\u8bbe\u7f6e\u4e2d\u624b\u52a8\u5f00\u542f";
|
|
4045
|
+
testMsgEl.style.color = "var(--fg-muted)";
|
|
4046
|
+
testMsgEl.classList.remove("hidden");
|
|
4047
|
+
}
|
|
4048
|
+
} else {
|
|
4049
|
+
statusEl.textContent = "\u672a\u6388\u6743";
|
|
4050
|
+
statusEl.style.color = "var(--warning)";
|
|
4051
|
+
if (requestBtn) requestBtn.classList.remove("hidden");
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
function testNotification() {
|
|
4056
|
+
var testMsgEl = document.getElementById("notification-test-message");
|
|
4057
|
+
|
|
4058
|
+
// Always show in-app bubble
|
|
4059
|
+
showNotificationBubble({
|
|
4060
|
+
title: "\u6d4b\u8bd5\u901a\u77e5",
|
|
4061
|
+
body: "\u8fd9\u662f\u4e00\u6761\u6d4b\u8bd5\u901a\u77e5\uff0c\u5e94\u7528\u5185\u6c14\u6ce1\u5df2\u6b63\u5e38\u5de5\u4f5c\u3002",
|
|
4062
|
+
type: "info",
|
|
4063
|
+
icon: "\u266a",
|
|
4064
|
+
duration: 5000,
|
|
4065
|
+
});
|
|
4066
|
+
|
|
4067
|
+
// Test browser notification
|
|
4068
|
+
if (typeof Notification === "undefined") {
|
|
4069
|
+
if (testMsgEl) {
|
|
4070
|
+
testMsgEl.textContent = "\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u901a\u77e5 API\uff0c\u4ec5\u53ef\u4f7f\u7528\u5e94\u7528\u5185\u6c14\u6ce1\u901a\u77e5\u3002";
|
|
4071
|
+
testMsgEl.style.color = "var(--fg-muted)";
|
|
4072
|
+
testMsgEl.classList.remove("hidden");
|
|
4073
|
+
}
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
if (Notification.permission === "granted") {
|
|
4078
|
+
try {
|
|
4079
|
+
var n = new Notification("Wand \u6d4b\u8bd5\u901a\u77e5", {
|
|
4080
|
+
body: "\u6d4f\u89c8\u5668\u901a\u77e5\u5df2\u6b63\u5e38\u5de5\u4f5c\u3002",
|
|
4081
|
+
icon: "/favicon.ico",
|
|
4082
|
+
tag: "wand-test",
|
|
4083
|
+
});
|
|
4084
|
+
setTimeout(function() { n.close(); }, 5000);
|
|
4085
|
+
if (testMsgEl) {
|
|
4086
|
+
testMsgEl.textContent = "\u2713 \u6d4f\u89c8\u5668\u901a\u77e5 + \u5e94\u7528\u5185\u6c14\u6ce1\u5747\u5df2\u53d1\u9001";
|
|
4087
|
+
testMsgEl.style.color = "var(--success)";
|
|
4088
|
+
testMsgEl.classList.remove("hidden");
|
|
4089
|
+
}
|
|
4090
|
+
} catch (_e) {
|
|
4091
|
+
if (testMsgEl) {
|
|
4092
|
+
testMsgEl.textContent = "\u6d4f\u89c8\u5668\u901a\u77e5\u53d1\u9001\u5931\u8d25\uff0c\u53ef\u80fd\u9700\u8981 HTTPS";
|
|
4093
|
+
testMsgEl.style.color = "var(--warning)";
|
|
4094
|
+
testMsgEl.classList.remove("hidden");
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
} else {
|
|
4098
|
+
// permission is "default" or "denied" — always try requesting
|
|
4099
|
+
Notification.requestPermission().then(function(result) {
|
|
4100
|
+
updateNotificationStatus();
|
|
4101
|
+
if (result === "granted") {
|
|
4102
|
+
testNotification();
|
|
4103
|
+
} else if (result === "denied") {
|
|
4104
|
+
if (testMsgEl) {
|
|
4105
|
+
testMsgEl.textContent = "\u6d4f\u89c8\u5668\u5df2\u62d2\u7edd\u901a\u77e5\u6743\u9650\uff0c\u8bf7\u70b9\u51fb\u5730\u5740\u680f\u5de6\u4fa7\u9501\u56fe\u6807\u6216\u5728\u6d4f\u89c8\u5668\u8bbe\u7f6e\u4e2d\u624b\u52a8\u5f00\u542f";
|
|
4106
|
+
testMsgEl.style.color = "var(--fg-muted)";
|
|
4107
|
+
testMsgEl.classList.remove("hidden");
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
|
|
3916
4114
|
function quickStartSession() {
|
|
3917
4115
|
var command = getPreferredTool();
|
|
3918
4116
|
var defaultCwd = getEffectiveCwd();
|
|
@@ -4418,9 +4616,7 @@
|
|
|
4418
4616
|
switchToSessionView(data.id);
|
|
4419
4617
|
updateSessionSnapshot(data);
|
|
4420
4618
|
updateSessionsList();
|
|
4421
|
-
|
|
4422
|
-
state.ws.send(JSON.stringify({ type: "subscribe", sessionId: data.id }));
|
|
4423
|
-
}
|
|
4619
|
+
subscribeToSession(data.id);
|
|
4424
4620
|
loadOutput(data.id).then(function() {
|
|
4425
4621
|
focusInputBox(true);
|
|
4426
4622
|
});
|
|
@@ -4481,9 +4677,7 @@
|
|
|
4481
4677
|
updateSessionSnapshot(data);
|
|
4482
4678
|
updateSessionsList();
|
|
4483
4679
|
// Subscribe to new session via WebSocket
|
|
4484
|
-
|
|
4485
|
-
state.ws.send(JSON.stringify({ type: 'subscribe', sessionId: data.id }));
|
|
4486
|
-
}
|
|
4680
|
+
subscribeToSession(data.id);
|
|
4487
4681
|
return loadOutput(data.id);
|
|
4488
4682
|
})
|
|
4489
4683
|
.catch(function(error) {
|
|
@@ -4656,9 +4850,7 @@
|
|
|
4656
4850
|
updateSessionSnapshot(data);
|
|
4657
4851
|
updateSessionsList();
|
|
4658
4852
|
switchToSessionView(data.id);
|
|
4659
|
-
|
|
4660
|
-
state.ws.send(JSON.stringify({ type: "subscribe", sessionId: data.id }));
|
|
4661
|
-
}
|
|
4853
|
+
subscribeToSession(data.id);
|
|
4662
4854
|
return loadOutput(data.id).then(function() {
|
|
4663
4855
|
focusInputBox(true);
|
|
4664
4856
|
return data;
|
|
@@ -5371,9 +5563,7 @@
|
|
|
5371
5563
|
switchToSessionView(data.id);
|
|
5372
5564
|
updateSessionSnapshot(data);
|
|
5373
5565
|
updateSessionsList();
|
|
5374
|
-
|
|
5375
|
-
state.ws.send(JSON.stringify({ type: "subscribe", sessionId: data.id }));
|
|
5376
|
-
}
|
|
5566
|
+
subscribeToSession(data.id);
|
|
5377
5567
|
return loadOutput(data.id).then(function() {
|
|
5378
5568
|
focusInputBox(true);
|
|
5379
5569
|
});
|
|
@@ -6587,9 +6777,7 @@
|
|
|
6587
6777
|
state.ws = ws;
|
|
6588
6778
|
state.wsConnected = true;
|
|
6589
6779
|
// Subscribe to current session if any
|
|
6590
|
-
|
|
6591
|
-
ws.send(JSON.stringify({ type: 'subscribe', sessionId: state.selectedId }));
|
|
6592
|
-
}
|
|
6780
|
+
subscribeToSession(state.selectedId);
|
|
6593
6781
|
// Flush pending messages after reconnection
|
|
6594
6782
|
flushPendingMessages();
|
|
6595
6783
|
};
|
|
@@ -6639,9 +6827,7 @@
|
|
|
6639
6827
|
}
|
|
6640
6828
|
updateSessionSnapshot(snapshot);
|
|
6641
6829
|
if (msg.sessionId === state.selectedId) {
|
|
6642
|
-
|
|
6643
|
-
state.currentMessages = msg.data.messages;
|
|
6644
|
-
}
|
|
6830
|
+
state.currentMessages = getPreferredMessages(snapshot, msg.data.output, false);
|
|
6645
6831
|
updateTaskDisplay();
|
|
6646
6832
|
scheduleChatRender();
|
|
6647
6833
|
}
|
|
@@ -6681,6 +6867,33 @@
|
|
|
6681
6867
|
}
|
|
6682
6868
|
updateSessionSnapshot(endedSnapshot);
|
|
6683
6869
|
|
|
6870
|
+
// Notify user when a session completes (browser + in-app if backgrounded or not viewing)
|
|
6871
|
+
var endedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
6872
|
+
var endedName = endedSession ? (endedSession.label || endedSession.command || msg.sessionId) : msg.sessionId;
|
|
6873
|
+
var endedExitCode = msg.data && msg.data.exitCode;
|
|
6874
|
+
var endedIsError = endedExitCode !== null && endedExitCode !== undefined && endedExitCode !== 0;
|
|
6875
|
+
sendBrowserNotification(
|
|
6876
|
+
endedIsError ? "\u4f1a\u8bdd\u5f02\u5e38\u7ed3\u675f" : "\u4f1a\u8bdd\u5df2\u5b8c\u6210",
|
|
6877
|
+
endedName,
|
|
6878
|
+
{
|
|
6879
|
+
tag: "wand-ended-" + msg.sessionId,
|
|
6880
|
+
onClick: function() {
|
|
6881
|
+
if (msg.sessionId !== state.selectedId) selectSession(msg.sessionId);
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
);
|
|
6885
|
+
if (msg.sessionId !== state.selectedId || document.hidden) {
|
|
6886
|
+
showNotificationBubble({
|
|
6887
|
+
title: endedIsError ? "\u4f1a\u8bdd\u5f02\u5e38\u7ed3\u675f" : "\u4f1a\u8bdd\u5df2\u5b8c\u6210",
|
|
6888
|
+
body: endedName,
|
|
6889
|
+
type: endedIsError ? "warning" : "success",
|
|
6890
|
+
icon: endedIsError ? "!" : "\u2713",
|
|
6891
|
+
duration: 6000,
|
|
6892
|
+
actionLabel: "\u67e5\u770b",
|
|
6893
|
+
action: function() { selectSession(msg.sessionId); }
|
|
6894
|
+
});
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6684
6897
|
// Clear stale queued inputs so they cannot race with the ended session.
|
|
6685
6898
|
// Each queued item's postInput will hit the server and get an error, but
|
|
6686
6899
|
// clearing the queues here prevents them from growing unbounded.
|
|
@@ -6738,6 +6951,35 @@
|
|
|
6738
6951
|
target: msg.data.permissionRequest.target,
|
|
6739
6952
|
reason: msg.data.permissionRequest.prompt
|
|
6740
6953
|
};
|
|
6954
|
+
// Browser notification for permission waiting (background tab)
|
|
6955
|
+
var permSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
6956
|
+
var permSessionName = permSession ? (permSession.label || permSession.command || msg.sessionId) : msg.sessionId;
|
|
6957
|
+
sendBrowserNotification(
|
|
6958
|
+
"\u4f1a\u8bdd\u7b49\u5f85\u6388\u6743",
|
|
6959
|
+
permSessionName + " \u2014 " + (msg.data.permissionRequest.prompt || "\u9700\u8981\u6743\u9650\u5ba1\u6279"),
|
|
6960
|
+
{
|
|
6961
|
+
tag: "wand-perm-" + msg.sessionId,
|
|
6962
|
+
onClick: function() {
|
|
6963
|
+
if (msg.sessionId !== state.selectedId) {
|
|
6964
|
+
selectSession(msg.sessionId);
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
);
|
|
6969
|
+
// In-app bubble if not currently viewing this session
|
|
6970
|
+
if (msg.sessionId !== state.selectedId) {
|
|
6971
|
+
showNotificationBubble({
|
|
6972
|
+
title: "\u4f1a\u8bdd\u7b49\u5f85\u6388\u6743",
|
|
6973
|
+
body: permSessionName + " \u2014 " + (msg.data.permissionRequest.prompt || "\u9700\u8981\u6743\u9650\u5ba1\u6279"),
|
|
6974
|
+
type: "warning",
|
|
6975
|
+
icon: "!",
|
|
6976
|
+
duration: 0,
|
|
6977
|
+
actionLabel: "\u67e5\u770b",
|
|
6978
|
+
action: function() {
|
|
6979
|
+
selectSession(msg.sessionId);
|
|
6980
|
+
}
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6741
6983
|
}
|
|
6742
6984
|
if (msg.data.permissionBlocked === false) {
|
|
6743
6985
|
statusUpdate.pendingEscalation = null;
|
|
@@ -6748,6 +6990,29 @@
|
|
|
6748
6990
|
}
|
|
6749
6991
|
}
|
|
6750
6992
|
break;
|
|
6993
|
+
case 'notification':
|
|
6994
|
+
if (msg.data) {
|
|
6995
|
+
if (msg.data.kind === "update") {
|
|
6996
|
+
showNotificationBubble({
|
|
6997
|
+
title: "\u53d1\u73b0\u65b0\u7248\u672c",
|
|
6998
|
+
body: "\u5f53\u524d " + (msg.data.current || "-") + " \u2192 \u6700\u65b0 " + (msg.data.latest || "-"),
|
|
6999
|
+
type: "info",
|
|
7000
|
+
icon: "\u2191",
|
|
7001
|
+
duration: 0,
|
|
7002
|
+
actionLabel: "\u53bb\u66f4\u65b0",
|
|
7003
|
+
action: function() {
|
|
7004
|
+
var settingsBtn = document.getElementById("open-settings-btn") || document.querySelector("[data-action='settings']");
|
|
7005
|
+
if (settingsBtn) settingsBtn.click();
|
|
7006
|
+
}
|
|
7007
|
+
});
|
|
7008
|
+
sendBrowserNotification(
|
|
7009
|
+
"Wand \u53d1\u73b0\u65b0\u7248\u672c",
|
|
7010
|
+
"\u5f53\u524d " + (msg.data.current || "-") + " \u2192 \u6700\u65b0 " + (msg.data.latest || "-"),
|
|
7011
|
+
{ tag: "wand-update" }
|
|
7012
|
+
);
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
7015
|
+
break;
|
|
6751
7016
|
}
|
|
6752
7017
|
}
|
|
6753
7018
|
|
|
@@ -6905,12 +7170,7 @@
|
|
|
6905
7170
|
// Re-parse messages from the latest session output (fallback for edge cases)
|
|
6906
7171
|
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
6907
7172
|
if (selectedSession) {
|
|
6908
|
-
|
|
6909
|
-
if (selectedSession.messages && selectedSession.messages.length > 0) {
|
|
6910
|
-
state.currentMessages = selectedSession.messages;
|
|
6911
|
-
} else if (selectedSession.output) {
|
|
6912
|
-
state.currentMessages = parseMessages(selectedSession.output, selectedSession.command);
|
|
6913
|
-
}
|
|
7173
|
+
state.currentMessages = getPreferredMessages(selectedSession, selectedSession.output, true);
|
|
6914
7174
|
}
|
|
6915
7175
|
renderChat();
|
|
6916
7176
|
}, 30);
|
|
@@ -8558,6 +8818,136 @@
|
|
|
8558
8818
|
}, type === "error" ? 4000 : 2200);
|
|
8559
8819
|
}
|
|
8560
8820
|
|
|
8821
|
+
// ── Notification Bubble System ──
|
|
8822
|
+
|
|
8823
|
+
var notificationStack = [];
|
|
8824
|
+
var notificationIdCounter = 0;
|
|
8825
|
+
var NOTIFICATION_GAP = 8;
|
|
8826
|
+
var NOTIFICATION_TOP = 24;
|
|
8827
|
+
|
|
8828
|
+
/**
|
|
8829
|
+
* Show an in-app notification bubble at bottom-right.
|
|
8830
|
+
* @param {object} opts
|
|
8831
|
+
* @param {string} opts.title - Notification title
|
|
8832
|
+
* @param {string} [opts.body] - Body text
|
|
8833
|
+
* @param {string} [opts.type] - "info" | "warning" | "success" (default "info")
|
|
8834
|
+
* @param {string} [opts.icon] - Icon character (default derived from type)
|
|
8835
|
+
* @param {number} [opts.duration] - Auto-dismiss ms, 0 = manual only (default 8000)
|
|
8836
|
+
* @param {string} [opts.actionLabel] - Action button label
|
|
8837
|
+
* @param {function} [opts.action] - Action button callback
|
|
8838
|
+
* @returns {{ dismiss: function }} handle
|
|
8839
|
+
*/
|
|
8840
|
+
function showNotificationBubble(opts) {
|
|
8841
|
+
var id = ++notificationIdCounter;
|
|
8842
|
+
var type = opts.type || "info";
|
|
8843
|
+
var icon = opts.icon || (type === "warning" ? "!" : type === "success" ? "\u2713" : "i");
|
|
8844
|
+
var duration = opts.duration !== undefined ? opts.duration : 8000;
|
|
8845
|
+
|
|
8846
|
+
var bubble = document.createElement("div");
|
|
8847
|
+
bubble.className = "notification-bubble";
|
|
8848
|
+
bubble.setAttribute("data-nid", id);
|
|
8849
|
+
|
|
8850
|
+
var headerHtml =
|
|
8851
|
+
'<div class="notification-bubble-header">' +
|
|
8852
|
+
'<span class="notification-bubble-icon ' + type + '">' + icon + '</span>' +
|
|
8853
|
+
'<span class="notification-bubble-title">' + escapeHtml(opts.title) + '</span>' +
|
|
8854
|
+
'<button class="notification-bubble-close" title="\u5173\u95ed">\u00d7</button>' +
|
|
8855
|
+
'</div>';
|
|
8856
|
+
|
|
8857
|
+
var bodyHtml = opts.body
|
|
8858
|
+
? '<div class="notification-bubble-body">' + escapeHtml(opts.body) + '</div>'
|
|
8859
|
+
: '';
|
|
8860
|
+
|
|
8861
|
+
var actionsHtml = opts.actionLabel
|
|
8862
|
+
? '<div class="notification-bubble-actions">' +
|
|
8863
|
+
'<button class="primary">' + escapeHtml(opts.actionLabel) + '</button>' +
|
|
8864
|
+
'</div>'
|
|
8865
|
+
: '';
|
|
8866
|
+
|
|
8867
|
+
bubble.innerHTML = headerHtml + bodyHtml + actionsHtml;
|
|
8868
|
+
document.body.appendChild(bubble);
|
|
8869
|
+
|
|
8870
|
+
// Stack position
|
|
8871
|
+
var entry = { id: id, el: bubble };
|
|
8872
|
+
notificationStack.push(entry);
|
|
8873
|
+
repositionNotifications();
|
|
8874
|
+
|
|
8875
|
+
// Wire close button
|
|
8876
|
+
var closeBtn = bubble.querySelector(".notification-bubble-close");
|
|
8877
|
+
if (closeBtn) closeBtn.onclick = function() { dismissNotification(id); };
|
|
8878
|
+
|
|
8879
|
+
// Wire action button
|
|
8880
|
+
if (opts.actionLabel && opts.action) {
|
|
8881
|
+
var actionBtn = bubble.querySelector(".notification-bubble-actions button");
|
|
8882
|
+
if (actionBtn) actionBtn.onclick = function() {
|
|
8883
|
+
opts.action();
|
|
8884
|
+
dismissNotification(id);
|
|
8885
|
+
};
|
|
8886
|
+
}
|
|
8887
|
+
|
|
8888
|
+
// Auto-dismiss
|
|
8889
|
+
var timer = null;
|
|
8890
|
+
if (duration > 0) {
|
|
8891
|
+
timer = setTimeout(function() { dismissNotification(id); }, duration);
|
|
8892
|
+
}
|
|
8893
|
+
|
|
8894
|
+
return {
|
|
8895
|
+
dismiss: function() { dismissNotification(id); }
|
|
8896
|
+
};
|
|
8897
|
+
}
|
|
8898
|
+
|
|
8899
|
+
function dismissNotification(id) {
|
|
8900
|
+
var idx = -1;
|
|
8901
|
+
for (var i = 0; i < notificationStack.length; i++) {
|
|
8902
|
+
if (notificationStack[i].id === id) { idx = i; break; }
|
|
8903
|
+
}
|
|
8904
|
+
if (idx === -1) return;
|
|
8905
|
+
var entry = notificationStack[idx];
|
|
8906
|
+
entry.el.classList.add("slide-out");
|
|
8907
|
+
notificationStack.splice(idx, 1);
|
|
8908
|
+
repositionNotifications();
|
|
8909
|
+
setTimeout(function() {
|
|
8910
|
+
if (entry.el.parentNode) entry.el.parentNode.removeChild(entry.el);
|
|
8911
|
+
}, 300);
|
|
8912
|
+
}
|
|
8913
|
+
|
|
8914
|
+
function repositionNotifications() {
|
|
8915
|
+
var top = NOTIFICATION_TOP;
|
|
8916
|
+
for (var i = 0; i < notificationStack.length; i++) {
|
|
8917
|
+
notificationStack[i].el.style.top = top + "px";
|
|
8918
|
+
top += notificationStack[i].el.offsetHeight + NOTIFICATION_GAP;
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
|
|
8922
|
+
// ── Browser Notification API ──
|
|
8923
|
+
|
|
8924
|
+
function requestNotificationPermission() {
|
|
8925
|
+
if (typeof Notification !== "undefined" && Notification.permission === "default") {
|
|
8926
|
+
Notification.requestPermission();
|
|
8927
|
+
}
|
|
8928
|
+
}
|
|
8929
|
+
|
|
8930
|
+
function sendBrowserNotification(title, body, opts) {
|
|
8931
|
+
if (typeof Notification === "undefined" || Notification.permission !== "granted") return;
|
|
8932
|
+
if (!document.hidden) return; // Only notify when tab is in background
|
|
8933
|
+
try {
|
|
8934
|
+
var n = new Notification(title, {
|
|
8935
|
+
body: body || "",
|
|
8936
|
+
icon: (opts && opts.icon) || "/favicon.ico",
|
|
8937
|
+
tag: (opts && opts.tag) || undefined,
|
|
8938
|
+
});
|
|
8939
|
+
n.onclick = function() {
|
|
8940
|
+
window.focus();
|
|
8941
|
+
n.close();
|
|
8942
|
+
if (opts && opts.onClick) opts.onClick();
|
|
8943
|
+
};
|
|
8944
|
+
// Auto-close after 10s
|
|
8945
|
+
setTimeout(function() { n.close(); }, 10000);
|
|
8946
|
+
} catch (_e) {
|
|
8947
|
+
// Notification constructor may fail in some contexts (e.g. insecure origin)
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
|
|
8561
8951
|
function escapeHtml(value) {
|
|
8562
8952
|
return String(value)
|
|
8563
8953
|
.replace(/&/g, "&")
|