clay-server 4.0.0-beta.6 → 4.0.0-beta.8

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.
@@ -20,6 +20,7 @@
20
20
  // auth_required forever.
21
21
 
22
22
  var vendorRegistry = require("./yoke/vendor-registry");
23
+ var yoke = require("./yoke");
23
24
 
24
25
  // Login CLIs print a success line and then hand the shell back, so the PTY
25
26
  // itself does not exit. Watch the output stream instead. Deliberately narrow:
@@ -62,7 +63,8 @@ function looksLikeLoginSuccess(text) {
62
63
  * sm, tm, adapters,
63
64
  * send, sendTo,
64
65
  * usersModule,
65
- * getOsUserInfoForWs, getOsUserInfoForLinuxUser, getLinuxUserForSession
66
+ * getOsUserInfoForWs, getOsUserInfoForLinuxUser, getLinuxUserForSession,
67
+ * refreshVendorAuthEverywhere
66
68
  */
67
69
  function attachVendorLogin(ctx) {
68
70
  var slug = ctx.slug;
@@ -80,6 +82,7 @@ function attachVendorLogin(ctx) {
80
82
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
81
83
  var getOsUserInfoForLinuxUser = ctx.getOsUserInfoForLinuxUser;
82
84
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
85
+ var refreshVendorAuthEverywhere = ctx.refreshVendorAuthEverywhere;
83
86
 
84
87
  // vendor -> flow record. At most one live login terminal per vendor.
85
88
  var _flows = Object.create(null);
@@ -181,13 +184,17 @@ function attachVendorLogin(ctx) {
181
184
  // the credentials the login just wrote. Shared adapter instances (Claude) are
182
185
  // reused by every project, so tearing one down here would kill unrelated
183
186
  // sessions; those runtimes pick up new credentials on their own.
184
- function refreshVendorAuth(vendor) {
187
+ function refreshVendorAuth(vendor, linuxUser) {
185
188
  var adapter = adapters[vendor];
186
189
  if (!adapter || typeof adapter.shutdown !== "function") return Promise.resolve(false);
187
190
  if (adapter.shared) {
188
191
  console.log("[vendor-login] " + vendor + " adapter is shared; skipping restart for " + slug);
189
192
  return Promise.resolve(false);
190
193
  }
194
+ if (typeof adapter.refreshAuthIdentity === "function") {
195
+ return Promise.resolve(adapter.refreshAuthIdentity(linuxUser || null));
196
+ }
197
+ if (linuxUser) return Promise.resolve(false);
191
198
  // adapter.shutdown() already fans out to every per-OS-user runtime it
192
199
  // created (shutdownUserRuntimes), so one call covers isolated setups too.
193
200
  return Promise.resolve()
@@ -209,7 +216,16 @@ function attachVendorLogin(ctx) {
209
216
  flow.finishing = true;
210
217
  flow.completed = !!succeeded;
211
218
 
212
- var refresh = succeeded ? refreshVendorAuth(vendor) : Promise.resolve(false);
219
+ // The next session must not reuse the pre-login "not authenticated"
220
+ // result. Re-check before restarting the runtime, which will then read
221
+ // the newly written credential file.
222
+ if (succeeded) yoke.invalidateAuthCache();
223
+ var refresh = Promise.resolve(false);
224
+ if (succeeded) {
225
+ refresh = typeof refreshVendorAuthEverywhere === "function"
226
+ ? refreshVendorAuthEverywhere(vendor, flow.linuxUser || null)
227
+ : refreshVendorAuth(vendor, flow.linuxUser || null);
228
+ }
213
229
  refresh.then(function (restarted) {
214
230
  discardFlow(vendor);
215
231
  closeFlowTerminal(flow);
@@ -391,6 +407,7 @@ function attachVendorLogin(ctx) {
391
407
  listFlows: listFlows,
392
408
  forgetTerminal: forgetTerminal,
393
409
  isLoginTerminal: isLoginTerminal,
410
+ refreshVendorAuth: refreshVendorAuth,
394
411
  };
395
412
  }
396
413
 
package/lib/project.js CHANGED
@@ -1610,6 +1610,7 @@ function createProjectContext(opts) {
1610
1610
  getOsUserInfoForWs: getOsUserInfoForWs,
1611
1611
  getOsUserInfoForLinuxUser: getOsUserInfoForLinuxUser,
1612
1612
  getLinuxUserForSession: getLinuxUserForSession,
1613
+ refreshVendorAuthEverywhere: opts.refreshVendorAuthEverywhere,
1613
1614
  });
1614
1615
 
1615
1616
  // --- Filesystem handler (delegated to project-filesystem.js) ---
@@ -2141,6 +2142,7 @@ function createProjectContext(opts) {
2141
2142
  listKnowledgeFiles: _knowledge.listKnowledgeFiles,
2142
2143
  refreshCapsuleCatalog: _capsuleCatalog.refreshSessions,
2143
2144
  getNotificationsModule: function () { return _notifications; },
2145
+ refreshVendorAuth: _vendorLogin.refreshVendorAuth,
2144
2146
  getSchedules: _loop.getSchedules,
2145
2147
  importSchedule: _loop.importSchedule,
2146
2148
  removeSchedule: _loop.removeSchedule,
@@ -61,6 +61,20 @@ function attachMessageProcessor(ctx) {
61
61
  if (session._lastAuthEmit && (now - session._lastAuthEmit) < 5000) return;
62
62
  session._lastAuthEmit = now;
63
63
 
64
+ // Codex can leave a transient 401 in its app-server event stream after a
65
+ // successful device login. Confirm the CLI's current credential state
66
+ // before interrupting another session with a login modal.
67
+ var vendor = session.vendor || "claude";
68
+ yoke.invalidateAuthCache();
69
+ var authState = yoke.checkAuth();
70
+ if (authState[vendor]) {
71
+ sendAndRecord(session, {
72
+ type: "error",
73
+ text: "Authentication is available, but " + vendor + " reported an auth-like error. Try again.",
74
+ });
75
+ return;
76
+ }
77
+
64
78
  var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
65
79
  var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
66
80
  var canAutoLogin = !usersModule.isMultiUser()
@@ -76,7 +90,7 @@ function attachMessageProcessor(ctx) {
76
90
  sendToSession(session, {
77
91
  type: "auth_required",
78
92
  text: authTitle,
79
- vendor: session.vendor || "claude",
93
+ vendor: vendor,
80
94
  loginCommand: loginCommand,
81
95
  linuxUser: authLinuxUser,
82
96
  canAutoLogin: canAutoLogin,
@@ -88,7 +102,7 @@ function attachMessageProcessor(ctx) {
88
102
  slug: slug,
89
103
  sessionId: session.localId,
90
104
  ownerId: session.ownerId || null,
91
- vendor: session.vendor || "claude",
105
+ vendor: vendor,
92
106
  loginCommand: loginCommand,
93
107
  linuxUser: authLinuxUser,
94
108
  canAutoLogin: canAutoLogin,
package/lib/server.js CHANGED
@@ -1155,6 +1155,24 @@ function createServer(opts) {
1155
1155
  }
1156
1156
 
1157
1157
  // --- Project management ---
1158
+ function refreshVendorAuthEverywhere(vendor, linuxUser) {
1159
+ var refreshes = [];
1160
+ projects.forEach(function (projectCtx) {
1161
+ if (!projectCtx || typeof projectCtx.refreshVendorAuth !== "function") return;
1162
+ try {
1163
+ refreshes.push(Promise.resolve(projectCtx.refreshVendorAuth(vendor, linuxUser)).catch(function (err) {
1164
+ console.error("[server] Cross-project " + vendor + " auth refresh failed:", err && err.message ? err.message : err);
1165
+ return false;
1166
+ }));
1167
+ } catch (err) {
1168
+ console.error("[server] Cross-project " + vendor + " auth refresh threw:", err && err.message ? err.message : err);
1169
+ }
1170
+ });
1171
+ return Promise.all(refreshes).then(function (results) {
1172
+ return results.some(function (refreshed) { return !!refreshed; });
1173
+ });
1174
+ }
1175
+
1158
1176
  function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
1159
1177
  if (projects.has(slug)) return false;
1160
1178
  var extra = extraOpts || {};
@@ -1327,6 +1345,7 @@ function createServer(opts) {
1327
1345
  onDmMessage: handleDmMessage,
1328
1346
  broadcastAll: broadcastAll,
1329
1347
  notificationsModule: _globalNotifications,
1348
+ refreshVendorAuthEverywhere: refreshVendorAuthEverywhere,
1330
1349
  getProject: function (s) { return projects.get(s) || null; },
1331
1350
  isUserOnline: isUserOnline,
1332
1351
  });
@@ -1943,6 +1943,23 @@ function createCodexAdapter(opts) {
1943
1943
  ]).then(function() { return true; });
1944
1944
  },
1945
1945
 
1946
+ // A device login writes credentials for one OS identity. Refresh only
1947
+ // that identity in OS-isolated deployments; single-user projects restart
1948
+ // their sole app-server.
1949
+ refreshAuthIdentity: function(linuxUser) {
1950
+ if (!_runtimeLinuxUser && linuxUser) {
1951
+ var userRuntime = _userRuntimes[linuxUser];
1952
+ if (!userRuntime) return Promise.resolve(false);
1953
+ return userRuntime.shutdown().then(function() {
1954
+ delete _userRuntimes[linuxUser];
1955
+ return true;
1956
+ });
1957
+ }
1958
+ if (_runtimeLinuxUser && linuxUser !== _runtimeLinuxUser) return Promise.resolve(false);
1959
+ if (_requiresLinuxUser) return Promise.resolve(false);
1960
+ return adapter.shutdown();
1961
+ },
1962
+
1946
1963
  shutdownIfIdle: function(idleMs) {
1947
1964
  if (!_runtimeLinuxUser && Object.keys(_userRuntimes).length > 0) {
1948
1965
  return shutdownUserRuntimes("shutdownIfIdle", idleMs).then(function(results) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.0.0-beta.6",
3
+ "version": "4.0.0-beta.8",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",