clay-server 2.40.0-beta.4 → 2.40.1-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.
@@ -314,19 +314,38 @@ function attachSessions(ctx) {
314
314
  if (!session) return;
315
315
  if (session.vendor && session.vendor !== "claude") { session.tuiSuspended = false; return; }
316
316
  var pref = getClaudeOpenModeForWs(ws);
317
+ // A LIVE runtime always wins over the viewer's claudeOpenMode pref:
318
+ // another user (or this user in another tab) may be in the session right
319
+ // now, so we join it in whatever mode it is actually running and never
320
+ // convert or kill it. "tui stays tui, gui stays gui."
321
+ var liveNativePty = tm && typeof session.terminalId === "number" && tm.has(session.terminalId);
322
+ var liveResumePty = tm && typeof session.runtimeTerminalId === "number" && tm.has(session.runtimeTerminalId);
323
+ var liveSdk = !!session.queryInstance || !!session.isProcessing;
324
+ if (liveNativePty) {
325
+ session.runtimeMode = "tui";
326
+ session.runtimeTerminalId = session.terminalId;
327
+ session.tuiSuspended = false;
328
+ return;
329
+ }
330
+ if (liveResumePty) {
331
+ session.runtimeMode = "tui";
332
+ session.tuiSuspended = false;
333
+ return;
334
+ }
335
+ if (liveSdk) {
336
+ // Actively running as a GUI/SDK session - show GUI for everyone.
337
+ session.runtimeMode = (session.mode === "tui") ? "gui" : null;
338
+ session.runtimeTerminalId = null;
339
+ session.tuiSuspended = false;
340
+ return;
341
+ }
342
+ // Cold session: apply the viewer's pref.
317
343
  if (session.mode === "tui") {
318
344
  if (pref === "gui") {
319
345
  prepareTuiSessionForGuiView(session);
320
346
  session.runtimeMode = "gui";
321
347
  session.runtimeTerminalId = null;
322
348
  session.tuiSuspended = false;
323
- } else if (tm && typeof session.terminalId === "number" && tm.has(session.terminalId)) {
324
- session.runtimeMode = "tui";
325
- session.runtimeTerminalId = session.terminalId;
326
- session.tuiSuspended = false;
327
- } else if (tm && typeof session.runtimeTerminalId === "number" && tm.has(session.runtimeTerminalId)) {
328
- session.runtimeMode = "tui";
329
- session.tuiSuspended = false;
330
349
  } else {
331
350
  prepareTuiSessionForGuiView(session);
332
351
  session.runtimeMode = null;
@@ -334,31 +353,14 @@ function attachSessions(ctx) {
334
353
  session.tuiSuspended = true;
335
354
  }
336
355
  } else {
337
- // Born-GUI: reattach a live resume PTY if one exists; never spawn here.
338
- if (pref === "tui" && tm && typeof session.runtimeTerminalId === "number" && tm.has(session.runtimeTerminalId)) {
339
- session.runtimeMode = "tui";
340
- } else {
341
- session.runtimeMode = null;
342
- }
356
+ // Born-GUI: always GUI. We no longer auto-convert a GUI session to a
357
+ // `claude --resume` terminal on a pref=tui click - that hijacked shared
358
+ // GUI sessions in multi-user. Such sessions render as their SDK chat.
359
+ session.runtimeMode = null;
343
360
  session.tuiSuspended = false;
344
361
  }
345
362
  }
346
363
 
347
- // Compute the runtimeMode the client should render for this session given
348
- // the user's current preference. Pure function over session + pref; the
349
- // caller decides whether to also mutate state (spawn PTY, convert, etc.)
350
- // via the helpers above.
351
- function computeRuntimeMode(session, pref) {
352
- if (!session) return "gui";
353
- if (session.vendor && session.vendor !== "claude") return session.mode || "gui";
354
- var effPref = (pref === "gui") ? "gui" : "tui";
355
- if (session.mode === "tui") {
356
- return effPref === "gui" ? "gui" : "tui";
357
- }
358
- // session.mode === 'gui'
359
- return effPref === "tui" ? "tui" : "gui";
360
- }
361
-
362
364
  function handleSessionsMessage(ws, msg) {
363
365
 
364
366
  if (msg.type === "push_subscribe") {
@@ -568,33 +570,17 @@ function attachSessions(ctx) {
568
570
 
569
571
  if (msg.type === "switch_session") {
570
572
  if (msg.id && sm.sessions.has(msg.id)) {
571
- // Apply the claudeOpenMode preference to the target Claude session
572
- // before sm.switchSession fires session_switched. Two transforms:
573
- //
574
- // - born-GUI viewed under TUI pref: spawn a transient PTY running
575
- // `claude --resume <cliSessionId>`; the session record stays GUI
576
- // so a later pref flip back to GUI just hides the runtime link.
577
- // - born-TUI viewed under GUI pref: hydrate session.history from
578
- // the jsonl transcript and tear down the PTY so the session
579
- // renders via the SDK chat. The born-TUI marker stays on the
580
- // record so a later pref flip back to TUI restores the
581
- // embedded-terminal experience.
582
- //
583
- // runtimeMode / runtimeTerminalId are set on the session record so
584
- // the session_switched and session_list broadcasts surface them to
585
- // the client without sessions.js needing to know about the pref.
573
+ // resolveSessionForView sets runtimeMode / runtimeTerminalId /
574
+ // tuiSuspended (and hydrates the transcript) before sm.switchSession
575
+ // broadcasts session_switched. A live runtime keeps its actual mode
576
+ // for every viewer; only cold sessions follow the clicker's
577
+ // claudeOpenMode pref. Nothing is spawned here.
586
578
  var xmTarget = sm.sessions.get(msg.id);
587
579
  if (xmTarget && (xmTarget.vendor === "claude" || !xmTarget.vendor)) {
588
- var xmPref = getClaudeOpenModeForWs(ws);
589
- // Born-GUI under TUI pref with no live resume PTY: spawn a transient
590
- // `claude --resume` now (only switch_session spawns; born-TUI sessions
591
- // stay lazy and the connect path never spawns). resolveSessionForView
592
- // then turns the live PTY into runtimeMode='tui'.
593
- if (xmTarget.mode === "gui" && xmTarget.cliSessionId &&
594
- computeRuntimeMode(xmTarget, xmPref) === "tui" &&
595
- !(tm && typeof xmTarget.runtimeTerminalId === "number" && tm.has(xmTarget.runtimeTerminalId))) {
596
- spawnRuntimeTuiPty(xmTarget, ws);
597
- }
580
+ // Single source of truth: live runtime wins (tui stays tui, gui stays
581
+ // gui); only cold sessions follow the viewer's pref. No PTY is spawned
582
+ // on switch - born-TUI resumes lazily via the Resume bar
583
+ // (resume_tui_session), and born-GUI never auto-converts to a terminal.
598
584
  resolveSessionForView(xmTarget, ws);
599
585
  }
600
586
  // If the target session's vendor doesn't own the currently cached
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.40.0-beta.4",
3
+ "version": "2.40.1-beta.1",
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",