@co0ontty/wand 1.5.5 → 1.6.0
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/process-manager.d.ts +2 -7
- package/dist/process-manager.js +20 -1
- package/dist/structured-session-manager.d.ts +1 -2
- package/dist/structured-session-manager.js +22 -2
- package/dist/types.d.ts +9 -0
- package/dist/web-ui/content/scripts.js +175 -105
- package/dist/web-ui/content/styles.css +113 -51
- package/dist/ws-broadcast.d.ts +2 -6
- package/package.json +1 -1
|
@@ -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;
|
package/dist/process-manager.js
CHANGED
|
@@ -397,6 +397,24 @@ function getLatestClaudeTaskId(excludeIds) {
|
|
|
397
397
|
return null;
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
|
+
/** Derive a short summary for a session from user messages or current task. */
|
|
401
|
+
function deriveSessionSummary(messages, currentTaskTitle) {
|
|
402
|
+
// Prefer first user message as summary
|
|
403
|
+
for (const msg of messages) {
|
|
404
|
+
if (msg.role !== "user")
|
|
405
|
+
continue;
|
|
406
|
+
for (const block of msg.content) {
|
|
407
|
+
if (block.type === "text" && block.text.trim()) {
|
|
408
|
+
return block.text.trim().slice(0, 120);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
break; // only check the first user turn
|
|
412
|
+
}
|
|
413
|
+
// Fallback to current task title
|
|
414
|
+
if (currentTaskTitle)
|
|
415
|
+
return currentTaskTitle.slice(0, 120);
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
400
418
|
export class ProcessManager extends EventEmitter {
|
|
401
419
|
config;
|
|
402
420
|
storage;
|
|
@@ -1102,7 +1120,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
1102
1120
|
resumedToSessionId: record.resumedToSessionId ?? undefined,
|
|
1103
1121
|
autoRecovered: record.autoRecovered ?? false,
|
|
1104
1122
|
autoApprovePermissions: record.autoApprovePermissions || undefined,
|
|
1105
|
-
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined
|
|
1123
|
+
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
|
|
1124
|
+
summary: deriveSessionSummary(messages, record.currentTask?.title ?? null),
|
|
1106
1125
|
};
|
|
1107
1126
|
}
|
|
1108
1127
|
isPermissionBlocked(record) {
|
|
@@ -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;
|
|
@@ -4,6 +4,23 @@ const STREAM_EMIT_DEBOUNCE_MS = 16;
|
|
|
4
4
|
function isRunningAsRoot() {
|
|
5
5
|
return process.getuid?.() === 0 || process.geteuid?.() === 0;
|
|
6
6
|
}
|
|
7
|
+
/** Enrich a snapshot with a derived summary from the first user message. */
|
|
8
|
+
function withSummary(snapshot) {
|
|
9
|
+
if (snapshot.summary)
|
|
10
|
+
return snapshot;
|
|
11
|
+
const messages = snapshot.messages ?? [];
|
|
12
|
+
for (const msg of messages) {
|
|
13
|
+
if (msg.role !== "user")
|
|
14
|
+
continue;
|
|
15
|
+
for (const block of msg.content) {
|
|
16
|
+
if (block.type === "text" && block.text.trim()) {
|
|
17
|
+
return { ...snapshot, summary: block.text.trim().slice(0, 120) };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
return snapshot;
|
|
23
|
+
}
|
|
7
24
|
/** Should we auto-approve permissions for this mode? */
|
|
8
25
|
function shouldAutoApproveForMode(mode) {
|
|
9
26
|
return mode === "full-access" || mode === "managed" || mode === "auto-edit";
|
|
@@ -46,10 +63,13 @@ export class StructuredSessionManager {
|
|
|
46
63
|
this.emitEvent = emitEvent;
|
|
47
64
|
}
|
|
48
65
|
list() {
|
|
49
|
-
return Array.from(this.sessions.values())
|
|
66
|
+
return Array.from(this.sessions.values())
|
|
67
|
+
.map(withSummary)
|
|
68
|
+
.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
50
69
|
}
|
|
51
70
|
get(id) {
|
|
52
|
-
|
|
71
|
+
const s = this.sessions.get(id);
|
|
72
|
+
return s ? withSummary(s) : null;
|
|
53
73
|
}
|
|
54
74
|
createSession(options) {
|
|
55
75
|
const id = randomUUID();
|
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;
|
|
@@ -182,6 +189,8 @@ export interface SessionSnapshot {
|
|
|
182
189
|
file: number;
|
|
183
190
|
total: number;
|
|
184
191
|
};
|
|
192
|
+
/** 会话摘要:从首条用户消息或当前任务提取 */
|
|
193
|
+
summary?: string;
|
|
185
194
|
}
|
|
186
195
|
export type SessionLifecycleState = "initializing" | "running" | "idle" | "thinking" | "waiting-input" | "archived";
|
|
187
196
|
export interface SessionLifecycle {
|
|
@@ -678,7 +678,6 @@
|
|
|
678
678
|
'</div>' +
|
|
679
679
|
'<div class="todo-progress-body hidden" id="todo-progress-body">' +
|
|
680
680
|
'<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
|
|
681
|
-
'<div id="recent-actions" class="recent-actions"></div>' +
|
|
682
681
|
'</div>' +
|
|
683
682
|
'</div>' +
|
|
684
683
|
'<div class="input-composer">' +
|
|
@@ -804,31 +803,50 @@
|
|
|
804
803
|
|
|
805
804
|
// General config tab
|
|
806
805
|
'<div class="settings-panel" id="settings-tab-general">' +
|
|
807
|
-
'<div class="field">' +
|
|
808
|
-
'<
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
'<
|
|
813
|
-
|
|
806
|
+
'<div class="field-row">' +
|
|
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" />' +
|
|
814
|
+
'</div>' +
|
|
814
815
|
'</div>' +
|
|
815
816
|
'<div class="field field-inline">' +
|
|
816
|
-
'<label class="field-label" for="cfg-https">启用 HTTPS</label>' +
|
|
817
817
|
'<input id="cfg-https" type="checkbox" class="field-checkbox" />' +
|
|
818
|
+
'<label class="field-label" for="cfg-https">启用 HTTPS</label>' +
|
|
818
819
|
'</div>' +
|
|
819
|
-
'<div class="field">' +
|
|
820
|
-
'<
|
|
821
|
-
|
|
822
|
-
'<
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
820
|
+
'<div class="field-row">' +
|
|
821
|
+
'<div class="field">' +
|
|
822
|
+
'<label class="field-label" for="cfg-mode">默认执行模式</label>' +
|
|
823
|
+
'<select id="cfg-mode" class="field-input">' +
|
|
824
|
+
'<option value="default">default</option>' +
|
|
825
|
+
'<option value="assist">assist</option>' +
|
|
826
|
+
'<option value="agent">agent</option>' +
|
|
827
|
+
'<option value="agent-max">agent-max</option>' +
|
|
828
|
+
'<option value="auto-edit">auto-edit</option>' +
|
|
829
|
+
'<option value="full-access">full-access</option>' +
|
|
830
|
+
'<option value="native">native</option>' +
|
|
831
|
+
'<option value="managed">managed</option>' +
|
|
832
|
+
'</select>' +
|
|
833
|
+
'</div>' +
|
|
834
|
+
'<div class="field">' +
|
|
835
|
+
'<label class="field-label" for="cfg-language">回复语言</label>' +
|
|
836
|
+
'<select id="cfg-language" class="field-input">' +
|
|
837
|
+
'<option value="">自动(不指定)</option>' +
|
|
838
|
+
'<option value="中文">中文</option>' +
|
|
839
|
+
'<option value="English">English</option>' +
|
|
840
|
+
'<option value="日本語">日本語</option>' +
|
|
841
|
+
'<option value="한국어">한국어</option>' +
|
|
842
|
+
'<option value="Español">Español</option>' +
|
|
843
|
+
'<option value="Français">Français</option>' +
|
|
844
|
+
'<option value="Deutsch">Deutsch</option>' +
|
|
845
|
+
'<option value="Русский">Русский</option>' +
|
|
846
|
+
'</select>' +
|
|
847
|
+
'</div>' +
|
|
831
848
|
'</div>' +
|
|
849
|
+
'<p class="field-hint" style="margin-top:-4px;">设置回复语言后,Claude 将尽量使用指定语言回复。</p>' +
|
|
832
850
|
'<div class="field">' +
|
|
833
851
|
'<label class="field-label" for="cfg-cwd">默认工作目录</label>' +
|
|
834
852
|
'<input id="cfg-cwd" type="text" class="field-input" placeholder="/home/user" />' +
|
|
@@ -837,52 +855,40 @@
|
|
|
837
855
|
'<label class="field-label" for="cfg-shell">Shell</label>' +
|
|
838
856
|
'<input id="cfg-shell" type="text" class="field-input" placeholder="/bin/bash" />' +
|
|
839
857
|
'</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
858
|
'<button id="save-config-button" class="btn btn-primary btn-block">保存配置</button>' +
|
|
856
859
|
'<p id="config-message" class="hint hidden"></p>' +
|
|
857
860
|
'</div>' +
|
|
858
861
|
|
|
859
862
|
// Security tab
|
|
860
863
|
'<div class="settings-panel" id="settings-tab-security">' +
|
|
861
|
-
'<
|
|
862
|
-
|
|
863
|
-
'<
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
'<
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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" />' +
|
|
864
|
+
'<div class="settings-card">' +
|
|
865
|
+
'<h3 class="settings-section-title">\ud83d\udd12 修改密码</h3>' +
|
|
866
|
+
'<div class="field">' +
|
|
867
|
+
'<label class="field-label" for="new-password">新密码</label>' +
|
|
868
|
+
'<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
|
|
869
|
+
'</div>' +
|
|
870
|
+
'<div class="field">' +
|
|
871
|
+
'<label class="field-label" for="confirm-password">确认密码</label>' +
|
|
872
|
+
'<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
|
|
873
|
+
'</div>' +
|
|
874
|
+
'<button id="save-password-button" class="btn btn-primary btn-block">保存密码</button>' +
|
|
875
|
+
'<p id="settings-error" class="error-message hidden"></p>' +
|
|
876
|
+
'<p id="settings-success" class="hint hidden" style="color: var(--success);"></p>' +
|
|
879
877
|
'</div>' +
|
|
880
|
-
'<div class="
|
|
881
|
-
'<
|
|
882
|
-
'<
|
|
878
|
+
'<div class="settings-card">' +
|
|
879
|
+
'<h3 class="settings-section-title">\ud83d\udd10 SSL 证书</h3>' +
|
|
880
|
+
'<p class="settings-hint" id="cert-status">加载中...</p>' +
|
|
881
|
+
'<div class="field">' +
|
|
882
|
+
'<label class="field-label" for="cert-key-file">私钥文件 (.key)</label>' +
|
|
883
|
+
'<input id="cert-key-file" type="file" class="field-input field-file" accept=".key,.pem" />' +
|
|
884
|
+
'</div>' +
|
|
885
|
+
'<div class="field">' +
|
|
886
|
+
'<label class="field-label" for="cert-cert-file">证书文件 (.crt/.pem)</label>' +
|
|
887
|
+
'<input id="cert-cert-file" type="file" class="field-input field-file" accept=".crt,.pem,.cert" />' +
|
|
888
|
+
'</div>' +
|
|
889
|
+
'<button id="upload-cert-button" class="btn btn-primary btn-block">上传证书</button>' +
|
|
890
|
+
'<p id="cert-message" class="hint hidden"></p>' +
|
|
883
891
|
'</div>' +
|
|
884
|
-
'<button id="upload-cert-button" class="btn btn-primary btn-block">上传证书</button>' +
|
|
885
|
-
'<p id="cert-message" class="hint hidden"></p>' +
|
|
886
892
|
'</div>' +
|
|
887
893
|
|
|
888
894
|
// Command presets tab
|
|
@@ -1809,7 +1815,9 @@
|
|
|
1809
1815
|
'<div class="session-item-row">' +
|
|
1810
1816
|
checkbox +
|
|
1811
1817
|
'<div class="session-main">' +
|
|
1812
|
-
|
|
1818
|
+
(session.summary
|
|
1819
|
+
? '<div class="session-title">' + escapeHtml(session.summary) + '</div>'
|
|
1820
|
+
: '<div class="session-command">' + escapeHtml(session.resumedFromSessionId ? session.command.replace(/\s+--resume\s+\S+/, '') : session.command) + '</div>') +
|
|
1813
1821
|
'<div class="session-meta">' +
|
|
1814
1822
|
modeBadge +
|
|
1815
1823
|
'<span>' + escapeHtml(modeName) + '</span>' +
|
|
@@ -3943,15 +3951,23 @@
|
|
|
3943
3951
|
function selectSession(id) {
|
|
3944
3952
|
var foundSession = state.sessions.find(function(item) { return item.id === id; });
|
|
3945
3953
|
console.log("[WAND] selectSession id:", id, "found:", !!foundSession, "sessionKind:", foundSession && foundSession.sessionKind, "runner:", foundSession && foundSession.runner, "isStructured:", isStructuredSession(foundSession));
|
|
3954
|
+
if (!foundSession) {
|
|
3955
|
+
console.warn("[WAND] selectSession: session not found, skipping", id);
|
|
3956
|
+
return;
|
|
3957
|
+
}
|
|
3946
3958
|
state.selectedId = id;
|
|
3947
3959
|
persistSelectedId();
|
|
3960
|
+
// Clear queued inputs from the previous session to prevent cross-session leaks
|
|
3961
|
+
state.messageQueue = [];
|
|
3962
|
+
state.pendingMessages = [];
|
|
3963
|
+
updateQueueCounter();
|
|
3948
3964
|
resetChatRenderCache();
|
|
3949
3965
|
state.currentMessages = [];
|
|
3950
3966
|
if (chatRenderTimer) { clearTimeout(chatRenderTimer); chatRenderTimer = null; }
|
|
3951
3967
|
// Reset todo progress bar
|
|
3952
3968
|
var todoEl = document.getElementById("todo-progress");
|
|
3953
3969
|
if (todoEl) todoEl.classList.add("hidden");
|
|
3954
|
-
var session =
|
|
3970
|
+
var session = foundSession;
|
|
3955
3971
|
state.preferredCommand = getPreferredTool();
|
|
3956
3972
|
state.chatMode = getSafeModeForTool("claude", session && session.mode ? session.mode : state.chatMode);
|
|
3957
3973
|
if (state.terminalInteractive && session && session.status !== "running") {
|
|
@@ -4010,6 +4026,8 @@
|
|
|
4010
4026
|
var focusTrapHandler = null;
|
|
4011
4027
|
|
|
4012
4028
|
function openSessionModal() {
|
|
4029
|
+
// Close settings modal first if open (mutual exclusion)
|
|
4030
|
+
closeSettingsModal();
|
|
4013
4031
|
state.modalOpen = true;
|
|
4014
4032
|
state.sessionsDrawerOpen = false;
|
|
4015
4033
|
updateDrawerState();
|
|
@@ -4084,6 +4102,8 @@
|
|
|
4084
4102
|
}
|
|
4085
4103
|
|
|
4086
4104
|
function openSettingsModal() {
|
|
4105
|
+
// Close session modal first if open (mutual exclusion)
|
|
4106
|
+
closeSessionModal();
|
|
4087
4107
|
var modal = document.getElementById("settings-modal");
|
|
4088
4108
|
if (modal) {
|
|
4089
4109
|
modal.classList.remove("hidden");
|
|
@@ -4240,7 +4260,7 @@
|
|
|
4240
4260
|
'<span class="preset-detail">' + escapeHtml(p.command) + (p.mode ? ' (' + escapeHtml(p.mode) + ')' : '') + '</span>' +
|
|
4241
4261
|
'</div>';
|
|
4242
4262
|
}
|
|
4243
|
-
if (!html) html = '<
|
|
4263
|
+
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
4264
|
presetsList.innerHTML = html;
|
|
4245
4265
|
}
|
|
4246
4266
|
})
|
|
@@ -4542,7 +4562,10 @@
|
|
|
4542
4562
|
});
|
|
4543
4563
|
}
|
|
4544
4564
|
|
|
4565
|
+
var _sessionCreating = false;
|
|
4566
|
+
|
|
4545
4567
|
function runCommand() {
|
|
4568
|
+
if (_sessionCreating) return;
|
|
4546
4569
|
var cwdEl = document.getElementById("cwd");
|
|
4547
4570
|
var errorEl = document.getElementById("modal-error");
|
|
4548
4571
|
var command = getPreferredTool();
|
|
@@ -4564,6 +4587,7 @@
|
|
|
4564
4587
|
|
|
4565
4588
|
function startStructuredSessionFromModal(cwd, mode, errorEl) {
|
|
4566
4589
|
console.log("[WAND] startStructuredSessionFromModal cwd:", cwd, "mode:", mode);
|
|
4590
|
+
_sessionCreating = true;
|
|
4567
4591
|
state.modeValue = mode;
|
|
4568
4592
|
state.chatMode = mode;
|
|
4569
4593
|
state.sessionTool = "claude";
|
|
@@ -4579,11 +4603,13 @@
|
|
|
4579
4603
|
.then(function() { focusInputBox(true); })
|
|
4580
4604
|
.catch(function(error) {
|
|
4581
4605
|
showError(errorEl, (error && error.message) || "无法启动结构化会话,请确认 Claude 已正确安装。");
|
|
4582
|
-
})
|
|
4606
|
+
})
|
|
4607
|
+
.finally(function() { _sessionCreating = false; });
|
|
4583
4608
|
}
|
|
4584
4609
|
|
|
4585
4610
|
function runPtyCommandFromModal(command, cwd, mode, errorEl) {
|
|
4586
4611
|
console.log("[WAND] runPtyCommandFromModal command:", command, "cwd:", cwd, "mode:", mode);
|
|
4612
|
+
_sessionCreating = true;
|
|
4587
4613
|
state.modeValue = mode;
|
|
4588
4614
|
state.chatMode = mode;
|
|
4589
4615
|
state.sessionTool = command;
|
|
@@ -4626,7 +4652,8 @@
|
|
|
4626
4652
|
})
|
|
4627
4653
|
.catch(function() {
|
|
4628
4654
|
showError(errorEl, "无法启动会话,请确认 Claude 已正确安装。");
|
|
4629
|
-
})
|
|
4655
|
+
})
|
|
4656
|
+
.finally(function() { _sessionCreating = false; });
|
|
4630
4657
|
}
|
|
4631
4658
|
|
|
4632
4659
|
function initBlankChatCwd() {
|
|
@@ -6012,9 +6039,12 @@
|
|
|
6012
6039
|
});
|
|
6013
6040
|
}
|
|
6014
6041
|
|
|
6042
|
+
var _resumeInProgress = false;
|
|
6043
|
+
|
|
6015
6044
|
function resumeSession(sessionId, errorEl) {
|
|
6016
6045
|
console.log("[WAND] resumeSession sessionId:", sessionId);
|
|
6017
|
-
if (!sessionId) return Promise.resolve(null);
|
|
6046
|
+
if (!sessionId || _resumeInProgress) return Promise.resolve(null);
|
|
6047
|
+
_resumeInProgress = true;
|
|
6018
6048
|
return fetch("/api/sessions/" + encodeURIComponent(sessionId) + "/resume", {
|
|
6019
6049
|
method: "POST",
|
|
6020
6050
|
headers: { "Content-Type": "application/json" },
|
|
@@ -6040,7 +6070,8 @@
|
|
|
6040
6070
|
if (errorEl) showError(errorEl, message);
|
|
6041
6071
|
else showToast(message, "error");
|
|
6042
6072
|
return null;
|
|
6043
|
-
})
|
|
6073
|
+
})
|
|
6074
|
+
.finally(function() { _resumeInProgress = false; });
|
|
6044
6075
|
}
|
|
6045
6076
|
|
|
6046
6077
|
function resumeClaudeSessionById(claudeSessionId, errorEl) {
|
|
@@ -8177,12 +8208,16 @@
|
|
|
8177
8208
|
smartScrollToBottom(chatMessages);
|
|
8178
8209
|
});
|
|
8179
8210
|
} else if (msgCount === existingCount && outputHash !== prevHash) {
|
|
8180
|
-
// Same message count but content changed (streaming update).
|
|
8181
|
-
//
|
|
8211
|
+
// Same message count but content changed (streaming update).
|
|
8212
|
+
// Optimization: only re-render the newest N messages (column-reverse: first children)
|
|
8213
|
+
// that actually differ, starting from the top (newest). Most streaming updates only
|
|
8214
|
+
// touch the latest assistant turn, so we can skip scanning all older messages.
|
|
8182
8215
|
var existingEls = Array.from(chatMessages.querySelectorAll(".chat-message"));
|
|
8183
8216
|
var reversedMessages = messages.slice().reverse();
|
|
8184
8217
|
var replacedAny = false;
|
|
8185
|
-
|
|
8218
|
+
// Scan from newest (index 0 in reversed) up to MAX_STREAMING_SCAN messages
|
|
8219
|
+
var MAX_STREAMING_SCAN = Math.min(4, reversedMessages.length, existingEls.length);
|
|
8220
|
+
for (var mi = 0; mi < MAX_STREAMING_SCAN; mi++) {
|
|
8186
8221
|
var currentEl = existingEls[mi];
|
|
8187
8222
|
var tmpWrap = document.createElement("div");
|
|
8188
8223
|
var srOrigIdx = reversedMessages.length - 1 - mi;
|
|
@@ -8193,8 +8228,16 @@
|
|
|
8193
8228
|
chatMessages.replaceChild(replacementEl, currentEl);
|
|
8194
8229
|
attachCopyHandler(replacementEl);
|
|
8195
8230
|
replacedAny = true;
|
|
8231
|
+
} else if (mi > 0) {
|
|
8232
|
+
// Once we hit an unchanged older message, stop scanning
|
|
8233
|
+
break;
|
|
8196
8234
|
}
|
|
8197
8235
|
}
|
|
8236
|
+
// Fallback: if hash changed but no visible diff found in the top N messages,
|
|
8237
|
+
// the change is deeper — trigger a full render to avoid stale display.
|
|
8238
|
+
if (!replacedAny && reversedMessages.length > MAX_STREAMING_SCAN) {
|
|
8239
|
+
fullRenderChat();
|
|
8240
|
+
}
|
|
8198
8241
|
if (replacedAny) {
|
|
8199
8242
|
requestAnimationFrame(function() {
|
|
8200
8243
|
smartScrollToBottom(chatMessages);
|
|
@@ -8333,40 +8376,6 @@
|
|
|
8333
8376
|
list.innerHTML = html;
|
|
8334
8377
|
}
|
|
8335
8378
|
|
|
8336
|
-
// Extract recent important actions for key points summary
|
|
8337
|
-
var recentActions = [];
|
|
8338
|
-
var actionTools = ["Write", "Edit", "Bash", "WebFetch", "WebSearch"];
|
|
8339
|
-
var msgCount = messages.length;
|
|
8340
|
-
for (var ai = 0; ai < msgCount && recentActions.length < 5; ai++) {
|
|
8341
|
-
var m = messages[ai];
|
|
8342
|
-
if (!m.content || !Array.isArray(m.content)) continue;
|
|
8343
|
-
for (var bi = 0; bi < m.content.length && recentActions.length < 5; bi++) {
|
|
8344
|
-
var blk = m.content[bi];
|
|
8345
|
-
if (blk.type !== "tool_use") continue;
|
|
8346
|
-
var toolName = blk.name || "";
|
|
8347
|
-
if (actionTools.indexOf(toolName) === -1) continue;
|
|
8348
|
-
var desc = blk.description || generateInputSummary(toolName, blk.input) || toolName;
|
|
8349
|
-
if (desc && desc.length > 50) desc = desc.slice(0, 47) + "...";
|
|
8350
|
-
var icon = getToolIcon(toolName);
|
|
8351
|
-
recentActions.push({ icon: icon, text: desc });
|
|
8352
|
-
}
|
|
8353
|
-
}
|
|
8354
|
-
|
|
8355
|
-
var actionsEl = document.getElementById("recent-actions");
|
|
8356
|
-
if (actionsEl) {
|
|
8357
|
-
if (recentActions.length > 0) {
|
|
8358
|
-
var actionsHtml = '<div class="recent-actions-label">最近操作</div>';
|
|
8359
|
-
actionsHtml += '<div class="recent-actions-list">';
|
|
8360
|
-
for (var ri = 0; ri < recentActions.length; ri++) {
|
|
8361
|
-
var a = recentActions[ri];
|
|
8362
|
-
actionsHtml += '<span class="recent-action-pill">' + a.icon + ' ' + escapeHtml(a.text) + '</span>';
|
|
8363
|
-
}
|
|
8364
|
-
actionsHtml += '</div>';
|
|
8365
|
-
actionsEl.innerHTML = actionsHtml;
|
|
8366
|
-
} else {
|
|
8367
|
-
actionsEl.innerHTML = '';
|
|
8368
|
-
}
|
|
8369
|
-
}
|
|
8370
8379
|
}
|
|
8371
8380
|
|
|
8372
8381
|
function updateQueueCounter() {
|
|
@@ -8902,6 +8911,67 @@
|
|
|
8902
8911
|
return messages;
|
|
8903
8912
|
}
|
|
8904
8913
|
|
|
8914
|
+
// ── 像素风猫咪头像 ──
|
|
8915
|
+
var PIXEL_AVATAR = (function() {
|
|
8916
|
+
var _ = "transparent";
|
|
8917
|
+
function buildSvg(grid, size) {
|
|
8918
|
+
var s = size || 3;
|
|
8919
|
+
var w = grid[0].length * s;
|
|
8920
|
+
var h = grid.length * s;
|
|
8921
|
+
var rects = "";
|
|
8922
|
+
for (var y = 0; y < grid.length; y++) {
|
|
8923
|
+
for (var x = 0; x < grid[y].length; x++) {
|
|
8924
|
+
if (grid[y][x] !== _) {
|
|
8925
|
+
rects += '<rect x="' + (x * s) + '" y="' + (y * s) + '" width="' + s + '" height="' + s + '" fill="' + grid[y][x] + '"/>';
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
}
|
|
8929
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + w + ' ' + h + '" class="pixel-avatar-svg">' + rects + '</svg>';
|
|
8930
|
+
}
|
|
8931
|
+
// 加菲猫 (勤劳初二 / AI) — 橙色系
|
|
8932
|
+
var o = "#F0923A", d = "#C46A1A", w = "#FFFFFF", k = "#2D2D2D", p = "#F28B9A", n = "#E87D5A";
|
|
8933
|
+
var garfield = [
|
|
8934
|
+
[_,d,_,_,_,_,_,_,d,_],
|
|
8935
|
+
[d,o,d,_,_,_,_,d,o,d],
|
|
8936
|
+
[d,o,o,o,o,o,o,o,o,d],
|
|
8937
|
+
[o,o,w,k,o,o,w,k,o,o],
|
|
8938
|
+
[o,o,w,w,o,o,w,w,o,o],
|
|
8939
|
+
[o,o,o,o,p,p,o,o,o,o],
|
|
8940
|
+
[o,d,o,n,o,o,n,o,d,o],
|
|
8941
|
+
[_,o,o,o,o,o,o,o,o,_],
|
|
8942
|
+
[_,_,o,d,o,o,d,o,_,_],
|
|
8943
|
+
[_,_,_,o,_,_,o,_,_,_],
|
|
8944
|
+
];
|
|
8945
|
+
// 美短 (赛博虎妞 / 用户) — 灰色系
|
|
8946
|
+
var g = "#9EAAB8", dg = "#6B7B8D", lg = "#C5CED8", gn = "#7EC88B";
|
|
8947
|
+
var shorthair = [
|
|
8948
|
+
[_,dg,_,_,_,_,_,_,dg,_],
|
|
8949
|
+
[dg,g,dg,_,_,_,_,dg,g,dg],
|
|
8950
|
+
[dg,g,g,g,g,g,g,g,g,dg],
|
|
8951
|
+
[g,g,w,gn,g,g,w,gn,g,g],
|
|
8952
|
+
[g,g,w,w,g,g,w,w,g,g],
|
|
8953
|
+
[g,g,g,g,p,p,g,g,g,g],
|
|
8954
|
+
[g,dg,g,lg,g,g,lg,g,dg,g],
|
|
8955
|
+
[_,g,g,g,g,g,g,g,g,_],
|
|
8956
|
+
[_,_,g,dg,g,g,dg,g,_,_],
|
|
8957
|
+
[_,_,_,g,_,_,g,_,_,_],
|
|
8958
|
+
];
|
|
8959
|
+
return {
|
|
8960
|
+
assistant: buildSvg(garfield),
|
|
8961
|
+
user: buildSvg(shorthair)
|
|
8962
|
+
};
|
|
8963
|
+
})();
|
|
8964
|
+
|
|
8965
|
+
function chatAvatar(role) {
|
|
8966
|
+
var isUser = role === "user";
|
|
8967
|
+
var svg = isUser ? PIXEL_AVATAR.user : PIXEL_AVATAR.assistant;
|
|
8968
|
+
var name = isUser ? "赛博虎妞" : "勤劳初二";
|
|
8969
|
+
return '<div class="chat-message-avatar ' + role + '">' +
|
|
8970
|
+
'<div class="pixel-avatar">' + svg + '</div>' +
|
|
8971
|
+
'<span class="avatar-name">' + name + '</span>' +
|
|
8972
|
+
'</div>';
|
|
8973
|
+
}
|
|
8974
|
+
|
|
8905
8975
|
function renderChatMessage(msg, roundUsage) {
|
|
8906
8976
|
// Thinking card (deep thought) — from PTY parsing
|
|
8907
8977
|
if (msg.role === "thinking") {
|
|
@@ -8930,7 +9000,7 @@
|
|
|
8930
9000
|
}
|
|
8931
9001
|
|
|
8932
9002
|
// Legacy string content (from PTY parsing)
|
|
8933
|
-
var avatar = msg.role
|
|
9003
|
+
var avatar = chatAvatar(msg.role);
|
|
8934
9004
|
var bubbleContent = msg.role === "assistant" ? renderMarkdown(msg.content) : escapeHtml(msg.content);
|
|
8935
9005
|
return '<div class="chat-message ' + msg.role + '">' +
|
|
8936
9006
|
avatar +
|
|
@@ -9075,7 +9145,7 @@
|
|
|
9075
9145
|
|
|
9076
9146
|
function renderStructuredMessage(msg, roundUsage) {
|
|
9077
9147
|
var role = msg.role;
|
|
9078
|
-
var avatar = role
|
|
9148
|
+
var avatar = chatAvatar(role);
|
|
9079
9149
|
|
|
9080
9150
|
if (!msg.content || msg.content.length === 0) {
|
|
9081
9151
|
if (role === "assistant") {
|
|
@@ -1162,6 +1162,17 @@
|
|
|
1162
1162
|
letter-spacing: -0.01em;
|
|
1163
1163
|
}
|
|
1164
1164
|
|
|
1165
|
+
.session-title {
|
|
1166
|
+
font-weight: 600;
|
|
1167
|
+
font-size: 0.8125rem;
|
|
1168
|
+
white-space: nowrap;
|
|
1169
|
+
overflow: hidden;
|
|
1170
|
+
text-overflow: ellipsis;
|
|
1171
|
+
color: var(--text-primary);
|
|
1172
|
+
letter-spacing: -0.01em;
|
|
1173
|
+
line-height: 1.3;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1165
1176
|
/* ===== 会话元信息 ===== */
|
|
1166
1177
|
.session-meta {
|
|
1167
1178
|
display: flex;
|
|
@@ -2292,16 +2303,51 @@
|
|
|
2292
2303
|
}
|
|
2293
2304
|
|
|
2294
2305
|
/* ===== 消息头像 ===== */
|
|
2295
|
-
.chat-message
|
|
2296
|
-
display:
|
|
2306
|
+
.chat-message-avatar {
|
|
2307
|
+
display: flex;
|
|
2308
|
+
align-items: center;
|
|
2309
|
+
gap: 6px;
|
|
2310
|
+
padding: 0 2px 4px 2px;
|
|
2297
2311
|
}
|
|
2298
2312
|
|
|
2299
|
-
.chat-message.assistant
|
|
2300
|
-
|
|
2313
|
+
.chat-message-avatar.assistant {
|
|
2314
|
+
flex-direction: row;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
.chat-message-avatar.user {
|
|
2318
|
+
flex-direction: row-reverse;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
.pixel-avatar {
|
|
2322
|
+
width: 24px;
|
|
2323
|
+
height: 24px;
|
|
2324
|
+
flex-shrink: 0;
|
|
2325
|
+
border-radius: 6px;
|
|
2326
|
+
overflow: hidden;
|
|
2327
|
+
background: var(--bg-tertiary);
|
|
2328
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
.pixel-avatar-svg {
|
|
2332
|
+
display: block;
|
|
2333
|
+
width: 100%;
|
|
2334
|
+
height: 100%;
|
|
2335
|
+
image-rendering: pixelated;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
.avatar-name {
|
|
2339
|
+
font-size: 0.7rem;
|
|
2301
2340
|
font-weight: 600;
|
|
2302
|
-
color: var(--accent);
|
|
2303
|
-
padding: 0 2px 4px 2px;
|
|
2304
2341
|
letter-spacing: 0.03em;
|
|
2342
|
+
line-height: 1;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
.chat-message.assistant .avatar-name {
|
|
2346
|
+
color: var(--accent);
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
.chat-message.user .avatar-name {
|
|
2350
|
+
color: var(--text-tertiary);
|
|
2305
2351
|
}
|
|
2306
2352
|
|
|
2307
2353
|
/* ===== 消息气泡 ===== */
|
|
@@ -3904,39 +3950,6 @@
|
|
|
3904
3950
|
.todo-item-icon.active { color: var(--accent); }
|
|
3905
3951
|
.todo-item-icon.done { color: #4a7a4f; }
|
|
3906
3952
|
|
|
3907
|
-
/* Recent actions key points section */
|
|
3908
|
-
.recent-actions {
|
|
3909
|
-
margin-top: 8px;
|
|
3910
|
-
padding-top: 8px;
|
|
3911
|
-
border-top: 1px solid var(--border-subtle);
|
|
3912
|
-
}
|
|
3913
|
-
.recent-actions-label {
|
|
3914
|
-
font-size: 0.6875rem;
|
|
3915
|
-
font-weight: 600;
|
|
3916
|
-
color: var(--text-muted);
|
|
3917
|
-
text-transform: uppercase;
|
|
3918
|
-
letter-spacing: 0.03em;
|
|
3919
|
-
margin-bottom: 6px;
|
|
3920
|
-
}
|
|
3921
|
-
.recent-actions-list {
|
|
3922
|
-
display: flex;
|
|
3923
|
-
flex-wrap: wrap;
|
|
3924
|
-
gap: 4px;
|
|
3925
|
-
}
|
|
3926
|
-
.recent-action-pill {
|
|
3927
|
-
display: inline-flex;
|
|
3928
|
-
align-items: center;
|
|
3929
|
-
gap: 3px;
|
|
3930
|
-
padding: 3px 8px;
|
|
3931
|
-
font-size: 0.6875rem;
|
|
3932
|
-
color: var(--text-secondary);
|
|
3933
|
-
background: rgba(79, 122, 88, 0.08);
|
|
3934
|
-
border-radius: 10px;
|
|
3935
|
-
white-space: nowrap;
|
|
3936
|
-
max-width: 220px;
|
|
3937
|
-
overflow: hidden;
|
|
3938
|
-
text-overflow: ellipsis;
|
|
3939
|
-
}
|
|
3940
3953
|
|
|
3941
3954
|
@keyframes spin {
|
|
3942
3955
|
to { transform: rotate(360deg); }
|
|
@@ -4802,7 +4815,7 @@
|
|
|
4802
4815
|
.modal-backdrop {
|
|
4803
4816
|
position: fixed;
|
|
4804
4817
|
inset: 0;
|
|
4805
|
-
z-index:
|
|
4818
|
+
z-index: 500;
|
|
4806
4819
|
background: rgba(42, 28, 18, 0.52);
|
|
4807
4820
|
backdrop-filter: blur(12px);
|
|
4808
4821
|
-webkit-backdrop-filter: blur(12px);
|
|
@@ -5684,6 +5697,7 @@
|
|
|
5684
5697
|
.modal { max-height: 80vh; }
|
|
5685
5698
|
.modal-header { padding: 8px 10px; min-height: 36px; }
|
|
5686
5699
|
.modal-body { padding: 8px 10px; }
|
|
5700
|
+
.field-row { grid-template-columns: 1fr; gap: 0; }
|
|
5687
5701
|
|
|
5688
5702
|
.btn { min-height: 36px; padding: 8px 12px; }
|
|
5689
5703
|
.btn-sm { min-height: 28px; padding: 4px 8px; }
|
|
@@ -6287,18 +6301,21 @@
|
|
|
6287
6301
|
background: none;
|
|
6288
6302
|
border: none;
|
|
6289
6303
|
border-bottom: 2px solid transparent;
|
|
6304
|
+
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
6290
6305
|
cursor: pointer;
|
|
6291
|
-
transition: color 0.15s, border-color 0.15s;
|
|
6306
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
6292
6307
|
white-space: nowrap;
|
|
6293
6308
|
}
|
|
6294
6309
|
|
|
6295
6310
|
.settings-tab:hover {
|
|
6296
6311
|
color: var(--text-primary);
|
|
6312
|
+
background: rgba(197, 101, 61, 0.06);
|
|
6297
6313
|
}
|
|
6298
6314
|
|
|
6299
6315
|
.settings-tab.active {
|
|
6300
6316
|
color: var(--accent);
|
|
6301
6317
|
border-bottom-color: var(--accent);
|
|
6318
|
+
background: var(--accent-muted);
|
|
6302
6319
|
}
|
|
6303
6320
|
|
|
6304
6321
|
.settings-panel {
|
|
@@ -6312,8 +6329,11 @@
|
|
|
6312
6329
|
.settings-about-info {
|
|
6313
6330
|
display: flex;
|
|
6314
6331
|
flex-direction: column;
|
|
6315
|
-
gap:
|
|
6332
|
+
gap: 0;
|
|
6316
6333
|
margin-bottom: 18px;
|
|
6334
|
+
background: var(--bg-secondary);
|
|
6335
|
+
border-radius: var(--radius-md);
|
|
6336
|
+
padding: 2px 14px;
|
|
6317
6337
|
}
|
|
6318
6338
|
|
|
6319
6339
|
.settings-about-row {
|
|
@@ -6321,6 +6341,12 @@
|
|
|
6321
6341
|
justify-content: space-between;
|
|
6322
6342
|
align-items: center;
|
|
6323
6343
|
font-size: 0.8125rem;
|
|
6344
|
+
padding: 9px 0;
|
|
6345
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
6346
|
+
}
|
|
6347
|
+
|
|
6348
|
+
.settings-about-row:last-child {
|
|
6349
|
+
border-bottom: none;
|
|
6324
6350
|
}
|
|
6325
6351
|
|
|
6326
6352
|
.settings-label {
|
|
@@ -6356,8 +6382,8 @@
|
|
|
6356
6382
|
.settings-section-title {
|
|
6357
6383
|
font-size: 0.8125rem;
|
|
6358
6384
|
font-weight: 600;
|
|
6359
|
-
color: var(--
|
|
6360
|
-
margin
|
|
6385
|
+
color: var(--text-primary);
|
|
6386
|
+
margin: 0 0 12px 0;
|
|
6361
6387
|
letter-spacing: 0.02em;
|
|
6362
6388
|
}
|
|
6363
6389
|
|
|
@@ -6367,13 +6393,6 @@
|
|
|
6367
6393
|
margin-top: 10px;
|
|
6368
6394
|
}
|
|
6369
6395
|
|
|
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
6396
|
.settings-divider {
|
|
6378
6397
|
border: none;
|
|
6379
6398
|
border-top: 1px solid var(--border-subtle);
|
|
@@ -6397,6 +6416,49 @@
|
|
|
6397
6416
|
margin-bottom: 0;
|
|
6398
6417
|
}
|
|
6399
6418
|
|
|
6419
|
+
.field-row {
|
|
6420
|
+
display: grid;
|
|
6421
|
+
grid-template-columns: 1fr 1fr;
|
|
6422
|
+
gap: 12px;
|
|
6423
|
+
margin-bottom: 14px;
|
|
6424
|
+
}
|
|
6425
|
+
.field-row .field {
|
|
6426
|
+
margin-bottom: 0;
|
|
6427
|
+
}
|
|
6428
|
+
|
|
6429
|
+
.settings-card {
|
|
6430
|
+
background: var(--bg-secondary);
|
|
6431
|
+
border-radius: var(--radius-md);
|
|
6432
|
+
padding: 16px;
|
|
6433
|
+
margin-bottom: 14px;
|
|
6434
|
+
}
|
|
6435
|
+
.settings-card .field:last-of-type {
|
|
6436
|
+
margin-bottom: 12px;
|
|
6437
|
+
}
|
|
6438
|
+
.settings-card .settings-section-title {
|
|
6439
|
+
margin-top: 0;
|
|
6440
|
+
}
|
|
6441
|
+
.settings-card .field-input {
|
|
6442
|
+
background: rgba(255, 255, 255, 0.6);
|
|
6443
|
+
}
|
|
6444
|
+
.settings-card .btn-block {
|
|
6445
|
+
margin-bottom: 0;
|
|
6446
|
+
}
|
|
6447
|
+
|
|
6448
|
+
.empty-state-compact {
|
|
6449
|
+
display: flex;
|
|
6450
|
+
flex-direction: column;
|
|
6451
|
+
align-items: center;
|
|
6452
|
+
gap: 6px;
|
|
6453
|
+
padding: 32px 16px;
|
|
6454
|
+
color: var(--text-muted);
|
|
6455
|
+
font-size: 0.8125rem;
|
|
6456
|
+
}
|
|
6457
|
+
.empty-state-compact .empty-icon {
|
|
6458
|
+
font-size: 1.5rem;
|
|
6459
|
+
opacity: 0.5;
|
|
6460
|
+
}
|
|
6461
|
+
|
|
6400
6462
|
.field-checkbox {
|
|
6401
6463
|
width: 18px;
|
|
6402
6464
|
height: 18px;
|
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;
|