clay-server 2.27.0-beta.8 → 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-debate.js +19 -12
- 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 -8521
- 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
package/lib/server-dm.js
ADDED
|
@@ -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 };
|