clay-server 4.0.0-beta.13 → 4.0.0-beta.15

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 (52) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +229 -0
  3. package/lib/capsule-pig-logic.js +268 -0
  4. package/lib/capsule-server-runtimes.js +41 -0
  5. package/lib/capsule-tictactoe-logic.js +260 -0
  6. package/lib/capsules/pig/display.js +190 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +46 -0
  9. package/lib/capsules/tictactoe/manifest.json +9 -0
  10. package/lib/capsules/tictactoe/ui.json +203 -0
  11. package/lib/project-capsule-catalog.js +9 -1
  12. package/lib/project-connection.js +1 -1
  13. package/lib/project-log-feedback-delivery.js +76 -0
  14. package/lib/project-logs.js +4 -0
  15. package/lib/project-pair-lifecycle.js +7 -12
  16. package/lib/project-session-pair.js +29 -39
  17. package/lib/project-worker-proposal.js +203 -72
  18. package/lib/project.js +13 -0
  19. package/lib/public/css/capsule-ui.css +18 -0
  20. package/lib/public/css/home-session-actions.css +66 -0
  21. package/lib/public/css/home-sidebar.css +66 -0
  22. package/lib/public/css/mobile-nav.css +80 -0
  23. package/lib/public/css/sidebar.css +92 -0
  24. package/lib/public/css/worker-proposal.css +34 -1
  25. package/lib/public/modules/app-messages.js +14 -1
  26. package/lib/public/modules/home-conversations-sheet.js +102 -48
  27. package/lib/public/modules/home-session-actions.js +6 -0
  28. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  29. package/lib/public/modules/home-tool-frame.js +165 -0
  30. package/lib/public/modules/home-tools.js +129 -1
  31. package/lib/public/modules/session-hierarchy.js +54 -0
  32. package/lib/public/modules/sidebar-mobile.js +35 -6
  33. package/lib/public/modules/sidebar-session-hierarchy.js +206 -0
  34. package/lib/public/modules/sidebar-sessions.js +49 -7
  35. package/lib/public/modules/worker-proposal-state.js +16 -0
  36. package/lib/public/modules/worker-proposal.js +49 -12
  37. package/lib/sdk-bridge.js +16 -8
  38. package/lib/sdk-message-processor.js +34 -4
  39. package/lib/server-home-chat.js +12 -1
  40. package/lib/server-tools.js +97 -9
  41. package/lib/server.js +3 -0
  42. package/lib/session-driver-eligibility.js +20 -165
  43. package/lib/session-pair-factory.js +31 -28
  44. package/lib/session-pair-mcp-server.js +5 -9
  45. package/lib/session-pair-prompts.js +15 -15
  46. package/lib/session-provenance.js +119 -0
  47. package/lib/session-spawn-mcp-server.js +1 -1
  48. package/lib/sessions.js +40 -2
  49. package/lib/tools-registry.js +44 -10
  50. package/lib/ws-schema.js +8 -3
  51. package/lib/yoke/adapters/claude.js +12 -0
  52. package/package.json +1 -1
@@ -612,6 +612,86 @@
612
612
  font-weight: 600;
613
613
  }
614
614
 
615
+ .mobile-driver-header {
616
+ display: flex;
617
+ align-items: center;
618
+ }
619
+
620
+ .mobile-driver-header > .mobile-session-item {
621
+ min-width: 0;
622
+ flex: 1;
623
+ }
624
+
625
+ .mobile-driver-toggle {
626
+ width: 32px;
627
+ height: 44px;
628
+ padding: 0;
629
+ border: 0;
630
+ border-radius: 8px;
631
+ display: inline-flex;
632
+ align-items: center;
633
+ justify-content: center;
634
+ color: var(--text-dimmer);
635
+ background: transparent;
636
+ }
637
+
638
+ .mobile-driver-toggle .lucide,
639
+ .mobile-orphan-toggle .lucide {
640
+ width: 15px;
641
+ height: 15px;
642
+ transition: transform 0.15s ease;
643
+ }
644
+
645
+ .mobile-driver-toggle[aria-expanded="true"] .lucide,
646
+ .mobile-orphan-toggle[aria-expanded="true"] .lucide {
647
+ transform: rotate(90deg);
648
+ }
649
+
650
+ .mobile-worker-children {
651
+ margin-left: 16px;
652
+ padding-left: 12px;
653
+ border-left: 1px solid color-mix(in srgb, var(--text-dimmer) 24%, transparent);
654
+ }
655
+
656
+ .mobile-worker-item {
657
+ min-height: 43px;
658
+ padding-block: 9px;
659
+ }
660
+
661
+ .mobile-worker-generation {
662
+ flex-shrink: 0;
663
+ padding: 2px 6px;
664
+ border-radius: 999px;
665
+ color: var(--text-dimmer);
666
+ background: rgba(var(--overlay-rgb), 0.04);
667
+ font-size: 10px;
668
+ font-weight: 650;
669
+ }
670
+
671
+ .mobile-worker-generation.current {
672
+ color: var(--success);
673
+ background: color-mix(in srgb, var(--success) 10%, transparent);
674
+ }
675
+
676
+ .mobile-orphan-toggle {
677
+ width: 100%;
678
+ min-height: 42px;
679
+ padding: 8px 12px;
680
+ border: 0;
681
+ border-radius: 9px;
682
+ display: flex;
683
+ align-items: center;
684
+ gap: 9px;
685
+ color: var(--text-dimmer);
686
+ background: transparent;
687
+ font: inherit;
688
+ text-align: left;
689
+ }
690
+
691
+ .mobile-orphan-toggle span:first-of-type {
692
+ flex: 1;
693
+ }
694
+
615
695
  .mobile-session-title {
616
696
  flex: 1;
617
697
  display: inline-flex;
@@ -926,6 +926,98 @@
926
926
  transition: opacity 0.15s;
927
927
  }
928
928
 
929
+ .session-driver-hierarchy {
930
+ position: relative;
931
+ }
932
+
933
+ .session-driver-toggle {
934
+ width: 18px;
935
+ height: 24px;
936
+ padding: 0;
937
+ border: 0;
938
+ border-radius: 5px;
939
+ display: inline-flex;
940
+ align-items: center;
941
+ justify-content: center;
942
+ flex-shrink: 0;
943
+ color: var(--text-dimmer);
944
+ background: transparent;
945
+ cursor: pointer;
946
+ }
947
+
948
+ .session-driver-toggle:hover,
949
+ .session-driver-toggle:focus-visible,
950
+ .session-orphan-toggle:hover,
951
+ .session-orphan-toggle:focus-visible {
952
+ color: var(--text);
953
+ background: rgba(var(--overlay-rgb), 0.05);
954
+ outline: none;
955
+ }
956
+
957
+ .session-driver-toggle .lucide,
958
+ .session-orphan-toggle .lucide {
959
+ width: 13px;
960
+ height: 13px;
961
+ transition: transform 0.15s ease;
962
+ }
963
+
964
+ .session-driver-toggle[aria-expanded="true"] .lucide,
965
+ .session-orphan-toggle[aria-expanded="true"] .lucide {
966
+ transform: rotate(90deg);
967
+ }
968
+
969
+ .session-worker-children {
970
+ margin-left: 17px;
971
+ padding-left: 8px;
972
+ border-left: 1px solid color-mix(in srgb, var(--text-dimmer) 22%, transparent);
973
+ }
974
+
975
+ .session-worker-item {
976
+ height: 33px;
977
+ border-radius: 7px;
978
+ }
979
+
980
+ .session-worker-generation,
981
+ .session-worker-count {
982
+ flex-shrink: 0;
983
+ padding: 1px 5px;
984
+ border-radius: 999px;
985
+ color: var(--text-dimmer);
986
+ background: rgba(var(--overlay-rgb), 0.04);
987
+ font-size: 9px;
988
+ font-weight: 650;
989
+ letter-spacing: 0.02em;
990
+ }
991
+
992
+ .session-worker-generation.current {
993
+ color: var(--success);
994
+ background: color-mix(in srgb, var(--success) 10%, transparent);
995
+ }
996
+
997
+ .session-orphan-toggle {
998
+ width: 100%;
999
+ height: 32px;
1000
+ padding: 0 9px 0 4px;
1001
+ border: 0;
1002
+ border-radius: 8px;
1003
+ display: flex;
1004
+ align-items: center;
1005
+ gap: 7px;
1006
+ color: var(--text-dimmer);
1007
+ background: transparent;
1008
+ cursor: pointer;
1009
+ font: inherit;
1010
+ text-align: left;
1011
+ }
1012
+
1013
+ .session-orphan-title {
1014
+ flex: 1;
1015
+ overflow: hidden;
1016
+ text-overflow: ellipsis;
1017
+ white-space: nowrap;
1018
+ font-size: 12px;
1019
+ }
1020
+
929
1021
  .split-group-text {
930
1022
  display: flex;
931
1023
  align-items: center;
@@ -1,4 +1,4 @@
1
- /* Inline approval surface for Fable's Driver/Worker recommendation. */
1
+ /* Inline approval surface for user-controlled Split Worker configuration. */
2
2
 
3
3
  .worker-proposal-card {
4
4
  width: min(var(--content-width), calc(100% - 40px));
@@ -67,6 +67,30 @@
67
67
 
68
68
  .worker-proposal-plan li::marker { color: var(--accent2); font-weight: 700; }
69
69
 
70
+ .worker-proposal-rationale {
71
+ display: grid;
72
+ gap: 4px;
73
+ margin: 0 0 14px;
74
+ padding: 10px 12px;
75
+ border-left: 2px solid color-mix(in srgb, var(--accent2) 62%, var(--border));
76
+ background: color-mix(in srgb, var(--accent2) 5%, transparent);
77
+ }
78
+
79
+ .worker-proposal-rationale > span {
80
+ color: var(--accent2);
81
+ font-size: 9px;
82
+ font-weight: 750;
83
+ letter-spacing: 0.06em;
84
+ text-transform: uppercase;
85
+ }
86
+
87
+ .worker-proposal-rationale p {
88
+ margin: 0;
89
+ color: var(--text-secondary);
90
+ font-size: 11px;
91
+ line-height: 1.45;
92
+ }
93
+
70
94
  .worker-proposal-config {
71
95
  display: grid;
72
96
  grid-template-columns: minmax(120px, 0.8fr) minmax(160px, 1.2fr) minmax(190px, auto);
@@ -100,6 +124,15 @@
100
124
  .worker-proposal-effort-btn:hover { color: var(--text); }
101
125
  .worker-proposal-effort-btn.active { background: color-mix(in srgb, var(--accent2) 18%, var(--bg)); color: var(--accent2); }
102
126
 
127
+ .worker-proposal-decision {
128
+ margin-top: 9px;
129
+ color: var(--text-dimmer);
130
+ font-size: 9px;
131
+ line-height: 1.35;
132
+ }
133
+
134
+ .worker-proposal-decision.automatic { color: var(--accent2); }
135
+
103
136
  .worker-proposal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
104
137
  .worker-proposal-action { min-height: 34px; padding: 0 13px; border-radius: 9px; font: inherit; font-size: 11px; font-weight: 700; cursor: pointer; }
105
138
  .worker-proposal-action.secondary { border: 1px solid var(--border); background: transparent; color: var(--text-muted); }
@@ -65,8 +65,9 @@ import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateCont
65
65
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
66
66
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
67
67
  import { hideHomeHub, showHomeHub } from './app-home-hub.js';
68
- import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolStorageResult, handleToolLlmResult, handleToolsError, handleToolControlRequest } from './home-tools.js';
68
+ import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolStorageResult, handleToolLlmResult, handleToolsError, handleToolControlRequest, handleToolServerState, handleToolServerEvent } from './home-tools.js';
69
69
  import { handleHomeDockState } from './home-dock.js';
70
+ import { handleToolFrameUrlState } from './home-tool-frame.js';
70
71
  import { enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
71
72
  import { handleRateLimitEvent, updateRateLimitUsage, addScheduledMessageBubble, removeScheduledMessageBubble, handleFastModeState } from './app-rate-limit.js';
72
73
  import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection, clearRemoteCursors } from './app-cursors.js';
@@ -627,6 +628,18 @@ export function processMessage(msg) {
627
628
  handleToolControlRequest(msg);
628
629
  break;
629
630
 
631
+ case "tool_server_state":
632
+ handleToolServerState(msg);
633
+ break;
634
+
635
+ case "tool_server_event":
636
+ handleToolServerEvent(msg);
637
+ break;
638
+
639
+ case "tool_frame_url_state":
640
+ handleToolFrameUrlState(msg);
641
+ break;
642
+
630
643
  case "input_sync":
631
644
  if (!store.get('dmMode')) handleInputSync(msg.text);
632
645
  break;
@@ -3,12 +3,14 @@
3
3
  import { store } from './store.js';
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
5
  import { getHomeSessionConversations, createHomeSessionActionsTrigger, disposeHomeSessionActionsMenu } from './home-session-actions.js';
6
+ import { buildSessionHierarchy } from './session-hierarchy.js';
6
7
 
7
8
  var overlay = null;
8
9
  var searchInput = null;
9
10
  var listEl = null;
10
11
  var sheetOpener = null;
11
12
  var selectConversation = null;
13
+ var hierarchyExpansion = new Map();
12
14
 
13
15
  function closeSheet(restoreFocus) {
14
16
  if (!overlay) return;
@@ -54,66 +56,118 @@ function handleKeydown(event) {
54
56
  }
55
57
  }
56
58
 
59
+ function conversationMatches(conversation, query) {
60
+ if (!query) return true;
61
+ return conversation.title.toLowerCase().indexOf(query) !== -1 || conversation.mateName.toLowerCase().indexOf(query) !== -1;
62
+ }
63
+
64
+ function createSheetItem(conversation, worker) {
65
+ var item = document.createElement("div");
66
+ item.className = "home-conversations-sheet-item" + (worker ? " home-conversations-worker-item" : "");
67
+ var row = document.createElement("button");
68
+ row.type = "button";
69
+ row.className = "home-conversations-sheet-row";
70
+ var copy = document.createElement("span");
71
+ copy.className = "home-conversations-sheet-copy";
72
+ var title = document.createElement("span");
73
+ title.className = "home-conversations-sheet-row-title";
74
+ title.textContent = conversation.title;
75
+ copy.appendChild(title);
76
+ var mate = document.createElement("span");
77
+ mate.className = "home-conversations-sheet-row-mate";
78
+ mate.textContent = worker && conversation.workerGeneration ? conversation.mateName + " · Worker generation " + conversation.workerGeneration : conversation.mateName;
79
+ copy.appendChild(mate);
80
+ row.appendChild(copy);
81
+ if (conversation.isProcessing) {
82
+ var activity = document.createElement("span");
83
+ activity.className = "home-sidebar-processing";
84
+ activity.setAttribute("aria-label", "Processing");
85
+ row.appendChild(activity);
86
+ }
87
+ row.addEventListener("click", function () {
88
+ var onSelect = selectConversation;
89
+ closeSheet();
90
+ if (onSelect) onSelect(conversation.mateId, conversation.sessionId);
91
+ });
92
+ var active = conversation.mateId === store.get('homeChatMateId') && conversation.sessionId === store.get('homeChatSessionId');
93
+ if (active) {
94
+ item.classList.add("is-active");
95
+ row.setAttribute("aria-current", "page");
96
+ }
97
+ var detailsReturn = sheetOpener;
98
+ var actions = createHomeSessionActionsTrigger(conversation, {
99
+ detailsOpener: detailsReturn,
100
+ beforeOpenDetails: function () { closeSheet(false); },
101
+ });
102
+ item.appendChild(row);
103
+ item.appendChild(actions);
104
+ return item;
105
+ }
106
+
107
+ function createSheetHierarchy(root, query) {
108
+ if (!root.workers.length) return createSheetItem(root.driver, false);
109
+ var key = root.driver.hierarchyId;
110
+ var expanded = hierarchyExpansion.has(key) ? hierarchyExpansion.get(key) : !!query;
111
+ for (var ai = 0; ai < root.workers.length; ai++) {
112
+ if (root.workers[ai].mateId === store.get('homeChatMateId') && root.workers[ai].sessionId === store.get('homeChatSessionId')) expanded = true;
113
+ }
114
+ var wrapper = document.createElement("div");
115
+ wrapper.className = "home-conversations-driver-hierarchy";
116
+ var header = document.createElement("div");
117
+ header.className = "home-conversations-driver-header";
118
+ var toggle = document.createElement("button");
119
+ toggle.type = "button";
120
+ toggle.className = "home-conversations-driver-toggle";
121
+ var childrenId = "home-sheet-workers-" + String(key).replace(/[^a-zA-Z0-9_-]/g, "-");
122
+ toggle.setAttribute("aria-expanded", String(expanded));
123
+ toggle.setAttribute("aria-controls", childrenId);
124
+ toggle.setAttribute("aria-label", (expanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "Unavailable Driver"));
125
+ toggle.innerHTML = iconHtml("chevron-right");
126
+ if (!root.driver.sessionId) toggle.insertAdjacentHTML("beforeend", "<span>Unavailable Driver</span><span>" + root.workers.length + "</span>");
127
+ toggle.addEventListener("click", function () {
128
+ hierarchyExpansion.set(key, !expanded);
129
+ renderRows();
130
+ });
131
+ header.appendChild(toggle);
132
+ if (root.driver.sessionId) header.appendChild(createSheetItem(root.driver, false));
133
+ wrapper.appendChild(header);
134
+ var children = document.createElement("div");
135
+ children.id = childrenId;
136
+ children.className = "home-conversations-worker-children";
137
+ children.setAttribute("role", "group");
138
+ children.setAttribute("aria-label", "Workers for " + (root.driver.title || "Unavailable Driver"));
139
+ children.hidden = !expanded;
140
+ var driverMatched = root.driver.sessionId && conversationMatches(root.driver, query);
141
+ for (var i = 0; i < root.workers.length; i++) {
142
+ if (!query || driverMatched || conversationMatches(root.workers[i], query)) children.appendChild(createSheetItem(root.workers[i], true));
143
+ }
144
+ wrapper.appendChild(children);
145
+ return wrapper;
146
+ }
147
+
57
148
  function renderRows() {
58
149
  if (!listEl) return;
59
150
  var query = searchInput ? searchInput.value.trim().toLowerCase() : "";
60
- var all = getHomeSessionConversations().filter(function (conversation) {
61
- if (!query) return true;
62
- return conversation.title.toLowerCase().indexOf(query) !== -1 || conversation.mateName.toLowerCase().indexOf(query) !== -1;
151
+ var hierarchy = buildSessionHierarchy(getHomeSessionConversations());
152
+ var roots = hierarchy.roots.filter(function (root) {
153
+ if (conversationMatches(root.driver, query)) return true;
154
+ for (var i = 0; i < root.workers.length; i++) if (conversationMatches(root.workers[i], query)) return true;
155
+ return false;
63
156
  });
157
+ var orphans = hierarchy.orphans.filter(function (worker) { return conversationMatches(worker, query); });
64
158
  disposeHomeSessionActionsMenu();
65
159
  listEl.innerHTML = "";
66
- if (!all.length) {
160
+ if (!roots.length && !orphans.length) {
67
161
  var empty = document.createElement("div");
68
162
  empty.className = "home-conversations-sheet-empty";
69
163
  empty.textContent = query ? "No chats match your search." : "No chats yet.";
70
164
  listEl.appendChild(empty);
71
165
  return;
72
166
  }
73
- for (var i = 0; i < all.length; i++) {
74
- (function (conversation) {
75
- var item = document.createElement("div");
76
- item.className = "home-conversations-sheet-item";
77
- var row = document.createElement("button");
78
- row.type = "button";
79
- row.className = "home-conversations-sheet-row";
80
- var copy = document.createElement("span");
81
- copy.className = "home-conversations-sheet-copy";
82
- var title = document.createElement("span");
83
- title.className = "home-conversations-sheet-row-title";
84
- title.textContent = conversation.title;
85
- copy.appendChild(title);
86
- var mate = document.createElement("span");
87
- mate.className = "home-conversations-sheet-row-mate";
88
- mate.textContent = conversation.mateName;
89
- copy.appendChild(mate);
90
- row.appendChild(copy);
91
- if (conversation.isProcessing) {
92
- var activity = document.createElement("span");
93
- activity.className = "home-sidebar-processing";
94
- activity.setAttribute("aria-label", "Processing");
95
- row.appendChild(activity);
96
- }
97
- row.addEventListener("click", function () {
98
- var onSelect = selectConversation;
99
- closeSheet();
100
- if (onSelect) onSelect(conversation.mateId, conversation.sessionId);
101
- });
102
- var active = conversation.mateId === store.get('homeChatMateId') && conversation.sessionId === store.get('homeChatSessionId');
103
- if (active) {
104
- item.classList.add("is-active");
105
- row.setAttribute("aria-current", "page");
106
- }
107
- var detailsReturn = sheetOpener;
108
- var actions = createHomeSessionActionsTrigger(conversation, {
109
- detailsOpener: detailsReturn,
110
- beforeOpenDetails: function () { closeSheet(false); },
111
- });
112
- item.appendChild(row);
113
- item.appendChild(actions);
114
- listEl.appendChild(item);
115
- })(all[i]);
116
- }
167
+ roots.sort(function (a, b) { return b.lastActivity - a.lastActivity; });
168
+ for (var i = 0; i < roots.length; i++) listEl.appendChild(createSheetHierarchy(roots[i], query));
169
+ if (orphans.length) listEl.appendChild(createSheetHierarchy({ driver: { hierarchyId: "orphan-sheet", title: "Unavailable Driver" }, workers: orphans }, query));
170
+ refreshIcons();
117
171
  }
118
172
 
119
173
  export function openHomeConversationsSheet(onSelect, opener) {
@@ -53,6 +53,12 @@ export function getHomeSessionConversations() {
53
53
  createdAt: typeof sessions[j].createdAt === "number" ? sessions[j].createdAt : 0,
54
54
  lastActivity: typeof sessions[j].lastActivity === "number" ? sessions[j].lastActivity : 0,
55
55
  isProcessing: sessions[j].isProcessing === true,
56
+ sessionRole: sessions[j].sessionRole === "worker" ? "worker" : "driver",
57
+ parentSessionId: safeText(sessions[j].parentSessionId, null),
58
+ parentAvailable: sessions[j].parentAvailable === true,
59
+ workerGeneration: typeof sessions[j].workerGeneration === "number" ? sessions[j].workerGeneration : null,
60
+ hierarchyId: mateIds[i] + ":" + sessions[j].id,
61
+ parentHierarchyId: sessions[j].parentSessionId ? mateIds[i] + ":" + sessions[j].parentSessionId : null,
56
62
  });
57
63
  }
58
64
  }
@@ -1,7 +1,98 @@
1
- // Complete, globally sorted Home chat archive rendered in the sidebar.
1
+ // Complete Home chat archive rendered as provenance-preserving session trees.
2
2
 
3
3
  import { store } from './store.js';
4
+ import { iconHtml, refreshIcons } from './icons.js';
4
5
  import { getHomeSessionConversations, createHomeSessionActionsTrigger, disposeHomeSessionActionsMenu } from './home-session-actions.js';
6
+ import { buildSessionHierarchy } from './session-hierarchy.js';
7
+
8
+ var expansion = new Map();
9
+
10
+ function isExpanded(key, workers) {
11
+ if (expansion.has(key)) return expansion.get(key);
12
+ for (var i = 0; i < workers.length; i++) {
13
+ if (workers[i].mateId === store.get('homeChatMateId') && workers[i].sessionId === store.get('homeChatSessionId')) return true;
14
+ }
15
+ return false;
16
+ }
17
+
18
+ function createChatItem(chat, openConversation, worker) {
19
+ var item = document.createElement("div");
20
+ item.className = "home-sidebar-recent-item" + (worker ? " home-sidebar-worker-item" : "");
21
+ var row = document.createElement("button");
22
+ row.type = "button";
23
+ row.className = "home-sidebar-recent-row";
24
+ row.setAttribute("aria-label", chat.title + ", with " + chat.mateName);
25
+ if (chat.mateId === store.get('homeChatMateId') && chat.sessionId === store.get('homeChatSessionId')) {
26
+ item.classList.add("is-active");
27
+ row.setAttribute("aria-current", "page");
28
+ }
29
+ var copy = document.createElement("span");
30
+ copy.className = "home-sidebar-recent-copy";
31
+ var title = document.createElement("span");
32
+ title.className = "home-sidebar-recent-title";
33
+ title.textContent = chat.title;
34
+ var mate = document.createElement("span");
35
+ mate.className = "home-sidebar-recent-mate";
36
+ mate.textContent = worker && chat.workerGeneration ? chat.mateName + " · Worker generation " + chat.workerGeneration : chat.mateName;
37
+ copy.appendChild(title);
38
+ copy.appendChild(mate);
39
+ row.appendChild(copy);
40
+ if (chat.isProcessing) {
41
+ var activity = document.createElement("span");
42
+ activity.className = "home-sidebar-processing";
43
+ activity.setAttribute("aria-label", "Processing");
44
+ row.appendChild(activity);
45
+ }
46
+ row.addEventListener("click", function () {
47
+ if (typeof openConversation === "function") openConversation(chat.mateId, chat.sessionId);
48
+ });
49
+ item.appendChild(row);
50
+ item.appendChild(createHomeSessionActionsTrigger(chat));
51
+ return item;
52
+ }
53
+
54
+ function createHierarchy(root, openConversation) {
55
+ if (!root.workers.length) return createChatItem(root.driver, openConversation, false);
56
+ var wrapper = document.createElement("div");
57
+ wrapper.className = "home-sidebar-driver-hierarchy";
58
+ var key = root.driver.hierarchyId;
59
+ var expanded = isExpanded(key, root.workers);
60
+ var childrenId = "home-sidebar-workers-" + String(key).replace(/[^a-zA-Z0-9_-]/g, "-");
61
+ var header = document.createElement("div");
62
+ header.className = "home-sidebar-driver-header";
63
+ var toggle = document.createElement("button");
64
+ toggle.type = "button";
65
+ toggle.className = "home-sidebar-driver-toggle";
66
+ toggle.setAttribute("aria-expanded", String(expanded));
67
+ toggle.setAttribute("aria-controls", childrenId);
68
+ toggle.setAttribute("aria-label", (expanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "Unavailable Driver"));
69
+ toggle.innerHTML = iconHtml("chevron-right");
70
+ toggle.addEventListener("click", function () {
71
+ expansion.set(key, !expanded);
72
+ renderHomeSidebarChats(openConversation);
73
+ });
74
+ header.appendChild(toggle);
75
+ if (root.driver.sessionId) header.appendChild(createChatItem(root.driver, openConversation, false));
76
+ wrapper.appendChild(header);
77
+ var children = document.createElement("div");
78
+ children.id = childrenId;
79
+ children.className = "home-sidebar-worker-children";
80
+ children.setAttribute("role", "group");
81
+ children.setAttribute("aria-label", "Workers for " + (root.driver.title || "Unavailable Driver"));
82
+ children.hidden = !expanded;
83
+ for (var i = 0; i < root.workers.length; i++) children.appendChild(createChatItem(root.workers[i], openConversation, true));
84
+ wrapper.appendChild(children);
85
+ return wrapper;
86
+ }
87
+
88
+ function createOrphans(workers, openConversation) {
89
+ var wrapper = createHierarchy({ driver: { hierarchyId: "orphan-home", title: "Unavailable Driver" }, workers: workers }, openConversation);
90
+ var toggle = wrapper.querySelector(".home-sidebar-driver-toggle");
91
+ toggle.classList.add("home-sidebar-orphan-toggle");
92
+ toggle.setAttribute("aria-label", "Toggle Workers whose Driver is unavailable");
93
+ toggle.insertAdjacentHTML("beforeend", "<span>Unavailable Driver</span><span>" + workers.length + "</span>");
94
+ return wrapper;
95
+ }
5
96
 
6
97
  export function renderHomeSidebarChats(openConversation) {
7
98
  var list = document.getElementById("home-sidebar-recent-list");
@@ -11,9 +102,7 @@ export function renderHomeSidebarChats(openConversation) {
11
102
  var chats = getHomeSessionConversations();
12
103
  var scope = store.get('homeChatScope') === "current" ? "current" : "all";
13
104
  var activeMateId = store.get('homeChatMateId');
14
- if (scope === "current") {
15
- chats = chats.filter(function (chat) { return chat.mateId === activeMateId; });
16
- }
105
+ if (scope === "current") chats = chats.filter(function (chat) { return chat.mateId === activeMateId; });
17
106
  if (!chats.length) {
18
107
  var empty = document.createElement("div");
19
108
  empty.className = "home-sidebar-recent-empty";
@@ -21,43 +110,14 @@ export function renderHomeSidebarChats(openConversation) {
21
110
  list.appendChild(empty);
22
111
  return 0;
23
112
  }
24
- var activeSessionId = store.get('homeChatSessionId');
25
- for (var i = 0; i < chats.length; i++) {
26
- (function (chat) {
27
- var item = document.createElement("div");
28
- item.className = "home-sidebar-recent-item";
29
- var row = document.createElement("button");
30
- row.type = "button";
31
- row.className = "home-sidebar-recent-row";
32
- row.setAttribute("aria-label", chat.title + ", with " + chat.mateName);
33
- if (chat.mateId === activeMateId && chat.sessionId === activeSessionId) {
34
- item.classList.add("is-active");
35
- row.setAttribute("aria-current", "page");
36
- }
37
- var copy = document.createElement("span");
38
- copy.className = "home-sidebar-recent-copy";
39
- var title = document.createElement("span");
40
- title.className = "home-sidebar-recent-title";
41
- title.textContent = chat.title;
42
- var mate = document.createElement("span");
43
- mate.className = "home-sidebar-recent-mate";
44
- mate.textContent = chat.mateName;
45
- copy.appendChild(title);
46
- copy.appendChild(mate);
47
- row.appendChild(copy);
48
- if (chat.isProcessing) {
49
- var activity = document.createElement("span");
50
- activity.className = "home-sidebar-processing";
51
- activity.setAttribute("aria-label", "Processing");
52
- row.appendChild(activity);
53
- }
54
- row.addEventListener("click", function () {
55
- if (typeof openConversation === "function") openConversation(chat.mateId, chat.sessionId);
56
- });
57
- item.appendChild(row);
58
- item.appendChild(createHomeSessionActionsTrigger(chat));
59
- list.appendChild(item);
60
- })(chats[i]);
113
+ var hierarchy = buildSessionHierarchy(chats);
114
+ var hasHierarchy = hierarchy.orphans.length > 0;
115
+ hierarchy.roots.sort(function (a, b) { return b.lastActivity - a.lastActivity; });
116
+ for (var i = 0; i < hierarchy.roots.length; i++) {
117
+ if (hierarchy.roots[i].workers.length) hasHierarchy = true;
118
+ list.appendChild(createHierarchy(hierarchy.roots[i], openConversation));
61
119
  }
120
+ if (hierarchy.orphans.length) list.appendChild(createOrphans(hierarchy.orphans, openConversation));
121
+ if (hasHierarchy) refreshIcons();
62
122
  return chats.length;
63
123
  }