clay-server 2.31.0 → 2.32.0-beta.2
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/browser-mcp-server.js +32 -44
- package/lib/debate-mcp-server.js +14 -31
- package/lib/mcp-local.js +31 -1
- package/lib/project-connection.js +4 -2
- package/lib/project-filesystem.js +47 -1
- package/lib/project-http.js +75 -8
- package/lib/project-mcp.js +4 -0
- package/lib/project-sessions.js +88 -51
- package/lib/project-user-message.js +12 -7
- package/lib/project.js +204 -90
- package/lib/public/app.js +123 -448
- package/lib/public/codex-avatar.png +0 -0
- package/lib/public/css/debate.css +3 -2
- package/lib/public/css/filebrowser.css +91 -1
- package/lib/public/css/icon-strip.css +21 -5
- package/lib/public/css/input.css +181 -100
- package/lib/public/css/mates.css +43 -0
- package/lib/public/css/mention.css +48 -4
- package/lib/public/css/menus.css +1 -1
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/notifications-center.css +19 -0
- package/lib/public/index.html +46 -24
- package/lib/public/modules/app-connection.js +138 -37
- package/lib/public/modules/app-cursors.js +18 -17
- package/lib/public/modules/app-debate-ui.js +9 -9
- package/lib/public/modules/app-dm.js +170 -131
- package/lib/public/modules/app-favicon.js +28 -26
- package/lib/public/modules/app-header.js +79 -68
- package/lib/public/modules/app-home-hub.js +55 -47
- package/lib/public/modules/app-loop-ui.js +34 -18
- package/lib/public/modules/app-loop-wizard.js +6 -6
- package/lib/public/modules/app-messages.js +195 -152
- package/lib/public/modules/app-misc.js +23 -12
- package/lib/public/modules/app-notifications.js +97 -3
- package/lib/public/modules/app-panels.js +203 -49
- package/lib/public/modules/app-projects.js +159 -150
- package/lib/public/modules/app-rate-limit.js +5 -4
- package/lib/public/modules/app-rendering.js +149 -101
- package/lib/public/modules/app-skills-install.js +4 -4
- package/lib/public/modules/context-sources.js +12 -41
- package/lib/public/modules/dom-refs.js +21 -0
- package/lib/public/modules/filebrowser.js +173 -2
- package/lib/public/modules/input.js +86 -0
- package/lib/public/modules/mate-sidebar.js +38 -0
- package/lib/public/modules/mention.js +24 -6
- package/lib/public/modules/scheduler.js +1 -1
- package/lib/public/modules/sidebar-mates.js +66 -34
- package/lib/public/modules/sidebar-mobile.js +34 -30
- package/lib/public/modules/sidebar-projects.js +60 -57
- package/lib/public/modules/sidebar-sessions.js +75 -69
- package/lib/public/modules/sidebar.js +12 -20
- package/lib/public/modules/skills.js +8 -9
- package/lib/public/modules/sticky-notes.js +1 -2
- package/lib/public/modules/store.js +9 -2
- package/lib/public/modules/stt.js +4 -1
- package/lib/public/modules/tools.js +14 -9
- package/lib/sdk-bridge.js +511 -1113
- package/lib/sdk-message-processor.js +123 -134
- package/lib/sdk-worker.js +4 -0
- package/lib/server-dm.js +1 -0
- package/lib/server.js +86 -1
- package/lib/sessions.js +47 -36
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker.js +559 -0
- package/lib/yoke/adapters/claude.js +1418 -0
- package/lib/yoke/adapters/codex.js +968 -0
- package/lib/yoke/adapters/gemini.js +668 -0
- package/lib/yoke/codex-app-server.js +307 -0
- package/lib/yoke/index.js +199 -0
- package/lib/yoke/instructions.js +62 -0
- package/lib/yoke/interface.js +92 -0
- package/lib/yoke/mcp-bridge-server.js +294 -0
- package/lib/yoke/package.json +7 -0
- package/package.json +3 -1
|
@@ -13,6 +13,9 @@ var _extRequestCallbacks = {};
|
|
|
13
13
|
|
|
14
14
|
// Queue for extension messages that arrived before WS was ready
|
|
15
15
|
var _pendingExtMessages = [];
|
|
16
|
+
// Cache last extension state so we can resend on WS reconnect (server restart)
|
|
17
|
+
var _lastTabListMsg = null;
|
|
18
|
+
var _lastMcpServersMsg = null;
|
|
16
19
|
|
|
17
20
|
function sendOrQueue(msgObj) {
|
|
18
21
|
var ws = getWs();
|
|
@@ -24,14 +27,20 @@ function sendOrQueue(msgObj) {
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export function flushPendingExtMessages() {
|
|
27
|
-
if (_pendingExtMessages.length === 0) return;
|
|
28
30
|
var ws = getWs();
|
|
29
31
|
if (!ws || ws.readyState !== 1) return;
|
|
30
|
-
|
|
31
|
-
_pendingExtMessages
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// Flush queued messages from before WS was ready
|
|
33
|
+
if (_pendingExtMessages.length > 0) {
|
|
34
|
+
var queued = _pendingExtMessages.slice();
|
|
35
|
+
_pendingExtMessages = [];
|
|
36
|
+
for (var i = 0; i < queued.length; i++) {
|
|
37
|
+
ws.send(JSON.stringify(queued[i]));
|
|
38
|
+
}
|
|
34
39
|
}
|
|
40
|
+
// Resend cached extension state on every WS reconnect so the server
|
|
41
|
+
// re-registers _extensionWs and rebuilds MCP proxy servers
|
|
42
|
+
if (_lastTabListMsg) ws.send(JSON.stringify(_lastTabListMsg));
|
|
43
|
+
if (_lastMcpServersMsg) ws.send(JSON.stringify(_lastMcpServersMsg));
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
export function initMisc() {
|
|
@@ -121,11 +130,9 @@ export function initMisc() {
|
|
|
121
130
|
if (msg.type === "clay_ext_tab_list") {
|
|
122
131
|
setExtensionConnected(true);
|
|
123
132
|
updateBrowserTabList(msg.tabs);
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
tabs: msg.tabs
|
|
128
|
-
});
|
|
133
|
+
// Cache and send (or queue) - resent on WS reconnect via flushPendingExtMessages
|
|
134
|
+
_lastTabListMsg = { type: "browser_tab_list", tabs: msg.tabs };
|
|
135
|
+
sendOrQueue(_lastTabListMsg);
|
|
129
136
|
}
|
|
130
137
|
if (msg.type === "clay_ext_result") {
|
|
131
138
|
handleExtensionResult(msg.requestId, msg.result);
|
|
@@ -135,12 +142,14 @@ export function initMisc() {
|
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
// MCP bridge: extension reports available MCP servers (queue if WS not ready yet)
|
|
145
|
+
// Cache for resend on WS reconnect (server restart loses _availableServers)
|
|
138
146
|
if (msg.type === "mcp_servers_available") {
|
|
139
|
-
|
|
147
|
+
_lastMcpServersMsg = {
|
|
140
148
|
type: "mcp_servers_available",
|
|
141
149
|
servers: msg.servers,
|
|
142
150
|
hostConnected: msg.hostConnected
|
|
143
|
-
}
|
|
151
|
+
};
|
|
152
|
+
sendOrQueue(_lastMcpServersMsg);
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
// MCP bridge: tool result from extension (tool results should not be queued,
|
|
@@ -161,6 +170,7 @@ export function initMisc() {
|
|
|
161
170
|
|
|
162
171
|
// Forward an MCP tool call from the server to the Chrome extension
|
|
163
172
|
export function forwardMcpToolCall(msg) {
|
|
173
|
+
console.log("[mcp] forwarding to extension:", msg.callId, msg.server, msg.method);
|
|
164
174
|
window.postMessage({
|
|
165
175
|
source: "clay-page",
|
|
166
176
|
payload: {
|
|
@@ -185,6 +195,7 @@ export function setHttpMcpServers(servers) {
|
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
export function handleMcpToolCallMessage(msg) {
|
|
198
|
+
console.log("[mcp] tool call received from server:", msg.callId, msg.server, msg.params && msg.params.name);
|
|
188
199
|
var httpUrl = _httpMcpServers[msg.server];
|
|
189
200
|
if (httpUrl) {
|
|
190
201
|
// HTTP transport: call directly via fetch
|
|
@@ -15,6 +15,12 @@ var bannerContainer = null;
|
|
|
15
15
|
var bellBtn = null;
|
|
16
16
|
var badgeEl = null;
|
|
17
17
|
|
|
18
|
+
// --- Update available banner state ---
|
|
19
|
+
var pendingUpdateMsg = null;
|
|
20
|
+
var updateReshowTimer = null;
|
|
21
|
+
var updateDismissedAt = 0;
|
|
22
|
+
var UPDATE_RESHOW_INTERVAL = 60 * 60 * 1000; // 1 hour
|
|
23
|
+
|
|
18
24
|
// ========================================================
|
|
19
25
|
// Init
|
|
20
26
|
// ========================================================
|
|
@@ -44,7 +50,10 @@ function showAllBanners() {
|
|
|
44
50
|
// Clear existing banners first
|
|
45
51
|
if (bannerContainer) bannerContainer.innerHTML = "";
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
// Re-add update banner if present
|
|
54
|
+
if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
|
|
55
|
+
|
|
56
|
+
if (notifications.length === 0 && !pendingUpdateMsg) {
|
|
48
57
|
showBanner({
|
|
49
58
|
id: "_empty",
|
|
50
59
|
type: "info",
|
|
@@ -237,7 +246,7 @@ export function handleNotificationCreated(msg) {
|
|
|
237
246
|
var notif = msg.notification;
|
|
238
247
|
|
|
239
248
|
// Auto-dismiss if it's for the session the user is currently viewing
|
|
240
|
-
var activeSession = store.
|
|
249
|
+
var activeSession = store.get('activeSessionId') || null;
|
|
241
250
|
console.log("[notif] created:", notif.type, "sessionId=" + notif.sessionId + "(" + typeof notif.sessionId + ")", "active=" + activeSession + "(" + typeof activeSession + ")", "match=" + (notif.sessionId == activeSession));
|
|
242
251
|
if (notif.sessionId && String(notif.sessionId) === String(activeSession)) {
|
|
243
252
|
dismissNotif(notif.id);
|
|
@@ -297,7 +306,7 @@ function navigateToNotification(notif) {
|
|
|
297
306
|
return;
|
|
298
307
|
}
|
|
299
308
|
|
|
300
|
-
var currentSlug = store.
|
|
309
|
+
var currentSlug = store.get('currentSlug') || "";
|
|
301
310
|
var needsProjectSwitch = notif.slug && notif.slug !== currentSlug;
|
|
302
311
|
|
|
303
312
|
if (needsProjectSwitch) {
|
|
@@ -314,6 +323,91 @@ function navigateToNotification(notif) {
|
|
|
314
323
|
}
|
|
315
324
|
}
|
|
316
325
|
|
|
326
|
+
// ========================================================
|
|
327
|
+
// Update available banner
|
|
328
|
+
// ========================================================
|
|
329
|
+
|
|
330
|
+
export function showUpdateBanner(msg) {
|
|
331
|
+
if (!msg || !msg.version) return;
|
|
332
|
+
pendingUpdateMsg = msg;
|
|
333
|
+
if (!bannerContainer) return;
|
|
334
|
+
|
|
335
|
+
// If user dismissed recently, skip until reshow timer fires
|
|
336
|
+
if (updateDismissedAt && (Date.now() - updateDismissedAt) < UPDATE_RESHOW_INTERVAL) return;
|
|
337
|
+
|
|
338
|
+
// Remove any existing update banner
|
|
339
|
+
var existing = bannerContainer.querySelector('[data-notif-id="_update"]');
|
|
340
|
+
if (existing) removeBanner(existing);
|
|
341
|
+
|
|
342
|
+
var isHeadless = store.get('isHeadlessMode');
|
|
343
|
+
var updTag = msg.version.indexOf("-beta") !== -1 ? "beta" : "latest";
|
|
344
|
+
|
|
345
|
+
var banner = document.createElement("div");
|
|
346
|
+
banner.className = "notif-banner notif-banner-update";
|
|
347
|
+
banner.setAttribute("data-notif-id", "_update");
|
|
348
|
+
|
|
349
|
+
var actionsHtml = '';
|
|
350
|
+
if (!isHeadless) {
|
|
351
|
+
actionsHtml =
|
|
352
|
+
'<div class="notif-banner-actions">' +
|
|
353
|
+
'<button class="notif-banner-update-now">Update now</button>' +
|
|
354
|
+
'</div>';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
banner.innerHTML =
|
|
358
|
+
'<div class="notif-banner-icon"><img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px"></div>' +
|
|
359
|
+
'<div class="notif-banner-body">' +
|
|
360
|
+
'<div class="notif-banner-project">CLAY</div>' +
|
|
361
|
+
'<div class="notif-banner-title">v' + escapeHtml(msg.version) + ' is available</div>' +
|
|
362
|
+
(isHeadless
|
|
363
|
+
? '<div class="notif-banner-text">Run: npx clay-server@' + escapeHtml(updTag) + '</div>'
|
|
364
|
+
: '') +
|
|
365
|
+
actionsHtml +
|
|
366
|
+
'</div>' +
|
|
367
|
+
'<button class="notif-banner-close">' + iconHtml("x") + '</button>';
|
|
368
|
+
|
|
369
|
+
bannerContainer.appendChild(banner);
|
|
370
|
+
refreshIcons();
|
|
371
|
+
|
|
372
|
+
requestAnimationFrame(function () {
|
|
373
|
+
banner.classList.add("show");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// "Update now" button
|
|
377
|
+
var updateBtn = banner.querySelector(".notif-banner-update-now");
|
|
378
|
+
if (updateBtn) {
|
|
379
|
+
updateBtn.addEventListener("click", function (e) {
|
|
380
|
+
e.stopPropagation();
|
|
381
|
+
var ws = getWs();
|
|
382
|
+
if (ws && ws.readyState === 1) {
|
|
383
|
+
ws.send(JSON.stringify({ type: "update_now" }));
|
|
384
|
+
updateBtn.textContent = "Updating...";
|
|
385
|
+
updateBtn.disabled = true;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Close button -> dismiss, re-show in 1 hour
|
|
391
|
+
var closeBtn = banner.querySelector(".notif-banner-close");
|
|
392
|
+
if (closeBtn) {
|
|
393
|
+
closeBtn.addEventListener("click", function (e) {
|
|
394
|
+
e.stopPropagation();
|
|
395
|
+
removeBanner(banner);
|
|
396
|
+
scheduleUpdateReshow();
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function scheduleUpdateReshow() {
|
|
402
|
+
updateDismissedAt = Date.now();
|
|
403
|
+
if (updateReshowTimer) clearTimeout(updateReshowTimer);
|
|
404
|
+
updateReshowTimer = setTimeout(function () {
|
|
405
|
+
updateReshowTimer = null;
|
|
406
|
+
updateDismissedAt = 0;
|
|
407
|
+
if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
|
|
408
|
+
}, UPDATE_RESHOW_INTERVAL);
|
|
409
|
+
}
|
|
410
|
+
|
|
317
411
|
// ========================================================
|
|
318
412
|
// Helpers
|
|
319
413
|
// ========================================================
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// Extracted from app.js (PR-30)
|
|
3
3
|
|
|
4
4
|
import { refreshIcons } from "./icons.js";
|
|
5
|
-
import { escapeHtml } from "./utils.js";
|
|
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
9
|
|
|
9
10
|
// --- Module-owned state (not in store) ---
|
|
10
11
|
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
@@ -28,6 +29,12 @@ var configThinkingSection = null;
|
|
|
28
29
|
var configThinkingBar = null;
|
|
29
30
|
var configThinkingBudgetRow = null;
|
|
30
31
|
var configThinkingBudgetInput = null;
|
|
32
|
+
var configApprovalSection = null;
|
|
33
|
+
var configApprovalBar = null;
|
|
34
|
+
var configSandboxSection = null;
|
|
35
|
+
var configSandboxBar = null;
|
|
36
|
+
var configWebsearchSection = null;
|
|
37
|
+
var configWebsearchBar = null;
|
|
31
38
|
|
|
32
39
|
var usagePanel = null;
|
|
33
40
|
var usagePanelClose = null;
|
|
@@ -78,10 +85,35 @@ var MODE_OPTIONS = [
|
|
|
78
85
|
];
|
|
79
86
|
var MODE_FULL_AUTO = { value: "bypassPermissions", label: "Full auto" };
|
|
80
87
|
var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
88
|
+
var EFFORT_LEVELS_BY_VENDOR = {
|
|
89
|
+
claude: ["low", "medium", "high", "xhigh", "max"],
|
|
90
|
+
codex: ["minimal", "low", "medium", "high", "xhigh"],
|
|
91
|
+
};
|
|
81
92
|
var THINKING_OPTIONS = ["disabled", "adaptive", "budget"];
|
|
93
|
+
var CODEX_APPROVAL_OPTIONS = [
|
|
94
|
+
{ value: "never", label: "Auto" },
|
|
95
|
+
{ value: "on-failure", label: "On Fail" },
|
|
96
|
+
{ value: "on-request", label: "Ask" },
|
|
97
|
+
];
|
|
98
|
+
var CODEX_SANDBOX_OPTIONS = [
|
|
99
|
+
{ value: "read-only", label: "Read Only" },
|
|
100
|
+
{ value: "workspace-write", label: "Workspace" },
|
|
101
|
+
{ value: "danger-full-access", label: "Full Access" },
|
|
102
|
+
];
|
|
103
|
+
var CODEX_WEBSEARCH_OPTIONS = [
|
|
104
|
+
{ value: "disabled", label: "Off" },
|
|
105
|
+
{ value: "cached", label: "Cached" },
|
|
106
|
+
{ value: "live", label: "Live" },
|
|
107
|
+
];
|
|
82
108
|
var KNOWN_CONTEXT_WINDOWS = {
|
|
83
109
|
"opus-4-6": 1000000,
|
|
84
|
-
"claude-sonnet-4": 1000000
|
|
110
|
+
"claude-sonnet-4": 1000000,
|
|
111
|
+
"gpt-5.4": 1048576,
|
|
112
|
+
"gpt-5.3": 1048576,
|
|
113
|
+
"gpt-5.2": 1048576,
|
|
114
|
+
"gpt-4.1": 1047576,
|
|
115
|
+
"o3": 200000,
|
|
116
|
+
"o4-mini": 200000,
|
|
85
117
|
};
|
|
86
118
|
// Categories to hide from the legend (noise, not actionable)
|
|
87
119
|
var CTX_HIDDEN_CATS = { "Free space": 1, "Autocompact buffer": 1 };
|
|
@@ -98,7 +130,9 @@ function modelDisplayName(value, models) {
|
|
|
98
130
|
if (!value) return "";
|
|
99
131
|
if (models) {
|
|
100
132
|
for (var i = 0; i < models.length; i++) {
|
|
101
|
-
|
|
133
|
+
var m = models[i];
|
|
134
|
+
if (typeof m === "string") { if (m === value) return m; }
|
|
135
|
+
else if (m.value === value && m.displayName) return m.displayName;
|
|
102
136
|
}
|
|
103
137
|
}
|
|
104
138
|
return value;
|
|
@@ -133,7 +167,7 @@ function isSonnetModel(model) {
|
|
|
133
167
|
}
|
|
134
168
|
|
|
135
169
|
function hasBeta(name) {
|
|
136
|
-
var betas = store.
|
|
170
|
+
var betas = store.get('currentBetas');
|
|
137
171
|
for (var i = 0; i < betas.length; i++) {
|
|
138
172
|
if (betas[i].indexOf(name) !== -1) return true;
|
|
139
173
|
}
|
|
@@ -143,12 +177,13 @@ function hasBeta(name) {
|
|
|
143
177
|
function rebuildModelList() {
|
|
144
178
|
if (!configModelList) return;
|
|
145
179
|
configModelList.innerHTML = "";
|
|
146
|
-
var s = store.
|
|
180
|
+
var s = store.snap();
|
|
147
181
|
var list = s.currentModels.length > 0 ? s.currentModels : (s.currentModel ? [{ value: s.currentModel, displayName: s.currentModel }] : []);
|
|
148
182
|
for (var i = 0; i < list.length; i++) {
|
|
149
183
|
var item = list[i];
|
|
150
|
-
|
|
151
|
-
var
|
|
184
|
+
// Support both object { value, displayName } and plain string formats
|
|
185
|
+
var value = typeof item === "string" ? item : (item.value || "");
|
|
186
|
+
var label = typeof item === "string" ? item : (item.displayName || value);
|
|
152
187
|
var btn = document.createElement("button");
|
|
153
188
|
btn.className = "config-radio-item";
|
|
154
189
|
if (value === s.currentModel) btn.classList.add("active");
|
|
@@ -171,14 +206,14 @@ function rebuildModeList() {
|
|
|
171
206
|
if (!configModeList) return;
|
|
172
207
|
configModeList.innerHTML = "";
|
|
173
208
|
var options = MODE_OPTIONS.slice();
|
|
174
|
-
if (store.
|
|
209
|
+
if (store.get('skipPermsEnabled')) {
|
|
175
210
|
options.push(MODE_FULL_AUTO);
|
|
176
211
|
}
|
|
177
212
|
for (var i = 0; i < options.length; i++) {
|
|
178
213
|
var opt = options[i];
|
|
179
214
|
var btn = document.createElement("button");
|
|
180
215
|
btn.className = "config-radio-item";
|
|
181
|
-
if (opt.value === store.
|
|
216
|
+
if (opt.value === store.get('currentMode')) btn.classList.add("active");
|
|
182
217
|
btn.dataset.mode = opt.value;
|
|
183
218
|
btn.textContent = opt.label;
|
|
184
219
|
btn.addEventListener("click", function () {
|
|
@@ -208,7 +243,7 @@ function rebuildEffortBar() {
|
|
|
208
243
|
var level = levels[i];
|
|
209
244
|
var btn = document.createElement("button");
|
|
210
245
|
btn.className = "config-segment-btn";
|
|
211
|
-
if (level === store.
|
|
246
|
+
if (level === store.get('currentEffort')) btn.classList.add("active");
|
|
212
247
|
btn.dataset.effort = level;
|
|
213
248
|
btn.textContent = effortDisplayName(level);
|
|
214
249
|
btn.addEventListener("click", function () {
|
|
@@ -227,7 +262,7 @@ function rebuildEffortBar() {
|
|
|
227
262
|
function rebuildBetaSection() {
|
|
228
263
|
if (!configBetaSection || !configBeta1mBtn) return;
|
|
229
264
|
// Only show for Sonnet models
|
|
230
|
-
if (!isSonnetModel(store.
|
|
265
|
+
if (!isSonnetModel(store.get('currentModel'))) {
|
|
231
266
|
configBetaSection.style.display = "none";
|
|
232
267
|
return;
|
|
233
268
|
}
|
|
@@ -241,7 +276,7 @@ function rebuildThinkingSection() {
|
|
|
241
276
|
if (!configThinkingBar || !configThinkingSection) return;
|
|
242
277
|
configThinkingSection.style.display = "";
|
|
243
278
|
configThinkingBar.innerHTML = "";
|
|
244
|
-
var s = store.
|
|
279
|
+
var s = store.snap();
|
|
245
280
|
for (var i = 0; i < THINKING_OPTIONS.length; i++) {
|
|
246
281
|
var opt = THINKING_OPTIONS[i];
|
|
247
282
|
var btn = document.createElement("button");
|
|
@@ -253,7 +288,7 @@ function rebuildThinkingSection() {
|
|
|
253
288
|
var thinking = this.dataset.thinking;
|
|
254
289
|
var msg = { type: "set_thinking", thinking: thinking };
|
|
255
290
|
if (thinking === "budget") {
|
|
256
|
-
msg.budgetTokens = store.
|
|
291
|
+
msg.budgetTokens = store.get('currentThinkingBudget');
|
|
257
292
|
}
|
|
258
293
|
var ws = getWs();
|
|
259
294
|
if (ws && ws.readyState === 1) {
|
|
@@ -271,6 +306,47 @@ function rebuildThinkingSection() {
|
|
|
271
306
|
}
|
|
272
307
|
}
|
|
273
308
|
|
|
309
|
+
function buildSegmentedBar(barEl, options, currentValue, msgType, msgKey) {
|
|
310
|
+
if (!barEl) return;
|
|
311
|
+
barEl.innerHTML = "";
|
|
312
|
+
for (var i = 0; i < options.length; i++) {
|
|
313
|
+
var opt = options[i];
|
|
314
|
+
var btn = document.createElement("button");
|
|
315
|
+
btn.className = "config-segment-btn";
|
|
316
|
+
if (opt.value === currentValue) btn.classList.add("active");
|
|
317
|
+
btn.dataset.val = opt.value;
|
|
318
|
+
btn.textContent = opt.label;
|
|
319
|
+
btn.addEventListener("click", function () {
|
|
320
|
+
var val = this.dataset.val;
|
|
321
|
+
var msg = { type: msgType };
|
|
322
|
+
msg[msgKey] = val;
|
|
323
|
+
var ws = getWs();
|
|
324
|
+
if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg));
|
|
325
|
+
configPopover.classList.add("hidden");
|
|
326
|
+
configChip.classList.remove("active");
|
|
327
|
+
});
|
|
328
|
+
barEl.appendChild(btn);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function rebuildCodexSections() {
|
|
333
|
+
var s = store.snap();
|
|
334
|
+
var isCodex = (s.currentVendor || "claude") === "codex";
|
|
335
|
+
|
|
336
|
+
if (configApprovalSection) {
|
|
337
|
+
configApprovalSection.style.display = isCodex ? "" : "none";
|
|
338
|
+
if (isCodex) buildSegmentedBar(configApprovalBar, CODEX_APPROVAL_OPTIONS, s.codexApproval || "on-failure", "set_codex_approval", "approval");
|
|
339
|
+
}
|
|
340
|
+
if (configSandboxSection) {
|
|
341
|
+
configSandboxSection.style.display = isCodex ? "" : "none";
|
|
342
|
+
if (isCodex) buildSegmentedBar(configSandboxBar, CODEX_SANDBOX_OPTIONS, s.codexSandbox || "workspace-write", "set_codex_sandbox", "sandbox");
|
|
343
|
+
}
|
|
344
|
+
if (configWebsearchSection) {
|
|
345
|
+
configWebsearchSection.style.display = isCodex ? "" : "none";
|
|
346
|
+
if (isCodex) buildSegmentedBar(configWebsearchBar, CODEX_WEBSEARCH_OPTIONS, s.codexWebSearch || "disabled", "set_codex_websearch", "webSearch");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
274
350
|
function escHtml(s) {
|
|
275
351
|
var div = document.createElement("div");
|
|
276
352
|
div.textContent = s;
|
|
@@ -301,6 +377,86 @@ export function initPanels() {
|
|
|
301
377
|
configThinkingBar = $("config-thinking-bar");
|
|
302
378
|
configThinkingBudgetRow = $("config-thinking-budget-row");
|
|
303
379
|
configThinkingBudgetInput = $("config-thinking-budget");
|
|
380
|
+
configApprovalSection = $("config-approval-section");
|
|
381
|
+
configApprovalBar = $("config-approval-bar");
|
|
382
|
+
configSandboxSection = $("config-sandbox-section");
|
|
383
|
+
configSandboxBar = $("config-sandbox-bar");
|
|
384
|
+
configWebsearchSection = $("config-websearch-section");
|
|
385
|
+
configWebsearchBar = $("config-websearch-bar");
|
|
386
|
+
|
|
387
|
+
// --- Vendor toggle ---
|
|
388
|
+
var vendorToggleWrap = $("vendor-toggle-wrap");
|
|
389
|
+
var vendorBtnClaude = $("vendor-btn-claude");
|
|
390
|
+
var vendorBtnCodex = $("vendor-btn-codex");
|
|
391
|
+
var vendorBtns = { claude: vendorBtnClaude, codex: vendorBtnCodex };
|
|
392
|
+
|
|
393
|
+
function updateVendorToggle() {
|
|
394
|
+
var installed = store.get('installedVendors') || [];
|
|
395
|
+
var current = store.get('currentVendor') || "claude";
|
|
396
|
+
|
|
397
|
+
var vendors = Object.keys(vendorBtns);
|
|
398
|
+
for (var i = 0; i < vendors.length; i++) {
|
|
399
|
+
var v = vendors[i];
|
|
400
|
+
var btn = vendorBtns[v];
|
|
401
|
+
if (!btn) continue;
|
|
402
|
+
var isInstalled = installed.indexOf(v) !== -1;
|
|
403
|
+
btn.classList.toggle("active", v === current);
|
|
404
|
+
btn.classList.toggle("disabled", !isInstalled);
|
|
405
|
+
btn.title = isInstalled ? (VENDOR_NAMES[v] || v) : (VENDOR_NAMES[v] || v) + " is not installed";
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function onVendorClick(vendor) {
|
|
410
|
+
if (vendor === (store.get('currentVendor') || "claude")) return;
|
|
411
|
+
var installed = store.get('installedVendors') || [];
|
|
412
|
+
if (installed.indexOf(vendor) === -1) return;
|
|
413
|
+
store.set({ currentVendor: vendor });
|
|
414
|
+
var ws = getWs();
|
|
415
|
+
if (ws) ws.send(JSON.stringify({ type: "set_vendor", vendor: vendor }));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (vendorBtnClaude) vendorBtnClaude.addEventListener("click", function() { onVendorClick("claude"); });
|
|
419
|
+
if (vendorBtnCodex) vendorBtnCodex.addEventListener("click", function() { onVendorClick("codex"); });
|
|
420
|
+
|
|
421
|
+
// --- Reactive UI sync ---
|
|
422
|
+
store.subscribe(function (state, prev) {
|
|
423
|
+
// Vendor toggle state
|
|
424
|
+
if (state.availableVendors !== prev.availableVendors ||
|
|
425
|
+
state.installedVendors !== prev.installedVendors ||
|
|
426
|
+
state.currentVendor !== prev.currentVendor) {
|
|
427
|
+
updateVendorToggle();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// richContextUsage changed -> update popover + panel
|
|
431
|
+
if (state.richContextUsage !== prev.richContextUsage) {
|
|
432
|
+
if (state.richContextUsage) {
|
|
433
|
+
var hce = store.get('headerContextEl');
|
|
434
|
+
if (hce) hce.removeAttribute("data-tip");
|
|
435
|
+
if (state.ctxPopoverVisible) renderCtxPopover();
|
|
436
|
+
} else {
|
|
437
|
+
hideCtxPopover();
|
|
438
|
+
}
|
|
439
|
+
updateContextPanel();
|
|
440
|
+
}
|
|
441
|
+
// Vendor changed -> switch model list and current model to match
|
|
442
|
+
if (state.currentVendor !== prev.currentVendor && state.currentVendor) {
|
|
443
|
+
var ws = getWs();
|
|
444
|
+
if (ws) ws.send(JSON.stringify({ type: "get_vendor_models", vendor: state.currentVendor }));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// config chip
|
|
448
|
+
if (state.currentModel !== prev.currentModel ||
|
|
449
|
+
state.currentMode !== prev.currentMode ||
|
|
450
|
+
state.currentEffort !== prev.currentEffort ||
|
|
451
|
+
state.currentBetas !== prev.currentBetas ||
|
|
452
|
+
state.currentThinking !== prev.currentThinking ||
|
|
453
|
+
state.currentVendor !== prev.currentVendor ||
|
|
454
|
+
state.codexApproval !== prev.codexApproval ||
|
|
455
|
+
state.codexSandbox !== prev.codexSandbox ||
|
|
456
|
+
state.codexWebSearch !== prev.codexWebSearch) {
|
|
457
|
+
updateConfigChip();
|
|
458
|
+
}
|
|
459
|
+
});
|
|
304
460
|
|
|
305
461
|
// Usage panel DOM refs
|
|
306
462
|
usagePanel = $("usage-panel");
|
|
@@ -353,7 +509,7 @@ export function initPanels() {
|
|
|
353
509
|
var val = parseInt(this.value, 10);
|
|
354
510
|
if (isNaN(val) || val < 1024) val = 1024;
|
|
355
511
|
if (val > 128000) val = 128000;
|
|
356
|
-
store.
|
|
512
|
+
store.set({ currentThinkingBudget: val });
|
|
357
513
|
this.value = val;
|
|
358
514
|
var ws = getWs();
|
|
359
515
|
if (ws && ws.readyState === 1) {
|
|
@@ -366,7 +522,7 @@ export function initPanels() {
|
|
|
366
522
|
configBeta1mBtn.addEventListener("click", function (e) {
|
|
367
523
|
e.stopPropagation();
|
|
368
524
|
var active = hasBeta("context-1m");
|
|
369
|
-
var betas = store.
|
|
525
|
+
var betas = store.get('currentBetas');
|
|
370
526
|
var newBetas;
|
|
371
527
|
if (active) {
|
|
372
528
|
// Remove context-1m beta
|
|
@@ -443,30 +599,27 @@ export function initPanels() {
|
|
|
443
599
|
export function updateConfigChip() {
|
|
444
600
|
if (!configChipWrap || !configChip) return;
|
|
445
601
|
configChipWrap.classList.remove("hidden");
|
|
446
|
-
var s = store.
|
|
447
|
-
var
|
|
448
|
-
|
|
449
|
-
// Only show effort if model supports it
|
|
450
|
-
var modelSupportsEffort = getModelSupportsEffort();
|
|
451
|
-
if (modelSupportsEffort) {
|
|
452
|
-
parts.push(effortDisplayName(s.currentEffort));
|
|
453
|
-
}
|
|
454
|
-
if (s.currentThinking && s.currentThinking !== "adaptive") {
|
|
455
|
-
parts.push(thinkingDisplayName(s.currentThinking));
|
|
456
|
-
}
|
|
457
|
-
if (hasBeta("context-1m")) {
|
|
458
|
-
parts.push("1M");
|
|
459
|
-
}
|
|
460
|
-
configChipLabel.textContent = parts.join(" \u00b7 ");
|
|
602
|
+
var s = store.snap();
|
|
603
|
+
var vendor = s.currentVendor || "claude";
|
|
604
|
+
configChipLabel.textContent = modelDisplayName(s.currentModel, s.currentModels);
|
|
461
605
|
rebuildModelList();
|
|
462
606
|
rebuildModeList();
|
|
463
607
|
rebuildEffortBar();
|
|
608
|
+
|
|
609
|
+
// Vendor-specific sections
|
|
610
|
+
var isClaude = vendor === "claude";
|
|
611
|
+
// MODE, THINKING, BETA are Claude-only
|
|
612
|
+
if (configModeList && configModeList.parentElement) configModeList.parentElement.style.display = isClaude ? "" : "none";
|
|
464
613
|
rebuildThinkingSection();
|
|
465
|
-
|
|
614
|
+
if (configThinkingSection) configThinkingSection.style.display = isClaude ? "" : "none";
|
|
615
|
+
// BETA section deprecated (1M context is now standard)
|
|
616
|
+
if (configBetaSection) configBetaSection.style.display = "none";
|
|
617
|
+
// APPROVAL, SANDBOX, WEB SEARCH are Codex-only
|
|
618
|
+
rebuildCodexSections();
|
|
466
619
|
}
|
|
467
620
|
|
|
468
621
|
export function getModelSupportsEffort() {
|
|
469
|
-
var s = store.
|
|
622
|
+
var s = store.snap();
|
|
470
623
|
if (!s.currentModels || s.currentModels.length === 0) return true; // assume yes if no info
|
|
471
624
|
for (var i = 0; i < s.currentModels.length; i++) {
|
|
472
625
|
if (s.currentModels[i].value === s.currentModel) {
|
|
@@ -478,17 +631,19 @@ export function getModelSupportsEffort() {
|
|
|
478
631
|
}
|
|
479
632
|
|
|
480
633
|
export function getModelEffortLevels() {
|
|
481
|
-
var s = store.
|
|
482
|
-
|
|
634
|
+
var s = store.snap();
|
|
635
|
+
var vendor = s.currentVendor || "claude";
|
|
636
|
+
var defaultLevels = EFFORT_LEVELS_BY_VENDOR[vendor] || EFFORT_LEVELS;
|
|
637
|
+
if (!s.currentModels || s.currentModels.length === 0) return defaultLevels;
|
|
483
638
|
for (var i = 0; i < s.currentModels.length; i++) {
|
|
484
639
|
if (s.currentModels[i].value === s.currentModel) {
|
|
485
640
|
if (s.currentModels[i].supportedEffortLevels && s.currentModels[i].supportedEffortLevels.length > 0) {
|
|
486
641
|
return s.currentModels[i].supportedEffortLevels;
|
|
487
642
|
}
|
|
488
|
-
return
|
|
643
|
+
return defaultLevels;
|
|
489
644
|
}
|
|
490
645
|
}
|
|
491
|
-
return
|
|
646
|
+
return defaultLevels;
|
|
492
647
|
}
|
|
493
648
|
|
|
494
649
|
// --- Usage panel ---
|
|
@@ -520,7 +675,7 @@ export function accumulateUsage(cost, usage) {
|
|
|
520
675
|
sessionUsage.cacheWrite += usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
521
676
|
}
|
|
522
677
|
sessionUsage.turns++;
|
|
523
|
-
if (!store.
|
|
678
|
+
if (!store.get('replayingHistory')) updateUsagePanel();
|
|
524
679
|
}
|
|
525
680
|
|
|
526
681
|
export function resetUsage() {
|
|
@@ -628,21 +783,21 @@ export function updateContextPanel() {
|
|
|
628
783
|
// Header bar
|
|
629
784
|
if (pct > 0) {
|
|
630
785
|
var statusArea = document.querySelector(".title-bar-content .status");
|
|
631
|
-
var hCtxEl = store.
|
|
786
|
+
var hCtxEl = store.get('headerContextEl');
|
|
632
787
|
if (statusArea && !hCtxEl) {
|
|
633
788
|
hCtxEl = document.createElement("div");
|
|
634
789
|
hCtxEl.className = "header-context";
|
|
635
790
|
hCtxEl.innerHTML = '<div class="header-context-bar"><div class="header-context-fill"></div></div><span class="header-context-label"></span>';
|
|
636
791
|
statusArea.insertBefore(hCtxEl, statusArea.firstChild);
|
|
637
792
|
hCtxEl.addEventListener("mouseenter", function() {
|
|
638
|
-
if (store.
|
|
793
|
+
if (store.get('richContextUsage')) {
|
|
639
794
|
showCtxPopover();
|
|
640
795
|
}
|
|
641
796
|
});
|
|
642
797
|
hCtxEl.addEventListener("mouseleave", function() {
|
|
643
798
|
ctxHoverTimer = setTimeout(hideCtxPopover, 120);
|
|
644
799
|
});
|
|
645
|
-
store.
|
|
800
|
+
store.set({ headerContextEl: hCtxEl });
|
|
646
801
|
}
|
|
647
802
|
if (hCtxEl) {
|
|
648
803
|
var hFill = hCtxEl.querySelector(".header-context-fill");
|
|
@@ -651,7 +806,7 @@ export function updateContextPanel() {
|
|
|
651
806
|
hFill.className = "header-context-fill" + cls;
|
|
652
807
|
hLabel.textContent = pct.toFixed(0) + "%";
|
|
653
808
|
// Use data-tip as fallback when rich data is not yet loaded
|
|
654
|
-
if (store.
|
|
809
|
+
if (store.get('richContextUsage')) {
|
|
655
810
|
hCtxEl.removeAttribute("data-tip");
|
|
656
811
|
} else {
|
|
657
812
|
hCtxEl.dataset.tip = "Context window " + pct.toFixed(0) + "% used (" + formatTokens(used) + " / " + formatTokens(win) + " tokens)";
|
|
@@ -701,7 +856,7 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
701
856
|
if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
|
|
702
857
|
}
|
|
703
858
|
}
|
|
704
|
-
if (!store.
|
|
859
|
+
if (!store.get('replayingHistory')) updateContextPanel();
|
|
705
860
|
}
|
|
706
861
|
|
|
707
862
|
// contextView: "off" | "mini" | "panel"
|
|
@@ -721,9 +876,8 @@ export function applyContextView(view) {
|
|
|
721
876
|
|
|
722
877
|
export function resetContextData() {
|
|
723
878
|
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
724
|
-
store.
|
|
725
|
-
hideCtxPopover
|
|
726
|
-
updateContextPanel();
|
|
879
|
+
store.set({ richContextUsage: null });
|
|
880
|
+
// hideCtxPopover + updateContextPanel handled by store subscriber
|
|
727
881
|
}
|
|
728
882
|
|
|
729
883
|
export function resetContext() {
|
|
@@ -770,24 +924,24 @@ export function ensureCtxPopover() {
|
|
|
770
924
|
}
|
|
771
925
|
|
|
772
926
|
export function showCtxPopover() {
|
|
773
|
-
var s = store.
|
|
927
|
+
var s = store.snap();
|
|
774
928
|
if (!s.headerContextEl || !s.richContextUsage) return;
|
|
775
929
|
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
776
930
|
ensureCtxPopover();
|
|
777
931
|
s.headerContextEl.appendChild(ctxPopoverEl);
|
|
778
932
|
renderCtxPopover();
|
|
779
933
|
ctxPopoverEl.classList.remove("hidden");
|
|
780
|
-
store.
|
|
934
|
+
store.set({ ctxPopoverVisible: true });
|
|
781
935
|
}
|
|
782
936
|
|
|
783
937
|
export function hideCtxPopover() {
|
|
784
938
|
if (!ctxPopoverEl) return;
|
|
785
939
|
ctxPopoverEl.classList.add("hidden");
|
|
786
|
-
store.
|
|
940
|
+
store.set({ ctxPopoverVisible: false });
|
|
787
941
|
}
|
|
788
942
|
|
|
789
943
|
export function renderCtxPopover() {
|
|
790
|
-
var richContextUsage = store.
|
|
944
|
+
var richContextUsage = store.get('richContextUsage');
|
|
791
945
|
if (!ctxPopoverEl || !richContextUsage) return;
|
|
792
946
|
var d = richContextUsage;
|
|
793
947
|
var cats = d.categories || [];
|