clay-server 5.3.0-beta.3 → 5.3.0-beta.5
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/ask-user-request-identity.js +18 -1
- package/lib/project-delegated-follow-up.js +18 -9
- package/lib/public/css/right-workbench.css +26 -3
- package/lib/public/css/shared-browser.css +21 -0
- package/lib/public/css/sticky-notes.css +3 -2
- package/lib/public/index.html +2 -2
- package/lib/public/modules/app-header.js +24 -2
- package/lib/public/modules/app-messages.js +1 -1
- package/lib/public/modules/clay-session-links.js +1 -1
- package/lib/public/modules/filebrowser.js +2 -2
- package/lib/public/modules/pane-activation.js +3 -0
- package/lib/public/modules/pane-browser-bridge.js +15 -5
- package/lib/public/modules/pane-header-runtime.js +18 -0
- package/lib/public/modules/right-workbench.js +92 -62
- package/lib/public/modules/shared-browser.js +6 -0
- package/lib/public/modules/split-pane-renderer.js +6 -8
- package/lib/public/modules/split-pane-runtime.js +23 -0
- package/lib/public/modules/split-view-workers.js +58 -2
- package/lib/public/modules/split-view.js +8 -14
- package/lib/public/modules/sticky-notes.js +8 -10
- package/lib/public/modules/terminal.js +7 -2
- package/lib/public/modules/theme.js +5 -0
- package/lib/public/modules/worker-created-presentation.js +2 -0
- package/lib/public/modules/workspace-assignment-card.js +9 -6
- package/lib/public/pane-assets/{app-K4LGSKX5.js → app-6H5S2Y3Z.js} +1 -1
- package/lib/public/pane-assets/chunk-3QV5SDC4.js +1 -0
- package/lib/public/pane-assets/{chunk-AILOLDZA.js → chunk-4FM3U3DW.js} +1 -1
- package/lib/public/pane-assets/{chunk-6SM2WQJX.js → chunk-7GZH3FZY.js} +1 -1
- package/lib/public/pane-assets/chunk-AGJTF5WQ.js +1 -0
- package/lib/public/pane-assets/chunk-NPKRTUNG.js +2 -0
- package/lib/public/pane-assets/{chunk-BYSBUZDZ.js → chunk-VGA2Y3D2.js} +1 -1
- package/lib/public/pane-assets/{chunk-W34XSARI.js → chunk-WKO3T75E.js} +93 -93
- package/lib/public/pane-assets/manifest.json +347 -345
- package/lib/public/pane-assets/{style-JVCOCG3W.css → style-TSRDU54Z.css} +1 -1
- package/lib/server.js +1 -0
- package/lib/workspace-assignment-service.js +56 -11
- package/lib/workspace-follow-up-queue.js +92 -0
- package/lib/workspace-query-mcp-server.js +1 -1
- package/package.json +1 -1
- package/lib/public/pane-assets/chunk-6AJEGTWL.js +0 -1
- package/lib/public/pane-assets/chunk-7H36SUZA.js +0 -2
- package/lib/public/pane-assets/chunk-QJPBAN5H.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
var normalizeQuestions = require('./yoke/user-input').normalizeQuestions;
|
|
2
|
+
|
|
1
3
|
function canonicalize(value) {
|
|
2
4
|
if (Array.isArray(value)) return value.map(canonicalize);
|
|
3
5
|
if (!value || typeof value !== "object") return value;
|
|
@@ -7,9 +9,24 @@ function canonicalize(value) {
|
|
|
7
9
|
return result;
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
// Compare SDK input with the canonical display payload without dropping custom
|
|
13
|
+
// review fields or merging independently identified requests.
|
|
14
|
+
function displayInput(input) {
|
|
15
|
+
input = input || {};
|
|
16
|
+
try {
|
|
17
|
+
var questions = normalizeQuestions(input);
|
|
18
|
+
return Object.assign({}, input, { questions: questions.map(function (question, index) {
|
|
19
|
+
var original = input.questions[index];
|
|
20
|
+
return Object.assign({}, original, question, { options: question.options.map(function (option, optionIndex) {
|
|
21
|
+
return Object.assign({}, original.options && original.options[optionIndex], option);
|
|
22
|
+
}) });
|
|
23
|
+
}) });
|
|
24
|
+
} catch (_error) { return input; }
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
function sameAskUserRequest(event, id, input) {
|
|
11
28
|
return !!(event && event.type === "tool_executing" && event.name === "AskUserQuestion"
|
|
12
|
-
&& event.id === id && JSON.stringify(canonicalize(event.input
|
|
29
|
+
&& event.id === id && JSON.stringify(canonicalize(displayInput(event.input))) === JSON.stringify(canonicalize(displayInput(input))));
|
|
13
30
|
}
|
|
14
31
|
|
|
15
32
|
function hasRecordedAskUserRequest(history, id, input) {
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
+
var isWorker = require("./session-provenance").isWorker;
|
|
2
|
+
|
|
1
3
|
function hasEntries(value) {
|
|
2
4
|
return !!(value && Object.keys(value).length > 0);
|
|
3
5
|
}
|
|
4
6
|
|
|
5
|
-
function unsafeReason(session, isPaired) {
|
|
7
|
+
function unsafeReason(session, isPaired, options) {
|
|
8
|
+
var opts = options || {}, update = opts.assignmentUpdate === true;
|
|
6
9
|
if (!session || session.hidden) return "Target session is unavailable.";
|
|
10
|
+
if (isWorker(session) || session.sessionRole === "worker") return "Project follow-ups must target the Driver, not a Worker.";
|
|
7
11
|
if (!session.cliSessionId) return "Target session must have a durable session reference.";
|
|
8
12
|
if (session.sessionVisibility !== "private") return "Target session must be private.";
|
|
9
|
-
if (session.isProcessing || session._queryStarting || session._awaitingTurnResult) return "Target session must be idle.";
|
|
13
|
+
if (!opts.allowBusy && (session.isProcessing || session._queryStarting || session._awaitingTurnResult)) return "Target session must be idle.";
|
|
10
14
|
if (hasEntries(session.pendingPermissions) || hasEntries(session.pendingAskUser) || hasEntries(session.pendingElicitations) || hasEntries(session.pendingUserDialogs)) return "Target session is waiting for user input.";
|
|
11
15
|
if ((session.mode && session.mode !== "gui") || (session.runtimeMode && session.runtimeMode !== "gui") || session.terminalId != null || session.runtimeTerminalId != null || session.tuiSuspended) return "Terminal sessions cannot receive project follow-ups.";
|
|
12
16
|
if (session.homeDebatePlanning || session.homeDebatePhase || session.debateSetupMode || session.debateState || session._debate) return "Debate sessions cannot receive project follow-ups.";
|
|
13
|
-
if (session.loop || session.spawn || session.handoff || session.assignment || session.singleTurn || session._pairDelegation || isPaired) return "Orchestrated sessions cannot receive project follow-ups.";
|
|
17
|
+
if (session.loop || session.spawn || session.handoff || (!update && (session.assignment || session.singleTurn)) || session._pairDelegation || (isPaired && !(opts.allowBusy && !isWorker(session)))) return "Orchestrated sessions cannot receive project follow-ups.";
|
|
14
18
|
if (session.dangerouslySkipPermissions || session.permissionMode === "bypassPermissions") return "Full-access sessions cannot receive project follow-ups.";
|
|
15
|
-
if (session.destroying || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._pendingMessageDrainPaused || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
|
|
19
|
+
if (session.destroying || (session.abortController && session.abortController.signal.aborted) || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._lastTurnInterrupted || session._pendingMessageDrainPaused || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
|
|
16
20
|
return "";
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
function attachDelegatedFollowUp(ctx) {
|
|
20
24
|
var sm = ctx.sm;
|
|
21
25
|
|
|
22
|
-
function inspect(principal, targetSessionRef) {
|
|
26
|
+
function inspect(principal, targetSessionRef, options) {
|
|
23
27
|
if (!principal || typeof targetSessionRef !== "string" || targetSessionRef.indexOf("session:") !== 0) {
|
|
24
28
|
throw new Error("A durable opaque targetSessionRef is required.");
|
|
25
29
|
}
|
|
@@ -34,15 +38,20 @@ function attachDelegatedFollowUp(ctx) {
|
|
|
34
38
|
throw new Error("Target session access denied.");
|
|
35
39
|
}
|
|
36
40
|
var paired = !!(ctx.isPairedSession && ctx.isPairedSession(session));
|
|
37
|
-
var
|
|
41
|
+
var assignment = session.assignment, opts = options || {};
|
|
42
|
+
var assignmentUpdate = !!(assignment && opts.parentAssignmentId && assignment.assignmentId === opts.parentAssignmentId &&
|
|
43
|
+
assignment.sourceMateId === principal.sourceMateId && assignment.sourceProjectSlug === principal.sourceProjectSlug &&
|
|
44
|
+
assignment.sourceSessionRef === principal.sourceSessionRef);
|
|
45
|
+
var reason = unsafeReason(session, paired, { allowBusy: opts.allowBusy === true, assignmentUpdate: assignmentUpdate });
|
|
38
46
|
if (reason) throw new Error(reason);
|
|
39
47
|
if (ctx.isPendingQueuePaused && ctx.isPendingQueuePaused(session)) throw new Error("The Driver queue is stopped; explicit owner resume is required.");
|
|
40
48
|
if (!ctx.canResumeVendor(session.vendor)) throw new Error("Target session provider cannot safely resume this conversation.");
|
|
41
|
-
return { session: session, sessionRef: targetSessionRef, title: session.title || "Existing session" };
|
|
49
|
+
return { session: session, sessionRef: targetSessionRef, title: session.title || "Existing session", linuxUser: ctx.getLinuxUserForSession(session) || null };
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
async function dispatch(principal, targetSessionRef, task, metadata, onReady) {
|
|
45
|
-
var resolved = inspect(principal, targetSessionRef);
|
|
53
|
+
var resolved = inspect(principal, targetSessionRef, { allowBusy: true, parentAssignmentId: metadata.parentAssignmentId });
|
|
54
|
+
if (resolved.session.isProcessing || resolved.session._queryStarting || resolved.session._awaitingTurnResult) throw new Error("Driver is still busy; the reviewed follow-up must remain queued.");
|
|
46
55
|
var session = resolved.session;
|
|
47
56
|
var sdk = ctx.getSdk();
|
|
48
57
|
if (!sdk || typeof sdk.pushMessage !== "function" || typeof sdk.startQuery !== "function") {
|
|
@@ -64,7 +73,7 @@ function attachDelegatedFollowUp(ctx) {
|
|
|
64
73
|
sm.broadcastSessionList();
|
|
65
74
|
try {
|
|
66
75
|
var delivered = sdk.pushMessage(session, task);
|
|
67
|
-
if (!delivered
|
|
76
|
+
if (!delivered && await sdk.startQuery(session, task, undefined, resolved.linuxUser) === false) throw new Error("Driver runtime did not accept the follow-up.");
|
|
68
77
|
} catch (error) {
|
|
69
78
|
session.isProcessing = false;
|
|
70
79
|
sm.sendAndRecord(session, {
|
|
@@ -49,15 +49,38 @@
|
|
|
49
49
|
pointer-events: none;
|
|
50
50
|
clip-path: inset(100%);
|
|
51
51
|
}
|
|
52
|
-
#main-panels
|
|
52
|
+
#main-panels > :is(#file-viewer, #terminal-container, #project-logs-panel, #you-panel, #issues-panel, #linear-panel, #notes-browser, #scheduled-tasks-panel, #debates-workbench, #shared-browser-panel, #mcp-skills-workbench, #mate-knowledge-workbench, #wireframe-panel)[data-workbench-placement="docked"]:not(.panel-fullscreen) {
|
|
53
53
|
position: relative;
|
|
54
54
|
inset: auto;
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
flex: 1 1 0;
|
|
56
|
+
width: 0;
|
|
57
|
+
min-width: 240px;
|
|
58
|
+
max-width: none;
|
|
59
|
+
height: 100%;
|
|
60
|
+
max-height: 100%;
|
|
61
|
+
align-self: stretch;
|
|
62
|
+
margin: 0;
|
|
63
|
+
z-index: 0;
|
|
64
|
+
border: 0;
|
|
65
|
+
border-left: 1px solid var(--border-subtle);
|
|
66
|
+
border-radius: 0;
|
|
67
|
+
background: var(--bg);
|
|
68
|
+
box-shadow: none;
|
|
57
69
|
animation: none;
|
|
58
70
|
}
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
@media (min-width: 1024px) {
|
|
74
|
+
#main-panels > :is(#file-viewer, #shared-browser-panel)[data-workbench-placement="docked"]:is(.file-viewer-wide, .shared-browser-wide):not(.panel-fullscreen) { flex-grow: 2; }
|
|
75
|
+
#main-panels > [data-workbench-placement="docked"] :is(.terminal-header, .file-viewer-tabbar, .shared-browser-viewport) {
|
|
76
|
+
background: var(--bg);
|
|
77
|
+
}
|
|
78
|
+
#main-panels > #terminal-container[data-workbench-placement="docked"] #terminal-body { background: var(--bg); }
|
|
79
|
+
}
|
|
80
|
+
@media (max-width: 1023px) {
|
|
81
|
+
#main-panels > [data-workbench-placement="docked"][data-workbench-active="false"] { display: none !important; }
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
#main-panels .workbench-layout-toggle {
|
|
62
85
|
display: inline-flex;
|
|
63
86
|
align-items: center;
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
@import url("./shared-browser-skins.css");
|
|
2
2
|
|
|
3
|
+
#shared-browser-toggle-btn { position: relative; }
|
|
4
|
+
.shared-browser-count {
|
|
5
|
+
position: absolute;
|
|
6
|
+
top: -4px;
|
|
7
|
+
right: -4px;
|
|
8
|
+
background: var(--brand-blue);
|
|
9
|
+
color: var(--brand-ink);
|
|
10
|
+
font-size: 9px;
|
|
11
|
+
font-weight: 700;
|
|
12
|
+
min-width: 14px;
|
|
13
|
+
height: 14px;
|
|
14
|
+
border-radius: 7px;
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
padding: 0 3px;
|
|
19
|
+
font-family: "Roboto Mono", monospace;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
.shared-browser-count.hidden { display: none; }
|
|
23
|
+
|
|
3
24
|
/* Shared browser uses the existing mutually exclusive right workbench. */
|
|
4
25
|
.shared-browser-panel { width: 50%; max-width: 780px; min-width: 360px; height: calc(100% - 16px); align-self: center; margin: 8px 8px 8px 10px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0; box-shadow: 4px 8px 18px rgba(var(--shadow-rgb), .14); }
|
|
5
26
|
.shared-browser-panel.hidden, #shared-browser-btn.hidden, #shared-browser-toggle-btn.hidden { display: none; }
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
position: absolute;
|
|
60
60
|
top: -4px;
|
|
61
61
|
right: -4px;
|
|
62
|
-
background: var(--
|
|
63
|
-
color:
|
|
62
|
+
background: var(--brand-gold);
|
|
63
|
+
color: var(--brand-ink);
|
|
64
64
|
font-size: 9px;
|
|
65
65
|
font-weight: 700;
|
|
66
66
|
min-width: 14px;
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
justify-content: center;
|
|
72
72
|
padding: 0 3px;
|
|
73
73
|
font-family: "Roboto Mono", monospace;
|
|
74
|
+
pointer-events: none;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
.sticky-notes-count.hidden { display: none; }
|
package/lib/public/index.html
CHANGED
|
@@ -78,9 +78,9 @@ body { background: transparent; color: inherit; }
|
|
|
78
78
|
<div id="skip-perms-pill" class="top-bar-pill pill-error hidden"><i data-lucide="shield-off"></i> <span>Skip perms</span></div>
|
|
79
79
|
<div id="client-count" class="top-bar-pill pill-accent hidden"><i data-lucide="users"></i> <span id="client-count-text"></span></div>
|
|
80
80
|
<!-- Right workbench controls: same nodes and handlers as before; app JS returns them to the title bar on mobile. -->
|
|
81
|
-
<button type="button" id="shared-browser-toggle-btn" class="hidden" title="Browser" aria-label="Browser" aria-controls="shared-browser-panel" aria-expanded="false" disabled><i data-lucide="globe"></i></button>
|
|
81
|
+
<button type="button" id="shared-browser-toggle-btn" class="hidden" title="Browser" aria-label="Browser" aria-controls="shared-browser-panel" aria-expanded="false" disabled><i data-lucide="globe"></i><span class="shared-browser-count hidden" aria-hidden="true"></span></button>
|
|
82
82
|
<button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
|
|
83
|
-
<button type="button" id="sticky-notes-toggle-btn" aria-label="Hide sticky notes" aria-pressed="false" title="Hide sticky notes" hidden><i data-lucide="sticky-note"></i></button>
|
|
83
|
+
<button type="button" id="sticky-notes-toggle-btn" aria-label="Hide sticky notes" aria-pressed="false" title="Hide sticky notes" hidden><i data-lucide="sticky-note"></i><span class="sticky-notes-count hidden" aria-hidden="true"></span></button>
|
|
84
84
|
<button id="notif-center-btn" title="Notifications"><i data-lucide="bell"></i><span id="notif-center-badge" class="notif-badge hidden">0</span></button>
|
|
85
85
|
<button id="server-settings-btn" title="Settings" aria-label="Settings" aria-expanded="false"><i data-lucide="settings"></i></button>
|
|
86
86
|
</div>
|
|
@@ -83,6 +83,21 @@ function createHeaderWorkerHistoryControl(root, context) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export function initHeader() {
|
|
86
|
+
store.subscribe(function (state, prev) {
|
|
87
|
+
if (!state.connected && prev.connected || state.panePendingBinding && state.panePendingBinding !== prev.panePendingBinding) {
|
|
88
|
+
store.set({ loadingMore: false });
|
|
89
|
+
}
|
|
90
|
+
if (state.paneReady && !prev.paneReady || !state.replayingHistory && prev.replayingHistory) {
|
|
91
|
+
// Replay can expose the sentinel before the pane accepts requests.
|
|
92
|
+
// Observe it again once the committed conversation is interactive.
|
|
93
|
+
var messages = getMessagesEl();
|
|
94
|
+
var sentinel = messages && messages.querySelector('.history-sentinel');
|
|
95
|
+
if (sentinel && historySentinelObserver) {
|
|
96
|
+
historySentinelObserver.unobserve(sentinel);
|
|
97
|
+
historySentinelObserver.observe(sentinel);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
86
101
|
var headerRenameBtn = document.getElementById("header-rename-btn");
|
|
87
102
|
var headerTitleEl = document.getElementById("header-title");
|
|
88
103
|
var headerInfoBtn = document.getElementById("header-info-btn");
|
|
@@ -339,12 +354,19 @@ export function updateHistorySentinel() {
|
|
|
339
354
|
export function requestMoreHistory() {
|
|
340
355
|
var ws = getWs();
|
|
341
356
|
var s = store.snap();
|
|
342
|
-
if (s.loadingMore || s.historyFrom <= 0 || !ws || !s.connected
|
|
357
|
+
if (s.loadingMore || s.historyFrom <= 0 || !ws || ws.readyState !== 1 || !s.connected ||
|
|
358
|
+
s.replayingHistory || s.historyReplayBatch || s.panePendingBinding || s.paneSuspended ||
|
|
359
|
+
s.paneCommittedBinding && !s.paneReady || s.deferredSessionSwitch) return;
|
|
343
360
|
store.set({ loadingMore: true });
|
|
344
361
|
var messagesEl = getMessagesEl();
|
|
345
362
|
var btn = messagesEl.querySelector(".load-more-btn");
|
|
346
363
|
if (btn) btn.classList.add("loading");
|
|
347
|
-
|
|
364
|
+
try {
|
|
365
|
+
ws.send(JSON.stringify({ type: "load_more_history", before: s.historyFrom, paneBinding: s.paneCommittedBinding || undefined }));
|
|
366
|
+
} catch (error) {
|
|
367
|
+
store.set({ loadingMore: false });
|
|
368
|
+
if (btn) btn.classList.remove("loading");
|
|
369
|
+
}
|
|
348
370
|
}
|
|
349
371
|
|
|
350
372
|
export function prependOlderHistory(items, meta) {
|
|
@@ -267,7 +267,7 @@ export function processMessage(msg) {
|
|
|
267
267
|
break;
|
|
268
268
|
|
|
269
269
|
case "history_meta":
|
|
270
|
-
store.set({ historyFrom: msg.from, historyTotal: msg.total, replayingHistory: true });
|
|
270
|
+
store.set({ historyFrom: msg.from, historyTotal: msg.total, loadingMore: false, replayingHistory: true });
|
|
271
271
|
updateHistorySentinel();
|
|
272
272
|
break;
|
|
273
273
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseClaySessionReference, renderClaySessionLink } from './clay-log-links.js';
|
|
2
2
|
|
|
3
3
|
// The prefix is a display compatibility label, never a navigation target.
|
|
4
|
-
var citationPattern = /\[([A-Za-z0-9][A-Za-z0-9_-]{0,79})\/(session:[A-Za-z0-9_-]{24}(?:#entry-[0-9]+)?)(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]/g;
|
|
4
|
+
var citationPattern = /\[([A-Za-z0-9][A-Za-z0-9_-]{0,79})\/(session:[A-Za-z0-9_-]{24}(?:#entry-[0-9]+)?)(?:\s+[\u2014\u00b7-]\s+([^\]\n]{1,80}))?\]/g;
|
|
5
5
|
function matches(text) {
|
|
6
6
|
var pattern = new RegExp(citationPattern.source, 'g'), results = [], match;
|
|
7
7
|
while ((match = pattern.exec(text))) if (parseClaySessionReference(match[2])) results.push({ start: match.index, end: pattern.lastIndex, prefix: match[1], ref: match[2], label: match[3] || '' });
|
|
@@ -4,7 +4,7 @@ import { iconHtml, refreshIcons } from './icons.js';
|
|
|
4
4
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
5
5
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf } from './markdown.js';
|
|
6
6
|
import { closeSidebar } from './sidebar.js';
|
|
7
|
-
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench, isRightWorkbenchCurrent } from './right-workbench.js';
|
|
7
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench, isRightWorkbenchCurrent, rightWorkbenchRevisionFor } from './right-workbench.js';
|
|
8
8
|
import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
|
|
9
9
|
import { initFileIcons, getFileIconSvg, getFolderIconSvg } from './fileicons.js';
|
|
10
10
|
import { copyMarkdownFormatting } from './rich-clipboard.js';
|
|
@@ -739,7 +739,7 @@ function requestFileContent(filePath) {
|
|
|
739
739
|
if (refreshPresentedDocument(filePath)) return;
|
|
740
740
|
var requestId = "file-read-" + Date.now() + "-" + Math.random().toString(36).slice(2);
|
|
741
741
|
var state = store.snap();
|
|
742
|
-
store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null, workbenchRevision:
|
|
742
|
+
store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null, workbenchRevision: rightWorkbenchRevisionFor('file-viewer') } });
|
|
743
743
|
var pending = store.get('pendingFileNavigation');
|
|
744
744
|
if (pending && pending.path === filePath) store.set({ pendingFileNavigation: Object.assign({}, pending, { requestId: requestId }) });
|
|
745
745
|
if (ctx.ws && ctx.connected) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { paneHeaderRuntime, initPaneHeaderRuntime } from './pane-header-runtime.js';
|
|
1
2
|
import { disarmStickyBottom } from './chat-render-runtime.js';
|
|
2
3
|
import { store } from './store.js';
|
|
3
4
|
import { getWs } from './ws-ref.js';
|
|
@@ -204,10 +205,12 @@ export function initPaneActivation() {
|
|
|
204
205
|
return binding && { ownerId: binding.viewerId, projectSlug: binding.projectSlug, sessionId: binding.sessionId,
|
|
205
206
|
binding: binding, ready: store.get('connected') && store.get('paneReady') && !store.get('paneSuspended') };
|
|
206
207
|
},
|
|
208
|
+
headerRuntime: paneHeaderRuntime,
|
|
207
209
|
activate: activatePaneSession,
|
|
208
210
|
checkDraft: function () { if (!store.get('panePendingBinding')) { var draft = takeComposerDraft(); restoreComposerDraft(draft); } },
|
|
209
211
|
suspend: suspendPane,
|
|
210
212
|
};
|
|
213
|
+
initPaneHeaderRuntime(frame);
|
|
211
214
|
// A pointer that began on the previous conversation cannot become a Stop or
|
|
212
215
|
// approval click on the replacement conversation after hydration.
|
|
213
216
|
document.addEventListener('pointerdown', function () { store.set({ panePointerBinding: store.get('paneCommittedBinding') }); }, true);
|
|
@@ -9,14 +9,24 @@ export function forwardPaneBrowserLink(url) {
|
|
|
9
9
|
}
|
|
10
10
|
export function handlePaneBrowserMessage(event, host) {
|
|
11
11
|
if (!event || event.origin !== window.location.origin || !host || !event.data || event.data.type !== 'clay-pane-open-browser') return false;
|
|
12
|
-
var msg = event.data, s = store.snap();
|
|
12
|
+
var msg = event.data, s = Object.assign({}, store.snap());
|
|
13
13
|
var frame = Array.from(host.querySelectorAll('.split-pane-frame')).find(function (item) { return item.contentWindow === event.source; });
|
|
14
|
-
var pane = frame && s.splitPanes && s.splitPanes.panes && s.splitPanes.panes.find(function (item) { return String(item.slug) === frame.dataset.projectSlug && String(item.sessionId) === frame.dataset.sessionId; });
|
|
15
|
-
if (!pane || msg.projectSlug !== s.currentSlug || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== frame.dataset.sessionId) return true;
|
|
14
|
+
var pane = frame && frame.clayPane ? { slug: frame.dataset.projectSlug, sessionId: frame.dataset.sessionId } : frame && s.splitPanes && s.splitPanes.panes && s.splitPanes.panes.find(function (item) { return String(item.slug) === frame.dataset.projectSlug && String(item.sessionId) === frame.dataset.sessionId; });
|
|
15
|
+
if (!s.connected || !pane || msg.projectSlug !== s.currentSlug || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== frame.dataset.sessionId) return true;
|
|
16
16
|
var expected = new URL('/p/' + encodeURIComponent(pane.slug) + '/?pane=1&session=' + encodeURIComponent(pane.sessionId), window.location.href).href;
|
|
17
17
|
if (frame.clayPane ? !livePaneBinding(frame, msg) : frame.src !== expected) return true;
|
|
18
18
|
// The authenticated host owns the browser; a live same-project pane supplies only a URL.
|
|
19
|
-
var binding = frame.clayPane && frame.clayPane.identity().binding;
|
|
20
|
-
|
|
19
|
+
var binding = frame.clayPane && frame.clayPane.identity().binding, document = frame.contentDocument;
|
|
20
|
+
function current() {
|
|
21
|
+
var now = store.snap();
|
|
22
|
+
if (!now.connected || now.currentSlug !== s.currentSlug || now.myUserId !== s.myUserId ||
|
|
23
|
+
now.currentProjectOwnerId !== s.currentProjectOwnerId || now.projectSplitViewSocketGeneration !== s.projectSplitViewSocketGeneration ||
|
|
24
|
+
frame.contentWindow !== event.source || frame.contentDocument !== document ||
|
|
25
|
+
frame.dataset.projectSlug !== msg.projectSlug || String(frame.dataset.sessionId) !== String(msg.sessionId) ||
|
|
26
|
+
!Array.from(host.querySelectorAll('.split-pane-frame')).includes(frame)) return false;
|
|
27
|
+
if (binding) return !!frame.clayPane && livePaneBinding(frame, msg) && frame.clayPane.identity().binding === binding;
|
|
28
|
+
return frame.src === expected && !!(now.splitPanes && now.splitPanes.panes.some(function (item) { return String(item.slug) === pane.slug && String(item.sessionId) === String(pane.sessionId); }));
|
|
29
|
+
}
|
|
30
|
+
import('./shared-browser.js').then(function (module) { if (current()) module.openSharedBrowserAt(msg.url); }).catch(function (error) { showToast(error.message || 'Could not open the browser.', 'warn'); });
|
|
21
31
|
return true;
|
|
22
32
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
|
|
3
|
+
// Read-only presentation metadata from this pane's committed authenticated view.
|
|
4
|
+
export function paneHeaderRuntime() {
|
|
5
|
+
var state = store.snap(), binding = state.paneCommittedBinding;
|
|
6
|
+
if (!binding || !state.connected || !state.paneReady || state.paneSuspended || state.panePendingBinding ||
|
|
7
|
+
state.activeSessionId !== binding.sessionId || state.sessionOriginId !== binding.sessionOriginId ||
|
|
8
|
+
state.currentSlug !== binding.projectSlug || (state.myUserId || null) !== (binding.viewerId || null)) return null;
|
|
9
|
+
return { binding: binding, vendor: state.currentVendor, model: state.currentModel || '' };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function initPaneHeaderRuntime(frame) {
|
|
13
|
+
store.subscribe(function (state, previous) {
|
|
14
|
+
if (state.currentModel === previous.currentModel && state.currentVendor === previous.currentVendor) return;
|
|
15
|
+
if (!paneHeaderRuntime() || frame.contentWindow !== window || frame.contentDocument !== document) return;
|
|
16
|
+
frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-config'));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Shared arbitration for docked background panes and one foreground overlay.
|
|
2
2
|
|
|
3
3
|
import { store } from './store.js';
|
|
4
4
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
@@ -25,48 +25,76 @@ function ensureLayoutPreference() {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function dockedOwners() { return store.get('rightWorkbenchDockedOwners') || []; }
|
|
29
|
+
function openOwner(name) { return store.get('rightWorkbenchOwner') === name || dockedOwners().indexOf(name) !== -1; }
|
|
30
|
+
export function rightWorkbenchRevisionFor(name) {
|
|
31
|
+
var revisions = store.get('rightWorkbenchPanelRevisions') || {};
|
|
32
|
+
return revisions[name] == null ? store.get('rightWorkbenchRevision') || 0 : revisions[name];
|
|
33
|
+
}
|
|
34
|
+
function publish(owner, docked, revisions) {
|
|
35
|
+
store.set({ rightWorkbenchOwner: owner, rightWorkbenchDockedOwners: docked,
|
|
36
|
+
rightWorkbenchPanelRevisions: revisions, rightWorkbenchRevision: (store.get('rightWorkbenchRevision') || 0) + 1 });
|
|
37
|
+
}
|
|
38
|
+
function setDocked(name, docked) {
|
|
39
|
+
if (!openOwner(name)) return;
|
|
40
|
+
var owners = dockedOwners().filter(function (item) { return item !== name; }), previous = store.get('rightWorkbenchOwner');
|
|
41
|
+
if (docked) owners.push(name);
|
|
42
|
+
publish(docked ? previous : name, owners, store.get('rightWorkbenchPanelRevisions') || {});
|
|
43
|
+
if (!docked && previous && previous !== name && owners.indexOf(previous) === -1 && closers[previous]) closers[previous]();
|
|
44
|
+
}
|
|
28
45
|
function renderLayoutControls() {
|
|
29
46
|
if (typeof document === 'undefined') return;
|
|
30
47
|
var main = document.getElementById('main-panels');
|
|
31
48
|
if (!main) return;
|
|
32
|
-
var state = store.get('workbenchLayoutPreference') || {}
|
|
33
|
-
main.dataset.workbenchMode =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
var owners = dockedOwners(), state = store.get('workbenchLayoutPreference') || {};
|
|
50
|
+
main.dataset.workbenchMode = owners.length ? 'docked' : 'overlay';
|
|
51
|
+
main.dataset.workbenchDocks = String(owners.length);
|
|
52
|
+
Object.keys(panelIds).forEach(function (name) {
|
|
53
|
+
var panel = document.getElementById(panelIds[name]);
|
|
54
|
+
if (!panel) return;
|
|
55
|
+
var docked = owners.indexOf(name) !== -1;
|
|
56
|
+
panel.dataset.workbenchPlacement = docked ? 'docked' : 'overlay';
|
|
57
|
+
panel.dataset.workbenchActive = store.get('rightWorkbenchOwner') === name ? 'true' : 'false';
|
|
58
|
+
if (!openOwner(name)) return;
|
|
59
|
+
var header = panel.querySelector(':scope > header, :scope > .file-viewer-header .file-viewer-tabbar, :scope > .terminal-header');
|
|
60
|
+
if (!header) return;
|
|
61
|
+
var button = panel.querySelector('.workbench-layout-toggle');
|
|
62
|
+
if (!button) {
|
|
63
|
+
var actions = header.querySelector('[class*="window-actions"], .terminal-header-actions, .shared-browser-window, :scope.mcp-skills-header > div') || header;
|
|
64
|
+
button = document.createElement('button'); button.type = 'button'; button.className = 'workbench-layout-toggle';
|
|
65
|
+
button.addEventListener('click', function () {
|
|
66
|
+
var current = store.get('workbenchLayoutPreference') || {};
|
|
67
|
+
if (!current.ready || current.loading || current.saving) { ensureLayoutPreference(); return; }
|
|
68
|
+
var owner = store.get('myUserId'), project = store.get('currentSlug'), revision = rightWorkbenchRevisionFor(name);
|
|
69
|
+
var wasDocked = dockedOwners().indexOf(name) !== -1;
|
|
70
|
+
function currentTarget() { return button.isConnected && openOwner(name) && owner === store.get('myUserId') && project === store.get('currentSlug') &&
|
|
71
|
+
revision === rightWorkbenchRevisionFor(name) && wasDocked === (dockedOwners().indexOf(name) !== -1); }
|
|
72
|
+
import('./workbench-layout-preference.js').then(function (api) {
|
|
73
|
+
var fresh = store.get('workbenchLayoutPreference') || {};
|
|
74
|
+
if (!currentTarget() || fresh.mode !== current.mode || fresh.revision !== current.revision || fresh.loading || fresh.saving || !fresh.ready) return false;
|
|
75
|
+
return api.saveWorkbenchLayoutMode(wasDocked ? 'overlay' : 'docked');
|
|
76
|
+
}).then(function (saved) {
|
|
77
|
+
if (!currentTarget()) return;
|
|
78
|
+
var fresh = store.get('workbenchLayoutPreference') || {};
|
|
79
|
+
if (saved === true && fresh.ready && fresh.mode === (wasDocked ? 'overlay' : 'docked')) setDocked(name, !wasDocked);
|
|
80
|
+
else if (saved === false) showToast(fresh.error || 'Workbench layout could not be saved. Try again.', 'error');
|
|
81
|
+
}).catch(function (error) {
|
|
82
|
+
if (currentTarget()) showToast(error.message || 'Workbench layout could not be saved. Try again.', 'error');
|
|
83
|
+
});
|
|
55
84
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
|
|
85
|
+
var anchor = actions === header ? header.querySelector('[data-knowledge-wide]') : actions.firstElementChild;
|
|
86
|
+
actions.insertBefore(button, anchor || null);
|
|
87
|
+
}
|
|
88
|
+
if (button.dataset.mode !== (docked ? 'docked' : 'overlay')) {
|
|
89
|
+
button.dataset.mode = docked ? 'docked' : 'overlay';
|
|
90
|
+
button.innerHTML = iconHtml(docked ? 'layers-2' : 'panel-right'); refreshIcons();
|
|
91
|
+
}
|
|
92
|
+
button.setAttribute('aria-label', docked ? 'Float workbench over sessions' : 'Dock workbench beside sessions');
|
|
93
|
+
button.title = state.error || button.getAttribute('aria-label');
|
|
94
|
+
button.setAttribute('aria-pressed', docked ? 'true' : 'false');
|
|
95
|
+
button.disabled = !!state.loading || !!state.saving;
|
|
96
|
+
button.setAttribute('aria-busy', state.loading || state.saving ? 'true' : 'false');
|
|
97
|
+
});
|
|
70
98
|
}
|
|
71
99
|
|
|
72
100
|
export function registerRightWorkbench(name, close) {
|
|
@@ -76,37 +104,39 @@ export function registerRightWorkbench(name, close) {
|
|
|
76
104
|
|
|
77
105
|
export function claimRightWorkbench(name) {
|
|
78
106
|
if (typeof document !== 'undefined') ensureLayoutPreference();
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
var
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
var owner = store.get('rightWorkbenchOwner'), docked = dockedOwners().slice(), preference = store.get('workbenchLayoutPreference') || {};
|
|
108
|
+
if (!owner && !docked.length && preference.ready && preference.mode === 'docked') docked.push(name);
|
|
109
|
+
var retained = docked.indexOf(name) !== -1 && owner && docked.indexOf(owner) === -1 ? owner : name;
|
|
110
|
+
var revision = (store.get('rightWorkbenchRevision') || 0) + 1, revisions = Object.assign({}, store.get('rightWorkbenchPanelRevisions') || {});
|
|
111
|
+
Object.keys(revisions).forEach(function (key) { if (key !== retained && docked.indexOf(key) === -1) delete revisions[key]; });
|
|
112
|
+
revisions[name] = revision;
|
|
113
|
+
publish(retained, docked, revisions);
|
|
114
|
+
Object.keys(closers).forEach(function (key) {
|
|
115
|
+
if (key === name || key === retained || docked.indexOf(key) !== -1) return;
|
|
116
|
+
try { closers[key](); } catch (error) { /* one panel must not block another */ }
|
|
117
|
+
});
|
|
87
118
|
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(renderLayoutControls);
|
|
88
|
-
return
|
|
119
|
+
return revision;
|
|
89
120
|
}
|
|
90
|
-
|
|
91
|
-
function storeSet(name, value) {
|
|
92
|
-
store.set({ rightWorkbenchOwner: name, rightWorkbenchRevision: value });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
121
|
export function releaseRightWorkbench(name) {
|
|
96
|
-
if (
|
|
97
|
-
|
|
122
|
+
if (!openOwner(name)) return;
|
|
123
|
+
var docked = dockedOwners().filter(function (item) { return item !== name; }), revisions = Object.assign({}, store.get('rightWorkbenchPanelRevisions') || {});
|
|
124
|
+
delete revisions[name];
|
|
125
|
+
publish(store.get('rightWorkbenchOwner') === name ? docked[docked.length - 1] || null : store.get('rightWorkbenchOwner'), docked, revisions);
|
|
98
126
|
}
|
|
99
|
-
|
|
100
127
|
export function isRightWorkbenchCurrent(name, expectedRevision) {
|
|
101
|
-
return
|
|
128
|
+
return openOwner(name) && rightWorkbenchRevisionFor(name) === expectedRevision;
|
|
102
129
|
}
|
|
103
|
-
|
|
104
130
|
export function resetRightWorkbench() {
|
|
105
|
-
|
|
131
|
+
publish(null, [], {});
|
|
132
|
+
Object.keys(closers).forEach(function (name) { try { closers[name](); } catch (error) { /* Clear other panes after a context change. */ } });
|
|
106
133
|
}
|
|
107
|
-
|
|
108
134
|
store.subscribe(function (state, previous) {
|
|
109
|
-
if (state.
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId) { resetRightWorkbench(); return; }
|
|
136
|
+
var preference = state.workbenchLayoutPreference || {}, before = previous.workbenchLayoutPreference || {};
|
|
137
|
+
if (!before.ready && preference.ready && preference.mode === 'docked' && !dockedOwners().length && state.rightWorkbenchOwner) {
|
|
138
|
+
setDocked(state.rightWorkbenchOwner, true); return;
|
|
139
|
+
}
|
|
140
|
+
if (state.workbenchLayoutPreference !== previous.workbenchLayoutPreference || state.rightWorkbenchOwner !== previous.rightWorkbenchOwner ||
|
|
141
|
+
state.rightWorkbenchDockedOwners !== previous.rightWorkbenchDockedOwners) renderLayoutControls();
|
|
112
142
|
});
|
|
@@ -82,12 +82,18 @@ function ensurePanel() {
|
|
|
82
82
|
}
|
|
83
83
|
function renderEntry() {
|
|
84
84
|
var browser = store.get('sharedBrowser');
|
|
85
|
+
var count = (store.get('sharedBrowsers') || []).length;
|
|
85
86
|
var ready = !!store.get('connected') && !!store.get('activeSessionId');
|
|
86
87
|
var label = !store.get('connected') ? 'Browser · Connecting…' : !store.get('activeSessionId') ? 'Browser · Loading session…' : browser ? 'View browser' : 'Open browser';
|
|
87
88
|
['shared-browser-btn', 'shared-browser-toggle-btn'].forEach(function (id) {
|
|
88
89
|
var button = document.getElementById(id);
|
|
89
90
|
if (!button) return;
|
|
90
91
|
var title = id === 'shared-browser-toggle-btn' && ready && store.get('sharedBrowserOpen') ? 'Hide browser' : label;
|
|
92
|
+
if (id === 'shared-browser-toggle-btn') {
|
|
93
|
+
var badge = button.querySelector('.shared-browser-count');
|
|
94
|
+
if (badge) { badge.textContent = String(count); badge.classList.toggle('hidden', count === 0); }
|
|
95
|
+
if (count) title += ' · ' + count + (count === 1 ? ' tab' : ' tabs');
|
|
96
|
+
}
|
|
91
97
|
button.classList.toggle('hidden', !visibleEntry());
|
|
92
98
|
button.title = ready && browser ? title + ' · ' + browser.phase : title;
|
|
93
99
|
button.setAttribute('aria-label', title);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderPaneRuntime } from './split-pane-runtime.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
import { getWs } from './ws-ref.js';
|
|
3
4
|
import { copyToClipboard } from './utils.js';
|
|
@@ -343,7 +344,7 @@ export function createSplitPane(pane, closePane) {
|
|
|
343
344
|
}); header.appendChild(sidebarButton);
|
|
344
345
|
var search = document.createElement("button"); search.type = "button"; search.className = "split-pane-search"; search.setAttribute("aria-label", "Search this session"); search.title = "Search this session"; search.innerHTML = iconHtml("search");
|
|
345
346
|
header.insertBefore(search, close);
|
|
346
|
-
|
|
347
|
+
function refreshReadyHeader() {
|
|
347
348
|
if (paneEl.classList.contains('split-pane-cached')) return;
|
|
348
349
|
var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId), current = sessionById(id);
|
|
349
350
|
var live = frame.clayPane && frame.clayPane.identity(), target = paneTarget(store.snap(), getCachedSessions(), id);
|
|
@@ -352,7 +353,9 @@ export function createSplitPane(pane, closePane) {
|
|
|
352
353
|
frame.dataset.sessionOriginId = live.binding.sessionOriginId;
|
|
353
354
|
updateSplitPaneMetadata(paneEl, { slug: frame.dataset.projectSlug, sessionId: id, title: current.title });
|
|
354
355
|
syncPairChrome(paneEl.parentNode, store.get('splitPanes'));
|
|
355
|
-
}
|
|
356
|
+
}
|
|
357
|
+
frame.addEventListener('clay-pane-ready', refreshReadyHeader);
|
|
358
|
+
frame.addEventListener('clay-pane-config', refreshReadyHeader);
|
|
356
359
|
frame.addEventListener('clay-pane-activation-failed', function () {
|
|
357
360
|
if (paneEl.classList.contains('split-pane-cached')) return;
|
|
358
361
|
syncPaneHeaderState(paneEl);
|
|
@@ -453,12 +456,7 @@ export function updateSplitPaneMetadata(paneEl, pane) {
|
|
|
453
456
|
var sidebarButton = paneEl.querySelector('.split-pane-sidebar');
|
|
454
457
|
if (sidebarButton) sidebarButton.hidden = store.get('projectSplitViewSingletonOrigin') !== (session && session.sessionOriginId) && paneEl.dataset.active !== 'true';
|
|
455
458
|
if (vendor) { var vendorName = session && session.vendor || "unknown"; vendor.hidden = !VENDOR_AVATARS[vendorName]; if (!vendor.hidden) vendor.src = VENDOR_AVATARS[vendorName]; vendor.title = VENDOR_NAMES[vendorName] || vendorName; }
|
|
456
|
-
|
|
457
|
-
var runtimeName = session && session.vendor || "unknown", runtimeModelName = session && typeof session.model === "string" && session.model.trim() || "Model unavailable";
|
|
458
|
-
var runtimeIcon = runtime.querySelector('.split-view-runtime-icon'), runtimeModel = runtime.querySelector('.split-view-model');
|
|
459
|
-
if (runtimeIcon) { runtimeIcon.hidden = !VENDOR_AVATARS[runtimeName]; if (!runtimeIcon.hidden) runtimeIcon.src = VENDOR_AVATARS[runtimeName]; runtimeIcon.title = VENDOR_NAMES[runtimeName] || runtimeName; runtimeIcon.alt = ""; }
|
|
460
|
-
if (runtimeModel) { runtimeModel.textContent = runtimeModelName; runtimeModel.title = (VENDOR_NAMES[runtimeName] || runtimeName) + " · " + runtimeModelName; }
|
|
461
|
-
runtime.setAttribute("aria-label", (VENDOR_NAMES[runtimeName] || runtimeName) + " · " + runtimeModelName);
|
|
459
|
+
renderPaneRuntime(paneEl, session);
|
|
462
460
|
renderPaneWorkers(paneEl, session);
|
|
463
461
|
updatePaneFullAccessButton(paneEl.querySelector('.split-pane-full-access'), session);
|
|
464
462
|
syncPaneHeaderState(paneEl);
|