clay-server 3.8.0-beta.1 → 3.8.0-beta.3

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 (40) hide show
  1. package/lib/builtin-mates.js +14 -14
  2. package/lib/mates.js +2 -6
  3. package/lib/project-mate-interaction.js +7 -7
  4. package/lib/project-session-pair.js +84 -4
  5. package/lib/project-sessions.js +1 -1
  6. package/lib/project-user-mention.js +4 -4
  7. package/lib/project-worker-proposal.js +1 -1
  8. package/lib/project.js +4 -3
  9. package/lib/public/css/avatar-imprints.css +46 -0
  10. package/lib/public/css/icon-strip.css +3 -3
  11. package/lib/public/css/pane.css +1 -7
  12. package/lib/public/css/profile.css +123 -95
  13. package/lib/public/css/session-actions.css +40 -9
  14. package/lib/public/css/user-settings.css +1 -1
  15. package/lib/public/index.html +2 -2
  16. package/lib/public/modules/app-cursors.js +2 -2
  17. package/lib/public/modules/app-dm.js +1 -8
  18. package/lib/public/modules/app-messages.js +2 -2
  19. package/lib/public/modules/app-projects.js +7 -6
  20. package/lib/public/modules/app-rate-limit.js +0 -1
  21. package/lib/public/modules/app-rendering.js +0 -3
  22. package/lib/public/modules/avatar-imprint.js +134 -0
  23. package/lib/public/modules/avatar.js +25 -19
  24. package/lib/public/modules/debate.js +10 -5
  25. package/lib/public/modules/mate-sidebar.js +2 -2
  26. package/lib/public/modules/mention.js +26 -16
  27. package/lib/public/modules/profile.js +73 -190
  28. package/lib/public/modules/session-actions.js +5 -3
  29. package/lib/public/modules/sidebar-mates.js +5 -4
  30. package/lib/public/modules/sidebar-sessions.js +1 -1
  31. package/lib/public/modules/split-pair-ui.js +2 -8
  32. package/lib/public/style.css +1 -0
  33. package/lib/server-auth.js +2 -2
  34. package/lib/server-dm.js +8 -8
  35. package/lib/server-mates.js +3 -3
  36. package/lib/server-settings.js +2 -2
  37. package/lib/server.js +5 -4
  38. package/lib/session-pair-mcp-server.js +16 -1
  39. package/lib/users.js +4 -4
  40. package/package.json +1 -1
@@ -18,8 +18,8 @@ var BUILTIN_MATES = [
18
18
  key: "clay",
19
19
  displayName: "Clay",
20
20
  bio: "Your workspace memory. Searches every session, project, and decision you've made and answers from the receipts. The chat surface for the home screen.",
21
- avatarColor: "#7c3aed",
22
- avatarStyle: "bottts",
21
+ avatarColor: "#5857fc",
22
+ avatarStyle: "imprint",
23
23
  avatarCustom: "/clay-studio-symbol.png", // Clay is the app — use the app symbol
24
24
  avatarLocked: true,
25
25
  primary: true, // code-managed, auto-updated on startup
@@ -48,8 +48,8 @@ var BUILTIN_MATES = [
48
48
  key: "ally",
49
49
  displayName: "Ally",
50
50
  bio: "Chief of staff. Quiet, sharp, sees across every mate. Knows what Arch decided yesterday, what Buzz pitched last week, and what you said three weeks ago.",
51
- avatarColor: "#00b894",
52
- avatarStyle: "bottts",
51
+ avatarColor: "#5857fc",
52
+ avatarStyle: "imprint",
53
53
  avatarCustom: "/mates/ally.png",
54
54
  avatarLocked: true,
55
55
  archived: true, // skip seeding for new users; demote on existing
@@ -69,8 +69,8 @@ var BUILTIN_MATES = [
69
69
  key: "arch",
70
70
  displayName: "Arch",
71
71
  bio: "Architect. Stubborn about structure, patient about everything else. Draws boxes and arrows before anyone writes code, and is annoyingly right about which ones will break.",
72
- avatarColor: "#0984e3",
73
- avatarStyle: "bottts",
72
+ avatarColor: "#5857fc",
73
+ avatarStyle: "imprint",
74
74
  seedData: {
75
75
  relationship: "colleague",
76
76
  activity: ["planning", "reviewing"],
@@ -87,8 +87,8 @@ var BUILTIN_MATES = [
87
87
  key: "rush",
88
88
  displayName: "Rush",
89
89
  bio: "Shipper. Impatient in the best way. Cuts your scope in half, ships it before lunch, and somehow it's better than the original plan.",
90
- avatarColor: "#e17055",
91
- avatarStyle: "bottts",
90
+ avatarColor: "#5857fc",
91
+ avatarStyle: "imprint",
92
92
  seedData: {
93
93
  relationship: "colleague",
94
94
  activity: ["coding", "planning"],
@@ -105,8 +105,8 @@ var BUILTIN_MATES = [
105
105
  key: "ward",
106
106
  displayName: "Ward",
107
107
  bio: "Guardian. Quietly anxious in a productive way. Reads error logs for fun and asks \"what happens when the input is null?\" before anyone else thinks of it.",
108
- avatarColor: "#00b894",
109
- avatarStyle: "bottts",
108
+ avatarColor: "#5857fc",
109
+ avatarStyle: "imprint",
110
110
  seedData: {
111
111
  relationship: "reviewer",
112
112
  activity: ["reviewing", "testing"],
@@ -123,8 +123,8 @@ var BUILTIN_MATES = [
123
123
  key: "pixel",
124
124
  displayName: "Pixel",
125
125
  bio: "Designer. Obsessed with the details nobody else notices. Sees the 2px misalignment, the confusing label, the flow that makes sense to the builder but not the user.",
126
- avatarColor: "#e84393",
127
- avatarStyle: "bottts",
126
+ avatarColor: "#5857fc",
127
+ avatarStyle: "imprint",
128
128
  seedData: {
129
129
  relationship: "colleague",
130
130
  activity: ["designing", "reviewing"],
@@ -141,8 +141,8 @@ var BUILTIN_MATES = [
141
141
  key: "buzz",
142
142
  displayName: "Buzz",
143
143
  bio: "Marketer. Competitive, trend-obsessed, always watching what others are doing. Researches your market, tracks what people search for, and turns \"we built a thing\" into a story people care about.",
144
- avatarColor: "#fdcb6e",
145
- avatarStyle: "bottts",
144
+ avatarColor: "#5857fc",
145
+ avatarStyle: "imprint",
146
146
  seedData: {
147
147
  relationship: "colleague",
148
148
  activity: ["writing", "marketing"],
package/lib/mates.js CHANGED
@@ -121,10 +121,6 @@ function createMate(ctx, seedData) {
121
121
  var id = generateMateId();
122
122
  var userId = ctx ? ctx.userId : null;
123
123
 
124
- // Pick a random avatar color from a pleasant palette
125
- var colors = ["#6c5ce7", "#00b894", "#e17055", "#0984e3", "#fdcb6e", "#e84393", "#00cec9", "#ff7675"];
126
- var colorIdx = crypto.randomBytes(1)[0] % colors.length;
127
-
128
124
  var mate = {
129
125
  id: id,
130
126
  name: null,
@@ -134,8 +130,8 @@ function createMate(ctx, seedData) {
134
130
  vendor: (seedData && seedData.vendor) || "claude",
135
131
  profile: {
136
132
  displayName: null,
137
- avatarColor: colors[colorIdx],
138
- avatarStyle: "bottts",
133
+ avatarColor: "#5857fc",
134
+ avatarStyle: "imprint",
139
135
  avatarSeed: crypto.randomBytes(4).toString("hex"),
140
136
  },
141
137
  bio: null,
@@ -39,14 +39,14 @@ function attachMateInteraction(ctx) {
39
39
  vendor: "claude",
40
40
  name: "Claude Code",
41
41
  avatarColor: "#cc785c",
42
- avatarStyle: "bottts",
42
+ avatarStyle: "imprint",
43
43
  avatarSeed: "plain-claude",
44
44
  },
45
45
  "plain:codex": {
46
46
  vendor: "codex",
47
47
  name: "Codex",
48
48
  avatarColor: "#10a37f",
49
- avatarStyle: "bottts",
49
+ avatarStyle: "imprint",
50
50
  avatarSeed: "plain-codex",
51
51
  },
52
52
  };
@@ -554,8 +554,8 @@ function attachMateInteraction(ctx) {
554
554
  }
555
555
 
556
556
  var mateName = (mate.profile && mate.profile.displayName) || mate.name || "Mate";
557
- var avatarColor = (mate.profile && mate.profile.avatarColor) || "#6c5ce7";
558
- var avatarStyle = (mate.profile && mate.profile.avatarStyle) || "bottts";
557
+ var avatarColor = (mate.profile && mate.profile.avatarColor) || "#5857fc";
558
+ var avatarStyle = (mate.profile && mate.profile.avatarStyle) || "imprint";
559
559
  var avatarSeed = (mate.profile && mate.profile.avatarSeed) || mate.id;
560
560
 
561
561
  // Build full mention text (include pasted content)
@@ -800,11 +800,11 @@ function attachMateInteraction(ctx) {
800
800
 
801
801
  function getMateProfile(mateCtx, mateId) {
802
802
  var mate = matesModule.getMate(mateCtx, mateId);
803
- if (!mate) return { name: "Mate", avatarColor: "#6c5ce7", avatarStyle: "bottts", avatarSeed: mateId };
803
+ if (!mate) return { name: "Mate", avatarColor: "#5857fc", avatarStyle: "imprint", avatarSeed: mateId };
804
804
  return {
805
805
  name: (mate.profile && mate.profile.displayName) || mate.name || "Mate",
806
- avatarColor: (mate.profile && mate.profile.avatarColor) || "#6c5ce7",
807
- avatarStyle: (mate.profile && mate.profile.avatarStyle) || "bottts",
806
+ avatarColor: (mate.profile && mate.profile.avatarColor) || "#5857fc",
807
+ avatarStyle: (mate.profile && mate.profile.avatarStyle) || "imprint",
808
808
  avatarSeed: (mate.profile && mate.profile.avatarSeed) || mateId,
809
809
  };
810
810
  }
@@ -203,9 +203,13 @@ function attachSessionPair(ctx) {
203
203
  async function sendToPartner(args, caller) {
204
204
  try {
205
205
  if (caller && caller._delegatedBy) throw new Error("delegated turns cannot delegate to another session");
206
- var resolved = groupAndPartner(caller);
207
206
  var message = typeof args.message === "string" ? args.message.trim() : "";
208
207
  if (!message) throw new Error("message is required");
208
+ var created = null;
209
+ if (caller && !store.groupForMember(caller.localId)) {
210
+ created = createWorkerForDriver(caller, args);
211
+ }
212
+ var resolved = groupAndPartner(caller);
209
213
  var wait = args.wait !== false;
210
214
  var timeout = Number.isFinite(args.timeoutSeconds) ? Math.floor(args.timeoutSeconds) : 300;
211
215
  timeout = Math.max(1, Math.min(900, timeout));
@@ -243,9 +247,14 @@ function attachSessionPair(ctx) {
243
247
  if (!wait) {
244
248
  token.detached = true;
245
249
  monitorPartner(resolved.group, caller, partner, token);
246
- return toolResult({ status: "running", partnerId: partner.localId, hint: "The completed result will be pushed back automatically. Use read_partner only for an interim status check." });
250
+ return toolResult({ status: "running", partnerId: partner.localId, workerCreated: !!created, hint: "The completed result will be pushed back automatically. Use read_partner only for an interim status check." });
251
+ }
252
+ var completed = await waitForPartner(resolved.group, caller, partner, token, timeout);
253
+ if (created) {
254
+ completed.workerCreated = true;
255
+ completed.partnerId = partner.localId;
247
256
  }
248
- return toolResult(await waitForPartner(resolved.group, caller, partner, token, timeout));
257
+ return toolResult(completed);
249
258
  } catch (e) {
250
259
  if (caller) {
251
260
  var group = store.groupForMember(caller.localId);
@@ -277,10 +286,46 @@ function attachSessionPair(ctx) {
277
286
  }
278
287
  }
279
288
 
289
+ function interruptPartner(args, caller) {
290
+ try {
291
+ var resolved = groupAndPartner(caller);
292
+ var partner = resolved.partner;
293
+ if (!partner.isProcessing && !partner._queryStarting) {
294
+ return toolResult({ status: "idle", partnerId: partner.localId, title: partner.title || "New Session" });
295
+ }
296
+ partner.taskStopRequested = true;
297
+ if (partner.abortController) partner.abortController.abort();
298
+ return toolResult({ status: "interrupting", partnerId: partner.localId, title: partner.title || "New Session" });
299
+ } catch (e) {
300
+ return toolError(e);
301
+ }
302
+ }
303
+
304
+ function closePartner(args, caller) {
305
+ try {
306
+ var resolved = groupAndPartner(caller);
307
+ var partner = resolved.partner;
308
+ var interrupted = !!(partner.isProcessing || partner._queryStarting);
309
+ if (interrupted) {
310
+ partner.taskStopRequested = true;
311
+ if (partner.abortController) partner.abortController.abort();
312
+ }
313
+ if (partner._pairDelegation) finishDelegation(resolved.group, caller, partner, partner._pairDelegation);
314
+ var ws = { _clayUser: caller.ownerId ? { id: caller.ownerId } : null };
315
+ var result = store.dissolve(ws, { id: resolved.group.id });
316
+ if (!result.ok) throw new Error(result.error || "could not close the Worker pair");
317
+ return toolResult({ status: "closed", partnerId: partner.localId, interrupted: interrupted, historyPreserved: true });
318
+ } catch (e) {
319
+ return toolError(e);
320
+ }
321
+ }
322
+
280
323
  function getToolDefs(boundSession) {
281
324
  if (!boundSession) return pairMcp.getToolDefs({
282
325
  send: function () { return toolError(new Error("send_to_partner requires a session-bound tool server")); },
283
326
  read: function () { return toolError(new Error("read_partner requires a session-bound tool server")); },
327
+ interrupt: function () { return toolError(new Error("interrupt_partner requires a session-bound tool server")); },
328
+ close: function () { return toolError(new Error("close_partner requires a session-bound tool server")); },
284
329
  });
285
330
  // Mount whenever we have a bound session: MCP servers are fixed for the
286
331
  // lifetime of a query, and Claude queries live across turns, so gating on
@@ -295,6 +340,8 @@ function attachSessionPair(ctx) {
295
340
  var tools = pairMcp.getToolDefs({
296
341
  send: function (args) { return sendToPartner(args, boundSession); },
297
342
  read: function (args) { return readPartner(args, boundSession); },
343
+ interrupt: function (args) { return interruptPartner(args, boundSession); },
344
+ close: function (args) { return closePartner(args, boundSession); },
298
345
  });
299
346
  return tools.concat(workerProposal.getToolDefs(boundSession));
300
347
  }
@@ -343,6 +390,37 @@ function attachSessionPair(ctx) {
343
390
  return { driver: driver, worker: worker, group: result.group };
344
391
  }
345
392
 
393
+ function createWorkerForDriver(driver, args) {
394
+ if (ctx.isMate) throw new Error("Pair sessions are only available in projects");
395
+ var installed = sm.installedVendors || [];
396
+ if (installed.length === 0) throw new Error("no coding agent is installed for a Worker session");
397
+ var vendor = typeof args.workerVendor === "string" ? args.workerVendor.trim() : "";
398
+ if (vendor) validateVendor(vendor);
399
+ if (!vendor) {
400
+ if (installed.indexOf("codex") !== -1 && driver.vendor !== "codex") vendor = "codex";
401
+ else {
402
+ vendor = installed[0];
403
+ for (var i = 0; i < installed.length; i++) {
404
+ if (installed[i] !== driver.vendor) { vendor = installed[i]; break; }
405
+ }
406
+ }
407
+ }
408
+ var ws = {
409
+ _clayActiveSession: driver.localId,
410
+ _clayUser: driver.ownerId ? { id: driver.ownerId } : null,
411
+ };
412
+ var created = createPairRecord(ws, {
413
+ driver: { sessionId: driver.localId },
414
+ worker: {
415
+ vendor: vendor,
416
+ model: typeof args.workerModel === "string" ? args.workerModel : "",
417
+ effort: typeof args.workerEffort === "string" ? args.workerEffort : "",
418
+ },
419
+ });
420
+ sm.sendToSession(driver, { type: "pair_session_created", ok: true, group: created.group });
421
+ return created;
422
+ }
423
+
346
424
  function createPair(ws, msg) {
347
425
  try {
348
426
  var created = createPairRecord(ws, msg);
@@ -390,7 +468,9 @@ function attachSessionPair(ctx) {
390
468
  var group = store.groupForMember(session.localId);
391
469
  var pairPrompt = "";
392
470
  if (group && group.pair && group.pair.driverId === session.localId) {
393
- pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner and read_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. A completed Worker turn leaves the Worker session available for more work. If a Worker turn is interrupted, its work is partial and unverified; review it and decide next steps with the user. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If send_to_partner reports that the Worker is no longer available, create a replacement with spawn_sessions and delegate the remaining implementation rather than taking it over. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
471
+ pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner, read_partner, interrupt_partner, and close_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. The human may also message or stop the Worker directly; treat that intervention as authoritative. Use close_partner immediately when the human asks to close, dismiss, or remove the Worker pane; it preserves the session history. A completed Worker turn leaves the Worker session available for more work. Use interrupt_partner when execution must stop or change direction but the pair should remain open; its work is partial and unverified until you review it. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If the Worker is no longer available, keep the work in the visible Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background session. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
472
+ } else if (!group && !ctx.isMate) {
473
+ pairPrompt = "When the user asks you to open or use a visible Worker, call send_to_partner directly. Clay will create the paired Worker session and open it in the right pane automatically before delivering the task. Do not ask the user to start split mode and do not use a background sub-agent as a substitute for a visible Worker.";
394
474
  }
395
475
  return [pairPrompt, workerProposal.getSystemPrompt(session)].filter(Boolean).join("\n\n");
396
476
  }
@@ -1391,7 +1391,7 @@ function attachSessions(ctx) {
1391
1391
  type: msg.type,
1392
1392
  userId: u.id,
1393
1393
  displayName: p.name || u.displayName || u.username,
1394
- avatarStyle: p.avatarStyle || "thumbs",
1394
+ avatarStyle: p.avatarStyle || "imprint",
1395
1395
  avatarSeed: p.avatarSeed || u.username,
1396
1396
  avatarCustom: p.avatarCustom || "",
1397
1397
  };
@@ -47,9 +47,9 @@ function attachUserMention(ctx) {
47
47
  var u = ws._clayUser;
48
48
  var p = u.profile || {};
49
49
  return {
50
- style: p.avatarStyle || "thumbs",
50
+ style: p.avatarStyle || "imprint",
51
51
  seed: p.avatarSeed || u.username || u.id,
52
- color: p.avatarColor || "#7c3aed",
52
+ color: p.avatarColor || "#5857fc",
53
53
  custom: p.avatarCustom || "",
54
54
  };
55
55
  }
@@ -63,9 +63,9 @@ function attachUserMention(ctx) {
63
63
  id: u.id,
64
64
  name: p.name || u.displayName || u.username || "User",
65
65
  username: u.username,
66
- avatarStyle: p.avatarStyle || "thumbs",
66
+ avatarStyle: p.avatarStyle || "imprint",
67
67
  avatarSeed: p.avatarSeed || u.username,
68
- avatarColor: p.avatarColor || "#7c3aed",
68
+ avatarColor: p.avatarColor || "#5857fc",
69
69
  };
70
70
  }
71
71
 
@@ -247,7 +247,7 @@ function attachWorkerProposal(ctx) {
247
247
  });
248
248
  var followup;
249
249
  if (result.status === "complete") {
250
- followup = "[Worker execution completed]\nReview and verify the Worker's result. The Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Worker-owned files yourself. If that Worker is no longer available, create a replacement with spawn_sessions for the remaining implementation.\n\n" + (result.response || "The Worker completed without a text summary.");
250
+ followup = "[Worker execution completed]\nReview and verify the Worker's result. The Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Worker-owned files yourself. If that Worker is no longer available, keep the work in the visible Worker flow and use send_to_partner again after the stale pair is removed; never substitute a background session.\n\n" + (result.response || "The Worker completed without a text summary.");
251
251
  } else if (result.status === "interrupted") {
252
252
  followup = "[Worker execution interrupted]\nThe user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
253
253
  } else if (result.status === "running") {
package/lib/project.js CHANGED
@@ -344,7 +344,7 @@ function createProjectContext(opts) {
344
344
  id: u.id,
345
345
  displayName: p.name || u.displayName || u.username,
346
346
  username: u.username,
347
- avatarStyle: p.avatarStyle || "thumbs",
347
+ avatarStyle: p.avatarStyle || "imprint",
348
348
  avatarSeed: p.avatarSeed || u.username,
349
349
  avatarCustom: p.avatarCustom || "",
350
350
  });
@@ -1277,8 +1277,9 @@ function createProjectContext(opts) {
1277
1277
  id: u.id,
1278
1278
  displayName: p.name || u.displayName || u.username,
1279
1279
  username: u.username,
1280
- avatarStyle: p.avatarStyle || "thumbs",
1280
+ avatarStyle: p.avatarStyle || "imprint",
1281
1281
  avatarSeed: p.avatarSeed || u.username,
1282
+ avatarColor: p.avatarColor || "#5857fc",
1282
1283
  avatarCustom: p.avatarCustom || "",
1283
1284
  });
1284
1285
  }
@@ -1827,7 +1828,7 @@ function createProjectContext(opts) {
1827
1828
  id: u.id,
1828
1829
  displayName: p.name || u.displayName || u.username,
1829
1830
  username: u.username,
1830
- avatarStyle: p.avatarStyle || "thumbs",
1831
+ avatarStyle: p.avatarStyle || "imprint",
1831
1832
  avatarSeed: p.avatarSeed || u.username,
1832
1833
  avatarCustom: p.avatarCustom || "",
1833
1834
  });
@@ -0,0 +1,46 @@
1
+ /* Clay Imprints use one consistent, pressed-square silhouette everywhere. */
2
+ .activity-inline-avatar,
3
+ .ask-mate-avatar,
4
+ .client-avatar,
5
+ .debate-info-avatar,
6
+ .debate-panel-item-avatar,
7
+ .debate-panelist-avatar,
8
+ .debate-speaker-avatar,
9
+ .delegated-card-avatar,
10
+ .dm-bubble-avatar,
11
+ .dm-header-avatar,
12
+ .dm-msg-avatar,
13
+ .dm-picker-mates-marquee-avatar,
14
+ .dm-user-picker-avatar,
15
+ .home-chat-avatar,
16
+ .home-hub-mate-avatar,
17
+ .icon-strip-me-avatar,
18
+ .icon-strip-user-avatar,
19
+ .input-mention-chip-avatar,
20
+ .mate-collapsed-avatar,
21
+ .mate-mobile-avatar,
22
+ .mate-profile-avatar,
23
+ .mate-sidebar-avatar,
24
+ .mention-avatar,
25
+ .mention-item-avatar,
26
+ .mobile-chat-chip-avatar,
27
+ .notif-banner-avatar,
28
+ .profile-avatar-custom-preview,
29
+ .profile-popover-avatar-img,
30
+ .project-switcher-avatar,
31
+ .remote-cursor-avatar,
32
+ .rewind-avatar,
33
+ .session-presence-avatar,
34
+ .sidebar-presence-avatar,
35
+ .us-account-avatar,
36
+ .us-account-avatar-img,
37
+ .user-island-avatar,
38
+ .user-island-avatar img {
39
+ border-radius: 26% !important;
40
+ }
41
+
42
+ .session-presence-avatar,
43
+ .sidebar-presence-avatar,
44
+ .client-avatar {
45
+ box-shadow: 0 0 0 1px rgba(var(--overlay-rgb), 0.12);
46
+ }
@@ -206,10 +206,10 @@
206
206
  }
207
207
 
208
208
  .icon-strip-item.active::before {
209
- background: var(--bg);
209
+ background: color-mix(in srgb, var(--brand-indigo) 6%, var(--bg));
210
210
  box-shadow:
211
- 0 0 0 1px color-mix(in srgb, var(--link) 62%, var(--border)),
212
- 0 1px 3px rgba(var(--shadow-rgb), 0.16);
211
+ 0 5px 14px color-mix(in srgb, var(--brand-indigo) 28%, transparent),
212
+ 0 1px 3px rgba(var(--shadow-rgb), 0.14);
213
213
  }
214
214
 
215
215
  .icon-strip-item.active {
@@ -137,13 +137,7 @@ body.pane-mode #input-area {
137
137
  .split-pair-role-driver { color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, transparent); }
138
138
  .split-pair-role-worker { color: var(--text-secondary); background: var(--bg-alt); }
139
139
 
140
- /* Role badges are buttons: Driver click clears roles, Worker click takes over. */
141
- button.split-pair-role {
142
- border: 0;
143
- cursor: pointer;
144
- font-family: inherit;
145
- }
146
- button.split-pair-role:hover { filter: brightness(1.25); }
140
+ /* Pair roles are status labels. Control remains with the Driver through MCP. */
147
141
 
148
142
  /* Ad-hoc splits: role assignment in each pane header. ALWAYS fully
149
143
  visible; hover/opacity gating made the control undiscoverable. */