clay-server 3.3.2-beta.3 → 3.4.0-beta.10

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 (77) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +1 -1
  3. package/lib/project-connection.js +20 -5
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-file-watch.js +77 -16
  6. package/lib/project-mate-interaction.js +21 -1
  7. package/lib/project-memory.js +3 -0
  8. package/lib/project-shell-command.js +160 -0
  9. package/lib/project-user-message.js +10 -0
  10. package/lib/project.js +19 -4
  11. package/lib/public/antigravity-avatar.png +0 -0
  12. package/lib/public/app.js +28 -0
  13. package/lib/public/copilot-avatar.svg +5 -0
  14. package/lib/public/css/command-palette.css +22 -2
  15. package/lib/public/css/icon-strip.css +29 -13
  16. package/lib/public/css/input.css +79 -0
  17. package/lib/public/css/mates.css +7 -0
  18. package/lib/public/css/menus.css +48 -14
  19. package/lib/public/css/messages.css +9 -0
  20. package/lib/public/css/pane.css +4 -0
  21. package/lib/public/css/pwa-mobile.css +17 -0
  22. package/lib/public/css/title-bar.css +19 -0
  23. package/lib/public/grok-avatar.svg +4 -0
  24. package/lib/public/index.html +43 -4
  25. package/lib/public/junie-avatar.svg +11 -0
  26. package/lib/public/kimi-avatar.svg +4 -0
  27. package/lib/public/modules/app-header.js +4 -0
  28. package/lib/public/modules/app-messages.js +28 -5
  29. package/lib/public/modules/app-panels.js +37 -5
  30. package/lib/public/modules/app-projects.js +3 -1
  31. package/lib/public/modules/app-rendering.js +22 -1
  32. package/lib/public/modules/input.js +58 -28
  33. package/lib/public/modules/mate-sidebar.js +17 -3
  34. package/lib/public/modules/notifications.js +7 -1
  35. package/lib/public/modules/pane-bridge.js +11 -0
  36. package/lib/public/modules/pane-links.js +23 -0
  37. package/lib/public/modules/project-switcher.js +7 -6
  38. package/lib/public/modules/shell-command.js +148 -0
  39. package/lib/public/modules/sidebar-mates.js +7 -1
  40. package/lib/public/modules/sidebar-mobile.js +2 -1
  41. package/lib/public/modules/sidebar-projects.js +34 -43
  42. package/lib/public/modules/sidebar-sessions.js +6 -6
  43. package/lib/public/modules/split-pair-ui.js +3 -2
  44. package/lib/public/modules/tools.js +12 -1
  45. package/lib/public/modules/vendor-priority.js +14 -0
  46. package/lib/public/modules/vendor-selection.js +20 -0
  47. package/lib/public/modules/worktree-location.js +17 -0
  48. package/lib/public/opencode-avatar.svg +4 -0
  49. package/lib/public/qwen-avatar.svg +4 -0
  50. package/lib/public/style.css +3 -2
  51. package/lib/sdk-bridge.js +59 -19
  52. package/lib/sdk-message-processor.js +10 -1
  53. package/lib/session-notes-mcp-server.js +7 -1
  54. package/lib/worktree.js +9 -4
  55. package/lib/ws-schema.js +3 -0
  56. package/lib/yoke/acp-agent-profiles.js +238 -0
  57. package/lib/yoke/acp-driver-runtime.js +50 -0
  58. package/lib/yoke/acp-event-normalizer.js +179 -0
  59. package/lib/yoke/acp-process-manager.js +264 -0
  60. package/lib/yoke/acp-query-handle.js +487 -0
  61. package/lib/yoke/adapters/acp.js +317 -0
  62. package/lib/yoke/adapters/antigravity.js +417 -0
  63. package/lib/yoke/adapters/codex.js +98 -3
  64. package/lib/yoke/adapters/copilot.js +7 -0
  65. package/lib/yoke/adapters/grok.js +7 -0
  66. package/lib/yoke/adapters/junie.js +7 -0
  67. package/lib/yoke/adapters/kimi.js +7 -0
  68. package/lib/yoke/adapters/kiro.js +4 -4
  69. package/lib/yoke/adapters/opencode.js +7 -0
  70. package/lib/yoke/adapters/qwen.js +7 -0
  71. package/lib/yoke/codex-app-server.js +25 -8
  72. package/lib/yoke/index.js +81 -13
  73. package/lib/yoke/instructions.js +3 -0
  74. package/lib/yoke/interface.js +2 -0
  75. package/lib/yoke/kiro-acp-server.js +30 -276
  76. package/lib/yoke/vendor-registry.js +77 -0
  77. package/package.json +1 -1
@@ -0,0 +1,148 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { iconHtml, refreshIcons } from './icons.js';
4
+ import { showToast } from './utils.js';
5
+
6
+ var defaultPlaceholder = "";
7
+
8
+ function getInput() {
9
+ return document.getElementById("input");
10
+ }
11
+
12
+ function appendToMessages(element) {
13
+ var messages = document.getElementById("messages");
14
+ if (!messages) return;
15
+ messages.appendChild(element);
16
+ requestAnimationFrame(function () { messages.scrollTop = messages.scrollHeight; });
17
+ }
18
+
19
+ function setMode(active) {
20
+ var input = getInput();
21
+ var button = document.getElementById("shell-command-btn");
22
+ var row = document.getElementById("input-row");
23
+ store.set({ shellCommandMode: active });
24
+ if (button) {
25
+ button.classList.toggle("active", active);
26
+ button.setAttribute("aria-pressed", active ? "true" : "false");
27
+ }
28
+ if (row) row.classList.toggle("shell-command-mode", active);
29
+ if (input) {
30
+ if (!defaultPlaceholder) defaultPlaceholder = input.placeholder;
31
+ input.placeholder = active ? "Run a shell command in this project…" : defaultPlaceholder;
32
+ input.focus();
33
+ }
34
+ }
35
+
36
+ export function isShellCommandMode() {
37
+ return !!store.get("shellCommandMode");
38
+ }
39
+
40
+ export function toggleShellCommandMode() {
41
+ if (store.get("shellCommandRunning")) return;
42
+ var target = store.get("dmTargetUser");
43
+ if (store.get("dmMode") && target && !target.isMate) {
44
+ showToast("Shell commands are available in agent sessions, not user DMs.", "error");
45
+ return;
46
+ }
47
+ setMode(!isShellCommandMode());
48
+ }
49
+
50
+ function renderPendingCommand(requestId, command) {
51
+ var card = document.createElement("div");
52
+ card.className = "shell-command-card running";
53
+ card.dataset.requestId = requestId;
54
+ card.innerHTML =
55
+ '<div class="shell-command-header">' +
56
+ '<span class="shell-command-icon">' + iconHtml("square-terminal") + '</span>' +
57
+ '<code></code><span class="shell-command-status">Running…</span>' +
58
+ '</div>' +
59
+ '<pre class="shell-command-output">Waiting for output…</pre>';
60
+ card.querySelector("code").textContent = "$ " + command;
61
+ appendToMessages(card);
62
+ refreshIcons();
63
+ }
64
+
65
+ export function submitShellCommand(command) {
66
+ command = String(command || "").trim();
67
+ if (!command || store.get("shellCommandRunning")) return false;
68
+ var ws = getWs();
69
+ if (!ws || ws.readyState !== 1) {
70
+ showToast("Not connected — command not run.", "error");
71
+ return false;
72
+ }
73
+
74
+ var requestId = "shell_" + Date.now() + "_" + Math.random().toString(36).slice(2, 9);
75
+ store.set({ shellCommandRunning: true, pendingShellCommandId: requestId });
76
+ var input = getInput();
77
+ if (input) {
78
+ input.disabled = true;
79
+ input.placeholder = "Running command…";
80
+ }
81
+ renderPendingCommand(requestId, command);
82
+ ws.send(JSON.stringify({ type: "shell_command", requestId: requestId, command: command }));
83
+ return true;
84
+ }
85
+
86
+ export function handleShellCommandResult(msg) {
87
+ var cards = document.querySelectorAll(".shell-command-card[data-request-id]");
88
+ var card = null;
89
+ for (var i = 0; i < cards.length; i++) {
90
+ if (cards[i].dataset.requestId === (msg.requestId || "")) {
91
+ card = cards[i];
92
+ break;
93
+ }
94
+ }
95
+ if (card) {
96
+ var status = card.querySelector(".shell-command-status");
97
+ var output = card.querySelector(".shell-command-output");
98
+ card.classList.remove("running");
99
+ if (msg.error) {
100
+ card.classList.add("error");
101
+ if (status) status.textContent = "Failed";
102
+ if (output) output.textContent = msg.error;
103
+ } else {
104
+ card.classList.toggle("error", msg.exitCode !== 0);
105
+ if (status) status.textContent = msg.timedOut ? "Timed out" : "Exit " + (msg.exitCode == null ? "—" : msg.exitCode);
106
+ if (output) output.textContent = msg.output || "(no output)";
107
+ }
108
+ }
109
+
110
+ store.set({ shellCommandRunning: false, pendingShellCommandId: null });
111
+ var input = getInput();
112
+ if (input) input.disabled = false;
113
+ if (msg.error) {
114
+ setMode(true);
115
+ } else {
116
+ setMode(false);
117
+ }
118
+ var messages = document.getElementById("messages");
119
+ if (messages) requestAnimationFrame(function () { messages.scrollTop = messages.scrollHeight; });
120
+ }
121
+
122
+ export function initShellCommand() {
123
+ var button = document.getElementById("shell-command-btn");
124
+ var mobileButton = document.getElementById("input-more-shell");
125
+ if (button) button.addEventListener("click", toggleShellCommandMode);
126
+ if (mobileButton) {
127
+ mobileButton.addEventListener("click", function () {
128
+ var sheet = document.getElementById("input-more-sheet");
129
+ if (sheet) {
130
+ sheet.classList.remove("open");
131
+ setTimeout(function () { sheet.classList.add("hidden"); }, 250);
132
+ }
133
+ toggleShellCommandMode();
134
+ });
135
+ }
136
+ store.subscribe(function (state, previous) {
137
+ if (previous.connected && !state.connected && state.shellCommandRunning) {
138
+ resetShellCommand();
139
+ }
140
+ });
141
+ }
142
+
143
+ export function resetShellCommand() {
144
+ store.set({ shellCommandMode: false, shellCommandRunning: false, pendingShellCommandId: null });
145
+ var input = getInput();
146
+ if (input) input.disabled = false;
147
+ setMode(false);
148
+ }
@@ -457,7 +457,13 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
457
457
  // Tooltip
458
458
  var displayName = mp.displayName || mate.name || "New Mate";
459
459
  var mateVendor = mate.vendor || "claude";
460
- var vendorLabels = { claude: "Claude Code", codex: "OpenAI Codex", kiro: "Kiro CLI" };
460
+ var vendorLabels = {
461
+ claude: "Claude Code",
462
+ codex: "OpenAI Codex",
463
+ antigravity: "Antigravity CLI",
464
+ opencode: "OpenCode",
465
+ kiro: "Kiro CLI",
466
+ };
461
467
  el.addEventListener("mouseenter", function () {
462
468
  var html = '<div style="font-weight:600">' + escapeHtml(displayName);
463
469
  if (mate.primary) {
@@ -15,7 +15,7 @@ import {
15
15
  resolveDefaultVendor,
16
16
  startNewSession
17
17
  } from './sidebar-sessions.js';
18
- import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES } from './app-rendering.js';
18
+ import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperimentalVendor } from './app-rendering.js';
19
19
  import {
20
20
  getCachedProjectList,
21
21
  getCachedCurrentSlug,
@@ -754,6 +754,7 @@ function renderMobileSessionsInto(container) {
754
754
  if (vendor === mobileDefaultVendor) vBtn.classList.add("active");
755
755
  vBtn.innerHTML = '<img src="' + VENDOR_AVATARS[vendor] + '" class="mobile-session-new-icon" alt="">' +
756
756
  '<span>' + name + '</span>' +
757
+ (isExperimentalVendor(vendor) ? '<span class="vendor-experimental-badge" aria-label="Experimental integration" title="Experimental integration; not yet validated through direct use testing">🧪</span>' : '') +
757
758
  (isInstalled ? "" : '<span class="mobile-vendor-note">Not installed</span>');
758
759
  vBtn.addEventListener("click", function () {
759
760
  if (!isInstalled) {
@@ -13,6 +13,7 @@ import { showIconTooltip, hideIconTooltip, closeUserCtxMenu, getCurrentDmUserId
13
13
  import { switchProject, openAddProjectModal, getCachedProjects } from './app-projects.js';
14
14
  import { showHomeHub } from './app-home-hub.js';
15
15
  import { getPendingTuiAttention } from './app-notifications.js';
16
+ import { isExternalWorktree, externalWorktreeTooltip, appendExternalWorktreeBadge } from './worktree-location.js';
16
17
 
17
18
  // --- Project state ---
18
19
  var cachedProjectList = [];
@@ -489,7 +490,7 @@ export function closeProjectCtxMenu() {
489
490
  }
490
491
  }
491
492
 
492
- function showIconCtxMenu(anchorEl, slug, name) {
493
+ function showIconCtxMenu(anchorEl, slug, name, options) {
493
494
  closeProjectCtxMenu();
494
495
  if (closeUserCtxMenu) closeUserCtxMenu();
495
496
  closeEmojiPicker();
@@ -510,17 +511,19 @@ function showIconCtxMenu(anchorEl, slug, name) {
510
511
  menu.appendChild(iconItem);
511
512
 
512
513
  if (isWorktree) {
513
- var removeWtItem = document.createElement("button");
514
- removeWtItem.className = "project-ctx-item project-ctx-delete";
515
- removeWtItem.innerHTML = iconHtml("trash-2") + " <span>Remove Worktree</span>";
516
- removeWtItem.addEventListener("click", function (e) {
517
- e.stopPropagation();
518
- closeProjectCtxMenu();
519
- if (getWs() && store.get('connected')) {
520
- getWs().send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name || slug }));
521
- }
522
- });
523
- menu.appendChild(removeWtItem);
514
+ if (!options || options.canRemoveWorktree !== false) {
515
+ var removeWtItem = document.createElement("button");
516
+ removeWtItem.className = "project-ctx-item project-ctx-delete";
517
+ removeWtItem.innerHTML = iconHtml("trash-2") + " <span>Remove Worktree</span>";
518
+ removeWtItem.addEventListener("click", function (e) {
519
+ e.stopPropagation();
520
+ closeProjectCtxMenu();
521
+ if (getWs() && store.get('connected')) {
522
+ getWs().send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name || slug }));
523
+ }
524
+ });
525
+ menu.appendChild(removeWtItem);
526
+ }
524
527
  } else {
525
528
  var wtItem = document.createElement("button");
526
529
  wtItem.className = "project-ctx-item";
@@ -1253,8 +1256,8 @@ export function renderIconStrip(projects, currentSlug) {
1253
1256
  (function (wt) {
1254
1257
  var wtEl = document.createElement("a");
1255
1258
  var isWtActive = wt.slug === currentSlug && !currentDmUserId;
1256
- var isAccessible = wt.worktreeAccessible !== false;
1257
- wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (!isAccessible ? " wt-disabled" : "");
1259
+ var isExternal = isExternalWorktree(wt);
1260
+ wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (isExternal ? " wt-external" : "");
1258
1261
  wtEl.href = "/p/" + wt.slug + "/";
1259
1262
  wtEl.dataset.slug = wt.slug;
1260
1263
 
@@ -1276,42 +1279,28 @@ export function renderIconStrip(projects, currentSlug) {
1276
1279
  if (wt.isProcessing) wtStatus.classList.add("processing");
1277
1280
  wtEl.appendChild(wtStatus);
1278
1281
 
1279
- var tooltipText = wt.name;
1280
- if (!isAccessible) {
1281
- tooltipText += " (outside project path, cannot be accessed)";
1282
- }
1282
+ if (isExternal) appendExternalWorktreeBadge(wtEl, "worktree-external-badge");
1283
+
1284
+ var tooltipText = isExternal ? externalWorktreeTooltip(wt.name) : wt.name;
1283
1285
  (function (text, elem) {
1284
1286
  elem.addEventListener("mouseenter", function () { showIconTooltip(elem, text); });
1285
1287
  elem.addEventListener("mouseleave", hideIconTooltip);
1286
1288
  })(tooltipText, wtEl);
1287
1289
 
1288
- if (isAccessible) {
1289
- (function (slug) {
1290
- wtEl.addEventListener("click", function (e) {
1291
- e.preventDefault();
1292
- if (switchProject) switchProject(slug);
1293
- });
1294
- })(wt.slug);
1295
- } else {
1290
+ (function (slug) {
1296
1291
  wtEl.addEventListener("click", function (e) {
1297
1292
  e.preventDefault();
1293
+ if (switchProject) switchProject(slug);
1298
1294
  });
1299
- }
1295
+ })(wt.slug);
1300
1296
 
1301
- if (isAccessible) {
1302
- (function (slug, name, elem) {
1303
- elem.addEventListener("contextmenu", function (e) {
1304
- e.preventDefault();
1305
- e.stopPropagation();
1306
- showIconCtxMenu(elem, slug, name);
1307
- });
1308
- })(wt.slug, wt.name, wtEl);
1309
- } else {
1310
- wtEl.addEventListener("contextmenu", function (e) {
1297
+ (function (slug, name, elem, external) {
1298
+ elem.addEventListener("contextmenu", function (e) {
1311
1299
  e.preventDefault();
1312
1300
  e.stopPropagation();
1301
+ showIconCtxMenu(elem, slug, name, { canRemoveWorktree: !external });
1313
1302
  });
1314
- }
1303
+ })(wt.slug, wt.name, wtEl, isExternal);
1315
1304
 
1316
1305
  if (!isWtActive && (wt.pendingPermissions > 0 || getPendingTuiAttention(wt.slug) > 0)) {
1317
1306
  wtEl.classList.add("has-pending-perm");
@@ -1360,7 +1349,10 @@ export function renderIconStrip(projects, currentSlug) {
1360
1349
 
1361
1350
  renderProjectList(projects, currentSlug);
1362
1351
 
1363
- try { lucide.createIcons({ nodes: [container] }); } catch (e) {}
1352
+ var iconNodes = [container];
1353
+ var mobileProjectList = document.getElementById("project-list");
1354
+ if (mobileProjectList) iconNodes.push(mobileProjectList);
1355
+ try { lucide.createIcons({ nodes: iconNodes }); } catch (e) {}
1364
1356
  }
1365
1357
 
1366
1358
  function renderProjectList(projects, currentSlug) {
@@ -1401,11 +1393,10 @@ function renderProjectList(projects, currentSlug) {
1401
1393
  var wtList = document.createElement("div");
1402
1394
  wtList.className = "mobile-folder-items";
1403
1395
  for (var wi = 0; wi < worktrees.length; wi++) {
1404
- var isAccessible = worktrees[wi].worktreeAccessible !== false;
1405
1396
  var wtItem = createMobileProjectItem(worktrees[wi], currentSlug, true);
1406
- if (!isAccessible) wtItem.classList.add("wt-disabled");
1407
- if (!isAccessible) {
1408
- wtItem.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
1397
+ if (isExternalWorktree(worktrees[wi])) {
1398
+ wtItem.classList.add("wt-external");
1399
+ appendExternalWorktreeBadge(wtItem, "mobile-worktree-external-badge");
1409
1400
  }
1410
1401
  wtList.appendChild(wtItem);
1411
1402
  }
@@ -12,7 +12,7 @@ import { dismissOverlayPanels, closeSidebar, updatePageTitle, spawnDustParticles
12
12
  import { showConfirm } from './app-misc.js';
13
13
  import { getUpcomingSchedules } from './scheduler.js';
14
14
  import { refreshMobileChatSheet } from './sidebar-mobile.js';
15
- import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES } from './app-rendering.js';
15
+ import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperimentalVendor } from './app-rendering.js';
16
16
  import { openGroup, separateGroup } from './split-view.js';
17
17
  import { groupedSessionIds } from './split-group-helpers.js';
18
18
  import { openPairDialog } from './split-pair-ui.js';
@@ -299,13 +299,10 @@ function appendSessionCloseButton(el, session) {
299
299
  el.appendChild(closeBtn);
300
300
  }
301
301
 
302
- // Resolve the vendor the "New session" button should launch: the project's
303
- // last-used vendor when its CLI is still installed, else the first installed
304
- // vendor, else Claude (so the button always has a sane label to render).
302
+ // Resolve the vendor the "New session" button should launch from the stable
303
+ // installed-vendor priority (Claude, then Codex, then the remaining vendors).
305
304
  export function resolveDefaultVendor() {
306
305
  var installed = store.get('installedVendors') || [];
307
- var last = store.get('lastVendor') || "";
308
- if (last && installed.indexOf(last) !== -1) return last;
309
306
  for (var i = 0; i < VENDOR_ORDER.length; i++) {
310
307
  if (installed.indexOf(VENDOR_ORDER[i]) !== -1) return VENDOR_ORDER[i];
311
308
  }
@@ -330,6 +327,7 @@ export function startNewSession(vendor, extra) {
330
327
  currentModel: "",
331
328
  currentModels: [],
332
329
  vendorSelectionLocked: true,
330
+ sessionVendorBound: true,
333
331
  });
334
332
  }
335
333
 
@@ -396,10 +394,12 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
396
394
  item.type = "button";
397
395
  item.innerHTML = '<img src="' + VENDOR_AVATARS[vendor] + '" class="session-top-action-icon" alt="">' +
398
396
  '<span class="session-new-vendor-name">' + name + '</span>' +
397
+ (isExperimentalVendor(vendor) ? '<span class="vendor-experimental-badge" aria-label="Experimental integration" title="Experimental integration; not yet validated through direct use testing">🧪</span>' : '') +
399
398
  (isInstalled ? "" : '<span class="session-new-vendor-note">Not installed</span>' + iconHtml("external-link"));
400
399
  item.title = isInstalled
401
400
  ? "New " + name + " session"
402
401
  : name + " is not installed - open " + VENDOR_HOMEPAGES[vendor];
402
+ if (isExperimentalVendor(vendor)) item.title += " · Experimental integration; not yet validated through direct use testing";
403
403
  item.addEventListener("click", function (e) {
404
404
  e.stopPropagation();
405
405
  closeSessionCtxMenu();
@@ -1,6 +1,6 @@
1
1
  import { store } from './store.js';
2
2
  import { getWs } from './ws-ref.js';
3
- import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER } from './app-rendering.js';
3
+ import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, isExperimentalVendor } from './app-rendering.js';
4
4
  import { effortLevelsFor, effortDisplayName } from './app-panels.js';
5
5
  import { showToast } from './utils.js';
6
6
 
@@ -46,7 +46,8 @@ function buildVendorSelect(installed, preferred) {
46
46
  if (installed.indexOf(VENDOR_ORDER[i]) === -1) continue;
47
47
  var option = document.createElement("option");
48
48
  option.value = VENDOR_ORDER[i];
49
- option.textContent = VENDOR_NAMES[VENDOR_ORDER[i]] || VENDOR_ORDER[i];
49
+ option.textContent = (isExperimentalVendor(VENDOR_ORDER[i]) ? "🧪 " : "") + (VENDOR_NAMES[VENDOR_ORDER[i]] || VENDOR_ORDER[i]);
50
+ if (isExperimentalVendor(VENDOR_ORDER[i])) option.title = "Experimental integration; not yet validated through direct use testing";
50
51
  select.appendChild(option);
51
52
  }
52
53
  if (preferred && installed.indexOf(preferred) !== -1) select.value = preferred;
@@ -814,7 +814,18 @@ function resolvePermissionIdentity(mateId, vendor) {
814
814
  }
815
815
  }
816
816
  // Project chat: use vendor name and avatar
817
- var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png", kiro: "/kiro-avatar.svg" };
817
+ var vendorAvatars = {
818
+ claude: "/claude-code-avatar.png",
819
+ codex: "/codex-avatar.png",
820
+ antigravity: "/antigravity-avatar.png",
821
+ opencode: "/opencode-avatar.svg",
822
+ kimi: "/kimi-avatar.svg",
823
+ grok: "/grok-avatar.svg",
824
+ copilot: "/copilot-avatar.svg",
825
+ qwen: "/qwen-avatar.svg",
826
+ junie: "/junie-avatar.svg",
827
+ kiro: "/kiro-avatar.svg",
828
+ };
818
829
  var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
819
830
  return {
820
831
  name: vendorName,
@@ -0,0 +1,14 @@
1
+ export var VENDOR_ORDER = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
2
+ export var EXPERIMENTAL_VENDORS = ["grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode"];
3
+
4
+ export function isExperimentalVendor(vendor) {
5
+ return EXPERIMENTAL_VENDORS.indexOf(vendor) !== -1;
6
+ }
7
+
8
+ export function firstInstalledVendor(installedVendors) {
9
+ var installed = Array.isArray(installedVendors) ? installedVendors : [];
10
+ for (var i = 0; i < VENDOR_ORDER.length; i++) {
11
+ if (installed.indexOf(VENDOR_ORDER[i]) !== -1) return VENDOR_ORDER[i];
12
+ }
13
+ return "";
14
+ }
@@ -0,0 +1,20 @@
1
+ import { store } from './store.js';
2
+ import { firstInstalledVendor } from './vendor-priority.js';
3
+
4
+ export function selectDefaultVendorForBlankSession() {
5
+ var state = store.snap();
6
+ if (!state.activeSessionId || state.sessionHasHistory || state.sessionVendorBound || state.dmMode) return "";
7
+ var vendor = firstInstalledVendor(state.installedVendors);
8
+ if (!vendor) return "";
9
+ if (vendor === state.currentVendor) {
10
+ if (state.vendorSelectionLocked) store.set({ vendorSelectionLocked: false });
11
+ return vendor;
12
+ }
13
+ store.set({
14
+ currentVendor: vendor,
15
+ currentModel: "",
16
+ currentModels: [],
17
+ vendorSelectionLocked: false,
18
+ });
19
+ return vendor;
20
+ }
@@ -0,0 +1,17 @@
1
+ export function isExternalWorktree(project) {
2
+ return !!(project && project.isWorktree && project.worktreeExternal === true);
3
+ }
4
+
5
+ export function externalWorktreeTooltip(name) {
6
+ return name + " — outside the main project folder";
7
+ }
8
+
9
+ export function appendExternalWorktreeBadge(container, className) {
10
+ var badge = document.createElement("span");
11
+ badge.className = className;
12
+ badge.setAttribute("role", "img");
13
+ badge.setAttribute("aria-label", "Outside the main project folder");
14
+ badge.innerHTML = '<i data-lucide="external-link"></i>';
15
+ container.appendChild(badge);
16
+ return badge;
17
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="OpenCode">
2
+ <rect width="64" height="64" rx="16" fill="#111814"/>
3
+ <path d="m26 19-13 13 13 13M38 19l13 13-13 13" fill="none" stroke="#78e08f" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Qwen Code">
2
+ <rect width="64" height="64" rx="14" fill="#6b4eff"/>
3
+ <path d="M32 12a20 20 0 1 0 12 36l7 5-3-11a20 20 0 0 0-16-30zm0 9a11 11 0 1 1-7.8 18.8A11 11 0 0 1 32 21z" fill="#fff"/>
4
+ </svg>
@@ -1,12 +1,12 @@
1
1
  @import url("css/base.css");
2
2
  @import url("css/icon-strip.css");
3
- @import url("css/title-bar.css");
3
+ @import url("css/title-bar.css?v=20260824");
4
4
  @import url("css/pane.css");
5
5
  @import url("css/worker-proposal.css");
6
6
  @import url("css/sidebar.css");
7
7
  @import url("css/overlays.css");
8
8
  @import url("css/menus.css");
9
- @import url("css/messages.css?v=20260407");
9
+ @import url("css/messages.css?v=20260824");
10
10
  @import url("css/rewind.css");
11
11
  @import url("css/input.css");
12
12
  @import url("css/filebrowser.css");
@@ -35,3 +35,4 @@
35
35
  @import url("css/debate.css");
36
36
  @import url("css/notifications-center.css");
37
37
  @import url("css/tui-attention.css");
38
+ @import url("css/pwa-mobile.css?v=20260824f");