@co0ontty/wand 1.29.3 → 1.29.6

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/server.js CHANGED
@@ -976,6 +976,10 @@ export async function startServer(config, configPath) {
976
976
  res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(macosDmg.fileName)}"`);
977
977
  createReadStream(macosDmg.filePath).pipe(res);
978
978
  });
979
+ // Public probe so the unauthenticated browser does not log a 401 on /api/config
980
+ app.get("/api/session-check", (req, res) => {
981
+ res.json({ authed: validateSession(readSessionCookie(req)) });
982
+ });
979
983
  app.use("/api", requireAuth);
980
984
  // ── Config & Session info ──
981
985
  app.get("/api/config", async (_req, res) => {
@@ -1148,14 +1148,24 @@
1148
1148
  }
1149
1149
 
1150
1150
  function restoreLoginSession() {
1151
- fetch("/api/config", { credentials: "same-origin" })
1152
- .then(function(res) {
1153
- if (!res.ok) {
1151
+ // Probe an unauthenticated endpoint first so an anonymous visit
1152
+ // does not leave a noisy 401 on /api/config in DevTools.
1153
+ fetch("/api/session-check", { credentials: "same-origin" })
1154
+ .then(function(res) { return res.ok ? res.json() : { authed: false }; })
1155
+ .then(function(info) {
1156
+ if (!info || !info.authed) {
1154
1157
  state.loginChecked = true;
1155
1158
  render();
1156
1159
  return null;
1157
1160
  }
1158
- return res.json();
1161
+ return fetch("/api/config", { credentials: "same-origin" }).then(function(res) {
1162
+ if (!res.ok) {
1163
+ state.loginChecked = true;
1164
+ render();
1165
+ return null;
1166
+ }
1167
+ return res.json();
1168
+ });
1159
1169
  })
1160
1170
  .then(function(config) {
1161
1171
  if (!config) return;
@@ -1286,6 +1296,7 @@
1286
1296
  '<span class="shortcut-sep">·</span>' +
1287
1297
  '<button class="shortcut-key" data-key="enter" type="button">↵</button>' +
1288
1298
  '<button class="shortcut-key" data-key="ctrl_enter" type="button">C-↵</button>' +
1299
+ '<button class="shortcut-key" data-key="shift_tab" type="button" title="Shift+Tab(切换 plan / 自动接受 模式)">⇧⇥</button>' +
1289
1300
  '<button class="shortcut-key" data-key="escape" type="button">Esc</button>';
1290
1301
  }
1291
1302
 
@@ -1365,7 +1376,8 @@
1365
1376
  '</div>' +
1366
1377
  '<div class="login-subtitle">在浏览器中运行本机终端</div>' +
1367
1378
  '</div>' +
1368
- '<div class="login-body">' +
1379
+ '<form id="login-form" class="login-body" autocomplete="on">' +
1380
+ '<input type="text" name="username" autocomplete="username" value="wand" tabindex="-1" aria-hidden="true" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none" readonly />' +
1369
1381
  '<p class="login-hint">输入 Wand 访问密码以进入控制台。</p>' +
1370
1382
  '<div class="field">' +
1371
1383
  '<label class="field-label" for="password">密码</label>' +
@@ -1376,7 +1388,7 @@
1376
1388
  '<p id="password-hint" class="hint">使用你在 Wand 中设置的访问密码。</p>' +
1377
1389
  '<p id="login-error" class="error-message hidden" role="alert"></p>' +
1378
1390
  '</div>' +
1379
- '<button id="login-button" class="btn btn-primary btn-block">进入控制台</button>' +
1391
+ '<button id="login-button" type="submit" class="btn btn-primary btn-block">进入控制台</button>' +
1380
1392
  (hasNativeSwitchServer() ?
1381
1393
  '<button id="login-switch-server-button" class="btn btn-ghost btn-block login-switch-server" type="button">' +
1382
1394
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="8" rx="2"/><rect x="2" y="13" width="20" height="8" rx="2"/><line x1="6" y1="7" x2="6.01" y2="7"/><line x1="6" y1="17" x2="6.01" y2="17"/></svg>' +
@@ -1384,7 +1396,7 @@
1384
1396
  '</button>'
1385
1397
  : ''
1386
1398
  ) +
1387
- '</div>' +
1399
+ '</form>' +
1388
1400
  '</div>' +
1389
1401
  '</div>';
1390
1402
  }
@@ -1442,7 +1454,7 @@
1442
1454
  '</div>' +
1443
1455
  '<div class="sidebar-body">' +
1444
1456
  '<div id="sessions-panel">' +
1445
- '<div class="sessions-list" id="sessions-list">' + renderSessions() + '</div>' +
1457
+ '<div class="sessions-list" id="sessions-list">' + renderSessionsListContent() + '</div>' +
1446
1458
  '</div>' +
1447
1459
  '</div>' +
1448
1460
  '<div class="sidebar-footer">' +
@@ -2943,19 +2955,22 @@
2943
2955
  '<p class="settings-card-desc">至少 6 个字符;保存后下次登录生效。</p>' +
2944
2956
  '</div>' +
2945
2957
  '</div>' +
2946
- '<div class="field">' +
2947
- '<label class="field-label" for="new-password">新密码</label>' +
2948
- '<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
2949
- '</div>' +
2950
- '<div class="field">' +
2951
- '<label class="field-label" for="confirm-password">确认密码</label>' +
2952
- '<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
2953
- '</div>' +
2954
- '<div class="settings-card-actions">' +
2955
- '<button id="save-password-button" class="btn btn-primary">保存密码</button>' +
2956
- '</div>' +
2957
- '<p id="settings-error" class="error-message hidden"></p>' +
2958
- '<p id="settings-success" class="hint settings-success-message hidden"></p>' +
2958
+ '<form id="change-password-form" autocomplete="on" onsubmit="return false;">' +
2959
+ '<input type="text" name="username" autocomplete="username" value="wand" tabindex="-1" aria-hidden="true" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none" readonly />' +
2960
+ '<div class="field">' +
2961
+ '<label class="field-label" for="new-password">新密码</label>' +
2962
+ '<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
2963
+ '</div>' +
2964
+ '<div class="field">' +
2965
+ '<label class="field-label" for="confirm-password">确认密码</label>' +
2966
+ '<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
2967
+ '</div>' +
2968
+ '<div class="settings-card-actions">' +
2969
+ '<button id="save-password-button" class="btn btn-primary" type="submit">保存密码</button>' +
2970
+ '</div>' +
2971
+ '<p id="settings-error" class="error-message hidden"></p>' +
2972
+ '<p id="settings-success" class="hint settings-success-message hidden"></p>' +
2973
+ '</form>' +
2959
2974
  '</div>' +
2960
2975
  '<div class="settings-card">' +
2961
2976
  '<div class="settings-card-head">' +
@@ -3091,6 +3106,35 @@
3091
3106
  return groups.join("");
3092
3107
  }
3093
3108
 
3109
+ function isSidebarNarrow() {
3110
+ return !!state.sidebarPinned && !isMobileLayout() && !!state.sidebarCollapsed;
3111
+ }
3112
+
3113
+ function renderCollapsedSessionTiles() {
3114
+ var running = state.sessions.filter(function(s) {
3115
+ return !s.archived && s.status === "running";
3116
+ });
3117
+ running.sort(function(a, b) {
3118
+ var ta = a.startedAt ? new Date(a.startedAt).getTime() : 0;
3119
+ var tb = b.startedAt ? new Date(b.startedAt).getTime() : 0;
3120
+ return ta - tb;
3121
+ });
3122
+ if (running.length === 0) {
3123
+ return '<div class="sidebar-collapsed-empty" title="无运行中的会话">—</div>';
3124
+ }
3125
+ return '<div class="sidebar-collapsed-tiles">' +
3126
+ running.map(function(s, i) {
3127
+ var activeCls = s.id === state.selectedId ? " active" : "";
3128
+ var title = s.summary || s.command || ("会话 " + (i + 1));
3129
+ return '<button class="sidebar-collapsed-tile' + activeCls + '" type="button" data-collapsed-session-id="' + escapeHtml(s.id) + '" title="' + escapeHtml(title) + '">' + (i + 1) + '</button>';
3130
+ }).join("") +
3131
+ '</div>';
3132
+ }
3133
+
3134
+ function renderSessionsListContent() {
3135
+ return isSidebarNarrow() ? renderCollapsedSessionTiles() : renderSessions();
3136
+ }
3137
+
3094
3138
  function renderSessionManageBar() {
3095
3139
  if (!state.sessionsManageMode) {
3096
3140
  return '<div class="session-manage-bar">' +
@@ -5378,6 +5422,11 @@
5378
5422
  var loginButton = document.getElementById("login-button");
5379
5423
  if (loginButton) {
5380
5424
  loginButton.addEventListener("click", login);
5425
+ var loginForm = document.getElementById("login-form");
5426
+ if (loginForm) loginForm.addEventListener("submit", function(e) {
5427
+ e.preventDefault();
5428
+ login();
5429
+ });
5381
5430
  var loginSwitchServerBtn = document.getElementById("login-switch-server-button");
5382
5431
  if (loginSwitchServerBtn) loginSwitchServerBtn.addEventListener("click", switchServer);
5383
5432
  var passwordEl = document.getElementById("password");
@@ -6540,6 +6589,14 @@
6540
6589
  var target = event.target;
6541
6590
  if (!target || !(target instanceof Element)) return;
6542
6591
 
6592
+ var collapsedTile = target.closest(".sidebar-collapsed-tile");
6593
+ if (collapsedTile && collapsedTile instanceof HTMLElement && collapsedTile.dataset.collapsedSessionId) {
6594
+ event.preventDefault();
6595
+ event.stopPropagation();
6596
+ activateSessionItem(collapsedTile.dataset.collapsedSessionId);
6597
+ return;
6598
+ }
6599
+
6543
6600
  var historyToggle = target.closest("#claude-history-toggle");
6544
6601
  if (historyToggle) {
6545
6602
  event.preventDefault();
@@ -8309,7 +8366,7 @@
8309
8366
  updateSessionsList();
8310
8367
  } else {
8311
8368
  var listEl = document.getElementById("sessions-list");
8312
- var rendered = renderSessions();
8369
+ var rendered = renderSessionsListContent();
8313
8370
  if (listEl && listEl.innerHTML === rendered) {
8314
8371
  var countEl = document.getElementById("session-count");
8315
8372
  if (countEl) countEl.textContent = String(state.sessions.length);
@@ -8368,7 +8425,7 @@
8368
8425
  function updateSessionsList() {
8369
8426
  var listEl = document.getElementById("sessions-list");
8370
8427
  var countEl = document.getElementById("session-count");
8371
- if (listEl) listEl.innerHTML = renderSessions();
8428
+ if (listEl) listEl.innerHTML = renderSessionsListContent();
8372
8429
  if (countEl) countEl.textContent = String(state.sessions.length);
8373
8430
  updateShellChrome();
8374
8431
  // Re-render cross-session queue (container may have been destroyed by DOM rebuild)
@@ -8637,7 +8694,15 @@
8637
8694
 
8638
8695
  function toggleSidebarCollapsed() {
8639
8696
  if (isMobileLayout()) return;
8640
- if (!state.sidebarPinned) return;
8697
+ // drawer 模式(未 pin)下点 collapse 视为「先固定、再收起为窄条」——
8698
+ // 用户直觉是「点了就该看到窄条」,过去这里 early return 让按钮看上去没反应。
8699
+ if (!state.sidebarPinned) {
8700
+ state.sidebarPinned = true;
8701
+ state.sessionsDrawerOpen = true;
8702
+ try {
8703
+ localStorage.setItem("wand-sidebar-pinned", "true");
8704
+ } catch (e) {}
8705
+ }
8641
8706
  state.sidebarCollapsed = !state.sidebarCollapsed;
8642
8707
  try {
8643
8708
  localStorage.setItem("wand-sidebar-collapsed", String(state.sidebarCollapsed));
@@ -11969,6 +12034,7 @@
11969
12034
  var ptySpecialKeyMap = {
11970
12035
  space: " ",
11971
12036
  tab: String.fromCharCode(9),
12037
+ shift_tab: String.fromCharCode(27) + "[Z",
11972
12038
  backspace: String.fromCharCode(127),
11973
12039
  home: String.fromCharCode(27) + "[H",
11974
12040
  end: String.fromCharCode(27) + "[F",
@@ -12026,7 +12092,9 @@
12026
12092
  return {
12027
12093
  ctrl: event.ctrlKey,
12028
12094
  alt: event.altKey,
12029
- shift: event.shiftKey && key.length === 1,
12095
+ // 仅对单字符键保留 shift(控制 toUpperCase 路径),
12096
+ // 但 Tab 特例:物理 Shift+Tab 要走 buildPtySequence 的 back-tab 分支。
12097
+ shift: event.shiftKey && (key.length === 1 || key === "tab"),
12030
12098
  meta: event.metaKey
12031
12099
  };
12032
12100
  }
@@ -12249,6 +12317,8 @@
12249
12317
  function buildPtySequence(key, modifiers) {
12250
12318
  var mods = modifiers || { ctrl: false, alt: false, shift: false };
12251
12319
  if (isModifierKey(key)) return "";
12320
+ // Shift+Tab → CSI Z (back-tab)。Claude Code 用它在 plan / 自动接受 模式间切换。
12321
+ if (key === "tab" && mods.shift) return String.fromCharCode(27) + "[Z";
12252
12322
  var specialSequence = getPtySpecialSequence(key);
12253
12323
  if (specialSequence) return specialSequence;
12254
12324
  if (key.indexOf("ctrl_") === 0) {
@@ -13936,6 +14006,9 @@
13936
14006
  function sendTerminalResize(cols, rows) {
13937
14007
  if (!state.selectedId) return;
13938
14008
  var selectedSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
14009
+ // 会话已被清除(如服务重启后 localStorage 还残留旧 id),后端 resize 会
14010
+ // 直接 400/404,console 留一条红色错误;这里提前剪掉,避免噪音。
14011
+ if (!selectedSess || selectedSess.status !== "running") return;
13939
14012
  if (isStructuredSession(selectedSess)) return;
13940
14013
  var nextSize = { cols: cols, rows: rows };
13941
14014
  if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {