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.
Files changed (177) hide show
  1. package/lib/background-task-timing.js +105 -0
  2. package/lib/capsule-display-floor.js +51 -0
  3. package/lib/capsule-frame-server.js +255 -0
  4. package/lib/capsule-mate-turn.js +105 -0
  5. package/lib/capsule-pig-logic.js +300 -0
  6. package/lib/capsule-server-runtimes.js +39 -0
  7. package/lib/capsules/pig/display.js +198 -0
  8. package/lib/capsules/pig/manifest.json +9 -0
  9. package/lib/capsules/pig/ui.json +84 -0
  10. package/lib/config.js +25 -1
  11. package/lib/daemon-projects.js +13 -8
  12. package/lib/daemon.js +127 -14
  13. package/lib/git-cli.js +95 -1
  14. package/lib/knowledge-import.js +436 -0
  15. package/lib/knowledge-record-store.js +188 -0
  16. package/lib/knowledge-search.js +240 -0
  17. package/lib/mate-knowledge-mcp-server.js +151 -0
  18. package/lib/mate-knowledge-migration.js +283 -0
  19. package/lib/mate-knowledge-service.js +488 -0
  20. package/lib/mate-knowledge-sync.js +482 -0
  21. package/lib/migrate-single-user.js +2 -0
  22. package/lib/notes-lifecycle.js +149 -0
  23. package/lib/notes.js +60 -3
  24. package/lib/os-users.js +3 -1
  25. package/lib/project-capsule-catalog.js +9 -1
  26. package/lib/project-capsule-turn.js +96 -0
  27. package/lib/project-connection.js +14 -1
  28. package/lib/project-http.js +15 -1
  29. package/lib/project-knowledge.js +22 -0
  30. package/lib/project-log-context.js +172 -0
  31. package/lib/project-log-feedback-delivery.js +118 -0
  32. package/lib/project-logs-comments.js +137 -0
  33. package/lib/project-logs-context-state.js +42 -0
  34. package/lib/project-logs-mcp-server.js +328 -0
  35. package/lib/project-logs-query.js +185 -0
  36. package/lib/project-logs-root.js +75 -0
  37. package/lib/project-logs-schema.js +259 -0
  38. package/lib/project-logs-service.js +463 -0
  39. package/lib/project-logs-snapshot.js +205 -0
  40. package/lib/project-logs-store.js +495 -0
  41. package/lib/project-logs-versioning.js +132 -0
  42. package/lib/project-logs.js +400 -0
  43. package/lib/project-mate-interaction.js +14 -0
  44. package/lib/project-mate-knowledge.js +110 -0
  45. package/lib/project-memory.js +22 -0
  46. package/lib/project-message-delivery.js +70 -0
  47. package/lib/project-pair-lifecycle.js +460 -0
  48. package/lib/project-session-handoff.js +1 -1
  49. package/lib/project-session-notes.js +64 -13
  50. package/lib/project-session-pair.js +159 -146
  51. package/lib/project-session-spawn.js +1 -1
  52. package/lib/project-sessions.js +58 -13
  53. package/lib/project-user-message.js +53 -4
  54. package/lib/project-vendor-login.js +20 -3
  55. package/lib/project-worker-permission.js +430 -0
  56. package/lib/project-worker-proposal.js +208 -62
  57. package/lib/project.js +178 -6
  58. package/lib/public/app.js +58 -35
  59. package/lib/public/css/admin.css +2 -2
  60. package/lib/public/css/capsule-ui.css +38 -0
  61. package/lib/public/css/command-palette.css +19 -9
  62. package/lib/public/css/git-placard.css +154 -0
  63. package/lib/public/css/home-hub.css +4 -0
  64. package/lib/public/css/home-session-actions.css +66 -0
  65. package/lib/public/css/home-sidebar.css +66 -0
  66. package/lib/public/css/input.css +163 -28
  67. package/lib/public/css/mobile-nav.css +80 -0
  68. package/lib/public/css/notifications-center.css +67 -0
  69. package/lib/public/css/pane.css +52 -0
  70. package/lib/public/css/project-logs.css +198 -0
  71. package/lib/public/css/scheduler.css +1 -1
  72. package/lib/public/css/session-actions.css +16 -101
  73. package/lib/public/css/sidebar.css +103 -0
  74. package/lib/public/css/sticky-notes.css +237 -229
  75. package/lib/public/css/worker-proposal.css +34 -1
  76. package/lib/public/index.html +45 -11
  77. package/lib/public/modules/admin.js +8 -8
  78. package/lib/public/modules/app-connection.js +109 -4
  79. package/lib/public/modules/app-home-hub.js +19 -7
  80. package/lib/public/modules/app-message-cards.js +2 -1
  81. package/lib/public/modules/app-messages.js +84 -7
  82. package/lib/public/modules/app-misc.js +2 -14
  83. package/lib/public/modules/app-notifications.js +11 -7
  84. package/lib/public/modules/app-panels.js +46 -4
  85. package/lib/public/modules/app-projects.js +13 -8
  86. package/lib/public/modules/app-rendering.js +1 -0
  87. package/lib/public/modules/background-tasks-ui.js +139 -16
  88. package/lib/public/modules/capsule-preference.js +44 -0
  89. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  90. package/lib/public/modules/clay-log-links.js +70 -0
  91. package/lib/public/modules/command-palette.js +52 -15
  92. package/lib/public/modules/context-sources.js +0 -3
  93. package/lib/public/modules/git-agent-sessions.js +85 -0
  94. package/lib/public/modules/git-panel.js +111 -84
  95. package/lib/public/modules/git-placard.js +161 -0
  96. package/lib/public/modules/home-chat-empty-state.js +39 -0
  97. package/lib/public/modules/home-chat-identity.js +9 -0
  98. package/lib/public/modules/home-conversations-sheet.js +102 -48
  99. package/lib/public/modules/home-mate-chat.js +15 -35
  100. package/lib/public/modules/home-session-actions.js +6 -0
  101. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  102. package/lib/public/modules/home-sidebar.js +7 -0
  103. package/lib/public/modules/home-tool-frame.js +182 -0
  104. package/lib/public/modules/home-tools.js +138 -1
  105. package/lib/public/modules/input.js +38 -7
  106. package/lib/public/modules/markdown.js +2 -0
  107. package/lib/public/modules/mate-sidebar.js +0 -8
  108. package/lib/public/modules/message-delivery.js +97 -0
  109. package/lib/public/modules/paste-modal.js +84 -0
  110. package/lib/public/modules/project-logs-render.js +405 -0
  111. package/lib/public/modules/project-logs.js +461 -0
  112. package/lib/public/modules/project-removal-target.js +26 -0
  113. package/lib/public/modules/scheduler.js +17 -3
  114. package/lib/public/modules/session-actions.js +29 -105
  115. package/lib/public/modules/session-hierarchy.js +54 -0
  116. package/lib/public/modules/sidebar-mobile.js +40 -15
  117. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  118. package/lib/public/modules/sidebar-sessions.js +77 -11
  119. package/lib/public/modules/sidebar.js +17 -5
  120. package/lib/public/modules/split-group-helpers.js +8 -0
  121. package/lib/public/modules/split-view.js +5 -2
  122. package/lib/public/modules/sticky-note-markdown.js +5 -0
  123. package/lib/public/modules/sticky-notes-browser.js +326 -0
  124. package/lib/public/modules/sticky-notes-card.js +360 -0
  125. package/lib/public/modules/sticky-notes-editor.js +291 -0
  126. package/lib/public/modules/sticky-notes-shared.js +90 -0
  127. package/lib/public/modules/sticky-notes.js +170 -946
  128. package/lib/public/modules/tool-palette-order.js +143 -0
  129. package/lib/public/modules/tool-palette-overlays.js +159 -0
  130. package/lib/public/modules/tool-palette.js +21 -188
  131. package/lib/public/modules/tools.js +21 -1
  132. package/lib/public/modules/update-snooze.js +169 -0
  133. package/lib/public/modules/user-settings.js +4 -0
  134. package/lib/public/modules/worker-pane-lock.js +176 -0
  135. package/lib/public/modules/worker-proposal-state.js +16 -0
  136. package/lib/public/modules/worker-proposal.js +49 -12
  137. package/lib/public/style.css +3 -1
  138. package/lib/sdk-bridge.js +132 -46
  139. package/lib/sdk-message-processor.js +60 -14
  140. package/lib/server-experimental-settings.js +49 -0
  141. package/lib/server-global-ws.js +11 -0
  142. package/lib/server-home-chat-events.js +31 -1
  143. package/lib/server-home-chat.js +27 -4
  144. package/lib/server-home-clay-entry.js +1 -1
  145. package/lib/server-home-clay-session-links.js +34 -5
  146. package/lib/server-settings.js +5 -0
  147. package/lib/server-tools.js +127 -9
  148. package/lib/server.js +124 -18
  149. package/lib/session-driver-eligibility.js +38 -0
  150. package/lib/session-driver-orchestration.js +31 -0
  151. package/lib/session-hygiene.js +3 -0
  152. package/lib/session-notes-mcp-server.js +25 -5
  153. package/lib/session-pair-factory.js +247 -0
  154. package/lib/session-pair-mcp-server.js +46 -8
  155. package/lib/session-pair-prompts.js +79 -0
  156. package/lib/session-pair-turn-control.js +153 -0
  157. package/lib/session-provenance.js +119 -0
  158. package/lib/session-spawn-mcp-server.js +1 -1
  159. package/lib/session-split-groups.js +4 -1
  160. package/lib/session-title-policy.js +60 -0
  161. package/lib/session-visibility.js +39 -0
  162. package/lib/sessions.js +61 -11
  163. package/lib/tools-registry.js +89 -10
  164. package/lib/update-snooze.js +437 -0
  165. package/lib/users-experimental-preferences.js +30 -0
  166. package/lib/users-permissions.js +2 -2
  167. package/lib/users.js +22 -16
  168. package/lib/workspace-query-access.js +102 -0
  169. package/lib/workspace-query-service.js +24 -17
  170. package/lib/worktree.js +25 -38
  171. package/lib/ws-schema.js +38 -7
  172. package/lib/yoke/adapters/claude-worker.js +16 -6
  173. package/lib/yoke/adapters/claude.js +81 -4
  174. package/lib/yoke/adapters/codex.js +63 -15
  175. package/lib/yoke/codex-background-tasks.js +8 -2
  176. package/lib/yoke/vendor-registry.js +1 -1
  177. 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 menu = null;
10
- var button = null;
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 showMainMenu() {
219
- closeMenu();
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
- button.classList.toggle("hidden", hidden);
250
- if (hidden) closeMenu();
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
- button = document.getElementById("header-session-actions-btn");
255
- if (!button) return;
256
- button.addEventListener("click", function () {
257
- if (menu) closeMenu();
258
- else showMainMenu();
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) updateVisibility();
186
+ state.activeSessionMode !== prev.activeSessionMode ||
187
+ state.sessionHasHistory !== prev.sessionHasHistory ||
188
+ state.sessionIsProcessing !== prev.sessionIsProcessing) updateControls();
264
189
  });
265
- window.addEventListener("resize", closeMenu);
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
- items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
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(createMobileSessionItem(bookmarkedItems[bi].data));
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: "calendar-clock", label: "Scheduled Tasks", action: "scheduler" }
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 === "scheduler") {
1029
- targetId = "scheduler-btn";
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
+ }