@duckmind/dm-darwin-x64 0.13.5 → 0.13.7
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/dm +0 -0
- package/extensions/.dm-extensions.json +39 -15
- package/extensions/dm-multicodex/package-lock.json +302 -1814
- package/extensions/dm-phone/README.md +23 -0
- package/extensions/dm-phone/index.ts +12 -0
- package/extensions/dm-phone/node_modules/.package-lock.json +29 -0
- package/extensions/dm-phone/node_modules/ws/LICENSE +20 -0
- package/extensions/dm-phone/node_modules/ws/README.md +548 -0
- package/extensions/dm-phone/node_modules/ws/browser.js +8 -0
- package/extensions/dm-phone/node_modules/ws/index.js +22 -0
- package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +131 -0
- package/extensions/dm-phone/node_modules/ws/lib/constants.js +19 -0
- package/extensions/dm-phone/node_modules/ws/lib/event-target.js +292 -0
- package/extensions/dm-phone/node_modules/ws/lib/extension.js +203 -0
- package/extensions/dm-phone/node_modules/ws/lib/limiter.js +55 -0
- package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/extensions/dm-phone/node_modules/ws/lib/receiver.js +706 -0
- package/extensions/dm-phone/node_modules/ws/lib/sender.js +602 -0
- package/extensions/dm-phone/node_modules/ws/lib/stream.js +161 -0
- package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +62 -0
- package/extensions/dm-phone/node_modules/ws/lib/validation.js +152 -0
- package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +554 -0
- package/extensions/dm-phone/node_modules/ws/lib/websocket.js +1393 -0
- package/extensions/dm-phone/node_modules/ws/package.json +70 -0
- package/extensions/dm-phone/node_modules/ws/wrapper.mjs +21 -0
- package/extensions/dm-phone/package-lock.json +66 -0
- package/extensions/dm-phone/package.json +35 -0
- package/extensions/dm-phone/phone-session-pool.ts +8 -0
- package/extensions/dm-phone/public/app/attachments.js +233 -0
- package/extensions/dm-phone/public/app/autocomplete-controller.js +81 -0
- package/extensions/dm-phone/public/app/autocomplete.js +135 -0
- package/extensions/dm-phone/public/app/bindings.js +178 -0
- package/extensions/dm-phone/public/app/command-catalog.js +76 -0
- package/extensions/dm-phone/public/app/commands.js +370 -0
- package/extensions/dm-phone/public/app/constants.js +60 -0
- package/extensions/dm-phone/public/app/formatters.js +131 -0
- package/extensions/dm-phone/public/app/handlers.js +442 -0
- package/extensions/dm-phone/public/app/main.js +6 -0
- package/extensions/dm-phone/public/app/markdown.js +105 -0
- package/extensions/dm-phone/public/app/messages.js +418 -0
- package/extensions/dm-phone/public/app/sheet-actions.js +113 -0
- package/extensions/dm-phone/public/app/sheet-navigation.js +19 -0
- package/extensions/dm-phone/public/app/sheets-view.js +272 -0
- package/extensions/dm-phone/public/app/state.js +95 -0
- package/extensions/dm-phone/public/app/tool-rendering.js +562 -0
- package/extensions/dm-phone/public/app/transport.js +176 -0
- package/extensions/dm-phone/public/app/ui.js +409 -0
- package/extensions/dm-phone/public/app.js +1 -0
- package/extensions/dm-phone/public/icon.svg +15 -0
- package/extensions/dm-phone/public/index.html +147 -0
- package/extensions/dm-phone/public/manifest.webmanifest +17 -0
- package/extensions/dm-phone/public/styles.css +1139 -0
- package/extensions/dm-phone/public/sw.js +78 -0
- package/extensions/dm-phone/src/extension/phone-args.ts +121 -0
- package/extensions/dm-phone/src/extension/phone-paths.ts +250 -0
- package/extensions/dm-phone/src/extension/phone-quota.ts +188 -0
- package/extensions/dm-phone/src/extension/phone-runtime.ts +154 -0
- package/extensions/dm-phone/src/extension/phone-server-runtime.ts +1217 -0
- package/extensions/dm-phone/src/extension/phone-sessions.ts +139 -0
- package/extensions/dm-phone/src/extension/phone-static.ts +30 -0
- package/extensions/dm-phone/src/extension/phone-tailscale.ts +148 -0
- package/extensions/dm-phone/src/extension/phone-theme.ts +85 -0
- package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +112 -0
- package/extensions/dm-phone/src/extension/register-phone-extension.ts +106 -0
- package/extensions/dm-phone/src/extension/types.ts +73 -0
- package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +881 -0
- package/extensions/dm-phone/src/session-pool/session-pool.ts +470 -0
- package/extensions/dm-phone/src/session-pool/session-worker.ts +734 -0
- package/extensions/dm-phone/src/session-pool/types.ts +105 -0
- package/extensions/dm-phone/src/session-pool/utils.ts +23 -0
- package/extensions/dm-subagents/artifacts.ts +11 -5
- package/extensions/dm-subagents/async-execution.ts +4 -1
- package/extensions/dm-subagents/index.ts +1 -1
- package/extensions/dm-subagents/schemas.ts +1 -1
- package/extensions/dm-subagents/settings.ts +6 -4
- package/extensions/dm-subagents/subagent-runner.ts +167 -50
- package/extensions/dm-subagents/types.ts +62 -2
- package/package.json +1 -1
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import { TOOL_LANGUAGE_LABELS } from "./constants.js";
|
|
2
|
+
import { state } from "./state.js";
|
|
3
|
+
import {
|
|
4
|
+
asRecord,
|
|
5
|
+
countTextLines,
|
|
6
|
+
escapeAttribute,
|
|
7
|
+
escapeHtml,
|
|
8
|
+
formatBytes,
|
|
9
|
+
normalizeNewlines,
|
|
10
|
+
} from "./formatters.js";
|
|
11
|
+
import { renderMarkdownLite } from "./markdown.js";
|
|
12
|
+
|
|
13
|
+
function normalizedToolName(name = "") {
|
|
14
|
+
return String(name || "").trim().split(" · ")[0].toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function detectLanguageLabel(filePath = "") {
|
|
18
|
+
const normalized = String(filePath || "").trim().toLowerCase();
|
|
19
|
+
const match = normalized.match(/\.([a-z0-9]+)$/);
|
|
20
|
+
if (!match) return "";
|
|
21
|
+
return TOOL_LANGUAGE_LABELS[match[1]] || match[1].toUpperCase();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getToolPath(item) {
|
|
25
|
+
return typeof item?.args?.path === "string" ? item.args.path : "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseNumberedDiffLines(diffText = "") {
|
|
29
|
+
const normalized = normalizeNewlines(diffText);
|
|
30
|
+
if (!normalized.trim()) return [];
|
|
31
|
+
|
|
32
|
+
return normalized
|
|
33
|
+
.split("\n")
|
|
34
|
+
.map((line) => {
|
|
35
|
+
const match = line.match(/^([+\-\s])(\s*\d*)\s(.*)$/);
|
|
36
|
+
if (!match) {
|
|
37
|
+
return { kind: "meta", prefix: "", lineNumber: "", text: line };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const prefix = match[1];
|
|
41
|
+
return {
|
|
42
|
+
kind: prefix === "+" ? "added" : prefix === "-" ? "removed" : "context",
|
|
43
|
+
prefix,
|
|
44
|
+
lineNumber: match[2].trim(),
|
|
45
|
+
text: match[3] || "",
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
.filter((line) => line.kind !== "meta" || line.text.trim());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildEditPreviewLines(oldText = "", newText = "") {
|
|
52
|
+
const oldLines = normalizeNewlines(oldText).split("\n");
|
|
53
|
+
const newLines = normalizeNewlines(newText).split("\n");
|
|
54
|
+
|
|
55
|
+
if (oldLines.length === 1 && oldLines[0] === "" && newLines.length === 1 && newLines[0] === "") {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let prefix = 0;
|
|
60
|
+
while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix]) {
|
|
61
|
+
prefix += 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let suffix = 0;
|
|
65
|
+
while (
|
|
66
|
+
suffix < oldLines.length - prefix
|
|
67
|
+
&& suffix < newLines.length - prefix
|
|
68
|
+
&& oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix]
|
|
69
|
+
) {
|
|
70
|
+
suffix += 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const lines = [];
|
|
74
|
+
|
|
75
|
+
for (let index = 0; index < prefix; index += 1) {
|
|
76
|
+
lines.push({ kind: "context", prefix: " ", lineNumber: String(index + 1), text: oldLines[index] || "" });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (let index = prefix; index < oldLines.length - suffix; index += 1) {
|
|
80
|
+
lines.push({ kind: "removed", prefix: "-", lineNumber: String(index + 1), text: oldLines[index] || "" });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (let index = prefix; index < newLines.length - suffix; index += 1) {
|
|
84
|
+
lines.push({ kind: "added", prefix: "+", lineNumber: String(index + 1), text: newLines[index] || "" });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (let index = suffix; index > 0; index -= 1) {
|
|
88
|
+
const oldIndex = oldLines.length - index;
|
|
89
|
+
const newIndex = newLines.length - index;
|
|
90
|
+
lines.push({ kind: "context", prefix: " ", lineNumber: String(newIndex + 1), text: newLines[newIndex] || oldLines[oldIndex] || "" });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lines;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function computeDiffStats(lines = []) {
|
|
97
|
+
return lines.reduce((stats, line) => {
|
|
98
|
+
if (line.kind === "added") stats.added += 1;
|
|
99
|
+
if (line.kind === "removed") stats.removed += 1;
|
|
100
|
+
return stats;
|
|
101
|
+
}, { added: 0, removed: 0 });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function renderToolBadge(text, variant = "neutral") {
|
|
105
|
+
if (!text) return "";
|
|
106
|
+
return `<span class="tool-panel-badge ${variant}">${escapeHtml(text)}</span>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderDiffLine(line) {
|
|
110
|
+
const prefix = line.prefix || (line.kind === "added" ? "+" : line.kind === "removed" ? "-" : " ");
|
|
111
|
+
const gutter = `${prefix}${line.lineNumber || ""}`.trimEnd() || prefix || " ";
|
|
112
|
+
const text = line.text === "" ? " " : line.text;
|
|
113
|
+
|
|
114
|
+
return `
|
|
115
|
+
<div class="tool-diff-line ${line.kind || "context"}">
|
|
116
|
+
<span class="tool-diff-gutter mono">${escapeHtml(gutter)}</span>
|
|
117
|
+
<span class="tool-diff-code mono">${escapeHtml(text)}</span>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderDiffPreview(lines, { limit = 80 } = {}) {
|
|
123
|
+
if (!lines.length) {
|
|
124
|
+
return '<div class="tool-panel-note">No diff preview available.</div>';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const visible = lines.slice(0, limit);
|
|
128
|
+
const hiddenCount = Math.max(0, lines.length - visible.length);
|
|
129
|
+
|
|
130
|
+
return `
|
|
131
|
+
<div class="tool-diff-block">
|
|
132
|
+
${visible.map(renderDiffLine).join("")}
|
|
133
|
+
</div>
|
|
134
|
+
${hiddenCount > 0 ? `<div class="tool-preview-truncated">… ${hiddenCount} more diff lines</div>` : ""}
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function splitToolNotice(text = "") {
|
|
139
|
+
const lines = normalizeNewlines(text).split("\n");
|
|
140
|
+
while (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
141
|
+
|
|
142
|
+
const lastLine = lines[lines.length - 1] || "";
|
|
143
|
+
if (lastLine.startsWith("[") && /(Use offset=|limit reached|truncated|saved to temp file|full output)/i.test(lastLine)) {
|
|
144
|
+
lines.pop();
|
|
145
|
+
return { body: lines.join("\n"), notice: lastLine };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { body: lines.join("\n"), notice: "" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderCodePreview(text = "", { limit = 24, startLine = 1, emptyLabel = "Empty file." } = {}) {
|
|
152
|
+
const { body, notice } = splitToolNotice(text);
|
|
153
|
+
const allLines = normalizeNewlines(body).split("\n");
|
|
154
|
+
if (allLines.length > 1 && allLines[allLines.length - 1] === "") {
|
|
155
|
+
allLines.pop();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!allLines.length) {
|
|
159
|
+
return `${notice ? `<div class="tool-panel-note">${escapeHtml(notice)}</div>` : ""}<div class="tool-panel-note">${escapeHtml(emptyLabel)}</div>`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const visible = allLines.slice(0, limit);
|
|
163
|
+
const hiddenCount = Math.max(0, allLines.length - visible.length);
|
|
164
|
+
|
|
165
|
+
return `
|
|
166
|
+
<div class="tool-code-block">
|
|
167
|
+
${visible.map((line, index) => `
|
|
168
|
+
<div class="tool-code-line">
|
|
169
|
+
<span class="tool-code-gutter mono">${escapeHtml(String(startLine + index))}</span>
|
|
170
|
+
<span class="tool-code-content mono">${escapeHtml(line === "" ? " " : line)}</span>
|
|
171
|
+
</div>
|
|
172
|
+
`).join("")}
|
|
173
|
+
</div>
|
|
174
|
+
${hiddenCount > 0 ? `<div class="tool-preview-truncated">… ${hiddenCount} more line${hiddenCount === 1 ? "" : "s"}</div>` : ""}
|
|
175
|
+
${notice ? `<div class="tool-panel-note">${escapeHtml(notice)}</div>` : ""}
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function renderMarkdownPreview(text = "", { limit = 80 } = {}) {
|
|
180
|
+
const { body, notice } = splitToolNotice(text);
|
|
181
|
+
const lines = normalizeNewlines(body).split("\n");
|
|
182
|
+
const visible = lines.slice(0, limit).join("\n").trim();
|
|
183
|
+
const hiddenCount = Math.max(0, lines.length - limit);
|
|
184
|
+
|
|
185
|
+
return `
|
|
186
|
+
<div class="tool-markdown-preview">
|
|
187
|
+
${renderMarkdownLite(visible || "(empty markdown file)")}
|
|
188
|
+
</div>
|
|
189
|
+
${hiddenCount > 0 ? `<div class="tool-preview-truncated">… ${hiddenCount} more markdown line${hiddenCount === 1 ? "" : "s"}</div>` : ""}
|
|
190
|
+
${notice ? `<div class="tool-panel-note">${escapeHtml(notice)}</div>` : ""}
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function renderTerminalPreview(text = "", { limit = 80 } = {}) {
|
|
195
|
+
const { body, notice } = splitToolNotice(text);
|
|
196
|
+
const lines = normalizeNewlines(body).split("\n");
|
|
197
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
198
|
+
|
|
199
|
+
const visible = lines.slice(0, limit);
|
|
200
|
+
const hiddenCount = Math.max(0, lines.length - visible.length);
|
|
201
|
+
const terminalText = visible.length ? visible.join("\n") : "(no output)";
|
|
202
|
+
|
|
203
|
+
return `
|
|
204
|
+
<pre class="tool-terminal-block mono">${escapeHtml(terminalText)}</pre>
|
|
205
|
+
${hiddenCount > 0 ? `<div class="tool-preview-truncated">… ${hiddenCount} more output line${hiddenCount === 1 ? "" : "s"}</div>` : ""}
|
|
206
|
+
${notice ? `<div class="tool-panel-note">${escapeHtml(notice)}</div>` : ""}
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function summarizeRange(startLine, lineCount) {
|
|
211
|
+
if (!lineCount) return "";
|
|
212
|
+
const endLine = startLine + lineCount - 1;
|
|
213
|
+
return startLine === endLine ? `L${startLine}` : `${startLine}-${endLine}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function isMarkdownPath(filePath = "") {
|
|
217
|
+
return /\.(md|markdown|mdx)$/i.test(filePath);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function firstImagePart(content) {
|
|
221
|
+
if (!Array.isArray(content)) return null;
|
|
222
|
+
return content.find((part) => part?.type === "image" && part?.data && part?.mimeType) || null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function renderImagePreview(content, alt = "Image preview") {
|
|
226
|
+
const image = firstImagePart(content);
|
|
227
|
+
if (!image) return "";
|
|
228
|
+
return `
|
|
229
|
+
<div class="tool-image-wrap">
|
|
230
|
+
<img class="tool-image-preview" src="data:${escapeAttribute(image.mimeType)};base64,${escapeAttribute(image.data)}" alt="${escapeAttribute(alt)}" loading="lazy" />
|
|
231
|
+
</div>
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function parseGrepMatches(text = "") {
|
|
236
|
+
const { body, notice } = splitToolNotice(text);
|
|
237
|
+
const lines = normalizeNewlines(body).split("\n").filter((line) => line.trim());
|
|
238
|
+
const entries = [];
|
|
239
|
+
|
|
240
|
+
for (const line of lines) {
|
|
241
|
+
let match = line.match(/^(.+?):(\d+):\s?(.*)$/);
|
|
242
|
+
if (match) {
|
|
243
|
+
entries.push({ path: match[1], lineNumber: Number(match[2]), text: match[3] || "", kind: "match" });
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
match = line.match(/^(.+?)-(\d+)-\s?(.*)$/);
|
|
248
|
+
if (match) {
|
|
249
|
+
entries.push({ path: match[1], lineNumber: Number(match[2]), text: match[3] || "", kind: "context" });
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const deduped = [];
|
|
255
|
+
let previousKey = "";
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
const key = `${entry.kind}:${entry.path}:${entry.lineNumber}:${entry.text}`;
|
|
258
|
+
if (key === previousKey) continue;
|
|
259
|
+
previousKey = key;
|
|
260
|
+
deduped.push(entry);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { entries: deduped, notice };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderGrepPreview(text = "", { limitFiles = 8, limitLinesPerFile = 10 } = {}) {
|
|
267
|
+
const { entries, notice } = parseGrepMatches(text);
|
|
268
|
+
if (!entries.length) {
|
|
269
|
+
return {
|
|
270
|
+
html: text ? `<pre class="tool-terminal-block mono">${escapeHtml(text)}</pre>` : '<div class="tool-panel-note">No matches.</div>',
|
|
271
|
+
matchCount: 0,
|
|
272
|
+
fileCount: 0,
|
|
273
|
+
notice,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const groups = new Map();
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
if (!groups.has(entry.path)) groups.set(entry.path, []);
|
|
280
|
+
groups.get(entry.path).push(entry);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const fileEntries = [...groups.entries()];
|
|
284
|
+
const hiddenFiles = Math.max(0, fileEntries.length - limitFiles);
|
|
285
|
+
const visibleFiles = fileEntries.slice(0, limitFiles);
|
|
286
|
+
|
|
287
|
+
const html = `
|
|
288
|
+
<div class="tool-match-groups">
|
|
289
|
+
${visibleFiles.map(([path, items]) => {
|
|
290
|
+
const visibleItems = items.slice(0, limitLinesPerFile);
|
|
291
|
+
const hiddenLines = Math.max(0, items.length - visibleItems.length);
|
|
292
|
+
return `
|
|
293
|
+
<section class="tool-match-group">
|
|
294
|
+
<div class="tool-match-group-header mono">${escapeHtml(path)}</div>
|
|
295
|
+
<div class="tool-match-group-lines">
|
|
296
|
+
${visibleItems.map((entry) => `
|
|
297
|
+
<div class="tool-match-line ${entry.kind}">
|
|
298
|
+
<span class="tool-match-gutter mono">${escapeHtml(String(entry.lineNumber))}</span>
|
|
299
|
+
<span class="tool-match-text mono">${escapeHtml(entry.text === "" ? " " : entry.text)}</span>
|
|
300
|
+
</div>
|
|
301
|
+
`).join("")}
|
|
302
|
+
</div>
|
|
303
|
+
${hiddenLines > 0 ? `<div class="tool-preview-truncated">… ${hiddenLines} more line${hiddenLines === 1 ? "" : "s"} in ${escapeHtml(path)}</div>` : ""}
|
|
304
|
+
</section>
|
|
305
|
+
`;
|
|
306
|
+
}).join("")}
|
|
307
|
+
</div>
|
|
308
|
+
${hiddenFiles > 0 ? `<div class="tool-preview-truncated">… ${hiddenFiles} more matching file${hiddenFiles === 1 ? "" : "s"}</div>` : ""}
|
|
309
|
+
${notice ? `<div class="tool-panel-note">${escapeHtml(notice)}</div>` : ""}
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
return { html, matchCount: entries.filter((entry) => entry.kind === "match").length, fileCount: fileEntries.length, notice };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function parseListEntries(text = "") {
|
|
316
|
+
const { body, notice } = splitToolNotice(text);
|
|
317
|
+
return {
|
|
318
|
+
entries: normalizeNewlines(body).split("\n").filter((line) => line.trim()),
|
|
319
|
+
notice,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function renderListPreview(entries, { limit = 40 } = {}) {
|
|
324
|
+
if (!entries.length) {
|
|
325
|
+
return '<div class="tool-panel-note">No results.</div>';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const visible = entries.slice(0, limit);
|
|
329
|
+
const hiddenCount = Math.max(0, entries.length - visible.length);
|
|
330
|
+
|
|
331
|
+
return `
|
|
332
|
+
<div class="tool-entry-list">
|
|
333
|
+
${visible.map((entry) => `
|
|
334
|
+
<div class="tool-entry-row ${entry.endsWith("/") ? "directory" : "file"}">
|
|
335
|
+
<span class="tool-entry-icon">${entry.endsWith("/") ? "📁" : "📄"}</span>
|
|
336
|
+
<span class="tool-entry-text mono">${escapeHtml(entry)}</span>
|
|
337
|
+
</div>
|
|
338
|
+
`).join("")}
|
|
339
|
+
</div>
|
|
340
|
+
${hiddenCount > 0 ? `<div class="tool-preview-truncated">… ${hiddenCount} more result${hiddenCount === 1 ? "" : "s"}</div>` : ""}
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function isToolPanelOpen(itemId, defaultOpen = false) {
|
|
345
|
+
if (state.toolPanelOpen.has(itemId)) {
|
|
346
|
+
return Boolean(state.toolPanelOpen.get(itemId));
|
|
347
|
+
}
|
|
348
|
+
return defaultOpen;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function renderToolPanel(item, {
|
|
352
|
+
variant,
|
|
353
|
+
eyebrow,
|
|
354
|
+
path,
|
|
355
|
+
badges = [],
|
|
356
|
+
note = "",
|
|
357
|
+
content = "",
|
|
358
|
+
defaultOpen = false,
|
|
359
|
+
}) {
|
|
360
|
+
const open = isToolPanelOpen(item.id, defaultOpen);
|
|
361
|
+
|
|
362
|
+
return `
|
|
363
|
+
<details class="tool-panel tool-panel-${escapeHtml(variant)}" data-tool-panel="${escapeHtml(item.id)}" ${open ? "open" : ""}>
|
|
364
|
+
<summary class="tool-panel-summary">
|
|
365
|
+
<div class="tool-panel-summary-copy">
|
|
366
|
+
<div class="tool-panel-eyebrow">${escapeHtml(eyebrow)}</div>
|
|
367
|
+
<div class="tool-panel-path mono">${escapeHtml(path)}</div>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="tool-panel-badges">${badges.join("")}</div>
|
|
370
|
+
</summary>
|
|
371
|
+
<div class="tool-panel-body">
|
|
372
|
+
${note ? `<div class="tool-panel-note">${escapeHtml(note)}</div>` : ""}
|
|
373
|
+
${content}
|
|
374
|
+
</div>
|
|
375
|
+
</details>
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function renderEditToolContent(item) {
|
|
380
|
+
const details = asRecord(item.details);
|
|
381
|
+
const path = getToolPath(item);
|
|
382
|
+
if (!path) return "";
|
|
383
|
+
|
|
384
|
+
const diffLines = typeof details?.diff === "string"
|
|
385
|
+
? parseNumberedDiffLines(details.diff)
|
|
386
|
+
: buildEditPreviewLines(item.args?.oldText || "", item.args?.newText || "");
|
|
387
|
+
|
|
388
|
+
if (!diffLines.length) return "";
|
|
389
|
+
|
|
390
|
+
const stats = computeDiffStats(diffLines);
|
|
391
|
+
const badges = [];
|
|
392
|
+
if (stats.added) badges.push(renderToolBadge(`+${stats.added}`, "added"));
|
|
393
|
+
if (stats.removed) badges.push(renderToolBadge(`-${stats.removed}`, "removed"));
|
|
394
|
+
if (!stats.added && !stats.removed) badges.push(renderToolBadge(item.live ? "editing" : "updated"));
|
|
395
|
+
if (typeof details?.firstChangedLine === "number") badges.push(renderToolBadge(`L${details.firstChangedLine}`, "neutral"));
|
|
396
|
+
|
|
397
|
+
return renderToolPanel(item, {
|
|
398
|
+
variant: "edit",
|
|
399
|
+
eyebrow: item.live ? "Editing file" : "Edit diff",
|
|
400
|
+
path,
|
|
401
|
+
badges,
|
|
402
|
+
note: typeof details?.diff === "string" ? "" : "Preview from the requested replacement block.",
|
|
403
|
+
content: renderDiffPreview(diffLines, { limit: item.live ? 120 : 80 }),
|
|
404
|
+
defaultOpen: true,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function renderWriteToolContent(item) {
|
|
409
|
+
const path = getToolPath(item);
|
|
410
|
+
const content = typeof item.args?.content === "string" ? item.args.content : "";
|
|
411
|
+
if (!path || !content) return "";
|
|
412
|
+
|
|
413
|
+
const lineCount = countTextLines(content);
|
|
414
|
+
const byteCount = typeof TextEncoder === "function" ? new TextEncoder().encode(content).length : content.length;
|
|
415
|
+
const languageLabel = detectLanguageLabel(path);
|
|
416
|
+
|
|
417
|
+
const badges = [];
|
|
418
|
+
if (lineCount) badges.push(renderToolBadge(`${lineCount} line${lineCount === 1 ? "" : "s"}`, "neutral"));
|
|
419
|
+
if (byteCount) badges.push(renderToolBadge(formatBytes(byteCount), "neutral"));
|
|
420
|
+
if (languageLabel) badges.push(renderToolBadge(languageLabel, "accent"));
|
|
421
|
+
|
|
422
|
+
return renderToolPanel(item, {
|
|
423
|
+
variant: "write",
|
|
424
|
+
eyebrow: item.live ? "Writing file" : "File preview",
|
|
425
|
+
path,
|
|
426
|
+
badges,
|
|
427
|
+
note: "Preview from the content sent to write.",
|
|
428
|
+
content: renderCodePreview(content, { limit: item.live ? 30 : 24 }),
|
|
429
|
+
defaultOpen: item.live || lineCount <= 14,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function renderReadToolContent(item) {
|
|
434
|
+
const path = getToolPath(item);
|
|
435
|
+
const text = item.text || "";
|
|
436
|
+
const rawContent = item.rawContent;
|
|
437
|
+
if (!path) return "";
|
|
438
|
+
|
|
439
|
+
const imageHtml = renderImagePreview(rawContent, path);
|
|
440
|
+
const languageLabel = detectLanguageLabel(path);
|
|
441
|
+
const startLine = Number.isFinite(Number(item.args?.offset)) && Number(item.args?.offset) > 0 ? Number(item.args.offset) : 1;
|
|
442
|
+
const { body } = splitToolNotice(text);
|
|
443
|
+
const visibleLineCount = countTextLines(body);
|
|
444
|
+
const rangeLabel = summarizeRange(startLine, visibleLineCount);
|
|
445
|
+
const badges = [];
|
|
446
|
+
if (rangeLabel) badges.push(renderToolBadge(rangeLabel, "neutral"));
|
|
447
|
+
if (languageLabel) badges.push(renderToolBadge(languageLabel, "accent"));
|
|
448
|
+
if (imageHtml) badges.push(renderToolBadge("image", "accent"));
|
|
449
|
+
|
|
450
|
+
const content = imageHtml
|
|
451
|
+
|| (isMarkdownPath(path)
|
|
452
|
+
? renderMarkdownPreview(text, { limit: 90 })
|
|
453
|
+
: renderCodePreview(text, {
|
|
454
|
+
limit: item.live ? 60 : 32,
|
|
455
|
+
startLine,
|
|
456
|
+
emptyLabel: "No readable text returned.",
|
|
457
|
+
}));
|
|
458
|
+
|
|
459
|
+
return renderToolPanel(item, {
|
|
460
|
+
variant: "read",
|
|
461
|
+
eyebrow: item.live ? "Reading file" : "Read result",
|
|
462
|
+
path,
|
|
463
|
+
badges,
|
|
464
|
+
content,
|
|
465
|
+
defaultOpen: Boolean(imageHtml) || isMarkdownPath(path) || visibleLineCount <= 14,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function renderBashToolContent(item) {
|
|
470
|
+
const details = asRecord(item.details);
|
|
471
|
+
const command = String(item.command || item.args?.command || item.title?.replace(/^bash\s*·\s*/, "") || item.title || "bash").trim();
|
|
472
|
+
if (!command) return "";
|
|
473
|
+
|
|
474
|
+
const badges = [];
|
|
475
|
+
if (item.status === "running" || item.live) badges.push(renderToolBadge("running", "accent"));
|
|
476
|
+
else if (item.status === "error") badges.push(renderToolBadge("failed", "removed"));
|
|
477
|
+
else if (item.status === "cancelled") badges.push(renderToolBadge("cancelled", "neutral"));
|
|
478
|
+
else badges.push(renderToolBadge("done", "added"));
|
|
479
|
+
if (typeof item.args?.timeout === "number") badges.push(renderToolBadge(`${item.args.timeout}s timeout`, "neutral"));
|
|
480
|
+
if (details?.fullOutputPath) badges.push(renderToolBadge("full log saved", "neutral"));
|
|
481
|
+
|
|
482
|
+
const note = typeof details?.fullOutputPath === "string" ? `Full output: ${details.fullOutputPath}` : "";
|
|
483
|
+
|
|
484
|
+
return renderToolPanel(item, {
|
|
485
|
+
variant: "bash",
|
|
486
|
+
eyebrow: item.live ? "Shell command running" : "Shell command",
|
|
487
|
+
path: command,
|
|
488
|
+
badges,
|
|
489
|
+
note,
|
|
490
|
+
content: renderTerminalPreview(item.text || "", { limit: item.live ? 140 : 100 }),
|
|
491
|
+
defaultOpen: true,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function renderGrepToolContent(item) {
|
|
496
|
+
const pattern = typeof item.args?.pattern === "string" ? item.args.pattern : "";
|
|
497
|
+
const searchPath = typeof item.args?.path === "string" && item.args.path.trim() ? item.args.path : ".";
|
|
498
|
+
const preview = renderGrepPreview(item.text || "", { limitFiles: 10, limitLinesPerFile: 8 });
|
|
499
|
+
const badges = [];
|
|
500
|
+
if (pattern) badges.push(renderToolBadge(pattern.length > 28 ? `${pattern.slice(0, 27)}…` : pattern, "accent"));
|
|
501
|
+
if (preview.matchCount) badges.push(renderToolBadge(`${preview.matchCount} match${preview.matchCount === 1 ? "" : "es"}`, "neutral"));
|
|
502
|
+
if (preview.fileCount) badges.push(renderToolBadge(`${preview.fileCount} file${preview.fileCount === 1 ? "" : "s"}`, "neutral"));
|
|
503
|
+
|
|
504
|
+
return renderToolPanel(item, {
|
|
505
|
+
variant: "grep",
|
|
506
|
+
eyebrow: item.live ? "Searching files" : "Search results",
|
|
507
|
+
path: searchPath,
|
|
508
|
+
badges,
|
|
509
|
+
content: preview.html,
|
|
510
|
+
defaultOpen: preview.matchCount > 0,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function renderFindToolContent(item) {
|
|
515
|
+
const { entries, notice } = parseListEntries(item.text || "");
|
|
516
|
+
const searchPath = typeof item.args?.path === "string" && item.args.path.trim() ? item.args.path : ".";
|
|
517
|
+
const badges = [];
|
|
518
|
+
if (typeof item.args?.pattern === "string") badges.push(renderToolBadge(item.args.pattern, "accent"));
|
|
519
|
+
if (entries.length) badges.push(renderToolBadge(`${entries.length} result${entries.length === 1 ? "" : "s"}`, "neutral"));
|
|
520
|
+
|
|
521
|
+
return renderToolPanel(item, {
|
|
522
|
+
variant: "find",
|
|
523
|
+
eyebrow: item.live ? "Finding paths" : "Find results",
|
|
524
|
+
path: searchPath,
|
|
525
|
+
badges,
|
|
526
|
+
note: notice,
|
|
527
|
+
content: renderListPreview(entries, { limit: 60 }),
|
|
528
|
+
defaultOpen: entries.length <= 16,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function renderLsToolContent(item) {
|
|
533
|
+
const { entries, notice } = parseListEntries(item.text || "");
|
|
534
|
+
const listPath = typeof item.args?.path === "string" && item.args.path.trim() ? item.args.path : ".";
|
|
535
|
+
const badges = [];
|
|
536
|
+
if (entries.length) badges.push(renderToolBadge(`${entries.length} entr${entries.length === 1 ? "y" : "ies"}`, "neutral"));
|
|
537
|
+
const dirCount = entries.filter((entry) => entry.endsWith("/")).length;
|
|
538
|
+
if (dirCount) badges.push(renderToolBadge(`${dirCount} dir${dirCount === 1 ? "" : "s"}`, "accent"));
|
|
539
|
+
|
|
540
|
+
return renderToolPanel(item, {
|
|
541
|
+
variant: "ls",
|
|
542
|
+
eyebrow: item.live ? "Listing directory" : "Directory listing",
|
|
543
|
+
path: listPath,
|
|
544
|
+
badges,
|
|
545
|
+
note: notice,
|
|
546
|
+
content: renderListPreview(entries, { limit: 80 }),
|
|
547
|
+
defaultOpen: entries.length <= 20,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function renderRichToolContent(item) {
|
|
552
|
+
const toolName = normalizedToolName(item.toolName || item.title || "");
|
|
553
|
+
|
|
554
|
+
if (toolName === "edit") return renderEditToolContent(item);
|
|
555
|
+
if (toolName === "write") return renderWriteToolContent(item);
|
|
556
|
+
if (toolName === "read") return renderReadToolContent(item);
|
|
557
|
+
if (toolName === "bash") return renderBashToolContent(item);
|
|
558
|
+
if (toolName === "grep") return renderGrepToolContent(item);
|
|
559
|
+
if (toolName === "find") return renderFindToolContent(item);
|
|
560
|
+
if (toolName === "ls") return renderLsToolContent(item);
|
|
561
|
+
return "";
|
|
562
|
+
}
|