@co0ontty/wand 1.5.5 → 1.5.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.
@@ -1,11 +1,7 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { WandStorage } from "./storage.js";
3
- import { ExecutionMode, SessionSnapshot, WandConfig } from "./types.js";
4
- export interface ProcessEvent {
5
- type: "output" | "status" | "started" | "ended" | "usage" | "task" | "notification";
6
- sessionId: string;
7
- data?: unknown;
8
- }
3
+ import { ExecutionMode, ProcessEventHandler, SessionSnapshot, WandConfig } from "./types.js";
4
+ export type { ProcessEvent, ProcessEventHandler } from "./types.js";
9
5
  /** Human-readable task information for the UI */
10
6
  export interface TaskInfo {
11
7
  title: string;
@@ -17,7 +13,6 @@ export declare class SessionInputError extends Error {
17
13
  readonly sessionStatus?: SessionSnapshot["status"] | undefined;
18
14
  constructor(message: string, code: "SESSION_NOT_FOUND" | "SESSION_NOT_RUNNING" | "SESSION_NO_PTY", sessionId: string, sessionStatus?: SessionSnapshot["status"] | undefined);
19
15
  }
20
- export type ProcessEventHandler = (event: ProcessEvent) => void;
21
16
  /** A Claude Code session discovered by scanning ~/.claude/projects/ directories. */
22
17
  export interface ClaudeHistorySession {
23
18
  claudeSessionId: string;
@@ -1,6 +1,5 @@
1
1
  import { WandStorage } from "./storage.js";
2
- import { ExecutionMode, SessionRunner, SessionSnapshot, WandConfig } from "./types.js";
3
- import { ProcessEvent } from "./ws-broadcast.js";
2
+ import { ExecutionMode, ProcessEvent, SessionRunner, SessionSnapshot, WandConfig } from "./types.js";
4
3
  interface CreateStructuredSessionOptions {
5
4
  cwd: string;
6
5
  mode: ExecutionMode;
package/dist/types.d.ts CHANGED
@@ -8,6 +8,13 @@ export type EscalationScope = "write_file" | "run_command" | "network" | "outsid
8
8
  export type EscalationRunner = "json" | "pty";
9
9
  export type EscalationResolution = "approve_once" | "approve_turn" | "deny" | "fallback_manual";
10
10
  export type EscalationSource = "tool_permission_request" | "sandbox_hard_block" | "workspace_policy_limit" | "cli_capability_limit" | "unknown";
11
+ /** WebSocket / ProcessManager event envelope used throughout the app. */
12
+ export interface ProcessEvent {
13
+ type: "output" | "status" | "started" | "ended" | "usage" | "task" | "notification";
14
+ sessionId: string;
15
+ data?: unknown;
16
+ }
17
+ export type ProcessEventHandler = (event: ProcessEvent) => void;
11
18
  export interface EscalationRequest {
12
19
  requestId: string;
13
20
  scope: EscalationScope;
@@ -804,31 +804,50 @@
804
804
 
805
805
  // General config tab
806
806
  '<div class="settings-panel" id="settings-tab-general">' +
807
- '<div class="field">' +
808
- '<label class="field-label" for="cfg-host">监听地址 (host)</label>' +
809
- '<input id="cfg-host" type="text" class="field-input" placeholder="127.0.0.1" />' +
810
- '</div>' +
811
- '<div class="field">' +
812
- '<label class="field-label" for="cfg-port">端口 (port)</label>' +
813
- '<input id="cfg-port" type="number" class="field-input" placeholder="8443" min="1" max="65535" />' +
807
+ '<div class="field-row">' +
808
+ '<div class="field">' +
809
+ '<label class="field-label" for="cfg-host">监听地址 (host)</label>' +
810
+ '<input id="cfg-host" type="text" class="field-input" placeholder="127.0.0.1" />' +
811
+ '</div>' +
812
+ '<div class="field">' +
813
+ '<label class="field-label" for="cfg-port">端口 (port)</label>' +
814
+ '<input id="cfg-port" type="number" class="field-input" placeholder="8443" min="1" max="65535" />' +
815
+ '</div>' +
814
816
  '</div>' +
815
817
  '<div class="field field-inline">' +
816
- '<label class="field-label" for="cfg-https">启用 HTTPS</label>' +
817
818
  '<input id="cfg-https" type="checkbox" class="field-checkbox" />' +
819
+ '<label class="field-label" for="cfg-https">启用 HTTPS</label>' +
818
820
  '</div>' +
819
- '<div class="field">' +
820
- '<label class="field-label" for="cfg-mode">默认执行模式</label>' +
821
- '<select id="cfg-mode" class="field-input">' +
822
- '<option value="default">default</option>' +
823
- '<option value="assist">assist</option>' +
824
- '<option value="agent">agent</option>' +
825
- '<option value="agent-max">agent-max</option>' +
826
- '<option value="auto-edit">auto-edit</option>' +
827
- '<option value="full-access">full-access</option>' +
828
- '<option value="native">native</option>' +
829
- '<option value="managed">managed</option>' +
830
- '</select>' +
821
+ '<div class="field-row">' +
822
+ '<div class="field">' +
823
+ '<label class="field-label" for="cfg-mode">默认执行模式</label>' +
824
+ '<select id="cfg-mode" class="field-input">' +
825
+ '<option value="default">default</option>' +
826
+ '<option value="assist">assist</option>' +
827
+ '<option value="agent">agent</option>' +
828
+ '<option value="agent-max">agent-max</option>' +
829
+ '<option value="auto-edit">auto-edit</option>' +
830
+ '<option value="full-access">full-access</option>' +
831
+ '<option value="native">native</option>' +
832
+ '<option value="managed">managed</option>' +
833
+ '</select>' +
834
+ '</div>' +
835
+ '<div class="field">' +
836
+ '<label class="field-label" for="cfg-language">回复语言</label>' +
837
+ '<select id="cfg-language" class="field-input">' +
838
+ '<option value="">自动(不指定)</option>' +
839
+ '<option value="中文">中文</option>' +
840
+ '<option value="English">English</option>' +
841
+ '<option value="日本語">日本語</option>' +
842
+ '<option value="한국어">한국어</option>' +
843
+ '<option value="Español">Español</option>' +
844
+ '<option value="Français">Français</option>' +
845
+ '<option value="Deutsch">Deutsch</option>' +
846
+ '<option value="Русский">Русский</option>' +
847
+ '</select>' +
848
+ '</div>' +
831
849
  '</div>' +
850
+ '<p class="field-hint" style="margin-top:-4px;">设置回复语言后,Claude 将尽量使用指定语言回复。</p>' +
832
851
  '<div class="field">' +
833
852
  '<label class="field-label" for="cfg-cwd">默认工作目录</label>' +
834
853
  '<input id="cfg-cwd" type="text" class="field-input" placeholder="/home/user" />' +
@@ -837,52 +856,40 @@
837
856
  '<label class="field-label" for="cfg-shell">Shell</label>' +
838
857
  '<input id="cfg-shell" type="text" class="field-input" placeholder="/bin/bash" />' +
839
858
  '</div>' +
840
- '<div class="field">' +
841
- '<label class="field-label" for="cfg-language">回复语言</label>' +
842
- '<select id="cfg-language" class="field-input">' +
843
- '<option value="">自动(不指定)</option>' +
844
- '<option value="中文">中文</option>' +
845
- '<option value="English">English</option>' +
846
- '<option value="日本語">日本語</option>' +
847
- '<option value="한국어">한국어</option>' +
848
- '<option value="Español">Español</option>' +
849
- '<option value="Français">Français</option>' +
850
- '<option value="Deutsch">Deutsch</option>' +
851
- '<option value="Русский">Русский</option>' +
852
- '</select>' +
853
- '<p class="hint" style="margin-top:4px;margin-bottom:0;">设置后,Claude 将尽量使用指定语言回复。</p>' +
854
- '</div>' +
855
859
  '<button id="save-config-button" class="btn btn-primary btn-block">保存配置</button>' +
856
860
  '<p id="config-message" class="hint hidden"></p>' +
857
861
  '</div>' +
858
862
 
859
863
  // Security tab
860
864
  '<div class="settings-panel" id="settings-tab-security">' +
861
- '<h3 class="settings-section-title">修改密码</h3>' +
862
- '<div class="field">' +
863
- '<label class="field-label" for="new-password">新密码</label>' +
864
- '<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
865
- '</div>' +
866
- '<div class="field">' +
867
- '<label class="field-label" for="confirm-password">确认密码</label>' +
868
- '<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
869
- '</div>' +
870
- '<button id="save-password-button" class="btn btn-primary btn-block">保存密码</button>' +
871
- '<p id="settings-error" class="error-message hidden"></p>' +
872
- '<p id="settings-success" class="hint hidden" style="color: var(--success);"></p>' +
873
- '<hr class="settings-divider" />' +
874
- '<h3 class="settings-section-title">SSL 证书</h3>' +
875
- '<p class="settings-hint" id="cert-status">加载中...</p>' +
876
- '<div class="field">' +
877
- '<label class="field-label" for="cert-key-file">私钥文件 (.key)</label>' +
878
- '<input id="cert-key-file" type="file" class="field-input field-file" accept=".key,.pem" />' +
865
+ '<div class="settings-card">' +
866
+ '<h3 class="settings-section-title">\ud83d\udd12 修改密码</h3>' +
867
+ '<div class="field">' +
868
+ '<label class="field-label" for="new-password">新密码</label>' +
869
+ '<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
870
+ '</div>' +
871
+ '<div class="field">' +
872
+ '<label class="field-label" for="confirm-password">确认密码</label>' +
873
+ '<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
874
+ '</div>' +
875
+ '<button id="save-password-button" class="btn btn-primary btn-block">保存密码</button>' +
876
+ '<p id="settings-error" class="error-message hidden"></p>' +
877
+ '<p id="settings-success" class="hint hidden" style="color: var(--success);"></p>' +
879
878
  '</div>' +
880
- '<div class="field">' +
881
- '<label class="field-label" for="cert-cert-file">证书文件 (.crt/.pem)</label>' +
882
- '<input id="cert-cert-file" type="file" class="field-input field-file" accept=".crt,.pem,.cert" />' +
879
+ '<div class="settings-card">' +
880
+ '<h3 class="settings-section-title">\ud83d\udd10 SSL 证书</h3>' +
881
+ '<p class="settings-hint" id="cert-status">加载中...</p>' +
882
+ '<div class="field">' +
883
+ '<label class="field-label" for="cert-key-file">私钥文件 (.key)</label>' +
884
+ '<input id="cert-key-file" type="file" class="field-input field-file" accept=".key,.pem" />' +
885
+ '</div>' +
886
+ '<div class="field">' +
887
+ '<label class="field-label" for="cert-cert-file">证书文件 (.crt/.pem)</label>' +
888
+ '<input id="cert-cert-file" type="file" class="field-input field-file" accept=".crt,.pem,.cert" />' +
889
+ '</div>' +
890
+ '<button id="upload-cert-button" class="btn btn-primary btn-block">上传证书</button>' +
891
+ '<p id="cert-message" class="hint hidden"></p>' +
883
892
  '</div>' +
884
- '<button id="upload-cert-button" class="btn btn-primary btn-block">上传证书</button>' +
885
- '<p id="cert-message" class="hint hidden"></p>' +
886
893
  '</div>' +
887
894
 
888
895
  // Command presets tab
@@ -3943,15 +3950,23 @@
3943
3950
  function selectSession(id) {
3944
3951
  var foundSession = state.sessions.find(function(item) { return item.id === id; });
3945
3952
  console.log("[WAND] selectSession id:", id, "found:", !!foundSession, "sessionKind:", foundSession && foundSession.sessionKind, "runner:", foundSession && foundSession.runner, "isStructured:", isStructuredSession(foundSession));
3953
+ if (!foundSession) {
3954
+ console.warn("[WAND] selectSession: session not found, skipping", id);
3955
+ return;
3956
+ }
3946
3957
  state.selectedId = id;
3947
3958
  persistSelectedId();
3959
+ // Clear queued inputs from the previous session to prevent cross-session leaks
3960
+ state.messageQueue = [];
3961
+ state.pendingMessages = [];
3962
+ updateQueueCounter();
3948
3963
  resetChatRenderCache();
3949
3964
  state.currentMessages = [];
3950
3965
  if (chatRenderTimer) { clearTimeout(chatRenderTimer); chatRenderTimer = null; }
3951
3966
  // Reset todo progress bar
3952
3967
  var todoEl = document.getElementById("todo-progress");
3953
3968
  if (todoEl) todoEl.classList.add("hidden");
3954
- var session = state.sessions.find(function(item) { return item.id === id; });
3969
+ var session = foundSession;
3955
3970
  state.preferredCommand = getPreferredTool();
3956
3971
  state.chatMode = getSafeModeForTool("claude", session && session.mode ? session.mode : state.chatMode);
3957
3972
  if (state.terminalInteractive && session && session.status !== "running") {
@@ -4010,6 +4025,8 @@
4010
4025
  var focusTrapHandler = null;
4011
4026
 
4012
4027
  function openSessionModal() {
4028
+ // Close settings modal first if open (mutual exclusion)
4029
+ closeSettingsModal();
4013
4030
  state.modalOpen = true;
4014
4031
  state.sessionsDrawerOpen = false;
4015
4032
  updateDrawerState();
@@ -4084,6 +4101,8 @@
4084
4101
  }
4085
4102
 
4086
4103
  function openSettingsModal() {
4104
+ // Close session modal first if open (mutual exclusion)
4105
+ closeSessionModal();
4087
4106
  var modal = document.getElementById("settings-modal");
4088
4107
  if (modal) {
4089
4108
  modal.classList.remove("hidden");
@@ -4240,7 +4259,7 @@
4240
4259
  '<span class="preset-detail">' + escapeHtml(p.command) + (p.mode ? ' (' + escapeHtml(p.mode) + ')' : '') + '</span>' +
4241
4260
  '</div>';
4242
4261
  }
4243
- if (!html) html = '<p class="settings-hint">没有命令预设。</p>';
4262
+ if (!html) html = '<div class="empty-state-compact"><span class="empty-icon">\u2699</span><span>\u6ca1\u6709\u547d\u4ee4\u9884\u8bbe</span><span class="hint">\u5728 config.json \u7684 commandPresets \u4e2d\u914d\u7f6e</span></div>';
4244
4263
  presetsList.innerHTML = html;
4245
4264
  }
4246
4265
  })
@@ -4542,7 +4561,10 @@
4542
4561
  });
4543
4562
  }
4544
4563
 
4564
+ var _sessionCreating = false;
4565
+
4545
4566
  function runCommand() {
4567
+ if (_sessionCreating) return;
4546
4568
  var cwdEl = document.getElementById("cwd");
4547
4569
  var errorEl = document.getElementById("modal-error");
4548
4570
  var command = getPreferredTool();
@@ -4564,6 +4586,7 @@
4564
4586
 
4565
4587
  function startStructuredSessionFromModal(cwd, mode, errorEl) {
4566
4588
  console.log("[WAND] startStructuredSessionFromModal cwd:", cwd, "mode:", mode);
4589
+ _sessionCreating = true;
4567
4590
  state.modeValue = mode;
4568
4591
  state.chatMode = mode;
4569
4592
  state.sessionTool = "claude";
@@ -4579,11 +4602,13 @@
4579
4602
  .then(function() { focusInputBox(true); })
4580
4603
  .catch(function(error) {
4581
4604
  showError(errorEl, (error && error.message) || "无法启动结构化会话,请确认 Claude 已正确安装。");
4582
- });
4605
+ })
4606
+ .finally(function() { _sessionCreating = false; });
4583
4607
  }
4584
4608
 
4585
4609
  function runPtyCommandFromModal(command, cwd, mode, errorEl) {
4586
4610
  console.log("[WAND] runPtyCommandFromModal command:", command, "cwd:", cwd, "mode:", mode);
4611
+ _sessionCreating = true;
4587
4612
  state.modeValue = mode;
4588
4613
  state.chatMode = mode;
4589
4614
  state.sessionTool = command;
@@ -4626,7 +4651,8 @@
4626
4651
  })
4627
4652
  .catch(function() {
4628
4653
  showError(errorEl, "无法启动会话,请确认 Claude 已正确安装。");
4629
- });
4654
+ })
4655
+ .finally(function() { _sessionCreating = false; });
4630
4656
  }
4631
4657
 
4632
4658
  function initBlankChatCwd() {
@@ -6012,9 +6038,12 @@
6012
6038
  });
6013
6039
  }
6014
6040
 
6041
+ var _resumeInProgress = false;
6042
+
6015
6043
  function resumeSession(sessionId, errorEl) {
6016
6044
  console.log("[WAND] resumeSession sessionId:", sessionId);
6017
- if (!sessionId) return Promise.resolve(null);
6045
+ if (!sessionId || _resumeInProgress) return Promise.resolve(null);
6046
+ _resumeInProgress = true;
6018
6047
  return fetch("/api/sessions/" + encodeURIComponent(sessionId) + "/resume", {
6019
6048
  method: "POST",
6020
6049
  headers: { "Content-Type": "application/json" },
@@ -6040,7 +6069,8 @@
6040
6069
  if (errorEl) showError(errorEl, message);
6041
6070
  else showToast(message, "error");
6042
6071
  return null;
6043
- });
6072
+ })
6073
+ .finally(function() { _resumeInProgress = false; });
6044
6074
  }
6045
6075
 
6046
6076
  function resumeClaudeSessionById(claudeSessionId, errorEl) {
@@ -8177,12 +8207,16 @@
8177
8207
  smartScrollToBottom(chatMessages);
8178
8208
  });
8179
8209
  } else if (msgCount === existingCount && outputHash !== prevHash) {
8180
- // Same message count but content changed (streaming update). Re-render in place
8181
- // by index so assistant growth, tool cards, and retroactive message fixes all show up.
8210
+ // Same message count but content changed (streaming update).
8211
+ // Optimization: only re-render the newest N messages (column-reverse: first children)
8212
+ // that actually differ, starting from the top (newest). Most streaming updates only
8213
+ // touch the latest assistant turn, so we can skip scanning all older messages.
8182
8214
  var existingEls = Array.from(chatMessages.querySelectorAll(".chat-message"));
8183
8215
  var reversedMessages = messages.slice().reverse();
8184
8216
  var replacedAny = false;
8185
- for (var mi = 0; mi < reversedMessages.length && mi < existingEls.length; mi++) {
8217
+ // Scan from newest (index 0 in reversed) up to MAX_STREAMING_SCAN messages
8218
+ var MAX_STREAMING_SCAN = Math.min(4, reversedMessages.length, existingEls.length);
8219
+ for (var mi = 0; mi < MAX_STREAMING_SCAN; mi++) {
8186
8220
  var currentEl = existingEls[mi];
8187
8221
  var tmpWrap = document.createElement("div");
8188
8222
  var srOrigIdx = reversedMessages.length - 1 - mi;
@@ -8193,8 +8227,16 @@
8193
8227
  chatMessages.replaceChild(replacementEl, currentEl);
8194
8228
  attachCopyHandler(replacementEl);
8195
8229
  replacedAny = true;
8230
+ } else if (mi > 0) {
8231
+ // Once we hit an unchanged older message, stop scanning
8232
+ break;
8196
8233
  }
8197
8234
  }
8235
+ // Fallback: if hash changed but no visible diff found in the top N messages,
8236
+ // the change is deeper — trigger a full render to avoid stale display.
8237
+ if (!replacedAny && reversedMessages.length > MAX_STREAMING_SCAN) {
8238
+ fullRenderChat();
8239
+ }
8198
8240
  if (replacedAny) {
8199
8241
  requestAnimationFrame(function() {
8200
8242
  smartScrollToBottom(chatMessages);
@@ -4802,7 +4802,7 @@
4802
4802
  .modal-backdrop {
4803
4803
  position: fixed;
4804
4804
  inset: 0;
4805
- z-index: 100;
4805
+ z-index: 500;
4806
4806
  background: rgba(42, 28, 18, 0.52);
4807
4807
  backdrop-filter: blur(12px);
4808
4808
  -webkit-backdrop-filter: blur(12px);
@@ -5684,6 +5684,7 @@
5684
5684
  .modal { max-height: 80vh; }
5685
5685
  .modal-header { padding: 8px 10px; min-height: 36px; }
5686
5686
  .modal-body { padding: 8px 10px; }
5687
+ .field-row { grid-template-columns: 1fr; gap: 0; }
5687
5688
 
5688
5689
  .btn { min-height: 36px; padding: 8px 12px; }
5689
5690
  .btn-sm { min-height: 28px; padding: 4px 8px; }
@@ -6287,18 +6288,21 @@
6287
6288
  background: none;
6288
6289
  border: none;
6289
6290
  border-bottom: 2px solid transparent;
6291
+ border-radius: var(--radius-sm) var(--radius-sm) 0 0;
6290
6292
  cursor: pointer;
6291
- transition: color 0.15s, border-color 0.15s;
6293
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
6292
6294
  white-space: nowrap;
6293
6295
  }
6294
6296
 
6295
6297
  .settings-tab:hover {
6296
6298
  color: var(--text-primary);
6299
+ background: rgba(197, 101, 61, 0.06);
6297
6300
  }
6298
6301
 
6299
6302
  .settings-tab.active {
6300
6303
  color: var(--accent);
6301
6304
  border-bottom-color: var(--accent);
6305
+ background: var(--accent-muted);
6302
6306
  }
6303
6307
 
6304
6308
  .settings-panel {
@@ -6312,8 +6316,11 @@
6312
6316
  .settings-about-info {
6313
6317
  display: flex;
6314
6318
  flex-direction: column;
6315
- gap: 10px;
6319
+ gap: 0;
6316
6320
  margin-bottom: 18px;
6321
+ background: var(--bg-secondary);
6322
+ border-radius: var(--radius-md);
6323
+ padding: 2px 14px;
6317
6324
  }
6318
6325
 
6319
6326
  .settings-about-row {
@@ -6321,6 +6328,12 @@
6321
6328
  justify-content: space-between;
6322
6329
  align-items: center;
6323
6330
  font-size: 0.8125rem;
6331
+ padding: 9px 0;
6332
+ border-bottom: 1px solid var(--border-subtle);
6333
+ }
6334
+
6335
+ .settings-about-row:last-child {
6336
+ border-bottom: none;
6324
6337
  }
6325
6338
 
6326
6339
  .settings-label {
@@ -6356,8 +6369,8 @@
6356
6369
  .settings-section-title {
6357
6370
  font-size: 0.8125rem;
6358
6371
  font-weight: 600;
6359
- color: var(--fg-primary);
6360
- margin-bottom: 10px;
6372
+ color: var(--text-primary);
6373
+ margin: 0 0 12px 0;
6361
6374
  letter-spacing: 0.02em;
6362
6375
  }
6363
6376
 
@@ -6367,13 +6380,6 @@
6367
6380
  margin-top: 10px;
6368
6381
  }
6369
6382
 
6370
- .settings-section-title {
6371
- font-size: 0.875rem;
6372
- font-weight: 600;
6373
- color: var(--text-primary);
6374
- margin: 0 0 12px 0;
6375
- }
6376
-
6377
6383
  .settings-divider {
6378
6384
  border: none;
6379
6385
  border-top: 1px solid var(--border-subtle);
@@ -6397,6 +6403,49 @@
6397
6403
  margin-bottom: 0;
6398
6404
  }
6399
6405
 
6406
+ .field-row {
6407
+ display: grid;
6408
+ grid-template-columns: 1fr 1fr;
6409
+ gap: 12px;
6410
+ margin-bottom: 14px;
6411
+ }
6412
+ .field-row .field {
6413
+ margin-bottom: 0;
6414
+ }
6415
+
6416
+ .settings-card {
6417
+ background: var(--bg-secondary);
6418
+ border-radius: var(--radius-md);
6419
+ padding: 16px;
6420
+ margin-bottom: 14px;
6421
+ }
6422
+ .settings-card .field:last-of-type {
6423
+ margin-bottom: 12px;
6424
+ }
6425
+ .settings-card .settings-section-title {
6426
+ margin-top: 0;
6427
+ }
6428
+ .settings-card .field-input {
6429
+ background: rgba(255, 255, 255, 0.6);
6430
+ }
6431
+ .settings-card .btn-block {
6432
+ margin-bottom: 0;
6433
+ }
6434
+
6435
+ .empty-state-compact {
6436
+ display: flex;
6437
+ flex-direction: column;
6438
+ align-items: center;
6439
+ gap: 6px;
6440
+ padding: 32px 16px;
6441
+ color: var(--text-muted);
6442
+ font-size: 0.8125rem;
6443
+ }
6444
+ .empty-state-compact .empty-icon {
6445
+ font-size: 1.5rem;
6446
+ opacity: 0.5;
6447
+ }
6448
+
6400
6449
  .field-checkbox {
6401
6450
  width: 18px;
6402
6451
  height: 18px;
@@ -3,12 +3,8 @@
3
3
  * Handles debounced output events, backpressure control, and client subscriptions.
4
4
  */
5
5
  import { WebSocketServer } from "ws";
6
- import type { SessionSnapshot } from "./types.js";
7
- export interface ProcessEvent {
8
- type: "output" | "status" | "started" | "ended" | "usage" | "task" | "notification";
9
- sessionId: string;
10
- data?: unknown;
11
- }
6
+ import type { SessionSnapshot, ProcessEvent } from "./types.js";
7
+ export type { ProcessEvent } from "./types.js";
12
8
  export declare class WsBroadcastManager {
13
9
  private wss;
14
10
  private clients;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.5.5",
3
+ "version": "1.5.7",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {