clay-server 2.32.0-beta.6 → 2.32.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/codex-defaults.js +18 -0
- package/lib/project-connection.js +5 -4
- package/lib/project-mate-interaction.js +30 -0
- package/lib/project-notifications.js +9 -0
- package/lib/project-sessions.js +7 -6
- package/lib/project.js +29 -9
- package/lib/public/app.js +5 -0
- package/lib/public/css/input.css +146 -3
- package/lib/public/css/notifications-center.css +7 -0
- package/lib/public/css/tooltip.css +47 -0
- package/lib/public/index.html +34 -4
- package/lib/public/modules/app-dm.js +5 -0
- package/lib/public/modules/app-messages.js +5 -4
- package/lib/public/modules/app-notifications.js +42 -26
- package/lib/public/modules/app-panels.js +3 -3
- package/lib/public/modules/context-sources.js +92 -27
- package/lib/public/modules/input.js +36 -0
- package/lib/public/modules/terminal.js +12 -0
- package/lib/public/modules/tools.js +4 -4
- package/lib/public/modules/tooltip.js +32 -5
- package/lib/sdk-bridge.js +7 -3
- package/lib/sdk-message-processor.js +9 -0
- package/lib/sessions.js +24 -2
- package/lib/yoke/adapters/claude.js +13 -0
- package/lib/yoke/adapters/codex.js +119 -8
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { getWs } from './ws-ref.js';
|
|
|
9
9
|
import { openDm } from './app-dm.js';
|
|
10
10
|
import { getCachedProjects } from './app-projects.js';
|
|
11
11
|
import { switchProject } from './app-projects.js';
|
|
12
|
+
import { mateAvatarUrl } from './avatar.js';
|
|
12
13
|
var notifications = [];
|
|
13
14
|
var unreadCount = 0;
|
|
14
15
|
var bannerContainer = null;
|
|
@@ -16,10 +17,10 @@ var bellBtn = null;
|
|
|
16
17
|
var badgeEl = null;
|
|
17
18
|
|
|
18
19
|
// --- Update available banner state ---
|
|
20
|
+
// Server pushes update_available on an hourly boundary; dismissal is
|
|
21
|
+
// per-banner-instance and doesn't need to persist. The next server push
|
|
22
|
+
// (next hour) acts as a fresh ping.
|
|
19
23
|
var pendingUpdateMsg = null;
|
|
20
|
-
var updateReshowTimer = null;
|
|
21
|
-
var updateDismissedAt = 0;
|
|
22
|
-
var UPDATE_RESHOW_INTERVAL = 60 * 60 * 1000; // 1 hour
|
|
23
24
|
|
|
24
25
|
// ========================================================
|
|
25
26
|
// Init
|
|
@@ -50,10 +51,12 @@ function showAllBanners() {
|
|
|
50
51
|
// Clear existing banners first
|
|
51
52
|
if (bannerContainer) bannerContainer.innerHTML = "";
|
|
52
53
|
|
|
53
|
-
// Re-add update banner if present
|
|
54
|
+
// Re-add update banner if present (may be suppressed by recent dismiss)
|
|
54
55
|
if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
|
|
55
56
|
|
|
56
|
-
if (
|
|
57
|
+
// Check if any banner actually got rendered (update banner can be suppressed)
|
|
58
|
+
var hasVisibleBanner = bannerContainer.children.length > 0;
|
|
59
|
+
if (notifications.length === 0 && !hasVisibleBanner) {
|
|
57
60
|
showBanner({
|
|
58
61
|
id: "_empty",
|
|
59
62
|
type: "info",
|
|
@@ -80,14 +83,17 @@ function showBanner(notif, autoDismissMs) {
|
|
|
80
83
|
var projectIcon = isEmpty ? null : getProjectIcon(notif.slug);
|
|
81
84
|
var projectName = isEmpty ? "" : getProjectName(notif.slug);
|
|
82
85
|
var isPermission = notif.type === "permission_request" && notif.meta && notif.meta.requestId;
|
|
86
|
+
var mate = isEmpty ? null : getMateForNotification(notif);
|
|
83
87
|
|
|
84
88
|
var banner = document.createElement("div");
|
|
85
89
|
banner.className = "notif-banner" + (isPermission ? " notif-banner-permission" : "");
|
|
86
90
|
if (!isEmpty) banner.setAttribute("data-notif-id", notif.id);
|
|
87
91
|
|
|
88
|
-
var iconHtmlStr =
|
|
89
|
-
? '<
|
|
90
|
-
:
|
|
92
|
+
var iconHtmlStr = mate
|
|
93
|
+
? '<img class="notif-banner-avatar" src="' + escapeHtml(mateAvatarUrl(mate, 32)) + '" alt="' + escapeHtml(mate.displayName || mate.name || "Mate") + '">'
|
|
94
|
+
: projectIcon
|
|
95
|
+
? '<span class="notif-banner-emoji">' + projectIcon + '</span>'
|
|
96
|
+
: iconHtml(isEmpty ? "check-circle" : "folder");
|
|
91
97
|
|
|
92
98
|
// Format permission title as "Can I ..." style
|
|
93
99
|
if (isPermission && notif.meta) {
|
|
@@ -102,7 +108,7 @@ function showBanner(notif, autoDismissMs) {
|
|
|
102
108
|
actionsHtml =
|
|
103
109
|
'<div class="notif-banner-actions">' +
|
|
104
110
|
'<button class="notif-banner-allow">Sure</button>' +
|
|
105
|
-
'<button class="notif-banner-always">
|
|
111
|
+
'<button class="notif-banner-always">Allow for session</button>' +
|
|
106
112
|
'<button class="notif-banner-deny">No</button>' +
|
|
107
113
|
'<button class="notif-banner-goto" title="Go to session">' + iconHtml("external-link") + '</button>' +
|
|
108
114
|
'</div>';
|
|
@@ -301,8 +307,12 @@ function updateBadge() {
|
|
|
301
307
|
// ========================================================
|
|
302
308
|
|
|
303
309
|
function navigateToNotification(notif) {
|
|
304
|
-
|
|
305
|
-
|
|
310
|
+
var mateId = notif.mateId || deriveMateIdFromNotification(notif);
|
|
311
|
+
if (mateId) {
|
|
312
|
+
if (notif.sessionId) {
|
|
313
|
+
try { sessionStorage.setItem("pending-notif-session", notif.sessionId); } catch (e) {}
|
|
314
|
+
}
|
|
315
|
+
openDm(mateId);
|
|
306
316
|
return;
|
|
307
317
|
}
|
|
308
318
|
|
|
@@ -323,6 +333,25 @@ function navigateToNotification(notif) {
|
|
|
323
333
|
}
|
|
324
334
|
}
|
|
325
335
|
|
|
336
|
+
function deriveMateIdFromNotification(notif) {
|
|
337
|
+
if (!notif) return null;
|
|
338
|
+
if (typeof notif.slug === "string" && notif.slug.indexOf("mate-") === 0) {
|
|
339
|
+
return notif.slug.substring(5) || null;
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function getMateForNotification(notif) {
|
|
345
|
+
var mateId = notif && notif.meta ? notif.meta.avatarMateId : null;
|
|
346
|
+
if (!mateId) mateId = deriveMateIdFromNotification(notif);
|
|
347
|
+
if (!mateId) return null;
|
|
348
|
+
var mates = store.get('cachedMatesList') || [];
|
|
349
|
+
for (var i = 0; i < mates.length; i++) {
|
|
350
|
+
if (mates[i] && mates[i].id === mateId) return mates[i];
|
|
351
|
+
}
|
|
352
|
+
return { id: mateId };
|
|
353
|
+
}
|
|
354
|
+
|
|
326
355
|
// ========================================================
|
|
327
356
|
// Update available banner
|
|
328
357
|
// ========================================================
|
|
@@ -332,9 +361,6 @@ export function showUpdateBanner(msg) {
|
|
|
332
361
|
pendingUpdateMsg = msg;
|
|
333
362
|
if (!bannerContainer) return;
|
|
334
363
|
|
|
335
|
-
// If user dismissed recently, skip until reshow timer fires
|
|
336
|
-
if (updateDismissedAt && (Date.now() - updateDismissedAt) < UPDATE_RESHOW_INTERVAL) return;
|
|
337
|
-
|
|
338
364
|
// Remove any existing update banner
|
|
339
365
|
var existing = bannerContainer.querySelector('[data-notif-id="_update"]');
|
|
340
366
|
if (existing) removeBanner(existing);
|
|
@@ -387,27 +413,17 @@ export function showUpdateBanner(msg) {
|
|
|
387
413
|
});
|
|
388
414
|
}
|
|
389
415
|
|
|
390
|
-
// Close button -> dismiss
|
|
416
|
+
// Close button -> dismiss. No local throttle; the server pushes a new
|
|
417
|
+
// update_available on the next hour boundary, which re-shows naturally.
|
|
391
418
|
var closeBtn = banner.querySelector(".notif-banner-close");
|
|
392
419
|
if (closeBtn) {
|
|
393
420
|
closeBtn.addEventListener("click", function (e) {
|
|
394
421
|
e.stopPropagation();
|
|
395
422
|
removeBanner(banner);
|
|
396
|
-
scheduleUpdateReshow();
|
|
397
423
|
});
|
|
398
424
|
}
|
|
399
425
|
}
|
|
400
426
|
|
|
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
|
-
|
|
411
427
|
// ========================================================
|
|
412
428
|
// Helpers
|
|
413
429
|
// ========================================================
|
|
@@ -335,15 +335,15 @@ function rebuildCodexSections() {
|
|
|
335
335
|
|
|
336
336
|
if (configApprovalSection) {
|
|
337
337
|
configApprovalSection.style.display = isCodex ? "" : "none";
|
|
338
|
-
if (isCodex) buildSegmentedBar(configApprovalBar, CODEX_APPROVAL_OPTIONS, s.codexApproval
|
|
338
|
+
if (isCodex) buildSegmentedBar(configApprovalBar, CODEX_APPROVAL_OPTIONS, s.codexApproval, "set_codex_approval", "approval");
|
|
339
339
|
}
|
|
340
340
|
if (configSandboxSection) {
|
|
341
341
|
configSandboxSection.style.display = isCodex ? "" : "none";
|
|
342
|
-
if (isCodex) buildSegmentedBar(configSandboxBar, CODEX_SANDBOX_OPTIONS, s.codexSandbox
|
|
342
|
+
if (isCodex) buildSegmentedBar(configSandboxBar, CODEX_SANDBOX_OPTIONS, s.codexSandbox, "set_codex_sandbox", "sandbox");
|
|
343
343
|
}
|
|
344
344
|
if (configWebsearchSection) {
|
|
345
345
|
configWebsearchSection.style.display = isCodex ? "" : "none";
|
|
346
|
-
if (isCodex) buildSegmentedBar(configWebsearchBar, CODEX_WEBSEARCH_OPTIONS, s.codexWebSearch
|
|
346
|
+
if (isCodex) buildSegmentedBar(configWebsearchBar, CODEX_WEBSEARCH_OPTIONS, s.codexWebSearch, "set_codex_websearch", "webSearch");
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -17,6 +17,8 @@ export function initContextSources(_ctx) {
|
|
|
17
17
|
|
|
18
18
|
var addBtn = document.getElementById("context-sources-add");
|
|
19
19
|
var picker = document.getElementById("context-sources-picker");
|
|
20
|
+
// Suppress tooltip when the picker is open
|
|
21
|
+
if (addBtn) addBtn.setAttribute("data-tip-suppress-when-open", "#context-sources-picker");
|
|
20
22
|
|
|
21
23
|
addBtn.addEventListener("click", function(e) {
|
|
22
24
|
e.stopPropagation();
|
|
@@ -36,8 +38,22 @@ export function initContextSources(_ctx) {
|
|
|
36
38
|
|
|
37
39
|
function closePicker() {
|
|
38
40
|
var picker = document.getElementById("context-sources-picker");
|
|
39
|
-
picker.classList.add("hidden");
|
|
41
|
+
if (picker) picker.classList.add("hidden");
|
|
40
42
|
document.removeEventListener("click", closePicker, true);
|
|
43
|
+
// Also close mobile bottom sheet if open
|
|
44
|
+
var moreSheet = document.getElementById("input-more-sheet");
|
|
45
|
+
if (moreSheet && moreSheet.classList.contains("open")) {
|
|
46
|
+
moreSheet.classList.remove("open");
|
|
47
|
+
setTimeout(function () { moreSheet.classList.add("hidden"); }, 250);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Re-render all open picker surfaces (desktop popover and mobile bottom sheet)
|
|
52
|
+
function renderAllOpen() {
|
|
53
|
+
var picker = document.getElementById("context-sources-picker");
|
|
54
|
+
if (picker && !picker.classList.contains("hidden")) renderPicker();
|
|
55
|
+
var moreSheet = document.getElementById("input-more-sheet");
|
|
56
|
+
if (moreSheet && moreSheet.classList.contains("open")) renderPicker("-mobile");
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
// Restore state from server
|
|
@@ -80,11 +96,7 @@ export function updateTerminalList(terminals) {
|
|
|
80
96
|
if (changed) saveToServer();
|
|
81
97
|
renderChips();
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
var picker = document.getElementById("context-sources-picker");
|
|
85
|
-
if (!picker.classList.contains("hidden")) {
|
|
86
|
-
renderPicker();
|
|
87
|
-
}
|
|
99
|
+
renderAllOpen();
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
// Called when Chrome extension sends tab list via postMessage
|
|
@@ -110,11 +122,7 @@ export function updateBrowserTabList(tabs) {
|
|
|
110
122
|
if (changed) saveToServer();
|
|
111
123
|
renderChips();
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
var picker = document.getElementById("context-sources-picker");
|
|
115
|
-
if (!picker.classList.contains("hidden")) {
|
|
116
|
-
renderPicker();
|
|
117
|
-
}
|
|
125
|
+
renderAllOpen();
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
// Called when email_accounts_list arrives from server
|
|
@@ -141,10 +149,7 @@ export function updateEmailAccountList(msg) {
|
|
|
141
149
|
if (changed) saveToServer();
|
|
142
150
|
renderChips();
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
if (!picker.classList.contains("hidden")) {
|
|
146
|
-
renderPicker();
|
|
147
|
-
}
|
|
152
|
+
renderAllOpen();
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
// Called when email_unread_update arrives from server
|
|
@@ -152,10 +157,7 @@ export function updateEmailUnreadCounts(msg) {
|
|
|
152
157
|
emailUnreadCounts = msg.unread || {};
|
|
153
158
|
renderChips();
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
if (!picker.classList.contains("hidden")) {
|
|
157
|
-
renderPicker();
|
|
158
|
-
}
|
|
160
|
+
renderAllOpen();
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
function toggleSource(sourceId) {
|
|
@@ -166,7 +168,7 @@ function toggleSource(sourceId) {
|
|
|
166
168
|
}
|
|
167
169
|
saveToServer();
|
|
168
170
|
renderChips();
|
|
169
|
-
|
|
171
|
+
renderAllOpen();
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
function removeSource(sourceId) {
|
|
@@ -174,10 +176,56 @@ function removeSource(sourceId) {
|
|
|
174
176
|
saveToServer();
|
|
175
177
|
renderChips();
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
renderAllOpen();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildActiveSourceRow(iconHtml, text) {
|
|
183
|
+
return '<div class="ctx-tip-row">' + iconHtml + '<span>' + escapeHtml(text) + '</span></div>';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getActiveSourceRowsHTML() {
|
|
187
|
+
var rows = [];
|
|
188
|
+
for (var id of activeSourceIds) {
|
|
189
|
+
var parts = id.split(":");
|
|
190
|
+
var type = parts[0];
|
|
191
|
+
var key = parts.slice(1).join(":");
|
|
192
|
+
if (type === "term") {
|
|
193
|
+
for (var i = 0; i < terminalList.length; i++) {
|
|
194
|
+
if (String(terminalList[i].id) === key) {
|
|
195
|
+
rows.push(buildActiveSourceRow(
|
|
196
|
+
'<i data-lucide="square-terminal"></i>',
|
|
197
|
+
terminalList[i].title || ("Terminal " + key)
|
|
198
|
+
));
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else if (type === "tab") {
|
|
203
|
+
var tabId = parseInt(key, 10);
|
|
204
|
+
for (var j = 0; j < browserTabList.length; j++) {
|
|
205
|
+
if (browserTabList[j].id === tabId) {
|
|
206
|
+
var t = browserTabList[j];
|
|
207
|
+
var title = t.title || t.url || "Tab";
|
|
208
|
+
if (title.length > 50) title = title.slice(0, 47) + "...";
|
|
209
|
+
var faviconHtml = t.favIconUrl
|
|
210
|
+
? '<img src="' + escapeHtml(t.favIconUrl) + '" class="ctx-tip-favicon" onerror="this.style.display=\'none\'">'
|
|
211
|
+
: '<i data-lucide="globe"></i>';
|
|
212
|
+
rows.push(buildActiveSourceRow(faviconHtml, title));
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} else if (type === "email") {
|
|
217
|
+
for (var k = 0; k < emailAccountList.length; k++) {
|
|
218
|
+
if (emailAccountList[k].id === key) {
|
|
219
|
+
rows.push(buildActiveSourceRow(
|
|
220
|
+
'<i data-lucide="mail"></i>',
|
|
221
|
+
emailAccountList[k].email
|
|
222
|
+
));
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
180
227
|
}
|
|
228
|
+
return rows;
|
|
181
229
|
}
|
|
182
230
|
|
|
183
231
|
function renderChips() {
|
|
@@ -193,15 +241,30 @@ function renderChips() {
|
|
|
193
241
|
addBtn.appendChild(existingBadge);
|
|
194
242
|
}
|
|
195
243
|
existingBadge.textContent = activeSourceIds.size;
|
|
244
|
+
var rows = getActiveSourceRowsHTML();
|
|
245
|
+
if (rows.length > 0) {
|
|
246
|
+
var html = '<div class="ctx-tip-header">Active context sources</div>' + rows.join("");
|
|
247
|
+
addBtn.setAttribute("data-tip-html", html);
|
|
248
|
+
addBtn.removeAttribute("data-tip");
|
|
249
|
+
} else {
|
|
250
|
+
addBtn.setAttribute("data-tip", "Add context sources");
|
|
251
|
+
addBtn.removeAttribute("data-tip-html");
|
|
252
|
+
}
|
|
253
|
+
addBtn.removeAttribute("title");
|
|
196
254
|
} else {
|
|
197
255
|
if (labelSpan) { labelSpan.style.display = ""; }
|
|
198
256
|
if (existingBadge) existingBadge.remove();
|
|
257
|
+
addBtn.setAttribute("data-tip", "Add context sources");
|
|
258
|
+
addBtn.removeAttribute("data-tip-html");
|
|
259
|
+
addBtn.removeAttribute("title");
|
|
199
260
|
}
|
|
200
261
|
}
|
|
201
262
|
|
|
202
|
-
function renderPicker() {
|
|
263
|
+
export function renderPicker(suffix) {
|
|
264
|
+
suffix = suffix || "";
|
|
203
265
|
// --- Terminals section ---
|
|
204
|
-
var termSection = document.getElementById("context-picker-terminals");
|
|
266
|
+
var termSection = document.getElementById("context-picker-terminals" + suffix);
|
|
267
|
+
if (!termSection) return;
|
|
205
268
|
termSection.innerHTML = "";
|
|
206
269
|
|
|
207
270
|
var termLabel = document.createElement("div");
|
|
@@ -239,7 +302,8 @@ function renderPicker() {
|
|
|
239
302
|
}
|
|
240
303
|
|
|
241
304
|
// --- Browser Tabs section ---
|
|
242
|
-
var tabSection = document.getElementById("context-picker-tabs");
|
|
305
|
+
var tabSection = document.getElementById("context-picker-tabs" + suffix);
|
|
306
|
+
if (!tabSection) return;
|
|
243
307
|
tabSection.innerHTML = "";
|
|
244
308
|
|
|
245
309
|
var tabLabel = document.createElement("div");
|
|
@@ -299,7 +363,8 @@ function renderPicker() {
|
|
|
299
363
|
}
|
|
300
364
|
|
|
301
365
|
// --- Email Accounts section ---
|
|
302
|
-
var emailSection = document.getElementById("context-picker-email");
|
|
366
|
+
var emailSection = document.getElementById("context-picker-email" + suffix);
|
|
367
|
+
if (!emailSection) return;
|
|
303
368
|
emailSection.innerHTML = "";
|
|
304
369
|
|
|
305
370
|
var emailLabel = document.createElement("div");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { setRewindMode, isRewindMode } from './rewind.js';
|
|
3
|
+
import { renderPicker as renderContextPicker } from './context-sources.js';
|
|
3
4
|
import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible, mentionMenuKeydown, setMentionAtIdx, parseMentionFromInput, clearMentionState, stickyReapplyMention, sendMention, renderMentionUser, removeMentionChip } from './mention.js';
|
|
4
5
|
import { store } from './store.js';
|
|
5
6
|
import { mateAvatarUrl } from './avatar.js';
|
|
@@ -657,6 +658,41 @@ export function initInput(_ctx) {
|
|
|
657
658
|
});
|
|
658
659
|
}
|
|
659
660
|
|
|
661
|
+
// Mobile "+" button -> unified bottom sheet with attach/image + context sources
|
|
662
|
+
var moreBtn = document.getElementById("input-more-btn");
|
|
663
|
+
var moreSheet = document.getElementById("input-more-sheet");
|
|
664
|
+
function openMoreSheet() {
|
|
665
|
+
if (!moreSheet) return;
|
|
666
|
+
// Render context sources into mobile sheet containers
|
|
667
|
+
try { renderContextPicker("-mobile"); } catch (e) {}
|
|
668
|
+
moreSheet.classList.remove("hidden");
|
|
669
|
+
requestAnimationFrame(function () { moreSheet.classList.add("open"); });
|
|
670
|
+
}
|
|
671
|
+
function closeMoreSheet() {
|
|
672
|
+
if (!moreSheet) return;
|
|
673
|
+
moreSheet.classList.remove("open");
|
|
674
|
+
setTimeout(function () { moreSheet.classList.add("hidden"); }, 250);
|
|
675
|
+
}
|
|
676
|
+
if (moreBtn && moreSheet) {
|
|
677
|
+
moreBtn.addEventListener("click", function (e) {
|
|
678
|
+
e.stopPropagation();
|
|
679
|
+
openMoreSheet();
|
|
680
|
+
});
|
|
681
|
+
var backdrop = moreSheet.querySelector(".input-more-backdrop");
|
|
682
|
+
if (backdrop) backdrop.addEventListener("click", closeMoreSheet);
|
|
683
|
+
|
|
684
|
+
var moreAttach = document.getElementById("input-more-attach");
|
|
685
|
+
if (moreAttach) moreAttach.addEventListener("click", function () {
|
|
686
|
+
closeMoreSheet();
|
|
687
|
+
createFileInput(null, null, true);
|
|
688
|
+
});
|
|
689
|
+
var moreImage = document.getElementById("input-more-image");
|
|
690
|
+
if (moreImage) moreImage.addEventListener("click", function () {
|
|
691
|
+
closeMoreSheet();
|
|
692
|
+
createFileInput("image/*", null, true);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
660
696
|
// Schedule button — inline expand with minute input
|
|
661
697
|
var scheduleBtn = document.getElementById("schedule-btn");
|
|
662
698
|
var scheduleInlineInput = null;
|
|
@@ -152,6 +152,18 @@ export function initTerminal(_ctx) {
|
|
|
152
152
|
fitTerminal();
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
// Header toggle button
|
|
156
|
+
var toggleBtn = document.getElementById("terminal-toggle-btn");
|
|
157
|
+
if (toggleBtn) {
|
|
158
|
+
toggleBtn.addEventListener("click", function () {
|
|
159
|
+
if (isOpen && !ctx.terminalContainerEl.classList.contains("hidden")) {
|
|
160
|
+
closeTerminal();
|
|
161
|
+
} else {
|
|
162
|
+
openTerminal();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
155
167
|
// Sidebar terminal button
|
|
156
168
|
var sidebarTermBtn = document.getElementById("terminal-sidebar-btn");
|
|
157
169
|
if (sidebarTermBtn) {
|
|
@@ -569,7 +569,7 @@ function renderFormalPermission(requestId, toolName, toolInput, decisionReason)
|
|
|
569
569
|
|
|
570
570
|
var allowAlwaysBtn = document.createElement("button");
|
|
571
571
|
allowAlwaysBtn.className = "permission-btn permission-allow-session";
|
|
572
|
-
allowAlwaysBtn.textContent = "
|
|
572
|
+
allowAlwaysBtn.textContent = "Allow for Session";
|
|
573
573
|
allowAlwaysBtn.addEventListener("click", function () {
|
|
574
574
|
sendPermissionResponse(container, requestId, "allow_always");
|
|
575
575
|
});
|
|
@@ -856,7 +856,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId,
|
|
|
856
856
|
|
|
857
857
|
var alwaysBtn = document.createElement("button");
|
|
858
858
|
alwaysBtn.className = "mate-permission-reply mate-permission-always";
|
|
859
|
-
alwaysBtn.textContent = "
|
|
859
|
+
alwaysBtn.textContent = "Allow for session";
|
|
860
860
|
alwaysBtn.addEventListener("click", function () {
|
|
861
861
|
sendPermissionResponse(container, requestId, "allow_always");
|
|
862
862
|
});
|
|
@@ -887,7 +887,7 @@ function sendPermissionResponse(container, requestId, decision) {
|
|
|
887
887
|
container.classList.add("resolved");
|
|
888
888
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
889
889
|
|
|
890
|
-
var label = decision === "deny" ? "Denied" : "Allowed";
|
|
890
|
+
var label = decision === "deny" ? "Denied" : decision === "allow_always" ? "Allowed for session" : "Allowed";
|
|
891
891
|
var resolvedClass = decision === "deny" ? "resolved-denied" : "resolved-allowed";
|
|
892
892
|
container.classList.add(resolvedClass);
|
|
893
893
|
|
|
@@ -928,7 +928,7 @@ export function markPermissionResolved(requestId, decision) {
|
|
|
928
928
|
var resolvedClass = isDeny ? "resolved-denied" : "resolved-allowed";
|
|
929
929
|
container.classList.add(resolvedClass);
|
|
930
930
|
|
|
931
|
-
var label = planLabelMap[decision] || (decision === "deny" ? "Denied" : "Allowed");
|
|
931
|
+
var label = planLabelMap[decision] || (decision === "deny" ? "Denied" : decision === "allow_always" ? "Allowed for session" : "Allowed");
|
|
932
932
|
var actions = container.querySelector(".permission-actions") || container.querySelector(".plan-permission-actions");
|
|
933
933
|
if (actions) {
|
|
934
934
|
actions.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
var tooltipEl = null;
|
|
7
7
|
var showTimer = null;
|
|
8
8
|
var SHOW_DELAY = 120;
|
|
9
|
+
var currentTarget = null;
|
|
9
10
|
|
|
10
11
|
function initTooltips() {
|
|
11
12
|
// Create singleton tooltip element
|
|
@@ -18,14 +19,17 @@ function initTooltips() {
|
|
|
18
19
|
|
|
19
20
|
// Delegate hover events on document for [data-tip]
|
|
20
21
|
document.addEventListener("mouseover", function (e) {
|
|
21
|
-
var target = e.target.closest("[data-tip]");
|
|
22
|
+
var target = e.target.closest("[data-tip], [data-tip-html]");
|
|
22
23
|
if (!target) return;
|
|
24
|
+
if (target === currentTarget) return; // already showing for this target
|
|
23
25
|
scheduleShow(target);
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
document.addEventListener("mouseout", function (e) {
|
|
27
|
-
var target = e.target.closest("[data-tip]");
|
|
29
|
+
var target = e.target.closest("[data-tip], [data-tip-html]");
|
|
28
30
|
if (!target) return;
|
|
31
|
+
// Only hide if we're leaving the target entirely (not moving to a child)
|
|
32
|
+
if (e.relatedTarget && target.contains(e.relatedTarget)) return;
|
|
29
33
|
cancelShow();
|
|
30
34
|
hideTooltip();
|
|
31
35
|
});
|
|
@@ -80,10 +84,32 @@ function cancelShow() {
|
|
|
80
84
|
|
|
81
85
|
function showTooltipAt(target) {
|
|
82
86
|
if (!tooltipEl) return;
|
|
87
|
+
// Suppress tooltip when an associated popover/picker is open
|
|
88
|
+
var suppressSel = target.getAttribute("data-tip-suppress-when-open");
|
|
89
|
+
if (suppressSel) {
|
|
90
|
+
var openEl = document.querySelector(suppressSel);
|
|
91
|
+
if (openEl && !openEl.classList.contains("hidden")) return;
|
|
92
|
+
}
|
|
93
|
+
var html = target.getAttribute("data-tip-html");
|
|
83
94
|
var text = target.getAttribute("data-tip");
|
|
84
|
-
if (!text) return;
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
if (!html && !text) return;
|
|
96
|
+
currentTarget = target;
|
|
97
|
+
|
|
98
|
+
if (html) {
|
|
99
|
+
tooltipEl.innerHTML = html;
|
|
100
|
+
tooltipEl.classList.add("multi-line");
|
|
101
|
+
// Render any lucide icons inside the tooltip
|
|
102
|
+
if (typeof window !== "undefined" && window.lucide && window.lucide.createIcons) {
|
|
103
|
+
window.lucide.createIcons({ icons: window.lucide.icons, nameAttr: "data-lucide", root: tooltipEl });
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
tooltipEl.textContent = text;
|
|
107
|
+
if (text.indexOf("\n") !== -1) {
|
|
108
|
+
tooltipEl.classList.add("multi-line");
|
|
109
|
+
} else {
|
|
110
|
+
tooltipEl.classList.remove("multi-line");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
87
113
|
tooltipEl.style.top = "-9999px";
|
|
88
114
|
tooltipEl.style.left = "0";
|
|
89
115
|
tooltipEl.style.right = "";
|
|
@@ -120,6 +146,7 @@ function hideTooltip() {
|
|
|
120
146
|
if (tooltipEl) {
|
|
121
147
|
tooltipEl.classList.remove("visible");
|
|
122
148
|
}
|
|
149
|
+
currentTarget = null;
|
|
123
150
|
}
|
|
124
151
|
|
|
125
152
|
export { initTooltips, registerTooltip };
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -3,6 +3,7 @@ var fs = require("fs");
|
|
|
3
3
|
var path = require("path");
|
|
4
4
|
var { execSync } = require("child_process");
|
|
5
5
|
var usersModule = require("./users");
|
|
6
|
+
var { getCodexConfig } = require("./codex-defaults");
|
|
6
7
|
var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
|
|
7
8
|
var { createMessageQueue } = require("./sdk-message-queue");
|
|
8
9
|
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
@@ -1037,6 +1038,7 @@ function createSDKBridge(opts) {
|
|
|
1037
1038
|
}
|
|
1038
1039
|
}
|
|
1039
1040
|
|
|
1041
|
+
var codexConfig = getCodexConfig(sm);
|
|
1040
1042
|
var queryOpts = {
|
|
1041
1043
|
cwd: cwd,
|
|
1042
1044
|
model: queryModel,
|
|
@@ -1058,8 +1060,8 @@ function createSDKBridge(opts) {
|
|
|
1058
1060
|
// Codex's native approval prompts are terminal-based and cannot be
|
|
1059
1061
|
// relayed through Clay's web UI, causing MCP tool calls to hang.
|
|
1060
1062
|
approvalPolicy: "never",
|
|
1061
|
-
sandboxMode:
|
|
1062
|
-
webSearchMode:
|
|
1063
|
+
sandboxMode: codexConfig.sandbox,
|
|
1064
|
+
webSearchMode: codexConfig.webSearch,
|
|
1063
1065
|
},
|
|
1064
1066
|
},
|
|
1065
1067
|
};
|
|
@@ -1238,7 +1240,8 @@ function createSDKBridge(opts) {
|
|
|
1238
1240
|
}
|
|
1239
1241
|
}
|
|
1240
1242
|
sm.slashCommands = combined;
|
|
1241
|
-
|
|
1243
|
+
sm.setSlashCommandsForVendor(defaultVendor, combined);
|
|
1244
|
+
send({ type: "slash_commands", commands: combined, vendor: defaultVendor });
|
|
1242
1245
|
}
|
|
1243
1246
|
if (result.defaultModel) {
|
|
1244
1247
|
sm.currentModel = sm.currentModel || sm._savedDefaultModel || result.defaultModel;
|
|
@@ -1266,6 +1269,7 @@ function createSDKBridge(opts) {
|
|
|
1266
1269
|
adapters[v].init({ cwd: cwd, clayPort: clayPort, clayTls: clayTls, clayAuthToken: clayAuthToken, slug: slug }).then(function(r) {
|
|
1267
1270
|
sm.modelsByVendor[v] = r.models || [];
|
|
1268
1271
|
sm.capabilitiesByVendor[v] = r.capabilities || {};
|
|
1272
|
+
if (r.slashCommands) sm.setSlashCommandsForVendor(v, r.slashCommands);
|
|
1269
1273
|
}).catch(function(e) {
|
|
1270
1274
|
console.error("[sdk-bridge] warmup: " + v + " init failed:", e.message || e);
|
|
1271
1275
|
});
|
|
@@ -20,6 +20,14 @@ function attachMessageProcessor(ctx) {
|
|
|
20
20
|
|
|
21
21
|
var AUTO_TITLE_TURN_THRESHOLD = 3;
|
|
22
22
|
|
|
23
|
+
function getMateIdForNotification() {
|
|
24
|
+
if (!isMate) return null;
|
|
25
|
+
if (typeof slug === "string" && slug.indexOf("mate-") === 0) {
|
|
26
|
+
return slug.substring(5) || null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
function sendAndRecord(session, obj) {
|
|
24
32
|
sm.sendAndRecord(session, obj);
|
|
25
33
|
}
|
|
@@ -404,6 +412,7 @@ function attachMessageProcessor(ctx) {
|
|
|
404
412
|
preview: _donePreviewText,
|
|
405
413
|
slug: slug,
|
|
406
414
|
sessionId: session.localId,
|
|
415
|
+
mateId: getMateIdForNotification(),
|
|
407
416
|
ownerId: session.ownerId || null,
|
|
408
417
|
});
|
|
409
418
|
}
|
package/lib/sessions.js
CHANGED
|
@@ -3,6 +3,7 @@ var path = require("path");
|
|
|
3
3
|
var config = require("./config");
|
|
4
4
|
var utils = require("./utils");
|
|
5
5
|
var users = require("./users");
|
|
6
|
+
var { CODEX_DEFAULTS } = require("./codex-defaults");
|
|
6
7
|
|
|
7
8
|
function createSessionManager(opts) {
|
|
8
9
|
var cwd = opts.cwd;
|
|
@@ -16,12 +17,16 @@ function createSessionManager(opts) {
|
|
|
16
17
|
var nextLocalId = 1;
|
|
17
18
|
var sessions = new Map(); // localId -> session object
|
|
18
19
|
var activeSessionId = null; // currently active local ID
|
|
19
|
-
var slashCommands = null; // shared across sessions
|
|
20
|
+
var slashCommands = null; // shared across sessions (deprecated, use slashCommandsByVendor)
|
|
21
|
+
var slashCommandsByVendor = {}; // vendor -> array of slash commands
|
|
20
22
|
var skillNames = null; // Claude-only skills to filter from slash menu
|
|
21
23
|
var singleUserUnread = {}; // sessionLocalId -> unread count (single-user mode)
|
|
22
24
|
var permissionRequestIndex = {}; // requestId -> sessionLocalId (O(1) lookup)
|
|
23
25
|
var capabilitiesByVendor = null; // set by sdk-bridge after adapter init
|
|
24
26
|
var defaultVendor = null; // set by sdk-bridge
|
|
27
|
+
var codexApproval = CODEX_DEFAULTS.approval;
|
|
28
|
+
var codexSandbox = CODEX_DEFAULTS.sandbox;
|
|
29
|
+
var codexWebSearch = CODEX_DEFAULTS.webSearch;
|
|
25
30
|
|
|
26
31
|
// --- Session persistence (centralized in ~/.clay/sessions/{encoded-cwd}/) ---
|
|
27
32
|
var sessionsBase = path.join(config.CONFIG_DIR, "sessions");
|
|
@@ -403,8 +408,12 @@ function createSessionManager(opts) {
|
|
|
403
408
|
var _send = (targetWs && sendTo) ? function (obj) { sendTo(targetWs, obj); } : send;
|
|
404
409
|
|
|
405
410
|
var _capsByVendor = capabilitiesByVendor || {};
|
|
406
|
-
var
|
|
411
|
+
var _sessionVendor = session.vendor || defaultVendor || "claude";
|
|
412
|
+
var _vendorCaps = _capsByVendor[_sessionVendor] || {};
|
|
407
413
|
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null, vendor: session.vendor || null, hasHistory: (session.history && session.history.length > 0), capabilities: _vendorCaps });
|
|
414
|
+
// Send vendor-specific slash commands
|
|
415
|
+
var _vendorCmds = slashCommandsByVendor[_sessionVendor] || slashCommands || [];
|
|
416
|
+
_send({ type: "slash_commands", commands: _vendorCmds, vendor: _sessionVendor });
|
|
408
417
|
broadcastSessionList();
|
|
409
418
|
replayHistory(session, undefined, targetWs, transform);
|
|
410
419
|
|
|
@@ -750,12 +759,25 @@ function createSessionManager(opts) {
|
|
|
750
759
|
get nextLocalId() { return nextLocalId; },
|
|
751
760
|
get slashCommands() { return slashCommands; },
|
|
752
761
|
set slashCommands(v) { slashCommands = v; },
|
|
762
|
+
get slashCommandsByVendor() { return slashCommandsByVendor; },
|
|
763
|
+
setSlashCommandsForVendor: function(vendor, cmds) {
|
|
764
|
+
slashCommandsByVendor[vendor] = cmds || [];
|
|
765
|
+
},
|
|
766
|
+
getSlashCommandsForVendor: function(vendor) {
|
|
767
|
+
return slashCommandsByVendor[vendor] || [];
|
|
768
|
+
},
|
|
753
769
|
get skillNames() { return skillNames; },
|
|
754
770
|
set skillNames(v) { skillNames = v; },
|
|
755
771
|
get capabilitiesByVendor() { return capabilitiesByVendor; },
|
|
756
772
|
set capabilitiesByVendor(v) { capabilitiesByVendor = v; },
|
|
757
773
|
get defaultVendor() { return defaultVendor; },
|
|
758
774
|
set defaultVendor(v) { defaultVendor = v; },
|
|
775
|
+
get codexApproval() { return codexApproval; },
|
|
776
|
+
set codexApproval(v) { codexApproval = v; },
|
|
777
|
+
get codexSandbox() { return codexSandbox; },
|
|
778
|
+
set codexSandbox(v) { codexSandbox = v; },
|
|
779
|
+
get codexWebSearch() { return codexWebSearch; },
|
|
780
|
+
set codexWebSearch(v) { codexWebSearch = v; },
|
|
759
781
|
sessions: sessions,
|
|
760
782
|
sessionsDir: sessionsDir,
|
|
761
783
|
HISTORY_PAGE_SIZE: HISTORY_PAGE_SIZE,
|