@co0ontty/wand 1.5.4 → 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
|
|
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;
|
|
@@ -164,10 +164,13 @@
|
|
|
164
164
|
var _statusBarStartTime = 0;
|
|
165
165
|
|
|
166
166
|
function renderStructuredStatusBar(chatMessages, session) {
|
|
167
|
-
//
|
|
168
|
-
var
|
|
167
|
+
// Status bar now lives above the input-composer, inside .input-panel
|
|
168
|
+
var inputPanel = document.querySelector(".input-panel");
|
|
169
|
+
var existing = document.querySelector(".structured-status-bar");
|
|
170
|
+
var composer = document.querySelector(".input-composer");
|
|
169
171
|
if (!session || !isStructuredSession(session)) {
|
|
170
172
|
if (existing) existing.remove();
|
|
173
|
+
if (composer) composer.classList.remove("in-flight");
|
|
171
174
|
clearInterval(_statusBarTimerId);
|
|
172
175
|
_statusBarTimerId = null;
|
|
173
176
|
return;
|
|
@@ -181,21 +184,26 @@
|
|
|
181
184
|
_statusBarStartTime = Date.now();
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
|
|
187
|
+
// Add glow to input composer
|
|
188
|
+
if (composer) composer.classList.add("in-flight");
|
|
189
|
+
|
|
190
|
+
if (!existing && inputPanel && composer) {
|
|
185
191
|
var bar = document.createElement("div");
|
|
186
192
|
bar.className = "structured-status-bar";
|
|
187
193
|
bar.innerHTML =
|
|
194
|
+
'<span class="status-bar-dot"></span>' +
|
|
188
195
|
'<span class="status-bar-label">回复中</span>' +
|
|
189
|
-
'<div class="status-bar-track"><div class="status-bar-fill"></div></div>' +
|
|
190
196
|
'<span class="status-bar-timer">0.0s</span>';
|
|
191
|
-
//
|
|
192
|
-
|
|
197
|
+
// Insert right before the input-composer element
|
|
198
|
+
inputPanel.insertBefore(bar, composer);
|
|
193
199
|
existing = bar;
|
|
194
|
-
} else if (existing.classList.contains("completed")) {
|
|
200
|
+
} else if (existing && existing.classList.contains("completed")) {
|
|
195
201
|
// Was completed, now in-flight again — reset
|
|
196
202
|
existing.classList.remove("completed");
|
|
197
203
|
existing.style.animation = "none";
|
|
198
204
|
existing.querySelector(".status-bar-label").textContent = "回复中";
|
|
205
|
+
var dot = existing.querySelector(".status-bar-dot");
|
|
206
|
+
if (dot) dot.style.display = "";
|
|
199
207
|
_statusBarStartTime = Date.now();
|
|
200
208
|
}
|
|
201
209
|
|
|
@@ -214,12 +222,17 @@
|
|
|
214
222
|
clearInterval(_statusBarTimerId);
|
|
215
223
|
_statusBarTimerId = null;
|
|
216
224
|
|
|
225
|
+
// Remove glow from input composer
|
|
226
|
+
if (composer) composer.classList.remove("in-flight");
|
|
227
|
+
|
|
217
228
|
if (existing && !existing.classList.contains("completed")) {
|
|
218
229
|
// Just finished — transition to completed state
|
|
219
230
|
var elapsed = _statusBarStartTime ? ((Date.now() - _statusBarStartTime) / 1000).toFixed(1) : "0.0";
|
|
220
231
|
existing.classList.add("completed");
|
|
221
232
|
existing.querySelector(".status-bar-label").textContent = "完成";
|
|
222
233
|
existing.querySelector(".status-bar-timer").textContent = elapsed + "s";
|
|
234
|
+
var dot = existing.querySelector(".status-bar-dot");
|
|
235
|
+
if (dot) dot.style.display = "none";
|
|
223
236
|
_statusBarStartTime = 0;
|
|
224
237
|
// Remove after animation ends
|
|
225
238
|
setTimeout(function() {
|
|
@@ -471,6 +484,8 @@
|
|
|
471
484
|
if (!state.selectedId) return "";
|
|
472
485
|
var isTerminal = state.currentView === "terminal";
|
|
473
486
|
if (!isTerminal) return "";
|
|
487
|
+
var sel = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
488
|
+
if (sel && isStructuredSession(sel)) return "";
|
|
474
489
|
var keys = renderShortcutKeys();
|
|
475
490
|
var arrow = state.shortcutsExpanded ? '›' : '‹';
|
|
476
491
|
return '<div class="inline-shortcuts-wrap' + (state.shortcutsExpanded ? ' expanded' : '') + '">' +
|
|
@@ -484,6 +499,8 @@
|
|
|
484
499
|
if (!state.selectedId) return "";
|
|
485
500
|
var isTerminal = state.currentView === "terminal";
|
|
486
501
|
if (!isTerminal) return "";
|
|
502
|
+
var sel = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
503
|
+
if (sel && isStructuredSession(sel)) return "";
|
|
487
504
|
return '<div class="inline-shortcuts-expanded-row' + (state.shortcutsExpanded ? ' visible' : '') + '">' + renderShortcutKeys() + '</div>';
|
|
488
505
|
}
|
|
489
506
|
|
|
@@ -700,12 +717,11 @@
|
|
|
700
717
|
'<span id="session-mode-display" class="session-mode-display">' + (selectedSession ? getModeLabel(selectedSession.mode) : '默认') + '</span>' +
|
|
701
718
|
(selectedSession && selectedSession.autoApprovePermissions ? '<span class="session-info-separator">|</span><span id="auto-approve-toggle" class="auto-approve-indicator active" title="自动批准已启用 — 点击关闭">🛡 自动批准</span>' : '<span class="session-info-separator">|</span><span id="auto-approve-toggle" class="auto-approve-indicator" title="自动批准已关闭 — 点击开启">🛡 手动</span>') +
|
|
702
719
|
'<span class="session-info-separator">|</span>' +
|
|
703
|
-
'<span id="session-kind-display" class="session-kind-display">' + (selectedSession ? (
|
|
720
|
+
'<span id="session-kind-display" class="session-kind-display">' + (selectedSession ? getSessionKindLabel(selectedSession) : '终端') + '</span>' +
|
|
704
721
|
'<span class="session-info-separator">|</span>' +
|
|
705
722
|
'<span id="session-status-display" class="session-status-display">' + (selectedSession ? getSessionStatusLabel(selectedSession) : '-') + '</span>' +
|
|
706
723
|
(selectedSession && selectedSession.claudeSessionId ? '<span class="session-info-separator">|</span><span id="claude-session-id-badge" class="claude-session-id-badge" data-claude-id="' + escapeHtml(selectedSession.claudeSessionId) + '" title="点击复制 Claude 会话 ID">☁ ' + escapeHtml(selectedSession.claudeSessionId.slice(0, 8)) + '</span>' : '') +
|
|
707
|
-
'<span class="session-info-separator">|</span>' +
|
|
708
|
-
'<span id="session-exit-display" class="session-exit-display">exit=' + (selectedSession && selectedSession.exitCode !== undefined ? selectedSession.exitCode : 'n/a') + '</span>' +
|
|
724
|
+
(selectedSession && !isStructuredSession(selectedSession) ? '<span class="session-info-separator">|</span><span id="session-exit-display" class="session-exit-display">退出码=' + (selectedSession.exitCode !== undefined ? selectedSession.exitCode : 'n/a') + '</span>' : '') +
|
|
709
725
|
'</div>' +
|
|
710
726
|
'</div>' +
|
|
711
727
|
'<p id="action-error" class="error-message hidden"></p>' +
|
|
@@ -788,31 +804,50 @@
|
|
|
788
804
|
|
|
789
805
|
// General config tab
|
|
790
806
|
'<div class="settings-panel" id="settings-tab-general">' +
|
|
791
|
-
'<div class="field">' +
|
|
792
|
-
'<
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
'<
|
|
797
|
-
|
|
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>' +
|
|
798
816
|
'</div>' +
|
|
799
817
|
'<div class="field field-inline">' +
|
|
800
|
-
'<label class="field-label" for="cfg-https">启用 HTTPS</label>' +
|
|
801
818
|
'<input id="cfg-https" type="checkbox" class="field-checkbox" />' +
|
|
819
|
+
'<label class="field-label" for="cfg-https">启用 HTTPS</label>' +
|
|
802
820
|
'</div>' +
|
|
803
|
-
'<div class="field">' +
|
|
804
|
-
'<
|
|
805
|
-
|
|
806
|
-
'<
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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>' +
|
|
815
849
|
'</div>' +
|
|
850
|
+
'<p class="field-hint" style="margin-top:-4px;">设置回复语言后,Claude 将尽量使用指定语言回复。</p>' +
|
|
816
851
|
'<div class="field">' +
|
|
817
852
|
'<label class="field-label" for="cfg-cwd">默认工作目录</label>' +
|
|
818
853
|
'<input id="cfg-cwd" type="text" class="field-input" placeholder="/home/user" />' +
|
|
@@ -821,52 +856,40 @@
|
|
|
821
856
|
'<label class="field-label" for="cfg-shell">Shell</label>' +
|
|
822
857
|
'<input id="cfg-shell" type="text" class="field-input" placeholder="/bin/bash" />' +
|
|
823
858
|
'</div>' +
|
|
824
|
-
'<div class="field">' +
|
|
825
|
-
'<label class="field-label" for="cfg-language">回复语言</label>' +
|
|
826
|
-
'<select id="cfg-language" class="field-input">' +
|
|
827
|
-
'<option value="">自动(不指定)</option>' +
|
|
828
|
-
'<option value="中文">中文</option>' +
|
|
829
|
-
'<option value="English">English</option>' +
|
|
830
|
-
'<option value="日本語">日本語</option>' +
|
|
831
|
-
'<option value="한국어">한국어</option>' +
|
|
832
|
-
'<option value="Español">Español</option>' +
|
|
833
|
-
'<option value="Français">Français</option>' +
|
|
834
|
-
'<option value="Deutsch">Deutsch</option>' +
|
|
835
|
-
'<option value="Русский">Русский</option>' +
|
|
836
|
-
'</select>' +
|
|
837
|
-
'<p class="hint" style="margin-top:4px;margin-bottom:0;">设置后,Claude 将尽量使用指定语言回复。</p>' +
|
|
838
|
-
'</div>' +
|
|
839
859
|
'<button id="save-config-button" class="btn btn-primary btn-block">保存配置</button>' +
|
|
840
860
|
'<p id="config-message" class="hint hidden"></p>' +
|
|
841
861
|
'</div>' +
|
|
842
862
|
|
|
843
863
|
// Security tab
|
|
844
864
|
'<div class="settings-panel" id="settings-tab-security">' +
|
|
845
|
-
'<
|
|
846
|
-
|
|
847
|
-
'<
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
'<
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
'<h3 class="settings-section-title">SSL 证书</h3>' +
|
|
859
|
-
'<p class="settings-hint" id="cert-status">加载中...</p>' +
|
|
860
|
-
'<div class="field">' +
|
|
861
|
-
'<label class="field-label" for="cert-key-file">私钥文件 (.key)</label>' +
|
|
862
|
-
'<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>' +
|
|
863
878
|
'</div>' +
|
|
864
|
-
'<div class="
|
|
865
|
-
'<
|
|
866
|
-
'<
|
|
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>' +
|
|
867
892
|
'</div>' +
|
|
868
|
-
'<button id="upload-cert-button" class="btn btn-primary btn-block">上传证书</button>' +
|
|
869
|
-
'<p id="cert-message" class="hint hidden"></p>' +
|
|
870
893
|
'</div>' +
|
|
871
894
|
|
|
872
895
|
// Command presets tab
|
|
@@ -1743,7 +1766,15 @@
|
|
|
1743
1766
|
if (!session) return "";
|
|
1744
1767
|
if (session.archived) return "已归档";
|
|
1745
1768
|
if (session.permissionBlocked) return "等待授权";
|
|
1746
|
-
|
|
1769
|
+
var statusMap = {
|
|
1770
|
+
"stopped": "已停止",
|
|
1771
|
+
"running": "运行中",
|
|
1772
|
+
"idle": "空闲",
|
|
1773
|
+
"thinking": "思考中",
|
|
1774
|
+
"waiting-input": "等待输入",
|
|
1775
|
+
"initializing": "启动中"
|
|
1776
|
+
};
|
|
1777
|
+
return statusMap[session.status] || session.status;
|
|
1747
1778
|
}
|
|
1748
1779
|
|
|
1749
1780
|
function getSessionStatusClass(session) {
|
|
@@ -2312,19 +2343,6 @@
|
|
|
2312
2343
|
var selectedIndex = -1;
|
|
2313
2344
|
var folderItems = [];
|
|
2314
2345
|
|
|
2315
|
-
function saveWorkingDir(path) {
|
|
2316
|
-
state.workingDir = path;
|
|
2317
|
-
try {
|
|
2318
|
-
localStorage.setItem("wand-working-dir", path);
|
|
2319
|
-
} catch (e) {
|
|
2320
|
-
// Ignore localStorage errors
|
|
2321
|
-
}
|
|
2322
|
-
// Also add to recent paths (defined later, will be called after function is available)
|
|
2323
|
-
if (typeof addRecentPath === "function") {
|
|
2324
|
-
addRecentPath(path);
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
2346
|
// Helper functions for path validation feedback
|
|
2329
2347
|
function showValidationError(message) {
|
|
2330
2348
|
if (folderPickerInput) {
|
|
@@ -2349,21 +2367,7 @@
|
|
|
2349
2367
|
}
|
|
2350
2368
|
|
|
2351
2369
|
// Helper functions for recent paths (single source: backend API)
|
|
2352
|
-
|
|
2353
|
-
fetch("/api/recent-paths", { credentials: "same-origin" })
|
|
2354
|
-
.then(function(res) { return res.json(); })
|
|
2355
|
-
.then(function(items) { callback(items || []); })
|
|
2356
|
-
.catch(function() { callback([]); });
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
function addRecentPath(path) {
|
|
2360
|
-
return fetch("/api/recent-paths", {
|
|
2361
|
-
method: "POST",
|
|
2362
|
-
headers: { "Content-Type": "application/json" },
|
|
2363
|
-
credentials: "same-origin",
|
|
2364
|
-
body: JSON.stringify({ path: path })
|
|
2365
|
-
}).catch(function() {});
|
|
2366
|
-
}
|
|
2370
|
+
// NOTE: fetchRecentPaths and addRecentPath are defined at outer scope
|
|
2367
2371
|
|
|
2368
2372
|
function renderRecentPathsHtml(items) {
|
|
2369
2373
|
if (!items.length) return "";
|
|
@@ -2690,6 +2694,32 @@
|
|
|
2690
2694
|
setupVisualViewportHandlers();
|
|
2691
2695
|
}
|
|
2692
2696
|
|
|
2697
|
+
function saveWorkingDir(path) {
|
|
2698
|
+
state.workingDir = path;
|
|
2699
|
+
try {
|
|
2700
|
+
localStorage.setItem("wand-working-dir", path);
|
|
2701
|
+
} catch (e) {
|
|
2702
|
+
// Ignore localStorage errors
|
|
2703
|
+
}
|
|
2704
|
+
addRecentPath(path);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
function fetchRecentPaths(callback) {
|
|
2708
|
+
fetch("/api/recent-paths", { credentials: "same-origin" })
|
|
2709
|
+
.then(function(res) { return res.json(); })
|
|
2710
|
+
.then(function(items) { callback(items || []); })
|
|
2711
|
+
.catch(function() { callback([]); });
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
function addRecentPath(path) {
|
|
2715
|
+
return fetch("/api/recent-paths", {
|
|
2716
|
+
method: "POST",
|
|
2717
|
+
headers: { "Content-Type": "application/json" },
|
|
2718
|
+
credentials: "same-origin",
|
|
2719
|
+
body: JSON.stringify({ path: path })
|
|
2720
|
+
}).catch(function() {});
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2693
2723
|
function activateSessionItem(sessionId) {
|
|
2694
2724
|
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
2695
2725
|
if (session && session.status !== "running" && !isStructuredSession(session)) {
|
|
@@ -3490,13 +3520,13 @@
|
|
|
3490
3520
|
}
|
|
3491
3521
|
|
|
3492
3522
|
function getSessionKindLabel(session) {
|
|
3493
|
-
return isStructuredSession(session) ? "
|
|
3523
|
+
return isStructuredSession(session) ? "结构化" : "终端";
|
|
3494
3524
|
}
|
|
3495
3525
|
|
|
3496
3526
|
function getSessionKindDescription(session) {
|
|
3497
3527
|
return isStructuredSession(session)
|
|
3498
|
-
? "
|
|
3499
|
-
: "
|
|
3528
|
+
? "结构化 · 块级记录"
|
|
3529
|
+
: "终端 · PTY 会话";
|
|
3500
3530
|
}
|
|
3501
3531
|
|
|
3502
3532
|
function isRecoverableToolError(toolResult, nextResult) {
|
|
@@ -3830,8 +3860,9 @@
|
|
|
3830
3860
|
var exitEl = document.getElementById("session-exit-display");
|
|
3831
3861
|
var cwdText = selectedSession && selectedSession.cwd ? selectedSession.cwd : "未设置目录";
|
|
3832
3862
|
var modeText = selectedSession ? getModeLabel(selectedSession.mode) : "默认";
|
|
3833
|
-
var kindText = selectedSession ? getSessionKindLabel(selectedSession) : "
|
|
3834
|
-
var
|
|
3863
|
+
var kindText = selectedSession ? getSessionKindLabel(selectedSession) : "终端";
|
|
3864
|
+
var isStructured = selectedSession && isStructuredSession(selectedSession);
|
|
3865
|
+
var exitText = isStructured ? "" : "退出码=" + (selectedSession && selectedSession.exitCode !== undefined ? selectedSession.exitCode : "n/a");
|
|
3835
3866
|
if (cwdEl && cwdEl.textContent !== cwdText) cwdEl.textContent = cwdText;
|
|
3836
3867
|
if (modeEl && modeEl.textContent !== modeText) modeEl.textContent = modeText;
|
|
3837
3868
|
if (kindEl && kindEl.textContent !== kindText) kindEl.textContent = kindText;
|
|
@@ -3919,15 +3950,23 @@
|
|
|
3919
3950
|
function selectSession(id) {
|
|
3920
3951
|
var foundSession = state.sessions.find(function(item) { return item.id === id; });
|
|
3921
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
|
+
}
|
|
3922
3957
|
state.selectedId = id;
|
|
3923
3958
|
persistSelectedId();
|
|
3959
|
+
// Clear queued inputs from the previous session to prevent cross-session leaks
|
|
3960
|
+
state.messageQueue = [];
|
|
3961
|
+
state.pendingMessages = [];
|
|
3962
|
+
updateQueueCounter();
|
|
3924
3963
|
resetChatRenderCache();
|
|
3925
3964
|
state.currentMessages = [];
|
|
3926
3965
|
if (chatRenderTimer) { clearTimeout(chatRenderTimer); chatRenderTimer = null; }
|
|
3927
3966
|
// Reset todo progress bar
|
|
3928
3967
|
var todoEl = document.getElementById("todo-progress");
|
|
3929
3968
|
if (todoEl) todoEl.classList.add("hidden");
|
|
3930
|
-
var session =
|
|
3969
|
+
var session = foundSession;
|
|
3931
3970
|
state.preferredCommand = getPreferredTool();
|
|
3932
3971
|
state.chatMode = getSafeModeForTool("claude", session && session.mode ? session.mode : state.chatMode);
|
|
3933
3972
|
if (state.terminalInteractive && session && session.status !== "running") {
|
|
@@ -3986,6 +4025,8 @@
|
|
|
3986
4025
|
var focusTrapHandler = null;
|
|
3987
4026
|
|
|
3988
4027
|
function openSessionModal() {
|
|
4028
|
+
// Close settings modal first if open (mutual exclusion)
|
|
4029
|
+
closeSettingsModal();
|
|
3989
4030
|
state.modalOpen = true;
|
|
3990
4031
|
state.sessionsDrawerOpen = false;
|
|
3991
4032
|
updateDrawerState();
|
|
@@ -4060,6 +4101,8 @@
|
|
|
4060
4101
|
}
|
|
4061
4102
|
|
|
4062
4103
|
function openSettingsModal() {
|
|
4104
|
+
// Close session modal first if open (mutual exclusion)
|
|
4105
|
+
closeSessionModal();
|
|
4063
4106
|
var modal = document.getElementById("settings-modal");
|
|
4064
4107
|
if (modal) {
|
|
4065
4108
|
modal.classList.remove("hidden");
|
|
@@ -4216,7 +4259,7 @@
|
|
|
4216
4259
|
'<span class="preset-detail">' + escapeHtml(p.command) + (p.mode ? ' (' + escapeHtml(p.mode) + ')' : '') + '</span>' +
|
|
4217
4260
|
'</div>';
|
|
4218
4261
|
}
|
|
4219
|
-
if (!html) html = '<
|
|
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>';
|
|
4220
4263
|
presetsList.innerHTML = html;
|
|
4221
4264
|
}
|
|
4222
4265
|
})
|
|
@@ -4518,7 +4561,10 @@
|
|
|
4518
4561
|
});
|
|
4519
4562
|
}
|
|
4520
4563
|
|
|
4564
|
+
var _sessionCreating = false;
|
|
4565
|
+
|
|
4521
4566
|
function runCommand() {
|
|
4567
|
+
if (_sessionCreating) return;
|
|
4522
4568
|
var cwdEl = document.getElementById("cwd");
|
|
4523
4569
|
var errorEl = document.getElementById("modal-error");
|
|
4524
4570
|
var command = getPreferredTool();
|
|
@@ -4540,6 +4586,7 @@
|
|
|
4540
4586
|
|
|
4541
4587
|
function startStructuredSessionFromModal(cwd, mode, errorEl) {
|
|
4542
4588
|
console.log("[WAND] startStructuredSessionFromModal cwd:", cwd, "mode:", mode);
|
|
4589
|
+
_sessionCreating = true;
|
|
4543
4590
|
state.modeValue = mode;
|
|
4544
4591
|
state.chatMode = mode;
|
|
4545
4592
|
state.sessionTool = "claude";
|
|
@@ -4555,11 +4602,13 @@
|
|
|
4555
4602
|
.then(function() { focusInputBox(true); })
|
|
4556
4603
|
.catch(function(error) {
|
|
4557
4604
|
showError(errorEl, (error && error.message) || "无法启动结构化会话,请确认 Claude 已正确安装。");
|
|
4558
|
-
})
|
|
4605
|
+
})
|
|
4606
|
+
.finally(function() { _sessionCreating = false; });
|
|
4559
4607
|
}
|
|
4560
4608
|
|
|
4561
4609
|
function runPtyCommandFromModal(command, cwd, mode, errorEl) {
|
|
4562
4610
|
console.log("[WAND] runPtyCommandFromModal command:", command, "cwd:", cwd, "mode:", mode);
|
|
4611
|
+
_sessionCreating = true;
|
|
4563
4612
|
state.modeValue = mode;
|
|
4564
4613
|
state.chatMode = mode;
|
|
4565
4614
|
state.sessionTool = command;
|
|
@@ -4602,7 +4651,8 @@
|
|
|
4602
4651
|
})
|
|
4603
4652
|
.catch(function() {
|
|
4604
4653
|
showError(errorEl, "无法启动会话,请确认 Claude 已正确安装。");
|
|
4605
|
-
})
|
|
4654
|
+
})
|
|
4655
|
+
.finally(function() { _sessionCreating = false; });
|
|
4606
4656
|
}
|
|
4607
4657
|
|
|
4608
4658
|
function initBlankChatCwd() {
|
|
@@ -5626,10 +5676,12 @@
|
|
|
5626
5676
|
}
|
|
5627
5677
|
});
|
|
5628
5678
|
// Inline keyboard visibility follows current view
|
|
5629
|
-
var inlineKeyboard = document.
|
|
5679
|
+
var inlineKeyboard = document.querySelector(".inline-shortcuts-wrap");
|
|
5630
5680
|
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", structured || state.currentView !== "terminal");
|
|
5681
|
+
var expandedRow = document.querySelector(".inline-shortcuts-expanded-row");
|
|
5682
|
+
if (expandedRow) expandedRow.classList.toggle("hidden", structured || state.currentView !== "terminal");
|
|
5631
5683
|
var inputHint = document.querySelector(".input-hint");
|
|
5632
|
-
if (inputHint) inputHint.classList.toggle("hidden", structured ?
|
|
5684
|
+
if (inputHint) inputHint.classList.toggle("hidden", structured ? true : state.currentView === "terminal");
|
|
5633
5685
|
var container = document.getElementById("output");
|
|
5634
5686
|
if (container) container.classList.toggle("interactive", !structured && state.terminalInteractive);
|
|
5635
5687
|
}
|
|
@@ -5986,9 +6038,12 @@
|
|
|
5986
6038
|
});
|
|
5987
6039
|
}
|
|
5988
6040
|
|
|
6041
|
+
var _resumeInProgress = false;
|
|
6042
|
+
|
|
5989
6043
|
function resumeSession(sessionId, errorEl) {
|
|
5990
6044
|
console.log("[WAND] resumeSession sessionId:", sessionId);
|
|
5991
|
-
if (!sessionId) return Promise.resolve(null);
|
|
6045
|
+
if (!sessionId || _resumeInProgress) return Promise.resolve(null);
|
|
6046
|
+
_resumeInProgress = true;
|
|
5992
6047
|
return fetch("/api/sessions/" + encodeURIComponent(sessionId) + "/resume", {
|
|
5993
6048
|
method: "POST",
|
|
5994
6049
|
headers: { "Content-Type": "application/json" },
|
|
@@ -6014,7 +6069,8 @@
|
|
|
6014
6069
|
if (errorEl) showError(errorEl, message);
|
|
6015
6070
|
else showToast(message, "error");
|
|
6016
6071
|
return null;
|
|
6017
|
-
})
|
|
6072
|
+
})
|
|
6073
|
+
.finally(function() { _resumeInProgress = false; });
|
|
6018
6074
|
}
|
|
6019
6075
|
|
|
6020
6076
|
function resumeClaudeSessionById(claudeSessionId, errorEl) {
|
|
@@ -8151,12 +8207,16 @@
|
|
|
8151
8207
|
smartScrollToBottom(chatMessages);
|
|
8152
8208
|
});
|
|
8153
8209
|
} else if (msgCount === existingCount && outputHash !== prevHash) {
|
|
8154
|
-
// Same message count but content changed (streaming update).
|
|
8155
|
-
//
|
|
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.
|
|
8156
8214
|
var existingEls = Array.from(chatMessages.querySelectorAll(".chat-message"));
|
|
8157
8215
|
var reversedMessages = messages.slice().reverse();
|
|
8158
8216
|
var replacedAny = false;
|
|
8159
|
-
|
|
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++) {
|
|
8160
8220
|
var currentEl = existingEls[mi];
|
|
8161
8221
|
var tmpWrap = document.createElement("div");
|
|
8162
8222
|
var srOrigIdx = reversedMessages.length - 1 - mi;
|
|
@@ -8167,8 +8227,16 @@
|
|
|
8167
8227
|
chatMessages.replaceChild(replacementEl, currentEl);
|
|
8168
8228
|
attachCopyHandler(replacementEl);
|
|
8169
8229
|
replacedAny = true;
|
|
8230
|
+
} else if (mi > 0) {
|
|
8231
|
+
// Once we hit an unchanged older message, stop scanning
|
|
8232
|
+
break;
|
|
8170
8233
|
}
|
|
8171
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
|
+
}
|
|
8172
8240
|
if (replacedAny) {
|
|
8173
8241
|
requestAnimationFrame(function() {
|
|
8174
8242
|
smartScrollToBottom(chatMessages);
|
|
@@ -8955,6 +9023,98 @@
|
|
|
8955
9023
|
return '<div class="structured-tool-hint">已自动恢复一次 ' + escapeHtml(getToolDisplayName(toolName)) + ' 参数问题</div>';
|
|
8956
9024
|
}
|
|
8957
9025
|
|
|
9026
|
+
// ── 连续同类工具调用分组 ──
|
|
9027
|
+
var GROUPABLE_TOOLS = { Read: 1, Glob: 1, Grep: 1, WebFetch: 1, WebSearch: 1, TodoRead: 1 };
|
|
9028
|
+
|
|
9029
|
+
function groupConsecutiveTools(content) {
|
|
9030
|
+
var groups = [];
|
|
9031
|
+
var i = 0;
|
|
9032
|
+
while (i < content.length) {
|
|
9033
|
+
var block = content[i];
|
|
9034
|
+
if (block.type === "tool_result") { i++; continue; }
|
|
9035
|
+
if (block.type === "tool_use" && GROUPABLE_TOOLS[block.name]) {
|
|
9036
|
+
var run = [{ block: block, index: i }];
|
|
9037
|
+
var j = i + 1;
|
|
9038
|
+
while (j < content.length) {
|
|
9039
|
+
if (content[j].type === "tool_result") { j++; continue; }
|
|
9040
|
+
if (content[j].type === "tool_use" && GROUPABLE_TOOLS[content[j].name]) {
|
|
9041
|
+
run.push({ block: content[j], index: j });
|
|
9042
|
+
j++;
|
|
9043
|
+
} else { break; }
|
|
9044
|
+
}
|
|
9045
|
+
if (run.length >= 2) {
|
|
9046
|
+
groups.push({ type: "group", items: run, endIndex: j });
|
|
9047
|
+
} else {
|
|
9048
|
+
groups.push({ type: "single", block: block, index: i });
|
|
9049
|
+
}
|
|
9050
|
+
i = j;
|
|
9051
|
+
} else {
|
|
9052
|
+
groups.push({ type: "single", block: block, index: i });
|
|
9053
|
+
i++;
|
|
9054
|
+
}
|
|
9055
|
+
}
|
|
9056
|
+
return groups;
|
|
9057
|
+
}
|
|
9058
|
+
|
|
9059
|
+
var TOOL_GROUP_LABELS = { Read: "读取", Glob: "搜索", Grep: "搜索", WebFetch: "抓取", WebSearch: "搜索", TodoRead: "待办" };
|
|
9060
|
+
|
|
9061
|
+
function renderToolGroup(items, role, toolResults) {
|
|
9062
|
+
// Count by tool name
|
|
9063
|
+
var counts = {};
|
|
9064
|
+
for (var k = 0; k < items.length; k++) {
|
|
9065
|
+
var n = items[k].block.name;
|
|
9066
|
+
counts[n] = (counts[n] || 0) + 1;
|
|
9067
|
+
}
|
|
9068
|
+
// Check if all done or still pending
|
|
9069
|
+
var allDone = true;
|
|
9070
|
+
var anyError = false;
|
|
9071
|
+
for (var k = 0; k < items.length; k++) {
|
|
9072
|
+
var b = items[k].block;
|
|
9073
|
+
var tr = pickToolResultForDisplay(toolResults, b.id);
|
|
9074
|
+
if (!tr) { allDone = false; }
|
|
9075
|
+
else if (tr.is_error) { anyError = true; }
|
|
9076
|
+
}
|
|
9077
|
+
var statusIcon = !allDone ? "…" : (anyError ? "✗" : "✓");
|
|
9078
|
+
var statusClass = !allDone ? "pending" : (anyError ? "error" : "done");
|
|
9079
|
+
// Summary text
|
|
9080
|
+
var parts = [];
|
|
9081
|
+
for (var name in counts) {
|
|
9082
|
+
parts.push(counts[name] + " " + (TOOL_GROUP_LABELS[name] || name));
|
|
9083
|
+
}
|
|
9084
|
+
var summaryText = parts.join(" · ");
|
|
9085
|
+
|
|
9086
|
+
// Render each item's inline-tool card
|
|
9087
|
+
var innerHtml = "";
|
|
9088
|
+
for (var k = 0; k < items.length; k++) {
|
|
9089
|
+
try {
|
|
9090
|
+
innerHtml += renderContentBlock(items[k].block, role, toolResults, items[k].index);
|
|
9091
|
+
} catch (e) {
|
|
9092
|
+
innerHtml += '<div class="render-error">工具渲染失败</div>';
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
|
|
9096
|
+
return '<div class="tool-group" data-expanded="false" data-status="' + statusClass + '">' +
|
|
9097
|
+
'<div class="tool-group-summary" onclick="__toolGroupToggle(this.parentNode)">' +
|
|
9098
|
+
'<span class="tool-group-status">' + statusIcon + '</span>' +
|
|
9099
|
+
'<span class="tool-group-text">' + escapeHtml(summaryText) + '</span>' +
|
|
9100
|
+
'<span class="tool-group-count">' + items.length + ' 个调用</span>' +
|
|
9101
|
+
'<svg class="tool-group-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>' +
|
|
9102
|
+
'</div>' +
|
|
9103
|
+
'<div class="tool-group-body">' + innerHtml + '</div>' +
|
|
9104
|
+
'</div>';
|
|
9105
|
+
}
|
|
9106
|
+
|
|
9107
|
+
// global toggle
|
|
9108
|
+
window.__toolGroupToggle = function(el) {
|
|
9109
|
+
if (!el) return;
|
|
9110
|
+
var expanded = el.getAttribute("data-expanded") === "true";
|
|
9111
|
+
el.setAttribute("data-expanded", expanded ? "false" : "true");
|
|
9112
|
+
var body = el.querySelector(".tool-group-body");
|
|
9113
|
+
if (body) body.style.display = expanded ? "none" : "block";
|
|
9114
|
+
var chevron = el.querySelector(".tool-group-chevron");
|
|
9115
|
+
if (chevron) chevron.style.transform = expanded ? "" : "rotate(180deg)";
|
|
9116
|
+
};
|
|
9117
|
+
|
|
8958
9118
|
function renderStructuredMessage(msg, roundUsage) {
|
|
8959
9119
|
var role = msg.role;
|
|
8960
9120
|
var avatar = role === "assistant" ? '<div class="chat-message-avatar">赛博虎妞</div>' : "";
|
|
@@ -8973,10 +9133,15 @@
|
|
|
8973
9133
|
var blocksHtml = "";
|
|
8974
9134
|
|
|
8975
9135
|
try {
|
|
8976
|
-
|
|
8977
|
-
|
|
9136
|
+
var groups = groupConsecutiveTools(msg.content);
|
|
9137
|
+
for (var g = 0; g < groups.length; g++) {
|
|
9138
|
+
var grp = groups[g];
|
|
8978
9139
|
try {
|
|
8979
|
-
|
|
9140
|
+
if (grp.type === "group") {
|
|
9141
|
+
blocksHtml += renderToolGroup(grp.items, role, toolResults);
|
|
9142
|
+
} else {
|
|
9143
|
+
blocksHtml += renderContentBlock(grp.block, role, toolResults, grp.index);
|
|
9144
|
+
}
|
|
8980
9145
|
} catch (e) {
|
|
8981
9146
|
blocksHtml += '<div class="render-error">消息块渲染失败</div>';
|
|
8982
9147
|
}
|
|
@@ -8989,17 +9154,6 @@
|
|
|
8989
9154
|
}
|
|
8990
9155
|
|
|
8991
9156
|
var usageHtml = "";
|
|
8992
|
-
if (role === "assistant" && roundUsage) {
|
|
8993
|
-
var u = roundUsage;
|
|
8994
|
-
var parts = [];
|
|
8995
|
-
if (u.inputTokens > 0) parts.push("输入 " + u.inputTokens);
|
|
8996
|
-
if (u.outputTokens > 0) parts.push("输出 " + u.outputTokens);
|
|
8997
|
-
if (u.cacheReadInputTokens > 0) parts.push("缓存 " + u.cacheReadInputTokens);
|
|
8998
|
-
if (u.totalCostUsd > 0) parts.push("$" + u.totalCostUsd.toFixed(4));
|
|
8999
|
-
if (parts.length > 0) {
|
|
9000
|
-
usageHtml = '<div class="message-usage">' + parts.join(" · ") + '</div>';
|
|
9001
|
-
}
|
|
9002
|
-
}
|
|
9003
9157
|
|
|
9004
9158
|
return '<div class="chat-message ' + role + '">' +
|
|
9005
9159
|
avatar +
|
|
@@ -2876,6 +2876,65 @@
|
|
|
2876
2876
|
border-radius: 2px;
|
|
2877
2877
|
}
|
|
2878
2878
|
|
|
2879
|
+
/* ── Tool Group (连续同类调用折叠) ── */
|
|
2880
|
+
.tool-group {
|
|
2881
|
+
margin: 2px 0;
|
|
2882
|
+
border-radius: 6px;
|
|
2883
|
+
border: 1px solid var(--border-subtle, rgba(127,127,127,0.1));
|
|
2884
|
+
overflow: hidden;
|
|
2885
|
+
}
|
|
2886
|
+
.tool-group-summary {
|
|
2887
|
+
display: flex;
|
|
2888
|
+
align-items: center;
|
|
2889
|
+
gap: 6px;
|
|
2890
|
+
padding: 5px 10px;
|
|
2891
|
+
cursor: pointer;
|
|
2892
|
+
font-size: 0.75rem;
|
|
2893
|
+
color: var(--text-secondary);
|
|
2894
|
+
user-select: none;
|
|
2895
|
+
transition: background var(--transition-fast);
|
|
2896
|
+
}
|
|
2897
|
+
.tool-group-summary:hover {
|
|
2898
|
+
background: var(--bg-hover, rgba(127,127,127,0.06));
|
|
2899
|
+
}
|
|
2900
|
+
.tool-group-status {
|
|
2901
|
+
font-size: 0.6875rem;
|
|
2902
|
+
flex-shrink: 0;
|
|
2903
|
+
width: 14px;
|
|
2904
|
+
text-align: center;
|
|
2905
|
+
}
|
|
2906
|
+
.tool-group[data-status="done"] .tool-group-status { color: var(--success, #22c55e); }
|
|
2907
|
+
.tool-group[data-status="error"] .tool-group-status { color: var(--error, #ef4444); }
|
|
2908
|
+
.tool-group[data-status="pending"] .tool-group-status { color: var(--text-muted); }
|
|
2909
|
+
.tool-group-text {
|
|
2910
|
+
flex: 1;
|
|
2911
|
+
min-width: 0;
|
|
2912
|
+
overflow: hidden;
|
|
2913
|
+
text-overflow: ellipsis;
|
|
2914
|
+
white-space: nowrap;
|
|
2915
|
+
}
|
|
2916
|
+
.tool-group-count {
|
|
2917
|
+
flex-shrink: 0;
|
|
2918
|
+
font-size: 0.625rem;
|
|
2919
|
+
color: var(--text-muted);
|
|
2920
|
+
}
|
|
2921
|
+
.tool-group-chevron {
|
|
2922
|
+
flex-shrink: 0;
|
|
2923
|
+
transition: transform 0.2s ease;
|
|
2924
|
+
color: var(--text-muted);
|
|
2925
|
+
}
|
|
2926
|
+
.tool-group[data-expanded="true"] .tool-group-chevron {
|
|
2927
|
+
transform: rotate(180deg);
|
|
2928
|
+
}
|
|
2929
|
+
.tool-group-body {
|
|
2930
|
+
display: none;
|
|
2931
|
+
padding: 2px 6px 4px;
|
|
2932
|
+
border-top: 1px solid var(--border-subtle, rgba(127,127,127,0.08));
|
|
2933
|
+
}
|
|
2934
|
+
.tool-group[data-expanded="true"] .tool-group-body {
|
|
2935
|
+
display: block;
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2879
2938
|
/* ── Inline Tool Display (Read, Glob, Grep, WebFetch, WebSearch, TodoRead) ── */
|
|
2880
2939
|
.inline-tool {
|
|
2881
2940
|
display: flex;
|
|
@@ -4743,7 +4802,7 @@
|
|
|
4743
4802
|
.modal-backdrop {
|
|
4744
4803
|
position: fixed;
|
|
4745
4804
|
inset: 0;
|
|
4746
|
-
z-index:
|
|
4805
|
+
z-index: 500;
|
|
4747
4806
|
background: rgba(42, 28, 18, 0.52);
|
|
4748
4807
|
backdrop-filter: blur(12px);
|
|
4749
4808
|
-webkit-backdrop-filter: blur(12px);
|
|
@@ -5625,6 +5684,7 @@
|
|
|
5625
5684
|
.modal { max-height: 80vh; }
|
|
5626
5685
|
.modal-header { padding: 8px 10px; min-height: 36px; }
|
|
5627
5686
|
.modal-body { padding: 8px 10px; }
|
|
5687
|
+
.field-row { grid-template-columns: 1fr; gap: 0; }
|
|
5628
5688
|
|
|
5629
5689
|
.btn { min-height: 36px; padding: 8px 12px; }
|
|
5630
5690
|
.btn-sm { min-height: 28px; padding: 4px 8px; }
|
|
@@ -6228,18 +6288,21 @@
|
|
|
6228
6288
|
background: none;
|
|
6229
6289
|
border: none;
|
|
6230
6290
|
border-bottom: 2px solid transparent;
|
|
6291
|
+
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
6231
6292
|
cursor: pointer;
|
|
6232
|
-
transition: color 0.15s, border-color 0.15s;
|
|
6293
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
6233
6294
|
white-space: nowrap;
|
|
6234
6295
|
}
|
|
6235
6296
|
|
|
6236
6297
|
.settings-tab:hover {
|
|
6237
6298
|
color: var(--text-primary);
|
|
6299
|
+
background: rgba(197, 101, 61, 0.06);
|
|
6238
6300
|
}
|
|
6239
6301
|
|
|
6240
6302
|
.settings-tab.active {
|
|
6241
6303
|
color: var(--accent);
|
|
6242
6304
|
border-bottom-color: var(--accent);
|
|
6305
|
+
background: var(--accent-muted);
|
|
6243
6306
|
}
|
|
6244
6307
|
|
|
6245
6308
|
.settings-panel {
|
|
@@ -6253,8 +6316,11 @@
|
|
|
6253
6316
|
.settings-about-info {
|
|
6254
6317
|
display: flex;
|
|
6255
6318
|
flex-direction: column;
|
|
6256
|
-
gap:
|
|
6319
|
+
gap: 0;
|
|
6257
6320
|
margin-bottom: 18px;
|
|
6321
|
+
background: var(--bg-secondary);
|
|
6322
|
+
border-radius: var(--radius-md);
|
|
6323
|
+
padding: 2px 14px;
|
|
6258
6324
|
}
|
|
6259
6325
|
|
|
6260
6326
|
.settings-about-row {
|
|
@@ -6262,6 +6328,12 @@
|
|
|
6262
6328
|
justify-content: space-between;
|
|
6263
6329
|
align-items: center;
|
|
6264
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;
|
|
6265
6337
|
}
|
|
6266
6338
|
|
|
6267
6339
|
.settings-label {
|
|
@@ -6297,8 +6369,8 @@
|
|
|
6297
6369
|
.settings-section-title {
|
|
6298
6370
|
font-size: 0.8125rem;
|
|
6299
6371
|
font-weight: 600;
|
|
6300
|
-
color: var(--
|
|
6301
|
-
margin
|
|
6372
|
+
color: var(--text-primary);
|
|
6373
|
+
margin: 0 0 12px 0;
|
|
6302
6374
|
letter-spacing: 0.02em;
|
|
6303
6375
|
}
|
|
6304
6376
|
|
|
@@ -6308,13 +6380,6 @@
|
|
|
6308
6380
|
margin-top: 10px;
|
|
6309
6381
|
}
|
|
6310
6382
|
|
|
6311
|
-
.settings-section-title {
|
|
6312
|
-
font-size: 0.875rem;
|
|
6313
|
-
font-weight: 600;
|
|
6314
|
-
color: var(--text-primary);
|
|
6315
|
-
margin: 0 0 12px 0;
|
|
6316
|
-
}
|
|
6317
|
-
|
|
6318
6383
|
.settings-divider {
|
|
6319
6384
|
border: none;
|
|
6320
6385
|
border-top: 1px solid var(--border-subtle);
|
|
@@ -6338,6 +6403,49 @@
|
|
|
6338
6403
|
margin-bottom: 0;
|
|
6339
6404
|
}
|
|
6340
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
|
+
|
|
6341
6449
|
.field-checkbox {
|
|
6342
6450
|
width: 18px;
|
|
6343
6451
|
height: 18px;
|
|
@@ -7522,13 +7630,61 @@
|
|
|
7522
7630
|
}
|
|
7523
7631
|
|
|
7524
7632
|
/* ── 结构化会话状态条 ── */
|
|
7633
|
+
/* ── 输入框顶部波浪脉冲(回复中) ── */
|
|
7634
|
+
.input-composer.in-flight {
|
|
7635
|
+
border-color: transparent;
|
|
7636
|
+
}
|
|
7637
|
+
.input-composer.in-flight::before {
|
|
7638
|
+
content: "";
|
|
7639
|
+
position: absolute;
|
|
7640
|
+
top: -2px;
|
|
7641
|
+
left: -60%;
|
|
7642
|
+
width: 220%;
|
|
7643
|
+
height: 3px;
|
|
7644
|
+
border-radius: 14px 14px 0 0;
|
|
7645
|
+
background:
|
|
7646
|
+
radial-gradient(ellipse 280px 4px,
|
|
7647
|
+
rgba(var(--accent-rgb, 197, 101, 61), 0.45) 0%,
|
|
7648
|
+
rgba(var(--accent-rgb, 197, 101, 61), 0.12) 35%,
|
|
7649
|
+
transparent 60%)
|
|
7650
|
+
no-repeat;
|
|
7651
|
+
background-size: 280px 4px;
|
|
7652
|
+
animation: composerWaveSlide 7s cubic-bezier(0.35, 0, 0.65, 1) infinite;
|
|
7653
|
+
z-index: 2;
|
|
7654
|
+
pointer-events: none;
|
|
7655
|
+
}
|
|
7656
|
+
.input-composer.in-flight::after {
|
|
7657
|
+
content: "";
|
|
7658
|
+
position: absolute;
|
|
7659
|
+
top: -2px;
|
|
7660
|
+
left: -60%;
|
|
7661
|
+
width: 220%;
|
|
7662
|
+
height: 3px;
|
|
7663
|
+
border-radius: 14px 14px 0 0;
|
|
7664
|
+
background:
|
|
7665
|
+
radial-gradient(ellipse 220px 3px,
|
|
7666
|
+
rgba(var(--accent-rgb, 197, 101, 61), 0.25) 0%,
|
|
7667
|
+
rgba(var(--accent-rgb, 197, 101, 61), 0.06) 35%,
|
|
7668
|
+
transparent 60%)
|
|
7669
|
+
no-repeat;
|
|
7670
|
+
background-size: 220px 3px;
|
|
7671
|
+
animation: composerWaveSlide 9s cubic-bezier(0.35, 0, 0.65, 1) infinite reverse;
|
|
7672
|
+
z-index: 2;
|
|
7673
|
+
pointer-events: none;
|
|
7674
|
+
}
|
|
7675
|
+
@keyframes composerWaveSlide {
|
|
7676
|
+
0% { background-position: 0% center; }
|
|
7677
|
+
100% { background-position: 100% center; }
|
|
7678
|
+
}
|
|
7679
|
+
|
|
7680
|
+
/* ── 结构化会话状态条(输入框右上角) ── */
|
|
7525
7681
|
.structured-status-bar {
|
|
7526
7682
|
display: flex;
|
|
7527
7683
|
align-items: center;
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7684
|
+
justify-content: flex-end;
|
|
7685
|
+
gap: 5px;
|
|
7686
|
+
margin: 0 4px 2px 0;
|
|
7687
|
+
padding: 0;
|
|
7532
7688
|
background: transparent;
|
|
7533
7689
|
border: none;
|
|
7534
7690
|
font-size: 0.6875rem;
|
|
@@ -7537,35 +7693,25 @@
|
|
|
7537
7693
|
overflow: hidden;
|
|
7538
7694
|
}
|
|
7539
7695
|
|
|
7540
|
-
.structured-status-bar .status-bar-
|
|
7696
|
+
.structured-status-bar .status-bar-dot {
|
|
7697
|
+
width: 5px;
|
|
7698
|
+
height: 5px;
|
|
7699
|
+
border-radius: 50%;
|
|
7700
|
+
background: rgba(var(--accent-rgb, 197, 101, 61), 0.8);
|
|
7701
|
+
animation: statusDotPulse 1.2s ease-in-out infinite;
|
|
7541
7702
|
flex-shrink: 0;
|
|
7542
|
-
font-weight: 500;
|
|
7543
|
-
color: var(--text-muted);
|
|
7544
7703
|
}
|
|
7545
7704
|
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
border-radius: 1px;
|
|
7550
|
-
background: rgba(var(--accent-rgb, 99, 102, 241), 0.08);
|
|
7551
|
-
overflow: hidden;
|
|
7552
|
-
position: relative;
|
|
7553
|
-
}
|
|
7554
|
-
|
|
7555
|
-
.structured-status-bar .status-bar-fill {
|
|
7556
|
-
position: absolute;
|
|
7557
|
-
top: 0;
|
|
7558
|
-
left: 0;
|
|
7559
|
-
width: 30%;
|
|
7560
|
-
height: 100%;
|
|
7561
|
-
border-radius: 1px;
|
|
7562
|
-
background: linear-gradient(90deg, transparent, var(--accent-soft), transparent);
|
|
7563
|
-
animation: marqueeScroll 1.5s ease-in-out infinite;
|
|
7705
|
+
@keyframes statusDotPulse {
|
|
7706
|
+
0%, 100% { opacity: 0.4; transform: scale(0.9); }
|
|
7707
|
+
50% { opacity: 1; transform: scale(1.1); }
|
|
7564
7708
|
}
|
|
7565
7709
|
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7710
|
+
.structured-status-bar .status-bar-label {
|
|
7711
|
+
flex-shrink: 0;
|
|
7712
|
+
font-weight: 500;
|
|
7713
|
+
color: var(--text-muted);
|
|
7714
|
+
font-size: 0.625rem;
|
|
7569
7715
|
}
|
|
7570
7716
|
|
|
7571
7717
|
.structured-status-bar .status-bar-timer {
|
|
@@ -7578,8 +7724,6 @@
|
|
|
7578
7724
|
|
|
7579
7725
|
/* 完成态 */
|
|
7580
7726
|
.structured-status-bar.completed {
|
|
7581
|
-
background: transparent;
|
|
7582
|
-
border-color: transparent;
|
|
7583
7727
|
animation: statusBarFadeOut 2s ease-out 1s forwards;
|
|
7584
7728
|
}
|
|
7585
7729
|
|
|
@@ -7587,22 +7731,10 @@
|
|
|
7587
7731
|
color: var(--success);
|
|
7588
7732
|
}
|
|
7589
7733
|
|
|
7590
|
-
.structured-status-bar.completed .status-bar-track {
|
|
7591
|
-
background: rgba(79, 122, 88, 0.1);
|
|
7592
|
-
}
|
|
7593
|
-
|
|
7594
|
-
.structured-status-bar.completed .status-bar-fill {
|
|
7595
|
-
width: 100%;
|
|
7596
|
-
background: var(--success);
|
|
7597
|
-
opacity: 0.5;
|
|
7598
|
-
animation: none;
|
|
7599
|
-
left: 0;
|
|
7600
|
-
}
|
|
7601
|
-
|
|
7602
7734
|
@keyframes statusBarFadeOut {
|
|
7603
|
-
0% { opacity: 1; max-height:
|
|
7604
|
-
70% { opacity: 0; max-height:
|
|
7605
|
-
100% { opacity: 0; max-height: 0; margin: 0;
|
|
7735
|
+
0% { opacity: 1; max-height: 20px; }
|
|
7736
|
+
70% { opacity: 0; max-height: 20px; }
|
|
7737
|
+
100% { opacity: 0; max-height: 0; margin: 0; }
|
|
7606
7738
|
}
|
|
7607
7739
|
|
|
7608
7740
|
/* 结束标记 */
|
package/dist/ws-broadcast.d.ts
CHANGED
|
@@ -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
|
|
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;
|