clay-server 3.2.2-beta.3 → 3.3.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/git-cli.js +370 -0
- package/lib/git-session-attribution.js +219 -0
- package/lib/project-connection.js +1 -15
- package/lib/project-email.js +0 -61
- package/lib/project-http.js +104 -1
- package/lib/project-session-pair.js +129 -43
- package/lib/project-sessions.js +1 -11
- package/lib/project-user-message.js +2 -0
- package/lib/project-worker-proposal.js +317 -0
- package/lib/project.js +23 -8
- package/lib/public/app.js +14 -2
- package/lib/public/css/filebrowser.css +320 -0
- package/lib/public/css/git-panel.css +424 -0
- package/lib/public/css/input.css +0 -3
- package/lib/public/css/worker-proposal.css +127 -0
- package/lib/public/index.html +12 -15
- package/lib/public/modules/app-messages.js +11 -6
- package/lib/public/modules/context-sources.js +0 -90
- package/lib/public/modules/filebrowser.js +135 -21
- package/lib/public/modules/git-panel.js +456 -0
- package/lib/public/modules/input.js +8 -0
- package/lib/public/modules/markdown-slides.js +292 -0
- package/lib/public/modules/mate-sidebar.js +0 -7
- package/lib/public/modules/sidebar.js +42 -0
- package/lib/public/modules/tool-palette.js +1 -2
- package/lib/public/modules/worker-proposal.js +239 -0
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +47 -6
- package/lib/sdk-message-processor.js +26 -2
- package/lib/session-pair-mcp-server.js +2 -2
- package/lib/sessions.js +1 -1
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/adapters/claude.js +43 -17
- package/lib/yoke/index.js +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { refreshIcons } from './icons.js';
|
|
3
|
+
|
|
4
|
+
var levelMenu = null;
|
|
5
|
+
|
|
6
|
+
function normalizedLevel(level) {
|
|
7
|
+
var parsed = Number(level);
|
|
8
|
+
return parsed >= 1 && parsed <= 3 ? parsed : 1;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function directHeadings(markdownEl, level) {
|
|
12
|
+
if (!markdownEl) return [];
|
|
13
|
+
var tagName = "H" + normalizedLevel(level);
|
|
14
|
+
return Array.from(markdownEl.children).filter(function (child) {
|
|
15
|
+
return child.tagName === tagName;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function countMarkdownSlides(markdownEl, level) {
|
|
20
|
+
return directHeadings(markdownEl, level).length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function suggestedMarkdownSlideLevel(markdownEl) {
|
|
24
|
+
var counts = [0, 1, 2, 3].map(function (level) {
|
|
25
|
+
return level === 0 ? 0 : countMarkdownSlides(markdownEl, level);
|
|
26
|
+
});
|
|
27
|
+
var selected = normalizedLevel(store.get('markdownSlidePreferredLevel') || store.get('markdownSlideLevel'));
|
|
28
|
+
if (store.get('markdownSlideLevelExplicit') && counts[selected] > 0) return selected;
|
|
29
|
+
if (counts[1] === 1 && counts[2] > 1) return 2;
|
|
30
|
+
if (counts[1] > 0) return 1;
|
|
31
|
+
if (counts[2] > 0) return 2;
|
|
32
|
+
if (counts[3] > 0) return 3;
|
|
33
|
+
return selected;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function updateSlideButton(active, available, level) {
|
|
37
|
+
var button = document.getElementById("file-viewer-slides");
|
|
38
|
+
var levelButton = document.getElementById("file-viewer-slide-level");
|
|
39
|
+
var selectedLevel = normalizedLevel(level || store.get('markdownSlideLevel'));
|
|
40
|
+
if (!button) return;
|
|
41
|
+
button.classList.toggle("hidden", !available);
|
|
42
|
+
button.classList.toggle("active", active);
|
|
43
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
44
|
+
button.setAttribute("aria-label", active ? "Exit slide show" : "Present Markdown by H" + selectedLevel + " headings");
|
|
45
|
+
button.title = active ? "Exit slide show (Esc)" : "Present by H" + selectedLevel + " sections";
|
|
46
|
+
if (levelButton) {
|
|
47
|
+
levelButton.classList.toggle("hidden", !available || active);
|
|
48
|
+
levelButton.setAttribute("aria-label", "Choose slide heading level. Current: H" + selectedLevel);
|
|
49
|
+
levelButton.title = "Split slides by heading level";
|
|
50
|
+
var label = levelButton.querySelector("span");
|
|
51
|
+
if (label) label.textContent = "H" + selectedLevel;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function syncMarkdownSlidesButton(markdownEl) {
|
|
56
|
+
if (!markdownEl) {
|
|
57
|
+
closeMarkdownSlideLevelMenu();
|
|
58
|
+
updateSlideButton(false, false, store.get('markdownSlideLevel'));
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
var level = suggestedMarkdownSlideLevel(markdownEl);
|
|
62
|
+
var count = countMarkdownSlides(markdownEl, level);
|
|
63
|
+
store.set({ markdownSlideLevel: level });
|
|
64
|
+
updateSlideButton(false, count > 0, level);
|
|
65
|
+
return count;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function closeLevelMenuOnOutside(event) {
|
|
69
|
+
var button = document.getElementById("file-viewer-slide-level");
|
|
70
|
+
if (levelMenu && (levelMenu.contains(event.target) || (button && button.contains(event.target)))) return;
|
|
71
|
+
closeMarkdownSlideLevelMenu();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function closeMarkdownSlideLevelMenu() {
|
|
75
|
+
if (levelMenu) levelMenu.remove();
|
|
76
|
+
levelMenu = null;
|
|
77
|
+
document.removeEventListener("pointerdown", closeLevelMenuOnOutside);
|
|
78
|
+
var button = document.getElementById("file-viewer-slide-level");
|
|
79
|
+
if (button) button.setAttribute("aria-expanded", "false");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function toggleMarkdownSlideLevelMenu(markdownEl) {
|
|
83
|
+
if (levelMenu) {
|
|
84
|
+
closeMarkdownSlideLevelMenu();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!markdownEl) return;
|
|
88
|
+
var button = document.getElementById("file-viewer-slide-level");
|
|
89
|
+
if (!button) return;
|
|
90
|
+
var selected = normalizedLevel(store.get('markdownSlideLevel'));
|
|
91
|
+
var menu = document.createElement("div");
|
|
92
|
+
menu.className = "markdown-slide-level-menu";
|
|
93
|
+
menu.setAttribute("role", "menu");
|
|
94
|
+
menu.setAttribute("aria-label", "Slide heading level");
|
|
95
|
+
menu.innerHTML = '<div class="markdown-slide-level-title">Start a new slide at</div>';
|
|
96
|
+
for (var level = 1; level <= 3; level++) {
|
|
97
|
+
var count = countMarkdownSlides(markdownEl, level);
|
|
98
|
+
var option = document.createElement("button");
|
|
99
|
+
option.type = "button";
|
|
100
|
+
option.className = "markdown-slide-level-option" + (level === selected ? " selected" : "");
|
|
101
|
+
option.disabled = count === 0;
|
|
102
|
+
option.dataset.level = String(level);
|
|
103
|
+
option.setAttribute("role", "menuitemradio");
|
|
104
|
+
option.setAttribute("aria-checked", level === selected ? "true" : "false");
|
|
105
|
+
option.innerHTML = '<span class="markdown-slide-level-name">H' + level + '</span>' +
|
|
106
|
+
'<code>' + "#".repeat(level) + '</code>' +
|
|
107
|
+
'<span class="markdown-slide-level-count">' + count + (count === 1 ? " heading" : " headings") + '</span>' +
|
|
108
|
+
'<i data-lucide="check"></i>';
|
|
109
|
+
option.addEventListener("click", function () {
|
|
110
|
+
var nextLevel = normalizedLevel(this.dataset.level);
|
|
111
|
+
store.set({ markdownSlideLevel: nextLevel, markdownSlidePreferredLevel: nextLevel, markdownSlideLevelExplicit: true });
|
|
112
|
+
updateSlideButton(false, true, nextLevel);
|
|
113
|
+
closeMarkdownSlideLevelMenu();
|
|
114
|
+
refreshIcons();
|
|
115
|
+
});
|
|
116
|
+
menu.appendChild(option);
|
|
117
|
+
}
|
|
118
|
+
document.body.appendChild(menu);
|
|
119
|
+
levelMenu = menu;
|
|
120
|
+
button.setAttribute("aria-expanded", "true");
|
|
121
|
+
var rect = button.getBoundingClientRect();
|
|
122
|
+
menu.style.left = Math.max(12, rect.right - menu.offsetWidth) + "px";
|
|
123
|
+
menu.style.top = Math.min(window.innerHeight - menu.offsetHeight - 12, rect.bottom + 8) + "px";
|
|
124
|
+
setTimeout(function () { document.addEventListener("pointerdown", closeLevelMenuOnOutside); }, 0);
|
|
125
|
+
refreshIcons();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function createControls(viewer) {
|
|
129
|
+
var controls = document.createElement("div");
|
|
130
|
+
controls.className = "markdown-slide-controls";
|
|
131
|
+
controls.innerHTML =
|
|
132
|
+
'<button type="button" class="markdown-slide-nav markdown-slide-prev" aria-label="Previous slide"><i data-lucide="arrow-left"></i></button>' +
|
|
133
|
+
'<span class="markdown-slide-counter" aria-live="polite"></span>' +
|
|
134
|
+
'<span class="markdown-slide-progress" aria-hidden="true"><span></span></span>' +
|
|
135
|
+
'<button type="button" class="markdown-slide-nav markdown-slide-next" aria-label="Next slide"><i data-lucide="arrow-right"></i></button>' +
|
|
136
|
+
'<button type="button" class="markdown-slide-exit" aria-label="Exit slide show"><i data-lucide="minimize-2"></i><span>Exit slides</span></button>';
|
|
137
|
+
controls.querySelector(".markdown-slide-prev").addEventListener("click", function () {
|
|
138
|
+
moveMarkdownSlide(-1);
|
|
139
|
+
});
|
|
140
|
+
controls.querySelector(".markdown-slide-next").addEventListener("click", function () {
|
|
141
|
+
moveMarkdownSlide(1);
|
|
142
|
+
});
|
|
143
|
+
controls.querySelector(".markdown-slide-exit").addEventListener("click", function () {
|
|
144
|
+
exitMarkdownSlides();
|
|
145
|
+
});
|
|
146
|
+
viewer.appendChild(controls);
|
|
147
|
+
return controls;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function showMarkdownSlide(index) {
|
|
151
|
+
var viewer = document.getElementById("file-viewer");
|
|
152
|
+
if (!viewer || !store.get('markdownSlidesActive')) return;
|
|
153
|
+
var slides = viewer.querySelectorAll(".markdown-slide");
|
|
154
|
+
if (!slides.length) return;
|
|
155
|
+
var previous = store.get('markdownSlideIndex') || 0;
|
|
156
|
+
var next = Math.max(0, Math.min(index, slides.length - 1));
|
|
157
|
+
viewer.dataset.slideDirection = next < previous ? "backward" : "forward";
|
|
158
|
+
for (var i = 0; i < slides.length; i++) {
|
|
159
|
+
var active = i === next;
|
|
160
|
+
slides[i].classList.toggle("active", active);
|
|
161
|
+
slides[i].setAttribute("aria-hidden", active ? "false" : "true");
|
|
162
|
+
slides[i].toggleAttribute("inert", !active);
|
|
163
|
+
}
|
|
164
|
+
store.set({ markdownSlideIndex: next, markdownSlideCount: slides.length });
|
|
165
|
+
var counter = viewer.querySelector(".markdown-slide-counter");
|
|
166
|
+
if (counter) counter.textContent = (next + 1) + " / " + slides.length;
|
|
167
|
+
var progress = viewer.querySelector(".markdown-slide-progress > span");
|
|
168
|
+
if (progress) progress.style.width = (((next + 1) / slides.length) * 100) + "%";
|
|
169
|
+
var prevButton = viewer.querySelector(".markdown-slide-prev");
|
|
170
|
+
var nextButton = viewer.querySelector(".markdown-slide-next");
|
|
171
|
+
if (prevButton) prevButton.disabled = next === 0;
|
|
172
|
+
if (nextButton) nextButton.disabled = next === slides.length - 1;
|
|
173
|
+
slides[next].scrollTop = 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function moveMarkdownSlide(delta) {
|
|
177
|
+
showMarkdownSlide((store.get('markdownSlideIndex') || 0) + delta);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function enterMarkdownSlides(markdownEl, startIndex, requestedLevel) {
|
|
181
|
+
var level = normalizedLevel(requestedLevel || store.get('markdownSlideLevel'));
|
|
182
|
+
var headingTag = "H" + level;
|
|
183
|
+
var headings = directHeadings(markdownEl, level);
|
|
184
|
+
if (!headings.length) return false;
|
|
185
|
+
exitMarkdownSlides();
|
|
186
|
+
|
|
187
|
+
var viewer = document.getElementById("file-viewer");
|
|
188
|
+
var deck = document.createElement("div");
|
|
189
|
+
deck.className = "markdown-slide-deck";
|
|
190
|
+
var children = Array.from(markdownEl.children);
|
|
191
|
+
var prelude = [];
|
|
192
|
+
var slide = null;
|
|
193
|
+
for (var i = 0; i < children.length; i++) {
|
|
194
|
+
var child = children[i];
|
|
195
|
+
if (child.tagName === headingTag) {
|
|
196
|
+
if (!slide && prelude.length) {
|
|
197
|
+
var cover = document.createElement("section");
|
|
198
|
+
cover.className = "markdown-slide markdown-slide-cover";
|
|
199
|
+
cover.setAttribute("role", "group");
|
|
200
|
+
cover.setAttribute("aria-roledescription", "slide");
|
|
201
|
+
for (var p = 0; p < prelude.length; p++) cover.appendChild(prelude[p]);
|
|
202
|
+
deck.appendChild(cover);
|
|
203
|
+
prelude = [];
|
|
204
|
+
}
|
|
205
|
+
slide = document.createElement("section");
|
|
206
|
+
slide.className = "markdown-slide";
|
|
207
|
+
slide.setAttribute("role", "group");
|
|
208
|
+
slide.setAttribute("aria-roledescription", "slide");
|
|
209
|
+
child.classList.add("markdown-slide-heading");
|
|
210
|
+
slide.appendChild(child);
|
|
211
|
+
deck.appendChild(slide);
|
|
212
|
+
} else if (slide) {
|
|
213
|
+
slide.appendChild(child);
|
|
214
|
+
} else {
|
|
215
|
+
prelude.push(child);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var slides = Array.from(deck.children);
|
|
220
|
+
for (var j = 0; j < slides.length; j++) {
|
|
221
|
+
slides[j].setAttribute("aria-label", "Slide " + (j + 1) + " of " + slides.length);
|
|
222
|
+
}
|
|
223
|
+
markdownEl.appendChild(deck);
|
|
224
|
+
viewer.classList.add("markdown-slides-active");
|
|
225
|
+
viewer.dataset.slideLevel = String(level);
|
|
226
|
+
createControls(viewer);
|
|
227
|
+
store.set({ markdownSlidesActive: true, markdownSlideCount: slides.length, markdownSlideIndex: 0, markdownSlideLevel: level });
|
|
228
|
+
updateSlideButton(true, true, level);
|
|
229
|
+
showMarkdownSlide(typeof startIndex === "number" ? startIndex : 0);
|
|
230
|
+
refreshIcons();
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function exitMarkdownSlides() {
|
|
235
|
+
var viewer = document.getElementById("file-viewer");
|
|
236
|
+
if (!viewer) return;
|
|
237
|
+
var markdownEl = viewer.querySelector(".file-viewer-markdown");
|
|
238
|
+
var deck = markdownEl ? markdownEl.querySelector(":scope > .markdown-slide-deck") : null;
|
|
239
|
+
if (deck) {
|
|
240
|
+
var fragment = document.createDocumentFragment();
|
|
241
|
+
var slides = Array.from(deck.children);
|
|
242
|
+
for (var i = 0; i < slides.length; i++) {
|
|
243
|
+
while (slides[i].firstChild) {
|
|
244
|
+
slides[i].firstChild.classList.remove("markdown-slide-heading");
|
|
245
|
+
fragment.appendChild(slides[i].firstChild);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
markdownEl.insertBefore(fragment, deck);
|
|
249
|
+
deck.remove();
|
|
250
|
+
}
|
|
251
|
+
var controls = viewer.querySelector(".markdown-slide-controls");
|
|
252
|
+
if (controls) controls.remove();
|
|
253
|
+
viewer.classList.remove("markdown-slides-active");
|
|
254
|
+
delete viewer.dataset.slideDirection;
|
|
255
|
+
delete viewer.dataset.slideLevel;
|
|
256
|
+
store.set({ markdownSlidesActive: false, markdownSlideCount: 0, markdownSlideIndex: 0 });
|
|
257
|
+
closeMarkdownSlideLevelMenu();
|
|
258
|
+
var level = normalizedLevel(store.get('markdownSlideLevel'));
|
|
259
|
+
updateSlideButton(false, countMarkdownSlides(markdownEl, level) > 0, level);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function handleMarkdownSlideKey(event) {
|
|
263
|
+
if (levelMenu && event.key === "Escape") {
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
closeMarkdownSlideLevelMenu();
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
if (!store.get('markdownSlidesActive')) return false;
|
|
269
|
+
var key = event.key;
|
|
270
|
+
if (key === "Escape") {
|
|
271
|
+
event.preventDefault();
|
|
272
|
+
exitMarkdownSlides();
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
if (event.target && event.target.closest && event.target.closest("button, a, input, textarea, select")) return false;
|
|
276
|
+
if (key === "ArrowRight" || key === "ArrowDown" || key === "PageDown" || key === " ") {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
moveMarkdownSlide(1);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (key === "ArrowLeft" || key === "ArrowUp" || key === "PageUp") {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
moveMarkdownSlide(-1);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (key === "Home" || key === "End") {
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
showMarkdownSlide(key === "Home" ? 0 : (store.get('markdownSlideCount') || 1) - 1);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
@@ -109,13 +109,6 @@ export function initMateSidebar(wsGetter) {
|
|
|
109
109
|
if (origBtn) origBtn.click();
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
|
-
var mateEmailBtn = document.getElementById("mate-email-btn");
|
|
113
|
-
if (mateEmailBtn) {
|
|
114
|
-
mateEmailBtn.addEventListener("click", function () {
|
|
115
|
-
var origBtn = document.getElementById("email-sidebar-btn");
|
|
116
|
-
if (origBtn) origBtn.click();
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
112
|
var mateSkillsBtn = document.getElementById("mate-skills-btn");
|
|
120
113
|
if (mateSkillsBtn) {
|
|
121
114
|
mateSkillsBtn.addEventListener("click", function () {
|
|
@@ -2,6 +2,7 @@ import { closeArchive } from './sticky-notes.js';
|
|
|
2
2
|
import { closeScheduler } from './scheduler.js';
|
|
3
3
|
import { initSidebarSessions } from './sidebar-sessions.js';
|
|
4
4
|
import { focusFileTree } from './filebrowser.js';
|
|
5
|
+
import { refreshGitStatus } from './git-panel.js';
|
|
5
6
|
import { initSidebarProjects, closeProjectCtxMenu } from './sidebar-projects.js';
|
|
6
7
|
import {
|
|
7
8
|
initSidebarMates,
|
|
@@ -118,16 +119,20 @@ export function initSidebar(_ctx) {
|
|
|
118
119
|
|
|
119
120
|
// --- Panel switch (sessions / files / projects) ---
|
|
120
121
|
var fileBrowserBtn = ctx.$("file-browser-btn");
|
|
122
|
+
var gitBtn = ctx.$("git-sidebar-btn");
|
|
121
123
|
var projectsPanel = ctx.$("sidebar-panel-projects");
|
|
122
124
|
var sessionsPanel = ctx.$("sidebar-panel-sessions");
|
|
123
125
|
var filesPanel = ctx.$("sidebar-panel-files");
|
|
126
|
+
var gitPanel = ctx.$("sidebar-panel-git");
|
|
124
127
|
var sessionsHeaderContent = ctx.$("sessions-header-content");
|
|
125
128
|
var filePanelClose = ctx.$("file-panel-close");
|
|
129
|
+
var gitPanelClose = ctx.$("git-panel-close");
|
|
126
130
|
|
|
127
131
|
function hideAllPanels() {
|
|
128
132
|
if (projectsPanel) projectsPanel.classList.add("hidden");
|
|
129
133
|
if (sessionsPanel) sessionsPanel.classList.add("hidden");
|
|
130
134
|
if (filesPanel) filesPanel.classList.add("hidden");
|
|
135
|
+
if (gitPanel) gitPanel.classList.add("hidden");
|
|
131
136
|
if (sessionsHeaderContent) sessionsHeaderContent.classList.add("hidden");
|
|
132
137
|
}
|
|
133
138
|
|
|
@@ -155,6 +160,16 @@ export function initSidebar(_ctx) {
|
|
|
155
160
|
requestAnimationFrame(function () { focusFileTree(); });
|
|
156
161
|
}
|
|
157
162
|
|
|
163
|
+
function showGitPanel() {
|
|
164
|
+
hideAllPanels();
|
|
165
|
+
if (gitPanel) {
|
|
166
|
+
gitPanel.classList.remove("hidden");
|
|
167
|
+
gitPanel.classList.remove("fb-exit");
|
|
168
|
+
gitPanel.classList.add("fb-enter");
|
|
169
|
+
}
|
|
170
|
+
refreshGitStatus();
|
|
171
|
+
}
|
|
172
|
+
|
|
158
173
|
function hideFilesPanel(cb) {
|
|
159
174
|
if (!filesPanel || filesPanel.classList.contains("hidden")) { if (cb) cb(); return; }
|
|
160
175
|
filesPanel.classList.remove("fb-enter");
|
|
@@ -168,6 +183,20 @@ export function initSidebar(_ctx) {
|
|
|
168
183
|
filesPanel.addEventListener("animationend", onDone);
|
|
169
184
|
}
|
|
170
185
|
|
|
186
|
+
function hideGitPanel(cb) {
|
|
187
|
+
if (!gitPanel || gitPanel.classList.contains("hidden")) { if (cb) cb(); return; }
|
|
188
|
+
gitPanel.classList.remove("fb-enter");
|
|
189
|
+
gitPanel.classList.add("fb-exit");
|
|
190
|
+
function onDone(e) {
|
|
191
|
+
if (e.target !== gitPanel) return;
|
|
192
|
+
gitPanel.removeEventListener("animationend", onDone);
|
|
193
|
+
gitPanel.classList.remove("fb-exit");
|
|
194
|
+
gitPanel.classList.add("hidden");
|
|
195
|
+
if (cb) cb();
|
|
196
|
+
}
|
|
197
|
+
gitPanel.addEventListener("animationend", onDone);
|
|
198
|
+
}
|
|
199
|
+
|
|
171
200
|
if (fileBrowserBtn) {
|
|
172
201
|
// Clicking the tile toggles: open if closed, close (back to sessions)
|
|
173
202
|
// if it's already the visible panel. Same behavior via the Cmd+O
|
|
@@ -187,6 +216,19 @@ export function initSidebar(_ctx) {
|
|
|
187
216
|
});
|
|
188
217
|
});
|
|
189
218
|
}
|
|
219
|
+
if (gitBtn) {
|
|
220
|
+
gitBtn.addEventListener("click", function () {
|
|
221
|
+
if (gitPanel && !gitPanel.classList.contains("hidden")) {
|
|
222
|
+
hideGitPanel(function () { showSessionsPanel(); });
|
|
223
|
+
}
|
|
224
|
+
else showGitPanel();
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (gitPanelClose) {
|
|
228
|
+
gitPanelClose.addEventListener("click", function () {
|
|
229
|
+
hideGitPanel(function () { showSessionsPanel(); });
|
|
230
|
+
});
|
|
231
|
+
}
|
|
190
232
|
|
|
191
233
|
// --- User island width sync ---
|
|
192
234
|
var userIsland = document.getElementById("user-island");
|
|
@@ -25,7 +25,7 @@ var SESSION_TOOLS = [
|
|
|
25
25
|
{ id: "sticky-notes-sidebar-btn", icon: "sticky-note", label: "Sticky Notes", countId: "sticky-notes-sidebar-count" },
|
|
26
26
|
{ id: "scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
|
|
27
27
|
{ id: "loop-tool-btn", icon: "repeat", label: "Loop" },
|
|
28
|
-
{ id: "
|
|
28
|
+
{ id: "git-sidebar-btn", icon: "git-branch", label: "Git", countId: "git-sidebar-count" },
|
|
29
29
|
{ id: "mcp-btn", icon: "cable", label: "MCP Servers", countId: "mcp-sidebar-count" },
|
|
30
30
|
{ id: "skills-btn", icon: "puzzle", label: "Skills" },
|
|
31
31
|
];
|
|
@@ -36,7 +36,6 @@ var MATE_TOOLS = [
|
|
|
36
36
|
{ id: "mate-sticky-notes-btn", icon: "sticky-note", label: "Sticky Notes" },
|
|
37
37
|
{ id: "mate-scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
|
|
38
38
|
{ id: "mate-debate-btn", icon: "mic", label: "Debate" },
|
|
39
|
-
{ id: "mate-email-btn", icon: "mail", label: "Email" },
|
|
40
39
|
{ id: "mate-mcp-btn", icon: "cable", label: "MCP Servers", countId: "mate-mcp-sidebar-count" },
|
|
41
40
|
{ id: "mate-skills-btn", icon: "puzzle", label: "Skills" },
|
|
42
41
|
];
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { getWs } from './ws-ref.js';
|
|
2
|
+
import { addToMessages, scrollToBottom, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
3
|
+
import { effortLevelsFor, effortDisplayName } from './app-panels.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
|
|
6
|
+
function optionValue(entry) {
|
|
7
|
+
if (typeof entry === "string") return entry;
|
|
8
|
+
return entry && (entry.value || entry.id) || "";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function optionLabel(entry) {
|
|
12
|
+
if (typeof entry === "string") return entry;
|
|
13
|
+
return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findCard(proposalId) {
|
|
17
|
+
var cards = document.querySelectorAll(".worker-proposal-card");
|
|
18
|
+
for (var i = 0; i < cards.length; i++) {
|
|
19
|
+
if (cards[i].dataset.proposalId === proposalId) return cards[i];
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function planSteps(plan) {
|
|
25
|
+
return String(plan || "").split("\n").map(function (line) {
|
|
26
|
+
return line.trim().replace(/^\s*(?:\d+[.)]|[-*])\s*/, "");
|
|
27
|
+
}).filter(Boolean).slice(0, 8);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildVendorSelect(msg) {
|
|
31
|
+
var select = document.createElement("select");
|
|
32
|
+
select.className = "worker-proposal-select worker-proposal-vendor";
|
|
33
|
+
var installed = (msg.options && msg.options.installedVendors) || [];
|
|
34
|
+
for (var i = 0; i < installed.length; i++) {
|
|
35
|
+
var option = document.createElement("option");
|
|
36
|
+
option.value = installed[i];
|
|
37
|
+
option.textContent = VENDOR_NAMES[installed[i]] || installed[i];
|
|
38
|
+
select.appendChild(option);
|
|
39
|
+
}
|
|
40
|
+
if (installed.indexOf(msg.recommendedVendor) !== -1) select.value = msg.recommendedVendor;
|
|
41
|
+
return select;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fillModels(select, vendor, msg, preferred) {
|
|
45
|
+
var models = (msg.options && msg.options.modelsByVendor && msg.options.modelsByVendor[vendor]) || [];
|
|
46
|
+
select.innerHTML = "";
|
|
47
|
+
var automatic = document.createElement("option");
|
|
48
|
+
automatic.value = "";
|
|
49
|
+
automatic.textContent = "Automatic";
|
|
50
|
+
select.appendChild(automatic);
|
|
51
|
+
for (var i = 0; i < models.length; i++) {
|
|
52
|
+
var option = document.createElement("option");
|
|
53
|
+
option.value = optionValue(models[i]);
|
|
54
|
+
option.textContent = optionLabel(models[i]);
|
|
55
|
+
select.appendChild(option);
|
|
56
|
+
}
|
|
57
|
+
select.value = preferred || "";
|
|
58
|
+
if (select.value !== (preferred || "")) select.value = "";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fillEffort(card, vendor, model, msg, preferred) {
|
|
62
|
+
var wrap = card.querySelector(".worker-proposal-effort");
|
|
63
|
+
var buttons = card.querySelector(".worker-proposal-effort-buttons");
|
|
64
|
+
var capabilities = (msg.options && msg.options.capabilitiesByVendor && msg.options.capabilitiesByVendor[vendor]) || {};
|
|
65
|
+
if (capabilities.effort === false) {
|
|
66
|
+
wrap.classList.add("hidden");
|
|
67
|
+
buttons.innerHTML = "";
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
wrap.classList.remove("hidden");
|
|
71
|
+
var models = (msg.options && msg.options.modelsByVendor && msg.options.modelsByVendor[vendor]) || [];
|
|
72
|
+
var levels = effortLevelsFor(vendor, models, model);
|
|
73
|
+
buttons.innerHTML = "";
|
|
74
|
+
var selected = levels.indexOf(preferred) !== -1 ? preferred : (levels.indexOf("medium") !== -1 ? "medium" : levels[0]);
|
|
75
|
+
for (var i = 0; i < levels.length; i++) {
|
|
76
|
+
var button = document.createElement("button");
|
|
77
|
+
button.type = "button";
|
|
78
|
+
button.className = "worker-proposal-effort-btn" + (levels[i] === selected ? " active" : "");
|
|
79
|
+
button.dataset.effort = levels[i];
|
|
80
|
+
button.textContent = effortDisplayName(levels[i]);
|
|
81
|
+
button.addEventListener("click", function () {
|
|
82
|
+
buttons.querySelectorAll("button").forEach(function (item) { item.classList.remove("active"); });
|
|
83
|
+
this.classList.add("active");
|
|
84
|
+
});
|
|
85
|
+
buttons.appendChild(button);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function setControlsDisabled(card, disabled) {
|
|
90
|
+
var controls = card.querySelectorAll("select, .worker-proposal-effort-btn, .worker-proposal-action");
|
|
91
|
+
for (var i = 0; i < controls.length; i++) controls[i].disabled = disabled;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function statusLabel(status) {
|
|
95
|
+
if (status === "starting") return "Starting Worker";
|
|
96
|
+
if (status === "running") return "Worker running";
|
|
97
|
+
if (status === "completed") return "Completed";
|
|
98
|
+
if (status === "declined") return "Continuing here";
|
|
99
|
+
if (status === "error") return "Needs attention";
|
|
100
|
+
return "Suggested by Fable";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function applyState(card, msg) {
|
|
104
|
+
var status = msg.status || "pending";
|
|
105
|
+
card.dataset.status = status;
|
|
106
|
+
var badge = card.querySelector(".worker-proposal-status");
|
|
107
|
+
if (badge) badge.textContent = statusLabel(status);
|
|
108
|
+
var error = card.querySelector(".worker-proposal-error");
|
|
109
|
+
if (error) {
|
|
110
|
+
error.textContent = msg.error || "";
|
|
111
|
+
error.classList.toggle("hidden", !msg.error);
|
|
112
|
+
}
|
|
113
|
+
var preview = card.querySelector(".worker-proposal-result");
|
|
114
|
+
if (preview && msg.resultPreview) {
|
|
115
|
+
preview.textContent = msg.resultPreview;
|
|
116
|
+
preview.classList.remove("hidden");
|
|
117
|
+
}
|
|
118
|
+
setControlsDisabled(card, status !== "pending");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function sendDecision(card, accepted) {
|
|
122
|
+
var ws = getWs();
|
|
123
|
+
if (!ws || ws.readyState !== 1 || card.dataset.status !== "pending") return;
|
|
124
|
+
var vendor = card.querySelector(".worker-proposal-vendor");
|
|
125
|
+
var model = card.querySelector(".worker-proposal-model");
|
|
126
|
+
var effort = card.querySelector(".worker-proposal-effort-btn.active");
|
|
127
|
+
applyState(card, { status: accepted ? "starting" : "declined" });
|
|
128
|
+
ws.send(JSON.stringify({
|
|
129
|
+
type: "worker_proposal_response",
|
|
130
|
+
proposalId: card.dataset.proposalId,
|
|
131
|
+
accepted: accepted,
|
|
132
|
+
vendor: vendor ? vendor.value : "",
|
|
133
|
+
model: model ? model.value : "",
|
|
134
|
+
effort: effort ? effort.dataset.effort : "",
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function renderWorkerProposal(msg) {
|
|
139
|
+
var existing = findCard(msg.proposalId);
|
|
140
|
+
if (existing) {
|
|
141
|
+
applyState(existing, msg);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
var card = document.createElement("section");
|
|
145
|
+
card.className = "worker-proposal-card";
|
|
146
|
+
card.dataset.proposalId = msg.proposalId;
|
|
147
|
+
card.dataset.status = msg.status || "pending";
|
|
148
|
+
|
|
149
|
+
var header = document.createElement("div");
|
|
150
|
+
header.className = "worker-proposal-header";
|
|
151
|
+
header.innerHTML = '<div class="worker-proposal-mark">' + iconHtml("git-branch") + '</div><div class="worker-proposal-heading"><span class="worker-proposal-kicker">DRIVER / WORKER</span><strong>Let a Worker handle the execution</strong></div><span class="worker-proposal-status"></span>';
|
|
152
|
+
card.appendChild(header);
|
|
153
|
+
|
|
154
|
+
var summary = document.createElement("p");
|
|
155
|
+
summary.className = "worker-proposal-summary";
|
|
156
|
+
summary.textContent = msg.summary || "This task can be planned here and executed in a visible Worker session.";
|
|
157
|
+
card.appendChild(summary);
|
|
158
|
+
|
|
159
|
+
var steps = planSteps(msg.plan);
|
|
160
|
+
if (steps.length > 0) {
|
|
161
|
+
var plan = document.createElement("ol");
|
|
162
|
+
plan.className = "worker-proposal-plan";
|
|
163
|
+
for (var i = 0; i < steps.length; i++) {
|
|
164
|
+
var item = document.createElement("li");
|
|
165
|
+
item.textContent = steps[i];
|
|
166
|
+
plan.appendChild(item);
|
|
167
|
+
}
|
|
168
|
+
card.appendChild(plan);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
var config = document.createElement("div");
|
|
172
|
+
config.className = "worker-proposal-config";
|
|
173
|
+
var vendorField = document.createElement("label");
|
|
174
|
+
vendorField.className = "worker-proposal-field worker-proposal-vendor-field";
|
|
175
|
+
vendorField.innerHTML = '<span>Worker</span><span class="worker-proposal-vendor-control"><img alt=""></span>';
|
|
176
|
+
var vendorSelect = buildVendorSelect(msg);
|
|
177
|
+
vendorField.querySelector(".worker-proposal-vendor-control").appendChild(vendorSelect);
|
|
178
|
+
config.appendChild(vendorField);
|
|
179
|
+
var modelField = document.createElement("label");
|
|
180
|
+
modelField.className = "worker-proposal-field";
|
|
181
|
+
modelField.innerHTML = "<span>Model</span>";
|
|
182
|
+
var modelSelect = document.createElement("select");
|
|
183
|
+
modelSelect.className = "worker-proposal-select worker-proposal-model";
|
|
184
|
+
modelField.appendChild(modelSelect);
|
|
185
|
+
config.appendChild(modelField);
|
|
186
|
+
var effortField = document.createElement("div");
|
|
187
|
+
effortField.className = "worker-proposal-field worker-proposal-effort";
|
|
188
|
+
effortField.innerHTML = '<span>Reasoning</span><div class="worker-proposal-effort-buttons"></div>';
|
|
189
|
+
config.appendChild(effortField);
|
|
190
|
+
card.appendChild(config);
|
|
191
|
+
|
|
192
|
+
function syncVendor(preferredModel, preferredEffort) {
|
|
193
|
+
var vendor = vendorSelect.value;
|
|
194
|
+
var avatar = vendorField.querySelector("img");
|
|
195
|
+
avatar.src = VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude;
|
|
196
|
+
fillModels(modelSelect, vendor, msg, preferredModel);
|
|
197
|
+
fillEffort(card, vendor, modelSelect.value, msg, preferredEffort);
|
|
198
|
+
}
|
|
199
|
+
vendorSelect.addEventListener("change", function () { syncVendor("", "medium"); });
|
|
200
|
+
modelSelect.addEventListener("change", function () {
|
|
201
|
+
var active = card.querySelector(".worker-proposal-effort-btn.active");
|
|
202
|
+
fillEffort(card, vendorSelect.value, modelSelect.value, msg, active ? active.dataset.effort : "medium");
|
|
203
|
+
});
|
|
204
|
+
syncVendor(msg.recommendedModel || "", msg.recommendedEffort || "medium");
|
|
205
|
+
|
|
206
|
+
var error = document.createElement("div");
|
|
207
|
+
error.className = "worker-proposal-error hidden";
|
|
208
|
+
card.appendChild(error);
|
|
209
|
+
var result = document.createElement("div");
|
|
210
|
+
result.className = "worker-proposal-result hidden";
|
|
211
|
+
card.appendChild(result);
|
|
212
|
+
|
|
213
|
+
var actions = document.createElement("div");
|
|
214
|
+
actions.className = "worker-proposal-actions";
|
|
215
|
+
var decline = document.createElement("button");
|
|
216
|
+
decline.type = "button";
|
|
217
|
+
decline.className = "worker-proposal-action secondary";
|
|
218
|
+
decline.textContent = "Continue here";
|
|
219
|
+
decline.addEventListener("click", function () { sendDecision(card, false); });
|
|
220
|
+
var accept = document.createElement("button");
|
|
221
|
+
accept.type = "button";
|
|
222
|
+
accept.className = "worker-proposal-action primary";
|
|
223
|
+
accept.innerHTML = iconHtml("panel-right-open") + "<span>Run with Worker</span>";
|
|
224
|
+
accept.addEventListener("click", function () { sendDecision(card, true); });
|
|
225
|
+
actions.appendChild(decline);
|
|
226
|
+
actions.appendChild(accept);
|
|
227
|
+
card.appendChild(actions);
|
|
228
|
+
|
|
229
|
+
addToMessages(card);
|
|
230
|
+
applyState(card, msg);
|
|
231
|
+
refreshIcons();
|
|
232
|
+
scrollToBottom();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function updateWorkerProposal(msg) {
|
|
236
|
+
var card = findCard(msg.proposalId);
|
|
237
|
+
if (!card) return;
|
|
238
|
+
applyState(card, msg);
|
|
239
|
+
}
|
package/lib/public/style.css
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
@import url("css/icon-strip.css");
|
|
3
3
|
@import url("css/title-bar.css");
|
|
4
4
|
@import url("css/pane.css");
|
|
5
|
+
@import url("css/worker-proposal.css");
|
|
5
6
|
@import url("css/sidebar.css");
|
|
6
7
|
@import url("css/overlays.css");
|
|
7
8
|
@import url("css/menus.css");
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
@import url("css/rewind.css");
|
|
10
11
|
@import url("css/input.css");
|
|
11
12
|
@import url("css/filebrowser.css");
|
|
13
|
+
@import url("css/git-panel.css");
|
|
12
14
|
@import url("css/diff.css");
|
|
13
15
|
@import url("css/highlight.css");
|
|
14
16
|
@import url("css/server-settings.css");
|