clay-server 5.2.0-beta.6 → 5.2.0-beta.8

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 (109) hide show
  1. package/lib/clay-control-presentation-binding.js +96 -0
  2. package/lib/clay-control-presentation.js +128 -0
  3. package/lib/clay-control-target-projection.js +26 -0
  4. package/lib/clay-mate-chat.js +1 -0
  5. package/lib/clay-mate-knowledge.js +1 -1
  6. package/lib/clay-product-control-tools.js +1 -1
  7. package/lib/clay-product-control.js +22 -12
  8. package/lib/clay-project-records.js +2 -1
  9. package/lib/clay-scheduling.js +1 -1
  10. package/lib/clay-screen-controls.js +13 -5
  11. package/lib/clay-workspace-controls.js +2 -1
  12. package/lib/contextual-screen-snapshot.js +38 -0
  13. package/lib/mate-context-service.js +138 -0
  14. package/lib/mate-context-tools.js +21 -0
  15. package/lib/project-connection.js +8 -5
  16. package/lib/project-control-git-view.js +12 -0
  17. package/lib/project-filesystem.js +5 -1
  18. package/lib/project-http.js +3 -0
  19. package/lib/project-notifications.js +5 -4
  20. package/lib/project-product-control.js +3 -1
  21. package/lib/project-scheduled-messages.js +4 -4
  22. package/lib/project-scheduled-task-control.js +1 -1
  23. package/lib/project-session-document.js +20 -5
  24. package/lib/project-shared-browser.js +22 -5
  25. package/lib/project-studio-presentation.js +58 -0
  26. package/lib/project-user-message.js +36 -0
  27. package/lib/project-workspace-query.js +11 -1
  28. package/lib/project.js +24 -7
  29. package/lib/public/app.js +24 -0
  30. package/lib/public/appearance-bootstrap.js +2 -0
  31. package/lib/public/css/contextual-clay.css +240 -0
  32. package/lib/public/modules/app-favicon.js +7 -7
  33. package/lib/public/modules/app-message-router.js +5 -1
  34. package/lib/public/modules/app-messages.js +14 -1
  35. package/lib/public/modules/app-notifications.js +10 -3
  36. package/lib/public/modules/app-projects.js +3 -0
  37. package/lib/public/modules/clay-control-activity.js +214 -0
  38. package/lib/public/modules/clay-control-copy.js +36 -0
  39. package/lib/public/modules/clay-control-detail-targets.js +44 -0
  40. package/lib/public/modules/clay-control-object-targets.js +52 -0
  41. package/lib/public/modules/clay-control-target-visibility.js +12 -0
  42. package/lib/public/modules/clay-control-targets.js +44 -0
  43. package/lib/public/modules/clay-navigation-render.js +52 -0
  44. package/lib/public/modules/clay-owner-navigation.js +11 -2
  45. package/lib/public/modules/clay-session-links.js +31 -0
  46. package/lib/public/modules/clay-studio-bridge.js +20 -0
  47. package/lib/public/modules/clay-studio-document.js +67 -0
  48. package/lib/public/modules/clay-studio-render.js +144 -0
  49. package/lib/public/modules/clay-tool-presentation.js +4 -0
  50. package/lib/public/modules/command-palette.js +26 -31
  51. package/lib/public/modules/contextual-ask-session.js +26 -0
  52. package/lib/public/modules/contextual-clay-geometry.js +155 -0
  53. package/lib/public/modules/contextual-clay-session.js +130 -0
  54. package/lib/public/modules/contextual-clay-workspace.js +157 -0
  55. package/lib/public/modules/contextual-clay.js +237 -0
  56. package/lib/public/modules/contextual-composer.js +91 -0
  57. package/lib/public/modules/contextual-mate-context.js +115 -0
  58. package/lib/public/modules/contextual-mate-hint.js +40 -0
  59. package/lib/public/modules/contextual-mate-identity.js +23 -0
  60. package/lib/public/modules/contextual-mate-sessions.js +134 -0
  61. package/lib/public/modules/contextual-search.js +137 -0
  62. package/lib/public/modules/contextual-session-citations.js +58 -0
  63. package/lib/public/modules/filebrowser.js +20 -1
  64. package/lib/public/modules/generation-stop.js +8 -5
  65. package/lib/public/modules/git-panel.js +3 -1
  66. package/lib/public/modules/icon-strip-tooltip.js +2 -1
  67. package/lib/public/modules/input.js +35 -1
  68. package/lib/public/modules/issues-render.js +3 -2
  69. package/lib/public/modules/knowledge-db-workbench.js +4 -4
  70. package/lib/public/modules/markdown.js +2 -34
  71. package/lib/public/modules/mate-knowledge-workbench.js +1 -1
  72. package/lib/public/modules/message-delivery.js +10 -0
  73. package/lib/public/modules/pane-bridge.js +2 -2
  74. package/lib/public/modules/pending-message-queue.js +1 -0
  75. package/lib/public/modules/product-review.js +26 -0
  76. package/lib/public/modules/project-chat-identity.js +13 -4
  77. package/lib/public/modules/project-logs-render.js +3 -0
  78. package/lib/public/modules/project-mate-navigation.js +58 -21
  79. package/lib/public/modules/scheduled-tasks.js +2 -0
  80. package/lib/public/modules/search-clay-chat.js +5 -454
  81. package/lib/public/modules/shared-browser.js +2 -0
  82. package/lib/public/modules/sidebar-mobile.js +4 -3
  83. package/lib/public/modules/sidebar-projects.js +1 -0
  84. package/lib/public/modules/sidebar-sessions.js +1 -0
  85. package/lib/public/modules/split-view.js +7 -16
  86. package/lib/public/modules/stt.js +7 -2
  87. package/lib/public/modules/terminal.js +9 -0
  88. package/lib/public/modules/tools.js +4 -1
  89. package/lib/public/modules/wireframe-workbench.js +5 -2
  90. package/lib/public/style.css +2 -0
  91. package/lib/sdk-bridge.js +4 -0
  92. package/lib/server-contextual-clay-settings.js +40 -0
  93. package/lib/server-contextual-mate-sessions.js +70 -0
  94. package/lib/server-home-chat.js +7 -1
  95. package/lib/server-home-clay-entry.js +23 -4
  96. package/lib/server-home-clay-session-links.js +12 -0
  97. package/lib/server-owner-ui.js +49 -8
  98. package/lib/server-settings.js +2 -0
  99. package/lib/server.js +17 -0
  100. package/lib/sessions.js +10 -3
  101. package/lib/terminal-manager.js +18 -11
  102. package/lib/users-contextual-clay-preferences.js +31 -0
  103. package/lib/users.js +3 -0
  104. package/lib/workspace-assignment-service.js +25 -0
  105. package/lib/workspace-query-mcp-server.js +1 -1
  106. package/lib/ws-request.js +1 -0
  107. package/lib/ws-schema.js +10 -0
  108. package/lib/yoke/claude-hook-installer.js +4 -0
  109. package/package.json +1 -1
@@ -1,454 +1,5 @@
1
- import { createHomeDebateQuestionCard, applyHomeDebateQuestion, resolveHomeDebateQuestion, markHomeDebateQuestionSubmitting } from './home-debate-planning.js';
2
- // Compact Clay conversation hosted inside the global search drawer.
3
- import { getWs } from './ws-ref.js';
4
- import { openProjectSession } from './project-session-navigation.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
- import { isExactSearchControlMessage, replaceSearchPermissions, applySearchPermissionMessage, createSearchPermissionCard } from './search-clay-controls.js';
10
-
11
- var state = store.get('searchClayChatState') || null;
12
- var hostEl = null;
13
- var backHandler = null;
14
- var closeHandler = null;
15
- var sequence = 0;
16
- var progressTimer = null;
17
-
18
- function send(payload) {
19
- var ws = getWs();
20
- if (!ws || ws.readyState !== 1) return false;
21
- ws.send(JSON.stringify(payload));
22
- return true;
23
- }
24
-
25
- function requestId() {
26
- sequence++;
27
- return "search-clay-" + Date.now() + "-" + sequence;
28
- }
29
-
30
- function stopProgressTimer() {
31
- if (!progressTimer) return;
32
- clearInterval(progressTimer);
33
- progressTimer = null;
34
- }
35
-
36
- function startProgressTimer() {
37
- if (progressTimer) return;
38
- progressTimer = setInterval(function () { if (state && state.processing) updateProgressDom(); else stopProgressTimer(); }, 1000);
39
- }
40
-
41
- function progressCopy() {
42
- var elapsed = state && state.startedAt ? Math.max(0, Math.floor((Date.now() - state.startedAt) / 1000)) : 0;
43
- var label = state && state.step ? "Considering what was found" : "Understanding what to look for";
44
- if (state.activity === "searching") label = state.step > 1 ? "Checking another lead" : "Searching your conversations";
45
- else if (state.activity === "reviewing") label = "Reviewing what Clay found";
46
- else if (state.activity === "reconsidering") label = "Trying another search route";
47
- else if (elapsed >= 12) label = "Following related context";
48
- else if (elapsed >= 4) label = "Searching your conversations";
49
- var detail = (state.step ? "Search pass " + state.step + " · " : "") + elapsed + "s";
50
- return { label: label, detail: detail };
51
- }
52
-
53
- function updateProgressDom() {
54
- if (!hostEl) return;
55
- var progress = progressCopy();
56
- var label = hostEl.querySelector(".search-clay-pending-copy strong");
57
- var detail = hostEl.querySelector(".search-clay-pending-copy small");
58
- if (label) label.textContent = progress.label;
59
- if (detail) detail.textContent = progress.detail;
60
- var activeDetails = hostEl.querySelectorAll(".search-clay-activity-item.is-active small");
61
- var activeIndex = 0;
62
- for (var i = 0; state && i < state.activities.length; i++) {
63
- if (state.activities[i].status !== "active") continue;
64
- var elapsed = Math.max(0, Math.floor((Date.now() - state.activities[i].startedAt) / 1000));
65
- state.activities[i].detail = elapsed + "s elapsed";
66
- if (activeDetails[activeIndex]) activeDetails[activeIndex].textContent = state.activities[i].detail;
67
- activeIndex++;
68
- }
69
- }
70
-
71
- function button(icon, label, title, handler) {
72
- var el = document.createElement("button");
73
- el.type = "button";
74
- el.className = "search-clay-icon-button";
75
- el.title = title;
76
- el.setAttribute("aria-label", title);
77
- el.innerHTML = '<i data-lucide="' + icon + '" aria-hidden="true"></i><span>' + label + '</span>';
78
- el.addEventListener("click", handler);
79
- return el;
80
- }
81
-
82
- function clayMate() {
83
- var mates = store.get('cachedMatesList') || [];
84
- for (var i = 0; i < mates.length; i++) {
85
- if (mates[i] && (mates[i].id === state.mateId || mates[i].builtinKey === "clay")) return mates[i];
86
- }
87
- return { name: "Clay", profile: { displayName: "Clay", avatarCustom: "/clay-studio-symbol.png" } };
88
- }
89
-
90
- function appendActivityList(content, items, expanded, onToggle) {
91
- var panel = document.createElement("div");
92
- panel.className = "search-clay-activity-panel " + (expanded ? "is-expanded" : "is-collapsed");
93
- var toggle = document.createElement("button");
94
- toggle.type = "button";
95
- toggle.className = "search-clay-activity-toggle";
96
- toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
97
- toggle.setAttribute("aria-label", (expanded ? "Collapse" : "Expand") + " Clay activity, " + items.length + (items.length === 1 ? " step" : " steps"));
98
- 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>';
99
- toggle.querySelector("small").textContent = items.length + (items.length === 1 ? " step" : " steps");
100
- if (items.length <= 2) {
101
- toggle.disabled = true;
102
- toggle.setAttribute("aria-label", "Clay activity, " + items.length + (items.length === 1 ? " step" : " steps"));
103
- } else {
104
- toggle.addEventListener("click", function () {
105
- expanded = !expanded;
106
- panel.classList.toggle("is-expanded", expanded);
107
- panel.classList.toggle("is-collapsed", !expanded);
108
- toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
109
- toggle.setAttribute("aria-label", (expanded ? "Collapse" : "Expand") + " Clay activity, " + items.length + " steps");
110
- onToggle(expanded);
111
- });
112
- }
113
- panel.appendChild(toggle);
114
- var list = document.createElement("div");
115
- list.className = "search-clay-activity-list";
116
- for (var i = 0; i < items.length; i++) {
117
- var item = document.createElement("div");
118
- item.className = "search-clay-activity-item is-" + items[i].status;
119
- item.innerHTML = '<i data-lucide="' + activityIcon(items[i].status) + '" aria-hidden="true"></i>';
120
- var copy = document.createElement("span");
121
- var label = document.createElement("strong");
122
- label.textContent = items[i].label;
123
- copy.appendChild(label);
124
- var detail = document.createElement("small");
125
- detail.textContent = items[i].detail || "";
126
- copy.appendChild(detail);
127
- item.appendChild(copy);
128
- list.appendChild(item);
129
- }
130
- panel.appendChild(list);
131
- content.appendChild(panel);
132
- }
133
-
134
- function renderMessage(message, streaming) {
135
- if (message.role === "question" && message.flow === "mcp_import") return createHomeDebateQuestionCard(message, respondToImport);
136
- var row;
137
- if (message.role === "user") {
138
- row = createUserBubble({ text: message.text || "" });
139
- row.classList.add("search-clay-message", "search-clay-message-user");
140
- } else {
141
- row = createAssistantBubble({ name: "Clay", avatarUrl: mateAvatarUrl(clayMate(), 28) });
142
- row.classList.add("search-clay-message", "search-clay-message-assistant");
143
- if (streaming) renderAssistantBubbleText(row, message.text || "", false);
144
- else finalizeAssistantBubble(row, message.text || "", false);
145
- if (message.activities && message.activities.length) {
146
- appendActivityList(row.querySelector(".dm-bubble-content"), message.activities, message.activitiesExpanded === true, message.toggleActivities || function (expanded) { message.activitiesExpanded = expanded; });
147
- row.classList.add("search-clay-activity-message");
148
- }
149
- }
150
- row.setAttribute("role", "article");
151
- row.setAttribute("aria-label", message.role === "user" ? "Your message" : "Message from Clay");
152
- return row;
153
- }
154
-
155
- function activityIcon(status) {
156
- if (status === "done") return "circle-check";
157
- if (status === "error") return "circle-alert";
158
- return "loader-circle";
159
- }
160
-
161
- function updateActivity(message) {
162
- var id = message.activityId || message.phase + ":" + (message.step || 0);
163
- var activity = null;
164
- for (var i = 0; i < state.activities.length; i++) if (state.activities[i].id === id) activity = state.activities[i];
165
- if (!activity) {
166
- activity = { id: id, label: message.label || "Working", detail: "0s elapsed", status: message.status || "active", startedAt: Date.now() };
167
- state.activities.push(activity);
168
- } else if (message.status === "active") {
169
- activity.label = message.label || activity.label;
170
- activity.status = "active";
171
- } else {
172
- var elapsed = Math.max(0, Math.floor((Date.now() - activity.startedAt) / 1000));
173
- activity.detail = (message.label || "Complete") + (elapsed ? " \u00b7 " + elapsed + "s" : "");
174
- activity.status = message.status || "done";
175
- }
176
- if (state.activities.length > 12) state.activities = state.activities.slice(state.activities.length - 12);
177
- }
178
-
179
- function takeActivities(finalLabel, finalStatus) {
180
- var now = Date.now();
181
- for (var i = 0; i < state.activities.length; i++) {
182
- if (state.activities[i].status !== "active") continue;
183
- var elapsed = Math.max(0, Math.floor((now - state.activities[i].startedAt) / 1000));
184
- state.activities[i].status = finalStatus || "done";
185
- state.activities[i].detail = finalLabel + (elapsed ? " \u00b7 " + elapsed + "s" : "");
186
- }
187
- var items = state.activities.slice();
188
- state.activities = [];
189
- return items;
190
- }
191
-
192
- function submit(input, sendButton) {
193
- var text = input.value.trim();
194
- if (!state || !text || !state.sessionId) return;
195
- if (!send({ type: "home_mate_send", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, text: text })) return;
196
- if (state.processing) {
197
- state.queuedUsers.push({ role: "user", text: text });
198
- } else {
199
- state.messages.push({ role: "user", text: text });
200
- state.processing = true;
201
- state.awaitingCompletion = true;
202
- state.stream = "";
203
- state.activity = "thinking";
204
- state.step = 0;
205
- state.activities = [];
206
- state.activityExpanded = false;
207
- state.startedAt = Date.now();
208
- }
209
- state.pendingTurns++;
210
- state.updatedAt = Date.now();
211
- startProgressTimer();
212
- state.draft = "";
213
- input.value = "";
214
- sendButton.disabled = true;
215
- render();
216
- }
217
-
218
- function stopSearchClayChat() {
219
- if (!state || !state.processing || state.stopping || !state.mateId || !state.sessionId) return false;
220
- if (!send({ type: "home_mate_stop", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId })) return false;
221
- state.stopping = true;
222
- render();
223
- return true;
224
- }
225
-
226
- function respondToImport(message, action, opener, answers) {
227
- if (!state || message.status !== 'pending') return;
228
- if (!send({ type: 'home_mcp_import_response', mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, toolId: message.toolId, answers: answers || {} })) return;
229
- markHomeDebateQuestionSubmitting(message, opener);
230
- store.set({ searchClayChatState: state }); render();
231
- }
232
-
233
- function respondToPermission(permission, decision) {
234
- if (!state || permission.status !== "pending") return false;
235
- if (!send({ type: "home_mate_permission_response", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, permissionRequestId: permission.permissionRequestId, decision: decision })) return false;
236
- permission.status = "answering";
237
- render();
238
- return true;
239
- }
240
-
241
- function expand() {
242
- if (!state || !state.mateId || !state.localId) return;
243
- if (closeHandler) closeHandler();
244
- openProjectSession('mate-' + state.mateId, state.localId);
245
- }
246
-
247
- function render() {
248
- if (!hostEl || !state) return;
249
- var shell = hostEl.querySelector(".search-clay-chat");
250
- var transcript = shell ? shell.querySelector(".search-clay-transcript") : null;
251
- var previousTop = transcript ? transcript.scrollTop : 0;
252
- var follow = !transcript || transcript.scrollHeight - transcript.scrollTop - transcript.clientHeight < 40;
253
- if (!shell) {
254
- hostEl.innerHTML = "";
255
- shell = document.createElement("section");
256
- shell.className = "search-clay-chat";
257
- shell.setAttribute("aria-label", "Chat with Clay");
258
- var header = document.createElement("header");
259
- header.className = "search-clay-header";
260
- var identity = document.createElement("div");
261
- identity.className = "search-clay-identity";
262
- identity.innerHTML = '<span><strong>Clay</strong><small>Workspace search</small></span>';
263
- header.appendChild(identity);
264
- var actions = document.createElement("div");
265
- actions.className = "search-clay-header-actions";
266
- actions.appendChild(button("search", "Search", "Back to search", function () { detachSearchClayChat(); if (backHandler) backHandler(); }));
267
- actions.appendChild(button("maximize-2", "Expand", "Open this conversation in project chat", expand));
268
- header.appendChild(actions);
269
- shell.appendChild(header);
270
- transcript = document.createElement("div");
271
- transcript.className = "search-clay-transcript";
272
- transcript.setAttribute("aria-live", "polite");
273
- shell.appendChild(transcript);
274
- var composer = document.createElement("div");
275
- composer.className = "search-clay-composer";
276
- var freshInput = document.createElement("textarea");
277
- freshInput.rows = 1;
278
- freshInput.value = state.draft || "";
279
- freshInput.setAttribute("aria-label", "Message Clay");
280
- var freshSendButton = document.createElement("button");
281
- freshSendButton.type = "button";
282
- freshSendButton.className = "search-clay-send";
283
- freshSendButton.setAttribute("aria-label", "Send to Clay");
284
- freshSendButton.innerHTML = '<i data-lucide="arrow-up" aria-hidden="true"></i>';
285
- freshInput.addEventListener("input", function () { state.draft = freshInput.value; freshSendButton.disabled = state.processing ? (!state.sessionId || state.stopping) : (!state.sessionId || !freshInput.value.trim()); });
286
- freshInput.addEventListener("keydown", function (event) { if (event.key === "Enter" && !event.shiftKey && !event.isComposing) { event.preventDefault(); submit(freshInput, freshSendButton); } });
287
- freshSendButton.addEventListener("click", function () { if (state.processing) stopSearchClayChat(); else submit(freshInput, freshSendButton); });
288
- composer.appendChild(freshInput);
289
- composer.appendChild(freshSendButton);
290
- shell.appendChild(composer);
291
- hostEl.appendChild(shell);
292
- }
293
- transcript.innerHTML = "";
294
- for (var i = 0; i < state.messages.length; i++) transcript.appendChild(renderMessage(state.messages[i], false));
295
- 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));
296
- for (var p = 0; p < state.permissions.length; p++) transcript.appendChild(createSearchPermissionCard(state.permissions[p], respondToPermission));
297
- if (state.processing && !state.stream && !state.activities.length) {
298
- var progress = progressCopy();
299
- var pending = document.createElement("div");
300
- pending.className = "search-clay-pending";
301
- pending.setAttribute("role", "status");
302
- var pendingCopy = document.createElement("span");
303
- pendingCopy.className = "search-clay-pending-copy";
304
- var pendingLabel = document.createElement("strong");
305
- pendingLabel.textContent = progress.label;
306
- var pendingDetail = document.createElement("small");
307
- pendingDetail.textContent = progress.detail;
308
- pendingCopy.appendChild(pendingLabel);
309
- pendingCopy.appendChild(pendingDetail);
310
- pending.appendChild(pendingCopy);
311
- var dots = document.createElement("span");
312
- dots.className = "search-clay-dots";
313
- dots.setAttribute("aria-hidden", "true");
314
- dots.innerHTML = "<i></i><i></i><i></i>";
315
- pending.appendChild(dots);
316
- transcript.appendChild(pending);
317
- }
318
- for (var q = 0; q < state.queuedUsers.length; q++) transcript.appendChild(renderMessage(state.queuedUsers[q], false));
319
- var input = shell.querySelector(".search-clay-composer textarea");
320
- var sendButton = shell.querySelector(".search-clay-send");
321
- input.placeholder = state.sessionId ? "Message Clay…" : "Starting Clay…";
322
- input.disabled = !state.sessionId;
323
- sendButton.disabled = state.processing ? (!state.sessionId || state.stopping) : (!state.sessionId || !input.value.trim());
324
- sendButton.setAttribute("aria-label", state.processing ? (state.stopping ? "Stopping Clay" : "Stop Clay") : "Send to Clay");
325
- sendButton.innerHTML = state.processing ? '<i data-lucide="square" aria-hidden="true"></i>' : '<i data-lucide="arrow-up" aria-hidden="true"></i>';
326
- var expandButton = shell.querySelector('.search-clay-icon-button[aria-label="Open this conversation in project chat"]');
327
- if (expandButton) expandButton.disabled = !state.localId;
328
- refreshIcons();
329
- transcript.scrollTop = follow ? transcript.scrollHeight : previousTop;
330
- }
331
-
332
- export function startSearchClayChat(query, host, onBack, onClose) {
333
- var clean = typeof query === "string" ? query.trim().slice(0, 12000) : "";
334
- if (!clean) return false;
335
- hostEl = host;
336
- backHandler = onBack;
337
- closeHandler = onClose;
338
- var startedAt = Date.now();
339
- state = { requestId: requestId(), mateId: null, sessionId: null, localId: null, messages: [{ role: "user", text: clean }], queuedUsers: [], pendingTurns: 1, activities: [], permissions: [], activityExpanded: false, stream: "", draft: "", processing: true, stopping: false, awaitingCompletion: true, activity: "thinking", step: 0, startedAt: startedAt, updatedAt: startedAt, query: clean };
340
- store.set({ searchClayChatState: state });
341
- startProgressTimer();
342
- render();
343
- if (!send({ type: "home_clay_ask", requestId: state.requestId, text: clean })) {
344
- state.processing = false;
345
- state.awaitingCompletion = false;
346
- state.pendingTurns = 0;
347
- stopProgressTimer();
348
- state.messages.push({ role: "assistant", text: "Clay is offline. Reconnect and try again." });
349
- render();
350
- }
351
- return true;
352
- }
353
-
354
- export function attachSearchClayChat(host, onBack, onClose) {
355
- if (!state) return false;
356
- hostEl = host;
357
- backHandler = onBack;
358
- closeHandler = onClose;
359
- if (state.processing) send({ type: "home_clay_ask", requestId: state.requestId, text: state.query });
360
- render();
361
- return true;
362
- }
363
-
364
- export function getSearchClayChatSummary() {
365
- if (!state) return null;
366
- var preview = "";
367
- var candidates = state.queuedUsers.length ? state.queuedUsers : state.messages;
368
- for (var i = candidates.length - 1; i >= 0; i--) {
369
- if (!candidates[i] || typeof candidates[i].text !== "string" || !candidates[i].text.trim()) continue;
370
- preview = candidates[i].text.replace(/\s+/g, " ").trim().slice(0, 120);
371
- break;
372
- }
373
- return { sessionId: state.sessionId, processing: state.processing === true, preview: preview, updatedAt: state.updatedAt || state.startedAt || null };
374
- }
375
-
376
- export function detachSearchClayChat() { hostEl = null; }
377
-
378
- export function handleSearchClayMessage(msg) {
379
- if (!isExactSearchControlMessage(state, msg)) return false;
380
- state.updatedAt = Date.now();
381
- if (msg.mateId) state.mateId = msg.mateId;
382
- if (msg.type === "home_mate_session_identity" && msg.sessionId) state.sessionId = msg.sessionId;
383
- if (msg.type === 'home_mcp_import_question') {
384
- if (state.stream) { state.messages.push({ role: 'assistant', text: state.stream }); state.stream = ''; }
385
- state.messages = applyHomeDebateQuestion(state.messages, msg);
386
- } else if (msg.type === 'home_mcp_import_question_resolved') {
387
- state.messages = resolveHomeDebateQuestion(state.messages, msg);
388
- } else if (msg.type === "home_clay_activity") {
389
- if (!state.awaitingCompletion) return true;
390
- state.activity = msg.phase || state.activity;
391
- state.step = typeof msg.step === "number" ? msg.step : state.step;
392
- updateActivity(msg);
393
- } else if (msg.type === "home_mate_history") {
394
- state.sessionId = msg.sessionId || state.sessionId;
395
- state.localId = typeof msg.localId === "number" ? msg.localId : state.localId;
396
- state.messages = Array.isArray(msg.messages) ? msg.messages.filter(function (item) { return item && (item.role === "user" || item.role === "assistant" || item.role === "question" && item.flow === "mcp_import"); }).map(function (item) { return item.role === "question" ? Object.assign({}, item) : { role: item.role, text: item.text || "" }; }) : state.messages;
397
- state.processing = msg.isProcessing === true;
398
- state.awaitingCompletion = state.processing;
399
- state.pendingTurns = state.processing ? Math.max(1, state.pendingTurns) : 0;
400
- if (state.processing) startProgressTimer();
401
- state.stream = "";
402
- state.stopping = false;
403
- replaceSearchPermissions(state, msg.pendingPermissions);
404
- } else if (msg.type === "home_mate_permission_request" || msg.type === "home_mate_permission_resolved" || msg.type === "home_mate_permission_cancelled") {
405
- applySearchPermissionMessage(state, msg);
406
- } else if (msg.type === "home_mate_stopping") {
407
- state.stopping = true;
408
- } else if (msg.type === "home_mate_delta") {
409
- if (!state.awaitingCompletion) return true;
410
- state.sessionId = msg.sessionId || state.sessionId;
411
- state.processing = true;
412
- state.pendingTurns = Math.max(1, state.pendingTurns);
413
- state.stream += msg.text || "";
414
- } else if (msg.type === "home_mate_done") {
415
- if (!state.awaitingCompletion) return true;
416
- var completedActivities = takeActivities("Response complete", "done");
417
- if (state.stream || msg.text || completedActivities.length) state.messages.push({ role: "assistant", text: state.stream || msg.text || "", activities: completedActivities, activitiesExpanded: state.activityExpanded });
418
- if (state.queuedUsers.length) state.messages.push(state.queuedUsers.shift());
419
- state.stream = "";
420
- state.activityExpanded = false;
421
- state.pendingTurns = Math.max(0, state.pendingTurns - 1);
422
- state.processing = state.pendingTurns > 0;
423
- state.stopping = false;
424
- state.awaitingCompletion = state.processing;
425
- state.startedAt = Date.now();
426
- if (!state.processing) stopProgressTimer();
427
- } else if (msg.type === "home_mate_error") {
428
- if (!state.awaitingCompletion) return true;
429
- var failedActivities = takeActivities("Stopped", "error");
430
- state.stream = "";
431
- state.messages.push({ role: "assistant", text: msg.text || "Clay could not complete that search.", activities: failedActivities, activitiesExpanded: state.activityExpanded });
432
- if (state.queuedUsers.length) state.messages.push(state.queuedUsers.shift());
433
- state.pendingTurns = Math.max(0, state.pendingTurns - 1);
434
- state.activityExpanded = false;
435
- state.processing = state.pendingTurns > 0;
436
- state.stopping = false;
437
- state.awaitingCompletion = state.processing;
438
- state.startedAt = Date.now();
439
- if (!state.processing) stopProgressTimer();
440
- }
441
- render();
442
- return true;
443
- }
444
-
445
- store.subscribe(function (appState, previous) {
446
- if (!state || appState.connected === previous.connected) return;
447
- if (!appState.connected) {
448
- replaceSearchPermissions(state, []);
449
- state.stopping = false;
450
- render();
451
- return;
452
- }
453
- if (state.processing) send({ type: "home_clay_ask", requestId: state.requestId, text: state.query });
454
- });
1
+ // Search is an entry point. The sole chat renderer is the contextual Mate pane.
2
+ import { startContextualSearch, handleContextualSearchMessage, retryContextualSearch } from './contextual-search.js';
3
+ export function startSearchClayChat(query) { return startContextualSearch(query); }
4
+ export function handleSearchClayMessage(message) { return handleContextualSearchMessage(message); }
5
+ export function retrySearchClayAsk() { return retryContextualSearch(); }
@@ -1,3 +1,4 @@
1
+ import { registerStudioPaint } from './clay-studio-render.js';
1
2
  import { browserLinkTarget, initBrowserLinks } from './clay-browser-links.js';
2
3
  import { browserSettingsHtml, bindBrowserSettings, renderBrowserSettings, closeBrowserSettings, handleBrowserSettingsMessage } from './shared-browser-settings.js';
3
4
  import { renderBrowserTabs } from './shared-browser-tabs.js';
@@ -229,6 +230,7 @@ export function handleSharedBrowserMessage(msg) {
229
230
  var canvas = panel().querySelector('canvas'); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight;
230
231
  canvas.getContext('2d').drawImage(image, 0, 0);
231
232
  ui({ decoding: false, hasFrame: true }); render();
233
+ registerStudioPaint(msg, canvas, null, canvas);
232
234
  }
233
235
  };
234
236
  image.onerror = function () { if (store.get('activeSessionId') === sessionId) ui({ decoding: false }); };
@@ -1,4 +1,5 @@
1
- import { openMateWorkspace, isMateWorkspace } from './project-mate-navigation.js';
1
+ import { contextualMate } from './contextual-mate-identity.js';
2
+ import { activateContextualMate, isMateWorkspace } from './project-mate-navigation.js';
2
3
  import { appendGithubWork } from './session-github.js';
3
4
  // sidebar-mobile.js - Mobile sheet overlays, tab bar, and mobile-specific rendering
4
5
  // Extracted from sidebar.js (PR-38)
@@ -258,7 +259,7 @@ function renderSheetSessions(listEl) {
258
259
  }
259
260
 
260
261
  var visibleChipMates = getCachedMates().filter(function (m) {
261
- return m && !m.archived;
262
+ return m && !m.archived && contextualMate(m.id);
262
263
  });
263
264
  var sortedChipMates = visibleChipMates.sort(function (a, b) {
264
265
  var aBuiltin = a.builtinKey ? 1 : 0;
@@ -327,7 +328,7 @@ function renderSheetSessions(listEl) {
327
328
  renderMobileSessionsInto(sessionListEl);
328
329
  } else if (type === "mate") {
329
330
  closeMobileSheet();
330
- openMateWorkspace(mateId);
331
+ activateContextualMate(mateId);
331
332
  }
332
333
 
333
334
  refreshIcons();
@@ -822,6 +822,7 @@ function createIconItem(p, currentSlug) {
822
822
  var el = document.createElement("a");
823
823
  var isActive = p.slug === currentSlug && !currentDmUserId;
824
824
  el.className = "icon-strip-item" + (isActive ? " active" : "");
825
+ el.dataset.controlRuntime = p.controlRuntimeId || "";
825
826
  el.href = "/p/" + p.slug + "/";
826
827
  el.dataset.slug = p.slug;
827
828
 
@@ -826,6 +826,7 @@ function renderSessionItem(s, options) {
826
826
  el.className = "session-item" + (displaySession.active ? " active" : "") + (isMatch ? " search-match" : "");
827
827
  if (itemOptions.worker) el.classList.add("session-worker-item");
828
828
  el.dataset.sessionId = s.id;
829
+ el.dataset.controlSessionOrigin = s.sessionOriginId || '';
829
830
 
830
831
  var textSpan = document.createElement("span");
831
832
  textSpan.className = "session-item-text";
@@ -1,3 +1,5 @@
1
+ import { adoptDocumentSource } from './clay-studio-document.js';
2
+ import { trustedStudioFrame } from './clay-studio-render.js';
1
3
  import { handlePaneBrowserMessage } from './pane-browser-bridge.js';
2
4
  import { openWireframe } from './wireframe-workbench.js';
3
5
  // Two-pane iframe shell for the split-view spike.
@@ -144,22 +146,11 @@ function handlePaneMessage(event) {
144
146
  if (event.origin !== window.location.origin) return;
145
147
  var msg = event.data;
146
148
  if (!msg || !host) return;
147
- if (msg.type === "clay-pane-present-wireframe") {
148
- var frames = host.querySelectorAll(".split-pane-frame");
149
- var split = store.get('splitPanes');
150
- var trusted = Array.prototype.some.call(frames, function (frame) {
151
- if (frame.contentWindow !== event.source || frame.dataset.projectSlug !== msg.projectSlug || String(frame.dataset.sessionId) !== String(msg.sessionId) || msg.projectSlug !== store.get('currentSlug')) return false;
152
- return split && split.panes.some(function (pane) {
153
- var expected = new URL('/p/' + encodeURIComponent(pane.slug) + '/?pane=1&session=' + encodeURIComponent(pane.sessionId), window.location.href).href;
154
- return pane.slug === msg.projectSlug && String(pane.sessionId) === String(msg.sessionId) && frame.src === expected;
155
- });
156
- });
157
- if (trusted && msg.message) openWireframe(msg.message);
158
- return;
159
- }
160
- if (msg.type === "clay-pane-present-markdown") {
161
- var present = msg.message;
162
- if (present) presentMarkdownEdit(present);
149
+ if (msg.type === "clay-pane-present-wireframe" || msg.type === "clay-pane-present-markdown") {
150
+ if (msg.message && !msg.message.controlPresentation && trustedStudioFrame(event, msg)) {
151
+ if (msg.type === "clay-pane-present-wireframe") openWireframe(msg.message);
152
+ else { adoptDocumentSource(event, msg, trustedStudioFrame(event, msg)); presentMarkdownEdit(msg.message); }
153
+ }
163
154
  return;
164
155
  }
165
156
  // Both panes can report auth_required for their own sessions. The shell runs
@@ -25,6 +25,7 @@ function bindComposer(id, inputId, wrapperId) {
25
25
  if (!button || !input || !wrapper || button.dataset.speechBound) return;
26
26
  var composer = document.getElementById(id === 'stt-btn' ? 'input-row' : 'home-mate-chat-composer') || wrapper;
27
27
  var toolbar = document.getElementById(id === 'stt-btn' ? 'input-bottom' : 'home-mate-chat-composer') || composer;
28
+ button.addEventListener('click', refreshSpeechBrowser);
28
29
  button.dataset.speechBound = 'true'; button.classList.add('stt-button');
29
30
  var group = document.createElement('span'); group.className = 'stt-controls';
30
31
  button.before(group); group.appendChild(button);
@@ -32,7 +33,7 @@ function bindComposer(id, inputId, wrapperId) {
32
33
  picker.className = 'stt-picker-toggle'; picker.innerHTML = iconHtml('chevron-down');
33
34
  picker.setAttribute('aria-label', 'Choose voice model'); picker.setAttribute('aria-haspopup', 'dialog'); picker.setAttribute('aria-expanded', 'false');
34
35
  picker.title = 'Choose voice model'; group.appendChild(picker);
35
- picker.addEventListener('click', function () { openSpeechPicker(picker); });
36
+ picker.addEventListener('click', function () { refreshSpeechBrowser(); openSpeechPicker(picker); });
36
37
  var row = document.createElement('div'); row.className = 'stt-progress'; row.hidden = true;
37
38
  var status = document.createElement('span'); status.id = id + '-status'; status.className = 'stt-status'; status.setAttribute('role', 'status');
38
39
  var timer = document.createElement('span'); timer.className = 'stt-timer'; timer.setAttribute('aria-hidden', 'true');
@@ -70,7 +71,7 @@ function bindComposer(id, inputId, wrapperId) {
70
71
  function render() {
71
72
  var active = store.get('speechActive'); var own = !!active && active.id === id;
72
73
  var model = voiceModel(); var provider = voiceProvider(model.provider);
73
- var browser = model.provider === 'browser' ? browserSpeechInfo() : null;
74
+ var browser = model.provider === 'browser' ? store.get('speechBrowserIdentity') : null;
74
75
  var pending = own && active.model && active.phase !== 'recording';
75
76
  var autoSend = own && !!active.autoSend;
76
77
  composer.classList.toggle('stt-recording', own);
@@ -117,7 +118,11 @@ function bindComposer(id, inputId, wrapperId) {
117
118
  observer.observe(input, { attributes: true, attributeFilter: ['disabled', 'readonly'] });
118
119
  render();
119
120
  }
121
+ // Browser identity may read layout; refresh on entry and interaction, not transcript updates.
122
+ function refreshSpeechBrowser() { store.set({ speechBrowserIdentity: browserSpeechInfo() }); }
120
123
  export function initSTT() {
124
+ refreshSpeechBrowser();
125
+ window.addEventListener('focus', refreshSpeechBrowser);
121
126
  initSpeechLifecycle(); initSpeechPicker();
122
127
  bindComposer('stt-btn', 'input', 'input-wrapper');
123
128
  bindComposer('home-stt-btn', 'home-mate-chat-input', 'home-mate-chat-composer-frame');
@@ -1,3 +1,4 @@
1
+ import { store } from './store.js';
1
2
  import { iconHtml, refreshIcons } from './icons.js';
2
3
  import { closeSidebar } from './sidebar.js';
3
4
  import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
@@ -538,6 +539,8 @@ function renderTabBar() {
538
539
  (function (t) {
539
540
  var el = document.createElement("div");
540
541
  el.className = "terminal-tab";
542
+ el.dataset.controlTerminalId = String(t.id);
543
+ el.dataset.controlRuntime = store.get("controlRuntimeId") || "";
541
544
  if (t.id === activeTabId) el.classList.add("active");
542
545
  if (t.exited) el.classList.add("exited");
543
546
 
@@ -696,6 +699,7 @@ function updateTerminalBadge() {
696
699
  // --- Handle server messages ---
697
700
 
698
701
  export function handleTermList(msg) {
702
+ store.set({ clayControlTerminalViews: (msg.terminals || []).map(function (item) { return { id: item.id, identity: item.controlIdentity, revision: item.controlRevision, runtimeId: store.get('controlRuntimeId') }; }) });
699
703
  // TUI session terminals are owned by session-tui-view.js and must not
700
704
  // appear in the bottom terminal panel. Filter them out before any tab
701
705
  // bookkeeping so the panel never tries to render or auto-attach them.
@@ -762,7 +766,11 @@ export function handleTermCreated(msg) {
762
766
  }
763
767
  }
764
768
 
769
+ export function handleTerminalProjection(msg) {
770
+ if (msg.controlIdentity && msg.controlRevision) store.set({ clayControlTerminalViews: (store.get('clayControlTerminalViews') || []).map(function (item) { return item.id === msg.id && item.identity === msg.controlIdentity ? Object.assign({}, item, { revision: msg.controlRevision }) : item; }) });
771
+ }
765
772
  export function handleTermOutput(msg) {
773
+ handleTerminalProjection(msg);
766
774
  if (!msg.id) return;
767
775
  var tab = tabs.get(msg.id);
768
776
  if (tab && tab.xterm && msg.data) {
@@ -771,6 +779,7 @@ export function handleTermOutput(msg) {
771
779
  }
772
780
 
773
781
  export function handleTermResized(msg) {
782
+ handleTerminalProjection(msg);
774
783
  if (!msg.id) return;
775
784
  var tab = tabs.get(msg.id);
776
785
  if (tab && tab.xterm && msg.cols > 0 && msg.rows > 0) {
@@ -1,3 +1,4 @@
1
+ import { renderProductReview } from './product-review.js';
1
2
  import { clayToolGroupSummary } from './clay-tool-presentation.js';
2
3
  import { applyClayToolItem, applyClayToolResult, finishClayToolItem, findEndedClayTool, clayToolResultGroup, bindToolGroupDisclosure, syncToolGroupDisclosure, syncClayToolGroup } from './clay-tool-activity.js';
3
4
  import { escapeHtml, copyToClipboard } from './utils.js';
@@ -298,6 +299,7 @@ export function renderAskUserQuestion(toolId, input) {
298
299
  var qText = document.createElement("div");
299
300
  qText.className = "ask-user-question-text";
300
301
  qText.textContent = q.question || "";
302
+ if (renderProductReview(qText, q)) qDiv.dataset.answerLabel = q.review.action;
301
303
  qDiv.appendChild(qText);
302
304
 
303
305
  var optionsDiv = document.createElement("div");
@@ -476,7 +478,8 @@ function showAnswerSummary(container, questions, answers) {
476
478
  var row = document.createElement("div");
477
479
  row.className = "ask-user-answer-row";
478
480
  var qi = parseInt(key, 10);
479
- var label = (questions && questions[qi] && questions[qi].question) ? questions[qi].question : "Answer";
481
+ var questionNode = container.querySelectorAll(".ask-user-question")[qi];
482
+ var label = questionNode && questionNode.dataset.answerLabel || (questions && questions[qi] && (questions[qi].header || questions[qi].question)) || "Answer";
480
483
  row.innerHTML = '<span class="ask-user-answer-label">' + escapeHtml(label) + '</span>' +
481
484
  '<span class="ask-user-answer-value">' + escapeHtml(String(answers[key])) + '</span>';
482
485
  summary.appendChild(row);