@co0ontty/wand 1.3.4 → 1.4.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/auth.js +2 -1
- package/dist/claude-pty-bridge.d.ts +38 -0
- package/dist/claude-pty-bridge.js +224 -9
- package/dist/cli.js +2 -2
- package/dist/middleware/rate-limit.js +2 -1
- package/dist/process-manager.d.ts +1 -0
- package/dist/process-manager.js +96 -178
- package/dist/pty-text-utils.d.ts +12 -0
- package/dist/pty-text-utils.js +37 -1
- package/dist/server-session-routes.d.ts +3 -1
- package/dist/server-session-routes.js +114 -10
- package/dist/server.js +14 -34
- package/dist/session-lifecycle.js +0 -5
- package/dist/session-logger.d.ts +10 -0
- package/dist/storage.js +42 -8
- package/dist/structured-session-manager.d.ts +55 -0
- package/dist/structured-session-manager.js +723 -0
- package/dist/types.d.ts +22 -0
- package/dist/web-ui/content/scripts.js +746 -102
- package/dist/web-ui/content/styles.css +275 -9
- package/package.json +2 -1
|
@@ -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 {
|
|
@@ -173,6 +246,12 @@
|
|
|
173
246
|
return (state.config && state.config.defaultCwd) || "/tmp";
|
|
174
247
|
}
|
|
175
248
|
|
|
249
|
+
function resetChatRenderCache() {
|
|
250
|
+
state.lastRenderedHash = 0;
|
|
251
|
+
state.lastRenderedMsgCount = 0;
|
|
252
|
+
state.lastRenderedEmpty = null;
|
|
253
|
+
}
|
|
254
|
+
|
|
176
255
|
function getEffectiveCwd() {
|
|
177
256
|
return state.workingDir || getConfigCwd();
|
|
178
257
|
}
|
|
@@ -270,7 +349,7 @@
|
|
|
270
349
|
body: "\u5f53\u524d " + (config.currentVersion || "-") + " \u2192 \u6700\u65b0 " + config.latestVersion,
|
|
271
350
|
type: "info",
|
|
272
351
|
icon: "\u2191",
|
|
273
|
-
duration:
|
|
352
|
+
duration: 10000,
|
|
274
353
|
actionLabel: "\u53bb\u66f4\u65b0",
|
|
275
354
|
action: function() {
|
|
276
355
|
var settingsBtn = document.getElementById("open-settings-btn") || document.querySelector("[data-action='settings']");
|
|
@@ -313,7 +392,7 @@
|
|
|
313
392
|
var app = document.getElementById("app");
|
|
314
393
|
var isLoggedIn = state.config !== null;
|
|
315
394
|
var wasModalOpen = state.modalOpen;
|
|
316
|
-
var shouldResetShell = !isLoggedIn ||
|
|
395
|
+
var shouldResetShell = !isLoggedIn || !!document.getElementById("output");
|
|
317
396
|
|
|
318
397
|
if (shouldResetShell) {
|
|
319
398
|
teardownTerminal();
|
|
@@ -324,9 +403,7 @@
|
|
|
324
403
|
|
|
325
404
|
app.innerHTML = isLoggedIn ? renderAppShell() : renderLogin();
|
|
326
405
|
// Reset chat render tracking since DOM was fully replaced
|
|
327
|
-
|
|
328
|
-
state.lastRenderedMsgCount = 0;
|
|
329
|
-
state.lastRenderedEmpty = null;
|
|
406
|
+
resetChatRenderCache();
|
|
330
407
|
attachEventListeners();
|
|
331
408
|
updateDrawerState();
|
|
332
409
|
syncComposerModeSelect();
|
|
@@ -334,6 +411,9 @@
|
|
|
334
411
|
if (!skipShellChrome) {
|
|
335
412
|
updateShellChrome();
|
|
336
413
|
}
|
|
414
|
+
if (isLoggedIn && state.filePanelOpen) {
|
|
415
|
+
refreshFileExplorer();
|
|
416
|
+
}
|
|
337
417
|
|
|
338
418
|
// Force reflow then re-enable transitions after layout settles
|
|
339
419
|
void document.body.offsetHeight;
|
|
@@ -367,6 +447,26 @@
|
|
|
367
447
|
'<button class="shortcut-key" data-key="escape" type="button">Esc</button>';
|
|
368
448
|
}
|
|
369
449
|
|
|
450
|
+
function renderApprovalStatsBadge() {
|
|
451
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
452
|
+
var stats = selectedSession && selectedSession.approvalStats;
|
|
453
|
+
if (!stats || stats.total === 0) return '<span class="approval-stats hidden" id="approval-stats"></span>';
|
|
454
|
+
return '<span class="approval-stats" id="approval-stats">' +
|
|
455
|
+
'<span class="approval-stats-divider"></span>' +
|
|
456
|
+
'<span class="approval-stats-badge" id="approval-stats-badge" title="本次会话自动批准统计">' +
|
|
457
|
+
'<svg class="approval-stats-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>' +
|
|
458
|
+
'<span class="approval-stats-total">' + stats.total + '</span>' +
|
|
459
|
+
'</span>' +
|
|
460
|
+
'<span class="approval-stats-popup" id="approval-stats-popup">' +
|
|
461
|
+
'<span class="approval-stats-popup-title">自动批准统计</span>' +
|
|
462
|
+
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">⚡</span><span class="approval-stats-row-label">命令执行</span><span class="approval-stats-row-count">' + stats.command + '</span></span>' : '') +
|
|
463
|
+
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">📝</span><span class="approval-stats-row-label">文件写入</span><span class="approval-stats-row-count">' + stats.file + '</span></span>' : '') +
|
|
464
|
+
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">🔧</span><span class="approval-stats-row-label">其他工具</span><span class="approval-stats-row-count">' + stats.tool + '</span></span>' : '') +
|
|
465
|
+
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon">∑</span><span class="approval-stats-row-label">合计</span><span class="approval-stats-row-count">' + stats.total + '</span></span>' +
|
|
466
|
+
'</span>' +
|
|
467
|
+
'</span>';
|
|
468
|
+
}
|
|
469
|
+
|
|
370
470
|
function renderInlineKeyboard() {
|
|
371
471
|
if (!state.selectedId) return "";
|
|
372
472
|
var isTerminal = state.currentView === "terminal";
|
|
@@ -462,6 +562,12 @@
|
|
|
462
562
|
'<span class="session-count" id="session-count">' + String(state.sessions.length) + '</span>' +
|
|
463
563
|
'</div>' +
|
|
464
564
|
'<div class="sidebar-header-actions">' +
|
|
565
|
+
'<button id="sidebar-home-btn" class="btn btn-ghost btn-sm" type="button" title="回到首页">' +
|
|
566
|
+
'<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 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>' +
|
|
567
|
+
'</button>' +
|
|
568
|
+
'<button id="sidebar-refresh-btn" class="btn btn-ghost btn-sm" type="button" title="刷新页面">' +
|
|
569
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' +
|
|
570
|
+
'</button>' +
|
|
465
571
|
'<button id="close-drawer-button" class="btn btn-ghost btn-sm sidebar-close" type="button" aria-label="关闭菜单">×</button>' +
|
|
466
572
|
'</div>' +
|
|
467
573
|
'</div>' +
|
|
@@ -524,11 +630,14 @@
|
|
|
524
630
|
'<div class="blank-chat-inner">' +
|
|
525
631
|
'<div class="blank-chat-logo">W</div>' +
|
|
526
632
|
'<h2 class="blank-chat-title">Wand</h2>' +
|
|
527
|
-
'<p class="blank-chat-subtitle"
|
|
633
|
+
'<p class="blank-chat-subtitle">支持终端 PTY 会话与结构化 chat 会话,两种模式可并存。</p>' +
|
|
528
634
|
'<div class="blank-chat-tools">' +
|
|
529
635
|
'<button class="blank-chat-tool-btn" id="welcome-tool-claude" type="button">' +
|
|
530
636
|
'<span class="tool-icon">🤖</span>新建终端会话' +
|
|
531
637
|
'</button>' +
|
|
638
|
+
'<button class="blank-chat-tool-btn" id="welcome-tool-structured" type="button">' +
|
|
639
|
+
'<span class="tool-icon">💬</span>新建结构化会话' +
|
|
640
|
+
'</button>' +
|
|
532
641
|
'</div>' +
|
|
533
642
|
'<div class="blank-chat-cwd-wrap">' +
|
|
534
643
|
'<div class="blank-chat-cwd" id="blank-chat-cwd" role="button" tabindex="0" title="点击切换工作目录">' +
|
|
@@ -569,6 +678,7 @@
|
|
|
569
678
|
'<button id="approve-permission-btn" class="btn btn-permission btn-permission-approve" type="button">批准</button>' +
|
|
570
679
|
'<button id="deny-permission-btn" class="btn btn-permission btn-permission-deny" type="button">拒绝</button>' +
|
|
571
680
|
'</span>' +
|
|
681
|
+
renderApprovalStatsBadge() +
|
|
572
682
|
'</div>' +
|
|
573
683
|
'<div class="input-composer-right">' +
|
|
574
684
|
'<span id="queue-counter" class="queue-counter hidden">队列: 0</span>' +
|
|
@@ -588,6 +698,9 @@
|
|
|
588
698
|
'<span id="session-cwd-display" class="session-cwd-display">' + (selectedSession && selectedSession.cwd ? escapeHtml(selectedSession.cwd) : '未设置目录') + '</span>' +
|
|
589
699
|
'<span class="session-info-separator">|</span>' +
|
|
590
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>' +
|
|
591
704
|
'<span class="session-info-separator">|</span>' +
|
|
592
705
|
'<span id="session-status-display" class="session-status-display">' + (selectedSession ? getSessionStatusLabel(selectedSession) : '-') + '</span>' +
|
|
593
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>' : '') +
|
|
@@ -1225,13 +1338,21 @@
|
|
|
1225
1338
|
explorer.innerHTML = '<div class="file-explorer empty">No working directory.</div>';
|
|
1226
1339
|
return;
|
|
1227
1340
|
}
|
|
1341
|
+
state.fileExplorerLoading = true;
|
|
1342
|
+
state.allFiles = [];
|
|
1228
1343
|
explorer.innerHTML = '<div class="file-explorer"><div class="tree-loading" style="padding:12px;color:var(--text-muted);font-size:0.8125rem;">Loading...</div></div>';
|
|
1229
1344
|
// Update the cwd display
|
|
1230
1345
|
if (cwdEl) cwdEl.textContent = cwd;
|
|
1231
1346
|
// Fetch with git status
|
|
1232
1347
|
fetch("/api/directory?q=" + encodeURIComponent(cwd) + "&gitStatus=true", { credentials: "same-origin" })
|
|
1233
|
-
.then(function(res) {
|
|
1348
|
+
.then(function(res) {
|
|
1349
|
+
if (!res.ok) {
|
|
1350
|
+
throw new Error("Failed to load directory.");
|
|
1351
|
+
}
|
|
1352
|
+
return res.json();
|
|
1353
|
+
})
|
|
1234
1354
|
.then(function(items) {
|
|
1355
|
+
state.fileExplorerLoading = false;
|
|
1235
1356
|
if (!items || items.length === 0) {
|
|
1236
1357
|
explorer.innerHTML = '<div class="file-explorer empty">Empty directory or inaccessible.</div>';
|
|
1237
1358
|
return;
|
|
@@ -1240,6 +1361,7 @@
|
|
|
1240
1361
|
filterFileTree();
|
|
1241
1362
|
})
|
|
1242
1363
|
.catch(function() {
|
|
1364
|
+
state.fileExplorerLoading = false;
|
|
1243
1365
|
explorer.innerHTML = '<div class="file-explorer empty">Failed to load files.</div>';
|
|
1244
1366
|
});
|
|
1245
1367
|
}
|
|
@@ -1636,7 +1758,7 @@
|
|
|
1636
1758
|
if (session.claudeSessionId) {
|
|
1637
1759
|
var shortId = session.claudeSessionId.slice(0, 8);
|
|
1638
1760
|
sessionIdDisplay = '<span class="session-id" title="' + escapeHtml(session.claudeSessionId) + '">' + escapeHtml(shortId) + '</span>';
|
|
1639
|
-
if (session.status !== "running" && !state.sessionsManageMode) {
|
|
1761
|
+
if (session.status !== "running" && !state.sessionsManageMode && !isStructuredSession(session)) {
|
|
1640
1762
|
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>';
|
|
1641
1763
|
}
|
|
1642
1764
|
}
|
|
@@ -1646,6 +1768,7 @@
|
|
|
1646
1768
|
}
|
|
1647
1769
|
|
|
1648
1770
|
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>';
|
|
1771
|
+
var modeBadge = renderSessionKindBadge(session);
|
|
1649
1772
|
var actionsHtml = '<span class="session-actions">' + resumeButton + deleteButton + '</span>';
|
|
1650
1773
|
|
|
1651
1774
|
return '<div class="session-item' + activeClass + selectedClass + '" data-session-id="' + session.id + '" role="button" tabindex="0">' +
|
|
@@ -1655,6 +1778,7 @@
|
|
|
1655
1778
|
'<div class="session-main">' +
|
|
1656
1779
|
'<div class="session-command">' + escapeHtml(session.resumedFromSessionId ? session.command.replace(/\s+--resume\s+\S+/, '') : session.command) + '</div>' +
|
|
1657
1780
|
'<div class="session-meta">' +
|
|
1781
|
+
modeBadge +
|
|
1658
1782
|
'<span>' + escapeHtml(modeName) + '</span>' +
|
|
1659
1783
|
'<span class="session-status ' + metaStatusClass + '">' + escapeHtml(metaStatus) + '</span>' +
|
|
1660
1784
|
sessionIdDisplay +
|
|
@@ -1666,6 +1790,15 @@
|
|
|
1666
1790
|
'</div>' +
|
|
1667
1791
|
'</div>';
|
|
1668
1792
|
}
|
|
1793
|
+
|
|
1794
|
+
function renderSessionKindBadge(session) {
|
|
1795
|
+
if (!session) return "";
|
|
1796
|
+
if (isStructuredSession(session)) {
|
|
1797
|
+
return '<span class="session-kind-badge structured">Structured</span>';
|
|
1798
|
+
}
|
|
1799
|
+
return '<span class="session-kind-badge pty">PTY</span>';
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1669
1802
|
function renderModeCards(selectedMode) {
|
|
1670
1803
|
var modes = [
|
|
1671
1804
|
{ id: "managed", label: "托管", desc: "全自动完成任务" },
|
|
@@ -1683,19 +1816,48 @@
|
|
|
1683
1816
|
}).join("");
|
|
1684
1817
|
}
|
|
1685
1818
|
|
|
1819
|
+
function renderSessionKindOptions(selectedKind) {
|
|
1820
|
+
var kinds = [
|
|
1821
|
+
{ id: "pty", label: "PTY", desc: "交互式终端会话" },
|
|
1822
|
+
{ id: "structured", label: "Structured", desc: "单轮结构化输出" }
|
|
1823
|
+
];
|
|
1824
|
+
return kinds.map(function(kind) {
|
|
1825
|
+
var active = kind.id === selectedKind ? " active" : "";
|
|
1826
|
+
return '<button type="button" class="mode-card session-kind-card' + active + '" data-session-kind="' + kind.id + '">' +
|
|
1827
|
+
'<span class="mode-card-label">' + kind.label + '</span>' +
|
|
1828
|
+
'<span class="mode-card-desc">' + kind.desc + '</span>' +
|
|
1829
|
+
'</button>';
|
|
1830
|
+
}).join("");
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function getSessionKindHint(kind) {
|
|
1834
|
+
if (kind === "structured") {
|
|
1835
|
+
return "直接使用 claude -p 获取结构化单轮结果。";
|
|
1836
|
+
}
|
|
1837
|
+
return "默认 PTY 会话,支持持续交互、终端视图和权限流。";
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1686
1840
|
function renderSessionModal() {
|
|
1687
1841
|
var modalTool = getPreferredTool();
|
|
1688
1842
|
var modalMode = getSafeModeForTool(modalTool, state.modeValue || state.chatMode || "default");
|
|
1843
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
1689
1844
|
return '<section id="session-modal" class="modal-backdrop hidden">' +
|
|
1690
1845
|
'<div class="modal session-modal">' +
|
|
1691
1846
|
'<div class="modal-header">' +
|
|
1692
1847
|
'<div>' +
|
|
1693
1848
|
'<h2 class="modal-title">新对话</h2>' +
|
|
1694
|
-
'<p class="modal-subtitle">启动 Claude
|
|
1849
|
+
'<p class="modal-subtitle">启动 Claude 会话,选择会话类型、模式和工作目录。</p>' +
|
|
1695
1850
|
'</div>' +
|
|
1696
1851
|
'<button id="close-modal-button" class="btn btn-ghost btn-icon">×</button>' +
|
|
1697
1852
|
'</div>' +
|
|
1698
1853
|
'<div class="modal-body">' +
|
|
1854
|
+
'<div class="field">' +
|
|
1855
|
+
'<label class="field-label">会话类型</label>' +
|
|
1856
|
+
'<div id="session-kind-cards" class="mode-cards">' +
|
|
1857
|
+
renderSessionKindOptions(sessionKind) +
|
|
1858
|
+
'</div>' +
|
|
1859
|
+
'<p id="session-kind-description" class="field-hint">' + escapeHtml(getSessionKindHint(sessionKind)) + '</p>' +
|
|
1860
|
+
'</div>' +
|
|
1699
1861
|
'<div class="field">' +
|
|
1700
1862
|
'<label class="field-label">模式</label>' +
|
|
1701
1863
|
'<div id="mode-cards" class="mode-cards">' +
|
|
@@ -1868,6 +2030,16 @@
|
|
|
1868
2030
|
quickStartSession();
|
|
1869
2031
|
});
|
|
1870
2032
|
}
|
|
2033
|
+
var welcomeStructuredBtn = document.getElementById("welcome-tool-structured");
|
|
2034
|
+
if (welcomeStructuredBtn) {
|
|
2035
|
+
welcomeStructuredBtn.addEventListener("click", function() {
|
|
2036
|
+
createStructuredSession().then(function() {
|
|
2037
|
+
focusInputBox(true);
|
|
2038
|
+
}).catch(function(error) {
|
|
2039
|
+
showToast((error && error.message) || "无法启动结构化会话。", "error");
|
|
2040
|
+
});
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
1871
2043
|
initBlankChatCwd();
|
|
1872
2044
|
|
|
1873
2045
|
var sessionsList = document.getElementById("sessions-list");
|
|
@@ -1880,6 +2052,17 @@
|
|
|
1880
2052
|
// Claude session ID badge click-to-copy (event delegation on document)
|
|
1881
2053
|
document.addEventListener("click", handleClaudeIdCopy);
|
|
1882
2054
|
|
|
2055
|
+
var kindCardsEl = document.getElementById("session-kind-cards");
|
|
2056
|
+
if (kindCardsEl) kindCardsEl.addEventListener("click", function(e) {
|
|
2057
|
+
var card = e.target.closest(".session-kind-card");
|
|
2058
|
+
if (!card) return;
|
|
2059
|
+
var kind = card.getAttribute("data-session-kind");
|
|
2060
|
+
if (kind) {
|
|
2061
|
+
state.sessionCreateKind = kind;
|
|
2062
|
+
syncSessionModalUI();
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
|
|
1883
2066
|
var modeCardsEl = document.getElementById("mode-cards");
|
|
1884
2067
|
if (modeCardsEl) modeCardsEl.addEventListener("click", function(e) {
|
|
1885
2068
|
var card = e.target.closest(".mode-card");
|
|
@@ -1904,6 +2087,18 @@
|
|
|
1904
2087
|
if (drawerBackdrop) drawerBackdrop.addEventListener("click", closeSessionsDrawer);
|
|
1905
2088
|
var closeDrawerBtn = document.getElementById("close-drawer-button");
|
|
1906
2089
|
if (closeDrawerBtn) closeDrawerBtn.addEventListener("click", closeSessionsDrawer);
|
|
2090
|
+
var homeBtn = document.getElementById("sidebar-home-btn");
|
|
2091
|
+
if (homeBtn) homeBtn.addEventListener("click", function() {
|
|
2092
|
+
state.selectedId = null;
|
|
2093
|
+
persistSelectedId();
|
|
2094
|
+
resetChatRenderCache();
|
|
2095
|
+
closeSessionsDrawer();
|
|
2096
|
+
render();
|
|
2097
|
+
});
|
|
2098
|
+
var refreshBtn = document.getElementById("sidebar-refresh-btn");
|
|
2099
|
+
if (refreshBtn) refreshBtn.addEventListener("click", function() {
|
|
2100
|
+
window.location.reload();
|
|
2101
|
+
});
|
|
1907
2102
|
var logoutBtn = document.getElementById("logout-button");
|
|
1908
2103
|
if (logoutBtn) logoutBtn.addEventListener("click", logout);
|
|
1909
2104
|
var settingsBtn = document.getElementById("settings-button");
|
|
@@ -1953,6 +2148,8 @@
|
|
|
1953
2148
|
if (approvePermissionBtn) approvePermissionBtn.addEventListener("click", approvePermission);
|
|
1954
2149
|
var denyPermissionBtn = document.getElementById("deny-permission-btn");
|
|
1955
2150
|
if (denyPermissionBtn) denyPermissionBtn.addEventListener("click", denyPermission);
|
|
2151
|
+
var autoApproveToggle = document.getElementById("auto-approve-toggle");
|
|
2152
|
+
if (autoApproveToggle) autoApproveToggle.addEventListener("click", toggleAutoApprove);
|
|
1956
2153
|
var sendBtn = document.getElementById("send-input-button");
|
|
1957
2154
|
if (sendBtn) sendBtn.addEventListener("click", function() {
|
|
1958
2155
|
closeSessionsDrawer();
|
|
@@ -2213,7 +2410,7 @@
|
|
|
2213
2410
|
state.selectedId = null;
|
|
2214
2411
|
persistSelectedId();
|
|
2215
2412
|
state.drafts = {};
|
|
2216
|
-
|
|
2413
|
+
render();
|
|
2217
2414
|
// 聚焦到目录输入框
|
|
2218
2415
|
setTimeout(function() {
|
|
2219
2416
|
var folderInput = document.getElementById("folder-picker-input");
|
|
@@ -2496,7 +2693,7 @@
|
|
|
2496
2693
|
|
|
2497
2694
|
function activateSessionItem(sessionId) {
|
|
2498
2695
|
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
2499
|
-
if (session && session.status !== "running") {
|
|
2696
|
+
if (session && session.status !== "running" && !isStructuredSession(session)) {
|
|
2500
2697
|
resumeSessionFromList(sessionId);
|
|
2501
2698
|
} else {
|
|
2502
2699
|
selectSession(sessionId);
|
|
@@ -3293,6 +3490,35 @@
|
|
|
3293
3490
|
return hints[mode] || '';
|
|
3294
3491
|
}
|
|
3295
3492
|
|
|
3493
|
+
function getSessionKindLabel(session) {
|
|
3494
|
+
return isStructuredSession(session) ? "Structured" : "PTY";
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
function getSessionKindDescription(session) {
|
|
3498
|
+
return isStructuredSession(session)
|
|
3499
|
+
? "Structured · block transcript"
|
|
3500
|
+
: "PTY · terminal session";
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
function isRecoverableToolError(toolResult, nextResult) {
|
|
3504
|
+
if (!toolResult || !toolResult.is_error || !nextResult || nextResult.is_error) {
|
|
3505
|
+
return false;
|
|
3506
|
+
}
|
|
3507
|
+
var currentText = extractToolResultText(toolResult.content).toLowerCase();
|
|
3508
|
+
var nextText = extractToolResultText(nextResult.content).toLowerCase();
|
|
3509
|
+
if (!currentText) return false;
|
|
3510
|
+
if (currentText.indexOf("invalid pages parameter") !== -1 && nextText.length > 0) {
|
|
3511
|
+
return true;
|
|
3512
|
+
}
|
|
3513
|
+
return false;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
function isStructuredSession(session) {
|
|
3517
|
+
var result = !!session && (session.sessionKind === "structured" || session.runner === "claude-cli-print");
|
|
3518
|
+
if (session) console.log("[WAND] isStructuredSession id:", session.id, "sessionKind:", session.sessionKind, "runner:", session.runner, "=>", result);
|
|
3519
|
+
return result;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3296
3522
|
function syncComposerModeSelect() {
|
|
3297
3523
|
var select = document.getElementById("chat-mode-select");
|
|
3298
3524
|
if (!select) return;
|
|
@@ -3303,29 +3529,89 @@
|
|
|
3303
3529
|
if (modeHint) modeHint.textContent = getModeHint(state.chatMode);
|
|
3304
3530
|
}
|
|
3305
3531
|
|
|
3532
|
+
function createStructuredSession(prompt, cwdOverride, modeOverride) {
|
|
3533
|
+
var payload = {
|
|
3534
|
+
cwd: cwdOverride || getEffectiveCwd(),
|
|
3535
|
+
mode: modeOverride || state.chatMode || (state.config && state.config.defaultMode) || "default",
|
|
3536
|
+
runner: state.structuredRunner || "claude-cli-print",
|
|
3537
|
+
prompt: prompt || undefined
|
|
3538
|
+
};
|
|
3539
|
+
console.log("[WAND] createStructuredSession payload:", JSON.stringify(payload));
|
|
3540
|
+
return fetch("/api/structured-sessions", {
|
|
3541
|
+
method: "POST",
|
|
3542
|
+
headers: { "Content-Type": "application/json" },
|
|
3543
|
+
credentials: "same-origin",
|
|
3544
|
+
body: JSON.stringify(payload)
|
|
3545
|
+
})
|
|
3546
|
+
.then(function(res) {
|
|
3547
|
+
console.log("[WAND] createStructuredSession response status:", res.status);
|
|
3548
|
+
return res.json();
|
|
3549
|
+
})
|
|
3550
|
+
.then(function(data) {
|
|
3551
|
+
console.log("[WAND] createStructuredSession data:", JSON.stringify({ id: data.id, error: data.error, sessionKind: data.sessionKind, runner: data.runner, status: data.status }));
|
|
3552
|
+
if (data.error) {
|
|
3553
|
+
throw new Error(data.error);
|
|
3554
|
+
}
|
|
3555
|
+
state.selectedId = data.id;
|
|
3556
|
+
persistSelectedId();
|
|
3557
|
+
state.drafts[data.id] = "";
|
|
3558
|
+
resetChatRenderCache();
|
|
3559
|
+
updateSessionSnapshot(data);
|
|
3560
|
+
updateSessionsList();
|
|
3561
|
+
switchToSessionView(data.id);
|
|
3562
|
+
subscribeToSession(data.id);
|
|
3563
|
+
return loadOutput(data.id).then(function() { return data; });
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3306
3567
|
function applyCurrentView() {
|
|
3307
3568
|
var hasSession = !!state.selectedId;
|
|
3308
3569
|
var terminalBtn = document.getElementById("view-terminal-btn");
|
|
3309
3570
|
var terminalContainer = document.getElementById("output");
|
|
3310
3571
|
var chatContainer = document.getElementById("chat-output");
|
|
3572
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
3573
|
+
var structured = isStructuredSession(selectedSession);
|
|
3574
|
+
var showTerminal = hasSession && !structured && state.currentView === "terminal";
|
|
3575
|
+
var showChat = hasSession && (structured || state.currentView !== "terminal");
|
|
3576
|
+
console.log("[WAND] applyCurrentView hasSession:", hasSession, "structured:", structured, "currentView:", state.currentView, "showTerminal:", showTerminal, "showChat:", showChat, "sessionKind:", selectedSession && selectedSession.sessionKind, "runner:", selectedSession && selectedSession.runner);
|
|
3577
|
+
|
|
3578
|
+
if (structured) {
|
|
3579
|
+
state.currentView = "chat";
|
|
3580
|
+
} else if (!hasSession) {
|
|
3581
|
+
state.currentView = "terminal";
|
|
3582
|
+
}
|
|
3311
3583
|
|
|
3312
|
-
if (terminalBtn)
|
|
3313
|
-
|
|
3584
|
+
if (terminalBtn) {
|
|
3585
|
+
terminalBtn.classList.toggle("hidden", structured || !hasSession);
|
|
3586
|
+
terminalBtn.classList.toggle("active", showTerminal);
|
|
3587
|
+
}
|
|
3588
|
+
if (terminalContainer) {
|
|
3589
|
+
terminalContainer.classList.toggle("active", showTerminal);
|
|
3590
|
+
terminalContainer.classList.toggle("hidden", !showTerminal);
|
|
3591
|
+
}
|
|
3314
3592
|
if (chatContainer) {
|
|
3315
|
-
chatContainer.classList.
|
|
3316
|
-
chatContainer.classList.
|
|
3593
|
+
chatContainer.classList.toggle("active", showChat);
|
|
3594
|
+
chatContainer.classList.toggle("hidden", !showChat);
|
|
3317
3595
|
}
|
|
3318
3596
|
updateInteractiveControls();
|
|
3319
3597
|
}
|
|
3320
3598
|
|
|
3321
3599
|
function syncSessionModalUI() {
|
|
3322
3600
|
var modeHint = document.getElementById("mode-description");
|
|
3601
|
+
var kindHint = document.getElementById("session-kind-description");
|
|
3323
3602
|
var tool = "claude";
|
|
3603
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
3324
3604
|
|
|
3325
3605
|
state.sessionTool = tool;
|
|
3326
3606
|
state.modeValue = getSafeModeForTool(tool, state.modeValue || state.chatMode || "default");
|
|
3327
3607
|
|
|
3328
|
-
|
|
3608
|
+
var kindCards = document.querySelectorAll("#session-kind-cards .session-kind-card");
|
|
3609
|
+
if (kindCards.length) {
|
|
3610
|
+
kindCards.forEach(function(card) {
|
|
3611
|
+
card.classList.toggle("active", card.getAttribute("data-session-kind") === sessionKind);
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3329
3615
|
var modeCards = document.querySelectorAll("#mode-cards .mode-card");
|
|
3330
3616
|
if (modeCards.length) {
|
|
3331
3617
|
modeCards.forEach(function(card) {
|
|
@@ -3333,11 +3619,22 @@
|
|
|
3333
3619
|
});
|
|
3334
3620
|
}
|
|
3335
3621
|
|
|
3622
|
+
if (kindHint) kindHint.textContent = getSessionKindHint(sessionKind);
|
|
3336
3623
|
if (modeHint) modeHint.textContent = getToolModeHint(tool, state.modeValue);
|
|
3337
3624
|
}
|
|
3338
3625
|
|
|
3339
3626
|
function updateSessionSnapshot(snapshot) {
|
|
3340
3627
|
if (!snapshot || !snapshot.id) return;
|
|
3628
|
+
if (snapshot.id === state.selectedId || (snapshot.sessionKind === "structured") || snapshot.structuredState) {
|
|
3629
|
+
console.log("[WAND] updateSessionSnapshot", snapshot.id, JSON.stringify({
|
|
3630
|
+
status: snapshot.status,
|
|
3631
|
+
exitCode: snapshot.exitCode,
|
|
3632
|
+
sessionKind: snapshot.sessionKind,
|
|
3633
|
+
runner: snapshot.runner,
|
|
3634
|
+
inFlight: snapshot.structuredState && snapshot.structuredState.inFlight,
|
|
3635
|
+
msgCount: snapshot.messages && snapshot.messages.length
|
|
3636
|
+
}));
|
|
3637
|
+
}
|
|
3341
3638
|
var updated = false;
|
|
3342
3639
|
var prevSession = null;
|
|
3343
3640
|
state.sessions = state.sessions.map(function(session) {
|
|
@@ -3367,14 +3664,34 @@
|
|
|
3367
3664
|
var localOutput = localSession.output || "";
|
|
3368
3665
|
var serverOutput = serverSession.output || "";
|
|
3369
3666
|
var keepLocalOutput = localOutput.length > serverOutput.length;
|
|
3667
|
+
var localStructuredState = localSession.structuredState || null;
|
|
3668
|
+
var serverStructuredState = serverSession.structuredState || null;
|
|
3669
|
+
var localHasPendingAssistant = !!(localSession.messages && localSession.messages.length && (function() {
|
|
3670
|
+
var last = localSession.messages[localSession.messages.length - 1];
|
|
3671
|
+
return last && last.role === "assistant" && Array.isArray(last.content) && last.content.some(function(block) {
|
|
3672
|
+
return block && block.__processing;
|
|
3673
|
+
});
|
|
3674
|
+
})());
|
|
3675
|
+
var preserveLocalStructuredProgress = (localSession.sessionKind === "structured")
|
|
3676
|
+
&& !!localStructuredState
|
|
3677
|
+
&& localStructuredState.inFlight === true
|
|
3678
|
+
&& (!serverStructuredState || serverStructuredState.inFlight !== true)
|
|
3679
|
+
&& localHasPendingAssistant
|
|
3680
|
+
&& !!localStructuredState.activeRequestId
|
|
3681
|
+
&& (!serverStructuredState || !serverStructuredState.activeRequestId || serverStructuredState.activeRequestId === localStructuredState.activeRequestId);
|
|
3370
3682
|
|
|
3371
3683
|
if (keepLocalOutput) {
|
|
3372
3684
|
merged.output = localOutput;
|
|
3373
3685
|
}
|
|
3374
3686
|
|
|
3687
|
+
if (preserveLocalStructuredProgress) {
|
|
3688
|
+
merged.status = localSession.status || merged.status;
|
|
3689
|
+
merged.structuredState = Object.assign({}, serverStructuredState || {}, localStructuredState, { inFlight: true });
|
|
3690
|
+
merged.messages = localSession.messages;
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3375
3693
|
if (localSession.id === state.selectedId) {
|
|
3376
3694
|
if (localSession.permissionBlocked && serverSession.permissionBlocked === false) {
|
|
3377
|
-
// server explicitly resolved it; keep resolved state
|
|
3378
3695
|
} else if (localSession.permissionBlocked && !serverSession.permissionBlocked) {
|
|
3379
3696
|
merged.permissionBlocked = true;
|
|
3380
3697
|
}
|
|
@@ -3409,15 +3726,13 @@
|
|
|
3409
3726
|
|
|
3410
3727
|
function getPreferredSessionId(sessions) {
|
|
3411
3728
|
if (!sessions || !sessions.length) return null;
|
|
3412
|
-
// Keep currently selected session as long as it still exists
|
|
3413
3729
|
if (state.selectedId) {
|
|
3414
3730
|
var stillExists = sessions.find(function(session) { return session.id === state.selectedId; });
|
|
3415
3731
|
if (stillExists) return stillExists.id;
|
|
3732
|
+
return null;
|
|
3416
3733
|
}
|
|
3417
|
-
// No selection — pick a running session, or fall back to most recent
|
|
3418
3734
|
var runningSession = sessions.find(function(session) { return session.status === "running"; });
|
|
3419
3735
|
if (runningSession) return runningSession.id;
|
|
3420
|
-
// Fall back to most recent non-archived session (sessions are sorted newest first)
|
|
3421
3736
|
var recent = sessions.find(function(session) { return !session.archived; });
|
|
3422
3737
|
return recent ? recent.id : sessions[0].id;
|
|
3423
3738
|
}
|
|
@@ -3444,7 +3759,10 @@
|
|
|
3444
3759
|
return mergeServerSession(localSession, serverSession);
|
|
3445
3760
|
});
|
|
3446
3761
|
|
|
3447
|
-
|
|
3762
|
+
var preferredSessionId = getPreferredSessionId(state.sessions);
|
|
3763
|
+
if (preferredSessionId !== undefined) {
|
|
3764
|
+
state.selectedId = preferredSessionId;
|
|
3765
|
+
}
|
|
3448
3766
|
persistSelectedId();
|
|
3449
3767
|
if (state.modalOpen) {
|
|
3450
3768
|
updateSessionsList();
|
|
@@ -3461,13 +3779,22 @@
|
|
|
3461
3779
|
}
|
|
3462
3780
|
}
|
|
3463
3781
|
updateShellChrome();
|
|
3782
|
+
|
|
3783
|
+
// For structured sessions, loadOutput is needed to fetch messages
|
|
3784
|
+
// (the sessions list endpoint doesn't include them).
|
|
3785
|
+
// On page refresh this is the only place that can trigger it.
|
|
3786
|
+
if (state.selectedId) {
|
|
3787
|
+
var sel = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
3788
|
+
if (isStructuredSession(sel)) {
|
|
3789
|
+
loadOutput(state.selectedId);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3464
3792
|
})
|
|
3465
3793
|
.catch(function(e) {
|
|
3466
3794
|
console.error("[wand] loadSessions failed:", e);
|
|
3467
3795
|
});
|
|
3468
3796
|
}
|
|
3469
3797
|
|
|
3470
|
-
|
|
3471
3798
|
function updateSessionsList() {
|
|
3472
3799
|
var listEl = document.getElementById("sessions-list");
|
|
3473
3800
|
var countEl = document.getElementById("session-count");
|
|
@@ -3495,22 +3822,23 @@
|
|
|
3495
3822
|
|
|
3496
3823
|
if (summaryEl && summaryEl.textContent !== terminalTitle) summaryEl.textContent = terminalTitle;
|
|
3497
3824
|
if (titleEl && titleEl.textContent !== terminalTitle) titleEl.textContent = terminalTitle;
|
|
3498
|
-
if (infoEl
|
|
3499
|
-
infoEl.textContent = terminalInfo;
|
|
3500
|
-
}
|
|
3825
|
+
if (infoEl) infoEl.textContent = selectedSession ? (terminalInfo + " · " + getSessionKindDescription(selectedSession)) : terminalInfo;
|
|
3501
3826
|
|
|
3502
|
-
// Update session info bar at bottom
|
|
3503
3827
|
var cwdEl = document.getElementById("session-cwd-display");
|
|
3504
3828
|
var modeEl = document.getElementById("session-mode-display");
|
|
3829
|
+
var kindEl = document.getElementById("session-kind-display");
|
|
3505
3830
|
var statusEl = document.getElementById("session-status-display");
|
|
3506
3831
|
var exitEl = document.getElementById("session-exit-display");
|
|
3507
3832
|
var cwdText = selectedSession && selectedSession.cwd ? selectedSession.cwd : "未设置目录";
|
|
3508
3833
|
var modeText = selectedSession ? getModeLabel(selectedSession.mode) : "默认";
|
|
3834
|
+
var kindText = selectedSession ? getSessionKindLabel(selectedSession) : "PTY";
|
|
3509
3835
|
var exitText = "exit=" + (selectedSession && selectedSession.exitCode !== undefined ? selectedSession.exitCode : "n/a");
|
|
3510
3836
|
if (cwdEl && cwdEl.textContent !== cwdText) cwdEl.textContent = cwdText;
|
|
3511
3837
|
if (modeEl && modeEl.textContent !== modeText) modeEl.textContent = modeText;
|
|
3838
|
+
if (kindEl && kindEl.textContent !== kindText) kindEl.textContent = kindText;
|
|
3512
3839
|
if (statusEl && statusEl.textContent !== terminalInfo) statusEl.textContent = terminalInfo;
|
|
3513
3840
|
if (exitEl && exitEl.textContent !== exitText) exitEl.textContent = exitText;
|
|
3841
|
+
updateAutoApproveIndicator();
|
|
3514
3842
|
|
|
3515
3843
|
if (!state.terminal && terminalContainer && selectedSession) {
|
|
3516
3844
|
initTerminal();
|
|
@@ -3558,7 +3886,12 @@
|
|
|
3558
3886
|
clearTimeout(chatRenderTimer);
|
|
3559
3887
|
chatRenderTimer = null;
|
|
3560
3888
|
}
|
|
3561
|
-
|
|
3889
|
+
var sess = state.sessions.find(function(s) { return s.id === id; });
|
|
3890
|
+
var url = "/api/sessions/" + id;
|
|
3891
|
+
if (isStructuredSession(sess)) {
|
|
3892
|
+
url += "?format=chat";
|
|
3893
|
+
}
|
|
3894
|
+
return fetch(url, { credentials: "same-origin" })
|
|
3562
3895
|
.then(function(res) { return res.json(); })
|
|
3563
3896
|
.then(function(data) {
|
|
3564
3897
|
if (data.error) {
|
|
@@ -3585,11 +3918,11 @@
|
|
|
3585
3918
|
}
|
|
3586
3919
|
|
|
3587
3920
|
function selectSession(id) {
|
|
3921
|
+
var foundSession = state.sessions.find(function(item) { return item.id === id; });
|
|
3922
|
+
console.log("[WAND] selectSession id:", id, "found:", !!foundSession, "sessionKind:", foundSession && foundSession.sessionKind, "runner:", foundSession && foundSession.runner, "isStructured:", isStructuredSession(foundSession));
|
|
3588
3923
|
state.selectedId = id;
|
|
3589
3924
|
persistSelectedId();
|
|
3590
|
-
|
|
3591
|
-
state.lastRenderedMsgCount = 0;
|
|
3592
|
-
state.lastRenderedEmpty = null;
|
|
3925
|
+
resetChatRenderCache();
|
|
3593
3926
|
state.currentMessages = [];
|
|
3594
3927
|
if (chatRenderTimer) { clearTimeout(chatRenderTimer); chatRenderTimer = null; }
|
|
3595
3928
|
// Reset todo progress bar
|
|
@@ -3662,6 +3995,7 @@
|
|
|
3662
3995
|
modal.classList.remove("hidden");
|
|
3663
3996
|
lastFocusedElement = document.activeElement;
|
|
3664
3997
|
state.sessionTool = getPreferredTool();
|
|
3998
|
+
state.sessionCreateKind = "pty";
|
|
3665
3999
|
state.modeValue = getSafeModeForTool(state.sessionTool, state.modeValue || state.chatMode);
|
|
3666
4000
|
syncSessionModalUI();
|
|
3667
4001
|
loadRecentPathBubbles();
|
|
@@ -4176,9 +4510,7 @@
|
|
|
4176
4510
|
state.selectedId = data.id;
|
|
4177
4511
|
persistSelectedId();
|
|
4178
4512
|
state.drafts[data.id] = "";
|
|
4179
|
-
|
|
4180
|
-
state.lastRenderedMsgCount = 0;
|
|
4181
|
-
state.lastRenderedEmpty = null;
|
|
4513
|
+
resetChatRenderCache();
|
|
4182
4514
|
return refreshAll();
|
|
4183
4515
|
})
|
|
4184
4516
|
.then(function() { focusInputBox(true); })
|
|
@@ -4191,13 +4523,45 @@
|
|
|
4191
4523
|
var cwdEl = document.getElementById("cwd");
|
|
4192
4524
|
var errorEl = document.getElementById("modal-error");
|
|
4193
4525
|
var command = getPreferredTool();
|
|
4526
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
4194
4527
|
|
|
4195
4528
|
hideError(errorEl);
|
|
4196
4529
|
|
|
4197
4530
|
var defaultCwd = getEffectiveCwd();
|
|
4531
|
+
var cwd = cwdEl.value.trim() || defaultCwd;
|
|
4198
4532
|
var selectedMode = getSafeModeForTool(command, state.modeValue);
|
|
4199
|
-
|
|
4200
|
-
|
|
4533
|
+
|
|
4534
|
+
if (sessionKind === "structured") {
|
|
4535
|
+
startStructuredSessionFromModal(cwd, selectedMode, errorEl);
|
|
4536
|
+
return;
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4539
|
+
runPtyCommandFromModal(command, cwd, selectedMode, errorEl);
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
function startStructuredSessionFromModal(cwd, mode, errorEl) {
|
|
4543
|
+
console.log("[WAND] startStructuredSessionFromModal cwd:", cwd, "mode:", mode);
|
|
4544
|
+
state.modeValue = mode;
|
|
4545
|
+
state.chatMode = mode;
|
|
4546
|
+
state.sessionTool = "claude";
|
|
4547
|
+
state.preferredCommand = "claude";
|
|
4548
|
+
syncComposerModeSelect();
|
|
4549
|
+
return createStructuredSession(undefined, cwd, mode)
|
|
4550
|
+
.then(function(data) {
|
|
4551
|
+
closeSessionModal();
|
|
4552
|
+
closeSessionsDrawer();
|
|
4553
|
+
return data;
|
|
4554
|
+
})
|
|
4555
|
+
.then(function() { focusInputBox(true); })
|
|
4556
|
+
.catch(function(error) {
|
|
4557
|
+
showError(errorEl, (error && error.message) || "无法启动结构化会话,请确认 Claude 已正确安装。");
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4561
|
+
function runPtyCommandFromModal(command, cwd, mode, errorEl) {
|
|
4562
|
+
console.log("[WAND] runPtyCommandFromModal command:", command, "cwd:", cwd, "mode:", mode);
|
|
4563
|
+
state.modeValue = mode;
|
|
4564
|
+
state.chatMode = mode;
|
|
4201
4565
|
state.sessionTool = command;
|
|
4202
4566
|
state.preferredCommand = command;
|
|
4203
4567
|
syncComposerModeSelect();
|
|
@@ -4208,8 +4572,8 @@
|
|
|
4208
4572
|
credentials: "same-origin",
|
|
4209
4573
|
body: JSON.stringify({
|
|
4210
4574
|
command: command,
|
|
4211
|
-
cwd:
|
|
4212
|
-
mode:
|
|
4575
|
+
cwd: cwd,
|
|
4576
|
+
mode: mode
|
|
4213
4577
|
})
|
|
4214
4578
|
})
|
|
4215
4579
|
.then(function(res) { return res.json(); })
|
|
@@ -4219,22 +4583,27 @@
|
|
|
4219
4583
|
return;
|
|
4220
4584
|
}
|
|
4221
4585
|
state.selectedId = data.id;
|
|
4586
|
+
console.log("[WAND] runPtyCommandFromModal created session:", data.id, "sessionKind:", data.sessionKind, "runner:", data.runner);
|
|
4222
4587
|
persistSelectedId();
|
|
4223
4588
|
state.drafts[data.id] = "";
|
|
4224
|
-
|
|
4225
|
-
state.lastRenderedMsgCount = 0;
|
|
4226
|
-
state.lastRenderedEmpty = null;
|
|
4589
|
+
resetChatRenderCache();
|
|
4227
4590
|
closeSessionModal();
|
|
4228
4591
|
closeSessionsDrawer();
|
|
4229
4592
|
return refreshAll();
|
|
4230
4593
|
})
|
|
4231
|
-
.then(function() {
|
|
4594
|
+
.then(function() {
|
|
4595
|
+
if (state.selectedId) {
|
|
4596
|
+
console.log("[WAND] runPtyCommandFromModal calling selectSession:", state.selectedId);
|
|
4597
|
+
selectSession(state.selectedId);
|
|
4598
|
+
} else {
|
|
4599
|
+
focusInputBox(true);
|
|
4600
|
+
}
|
|
4601
|
+
})
|
|
4232
4602
|
.catch(function() {
|
|
4233
4603
|
showError(errorEl, "无法启动会话,请确认 Claude 已正确安装。");
|
|
4234
4604
|
});
|
|
4235
4605
|
}
|
|
4236
4606
|
|
|
4237
|
-
// Blank-chat CWD inline display + dropdown
|
|
4238
4607
|
function initBlankChatCwd() {
|
|
4239
4608
|
var cwdEl = document.getElementById("blank-chat-cwd");
|
|
4240
4609
|
if (!cwdEl) return;
|
|
@@ -4653,14 +5022,14 @@
|
|
|
4653
5022
|
state.selectedId = data.id;
|
|
4654
5023
|
persistSelectedId();
|
|
4655
5024
|
state.drafts[data.id] = "";
|
|
4656
|
-
|
|
4657
|
-
state.lastRenderedMsgCount = 0;
|
|
4658
|
-
state.lastRenderedEmpty = null;
|
|
4659
|
-
switchToSessionView(data.id);
|
|
5025
|
+
resetChatRenderCache();
|
|
4660
5026
|
updateSessionSnapshot(data);
|
|
4661
5027
|
updateSessionsList();
|
|
5028
|
+
switchToSessionView(data.id);
|
|
4662
5029
|
subscribeToSession(data.id);
|
|
4663
5030
|
loadOutput(data.id).then(function() {
|
|
5031
|
+
welcomeInput.placeholder = "输入你的问题,按 Enter 发送...";
|
|
5032
|
+
welcomeInput.disabled = false;
|
|
4664
5033
|
focusInputBox(true);
|
|
4665
5034
|
});
|
|
4666
5035
|
})
|
|
@@ -4711,14 +5080,12 @@
|
|
|
4711
5080
|
state.selectedId = data.id;
|
|
4712
5081
|
persistSelectedId();
|
|
4713
5082
|
state.drafts[data.id] = "";
|
|
4714
|
-
|
|
4715
|
-
state.lastRenderedMsgCount = 0;
|
|
4716
|
-
state.lastRenderedEmpty = null;
|
|
5083
|
+
resetChatRenderCache();
|
|
4717
5084
|
if (inputBox) inputBox.value = "";
|
|
4718
5085
|
if (welcomeInput) welcomeInput.value = "";
|
|
4719
|
-
switchToSessionView(data.id);
|
|
4720
5086
|
updateSessionSnapshot(data);
|
|
4721
5087
|
updateSessionsList();
|
|
5088
|
+
switchToSessionView(data.id);
|
|
4722
5089
|
// Subscribe to new session via WebSocket
|
|
4723
5090
|
subscribeToSession(data.id);
|
|
4724
5091
|
return loadOutput(data.id);
|
|
@@ -4730,6 +5097,7 @@
|
|
|
4730
5097
|
|
|
4731
5098
|
function switchToSessionView(sessionId) {
|
|
4732
5099
|
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
5100
|
+
console.log("[WAND] switchToSessionView id:", sessionId, "found:", !!session, "sessionKind:", session && session.sessionKind, "runner:", session && session.runner, "isStructured:", isStructuredSession(session), "currentView:", state.currentView);
|
|
4733
5101
|
var blankChat = document.getElementById("blank-chat");
|
|
4734
5102
|
var terminalContainer = document.getElementById("output");
|
|
4735
5103
|
var chatContainer = document.getElementById("chat-output");
|
|
@@ -4737,28 +5105,36 @@
|
|
|
4737
5105
|
var terminalTitle = document.getElementById("terminal-title");
|
|
4738
5106
|
var terminalInfo = document.getElementById("terminal-info");
|
|
4739
5107
|
var sessionSummary = document.querySelector(".session-summary-value");
|
|
5108
|
+
var structured = isStructuredSession(session);
|
|
4740
5109
|
|
|
4741
5110
|
if (blankChat) blankChat.classList.add("hidden");
|
|
4742
|
-
if (terminalContainer)
|
|
5111
|
+
if (terminalContainer) {
|
|
5112
|
+
terminalContainer.classList.toggle("hidden", structured);
|
|
5113
|
+
}
|
|
4743
5114
|
if (chatContainer) {
|
|
4744
5115
|
chatContainer.classList.remove("hidden");
|
|
4745
5116
|
}
|
|
4746
5117
|
if (stopBtn) stopBtn.classList.remove("hidden");
|
|
4747
5118
|
|
|
5119
|
+
if (structured) {
|
|
5120
|
+
state.currentView = "chat";
|
|
5121
|
+
} else {
|
|
5122
|
+
state.currentView = "terminal";
|
|
5123
|
+
}
|
|
5124
|
+
|
|
4748
5125
|
var title = session ? shortCommand(session.command) : "Wand";
|
|
4749
5126
|
var info = session ? getSessionStatusLabel(session) : "开始对话";
|
|
4750
5127
|
if (terminalTitle) terminalTitle.textContent = title;
|
|
4751
5128
|
if (terminalInfo) terminalInfo.textContent = info;
|
|
4752
5129
|
if (sessionSummary) sessionSummary.textContent = title;
|
|
4753
5130
|
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
5131
|
+
if (!structured) {
|
|
5132
|
+
if (!state.terminal) initTerminal();
|
|
5133
|
+
if (state.terminal && state.fitAddon) {
|
|
5134
|
+
ensureTerminalFit();
|
|
5135
|
+
}
|
|
4759
5136
|
}
|
|
4760
|
-
|
|
4761
|
-
// Calling renderChat() prematurely would render with stale/empty messages.
|
|
5137
|
+
applyCurrentView();
|
|
4762
5138
|
focusInputBox();
|
|
4763
5139
|
}
|
|
4764
5140
|
|
|
@@ -4773,9 +5149,12 @@
|
|
|
4773
5149
|
var value = inputBox ? inputBox.value : "";
|
|
4774
5150
|
var selectedSession = state.sessions.find(function(session) { return session.id === state.selectedId; }) || null;
|
|
4775
5151
|
if (value) {
|
|
4776
|
-
console.log("[
|
|
5152
|
+
console.log("[WAND] sendInputFromBox", {
|
|
4777
5153
|
sessionId: state.selectedId,
|
|
4778
5154
|
sessionStatus: selectedSession ? selectedSession.status : null,
|
|
5155
|
+
sessionKind: selectedSession ? selectedSession.sessionKind : null,
|
|
5156
|
+
runner: selectedSession ? selectedSession.runner : null,
|
|
5157
|
+
isStructured: isStructuredSession(selectedSession),
|
|
4779
5158
|
view: state.currentView,
|
|
4780
5159
|
wsConnected: state.wsConnected,
|
|
4781
5160
|
terminalInteractive: state.terminalInteractive,
|
|
@@ -4784,6 +5163,11 @@
|
|
|
4784
5163
|
// Clear todo progress bar at the start of a new user turn
|
|
4785
5164
|
var todoEl = document.getElementById("todo-progress");
|
|
4786
5165
|
if (todoEl) todoEl.classList.add("hidden");
|
|
5166
|
+
|
|
5167
|
+
if (isStructuredSession(selectedSession)) {
|
|
5168
|
+
return postStructuredInput(value, inputBox, selectedSession);
|
|
5169
|
+
}
|
|
5170
|
+
|
|
4787
5171
|
// Send text + Enter as a single call to avoid race conditions
|
|
4788
5172
|
var combinedInput = value + getControlInput("enter");
|
|
4789
5173
|
var isOffline = !state.wsConnected;
|
|
@@ -4824,6 +5208,79 @@
|
|
|
4824
5208
|
return Promise.resolve();
|
|
4825
5209
|
}
|
|
4826
5210
|
|
|
5211
|
+
function postStructuredInput(input, inputBox, session) {
|
|
5212
|
+
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 });
|
|
5213
|
+
if (!state.selectedId || !input) return Promise.resolve();
|
|
5214
|
+
if (!session) {
|
|
5215
|
+
showToast("会话不存在,请重新选择或新建会话。", "error");
|
|
5216
|
+
return Promise.resolve();
|
|
5217
|
+
}
|
|
5218
|
+
if (session.structuredState && session.structuredState.inFlight && session.status === "running") {
|
|
5219
|
+
// Disable send button while processing, show subtle indicator
|
|
5220
|
+
var sendBtn = document.getElementById("send-input-button");
|
|
5221
|
+
if (sendBtn) sendBtn.disabled = true;
|
|
5222
|
+
showToast("正在等待上一条消息处理完成…", "info");
|
|
5223
|
+
return Promise.resolve();
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
// Immediately render user message with thinking indicator
|
|
5227
|
+
var userTurn = { role: "user", content: [{ type: "text", text: input }] };
|
|
5228
|
+
var thinkingTurn = { role: "assistant", content: [{ type: "text", text: "", __processing: true }] };
|
|
5229
|
+
var userMsgs = Array.isArray(session.messages) ? session.messages.slice() : [];
|
|
5230
|
+
userMsgs.push(userTurn);
|
|
5231
|
+
userMsgs.push(thinkingTurn);
|
|
5232
|
+
session.messages = userMsgs;
|
|
5233
|
+
state.currentMessages = userMsgs;
|
|
5234
|
+
// Mark inFlight optimistically to prevent double-send via WS updates
|
|
5235
|
+
if (session.structuredState) {
|
|
5236
|
+
session.structuredState.inFlight = true;
|
|
5237
|
+
}
|
|
5238
|
+
session.status = "running";
|
|
5239
|
+
if (inputBox) {
|
|
5240
|
+
inputBox.value = "";
|
|
5241
|
+
autoResizeInput(inputBox);
|
|
5242
|
+
}
|
|
5243
|
+
// Disable send button so user can't double-send
|
|
5244
|
+
var sendBtnEl = document.getElementById("send-input-button");
|
|
5245
|
+
if (sendBtnEl) sendBtnEl.disabled = true;
|
|
5246
|
+
updateInputHint("思考中…");
|
|
5247
|
+
setDraftValue("");
|
|
5248
|
+
renderChat(true);
|
|
5249
|
+
|
|
5250
|
+
return fetch("/api/structured-sessions/" + state.selectedId + "/messages", {
|
|
5251
|
+
method: "POST",
|
|
5252
|
+
headers: { "Content-Type": "application/json" },
|
|
5253
|
+
credentials: "same-origin",
|
|
5254
|
+
body: JSON.stringify({ input: input })
|
|
5255
|
+
})
|
|
5256
|
+
.then(function(res) { return res.json(); })
|
|
5257
|
+
.then(function(snapshot) {
|
|
5258
|
+
if (snapshot && snapshot.error) {
|
|
5259
|
+
throw new Error(snapshot.error);
|
|
5260
|
+
}
|
|
5261
|
+
if (snapshot && snapshot.id) {
|
|
5262
|
+
updateSessionSnapshot(snapshot);
|
|
5263
|
+
if (snapshot.messages && snapshot.messages.length > 0) {
|
|
5264
|
+
state.currentMessages = snapshot.messages;
|
|
5265
|
+
}
|
|
5266
|
+
renderChat(true);
|
|
5267
|
+
updateInputHint("Enter 发送 · Shift+Enter 换行");
|
|
5268
|
+
}
|
|
5269
|
+
})
|
|
5270
|
+
.catch(function(error) {
|
|
5271
|
+
var errSendBtn = document.getElementById("send-input-button");
|
|
5272
|
+
if (errSendBtn) errSendBtn.disabled = false;
|
|
5273
|
+
updateInputHint("Enter 发送 · Shift+Enter 换行");
|
|
5274
|
+
showToast((error && error.message) || "无法发送结构化消息。", "error");
|
|
5275
|
+
throw error;
|
|
5276
|
+
});
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
function updateInputHint(text) {
|
|
5280
|
+
var hint = document.querySelector(".input-hint");
|
|
5281
|
+
if (hint) hint.textContent = text;
|
|
5282
|
+
}
|
|
5283
|
+
|
|
4827
5284
|
function getInputErrorMessage(error) {
|
|
4828
5285
|
if (error && (error.errorCode === "SESSION_NOT_RUNNING" || error.errorCode === "SESSION_NO_PTY")) {
|
|
4829
5286
|
return "会话已结束;若存在 Claude 历史会话,将在你下次发送消息时自动恢复。";
|
|
@@ -4875,6 +5332,7 @@
|
|
|
4875
5332
|
}
|
|
4876
5333
|
|
|
4877
5334
|
function ensureSessionReadyForInput(session, errorEl) {
|
|
5335
|
+
console.log("[WAND] ensureSessionReadyForInput session:", session && { id: session.id, status: session.status, claudeSessionId: session.claudeSessionId, sessionKind: session.sessionKind, runner: session.runner });
|
|
4878
5336
|
if (!session) {
|
|
4879
5337
|
showToast("会话不存在,请重新选择或新建会话。", "error");
|
|
4880
5338
|
return Promise.resolve(null);
|
|
@@ -5170,21 +5628,24 @@
|
|
|
5170
5628
|
}
|
|
5171
5629
|
|
|
5172
5630
|
function updateInteractiveControls() {
|
|
5631
|
+
var selectedSession = state.sessions.find(function(session) { return session.id === state.selectedId; });
|
|
5632
|
+
var structured = isStructuredSession(selectedSession);
|
|
5173
5633
|
// Update both toggle buttons (topbar and terminal-header)
|
|
5174
5634
|
var toggles = ["terminal-interactive-toggle-top"];
|
|
5175
5635
|
toggles.forEach(function(id) {
|
|
5176
5636
|
var toggle = document.getElementById(id);
|
|
5177
5637
|
if (toggle) {
|
|
5178
5638
|
toggle.classList.toggle("active", state.terminalInteractive);
|
|
5639
|
+
toggle.classList.toggle("hidden", structured || state.currentView !== "terminal" || !selectedSession);
|
|
5179
5640
|
}
|
|
5180
5641
|
});
|
|
5181
5642
|
// Inline keyboard visibility follows current view
|
|
5182
5643
|
var inlineKeyboard = document.getElementById("inline-keyboard");
|
|
5183
|
-
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", state.currentView !== "terminal");
|
|
5644
|
+
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", structured || state.currentView !== "terminal");
|
|
5184
5645
|
var inputHint = document.querySelector(".input-hint");
|
|
5185
|
-
if (inputHint) inputHint.classList.toggle("hidden", state.currentView === "terminal");
|
|
5646
|
+
if (inputHint) inputHint.classList.toggle("hidden", structured ? false : state.currentView === "terminal");
|
|
5186
5647
|
var container = document.getElementById("output");
|
|
5187
|
-
if (container) container.classList.toggle("interactive", state.terminalInteractive);
|
|
5648
|
+
if (container) container.classList.toggle("interactive", !structured && state.terminalInteractive);
|
|
5188
5649
|
}
|
|
5189
5650
|
|
|
5190
5651
|
function captureTerminalInput(event) {
|
|
@@ -5540,6 +6001,7 @@
|
|
|
5540
6001
|
}
|
|
5541
6002
|
|
|
5542
6003
|
function resumeSession(sessionId, errorEl) {
|
|
6004
|
+
console.log("[WAND] resumeSession sessionId:", sessionId);
|
|
5543
6005
|
if (!sessionId) return Promise.resolve(null);
|
|
5544
6006
|
return fetch("/api/sessions/" + encodeURIComponent(sessionId) + "/resume", {
|
|
5545
6007
|
method: "POST",
|
|
@@ -5601,9 +6063,7 @@
|
|
|
5601
6063
|
|
|
5602
6064
|
function activateSession(data) {
|
|
5603
6065
|
if (!data || !data.id) return Promise.resolve();
|
|
5604
|
-
|
|
5605
|
-
state.lastRenderedMsgCount = 0;
|
|
5606
|
-
state.lastRenderedEmpty = null;
|
|
6066
|
+
resetChatRenderCache();
|
|
5607
6067
|
switchToSessionView(data.id);
|
|
5608
6068
|
updateSessionSnapshot(data);
|
|
5609
6069
|
updateSessionsList();
|
|
@@ -5614,6 +6074,7 @@
|
|
|
5614
6074
|
}
|
|
5615
6075
|
|
|
5616
6076
|
function resumeSessionFromList(sessionId) {
|
|
6077
|
+
console.log("[WAND] resumeSessionFromList sessionId:", sessionId);
|
|
5617
6078
|
return resumeSession(sessionId).then(function(data) {
|
|
5618
6079
|
if (!data) return null;
|
|
5619
6080
|
return activateSession(data).then(function() {
|
|
@@ -5702,6 +6163,7 @@
|
|
|
5702
6163
|
}
|
|
5703
6164
|
|
|
5704
6165
|
function handleResumeAction(actionButton) {
|
|
6166
|
+
console.log("[WAND] handleResumeAction sessionId:", actionButton.dataset.sessionId);
|
|
5705
6167
|
actionButton.disabled = true;
|
|
5706
6168
|
resumeSessionFromList(actionButton.dataset.sessionId)
|
|
5707
6169
|
.finally(function() {
|
|
@@ -5712,6 +6174,7 @@
|
|
|
5712
6174
|
function handleResumeHistoryAction(actionButton) {
|
|
5713
6175
|
var claudeSessionId = actionButton.dataset.claudeSessionId;
|
|
5714
6176
|
var cwd = actionButton.dataset.cwd;
|
|
6177
|
+
console.log("[WAND] handleResumeHistoryAction claudeSessionId:", claudeSessionId, "cwd:", cwd);
|
|
5715
6178
|
if (!claudeSessionId) return;
|
|
5716
6179
|
actionButton.disabled = true;
|
|
5717
6180
|
resumeClaudeHistorySession(claudeSessionId, cwd)
|
|
@@ -6757,15 +7220,18 @@
|
|
|
6757
7220
|
state.fitAddon.fit();
|
|
6758
7221
|
maybeScrollTerminalToBottom("resize");
|
|
6759
7222
|
if (state.selectedId && state.terminal) {
|
|
6760
|
-
var
|
|
6761
|
-
if (
|
|
6762
|
-
state.
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
7223
|
+
var selectedSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7224
|
+
if (!isStructuredSession(selectedSess)) {
|
|
7225
|
+
var nextSize = { cols: state.terminal.cols, rows: state.terminal.rows };
|
|
7226
|
+
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
7227
|
+
state.lastResize = nextSize;
|
|
7228
|
+
fetch("/api/sessions/" + state.selectedId + "/resize", {
|
|
7229
|
+
method: "POST",
|
|
7230
|
+
headers: { "Content-Type": "application/json" },
|
|
7231
|
+
credentials: "same-origin",
|
|
7232
|
+
body: JSON.stringify(nextSize)
|
|
7233
|
+
}).catch(function() {});
|
|
7234
|
+
}
|
|
6769
7235
|
}
|
|
6770
7236
|
}
|
|
6771
7237
|
} else if (attempt < maxAttempts) {
|
|
@@ -6805,6 +7271,10 @@
|
|
|
6805
7271
|
|
|
6806
7272
|
if (!state.selectedId) return;
|
|
6807
7273
|
|
|
7274
|
+
// Skip resize for structured sessions (no PTY)
|
|
7275
|
+
var resizeSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7276
|
+
if (isStructuredSession(resizeSess)) return;
|
|
7277
|
+
|
|
6808
7278
|
// Only send resize API call if dimensions actually changed
|
|
6809
7279
|
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
6810
7280
|
state.lastResize = nextSize;
|
|
@@ -6899,6 +7369,17 @@
|
|
|
6899
7369
|
updateSessionSnapshot(snapshot);
|
|
6900
7370
|
if (msg.sessionId === state.selectedId) {
|
|
6901
7371
|
state.currentMessages = getPreferredMessages(snapshot, msg.data.output, false);
|
|
7372
|
+
// Structured session with inFlight: keep __processing placeholder
|
|
7373
|
+
// so the loading indicator stays visible until assistant content arrives
|
|
7374
|
+
if (msg.data.sessionKind === 'structured') {
|
|
7375
|
+
var outSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
7376
|
+
if (outSession && outSession.structuredState && outSession.structuredState.inFlight) {
|
|
7377
|
+
var lastCur = state.currentMessages[state.currentMessages.length - 1];
|
|
7378
|
+
if (!lastCur || lastCur.role !== 'assistant') {
|
|
7379
|
+
state.currentMessages.push({ role: "assistant", content: [{ type: "text", text: "", __processing: true }] });
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
6902
7383
|
updateTaskDisplay();
|
|
6903
7384
|
scheduleChatRender();
|
|
6904
7385
|
}
|
|
@@ -6937,8 +7418,19 @@
|
|
|
6937
7418
|
if (msg.data && msg.data.messages) {
|
|
6938
7419
|
endedSnapshot.messages = msg.data.messages;
|
|
6939
7420
|
}
|
|
7421
|
+
if (msg.data && msg.data.structuredState) {
|
|
7422
|
+
endedSnapshot.structuredState = msg.data.structuredState;
|
|
7423
|
+
}
|
|
6940
7424
|
updateSessionSnapshot(endedSnapshot);
|
|
6941
7425
|
|
|
7426
|
+
// Re-enable send button when structured session finishes
|
|
7427
|
+
if (msg.sessionId === state.selectedId) {
|
|
7428
|
+
var endedSendBtn = document.getElementById("send-input-button");
|
|
7429
|
+
if (endedSendBtn) endedSendBtn.disabled = false;
|
|
7430
|
+
updateInputHint("Enter 发送 · Shift+Enter 换行");
|
|
7431
|
+
// Trigger status bar completion animation
|
|
7432
|
+
scheduleChatRender(true);
|
|
7433
|
+
}
|
|
6942
7434
|
// Notify user when a session completes (browser + in-app if backgrounded or not viewing)
|
|
6943
7435
|
var endedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
6944
7436
|
var endedName = endedSession ? (endedSession.label || endedSession.command || msg.sessionId) : msg.sessionId;
|
|
@@ -7013,7 +7505,24 @@
|
|
|
7013
7505
|
break;
|
|
7014
7506
|
case 'status':
|
|
7015
7507
|
if (msg.sessionId && msg.data) {
|
|
7508
|
+
console.log('[WAND] ws status', msg.sessionId, JSON.stringify(msg.data));
|
|
7016
7509
|
var statusUpdate = { id: msg.sessionId };
|
|
7510
|
+
if (Object.prototype.hasOwnProperty.call(msg.data, 'status')) {
|
|
7511
|
+
statusUpdate.status = msg.data.status;
|
|
7512
|
+
}
|
|
7513
|
+
if (Object.prototype.hasOwnProperty.call(msg.data, 'exitCode')) {
|
|
7514
|
+
statusUpdate.exitCode = msg.data.exitCode;
|
|
7515
|
+
}
|
|
7516
|
+
if (msg.data.structuredState) {
|
|
7517
|
+
statusUpdate.structuredState = msg.data.structuredState;
|
|
7518
|
+
} else if (Object.prototype.hasOwnProperty.call(msg.data, 'status')) {
|
|
7519
|
+
var existingSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
7520
|
+
if (existingSession && existingSession.sessionKind === 'structured') {
|
|
7521
|
+
statusUpdate.structuredState = Object.assign({}, existingSession.structuredState || {}, {
|
|
7522
|
+
inFlight: msg.data.status === 'running'
|
|
7523
|
+
});
|
|
7524
|
+
}
|
|
7525
|
+
}
|
|
7017
7526
|
if (Object.prototype.hasOwnProperty.call(msg.data, 'permissionBlocked')) {
|
|
7018
7527
|
statusUpdate.permissionBlocked = !!msg.data.permissionBlocked;
|
|
7019
7528
|
}
|
|
@@ -7056,9 +7565,19 @@
|
|
|
7056
7565
|
if (msg.data.permissionBlocked === false) {
|
|
7057
7566
|
statusUpdate.pendingEscalation = null;
|
|
7058
7567
|
}
|
|
7568
|
+
if (msg.data.approvalStats) {
|
|
7569
|
+
statusUpdate.approvalStats = msg.data.approvalStats;
|
|
7570
|
+
}
|
|
7059
7571
|
updateSessionSnapshot(statusUpdate);
|
|
7060
7572
|
if (msg.sessionId === state.selectedId) {
|
|
7061
7573
|
updateTaskDisplay();
|
|
7574
|
+
if (msg.data.approvalStats) {
|
|
7575
|
+
updateApprovalStats();
|
|
7576
|
+
}
|
|
7577
|
+
// Re-render chat when structured session inFlight state changes
|
|
7578
|
+
if (statusUpdate.structuredState) {
|
|
7579
|
+
scheduleChatRender();
|
|
7580
|
+
}
|
|
7062
7581
|
}
|
|
7063
7582
|
}
|
|
7064
7583
|
break;
|
|
@@ -7098,9 +7617,12 @@
|
|
|
7098
7617
|
var isBlocked = pendingEscalation || (selectedSession && selectedSession.permissionBlocked);
|
|
7099
7618
|
|
|
7100
7619
|
if (isBlocked) {
|
|
7620
|
+
var isAutoApprove = selectedSession && selectedSession.autoApprovePermissions;
|
|
7101
7621
|
// Show permission label in input composer area
|
|
7102
7622
|
if (permissionLabel) {
|
|
7103
|
-
if (
|
|
7623
|
+
if (isAutoApprove) {
|
|
7624
|
+
permissionLabel.textContent = "自动批准中...";
|
|
7625
|
+
} else if (pendingEscalation) {
|
|
7104
7626
|
var reason = pendingEscalation.reason || "等待授权";
|
|
7105
7627
|
var target = pendingEscalation.target ? " · " + pendingEscalation.target : "";
|
|
7106
7628
|
permissionLabel.textContent = reason + target;
|
|
@@ -7108,7 +7630,14 @@
|
|
|
7108
7630
|
permissionLabel.textContent = "等待授权";
|
|
7109
7631
|
}
|
|
7110
7632
|
}
|
|
7111
|
-
if (permissionActionsEl)
|
|
7633
|
+
if (permissionActionsEl) {
|
|
7634
|
+
permissionActionsEl.classList.remove("hidden");
|
|
7635
|
+
// Hide approve/deny buttons when auto-approve is active
|
|
7636
|
+
var approveBtn = document.getElementById("approve-permission-btn");
|
|
7637
|
+
var denyBtn = document.getElementById("deny-permission-btn");
|
|
7638
|
+
if (approveBtn) approveBtn.classList.toggle("hidden", !!isAutoApprove);
|
|
7639
|
+
if (denyBtn) denyBtn.classList.toggle("hidden", !!isAutoApprove);
|
|
7640
|
+
}
|
|
7112
7641
|
// Hide top task bar — permission info is already shown in the composer
|
|
7113
7642
|
taskEl.textContent = "";
|
|
7114
7643
|
taskEl.classList.add("hidden");
|
|
@@ -7128,6 +7657,39 @@
|
|
|
7128
7657
|
}
|
|
7129
7658
|
}
|
|
7130
7659
|
|
|
7660
|
+
function updateApprovalStats() {
|
|
7661
|
+
var container = document.getElementById("approval-stats");
|
|
7662
|
+
if (!container) return;
|
|
7663
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7664
|
+
var stats = selectedSession && selectedSession.approvalStats;
|
|
7665
|
+
if (!stats || stats.total === 0) {
|
|
7666
|
+
container.className = "approval-stats hidden";
|
|
7667
|
+
container.innerHTML = "";
|
|
7668
|
+
return;
|
|
7669
|
+
}
|
|
7670
|
+
container.className = "approval-stats";
|
|
7671
|
+
container.innerHTML =
|
|
7672
|
+
'<span class="approval-stats-divider"></span>' +
|
|
7673
|
+
'<span class="approval-stats-badge" id="approval-stats-badge" title="本次会话自动批准统计">' +
|
|
7674
|
+
'<svg class="approval-stats-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>' +
|
|
7675
|
+
'<span class="approval-stats-total">' + stats.total + '</span>' +
|
|
7676
|
+
'</span>' +
|
|
7677
|
+
'<span class="approval-stats-popup" id="approval-stats-popup">' +
|
|
7678
|
+
'<span class="approval-stats-popup-title">自动批准统计</span>' +
|
|
7679
|
+
(stats.command > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">⚡</span><span class="approval-stats-row-label">命令执行</span><span class="approval-stats-row-count">' + stats.command + '</span></span>' : '') +
|
|
7680
|
+
(stats.file > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">📝</span><span class="approval-stats-row-label">文件写入</span><span class="approval-stats-row-count">' + stats.file + '</span></span>' : '') +
|
|
7681
|
+
(stats.tool > 0 ? '<span class="approval-stats-row"><span class="approval-stats-row-icon">🔧</span><span class="approval-stats-row-label">其他工具</span><span class="approval-stats-row-count">' + stats.tool + '</span></span>' : '') +
|
|
7682
|
+
'<span class="approval-stats-row approval-stats-row-total"><span class="approval-stats-row-icon">∑</span><span class="approval-stats-row-label">合计</span><span class="approval-stats-row-count">' + stats.total + '</span></span>' +
|
|
7683
|
+
'</span>';
|
|
7684
|
+
// Pulse animation on the badge
|
|
7685
|
+
var badge = container.querySelector(".approval-stats-badge");
|
|
7686
|
+
if (badge) {
|
|
7687
|
+
badge.classList.remove("approval-stats-pulse");
|
|
7688
|
+
void badge.offsetWidth;
|
|
7689
|
+
badge.classList.add("approval-stats-pulse");
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7131
7693
|
function approvePermission() {
|
|
7132
7694
|
if (!state.selectedId) return;
|
|
7133
7695
|
var approveBtn = document.getElementById("approve-permission-btn");
|
|
@@ -7184,6 +7746,49 @@
|
|
|
7184
7746
|
});
|
|
7185
7747
|
}
|
|
7186
7748
|
|
|
7749
|
+
function toggleAutoApprove() {
|
|
7750
|
+
if (!state.selectedId) return;
|
|
7751
|
+
var toggle = document.getElementById("auto-approve-toggle");
|
|
7752
|
+
if (toggle) toggle.style.opacity = "0.5";
|
|
7753
|
+
fetch("/api/sessions/" + encodeURIComponent(state.selectedId) + "/toggle-auto-approve", {
|
|
7754
|
+
method: "POST",
|
|
7755
|
+
credentials: "same-origin"
|
|
7756
|
+
})
|
|
7757
|
+
.then(function(res) { return res.json(); })
|
|
7758
|
+
.then(function(data) {
|
|
7759
|
+
if (data && data.error) {
|
|
7760
|
+
showToast(data.error, "error");
|
|
7761
|
+
return;
|
|
7762
|
+
}
|
|
7763
|
+
updateSessionSnapshot(data);
|
|
7764
|
+
updateAutoApproveIndicator();
|
|
7765
|
+
var enabled = data.autoApprovePermissions;
|
|
7766
|
+
showToast(enabled ? "自动批准已开启" : "自动批准已关闭", "info");
|
|
7767
|
+
})
|
|
7768
|
+
.catch(function(error) {
|
|
7769
|
+
showToast((error && error.message) || "无法切换自动批准。", "error");
|
|
7770
|
+
})
|
|
7771
|
+
.finally(function() {
|
|
7772
|
+
if (toggle) toggle.style.opacity = "";
|
|
7773
|
+
});
|
|
7774
|
+
}
|
|
7775
|
+
|
|
7776
|
+
function updateAutoApproveIndicator() {
|
|
7777
|
+
var toggle = document.getElementById("auto-approve-toggle");
|
|
7778
|
+
if (!toggle) return;
|
|
7779
|
+
var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
7780
|
+
var enabled = selectedSession && selectedSession.autoApprovePermissions;
|
|
7781
|
+
if (enabled) {
|
|
7782
|
+
toggle.className = "auto-approve-indicator active";
|
|
7783
|
+
toggle.title = "自动批准已启用 — 点击关闭";
|
|
7784
|
+
toggle.textContent = "🛡 自动批准";
|
|
7785
|
+
} else {
|
|
7786
|
+
toggle.className = "auto-approve-indicator";
|
|
7787
|
+
toggle.title = "自动批准已关闭 — 点击开启";
|
|
7788
|
+
toggle.textContent = "🛡 手动";
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7791
|
+
|
|
7187
7792
|
function updateTerminalOutput(output, sessionId, mode) {
|
|
7188
7793
|
if (!state.terminal) return false;
|
|
7189
7794
|
return syncTerminalBuffer(sessionId || state.selectedId, output, { mode: mode || "append" });
|
|
@@ -7392,6 +7997,10 @@
|
|
|
7392
7997
|
// Force full render if message count changed or explicitly requested
|
|
7393
7998
|
var forceRender = forceFullRender || msgCount !== state.lastRenderedMsgCount;
|
|
7394
7999
|
if (!forceRender && msgCount === state.lastRenderedMsgCount && outputHash === state.lastRenderedHash) {
|
|
8000
|
+
// Even if message content hasn't changed, update the status bar
|
|
8001
|
+
// (inFlight state may have changed without new message content)
|
|
8002
|
+
var chatMessages = chatOutput.querySelector(".chat-messages");
|
|
8003
|
+
if (chatMessages) renderStructuredStatusBar(chatMessages, selectedSession);
|
|
7395
8004
|
return;
|
|
7396
8005
|
}
|
|
7397
8006
|
var prevHash = state.lastRenderedHash;
|
|
@@ -7545,6 +8154,9 @@
|
|
|
7545
8154
|
fullRenderChat();
|
|
7546
8155
|
}
|
|
7547
8156
|
|
|
8157
|
+
// Update structured session status bar (in-flight / completed indicator)
|
|
8158
|
+
renderStructuredStatusBar(chatMessages, selectedSession);
|
|
8159
|
+
|
|
7548
8160
|
// Update todo progress bar from latest messages
|
|
7549
8161
|
updateTodoProgress(messages);
|
|
7550
8162
|
}
|
|
@@ -8267,11 +8879,53 @@
|
|
|
8267
8879
|
'</div>';
|
|
8268
8880
|
}
|
|
8269
8881
|
|
|
8882
|
+
function buildToolResultMap(contentBlocks) {
|
|
8883
|
+
var toolResults = {};
|
|
8884
|
+
if (!Array.isArray(contentBlocks)) return toolResults;
|
|
8885
|
+
for (var i = 0; i < contentBlocks.length; i++) {
|
|
8886
|
+
var block = contentBlocks[i];
|
|
8887
|
+
if (block && block.type === "tool_result") {
|
|
8888
|
+
var toolUseId = block.tool_use_id;
|
|
8889
|
+
if (!toolUseId) continue;
|
|
8890
|
+
if (!toolResults[toolUseId]) {
|
|
8891
|
+
toolResults[toolUseId] = [];
|
|
8892
|
+
}
|
|
8893
|
+
toolResults[toolUseId].push(block);
|
|
8894
|
+
}
|
|
8895
|
+
}
|
|
8896
|
+
return toolResults;
|
|
8897
|
+
}
|
|
8898
|
+
|
|
8899
|
+
function pickToolResultForDisplay(toolResults, toolUseId) {
|
|
8900
|
+
var entries = toolResults && toolUseId ? toolResults[toolUseId] : null;
|
|
8901
|
+
if (!entries || !entries.length) return null;
|
|
8902
|
+
for (var i = 0; i < entries.length - 1; i++) {
|
|
8903
|
+
if (isRecoverableToolError(entries[i], entries[i + 1])) {
|
|
8904
|
+
return entries[i + 1];
|
|
8905
|
+
}
|
|
8906
|
+
}
|
|
8907
|
+
return entries[entries.length - 1];
|
|
8908
|
+
}
|
|
8909
|
+
|
|
8910
|
+
function hasRecoveredToolNoise(toolResults, toolUseId) {
|
|
8911
|
+
var entries = toolResults && toolUseId ? toolResults[toolUseId] : null;
|
|
8912
|
+
if (!entries || entries.length < 2) return false;
|
|
8913
|
+
for (var i = 0; i < entries.length - 1; i++) {
|
|
8914
|
+
if (isRecoverableToolError(entries[i], entries[i + 1])) {
|
|
8915
|
+
return true;
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
return false;
|
|
8919
|
+
}
|
|
8920
|
+
|
|
8921
|
+
function renderRecoveredToolHint(toolName) {
|
|
8922
|
+
return '<div class="structured-tool-hint">已自动恢复一次 ' + escapeHtml(getToolDisplayName(toolName)) + ' 参数问题</div>';
|
|
8923
|
+
}
|
|
8924
|
+
|
|
8270
8925
|
function renderStructuredMessage(msg) {
|
|
8271
8926
|
var role = msg.role;
|
|
8272
8927
|
var avatar = role === "assistant" ? '<div class="chat-message-avatar">AI</div>' : "";
|
|
8273
8928
|
|
|
8274
|
-
// Empty content array — streaming placeholder, show typing indicator
|
|
8275
8929
|
if (!msg.content || msg.content.length === 0) {
|
|
8276
8930
|
if (role === "assistant") {
|
|
8277
8931
|
return '<div class="chat-message ' + role + '">' +
|
|
@@ -8282,18 +8936,7 @@
|
|
|
8282
8936
|
return "";
|
|
8283
8937
|
}
|
|
8284
8938
|
|
|
8285
|
-
|
|
8286
|
-
var toolResults = {};
|
|
8287
|
-
for (var i = 0; i < msg.content.length; i++) {
|
|
8288
|
-
var block = msg.content[i];
|
|
8289
|
-
if (block && block.type === "tool_result") {
|
|
8290
|
-
var toolUseId = block.tool_use_id;
|
|
8291
|
-
if (toolUseId) {
|
|
8292
|
-
toolResults[toolUseId] = block;
|
|
8293
|
-
}
|
|
8294
|
-
}
|
|
8295
|
-
}
|
|
8296
|
-
|
|
8939
|
+
var toolResults = buildToolResultMap(msg.content);
|
|
8297
8940
|
var blocksHtml = "";
|
|
8298
8941
|
|
|
8299
8942
|
try {
|
|
@@ -8302,19 +8945,16 @@
|
|
|
8302
8945
|
try {
|
|
8303
8946
|
blocksHtml += renderContentBlock(block, role, toolResults, i);
|
|
8304
8947
|
} catch (e) {
|
|
8305
|
-
// Render error for individual block
|
|
8306
8948
|
blocksHtml += '<div class="render-error">消息块渲染失败</div>';
|
|
8307
8949
|
}
|
|
8308
8950
|
}
|
|
8309
8951
|
} catch (e) {
|
|
8310
|
-
// Render error for entire message
|
|
8311
8952
|
return '<div class="chat-message ' + role + '">' +
|
|
8312
8953
|
avatar +
|
|
8313
8954
|
'<div class="chat-message-bubble"><div class="render-error">消息渲染失败</div></div>' +
|
|
8314
8955
|
'</div>';
|
|
8315
8956
|
}
|
|
8316
8957
|
|
|
8317
|
-
// Build usage indicator for assistant messages
|
|
8318
8958
|
var usageHtml = "";
|
|
8319
8959
|
if (role === "assistant" && msg.usage) {
|
|
8320
8960
|
var u = msg.usage;
|
|
@@ -8334,21 +8974,21 @@
|
|
|
8334
8974
|
usageHtml +
|
|
8335
8975
|
'</div>';
|
|
8336
8976
|
}
|
|
8337
|
-
|
|
8338
8977
|
function renderContentBlock(block, role, toolResults, index) {
|
|
8339
8978
|
if (!block || !block.type) return "";
|
|
8340
8979
|
|
|
8341
8980
|
switch (block.type) {
|
|
8342
8981
|
case "text":
|
|
8982
|
+
if (role === "assistant" && block.__processing) {
|
|
8983
|
+
return '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
|
8984
|
+
}
|
|
8343
8985
|
return role === "assistant" ? renderMarkdown(block.text || "") : escapeHtml(block.text || "");
|
|
8344
8986
|
|
|
8345
8987
|
case "thinking":
|
|
8346
8988
|
var thinkingText = block.thinking || "";
|
|
8347
|
-
// Compact display: brain icon + brief text, click to expand
|
|
8348
8989
|
var preview = thinkingText.length > 60 ? thinkingText.slice(0, 57) + "…" : thinkingText;
|
|
8349
8990
|
var isStreaming = block.thinking === undefined && block.type === "thinking";
|
|
8350
8991
|
if (isStreaming) {
|
|
8351
|
-
// During streaming: show 3-line scrollable area
|
|
8352
8992
|
return '<div class="thinking-inline thinking-streaming" data-thinking="">' +
|
|
8353
8993
|
'<div class="thinking-streaming-inner">' +
|
|
8354
8994
|
'<span class="thinking-streaming-icon spinning">🧠</span>' +
|
|
@@ -8363,11 +9003,14 @@
|
|
|
8363
9003
|
'</div>';
|
|
8364
9004
|
|
|
8365
9005
|
case "tool_use":
|
|
8366
|
-
var toolResult = toolResults
|
|
8367
|
-
|
|
9006
|
+
var toolResult = pickToolResultForDisplay(toolResults, block.id);
|
|
9007
|
+
var rendered = renderToolUseCard(block, toolResult, index);
|
|
9008
|
+
if (hasRecoveredToolNoise(toolResults, block.id)) {
|
|
9009
|
+
rendered = renderRecoveredToolHint(block.name || "工具") + rendered;
|
|
9010
|
+
}
|
|
9011
|
+
return rendered;
|
|
8368
9012
|
|
|
8369
9013
|
case "tool_result":
|
|
8370
|
-
// tool_result 已经在 tool_use 渲染时处理了,不再单独渲染
|
|
8371
9014
|
return "";
|
|
8372
9015
|
|
|
8373
9016
|
default:
|
|
@@ -8375,11 +9018,11 @@
|
|
|
8375
9018
|
}
|
|
8376
9019
|
}
|
|
8377
9020
|
|
|
8378
|
-
// Lightweight inline display — used for Read, Glob, Grep, WebFetch, WebSearch, TodoRead
|
|
8379
9021
|
function renderInlineTool(block, toolResult, toolName, fileInfo, extraInfo) {
|
|
8380
9022
|
var toolId = block.id || "tool-" + toolName;
|
|
8381
9023
|
var inputData = block.input || {};
|
|
8382
|
-
var resultContent = (toolResult && toolResult.content)
|
|
9024
|
+
var resultContent = extractToolResultText(toolResult && toolResult.content);
|
|
9025
|
+
|
|
8383
9026
|
var isError = toolResult && toolResult.is_error;
|
|
8384
9027
|
var hasResult = resultContent.length > 0;
|
|
8385
9028
|
var statusIcon = isError ? "⚠️" : (hasResult ? "✅" : "⏳");
|
|
@@ -8482,7 +9125,8 @@
|
|
|
8482
9125
|
function renderTerminalTool(block, toolResult, toolName) {
|
|
8483
9126
|
var inputData = block.input || {};
|
|
8484
9127
|
var command = inputData.command || inputData.cmd || "";
|
|
8485
|
-
var resultContent = (toolResult && toolResult.content)
|
|
9128
|
+
var resultContent = extractToolResultText(toolResult && toolResult.content);
|
|
9129
|
+
|
|
8486
9130
|
var isError = toolResult && toolResult.is_error;
|
|
8487
9131
|
var exitCode = inputData.exitCode;
|
|
8488
9132
|
var hasResult = resultContent.length > 0;
|
|
@@ -8699,7 +9343,7 @@
|
|
|
8699
9343
|
|
|
8700
9344
|
if (toolResult) {
|
|
8701
9345
|
var isError = toolResult.is_error;
|
|
8702
|
-
var content = toolResult.content
|
|
9346
|
+
var content = extractToolResultText(toolResult.content);
|
|
8703
9347
|
statusClass = isError ? "error" : "success";
|
|
8704
9348
|
headerIcon = getToolIcon(toolName);
|
|
8705
9349
|
var hasContent = content && content.trim().length > 0;
|