clay-server 2.26.0-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 +5 -9
- package/lib/os-users.js +23 -0
- package/lib/project.js +2 -2
- package/lib/public/app.js +5 -1
- package/lib/public/modules/terminal.js +8 -0
- package/lib/terminal-manager.js +16 -2
- package/package.json +1 -1
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
|
-
|
|
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;
|
|
@@ -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);
|
package/lib/terminal-manager.js
CHANGED
|
@@ -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
|
}
|