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.
- package/lib/daemon-projects.js +3 -3
- package/lib/daemon.js +59 -59
- package/lib/project-connection.js +19 -6
- 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 +35 -79
- package/lib/public/app.js +11 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/command-palette.css +22 -2
- package/lib/public/css/filebrowser.css +6 -1
- 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 +35 -56
- package/lib/public/css/pane.css +18 -3
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +26 -9
- 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-connection.js +6 -0
- package/lib/public/modules/app-header.js +0 -22
- package/lib/public/modules/app-messages.js +48 -9
- package/lib/public/modules/app-panels.js +33 -82
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +16 -1
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/filebrowser-tabs.js +65 -0
- package/lib/public/modules/filebrowser.js +23 -3
- package/lib/public/modules/mate-sidebar.js +10 -2
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/pane-bridge.js +6 -0
- package/lib/public/modules/project-switcher.js +7 -6
- package/lib/public/modules/session-actions.js +294 -0
- 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 +17 -1
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +11 -1
- package/lib/public/modules/tools.js +5 -0
- package/lib/public/modules/vendor-priority.js +6 -1
- 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 +1 -0
- package/lib/sdk-bridge.js +57 -34
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +110 -0
- package/lib/session-notes-mcp-server.js +1 -0
- 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 -0
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +38 -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/kiro.js +4 -3
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-background-tasks.js +84 -0
- package/lib/yoke/index.js +43 -14
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +21 -0
- package/lib/yoke/vendor-registry.js +55 -0
- package/package.json +1 -1
|
@@ -21,12 +21,14 @@ import { handlePaletteSessionSwitch, setPaletteVersion } from './command-palette
|
|
|
21
21
|
import { handleFindInSessionResults } from './session-search.js';
|
|
22
22
|
import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
|
|
23
23
|
import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
|
|
24
|
+
import { handleSessionActionMessage } from './session-actions.js';
|
|
24
25
|
import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
|
|
25
26
|
import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent } from './input.js';
|
|
26
27
|
import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
|
|
27
28
|
import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
|
|
28
29
|
import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch, presentMarkdownEdit } from './filebrowser.js';
|
|
29
30
|
import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
|
|
31
|
+
import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
|
|
30
32
|
import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
|
|
31
33
|
import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
|
|
32
34
|
import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
|
|
@@ -53,6 +55,7 @@ import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWh
|
|
|
53
55
|
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
54
56
|
import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
|
|
55
57
|
import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
|
|
58
|
+
import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
|
|
56
59
|
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
|
|
57
60
|
import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
|
|
58
61
|
import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
|
|
@@ -431,12 +434,12 @@ export function processMessage(msg) {
|
|
|
431
434
|
// currentModels with the wrong vendor's list and trigger app-panels
|
|
432
435
|
// to request models for the "wrong" vendor, which feeds back into a
|
|
433
436
|
// ping-pong loop of vendor flapping. See issue #336.
|
|
434
|
-
var
|
|
435
|
-
if (
|
|
437
|
+
var _modelLoadUpdate = getModelInfoUpdate(msg);
|
|
438
|
+
if (!_modelLoadUpdate) break;
|
|
436
439
|
|
|
437
440
|
var _modelVal = msg.model;
|
|
438
441
|
if (_modelVal && typeof _modelVal === "object") _modelVal = _modelVal.value || _modelVal.displayName || "";
|
|
439
|
-
var _miUpdate = { currentModels: msg.models || [] };
|
|
442
|
+
var _miUpdate = Object.assign({ currentModels: msg.models || [] }, _modelLoadUpdate);
|
|
440
443
|
if (Object.prototype.hasOwnProperty.call(msg, "model")) {
|
|
441
444
|
// Keep the user's existing selection only if it actually belongs to
|
|
442
445
|
// this vendor's model list. Otherwise (e.g. switching to a Codex
|
|
@@ -445,10 +448,15 @@ export function processMessage(msg) {
|
|
|
445
448
|
// model.
|
|
446
449
|
var _curModel = store.get('currentModel');
|
|
447
450
|
var _curInList = !!_curModel && (msg.models || []).some(function (m) {
|
|
448
|
-
|
|
449
|
-
return v === _curModel;
|
|
451
|
+
return modelEntryMatches(m, _curModel);
|
|
450
452
|
});
|
|
451
|
-
|
|
453
|
+
var _incomingInList = !_modelVal || (msg.models || []).length === 0 || (msg.models || []).some(function (m) {
|
|
454
|
+
return modelEntryMatches(m, _modelVal);
|
|
455
|
+
});
|
|
456
|
+
if (!_incomingInList) {
|
|
457
|
+
console.warn("[model-state] Ignoring model not listed for vendor " + (msg.vendor || store.get('currentVendor')) + ": " + _modelVal);
|
|
458
|
+
_miUpdate.currentModel = _curInList ? _curModel : modelEntryValue((msg.models || [])[0]);
|
|
459
|
+
} else if (store.get('vendorSelectionLocked') && _curInList) {
|
|
452
460
|
// Keep the user's existing (valid) selection; only update models list
|
|
453
461
|
} else {
|
|
454
462
|
_miUpdate.currentModel = _modelVal || "";
|
|
@@ -463,9 +471,25 @@ export function processMessage(msg) {
|
|
|
463
471
|
break;
|
|
464
472
|
}
|
|
465
473
|
|
|
474
|
+
case "model_selection_result":
|
|
475
|
+
handleModelSelectionResult(msg);
|
|
476
|
+
break;
|
|
477
|
+
|
|
466
478
|
case "config_state": {
|
|
479
|
+
if ((msg.sessionId != null && msg.sessionId !== store.get('activeSessionId')) || (msg.vendor && msg.vendor !== store.get('currentVendor'))) break;
|
|
467
480
|
var _cs = {};
|
|
468
|
-
if (msg.model)
|
|
481
|
+
if (msg.model) {
|
|
482
|
+
var _knownModels = store.get('currentModels') || [];
|
|
483
|
+
var _modelKnown = _knownModels.length === 0 || _knownModels.some(function(model) { return modelEntryMatches(model, msg.model); });
|
|
484
|
+
if (_modelKnown) {
|
|
485
|
+
_cs.currentModel = msg.model;
|
|
486
|
+
} else {
|
|
487
|
+
console.warn("[model-state] Ignoring model not listed for current vendor: " + msg.model);
|
|
488
|
+
var _existingModel = store.get('currentModel');
|
|
489
|
+
var _existingKnown = _knownModels.some(function(model) { return modelEntryMatches(model, _existingModel); });
|
|
490
|
+
_cs.currentModel = _existingKnown ? _existingModel : modelEntryValue(_knownModels[0]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
469
493
|
if (msg.mode) _cs.currentMode = msg.mode;
|
|
470
494
|
if (msg.effort) _cs.currentEffort = msg.effort;
|
|
471
495
|
if (msg.betas) _cs.currentBetas = msg.betas;
|
|
@@ -597,6 +621,13 @@ export function processMessage(msg) {
|
|
|
597
621
|
if (createdPairGroup) openGroup(createdPairGroup);
|
|
598
622
|
break;
|
|
599
623
|
|
|
624
|
+
case "session_handoff_result":
|
|
625
|
+
case "handoff_session_options":
|
|
626
|
+
case "handoff_context":
|
|
627
|
+
case "handoff_created":
|
|
628
|
+
handleSessionActionMessage(msg);
|
|
629
|
+
break;
|
|
630
|
+
|
|
600
631
|
case "worker_proposal":
|
|
601
632
|
renderWorkerProposal(msg);
|
|
602
633
|
break;
|
|
@@ -669,7 +700,7 @@ export function processMessage(msg) {
|
|
|
669
700
|
var _effectiveTerminalId = (typeof msg.runtimeTerminalId === "number")
|
|
670
701
|
? msg.runtimeTerminalId
|
|
671
702
|
: (typeof msg.terminalId === "number" ? msg.terminalId : null);
|
|
672
|
-
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model ||
|
|
703
|
+
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", 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') });
|
|
673
704
|
// TUI sessions swap the chat UI for an embedded xterm running
|
|
674
705
|
// `claude` inside a real PTY. Mount or tear down before the rest of
|
|
675
706
|
// the chat-side bookkeeping runs so we don't waste work on hidden DOM.
|
|
@@ -716,6 +747,7 @@ export function processMessage(msg) {
|
|
|
716
747
|
if (!msg.hasHistory && !msg.vendor) {
|
|
717
748
|
selectDefaultVendorForBlankSession();
|
|
718
749
|
}
|
|
750
|
+
if (!msg.model) requestVendorModels(store.get('currentVendor') || "claude", true);
|
|
719
751
|
// Vendor toggle visibility + active-vendor indicator next to the
|
|
720
752
|
// model chip.
|
|
721
753
|
// - Session has an explicit vendor: hide the toggle (it's
|
|
@@ -1116,6 +1148,10 @@ export function processMessage(msg) {
|
|
|
1116
1148
|
updateSubagentTaskStatus(msg.parentToolId, msg.patch);
|
|
1117
1149
|
break;
|
|
1118
1150
|
|
|
1151
|
+
case "active_background_tasks":
|
|
1152
|
+
store.set({ activeBackgroundTasks: Array.isArray(msg.tasks) ? msg.tasks : [] });
|
|
1153
|
+
break;
|
|
1154
|
+
|
|
1119
1155
|
case "result":
|
|
1120
1156
|
// Result marks turn end. Drop pre-thinking even if no delta/tool
|
|
1121
1157
|
// event ever arrived (e.g. tool-only turn whose progress signals
|
|
@@ -1365,7 +1401,10 @@ export function processMessage(msg) {
|
|
|
1365
1401
|
break;
|
|
1366
1402
|
|
|
1367
1403
|
case "markdown_edit_present":
|
|
1368
|
-
if (!store.get('replayingHistory') && !store.get('dmMode'))
|
|
1404
|
+
if (!store.get('replayingHistory') && !store.get('dmMode')) {
|
|
1405
|
+
if (store.get('paneMode')) forwardPaneMarkdownPresentation(msg);
|
|
1406
|
+
else if (!store.get('splitPanes')) presentMarkdownEdit(msg);
|
|
1407
|
+
}
|
|
1369
1408
|
break;
|
|
1370
1409
|
|
|
1371
1410
|
case "fs_dir_changed":
|
|
@@ -5,8 +5,9 @@ import { refreshIcons } from "./icons.js";
|
|
|
5
5
|
import { escapeHtml, showToast } from "./utils.js";
|
|
6
6
|
import { store } from './store.js';
|
|
7
7
|
import { getWs } from './ws-ref.js';
|
|
8
|
-
import { VENDOR_NAMES } from './app-rendering.js';
|
|
8
|
+
import { VENDOR_NAMES, isExperimentalVendor } from './app-rendering.js';
|
|
9
9
|
import { reportPaneContext } from './pane-bridge.js';
|
|
10
|
+
import { setupModelPicker, renderModelPicker, requestVendorModels, prepareModelPickerOpen, modelDisplayName } from './model-picker.js';
|
|
10
11
|
|
|
11
12
|
// --- Module-owned state (not in store) ---
|
|
12
13
|
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
@@ -20,7 +21,6 @@ var configChipWrap = null;
|
|
|
20
21
|
var configChip = null;
|
|
21
22
|
var configChipLabel = null;
|
|
22
23
|
var configPopover = null;
|
|
23
|
-
var configModelList = null;
|
|
24
24
|
var configModeList = null;
|
|
25
25
|
var configEffortSection = null;
|
|
26
26
|
var configEffortBar = null;
|
|
@@ -132,18 +132,6 @@ export function setContextData(v) { contextData = v; }
|
|
|
132
132
|
|
|
133
133
|
// --- Internal helpers ---
|
|
134
134
|
|
|
135
|
-
function modelDisplayName(value, models) {
|
|
136
|
-
if (!value) return "";
|
|
137
|
-
if (models) {
|
|
138
|
-
for (var i = 0; i < models.length; i++) {
|
|
139
|
-
var m = models[i];
|
|
140
|
-
if (typeof m === "string") { if (m === value) return m; }
|
|
141
|
-
else if (m.value === value && m.displayName) return m.displayName;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return value;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
135
|
function modeDisplayName(value) {
|
|
148
136
|
for (var i = 0; i < MODE_OPTIONS.length; i++) {
|
|
149
137
|
if (MODE_OPTIONS[i].value === value) return MODE_OPTIONS[i].label;
|
|
@@ -198,65 +186,6 @@ function hasBeta(name) {
|
|
|
198
186
|
return false;
|
|
199
187
|
}
|
|
200
188
|
|
|
201
|
-
function rebuildModelList() {
|
|
202
|
-
if (!configModelList) return;
|
|
203
|
-
// Picker lock policy is capability-based (yoke midSessionModelSwitch):
|
|
204
|
-
// Claude -> free. The SDK supports mid-thread swaps on a live query
|
|
205
|
-
// (query.setModel: "Change the model used for subsequent
|
|
206
|
-
// responses"), and sdk-bridge already routes set_model to
|
|
207
|
-
// queryInstance.setModel. Cache re-priming on switch is a
|
|
208
|
-
// cost, not a breakage.
|
|
209
|
-
// Codex / Kiro -> locked once the session has history: their threads
|
|
210
|
-
// bind the model at creation and a mid-thread change would be
|
|
211
|
-
// silently ignored. Picks made before the first message still
|
|
212
|
-
// work everywhere (sdk-bridge stores into sm.currentModel when
|
|
213
|
-
// there's no active queryInstance).
|
|
214
|
-
var modelSection = configModelList.parentElement;
|
|
215
|
-
var s = store.snap();
|
|
216
|
-
var vendor = s.currentVendor || "claude";
|
|
217
|
-
if (modelSection) modelSection.style.display = "";
|
|
218
|
-
configModelList.innerHTML = "";
|
|
219
|
-
|
|
220
|
-
var caps = s.vendorCapabilities || {};
|
|
221
|
-
var lockedForGui = s.activeSessionMode === "gui" && !!s.sessionHasHistory && !caps.midSessionModelSwitch;
|
|
222
|
-
|
|
223
|
-
var list = s.currentModels.length > 0 ? s.currentModels : (s.currentModel ? [{ value: s.currentModel, displayName: s.currentModel }] : []);
|
|
224
|
-
for (var i = 0; i < list.length; i++) {
|
|
225
|
-
var item = list[i];
|
|
226
|
-
// Support both object { value, displayName } and plain string formats
|
|
227
|
-
var value = typeof item === "string" ? item : (item.value || "");
|
|
228
|
-
var label = typeof item === "string" ? item : (item.displayName || value);
|
|
229
|
-
var btn = document.createElement("button");
|
|
230
|
-
btn.className = "config-radio-item";
|
|
231
|
-
if (value === s.currentModel) btn.classList.add("active");
|
|
232
|
-
if (lockedForGui) btn.classList.add("locked");
|
|
233
|
-
btn.dataset.model = value;
|
|
234
|
-
btn.textContent = label;
|
|
235
|
-
if (lockedForGui) {
|
|
236
|
-
btn.disabled = true;
|
|
237
|
-
btn.title = "This vendor binds the model when the session starts. Start a new session to change it.";
|
|
238
|
-
} else {
|
|
239
|
-
btn.addEventListener("click", function () {
|
|
240
|
-
var model = this.dataset.model;
|
|
241
|
-
var ws = getWs();
|
|
242
|
-
if (ws && ws.readyState === 1) {
|
|
243
|
-
ws.send(JSON.stringify({ type: "set_model", model: model }));
|
|
244
|
-
}
|
|
245
|
-
configPopover.classList.add("hidden");
|
|
246
|
-
configChip.classList.remove("active");
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
configModelList.appendChild(btn);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (lockedForGui) {
|
|
253
|
-
var hint = document.createElement("div");
|
|
254
|
-
hint.className = "config-model-hint";
|
|
255
|
-
hint.textContent = "Locked after first message. Start a new session to change models.";
|
|
256
|
-
configModelList.appendChild(hint);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
189
|
function rebuildModeList() {
|
|
261
190
|
if (!configModeList) return;
|
|
262
191
|
configModeList.innerHTML = "";
|
|
@@ -422,7 +351,7 @@ export function initPanels() {
|
|
|
422
351
|
configChip = $("config-chip");
|
|
423
352
|
configChipLabel = $("config-chip-label");
|
|
424
353
|
configPopover = $("config-popover");
|
|
425
|
-
|
|
354
|
+
setupModelPicker();
|
|
426
355
|
configModeList = $("config-mode-list");
|
|
427
356
|
configEffortSection = $("config-effort-section");
|
|
428
357
|
configEffortBar = $("config-effort-bar");
|
|
@@ -445,12 +374,22 @@ export function initPanels() {
|
|
|
445
374
|
var vendorBtnCodex = $("vendor-btn-codex");
|
|
446
375
|
var vendorBtnAntigravity = $("vendor-btn-antigravity");
|
|
447
376
|
var vendorBtnOpenCode = $("vendor-btn-opencode");
|
|
377
|
+
var vendorBtnKimi = $("vendor-btn-kimi");
|
|
378
|
+
var vendorBtnGrok = $("vendor-btn-grok");
|
|
379
|
+
var vendorBtnCopilot = $("vendor-btn-copilot");
|
|
380
|
+
var vendorBtnQwen = $("vendor-btn-qwen");
|
|
381
|
+
var vendorBtnJunie = $("vendor-btn-junie");
|
|
448
382
|
var vendorBtnKiro = $("vendor-btn-kiro");
|
|
449
383
|
var vendorBtns = {
|
|
450
384
|
claude: vendorBtnClaude,
|
|
451
385
|
codex: vendorBtnCodex,
|
|
452
386
|
antigravity: vendorBtnAntigravity,
|
|
453
387
|
opencode: vendorBtnOpenCode,
|
|
388
|
+
kimi: vendorBtnKimi,
|
|
389
|
+
grok: vendorBtnGrok,
|
|
390
|
+
copilot: vendorBtnCopilot,
|
|
391
|
+
qwen: vendorBtnQwen,
|
|
392
|
+
junie: vendorBtnJunie,
|
|
454
393
|
kiro: vendorBtnKiro,
|
|
455
394
|
};
|
|
456
395
|
|
|
@@ -466,7 +405,10 @@ export function initPanels() {
|
|
|
466
405
|
var isInstalled = installed.indexOf(v) !== -1;
|
|
467
406
|
btn.classList.toggle("active", v === current);
|
|
468
407
|
btn.classList.toggle("disabled", !isInstalled);
|
|
469
|
-
btn.
|
|
408
|
+
btn.classList.toggle("experimental", isExperimentalVendor(v));
|
|
409
|
+
var title = isInstalled ? (VENDOR_NAMES[v] || v) : (VENDOR_NAMES[v] || v) + " is not installed";
|
|
410
|
+
if (isExperimentalVendor(v)) title += " · Experimental integration; not yet validated through direct use testing";
|
|
411
|
+
btn.title = title;
|
|
470
412
|
}
|
|
471
413
|
}
|
|
472
414
|
|
|
@@ -474,7 +416,7 @@ export function initPanels() {
|
|
|
474
416
|
if (vendor === (store.get('currentVendor') || "claude")) return;
|
|
475
417
|
var installed = store.get('installedVendors') || [];
|
|
476
418
|
if (installed.indexOf(vendor) === -1) return;
|
|
477
|
-
store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorSelectionLocked: true, sessionVendorBound: true });
|
|
419
|
+
store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorCapabilities: {}, vendorSelectionLocked: true, sessionVendorBound: true });
|
|
478
420
|
var ws = getWs();
|
|
479
421
|
if (ws) ws.send(JSON.stringify({ type: "set_vendor", vendor: vendor }));
|
|
480
422
|
}
|
|
@@ -483,6 +425,11 @@ export function initPanels() {
|
|
|
483
425
|
if (vendorBtnCodex) vendorBtnCodex.addEventListener("click", function() { onVendorClick("codex"); });
|
|
484
426
|
if (vendorBtnAntigravity) vendorBtnAntigravity.addEventListener("click", function() { onVendorClick("antigravity"); });
|
|
485
427
|
if (vendorBtnOpenCode) vendorBtnOpenCode.addEventListener("click", function() { onVendorClick("opencode"); });
|
|
428
|
+
if (vendorBtnKimi) vendorBtnKimi.addEventListener("click", function() { onVendorClick("kimi"); });
|
|
429
|
+
if (vendorBtnGrok) vendorBtnGrok.addEventListener("click", function() { onVendorClick("grok"); });
|
|
430
|
+
if (vendorBtnCopilot) vendorBtnCopilot.addEventListener("click", function() { onVendorClick("copilot"); });
|
|
431
|
+
if (vendorBtnQwen) vendorBtnQwen.addEventListener("click", function() { onVendorClick("qwen"); });
|
|
432
|
+
if (vendorBtnJunie) vendorBtnJunie.addEventListener("click", function() { onVendorClick("junie"); });
|
|
486
433
|
if (vendorBtnKiro) vendorBtnKiro.addEventListener("click", function() { onVendorClick("kiro"); });
|
|
487
434
|
|
|
488
435
|
// --- Reactive UI sync ---
|
|
@@ -510,8 +457,7 @@ export function initPanels() {
|
|
|
510
457
|
|
|
511
458
|
// Vendor changed -> switch model list and current model to match
|
|
512
459
|
if (state.currentVendor !== prev.currentVendor && state.currentVendor) {
|
|
513
|
-
|
|
514
|
-
if (ws) ws.send(JSON.stringify({ type: "get_vendor_models", vendor: state.currentVendor }));
|
|
460
|
+
requestVendorModels(state.currentVendor, true);
|
|
515
461
|
}
|
|
516
462
|
|
|
517
463
|
// config chip
|
|
@@ -521,6 +467,12 @@ export function initPanels() {
|
|
|
521
467
|
state.currentBetas !== prev.currentBetas ||
|
|
522
468
|
state.currentThinking !== prev.currentThinking ||
|
|
523
469
|
state.currentVendor !== prev.currentVendor ||
|
|
470
|
+
state.currentModels !== prev.currentModels ||
|
|
471
|
+
state.vendorCapabilities !== prev.vendorCapabilities ||
|
|
472
|
+
state.modelListStatus !== prev.modelListStatus ||
|
|
473
|
+
state.modelListError !== prev.modelListError ||
|
|
474
|
+
state.modelSelectionPending !== prev.modelSelectionPending ||
|
|
475
|
+
state.modelSelectionError !== prev.modelSelectionError ||
|
|
524
476
|
state.codexApproval !== prev.codexApproval ||
|
|
525
477
|
state.codexSandbox !== prev.codexSandbox ||
|
|
526
478
|
state.codexWebSearch !== prev.codexWebSearch ||
|
|
@@ -621,6 +573,7 @@ export function initPanels() {
|
|
|
621
573
|
e.stopPropagation();
|
|
622
574
|
var wasHidden = configPopover.classList.toggle("hidden");
|
|
623
575
|
configChip.classList.toggle("active", !wasHidden);
|
|
576
|
+
if (!wasHidden) prepareModelPickerOpen();
|
|
624
577
|
});
|
|
625
578
|
}
|
|
626
579
|
|
|
@@ -674,7 +627,7 @@ export function updateConfigChip() {
|
|
|
674
627
|
var s = store.snap();
|
|
675
628
|
var vendor = s.currentVendor || "claude";
|
|
676
629
|
configChipLabel.textContent = modelDisplayName(s.currentModel, s.currentModels);
|
|
677
|
-
|
|
630
|
+
renderModelPicker();
|
|
678
631
|
rebuildModeList();
|
|
679
632
|
rebuildEffortBar();
|
|
680
633
|
|
|
@@ -861,9 +814,7 @@ export function updateContextPanel() {
|
|
|
861
814
|
hCtxEl = document.createElement("div");
|
|
862
815
|
hCtxEl.className = "header-context";
|
|
863
816
|
hCtxEl.innerHTML = '<div class="header-context-bar"><div class="header-context-fill"></div></div><span class="header-context-label"></span>';
|
|
864
|
-
|
|
865
|
-
var contextAnchor = workerBtn && workerBtn.parentNode === statusArea ? workerBtn.nextSibling : statusArea.firstChild;
|
|
866
|
-
statusArea.insertBefore(hCtxEl, contextAnchor);
|
|
817
|
+
statusArea.insertBefore(hCtxEl, statusArea.firstChild);
|
|
867
818
|
hCtxEl.addEventListener("mouseenter", function() {
|
|
868
819
|
if (store.get('richContextUsage')) {
|
|
869
820
|
showCtxPopover();
|
|
@@ -5,6 +5,7 @@ import { escapeHtml, showToast } from './utils.js';
|
|
|
5
5
|
import { refreshIcons } from './icons.js';
|
|
6
6
|
import { parseEmojis } from './markdown.js';
|
|
7
7
|
import { store } from './store.js';
|
|
8
|
+
import { resetModelPickerState } from './model-picker.js';
|
|
8
9
|
import { getWs, setWs } from './ws-ref.js';
|
|
9
10
|
import { getMessagesEl, getStatusDot } from './dom-refs.js';
|
|
10
11
|
import { userAvatarUrl } from './avatar.js';
|
|
@@ -322,7 +323,7 @@ export function renderProjectList() {
|
|
|
322
323
|
isWorktree: p.isWorktree || false,
|
|
323
324
|
parentSlug: p.parentSlug || null,
|
|
324
325
|
branch: p.branch || null,
|
|
325
|
-
|
|
326
|
+
worktreeExternal: p.worktreeExternal === true,
|
|
326
327
|
};
|
|
327
328
|
});
|
|
328
329
|
var st = store.snap();
|
|
@@ -363,6 +364,7 @@ export function renderProjectList() {
|
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
export function resetClientState() {
|
|
367
|
+
resetModelPickerState();
|
|
366
368
|
if (isSearchOpen()) closeSearch();
|
|
367
369
|
getMessagesEl().innerHTML = "";
|
|
368
370
|
store.set({ currentMsgEl: null });
|
|
@@ -13,7 +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
|
+
export { VENDOR_ORDER, EXPERIMENTAL_VENDORS, isExperimentalVendor } from './vendor-priority.js';
|
|
17
17
|
|
|
18
18
|
// Keep these client constants aligned with lib/yoke/vendor-registry.js. The
|
|
19
19
|
// browser cannot import the server's CommonJS registry directly.
|
|
@@ -22,6 +22,11 @@ export var VENDOR_AVATARS = {
|
|
|
22
22
|
codex: "/codex-avatar.png",
|
|
23
23
|
antigravity: "/antigravity-avatar.png",
|
|
24
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",
|
|
25
30
|
kiro: "/kiro-avatar.svg",
|
|
26
31
|
};
|
|
27
32
|
export var VENDOR_NAMES = {
|
|
@@ -29,6 +34,11 @@ export var VENDOR_NAMES = {
|
|
|
29
34
|
codex: "Codex",
|
|
30
35
|
antigravity: "Antigravity CLI",
|
|
31
36
|
opencode: "OpenCode",
|
|
37
|
+
kimi: "Kimi Code",
|
|
38
|
+
grok: "Grok Build",
|
|
39
|
+
copilot: "GitHub Copilot CLI",
|
|
40
|
+
qwen: "Qwen Code",
|
|
41
|
+
junie: "Junie CLI",
|
|
32
42
|
kiro: "Kiro CLI",
|
|
33
43
|
};
|
|
34
44
|
// Display order for every vendor Clay knows about, installed or not. Pickers
|
|
@@ -40,6 +50,11 @@ export var VENDOR_HOMEPAGES = {
|
|
|
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";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { escapeHtml } from './utils.js';
|
|
4
|
+
|
|
5
|
+
var expanded = false;
|
|
6
|
+
|
|
7
|
+
export function initBackgroundTasksUi() {
|
|
8
|
+
store.subscribe(function(state, prev) {
|
|
9
|
+
if (state.activeBackgroundTasks !== prev.activeBackgroundTasks) renderBackgroundTasks();
|
|
10
|
+
});
|
|
11
|
+
renderBackgroundTasks();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function renderBackgroundTasks() {
|
|
15
|
+
var tasks = store.get('activeBackgroundTasks') || [];
|
|
16
|
+
var existing = document.getElementById('background-tasks-bar');
|
|
17
|
+
if (tasks.length === 0) {
|
|
18
|
+
if (existing) existing.remove();
|
|
19
|
+
expanded = false;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
var inputArea = document.getElementById('input-area');
|
|
23
|
+
if (!inputArea || !inputArea.parentNode) return;
|
|
24
|
+
var bar = existing || document.createElement('div');
|
|
25
|
+
bar.id = 'background-tasks-bar';
|
|
26
|
+
bar.className = 'background-tasks-bar';
|
|
27
|
+
var taskLabel = tasks.length === 1 ? 'background task' : 'background tasks';
|
|
28
|
+
var html = '<button type="button" class="background-tasks-toggle" aria-expanded="' + expanded + '">' +
|
|
29
|
+
'<span aria-hidden="true">⏳</span><span class="background-tasks-chip">' + tasks.length + '</span>' +
|
|
30
|
+
'<span>' + tasks.length + ' ' + taskLabel + '</span></button>';
|
|
31
|
+
if (expanded) {
|
|
32
|
+
html += '<div class="background-tasks-list">';
|
|
33
|
+
for (var i = 0; i < tasks.length; i++) {
|
|
34
|
+
var task = tasks[i];
|
|
35
|
+
html += '<div class="background-task-row"><span class="background-task-description" title="' + escapeHtml(task.description || '') + '">' +
|
|
36
|
+
escapeHtml(task.description || 'Background task') + '</span><span class="background-task-type">' +
|
|
37
|
+
escapeHtml(task.task_type || 'other') + '</span><button type="button" class="background-task-stop" data-task-id="' +
|
|
38
|
+
escapeHtml(task.task_id || '') + '">Stop</button></div>';
|
|
39
|
+
}
|
|
40
|
+
html += '</div>';
|
|
41
|
+
}
|
|
42
|
+
bar.innerHTML = html;
|
|
43
|
+
if (!existing) inputArea.parentNode.insertBefore(bar, inputArea);
|
|
44
|
+
bar.querySelector('.background-tasks-toggle').addEventListener('click', function() {
|
|
45
|
+
expanded = !expanded;
|
|
46
|
+
renderBackgroundTasks();
|
|
47
|
+
});
|
|
48
|
+
var stopButtons = bar.querySelectorAll('.background-task-stop');
|
|
49
|
+
for (var j = 0; j < stopButtons.length; j++) {
|
|
50
|
+
stopButtons[j].addEventListener('click', function() {
|
|
51
|
+
var ws = getWs();
|
|
52
|
+
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'stop_task', taskId: this.dataset.taskId }));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
var tabs = new Map();
|
|
2
|
+
var focusedPath = null;
|
|
3
|
+
var onFocus = null;
|
|
4
|
+
var onEmpty = null;
|
|
5
|
+
|
|
6
|
+
function label(path) {
|
|
7
|
+
var parts = String(path || "").split("/");
|
|
8
|
+
return parts[parts.length - 1] || path;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function render() {
|
|
12
|
+
var root = document.getElementById("file-viewer-tabs");
|
|
13
|
+
if (!root) return;
|
|
14
|
+
root.innerHTML = "";
|
|
15
|
+
tabs.forEach(function(value, path) {
|
|
16
|
+
var tab = document.createElement("button");
|
|
17
|
+
tab.type = "button";
|
|
18
|
+
tab.className = "file-viewer-tab" + (path === focusedPath ? " active" : "");
|
|
19
|
+
tab.textContent = label(path);
|
|
20
|
+
tab.title = path;
|
|
21
|
+
tab.addEventListener("click", function() { focusFileViewerTab(path); });
|
|
22
|
+
var close = document.createElement("span");
|
|
23
|
+
close.className = "file-viewer-tab-close";
|
|
24
|
+
close.textContent = "×";
|
|
25
|
+
close.addEventListener("click", function(event) { event.stopPropagation(); closeFileViewerTab(path); });
|
|
26
|
+
tab.appendChild(close);
|
|
27
|
+
root.appendChild(tab);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function initFileViewerTabs(options) {
|
|
32
|
+
onFocus = options && options.onFocus;
|
|
33
|
+
onEmpty = options && options.onEmpty;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function openFileViewerTab(path, data) {
|
|
37
|
+
if (!path) return false;
|
|
38
|
+
var exists = tabs.has(path);
|
|
39
|
+
tabs.set(path, Object.assign(tabs.get(path) || {}, data || {}));
|
|
40
|
+
focusedPath = path;
|
|
41
|
+
render();
|
|
42
|
+
return !exists;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function focusFileViewerTab(path) {
|
|
46
|
+
if (!tabs.has(path)) return false;
|
|
47
|
+
focusedPath = path;
|
|
48
|
+
render();
|
|
49
|
+
if (onFocus) onFocus(path, tabs.get(path));
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function closeFileViewerTab(path) {
|
|
54
|
+
if (!tabs.has(path)) return;
|
|
55
|
+
var paths = Array.from(tabs.keys());
|
|
56
|
+
var index = paths.indexOf(path);
|
|
57
|
+
tabs.delete(path);
|
|
58
|
+
if (focusedPath === path) focusedPath = paths[index + 1] || paths[index - 1] || null;
|
|
59
|
+
render();
|
|
60
|
+
if (focusedPath && onFocus) onFocus(focusedPath, tabs.get(focusedPath));
|
|
61
|
+
if (!focusedPath && onEmpty) onEmpty();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function focusedFileViewerTab() { return focusedPath; }
|
|
65
|
+
export function clearFileViewerTabs() { tabs.clear(); focusedPath = null; render(); }
|
|
@@ -9,6 +9,7 @@ import { copyMarkdownFormatting } from './rich-clipboard.js';
|
|
|
9
9
|
import { animateMarkdownChange, beginMarkdownPresentation, cancelMarkdownFollow, isFollowingMarkdown } from './markdown-live-edit.js';
|
|
10
10
|
import { store } from './store.js';
|
|
11
11
|
import { enterMarkdownSlides, exitMarkdownSlides, handleMarkdownSlideKey, syncMarkdownSlidesButton, toggleMarkdownSlideLevelMenu } from './markdown-slides.js';
|
|
12
|
+
import { initFileViewerTabs, openFileViewerTab, closeFileViewerTab, focusedFileViewerTab, clearFileViewerTabs } from './filebrowser-tabs.js';
|
|
12
13
|
|
|
13
14
|
var ctx;
|
|
14
15
|
var showDropHint = function () {};
|
|
@@ -30,6 +31,14 @@ var pendingFileAt = null; // callback for pending file-at
|
|
|
30
31
|
|
|
31
32
|
export function initFileBrowser(_ctx) {
|
|
32
33
|
ctx = _ctx;
|
|
34
|
+
var mainPanels = document.getElementById("main-panels");
|
|
35
|
+
if (mainPanels && ctx.fileViewerEl) mainPanels.insertBefore(ctx.fileViewerEl, mainPanels.firstChild);
|
|
36
|
+
initFileViewerTabs({
|
|
37
|
+
onFocus: function(path) {
|
|
38
|
+
if (path !== currentFilePath) requestFileContent(path);
|
|
39
|
+
},
|
|
40
|
+
onEmpty: function() { teardownFileViewer(); },
|
|
41
|
+
});
|
|
33
42
|
|
|
34
43
|
// Load material file icons in background
|
|
35
44
|
initFileIcons().then(function () {
|
|
@@ -133,7 +142,7 @@ export function initFileBrowser(_ctx) {
|
|
|
133
142
|
|
|
134
143
|
// Close button
|
|
135
144
|
document.getElementById("file-viewer-close").addEventListener("click", function () {
|
|
136
|
-
|
|
145
|
+
closeFileViewerTab(focusedFileViewerTab());
|
|
137
146
|
});
|
|
138
147
|
|
|
139
148
|
// Full-viewport presentation toggle
|
|
@@ -448,6 +457,14 @@ function ensureRenderedMarkdown() {
|
|
|
448
457
|
}
|
|
449
458
|
|
|
450
459
|
export function closeFileViewer() {
|
|
460
|
+
if (focusedFileViewerTab()) {
|
|
461
|
+
closeFileViewerTab(focusedFileViewerTab());
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
teardownFileViewer();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function teardownFileViewer() {
|
|
451
468
|
if (currentFilePath && isFollowingMarkdown(currentFilePath)) cancelMarkdownFollow();
|
|
452
469
|
sendUnwatch();
|
|
453
470
|
inlineDiffActive = false;
|
|
@@ -475,8 +492,8 @@ export function setFileViewerFullscreen(enabled) {
|
|
|
475
492
|
}
|
|
476
493
|
|
|
477
494
|
export function resetFileBrowser() {
|
|
478
|
-
|
|
479
|
-
|
|
495
|
+
clearFileViewerTabs();
|
|
496
|
+
teardownFileViewer();
|
|
480
497
|
// Clear all cached state
|
|
481
498
|
treeData = {};
|
|
482
499
|
currentContent = null;
|
|
@@ -513,6 +530,7 @@ export function openFile(filePath, opts) {
|
|
|
513
530
|
cancelMarkdownFollow();
|
|
514
531
|
}
|
|
515
532
|
pendingRenderedOpen = !!(opts && opts.rendered);
|
|
533
|
+
openFileViewerTab(filePath);
|
|
516
534
|
if (opts && opts.diff) {
|
|
517
535
|
pendingOpenMode = { type: "diff", oldStr: opts.diff.oldStr, newStr: opts.diff.newStr };
|
|
518
536
|
} else {
|
|
@@ -540,6 +558,7 @@ export function openWorkingTreeDiff(diff) {
|
|
|
540
558
|
|
|
541
559
|
export function presentMarkdownEdit(msg) {
|
|
542
560
|
if (!msg || !beginMarkdownPresentation(msg.path)) return;
|
|
561
|
+
openFileViewerTab(msg.path, { content: msg.content });
|
|
543
562
|
pendingOpenMode = null;
|
|
544
563
|
pendingRenderedOpen = true;
|
|
545
564
|
pendingRefresh = false;
|
|
@@ -1031,6 +1050,7 @@ function renderEntries(container, entries, depth) {
|
|
|
1031
1050
|
// --- File viewer ---
|
|
1032
1051
|
|
|
1033
1052
|
function showFileContent(msg) {
|
|
1053
|
+
openFileViewerTab(msg.path, { content: msg.content });
|
|
1034
1054
|
var pathEl = document.getElementById("file-viewer-path");
|
|
1035
1055
|
var bodyEl = document.getElementById("file-viewer-body");
|
|
1036
1056
|
var renderBtn = document.getElementById("file-viewer-render");
|
|
@@ -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;
|
|
@@ -154,17 +155,24 @@ export function showMateSidebar(mateId, mateData) {
|
|
|
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", antigravity: "Antigravity CLI", opencode: "OpenCode", kiro: "Kiro CLI" };
|
|
160
|
-
var vendorKeys = ["claude", "codex", "antigravity", "opencode", "kiro"];
|
|
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;
|