clay-server 2.34.0-beta.7 → 2.34.0-beta.9

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/os-users.js CHANGED
@@ -4,6 +4,11 @@
4
4
  var fs = require("fs");
5
5
  var path = require("path");
6
6
  var execFileSync = require("child_process").execFileSync;
7
+ var _aclGrantCache = Object.create(null);
8
+
9
+ function aclCacheKey(projectPath, linuxUser) {
10
+ return path.resolve(projectPath) + "::" + linuxUser;
11
+ }
7
12
 
8
13
  function isSafeLinuxUsername(username) {
9
14
  return typeof username === "string" && /^[a-z_][a-z0-9_-]*[$]?$/.test(username);
@@ -191,6 +196,10 @@ function grantProjectAccess(projectPath, linuxUser) {
191
196
  console.error("[os-users] Invalid Linux username for ACL grant: " + linuxUser);
192
197
  return;
193
198
  }
199
+ var cacheKey = aclCacheKey(projectPath, linuxUser);
200
+ if (_aclGrantCache[cacheKey]) {
201
+ return;
202
+ }
194
203
  try {
195
204
  // Recursive ACL for existing files
196
205
  execFileSync("setfacl", ["-R", "-m", "u:" + linuxUser + ":rwX", projectPath], {
@@ -204,8 +213,10 @@ function grantProjectAccess(projectPath, linuxUser) {
204
213
  timeout: 30000,
205
214
  stdio: "pipe",
206
215
  });
216
+ _aclGrantCache[cacheKey] = true;
207
217
  console.log("[os-users] Granted ACL access for " + linuxUser + " on " + projectPath);
208
218
  } catch (e) {
219
+ delete _aclGrantCache[cacheKey];
209
220
  var errMsg = (e.stderr || e.message || "").toString();
210
221
  if (errMsg.indexOf("not found") !== -1 || errMsg.indexOf("ENOENT") !== -1) {
211
222
  var cmd = getAclInstallCommand();
@@ -229,6 +240,7 @@ function revokeProjectAccess(projectPath, linuxUser) {
229
240
  console.error("[os-users] Invalid Linux username for ACL revoke: " + linuxUser);
230
241
  return;
231
242
  }
243
+ var cacheKey = aclCacheKey(projectPath, linuxUser);
232
244
  try {
233
245
  execFileSync("setfacl", ["-R", "-x", "u:" + linuxUser, projectPath], {
234
246
  encoding: "utf8",
@@ -240,6 +252,7 @@ function revokeProjectAccess(projectPath, linuxUser) {
240
252
  timeout: 30000,
241
253
  stdio: "pipe",
242
254
  });
255
+ delete _aclGrantCache[cacheKey];
243
256
  console.log("[os-users] Revoked ACL access for " + linuxUser + " on " + projectPath);
244
257
  } catch (e) {
245
258
  var errMsg = (e.stderr || e.message || "").toString();
@@ -82,16 +82,7 @@ function attachConnection(ctx) {
82
82
  setTimeout(function() { _loop.resumeLoop(); }, 500);
83
83
  }
84
84
 
85
- // Auto-assign owner if project has none and a user connects (e.g. IPC-added projects)
86
85
  var projectOwnerId = getProjectOwnerId();
87
- if (!projectOwnerId && ws._clayUser && ws._clayUser.id && !isMate) {
88
- setProjectOwnerId(ws._clayUser.id);
89
- projectOwnerId = ws._clayUser.id;
90
- if (opts.onProjectOwnerChanged) {
91
- opts.onProjectOwnerChanged(slug, projectOwnerId);
92
- }
93
- console.log("[project] Auto-assigned owner for " + slug + ": " + projectOwnerId);
94
- }
95
86
 
96
87
  // Send cached state
97
88
  var _userId = ws._clayUser ? ws._clayUser.id : null;
@@ -39,7 +39,7 @@ function formatAskUserAnswerAsMessage(input, answers) {
39
39
  * sm, sdk, tm, clients,
40
40
  * send, sendTo, sendToAdmins, sendToSession, sendToSessionOthers,
41
41
  * opts, usersModule, userPresence, matesModule, pushModule,
42
- * getSessionForWs, getLinuxUserForSession, getOsUserInfoForWs,
42
+ * getSessionForWs, getLinuxUserForSession, ensureProjectAccessForSession, getOsUserInfoForWs,
43
43
  * hydrateImageRefs, onProcessingChanged, broadcastPresence,
44
44
  * adapter, getProjectList, getProjectCount, getScheduleCount,
45
45
  * moveScheduleToProject, moveAllSchedulesToProject, getHubSchedules,
@@ -70,6 +70,7 @@ function attachSessions(ctx) {
70
70
  var pushModule = ctx.pushModule;
71
71
  var getSessionForWs = ctx.getSessionForWs;
72
72
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
73
+ var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
73
74
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
74
75
  var hydrateImageRefs = ctx.hydrateImageRefs;
75
76
  var onProcessingChanged = ctx.onProcessingChanged;
@@ -803,7 +804,7 @@ function attachSessions(ctx) {
803
804
  session.sentToolResults = {};
804
805
  sendToSession(session.localId, { type: "status", status: "processing" });
805
806
  if (!session.queryInstance && !session.worker) {
806
- sdk.startQuery(session, answerText, undefined, getLinuxUserForSession(session));
807
+ sdk.startQuery(session, answerText, undefined, ensureProjectAccessForSession(session));
807
808
  } else {
808
809
  sdk.pushMessage(session, answerText);
809
810
  }
@@ -932,7 +933,7 @@ function attachSessions(ctx) {
932
933
  newSession.sentToolResults = {};
933
934
  sendToSession(newSession.localId, { type: "status", status: "processing" });
934
935
  newSession.acceptEditsAfterStart = true;
935
- sdk.startQuery(newSession, planPrompt, undefined, getLinuxUserForSession(newSession));
936
+ sdk.startQuery(newSession, planPrompt, undefined, ensureProjectAccessForSession(newSession));
936
937
  } catch (e) {
937
938
  console.error("[project] Error starting plan execution:", e);
938
939
  sendTo(ws, { type: "error", text: "Failed to start plan execution: " + (e.message || e) });
@@ -963,7 +964,7 @@ function attachSessions(ctx) {
963
964
  session.sentToolResults = {};
964
965
  sendToSession(session.localId, { type: "status", status: "processing" });
965
966
  if (!session.queryInstance && !session.worker) {
966
- sdk.startQuery(session, feedback, undefined, getLinuxUserForSession(session));
967
+ sdk.startQuery(session, feedback, undefined, ensureProjectAccessForSession(session));
967
968
  } else {
968
969
  sdk.pushMessage(session, feedback);
969
970
  }
@@ -13,7 +13,7 @@ var fs = require("fs");
13
13
  * send, sendTo, sendToSession, sendToSessionOthers,
14
14
  * clients, opts,
15
15
  * usersModule, matesModule,
16
- * getSessionForWs, getLinuxUserForSession, getOsUserInfoForWs,
16
+ * getSessionForWs, getLinuxUserForSession, ensureProjectAccessForSession, getOsUserInfoForWs,
17
17
  * hydrateImageRefs, saveImageFile, imagesDir,
18
18
  * onProcessingChanged, onSessionDone,
19
19
  * _loop - { handleLoopMessage: fn(ws, msg) }
@@ -49,6 +49,7 @@ function attachUserMessage(ctx) {
49
49
 
50
50
  var getSessionForWs = ctx.getSessionForWs;
51
51
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
52
+ var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
52
53
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
53
54
 
54
55
  var hydrateImageRefs = ctx.hydrateImageRefs;
@@ -281,7 +282,7 @@ function attachUserMessage(ctx) {
281
282
  nowSession.isProcessing = true;
282
283
  onProcessingChanged();
283
284
  sendToSession(nowSession.localId, { type: "status", status: "processing" });
284
- sdk.startQuery(nowSession, schedText, null, getLinuxUserForSession(nowSession));
285
+ sdk.startQuery(nowSession, schedText, null, ensureProjectAccessForSession(nowSession));
285
286
  sm.broadcastSessionList();
286
287
  return true;
287
288
  }
@@ -484,7 +485,7 @@ function attachUserMessage(ctx) {
484
485
  // No active query (or worker idle between queries): start a new query
485
486
  session._queryStartTs = Date.now();
486
487
  console.log("[PERF] project.js: startQuery called, localId=" + session.localId + " t=0ms");
487
- sdk.startQuery(session, finalText, msg.images, getLinuxUserForSession(session));
488
+ sdk.startQuery(session, finalText, msg.images, ensureProjectAccessForSession(session));
488
489
  } else {
489
490
  sdk.pushMessage(session, finalText, msg.images);
490
491
  }
package/lib/project.js CHANGED
@@ -9,7 +9,7 @@ var { createNotesManager } = require("./notes");
9
9
  var { fetchLatestVersion, fetchVersion, isNewer } = require("./updater");
10
10
  var { execFileSync, spawn } = require("child_process");
11
11
  var usersModule = require("./users");
12
- var { resolveOsUserInfo, fsAsUser } = require("./os-users");
12
+ var { resolveOsUserInfo, fsAsUser, grantProjectAccess } = require("./os-users");
13
13
  var crisisSafety = require("./crisis-safety");
14
14
  var matesModule = require("./mates");
15
15
  var sessionSearch = require("./session-search");
@@ -160,6 +160,7 @@ function createProjectContext(opts) {
160
160
  var serverTls = opts.tls || false;
161
161
  var serverAuthToken = opts.authToken || null;
162
162
  var latestVersion = null;
163
+ var sessionTitleMigrationScheduled = false;
163
164
 
164
165
  // --- YOKE adapters (multi-vendor, lazy init) ---
165
166
  var _yokeState = yoke.createAdapters({ cwd: cwd, slug: slug });
@@ -187,6 +188,14 @@ function createProjectContext(opts) {
187
188
  return user.linuxUser;
188
189
  }
189
190
 
191
+ function ensureProjectAccessForSession(session) {
192
+ var linuxUser = getLinuxUserForSession(session);
193
+ if (linuxUser) {
194
+ grantProjectAccess(cwd, linuxUser);
195
+ }
196
+ return linuxUser;
197
+ }
198
+
190
199
  function getLinuxUserForWs(ws) {
191
200
  if (!osUsers) return null;
192
201
  if (!ws._clayUser || !ws._clayUser.linuxUser) return null;
@@ -799,7 +808,7 @@ function createProjectContext(opts) {
799
808
  session.isProcessing = true;
800
809
  onProcessingChanged();
801
810
  sendToSession(session.localId, { type: "status", status: "processing" });
802
- sdk.startQuery(session, text, null, getLinuxUserForSession(session));
811
+ sdk.startQuery(session, text, null, ensureProjectAccessForSession(session));
803
812
  sm.broadcastSessionList();
804
813
  }, schedDelay),
805
814
  };
@@ -1133,6 +1142,7 @@ function createProjectContext(opts) {
1133
1142
  pushModule: pushModule,
1134
1143
  getSessionForWs: getSessionForWs,
1135
1144
  getLinuxUserForSession: getLinuxUserForSession,
1145
+ ensureProjectAccessForSession: ensureProjectAccessForSession,
1136
1146
  getOsUserInfoForWs: getOsUserInfoForWs,
1137
1147
  hydrateImageRefs: hydrateImageRefs,
1138
1148
  onProcessingChanged: onProcessingChanged,
@@ -1181,6 +1191,7 @@ function createProjectContext(opts) {
1181
1191
  matesModule: matesModule,
1182
1192
  getSessionForWs: getSessionForWs,
1183
1193
  getLinuxUserForSession: getLinuxUserForSession,
1194
+ ensureProjectAccessForSession: ensureProjectAccessForSession,
1184
1195
  getOsUserInfoForWs: getOsUserInfoForWs,
1185
1196
  hydrateImageRefs: hydrateImageRefs,
1186
1197
  saveImageFile: saveImageFile,
@@ -1380,7 +1391,16 @@ function createProjectContext(opts) {
1380
1391
  warmup: function () {
1381
1392
  sdk.warmup();
1382
1393
  sdk.startIdleReaper();
1383
- sm.migrateSessionTitles(adapter, cwd);
1394
+ if (!osUsers && !sessionTitleMigrationScheduled) {
1395
+ sessionTitleMigrationScheduled = true;
1396
+ setTimeout(function () {
1397
+ try {
1398
+ sm.migrateSessionTitles(adapter, cwd);
1399
+ } catch (e) {
1400
+ console.error("[project] Session title migration failed for " + slug + ":", e && e.message ? e.message : e);
1401
+ }
1402
+ }, 5000);
1403
+ }
1384
1404
  },
1385
1405
  });
1386
1406
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.34.0-beta.7",
3
+ "version": "2.34.0-beta.9",
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",