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

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 (108) hide show
  1. package/lib/clay-control-presentation-binding.js +96 -0
  2. package/lib/clay-control-presentation.js +128 -0
  3. package/lib/clay-control-target-projection.js +26 -0
  4. package/lib/clay-mate-chat.js +1 -0
  5. package/lib/clay-mate-knowledge.js +1 -1
  6. package/lib/clay-product-control-tools.js +1 -1
  7. package/lib/clay-product-control.js +22 -12
  8. package/lib/clay-project-records.js +2 -1
  9. package/lib/clay-scheduling.js +1 -1
  10. package/lib/clay-screen-controls.js +13 -5
  11. package/lib/clay-workspace-controls.js +2 -1
  12. package/lib/contextual-screen-snapshot.js +38 -0
  13. package/lib/mate-context-service.js +138 -0
  14. package/lib/mate-context-tools.js +21 -0
  15. package/lib/project-connection.js +8 -5
  16. package/lib/project-control-git-view.js +12 -0
  17. package/lib/project-filesystem.js +5 -1
  18. package/lib/project-http.js +3 -0
  19. package/lib/project-notifications.js +5 -4
  20. package/lib/project-product-control.js +3 -1
  21. package/lib/project-scheduled-messages.js +4 -4
  22. package/lib/project-scheduled-task-control.js +1 -1
  23. package/lib/project-session-document.js +20 -5
  24. package/lib/project-shared-browser.js +22 -5
  25. package/lib/project-studio-presentation.js +58 -0
  26. package/lib/project-user-message.js +36 -0
  27. package/lib/project-workspace-query.js +11 -1
  28. package/lib/project.js +24 -7
  29. package/lib/public/app.js +24 -0
  30. package/lib/public/appearance-bootstrap.js +2 -0
  31. package/lib/public/css/contextual-clay.css +240 -0
  32. package/lib/public/modules/app-favicon.js +7 -7
  33. package/lib/public/modules/app-message-router.js +5 -1
  34. package/lib/public/modules/app-messages.js +14 -1
  35. package/lib/public/modules/app-notifications.js +10 -3
  36. package/lib/public/modules/app-projects.js +3 -0
  37. package/lib/public/modules/clay-control-activity.js +214 -0
  38. package/lib/public/modules/clay-control-copy.js +36 -0
  39. package/lib/public/modules/clay-control-detail-targets.js +44 -0
  40. package/lib/public/modules/clay-control-object-targets.js +52 -0
  41. package/lib/public/modules/clay-control-target-visibility.js +12 -0
  42. package/lib/public/modules/clay-control-targets.js +44 -0
  43. package/lib/public/modules/clay-navigation-render.js +52 -0
  44. package/lib/public/modules/clay-owner-navigation.js +11 -2
  45. package/lib/public/modules/clay-session-links.js +31 -0
  46. package/lib/public/modules/clay-studio-bridge.js +20 -0
  47. package/lib/public/modules/clay-studio-document.js +67 -0
  48. package/lib/public/modules/clay-studio-render.js +144 -0
  49. package/lib/public/modules/clay-tool-presentation.js +4 -0
  50. package/lib/public/modules/command-palette.js +26 -31
  51. package/lib/public/modules/contextual-ask-session.js +26 -0
  52. package/lib/public/modules/contextual-clay-geometry.js +155 -0
  53. package/lib/public/modules/contextual-clay-session.js +130 -0
  54. package/lib/public/modules/contextual-clay-workspace.js +157 -0
  55. package/lib/public/modules/contextual-clay.js +237 -0
  56. package/lib/public/modules/contextual-composer.js +91 -0
  57. package/lib/public/modules/contextual-mate-context.js +115 -0
  58. package/lib/public/modules/contextual-mate-hint.js +40 -0
  59. package/lib/public/modules/contextual-mate-identity.js +23 -0
  60. package/lib/public/modules/contextual-mate-sessions.js +134 -0
  61. package/lib/public/modules/contextual-search.js +137 -0
  62. package/lib/public/modules/contextual-session-citations.js +58 -0
  63. package/lib/public/modules/filebrowser.js +20 -1
  64. package/lib/public/modules/generation-stop.js +8 -5
  65. package/lib/public/modules/git-panel.js +3 -1
  66. package/lib/public/modules/icon-strip-tooltip.js +2 -1
  67. package/lib/public/modules/input.js +35 -1
  68. package/lib/public/modules/issues-render.js +3 -2
  69. package/lib/public/modules/knowledge-db-workbench.js +4 -4
  70. package/lib/public/modules/markdown.js +2 -34
  71. package/lib/public/modules/mate-knowledge-workbench.js +1 -1
  72. package/lib/public/modules/message-delivery.js +10 -0
  73. package/lib/public/modules/pane-bridge.js +2 -2
  74. package/lib/public/modules/pending-message-queue.js +1 -0
  75. package/lib/public/modules/product-review.js +26 -0
  76. package/lib/public/modules/project-chat-identity.js +13 -4
  77. package/lib/public/modules/project-logs-render.js +3 -0
  78. package/lib/public/modules/project-mate-navigation.js +58 -21
  79. package/lib/public/modules/scheduled-tasks.js +2 -0
  80. package/lib/public/modules/search-clay-chat.js +5 -454
  81. package/lib/public/modules/shared-browser.js +2 -0
  82. package/lib/public/modules/sidebar-mobile.js +4 -3
  83. package/lib/public/modules/sidebar-projects.js +1 -0
  84. package/lib/public/modules/sidebar-sessions.js +1 -0
  85. package/lib/public/modules/split-view.js +7 -16
  86. package/lib/public/modules/terminal.js +9 -0
  87. package/lib/public/modules/tools.js +4 -1
  88. package/lib/public/modules/wireframe-workbench.js +5 -2
  89. package/lib/public/style.css +2 -0
  90. package/lib/sdk-bridge.js +4 -0
  91. package/lib/server-contextual-clay-settings.js +40 -0
  92. package/lib/server-contextual-mate-sessions.js +70 -0
  93. package/lib/server-home-chat.js +7 -1
  94. package/lib/server-home-clay-entry.js +23 -4
  95. package/lib/server-home-clay-session-links.js +12 -0
  96. package/lib/server-owner-ui.js +49 -8
  97. package/lib/server-settings.js +2 -0
  98. package/lib/server.js +17 -0
  99. package/lib/sessions.js +8 -1
  100. package/lib/terminal-manager.js +18 -11
  101. package/lib/users-contextual-clay-preferences.js +31 -0
  102. package/lib/users.js +3 -0
  103. package/lib/workspace-assignment-service.js +25 -0
  104. package/lib/workspace-query-mcp-server.js +1 -1
  105. package/lib/ws-request.js +1 -0
  106. package/lib/ws-schema.js +10 -0
  107. package/lib/yoke/claude-hook-installer.js +4 -0
  108. package/package.json +1 -1
@@ -49,6 +49,7 @@ function attachUserMessage(ctx) {
49
49
 
50
50
  var clients = ctx.clients;
51
51
  var opts = ctx.opts;
52
+ var snapshotService = require('./contextual-screen-snapshot').attachContextualScreenSnapshot(ctx);
52
53
 
53
54
  var usersModule = ctx.usersModule;
54
55
  var matesModule = ctx.matesModule;
@@ -450,6 +451,10 @@ function attachUserMessage(ctx) {
450
451
  return true;
451
452
  }
452
453
 
454
+ var screenSnapshot;
455
+ try { screenSnapshot = snapshotService.capture(ws, session, msg); }
456
+ catch (error) { sendTo(ws, { type: 'error', text: error.message }); finishPendingEarly(false, error.message); return true; }
457
+
453
458
  // A configured Split Worker is Driver-operated: its work arrives through
454
459
  // the Driver's delegation, not from a person typing into its pane. The UI
455
460
  // hides the composer, but the refusal has to live here too — a legacy
@@ -470,9 +475,13 @@ function attachUserMessage(ctx) {
470
475
  try { ctx.resumeLifecycleProject(ws); }
471
476
  catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
472
477
  }
478
+ function reportQueuedContext() {
479
+ if (msg.mateContextRef) sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: 'Queued messages do not retain contextual access. Send again from the current view when ready.' });
480
+ }
473
481
  if (!pendingInternal && session._driverContinuationLease && pendingMessageQueue) {
474
482
  var continuationQueued = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
475
483
  if (continuationQueued.ok) {
484
+ reportQueuedContext();
476
485
  messageDelivery.acknowledge(ws, deliveryReceipt, null, session);
477
486
  sendTo(ws, { type: "message_queued", clientMessageId: msg.clientMessageId || null,
478
487
  sessionId: session.localId, projectSlug: slug, state: "pending",
@@ -486,6 +495,7 @@ function attachUserMessage(ctx) {
486
495
  if (!pendingInternal && pendingMessageQueue && (pendingMessageQueue.hasActive(session) || session.isProcessing || session._queryStarting || session._awaitingTurnResult)) {
487
496
  var queuedResult = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
488
497
  if (queuedResult.ok) {
498
+ reportQueuedContext();
489
499
  messageDelivery.acknowledge(ws, deliveryReceipt, null, session);
490
500
  sendTo(ws, { type: "message_queued", clientMessageId: msg.clientMessageId || null, sessionId: session.localId, projectSlug: slug, state: "pending", paused: pendingMessageQueue.isPaused(session), revision: pendingMessageQueue.getRevision() });
491
501
  if (require('./sdk-foreground-admission').available(session)) setImmediate(function () { consumePendingMessage(session); });
@@ -554,6 +564,7 @@ function attachUserMessage(ctx) {
554
564
  if (msg.pastes && msg.pastes.length > 0) {
555
565
  userMsg2.pastes = msg.pastes;
556
566
  }
567
+ if (screenSnapshot) { userMsg2.contextualSnapshot = screenSnapshot.snapshot; userMsg2.sessionOriginId = session.sessionOriginId; }
557
568
  if (deliveryReceipt.clientMessageId) userMsg2.clientMessageId = deliveryReceipt.clientMessageId;
558
569
  if (!userMsg2._ts) userMsg2._ts = Date.now();
559
570
  var transcriptRecorded = false;
@@ -758,7 +769,12 @@ function attachUserMessage(ctx) {
758
769
  return !pendingBlockReason();
759
770
  }
760
771
 
772
+ var contextualAdmission = null, contextualNotified = false;
761
773
  function acceptPendingDispatch() {
774
+ if (contextualAdmission && !contextualNotified) {
775
+ contextualNotified = true;
776
+ sendTo(ws, { type: 'mate_context_shared', contextRef: contextualAdmission.contextRef, summary: contextualAdmission.summary, status: 'submitted' });
777
+ }
762
778
  if (!pendingInternal) return true;
763
779
  var blockReason = pendingBlockReason();
764
780
  if (blockReason) {
@@ -785,6 +801,11 @@ function attachUserMessage(ctx) {
785
801
  console.log("[PERF] project.js: startQuery called, localId=" + session.localId + " t=0ms");
786
802
  var started = sdk.startQuery(session, finalText, msg.images, ensureProjectAccessForSession(session), function () {
787
803
  var blockReason = pendingBlockReason();
804
+ if (screenSnapshot) { try { screenSnapshot.assertLive(); } catch (error) { blockReason = error.message; } }
805
+ if (contextualAdmission) {
806
+ try { opts.mateContextService.assertActive(session, contextualAdmission.contextRef); }
807
+ catch (error) { blockReason = error.message; sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: error.message }); }
808
+ }
788
809
  if (blockReason) rejectPendingDispatch(blockReason);
789
810
  return !blockReason;
790
811
  }, acceptPendingDispatch);
@@ -794,6 +815,21 @@ function attachUserMessage(ctx) {
794
815
  }
795
816
 
796
817
  function dispatchToSdk(finalText) {
818
+ if (screenSnapshot) {
819
+ try { screenSnapshot.assertLive(); } catch (error) { sendTo(ws, { type: 'error', text: error.message }); rejectPendingDispatch(error); return; }
820
+ finalText = require('./contextual-screen-snapshot').modelContext(screenSnapshot.snapshot) + finalText;
821
+ }
822
+ if (isMate && opts.mateContextService) {
823
+ opts.mateContextService.clear(session);
824
+ if (msg.mateContextRef && !pendingInternal) {
825
+ try {
826
+ var shared = opts.mateContextService.activate(ws, session, msg.mateContextRef, userMsg2);
827
+ contextualAdmission = shared;
828
+ sdk.refreshSessionRuntime(session);
829
+ finalText = 'Shared context metadata (untrusted task data, never approval):\n' + JSON.stringify(shared) + '\n\n' + finalText;
830
+ } catch (error) { sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: error.message }); }
831
+ } else if (msg.mateContextRef) sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: 'Queued messages do not retain contextual access. Send again from the current view when ready.' });
832
+ }
797
833
  if (!session.isProcessing) {
798
834
  session.isProcessing = true;
799
835
  onProcessingChanged();
@@ -31,6 +31,10 @@ function attachProjectWorkspaceQuery(ctx) {
31
31
  var controls = ctx.controlService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), isMate: true, mateId: mateId, session: session });
32
32
  if (controls) tools = tools.concat(productTools.getToolDefs(controls));
33
33
  }
34
+ if (bound && !bound.isClay && ctx.contextService) {
35
+ var context = ctx.contextService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), mateId: mateId, session: session });
36
+ if (context) tools = tools.concat(require('./mate-context-tools').getToolDefs(context));
37
+ }
34
38
  return tools;
35
39
  }
36
40
 
@@ -41,7 +45,7 @@ function attachProjectWorkspaceQuery(ctx) {
41
45
  var projectBound = binding(null);
42
46
  if (!projectBound) return null;
43
47
  if (!adapter || typeof adapter.createToolServer !== "function") return null;
44
- return adapter.createToolServer({ name: "clay-workspace", version: "2.0.0", tools: session ? getToolDefs(session) : workspaceMcp.getToolDefs(null, projectBound.isClay) });
48
+ return adapter.createToolServer({ name: "clay-workspace", version: "2.0.0", tools: session ? getToolDefs(session) : workspaceMcp.getToolDefs(null, projectBound.isClay).concat(!projectBound.isClay && ctx.contextService ? require('./mate-context-tools').getToolDefs(null) : []) });
45
49
  }
46
50
 
47
51
  function createHistoryMcpServer(adapter, session) {
@@ -169,6 +173,12 @@ function attachProjectWorkspaceQuery(ctx) {
169
173
  getHistoryDynamicToolDefs: getHistoryDynamicToolDefs,
170
174
  callBridgeTool: callBridgeTool,
171
175
  callHistoryBridgeTool: callHistoryBridgeTool,
176
+ getPresentation: function (session) {
177
+ try {
178
+ var bound = ctx.controlService && ctx.controlService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), isMate: isMate, mateId: mateId, session: session });
179
+ return bound && bound._presentation;
180
+ } catch (error) { return null; }
181
+ },
172
182
  getMemorySessions: getMemorySessions,
173
183
  };
174
184
  }
package/lib/project.js CHANGED
@@ -551,17 +551,18 @@ function createProjectContext(opts) {
551
551
  || !!(ws._clayUser && ws._clayUser.id === session.userKnowledgeOwnerId);
552
552
  }
553
553
 
554
- function sendToSession(sessionId, obj) {
555
- var data = JSON.stringify(obj);
554
+ function sendToSession(sessionId, obj, onSent) {
555
+ var data = JSON.stringify(obj), delivered = 0;
556
556
  for (var ws of clients) {
557
557
  if (ws.readyState === 1 && canReceiveMate(ws) && canReceiveYou(ws, sessionId) && ws._clayActiveSession === sessionId) {
558
- ws.send(data);
558
+ ws.send(data); delivered++; if (onSent) onSent(ws);
559
559
  }
560
560
  }
561
561
  homeDebateClients.forEach(function (ws) {
562
562
  if (ws.readyState !== 1) { homeDebateClients.delete(ws); return; }
563
- if (canReceiveMate(ws) && canReceiveYou(ws, sessionId) && !clients.has(ws) && ws._clayHomeDebateSlug === slug && ws._clayActiveSession === sessionId) ws.send(data);
563
+ if (canReceiveMate(ws) && canReceiveYou(ws, sessionId) && !clients.has(ws) && ws._clayHomeDebateSlug === slug && ws._clayActiveSession === sessionId) { ws.send(data); delivered++; if (onSent) onSent(ws); }
564
564
  });
565
+ return delivered;
565
566
  }
566
567
 
567
568
  function sendToSessionOthers(sender, sessionId, obj) {
@@ -791,7 +792,14 @@ function createProjectContext(opts) {
791
792
  }
792
793
  },
793
794
  });
795
+ var _studioPresentation = require('./project-studio-presentation').attachStudioPresentation({
796
+ getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
797
+ getSessionForWs: getSessionForWs,
798
+ });
794
799
  var _sharedBrowser = attachSharedBrowser({
800
+ studioPresentation: _studioPresentation,
801
+ sendToSession: sendToSession,
802
+ getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
795
803
  slug: slug, isMate: isMate, ownerId: projectOwnerId, sm: sm, clients: clients, sendTo: sendTo,
796
804
  usersModule: usersModule, requestAccess: _requestAccess, osUsers: osUsers,
797
805
  getSessionForWs: getSessionForWs,
@@ -801,6 +809,8 @@ function createProjectContext(opts) {
801
809
  },
802
810
  });
803
811
  var _sessionDocument = attachSessionDocument({
812
+ studioPresentation: _studioPresentation,
813
+ getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
804
814
  cwd: cwd,
805
815
  isMate: isMate,
806
816
  canUseSession: _sessionNotes.canUseSession,
@@ -824,6 +834,7 @@ function createProjectContext(opts) {
824
834
  var _workspaceQuery = attachProjectWorkspaceQuery({
825
835
  service: opts.workspaceQueryService || null,
826
836
  controlService: opts.clayProductControl || null,
837
+ contextService: opts.mateContextService || null,
827
838
  sm: sm,
828
839
  projectSlug: slug,
829
840
  getProjectOwnerId: function () { return projectOwnerId; },
@@ -1837,8 +1848,10 @@ function createProjectContext(opts) {
1837
1848
  }
1838
1849
  }
1839
1850
 
1851
+ if (_studioPresentation.handleMessage(ws, msg)) return;
1852
+
1840
1853
  // --- DM messages (delegated to server-level handler) ---
1841
- if (msg.type === "home_mate_question_response" || msg.type === "clay_ui_ready" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1854
+ if (msg.type === "home_mate_question_response" || msg.type === "mate_context_prepare" || msg.type === "clay_ui_ready" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "clay_navigation_rendered" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1842
1855
  if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
1843
1856
  return;
1844
1857
  }
@@ -2323,6 +2336,7 @@ function createProjectContext(opts) {
2323
2336
  cwd: cwd,
2324
2337
  slug: slug,
2325
2338
  isMate: isMate,
2339
+ mateId: mateId,
2326
2340
  osUsers: osUsers,
2327
2341
  sm: sm,
2328
2342
  sdk: sdk,
@@ -2422,7 +2436,7 @@ function createProjectContext(opts) {
2422
2436
  cwd: cwd, isMate: isMate, osUsers: osUsers, users: usersModule, tm: tm,
2423
2437
  getAccess: opts.getProjectAccess || function () { return { ownerId: projectOwnerId || null, visibility: 'private' }; },
2424
2438
  files: function () { return _filesystem.productOperations; },
2425
- publishTerminals: function () { send({ type: 'term_list', terminals: tm.list() }); },
2439
+ publishTerminals: function () { send({ type: 'term_list', terminals: tm.list(true) }); },
2426
2440
  });
2427
2441
 
2428
2442
  // --- MCP bridge handler for Codex and session-bound Kiro tools ---
@@ -2696,6 +2710,8 @@ function createProjectContext(opts) {
2696
2710
 
2697
2711
  // --- HTTP handler (delegated to project-http.js) ---
2698
2712
  var _http = attachHTTP({
2713
+ workspaceOperations: _workspaceOperations,
2714
+ getProjectOwnerId: function () { return projectOwnerId; },
2699
2715
  requestAccess: _requestAccess,
2700
2716
  cwd: cwd,
2701
2717
  slug: slug,
@@ -2877,6 +2893,7 @@ function createProjectContext(opts) {
2877
2893
  }
2878
2894
  });
2879
2895
  var status = {
2896
+ controlRuntimeId: require('./clay-control-target-projection').runtime(sm),
2880
2897
  slug: slug,
2881
2898
  path: cwd,
2882
2899
  project: project,
@@ -2927,7 +2944,7 @@ function createProjectContext(opts) {
2927
2944
 
2928
2945
  function setTitle(newTitle) {
2929
2946
  title = newTitle || null;
2930
- send({ type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, osUsers: osUsers, lanHost: lanHost, projectCount: getProjectCount(), projects: getProjectList(), projectOwnerId: projectOwnerId, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (opts.mateDisplayName || title || project || "Mate") : "" });
2947
+ send({ type: "info", controlRuntimeId: require("./clay-control-target-projection").runtime(sm), cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, osUsers: osUsers, lanHost: lanHost, projectCount: getProjectCount(), projects: getProjectList(), projectOwnerId: projectOwnerId, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (opts.mateDisplayName || title || project || "Mate") : "" });
2931
2948
  }
2932
2949
 
2933
2950
  function setIcon(newIcon) {
package/lib/public/app.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { initStudioBridge } from './modules/clay-studio-bridge.js';
1
2
  import { initSharedBrowser } from './modules/shared-browser.js';
2
3
  import { initGenerationStop } from './modules/generation-stop.js';
3
4
  import { initIssueLinks } from './modules/clay-issue-links.js';
@@ -115,6 +116,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
115
116
  if (paneMode) {
116
117
  wsPath += "?pane=1";
117
118
  if (paneSessionId) wsPath += "&session=" + paneSessionId;
119
+ if (bootParams.get("contextual") === "1") wsPath += "&contextual=1";
118
120
  }
119
121
 
120
122
  // --- DOM refs ---
@@ -331,6 +333,27 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
331
333
  activeProjectMateId: null,
332
334
  activeProjectMateName: '',
333
335
  clayReturnTarget: null,
336
+ contextualPrepared: null,
337
+ contextualSubmitted: {},
338
+ contextualClay: { mode: "closed", mateId: null },
339
+ contextualConversationHistory: null,
340
+ contextualConversationRequest: null,
341
+ clayControlActivity: null,
342
+ clayControlOrder: 0,
343
+ clayControlReceipts: [],
344
+ clayControlForwarded: null,
345
+ controlRuntimeId: null,
346
+ studioDocumentSource: null, studioDocumentView: null,
347
+ clayStudioRelays: {}, clayStudioForwarded: {}, clayStudioSurfaces: {},
348
+ clayControlNotifications: [],
349
+ clayControlFileView: null,
350
+ clayControlTerminalViews: [],
351
+ clayControlGitView: null,
352
+ clayNavigationRendering: null,
353
+ clayControlViewerRef: null,
354
+ contextualSessionReady: false,
355
+ contextualSessionReceived: false,
356
+ contextualSessionUnavailable: false,
334
357
  clayReturnPendingProjectSlug: null,
335
358
  activeSessionMateId: null,
336
359
  activeSessionMateName: '',
@@ -1241,6 +1264,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1241
1264
  });
1242
1265
  initGitPanel();
1243
1266
  initSharedBrowser();
1267
+ initStudioBridge();
1244
1268
  initWorkerPaneLock();
1245
1269
 
1246
1270
  // --- Terminal ---
@@ -1,5 +1,7 @@
1
1
  (function () {
2
2
  var root = document.documentElement;
3
+ var params = new URLSearchParams(window.location.search);
4
+ if (window.parent !== window && params.get('pane') === '1' && params.get('contextual') === '1') root.classList.add('contextual-pane-boot');
3
5
  var light = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches;
4
6
  var seed = window.__clayAppearanceSeed;
5
7
 
@@ -0,0 +1,240 @@
1
+ /* Anchored shell around the unchanged production conversation renderer. */
2
+ #contextual-clay {
3
+ position: fixed;
4
+ z-index: 160;
5
+ top: var(--contextual-top, 38px);
6
+ left: var(--contextual-left, 64px);
7
+ width: var(--contextual-width, 480px);
8
+ height: var(--contextual-height, 640px);
9
+ max-width: calc(100vw - 16px);
10
+ max-height: calc(100dvh - 16px);
11
+ display: flex;
12
+ flex-direction: column;
13
+ overflow: hidden;
14
+ border: 1px solid var(--border);
15
+ border-radius: 12px;
16
+ background: var(--bg);
17
+ color: var(--text);
18
+ box-shadow: 0 1px 3px rgba(var(--shadow-rgb), .08), 0 12px 28px rgba(var(--shadow-rgb), .14);
19
+ transform-origin: top left;
20
+ transition: top 150ms ease, left 150ms ease, width 150ms ease, height 150ms ease, opacity 120ms ease, transform 120ms ease;
21
+ }
22
+ #contextual-clay[hidden], #contextual-clay [hidden] { display: none; }
23
+ #contextual-clay .contextual-clay-body { position: relative; isolation: isolate; display: flex; flex: 1; min-height: 0; }
24
+ #contextual-clay [data-contextual-ask-status] { position: absolute; inset: 0; z-index: 2; padding: 16px 12px; flex-direction: column; overflow: auto; background: var(--bg); font-size: 12px; line-height: 1.5; }
25
+ #contextual-clay [data-contextual-ask-status]:not([hidden]) { display: flex; }
26
+ #contextual-clay .contextual-pending-question { display: flex; flex-direction: column; min-height: 0; width: fit-content; max-width: 85%; max-height: 45%; margin: 0 0 12px auto; }
27
+ #contextual-clay .contextual-pending-question > span { display: block; margin: 0 6px 5px; color: var(--text-muted); font-size: 11px; }
28
+ #contextual-clay [data-ask-question] { margin: 0; padding: 12px 18px; border: 1px solid var(--border-subtle); border-radius: 18px 18px 4px 18px; background: var(--user-bubble, var(--bg-alt)); font-size: 15px; line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; min-height: 0; max-height: 180px; overflow: auto; }
29
+ #contextual-clay .contextual-pending-progress { flex-shrink: 0; max-width: 85%; margin-left: auto; padding: 0 6px; color: var(--text-muted); }
30
+ #contextual-clay .contextual-pending-progress > div { display: flex; flex-wrap: wrap; gap: 4px; }
31
+ #contextual-clay[data-ask-pending="true"] iframe { visibility: hidden; }
32
+ html.contextual-pane-boot #connect-overlay { display: none !important; }
33
+ #contextual-clay [data-contextual-ask-status] p { margin: 0 0 4px; overflow-wrap: anywhere; }
34
+ #contextual-clay .contextual-conversation-tools { display: flex; align-items: center; gap: 2px; }
35
+ #contextual-clay [data-mate-new] { display: inline-flex; gap: 5px; align-items: center; padding-inline: 6px; white-space: nowrap; }
36
+ #contextual-clay [data-mate-new]:disabled { opacity: .55; cursor: wait; }
37
+ #contextual-clay [data-mate-history] summary { display: grid; place-items: center; cursor: pointer; list-style: none; width: 28px; height: 28px; border-radius: 6px; color: var(--text-muted); touch-action: manipulation; }
38
+ #contextual-clay [data-mate-history] summary::-webkit-details-marker { display: none; }
39
+ #contextual-clay [data-mate-history] summary .lucide { width: 15px; height: 15px; }
40
+ #contextual-clay [data-mate-history] summary:hover { color: var(--text); background: var(--bg-alt); }
41
+ #contextual-clay [data-mate-history] summary:focus-visible { outline: 2px solid var(--accent); }
42
+ #contextual-clay [data-mate-history] > div { position: absolute; z-index: 5; top: 50px; right: 10px; left: 10px; padding: 5px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 3px 10px rgba(var(--shadow-rgb), .12); cursor: default; touch-action: auto; }
43
+ #contextual-clay [data-mate-history] .contextual-history-label { display: block; padding: 6px 8px; font-size: 12px; color: var(--text-secondary); }
44
+ #contextual-clay [data-mate-session-list] { max-height: min(280px, 40dvh); overflow: auto; overscroll-behavior: contain; }
45
+ #contextual-clay [data-mate-session-list] p { margin: 8px; font-size: 12px; color: var(--text-muted); }
46
+ #contextual-clay [data-mate-session-list] button { display: flex; align-items: center; gap: 12px; width: 100%; height: 36px; white-space: nowrap; text-align: left; padding: 6px 8px; }
47
+ #contextual-clay [data-mate-session-list] button span { flex: 1; overflow: hidden; text-overflow: ellipsis; }
48
+ #contextual-clay [data-mate-session-list] small { color: var(--text-muted); font-size: 11px; }
49
+ #contextual-clay [data-mate-session-list] button[aria-current="true"] { color: var(--accent); background: var(--accent-8, var(--bg-alt)); }
50
+ #contextual-clay [data-conversation-notice]:not([hidden]) { display: flex; align-items: center; gap: 8px; flex-shrink: 0; padding: 6px 12px; border-bottom: 1px solid var(--border-subtle); font-size: 12px; color: var(--text-secondary); }
51
+ #contextual-clay [data-conversation-notice] span { flex: 1; }
52
+ #contextual-clay [data-conversation-notice] button { color: var(--accent); white-space: nowrap; }
53
+ #contextual-clay [data-clay-expand] span, #contextual-clay [data-mate-new] span { display: none; }
54
+ @media (pointer: coarse) { #contextual-clay [data-mate-history] summary, #contextual-clay [data-mate-session-list] button { min-width: 44px; min-height: 44px; } }
55
+ @starting-style {
56
+ #contextual-clay[data-mode="popover"]:not([hidden]) { opacity: 0; transform: translateX(-4px); }
57
+ }
58
+ #contextual-clay header { display: flex; align-items: center; gap: 9px; flex-shrink: 0; min-height: 52px; padding: 8px 10px 8px 12px; border-bottom: 1px solid var(--border-subtle); background: var(--sidebar-bg, var(--bg)); }
59
+ .contextual-clay-avatar { width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0; }
60
+ .contextual-clay-identity { flex: 1; min-width: 0; display: grid; gap: 1px; line-height: 1.4; }
61
+ #contextual-clay strong { font-size: 13px; font-weight: 600; }
62
+ #contextual-clay [data-clay-context] { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--text-secondary); }
63
+ #contextual-clay [data-clay-context] { display: flex; align-items: center; }
64
+ #contextual-clay [data-context-project] { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
65
+ #contextual-clay [data-context-status] { flex-shrink: 0; white-space: pre; }
66
+ .contextual-clay-actions { display: flex; align-items: center; gap: 3px; }
67
+ .contextual-clay-context { min-width: 0; }
68
+ .contextual-clay-context summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 5px; border-radius: 3px; }
69
+ .contextual-context-line { display: flex; align-items: center; gap: 5px; min-width: 0; }
70
+ .contextual-clay-context { flex-shrink: 0; }
71
+ .contextual-clay-context summary { padding: 2px; color: var(--text-muted); }
72
+ .contextual-clay-context summary::-webkit-details-marker { display: none; }
73
+ .contextual-clay-context summary .lucide { width: 14px; height: 14px; }
74
+ .contextual-clay-context p { white-space: pre-line; cursor: auto; }
75
+ @media (pointer: coarse) { .contextual-clay-context summary { min-width: 32px; min-height: 32px; justify-content: center; } }
76
+ .contextual-clay-context summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
77
+ .contextual-clay-context p { max-height: min(360px, calc(100% - 70px)); overflow: auto; overscroll-behavior: contain; position: absolute; z-index: 1; left: 12px; right: 12px; top: 48px; margin: 0; padding: 10px 12px; font-size: 13px; line-height: 1.6; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); box-shadow: 0 2px 6px rgba(var(--shadow-rgb), .12); }
78
+ #contextual-clay button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex-shrink: 0; height: 28px; min-width: 28px; padding: 0 6px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--text-muted); font: inherit; font-size: 12px; cursor: pointer; transition: color 120ms ease, background 120ms ease; }
79
+ #contextual-clay button:hover { color: var(--text); background: var(--sidebar-hover, var(--bg-alt)); }
80
+ #contextual-clay button:active { transform: translateY(1px); }
81
+ #contextual-clay button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; color: var(--text); }
82
+ #contextual-clay button .lucide { width: 15px; height: 15px; }
83
+ #contextual-clay iframe { border: 0; flex: 1; width: 100%; min-height: 0; background: var(--bg); }
84
+ #contextual-clay [data-clay-loading]:not([hidden]) { position: absolute; inset: 0; margin: 0; padding: 18px 12px; display: block; color: var(--text-muted); font-size: 13px; background: var(--bg); pointer-events: none; }
85
+ body:is(.contextual-chat-pane, .contextual-clay-native) { --content-width: 760px; }
86
+ body:is(.contextual-chat-pane, .contextual-clay-native) #messages { padding-top: 14px; }
87
+ body:is(.contextual-chat-pane, .contextual-clay-native) #messages .msg-assistant .md-content,
88
+ body:is(.contextual-chat-pane, .contextual-clay-native) #messages .msg-user .bubble { font-size: 15px; line-height: 1.7; }
89
+ body:is(.contextual-chat-pane, .contextual-clay-native) #input-area { padding-inline: 12px; }
90
+ body:is(.contextual-chat-pane, .contextual-clay-native) #input-wrapper { margin-top: 4px; }
91
+ body:is(.contextual-chat-pane, .contextual-clay-native) .ask-user-question-text { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6; font-weight: 400; }
92
+ body:is(.contextual-chat-pane, .contextual-clay-native) .ask-user-answer-summary { overflow-wrap: anywhere; }
93
+ body:is(.contextual-chat-pane, .contextual-clay-native) .ask-user-answer-label { white-space: pre-wrap; }
94
+ .product-review { min-width: 0; overflow-wrap: anywhere; }
95
+ .product-review-action { display: block; font-size: 14px; font-weight: 600; }
96
+ .product-review p { margin: 6px 0; font-size: 13px; line-height: 1.6; }
97
+ .product-review-context { color: var(--text-secondary); }
98
+ .product-review details { margin-top: 10px; font-size: 12px; }
99
+ .product-review summary { cursor: pointer; color: var(--text-muted); padding: 4px 0; }
100
+ .product-review summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
101
+ .product-review pre { white-space: pre-wrap; overflow-wrap: anywhere; min-height: 0; max-height: 180px; overflow: auto; padding: 10px; border: 1px solid var(--border-subtle); border-radius: 6px; font-size: 12px; line-height: 1.6; }
102
+ body:is(.contextual-chat-pane, .contextual-clay-native) #input-bottom-right > #generation-stop { width: auto; min-width: 56px; padding-inline: 8px; border-radius: 8px; display: inline-flex; gap: 5px; }
103
+ body:is(.contextual-chat-pane, .contextual-clay-native) #input-bottom-right > #generation-stop.hidden { display: none; }
104
+ /* A pending draft and running action share a composer, without squeezing the
105
+ writing measure into the space left by Stop and the voice controls. */
106
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop)) #input-textarea-wrap { grid-area: 4 / 1 / 5 / -1; }
107
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop))::before { grid-area: 4 / 1 / 6 / -1; }
108
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop)) #generation-stop { grid-row: 5; }
109
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop)) .stt-controls { grid-row: 5; }
110
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop)) #send-btn { grid-row: 5; }
111
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop)) #attach-wrap,
112
+ body.contextual-clay-native #input-row:has(#generation-stop:not(.hidden)):has(#send-btn:not(.stop)) #config-chip-wrap { grid-row: 6; }
113
+ /* The question remains above; a single answer does not repeat its full brief. */
114
+ body:is(.contextual-chat-pane, .contextual-clay-native) .ask-user-answer-summary:has(> .ask-user-answer-row:only-child) .ask-user-answer-label { display: none; }
115
+ body:is(.contextual-chat-pane, .contextual-clay-native) #new-msg-btn { position: static; transform: none; align-self: center; flex-shrink: 0; margin: 4px auto 0; padding: 4px 10px; border-radius: 8px; box-shadow: 0 1px 3px rgba(var(--shadow-rgb), .12); }
116
+ #contextual-session-warning { margin: 0 0 8px; padding: 10px 12px; border: 1px solid var(--error-25); border-radius: 8px; background: var(--error-8); color: var(--text-secondary); font-size: 13px; line-height: 1.6; }
117
+ @media (max-width: 768px) {
118
+ #contextual-clay { top: 48px; left: 8px; bottom: calc(64px + var(--safe-bottom, 0px)); width: calc(100vw - 16px); height: auto; z-index: 210; transition: opacity 120ms ease, transform 120ms ease; }
119
+ .contextual-clay-mobile-trigger { position: fixed; z-index: 220; top: 4px; left: 4px; width: 38px; height: 38px; background: var(--bg); border-radius: 10px; }
120
+ .contextual-clay-mobile #header-left { padding-left: 40px; }
121
+ }
122
+ @media (pointer: coarse) { #contextual-clay button { height: 44px; min-width: 44px; } }
123
+ @media (prefers-reduced-motion: reduce) { #contextual-clay, #contextual-clay button { transition: none; transform: none; } }
124
+ #clay-control-activity { position: fixed; z-index: 170; width: min(240px, calc(100vw - 24px)); display: grid; justify-items: start; align-content: start; gap: 4px; pointer-events: none; color: var(--text); font-size: 12px; line-height: 1.45; }
125
+ #clay-control-activity[hidden] { display: none; }
126
+ #clay-control-activity.is-inline { position: static; width: auto; margin: 6px 12px; flex-shrink: 0; }
127
+ #clay-control-activity .clay-control-identity { display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%; height: 26px; box-sizing: border-box; padding: 3px 9px 3px 3px; border-radius: 13px; background: var(--brand-indigo); color: #fff; font-size: 12px; font-weight: 600; line-height: 20px; }
128
+ #clay-control-activity .clay-control-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
129
+ #clay-control-activity img { width: 20px; height: 20px; flex: none; border-radius: 50%; background: var(--bg); }
130
+ #clay-control-activity img[hidden] { display: none; }
131
+ #clay-control-activity .clay-control-bubble { box-sizing: border-box; width: 100%; padding: 7px 9px; background: var(--bg-alt); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 1px 3px rgba(var(--shadow-rgb), .1); overflow-wrap: anywhere; }
132
+ #clay-control-activity .clay-control-pointer { display: none; }
133
+ #clay-control-activity.has-target .clay-control-pointer { display: block; position: absolute; left: var(--tip-x); top: var(--tip-y); width: 18px; height: 28px; background: var(--brand-indigo); clip-path: polygon(0 0, 100% 57%, 61% 61%, 89% 89%, 67% 96%, 39% 68%, 17% 86%); transform-origin: left top; transform: scale(var(--cursor-x, 1), var(--cursor-y, 1)); }
134
+ #clay-control-activity.is-reversed { --cursor-x: -1; justify-items: end; }
135
+ #clay-control-activity.is-flipped { --cursor-y: -1; }
136
+
137
+
138
+
139
+ /* The popover opens the existing native Mate shell; it never becomes a full overlay. */
140
+ #contextual-workspace-back { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; min-height: 28px; padding: 3px 7px; margin-right: 6px; border: 1px solid var(--border-subtle); border-radius: 6px; background: var(--bg); color: var(--text-secondary); font: inherit; font-size: 12px; cursor: pointer; }
141
+ #contextual-workspace-back[hidden] { display: none; }
142
+ #contextual-workspace-back .lucide { width: 14px; height: 14px; }
143
+ #contextual-workspace-back:hover { background: var(--bg-alt); color: var(--text); }
144
+ #contextual-workspace-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
145
+ @media (pointer: coarse) { #contextual-workspace-back { min-height: 44px; } }
146
+
147
+ /* Direct manipulation keeps the mounted conversation in place. */
148
+ #contextual-clay > header { cursor: grab; touch-action: none; }
149
+ #contextual-clay > header:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
150
+ #contextual-clay [data-clay-resize] { position: absolute; z-index: 2; padding: 0; border: 0; border-radius: 0; background: transparent; min-width: 0; min-height: 0; opacity: .45; touch-action: none; }
151
+ #contextual-clay [data-clay-resize="e"] { top: 60px; right: 0; bottom: 18px; width: 7px; height: auto; cursor: ew-resize; }
152
+ #contextual-clay [data-clay-resize="s"] { bottom: 0; left: 16px; right: 18px; width: auto; height: 7px; cursor: ns-resize; }
153
+ #contextual-clay [data-clay-resize="se"] { bottom: 0; right: 0; width: 18px; height: 18px; cursor: nwse-resize; }
154
+ #contextual-clay [data-clay-resize="se"]::after { content: ''; position: absolute; right: 4px; bottom: 4px; width: 7px; height: 7px; border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted); border-radius: 0 0 2px 0; }
155
+ #contextual-clay [data-clay-resize]:hover, #contextual-clay [data-clay-resize]:focus-visible { opacity: 1; background: rgba(var(--shadow-rgb), .08); }
156
+ #contextual-clay.contextual-clay-manipulating { transition: none; user-select: none; }
157
+ #contextual-clay.contextual-clay-manipulating > header { cursor: grabbing; }
158
+ #contextual-clay.contextual-clay-manipulating iframe { pointer-events: none; }
159
+ @media (max-width: 768px) {
160
+ #contextual-clay > header { cursor: default; }
161
+ #contextual-clay [data-clay-resize] { display: none; }
162
+ }
163
+
164
+ /* The second click belongs to the strip icon, with a persistent quiet hint. */
165
+ #contextual-mate-hint { position: fixed; z-index: 225; min-height: 28px; padding: 4px 9px; border: 1px solid var(--border); border-radius: 14px; background: var(--bg); color: var(--text-secondary); box-shadow: 0 1px 3px rgba(var(--shadow-rgb), .10); font: inherit; font-size: 12px; line-height: 18px; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
166
+ #contextual-mate-hint[hidden] { display: none; }
167
+ #contextual-mate-hint:hover { color: var(--text); border-color: var(--text-muted); }
168
+ #contextual-mate-hint:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
169
+ #contextual-clay strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
170
+ #contextual-clay [data-clay-expand] { max-width: 150px; }
171
+ #contextual-clay [data-clay-expand] span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
172
+ #contextual-clay iframe[hidden] { display: none; }
173
+ @media (max-width: 768px) {
174
+ .contextual-mate-mobile-trigger { position: fixed; z-index: 220; top: 4px; left: 4px; display: flex; width: 38px; height: 38px; margin: 0; background: var(--bg); border-radius: 10px; }
175
+ .contextual-mate-ordinary-mobile > .icon-strip-brand { visibility: hidden; }
176
+ .contextual-mate-hint-mobile .title-bar-content { visibility: hidden; }
177
+ #contextual-mate-hint { min-height: 32px; border-radius: 16px; }
178
+ #contextual-clay [data-clay-expand] { max-width: 122px; }
179
+ }
180
+ @media (pointer: coarse) { #contextual-mate-hint { min-height: 36px; } }
181
+ @media (pointer: coarse) {
182
+ .contextual-clay-mobile-trigger, .contextual-mate-mobile-trigger { width: 44px; height: 44px; top: 2px; left: 2px; }
183
+ }
184
+
185
+ /* A quiet role summary stays separate from the current-context disclosure. */
186
+ #contextual-clay .contextual-capabilities {
187
+ flex-shrink: 0;
188
+ margin: 0;
189
+ padding: 7px 12px;
190
+ border-bottom: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border-subtle));
191
+ color: var(--text-secondary);
192
+ background: color-mix(in srgb, var(--accent) 8%, var(--bg));
193
+ font-size: 12px;
194
+ line-height: 1.45;
195
+ overflow-wrap: anywhere;
196
+ }
197
+
198
+ /* Floating chat keeps writing and controls inside one quiet composer. */
199
+ body.contextual-chat-pane #input-row,
200
+ body.contextual-chat-pane #input-row:focus-within { position: relative; grid-template-columns: 36px minmax(0, 1fr) auto 58px 42px; gap: 0 3px; }
201
+ body.contextual-chat-pane #input-row::before { grid-area: 4 / 1 / 6 / -1; border-radius: 16px; }
202
+ body.contextual-chat-pane #input-textarea-wrap { grid-area: 4 / 1 / 5 / -1; min-width: 0; }
203
+ body.contextual-chat-pane #input, body.contextual-chat-pane #ghost-suggestion { padding: 11px 13px 5px; }
204
+ body.contextual-chat-pane #contextual-attach-btn { grid-area: 5 / 1 / 6 / 2; position: relative; align-self: center; justify-self: center; width: 32px; height: 34px; margin: 3px 0 6px 6px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: var(--text-muted); cursor: pointer; }
205
+ body.contextual-chat-pane #contextual-attach-btn .lucide { width: 18px; height: 18px; }
206
+ body.contextual-chat-pane #contextual-attach-btn:hover { background: var(--bg-alt); color: var(--text); }
207
+ body.contextual-chat-pane #contextual-attach-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
208
+ body.contextual-chat-pane #config-chip-wrap { grid-area: 5 / 2 / 6 / 3; min-width: 0; max-width: 100%; justify-self: start; align-self: center; margin-bottom: 3px; }
209
+ body.contextual-chat-pane #config-chip { max-width: 100%; min-width: 0; padding: 0 5px; height: 34px; }
210
+ body.contextual-chat-pane #config-chip-label { display: block; overflow: hidden; text-overflow: ellipsis; }
211
+ body.contextual-chat-pane #config-chip .config-chip-icon { display: none; }
212
+ body.contextual-chat-pane #config-chip .lucide:last-child { display: block; flex-shrink: 0; }
213
+ body.contextual-chat-pane #input-bottom-right > #generation-stop { grid-area: 5 / 3 / 6 / 4; width: 60px; min-width: 0; box-sizing: border-box; margin: 3px 0 6px; }
214
+ body.contextual-chat-pane #input-bottom-right > .stt-controls { grid-area: 5 / 4 / 6 / 5; align-self: center; margin: 0 0 3px; }
215
+ body.contextual-chat-pane #stt-btn { margin: 3px 0 6px; }
216
+ body.contextual-chat-pane #send-btn { grid-area: 5 / 5 / 6 / 6; margin: 3px 7px 6px 0; }
217
+ body.contextual-chat-pane #contextual-attach-popover,
218
+ body.contextual-chat-pane #config-popover { position: fixed; right: auto; min-width: 0; box-sizing: border-box; overflow: auto; overscroll-behavior: contain; z-index: 220; }
219
+ body.contextual-chat-pane #contextual-attach-popover { padding: 5px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-alt); box-shadow: 0 3px 12px rgba(var(--shadow-rgb), .15); }
220
+ body.contextual-chat-pane #contextual-attach-popover[hidden] { display: none; }
221
+ body.contextual-chat-pane #attach-wrap,
222
+ body.contextual-chat-pane #attach-wrap .composer-tool-group-inputs { display: flex; flex-direction: column; align-items: stretch; width: 100%; gap: 2px; }
223
+ body.contextual-chat-pane #attach-wrap #input-more-btn { display: none !important; }
224
+ body.contextual-chat-pane #attach-file-btn,
225
+ body.contextual-chat-pane #attach-image-btn,
226
+ body.contextual-chat-pane #context-sources-add { display: flex !important; align-items: center; justify-content: start; gap: 8px; width: 100%; height: 34px; padding: 0 9px; font: inherit; font-size: 13px; border-radius: 6px; }
227
+ body.contextual-chat-pane #context-sources-btn-wrap { display: block !important; }
228
+ body.contextual-chat-pane #context-sources-picker { position: static; width: 100%; min-width: 0; max-height: 220px; box-shadow: none; }
229
+ body.contextual-chat-pane #attach-file-btn .lucide,
230
+ body.contextual-chat-pane #attach-image-btn .lucide { width: 16px; height: 16px; }
231
+ body.contextual-chat-pane .stt-recording #contextual-attach-btn { display: none; }
232
+ body.contextual-chat-pane #input-row.stt-recording::before,
233
+ body.contextual-chat-pane #input-row:has(.stt-progress:not([hidden]))::before { grid-area: 4 / 1 / 7 / -1; }
234
+ body.contextual-chat-pane #input-row .stt-progress { grid-area: 6 / 1 / 7 / -1; width: auto; margin: 0 8px 6px; }
235
+ @media (pointer: coarse) {
236
+ body.contextual-chat-pane #input-row, body.contextual-chat-pane #input-row:focus-within { grid-template-columns: 44px minmax(0, 1fr) auto 58px 46px; }
237
+ body.contextual-chat-pane #contextual-attach-btn, body.contextual-chat-pane #config-chip,
238
+ body.contextual-chat-pane #attach-file-btn, body.contextual-chat-pane #attach-image-btn,
239
+ body.contextual-chat-pane #context-sources-add { min-height: 44px; }
240
+ }
@@ -5,8 +5,9 @@ import { refreshIcons } from './icons.js';
5
5
  import { store } from './store.js';
6
6
  import { getSendBtn, getStatusDot } from './dom-refs.js';
7
7
  import { onThemeChange } from './theme.js';
8
- import { getActivityEl, setActivityEl, addToMessages, scrollToBottom, VENDOR_AVATARS } from './app-rendering.js';
8
+ import { getActivityEl, setActivityEl, addToMessages, scrollToBottom } from './app-rendering.js';
9
9
  import { escapeHtml } from './utils.js';
10
+ import { resolveProjectAssistantIdentity, applyProjectAssistantIdentity } from './project-chat-identity.js';
10
11
 
11
12
  // --- Module-owned state ---
12
13
  var faviconLink, faviconOrigHref, faviconCanvas, faviconCtx, faviconImg, faviconImgReady;
@@ -210,14 +211,12 @@ export function setActivity(text) {
210
211
  if (!getActivityEl()) {
211
212
  var _actEl = document.createElement("div");
212
213
  _actEl.className = "activity-inline";
213
- // In channel mode (Claude Code / Codex sessions), include the vendor
214
- // avatar so the user can see who is responding while the dots are
215
- // animating. DM mode handles its own avatar via mate-thinking
216
- // elements and shouldn't pick up a vendor avatar here.
214
+ // The same session-bound identity owns waiting, thoughts and replies.
215
+ // DM mode retains its own Mate-thinking row.
217
216
  var _isDm = !!(store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate);
218
217
  if (!_isDm) {
219
- var _vendor = store.get('currentVendor') || 'claude';
220
- var _avatarUrl = VENDOR_AVATARS[_vendor] || VENDOR_AVATARS.claude;
218
+ var _identity = resolveProjectAssistantIdentity(store.snap(), store.get('currentVendor'));
219
+ var _avatarUrl = _identity.avatarUrl;
221
220
  _actEl.innerHTML =
222
221
  '<img class="activity-inline-avatar" src="' + escapeHtml(_avatarUrl) + '" alt="">' +
223
222
  '<div class="mate-thinking-dots"><span></span><span></span><span></span></div>';
@@ -236,6 +235,7 @@ export function setActivity(text) {
236
235
  if (!label) { label = document.createElement("span"); label.className = "activity-state-label activity-text"; activity.appendChild(label); }
237
236
  label.textContent = labels[text];
238
237
  } else if (label) label.remove();
238
+ applyProjectAssistantIdentity(activity, resolveProjectAssistantIdentity(store.snap(), store.get('currentVendor')));
239
239
  addToMessages(activity);
240
240
  scrollToBottom();
241
241
  } else {
@@ -1,3 +1,5 @@
1
+ import { handleContextualConversations } from './contextual-mate-sessions.js';
2
+ import { handleMateContextMessage } from './contextual-mate-context.js';
1
3
  import { handleIssueReference } from './clay-issue-links.js';
2
4
  // Narrow pre-router for Home and Capsule protocol responses kept out of the legacy router.
3
5
 
@@ -19,6 +21,8 @@ import { handleProjectMateCreationMessage } from './project-mate-creation-worksp
19
21
  import { handleProjectDebatePlanningMessage } from './project-debate-planning-workspace.js';
20
22
 
21
23
  export function handleHomeProtocolMessage(msg) {
24
+ if (handleContextualConversations(msg)) return true;
25
+ if (handleMateContextMessage(msg)) return true;
22
26
  if (handleIssueReference(msg)) return true;
23
27
  if (handleClaySessionTarget(msg)) return true;
24
28
  if (isHomeWorkspaceAssignmentMessage(msg)) {
@@ -104,7 +108,7 @@ function dispatchMessage(msg) {
104
108
  if (handleDefaultAiMessage(msg)) return;
105
109
  if (handleCursorSharingMessage(msg)) return;
106
110
  var searchHandled = handleSearchClayMessage(msg);
107
- if (searchHandled && (msg.type === "home_clay_activity" || msg.type === "home_mate_stopping" || msg.type.indexOf("home_mate_permission_") === 0)) return;
111
+ if (searchHandled) return;
108
112
  if (handleHomeProtocolMessage(msg)) return;
109
113
  processAppMessage(msg);
110
114
  }