clay-server 3.4.0-beta.8 → 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 (73) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +59 -59
  3. package/lib/project-connection.js +19 -6
  4. package/lib/project-models.js +220 -0
  5. package/lib/project-session-handoff.js +162 -0
  6. package/lib/project-session-pair.js +14 -7
  7. package/lib/project-session-spawn.js +1 -1
  8. package/lib/project-sessions.js +13 -45
  9. package/lib/project-worker-proposal.js +51 -14
  10. package/lib/project.js +35 -79
  11. package/lib/public/app.js +11 -0
  12. package/lib/public/copilot-avatar.svg +5 -0
  13. package/lib/public/css/command-palette.css +22 -2
  14. package/lib/public/css/filebrowser.css +6 -1
  15. package/lib/public/css/icon-strip.css +29 -13
  16. package/lib/public/css/input.css +56 -0
  17. package/lib/public/css/mates.css +6 -0
  18. package/lib/public/css/menus.css +35 -56
  19. package/lib/public/css/pane.css +18 -3
  20. package/lib/public/css/session-actions.css +98 -0
  21. package/lib/public/grok-avatar.svg +4 -0
  22. package/lib/public/index.html +26 -9
  23. package/lib/public/junie-avatar.svg +11 -0
  24. package/lib/public/kimi-avatar.svg +4 -0
  25. package/lib/public/modules/agent-config-selects.js +69 -0
  26. package/lib/public/modules/app-connection.js +6 -0
  27. package/lib/public/modules/app-header.js +0 -22
  28. package/lib/public/modules/app-messages.js +48 -9
  29. package/lib/public/modules/app-panels.js +33 -82
  30. package/lib/public/modules/app-projects.js +3 -1
  31. package/lib/public/modules/app-rendering.js +16 -1
  32. package/lib/public/modules/background-tasks-ui.js +55 -0
  33. package/lib/public/modules/filebrowser-tabs.js +65 -0
  34. package/lib/public/modules/filebrowser.js +23 -3
  35. package/lib/public/modules/mate-sidebar.js +10 -2
  36. package/lib/public/modules/model-picker.js +263 -0
  37. package/lib/public/modules/pane-bridge.js +6 -0
  38. package/lib/public/modules/project-switcher.js +7 -6
  39. package/lib/public/modules/session-actions.js +294 -0
  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 +17 -1
  43. package/lib/public/modules/split-pair-ui.js +16 -77
  44. package/lib/public/modules/split-view.js +11 -1
  45. package/lib/public/modules/tools.js +5 -0
  46. package/lib/public/modules/vendor-priority.js +6 -1
  47. package/lib/public/modules/worker-proposal.js +6 -1
  48. package/lib/public/modules/worktree-location.js +17 -0
  49. package/lib/public/qwen-avatar.svg +4 -0
  50. package/lib/public/style.css +1 -0
  51. package/lib/sdk-bridge.js +57 -34
  52. package/lib/sdk-message-processor.js +26 -4
  53. package/lib/session-handoff-context.js +110 -0
  54. package/lib/session-notes-mcp-server.js +1 -0
  55. package/lib/sessions.js +4 -0
  56. package/lib/worktree.js +9 -4
  57. package/lib/ws-schema.js +9 -1
  58. package/lib/yoke/acp-agent-profiles.js +64 -0
  59. package/lib/yoke/adapters/acp.js +2 -1
  60. package/lib/yoke/adapters/claude.js +34 -0
  61. package/lib/yoke/adapters/codex.js +38 -3
  62. package/lib/yoke/adapters/copilot.js +7 -0
  63. package/lib/yoke/adapters/grok.js +7 -0
  64. package/lib/yoke/adapters/junie.js +7 -0
  65. package/lib/yoke/adapters/kimi.js +7 -0
  66. package/lib/yoke/adapters/kiro.js +4 -3
  67. package/lib/yoke/adapters/qwen.js +7 -0
  68. package/lib/yoke/codex-background-tasks.js +84 -0
  69. package/lib/yoke/index.js +43 -14
  70. package/lib/yoke/instructions.js +3 -0
  71. package/lib/yoke/interface.js +21 -0
  72. package/lib/yoke/vendor-registry.js +55 -0
  73. package/package.json +1 -1
@@ -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';
@@ -394,10 +394,12 @@ function showNewSessionMenu(anchorBtn, defaultVendor) {
394
394
  item.type = "button";
395
395
  item.innerHTML = '<img src="' + VENDOR_AVATARS[vendor] + '" class="session-top-action-icon" alt="">' +
396
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>' : '') +
397
398
  (isInstalled ? "" : '<span class="session-new-vendor-note">Not installed</span>' + iconHtml("external-link"));
398
399
  item.title = isInstalled
399
400
  ? "New " + name + " session"
400
401
  : name + " is not installed - open " + VENDOR_HOMEPAGES[vendor];
402
+ if (isExperimentalVendor(vendor)) item.title += " · Experimental integration; not yet validated through direct use testing";
401
403
  item.addEventListener("click", function (e) {
402
404
  e.stopPropagation();
403
405
  closeSessionCtxMenu();
@@ -975,6 +977,9 @@ function renderLoopChild(s) {
975
977
  if (s.isProcessing) {
976
978
  textHtml += '<span class="session-processing"></span>';
977
979
  }
980
+ if (s.backgroundTaskCount) {
981
+ textHtml += '<span class="session-background-task-count">' + s.backgroundTaskCount + '</span>';
982
+ }
978
983
  if (s.loop) {
979
984
  var isRalphChild = s.loop.source === "ralph";
980
985
  var roleName = s.loop.role === "crafting" ? "Crafting" : s.loop.role === "judge" ? "Judge" : (isRalphChild ? "Coder" : "Run");
@@ -1094,6 +1099,9 @@ function renderLoopGroup(loopId, children, groupKey) {
1094
1099
  if (anyProcessing) {
1095
1100
  textHtml += '<span class="session-processing"></span>';
1096
1101
  }
1102
+ var groupBackgroundTaskCount = 0;
1103
+ for (var bi = 0; bi < children.length; bi++) groupBackgroundTaskCount += children[bi].backgroundTaskCount || 0;
1104
+ if (groupBackgroundTaskCount) textHtml += '<span class="session-background-task-count">' + groupBackgroundTaskCount + '</span>';
1097
1105
  var groupIcon = isDebate ? "mic" : (isRalph ? "repeat" : "calendar-clock");
1098
1106
  var iconClass = isDebate ? " debate" : (isRalph ? "" : " scheduled");
1099
1107
  textHtml += '<span class="session-loop-icon' + iconClass + '">' + iconHtml(groupIcon) + '</span>';
@@ -1203,6 +1211,9 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
1203
1211
  if (anyProcessing) {
1204
1212
  textHtml += '<span class="session-processing"></span>';
1205
1213
  }
1214
+ var runBackgroundTaskCount = 0;
1215
+ for (var bi = 0; bi < sessions.length; bi++) runBackgroundTaskCount += sessions[bi].backgroundTaskCount || 0;
1216
+ if (runBackgroundTaskCount) textHtml += '<span class="session-background-task-count">' + runBackgroundTaskCount + '</span>';
1206
1217
  textHtml += '<span class="session-loop-run-time">' + escapeHtml(timeLabel) + '</span>';
1207
1218
  textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + sessions.length + '</span>';
1208
1219
  textSpan.innerHTML = textHtml;
@@ -1252,6 +1263,9 @@ function renderSessionItem(s) {
1252
1263
  if (s.isProcessing) {
1253
1264
  textHtml += '<span class="session-processing"></span>';
1254
1265
  }
1266
+ if (s.backgroundTaskCount) {
1267
+ textHtml += '<span class="session-background-task-count">' + s.backgroundTaskCount + '</span>';
1268
+ }
1255
1269
  if (s.loop && s.loop.source === "debate") {
1256
1270
  textHtml += '<span class="session-debate-icon" title="Debate">' + iconHtml("mic") + '</span>';
1257
1271
  }
@@ -1320,6 +1334,8 @@ function renderSplitGroupItem(group, members) {
1320
1334
  }
1321
1335
  html += "</span>";
1322
1336
  if (members[0].isProcessing || members[1].isProcessing) html += '<span class="session-processing"></span>';
1337
+ var splitBackgroundTaskCount = (members[0].backgroundTaskCount || 0) + (members[1].backgroundTaskCount || 0);
1338
+ if (splitBackgroundTaskCount) html += '<span class="session-background-task-count">' + splitBackgroundTaskCount + '</span>';
1323
1339
  html += '<span class="split-group-name">' + highlightMatch(group.name || "Split", searchQuery) + "</span>";
1324
1340
  text.innerHTML = html;
1325
1341
  el.appendChild(text);
@@ -1,7 +1,7 @@
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';
4
- import { effortLevelsFor, effortDisplayName } from './app-panels.js';
3
+ import { VENDOR_AVATARS } from './app-rendering.js';
4
+ import { buildAgentVendorSelect, fillAgentModels, buildAgentEffortSelect, fillAgentEffort } from './agent-config-selects.js';
5
5
  import { showToast } from './utils.js';
6
6
 
7
7
  var pairDialog = null;
@@ -9,77 +9,14 @@ var pairDialog = null;
9
9
  // that session becomes the Driver and the dialog only configures the Worker.
10
10
  var pendingDriver = null;
11
11
 
12
- function optionValue(entry) {
13
- if (typeof entry === "string") return entry;
14
- return entry && (entry.value || entry.id) || "";
15
- }
16
-
17
- function optionLabel(entry) {
18
- if (typeof entry === "string") return entry;
19
- return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
20
- }
21
-
22
- function fillModels(select, vendor, options) {
23
- var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
24
- select.innerHTML = "";
25
- var automatic = document.createElement("option");
26
- automatic.value = "";
27
- automatic.textContent = "Automatic";
28
- select.appendChild(automatic);
29
- for (var i = 0; i < models.length; i++) {
30
- var option = document.createElement("option");
31
- option.value = optionValue(models[i]);
32
- option.textContent = optionLabel(models[i]);
33
- select.appendChild(option);
34
- }
35
- }
36
-
37
12
  function closePairDialog() {
38
13
  if (pairDialog) pairDialog.remove();
39
14
  pairDialog = null;
40
15
  }
41
16
 
42
- function buildVendorSelect(installed, preferred) {
43
- var select = document.createElement("select");
44
- select.className = "wt-modal-input";
45
- for (var i = 0; i < VENDOR_ORDER.length; i++) {
46
- if (installed.indexOf(VENDOR_ORDER[i]) === -1) continue;
47
- var option = document.createElement("option");
48
- option.value = VENDOR_ORDER[i];
49
- option.textContent = VENDOR_NAMES[VENDOR_ORDER[i]] || VENDOR_ORDER[i];
50
- select.appendChild(option);
51
- }
52
- if (preferred && installed.indexOf(preferred) !== -1) select.value = preferred;
53
- return select;
54
- }
55
-
56
17
  // Effort levels are vendor- and model-specific (codex: minimal..xhigh,
57
18
  // claude/kiro: low..max, plus per-model supportedEffortLevels overrides), so
58
19
  // options are rebuilt whenever the role's vendor or model changes.
59
- function fillEffortOptions(select, vendor, options, modelValue) {
60
- var previous = select.value;
61
- var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
62
- var levels = effortLevelsFor(vendor, models, modelValue);
63
- select.innerHTML = "";
64
- var automatic = document.createElement("option");
65
- automatic.value = "";
66
- automatic.textContent = "Default";
67
- select.appendChild(automatic);
68
- for (var i = 0; i < levels.length; i++) {
69
- var option = document.createElement("option");
70
- option.value = levels[i];
71
- option.textContent = effortDisplayName(levels[i]);
72
- select.appendChild(option);
73
- }
74
- select.value = levels.indexOf(previous) !== -1 ? previous : "";
75
- }
76
-
77
- function buildEffortSelect() {
78
- var select = document.createElement("select");
79
- select.className = "wt-modal-input";
80
- return select;
81
- }
82
-
83
20
  function fieldLabel(text, control) {
84
21
  var label = document.createElement("label");
85
22
  label.className = "wt-modal-label";
@@ -154,25 +91,25 @@ export function showPairDialog(options) {
154
91
  : "One Driver plans and directs a visible Worker session.";
155
92
  modal.appendChild(desc);
156
93
 
157
- var driverVendor = buildVendorSelect(installed, installed.indexOf("claude") !== -1 ? "claude" : installed[0]);
94
+ var driverVendor = buildAgentVendorSelect(installed, installed.indexOf("claude") !== -1 ? "claude" : installed[0]);
158
95
  var workerDefault = options.lastVendor && options.lastVendor !== driverVendor.value ? options.lastVendor : (installed.indexOf("codex") !== -1 ? "codex" : installed[0]);
159
- var workerVendor = buildVendorSelect(installed, workerDefault);
96
+ var workerVendor = buildAgentVendorSelect(installed, workerDefault);
160
97
  var driverModel = document.createElement("select");
161
98
  driverModel.className = "wt-modal-input";
162
99
  var workerModel = document.createElement("select");
163
100
  workerModel.className = "wt-modal-input";
164
- var driverEffort = buildEffortSelect();
165
- var workerEffort = buildEffortSelect();
166
- function refreshDriverEffort() { fillEffortOptions(driverEffort, driverVendor.value, options, driverModel.value); }
167
- function refreshWorkerEffort() { fillEffortOptions(workerEffort, workerVendor.value, options, workerModel.value); }
168
- fillModels(driverModel, driverVendor.value, options);
169
- fillModels(workerModel, workerVendor.value, options);
101
+ var driverEffort = buildAgentEffortSelect();
102
+ var workerEffort = buildAgentEffortSelect();
103
+ function refreshDriverEffort() { fillAgentEffort(driverEffort, driverVendor.value, options, driverModel.value); }
104
+ function refreshWorkerEffort() { fillAgentEffort(workerEffort, workerVendor.value, options, workerModel.value); }
105
+ fillAgentModels(driverModel, driverVendor.value, options);
106
+ fillAgentModels(workerModel, workerVendor.value, options);
170
107
  refreshDriverEffort();
171
108
  refreshWorkerEffort();
172
109
  workerEffort.value = "medium";
173
110
  if (workerEffort.value !== "medium") workerEffort.value = "";
174
- driverVendor.addEventListener("change", function () { fillModels(driverModel, driverVendor.value, options); refreshDriverEffort(); });
175
- workerVendor.addEventListener("change", function () { fillModels(workerModel, workerVendor.value, options); refreshWorkerEffort(); });
111
+ driverVendor.addEventListener("change", function () { fillAgentModels(driverModel, driverVendor.value, options); refreshDriverEffort(); });
112
+ workerVendor.addEventListener("change", function () { fillAgentModels(workerModel, workerVendor.value, options); refreshWorkerEffort(); });
176
113
  driverModel.addEventListener("change", refreshDriverEffort);
177
114
  workerModel.addEventListener("change", refreshWorkerEffort);
178
115
 
@@ -318,7 +255,8 @@ export function syncPairChrome(host, split) {
318
255
  setBtn.title = "Make this session the Driver; the other pane becomes its Worker";
319
256
  setBtn.addEventListener("click", function () { sendSetPair(group.id, paneSessionId); });
320
257
  var titleEl = header.querySelector(".split-pane-title");
321
- header.insertBefore(setBtn, titleEl ? titleEl.nextSibling : null);
258
+ var accessEl = header.querySelector(".split-pane-full-access");
259
+ header.insertBefore(setBtn, accessEl ? accessEl.nextSibling : (titleEl ? titleEl.nextSibling : null));
322
260
  })(split.panes[ai].sessionId, headers[ai]);
323
261
  }
324
262
  return;
@@ -338,7 +276,8 @@ export function syncPairChrome(host, split) {
338
276
  sendSetPair(group.id, isDriver ? null : sessionId);
339
277
  });
340
278
  var title = header.querySelector(".split-pane-title");
341
- header.insertBefore(badge, title ? title.nextSibling : null);
279
+ var access = header.querySelector(".split-pane-full-access");
280
+ header.insertBefore(badge, access ? access.nextSibling : (title ? title.nextSibling : null));
342
281
  })(split.panes[pi].sessionId, headers[pi]);
343
282
  }
344
283
  var active = (store.get('splitDelegations') || {})[group.id];
@@ -10,6 +10,7 @@ import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
10
10
  import { groupedSessionIds, findSplitGroup } from './split-group-helpers.js';
11
11
  import { showConfirm } from './app-misc.js';
12
12
  import { syncPairChrome } from './split-pair-ui.js';
13
+ import { presentMarkdownEdit } from './filebrowser.js';
13
14
 
14
15
  var host = null;
15
16
  var nativeApp = null;
@@ -231,6 +232,9 @@ function createPane(pane, index) {
231
232
  }, "Skip Permissions", true);
232
233
  });
233
234
  header.appendChild(fullAccess);
235
+ // Permission state belongs to the session identity on the left. Keep it
236
+ // immediately after the title instead of grouping it with usage and close.
237
+ header.insertBefore(fullAccess, ctxChip);
234
238
 
235
239
  var close = document.createElement("button");
236
240
  close.type = "button";
@@ -266,7 +270,13 @@ function updatePaneContextChip(paneEl, msg) {
266
270
  function handlePaneMessage(event) {
267
271
  if (event.origin !== window.location.origin) return;
268
272
  var msg = event.data;
269
- if (!msg || msg.type !== "clay-pane-context" || !host) return;
273
+ if (!msg || !host) return;
274
+ if (msg.type === "clay-pane-present-markdown") {
275
+ var present = msg.message;
276
+ if (present) presentMarkdownEdit(present);
277
+ return;
278
+ }
279
+ if (msg.type !== "clay-pane-context") return;
270
280
  var frames = host.querySelectorAll(".split-pane-frame");
271
281
  for (var i = 0; i < frames.length; i++) {
272
282
  if (frames[i].contentWindow === event.source) {
@@ -819,6 +819,11 @@ function resolvePermissionIdentity(mateId, vendor) {
819
819
  codex: "/codex-avatar.png",
820
820
  antigravity: "/antigravity-avatar.png",
821
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",
822
827
  kiro: "/kiro-avatar.svg",
823
828
  };
824
829
  var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
@@ -1,4 +1,9 @@
1
- export var VENDOR_ORDER = ["claude", "codex", "antigravity", "opencode", "kiro"];
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
+ }
2
7
 
3
8
  export function firstInstalledVendor(installedVendors) {
4
9
  var installed = Array.isArray(installedVendors) ? installedVendors : [];
@@ -95,6 +95,7 @@ function statusLabel(status) {
95
95
  if (status === "starting") return "Starting Worker";
96
96
  if (status === "running") return "Worker running";
97
97
  if (status === "completed") return "Completed";
98
+ if (status === "interrupted") return "Interrupted";
98
99
  if (status === "declined") return "Continuing here";
99
100
  if (status === "error") return "Needs attention";
100
101
  return "Suggested by Fable";
@@ -103,8 +104,10 @@ function statusLabel(status) {
103
104
  function applyState(card, msg) {
104
105
  var status = msg.status || "pending";
105
106
  card.dataset.status = status;
107
+ if (msg.autoApproved) card.dataset.autoApproved = "true";
108
+ var autoApproved = card.dataset.autoApproved === "true";
106
109
  var badge = card.querySelector(".worker-proposal-status");
107
- if (badge) badge.textContent = statusLabel(status);
110
+ if (badge) badge.textContent = statusLabel(status) + (autoApproved ? " · auto-approved" : "");
108
111
  var error = card.querySelector(".worker-proposal-error");
109
112
  if (error) {
110
113
  error.textContent = msg.error || "";
@@ -116,6 +119,8 @@ function applyState(card, msg) {
116
119
  preview.classList.remove("hidden");
117
120
  }
118
121
  setControlsDisabled(card, status !== "pending");
122
+ var actions = card.querySelector(".worker-proposal-actions");
123
+ if (actions) actions.classList.toggle("hidden", autoApproved);
119
124
  }
120
125
 
121
126
  function sendDecision(card, accepted) {
@@ -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="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>
@@ -3,6 +3,7 @@
3
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
+ @import url("css/session-actions.css");
6
7
  @import url("css/sidebar.css");
7
8
  @import url("css/overlays.css");
8
9
  @import url("css/menus.css");