claude-relay 2.4.2 → 2.5.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/bin/cli.js +1 -2350
- package/package.json +7 -42
- package/LICENSE +0 -21
- package/README.md +0 -281
- package/lib/cli-sessions.js +0 -270
- package/lib/config.js +0 -222
- package/lib/daemon.js +0 -423
- package/lib/ipc.js +0 -112
- package/lib/pages.js +0 -714
- package/lib/project.js +0 -1224
- package/lib/public/app.js +0 -2157
- package/lib/public/apple-touch-icon.png +0 -0
- package/lib/public/css/base.css +0 -145
- package/lib/public/css/diff.css +0 -128
- package/lib/public/css/filebrowser.css +0 -1076
- package/lib/public/css/highlight.css +0 -144
- package/lib/public/css/input.css +0 -512
- package/lib/public/css/menus.css +0 -683
- package/lib/public/css/messages.css +0 -1159
- package/lib/public/css/overlays.css +0 -731
- package/lib/public/css/rewind.css +0 -529
- package/lib/public/css/sidebar.css +0 -794
- package/lib/public/favicon.svg +0 -26
- package/lib/public/icon-192.png +0 -0
- package/lib/public/icon-512.png +0 -0
- package/lib/public/icon-mono.svg +0 -19
- package/lib/public/index.html +0 -460
- package/lib/public/manifest.json +0 -27
- package/lib/public/modules/diff.js +0 -398
- package/lib/public/modules/events.js +0 -21
- package/lib/public/modules/filebrowser.js +0 -1375
- package/lib/public/modules/fileicons.js +0 -172
- package/lib/public/modules/icons.js +0 -54
- package/lib/public/modules/input.js +0 -578
- package/lib/public/modules/markdown.js +0 -149
- package/lib/public/modules/notifications.js +0 -643
- package/lib/public/modules/qrcode.js +0 -70
- package/lib/public/modules/rewind.js +0 -334
- package/lib/public/modules/sidebar.js +0 -628
- package/lib/public/modules/state.js +0 -3
- package/lib/public/modules/terminal.js +0 -658
- package/lib/public/modules/theme.js +0 -622
- package/lib/public/modules/tools.js +0 -1410
- package/lib/public/modules/utils.js +0 -56
- package/lib/public/style.css +0 -10
- package/lib/public/sw.js +0 -75
- package/lib/push.js +0 -125
- package/lib/sdk-bridge.js +0 -771
- package/lib/server.js +0 -577
- package/lib/sessions.js +0 -402
- package/lib/terminal-manager.js +0 -187
- package/lib/terminal.js +0 -24
- package/lib/themes/ayu-light.json +0 -9
- package/lib/themes/catppuccin-latte.json +0 -9
- package/lib/themes/catppuccin-mocha.json +0 -9
- package/lib/themes/claude-light.json +0 -9
- package/lib/themes/claude.json +0 -9
- package/lib/themes/dracula.json +0 -9
- package/lib/themes/everforest-light.json +0 -9
- package/lib/themes/everforest.json +0 -9
- package/lib/themes/github-light.json +0 -9
- package/lib/themes/gruvbox-dark.json +0 -9
- package/lib/themes/gruvbox-light.json +0 -9
- package/lib/themes/monokai.json +0 -9
- package/lib/themes/nord-light.json +0 -9
- package/lib/themes/nord.json +0 -9
- package/lib/themes/one-dark.json +0 -9
- package/lib/themes/one-light.json +0 -9
- package/lib/themes/rose-pine-dawn.json +0 -9
- package/lib/themes/rose-pine.json +0 -9
- package/lib/themes/solarized-dark.json +0 -9
- package/lib/themes/solarized-light.json +0 -9
- package/lib/themes/tokyo-night-light.json +0 -9
- package/lib/themes/tokyo-night.json +0 -9
- package/lib/updater.js +0 -96
|
@@ -1,1410 +0,0 @@
|
|
|
1
|
-
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
2
|
-
import { iconHtml, refreshIcons, randomThinkingVerb } from './icons.js';
|
|
3
|
-
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
4
|
-
import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
|
|
5
|
-
import { openFile } from './filebrowser.js';
|
|
6
|
-
|
|
7
|
-
var ctx;
|
|
8
|
-
|
|
9
|
-
// --- Plan mode state ---
|
|
10
|
-
var inPlanMode = false;
|
|
11
|
-
var planContent = null;
|
|
12
|
-
|
|
13
|
-
// --- Todo state ---
|
|
14
|
-
var todoItems = [];
|
|
15
|
-
var todoWidgetEl = null;
|
|
16
|
-
var todoWidgetVisible = true; // whether in-chat widget is in viewport
|
|
17
|
-
var todoObserver = null;
|
|
18
|
-
|
|
19
|
-
// --- Tool tracking ---
|
|
20
|
-
var tools = {};
|
|
21
|
-
var currentThinking = null;
|
|
22
|
-
var pendingPermissions = {};
|
|
23
|
-
|
|
24
|
-
// --- Tool group tracking ---
|
|
25
|
-
var currentToolGroup = null;
|
|
26
|
-
var toolGroupCounter = 0;
|
|
27
|
-
var toolGroups = {};
|
|
28
|
-
|
|
29
|
-
// --- Tool helpers ---
|
|
30
|
-
var PLAN_MODE_TOOLS = { EnterPlanMode: 1, ExitPlanMode: 1 };
|
|
31
|
-
var TODO_TOOLS = { TodoWrite: 1, TaskCreate: 1, TaskUpdate: 1, TaskList: 1, TaskGet: 1 };
|
|
32
|
-
var HIDDEN_RESULT_TOOLS = { EnterPlanMode: 1, ExitPlanMode: 1, TaskCreate: 1, TaskUpdate: 1, TaskList: 1, TaskGet: 1, TodoWrite: 1 };
|
|
33
|
-
|
|
34
|
-
// --- Tool group helpers ---
|
|
35
|
-
function closeToolGroup() {
|
|
36
|
-
if (currentToolGroup) {
|
|
37
|
-
currentToolGroup.closed = true;
|
|
38
|
-
}
|
|
39
|
-
currentToolGroup = null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function findToolGroup(groupId) {
|
|
43
|
-
return toolGroups[groupId] || null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function toolGroupSummary(group) {
|
|
47
|
-
var names = group.toolNames;
|
|
48
|
-
var count = names.length;
|
|
49
|
-
var allDone = group.doneCount >= count;
|
|
50
|
-
|
|
51
|
-
// Count by tool name
|
|
52
|
-
var counts = {};
|
|
53
|
-
for (var i = 0; i < names.length; i++) {
|
|
54
|
-
counts[names[i]] = (counts[names[i]] || 0) + 1;
|
|
55
|
-
}
|
|
56
|
-
var uniqueNames = Object.keys(counts);
|
|
57
|
-
|
|
58
|
-
if (uniqueNames.length === 1) {
|
|
59
|
-
var name = uniqueNames[0];
|
|
60
|
-
var n = counts[name];
|
|
61
|
-
if (allDone) {
|
|
62
|
-
switch (name) {
|
|
63
|
-
case "Read": return "Read " + n + " file" + (n > 1 ? "s" : "");
|
|
64
|
-
case "Edit": return "Edited " + n + " file" + (n > 1 ? "s" : "");
|
|
65
|
-
case "Write": return "Wrote " + n + " file" + (n > 1 ? "s" : "");
|
|
66
|
-
case "Bash": return "Ran " + n + " command" + (n > 1 ? "s" : "");
|
|
67
|
-
case "Grep": return "Searched " + n + " pattern" + (n > 1 ? "s" : "");
|
|
68
|
-
case "Glob": return "Found " + n + " pattern" + (n > 1 ? "s" : "");
|
|
69
|
-
case "Task": return "Ran " + n + " task" + (n > 1 ? "s" : "");
|
|
70
|
-
case "WebSearch": return "Searched " + n + " quer" + (n > 1 ? "ies" : "y");
|
|
71
|
-
case "WebFetch": return "Fetched " + n + " URL" + (n > 1 ? "s" : "");
|
|
72
|
-
default: return "Ran " + n + " tool" + (n > 1 ? "s" : "");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
switch (name) {
|
|
76
|
-
case "Read": return "Reading " + n + " file" + (n > 1 ? "s" : "") + "...";
|
|
77
|
-
case "Edit": return "Editing " + n + " file" + (n > 1 ? "s" : "") + "...";
|
|
78
|
-
case "Write": return "Writing " + n + " file" + (n > 1 ? "s" : "") + "...";
|
|
79
|
-
case "Bash": return "Running " + n + " command" + (n > 1 ? "s" : "") + "...";
|
|
80
|
-
case "Grep": return "Searching " + n + " pattern" + (n > 1 ? "s" : "") + "...";
|
|
81
|
-
case "Glob": return "Finding " + n + " pattern" + (n > 1 ? "s" : "") + "...";
|
|
82
|
-
case "Task": return "Running " + n + " task" + (n > 1 ? "s" : "") + "...";
|
|
83
|
-
case "WebSearch": return "Searching " + n + " quer" + (n > 1 ? "ies" : "y") + "...";
|
|
84
|
-
case "WebFetch": return "Fetching " + n + " URL" + (n > 1 ? "s" : "") + "...";
|
|
85
|
-
default: return "Running " + n + " tool" + (n > 1 ? "s" : "") + "...";
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Mixed tools
|
|
90
|
-
if (allDone) return "Ran " + count + " tools";
|
|
91
|
-
return "Running " + count + " tools...";
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function updateToolGroupHeader(group) {
|
|
95
|
-
if (!group || !group.el) return;
|
|
96
|
-
var label = group.el.querySelector(".tool-group-label");
|
|
97
|
-
if (label) label.textContent = toolGroupSummary(group);
|
|
98
|
-
|
|
99
|
-
var allDone = group.doneCount >= group.toolCount;
|
|
100
|
-
var statusIcon = group.el.querySelector(".tool-group-status-icon");
|
|
101
|
-
var bullet = group.el.querySelector(".tool-group-bullet");
|
|
102
|
-
|
|
103
|
-
if (allDone) {
|
|
104
|
-
group.el.classList.add("done");
|
|
105
|
-
if (group.errorCount > 0) {
|
|
106
|
-
statusIcon.innerHTML = '<span class="err-icon">' + iconHtml("alert-triangle") + '</span>';
|
|
107
|
-
if (bullet) bullet.classList.add("error");
|
|
108
|
-
} else {
|
|
109
|
-
statusIcon.innerHTML = '<span class="check">' + iconHtml("check") + '</span>';
|
|
110
|
-
}
|
|
111
|
-
refreshIcons();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Show group header only when 2+ visible tools
|
|
115
|
-
var header = group.el.querySelector(".tool-group-header");
|
|
116
|
-
if (group.toolCount >= 2) {
|
|
117
|
-
header.style.display = "";
|
|
118
|
-
// When 2+ tools, ensure collapsed by default (unless user already toggled)
|
|
119
|
-
if (!group.userToggled && !group.el.classList.contains("expanded-by-user")) {
|
|
120
|
-
group.el.classList.add("collapsed");
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
header.style.display = "none";
|
|
124
|
-
group.el.classList.remove("collapsed");
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function isPlanFile(filePath) {
|
|
129
|
-
return filePath && filePath.indexOf(".claude/plans/") !== -1;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function toolSummary(name, input) {
|
|
133
|
-
if (!input || typeof input !== "object") return "";
|
|
134
|
-
switch (name) {
|
|
135
|
-
case "Read": return shortPath(input.file_path);
|
|
136
|
-
case "Edit": return shortPath(input.file_path);
|
|
137
|
-
case "Write": return shortPath(input.file_path);
|
|
138
|
-
case "Bash": return (input.command || "").substring(0, 80);
|
|
139
|
-
case "Glob": return input.pattern || "";
|
|
140
|
-
case "Grep": return (input.pattern || "") + (input.path ? " in " + shortPath(input.path) : "");
|
|
141
|
-
case "WebFetch": return input.url || "";
|
|
142
|
-
case "WebSearch": return input.query || "";
|
|
143
|
-
case "Task": return input.description || "";
|
|
144
|
-
case "EnterPlanMode": return "";
|
|
145
|
-
case "ExitPlanMode": return "";
|
|
146
|
-
default: return JSON.stringify(input).substring(0, 60);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function toolActivityText(name, input) {
|
|
151
|
-
if (name === "Bash" && input && input.description) return input.description;
|
|
152
|
-
if (name === "Read" && input && input.file_path) return "Reading " + shortPath(input.file_path);
|
|
153
|
-
if (name === "Edit" && input && input.file_path) return "Editing " + shortPath(input.file_path);
|
|
154
|
-
if (name === "Write" && input && input.file_path) return "Writing " + shortPath(input.file_path);
|
|
155
|
-
if (name === "Grep" && input && input.pattern) return "Searching for " + input.pattern;
|
|
156
|
-
if (name === "Glob" && input && input.pattern) return "Finding " + input.pattern;
|
|
157
|
-
if (name === "WebSearch" && input && input.query) return "Searching: " + input.query;
|
|
158
|
-
if (name === "WebFetch") return "Fetching URL...";
|
|
159
|
-
if (name === "Task" && input && input.description) return input.description;
|
|
160
|
-
if (name === "EnterPlanMode") return "Entering plan mode...";
|
|
161
|
-
if (name === "ExitPlanMode") return "Finalizing the plan...";
|
|
162
|
-
return "Running " + name + "...";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function shortPath(p) {
|
|
166
|
-
if (!p) return "";
|
|
167
|
-
var parts = p.split("/");
|
|
168
|
-
return parts.length > 3 ? ".../" + parts.slice(-3).join("/") : p;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// --- AskUserQuestion ---
|
|
172
|
-
export function renderAskUserQuestion(toolId, input) {
|
|
173
|
-
ctx.finalizeAssistantBlock();
|
|
174
|
-
stopThinking();
|
|
175
|
-
closeToolGroup();
|
|
176
|
-
|
|
177
|
-
var questions = input.questions || [];
|
|
178
|
-
if (questions.length === 0) return;
|
|
179
|
-
|
|
180
|
-
var container = document.createElement("div");
|
|
181
|
-
container.className = "ask-user-container";
|
|
182
|
-
container.dataset.toolId = toolId;
|
|
183
|
-
|
|
184
|
-
var answers = {};
|
|
185
|
-
var multiSelections = {};
|
|
186
|
-
|
|
187
|
-
questions.forEach(function (q, qIdx) {
|
|
188
|
-
var qDiv = document.createElement("div");
|
|
189
|
-
qDiv.className = "ask-user-question";
|
|
190
|
-
|
|
191
|
-
var qText = document.createElement("div");
|
|
192
|
-
qText.className = "ask-user-question-text";
|
|
193
|
-
qText.textContent = q.question || "";
|
|
194
|
-
qDiv.appendChild(qText);
|
|
195
|
-
|
|
196
|
-
var optionsDiv = document.createElement("div");
|
|
197
|
-
optionsDiv.className = "ask-user-options";
|
|
198
|
-
|
|
199
|
-
var isMulti = q.multiSelect || false;
|
|
200
|
-
if (isMulti) multiSelections[qIdx] = new Set();
|
|
201
|
-
|
|
202
|
-
(q.options || []).forEach(function (opt) {
|
|
203
|
-
var btn = document.createElement("button");
|
|
204
|
-
btn.className = "ask-user-option";
|
|
205
|
-
btn.innerHTML =
|
|
206
|
-
'<div class="option-label"></div>' +
|
|
207
|
-
(opt.description ? '<div class="option-desc"></div>' : '');
|
|
208
|
-
btn.querySelector(".option-label").textContent = opt.label;
|
|
209
|
-
if (opt.description) btn.querySelector(".option-desc").textContent = opt.description;
|
|
210
|
-
|
|
211
|
-
btn.addEventListener("click", function () {
|
|
212
|
-
if (container.classList.contains("answered")) return;
|
|
213
|
-
|
|
214
|
-
if (isMulti) {
|
|
215
|
-
var set = multiSelections[qIdx];
|
|
216
|
-
if (set.has(opt.label)) {
|
|
217
|
-
set.delete(opt.label);
|
|
218
|
-
btn.classList.remove("selected");
|
|
219
|
-
} else {
|
|
220
|
-
set.add(opt.label);
|
|
221
|
-
btn.classList.add("selected");
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
optionsDiv.querySelectorAll(".ask-user-option").forEach(function (b) {
|
|
225
|
-
b.classList.remove("selected");
|
|
226
|
-
});
|
|
227
|
-
btn.classList.add("selected");
|
|
228
|
-
answers[qIdx] = opt.label;
|
|
229
|
-
var otherInput = qDiv.querySelector(".ask-user-other input");
|
|
230
|
-
if (otherInput) otherInput.value = "";
|
|
231
|
-
if (questions.length === 1) {
|
|
232
|
-
submitAskUserAnswer(container, toolId, questions, answers, multiSelections);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
optionsDiv.appendChild(btn);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
qDiv.appendChild(optionsDiv);
|
|
241
|
-
|
|
242
|
-
// "Other" text input
|
|
243
|
-
var otherDiv = document.createElement("div");
|
|
244
|
-
otherDiv.className = "ask-user-other";
|
|
245
|
-
var otherInput = document.createElement("input");
|
|
246
|
-
otherInput.type = "text";
|
|
247
|
-
otherInput.placeholder = "Other...";
|
|
248
|
-
otherInput.addEventListener("input", function () {
|
|
249
|
-
if (container.classList.contains("answered")) return;
|
|
250
|
-
if (otherInput.value.trim()) {
|
|
251
|
-
optionsDiv.querySelectorAll(".ask-user-option").forEach(function (b) {
|
|
252
|
-
b.classList.remove("selected");
|
|
253
|
-
});
|
|
254
|
-
if (isMulti) multiSelections[qIdx] = new Set();
|
|
255
|
-
answers[qIdx] = otherInput.value.trim();
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
otherInput.addEventListener("keydown", function (e) {
|
|
259
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
260
|
-
e.preventDefault();
|
|
261
|
-
submitAskUserAnswer(container, toolId, questions, answers, multiSelections);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
otherDiv.appendChild(otherInput);
|
|
265
|
-
qDiv.appendChild(otherDiv);
|
|
266
|
-
container.appendChild(qDiv);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Single submit button at the bottom (only for multi-question)
|
|
270
|
-
if (questions.length > 1) {
|
|
271
|
-
var submitBtn = document.createElement("button");
|
|
272
|
-
submitBtn.className = "ask-user-submit";
|
|
273
|
-
submitBtn.textContent = "Submit";
|
|
274
|
-
submitBtn.addEventListener("click", function () {
|
|
275
|
-
submitAskUserAnswer(container, toolId, questions, answers, multiSelections);
|
|
276
|
-
});
|
|
277
|
-
container.appendChild(submitBtn);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Skip button
|
|
281
|
-
var skipBtn = document.createElement("button");
|
|
282
|
-
skipBtn.className = "ask-user-skip";
|
|
283
|
-
skipBtn.textContent = "Skip";
|
|
284
|
-
skipBtn.addEventListener("click", function () {
|
|
285
|
-
if (container.classList.contains("answered")) return;
|
|
286
|
-
container.classList.add("answered");
|
|
287
|
-
enableMainInput();
|
|
288
|
-
if (ctx.ws && ctx.connected) {
|
|
289
|
-
ctx.ws.send(JSON.stringify({ type: "stop" }));
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
container.appendChild(skipBtn);
|
|
293
|
-
|
|
294
|
-
ctx.addToMessages(container);
|
|
295
|
-
disableMainInput();
|
|
296
|
-
ctx.setActivity(null);
|
|
297
|
-
ctx.scrollToBottom();
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export function disableMainInput() {
|
|
301
|
-
ctx.inputEl.disabled = true;
|
|
302
|
-
ctx.inputEl.placeholder = "Answer the question above to continue...";
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function enableMainInput() {
|
|
306
|
-
ctx.inputEl.disabled = false;
|
|
307
|
-
ctx.inputEl.placeholder = "Message Claude Code...";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function submitAskUserAnswer(container, toolId, questions, answers, multiSelections) {
|
|
311
|
-
if (container.classList.contains("answered")) return;
|
|
312
|
-
|
|
313
|
-
var result = {};
|
|
314
|
-
for (var i = 0; i < questions.length; i++) {
|
|
315
|
-
var q = questions[i];
|
|
316
|
-
if (q.multiSelect && multiSelections[i] && multiSelections[i].size > 0) {
|
|
317
|
-
result[i] = Array.from(multiSelections[i]).join(", ");
|
|
318
|
-
} else if (answers[i]) {
|
|
319
|
-
result[i] = answers[i];
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (Object.keys(result).length === 0) return;
|
|
324
|
-
|
|
325
|
-
container.classList.add("answered");
|
|
326
|
-
enableMainInput();
|
|
327
|
-
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
328
|
-
|
|
329
|
-
if (ctx.ws && ctx.connected) {
|
|
330
|
-
ctx.ws.send(JSON.stringify({
|
|
331
|
-
type: "ask_user_response",
|
|
332
|
-
toolId: toolId,
|
|
333
|
-
answers: result,
|
|
334
|
-
}));
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export function markAskUserAnswered(toolId) {
|
|
339
|
-
var container = document.querySelector('.ask-user-container[data-tool-id="' + toolId + '"]');
|
|
340
|
-
if (container && !container.classList.contains("answered")) {
|
|
341
|
-
container.classList.add("answered");
|
|
342
|
-
enableMainInput();
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// --- Permission request ---
|
|
347
|
-
function permissionInputSummary(toolName, input) {
|
|
348
|
-
if (!input || typeof input !== "object") return "";
|
|
349
|
-
switch (toolName) {
|
|
350
|
-
case "Bash": return input.command || input.description || "";
|
|
351
|
-
case "Edit": return shortPath(input.file_path);
|
|
352
|
-
case "Write": return shortPath(input.file_path);
|
|
353
|
-
case "Read": return shortPath(input.file_path);
|
|
354
|
-
case "Glob": return input.pattern || "";
|
|
355
|
-
case "Grep": return (input.pattern || "") + (input.path ? " in " + shortPath(input.path) : "");
|
|
356
|
-
default: return toolSummary(toolName, input);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason) {
|
|
361
|
-
if (pendingPermissions[requestId]) return;
|
|
362
|
-
ctx.finalizeAssistantBlock();
|
|
363
|
-
stopThinking();
|
|
364
|
-
closeToolGroup();
|
|
365
|
-
|
|
366
|
-
// ExitPlanMode: render as plan confirmation instead of generic permission
|
|
367
|
-
if (toolName === "ExitPlanMode") {
|
|
368
|
-
renderPlanPermission(requestId);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
var container = document.createElement("div");
|
|
373
|
-
container.className = "permission-container";
|
|
374
|
-
container.dataset.requestId = requestId;
|
|
375
|
-
|
|
376
|
-
// Header
|
|
377
|
-
var header = document.createElement("div");
|
|
378
|
-
header.className = "permission-header";
|
|
379
|
-
header.innerHTML =
|
|
380
|
-
'<span class="permission-icon">' + iconHtml("shield") + '</span>' +
|
|
381
|
-
'<span class="permission-title">Permission Required</span>';
|
|
382
|
-
|
|
383
|
-
// Body
|
|
384
|
-
var body = document.createElement("div");
|
|
385
|
-
body.className = "permission-body";
|
|
386
|
-
|
|
387
|
-
var summary = document.createElement("div");
|
|
388
|
-
summary.className = "permission-summary";
|
|
389
|
-
var summaryText = permissionInputSummary(toolName, toolInput);
|
|
390
|
-
summary.innerHTML =
|
|
391
|
-
'<span class="permission-tool-name"></span>' +
|
|
392
|
-
(summaryText ? '<span class="permission-tool-desc"></span>' : '');
|
|
393
|
-
summary.querySelector(".permission-tool-name").textContent = toolName;
|
|
394
|
-
if (summaryText) {
|
|
395
|
-
summary.querySelector(".permission-tool-desc").textContent = summaryText;
|
|
396
|
-
}
|
|
397
|
-
body.appendChild(summary);
|
|
398
|
-
|
|
399
|
-
if (decisionReason) {
|
|
400
|
-
var reason = document.createElement("div");
|
|
401
|
-
reason.className = "permission-reason";
|
|
402
|
-
reason.textContent = decisionReason;
|
|
403
|
-
body.appendChild(reason);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Collapsible details
|
|
407
|
-
var details = document.createElement("details");
|
|
408
|
-
details.className = "permission-details";
|
|
409
|
-
var detailsSummary = document.createElement("summary");
|
|
410
|
-
detailsSummary.textContent = "Details";
|
|
411
|
-
var detailsPre = document.createElement("pre");
|
|
412
|
-
detailsPre.textContent = JSON.stringify(toolInput, null, 2);
|
|
413
|
-
details.appendChild(detailsSummary);
|
|
414
|
-
details.appendChild(detailsPre);
|
|
415
|
-
body.appendChild(details);
|
|
416
|
-
|
|
417
|
-
// Actions
|
|
418
|
-
var actions = document.createElement("div");
|
|
419
|
-
actions.className = "permission-actions";
|
|
420
|
-
|
|
421
|
-
var allowBtn = document.createElement("button");
|
|
422
|
-
allowBtn.className = "permission-btn permission-allow";
|
|
423
|
-
allowBtn.textContent = "Allow Once";
|
|
424
|
-
allowBtn.addEventListener("click", function () {
|
|
425
|
-
sendPermissionResponse(container, requestId, "allow");
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
var allowAlwaysBtn = document.createElement("button");
|
|
429
|
-
allowAlwaysBtn.className = "permission-btn permission-allow-session";
|
|
430
|
-
allowAlwaysBtn.textContent = "Always Allow";
|
|
431
|
-
allowAlwaysBtn.addEventListener("click", function () {
|
|
432
|
-
sendPermissionResponse(container, requestId, "allow_always");
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
var denyBtn = document.createElement("button");
|
|
436
|
-
denyBtn.className = "permission-btn permission-deny";
|
|
437
|
-
denyBtn.textContent = "Deny";
|
|
438
|
-
denyBtn.addEventListener("click", function () {
|
|
439
|
-
sendPermissionResponse(container, requestId, "deny");
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
actions.appendChild(allowBtn);
|
|
443
|
-
actions.appendChild(allowAlwaysBtn);
|
|
444
|
-
actions.appendChild(denyBtn);
|
|
445
|
-
|
|
446
|
-
container.appendChild(header);
|
|
447
|
-
container.appendChild(body);
|
|
448
|
-
container.appendChild(actions);
|
|
449
|
-
ctx.addToMessages(container);
|
|
450
|
-
|
|
451
|
-
pendingPermissions[requestId] = container;
|
|
452
|
-
refreshIcons();
|
|
453
|
-
ctx.setActivity(null);
|
|
454
|
-
ctx.scrollToBottom();
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function renderPlanPermission(requestId) {
|
|
458
|
-
if (pendingPermissions[requestId]) return;
|
|
459
|
-
var container = document.createElement("div");
|
|
460
|
-
container.className = "permission-container plan-permission";
|
|
461
|
-
container.dataset.requestId = requestId;
|
|
462
|
-
|
|
463
|
-
// Header
|
|
464
|
-
var header = document.createElement("div");
|
|
465
|
-
header.className = "permission-header plan-permission-header";
|
|
466
|
-
header.innerHTML =
|
|
467
|
-
'<span class="permission-icon">' + iconHtml("check-circle") + '</span>' +
|
|
468
|
-
'<span class="permission-title">Plan Approval</span>';
|
|
469
|
-
|
|
470
|
-
// Body (plan content already visible above, no need to repeat)
|
|
471
|
-
var body = document.createElement("div");
|
|
472
|
-
body.className = "permission-body";
|
|
473
|
-
|
|
474
|
-
// Actions
|
|
475
|
-
var actions = document.createElement("div");
|
|
476
|
-
actions.className = "permission-actions";
|
|
477
|
-
|
|
478
|
-
var approveBtn = document.createElement("button");
|
|
479
|
-
approveBtn.className = "permission-btn permission-allow";
|
|
480
|
-
approveBtn.textContent = "Approve Plan";
|
|
481
|
-
approveBtn.addEventListener("click", function () {
|
|
482
|
-
sendPermissionResponse(container, requestId, "allow");
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
var rejectBtn = document.createElement("button");
|
|
486
|
-
rejectBtn.className = "permission-btn permission-deny";
|
|
487
|
-
rejectBtn.textContent = "Reject Plan";
|
|
488
|
-
rejectBtn.addEventListener("click", function () {
|
|
489
|
-
sendPermissionResponse(container, requestId, "deny");
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
actions.appendChild(approveBtn);
|
|
493
|
-
actions.appendChild(rejectBtn);
|
|
494
|
-
|
|
495
|
-
container.appendChild(header);
|
|
496
|
-
container.appendChild(body);
|
|
497
|
-
container.appendChild(actions);
|
|
498
|
-
ctx.addToMessages(container);
|
|
499
|
-
|
|
500
|
-
pendingPermissions[requestId] = container;
|
|
501
|
-
refreshIcons();
|
|
502
|
-
ctx.setActivity(null);
|
|
503
|
-
ctx.scrollToBottom();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function sendPermissionResponse(container, requestId, decision) {
|
|
507
|
-
if (container.classList.contains("resolved")) return;
|
|
508
|
-
container.classList.add("resolved");
|
|
509
|
-
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
510
|
-
|
|
511
|
-
var label = decision === "deny" ? "Denied" : "Allowed";
|
|
512
|
-
var resolvedClass = decision === "deny" ? "resolved-denied" : "resolved-allowed";
|
|
513
|
-
container.classList.add(resolvedClass);
|
|
514
|
-
|
|
515
|
-
// Replace actions with decision label
|
|
516
|
-
var actions = container.querySelector(".permission-actions");
|
|
517
|
-
if (actions) {
|
|
518
|
-
actions.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (ctx.ws && ctx.connected) {
|
|
522
|
-
ctx.ws.send(JSON.stringify({
|
|
523
|
-
type: "permission_response",
|
|
524
|
-
requestId: requestId,
|
|
525
|
-
decision: decision,
|
|
526
|
-
}));
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
delete pendingPermissions[requestId];
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
export function markPermissionResolved(requestId, decision) {
|
|
533
|
-
var container = pendingPermissions[requestId];
|
|
534
|
-
if (!container) {
|
|
535
|
-
// Find by data attribute (history replay)
|
|
536
|
-
container = ctx.messagesEl.querySelector('[data-request-id="' + requestId + '"]');
|
|
537
|
-
}
|
|
538
|
-
if (!container || container.classList.contains("resolved")) return;
|
|
539
|
-
|
|
540
|
-
container.classList.add("resolved");
|
|
541
|
-
var resolvedClass = decision === "deny" ? "resolved-denied" : "resolved-allowed";
|
|
542
|
-
container.classList.add(resolvedClass);
|
|
543
|
-
|
|
544
|
-
var label = decision === "deny" ? "Denied" : "Allowed";
|
|
545
|
-
var actions = container.querySelector(".permission-actions");
|
|
546
|
-
if (actions) {
|
|
547
|
-
actions.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
delete pendingPermissions[requestId];
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
export function markPermissionCancelled(requestId) {
|
|
554
|
-
var container = pendingPermissions[requestId];
|
|
555
|
-
if (!container) {
|
|
556
|
-
container = ctx.messagesEl.querySelector('[data-request-id="' + requestId + '"]');
|
|
557
|
-
}
|
|
558
|
-
if (!container || container.classList.contains("resolved")) return;
|
|
559
|
-
|
|
560
|
-
container.classList.add("resolved", "resolved-cancelled");
|
|
561
|
-
var actions = container.querySelector(".permission-actions");
|
|
562
|
-
if (actions) {
|
|
563
|
-
actions.innerHTML = '<span class="permission-decision-label">Cancelled</span>';
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
delete pendingPermissions[requestId];
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// --- Plan mode rendering ---
|
|
570
|
-
export function renderPlanBanner(type) {
|
|
571
|
-
ctx.finalizeAssistantBlock();
|
|
572
|
-
stopThinking();
|
|
573
|
-
closeToolGroup();
|
|
574
|
-
|
|
575
|
-
var el = document.createElement("div");
|
|
576
|
-
el.className = "plan-banner";
|
|
577
|
-
|
|
578
|
-
if (type === "enter") {
|
|
579
|
-
inPlanMode = true;
|
|
580
|
-
planContent = null;
|
|
581
|
-
el.innerHTML =
|
|
582
|
-
'<span class="plan-banner-icon">' + iconHtml("map") + '</span>' +
|
|
583
|
-
'<span class="plan-banner-text">Entered plan mode</span>' +
|
|
584
|
-
'<span class="plan-banner-hint">Exploring codebase and designing implementation...</span>';
|
|
585
|
-
el.classList.add("plan-enter");
|
|
586
|
-
} else {
|
|
587
|
-
inPlanMode = false;
|
|
588
|
-
el.innerHTML =
|
|
589
|
-
'<span class="plan-banner-icon">' + iconHtml("check-circle") + '</span>' +
|
|
590
|
-
'<span class="plan-banner-text">Plan ready for review</span>';
|
|
591
|
-
el.classList.add("plan-exit");
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
ctx.addToMessages(el);
|
|
595
|
-
refreshIcons();
|
|
596
|
-
ctx.scrollToBottom();
|
|
597
|
-
return el;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
export function renderPlanCard(content) {
|
|
601
|
-
ctx.finalizeAssistantBlock();
|
|
602
|
-
closeToolGroup();
|
|
603
|
-
|
|
604
|
-
var el = document.createElement("div");
|
|
605
|
-
el.className = "plan-card";
|
|
606
|
-
|
|
607
|
-
var header = document.createElement("div");
|
|
608
|
-
header.className = "plan-card-header";
|
|
609
|
-
header.innerHTML =
|
|
610
|
-
'<span class="plan-card-icon">' + iconHtml("file-text") + '</span>' +
|
|
611
|
-
'<span class="plan-card-title">Implementation Plan</span>' +
|
|
612
|
-
'<button class="plan-card-copy" title="Copy plan">' + iconHtml("copy") + '</button>' +
|
|
613
|
-
'<span class="plan-card-chevron">' + iconHtml("chevron-down") + '</span>';
|
|
614
|
-
|
|
615
|
-
var body = document.createElement("div");
|
|
616
|
-
body.className = "plan-card-body";
|
|
617
|
-
body.innerHTML = renderMarkdown(content);
|
|
618
|
-
highlightCodeBlocks(body);
|
|
619
|
-
renderMermaidBlocks(body);
|
|
620
|
-
|
|
621
|
-
var copyBtn = header.querySelector(".plan-card-copy");
|
|
622
|
-
if (copyBtn) {
|
|
623
|
-
copyBtn.addEventListener("click", function (e) {
|
|
624
|
-
e.stopPropagation();
|
|
625
|
-
copyToClipboard(content).then(function () {
|
|
626
|
-
copyBtn.innerHTML = iconHtml("check");
|
|
627
|
-
refreshIcons();
|
|
628
|
-
setTimeout(function () {
|
|
629
|
-
copyBtn.innerHTML = iconHtml("copy");
|
|
630
|
-
refreshIcons();
|
|
631
|
-
}, 1500);
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
header.addEventListener("click", function () {
|
|
637
|
-
el.classList.toggle("collapsed");
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
el.appendChild(header);
|
|
641
|
-
el.appendChild(body);
|
|
642
|
-
ctx.addToMessages(el);
|
|
643
|
-
refreshIcons();
|
|
644
|
-
ctx.scrollToBottom();
|
|
645
|
-
return el;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// --- Todo rendering ---
|
|
649
|
-
function todoStatusIcon(status) {
|
|
650
|
-
switch (status) {
|
|
651
|
-
case "completed": return iconHtml("check-circle");
|
|
652
|
-
case "in_progress": return iconHtml("loader", "icon-spin");
|
|
653
|
-
default: return iconHtml("circle");
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export function handleTodoWrite(input) {
|
|
658
|
-
if (!input || !Array.isArray(input.todos)) return;
|
|
659
|
-
todoItems = input.todos.map(function (t, i) {
|
|
660
|
-
return {
|
|
661
|
-
id: t.id || String(i + 1),
|
|
662
|
-
content: t.content || t.subject || "",
|
|
663
|
-
status: t.status || "pending",
|
|
664
|
-
activeForm: t.activeForm || "",
|
|
665
|
-
};
|
|
666
|
-
});
|
|
667
|
-
renderTodoWidget();
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
export function handleTaskCreate(input) {
|
|
671
|
-
if (!input) return;
|
|
672
|
-
var id = String(todoItems.length + 1);
|
|
673
|
-
todoItems.push({
|
|
674
|
-
id: id,
|
|
675
|
-
content: input.subject || input.description || "",
|
|
676
|
-
status: "pending",
|
|
677
|
-
activeForm: input.activeForm || "",
|
|
678
|
-
});
|
|
679
|
-
renderTodoWidget();
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
export function handleTaskUpdate(input) {
|
|
683
|
-
if (!input || !input.taskId) return;
|
|
684
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
685
|
-
if (todoItems[i].id === input.taskId) {
|
|
686
|
-
if (input.status === "deleted") {
|
|
687
|
-
todoItems.splice(i, 1);
|
|
688
|
-
} else {
|
|
689
|
-
if (input.status) todoItems[i].status = input.status;
|
|
690
|
-
if (input.subject) todoItems[i].content = input.subject;
|
|
691
|
-
if (input.activeForm) todoItems[i].activeForm = input.activeForm;
|
|
692
|
-
}
|
|
693
|
-
break;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
renderTodoWidget();
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function renderTodoWidget() {
|
|
700
|
-
if (todoItems.length === 0) {
|
|
701
|
-
if (todoWidgetEl) { todoWidgetEl.remove(); todoWidgetEl = null; }
|
|
702
|
-
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
|
703
|
-
todoWidgetVisible = true;
|
|
704
|
-
updateTodoSticky();
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
var isNew = !todoWidgetEl;
|
|
709
|
-
if (isNew) {
|
|
710
|
-
todoWidgetEl = document.createElement("div");
|
|
711
|
-
todoWidgetEl.className = "todo-widget";
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
var completed = 0;
|
|
715
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
716
|
-
if (todoItems[i].status === "completed") completed++;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
var html = '<div class="todo-header">' +
|
|
720
|
-
'<span class="todo-header-icon">' + iconHtml("list-checks") + '</span>' +
|
|
721
|
-
'<span class="todo-header-title">Tasks</span>' +
|
|
722
|
-
'<span class="todo-header-count">' + completed + '/' + todoItems.length + '</span>' +
|
|
723
|
-
'</div>';
|
|
724
|
-
html += '<div class="todo-progress"><div class="todo-progress-bar" style="width:' +
|
|
725
|
-
(todoItems.length > 0 ? Math.round(completed / todoItems.length * 100) : 0) + '%"></div></div>';
|
|
726
|
-
html += '<div class="todo-items">';
|
|
727
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
728
|
-
var t = todoItems[i];
|
|
729
|
-
var statusClass = t.status === "completed" ? "completed" : t.status === "in_progress" ? "in-progress" : "pending";
|
|
730
|
-
html += '<div class="todo-item ' + statusClass + '">' +
|
|
731
|
-
'<span class="todo-item-icon">' + todoStatusIcon(t.status) + '</span>' +
|
|
732
|
-
'<span class="todo-item-text">' + escapeHtml(t.status === "in_progress" && t.activeForm ? t.activeForm : t.content) + '</span>' +
|
|
733
|
-
'</div>';
|
|
734
|
-
}
|
|
735
|
-
html += '</div>';
|
|
736
|
-
|
|
737
|
-
todoWidgetEl.innerHTML = html;
|
|
738
|
-
|
|
739
|
-
if (isNew) {
|
|
740
|
-
ctx.addToMessages(todoWidgetEl);
|
|
741
|
-
setupTodoObserver();
|
|
742
|
-
}
|
|
743
|
-
updateTodoSticky();
|
|
744
|
-
refreshIcons();
|
|
745
|
-
ctx.scrollToBottom();
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function setupTodoObserver() {
|
|
749
|
-
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
|
750
|
-
if (!todoWidgetEl) return;
|
|
751
|
-
|
|
752
|
-
var messagesEl = document.getElementById("messages");
|
|
753
|
-
if (!messagesEl) return;
|
|
754
|
-
|
|
755
|
-
todoObserver = new IntersectionObserver(function (entries) {
|
|
756
|
-
todoWidgetVisible = entries[0].isIntersecting;
|
|
757
|
-
updateTodoStickyVisibility();
|
|
758
|
-
}, { root: messagesEl, threshold: 0 });
|
|
759
|
-
|
|
760
|
-
todoObserver.observe(todoWidgetEl);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function updateTodoStickyVisibility() {
|
|
764
|
-
var stickyEl = document.getElementById("todo-sticky");
|
|
765
|
-
if (!stickyEl) return;
|
|
766
|
-
|
|
767
|
-
if (todoWidgetVisible) {
|
|
768
|
-
stickyEl.classList.add("hidden");
|
|
769
|
-
} else {
|
|
770
|
-
// Only show if there are active (non-completed) tasks
|
|
771
|
-
var hasActive = false;
|
|
772
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
773
|
-
if (todoItems[i].status !== "completed") { hasActive = true; break; }
|
|
774
|
-
}
|
|
775
|
-
if (hasActive) {
|
|
776
|
-
stickyEl.classList.remove("hidden");
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
function updateTodoSticky() {
|
|
782
|
-
var stickyEl = document.getElementById("todo-sticky");
|
|
783
|
-
if (!stickyEl) return;
|
|
784
|
-
|
|
785
|
-
// Hide if no active tasks (all completed or empty)
|
|
786
|
-
var hasActive = false;
|
|
787
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
788
|
-
if (todoItems[i].status !== "completed") { hasActive = true; break; }
|
|
789
|
-
}
|
|
790
|
-
if (!hasActive) {
|
|
791
|
-
stickyEl.classList.add("hidden");
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
var completed = 0;
|
|
796
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
797
|
-
if (todoItems[i].status === "completed") completed++;
|
|
798
|
-
}
|
|
799
|
-
var pct = Math.round(completed / todoItems.length * 100);
|
|
800
|
-
var wasCollapsed = stickyEl.classList.contains("collapsed");
|
|
801
|
-
|
|
802
|
-
var inProgressItem = null;
|
|
803
|
-
for (var j = 0; j < todoItems.length; j++) {
|
|
804
|
-
if (todoItems[j].status === "in_progress") { inProgressItem = todoItems[j]; break; }
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
var html = '<div class="todo-sticky-inner">' +
|
|
808
|
-
'<div class="todo-sticky-header">' +
|
|
809
|
-
'<span class="todo-sticky-icon">' + iconHtml("list-checks") + '</span>' +
|
|
810
|
-
'<span class="todo-sticky-title">Tasks</span>' +
|
|
811
|
-
(inProgressItem ? '<span class="todo-sticky-active">' + iconHtml("loader", "icon-spin") + ' ' + escapeHtml(inProgressItem.activeForm || inProgressItem.content) + '</span>' : '') +
|
|
812
|
-
'<span class="todo-sticky-count">' + completed + '/' + todoItems.length + '</span>' +
|
|
813
|
-
'<span class="todo-sticky-chevron">' + iconHtml("chevron-down") + '</span>' +
|
|
814
|
-
'</div>' +
|
|
815
|
-
'<div class="todo-sticky-progress"><div class="todo-sticky-progress-bar" style="width:' + pct + '%"></div></div>' +
|
|
816
|
-
'<div class="todo-sticky-items">';
|
|
817
|
-
|
|
818
|
-
for (var i = 0; i < todoItems.length; i++) {
|
|
819
|
-
var t = todoItems[i];
|
|
820
|
-
var statusClass = t.status === "completed" ? "completed" : t.status === "in_progress" ? "in-progress" : "pending";
|
|
821
|
-
html += '<div class="todo-sticky-item ' + statusClass + '">' +
|
|
822
|
-
'<span class="todo-sticky-item-icon">' + todoStatusIcon(t.status) + '</span>' +
|
|
823
|
-
'<span class="todo-sticky-item-text">' + escapeHtml(t.status === "in_progress" && t.activeForm ? t.activeForm : t.content) + '</span>' +
|
|
824
|
-
'</div>';
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
html += '</div></div>';
|
|
828
|
-
stickyEl.innerHTML = html;
|
|
829
|
-
|
|
830
|
-
// Only show sticky when in-chat widget is not visible in viewport
|
|
831
|
-
if (todoWidgetVisible) {
|
|
832
|
-
stickyEl.classList.add("hidden");
|
|
833
|
-
} else {
|
|
834
|
-
stickyEl.classList.remove("hidden");
|
|
835
|
-
}
|
|
836
|
-
if (wasCollapsed) stickyEl.classList.add("collapsed");
|
|
837
|
-
|
|
838
|
-
stickyEl.querySelector(".todo-sticky-header").addEventListener("click", function () {
|
|
839
|
-
stickyEl.classList.toggle("collapsed");
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
refreshIcons();
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// --- Thinking ---
|
|
846
|
-
export function startThinking() {
|
|
847
|
-
ctx.finalizeAssistantBlock();
|
|
848
|
-
|
|
849
|
-
var el = document.createElement("div");
|
|
850
|
-
el.className = "thinking-item";
|
|
851
|
-
el.innerHTML =
|
|
852
|
-
'<div class="thinking-header">' +
|
|
853
|
-
'<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
854
|
-
'<span class="thinking-label">Thinking</span>' +
|
|
855
|
-
'<span class="thinking-duration"></span>' +
|
|
856
|
-
'<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
857
|
-
'</div>' +
|
|
858
|
-
'<div class="thinking-content"></div>';
|
|
859
|
-
|
|
860
|
-
el.querySelector(".thinking-header").addEventListener("click", function () {
|
|
861
|
-
el.classList.toggle("expanded");
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
ctx.addToMessages(el);
|
|
865
|
-
refreshIcons();
|
|
866
|
-
ctx.scrollToBottom();
|
|
867
|
-
currentThinking = { el: el, fullText: "", startTime: Date.now() };
|
|
868
|
-
ctx.setActivity(randomThinkingVerb() + "...");
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
export function appendThinking(text) {
|
|
872
|
-
if (!currentThinking) return;
|
|
873
|
-
currentThinking.fullText += text;
|
|
874
|
-
currentThinking.el.querySelector(".thinking-content").textContent = currentThinking.fullText;
|
|
875
|
-
ctx.scrollToBottom();
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
export function stopThinking() {
|
|
879
|
-
if (!currentThinking) return;
|
|
880
|
-
var secs = ((Date.now() - currentThinking.startTime) / 1000).toFixed(1);
|
|
881
|
-
currentThinking.el.classList.add("done");
|
|
882
|
-
currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs + "s";
|
|
883
|
-
currentThinking = null;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// --- Tool items ---
|
|
887
|
-
export function createToolItem(id, name) {
|
|
888
|
-
ctx.finalizeAssistantBlock();
|
|
889
|
-
stopThinking();
|
|
890
|
-
|
|
891
|
-
// Group management: create new group or reuse existing open group
|
|
892
|
-
if (!currentToolGroup || currentToolGroup.closed) {
|
|
893
|
-
toolGroupCounter++;
|
|
894
|
-
var groupEl = document.createElement("div");
|
|
895
|
-
groupEl.className = "tool-group";
|
|
896
|
-
groupEl.dataset.groupId = "g" + toolGroupCounter;
|
|
897
|
-
groupEl.innerHTML =
|
|
898
|
-
'<div class="tool-group-header" style="display:none">' +
|
|
899
|
-
'<span class="tool-group-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
900
|
-
'<span class="tool-group-bullet"></span>' +
|
|
901
|
-
'<span class="tool-group-label">Running...</span>' +
|
|
902
|
-
'<span class="tool-group-status-icon">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
903
|
-
'</div>' +
|
|
904
|
-
'<div class="tool-group-items"></div>';
|
|
905
|
-
|
|
906
|
-
groupEl.querySelector(".tool-group-header").addEventListener("click", function () {
|
|
907
|
-
groupEl.classList.toggle("collapsed");
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
ctx.addToMessages(groupEl);
|
|
911
|
-
refreshIcons();
|
|
912
|
-
|
|
913
|
-
currentToolGroup = {
|
|
914
|
-
el: groupEl,
|
|
915
|
-
id: "g" + toolGroupCounter,
|
|
916
|
-
toolNames: [],
|
|
917
|
-
toolCount: 0,
|
|
918
|
-
doneCount: 0,
|
|
919
|
-
errorCount: 0,
|
|
920
|
-
closed: false,
|
|
921
|
-
};
|
|
922
|
-
toolGroups[currentToolGroup.id] = currentToolGroup;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
var el = document.createElement("div");
|
|
926
|
-
el.className = "tool-item";
|
|
927
|
-
el.dataset.toolId = id;
|
|
928
|
-
el.innerHTML =
|
|
929
|
-
'<div class="tool-header">' +
|
|
930
|
-
'<span class="tool-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
931
|
-
'<span class="tool-bullet"></span>' +
|
|
932
|
-
'<span class="tool-name"></span>' +
|
|
933
|
-
'<span class="tool-desc"></span>' +
|
|
934
|
-
'<span class="tool-status-icon">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
935
|
-
'</div>' +
|
|
936
|
-
'<div class="tool-subtitle">' +
|
|
937
|
-
'<span class="tool-connector">└</span>' +
|
|
938
|
-
'<span class="tool-subtitle-text">Running...</span>' +
|
|
939
|
-
'</div>';
|
|
940
|
-
|
|
941
|
-
el.querySelector(".tool-name").textContent = name;
|
|
942
|
-
|
|
943
|
-
// Append to group instead of messages directly
|
|
944
|
-
currentToolGroup.el.querySelector(".tool-group-items").appendChild(el);
|
|
945
|
-
currentToolGroup.toolNames.push(name);
|
|
946
|
-
currentToolGroup.toolCount++;
|
|
947
|
-
updateToolGroupHeader(currentToolGroup);
|
|
948
|
-
|
|
949
|
-
refreshIcons();
|
|
950
|
-
ctx.scrollToBottom();
|
|
951
|
-
|
|
952
|
-
tools[id] = { el: el, name: name, input: null, done: false, groupId: currentToolGroup.id };
|
|
953
|
-
ctx.setActivity("Running " + name + "...");
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
export function updateToolExecuting(id, name, input) {
|
|
957
|
-
var tool = tools[id];
|
|
958
|
-
if (!tool) return;
|
|
959
|
-
|
|
960
|
-
tool.input = input;
|
|
961
|
-
var descEl = tool.el.querySelector(".tool-desc");
|
|
962
|
-
descEl.textContent = toolSummary(name, input);
|
|
963
|
-
|
|
964
|
-
// Make file path clickable for Read/Edit/Write tools
|
|
965
|
-
var filePath = input && input.file_path;
|
|
966
|
-
if (filePath && (name === "Read" || name === "Edit" || name === "Write")) {
|
|
967
|
-
descEl.classList.add("tool-desc-link");
|
|
968
|
-
descEl.dataset.filePath = filePath;
|
|
969
|
-
descEl.insertAdjacentHTML("beforeend", '<span class="tool-desc-link-icon">' + iconHtml("external-link") + '</span>');
|
|
970
|
-
refreshIcons();
|
|
971
|
-
(function (toolName, toolInput) {
|
|
972
|
-
descEl.onclick = function (e) {
|
|
973
|
-
e.stopPropagation();
|
|
974
|
-
if (toolName === "Edit" && toolInput && (toolInput.old_string || toolInput.new_string)) {
|
|
975
|
-
openFile(filePath, { diff: { oldStr: toolInput.old_string || "", newStr: toolInput.new_string || "" } });
|
|
976
|
-
} else {
|
|
977
|
-
openFile(filePath);
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
})(name, input);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
ctx.setActivity(toolActivityText(name, input));
|
|
984
|
-
|
|
985
|
-
var subtitleText = tool.el.querySelector(".tool-subtitle-text");
|
|
986
|
-
if (subtitleText) subtitleText.textContent = toolActivityText(name, input);
|
|
987
|
-
|
|
988
|
-
ctx.scrollToBottom();
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
function renderEditDiff(oldStr, newStr, filePath) {
|
|
992
|
-
var wrapper = document.createElement("div");
|
|
993
|
-
wrapper.className = "edit-diff";
|
|
994
|
-
var lang = getLanguageFromPath(filePath);
|
|
995
|
-
|
|
996
|
-
// Header with file path and split toggle (desktop only)
|
|
997
|
-
var header = document.createElement("div");
|
|
998
|
-
header.className = "edit-diff-header";
|
|
999
|
-
|
|
1000
|
-
var pathSpan = document.createElement("span");
|
|
1001
|
-
pathSpan.className = "edit-diff-path edit-diff-path-link";
|
|
1002
|
-
pathSpan.textContent = filePath || "";
|
|
1003
|
-
if (filePath) {
|
|
1004
|
-
(function (fp, os, ns) {
|
|
1005
|
-
pathSpan.addEventListener("click", function (e) {
|
|
1006
|
-
e.stopPropagation();
|
|
1007
|
-
openFile(fp, { diff: { oldStr: os || "", newStr: ns || "" } });
|
|
1008
|
-
});
|
|
1009
|
-
})(filePath, oldStr, newStr);
|
|
1010
|
-
}
|
|
1011
|
-
header.appendChild(pathSpan);
|
|
1012
|
-
|
|
1013
|
-
var isMobile = "ontouchstart" in window;
|
|
1014
|
-
var isSplit = false;
|
|
1015
|
-
|
|
1016
|
-
var unifiedBtn = document.createElement("button");
|
|
1017
|
-
unifiedBtn.className = "edit-diff-toggle active";
|
|
1018
|
-
unifiedBtn.innerHTML = iconHtml("list");
|
|
1019
|
-
unifiedBtn.title = "Unified view";
|
|
1020
|
-
|
|
1021
|
-
var splitBtn = document.createElement("button");
|
|
1022
|
-
splitBtn.className = "edit-diff-toggle";
|
|
1023
|
-
splitBtn.innerHTML = iconHtml("columns-2");
|
|
1024
|
-
splitBtn.title = "Split view";
|
|
1025
|
-
|
|
1026
|
-
var toggleWrap = document.createElement("span");
|
|
1027
|
-
toggleWrap.className = "edit-diff-toggles";
|
|
1028
|
-
if (isMobile) toggleWrap.style.display = "none";
|
|
1029
|
-
toggleWrap.appendChild(unifiedBtn);
|
|
1030
|
-
toggleWrap.appendChild(splitBtn);
|
|
1031
|
-
header.appendChild(toggleWrap);
|
|
1032
|
-
|
|
1033
|
-
wrapper.appendChild(header);
|
|
1034
|
-
|
|
1035
|
-
var currentBody = renderUnifiedDiff(oldStr, newStr, lang);
|
|
1036
|
-
wrapper.appendChild(currentBody);
|
|
1037
|
-
|
|
1038
|
-
unifiedBtn.addEventListener("click", function (e) {
|
|
1039
|
-
e.stopPropagation();
|
|
1040
|
-
if (!isSplit) return;
|
|
1041
|
-
isSplit = false;
|
|
1042
|
-
unifiedBtn.classList.add("active");
|
|
1043
|
-
splitBtn.classList.remove("active");
|
|
1044
|
-
wrapper.removeChild(currentBody);
|
|
1045
|
-
currentBody = renderUnifiedDiff(oldStr, newStr, lang);
|
|
1046
|
-
wrapper.appendChild(currentBody);
|
|
1047
|
-
refreshIcons();
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
splitBtn.addEventListener("click", function (e) {
|
|
1051
|
-
e.stopPropagation();
|
|
1052
|
-
if (isSplit) return;
|
|
1053
|
-
isSplit = true;
|
|
1054
|
-
splitBtn.classList.add("active");
|
|
1055
|
-
unifiedBtn.classList.remove("active");
|
|
1056
|
-
wrapper.removeChild(currentBody);
|
|
1057
|
-
currentBody = renderSplitDiff(oldStr, newStr, lang);
|
|
1058
|
-
wrapper.appendChild(currentBody);
|
|
1059
|
-
refreshIcons();
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
return wrapper;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
function isDiffContent(text) {
|
|
1066
|
-
var lines = text.split("\n");
|
|
1067
|
-
var diffMarkers = 0;
|
|
1068
|
-
for (var i = 0; i < Math.min(lines.length, 20); i++) {
|
|
1069
|
-
var l = lines[i];
|
|
1070
|
-
if (l.startsWith("@@") || l.startsWith("---") || l.startsWith("+++")) {
|
|
1071
|
-
diffMarkers++;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
return diffMarkers >= 2;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
function getLanguageFromPath(filePath) {
|
|
1078
|
-
if (!filePath) return null;
|
|
1079
|
-
var parts = filePath.split("/");
|
|
1080
|
-
var filename = parts[parts.length - 1].toLowerCase();
|
|
1081
|
-
var dotIdx = filename.lastIndexOf(".");
|
|
1082
|
-
if (dotIdx === -1 || dotIdx === filename.length - 1) return null;
|
|
1083
|
-
var ext = filename.substring(dotIdx + 1);
|
|
1084
|
-
var map = {
|
|
1085
|
-
js: "javascript", jsx: "javascript", mjs: "javascript", cjs: "javascript",
|
|
1086
|
-
ts: "typescript", tsx: "typescript", mts: "typescript",
|
|
1087
|
-
py: "python", rb: "ruby", rs: "rust", go: "go",
|
|
1088
|
-
java: "java", kt: "kotlin", kts: "kotlin",
|
|
1089
|
-
cs: "csharp", cpp: "cpp", cc: "cpp", c: "c", h: "c", hpp: "cpp",
|
|
1090
|
-
css: "css", scss: "scss", less: "less",
|
|
1091
|
-
html: "xml", htm: "xml", xml: "xml", svg: "xml",
|
|
1092
|
-
json: "json", yaml: "yaml", yml: "yaml",
|
|
1093
|
-
md: "markdown", sh: "bash", bash: "bash", zsh: "bash",
|
|
1094
|
-
sql: "sql", swift: "swift", php: "php",
|
|
1095
|
-
toml: "ini", ini: "ini", conf: "ini",
|
|
1096
|
-
lua: "lua", r: "r", pl: "perl",
|
|
1097
|
-
ex: "elixir", exs: "elixir",
|
|
1098
|
-
erl: "erlang", hs: "haskell",
|
|
1099
|
-
graphql: "graphql", gql: "graphql",
|
|
1100
|
-
};
|
|
1101
|
-
return map[ext] || null;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
function parseLineNumberedContent(text) {
|
|
1105
|
-
var lines = text.split("\n");
|
|
1106
|
-
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
1107
|
-
lines.pop();
|
|
1108
|
-
}
|
|
1109
|
-
if (lines.length === 0) return null;
|
|
1110
|
-
|
|
1111
|
-
var pattern = /^\s*(\d+)[→\t](.*)$/;
|
|
1112
|
-
var checkCount = Math.min(lines.length, 5);
|
|
1113
|
-
var matchCount = 0;
|
|
1114
|
-
for (var i = 0; i < checkCount; i++) {
|
|
1115
|
-
if (pattern.test(lines[i])) matchCount++;
|
|
1116
|
-
}
|
|
1117
|
-
if (matchCount < Math.ceil(checkCount * 0.6)) return null;
|
|
1118
|
-
|
|
1119
|
-
var numbers = [];
|
|
1120
|
-
var code = [];
|
|
1121
|
-
for (var i = 0; i < lines.length; i++) {
|
|
1122
|
-
var m = lines[i].match(pattern);
|
|
1123
|
-
if (m) {
|
|
1124
|
-
numbers.push(m[1]);
|
|
1125
|
-
code.push(m[2]);
|
|
1126
|
-
} else {
|
|
1127
|
-
numbers.push("");
|
|
1128
|
-
code.push(lines[i]);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
return { numbers: numbers, code: code };
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
export function updateToolResult(id, content, isError) {
|
|
1135
|
-
var tool = tools[id];
|
|
1136
|
-
if (!tool) return;
|
|
1137
|
-
|
|
1138
|
-
var subtitleText = tool.el.querySelector(".tool-subtitle-text");
|
|
1139
|
-
if (subtitleText && tool.input) {
|
|
1140
|
-
subtitleText.textContent = toolActivityText(tool.name, tool.input);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
var resultBlock = document.createElement("div");
|
|
1144
|
-
var displayContent = content || "(no output)";
|
|
1145
|
-
displayContent = displayContent.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
1146
|
-
if (displayContent.length > 10000) displayContent = displayContent.substring(0, 10000) + "\n... (truncated)";
|
|
1147
|
-
|
|
1148
|
-
var hasEditDiff = !isError && tool.name === "Edit" && tool.input && tool.input.old_string && tool.input.new_string;
|
|
1149
|
-
var expandByDefault = hasEditDiff || (!isError && tool.name === "Edit" && isDiffContent(displayContent));
|
|
1150
|
-
if (expandByDefault) {
|
|
1151
|
-
resultBlock.className = "tool-result-block";
|
|
1152
|
-
tool.el.classList.add("expanded");
|
|
1153
|
-
} else {
|
|
1154
|
-
resultBlock.className = "tool-result-block collapsed";
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
if (hasEditDiff) {
|
|
1158
|
-
resultBlock.appendChild(renderEditDiff(tool.input.old_string, tool.input.new_string, tool.input.file_path));
|
|
1159
|
-
} else if (!isError && isDiffContent(displayContent)) {
|
|
1160
|
-
var patchLang = tool.input && tool.input.file_path ? getLanguageFromPath(tool.input.file_path) : null;
|
|
1161
|
-
resultBlock.appendChild(renderPatchDiff(displayContent, patchLang));
|
|
1162
|
-
} else if (!isError && tool.name === "Read" && tool.input && tool.input.file_path) {
|
|
1163
|
-
var parsed = parseLineNumberedContent(displayContent);
|
|
1164
|
-
if (parsed) {
|
|
1165
|
-
var lang = getLanguageFromPath(tool.input.file_path);
|
|
1166
|
-
var viewer = document.createElement("div");
|
|
1167
|
-
viewer.className = "code-viewer";
|
|
1168
|
-
|
|
1169
|
-
var gutter = document.createElement("pre");
|
|
1170
|
-
gutter.className = "code-gutter";
|
|
1171
|
-
gutter.textContent = parsed.numbers.join("\n");
|
|
1172
|
-
|
|
1173
|
-
var codeBlock = document.createElement("pre");
|
|
1174
|
-
codeBlock.className = "code-content";
|
|
1175
|
-
var codeText = parsed.code.join("\n");
|
|
1176
|
-
|
|
1177
|
-
if (lang) {
|
|
1178
|
-
try {
|
|
1179
|
-
var highlighted = hljs.highlight(codeText, { language: lang });
|
|
1180
|
-
var codeEl = document.createElement("code");
|
|
1181
|
-
codeEl.className = "hljs language-" + lang;
|
|
1182
|
-
codeEl.innerHTML = highlighted.value;
|
|
1183
|
-
codeBlock.appendChild(codeEl);
|
|
1184
|
-
} catch (e) {
|
|
1185
|
-
codeBlock.textContent = codeText;
|
|
1186
|
-
}
|
|
1187
|
-
} else {
|
|
1188
|
-
codeBlock.textContent = codeText;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
viewer.appendChild(gutter);
|
|
1192
|
-
viewer.appendChild(codeBlock);
|
|
1193
|
-
|
|
1194
|
-
// Sync vertical scroll between gutter and code
|
|
1195
|
-
viewer.addEventListener("scroll", function () {
|
|
1196
|
-
gutter.scrollTop = viewer.scrollTop;
|
|
1197
|
-
codeBlock.scrollTop = viewer.scrollTop;
|
|
1198
|
-
});
|
|
1199
|
-
|
|
1200
|
-
resultBlock.appendChild(viewer);
|
|
1201
|
-
} else {
|
|
1202
|
-
var pre = document.createElement("pre");
|
|
1203
|
-
pre.textContent = displayContent;
|
|
1204
|
-
resultBlock.appendChild(pre);
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
var pre = document.createElement("pre");
|
|
1208
|
-
if (isError) pre.className = "is-error";
|
|
1209
|
-
pre.textContent = displayContent;
|
|
1210
|
-
resultBlock.appendChild(pre);
|
|
1211
|
-
}
|
|
1212
|
-
tool.el.appendChild(resultBlock);
|
|
1213
|
-
|
|
1214
|
-
tool.el.querySelector(".tool-header").addEventListener("click", function () {
|
|
1215
|
-
resultBlock.classList.toggle("collapsed");
|
|
1216
|
-
tool.el.classList.toggle("expanded");
|
|
1217
|
-
});
|
|
1218
|
-
|
|
1219
|
-
markToolDone(id, isError);
|
|
1220
|
-
ctx.scrollToBottom();
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
export function markToolDone(id, isError) {
|
|
1224
|
-
var tool = tools[id];
|
|
1225
|
-
if (!tool || tool.done) return;
|
|
1226
|
-
|
|
1227
|
-
tool.done = true;
|
|
1228
|
-
if (!tool.el) return; // hidden tool (plan mode)
|
|
1229
|
-
|
|
1230
|
-
tool.el.classList.add("done");
|
|
1231
|
-
if (isError) tool.el.classList.add("error");
|
|
1232
|
-
|
|
1233
|
-
var icon = tool.el.querySelector(".tool-status-icon");
|
|
1234
|
-
if (isError) {
|
|
1235
|
-
icon.innerHTML = '<span class="err-icon">' + iconHtml("alert-triangle") + '</span>';
|
|
1236
|
-
} else {
|
|
1237
|
-
icon.innerHTML = '<span class="check">' + iconHtml("check") + '</span>';
|
|
1238
|
-
}
|
|
1239
|
-
refreshIcons();
|
|
1240
|
-
|
|
1241
|
-
// Update group state
|
|
1242
|
-
if (tool.groupId) {
|
|
1243
|
-
var group = findToolGroup(tool.groupId);
|
|
1244
|
-
if (group) {
|
|
1245
|
-
group.doneCount++;
|
|
1246
|
-
if (isError) group.errorCount++;
|
|
1247
|
-
updateToolGroupHeader(group);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
export function markAllToolsDone() {
|
|
1253
|
-
for (var id in tools) {
|
|
1254
|
-
if (tools.hasOwnProperty(id) && !tools[id].done) {
|
|
1255
|
-
markToolDone(id, false);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// --- Sub-agent (Task tool) log ---
|
|
1261
|
-
export function updateSubagentActivity(parentToolId, text) {
|
|
1262
|
-
var tool = tools[parentToolId];
|
|
1263
|
-
if (!tool || !tool.el) return;
|
|
1264
|
-
|
|
1265
|
-
// Update subtitle text with current activity
|
|
1266
|
-
var subtitleText = tool.el.querySelector(".tool-subtitle-text");
|
|
1267
|
-
if (subtitleText) subtitleText.textContent = text;
|
|
1268
|
-
|
|
1269
|
-
// Update or create the subagent log
|
|
1270
|
-
var log = tool.el.querySelector(".subagent-log");
|
|
1271
|
-
if (!log) {
|
|
1272
|
-
log = document.createElement("div");
|
|
1273
|
-
log.className = "subagent-log";
|
|
1274
|
-
tool.el.appendChild(log);
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
ctx.setActivity(text);
|
|
1278
|
-
ctx.scrollToBottom();
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
export function addSubagentToolEntry(parentToolId, toolName, toolId, text) {
|
|
1282
|
-
var tool = tools[parentToolId];
|
|
1283
|
-
if (!tool || !tool.el) return;
|
|
1284
|
-
|
|
1285
|
-
// Update subtitle
|
|
1286
|
-
var subtitleText = tool.el.querySelector(".tool-subtitle-text");
|
|
1287
|
-
if (subtitleText) subtitleText.textContent = text;
|
|
1288
|
-
|
|
1289
|
-
// Create log if needed
|
|
1290
|
-
var log = tool.el.querySelector(".subagent-log");
|
|
1291
|
-
if (!log) {
|
|
1292
|
-
log = document.createElement("div");
|
|
1293
|
-
log.className = "subagent-log";
|
|
1294
|
-
tool.el.appendChild(log);
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// Add entry
|
|
1298
|
-
var entry = document.createElement("div");
|
|
1299
|
-
entry.className = "subagent-log-entry";
|
|
1300
|
-
entry.innerHTML =
|
|
1301
|
-
'<span class="subagent-log-bullet"></span>' +
|
|
1302
|
-
'<span class="subagent-log-tool"></span>' +
|
|
1303
|
-
'<span class="subagent-log-text"></span>';
|
|
1304
|
-
entry.querySelector(".subagent-log-tool").textContent = toolName;
|
|
1305
|
-
entry.querySelector(".subagent-log-text").textContent = text;
|
|
1306
|
-
log.appendChild(entry);
|
|
1307
|
-
|
|
1308
|
-
// Auto-scroll to latest entry
|
|
1309
|
-
log.scrollTop = log.scrollHeight;
|
|
1310
|
-
|
|
1311
|
-
ctx.setActivity(text);
|
|
1312
|
-
ctx.scrollToBottom();
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
export function markSubagentDone(parentToolId) {
|
|
1316
|
-
var tool = tools[parentToolId];
|
|
1317
|
-
if (!tool || !tool.el) return;
|
|
1318
|
-
|
|
1319
|
-
var subtitleText = tool.el.querySelector(".tool-subtitle-text");
|
|
1320
|
-
if (subtitleText) subtitleText.textContent = "Agent finished";
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
export function addTurnMeta(cost, duration) {
|
|
1324
|
-
closeToolGroup();
|
|
1325
|
-
var div = document.createElement("div");
|
|
1326
|
-
div.className = "turn-meta";
|
|
1327
|
-
div.dataset.turn = ctx.turnCounter;
|
|
1328
|
-
var parts = [];
|
|
1329
|
-
if (cost != null) parts.push("$" + cost.toFixed(4));
|
|
1330
|
-
if (duration != null) parts.push((duration / 1000).toFixed(1) + "s");
|
|
1331
|
-
if (parts.length) {
|
|
1332
|
-
div.textContent = parts.join(" \u00b7 ");
|
|
1333
|
-
ctx.addToMessages(div);
|
|
1334
|
-
ctx.scrollToBottom();
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// --- Tool group exports ---
|
|
1339
|
-
export { closeToolGroup };
|
|
1340
|
-
|
|
1341
|
-
export function removeToolFromGroup(toolId) {
|
|
1342
|
-
var tool = tools[toolId];
|
|
1343
|
-
if (!tool || !tool.groupId) return;
|
|
1344
|
-
var group = findToolGroup(tool.groupId);
|
|
1345
|
-
if (!group) return;
|
|
1346
|
-
group.toolCount--;
|
|
1347
|
-
// Remove tool name from the names array (remove first occurrence)
|
|
1348
|
-
var idx = group.toolNames.indexOf(tool.name);
|
|
1349
|
-
if (idx !== -1) group.toolNames.splice(idx, 1);
|
|
1350
|
-
if (tool.done) group.doneCount--;
|
|
1351
|
-
updateToolGroupHeader(group);
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
// Expose state getters and reset
|
|
1355
|
-
export function getTools() { return tools; }
|
|
1356
|
-
export function isInPlanMode() { return inPlanMode; }
|
|
1357
|
-
export function getPlanContent() { return planContent; }
|
|
1358
|
-
export function setPlanContent(c) { planContent = c; }
|
|
1359
|
-
export function isPlanFilePath(fp) { return isPlanFile(fp); }
|
|
1360
|
-
export function getPlanModeTools() { return PLAN_MODE_TOOLS; }
|
|
1361
|
-
export function getTodoTools() { return TODO_TOOLS; }
|
|
1362
|
-
export function getHiddenResultTools() { return HIDDEN_RESULT_TOOLS; }
|
|
1363
|
-
|
|
1364
|
-
export function saveToolState() {
|
|
1365
|
-
return {
|
|
1366
|
-
tools: tools,
|
|
1367
|
-
currentThinking: currentThinking,
|
|
1368
|
-
todoWidgetEl: todoWidgetEl,
|
|
1369
|
-
inPlanMode: inPlanMode,
|
|
1370
|
-
planContent: planContent,
|
|
1371
|
-
currentToolGroup: currentToolGroup,
|
|
1372
|
-
toolGroupCounter: toolGroupCounter,
|
|
1373
|
-
toolGroups: toolGroups,
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
export function restoreToolState(saved) {
|
|
1378
|
-
tools = saved.tools;
|
|
1379
|
-
currentThinking = saved.currentThinking;
|
|
1380
|
-
todoWidgetEl = saved.todoWidgetEl;
|
|
1381
|
-
inPlanMode = saved.inPlanMode;
|
|
1382
|
-
planContent = saved.planContent;
|
|
1383
|
-
currentToolGroup = saved.currentToolGroup;
|
|
1384
|
-
toolGroupCounter = saved.toolGroupCounter;
|
|
1385
|
-
toolGroups = saved.toolGroups;
|
|
1386
|
-
if (todoWidgetEl) {
|
|
1387
|
-
setupTodoObserver();
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
export function resetToolState() {
|
|
1392
|
-
tools = {};
|
|
1393
|
-
currentThinking = null;
|
|
1394
|
-
inPlanMode = false;
|
|
1395
|
-
planContent = null;
|
|
1396
|
-
todoItems = [];
|
|
1397
|
-
todoWidgetEl = null;
|
|
1398
|
-
todoWidgetVisible = true;
|
|
1399
|
-
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
|
1400
|
-
pendingPermissions = {};
|
|
1401
|
-
currentToolGroup = null;
|
|
1402
|
-
toolGroupCounter = 0;
|
|
1403
|
-
toolGroups = {};
|
|
1404
|
-
var stickyEl = document.getElementById("todo-sticky");
|
|
1405
|
-
if (stickyEl) { stickyEl.classList.add("hidden"); stickyEl.innerHTML = ""; }
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
export function initTools(_ctx) {
|
|
1409
|
-
ctx = _ctx;
|
|
1410
|
-
}
|