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 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
- var allMates = mates.getAllMates();
941
- for (var mi = 0; mi < allMates.length; mi++) {
942
- var m = allMates[mi];
943
- var mateDir = path.join(mates.MATES_DIR, m.id);
944
- var mateSlug = "mate-" + m.id;
945
- var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
946
- if (fs.existsSync(mateDir)) {
947
- console.log("[daemon] Adding mate project:", mateSlug);
948
- relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true });
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 is a mate (AI persona)
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.isMate(userId);
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
- var MATES_FILE = path.join(config.CONFIG_DIR, "mates.json");
7
- var MATES_DIR = path.join(process.cwd(), ".claude", "mates");
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 loadMates() {
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(MATES_FILE, "utf8");
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
- fs.mkdirSync(path.dirname(MATES_FILE), { recursive: true });
30
- var tmpPath = MATES_FILE + ".tmp";
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, MATES_FILE);
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(seedData, userId) {
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 = path.join(MATES_DIR, id);
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 = path.join(MATES_DIR, id);
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 getMatesByUser(userId) {
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(MATES_DIR, id);
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
- MATES_FILE: MATES_FILE,
210
- MATES_DIR: MATES_DIR,
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");
@@ -798,8 +798,10 @@
798
798
  .mate-knowledge-header {
799
799
  display: flex;
800
800
  align-items: center;
801
- padding: 10px 16px;
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
- padding: 10px 16px;
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: 0 10px 10px;
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
- padding: 10px 16px;
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 {
@@ -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 }));
@@ -182,6 +182,7 @@ function startRecording() {
182
182
 
183
183
  ctx.inputEl.value = text;
184
184
  autoResize();
185
+ if (ctx.scrollToBottom) ctx.scrollToBottom();
185
186
  };
186
187
 
187
188
  recognition.onerror = function(e) {
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 mateList = mates.getAllMates();
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
- if (mates.isMate(msg.targetUserId)) {
2627
- var mate = mates.getMate(msg.targetUserId);
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: "mate-" + mate.id,
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
- if (parts[0] === "mate" && mates.isMate(parts[1])) {
2694
- var mate = mates.getMate(parts[1]);
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 mate = mates.createMate(msg.seedData, userId);
2772
+ var mateCtx4 = mates.buildMateCtx(userId);
2773
+ var mate = mates.createMate(mateCtx4, msg.seedData);
2762
2774
  // Register mate as a project
2763
- var mateDir = path.join(mates.MATES_DIR, mate.id);
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 mateList = mates.getAllMates();
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 result = mates.deleteMate(msg.mateId);
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 updated = mates.updateMate(msg.mateId, msg.updates);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.13.0-beta.5",
3
+ "version": "2.13.0-beta.7",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",