clay-server 2.25.1-beta.1 → 2.26.0-beta.2

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
@@ -1507,6 +1507,7 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1507
1507
  if (prevProjectMap[cwd]) {
1508
1508
  if (prevProjectMap[cwd].visibility) cwdEntry.visibility = prevProjectMap[cwd].visibility;
1509
1509
  if (prevProjectMap[cwd].allowedUsers) cwdEntry.allowedUsers = prevProjectMap[cwd].allowedUsers;
1510
+ if (prevProjectMap[cwd].ownerId) cwdEntry.ownerId = prevProjectMap[cwd].ownerId;
1510
1511
  }
1511
1512
  allProjects.push(cwdEntry);
1512
1513
  usedSlugs.push(slug);
@@ -1525,6 +1526,7 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
1525
1526
  if (prevProjectMap[rp.path]) {
1526
1527
  if (prevProjectMap[rp.path].visibility) rpEntry.visibility = prevProjectMap[rp.path].visibility;
1527
1528
  if (prevProjectMap[rp.path].allowedUsers) rpEntry.allowedUsers = prevProjectMap[rp.path].allowedUsers;
1529
+ if (prevProjectMap[rp.path].ownerId) rpEntry.ownerId = prevProjectMap[rp.path].ownerId;
1528
1530
  }
1529
1531
  allProjects.push(rpEntry);
1530
1532
  }
@@ -1879,19 +1881,13 @@ async function restartDaemonWithTLS(config, callback) {
1879
1881
  }
1880
1882
  clearStaleConfig();
1881
1883
 
1882
- // Re-fork with TLS
1883
- var newConfig = {
1884
+ // Re-fork with TLS (preserve all existing config fields)
1885
+ var newConfig = Object.assign({}, config, {
1884
1886
  pid: null,
1885
- port: config.port,
1886
- pinHash: config.pinHash || null,
1887
1887
  tls: true,
1888
1888
  builtinCert: hasBuiltinCert,
1889
1889
  mkcertDetected: mkcertDetected,
1890
- debug: config.debug || false,
1891
- keepAwake: config.keepAwake || false,
1892
- dangerouslySkipPermissions: config.dangerouslySkipPermissions || false,
1893
- projects: config.projects || [],
1894
- };
1890
+ });
1895
1891
 
1896
1892
  ensureConfigDir();
1897
1893
  saveConfig(newConfig);
package/lib/os-users.js CHANGED
@@ -254,6 +254,26 @@ function toLinuxUsername(clayUsername) {
254
254
  return name;
255
255
  }
256
256
 
257
+ /**
258
+ * Ensure linger is enabled for a Linux user so systemd creates /run/user/<uid>.
259
+ * Required for CLI tools like gcloud and gh that need XDG_RUNTIME_DIR.
260
+ */
261
+ function ensureLinger(username) {
262
+ try {
263
+ var uid = execSync("id -u " + username, { encoding: "utf8", timeout: 5000, stdio: "pipe" }).trim();
264
+ var lingerFile = "/var/lib/systemd/linger/" + username;
265
+ if (fs.existsSync(lingerFile)) return;
266
+ execSync("loginctl enable-linger " + username, {
267
+ encoding: "utf8",
268
+ timeout: 10000,
269
+ stdio: "pipe",
270
+ });
271
+ console.log("[os-users] Enabled linger for " + username + " (uid " + uid + ")");
272
+ } catch (e) {
273
+ console.warn("[os-users] Failed to enable linger for " + username + ": " + (e.stderr || e.message || "").trim());
274
+ }
275
+ }
276
+
257
277
  /**
258
278
  * Check if a Linux user already exists.
259
279
  */
@@ -353,6 +373,7 @@ function provisionLinuxUser(clayUsername) {
353
373
  timeout: 15000,
354
374
  stdio: "pipe",
355
375
  });
376
+ ensureLinger(linuxName);
356
377
  console.log("[os-users] Provisioned Linux user: " + linuxName + " (Clay user: " + clayUsername + ")");
357
378
  installClaudeCli(linuxName);
358
379
  return { ok: true, linuxUser: linuxName };
@@ -383,6 +404,8 @@ function provisionAllUsers(usersModule) {
383
404
  console.log("[os-users] Claude CLI missing for " + user.linuxUser + ", installing...");
384
405
  installClaudeCli(user.linuxUser);
385
406
  }
407
+ // Ensure linger is enabled for existing users
408
+ ensureLinger(user.linuxUser);
386
409
  result.skipped.push({ id: user.id, username: user.username, linuxUser: user.linuxUser });
387
410
  continue;
388
411
  }
package/lib/project.js CHANGED
@@ -3328,7 +3328,7 @@ function createProjectContext(opts) {
3328
3328
  return;
3329
3329
  }
3330
3330
  }
3331
- var t = tm.create(msg.cols || 80, msg.rows || 24, getOsUserInfoForWs(ws));
3331
+ var t = tm.create(msg.cols || 80, msg.rows || 24, getOsUserInfoForWs(ws), ws);
3332
3332
  if (!t) {
3333
3333
  sendTo(ws, { type: "term_error", error: "Cannot create terminal (node-pty not available or limit reached)" });
3334
3334
  return;
@@ -3356,7 +3356,7 @@ function createProjectContext(opts) {
3356
3356
 
3357
3357
  if (msg.type === "term_resize") {
3358
3358
  if (msg.id && msg.cols > 0 && msg.rows > 0) {
3359
- tm.resize(msg.id, msg.cols, msg.rows);
3359
+ tm.resize(msg.id, msg.cols, msg.rows, ws);
3360
3360
  }
3361
3361
  return;
3362
3362
  }
package/lib/public/app.js CHANGED
@@ -11,7 +11,7 @@ import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEna
11
11
  import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent, setScheduleBtnDisabled, setScheduleDelayMs, clearScheduleDelay } from './modules/input.js';
12
12
  import { initQrCode, triggerShare } from './modules/qrcode.js';
13
13
  import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
14
- import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
14
+ import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
15
15
  import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible } from './modules/sticky-notes.js';
16
16
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
17
17
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
@@ -4809,6 +4809,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4809
4809
  handleTermOutput(msg);
4810
4810
  break;
4811
4811
 
4812
+ case "term_resized":
4813
+ handleTermResized(msg);
4814
+ break;
4815
+
4812
4816
  case "term_exited":
4813
4817
  handleTermExited(msg);
4814
4818
  break;
@@ -1615,8 +1615,12 @@ function renderSheetTools(listEl) {
1615
1615
  var isMateDm = document.body.classList.contains("mate-dm-active");
1616
1616
 
1617
1617
  var items = isMateDm ? [
1618
+ { icon: "brain", label: "Memory", action: "mate-memory" },
1618
1619
  { icon: "book-open", label: "Knowledge", action: "mate-knowledge" },
1619
- { icon: "calendar-clock", label: "Scheduled Tasks", action: "mate-scheduler" }
1620
+ { icon: "sticky-note", label: "Sticky Notes", action: "mate-sticky" },
1621
+ { icon: "puzzle", label: "Skills", action: "mate-skills" },
1622
+ { icon: "calendar-clock", label: "Scheduled Tasks", action: "mate-scheduler" },
1623
+ { icon: "mic", label: "Debate", action: "mate-debate" }
1620
1624
  ] : [
1621
1625
  { icon: "folder-tree", label: "Files", action: "files" },
1622
1626
  { icon: "square-terminal", label: "Terminal", action: "terminal" },
@@ -1641,11 +1645,15 @@ function renderSheetTools(listEl) {
1641
1645
  setTimeout(function () { openMobileSheet("mate-knowledge"); }, 250);
1642
1646
  return;
1643
1647
  } else if (item.action === "mate-sticky") {
1644
- targetId = "sticky-notes-toggle-btn";
1648
+ targetId = "mate-sticky-notes-btn";
1645
1649
  } else if (item.action === "mate-skills") {
1646
1650
  targetId = "mate-skills-btn";
1651
+ } else if (item.action === "mate-memory") {
1652
+ targetId = "mate-memory-btn";
1647
1653
  } else if (item.action === "mate-scheduler") {
1648
1654
  targetId = "mate-scheduler-btn";
1655
+ } else if (item.action === "mate-debate") {
1656
+ targetId = "mate-debate-btn";
1649
1657
  }
1650
1658
  if (targetId) {
1651
1659
  var targetBtn = document.getElementById(targetId);
@@ -653,6 +653,14 @@ export function handleTermOutput(msg) {
653
653
  }
654
654
  }
655
655
 
656
+ export function handleTermResized(msg) {
657
+ if (!msg.id) return;
658
+ var tab = tabs.get(msg.id);
659
+ if (tab && tab.xterm && msg.cols > 0 && msg.rows > 0) {
660
+ tab.xterm.resize(msg.cols, msg.rows);
661
+ }
662
+ }
663
+
656
664
  export function handleTermExited(msg) {
657
665
  if (!msg.id) return;
658
666
  var tab = tabs.get(msg.id);
@@ -16,7 +16,7 @@ function createTerminalManager(opts) {
16
16
  var nextId = 1;
17
17
  var terminals = new Map(); // id -> terminal session
18
18
 
19
- function create(cols, rows, osUserInfo) {
19
+ function create(cols, rows, osUserInfo, ownerWs) {
20
20
  if (terminals.size >= MAX_TERMINALS) return null;
21
21
 
22
22
  var pty = createTerminal(cwd, cols, rows, osUserInfo);
@@ -34,6 +34,7 @@ function createTerminalManager(opts) {
34
34
  exited: false,
35
35
  exitCode: null,
36
36
  subscribers: new Set(),
37
+ ownerWs: ownerWs || null,
37
38
  };
38
39
 
39
40
  pty.onData(function (data) {
@@ -84,6 +85,11 @@ function createTerminalManager(opts) {
84
85
  sendTo(ws, { type: "term_output", id: id, data: replay });
85
86
  }
86
87
 
88
+ // Send current terminal dimensions so the client renders at the correct size
89
+ if (!alreadySubscribed && session.cols && session.rows) {
90
+ sendTo(ws, { type: "term_resized", id: id, cols: session.cols, rows: session.rows });
91
+ }
92
+
87
93
  // If already exited, notify
88
94
  if (session.exited) {
89
95
  sendTo(ws, { type: "term_exited", id: id });
@@ -111,14 +117,22 @@ function createTerminalManager(opts) {
111
117
  }
112
118
  }
113
119
 
114
- function resize(id, cols, rows) {
120
+ function resize(id, cols, rows, sourceWs) {
115
121
  var session = terminals.get(id);
116
122
  if (!session || !session.pty) return;
123
+ // Only the terminal owner can resize the PTY.
124
+ // Observers resizing would cause SIGWINCH and flood the owner with escape sequences.
125
+ if (session.ownerWs && sourceWs && sourceWs !== session.ownerWs) return;
117
126
  if (cols > 0 && rows > 0) {
118
127
  try {
119
128
  session.pty.resize(cols, rows);
120
129
  session.cols = cols;
121
130
  session.rows = rows;
131
+ // Notify other subscribers about the resize so their xterm stays in sync
132
+ var msg = JSON.stringify({ type: "term_resized", id: id, cols: cols, rows: rows });
133
+ for (var ws of session.subscribers) {
134
+ if (ws.readyState === 1 && ws !== sourceWs) ws.send(msg);
135
+ }
122
136
  } catch (e) {}
123
137
  }
124
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.25.1-beta.1",
3
+ "version": "2.26.0-beta.2",
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",