@duckmind/dm-darwin-arm64 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.

Potentially problematic release.


This version of @duckmind/dm-darwin-arm64 might be problematic. Click here for more details.

Files changed (78) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +39 -15
  3. package/extensions/dm-multicodex/package-lock.json +302 -1814
  4. package/extensions/dm-phone/README.md +23 -0
  5. package/extensions/dm-phone/index.ts +12 -0
  6. package/extensions/dm-phone/node_modules/.package-lock.json +29 -0
  7. package/extensions/dm-phone/node_modules/ws/LICENSE +20 -0
  8. package/extensions/dm-phone/node_modules/ws/README.md +548 -0
  9. package/extensions/dm-phone/node_modules/ws/browser.js +8 -0
  10. package/extensions/dm-phone/node_modules/ws/index.js +22 -0
  11. package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +131 -0
  12. package/extensions/dm-phone/node_modules/ws/lib/constants.js +19 -0
  13. package/extensions/dm-phone/node_modules/ws/lib/event-target.js +292 -0
  14. package/extensions/dm-phone/node_modules/ws/lib/extension.js +203 -0
  15. package/extensions/dm-phone/node_modules/ws/lib/limiter.js +55 -0
  16. package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +528 -0
  17. package/extensions/dm-phone/node_modules/ws/lib/receiver.js +706 -0
  18. package/extensions/dm-phone/node_modules/ws/lib/sender.js +602 -0
  19. package/extensions/dm-phone/node_modules/ws/lib/stream.js +161 -0
  20. package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +62 -0
  21. package/extensions/dm-phone/node_modules/ws/lib/validation.js +152 -0
  22. package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +554 -0
  23. package/extensions/dm-phone/node_modules/ws/lib/websocket.js +1393 -0
  24. package/extensions/dm-phone/node_modules/ws/package.json +70 -0
  25. package/extensions/dm-phone/node_modules/ws/wrapper.mjs +21 -0
  26. package/extensions/dm-phone/package-lock.json +66 -0
  27. package/extensions/dm-phone/package.json +35 -0
  28. package/extensions/dm-phone/phone-session-pool.ts +8 -0
  29. package/extensions/dm-phone/public/app/attachments.js +233 -0
  30. package/extensions/dm-phone/public/app/autocomplete-controller.js +81 -0
  31. package/extensions/dm-phone/public/app/autocomplete.js +135 -0
  32. package/extensions/dm-phone/public/app/bindings.js +178 -0
  33. package/extensions/dm-phone/public/app/command-catalog.js +76 -0
  34. package/extensions/dm-phone/public/app/commands.js +370 -0
  35. package/extensions/dm-phone/public/app/constants.js +60 -0
  36. package/extensions/dm-phone/public/app/formatters.js +131 -0
  37. package/extensions/dm-phone/public/app/handlers.js +442 -0
  38. package/extensions/dm-phone/public/app/main.js +6 -0
  39. package/extensions/dm-phone/public/app/markdown.js +105 -0
  40. package/extensions/dm-phone/public/app/messages.js +418 -0
  41. package/extensions/dm-phone/public/app/sheet-actions.js +113 -0
  42. package/extensions/dm-phone/public/app/sheet-navigation.js +19 -0
  43. package/extensions/dm-phone/public/app/sheets-view.js +272 -0
  44. package/extensions/dm-phone/public/app/state.js +95 -0
  45. package/extensions/dm-phone/public/app/tool-rendering.js +562 -0
  46. package/extensions/dm-phone/public/app/transport.js +176 -0
  47. package/extensions/dm-phone/public/app/ui.js +409 -0
  48. package/extensions/dm-phone/public/app.js +1 -0
  49. package/extensions/dm-phone/public/icon.svg +15 -0
  50. package/extensions/dm-phone/public/index.html +147 -0
  51. package/extensions/dm-phone/public/manifest.webmanifest +17 -0
  52. package/extensions/dm-phone/public/styles.css +1139 -0
  53. package/extensions/dm-phone/public/sw.js +78 -0
  54. package/extensions/dm-phone/src/extension/phone-args.ts +121 -0
  55. package/extensions/dm-phone/src/extension/phone-paths.ts +250 -0
  56. package/extensions/dm-phone/src/extension/phone-quota.ts +188 -0
  57. package/extensions/dm-phone/src/extension/phone-runtime.ts +154 -0
  58. package/extensions/dm-phone/src/extension/phone-server-runtime.ts +1217 -0
  59. package/extensions/dm-phone/src/extension/phone-sessions.ts +139 -0
  60. package/extensions/dm-phone/src/extension/phone-static.ts +30 -0
  61. package/extensions/dm-phone/src/extension/phone-tailscale.ts +148 -0
  62. package/extensions/dm-phone/src/extension/phone-theme.ts +85 -0
  63. package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +112 -0
  64. package/extensions/dm-phone/src/extension/register-phone-extension.ts +106 -0
  65. package/extensions/dm-phone/src/extension/types.ts +73 -0
  66. package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +881 -0
  67. package/extensions/dm-phone/src/session-pool/session-pool.ts +470 -0
  68. package/extensions/dm-phone/src/session-pool/session-worker.ts +734 -0
  69. package/extensions/dm-phone/src/session-pool/types.ts +105 -0
  70. package/extensions/dm-phone/src/session-pool/utils.ts +23 -0
  71. package/extensions/dm-subagents/artifacts.ts +11 -5
  72. package/extensions/dm-subagents/async-execution.ts +4 -1
  73. package/extensions/dm-subagents/index.ts +1 -1
  74. package/extensions/dm-subagents/schemas.ts +1 -1
  75. package/extensions/dm-subagents/settings.ts +6 -4
  76. package/extensions/dm-subagents/subagent-runner.ts +167 -50
  77. package/extensions/dm-subagents/types.ts +62 -2
  78. package/package.json +1 -1
@@ -0,0 +1,131 @@
1
+ export function escapeHtml(text = "") {
2
+ return String(text)
3
+ .replaceAll("&", "&")
4
+ .replaceAll("<", "&lt;")
5
+ .replaceAll(">", "&gt;")
6
+ .replaceAll('"', "&quot;")
7
+ .replaceAll("'", "&#039;");
8
+ }
9
+
10
+ export function escapeAttribute(text = "") {
11
+ return escapeHtml(text).replaceAll("`", "&#096;");
12
+ }
13
+
14
+ export function formatCwdDisplay(path = "") {
15
+ const value = String(path || "").trim();
16
+ if (!value) return "";
17
+
18
+ const homeMatch = value.match(/^\/(?:home|Users)\/[^/]+(\/.*)?$/);
19
+ if (homeMatch) {
20
+ const suffix = homeMatch[1] || "";
21
+ const parts = suffix.split("/").filter(Boolean);
22
+ if (!parts.length) return "~";
23
+ if (parts.length === 1) return `~/${parts[0]}`;
24
+ return `~/…/${parts[parts.length - 1]}`;
25
+ }
26
+
27
+ if (value === "/") return value;
28
+
29
+ const parts = value.split("/").filter(Boolean);
30
+ if (parts.length <= 2) return value;
31
+ return `/…/${parts[parts.length - 1]}`;
32
+ }
33
+
34
+ export function formatTimestamp(timestamp) {
35
+ if (!timestamp) return "";
36
+ const date = new Date(timestamp);
37
+ return date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
38
+ }
39
+
40
+ export function formatDateTime(value) {
41
+ if (!value) return "";
42
+ const date = new Date(value);
43
+ if (Number.isNaN(date.getTime())) return "";
44
+ return date.toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
45
+ }
46
+
47
+ export function formatBytes(bytes) {
48
+ if (!Number.isFinite(bytes)) return "";
49
+ if (bytes < 1024) return `${bytes} B`;
50
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
51
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
52
+ }
53
+
54
+ export function formatTokenCount(count) {
55
+ if (!Number.isFinite(count) || count <= 0) return "";
56
+ if (count < 1000) return String(Math.round(count));
57
+ if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
58
+ if (count < 1000000) return `${Math.round(count / 1000)}k`;
59
+ if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
60
+ return `${Math.round(count / 1000000)}M`;
61
+ }
62
+
63
+ export function normalizeNewlines(text = "") {
64
+ return String(text ?? "").replace(/\r\n?/g, "\n");
65
+ }
66
+
67
+ export function stripTerminalControlSequences(text = "") {
68
+ return String(text ?? "")
69
+ .replace(/\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)/g, "")
70
+ .replace(/\u009D[^\u009C]*\u009C/g, "")
71
+ .replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "")
72
+ .replace(/\u009B[0-?]*[ -/]*[@-~]/g, "")
73
+ .replace(/\u001B[@-_]/g, "")
74
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, "");
75
+ }
76
+
77
+ export function countTextLines(text = "") {
78
+ const normalized = normalizeNewlines(text);
79
+ if (!normalized.length) return 0;
80
+ return normalized.split("\n").length;
81
+ }
82
+
83
+ export function asRecord(value) {
84
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
85
+ return value;
86
+ }
87
+
88
+ export function contentToText(content) {
89
+ if (typeof content === "string") return stripTerminalControlSequences(content);
90
+ if (!Array.isArray(content)) return "";
91
+ return stripTerminalControlSequences(content
92
+ .map((part) => {
93
+ if (part.type === "text") return part.text || "";
94
+ if (part.type === "image") return "[image]";
95
+ if (part.type === "thinking") return "";
96
+ if (part.type === "toolCall") return "";
97
+ return "";
98
+ })
99
+ .join(" ")
100
+ .trim());
101
+ }
102
+
103
+ export function countImages(content) {
104
+ if (!Array.isArray(content)) return 0;
105
+ return content.filter((part) => part.type === "image").length;
106
+ }
107
+
108
+ export function assistantParts(content) {
109
+ const parts = { text: "", thinking: "", toolCalls: [] };
110
+ if (!Array.isArray(content)) return parts;
111
+
112
+ for (const block of content) {
113
+ if (block.type === "text") parts.text += stripTerminalControlSequences(block.text || "");
114
+ if (block.type === "thinking") parts.thinking += stripTerminalControlSequences(block.thinking || "");
115
+ if (block.type === "toolCall") {
116
+ parts.toolCalls.push({ id: block.id || "", name: block.name || "tool", arguments: block.arguments || {} });
117
+ }
118
+ }
119
+
120
+ return parts;
121
+ }
122
+
123
+ export function toDetailString(details) {
124
+ if (details == null) return "";
125
+ if (typeof details === "string") return details;
126
+ try {
127
+ return JSON.stringify(details, null, 2);
128
+ } catch {
129
+ return String(details);
130
+ }
131
+ }
@@ -0,0 +1,442 @@
1
+ import { contentToText } from "./formatters.js";
2
+ import {
3
+ clearSnapshotView,
4
+ clearTransientState,
5
+ handleAssistantEvent,
6
+ renderMessages,
7
+ renderWidgets,
8
+ transformMessage,
9
+ upsertLiveTool,
10
+ } from "./messages.js";
11
+ import { closeSheet } from "./sheet-navigation.js";
12
+ import { renderSheet } from "./sheets-view.js";
13
+ import { state } from "./state.js";
14
+ import { refreshAll, refreshQuota, sendRpc } from "./transport.js";
15
+ import {
16
+ autoResizeTextarea,
17
+ clearUiModal,
18
+ openTokenModal,
19
+ openUiModalForRequest,
20
+ renderHeader,
21
+ resetToken,
22
+ showBanner,
23
+ showToast,
24
+ } from "./ui.js";
25
+ import { renderCommandSuggestions } from "./autocomplete-controller.js";
26
+ import { renderAutocompleteItems } from "./autocomplete.js";
27
+
28
+ export function handleAuthFailure() {
29
+ resetToken({ clearInput: true });
30
+ state.socket = null;
31
+ renderHeader();
32
+ openTokenModal();
33
+ showBanner("Access token required. Enter the current /phone-start token.", "error");
34
+ }
35
+
36
+ function sendUiResponse(payload) {
37
+ sendRpc({ type: "extension_ui_response", ...payload });
38
+ clearUiModal();
39
+ }
40
+
41
+ function handleExtensionUiRequest(request) {
42
+ if (request.method === "notify") {
43
+ showToast(request.message || "Notification");
44
+ return;
45
+ }
46
+
47
+ if (request.method === "setStatus") {
48
+ state.footerStatus = request.statusText || "";
49
+ renderWidgets();
50
+ return;
51
+ }
52
+
53
+ if (request.method === "setWidget") {
54
+ if (request.widgetLines?.length) state.widgets.set(request.widgetKey || "widget", request.widgetLines);
55
+ else state.widgets.delete(request.widgetKey || "widget");
56
+ renderWidgets();
57
+ return;
58
+ }
59
+
60
+ if (request.method === "setTitle") {
61
+ document.title = request.title || "DM Phone";
62
+ return;
63
+ }
64
+
65
+ if (request.method === "set_editor_text") {
66
+ const promptInput = document.querySelector("#prompt-input");
67
+ if (promptInput) promptInput.value = request.text || "";
68
+ autoResizeTextarea();
69
+ renderCommandSuggestions();
70
+ return;
71
+ }
72
+
73
+ if (!["select", "confirm", "input", "editor"].includes(request.method)) {
74
+ showToast(`Unsupported UI request: ${request.method || "unknown"}`);
75
+ return;
76
+ }
77
+
78
+ openUiModalForRequest(request, sendUiResponse);
79
+ }
80
+
81
+ function handleRpcPayload(payload) {
82
+ if (!payload) return;
83
+
84
+ if (payload.type === "response") {
85
+ if (!payload.success) {
86
+ if (payload.command === "path_suggestions") {
87
+ renderAutocompleteItems([]);
88
+ return;
89
+ }
90
+ showToast(payload.error || `Command failed: ${payload.command}`, "error");
91
+ return;
92
+ }
93
+
94
+ if (payload.command === "get_state") {
95
+ state.snapshotState = payload.data || state.snapshotState;
96
+ renderHeader();
97
+ void refreshQuota();
98
+ return;
99
+ }
100
+
101
+ if (payload.command === "get_messages") {
102
+ state.messages = (payload.data?.messages || []).flatMap(transformMessage);
103
+ clearTransientState();
104
+ renderMessages();
105
+ return;
106
+ }
107
+
108
+ if (payload.command === "get_commands") {
109
+ state.commands = payload.data?.commands || [];
110
+ renderCommandSuggestions();
111
+ renderSheet();
112
+ return;
113
+ }
114
+
115
+ if (payload.command === "path_suggestions") {
116
+ const context = state.autocompleteContext;
117
+ if (!context || context.type !== "path") return;
118
+ if (Number(payload.data?.requestId) !== state.autocompleteRemoteRequestId) return;
119
+ if (payload.data?.mode !== context.mode) return;
120
+ if ((payload.data?.query || "") !== context.query) return;
121
+
122
+ const suggestions = Array.isArray(payload.data?.suggestions) ? payload.data.suggestions : [];
123
+ renderAutocompleteItems(suggestions.map((suggestion) => ({
124
+ kind: "path",
125
+ label: context.mode === "mention" ? `@${suggestion.value}` : suggestion.value,
126
+ badge: suggestion.kind === "previous" ? "recent" : suggestion.isDirectory ? "dir" : "file",
127
+ description: suggestion.description || suggestion.value,
128
+ value: suggestion.value,
129
+ isDirectory: Boolean(suggestion.isDirectory),
130
+ title: suggestion.description || suggestion.value,
131
+ })));
132
+ return;
133
+ }
134
+
135
+ if (payload.command === "cd") {
136
+ showToast(`Changed directory to ${payload.data?.cwd || "the selected path"}.`);
137
+ refreshAll();
138
+ return;
139
+ }
140
+
141
+ if (payload.command === "get_available_models") {
142
+ state.models = payload.data?.models || [];
143
+ renderSheet();
144
+ return;
145
+ }
146
+
147
+ if (payload.command === "get_session_stats") {
148
+ state.stats = payload.data || null;
149
+ renderSheet();
150
+ return;
151
+ }
152
+
153
+ if (payload.command === "phone_list_sessions") {
154
+ state.sessions = payload.data?.sessions || [];
155
+ renderSheet();
156
+ return;
157
+ }
158
+
159
+ if (payload.command === "phone_get_tree") {
160
+ state.tree = payload.data || null;
161
+ renderSheet();
162
+ return;
163
+ }
164
+
165
+ if (payload.command === "new_parent_session") {
166
+ clearTransientState();
167
+ refreshAll();
168
+ showToast("Started a new parent session.");
169
+ return;
170
+ }
171
+
172
+ if (payload.command === "new_session") {
173
+ clearTransientState();
174
+ refreshAll();
175
+ showToast("Started a new DM session.");
176
+ return;
177
+ }
178
+
179
+ if (payload.command === "compact") {
180
+ showToast("Compaction triggered.");
181
+ refreshAll();
182
+ return;
183
+ }
184
+
185
+ if (payload.command === "slash_command") {
186
+ if (payload.data?.source === "extension") {
187
+ refreshAll({ forceQuota: true });
188
+ }
189
+ return;
190
+ }
191
+
192
+ if (payload.command === "reload") {
193
+ clearTransientState();
194
+ showToast("Reloaded extensions, skills, prompts, and themes.");
195
+ refreshAll({ forceQuota: true });
196
+ return;
197
+ }
198
+
199
+ if (payload.command === "set_model") {
200
+ showToast("Model updated.");
201
+ refreshAll();
202
+ return;
203
+ }
204
+
205
+ if (payload.command === "set_thinking_level") {
206
+ showToast("Thinking level updated.");
207
+ refreshAll();
208
+ return;
209
+ }
210
+
211
+ if (payload.command === "switch_session") {
212
+ showToast("Session switched.");
213
+ refreshAll();
214
+ closeSheet();
215
+ return;
216
+ }
217
+
218
+ if (payload.command === "fork") {
219
+ showToast("Fork created.");
220
+ refreshAll();
221
+ closeSheet();
222
+ return;
223
+ }
224
+
225
+ if (payload.command === "phone_open_branch_path") {
226
+ showToast("Opened selected branch path as a new session.");
227
+ refreshAll();
228
+ closeSheet();
229
+ return;
230
+ }
231
+
232
+ return;
233
+ }
234
+
235
+ if (payload.type === "agent_start") {
236
+ state.status = { ...(state.status || {}), isStreaming: true };
237
+ renderHeader();
238
+ return;
239
+ }
240
+
241
+ if (payload.type === "agent_end") {
242
+ state.status = { ...(state.status || {}), isStreaming: false };
243
+ renderHeader();
244
+ refreshAll({ forceQuota: true });
245
+ return;
246
+ }
247
+
248
+ if (payload.type === "message_update") {
249
+ handleAssistantEvent(payload.assistantMessageEvent);
250
+ return;
251
+ }
252
+
253
+ if (payload.type === "message_end" && payload.message?.role === "assistant") {
254
+ const transformed = transformMessage(payload.message, Date.now())[0];
255
+ if (transformed) {
256
+ state.liveAssistant = { ...transformed, live: false };
257
+ renderMessages();
258
+ }
259
+ return;
260
+ }
261
+
262
+ if (payload.type === "tool_execution_start") {
263
+ upsertLiveTool(payload.toolCallId, {
264
+ id: `tool-live-${payload.toolCallId}`,
265
+ kind: "tool",
266
+ toolCallId: payload.toolCallId,
267
+ toolName: payload.toolName || "tool",
268
+ args: payload.args || {},
269
+ command: payload.args?.command || "",
270
+ live: true,
271
+ title: payload.toolName || "tool",
272
+ text: JSON.stringify(payload.args || {}, null, 2),
273
+ meta: "Running…",
274
+ status: "running",
275
+ rawContent: null,
276
+ });
277
+ return;
278
+ }
279
+
280
+ if (payload.type === "tool_execution_update") {
281
+ upsertLiveTool(payload.toolCallId, {
282
+ ...(state.liveTools.get(payload.toolCallId) || {}),
283
+ id: `tool-live-${payload.toolCallId}`,
284
+ kind: "tool",
285
+ toolCallId: payload.toolCallId,
286
+ toolName: payload.toolName || "tool",
287
+ args: payload.args || state.liveTools.get(payload.toolCallId)?.args || {},
288
+ command: payload.args?.command || state.liveTools.get(payload.toolCallId)?.command || "",
289
+ live: true,
290
+ title: payload.toolName || "tool",
291
+ text: contentToText(payload.partialResult?.content) || JSON.stringify(payload.args || {}, null, 2),
292
+ meta: "Running…",
293
+ status: "running",
294
+ details: payload.partialResult?.details,
295
+ rawContent: payload.partialResult?.content || state.liveTools.get(payload.toolCallId)?.rawContent || null,
296
+ });
297
+ return;
298
+ }
299
+
300
+ if (payload.type === "tool_execution_end") {
301
+ upsertLiveTool(payload.toolCallId, {
302
+ ...(state.liveTools.get(payload.toolCallId) || {}),
303
+ id: `tool-live-${payload.toolCallId}`,
304
+ kind: "tool",
305
+ toolCallId: payload.toolCallId,
306
+ toolName: payload.toolName || "tool",
307
+ args: payload.args || state.liveTools.get(payload.toolCallId)?.args || {},
308
+ command: payload.args?.command || state.liveTools.get(payload.toolCallId)?.command || "",
309
+ live: false,
310
+ title: payload.toolName || "tool",
311
+ text: contentToText(payload.result?.content),
312
+ meta: payload.isError ? "Failed" : "Done",
313
+ status: payload.isError ? "error" : "done",
314
+ details: payload.result?.details,
315
+ rawContent: payload.result?.content || state.liveTools.get(payload.toolCallId)?.rawContent || null,
316
+ });
317
+ return;
318
+ }
319
+
320
+ if (payload.type === "extension_ui_request") {
321
+ handleExtensionUiRequest(payload);
322
+ return;
323
+ }
324
+
325
+ if (payload.type === "auto_retry_start") {
326
+ showBanner(`Retrying after error: ${payload.errorMessage || "temporary failure"}`);
327
+ return;
328
+ }
329
+
330
+ if (payload.type === "auto_retry_end") {
331
+ showBanner(payload.success ? "" : `Retry failed: ${payload.finalError || "Unknown error"}`, payload.success ? "info" : "error");
332
+ }
333
+ }
334
+
335
+ export async function handleEnvelope(event) {
336
+ if (event.channel === "sessions" && event.event === "catalog") {
337
+ const nextActiveSessionId = event.data?.activeSessionId || state.activeSessionId;
338
+ const activeSessionChanged = nextActiveSessionId !== state.activeSessionId;
339
+
340
+ state.activeSessions = event.data?.sessions || [];
341
+ state.activeSessionId = nextActiveSessionId;
342
+
343
+ if (activeSessionChanged && state.snapshotWorkerId && state.snapshotWorkerId !== state.activeSessionId) {
344
+ clearSnapshotView();
345
+ renderMessages();
346
+ }
347
+
348
+ renderHeader();
349
+ renderSheet();
350
+ return;
351
+ }
352
+
353
+ if (event.channel === "snapshot") {
354
+ if (event.sessionWorkerId && state.activeSessionId && event.sessionWorkerId !== state.activeSessionId) {
355
+ return;
356
+ }
357
+
358
+ state.snapshotState = event.state || null;
359
+ state.snapshotWorkerId = event.sessionWorkerId || state.activeSessionId || null;
360
+ state.status = { ...(state.status || {}), isStreaming: Boolean(event.state?.isStreaming) };
361
+ state.messages = (event.messages || []).flatMap(transformMessage);
362
+ state.commands = event.commands || state.commands;
363
+ clearTransientState();
364
+
365
+ if (event.liveAssistantMessage?.role === "assistant") {
366
+ const assistant = transformMessage(event.liveAssistantMessage, Date.now())[0];
367
+ if (assistant) {
368
+ state.liveAssistant = { ...assistant, id: "assistant-live", live: true };
369
+ }
370
+ }
371
+
372
+ for (const tool of event.liveTools || []) {
373
+ const text =
374
+ contentToText(tool.result?.content)
375
+ || contentToText(tool.partialResult?.content)
376
+ || JSON.stringify(tool.args || {}, null, 2);
377
+
378
+ state.liveTools.set(tool.toolCallId, {
379
+ id: `tool-live-${tool.toolCallId}`,
380
+ kind: "tool",
381
+ toolCallId: tool.toolCallId,
382
+ toolName: tool.toolName || "tool",
383
+ args: tool.args || {},
384
+ command: tool.args?.command || "",
385
+ live: !tool.result,
386
+ title: tool.toolName || "tool",
387
+ text,
388
+ meta: tool.result ? (tool.isError ? "Failed" : "Done") : "Running…",
389
+ details: tool.result?.details || tool.partialResult?.details,
390
+ status: tool.isError ? "error" : (tool.result ? "done" : "running"),
391
+ rawContent: tool.result?.content || tool.partialResult?.content || null,
392
+ });
393
+ }
394
+
395
+ renderHeader();
396
+ renderMessages();
397
+ renderSheet();
398
+ renderCommandSuggestions();
399
+ void refreshQuota();
400
+ return;
401
+ }
402
+
403
+ if (event.channel === "server") {
404
+ if (event.event === "status") {
405
+ state.status = event.data;
406
+ renderHeader();
407
+ return;
408
+ }
409
+ if (event.event === "stderr") {
410
+ showBanner(event.data?.text?.trim() || "", "error");
411
+ return;
412
+ }
413
+ if (event.event === "reloading") {
414
+ showBanner(event.data?.message || "");
415
+ return;
416
+ }
417
+ if (event.event === "session-spawned") {
418
+ showToast(event.data?.message || "Opened new parallel session.");
419
+ return;
420
+ }
421
+ if (event.event === "single-client-replaced") {
422
+ showBanner(event.data?.message || "This phone session was replaced by another client.", "error");
423
+ return;
424
+ }
425
+ if (event.event === "idle-timeout") {
426
+ showBanner(event.data?.message || "DM Phone stopped because it was idle.", "error");
427
+ return;
428
+ }
429
+ if (["startup-error", "snapshot-error", "client-error"].includes(event.event)) {
430
+ showToast(event.data?.message || "Server error", "error");
431
+ return;
432
+ }
433
+ if (event.event === "agent-exit") {
434
+ showBanner(event.data?.message || "DM rpc exited.", "error");
435
+ return;
436
+ }
437
+ }
438
+
439
+ if (event.channel === "rpc") {
440
+ handleRpcPayload(event.payload);
441
+ }
442
+ }
@@ -0,0 +1,6 @@
1
+ import { initializeBindings } from "./bindings.js";
2
+ import { handleAuthFailure, handleEnvelope } from "./handlers.js";
3
+ import { boot } from "./transport.js";
4
+
5
+ initializeBindings({ handleEnvelope, handleAuthFailure });
6
+ boot({ handleEnvelope, handleAuthFailure });
@@ -0,0 +1,105 @@
1
+ import { escapeHtml } from "./formatters.js";
2
+
3
+ function findInlineCodeMarker(text, startIndex = 0) {
4
+ for (let index = Math.max(0, startIndex); index < text.length; index += 1) {
5
+ if (text[index] !== "`") continue;
6
+ if (text[index - 1] === "`" || text[index + 1] === "`") continue;
7
+ return index;
8
+ }
9
+ return -1;
10
+ }
11
+
12
+ function renderStrongText(text = "") {
13
+ let html = "";
14
+ let cursor = 0;
15
+
16
+ while (cursor < text.length) {
17
+ const open = text.indexOf("**", cursor);
18
+ if (open === -1) {
19
+ html += escapeHtml(text.slice(cursor));
20
+ break;
21
+ }
22
+
23
+ const close = text.indexOf("**", open + 2);
24
+ if (close === -1) {
25
+ html += escapeHtml(text.slice(cursor));
26
+ break;
27
+ }
28
+
29
+ html += escapeHtml(text.slice(cursor, open));
30
+ html += `<strong>${escapeHtml(text.slice(open + 2, close))}</strong>`;
31
+ cursor = close + 2;
32
+ }
33
+
34
+ return html;
35
+ }
36
+
37
+ function renderInlineMarkdown(text = "") {
38
+ let html = "";
39
+ let cursor = 0;
40
+
41
+ while (cursor < text.length) {
42
+ const open = findInlineCodeMarker(text, cursor);
43
+ if (open === -1) {
44
+ html += renderStrongText(text.slice(cursor));
45
+ break;
46
+ }
47
+
48
+ const close = findInlineCodeMarker(text, open + 1);
49
+ if (close === -1) {
50
+ html += renderStrongText(text.slice(cursor));
51
+ break;
52
+ }
53
+
54
+ html += renderStrongText(text.slice(cursor, open));
55
+ html += `<code>${escapeHtml(text.slice(open + 1, close))}</code>`;
56
+ cursor = close + 1;
57
+ }
58
+
59
+ return html;
60
+ }
61
+
62
+ function renderTextBlocks(text = "") {
63
+ const blocks = text
64
+ .split(/\n{2,}/)
65
+ .map((block) => (block.trim() ? `<p>${renderInlineMarkdown(block)}</p>` : ""))
66
+ .filter(Boolean);
67
+
68
+ if (blocks.length) return blocks.join("");
69
+ return text.trim() ? `<p>${renderInlineMarkdown(text)}</p>` : "";
70
+ }
71
+
72
+ function renderCodeBlock(code = "") {
73
+ return `
74
+ <pre class="message-code-block"><code>${escapeHtml(code)}</code></pre>
75
+ `;
76
+ }
77
+
78
+ export function renderMarkdownLite(text = "") {
79
+ const normalized = String(text || "").replace(/\r\n?/g, "\n");
80
+ const fencePattern = /```([^`\n]*)\n([\s\S]*?)```/g;
81
+ const parts = [];
82
+ let cursor = 0;
83
+ let match;
84
+
85
+ while ((match = fencePattern.exec(normalized))) {
86
+ if (match.index > cursor) {
87
+ parts.push({ type: "text", value: normalized.slice(cursor, match.index) });
88
+ }
89
+
90
+ parts.push({ type: "code", value: match[2].replace(/\n$/, "") });
91
+ cursor = match.index + match[0].length;
92
+ }
93
+
94
+ if (cursor < normalized.length) {
95
+ parts.push({ type: "text", value: normalized.slice(cursor) });
96
+ }
97
+
98
+ const html = parts.map((part) => (
99
+ part.type === "code"
100
+ ? renderCodeBlock(part.value)
101
+ : renderTextBlocks(part.value)
102
+ )).join("");
103
+
104
+ return html || '<p><span class="label">(no text)</span></p>';
105
+ }