clay-server 2.27.0-beta.9 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/project-connection.js +2 -0
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8517
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +389 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -0
- package/package.json +1 -1
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
// app-dm.js - DM mode, mate project switching, mate onboarding
|
|
2
|
+
// Extracted from app.js (PR-24)
|
|
3
|
+
|
|
4
|
+
var _ctx = null;
|
|
5
|
+
|
|
6
|
+
var MATE_ONBOARDING_KEY = "clay-mate-onboarding-shown";
|
|
7
|
+
var CLAUDE_CODE_AVATAR = "/claude-code-avatar.png";
|
|
8
|
+
var bgMateIoTimers = {};
|
|
9
|
+
var dmTypingTimer = null;
|
|
10
|
+
|
|
11
|
+
export function initDm(ctx) {
|
|
12
|
+
_ctx = ctx;
|
|
13
|
+
|
|
14
|
+
// --- Mobile mate title bar click handlers ---
|
|
15
|
+
var mobileBack = document.getElementById("mate-mobile-back");
|
|
16
|
+
var mobileTitle = document.getElementById("mate-mobile-title");
|
|
17
|
+
var mobileMore = document.getElementById("mate-mobile-more");
|
|
18
|
+
if (mobileBack) {
|
|
19
|
+
mobileBack.addEventListener("click", function (e) {
|
|
20
|
+
e.stopPropagation();
|
|
21
|
+
exitDmMode();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (mobileMore) {
|
|
25
|
+
mobileMore.addEventListener("click", function (e) {
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
_ctx.openMobileSheet("mate-profile");
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (mobileTitle) {
|
|
31
|
+
mobileTitle.addEventListener("click", function () {
|
|
32
|
+
_ctx.openMobileSheet("mate-profile");
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function openDm(targetUserId) {
|
|
38
|
+
if (!_ctx.ws || _ctx.ws.readyState !== 1) return;
|
|
39
|
+
// Persist DM state for refresh recovery
|
|
40
|
+
try { localStorage.setItem("clay-active-dm", targetUserId); } catch (e) {}
|
|
41
|
+
// Check mate skill updates before opening mate DM
|
|
42
|
+
if (typeof targetUserId === "string" && targetUserId.indexOf("mate_") === 0) {
|
|
43
|
+
showMateOnboarding(function () {
|
|
44
|
+
_ctx.requireClayMateInterview(function () {
|
|
45
|
+
_ctx.ws.send(JSON.stringify({ type: "dm_open", targetUserId: targetUserId }));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
_ctx.ws.send(JSON.stringify({ type: "dm_open", targetUserId: targetUserId }));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function showMateOnboarding(callback) {
|
|
54
|
+
try {
|
|
55
|
+
if (localStorage.getItem(MATE_ONBOARDING_KEY)) { callback(); return; }
|
|
56
|
+
} catch (e) {}
|
|
57
|
+
|
|
58
|
+
var overlay = document.createElement("div");
|
|
59
|
+
overlay.className = "mate-onboarding-overlay";
|
|
60
|
+
overlay.innerHTML =
|
|
61
|
+
'<div class="mate-onboarding-card">' +
|
|
62
|
+
'<h2 class="mate-onboarding-title">Meet your Mates</h2>' +
|
|
63
|
+
'<p class="mate-onboarding-desc">' +
|
|
64
|
+
'Mates are AI teammates powered by your Claude Code.<br>Each one has a distinct role, builds its own knowledge, and gets sharper over time.' +
|
|
65
|
+
'</p>' +
|
|
66
|
+
'<ul class="mate-onboarding-features">' +
|
|
67
|
+
'<li><span class="mate-onboarding-bullet">\uD83C\uDFAD</span><div><strong>Specialized personas</strong><br><span class="mate-onboarding-sub">Architect, reviewer, researcher, chief of staff, and more</span></div></li>' +
|
|
68
|
+
'<li><span class="mate-onboarding-bullet">\uD83D\uDD04</span><div><strong>Persistent memory</strong><br><span class="mate-onboarding-sub">Every conversation makes them smarter about you and your work</span></div></li>' +
|
|
69
|
+
'<li><span class="mate-onboarding-bullet">\uD83D\uDCAC</span><div><strong>Shared context across the team</strong><br><span class="mate-onboarding-sub">What one mate learns, the others can reference</span></div></li>' +
|
|
70
|
+
'<li><span class="mate-onboarding-bullet">\uD83D\uDCDA</span><div><strong>Self-growing knowledge base</strong><br><span class="mate-onboarding-sub">They accumulate notes, decisions, and observations on their own</span></div></li>' +
|
|
71
|
+
'</ul>' +
|
|
72
|
+
'<button class="mate-onboarding-btn">Let\u2019s go</button>' +
|
|
73
|
+
'</div>';
|
|
74
|
+
|
|
75
|
+
document.body.appendChild(overlay);
|
|
76
|
+
|
|
77
|
+
// Animate in
|
|
78
|
+
requestAnimationFrame(function () {
|
|
79
|
+
overlay.classList.add("visible");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function dismissOnboarding() {
|
|
83
|
+
try { localStorage.setItem(MATE_ONBOARDING_KEY, "1"); } catch (e) {}
|
|
84
|
+
fetch("/api/user/mate-onboarded", { method: "POST" }).catch(function () {});
|
|
85
|
+
overlay.classList.remove("visible");
|
|
86
|
+
setTimeout(function () { overlay.remove(); callback(); }, 200);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
overlay.querySelector(".mate-onboarding-btn").addEventListener("click", dismissOnboarding);
|
|
90
|
+
|
|
91
|
+
// Click outside to dismiss
|
|
92
|
+
overlay.addEventListener("click", function (e) {
|
|
93
|
+
if (e.target === overlay) dismissOnboarding();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function enterDmMode(key, targetUser, messages) {
|
|
98
|
+
console.log("[DEBUG enterDmMode] key=" + key, "isMate=" + (targetUser && targetUser.isMate), "messages=" + (messages ? messages.length : 0));
|
|
99
|
+
// Clean up previous DM/mate state before entering new one
|
|
100
|
+
if (_ctx.dmMode) {
|
|
101
|
+
_ctx.hideMateSidebar();
|
|
102
|
+
_ctx.hideKnowledge();
|
|
103
|
+
_ctx.hideMemory();
|
|
104
|
+
// Reset dm-header-bar
|
|
105
|
+
var prevHeader = document.getElementById("dm-header-bar");
|
|
106
|
+
if (prevHeader) {
|
|
107
|
+
prevHeader.style.display = "";
|
|
108
|
+
prevHeader.style.background = "";
|
|
109
|
+
var prevTag = prevHeader.querySelector(".dm-header-mate-tag");
|
|
110
|
+
if (prevTag) prevTag.remove();
|
|
111
|
+
}
|
|
112
|
+
// Restore terminal button
|
|
113
|
+
var prevTermBtn = document.getElementById("terminal-toggle-btn");
|
|
114
|
+
if (prevTermBtn) prevTermBtn.style.display = "";
|
|
115
|
+
// Remove dm-mode classes
|
|
116
|
+
var prevMain = document.getElementById("main-column");
|
|
117
|
+
if (prevMain) prevMain.classList.remove("dm-mode");
|
|
118
|
+
var prevSidebar = document.getElementById("sidebar-column");
|
|
119
|
+
if (prevSidebar) prevSidebar.classList.remove("dm-mode");
|
|
120
|
+
var prevResize = document.getElementById("sidebar-resize-handle");
|
|
121
|
+
if (prevResize) prevResize.classList.remove("dm-mode");
|
|
122
|
+
// Reset chat title bar
|
|
123
|
+
var prevTitleBar = document.querySelector(".title-bar-content");
|
|
124
|
+
if (prevTitleBar) {
|
|
125
|
+
prevTitleBar.style.background = "";
|
|
126
|
+
prevTitleBar.classList.remove("mate-dm-active");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_ctx.dmMode = true;
|
|
131
|
+
_ctx.dmKey = key;
|
|
132
|
+
_ctx.dmTargetUser = targetUser;
|
|
133
|
+
|
|
134
|
+
// Notify server of active mate DM (server-side presence tracking)
|
|
135
|
+
// IMPORTANT: set_mate_dm must go to the MAIN project, not a mate project WS.
|
|
136
|
+
// When switching between mates, ws points to the current mate project,
|
|
137
|
+
// so we defer sending set_mate_dm until we reconnect to the main project's context.
|
|
138
|
+
// The server will also receive it via the mate project's onDmMessage handler,
|
|
139
|
+
// but the presence should only be stored on the main project slug.
|
|
140
|
+
if (targetUser && targetUser.isMate) {
|
|
141
|
+
// Send to the current WS only if it's the main project (not another mate)
|
|
142
|
+
if (!_ctx.mateProjectSlug && _ctx.ws && _ctx.ws.readyState === 1) {
|
|
143
|
+
try { _ctx.ws.send(JSON.stringify({ type: "set_mate_dm", mateId: targetUser.id })); } catch(e) {}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Clear unread for this user
|
|
148
|
+
if (targetUser) {
|
|
149
|
+
_ctx.dmUnread[targetUser.id] = 0;
|
|
150
|
+
_ctx.updateDmBadge(targetUser.id, 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Update icon strip active state
|
|
154
|
+
_ctx.setCurrentDmUser(targetUser ? targetUser.id : null);
|
|
155
|
+
var activeProj = document.querySelector("#icon-strip-projects .icon-strip-item.active");
|
|
156
|
+
if (activeProj) activeProj.classList.remove("active");
|
|
157
|
+
var homeIcon = document.querySelector(".icon-strip-home");
|
|
158
|
+
if (homeIcon) homeIcon.classList.remove("active");
|
|
159
|
+
// Re-render user strip to show active state
|
|
160
|
+
if (_ctx.cachedProjects && _ctx.cachedProjects.length > 0) {
|
|
161
|
+
_ctx.renderProjectList();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Hide home hub if visible
|
|
165
|
+
_ctx.hideHomeHub();
|
|
166
|
+
|
|
167
|
+
// Hide sticky notes if visible
|
|
168
|
+
_ctx.hideNotes();
|
|
169
|
+
|
|
170
|
+
var isMate = targetUser && targetUser.isMate;
|
|
171
|
+
|
|
172
|
+
// Hide project UI + sidebar, show DM UI
|
|
173
|
+
var mainCol = document.getElementById("main-column");
|
|
174
|
+
if (mainCol && !isMate) mainCol.classList.add("dm-mode");
|
|
175
|
+
var sidebarCol = document.getElementById("sidebar-column");
|
|
176
|
+
if (sidebarCol) sidebarCol.classList.add("dm-mode");
|
|
177
|
+
var resizeHandle = document.getElementById("sidebar-resize-handle");
|
|
178
|
+
if (resizeHandle) resizeHandle.classList.add("dm-mode");
|
|
179
|
+
if (isMate && targetUser.projectSlug) {
|
|
180
|
+
// Mate DM: switch to mate's project (same as project switching)
|
|
181
|
+
_ctx.showMateSidebar(targetUser.id, targetUser);
|
|
182
|
+
// Close file viewer and terminal panel BEFORE switching WS (needs old WS still open)
|
|
183
|
+
try { _ctx.closeFileViewer(); } catch(e) {}
|
|
184
|
+
_ctx.closeTerminal();
|
|
185
|
+
var termBtn = document.getElementById("terminal-toggle-btn");
|
|
186
|
+
if (termBtn) termBtn.style.display = "none";
|
|
187
|
+
// Apply mate color to chat title bar and panels
|
|
188
|
+
var mateColor = (targetUser.profile && targetUser.profile.avatarColor) || targetUser.avatarColor || "#7c3aed";
|
|
189
|
+
document.body.style.setProperty("--mate-color", mateColor);
|
|
190
|
+
document.body.style.setProperty("--mate-color-tint", mateColor + "0a");
|
|
191
|
+
document.body.classList.add("mate-dm-active");
|
|
192
|
+
// Build mate avatar URL for DM bubble injection
|
|
193
|
+
var mp = targetUser.profile || {};
|
|
194
|
+
var mateAvUrlDm = _ctx.mateAvatarUrl(targetUser, 36);
|
|
195
|
+
var myUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
|
|
196
|
+
if (!myUser) {
|
|
197
|
+
try { var cached = JSON.parse(localStorage.getItem("clay_my_user") || "null"); if (cached) myUser = cached; } catch(e) {}
|
|
198
|
+
}
|
|
199
|
+
var myAvatarUrl = _ctx.userAvatarUrl(myUser || { id: _ctx.myUserId }, 36);
|
|
200
|
+
var myDisplayName = (myUser && myUser.displayName) || "";
|
|
201
|
+
document.body.dataset.mateAvatarUrl = mateAvUrlDm;
|
|
202
|
+
document.body.dataset.mateName = mp.displayName || targetUser.displayName || targetUser.name || "";
|
|
203
|
+
document.body.dataset.myAvatarUrl = myAvatarUrl;
|
|
204
|
+
document.body.dataset.myDisplayName = myDisplayName;
|
|
205
|
+
// Cache my info for restore after hard refresh
|
|
206
|
+
if (myUser) {
|
|
207
|
+
try { localStorage.setItem("clay_my_user", JSON.stringify({ displayName: myUser.displayName, avatarStyle: myUser.avatarStyle, avatarSeed: myUser.avatarSeed, avatarCustom: myUser.avatarCustom, username: myUser.username })); } catch(e) {}
|
|
208
|
+
}
|
|
209
|
+
var titleBarContent = document.querySelector(".title-bar-content");
|
|
210
|
+
if (titleBarContent) {
|
|
211
|
+
titleBarContent.style.background = mateColor;
|
|
212
|
+
titleBarContent.classList.add("mate-dm-active");
|
|
213
|
+
}
|
|
214
|
+
// Populate mobile title bar for mate DM (CSS handles visibility via media query)
|
|
215
|
+
var mateMobileTitle = document.getElementById("mate-mobile-title");
|
|
216
|
+
if (mateMobileTitle) {
|
|
217
|
+
var mateMobileAvatar = document.getElementById("mate-mobile-avatar");
|
|
218
|
+
var mateMobileName = document.getElementById("mate-mobile-name");
|
|
219
|
+
var mateMobileStatus = document.getElementById("mate-mobile-status");
|
|
220
|
+
if (mateMobileAvatar) mateMobileAvatar.src = mateAvUrlDm;
|
|
221
|
+
if (mateMobileName) mateMobileName.textContent = (mp.displayName || targetUser.displayName || targetUser.name || "");
|
|
222
|
+
if (mateMobileStatus) mateMobileStatus.textContent = "online";
|
|
223
|
+
mateMobileTitle.classList.remove("hidden");
|
|
224
|
+
// Store mate data for profile sheet
|
|
225
|
+
_ctx.setMobileSheetMateData({
|
|
226
|
+
id: targetUser.id,
|
|
227
|
+
displayName: mp.displayName || targetUser.displayName || targetUser.name || "",
|
|
228
|
+
description: mp.description || targetUser.description || "",
|
|
229
|
+
avatarUrl: mateAvUrlDm,
|
|
230
|
+
color: mateColor
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
// Switch to mate project WS LAST, after all UI setup is complete.
|
|
234
|
+
// Must be last because connect() changes ws to CONNECTING state,
|
|
235
|
+
// and earlier code (closeFileViewer etc.) needs the old WS still open.
|
|
236
|
+
connectMateProject(targetUser.projectSlug);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Hide user-island in human DM, keep visible in Mate DM
|
|
240
|
+
var userIsland = document.getElementById("user-island");
|
|
241
|
+
if (userIsland && !isMate) userIsland.classList.add("dm-hidden");
|
|
242
|
+
|
|
243
|
+
// Render DM messages
|
|
244
|
+
_ctx.dmMessageCache = messages ? messages.slice() : [];
|
|
245
|
+
_ctx.messagesEl.innerHTML = "";
|
|
246
|
+
if (messages && messages.length > 0) {
|
|
247
|
+
for (var i = 0; i < messages.length; i++) {
|
|
248
|
+
appendDmMessage(messages[i]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
_ctx.scrollToBottom();
|
|
252
|
+
|
|
253
|
+
// Focus input
|
|
254
|
+
if (_ctx.inputEl) {
|
|
255
|
+
var targetName = targetUser ? ((targetUser.profile && targetUser.profile.displayName) || targetUser.displayName || targetUser.name || "") : "";
|
|
256
|
+
_ctx.inputEl.placeholder = "Message " + targetName;
|
|
257
|
+
_ctx.inputEl.focus();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Populate DM header bar with user avatar, name, and personal color
|
|
261
|
+
if (targetUser) {
|
|
262
|
+
var dmHeaderBar = document.getElementById("dm-header-bar");
|
|
263
|
+
var dmAvatar = document.getElementById("dm-header-avatar");
|
|
264
|
+
var dmName = document.getElementById("dm-header-name");
|
|
265
|
+
if (isMate) {
|
|
266
|
+
// Mate uses project chat title bar, hide DM header
|
|
267
|
+
if (dmHeaderBar) dmHeaderBar.style.display = "none";
|
|
268
|
+
} else {
|
|
269
|
+
if (dmHeaderBar) dmHeaderBar.style.display = "";
|
|
270
|
+
if (dmAvatar) {
|
|
271
|
+
dmAvatar.src = _ctx.userAvatarUrl(targetUser, 28);
|
|
272
|
+
}
|
|
273
|
+
if (dmName) dmName.textContent = targetUser.displayName;
|
|
274
|
+
if (dmHeaderBar && targetUser.avatarColor) {
|
|
275
|
+
dmHeaderBar.style.background = targetUser.avatarColor;
|
|
276
|
+
}
|
|
277
|
+
// Remove mate tag for regular DM
|
|
278
|
+
var existingTag = dmHeaderBar ? dmHeaderBar.querySelector(".dm-header-mate-tag") : null;
|
|
279
|
+
if (existingTag) existingTag.remove();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function exitDmMode(skipProjectSwitch) {
|
|
285
|
+
if (!_ctx.dmMode) return;
|
|
286
|
+
var wasMate = _ctx.dmTargetUser && _ctx.dmTargetUser.isMate;
|
|
287
|
+
_ctx.dmMode = false;
|
|
288
|
+
_ctx.dmKey = null;
|
|
289
|
+
_ctx.dmTargetUser = null;
|
|
290
|
+
try { localStorage.removeItem("clay-active-dm"); } catch (e) {}
|
|
291
|
+
_ctx.setCurrentDmUser(null);
|
|
292
|
+
|
|
293
|
+
var mainCol = document.getElementById("main-column");
|
|
294
|
+
if (mainCol) mainCol.classList.remove("dm-mode");
|
|
295
|
+
var sidebarCol = document.getElementById("sidebar-column");
|
|
296
|
+
if (sidebarCol) sidebarCol.classList.remove("dm-mode");
|
|
297
|
+
var resizeHandle = document.getElementById("sidebar-resize-handle");
|
|
298
|
+
if (resizeHandle) resizeHandle.classList.remove("dm-mode");
|
|
299
|
+
_ctx.hideMateSidebar();
|
|
300
|
+
_ctx.hideKnowledge();
|
|
301
|
+
_ctx.hideMemory();
|
|
302
|
+
if (_ctx.isSchedulerOpen()) _ctx.closeScheduler();
|
|
303
|
+
// Restore terminal button
|
|
304
|
+
var termBtn = document.getElementById("terminal-toggle-btn");
|
|
305
|
+
if (termBtn) termBtn.style.display = "";
|
|
306
|
+
|
|
307
|
+
// Reset DM header
|
|
308
|
+
var dmHeaderBar = document.getElementById("dm-header-bar");
|
|
309
|
+
if (dmHeaderBar) {
|
|
310
|
+
dmHeaderBar.style.display = "";
|
|
311
|
+
dmHeaderBar.style.background = "";
|
|
312
|
+
var mateTag = dmHeaderBar.querySelector(".dm-header-mate-tag");
|
|
313
|
+
if (mateTag) mateTag.remove();
|
|
314
|
+
}
|
|
315
|
+
// Reset chat title bar and mate color
|
|
316
|
+
document.body.style.removeProperty("--mate-color");
|
|
317
|
+
document.body.style.removeProperty("--mate-color-tint");
|
|
318
|
+
document.body.classList.remove("mate-dm-active");
|
|
319
|
+
delete document.body.dataset.mateAvatarUrl;
|
|
320
|
+
delete document.body.dataset.mateName;
|
|
321
|
+
delete document.body.dataset.myAvatarUrl;
|
|
322
|
+
// Remove injected DM bubble avatars
|
|
323
|
+
var bubbleAvatars = _ctx.messagesEl.querySelectorAll(".dm-bubble-avatar");
|
|
324
|
+
for (var ba = 0; ba < bubbleAvatars.length; ba++) bubbleAvatars[ba].remove();
|
|
325
|
+
var titleBarContent = document.querySelector(".title-bar-content");
|
|
326
|
+
if (titleBarContent) {
|
|
327
|
+
titleBarContent.style.background = "";
|
|
328
|
+
titleBarContent.classList.remove("mate-dm-active");
|
|
329
|
+
}
|
|
330
|
+
// Hide mobile mate title bar
|
|
331
|
+
var mateMobileTitle = document.getElementById("mate-mobile-title");
|
|
332
|
+
if (mateMobileTitle) mateMobileTitle.classList.add("hidden");
|
|
333
|
+
|
|
334
|
+
// Restore user-island (covers my avatar again)
|
|
335
|
+
var userIsland = document.getElementById("user-island");
|
|
336
|
+
if (userIsland) userIsland.classList.remove("dm-hidden");
|
|
337
|
+
|
|
338
|
+
if (_ctx.inputEl) _ctx.inputEl.placeholder = "";
|
|
339
|
+
|
|
340
|
+
// Switch back to main project (same as project switching)
|
|
341
|
+
if (wasMate && !skipProjectSwitch) {
|
|
342
|
+
disconnectMateProject();
|
|
343
|
+
} else if (wasMate && skipProjectSwitch) {
|
|
344
|
+
// Just clean up mate state, caller will handle project switch
|
|
345
|
+
_ctx.returningFromMateDm = true;
|
|
346
|
+
_ctx.mateProjectSlug = null;
|
|
347
|
+
_ctx.savedMainSlug = null;
|
|
348
|
+
_ctx.showDebateSticky("hide", null);
|
|
349
|
+
var debateFloat = document.getElementById("debate-info-float");
|
|
350
|
+
if (debateFloat) { debateFloat.classList.add("hidden"); debateFloat.innerHTML = ""; }
|
|
351
|
+
} else {
|
|
352
|
+
// Human DM: just re-request state from main project
|
|
353
|
+
if (_ctx.ws && _ctx.ws.readyState === 1) {
|
|
354
|
+
_ctx.ws.send(JSON.stringify({ type: "switch_session", id: _ctx.activeSessionId }));
|
|
355
|
+
_ctx.ws.send(JSON.stringify({ type: "note_list_request" }));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
_ctx.renderProjectList();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function handleMateCreatedInApp(mate, msg) {
|
|
362
|
+
if (!mate) return;
|
|
363
|
+
_ctx.cachedMatesList.push(mate);
|
|
364
|
+
if (msg && msg.availableBuiltins) _ctx.cachedAvailableBuiltins = msg.availableBuiltins;
|
|
365
|
+
if (msg && msg.dmFavorites) _ctx.cachedDmFavorites = msg.dmFavorites;
|
|
366
|
+
_ctx.renderUserStrip(_ctx.cachedAllUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
|
|
367
|
+
// Built-in mates handle their own onboarding via CLAUDE.md, skip auto-interview
|
|
368
|
+
if (!mate.builtinKey) {
|
|
369
|
+
_ctx.pendingMateInterview = mate;
|
|
370
|
+
}
|
|
371
|
+
openDm(mate.id);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function renderAvailableBuiltins(builtins) {
|
|
375
|
+
// Append deleted built-in mates to the mates list in the picker
|
|
376
|
+
var matesList = document.querySelector(".dm-mates-list");
|
|
377
|
+
if (!matesList) return;
|
|
378
|
+
if (!builtins || builtins.length === 0) return;
|
|
379
|
+
|
|
380
|
+
for (var i = 0; i < builtins.length; i++) {
|
|
381
|
+
(function (b) {
|
|
382
|
+
var item = document.createElement("div");
|
|
383
|
+
item.className = "dm-user-picker-item dm-user-picker-builtin-item";
|
|
384
|
+
item.style.opacity = "0.5";
|
|
385
|
+
|
|
386
|
+
var av = document.createElement("img");
|
|
387
|
+
av.className = "dm-user-picker-avatar";
|
|
388
|
+
av.src = b.avatarCustom || "";
|
|
389
|
+
av.alt = b.displayName;
|
|
390
|
+
item.appendChild(av);
|
|
391
|
+
|
|
392
|
+
var nameWrap = document.createElement("div");
|
|
393
|
+
nameWrap.style.cssText = "flex:1;min-width:0;";
|
|
394
|
+
var nameEl = document.createElement("span");
|
|
395
|
+
nameEl.className = "dm-user-picker-name";
|
|
396
|
+
nameEl.textContent = b.displayName;
|
|
397
|
+
nameWrap.appendChild(nameEl);
|
|
398
|
+
var bioEl = document.createElement("div");
|
|
399
|
+
bioEl.style.cssText = "font-size:11px;color:var(--text-dimmer);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
400
|
+
bioEl.textContent = "Deleted";
|
|
401
|
+
nameWrap.appendChild(bioEl);
|
|
402
|
+
item.appendChild(nameWrap);
|
|
403
|
+
|
|
404
|
+
var addBtn = document.createElement("button");
|
|
405
|
+
addBtn.style.cssText = "border:none;background:none;cursor:pointer;padding:2px 6px;color:var(--accent, #6366f1);font-size:12px;font-weight:600;";
|
|
406
|
+
addBtn.textContent = "+ Add";
|
|
407
|
+
addBtn.title = "Re-add " + b.displayName;
|
|
408
|
+
addBtn.addEventListener("click", function (e) {
|
|
409
|
+
e.stopPropagation();
|
|
410
|
+
if (_ctx.ws && _ctx.ws.readyState === 1) {
|
|
411
|
+
_ctx.ws.send(JSON.stringify({ type: "mate_readd_builtin", builtinKey: b.key }));
|
|
412
|
+
}
|
|
413
|
+
_ctx.closeDmUserPicker();
|
|
414
|
+
});
|
|
415
|
+
item.appendChild(addBtn);
|
|
416
|
+
|
|
417
|
+
item.addEventListener("click", function () {
|
|
418
|
+
if (_ctx.ws && _ctx.ws.readyState === 1) {
|
|
419
|
+
_ctx.ws.send(JSON.stringify({ type: "mate_readd_builtin", builtinKey: b.key }));
|
|
420
|
+
}
|
|
421
|
+
_ctx.closeDmUserPicker();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
matesList.appendChild(item);
|
|
425
|
+
})(builtins[i]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function buildMateInterviewPrompt(mate) {
|
|
430
|
+
var sd = mate.seedData || {};
|
|
431
|
+
var parts = [];
|
|
432
|
+
var spokenLang = _ctx.getProfileLang() || "en-US";
|
|
433
|
+
parts.push("Spoken Language: " + spokenLang);
|
|
434
|
+
if (sd.relationship) parts.push("Relationship: " + sd.relationship);
|
|
435
|
+
if (sd.activity && sd.activity.length > 0) parts.push("Activities: " + sd.activity.join(", "));
|
|
436
|
+
if (sd.communicationStyle && sd.communicationStyle.length > 0) {
|
|
437
|
+
var styleLabels = {
|
|
438
|
+
direct_concise: "direct and concise",
|
|
439
|
+
soft_detailed: "soft and detailed",
|
|
440
|
+
witty: "witty",
|
|
441
|
+
encouraging: "encouraging",
|
|
442
|
+
formal: "formal",
|
|
443
|
+
no_nonsense: "no-nonsense",
|
|
444
|
+
};
|
|
445
|
+
var styles = sd.communicationStyle.map(function (s) { return styleLabels[s] || s.replace(/_/g, " "); });
|
|
446
|
+
parts.push("Communication: " + styles.join(", "));
|
|
447
|
+
}
|
|
448
|
+
if (sd.autonomy) parts.push("Autonomy: " + sd.autonomy.replace(/_/g, " "));
|
|
449
|
+
|
|
450
|
+
return "Use the /clay-mate-interview skill to start the interview.\n\n" +
|
|
451
|
+
"Mate ID: " + mate.id + "\n" +
|
|
452
|
+
"Mate Directory: .claude/mates/" + mate.id + "\n\n" +
|
|
453
|
+
"Seed Data:\n" + parts.join("\n");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function updateMateIconStatus(msg) {
|
|
457
|
+
if (!_ctx.mateProjectSlug) return;
|
|
458
|
+
var slug = _ctx.mateProjectSlug;
|
|
459
|
+
if (msg.type === "content" || msg.type === "tool" || msg.type === "tool_use" || msg.type === "thinking") {
|
|
460
|
+
var ioDot = document.querySelector('.icon-strip-mate[data-mate-slug="' + slug + '"] .icon-strip-status');
|
|
461
|
+
if (ioDot) {
|
|
462
|
+
ioDot.classList.add("io");
|
|
463
|
+
clearTimeout(bgMateIoTimers[slug]);
|
|
464
|
+
bgMateIoTimers[slug] = setTimeout(function () { ioDot.classList.remove("io"); }, 80);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (msg.type === "status" && msg.status === "processing") {
|
|
468
|
+
var dot = document.querySelector('.icon-strip-mate[data-mate-slug="' + slug + '"] .icon-strip-status');
|
|
469
|
+
if (dot) dot.classList.add("processing");
|
|
470
|
+
var mateSessionDot = document.querySelector(".mate-session-item.active .session-processing");
|
|
471
|
+
if (mateSessionDot) mateSessionDot.style.display = "";
|
|
472
|
+
}
|
|
473
|
+
if (msg.type === "done") {
|
|
474
|
+
var dot = document.querySelector('.icon-strip-mate[data-mate-slug="' + slug + '"] .icon-strip-status');
|
|
475
|
+
if (dot) dot.classList.remove("processing");
|
|
476
|
+
var mateSessionDot = document.querySelector(".mate-session-item.active .session-processing");
|
|
477
|
+
if (mateSessionDot) mateSessionDot.style.display = "none";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function connectMateProject(slug) {
|
|
482
|
+
_ctx.mateProjectSlug = slug;
|
|
483
|
+
// Only save the main slug on the FIRST mate switch (preserve original main project)
|
|
484
|
+
if (!_ctx.savedMainSlug) _ctx.savedMainSlug = _ctx.currentSlug;
|
|
485
|
+
_ctx.currentSlug = slug;
|
|
486
|
+
_ctx.wsPath = "/p/" + slug + "/ws";
|
|
487
|
+
_ctx.resetClientState();
|
|
488
|
+
_ctx.connect();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function disconnectMateProject() {
|
|
492
|
+
_ctx.mateProjectSlug = null;
|
|
493
|
+
// Hide debate sticky when leaving mate DM
|
|
494
|
+
_ctx.showDebateSticky("hide", null);
|
|
495
|
+
// Hide debate info float
|
|
496
|
+
var debateFloat = document.getElementById("debate-info-float");
|
|
497
|
+
if (debateFloat) { debateFloat.classList.add("hidden"); debateFloat.innerHTML = ""; }
|
|
498
|
+
// Switch back to main project
|
|
499
|
+
if (_ctx.savedMainSlug) {
|
|
500
|
+
_ctx.returningFromMateDm = true;
|
|
501
|
+
_ctx.currentSlug = _ctx.savedMainSlug;
|
|
502
|
+
_ctx.basePath = "/p/" + _ctx.savedMainSlug + "/";
|
|
503
|
+
_ctx.wsPath = "/p/" + _ctx.savedMainSlug + "/ws";
|
|
504
|
+
_ctx.savedMainSlug = null;
|
|
505
|
+
_ctx.resetClientState();
|
|
506
|
+
_ctx.connect();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function appendDmMessage(msg) {
|
|
511
|
+
if (_ctx.dmMode) _ctx.dmMessageCache.push(msg);
|
|
512
|
+
var isMe = msg.from === _ctx.myUserId;
|
|
513
|
+
var d = new Date(msg.ts);
|
|
514
|
+
var timeStr = d.getHours().toString().padStart(2, "0") + ":" + d.getMinutes().toString().padStart(2, "0");
|
|
515
|
+
|
|
516
|
+
// Check if we can compact (same sender as previous, within 5 min)
|
|
517
|
+
var prev = _ctx.messagesEl.lastElementChild;
|
|
518
|
+
var compact = false;
|
|
519
|
+
if (prev && prev.dataset.from === msg.from) {
|
|
520
|
+
var prevTs = parseInt(prev.dataset.ts || "0", 10);
|
|
521
|
+
if (msg.ts - prevTs < 300000) compact = true;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
var div = document.createElement("div");
|
|
525
|
+
div.className = "dm-msg" + (compact ? " dm-msg-compact" : "");
|
|
526
|
+
div.dataset.from = msg.from;
|
|
527
|
+
div.dataset.ts = msg.ts;
|
|
528
|
+
|
|
529
|
+
if (compact) {
|
|
530
|
+
// Compact: just hover-time + text, no avatar/name
|
|
531
|
+
var hoverTime = document.createElement("span");
|
|
532
|
+
hoverTime.className = "dm-msg-hover-time";
|
|
533
|
+
hoverTime.textContent = timeStr;
|
|
534
|
+
div.appendChild(hoverTime);
|
|
535
|
+
|
|
536
|
+
var body = document.createElement("div");
|
|
537
|
+
body.className = "dm-msg-body";
|
|
538
|
+
body.textContent = msg.text;
|
|
539
|
+
div.appendChild(body);
|
|
540
|
+
} else {
|
|
541
|
+
// Full: avatar + header(name, time) + text
|
|
542
|
+
var avatar = document.createElement("img");
|
|
543
|
+
avatar.className = "dm-msg-avatar";
|
|
544
|
+
if (isMe) {
|
|
545
|
+
var myUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
|
|
546
|
+
avatar.src = _ctx.userAvatarUrl(myUser || { id: _ctx.myUserId }, 36);
|
|
547
|
+
} else if (_ctx.dmTargetUser) {
|
|
548
|
+
avatar.src = _ctx.userAvatarUrl(_ctx.dmTargetUser, 36);
|
|
549
|
+
}
|
|
550
|
+
div.appendChild(avatar);
|
|
551
|
+
|
|
552
|
+
var content = document.createElement("div");
|
|
553
|
+
content.className = "dm-msg-content";
|
|
554
|
+
|
|
555
|
+
var header = document.createElement("div");
|
|
556
|
+
header.className = "dm-msg-header";
|
|
557
|
+
|
|
558
|
+
var name = document.createElement("span");
|
|
559
|
+
name.className = "dm-msg-name";
|
|
560
|
+
if (isMe) {
|
|
561
|
+
var mu = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
|
|
562
|
+
name.textContent = mu ? mu.displayName : "Me";
|
|
563
|
+
} else {
|
|
564
|
+
name.textContent = _ctx.dmTargetUser ? _ctx.dmTargetUser.displayName : "User";
|
|
565
|
+
}
|
|
566
|
+
header.appendChild(name);
|
|
567
|
+
|
|
568
|
+
var time = document.createElement("span");
|
|
569
|
+
time.className = "dm-msg-time";
|
|
570
|
+
time.textContent = timeStr;
|
|
571
|
+
header.appendChild(time);
|
|
572
|
+
|
|
573
|
+
content.appendChild(header);
|
|
574
|
+
|
|
575
|
+
var body = document.createElement("div");
|
|
576
|
+
body.className = "dm-msg-body";
|
|
577
|
+
body.textContent = msg.text;
|
|
578
|
+
content.appendChild(body);
|
|
579
|
+
|
|
580
|
+
div.appendChild(content);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
_ctx.messagesEl.appendChild(div);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function showDmTypingIndicator(typing) {
|
|
587
|
+
var existing = document.getElementById("dm-typing-indicator");
|
|
588
|
+
if (!typing) {
|
|
589
|
+
if (existing) existing.remove();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (existing) return; // already showing
|
|
593
|
+
if (!_ctx.dmTargetUser) return;
|
|
594
|
+
|
|
595
|
+
var div = document.createElement("div");
|
|
596
|
+
div.id = "dm-typing-indicator";
|
|
597
|
+
div.className = "dm-msg dm-typing-indicator";
|
|
598
|
+
|
|
599
|
+
var avatar = document.createElement("img");
|
|
600
|
+
avatar.className = "dm-msg-avatar";
|
|
601
|
+
avatar.src = _ctx.userAvatarUrl(_ctx.dmTargetUser, 36);
|
|
602
|
+
div.appendChild(avatar);
|
|
603
|
+
|
|
604
|
+
var dots = document.createElement("div");
|
|
605
|
+
dots.className = "dm-typing-dots";
|
|
606
|
+
dots.innerHTML = "<span></span><span></span><span></span>";
|
|
607
|
+
div.appendChild(dots);
|
|
608
|
+
|
|
609
|
+
_ctx.messagesEl.appendChild(div);
|
|
610
|
+
_ctx.scrollToBottom();
|
|
611
|
+
|
|
612
|
+
// Auto-hide after 5s in case stop signal is missed
|
|
613
|
+
clearTimeout(dmTypingTimer);
|
|
614
|
+
dmTypingTimer = setTimeout(function () {
|
|
615
|
+
showDmTypingIndicator(false);
|
|
616
|
+
}, 5000);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function handleDmSend() {
|
|
620
|
+
if (!_ctx.dmMode || !_ctx.dmKey || !_ctx.inputEl) return false;
|
|
621
|
+
var text = _ctx.inputEl.value.trim();
|
|
622
|
+
if (!text) return false;
|
|
623
|
+
_ctx.ws.send(JSON.stringify({ type: "dm_send", dmKey: _ctx.dmKey, text: text }));
|
|
624
|
+
_ctx.inputEl.value = "";
|
|
625
|
+
_ctx.autoResize();
|
|
626
|
+
return true;
|
|
627
|
+
}
|