clay-server 2.13.0-beta.5 → 2.13.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/daemon.js +33 -9
- package/lib/dm.js +2 -2
- package/lib/mates.js +133 -37
- package/lib/public/app.js +24 -2
- package/lib/public/css/mates.css +87 -1
- package/lib/public/css/scheduler.css +4 -2
- package/lib/public/css/sticky-notes.css +3 -1
- package/lib/public/index.html +1 -0
- package/lib/public/modules/mate-sidebar.js +17 -0
- package/lib/public/modules/stt.js +1 -0
- package/lib/server.js +37 -11
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -936,16 +936,40 @@ for (var i = 0; i < projects.length; i++) {
|
|
|
936
936
|
}
|
|
937
937
|
}
|
|
938
938
|
|
|
939
|
+
// Migrate legacy mates storage before registration
|
|
940
|
+
mates.migrateLegacyMates();
|
|
941
|
+
|
|
939
942
|
// Register existing mates as projects
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
var
|
|
943
|
-
var
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
943
|
+
if (usersModule.isMultiUser()) {
|
|
944
|
+
// Multi-user mode: iterate over all users and load each user's mates
|
|
945
|
+
var allUsers = usersModule.getAllUsers();
|
|
946
|
+
for (var ui = 0; ui < allUsers.length; ui++) {
|
|
947
|
+
var mateCtx = mates.buildMateCtx(allUsers[ui].id);
|
|
948
|
+
var userMates = mates.getAllMates(mateCtx);
|
|
949
|
+
for (var mi = 0; mi < userMates.length; mi++) {
|
|
950
|
+
var m = userMates[mi];
|
|
951
|
+
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
952
|
+
var mateSlug = "mate-" + m.id;
|
|
953
|
+
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
954
|
+
if (fs.existsSync(mateDir)) {
|
|
955
|
+
console.log("[daemon] Adding mate project:", mateSlug);
|
|
956
|
+
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
// Single-user mode: use null ctx
|
|
962
|
+
var mateCtx = mates.buildMateCtx(null);
|
|
963
|
+
var allMates = mates.getAllMates(mateCtx);
|
|
964
|
+
for (var mi = 0; mi < allMates.length; mi++) {
|
|
965
|
+
var m = allMates[mi];
|
|
966
|
+
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
967
|
+
var mateSlug = "mate-" + m.id;
|
|
968
|
+
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
969
|
+
if (fs.existsSync(mateDir)) {
|
|
970
|
+
console.log("[daemon] Adding mate project:", mateSlug);
|
|
971
|
+
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true });
|
|
972
|
+
}
|
|
949
973
|
}
|
|
950
974
|
}
|
|
951
975
|
|
package/lib/dm.js
CHANGED
|
@@ -118,10 +118,10 @@ function getDmList(userId) {
|
|
|
118
118
|
return dms;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// Check if a user
|
|
121
|
+
// Check if a user ID looks like a mate (AI persona) by format only
|
|
122
122
|
function isMate(userId) {
|
|
123
123
|
var mates = require("./mates");
|
|
124
|
-
return mates.
|
|
124
|
+
return mates.isMateIdFormat(userId);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
module.exports = {
|
package/lib/mates.js
CHANGED
|
@@ -3,8 +3,40 @@ var path = require("path");
|
|
|
3
3
|
var crypto = require("crypto");
|
|
4
4
|
var config = require("./config");
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// --- Path resolution ---
|
|
7
|
+
|
|
8
|
+
function resolveMatesRoot(ctx) {
|
|
9
|
+
// OS-users mode: per-linuxUser home directory
|
|
10
|
+
if (ctx && ctx.linuxUser) {
|
|
11
|
+
return path.join("/home", ctx.linuxUser, ".clay", "mates");
|
|
12
|
+
}
|
|
13
|
+
// Multi-user mode: per-userId subdirectory
|
|
14
|
+
if (ctx && ctx.multiUser && ctx.userId) {
|
|
15
|
+
return path.join(config.CONFIG_DIR, "mates", ctx.userId);
|
|
16
|
+
}
|
|
17
|
+
// Single-user mode: flat directory
|
|
18
|
+
return path.join(config.CONFIG_DIR, "mates");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildMateCtx(userId) {
|
|
22
|
+
if (!userId) return { userId: null, multiUser: false, linuxUser: null };
|
|
23
|
+
// Lazy require to avoid circular dependency
|
|
24
|
+
var users = require("./users");
|
|
25
|
+
var multiUser = users.isMultiUser();
|
|
26
|
+
var linuxUser = null;
|
|
27
|
+
if (multiUser && userId) {
|
|
28
|
+
var user = users.findUserById(userId);
|
|
29
|
+
if (user && user.linuxUser) {
|
|
30
|
+
linuxUser = user.linuxUser;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { userId: userId, multiUser: multiUser, linuxUser: linuxUser };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isMateIdFormat(id) {
|
|
37
|
+
if (!id) return false;
|
|
38
|
+
return typeof id === "string" && id.indexOf("mate_") === 0;
|
|
39
|
+
}
|
|
8
40
|
|
|
9
41
|
// --- Default data ---
|
|
10
42
|
|
|
@@ -14,9 +46,13 @@ function defaultData() {
|
|
|
14
46
|
|
|
15
47
|
// --- Load / Save ---
|
|
16
48
|
|
|
17
|
-
function
|
|
49
|
+
function matesFilePath(ctx) {
|
|
50
|
+
return path.join(resolveMatesRoot(ctx), "mates.json");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function loadMates(ctx) {
|
|
18
54
|
try {
|
|
19
|
-
var raw = fs.readFileSync(
|
|
55
|
+
var raw = fs.readFileSync(matesFilePath(ctx), "utf8");
|
|
20
56
|
var data = JSON.parse(raw);
|
|
21
57
|
if (!data.mates) data.mates = [];
|
|
22
58
|
return data;
|
|
@@ -25,11 +61,12 @@ function loadMates() {
|
|
|
25
61
|
}
|
|
26
62
|
}
|
|
27
63
|
|
|
28
|
-
function saveMates(data) {
|
|
29
|
-
|
|
30
|
-
|
|
64
|
+
function saveMates(ctx, data) {
|
|
65
|
+
var filePath = matesFilePath(ctx);
|
|
66
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
67
|
+
var tmpPath = filePath + ".tmp";
|
|
31
68
|
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
32
|
-
fs.renameSync(tmpPath,
|
|
69
|
+
fs.renameSync(tmpPath, filePath);
|
|
33
70
|
}
|
|
34
71
|
|
|
35
72
|
// --- CRUD ---
|
|
@@ -38,9 +75,10 @@ function generateMateId() {
|
|
|
38
75
|
return "mate_" + crypto.randomUUID();
|
|
39
76
|
}
|
|
40
77
|
|
|
41
|
-
function createMate(
|
|
42
|
-
var data = loadMates();
|
|
78
|
+
function createMate(ctx, seedData) {
|
|
79
|
+
var data = loadMates(ctx);
|
|
43
80
|
var id = generateMateId();
|
|
81
|
+
var userId = ctx ? ctx.userId : null;
|
|
44
82
|
|
|
45
83
|
// Pick a random avatar color from a pleasant palette
|
|
46
84
|
var colors = ["#6c5ce7", "#00b894", "#e17055", "#0984e3", "#fdcb6e", "#e84393", "#00cec9", "#ff7675"];
|
|
@@ -63,10 +101,10 @@ function createMate(seedData, userId) {
|
|
|
63
101
|
};
|
|
64
102
|
|
|
65
103
|
data.mates.push(mate);
|
|
66
|
-
saveMates(data);
|
|
104
|
+
saveMates(ctx, data);
|
|
67
105
|
|
|
68
106
|
// Create the mate's identity directory
|
|
69
|
-
var mateDir =
|
|
107
|
+
var mateDir = getMateDir(ctx, id);
|
|
70
108
|
fs.mkdirSync(mateDir, { recursive: true });
|
|
71
109
|
|
|
72
110
|
// Write initial mate.yaml
|
|
@@ -98,40 +136,40 @@ function createMate(seedData, userId) {
|
|
|
98
136
|
return mate;
|
|
99
137
|
}
|
|
100
138
|
|
|
101
|
-
function getMate(id) {
|
|
102
|
-
var data = loadMates();
|
|
139
|
+
function getMate(ctx, id) {
|
|
140
|
+
var data = loadMates(ctx);
|
|
103
141
|
for (var i = 0; i < data.mates.length; i++) {
|
|
104
142
|
if (data.mates[i].id === id) return data.mates[i];
|
|
105
143
|
}
|
|
106
144
|
return null;
|
|
107
145
|
}
|
|
108
146
|
|
|
109
|
-
function updateMate(id, updates) {
|
|
110
|
-
var data = loadMates();
|
|
147
|
+
function updateMate(ctx, id, updates) {
|
|
148
|
+
var data = loadMates(ctx);
|
|
111
149
|
for (var i = 0; i < data.mates.length; i++) {
|
|
112
150
|
if (data.mates[i].id === id) {
|
|
113
151
|
var keys = Object.keys(updates);
|
|
114
152
|
for (var j = 0; j < keys.length; j++) {
|
|
115
153
|
data.mates[i][keys[j]] = updates[keys[j]];
|
|
116
154
|
}
|
|
117
|
-
saveMates(data);
|
|
155
|
+
saveMates(ctx, data);
|
|
118
156
|
return data.mates[i];
|
|
119
157
|
}
|
|
120
158
|
}
|
|
121
159
|
return null;
|
|
122
160
|
}
|
|
123
161
|
|
|
124
|
-
function deleteMate(id) {
|
|
125
|
-
var data = loadMates();
|
|
162
|
+
function deleteMate(ctx, id) {
|
|
163
|
+
var data = loadMates(ctx);
|
|
126
164
|
var before = data.mates.length;
|
|
127
165
|
data.mates = data.mates.filter(function (m) {
|
|
128
166
|
return m.id !== id;
|
|
129
167
|
});
|
|
130
168
|
if (data.mates.length === before) return { error: "Mate not found" };
|
|
131
|
-
saveMates(data);
|
|
169
|
+
saveMates(ctx, data);
|
|
132
170
|
|
|
133
171
|
// Remove mate directory
|
|
134
|
-
var mateDir =
|
|
172
|
+
var mateDir = getMateDir(ctx, id);
|
|
135
173
|
try {
|
|
136
174
|
fs.rmSync(mateDir, { recursive: true, force: true });
|
|
137
175
|
} catch (e) {
|
|
@@ -141,29 +179,86 @@ function deleteMate(id) {
|
|
|
141
179
|
return { ok: true };
|
|
142
180
|
}
|
|
143
181
|
|
|
144
|
-
function getAllMates() {
|
|
145
|
-
var data = loadMates();
|
|
182
|
+
function getAllMates(ctx) {
|
|
183
|
+
var data = loadMates(ctx);
|
|
146
184
|
return data.mates;
|
|
147
185
|
}
|
|
148
186
|
|
|
149
|
-
function
|
|
150
|
-
var data = loadMates();
|
|
151
|
-
return data.mates.filter(function (m) {
|
|
152
|
-
return m.createdBy === userId;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function isMate(id) {
|
|
187
|
+
function isMate(ctx, id) {
|
|
157
188
|
if (!id) return false;
|
|
158
189
|
if (typeof id === "string" && id.indexOf("mate_") === 0) {
|
|
159
190
|
// Double check it exists in registry
|
|
160
|
-
return !!getMate(id);
|
|
191
|
+
return !!getMate(ctx, id);
|
|
161
192
|
}
|
|
162
193
|
return false;
|
|
163
194
|
}
|
|
164
195
|
|
|
165
|
-
function getMateDir(id) {
|
|
166
|
-
return path.join(
|
|
196
|
+
function getMateDir(ctx, id) {
|
|
197
|
+
return path.join(resolveMatesRoot(ctx), id);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --- Migration ---
|
|
201
|
+
|
|
202
|
+
function migrateLegacyMates() {
|
|
203
|
+
var legacyFile = path.join(config.CONFIG_DIR, "mates.json");
|
|
204
|
+
if (!fs.existsSync(legacyFile)) return;
|
|
205
|
+
|
|
206
|
+
// Check if already migrated
|
|
207
|
+
var migratedMarker = legacyFile + ".migrated";
|
|
208
|
+
if (fs.existsSync(migratedMarker)) return;
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
var raw = fs.readFileSync(legacyFile, "utf8");
|
|
212
|
+
var data = JSON.parse(raw);
|
|
213
|
+
if (!data.mates || data.mates.length === 0) {
|
|
214
|
+
// Nothing to migrate, just mark as done
|
|
215
|
+
fs.renameSync(legacyFile, migratedMarker);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Group mates by createdBy
|
|
220
|
+
var byUser = {};
|
|
221
|
+
for (var i = 0; i < data.mates.length; i++) {
|
|
222
|
+
var m = data.mates[i];
|
|
223
|
+
var key = m.createdBy || "__null__";
|
|
224
|
+
if (!byUser[key]) byUser[key] = [];
|
|
225
|
+
byUser[key].push(m);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Write each user's mates to their own storage path
|
|
229
|
+
var keys = Object.keys(byUser);
|
|
230
|
+
for (var k = 0; k < keys.length; k++) {
|
|
231
|
+
var userId = keys[k] === "__null__" ? null : keys[k];
|
|
232
|
+
var ctx = buildMateCtx(userId);
|
|
233
|
+
var userData = { mates: byUser[keys[k]] };
|
|
234
|
+
saveMates(ctx, userData);
|
|
235
|
+
|
|
236
|
+
// Move mate identity directories to new location
|
|
237
|
+
var legacyMatesDir = path.join(config.CONFIG_DIR, "mates");
|
|
238
|
+
var newRoot = resolveMatesRoot(ctx);
|
|
239
|
+
for (var mi = 0; mi < byUser[keys[k]].length; mi++) {
|
|
240
|
+
var mateId = byUser[keys[k]][mi].id;
|
|
241
|
+
var oldDir = path.join(legacyMatesDir, mateId);
|
|
242
|
+
var newDir = path.join(newRoot, mateId);
|
|
243
|
+
if (fs.existsSync(oldDir) && oldDir !== newDir) {
|
|
244
|
+
fs.mkdirSync(path.dirname(newDir), { recursive: true });
|
|
245
|
+
try {
|
|
246
|
+
fs.renameSync(oldDir, newDir);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
// Cross-device or other issue, copy instead
|
|
249
|
+
fs.cpSync(oldDir, newDir, { recursive: true });
|
|
250
|
+
fs.rmSync(oldDir, { recursive: true, force: true });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Mark legacy file as migrated
|
|
257
|
+
fs.renameSync(legacyFile, migratedMarker);
|
|
258
|
+
console.log("[mates] Migrated legacy mates.json to per-user storage");
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.error("[mates] Legacy migration failed:", e.message);
|
|
261
|
+
}
|
|
167
262
|
}
|
|
168
263
|
|
|
169
264
|
// Format seed data as a human-readable context string
|
|
@@ -206,8 +301,9 @@ function formatSeedContext(seedData) {
|
|
|
206
301
|
}
|
|
207
302
|
|
|
208
303
|
module.exports = {
|
|
209
|
-
|
|
210
|
-
|
|
304
|
+
resolveMatesRoot: resolveMatesRoot,
|
|
305
|
+
buildMateCtx: buildMateCtx,
|
|
306
|
+
isMateIdFormat: isMateIdFormat,
|
|
211
307
|
loadMates: loadMates,
|
|
212
308
|
saveMates: saveMates,
|
|
213
309
|
createMate: createMate,
|
|
@@ -215,8 +311,8 @@ module.exports = {
|
|
|
215
311
|
updateMate: updateMate,
|
|
216
312
|
deleteMate: deleteMate,
|
|
217
313
|
getAllMates: getAllMates,
|
|
218
|
-
getMatesByUser: getMatesByUser,
|
|
219
314
|
isMate: isMate,
|
|
220
315
|
getMateDir: getMateDir,
|
|
316
|
+
migrateLegacyMates: migrateLegacyMates,
|
|
221
317
|
formatSeedContext: formatSeedContext,
|
|
222
318
|
};
|
package/lib/public/app.js
CHANGED
|
@@ -71,6 +71,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
71
71
|
// --- Mate WS (separate connection to mate project) ---
|
|
72
72
|
var mateWs = null;
|
|
73
73
|
var mateProjectSlug = null;
|
|
74
|
+
var savedActiveSessionId = null; // main project session ID saved during mate DM
|
|
74
75
|
|
|
75
76
|
// --- Home Hub ---
|
|
76
77
|
var homeHub = $("home-hub");
|
|
@@ -629,8 +630,10 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
629
630
|
// Hide terminal button (not relevant for mate)
|
|
630
631
|
var termBtn = document.getElementById("terminal-toggle-btn");
|
|
631
632
|
if (termBtn) termBtn.style.display = "none";
|
|
632
|
-
// Apply mate color to chat title bar
|
|
633
|
+
// Apply mate color to chat title bar and panels
|
|
633
634
|
var mateColor = (targetUser.profile && targetUser.profile.avatarColor) || targetUser.avatarColor || "#7c3aed";
|
|
635
|
+
document.body.style.setProperty("--mate-color", mateColor);
|
|
636
|
+
document.body.classList.add("mate-dm-active");
|
|
634
637
|
var titleBarContent = document.querySelector(".title-bar-content");
|
|
635
638
|
if (titleBarContent) {
|
|
636
639
|
titleBarContent.style.background = mateColor;
|
|
@@ -696,6 +699,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
696
699
|
if (resizeHandle) resizeHandle.classList.remove("dm-mode");
|
|
697
700
|
hideMateSidebar();
|
|
698
701
|
hideKnowledge();
|
|
702
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
699
703
|
disconnectMateWs();
|
|
700
704
|
// Restore terminal button
|
|
701
705
|
var termBtn = document.getElementById("terminal-toggle-btn");
|
|
@@ -713,7 +717,9 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
713
717
|
var mateTag = dmHeaderBar.querySelector(".dm-header-mate-tag");
|
|
714
718
|
if (mateTag) mateTag.remove();
|
|
715
719
|
}
|
|
716
|
-
// Reset chat title bar
|
|
720
|
+
// Reset chat title bar and mate color
|
|
721
|
+
document.body.style.removeProperty("--mate-color");
|
|
722
|
+
document.body.classList.remove("mate-dm-active");
|
|
717
723
|
var titleBarContent = document.querySelector(".title-bar-content");
|
|
718
724
|
if (titleBarContent) {
|
|
719
725
|
titleBarContent.style.background = "";
|
|
@@ -779,6 +785,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
779
785
|
mateWs.onopen = function () {
|
|
780
786
|
// Swap main ws to mateWs so all UI (input, model selector, etc.) routes through mate project
|
|
781
787
|
savedMainWs = ws;
|
|
788
|
+
savedActiveSessionId = activeSessionId;
|
|
782
789
|
ws = mateWs;
|
|
783
790
|
connected = true;
|
|
784
791
|
};
|
|
@@ -858,6 +865,11 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
858
865
|
mateWs.close();
|
|
859
866
|
mateWs = null;
|
|
860
867
|
}
|
|
868
|
+
// Restore main project's active session ID
|
|
869
|
+
if (savedActiveSessionId) {
|
|
870
|
+
activeSessionId = savedActiveSessionId;
|
|
871
|
+
savedActiveSessionId = null;
|
|
872
|
+
}
|
|
861
873
|
mateProjectSlug = null;
|
|
862
874
|
// If main WS was disconnected while in mate DM, reconnect now
|
|
863
875
|
if (ws && ws.readyState !== 1) {
|
|
@@ -1512,6 +1524,13 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
1512
1524
|
dmConversations: function () { return cachedDmConversations || []; },
|
|
1513
1525
|
myUserId: function () { return myUserId; },
|
|
1514
1526
|
selectSession: function (id) {
|
|
1527
|
+
// Close any open panels before switching
|
|
1528
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
1529
|
+
var stickyPanel = document.getElementById("sticky-notes-panel");
|
|
1530
|
+
if (stickyPanel && !stickyPanel.classList.contains("hidden")) {
|
|
1531
|
+
var stickyBtn = document.getElementById("sticky-notes-sidebar-btn");
|
|
1532
|
+
if (stickyBtn) stickyBtn.click();
|
|
1533
|
+
}
|
|
1515
1534
|
if (ws && ws.readyState === 1) {
|
|
1516
1535
|
ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
|
1517
1536
|
}
|
|
@@ -4434,6 +4453,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
4434
4453
|
initSTT({
|
|
4435
4454
|
inputEl: inputEl,
|
|
4436
4455
|
addSystemMessage: addSystemMessage,
|
|
4456
|
+
scrollToBottom: scrollToBottom,
|
|
4437
4457
|
});
|
|
4438
4458
|
|
|
4439
4459
|
// --- User profile (Discord-style popover on user island) ---
|
|
@@ -4574,6 +4594,8 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
|
|
|
4574
4594
|
if (!myPermissions.scheduledTasks) {
|
|
4575
4595
|
var schBtn = document.getElementById("scheduler-btn");
|
|
4576
4596
|
if (schBtn) schBtn.style.display = "none";
|
|
4597
|
+
var mateSchBtn = document.getElementById("mate-scheduler-btn");
|
|
4598
|
+
if (mateSchBtn) mateSchBtn.style.display = "none";
|
|
4577
4599
|
}
|
|
4578
4600
|
if (!myPermissions.createProject) {
|
|
4579
4601
|
var addProjBtn = document.getElementById("icon-strip-add");
|
package/lib/public/css/mates.css
CHANGED
|
@@ -798,8 +798,10 @@
|
|
|
798
798
|
.mate-knowledge-header {
|
|
799
799
|
display: flex;
|
|
800
800
|
align-items: center;
|
|
801
|
-
|
|
801
|
+
height: 48px;
|
|
802
|
+
padding: 0 16px;
|
|
802
803
|
flex-shrink: 0;
|
|
804
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
803
805
|
}
|
|
804
806
|
.mate-knowledge-title-wrap {
|
|
805
807
|
display: flex;
|
|
@@ -1130,3 +1132,87 @@
|
|
|
1130
1132
|
margin-right: 8px;
|
|
1131
1133
|
vertical-align: middle;
|
|
1132
1134
|
}
|
|
1135
|
+
|
|
1136
|
+
/* --- Mate color theming for panels --- */
|
|
1137
|
+
|
|
1138
|
+
/* Match panel header heights to sidebar header (48px) */
|
|
1139
|
+
body.mate-dm-active .mate-knowledge-header,
|
|
1140
|
+
body.mate-dm-active .scheduler-top-bar,
|
|
1141
|
+
body.mate-dm-active .notes-archive-header {
|
|
1142
|
+
height: 48px;
|
|
1143
|
+
box-sizing: border-box;
|
|
1144
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/* Knowledge header */
|
|
1148
|
+
body.mate-dm-active .mate-knowledge-header {
|
|
1149
|
+
background: var(--mate-color);
|
|
1150
|
+
color: #fff;
|
|
1151
|
+
}
|
|
1152
|
+
body.mate-dm-active .mate-knowledge-header .lucide,
|
|
1153
|
+
body.mate-dm-active .mate-knowledge-header h2 {
|
|
1154
|
+
color: #fff;
|
|
1155
|
+
}
|
|
1156
|
+
body.mate-dm-active .mate-knowledge-header-actions button {
|
|
1157
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1158
|
+
}
|
|
1159
|
+
body.mate-dm-active .mate-knowledge-header-actions button:hover {
|
|
1160
|
+
color: #fff;
|
|
1161
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1162
|
+
}
|
|
1163
|
+
body.mate-dm-active .mate-knowledge-count {
|
|
1164
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/* Scheduler top bar (main header) */
|
|
1168
|
+
body.mate-dm-active .scheduler-top-bar {
|
|
1169
|
+
background: var(--mate-color);
|
|
1170
|
+
color: #fff;
|
|
1171
|
+
}
|
|
1172
|
+
body.mate-dm-active .scheduler-top-title,
|
|
1173
|
+
body.mate-dm-active .scheduler-top-title .lucide {
|
|
1174
|
+
color: #fff;
|
|
1175
|
+
}
|
|
1176
|
+
body.mate-dm-active .scheduler-close-btn {
|
|
1177
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1178
|
+
}
|
|
1179
|
+
body.mate-dm-active .scheduler-close-btn:hover {
|
|
1180
|
+
color: #fff;
|
|
1181
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1182
|
+
}
|
|
1183
|
+
body.mate-dm-active .scheduler-scope-toggle {
|
|
1184
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1185
|
+
}
|
|
1186
|
+
body.mate-dm-active .scheduler-scope-label {
|
|
1187
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1188
|
+
}
|
|
1189
|
+
body.mate-dm-active .scheduler-scope-label[data-side="off"],
|
|
1190
|
+
body.mate-dm-active .scheduler-scope-toggle.active .scheduler-scope-label[data-side="on"] {
|
|
1191
|
+
color: #fff;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/* Scheduler: breathing room below top-bar */
|
|
1195
|
+
body.mate-dm-active .scheduler-sidebar,
|
|
1196
|
+
body.mate-dm-active .scheduler-content {
|
|
1197
|
+
padding-top: 12px;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/* Sticky Notes header */
|
|
1201
|
+
body.mate-dm-active .notes-archive-header {
|
|
1202
|
+
background: var(--mate-color);
|
|
1203
|
+
color: #fff;
|
|
1204
|
+
}
|
|
1205
|
+
body.mate-dm-active .notes-archive-header .lucide,
|
|
1206
|
+
body.mate-dm-active .notes-archive-header h2 {
|
|
1207
|
+
color: #fff;
|
|
1208
|
+
}
|
|
1209
|
+
body.mate-dm-active .notes-archive-count {
|
|
1210
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1211
|
+
}
|
|
1212
|
+
body.mate-dm-active .notes-archive-header button {
|
|
1213
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1214
|
+
}
|
|
1215
|
+
body.mate-dm-active .notes-archive-header button:hover {
|
|
1216
|
+
color: #fff;
|
|
1217
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1218
|
+
}
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
.scheduler-top-bar {
|
|
19
19
|
display: flex;
|
|
20
20
|
align-items: center;
|
|
21
|
-
|
|
21
|
+
height: 48px;
|
|
22
|
+
padding: 0 16px;
|
|
22
23
|
flex-shrink: 0;
|
|
24
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
23
25
|
}
|
|
24
26
|
.scheduler-top-title {
|
|
25
27
|
display: flex;
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
display: flex;
|
|
97
99
|
flex-direction: row;
|
|
98
100
|
gap: 10px;
|
|
99
|
-
padding:
|
|
101
|
+
padding: 12px 10px 10px;
|
|
100
102
|
min-height: 0;
|
|
101
103
|
overflow: hidden;
|
|
102
104
|
}
|
|
@@ -531,8 +531,10 @@
|
|
|
531
531
|
.notes-archive-header {
|
|
532
532
|
display: flex;
|
|
533
533
|
align-items: center;
|
|
534
|
-
|
|
534
|
+
height: 48px;
|
|
535
|
+
padding: 0 16px;
|
|
535
536
|
flex-shrink: 0;
|
|
537
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
536
538
|
}
|
|
537
539
|
|
|
538
540
|
.notes-archive-title-wrap {
|
package/lib/public/index.html
CHANGED
|
@@ -203,6 +203,7 @@
|
|
|
203
203
|
<button id="mate-knowledge-btn"><i data-lucide="book-open"></i> <span>Knowledge</span><span id="mate-knowledge-count" class="sidebar-badge hidden"></span></button>
|
|
204
204
|
<button id="mate-sticky-notes-btn"><i data-lucide="sticky-note"></i> <span>Sticky Notes</span></button>
|
|
205
205
|
<button id="mate-skills-btn"><i data-lucide="puzzle"></i> <span>Skills</span></button>
|
|
206
|
+
<button id="mate-scheduler-btn"><i data-lucide="calendar-clock"></i> <span>Scheduled Tasks</span></button>
|
|
206
207
|
</div>
|
|
207
208
|
<div class="mate-sidebar-sessions-header">
|
|
208
209
|
<span>Conversations</span>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { hideKnowledge } from './mate-knowledge.js';
|
|
4
|
+
import { isSchedulerOpen, closeScheduler } from './scheduler.js';
|
|
4
5
|
|
|
5
6
|
var getMateWs = null;
|
|
6
7
|
var currentMateId = null;
|
|
@@ -58,6 +59,14 @@ export function initMateSidebar(mateWsGetter) {
|
|
|
58
59
|
if (origBtn) origBtn.click();
|
|
59
60
|
});
|
|
60
61
|
}
|
|
62
|
+
var mateSchedulerBtn = document.getElementById("mate-scheduler-btn");
|
|
63
|
+
if (mateSchedulerBtn) {
|
|
64
|
+
mateSchedulerBtn.addEventListener("click", function () {
|
|
65
|
+
hideKnowledge();
|
|
66
|
+
var origBtn = document.getElementById("scheduler-btn");
|
|
67
|
+
if (origBtn) origBtn.click();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
export function showMateSidebar(mateId, mateData) {
|
|
@@ -293,6 +302,14 @@ function renderMateSessionItem(s) {
|
|
|
293
302
|
|
|
294
303
|
el.addEventListener("click", (function (id) {
|
|
295
304
|
return function () {
|
|
305
|
+
// Close any open panels
|
|
306
|
+
hideKnowledge();
|
|
307
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
308
|
+
var stickyBtn = document.getElementById("sticky-notes-sidebar-btn");
|
|
309
|
+
var stickyPanel = document.getElementById("sticky-notes-panel");
|
|
310
|
+
if (stickyPanel && !stickyPanel.classList.contains("hidden")) {
|
|
311
|
+
if (stickyBtn) stickyBtn.click();
|
|
312
|
+
}
|
|
296
313
|
var ws = getMateWs ? getMateWs() : null;
|
|
297
314
|
if (ws && ws.readyState === 1) {
|
|
298
315
|
ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
package/lib/server.js
CHANGED
|
@@ -2614,7 +2614,8 @@ function createServer(opts) {
|
|
|
2614
2614
|
}
|
|
2615
2615
|
}
|
|
2616
2616
|
// Include mates in the list
|
|
2617
|
-
var
|
|
2617
|
+
var mateCtx = mates.buildMateCtx(userId);
|
|
2618
|
+
var mateList = mates.getAllMates(mateCtx);
|
|
2618
2619
|
ws.send(JSON.stringify({ type: "dm_list", dms: dmList, mates: mateList }));
|
|
2619
2620
|
return;
|
|
2620
2621
|
}
|
|
@@ -2623,16 +2624,25 @@ function createServer(opts) {
|
|
|
2623
2624
|
if (!msg.targetUserId) return;
|
|
2624
2625
|
|
|
2625
2626
|
// Check if target is a mate
|
|
2626
|
-
|
|
2627
|
-
|
|
2627
|
+
var mateCtx2 = mates.buildMateCtx(userId);
|
|
2628
|
+
if (mates.isMate(mateCtx2, msg.targetUserId)) {
|
|
2629
|
+
var mate = mates.getMate(mateCtx2, msg.targetUserId);
|
|
2628
2630
|
if (!mate) return;
|
|
2631
|
+
// Ensure mate project is registered (survives server restarts)
|
|
2632
|
+
var mateSlug2 = "mate-" + mate.id;
|
|
2633
|
+
if (!projects.has(mateSlug2)) {
|
|
2634
|
+
var mateDir2 = mates.getMateDir(mateCtx2, mate.id);
|
|
2635
|
+
fs.mkdirSync(mateDir2, { recursive: true });
|
|
2636
|
+
var mateName2 = (mate.profile && mate.profile.displayName) || mate.name || "New Mate";
|
|
2637
|
+
addProject(mateDir2, mateSlug2, mateName2, null, mate.createdBy || userId, null, { isMate: true });
|
|
2638
|
+
}
|
|
2629
2639
|
var mp = mate.profile || {};
|
|
2630
2640
|
ws.send(JSON.stringify({
|
|
2631
2641
|
type: "dm_history",
|
|
2632
2642
|
dmKey: "mate:" + mate.id,
|
|
2633
2643
|
messages: dm.loadHistory("mate:" + mate.id),
|
|
2634
2644
|
isMate: true,
|
|
2635
|
-
projectSlug:
|
|
2645
|
+
projectSlug: mateSlug2,
|
|
2636
2646
|
targetUser: {
|
|
2637
2647
|
id: mate.id,
|
|
2638
2648
|
displayName: mp.displayName || mate.name || "New Mate",
|
|
@@ -2690,8 +2700,9 @@ function createServer(opts) {
|
|
|
2690
2700
|
var parts = msg.dmKey.split(":");
|
|
2691
2701
|
|
|
2692
2702
|
// Handle mate DM: dmKey is "mate:mate_xxx"
|
|
2693
|
-
|
|
2694
|
-
|
|
2703
|
+
var mateCtx3 = mates.buildMateCtx(userId);
|
|
2704
|
+
if (parts[0] === "mate" && mates.isMate(mateCtx3, parts[1])) {
|
|
2705
|
+
var mate = mates.getMate(mateCtx3, parts[1]);
|
|
2695
2706
|
if (!mate) return;
|
|
2696
2707
|
// Verify sender is the mate's creator
|
|
2697
2708
|
if (mate.createdBy !== userId) return;
|
|
@@ -2758,9 +2769,10 @@ function createServer(opts) {
|
|
|
2758
2769
|
if (msg.type === "mate_create") {
|
|
2759
2770
|
if (!msg.seedData) return;
|
|
2760
2771
|
try {
|
|
2761
|
-
var
|
|
2772
|
+
var mateCtx4 = mates.buildMateCtx(userId);
|
|
2773
|
+
var mate = mates.createMate(mateCtx4, msg.seedData);
|
|
2762
2774
|
// Register mate as a project
|
|
2763
|
-
var mateDir =
|
|
2775
|
+
var mateDir = mates.getMateDir(mateCtx4, mate.id);
|
|
2764
2776
|
var mateSlug = "mate-" + mate.id;
|
|
2765
2777
|
var mateName = (mate.profile && mate.profile.displayName) || mate.name || "New Mate";
|
|
2766
2778
|
addProject(mateDir, mateSlug, mateName, null, mate.createdBy, null, { isMate: true });
|
|
@@ -2772,14 +2784,27 @@ function createServer(opts) {
|
|
|
2772
2784
|
}
|
|
2773
2785
|
|
|
2774
2786
|
if (msg.type === "mate_list") {
|
|
2775
|
-
var
|
|
2787
|
+
var mateCtx5 = mates.buildMateCtx(userId);
|
|
2788
|
+
var mateList = mates.getAllMates(mateCtx5);
|
|
2789
|
+
// Ensure all mate projects are registered (survives server restarts)
|
|
2790
|
+
for (var mi = 0; mi < mateList.length; mi++) {
|
|
2791
|
+
var m = mateList[mi];
|
|
2792
|
+
var mSlug = "mate-" + m.id;
|
|
2793
|
+
if (!projects.has(mSlug)) {
|
|
2794
|
+
var mDir = mates.getMateDir(mateCtx5, m.id);
|
|
2795
|
+
fs.mkdirSync(mDir, { recursive: true });
|
|
2796
|
+
var mName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
2797
|
+
addProject(mDir, mSlug, mName, null, m.createdBy || userId, null, { isMate: true });
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2776
2800
|
ws.send(JSON.stringify({ type: "mate_list", mates: mateList }));
|
|
2777
2801
|
return;
|
|
2778
2802
|
}
|
|
2779
2803
|
|
|
2780
2804
|
if (msg.type === "mate_delete") {
|
|
2781
2805
|
if (!msg.mateId) return;
|
|
2782
|
-
var
|
|
2806
|
+
var mateCtx6 = mates.buildMateCtx(userId);
|
|
2807
|
+
var result = mates.deleteMate(mateCtx6, msg.mateId);
|
|
2783
2808
|
if (result.error) {
|
|
2784
2809
|
ws.send(JSON.stringify({ type: "mate_error", error: result.error }));
|
|
2785
2810
|
} else {
|
|
@@ -2799,7 +2824,8 @@ function createServer(opts) {
|
|
|
2799
2824
|
|
|
2800
2825
|
if (msg.type === "mate_update") {
|
|
2801
2826
|
if (!msg.mateId || !msg.updates) return;
|
|
2802
|
-
var
|
|
2827
|
+
var mateCtx7 = mates.buildMateCtx(userId);
|
|
2828
|
+
var updated = mates.updateMate(mateCtx7, msg.mateId, msg.updates);
|
|
2803
2829
|
if (updated) {
|
|
2804
2830
|
ws.send(JSON.stringify({ type: "mate_updated", mate: updated }));
|
|
2805
2831
|
// Broadcast update
|