@co0ontty/wand 1.3.6 → 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 +6 -0
- package/dist/claude-pty-bridge.js +38 -1
- 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 +37 -5
- package/dist/server-session-routes.d.ts +2 -1
- package/dist/server-session-routes.js +113 -7
- package/dist/server.js +8 -3
- 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 +15 -0
- package/dist/web-ui/content/scripts.js +656 -82
- package/dist/web-ui/content/styles.css +164 -7
- 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 {
|
|
@@ -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>' : '') +
|
|
@@ -1256,13 +1338,21 @@
|
|
|
1256
1338
|
explorer.innerHTML = '<div class="file-explorer empty">No working directory.</div>';
|
|
1257
1339
|
return;
|
|
1258
1340
|
}
|
|
1341
|
+
state.fileExplorerLoading = true;
|
|
1342
|
+
state.allFiles = [];
|
|
1259
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>';
|
|
1260
1344
|
// Update the cwd display
|
|
1261
1345
|
if (cwdEl) cwdEl.textContent = cwd;
|
|
1262
1346
|
// Fetch with git status
|
|
1263
1347
|
fetch("/api/directory?q=" + encodeURIComponent(cwd) + "&gitStatus=true", { credentials: "same-origin" })
|
|
1264
|
-
.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
|
+
})
|
|
1265
1354
|
.then(function(items) {
|
|
1355
|
+
state.fileExplorerLoading = false;
|
|
1266
1356
|
if (!items || items.length === 0) {
|
|
1267
1357
|
explorer.innerHTML = '<div class="file-explorer empty">Empty directory or inaccessible.</div>';
|
|
1268
1358
|
return;
|
|
@@ -1271,6 +1361,7 @@
|
|
|
1271
1361
|
filterFileTree();
|
|
1272
1362
|
})
|
|
1273
1363
|
.catch(function() {
|
|
1364
|
+
state.fileExplorerLoading = false;
|
|
1274
1365
|
explorer.innerHTML = '<div class="file-explorer empty">Failed to load files.</div>';
|
|
1275
1366
|
});
|
|
1276
1367
|
}
|
|
@@ -1667,7 +1758,7 @@
|
|
|
1667
1758
|
if (session.claudeSessionId) {
|
|
1668
1759
|
var shortId = session.claudeSessionId.slice(0, 8);
|
|
1669
1760
|
sessionIdDisplay = '<span class="session-id" title="' + escapeHtml(session.claudeSessionId) + '">' + escapeHtml(shortId) + '</span>';
|
|
1670
|
-
if (session.status !== "running" && !state.sessionsManageMode) {
|
|
1761
|
+
if (session.status !== "running" && !state.sessionsManageMode && !isStructuredSession(session)) {
|
|
1671
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>';
|
|
1672
1763
|
}
|
|
1673
1764
|
}
|
|
@@ -1677,6 +1768,7 @@
|
|
|
1677
1768
|
}
|
|
1678
1769
|
|
|
1679
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);
|
|
1680
1772
|
var actionsHtml = '<span class="session-actions">' + resumeButton + deleteButton + '</span>';
|
|
1681
1773
|
|
|
1682
1774
|
return '<div class="session-item' + activeClass + selectedClass + '" data-session-id="' + session.id + '" role="button" tabindex="0">' +
|
|
@@ -1686,6 +1778,7 @@
|
|
|
1686
1778
|
'<div class="session-main">' +
|
|
1687
1779
|
'<div class="session-command">' + escapeHtml(session.resumedFromSessionId ? session.command.replace(/\s+--resume\s+\S+/, '') : session.command) + '</div>' +
|
|
1688
1780
|
'<div class="session-meta">' +
|
|
1781
|
+
modeBadge +
|
|
1689
1782
|
'<span>' + escapeHtml(modeName) + '</span>' +
|
|
1690
1783
|
'<span class="session-status ' + metaStatusClass + '">' + escapeHtml(metaStatus) + '</span>' +
|
|
1691
1784
|
sessionIdDisplay +
|
|
@@ -1697,6 +1790,15 @@
|
|
|
1697
1790
|
'</div>' +
|
|
1698
1791
|
'</div>';
|
|
1699
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
|
+
|
|
1700
1802
|
function renderModeCards(selectedMode) {
|
|
1701
1803
|
var modes = [
|
|
1702
1804
|
{ id: "managed", label: "托管", desc: "全自动完成任务" },
|
|
@@ -1714,19 +1816,48 @@
|
|
|
1714
1816
|
}).join("");
|
|
1715
1817
|
}
|
|
1716
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
|
+
|
|
1717
1840
|
function renderSessionModal() {
|
|
1718
1841
|
var modalTool = getPreferredTool();
|
|
1719
1842
|
var modalMode = getSafeModeForTool(modalTool, state.modeValue || state.chatMode || "default");
|
|
1843
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
1720
1844
|
return '<section id="session-modal" class="modal-backdrop hidden">' +
|
|
1721
1845
|
'<div class="modal session-modal">' +
|
|
1722
1846
|
'<div class="modal-header">' +
|
|
1723
1847
|
'<div>' +
|
|
1724
1848
|
'<h2 class="modal-title">新对话</h2>' +
|
|
1725
|
-
'<p class="modal-subtitle">启动 Claude
|
|
1849
|
+
'<p class="modal-subtitle">启动 Claude 会话,选择会话类型、模式和工作目录。</p>' +
|
|
1726
1850
|
'</div>' +
|
|
1727
1851
|
'<button id="close-modal-button" class="btn btn-ghost btn-icon">×</button>' +
|
|
1728
1852
|
'</div>' +
|
|
1729
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>' +
|
|
1730
1861
|
'<div class="field">' +
|
|
1731
1862
|
'<label class="field-label">模式</label>' +
|
|
1732
1863
|
'<div id="mode-cards" class="mode-cards">' +
|
|
@@ -1899,6 +2030,16 @@
|
|
|
1899
2030
|
quickStartSession();
|
|
1900
2031
|
});
|
|
1901
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
|
+
}
|
|
1902
2043
|
initBlankChatCwd();
|
|
1903
2044
|
|
|
1904
2045
|
var sessionsList = document.getElementById("sessions-list");
|
|
@@ -1911,6 +2052,17 @@
|
|
|
1911
2052
|
// Claude session ID badge click-to-copy (event delegation on document)
|
|
1912
2053
|
document.addEventListener("click", handleClaudeIdCopy);
|
|
1913
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
|
+
|
|
1914
2066
|
var modeCardsEl = document.getElementById("mode-cards");
|
|
1915
2067
|
if (modeCardsEl) modeCardsEl.addEventListener("click", function(e) {
|
|
1916
2068
|
var card = e.target.closest(".mode-card");
|
|
@@ -1941,7 +2093,7 @@
|
|
|
1941
2093
|
persistSelectedId();
|
|
1942
2094
|
resetChatRenderCache();
|
|
1943
2095
|
closeSessionsDrawer();
|
|
1944
|
-
|
|
2096
|
+
render();
|
|
1945
2097
|
});
|
|
1946
2098
|
var refreshBtn = document.getElementById("sidebar-refresh-btn");
|
|
1947
2099
|
if (refreshBtn) refreshBtn.addEventListener("click", function() {
|
|
@@ -1996,6 +2148,8 @@
|
|
|
1996
2148
|
if (approvePermissionBtn) approvePermissionBtn.addEventListener("click", approvePermission);
|
|
1997
2149
|
var denyPermissionBtn = document.getElementById("deny-permission-btn");
|
|
1998
2150
|
if (denyPermissionBtn) denyPermissionBtn.addEventListener("click", denyPermission);
|
|
2151
|
+
var autoApproveToggle = document.getElementById("auto-approve-toggle");
|
|
2152
|
+
if (autoApproveToggle) autoApproveToggle.addEventListener("click", toggleAutoApprove);
|
|
1999
2153
|
var sendBtn = document.getElementById("send-input-button");
|
|
2000
2154
|
if (sendBtn) sendBtn.addEventListener("click", function() {
|
|
2001
2155
|
closeSessionsDrawer();
|
|
@@ -2256,7 +2410,7 @@
|
|
|
2256
2410
|
state.selectedId = null;
|
|
2257
2411
|
persistSelectedId();
|
|
2258
2412
|
state.drafts = {};
|
|
2259
|
-
|
|
2413
|
+
render();
|
|
2260
2414
|
// 聚焦到目录输入框
|
|
2261
2415
|
setTimeout(function() {
|
|
2262
2416
|
var folderInput = document.getElementById("folder-picker-input");
|
|
@@ -2539,7 +2693,7 @@
|
|
|
2539
2693
|
|
|
2540
2694
|
function activateSessionItem(sessionId) {
|
|
2541
2695
|
var session = state.sessions.find(function(s) { return s.id === sessionId; });
|
|
2542
|
-
if (session && session.status !== "running") {
|
|
2696
|
+
if (session && session.status !== "running" && !isStructuredSession(session)) {
|
|
2543
2697
|
resumeSessionFromList(sessionId);
|
|
2544
2698
|
} else {
|
|
2545
2699
|
selectSession(sessionId);
|
|
@@ -3336,6 +3490,35 @@
|
|
|
3336
3490
|
return hints[mode] || '';
|
|
3337
3491
|
}
|
|
3338
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
|
+
|
|
3339
3522
|
function syncComposerModeSelect() {
|
|
3340
3523
|
var select = document.getElementById("chat-mode-select");
|
|
3341
3524
|
if (!select) return;
|
|
@@ -3346,29 +3529,89 @@
|
|
|
3346
3529
|
if (modeHint) modeHint.textContent = getModeHint(state.chatMode);
|
|
3347
3530
|
}
|
|
3348
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
|
+
|
|
3349
3567
|
function applyCurrentView() {
|
|
3350
3568
|
var hasSession = !!state.selectedId;
|
|
3351
3569
|
var terminalBtn = document.getElementById("view-terminal-btn");
|
|
3352
3570
|
var terminalContainer = document.getElementById("output");
|
|
3353
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
|
+
}
|
|
3354
3583
|
|
|
3355
|
-
if (terminalBtn)
|
|
3356
|
-
|
|
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
|
+
}
|
|
3357
3592
|
if (chatContainer) {
|
|
3358
|
-
chatContainer.classList.
|
|
3359
|
-
chatContainer.classList.
|
|
3593
|
+
chatContainer.classList.toggle("active", showChat);
|
|
3594
|
+
chatContainer.classList.toggle("hidden", !showChat);
|
|
3360
3595
|
}
|
|
3361
3596
|
updateInteractiveControls();
|
|
3362
3597
|
}
|
|
3363
3598
|
|
|
3364
3599
|
function syncSessionModalUI() {
|
|
3365
3600
|
var modeHint = document.getElementById("mode-description");
|
|
3601
|
+
var kindHint = document.getElementById("session-kind-description");
|
|
3366
3602
|
var tool = "claude";
|
|
3603
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
3367
3604
|
|
|
3368
3605
|
state.sessionTool = tool;
|
|
3369
3606
|
state.modeValue = getSafeModeForTool(tool, state.modeValue || state.chatMode || "default");
|
|
3370
3607
|
|
|
3371
|
-
|
|
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
|
+
|
|
3372
3615
|
var modeCards = document.querySelectorAll("#mode-cards .mode-card");
|
|
3373
3616
|
if (modeCards.length) {
|
|
3374
3617
|
modeCards.forEach(function(card) {
|
|
@@ -3376,11 +3619,22 @@
|
|
|
3376
3619
|
});
|
|
3377
3620
|
}
|
|
3378
3621
|
|
|
3622
|
+
if (kindHint) kindHint.textContent = getSessionKindHint(sessionKind);
|
|
3379
3623
|
if (modeHint) modeHint.textContent = getToolModeHint(tool, state.modeValue);
|
|
3380
3624
|
}
|
|
3381
3625
|
|
|
3382
3626
|
function updateSessionSnapshot(snapshot) {
|
|
3383
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
|
+
}
|
|
3384
3638
|
var updated = false;
|
|
3385
3639
|
var prevSession = null;
|
|
3386
3640
|
state.sessions = state.sessions.map(function(session) {
|
|
@@ -3410,14 +3664,34 @@
|
|
|
3410
3664
|
var localOutput = localSession.output || "";
|
|
3411
3665
|
var serverOutput = serverSession.output || "";
|
|
3412
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);
|
|
3413
3682
|
|
|
3414
3683
|
if (keepLocalOutput) {
|
|
3415
3684
|
merged.output = localOutput;
|
|
3416
3685
|
}
|
|
3417
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
|
+
|
|
3418
3693
|
if (localSession.id === state.selectedId) {
|
|
3419
3694
|
if (localSession.permissionBlocked && serverSession.permissionBlocked === false) {
|
|
3420
|
-
// server explicitly resolved it; keep resolved state
|
|
3421
3695
|
} else if (localSession.permissionBlocked && !serverSession.permissionBlocked) {
|
|
3422
3696
|
merged.permissionBlocked = true;
|
|
3423
3697
|
}
|
|
@@ -3452,15 +3726,13 @@
|
|
|
3452
3726
|
|
|
3453
3727
|
function getPreferredSessionId(sessions) {
|
|
3454
3728
|
if (!sessions || !sessions.length) return null;
|
|
3455
|
-
// Keep currently selected session as long as it still exists
|
|
3456
3729
|
if (state.selectedId) {
|
|
3457
3730
|
var stillExists = sessions.find(function(session) { return session.id === state.selectedId; });
|
|
3458
3731
|
if (stillExists) return stillExists.id;
|
|
3732
|
+
return null;
|
|
3459
3733
|
}
|
|
3460
|
-
// No selection — pick a running session, or fall back to most recent
|
|
3461
3734
|
var runningSession = sessions.find(function(session) { return session.status === "running"; });
|
|
3462
3735
|
if (runningSession) return runningSession.id;
|
|
3463
|
-
// Fall back to most recent non-archived session (sessions are sorted newest first)
|
|
3464
3736
|
var recent = sessions.find(function(session) { return !session.archived; });
|
|
3465
3737
|
return recent ? recent.id : sessions[0].id;
|
|
3466
3738
|
}
|
|
@@ -3487,7 +3759,10 @@
|
|
|
3487
3759
|
return mergeServerSession(localSession, serverSession);
|
|
3488
3760
|
});
|
|
3489
3761
|
|
|
3490
|
-
|
|
3762
|
+
var preferredSessionId = getPreferredSessionId(state.sessions);
|
|
3763
|
+
if (preferredSessionId !== undefined) {
|
|
3764
|
+
state.selectedId = preferredSessionId;
|
|
3765
|
+
}
|
|
3491
3766
|
persistSelectedId();
|
|
3492
3767
|
if (state.modalOpen) {
|
|
3493
3768
|
updateSessionsList();
|
|
@@ -3504,13 +3779,22 @@
|
|
|
3504
3779
|
}
|
|
3505
3780
|
}
|
|
3506
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
|
+
}
|
|
3507
3792
|
})
|
|
3508
3793
|
.catch(function(e) {
|
|
3509
3794
|
console.error("[wand] loadSessions failed:", e);
|
|
3510
3795
|
});
|
|
3511
3796
|
}
|
|
3512
3797
|
|
|
3513
|
-
|
|
3514
3798
|
function updateSessionsList() {
|
|
3515
3799
|
var listEl = document.getElementById("sessions-list");
|
|
3516
3800
|
var countEl = document.getElementById("session-count");
|
|
@@ -3538,22 +3822,23 @@
|
|
|
3538
3822
|
|
|
3539
3823
|
if (summaryEl && summaryEl.textContent !== terminalTitle) summaryEl.textContent = terminalTitle;
|
|
3540
3824
|
if (titleEl && titleEl.textContent !== terminalTitle) titleEl.textContent = terminalTitle;
|
|
3541
|
-
if (infoEl
|
|
3542
|
-
infoEl.textContent = terminalInfo;
|
|
3543
|
-
}
|
|
3825
|
+
if (infoEl) infoEl.textContent = selectedSession ? (terminalInfo + " · " + getSessionKindDescription(selectedSession)) : terminalInfo;
|
|
3544
3826
|
|
|
3545
|
-
// Update session info bar at bottom
|
|
3546
3827
|
var cwdEl = document.getElementById("session-cwd-display");
|
|
3547
3828
|
var modeEl = document.getElementById("session-mode-display");
|
|
3829
|
+
var kindEl = document.getElementById("session-kind-display");
|
|
3548
3830
|
var statusEl = document.getElementById("session-status-display");
|
|
3549
3831
|
var exitEl = document.getElementById("session-exit-display");
|
|
3550
3832
|
var cwdText = selectedSession && selectedSession.cwd ? selectedSession.cwd : "未设置目录";
|
|
3551
3833
|
var modeText = selectedSession ? getModeLabel(selectedSession.mode) : "默认";
|
|
3834
|
+
var kindText = selectedSession ? getSessionKindLabel(selectedSession) : "PTY";
|
|
3552
3835
|
var exitText = "exit=" + (selectedSession && selectedSession.exitCode !== undefined ? selectedSession.exitCode : "n/a");
|
|
3553
3836
|
if (cwdEl && cwdEl.textContent !== cwdText) cwdEl.textContent = cwdText;
|
|
3554
3837
|
if (modeEl && modeEl.textContent !== modeText) modeEl.textContent = modeText;
|
|
3838
|
+
if (kindEl && kindEl.textContent !== kindText) kindEl.textContent = kindText;
|
|
3555
3839
|
if (statusEl && statusEl.textContent !== terminalInfo) statusEl.textContent = terminalInfo;
|
|
3556
3840
|
if (exitEl && exitEl.textContent !== exitText) exitEl.textContent = exitText;
|
|
3841
|
+
updateAutoApproveIndicator();
|
|
3557
3842
|
|
|
3558
3843
|
if (!state.terminal && terminalContainer && selectedSession) {
|
|
3559
3844
|
initTerminal();
|
|
@@ -3601,7 +3886,12 @@
|
|
|
3601
3886
|
clearTimeout(chatRenderTimer);
|
|
3602
3887
|
chatRenderTimer = null;
|
|
3603
3888
|
}
|
|
3604
|
-
|
|
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" })
|
|
3605
3895
|
.then(function(res) { return res.json(); })
|
|
3606
3896
|
.then(function(data) {
|
|
3607
3897
|
if (data.error) {
|
|
@@ -3628,6 +3918,8 @@
|
|
|
3628
3918
|
}
|
|
3629
3919
|
|
|
3630
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));
|
|
3631
3923
|
state.selectedId = id;
|
|
3632
3924
|
persistSelectedId();
|
|
3633
3925
|
resetChatRenderCache();
|
|
@@ -3703,6 +3995,7 @@
|
|
|
3703
3995
|
modal.classList.remove("hidden");
|
|
3704
3996
|
lastFocusedElement = document.activeElement;
|
|
3705
3997
|
state.sessionTool = getPreferredTool();
|
|
3998
|
+
state.sessionCreateKind = "pty";
|
|
3706
3999
|
state.modeValue = getSafeModeForTool(state.sessionTool, state.modeValue || state.chatMode);
|
|
3707
4000
|
syncSessionModalUI();
|
|
3708
4001
|
loadRecentPathBubbles();
|
|
@@ -4230,13 +4523,45 @@
|
|
|
4230
4523
|
var cwdEl = document.getElementById("cwd");
|
|
4231
4524
|
var errorEl = document.getElementById("modal-error");
|
|
4232
4525
|
var command = getPreferredTool();
|
|
4526
|
+
var sessionKind = state.sessionCreateKind || "pty";
|
|
4233
4527
|
|
|
4234
4528
|
hideError(errorEl);
|
|
4235
4529
|
|
|
4236
4530
|
var defaultCwd = getEffectiveCwd();
|
|
4531
|
+
var cwd = cwdEl.value.trim() || defaultCwd;
|
|
4237
4532
|
var selectedMode = getSafeModeForTool(command, state.modeValue);
|
|
4238
|
-
|
|
4239
|
-
|
|
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;
|
|
4240
4565
|
state.sessionTool = command;
|
|
4241
4566
|
state.preferredCommand = command;
|
|
4242
4567
|
syncComposerModeSelect();
|
|
@@ -4247,8 +4572,8 @@
|
|
|
4247
4572
|
credentials: "same-origin",
|
|
4248
4573
|
body: JSON.stringify({
|
|
4249
4574
|
command: command,
|
|
4250
|
-
cwd:
|
|
4251
|
-
mode:
|
|
4575
|
+
cwd: cwd,
|
|
4576
|
+
mode: mode
|
|
4252
4577
|
})
|
|
4253
4578
|
})
|
|
4254
4579
|
.then(function(res) { return res.json(); })
|
|
@@ -4258,6 +4583,7 @@
|
|
|
4258
4583
|
return;
|
|
4259
4584
|
}
|
|
4260
4585
|
state.selectedId = data.id;
|
|
4586
|
+
console.log("[WAND] runPtyCommandFromModal created session:", data.id, "sessionKind:", data.sessionKind, "runner:", data.runner);
|
|
4261
4587
|
persistSelectedId();
|
|
4262
4588
|
state.drafts[data.id] = "";
|
|
4263
4589
|
resetChatRenderCache();
|
|
@@ -4265,13 +4591,19 @@
|
|
|
4265
4591
|
closeSessionsDrawer();
|
|
4266
4592
|
return refreshAll();
|
|
4267
4593
|
})
|
|
4268
|
-
.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
|
+
})
|
|
4269
4602
|
.catch(function() {
|
|
4270
4603
|
showError(errorEl, "无法启动会话,请确认 Claude 已正确安装。");
|
|
4271
4604
|
});
|
|
4272
4605
|
}
|
|
4273
4606
|
|
|
4274
|
-
// Blank-chat CWD inline display + dropdown
|
|
4275
4607
|
function initBlankChatCwd() {
|
|
4276
4608
|
var cwdEl = document.getElementById("blank-chat-cwd");
|
|
4277
4609
|
if (!cwdEl) return;
|
|
@@ -4691,11 +5023,13 @@
|
|
|
4691
5023
|
persistSelectedId();
|
|
4692
5024
|
state.drafts[data.id] = "";
|
|
4693
5025
|
resetChatRenderCache();
|
|
4694
|
-
switchToSessionView(data.id);
|
|
4695
5026
|
updateSessionSnapshot(data);
|
|
4696
5027
|
updateSessionsList();
|
|
5028
|
+
switchToSessionView(data.id);
|
|
4697
5029
|
subscribeToSession(data.id);
|
|
4698
5030
|
loadOutput(data.id).then(function() {
|
|
5031
|
+
welcomeInput.placeholder = "输入你的问题,按 Enter 发送...";
|
|
5032
|
+
welcomeInput.disabled = false;
|
|
4699
5033
|
focusInputBox(true);
|
|
4700
5034
|
});
|
|
4701
5035
|
})
|
|
@@ -4749,9 +5083,9 @@
|
|
|
4749
5083
|
resetChatRenderCache();
|
|
4750
5084
|
if (inputBox) inputBox.value = "";
|
|
4751
5085
|
if (welcomeInput) welcomeInput.value = "";
|
|
4752
|
-
switchToSessionView(data.id);
|
|
4753
5086
|
updateSessionSnapshot(data);
|
|
4754
5087
|
updateSessionsList();
|
|
5088
|
+
switchToSessionView(data.id);
|
|
4755
5089
|
// Subscribe to new session via WebSocket
|
|
4756
5090
|
subscribeToSession(data.id);
|
|
4757
5091
|
return loadOutput(data.id);
|
|
@@ -4763,6 +5097,7 @@
|
|
|
4763
5097
|
|
|
4764
5098
|
function switchToSessionView(sessionId) {
|
|
4765
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);
|
|
4766
5101
|
var blankChat = document.getElementById("blank-chat");
|
|
4767
5102
|
var terminalContainer = document.getElementById("output");
|
|
4768
5103
|
var chatContainer = document.getElementById("chat-output");
|
|
@@ -4770,28 +5105,36 @@
|
|
|
4770
5105
|
var terminalTitle = document.getElementById("terminal-title");
|
|
4771
5106
|
var terminalInfo = document.getElementById("terminal-info");
|
|
4772
5107
|
var sessionSummary = document.querySelector(".session-summary-value");
|
|
5108
|
+
var structured = isStructuredSession(session);
|
|
4773
5109
|
|
|
4774
5110
|
if (blankChat) blankChat.classList.add("hidden");
|
|
4775
|
-
if (terminalContainer)
|
|
5111
|
+
if (terminalContainer) {
|
|
5112
|
+
terminalContainer.classList.toggle("hidden", structured);
|
|
5113
|
+
}
|
|
4776
5114
|
if (chatContainer) {
|
|
4777
5115
|
chatContainer.classList.remove("hidden");
|
|
4778
5116
|
}
|
|
4779
5117
|
if (stopBtn) stopBtn.classList.remove("hidden");
|
|
4780
5118
|
|
|
5119
|
+
if (structured) {
|
|
5120
|
+
state.currentView = "chat";
|
|
5121
|
+
} else {
|
|
5122
|
+
state.currentView = "terminal";
|
|
5123
|
+
}
|
|
5124
|
+
|
|
4781
5125
|
var title = session ? shortCommand(session.command) : "Wand";
|
|
4782
5126
|
var info = session ? getSessionStatusLabel(session) : "开始对话";
|
|
4783
5127
|
if (terminalTitle) terminalTitle.textContent = title;
|
|
4784
5128
|
if (terminalInfo) terminalInfo.textContent = info;
|
|
4785
5129
|
if (sessionSummary) sessionSummary.textContent = title;
|
|
4786
5130
|
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
5131
|
+
if (!structured) {
|
|
5132
|
+
if (!state.terminal) initTerminal();
|
|
5133
|
+
if (state.terminal && state.fitAddon) {
|
|
5134
|
+
ensureTerminalFit();
|
|
5135
|
+
}
|
|
4792
5136
|
}
|
|
4793
|
-
|
|
4794
|
-
// Calling renderChat() prematurely would render with stale/empty messages.
|
|
5137
|
+
applyCurrentView();
|
|
4795
5138
|
focusInputBox();
|
|
4796
5139
|
}
|
|
4797
5140
|
|
|
@@ -4806,9 +5149,12 @@
|
|
|
4806
5149
|
var value = inputBox ? inputBox.value : "";
|
|
4807
5150
|
var selectedSession = state.sessions.find(function(session) { return session.id === state.selectedId; }) || null;
|
|
4808
5151
|
if (value) {
|
|
4809
|
-
console.log("[
|
|
5152
|
+
console.log("[WAND] sendInputFromBox", {
|
|
4810
5153
|
sessionId: state.selectedId,
|
|
4811
5154
|
sessionStatus: selectedSession ? selectedSession.status : null,
|
|
5155
|
+
sessionKind: selectedSession ? selectedSession.sessionKind : null,
|
|
5156
|
+
runner: selectedSession ? selectedSession.runner : null,
|
|
5157
|
+
isStructured: isStructuredSession(selectedSession),
|
|
4812
5158
|
view: state.currentView,
|
|
4813
5159
|
wsConnected: state.wsConnected,
|
|
4814
5160
|
terminalInteractive: state.terminalInteractive,
|
|
@@ -4817,6 +5163,11 @@
|
|
|
4817
5163
|
// Clear todo progress bar at the start of a new user turn
|
|
4818
5164
|
var todoEl = document.getElementById("todo-progress");
|
|
4819
5165
|
if (todoEl) todoEl.classList.add("hidden");
|
|
5166
|
+
|
|
5167
|
+
if (isStructuredSession(selectedSession)) {
|
|
5168
|
+
return postStructuredInput(value, inputBox, selectedSession);
|
|
5169
|
+
}
|
|
5170
|
+
|
|
4820
5171
|
// Send text + Enter as a single call to avoid race conditions
|
|
4821
5172
|
var combinedInput = value + getControlInput("enter");
|
|
4822
5173
|
var isOffline = !state.wsConnected;
|
|
@@ -4857,6 +5208,79 @@
|
|
|
4857
5208
|
return Promise.resolve();
|
|
4858
5209
|
}
|
|
4859
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
|
+
|
|
4860
5284
|
function getInputErrorMessage(error) {
|
|
4861
5285
|
if (error && (error.errorCode === "SESSION_NOT_RUNNING" || error.errorCode === "SESSION_NO_PTY")) {
|
|
4862
5286
|
return "会话已结束;若存在 Claude 历史会话,将在你下次发送消息时自动恢复。";
|
|
@@ -4908,6 +5332,7 @@
|
|
|
4908
5332
|
}
|
|
4909
5333
|
|
|
4910
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 });
|
|
4911
5336
|
if (!session) {
|
|
4912
5337
|
showToast("会话不存在,请重新选择或新建会话。", "error");
|
|
4913
5338
|
return Promise.resolve(null);
|
|
@@ -5203,21 +5628,24 @@
|
|
|
5203
5628
|
}
|
|
5204
5629
|
|
|
5205
5630
|
function updateInteractiveControls() {
|
|
5631
|
+
var selectedSession = state.sessions.find(function(session) { return session.id === state.selectedId; });
|
|
5632
|
+
var structured = isStructuredSession(selectedSession);
|
|
5206
5633
|
// Update both toggle buttons (topbar and terminal-header)
|
|
5207
5634
|
var toggles = ["terminal-interactive-toggle-top"];
|
|
5208
5635
|
toggles.forEach(function(id) {
|
|
5209
5636
|
var toggle = document.getElementById(id);
|
|
5210
5637
|
if (toggle) {
|
|
5211
5638
|
toggle.classList.toggle("active", state.terminalInteractive);
|
|
5639
|
+
toggle.classList.toggle("hidden", structured || state.currentView !== "terminal" || !selectedSession);
|
|
5212
5640
|
}
|
|
5213
5641
|
});
|
|
5214
5642
|
// Inline keyboard visibility follows current view
|
|
5215
5643
|
var inlineKeyboard = document.getElementById("inline-keyboard");
|
|
5216
|
-
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", state.currentView !== "terminal");
|
|
5644
|
+
if (inlineKeyboard) inlineKeyboard.classList.toggle("hidden", structured || state.currentView !== "terminal");
|
|
5217
5645
|
var inputHint = document.querySelector(".input-hint");
|
|
5218
|
-
if (inputHint) inputHint.classList.toggle("hidden", state.currentView === "terminal");
|
|
5646
|
+
if (inputHint) inputHint.classList.toggle("hidden", structured ? false : state.currentView === "terminal");
|
|
5219
5647
|
var container = document.getElementById("output");
|
|
5220
|
-
if (container) container.classList.toggle("interactive", state.terminalInteractive);
|
|
5648
|
+
if (container) container.classList.toggle("interactive", !structured && state.terminalInteractive);
|
|
5221
5649
|
}
|
|
5222
5650
|
|
|
5223
5651
|
function captureTerminalInput(event) {
|
|
@@ -5573,6 +6001,7 @@
|
|
|
5573
6001
|
}
|
|
5574
6002
|
|
|
5575
6003
|
function resumeSession(sessionId, errorEl) {
|
|
6004
|
+
console.log("[WAND] resumeSession sessionId:", sessionId);
|
|
5576
6005
|
if (!sessionId) return Promise.resolve(null);
|
|
5577
6006
|
return fetch("/api/sessions/" + encodeURIComponent(sessionId) + "/resume", {
|
|
5578
6007
|
method: "POST",
|
|
@@ -5645,6 +6074,7 @@
|
|
|
5645
6074
|
}
|
|
5646
6075
|
|
|
5647
6076
|
function resumeSessionFromList(sessionId) {
|
|
6077
|
+
console.log("[WAND] resumeSessionFromList sessionId:", sessionId);
|
|
5648
6078
|
return resumeSession(sessionId).then(function(data) {
|
|
5649
6079
|
if (!data) return null;
|
|
5650
6080
|
return activateSession(data).then(function() {
|
|
@@ -5733,6 +6163,7 @@
|
|
|
5733
6163
|
}
|
|
5734
6164
|
|
|
5735
6165
|
function handleResumeAction(actionButton) {
|
|
6166
|
+
console.log("[WAND] handleResumeAction sessionId:", actionButton.dataset.sessionId);
|
|
5736
6167
|
actionButton.disabled = true;
|
|
5737
6168
|
resumeSessionFromList(actionButton.dataset.sessionId)
|
|
5738
6169
|
.finally(function() {
|
|
@@ -5743,6 +6174,7 @@
|
|
|
5743
6174
|
function handleResumeHistoryAction(actionButton) {
|
|
5744
6175
|
var claudeSessionId = actionButton.dataset.claudeSessionId;
|
|
5745
6176
|
var cwd = actionButton.dataset.cwd;
|
|
6177
|
+
console.log("[WAND] handleResumeHistoryAction claudeSessionId:", claudeSessionId, "cwd:", cwd);
|
|
5746
6178
|
if (!claudeSessionId) return;
|
|
5747
6179
|
actionButton.disabled = true;
|
|
5748
6180
|
resumeClaudeHistorySession(claudeSessionId, cwd)
|
|
@@ -6788,15 +7220,18 @@
|
|
|
6788
7220
|
state.fitAddon.fit();
|
|
6789
7221
|
maybeScrollTerminalToBottom("resize");
|
|
6790
7222
|
if (state.selectedId && state.terminal) {
|
|
6791
|
-
var
|
|
6792
|
-
if (
|
|
6793
|
-
state.
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
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
|
+
}
|
|
6800
7235
|
}
|
|
6801
7236
|
}
|
|
6802
7237
|
} else if (attempt < maxAttempts) {
|
|
@@ -6836,6 +7271,10 @@
|
|
|
6836
7271
|
|
|
6837
7272
|
if (!state.selectedId) return;
|
|
6838
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
|
+
|
|
6839
7278
|
// Only send resize API call if dimensions actually changed
|
|
6840
7279
|
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
6841
7280
|
state.lastResize = nextSize;
|
|
@@ -6930,6 +7369,17 @@
|
|
|
6930
7369
|
updateSessionSnapshot(snapshot);
|
|
6931
7370
|
if (msg.sessionId === state.selectedId) {
|
|
6932
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
|
+
}
|
|
6933
7383
|
updateTaskDisplay();
|
|
6934
7384
|
scheduleChatRender();
|
|
6935
7385
|
}
|
|
@@ -6968,8 +7418,19 @@
|
|
|
6968
7418
|
if (msg.data && msg.data.messages) {
|
|
6969
7419
|
endedSnapshot.messages = msg.data.messages;
|
|
6970
7420
|
}
|
|
7421
|
+
if (msg.data && msg.data.structuredState) {
|
|
7422
|
+
endedSnapshot.structuredState = msg.data.structuredState;
|
|
7423
|
+
}
|
|
6971
7424
|
updateSessionSnapshot(endedSnapshot);
|
|
6972
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
|
+
}
|
|
6973
7434
|
// Notify user when a session completes (browser + in-app if backgrounded or not viewing)
|
|
6974
7435
|
var endedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
|
|
6975
7436
|
var endedName = endedSession ? (endedSession.label || endedSession.command || msg.sessionId) : msg.sessionId;
|
|
@@ -7044,7 +7505,24 @@
|
|
|
7044
7505
|
break;
|
|
7045
7506
|
case 'status':
|
|
7046
7507
|
if (msg.sessionId && msg.data) {
|
|
7508
|
+
console.log('[WAND] ws status', msg.sessionId, JSON.stringify(msg.data));
|
|
7047
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
|
+
}
|
|
7048
7526
|
if (Object.prototype.hasOwnProperty.call(msg.data, 'permissionBlocked')) {
|
|
7049
7527
|
statusUpdate.permissionBlocked = !!msg.data.permissionBlocked;
|
|
7050
7528
|
}
|
|
@@ -7096,6 +7574,10 @@
|
|
|
7096
7574
|
if (msg.data.approvalStats) {
|
|
7097
7575
|
updateApprovalStats();
|
|
7098
7576
|
}
|
|
7577
|
+
// Re-render chat when structured session inFlight state changes
|
|
7578
|
+
if (statusUpdate.structuredState) {
|
|
7579
|
+
scheduleChatRender();
|
|
7580
|
+
}
|
|
7099
7581
|
}
|
|
7100
7582
|
}
|
|
7101
7583
|
break;
|
|
@@ -7135,9 +7617,12 @@
|
|
|
7135
7617
|
var isBlocked = pendingEscalation || (selectedSession && selectedSession.permissionBlocked);
|
|
7136
7618
|
|
|
7137
7619
|
if (isBlocked) {
|
|
7620
|
+
var isAutoApprove = selectedSession && selectedSession.autoApprovePermissions;
|
|
7138
7621
|
// Show permission label in input composer area
|
|
7139
7622
|
if (permissionLabel) {
|
|
7140
|
-
if (
|
|
7623
|
+
if (isAutoApprove) {
|
|
7624
|
+
permissionLabel.textContent = "自动批准中...";
|
|
7625
|
+
} else if (pendingEscalation) {
|
|
7141
7626
|
var reason = pendingEscalation.reason || "等待授权";
|
|
7142
7627
|
var target = pendingEscalation.target ? " · " + pendingEscalation.target : "";
|
|
7143
7628
|
permissionLabel.textContent = reason + target;
|
|
@@ -7145,7 +7630,14 @@
|
|
|
7145
7630
|
permissionLabel.textContent = "等待授权";
|
|
7146
7631
|
}
|
|
7147
7632
|
}
|
|
7148
|
-
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
|
+
}
|
|
7149
7641
|
// Hide top task bar — permission info is already shown in the composer
|
|
7150
7642
|
taskEl.textContent = "";
|
|
7151
7643
|
taskEl.classList.add("hidden");
|
|
@@ -7254,6 +7746,49 @@
|
|
|
7254
7746
|
});
|
|
7255
7747
|
}
|
|
7256
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
|
+
|
|
7257
7792
|
function updateTerminalOutput(output, sessionId, mode) {
|
|
7258
7793
|
if (!state.terminal) return false;
|
|
7259
7794
|
return syncTerminalBuffer(sessionId || state.selectedId, output, { mode: mode || "append" });
|
|
@@ -7462,6 +7997,10 @@
|
|
|
7462
7997
|
// Force full render if message count changed or explicitly requested
|
|
7463
7998
|
var forceRender = forceFullRender || msgCount !== state.lastRenderedMsgCount;
|
|
7464
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);
|
|
7465
8004
|
return;
|
|
7466
8005
|
}
|
|
7467
8006
|
var prevHash = state.lastRenderedHash;
|
|
@@ -7615,6 +8154,9 @@
|
|
|
7615
8154
|
fullRenderChat();
|
|
7616
8155
|
}
|
|
7617
8156
|
|
|
8157
|
+
// Update structured session status bar (in-flight / completed indicator)
|
|
8158
|
+
renderStructuredStatusBar(chatMessages, selectedSession);
|
|
8159
|
+
|
|
7618
8160
|
// Update todo progress bar from latest messages
|
|
7619
8161
|
updateTodoProgress(messages);
|
|
7620
8162
|
}
|
|
@@ -8337,11 +8879,53 @@
|
|
|
8337
8879
|
'</div>';
|
|
8338
8880
|
}
|
|
8339
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
|
+
|
|
8340
8925
|
function renderStructuredMessage(msg) {
|
|
8341
8926
|
var role = msg.role;
|
|
8342
8927
|
var avatar = role === "assistant" ? '<div class="chat-message-avatar">AI</div>' : "";
|
|
8343
8928
|
|
|
8344
|
-
// Empty content array — streaming placeholder, show typing indicator
|
|
8345
8929
|
if (!msg.content || msg.content.length === 0) {
|
|
8346
8930
|
if (role === "assistant") {
|
|
8347
8931
|
return '<div class="chat-message ' + role + '">' +
|
|
@@ -8352,18 +8936,7 @@
|
|
|
8352
8936
|
return "";
|
|
8353
8937
|
}
|
|
8354
8938
|
|
|
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
|
-
|
|
8939
|
+
var toolResults = buildToolResultMap(msg.content);
|
|
8367
8940
|
var blocksHtml = "";
|
|
8368
8941
|
|
|
8369
8942
|
try {
|
|
@@ -8372,19 +8945,16 @@
|
|
|
8372
8945
|
try {
|
|
8373
8946
|
blocksHtml += renderContentBlock(block, role, toolResults, i);
|
|
8374
8947
|
} catch (e) {
|
|
8375
|
-
// Render error for individual block
|
|
8376
8948
|
blocksHtml += '<div class="render-error">消息块渲染失败</div>';
|
|
8377
8949
|
}
|
|
8378
8950
|
}
|
|
8379
8951
|
} catch (e) {
|
|
8380
|
-
// Render error for entire message
|
|
8381
8952
|
return '<div class="chat-message ' + role + '">' +
|
|
8382
8953
|
avatar +
|
|
8383
8954
|
'<div class="chat-message-bubble"><div class="render-error">消息渲染失败</div></div>' +
|
|
8384
8955
|
'</div>';
|
|
8385
8956
|
}
|
|
8386
8957
|
|
|
8387
|
-
// Build usage indicator for assistant messages
|
|
8388
8958
|
var usageHtml = "";
|
|
8389
8959
|
if (role === "assistant" && msg.usage) {
|
|
8390
8960
|
var u = msg.usage;
|
|
@@ -8404,21 +8974,21 @@
|
|
|
8404
8974
|
usageHtml +
|
|
8405
8975
|
'</div>';
|
|
8406
8976
|
}
|
|
8407
|
-
|
|
8408
8977
|
function renderContentBlock(block, role, toolResults, index) {
|
|
8409
8978
|
if (!block || !block.type) return "";
|
|
8410
8979
|
|
|
8411
8980
|
switch (block.type) {
|
|
8412
8981
|
case "text":
|
|
8982
|
+
if (role === "assistant" && block.__processing) {
|
|
8983
|
+
return '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
|
8984
|
+
}
|
|
8413
8985
|
return role === "assistant" ? renderMarkdown(block.text || "") : escapeHtml(block.text || "");
|
|
8414
8986
|
|
|
8415
8987
|
case "thinking":
|
|
8416
8988
|
var thinkingText = block.thinking || "";
|
|
8417
|
-
// Compact display: brain icon + brief text, click to expand
|
|
8418
8989
|
var preview = thinkingText.length > 60 ? thinkingText.slice(0, 57) + "…" : thinkingText;
|
|
8419
8990
|
var isStreaming = block.thinking === undefined && block.type === "thinking";
|
|
8420
8991
|
if (isStreaming) {
|
|
8421
|
-
// During streaming: show 3-line scrollable area
|
|
8422
8992
|
return '<div class="thinking-inline thinking-streaming" data-thinking="">' +
|
|
8423
8993
|
'<div class="thinking-streaming-inner">' +
|
|
8424
8994
|
'<span class="thinking-streaming-icon spinning">🧠</span>' +
|
|
@@ -8433,11 +9003,14 @@
|
|
|
8433
9003
|
'</div>';
|
|
8434
9004
|
|
|
8435
9005
|
case "tool_use":
|
|
8436
|
-
var toolResult = toolResults
|
|
8437
|
-
|
|
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;
|
|
8438
9012
|
|
|
8439
9013
|
case "tool_result":
|
|
8440
|
-
// tool_result 已经在 tool_use 渲染时处理了,不再单独渲染
|
|
8441
9014
|
return "";
|
|
8442
9015
|
|
|
8443
9016
|
default:
|
|
@@ -8445,11 +9018,11 @@
|
|
|
8445
9018
|
}
|
|
8446
9019
|
}
|
|
8447
9020
|
|
|
8448
|
-
// Lightweight inline display — used for Read, Glob, Grep, WebFetch, WebSearch, TodoRead
|
|
8449
9021
|
function renderInlineTool(block, toolResult, toolName, fileInfo, extraInfo) {
|
|
8450
9022
|
var toolId = block.id || "tool-" + toolName;
|
|
8451
9023
|
var inputData = block.input || {};
|
|
8452
|
-
var resultContent = (toolResult && toolResult.content)
|
|
9024
|
+
var resultContent = extractToolResultText(toolResult && toolResult.content);
|
|
9025
|
+
|
|
8453
9026
|
var isError = toolResult && toolResult.is_error;
|
|
8454
9027
|
var hasResult = resultContent.length > 0;
|
|
8455
9028
|
var statusIcon = isError ? "⚠️" : (hasResult ? "✅" : "⏳");
|
|
@@ -8552,7 +9125,8 @@
|
|
|
8552
9125
|
function renderTerminalTool(block, toolResult, toolName) {
|
|
8553
9126
|
var inputData = block.input || {};
|
|
8554
9127
|
var command = inputData.command || inputData.cmd || "";
|
|
8555
|
-
var resultContent = (toolResult && toolResult.content)
|
|
9128
|
+
var resultContent = extractToolResultText(toolResult && toolResult.content);
|
|
9129
|
+
|
|
8556
9130
|
var isError = toolResult && toolResult.is_error;
|
|
8557
9131
|
var exitCode = inputData.exitCode;
|
|
8558
9132
|
var hasResult = resultContent.length > 0;
|
|
@@ -8769,7 +9343,7 @@
|
|
|
8769
9343
|
|
|
8770
9344
|
if (toolResult) {
|
|
8771
9345
|
var isError = toolResult.is_error;
|
|
8772
|
-
var content = toolResult.content
|
|
9346
|
+
var content = extractToolResultText(toolResult.content);
|
|
8773
9347
|
statusClass = isError ? "error" : "success";
|
|
8774
9348
|
headerIcon = getToolIcon(toolName);
|
|
8775
9349
|
var hasContent = content && content.trim().length > 0;
|