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.
- package/lib/daemon-projects.js +3 -3
- package/lib/daemon.js +59 -59
- package/lib/project-connection.js +35 -7
- package/lib/project-debate.js +4 -0
- package/lib/project-mate-interaction.js +21 -1
- package/lib/project-memory.js +3 -0
- package/lib/project-models.js +220 -0
- package/lib/project-session-handoff.js +162 -0
- package/lib/project-session-pair.js +14 -7
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +13 -45
- package/lib/project-worker-proposal.js +51 -14
- package/lib/project.js +38 -80
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +29 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/command-palette.css +22 -2
- package/lib/public/css/icon-strip.css +29 -13
- package/lib/public/css/input.css +56 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +38 -25
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pane.css +16 -3
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +29 -7
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-header.js +4 -22
- package/lib/public/modules/app-messages.js +54 -11
- package/lib/public/modules/app-panels.js +36 -83
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +19 -4
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/mate-sidebar.js +11 -3
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/notifications.js +7 -1
- package/lib/public/modules/pane-bridge.js +11 -0
- package/lib/public/modules/pane-links.js +23 -0
- package/lib/public/modules/project-switcher.js +7 -6
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-projects.js +34 -43
- package/lib/public/modules/sidebar-sessions.js +20 -6
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +3 -0
- package/lib/public/modules/tools.js +6 -1
- package/lib/public/modules/vendor-priority.js +14 -0
- package/lib/public/modules/vendor-selection.js +20 -0
- package/lib/public/modules/worker-proposal.js +6 -1
- package/lib/public/modules/worktree-location.js +17 -0
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +4 -2
- package/lib/sdk-bridge.js +103 -56
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +110 -0
- package/lib/session-notes-mcp-server.js +8 -1
- package/lib/sessions.js +4 -0
- package/lib/worktree.js +9 -4
- package/lib/ws-schema.js +9 -1
- package/lib/yoke/acp-agent-profiles.js +64 -14
- package/lib/yoke/adapters/antigravity.js +417 -0
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +101 -5
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +2 -2
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-app-server.js +25 -8
- package/lib/yoke/index.js +67 -28
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +12 -0
- package/lib/yoke/vendor-registry.js +61 -6
- package/package.json +1 -1
- package/lib/public/gemini-avatar.svg +0 -11
- package/lib/yoke/adapters/gemini.js +0 -7
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { store } from './store.js';
|
|
7
7
|
import { applyContextView, getContextView } from './app-panels.js';
|
|
8
|
+
import { forceExternalLinkToNewTab } from './pane-links.js';
|
|
8
9
|
|
|
9
10
|
var panelOpen = false;
|
|
10
11
|
|
|
@@ -29,9 +30,19 @@ function togglePaneContextPanel() {
|
|
|
29
30
|
applyContextView(panelOpen ? "panel" : "off");
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function preparePaneLink(event) {
|
|
34
|
+
var target = event.target;
|
|
35
|
+
if (!target || typeof target.closest !== "function") return;
|
|
36
|
+
var anchor = target.closest("a[href]");
|
|
37
|
+
if (!anchor || anchor.hasAttribute("download")) return;
|
|
38
|
+
forceExternalLinkToNewTab(anchor, window.location.href);
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
export function initPaneBridge() {
|
|
33
42
|
if (!store.get('paneMode')) return;
|
|
34
43
|
panelOpen = getContextView() === "panel";
|
|
44
|
+
document.addEventListener("click", preparePaneLink, true);
|
|
45
|
+
document.addEventListener("auxclick", preparePaneLink, true);
|
|
35
46
|
window.addEventListener("message", function (event) {
|
|
36
47
|
if (event.origin !== window.location.origin) return;
|
|
37
48
|
var msg = event.data;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function isExternalWebLink(href, currentHref) {
|
|
2
|
+
if (!href) return false;
|
|
3
|
+
try {
|
|
4
|
+
var url = new URL(href, currentHref);
|
|
5
|
+
var currentUrl = new URL(currentHref);
|
|
6
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
7
|
+
return url.origin !== currentUrl.origin;
|
|
8
|
+
} catch (err) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function forceExternalLinkToNewTab(anchor, currentHref) {
|
|
14
|
+
if (!anchor || !isExternalWebLink(anchor.href, currentHref)) return false;
|
|
15
|
+
|
|
16
|
+
var rel = (anchor.getAttribute("rel") || "").split(/\s+/).filter(Boolean);
|
|
17
|
+
if (rel.indexOf("noopener") === -1) rel.push("noopener");
|
|
18
|
+
if (rel.indexOf("noreferrer") === -1) rel.push("noreferrer");
|
|
19
|
+
|
|
20
|
+
anchor.setAttribute("target", "_blank");
|
|
21
|
+
anchor.setAttribute("rel", rel.join(" "));
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
@@ -19,6 +19,7 @@ import { getCachedProjects, switchProject } from './app-projects.js';
|
|
|
19
19
|
import { openDm } from './app-dm.js';
|
|
20
20
|
import { mateAvatarUrl } from './avatar.js';
|
|
21
21
|
import { parseEmojis } from './markdown.js';
|
|
22
|
+
import { isExternalWorktree, externalWorktreeTooltip, appendExternalWorktreeBadge } from './worktree-location.js';
|
|
22
23
|
|
|
23
24
|
var _projectMru = []; // project slugs, most recent first
|
|
24
25
|
var _mateMru = []; // mate ids (mate_XXX), most recent first
|
|
@@ -257,18 +258,16 @@ function buildProjectEntries() {
|
|
|
257
258
|
// Keep natural (server-provided) order so the list looks the same
|
|
258
259
|
// every time the switcher opens. MRU only drives initial highlight.
|
|
259
260
|
return projects.map(function (p) {
|
|
260
|
-
|
|
261
|
-
// unmounted or moved); those are effectively unreachable from
|
|
262
|
-
// this session and shouldn't be switchable.
|
|
263
|
-
var unreachable = p.isWorktree && p.worktreeAccessible === false;
|
|
261
|
+
var external = isExternalWorktree(p);
|
|
264
262
|
return {
|
|
265
263
|
key: p.slug,
|
|
266
264
|
title: p.title || p.project || p.slug,
|
|
267
265
|
icon: p.icon || null,
|
|
268
266
|
fallbackIcon: 'folder',
|
|
269
267
|
isCurrent: p.slug === store.get('currentSlug'),
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
external: external,
|
|
269
|
+
disabled: false,
|
|
270
|
+
disabledReason: null,
|
|
272
271
|
};
|
|
273
272
|
});
|
|
274
273
|
}
|
|
@@ -336,6 +335,7 @@ function buildEntryNode(entry, index) {
|
|
|
336
335
|
item.className = 'cmd-palette-item project-switcher-item';
|
|
337
336
|
if (entry.disabled) item.classList.add('disabled');
|
|
338
337
|
if (entry.disabled && entry.disabledReason) item.title = entry.disabledReason;
|
|
338
|
+
if (entry.external) item.title = externalWorktreeTooltip(entry.title);
|
|
339
339
|
item.dataset.index = String(index);
|
|
340
340
|
|
|
341
341
|
var iconWrap = document.createElement('div');
|
|
@@ -353,6 +353,7 @@ function buildEntryNode(entry, index) {
|
|
|
353
353
|
fallback.setAttribute('data-lucide', entry.fallbackIcon || 'folder');
|
|
354
354
|
iconWrap.appendChild(fallback);
|
|
355
355
|
}
|
|
356
|
+
if (entry.external) appendExternalWorktreeBadge(iconWrap, 'project-switcher-external-badge');
|
|
356
357
|
item.appendChild(iconWrap);
|
|
357
358
|
|
|
358
359
|
var body = document.createElement('div');
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
4
|
+
import { addSystemMessage, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
5
|
+
import { openPairDialog } from './split-pair-ui.js';
|
|
6
|
+
import { buildAgentVendorSelect, fillAgentModels, buildAgentEffortSelect, fillAgentEffort } from './agent-config-selects.js';
|
|
7
|
+
import { showToast } from './utils.js';
|
|
8
|
+
|
|
9
|
+
var menu = null;
|
|
10
|
+
var button = null;
|
|
11
|
+
var outsideHandler = null;
|
|
12
|
+
var escapeHandler = null;
|
|
13
|
+
var handoffDialog = null;
|
|
14
|
+
var handoffEscapeHandler = null;
|
|
15
|
+
var handoffSubmitButton = null;
|
|
16
|
+
|
|
17
|
+
function closeMenu() {
|
|
18
|
+
if (menu) menu.remove();
|
|
19
|
+
menu = null;
|
|
20
|
+
if (button) button.setAttribute("aria-expanded", "false");
|
|
21
|
+
if (outsideHandler) document.removeEventListener("pointerdown", outsideHandler, true);
|
|
22
|
+
if (escapeHandler) document.removeEventListener("keydown", escapeHandler);
|
|
23
|
+
outsideHandler = null;
|
|
24
|
+
escapeHandler = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function positionMenu() {
|
|
28
|
+
if (!menu || !button) return;
|
|
29
|
+
var rect = button.getBoundingClientRect();
|
|
30
|
+
var width = menu.offsetWidth;
|
|
31
|
+
menu.style.top = Math.round(rect.bottom + 6) + "px";
|
|
32
|
+
menu.style.left = Math.max(8, Math.round(rect.right - width)) + "px";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function actionRow(icon, label, description) {
|
|
36
|
+
var row = document.createElement("button");
|
|
37
|
+
row.type = "button";
|
|
38
|
+
row.className = "session-actions-row";
|
|
39
|
+
row.innerHTML = iconHtml(icon, "session-actions-row-icon") +
|
|
40
|
+
'<span class="session-actions-row-copy"><strong></strong><span></span></span>' +
|
|
41
|
+
iconHtml("chevron-right", "session-actions-row-chevron");
|
|
42
|
+
row.querySelector("strong").textContent = label;
|
|
43
|
+
row.querySelector(".session-actions-row-copy > span").textContent = description;
|
|
44
|
+
return row;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function menuShell(label) {
|
|
48
|
+
var el = document.createElement("div");
|
|
49
|
+
el.className = "session-actions-menu";
|
|
50
|
+
el.setAttribute("role", "menu");
|
|
51
|
+
el.setAttribute("aria-label", label);
|
|
52
|
+
document.body.appendChild(el);
|
|
53
|
+
menu = el;
|
|
54
|
+
button.setAttribute("aria-expanded", "true");
|
|
55
|
+
return el;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function installCloseHandlers() {
|
|
59
|
+
outsideHandler = function (event) {
|
|
60
|
+
if (menu && !menu.contains(event.target) && event.target !== button && !button.contains(event.target)) closeMenu();
|
|
61
|
+
};
|
|
62
|
+
escapeHandler = function (event) {
|
|
63
|
+
if (event.key === "Escape") {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
closeMenu();
|
|
66
|
+
if (button) button.focus();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
setTimeout(function () {
|
|
70
|
+
document.addEventListener("pointerdown", outsideHandler, true);
|
|
71
|
+
document.addEventListener("keydown", escapeHandler);
|
|
72
|
+
}, 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function closeHandoffDialog() {
|
|
76
|
+
if (handoffDialog) handoffDialog.remove();
|
|
77
|
+
if (handoffEscapeHandler) document.removeEventListener("keydown", handoffEscapeHandler);
|
|
78
|
+
handoffDialog = null;
|
|
79
|
+
handoffEscapeHandler = null;
|
|
80
|
+
handoffSubmitButton = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handoffField(labelText, control) {
|
|
84
|
+
var field = document.createElement("div");
|
|
85
|
+
field.className = "handoff-modal-field";
|
|
86
|
+
var label = document.createElement("label");
|
|
87
|
+
label.className = "wt-modal-label";
|
|
88
|
+
label.textContent = labelText;
|
|
89
|
+
field.appendChild(label);
|
|
90
|
+
field.appendChild(control);
|
|
91
|
+
return field;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function requestHandoffDialog() {
|
|
95
|
+
var ws = getWs();
|
|
96
|
+
if (!ws || ws.readyState !== 1) return;
|
|
97
|
+
closeMenu();
|
|
98
|
+
ws.send(JSON.stringify({ type: "handoff_session_options" }));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function showHandoffDialog(options) {
|
|
102
|
+
closeHandoffDialog();
|
|
103
|
+
var installed = options.installedVendors || [];
|
|
104
|
+
var vendorInfo = store.get('vendorInfo') || {};
|
|
105
|
+
if (store.get('isOsUsers')) {
|
|
106
|
+
installed = installed.filter(function (vendor) {
|
|
107
|
+
return !vendorInfo[vendor] || vendorInfo[vendor].osUserIsolation !== false;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (installed.length < 1) {
|
|
111
|
+
showToast("Install a coding agent before continuing this session.", "error");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var currentVendor = store.get('currentVendor') || "claude";
|
|
116
|
+
var currentModel = store.get('currentModel') || "";
|
|
117
|
+
var currentEffort = store.get('currentEffort') || "";
|
|
118
|
+
var container = document.createElement("div");
|
|
119
|
+
var overlay = document.createElement("div");
|
|
120
|
+
overlay.className = "wt-modal-overlay";
|
|
121
|
+
container.appendChild(overlay);
|
|
122
|
+
var modal = document.createElement("div");
|
|
123
|
+
modal.className = "wt-modal handoff-modal";
|
|
124
|
+
modal.setAttribute("role", "dialog");
|
|
125
|
+
modal.setAttribute("aria-modal", "true");
|
|
126
|
+
modal.setAttribute("aria-labelledby", "handoff-modal-title");
|
|
127
|
+
|
|
128
|
+
var title = document.createElement("div");
|
|
129
|
+
title.id = "handoff-modal-title";
|
|
130
|
+
title.className = "wt-modal-title";
|
|
131
|
+
title.textContent = "Continue in a new agent";
|
|
132
|
+
modal.appendChild(title);
|
|
133
|
+
var desc = document.createElement("div");
|
|
134
|
+
desc.className = "handoff-modal-desc";
|
|
135
|
+
desc.textContent = "Start an independent session with a focused copy of this conversation's context.";
|
|
136
|
+
modal.appendChild(desc);
|
|
137
|
+
|
|
138
|
+
var source = document.createElement("div");
|
|
139
|
+
source.className = "handoff-modal-source";
|
|
140
|
+
var sourceAvatar = document.createElement("img");
|
|
141
|
+
sourceAvatar.src = VENDOR_AVATARS[currentVendor] || VENDOR_AVATARS.claude;
|
|
142
|
+
sourceAvatar.alt = "";
|
|
143
|
+
source.appendChild(sourceAvatar);
|
|
144
|
+
var sourceCopy = document.createElement("span");
|
|
145
|
+
var sourceTitle = document.createElement("strong");
|
|
146
|
+
sourceTitle.textContent = document.getElementById("header-title").textContent || "Current session";
|
|
147
|
+
var sourceMeta = document.createElement("span");
|
|
148
|
+
sourceMeta.textContent = "From " + (VENDOR_NAMES[currentVendor] || currentVendor);
|
|
149
|
+
sourceCopy.appendChild(sourceTitle);
|
|
150
|
+
sourceCopy.appendChild(sourceMeta);
|
|
151
|
+
source.appendChild(sourceCopy);
|
|
152
|
+
modal.appendChild(source);
|
|
153
|
+
|
|
154
|
+
var vendorSelect = buildAgentVendorSelect(installed, currentVendor);
|
|
155
|
+
var modelSelect = document.createElement("select");
|
|
156
|
+
modelSelect.className = "wt-modal-input";
|
|
157
|
+
var effortSelect = buildAgentEffortSelect();
|
|
158
|
+
var vendorField = handoffField("Agent", vendorSelect);
|
|
159
|
+
var modelField = handoffField("Model", modelSelect);
|
|
160
|
+
var effortField = handoffField("Reasoning effort", effortSelect);
|
|
161
|
+
modal.appendChild(vendorField);
|
|
162
|
+
modal.appendChild(modelField);
|
|
163
|
+
modal.appendChild(effortField);
|
|
164
|
+
|
|
165
|
+
function syncEffort(preferred) {
|
|
166
|
+
fillAgentEffort(effortSelect, vendorSelect.value, options, modelSelect.value, preferred);
|
|
167
|
+
var capabilities = (options.capabilitiesByVendor && options.capabilitiesByVendor[vendorSelect.value]) || {};
|
|
168
|
+
effortField.style.display = capabilities.effort === false ? "none" : "";
|
|
169
|
+
}
|
|
170
|
+
fillAgentModels(modelSelect, vendorSelect.value, options, vendorSelect.value === currentVendor ? currentModel : "");
|
|
171
|
+
syncEffort(vendorSelect.value === currentVendor ? currentEffort : "");
|
|
172
|
+
vendorSelect.addEventListener("change", function () {
|
|
173
|
+
fillAgentModels(modelSelect, vendorSelect.value, options, "");
|
|
174
|
+
syncEffort("");
|
|
175
|
+
});
|
|
176
|
+
modelSelect.addEventListener("change", function () { syncEffort(effortSelect.value); });
|
|
177
|
+
|
|
178
|
+
var actions = document.createElement("div");
|
|
179
|
+
actions.className = "wt-modal-actions";
|
|
180
|
+
var cancel = document.createElement("button");
|
|
181
|
+
cancel.type = "button";
|
|
182
|
+
cancel.className = "wt-modal-btn";
|
|
183
|
+
cancel.textContent = "Cancel";
|
|
184
|
+
var submit = document.createElement("button");
|
|
185
|
+
submit.type = "button";
|
|
186
|
+
submit.className = "wt-modal-btn primary";
|
|
187
|
+
submit.textContent = "Continue";
|
|
188
|
+
actions.appendChild(cancel);
|
|
189
|
+
actions.appendChild(submit);
|
|
190
|
+
modal.appendChild(actions);
|
|
191
|
+
container.appendChild(modal);
|
|
192
|
+
document.body.appendChild(container);
|
|
193
|
+
handoffDialog = container;
|
|
194
|
+
handoffSubmitButton = submit;
|
|
195
|
+
|
|
196
|
+
cancel.addEventListener("click", closeHandoffDialog);
|
|
197
|
+
overlay.addEventListener("click", closeHandoffDialog);
|
|
198
|
+
handoffEscapeHandler = function (event) {
|
|
199
|
+
if (event.key === "Escape") closeHandoffDialog();
|
|
200
|
+
};
|
|
201
|
+
document.addEventListener("keydown", handoffEscapeHandler);
|
|
202
|
+
submit.addEventListener("click", function () {
|
|
203
|
+
var ws = getWs();
|
|
204
|
+
if (!ws || ws.readyState !== 1) return;
|
|
205
|
+
ws.send(JSON.stringify({
|
|
206
|
+
type: "handoff_session",
|
|
207
|
+
targetVendor: vendorSelect.value,
|
|
208
|
+
model: modelSelect.value,
|
|
209
|
+
effort: effortSelect.value,
|
|
210
|
+
}));
|
|
211
|
+
submit.disabled = true;
|
|
212
|
+
submit.textContent = "Continuing…";
|
|
213
|
+
});
|
|
214
|
+
vendorSelect.focus();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function showMainMenu() {
|
|
218
|
+
closeMenu();
|
|
219
|
+
var el = menuShell("Session actions");
|
|
220
|
+
var addWorker = actionRow("bot", "Add AI Worker", "Open a second agent beside this session.");
|
|
221
|
+
addWorker.addEventListener("click", function () {
|
|
222
|
+
var sessionId = store.get('activeSessionId');
|
|
223
|
+
if (!sessionId) return;
|
|
224
|
+
closeMenu();
|
|
225
|
+
openPairDialog({
|
|
226
|
+
sessionId: sessionId,
|
|
227
|
+
title: document.getElementById("header-title").textContent,
|
|
228
|
+
vendor: store.get('currentVendor') || "claude",
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
var handoff = actionRow("forward", "Continue in another agent", "Carry this conversation into a new session.");
|
|
232
|
+
var unavailable = !store.get('sessionHasHistory') || store.get('sessionIsProcessing');
|
|
233
|
+
handoff.disabled = unavailable;
|
|
234
|
+
handoff.title = unavailable ? "Available after the current turn is complete" : "";
|
|
235
|
+
handoff.addEventListener("click", requestHandoffDialog);
|
|
236
|
+
el.appendChild(addWorker);
|
|
237
|
+
el.appendChild(handoff);
|
|
238
|
+
refreshIcons();
|
|
239
|
+
positionMenu();
|
|
240
|
+
installCloseHandlers();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function updateVisibility() {
|
|
244
|
+
if (!button) return;
|
|
245
|
+
var state = store.snap();
|
|
246
|
+
var hidden = !state.activeSessionId || state.dmMode || state.paneMode || !!state.splitPanes || state.activeSessionMode !== "gui";
|
|
247
|
+
button.classList.toggle("hidden", hidden);
|
|
248
|
+
if (hidden) closeMenu();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function initSessionActions() {
|
|
252
|
+
button = document.getElementById("header-session-actions-btn");
|
|
253
|
+
if (!button) return;
|
|
254
|
+
button.addEventListener("click", function () {
|
|
255
|
+
if (menu) closeMenu();
|
|
256
|
+
else showMainMenu();
|
|
257
|
+
});
|
|
258
|
+
store.subscribe(function (state, prev) {
|
|
259
|
+
if (state.activeSessionId !== prev.activeSessionId || state.dmMode !== prev.dmMode ||
|
|
260
|
+
state.paneMode !== prev.paneMode || state.splitPanes !== prev.splitPanes ||
|
|
261
|
+
state.activeSessionMode !== prev.activeSessionMode) updateVisibility();
|
|
262
|
+
});
|
|
263
|
+
window.addEventListener("resize", closeMenu);
|
|
264
|
+
updateVisibility();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function handleSessionActionMessage(msg) {
|
|
268
|
+
if (msg.type === "handoff_session_options") {
|
|
269
|
+
showHandoffDialog(msg);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
if (msg.type === "session_handoff_result") {
|
|
273
|
+
if (msg.ok) closeHandoffDialog();
|
|
274
|
+
else {
|
|
275
|
+
showToast(msg.error || "Could not continue the session in another agent.", "error");
|
|
276
|
+
if (handoffSubmitButton) {
|
|
277
|
+
handoffSubmitButton.disabled = false;
|
|
278
|
+
handoffSubmitButton.textContent = "Continue";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
if (msg.type === "handoff_context") {
|
|
284
|
+
var sourceName = VENDOR_NAMES[msg.sourceVendor] || msg.sourceVendor || "another agent";
|
|
285
|
+
addSystemMessage("Continued from “" + (msg.sourceTitle || "Untitled session") + "” in " + sourceName + ".", false);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (msg.type === "handoff_created") {
|
|
289
|
+
var targetName = VENDOR_NAMES[msg.targetVendor] || msg.targetVendor || "another agent";
|
|
290
|
+
addSystemMessage("Continued in a new " + targetName + " session.", false);
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
@@ -460,7 +460,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
460
460
|
var vendorLabels = {
|
|
461
461
|
claude: "Claude Code",
|
|
462
462
|
codex: "OpenAI Codex",
|
|
463
|
-
|
|
463
|
+
antigravity: "Antigravity CLI",
|
|
464
464
|
opencode: "OpenCode",
|
|
465
465
|
kiro: "Kiro CLI",
|
|
466
466
|
};
|
|
@@ -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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
getWs().
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
|
1257
|
-
wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (
|
|
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
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1302
|
-
(function (
|
|
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
|
-
|
|
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 (
|
|
1407
|
-
|
|
1408
|
-
wtItem
|
|
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
|
|
303
|
-
//
|
|
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();
|
|
@@ -977,6 +977,9 @@ function renderLoopChild(s) {
|
|
|
977
977
|
if (s.isProcessing) {
|
|
978
978
|
textHtml += '<span class="session-processing"></span>';
|
|
979
979
|
}
|
|
980
|
+
if (s.backgroundTaskCount) {
|
|
981
|
+
textHtml += '<span class="session-background-task-count">' + s.backgroundTaskCount + '</span>';
|
|
982
|
+
}
|
|
980
983
|
if (s.loop) {
|
|
981
984
|
var isRalphChild = s.loop.source === "ralph";
|
|
982
985
|
var roleName = s.loop.role === "crafting" ? "Crafting" : s.loop.role === "judge" ? "Judge" : (isRalphChild ? "Coder" : "Run");
|
|
@@ -1096,6 +1099,9 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
1096
1099
|
if (anyProcessing) {
|
|
1097
1100
|
textHtml += '<span class="session-processing"></span>';
|
|
1098
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>';
|
|
1099
1105
|
var groupIcon = isDebate ? "mic" : (isRalph ? "repeat" : "calendar-clock");
|
|
1100
1106
|
var iconClass = isDebate ? " debate" : (isRalph ? "" : " scheduled");
|
|
1101
1107
|
textHtml += '<span class="session-loop-icon' + iconClass + '">' + iconHtml(groupIcon) + '</span>';
|
|
@@ -1205,6 +1211,9 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
|
|
|
1205
1211
|
if (anyProcessing) {
|
|
1206
1212
|
textHtml += '<span class="session-processing"></span>';
|
|
1207
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>';
|
|
1208
1217
|
textHtml += '<span class="session-loop-run-time">' + escapeHtml(timeLabel) + '</span>';
|
|
1209
1218
|
textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + sessions.length + '</span>';
|
|
1210
1219
|
textSpan.innerHTML = textHtml;
|
|
@@ -1254,6 +1263,9 @@ function renderSessionItem(s) {
|
|
|
1254
1263
|
if (s.isProcessing) {
|
|
1255
1264
|
textHtml += '<span class="session-processing"></span>';
|
|
1256
1265
|
}
|
|
1266
|
+
if (s.backgroundTaskCount) {
|
|
1267
|
+
textHtml += '<span class="session-background-task-count">' + s.backgroundTaskCount + '</span>';
|
|
1268
|
+
}
|
|
1257
1269
|
if (s.loop && s.loop.source === "debate") {
|
|
1258
1270
|
textHtml += '<span class="session-debate-icon" title="Debate">' + iconHtml("mic") + '</span>';
|
|
1259
1271
|
}
|
|
@@ -1322,6 +1334,8 @@ function renderSplitGroupItem(group, members) {
|
|
|
1322
1334
|
}
|
|
1323
1335
|
html += "</span>";
|
|
1324
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>';
|
|
1325
1339
|
html += '<span class="split-group-name">' + highlightMatch(group.name || "Split", searchQuery) + "</span>";
|
|
1326
1340
|
text.innerHTML = html;
|
|
1327
1341
|
el.appendChild(text);
|