clay-server 2.45.0-beta.1 → 2.45.0-beta.3
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/project-user-message.js +2 -1
- package/lib/public/css/tui-attention.css +6 -0
- package/lib/public/modules/app-messages.js +18 -2
- package/lib/public/modules/app-notifications.js +58 -1
- package/lib/public/modules/app-panels.js +14 -18
- package/lib/public/modules/tui-attention.js +6 -0
- package/lib/yoke/codex-app-server.js +12 -1
- package/package.json +2 -2
|
@@ -145,7 +145,8 @@ function attachUserMessage(ctx) {
|
|
|
145
145
|
return true;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
var t = tm.create(msg.cols || 80, msg.rows || 24, getOsUserInfoForWs(ws), ws
|
|
148
|
+
var t = tm.create(msg.cols || 80, msg.rows || 24, getOsUserInfoForWs(ws), ws,
|
|
149
|
+
msg.initialCommand ? { initialInput: String(msg.initialCommand) } : undefined);
|
|
149
150
|
if (!t) {
|
|
150
151
|
sendTo(ws, { type: "term_error", error: "Cannot create terminal (node-pty not available or limit reached)" });
|
|
151
152
|
return true;
|
|
@@ -349,6 +349,12 @@ body.tui-suspended #tui-resume-bar {
|
|
|
349
349
|
border: 1px solid var(--border);
|
|
350
350
|
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.6);
|
|
351
351
|
}
|
|
352
|
+
/* Compact variant for short content (e.g. login wizard): smaller box so the
|
|
353
|
+
terminal hugs the content instead of leaving a tall empty area below. */
|
|
354
|
+
.tui-modal.tui-modal-compact {
|
|
355
|
+
width: min(720px, 96%);
|
|
356
|
+
height: min(480px, 90%);
|
|
357
|
+
}
|
|
352
358
|
.tui-modal-header {
|
|
353
359
|
display: flex;
|
|
354
360
|
align-items: center;
|
|
@@ -29,7 +29,7 @@ import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSe
|
|
|
29
29
|
import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
|
|
30
30
|
import { attachTuiView, detachTuiView, setTuiSuspendedView, tuiHandleTermOutput, tuiHandleTermResized, tuiHandleTermExited, tuiHandleTermClosed } from './session-tui-view.js';
|
|
31
31
|
import { handleTuiTranscriptState } from './tui-grab.js';
|
|
32
|
-
import { tuiModalHandleTermOutput, tuiModalHandleTermResized, tuiModalHandleTermExited, tuiModalHandleTermClosed } from './tui-attention.js';
|
|
32
|
+
import { tuiModalHandleTermOutput, tuiModalHandleTermResized, tuiModalHandleTermExited, tuiModalHandleTermClosed, openTuiModal } from './tui-attention.js';
|
|
33
33
|
import { updateTerminalList, handleContextSourcesState, updateEmailAccountList, updateEmailUnreadCounts, handleEmailTestResult, handleEmailAddResult, handleEmailRemoveResult, handleEmailDefaults } from './context-sources.js';
|
|
34
34
|
import { refreshEmailSettings } from './user-settings.js';
|
|
35
35
|
import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted } from './sticky-notes.js';
|
|
@@ -57,7 +57,7 @@ import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection,
|
|
|
57
57
|
import { showLoopBanner, updateLoopBanner, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
|
|
58
58
|
import { showDebateSticky, showDebateConcludeConfirm, showDebateUserFloor, exitDebateFloorMode, exitDebateConcludeMode, exitDebateEndedMode, updateDebateRound, renderDebateUserFloorDone } from './app-debate-ui.js';
|
|
59
59
|
import { handleSkillInstallWs } from './app-skills-install.js';
|
|
60
|
-
import { handleNotificationsState, handleNotificationCreated, handleNotificationDismissed, handleNotificationDismissedAll, showUpdateBanner } from './app-notifications.js';
|
|
60
|
+
import { handleNotificationsState, handleNotificationCreated, handleNotificationDismissed, handleNotificationDismissedAll, showUpdateBanner, autoStartLoginIfNeeded } from './app-notifications.js';
|
|
61
61
|
import { handleDebatePreparing, handleDebateBriefReady, renderDebateBriefReady, handleDebateStarted, renderDebateStarted, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, renderDebateCommentInjected, handleDebateResumed, handleDebateEnded, renderDebateEnded, handleDebateError, isDebateActive, renderMcpDebateProposal, renderDebateUserResume } from './debate.js';
|
|
62
62
|
import { handleMentionStart, handleMentionActivity, handleMentionStream, handleMentionDone, handleMentionError, renderMentionUser, renderMentionResponse, renderUserMention } from './mention.js';
|
|
63
63
|
|
|
@@ -1156,6 +1156,9 @@ export function processMessage(msg) {
|
|
|
1156
1156
|
appendDelta((msg.text || "Authentication required.") + "\n");
|
|
1157
1157
|
setStatus("connected");
|
|
1158
1158
|
if (!store.get('loopActive')) enableMainInput();
|
|
1159
|
+
// Auto-open the login modal terminal when the session can self-login.
|
|
1160
|
+
// No-op otherwise (the auth_required banner remains the manual path).
|
|
1161
|
+
autoStartLoginIfNeeded(msg);
|
|
1159
1162
|
break;
|
|
1160
1163
|
|
|
1161
1164
|
case "rate_limit":
|
|
@@ -1338,6 +1341,19 @@ export function processMessage(msg) {
|
|
|
1338
1341
|
break;
|
|
1339
1342
|
|
|
1340
1343
|
case "term_created":
|
|
1344
|
+
// Login-modal path: the terminal was created with the login command as
|
|
1345
|
+
// its initial input; attach the modal dialog to it (replays scrollback
|
|
1346
|
+
// so the login URL is visible) instead of the sidebar terminal.
|
|
1347
|
+
if (store.get('pendingLoginModal')) {
|
|
1348
|
+
var _lm = store.get('pendingLoginModal');
|
|
1349
|
+
store.set({ pendingLoginModal: null });
|
|
1350
|
+
openTuiModal(msg.id, _lm.slug, {
|
|
1351
|
+
sessionTitle: (_lm.vendor === "codex" ? "Codex" : "Claude") + " login",
|
|
1352
|
+
projectName: _lm.slug,
|
|
1353
|
+
compact: true,
|
|
1354
|
+
});
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1341
1357
|
handleTermCreated(msg);
|
|
1342
1358
|
if (store.get('pendingTermCommand')) {
|
|
1343
1359
|
var cmd = store.get('pendingTermCommand');
|
|
@@ -252,7 +252,7 @@ function showBanner(notif, autoDismissMs) {
|
|
|
252
252
|
removeBanner(banner);
|
|
253
253
|
dismissNotif(notif.id);
|
|
254
254
|
var authMeta = notif.meta || {};
|
|
255
|
-
|
|
255
|
+
startLoginInModal(authMeta.loginCommand || ((authMeta.vendor || "claude") === "codex" ? "codex login --device-auth" : "claude login"), authMeta.vendor || "claude");
|
|
256
256
|
showLoginReminderBanner();
|
|
257
257
|
});
|
|
258
258
|
}
|
|
@@ -311,7 +311,42 @@ function showBanner(notif, autoDismissMs) {
|
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
// Open the login terminal as a modal dialog (the same modal used for TUI
|
|
315
|
+
// attention), running the vendor login command. The terminal is created with
|
|
316
|
+
// the command as its initial input, so when the modal attaches it replays the
|
|
317
|
+
// scrollback and the user sees the login URL / prompts immediately.
|
|
318
|
+
// Falls back to the sidebar terminal if the current project slug is unknown
|
|
319
|
+
// (the modal needs a slug to open its own WS to the project).
|
|
320
|
+
function currentProjectSlug() {
|
|
321
|
+
var slug = store.get('currentSlug') || "";
|
|
322
|
+
if (slug) return slug;
|
|
323
|
+
// Fallback: derive from the URL (/p/<slug>/...).
|
|
324
|
+
try {
|
|
325
|
+
var m = (window.location.pathname || "").match(/^\/p\/([a-z0-9_-]+)/);
|
|
326
|
+
if (m) return m[1];
|
|
327
|
+
} catch (e) {}
|
|
328
|
+
return "";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function startLoginInModal(loginCommand, vendor) {
|
|
332
|
+
if (authReminderVisible) return;
|
|
333
|
+
var ws = getWs();
|
|
334
|
+
if (!ws || ws.readyState !== 1) return;
|
|
335
|
+
var cmd = loginCommand || (vendor === "codex" ? "codex login --device-auth" : "claude login");
|
|
336
|
+
var slug = currentProjectSlug();
|
|
337
|
+
if (!slug) { startLoginCommand(cmd); return; }
|
|
338
|
+
store.set({ pendingLoginModal: { slug: slug, vendor: vendor || "claude" } });
|
|
339
|
+
ws.send(JSON.stringify({
|
|
340
|
+
type: "term_create",
|
|
341
|
+
cols: 100,
|
|
342
|
+
rows: 30,
|
|
343
|
+
initialCommand: cmd + "\n",
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Sidebar-terminal fallback (used when no slug is available for the modal).
|
|
314
348
|
function startLoginCommand(loginCommand) {
|
|
349
|
+
if (authReminderVisible) return;
|
|
315
350
|
var ws = getWs();
|
|
316
351
|
if (!ws || ws.readyState !== 1) return;
|
|
317
352
|
var termCommand = (loginCommand || "claude login") + "\n";
|
|
@@ -320,6 +355,23 @@ function startLoginCommand(loginCommand) {
|
|
|
320
355
|
openTerminal();
|
|
321
356
|
}
|
|
322
357
|
|
|
358
|
+
// Auto-open the login modal and run the login command when the server reports
|
|
359
|
+
// the vendor isn't logged in. The popup terminal runs as the connected user's
|
|
360
|
+
// own OS identity (term_create -> getOsUserInfoForWs), so login always targets
|
|
361
|
+
// the right account — we pop it unconditionally rather than gating on
|
|
362
|
+
// canAutoLogin. Idempotent: startLoginInModal is guarded by authReminderVisible
|
|
363
|
+
// so repeat auth_required events no-op.
|
|
364
|
+
export function autoStartLoginIfNeeded(msg) {
|
|
365
|
+
if (!msg) return false;
|
|
366
|
+
if (authReminderVisible) return false;
|
|
367
|
+
var vendor = msg.vendor || "claude";
|
|
368
|
+
var cmd = msg.loginCommand
|
|
369
|
+
|| (vendor === "codex" ? "codex login --device-auth" : "claude login");
|
|
370
|
+
startLoginInModal(cmd, vendor);
|
|
371
|
+
showLoginReminderBanner();
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
323
375
|
function showLoginReminderBanner() {
|
|
324
376
|
if (!bannerContainer) return;
|
|
325
377
|
authReminderVisible = true;
|
|
@@ -554,6 +606,11 @@ export function handleNotificationCreated(msg) {
|
|
|
554
606
|
unreadCount = msg.unreadCount;
|
|
555
607
|
updateBadge();
|
|
556
608
|
|
|
609
|
+
// auth_required no longer pops a banner: the login terminal modal opens
|
|
610
|
+
// automatically (see autoStartLoginIfNeeded). Keep the notification in the
|
|
611
|
+
// center (above) for history, but don't show the redundant banner.
|
|
612
|
+
if (notif.type === "auth_required") return;
|
|
613
|
+
|
|
557
614
|
// tui_attention drives the project icon shake and the favicon blink —
|
|
558
615
|
// same visual cues the SDK already produces from pendingPermissions /
|
|
559
616
|
// permission_request messages, so the user notices either kind without
|
|
@@ -178,25 +178,21 @@ function hasBeta(name) {
|
|
|
178
178
|
function rebuildModelList() {
|
|
179
179
|
if (!configModelList) return;
|
|
180
180
|
// Picker visibility by vendor+mode:
|
|
181
|
-
// Claude TUI -> shown (Claude TUI accepts mid-thread model swaps).
|
|
182
|
-
// Claude
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// active queryInstance, so
|
|
188
|
-
// message
|
|
181
|
+
// Claude TUI -> shown, free (Claude TUI accepts mid-thread model swaps).
|
|
182
|
+
// GUI (Claude SDK + Codex) -> shown, but locked once the session has
|
|
183
|
+
// history. Both bind the model at session creation and
|
|
184
|
+
// changing mid-thread breaks tool schemas / cache reuse,
|
|
185
|
+
// so we allow the pick only before the first message.
|
|
186
|
+
// sdk-bridge setModel stores into sm.currentModel when
|
|
187
|
+
// there's no active queryInstance, so a pick made before
|
|
188
|
+
// the first message takes effect on session start.
|
|
189
189
|
var modelSection = configModelList.parentElement;
|
|
190
190
|
var s = store.snap();
|
|
191
191
|
var vendor = s.currentVendor || "claude";
|
|
192
|
-
|
|
193
|
-
if (modelSection) modelSection.style.display = hideModelPicker ? "none" : "";
|
|
192
|
+
if (modelSection) modelSection.style.display = "";
|
|
194
193
|
configModelList.innerHTML = "";
|
|
195
|
-
if (hideModelPicker) return;
|
|
196
194
|
|
|
197
|
-
var
|
|
198
|
-
&& s.activeSessionMode === "gui"
|
|
199
|
-
&& !!s.sessionHasHistory;
|
|
195
|
+
var lockedForGui = s.activeSessionMode === "gui" && !!s.sessionHasHistory;
|
|
200
196
|
|
|
201
197
|
var list = s.currentModels.length > 0 ? s.currentModels : (s.currentModel ? [{ value: s.currentModel, displayName: s.currentModel }] : []);
|
|
202
198
|
for (var i = 0; i < list.length; i++) {
|
|
@@ -207,12 +203,12 @@ function rebuildModelList() {
|
|
|
207
203
|
var btn = document.createElement("button");
|
|
208
204
|
btn.className = "config-radio-item";
|
|
209
205
|
if (value === s.currentModel) btn.classList.add("active");
|
|
210
|
-
if (
|
|
206
|
+
if (lockedForGui) btn.classList.add("locked");
|
|
211
207
|
btn.dataset.model = value;
|
|
212
208
|
btn.textContent = label;
|
|
213
|
-
if (
|
|
209
|
+
if (lockedForGui) {
|
|
214
210
|
btn.disabled = true;
|
|
215
|
-
btn.title = "Model is locked after the first message in a
|
|
211
|
+
btn.title = "Model is locked after the first message in a GUI session. Start a new session to change it.";
|
|
216
212
|
} else {
|
|
217
213
|
btn.addEventListener("click", function () {
|
|
218
214
|
var model = this.dataset.model;
|
|
@@ -227,7 +223,7 @@ function rebuildModelList() {
|
|
|
227
223
|
configModelList.appendChild(btn);
|
|
228
224
|
}
|
|
229
225
|
|
|
230
|
-
if (
|
|
226
|
+
if (lockedForGui) {
|
|
231
227
|
var hint = document.createElement("div");
|
|
232
228
|
hint.className = "config-model-hint";
|
|
233
229
|
hint.textContent = "Locked after first message — start a new session to change.";
|
|
@@ -147,6 +147,12 @@ export function openTuiModal(terminalId, sourceSlug, info) {
|
|
|
147
147
|
});
|
|
148
148
|
modalEl.classList.remove("hidden");
|
|
149
149
|
|
|
150
|
+
// Compact sizing for short, content-light terminals (e.g. the login
|
|
151
|
+
// wizard) so the box hugs the content instead of leaving a tall empty
|
|
152
|
+
// black area below it. Full-size for live TUI sessions that fill the screen.
|
|
153
|
+
var boxEl = modalEl.querySelector(".tui-modal");
|
|
154
|
+
if (boxEl) boxEl.classList.toggle("tui-modal-compact", !!infoObj.compact);
|
|
155
|
+
|
|
150
156
|
var bodyEl = modalEl.querySelector(".tui-modal-body");
|
|
151
157
|
bodyEl.innerHTML = "";
|
|
152
158
|
modalXterm = new Terminal({
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
var { spawn } = require("child_process");
|
|
8
8
|
var readline = require("readline");
|
|
9
9
|
var path = require("path");
|
|
10
|
+
var fs = require("fs");
|
|
10
11
|
var { createRequire } = require("module");
|
|
11
12
|
|
|
12
13
|
// --- Find the codex binary path ---
|
|
@@ -48,7 +49,17 @@ function findCodexPath() {
|
|
|
48
49
|
var platformPkgJson = codexRequire.resolve(platformPkg + "/package.json");
|
|
49
50
|
var vendorRoot = path.join(path.dirname(platformPkgJson), "vendor");
|
|
50
51
|
var binaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
|
51
|
-
|
|
52
|
+
// The binary layout changed across versions: 0.142+ ships it under
|
|
53
|
+
// vendor/<triple>/bin/, older builds used vendor/<triple>/codex/.
|
|
54
|
+
// Try the known layouts and return the first that exists.
|
|
55
|
+
var candidates = [
|
|
56
|
+
path.join(vendorRoot, triple, "bin", binaryName),
|
|
57
|
+
path.join(vendorRoot, triple, "codex", binaryName),
|
|
58
|
+
];
|
|
59
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
60
|
+
if (fs.existsSync(candidates[i])) return candidates[i];
|
|
61
|
+
}
|
|
62
|
+
throw new Error("codex binary not found in any known layout under " + path.join(vendorRoot, triple));
|
|
52
63
|
} catch (e) {
|
|
53
64
|
throw new Error("Could not find codex binary: " + e.message);
|
|
54
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "2.45.0-beta.
|
|
3
|
+
"version": "2.45.0-beta.3",
|
|
4
4
|
"description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clay-server": "./bin/cli.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@anthropic-ai/claude-agent-sdk": "^0.3.196",
|
|
52
52
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
53
|
-
"@openai/codex": "^0.
|
|
53
|
+
"@openai/codex": "^0.142.4",
|
|
54
54
|
"imapflow": "^1.3.1",
|
|
55
55
|
"nodemailer": "^6.10.1",
|
|
56
56
|
"qrcode-terminal": "^0.12.0",
|