clay-server 3.4.0-beta.2 → 3.4.0-beta.21

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.
Files changed (82) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +59 -59
  3. package/lib/project-connection.js +35 -7
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-mate-interaction.js +21 -1
  6. package/lib/project-memory.js +3 -0
  7. package/lib/project-models.js +220 -0
  8. package/lib/project-session-handoff.js +162 -0
  9. package/lib/project-session-pair.js +14 -7
  10. package/lib/project-session-spawn.js +1 -1
  11. package/lib/project-sessions.js +13 -45
  12. package/lib/project-worker-proposal.js +51 -14
  13. package/lib/project.js +38 -80
  14. package/lib/public/antigravity-avatar.png +0 -0
  15. package/lib/public/app.js +29 -0
  16. package/lib/public/copilot-avatar.svg +5 -0
  17. package/lib/public/css/command-palette.css +22 -2
  18. package/lib/public/css/icon-strip.css +29 -13
  19. package/lib/public/css/input.css +56 -0
  20. package/lib/public/css/mates.css +6 -0
  21. package/lib/public/css/menus.css +38 -25
  22. package/lib/public/css/messages.css +9 -0
  23. package/lib/public/css/pane.css +16 -3
  24. package/lib/public/css/pwa-mobile.css +17 -0
  25. package/lib/public/css/session-actions.css +98 -0
  26. package/lib/public/css/title-bar.css +19 -0
  27. package/lib/public/grok-avatar.svg +4 -0
  28. package/lib/public/index.html +29 -7
  29. package/lib/public/junie-avatar.svg +11 -0
  30. package/lib/public/kimi-avatar.svg +4 -0
  31. package/lib/public/modules/agent-config-selects.js +69 -0
  32. package/lib/public/modules/app-header.js +4 -22
  33. package/lib/public/modules/app-messages.js +54 -11
  34. package/lib/public/modules/app-panels.js +36 -83
  35. package/lib/public/modules/app-projects.js +3 -1
  36. package/lib/public/modules/app-rendering.js +19 -4
  37. package/lib/public/modules/background-tasks-ui.js +55 -0
  38. package/lib/public/modules/mate-sidebar.js +11 -3
  39. package/lib/public/modules/model-picker.js +263 -0
  40. package/lib/public/modules/notifications.js +7 -1
  41. package/lib/public/modules/pane-bridge.js +11 -0
  42. package/lib/public/modules/pane-links.js +23 -0
  43. package/lib/public/modules/project-switcher.js +7 -6
  44. package/lib/public/modules/session-actions.js +294 -0
  45. package/lib/public/modules/sidebar-mates.js +1 -1
  46. package/lib/public/modules/sidebar-mobile.js +2 -1
  47. package/lib/public/modules/sidebar-projects.js +34 -43
  48. package/lib/public/modules/sidebar-sessions.js +20 -6
  49. package/lib/public/modules/split-pair-ui.js +16 -77
  50. package/lib/public/modules/split-view.js +3 -0
  51. package/lib/public/modules/tools.js +6 -1
  52. package/lib/public/modules/vendor-priority.js +14 -0
  53. package/lib/public/modules/vendor-selection.js +20 -0
  54. package/lib/public/modules/worker-proposal.js +6 -1
  55. package/lib/public/modules/worktree-location.js +17 -0
  56. package/lib/public/qwen-avatar.svg +4 -0
  57. package/lib/public/style.css +4 -2
  58. package/lib/sdk-bridge.js +103 -56
  59. package/lib/sdk-message-processor.js +26 -4
  60. package/lib/session-handoff-context.js +110 -0
  61. package/lib/session-notes-mcp-server.js +8 -1
  62. package/lib/sessions.js +4 -0
  63. package/lib/worktree.js +9 -4
  64. package/lib/ws-schema.js +9 -1
  65. package/lib/yoke/acp-agent-profiles.js +64 -14
  66. package/lib/yoke/adapters/antigravity.js +417 -0
  67. package/lib/yoke/adapters/claude.js +34 -0
  68. package/lib/yoke/adapters/codex.js +101 -5
  69. package/lib/yoke/adapters/copilot.js +7 -0
  70. package/lib/yoke/adapters/grok.js +7 -0
  71. package/lib/yoke/adapters/junie.js +7 -0
  72. package/lib/yoke/adapters/kimi.js +7 -0
  73. package/lib/yoke/adapters/kiro.js +2 -2
  74. package/lib/yoke/adapters/qwen.js +7 -0
  75. package/lib/yoke/codex-app-server.js +25 -8
  76. package/lib/yoke/index.js +67 -28
  77. package/lib/yoke/instructions.js +3 -0
  78. package/lib/yoke/interface.js +12 -0
  79. package/lib/yoke/vendor-registry.js +61 -6
  80. package/package.json +1 -1
  81. package/lib/public/gemini-avatar.svg +0 -11
  82. package/lib/yoke/adapters/gemini.js +0 -7
package/lib/project.js CHANGED
@@ -27,6 +27,7 @@ var { attachImage } = require("./project-image");
27
27
  var { attachKnowledge } = require("./project-knowledge");
28
28
  var { attachFilesystem } = require("./project-filesystem");
29
29
  var { attachSessions } = require("./project-sessions");
30
+ var { attachModels } = require("./project-models");
30
31
  var { attachUserMessage } = require("./project-user-message");
31
32
  var { attachShellCommand } = require("./project-shell-command");
32
33
  var { attachConnection } = require("./project-connection");
@@ -35,6 +36,7 @@ var { createLocalMcp } = require("./mcp-local");
35
36
  var { attachEmail: attachEmailModule } = require("./project-email");
36
37
  var { attachSessionSpawn } = require("./project-session-spawn");
37
38
  var { attachSessionPair } = require("./project-session-pair");
39
+ var { attachSessionHandoff } = require("./project-session-handoff");
38
40
  var { attachSessionNotes, composeSystemPrompts } = require("./project-session-notes");
39
41
  var { attachSessionDocument } = require("./project-session-document");
40
42
  var { attachSplitGroups } = require("./session-split-groups");
@@ -163,7 +165,7 @@ function createProjectContext(opts) {
163
165
  var updateChannel = opts.updateChannel || "stable";
164
166
  var osUsers = opts.osUsers || false;
165
167
  var projectOwnerId = opts.projectOwnerId || null;
166
- var worktreeMeta = opts.worktreeMeta || null; // { parentSlug, branch, accessible }
168
+ var worktreeMeta = opts.worktreeMeta || null; // { parentSlug, branch, external }
167
169
  var isMate = opts.isMate || false;
168
170
  var onCreateWorktree = opts.onCreateWorktree || null;
169
171
  var serverPort = opts.port || 2633;
@@ -470,10 +472,9 @@ function createProjectContext(opts) {
470
472
 
471
473
  var _projModel = typeof opts.onGetProjectDefaultModel === "function" ? opts.onGetProjectDefaultModel(slug) : null;
472
474
  var _srvModel = typeof opts.onGetServerDefaultModel === "function" ? opts.onGetServerDefaultModel() : null;
473
- sm._savedDefaultModel = (_projModel && _projModel.model) || (_srvModel && _srvModel.model) || null;
474
- // Immediately apply the saved default so config_state on connect reflects it
475
- // before the SDK has warmed up and fired system/init.
476
- if (sm._savedDefaultModel) sm.currentModel = sm._savedDefaultModel;
475
+ var savedDefaultModel = (_projModel && _projModel.model) || (_srvModel && _srvModel.model) || null;
476
+ sm.defaultModelByVendor = sm.defaultModelByVendor || {};
477
+ if (savedDefaultModel) sm.defaultModelByVendor[sm.defaultVendor || "claude"] = savedDefaultModel;
477
478
 
478
479
  // --- Local MCP (direct process management for localhost clients) ---
479
480
  var _localMcp = createLocalMcp();
@@ -569,6 +570,18 @@ function createProjectContext(opts) {
569
570
  getLinuxUserForSession: getLinuxUserForSession,
570
571
  getPairToolDefs: function (boundSession) { return _sessionPair.getToolDefs(boundSession); },
571
572
  });
573
+ var _sessionHandoff = attachSessionHandoff({
574
+ cwd: cwd,
575
+ sm: sm,
576
+ isMate: isMate,
577
+ splitStore: _splitGroups.store,
578
+ getSdk: function () { return sdk; },
579
+ sendTo: sendTo,
580
+ usersModule: usersModule,
581
+ adapters: adapters,
582
+ getLinuxUserForSession: getLinuxUserForSession,
583
+ onProcessingChanged: onProcessingChanged,
584
+ });
572
585
  var _debate = null;
573
586
  var _debateProposal = attachDebateProposal({
574
587
  cwd: cwd,
@@ -835,6 +848,7 @@ function createProjectContext(opts) {
835
848
  getNotificationsModule: function () { return _notifications; },
836
849
  mateDisplayName: opts.mateDisplayName || "",
837
850
  isMate: isMate,
851
+ osUsers: osUsers,
838
852
  dangerouslySkipPermissions: dangerouslySkipPermissions,
839
853
  mcpServers: getLocalMcpServers,
840
854
  getRemoteMcpServers: function () { return _mcp.getMcpServers(); },
@@ -1052,78 +1066,6 @@ function createProjectContext(opts) {
1052
1066
  return;
1053
1067
  }
1054
1068
 
1055
- // --- Vendor model switching ---
1056
- if (msg.type === "get_vendor_models") {
1057
- (async function() {
1058
- if (msg.vendor) {
1059
- try {
1060
- var modelLinuxUser = getLinuxUserForWs(ws);
1061
- var vendorAdapter = adapters[msg.vendor] || null;
1062
- if (!vendorAdapter) {
1063
- vendorAdapter = await yoke.lazyCreateAdapter(adapters, msg.vendor, {
1064
- cwd: cwd,
1065
- linuxUser: modelLinuxUser || undefined,
1066
- clayPort: serverPort,
1067
- clayTls: serverTls,
1068
- clayAuthToken: serverAuthToken,
1069
- slug: slug,
1070
- });
1071
- }
1072
- var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[msg.vendor]
1073
- || !sm.modelsByVendor || !sm.modelsByVendor[msg.vendor];
1074
- if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
1075
- // Init warms the adapter, but a slow/failed init must not block
1076
- // model listing (e.g. Codex models are a fixed list). Keep going
1077
- // to supportedModels() even if init throws.
1078
- try {
1079
- var readyResult = await vendorAdapter.init({
1080
- cwd: cwd,
1081
- clayPort: serverPort,
1082
- clayTls: serverTls,
1083
- clayAuthToken: serverAuthToken,
1084
- slug: slug,
1085
- });
1086
- sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
1087
- sm.capabilitiesByVendor[msg.vendor] = readyResult.capabilities || {};
1088
- sm.modelsByVendor = sm.modelsByVendor || {};
1089
- if (Array.isArray(readyResult.models)) sm.modelsByVendor[msg.vendor] = readyResult.models;
1090
- } catch (e) {
1091
- console.error("[project] " + msg.vendor + " init failed (continuing to model list):", e.message || e);
1092
- }
1093
- }
1094
- if (vendorAdapter) {
1095
- sm.availableVendors = Object.keys(adapters);
1096
- sm.modelsByVendor = sm.modelsByVendor || {};
1097
- if (!sm.modelsByVendor[msg.vendor] && typeof vendorAdapter.supportedModels === "function") {
1098
- sm.modelsByVendor[msg.vendor] = await vendorAdapter.supportedModels();
1099
- }
1100
- }
1101
- } catch (e) {
1102
- console.error("[project] get_vendor_models lazy init failed for " + msg.vendor + ":", e.message || e);
1103
- }
1104
- }
1105
- var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
1106
- var firstModel = vendorModels[0] || "";
1107
- // model value can be string or {value, displayName} object
1108
- var defaultModel = typeof firstModel === "string" ? firstModel : (firstModel.value || "");
1109
- // Preserve the user's current model selection if it belongs to this
1110
- // vendor, rather than always snapping back to the vendor's default.
1111
- var modelToSend = defaultModel;
1112
- if (sm.currentModel) {
1113
- for (var mi = 0; mi < vendorModels.length; mi++) {
1114
- var mv = typeof vendorModels[mi] === "string" ? vendorModels[mi] : (vendorModels[mi].value || "");
1115
- if (mv === sm.currentModel) {
1116
- modelToSend = sm.currentModel;
1117
- break;
1118
- }
1119
- }
1120
- }
1121
- var vendorCapabilities = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[msg.vendor]) || {};
1122
- sendTo(ws, { type: "model_info", model: modelToSend, models: vendorModels, vendor: msg.vendor, capabilities: vendorCapabilities, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
1123
- })();
1124
- return;
1125
- }
1126
-
1127
1069
  // --- Debate ---
1128
1070
  if (msg.type === "debate_start") {
1129
1071
  handleDebateStart(ws, msg);
@@ -1171,7 +1113,9 @@ function createProjectContext(opts) {
1171
1113
 
1172
1114
  // --- Sessions, config, project mgmt (delegated to project-sessions.js) ---
1173
1115
  if (_sessionPair.handleMessage(ws, msg)) return;
1116
+ if (_sessionHandoff.handleMessage(ws, msg)) return;
1174
1117
  if (_splitGroups.handleMessage(ws, msg)) return;
1118
+ if (_models.handleMessage(ws, msg)) return;
1175
1119
  if (_sessions.handleSessionsMessage(ws, msg)) return;
1176
1120
 
1177
1121
  // --- Filesystem, settings, env (delegated to project-filesystem.js) ---
@@ -1384,6 +1328,20 @@ function createProjectContext(opts) {
1384
1328
  _notifications: _notifications,
1385
1329
  });
1386
1330
 
1331
+ var _models = attachModels({
1332
+ cwd: cwd,
1333
+ slug: slug,
1334
+ sm: sm,
1335
+ sdk: sdk,
1336
+ adapters: adapters,
1337
+ sendTo: sendTo,
1338
+ getSessionForWs: getSessionForWs,
1339
+ getLinuxUserForWs: getLinuxUserForWs,
1340
+ serverPort: serverPort,
1341
+ serverTls: serverTls,
1342
+ serverAuthToken: serverAuthToken,
1343
+ });
1344
+
1387
1345
  // --- User message handler (delegated to project-user-message.js) ---
1388
1346
  var _userMessage = attachUserMessage({
1389
1347
  cwd: cwd,
@@ -1686,8 +1644,8 @@ function createProjectContext(opts) {
1686
1644
  getProject: function () { return project; },
1687
1645
  // Exposed so the first websocket connection can lazily warm up the
1688
1646
  // adapters for this project (see project-connection handleConnection).
1689
- warmup: function () {
1690
- sdk.warmup();
1647
+ warmup: function (linuxUser) {
1648
+ sdk.warmup(osUsers ? linuxUser : null);
1691
1649
  sdk.startIdleReaper();
1692
1650
  if (!osUsers && !sessionTitleMigrationScheduled) {
1693
1651
  sessionTitleMigrationScheduled = true;
@@ -1813,7 +1771,7 @@ function createProjectContext(opts) {
1813
1771
  status.isWorktree = true;
1814
1772
  status.parentSlug = worktreeMeta.parentSlug;
1815
1773
  status.branch = worktreeMeta.branch;
1816
- status.worktreeAccessible = worktreeMeta.accessible;
1774
+ status.worktreeExternal = worktreeMeta.external === true;
1817
1775
  }
1818
1776
  if (usersModule.isMultiUser()) {
1819
1777
  var seen = {};
package/lib/public/app.js CHANGED
@@ -60,10 +60,12 @@ import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateR
60
60
  import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
61
61
  import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity, drawFaviconAnimFrame as _favDrawFaviconAnimFrame } from './modules/app-favicon.js';
62
62
  import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
63
+ import { initSessionActions } from './modules/session-actions.js';
63
64
  import { initMisc, flushPendingExtMessages, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showPasteModal as _miscShowPasteModal, closePasteModal as _miscClosePasteModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
64
65
  import { initSkillInstall, requireSkills as _siRequireSkills, requireClayMateInterview as _siRequireClayMateInterview, handleSkillInstallWs as _siHandleSkillInstallWs } from './modules/app-skills-install.js';
65
66
  import { initDebateUi, showDebateConcludeConfirm as _debShowDebateConcludeConfirm, exitDebateConcludeMode as _debExitDebateConcludeMode, handleDebateConcludeSend as _debHandleDebateConcludeSend, showDebateEndedMode as _debShowDebateEndedMode, exitDebateEndedMode as _debExitDebateEndedMode, showDebateUserFloor as _debShowDebateUserFloor, exitDebateFloorMode as _debExitDebateFloorMode, handleDebateFloorSend as _debHandleDebateFloorSend, renderDebateUserFloorDone as _debRenderDebateUserFloorDone, showDebateSticky as _debShowDebateSticky, showDebateBottomBar as _debShowDebateBottomBar, removeDebateBottomBar as _debRemoveDebateBottomBar, sendDebateStickyComment as _debSendDebateStickyComment, updateDebateRound as _debUpdateDebateRound } from './modules/app-debate-ui.js';
66
67
  import { initLoopUi, updateLoopInputVisibility as _loopUpdateLoopInputVisibility, updateLoopButton as _loopUpdateLoopButton, showLoopBanner as _loopShowLoopBanner, updateLoopBanner as _loopUpdateLoopBanner, updateRalphBars as _loopUpdateRalphBars, showRalphCraftingBar as _loopShowRalphCraftingBar, showRalphApprovalBar as _loopShowRalphApprovalBar, updateRalphApprovalStatus as _loopUpdateRalphApprovalStatus, openRalphPreviewModal as _loopOpenRalphPreviewModal, showExecModal as _loopShowExecModal, closeExecModal as _loopCloseExecModal, updateExecModalStatus as _loopUpdateExecModalStatus } from './modules/app-loop-ui.js';
68
+ import { initBackgroundTasksUi } from './modules/background-tasks-ui.js';
67
69
  import { initLoopWizard, openRalphWizard as _loopOpenRalphWizard, closeRalphWizard as _loopCloseRalphWizard, getWizardSource as _loopGetWizardSource } from './modules/app-loop-wizard.js';
68
70
  import { initAppNotifications, handleNotificationsState as _notifHandleState, handleNotificationCreated as _notifHandleCreated, handleNotificationDismissed as _notifHandleDismissed, handleNotificationDismissedAll as _notifHandleDismissedAll } from './modules/app-notifications.js';
69
71
  import { initWhatsNew, handleWhatsNewState as _wnHandleState, handleWhatsNewSeenResult as _wnHandleSeenResult } from './modules/whats-new.js';
@@ -295,6 +297,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
295
297
  slashCommands: [],
296
298
  processing: false,
297
299
  activeSessionId: null,
300
+ activeBackgroundTasks: [],
301
+ sessionVendorBound: false,
298
302
  cliSessionId: null,
299
303
  isHeadlessMode: false,
300
304
  savedMainSlug: null,
@@ -338,6 +342,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
338
342
  markdownSlideLevelExplicit: false,
339
343
  currentModel: "",
340
344
  currentModels: [],
345
+ modelListVendor: "",
346
+ modelListStatus: "idle",
347
+ modelListError: "",
348
+ modelRequestId: "",
349
+ modelSelectionPending: null,
350
+ modelSelectionError: "",
341
351
  // Project's last-used vendor; seeds the sidebar's "New session" button.
342
352
  lastVendor: "",
343
353
  // Static adapter metadata sent before any vendor is initialized.
@@ -556,6 +566,22 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
556
566
  var newMsgBtnDefault = "\u2193 Latest";
557
567
  var newMsgBtnActivity = "\u2193 New activity";
558
568
 
569
+ // The composer grows for mobile tabs, safe areas, multiline input, and the
570
+ // keyboard. Keep the jump button immediately above its real height instead
571
+ // of relying on one fixed bottom offset.
572
+ function positionNewMsgButton() {
573
+ var inputArea = $("input-area");
574
+ if (!inputArea) return;
575
+ newMsgBtn.style.bottom = (inputArea.offsetHeight + 12) + "px";
576
+ }
577
+ var inputAreaForNewMsgBtn = $("input-area");
578
+ if (inputAreaForNewMsgBtn && window.ResizeObserver) {
579
+ new ResizeObserver(positionNewMsgButton).observe(inputAreaForNewMsgBtn);
580
+ } else {
581
+ window.addEventListener("resize", positionNewMsgButton);
582
+ }
583
+ positionNewMsgButton();
584
+
559
585
  messagesEl.addEventListener("scroll", function () {
560
586
  // While sticky-bottom is armed (e.g. just after history_done or a "New
561
587
  // activity" click), suppress "user scrolled up" detection. Growth-induced
@@ -616,6 +642,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
616
642
 
617
643
  // --- Header module (rename, info popover, history) ---
618
644
  initHeader();
645
+ initSessionActions();
619
646
 
620
647
  // --- Skill Install module ---
621
648
  initSkillInstall();
@@ -624,6 +651,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
624
651
  initDebateUi();
625
652
 
626
653
  initLoopUi();
654
+ initBackgroundTasksUi();
627
655
  initLoopWizard();
628
656
 
629
657
  // --- Notifications module ---
@@ -932,6 +960,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
932
960
  messagesEl: messagesEl,
933
961
  sessionListEl: sessionListEl,
934
962
  scrollToBottom: scrollToBottom,
963
+ forceScrollToBottom: forceScrollToBottom,
935
964
  basePath: basePath,
936
965
  toggleUsagePanel: toggleUsagePanel,
937
966
  toggleStatusPanel: toggleStatusPanel,
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="GitHub Copilot CLI">
2
+ <rect width="64" height="64" rx="14" fill="#24292f"/>
3
+ <path d="M15 25c0-8 7-14 17-14s17 6 17 14v15c0 8-7 13-17 13s-17-5-17-13zm8 1v13c0 4 4 7 9 7s9-3 9-7V26c0-4-4-7-9-7s-9 3-9 7z" fill="#fff"/>
4
+ <circle cx="27" cy="30" r="3" fill="#24292f"/><circle cx="37" cy="30" r="3" fill="#24292f"/>
5
+ </svg>
@@ -434,6 +434,7 @@
434
434
  on hover/active, and a 4-direction drop-shadow + white glow on the
435
435
  emoji img for readability on either background. */
436
436
  .project-switcher-item .cmd-palette-item-icon {
437
+ position: relative;
437
438
  width: 44px;
438
439
  height: 44px;
439
440
  font-size: 28px;
@@ -447,6 +448,26 @@
447
448
  flex-shrink: 0;
448
449
  transition: background 0.15s, color 0.15s;
449
450
  }
451
+ .project-switcher-external-badge {
452
+ position: absolute;
453
+ right: -5px;
454
+ bottom: -5px;
455
+ width: 17px;
456
+ height: 17px;
457
+ display: flex;
458
+ align-items: center;
459
+ justify-content: center;
460
+ border: 2px solid var(--bg);
461
+ border-radius: 50%;
462
+ background: var(--warning);
463
+ color: var(--bg);
464
+ pointer-events: none;
465
+ }
466
+ .project-switcher-item .cmd-palette-item-icon .project-switcher-external-badge .lucide {
467
+ width: 9px;
468
+ height: 9px;
469
+ stroke-width: 2.8;
470
+ }
450
471
  .project-switcher-item .cmd-palette-item-icon .lucide {
451
472
  width: 22px;
452
473
  height: 22px;
@@ -515,8 +536,7 @@
515
536
  letter-spacing: 0.5px;
516
537
  }
517
538
 
518
- /* Disabled tile (e.g. worktree with worktreeAccessible: false). Not a
519
- valid switch target, so skip highlight, skip click, visually dim. */
539
+ /* Disabled tiles are skipped by keyboard and pointer selection. */
520
540
  .project-switcher-item.disabled {
521
541
  opacity: 0.35;
522
542
  cursor: not-allowed;
@@ -1257,20 +1257,27 @@
1257
1257
  z-index: 1;
1258
1258
  }
1259
1259
 
1260
- /* Disabled/inaccessible worktree */
1261
- .icon-strip-wt-item.wt-disabled {
1262
- opacity: 0.35;
1263
- cursor: not-allowed;
1264
- }
1265
-
1266
- .icon-strip-wt-item.wt-disabled::after {
1267
- content: "\1F6AB";
1260
+ /* Worktree registered outside the main project folder. It remains usable;
1261
+ the badge communicates location without treating it as an error. */
1262
+ .worktree-external-badge {
1268
1263
  position: absolute;
1269
- font-size: 14px;
1264
+ right: 5px;
1265
+ bottom: 1px;
1266
+ width: 14px;
1267
+ height: 14px;
1268
+ display: flex;
1269
+ align-items: center;
1270
+ justify-content: center;
1271
+ border-radius: 50%;
1272
+ border: 2px solid var(--sidebar-bg);
1273
+ background: var(--warning);
1274
+ color: var(--bg);
1270
1275
  z-index: 3;
1271
- filter: grayscale(0.3);
1276
+ pointer-events: none;
1272
1277
  }
1273
1278
 
1279
+ .worktree-external-badge .lucide { width: 8px; height: 8px; stroke-width: 2.8; }
1280
+
1274
1281
  /* Group add button */
1275
1282
  .icon-strip-group-add {
1276
1283
  width: 30px;
@@ -1420,11 +1427,20 @@ select.wt-modal-input {
1420
1427
  opacity: 0.85;
1421
1428
  }
1422
1429
 
1423
- .mobile-project-item.wt-disabled {
1424
- opacity: 0.35;
1425
- cursor: not-allowed;
1430
+ .mobile-worktree-external-badge {
1431
+ width: 20px;
1432
+ height: 20px;
1433
+ display: inline-flex;
1434
+ align-items: center;
1435
+ justify-content: center;
1436
+ border-radius: 7px;
1437
+ background: color-mix(in srgb, var(--warning) 14%, transparent);
1438
+ color: var(--warning);
1439
+ flex-shrink: 0;
1426
1440
  }
1427
1441
 
1442
+ .mobile-worktree-external-badge .lucide { width: 12px; height: 12px; stroke-width: 2.4; }
1443
+
1428
1444
  .mobile-folder-chevron {
1429
1445
  font-size: 8px;
1430
1446
  color: var(--text-dimmer);
@@ -667,6 +667,50 @@
667
667
  position: relative;
668
668
  }
669
669
 
670
+ .background-tasks-bar {
671
+ max-width: var(--content-width);
672
+ margin: 0 auto 8px;
673
+ border: 1px solid var(--border);
674
+ border-radius: 8px;
675
+ background: var(--input-bg);
676
+ overflow: hidden;
677
+ }
678
+
679
+ .background-tasks-toggle {
680
+ width: 100%;
681
+ display: flex;
682
+ align-items: center;
683
+ gap: 7px;
684
+ padding: 8px 10px;
685
+ border: 0;
686
+ background: transparent;
687
+ color: var(--text-secondary);
688
+ cursor: pointer;
689
+ font: inherit;
690
+ text-align: left;
691
+ }
692
+
693
+ .background-tasks-chip, .session-background-task-count {
694
+ display: inline-flex;
695
+ align-items: center;
696
+ justify-content: center;
697
+ min-width: 16px;
698
+ height: 16px;
699
+ padding: 0 4px;
700
+ border-radius: 8px;
701
+ background: var(--accent);
702
+ color: var(--bg);
703
+ font-size: 10px;
704
+ font-weight: 700;
705
+ }
706
+
707
+ .background-tasks-list { padding: 0 10px 8px; }
708
+ .background-task-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid var(--border-subtle); }
709
+ .background-task-description { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
710
+ .background-task-type { padding: 2px 6px; border-radius: 5px; background: var(--code-bg); color: var(--text-muted); font-size: 11px; }
711
+ .background-task-stop { border: 1px solid var(--border); border-radius: 5px; padding: 3px 7px; background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: 12px; }
712
+ .background-task-stop:hover { color: var(--text); border-color: var(--text-muted); }
713
+
670
714
  #input-row {
671
715
  display: flex;
672
716
  flex-direction: column;
@@ -790,6 +834,7 @@
790
834
  opacity: 0.35;
791
835
  cursor: default;
792
836
  }
837
+ .vendor-toggle-btn.disabled:not(.active) { display: none; }
793
838
  .vendor-toggle-icon {
794
839
  width: 16px;
795
840
  height: 16px;
@@ -799,6 +844,17 @@
799
844
  .vendor-toggle-label {
800
845
  pointer-events: none;
801
846
  }
847
+ .vendor-toggle-btn.experimental::after,
848
+ .vendor-experimental-badge {
849
+ content: "🧪";
850
+ font-size: 10px;
851
+ line-height: 1;
852
+ font-weight: 400;
853
+ filter: saturate(0.85);
854
+ }
855
+ .vendor-experimental-badge {
856
+ margin-left: auto;
857
+ }
802
858
  @media (min-width: 901px) {
803
859
  .vendor-toggle-btn:not(.active) .vendor-toggle-label { display: none; }
804
860
  .vendor-toggle-btn:not(.active) { padding: 0 8px; }
@@ -682,6 +682,12 @@
682
682
  .mate-vendor-label {
683
683
  pointer-events: none;
684
684
  }
685
+ .mate-vendor-btn.experimental::after {
686
+ content: "🧪";
687
+ font-size: 9px;
688
+ line-height: 1;
689
+ font-weight: 400;
690
+ }
685
691
  .mate-vendor-btn:not(.active) .mate-vendor-label { display: none; }
686
692
 
687
693
  /* Collapse button inside mate header: white to match header style */
@@ -43,30 +43,9 @@
43
43
  #header-rename-btn:hover { color: var(--text-secondary); background: rgba(var(--overlay-rgb),0.04); border-color: var(--border); }
44
44
  #header-rename-btn .lucide { width: 13px; height: 13px; }
45
45
 
46
- #header-add-worker-btn {
47
- display: flex;
48
- align-items: center;
49
- justify-content: center;
50
- width: 24px;
51
- height: 24px;
52
- border: 1px solid transparent;
53
- background: none;
54
- color: var(--text-dimmer);
55
- cursor: pointer;
56
- border-radius: 8px;
57
- flex-shrink: 0;
58
- padding: 0;
59
- margin-left: 4px;
60
- transition: color 0.15s, background 0.15s, border-color 0.15s;
61
- }
62
- #header-add-worker-btn:hover { color: var(--text-secondary); background: rgba(var(--overlay-rgb),0.04); border-color: var(--border); }
63
- #header-add-worker-btn .lucide { width: 13px; height: 13px; }
64
- #header-add-worker-btn.hidden { display: none; }
65
- body.dm-mode #header-add-worker-btn { display: none; }
66
-
67
- /* "Close terminal" button: sits just left of the rename pencil during a live
68
- TUI session. Matches the rename button's chrome; reds out on hover since it
69
- stops the running claude (the session stays resumable). */
46
+ /* "Close terminal" button for a live TUI session. Matches the session-control
47
+ chrome; reds out on hover since it stops the running agent while preserving
48
+ the resumable session. */
70
49
  #header-tui-close-btn {
71
50
  display: flex;
72
51
  align-items: center;
@@ -667,6 +646,41 @@ body.dm-mode #header-add-worker-btn { display: none; }
667
646
  color: var(--text-dimmer);
668
647
  font-style: italic;
669
648
  }
649
+ .config-model-state {
650
+ display: flex;
651
+ align-items: center;
652
+ gap: 10px;
653
+ min-width: 280px;
654
+ padding: 10px 14px;
655
+ color: var(--text-secondary);
656
+ font-size: 12px;
657
+ white-space: normal;
658
+ }
659
+ .config-model-state > div { flex: 1; min-width: 0; }
660
+ .config-model-state-title { color: var(--text); font-weight: 600; }
661
+ .config-model-state-detail { margin-top: 2px; color: var(--text-muted); line-height: 1.35; }
662
+ .config-model-spinner {
663
+ width: 14px;
664
+ height: 14px;
665
+ flex: 0 0 auto;
666
+ border: 2px solid var(--border);
667
+ border-top-color: var(--accent);
668
+ border-radius: 50%;
669
+ animation: config-model-spin 0.8s linear infinite;
670
+ }
671
+ @keyframes config-model-spin { to { transform: rotate(360deg); } }
672
+ .config-model-retry {
673
+ flex: 0 0 auto;
674
+ border: 1px solid var(--border);
675
+ border-radius: 5px;
676
+ padding: 4px 8px;
677
+ background: var(--surface-raised);
678
+ color: var(--text-secondary);
679
+ font: inherit;
680
+ cursor: pointer;
681
+ }
682
+ .config-model-retry:hover { color: var(--text); border-color: var(--text-muted); }
683
+ .config-radio-item.pending { color: var(--accent); opacity: 0.75; }
670
684
 
671
685
  /* Segmented control for effort */
672
686
  .config-segmented {
@@ -980,4 +994,3 @@ body.dm-mode #header-add-worker-btn { display: none; }
980
994
  #config-chip .config-chip-icon { display: inline; width: 14px; height: 14px; }
981
995
  #config-chip-label { display: none; }
982
996
  }
983
-
@@ -5,12 +5,21 @@
5
5
  #messages {
6
6
  flex: 1;
7
7
  overflow-y: auto;
8
+ overflow-x: hidden;
8
9
  -webkit-overflow-scrolling: touch;
9
10
  overscroll-behavior-y: contain;
10
11
  padding: 20px 0 12px;
11
12
  position: relative;
12
13
  }
13
14
 
15
+ /* Rich message content can contain wide tables, diagrams, or generated HTML.
16
+ Keep that content inside the chat scroller; nested code blocks still own
17
+ their intentional horizontal scrolling. */
18
+ #messages > * {
19
+ min-width: 0;
20
+ max-width: 100%;
21
+ }
22
+
14
23
  /* --- Remote cursors (Figma-style) --- */
15
24
  .remote-cursor {
16
25
  pointer-events: none;
@@ -23,6 +23,10 @@ body.pane-mode #sticky-notes-container {
23
23
  display: none !important;
24
24
  }
25
25
 
26
+ body.pane-mode .notif-banner-container {
27
+ display: none !important;
28
+ }
29
+
26
30
  body.pane-mode #main-area {
27
31
  border: 0;
28
32
  border-radius: 0;
@@ -96,7 +100,8 @@ body.pane-mode #input-area {
96
100
  }
97
101
 
98
102
  .split-pane-title {
99
- flex: 1;
103
+ flex: 0 1 auto;
104
+ max-width: 46%;
100
105
  min-width: 0;
101
106
  overflow: hidden;
102
107
  text-overflow: ellipsis;
@@ -198,15 +203,18 @@ button.split-pair-role:hover { filter: brightness(1.25); }
198
203
  max-width: var(--content-width);
199
204
  margin: 0 auto;
200
205
  padding: 4px 10px;
201
- border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
202
- border-bottom: 0;
203
206
  border-radius: 8px 8px 0 0;
204
207
  background: color-mix(in srgb, var(--accent) 8%, var(--bg));
205
208
  color: var(--text-secondary);
206
209
  font-size: 11px;
207
210
  line-height: 1.4;
211
+ transition: box-shadow 0.2s;
208
212
  }
209
213
  .pane-delegation-notice.visible { display: flex; }
214
+ .pane-delegation-notice.visible:has(+ #input-wrapper #input-row:focus-within) {
215
+ box-shadow: 0 0 0 1px var(--border);
216
+ clip-path: inset(-1px -1px 0 -1px);
217
+ }
210
218
  .pane-delegation-notice.visible + #input-wrapper #input-row {
211
219
  border-radius: 0 0 8px 8px;
212
220
  }
@@ -279,12 +287,17 @@ button.split-pair-role:hover { filter: brightness(1.25); }
279
287
  color: var(--text-dimmer);
280
288
  cursor: pointer;
281
289
  flex-shrink: 0;
290
+ margin-left: auto;
282
291
  }
283
292
 
284
293
  .split-pane-context.has-data {
285
294
  display: inline-flex;
286
295
  }
287
296
 
297
+ .split-pane-context:not(.has-data) + .split-pane-close {
298
+ margin-left: auto;
299
+ }
300
+
288
301
  .split-pane-context:hover {
289
302
  background: var(--bg-alt);
290
303
  color: var(--text);
@@ -0,0 +1,17 @@
1
+ /* In an iOS Home Screen app, a 100%-tall root can be measured against the
2
+ shortened safe-area viewport. Use the physical viewport for the root while
3
+ retaining safe-area padding on controls inside the app. */
4
+ @media (max-width: 768px) {
5
+ html.pwa-standalone,
6
+ html.pwa-standalone body {
7
+ height: 100vh;
8
+ }
9
+ }
10
+
11
+ @media (max-width: 768px) and (display-mode: standalone),
12
+ (max-width: 768px) and (display-mode: fullscreen) {
13
+ html,
14
+ body {
15
+ height: 100vh;
16
+ }
17
+ }