clay-server 3.4.0-beta.6 → 3.4.0-beta.8
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/public/app.js +1 -0
- package/lib/public/css/menus.css +48 -14
- package/lib/public/css/pane.css +4 -0
- package/lib/public/index.html +8 -2
- package/lib/public/modules/app-messages.js +12 -4
- package/lib/public/modules/app-panels.js +4 -2
- package/lib/public/modules/app-rendering.js +1 -1
- package/lib/public/modules/pane-bridge.js +11 -0
- package/lib/public/modules/pane-links.js +23 -0
- package/lib/public/modules/sidebar-sessions.js +3 -5
- package/lib/public/modules/vendor-priority.js +9 -0
- package/lib/public/modules/vendor-selection.js +20 -0
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -295,6 +295,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
295
295
|
slashCommands: [],
|
|
296
296
|
processing: false,
|
|
297
297
|
activeSessionId: null,
|
|
298
|
+
sessionVendorBound: false,
|
|
298
299
|
cliSessionId: null,
|
|
299
300
|
isHeadlessMode: false,
|
|
300
301
|
savedMainSlug: null,
|
package/lib/public/css/menus.css
CHANGED
|
@@ -44,29 +44,64 @@
|
|
|
44
44
|
#header-rename-btn .lucide { width: 13px; height: 13px; }
|
|
45
45
|
|
|
46
46
|
#header-add-worker-btn {
|
|
47
|
-
display: flex;
|
|
47
|
+
display: inline-flex;
|
|
48
48
|
align-items: center;
|
|
49
49
|
justify-content: center;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
gap: 7px;
|
|
51
|
+
width: auto;
|
|
52
|
+
height: 28px;
|
|
53
|
+
border: 1px solid color-mix(in srgb, var(--accent2) 28%, var(--border));
|
|
54
|
+
background: var(--accent2-8);
|
|
55
|
+
color: var(--accent2);
|
|
55
56
|
cursor: pointer;
|
|
56
57
|
border-radius: 8px;
|
|
57
58
|
flex-shrink: 0;
|
|
58
|
-
padding: 0;
|
|
59
|
-
margin-left:
|
|
59
|
+
padding: 0 9px 0 7px;
|
|
60
|
+
margin-left: 0;
|
|
61
|
+
font-family: inherit;
|
|
62
|
+
font-size: 11px;
|
|
63
|
+
font-weight: 700;
|
|
64
|
+
line-height: 1;
|
|
65
|
+
white-space: nowrap;
|
|
60
66
|
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
61
67
|
}
|
|
62
|
-
#header-add-worker-btn:hover {
|
|
63
|
-
|
|
68
|
+
#header-add-worker-btn:hover {
|
|
69
|
+
color: var(--accent2-hover);
|
|
70
|
+
background: var(--accent2-15);
|
|
71
|
+
border-color: color-mix(in srgb, var(--accent2) 48%, var(--border));
|
|
72
|
+
}
|
|
73
|
+
.header-worker-icon {
|
|
74
|
+
position: relative;
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
width: 16px;
|
|
79
|
+
height: 16px;
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
}
|
|
82
|
+
.header-worker-icon > .lucide { width: 15px; height: 15px; }
|
|
83
|
+
.header-worker-add-badge {
|
|
84
|
+
position: absolute;
|
|
85
|
+
right: -4px;
|
|
86
|
+
bottom: -3px;
|
|
87
|
+
display: inline-flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
width: 9px;
|
|
91
|
+
height: 9px;
|
|
92
|
+
border-radius: 50%;
|
|
93
|
+
background: var(--accent2);
|
|
94
|
+
color: var(--bg);
|
|
95
|
+
box-shadow: 0 0 0 2px var(--bg);
|
|
96
|
+
}
|
|
97
|
+
.header-worker-add-badge .lucide { width: 7px; height: 7px; stroke-width: 3; }
|
|
98
|
+
.header-worker-label { letter-spacing: 0.01em; }
|
|
64
99
|
#header-add-worker-btn.hidden { display: none; }
|
|
65
100
|
body.dm-mode #header-add-worker-btn { display: none; }
|
|
66
101
|
|
|
67
|
-
/* "Close terminal" button
|
|
68
|
-
|
|
69
|
-
|
|
102
|
+
/* "Close terminal" button for a live TUI session. Matches the session-control
|
|
103
|
+
chrome; reds out on hover since it stops the running agent while preserving
|
|
104
|
+
the resumable session. */
|
|
70
105
|
#header-tui-close-btn {
|
|
71
106
|
display: flex;
|
|
72
107
|
align-items: center;
|
|
@@ -980,4 +1015,3 @@ body.dm-mode #header-add-worker-btn { display: none; }
|
|
|
980
1015
|
#config-chip .config-chip-icon { display: inline; width: 14px; height: 14px; }
|
|
981
1016
|
#config-chip-label { display: none; }
|
|
982
1017
|
}
|
|
983
|
-
|
package/lib/public/css/pane.css
CHANGED
package/lib/public/index.html
CHANGED
|
@@ -373,12 +373,11 @@
|
|
|
373
373
|
</div>
|
|
374
374
|
<button id="hamburger-btn" aria-label="Toggle sidebar" title="Toggle sidebar"><i data-lucide="menu"></i></button>
|
|
375
375
|
<span class="header-title" id="header-title">Connecting...</span>
|
|
376
|
+
<button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
|
|
376
377
|
<button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
|
|
377
378
|
<button id="header-full-access-btn" type="button" class="session-full-access hidden" role="switch" aria-checked="false" title="Skip Permissions is off — permission prompts are on">
|
|
378
379
|
<span class="session-full-access-label">Skip Permissions</span><span class="session-full-access-track" aria-hidden="true"><span></span></span>
|
|
379
380
|
</button>
|
|
380
|
-
<button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
|
|
381
|
-
<button id="header-add-worker-btn" type="button" title="Add Worker — this session becomes the Driver of a pair"><i data-lucide="user-plus"></i></button>
|
|
382
381
|
<button type="button" id="header-tui-close-btn" class="header-tui-close-btn hidden" title="Close terminal (keeps history, resume anytime)" aria-label="Close terminal"><i data-lucide="power"></i></button>
|
|
383
382
|
<div id="mate-mobile-title" class="mate-mobile-title hidden">
|
|
384
383
|
<img id="mate-mobile-avatar" class="mate-mobile-avatar" alt="">
|
|
@@ -389,6 +388,13 @@
|
|
|
389
388
|
<div id="ralph-sticky" class="hidden"></div>
|
|
390
389
|
<div id="debate-sticky" class="hidden"></div>
|
|
391
390
|
<div class="status">
|
|
391
|
+
<button id="header-add-worker-btn" type="button" aria-label="Add AI Worker" title="Add an AI Worker — this session becomes the Driver of a pair">
|
|
392
|
+
<span class="header-worker-icon" aria-hidden="true">
|
|
393
|
+
<i data-lucide="bot"></i>
|
|
394
|
+
<span class="header-worker-add-badge"><i data-lucide="plus"></i></span>
|
|
395
|
+
</span>
|
|
396
|
+
<span class="header-worker-label">Add Worker</span>
|
|
397
|
+
</button>
|
|
392
398
|
<button type="button" id="header-tui-font-btn" class="header-tui-font-btn hidden" title="Terminal font" aria-label="Terminal font">
|
|
393
399
|
<i data-lucide="type"></i>
|
|
394
400
|
</button>
|
|
@@ -52,6 +52,7 @@ import { setStatus } from './app-connection.js';
|
|
|
52
52
|
import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
|
|
53
53
|
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
54
54
|
import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
|
|
55
|
+
import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
|
|
55
56
|
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
|
|
56
57
|
import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
|
|
57
58
|
import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
|
|
@@ -414,6 +415,15 @@ export function processMessage(msg) {
|
|
|
414
415
|
break;
|
|
415
416
|
|
|
416
417
|
case "model_info": {
|
|
418
|
+
// Installation state applies across vendors. Keep it even when the
|
|
419
|
+
// model payload itself is stale for the active session.
|
|
420
|
+
var _vendorState = {};
|
|
421
|
+
if (msg.availableVendors) _vendorState.availableVendors = msg.availableVendors;
|
|
422
|
+
if (msg.installedVendors) _vendorState.installedVendors = msg.installedVendors;
|
|
423
|
+
if (Object.keys(_vendorState).length > 0) {
|
|
424
|
+
store.set(_vendorState);
|
|
425
|
+
selectDefaultVendorForBlankSession();
|
|
426
|
+
}
|
|
417
427
|
// Drop stale model_info from a vendor that doesn't match the active
|
|
418
428
|
// session's vendor. On high-latency connections, the server's default-
|
|
419
429
|
// adapter model_info can arrive after session_switched has already
|
|
@@ -447,8 +457,6 @@ export function processMessage(msg) {
|
|
|
447
457
|
_miUpdate.currentModel = store.get('currentModel');
|
|
448
458
|
}
|
|
449
459
|
if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
|
|
450
|
-
if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
|
|
451
|
-
if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
|
|
452
460
|
if (msg.capabilities) _miUpdate.vendorCapabilities = msg.capabilities;
|
|
453
461
|
store.set(_miUpdate);
|
|
454
462
|
updateSettingsModels(_modelVal, msg.models || []);
|
|
@@ -661,7 +669,7 @@ export function processMessage(msg) {
|
|
|
661
669
|
var _effectiveTerminalId = (typeof msg.runtimeTerminalId === "number")
|
|
662
670
|
? msg.runtimeTerminalId
|
|
663
671
|
: (typeof msg.terminalId === "number" ? msg.terminalId : null);
|
|
664
|
-
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || store.get('currentModel'), currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
|
|
672
|
+
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || store.get('currentModel'), currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
|
|
665
673
|
// TUI sessions swap the chat UI for an embedded xterm running
|
|
666
674
|
// `claude` inside a real PTY. Mount or tear down before the rest of
|
|
667
675
|
// the chat-side bookkeeping runs so we don't waste work on hidden DOM.
|
|
@@ -706,7 +714,7 @@ export function processMessage(msg) {
|
|
|
706
714
|
}
|
|
707
715
|
}
|
|
708
716
|
if (!msg.hasHistory && !msg.vendor) {
|
|
709
|
-
|
|
717
|
+
selectDefaultVendorForBlankSession();
|
|
710
718
|
}
|
|
711
719
|
// Vendor toggle visibility + active-vendor indicator next to the
|
|
712
720
|
// model chip.
|
|
@@ -474,7 +474,7 @@ export function initPanels() {
|
|
|
474
474
|
if (vendor === (store.get('currentVendor') || "claude")) return;
|
|
475
475
|
var installed = store.get('installedVendors') || [];
|
|
476
476
|
if (installed.indexOf(vendor) === -1) return;
|
|
477
|
-
store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorSelectionLocked: true });
|
|
477
|
+
store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorSelectionLocked: true, sessionVendorBound: true });
|
|
478
478
|
var ws = getWs();
|
|
479
479
|
if (ws) ws.send(JSON.stringify({ type: "set_vendor", vendor: vendor }));
|
|
480
480
|
}
|
|
@@ -861,7 +861,9 @@ export function updateContextPanel() {
|
|
|
861
861
|
hCtxEl = document.createElement("div");
|
|
862
862
|
hCtxEl.className = "header-context";
|
|
863
863
|
hCtxEl.innerHTML = '<div class="header-context-bar"><div class="header-context-fill"></div></div><span class="header-context-label"></span>';
|
|
864
|
-
|
|
864
|
+
var workerBtn = document.getElementById("header-add-worker-btn");
|
|
865
|
+
var contextAnchor = workerBtn && workerBtn.parentNode === statusArea ? workerBtn.nextSibling : statusArea.firstChild;
|
|
866
|
+
statusArea.insertBefore(hCtxEl, contextAnchor);
|
|
865
867
|
hCtxEl.addEventListener("mouseenter", function() {
|
|
866
868
|
if (store.get('richContextUsage')) {
|
|
867
869
|
showCtxPopover();
|
|
@@ -13,6 +13,7 @@ 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 } 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.
|
|
@@ -33,7 +34,6 @@ export var VENDOR_NAMES = {
|
|
|
33
34
|
// Display order for every vendor Clay knows about, installed or not. Pickers
|
|
34
35
|
// render the full list so a missing CLI reads as "not installed yet" rather
|
|
35
36
|
// than "Clay doesn't support it".
|
|
36
|
-
export var VENDOR_ORDER = ["claude", "codex", "antigravity", "opencode", "kiro"];
|
|
37
37
|
// Where to send the user when they pick a vendor whose CLI isn't installed.
|
|
38
38
|
export var VENDOR_HOMEPAGES = {
|
|
39
39
|
claude: "https://claude.com/product/claude-code",
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export var VENDOR_ORDER = ["claude", "codex", "antigravity", "opencode", "kiro"];
|
|
2
|
+
|
|
3
|
+
export function firstInstalledVendor(installedVendors) {
|
|
4
|
+
var installed = Array.isArray(installedVendors) ? installedVendors : [];
|
|
5
|
+
for (var i = 0; i < VENDOR_ORDER.length; i++) {
|
|
6
|
+
if (installed.indexOf(VENDOR_ORDER[i]) !== -1) return VENDOR_ORDER[i];
|
|
7
|
+
}
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { firstInstalledVendor } from './vendor-priority.js';
|
|
3
|
+
|
|
4
|
+
export function selectDefaultVendorForBlankSession() {
|
|
5
|
+
var state = store.snap();
|
|
6
|
+
if (!state.activeSessionId || state.sessionHasHistory || state.sessionVendorBound || state.dmMode) return "";
|
|
7
|
+
var vendor = firstInstalledVendor(state.installedVendors);
|
|
8
|
+
if (!vendor) return "";
|
|
9
|
+
if (vendor === state.currentVendor) {
|
|
10
|
+
if (state.vendorSelectionLocked) store.set({ vendorSelectionLocked: false });
|
|
11
|
+
return vendor;
|
|
12
|
+
}
|
|
13
|
+
store.set({
|
|
14
|
+
currentVendor: vendor,
|
|
15
|
+
currentModel: "",
|
|
16
|
+
currentModels: [],
|
|
17
|
+
vendorSelectionLocked: false,
|
|
18
|
+
});
|
|
19
|
+
return vendor;
|
|
20
|
+
}
|
package/package.json
CHANGED