clay-server 3.8.1 → 4.0.0-beta.2

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 (189) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/builtin-mates.js +6 -6
  3. package/lib/claude-hook-installer.js +15 -0
  4. package/lib/clay-history-mcp-server.js +62 -297
  5. package/lib/daemon.js +1 -1
  6. package/lib/debate-model-selection.js +119 -0
  7. package/lib/home-debate-tool-policy.js +164 -0
  8. package/lib/home-response-notifications.js +26 -0
  9. package/lib/mate-creation-mcp-server.js +55 -0
  10. package/lib/mate-ready-creation.js +95 -0
  11. package/lib/mates.js +2 -0
  12. package/lib/notes.js +2 -1
  13. package/lib/project-ask-user.js +64 -0
  14. package/lib/project-capsule-catalog.js +101 -0
  15. package/lib/project-connection.js +4 -0
  16. package/lib/project-debate-proposal.js +133 -17
  17. package/lib/project-debate.js +214 -110
  18. package/lib/project-delegated-follow-up.js +84 -0
  19. package/lib/project-delegated-session.js +53 -0
  20. package/lib/project-filesystem.js +7 -9
  21. package/lib/project-knowledge.js +1 -0
  22. package/lib/project-mate-creation-proposal.js +120 -0
  23. package/lib/project-memory.js +14 -7
  24. package/lib/project-models.js +157 -32
  25. package/lib/project-session-notes.js +2 -2
  26. package/lib/project-session-pair.js +8 -8
  27. package/lib/project-sessions.js +66 -51
  28. package/lib/project-vendor-login.js +397 -0
  29. package/lib/project-worker-proposal.js +25 -25
  30. package/lib/project-workspace-query.js +144 -0
  31. package/lib/project.js +349 -102
  32. package/lib/public/app.js +85 -42
  33. package/lib/public/css/avatar-imprints.css +0 -1
  34. package/lib/public/css/base.css +4 -0
  35. package/lib/public/css/capsule-ui.css +389 -0
  36. package/lib/public/css/command-palette.css +306 -6
  37. package/lib/public/css/home-capsule-library.css +243 -0
  38. package/lib/public/css/home-capsule-source.css +192 -0
  39. package/lib/public/css/home-debate-live.css +146 -0
  40. package/lib/public/css/home-debate-planning.css +125 -0
  41. package/lib/public/css/home-debates-archive.css +281 -0
  42. package/lib/public/css/home-hub.css +896 -405
  43. package/lib/public/css/home-mate-creation.css +34 -0
  44. package/lib/public/css/home-mate-model-picker.css +131 -0
  45. package/lib/public/css/home-mate-settings.css +344 -0
  46. package/lib/public/css/home-session-actions.css +265 -0
  47. package/lib/public/css/home-sidebar.css +667 -0
  48. package/lib/public/css/icon-strip.css +1 -2
  49. package/lib/public/css/mates.css +7 -34
  50. package/lib/public/css/pane.css +1 -1
  51. package/lib/public/css/playbook.css +0 -80
  52. package/lib/public/css/sticky-notes.css +53 -117
  53. package/lib/public/css/tui-attention.css +0 -44
  54. package/lib/public/css/workspace-assignment.css +45 -0
  55. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  56. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  57. package/lib/public/index.html +93 -74
  58. package/lib/public/modules/app-connection.js +10 -1
  59. package/lib/public/modules/app-dm.js +43 -160
  60. package/lib/public/modules/app-home-hub.js +328 -585
  61. package/lib/public/modules/app-message-cards.js +242 -0
  62. package/lib/public/modules/app-message-router.js +84 -0
  63. package/lib/public/modules/app-messages.js +102 -37
  64. package/lib/public/modules/app-notifications.js +8 -119
  65. package/lib/public/modules/app-projects.js +9 -1
  66. package/lib/public/modules/app-rendering.js +21 -602
  67. package/lib/public/modules/avatar-imprint.js +11 -8
  68. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  69. package/lib/public/modules/chat-render-runtime.js +198 -0
  70. package/lib/public/modules/command-palette.js +225 -436
  71. package/lib/public/modules/dm-render.js +82 -0
  72. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  73. package/lib/public/modules/home-capsule-library.js +146 -0
  74. package/lib/public/modules/home-capsule-source.js +383 -0
  75. package/lib/public/modules/home-chat-identity.js +35 -0
  76. package/lib/public/modules/home-chat-scroll.js +77 -0
  77. package/lib/public/modules/home-chat-stream-state.js +34 -0
  78. package/lib/public/modules/home-composer-focus.js +25 -0
  79. package/lib/public/modules/home-conversations-sheet.js +179 -0
  80. package/lib/public/modules/home-debate-activity.js +48 -0
  81. package/lib/public/modules/home-debate-controls.js +295 -0
  82. package/lib/public/modules/home-debate-launch.js +41 -0
  83. package/lib/public/modules/home-debate-live.js +284 -0
  84. package/lib/public/modules/home-debate-models.js +73 -0
  85. package/lib/public/modules/home-debate-planning.js +335 -0
  86. package/lib/public/modules/home-debates-archive.js +279 -0
  87. package/lib/public/modules/home-dock-resize.js +74 -0
  88. package/lib/public/modules/home-dock.js +446 -0
  89. package/lib/public/modules/home-mate-chat.js +496 -0
  90. package/lib/public/modules/home-mate-creation.js +109 -0
  91. package/lib/public/modules/home-mate-model-picker.js +281 -0
  92. package/lib/public/modules/home-mate-selection.js +38 -0
  93. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  94. package/lib/public/modules/home-mate-settings.js +383 -0
  95. package/lib/public/modules/home-session-actions.js +373 -0
  96. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  97. package/lib/public/modules/home-shell.js +9 -0
  98. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  99. package/lib/public/modules/home-sidebar.js +158 -0
  100. package/lib/public/modules/home-sub-surface.js +19 -0
  101. package/lib/public/modules/home-surface-boot.js +68 -0
  102. package/lib/public/modules/home-surface.js +141 -0
  103. package/lib/public/modules/home-tools.js +271 -0
  104. package/lib/public/modules/markdown.js +50 -2
  105. package/lib/public/modules/mate-management.js +86 -0
  106. package/lib/public/modules/mate-wizard.js +0 -7
  107. package/lib/public/modules/mention.js +17 -19
  108. package/lib/public/modules/pane-bridge.js +9 -0
  109. package/lib/public/modules/profile.js +4 -5
  110. package/lib/public/modules/project-settings.js +34 -28
  111. package/lib/public/modules/project-switcher.js +11 -5
  112. package/lib/public/modules/project-workspace-assignment.js +23 -0
  113. package/lib/public/modules/search-clay-chat.js +402 -0
  114. package/lib/public/modules/server-settings.js +14 -12
  115. package/lib/public/modules/session-actions.js +1 -1
  116. package/lib/public/modules/sidebar-mates.js +56 -552
  117. package/lib/public/modules/sidebar-mobile.js +7 -64
  118. package/lib/public/modules/sidebar-presence.js +37 -0
  119. package/lib/public/modules/split-pair-ui.js +13 -13
  120. package/lib/public/modules/split-view.js +7 -0
  121. package/lib/public/modules/sticky-notes.js +7 -6
  122. package/lib/public/modules/tool-input-composition.js +48 -0
  123. package/lib/public/modules/tool-llm-status.js +167 -0
  124. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  125. package/lib/public/modules/tool-renderer-chart.js +158 -0
  126. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  127. package/lib/public/modules/tool-renderer.js +464 -0
  128. package/lib/public/modules/tool-runtime.js +309 -0
  129. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  130. package/lib/public/modules/tool-ui-tree.js +21 -0
  131. package/lib/public/modules/tui-attention.js +10 -0
  132. package/lib/public/modules/user-settings.js +16 -23
  133. package/lib/public/modules/vendor-login.js +287 -0
  134. package/lib/public/modules/worker-proposal.js +2 -2
  135. package/lib/public/modules/workspace-assignment-card.js +149 -0
  136. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  137. package/lib/public/style.css +13 -2
  138. package/lib/runtime-env.js +65 -0
  139. package/lib/sdk-bridge.js +120 -32
  140. package/lib/sdk-message-processor.js +10 -6
  141. package/lib/server-home-capsule-creation.js +36 -0
  142. package/lib/server-home-chat-events.js +287 -0
  143. package/lib/server-home-chat.js +518 -0
  144. package/lib/server-home-clay-entry.js +65 -0
  145. package/lib/server-home-clay-session-links.js +26 -0
  146. package/lib/server-home-debate-planning.js +214 -0
  147. package/lib/server-home-debates.js +137 -0
  148. package/lib/server-home-mate-creation.js +261 -0
  149. package/lib/server-home-models.js +241 -0
  150. package/lib/server-home-preferences.js +61 -0
  151. package/lib/server-mates.js +37 -0
  152. package/lib/server-tools.js +377 -0
  153. package/lib/server.js +111 -59
  154. package/lib/session-pair-mcp-server.js +6 -6
  155. package/lib/sessions.js +18 -3
  156. package/lib/tool-capsule-source.js +142 -0
  157. package/lib/tool-control-mcp-server.js +126 -0
  158. package/lib/tool-llm.js +48 -0
  159. package/lib/tool-storage.js +77 -0
  160. package/lib/tool-ui-spec-advanced.js +82 -0
  161. package/lib/tool-ui-spec.js +261 -0
  162. package/lib/tools-registry.js +286 -0
  163. package/lib/users-home-dock-preferences.js +77 -0
  164. package/lib/users-home-surface-preferences.js +99 -0
  165. package/lib/users-preferences.js +4 -7
  166. package/lib/users.js +12 -0
  167. package/lib/whats-new-content.js +3 -3
  168. package/lib/workspace-assignment-service.js +420 -0
  169. package/lib/workspace-query-mcp-server.js +154 -0
  170. package/lib/workspace-query-service.js +492 -0
  171. package/lib/ws-schema.js +93 -12
  172. package/lib/yoke/adapters/acp.js +2 -1
  173. package/lib/yoke/adapters/antigravity.js +2 -1
  174. package/lib/yoke/adapters/claude-worker.js +11 -4
  175. package/lib/yoke/adapters/claude.js +73 -43
  176. package/lib/yoke/adapters/codex.js +40 -10
  177. package/lib/yoke/adapters/kiro.js +2 -1
  178. package/lib/yoke/claude-user-input.js +38 -0
  179. package/lib/yoke/codex-app-server.js +20 -2
  180. package/lib/yoke/codex-user-input.js +64 -0
  181. package/lib/yoke/complete-once.js +164 -0
  182. package/lib/yoke/index.js +5 -1
  183. package/lib/yoke/interface.js +19 -0
  184. package/lib/yoke/mcp-bridge-server.js +2 -6
  185. package/lib/yoke/user-input.js +254 -0
  186. package/package.json +3 -2
  187. package/lib/public/css/home-chat.css +0 -380
  188. package/lib/public/modules/home-chat.js +0 -438
  189. package/lib/server-clay-home.js +0 -245
@@ -0,0 +1,402 @@
1
+ // Compact Clay conversation hosted inside the global search drawer.
2
+ import { getWs } from './ws-ref.js';
3
+ import { showHomeHub } from './app-home-hub.js';
4
+ import { openHomeConversation } from './home-mate-chat.js';
5
+ import { refreshIcons } from './icons.js';
6
+ import { store } from './store.js';
7
+ import { mateAvatarUrl } from './avatar.js';
8
+ import { createAssistantBubble, createUserBubble, finalizeAssistantBubble, renderAssistantBubbleText } from './chat-bubble-renderer.js';
9
+
10
+ var state = null;
11
+ var hostEl = null;
12
+ var backHandler = null;
13
+ var closeHandler = null;
14
+ var sequence = 0;
15
+ var progressTimer = null;
16
+
17
+ function send(payload) {
18
+ var ws = getWs();
19
+ if (!ws || ws.readyState !== 1) return false;
20
+ ws.send(JSON.stringify(payload));
21
+ return true;
22
+ }
23
+
24
+ function requestId() {
25
+ sequence++;
26
+ return "search-clay-" + Date.now() + "-" + sequence;
27
+ }
28
+
29
+ function stopProgressTimer() {
30
+ if (!progressTimer) return;
31
+ clearInterval(progressTimer);
32
+ progressTimer = null;
33
+ }
34
+
35
+ function startProgressTimer() {
36
+ if (progressTimer) return;
37
+ progressTimer = setInterval(function () { if (state && state.processing) updateProgressDom(); else stopProgressTimer(); }, 1000);
38
+ }
39
+
40
+ function progressCopy() {
41
+ var elapsed = state && state.startedAt ? Math.max(0, Math.floor((Date.now() - state.startedAt) / 1000)) : 0;
42
+ var label = state && state.step ? "Considering what was found" : "Understanding what to look for";
43
+ if (state.activity === "searching") label = state.step > 1 ? "Checking another lead" : "Searching your conversations";
44
+ else if (state.activity === "reviewing") label = "Reviewing what Clay found";
45
+ else if (state.activity === "reconsidering") label = "Trying another search route";
46
+ else if (elapsed >= 12) label = "Following related context";
47
+ else if (elapsed >= 4) label = "Searching your conversations";
48
+ var detail = (state.step ? "Search pass " + state.step + " · " : "") + elapsed + "s";
49
+ return { label: label, detail: detail };
50
+ }
51
+
52
+ function updateProgressDom() {
53
+ if (!hostEl) return;
54
+ var progress = progressCopy();
55
+ var label = hostEl.querySelector(".search-clay-pending-copy strong");
56
+ var detail = hostEl.querySelector(".search-clay-pending-copy small");
57
+ if (label) label.textContent = progress.label;
58
+ if (detail) detail.textContent = progress.detail;
59
+ var activeDetails = hostEl.querySelectorAll(".search-clay-activity-item.is-active small");
60
+ var activeIndex = 0;
61
+ for (var i = 0; state && i < state.activities.length; i++) {
62
+ if (state.activities[i].status !== "active") continue;
63
+ var elapsed = Math.max(0, Math.floor((Date.now() - state.activities[i].startedAt) / 1000));
64
+ state.activities[i].detail = elapsed + "s elapsed";
65
+ if (activeDetails[activeIndex]) activeDetails[activeIndex].textContent = state.activities[i].detail;
66
+ activeIndex++;
67
+ }
68
+ }
69
+
70
+ function button(icon, label, title, handler) {
71
+ var el = document.createElement("button");
72
+ el.type = "button";
73
+ el.className = "search-clay-icon-button";
74
+ el.title = title;
75
+ el.setAttribute("aria-label", title);
76
+ el.innerHTML = '<i data-lucide="' + icon + '" aria-hidden="true"></i><span>' + label + '</span>';
77
+ el.addEventListener("click", handler);
78
+ return el;
79
+ }
80
+
81
+ function clayMate() {
82
+ var mates = store.get('cachedMatesList') || [];
83
+ for (var i = 0; i < mates.length; i++) {
84
+ if (mates[i] && (mates[i].id === state.mateId || mates[i].builtinKey === "clay")) return mates[i];
85
+ }
86
+ return { name: "Clay", profile: { displayName: "Clay", avatarCustom: "/clay-studio-symbol.png" } };
87
+ }
88
+
89
+ function appendActivityList(content, items, expanded, onToggle) {
90
+ var panel = document.createElement("div");
91
+ panel.className = "search-clay-activity-panel " + (expanded ? "is-expanded" : "is-collapsed");
92
+ var toggle = document.createElement("button");
93
+ toggle.type = "button";
94
+ toggle.className = "search-clay-activity-toggle";
95
+ toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
96
+ toggle.setAttribute("aria-label", (expanded ? "Collapse" : "Expand") + " Clay activity, " + items.length + (items.length === 1 ? " step" : " steps"));
97
+ toggle.innerHTML = '<i data-lucide="activity" aria-hidden="true"></i><span><strong>Activity</strong><small></small></span><i class="search-clay-activity-chevron" data-lucide="chevron-down" aria-hidden="true"></i>';
98
+ toggle.querySelector("small").textContent = items.length + (items.length === 1 ? " step" : " steps");
99
+ if (items.length <= 2) {
100
+ toggle.disabled = true;
101
+ toggle.setAttribute("aria-label", "Clay activity, " + items.length + (items.length === 1 ? " step" : " steps"));
102
+ } else {
103
+ toggle.addEventListener("click", function () {
104
+ expanded = !expanded;
105
+ panel.classList.toggle("is-expanded", expanded);
106
+ panel.classList.toggle("is-collapsed", !expanded);
107
+ toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
108
+ toggle.setAttribute("aria-label", (expanded ? "Collapse" : "Expand") + " Clay activity, " + items.length + " steps");
109
+ onToggle(expanded);
110
+ });
111
+ }
112
+ panel.appendChild(toggle);
113
+ var list = document.createElement("div");
114
+ list.className = "search-clay-activity-list";
115
+ for (var i = 0; i < items.length; i++) {
116
+ var item = document.createElement("div");
117
+ item.className = "search-clay-activity-item is-" + items[i].status;
118
+ item.innerHTML = '<i data-lucide="' + activityIcon(items[i].status) + '" aria-hidden="true"></i>';
119
+ var copy = document.createElement("span");
120
+ var label = document.createElement("strong");
121
+ label.textContent = items[i].label;
122
+ copy.appendChild(label);
123
+ var detail = document.createElement("small");
124
+ detail.textContent = items[i].detail || "";
125
+ copy.appendChild(detail);
126
+ item.appendChild(copy);
127
+ list.appendChild(item);
128
+ }
129
+ panel.appendChild(list);
130
+ content.appendChild(panel);
131
+ }
132
+
133
+ function renderMessage(message, streaming) {
134
+ var row;
135
+ if (message.role === "user") {
136
+ row = createUserBubble({ text: message.text || "" });
137
+ row.classList.add("search-clay-message", "search-clay-message-user");
138
+ } else {
139
+ row = createAssistantBubble({ name: "Clay", avatarUrl: mateAvatarUrl(clayMate(), 28) });
140
+ row.classList.add("search-clay-message", "search-clay-message-assistant");
141
+ if (streaming) renderAssistantBubbleText(row, message.text || "", false);
142
+ else finalizeAssistantBubble(row, message.text || "", false);
143
+ if (message.activities && message.activities.length) {
144
+ appendActivityList(row.querySelector(".dm-bubble-content"), message.activities, message.activitiesExpanded === true, message.toggleActivities || function (expanded) { message.activitiesExpanded = expanded; });
145
+ row.classList.add("search-clay-activity-message");
146
+ }
147
+ }
148
+ row.setAttribute("role", "article");
149
+ row.setAttribute("aria-label", message.role === "user" ? "Your message" : "Message from Clay");
150
+ return row;
151
+ }
152
+
153
+ function activityIcon(status) {
154
+ if (status === "done") return "circle-check";
155
+ if (status === "error") return "circle-alert";
156
+ return "loader-circle";
157
+ }
158
+
159
+ function updateActivity(message) {
160
+ var id = message.activityId || message.phase + ":" + (message.step || 0);
161
+ var activity = null;
162
+ for (var i = 0; i < state.activities.length; i++) if (state.activities[i].id === id) activity = state.activities[i];
163
+ if (!activity) {
164
+ activity = { id: id, label: message.label || "Working", detail: "0s elapsed", status: message.status || "active", startedAt: Date.now() };
165
+ state.activities.push(activity);
166
+ } else if (message.status === "active") {
167
+ activity.label = message.label || activity.label;
168
+ activity.status = "active";
169
+ } else {
170
+ var elapsed = Math.max(0, Math.floor((Date.now() - activity.startedAt) / 1000));
171
+ activity.detail = (message.label || "Complete") + (elapsed ? " \u00b7 " + elapsed + "s" : "");
172
+ activity.status = message.status || "done";
173
+ }
174
+ if (state.activities.length > 12) state.activities = state.activities.slice(state.activities.length - 12);
175
+ }
176
+
177
+ function takeActivities(finalLabel, finalStatus) {
178
+ var now = Date.now();
179
+ for (var i = 0; i < state.activities.length; i++) {
180
+ if (state.activities[i].status !== "active") continue;
181
+ var elapsed = Math.max(0, Math.floor((now - state.activities[i].startedAt) / 1000));
182
+ state.activities[i].status = finalStatus || "done";
183
+ state.activities[i].detail = finalLabel + (elapsed ? " \u00b7 " + elapsed + "s" : "");
184
+ }
185
+ var items = state.activities.slice();
186
+ state.activities = [];
187
+ return items;
188
+ }
189
+
190
+ function submit(input, sendButton) {
191
+ var text = input.value.trim();
192
+ if (!state || !text || !state.sessionId) return;
193
+ if (!send({ type: "home_mate_send", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, text: text })) return;
194
+ if (state.processing) {
195
+ state.queuedUsers.push({ role: "user", text: text });
196
+ } else {
197
+ state.messages.push({ role: "user", text: text });
198
+ state.processing = true;
199
+ state.awaitingCompletion = true;
200
+ state.stream = "";
201
+ state.activity = "thinking";
202
+ state.step = 0;
203
+ state.activities = [];
204
+ state.activityExpanded = false;
205
+ state.startedAt = Date.now();
206
+ }
207
+ state.pendingTurns++;
208
+ state.updatedAt = Date.now();
209
+ startProgressTimer();
210
+ state.draft = "";
211
+ input.value = "";
212
+ sendButton.disabled = true;
213
+ render();
214
+ }
215
+
216
+ function expand() {
217
+ if (!state || !state.mateId || !state.sessionId) return;
218
+ var mateId = state.mateId;
219
+ var sessionId = state.sessionId;
220
+ if (closeHandler) closeHandler();
221
+ showHomeHub();
222
+ openHomeConversation(mateId, sessionId);
223
+ }
224
+
225
+ function render() {
226
+ if (!hostEl || !state) return;
227
+ var shell = hostEl.querySelector(".search-clay-chat");
228
+ var transcript = shell ? shell.querySelector(".search-clay-transcript") : null;
229
+ var previousTop = transcript ? transcript.scrollTop : 0;
230
+ var follow = !transcript || transcript.scrollHeight - transcript.scrollTop - transcript.clientHeight < 40;
231
+ if (!shell) {
232
+ hostEl.innerHTML = "";
233
+ shell = document.createElement("section");
234
+ shell.className = "search-clay-chat";
235
+ shell.setAttribute("aria-label", "Chat with Clay");
236
+ var header = document.createElement("header");
237
+ header.className = "search-clay-header";
238
+ var identity = document.createElement("div");
239
+ identity.className = "search-clay-identity";
240
+ identity.innerHTML = '<span><strong>Clay</strong><small>Workspace search</small></span>';
241
+ header.appendChild(identity);
242
+ var actions = document.createElement("div");
243
+ actions.className = "search-clay-header-actions";
244
+ actions.appendChild(button("search", "Search", "Back to search", function () { detachSearchClayChat(); if (backHandler) backHandler(); }));
245
+ actions.appendChild(button("maximize-2", "Expand", "Open this conversation in Home", expand));
246
+ header.appendChild(actions);
247
+ shell.appendChild(header);
248
+ transcript = document.createElement("div");
249
+ transcript.className = "search-clay-transcript";
250
+ transcript.setAttribute("aria-live", "polite");
251
+ shell.appendChild(transcript);
252
+ var composer = document.createElement("div");
253
+ composer.className = "search-clay-composer";
254
+ var freshInput = document.createElement("textarea");
255
+ freshInput.rows = 1;
256
+ freshInput.value = state.draft || "";
257
+ freshInput.setAttribute("aria-label", "Message Clay");
258
+ var freshSendButton = document.createElement("button");
259
+ freshSendButton.type = "button";
260
+ freshSendButton.className = "search-clay-send";
261
+ freshSendButton.setAttribute("aria-label", "Send to Clay");
262
+ freshSendButton.innerHTML = '<i data-lucide="arrow-up" aria-hidden="true"></i>';
263
+ freshInput.addEventListener("input", function () { state.draft = freshInput.value; freshSendButton.disabled = !state.sessionId || !freshInput.value.trim(); });
264
+ freshInput.addEventListener("keydown", function (event) { if (event.key === "Enter" && !event.shiftKey && !event.isComposing) { event.preventDefault(); submit(freshInput, freshSendButton); } });
265
+ freshSendButton.addEventListener("click", function () { submit(freshInput, freshSendButton); });
266
+ composer.appendChild(freshInput);
267
+ composer.appendChild(freshSendButton);
268
+ shell.appendChild(composer);
269
+ hostEl.appendChild(shell);
270
+ }
271
+ transcript.innerHTML = "";
272
+ for (var i = 0; i < state.messages.length; i++) transcript.appendChild(renderMessage(state.messages[i], false));
273
+ if (state.activities.length || state.stream) transcript.appendChild(renderMessage({ role: "assistant", text: state.stream, activities: state.activities, activitiesExpanded: state.activityExpanded, toggleActivities: function (expanded) { state.activityExpanded = expanded; } }, true));
274
+ if (state.processing && !state.stream && !state.activities.length) {
275
+ var progress = progressCopy();
276
+ var pending = document.createElement("div");
277
+ pending.className = "search-clay-pending";
278
+ pending.setAttribute("role", "status");
279
+ var pendingCopy = document.createElement("span");
280
+ pendingCopy.className = "search-clay-pending-copy";
281
+ var pendingLabel = document.createElement("strong");
282
+ pendingLabel.textContent = progress.label;
283
+ var pendingDetail = document.createElement("small");
284
+ pendingDetail.textContent = progress.detail;
285
+ pendingCopy.appendChild(pendingLabel);
286
+ pendingCopy.appendChild(pendingDetail);
287
+ pending.appendChild(pendingCopy);
288
+ var dots = document.createElement("span");
289
+ dots.className = "search-clay-dots";
290
+ dots.setAttribute("aria-hidden", "true");
291
+ dots.innerHTML = "<i></i><i></i><i></i>";
292
+ pending.appendChild(dots);
293
+ transcript.appendChild(pending);
294
+ }
295
+ for (var q = 0; q < state.queuedUsers.length; q++) transcript.appendChild(renderMessage(state.queuedUsers[q], false));
296
+ var input = shell.querySelector(".search-clay-composer textarea");
297
+ var sendButton = shell.querySelector(".search-clay-send");
298
+ input.placeholder = state.sessionId ? "Message Clay…" : "Starting Clay…";
299
+ input.disabled = !state.sessionId;
300
+ sendButton.disabled = !state.sessionId || !input.value.trim();
301
+ var expandButton = shell.querySelector('.search-clay-icon-button[aria-label="Open this conversation in Home"]');
302
+ if (expandButton) expandButton.disabled = !state.sessionId;
303
+ refreshIcons();
304
+ transcript.scrollTop = follow ? transcript.scrollHeight : previousTop;
305
+ }
306
+
307
+ export function startSearchClayChat(query, host, onBack, onClose) {
308
+ var clean = typeof query === "string" ? query.trim().slice(0, 12000) : "";
309
+ if (!clean) return false;
310
+ hostEl = host;
311
+ backHandler = onBack;
312
+ closeHandler = onClose;
313
+ var startedAt = Date.now();
314
+ state = { requestId: requestId(), mateId: null, sessionId: null, messages: [{ role: "user", text: clean }], queuedUsers: [], pendingTurns: 1, activities: [], activityExpanded: false, stream: "", draft: "", processing: true, awaitingCompletion: true, activity: "thinking", step: 0, startedAt: startedAt, updatedAt: startedAt };
315
+ startProgressTimer();
316
+ render();
317
+ if (!send({ type: "home_clay_ask", requestId: state.requestId, text: clean })) {
318
+ state.processing = false;
319
+ state.awaitingCompletion = false;
320
+ state.pendingTurns = 0;
321
+ stopProgressTimer();
322
+ state.messages.push({ role: "assistant", text: "Clay is offline. Reconnect and try again." });
323
+ render();
324
+ }
325
+ return true;
326
+ }
327
+
328
+ export function attachSearchClayChat(host, onBack, onClose) {
329
+ if (!state) return false;
330
+ hostEl = host;
331
+ backHandler = onBack;
332
+ closeHandler = onClose;
333
+ render();
334
+ return true;
335
+ }
336
+
337
+ export function getSearchClayChatSummary() {
338
+ if (!state) return null;
339
+ var preview = "";
340
+ var candidates = state.queuedUsers.length ? state.queuedUsers : state.messages;
341
+ for (var i = candidates.length - 1; i >= 0; i--) {
342
+ if (!candidates[i] || typeof candidates[i].text !== "string" || !candidates[i].text.trim()) continue;
343
+ preview = candidates[i].text.replace(/\s+/g, " ").trim().slice(0, 120);
344
+ break;
345
+ }
346
+ return { sessionId: state.sessionId, processing: state.processing === true, preview: preview, updatedAt: state.updatedAt || state.startedAt || null };
347
+ }
348
+
349
+ export function detachSearchClayChat() { hostEl = null; }
350
+
351
+ export function handleSearchClayMessage(msg) {
352
+ if (!state || !msg || msg.requestId !== state.requestId) return false;
353
+ state.updatedAt = Date.now();
354
+ if (msg.mateId) state.mateId = msg.mateId;
355
+ if (msg.type === "home_mate_session_identity" && msg.sessionId) state.sessionId = msg.sessionId;
356
+ if (msg.type === "home_clay_activity") {
357
+ if (!state.awaitingCompletion) return true;
358
+ state.activity = msg.phase || state.activity;
359
+ state.step = typeof msg.step === "number" ? msg.step : state.step;
360
+ updateActivity(msg);
361
+ } else if (msg.type === "home_mate_history") {
362
+ state.sessionId = msg.sessionId || state.sessionId;
363
+ state.messages = Array.isArray(msg.messages) ? msg.messages.filter(function (item) { return item && (item.role === "user" || item.role === "assistant"); }).map(function (item) { return { role: item.role, text: item.text || "" }; }) : state.messages;
364
+ state.processing = msg.isProcessing === true;
365
+ state.awaitingCompletion = state.processing;
366
+ state.pendingTurns = state.processing ? Math.max(1, state.pendingTurns) : 0;
367
+ if (state.processing) startProgressTimer();
368
+ state.stream = "";
369
+ } else if (msg.type === "home_mate_delta") {
370
+ if (!state.awaitingCompletion) return true;
371
+ state.sessionId = msg.sessionId || state.sessionId;
372
+ state.processing = true;
373
+ state.pendingTurns = Math.max(1, state.pendingTurns);
374
+ state.stream += msg.text || "";
375
+ } else if (msg.type === "home_mate_done") {
376
+ if (!state.awaitingCompletion) return true;
377
+ var completedActivities = takeActivities("Response complete", "done");
378
+ if (state.stream || msg.text || completedActivities.length) state.messages.push({ role: "assistant", text: state.stream || msg.text || "", activities: completedActivities, activitiesExpanded: state.activityExpanded });
379
+ if (state.queuedUsers.length) state.messages.push(state.queuedUsers.shift());
380
+ state.stream = "";
381
+ state.activityExpanded = false;
382
+ state.pendingTurns = Math.max(0, state.pendingTurns - 1);
383
+ state.processing = state.pendingTurns > 0;
384
+ state.awaitingCompletion = state.processing;
385
+ state.startedAt = Date.now();
386
+ if (!state.processing) stopProgressTimer();
387
+ } else if (msg.type === "home_mate_error") {
388
+ if (!state.awaitingCompletion) return true;
389
+ var failedActivities = takeActivities("Stopped", "error");
390
+ state.stream = "";
391
+ state.messages.push({ role: "assistant", text: msg.text || "Clay could not complete that search.", activities: failedActivities, activitiesExpanded: state.activityExpanded });
392
+ if (state.queuedUsers.length) state.messages.push(state.queuedUsers.shift());
393
+ state.pendingTurns = Math.max(0, state.pendingTurns - 1);
394
+ state.activityExpanded = false;
395
+ state.processing = state.pendingTurns > 0;
396
+ state.awaitingCompletion = state.processing;
397
+ state.startedAt = Date.now();
398
+ if (!state.processing) stopProgressTimer();
399
+ }
400
+ render();
401
+ return true;
402
+ }
@@ -1,7 +1,7 @@
1
1
  // server-settings.js — Full-screen server settings overlay
2
2
  import { refreshIcons } from './icons.js';
3
3
  import { showToast, copyToClipboard } from './utils.js';
4
- import { parseEnvString, looksLikeEnv } from './project-settings.js';
4
+ import { parseEnvString, looksLikeEnv, serializeEnvValue } from './project-settings.js';
5
5
  import { checkAdminAccess, loadAdminSection } from './admin.js';
6
6
  import { closeFileViewer } from './filebrowser.js';
7
7
 
@@ -637,6 +637,15 @@ export function handleGlobalClaudeMdWrite(msg) {
637
637
 
638
638
  // ===== Shared Environment Variables =====
639
639
  var sharedEnvSaveTimer = null;
640
+ var sharedEnvStatusTimer = null;
641
+
642
+ function showSharedEnvSaveStatus(text, duration) {
643
+ var saveStatus = document.getElementById("ss-env-save-status");
644
+ if (!saveStatus) return;
645
+ if (sharedEnvStatusTimer) clearTimeout(sharedEnvStatusTimer);
646
+ saveStatus.textContent = text;
647
+ sharedEnvStatusTimer = setTimeout(function () { saveStatus.textContent = ""; }, duration);
648
+ }
640
649
 
641
650
  function loadSharedEnv() {
642
651
  var saveStatus = document.getElementById("ss-env-save-status");
@@ -661,13 +670,10 @@ export function handleSharedEnv(msg) {
661
670
  }
662
671
 
663
672
  export function handleSharedEnvSaved(msg) {
664
- var saveStatus = document.getElementById("ss-env-save-status");
665
- if (!saveStatus) return;
666
673
  if (msg.ok) {
667
- saveStatus.textContent = "Saved";
668
- setTimeout(function () { saveStatus.textContent = ""; }, 2000);
674
+ showSharedEnvSaveStatus(msg.timing || "Saved", 5000);
669
675
  } else {
670
- saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
676
+ showSharedEnvSaveStatus("Error: " + (msg.error || "Failed to save"), 5000);
671
677
  }
672
678
  }
673
679
 
@@ -681,7 +687,7 @@ function buildSharedEnvString() {
681
687
  var valInput = rows[i].querySelector(".ps-env-val");
682
688
  var key = keyInput ? keyInput.value.trim() : "";
683
689
  var val = valInput ? valInput.value : "";
684
- if (key) lines.push("export " + key + "=" + val);
690
+ if (key) lines.push("export " + key + "=" + serializeEnvValue(val));
685
691
  }
686
692
  return lines.join("\n");
687
693
  }
@@ -771,11 +777,7 @@ function autoSaveSharedEnv() {
771
777
  var ws = ctx.ws;
772
778
  if (ws && ws.readyState === 1) {
773
779
  ws.send(JSON.stringify({ type: "set_shared_env", envrc: envrc }));
774
- var saveStatus = document.getElementById("ss-env-save-status");
775
- if (saveStatus) {
776
- saveStatus.textContent = "Saved";
777
- setTimeout(function () { saveStatus.textContent = ""; }, 2000);
778
- }
780
+ showSharedEnvSaveStatus("Saving...", 2000);
779
781
  }
780
782
  }, 800);
781
783
  }
@@ -218,7 +218,7 @@ function showHandoffDialog(options) {
218
218
  function showMainMenu() {
219
219
  closeMenu();
220
220
  var el = menuShell("Session actions");
221
- var addWorker = actionRow("bot", "Add AI worker", "Open a second agent beside this session.");
221
+ var addWorker = actionRow("bot", "Add Split Worker", "Open a visible Split Worker beside this session.");
222
222
  addWorker.classList.add("is-primary");
223
223
  addWorker.addEventListener("click", function () {
224
224
  var sessionId = store.get('activeSessionId');