clay-server 3.4.0-beta.9 → 3.4.0

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 (66) hide show
  1. package/lib/daemon.js +58 -58
  2. package/lib/project-connection.js +19 -6
  3. package/lib/project-models.js +220 -0
  4. package/lib/project-session-handoff.js +162 -0
  5. package/lib/project-session-pair.js +14 -7
  6. package/lib/project-session-spawn.js +1 -1
  7. package/lib/project-sessions.js +13 -45
  8. package/lib/project-worker-proposal.js +51 -14
  9. package/lib/project.js +33 -77
  10. package/lib/public/app.js +11 -0
  11. package/lib/public/copilot-avatar.svg +5 -0
  12. package/lib/public/css/filebrowser.css +6 -1
  13. package/lib/public/css/input.css +56 -0
  14. package/lib/public/css/mates.css +6 -0
  15. package/lib/public/css/menus.css +35 -56
  16. package/lib/public/css/pane.css +18 -3
  17. package/lib/public/css/session-actions.css +98 -0
  18. package/lib/public/grok-avatar.svg +4 -0
  19. package/lib/public/index.html +26 -9
  20. package/lib/public/junie-avatar.svg +11 -0
  21. package/lib/public/kimi-avatar.svg +4 -0
  22. package/lib/public/modules/agent-config-selects.js +69 -0
  23. package/lib/public/modules/app-connection.js +6 -0
  24. package/lib/public/modules/app-header.js +0 -22
  25. package/lib/public/modules/app-messages.js +48 -9
  26. package/lib/public/modules/app-panels.js +33 -82
  27. package/lib/public/modules/app-projects.js +2 -0
  28. package/lib/public/modules/app-rendering.js +16 -1
  29. package/lib/public/modules/background-tasks-ui.js +55 -0
  30. package/lib/public/modules/filebrowser-tabs.js +65 -0
  31. package/lib/public/modules/filebrowser.js +23 -3
  32. package/lib/public/modules/mate-sidebar.js +10 -2
  33. package/lib/public/modules/model-picker.js +263 -0
  34. package/lib/public/modules/pane-bridge.js +6 -0
  35. package/lib/public/modules/session-actions.js +294 -0
  36. package/lib/public/modules/sidebar-mobile.js +2 -1
  37. package/lib/public/modules/sidebar-sessions.js +17 -1
  38. package/lib/public/modules/split-pair-ui.js +16 -77
  39. package/lib/public/modules/split-view.js +11 -1
  40. package/lib/public/modules/tools.js +5 -0
  41. package/lib/public/modules/vendor-priority.js +6 -1
  42. package/lib/public/modules/worker-proposal.js +6 -1
  43. package/lib/public/qwen-avatar.svg +4 -0
  44. package/lib/public/style.css +1 -0
  45. package/lib/sdk-bridge.js +57 -34
  46. package/lib/sdk-message-processor.js +26 -4
  47. package/lib/session-handoff-context.js +110 -0
  48. package/lib/session-notes-mcp-server.js +1 -0
  49. package/lib/sessions.js +4 -0
  50. package/lib/ws-schema.js +9 -1
  51. package/lib/yoke/acp-agent-profiles.js +64 -0
  52. package/lib/yoke/adapters/acp.js +2 -1
  53. package/lib/yoke/adapters/claude.js +34 -0
  54. package/lib/yoke/adapters/codex.js +38 -3
  55. package/lib/yoke/adapters/copilot.js +7 -0
  56. package/lib/yoke/adapters/grok.js +7 -0
  57. package/lib/yoke/adapters/junie.js +7 -0
  58. package/lib/yoke/adapters/kimi.js +7 -0
  59. package/lib/yoke/adapters/kiro.js +4 -3
  60. package/lib/yoke/adapters/qwen.js +7 -0
  61. package/lib/yoke/codex-background-tasks.js +84 -0
  62. package/lib/yoke/index.js +43 -14
  63. package/lib/yoke/instructions.js +3 -0
  64. package/lib/yoke/interface.js +21 -0
  65. package/lib/yoke/vendor-registry.js +55 -0
  66. package/package.json +1 -1
@@ -43,62 +43,6 @@
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: inline-flex;
48
- align-items: center;
49
- justify-content: center;
50
- gap: 7px;
51
- width: auto;
52
- height: 28px;
53
- border: 1px solid color-mix(in srgb, var(--accent2) 28%, var(--border));
54
- background: var(--accent2-8);
55
- color: var(--accent2);
56
- cursor: pointer;
57
- border-radius: 8px;
58
- flex-shrink: 0;
59
- padding: 0 9px 0 7px;
60
- margin-left: 0;
61
- font-family: inherit;
62
- font-size: 11px;
63
- font-weight: 700;
64
- line-height: 1;
65
- white-space: nowrap;
66
- transition: color 0.15s, background 0.15s, border-color 0.15s;
67
- }
68
- #header-add-worker-btn:hover {
69
- color: var(--accent2-hover);
70
- background: var(--accent2-15);
71
- border-color: color-mix(in srgb, var(--accent2) 48%, var(--border));
72
- }
73
- .header-worker-icon {
74
- position: relative;
75
- display: inline-flex;
76
- align-items: center;
77
- justify-content: center;
78
- width: 16px;
79
- height: 16px;
80
- flex-shrink: 0;
81
- }
82
- .header-worker-icon > .lucide { width: 15px; height: 15px; }
83
- .header-worker-add-badge {
84
- position: absolute;
85
- right: -4px;
86
- bottom: -3px;
87
- display: inline-flex;
88
- align-items: center;
89
- justify-content: center;
90
- width: 9px;
91
- height: 9px;
92
- border-radius: 50%;
93
- background: var(--accent2);
94
- color: var(--bg);
95
- box-shadow: 0 0 0 2px var(--bg);
96
- }
97
- .header-worker-add-badge .lucide { width: 7px; height: 7px; stroke-width: 3; }
98
- .header-worker-label { letter-spacing: 0.01em; }
99
- #header-add-worker-btn.hidden { display: none; }
100
- body.dm-mode #header-add-worker-btn { display: none; }
101
-
102
46
  /* "Close terminal" button for a live TUI session. Matches the session-control
103
47
  chrome; reds out on hover since it stops the running agent while preserving
104
48
  the resumable session. */
@@ -702,6 +646,41 @@ body.dm-mode #header-add-worker-btn { display: none; }
702
646
  color: var(--text-dimmer);
703
647
  font-style: italic;
704
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; }
705
684
 
706
685
  /* Segmented control for effort */
707
686
  .config-segmented {
@@ -23,6 +23,12 @@ body.pane-mode #sticky-notes-container {
23
23
  display: none !important;
24
24
  }
25
25
 
26
+ body.pane-mode #file-viewer { display: none !important; }
27
+
28
+ body.pane-mode #connect-overlay { background: var(--bg); }
29
+ body.pane-mode #connect-overlay #ascii-logo-canvas { display: none; }
30
+ body.pane-mode #connect-overlay-msg { color: var(--text-dimmer); }
31
+
26
32
  body.pane-mode .notif-banner-container {
27
33
  display: none !important;
28
34
  }
@@ -100,7 +106,8 @@ body.pane-mode #input-area {
100
106
  }
101
107
 
102
108
  .split-pane-title {
103
- flex: 1;
109
+ flex: 0 1 auto;
110
+ max-width: 46%;
104
111
  min-width: 0;
105
112
  overflow: hidden;
106
113
  text-overflow: ellipsis;
@@ -202,15 +209,18 @@ button.split-pair-role:hover { filter: brightness(1.25); }
202
209
  max-width: var(--content-width);
203
210
  margin: 0 auto;
204
211
  padding: 4px 10px;
205
- border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
206
- border-bottom: 0;
207
212
  border-radius: 8px 8px 0 0;
208
213
  background: color-mix(in srgb, var(--accent) 8%, var(--bg));
209
214
  color: var(--text-secondary);
210
215
  font-size: 11px;
211
216
  line-height: 1.4;
217
+ transition: box-shadow 0.2s;
212
218
  }
213
219
  .pane-delegation-notice.visible { display: flex; }
220
+ .pane-delegation-notice.visible:has(+ #input-wrapper #input-row:focus-within) {
221
+ box-shadow: 0 0 0 1px var(--border);
222
+ clip-path: inset(-1px -1px 0 -1px);
223
+ }
214
224
  .pane-delegation-notice.visible + #input-wrapper #input-row {
215
225
  border-radius: 0 0 8px 8px;
216
226
  }
@@ -283,12 +293,17 @@ button.split-pair-role:hover { filter: brightness(1.25); }
283
293
  color: var(--text-dimmer);
284
294
  cursor: pointer;
285
295
  flex-shrink: 0;
296
+ margin-left: auto;
286
297
  }
287
298
 
288
299
  .split-pane-context.has-data {
289
300
  display: inline-flex;
290
301
  }
291
302
 
303
+ .split-pane-context:not(.has-data) + .split-pane-close {
304
+ margin-left: auto;
305
+ }
306
+
292
307
  .split-pane-context:hover {
293
308
  background: var(--bg-alt);
294
309
  color: var(--text);
@@ -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; }
@@ -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=20260824f">
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 */
@@ -388,12 +388,8 @@
388
388
  <div id="ralph-sticky" class="hidden"></div>
389
389
  <div id="debate-sticky" class="hidden"></div>
390
390
  <div class="status">
391
- <button id="header-add-worker-btn" type="button" aria-label="Add AI Worker" title="Add an AI Worker — this session becomes the Driver of a pair">
392
- <span class="header-worker-icon" aria-hidden="true">
393
- <i data-lucide="bot"></i>
394
- <span class="header-worker-add-badge"><i data-lucide="plus"></i></span>
395
- </span>
396
- <span class="header-worker-label">Add Worker</span>
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>
397
393
  </button>
398
394
  <button type="button" id="header-tui-font-btn" class="header-tui-font-btn hidden" title="Terminal font" aria-label="Terminal font">
399
395
  <i data-lucide="type"></i>
@@ -407,7 +403,7 @@
407
403
  <div id="app">
408
404
  <div id="connect-overlay">
409
405
  <canvas id="ascii-logo-canvas"></canvas>
410
- <p id="connect-overlay-msg">Reconnecting to server…</p>
406
+ <p id="connect-overlay-msg">Connecting…</p>
411
407
  </div>
412
408
  <div id="sticky-notes-container" class="hidden"></div>
413
409
  <div id="debate-info-float" class="hidden"></div>
@@ -542,6 +538,26 @@
542
538
  <img src="/opencode-avatar.svg" class="vendor-toggle-icon" alt="OpenCode">
543
539
  <span class="vendor-toggle-label">OpenCode</span>
544
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>
545
561
  <button id="vendor-btn-kiro" class="vendor-toggle-btn" data-vendor="kiro">
546
562
  <img src="/kiro-avatar.svg" class="vendor-toggle-icon" alt="Kiro">
547
563
  <span class="vendor-toggle-label">Kiro CLI</span>
@@ -664,6 +680,7 @@
664
680
 
665
681
  <div id="file-viewer" class="hidden">
666
682
  <div class="file-viewer-header">
683
+ <div class="file-viewer-tabs" id="file-viewer-tabs" role="tablist"></div>
667
684
  <span class="file-viewer-path" id="file-viewer-path"></span>
668
685
  <span class="file-viewer-live-status hidden" id="file-viewer-live-status" aria-live="polite">
669
686
  <span class="file-viewer-live-dot"></span>
@@ -2344,7 +2361,7 @@
2344
2361
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
2345
2362
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
2346
2363
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
2347
- <script type="module" src="app.js?v=20260824g"></script>
2364
+ <script type="module" src="app.js?v=20260826a"></script>
2348
2365
  <div id="pwa-install-modal" class="pwa-modal hidden">
2349
2366
  <div class="pwa-modal-backdrop"></div>
2350
2367
  <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
+ }
@@ -17,6 +17,7 @@ var reconnectTimer = null;
17
17
  var reconnectDelay = 1000;
18
18
  var connectTimeoutId = null;
19
19
  var connectOverlay = null;
20
+ var hasConnectedOnce = false;
20
21
 
21
22
  export function initConnection() {
22
23
  connectOverlay = document.getElementById("connect-overlay");
@@ -39,6 +40,7 @@ export function initConnection() {
39
40
  if (state.connected !== prev.connected) {
40
41
  var sendBtn = getSendBtn();
41
42
  if (state.connected) {
43
+ hasConnectedOnce = true;
42
44
  if (sendBtn) sendBtn.disabled = false;
43
45
  if (connectOverlay) connectOverlay.classList.add("hidden");
44
46
  var updPill = document.getElementById("update-pill-wrap");
@@ -46,6 +48,10 @@ export function initConnection() {
46
48
  stopLogoAnimation();
47
49
  } else {
48
50
  if (sendBtn) sendBtn.disabled = true;
51
+ if (hasConnectedOnce && connectOverlay) {
52
+ var overlayMessage = document.getElementById("connect-overlay-msg");
53
+ if (overlayMessage) overlayMessage.textContent = "Reconnecting to server…";
54
+ }
49
55
  if (connectOverlay) connectOverlay.classList.remove("hidden");
50
56
  startLogoAnimation();
51
57
  }
@@ -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() {
@@ -21,12 +21,14 @@ 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';
27
28
  import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
28
29
  import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch, presentMarkdownEdit } from './filebrowser.js';
29
30
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
31
+ import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
30
32
  import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
31
33
  import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
32
34
  import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
@@ -53,6 +55,7 @@ import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWh
53
55
  import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
54
56
  import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
55
57
  import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
58
+ import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
56
59
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
57
60
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
58
61
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
@@ -431,12 +434,12 @@ export function processMessage(msg) {
431
434
  // currentModels with the wrong vendor's list and trigger app-panels
432
435
  // to request models for the "wrong" vendor, which feeds back into a
433
436
  // ping-pong loop of vendor flapping. See issue #336.
434
- var _curV = store.get('currentVendor');
435
- if (msg.vendor && _curV && msg.vendor !== _curV) break;
437
+ var _modelLoadUpdate = getModelInfoUpdate(msg);
438
+ if (!_modelLoadUpdate) break;
436
439
 
437
440
  var _modelVal = msg.model;
438
441
  if (_modelVal && typeof _modelVal === "object") _modelVal = _modelVal.value || _modelVal.displayName || "";
439
- var _miUpdate = { currentModels: msg.models || [] };
442
+ var _miUpdate = Object.assign({ currentModels: msg.models || [] }, _modelLoadUpdate);
440
443
  if (Object.prototype.hasOwnProperty.call(msg, "model")) {
441
444
  // Keep the user's existing selection only if it actually belongs to
442
445
  // this vendor's model list. Otherwise (e.g. switching to a Codex
@@ -445,10 +448,15 @@ export function processMessage(msg) {
445
448
  // model.
446
449
  var _curModel = store.get('currentModel');
447
450
  var _curInList = !!_curModel && (msg.models || []).some(function (m) {
448
- var v = typeof m === "string" ? m : (m && m.value);
449
- return v === _curModel;
451
+ return modelEntryMatches(m, _curModel);
450
452
  });
451
- if (store.get('vendorSelectionLocked') && _curInList) {
453
+ var _incomingInList = !_modelVal || (msg.models || []).length === 0 || (msg.models || []).some(function (m) {
454
+ return modelEntryMatches(m, _modelVal);
455
+ });
456
+ if (!_incomingInList) {
457
+ console.warn("[model-state] Ignoring model not listed for vendor " + (msg.vendor || store.get('currentVendor')) + ": " + _modelVal);
458
+ _miUpdate.currentModel = _curInList ? _curModel : modelEntryValue((msg.models || [])[0]);
459
+ } else if (store.get('vendorSelectionLocked') && _curInList) {
452
460
  // Keep the user's existing (valid) selection; only update models list
453
461
  } else {
454
462
  _miUpdate.currentModel = _modelVal || "";
@@ -463,9 +471,25 @@ export function processMessage(msg) {
463
471
  break;
464
472
  }
465
473
 
474
+ case "model_selection_result":
475
+ handleModelSelectionResult(msg);
476
+ break;
477
+
466
478
  case "config_state": {
479
+ if ((msg.sessionId != null && msg.sessionId !== store.get('activeSessionId')) || (msg.vendor && msg.vendor !== store.get('currentVendor'))) break;
467
480
  var _cs = {};
468
- if (msg.model) _cs.currentModel = msg.model;
481
+ if (msg.model) {
482
+ var _knownModels = store.get('currentModels') || [];
483
+ var _modelKnown = _knownModels.length === 0 || _knownModels.some(function(model) { return modelEntryMatches(model, msg.model); });
484
+ if (_modelKnown) {
485
+ _cs.currentModel = msg.model;
486
+ } else {
487
+ console.warn("[model-state] Ignoring model not listed for current vendor: " + msg.model);
488
+ var _existingModel = store.get('currentModel');
489
+ var _existingKnown = _knownModels.some(function(model) { return modelEntryMatches(model, _existingModel); });
490
+ _cs.currentModel = _existingKnown ? _existingModel : modelEntryValue(_knownModels[0]);
491
+ }
492
+ }
469
493
  if (msg.mode) _cs.currentMode = msg.mode;
470
494
  if (msg.effort) _cs.currentEffort = msg.effort;
471
495
  if (msg.betas) _cs.currentBetas = msg.betas;
@@ -597,6 +621,13 @@ export function processMessage(msg) {
597
621
  if (createdPairGroup) openGroup(createdPairGroup);
598
622
  break;
599
623
 
624
+ case "session_handoff_result":
625
+ case "handoff_session_options":
626
+ case "handoff_context":
627
+ case "handoff_created":
628
+ handleSessionActionMessage(msg);
629
+ break;
630
+
600
631
  case "worker_proposal":
601
632
  renderWorkerProposal(msg);
602
633
  break;
@@ -669,7 +700,7 @@ export function processMessage(msg) {
669
700
  var _effectiveTerminalId = (typeof msg.runtimeTerminalId === "number")
670
701
  ? msg.runtimeTerminalId
671
702
  : (typeof msg.terminalId === "number" ? msg.terminalId : null);
672
- 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, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
703
+ 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') });
673
704
  // TUI sessions swap the chat UI for an embedded xterm running
674
705
  // `claude` inside a real PTY. Mount or tear down before the rest of
675
706
  // the chat-side bookkeeping runs so we don't waste work on hidden DOM.
@@ -716,6 +747,7 @@ export function processMessage(msg) {
716
747
  if (!msg.hasHistory && !msg.vendor) {
717
748
  selectDefaultVendorForBlankSession();
718
749
  }
750
+ if (!msg.model) requestVendorModels(store.get('currentVendor') || "claude", true);
719
751
  // Vendor toggle visibility + active-vendor indicator next to the
720
752
  // model chip.
721
753
  // - Session has an explicit vendor: hide the toggle (it's
@@ -1116,6 +1148,10 @@ export function processMessage(msg) {
1116
1148
  updateSubagentTaskStatus(msg.parentToolId, msg.patch);
1117
1149
  break;
1118
1150
 
1151
+ case "active_background_tasks":
1152
+ store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [] });
1153
+ break;
1154
+
1119
1155
  case "result":
1120
1156
  // Result marks turn end. Drop pre-thinking even if no delta/tool
1121
1157
  // event ever arrived (e.g. tool-only turn whose progress signals
@@ -1365,7 +1401,10 @@ export function processMessage(msg) {
1365
1401
  break;
1366
1402
 
1367
1403
  case "markdown_edit_present":
1368
- if (!store.get('replayingHistory') && !store.get('dmMode')) presentMarkdownEdit(msg);
1404
+ if (!store.get('replayingHistory') && !store.get('dmMode')) {
1405
+ if (store.get('paneMode')) forwardPaneMarkdownPresentation(msg);
1406
+ else if (!store.get('splitPanes')) presentMarkdownEdit(msg);
1407
+ }
1369
1408
  break;
1370
1409
 
1371
1410
  case "fs_dir_changed":