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
@@ -5,12 +5,14 @@ var browserTools = require("./shared-browser-tools");
5
5
  var createRuntime = require("./shared-browser-runtime").createRuntime;
6
6
 
7
7
  function attachSharedBrowser(ctx) {
8
+ var storageStore = ctx.storageStore || require("./shared-browser-storage").attachStorageStore(require("./config").CONFIG_DIR);
8
9
  var records = new Map();
9
10
  var viewers = new Map();
10
11
  var destroyed = false;
11
12
  function allowed(session, ws) {
12
13
  try {
13
- if (ctx.isMate || !session || ctx.sm.sessions.get(session.localId) !== session) return false;
14
+ if (!session || ctx.sm.sessions.get(session.localId) !== session) return false;
15
+ if (ctx.isMate && ctx.usersModule.isMultiUser() && (!ctx.ownerId || session.ownerId !== ctx.ownerId)) return false;
14
16
  if (ctx.usersModule.isMultiUser()) {
15
17
  var user = ctx.usersModule.findUserById(session.ownerId);
16
18
  if (!user || (ws && (!ws._clayUser || String(ws._clayUser.id) !== String(user.id)))) return false;
@@ -35,7 +37,7 @@ function attachSharedBrowser(ctx) {
35
37
  function projection(record) {
36
38
  if (!record) return null;
37
39
  return { id: record.id, phase: record.phase, control: record.control, epoch: record.epoch, handoff: !!record.handoff,
38
- url: record.url, width: record.width, height: record.height, activity: record.activity || null, activityHistory: record.activityHistory || [], pointer: record.pointer || null, error: record.error || "" };
40
+ browserLink: record.url ? "browser:" + record.url : null, incognito: record.incognito, personalCookiesAvailable: !!ctx.isMate, url: record.url, width: record.width, height: record.height, activity: record.activity || null, activityHistory: record.activityHistory || [], pointer: record.pointer || null, error: record.error || "" };
39
41
  }
40
42
  function sessionRecords(session) {
41
43
  return Array.from(records.values()).filter(function (record) { return record.session === session && recordAllowed(record); });
@@ -93,6 +95,13 @@ function attachSharedBrowser(ctx) {
93
95
  if ((record.activity || record.pointer) && record.activityGeneration !== Number(record.session._sdkQueryGeneration || 0)) {
94
96
  activityHistory.interruptActivity(record); record.pointer = null; broadcast(record);
95
97
  }
98
+ if (message.type === "navigation") {
99
+ try { record.navigationUrl = policy.browserUrl(message.navigationUrl || message.url); } catch (error) { record.navigationUrl = null; }
100
+ }
101
+ if (message.type === "navigation" && !record.incognito) {
102
+ try { storageStore.visit(record.cookieOwner, message.url); }
103
+ catch (error) { end(record, "Could not save personal browsing history"); return; }
104
+ }
96
105
  if (message.type === "ready") { record.phase = "live"; record.resolveReady(); }
97
106
  if (message.type === "error") { end(record, message.error); return; }
98
107
  if (message.url !== undefined) record.url = policy.visibleUrl(message.url);
@@ -108,10 +117,12 @@ function attachSharedBrowser(ctx) {
108
117
  }
109
118
  // Every new tab starts shared. Exclusive human mode is explicit, per tab, and only the human clears it,
110
119
  // so an agent open never reuses (or bypasses) an exclusive tab.
111
- function create(session, actor, newTab) {
120
+ function create(session, actor, newTab, incognito) {
112
121
  requireAccess(session);
122
+ if (incognito !== undefined && typeof incognito !== "boolean") throw new Error("Invalid incognito mode");
123
+ var temporary = !ctx.isMate || incognito === true;
113
124
  var tabs = sessionRecords(session);
114
- var previous = !newTab && tabs.find(function (record) { return record.runtime && (actor === "user" || record.control === "agent"); });
125
+ var previous = !newTab && tabs.find(function (record) { return record.runtime && (incognito === undefined || record.incognito === temporary) && (actor === "user" || record.control === "agent"); });
115
126
  if (previous) return previous;
116
127
  if (tabs.length >= 12) {
117
128
  var expired = tabs.find(function (record) { return !record.runtime; });
@@ -122,12 +133,15 @@ function attachSharedBrowser(ctx) {
122
133
  records.forEach(function (record) { if (record.runtime && record.ownerId === session.ownerId) active++; });
123
134
  if (active >= 4) throw new Error("End another browser before opening more than four active tabs");
124
135
  var record = { session: session, ownerId: session.ownerId, identity: identityKey(session), id: crypto.randomUUID(), phase: "starting", control: "agent", epoch: 0,
125
- url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
136
+ cookieOwner: ctx.isMate ? (ctx.ownerId || session.ownerId || "default") : null, incognito: temporary, url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
126
137
  record.ready = new Promise(function (resolve, reject) { record.resolveReady = resolve; record.rejectReady = reject; });
127
138
  record.ready.catch(function () {});
128
139
  records.set(record.id, record);
129
140
  try {
130
- record.runtime = (ctx.createRuntime || createRuntime)({ identity: ctx.getIdentity(session), onEvent: function (message) { receive(record, message); } });
141
+ record.runtime = (ctx.createRuntime || createRuntime)({ identity: ctx.getIdentity(session), storage: temporary ? null : {
142
+ read: function () { if (!recordAllowed(record)) throw new Error("Browser access was revoked"); return storageStore.read(record.cookieOwner); },
143
+ exchange: function (previous, current) { if (!recordAllowed(record)) throw new Error("Browser access was revoked"); return storageStore.exchange(record.cookieOwner, previous, current); },
144
+ }, onEvent: function (message) { receive(record, message); } });
131
145
  } catch (e) { end(record, e.message); }
132
146
  broadcast(record, true);
133
147
  return record;
@@ -186,6 +200,7 @@ function attachSharedBrowser(ctx) {
186
200
  requireAccess(session);
187
201
  if (actor === "agent" && Number(session._sdkQueryGeneration || 0) !== generation) throw new Error("Browser result belongs to an older query");
188
202
  if (!recordAllowed(record) || records.get(record.id) !== record || record.epoch !== epoch) throw new Error("Browser control changed during the action");
203
+ if (result && result.url !== undefined) record.url = policy.visibleUrl(result.url);
189
204
  return result;
190
205
  }
191
206
  async function tool(session, args) {
@@ -196,8 +211,10 @@ function attachSharedBrowser(ctx) {
196
211
  var value;
197
212
  if (args.action === "status") value = { browsers: sessionRecords(session).map(projection) };
198
213
  else if (args.action === "open") {
214
+ if (args.incognito !== undefined && typeof args.incognito !== "boolean") throw new Error("Invalid incognito mode");
199
215
  var url = args.url ? policy.browserUrl(args.url) : null;
200
- record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true);
216
+ record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true, args.incognito);
217
+ if (args.incognito !== undefined && record.incognito !== (!ctx.isMate || args.incognito)) throw new Error("This tab has a different cookie mode. Open a new tab with the requested mode.");
201
218
  if (url) await run(session, record, "agent", "action", { kind: "navigate", url: url }, args.intent);
202
219
  value = projection(record);
203
220
  } else if (args.action === "finish") {
@@ -206,14 +223,27 @@ function attachSharedBrowser(ctx) {
206
223
  } else if (args.action === "inspect") {
207
224
  value = await run(session, record, "agent", "inspect", null, args.intent);
208
225
  value.browserId = record.id;
226
+ value.browserLink = record.url ? "browser:" + record.url : null;
209
227
  var image = value.image; delete value.image;
210
228
  return { content: [{ type: "text", text: JSON.stringify(value) }, { type: "image", mimeType: "image/jpeg", data: image }] };
211
229
  } else {
212
230
  value = await run(session, record, "agent", "action", policy.validateAction(Object.assign({}, args, { kind: args.action })), args.intent);
213
231
  }
232
+ if (value && value.url) value.browserLink = "browser:" + policy.visibleUrl(value.url);
214
233
  return { content: [{ type: "text", text: JSON.stringify(value) }] };
215
234
  } catch (e) { return { isError: true, content: [{ type: "text", text: e.message }] }; }
216
235
  }
236
+ async function openLinkedPage(session, url, incognito) {
237
+ var record = sessionRecords(session).find(function (tab) {
238
+ return tab.runtime && ((tab.phase === "live" && (tab.navigationUrl || tab.url) === url) || (tab.linkOpening && tab.linkOpening.url === url));
239
+ });
240
+ if (record) { if (record.linkOpening) await record.linkOpening.promise; return record; }
241
+ record = create(session, "user", true, incognito);
242
+ record.navigationUrl = url;
243
+ record.linkOpening = { url: url, promise: run(session, record, "user", "action", { kind: "navigate", url: url }) };
244
+ try { await record.linkOpening.promise; } finally { record.linkOpening = null; }
245
+ return record;
246
+ }
217
247
  function handleMessage(ws, msg) {
218
248
  if (!msg.type || msg.type.indexOf("shared_browser_") !== 0) return false;
219
249
  var session = ctx.getSessionForWs(ws);
@@ -221,8 +251,22 @@ function attachSharedBrowser(ctx) {
221
251
  requireAccess(session, ws);
222
252
  if (String(msg.sessionId) !== String(session.localId)) throw new Error("Browser session changed");
223
253
  var record;
254
+ if (msg.type === "shared_browser_settings") {
255
+ if (!ctx.isMate) throw new Error("Personal browser settings are available only in Mates");
256
+ if (typeof msg.requestId !== "string" || msg.requestId.length > 100) throw new Error("Invalid browser settings request");
257
+ var owner = ctx.ownerId || session.ownerId || "default";
258
+ var settings = storageStore.manage(owner, msg.operation, msg.target);
259
+ requireAccess(session, ws);
260
+ ctx.sendTo(ws, { type: "shared_browser_settings", sessionId: session.localId, requestId: msg.requestId, settings: settings });
261
+ return;
262
+ }
224
263
  if (msg.type === "shared_browser_state_request") { sendState(ws, session, sessionRecords(session)[0]); return; }
225
- if (msg.type === "shared_browser_open") { record = create(session, "user", msg.newTab === true); sendState(ws, session, record, true, true); return; }
264
+ if (msg.type === "shared_browser_open") {
265
+ var targetUrl = msg.url === undefined ? null : policy.browserUrl(msg.url);
266
+ if (targetUrl) record = await openLinkedPage(session, targetUrl, msg.incognito);
267
+ else record = create(session, "user", msg.newTab === true, msg.incognito);
268
+ sendState(ws, session, record, true, true); return;
269
+ }
226
270
  record = selectRecord(session, msg.browserId);
227
271
  if (!record || !recordAllowed(record, ws) || msg.browserId !== record.id) throw new Error("Browser session expired");
228
272
  if (msg.type === "shared_browser_view") {
@@ -242,7 +286,7 @@ function attachSharedBrowser(ctx) {
242
286
  await run(session, record, "user", "action", policy.validateAction(msg.event));
243
287
  }
244
288
  }).catch(function (e) {
245
- if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message });
289
+ if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message, requestId: msg.requestId });
246
290
  });
247
291
  return true;
248
292
  }
@@ -257,7 +301,7 @@ function attachSharedBrowser(ctx) {
257
301
  sweep.unref();
258
302
  function getToolDefs(session) {
259
303
  var generation = Number(session && session._sdkQueryGeneration || 0);
260
- return ctx.isMate ? [] : browserTools.getToolDefs(function (args) {
304
+ return browserTools.getToolDefs(function (args) {
261
305
  if (session && Number(session._sdkQueryGeneration || 0) !== generation) {
262
306
  return Promise.resolve({ isError: true, content: [{ type: "text", text: "This browser tool belongs to an older query" }] });
263
307
  }
@@ -267,8 +311,8 @@ function attachSharedBrowser(ctx) {
267
311
  return {
268
312
  handleMessage: handleMessage,
269
313
  getToolDefs: getToolDefs,
270
- getSystemPrompt: function () { return ctx.isMate ? "" : browserTools.PROMPT; },
271
- createMcpServer: function (adapter, session) { return ctx.isMate || !adapter || typeof adapter.createToolServer !== "function" ? null : adapter.createToolServer({ name: "clay-shared-browser", version: "1.0.0", tools: getToolDefs(session) }); },
314
+ getSystemPrompt: function () { return browserTools.PROMPT; },
315
+ createMcpServer: function (adapter, session) { return !adapter || typeof adapter.createToolServer !== "function" ? null : adapter.createToolServer({ name: "clay-shared-browser", version: "1.0.0", tools: getToolDefs(session) }); },
272
316
  destroy: function () { destroyed = true; clearInterval(sweep); records.forEach(function (record) { end(record); }); records.clear(); viewers.clear(); },
273
317
  };
274
318
  }
package/lib/project.js CHANGED
@@ -348,6 +348,10 @@ function createProjectContext(opts) {
348
348
  function getRuntimeEnvForSession(session) {
349
349
  var linuxUser = (session && session.linuxUser) || getLinuxUserForSession(session);
350
350
  var osUserInfo = getOsUserInfoForLinuxUser(linuxUser);
351
+ return resolveOwnerRuntimeEnv(osUserInfo);
352
+ }
353
+
354
+ function resolveOwnerRuntimeEnv(osUserInfo) {
351
355
  var baseEnv = osUserInfo ? buildUserEnv(osUserInfo) : process.env;
352
356
  var shared = typeof opts.onGetSharedEnv === "function" ? opts.onGetSharedEnv() : null;
353
357
  var projectEnv = typeof opts.onGetProjectEnv === "function" ? opts.onGetProjectEnv(slug) : null;
@@ -709,7 +713,22 @@ function createProjectContext(opts) {
709
713
 
710
714
  // Personal computer tools use the extension; remote URLs are owner-bound.
711
715
  var _mcp = attachMcp({
712
- sendTo: sendTo, slug: slug, clients: clients,
716
+ sendTo: sendTo, slug: slug, clients: clients, cwd: cwd,
717
+ userInput: function (session) { return _askUser.createHandler(session); },
718
+ runtimeEnvForSession: getRuntimeEnvForSession,
719
+ canDiscoverSession: function (session) { var scope = _requestAccess.fileScope({ _clayUser: session.ownerId && usersModule.findUserById(session.ownerId) }); return !!scope.identity || !scope.projectBound; },
720
+ sessionVendor: function (session) { return session.vendor || sm.defaultVendor || "claude"; },
721
+ runtimeEnv: function (owner, ws) { return resolveOwnerRuntimeEnv(_requestAccess.fileScope(ws).identity); },
722
+ canDiscover: function (ws) { var scope = _requestAccess.fileScope(ws); return !!scope.identity || !scope.projectBound; },
723
+ ownerIdentity: function (owner) {
724
+ if (!osUsers) {
725
+ if (usersModule.isMultiUser() && (usersModule.getAllUsers().length !== 1 || !usersModule.findUserById(owner))) throw new Error('Local MCP requires OS user mapping in a shared installation.');
726
+ return null;
727
+ }
728
+ var user = usersModule.findUserById(owner);
729
+ if (!user || !user.linuxUser) throw new Error('OS user identity is unavailable.');
730
+ return getOsUserInfoForWs({ _clayUser: user });
731
+ },
713
732
  root: require("./config").CONFIG_DIR,
714
733
  isMultiUser: function () { return usersModule.isMultiUser(); },
715
734
  canAccess: function (ws) { return _requestAccess.canAccessProject(ws, slug); },
@@ -769,7 +788,7 @@ function createProjectContext(opts) {
769
788
  },
770
789
  });
771
790
  var _sharedBrowser = attachSharedBrowser({
772
- slug: slug, isMate: isMate, sm: sm, clients: clients, sendTo: sendTo,
791
+ slug: slug, isMate: isMate, ownerId: projectOwnerId, sm: sm, clients: clients, sendTo: sendTo,
773
792
  usersModule: usersModule, requestAccess: _requestAccess, osUsers: osUsers,
774
793
  getSessionForWs: getSessionForWs,
775
794
  getIdentity: function (session) {
@@ -780,6 +799,7 @@ function createProjectContext(opts) {
780
799
  var _sessionDocument = attachSessionDocument({
781
800
  cwd: cwd,
782
801
  isMate: isMate,
802
+ canUseSession: _sessionNotes.canUseSession,
783
803
  sendToSession: sendToSession,
784
804
  FS_MAX_SIZE: FS_MAX_SIZE,
785
805
  getOsUserInfoForSession: function (session) {
@@ -839,7 +859,12 @@ function createProjectContext(opts) {
839
859
  sendTo: sendTo, getClients: function () { return clients; }, startWork: _issueWork,
840
860
  });
841
861
  var _sessionGithub = require("./project-session-github").attachSessionGithub({
842
- cwd: cwd, sm: sm, isMate: isMate, osUsers: osUsers,
862
+ cwd: cwd, sm: sm, isMate: isMate, osUsers: osUsers, projectSlug: slug, sendTo: sendTo,
863
+ userInput: function (session) { return _askUser.createHandler(session); },
864
+ canAccess: function (ws) {
865
+ return !isMate && clients.has(ws) && ws.readyState === 1 && (!usersModule.isMultiUser() ||
866
+ !!(ws._clayUser && usersModule.findUserById(ws._clayUser.id) && opts.canAccessProjectSlug(ws._clayUser.id, slug)));
867
+ },
843
868
  canUse: function (session) {
844
869
  if (session.hidden || session.delegated || session._pairDelegation || session._delegatedBy || require("./session-provenance").isWorker(session)) return false;
845
870
  if (!usersModule.isMultiUser()) return true;
@@ -858,9 +883,10 @@ function createProjectContext(opts) {
858
883
  });
859
884
  var _sessionLinear = require("./project-session-linear").attachSessionLinear({
860
885
  sm: sm, isMate: isMate, storage: opts.linearStorage, projectSlug: slug,
886
+ userInput: function (session) { return _askUser.createHandler(session); },
861
887
  isMultiUser: usersModule.isMultiUser, sendTo: sendTo,
862
888
  canAccess: function (ws) {
863
- return !isMate && clients.has(ws) && ws.readyState === 1 && (!usersModule.isMultiUser() ||
889
+ return clients.has(ws) && ws.readyState === 1 && (!usersModule.isMultiUser() ||
864
890
  !!(ws._clayUser && usersModule.findUserById(ws._clayUser.id) && opts.canAccessProjectSlug(ws._clayUser.id, slug)));
865
891
  },
866
892
  startWork: require('./project-linear-launch').attachLinearLaunch({
@@ -1055,14 +1081,12 @@ function createProjectContext(opts) {
1055
1081
  }
1056
1082
  }
1057
1083
 
1058
- // Shared sticky-note memory (main projects only).
1059
- if (!isMate) {
1060
- try {
1061
- var sessionNotesMcpConfig = _sessionNotes.createMcpServer(adapter);
1062
- if (sessionNotesMcpConfig) servers[sessionNotesMcpConfig.name || "clay-notes"] = sessionNotesMcpConfig;
1063
- } catch (e) {
1064
- console.error("[project] Failed to create session notes MCP server:", e.message);
1065
- }
1084
+ // Shared sticky-note memory is available in both owner-private projects and Mates.
1085
+ try {
1086
+ var sessionNotesMcpConfig = _sessionNotes.createMcpServer(adapter);
1087
+ if (sessionNotesMcpConfig) servers[sessionNotesMcpConfig.name || "clay-notes"] = sessionNotesMcpConfig;
1088
+ } catch (e) {
1089
+ console.error("[project] Failed to create session notes MCP server:", e.message);
1066
1090
  }
1067
1091
 
1068
1092
  // Project Logs. Project sessions get the project-scoped set; Mate projects
@@ -1107,13 +1131,11 @@ function createProjectContext(opts) {
1107
1131
  if (sharedBrowserConfig) servers["clay-shared-browser"] = sharedBrowserConfig;
1108
1132
 
1109
1133
  // Explicit agent signal for user-requested Markdown presentation.
1110
- if (!isMate) {
1111
- try {
1112
- var sessionDocumentMcpConfig = _sessionDocument.createMcpServer(adapter);
1113
- if (sessionDocumentMcpConfig) servers[sessionDocumentMcpConfig.name || "clay-documents"] = sessionDocumentMcpConfig;
1114
- } catch (e) {
1115
- console.error("[project] Failed to create session document MCP server:", e.message);
1116
- }
1134
+ try {
1135
+ var sessionDocumentMcpConfig = _sessionDocument.createMcpServer(adapter);
1136
+ if (sessionDocumentMcpConfig) servers[sessionDocumentMcpConfig.name || "clay-documents"] = sessionDocumentMcpConfig;
1137
+ } catch (e) {
1138
+ console.error("[project] Failed to create session document MCP server:", e.message);
1117
1139
  }
1118
1140
 
1119
1141
  // Debate MCP server (available to both mates and main project)
@@ -2336,6 +2358,8 @@ function createProjectContext(opts) {
2336
2358
  function getMcpBridgeHandler(sessionId, sessionOnly, queryGeneration) {
2337
2359
  var boundSession = Number.isInteger(sessionId) ? sm.sessions.get(sessionId) : null;
2338
2360
  var queryToolBridge = createSessionQueryToolBridge({ session: boundSession, sdk: sdk });
2361
+ var externalQueryBridge = require('./mcp-query-bridge').createQueryBridge({ valid: sessionQueryValid,
2362
+ getServers: function () { return _mcp.getMcpServers(boundSession, sessionQueryValid()); } });
2339
2363
  function sessionQueryValid() {
2340
2364
  return !!boundSession
2341
2365
  && Number.isInteger(queryGeneration)
@@ -2460,7 +2484,7 @@ function createProjectContext(opts) {
2460
2484
  }
2461
2485
  }
2462
2486
 
2463
- if (sessionOnly) return Promise.resolve(tools);
2487
+ if (sessionOnly) return Promise.resolve(tools.concat(externalQueryBridge.list()));
2464
2488
 
2465
2489
  // In-app MCP servers (debate, browser, email).
2466
2490
  // Use getLocalMcpServers() so clay-browser is hidden unless the
@@ -2480,7 +2504,7 @@ function createProjectContext(opts) {
2480
2504
  }
2481
2505
 
2482
2506
  // Personal computer and remote URL connections.
2483
- var remoteServers = _mcp.getMcpServers(boundSession);
2507
+ var remoteServers = sessionQueryValid() ? _mcp.getMcpServers(boundSession, sessionQueryValid()) : {};
2484
2508
  if (remoteServers) {
2485
2509
  var remoteNames = Object.keys(remoteServers);
2486
2510
  for (var ri = 0; ri < remoteNames.length; ri++) {
@@ -2578,7 +2602,7 @@ function createProjectContext(opts) {
2578
2602
  if (serverName === "clay-sessions" || serverName === "clay-notes" || serverName === "clay-handoff" || serverName === "clay-documents" || serverName === "clay-shared-browser" || serverName === "clay-debate" || serverName === "clay-mate-creation" || serverName === "clay-workspace" || serverName === "clay-history" || serverName === "clay-github" || serverName === "clay-linear" || serverName === "clay-issues" || serverName === "clay-logs" || serverName === "clay-knowledge" || serverName === "clay-you") {
2579
2603
  return Promise.reject(new Error("Session-bound tool requires a valid Clay session: " + serverName + "/" + toolName));
2580
2604
  }
2581
- if (sessionOnly) return Promise.reject(new Error("Session tool not found: " + serverName + "/" + toolName));
2605
+ if (sessionOnly) return externalQueryBridge.call(serverName, toolName, args);
2582
2606
  // Try in-app servers first (gated by extension connectivity for clay-browser).
2583
2607
  var localMcp = getLocalMcpServers(boundSession);
2584
2608
  if (localMcp && localMcp[serverName]) {
@@ -2590,17 +2614,7 @@ function createProjectContext(opts) {
2590
2614
  }
2591
2615
  }
2592
2616
  }
2593
- // Try remote/local proxy servers
2594
- var remoteServers = _mcp.getMcpServers(boundSession);
2595
- if (remoteServers && remoteServers[serverName]) {
2596
- var rServer = remoteServers[serverName];
2597
- if (rServer.instance && rServer.instance._registeredTools && rServer.instance._registeredTools[toolName]) {
2598
- var rHandler = rServer.instance._registeredTools[toolName].handler;
2599
- if (typeof rHandler === "function") {
2600
- return Promise.resolve(rHandler(args));
2601
- }
2602
- }
2603
- }
2617
+ if (externalQueryBridge.list().some(function (tool) { return tool.server === serverName && tool.name === toolName; })) return externalQueryBridge.call(serverName, toolName, args);
2604
2618
  return Promise.reject(new Error("Tool not found: " + serverName + "/" + toolName));
2605
2619
  },
2606
2620
  };
@@ -2917,7 +2931,8 @@ function createProjectContext(opts) {
2917
2931
  unregisterHomeDebateClient: function (ws) { homeDebateClients.delete(ws); },
2918
2932
  handleHomeDebateProposalResponse: function (ws, msg, session) { return _debateProposal.handleHomeMessage(ws, msg, session); },
2919
2933
  handleHomeMateCreationProposalResponse: function (ws, msg, session) { return _mateCreationProposal.handleHomeMessage(ws, msg, session); },
2920
- handleHomeAskUserResponse: function (msg, session) { return _sessions.respondToAskUser(session, msg); },
2934
+ registerMcpConsentViewer: _mcp.registerConsentViewer,
2935
+ handleHomeAskUserResponse: function (msg, session, ws) { return _sessions.respondToAskUser(session, msg, ws); },
2921
2936
  handleHomePermissionResponse: function (ws, msg, session) { return _sessions.respondToHomePermission(ws, msg, session); },
2922
2937
  stopHomeSession: function (session) { return _sessions.stopHomeSession(session); },
2923
2938
  handleHomeDebateControl: function (ws, msg, session) { return _debate.handleHomeControl(ws, msg, session); },
package/lib/public/app.js CHANGED
@@ -285,6 +285,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
285
285
  mcpConnections: { screen: "list", servers: [], search: "", loaded: false },
286
286
  sharedBrowserOpen: false,
287
287
  sharedBrowserUi: {},
288
+ sharedBrowserSettings: {},
289
+ browserLinksBound: false,
288
290
  // session / routing
289
291
  basePath: basePath,
290
292
  wsPath: wsPath,
@@ -324,9 +326,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
324
326
  githubWorkSessions: [],
325
327
  githubWorkProject: null,
326
328
  githubWorkRefresh: null,
329
+ workPrimaryPending: null,
327
330
  activeProjectSlug: null,
328
331
  activeProjectMateId: null,
329
332
  activeProjectMateName: '',
333
+ clayReturnTarget: null,
334
+ clayReturnPendingProjectSlug: null,
330
335
  activeSessionMateId: null,
331
336
  activeSessionMateName: '',
332
337
  sessionActivatedProjectSlug: null,
@@ -0,0 +1,14 @@
1
+ (function () {
2
+ var root = document.documentElement;
3
+ var light = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches;
4
+ var seed = window.__clayAppearanceSeed;
5
+
6
+ function applyMode(mode) {
7
+ if (mode !== "light" && mode !== "dark") return;
8
+ root.classList.toggle("light-theme", mode === "light");
9
+ root.classList.toggle("dark-theme", mode !== "light");
10
+ }
11
+
12
+ var savedMode = seed && seed.configured && (seed.mode === "light" || seed.mode === "dark") ? seed.mode : null;
13
+ applyMode(savedMode || (light ? "light" : "dark"));
14
+ }());
@@ -4,6 +4,35 @@
4
4
  padding: 0;
5
5
  cursor: pointer;
6
6
  }
7
+ .clay-brand-flip {
8
+ position: relative;
9
+ display: block;
10
+ width: 38px;
11
+ height: 38px;
12
+ transform-style: preserve-3d;
13
+ transition: transform 220ms cubic-bezier(.2,.75,.25,1);
14
+ }
15
+ .clay-brand-face {
16
+ position: absolute;
17
+ inset: 0;
18
+ width: 38px;
19
+ height: 38px;
20
+ object-fit: contain;
21
+ backface-visibility: hidden;
22
+ transition: opacity 120ms ease;
23
+ }
24
+ .clay-brand-avatar { transform: rotateY(180deg); border-radius: 11px; }
25
+ .icon-strip-brand[data-clay-mode="mate"] .clay-brand-flip { transform: rotateY(180deg); }
26
+ .icon-strip-brand:hover .clay-brand-flip,
27
+ .icon-strip-brand:focus-visible .clay-brand-flip,
28
+ .icon-strip-brand.clay-speaking[data-clay-mode="project"] .clay-brand-flip { transform: rotateY(180deg); }
29
+ .icon-strip-brand[data-clay-mode="mate"]:hover .clay-brand-flip,
30
+ .icon-strip-brand[data-clay-mode="mate"]:focus-visible .clay-brand-flip { transform: rotateY(0deg); }
31
+ .icon-strip-brand.clay-speaking[data-clay-mode="mate"] .clay-brand-flip { transform: rotateY(180deg); }
32
+ .icon-strip-brand.clay-speaking .clay-brand-face { transition-duration: 80ms; }
33
+ @media (prefers-reduced-motion: reduce) {
34
+ .clay-brand-flip, .clay-brand-face { transition: none; }
35
+ }
7
36
  .icon-strip-brand:hover::before {
8
37
  background: var(--bg-alt);
9
38
  box-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.08);
@@ -87,6 +87,8 @@
87
87
  }
88
88
 
89
89
  .home-debate-question-text { margin: 0 0 14px; font-size: 15px; }
90
+ .mcp-import-consent .home-debate-question-text,
91
+ .mcp-import-consent .ask-user-question-text { white-space: pre-line; font-size: 13px; line-height: 1.6; }
90
92
  .home-debate-question-form { display: grid; gap: 8px; }
91
93
  .home-debate-question-option {
92
94
  display: grid;
@@ -0,0 +1,12 @@
1
+ /* Coin-size portraits in an explicit, compact visual catalog. */
2
+ .home-mate-avatar-selection { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
3
+ .home-mate-avatar-selection p { color: var(--text-secondary); font-size: 12px; }
4
+ .home-mate-avatar-editor h4 { margin: 16px 0 8px; font-size: 13px; color: var(--text); }
5
+ .home-mate-avatar-choices { grid-template-columns: repeat(auto-fill, minmax(54px, 1fr)); gap: 5px; }
6
+ .home-mate-avatar-choice img { width: 42px; height: 42px; }
7
+ .home-mate-avatar-choice[aria-pressed="true"] { border: 2px solid var(--brand-indigo); padding: 4px 2px 5px; }
8
+ .home-mate-avatar-choice:disabled { cursor: default; opacity: .65; }
9
+ .home-mate-avatar-actions { position: sticky; bottom: 0; padding: 10px 0; background: var(--bg); z-index: 1; }
10
+ .home-mate-avatar-actions button.primary { background: var(--brand-indigo); color: white; border-color: var(--brand-indigo); }
11
+ @media (pointer: coarse) { .home-mate-avatar-choice { min-height: 62px; } }
12
+ .home-mate-avatar-actions .home-mate-avatar-status { flex: 0 0 100%; margin: 0; padding-top: 0; }
@@ -1,11 +1,16 @@
1
1
  /* Restore the Home conversation's reading measure inside Mate projects.
2
2
  The existing project renderer, queue and composer still own behavior. */
3
- body.mate-workspace-active { --content-width: 720px; }
4
- body.mate-workspace-active #messages { padding-inline: max(0px, calc((100% - 720px) / 2)); }
3
+ body.mate-workspace-active {
4
+ --mate-content-width: 860px;
5
+ --content-width: var(--mate-content-width);
6
+ }
7
+ body.mate-workspace-active #messages {
8
+ padding-inline: max(12px, calc((100% - var(--mate-content-width)) / 2));
9
+ }
5
10
  body.mate-workspace-active #messages > .msg-assistant,
6
11
  body.mate-workspace-active #messages > .msg-user {
7
12
  width: 100%;
8
- max-width: 720px;
13
+ max-width: var(--mate-content-width);
9
14
  box-sizing: border-box;
10
15
  margin: 0 auto 12px;
11
16
  padding: 4px 20px;
@@ -66,7 +71,7 @@ body.mate-workspace-active .msg-user:not(.msg-user-other):not(.msg-user-delegate
66
71
 
67
72
  /* One writing row, with secondary tools and model selection underneath.
68
73
  Display contents preserves the live controls and their event handlers. */
69
- body.mate-workspace-active #input-wrapper { max-width: 720px; }
74
+ body.mate-workspace-active #input-wrapper { max-width: var(--mate-content-width); }
70
75
  body.mate-workspace-active #input-row,
71
76
  body.mate-workspace-active #input-row:focus-within {
72
77
  display: grid;
@@ -144,7 +149,7 @@ body.mate-workspace-active #messages > .mate-thinking,
144
149
  body.mate-workspace-active #messages > .mate-permission,
145
150
  body.mate-workspace-active #messages > .mate-ask-user {
146
151
  box-sizing: border-box;
147
- max-width: 720px;
152
+ max-width: var(--mate-content-width);
148
153
  margin: 0 auto 12px;
149
154
  padding: 4px 20px;
150
155
  gap: 9px;
@@ -114,9 +114,29 @@
114
114
  .github-state-merged .github-work-status svg, .github-state-closed .github-work-status svg { color: #a371f7; }
115
115
  .github-state-draft .github-work-status svg, .github-state-unknown .github-work-status svg { color: var(--text-muted, #888); }
116
116
  #header-github-links { min-width: 0; max-width: 200px; flex: 0 0 auto; margin-left: 4px; }
117
- .github-work-details { flex-wrap: wrap; padding: 8px 0; gap: 8px 12px; }
117
+ .github-work-details { flex-wrap: wrap; padding: 8px 0; gap: 8px 12px; align-items: flex-start; }
118
118
  .github-work-action { color: var(--text-muted, #888); text-decoration: none; font-size: 11px; }
119
119
  .github-work-action:hover { text-decoration: underline; }
120
+ .github-work-pr-group {
121
+ display: grid;
122
+ grid-template-columns: minmax(0, auto) minmax(0, 1fr);
123
+ gap: 3px 10px;
124
+ flex: 1 1 100%;
125
+ min-width: 0;
126
+ padding-top: 7px;
127
+ border-top: 1px solid color-mix(in srgb, var(--text, #888) 14%, transparent);
128
+ }
129
+ .github-work-pr-header,
130
+ .github-work-pr-actions,
131
+ .github-work-pr-checks { min-width: 0; }
132
+ .github-work-pr-header { grid-row: span 2; }
133
+ .github-work-pr-actions,
134
+ .github-work-pr-checks { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 10px; }
135
+ .github-work-pr-checks { grid-column: 2; }
136
+ .github-work-check { display: inline-flex; min-width: 0; max-width: min(100%, 220px); align-items: baseline; gap: 4px; }
137
+ .github-work-check-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
138
+ .github-work-check-state { flex: 0 0 auto; white-space: nowrap; }
139
+ .github-work-pr-group .session-github-link { max-width: 100%; }
120
140
  @media (max-width: 600px) {
121
141
  #header-github-links { max-width: 82px; }
122
142
  .mobile-session-item .session-github-link { min-height: 28px; padding-right: 4px; }
@@ -210,3 +230,25 @@
210
230
  .mobile-session-item .session-row-github-links .github-work-more { padding-inline: 2px; }
211
231
  .mobile-session-item .session-row-github-links { min-height: 28px; }
212
232
  }
233
+
234
+ .work-primary-group + .work-primary-group { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border, #444); }
235
+ .work-primary-group h3 { margin: 0 0 4px; color: var(--text-muted, #888); font-size: 11px; font-weight: 500; }
236
+ .work-primary-row { display: grid; grid-template-columns: minmax(0, 1fr) 22px; gap: 5px; align-items: start; padding: 5px 0; }
237
+ .work-primary-row .github-work-entry { padding: 0; }
238
+ .work-primary-row .session-linear-link { min-width: 0; max-width: 100%; box-sizing: border-box; }
239
+ .work-primary-row .linear-work-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
240
+ .work-primary-choice { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 4px; background: transparent; color: var(--accent, #5857fc); opacity: .65; cursor: pointer; touch-action: manipulation; }
241
+ .work-primary-choice svg { width: 13px; height: 13px; transform: rotate(35deg); }
242
+ .work-primary-choice[aria-pressed="true"] .work-primary-pin-head { fill: currentColor; }
243
+ .work-primary-choice:hover:not(:disabled) { opacity: 1; background: color-mix(in srgb, var(--accent, #5857fc) 10%, transparent); }
244
+ .work-primary-choice:focus-visible { opacity: 1; outline: 2px solid var(--accent, #5857fc); outline-offset: 2px; }
245
+ .work-primary-choice[aria-pressed="true"] { color: var(--accent, #5857fc); opacity: 1; cursor: default; }
246
+ .work-primary-choice:disabled:not([aria-pressed="true"]) { opacity: .5; cursor: wait; }
247
+ .work-primary-feedback { margin: 6px 0 0; color: var(--text-muted, #888); font-size: 11px; }
248
+ .work-primary-feedback:empty { display: none; }
249
+ @media (max-width: 600px), (pointer: coarse) {
250
+ .work-primary-row { grid-template-columns: minmax(0, 1fr) 28px; }
251
+ .work-primary-choice { width: 28px; height: 28px; }
252
+ }
253
+ .work-primary-row .linear-work-id, .work-primary-row .linear-work-state { flex: none; white-space: nowrap; }
254
+ .work-primary-row .linear-work-state { max-width: 62px; overflow: hidden; text-overflow: ellipsis; }
@@ -29,7 +29,7 @@
29
29
  .shared-browser-monitor-stand::before { content: ""; position: absolute; top: 0; bottom: 3px; left: 30%; right: 30%; background: linear-gradient(#aaa69c, #d2cdbf); clip-path: polygon(18% 0, 82% 0, 100% 100%, 0 100%); }
30
30
  .shared-browser-monitor-stand::after { content: ""; position: absolute; inset: auto 0 0; height: 4px; background: #d8d3c6; border-radius: 2px; box-shadow: 0 1px 0 #9b978d, 0 6px 10px rgba(0,0,0,.22); }
31
31
  .shared-browser-monitor-label { color: #a5aaaf; font-size: 10px; font-variant-numeric: tabular-nums; letter-spacing: .04em; }
32
- .shared-browser-monitor .shared-browser-empty { color: #a5aaaf; }
32
+ .shared-browser-monitor .shared-browser-empty { color: #a5aaaf; background: #111315; }
33
33
  .shared-browser-monitor .shared-browser-empty h3 { color: #eceeef; }
34
34
  .shared-browser-viewport canvas { display: block; width: 100%; height: 100%; object-fit: contain; touch-action: none; }
35
35
  .shared-browser-keyboard { position: absolute; bottom: 0; left: 50%; width: 1px; height: 1px; opacity: 0; padding: 0; border: 0; }
@@ -96,3 +96,37 @@
96
96
  .shared-browser-tab > button:last-child { padding: 5px; font-size: 15px; color: var(--text-muted); }
97
97
 
98
98
  #shared-browser-toggle-btn[aria-expanded="true"] { color: var(--text); background: rgba(var(--overlay-rgb), .06); }
99
+
100
+ .shared-browser-cookie-mode { font-size: 11px; color: var(--text-muted); margin-right: auto; white-space: nowrap; }
101
+
102
+ /* Personal browser data uses the compact settings anatomy inside the browser panel. */
103
+ .shared-browser-settings { flex: 1; min-height: 0; overflow: auto; padding: 16px 20px; background: var(--bg); color: var(--text); font-size: 13px; }
104
+ .shared-browser-settings[hidden] { display: none; }
105
+ .browser-settings-open > .shared-browser-viewport, .browser-settings-open > .shared-browser-address, .browser-settings-open > .shared-browser-tabs, .browser-settings-open > .shared-browser-footer { display: none; }
106
+ .browser-settings-heading { display: flex; align-items: center; gap: 16px; }
107
+ .browser-settings-heading h3 { font-size: 16px; margin: 0; }
108
+ .shared-browser-settings button { min-height: 28px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-alt); color: var(--text); padding: 4px 8px; font: inherit; cursor: pointer; }
109
+ .shared-browser-settings button:disabled { opacity: .5; cursor: default; }
110
+ .shared-browser-settings button:hover:not(:disabled) { background: var(--accent-bg); }
111
+ .shared-browser-settings button:focus-visible, .shared-browser-settings input:focus-visible, .shared-browser-settings summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
112
+ .browser-settings-muted, .browser-settings-row small, .browser-settings-origin small, .shared-browser-settings summary small { color: var(--text-muted); font-size: 12px; line-height: 1.5; }
113
+ .shared-browser-settings nav { display: flex; gap: 8px; margin: 18px 0 12px; }
114
+ .shared-browser-settings nav [aria-current] { border-color: var(--accent); color: var(--accent); }
115
+ .browser-settings-tools { display: flex; gap: 8px; flex-wrap: wrap; }
116
+ .browser-settings-tools input { flex: 1; min-width: 140px; height: 32px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-alt); color: var(--text); padding: 0 8px; font: inherit; }
117
+ .browser-settings-feedback { font-size: 12px; min-height: 18px; }
118
+ .browser-settings-review { border: 1px solid var(--border); border-radius: 8px; padding: 12px; margin-bottom: 12px; }
119
+ .browser-settings-review p { font-size: 12px; line-height: 1.5; }
120
+ .browser-settings-review button + button { margin-left: 8px; }
121
+ .browser-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
122
+ .browser-settings-row > div { min-width: 0; overflow-wrap: anywhere; }
123
+ .browser-settings-row button { flex-shrink: 0; }
124
+ .browser-settings-row small, .browser-settings-origin small, .shared-browser-settings summary small { display: block; }
125
+ .shared-browser-settings details { border-bottom: 1px solid var(--border); }
126
+ .shared-browser-settings summary { cursor: pointer; padding: 10px 0; overflow-wrap: anywhere; }
127
+ .browser-settings-origin { font-size: 12px; overflow-wrap: anywhere; }
128
+
129
+ .clay-browser-link { max-width: 100%; }
130
+ .clay-browser-link::before { display: none; }
131
+ .clay-browser-link > svg, .clay-browser-link > i { width: 14px; height: 14px; flex-shrink: 0; color: var(--accent); }
132
+ .clay-browser-link > small { max-width: 24em; }
@@ -27,9 +27,12 @@
27
27
  is lazy-loaded by terminal-prefs.js the first time the user selects
28
28
  it (or boots with it already saved). See ensureD2CodingWebfont(). -->
29
29
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
30
- <script>
31
- (function(){var light=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;document.documentElement.classList.add(light?"light-theme":"dark-theme");})();
32
- </script>
30
+ <script src="appearance-bootstrap.js"></script>
31
+ <style>
32
+ html { background: #171715; color: #e8e8e2; }
33
+ html.light-theme { background: #f7f7f3; color: #252522; }
34
+ body { background: transparent; color: inherit; }
35
+ </style>
33
36
  <script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
34
37
  <link rel="stylesheet" href="style.css?v=20260913-scheduled-tasks4">
35
38
  <link rel="stylesheet" href="css/default-ai.css">
@@ -83,7 +86,10 @@
83
86
  <!-- === Icon Strip === -->
84
87
  <div id="icon-strip">
85
88
  <button type="button" class="icon-strip-home icon-strip-brand" aria-label="Open Clay" disabled>
86
- <img class="icon-strip-logo" src="clay-studio-symbol.png" width="38" height="38" alt="">
89
+ <span class="clay-brand-flip" aria-hidden="true">
90
+ <img class="icon-strip-logo clay-brand-face clay-brand-logo" src="clay-studio-symbol.png" width="38" height="38" alt="">
91
+ <img class="clay-brand-face clay-brand-avatar" width="38" height="38" alt="">
92
+ </span>
87
93
  </button>
88
94
  <div class="icon-strip-separator"></div>
89
95
  <button type="button" class="icon-strip-hint" id="icon-strip-hint-project" data-switcher-mode="project" aria-label="Switch project"></button>
Binary file
Binary file
Binary file
Binary file
Binary file