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,112 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { escapeHtml as escapeText } from './utils.js';
4
+
5
+ function escapeHtml(value) { return escapeText(String(value)).replace(/"/g, '"').replace(/'/g, '''); }
6
+ function state() { return store.get('sharedBrowserSettings') || {}; }
7
+ function set(patch) { store.set({ sharedBrowserSettings: Object.assign({}, state(), patch) }); }
8
+ function root() { return document.getElementById('shared-browser-settings'); }
9
+ function button(label, action, target, description) {
10
+ return '<button type="button" data-settings-action="' + action + '" data-target="' + escapeHtml(target || '') + '" data-label="' + escapeHtml(description || target || '') + '">' + escapeHtml(label) + '</button>';
11
+ }
12
+ function request(operation, target) {
13
+ var ws = getWs();
14
+ if (!ws || ws.readyState !== 1) { set({ error: 'Reconnect to manage browser data.', pending: false }); renderBrowserSettings(); return; }
15
+ var id = crypto.randomUUID();
16
+ set({ pending: true, requestId: id, error: '', notice: '' });
17
+ ws.send(JSON.stringify({ type: 'shared_browser_settings', sessionId: store.get('activeSessionId'), requestId: id, operation: operation, target: target }));
18
+ renderBrowserSettings();
19
+ setTimeout(function () {
20
+ if (state().requestId === id && state().pending) { set({ pending: false, error: 'The request timed out. Refresh to check the saved state.' }); renderBrowserSettings(); }
21
+ }, 15000);
22
+ }
23
+ export function closeBrowserSettings(restoreFocus) {
24
+ var wasOpen = !!state().open;
25
+ set({ open: false, review: null }); renderBrowserSettings();
26
+ var trigger = document.querySelector('[data-action="settings"]'); if (trigger && wasOpen && restoreFocus !== false) trigger.focus();
27
+ }
28
+ export function browserSettingsHtml() {
29
+ return '<section id="shared-browser-settings" class="shared-browser-settings" aria-label="Browser settings" hidden></section>';
30
+ }
31
+ export function bindBrowserSettings(panel) {
32
+ panel.querySelector('[data-action="settings"]').onclick = function () {
33
+ set({ open: true, page: 'sites', data: null, review: null, filter: '' });
34
+ request('list');
35
+ root().querySelector('[data-settings-action="back"]').focus();
36
+ };
37
+ root().addEventListener('keydown', function (event) {
38
+ if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); closeBrowserSettings(); }
39
+ });
40
+ root().addEventListener('input', function (event) {
41
+ if (event.target.matches('[data-settings-filter]')) { set({ filter: event.target.value }); renderRows(); root().dataset.key = JSON.stringify([state(), store.get('connected')]); }
42
+ });
43
+ root().addEventListener('click', function (event) {
44
+ var control = event.target.closest('[data-settings-action]');
45
+ if (!control || control.disabled) return;
46
+ var action = control.dataset.settingsAction;
47
+ if (action === 'back') { closeBrowserSettings(); return; }
48
+ if (action === 'sites' || action === 'history') { set({ page: action, filter: '', review: null, notice: '', error: '' }); renderBrowserSettings(); return; }
49
+ if (action === 'refresh') { request('list'); return; }
50
+ if (action === 'cancel') { set({ review: null }); renderBrowserSettings(); root().querySelector('[data-settings-action="refresh"]').focus(); return; }
51
+ if (action === 'confirm') { var review = state().review; if (review) request(review.action, review.target); return; }
52
+ set({ notice: '', error: '', review: { action: action, target: control.dataset.target, label: control.dataset.label || control.dataset.target } });
53
+ renderBrowserSettings(); root().querySelector('[data-settings-action="cancel"]').focus();
54
+ });
55
+ }
56
+ function renderRows() {
57
+ var el = root(), s = state(); if (!el || !s.open) return;
58
+ var rows = el.querySelector('.browser-settings-rows'); if (!rows) return;
59
+ var filter = (s.filter || '').toLowerCase();
60
+ var data = s.data || { sites: [], history: [] };
61
+ if (s.page === 'history') {
62
+ rows.innerHTML = data.history.filter(function (entry) { return entry.url.toLowerCase().includes(filter); }).map(function (entry) {
63
+ return '<div class="browser-settings-row"><div><span class="browser-settings-url">' + escapeHtml(entry.url) + '</span><small>' + escapeHtml(new Date(entry.at).toLocaleString()) + '</small></div>' + button('Remove', 'delete_history', entry.id, entry.url) + '</div>';
64
+ }).join('');
65
+ } else {
66
+ rows.innerHTML = data.sites.filter(function (site) { return site.host.toLowerCase().includes(filter); }).map(function (site) {
67
+ return '<details><summary>' + escapeHtml(site.host) + '<small>' + site.cookies.length + (site.cookies.length === 1 ? ' cookie · ' : ' cookies · ') + site.origins.length + (site.origins.length === 1 ? ' storage origin' : ' storage origins') + '</small></summary><div class="browser-settings-row"><span>Cookies and site storage</span>' + button('Remove site data', 'delete_site', site.host) + '</div>' +
68
+ site.origins.map(function (origin) { return '<p class="browser-settings-origin">' + escapeHtml(origin.origin) + '<small>' + origin.localStorage + ' localStorage entries · ' + origin.databases + ' IndexedDB databases</small></p>'; }).join('') +
69
+ site.cookies.map(function (cookie) {
70
+ var info = cookie.domain + cookie.path + ' · ' + (cookie.expires === -1 ? 'Session cookie' : 'Expires ' + new Date(cookie.expires * 1000).toLocaleDateString()) + (cookie.httpOnly ? ' · HttpOnly' : '') + (cookie.secure ? ' · Secure' : '') + (cookie.partitioned ? ' · Partitioned' : '');
71
+ return '<div class="browser-settings-row"><div><span>' + escapeHtml(cookie.name) + '</span><small>' + escapeHtml(info) + '</small></div>' + button('Remove cookie', 'delete_cookie', cookie.id, cookie.name + ' · ' + cookie.domain + cookie.path) + '</div>';
72
+ }).join('') + '</details>';
73
+ }).join('');
74
+ }
75
+ if (!rows.innerHTML) rows.innerHTML = '<p class="browser-settings-muted">' + (s.pending && !s.data ? 'Loading saved data…' : filter ? 'No matching entries.' : s.page === 'history' ? 'No saved browsing history.' : 'No saved cookies or site data.') + '</p>';
76
+ rows.querySelectorAll('button').forEach(function (control) { control.disabled = !!s.pending || !store.get('connected'); });
77
+ }
78
+ export function renderBrowserSettings() {
79
+ var el = root(); if (!el) return;
80
+ var s = state(), panel = el.closest('.shared-browser-panel');
81
+ var browser = store.get('sharedBrowser');
82
+ var trigger = panel.querySelector('[data-action="settings"]');
83
+ trigger.hidden = !(browser && browser.personalCookiesAvailable);
84
+ trigger.disabled = !store.get('connected');
85
+ trigger.setAttribute('aria-expanded', String(!!s.open));
86
+ el.hidden = !s.open;
87
+ panel.classList.toggle('browser-settings-open', !!s.open);
88
+ if (!s.open) return;
89
+ var key = JSON.stringify([s, store.get('connected')]); if (el.dataset.key === key) return; el.dataset.key = key;
90
+ var page = s.page || 'sites';
91
+ el.innerHTML = '<div class="browser-settings-heading">' + button('Back to browser', 'back') + '<h3>Browser settings</h3></div><p class="browser-settings-muted">Personal · Shared across your Mates. Incognito tabs do not save data here.</p>' +
92
+ '<nav aria-label="Browser settings sections">' + button('Cookies & site data', 'sites') + button('History', 'history') + '</nav>' +
93
+ '<p class="browser-settings-muted">' + (page === 'sites' ? 'Cookies, localStorage and IndexedDB are saved for your account. New personal tabs restore saved site storage. Removing data may sign you out. Removals also apply to open personal tabs. Sites may create new data afterwards.' : 'Your latest 500 page visits. Clearing this list leaves open tabs, their Back/Forward navigation and conversation activity unchanged.') + '</p>' +
94
+ '<div class="browser-settings-tools"><input data-settings-filter aria-label="Filter saved browser data" placeholder="' + (page === 'sites' ? 'Find a site' : 'Find a page') + '" value="' + escapeHtml(s.filter || '') + '">' + button('Refresh', 'refresh') + button(page === 'sites' ? 'Clear all site data' : 'Clear history', page === 'sites' ? 'clear_data' : 'clear_history') + '</div>' +
95
+ '<p class="browser-settings-feedback" role="status">' + escapeHtml(s.error || (!store.get('connected') ? 'Disconnected. Reconnect and refresh to see current saved data.' : s.pending ? 'Updating…' : s.notice || '')) + '</p>' +
96
+ (s.review ? '<div class="browser-settings-review" role="group" aria-label="Confirm removal"><strong>' + (s.review.action.includes('history') ? 'Remove browsing history?' : 'Remove saved site data?') + '</strong><p>' + (s.review.action.includes('history') ? 'This removes ' + (s.review.action === 'clear_history' ? 'all saved visits' : 'this saved visit') + ' from your personal history.' : s.review.action === 'delete_cookie' ? 'This removes ' + escapeHtml(s.review.label) + ' across your personal Mate browsers and may sign you out.' : 'This removes ' + (s.review.action === 'clear_data' ? 'all cookies and site storage' : 'cookies and storage for ' + escapeHtml(s.review.target)) + ' across your personal Mate browsers and may sign you out.') + '</p>' + button('Cancel', 'cancel') + button('Remove', 'confirm') + '</div>' : '') +
97
+ '<div class="browser-settings-rows"></div>';
98
+ el.querySelector('[data-settings-action="' + page + '"]').setAttribute('aria-current', 'page');
99
+ el.querySelectorAll('button').forEach(function (control) { control.disabled = control.dataset.settingsAction !== 'back' && (!!s.pending || !store.get('connected')); });
100
+ renderRows();
101
+ }
102
+ export function handleBrowserSettingsMessage(msg) {
103
+ if (!msg.requestId || msg.requestId !== state().requestId) return false;
104
+ if (msg.type === 'shared_browser_settings') {
105
+ var removed = !!state().review;
106
+ set({ data: msg.settings, pending: false, review: null, error: '', notice: removed ? 'Removed from your saved browser data.' : '' });
107
+ } else if (msg.type === 'shared_browser_error') set({ pending: false, error: msg.error });
108
+ else return false;
109
+ renderBrowserSettings();
110
+ if (state().open && removed) root().querySelector('[data-settings-action="refresh"]').focus();
111
+ return true;
112
+ }
@@ -1,10 +1,11 @@
1
+ import { iconHtml, refreshIcons } from './icons.js';
1
2
  import { store } from './store.js';
2
3
 
3
4
  export function renderBrowserTabs(el, select, send) {
4
5
  var bar = el.querySelector('.shared-browser-tabs');
5
6
  var tabs = store.get('sharedBrowsers') || [];
6
7
  var selected = store.get('sharedBrowser');
7
- var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase]; }), selected && selected.id, store.get('connected')]);
8
+ var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase, tab.incognito, tab.personalCookiesAvailable]; }), selected && selected.id, store.get('connected')]);
8
9
  if (bar.dataset.key === key) return;
9
10
  bar.dataset.key = key;
10
11
  bar.replaceChildren();
@@ -14,9 +15,9 @@ export function renderBrowserTabs(el, select, send) {
14
15
  button.setAttribute('aria-selected', String(!!selected && selected.id === tab.id));
15
16
  var title = 'Tab ' + (index + 1);
16
17
  try { if (tab.url) title = new URL(tab.url).hostname; } catch (e) {}
17
- var label = document.createElement('span'); label.textContent = title;
18
+ var label = document.createElement('span'); label.textContent = title + (tab.personalCookiesAvailable && tab.incognito ? ' · Incognito' : '');
18
19
  var owner = document.createElement('small'); owner.textContent = tab.phase === 'ended' ? 'Ended' : tab.control === 'agent' ? 'Shared' : 'You only';
19
- button.append(label, owner); button.title = title + ' · ' + (tab.phase === 'ended' ? 'Ended' : tab.control === 'agent' ? 'You and Clay can control this tab' : 'Only you can control this tab. Clay is paused.');
20
+ button.append(label, owner); button.title = title + ' · ' + (tab.incognito === false ? 'Personal browser' : 'Incognito') + ' · ' + (tab.phase === 'ended' ? 'Ended' : tab.control === 'agent' ? 'You and Clay can control this tab' : 'Only you can control this tab. Clay is paused.');
20
21
  button.onclick = function () { select(tab.id); };
21
22
  button.onkeydown = function (event) {
22
23
  var next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : -1;
@@ -28,6 +29,15 @@ export function renderBrowserTabs(el, select, send) {
28
29
  item.append(button, close); bar.appendChild(item);
29
30
  });
30
31
  var add = document.createElement('button'); add.type = 'button'; add.textContent = '+'; add.setAttribute('aria-label', 'New browser tab'); add.disabled = !store.get('connected');
32
+ add.title = selected && selected.personalCookiesAvailable ? 'New tab · Personal browser' : 'New incognito tab';
31
33
  add.onclick = function () { send('open', { newTab: true }); };
32
34
  bar.appendChild(add);
35
+ if (selected && selected.personalCookiesAvailable) {
36
+ var incognito = document.createElement('button'); incognito.type = 'button';
37
+ incognito.innerHTML = iconHtml('glasses');
38
+ incognito.setAttribute('aria-label', 'New incognito tab'); incognito.title = 'New incognito tab · Site data is not saved';
39
+ incognito.disabled = !store.get('connected');
40
+ incognito.onclick = function () { send('open', { newTab: true, incognito: true }); };
41
+ bar.appendChild(incognito); refreshIcons();
42
+ }
33
43
  }
@@ -1,3 +1,5 @@
1
+ import { browserLinkTarget, initBrowserLinks } from './clay-browser-links.js';
2
+ import { browserSettingsHtml, bindBrowserSettings, renderBrowserSettings, closeBrowserSettings, handleBrowserSettingsMessage } from './shared-browser-settings.js';
1
3
  import { renderBrowserTabs } from './shared-browser-tabs.js';
2
4
  import { store } from './store.js';
3
5
  import { getWs } from './ws-ref.js';
@@ -50,15 +52,15 @@ function ensurePanel() {
50
52
  el.className = 'shared-browser-panel hidden';
51
53
  el.setAttribute('aria-label', 'Shared browser');
52
54
  el.innerHTML = '<header class="shared-browser-header"><span class="shared-browser-title">' + iconHtml('globe') + 'Browser</span><span class="shared-browser-phase" role="status">Ready</span><div class="shared-browser-window">' +
53
- iconButton('wide', 'chevrons-left-right', 'Widen browser panel') + iconButton('fullscreen', 'maximize-2', 'Toggle browser fullscreen') + iconButton('hide', 'x', 'Hide browser panel') + '</div></header>' +
55
+ iconButton('settings', 'settings-2', 'Browser settings') + iconButton('wide', 'chevrons-left-right', 'Widen browser panel') + iconButton('fullscreen', 'maximize-2', 'Toggle browser fullscreen') + iconButton('hide', 'x', 'Hide browser panel') + '</div></header>' +
54
56
  '<div class="shared-browser-tabs" role="tablist" aria-label="Browser tabs"></div><form class="shared-browser-address">' + iconButton('back', 'arrow-left', 'Back') + iconButton('forward', 'arrow-right', 'Forward') + iconButton('reload', 'rotate-cw', 'Reload') +
55
57
  '<input aria-label="Browser address" placeholder="Enter a URL" type="text" autocomplete="off" spellcheck="false"><button type="submit" aria-label="Open address">' + iconHtml('arrow-right') + '</button></form>' +
56
- '<div class="shared-browser-viewport"><div class="shared-browser-monitor"><div class="shared-browser-pointer-layer" aria-hidden="true"><div class="shared-browser-pointer" hidden><svg viewBox="0 0 24 28"><path d="M3 2 L3 22 L8 17 L12 26 L16 24 L12 15 L20 15 Z"/></svg><i></i></div></div><canvas width="1280" height="800" tabindex="0" aria-label="Shared browser page"></canvas>' +
57
- '<div class="shared-browser-empty">' + iconHtml('globe') + '<h3>A browser you can share with your Driver</h3><p>Enter an address to browse together.<br>Localhost connects to the Clay host.</p><p class="shared-browser-private">Cookies last only for this browser session.</p></div>' +
58
+ browserSettingsHtml() + '<div class="shared-browser-viewport"><div class="shared-browser-monitor"><div class="shared-browser-pointer-layer" aria-hidden="true"><div class="shared-browser-pointer" hidden><svg viewBox="0 0 24 28"><path d="M3 2 L3 22 L8 17 L12 26 L16 24 L12 15 L20 15 Z"/></svg><i></i></div></div><canvas width="1280" height="800" tabindex="0" aria-label="Shared browser page"></canvas>' +
59
+ '<div class="shared-browser-empty">' + iconHtml('globe') + '<h3>A browser you can use with Clay</h3><p>Enter an address to browse together.<br>Localhost connects to the Clay host.</p><p class="shared-browser-private">Site data lasts only for this browser session.</p></div>' +
58
60
  '<textarea class="shared-browser-keyboard" aria-label="Type into shared browser" autocomplete="off" autocapitalize="off" spellcheck="false"></textarea><span class="shared-browser-monitor-brand" aria-hidden="true">Clay Studio</span><span class="shared-browser-monitor-detail" aria-hidden="true"></span><span class="shared-browser-monitor-stand" aria-hidden="true"></span></div><div class="shared-browser-caption"><span class="shared-browser-monitor-label"></span>' + activityHtml() + '</div></div>' +
59
61
  '<p class="shared-browser-error hidden" role="status"></p>' +
60
62
  '<footer class="shared-browser-footer"><div class="shared-browser-control"><span class="shared-browser-control-status" role="status" aria-live="polite">You and Clay can control this browser</span><button type="button" data-action="control" aria-pressed="false"></button></div>' +
61
- '<div class="shared-browser-details">' + viewportMenuHtml() + '<button type="button" data-action="end">End browser</button></div></footer>';
63
+ '<div class="shared-browser-details"><span class="shared-browser-cookie-mode"></span>' + viewportMenuHtml() + '<button type="button" data-action="end">End browser</button></div></footer>';
62
64
  root.appendChild(el);
63
65
  el.querySelector('[data-action="hide"]').onclick = hideSharedBrowser;
64
66
  el.querySelector('[data-action="wide"]').onclick = function () { var wide = !(store.get('sharedBrowserUi') || {}).wide; ui({ wide: wide }); render(); };
@@ -69,8 +71,9 @@ function ensurePanel() {
69
71
  var browser = store.get('sharedBrowser');
70
72
  if (browser) send('control', { control: browser.control === 'user' ? 'agent' : 'user' });
71
73
  };
72
- el.querySelector('[data-action="end"]').onclick = function () { var browser = store.get('sharedBrowser'); send(browser && browser.phase === 'ended' ? 'open' : 'end'); };
74
+ el.querySelector('[data-action="end"]').onclick = function () { var browser = store.get('sharedBrowser'); send(browser && browser.phase === 'ended' ? 'open' : 'end', browser && browser.phase === 'ended' ? { incognito: browser.incognito } : undefined); };
73
75
  bindViewportMenu(el);
76
+ bindBrowserSettings(el);
74
77
  var observer = new ResizeObserver(layoutMonitor);
75
78
  observer.observe(el.querySelector('.shared-browser-viewport'));
76
79
  bindBrowserInput(el.querySelector('canvas'), el.querySelector('textarea'));
@@ -141,11 +144,16 @@ function render() {
141
144
  el.querySelector('[data-action="control"]').hidden = !live;
142
145
  el.querySelector('[data-action="end"]').disabled = !browser || !connected;
143
146
  el.querySelector('[data-action="end"]').textContent = browser && browser.phase === 'ended' ? 'New browser' : 'End browser';
147
+ var personal = browser && browser.incognito === false;
148
+ el.querySelector('.shared-browser-cookie-mode').textContent = personal ? 'Personal browser' : 'Incognito';
149
+ el.querySelector('.shared-browser-cookie-mode').title = personal ? 'Cookies and site storage are saved for your account across your Mates.' : 'Cookies and site storage are isolated and discarded when this tab ends.';
150
+ el.querySelector('.shared-browser-private').textContent = personal ? 'Your cookies and site storage are remembered across your Mates and browser restarts.' : 'Site data lasts only for this browser session.';
144
151
  var error = state.error || (browser && browser.error) || '';
145
152
  el.querySelector('.shared-browser-error').textContent = error;
146
153
  el.querySelector('.shared-browser-error').classList.toggle('hidden', !error);
147
154
  el.querySelector('.shared-browser-empty').classList.toggle('hidden', !!(browser && browser.url && state.hasFrame));
148
155
  renderViewportMenu(el, browser, operable);
156
+ renderBrowserSettings();
149
157
  renderBrowserActivity(el, browser, connected);
150
158
  layoutMonitor();
151
159
  }
@@ -163,6 +171,16 @@ function selectBrowserTab(id) {
163
171
  setBrowserTab(browser);
164
172
  send('view', { visible: desktop() && !document.hidden }); render();
165
173
  }
174
+ export function openSharedBrowserAt(value) {
175
+ var url = browserLinkTarget('browser:' + value);
176
+ if (!url) throw new Error('Enter a valid HTTP or HTTPS browser address.');
177
+ if (!desktop()) throw new Error('Clay Browser is available on desktop screens.');
178
+ if (!eligible()) throw new Error('Browser access is unavailable in this conversation.');
179
+ if (!store.get('connected')) throw new Error('Reconnect before opening this browser link.');
180
+ ensurePanel(); closeBrowserSettings(false);
181
+ claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
182
+ ui({ error: '' }); send('open', { url: url }); render();
183
+ }
166
184
  export function openSharedBrowser() {
167
185
  if (!eligible() || !store.get('connected')) return;
168
186
  ensurePanel();
@@ -175,6 +193,7 @@ export function openSharedBrowser() {
175
193
  }
176
194
  export function hideSharedBrowser() {
177
195
  closeViewportMenu();
196
+ closeBrowserSettings();
178
197
  clearBrowserInput();
179
198
  if (store.get('sharedBrowserOpen')) send('view', { visible: false });
180
199
  store.set({ sharedBrowserOpen: false });
@@ -184,6 +203,7 @@ export function hideSharedBrowser() {
184
203
  export function handleSharedBrowserMessage(msg) {
185
204
  if (!msg.type || msg.type.indexOf('shared_browser_') !== 0) return false;
186
205
  if (String(msg.sessionId) !== String(store.get('activeSessionId'))) return true;
206
+ if (handleBrowserSettingsMessage(msg)) return true;
187
207
  if (msg.type === 'shared_browser_state') {
188
208
  var old = store.get('sharedBrowser');
189
209
  var tabs = msg.browsers || (msg.browser ? [msg.browser] : []);
@@ -217,6 +237,7 @@ export function handleSharedBrowserMessage(msg) {
217
237
  return true;
218
238
  }
219
239
  export function initSharedBrowser() {
240
+ initBrowserLinks();
220
241
  var button = document.getElementById('shared-browser-btn');
221
242
  if (button) button.onclick = openSharedBrowser;
222
243
  var toggle = document.getElementById('shared-browser-toggle-btn');
@@ -233,7 +254,7 @@ registerRightWorkbench('browser', hideSharedBrowser);
233
254
  store.subscribe(function (state, previous) {
234
255
  if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.myUserId !== previous.myUserId) {
235
256
  clearBrowserInput(); releaseRightWorkbench('browser');
236
- store.set({ sharedBrowser: null, sharedBrowsers: [], sharedBrowserOpen: false, sharedBrowserUi: {} });
257
+ store.set({ sharedBrowser: null, sharedBrowsers: [], sharedBrowserOpen: false, sharedBrowserUi: {}, sharedBrowserSettings: {} });
237
258
  if (state.connected) send('state_request');
238
259
  render();
239
260
  } else if (state.connected !== previous.connected) {
@@ -1,3 +1,4 @@
1
+ import { handlePaneBrowserMessage } from './pane-browser-bridge.js';
1
2
  import { openWireframe } from './wireframe-workbench.js';
2
3
  // Two-pane iframe shell for the split-view spike.
3
4
 
@@ -168,6 +169,7 @@ function handlePaneMessage(event) {
168
169
  return;
169
170
  }
170
171
  if (handlePaneIssueMessage(event, host)) return;
172
+ if (handlePaneBrowserMessage(event, host)) return;
171
173
  if (handlePaneFileMessage(event, host, openFile, revealFilesPanel)) return;
172
174
  if (msg.type !== "clay-pane-context") return;
173
175
  var frames = host.querySelectorAll(".split-pane-frame");
@@ -614,7 +614,14 @@ function rebuildPicker() {
614
614
 
615
615
  // --- Init ---
616
616
  export function initTheme() {
617
- currentThemeId = getSystemMode() === 'light' ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
617
+ var seed = window.__clayAppearanceSeed;
618
+ if (seed && seed.configured) store.set({ appearancePreference: seed });
619
+ var initialMode = seed && seed.configured && (seed.mode === 'light' || seed.mode === 'dark')
620
+ ? seed.mode
621
+ : getSystemMode();
622
+ currentThemeId = seed && seed.configured && seed[initialMode + 'Theme']
623
+ ? seed[initialMode + 'Theme']
624
+ : (initialMode === 'light' ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID);
618
625
  document.body.classList.toggle('wide-view', isWideView());
619
626
 
620
627
  // Load all themes from server, then apply properly
@@ -244,6 +244,7 @@ export function renderAskUserQuestion(toolId, input) {
244
244
 
245
245
  var container = document.createElement("div");
246
246
  container.className = "ask-user-container";
247
+ if (input.source === "mcp-import") container.classList.add("mcp-import-consent");
247
248
  container.dataset.toolId = toolId;
248
249
  var requestKey = getAskUserRequestKey(toolId);
249
250
  container.dataset.askUserRequestKey = requestKey;
@@ -364,7 +365,7 @@ export function renderAskUserQuestion(toolId, input) {
364
365
  }
365
366
  });
366
367
  otherDiv.appendChild(otherInput);
367
- qDiv.appendChild(otherDiv);
368
+ if (q.allowOther !== false || !(q.options || []).length) qDiv.appendChild(otherDiv);
368
369
  container.appendChild(qDiv);
369
370
  });
370
371
 
@@ -59,6 +59,7 @@
59
59
  @import url("css/home-debate-planning.css");
60
60
  @import url("css/home-mate-creation.css");
61
61
  @import url("css/home-mate-settings.css");
62
+ @import url("css/mate-avatar-gallery.css");
62
63
  @import url("css/home-mate-model-picker.css");
63
64
  @import url("css/project-mate-creation.css");
64
65
  @import url("css/home-debate-live.css");
package/lib/public/sw.js CHANGED
@@ -1,4 +1,4 @@
1
- var CACHE_NAME = "clay-offline-v4";
1
+ var CACHE_NAME = "clay-offline-v5";
2
2
 
3
3
  self.addEventListener("install", function (event) {
4
4
  event.waitUntil(self.skipWaiting());
@@ -23,6 +23,8 @@ self.addEventListener("activate", function (event) {
23
23
  function shouldCache(request, response) {
24
24
  if (request.method !== "GET") return false;
25
25
  if (!response || !response.ok) return false;
26
+ var cacheControl = response.headers && response.headers.get("Cache-Control") || "";
27
+ if (/(^|,)\s*(private|no-store)(?:\s*=\s*[^,]*)?(?=\s*(?:,|$))/i.test(cacheControl)) return false;
26
28
  // Cache same-origin static assets and CDN resources (jsdelivr, fonts)
27
29
  var url = new URL(request.url);
28
30
  if (url.origin === self.location.origin) return true;
package/lib/sdk-bridge.js CHANGED
@@ -1812,7 +1812,7 @@ function createSDKBridge(opts) {
1812
1812
  }
1813
1813
  var hasSharedBrowserCatalog = sessionToolDefs.some(function (definition) { return definition.name === "shared_browser"; });
1814
1814
  var recoverCodexBrowserTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
1815
- && hasSharedBrowserCatalog && Number(session.codexSharedBrowserToolCatalogVersion || 0) < 5;
1815
+ && hasSharedBrowserCatalog && Number(session.codexSharedBrowserToolCatalogVersion || 0) < 6;
1816
1816
  var recoverCodexIssueTools = sessionAdapter.vendor === "codex"
1817
1817
  && !!session.cliSessionId
1818
1818
  && hasIssueCatalog
@@ -1931,6 +1931,12 @@ function createSDKBridge(opts) {
1931
1931
  return snapshotDef;
1932
1932
  }
1933
1933
 
1934
+ var externalQueryBridge = require('./mcp-query-bridge').createQueryBridge({ valid: queryContextAvailable,
1935
+ getServers: function () { return getRemoteMcpServers ? getRemoteMcpServers(session) || {} : {}; } });
1936
+ var externalToolDefs = sessionAdapter.vendor === 'codex' ? externalQueryBridge.dynamicDefs() : [];
1937
+ if (externalToolDefs.length) { sessionToolDefs = sessionToolDefs.concat(externalToolDefs); queryToolSnapshots.set(session, { generation: queryGeneration, defs: sessionToolDefs.slice() }); }
1938
+ var recoverCodexExternalTools = sessionAdapter.vendor === 'codex' && !!session.cliSessionId && (externalToolDefs.length > 0 || isMate);
1939
+
1934
1940
  // Give fresh queries an immediate provisional label. The visible user
1935
1941
  // message in history wins over `text`, which may contain an internal
1936
1942
  // provider wrapper rather than what the person typed.
@@ -1986,7 +1992,7 @@ function createSDKBridge(opts) {
1986
1992
  dynamicTools: sessionToolDefs,
1987
1993
  // Codex persists dynamic tools with a thread. A stale authorized Driver
1988
1994
  // gets this exact-query bridge; its handlers still reauthorize live.
1989
- sessionMcpServer: (recoverCodexIssueTools || recoverCodexQuestionTools || recoverCodexBrowserTools || recoverCodexLinearTools || recoverCodexYouTools) ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
1995
+ sessionMcpServer: (recoverCodexIssueTools || recoverCodexQuestionTools || recoverCodexBrowserTools || recoverCodexLinearTools || recoverCodexYouTools || recoverCodexExternalTools) ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
1990
1996
  callDynamicTool: function (toolName, args) {
1991
1997
  var toolDef = findSessionToolDef(toolName);
1992
1998
  if (toolDef && typeof toolDef.handler === "function") {
@@ -2208,7 +2214,7 @@ function createSDKBridge(opts) {
2208
2214
  if (hasYouCatalog) codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, youVersion: 1 });
2209
2215
  if (hasLinearCatalog) codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, linearVersion: 1 });
2210
2216
  if (hasSharedBrowserCatalog) {
2211
- codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, sharedBrowserVersion: 5 });
2217
+ codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, sharedBrowserVersion: 6 });
2212
2218
  }
2213
2219
  if (hasIssueCatalog) {
2214
2220
  codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, {
@@ -34,6 +34,18 @@ function attachAppearanceSettings(ctx) {
34
34
  fs.renameSync(defaultPath + '.tmp', defaultPath);
35
35
  }
36
36
  }
37
+ function getInitialAppearance(req) {
38
+ var user = identity(req);
39
+ if (!user) return null;
40
+ var saved = read(user);
41
+ return {
42
+ configured: saved.configured,
43
+ theme: saved.theme,
44
+ mode: saved.mode,
45
+ lightTheme: saved.lightTheme || null,
46
+ darkTheme: saved.darkTheme || null
47
+ };
48
+ }
37
49
  function handleRequest(req, res, url) {
38
50
  if (url !== '/api/user/appearance') return false;
39
51
  var user = identity(req);
@@ -66,6 +78,6 @@ function attachAppearanceSettings(ctx) {
66
78
  });
67
79
  return true;
68
80
  }
69
- return { handleRequest: handleRequest };
81
+ return { handleRequest: handleRequest, getInitialAppearance: getInitialAppearance };
70
82
  }
71
83
  module.exports = { attachAppearanceSettings: attachAppearanceSettings };
package/lib/server-dm.js CHANGED
@@ -69,6 +69,7 @@ function attachDm(ctx) {
69
69
  id: mate.id,
70
70
  displayName: mp.displayName || mate.name || "New Mate",
71
71
  username: mate.id,
72
+ avatarCatalogId: mp.avatarCatalogId || "",
72
73
  avatarStyle: mp.avatarStyle || "imprint",
73
74
  avatarSeed: mp.avatarSeed || mate.id,
74
75
  avatarColor: mp.avatarColor || "#5857fc",
@@ -3,7 +3,7 @@
3
3
  function homeDebateQuestions(input) {
4
4
  var source = input && Array.isArray(input.questions) && input.questions[0] ? input.questions[0] : null;
5
5
  if (!source) return [];
6
- var question = { header: source.header || "", question: source.question || "", multiSelect: false, options: [] };
6
+ var question = { header: source.header || "", question: source.question || "", multiSelect: false, allowOther: source.allowOther !== false, options: [] };
7
7
  var options = Array.isArray(source.options) ? source.options.slice(0, 6) : [];
8
8
  for (var i = 0; i < options.length; i++) question.options.push({ label: options[i].label || "Option", description: options[i].description || "" });
9
9
  return [question];
@@ -36,6 +36,7 @@ function findDebateTurn(messages, event) {
36
36
  function debateIdentity(source, fallback) {
37
37
  var prior = fallback || {};
38
38
  return {
39
+ avatarCatalogId: source.avatarCatalogId || prior.avatarCatalogId || "",
39
40
  avatarStyle: source.avatarStyle || prior.avatarStyle || "imprint",
40
41
  avatarSeed: source.avatarSeed || prior.avatarSeed || source.mateId || "mate",
41
42
  avatarColor: source.avatarColor || prior.avatarColor || "",
@@ -112,7 +113,7 @@ function applyAssignmentHistoryEvent(messages, event) {
112
113
  messages.push({ role: "assignment", assignment: assignment });
113
114
  }
114
115
 
115
- function historyToHomeChat(history, debateSetupMode, mateCreationMode) {
116
+ function historyToHomeChat(history, debateSetupMode, mateCreationMode, pendingAskUser) {
116
117
  var messages = [];
117
118
  var pending = "";
118
119
  var interviewMode = debateSetupMode === true || mateCreationMode === true;
@@ -124,6 +125,12 @@ function historyToHomeChat(history, debateSetupMode, mateCreationMode) {
124
125
  for (var i = 0; i < history.length; i++) {
125
126
  var event = history[i];
126
127
  if (!event) continue;
128
+ if (event.type === 'tool_executing' && event.name === 'AskUserQuestion' && event.source === 'mcp-import') {
129
+ flushAssistant();
130
+ var activeConsent = pendingAskUser === undefined || !!(pendingAskUser && pendingAskUser[event.id]);
131
+ messages.push({ role: 'question', flow: 'mcp_import', toolId: event.id, questions: homeDebateQuestions(event.input), status: activeConsent ? 'pending' : 'expired', error: activeConsent ? '' : 'This import consent is no longer active. Ask for a new scan and review.' });
132
+ continue;
133
+ }
127
134
  if (event.type === "project_assignment_proposal" || event.type === "project_assignment_status") {
128
135
  flushAssistant();
129
136
  applyAssignmentHistoryEvent(messages, event);
@@ -185,7 +192,7 @@ function historyToHomeChat(history, debateSetupMode, mateCreationMode) {
185
192
  } else if (interviewMode && event.type === "tool_executing" && event.name === "AskUserQuestion" && event.id && event.input) {
186
193
  flushAssistant();
187
194
  messages.push({ role: "question", flow: mateCreationMode === true ? "mate_creation" : "debate", toolId: event.id, questions: homeDebateQuestions(event.input), status: "pending", error: "" });
188
- } else if ((event.type === "ask_user_answered" || event.type === "home_debate_question_expired" || event.type === "home_mate_creation_question_expired") && event.toolId) {
195
+ } else if ((event.type === "ask_user_answered" || event.type === "home_debate_question_expired" || event.type === "home_mate_creation_question_expired" || event.type === "mcp_import_consent_expired") && event.toolId) {
189
196
  for (var k = messages.length - 1; k >= 0; k--) {
190
197
  if (messages[k].role === "question" && messages[k].toolId === event.toolId) {
191
198
  messages[k].status = event.type === "ask_user_answered" ? "answered" : "expired";
@@ -273,7 +280,7 @@ function transformEvent(event, mateId, session, requestId, stableSessionId) {
273
280
  }
274
281
  if (session && session.homeDebatePlanning === true && HOME_DEBATE_EVENT_TYPES[event.type]) {
275
282
  var live = { type: "home_debate_event", eventType: event.type };
276
- var fields = ["turnId", "decisionId", "decision", "toolName", "command", "commandTruncated", "topic", "format", "moderatorId", "moderatorName", "panelists", "mateName", "role", "round", "activity", "delta", "text", "reason", "action", "avatarStyle", "avatarSeed", "avatarColor", "avatarCustom"];
283
+ var fields = ["turnId", "decisionId", "decision", "toolName", "command", "commandTruncated", "topic", "format", "moderatorId", "moderatorName", "panelists", "mateName", "role", "round", "activity", "delta", "text", "reason", "action", "avatarCatalogId", "avatarStyle", "avatarSeed", "avatarColor", "avatarCustom"];
277
284
  for (var fi = 0; fi < fields.length; fi++) if (Object.prototype.hasOwnProperty.call(event, fields[fi])) live[fields[fi]] = event[fields[fi]];
278
285
  if (event.mateId) live.speakerMateId = event.mateId;
279
286
  return Object.assign(live, metadata);
@@ -298,6 +305,9 @@ function transformEvent(event, mateId, session, requestId, stableSessionId) {
298
305
  if (event.type === "debate_proposal_resolved" && event.proposalId) return Object.assign({ type: "home_debate_proposal_resolved", proposalId: event.proposalId, action: event.action || "cancel", error: event.error || "" }, metadata);
299
306
  if (session && session.mateCreationMode === true && event.type === "mate_creation_proposal" && event.proposal) return Object.assign({ type: "home_mate_creation_proposal", proposal: event.proposal }, metadata);
300
307
  if (session && session.mateCreationMode === true && event.type === "mate_creation_proposal_resolved" && event.proposalId) return Object.assign({ type: "home_mate_creation_proposal_resolved", proposalId: event.proposalId, action: event.action || "cancel", createdMateId: event.mateId || null, mateName: event.mateName || null, error: event.error || "" }, metadata);
308
+ if (event.type === 'tool_executing' && event.name === 'AskUserQuestion' && event.source === 'mcp-import') return Object.assign({ type: 'home_mcp_import_question', toolId: event.id, questions: homeDebateQuestions(event.input) }, metadata);
309
+ if (event.type === 'mcp_import_consent_expired') return Object.assign({ type: 'home_mcp_import_question_resolved', toolId: event.toolId, status: 'expired', error: event.error }, metadata);
310
+ if (session && event.type === 'ask_user_answered' && event.toolId && (session.history || []).some(function (item) { return item.id === event.toolId && item.source === 'mcp-import'; })) return Object.assign({ type: 'home_mcp_import_question_resolved', toolId: event.toolId, status: 'answered', answers: event.answers }, metadata);
301
311
  if (session && session.debateSetupMode === true && event.type === "tool_executing" && event.name === "AskUserQuestion" && event.id && event.input) {
302
312
  return Object.assign({ type: "home_debate_question", toolId: event.id, questions: homeDebateQuestions(event.input) }, metadata);
303
313
  }
@@ -184,6 +184,12 @@ function attachHomeChat(deps) {
184
184
  }
185
185
  });
186
186
  if (!unsubscribe) return;
187
+ var unregisterConsent = null;
188
+ if (openedSession && typeof found.ctx.registerMcpConsentViewer === 'function') {
189
+ try { unregisterConsent = found.ctx.registerMcpConsentViewer(ws, openedSession); } catch (error) {}
190
+ }
191
+ var unsubscribeEvents = unsubscribe;
192
+ unsubscribe = function () { if (unregisterConsent) unregisterConsent(); unsubscribeEvents(); };
187
193
  ws[tapKey] = {
188
194
  unsubscribe: unsubscribe,
189
195
  sessionId: sessionId,
@@ -238,7 +244,7 @@ function attachHomeChat(deps) {
238
244
  requestId: requestId || null,
239
245
  model: session.model || null,
240
246
  vendor: session.vendor || null,
241
- messages: historyToHomeChat(session.history || [], session.debateSetupMode === true, session.mateCreationMode === true),
247
+ messages: historyToHomeChat(session.history || [], session.debateSetupMode === true, session.mateCreationMode === true, session.pendingAskUser),
242
248
  isProcessing: session.isProcessing === true,
243
249
  };
244
250
  if (session.homeClayEntryMode === "search") {
@@ -293,6 +299,7 @@ function attachHomeChat(deps) {
293
299
  homeModels: homeModels, isActorLive: isActorLive, persistActiveSession: persistActiveSession,
294
300
  });
295
301
  var homeMateCreation = attachHomeMateCreation({ findMateProject: findMateProject, resolveHomeSession: resolveHomeSession, sessionReference: sessionReference, setupTap: setupTap, sendHistory: sendHistory, sendSessionList: sendSessionList, sendError: sendError, sendModelError: sendModelError, homeModels: homeModels, isActorLive: isActorLive, persistActiveSession: persistActiveSession });
302
+ var homeMcpConsent = require('./server-home-mcp-consent').attachHomeMcpConsent({ findMateProject: findMateProject, resolveHomeSession: resolveHomeSession, messageTap: messageTap, isActorLive: isActorLive, sendError: sendError });
296
303
  var homeClayEntry = attachHomeClayEntry({ findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, setupSearchTap: setupSearchTap, sendHistory: sendHistory, sendSessionList: sendSessionList, sendError: sendError, sendModelError: sendModelError, homeModels: homeModels, isActorLive: isActorLive });
297
304
 
298
305
  async function openExactSession(ws, found, userId, selected, msg) {
@@ -449,7 +456,7 @@ function attachHomeChat(deps) {
449
456
  }
450
457
  return true;
451
458
  }
452
- if (msg.type !== "issue_reference_resolve" && msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && msg.type !== "home_clay_log_resolve" && msg.type !== "home_mate_present" && msg.type !== "home_mate_open" && msg.type !== "home_mate_sessions_list" && msg.type !== "home_mate_session_open" && msg.type !== "home_mate_send" && msg.type !== "home_mate_new_session" && msg.type !== "home_mate_debate_plan" && msg.type !== "home_debate_proposal_response" && msg.type !== "home_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && msg.type !== "home_mate_creation_proposal_response" && msg.type !== "home_mate_close" && msg.type !== "home_mate_memory_list" && msg.type !== "home_mate_knowledge_list" && msg.type !== "home_mate_models_get" && msg.type !== "home_mate_model_set") {
459
+ if (msg.type !== "issue_reference_resolve" && msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && msg.type !== "home_clay_log_resolve" && msg.type !== "home_mate_present" && msg.type !== "home_mate_open" && msg.type !== "home_mate_sessions_list" && msg.type !== "home_mate_session_open" && msg.type !== "home_mate_send" && msg.type !== "home_mate_new_session" && msg.type !== "home_mate_debate_plan" && msg.type !== "home_debate_proposal_response" && msg.type !== "home_mcp_import_response" && msg.type !== "home_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && msg.type !== "home_mate_creation_proposal_response" && msg.type !== "home_mate_close" && msg.type !== "home_mate_memory_list" && msg.type !== "home_mate_knowledge_list" && msg.type !== "home_mate_models_get" && msg.type !== "home_mate_model_set") {
453
460
  return false;
454
461
  }
455
462
 
@@ -474,6 +481,7 @@ function attachHomeChat(deps) {
474
481
  return true;
475
482
  }
476
483
 
484
+ if (msg.type === "home_mcp_import_response") { homeMcpConsent.respond(ws, userId, msg); return true; }
477
485
  if (msg.type === "issue_reference_resolve") { resolveIssueLink(ws, msg); return true; }
478
486
  if (homeDebates.handle(ws, userId, msg)) return true;
479
487
  if (msg.type === "home_clay_ask") { homeClayEntry.start(ws, userId, msg); return true; }
@@ -0,0 +1,14 @@
1
+ // Home transcript responses retain the project's server-owned consent request.
2
+ function attachHomeMcpConsent(ctx) {
3
+ function respond(ws, userId, msg) {
4
+ var found = ctx.findMateProject(userId, msg.mateId || null, false);
5
+ var session = found && ctx.resolveHomeSession(found, userId, msg.sessionId);
6
+ var tap = ctx.messageTap(ws, msg), pending = session && session.pendingAskUser && session.pendingAskUser[msg.toolId];
7
+ if (!ctx.isActorLive(ws, userId) || !found || !session || !tap || tap.mateSlug !== found.slug || tap.sessionId !== session.localId || (tap.requestId || null) !== (msg.requestId || null) || !pending || !pending.request || pending.request.source !== 'mcp-import') {
8
+ ctx.sendError(ws, found && found.mate.id, 'This owner import consent is unavailable or expired.', msg.requestId || null, msg.sessionId || null, 'question_expired', { toolId: msg.toolId }); return;
9
+ }
10
+ if (!found.ctx.handleHomeAskUserResponse({ type: 'ask_user_response', toolId: msg.toolId, answers: msg.answers || {} }, session, ws)) ctx.sendError(ws, found.mate.id, 'This import consent could not be submitted. No import was authorized.', msg.requestId || null, msg.sessionId, 'question_refused', { toolId: msg.toolId });
11
+ }
12
+ return { respond: respond };
13
+ }
14
+ module.exports = { attachHomeMcpConsent: attachHomeMcpConsent };
@@ -304,7 +304,12 @@ function attachMates(ctx) {
304
304
  return true;
305
305
  }
306
306
  var mateCtx7 = mates.buildMateCtx(userId);
307
- var updated = mates.updateMate(mateCtx7, msg.mateId, msg.updates);
307
+ var updated;
308
+ try { updated = mates.updateMate(mateCtx7, msg.mateId, msg.updates); }
309
+ catch (error) {
310
+ ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: "Could not save this Mate. Your changes were not acknowledged; try again." }));
311
+ return true;
312
+ }
308
313
  if (updated && updated.error) {
309
314
  ws.send(JSON.stringify({ type: "mate_error", mateId: msg.mateId, requestId: msg.requestId, error: updated.error }));
310
315
  return true;
@@ -755,7 +755,10 @@ function attachSettings(ctx) {
755
755
  return false;
756
756
  }
757
757
 
758
- return { handleRequest: handleRequest };
758
+ return {
759
+ handleRequest: handleRequest,
760
+ getInitialAppearance: appearanceSettings.getInitialAppearance
761
+ };
759
762
  }
760
763
 
761
764
  module.exports = { attachSettings: attachSettings };