clay-server 5.2.0-beta.2 → 5.2.0-beta.4

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 (205) hide show
  1. package/lib/builtin-mates.js +2 -2
  2. package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
  3. package/lib/dm-digest-cursor.js +120 -0
  4. package/lib/knowledge-db-import-validation.js +46 -0
  5. package/lib/knowledge-db-import.js +98 -0
  6. package/lib/knowledge-db-service.js +18 -30
  7. package/lib/knowledge-db-storage-nedb-maintenance.js +99 -0
  8. package/lib/knowledge-db-storage-nedb.js +66 -0
  9. package/lib/knowledge-db-storage.js +72 -0
  10. package/lib/knowledge-db-validation.js +0 -0
  11. package/lib/knowledge-file-store.js +7 -1
  12. package/lib/mate-avatar-catalog.js +12 -0
  13. package/lib/mate-avatar-migration.js +41 -0
  14. package/lib/mate-knowledge-mate-bound.js +53 -0
  15. package/lib/mate-knowledge-mcp-server.js +55 -8
  16. package/lib/mate-knowledge-service.js +9 -30
  17. package/lib/mate-knowledge-text-tools.js +106 -0
  18. package/lib/mates.js +14 -3
  19. package/lib/mcp-agent-tools.js +117 -0
  20. package/lib/mcp-connection-store.js +119 -15
  21. package/lib/mcp-import-credentials.js +31 -0
  22. package/lib/mcp-local-transport.js +13 -0
  23. package/lib/mcp-oauth.js +8 -6
  24. package/lib/mcp-query-bridge.js +30 -0
  25. package/lib/mcp-remote-connections.js +74 -20
  26. package/lib/mcp-source-adapters.js +91 -0
  27. package/lib/mcp-source-context.js +42 -0
  28. package/lib/mcp-source-discovery.js +169 -0
  29. package/lib/mcp-source-plugins.js +68 -0
  30. package/lib/mcp-source-restrictions.js +47 -0
  31. package/lib/mcp-tool-policy.js +34 -0
  32. package/lib/project-ask-user.js +6 -4
  33. package/lib/project-debate.js +1 -0
  34. package/lib/project-mate-interaction.js +40 -62
  35. package/lib/project-mcp.js +54 -16
  36. package/lib/project-session-document.js +8 -3
  37. package/lib/project-session-github.js +16 -7
  38. package/lib/project-session-linear.js +14 -6
  39. package/lib/project-session-notes.js +7 -3
  40. package/lib/project-sessions.js +6 -2
  41. package/lib/project-shared-browser.js +56 -12
  42. package/lib/project.js +49 -34
  43. package/lib/public/app.js +5 -0
  44. package/lib/public/appearance-bootstrap.js +14 -0
  45. package/lib/public/css/clay-workspace.css +29 -0
  46. package/lib/public/css/home-debate-planning.css +2 -0
  47. package/lib/public/css/mate-avatar-gallery.css +12 -0
  48. package/lib/public/css/mate-conversation.css +10 -5
  49. package/lib/public/css/session-github.css +43 -1
  50. package/lib/public/css/shared-browser.css +35 -1
  51. package/lib/public/index.html +10 -4
  52. package/lib/public/mate-avatars/B01.png +0 -0
  53. package/lib/public/mate-avatars/B02.png +0 -0
  54. package/lib/public/mate-avatars/B03.png +0 -0
  55. package/lib/public/mate-avatars/B04.png +0 -0
  56. package/lib/public/mate-avatars/B05.png +0 -0
  57. package/lib/public/mate-avatars/B06.png +0 -0
  58. package/lib/public/mate-avatars/B07.png +0 -0
  59. package/lib/public/mate-avatars/B08.png +0 -0
  60. package/lib/public/mate-avatars/B09.png +0 -0
  61. package/lib/public/mate-avatars/B10.png +0 -0
  62. package/lib/public/mate-avatars/B11.png +0 -0
  63. package/lib/public/mate-avatars/B12.png +0 -0
  64. package/lib/public/mate-avatars/B13.png +0 -0
  65. package/lib/public/mate-avatars/B14.png +0 -0
  66. package/lib/public/mate-avatars/B15.png +0 -0
  67. package/lib/public/mate-avatars/B16.png +0 -0
  68. package/lib/public/mate-avatars/B17.png +0 -0
  69. package/lib/public/mate-avatars/B18.png +0 -0
  70. package/lib/public/mate-avatars/B19.png +0 -0
  71. package/lib/public/mate-avatars/B20.png +0 -0
  72. package/lib/public/mate-avatars/B21.png +0 -0
  73. package/lib/public/mate-avatars/B22.png +0 -0
  74. package/lib/public/mate-avatars/B23.png +0 -0
  75. package/lib/public/mate-avatars/B24.png +0 -0
  76. package/lib/public/mate-avatars/B25.png +0 -0
  77. package/lib/public/mate-avatars/B26.png +0 -0
  78. package/lib/public/mate-avatars/B27.png +0 -0
  79. package/lib/public/mate-avatars/B28.png +0 -0
  80. package/lib/public/mate-avatars/B29.png +0 -0
  81. package/lib/public/mate-avatars/B30.png +0 -0
  82. package/lib/public/mate-avatars/B31.png +0 -0
  83. package/lib/public/mate-avatars/B32.png +0 -0
  84. package/lib/public/mate-avatars/B33.png +0 -0
  85. package/lib/public/mate-avatars/B34.png +0 -0
  86. package/lib/public/mate-avatars/B35.png +0 -0
  87. package/lib/public/mate-avatars/B36.png +0 -0
  88. package/lib/public/mate-avatars/B37.png +0 -0
  89. package/lib/public/mate-avatars/B38.png +0 -0
  90. package/lib/public/mate-avatars/B39.png +0 -0
  91. package/lib/public/mate-avatars/B40.png +0 -0
  92. package/lib/public/mate-avatars/B41.png +0 -0
  93. package/lib/public/mate-avatars/B42.png +0 -0
  94. package/lib/public/mate-avatars/B43.png +0 -0
  95. package/lib/public/mate-avatars/B44.png +0 -0
  96. package/lib/public/mate-avatars/B45.png +0 -0
  97. package/lib/public/mate-avatars/B46.png +0 -0
  98. package/lib/public/mate-avatars/B47.png +0 -0
  99. package/lib/public/mate-avatars/B48.png +0 -0
  100. package/lib/public/mate-avatars/C01.png +0 -0
  101. package/lib/public/mate-avatars/C02.png +0 -0
  102. package/lib/public/mate-avatars/C03.png +0 -0
  103. package/lib/public/mate-avatars/C04.png +0 -0
  104. package/lib/public/mate-avatars/C05.png +0 -0
  105. package/lib/public/mate-avatars/C06.png +0 -0
  106. package/lib/public/mate-avatars/C07.png +0 -0
  107. package/lib/public/mate-avatars/C08.png +0 -0
  108. package/lib/public/mate-avatars/C09.png +0 -0
  109. package/lib/public/mate-avatars/C10.png +0 -0
  110. package/lib/public/mate-avatars/C11.png +0 -0
  111. package/lib/public/mate-avatars/C12.png +0 -0
  112. package/lib/public/mate-avatars/C13.png +0 -0
  113. package/lib/public/mate-avatars/C14.png +0 -0
  114. package/lib/public/mate-avatars/C15.png +0 -0
  115. package/lib/public/mate-avatars/C16.png +0 -0
  116. package/lib/public/mate-avatars/C17.png +0 -0
  117. package/lib/public/mate-avatars/C18.png +0 -0
  118. package/lib/public/mate-avatars/C19.png +0 -0
  119. package/lib/public/mate-avatars/C20.png +0 -0
  120. package/lib/public/mate-avatars/C21.png +0 -0
  121. package/lib/public/mate-avatars/C22.png +0 -0
  122. package/lib/public/mate-avatars/C23.png +0 -0
  123. package/lib/public/mate-avatars/C24.png +0 -0
  124. package/lib/public/mate-avatars/C25.png +0 -0
  125. package/lib/public/mate-avatars/C26.png +0 -0
  126. package/lib/public/mate-avatars/C27.png +0 -0
  127. package/lib/public/mate-avatars/C28.png +0 -0
  128. package/lib/public/mate-avatars/C29.png +0 -0
  129. package/lib/public/mate-avatars/C30.png +0 -0
  130. package/lib/public/mate-avatars/C31.png +0 -0
  131. package/lib/public/mate-avatars/C32.png +0 -0
  132. package/lib/public/mate-avatars/C33.png +0 -0
  133. package/lib/public/mate-avatars/C34.png +0 -0
  134. package/lib/public/mate-avatars/C35.png +0 -0
  135. package/lib/public/mate-avatars/C36.png +0 -0
  136. package/lib/public/mate-avatars/C37.png +0 -0
  137. package/lib/public/mate-avatars/C38.png +0 -0
  138. package/lib/public/mate-avatars/C39.png +0 -0
  139. package/lib/public/mate-avatars/C40.png +0 -0
  140. package/lib/public/mate-avatars/C41.png +0 -0
  141. package/lib/public/mate-avatars/C42.png +0 -0
  142. package/lib/public/mate-avatars/C43.png +0 -0
  143. package/lib/public/mate-avatars/C44.png +0 -0
  144. package/lib/public/mate-avatars/C45.png +0 -0
  145. package/lib/public/mate-avatars/C46.png +0 -0
  146. package/lib/public/mate-avatars/C47.png +0 -0
  147. package/lib/public/mate-avatars/C48.png +0 -0
  148. package/lib/public/mate-avatars/catalog.json +482 -0
  149. package/lib/public/mate-avatars/lead-clay.png +0 -0
  150. package/lib/public/modules/app-message-router.js +4 -0
  151. package/lib/public/modules/app-messages.js +13 -2
  152. package/lib/public/modules/app-rendering.js +1 -1
  153. package/lib/public/modules/avatar.js +15 -41
  154. package/lib/public/modules/chat-render-runtime.js +10 -1
  155. package/lib/public/modules/clay-browser-links.js +47 -0
  156. package/lib/public/modules/clay-log-links.js +1 -1
  157. package/lib/public/modules/command-palette.js +1 -1
  158. package/lib/public/modules/debate.js +1 -0
  159. package/lib/public/modules/history-replay-batch.js +5 -1
  160. package/lib/public/modules/home-debate-live.js +2 -0
  161. package/lib/public/modules/home-debate-planning.js +7 -7
  162. package/lib/public/modules/home-mate-avatar-editor.js +127 -208
  163. package/lib/public/modules/home-mate-chat.js +6 -6
  164. package/lib/public/modules/home-mate-settings.js +19 -12
  165. package/lib/public/modules/markdown.js +4 -0
  166. package/lib/public/modules/mate-avatar-catalog.js +97 -0
  167. package/lib/public/modules/mate-management.js +40 -7
  168. package/lib/public/modules/mcp-connection-view.js +31 -13
  169. package/lib/public/modules/mcp-discovery-view.js +31 -0
  170. package/lib/public/modules/mcp-ui.js +23 -4
  171. package/lib/public/modules/mention.js +3 -0
  172. package/lib/public/modules/pane-browser-bridge.js +20 -0
  173. package/lib/public/modules/profile.js +1 -238
  174. package/lib/public/modules/project-mate-navigation.js +99 -14
  175. package/lib/public/modules/search-clay-chat.js +16 -2
  176. package/lib/public/modules/session-github.js +23 -9
  177. package/lib/public/modules/session-work-primary.js +51 -0
  178. package/lib/public/modules/shared-browser-settings.js +112 -0
  179. package/lib/public/modules/shared-browser-tabs.js +13 -3
  180. package/lib/public/modules/shared-browser.js +27 -6
  181. package/lib/public/modules/split-view.js +2 -0
  182. package/lib/public/modules/theme.js +8 -1
  183. package/lib/public/modules/tools.js +2 -1
  184. package/lib/public/style.css +1 -0
  185. package/lib/public/sw.js +3 -1
  186. package/lib/sdk-bridge.js +9 -3
  187. package/lib/server-appearance-settings.js +13 -1
  188. package/lib/server-dm.js +1 -0
  189. package/lib/server-home-chat-events.js +14 -4
  190. package/lib/server-home-chat.js +10 -2
  191. package/lib/server-home-mcp-consent.js +14 -0
  192. package/lib/server-mates.js +6 -1
  193. package/lib/server-settings.js +4 -1
  194. package/lib/server.js +45 -12
  195. package/lib/session-work-primary.js +50 -0
  196. package/lib/sessions.js +3 -0
  197. package/lib/shared-browser-cookie-sync.js +32 -0
  198. package/lib/shared-browser-cookies.js +79 -0
  199. package/lib/shared-browser-runtime.js +25 -9
  200. package/lib/shared-browser-storage-sync.js +43 -0
  201. package/lib/shared-browser-storage.js +101 -0
  202. package/lib/shared-browser-tools.js +2 -1
  203. package/lib/shared-browser-worker.js +46 -10
  204. package/lib/yoke/acp-agent-profiles.js +2 -2
  205. package/package.json +5 -1
@@ -0,0 +1,47 @@
1
+ import { store } from './store.js';
2
+ import { iconHtml, refreshIcons } from './icons.js';
3
+ import { showToast } from './utils.js';
4
+ import { forwardPaneBrowserLink } from './pane-browser-bridge.js';
5
+
6
+ function escape(value) { return String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
7
+ export function browserLinkTarget(href) {
8
+ if (typeof href !== 'string' || !href.startsWith('browser:') || href.length > 8200) return null;
9
+ var raw = href.slice(8);
10
+ if (!/^https?:\/\//i.test(raw) || /[\u0000-\u0020]/.test(raw)) return null;
11
+ try {
12
+ var url = new URL(raw);
13
+ if (url.username || url.password || !['http:', 'https:'].includes(url.protocol)) return null;
14
+ return url.href;
15
+ } catch (error) { return null; }
16
+ }
17
+ export function renderBrowserLink(href, title, text) {
18
+ var url = browserLinkTarget(href);
19
+ if (!url) return null;
20
+ var label = String(text || '').replace(/<[^>]*>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
21
+ if (!label || label === href || label === url) label = new URL(url).host;
22
+ return '<button type="button" class="clay-browser-link clayos-record-link" data-browser-url="' + escape(url) + '" data-browser-project="' + escape(store.get('currentSlug') || '') + '" title="' + escape('Open in Clay Browser · ' + url) + '" aria-label="' + escape('Open in Clay Browser: ' + label) + '">' + iconHtml('globe') + '<span>Browser</span><small>' + escape(label) + '</small></button>';
23
+ }
24
+ export function enhanceBrowserLinks(root) {
25
+ if (!root || typeof document === 'undefined') return;
26
+ root.querySelectorAll('a[href]').forEach(function (anchor) {
27
+ if (anchor.closest('pre, code, button')) return;
28
+ var html = renderBrowserLink(anchor.getAttribute('href'), '', anchor.textContent);
29
+ if (html) { var template = document.createElement('template'); template.innerHTML = html; anchor.replaceWith(template.content); }
30
+ });
31
+ initBrowserLinks();
32
+ refreshIcons();
33
+ }
34
+ export function initBrowserLinks() {
35
+ if (store.get('browserLinksBound')) return;
36
+ store.set({ browserLinksBound: true });
37
+ document.addEventListener('click', function (event) {
38
+ var button = event.target.closest && event.target.closest('.clay-browser-link');
39
+ if (!button) return;
40
+ event.preventDefault(); event.stopPropagation();
41
+ var url = browserLinkTarget('browser:' + button.dataset.browserUrl);
42
+ if (!url) { showToast('This browser link is invalid.', 'warn'); return; }
43
+ if (button.dataset.browserProject && button.dataset.browserProject !== store.get('currentSlug')) { showToast('Open this link from its original project.', 'warn'); return; }
44
+ if (forwardPaneBrowserLink(url)) return;
45
+ import('./shared-browser.js').then(function (module) { module.openSharedBrowserAt(url); }).catch(function (error) { showToast(error.message || 'Could not open the browser.', 'warn'); });
46
+ });
47
+ }
@@ -16,7 +16,7 @@ export function renderClaySessionLink(ref, text) {
16
16
  var parsed = parseClaySessionReference(ref);
17
17
  if (!parsed) return null;
18
18
  var label = text ? String(text).trim() : "";
19
- var safeLabel = escapeHtml(label);
19
+ var safeLabel = escapeHtml(label).replace(/"/g, "&quot;");
20
20
  var entryAttr = parsed.entryIndex === null ? "" : ' data-session-entry-index="' + parsed.entryIndex + '"';
21
21
  return '<button type="button" class="clayos-session-link clayos-record-link" data-session-ref="' + parsed.ref + '"' + entryAttr + ' aria-label="Open session' + (safeLabel ? " from " + safeLabel : "") + '"><span>Session</span>' + (safeLabel ? '<small>' + safeLabel + '</small>' : '') + '</button>';
22
22
  }
@@ -205,7 +205,7 @@ export function handlePaletteHistoryPrepended() {
205
205
  return;
206
206
  }
207
207
  requestAnimationFrame(function () {
208
- if (store.get('pendingSessionEntryNavigation') === pending) jumpToSessionEntry(pending.entryIndex, pending);
208
+ if (store.get('pendingSessionEntryNavigation') === pending) handlePaletteHistoryDone();
209
209
  });
210
210
  }
211
211
 
@@ -58,6 +58,7 @@ function buildAvatarUrl(meta) {
58
58
  return mateAvatarUrl({
59
59
  id: meta.mateId,
60
60
  profile: {
61
+ avatarCatalogId: meta.avatarCatalogId || "",
61
62
  avatarStyle: meta.avatarStyle || "imprint",
62
63
  avatarSeed: meta.avatarSeed || meta.mateId || "mate",
63
64
  avatarColor: meta.avatarColor || "#5857fc",
@@ -27,7 +27,7 @@ function canCombine(previous, message) {
27
27
  // can differ; the first token still supplies the message's display time.
28
28
  var keys = Object.keys(previous).concat(Object.keys(message));
29
29
  return keys.every(function (key) {
30
- return key === 'text' || key === '_ts' || previous[key] === message[key];
30
+ return key === 'text' || key === '_ts' || key === '_historyIndex' || key === '_historyIndexes' || previous[key] === message[key];
31
31
  });
32
32
  }
33
33
 
@@ -105,6 +105,10 @@ export function receiveHistoryReplay(msg, dispatch) {
105
105
  if (canCombine(previous, msg)) {
106
106
  if (!batch.textParts) batch.textParts = [previous.text];
107
107
  batch.textParts.push(msg.text);
108
+ if (Number.isSafeInteger(msg._historyIndex) && msg._historyIndex >= 0) {
109
+ if (!previous._historyIndexes) previous._historyIndexes = Number.isSafeInteger(previous._historyIndex) ? [previous._historyIndex] : [];
110
+ previous._historyIndexes.push(msg._historyIndex);
111
+ }
108
112
  } else {
109
113
  finishText(batch);
110
114
  batch.items.push(Object.assign({}, msg));
@@ -23,6 +23,7 @@ function debateIdentity(message, prior) {
23
23
  return {
24
24
  mateId: message.speakerMateId || message.mateId || previous.mateId || null,
25
25
  mateName: name,
26
+ avatarCatalogId: message.avatarCatalogId || previous.avatarCatalogId || cachedProfile.avatarCatalogId || "",
26
27
  avatarStyle: message.avatarStyle || previous.avatarStyle || cachedProfile.avatarStyle || "imprint",
27
28
  avatarSeed: message.avatarSeed || previous.avatarSeed || cachedProfile.avatarSeed || message.speakerMateId || message.mateId || name,
28
29
  avatarColor: message.avatarColor || previous.avatarColor || cachedProfile.avatarColor || "",
@@ -37,6 +38,7 @@ function mateForTurn(message) {
37
38
  name: identity.mateName,
38
39
  profile: {
39
40
  displayName: identity.mateName,
41
+ avatarCatalogId: identity.avatarCatalogId,
40
42
  avatarStyle: identity.avatarStyle,
41
43
  avatarSeed: identity.avatarSeed,
42
44
  avatarColor: identity.avatarColor,
@@ -123,11 +123,11 @@ export function createHomeDebateProposalCard(message, respond) {
123
123
  export function createHomeDebateQuestionCard(message, respond) {
124
124
  var question = Array.isArray(message.questions) && message.questions.length ? message.questions[0] : {};
125
125
  var card = document.createElement("section");
126
- card.className = "home-debate-question";
126
+ card.className = "home-debate-question" + (message.flow === "mcp_import" ? " mcp-import-consent" : "");
127
127
  card.dataset.toolId = message.toolId || "";
128
128
  card.setAttribute("aria-busy", message.status === "submitting" ? "true" : "false");
129
- var isMateCreation = message.flow === "mate_creation";
130
- card.setAttribute("aria-label", (isMateCreation ? "Mate creation interview question: " : "Debate planning question: ") + (question.question || "Question"));
129
+ var isMateCreation = message.flow === "mate_creation", isMcpImport = message.flow === "mcp_import";
130
+ card.setAttribute("aria-label", (isMcpImport ? "MCP import consent: " : isMateCreation ? "Mate creation interview question: " : "Debate planning question: ") + (question.question || "Question"));
131
131
  if (question.header) card.appendChild(textElement("div", "home-debate-question-header", question.header));
132
132
  card.appendChild(textElement("h3", "home-debate-question-text", question.question || "Clay has a question."));
133
133
  var form = document.createElement("div");
@@ -162,8 +162,8 @@ export function createHomeDebateQuestionCard(message, respond) {
162
162
  answerControl.className = freeform ? "home-debate-question-freeform" : "home-debate-question-other";
163
163
  answerControl.placeholder = freeform ? (isMateCreation ? "Describe what you want in your own words" : "Describe the topic in your own words") : "Type another answer";
164
164
  answerLabel.appendChild(answerControl);
165
- form.appendChild(answerLabel);
166
- var submit = textElement("button", "home-debate-question-submit", message.status === "submitting" ? "Submitting…" : "Submit answer");
165
+ if (freeform || question.allowOther !== false) form.appendChild(answerLabel);
166
+ var submit = textElement("button", "home-debate-question-submit", message.status === "submitting" ? "Submitting…" : isMcpImport ? "Confirm choice" : "Submit answer");
167
167
  submit.type = "button";
168
168
  submit.disabled = message.status !== "pending" || !selected;
169
169
  function submitAnswer() {
@@ -253,7 +253,7 @@ export function normalizeHomeTranscript(raw) {
253
253
  export function applyHomeDebateQuestion(messages, msg) {
254
254
  if (!msg || !msg.toolId) return messages;
255
255
  for (var i = 0; i < messages.length; i++) if (messages[i].role === "question" && messages[i].toolId === msg.toolId) return messages;
256
- return messages.concat([{ role: "question", flow: msg.type === "home_mate_creation_question" ? "mate_creation" : "debate", toolId: msg.toolId, questions: msg.questions || [], status: "pending", error: "" }]);
256
+ return messages.concat([{ role: "question", flow: msg.type === "home_mcp_import_question" ? "mcp_import" : msg.type === "home_mate_creation_question" ? "mate_creation" : "debate", toolId: msg.toolId, questions: msg.questions || [], status: "pending", error: "" }]);
257
257
  }
258
258
 
259
259
  export function resolveHomeDebateQuestion(messages, msg) {
@@ -296,7 +296,7 @@ export function markHomeDebateQuestionSubmitting(message, opener) {
296
296
  export function createHomeDebateResponder(sendMessage, getContext, onChange) {
297
297
  return function respond(message, action, opener, answers, modelOverrides) {
298
298
  if (!message || message.status === "submitting") return;
299
- var payload = message.role === "question" ? { type: message.flow === "mate_creation" ? "home_mate_creation_question_response" : "home_debate_question_response", toolId: message.toolId, answers: answers || {} } : { type: "home_debate_proposal_response", proposalId: message.proposal.proposalId, action: action, modelOverrides: Array.isArray(modelOverrides) ? modelOverrides : [] };
299
+ var payload = message.role === "question" ? { type: message.flow === "mcp_import" ? "home_mcp_import_response" : message.flow === "mate_creation" ? "home_mate_creation_question_response" : "home_debate_question_response", toolId: message.toolId, answers: answers || {} } : { type: "home_debate_proposal_response", proposalId: message.proposal.proposalId, action: action, modelOverrides: Array.isArray(modelOverrides) ? modelOverrides : [] };
300
300
  Object.assign(payload, getContext());
301
301
  if (!sendMessage(payload)) return;
302
302
  if (message.role === "question") markHomeDebateQuestionSubmitting(message, opener);
@@ -1,228 +1,147 @@
1
- // Mate avatar choices hosted inside the current settings dialog.
2
-
3
- import { mateStyleAvatarUrl, mateAvatarUrl, MATE_AVATAR_STYLES } from './avatar.js';
1
+ // Explicit avatar drafts in the existing owner-scoped Mate settings surface.
2
+ import { mateAvatarUrl, mateCatalogAvatarUrl } from './avatar.js';
4
3
  import { showAvatarPositioner } from './profile.js';
5
4
  import { getWs } from './ws-ref.js';
6
5
  import { store } from './store.js';
6
+ import { MATE_AVATAR_COLLECTIONS } from './mate-avatar-catalog.js';
7
7
 
8
- var activeToken = 0;
9
- var activeMateId = null;
10
- var pending = null;
11
- var statusByMate = Object.create(null);
12
- var draftByMate = Object.create(null);
13
-
14
- function mateName(mate) {
15
- var profile = mate && mate.profile ? mate.profile : {};
16
- return profile.displayName || (mate && (mate.displayName || mate.name)) || 'Mate';
17
- }
18
-
19
- function mateProfile(mate) {
20
- return Object.assign({}, mate && mate.profile ? mate.profile : {});
8
+ var timer = null;
9
+ function state() { return store.get('mateAvatarDraft'); }
10
+ function put(value) { store.set({ mateAvatarDraft: value }); }
11
+ function profile(mate) { return Object.assign({}, mate.profile || {}); }
12
+ function currentMate(mate) {
13
+ return (store.get('cachedMatesList') || []).find(function (item) { return item.id === mate.id; }) || mate;
21
14
  }
22
-
23
- function sendProfile(mate, profile, rerender, message) {
24
- draftByMate[mate.id] = profile;
25
- var ws = getWs();
26
- if (!ws || ws.readyState !== 1) {
27
- statusByMate[mate.id] = { kind: 'error', text: 'Clay is offline. Your choice was not saved.' };
28
- rerender();
29
- return false;
15
+ function ensure(mate) {
16
+ var s = state();
17
+ if (!s || s.mateId !== mate.id) {
18
+ s = { mateId: mate.id, selected: (mate.profile || {}).avatarCatalogId || '', dirty: false, requestId: null, error: '', saved: false, uploading: false };
19
+ put(s);
20
+ } else if (!s.dirty && !s.requestId && !s.uploading && s.selected !== ((mate.profile || {}).avatarCatalogId || '')) {
21
+ s = Object.assign({}, s, { selected: (mate.profile || {}).avatarCatalogId || '' }); put(s);
30
22
  }
31
- pending = { mateId: mate.id, style: profile.avatarStyle || 'imprint', seed: profile.avatarSeed || '', custom: profile.avatarCustom || '' };
32
- statusByMate[mate.id] = { kind: 'pending', text: message || 'Saving avatar…' };
33
- ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: profile } }));
34
- rerender();
35
- return true;
36
- }
37
-
38
- function appendStatus(body, mate) {
39
- var state = statusByMate[mate.id];
40
- var status = document.createElement('div');
41
- status.className = 'home-mate-avatar-status' + (state ? ' is-' + state.kind : '');
42
- status.setAttribute('aria-live', 'polite');
43
- status.textContent = state ? state.text : '';
44
- body.appendChild(status);
23
+ return s;
45
24
  }
46
-
47
- function createChoice(label, src, selected) {
48
- var button = document.createElement('button');
49
- button.type = 'button';
50
- button.className = 'home-mate-avatar-choice';
51
- button.setAttribute('aria-label', label + ' avatar');
52
- button.setAttribute('aria-pressed', selected ? 'true' : 'false');
53
- button.title = label;
54
- var image = document.createElement('img');
55
- image.src = src;
56
- image.alt = '';
57
- image.draggable = false;
58
- var text = document.createElement('span');
59
- text.textContent = label;
60
- button.appendChild(image);
61
- button.appendChild(text);
62
- return button;
25
+ function button(label, id, action) {
26
+ var b = document.createElement('button');
27
+ b.type = 'button'; b.textContent = label; b.id = id;
28
+ b.addEventListener('click', action);
29
+ return b;
63
30
  }
64
-
65
- function selectStyle(mate, style, seed, rerender) {
66
- var profile = mateProfile(mate);
67
- profile.avatarStyle = style;
68
- profile.avatarSeed = seed;
69
- profile.avatarCustom = '';
70
- sendProfile(mate, profile, rerender);
31
+ function closePicker(render) {
32
+ clearHomeMateAvatarEditor();
33
+ store.set({ mateSettingsAvatarOpen: false }); render();
34
+ var opener = document.getElementById('mate-profile-avatar');
35
+ if (opener) opener.focus();
71
36
  }
72
-
73
- function uploadBlob(mate, blob, token, rerender) {
74
- pending = null;
75
- statusByMate[mate.id] = { kind: 'pending', text: 'Uploading image…' };
76
- rerender();
77
- blob.arrayBuffer().then(function (buffer) {
78
- return fetch('/api/mate-avatar/' + encodeURIComponent(mate.id), {
79
- method: 'POST',
80
- headers: { 'Content-Type': 'application/octet-stream' },
81
- body: new Uint8Array(buffer),
82
- });
83
- }).then(function (response) {
84
- return response.json().then(function (data) {
85
- if (!response.ok || !data.ok || !data.avatar) throw new Error(data.error || 'Upload failed');
86
- return data.avatar;
87
- });
88
- }).then(function (avatar) {
89
- if (token !== activeToken) return;
90
- var profile = mateProfile(mate);
91
- profile.avatarCustom = avatar;
92
- draftByMate[mate.id] = profile;
93
- statusByMate[mate.id] = { kind: 'saved', text: 'Image saved.' };
94
- var mates = store.get('cachedMatesList') || [];
95
- store.set({ cachedMatesList: mates.map(function (item) {
96
- return item && item.id === mate.id ? Object.assign({}, item, { profile: profile }) : item;
97
- }) });
98
- var ws = getWs();
99
- if (ws && ws.readyState === 1) {
100
- ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: profile } }));
101
- }
102
- rerender();
103
- }).catch(function (error) {
104
- if (token !== activeToken) return;
105
- statusByMate[mate.id] = { kind: 'error', text: error && error.message ? error.message : 'Could not upload this image.' };
106
- rerender();
107
- });
37
+ function save(mate, render) {
38
+ var s = state(); var ws = getWs();
39
+ if (!s || s.requestId || !s.dirty) return;
40
+ if (!ws || ws.readyState !== 1) { put(Object.assign({}, s, { error: 'Clay is offline. Your choice was not saved.' })); render(); return; }
41
+ var requestId = 'mate-avatar-' + Date.now() + '-' + Math.random().toString(36).slice(2);
42
+ var p = profile(currentMate(mate)); p.avatarCatalogId = s.selected; p.avatarCustom = '';
43
+ put(Object.assign({}, s, { requestId: requestId, error: '', saved: false })); render();
44
+ try { ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, requestId: requestId, updates: { profile: p } })); }
45
+ catch (error) { failHomeMateAvatar(mate.id, 'Could not send your choice. Try again.', requestId); render(); return; }
46
+ clearTimeout(timer);
47
+ timer = setTimeout(function () { if (failHomeMateAvatar(mate.id, 'No response received. Your choice is retained; try again.', requestId)) render(); }, 15000);
108
48
  }
109
-
110
- function openUpload(mate, input, rerender) {
111
- var file = input.files && input.files[0];
112
- if (!file) return;
113
- var token = ++activeToken;
114
- var image = new Image();
115
- var objectUrl = URL.createObjectURL(file);
49
+ function upload(mate, input, render) {
50
+ var file = input.files && input.files[0]; if (!file) return;
51
+ var image = new Image(); var url = URL.createObjectURL(file); var originalState = state();
52
+ image.onerror = function () {
53
+ URL.revokeObjectURL(url);
54
+ if (state() === originalState) { put(Object.assign({}, originalState, { error: 'Could not read this image.' })); render(); }
55
+ };
116
56
  image.onload = function () {
117
- if (token !== activeToken) {
118
- URL.revokeObjectURL(objectUrl);
119
- return;
120
- }
121
- showAvatarPositioner(image, objectUrl, function (blob) {
122
- URL.revokeObjectURL(objectUrl);
123
- if (token === activeToken) uploadBlob(mate, blob, token, rerender);
57
+ if (state() !== originalState) { URL.revokeObjectURL(url); return; }
58
+ showAvatarPositioner(image, url, function (blob) {
59
+ URL.revokeObjectURL(url);
60
+ if (state() !== originalState) return;
61
+ var uploadingState = Object.assign({}, originalState, { uploading: true, error: '' }); put(uploadingState); render();
62
+ blob.arrayBuffer().then(function (buffer) {
63
+ return fetch('/api/mate-avatar/' + encodeURIComponent(mate.id), { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: new Uint8Array(buffer) });
64
+ }).then(function (response) {
65
+ return response.json().then(function (data) { if (!response.ok || !data.ok || !data.avatar) throw new Error(data.error || 'Upload failed.'); return data.avatar; });
66
+ }).then(function (avatar) {
67
+ if (state() !== uploadingState) return;
68
+ var p = profile(currentMate(mate)); p.avatarCustom = avatar;
69
+ store.set({ cachedMatesList: (store.get('cachedMatesList') || []).map(function (item) { return item.id === mate.id ? Object.assign({}, item, { profile: p }) : item; }) });
70
+ put(Object.assign({}, uploadingState, { uploading: false, dirty: false, saved: true }));
71
+ // The HTTP upload is persisted; the update also refreshes other owner tabs.
72
+ var ws = getWs();
73
+ if (ws && ws.readyState === 1) {
74
+ try { ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: p } })); }
75
+ catch (error) { /* The acknowledged upload remains saved. */ }
76
+ }
77
+ render();
78
+ }).catch(function (error) {
79
+ if (state() !== uploadingState) return;
80
+ put(Object.assign({}, uploadingState, { uploading: false, error: error.message || 'Could not upload this image.' })); render();
81
+ });
124
82
  });
125
83
  };
126
- image.onerror = function () {
127
- URL.revokeObjectURL(objectUrl);
128
- if (token !== activeToken) return;
129
- statusByMate[mate.id] = { kind: 'error', text: 'Could not read this image.' };
130
- rerender();
131
- };
132
- image.src = objectUrl;
84
+ image.src = url;
133
85
  }
134
-
135
- export function renderHomeMateAvatarEditor(body, mate, rerender) {
86
+ export function renderHomeMateAvatarEditor(body, mate, render, onCancel) {
136
87
  if (!mate) return;
137
- if (activeMateId !== mate.id) {
138
- activeMateId = mate.id;
139
- activeToken++;
140
- }
141
- var profile = draftByMate[mate.id] || mateProfile(mate);
142
- var name = mateName(mate);
143
- var style = profile.avatarStyle || 'imprint';
144
- var seed = profile.avatarSeed || mate.id || name;
145
- var locked = profile.avatarLocked === true;
146
- var section = document.createElement('section');
147
- section.className = 'home-mate-avatar-editor';
148
- var heading = document.createElement('div');
149
- heading.className = 'home-mate-avatar-heading';
150
- var preview = document.createElement('img');
151
- preview.className = 'home-mate-avatar-preview';
152
- preview.src = mateAvatarUrl(Object.assign({}, mate, { profile: profile }), 72);
153
- preview.alt = name + ' avatar';
154
- var copy = document.createElement('div');
155
- var title = document.createElement('h3');
156
- title.textContent = 'Avatar';
157
- var note = document.createElement('p');
158
- note.textContent = locked ? 'This built-in avatar is managed by Clay.' : 'Choose a locally generated style or upload your own image.';
159
- copy.appendChild(title);
160
- copy.appendChild(note);
161
- heading.appendChild(preview);
162
- heading.appendChild(copy);
163
- section.appendChild(heading);
164
- if (!locked) {
165
- var choices = document.createElement('div');
166
- choices.className = 'home-mate-avatar-choices';
167
- choices.setAttribute('role', 'group');
168
- choices.setAttribute('aria-label', 'Avatar for ' + name);
169
- for (var i = 0; i < MATE_AVATAR_STYLES.length; i++) {
170
- (function (entry) {
171
- var choice = createChoice(entry.name, mateStyleAvatarUrl(entry.id, seed, 48), !profile.avatarCustom && style === entry.id);
172
- choice.addEventListener('click', function () { selectStyle(mate, entry.id, seed, rerender); });
173
- choices.appendChild(choice);
174
- })(MATE_AVATAR_STYLES[i]);
175
- }
176
- section.appendChild(choices);
177
- var actions = document.createElement('div');
178
- actions.className = 'home-mate-avatar-actions';
179
- var shuffle = document.createElement('button');
180
- shuffle.type = 'button';
181
- shuffle.textContent = 'Shuffle selected style';
182
- shuffle.disabled = style === 'imprint' || style === 'initial' || !!profile.avatarCustom;
183
- shuffle.addEventListener('click', function () {
184
- selectStyle(mate, style, Math.random().toString(36).substring(2, 10), rerender);
88
+ var s = ensure(mate); var p = profile(mate); var busy = !!s.requestId || s.uploading;
89
+ var section = document.createElement('section'); section.className = 'home-mate-avatar-editor';
90
+ var preview = document.createElement('div'); preview.className = 'home-mate-avatar-selection';
91
+ var image = document.createElement('img'); image.className = 'home-mate-avatar-preview';
92
+ image.src = s.dirty ? mateCatalogAvatarUrl(s.selected) : mateAvatarUrl(mate, 52); image.alt = 'Avatar preview';
93
+ var caption = document.createElement('p'); caption.textContent = s.dirty ? 'Preview' : 'Current avatar';
94
+ preview.appendChild(image); preview.appendChild(caption); section.appendChild(preview);
95
+ if (!p.avatarLocked) {
96
+ MATE_AVATAR_COLLECTIONS.forEach(function (collection) {
97
+ var family = collection.id;
98
+ var heading = document.createElement('h4'); heading.textContent = collection.name;
99
+ heading.id = 'mate-avatar-family-' + family; section.appendChild(heading);
100
+ var grid = document.createElement('div'); grid.className = 'home-mate-avatar-choices';
101
+ grid.setAttribute('role', 'group'); grid.setAttribute('aria-labelledby', heading.id);
102
+ collection.avatars.forEach(function (entry) {
103
+ var id = entry.id;
104
+ var choice = button(id, 'mate-avatar-' + id, function () {
105
+ put(Object.assign({}, state(), { selected: id, dirty: id !== (currentMate(mate).profile || {}).avatarCatalogId || !!(currentMate(mate).profile || {}).avatarCustom, error: '', saved: false }));
106
+ render(); var target = document.getElementById('mate-avatar-' + id); if (target) target.focus({ preventScroll: true });
107
+ });
108
+ choice.textContent = ''; choice.className = 'home-mate-avatar-choice'; choice.disabled = busy;
109
+ choice.setAttribute('aria-label', entry.name + ' avatar'); choice.title = entry.name; choice.setAttribute('aria-pressed', String(s.selected === id && (s.dirty || !p.avatarCustom)));
110
+ var img = document.createElement('img'); img.src = mateCatalogAvatarUrl(id); img.alt = ''; img.draggable = false;
111
+ choice.appendChild(img);
112
+ choice.addEventListener('keydown', function (event) {
113
+ var buttons = Array.from(grid.querySelectorAll('button')); var index = buttons.indexOf(choice);
114
+ var columns = getComputedStyle(grid).gridTemplateColumns.split(' ').length;
115
+ var offset = { ArrowRight: 1, ArrowLeft: -1, ArrowDown: columns, ArrowUp: -columns }[event.key];
116
+ if (offset !== undefined || event.key === 'Home' || event.key === 'End') {
117
+ event.preventDefault(); var next = event.key === 'Home' ? 0 : event.key === 'End' ? 47 : Math.max(0, Math.min(47, index + offset)); buttons[next].focus();
118
+ }
119
+ });
120
+ grid.appendChild(choice);
121
+ });
122
+ section.appendChild(grid);
185
123
  });
186
- var upload = document.createElement('button');
187
- upload.type = 'button';
188
- upload.textContent = profile.avatarCustom ? 'Replace image' : 'Upload image';
189
- var input = document.createElement('input');
190
- input.type = 'file';
191
- input.accept = 'image/png,image/jpeg,image/gif,image/webp';
192
- input.className = 'home-mate-avatar-file-input';
193
- input.hidden = true;
194
- input.tabIndex = -1;
195
- input.setAttribute('aria-hidden', 'true');
196
- input.addEventListener('change', function () { openUpload(mate, input, rerender); });
197
- upload.addEventListener('click', function () { input.click(); });
198
- actions.appendChild(shuffle);
199
- actions.appendChild(upload);
200
- actions.appendChild(input);
201
- section.appendChild(actions);
124
+ var actions = document.createElement('div'); actions.className = 'home-mate-avatar-actions';
125
+ var cancel = button('Cancel', 'mate-avatar-cancel', function () { if (onCancel) onCancel(); else closePicker(render); }); cancel.disabled = busy;
126
+ var saveButton = button('Save avatar', 'mate-avatar-save', function () { save(mate, render); }); saveButton.disabled = busy || !s.dirty;
127
+ saveButton.className = 'primary';
128
+ var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/png,image/jpeg,image/gif,image/webp'; input.hidden = true; input.tabIndex = -1;
129
+ input.className = 'home-mate-avatar-file-input'; input.addEventListener('change', function () { upload(mate, input, render); });
130
+ var uploadButton = button('Upload image', 'mate-avatar-upload', function () { input.click(); }); uploadButton.disabled = busy;
131
+ actions.appendChild(cancel); actions.appendChild(saveButton); actions.appendChild(uploadButton); actions.appendChild(input); section.appendChild(actions);
202
132
  }
203
- appendStatus(section, mate);
204
- body.appendChild(section);
133
+ var status = document.createElement('p'); status.className = 'home-mate-avatar-status' + (s.error ? ' is-error' : '');
134
+ status.setAttribute('aria-live', 'polite'); status.setAttribute('role', s.error ? 'alert' : 'status'); status.textContent = s.error || (s.requestId ? 'Saving avatar…' : s.uploading ? 'Uploading image…' : s.saved ? 'Avatar saved.' : 'Choose a face, then save.');
135
+ (actions || section).appendChild(status); body.appendChild(section);
205
136
  }
206
-
207
- export function confirmHomeMateAvatar(mate) {
208
- if (!mate || !pending || pending.mateId !== mate.id) return false;
209
- var profile = mateProfile(mate);
210
- if ((profile.avatarStyle || 'imprint') !== pending.style || (profile.avatarSeed || '') !== pending.seed || (profile.avatarCustom || '') !== pending.custom) return false;
211
- pending = null;
212
- delete draftByMate[mate.id];
213
- statusByMate[mate.id] = { kind: 'saved', text: 'Avatar saved.' };
214
- return true;
137
+ export function hasHomeMateAvatarDraft() { var s = state(); return !!s && s.dirty; }
138
+ export function homeMateAvatarSaving() { var s = state(); return !!s && (!!s.requestId || s.uploading); }
139
+ export function confirmHomeMateAvatar(mate, requestId) {
140
+ var s = state(); if (!mate || !s || s.mateId !== mate.id || !s.requestId || s.requestId !== requestId || (mate.profile || {}).avatarCatalogId !== s.selected) return false;
141
+ clearTimeout(timer); put(Object.assign({}, s, { requestId: null, dirty: false, saved: true, error: '' })); return true;
215
142
  }
216
-
217
- export function failHomeMateAvatar(mateId, message) {
218
- if (!pending || !mateId || pending.mateId !== mateId) return false;
219
- statusByMate[pending.mateId] = { kind: 'error', text: message || 'Could not save this avatar.' };
220
- pending = null;
221
- return true;
222
- }
223
-
224
- export function clearHomeMateAvatarEditor() {
225
- activeToken++;
226
- activeMateId = null;
227
- pending = null;
143
+ export function failHomeMateAvatar(mateId, message, requestId) {
144
+ var s = state(); if (!s || !s.requestId || s.mateId !== mateId || s.requestId !== requestId) return false;
145
+ clearTimeout(timer); put(Object.assign({}, s, { requestId: null, error: message || 'Could not save this avatar.' })); return true;
228
146
  }
147
+ export function clearHomeMateAvatarEditor() { clearTimeout(timer); put(null); }
@@ -340,24 +340,24 @@ export function handleHomeDebateTranscript(msg) {
340
340
  if (!isCurrentSessionMessage(msg)) return;
341
341
  markHomeChatActivity();
342
342
  settleHomeDebateLaunch(msg);
343
- if (msg.type === "home_debate_proposal" || msg.type === "home_debate_question" || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") messages = finalizeHomeAssistant(messages, streamingText, "", Date.now());
343
+ if (msg.type === "home_debate_proposal" || (msg.type === "home_debate_question" || msg.type === "home_mcp_import_question") || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") messages = finalizeHomeAssistant(messages, streamingText, "", Date.now());
344
344
  if (msg.type === "home_debate_event") messages = applyHomeDebateEvent(messages, msg);
345
345
  if (msg.type === "home_debate_proposal") messages = applyHomeDebateProposal(messages, msg);
346
346
  if (msg.type === "home_debate_proposal_resolved") messages = resolveHomeDebateProposal(messages, msg);
347
- if (msg.type === "home_debate_question") messages = applyHomeDebateQuestion(messages, msg);
348
- if (msg.type === "home_debate_question_resolved") messages = resolveHomeDebateQuestion(messages, msg);
347
+ if ((msg.type === "home_debate_question" || msg.type === "home_mcp_import_question")) messages = applyHomeDebateQuestion(messages, msg);
348
+ if ((msg.type === "home_debate_question_resolved" || msg.type === "home_mcp_import_question_resolved")) messages = resolveHomeDebateQuestion(messages, msg);
349
349
  if (msg.type === "home_mate_creation_question") messages = applyHomeDebateQuestion(messages, msg);
350
350
  if (msg.type === "home_mate_creation_question_resolved") messages = resolveHomeDebateQuestion(messages, msg);
351
351
  if (msg.type === "home_mate_creation_proposal") messages = applyHomeMateProposal(messages, msg);
352
352
  if (msg.type === "home_mate_creation_proposal_resolved") messages = resolveHomeMateProposal(messages, msg);
353
353
  if (msg.type === "home_mate_error" && /^question_/.test(msg.code || "")) messages = failHomeDebateQuestion(messages, msg);
354
- if (msg.type === "home_debate_proposal" || msg.type === "home_debate_question" || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") { clearHomeDebatePlanningPending(); streaming = false; streamingText = ""; }
355
- if ((msg.type === "home_debate_question_resolved" || msg.type === "home_mate_creation_question_resolved") && msg.status === "expired") clearHomeDebatePlanningPending();
354
+ if (msg.type === "home_debate_proposal" || (msg.type === "home_debate_question" || msg.type === "home_mcp_import_question") || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") { clearHomeDebatePlanningPending(); streaming = false; streamingText = ""; }
355
+ if (((msg.type === "home_debate_question_resolved" || msg.type === "home_mcp_import_question_resolved") || msg.type === "home_mate_creation_question_resolved") && msg.status === "expired") clearHomeDebatePlanningPending();
356
356
  renderHomeChat();
357
357
  if (msg.proposalId) restoreHomeDebateProposalFocus(msg.proposalId);
358
358
  if (msg.proposalId && msg.type === "home_mate_creation_proposal_resolved") restoreHomeMateProposalFocus(msg.proposalId);
359
359
  if (msg.toolId && msg.type === "home_mate_creation_question") restoreHomeDebateQuestionFocus(msg.toolId);
360
- if (msg.toolId && msg.type === "home_debate_question_resolved") restoreHomeDebateQuestionFocus(msg.toolId);
360
+ if (msg.toolId && (msg.type === "home_debate_question_resolved" || msg.type === "home_mcp_import_question_resolved")) restoreHomeDebateQuestionFocus(msg.toolId);
361
361
  if (msg.toolId && msg.type === "home_mate_error") restoreHomeDebateQuestionFocus(msg.toolId);
362
362
  }
363
363
  export function handleHomeMateDelta(msg) {