clay-server 3.4.0-beta.1 → 3.4.0-beta.11
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 +1 -1
- package/lib/project-connection.js +20 -5
- 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-shell-command.js +160 -0
- package/lib/project-user-message.js +10 -0
- package/lib/project.js +19 -4
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +28 -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 +75 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +48 -14
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pane.css +4 -0
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +38 -7
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/app-header.js +4 -0
- package/lib/public/modules/app-messages.js +28 -5
- package/lib/public/modules/app-panels.js +28 -8
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +19 -4
- package/lib/public/modules/input.js +58 -28
- package/lib/public/modules/mate-sidebar.js +11 -3
- 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/shell-command.js +148 -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 +6 -6
- package/lib/public/modules/split-pair-ui.js +3 -2
- 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/worktree-location.js +17 -0
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +3 -2
- package/lib/sdk-bridge.js +49 -25
- package/lib/sdk-message-processor.js +10 -1
- package/lib/session-notes-mcp-server.js +7 -1
- package/lib/worktree.js +9 -4
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/acp-agent-profiles.js +64 -14
- package/lib/yoke/adapters/antigravity.js +417 -0
- package/lib/yoke/adapters/codex.js +98 -3
- 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/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/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
|
@@ -13,33 +13,48 @@ import { showImageModal, showPasteModal } from './app-misc.js';
|
|
|
13
13
|
import { sendMessage, hasSendableContent } from './input.js';
|
|
14
14
|
import { getChatLayout } from './theme.js';
|
|
15
15
|
import { getScheduledMsgEl } from './app-rate-limit.js';
|
|
16
|
+
export { VENDOR_ORDER, EXPERIMENTAL_VENDORS, isExperimentalVendor } from './vendor-priority.js';
|
|
16
17
|
|
|
17
18
|
// Keep these client constants aligned with lib/yoke/vendor-registry.js. The
|
|
18
19
|
// browser cannot import the server's CommonJS registry directly.
|
|
19
20
|
export var VENDOR_AVATARS = {
|
|
20
21
|
claude: "/claude-code-avatar.png",
|
|
21
22
|
codex: "/codex-avatar.png",
|
|
22
|
-
|
|
23
|
+
antigravity: "/antigravity-avatar.png",
|
|
23
24
|
opencode: "/opencode-avatar.svg",
|
|
25
|
+
kimi: "/kimi-avatar.svg",
|
|
26
|
+
grok: "/grok-avatar.svg",
|
|
27
|
+
copilot: "/copilot-avatar.svg",
|
|
28
|
+
qwen: "/qwen-avatar.svg",
|
|
29
|
+
junie: "/junie-avatar.svg",
|
|
24
30
|
kiro: "/kiro-avatar.svg",
|
|
25
31
|
};
|
|
26
32
|
export var VENDOR_NAMES = {
|
|
27
33
|
claude: "Claude Code",
|
|
28
34
|
codex: "Codex",
|
|
29
|
-
|
|
35
|
+
antigravity: "Antigravity CLI",
|
|
30
36
|
opencode: "OpenCode",
|
|
37
|
+
kimi: "Kimi Code",
|
|
38
|
+
grok: "Grok Build",
|
|
39
|
+
copilot: "GitHub Copilot CLI",
|
|
40
|
+
qwen: "Qwen Code",
|
|
41
|
+
junie: "Junie CLI",
|
|
31
42
|
kiro: "Kiro CLI",
|
|
32
43
|
};
|
|
33
44
|
// Display order for every vendor Clay knows about, installed or not. Pickers
|
|
34
45
|
// render the full list so a missing CLI reads as "not installed yet" rather
|
|
35
46
|
// than "Clay doesn't support it".
|
|
36
|
-
export var VENDOR_ORDER = ["claude", "codex", "gemini", "opencode", "kiro"];
|
|
37
47
|
// Where to send the user when they pick a vendor whose CLI isn't installed.
|
|
38
48
|
export var VENDOR_HOMEPAGES = {
|
|
39
49
|
claude: "https://claude.com/product/claude-code",
|
|
40
50
|
codex: "https://openai.com/codex/",
|
|
41
|
-
|
|
51
|
+
antigravity: "https://antigravity.google/product/antigravity-cli",
|
|
42
52
|
opencode: "https://opencode.ai/",
|
|
53
|
+
kimi: "https://www.kimi.com/code/",
|
|
54
|
+
grok: "https://docs.x.ai/build/overview",
|
|
55
|
+
copilot: "https://github.com/features/copilot/cli",
|
|
56
|
+
qwen: "https://qwenlm.github.io/qwen-code-docs/",
|
|
57
|
+
junie: "https://junie.jetbrains.com/",
|
|
43
58
|
kiro: "https://kiro.dev/",
|
|
44
59
|
};
|
|
45
60
|
var NEW_MSG_BTN_DEFAULT = "\u2193 Latest";
|
|
@@ -7,6 +7,7 @@ import { mateAvatarUrl } from './avatar.js';
|
|
|
7
7
|
import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
|
|
8
8
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
9
9
|
import { showToast } from './utils.js';
|
|
10
|
+
import { isShellCommandMode, submitShellCommand } from './shell-command.js';
|
|
10
11
|
|
|
11
12
|
var ctx;
|
|
12
13
|
|
|
@@ -75,6 +76,37 @@ export var builtinCommands = [
|
|
|
75
76
|
{ name: "status", desc: "Process status and resource usage" },
|
|
76
77
|
];
|
|
77
78
|
|
|
79
|
+
function commitVendorForTurn() {
|
|
80
|
+
var committedVendor = store.get('currentVendor');
|
|
81
|
+
var vendorToggle = document.getElementById("vendor-toggle-wrap");
|
|
82
|
+
var activeIndicator = document.getElementById("active-vendor-indicator");
|
|
83
|
+
var activeIcon = document.getElementById("active-vendor-icon");
|
|
84
|
+
if (committedVendor) {
|
|
85
|
+
if (vendorToggle) {
|
|
86
|
+
vendorToggle.classList.add("hidden");
|
|
87
|
+
vendorToggle.classList.remove("locked");
|
|
88
|
+
}
|
|
89
|
+
if (activeIndicator && activeIcon) {
|
|
90
|
+
activeIcon.src = VENDOR_AVATARS[committedVendor] || VENDOR_AVATARS.claude;
|
|
91
|
+
activeIcon.alt = VENDOR_NAMES[committedVendor] || VENDOR_NAMES.claude;
|
|
92
|
+
activeIndicator.title = (VENDOR_NAMES[committedVendor] || VENDOR_NAMES.claude) + " session";
|
|
93
|
+
activeIndicator.classList.remove("hidden");
|
|
94
|
+
}
|
|
95
|
+
} else if (vendorToggle) {
|
|
96
|
+
vendorToggle.classList.remove("hidden");
|
|
97
|
+
vendorToggle.classList.add("locked");
|
|
98
|
+
}
|
|
99
|
+
store.set({ vendorSelectionLocked: false, sessionHasHistory: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function showPreThinkingForTurn() {
|
|
103
|
+
if (ctx.isMateDm && ctx.isMateDm()) {
|
|
104
|
+
ctx.showMatePreThinking();
|
|
105
|
+
} else if (ctx.showClaudePreThinking) {
|
|
106
|
+
ctx.showClaudePreThinking();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
78
110
|
// --- Send ---
|
|
79
111
|
export function sendMessage() {
|
|
80
112
|
// Debate ended mode intercept: route to resume handler
|
|
@@ -92,6 +124,14 @@ export function sendMessage() {
|
|
|
92
124
|
ctx.handleDebateFloorSend();
|
|
93
125
|
return;
|
|
94
126
|
}
|
|
127
|
+
if (isShellCommandMode()) {
|
|
128
|
+
var shellText = ctx.inputEl.value.trim();
|
|
129
|
+
if (!shellText || !submitShellCommand(shellText)) return;
|
|
130
|
+
ctx.inputEl.value = "";
|
|
131
|
+
sendInputSync();
|
|
132
|
+
autoResize();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
95
135
|
// DM mode intercept: if in DM mode, route to DM handler instead
|
|
96
136
|
if (ctx.isDmMode && ctx.isDmMode() && ctx.handleDmSend) {
|
|
97
137
|
ctx.handleDmSend();
|
|
@@ -304,36 +344,10 @@ export function sendMessage() {
|
|
|
304
344
|
// Bumping sessionHasHistory drives in-session lock states (e.g. the
|
|
305
345
|
// Codex model picker that becomes informational once the thread is
|
|
306
346
|
// bound to a model).
|
|
307
|
-
|
|
308
|
-
var _vtw2 = document.getElementById("vendor-toggle-wrap");
|
|
309
|
-
var _avi = document.getElementById("active-vendor-indicator");
|
|
310
|
-
var _avIcon = document.getElementById("active-vendor-icon");
|
|
311
|
-
if (_committedVendor) {
|
|
312
|
-
if (_vtw2) {
|
|
313
|
-
_vtw2.classList.add("hidden");
|
|
314
|
-
_vtw2.classList.remove("locked");
|
|
315
|
-
}
|
|
316
|
-
if (_avi && _avIcon) {
|
|
317
|
-
_avIcon.src = VENDOR_AVATARS[_committedVendor] || VENDOR_AVATARS.claude;
|
|
318
|
-
_avIcon.alt = VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude;
|
|
319
|
-
_avi.title = (VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude) + " session";
|
|
320
|
-
_avi.classList.remove("hidden");
|
|
321
|
-
}
|
|
322
|
-
} else if (_vtw2) {
|
|
323
|
-
// No committed vendor (defensive — shouldn't happen because the
|
|
324
|
-
// input is otherwise gated on a vendor pick). Fall back to the
|
|
325
|
-
// locked toggle so the running vendor is still visible.
|
|
326
|
-
_vtw2.classList.remove("hidden");
|
|
327
|
-
_vtw2.classList.add("locked");
|
|
328
|
-
}
|
|
329
|
-
store.set({ vendorSelectionLocked: false, sessionHasHistory: true });
|
|
347
|
+
commitVendorForTurn();
|
|
330
348
|
|
|
331
349
|
// Show pre-thinking dots before server responds
|
|
332
|
-
|
|
333
|
-
ctx.showMatePreThinking();
|
|
334
|
-
} else if (ctx.showClaudePreThinking) {
|
|
335
|
-
ctx.showClaudePreThinking();
|
|
336
|
-
}
|
|
350
|
+
showPreThinkingForTurn();
|
|
337
351
|
|
|
338
352
|
ctx.inputEl.value = "";
|
|
339
353
|
sendInputSync();
|
|
@@ -354,6 +368,22 @@ export function sendTextMessage(text) {
|
|
|
354
368
|
return true;
|
|
355
369
|
}
|
|
356
370
|
|
|
371
|
+
export function sendShellResultToAgent(msg) {
|
|
372
|
+
if (!ctx || !ctx.connected || !msg || msg.error || !msg.command) return false;
|
|
373
|
+
if (String(msg.sessionId) !== String(store.get("activeSessionId"))) return false;
|
|
374
|
+
var payload = {
|
|
375
|
+
type: "message",
|
|
376
|
+
text: "$ " + msg.command,
|
|
377
|
+
shellCommandResponse: true,
|
|
378
|
+
};
|
|
379
|
+
var selectedVendor = store.get("currentVendor") || null;
|
|
380
|
+
if (selectedVendor) payload.vendor = selectedVendor;
|
|
381
|
+
ctx.ws.send(JSON.stringify(payload));
|
|
382
|
+
commitVendorForTurn();
|
|
383
|
+
showPreThinkingForTurn();
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
357
387
|
export function autoResize() {
|
|
358
388
|
ctx.inputEl.style.height = "auto";
|
|
359
389
|
ctx.inputEl.style.height = Math.min(ctx.inputEl.scrollHeight, 120) + "px";
|
|
@@ -6,6 +6,7 @@ import { hideKnowledge } from './mate-knowledge.js';
|
|
|
6
6
|
import { isSchedulerOpen, closeScheduler } from './scheduler.js';
|
|
7
7
|
import { hideNotes } from './sticky-notes.js';
|
|
8
8
|
import { openSearch as openSessionSearch } from './session-search.js';
|
|
9
|
+
import { isExperimentalVendor } from './vendor-priority.js';
|
|
9
10
|
|
|
10
11
|
var getWs = null;
|
|
11
12
|
var currentMateId = null;
|
|
@@ -152,19 +153,26 @@ export function showMateSidebar(mateId, mateData) {
|
|
|
152
153
|
var vendorIcons = {
|
|
153
154
|
claude: "/claude-code-avatar.png",
|
|
154
155
|
codex: "/codex-avatar.png",
|
|
155
|
-
|
|
156
|
+
antigravity: "/antigravity-avatar.png",
|
|
156
157
|
opencode: "/opencode-avatar.svg",
|
|
158
|
+
kimi: "/kimi-avatar.svg",
|
|
159
|
+
grok: "/grok-avatar.svg",
|
|
160
|
+
copilot: "/copilot-avatar.svg",
|
|
161
|
+
qwen: "/qwen-avatar.svg",
|
|
162
|
+
junie: "/junie-avatar.svg",
|
|
157
163
|
kiro: "/kiro-avatar.svg",
|
|
158
164
|
};
|
|
159
|
-
var vendorNames = { claude: "Claude Code", codex: "Codex",
|
|
160
|
-
var vendorKeys = ["claude", "codex", "
|
|
165
|
+
var vendorNames = { claude: "Claude Code", codex: "Codex", grok: "Grok Build", kimi: "Kimi Code", copilot: "GitHub Copilot CLI", qwen: "Qwen Code", junie: "Junie CLI", antigravity: "Antigravity CLI", opencode: "OpenCode", kiro: "Kiro CLI" };
|
|
166
|
+
var vendorKeys = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
|
|
161
167
|
mateVendorWrap.innerHTML = "";
|
|
162
168
|
for (var vi = 0; vi < vendorKeys.length; vi++) {
|
|
163
169
|
var vk = vendorKeys[vi];
|
|
164
170
|
var vBtn = document.createElement("button");
|
|
165
171
|
vBtn.className = "mate-vendor-btn" + (vk === mateVendor ? " active" : "");
|
|
172
|
+
if (isExperimentalVendor(vk)) vBtn.classList.add("experimental");
|
|
166
173
|
if (available.indexOf(vk) === -1) vBtn.classList.add("disabled");
|
|
167
174
|
vBtn.dataset.vendor = vk;
|
|
175
|
+
vBtn.title = vendorNames[vk] + (isExperimentalVendor(vk) ? " · Experimental integration; not yet validated through direct use testing" : "");
|
|
168
176
|
vBtn.innerHTML = '<img src="' + vendorIcons[vk] + '" class="mate-vendor-icon" alt="' + vendorNames[vk] + '"><span class="mate-vendor-label">' + vendorNames[vk] + '</span>';
|
|
169
177
|
vBtn.addEventListener("click", function() {
|
|
170
178
|
var v = this.dataset.vendor;
|
|
@@ -72,6 +72,7 @@ export function initNotifications(_ctx) {
|
|
|
72
72
|
if (window.visualViewport) {
|
|
73
73
|
var layout = $("layout");
|
|
74
74
|
var mobileTabBar = document.getElementById("mobile-tab-bar");
|
|
75
|
+
var keyboardWasOpen = false;
|
|
75
76
|
// Capture initial viewport height before any keyboard interaction.
|
|
76
77
|
// In iOS PWA standalone mode, window.innerHeight shrinks when the
|
|
77
78
|
// keyboard opens, so we need a stable baseline for comparison.
|
|
@@ -86,7 +87,12 @@ export function initNotifications(_ctx) {
|
|
|
86
87
|
// Toggle class so CSS can remove the tab-bar bottom padding while keyboard is up
|
|
87
88
|
var keyboardOpen = vv.height < stableViewportHeight - 100;
|
|
88
89
|
document.body.classList.toggle("keyboard-open", keyboardOpen);
|
|
89
|
-
if (
|
|
90
|
+
if (keyboardOpen && !keyboardWasOpen && ctx.forceScrollToBottom) {
|
|
91
|
+
requestAnimationFrame(function() { ctx.forceScrollToBottom(); });
|
|
92
|
+
} else if (!keyboardOpen) {
|
|
93
|
+
ctx.scrollToBottom();
|
|
94
|
+
}
|
|
95
|
+
keyboardWasOpen = keyboardOpen;
|
|
90
96
|
// Hide tab bar when software keyboard is open
|
|
91
97
|
if (mobileTabBar) {
|
|
92
98
|
if (keyboardOpen) {
|
|
@@ -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,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
|
+
}
|
|
@@ -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
|
}
|