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
@@ -0,0 +1,98 @@
1
+ #header-session-actions-btn {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ width: 28px;
6
+ height: 28px;
7
+ padding: 0;
8
+ border: 1px solid transparent;
9
+ border-radius: 8px;
10
+ background: transparent;
11
+ color: var(--text-dimmer);
12
+ cursor: pointer;
13
+ flex-shrink: 0;
14
+ transition: color 0.15s, background 0.15s, border-color 0.15s;
15
+ }
16
+
17
+ #header-session-actions-btn:hover,
18
+ #header-session-actions-btn[aria-expanded="true"] {
19
+ color: var(--text);
20
+ background: rgba(var(--overlay-rgb), 0.05);
21
+ border-color: var(--border);
22
+ }
23
+
24
+ #header-session-actions-btn.hidden { display: none; }
25
+ #header-session-actions-btn .lucide { width: 16px; height: 16px; }
26
+
27
+ .session-actions-menu {
28
+ position: fixed;
29
+ z-index: 10020;
30
+ width: 286px;
31
+ padding: 6px;
32
+ border: 1px solid var(--border);
33
+ border-radius: 12px;
34
+ background: color-mix(in srgb, var(--bg) 96%, transparent);
35
+ box-shadow: 0 14px 38px rgba(var(--shadow-rgb), 0.32), 0 2px 8px rgba(var(--shadow-rgb), 0.2);
36
+ backdrop-filter: blur(18px);
37
+ }
38
+
39
+ .session-actions-row {
40
+ width: 100%;
41
+ border: 0;
42
+ background: transparent;
43
+ color: var(--text);
44
+ font: inherit;
45
+ text-align: left;
46
+ cursor: pointer;
47
+ }
48
+
49
+ .session-actions-row {
50
+ display: grid;
51
+ grid-template-columns: 30px minmax(0, 1fr) 16px;
52
+ align-items: center;
53
+ gap: 8px;
54
+ min-height: 54px;
55
+ padding: 7px 8px;
56
+ border-radius: 8px;
57
+ }
58
+
59
+ .session-actions-row:hover { background: var(--bg-alt); }
60
+ .session-actions-row:disabled { cursor: default; opacity: 0.42; }
61
+ .session-actions-row:disabled:hover { background: transparent; }
62
+
63
+ .session-actions-row-icon {
64
+ display: grid;
65
+ place-items: center;
66
+ width: 28px;
67
+ height: 28px;
68
+ border: 1px solid var(--border-subtle);
69
+ border-radius: 8px;
70
+ background: rgba(var(--overlay-rgb), 0.035);
71
+ color: var(--accent2);
72
+ }
73
+
74
+ .session-actions-row-icon .lucide { width: 15px; height: 15px; }
75
+ .session-actions-row-copy { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
76
+ .session-actions-row-copy strong { font-size: 12px; font-weight: 750; }
77
+ .session-actions-row-copy > span { color: var(--text-dimmer); font-size: 10px; line-height: 1.25; }
78
+ .session-actions-row-chevron { color: var(--text-dimmer); }
79
+ .session-actions-row-chevron .lucide { width: 13px; height: 13px; }
80
+
81
+ .handoff-modal { width: min(420px, calc(100vw - 32px)); }
82
+ .handoff-modal-desc { margin: -10px 0 14px; color: var(--text-dimmer); font-size: 12px; line-height: 1.45; }
83
+ .handoff-modal-source {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 10px;
87
+ margin-bottom: 14px;
88
+ padding: 10px;
89
+ border: 1px solid var(--border-subtle);
90
+ border-radius: 9px;
91
+ background: var(--bg-alt);
92
+ }
93
+ .handoff-modal-source > img { width: 26px; height: 26px; object-fit: contain; border-radius: 6px; }
94
+ .handoff-modal-source > span { display: grid; min-width: 0; gap: 1px; }
95
+ .handoff-modal-source strong { overflow: hidden; color: var(--text); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
96
+ .handoff-modal-source span span { color: var(--text-dimmer); font-size: 10px; }
97
+ .handoff-modal-field + .handoff-modal-field { margin-top: 10px; }
98
+ .handoff-modal-field .wt-modal-label { margin-top: 0; }
@@ -324,6 +324,25 @@
324
324
  display: none !important;
325
325
  }
326
326
 
327
+ .title-bar-content {
328
+ height: calc(48px + var(--safe-top));
329
+ padding: var(--safe-top) 16px 0;
330
+ }
331
+
332
+ /* The inline title editor must own the header while the keyboard is open.
333
+ Leaving the other controls visible made its usable width nearly zero. */
334
+ .title-bar-content.session-renaming .status,
335
+ .title-bar-content.session-renaming #header-left > :not(.header-rename-input) {
336
+ display: none !important;
337
+ }
338
+
339
+ .title-bar-content.session-renaming .header-rename-input {
340
+ flex: 1 1 100%;
341
+ width: 100%;
342
+ min-height: 36px;
343
+ font-size: 16px;
344
+ }
345
+
327
346
  #sidebar-column {
328
347
  width: 0 !important;
329
348
  min-width: 0 !important;
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Grok Build">
2
+ <rect width="64" height="64" rx="14" fill="#111"/>
3
+ <path d="M45 17A21 21 0 1 0 49 42H35v-9h22v4A25 25 0 1 1 51 13z" fill="#fff"/>
4
+ </svg>
@@ -30,7 +30,7 @@
30
30
  (function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
31
31
  </script>
32
32
  <script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
33
- <link rel="stylesheet" href="style.css?v=20260407">
33
+ <link rel="stylesheet" href="style.css?v=20260826a">
34
34
  <style>
35
35
  @media(max-width:768px){
36
36
  /* User messages: vertical stack, avatar on top, right-aligned */
@@ -373,12 +373,11 @@
373
373
  </div>
374
374
  <button id="hamburger-btn" aria-label="Toggle sidebar" title="Toggle sidebar"><i data-lucide="menu"></i></button>
375
375
  <span class="header-title" id="header-title">Connecting...</span>
376
+ <button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
376
377
  <button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
377
378
  <button id="header-full-access-btn" type="button" class="session-full-access hidden" role="switch" aria-checked="false" title="Skip Permissions is off — permission prompts are on">
378
379
  <span class="session-full-access-label">Skip Permissions</span><span class="session-full-access-track" aria-hidden="true"><span></span></span>
379
380
  </button>
380
- <button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
381
- <button id="header-add-worker-btn" type="button" title="Add Worker — this session becomes the Driver of a pair"><i data-lucide="user-plus"></i></button>
382
381
  <button type="button" id="header-tui-close-btn" class="header-tui-close-btn hidden" title="Close terminal (keeps history, resume anytime)" aria-label="Close terminal"><i data-lucide="power"></i></button>
383
382
  <div id="mate-mobile-title" class="mate-mobile-title hidden">
384
383
  <img id="mate-mobile-avatar" class="mate-mobile-avatar" alt="">
@@ -389,6 +388,9 @@
389
388
  <div id="ralph-sticky" class="hidden"></div>
390
389
  <div id="debate-sticky" class="hidden"></div>
391
390
  <div class="status">
391
+ <button id="header-session-actions-btn" type="button" aria-label="Session actions" title="Session actions" aria-haspopup="menu" aria-expanded="false">
392
+ <i data-lucide="ellipsis"></i>
393
+ </button>
392
394
  <button type="button" id="header-tui-font-btn" class="header-tui-font-btn hidden" title="Terminal font" aria-label="Terminal font">
393
395
  <i data-lucide="type"></i>
394
396
  </button>
@@ -528,14 +530,34 @@
528
530
  <img src="/codex-avatar.png" class="vendor-toggle-icon" alt="Codex">
529
531
  <span class="vendor-toggle-label">Codex</span>
530
532
  </button>
531
- <button id="vendor-btn-gemini" class="vendor-toggle-btn" data-vendor="gemini">
532
- <img src="/gemini-avatar.svg" class="vendor-toggle-icon" alt="Gemini">
533
- <span class="vendor-toggle-label">Gemini CLI</span>
533
+ <button id="vendor-btn-antigravity" class="vendor-toggle-btn" data-vendor="antigravity">
534
+ <img src="/antigravity-avatar.png" class="vendor-toggle-icon" alt="Antigravity">
535
+ <span class="vendor-toggle-label">Antigravity CLI</span>
534
536
  </button>
535
537
  <button id="vendor-btn-opencode" class="vendor-toggle-btn" data-vendor="opencode">
536
538
  <img src="/opencode-avatar.svg" class="vendor-toggle-icon" alt="OpenCode">
537
539
  <span class="vendor-toggle-label">OpenCode</span>
538
540
  </button>
541
+ <button id="vendor-btn-kimi" class="vendor-toggle-btn" data-vendor="kimi">
542
+ <img src="/kimi-avatar.svg" class="vendor-toggle-icon" alt="Kimi">
543
+ <span class="vendor-toggle-label">Kimi Code</span>
544
+ </button>
545
+ <button id="vendor-btn-grok" class="vendor-toggle-btn" data-vendor="grok">
546
+ <img src="/grok-avatar.svg" class="vendor-toggle-icon" alt="Grok">
547
+ <span class="vendor-toggle-label">Grok Build</span>
548
+ </button>
549
+ <button id="vendor-btn-copilot" class="vendor-toggle-btn" data-vendor="copilot">
550
+ <img src="/copilot-avatar.svg" class="vendor-toggle-icon" alt="GitHub Copilot">
551
+ <span class="vendor-toggle-label">GitHub Copilot CLI</span>
552
+ </button>
553
+ <button id="vendor-btn-qwen" class="vendor-toggle-btn" data-vendor="qwen">
554
+ <img src="/qwen-avatar.svg" class="vendor-toggle-icon" alt="Qwen">
555
+ <span class="vendor-toggle-label">Qwen Code</span>
556
+ </button>
557
+ <button id="vendor-btn-junie" class="vendor-toggle-btn" data-vendor="junie">
558
+ <img src="/junie-avatar.svg" class="vendor-toggle-icon" alt="Junie">
559
+ <span class="vendor-toggle-label">Junie CLI</span>
560
+ </button>
539
561
  <button id="vendor-btn-kiro" class="vendor-toggle-btn" data-vendor="kiro">
540
562
  <img src="/kiro-avatar.svg" class="vendor-toggle-icon" alt="Kiro">
541
563
  <span class="vendor-toggle-label">Kiro CLI</span>
@@ -2338,7 +2360,7 @@
2338
2360
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
2339
2361
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
2340
2362
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
2341
- <script type="module" src="app.js"></script>
2363
+ <script type="module" src="app.js?v=20260826a"></script>
2342
2364
  <div id="pwa-install-modal" class="pwa-modal hidden">
2343
2365
  <div class="pwa-modal-backdrop"></div>
2344
2366
  <div class="pwa-modal-card">
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Junie">
2
+ <defs>
3
+ <linearGradient id="junie-gradient" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#8B5CF6"/>
5
+ <stop offset="1" stop-color="#F97316"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="64" height="64" rx="15" fill="url(#junie-gradient)"/>
9
+ <path d="M39 15v26c0 7.7-5.4 12-13.2 12-5.3 0-9.4-2.2-12-6.4l7.1-5c1.3 2 2.7 3 4.6 3 2.5 0 4-1.4 4-4.4V15H39Z" fill="#fff"/>
10
+ <circle cx="47.5" cy="17.5" r="4.5" fill="#fff" fill-opacity=".9"/>
11
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Kimi Code">
2
+ <rect width="64" height="64" rx="14" fill="#1769ff"/>
3
+ <path d="M18 14h9v15l13-15h11L36 31l16 19H40L27 34v16h-9z" fill="#fff"/>
4
+ </svg>
@@ -0,0 +1,69 @@
1
+ import { VENDOR_NAMES, VENDOR_ORDER, isExperimentalVendor } from './app-rendering.js';
2
+ import { effortLevelsFor, effortDisplayName } from './app-panels.js';
3
+
4
+ function optionValue(entry) {
5
+ if (typeof entry === "string") return entry;
6
+ return entry && (entry.value || entry.id) || "";
7
+ }
8
+
9
+ function optionLabel(entry) {
10
+ if (typeof entry === "string") return entry;
11
+ return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
12
+ }
13
+
14
+ export function buildAgentVendorSelect(installed, preferred) {
15
+ var select = document.createElement("select");
16
+ select.className = "wt-modal-input";
17
+ for (var i = 0; i < VENDOR_ORDER.length; i++) {
18
+ var vendor = VENDOR_ORDER[i];
19
+ if (installed.indexOf(vendor) === -1) continue;
20
+ var option = document.createElement("option");
21
+ option.value = vendor;
22
+ option.textContent = (isExperimentalVendor(vendor) ? "🧪 " : "") + (VENDOR_NAMES[vendor] || vendor);
23
+ if (isExperimentalVendor(vendor)) option.title = "Experimental integration; not yet validated through direct use testing";
24
+ select.appendChild(option);
25
+ }
26
+ if (preferred && installed.indexOf(preferred) !== -1) select.value = preferred;
27
+ return select;
28
+ }
29
+
30
+ export function fillAgentModels(select, vendor, options, preferred) {
31
+ var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
32
+ select.innerHTML = "";
33
+ var automatic = document.createElement("option");
34
+ automatic.value = "";
35
+ automatic.textContent = "Automatic";
36
+ select.appendChild(automatic);
37
+ for (var i = 0; i < models.length; i++) {
38
+ var option = document.createElement("option");
39
+ option.value = optionValue(models[i]);
40
+ option.textContent = optionLabel(models[i]);
41
+ select.appendChild(option);
42
+ }
43
+ select.value = preferred || "";
44
+ if (select.selectedIndex === -1) select.value = "";
45
+ }
46
+
47
+ export function buildAgentEffortSelect() {
48
+ var select = document.createElement("select");
49
+ select.className = "wt-modal-input";
50
+ return select;
51
+ }
52
+
53
+ export function fillAgentEffort(select, vendor, options, modelValue, preferred) {
54
+ var previous = preferred === undefined ? select.value : preferred;
55
+ var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
56
+ var levels = effortLevelsFor(vendor, models, modelValue);
57
+ select.innerHTML = "";
58
+ var automatic = document.createElement("option");
59
+ automatic.value = "";
60
+ automatic.textContent = "Default";
61
+ select.appendChild(automatic);
62
+ for (var i = 0; i < levels.length; i++) {
63
+ var option = document.createElement("option");
64
+ option.value = levels[i];
65
+ option.textContent = effortDisplayName(levels[i]);
66
+ select.appendChild(option);
67
+ }
68
+ select.value = levels.indexOf(previous) !== -1 ? previous : "";
69
+ }
@@ -12,7 +12,6 @@ import { saveToolState, resetToolState, restoreToolState } from './tools.js';
12
12
  import { getSessionUsage, setSessionUsage, getContextData, setContextData, updateContextPanel, updateUsagePanel } from './app-panels.js';
13
13
  import { onHistoryPrepended as onSessionSearchHistoryPrepended } from './session-search.js';
14
14
  import { getCachedSessions } from './sidebar-sessions.js';
15
- import { openPairDialog } from './split-pair-ui.js';
16
15
  import { showConfirm } from './app-misc.js';
17
16
 
18
17
  // --- Module-owned state ---
@@ -23,27 +22,6 @@ export function initHeader() {
23
22
  var headerRenameBtn = document.getElementById("header-rename-btn");
24
23
  var headerTitleEl = document.getElementById("header-title");
25
24
  var headerInfoBtn = document.getElementById("header-info-btn");
26
- var headerAddWorkerBtn = document.getElementById("header-add-worker-btn");
27
-
28
- // --- Add Worker: the current session becomes the Driver of a new pair ---
29
- if (headerAddWorkerBtn) {
30
- headerAddWorkerBtn.addEventListener("click", function () {
31
- var sessionId = store.get('activeSessionId');
32
- if (!sessionId || store.get('splitPanes') || store.get('dmMode')) return;
33
- openPairDialog({
34
- sessionId: sessionId,
35
- title: headerTitleEl ? headerTitleEl.textContent : "",
36
- vendor: store.get('currentVendor') || "claude",
37
- });
38
- });
39
- // A split shows the GROUP in the title bar; adding a worker there makes
40
- // no sense (the group already has two members), so hide the button.
41
- store.subscribe(function (state, prev) {
42
- if (state.splitPanes !== prev.splitPanes) {
43
- headerAddWorkerBtn.classList.toggle("hidden", !!state.splitPanes);
44
- }
45
- });
46
- }
47
25
  var headerFullAccessBtn = document.getElementById("header-full-access-btn");
48
26
 
49
27
  function updateFullAccessButton() {
@@ -93,6 +71,8 @@ export function initHeader() {
93
71
  input.type = "text";
94
72
  input.className = "header-rename-input";
95
73
  input.value = currentText;
74
+ var titleBar = headerTitleEl.closest(".title-bar-content");
75
+ if (titleBar) titleBar.classList.add("session-renaming");
96
76
  headerTitleEl.style.display = "none";
97
77
  headerRenameBtn.style.display = "none";
98
78
  headerTitleEl.parentNode.insertBefore(input, headerTitleEl.nextSibling);
@@ -117,6 +97,7 @@ export function initHeader() {
117
97
  }
118
98
  }
119
99
  input.remove();
100
+ if (titleBar) titleBar.classList.remove("session-renaming");
120
101
  headerTitleEl.style.display = "";
121
102
  headerRenameBtn.style.display = "";
122
103
  }
@@ -126,6 +107,7 @@ export function initHeader() {
126
107
  if (e.key === "Escape") {
127
108
  e.preventDefault();
128
109
  input.remove();
110
+ if (titleBar) titleBar.classList.remove("session-renaming");
129
111
  headerTitleEl.style.display = "";
130
112
  headerRenameBtn.style.display = "";
131
113
  }
@@ -21,6 +21,7 @@ import { handlePaletteSessionSwitch, setPaletteVersion } from './command-palette
21
21
  import { handleFindInSessionResults } from './session-search.js';
22
22
  import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
23
23
  import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
24
+ import { handleSessionActionMessage } from './session-actions.js';
24
25
  import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
25
26
  import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent } from './input.js';
26
27
  import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
@@ -52,6 +53,8 @@ import { setStatus } from './app-connection.js';
52
53
  import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
53
54
  import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
54
55
  import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
56
+ import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
57
+ import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
55
58
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
56
59
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
57
60
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
@@ -414,6 +417,15 @@ export function processMessage(msg) {
414
417
  break;
415
418
 
416
419
  case "model_info": {
420
+ // Installation state applies across vendors. Keep it even when the
421
+ // model payload itself is stale for the active session.
422
+ var _vendorState = {};
423
+ if (msg.availableVendors) _vendorState.availableVendors = msg.availableVendors;
424
+ if (msg.installedVendors) _vendorState.installedVendors = msg.installedVendors;
425
+ if (Object.keys(_vendorState).length > 0) {
426
+ store.set(_vendorState);
427
+ selectDefaultVendorForBlankSession();
428
+ }
417
429
  // Drop stale model_info from a vendor that doesn't match the active
418
430
  // session's vendor. On high-latency connections, the server's default-
419
431
  // adapter model_info can arrive after session_switched has already
@@ -421,12 +433,12 @@ export function processMessage(msg) {
421
433
  // currentModels with the wrong vendor's list and trigger app-panels
422
434
  // to request models for the "wrong" vendor, which feeds back into a
423
435
  // ping-pong loop of vendor flapping. See issue #336.
424
- var _curV = store.get('currentVendor');
425
- if (msg.vendor && _curV && msg.vendor !== _curV) break;
436
+ var _modelLoadUpdate = getModelInfoUpdate(msg);
437
+ if (!_modelLoadUpdate) break;
426
438
 
427
439
  var _modelVal = msg.model;
428
440
  if (_modelVal && typeof _modelVal === "object") _modelVal = _modelVal.value || _modelVal.displayName || "";
429
- var _miUpdate = { currentModels: msg.models || [] };
441
+ var _miUpdate = Object.assign({ currentModels: msg.models || [] }, _modelLoadUpdate);
430
442
  if (Object.prototype.hasOwnProperty.call(msg, "model")) {
431
443
  // Keep the user's existing selection only if it actually belongs to
432
444
  // this vendor's model list. Otherwise (e.g. switching to a Codex
@@ -435,10 +447,15 @@ export function processMessage(msg) {
435
447
  // model.
436
448
  var _curModel = store.get('currentModel');
437
449
  var _curInList = !!_curModel && (msg.models || []).some(function (m) {
438
- var v = typeof m === "string" ? m : (m && m.value);
439
- return v === _curModel;
450
+ return modelEntryMatches(m, _curModel);
451
+ });
452
+ var _incomingInList = !_modelVal || (msg.models || []).length === 0 || (msg.models || []).some(function (m) {
453
+ return modelEntryMatches(m, _modelVal);
440
454
  });
441
- if (store.get('vendorSelectionLocked') && _curInList) {
455
+ if (!_incomingInList) {
456
+ console.warn("[model-state] Ignoring model not listed for vendor " + (msg.vendor || store.get('currentVendor')) + ": " + _modelVal);
457
+ _miUpdate.currentModel = _curInList ? _curModel : modelEntryValue((msg.models || [])[0]);
458
+ } else if (store.get('vendorSelectionLocked') && _curInList) {
442
459
  // Keep the user's existing (valid) selection; only update models list
443
460
  } else {
444
461
  _miUpdate.currentModel = _modelVal || "";
@@ -447,17 +464,31 @@ export function processMessage(msg) {
447
464
  _miUpdate.currentModel = store.get('currentModel');
448
465
  }
449
466
  if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
450
- if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
451
- if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
452
467
  if (msg.capabilities) _miUpdate.vendorCapabilities = msg.capabilities;
453
468
  store.set(_miUpdate);
454
469
  updateSettingsModels(_modelVal, msg.models || []);
455
470
  break;
456
471
  }
457
472
 
473
+ case "model_selection_result":
474
+ handleModelSelectionResult(msg);
475
+ break;
476
+
458
477
  case "config_state": {
478
+ if ((msg.sessionId != null && msg.sessionId !== store.get('activeSessionId')) || (msg.vendor && msg.vendor !== store.get('currentVendor'))) break;
459
479
  var _cs = {};
460
- if (msg.model) _cs.currentModel = msg.model;
480
+ if (msg.model) {
481
+ var _knownModels = store.get('currentModels') || [];
482
+ var _modelKnown = _knownModels.length === 0 || _knownModels.some(function(model) { return modelEntryMatches(model, msg.model); });
483
+ if (_modelKnown) {
484
+ _cs.currentModel = msg.model;
485
+ } else {
486
+ console.warn("[model-state] Ignoring model not listed for current vendor: " + msg.model);
487
+ var _existingModel = store.get('currentModel');
488
+ var _existingKnown = _knownModels.some(function(model) { return modelEntryMatches(model, _existingModel); });
489
+ _cs.currentModel = _existingKnown ? _existingModel : modelEntryValue(_knownModels[0]);
490
+ }
491
+ }
461
492
  if (msg.mode) _cs.currentMode = msg.mode;
462
493
  if (msg.effort) _cs.currentEffort = msg.effort;
463
494
  if (msg.betas) _cs.currentBetas = msg.betas;
@@ -589,6 +620,13 @@ export function processMessage(msg) {
589
620
  if (createdPairGroup) openGroup(createdPairGroup);
590
621
  break;
591
622
 
623
+ case "session_handoff_result":
624
+ case "handoff_session_options":
625
+ case "handoff_context":
626
+ case "handoff_created":
627
+ handleSessionActionMessage(msg);
628
+ break;
629
+
592
630
  case "worker_proposal":
593
631
  renderWorkerProposal(msg);
594
632
  break;
@@ -661,7 +699,7 @@ export function processMessage(msg) {
661
699
  var _effectiveTerminalId = (typeof msg.runtimeTerminalId === "number")
662
700
  ? msg.runtimeTerminalId
663
701
  : (typeof msg.terminalId === "number" ? msg.terminalId : null);
664
- store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || store.get('currentModel'), currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
702
+ store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
665
703
  // TUI sessions swap the chat UI for an embedded xterm running
666
704
  // `claude` inside a real PTY. Mount or tear down before the rest of
667
705
  // the chat-side bookkeeping runs so we don't waste work on hidden DOM.
@@ -706,8 +744,9 @@ export function processMessage(msg) {
706
744
  }
707
745
  }
708
746
  if (!msg.hasHistory && !msg.vendor) {
709
- // Preserve explicit pre-message vendor choice on brand-new sessions.
747
+ selectDefaultVendorForBlankSession();
710
748
  }
749
+ if (!msg.model) requestVendorModels(store.get('currentVendor') || "claude", true);
711
750
  // Vendor toggle visibility + active-vendor indicator next to the
712
751
  // model chip.
713
752
  // - Session has an explicit vendor: hide the toggle (it's
@@ -1108,6 +1147,10 @@ export function processMessage(msg) {
1108
1147
  updateSubagentTaskStatus(msg.parentToolId, msg.patch);
1109
1148
  break;
1110
1149
 
1150
+ case "active_background_tasks":
1151
+ store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [] });
1152
+ break;
1153
+
1111
1154
  case "result":
1112
1155
  // Result marks turn end. Drop pre-thinking even if no delta/tool
1113
1156
  // event ever arrived (e.g. tool-only turn whose progress signals