clay-server 2.39.0-beta.3 → 2.39.0-beta.5
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/daemon.js +116 -0
- package/lib/project-sessions.js +36 -112
- package/lib/public/app.js +1 -3
- package/lib/public/css/sidebar.css +0 -29
- package/lib/public/css/tui-attention.css +10 -0
- package/lib/public/css/user-settings.css +43 -0
- package/lib/public/index.html +17 -33
- package/lib/public/modules/app-messages.js +1 -5
- package/lib/public/modules/session-tui-view.js +163 -30
- package/lib/public/modules/sidebar-mobile.js +1 -11
- package/lib/public/modules/sidebar-sessions.js +5 -100
- package/lib/public/modules/terminal-prefs.js +53 -0
- package/lib/public/modules/terminal.js +18 -2
- package/lib/public/modules/theme.js +89 -20
- package/lib/public/modules/tui-attention.js +35 -10
- package/lib/public/modules/user-settings.js +73 -25
- package/lib/server-settings.js +55 -0
- package/lib/sessions.js +137 -1
- package/lib/users-preferences.js +49 -3
- package/lib/users.js +4 -0
- package/lib/ws-schema.js +0 -3
- package/lib/yoke/adapters/claude-worker.js +19 -0
- package/lib/yoke/adapters/claude.js +18 -0
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -728,6 +728,19 @@ var relay = createServer({
|
|
|
728
728
|
console.log("[daemon] Chat layout:", val, "(web)");
|
|
729
729
|
return { ok: true, chatLayout: val };
|
|
730
730
|
},
|
|
731
|
+
onSetTerminalFont: function (family, size) {
|
|
732
|
+
var DEFAULT_FAMILY = "'SF Mono', Menlo, Monaco, 'Courier New', monospace";
|
|
733
|
+
var current = config.terminalFont || { family: DEFAULT_FAMILY, size: 13 };
|
|
734
|
+
var nextFamily = (typeof family === "string" && family.trim()) ? family.trim().slice(0, 200) : current.family;
|
|
735
|
+
var nextSize = current.size;
|
|
736
|
+
if (typeof size === "number" && size >= 9 && size <= 32) {
|
|
737
|
+
nextSize = Math.round(size);
|
|
738
|
+
}
|
|
739
|
+
config.terminalFont = { family: nextFamily, size: nextSize };
|
|
740
|
+
saveConfig(config);
|
|
741
|
+
console.log("[daemon] Terminal font:", nextFamily, nextSize);
|
|
742
|
+
return config.terminalFont;
|
|
743
|
+
},
|
|
731
744
|
onSetMateOnboarded: function () {
|
|
732
745
|
config.mateOnboardingShown = true;
|
|
733
746
|
saveConfig(config);
|
|
@@ -1248,11 +1261,114 @@ var ipc = createIPCServer(socketPath(), function (msg) {
|
|
|
1248
1261
|
}
|
|
1249
1262
|
});
|
|
1250
1263
|
|
|
1264
|
+
// --- Cleanup stale SDK warmup CLI session files ---
|
|
1265
|
+
//
|
|
1266
|
+
// On adapter init the SDK is given a dummy "hi" prompt to discover models,
|
|
1267
|
+
// skills, and capabilities. Even though we abort before any response
|
|
1268
|
+
// arrives, the SDK creates a real CLI session jsonl on disk. Going forward
|
|
1269
|
+
// we delete those inline (see lib/yoke/adapters/claude.js and
|
|
1270
|
+
// claude-worker.js), but installs from before that fix can have a long
|
|
1271
|
+
// tail of "hi"-only sessions polluting the Resume CLI session dialog.
|
|
1272
|
+
//
|
|
1273
|
+
// On every daemon startup we scan REAL_HOME/.claude/projects/*/ and unlink
|
|
1274
|
+
// files that match a strict warmup signature so legitimate "hi" sessions
|
|
1275
|
+
// from real users are never touched. All checks must pass:
|
|
1276
|
+
//
|
|
1277
|
+
// 1. file size < 64 KB (real sessions grow past this fast;
|
|
1278
|
+
// threshold is generous because warmup files include the SDK's
|
|
1279
|
+
// skill_listing / deferred_tools attachments which can run into the
|
|
1280
|
+
// tens of KB on its own)
|
|
1281
|
+
// 2. mtime older than 60 seconds (avoid racing an in-flight warmup)
|
|
1282
|
+
// 3. exactly one user message, content text equals literal "hi"
|
|
1283
|
+
// 4. zero assistant messages (real users virtually always get a reply)
|
|
1284
|
+
//
|
|
1285
|
+
// Multi-user OS-isolated installs have per-user ~/.claude trees that the
|
|
1286
|
+
// daemon process can't traverse; the inline cleanup in claude-worker.js
|
|
1287
|
+
// covers those.
|
|
1288
|
+
function cleanupSdkWarmupSessions() {
|
|
1289
|
+
var projectsRoot = path.join(REAL_HOME, ".claude", "projects");
|
|
1290
|
+
var projectDirs;
|
|
1291
|
+
try { projectDirs = fs.readdirSync(projectsRoot, { withFileTypes: true }); }
|
|
1292
|
+
catch (e) { return; }
|
|
1293
|
+
|
|
1294
|
+
var deleted = 0;
|
|
1295
|
+
var nowMs = Date.now();
|
|
1296
|
+
var MAX_BYTES = 64 * 1024;
|
|
1297
|
+
var MIN_AGE_MS = 60 * 1000;
|
|
1298
|
+
|
|
1299
|
+
for (var di = 0; di < projectDirs.length; di++) {
|
|
1300
|
+
if (!projectDirs[di].isDirectory()) continue;
|
|
1301
|
+
var projDir = path.join(projectsRoot, projectDirs[di].name);
|
|
1302
|
+
var files;
|
|
1303
|
+
try { files = fs.readdirSync(projDir); } catch (e) { continue; }
|
|
1304
|
+
|
|
1305
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
1306
|
+
var f = files[fi];
|
|
1307
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
1308
|
+
var fp = path.join(projDir, f);
|
|
1309
|
+
|
|
1310
|
+
var st;
|
|
1311
|
+
try { st = fs.statSync(fp); } catch (e) { continue; }
|
|
1312
|
+
if (st.size >= MAX_BYTES) continue;
|
|
1313
|
+
if (nowMs - st.mtimeMs < MIN_AGE_MS) continue;
|
|
1314
|
+
|
|
1315
|
+
var raw;
|
|
1316
|
+
try { raw = fs.readFileSync(fp, "utf8"); } catch (e) { continue; }
|
|
1317
|
+
var lines = raw.split("\n");
|
|
1318
|
+
var userCount = 0;
|
|
1319
|
+
var assistantCount = 0;
|
|
1320
|
+
var userText = null;
|
|
1321
|
+
var parseOk = true;
|
|
1322
|
+
for (var li = 0; li < lines.length; li++) {
|
|
1323
|
+
var line = lines[li];
|
|
1324
|
+
if (!line) continue;
|
|
1325
|
+
var ev;
|
|
1326
|
+
try { ev = JSON.parse(line); } catch (e) { parseOk = false; break; }
|
|
1327
|
+
if (!ev || typeof ev !== "object") continue;
|
|
1328
|
+
if (ev.type === "user") {
|
|
1329
|
+
userCount++;
|
|
1330
|
+
if (userCount > 1) break;
|
|
1331
|
+
var msg = ev.message;
|
|
1332
|
+
var content = msg && msg.content;
|
|
1333
|
+
if (typeof content === "string") {
|
|
1334
|
+
userText = content;
|
|
1335
|
+
} else if (Array.isArray(content)) {
|
|
1336
|
+
var parts = [];
|
|
1337
|
+
for (var ci = 0; ci < content.length; ci++) {
|
|
1338
|
+
if (content[ci] && content[ci].type === "text" && typeof content[ci].text === "string") {
|
|
1339
|
+
parts.push(content[ci].text);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
userText = parts.join("");
|
|
1343
|
+
}
|
|
1344
|
+
} else if (ev.type === "assistant") {
|
|
1345
|
+
assistantCount++;
|
|
1346
|
+
if (assistantCount > 0) break;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (!parseOk) continue;
|
|
1350
|
+
if (userCount !== 1) continue;
|
|
1351
|
+
if (assistantCount !== 0) continue;
|
|
1352
|
+
if (userText !== "hi") continue;
|
|
1353
|
+
|
|
1354
|
+
try { fs.unlinkSync(fp); deleted++; } catch (e) {}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (deleted > 0) {
|
|
1359
|
+
console.log("[daemon] Removed " + deleted + " stale SDK warmup session file(s)");
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1251
1363
|
// --- Start listening (with retry for port-in-use during update handoff) ---
|
|
1252
1364
|
var listenRetries = 0;
|
|
1253
1365
|
var MAX_LISTEN_RETRIES = 15;
|
|
1254
1366
|
|
|
1255
1367
|
function startListening() {
|
|
1368
|
+
// One-shot cleanup of stale SDK warmup CLI session files from previous
|
|
1369
|
+
// daemon runs (best-effort, never fatal).
|
|
1370
|
+
try { cleanupSdkWarmupSessions(); } catch (e) {}
|
|
1371
|
+
|
|
1256
1372
|
relay.server.listen(config.port, listenHost, function () {
|
|
1257
1373
|
var protocol = tlsOptions ? "https" : "http";
|
|
1258
1374
|
console.log("[daemon] Listening on " + protocol + "://" + listenHost + ":" + config.port);
|
package/lib/project-sessions.js
CHANGED
|
@@ -265,12 +265,21 @@ function attachSessions(ctx) {
|
|
|
265
265
|
return null;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
|
|
268
|
+
// Prepare a born-TUI session to be rendered via the SDK GUI chat for the
|
|
269
|
+
// current click. Reads the CLI jsonl transcript (cli-sessions.js),
|
|
270
|
+
// populates session.history, and tears
|
|
271
|
+
// down the PTY. The born-TUI marker (session.mode === 'tui') is kept on
|
|
272
|
+
// disk so that flipping claudeOpenMode back to 'tui' later restores the
|
|
273
|
+
// embedded-terminal experience instead of locking the session as GUI.
|
|
274
|
+
//
|
|
275
|
+
// Idempotent: if the PTY is already gone and history is populated, this
|
|
276
|
+
// is a no-op (avoids re-reading jsonl and re-writing the session file on
|
|
277
|
+
// every click while the user is reading the session in GUI mode).
|
|
278
|
+
function prepareTuiSessionForGuiView(session) {
|
|
273
279
|
if (!session || session.cliSessionId == null) return;
|
|
280
|
+
var alreadyHydrated = (typeof session.terminalId !== "number") &&
|
|
281
|
+
Array.isArray(session.history) && session.history.length > 0;
|
|
282
|
+
if (alreadyHydrated) return;
|
|
274
283
|
var cliSess;
|
|
275
284
|
try { cliSess = require("./cli-sessions"); } catch (e) { return; }
|
|
276
285
|
var history = null;
|
|
@@ -278,7 +287,6 @@ function attachSessions(ctx) {
|
|
|
278
287
|
if (Array.isArray(history)) {
|
|
279
288
|
session.history = history;
|
|
280
289
|
}
|
|
281
|
-
session.mode = "gui";
|
|
282
290
|
if (typeof session.terminalId === "number" && tm) {
|
|
283
291
|
try { tm.close(session.terminalId); } catch (e) {}
|
|
284
292
|
}
|
|
@@ -494,101 +502,10 @@ function attachSessions(ctx) {
|
|
|
494
502
|
return true;
|
|
495
503
|
}
|
|
496
504
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
// If Clay already has a persisted meta file for this cliSessionId, read
|
|
502
|
-
// its vendor so resumeSession doesn't silently default to the project's
|
|
503
|
-
// primary vendor (which would break codex sessions after server restart).
|
|
504
|
-
var persistedVendor = null;
|
|
505
|
-
try {
|
|
506
|
-
var _fsResume = require("fs");
|
|
507
|
-
var _pathResume = require("path");
|
|
508
|
-
var metaPath = _pathResume.join(sm.sessionsDir, msg.cliSessionId + ".jsonl");
|
|
509
|
-
if (_fsResume.existsSync(metaPath)) {
|
|
510
|
-
var firstLine = _fsResume.readFileSync(metaPath, "utf8").split("\n", 1)[0];
|
|
511
|
-
try {
|
|
512
|
-
var metaObj = JSON.parse(firstLine);
|
|
513
|
-
if (metaObj && metaObj.type === "meta" && metaObj.vendor) persistedVendor = metaObj.vendor;
|
|
514
|
-
} catch (e) {}
|
|
515
|
-
}
|
|
516
|
-
} catch (e) {}
|
|
517
|
-
|
|
518
|
-
// Try SDK for title first, then fall back to manual parsing
|
|
519
|
-
var titlePromise = adapter.getSessionInfo(msg.cliSessionId, { dir: cwd }).then(function(info) {
|
|
520
|
-
return (info && info.summary) ? info.summary.substring(0, 100) : null;
|
|
521
|
-
}).catch(function() { return null; });
|
|
522
|
-
|
|
523
|
-
Promise.all([
|
|
524
|
-
cliSess.readCliSessionHistory(cwd, msg.cliSessionId),
|
|
525
|
-
titlePromise
|
|
526
|
-
]).then(function(results) {
|
|
527
|
-
var history = results[0];
|
|
528
|
-
var sdkTitle = results[1];
|
|
529
|
-
var title = sdkTitle || "Resumed session";
|
|
530
|
-
if (!sdkTitle) {
|
|
531
|
-
for (var i = 0; i < history.length; i++) {
|
|
532
|
-
if (history[i].type === "user_message" && history[i].text) {
|
|
533
|
-
title = history[i].text.substring(0, 50);
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
var resumed = sm.resumeSession(msg.cliSessionId, { history: history, title: title, vendor: persistedVendor || undefined }, ws);
|
|
539
|
-
if (resumed) ws._clayActiveSession = resumed.localId;
|
|
540
|
-
}).catch(function() {
|
|
541
|
-
var resumed = sm.resumeSession(msg.cliSessionId, persistedVendor ? { vendor: persistedVendor } : undefined, ws);
|
|
542
|
-
if (resumed) ws._clayActiveSession = resumed.localId;
|
|
543
|
-
});
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (msg.type === "list_cli_sessions") {
|
|
548
|
-
var _fs = require("fs");
|
|
549
|
-
// Collect session IDs already in relay (in-memory + persisted on disk)
|
|
550
|
-
var relayIds = {};
|
|
551
|
-
sm.sessions.forEach(function (s) {
|
|
552
|
-
if (s.cliSessionId) relayIds[s.cliSessionId] = true;
|
|
553
|
-
});
|
|
554
|
-
try {
|
|
555
|
-
var sessDir = sm.sessionsDir;
|
|
556
|
-
var diskFiles = _fs.readdirSync(sessDir);
|
|
557
|
-
for (var fi = 0; fi < diskFiles.length; fi++) {
|
|
558
|
-
if (diskFiles[fi].endsWith(".jsonl")) {
|
|
559
|
-
relayIds[diskFiles[fi].replace(".jsonl", "")] = true;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
} catch (e) {}
|
|
563
|
-
|
|
564
|
-
adapter.listSessions({ dir: cwd }).then(function(sdkSessions) {
|
|
565
|
-
var filtered = sdkSessions.filter(function(s) {
|
|
566
|
-
return !relayIds[s.sessionId];
|
|
567
|
-
}).map(function(s) {
|
|
568
|
-
return {
|
|
569
|
-
sessionId: s.sessionId,
|
|
570
|
-
firstPrompt: s.summary || s.firstPrompt || "",
|
|
571
|
-
model: null,
|
|
572
|
-
gitBranch: s.gitBranch || null,
|
|
573
|
-
startTime: s.createdAt ? new Date(s.createdAt).toISOString() : null,
|
|
574
|
-
lastActivity: s.lastModified ? new Date(s.lastModified).toISOString() : null,
|
|
575
|
-
};
|
|
576
|
-
});
|
|
577
|
-
sendTo(ws, { type: "cli_session_list", sessions: filtered });
|
|
578
|
-
}).catch(function() {
|
|
579
|
-
// Fallback to manual parsing if SDK fails
|
|
580
|
-
var cliSessions = require("./cli-sessions");
|
|
581
|
-
cliSessions.listCliSessions(cwd).then(function(sessions) {
|
|
582
|
-
var filtered = sessions.filter(function(s) {
|
|
583
|
-
return !relayIds[s.sessionId];
|
|
584
|
-
});
|
|
585
|
-
sendTo(ws, { type: "cli_session_list", sessions: filtered });
|
|
586
|
-
}).catch(function() {
|
|
587
|
-
sendTo(ws, { type: "cli_session_list", sessions: [] });
|
|
588
|
-
});
|
|
589
|
-
});
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
505
|
+
// Note: the old `resume_session` and `list_cli_sessions` WS handlers
|
|
506
|
+
// were removed when CLI sessions started being auto-adopted into the
|
|
507
|
+
// unified session list on server start (see sessions.js
|
|
508
|
+
// adoptOrphanedCliSessions). Click a session in the sidebar instead.
|
|
592
509
|
|
|
593
510
|
if (msg.type === "switch_session") {
|
|
594
511
|
if (msg.id && sm.sessions.has(msg.id)) {
|
|
@@ -598,21 +515,24 @@ function attachSessions(ctx) {
|
|
|
598
515
|
// - born-GUI viewed under TUI pref: spawn a transient PTY running
|
|
599
516
|
// `claude --resume <cliSessionId>`; the session record stays GUI
|
|
600
517
|
// so a later pref flip back to GUI just hides the runtime link.
|
|
601
|
-
// - born-TUI viewed under GUI pref:
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
//
|
|
518
|
+
// - born-TUI viewed under GUI pref: hydrate session.history from
|
|
519
|
+
// the jsonl transcript and tear down the PTY so the session
|
|
520
|
+
// renders via the SDK chat. The born-TUI marker stays on the
|
|
521
|
+
// record so a later pref flip back to TUI restores the
|
|
522
|
+
// embedded-terminal experience.
|
|
605
523
|
//
|
|
606
524
|
// runtimeMode / runtimeTerminalId are set on the session record so
|
|
607
525
|
// the session_switched and session_list broadcasts surface them to
|
|
608
526
|
// the client without sessions.js needing to know about the pref.
|
|
609
527
|
var xmTarget = sm.sessions.get(msg.id);
|
|
610
528
|
if (xmTarget && (xmTarget.vendor === "claude" || !xmTarget.vendor)) {
|
|
529
|
+
var xmPref = getClaudeOpenModeForWs(ws);
|
|
611
530
|
// Born-TUI session whose PTY is gone (typically after a daemon
|
|
612
|
-
// restart)
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
|
|
531
|
+
// restart) AND the user wants TUI rendering this click. Respawn
|
|
532
|
+
// via `claude --resume <cliSessionId>` so the rest of the pipeline
|
|
533
|
+
// has a live PTY to point at. Skipped when the user's pref is GUI
|
|
534
|
+
// - we'd just tear it back down two lines later.
|
|
535
|
+
if (xmTarget.mode === "tui" && xmTarget.cliSessionId && tm && xmPref === "tui" &&
|
|
616
536
|
(typeof xmTarget.terminalId !== "number" || !tm.has(xmTarget.terminalId))) {
|
|
617
537
|
var rsSid = xmTarget.cliSessionId;
|
|
618
538
|
var rsLocalId = xmTarget.localId;
|
|
@@ -632,11 +552,15 @@ function attachSessions(ctx) {
|
|
|
632
552
|
if (rsTerm) xmTarget.terminalId = rsTerm.id;
|
|
633
553
|
startTitleWatcher(xmTarget);
|
|
634
554
|
}
|
|
635
|
-
var xmPref = getClaudeOpenModeForWs(ws);
|
|
636
555
|
var xmRuntime = computeRuntimeMode(xmTarget, xmPref);
|
|
637
556
|
if (xmRuntime === "gui" && xmTarget.mode === "tui") {
|
|
638
|
-
|
|
639
|
-
|
|
557
|
+
// Born-TUI session under GUI pref. Hydrate history + drop PTY
|
|
558
|
+
// without flipping session.mode, so flipping back to TUI later
|
|
559
|
+
// restores the embedded-terminal rendering. runtimeMode is set
|
|
560
|
+
// to 'gui' explicitly so the client doesn't fall back to
|
|
561
|
+
// session.mode (which is still 'tui').
|
|
562
|
+
prepareTuiSessionForGuiView(xmTarget);
|
|
563
|
+
xmTarget.runtimeMode = "gui";
|
|
640
564
|
xmTarget.runtimeTerminalId = null;
|
|
641
565
|
} else if (xmRuntime === "tui" && xmTarget.mode === "gui" && xmTarget.cliSessionId) {
|
|
642
566
|
var xmRid = spawnRuntimeTuiPty(xmTarget, ws);
|
package/lib/public/app.js
CHANGED
|
@@ -5,7 +5,7 @@ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidM
|
|
|
5
5
|
import { initSidebar, updatePageTitle, spawnDustParticles } from './modules/sidebar.js';
|
|
6
6
|
import {
|
|
7
7
|
renderSessionList, handleSearchResults, updateSessionPresence,
|
|
8
|
-
updateSessionBadge
|
|
8
|
+
updateSessionBadge
|
|
9
9
|
} from './modules/sidebar-sessions.js';
|
|
10
10
|
import {
|
|
11
11
|
renderIconStrip, getEmojiCategories, updateProjectBadge
|
|
@@ -99,7 +99,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
99
99
|
var hamburgerBtn = $("hamburger-btn");
|
|
100
100
|
var sidebarToggleBtn = $("sidebar-toggle-btn");
|
|
101
101
|
var sidebarExpandBtn = $("sidebar-expand-btn");
|
|
102
|
-
var resumeSessionBtn = $("resume-session-btn");
|
|
103
102
|
var imagePreviewBar = $("image-preview-bar");
|
|
104
103
|
var connectOverlay = $("connect-overlay");
|
|
105
104
|
|
|
@@ -378,7 +377,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
378
377
|
sidebarExpandBtn: sidebarExpandBtn,
|
|
379
378
|
hamburgerBtn: hamburgerBtn,
|
|
380
379
|
newSessionBtn: newSessionBtn,
|
|
381
|
-
resumeSessionBtn: resumeSessionBtn,
|
|
382
380
|
headerTitleEl: headerTitleEl,
|
|
383
381
|
showConfirm: showConfirm,
|
|
384
382
|
onFilesTabOpen: function () { loadRootDirectory(); },
|
|
@@ -1119,35 +1119,6 @@
|
|
|
1119
1119
|
margin-left: 12px;
|
|
1120
1120
|
}
|
|
1121
1121
|
|
|
1122
|
-
/* TUI session indicator: a tiny black "screen" with a bold green prompt
|
|
1123
|
-
glyph, meant to read as a miniature terminal (CRT-style cursor) at
|
|
1124
|
-
sidebar scale. Aligned to the middle of the text x-height so it sits
|
|
1125
|
-
visually centered with the session title rather than dropping below. */
|
|
1126
|
-
.session-tui-icon {
|
|
1127
|
-
display: inline-flex;
|
|
1128
|
-
align-items: center;
|
|
1129
|
-
justify-content: center;
|
|
1130
|
-
width: 14px;
|
|
1131
|
-
height: 14px;
|
|
1132
|
-
border-radius: 3px;
|
|
1133
|
-
background: #000;
|
|
1134
|
-
margin-right: 6px;
|
|
1135
|
-
vertical-align: middle;
|
|
1136
|
-
flex-shrink: 0;
|
|
1137
|
-
/* Nudge upward a hair so the icon optical-centers with lowercase glyphs
|
|
1138
|
-
(lucide icons render slightly below the geometric center). */
|
|
1139
|
-
position: relative;
|
|
1140
|
-
top: -1px;
|
|
1141
|
-
}
|
|
1142
|
-
.session-tui-icon svg,
|
|
1143
|
-
.session-tui-icon .lucide {
|
|
1144
|
-
width: 10px;
|
|
1145
|
-
height: 10px;
|
|
1146
|
-
color: #50fa7b;
|
|
1147
|
-
stroke: #50fa7b;
|
|
1148
|
-
stroke-width: 3;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
1122
|
.session-top-action:hover {
|
|
1152
1123
|
background: var(--sidebar-hover);
|
|
1153
1124
|
color: var(--text);
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/* Subtle 1px divider against the sidebar so the embedded terminal
|
|
2
|
+
doesn't visually bleed into the session list. The host is
|
|
3
|
+
position:fixed and anchored to #messages' bounding rect, so its
|
|
4
|
+
left edge sits exactly on the sidebar boundary. Color matches
|
|
5
|
+
#main-area's existing border-left so the line reads as one
|
|
6
|
+
continuous edge with the rest of the chrome. */
|
|
7
|
+
#tui-session-host {
|
|
8
|
+
border-left: 1px solid var(--border-subtle);
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
/* Policy notice that sits above the embedded xterm in fullscreen TUI
|
|
2
12
|
sessions, explaining why this mode exists (post-2026-06-15 Agent SDK
|
|
3
13
|
billing split). Thin so it doesn't eat terminal real estate. */
|
|
@@ -859,3 +859,46 @@
|
|
|
859
859
|
align-items: stretch;
|
|
860
860
|
}
|
|
861
861
|
}
|
|
862
|
+
|
|
863
|
+
/* Terminal font picker row */
|
|
864
|
+
.us-term-font-row {
|
|
865
|
+
display: flex;
|
|
866
|
+
align-items: center;
|
|
867
|
+
gap: 8px;
|
|
868
|
+
flex-wrap: wrap;
|
|
869
|
+
}
|
|
870
|
+
.us-term-font-family {
|
|
871
|
+
flex: 1 1 auto;
|
|
872
|
+
min-width: 180px;
|
|
873
|
+
padding: 7px 10px;
|
|
874
|
+
border: 1px solid var(--border);
|
|
875
|
+
border-radius: 8px;
|
|
876
|
+
background: var(--input-bg);
|
|
877
|
+
color: var(--text);
|
|
878
|
+
font-size: 13px;
|
|
879
|
+
}
|
|
880
|
+
.us-term-font-custom {
|
|
881
|
+
flex: 1 1 100%;
|
|
882
|
+
padding: 7px 10px;
|
|
883
|
+
border: 1px solid var(--border);
|
|
884
|
+
border-radius: 8px;
|
|
885
|
+
background: var(--input-bg);
|
|
886
|
+
color: var(--text);
|
|
887
|
+
font-size: 13px;
|
|
888
|
+
font-family: "Roboto Mono", monospace;
|
|
889
|
+
}
|
|
890
|
+
.us-term-font-custom.hidden { display: none; }
|
|
891
|
+
.us-term-font-size {
|
|
892
|
+
width: 64px;
|
|
893
|
+
padding: 7px 10px;
|
|
894
|
+
border: 1px solid var(--border);
|
|
895
|
+
border-radius: 8px;
|
|
896
|
+
background: var(--input-bg);
|
|
897
|
+
color: var(--text);
|
|
898
|
+
font-size: 13px;
|
|
899
|
+
text-align: center;
|
|
900
|
+
}
|
|
901
|
+
.us-term-font-unit {
|
|
902
|
+
font-size: 13px;
|
|
903
|
+
color: var(--text-dimmer);
|
|
904
|
+
}
|
package/lib/public/index.html
CHANGED
|
@@ -879,7 +879,7 @@
|
|
|
879
879
|
</div>
|
|
880
880
|
</div>
|
|
881
881
|
<div class="user-island-actions">
|
|
882
|
-
<button id="user-theme-toggle-btn" title="
|
|
882
|
+
<button id="user-theme-toggle-btn" title="Themes" aria-label="Themes"><i data-lucide="palette"></i></button>
|
|
883
883
|
<button id="user-settings-btn" title="User settings"><i data-lucide="settings"></i></button>
|
|
884
884
|
</div>
|
|
885
885
|
</div>
|
|
@@ -1002,18 +1002,22 @@
|
|
|
1002
1002
|
<div class="us-section" data-section="us-appearance">
|
|
1003
1003
|
<h2>Appearance</h2>
|
|
1004
1004
|
<div class="settings-card">
|
|
1005
|
-
<div class="settings-label" style="margin-bottom:10px;">
|
|
1006
|
-
<div class="
|
|
1007
|
-
<
|
|
1008
|
-
<
|
|
1009
|
-
<
|
|
1010
|
-
<
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
<
|
|
1014
|
-
<
|
|
1015
|
-
<
|
|
1016
|
-
|
|
1005
|
+
<div class="settings-label" style="margin-bottom:10px;">Terminal font</div>
|
|
1006
|
+
<div class="us-term-font-row">
|
|
1007
|
+
<select id="us-term-font-family" class="us-term-font-family">
|
|
1008
|
+
<option value="'SF Mono', Menlo, Monaco, 'Courier New', monospace">SF Mono / Menlo (default)</option>
|
|
1009
|
+
<option value="'JetBrains Mono', 'SF Mono', Menlo, monospace">JetBrains Mono</option>
|
|
1010
|
+
<option value="'Fira Code', 'SF Mono', Menlo, monospace">Fira Code</option>
|
|
1011
|
+
<option value="'Cascadia Code', 'SF Mono', Menlo, monospace">Cascadia Code</option>
|
|
1012
|
+
<option value="'IBM Plex Mono', 'SF Mono', Menlo, monospace">IBM Plex Mono</option>
|
|
1013
|
+
<option value="'Source Code Pro', 'SF Mono', Menlo, monospace">Source Code Pro</option>
|
|
1014
|
+
<option value="'Roboto Mono', 'SF Mono', Menlo, monospace">Roboto Mono</option>
|
|
1015
|
+
<option value="ui-monospace, monospace">System monospace</option>
|
|
1016
|
+
<option value="__custom__">Custom...</option>
|
|
1017
|
+
</select>
|
|
1018
|
+
<input type="text" id="us-term-font-family-custom" class="us-term-font-custom hidden" placeholder="e.g. 'My Font', monospace" maxlength="200">
|
|
1019
|
+
<input type="number" id="us-term-font-size" class="us-term-font-size" min="9" max="32" step="1">
|
|
1020
|
+
<span class="us-term-font-unit">px</span>
|
|
1017
1021
|
</div>
|
|
1018
1022
|
</div>
|
|
1019
1023
|
<div class="settings-card">
|
|
@@ -1556,26 +1560,6 @@
|
|
|
1556
1560
|
</div>
|
|
1557
1561
|
</div>
|
|
1558
1562
|
|
|
1559
|
-
<div id="resume-modal" class="hidden">
|
|
1560
|
-
<div class="confirm-backdrop"></div>
|
|
1561
|
-
<div class="confirm-dialog resume-picker-dialog">
|
|
1562
|
-
<div class="resume-modal-title">Resume CLI session</div>
|
|
1563
|
-
<div class="resume-picker-body">
|
|
1564
|
-
<div id="resume-picker-loading" class="resume-picker-loading">
|
|
1565
|
-
<div class="resume-picker-spinner"></div>
|
|
1566
|
-
<span>Loading sessions...</span>
|
|
1567
|
-
</div>
|
|
1568
|
-
<div id="resume-picker-empty" class="resume-picker-empty hidden">
|
|
1569
|
-
No CLI sessions found for this project.
|
|
1570
|
-
</div>
|
|
1571
|
-
<div id="resume-picker-list" class="resume-picker-list hidden"></div>
|
|
1572
|
-
</div>
|
|
1573
|
-
<div class="confirm-actions">
|
|
1574
|
-
<button class="confirm-btn confirm-cancel" id="resume-cancel">Cancel</button>
|
|
1575
|
-
</div>
|
|
1576
|
-
</div>
|
|
1577
|
-
</div>
|
|
1578
|
-
|
|
1579
1563
|
<div id="rewind-modal" class="hidden">
|
|
1580
1564
|
<div class="confirm-backdrop"></div>
|
|
1581
1565
|
<div class="confirm-dialog">
|
|
@@ -10,7 +10,7 @@ import { showToast } from './utils.js';
|
|
|
10
10
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
11
11
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
12
12
|
import { updatePageTitle } from './sidebar.js';
|
|
13
|
-
import { renderSessionList, updateSessionPresence,
|
|
13
|
+
import { renderSessionList, updateSessionPresence, handleSearchResults, updateSessionBadge } from './sidebar-sessions.js';
|
|
14
14
|
import { updateDmBadge, renderSidebarPresence, setMentionActive, renderUserStrip } from './sidebar-mates.js';
|
|
15
15
|
import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
16
16
|
import { renderMateSessionList, handleMateSearchResults, updateMateSidebarProfile } from './mate-sidebar.js';
|
|
@@ -585,10 +585,6 @@ export function processMessage(msg) {
|
|
|
585
585
|
}
|
|
586
586
|
break;
|
|
587
587
|
|
|
588
|
-
case "cli_session_list":
|
|
589
|
-
populateCliSessionList(msg.sessions || []);
|
|
590
|
-
break;
|
|
591
|
-
|
|
592
588
|
case "session_switched":
|
|
593
589
|
hideHomeHub();
|
|
594
590
|
closeWhatsNewArticle();
|