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.
Files changed (71) hide show
  1. package/README.md +10 -0
  2. package/lib/daemon-projects.js +164 -0
  3. package/lib/daemon.js +13 -126
  4. package/lib/mates-identity.js +132 -0
  5. package/lib/mates-knowledge.js +113 -0
  6. package/lib/mates-prompts.js +398 -0
  7. package/lib/mates.js +40 -599
  8. package/lib/project-connection.js +2 -0
  9. package/lib/project-http.js +4 -2
  10. package/lib/project-loop.js +110 -48
  11. package/lib/project-mate-interaction.js +4 -0
  12. package/lib/project-notifications.js +210 -0
  13. package/lib/project-sessions.js +5 -2
  14. package/lib/project-user-message.js +2 -1
  15. package/lib/project.js +26 -2
  16. package/lib/public/app.js +1193 -8517
  17. package/lib/public/css/command-palette.css +14 -0
  18. package/lib/public/css/loop.css +301 -0
  19. package/lib/public/css/notifications-center.css +190 -0
  20. package/lib/public/css/rewind.css +6 -0
  21. package/lib/public/index.html +89 -35
  22. package/lib/public/modules/app-connection.js +160 -0
  23. package/lib/public/modules/app-cursors.js +473 -0
  24. package/lib/public/modules/app-debate-ui.js +389 -0
  25. package/lib/public/modules/app-dm.js +627 -0
  26. package/lib/public/modules/app-favicon.js +212 -0
  27. package/lib/public/modules/app-header.js +229 -0
  28. package/lib/public/modules/app-home-hub.js +600 -0
  29. package/lib/public/modules/app-loop-ui.js +589 -0
  30. package/lib/public/modules/app-loop-wizard.js +439 -0
  31. package/lib/public/modules/app-messages.js +1560 -0
  32. package/lib/public/modules/app-misc.js +299 -0
  33. package/lib/public/modules/app-notifications.js +372 -0
  34. package/lib/public/modules/app-panels.js +888 -0
  35. package/lib/public/modules/app-projects.js +798 -0
  36. package/lib/public/modules/app-rate-limit.js +451 -0
  37. package/lib/public/modules/app-rendering.js +597 -0
  38. package/lib/public/modules/app-skills-install.js +234 -0
  39. package/lib/public/modules/command-palette.js +27 -4
  40. package/lib/public/modules/input.js +31 -20
  41. package/lib/public/modules/scheduler-config.js +1532 -0
  42. package/lib/public/modules/scheduler-history.js +79 -0
  43. package/lib/public/modules/scheduler.js +33 -1554
  44. package/lib/public/modules/session-search.js +13 -1
  45. package/lib/public/modules/sidebar-mates.js +812 -0
  46. package/lib/public/modules/sidebar-mobile.js +1269 -0
  47. package/lib/public/modules/sidebar-projects.js +1449 -0
  48. package/lib/public/modules/sidebar-sessions.js +986 -0
  49. package/lib/public/modules/sidebar.js +232 -4591
  50. package/lib/public/modules/store.js +27 -0
  51. package/lib/public/modules/ws-ref.js +7 -0
  52. package/lib/public/style.css +1 -0
  53. package/lib/sdk-bridge.js +96 -717
  54. package/lib/sdk-message-processor.js +587 -0
  55. package/lib/sdk-message-queue.js +42 -0
  56. package/lib/sdk-skill-discovery.js +131 -0
  57. package/lib/server-admin.js +712 -0
  58. package/lib/server-auth.js +737 -0
  59. package/lib/server-dm.js +221 -0
  60. package/lib/server-mates.js +281 -0
  61. package/lib/server-palette.js +110 -0
  62. package/lib/server-settings.js +479 -0
  63. package/lib/server-skills.js +280 -0
  64. package/lib/server.js +246 -2755
  65. package/lib/sessions.js +11 -4
  66. package/lib/users-auth.js +146 -0
  67. package/lib/users-permissions.js +118 -0
  68. package/lib/users-preferences.js +210 -0
  69. package/lib/users.js +48 -398
  70. package/lib/ws-schema.js +498 -0
  71. package/package.json +1 -1
@@ -0,0 +1,221 @@
1
+ var fs = require("fs");
2
+
3
+ function attachDm(ctx) {
4
+ var users = ctx.users;
5
+ var dm = ctx.dm;
6
+ var mates = ctx.mates;
7
+ var projects = ctx.projects;
8
+ var pushModule = ctx.pushModule;
9
+ var addProject = ctx.addProject;
10
+
11
+ function handleMessage(ws, msg) {
12
+ if (!users.isMultiUser() || !ws._clayUser) return false;
13
+ var userId = ws._clayUser.id;
14
+
15
+ if (msg.type === "dm_list") {
16
+ var dmList = dm.getDmList(userId);
17
+ // Enrich with user info
18
+ for (var i = 0; i < dmList.length; i++) {
19
+ var otherUser = users.findUserById(dmList[i].otherUserId);
20
+ if (otherUser) {
21
+ var p = otherUser.profile || {};
22
+ dmList[i].otherUser = {
23
+ id: otherUser.id,
24
+ displayName: p.name || otherUser.displayName || otherUser.username,
25
+ username: otherUser.username,
26
+ avatarStyle: p.avatarStyle || "thumbs",
27
+ avatarSeed: p.avatarSeed || otherUser.username,
28
+ avatarColor: p.avatarColor || "#7c3aed",
29
+ avatarCustom: p.avatarCustom || "",
30
+ };
31
+ }
32
+ }
33
+ // Include mates in the list
34
+ var mateCtx = mates.buildMateCtx(userId);
35
+ var mateList = mates.getAllMates(mateCtx);
36
+ ws.send(JSON.stringify({ type: "dm_list", dms: dmList, mates: mateList }));
37
+ return true;
38
+ }
39
+
40
+ if (msg.type === "dm_open") {
41
+ if (!msg.targetUserId) return true;
42
+
43
+ // Check if target is a mate
44
+ var mateCtx2 = mates.buildMateCtx(userId);
45
+ if (mates.isMate(mateCtx2, msg.targetUserId)) {
46
+ var mate = mates.getMate(mateCtx2, msg.targetUserId);
47
+ if (!mate) return true;
48
+ // Ensure mate project is registered (survives server restarts)
49
+ var mateSlug2 = "mate-" + mate.id;
50
+ if (!projects.has(mateSlug2)) {
51
+ var mateDir2 = mates.getMateDir(mateCtx2, mate.id);
52
+ fs.mkdirSync(mateDir2, { recursive: true });
53
+ var mateName2 = (mate.profile && mate.profile.displayName) || mate.name || "New Mate";
54
+ addProject(mateDir2, mateSlug2, mateName2, null, mate.createdBy || userId, null, { isMate: true, mateDisplayName: mateName2 });
55
+ }
56
+ var mp = mate.profile || {};
57
+ ws.send(JSON.stringify({
58
+ type: "dm_history",
59
+ dmKey: "mate:" + mate.id,
60
+ messages: dm.loadHistory("mate:" + mate.id),
61
+ isMate: true,
62
+ projectSlug: mateSlug2,
63
+ targetUser: {
64
+ id: mate.id,
65
+ displayName: mp.displayName || mate.name || "New Mate",
66
+ username: mate.id,
67
+ avatarStyle: mp.avatarStyle || "bottts",
68
+ avatarSeed: mp.avatarSeed || mate.id,
69
+ avatarColor: mp.avatarColor || "#6c5ce7",
70
+ avatarCustom: mp.avatarCustom || "",
71
+ isMate: true,
72
+ primary: !!mate.primary,
73
+ mateStatus: mate.status,
74
+ seedData: mate.seedData || {},
75
+ },
76
+ }));
77
+ return true;
78
+ }
79
+
80
+ var result = dm.openDm(userId, msg.targetUserId);
81
+ var targetUser = users.findUserById(msg.targetUserId);
82
+ var tp = targetUser ? (targetUser.profile || {}) : {};
83
+ ws.send(JSON.stringify({
84
+ type: "dm_history",
85
+ dmKey: result.dmKey,
86
+ messages: result.messages,
87
+ targetUser: targetUser ? {
88
+ id: targetUser.id,
89
+ displayName: tp.name || targetUser.displayName || targetUser.username,
90
+ username: targetUser.username,
91
+ avatarStyle: tp.avatarStyle || "thumbs",
92
+ avatarSeed: tp.avatarSeed || targetUser.username,
93
+ avatarColor: tp.avatarColor || "#7c3aed",
94
+ avatarCustom: tp.avatarCustom || "",
95
+ } : null,
96
+ }));
97
+ return true;
98
+ }
99
+
100
+ if (msg.type === "dm_typing") {
101
+ // Relay typing indicator to DM partner
102
+ var dmKey = msg.dmKey;
103
+ if (!dmKey) return true;
104
+ var parts = dmKey.split(":");
105
+ if (parts.indexOf(userId) === -1) return true;
106
+ var targetId = parts[0] === userId ? parts[1] : parts[0];
107
+ projects.forEach(function (ctx) {
108
+ ctx.forEachClient(function (otherWs) {
109
+ if (otherWs === ws) return;
110
+ if (!otherWs._clayUser || otherWs._clayUser.id !== targetId) return;
111
+ if (otherWs.readyState !== 1) return;
112
+ otherWs.send(JSON.stringify({ type: "dm_typing", dmKey: dmKey, userId: userId, typing: !!msg.typing }));
113
+ });
114
+ });
115
+ return true;
116
+ }
117
+
118
+ if (msg.type === "dm_send") {
119
+ if (!msg.dmKey || !msg.text) return true;
120
+ var parts = msg.dmKey.split(":");
121
+
122
+ // Handle mate DM: dmKey is "mate:mate_xxx"
123
+ var mateCtx3 = mates.buildMateCtx(userId);
124
+ if (parts[0] === "mate" && mates.isMate(mateCtx3, parts[1])) {
125
+ var mate = mates.getMate(mateCtx3, parts[1]);
126
+ if (!mate) return true;
127
+ // Verify sender is the mate's creator
128
+ if (mate.createdBy !== userId) return true;
129
+ var message = dm.sendMessage(msg.dmKey, userId, msg.text);
130
+ ws.send(JSON.stringify({ type: "dm_message", dmKey: msg.dmKey, message: message }));
131
+ return true;
132
+ }
133
+
134
+ // Regular DM: verify sender is a participant
135
+ if (parts.indexOf(userId) === -1) return true;
136
+ var message = dm.sendMessage(msg.dmKey, userId, msg.text);
137
+ // Send confirmation to sender
138
+ ws.send(JSON.stringify({ type: "dm_message", dmKey: msg.dmKey, message: message }));
139
+ // Broadcast to target user's connections across all projects
140
+ var targetId = parts[0] === userId ? parts[1] : parts[0];
141
+ projects.forEach(function (ctx) {
142
+ ctx.forEachClient(function (otherWs) {
143
+ if (otherWs === ws) return;
144
+ if (!otherWs._clayUser || otherWs._clayUser.id !== targetId) return;
145
+ if (otherWs.readyState !== 1) return;
146
+ otherWs.send(JSON.stringify({ type: "dm_message", dmKey: msg.dmKey, message: message }));
147
+ });
148
+ });
149
+ // Send push notification to target user
150
+ var senderName = ws._clayUser ? (ws._clayUser.displayName || ws._clayUser.username || "Someone") : "Someone";
151
+ var preview = (msg.text || "").substring(0, 140);
152
+ if (pushModule && pushModule.sendPushToUser) {
153
+ pushModule.sendPushToUser(targetId, {
154
+ type: "dm",
155
+ title: senderName,
156
+ body: preview,
157
+ tag: "dm-" + msg.dmKey,
158
+ dmKey: msg.dmKey,
159
+ });
160
+ }
161
+ // Create in-app notification via any project's notifications module
162
+ var _nmCtx = null;
163
+ projects.forEach(function (pCtx) { if (!_nmCtx && pCtx.getNotificationsModule) _nmCtx = pCtx; });
164
+ if (_nmCtx) {
165
+ var _nm = _nmCtx.getNotificationsModule();
166
+ if (_nm) {
167
+ _nm.notify("mate_dm", {
168
+ senderName: senderName,
169
+ preview: preview,
170
+ mateId: userId,
171
+ });
172
+ }
173
+ }
174
+ return true;
175
+ }
176
+
177
+ if (msg.type === "dm_add_favorite") {
178
+ if (!msg.targetUserId) return true;
179
+ users.removeDmHidden(userId, msg.targetUserId);
180
+ var updatedFavorites = users.addDmFavorite(userId, msg.targetUserId);
181
+ var allUsersList = users.getAllUsers().map(function (u) {
182
+ var p = u.profile || {};
183
+ return {
184
+ id: u.id,
185
+ displayName: p.name || u.displayName || u.username,
186
+ username: u.username,
187
+ role: u.role,
188
+ avatarStyle: p.avatarStyle || "thumbs",
189
+ avatarSeed: p.avatarSeed || u.username,
190
+ avatarColor: p.avatarColor || "#7c3aed",
191
+ avatarCustom: p.avatarCustom || "",
192
+ };
193
+ });
194
+ ws.send(JSON.stringify({
195
+ type: "dm_favorites_updated",
196
+ dmFavorites: updatedFavorites,
197
+ allUsers: allUsersList,
198
+ }));
199
+ return true;
200
+ }
201
+
202
+ if (msg.type === "dm_remove_favorite") {
203
+ if (!msg.targetUserId) return true;
204
+ users.addDmHidden(userId, msg.targetUserId);
205
+ var updatedFavorites = users.removeDmFavorite(userId, msg.targetUserId);
206
+ ws.send(JSON.stringify({
207
+ type: "dm_favorites_updated",
208
+ dmFavorites: updatedFavorites,
209
+ }));
210
+ return true;
211
+ }
212
+
213
+ return false;
214
+ }
215
+
216
+ return {
217
+ handleMessage: handleMessage,
218
+ };
219
+ }
220
+
221
+ module.exports = { attachDm: attachDm };
@@ -0,0 +1,281 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+
4
+ function attachMates(ctx) {
5
+ var users = ctx.users;
6
+ var mates = ctx.mates;
7
+ var projects = ctx.projects;
8
+ var addProject = ctx.addProject;
9
+ var removeProject = ctx.removeProject;
10
+ var onGetProjectAccess = ctx.onGetProjectAccess;
11
+
12
+ // --- Team section enforcement ---
13
+
14
+ function refreshTeamSections(mateCtx) {
15
+ try {
16
+ var allMates = mates.getAllMates(mateCtx);
17
+ // Collect non-mate projects accessible to this user
18
+ var userId = mateCtx.userId || null;
19
+ var projList = [];
20
+ projects.forEach(function (pCtx, pSlug) {
21
+ var st = pCtx.getStatus();
22
+ if (st.isMate || st.isWorktree) return;
23
+ // Filter by user access in multi-user mode
24
+ if (userId && users.isMultiUser() && onGetProjectAccess) {
25
+ var access = onGetProjectAccess(pSlug);
26
+ if (access && !access.error && !users.canAccessProject(userId, access)) return;
27
+ }
28
+ projList.push(st);
29
+ });
30
+ for (var ri = 0; ri < allMates.length; ri++) {
31
+ var mDir = mates.getMateDir(mateCtx, allMates[ri].id);
32
+ var claudePath = path.join(mDir, "CLAUDE.md");
33
+ try {
34
+ mates.enforceAllSections(claudePath, { ctx: mateCtx, mateId: allMates[ri].id, projects: projList });
35
+ } catch (e) {}
36
+ }
37
+ } catch (e) {
38
+ console.error("[mates] refreshTeamSections failed:", e.message);
39
+ }
40
+ }
41
+
42
+ // Debounced project registry refresh for all mates
43
+ var _registryRefreshTimer = null;
44
+ function scheduleRegistryRefresh() {
45
+ if (_registryRefreshTimer) clearTimeout(_registryRefreshTimer);
46
+ _registryRefreshTimer = setTimeout(function () {
47
+ _registryRefreshTimer = null;
48
+ // Refresh for all known user contexts
49
+ try {
50
+ var allCtxs = {};
51
+ projects.forEach(function (pCtx) {
52
+ var st = pCtx.getStatus();
53
+ if (st.projectOwnerId && !allCtxs[st.projectOwnerId]) {
54
+ allCtxs[st.projectOwnerId] = mates.buildMateCtx(st.projectOwnerId);
55
+ }
56
+ });
57
+ var ctxKeys = Object.keys(allCtxs);
58
+ for (var ci = 0; ci < ctxKeys.length; ci++) {
59
+ refreshTeamSections(allCtxs[ctxKeys[ci]]);
60
+ }
61
+ } catch (e) {}
62
+ }, 2000);
63
+ }
64
+
65
+ // --- Mate message handlers ---
66
+
67
+ function handleMessage(ws, msg) {
68
+ if (!users.isMultiUser() || !ws._clayUser) return false;
69
+ var userId = ws._clayUser.id;
70
+
71
+ if (msg.type === "mate_create") {
72
+ if (!msg.seedData) return true;
73
+ try {
74
+ var mateCtx4 = mates.buildMateCtx(userId);
75
+ var mate = mates.createMate(mateCtx4, msg.seedData);
76
+ // Register mate as a project
77
+ var mateDir = mates.getMateDir(mateCtx4, mate.id);
78
+ var mateSlug = "mate-" + mate.id;
79
+ var mateName = (mate.profile && mate.profile.displayName) || mate.name || "New Mate";
80
+ addProject(mateDir, mateSlug, mateName, null, mate.createdBy, null, { isMate: true, mateDisplayName: mateName });
81
+ // Auto-add to favorites so it shows in sidebar
82
+ users.addDmFavorite(userId, mate.id);
83
+ ws.send(JSON.stringify({ type: "mate_created", mate: mate, projectSlug: mateSlug }));
84
+ } catch (e) {
85
+ ws.send(JSON.stringify({ type: "mate_error", error: "Failed to create mate: " + e.message }));
86
+ }
87
+ return true;
88
+ }
89
+
90
+ if (msg.type === "mate_list") {
91
+ var mateCtx5 = mates.buildMateCtx(userId);
92
+ // Backfill built-in mates for existing users
93
+ try {
94
+ var deletedKeys = users.getDeletedBuiltinKeys(userId);
95
+ var newBuiltins = mates.ensureBuiltinMates(mateCtx5, deletedKeys);
96
+ for (var bi = 0; bi < newBuiltins.length; bi++) {
97
+ var nb = newBuiltins[bi];
98
+ var nbSlug = "mate-" + nb.id;
99
+ var nbDir = mates.getMateDir(mateCtx5, nb.id);
100
+ var nbName = (nb.profile && nb.profile.displayName) || nb.name || "New Mate";
101
+ addProject(nbDir, nbSlug, nbName, null, nb.createdBy || userId, null, { isMate: true, mateDisplayName: nbName });
102
+ users.addDmFavorite(userId, nb.id);
103
+ }
104
+ } catch (e) {
105
+ console.error("[server] Failed to ensure built-in mates:", e.message);
106
+ }
107
+ // Auto-sync primary mates (Ally) with latest definition
108
+ try { mates.syncPrimaryMates(mateCtx5); } catch (e) {}
109
+ // Ensure core built-in mates are in favorites (unless user explicitly removed them)
110
+ // Only auto-favorite the core 3: Ally (chief of staff), Arch (architect), Buzz (marketer)
111
+ var coreMateKeys = ["ally", "arch", "buzz"];
112
+ var mateList = mates.getAllMates(mateCtx5);
113
+ var currentFavs = users.getDmFavorites(userId);
114
+ var hiddenIds = users.getDmHidden(userId);
115
+ for (var bfi = 0; bfi < mateList.length; bfi++) {
116
+ if (mateList[bfi].builtinKey && coreMateKeys.indexOf(mateList[bfi].builtinKey) !== -1 && currentFavs.indexOf(mateList[bfi].id) === -1 && hiddenIds.indexOf(mateList[bfi].id) === -1) {
117
+ users.addDmFavorite(userId, mateList[bfi].id);
118
+ }
119
+ }
120
+ // Ensure all mate projects are registered (survives server restarts)
121
+ for (var mi = 0; mi < mateList.length; mi++) {
122
+ var m = mateList[mi];
123
+ var mSlug = "mate-" + m.id;
124
+ if (!projects.has(mSlug)) {
125
+ var mDir = mates.getMateDir(mateCtx5, m.id);
126
+ fs.mkdirSync(mDir, { recursive: true });
127
+ var mName = (m.profile && m.profile.displayName) || m.name || "New Mate";
128
+ addProject(mDir, mSlug, mName, null, m.createdBy || userId, null, { isMate: true, mateDisplayName: mName });
129
+ }
130
+ }
131
+ // Include deleted built-in mates for re-add UI
132
+ var builtinDefs2 = require("./builtin-mates");
133
+ var missingKeys2 = mates.getMissingBuiltinKeys(mateCtx5);
134
+ var availableBuiltins2 = [];
135
+ for (var abk2 = 0; abk2 < missingKeys2.length; abk2++) {
136
+ var bDef2 = builtinDefs2.getBuiltinByKey(missingKeys2[abk2]);
137
+ if (bDef2) {
138
+ availableBuiltins2.push({
139
+ key: bDef2.key,
140
+ displayName: bDef2.displayName,
141
+ bio: bDef2.bio,
142
+ avatarCustom: bDef2.avatarCustom || "",
143
+ avatarStyle: bDef2.avatarStyle || "bottts",
144
+ avatarColor: bDef2.avatarColor || "",
145
+ });
146
+ }
147
+ }
148
+ ws.send(JSON.stringify({ type: "mate_list", mates: mateList, availableBuiltins: availableBuiltins2 }));
149
+ return true;
150
+ }
151
+
152
+ if (msg.type === "mate_delete") {
153
+ if (!msg.mateId) return true;
154
+ var mateCtx6 = mates.buildMateCtx(userId);
155
+ // Track deleted built-in mate key so it doesn't auto-recreate
156
+ var mateToDelete = mates.getMate(mateCtx6, msg.mateId);
157
+ if (mateToDelete && mateToDelete.builtinKey) {
158
+ users.addDeletedBuiltinKey(userId, mateToDelete.builtinKey);
159
+ }
160
+ var result = mates.deleteMate(mateCtx6, msg.mateId);
161
+ if (result.error) {
162
+ ws.send(JSON.stringify({ type: "mate_error", error: result.error }));
163
+ } else {
164
+ removeProject("mate-" + msg.mateId);
165
+ // Build updated available builtins list
166
+ var builtinDefs3 = require("./builtin-mates");
167
+ var missingKeys3 = mates.getMissingBuiltinKeys(mateCtx6);
168
+ var availableBuiltins3 = [];
169
+ for (var abk3 = 0; abk3 < missingKeys3.length; abk3++) {
170
+ var bDef3 = builtinDefs3.getBuiltinByKey(missingKeys3[abk3]);
171
+ if (bDef3) {
172
+ availableBuiltins3.push({
173
+ key: bDef3.key,
174
+ displayName: bDef3.displayName,
175
+ bio: bDef3.bio,
176
+ avatarCustom: bDef3.avatarCustom || "",
177
+ avatarStyle: bDef3.avatarStyle || "bottts",
178
+ avatarColor: bDef3.avatarColor || "",
179
+ });
180
+ }
181
+ }
182
+ ws.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
183
+ // Broadcast to all clients so strips update
184
+ projects.forEach(function (pCtx) {
185
+ pCtx.forEachClient(function (otherWs) {
186
+ if (otherWs === ws) return;
187
+ if (otherWs.readyState !== 1) return;
188
+ otherWs.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId, availableBuiltins: availableBuiltins3 }));
189
+ });
190
+ });
191
+ }
192
+ return true;
193
+ }
194
+
195
+ if (msg.type === "mate_readd_builtin") {
196
+ if (!msg.builtinKey) return true;
197
+ try {
198
+ var mateCtxR = mates.buildMateCtx(userId);
199
+ var missingKeys = mates.getMissingBuiltinKeys(mateCtxR);
200
+ if (missingKeys.indexOf(msg.builtinKey) === -1) {
201
+ ws.send(JSON.stringify({ type: "mate_error", error: "This built-in mate already exists" }));
202
+ return true;
203
+ }
204
+ var newMate = mates.createBuiltinMate(mateCtxR, msg.builtinKey);
205
+ users.removeDeletedBuiltinKey(userId, msg.builtinKey);
206
+ var updatedFavsR = users.addDmFavorite(userId, newMate.id);
207
+ var readdSlug = "mate-" + newMate.id;
208
+ var readdDir = mates.getMateDir(mateCtxR, newMate.id);
209
+ var readdName = (newMate.profile && newMate.profile.displayName) || newMate.name || "New Mate";
210
+ addProject(readdDir, readdSlug, readdName, null, newMate.createdBy || userId, null, { isMate: true, mateDisplayName: readdName });
211
+ // Build updated available builtins
212
+ var builtinDefsR = require("./builtin-mates");
213
+ var missingKeysR = mates.getMissingBuiltinKeys(mateCtxR);
214
+ var availableBuiltinsR = [];
215
+ for (var abkR = 0; abkR < missingKeysR.length; abkR++) {
216
+ var bDefR = builtinDefsR.getBuiltinByKey(missingKeysR[abkR]);
217
+ if (bDefR) {
218
+ availableBuiltinsR.push({ key: bDefR.key, displayName: bDefR.displayName, bio: bDefR.bio, avatarCustom: bDefR.avatarCustom || "", avatarStyle: bDefR.avatarStyle || "bottts", avatarColor: bDefR.avatarColor || "" });
219
+ }
220
+ }
221
+ ws.send(JSON.stringify({ type: "mate_created", mate: newMate, projectSlug: readdSlug, availableBuiltins: availableBuiltinsR, dmFavorites: updatedFavsR }));
222
+ } catch (e) {
223
+ ws.send(JSON.stringify({ type: "mate_error", error: "Failed to re-add built-in mate: " + e.message }));
224
+ }
225
+ return true;
226
+ }
227
+
228
+ if (msg.type === "mate_list_available_builtins") {
229
+ var mateCtxAB = mates.buildMateCtx(userId);
230
+ var missingBuiltinKeys = mates.getMissingBuiltinKeys(mateCtxAB);
231
+ var builtinDefs = require("./builtin-mates");
232
+ var availableBuiltins = [];
233
+ for (var abk = 0; abk < missingBuiltinKeys.length; abk++) {
234
+ var bDef = builtinDefs.getBuiltinByKey(missingBuiltinKeys[abk]);
235
+ if (bDef) {
236
+ availableBuiltins.push({
237
+ key: bDef.key,
238
+ displayName: bDef.displayName,
239
+ bio: bDef.bio,
240
+ avatarColor: bDef.avatarColor,
241
+ avatarStyle: bDef.avatarStyle,
242
+ avatarCustom: bDef.avatarCustom || "",
243
+ });
244
+ }
245
+ }
246
+ ws.send(JSON.stringify({ type: "mate_available_builtins", builtins: availableBuiltins }));
247
+ return true;
248
+ }
249
+
250
+ if (msg.type === "mate_update") {
251
+ if (!msg.mateId || !msg.updates) return true;
252
+ var mateCtx7 = mates.buildMateCtx(userId);
253
+ var updated = mates.updateMate(mateCtx7, msg.mateId, msg.updates);
254
+ if (updated) {
255
+ ws.send(JSON.stringify({ type: "mate_updated", mate: updated }));
256
+ // Broadcast update
257
+ projects.forEach(function (pCtx) {
258
+ pCtx.forEachClient(function (otherWs) {
259
+ if (otherWs === ws) return;
260
+ if (otherWs.readyState !== 1) return;
261
+ otherWs.send(JSON.stringify({ type: "mate_updated", mate: updated }));
262
+ });
263
+ });
264
+ // Re-enforce team sections across all mate projects so roster stays current
265
+ refreshTeamSections(mateCtx7);
266
+ } else {
267
+ ws.send(JSON.stringify({ type: "mate_error", error: "Mate not found" }));
268
+ }
269
+ return true;
270
+ }
271
+
272
+ return false;
273
+ }
274
+
275
+ return {
276
+ handleMessage: handleMessage,
277
+ scheduleRegistryRefresh: scheduleRegistryRefresh,
278
+ };
279
+ }
280
+
281
+ module.exports = { attachMates: attachMates };
@@ -0,0 +1,110 @@
1
+ var sessionSearch = require("./session-search");
2
+
3
+ function attachPalette(ctx) {
4
+ var users = ctx.users;
5
+ var projects = ctx.projects;
6
+ var getMultiUserFromReq = ctx.getMultiUserFromReq;
7
+ var onGetProjectAccess = ctx.onGetProjectAccess;
8
+
9
+ function handleRequest(req, res, fullUrl) {
10
+ if (req.method !== "GET" || fullUrl !== "/api/palette/search") return false;
11
+
12
+ var paletteUser = null;
13
+ if (users.isMultiUser()) {
14
+ paletteUser = getMultiUserFromReq(req);
15
+ if (!paletteUser) {
16
+ res.writeHead(401, { "Content-Type": "application/json" });
17
+ res.end('{"error":"unauthorized"}');
18
+ return true;
19
+ }
20
+ }
21
+ var pqs = req.url.indexOf("?") >= 0 ? req.url.substring(req.url.indexOf("?")) : "";
22
+ var pQuery = new URLSearchParams(pqs).get("q") || "";
23
+ var pResults = [];
24
+
25
+ if (!pQuery) {
26
+ // Recent mode: return all sessions sorted by lastActivity
27
+ projects.forEach(function (pCtx, pSlug) {
28
+ var status = pCtx.getStatus();
29
+ if (status.isWorktree) return;
30
+ if (paletteUser && onGetProjectAccess) {
31
+ var pAccess = onGetProjectAccess(pSlug);
32
+ if (pAccess && !pAccess.error && !users.canAccessProject(paletteUser.id, pAccess)) return;
33
+ }
34
+ pCtx.sm.sessions.forEach(function (session) {
35
+ if (session.hidden) return;
36
+ if (paletteUser) {
37
+ if (users.isMultiUser()) {
38
+ var sAccess = onGetProjectAccess ? onGetProjectAccess(pSlug) : null;
39
+ if (!users.canAccessSession(paletteUser.id, session, sAccess)) return;
40
+ }
41
+ } else {
42
+ if (session.ownerId) return;
43
+ }
44
+ var pItem = {
45
+ projectSlug: pSlug,
46
+ projectTitle: status.title || status.project,
47
+ projectIcon: status.icon || null,
48
+ sessionId: session.localId,
49
+ sessionTitle: session.title || "New Session",
50
+ lastActivity: session.lastActivity || session.createdAt || 0,
51
+ matchType: null,
52
+ snippet: null
53
+ };
54
+ if (status.isMate) {
55
+ pItem.isMate = true;
56
+ pItem.mateId = status.mateId || null;
57
+ }
58
+ pResults.push(pItem);
59
+ });
60
+ });
61
+ pResults.sort(function (a, b) { return b.lastActivity - a.lastActivity; });
62
+ if (pResults.length > 30) pResults = pResults.slice(0, 30);
63
+ } else {
64
+ // Search mode: BM25 ranked search across all sessions
65
+ var projectSessions = [];
66
+ projects.forEach(function (pCtx, pSlug) {
67
+ var status = pCtx.getStatus();
68
+ if (status.isWorktree) return;
69
+ if (paletteUser && onGetProjectAccess) {
70
+ var pAccess = onGetProjectAccess(pSlug);
71
+ if (pAccess && !pAccess.error && !users.canAccessProject(paletteUser.id, pAccess)) return;
72
+ }
73
+ var accessibleSessions = [];
74
+ pCtx.sm.sessions.forEach(function (session) {
75
+ if (session.hidden) return;
76
+ if (paletteUser) {
77
+ if (users.isMultiUser()) {
78
+ var sAccess = onGetProjectAccess ? onGetProjectAccess(pSlug) : null;
79
+ if (!users.canAccessSession(paletteUser.id, session, sAccess)) return;
80
+ }
81
+ } else {
82
+ if (session.ownerId) return;
83
+ }
84
+ accessibleSessions.push(session);
85
+ });
86
+ if (accessibleSessions.length > 0) {
87
+ projectSessions.push({
88
+ projectSlug: pSlug,
89
+ projectTitle: status.title || status.project,
90
+ projectIcon: status.icon || null,
91
+ isMate: status.isMate || false,
92
+ mateId: status.mateId || null,
93
+ sessions: accessibleSessions
94
+ });
95
+ }
96
+ });
97
+ pResults = sessionSearch.searchPalette(projectSessions, pQuery, { maxResults: 30 });
98
+ }
99
+
100
+ res.writeHead(200, { "Content-Type": "application/json" });
101
+ res.end(JSON.stringify({ results: pResults }));
102
+ return true;
103
+ }
104
+
105
+ return {
106
+ handleRequest: handleRequest,
107
+ };
108
+ }
109
+
110
+ module.exports = { attachPalette: attachPalette };