clay-server 2.41.1-beta.1 → 2.42.0-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/lib/cli-sessions.js
CHANGED
|
@@ -222,9 +222,9 @@ function appendCliRecord(obj, state, history) {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
function readCliSessionHistory(cwd, sessionId) {
|
|
225
|
+
function readCliSessionHistory(home, cwd, sessionId) {
|
|
226
226
|
var encoded = encodeCwd(cwd);
|
|
227
|
-
var filePath = path.join(REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
|
|
227
|
+
var filePath = path.join(home || REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
|
|
228
228
|
|
|
229
229
|
return new Promise(function (resolve) {
|
|
230
230
|
var history = [];
|
|
@@ -265,15 +265,15 @@ function readCliSessionHistory(cwd, sessionId) {
|
|
|
265
265
|
// are small enough that blocking on a local read is fine.
|
|
266
266
|
// Modified-time (ms) of a CLI session's jsonl, or 0 if missing. Lets callers
|
|
267
267
|
// cheaply detect that the transcript grew (e.g. after a TUI turn) and re-read.
|
|
268
|
-
function cliSessionFileMtime(cwd, sessionId) {
|
|
268
|
+
function cliSessionFileMtime(home, cwd, sessionId) {
|
|
269
269
|
var encoded = encodeCwd(cwd);
|
|
270
|
-
var filePath = path.join(REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
|
|
270
|
+
var filePath = path.join(home || REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
|
|
271
271
|
try { return fs.statSync(filePath).mtimeMs; } catch (e) { return 0; }
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
function readCliSessionHistorySync(cwd, sessionId) {
|
|
274
|
+
function readCliSessionHistorySync(home, cwd, sessionId) {
|
|
275
275
|
var encoded = encodeCwd(cwd);
|
|
276
|
-
var filePath = path.join(REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
|
|
276
|
+
var filePath = path.join(home || REAL_HOME, ".claude", "projects", encoded, sessionId + ".jsonl");
|
|
277
277
|
var raw;
|
|
278
278
|
try { raw = fs.readFileSync(filePath, "utf8"); } catch (e) { return []; }
|
|
279
279
|
var history = [];
|
package/lib/project-sessions.js
CHANGED
|
@@ -260,7 +260,8 @@ function attachSessions(ctx) {
|
|
|
260
260
|
}
|
|
261
261
|
var sid = session.cliSessionId;
|
|
262
262
|
var localId = session.localId;
|
|
263
|
-
var
|
|
263
|
+
var resumeSkip = session.dangerouslySkipPermissions ? " --dangerously-skip-permissions" : "";
|
|
264
|
+
var cmd = "claude --resume " + sid + resumeSkip + "; exit\n";
|
|
264
265
|
var term = tm.create(80, 24, getOsUserInfoForWs(ws), ws, {
|
|
265
266
|
initialInput: cmd,
|
|
266
267
|
kind: "tui-session",
|
|
@@ -302,7 +303,8 @@ function attachSessions(ctx) {
|
|
|
302
303
|
// turn appended messages), not just when history is empty. The earlier
|
|
303
304
|
// "already hydrated" short-circuit kept stale history after a
|
|
304
305
|
// Resume -> chat -> Close cycle (showed A instead of A').
|
|
305
|
-
var
|
|
306
|
+
var home = resolveSessionHome(session);
|
|
307
|
+
var mtime = cliSess.cliSessionFileMtime(home, cwd, session.cliSessionId);
|
|
306
308
|
var fresh = (typeof session.terminalId !== "number") &&
|
|
307
309
|
Array.isArray(session.history) && session.history.length > 0 &&
|
|
308
310
|
session._historyMtime === mtime;
|
|
@@ -311,7 +313,7 @@ function attachSessions(ctx) {
|
|
|
311
313
|
// Synchronous read: switch_session must populate session.history before
|
|
312
314
|
// the session_switched broadcast replays it. readCliSessionHistory is
|
|
313
315
|
// Promise-based and would resolve too late (the transcript came up empty).
|
|
314
|
-
try { history = cliSess.readCliSessionHistorySync(cwd, session.cliSessionId); } catch (e) { history = null; }
|
|
316
|
+
try { history = cliSess.readCliSessionHistorySync(home, cwd, session.cliSessionId); } catch (e) { history = null; }
|
|
315
317
|
if (Array.isArray(history)) {
|
|
316
318
|
session.history = history;
|
|
317
319
|
session._historyMtime = mtime;
|
|
@@ -436,11 +438,15 @@ function attachSessions(ctx) {
|
|
|
436
438
|
sessionOpts.mode = "tui";
|
|
437
439
|
sessionOpts.cliSessionId = crypto.randomUUID();
|
|
438
440
|
sessionOpts.vendor = sessionOpts.vendor || "claude";
|
|
441
|
+
// Per-session bypass-permissions: TUI shell command only. The flag is
|
|
442
|
+
// persisted on the session so lazy-resume re-spawns the same way.
|
|
443
|
+
if (msg.dangerouslySkipPermissions) sessionOpts.dangerouslySkipPermissions = true;
|
|
439
444
|
newSess = sm.createSessionRaw(sessionOpts);
|
|
440
445
|
if (tm) {
|
|
441
446
|
var tuiSid = newSess.cliSessionId;
|
|
442
447
|
var tuiLocalId = newSess.localId;
|
|
443
|
-
var
|
|
448
|
+
var tuiSkip = newSess.dangerouslySkipPermissions ? " --dangerously-skip-permissions" : "";
|
|
449
|
+
var tuiCmd = "claude --session-id " + tuiSid + tuiSkip + "; exit\n";
|
|
444
450
|
var tuiTerm = tm.create(80, 24, getOsUserInfoForWs(ws), ws, {
|
|
445
451
|
initialInput: tuiCmd,
|
|
446
452
|
kind: "tui-session",
|
|
@@ -1200,7 +1206,7 @@ function attachSessions(ctx) {
|
|
|
1200
1206
|
} else {
|
|
1201
1207
|
// Read history from CLI session files
|
|
1202
1208
|
var cliSess = require("./cli-sessions");
|
|
1203
|
-
return cliSess.readCliSessionHistory(cwd, result.sessionId).then(function(history) {
|
|
1209
|
+
return cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId).then(function(history) {
|
|
1204
1210
|
var forked = sm.resumeSession(result.sessionId, { history: history, title: forkTitle }, ws);
|
|
1205
1211
|
if (forked) {
|
|
1206
1212
|
ws._clayActiveSession = forked.localId;
|
|
@@ -1040,6 +1040,28 @@
|
|
|
1040
1040
|
opacity: 1;
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
|
+
/* Split button: main action + chevron sharing one grid cell. */
|
|
1044
|
+
.session-top-action-split {
|
|
1045
|
+
display: flex;
|
|
1046
|
+
align-items: stretch;
|
|
1047
|
+
gap: 2px;
|
|
1048
|
+
min-width: 0;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.session-top-action-split .split-main {
|
|
1052
|
+
flex: 1 1 auto;
|
|
1053
|
+
width: auto;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
.session-top-action-split .split-chevron {
|
|
1057
|
+
flex: 0 0 auto;
|
|
1058
|
+
width: 28px;
|
|
1059
|
+
min-width: 28px;
|
|
1060
|
+
padding: 0;
|
|
1061
|
+
justify-content: center;
|
|
1062
|
+
gap: 0;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1043
1065
|
.session-favorites-section {
|
|
1044
1066
|
min-height: 6px;
|
|
1045
1067
|
margin: 0 8px;
|
|
@@ -296,11 +296,14 @@ function renderSessionTopActions() {
|
|
|
296
296
|
var wrap = document.createElement("div");
|
|
297
297
|
wrap.className = "session-top-actions";
|
|
298
298
|
|
|
299
|
-
// Claude:
|
|
300
|
-
// (
|
|
301
|
-
//
|
|
299
|
+
// Claude: split button. The main button creates a session using the user's
|
|
300
|
+
// claudeOpenMode pref (server applies it). The chevron opens a menu with
|
|
301
|
+
// alternate launch modes (e.g. bypass-permissions, TUI shell only).
|
|
302
|
+
var claudeCell = document.createElement("div");
|
|
303
|
+
claudeCell.className = "session-top-action-split";
|
|
304
|
+
|
|
302
305
|
var claudeBtn = document.createElement("button");
|
|
303
|
-
claudeBtn.className = "session-top-action";
|
|
306
|
+
claudeBtn.className = "session-top-action split-main";
|
|
304
307
|
claudeBtn.type = "button";
|
|
305
308
|
claudeBtn.title = "New Claude session";
|
|
306
309
|
claudeBtn.innerHTML = '<img src="/claude-code-avatar.png" class="session-top-action-icon" alt=""><span>Claude</span>';
|
|
@@ -309,7 +312,23 @@ function renderSessionTopActions() {
|
|
|
309
312
|
getWs().send(JSON.stringify({ type: "new_session", vendor: "claude" }));
|
|
310
313
|
}
|
|
311
314
|
});
|
|
312
|
-
|
|
315
|
+
claudeCell.appendChild(claudeBtn);
|
|
316
|
+
|
|
317
|
+
var claudeChevron = document.createElement("button");
|
|
318
|
+
claudeChevron.className = "session-top-action split-chevron";
|
|
319
|
+
claudeChevron.type = "button";
|
|
320
|
+
claudeChevron.title = "More Claude launch options";
|
|
321
|
+
claudeChevron.setAttribute("aria-label", "More Claude launch options");
|
|
322
|
+
claudeChevron.innerHTML = iconHtml("chevron-down");
|
|
323
|
+
claudeChevron.addEventListener("click", function (e) {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
e.stopPropagation();
|
|
326
|
+
if (sessionCtxMenu) { closeSessionCtxMenu(); return; }
|
|
327
|
+
showClaudeStartMenu(claudeChevron);
|
|
328
|
+
});
|
|
329
|
+
claudeCell.appendChild(claudeChevron);
|
|
330
|
+
|
|
331
|
+
wrap.appendChild(claudeCell);
|
|
313
332
|
|
|
314
333
|
// Codex: always GUI (no TUI adapter for Codex).
|
|
315
334
|
var codexBtn = document.createElement("button");
|
|
@@ -327,6 +346,53 @@ function renderSessionTopActions() {
|
|
|
327
346
|
return wrap;
|
|
328
347
|
}
|
|
329
348
|
|
|
349
|
+
// Dropdown anchored to the Claude split-button chevron. Reuses the
|
|
350
|
+
// session-ctx-menu element/var so the global document click handler closes it.
|
|
351
|
+
function showClaudeStartMenu(anchorBtn) {
|
|
352
|
+
closeSessionCtxMenu();
|
|
353
|
+
|
|
354
|
+
var menu = document.createElement("div");
|
|
355
|
+
menu.className = "session-ctx-menu";
|
|
356
|
+
|
|
357
|
+
var skipItem = document.createElement("button");
|
|
358
|
+
skipItem.className = "session-ctx-item";
|
|
359
|
+
skipItem.innerHTML = iconHtml("shield-off") + " <span>Skip permissions (TUI)</span>";
|
|
360
|
+
skipItem.title = "Start a terminal session with --dangerously-skip-permissions";
|
|
361
|
+
skipItem.addEventListener("click", function (e) {
|
|
362
|
+
e.stopPropagation();
|
|
363
|
+
closeSessionCtxMenu();
|
|
364
|
+
if (getWs() && store.get('connected')) {
|
|
365
|
+
getWs().send(JSON.stringify({
|
|
366
|
+
type: "new_session",
|
|
367
|
+
vendor: "claude",
|
|
368
|
+
mode: "tui",
|
|
369
|
+
dangerouslySkipPermissions: true,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
menu.appendChild(skipItem);
|
|
374
|
+
|
|
375
|
+
document.body.appendChild(menu);
|
|
376
|
+
sessionCtxMenu = menu;
|
|
377
|
+
refreshIcons();
|
|
378
|
+
|
|
379
|
+
requestAnimationFrame(function () {
|
|
380
|
+
var btnRect = anchorBtn.getBoundingClientRect();
|
|
381
|
+
menu.style.position = "fixed";
|
|
382
|
+
menu.style.top = (btnRect.bottom + 2) + "px";
|
|
383
|
+
menu.style.left = btnRect.left + "px";
|
|
384
|
+
menu.style.right = "auto";
|
|
385
|
+
var menuRect = menu.getBoundingClientRect();
|
|
386
|
+
if (menuRect.right > window.innerWidth - 8) {
|
|
387
|
+
menu.style.left = "auto";
|
|
388
|
+
menu.style.right = (window.innerWidth - btnRect.right) + "px";
|
|
389
|
+
}
|
|
390
|
+
if (menuRect.bottom > window.innerHeight - 8) {
|
|
391
|
+
menu.style.top = (btnRect.top - menuRect.height - 2) + "px";
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
330
396
|
function runSessionSearch(query) {
|
|
331
397
|
var normalizedQuery = query || "";
|
|
332
398
|
var trimmedQuery = normalizedQuery.trim();
|
package/lib/sessions.js
CHANGED
|
@@ -100,6 +100,9 @@ function createSessionManager(opts) {
|
|
|
100
100
|
// the click handler will respawn the PTY via `claude --resume` when
|
|
101
101
|
// the user reopens the session.
|
|
102
102
|
if (session.mode === "tui") metaObj.mode = "tui";
|
|
103
|
+
// Born-TUI sessions launched in bypass-permissions mode persist the flag
|
|
104
|
+
// so lazy-resume (`claude --resume`) re-spawns with the same flag.
|
|
105
|
+
if (session.dangerouslySkipPermissions) metaObj.dangerouslySkipPermissions = true;
|
|
103
106
|
if (session.sessionVisibility) metaObj.sessionVisibility = session.sessionVisibility;
|
|
104
107
|
if (session.bookmarked) metaObj.bookmarked = true;
|
|
105
108
|
if (typeof session.favoriteOrder === "number") metaObj.favoriteOrder = session.favoriteOrder;
|
|
@@ -212,6 +215,7 @@ function createSessionManager(opts) {
|
|
|
212
215
|
// here so it shows up in the sidebar with the right icon; the
|
|
213
216
|
// switch_session handler respawns the PTY on click.
|
|
214
217
|
session.mode = (m.mode === "tui") ? "tui" : "gui";
|
|
218
|
+
session.dangerouslySkipPermissions = !!m.dangerouslySkipPermissions;
|
|
215
219
|
session.terminalId = null;
|
|
216
220
|
session.runtimeMode = null;
|
|
217
221
|
session.runtimeTerminalId = null;
|
|
@@ -497,6 +501,7 @@ function createSessionManager(opts) {
|
|
|
497
501
|
favoriteOrder: null,
|
|
498
502
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
499
503
|
mode: (sessionOpts && sessionOpts.mode === "tui") ? "tui" : "gui",
|
|
504
|
+
dangerouslySkipPermissions: !!(sessionOpts && sessionOpts.dangerouslySkipPermissions),
|
|
500
505
|
terminalId: null,
|
|
501
506
|
};
|
|
502
507
|
sessions.set(localId, session);
|
package/lib/ws-schema.js
CHANGED
|
@@ -19,7 +19,7 @@ var schema = {
|
|
|
19
19
|
"switch_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Switch the active session by local ID" },
|
|
20
20
|
"resume_tui_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Spawn the claude --resume PTY for a TUI session shown read-only (lazy resume)" },
|
|
21
21
|
"suspend_tui_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Close a live TUI session's PTY now but keep it resumable (explicit Close)" },
|
|
22
|
-
"new_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Create a new blank session" },
|
|
22
|
+
"new_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Create a new blank session (opts: vendor, mode, sessionVisibility, dangerouslySkipPermissions)" },
|
|
23
23
|
"delete_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a session by ID" },
|
|
24
24
|
"rename_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Rename a session" },
|
|
25
25
|
"set_session_bookmark": { direction: "c2s", handler: "lib/project-sessions.js", description: "Bookmark or unbookmark a session in the sidebar" },
|
package/package.json
CHANGED