clay-server 4.0.0-beta.2 → 4.0.0-beta.21
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/background-task-timing.js +105 -0
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +14 -1
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-vendor-login.js +20 -3
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +178 -6
- package/lib/public/app.js +58 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +163 -28
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/index.html +45 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +109 -4
- package/lib/public/modules/app-home-hub.js +19 -7
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +84 -7
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +46 -4
- package/lib/public/modules/app-projects.js +13 -8
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +21 -1
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +132 -46
- package/lib/sdk-message-processor.js +60 -14
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +124 -18
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +61 -11
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +2 -2
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +81 -4
- package/lib/yoke/adapters/codex.js +63 -15
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +3 -3
|
@@ -1,78 +1,16 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
-
import { refreshIcons, iconHtml } from './icons.js';
|
|
4
3
|
import { addSystemMessage, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
5
4
|
import { openPairDialog } from './split-pair-ui.js';
|
|
6
5
|
import { buildAgentVendorSelect, fillAgentModels, buildAgentEffortSelect, fillAgentEffort } from './agent-config-selects.js';
|
|
7
6
|
import { showToast } from './utils.js';
|
|
8
7
|
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var outsideHandler = null;
|
|
12
|
-
var escapeHandler = null;
|
|
8
|
+
var addWorkerButton = null;
|
|
9
|
+
var handoffButton = null;
|
|
13
10
|
var handoffDialog = null;
|
|
14
11
|
var handoffEscapeHandler = null;
|
|
15
12
|
var handoffSubmitButton = null;
|
|
16
13
|
|
|
17
|
-
function closeMenu() {
|
|
18
|
-
if (menu) menu.remove();
|
|
19
|
-
menu = null;
|
|
20
|
-
if (button) button.setAttribute("aria-expanded", "false");
|
|
21
|
-
if (outsideHandler) document.removeEventListener("pointerdown", outsideHandler, true);
|
|
22
|
-
if (escapeHandler) document.removeEventListener("keydown", escapeHandler);
|
|
23
|
-
outsideHandler = null;
|
|
24
|
-
escapeHandler = null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function positionMenu() {
|
|
28
|
-
if (!menu || !button) return;
|
|
29
|
-
var rect = button.getBoundingClientRect();
|
|
30
|
-
var width = menu.offsetWidth;
|
|
31
|
-
menu.style.top = Math.round(rect.bottom + 6) + "px";
|
|
32
|
-
menu.style.left = Math.max(8, Math.min(window.innerWidth - width - 8, Math.round(rect.right - width))) + "px";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function actionRow(icon, label, description) {
|
|
36
|
-
var row = document.createElement("button");
|
|
37
|
-
row.type = "button";
|
|
38
|
-
row.className = "session-actions-row";
|
|
39
|
-
row.setAttribute("role", "menuitem");
|
|
40
|
-
row.innerHTML = iconHtml(icon, "session-actions-row-icon") +
|
|
41
|
-
'<span class="session-actions-row-copy"><strong></strong><span></span></span>' +
|
|
42
|
-
iconHtml("chevron-right", "session-actions-row-chevron");
|
|
43
|
-
row.querySelector("strong").textContent = label;
|
|
44
|
-
row.querySelector(".session-actions-row-copy > span").textContent = description;
|
|
45
|
-
return row;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function menuShell(label) {
|
|
49
|
-
var el = document.createElement("div");
|
|
50
|
-
el.className = "session-actions-menu";
|
|
51
|
-
el.setAttribute("role", "menu");
|
|
52
|
-
el.setAttribute("aria-label", label);
|
|
53
|
-
document.body.appendChild(el);
|
|
54
|
-
menu = el;
|
|
55
|
-
button.setAttribute("aria-expanded", "true");
|
|
56
|
-
return el;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function installCloseHandlers() {
|
|
60
|
-
outsideHandler = function (event) {
|
|
61
|
-
if (menu && !menu.contains(event.target) && event.target !== button && !button.contains(event.target)) closeMenu();
|
|
62
|
-
};
|
|
63
|
-
escapeHandler = function (event) {
|
|
64
|
-
if (event.key === "Escape") {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
closeMenu();
|
|
67
|
-
if (button) button.focus();
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
setTimeout(function () {
|
|
71
|
-
document.addEventListener("pointerdown", outsideHandler, true);
|
|
72
|
-
document.addEventListener("keydown", escapeHandler);
|
|
73
|
-
}, 0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
14
|
function closeHandoffDialog() {
|
|
77
15
|
if (handoffDialog) handoffDialog.remove();
|
|
78
16
|
if (handoffEscapeHandler) document.removeEventListener("keydown", handoffEscapeHandler);
|
|
@@ -95,10 +33,19 @@ function handoffField(labelText, control) {
|
|
|
95
33
|
function requestHandoffDialog() {
|
|
96
34
|
var ws = getWs();
|
|
97
35
|
if (!ws || ws.readyState !== 1) return;
|
|
98
|
-
closeMenu();
|
|
99
36
|
ws.send(JSON.stringify({ type: "handoff_session_options" }));
|
|
100
37
|
}
|
|
101
38
|
|
|
39
|
+
function openWorkerDialog() {
|
|
40
|
+
var sessionId = store.get('activeSessionId');
|
|
41
|
+
if (!sessionId) return;
|
|
42
|
+
openPairDialog({
|
|
43
|
+
sessionId: sessionId,
|
|
44
|
+
title: document.getElementById("header-title").textContent,
|
|
45
|
+
vendor: store.get('currentVendor') || "claude",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
102
49
|
function showHandoffDialog(options) {
|
|
103
50
|
closeHandoffDialog();
|
|
104
51
|
var installed = options.installedVendors || [];
|
|
@@ -215,55 +162,32 @@ function showHandoffDialog(options) {
|
|
|
215
162
|
vendorSelect.focus();
|
|
216
163
|
}
|
|
217
164
|
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
var el = menuShell("Session actions");
|
|
221
|
-
var addWorker = actionRow("bot", "Add Split Worker", "Open a visible Split Worker beside this session.");
|
|
222
|
-
addWorker.classList.add("is-primary");
|
|
223
|
-
addWorker.addEventListener("click", function () {
|
|
224
|
-
var sessionId = store.get('activeSessionId');
|
|
225
|
-
if (!sessionId) return;
|
|
226
|
-
closeMenu();
|
|
227
|
-
openPairDialog({
|
|
228
|
-
sessionId: sessionId,
|
|
229
|
-
title: document.getElementById("header-title").textContent,
|
|
230
|
-
vendor: store.get('currentVendor') || "claude",
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
var handoff = actionRow("send", "Send context to another agent", "Start a new session with this conversation's context.");
|
|
234
|
-
var unavailable = !store.get('sessionHasHistory') || store.get('sessionIsProcessing');
|
|
235
|
-
handoff.disabled = unavailable;
|
|
236
|
-
handoff.title = unavailable ? "Available after the current turn is complete" : "";
|
|
237
|
-
handoff.addEventListener("click", requestHandoffDialog);
|
|
238
|
-
el.appendChild(addWorker);
|
|
239
|
-
el.appendChild(handoff);
|
|
240
|
-
refreshIcons();
|
|
241
|
-
positionMenu();
|
|
242
|
-
installCloseHandlers();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function updateVisibility() {
|
|
246
|
-
if (!button) return;
|
|
165
|
+
function updateControls() {
|
|
166
|
+
if (!addWorkerButton || !handoffButton) return;
|
|
247
167
|
var state = store.snap();
|
|
248
168
|
var hidden = !state.activeSessionId || state.dmMode || state.paneMode || !!state.splitPanes || state.activeSessionMode !== "gui";
|
|
249
|
-
|
|
250
|
-
|
|
169
|
+
addWorkerButton.classList.toggle("hidden", hidden);
|
|
170
|
+
handoffButton.classList.toggle("hidden", hidden);
|
|
171
|
+
handoffButton.disabled = hidden || !state.sessionHasHistory || state.sessionIsProcessing;
|
|
172
|
+
handoffButton.title = handoffButton.disabled && !hidden
|
|
173
|
+
? "Available after the current turn is complete"
|
|
174
|
+
: "Continue in another agent";
|
|
251
175
|
}
|
|
252
176
|
|
|
253
177
|
export function initSessionActions() {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
});
|
|
178
|
+
addWorkerButton = document.getElementById("composer-add-worker-btn");
|
|
179
|
+
handoffButton = document.getElementById("composer-handoff-btn");
|
|
180
|
+
if (!addWorkerButton || !handoffButton) return;
|
|
181
|
+
addWorkerButton.addEventListener("click", openWorkerDialog);
|
|
182
|
+
handoffButton.addEventListener("click", requestHandoffDialog);
|
|
260
183
|
store.subscribe(function (state, prev) {
|
|
261
184
|
if (state.activeSessionId !== prev.activeSessionId || state.dmMode !== prev.dmMode ||
|
|
262
185
|
state.paneMode !== prev.paneMode || state.splitPanes !== prev.splitPanes ||
|
|
263
|
-
state.activeSessionMode !== prev.activeSessionMode
|
|
186
|
+
state.activeSessionMode !== prev.activeSessionMode ||
|
|
187
|
+
state.sessionHasHistory !== prev.sessionHasHistory ||
|
|
188
|
+
state.sessionIsProcessing !== prev.sessionIsProcessing) updateControls();
|
|
264
189
|
});
|
|
265
|
-
|
|
266
|
-
updateVisibility();
|
|
190
|
+
updateControls();
|
|
267
191
|
}
|
|
268
192
|
|
|
269
193
|
export function handleSessionActionMessage(msg) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Pure projection shared by desktop, mobile and Home session lists.
|
|
2
|
+
|
|
3
|
+
export function buildSessionHierarchy(sessions) {
|
|
4
|
+
var source = Array.isArray(sessions) ? sessions : [];
|
|
5
|
+
var byId = new Map();
|
|
6
|
+
var workersByParent = new Map();
|
|
7
|
+
var roots = [];
|
|
8
|
+
var orphans = [];
|
|
9
|
+
for (var i = 0; i < source.length; i++) byId.set(nodeId(source[i]), source[i]);
|
|
10
|
+
for (var j = 0; j < source.length; j++) {
|
|
11
|
+
var session = source[j];
|
|
12
|
+
if (session.sessionRole !== "worker") {
|
|
13
|
+
roots.push({ driver: session, workers: [] });
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
var parent = session.parentAvailable ? byId.get(parentNodeId(session)) : null;
|
|
17
|
+
if (!parent || parent.sessionRole === "worker") {
|
|
18
|
+
orphans.push(session);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!workersByParent.has(nodeId(parent))) workersByParent.set(nodeId(parent), []);
|
|
22
|
+
workersByParent.get(nodeId(parent)).push(session);
|
|
23
|
+
}
|
|
24
|
+
for (var k = 0; k < roots.length; k++) {
|
|
25
|
+
roots[k].workers = workersByParent.get(nodeId(roots[k].driver)) || [];
|
|
26
|
+
roots[k].workers.sort(compareWorkers);
|
|
27
|
+
roots[k].lastActivity = latestActivity(roots[k]);
|
|
28
|
+
}
|
|
29
|
+
orphans.sort(compareWorkers);
|
|
30
|
+
return { roots: roots, orphans: orphans };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nodeId(session) {
|
|
34
|
+
return session && session.hierarchyId != null ? session.hierarchyId : session.id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parentNodeId(session) {
|
|
38
|
+
return session && session.parentHierarchyId != null ? session.parentHierarchyId : session.parentSessionId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function compareWorkers(a, b) {
|
|
42
|
+
var ag = typeof a.workerGeneration === "number" ? a.workerGeneration : 0;
|
|
43
|
+
var bg = typeof b.workerGeneration === "number" ? b.workerGeneration : 0;
|
|
44
|
+
if (ag !== bg) return bg - ag;
|
|
45
|
+
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function latestActivity(root) {
|
|
49
|
+
var latest = root.driver.lastActivity || 0;
|
|
50
|
+
for (var i = 0; i < root.workers.length; i++) {
|
|
51
|
+
if ((root.workers[i].lastActivity || 0) > latest) latest = root.workers[i].lastActivity || 0;
|
|
52
|
+
}
|
|
53
|
+
return latest;
|
|
54
|
+
}
|
|
@@ -37,6 +37,11 @@ import { openHomeChat } from './home-mate-chat.js';
|
|
|
37
37
|
import { openTerminal } from './terminal.js';
|
|
38
38
|
import { requestKnowledgeList } from './mate-knowledge.js';
|
|
39
39
|
import { loadRootDirectory } from './filebrowser.js';
|
|
40
|
+
import {
|
|
41
|
+
prepareSidebarHierarchy,
|
|
42
|
+
renderMobileDriverHierarchy,
|
|
43
|
+
renderMobileOrphanHierarchy
|
|
44
|
+
} from './sidebar-session-hierarchy.js';
|
|
40
45
|
|
|
41
46
|
// --- Mobile state ---
|
|
42
47
|
var mobileChatSheetOpen = false;
|
|
@@ -374,9 +379,11 @@ function renderSheetSessions(listEl) {
|
|
|
374
379
|
}
|
|
375
380
|
|
|
376
381
|
// Helper: create a mobile session item element
|
|
377
|
-
function createMobileSessionItem(s) {
|
|
382
|
+
function createMobileSessionItem(s, options) {
|
|
383
|
+
var itemOptions = options || {};
|
|
378
384
|
var el = document.createElement("button");
|
|
379
385
|
el.className = "mobile-session-item" + (s.active ? " active" : "");
|
|
386
|
+
if (itemOptions.worker) el.classList.add("mobile-worker-item");
|
|
380
387
|
|
|
381
388
|
// Vendor badge (legacy sessions without a vendor are Claude)
|
|
382
389
|
var mVendor = s.vendor || "claude";
|
|
@@ -398,6 +405,13 @@ function createMobileSessionItem(s) {
|
|
|
398
405
|
titleSpan.appendChild(document.createTextNode(s.title || "New Session"));
|
|
399
406
|
el.appendChild(titleSpan);
|
|
400
407
|
|
|
408
|
+
if (itemOptions.worker) {
|
|
409
|
+
var generation = document.createElement("span");
|
|
410
|
+
generation.className = "mobile-worker-generation" + (itemOptions.current ? " current" : "");
|
|
411
|
+
generation.textContent = itemOptions.current ? "Current" : (s.workerGeneration ? "Gen " + s.workerGeneration : "Worker");
|
|
412
|
+
el.appendChild(generation);
|
|
413
|
+
}
|
|
414
|
+
|
|
401
415
|
// Unread badge (right side)
|
|
402
416
|
if (s.unread > 0 && !s.active) {
|
|
403
417
|
var badge = document.createElement("span");
|
|
@@ -720,10 +734,13 @@ function renderMobileSessionsInto(container) {
|
|
|
720
734
|
|
|
721
735
|
// Partition: loop sessions vs normal sessions (same logic as desktop renderSessionList)
|
|
722
736
|
var sessions = getCachedSessions();
|
|
737
|
+
var hierarchy = prepareSidebarHierarchy(sessions);
|
|
738
|
+
var hierarchyByDriver = hierarchy.byDriver;
|
|
723
739
|
var loopGroups = {};
|
|
724
740
|
var normalSessions = [];
|
|
725
741
|
for (var i = 0; i < sessions.length; i++) {
|
|
726
742
|
var s = sessions[i];
|
|
743
|
+
if (s.sessionRole === "worker") continue;
|
|
727
744
|
if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph" && s.loop.source !== "debate") {
|
|
728
745
|
continue;
|
|
729
746
|
} else if (s.loop && s.loop.loopId) {
|
|
@@ -740,7 +757,14 @@ function renderMobileSessionsInto(container) {
|
|
|
740
757
|
// Build virtual items
|
|
741
758
|
var items = [];
|
|
742
759
|
for (var j = 0; j < normalSessions.length; j++) {
|
|
743
|
-
|
|
760
|
+
var driverRoot = hierarchyByDriver.get(normalSessions[j].id);
|
|
761
|
+
if (driverRoot) items.push({ type: "driver-hierarchy", data: driverRoot.driver, root: driverRoot, lastActivity: driverRoot.lastActivity });
|
|
762
|
+
else items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
|
|
763
|
+
}
|
|
764
|
+
if (hierarchy.orphans.length) {
|
|
765
|
+
var orphanActivity = 0;
|
|
766
|
+
for (var oi = 0; oi < hierarchy.orphans.length; oi++) orphanActivity = Math.max(orphanActivity, hierarchy.orphans[oi].lastActivity || 0);
|
|
767
|
+
items.push({ type: "orphan-workers", workers: hierarchy.orphans, lastActivity: orphanActivity });
|
|
744
768
|
}
|
|
745
769
|
var groupKeys = Object.keys(loopGroups);
|
|
746
770
|
for (var k = 0; k < groupKeys.length; k++) {
|
|
@@ -757,8 +781,8 @@ function renderMobileSessionsInto(container) {
|
|
|
757
781
|
|
|
758
782
|
// Sort by lastActivity descending
|
|
759
783
|
items.sort(function (a, b) {
|
|
760
|
-
var aBookmarked = !!(a.type === "loop" ? false : a.data && a.data.bookmarked);
|
|
761
|
-
var bBookmarked = !!(b.type === "loop" ? false : b.data && b.data.bookmarked);
|
|
784
|
+
var aBookmarked = !!(a.type === "loop" || a.type === "orphan-workers" ? false : a.data && a.data.bookmarked);
|
|
785
|
+
var bBookmarked = !!(b.type === "loop" || b.type === "orphan-workers" ? false : b.data && b.data.bookmarked);
|
|
762
786
|
if (aBookmarked !== bBookmarked) return aBookmarked ? -1 : 1;
|
|
763
787
|
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
764
788
|
});
|
|
@@ -767,7 +791,7 @@ function renderMobileSessionsInto(container) {
|
|
|
767
791
|
var regularItems = [];
|
|
768
792
|
for (var n = 0; n < items.length; n++) {
|
|
769
793
|
var item = items[n];
|
|
770
|
-
if (item.type === "session" && item.data && item.data.bookmarked) {
|
|
794
|
+
if ((item.type === "session" || item.type === "driver-hierarchy") && item.data && item.data.bookmarked) {
|
|
771
795
|
bookmarkedItems.push(item);
|
|
772
796
|
} else {
|
|
773
797
|
regularItems.push(item);
|
|
@@ -781,7 +805,8 @@ function renderMobileSessionsInto(container) {
|
|
|
781
805
|
container.appendChild(bookmarkedHeader);
|
|
782
806
|
|
|
783
807
|
for (var bi = 0; bi < bookmarkedItems.length; bi++) {
|
|
784
|
-
container.appendChild(
|
|
808
|
+
if (bookmarkedItems[bi].type === "driver-hierarchy") container.appendChild(renderMobileDriverHierarchy(bookmarkedItems[bi].root, createMobileSessionItem, refreshMobileChatSheet));
|
|
809
|
+
else container.appendChild(createMobileSessionItem(bookmarkedItems[bi].data));
|
|
785
810
|
}
|
|
786
811
|
}
|
|
787
812
|
|
|
@@ -800,6 +825,10 @@ function renderMobileSessionsInto(container) {
|
|
|
800
825
|
}
|
|
801
826
|
if (item.type === "loop") {
|
|
802
827
|
container.appendChild(createMobileLoopGroup(item.loopId, item.children, item.groupKey));
|
|
828
|
+
} else if (item.type === "driver-hierarchy") {
|
|
829
|
+
container.appendChild(renderMobileDriverHierarchy(item.root, createMobileSessionItem, refreshMobileChatSheet));
|
|
830
|
+
} else if (item.type === "orphan-workers") {
|
|
831
|
+
container.appendChild(renderMobileOrphanHierarchy(item.workers, createMobileSessionItem, refreshMobileChatSheet));
|
|
803
832
|
} else {
|
|
804
833
|
container.appendChild(createMobileSessionItem(item.data));
|
|
805
834
|
}
|
|
@@ -887,8 +916,7 @@ function renderSheetMateProfile(listEl) {
|
|
|
887
916
|
var actions = [
|
|
888
917
|
{ icon: "book-open", label: "Knowledge", btnId: "mate-knowledge-btn", countId: "mate-knowledge-count" },
|
|
889
918
|
{ icon: "sticky-note", label: "Sticky Notes", btnId: "sticky-notes-sidebar-btn", countId: "sticky-notes-sidebar-count" },
|
|
890
|
-
{ icon: "puzzle", label: "Skills", btnId: "mate-skills-btn" }
|
|
891
|
-
{ icon: "calendar", label: "Scheduled Tasks", btnId: "mate-scheduler-btn" }
|
|
919
|
+
{ icon: "puzzle", label: "Skills", btnId: "mate-skills-btn" }
|
|
892
920
|
];
|
|
893
921
|
|
|
894
922
|
for (var i = 0; i < actions.length; i++) {
|
|
@@ -1005,12 +1033,11 @@ function renderSheetTools(listEl) {
|
|
|
1005
1033
|
{ icon: "brain", label: "Memory", action: "mate-memory" },
|
|
1006
1034
|
{ icon: "book-open", label: "Knowledge", action: "mate-knowledge" },
|
|
1007
1035
|
{ icon: "sticky-note", label: "Sticky Notes", action: "mate-sticky" },
|
|
1008
|
-
{ icon: "puzzle", label: "Skills", action: "mate-skills" }
|
|
1009
|
-
{ icon: "calendar-clock", label: "Scheduled Tasks", action: "mate-scheduler" }
|
|
1036
|
+
{ icon: "puzzle", label: "Skills", action: "mate-skills" }
|
|
1010
1037
|
] : [
|
|
1011
1038
|
{ icon: "folder-tree", label: "Files", action: "files" },
|
|
1012
1039
|
{ icon: "square-terminal", label: "Terminal", action: "terminal" },
|
|
1013
|
-
{ icon: "
|
|
1040
|
+
{ icon: "notebook-tabs", label: "Logs", action: "project-logs" }
|
|
1014
1041
|
];
|
|
1015
1042
|
|
|
1016
1043
|
for (var i = 0; i < items.length; i++) {
|
|
@@ -1025,8 +1052,8 @@ function renderSheetTools(listEl) {
|
|
|
1025
1052
|
setTimeout(function () { openMobileSheet("files"); }, 250);
|
|
1026
1053
|
} else if (item.action === "terminal") {
|
|
1027
1054
|
openTerminal();
|
|
1028
|
-
} else if (item.action === "
|
|
1029
|
-
targetId = "
|
|
1055
|
+
} else if (item.action === "project-logs") {
|
|
1056
|
+
targetId = "project-logs-btn";
|
|
1030
1057
|
} else if (item.action === "mate-knowledge") {
|
|
1031
1058
|
setTimeout(function () { openMobileSheet("mate-knowledge"); }, 250);
|
|
1032
1059
|
return;
|
|
@@ -1036,8 +1063,6 @@ function renderSheetTools(listEl) {
|
|
|
1036
1063
|
targetId = "mate-skills-btn";
|
|
1037
1064
|
} else if (item.action === "mate-memory") {
|
|
1038
1065
|
targetId = "mate-memory-btn";
|
|
1039
|
-
} else if (item.action === "mate-scheduler") {
|
|
1040
|
-
targetId = "mate-scheduler-btn";
|
|
1041
1066
|
} else if (item.action === "mate-debate") {
|
|
1042
1067
|
targetId = "mate-debate-btn";
|
|
1043
1068
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Shared Driver/Worker hierarchy behavior for the project desktop and mobile lists.
|
|
2
|
+
|
|
3
|
+
import { iconHtml } from './icons.js';
|
|
4
|
+
import { store } from './store.js';
|
|
5
|
+
import { buildSessionHierarchy } from './session-hierarchy.js';
|
|
6
|
+
|
|
7
|
+
var expansionBySurface = {
|
|
8
|
+
desktop: new Map(),
|
|
9
|
+
mobile: new Map(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function prepareSidebarHierarchy(sessions) {
|
|
13
|
+
var hierarchy = buildSessionHierarchy(sessions);
|
|
14
|
+
var byDriver = new Map();
|
|
15
|
+
var sessionIds = new Set();
|
|
16
|
+
for (var i = 0; i < hierarchy.roots.length; i++) {
|
|
17
|
+
var root = hierarchy.roots[i];
|
|
18
|
+
if (!root.workers.length) continue;
|
|
19
|
+
byDriver.set(root.driver.id, root);
|
|
20
|
+
sessionIds.add(root.driver.id);
|
|
21
|
+
for (var j = 0; j < root.workers.length; j++) sessionIds.add(root.workers[j].id);
|
|
22
|
+
}
|
|
23
|
+
for (var k = 0; k < hierarchy.orphans.length; k++) sessionIds.add(hierarchy.orphans[k].id);
|
|
24
|
+
return {
|
|
25
|
+
byDriver: byDriver,
|
|
26
|
+
orphans: hierarchy.orphans,
|
|
27
|
+
sessionIds: sessionIds,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function currentWorkerIds() {
|
|
32
|
+
var result = new Set();
|
|
33
|
+
var groups = store.get('splitGroups') || [];
|
|
34
|
+
for (var i = 0; i < groups.length; i++) {
|
|
35
|
+
if (groups[i].pair && Number.isInteger(groups[i].pair.workerId)) result.add(groups[i].pair.workerId);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function hierarchyItemMatches(item, matchIds) {
|
|
41
|
+
if (matchIds === null) return true;
|
|
42
|
+
if (item.type === "driver-hierarchy") {
|
|
43
|
+
if (matchIds.has(item.root.driver.id)) return true;
|
|
44
|
+
for (var i = 0; i < item.root.workers.length; i++) {
|
|
45
|
+
if (matchIds.has(item.root.workers[i].id)) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (item.type === "orphan-workers") {
|
|
50
|
+
for (var j = 0; j < item.workers.length; j++) {
|
|
51
|
+
if (matchIds.has(item.workers[j].id)) return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function expanded(surface, key, workers, matchIds) {
|
|
59
|
+
var state = expansionBySurface[surface];
|
|
60
|
+
if (state.has(key)) return state.get(key);
|
|
61
|
+
return defaultHierarchyExpanded(workers, matchIds, currentWorkerIds());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function defaultHierarchyExpanded(workers, matchIds, current) {
|
|
65
|
+
var currentIds = current || new Set();
|
|
66
|
+
for (var i = 0; i < workers.length; i++) {
|
|
67
|
+
if (workers[i].active || currentIds.has(workers[i].id) || (matchIds !== null && matchIds.has(workers[i].id))) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function toggle(surface, key, wasExpanded, rerender) {
|
|
73
|
+
expansionBySurface[surface].set(key, !wasExpanded);
|
|
74
|
+
rerender();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function visibleWorker(worker, driver, matchIds) {
|
|
78
|
+
return matchIds === null || matchIds.has(worker.id) || matchIds.has(driver.id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function renderDesktopDriverHierarchy(root, renderSession, rerender, matchIds) {
|
|
82
|
+
var wrapper = document.createElement("div");
|
|
83
|
+
wrapper.className = "session-driver-hierarchy";
|
|
84
|
+
var key = "driver:" + root.driver.id;
|
|
85
|
+
var isExpanded = expanded("desktop", key, root.workers, matchIds);
|
|
86
|
+
var childrenId = "session-workers-" + root.driver.id;
|
|
87
|
+
var header = document.createElement("div");
|
|
88
|
+
header.className = "session-driver-header";
|
|
89
|
+
var row = renderSession(root.driver);
|
|
90
|
+
row.classList.add("session-driver-item");
|
|
91
|
+
var control = document.createElement("button");
|
|
92
|
+
control.type = "button";
|
|
93
|
+
control.className = "session-driver-toggle";
|
|
94
|
+
control.setAttribute("aria-expanded", String(isExpanded));
|
|
95
|
+
control.setAttribute("aria-controls", childrenId);
|
|
96
|
+
control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
97
|
+
control.innerHTML = iconHtml("chevron-right");
|
|
98
|
+
var children = document.createElement("div");
|
|
99
|
+
children.id = childrenId;
|
|
100
|
+
children.className = "session-worker-children";
|
|
101
|
+
children.setAttribute("role", "group");
|
|
102
|
+
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
103
|
+
children.hidden = !isExpanded;
|
|
104
|
+
control.addEventListener("click", function (event) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
var nextExpanded = !isExpanded;
|
|
108
|
+
expansionBySurface.desktop.set(key, nextExpanded);
|
|
109
|
+
control.setAttribute("aria-expanded", String(nextExpanded));
|
|
110
|
+
control.setAttribute("aria-label", (nextExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
111
|
+
children.hidden = !nextExpanded;
|
|
112
|
+
isExpanded = nextExpanded;
|
|
113
|
+
});
|
|
114
|
+
header.appendChild(control);
|
|
115
|
+
header.appendChild(row);
|
|
116
|
+
wrapper.appendChild(header);
|
|
117
|
+
|
|
118
|
+
var current = currentWorkerIds();
|
|
119
|
+
for (var i = 0; i < root.workers.length; i++) {
|
|
120
|
+
if (!visibleWorker(root.workers[i], root.driver, matchIds)) continue;
|
|
121
|
+
children.appendChild(renderSession(root.workers[i], { worker: true, current: current.has(root.workers[i].id) }));
|
|
122
|
+
}
|
|
123
|
+
wrapper.appendChild(children);
|
|
124
|
+
return wrapper;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function renderDesktopOrphanHierarchy(workers, renderSession, rerender, matchIds) {
|
|
128
|
+
var wrapper = document.createElement("div");
|
|
129
|
+
wrapper.className = "session-driver-hierarchy session-orphan-hierarchy";
|
|
130
|
+
var isExpanded = expanded("desktop", "orphan", workers, matchIds);
|
|
131
|
+
var control = document.createElement("button");
|
|
132
|
+
control.type = "button";
|
|
133
|
+
control.className = "session-orphan-toggle";
|
|
134
|
+
control.setAttribute("aria-expanded", String(isExpanded));
|
|
135
|
+
control.setAttribute("aria-controls", "session-orphan-workers");
|
|
136
|
+
control.innerHTML = iconHtml("chevron-right") + '<span class="session-orphan-title">Unavailable Driver</span><span class="session-worker-count">' + workers.length + "</span>";
|
|
137
|
+
control.addEventListener("click", function () { toggle("desktop", "orphan", isExpanded, rerender); });
|
|
138
|
+
wrapper.appendChild(control);
|
|
139
|
+
|
|
140
|
+
var children = document.createElement("div");
|
|
141
|
+
children.id = "session-orphan-workers";
|
|
142
|
+
children.className = "session-worker-children";
|
|
143
|
+
children.setAttribute("role", "group");
|
|
144
|
+
children.setAttribute("aria-label", "Workers whose Driver is unavailable");
|
|
145
|
+
children.hidden = !isExpanded;
|
|
146
|
+
var current = currentWorkerIds();
|
|
147
|
+
for (var i = 0; i < workers.length; i++) {
|
|
148
|
+
if (matchIds !== null && !matchIds.has(workers[i].id)) continue;
|
|
149
|
+
children.appendChild(renderSession(workers[i], { worker: true, current: current.has(workers[i].id) }));
|
|
150
|
+
}
|
|
151
|
+
wrapper.appendChild(children);
|
|
152
|
+
return wrapper;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function renderMobileDriverHierarchy(root, renderSession, rerender) {
|
|
156
|
+
var wrapper = document.createElement("div");
|
|
157
|
+
wrapper.className = "mobile-driver-hierarchy";
|
|
158
|
+
var key = "driver:" + root.driver.id;
|
|
159
|
+
var isExpanded = expanded("mobile", key, root.workers, null);
|
|
160
|
+
var childrenId = "mobile-session-workers-" + root.driver.id;
|
|
161
|
+
var header = document.createElement("div");
|
|
162
|
+
header.className = "mobile-driver-header";
|
|
163
|
+
var control = document.createElement("button");
|
|
164
|
+
control.type = "button";
|
|
165
|
+
control.className = "mobile-driver-toggle";
|
|
166
|
+
control.setAttribute("aria-expanded", String(isExpanded));
|
|
167
|
+
control.setAttribute("aria-controls", childrenId);
|
|
168
|
+
control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
169
|
+
control.innerHTML = iconHtml("chevron-right");
|
|
170
|
+
control.addEventListener("click", function () { toggle("mobile", key, isExpanded, rerender); });
|
|
171
|
+
header.appendChild(control);
|
|
172
|
+
header.appendChild(renderSession(root.driver));
|
|
173
|
+
wrapper.appendChild(header);
|
|
174
|
+
|
|
175
|
+
var children = document.createElement("div");
|
|
176
|
+
children.id = childrenId;
|
|
177
|
+
children.className = "mobile-worker-children";
|
|
178
|
+
children.setAttribute("role", "group");
|
|
179
|
+
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
180
|
+
children.hidden = !isExpanded;
|
|
181
|
+
var current = currentWorkerIds();
|
|
182
|
+
for (var i = 0; i < root.workers.length; i++) {
|
|
183
|
+
children.appendChild(renderSession(root.workers[i], { worker: true, current: current.has(root.workers[i].id) }));
|
|
184
|
+
}
|
|
185
|
+
wrapper.appendChild(children);
|
|
186
|
+
return wrapper;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function renderMobileOrphanHierarchy(workers, renderSession, rerender) {
|
|
190
|
+
var wrapper = document.createElement("div");
|
|
191
|
+
wrapper.className = "mobile-driver-hierarchy mobile-orphan-hierarchy";
|
|
192
|
+
var isExpanded = expanded("mobile", "orphan", workers, null);
|
|
193
|
+
var control = document.createElement("button");
|
|
194
|
+
control.type = "button";
|
|
195
|
+
control.className = "mobile-orphan-toggle";
|
|
196
|
+
control.setAttribute("aria-expanded", String(isExpanded));
|
|
197
|
+
control.setAttribute("aria-controls", "mobile-orphan-workers");
|
|
198
|
+
control.innerHTML = iconHtml("chevron-right") + "<span>Unavailable Driver</span><span>" + workers.length + "</span>";
|
|
199
|
+
control.addEventListener("click", function () { toggle("mobile", "orphan", isExpanded, rerender); });
|
|
200
|
+
wrapper.appendChild(control);
|
|
201
|
+
|
|
202
|
+
var children = document.createElement("div");
|
|
203
|
+
children.id = "mobile-orphan-workers";
|
|
204
|
+
children.className = "mobile-worker-children";
|
|
205
|
+
children.setAttribute("role", "group");
|
|
206
|
+
children.setAttribute("aria-label", "Workers whose Driver is unavailable");
|
|
207
|
+
children.hidden = !isExpanded;
|
|
208
|
+
var current = currentWorkerIds();
|
|
209
|
+
for (var i = 0; i < workers.length; i++) {
|
|
210
|
+
children.appendChild(renderSession(workers[i], { worker: true, current: current.has(workers[i].id) }));
|
|
211
|
+
}
|
|
212
|
+
wrapper.appendChild(children);
|
|
213
|
+
return wrapper;
|
|
214
|
+
}
|