@duckmind/dm-darwin-arm64 0.13.6 → 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/extensions/.dm-extensions.json +25 -1
- 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/package.json +1 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { el, state } from "./state.js";
|
|
2
|
+
import {
|
|
3
|
+
assistantParts,
|
|
4
|
+
contentToText,
|
|
5
|
+
countImages,
|
|
6
|
+
escapeAttribute,
|
|
7
|
+
escapeHtml,
|
|
8
|
+
formatTimestamp,
|
|
9
|
+
toDetailString,
|
|
10
|
+
stripTerminalControlSequences,
|
|
11
|
+
} from "./formatters.js";
|
|
12
|
+
import { renderMarkdownLite } from "./markdown.js";
|
|
13
|
+
import { renderRichToolContent } from "./tool-rendering.js";
|
|
14
|
+
import { scrollMessagesToBottom, showToast, updateJumpToLatestButton } from "./ui.js";
|
|
15
|
+
|
|
16
|
+
const INLINE_USER_CUSTOM_TYPES = new Set(["phone-inline-user-message"]);
|
|
17
|
+
|
|
18
|
+
function userContentDisplayText(content) {
|
|
19
|
+
const imageCount = countImages(content);
|
|
20
|
+
if (!Array.isArray(content)) return contentToText(content);
|
|
21
|
+
|
|
22
|
+
const text = content
|
|
23
|
+
.filter((part) => part?.type === "text")
|
|
24
|
+
.map((part) => part.text || "")
|
|
25
|
+
.join("")
|
|
26
|
+
.trim();
|
|
27
|
+
|
|
28
|
+
if (text) return text;
|
|
29
|
+
if (imageCount === 1) return "[1 image attached]";
|
|
30
|
+
if (imageCount > 1) return `[${imageCount} images attached]`;
|
|
31
|
+
return contentToText(content);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function imageSource(part) {
|
|
35
|
+
if (!part || part.type !== "image") return "";
|
|
36
|
+
if (typeof part.previewUrl === "string" && part.previewUrl) return part.previewUrl;
|
|
37
|
+
if (typeof part.url === "string" && part.url) return part.url;
|
|
38
|
+
if (typeof part.data === "string" && part.data && typeof part.mimeType === "string" && part.mimeType) {
|
|
39
|
+
return `data:${escapeAttribute(part.mimeType)};base64,${escapeAttribute(part.data)}`;
|
|
40
|
+
}
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderUserContent(content, fallbackText = "") {
|
|
45
|
+
if (!Array.isArray(content)) {
|
|
46
|
+
return {
|
|
47
|
+
html: renderMarkdownLite(typeof content === "string" ? content : fallbackText),
|
|
48
|
+
renderedImages: 0,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const blocks = [];
|
|
53
|
+
let imageIndex = 0;
|
|
54
|
+
|
|
55
|
+
for (const part of content) {
|
|
56
|
+
if (part?.type === "text") {
|
|
57
|
+
const text = String(part.text || "");
|
|
58
|
+
if (text.trim()) {
|
|
59
|
+
blocks.push(`<div class="user-message-text-block">${renderMarkdownLite(text)}</div>`);
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (part?.type !== "image") continue;
|
|
65
|
+
const src = imageSource(part);
|
|
66
|
+
if (!src) continue;
|
|
67
|
+
imageIndex += 1;
|
|
68
|
+
const alt = part.name || `Attached image ${imageIndex}`;
|
|
69
|
+
blocks.push(`
|
|
70
|
+
<div class="user-message-image-wrap">
|
|
71
|
+
<img class="user-message-image" src="${src}" alt="${escapeAttribute(alt)}" loading="lazy" />
|
|
72
|
+
</div>
|
|
73
|
+
`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!blocks.length) {
|
|
77
|
+
return {
|
|
78
|
+
html: renderMarkdownLite(fallbackText),
|
|
79
|
+
renderedImages: 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
html: `<div class="user-message-inline-content">${blocks.join("")}</div>`,
|
|
85
|
+
renderedImages: imageIndex,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function transformMessage(message, index) {
|
|
90
|
+
if (!message || typeof message !== "object") return [];
|
|
91
|
+
|
|
92
|
+
if (message.role === "user") {
|
|
93
|
+
return [{
|
|
94
|
+
id: `user-${message.timestamp || index}`,
|
|
95
|
+
kind: "user",
|
|
96
|
+
meta: formatTimestamp(message.timestamp),
|
|
97
|
+
text: userContentDisplayText(message.content),
|
|
98
|
+
rawContent: message.content,
|
|
99
|
+
imageCount: countImages(message.content),
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (message.role === "assistant") {
|
|
104
|
+
const parts = assistantParts(message.content);
|
|
105
|
+
return [{
|
|
106
|
+
id: `assistant-${message.timestamp || index}`,
|
|
107
|
+
kind: "assistant",
|
|
108
|
+
meta: [message.model, formatTimestamp(message.timestamp)].filter(Boolean).join(" · "),
|
|
109
|
+
text: parts.text,
|
|
110
|
+
thinking: parts.thinking,
|
|
111
|
+
toolCalls: parts.toolCalls,
|
|
112
|
+
details: message.usage || message.stopReason ? {
|
|
113
|
+
usage: message.usage,
|
|
114
|
+
stopReason: message.stopReason,
|
|
115
|
+
} : undefined,
|
|
116
|
+
}];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (message.role === "toolResult") {
|
|
120
|
+
return [{
|
|
121
|
+
id: `tool-${message.toolCallId || message.timestamp || index}`,
|
|
122
|
+
kind: "tool",
|
|
123
|
+
toolCallId: message.toolCallId,
|
|
124
|
+
toolName: message.toolName || "tool",
|
|
125
|
+
title: message.toolName || "tool",
|
|
126
|
+
status: message.isError ? "error" : "done",
|
|
127
|
+
text: contentToText(message.content),
|
|
128
|
+
rawContent: message.content,
|
|
129
|
+
meta: formatTimestamp(message.timestamp),
|
|
130
|
+
details: message.details,
|
|
131
|
+
}];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (message.role === "bashExecution") {
|
|
135
|
+
return [{
|
|
136
|
+
id: `bash-${message.timestamp || index}`,
|
|
137
|
+
kind: "tool",
|
|
138
|
+
toolName: "bash",
|
|
139
|
+
title: `bash · ${message.command || ""}`,
|
|
140
|
+
command: message.command || "",
|
|
141
|
+
args: { command: message.command || "" },
|
|
142
|
+
status: message.cancelled ? "cancelled" : "done",
|
|
143
|
+
text: message.output || "",
|
|
144
|
+
meta: formatTimestamp(message.timestamp),
|
|
145
|
+
details: {
|
|
146
|
+
exitCode: message.exitCode,
|
|
147
|
+
truncated: message.truncated,
|
|
148
|
+
fullOutputPath: message.fullOutputPath,
|
|
149
|
+
},
|
|
150
|
+
}];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (message.role === "custom") {
|
|
154
|
+
if (message.display === false) return [];
|
|
155
|
+
|
|
156
|
+
if (INLINE_USER_CUSTOM_TYPES.has(message.customType || "")) {
|
|
157
|
+
return [{
|
|
158
|
+
id: `custom-user-${message.timestamp || index}`,
|
|
159
|
+
kind: "user",
|
|
160
|
+
meta: formatTimestamp(message.timestamp),
|
|
161
|
+
text: userContentDisplayText(message.content),
|
|
162
|
+
rawContent: message.content,
|
|
163
|
+
imageCount: countImages(message.content),
|
|
164
|
+
}];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return [{
|
|
168
|
+
id: `custom-${message.timestamp || index}`,
|
|
169
|
+
kind: "custom",
|
|
170
|
+
title: message.customType || "extension",
|
|
171
|
+
text: contentToText(message.content),
|
|
172
|
+
meta: formatTimestamp(message.timestamp),
|
|
173
|
+
details: message.details,
|
|
174
|
+
imageCount: countImages(message.content),
|
|
175
|
+
}];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (message.role === "branchSummary") {
|
|
179
|
+
return [{
|
|
180
|
+
id: `branch-summary-${message.timestamp || index}`,
|
|
181
|
+
kind: "summary",
|
|
182
|
+
title: "Branch summary",
|
|
183
|
+
text: message.summary || "",
|
|
184
|
+
meta: formatTimestamp(message.timestamp),
|
|
185
|
+
details: { fromId: message.fromId },
|
|
186
|
+
}];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (message.role === "compactionSummary") {
|
|
190
|
+
return [{
|
|
191
|
+
id: `compaction-summary-${message.timestamp || index}`,
|
|
192
|
+
kind: "summary",
|
|
193
|
+
title: `Compaction summary${message.tokensBefore ? ` · ${message.tokensBefore.toLocaleString()} tokens` : ""}`,
|
|
194
|
+
text: message.summary || "",
|
|
195
|
+
meta: formatTimestamp(message.timestamp),
|
|
196
|
+
}];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function renderMessageMeta(item, options = {}) {
|
|
203
|
+
const pills = [];
|
|
204
|
+
if (item.imageCount && !options.suppressImageCount) {
|
|
205
|
+
pills.push(`<span class="inline-pill">${item.imageCount} image${item.imageCount === 1 ? "" : "s"}</span>`);
|
|
206
|
+
}
|
|
207
|
+
if (item.status) pills.push(`<span class="inline-pill">${escapeHtml(item.status)}</span>`);
|
|
208
|
+
return pills.join("");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function renderDetailSection(title, value, options = {}) {
|
|
212
|
+
if (!value) return "";
|
|
213
|
+
|
|
214
|
+
const body = options.markdown
|
|
215
|
+
? `<div class="detail-content detail-markdown">${renderMarkdownLite(value)}</div>`
|
|
216
|
+
: `<pre class="detail-pre">${escapeHtml(value)}</pre>`;
|
|
217
|
+
|
|
218
|
+
return `
|
|
219
|
+
<details>
|
|
220
|
+
<summary>${escapeHtml(title)}</summary>
|
|
221
|
+
${body}
|
|
222
|
+
</details>
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderAssistantDetails(item) {
|
|
227
|
+
const sections = [];
|
|
228
|
+
if (item.thinking) sections.push(renderDetailSection("Thinking", item.thinking, { markdown: true }));
|
|
229
|
+
|
|
230
|
+
if (item.toolCalls?.length) {
|
|
231
|
+
sections.push(renderDetailSection("Tool calls", JSON.stringify(item.toolCalls, null, 2)));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (item.details) {
|
|
235
|
+
sections.push(renderDetailSection("Details", toDetailString(item.details)));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return sections.join("");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function toolDetailsForSecondarySection(item) {
|
|
242
|
+
if (!item.details) return null;
|
|
243
|
+
|
|
244
|
+
if (String(item.toolName || item.title || "").trim().split(" · ")[0].toLowerCase() !== "edit") {
|
|
245
|
+
return item.details;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const details = item.details && typeof item.details === "object" && !Array.isArray(item.details) ? item.details : null;
|
|
249
|
+
if (!details) return item.details;
|
|
250
|
+
|
|
251
|
+
const { diff, firstChangedLine, ...rest } = details;
|
|
252
|
+
return Object.keys(rest).length ? rest : null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderMessage(item) {
|
|
256
|
+
const richTool = item.kind === "tool" ? renderRichToolContent(item) : "";
|
|
257
|
+
const roleLabel = {
|
|
258
|
+
assistant: "DM",
|
|
259
|
+
custom: item.title || "Extension",
|
|
260
|
+
summary: item.title || "Summary",
|
|
261
|
+
system: "System",
|
|
262
|
+
tool: richTool ? "Tool" : item.title || "Tool",
|
|
263
|
+
user: "You",
|
|
264
|
+
}[item.kind] || "Message";
|
|
265
|
+
|
|
266
|
+
const renderedUser = item.kind === "user"
|
|
267
|
+
? renderUserContent(item.rawContent, item.text || "")
|
|
268
|
+
: { html: "", renderedImages: 0 };
|
|
269
|
+
|
|
270
|
+
const bodyMain = richTool || (item.kind === "tool"
|
|
271
|
+
? `<pre>${escapeHtml(item.text || "")}</pre>`
|
|
272
|
+
: item.kind === "user"
|
|
273
|
+
? renderedUser.html
|
|
274
|
+
: renderMarkdownLite(item.text || ""));
|
|
275
|
+
|
|
276
|
+
const detailValue = item.kind === "tool" ? toolDetailsForSecondarySection(item) : item.details;
|
|
277
|
+
const extraDetails = item.kind === "assistant"
|
|
278
|
+
? renderAssistantDetails(item)
|
|
279
|
+
: detailValue
|
|
280
|
+
? renderDetailSection("Details", toDetailString(detailValue))
|
|
281
|
+
: "";
|
|
282
|
+
|
|
283
|
+
return `
|
|
284
|
+
<article class="message ${item.kind}">
|
|
285
|
+
<div class="message-header">
|
|
286
|
+
<div class="role-badge">${escapeHtml(roleLabel)}${item.live ? " · live" : ""}</div>
|
|
287
|
+
<div class="meta">${escapeHtml(item.meta || "")}</div>
|
|
288
|
+
</div>
|
|
289
|
+
<div class="message-body">
|
|
290
|
+
${bodyMain}
|
|
291
|
+
${richTool ? "" : renderMessageMeta(item, { suppressImageCount: renderedUser.renderedImages > 0 })}
|
|
292
|
+
${extraDetails}
|
|
293
|
+
</div>
|
|
294
|
+
</article>
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function enrichToolItems(items) {
|
|
299
|
+
const toolCalls = new Map();
|
|
300
|
+
|
|
301
|
+
for (const item of items) {
|
|
302
|
+
if (item.kind !== "assistant" || !Array.isArray(item.toolCalls)) continue;
|
|
303
|
+
for (const toolCall of item.toolCalls) {
|
|
304
|
+
if (!toolCall?.id) continue;
|
|
305
|
+
toolCalls.set(toolCall.id, toolCall);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return items.map((item) => {
|
|
310
|
+
if (item.kind !== "tool" || item.args || !item.toolCallId) return item;
|
|
311
|
+
const linked = toolCalls.get(item.toolCallId);
|
|
312
|
+
if (!linked) return item;
|
|
313
|
+
return { ...item, args: linked.arguments || {} };
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function currentItems() {
|
|
318
|
+
const items = [...state.messages];
|
|
319
|
+
for (const tool of state.liveTools.values()) items.push(tool);
|
|
320
|
+
if (state.liveAssistant) items.push(state.liveAssistant);
|
|
321
|
+
return enrichToolItems(items);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function hasLiveItems() {
|
|
325
|
+
if (state.liveAssistant?.live) return true;
|
|
326
|
+
for (const tool of state.liveTools.values()) {
|
|
327
|
+
if (tool?.live) return true;
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function clearTransientState() {
|
|
333
|
+
state.liveAssistant = null;
|
|
334
|
+
state.liveTools.clear();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function clearSnapshotView() {
|
|
338
|
+
state.snapshotState = null;
|
|
339
|
+
state.snapshotWorkerId = null;
|
|
340
|
+
state.messages = [];
|
|
341
|
+
clearTransientState();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function handleAssistantEvent(event) {
|
|
345
|
+
if (!event) return;
|
|
346
|
+
if (!state.liveAssistant) {
|
|
347
|
+
state.liveAssistant = {
|
|
348
|
+
id: "assistant-live",
|
|
349
|
+
kind: "assistant",
|
|
350
|
+
live: true,
|
|
351
|
+
text: "",
|
|
352
|
+
thinking: "",
|
|
353
|
+
toolCalls: [],
|
|
354
|
+
meta: "Streaming…",
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (event.type === "text_delta") state.liveAssistant.text += stripTerminalControlSequences(event.delta || "");
|
|
359
|
+
if (event.type === "thinking_delta") state.liveAssistant.thinking += stripTerminalControlSequences(event.delta || "");
|
|
360
|
+
if (event.type === "toolcall_end" && event.toolCall) {
|
|
361
|
+
state.liveAssistant.toolCalls.push({ id: event.toolCall.id || "", name: event.toolCall.name || "tool", arguments: event.toolCall.arguments || {} });
|
|
362
|
+
}
|
|
363
|
+
if (event.type === "error") showToast(event.message || "Agent error", "error");
|
|
364
|
+
renderMessages();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function upsertLiveTool(toolId, value) {
|
|
368
|
+
state.liveTools.set(toolId, value);
|
|
369
|
+
renderMessages();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function renderMessages({ forceScroll = false, streaming = hasLiveItems() } = {}) {
|
|
373
|
+
const items = currentItems();
|
|
374
|
+
if (!items.length) {
|
|
375
|
+
el.messages.innerHTML = `
|
|
376
|
+
<article class="message system">
|
|
377
|
+
<div class="message-header"><div class="role-badge">Ready</div></div>
|
|
378
|
+
<div class="message-body">
|
|
379
|
+
<p>This phone UI now exposes much more of DM: commands, models, thinking, sessions, tree history, custom extension messages, and image upload.</p>
|
|
380
|
+
</div>
|
|
381
|
+
</article>
|
|
382
|
+
`;
|
|
383
|
+
updateJumpToLatestButton();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
el.messages.innerHTML = items.map(renderMessage).join("");
|
|
388
|
+
updateJumpToLatestButton();
|
|
389
|
+
scrollMessagesToBottom({ force: forceScroll, streaming, behavior: "smooth" });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function renderWidgets() {
|
|
393
|
+
const widgets = [...state.widgets.entries()];
|
|
394
|
+
if (!widgets.length && !state.footerStatus) {
|
|
395
|
+
el.widgetStack.classList.add("hidden");
|
|
396
|
+
el.widgetStack.innerHTML = "";
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const cards = widgets.map(([key, lines]) => `
|
|
401
|
+
<article class="widget-card">
|
|
402
|
+
<h3>${escapeHtml(key)}</h3>
|
|
403
|
+
<ul>${lines.map((line) => `<li>${escapeHtml(line)}</li>`).join("")}</ul>
|
|
404
|
+
</article>
|
|
405
|
+
`);
|
|
406
|
+
|
|
407
|
+
if (state.footerStatus) {
|
|
408
|
+
cards.unshift(`
|
|
409
|
+
<article class="widget-card">
|
|
410
|
+
<h3>Extension status</h3>
|
|
411
|
+
<div>${escapeHtml(state.footerStatus)}</div>
|
|
412
|
+
</article>
|
|
413
|
+
`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
el.widgetStack.innerHTML = cards.join("");
|
|
417
|
+
el.widgetStack.classList.remove("hidden");
|
|
418
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { renderCommandSuggestions } from "./autocomplete-controller.js";
|
|
2
|
+
import {
|
|
3
|
+
handleInsertOnlyLocalCommand,
|
|
4
|
+
insertSlashCommand,
|
|
5
|
+
prepareParentSessionNew,
|
|
6
|
+
prepareSessionSelection,
|
|
7
|
+
prepareSessionSpawn,
|
|
8
|
+
tryHandleLocalCommand,
|
|
9
|
+
} from "./commands.js";
|
|
10
|
+
import { el, state } from "./state.js";
|
|
11
|
+
import { closeSheet, openSheet } from "./sheet-navigation.js";
|
|
12
|
+
import { refreshAll, sendRpc } from "./transport.js";
|
|
13
|
+
import { autoResizeTextarea } from "./ui.js";
|
|
14
|
+
|
|
15
|
+
export function sheetButtonActionKey(button) {
|
|
16
|
+
return [
|
|
17
|
+
button.getAttribute("data-sheet-action") || "",
|
|
18
|
+
button.getAttribute("data-active-session-id") || "",
|
|
19
|
+
button.getAttribute("data-session-path") || "",
|
|
20
|
+
button.getAttribute("data-open-branch-entry") || "",
|
|
21
|
+
button.getAttribute("data-fork-entry") || "",
|
|
22
|
+
button.getAttribute("data-run-command") || "",
|
|
23
|
+
button.getAttribute("data-run-local-command") || "",
|
|
24
|
+
].join("|");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function handleSheetButtonAction(button) {
|
|
28
|
+
const action = button.getAttribute("data-sheet-action");
|
|
29
|
+
if (action === "refresh") return refreshSheet(), true;
|
|
30
|
+
if (action === "new-session") return sendRpc({ type: "new_session" }), true;
|
|
31
|
+
if (action === "compact") return sendRpc({ type: "compact" }), true;
|
|
32
|
+
if (action === "stats") return sendRpc({ type: "get_session_stats" }), true;
|
|
33
|
+
if (action === "models") return openSheet("models"), true;
|
|
34
|
+
if (action === "thinking") return openSheet("thinking"), true;
|
|
35
|
+
if (action === "commands") return openSheet("commands"), true;
|
|
36
|
+
if (action === "sessions") return openSheet("sessions"), true;
|
|
37
|
+
if (action === "new-parent-session") return handleNewParentSession(), true;
|
|
38
|
+
if (action === "new-parallel-session") return handleSpawnActiveSession(), true;
|
|
39
|
+
if (action === "tree") return openSheet("tree"), true;
|
|
40
|
+
|
|
41
|
+
const thinkingLevel = button.getAttribute("data-thinking-level");
|
|
42
|
+
if (thinkingLevel) return sendRpc({ type: "set_thinking_level", level: thinkingLevel }), true;
|
|
43
|
+
|
|
44
|
+
const modelProvider = button.getAttribute("data-model-provider");
|
|
45
|
+
const modelId = button.getAttribute("data-model-id");
|
|
46
|
+
if (modelProvider && modelId) return sendRpc({ type: "set_model", provider: modelProvider, modelId }), true;
|
|
47
|
+
|
|
48
|
+
const runLocalCommand = button.getAttribute("data-run-local-command");
|
|
49
|
+
if (runLocalCommand) return handleRunLocalCommand(runLocalCommand), true;
|
|
50
|
+
|
|
51
|
+
const runCommand = button.getAttribute("data-run-command");
|
|
52
|
+
if (runCommand) return handleInsertRunCommand(runCommand), true;
|
|
53
|
+
|
|
54
|
+
const activeSessionId = button.getAttribute("data-active-session-id");
|
|
55
|
+
if (activeSessionId) return handleSelectActiveSession(activeSessionId), true;
|
|
56
|
+
|
|
57
|
+
const sessionPath = button.getAttribute("data-session-path");
|
|
58
|
+
if (sessionPath) return sendRpc({ type: "switch_session", sessionPath }), true;
|
|
59
|
+
|
|
60
|
+
const openBranchEntry = button.getAttribute("data-open-branch-entry");
|
|
61
|
+
if (openBranchEntry) return sendRpc({ type: "phone_open_branch_path", entryId: openBranchEntry }), true;
|
|
62
|
+
|
|
63
|
+
const forkEntry = button.getAttribute("data-fork-entry");
|
|
64
|
+
if (forkEntry) return sendRpc({ type: "fork", entryId: forkEntry }), true;
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function refreshSheet() {
|
|
70
|
+
refreshAll();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleNewParentSession() {
|
|
74
|
+
if (!prepareParentSessionNew()) return;
|
|
75
|
+
closeSheet();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleSpawnActiveSession() {
|
|
79
|
+
if (!prepareSessionSpawn()) return;
|
|
80
|
+
closeSheet();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleRunLocalCommand(runLocalCommand) {
|
|
84
|
+
if (handleInsertOnlyLocalCommand(runLocalCommand)) {
|
|
85
|
+
closeSheet();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = tryHandleLocalCommand(`/${runLocalCommand}`, { hasAttachments: state.attachments.length > 0 });
|
|
90
|
+
if (result === "handled") {
|
|
91
|
+
el.promptInput.value = "";
|
|
92
|
+
autoResizeTextarea();
|
|
93
|
+
renderCommandSuggestions();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleInsertRunCommand(runCommand) {
|
|
98
|
+
const commandName = runCommand.replace(/^\//, "").trim();
|
|
99
|
+
if (commandName) {
|
|
100
|
+
insertSlashCommand(commandName);
|
|
101
|
+
} else {
|
|
102
|
+
el.promptInput.value = `${runCommand} `;
|
|
103
|
+
autoResizeTextarea();
|
|
104
|
+
renderCommandSuggestions();
|
|
105
|
+
el.promptInput.focus();
|
|
106
|
+
}
|
|
107
|
+
closeSheet();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleSelectActiveSession(activeSessionId) {
|
|
111
|
+
if (!prepareSessionSelection(activeSessionId)) return showToast("Not connected to DM.", "error");
|
|
112
|
+
closeSheet();
|
|
113
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { renderSheet } from "./sheets-view.js";
|
|
2
|
+
import { el, state } from "./state.js";
|
|
3
|
+
import { sendRpc } from "./transport.js";
|
|
4
|
+
|
|
5
|
+
export function openSheet(mode = "actions") {
|
|
6
|
+
state.sheetMode = mode;
|
|
7
|
+
el.sheetModal.classList.remove("hidden");
|
|
8
|
+
renderSheet();
|
|
9
|
+
|
|
10
|
+
if (mode === "actions") sendRpc({ type: "get_session_stats" });
|
|
11
|
+
if (mode === "models") sendRpc({ type: "get_available_models" });
|
|
12
|
+
if (mode === "commands") sendRpc({ type: "get_commands" });
|
|
13
|
+
if (mode === "sessions") sendRpc({ type: "phone_list_sessions" });
|
|
14
|
+
if (mode === "tree") sendRpc({ type: "phone_get_tree" });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function closeSheet() {
|
|
18
|
+
el.sheetModal.classList.add("hidden");
|
|
19
|
+
}
|