clay-server 2.47.0-beta.5 → 3.0.0-beta.1
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/daemon.js +6 -27
- package/lib/project-connection.js +21 -8
- package/lib/project-http.js +5 -2
- package/lib/project-session-pair.js +312 -0
- package/lib/project-session-spawn.js +8 -4
- package/lib/project-sessions.js +108 -10
- package/lib/project-user-message.js +18 -8
- package/lib/project.js +33 -0
- package/lib/public/app.js +24 -1
- package/lib/public/css/icon-strip.css +0 -190
- package/lib/public/css/menus.css +83 -1
- package/lib/public/css/messages.css +14 -0
- package/lib/public/css/mobile-nav.css +4 -0
- package/lib/public/css/pane.css +420 -0
- package/lib/public/css/sidebar.css +40 -0
- package/lib/public/css/sticky-notes.css +2 -0
- package/lib/public/css/title-bar.css +169 -0
- package/lib/public/index.html +10 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-favicon.js +6 -10
- package/lib/public/modules/app-header.js +101 -6
- package/lib/public/modules/app-home-hub.js +2 -0
- package/lib/public/modules/app-messages.js +63 -13
- package/lib/public/modules/app-panels.js +49 -27
- package/lib/public/modules/app-projects.js +43 -30
- package/lib/public/modules/app-rendering.js +11 -4
- package/lib/public/modules/branch-switcher.js +178 -0
- package/lib/public/modules/dom-refs.js +0 -1
- package/lib/public/modules/pane-bridge.js +41 -0
- package/lib/public/modules/pane-session.js +18 -0
- package/lib/public/modules/project-switcher.js +2 -1
- package/lib/public/modules/sidebar-mobile.js +19 -6
- package/lib/public/modules/sidebar-projects.js +63 -236
- package/lib/public/modules/sidebar-sessions.js +175 -1
- package/lib/public/modules/sidebar.js +2 -0
- package/lib/public/modules/split-group-helpers.js +18 -0
- package/lib/public/modules/split-pair-ui.js +324 -0
- package/lib/public/modules/split-view.js +499 -0
- package/lib/public/modules/sticky-notes.js +32 -91
- package/lib/public/modules/worktree-family.js +60 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +111 -20
- package/lib/sdk-message-processor.js +2 -2
- package/lib/server.js +24 -4
- package/lib/session-hygiene.js +100 -0
- package/lib/session-pair-mcp-server.js +28 -0
- package/lib/session-split-groups.js +298 -0
- package/lib/sessions.js +163 -34
- package/lib/ws-request.js +14 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/adapters/claude.js +32 -0
- package/lib/yoke/adapters/codex.js +100 -77
- package/lib/yoke/adapters/kiro.js +34 -35
- package/lib/yoke/codex-app-server.js +56 -6
- package/lib/yoke/index.js +1 -0
- package/lib/yoke/skill-discovery.js +196 -0
- package/lib/yoke/vendor-registry.js +34 -0
- package/package.json +1 -1
|
@@ -272,6 +272,175 @@
|
|
|
272
272
|
transform: rotate(180deg);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
.branch-chip {
|
|
276
|
+
display: inline-flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 4px;
|
|
279
|
+
min-width: 0;
|
|
280
|
+
max-width: 132px;
|
|
281
|
+
/* Optical alignment: the pill reads slightly high next to the 17px/800
|
|
282
|
+
project name, so nudge it down within the centered flex row. */
|
|
283
|
+
margin-top: 2px;
|
|
284
|
+
padding: 4px 7px;
|
|
285
|
+
border: 1px solid var(--border);
|
|
286
|
+
border-radius: 999px;
|
|
287
|
+
background: var(--bg-alt);
|
|
288
|
+
color: var(--text-secondary);
|
|
289
|
+
font: 600 11px/1.2 "Pretendard", system-ui, sans-serif;
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
flex-shrink: 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.branch-chip.hidden,
|
|
295
|
+
.branch-chip-menu.hidden {
|
|
296
|
+
display: none;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.branch-chip:hover,
|
|
300
|
+
.branch-chip.open {
|
|
301
|
+
border-color: var(--text-dimmer);
|
|
302
|
+
color: var(--text);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.branch-chip > svg {
|
|
306
|
+
width: 12px;
|
|
307
|
+
height: 12px;
|
|
308
|
+
flex-shrink: 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#branch-chip-label {
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
text-overflow: ellipsis;
|
|
314
|
+
white-space: nowrap;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.branch-chip-chevron {
|
|
318
|
+
transition: transform 0.15s;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.branch-chip.open .branch-chip-chevron {
|
|
322
|
+
transform: rotate(180deg);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.branch-chip-menu {
|
|
326
|
+
position: fixed;
|
|
327
|
+
z-index: 10020;
|
|
328
|
+
width: 250px;
|
|
329
|
+
padding: 6px;
|
|
330
|
+
border: 1px solid var(--border);
|
|
331
|
+
border-radius: 10px;
|
|
332
|
+
background: var(--bg);
|
|
333
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.branch-chip-row {
|
|
337
|
+
display: flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
border-radius: 7px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.branch-chip-row:hover {
|
|
343
|
+
background: var(--bg-alt);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.branch-chip-row-main,
|
|
347
|
+
.branch-chip-row-action,
|
|
348
|
+
.branch-chip-new {
|
|
349
|
+
border: 0;
|
|
350
|
+
background: transparent;
|
|
351
|
+
color: var(--text-secondary);
|
|
352
|
+
font-family: inherit;
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.branch-chip-row-main {
|
|
357
|
+
min-width: 0;
|
|
358
|
+
flex: 1;
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
gap: 7px;
|
|
362
|
+
padding: 8px;
|
|
363
|
+
text-align: left;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.branch-chip-row-main:disabled {
|
|
367
|
+
opacity: 0.38;
|
|
368
|
+
cursor: not-allowed;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.branch-chip-row-main svg,
|
|
372
|
+
.branch-chip-row-action svg,
|
|
373
|
+
.branch-chip-new svg {
|
|
374
|
+
width: 14px;
|
|
375
|
+
height: 14px;
|
|
376
|
+
flex-shrink: 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.branch-chip-row-label {
|
|
380
|
+
overflow: hidden;
|
|
381
|
+
text-overflow: ellipsis;
|
|
382
|
+
white-space: nowrap;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.branch-chip-row.current .branch-chip-row-label {
|
|
386
|
+
color: var(--text);
|
|
387
|
+
font-weight: 700;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.branch-chip-row-main > .lucide-check {
|
|
391
|
+
margin-left: auto;
|
|
392
|
+
color: var(--accent);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.branch-chip-processing {
|
|
396
|
+
width: 7px;
|
|
397
|
+
height: 7px;
|
|
398
|
+
border-radius: 50%;
|
|
399
|
+
background: var(--success, #23a55a);
|
|
400
|
+
flex-shrink: 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.branch-chip-pending {
|
|
404
|
+
min-width: 17px;
|
|
405
|
+
padding: 1px 4px;
|
|
406
|
+
border-radius: 8px;
|
|
407
|
+
background: var(--danger, #e74c3c);
|
|
408
|
+
color: #fff;
|
|
409
|
+
font-size: 9px;
|
|
410
|
+
text-align: center;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.branch-chip-row-action {
|
|
414
|
+
display: flex;
|
|
415
|
+
padding: 7px;
|
|
416
|
+
opacity: 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.branch-chip-row:hover .branch-chip-row-action,
|
|
420
|
+
.branch-chip-row-action:focus-visible {
|
|
421
|
+
opacity: 0.7;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.branch-chip-row-action:hover {
|
|
425
|
+
color: var(--danger, #e74c3c);
|
|
426
|
+
opacity: 1;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.branch-chip-new {
|
|
430
|
+
width: 100%;
|
|
431
|
+
display: flex;
|
|
432
|
+
align-items: center;
|
|
433
|
+
gap: 7px;
|
|
434
|
+
margin-top: 5px;
|
|
435
|
+
padding: 9px 8px 7px;
|
|
436
|
+
border-top: 1px solid var(--border-subtle);
|
|
437
|
+
text-align: left;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.branch-chip-new:hover {
|
|
441
|
+
color: var(--text);
|
|
442
|
+
}
|
|
443
|
+
|
|
275
444
|
.project-rename-input {
|
|
276
445
|
width: 100%;
|
|
277
446
|
font-family: "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
package/lib/public/index.html
CHANGED
|
@@ -210,6 +210,12 @@
|
|
|
210
210
|
<span class="title-bar-project-name" id="title-bar-project-name"></span>
|
|
211
211
|
<i data-lucide="chevron-down" class="title-bar-chevron"></i>
|
|
212
212
|
</button>
|
|
213
|
+
<button id="branch-chip" class="branch-chip hidden" type="button" aria-label="Switch branch" aria-expanded="false" aria-haspopup="menu">
|
|
214
|
+
<i data-lucide="git-branch"></i>
|
|
215
|
+
<span id="branch-chip-label"></span>
|
|
216
|
+
<i data-lucide="chevron-down" class="branch-chip-chevron"></i>
|
|
217
|
+
</button>
|
|
218
|
+
<div id="branch-chip-menu" class="branch-chip-menu hidden"></div>
|
|
213
219
|
<span id="sidebar-presence" class="sidebar-presence"></span>
|
|
214
220
|
<button id="sidebar-toggle-btn" class="sidebar-collapse-btn" title="Collapse sidebar"><i data-lucide="panel-left-close"></i></button>
|
|
215
221
|
</div>
|
|
@@ -364,7 +370,11 @@
|
|
|
364
370
|
<button id="hamburger-btn" aria-label="Toggle sidebar" title="Toggle sidebar"><i data-lucide="menu"></i></button>
|
|
365
371
|
<span class="header-title" id="header-title">Connecting...</span>
|
|
366
372
|
<button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
|
|
373
|
+
<button id="header-full-access-btn" type="button" class="session-full-access hidden" role="switch" aria-checked="false" title="Skip Permissions is off — permission prompts are on">
|
|
374
|
+
<span class="session-full-access-label">Skip Permissions</span><span class="session-full-access-track" aria-hidden="true"><span></span></span>
|
|
375
|
+
</button>
|
|
367
376
|
<button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
|
|
377
|
+
<button id="header-add-worker-btn" type="button" title="Add Worker — this session becomes the Driver of a pair"><i data-lucide="user-plus"></i></button>
|
|
368
378
|
<button type="button" id="header-tui-close-btn" class="header-tui-close-btn hidden" title="Close terminal (keeps history, resume anytime)" aria-label="Close terminal"><i data-lucide="power"></i></button>
|
|
369
379
|
<div id="mate-mobile-title" class="mate-mobile-title hidden">
|
|
370
380
|
<img id="mate-mobile-avatar" class="mate-mobile-avatar" alt="">
|
|
@@ -144,6 +144,12 @@ export function connect() {
|
|
|
144
144
|
reconnectDelay = 1000;
|
|
145
145
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
146
146
|
|
|
147
|
+
// A pane pin is one-shot per WebSocket, not per page lifetime. The server
|
|
148
|
+
// intentionally does not restore pane presence after a daemon restart.
|
|
149
|
+
if (store.get('paneMode') && store.get('paneSessionId')) {
|
|
150
|
+
store.set({ panePinPending: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
147
153
|
// Wrap ws.send to blink LED on outgoing traffic
|
|
148
154
|
var currentWs = getWs();
|
|
149
155
|
var _origSend = currentWs.send.bind(currentWs);
|
|
@@ -18,6 +18,7 @@ var sessionIoTimers = {};
|
|
|
18
18
|
var crossProjectBlinkTimer = null;
|
|
19
19
|
|
|
20
20
|
export function initFavicon() {
|
|
21
|
+
if (store.get('paneMode')) return;
|
|
21
22
|
faviconLink = document.querySelector('link[rel="icon"]');
|
|
22
23
|
faviconCanvas = document.createElement("canvas");
|
|
23
24
|
faviconCanvas.width = 32;
|
|
@@ -38,6 +39,7 @@ export function initFavicon() {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function updateFavicon(bgColor) {
|
|
42
|
+
if (store.get('paneMode')) return;
|
|
41
43
|
if (!faviconLink) return;
|
|
42
44
|
if (!bgColor) {
|
|
43
45
|
if (faviconOrigHref) { faviconLink.href = faviconOrigHref; faviconOrigHref = null; }
|
|
@@ -59,6 +61,7 @@ export function updateFavicon(bgColor) {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export function drawFaviconAnimFrame() {
|
|
64
|
+
if (store.get('paneMode')) return;
|
|
62
65
|
if (!faviconImgReady) return;
|
|
63
66
|
var S = 32;
|
|
64
67
|
var bands = BAND_COLORS.length;
|
|
@@ -106,14 +109,6 @@ export function blinkIO() {
|
|
|
106
109
|
var sessionDot = document.querySelector(".session-item.active .session-processing") ||
|
|
107
110
|
document.querySelector(".mate-session-item.active .session-processing");
|
|
108
111
|
if (sessionDot) sessionDot.classList.add("io");
|
|
109
|
-
// If active project is a worktree, also blink the parent project dot
|
|
110
|
-
var activeWt = document.querySelector("#icon-strip-projects .icon-strip-wt-item.active");
|
|
111
|
-
var parentDot = null;
|
|
112
|
-
if (activeWt) {
|
|
113
|
-
var group = activeWt.closest(".icon-strip-group");
|
|
114
|
-
if (group) parentDot = group.querySelector(".folder-header .icon-strip-status");
|
|
115
|
-
if (parentDot) parentDot.classList.add("io");
|
|
116
|
-
}
|
|
117
112
|
// Mobile chat chip dot + mobile session dot
|
|
118
113
|
var mobileChipDot = null;
|
|
119
114
|
var _s = store.snap();
|
|
@@ -132,7 +127,6 @@ export function blinkIO() {
|
|
|
132
127
|
var sd = document.querySelector(".session-item.active .session-processing.io") ||
|
|
133
128
|
document.querySelector(".mate-session-item.active .session-processing.io");
|
|
134
129
|
if (sd) sd.classList.remove("io");
|
|
135
|
-
if (parentDot) parentDot.classList.remove("io");
|
|
136
130
|
if (mobileChipDot) mobileChipDot.classList.remove("io");
|
|
137
131
|
if (mobileSessionDot) mobileSessionDot.classList.remove("io");
|
|
138
132
|
}, 80);
|
|
@@ -152,7 +146,7 @@ export function blinkSessionDot(sessionId) {
|
|
|
152
146
|
export function updateCrossProjectBlink() {
|
|
153
147
|
if (crossProjectBlinkTimer) { clearTimeout(crossProjectBlinkTimer); crossProjectBlinkTimer = null; }
|
|
154
148
|
function doBlink() {
|
|
155
|
-
var dots = document.querySelectorAll("#icon-strip-projects .icon-strip-item:not(.active) .icon-strip-status.processing, #icon-strip-
|
|
149
|
+
var dots = document.querySelectorAll("#icon-strip-projects .icon-strip-item:not(.active) .icon-strip-status.processing, #icon-strip-users .icon-strip-mate:not(.active) .icon-strip-status.processing");
|
|
156
150
|
// Also blink mobile chat chip dots (same icon-strip-status class inside chips)
|
|
157
151
|
var mobileDots = document.querySelectorAll(".mobile-chat-chip .icon-strip-status.processing");
|
|
158
152
|
var allDots = [];
|
|
@@ -169,6 +163,7 @@ export function updateCrossProjectBlink() {
|
|
|
169
163
|
}
|
|
170
164
|
|
|
171
165
|
export function startUrgentBlink() {
|
|
166
|
+
if (store.get('paneMode')) return;
|
|
172
167
|
if (urgentBlinkTimer) return;
|
|
173
168
|
savedTitle = document.title;
|
|
174
169
|
if (!faviconOrigHref && faviconLink) faviconOrigHref = faviconLink.href;
|
|
@@ -184,6 +179,7 @@ export function startUrgentBlink() {
|
|
|
184
179
|
}
|
|
185
180
|
|
|
186
181
|
export function stopUrgentBlink() {
|
|
182
|
+
if (store.get('paneMode')) return;
|
|
187
183
|
if (!urgentBlinkTimer) return;
|
|
188
184
|
clearInterval(urgentBlinkTimer);
|
|
189
185
|
clearInterval(urgentTitleTimer);
|
|
@@ -11,6 +11,9 @@ import { processMessage } from './app-messages.js';
|
|
|
11
11
|
import { saveToolState, resetToolState, restoreToolState } from './tools.js';
|
|
12
12
|
import { getSessionUsage, setSessionUsage, getContextData, setContextData, updateContextPanel, updateUsagePanel } from './app-panels.js';
|
|
13
13
|
import { onHistoryPrepended as onSessionSearchHistoryPrepended } from './session-search.js';
|
|
14
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
15
|
+
import { openPairDialog } from './split-pair-ui.js';
|
|
16
|
+
import { showConfirm } from './app-misc.js';
|
|
14
17
|
|
|
15
18
|
// --- Module-owned state ---
|
|
16
19
|
var sessionInfoPopover = null;
|
|
@@ -20,6 +23,66 @@ export function initHeader() {
|
|
|
20
23
|
var headerRenameBtn = document.getElementById("header-rename-btn");
|
|
21
24
|
var headerTitleEl = document.getElementById("header-title");
|
|
22
25
|
var headerInfoBtn = document.getElementById("header-info-btn");
|
|
26
|
+
var headerAddWorkerBtn = document.getElementById("header-add-worker-btn");
|
|
27
|
+
|
|
28
|
+
// --- Add Worker: the current session becomes the Driver of a new pair ---
|
|
29
|
+
if (headerAddWorkerBtn) {
|
|
30
|
+
headerAddWorkerBtn.addEventListener("click", function () {
|
|
31
|
+
var sessionId = store.get('activeSessionId');
|
|
32
|
+
if (!sessionId || store.get('splitPanes') || store.get('dmMode')) return;
|
|
33
|
+
openPairDialog({
|
|
34
|
+
sessionId: sessionId,
|
|
35
|
+
title: headerTitleEl ? headerTitleEl.textContent : "",
|
|
36
|
+
vendor: store.get('currentVendor') || "claude",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
// A split shows the GROUP in the title bar; adding a worker there makes
|
|
40
|
+
// no sense (the group already has two members), so hide the button.
|
|
41
|
+
store.subscribe(function (state, prev) {
|
|
42
|
+
if (state.splitPanes !== prev.splitPanes) {
|
|
43
|
+
headerAddWorkerBtn.classList.toggle("hidden", !!state.splitPanes);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
var headerFullAccessBtn = document.getElementById("header-full-access-btn");
|
|
48
|
+
|
|
49
|
+
function updateFullAccessButton() {
|
|
50
|
+
if (!headerFullAccessBtn) return;
|
|
51
|
+
var s = store.snap();
|
|
52
|
+
var visible = !s.dmMode && !s.splitPanes && !s.skipPermsEnabled && s.activeSessionMode === "gui" && !!s.activeSessionId;
|
|
53
|
+
var enabled = !!s.sessionFullAccess;
|
|
54
|
+
headerFullAccessBtn.classList.toggle("hidden", !visible);
|
|
55
|
+
headerFullAccessBtn.classList.toggle("active", enabled);
|
|
56
|
+
headerFullAccessBtn.setAttribute("aria-checked", enabled ? "true" : "false");
|
|
57
|
+
headerFullAccessBtn.title = enabled ? "Skip Permissions is on — click to restore permission prompts" : "Skip Permissions is off — permission prompts are on";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sendFullAccess(enabled) {
|
|
61
|
+
var ws = getWs();
|
|
62
|
+
var id = store.get('activeSessionId');
|
|
63
|
+
if (ws && ws.readyState === 1 && id) {
|
|
64
|
+
ws.send(JSON.stringify({ type: "set_session_full_access", id: id, enabled: enabled }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (headerFullAccessBtn) {
|
|
69
|
+
headerFullAccessBtn.addEventListener("click", function () {
|
|
70
|
+
if (store.get('sessionFullAccess')) {
|
|
71
|
+
sendFullAccess(false);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
showConfirm("Skip permission prompts for this session? Tool requests will be approved automatically.", function () {
|
|
75
|
+
sendFullAccess(true);
|
|
76
|
+
}, "Skip Permissions", true);
|
|
77
|
+
});
|
|
78
|
+
store.subscribe(function (state, prev) {
|
|
79
|
+
if (state.sessionFullAccess !== prev.sessionFullAccess || state.activeSessionId !== prev.activeSessionId ||
|
|
80
|
+
state.activeSessionMode !== prev.activeSessionMode ||
|
|
81
|
+
state.dmMode !== prev.dmMode || state.splitPanes !== prev.splitPanes ||
|
|
82
|
+
state.skipPermsEnabled !== prev.skipPermsEnabled) updateFullAccessButton();
|
|
83
|
+
});
|
|
84
|
+
updateFullAccessButton();
|
|
85
|
+
}
|
|
23
86
|
|
|
24
87
|
// --- Header session rename ---
|
|
25
88
|
if (headerRenameBtn) {
|
|
@@ -40,8 +103,18 @@ export function initHeader() {
|
|
|
40
103
|
var newTitle = input.value.trim();
|
|
41
104
|
var ws = getWs();
|
|
42
105
|
if (newTitle && newTitle !== currentText && ws && ws.readyState === 1) {
|
|
43
|
-
|
|
44
|
-
|
|
106
|
+
// With a split group open the header shows the GROUP name, so the
|
|
107
|
+
// pencil must rename the group, not the parent's last active session.
|
|
108
|
+
var split = store.get('splitPanes');
|
|
109
|
+
if (split) {
|
|
110
|
+
if (split.groupId) {
|
|
111
|
+
ws.send(JSON.stringify({ type: "split_group_rename", id: split.groupId, name: newTitle }));
|
|
112
|
+
headerTitleEl.textContent = newTitle;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
ws.send(JSON.stringify({ type: "rename_session", id: store.get('activeSessionId'), title: newTitle }));
|
|
116
|
+
headerTitleEl.textContent = newTitle;
|
|
117
|
+
}
|
|
45
118
|
}
|
|
46
119
|
input.remove();
|
|
47
120
|
headerTitleEl.style.display = "";
|
|
@@ -89,10 +162,32 @@ export function initHeader() {
|
|
|
89
162
|
pop.appendChild(row);
|
|
90
163
|
}
|
|
91
164
|
|
|
92
|
-
var
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
165
|
+
var split = store.get('splitPanes');
|
|
166
|
+
if (split && split.panes) {
|
|
167
|
+
// A split group is open: single-session info makes no sense here.
|
|
168
|
+
// Show one section per member instead.
|
|
169
|
+
var cached = getCachedSessions() || [];
|
|
170
|
+
for (var pi = 0; pi < split.panes.length; pi++) {
|
|
171
|
+
var member = null;
|
|
172
|
+
for (var ci = 0; ci < cached.length; ci++) {
|
|
173
|
+
if (cached[ci].id === split.panes[pi].sessionId) { member = cached[ci]; break; }
|
|
174
|
+
}
|
|
175
|
+
var heading = document.createElement("div");
|
|
176
|
+
heading.className = "info-section-title";
|
|
177
|
+
heading.textContent = (member && member.title) || split.panes[pi].title || ("Session " + split.panes[pi].sessionId);
|
|
178
|
+
pop.appendChild(heading);
|
|
179
|
+
addRow("Local ID", split.panes[pi].sessionId);
|
|
180
|
+
if (member && member.cliSessionId) {
|
|
181
|
+
addRow("Session ID", member.cliSessionId);
|
|
182
|
+
addRow("Resume", "claude --resume " + member.cliSessionId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
var s = store.snap();
|
|
187
|
+
if (s.cliSessionId) addRow("Session ID", s.cliSessionId);
|
|
188
|
+
if (s.activeSessionId) addRow("Local ID", s.activeSessionId);
|
|
189
|
+
if (s.cliSessionId) addRow("Resume", "claude --resume " + s.cliSessionId);
|
|
190
|
+
}
|
|
96
191
|
|
|
97
192
|
document.body.appendChild(pop);
|
|
98
193
|
sessionInfoPopover = pop;
|
|
@@ -619,6 +619,7 @@ export function showHomeHub() {
|
|
|
619
619
|
// anywhere via the persistent FAB (#clay-fab), not embedded here.
|
|
620
620
|
if (store.get('dmMode')) exitDmMode();
|
|
621
621
|
homeHubVisible = true;
|
|
622
|
+
store.set({ homeHubVisible: true });
|
|
622
623
|
homeHub.classList.remove("hidden");
|
|
623
624
|
// Show close button only if there's a project to return to
|
|
624
625
|
if (hubCloseBtn) {
|
|
@@ -652,6 +653,7 @@ export function showHomeHub() {
|
|
|
652
653
|
export function hideHomeHub() {
|
|
653
654
|
if (!homeHubVisible) return;
|
|
654
655
|
homeHubVisible = false;
|
|
656
|
+
store.set({ homeHubVisible: false });
|
|
655
657
|
homeHub.classList.add("hidden");
|
|
656
658
|
stopTipRotation();
|
|
657
659
|
var mobileHome = document.getElementById("mobile-home-btn");
|
|
@@ -19,6 +19,8 @@ import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js
|
|
|
19
19
|
import { renderMemoryList } from './mate-memory.js';
|
|
20
20
|
import { handlePaletteSessionSwitch, setPaletteVersion } from './command-palette.js';
|
|
21
21
|
import { handleFindInSessionResults } from './session-search.js';
|
|
22
|
+
import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
|
|
23
|
+
import { showPairDialog, handlePairCreated, handleSplitDelegation } from './split-pair-ui.js';
|
|
22
24
|
import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled } from './input.js';
|
|
23
25
|
import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
|
|
24
26
|
import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
|
|
@@ -46,8 +48,9 @@ import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, update
|
|
|
46
48
|
import { setStatus } from './app-connection.js';
|
|
47
49
|
import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
|
|
48
50
|
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
51
|
+
import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
|
|
49
52
|
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
|
|
50
|
-
import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
|
|
53
|
+
import { updateProjectList, updateTitleBarProjectIdentity, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
|
|
51
54
|
import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
|
|
52
55
|
import { hideHomeHub, handleHubSchedules } from './app-home-hub.js';
|
|
53
56
|
import { openDm, enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
|
|
@@ -82,8 +85,7 @@ export function processMessage(msg) {
|
|
|
82
85
|
// Override title bar with mate name and re-apply color
|
|
83
86
|
var _mdn = (store.get('dmTargetUser').displayName || "New Mate");
|
|
84
87
|
if (headerTitleEl) headerTitleEl.textContent = _mdn;
|
|
85
|
-
|
|
86
|
-
if (_tbpn) _tbpn.textContent = _mdn;
|
|
88
|
+
updateTitleBarProjectIdentity(_mdn, null);
|
|
87
89
|
var _mc2 = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
|
|
88
90
|
var _tbc2 = document.querySelector(".title-bar-content");
|
|
89
91
|
if (_tbc2) { _tbc2.style.background = _mc2; _tbc2.classList.add("mate-dm-active"); }
|
|
@@ -296,8 +298,7 @@ export function processMessage(msg) {
|
|
|
296
298
|
if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
|
|
297
299
|
var _mateDN = store.get('dmTargetUser').displayName || "New Mate";
|
|
298
300
|
headerTitleEl.textContent = _mateDN;
|
|
299
|
-
|
|
300
|
-
if (tbProjectName) tbProjectName.textContent = _mateDN;
|
|
301
|
+
updateTitleBarProjectIdentity(_mateDN, null);
|
|
301
302
|
// Re-apply mate title bar styling (may be lost during project switch)
|
|
302
303
|
var _mc = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
|
|
303
304
|
var _tbc = document.querySelector(".title-bar-content");
|
|
@@ -305,8 +306,7 @@ export function processMessage(msg) {
|
|
|
305
306
|
document.body.classList.add("mate-dm-active");
|
|
306
307
|
} else {
|
|
307
308
|
headerTitleEl.textContent = store.get('projectName');
|
|
308
|
-
|
|
309
|
-
if (tbProjectName) tbProjectName.textContent = msg.title || store.get('projectName');
|
|
309
|
+
updateTitleBarProjectIdentity(msg.title || store.get('projectName'), null);
|
|
310
310
|
}
|
|
311
311
|
updatePageTitle();
|
|
312
312
|
if (msg.version) {
|
|
@@ -552,9 +552,42 @@ export function processMessage(msg) {
|
|
|
552
552
|
case "session_list":
|
|
553
553
|
renderMateSessionList(msg.sessions || []);
|
|
554
554
|
renderSessionList(msg.sessions || []);
|
|
555
|
+
syncPaneTitles();
|
|
556
|
+
var panePin = resolvePaneSession(store.get('paneMode'), store.get('panePinPending'), store.get('paneSessionId'), msg.sessions);
|
|
557
|
+
if (panePin.consumed) {
|
|
558
|
+
store.set({ panePinPending: false });
|
|
559
|
+
if (panePin.sessionId && getWs() && getWs().readyState === 1) {
|
|
560
|
+
getWs().send(JSON.stringify({ type: "switch_session", id: panePin.sessionId }));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
555
563
|
handlePaletteSessionSwitch();
|
|
556
564
|
break;
|
|
557
565
|
|
|
566
|
+
case "split_groups":
|
|
567
|
+
store.set({ splitGroups: msg.groups || [] });
|
|
568
|
+
renderSessionList(null);
|
|
569
|
+
maybeRestoreSplitGroup();
|
|
570
|
+
syncPaneTitles();
|
|
571
|
+
break;
|
|
572
|
+
|
|
573
|
+
case "split_group_result":
|
|
574
|
+
if (!msg.ok && msg.error) showToast(msg.error, "error");
|
|
575
|
+
break;
|
|
576
|
+
|
|
577
|
+
case "pair_session_options":
|
|
578
|
+
showPairDialog(msg);
|
|
579
|
+
break;
|
|
580
|
+
|
|
581
|
+
case "pair_session_created":
|
|
582
|
+
var createdPairGroup = handlePairCreated(msg);
|
|
583
|
+
if (createdPairGroup) openGroup(createdPairGroup);
|
|
584
|
+
break;
|
|
585
|
+
|
|
586
|
+
case "split_delegation":
|
|
587
|
+
handleSplitDelegation(msg);
|
|
588
|
+
syncPaneTitles();
|
|
589
|
+
break;
|
|
590
|
+
|
|
558
591
|
case "session_presence":
|
|
559
592
|
updateSessionPresence(msg.presence || {});
|
|
560
593
|
break;
|
|
@@ -614,11 +647,14 @@ export function processMessage(msg) {
|
|
|
614
647
|
var _effectiveTerminalId = (typeof msg.runtimeTerminalId === "number")
|
|
615
648
|
? msg.runtimeTerminalId
|
|
616
649
|
: (typeof msg.terminalId === "number" ? msg.terminalId : null);
|
|
617
|
-
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory });
|
|
650
|
+
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || store.get('currentModel'), currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
|
|
618
651
|
// TUI sessions swap the chat UI for an embedded xterm running
|
|
619
652
|
// `claude` inside a real PTY. Mount or tear down before the rest of
|
|
620
653
|
// the chat-side bookkeeping runs so we don't waste work on hidden DOM.
|
|
621
|
-
|
|
654
|
+
// With a split open the native app is hidden behind the pane iframes,
|
|
655
|
+
// but the TUI host is position:fixed on body -- attaching it here
|
|
656
|
+
// would overlay the split. Members render inside their own panes.
|
|
657
|
+
if (_effectiveMode === "tui" && typeof _effectiveTerminalId === "number" && !store.get('splitPanes')) {
|
|
622
658
|
attachTuiView(_effectiveTerminalId, msg.id, msg.vendor || null);
|
|
623
659
|
} else {
|
|
624
660
|
detachTuiView();
|
|
@@ -627,9 +663,10 @@ export function processMessage(msg) {
|
|
|
627
663
|
// (the composer is hidden; clicking Resume spawns claude --resume).
|
|
628
664
|
setTuiSuspendedView(!!msg.tuiSuspended, msg.id);
|
|
629
665
|
if (msg.vendor) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
666
|
+
// session_switched is authoritative. The lock rejects stale
|
|
667
|
+
// model_info responses; it must not block an intentional switch to
|
|
668
|
+
// a blank session owned by another vendor.
|
|
669
|
+
store.set({ currentVendor: resolveSwitchedVendor(store.get('currentVendor'), msg.vendor) });
|
|
633
670
|
// Sessions with history have their vendor structurally bound to
|
|
634
671
|
// the session: lock so a late-arriving default-adapter model_info
|
|
635
672
|
// can't flip the UI back. Previously this branch unlocked, which
|
|
@@ -706,6 +743,19 @@ export function processMessage(msg) {
|
|
|
706
743
|
if (!("ontouchstart" in window)) {
|
|
707
744
|
inputEl.focus();
|
|
708
745
|
}
|
|
746
|
+
// Reload survival: a restored active session that is a split-group
|
|
747
|
+
// member reopens its group (no-op when a split is already open).
|
|
748
|
+
maybeRestoreSplitGroup();
|
|
749
|
+
break;
|
|
750
|
+
|
|
751
|
+
case "session_full_access_changed":
|
|
752
|
+
if (msg.id === store.get('activeSessionId')) {
|
|
753
|
+
store.set({ sessionFullAccess: !!msg.enabled, currentMode: msg.permissionMode || "default" });
|
|
754
|
+
}
|
|
755
|
+
break;
|
|
756
|
+
|
|
757
|
+
case "session_full_access_result":
|
|
758
|
+
if (!msg.ok && msg.error) showToast(msg.error, "error");
|
|
709
759
|
break;
|
|
710
760
|
|
|
711
761
|
case "session_id":
|
|
@@ -736,7 +786,7 @@ export function processMessage(msg) {
|
|
|
736
786
|
renderPlanCard(msg.planContent);
|
|
737
787
|
addUserMessage("Execute the following plan. Do NOT re-enter plan mode — just implement it step by step.", msg.images || null, msg.pastes || null, msg.from, msg.fromName);
|
|
738
788
|
} else {
|
|
739
|
-
addUserMessage(msg.text, msg.images || null, msg.pastes || null, msg.from, msg.fromName);
|
|
789
|
+
addUserMessage(msg.text, msg.images || null, msg.pastes || null, msg.from, msg.fromName, msg.delegated ? msg : null);
|
|
740
790
|
}
|
|
741
791
|
break;
|
|
742
792
|
|