clay-server 2.9.1 → 2.9.3-beta.1

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/bin/cli.js CHANGED
@@ -28,7 +28,7 @@ if (_isDev) process.env.CLAY_DEV = "1";
28
28
  var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
29
29
  var { sendIPCCommand } = require("../lib/ipc");
30
30
  var { generateAuthToken } = require("../lib/server");
31
- var { enableMultiUser, hasAdmin, isMultiUser } = require("../lib/users");
31
+ var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser } = require("../lib/users");
32
32
 
33
33
  function openUrl(url) {
34
34
  try {
@@ -2319,7 +2319,7 @@ function showSettingsMenu(config, ip) {
2319
2319
  items.push({ label: "Set PIN", value: "pin" });
2320
2320
  }
2321
2321
  if (muEnabled) {
2322
- items.push({ label: "Multi-user mode (enabled)", value: "multi_user" });
2322
+ items.push({ label: "Disable multi-user mode", value: "disable_multi_user" });
2323
2323
  } else {
2324
2324
  items.push({ label: "Enable multi-user mode", value: "multi_user" });
2325
2325
  }
@@ -2365,39 +2365,51 @@ function showSettingsMenu(config, ip) {
2365
2365
  break;
2366
2366
 
2367
2367
  case "multi_user":
2368
- if (muEnabled && hasAdmin()) {
2368
+ var muResult = enableMultiUser();
2369
+ log(sym.bar);
2370
+ log(sym.bar + " " + a.yellow + sym.warn + " Experimental Feature" + a.reset);
2371
+ log(sym.bar);
2372
+ log(sym.bar + " " + a.dim + "Multi-user mode is experimental and may change in future releases." + a.reset);
2373
+ log(sym.bar + " " + a.dim + "Sharing access to AI-powered tools may be subject to your provider's" + a.reset);
2374
+ log(sym.bar + " " + a.dim + "terms of service. Please review the applicable usage policies before" + a.reset);
2375
+ log(sym.bar + " " + a.dim + "granting access to other users." + a.reset);
2376
+ log(sym.bar);
2377
+ if (muResult.setupCode) {
2378
+ log(sym.bar + " " + a.green + "Multi-user mode enabled." + a.reset);
2369
2379
  log(sym.bar);
2370
- log(sym.bar + " " + a.dim + "Multi-user mode is already enabled and an admin account exists." + a.reset);
2371
- log(sym.bar + " " + a.dim + "No changes made." + a.reset);
2380
+ log(sym.bar + " Setup code: " + a.bold + muResult.setupCode + a.reset);
2372
2381
  log(sym.bar);
2373
- promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2374
- showSettingsMenu(config, ip);
2375
- });
2382
+ log(sym.bar + " " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
2383
+ log(sym.bar + " " + a.dim + "The code is single-use and will be cleared once the admin is set up." + a.reset);
2376
2384
  } else {
2377
- var muResult = enableMultiUser();
2378
- log(sym.bar);
2379
- log(sym.bar + " " + a.yellow + sym.warn + " Experimental Feature" + a.reset);
2380
- log(sym.bar);
2381
- log(sym.bar + " " + a.dim + "Multi-user mode is experimental and may change in future releases." + a.reset);
2382
- log(sym.bar + " " + a.dim + "Sharing access to AI-powered tools may be subject to your provider's" + a.reset);
2383
- log(sym.bar + " " + a.dim + "terms of service. Please review the applicable usage policies before" + a.reset);
2384
- log(sym.bar + " " + a.dim + "granting access to other users." + a.reset);
2385
- log(sym.bar);
2386
- if (muResult.setupCode) {
2387
- log(sym.bar + " " + a.green + "Multi-user mode enabled." + a.reset);
2385
+ log(sym.bar + " " + a.dim + "Multi-user mode is already enabled." + a.reset);
2386
+ }
2387
+ log(sym.bar);
2388
+ promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2389
+ showSettingsMenu(config, ip);
2390
+ });
2391
+ break;
2392
+
2393
+ case "disable_multi_user":
2394
+ log(sym.bar);
2395
+ log(sym.bar + " " + a.yellow + sym.warn + " Disable multi-user mode?" + a.reset);
2396
+ log(sym.bar);
2397
+ log(sym.bar + " " + a.dim + "Sessions created by other users will no longer be visible." + a.reset);
2398
+ log(sym.bar + " " + a.dim + "User accounts will be preserved and restored if re-enabled." + a.reset);
2399
+ log(sym.bar);
2400
+ promptSelect("Confirm", [
2401
+ { label: "Disable multi-user mode", value: "confirm" },
2402
+ { label: "Cancel", value: "cancel" },
2403
+ ], function (confirmChoice) {
2404
+ if (confirmChoice === "confirm") {
2405
+ disableMultiUser();
2388
2406
  log(sym.bar);
2389
- log(sym.bar + " Setup code: " + a.bold + muResult.setupCode + a.reset);
2407
+ log(sym.done + " " + a.green + "Multi-user mode disabled." + a.reset);
2408
+ log(sym.bar + " " + a.dim + "Restart the daemon for changes to take full effect." + a.reset);
2390
2409
  log(sym.bar);
2391
- log(sym.bar + " " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
2392
- log(sym.bar + " " + a.dim + "The code is single-use and will be cleared once the admin is set up." + a.reset);
2393
- } else {
2394
- log(sym.bar + " " + a.dim + "Multi-user mode is already enabled." + a.reset);
2395
2410
  }
2396
- log(sym.bar);
2397
- promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
2398
- showSettingsMenu(config, ip);
2399
- });
2400
- }
2411
+ showSettingsMenu(config, ip);
2412
+ });
2401
2413
  break;
2402
2414
 
2403
2415
  case "logs":
@@ -2456,7 +2468,12 @@ var currentVersion = require("../package.json").version;
2456
2468
  clearStaleConfig();
2457
2469
  await new Promise(function (resolve) { setTimeout(resolve, 500); });
2458
2470
  }
2459
- // First rungo through setup (disclaimer, port, PIN, etc.)
2471
+ // No running daemon clear config so setup runs fresh
2472
+ if (!devAlive && devConfig) {
2473
+ if (devConfig.pid) clearStaleConfig();
2474
+ devConfig = null;
2475
+ }
2476
+ // No config — go through setup (disclaimer, port, PIN, etc.)
2460
2477
  if (!devConfig) {
2461
2478
  setup(function (pin, keepAwake) {
2462
2479
  devMode(pin, keepAwake, null);
package/lib/project.js CHANGED
@@ -156,6 +156,15 @@ function createProjectContext(opts) {
156
156
  }
157
157
  }
158
158
 
159
+ function sendToSession(sessionId, obj) {
160
+ var data = JSON.stringify(obj);
161
+ for (var ws of clients) {
162
+ if (ws.readyState === 1 && ws._clayActiveSession === sessionId) {
163
+ ws.send(data);
164
+ }
165
+ }
166
+ }
167
+
159
168
  function sendToSessionOthers(sender, sessionId, obj) {
160
169
  var data = JSON.stringify(obj);
161
170
  for (var ws of clients) {
@@ -712,7 +721,7 @@ function createProjectContext(opts) {
712
721
  session.isProcessing = true;
713
722
  onProcessingChanged();
714
723
  session.sentToolResults = {};
715
- send({ type: "status", status: "processing" });
724
+ sendToSession(session.localId, { type: "status", status: "processing" });
716
725
  session.acceptEditsAfterStart = true;
717
726
  session.singleTurn = true;
718
727
  sdk.startQuery(session, loopState.promptText);
@@ -1025,12 +1034,14 @@ function createProjectContext(opts) {
1025
1034
  });
1026
1035
  }
1027
1036
 
1028
- // Session list (filtered for multi-user)
1037
+ // Session list (filtered for access control)
1029
1038
  var allSessions = [].concat(Array.from(sm.sessions.values())).filter(function (s) { return !s.hidden; });
1030
1039
  if (usersModule.isMultiUser() && wsUser) {
1031
1040
  allSessions = allSessions.filter(function (s) {
1032
1041
  return usersModule.canAccessSession(wsUser.id, s, { visibility: "public" });
1033
1042
  });
1043
+ } else if (!usersModule.isMultiUser()) {
1044
+ allSessions = allSessions.filter(function (s) { return !s.ownerId; });
1034
1045
  }
1035
1046
  sendTo(ws, {
1036
1047
  type: "session_list",
@@ -1057,12 +1068,23 @@ function createProjectContext(opts) {
1057
1068
  }),
1058
1069
  });
1059
1070
 
1060
- // Restore active session for this client (check access in multi-user mode)
1071
+ // Restore active session for this client (check access)
1061
1072
  var active = sm.getActiveSession();
1062
1073
  if (active && usersModule.isMultiUser() && wsUser) {
1063
1074
  if (!usersModule.canAccessSession(wsUser.id, active, { visibility: "public" })) {
1064
1075
  active = null;
1065
1076
  }
1077
+ } else if (active && !usersModule.isMultiUser() && active.ownerId) {
1078
+ active = null;
1079
+ }
1080
+ // Fallback: pick the most recent accessible session
1081
+ if (!active && allSessions.length > 0) {
1082
+ active = allSessions[0];
1083
+ for (var fi = 1; fi < allSessions.length; fi++) {
1084
+ if ((allSessions[fi].lastActivity || 0) > (active.lastActivity || 0)) {
1085
+ active = allSessions[fi];
1086
+ }
1087
+ }
1066
1088
  }
1067
1089
  if (active) {
1068
1090
  ws._clayActiveSession = active.localId;
@@ -1110,6 +1132,10 @@ function createProjectContext(opts) {
1110
1132
  }
1111
1133
 
1112
1134
  // --- WS message handler ---
1135
+ function getSessionForWs(ws) {
1136
+ return sm.sessions.get(ws._clayActiveSession) || null;
1137
+ }
1138
+
1113
1139
  function handleMessage(ws, msg) {
1114
1140
  if (msg.type === "push_subscribe") {
1115
1141
  if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription, msg.replaceEndpoint);
@@ -1117,7 +1143,7 @@ function createProjectContext(opts) {
1117
1143
  }
1118
1144
 
1119
1145
  if (msg.type === "load_more_history") {
1120
- var session = sm.getActiveSession();
1146
+ var session = getSessionForWs(ws);
1121
1147
  if (!session || typeof msg.before !== "number") return;
1122
1148
  var before = msg.before;
1123
1149
  var from = sm.findTurnBoundary(session.history, Math.max(0, before - sm.HISTORY_PAGE_SIZE));
@@ -1135,9 +1161,9 @@ function createProjectContext(opts) {
1135
1161
  var sessionOpts = {};
1136
1162
  if (ws._clayUser) sessionOpts.ownerId = ws._clayUser.id;
1137
1163
  if (msg.sessionVisibility) sessionOpts.sessionVisibility = msg.sessionVisibility;
1138
- var newSess = sm.createSession(sessionOpts, usersModule.isMultiUser() ? ws : undefined);
1164
+ var newSess = sm.createSession(sessionOpts, ws);
1165
+ ws._clayActiveSession = newSess.localId;
1139
1166
  if (usersModule.isMultiUser()) {
1140
- ws._clayActiveSession = newSess.localId;
1141
1167
  broadcastPresence();
1142
1168
  }
1143
1169
  return;
@@ -1161,11 +1187,11 @@ function createProjectContext(opts) {
1161
1187
  break;
1162
1188
  }
1163
1189
  }
1164
- var _rws = usersModule.isMultiUser() ? ws : undefined;
1165
- sm.resumeSession(msg.cliSessionId, { history: history, title: title }, _rws);
1190
+ var resumed = sm.resumeSession(msg.cliSessionId, { history: history, title: title }, ws);
1191
+ if (resumed) ws._clayActiveSession = resumed.localId;
1166
1192
  }).catch(function () {
1167
- var _rws = usersModule.isMultiUser() ? ws : undefined;
1168
- sm.resumeSession(msg.cliSessionId, undefined, _rws);
1193
+ var resumed = sm.resumeSession(msg.cliSessionId, undefined, ws);
1194
+ if (resumed) ws._clayActiveSession = resumed.localId;
1169
1195
  });
1170
1196
  return;
1171
1197
  }
@@ -1210,7 +1236,8 @@ function createProjectContext(opts) {
1210
1236
  sm.switchSession(msg.id, ws);
1211
1237
  broadcastPresence();
1212
1238
  } else {
1213
- sm.switchSession(msg.id);
1239
+ ws._clayActiveSession = msg.id;
1240
+ sm.switchSession(msg.id, ws);
1214
1241
  }
1215
1242
  }
1216
1243
  return;
@@ -1218,7 +1245,7 @@ function createProjectContext(opts) {
1218
1245
 
1219
1246
  if (msg.type === "delete_session") {
1220
1247
  if (msg.id && sm.sessions.has(msg.id)) {
1221
- sm.deleteSession(msg.id, usersModule.isMultiUser() ? ws : undefined);
1248
+ sm.deleteSession(msg.id, ws);
1222
1249
  }
1223
1250
  return;
1224
1251
  }
@@ -1285,7 +1312,7 @@ function createProjectContext(opts) {
1285
1312
  }
1286
1313
 
1287
1314
  if (msg.type === "stop") {
1288
- var session = sm.getActiveSession();
1315
+ var session = getSessionForWs(ws);
1289
1316
  if (session && session.abortController && session.isProcessing) {
1290
1317
  session.abortController.abort();
1291
1318
  }
@@ -1306,22 +1333,22 @@ function createProjectContext(opts) {
1306
1333
  // Verify target is actually a claude process before killing
1307
1334
  if (!sdk.isClaudeProcess(pid)) {
1308
1335
  console.error("[project] Refused to kill PID " + pid + ": not a claude process");
1309
- send({ type: "error", text: "Process " + pid + " is not a Claude process." });
1336
+ sendTo(ws, { type: "error", text: "Process " + pid + " is not a Claude process." });
1310
1337
  return;
1311
1338
  }
1312
1339
  try {
1313
1340
  process.kill(pid, "SIGTERM");
1314
1341
  console.log("[project] Sent SIGTERM to conflicting Claude process PID " + pid);
1315
- send({ type: "process_killed", pid: pid });
1342
+ sendTo(ws, { type: "process_killed", pid: pid });
1316
1343
  } catch (e) {
1317
1344
  console.error("[project] Failed to kill PID " + pid + ":", e.message);
1318
- send({ type: "error", text: "Failed to kill process " + pid + ": " + (e.message || e) });
1345
+ sendTo(ws, { type: "error", text: "Failed to kill process " + pid + ": " + (e.message || e) });
1319
1346
  }
1320
1347
  return;
1321
1348
  }
1322
1349
 
1323
1350
  if (msg.type === "set_model" && msg.model) {
1324
- var session = sm.getActiveSession();
1351
+ var session = getSessionForWs(ws);
1325
1352
  if (session) {
1326
1353
  sdk.setModel(session, msg.model);
1327
1354
  }
@@ -1332,7 +1359,7 @@ function createProjectContext(opts) {
1332
1359
  if (typeof opts.onSetServerDefaultModel === "function") {
1333
1360
  opts.onSetServerDefaultModel(msg.model);
1334
1361
  }
1335
- var session = sm.getActiveSession();
1362
+ var session = getSessionForWs(ws);
1336
1363
  if (session) {
1337
1364
  sdk.setModel(session, msg.model);
1338
1365
  }
@@ -1343,7 +1370,7 @@ function createProjectContext(opts) {
1343
1370
  if (typeof opts.onSetProjectDefaultModel === "function") {
1344
1371
  opts.onSetProjectDefaultModel(slug, msg.model);
1345
1372
  }
1346
- var session = sm.getActiveSession();
1373
+ var session = getSessionForWs(ws);
1347
1374
  if (session) {
1348
1375
  sdk.setModel(session, msg.model);
1349
1376
  }
@@ -1357,7 +1384,7 @@ function createProjectContext(opts) {
1357
1384
  return;
1358
1385
  }
1359
1386
  sm.currentPermissionMode = msg.mode;
1360
- var session = sm.getActiveSession();
1387
+ var session = getSessionForWs(ws);
1361
1388
  if (session) {
1362
1389
  sdk.setPermissionMode(session, msg.mode);
1363
1390
  }
@@ -1371,7 +1398,7 @@ function createProjectContext(opts) {
1371
1398
  }
1372
1399
  if (!dangerouslySkipPermissions) {
1373
1400
  sm.currentPermissionMode = msg.mode;
1374
- var session = sm.getActiveSession();
1401
+ var session = getSessionForWs(ws);
1375
1402
  if (session) {
1376
1403
  sdk.setPermissionMode(session, msg.mode);
1377
1404
  }
@@ -1386,7 +1413,7 @@ function createProjectContext(opts) {
1386
1413
  }
1387
1414
  if (!dangerouslySkipPermissions) {
1388
1415
  sm.currentPermissionMode = msg.mode;
1389
- var session = sm.getActiveSession();
1416
+ var session = getSessionForWs(ws);
1390
1417
  if (session) {
1391
1418
  sdk.setPermissionMode(session, msg.mode);
1392
1419
  }
@@ -1426,7 +1453,7 @@ function createProjectContext(opts) {
1426
1453
  }
1427
1454
 
1428
1455
  if (msg.type === "rewind_preview") {
1429
- var session = sm.getActiveSession();
1456
+ var session = getSessionForWs(ws);
1430
1457
  if (!session || !session.cliSessionId || !msg.uuid) return;
1431
1458
 
1432
1459
  (async function () {
@@ -1455,7 +1482,7 @@ function createProjectContext(opts) {
1455
1482
  }
1456
1483
 
1457
1484
  if (msg.type === "rewind_execute") {
1458
- var session = sm.getActiveSession();
1485
+ var session = getSessionForWs(ws);
1459
1486
  if (!session || !session.cliSessionId || !msg.uuid) return;
1460
1487
  var mode = msg.mode || "both";
1461
1488
 
@@ -1510,11 +1537,11 @@ function createProjectContext(opts) {
1510
1537
  onProcessingChanged();
1511
1538
 
1512
1539
  sm.saveSessionFile(session);
1513
- sm.switchSession(session.localId, usersModule.isMultiUser() ? ws : undefined);
1540
+ sm.switchSession(session.localId, ws);
1514
1541
  sm.sendAndRecord(session, { type: "rewind_complete", mode: mode });
1515
1542
  sm.broadcastSessionList();
1516
1543
  } catch (err) {
1517
- send({ type: "rewind_error", text: "Rewind failed: " + err.message });
1544
+ sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + err.message });
1518
1545
  } finally {
1519
1546
  if (result && result.isTemp) result.cleanup();
1520
1547
  }
@@ -1523,7 +1550,7 @@ function createProjectContext(opts) {
1523
1550
  }
1524
1551
 
1525
1552
  if (msg.type === "ask_user_response") {
1526
- var session = sm.getActiveSession();
1553
+ var session = getSessionForWs(ws);
1527
1554
  if (!session) return;
1528
1555
  var toolId = msg.toolId;
1529
1556
  var answers = msg.answers || {};
@@ -1544,7 +1571,7 @@ function createProjectContext(opts) {
1544
1571
  }
1545
1572
 
1546
1573
  if (msg.type === "permission_response") {
1547
- var session = sm.getActiveSession();
1574
+ var session = getSessionForWs(ws);
1548
1575
  if (!session) return;
1549
1576
  var requestId = msg.requestId;
1550
1577
  var decision = msg.decision;
@@ -1612,17 +1639,17 @@ function createProjectContext(opts) {
1612
1639
  newSession.title = "Plan execution (cleared context)";
1613
1640
  sm.saveSessionFile(newSession);
1614
1641
  sm.broadcastSessionList();
1615
- send(userMsg);
1642
+ sendToSession(newSession.localId, userMsg);
1616
1643
 
1617
1644
  newSession.isProcessing = true;
1618
1645
  onProcessingChanged();
1619
1646
  newSession.sentToolResults = {};
1620
- send({ type: "status", status: "processing" });
1647
+ sendToSession(newSession.localId, { type: "status", status: "processing" });
1621
1648
  newSession.acceptEditsAfterStart = true;
1622
1649
  sdk.startQuery(newSession, planPrompt);
1623
1650
  } catch (e) {
1624
1651
  console.error("[project] Error starting plan execution:", e);
1625
- send({ type: "error", text: "Failed to start plan execution: " + (e.message || e) });
1652
+ sendTo(ws, { type: "error", text: "Failed to start plan execution: " + (e.message || e) });
1626
1653
  }
1627
1654
  });
1628
1655
  return;
@@ -1640,13 +1667,13 @@ function createProjectContext(opts) {
1640
1667
  var userMsg = { type: "user_message", text: feedback };
1641
1668
  session.history.push(userMsg);
1642
1669
  sm.appendToSessionFile(session, userMsg);
1643
- send(userMsg);
1670
+ sendToSession(session.localId, userMsg);
1644
1671
 
1645
1672
  if (!session.isProcessing) {
1646
1673
  session.isProcessing = true;
1647
1674
  onProcessingChanged();
1648
1675
  session.sentToolResults = {};
1649
- send({ type: "status", status: "processing" });
1676
+ sendToSession(session.localId, { type: "status", status: "processing" });
1650
1677
  if (!session.queryInstance) {
1651
1678
  sdk.startQuery(session, feedback);
1652
1679
  } else {
@@ -2415,11 +2442,11 @@ function createProjectContext(opts) {
2415
2442
  // Send crafting prompt and start the conversation with Claude.
2416
2443
  craftingSession.history.push({ type: "user_message", text: craftingPrompt });
2417
2444
  sm.appendToSessionFile(craftingSession, { type: "user_message", text: craftingPrompt });
2418
- send({ type: "user_message", text: craftingPrompt });
2445
+ sendToSession(craftingSession.localId, { type: "user_message", text: craftingPrompt });
2419
2446
  craftingSession.isProcessing = true;
2420
2447
  onProcessingChanged();
2421
2448
  craftingSession.sentToolResults = {};
2422
- send({ type: "status", status: "processing" });
2449
+ sendToSession(craftingSession.localId, { type: "status", status: "processing" });
2423
2450
  sdk.startQuery(craftingSession, craftingPrompt);
2424
2451
 
2425
2452
  send({ type: "ralph_crafting_started", sessionId: craftingSession.localId, taskId: newLoopId, source: recordSource });
@@ -2609,7 +2636,7 @@ function createProjectContext(opts) {
2609
2636
  if (msg.type !== "message") return;
2610
2637
  if (!msg.text && (!msg.images || msg.images.length === 0) && (!msg.pastes || msg.pastes.length === 0)) return;
2611
2638
 
2612
- var session = sm.getActiveSession();
2639
+ var session = getSessionForWs(ws);
2613
2640
  if (!session) return;
2614
2641
 
2615
2642
  var userMsg = { type: "user_message", text: msg.text || "" };
@@ -2641,7 +2668,7 @@ function createProjectContext(opts) {
2641
2668
  session.isProcessing = true;
2642
2669
  onProcessingChanged();
2643
2670
  session.sentToolResults = {};
2644
- send({ type: "status", status: "processing" });
2671
+ sendToSession(session.localId, { type: "status", status: "processing" });
2645
2672
  if (!session.queryInstance) {
2646
2673
  sdk.startQuery(session, fullText, msg.images);
2647
2674
  } else {
package/lib/public/app.js CHANGED
@@ -2659,9 +2659,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2659
2659
  // Non-multi-user mode: simple count in topbar
2660
2660
  if (!msg.users) {
2661
2661
  var countEl = document.getElementById("client-count");
2662
- if (countEl) {
2662
+ var countTextEl = document.getElementById("client-count-text");
2663
+ if (countEl && countTextEl) {
2663
2664
  if (msg.count > 1) {
2664
- countEl.textContent = msg.count;
2665
+ countTextEl.textContent = msg.count + " connected";
2665
2666
  countEl.classList.remove("hidden");
2666
2667
  } else {
2667
2668
  countEl.classList.add("hidden");
@@ -407,13 +407,10 @@
407
407
  .notif-action.copied { color: var(--success); }
408
408
 
409
409
  #client-count {
410
- display: inline-flex;
411
- align-items: center;
412
410
  cursor: default;
411
+ font-size: 11px;
413
412
  }
414
413
 
415
- #client-count.hidden { display: none; }
416
-
417
414
  .client-avatar {
418
415
  width: 22px;
419
416
  height: 22px;
@@ -43,7 +43,7 @@
43
43
  <div class="top-bar-actions">
44
44
  <!-- Pill badges -->
45
45
  <div id="skip-perms-pill" class="top-bar-pill pill-error hidden"><i data-lucide="shield-off"></i> <span>Skip perms</span></div>
46
- <span id="client-count" class="hidden"></span>
46
+ <div id="client-count" class="top-bar-pill pill-accent hidden"><i data-lucide="users"></i> <span id="client-count-text"></span></div>
47
47
  <label id="theme-toggle-btn" class="theme-toggle" title="Toggle dark/light mode">
48
48
  <input type="checkbox" id="theme-toggle-check">
49
49
  <span class="theme-toggle-track">
package/lib/sdk-bridge.js CHANGED
@@ -111,9 +111,7 @@ function createSDKBridge(opts) {
111
111
  if (parsed.session_id && !session.cliSessionId) {
112
112
  session.cliSessionId = parsed.session_id;
113
113
  sm.saveSessionFile(session);
114
- if (session.localId === sm.activeSessionId) {
115
- send({ type: "session_id", cliSessionId: session.cliSessionId });
116
- }
114
+ sendAndRecord(session, { type: "session_id", cliSessionId: session.cliSessionId });
117
115
  } else if (parsed.session_id) {
118
116
  session.cliSessionId = parsed.session_id;
119
117
  }
@@ -704,7 +702,7 @@ function createSDKBridge(opts) {
704
702
  try {
705
703
  sdk = await getSDK();
706
704
  } catch (e) {
707
- send({ type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
705
+ sendAndRecord(session, { type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
708
706
  throw e;
709
707
  }
710
708
  var mq = createMessageQueue();
@@ -738,7 +736,7 @@ function createSDKBridge(opts) {
738
736
  } catch (e) {
739
737
  session.isProcessing = false;
740
738
  onProcessingChanged();
741
- send({ type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
739
+ sendAndRecord(session, { type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
742
740
  sendAndRecord(session, { type: "done", code: 1 });
743
741
  sm.broadcastSessionList();
744
742
  return;
@@ -831,7 +829,7 @@ function createSDKBridge(opts) {
831
829
  session.queryInstance = null;
832
830
  session.messageQueue = null;
833
831
  session.abortController = null;
834
- send({ type: "error", text: "Failed to start query: " + (e.message || e) });
832
+ sendAndRecord(session, { type: "error", text: "Failed to start query: " + (e.message || e) });
835
833
  sendAndRecord(session, { type: "done", code: 1 });
836
834
  sm.broadcastSessionList();
837
835
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.9.1",
3
+ "version": "2.9.3-beta.1",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "dev": "node bin/cli.js --dev",
11
11
  "prepack": "npm pkg delete bin.clay-dev",
12
- "postpack": "npm pkg set bin.clay-dev=./bin/cli.js"
12
+ "postpack": "npm pkg set bin.clay-dev=./bin/cli.js",
13
+ "semantic-release": "semantic-release"
13
14
  },
14
15
  "files": [
15
16
  "bin/",
@@ -45,5 +46,11 @@
45
46
  "license": "MIT",
46
47
  "engines": {
47
48
  "node": ">=20.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@semantic-release/changelog": "^6.0.3",
52
+ "@semantic-release/exec": "^7.1.0",
53
+ "@semantic-release/git": "^10.0.1",
54
+ "semantic-release": "^25.0.3"
48
55
  }
49
56
  }