clay-server 3.3.0-beta.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/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-sessions.js +1 -11
- package/lib/project-user-message.js +2 -0
- package/lib/project.js +18 -5
- 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/index.html +12 -15
- package/lib/public/modules/app-messages.js +1 -5
- 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/style.css +1 -0
- package/lib/sdk-bridge.js +46 -5
- package/lib/sdk-message-processor.js +26 -2
- package/lib/sessions.js +1 -1
- 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
|
];
|
package/lib/public/style.css
CHANGED
package/lib/sdk-bridge.js
CHANGED
|
@@ -863,7 +863,8 @@ function createSDKBridge(opts) {
|
|
|
863
863
|
// Stream ended normally after a task stop — no "result" message was sent,
|
|
864
864
|
// so the session is still marked as processing. Send interrupted feedback.
|
|
865
865
|
console.log("[sdk-bridge] processQueryStream ended: isProcessing=" + session.isProcessing + " taskStopRequested=" + session.taskStopRequested);
|
|
866
|
-
|
|
866
|
+
var stillOwnsRuntime = session.queryInstance === myQueryInstance;
|
|
867
|
+
if (session.isProcessing && session.taskStopRequested && stillOwnsRuntime) {
|
|
867
868
|
session.isProcessing = false;
|
|
868
869
|
onProcessingChanged();
|
|
869
870
|
send({ type: "status", processing: false });
|
|
@@ -874,9 +875,24 @@ function createSDKBridge(opts) {
|
|
|
874
875
|
sendAndRecord(session, { type: "info", text: interruptMsg });
|
|
875
876
|
sendAndRecord(session, { type: "done", code: 0 });
|
|
876
877
|
sm.broadcastSessionList();
|
|
878
|
+
} else if (session.isProcessing && stillOwnsRuntime) {
|
|
879
|
+
// Codex and Kiro can report a protocol error and then close their
|
|
880
|
+
// iterator without a result event. Treat any result-less stream end as
|
|
881
|
+
// a failed turn so the UI cannot remain on processing forever.
|
|
882
|
+
session.isProcessing = false;
|
|
883
|
+
onProcessingChanged();
|
|
884
|
+
if (!session._lastAdapterError) {
|
|
885
|
+
sendAndRecord(session, {
|
|
886
|
+
type: "error",
|
|
887
|
+
text: "The agent connection ended before completing the response.",
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
session._lastAdapterError = null;
|
|
891
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
892
|
+
sm.broadcastSessionList();
|
|
877
893
|
}
|
|
878
894
|
} catch (err) {
|
|
879
|
-
if (session.isProcessing) {
|
|
895
|
+
if (session.isProcessing && session.queryInstance === myQueryInstance) {
|
|
880
896
|
session.isProcessing = false;
|
|
881
897
|
onProcessingChanged();
|
|
882
898
|
if (err.name === "AbortError" || (myAbortController && myAbortController.signal.aborted) || session.taskStopRequested) {
|
|
@@ -1310,6 +1326,7 @@ function createSDKBridge(opts) {
|
|
|
1310
1326
|
session.pendingElicitations = {};
|
|
1311
1327
|
session.streamedText = false;
|
|
1312
1328
|
session.responsePreview = "";
|
|
1329
|
+
session._lastAdapterError = null;
|
|
1313
1330
|
|
|
1314
1331
|
// For in-process path, create AbortController. For worker path, the adapter
|
|
1315
1332
|
// handles abort internally and exposes it via handle.abort().
|
|
@@ -1574,7 +1591,22 @@ function createSDKBridge(opts) {
|
|
|
1574
1591
|
|
|
1575
1592
|
// Push initial user message through the QueryHandle
|
|
1576
1593
|
console.log("[sdk-bridge] pushing initial message via handle.pushMessage...");
|
|
1577
|
-
handle.pushMessage(text, images);
|
|
1594
|
+
var initialMessageAccepted = handle.pushMessage(text, images) !== false;
|
|
1595
|
+
if (!initialMessageAccepted) {
|
|
1596
|
+
console.error("[sdk-bridge] Query rejected initial message for session " + session.localId);
|
|
1597
|
+
try { handle.close(); } catch (e) {}
|
|
1598
|
+
if (session.queryInstance === handle) session.queryInstance = null;
|
|
1599
|
+
session.messageQueue = null;
|
|
1600
|
+
session.abortController = null;
|
|
1601
|
+
session.isProcessing = false;
|
|
1602
|
+
onProcessingChanged();
|
|
1603
|
+
sendAndRecord(session, { type: "error", text: "The agent connection closed before it received your message. Please send it again." });
|
|
1604
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
1605
|
+
sm.broadcastSessionList();
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
session._awaitingTurnResult = true;
|
|
1609
|
+
session._queuedTurnCount = 0;
|
|
1578
1610
|
console.log("[sdk-bridge] pushMessage done, starting processQueryStream...");
|
|
1579
1611
|
|
|
1580
1612
|
// Flush messages that arrived while createQuery was still awaiting
|
|
@@ -1585,7 +1617,9 @@ function createSDKBridge(opts) {
|
|
|
1585
1617
|
session.pendingPush = [];
|
|
1586
1618
|
console.log("[sdk-bridge] flushing " + backlog.length + " buffered message(s) into new query");
|
|
1587
1619
|
for (var bi = 0; bi < backlog.length; bi++) {
|
|
1588
|
-
handle.pushMessage(backlog[bi].text, backlog[bi].images)
|
|
1620
|
+
if (handle.pushMessage(backlog[bi].text, backlog[bi].images) !== false) {
|
|
1621
|
+
session._queuedTurnCount++;
|
|
1622
|
+
}
|
|
1589
1623
|
}
|
|
1590
1624
|
}
|
|
1591
1625
|
|
|
@@ -1634,7 +1668,14 @@ function createSDKBridge(opts) {
|
|
|
1634
1668
|
} catch (e) {
|
|
1635
1669
|
console.error("[sdk-bridge] QueryHandle rejected message for session " + session.localId + ":", e.message || e);
|
|
1636
1670
|
}
|
|
1637
|
-
if (delivered)
|
|
1671
|
+
if (delivered) {
|
|
1672
|
+
if (session._awaitingTurnResult) {
|
|
1673
|
+
session._queuedTurnCount = (session._queuedTurnCount || 0) + 1;
|
|
1674
|
+
} else {
|
|
1675
|
+
session._awaitingTurnResult = true;
|
|
1676
|
+
}
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1638
1679
|
|
|
1639
1680
|
// The handle object can outlive its underlying input queue or worker.
|
|
1640
1681
|
// Retire it before the caller starts a resumed replacement query; the
|
|
@@ -451,6 +451,8 @@ function attachMessageProcessor(ctx) {
|
|
|
451
451
|
if (parsed.terminalReason) execError += " (reason: " + parsed.terminalReason + ")";
|
|
452
452
|
console.error("[sdk-bridge] Execution error for session " + session.localId + ": " + execError);
|
|
453
453
|
session.isProcessing = false;
|
|
454
|
+
session._awaitingTurnResult = false;
|
|
455
|
+
session._queuedTurnCount = 0;
|
|
454
456
|
onProcessingChanged();
|
|
455
457
|
sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
|
|
456
458
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
@@ -458,8 +460,17 @@ function attachMessageProcessor(ctx) {
|
|
|
458
460
|
return;
|
|
459
461
|
}
|
|
460
462
|
|
|
461
|
-
session.
|
|
462
|
-
|
|
463
|
+
session._lastAdapterError = null;
|
|
464
|
+
var hasQueuedTurn = (session._queuedTurnCount || 0) > 0;
|
|
465
|
+
if (hasQueuedTurn) {
|
|
466
|
+
session._queuedTurnCount--;
|
|
467
|
+
session._awaitingTurnResult = true;
|
|
468
|
+
} else {
|
|
469
|
+
session.isProcessing = false;
|
|
470
|
+
session._awaitingTurnResult = false;
|
|
471
|
+
session._queuedTurnCount = 0;
|
|
472
|
+
onProcessingChanged();
|
|
473
|
+
}
|
|
463
474
|
// Detect "Not logged in" scenario early for the check below
|
|
464
475
|
var previewTrimmed = (session.responsePreview || "").trim();
|
|
465
476
|
var isZeroCost = !parsed.cost || parsed.cost === 0;
|
|
@@ -494,6 +505,11 @@ function attachMessageProcessor(ctx) {
|
|
|
494
505
|
emitAuthRequired(session);
|
|
495
506
|
}
|
|
496
507
|
sendAndRecord(session, { type: "done", code: 0 });
|
|
508
|
+
if (hasQueuedTurn) {
|
|
509
|
+
// The adapter immediately continues with the already queued user turn.
|
|
510
|
+
// Restore the client stop state after the previous turn's done event.
|
|
511
|
+
sendToSession(session, { type: "status", status: "processing" });
|
|
512
|
+
}
|
|
497
513
|
var _donePreviewText = (session.responsePreview || "").replace(/\s+/g, " ").trim();
|
|
498
514
|
if (_donePreviewText.length > 140) _donePreviewText = _donePreviewText.substring(0, 140) + "...";
|
|
499
515
|
var _doneTitle = mateDisplayName ? (mateDisplayName + " responded") : (session.title || "Claude");
|
|
@@ -770,6 +786,14 @@ function attachMessageProcessor(ctx) {
|
|
|
770
786
|
onProcessingChanged();
|
|
771
787
|
emitAuthRequired(session);
|
|
772
788
|
|
|
789
|
+
} else if (parsed.yokeType === "error") {
|
|
790
|
+
// Adapters use a neutral error event for turn failures that arrive as
|
|
791
|
+
// protocol messages instead of thrown iterator errors. Remember it so
|
|
792
|
+
// processQueryStream can finish the turn when the iterator closes.
|
|
793
|
+
var adapterErrorText = parsed.text || parsed.message || parsed.error || "Agent runtime error";
|
|
794
|
+
session._lastAdapterError = adapterErrorText;
|
|
795
|
+
sendAndRecord(session, { type: "error", text: adapterErrorText });
|
|
796
|
+
|
|
773
797
|
} else if (parsed.yokeType === "model_refusal") {
|
|
774
798
|
// Model declined the request. "fallback" => the CLI retried on another
|
|
775
799
|
// model; "no_fallback" => the turn ended with a refusal.
|
package/lib/sessions.js
CHANGED
|
@@ -926,7 +926,7 @@ function createSessionManager(opts) {
|
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
928
|
// Notify server for cross-project unread tracking
|
|
929
|
-
if (obj.type === "done") onSessionDone();
|
|
929
|
+
if (obj.type === "done") onSessionDone(session);
|
|
930
930
|
}
|
|
931
931
|
|
|
932
932
|
function resumeSession(cliSessionId, opts, targetWs) {
|