clay-server 2.27.0-beta.11 → 2.27.0-beta.13

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.
@@ -0,0 +1,1478 @@
1
+ // app-messages.js - WebSocket message router
2
+ // Extracted from app.js (PR-23)
3
+
4
+ var _ctx = null;
5
+
6
+ export function initMessages(ctx) {
7
+ _ctx = ctx;
8
+ }
9
+
10
+ export function processMessage(msg) {
11
+ // Preserve original timestamp from history replay
12
+ _ctx.currentMsgTs = msg._ts || null;
13
+ var isMateDm = _ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate;
14
+
15
+ // DEBUG: trace session/history loading
16
+ if (msg.type === "session_switched" || msg.type === "history_meta" || msg.type === "history_done" || msg.type === "mention_user" || msg.type === "mention_response") {
17
+ console.log("[DEBUG msg]", msg.type, msg.type === "session_switched" ? "id=" + msg.id + " cli=" + (msg.cliSessionId || "").substring(0, 8) : "", msg.type === "history_meta" ? "from=" + msg.from + " total=" + msg.total : "", msg.type === "mention_user" ? "mate=" + msg.mateName : "", "dmMode=" + _ctx.dmMode);
18
+ }
19
+
20
+ // Mate DM: update mate icon status indicators
21
+ if (isMateDm) _ctx.updateMateIconStatus(msg);
22
+
23
+ // Mate DM: intercept mate-specific messages
24
+ if (isMateDm) {
25
+ if (msg.type === "session_list") {
26
+ _ctx.renderMateSessionList(msg.sessions || []);
27
+ _ctx.refreshMobileChatSheet();
28
+ // Override title bar with mate name and re-apply color
29
+ var _mdn = (_ctx.dmTargetUser.displayName || "New Mate");
30
+ if (_ctx.headerTitleEl) _ctx.headerTitleEl.textContent = _mdn;
31
+ var _tbpn = document.getElementById("title-bar-project-name");
32
+ if (_tbpn) _tbpn.textContent = _mdn;
33
+ var _mc2 = (_ctx.dmTargetUser.profile && _ctx.dmTargetUser.profile.avatarColor) || _ctx.dmTargetUser.avatarColor || "#7c3aed";
34
+ var _tbc2 = document.querySelector(".title-bar-content");
35
+ if (_tbc2) { _tbc2.style.background = _mc2; _tbc2.classList.add("mate-dm-active"); }
36
+ document.body.classList.add("mate-dm-active");
37
+ // Still let normal session_list handler run below
38
+ }
39
+ if (msg.type === "search_results") {
40
+ _ctx.handleMateSearchResults(msg);
41
+ return;
42
+ }
43
+ if (msg.type === "knowledge_list") {
44
+ _ctx.renderKnowledgeList(msg.files);
45
+ return;
46
+ }
47
+ if (msg.type === "knowledge_content") {
48
+ _ctx.handleKnowledgeContent(msg);
49
+ return;
50
+ }
51
+ if (msg.type === "knowledge_saved" || msg.type === "knowledge_deleted" || msg.type === "knowledge_promoted" || msg.type === "knowledge_depromoted") {
52
+ return;
53
+ }
54
+ if (msg.type === "memory_list") {
55
+ _ctx.renderMemoryList(msg.entries, msg.summary);
56
+ return;
57
+ }
58
+ if (msg.type === "memory_deleted") {
59
+ return;
60
+ }
61
+ // On done: scan DOM for [[MATE_READY: name]], update name, strip marker
62
+ if (msg.type === "done") {
63
+ setTimeout(function () { _ctx.scrollToBottom(); }, 100);
64
+ setTimeout(function () { _ctx.scrollToBottom(); }, 400);
65
+ setTimeout(function () {
66
+ var fullText = _ctx.messagesEl ? _ctx.messagesEl.textContent : "";
67
+ var readyMatch = fullText.match(/\[\[MATE_READY:\s*(.+?)\]\]/);
68
+ if (readyMatch) {
69
+ var newName = readyMatch[1].trim();
70
+ _ctx.dmTargetUser.displayName = newName;
71
+ _ctx.updateMateSidebarProfile({ profile: { displayName: newName, avatarColor: _ctx.dmTargetUser.avatarColor, avatarStyle: _ctx.dmTargetUser.avatarStyle, avatarSeed: _ctx.dmTargetUser.avatarSeed } });
72
+ if (_ctx.ws && _ctx.ws.readyState === 1) {
73
+ _ctx.ws.send(JSON.stringify({
74
+ type: "mate_update",
75
+ mateId: _ctx.dmTargetUser.id,
76
+ updates: { name: newName, status: "ready", profile: { displayName: newName } },
77
+ }));
78
+ }
79
+ }
80
+ var walker = document.createTreeWalker(_ctx.messagesEl, NodeFilter.SHOW_TEXT, null, false);
81
+ var node;
82
+ while (node = walker.nextNode()) {
83
+ if (node.nodeValue.indexOf("[[MATE_READY:") !== -1) {
84
+ node.nodeValue = node.nodeValue.replace(/\[\[MATE_READY:\s*.+?\]\]/g, "").trim();
85
+ }
86
+ }
87
+ }, 100);
88
+ }
89
+ }
90
+
91
+ switch (msg.type) {
92
+ case "history_meta":
93
+ _ctx.historyFrom = msg.from;
94
+ _ctx.historyTotal = msg.total;
95
+ _ctx.replayingHistory = true;
96
+ _ctx.updateHistorySentinel();
97
+ break;
98
+
99
+ case "history_prepend":
100
+ _ctx.prependOlderHistory(msg.items, msg.meta);
101
+ break;
102
+
103
+ case "history_done":
104
+ _ctx.replayingHistory = false;
105
+ // Restore cached rich context usage BEFORE updateContextPanel runs
106
+ if (msg.contextUsage) {
107
+ _ctx.richContextUsage = msg.contextUsage;
108
+ }
109
+ // Restore accurate context data from the last result in full history
110
+ if (msg.lastUsage || msg.lastModelUsage) {
111
+ _ctx.accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
112
+ }
113
+ _ctx.updateContextPanel();
114
+ _ctx.updateUsagePanel();
115
+ // Render + finalize any incomplete turn from the replayed history
116
+ if (_ctx.currentMsgEl && _ctx.currentFullText) {
117
+ var replayContentEl = _ctx.currentMsgEl.querySelector(".md-content");
118
+ if (replayContentEl) {
119
+ replayContentEl.innerHTML = _ctx.renderMarkdown(_ctx.currentFullText);
120
+ }
121
+ }
122
+ _ctx.markAllToolsDone();
123
+ _ctx.finalizeAssistantBlock();
124
+ _ctx.stopUrgentBlink();
125
+ // Clean up debate UI if debate is not active after replay
126
+ if (!_ctx.isDebateActive()) {
127
+ var dbBar = document.getElementById("debate-bottom-bar");
128
+ if (dbBar) dbBar.remove();
129
+ var dhBar = document.getElementById("debate-hand-raise-bar");
130
+ if (dhBar) dhBar.remove();
131
+ var dbBadges = document.querySelectorAll(".debate-header-badge");
132
+ for (var dbi = 0; dbi < dbBadges.length; dbi++) dbBadges[dbi].remove();
133
+ // Clean up all debate mode banners if debate is not active on this session
134
+ if (_ctx.debateFloorMode) _ctx.exitDebateFloorMode();
135
+ if (_ctx.debateConcludeMode) _ctx.exitDebateConcludeMode();
136
+ if (_ctx.debateEndedMode) _ctx.exitDebateEndedMode();
137
+ var dbBanner = document.getElementById("debate-floor-banner");
138
+ if (dbBanner) dbBanner.remove();
139
+ }
140
+ _ctx.scrollToBottom();
141
+ // Scroll to tool element if navigating from file edit history
142
+ var nav = _ctx.getPendingNavigate();
143
+ if (nav && (nav.toolId || nav.assistantUuid)) {
144
+ requestAnimationFrame(function() {
145
+ // Prefer scrolling to the exact tool element
146
+ var target = nav.toolId ? _ctx.messagesEl.querySelector('[data-tool-id="' + nav.toolId + '"]') : null;
147
+ if (!target && nav.assistantUuid) {
148
+ target = _ctx.messagesEl.querySelector('[data-uuid="' + nav.assistantUuid + '"]');
149
+ }
150
+ if (target) {
151
+ // Auto-expand parent tool group if collapsed
152
+ var parentGroup = target.closest(".tool-group");
153
+ if (parentGroup) parentGroup.classList.remove("collapsed");
154
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
155
+ target.classList.add("message-blink");
156
+ setTimeout(function() { target.classList.remove("message-blink"); }, 2000);
157
+ }
158
+ });
159
+ }
160
+ break;
161
+
162
+ case "restore_mate_dm":
163
+ if (msg.mateId && !_ctx.returningFromMateDm) {
164
+ // Server-driven mate DM restore on reconnect
165
+ // Note: do NOT remove mate-dm-active here; openDm is async (skill check)
166
+ // and removing the class causes a flash where mate UI is lost.
167
+ // enterDmMode will properly set/reset the class when DM is entered.
168
+ if (_ctx.dmMode) {
169
+ _ctx.dmMode = false;
170
+ }
171
+ _ctx.messagesEl.innerHTML = "";
172
+ _ctx.openDm(msg.mateId);
173
+ }
174
+ // Clear the flag and notify server that mate DM is closed
175
+ if (_ctx.returningFromMateDm) {
176
+ _ctx.returningFromMateDm = false;
177
+ if (_ctx.ws && _ctx.ws.readyState === 1) {
178
+ try { _ctx.ws.send(JSON.stringify({ type: "set_mate_dm", mateId: null })); } catch(e) {}
179
+ }
180
+ }
181
+ break;
182
+
183
+ case "info":
184
+ if (msg.text && !msg.project && !msg.cwd) {
185
+ _ctx.addSystemMessage(msg.text, false);
186
+ break;
187
+ }
188
+ _ctx.projectName = msg.project || msg.cwd;
189
+ if (msg.slug) _ctx.currentSlug = msg.slug;
190
+ try { localStorage.setItem("clay-project-name-" + (_ctx.currentSlug || "default"), _ctx.projectName); } catch (e) {}
191
+ // In mate DM, keep title as mate name and re-apply mate color
192
+ if (_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate) {
193
+ var _mateDN = _ctx.dmTargetUser.displayName || "New Mate";
194
+ _ctx.headerTitleEl.textContent = _mateDN;
195
+ var tbProjectName = _ctx.$("title-bar-project-name");
196
+ if (tbProjectName) tbProjectName.textContent = _mateDN;
197
+ // Re-apply mate title bar styling (may be lost during project switch)
198
+ var _mc = (_ctx.dmTargetUser.profile && _ctx.dmTargetUser.profile.avatarColor) || _ctx.dmTargetUser.avatarColor || "#7c3aed";
199
+ var _tbc = document.querySelector(".title-bar-content");
200
+ if (_tbc) { _tbc.style.background = _mc; _tbc.classList.add("mate-dm-active"); }
201
+ document.body.classList.add("mate-dm-active");
202
+ } else {
203
+ _ctx.headerTitleEl.textContent = _ctx.projectName;
204
+ var tbProjectName = _ctx.$("title-bar-project-name");
205
+ if (tbProjectName) tbProjectName.textContent = msg.title || _ctx.projectName;
206
+ }
207
+ _ctx.updatePageTitle();
208
+ if (msg.version) {
209
+ _ctx.setPaletteVersion(msg.version);
210
+ var serverVersionEl = document.getElementById("settings-server-version");
211
+ if (serverVersionEl) serverVersionEl.textContent = msg.version;
212
+ }
213
+ if (msg.projectOwnerId !== undefined) _ctx.currentProjectOwnerId = msg.projectOwnerId;
214
+ if (msg.osUsers !== undefined) _ctx.isOsUsers = !!msg.osUsers;
215
+ if (msg.lanHost) window.__lanHost = msg.lanHost;
216
+ if (msg.dangerouslySkipPermissions) {
217
+ _ctx.skipPermsEnabled = true;
218
+ var spBanner = _ctx.$("skip-perms-pill");
219
+ if (spBanner) spBanner.classList.remove("hidden");
220
+ }
221
+ _ctx.updateProjectList(msg);
222
+ break;
223
+
224
+ case "update_available":
225
+ // In multi-user mode, only show update UI to admins
226
+ if (_ctx.isMultiUserMode) {
227
+ _ctx.checkAdminAccess().then(function (isAdmin) {
228
+ if (!isAdmin) return;
229
+ _ctx.showUpdateAvailable(msg);
230
+ });
231
+ } else {
232
+ _ctx.showUpdateAvailable(msg);
233
+ }
234
+ break;
235
+
236
+ case "up_to_date":
237
+ var utdBtn = _ctx.$("settings-update-check");
238
+ if (utdBtn) {
239
+ utdBtn.innerHTML = "";
240
+ var utdIcon = document.createElement("i");
241
+ utdIcon.setAttribute("data-lucide", "check");
242
+ utdBtn.appendChild(utdIcon);
243
+ utdBtn.appendChild(document.createTextNode(" Up to date (v" + msg.version + ")"));
244
+ utdBtn.disabled = true;
245
+ _ctx.refreshIcons();
246
+ setTimeout(function () {
247
+ utdBtn.innerHTML = "";
248
+ var rwIcon = document.createElement("i");
249
+ rwIcon.setAttribute("data-lucide", "refresh-cw");
250
+ utdBtn.appendChild(rwIcon);
251
+ utdBtn.appendChild(document.createTextNode(" Check for updates"));
252
+ utdBtn.disabled = false;
253
+ utdBtn.classList.remove("settings-btn-update-available");
254
+ _ctx.refreshIcons();
255
+ }, 3000);
256
+ }
257
+ break;
258
+
259
+ case "update_started":
260
+ var updNowBtn = _ctx.$("update-now");
261
+ if (updNowBtn) {
262
+ updNowBtn.innerHTML = '<i data-lucide="loader"></i> Updating...';
263
+ updNowBtn.disabled = true;
264
+ _ctx.refreshIcons();
265
+ var spinIcon = updNowBtn.querySelector(".lucide");
266
+ if (spinIcon) spinIcon.classList.add("icon-spin-inline");
267
+ }
268
+ // Block the entire screen with the connect overlay
269
+ _ctx.connectOverlay.classList.remove("hidden");
270
+ break;
271
+
272
+ case "slash_commands":
273
+ var reserved = new Set(_ctx.builtinCommands.map(function (c) { return c.name; }));
274
+ _ctx.slashCommands = (msg.commands || []).filter(function (name) {
275
+ return !reserved.has(name);
276
+ }).map(function (name) {
277
+ return { name: name, desc: "Skill" };
278
+ });
279
+ break;
280
+
281
+ case "model_info":
282
+ _ctx.currentModel = msg.model || _ctx.currentModel;
283
+ _ctx.currentModels = msg.models || [];
284
+ _ctx.updateConfigChip();
285
+ _ctx.updateSettingsModels(msg.model, msg.models || []);
286
+ break;
287
+
288
+ case "config_state":
289
+ if (msg.model) _ctx.currentModel = msg.model;
290
+ if (msg.mode) _ctx.currentMode = msg.mode;
291
+ if (msg.effort) _ctx.currentEffort = msg.effort;
292
+ if (msg.betas) _ctx.currentBetas = msg.betas;
293
+ if (msg.thinking) _ctx.currentThinking = msg.thinking;
294
+ if (msg.thinkingBudget) _ctx.currentThinkingBudget = msg.thinkingBudget;
295
+ // Validate effort against current model's supported levels
296
+ if (_ctx.currentModels.length > 0) {
297
+ var levels = _ctx.getModelEffortLevels();
298
+ var effortValid = false;
299
+ for (var ei = 0; ei < levels.length; ei++) {
300
+ if (levels[ei] === _ctx.currentEffort) { effortValid = true; break; }
301
+ }
302
+ if (!effortValid) _ctx.currentEffort = "medium";
303
+ }
304
+ _ctx.updateConfigChip();
305
+ break;
306
+
307
+ case "client_count":
308
+ // Sidebar presence: current project's online users
309
+ if (msg.users) {
310
+ _ctx.renderSidebarPresence(msg.users);
311
+ }
312
+ // Non-multi-user mode: simple count in topbar
313
+ if (!msg.users) {
314
+ var countEl = document.getElementById("client-count");
315
+ var countTextEl = document.getElementById("client-count-text");
316
+ if (countEl && countTextEl) {
317
+ if (msg.count > 1) {
318
+ countTextEl.textContent = msg.count + " connected";
319
+ countEl.classList.remove("hidden");
320
+ } else {
321
+ countEl.classList.add("hidden");
322
+ }
323
+ }
324
+ }
325
+ break;
326
+
327
+ case "toast":
328
+ _ctx.showToast(msg.message, msg.level, msg.detail);
329
+ break;
330
+
331
+ case "skill_installed":
332
+ _ctx.handleSkillInstalled(msg);
333
+ if (msg.success) _ctx.knownInstalledSkills[msg.skill] = true;
334
+ _ctx.handleSkillInstallWs(msg);
335
+ break;
336
+
337
+ case "skill_uninstalled":
338
+ _ctx.handleSkillUninstalled(msg);
339
+ if (msg.success) delete _ctx.knownInstalledSkills[msg.skill];
340
+ break;
341
+
342
+ case "loop_registry_updated":
343
+ _ctx.handleLoopRegistryUpdated(msg);
344
+ break;
345
+
346
+ case "schedule_run_started":
347
+ _ctx.handleScheduleRunStarted(msg);
348
+ break;
349
+
350
+ case "schedule_run_finished":
351
+ _ctx.handleScheduleRunFinished(msg);
352
+ break;
353
+
354
+ case "loop_scheduled":
355
+ _ctx.handleLoopScheduled(msg);
356
+ break;
357
+
358
+ case "schedule_move_result":
359
+ if (msg.ok) {
360
+ _ctx.showToast("Task moved", "success");
361
+ } else {
362
+ _ctx.showToast(msg.error || "Failed to move task", "error");
363
+ }
364
+ break;
365
+
366
+ case "remove_project_check_result":
367
+ _ctx.handleRemoveProjectCheckResult(msg);
368
+ break;
369
+
370
+ case "hub_schedules":
371
+ _ctx.handleHubSchedules(msg);
372
+ break;
373
+
374
+ case "input_sync":
375
+ if (!_ctx.dmMode) _ctx.handleInputSync(msg.text);
376
+ break;
377
+
378
+ case "session_list":
379
+ _ctx.renderMateSessionList(msg.sessions || []);
380
+ _ctx.renderSessionList(msg.sessions || []);
381
+ _ctx.handlePaletteSessionSwitch();
382
+ break;
383
+
384
+ case "session_presence":
385
+ _ctx.updateSessionPresence(msg.presence || {});
386
+ break;
387
+
388
+ case "cursor_move":
389
+ _ctx.handleRemoteCursorMove(msg);
390
+ break;
391
+
392
+ case "cursor_leave":
393
+ _ctx.handleRemoteCursorLeave(msg);
394
+ break;
395
+
396
+ case "text_select":
397
+ _ctx.handleRemoteSelection(msg);
398
+ break;
399
+
400
+ case "session_io":
401
+ _ctx.blinkSessionDot(msg.id);
402
+ break;
403
+
404
+ case "session_unread":
405
+ _ctx.updateSessionBadge(msg.id, msg.count);
406
+ break;
407
+
408
+ case "search_results":
409
+ _ctx.handleSearchResults(msg);
410
+ break;
411
+
412
+ case "search_content_results":
413
+ if (msg.source === "find_in_session") {
414
+ _ctx.handleFindInSessionResults(msg);
415
+ }
416
+ break;
417
+
418
+ case "cli_session_list":
419
+ _ctx.populateCliSessionList(msg.sessions || []);
420
+ break;
421
+
422
+ case "session_switched":
423
+ _ctx.hideHomeHub();
424
+ // Save draft from outgoing session
425
+ if (_ctx.activeSessionId && _ctx.inputEl.value) {
426
+ _ctx.sessionDrafts[_ctx.activeSessionId] = _ctx.inputEl.value;
427
+ } else if (_ctx.activeSessionId) {
428
+ delete _ctx.sessionDrafts[_ctx.activeSessionId];
429
+ }
430
+ _ctx.activeSessionId = msg.id;
431
+ _ctx.cliSessionId = msg.cliSessionId || null;
432
+ // Session presence is now tracked server-side (user-presence.json)
433
+ _ctx.clearRemoteCursors();
434
+ _ctx.resetClientState();
435
+ _ctx.updateRalphBars();
436
+ _ctx.updateLoopInputVisibility(msg.loop);
437
+ // Restore input area visibility (may have been hidden by auth_required)
438
+ var inputAreaSw = document.getElementById("input-area");
439
+ if (inputAreaSw) inputAreaSw.classList.remove("hidden");
440
+ // Restore draft for incoming session
441
+ var draft = _ctx.sessionDrafts[_ctx.activeSessionId] || "";
442
+ _ctx.inputEl.value = draft;
443
+ _ctx.autoResize();
444
+ if (!("ontouchstart" in window)) {
445
+ _ctx.inputEl.focus();
446
+ }
447
+ break;
448
+
449
+ case "session_id":
450
+ _ctx.cliSessionId = msg.cliSessionId;
451
+ break;
452
+
453
+ case "message_uuid":
454
+ var uuidTarget;
455
+ if (msg.messageType === "user") {
456
+ var allUsers = _ctx.messagesEl.querySelectorAll(".msg-user:not([data-uuid])");
457
+ if (allUsers.length > 0) uuidTarget = allUsers[allUsers.length - 1];
458
+ } else {
459
+ var allAssistants = _ctx.messagesEl.querySelectorAll(".msg-assistant:not([data-uuid])");
460
+ if (allAssistants.length > 0) uuidTarget = allAssistants[allAssistants.length - 1];
461
+ }
462
+ if (uuidTarget) {
463
+ uuidTarget.dataset.uuid = msg.uuid;
464
+ if (msg.messageType === "user") _ctx.addRewindButton(uuidTarget);
465
+ }
466
+ _ctx.messageUuidMap.push({ uuid: msg.uuid, type: msg.messageType });
467
+ break;
468
+
469
+ case "user_message":
470
+ if (msg._internal) break;
471
+ _ctx.resetThinkingGroup();
472
+ if (msg.planContent) {
473
+ _ctx.setPlanContent(msg.planContent);
474
+ _ctx.renderPlanCard(msg.planContent);
475
+ _ctx.addUserMessage("Execute the following plan. Do NOT re-enter plan mode — just implement it step by step.", msg.images || null, msg.pastes || null, msg.from, msg.fromName);
476
+ } else {
477
+ _ctx.addUserMessage(msg.text, msg.images || null, msg.pastes || null, msg.from, msg.fromName);
478
+ }
479
+ break;
480
+
481
+ case "context_preview":
482
+ // Show a Context Card with tab screenshot between user message and assistant response
483
+ if (msg.tab) {
484
+ var card = document.createElement("div");
485
+ card.className = "context-card";
486
+
487
+ // Header
488
+ var header = document.createElement("div");
489
+ header.className = "context-card-header";
490
+ var icon = document.createElement("span");
491
+ icon.className = "context-card-icon";
492
+ icon.innerHTML = _ctx.iconHtml("globe");
493
+ header.appendChild(icon);
494
+ var label = document.createElement("span");
495
+ label.textContent = "Viewing tab";
496
+ header.appendChild(label);
497
+ card.appendChild(header);
498
+
499
+ // Screenshot
500
+ if (msg.tab.screenshotUrl) {
501
+ var img = document.createElement("img");
502
+ img.className = "context-card-screenshot";
503
+ img.src = msg.tab.screenshotUrl;
504
+ img.loading = "lazy";
505
+ img.addEventListener("click", function () { _ctx.showImageModal(this.src); });
506
+ card.appendChild(img);
507
+ }
508
+
509
+ // Meta: title + domain
510
+ var tabTitle = msg.tab.title || "";
511
+ var tabDomain = "";
512
+ try { tabDomain = new URL(msg.tab.url).hostname; } catch (e) {}
513
+ if (tabTitle || tabDomain) {
514
+ var meta = document.createElement("div");
515
+ meta.className = "context-card-meta";
516
+ if (msg.tab.favIconUrl) {
517
+ var fav = document.createElement("img");
518
+ fav.className = "context-card-favicon";
519
+ fav.src = msg.tab.favIconUrl;
520
+ fav.width = 14;
521
+ fav.height = 14;
522
+ fav.onerror = function () { this.style.display = "none"; };
523
+ meta.appendChild(fav);
524
+ }
525
+ var titleEl = document.createElement("span");
526
+ titleEl.className = "context-card-title";
527
+ titleEl.textContent = tabTitle;
528
+ meta.appendChild(titleEl);
529
+ if (tabDomain) {
530
+ var domainEl = document.createElement("span");
531
+ domainEl.className = "context-card-domain";
532
+ domainEl.textContent = tabDomain;
533
+ meta.appendChild(domainEl);
534
+ }
535
+ card.appendChild(meta);
536
+ }
537
+
538
+ _ctx.messagesEl.appendChild(card);
539
+ _ctx.scrollToBottom();
540
+ }
541
+ break;
542
+
543
+ case "status":
544
+ if (msg.status === "processing") {
545
+ _ctx.setStatus("processing");
546
+ if (!(_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate) && !_ctx.matePreThinkingEl) {
547
+ _ctx.setActivity("thinking");
548
+ }
549
+ }
550
+ break;
551
+
552
+ case "compacting":
553
+ if (msg.active) {
554
+ _ctx.setActivity("compacting");
555
+ } else if (!(_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate)) {
556
+ _ctx.setActivity("thinking");
557
+ }
558
+ break;
559
+
560
+ case "thinking_start":
561
+ _ctx.removeMatePreThinking();
562
+ _ctx.startThinking();
563
+ break;
564
+
565
+ case "thinking_delta":
566
+ if (typeof msg.text === "string") _ctx.appendThinking(msg.text);
567
+ break;
568
+
569
+ case "thinking_stop":
570
+ _ctx.stopThinking(msg.duration);
571
+ if (!(_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate)) {
572
+ _ctx.setActivity("thinking");
573
+ }
574
+ break;
575
+
576
+ case "delta":
577
+ if (typeof msg.text !== "string") break;
578
+ _ctx.removeMatePreThinking();
579
+ _ctx.stopThinking();
580
+ _ctx.resetThinkingGroup();
581
+ _ctx.setActivity(null);
582
+ _ctx.appendDelta(msg.text);
583
+ break;
584
+
585
+ case "tool_start":
586
+ _ctx.removeMatePreThinking();
587
+ _ctx.stopThinking();
588
+ _ctx.markAllToolsDone();
589
+ if (msg.name === "EnterPlanMode") {
590
+ _ctx.renderPlanBanner("enter");
591
+ _ctx.getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
592
+ } else if (msg.name === "ExitPlanMode") {
593
+ if (_ctx.getPlanContent()) {
594
+ _ctx.renderPlanCard(_ctx.getPlanContent());
595
+ }
596
+ _ctx.renderPlanBanner("exit");
597
+ _ctx.getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
598
+ } else if (msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) {
599
+ _ctx.getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
600
+ } else if (_ctx.getTodoTools()[msg.name]) {
601
+ _ctx.getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
602
+ } else {
603
+ _ctx.createToolItem(msg.id, msg.name);
604
+ }
605
+ break;
606
+
607
+ case "tool_executing":
608
+ if ((msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) && msg.input) {
609
+ var _dpTool = _ctx.getTools()[msg.id];
610
+ if (_dpTool) {
611
+ if (_dpTool.el) _dpTool.el.style.display = "none";
612
+ _dpTool.done = true;
613
+ _dpTool.hidden = true;
614
+ _ctx.removeToolFromGroup(msg.id);
615
+ }
616
+ _ctx.finalizeAssistantBlock();
617
+ _ctx.renderMcpDebateProposal(msg.id, msg.input);
618
+ _ctx.startUrgentBlink();
619
+ } else if (msg.name === "AskUserQuestion" && msg.input && msg.input.questions) {
620
+ var askTool = _ctx.getTools()[msg.id];
621
+ if (askTool) {
622
+ if (askTool.el) askTool.el.style.display = "none";
623
+ askTool.done = true;
624
+ _ctx.removeToolFromGroup(msg.id);
625
+ }
626
+ _ctx.renderAskUserQuestion(msg.id, msg.input);
627
+ _ctx.startUrgentBlink();
628
+ } else if (msg.name === "Write" && msg.input && _ctx.isPlanFilePath(msg.input.file_path)) {
629
+ _ctx.setPlanContent(msg.input.content || "");
630
+ _ctx.updateToolExecuting(msg.id, msg.name, msg.input);
631
+ } else if (msg.name === "Edit" && msg.input && _ctx.isPlanFilePath(msg.input.file_path)) {
632
+ var pc = _ctx.getPlanContent() || "";
633
+ if (msg.input.old_string && pc.indexOf(msg.input.old_string) !== -1) {
634
+ if (msg.input.replace_all) {
635
+ _ctx.setPlanContent(pc.split(msg.input.old_string).join(msg.input.new_string || ""));
636
+ } else {
637
+ _ctx.setPlanContent(pc.replace(msg.input.old_string, msg.input.new_string || ""));
638
+ }
639
+ }
640
+ _ctx.updateToolExecuting(msg.id, msg.name, msg.input);
641
+ } else if (msg.name === "TodoWrite") {
642
+ _ctx.handleTodoWrite(msg.input);
643
+ } else if (msg.name === "TaskCreate") {
644
+ _ctx.handleTaskCreate(msg.input);
645
+ } else if (msg.name === "TaskUpdate") {
646
+ _ctx.handleTaskUpdate(msg.input);
647
+ } else if (_ctx.getTodoTools()[msg.name]) {
648
+ // TaskList, TaskGet - silently skip
649
+ } else {
650
+ var t = _ctx.getTools()[msg.id];
651
+ if (t && t.hidden) break;
652
+ _ctx.updateToolExecuting(msg.id, msg.name, msg.input);
653
+ }
654
+ break;
655
+
656
+ case "tool_result": {
657
+ var tr = _ctx.getTools()[msg.id];
658
+ if (tr && tr.hidden) break; // skip hidden plan tools
659
+ // Always call updateToolResult for Edit (to show diff from input), or when content exists
660
+ if (msg.content != null || msg.images || (tr && tr.name === "Edit" && tr.input && tr.input.old_string)) {
661
+ _ctx.updateToolResult(msg.id, msg.content || "", msg.is_error || false, msg.images);
662
+ }
663
+ // Refresh file browser if an Edit/Write tool modified the open file
664
+ if (!msg.is_error && tr && (tr.name === "Edit" || tr.name === "Write") && tr.input && tr.input.file_path) {
665
+ _ctx.refreshIfOpen(tr.input.file_path);
666
+ }
667
+ }
668
+ break;
669
+
670
+ case "ask_user_answered":
671
+ _ctx.markAskUserAnswered(msg.toolId, msg.answers);
672
+ _ctx.stopUrgentBlink();
673
+ break;
674
+
675
+ case "permission_request":
676
+ _ctx.renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId);
677
+ _ctx.startUrgentBlink();
678
+ break;
679
+
680
+ case "permission_cancel":
681
+ _ctx.markPermissionCancelled(msg.requestId);
682
+ _ctx.stopUrgentBlink();
683
+ break;
684
+
685
+ case "permission_resolved":
686
+ _ctx.markPermissionResolved(msg.requestId, msg.decision);
687
+ _ctx.stopUrgentBlink();
688
+ break;
689
+
690
+ case "permission_request_pending":
691
+ _ctx.renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId);
692
+ _ctx.startUrgentBlink();
693
+ break;
694
+
695
+ case "elicitation_request":
696
+ _ctx.renderElicitationRequest(msg);
697
+ _ctx.startUrgentBlink();
698
+ break;
699
+
700
+ case "elicitation_resolved":
701
+ _ctx.markElicitationResolved(msg.requestId, msg.action);
702
+ _ctx.stopUrgentBlink();
703
+ break;
704
+
705
+ case "slash_command_result":
706
+ _ctx.finalizeAssistantBlock();
707
+ var cmdBlock = document.createElement("div");
708
+ cmdBlock.className = "assistant-block";
709
+ cmdBlock.style.maxWidth = "var(--content-width)";
710
+ cmdBlock.style.margin = "12px auto";
711
+ cmdBlock.style.padding = "0 20px";
712
+ var pre = document.createElement("pre");
713
+ pre.style.cssText = "background:var(--code-bg);border:1px solid var(--border-subtle);border-radius:10px;padding:12px 14px;font-family:'SF Mono',Menlo,Monaco,monospace;font-size:12px;line-height:1.55;color:var(--text-secondary);white-space:pre-wrap;word-break:break-word;max-height:400px;overflow-y:auto;margin:0";
714
+ pre.textContent = msg.text;
715
+ cmdBlock.appendChild(pre);
716
+ _ctx.addToMessages(cmdBlock);
717
+ _ctx.scrollToBottom();
718
+ break;
719
+
720
+ case "subagent_activity":
721
+ _ctx.updateSubagentActivity(msg.parentToolId, msg.text);
722
+ break;
723
+
724
+ case "subagent_tool":
725
+ _ctx.addSubagentToolEntry(msg.parentToolId, msg.toolName, msg.toolId, msg.text);
726
+ break;
727
+
728
+ case "subagent_done":
729
+ _ctx.markSubagentDone(msg.parentToolId, msg.status, msg.summary, msg.usage);
730
+ break;
731
+
732
+ case "task_started":
733
+ _ctx.initSubagentStop(msg.parentToolId, msg.taskId);
734
+ break;
735
+
736
+ case "task_progress":
737
+ _ctx.updateSubagentProgress(msg.parentToolId, msg.usage, msg.lastToolName, msg.summary);
738
+ break;
739
+
740
+ case "result":
741
+ _ctx.setActivity(null);
742
+ _ctx.stopThinking();
743
+ _ctx.markAllToolsDone();
744
+ _ctx.closeToolGroup();
745
+ _ctx.finalizeAssistantBlock();
746
+ _ctx.addTurnMeta(msg.cost, msg.duration);
747
+ _ctx.accumulateUsage(msg.cost, msg.usage);
748
+ _ctx.accumulateContext(msg.cost, msg.usage, msg.modelUsage, msg.lastStreamInputTokens);
749
+ break;
750
+
751
+ case "context_usage":
752
+ if (msg.data && !_ctx.replayingHistory) {
753
+ _ctx.richContextUsage = msg.data;
754
+ if (_ctx.headerContextEl) _ctx.headerContextEl.removeAttribute("data-tip");
755
+ if (_ctx.ctxPopoverVisible) _ctx.renderCtxPopover();
756
+ }
757
+ break;
758
+
759
+ case "done":
760
+ _ctx.setActivity(null);
761
+ _ctx.stopThinking();
762
+ _ctx.markAllToolsDone();
763
+ _ctx.closeToolGroup();
764
+ _ctx.finalizeAssistantBlock();
765
+ _ctx.processing = false;
766
+ _ctx.setStatus("connected");
767
+ if (!_ctx.loopActive) _ctx.enableMainInput();
768
+ _ctx.resetToolState();
769
+ _ctx.stopUrgentBlink();
770
+ if (document.hidden) {
771
+ if (_ctx.isNotifAlertEnabled() && !window._pushSubscription) _ctx.showDoneNotification();
772
+ if (_ctx.isNotifSoundEnabled()) _ctx.playDoneSound();
773
+ }
774
+ break;
775
+
776
+ case "stderr":
777
+ _ctx.addSystemMessage(msg.text, false);
778
+ break;
779
+
780
+ case "error":
781
+ _ctx.setActivity(null);
782
+ _ctx.addSystemMessage(msg.text, true);
783
+ break;
784
+
785
+ case "process_conflict":
786
+ _ctx.setActivity(null);
787
+ _ctx.addConflictMessage(msg);
788
+ break;
789
+
790
+ case "context_overflow":
791
+ _ctx.setActivity(null);
792
+ _ctx.addContextOverflowMessage(msg);
793
+ break;
794
+
795
+ case "auth_required":
796
+ _ctx.setActivity(null);
797
+ _ctx.addAuthRequiredMessage(msg);
798
+ break;
799
+
800
+ case "rate_limit":
801
+ _ctx.handleRateLimitEvent(msg);
802
+ break;
803
+
804
+ case "rate_limit_usage":
805
+ _ctx.updateRateLimitUsage(msg);
806
+ break;
807
+
808
+ case "scheduled_message_queued":
809
+ _ctx.addScheduledMessageBubble(msg.text, msg.resetsAt);
810
+ _ctx.setScheduleBtnDisabled(true);
811
+ break;
812
+
813
+ case "scheduled_message_sent":
814
+ _ctx.removeScheduledMessageBubble();
815
+ _ctx.setScheduleBtnDisabled(false);
816
+ _ctx.processing = true;
817
+ _ctx.setStatus("processing");
818
+ break;
819
+
820
+ case "scheduled_message_cancelled":
821
+ _ctx.removeScheduledMessageBubble();
822
+ _ctx.setScheduleBtnDisabled(false);
823
+ break;
824
+
825
+ case "auto_continue_scheduled":
826
+ // Scheduler auto-continue, just show info
827
+ break;
828
+
829
+ case "auto_continue_fired":
830
+ _ctx.processing = true;
831
+ _ctx.setStatus("processing");
832
+ break;
833
+
834
+ case "prompt_suggestion":
835
+ _ctx.showSuggestionChips(msg.suggestion);
836
+ break;
837
+
838
+ case "fast_mode_state":
839
+ _ctx.handleFastModeState(msg.state);
840
+ break;
841
+
842
+ case "process_killed":
843
+ _ctx.addSystemMessage("Process " + msg.pid + " has been terminated. You can retry your message now.", false);
844
+ break;
845
+
846
+ case "rewind_preview_result":
847
+ _ctx.showRewindModal(msg);
848
+ break;
849
+
850
+ case "rewind_complete":
851
+ _ctx.onRewindComplete();
852
+ _ctx.setRewindMode(false);
853
+ var rewindText = "Rewound to earlier point. Files have been restored.";
854
+ if (msg.mode === "chat") rewindText = "Conversation rewound to earlier point.";
855
+ else if (msg.mode === "files") rewindText = "Files restored to earlier point.";
856
+ _ctx.addSystemMessage(rewindText, false);
857
+ break;
858
+
859
+ case "rewind_error":
860
+ _ctx.onRewindError();
861
+ _ctx.clearPendingRewindUuid();
862
+ _ctx.addSystemMessage(msg.text || "Rewind failed.", true);
863
+ break;
864
+
865
+ case "fork_complete":
866
+ _ctx.addSystemMessage("Session forked successfully.");
867
+ break;
868
+
869
+ case "fs_list_result":
870
+ _ctx.handleFsList(msg);
871
+ break;
872
+
873
+ case "fs_read_result":
874
+ if (msg.path === "CLAUDE.md" && _ctx.isProjectSettingsOpen()) {
875
+ _ctx.handleInstructionsRead(msg);
876
+ } else {
877
+ _ctx.handleFsRead(msg);
878
+ }
879
+ break;
880
+
881
+ case "fs_write_result":
882
+ _ctx.handleInstructionsWrite(msg);
883
+ break;
884
+
885
+ case "project_env_result":
886
+ _ctx.handleProjectEnv(msg);
887
+ break;
888
+
889
+ case "set_project_env_result":
890
+ _ctx.handleProjectEnvSaved(msg);
891
+ break;
892
+
893
+ case "global_claude_md_result":
894
+ _ctx.handleGlobalClaudeMdRead(msg);
895
+ break;
896
+
897
+ case "write_global_claude_md_result":
898
+ _ctx.handleGlobalClaudeMdWrite(msg);
899
+ break;
900
+
901
+ case "shared_env_result":
902
+ _ctx.handleSharedEnv(msg);
903
+ _ctx.handleProjectSharedEnv(msg);
904
+ break;
905
+
906
+ case "set_shared_env_result":
907
+ _ctx.handleSharedEnvSaved(msg);
908
+ _ctx.handleProjectSharedEnvSaved(msg);
909
+ break;
910
+
911
+ case "fs_file_changed":
912
+ _ctx.handleFileChanged(msg);
913
+ break;
914
+
915
+ case "fs_dir_changed":
916
+ _ctx.handleDirChanged(msg);
917
+ break;
918
+
919
+ case "fs_file_history_result":
920
+ _ctx.handleFileHistory(msg);
921
+ break;
922
+
923
+ case "fs_git_diff_result":
924
+ _ctx.handleGitDiff(msg);
925
+ break;
926
+
927
+ case "fs_file_at_result":
928
+ _ctx.handleFileAt(msg);
929
+ break;
930
+
931
+ case "term_list":
932
+ _ctx.handleTermList(msg);
933
+ _ctx.updateTerminalList(msg.terminals);
934
+ break;
935
+
936
+ case "context_sources_state":
937
+ _ctx.handleContextSourcesState(msg);
938
+ break;
939
+
940
+ case "extension_command":
941
+ _ctx.sendExtensionCommand(msg.command, msg.args, msg.requestId);
942
+ break;
943
+
944
+ case "term_created":
945
+ _ctx.handleTermCreated(msg);
946
+ if (_ctx.pendingTermCommand) {
947
+ var cmd = _ctx.pendingTermCommand;
948
+ _ctx.pendingTermCommand = null;
949
+ // Small delay to let terminal initialize
950
+ setTimeout(function() {
951
+ _ctx.sendTerminalCommand(cmd);
952
+ }, 300);
953
+ }
954
+ break;
955
+
956
+ case "term_output":
957
+ _ctx.handleTermOutput(msg);
958
+ break;
959
+
960
+ case "term_resized":
961
+ _ctx.handleTermResized(msg);
962
+ break;
963
+
964
+ case "term_exited":
965
+ _ctx.handleTermExited(msg);
966
+ break;
967
+
968
+ case "term_closed":
969
+ _ctx.handleTermClosed(msg);
970
+ break;
971
+
972
+ case "notes_list":
973
+ _ctx.handleNotesList(msg);
974
+ break;
975
+
976
+ case "note_created":
977
+ _ctx.handleNoteCreated(msg);
978
+ break;
979
+
980
+ case "note_updated":
981
+ _ctx.handleNoteUpdated(msg);
982
+ break;
983
+
984
+ case "note_deleted":
985
+ _ctx.handleNoteDeleted(msg);
986
+ break;
987
+
988
+ case "process_stats":
989
+ _ctx.updateStatusPanel(msg);
990
+ _ctx.updateSettingsStats(msg);
991
+ break;
992
+
993
+ case "browse_dir_result":
994
+ _ctx.handleBrowseDirResult(msg);
995
+ break;
996
+
997
+ case "add_project_result":
998
+ _ctx.handleAddProjectResult(msg);
999
+ break;
1000
+
1001
+ case "clone_project_progress":
1002
+ _ctx.handleCloneProgress(msg);
1003
+ break;
1004
+
1005
+ case "remove_project_result":
1006
+ _ctx.handleRemoveProjectResult(msg);
1007
+ break;
1008
+
1009
+ case "reorder_projects_result":
1010
+ if (!msg.ok) {
1011
+ _ctx.showToast(msg.error || "Failed to reorder projects", "error");
1012
+ }
1013
+ break;
1014
+
1015
+ case "set_project_title_result":
1016
+ if (!msg.ok) {
1017
+ _ctx.showToast(msg.error || "Failed to rename project", "error");
1018
+ }
1019
+ break;
1020
+
1021
+ case "set_project_icon_result":
1022
+ if (!msg.ok) {
1023
+ _ctx.showToast(msg.error || "Failed to set icon", "error");
1024
+ }
1025
+ break;
1026
+
1027
+ case "projects_updated":
1028
+ _ctx.updateProjectList(msg);
1029
+ break;
1030
+
1031
+ case "project_owner_changed":
1032
+ _ctx.currentProjectOwnerId = msg.ownerId;
1033
+ _ctx.handleProjectOwnerChanged(msg);
1034
+ break;
1035
+
1036
+ // --- DM ---
1037
+ case "dm_history":
1038
+ // Attach projectSlug to targetUser for mate DMs
1039
+ if (msg.projectSlug && msg.targetUser) {
1040
+ msg.targetUser.projectSlug = msg.projectSlug;
1041
+ }
1042
+ _ctx.enterDmMode(msg.dmKey, msg.targetUser, msg.messages);
1043
+ // Auto-send first interview prompt after mate DM opens
1044
+ if (_ctx.pendingMateInterview && msg.targetUser && msg.targetUser.isMate && msg.projectSlug) {
1045
+ var interviewMate = _ctx.pendingMateInterview;
1046
+ _ctx.pendingMateInterview = null;
1047
+ // Wait for mate project WS to connect, then send interview prompt
1048
+ var checkMateReady = setInterval(function () {
1049
+ if (_ctx.ws && _ctx.ws.readyState === 1 && _ctx.mateProjectSlug) {
1050
+ clearInterval(checkMateReady);
1051
+ var interviewText = _ctx.buildMateInterviewPrompt(interviewMate);
1052
+ _ctx.ws.send(JSON.stringify({ type: "message", text: interviewText }));
1053
+ }
1054
+ }, 100);
1055
+ setTimeout(function () { clearInterval(checkMateReady); }, 5000);
1056
+ }
1057
+ break;
1058
+
1059
+ case "dm_message":
1060
+ if (_ctx.dmMode && msg.dmKey === _ctx.dmKey) {
1061
+ _ctx.showDmTypingIndicator(false); // hide typing when message arrives
1062
+ _ctx.appendDmMessage(msg.message);
1063
+ _ctx.scrollToBottom();
1064
+ } else if (msg.message) {
1065
+ // DM notification when not in that DM
1066
+ var fromId = msg.message.from;
1067
+ if (fromId && fromId !== _ctx.myUserId) {
1068
+ _ctx.dmUnread[fromId] = (_ctx.dmUnread[fromId] || 0) + 1;
1069
+ // Re-render strip so non-favorited sender appears
1070
+ _ctx.renderUserStrip(_ctx.cachedAllUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
1071
+ _ctx.updateDmBadge(fromId, _ctx.dmUnread[fromId]);
1072
+ }
1073
+ }
1074
+ break;
1075
+
1076
+ case "dm_typing":
1077
+ if (_ctx.dmMode && msg.dmKey === _ctx.dmKey) {
1078
+ _ctx.showDmTypingIndicator(msg.typing);
1079
+ }
1080
+ break;
1081
+
1082
+ case "dm_list":
1083
+ // Could be used for DM list view later
1084
+ break;
1085
+
1086
+ case "dm_favorites_updated":
1087
+ // Track users explicitly removed from favorites
1088
+ if (_ctx.cachedDmFavorites && msg.dmFavorites) {
1089
+ for (var ri = 0; ri < _ctx.cachedDmFavorites.length; ri++) {
1090
+ if (msg.dmFavorites.indexOf(_ctx.cachedDmFavorites[ri]) === -1) {
1091
+ _ctx.dmRemovedUsers[_ctx.cachedDmFavorites[ri]] = true;
1092
+ }
1093
+ }
1094
+ }
1095
+ // Clear removed flag for users being added back
1096
+ if (msg.dmFavorites) {
1097
+ for (var ai = 0; ai < msg.dmFavorites.length; ai++) {
1098
+ delete _ctx.dmRemovedUsers[msg.dmFavorites[ai]];
1099
+ }
1100
+ }
1101
+ _ctx.cachedDmFavorites = msg.dmFavorites || [];
1102
+ _ctx.renderUserStrip(_ctx.cachedAllUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
1103
+ break;
1104
+
1105
+ case "mate_created":
1106
+ _ctx.handleMateCreatedInApp(msg.mate, msg);
1107
+ break;
1108
+
1109
+ case "mate_deleted":
1110
+ _ctx.cachedMatesList = _ctx.cachedMatesList.filter(function (m) { return m.id !== msg.mateId; });
1111
+ if (msg.availableBuiltins) _ctx.cachedAvailableBuiltins = msg.availableBuiltins;
1112
+ _ctx.renderUserStrip(_ctx.cachedAllUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
1113
+ // If currently in DM with this mate, exit DM mode
1114
+ if (_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.id === msg.mateId) {
1115
+ _ctx.exitDmMode();
1116
+ }
1117
+ break;
1118
+
1119
+ case "mate_updated":
1120
+ if (msg.mate) {
1121
+ for (var mi = 0; mi < _ctx.cachedMatesList.length; mi++) {
1122
+ if (_ctx.cachedMatesList[mi].id === msg.mate.id) {
1123
+ _ctx.cachedMatesList[mi] = msg.mate;
1124
+ break;
1125
+ }
1126
+ }
1127
+ _ctx.renderUserStrip(_ctx.cachedAllUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
1128
+ // Update mate sidebar if currently viewing this mate
1129
+ if (_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate && _ctx.dmTargetUser.id === msg.mate.id) {
1130
+ _ctx.updateMateSidebarProfile(msg.mate);
1131
+ // Sync dmTargetUser so subsequent renders use fresh data
1132
+ var mp2 = msg.mate.profile || {};
1133
+ _ctx.dmTargetUser.displayName = mp2.displayName || msg.mate.name || _ctx.dmTargetUser.displayName;
1134
+ _ctx.dmTargetUser.avatarStyle = mp2.avatarStyle || _ctx.dmTargetUser.avatarStyle;
1135
+ _ctx.dmTargetUser.avatarSeed = mp2.avatarSeed || _ctx.dmTargetUser.avatarSeed;
1136
+ _ctx.dmTargetUser.avatarColor = mp2.avatarColor || _ctx.dmTargetUser.avatarColor;
1137
+ _ctx.dmTargetUser.avatarCustom = mp2.avatarCustom || "";
1138
+ _ctx.dmTargetUser.profile = mp2;
1139
+ // Refresh body dataset so new chat bubbles use the updated avatar
1140
+ document.body.dataset.mateAvatarUrl = _ctx.mateAvatarUrl(_ctx.dmTargetUser, 36);
1141
+ document.body.dataset.mateName = mp2.displayName || msg.mate.name || "";
1142
+ // Update existing chat bubble avatars
1143
+ var mateAvis = document.querySelectorAll(".dm-bubble-avatar-mate");
1144
+ for (var mbi = 0; mbi < mateAvis.length; mbi++) {
1145
+ mateAvis[mbi].src = document.body.dataset.mateAvatarUrl;
1146
+ }
1147
+ }
1148
+ // Update DM header if currently chatting with this mate
1149
+ if (_ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.id === msg.mate.id) {
1150
+ var updatedName = (msg.mate.profile && msg.mate.profile.displayName) || msg.mate.name;
1151
+ if (updatedName) {
1152
+ var dmHeaderName = document.getElementById("dm-header-name");
1153
+ if (dmHeaderName) dmHeaderName.textContent = updatedName;
1154
+ var dmInput = document.getElementById("dm-input");
1155
+ if (dmInput) dmInput.placeholder = "Message " + updatedName;
1156
+ }
1157
+ }
1158
+ }
1159
+ break;
1160
+
1161
+ case "mate_list":
1162
+ _ctx.cachedMatesList = msg.mates || [];
1163
+ _ctx.cachedAvailableBuiltins = msg.availableBuiltins || [];
1164
+ _ctx.renderUserStrip(_ctx.cachedAllUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
1165
+ break;
1166
+
1167
+ case "mate_available_builtins":
1168
+ // Handled via mate_list.availableBuiltins now
1169
+ break;
1170
+
1171
+ case "mate_error":
1172
+ _ctx.showToast(msg.error || "Mate operation failed", "error");
1173
+ break;
1174
+
1175
+ // --- @Mention ---
1176
+ case "mention_processing":
1177
+ // Broadcast: show/hide activity dot on mate avatar across all tabs
1178
+ if (msg.mateId) {
1179
+ var mateContainers = document.querySelectorAll('.icon-strip-mate[data-user-id="' + msg.mateId + '"]');
1180
+ for (var mi = 0; mi < mateContainers.length; mi++) {
1181
+ var dot = mateContainers[mi].querySelector(".icon-strip-status");
1182
+ if (msg.active) {
1183
+ if (dot) dot.classList.add("processing");
1184
+ mateContainers[mi].classList.add("mention-active");
1185
+ } else {
1186
+ if (dot) dot.classList.remove("processing");
1187
+ mateContainers[mi].classList.remove("mention-active");
1188
+ }
1189
+ }
1190
+ }
1191
+ break;
1192
+
1193
+ case "mention_start":
1194
+ _ctx.handleMentionStart(msg);
1195
+ break;
1196
+
1197
+ case "mention_activity":
1198
+ _ctx.handleMentionActivity(msg);
1199
+ break;
1200
+
1201
+ case "mention_stream":
1202
+ _ctx.handleMentionStream(msg);
1203
+ break;
1204
+
1205
+ case "mention_done":
1206
+ _ctx.handleMentionDone(msg);
1207
+ break;
1208
+
1209
+ case "mention_error":
1210
+ _ctx.handleMentionError(msg);
1211
+ if (msg.error) _ctx.showToast("@Mention: " + msg.error, "error");
1212
+ break;
1213
+
1214
+ case "mention_user":
1215
+ // Finalize current assistant block so mention renders in correct DOM position
1216
+ _ctx.finalizeAssistantBlock();
1217
+ _ctx.renderMentionUser(msg);
1218
+ break;
1219
+
1220
+ case "mention_response":
1221
+ _ctx.finalizeAssistantBlock();
1222
+ _ctx.renderMentionResponse(msg);
1223
+ break;
1224
+
1225
+ // --- Debate ---
1226
+ case "debate_preparing":
1227
+ if (!_ctx.replayingHistory) _ctx.showDebateSticky("preparing", msg);
1228
+ _ctx.handleDebatePreparing(msg);
1229
+ break;
1230
+
1231
+ case "debate_brief_ready":
1232
+ if (_ctx.replayingHistory) {
1233
+ _ctx.renderDebateBriefReady(msg);
1234
+ } else {
1235
+ _ctx.handleDebateBriefReady(msg);
1236
+ }
1237
+ break;
1238
+
1239
+ case "debate_started":
1240
+ if (!_ctx.replayingHistory) _ctx.showDebateSticky("live", msg);
1241
+ if (_ctx.replayingHistory) {
1242
+ _ctx.renderDebateStarted(msg);
1243
+ } else {
1244
+ _ctx.handleDebateStarted(msg);
1245
+ }
1246
+ break;
1247
+
1248
+ case "debate_turn":
1249
+ _ctx.handleDebateTurn(msg);
1250
+ if (msg.round) _ctx.updateDebateRound(msg.round);
1251
+ break;
1252
+
1253
+ case "debate_activity":
1254
+ _ctx.handleDebateActivity(msg);
1255
+ break;
1256
+
1257
+ case "debate_stream":
1258
+ _ctx.handleDebateStream(msg);
1259
+ break;
1260
+
1261
+ case "debate_turn_done":
1262
+ if (msg.round) _ctx.updateDebateRound(msg.round);
1263
+ _ctx.handleDebateTurnDone(msg);
1264
+ break;
1265
+
1266
+ case "debate_hand_raised":
1267
+ // Visual feedback: hand is raised, waiting for floor
1268
+ break;
1269
+
1270
+ case "debate_comment_queued":
1271
+ _ctx.handleDebateCommentQueued(msg);
1272
+ break;
1273
+
1274
+ case "debate_comment_injected":
1275
+ if (_ctx.replayingHistory) {
1276
+ _ctx.renderDebateCommentInjected(msg);
1277
+ } else {
1278
+ _ctx.handleDebateCommentInjected(msg);
1279
+ }
1280
+ break;
1281
+
1282
+ case "debate_conclude_confirm":
1283
+ if (!_ctx.replayingHistory) _ctx.showDebateConcludeConfirm(msg);
1284
+ break;
1285
+
1286
+ case "debate_user_floor":
1287
+ if (!_ctx.replayingHistory) _ctx.showDebateUserFloor(msg);
1288
+ break;
1289
+
1290
+ case "debate_user_floor_done":
1291
+ _ctx.renderDebateUserFloorDone(msg);
1292
+ break;
1293
+
1294
+ case "debate_user_resume":
1295
+ _ctx.renderDebateUserResume(msg);
1296
+ break;
1297
+
1298
+ case "debate_resumed":
1299
+ _ctx.handleDebateResumed(msg);
1300
+ if (!_ctx.replayingHistory) _ctx.showDebateSticky("live", msg);
1301
+ break;
1302
+
1303
+ case "debate_ended":
1304
+ if (!_ctx.replayingHistory) _ctx.showDebateSticky("ended", msg);
1305
+ if (_ctx.replayingHistory) {
1306
+ _ctx.renderDebateEnded(msg);
1307
+ } else {
1308
+ _ctx.handleDebateEnded(msg);
1309
+ }
1310
+ break;
1311
+
1312
+ case "debate_error":
1313
+ _ctx.handleDebateError(msg);
1314
+ if (msg.error) _ctx.showToast("Debate: " + msg.error, "error");
1315
+ break;
1316
+
1317
+ case "daemon_config":
1318
+ if (msg.config && msg.config.headless) _ctx.isHeadlessMode = true;
1319
+ _ctx.updateDaemonConfig(msg.config);
1320
+ break;
1321
+
1322
+ case "set_pin_result":
1323
+ _ctx.handleSetPinResult(msg);
1324
+ break;
1325
+
1326
+ case "set_keep_awake_result":
1327
+ _ctx.handleKeepAwakeChanged(msg);
1328
+ break;
1329
+
1330
+ case "keep_awake_changed":
1331
+ _ctx.handleKeepAwakeChanged(msg);
1332
+ break;
1333
+
1334
+ case "set_auto_continue_result":
1335
+ case "auto_continue_changed":
1336
+ _ctx.handleAutoContinueChanged(msg);
1337
+ break;
1338
+
1339
+ case "restart_server_result":
1340
+ _ctx.handleRestartResult(msg);
1341
+ break;
1342
+
1343
+ case "shutdown_server_result":
1344
+ _ctx.handleShutdownResult(msg);
1345
+ break;
1346
+
1347
+ // --- Ralph Loop ---
1348
+ case "loop_available":
1349
+ _ctx.loopAvailable = msg.available;
1350
+ _ctx.loopActive = msg.active;
1351
+ _ctx.loopIteration = msg.iteration || 0;
1352
+ _ctx.loopMaxIterations = msg.maxIterations || 20;
1353
+ _ctx.loopBannerName = msg.name || null;
1354
+ _ctx.updateLoopButton();
1355
+ if (_ctx.loopActive) {
1356
+ _ctx.showLoopBanner(true);
1357
+ if (_ctx.loopIteration > 0) {
1358
+ _ctx.updateLoopBanner(_ctx.loopIteration, _ctx.loopMaxIterations, "running");
1359
+ }
1360
+ _ctx.inputEl.disabled = true;
1361
+ _ctx.inputEl.placeholder = (_ctx.loopBannerName || "Loop") + " is running...";
1362
+ }
1363
+ break;
1364
+
1365
+ case "loop_started":
1366
+ _ctx.loopActive = true;
1367
+ _ctx.ralphPhase = "executing";
1368
+ _ctx.loopIteration = 0;
1369
+ _ctx.loopMaxIterations = msg.maxIterations;
1370
+ _ctx.loopBannerName = msg.name || null;
1371
+ _ctx.showLoopBanner(true);
1372
+ _ctx.updateLoopButton();
1373
+ _ctx.addSystemMessage((_ctx.loopBannerName || "Loop") + " started (max " + msg.maxIterations + " iterations)", false);
1374
+ _ctx.inputEl.disabled = true;
1375
+ _ctx.inputEl.placeholder = (_ctx.loopBannerName || "Loop") + " is running...";
1376
+ break;
1377
+
1378
+ case "loop_iteration":
1379
+ _ctx.loopIteration = msg.iteration;
1380
+ _ctx.loopMaxIterations = msg.maxIterations;
1381
+ _ctx.updateLoopBanner(msg.iteration, msg.maxIterations, "running");
1382
+ _ctx.updateLoopButton();
1383
+ _ctx.addSystemMessage((_ctx.loopBannerName || "Loop") + " iteration #" + msg.iteration + " started", false);
1384
+ _ctx.inputEl.disabled = true;
1385
+ _ctx.inputEl.placeholder = (_ctx.loopBannerName || "Loop") + " is running...";
1386
+ break;
1387
+
1388
+ case "loop_judging":
1389
+ _ctx.updateLoopBanner(_ctx.loopIteration, _ctx.loopMaxIterations, "judging");
1390
+ _ctx.addSystemMessage("Judging iteration #" + msg.iteration + "...", false);
1391
+ _ctx.inputEl.disabled = true;
1392
+ _ctx.inputEl.placeholder = (_ctx.loopBannerName || "Loop") + " is judging...";
1393
+ break;
1394
+
1395
+ case "loop_verdict":
1396
+ _ctx.addSystemMessage("Judge: " + msg.verdict.toUpperCase() + " - " + (msg.summary || ""), false);
1397
+ break;
1398
+
1399
+ case "loop_stopping":
1400
+ _ctx.updateLoopBanner(_ctx.loopIteration, _ctx.loopMaxIterations, "stopping");
1401
+ break;
1402
+
1403
+ case "loop_finished":
1404
+ _ctx.loopActive = false;
1405
+ _ctx.ralphPhase = "done";
1406
+ _ctx.loopBannerName = null;
1407
+ _ctx.showLoopBanner(false);
1408
+ _ctx.updateLoopButton();
1409
+ _ctx.enableMainInput();
1410
+ var loopLabel = _ctx.loopBannerName || "Loop";
1411
+ var finishMsg = msg.reason === "pass"
1412
+ ? loopLabel + " completed successfully after " + msg.iterations + " iteration(s)."
1413
+ : msg.reason === "max_iterations"
1414
+ ? loopLabel + " reached maximum iterations (" + msg.iterations + ")."
1415
+ : msg.reason === "stopped"
1416
+ ? loopLabel + " stopped."
1417
+ : loopLabel + " ended with error.";
1418
+ _ctx.addSystemMessage(finishMsg, false);
1419
+ break;
1420
+
1421
+ case "loop_error":
1422
+ _ctx.addSystemMessage((_ctx.loopBannerName || "Loop") + " error: " + msg.text, true);
1423
+ break;
1424
+
1425
+ // --- Ralph Wizard / Crafting ---
1426
+ case "ralph_phase":
1427
+ _ctx.ralphPhase = msg.phase || "idle";
1428
+ if (msg.craftingSessionId) _ctx.ralphCraftingSessionId = msg.craftingSessionId;
1429
+ if (msg.source !== undefined) _ctx.ralphCraftingSource = msg.source;
1430
+ _ctx.updateLoopButton();
1431
+ _ctx.updateRalphBars();
1432
+ break;
1433
+
1434
+ case "ralph_crafting_started":
1435
+ _ctx.ralphPhase = "crafting";
1436
+ _ctx.ralphCraftingSessionId = msg.sessionId || _ctx.activeSessionId;
1437
+ _ctx.ralphCraftingSource = msg.source || null;
1438
+ _ctx.updateLoopButton();
1439
+ _ctx.updateRalphBars();
1440
+ if (msg.source !== "ralph") {
1441
+ // Task sessions open in the scheduler calendar window
1442
+ _ctx.enterCraftingMode(msg.sessionId, msg.taskId);
1443
+ }
1444
+ // Ralph crafting sessions show in session list as part of the loop group
1445
+ break;
1446
+
1447
+ case "ralph_files_status":
1448
+ _ctx.ralphFilesReady = {
1449
+ promptReady: msg.promptReady,
1450
+ judgeReady: msg.judgeReady,
1451
+ bothReady: msg.bothReady,
1452
+ };
1453
+ if (msg.bothReady && (_ctx.ralphPhase === "crafting" || _ctx.ralphPhase === "approval")) {
1454
+ _ctx.ralphPhase = "approval";
1455
+ if (_ctx.ralphCraftingSource !== "ralph" || _ctx.isSchedulerOpen()) {
1456
+ // Task crafting in scheduler: switch from crafting chat to detail view showing files
1457
+ _ctx.exitCraftingMode(msg.taskId);
1458
+ } else {
1459
+ _ctx.showRalphApprovalBar(true);
1460
+ }
1461
+ }
1462
+ _ctx.updateRalphApprovalStatus();
1463
+ break;
1464
+
1465
+ case "loop_registry_files_content":
1466
+ _ctx.handleLoopRegistryFiles(msg);
1467
+ break;
1468
+
1469
+ case "ralph_files_content":
1470
+ _ctx.ralphPreviewContent = { prompt: msg.prompt || "", judge: msg.judge || "" };
1471
+ _ctx.openRalphPreviewModal();
1472
+ break;
1473
+
1474
+ case "loop_registry_error":
1475
+ _ctx.addSystemMessage("Error: " + msg.text, true);
1476
+ break;
1477
+ }
1478
+ }