@co0ontty/wand 1.3.6 → 1.5.1
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/auth.js +2 -1
- package/dist/claude-pty-bridge.d.ts +6 -0
- package/dist/claude-pty-bridge.js +38 -1
- package/dist/cli.js +2 -2
- package/dist/config.js +3 -2
- package/dist/middleware/rate-limit.js +2 -1
- package/dist/process-manager.d.ts +1 -0
- package/dist/process-manager.js +44 -5
- package/dist/server-session-routes.d.ts +2 -1
- package/dist/server-session-routes.js +113 -7
- package/dist/server.js +13 -9
- package/dist/storage.js +42 -8
- package/dist/structured-session-manager.d.ts +56 -0
- package/dist/structured-session-manager.js +730 -0
- package/dist/types.d.ts +17 -2
- package/dist/web-ui/content/scripts.js +730 -137
- package/dist/web-ui/content/styles.css +288 -79
- package/dist/web-ui/index.js +1 -1
- package/package.json +5 -4
|
@@ -100,8 +100,10 @@
|
|
|
100
100
|
cwdValue: "",
|
|
101
101
|
modeValue: "managed",
|
|
102
102
|
chatMode: "managed",
|
|
103
|
+
sessionCreateKind: "pty",
|
|
103
104
|
sessionTool: "claude",
|
|
104
105
|
preferredCommand: "claude",
|
|
106
|
+
structuredRunner: "claude-cli-print",
|
|
105
107
|
lastResize: { cols: 0, rows: 0 },
|
|
106
108
|
isOnline: navigator.onLine,
|
|
107
109
|
deferredPrompt: null,
|
|
@@ -137,6 +139,7 @@
|
|
|
137
139
|
shortcutsExpanded: false,
|
|
138
140
|
modifiers: { ctrl: false, alt: false, shift: false },
|
|
139
141
|
fileSearchQuery: "",
|
|
142
|
+
fileExplorerLoading: false,
|
|
140
143
|
allFiles: [],
|
|
141
144
|
claudeHistory: [],
|
|
142
145
|
claudeHistoryLoaded: false,
|
|
@@ -156,6 +159,76 @@
|
|
|
156
159
|
})()
|
|
157
160
|
};
|
|
158
161
|
|
|
162
|
+
// ── Structured session status bar (in-flight timer) ──
|
|
163
|
+
var _statusBarTimerId = null;
|
|
164
|
+
var _statusBarStartTime = 0;
|
|
165
|
+
|
|
166
|
+
function renderStructuredStatusBar(chatMessages, session) {
|
|
167
|
+
// Remove stale bar if session changed or not structured
|
|
168
|
+
var existing = chatMessages.querySelector(".structured-status-bar");
|
|
169
|
+
if (!session || !isStructuredSession(session)) {
|
|
170
|
+
if (existing) existing.remove();
|
|
171
|
+
clearInterval(_statusBarTimerId);
|
|
172
|
+
_statusBarTimerId = null;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
var isInFlight = session.structuredState && session.structuredState.inFlight;
|
|
177
|
+
|
|
178
|
+
if (isInFlight) {
|
|
179
|
+
// Start timer if not already running
|
|
180
|
+
if (!_statusBarTimerId) {
|
|
181
|
+
_statusBarStartTime = Date.now();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!existing) {
|
|
185
|
+
var bar = document.createElement("div");
|
|
186
|
+
bar.className = "structured-status-bar";
|
|
187
|
+
bar.innerHTML =
|
|
188
|
+
'<span class="status-bar-label">回复中</span>' +
|
|
189
|
+
'<div class="status-bar-track"><div class="status-bar-fill"></div></div>' +
|
|
190
|
+
'<span class="status-bar-timer">0.0s</span>';
|
|
191
|
+
// column-reverse: first child = visual bottom
|
|
192
|
+
chatMessages.insertBefore(bar, chatMessages.firstChild);
|
|
193
|
+
existing = bar;
|
|
194
|
+
} else if (existing.classList.contains("completed")) {
|
|
195
|
+
// Was completed, now in-flight again — reset
|
|
196
|
+
existing.classList.remove("completed");
|
|
197
|
+
existing.style.animation = "none";
|
|
198
|
+
existing.querySelector(".status-bar-label").textContent = "回复中";
|
|
199
|
+
_statusBarStartTime = Date.now();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Start interval to update timer
|
|
203
|
+
if (!_statusBarTimerId) {
|
|
204
|
+
_statusBarTimerId = setInterval(function() {
|
|
205
|
+
var bar = document.querySelector(".structured-status-bar:not(.completed)");
|
|
206
|
+
if (!bar) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; return; }
|
|
207
|
+
var elapsed = ((Date.now() - _statusBarStartTime) / 1000).toFixed(1);
|
|
208
|
+
var timerEl = bar.querySelector(".status-bar-timer");
|
|
209
|
+
if (timerEl) timerEl.textContent = elapsed + "s";
|
|
210
|
+
}, 100);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Not in-flight: show completion or remove
|
|
214
|
+
clearInterval(_statusBarTimerId);
|
|
215
|
+
_statusBarTimerId = null;
|
|
216
|
+
|
|
217
|
+
if (existing && !existing.classList.contains("completed")) {
|
|
218
|
+
// Just finished — transition to completed state
|
|
219
|
+
var elapsed = _statusBarStartTime ? ((Date.now() - _statusBarStartTime) / 1000).toFixed(1) : "0.0";
|
|
220
|
+
existing.classList.add("completed");
|
|
221
|
+
existing.querySelector(".status-bar-label").textContent = "完成";
|
|
222
|
+
existing.querySelector(".status-bar-timer").textContent = elapsed + "s";
|
|
223
|
+
_statusBarStartTime = 0;
|
|
224
|
+
// Remove after animation ends
|
|
225
|
+
setTimeout(function() {
|
|
226
|
+
if (existing.parentNode) existing.remove();
|
|
227
|
+
}, 3000);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
159
232
|
// Helper function to persist selected session ID to localStorage
|
|
160
233
|
function persistSelectedId() {
|
|
161
234
|
try {
|
|
@@ -276,7 +349,7 @@
|
|
|
276
349
|
body: "\u5f53\u524d " + (config.currentVersion || "-") + " \u2192 \u6700\u65b0 " + config.latestVersion,
|
|
277
350
|
type: "info",
|
|
278
351
|
icon: "\u2191",
|
|
279
|
-
duration:
|
|
352
|
+
duration: 10000,
|
|
280
353
|
actionLabel: "\u53bb\u66f4\u65b0",
|
|
281
354
|
action: function() {
|
|
282
355
|
var settingsBtn = document.getElementById("open-settings-btn") || document.querySelector("[data-action='settings']");
|
|
@@ -319,7 +392,7 @@
|
|
|
319
392
|
var app = document.getElementById("app");
|
|
320
393
|
var isLoggedIn = state.config !== null;
|
|
321
394
|
var wasModalOpen = state.modalOpen;
|
|
322
|
-
var shouldResetShell = !isLoggedIn ||
|
|
395
|
+
var shouldResetShell = !isLoggedIn || !!document.getElementById("output");
|
|
323
396
|
|
|
324
397
|
if (shouldResetShell) {
|
|
325
398
|
teardownTerminal();
|
|
@@ -338,6 +411,9 @@
|
|
|
338
411
|
if (!skipShellChrome) {
|
|
339
412
|
updateShellChrome();
|
|
340
413
|
}
|
|
414
|
+
if (isLoggedIn && state.filePanelOpen) {
|
|
415
|
+
refreshFileExplorer();
|
|
416
|
+
}
|
|
341
417
|
|
|
342
418
|
// Force reflow then re-enable transitions after layout settles
|
|
343
419
|
void document.body.offsetHeight;
|
|
@@ -554,11 +630,14 @@
|
|
|
554
630
|
'<div class="blank-chat-inner">' +
|
|
555
631
|
'<div class="blank-chat-logo">W</div>' +
|
|
556
632
|
'<h2 class="blank-chat-title">Wand</h2>' +
|
|
557
|
-
'<p class="blank-chat-subtitle"
|
|
633
|
+
'<p class="blank-chat-subtitle">支持终端 PTY 会话与结构化 chat 会话,两种模式可并存。</p>' +
|
|
558
634
|
'<div class="blank-chat-tools">' +
|
|
559
635
|
'<button class="blank-chat-tool-btn" id="welcome-tool-claude" type="button">' +
|
|
560
636
|
'<span class="tool-icon">🤖</span>新建终端会话' +
|
|
561
637
|
'</button>' +
|
|
638
|
+
'<button class="blank-chat-tool-btn" id="welcome-tool-structured" type="button">' +
|
|
639
|
+
'<span class="tool-icon">💬</span>新建结构化会话' +
|
|
640
|
+
'</button>' +
|
|
562
641
|
'</div>' +
|
|
563
642
|
'<div class="blank-chat-cwd-wrap">' +
|
|
564
643
|
'<div class="blank-chat-cwd" id="blank-chat-cwd" role="button" tabindex="0" title="点击切换工作目录">' +
|
|
@@ -619,6 +698,9 @@
|
|
|
619
698
|
'<span id="session-cwd-display" class="session-cwd-display">' + (selectedSession && selectedSession.cwd ? escapeHtml(selectedSession.cwd) : '未设置目录') + '</span>' +
|
|
620
699
|
'<span class="session-info-separator">|</span>' +
|
|
621
700
|
'<span id="session-mode-display" class="session-mode-display">' + (selectedSession ? getModeLabel(selectedSession.mode) : '默认') + '</span>' +
|
|
701
|
+
(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
|
+
'<span class="session-info-separator">|</span>' +
|
|
703
|
+
'<span id="session-kind-display" class="session-kind-display">' + (selectedSession ? (isStructuredSession(selectedSession) ? 'Structured' : 'PTY') : 'PTY') + '</span>' +
|
|
622
704
|
'<span class="session-info-separator">|</span>' +
|
|
623
705
|
'<span id="session-status-display" class="session-status-display">' + (selectedSession ? getSessionStatusLabel(selectedSession) : '-') + '</span>' +
|
|
624
706
|
(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>' : '') +
|
|
@@ -739,11 +821,21 @@
|
|
|
739
821
|
'<label class="field-label" for="cfg-shell">Shell</label>' +
|
|
740
822
|
'<input id="cfg-shell" type="text" class="field-input" placeholder="/bin/bash" />' +
|
|
741
823
|
'</div>' +
|
|
742
|
-
'<div class="field
|
|
743
|
-
'<label class="field-label" for="cfg-
|
|
744
|
-
'<
|
|
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>' +
|
|
745
838
|
'</div>' +
|
|
746
|
-
'<p class="hint" style="margin-top:-8px;margin-bottom:8px;">移动端使用 DOM 渲染终端,支持原生文本选择与复制。保存后刷新页面生效。</p>' +
|
|
747
839
|
'<button id="save-config-button" class="btn btn-primary btn-block">保存配置</button>' +
|
|
748
840
|
'<p id="config-message" class="hint hidden"></p>' +
|
|
749
841
|
'</div>' +
|
|
@@ -1256,13 +1348,21 @@
|
|
|
1256
1348
|
explorer.innerHTML = '<div class="file-explorer empty">No working directory.</div>';
|
|
1257
1349
|
return;
|
|
1258
1350
|
}
|
|
1351
|
+
state.fileExplorerLoading = true;
|
|
1352
|
+
state.allFiles = [];
|
|
1259
1353
|
explorer.innerHTML = '<div class="file-explorer"><div class="tree-loading" style="padding:12px;color:var(--text-muted);font-size:0.8125rem;">Loading...</div></div>';
|
|
1260
1354
|
// Update the cwd display
|
|
1261
1355
|
if (cwdEl) cwdEl.textContent = cwd;
|
|
1262
1356
|
// Fetch with git status
|
|
1263
1357
|
fetch("/api/directory?q=" + encodeURIComponent(cwd) + "&gitStatus=true", { credentials: "same-origin" })
|
|
1264
|
-
.then(function(res) {
|
|
1358
|
+
.then(function(res) {
|
|
1359
|
+
if (!res.ok) {
|
|
1360
|
+
throw new Error("Failed to load directory.");
|
|
1361
|
+
}
|
|
1362
|
+
return res.json();
|
|
1363
|
+
})
|
|
1265
1364
|
.then(function(items) {
|
|
1365
|
+
state.fileExplorerLoading = false;
|
|
1266
1366
|
if (!items || items.length === 0) {
|
|
1267
1367
|
explorer.innerHTML = '<div class="file-explorer empty">Empty directory or inaccessible.</div>';
|
|
1268
1368
|
return;
|
|
@@ -1271,6 +1371,7 @@
|
|
|
1271
1371
|
filterFileTree();
|
|
1272
1372
|
})
|
|
1273
1373
|
.catch(function() {
|
|
1374
|
+
state.fileExplorerLoading = false;
|
|
1274
1375
|
explorer.innerHTML = '<div class="file-explorer empty">Failed to load files.</div>';
|
|
1275
1376
|
});
|
|
1276
1377
|
}
|
|
@@ -1667,7 +1768,7 @@
|
|
|
1667
1768
|
if (session.claudeSessionId) {
|
|
1668
1769
|
var shortId = session.claudeSessionId.slice(0, 8);
|
|
1669
1770
|
sessionIdDisplay = '<span class="session-id" title="' + escapeHtml(session.claudeSessionId) + '">' + escapeHtml(shortId) + '</span>';
|
|
1670
|
-
if (session.status !== "running" && !state.sessionsManageMode) {
|
|
1771
|
+
if (session.status !== "running" && !state.sessionsManageMode && !isStructuredSession(session)) {
|
|
1671
1772
|
resumeButton = '<button class="session-action-btn" data-action="resume" data-session-id="' + session.id + '" type="button" aria-label="恢复会话" title="恢复 Claude 会话"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 105.64-11.36L3 10"/></svg></button>';
|
|
1672
1773
|
}
|
|
1673
1774
|
}
|
|
@@ -1677,6 +1778,7 @@
|
|
|
1677
1778
|
}
|
|
1678
1779
|
|
|
1679
1780
|
var deleteButton = state.sessionsManageMode ? '' : '<button class="session-action-btn delete-btn" data-action="delete-session" data-session-id="' + session.id + '" type="button" aria-label="删除会话" title="删除此会话"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg></button>';
|
|
1781
|
+
var modeBadge = renderSessionKindBadge(session);
|
|
1680
1782
|
var actionsHtml = '<span class="session-actions">' + resumeButton + deleteButton + '</span>';
|
|
1681
1783
|
|
|
1682
1784
|
return '<div class="session-item' + activeClass + selectedClass + '" data-session-id="' + session.id + '" role="button" tabindex="0">' +
|
|
@@ -1686,6 +1788,7 @@
|
|
|
1686
1788
|
'<div class="session-main">' +
|
|
1687
1789
|
'<div class="session-command">' + escapeHtml(session.resumedFromSessionId ? session.command.replace(/\s+--resume\s+\S+/, '') : session.command) + '</div>' +
|
|
1688
1790
|
'<div class="session-meta">' +
|
|
1791
|
+
modeBadge +
|
|
1689
1792
|
'<span>' + escapeHtml(modeName) + '</span>' +
|
|
1690
1793
|
'<span class="session-status ' + metaStatusClass + '">' + escapeHtml(metaStatus) + '</span>' +
|
|
1691
1794
|
sessionIdDisplay +
|
|
@@ -1697,6 +1800,15 @@
|
|
|
1697
1800
|
'</div>' +
|
|
1698
1801
|
'</div>';
|
|
1699
1802
|
}
|
|
1803
|
+
|
|
1804
|
+
function renderSessionKindBadge(session) {
|
|
1805
|
+
if (!session) return "";
|
|
1806
|
+
if (isStructuredSession(session)) {
|
|
1807
|
+
return '<span class="session-kind-badge structured">Structured</span>';
|
|
1808
|
+
}
|
|
1809
|
+
return '<span class="session-kind-badge pty">PTY</span>';
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1700
1812
|
function renderModeCards(selectedMode) {
|
|
1701
1813
|
var modes = [
|
|
1702
1814
|
{ id: "managed", label: "托管", desc: "全自动完成任务" },
|
|
@@ -1714,19 +1826,48 @@
|
|
|
1714
1826
|
}).join("");
|
|
1715
1827
|
}
|
|
1716
1828
|
|
|
1829
|
+
function renderSessionKindOptions(selectedKind) {
|
|
1830
|
+
var kinds = [
|
|
1831
|
+
{ id: "pty", label: "PTY", desc: "交互式终端会话" },
|
|
1832
|
+
{ id: "structured", label: "Structured", desc: "单轮结构化输出" }
|
|
1833
|
+
];
|
|
1834
|
+
return kinds.map(function(kind) {
|
|
1835
|
+
var active = kind.id === selectedKind ? " active" : "";
|
|
1836
|
+
return '<button type="button" class="mode-card session-kind-card' + active + '" data-session-kind="' + kind.id + '">' +
|
|
1837
|
+
'<span class="mode-card-label">' + kind.label + '</span>' +
|
|
1838
|
+
'<span class="mode-card-desc">' + kind.desc + '</span>' +
|
|
1839
|
+
'</button>';
|
|
1840
|
+
}).join("");
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
function getSessionKindHint(kind) {
|
|
1844
|
+
if (kind === "structured") {
|
|
1845
|
+
return "直接使用 claude -p 获取结构化单轮结果。";
|
|
1846
|
+
}
|
|
1847
|
+
return "默认 PTY 会话,支持持续交互、终端视图和权限流。";
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1717
1850
|
function renderSessionModal() {
|
|
1718
1851
|
var modalTool = getPreferredTool();
|
|
1719
1852
|
var modalMode = getSafeModeForTool(modalTool, state.modeValue || state.chatMode || "default");
|
|
1853
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
1720
1854
|
return '<section id="session-modal" class="modal-backdrop hidden">' +
|
|
1721
1855
|
'<div class="modal session-modal">' +
|
|
1722
1856
|
'<div class="modal-header">' +
|
|
1723
1857
|
'<div>' +
|
|
1724
1858
|
'<h2 class="modal-title">新对话</h2>' +
|
|
1725
|
-
'<p class="modal-subtitle">启动 Claude
|
|
1859
|
+
'<p class="modal-subtitle">启动 Claude 会话,选择会话类型、模式和工作目录。</p>' +
|
|
1726
1860
|
'</div>' +
|
|
1727
1861
|
'<button id="close-modal-button" class="btn btn-ghost btn-icon">×</button>' +
|
|
1728
1862
|
'</div>' +
|
|
1729
1863
|
'<div class="modal-body">' +
|
|
1864
|
+
'<div class="field">' +
|
|
1865
|
+
'<label class="field-label">会话类型</label>' +
|
|
1866
|
+
'<div id="session-kind-cards" class="mode-cards">' +
|
|
1867
|
+
renderSessionKindOptions(sessionKind) +
|
|
1868
|
+
'</div>' +
|
|
1869
|
+
'<p id="session-kind-description" class="field-hint">' + escapeHtml(getSessionKindHint(sessionKind)) + '</p>' +
|
|
1870
|
+
'</div>' +
|
|
1730
1871
|
'<div class="field">' +
|
|
1731
1872
|
'<label class="field-label">模式</label>' +
|
|
1732
1873
|
'<div id="mode-cards" class="mode-cards">' +
|
|
@@ -1785,21 +1926,23 @@
|
|
|
1785
1926
|
var statusSpan = el.querySelector(".inline-tool-status");
|
|
1786
1927
|
if (statusSpan) {
|
|
1787
1928
|
if (el.dataset.status === "error") {
|
|
1788
|
-
statusSpan.textContent = "
|
|
1929
|
+
statusSpan.textContent = "✗";
|
|
1789
1930
|
} else if (el.dataset.status === "done") {
|
|
1790
|
-
statusSpan.textContent =
|
|
1931
|
+
statusSpan.textContent = "✓";
|
|
1791
1932
|
}
|
|
1792
1933
|
}
|
|
1793
1934
|
};
|
|
1794
1935
|
// Toggle function for terminal tool blocks
|
|
1795
1936
|
window.__terminalExpand = function(el) {
|
|
1796
|
-
var
|
|
1937
|
+
var container = el.closest(".inline-terminal");
|
|
1938
|
+
if (!container) return;
|
|
1939
|
+
var body = container.querySelector(".term-body");
|
|
1797
1940
|
if (body) {
|
|
1798
1941
|
var isHidden = body.style.display === "none";
|
|
1799
1942
|
body.style.display = isHidden ? "block" : "none";
|
|
1800
|
-
|
|
1943
|
+
container.dataset.expanded = isHidden ? "true" : "false";
|
|
1801
1944
|
var toggleIcon = el.querySelector(".term-toggle-icon");
|
|
1802
|
-
if (toggleIcon) toggleIcon.textContent = isHidden ? "▼" : "
|
|
1945
|
+
if (toggleIcon) toggleIcon.textContent = isHidden ? "▼" : "▶";
|
|
1803
1946
|
}
|
|
1804
1947
|
};
|
|
1805
1948
|
// Update streaming thinking content (called from WebSocket handler)
|
|
@@ -1899,6 +2042,16 @@
|
|
|
1899
2042
|
quickStartSession();
|
|
1900
2043
|
});
|
|
1901
2044
|
}
|
|
2045
|
+
var welcomeStructuredBtn = document.getElementById("welcome-tool-structured");
|
|
2046
|
+
if (welcomeStructuredBtn) {
|
|
2047
|
+
welcomeStructuredBtn.addEventListener("click", function() {
|
|
2048
|
+
createStructuredSession().then(function() {
|
|
2049
|
+
focusInputBox(true);
|
|
2050
|
+
}).catch(function(error) {
|
|
2051
|
+
showToast((error && error.message) || "无法启动结构化会话。", "error");
|
|
2052
|
+
});
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
1902
2055
|
initBlankChatCwd();
|
|
1903
2056
|
|
|
1904
2057
|
var sessionsList = document.getElementById("sessions-list");
|
|
@@ -1911,6 +2064,17 @@
|
|
|
1911
2064
|
// Claude session ID badge click-to-copy (event delegation on document)
|
|
1912
2065
|
document.addEventListener("click", handleClaudeIdCopy);
|
|
1913
2066
|
|
|
2067
|
+
var kindCardsEl = document.getElementById("session-kind-cards");
|
|
2068
|
+
if (kindCardsEl) kindCardsEl.addEventListener("click", function(e) {
|
|
2069
|
+
var card = e.target.closest(".session-kind-card");
|
|
2070
|
+
if (!card) return;
|
|
2071
|
+
var kind = card.getAttribute("data-session-kind");
|
|
2072
|
+
if (kind) {
|
|
2073
|
+
state.sessionCreateKind = kind;
|
|
2074
|
+
syncSessionModalUI();
|
|
2075
|
+
}
|
|
2076
|
+
});
|
|
2077
|
+
|
|
1914
2078
|
var modeCardsEl = document.getElementById("mode-cards");
|
|
1915
2079
|
if (modeCardsEl) modeCardsEl.addEventListener("click", function(e) {
|
|
1916
2080
|
var card = e.target.closest(".mode-card");
|
|
@@ -1941,7 +2105,7 @@
|
|
|
1941
2105
|
persistSelectedId();
|
|
1942
2106
|
resetChatRenderCache();
|
|
1943
2107
|
closeSessionsDrawer();
|
|
1944
|
-
|
|
2108
|
+
render();
|
|
1945
2109
|
});
|
|
1946
2110
|
var refreshBtn = document.getElementById("sidebar-refresh-btn");
|
|
1947
2111
|
if (refreshBtn) refreshBtn.addEventListener("click", function() {
|
|
@@ -1996,6 +2160,8 @@
|
|
|
1996
2160
|
if (approvePermissionBtn) approvePermissionBtn.addEventListener("click", approvePermission);
|
|
1997
2161
|
var denyPermissionBtn = document.getElementById("deny-permission-btn");
|
|
1998
2162
|
if (denyPermissionBtn) denyPermissionBtn.addEventListener("click", denyPermission);
|
|
2163
|
+
var autoApproveToggle = document.getElementById("auto-approve-toggle");
|
|
2164
|
+
if (autoApproveToggle) autoApproveToggle.addEventListener("click", toggleAutoApprove);
|
|
1999
2165
|
var sendBtn = document.getElementById("send-input-button");
|
|
2000
2166
|
if (sendBtn) sendBtn.addEventListener("click", function() {
|
|
2001
2167
|
closeSessionsDrawer();
|
|
@@ -2256,7 +2422,7 @@
|
|
|
2256
2422
|
state.selectedId = null;
|
|
2257
2423
|
persistSelectedId();
|
|
2258
2424
|
state.drafts = {};
|
|
2259
|
-
|
|
2425
|
+
render();
|
|
2260
2426
|
// 聚焦到目录输入框
|
|
2261
2427
|
setTimeout(function() {
|
|
2262
2428
|
var folderInput = document.getElementById("folder-picker-input");
|
|
@@ -2539,7 +2705,7 @@
|
|
|
2539
2705
|
|
|
2540
2706
|
function activateSessionItem(sessionId) {
|
|
2541
2707
|
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
2542
|
-
if (session && session.status !== "running") {
|
|
2708
|
+
if (session && session.status !== "running" && !isStructuredSession(session)) {
|
|
2543
2709
|
resumeSessionFromList(sessionId);
|
|
2544
2710
|
} else {
|
|
2545
2711
|
selectSession(sessionId);
|
|
@@ -3336,6 +3502,35 @@
|
|
|
3336
3502
|
return hints[mode] || '';
|
|
3337
3503
|
}
|
|
3338
3504
|
|
|
3505
|
+
function getSessionKindLabel(session) {
|
|
3506
|
+
return isStructuredSession(session) ? "Structured" : "PTY";
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
function getSessionKindDescription(session) {
|
|
3510
|
+
return isStructuredSession(session)
|
|
3511
|
+
? "Structured · block transcript"
|
|
3512
|
+
: "PTY · terminal session";
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
function isRecoverableToolError(toolResult, nextResult) {
|
|
3516
|
+
if (!toolResult || !toolResult.is_error || !nextResult || nextResult.is_error) {
|
|
3517
|
+
return false;
|
|
3518
|
+
}
|
|
3519
|
+
var currentText = extractToolResultText(toolResult.content).toLowerCase();
|
|
3520
|
+
var nextText = extractToolResultText(nextResult.content).toLowerCase();
|
|
3521
|
+
if (!currentText) return false;
|
|
3522
|
+
if (currentText.indexOf("invalid pages parameter") !== -1 && nextText.length > 0) {
|
|
3523
|
+
return true;
|
|
3524
|
+
}
|
|
3525
|
+
return false;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
function isStructuredSession(session) {
|
|
3529
|
+
var result = !!session && (session.sessionKind === "structured" || session.runner === "claude-cli-print");
|
|
3530
|
+
if (session) console.log("[WAND] isStructuredSession id:", session.id, "sessionKind:", session.sessionKind, "runner:", session.runner, "=>", result);
|
|
3531
|
+
return result;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3339
3534
|
function syncComposerModeSelect() {
|
|
3340
3535
|
var select = document.getElementById("chat-mode-select");
|
|
3341
3536
|
if (!select) return;
|
|
@@ -3346,29 +3541,89 @@
|
|
|
3346
3541
|
if (modeHint) modeHint.textContent = getModeHint(state.chatMode);
|
|
3347
3542
|
}
|
|
3348
3543
|
|
|
3544
|
+
function createStructuredSession(prompt, cwdOverride, modeOverride) {
|
|
3545
|
+
var payload = {
|
|
3546
|
+
cwd: cwdOverride || getEffectiveCwd(),
|
|
3547
|
+
mode: modeOverride || state.chatMode || (state.config && state.config.defaultMode) || "default",
|
|
3548
|
+
runner: state.structuredRunner || "claude-cli-print",
|
|
3549
|
+
prompt: prompt || undefined
|
|
3550
|
+
};
|
|
3551
|
+
console.log("[WAND] createStructuredSession payload:", JSON.stringify(payload));
|
|
3552
|
+
return fetch("/api/structured-sessions", {
|
|
3553
|
+
method: "POST",
|
|
3554
|
+
headers: { "Content-Type": "application/json" },
|
|
3555
|
+
credentials: "same-origin",
|
|
3556
|
+
body: JSON.stringify(payload)
|
|
3557
|
+
})
|
|
3558
|
+
.then(function(res) {
|
|
3559
|
+
console.log("[WAND] createStructuredSession response status:", res.status);
|
|
3560
|
+
return res.json();
|
|
3561
|
+
})
|
|
3562
|
+
.then(function(data) {
|
|
3563
|
+
console.log("[WAND] createStructuredSession data:", JSON.stringify({ id: data.id, error: data.error, sessionKind: data.sessionKind, runner: data.runner, status: data.status }));
|
|
3564
|
+
if (data.error) {
|
|
3565
|
+
throw new Error(data.error);
|
|
3566
|
+
}
|
|
3567
|
+
state.selectedId = data.id;
|
|
3568
|
+
persistSelectedId();
|
|
3569
|
+
state.drafts[data.id] = "";
|
|
3570
|
+
resetChatRenderCache();
|
|
3571
|
+
updateSessionSnapshot(data);
|
|
3572
|
+
updateSessionsList();
|
|
3573
|
+
switchToSessionView(data.id);
|
|
3574
|
+
subscribeToSession(data.id);
|
|
3575
|
+
return loadOutput(data.id).then(function() { return data; });
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3349
3579
|
function applyCurrentView() {
|
|
3350
3580
|
var hasSession = !!state.selectedId;
|
|
3351
3581
|
var terminalBtn = document.getElementById("view-terminal-btn");
|
|
3352
3582
|
var terminalContainer = document.getElementById("output");
|
|
3353
3583
|
var chatContainer = document.getElementById("chat-output");
|
|
3584
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
3585
|
+
var structured = isStructuredSession(selectedSession);
|
|
3586
|
+
var showTerminal = hasSession && !structured && state.currentView === "terminal";
|
|
3587
|
+
var showChat = hasSession && (structured || state.currentView !== "terminal");
|
|
3588
|
+
console.log("[WAND] applyCurrentView hasSession:", hasSession, "structured:", structured, "currentView:", state.currentView, "showTerminal:", showTerminal, "showChat:", showChat, "sessionKind:", selectedSession && selectedSession.sessionKind, "runner:", selectedSession && selectedSession.runner);
|
|
3589
|
+
|
|
3590
|
+
if (structured) {
|
|
3591
|
+
state.currentView = "chat";
|
|
3592
|
+
} else if (!hasSession) {
|
|
3593
|
+
state.currentView = "terminal";
|
|
3594
|
+
}
|
|
3354
3595
|
|
|
3355
|
-
if (terminalBtn)
|
|
3356
|
-
|
|
3596
|
+
if (terminalBtn) {
|
|
3597
|
+
terminalBtn.classList.toggle("hidden", structured || !hasSession);
|
|
3598
|
+
terminalBtn.classList.toggle("active", showTerminal);
|
|
3599
|
+
}
|
|
3600
|
+
if (terminalContainer) {
|
|
3601
|
+
terminalContainer.classList.toggle("active", showTerminal);
|
|
3602
|
+
terminalContainer.classList.toggle("hidden", !showTerminal);
|
|
3603
|
+
}
|
|
3357
3604
|
if (chatContainer) {
|
|
3358
|
-
chatContainer.classList.
|
|
3359
|
-
chatContainer.classList.
|
|
3605
|
+
chatContainer.classList.toggle("active", showChat);
|
|
3606
|
+
chatContainer.classList.toggle("hidden", !showChat);
|
|
3360
3607
|
}
|
|
3361
3608
|
updateInteractiveControls();
|
|
3362
3609
|
}
|
|
3363
3610
|
|
|
3364
3611
|
function syncSessionModalUI() {
|
|
3365
3612
|
var modeHint = document.getElementById("mode-description");
|
|
3613
|
+
var kindHint = document.getElementById("session-kind-description");
|
|
3366
3614
|
var tool = "claude";
|
|
3615
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
3367
3616
|
|
|
3368
3617
|
state.sessionTool = tool;
|
|
3369
3618
|
state.modeValue = getSafeModeForTool(tool, state.modeValue || state.chatMode || "default");
|
|
3370
3619
|
|
|
3371
|
-
|
|
3620
|
+
var kindCards = document.querySelectorAll("#session-kind-cards .session-kind-card");
|
|
3621
|
+
if (kindCards.length) {
|
|
3622
|
+
kindCards.forEach(function(card) {
|
|
3623
|
+
card.classList.toggle("active", card.getAttribute("data-session-kind") === sessionKind);
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3372
3627
|
var modeCards = document.querySelectorAll("#mode-cards .mode-card");
|
|
3373
3628
|
if (modeCards.length) {
|
|
3374
3629
|
modeCards.forEach(function(card) {
|
|
@@ -3376,11 +3631,22 @@
|
|
|
3376
3631
|
});
|
|
3377
3632
|
}
|
|
3378
3633
|
|
|
3634
|
+
if (kindHint) kindHint.textContent = getSessionKindHint(sessionKind);
|
|
3379
3635
|
if (modeHint) modeHint.textContent = getToolModeHint(tool, state.modeValue);
|
|
3380
3636
|
}
|
|
3381
3637
|
|
|
3382
3638
|
function updateSessionSnapshot(snapshot) {
|
|
3383
3639
|
if (!snapshot || !snapshot.id) return;
|
|
3640
|
+
if (snapshot.id === state.selectedId || (snapshot.sessionKind === "structured") || snapshot.structuredState) {
|
|
3641
|
+
console.log("[WAND] updateSessionSnapshot", snapshot.id, JSON.stringify({
|
|
3642
|
+
status: snapshot.status,
|
|
3643
|
+
exitCode: snapshot.exitCode,
|
|
3644
|
+
sessionKind: snapshot.sessionKind,
|
|
3645
|
+
runner: snapshot.runner,
|
|
3646
|
+
inFlight: snapshot.structuredState && snapshot.structuredState.inFlight,
|
|
3647
|
+
msgCount: snapshot.messages && snapshot.messages.length
|
|
3648
|
+
}));
|
|
3649
|
+
}
|
|
3384
3650
|
var updated = false;
|
|
3385
3651
|
var prevSession = null;
|
|
3386
3652
|
state.sessions = state.sessions.map(function(session) {
|
|
@@ -3410,14 +3676,34 @@
|
|
|
3410
3676
|
var localOutput = localSession.output || "";
|
|
3411
3677
|
var serverOutput = serverSession.output || "";
|
|
3412
3678
|
var keepLocalOutput = localOutput.length > serverOutput.length;
|
|
3679
|
+
var localStructuredState = localSession.structuredState || null;
|
|
3680
|
+
var serverStructuredState = serverSession.structuredState || null;
|
|
3681
|
+
var localHasPendingAssistant = !!(localSession.messages && localSession.messages.length && (function() {
|
|
3682
|
+
var last = localSession.messages[localSession.messages.length - 1];
|
|
3683
|
+
return last && last.role === "assistant" && Array.isArray(last.content) && last.content.some(function(block) {
|
|
3684
|
+
return block && block.__processing;
|
|
3685
|
+
});
|
|
3686
|
+
})());
|
|
3687
|
+
var preserveLocalStructuredProgress = (localSession.sessionKind === "structured")
|
|
3688
|
+
&& !!localStructuredState
|
|
3689
|
+
&& localStructuredState.inFlight === true
|
|
3690
|
+
&& (!serverStructuredState || serverStructuredState.inFlight !== true)
|
|
3691
|
+
&& localHasPendingAssistant
|
|
3692
|
+
&& !!localStructuredState.activeRequestId
|
|
3693
|
+
&& (!serverStructuredState || !serverStructuredState.activeRequestId || serverStructuredState.activeRequestId === localStructuredState.activeRequestId);
|
|
3413
3694
|
|
|
3414
3695
|
if (keepLocalOutput) {
|
|
3415
3696
|
merged.output = localOutput;
|
|
3416
3697
|
}
|
|
3417
3698
|
|
|
3699
|
+
if (preserveLocalStructuredProgress) {
|
|
3700
|
+
merged.status = localSession.status || merged.status;
|
|
3701
|
+
merged.structuredState = Object.assign({}, serverStructuredState || {}, localStructuredState, { inFlight: true });
|
|
3702
|
+
merged.messages = localSession.messages;
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3418
3705
|
if (localSession.id === state.selectedId) {
|
|
3419
3706
|
if (localSession.permissionBlocked && serverSession.permissionBlocked === false) {
|
|
3420
|
-
// server explicitly resolved it; keep resolved state
|
|
3421
3707
|
} else if (localSession.permissionBlocked && !serverSession.permissionBlocked) {
|
|
3422
3708
|
merged.permissionBlocked = true;
|
|
3423
3709
|
}
|
|
@@ -3452,15 +3738,13 @@
|
|
|
3452
3738
|
|
|
3453
3739
|
function getPreferredSessionId(sessions) {
|
|
3454
3740
|
if (!sessions || !sessions.length) return null;
|
|
3455
|
-
// Keep currently selected session as long as it still exists
|
|
3456
3741
|
if (state.selectedId) {
|
|
3457
3742
|
var stillExists = sessions.find(function(session) { return session.id === state.selectedId; });
|
|
3458
3743
|
if (stillExists) return stillExists.id;
|
|
3744
|
+
return null;
|
|
3459
3745
|
}
|
|
3460
|
-
// No selection — pick a running session, or fall back to most recent
|
|
3461
3746
|
var runningSession = sessions.find(function(session) { return session.status === "running"; });
|
|
3462
3747
|
if (runningSession) return runningSession.id;
|
|
3463
|
-
// Fall back to most recent non-archived session (sessions are sorted newest first)
|
|
3464
3748
|
var recent = sessions.find(function(session) { return !session.archived; });
|
|
3465
3749
|
return recent ? recent.id : sessions[0].id;
|
|
3466
3750
|
}
|
|
@@ -3487,7 +3771,10 @@
|
|
|
3487
3771
|
return mergeServerSession(localSession, serverSession);
|
|
3488
3772
|
});
|
|
3489
3773
|
|
|
3490
|
-
|
|
3774
|
+
var preferredSessionId = getPreferredSessionId(state.sessions);
|
|
3775
|
+
if (preferredSessionId !== undefined) {
|
|
3776
|
+
state.selectedId = preferredSessionId;
|
|
3777
|
+
}
|
|
3491
3778
|
persistSelectedId();
|
|
3492
3779
|
if (state.modalOpen) {
|
|
3493
3780
|
updateSessionsList();
|
|
@@ -3504,13 +3791,22 @@
|
|
|
3504
3791
|
}
|
|
3505
3792
|
}
|
|
3506
3793
|
updateShellChrome();
|
|
3794
|
+
|
|
3795
|
+
// For structured sessions, loadOutput is needed to fetch messages
|
|
3796
|
+
// (the sessions list endpoint doesn't include them).
|
|
3797
|
+
// On page refresh this is the only place that can trigger it.
|
|
3798
|
+
if (state.selectedId) {
|
|
3799
|
+
var sel = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
3800
|
+
if (isStructuredSession(sel)) {
|
|
3801
|
+
loadOutput(state.selectedId);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3507
3804
|
})
|
|
3508
3805
|
.catch(function(e) {
|
|
3509
3806
|
console.error("[wand] loadSessions failed:", e);
|
|
3510
3807
|
});
|
|
3511
3808
|
}
|
|
3512
3809
|
|
|
3513
|
-
|
|
3514
3810
|
function updateSessionsList() {
|
|
3515
3811
|
var listEl = document.getElementById("sessions-list");
|
|
3516
3812
|
var countEl = document.getElementById("session-count");
|
|
@@ -3538,22 +3834,23 @@
|
|
|
3538
3834
|
|
|
3539
3835
|
if (summaryEl && summaryEl.textContent !== terminalTitle) summaryEl.textContent = terminalTitle;
|
|
3540
3836
|
if (titleEl && titleEl.textContent !== terminalTitle) titleEl.textContent = terminalTitle;
|
|
3541
|
-
if (infoEl
|
|
3542
|
-
infoEl.textContent = terminalInfo;
|
|
3543
|
-
}
|
|
3837
|
+
if (infoEl) infoEl.textContent = selectedSession ? (terminalInfo + " · " + getSessionKindDescription(selectedSession)) : terminalInfo;
|
|
3544
3838
|
|
|
3545
|
-
// Update session info bar at bottom
|
|
3546
3839
|
var cwdEl = document.getElementById("session-cwd-display");
|
|
3547
3840
|
var modeEl = document.getElementById("session-mode-display");
|
|
3841
|
+
var kindEl = document.getElementById("session-kind-display");
|
|
3548
3842
|
var statusEl = document.getElementById("session-status-display");
|
|
3549
3843
|
var exitEl = document.getElementById("session-exit-display");
|
|
3550
3844
|
var cwdText = selectedSession && selectedSession.cwd ? selectedSession.cwd : "未设置目录";
|
|
3551
3845
|
var modeText = selectedSession ? getModeLabel(selectedSession.mode) : "默认";
|
|
3846
|
+
var kindText = selectedSession ? getSessionKindLabel(selectedSession) : "PTY";
|
|
3552
3847
|
var exitText = "exit=" + (selectedSession && selectedSession.exitCode !== undefined ? selectedSession.exitCode : "n/a");
|
|
3553
3848
|
if (cwdEl && cwdEl.textContent !== cwdText) cwdEl.textContent = cwdText;
|
|
3554
3849
|
if (modeEl && modeEl.textContent !== modeText) modeEl.textContent = modeText;
|
|
3850
|
+
if (kindEl && kindEl.textContent !== kindText) kindEl.textContent = kindText;
|
|
3555
3851
|
if (statusEl && statusEl.textContent !== terminalInfo) statusEl.textContent = terminalInfo;
|
|
3556
3852
|
if (exitEl && exitEl.textContent !== exitText) exitEl.textContent = exitText;
|
|
3853
|
+
updateAutoApproveIndicator();
|
|
3557
3854
|
|
|
3558
3855
|
if (!state.terminal && terminalContainer && selectedSession) {
|
|
3559
3856
|
initTerminal();
|
|
@@ -3601,7 +3898,12 @@
|
|
|
3601
3898
|
clearTimeout(chatRenderTimer);
|
|
3602
3899
|
chatRenderTimer = null;
|
|
3603
3900
|
}
|
|
3604
|
-
|
|
3901
|
+
var sess = state.sessions.find(function(s) { return s.id === id; });
|
|
3902
|
+
var url = "/api/sessions/" + id;
|
|
3903
|
+
if (isStructuredSession(sess)) {
|
|
3904
|
+
url += "?format=chat";
|
|
3905
|
+
}
|
|
3906
|
+
return fetch(url, { credentials: "same-origin" })
|
|
3605
3907
|
.then(function(res) { return res.json(); })
|
|
3606
3908
|
.then(function(data) {
|
|
3607
3909
|
if (data.error) {
|
|
@@ -3628,6 +3930,8 @@
|
|
|
3628
3930
|
}
|
|
3629
3931
|
|
|
3630
3932
|
function selectSession(id) {
|
|
3933
|
+
var foundSession = state.sessions.find(function(item) { return item.id === id; });
|
|
3934
|
+
console.log("[WAND] selectSession id:", id, "found:", !!foundSession, "sessionKind:", foundSession && foundSession.sessionKind, "runner:", foundSession && foundSession.runner, "isStructured:", isStructuredSession(foundSession));
|
|
3631
3935
|
state.selectedId = id;
|
|
3632
3936
|
persistSelectedId();
|
|
3633
3937
|
resetChatRenderCache();
|
|
@@ -3703,6 +4007,7 @@
|
|
|
3703
4007
|
modal.classList.remove("hidden");
|
|
3704
4008
|
lastFocusedElement = document.activeElement;
|
|
3705
4009
|
state.sessionTool = getPreferredTool();
|
|
4010
|
+
state.sessionCreateKind = "pty";
|
|
3706
4011
|
state.modeValue = getSafeModeForTool(state.sessionTool, state.modeValue || state.chatMode);
|
|
3707
4012
|
syncSessionModalUI();
|
|
3708
4013
|
loadRecentPathBubbles();
|
|
@@ -3903,8 +4208,8 @@
|
|
|
3903
4208
|
if (modeEl) modeEl.value = cfg.defaultMode || "default";
|
|
3904
4209
|
if (cwdEl) cwdEl.value = cfg.defaultCwd || "";
|
|
3905
4210
|
if (shellEl) shellEl.value = cfg.shell || "";
|
|
3906
|
-
var
|
|
3907
|
-
if (
|
|
4211
|
+
var langEl = document.getElementById("cfg-language");
|
|
4212
|
+
if (langEl) langEl.value = cfg.language || "";
|
|
3908
4213
|
|
|
3909
4214
|
// Cert status
|
|
3910
4215
|
var certStatus = document.getElementById("cert-status");
|
|
@@ -3942,7 +4247,7 @@
|
|
|
3942
4247
|
defaultMode: (document.getElementById("cfg-mode") || {}).value,
|
|
3943
4248
|
defaultCwd: (document.getElementById("cfg-cwd") || {}).value,
|
|
3944
4249
|
shell: (document.getElementById("cfg-shell") || {}).value,
|
|
3945
|
-
|
|
4250
|
+
language: (document.getElementById("cfg-language") || {}).value || "",
|
|
3946
4251
|
};
|
|
3947
4252
|
|
|
3948
4253
|
fetch("/api/settings/config", {
|
|
@@ -4230,13 +4535,45 @@
|
|
|
4230
4535
|
var cwdEl = document.getElementById("cwd");
|
|
4231
4536
|
var errorEl = document.getElementById("modal-error");
|
|
4232
4537
|
var command = getPreferredTool();
|
|
4538
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
4233
4539
|
|
|
4234
4540
|
hideError(errorEl);
|
|
4235
4541
|
|
|
4236
4542
|
var defaultCwd = getEffectiveCwd();
|
|
4543
|
+
var cwd = cwdEl.value.trim() || defaultCwd;
|
|
4237
4544
|
var selectedMode = getSafeModeForTool(command, state.modeValue);
|
|
4238
|
-
|
|
4239
|
-
|
|
4545
|
+
|
|
4546
|
+
if (sessionKind === "structured") {
|
|
4547
|
+
startStructuredSessionFromModal(cwd, selectedMode, errorEl);
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
runPtyCommandFromModal(command, cwd, selectedMode, errorEl);
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4554
|
+
function startStructuredSessionFromModal(cwd, mode, errorEl) {
|
|
4555
|
+
console.log("[WAND] startStructuredSessionFromModal cwd:", cwd, "mode:", mode);
|
|
4556
|
+
state.modeValue = mode;
|
|
4557
|
+
state.chatMode = mode;
|
|
4558
|
+
state.sessionTool = "claude";
|
|
4559
|
+
state.preferredCommand = "claude";
|
|
4560
|
+
syncComposerModeSelect();
|
|
4561
|
+
return createStructuredSession(undefined, cwd, mode)
|
|
4562
|
+
.then(function(data) {
|
|
4563
|
+
closeSessionModal();
|
|
4564
|
+
closeSessionsDrawer();
|
|
4565
|
+
return data;
|
|
4566
|
+
})
|
|
4567
|
+
.then(function() { focusInputBox(true); })
|
|
4568
|
+
.catch(function(error) {
|
|
4569
|
+
showError(errorEl, (error && error.message) || "无法启动结构化会话,请确认 Claude 已正确安装。");
|
|
4570
|
+
});
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
function runPtyCommandFromModal(command, cwd, mode, errorEl) {
|
|
4574
|
+
console.log("[WAND] runPtyCommandFromModal command:", command, "cwd:", cwd, "mode:", mode);
|
|
4575
|
+
state.modeValue = mode;
|
|
4576
|
+
state.chatMode = mode;
|
|
4240
4577
|
state.sessionTool = command;
|
|
4241
4578
|
state.preferredCommand = command;
|
|
4242
4579
|
syncComposerModeSelect();
|
|
@@ -4247,8 +4584,8 @@
|
|
|
4247
4584
|
credentials: "same-origin",
|
|
4248
4585
|
body: JSON.stringify({
|
|
4249
4586
|
command: command,
|
|
4250
|
-
cwd:
|
|
4251
|
-
mode:
|
|
4587
|
+
cwd: cwd,
|
|
4588
|
+
mode: mode
|
|
4252
4589
|
})
|
|
4253
4590
|
})
|
|
4254
4591
|
.then(function(res) { return res.json(); })
|
|
@@ -4258,6 +4595,7 @@
|
|
|
4258
4595
|
return;
|
|
4259
4596
|
}
|
|
4260
4597
|
state.selectedId = data.id;
|
|
4598
|
+
console.log("[WAND] runPtyCommandFromModal created session:", data.id, "sessionKind:", data.sessionKind, "runner:", data.runner);
|
|
4261
4599
|
persistSelectedId();
|
|
4262
4600
|
state.drafts[data.id] = "";
|
|
4263
4601
|
resetChatRenderCache();
|
|
@@ -4265,13 +4603,19 @@
|
|
|
4265
4603
|
closeSessionsDrawer();
|
|
4266
4604
|
return refreshAll();
|
|
4267
4605
|
})
|
|
4268
|
-
.then(function() {
|
|
4606
|
+
.then(function() {
|
|
4607
|
+
if (state.selectedId) {
|
|
4608
|
+
console.log("[WAND] runPtyCommandFromModal calling selectSession:", state.selectedId);
|
|
4609
|
+
selectSession(state.selectedId);
|
|
4610
|
+
} else {
|
|
4611
|
+
focusInputBox(true);
|
|
4612
|
+
}
|
|
4613
|
+
})
|
|
4269
4614
|
.catch(function() {
|
|
4270
4615
|
showError(errorEl, "无法启动会话,请确认 Claude 已正确安装。");
|
|
4271
4616
|
});
|
|
4272
4617
|
}
|
|
4273
4618
|
|
|
4274
|
-
// Blank-chat CWD inline display + dropdown
|
|
4275
4619
|
function initBlankChatCwd() {
|
|
4276
4620
|
var cwdEl = document.getElementById("blank-chat-cwd");
|
|
4277
4621
|
if (!cwdEl) return;
|
|
@@ -4691,11 +5035,13 @@
|
|
|
4691
5035
|
persistSelectedId();
|
|
4692
5036
|
state.drafts[data.id] = "";
|
|
4693
5037
|
resetChatRenderCache();
|
|
4694
|
-
switchToSessionView(data.id);
|
|
4695
5038
|
updateSessionSnapshot(data);
|
|
4696
5039
|
updateSessionsList();
|
|
5040
|
+
switchToSessionView(data.id);
|
|
4697
5041
|
subscribeToSession(data.id);
|
|
4698
5042
|
loadOutput(data.id).then(function() {
|
|
5043
|
+
welcomeInput.placeholder = "输入你的问题,按 Enter 发送...";
|
|
5044
|
+
welcomeInput.disabled = false;
|
|
4699
5045
|
focusInputBox(true);
|
|
4700
5046
|
});
|
|
4701
5047
|
})
|
|
@@ -4749,9 +5095,9 @@
|
|
|
4749
5095
|
resetChatRenderCache();
|
|
4750
5096
|
if (inputBox) inputBox.value = "";
|
|
4751
5097
|
if (welcomeInput) welcomeInput.value = "";
|
|
4752
|
-
switchToSessionView(data.id);
|
|
4753
5098
|
updateSessionSnapshot(data);
|
|
4754
5099
|
updateSessionsList();
|
|
5100
|
+
switchToSessionView(data.id);
|
|
4755
5101
|
// Subscribe to new session via WebSocket
|
|
4756
5102
|
subscribeToSession(data.id);
|
|
4757
5103
|
return loadOutput(data.id);
|
|
@@ -4763,6 +5109,7 @@
|
|
|
4763
5109
|
|
|
4764
5110
|
function switchToSessionView(sessionId) {
|
|
4765
5111
|
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
5112
|
+
console.log("[WAND] switchToSessionView id:", sessionId, "found:", !!session, "sessionKind:", session && session.sessionKind, "runner:", session && session.runner, "isStructured:", isStructuredSession(session), "currentView:", state.currentView);
|
|
4766
5113
|
var blankChat = document.getElementById("blank-chat");
|
|
4767
5114
|
var terminalContainer = document.getElementById("output");
|
|
4768
5115
|
var chatContainer = document.getElementById("chat-output");
|
|
@@ -4770,28 +5117,36 @@
|
|
|
4770
5117
|
var terminalTitle = document.getElementById("terminal-title");
|
|
4771
5118
|
var terminalInfo = document.getElementById("terminal-info");
|
|
4772
5119
|
var sessionSummary = document.querySelector(".session-summary-value");
|
|
5120
|
+
var structured = isStructuredSession(session);
|
|
4773
5121
|
|
|
4774
5122
|
if (blankChat) blankChat.classList.add("hidden");
|
|
4775
|
-
if (terminalContainer)
|
|
5123
|
+
if (terminalContainer) {
|
|
5124
|
+
terminalContainer.classList.toggle("hidden", structured);
|
|
5125
|
+
}
|
|
4776
5126
|
if (chatContainer) {
|
|
4777
5127
|
chatContainer.classList.remove("hidden");
|
|
4778
5128
|
}
|
|
4779
5129
|
if (stopBtn) stopBtn.classList.remove("hidden");
|
|
4780
5130
|
|
|
5131
|
+
if (structured) {
|
|
5132
|
+
state.currentView = "chat";
|
|
5133
|
+
} else {
|
|
5134
|
+
state.currentView = "terminal";
|
|
5135
|
+
}
|
|
5136
|
+
|
|
4781
5137
|
var title = session ? shortCommand(session.command) : "Wand";
|
|
4782
5138
|
var info = session ? getSessionStatusLabel(session) : "开始对话";
|
|
4783
5139
|
if (terminalTitle) terminalTitle.textContent = title;
|
|
4784
5140
|
if (terminalInfo) terminalInfo.textContent = info;
|
|
4785
5141
|
if (sessionSummary) sessionSummary.textContent = title;
|
|
4786
5142
|
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
5143
|
+
if (!structured) {
|
|
5144
|
+
if (!state.terminal) initTerminal();
|
|
5145
|
+
if (state.terminal && state.fitAddon) {
|
|
5146
|
+
ensureTerminalFit();
|
|
5147
|
+
}
|
|
4792
5148
|
}
|
|
4793
|
-
|
|
4794
|
-
// Calling renderChat() prematurely would render with stale/empty messages.
|
|
5149
|
+
applyCurrentView();
|
|
4795
5150
|
focusInputBox();
|
|
4796
5151
|
}
|
|
4797
5152
|
|
|
@@ -4806,9 +5161,12 @@
|
|
|
4806
5161
|
var value = inputBox ? inputBox.value : "";
|
|
4807
5162
|
var selectedSession = state.sessions.find(function(session) { return session.id === state.selectedId; }) || null;
|
|
4808
5163
|
if (value) {
|
|
4809
|
-
console.log("[
|
|
5164
|
+
console.log("[WAND] sendInputFromBox", {
|
|
4810
5165
|
sessionId: state.selectedId,
|
|
4811
5166
|
sessionStatus: selectedSession ? selectedSession.status : null,
|
|
5167
|
+
sessionKind: selectedSession ? selectedSession.sessionKind : null,
|
|
5168
|
+
runner: selectedSession ? selectedSession.runner : null,
|
|
5169
|
+
isStructured: isStructuredSession(selectedSession),
|
|
4812
5170
|
view: state.currentView,
|
|
4813
5171
|
wsConnected: state.wsConnected,
|
|
4814
5172
|
terminalInteractive: state.terminalInteractive,
|
|
@@ -4817,6 +5175,11 @@
|
|
|
4817
5175
|
// Clear todo progress bar at the start of a new user turn
|
|
4818
5176
|
var todoEl = document.getElementById("todo-progress");
|
|
4819
5177
|
if (todoEl) todoEl.classList.add("hidden");
|
|
5178
|
+
|
|
5179
|
+
if (isStructuredSession(selectedSession)) {
|
|
5180
|
+
return postStructuredInput(value, inputBox, selectedSession);
|
|
5181
|
+
}
|
|
5182
|
+
|
|
4820
5183
|
// Send text + Enter as a single call to avoid race conditions
|
|
4821
5184
|
var combinedInput = value + getControlInput("enter");
|
|
4822
5185
|
var isOffline = !state.wsConnected;
|
|
@@ -4857,6 +5220,79 @@
|
|
|
4857
5220
|
return Promise.resolve();
|
|
4858
5221
|
}
|
|
4859
5222
|
|
|
5223
|
+
function postStructuredInput(input, inputBox, session) {
|
|
5224
|
+
console.log("[WAND] postStructuredInput selectedId:", state.selectedId, "input:", input && input.substring(0, 50), "session:", session && { id: session.id, sessionKind: session.sessionKind, runner: session.runner, status: session.status, inFlight: session.structuredState && session.structuredState.inFlight });
|
|
5225
|
+
if (!state.selectedId || !input) return Promise.resolve();
|
|
5226
|
+
if (!session) {
|
|
5227
|
+
showToast("会话不存在,请重新选择或新建会话。", "error");
|
|
5228
|
+
return Promise.resolve();
|
|
5229
|
+
}
|
|
5230
|
+
if (session.structuredState && session.structuredState.inFlight && session.status === "running") {
|
|
5231
|
+
// Disable send button while processing, show subtle indicator
|
|
5232
|
+
var sendBtn = document.getElementById("send-input-button");
|
|
5233
|
+
if (sendBtn) sendBtn.disabled = true;
|
|
5234
|
+
showToast("正在等待上一条消息处理完成…", "info");
|
|
5235
|
+
return Promise.resolve();
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
// Immediately render user message with thinking indicator
|
|
5239
|
+
var userTurn = { role: "user", content: [{ type: "text", text: input }] };
|
|
5240
|
+
var thinkingTurn = { role: "assistant", content: [{ type: "text", text: "", __processing: true }] };
|
|
5241
|
+
var userMsgs = Array.isArray(session.messages) ? session.messages.slice() : [];
|
|
5242
|
+
userMsgs.push(userTurn);
|
|
5243
|
+
userMsgs.push(thinkingTurn);
|
|
5244
|
+
session.messages = userMsgs;
|
|
5245
|
+
state.currentMessages = userMsgs;
|
|
5246
|
+
// Mark inFlight optimistically to prevent double-send via WS updates
|
|
5247
|
+
if (session.structuredState) {
|
|
5248
|
+
session.structuredState.inFlight = true;
|
|
5249
|
+
}
|
|
5250
|
+
session.status = "running";
|
|
5251
|
+
if (inputBox) {
|
|
5252
|
+
inputBox.value = "";
|
|
5253
|
+
autoResizeInput(inputBox);
|
|
5254
|
+
}
|
|
5255
|
+
// Disable send button so user can't double-send
|
|
5256
|
+
var sendBtnEl = document.getElementById("send-input-button");
|
|
5257
|
+
if (sendBtnEl) sendBtnEl.disabled = true;
|
|
5258
|
+
updateInputHint("思考中…");
|
|
5259
|
+
setDraftValue("");
|
|
5260
|
+
renderChat(true);
|
|
5261
|
+
|
|
5262
|
+
return fetch("/api/structured-sessions/" + state.selectedId + "/messages", {
|
|
5263
|
+
method: "POST",
|
|
5264
|
+
headers: { "Content-Type": "application/json" },
|
|
5265
|
+
credentials: "same-origin",
|
|
5266
|
+
body: JSON.stringify({ input: input })
|
|
5267
|
+
})
|
|
5268
|
+
.then(function(res) { return res.json(); })
|
|
5269
|
+
.then(function(snapshot) {
|
|
5270
|
+
if (snapshot && snapshot.error) {
|
|
5271
|
+
throw new Error(snapshot.error);
|
|
5272
|
+
}
|
|
5273
|
+
if (snapshot && snapshot.id) {
|
|
5274
|
+
updateSessionSnapshot(snapshot);
|
|
5275
|
+
if (snapshot.messages && snapshot.messages.length > 0) {
|
|
5276
|
+
state.currentMessages = snapshot.messages;
|
|
5277
|
+
}
|
|
5278
|
+
renderChat(true);
|
|
5279
|
+
updateInputHint("Enter 发送 · Shift+Enter 换行");
|
|
5280
|
+
}
|
|
5281
|
+
})
|
|
5282
|
+
.catch(function(error) {
|
|
5283
|
+
var errSendBtn = document.getElementById("send-input-button");
|
|
5284
|
+
if (errSendBtn) errSendBtn.disabled = false;
|
|
5285
|
+
updateInputHint("Enter 发送 · Shift+Enter 换行");
|
|
5286
|
+
showToast((error && error.message) || "无法发送结构化消息。", "error");
|
|
5287
|
+
throw error;
|
|
5288
|
+
});
|
|
5289
|
+
}
|
|
5290
|
+
|
|
5291
|
+
function updateInputHint(text) {
|
|
5292
|
+
var hint = document.querySelector(".input-hint");
|
|
5293
|
+
if (hint) hint.textContent = text;
|
|
5294
|
+
}
|
|
5295
|
+
|
|
4860
5296
|
function getInputErrorMessage(error) {
|
|
4861
5297
|
if (error && (error.errorCode === "SESSION_NOT_RUNNING" || error.errorCode === "SESSION_NO_PTY")) {
|
|
4862
5298
|
return "会话已结束;若存在 Claude 历史会话,将在你下次发送消息时自动恢复。";
|
|
@@ -4908,6 +5344,7 @@
|
|
|
4908
5344
|
}
|
|
4909
5345
|
|
|
4910
5346
|
function ensureSessionReadyForInput(session, errorEl) {
|
|
5347
|
+
console.log("[WAND] ensureSessionReadyForInput session:", session && { id: session.id, status: session.status, claudeSessionId: session.claudeSessionId, sessionKind: session.sessionKind, runner: session.runner });
|
|
4911
5348
|
if (!session) {
|
|
4912
5349
|
showToast("会话不存在,请重新选择或新建会话。", "error");
|
|
4913
5350
|
return Promise.resolve(null);
|
|
@@ -5203,21 +5640,24 @@
|
|
|
5203
5640
|
}
|
|
5204
5641
|
|
|
5205
5642
|
function updateInteractiveControls() {
|
|
5643
|
+
var selectedSession = state.sessions.find(function(session) { return session.id === state.selectedId; });
|
|
5644
|
+
var structured = isStructuredSession(selectedSession);
|
|
5206
5645
|
// Update both toggle buttons (topbar and terminal-header)
|
|
5207
5646
|
var toggles = ["terminal-interactive-toggle-top"];
|
|
5208
5647
|
toggles.forEach(function(id) {
|
|
5209
5648
|
var toggle = document.getElementById(id);
|
|
5210
5649
|
if (toggle) {
|
|
5211
5650
|
toggle.classList.toggle("active", state.terminalInteractive);
|
|
5651
|
+
toggle.classList.toggle("hidden", structured || state.currentView !== "terminal" || !selectedSession);
|
|
5212
5652
|
}
|
|
5213
5653
|
});
|
|
5214
5654
|
// Inline keyboard visibility follows current view
|
|
5215
5655
|
var inlineKeyboard = document.getElementById("inline-keyboard");
|
|
5216
|
-
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", state.currentView !== "terminal");
|
|
5656
|
+
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", structured || state.currentView !== "terminal");
|
|
5217
5657
|
var inputHint = document.querySelector(".input-hint");
|
|
5218
|
-
if (inputHint) inputHint.classList.toggle("hidden", state.currentView === "terminal");
|
|
5658
|
+
if (inputHint) inputHint.classList.toggle("hidden", structured ? false : state.currentView === "terminal");
|
|
5219
5659
|
var container = document.getElementById("output");
|
|
5220
|
-
if (container) container.classList.toggle("interactive", state.terminalInteractive);
|
|
5660
|
+
if (container) container.classList.toggle("interactive", !structured && state.terminalInteractive);
|
|
5221
5661
|
}
|
|
5222
5662
|
|
|
5223
5663
|
function captureTerminalInput(event) {
|
|
@@ -5573,6 +6013,7 @@
|
|
|
5573
6013
|
}
|
|
5574
6014
|
|
|
5575
6015
|
function resumeSession(sessionId, errorEl) {
|
|
6016
|
+
console.log("[WAND] resumeSession sessionId:", sessionId);
|
|
5576
6017
|
if (!sessionId) return Promise.resolve(null);
|
|
5577
6018
|
return fetch("/api/sessions/" + encodeURIComponent(sessionId) + "/resume", {
|
|
5578
6019
|
method: "POST",
|
|
@@ -5645,6 +6086,7 @@
|
|
|
5645
6086
|
}
|
|
5646
6087
|
|
|
5647
6088
|
function resumeSessionFromList(sessionId) {
|
|
6089
|
+
console.log("[WAND] resumeSessionFromList sessionId:", sessionId);
|
|
5648
6090
|
return resumeSession(sessionId).then(function(data) {
|
|
5649
6091
|
if (!data) return null;
|
|
5650
6092
|
return activateSession(data).then(function() {
|
|
@@ -5733,6 +6175,7 @@
|
|
|
5733
6175
|
}
|
|
5734
6176
|
|
|
5735
6177
|
function handleResumeAction(actionButton) {
|
|
6178
|
+
console.log("[WAND] handleResumeAction sessionId:", actionButton.dataset.sessionId);
|
|
5736
6179
|
actionButton.disabled = true;
|
|
5737
6180
|
resumeSessionFromList(actionButton.dataset.sessionId)
|
|
5738
6181
|
.finally(function() {
|
|
@@ -5743,6 +6186,7 @@
|
|
|
5743
6186
|
function handleResumeHistoryAction(actionButton) {
|
|
5744
6187
|
var claudeSessionId = actionButton.dataset.claudeSessionId;
|
|
5745
6188
|
var cwd = actionButton.dataset.cwd;
|
|
6189
|
+
console.log("[WAND] handleResumeHistoryAction claudeSessionId:", claudeSessionId, "cwd:", cwd);
|
|
5746
6190
|
if (!claudeSessionId) return;
|
|
5747
6191
|
actionButton.disabled = true;
|
|
5748
6192
|
resumeClaudeHistorySession(claudeSessionId, cwd)
|
|
@@ -6788,15 +7232,18 @@
|
|
|
6788
7232
|
state.fitAddon.fit();
|
|
6789
7233
|
maybeScrollTerminalToBottom("resize");
|
|
6790
7234
|
if (state.selectedId && state.terminal) {
|
|
6791
|
-
var
|
|
6792
|
-
if (
|
|
6793
|
-
state.
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
7235
|
+
var selectedSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7236
|
+
if (!isStructuredSession(selectedSess)) {
|
|
7237
|
+
var nextSize = { cols: state.terminal.cols, rows: state.terminal.rows };
|
|
7238
|
+
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
7239
|
+
state.lastResize = nextSize;
|
|
7240
|
+
fetch("/api/sessions/" + state.selectedId + "/resize", {
|
|
7241
|
+
method: "POST",
|
|
7242
|
+
headers: { "Content-Type": "application/json" },
|
|
7243
|
+
credentials: "same-origin",
|
|
7244
|
+
body: JSON.stringify(nextSize)
|
|
7245
|
+
}).catch(function() {});
|
|
7246
|
+
}
|
|
6800
7247
|
}
|
|
6801
7248
|
}
|
|
6802
7249
|
} else if (attempt < maxAttempts) {
|
|
@@ -6836,6 +7283,10 @@
|
|
|
6836
7283
|
|
|
6837
7284
|
if (!state.selectedId) return;
|
|
6838
7285
|
|
|
7286
|
+
// Skip resize for structured sessions (no PTY)
|
|
7287
|
+
var resizeSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7288
|
+
if (isStructuredSession(resizeSess)) return;
|
|
7289
|
+
|
|
6839
7290
|
// Only send resize API call if dimensions actually changed
|
|
6840
7291
|
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
6841
7292
|
state.lastResize = nextSize;
|
|
@@ -6918,7 +7369,8 @@
|
|
|
6918
7369
|
switch (msg.type) {
|
|
6919
7370
|
case 'output':
|
|
6920
7371
|
// Update session output (for terminal display and local message parsing)
|
|
6921
|
-
|
|
7372
|
+
// NOTE: For structured sessions, output may be "" during streaming — check messages too
|
|
7373
|
+
if (msg.data && (msg.data.output || msg.data.messages) && msg.sessionId) {
|
|
6922
7374
|
var snapshot = { id: msg.sessionId, output: msg.data.output };
|
|
6923
7375
|
if (Object.prototype.hasOwnProperty.call(msg.data, 'permissionBlocked')) {
|
|
6924
7376
|
snapshot.permissionBlocked = !!msg.data.permissionBlocked;
|
|
@@ -6930,8 +7382,24 @@
|
|
|
6930
7382
|
updateSessionSnapshot(snapshot);
|
|
6931
7383
|
if (msg.sessionId === state.selectedId) {
|
|
6932
7384
|
state.currentMessages = getPreferredMessages(snapshot, msg.data.output, false);
|
|
7385
|
+
// Structured session with inFlight: keep __processing placeholder
|
|
7386
|
+
// so the loading indicator stays visible until assistant content arrives
|
|
7387
|
+
if (msg.data.sessionKind === 'structured') {
|
|
7388
|
+
var outSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
7389
|
+
if (outSession && outSession.structuredState && outSession.structuredState.inFlight) {
|
|
7390
|
+
var lastCur = state.currentMessages[state.currentMessages.length - 1];
|
|
7391
|
+
if (!lastCur || lastCur.role !== 'assistant') {
|
|
7392
|
+
state.currentMessages.push({ role: "assistant", content: [{ type: "text", text: "", __processing: true }] });
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
6933
7396
|
updateTaskDisplay();
|
|
6934
|
-
|
|
7397
|
+
// Structured sessions: render immediately for responsiveness
|
|
7398
|
+
if (msg.data.sessionKind === 'structured') {
|
|
7399
|
+
renderChat();
|
|
7400
|
+
} else {
|
|
7401
|
+
scheduleChatRender();
|
|
7402
|
+
}
|
|
6935
7403
|
}
|
|
6936
7404
|
|
|
6937
7405
|
}
|
|
@@ -6968,8 +7436,19 @@
|
|
|
6968
7436
|
if (msg.data && msg.data.messages) {
|
|
6969
7437
|
endedSnapshot.messages = msg.data.messages;
|
|
6970
7438
|
}
|
|
7439
|
+
if (msg.data && msg.data.structuredState) {
|
|
7440
|
+
endedSnapshot.structuredState = msg.data.structuredState;
|
|
7441
|
+
}
|
|
6971
7442
|
updateSessionSnapshot(endedSnapshot);
|
|
6972
7443
|
|
|
7444
|
+
// Re-enable send button when structured session finishes
|
|
7445
|
+
if (msg.sessionId === state.selectedId) {
|
|
7446
|
+
var endedSendBtn = document.getElementById("send-input-button");
|
|
7447
|
+
if (endedSendBtn) endedSendBtn.disabled = false;
|
|
7448
|
+
updateInputHint("Enter 发送 · Shift+Enter 换行");
|
|
7449
|
+
// Trigger status bar completion animation
|
|
7450
|
+
scheduleChatRender(true);
|
|
7451
|
+
}
|
|
6973
7452
|
// Notify user when a session completes (browser + in-app if backgrounded or not viewing)
|
|
6974
7453
|
var endedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
6975
7454
|
var endedName = endedSession ? (endedSession.label || endedSession.command || msg.sessionId) : msg.sessionId;
|
|
@@ -7044,7 +7523,24 @@
|
|
|
7044
7523
|
break;
|
|
7045
7524
|
case 'status':
|
|
7046
7525
|
if (msg.sessionId && msg.data) {
|
|
7526
|
+
console.log('[WAND] ws status', msg.sessionId, JSON.stringify(msg.data));
|
|
7047
7527
|
var statusUpdate = { id: msg.sessionId };
|
|
7528
|
+
if (Object.prototype.hasOwnProperty.call(msg.data, 'status')) {
|
|
7529
|
+
statusUpdate.status = msg.data.status;
|
|
7530
|
+
}
|
|
7531
|
+
if (Object.prototype.hasOwnProperty.call(msg.data, 'exitCode')) {
|
|
7532
|
+
statusUpdate.exitCode = msg.data.exitCode;
|
|
7533
|
+
}
|
|
7534
|
+
if (msg.data.structuredState) {
|
|
7535
|
+
statusUpdate.structuredState = msg.data.structuredState;
|
|
7536
|
+
} else if (Object.prototype.hasOwnProperty.call(msg.data, 'status')) {
|
|
7537
|
+
var existingSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
7538
|
+
if (existingSession && existingSession.sessionKind === 'structured') {
|
|
7539
|
+
statusUpdate.structuredState = Object.assign({}, existingSession.structuredState || {}, {
|
|
7540
|
+
inFlight: msg.data.status === 'running'
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7048
7544
|
if (Object.prototype.hasOwnProperty.call(msg.data, 'permissionBlocked')) {
|
|
7049
7545
|
statusUpdate.permissionBlocked = !!msg.data.permissionBlocked;
|
|
7050
7546
|
}
|
|
@@ -7096,6 +7592,10 @@
|
|
|
7096
7592
|
if (msg.data.approvalStats) {
|
|
7097
7593
|
updateApprovalStats();
|
|
7098
7594
|
}
|
|
7595
|
+
// Re-render chat when structured session inFlight state changes
|
|
7596
|
+
if (statusUpdate.structuredState) {
|
|
7597
|
+
scheduleChatRender();
|
|
7598
|
+
}
|
|
7099
7599
|
}
|
|
7100
7600
|
}
|
|
7101
7601
|
break;
|
|
@@ -7135,9 +7635,12 @@
|
|
|
7135
7635
|
var isBlocked = pendingEscalation || (selectedSession && selectedSession.permissionBlocked);
|
|
7136
7636
|
|
|
7137
7637
|
if (isBlocked) {
|
|
7638
|
+
var isAutoApprove = selectedSession && selectedSession.autoApprovePermissions;
|
|
7138
7639
|
// Show permission label in input composer area
|
|
7139
7640
|
if (permissionLabel) {
|
|
7140
|
-
if (
|
|
7641
|
+
if (isAutoApprove) {
|
|
7642
|
+
permissionLabel.textContent = "自动批准中...";
|
|
7643
|
+
} else if (pendingEscalation) {
|
|
7141
7644
|
var reason = pendingEscalation.reason || "等待授权";
|
|
7142
7645
|
var target = pendingEscalation.target ? " · " + pendingEscalation.target : "";
|
|
7143
7646
|
permissionLabel.textContent = reason + target;
|
|
@@ -7145,7 +7648,14 @@
|
|
|
7145
7648
|
permissionLabel.textContent = "等待授权";
|
|
7146
7649
|
}
|
|
7147
7650
|
}
|
|
7148
|
-
if (permissionActionsEl)
|
|
7651
|
+
if (permissionActionsEl) {
|
|
7652
|
+
permissionActionsEl.classList.remove("hidden");
|
|
7653
|
+
// Hide approve/deny buttons when auto-approve is active
|
|
7654
|
+
var approveBtn = document.getElementById("approve-permission-btn");
|
|
7655
|
+
var denyBtn = document.getElementById("deny-permission-btn");
|
|
7656
|
+
if (approveBtn) approveBtn.classList.toggle("hidden", !!isAutoApprove);
|
|
7657
|
+
if (denyBtn) denyBtn.classList.toggle("hidden", !!isAutoApprove);
|
|
7658
|
+
}
|
|
7149
7659
|
// Hide top task bar — permission info is already shown in the composer
|
|
7150
7660
|
taskEl.textContent = "";
|
|
7151
7661
|
taskEl.classList.add("hidden");
|
|
@@ -7254,6 +7764,49 @@
|
|
|
7254
7764
|
});
|
|
7255
7765
|
}
|
|
7256
7766
|
|
|
7767
|
+
function toggleAutoApprove() {
|
|
7768
|
+
if (!state.selectedId) return;
|
|
7769
|
+
var toggle = document.getElementById("auto-approve-toggle");
|
|
7770
|
+
if (toggle) toggle.style.opacity = "0.5";
|
|
7771
|
+
fetch("/api/sessions/" + encodeURIComponent(state.selectedId) + "/toggle-auto-approve", {
|
|
7772
|
+
method: "POST",
|
|
7773
|
+
credentials: "same-origin"
|
|
7774
|
+
})
|
|
7775
|
+
.then(function(res) { return res.json(); })
|
|
7776
|
+
.then(function(data) {
|
|
7777
|
+
if (data && data.error) {
|
|
7778
|
+
showToast(data.error, "error");
|
|
7779
|
+
return;
|
|
7780
|
+
}
|
|
7781
|
+
updateSessionSnapshot(data);
|
|
7782
|
+
updateAutoApproveIndicator();
|
|
7783
|
+
var enabled = data.autoApprovePermissions;
|
|
7784
|
+
showToast(enabled ? "自动批准已开启" : "自动批准已关闭", "info");
|
|
7785
|
+
})
|
|
7786
|
+
.catch(function(error) {
|
|
7787
|
+
showToast((error && error.message) || "无法切换自动批准。", "error");
|
|
7788
|
+
})
|
|
7789
|
+
.finally(function() {
|
|
7790
|
+
if (toggle) toggle.style.opacity = "";
|
|
7791
|
+
});
|
|
7792
|
+
}
|
|
7793
|
+
|
|
7794
|
+
function updateAutoApproveIndicator() {
|
|
7795
|
+
var toggle = document.getElementById("auto-approve-toggle");
|
|
7796
|
+
if (!toggle) return;
|
|
7797
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7798
|
+
var enabled = selectedSession && selectedSession.autoApprovePermissions;
|
|
7799
|
+
if (enabled) {
|
|
7800
|
+
toggle.className = "auto-approve-indicator active";
|
|
7801
|
+
toggle.title = "自动批准已启用 — 点击关闭";
|
|
7802
|
+
toggle.textContent = "🛡 自动批准";
|
|
7803
|
+
} else {
|
|
7804
|
+
toggle.className = "auto-approve-indicator";
|
|
7805
|
+
toggle.title = "自动批准已关闭 — 点击开启";
|
|
7806
|
+
toggle.textContent = "🛡 手动";
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
|
|
7257
7810
|
function updateTerminalOutput(output, sessionId, mode) {
|
|
7258
7811
|
if (!state.terminal) return false;
|
|
7259
7812
|
return syncTerminalBuffer(sessionId || state.selectedId, output, { mode: mode || "append" });
|
|
@@ -7462,6 +8015,10 @@
|
|
|
7462
8015
|
// Force full render if message count changed or explicitly requested
|
|
7463
8016
|
var forceRender = forceFullRender || msgCount !== state.lastRenderedMsgCount;
|
|
7464
8017
|
if (!forceRender && msgCount === state.lastRenderedMsgCount && outputHash === state.lastRenderedHash) {
|
|
8018
|
+
// Even if message content hasn't changed, update the status bar
|
|
8019
|
+
// (inFlight state may have changed without new message content)
|
|
8020
|
+
var chatMessages = chatOutput.querySelector(".chat-messages");
|
|
8021
|
+
if (chatMessages) renderStructuredStatusBar(chatMessages, selectedSession);
|
|
7465
8022
|
return;
|
|
7466
8023
|
}
|
|
7467
8024
|
var prevHash = state.lastRenderedHash;
|
|
@@ -7615,6 +8172,9 @@
|
|
|
7615
8172
|
fullRenderChat();
|
|
7616
8173
|
}
|
|
7617
8174
|
|
|
8175
|
+
// Update structured session status bar (in-flight / completed indicator)
|
|
8176
|
+
renderStructuredStatusBar(chatMessages, selectedSession);
|
|
8177
|
+
|
|
7618
8178
|
// Update todo progress bar from latest messages
|
|
7619
8179
|
updateTodoProgress(messages);
|
|
7620
8180
|
}
|
|
@@ -7904,10 +8464,8 @@
|
|
|
7904
8464
|
// ===== Terminal copy button for mobile =====
|
|
7905
8465
|
// ===== Mobile DOM terminal view =====
|
|
7906
8466
|
function initMobileDomTerminal(container) {
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
// Gated by experimental config flag
|
|
7910
|
-
if (!state.config || !state.config.experimentalDomTerminal) return;
|
|
8467
|
+
// DOM terminal feature removed — always return
|
|
8468
|
+
return;
|
|
7911
8469
|
|
|
7912
8470
|
// Create DOM view container
|
|
7913
8471
|
var domView = document.createElement("div");
|
|
@@ -8306,7 +8864,7 @@
|
|
|
8306
8864
|
if (msg.role === "thinking") {
|
|
8307
8865
|
return '<div class="chat-message thinking">' +
|
|
8308
8866
|
'<div class="thinking-inline thinking-pty collapsed" data-thinking="" onclick="__thinkingToggle(this)">' +
|
|
8309
|
-
'<span class="thinking-inline-icon"
|
|
8867
|
+
'<span class="thinking-inline-icon">⦿</span>' +
|
|
8310
8868
|
'<span class="thinking-inline-preview">' + escapeHtml(msg.content) + '</span>' +
|
|
8311
8869
|
'<span class="thinking-inline-action">展开</span>' +
|
|
8312
8870
|
'</div>' +
|
|
@@ -8317,7 +8875,7 @@
|
|
|
8317
8875
|
if (msg.role === "prompt") {
|
|
8318
8876
|
return '<div class="chat-message prompt">' +
|
|
8319
8877
|
'<div class="prompt-card">' +
|
|
8320
|
-
'<div class="prompt-icon"
|
|
8878
|
+
'<div class="prompt-icon">→</div>' +
|
|
8321
8879
|
'<div class="prompt-content">试试:<span class="prompt-text">' + escapeHtml(msg.content) + '</span></div>' +
|
|
8322
8880
|
'</div>' +
|
|
8323
8881
|
'</div>';
|
|
@@ -8329,7 +8887,7 @@
|
|
|
8329
8887
|
}
|
|
8330
8888
|
|
|
8331
8889
|
// Legacy string content (from PTY parsing)
|
|
8332
|
-
var avatar = msg.role === "assistant" ? '<div class="chat-message-avatar"
|
|
8890
|
+
var avatar = msg.role === "assistant" ? '<div class="chat-message-avatar">赛博虎妞</div>' : "";
|
|
8333
8891
|
var bubbleContent = msg.role === "assistant" ? renderMarkdown(msg.content) : escapeHtml(msg.content);
|
|
8334
8892
|
return '<div class="chat-message ' + msg.role + '">' +
|
|
8335
8893
|
avatar +
|
|
@@ -8337,11 +8895,53 @@
|
|
|
8337
8895
|
'</div>';
|
|
8338
8896
|
}
|
|
8339
8897
|
|
|
8898
|
+
function buildToolResultMap(contentBlocks) {
|
|
8899
|
+
var toolResults = {};
|
|
8900
|
+
if (!Array.isArray(contentBlocks)) return toolResults;
|
|
8901
|
+
for (var i = 0; i < contentBlocks.length; i++) {
|
|
8902
|
+
var block = contentBlocks[i];
|
|
8903
|
+
if (block && block.type === "tool_result") {
|
|
8904
|
+
var toolUseId = block.tool_use_id;
|
|
8905
|
+
if (!toolUseId) continue;
|
|
8906
|
+
if (!toolResults[toolUseId]) {
|
|
8907
|
+
toolResults[toolUseId] = [];
|
|
8908
|
+
}
|
|
8909
|
+
toolResults[toolUseId].push(block);
|
|
8910
|
+
}
|
|
8911
|
+
}
|
|
8912
|
+
return toolResults;
|
|
8913
|
+
}
|
|
8914
|
+
|
|
8915
|
+
function pickToolResultForDisplay(toolResults, toolUseId) {
|
|
8916
|
+
var entries = toolResults && toolUseId ? toolResults[toolUseId] : null;
|
|
8917
|
+
if (!entries || !entries.length) return null;
|
|
8918
|
+
for (var i = 0; i < entries.length - 1; i++) {
|
|
8919
|
+
if (isRecoverableToolError(entries[i], entries[i + 1])) {
|
|
8920
|
+
return entries[i + 1];
|
|
8921
|
+
}
|
|
8922
|
+
}
|
|
8923
|
+
return entries[entries.length - 1];
|
|
8924
|
+
}
|
|
8925
|
+
|
|
8926
|
+
function hasRecoveredToolNoise(toolResults, toolUseId) {
|
|
8927
|
+
var entries = toolResults && toolUseId ? toolResults[toolUseId] : null;
|
|
8928
|
+
if (!entries || entries.length < 2) return false;
|
|
8929
|
+
for (var i = 0; i < entries.length - 1; i++) {
|
|
8930
|
+
if (isRecoverableToolError(entries[i], entries[i + 1])) {
|
|
8931
|
+
return true;
|
|
8932
|
+
}
|
|
8933
|
+
}
|
|
8934
|
+
return false;
|
|
8935
|
+
}
|
|
8936
|
+
|
|
8937
|
+
function renderRecoveredToolHint(toolName) {
|
|
8938
|
+
return '<div class="structured-tool-hint">已自动恢复一次 ' + escapeHtml(getToolDisplayName(toolName)) + ' 参数问题</div>';
|
|
8939
|
+
}
|
|
8940
|
+
|
|
8340
8941
|
function renderStructuredMessage(msg) {
|
|
8341
8942
|
var role = msg.role;
|
|
8342
|
-
var avatar = role === "assistant" ? '<div class="chat-message-avatar"
|
|
8943
|
+
var avatar = role === "assistant" ? '<div class="chat-message-avatar">赛博虎妞</div>' : "";
|
|
8343
8944
|
|
|
8344
|
-
// Empty content array — streaming placeholder, show typing indicator
|
|
8345
8945
|
if (!msg.content || msg.content.length === 0) {
|
|
8346
8946
|
if (role === "assistant") {
|
|
8347
8947
|
return '<div class="chat-message ' + role + '">' +
|
|
@@ -8352,18 +8952,7 @@
|
|
|
8352
8952
|
return "";
|
|
8353
8953
|
}
|
|
8354
8954
|
|
|
8355
|
-
|
|
8356
|
-
var toolResults = {};
|
|
8357
|
-
for (var i = 0; i < msg.content.length; i++) {
|
|
8358
|
-
var block = msg.content[i];
|
|
8359
|
-
if (block && block.type === "tool_result") {
|
|
8360
|
-
var toolUseId = block.tool_use_id;
|
|
8361
|
-
if (toolUseId) {
|
|
8362
|
-
toolResults[toolUseId] = block;
|
|
8363
|
-
}
|
|
8364
|
-
}
|
|
8365
|
-
}
|
|
8366
|
-
|
|
8955
|
+
var toolResults = buildToolResultMap(msg.content);
|
|
8367
8956
|
var blocksHtml = "";
|
|
8368
8957
|
|
|
8369
8958
|
try {
|
|
@@ -8372,19 +8961,16 @@
|
|
|
8372
8961
|
try {
|
|
8373
8962
|
blocksHtml += renderContentBlock(block, role, toolResults, i);
|
|
8374
8963
|
} catch (e) {
|
|
8375
|
-
// Render error for individual block
|
|
8376
8964
|
blocksHtml += '<div class="render-error">消息块渲染失败</div>';
|
|
8377
8965
|
}
|
|
8378
8966
|
}
|
|
8379
8967
|
} catch (e) {
|
|
8380
|
-
// Render error for entire message
|
|
8381
8968
|
return '<div class="chat-message ' + role + '">' +
|
|
8382
8969
|
avatar +
|
|
8383
|
-
'<div class="chat-message-
|
|
8970
|
+
'<div class="chat-message-content"><div class="render-error">消息渲染失败</div></div>' +
|
|
8384
8971
|
'</div>';
|
|
8385
8972
|
}
|
|
8386
8973
|
|
|
8387
|
-
// Build usage indicator for assistant messages
|
|
8388
8974
|
var usageHtml = "";
|
|
8389
8975
|
if (role === "assistant" && msg.usage) {
|
|
8390
8976
|
var u = msg.usage;
|
|
@@ -8400,44 +8986,47 @@
|
|
|
8400
8986
|
|
|
8401
8987
|
return '<div class="chat-message ' + role + '">' +
|
|
8402
8988
|
avatar +
|
|
8403
|
-
'<div class="chat-message-
|
|
8989
|
+
'<div class="chat-message-content">' + blocksHtml + '</div>' +
|
|
8404
8990
|
usageHtml +
|
|
8405
8991
|
'</div>';
|
|
8406
8992
|
}
|
|
8407
|
-
|
|
8408
8993
|
function renderContentBlock(block, role, toolResults, index) {
|
|
8409
8994
|
if (!block || !block.type) return "";
|
|
8410
8995
|
|
|
8411
8996
|
switch (block.type) {
|
|
8412
8997
|
case "text":
|
|
8998
|
+
if (role === "assistant" && block.__processing) {
|
|
8999
|
+
return '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
|
9000
|
+
}
|
|
8413
9001
|
return role === "assistant" ? renderMarkdown(block.text || "") : escapeHtml(block.text || "");
|
|
8414
9002
|
|
|
8415
9003
|
case "thinking":
|
|
8416
9004
|
var thinkingText = block.thinking || "";
|
|
8417
|
-
// Compact display: brain icon + brief text, click to expand
|
|
8418
9005
|
var preview = thinkingText.length > 60 ? thinkingText.slice(0, 57) + "…" : thinkingText;
|
|
8419
9006
|
var isStreaming = block.thinking === undefined && block.type === "thinking";
|
|
8420
9007
|
if (isStreaming) {
|
|
8421
|
-
// During streaming: show 3-line scrollable area
|
|
8422
9008
|
return '<div class="thinking-inline thinking-streaming" data-thinking="">' +
|
|
8423
9009
|
'<div class="thinking-streaming-inner">' +
|
|
8424
|
-
'<span class="thinking-streaming-icon spinning"
|
|
9010
|
+
'<span class="thinking-streaming-icon spinning">⦿</span>' +
|
|
8425
9011
|
'<div class="thinking-streaming-text"></div>' +
|
|
8426
9012
|
'</div>' +
|
|
8427
9013
|
'</div>';
|
|
8428
9014
|
}
|
|
8429
9015
|
return '<div class="thinking-inline collapsed" data-thinking="' + escapeHtml(thinkingText) + '" onclick="__thinkingToggle(this)">' +
|
|
8430
|
-
'<span class="thinking-inline-icon"
|
|
9016
|
+
'<span class="thinking-inline-icon">⦿</span>' +
|
|
8431
9017
|
'<span class="thinking-inline-preview">' + escapeHtml(preview) + '</span>' +
|
|
8432
9018
|
'<span class="thinking-inline-action">展开</span>' +
|
|
8433
9019
|
'</div>';
|
|
8434
9020
|
|
|
8435
9021
|
case "tool_use":
|
|
8436
|
-
var toolResult = toolResults
|
|
8437
|
-
|
|
9022
|
+
var toolResult = pickToolResultForDisplay(toolResults, block.id);
|
|
9023
|
+
var rendered = renderToolUseCard(block, toolResult, index);
|
|
9024
|
+
if (hasRecoveredToolNoise(toolResults, block.id)) {
|
|
9025
|
+
rendered = renderRecoveredToolHint(block.name || "工具") + rendered;
|
|
9026
|
+
}
|
|
9027
|
+
return rendered;
|
|
8438
9028
|
|
|
8439
9029
|
case "tool_result":
|
|
8440
|
-
// tool_result 已经在 tool_use 渲染时处理了,不再单独渲染
|
|
8441
9030
|
return "";
|
|
8442
9031
|
|
|
8443
9032
|
default:
|
|
@@ -8445,14 +9034,14 @@
|
|
|
8445
9034
|
}
|
|
8446
9035
|
}
|
|
8447
9036
|
|
|
8448
|
-
// Lightweight inline display — used for Read, Glob, Grep, WebFetch, WebSearch, TodoRead
|
|
8449
9037
|
function renderInlineTool(block, toolResult, toolName, fileInfo, extraInfo) {
|
|
8450
9038
|
var toolId = block.id || "tool-" + toolName;
|
|
8451
9039
|
var inputData = block.input || {};
|
|
8452
|
-
var resultContent = (toolResult && toolResult.content)
|
|
9040
|
+
var resultContent = extractToolResultText(toolResult && toolResult.content);
|
|
9041
|
+
|
|
8453
9042
|
var isError = toolResult && toolResult.is_error;
|
|
8454
9043
|
var hasResult = resultContent.length > 0;
|
|
8455
|
-
var statusIcon = isError ? "
|
|
9044
|
+
var statusIcon = isError ? "✗" : (hasResult ? "✓" : "…");
|
|
8456
9045
|
|
|
8457
9046
|
// Build the inline preview line
|
|
8458
9047
|
var icon = "";
|
|
@@ -8517,7 +9106,7 @@
|
|
|
8517
9106
|
var fullResult = resultContent;
|
|
8518
9107
|
|
|
8519
9108
|
var expandedHtml = "";
|
|
8520
|
-
var shouldExpand =
|
|
9109
|
+
var shouldExpand = false; // All inline tools collapsed by default
|
|
8521
9110
|
if (hasResult) {
|
|
8522
9111
|
expandedHtml = '<div class="inline-tool-expanded" style="display: ' + (shouldExpand ? 'block' : 'none') + ';">' +
|
|
8523
9112
|
'<div class="inline-tool-result">' + formatInlineResult(resultContent, toolName) + '</div>' +
|
|
@@ -8531,7 +9120,7 @@
|
|
|
8531
9120
|
|
|
8532
9121
|
var extraInfoHtml = meta ? '<span class="inline-tool-meta">' + escapeHtml(meta) + '</span>' : '';
|
|
8533
9122
|
var extraClass = isError ? 'inline-tool-error-inline' : '';
|
|
8534
|
-
if (
|
|
9123
|
+
if (shouldExpand) extraClass += ' inline-tool-open';
|
|
8535
9124
|
|
|
8536
9125
|
return '<div class="inline-tool ' + extraClass + '" ' +
|
|
8537
9126
|
'data-result="' + escapeHtml(fullResult) + '" ' +
|
|
@@ -8552,7 +9141,8 @@
|
|
|
8552
9141
|
function renderTerminalTool(block, toolResult, toolName) {
|
|
8553
9142
|
var inputData = block.input || {};
|
|
8554
9143
|
var command = inputData.command || inputData.cmd || "";
|
|
8555
|
-
var resultContent = (toolResult && toolResult.content)
|
|
9144
|
+
var resultContent = extractToolResultText(toolResult && toolResult.content);
|
|
9145
|
+
|
|
8556
9146
|
var isError = toolResult && toolResult.is_error;
|
|
8557
9147
|
var exitCode = inputData.exitCode;
|
|
8558
9148
|
var hasResult = resultContent.length > 0;
|
|
@@ -8587,13 +9177,16 @@
|
|
|
8587
9177
|
exitCodeHtml = '<div class="term-exit ' + codeClass + '">exit ' + exitCode + '</div>';
|
|
8588
9178
|
}
|
|
8589
9179
|
|
|
8590
|
-
|
|
9180
|
+
// Show command preview in header (truncate long commands)
|
|
9181
|
+
var cmdPreview = command.length > 80 ? command.slice(0, 77) + "…" : command;
|
|
9182
|
+
|
|
9183
|
+
return '<div class="inline-terminal" data-expanded="false">' +
|
|
8591
9184
|
'<div class="term-header" onclick="__terminalExpand(this)">' +
|
|
8592
9185
|
statusDot +
|
|
8593
|
-
'<span class="term-
|
|
8594
|
-
'<span class="term-toggle-icon"
|
|
9186
|
+
'<span class="term-cmd-preview"><span class="term-prompt">$</span> ' + escapeHtml(cmdPreview) + '</span>' +
|
|
9187
|
+
'<span class="term-toggle-icon">▶</span>' +
|
|
8595
9188
|
'</div>' +
|
|
8596
|
-
'<div class="term-body">' +
|
|
9189
|
+
'<div class="term-body" style="display:none;">' +
|
|
8597
9190
|
'<div class="term-command"><span class="term-prompt">$</span> ' + cmdDisplay + '</div>' +
|
|
8598
9191
|
(outputHtml ? '<div class="term-output">' + outputHtml + '</div>' : '') +
|
|
8599
9192
|
exitCodeHtml +
|
|
@@ -8656,15 +9249,15 @@
|
|
|
8656
9249
|
if (isError) {
|
|
8657
9250
|
statusClass = "diff-error";
|
|
8658
9251
|
statusText = toolResultText.indexOf("haven't granted") !== -1 || toolResultText.indexOf("permission") !== -1
|
|
8659
|
-
? "
|
|
8660
|
-
: "
|
|
9252
|
+
? "等待授权"
|
|
9253
|
+
: "失败";
|
|
8661
9254
|
} else {
|
|
8662
9255
|
statusClass = "diff-success";
|
|
8663
|
-
statusText = "
|
|
9256
|
+
statusText = "已修改";
|
|
8664
9257
|
}
|
|
8665
9258
|
} else {
|
|
8666
9259
|
statusClass = "diff-pending";
|
|
8667
|
-
statusText = "
|
|
9260
|
+
statusText = "执行中";
|
|
8668
9261
|
}
|
|
8669
9262
|
|
|
8670
9263
|
// If only one column has content, show full width
|
|
@@ -8673,7 +9266,7 @@
|
|
|
8673
9266
|
|
|
8674
9267
|
return '<div class="inline-diff" data-tool-name="' + escapeHtml(toolName) + '">' +
|
|
8675
9268
|
'<div class="diff-header">' +
|
|
8676
|
-
'<span class="diff-file-icon"
|
|
9269
|
+
'<span class="diff-file-icon"></span>' +
|
|
8677
9270
|
'<span class="diff-file-name">' + escapeHtml(fileName) + '</span>' +
|
|
8678
9271
|
'<span class="diff-path">' + escapeHtml(path) + '</span>' +
|
|
8679
9272
|
'<span class="diff-status ' + statusClass + '">' + statusText + '</span>' +
|
|
@@ -8732,7 +9325,7 @@
|
|
|
8732
9325
|
}
|
|
8733
9326
|
return '<div class="tool-use-card ask-user" data-tool-use-id="' + escapeHtml(toolId) + '">' +
|
|
8734
9327
|
'<div class="tool-use-header" data-tool-toggle onclick="__tcToggle(event,this)">' +
|
|
8735
|
-
'<span class="tool-use-icon"
|
|
9328
|
+
'<span class="tool-use-icon">?</span>' +
|
|
8736
9329
|
'<span class="tool-use-name">提问</span>' +
|
|
8737
9330
|
'</div>' +
|
|
8738
9331
|
'<div class="tool-use-body ask-user-body">' +
|
|
@@ -8769,7 +9362,7 @@
|
|
|
8769
9362
|
|
|
8770
9363
|
if (toolResult) {
|
|
8771
9364
|
var isError = toolResult.is_error;
|
|
8772
|
-
var content = toolResult.content
|
|
9365
|
+
var content = extractToolResultText(toolResult.content);
|
|
8773
9366
|
statusClass = isError ? "error" : "success";
|
|
8774
9367
|
headerIcon = getToolIcon(toolName);
|
|
8775
9368
|
var hasContent = content && content.trim().length > 0;
|
|
@@ -8823,23 +9416,23 @@
|
|
|
8823
9416
|
|
|
8824
9417
|
function getToolIcon(toolName) {
|
|
8825
9418
|
var icons = {
|
|
8826
|
-
"Read": "
|
|
8827
|
-
"Write": "
|
|
8828
|
-
"Edit": "
|
|
8829
|
-
"MultiEdit": "
|
|
8830
|
-
"Bash": "
|
|
8831
|
-
"Grep": "
|
|
8832
|
-
"Glob": "
|
|
8833
|
-
"WebFetch": "
|
|
8834
|
-
"WebSearch": "
|
|
8835
|
-
"Task": "
|
|
8836
|
-
"TodoWrite": "
|
|
8837
|
-
"TodoRead": "
|
|
8838
|
-
"NotebookEdit": "
|
|
8839
|
-
"Agent": "
|
|
8840
|
-
"Exit": "
|
|
9419
|
+
"Read": "R",
|
|
9420
|
+
"Write": "W",
|
|
9421
|
+
"Edit": "E",
|
|
9422
|
+
"MultiEdit": "E",
|
|
9423
|
+
"Bash": "$",
|
|
9424
|
+
"Grep": "G",
|
|
9425
|
+
"Glob": "F",
|
|
9426
|
+
"WebFetch": "⇣",
|
|
9427
|
+
"WebSearch": "⇢",
|
|
9428
|
+
"Task": "T",
|
|
9429
|
+
"TodoWrite": "☐",
|
|
9430
|
+
"TodoRead": "☑",
|
|
9431
|
+
"NotebookEdit": "N",
|
|
9432
|
+
"Agent": "A",
|
|
9433
|
+
"Exit": "×"
|
|
8841
9434
|
};
|
|
8842
|
-
return icons[toolName] || "
|
|
9435
|
+
return icons[toolName] || "·";
|
|
8843
9436
|
}
|
|
8844
9437
|
|
|
8845
9438
|
function generateInputSummary(toolName, input) {
|