clay-server 2.23.0-beta.1 → 2.23.0-beta.3

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
@@ -791,9 +791,10 @@ var relay = createServer({
791
791
  onGetProjectAccess: function (slug) {
792
792
  for (var i = 0; i < config.projects.length; i++) {
793
793
  if (config.projects[i].slug === slug) {
794
+ var isMateProject = slug.indexOf("mate-") === 0;
794
795
  return {
795
796
  slug: slug,
796
- visibility: config.projects[i].visibility || "public",
797
+ visibility: isMateProject ? "private" : (config.projects[i].visibility || "public"),
797
798
  allowedUsers: config.projects[i].allowedUsers || [],
798
799
  ownerId: config.projects[i].ownerId || null,
799
800
  };
package/lib/mates.js CHANGED
@@ -409,17 +409,12 @@ function enforceTeamAwareness(filePath) {
409
409
  content = content.substring(0, teamIdx).trimEnd() + content.substring(endOfTeam);
410
410
  }
411
411
 
412
- // Insert before project registry, session memory, or crisis safety section if present, otherwise append
413
- var projRegPos = content.indexOf(PROJECT_REGISTRY_MARKER);
414
- var sessionMemPos = content.indexOf(SESSION_MEMORY_MARKER);
415
- var crisisPos = content.indexOf(crisisSafety.MARKER);
412
+ // Insert before the first subsequent system section (in order)
416
413
  var insertBefore = -1;
417
- if (projRegPos !== -1) {
418
- insertBefore = projRegPos;
419
- } else if (sessionMemPos !== -1) {
420
- insertBefore = sessionMemPos;
421
- } else if (crisisPos !== -1) {
422
- insertBefore = crisisPos;
414
+ var teamInsertCandidates = [PROJECT_REGISTRY_MARKER, SESSION_MEMORY_MARKER, STICKY_NOTES_MARKER, DEBATE_AWARENESS_MARKER, crisisSafety.MARKER];
415
+ for (var ti = 0; ti < teamInsertCandidates.length; ti++) {
416
+ var tip = content.indexOf(teamInsertCandidates[ti]);
417
+ if (tip !== -1) { insertBefore = tip; break; }
423
418
  }
424
419
  if (insertBefore !== -1) {
425
420
  content = content.substring(0, insertBefore).trimEnd() + TEAM_SECTION + "\n\n" + content.substring(insertBefore);
package/lib/project.js CHANGED
@@ -1367,7 +1367,7 @@ function createProjectContext(opts) {
1367
1367
  if (active) {
1368
1368
  userPresence.setPresence(slug, presenceKey, active.localId, storedPresence ? storedPresence.mateDm : null);
1369
1369
  }
1370
- if (storedPresence && storedPresence.mateDm) {
1370
+ if (storedPresence && storedPresence.mateDm && !isMate) {
1371
1371
  sendTo(ws, { type: "restore_mate_dm", mateId: storedPresence.mateDm });
1372
1372
  }
1373
1373
 
@@ -1870,8 +1870,12 @@ function createProjectContext(opts) {
1870
1870
  }
1871
1871
 
1872
1872
  if (msg.type === "set_mate_dm") {
1873
- var dmPresKey = ws._clayUser ? ws._clayUser.id : "_default";
1874
- userPresence.setMateDm(slug, dmPresKey, msg.mateId || null);
1873
+ // Only store mateDm on non-mate projects (main project presence).
1874
+ // Mate projects should never hold mateDm to avoid circular restore loops.
1875
+ if (!isMate) {
1876
+ var dmPresKey = ws._clayUser ? ws._clayUser.id : "_default";
1877
+ userPresence.setMateDm(slug, dmPresKey, msg.mateId || null);
1878
+ }
1875
1879
  return;
1876
1880
  }
1877
1881
 
@@ -7041,11 +7045,6 @@ function createProjectContext(opts) {
7041
7045
  var claudeMdPath = path.join(cwd, "CLAUDE.md");
7042
7046
  // Enforce immediately on startup
7043
7047
  try { matesModule.enforceTeamAwareness(claudeMdPath); } catch (e) {}
7044
- try {
7045
- var _projList = getProjectList();
7046
- var _projData = _projList.filter(function (p) { return !p.isMate && !p.isWorktree; }).map(function (p) { return { slug: p.slug, path: p.path, title: p.title || p.project, icon: p.icon }; });
7047
- matesModule.enforceProjectRegistry(claudeMdPath, _projData);
7048
- } catch (e) {}
7049
7048
  try { matesModule.enforceSessionMemory(claudeMdPath); } catch (e) {}
7050
7049
  try { matesModule.enforceStickyNotes(claudeMdPath); } catch (e) {}
7051
7050
  try { matesModule.enforceDebateAwareness(claudeMdPath); } catch (e) {}
@@ -7069,11 +7068,8 @@ function createProjectContext(opts) {
7069
7068
  crisisDebounce = setTimeout(function () {
7070
7069
  crisisDebounce = null;
7071
7070
  try { matesModule.enforceTeamAwareness(claudeMdPath); } catch (e) {}
7072
- try {
7073
- var _projList2 = getProjectList();
7074
- var _projData2 = _projList2.filter(function (p) { return !p.isMate && !p.isWorktree; }).map(function (p) { return { slug: p.slug, path: p.path, title: p.title || p.project, icon: p.icon }; });
7075
- matesModule.enforceProjectRegistry(claudeMdPath, _projData2);
7076
- } catch (e) {}
7071
+ // Note: project registry is NOT enforced in the watcher to avoid
7072
+ // write cascades (server.js scheduleProjectRegistryRefresh handles updates)
7077
7073
  try { matesModule.enforceSessionMemory(claudeMdPath); } catch (e) {}
7078
7074
  try { matesModule.enforceStickyNotes(claudeMdPath); } catch (e) {}
7079
7075
  try { matesModule.enforceDebateAwareness(claudeMdPath); } catch (e) {}
package/lib/public/app.js CHANGED
@@ -607,8 +607,16 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
607
607
  dmTargetUser = targetUser;
608
608
 
609
609
  // Notify server of active mate DM (server-side presence tracking)
610
- if (targetUser && targetUser.isMate && ws && ws.readyState === 1) {
611
- try { ws.send(JSON.stringify({ type: "set_mate_dm", mateId: targetUser.id })); } catch(e) {}
610
+ // IMPORTANT: set_mate_dm must go to the MAIN project, not a mate project WS.
611
+ // When switching between mates, ws points to the current mate project,
612
+ // so we defer sending set_mate_dm until we reconnect to the main project's context.
613
+ // The server will also receive it via the mate project's onDmMessage handler,
614
+ // but the presence should only be stored on the main project slug.
615
+ if (targetUser && targetUser.isMate) {
616
+ // Send to the current WS only if it's the main project (not another mate)
617
+ if (!mateProjectSlug && ws && ws.readyState === 1) {
618
+ try { ws.send(JSON.stringify({ type: "set_mate_dm", mateId: targetUser.id })); } catch(e) {}
619
+ }
612
620
  }
613
621
 
614
622
  // Clear unread for this user
@@ -972,7 +980,8 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
972
980
 
973
981
  function connectMateProject(slug) {
974
982
  mateProjectSlug = slug;
975
- savedMainSlug = currentSlug;
983
+ // Only save the main slug on the FIRST mate switch (preserve original main project)
984
+ if (!savedMainSlug) savedMainSlug = currentSlug;
976
985
  currentSlug = slug;
977
986
  wsPath = "/p/" + slug + "/ws";
978
987
  resetClientState();
@@ -764,8 +764,8 @@ function renderSmtpTab(body, cfg) {
764
764
  // --- Projects ---
765
765
  function loadProjectsTab(body) {
766
766
  var projectList = (ctx && ctx.projectList) || [];
767
- // Exclude worktree projects (they inherit parent settings)
768
- projectList = projectList.filter(function (p) { return !p.isWorktree; });
767
+ // Exclude worktree and mate projects (mates are always private to their owner)
768
+ projectList = projectList.filter(function (p) { return !p.isWorktree && !p.isMate; });
769
769
  cachedProjects = projectList;
770
770
 
771
771
  if (projectList.length === 0) {
package/lib/server.js CHANGED
@@ -2931,28 +2931,9 @@ function createServer(opts) {
2931
2931
  });
2932
2932
  projects.set(slug, ctx);
2933
2933
  ctx.warmup();
2934
- refreshMateProjectRegistries();
2935
2934
  return true;
2936
2935
  }
2937
2936
 
2938
- // --- Refresh project registry on all mate CLAUDE.md files ---
2939
- function refreshMateProjectRegistries() {
2940
- var projList = [];
2941
- projects.forEach(function (ctx) {
2942
- var status = ctx.getStatus();
2943
- if (!status.isMate && !status.isWorktree) {
2944
- projList.push({ slug: status.slug, path: status.path, title: status.title || status.project, icon: status.icon });
2945
- }
2946
- });
2947
- projects.forEach(function (ctx) {
2948
- var status = ctx.getStatus();
2949
- if (status.isMate) {
2950
- var claudeMdPath = path.join(status.path, "CLAUDE.md");
2951
- try { mates.enforceProjectRegistry(claudeMdPath, projList); } catch (e) {}
2952
- }
2953
- });
2954
- }
2955
-
2956
2937
  // --- DM message handler (server-level, cross-project) ---
2957
2938
  function handleDmMessage(ws, msg) {
2958
2939
  if (!users.isMultiUser() || !ws._clayUser) return;
@@ -3335,7 +3316,6 @@ function createServer(opts) {
3335
3316
  if (!ctx) return false;
3336
3317
  ctx.destroy();
3337
3318
  projects.delete(slug);
3338
- refreshMateProjectRegistries();
3339
3319
  return true;
3340
3320
  }
3341
3321
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.23.0-beta.1",
3
+ "version": "2.23.0-beta.3",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",