clay-server 4.1.0-beta.9 → 4.2.0-beta.1

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 (119) hide show
  1. package/lib/context-usage.js +47 -0
  2. package/lib/daemon-project-access.js +14 -0
  3. package/lib/daemon.js +7 -0
  4. package/lib/issues-mcp-server.js +1 -1
  5. package/lib/issues-service.js +3 -1
  6. package/lib/loop-registry-occurrence.js +1 -0
  7. package/lib/notes.js +20 -3
  8. package/lib/os-users.js +6 -0
  9. package/lib/project-connection.js +14 -3
  10. package/lib/project-file-path.js +13 -0
  11. package/lib/project-file-watch.js +19 -8
  12. package/lib/project-filesystem.js +10 -16
  13. package/lib/project-http.js +53 -48
  14. package/lib/project-issue-launch.js +3 -1
  15. package/lib/project-logs-mcp-server.js +8 -8
  16. package/lib/project-logs-schema.js +1 -1
  17. package/lib/project-logs-service.js +46 -13
  18. package/lib/project-logs-store.js +17 -15
  19. package/lib/project-logs.js +70 -0
  20. package/lib/project-loop.js +1 -0
  21. package/lib/project-notifications.js +1 -1
  22. package/lib/project-scheduled-result-store.js +131 -0
  23. package/lib/project-scheduled-task-execution.js +35 -2
  24. package/lib/project-session-notes.js +11 -4
  25. package/lib/project-sessions.js +39 -6
  26. package/lib/project-user-message.js +47 -0
  27. package/lib/project.js +57 -3
  28. package/lib/public/app.js +27 -9
  29. package/lib/public/css/avatar-imprints.css +1 -1
  30. package/lib/public/css/command-palette.css +57 -19
  31. package/lib/public/css/git-panel.css +13 -12
  32. package/lib/public/css/git-placard.css +4 -4
  33. package/lib/public/css/issues.css +79 -32
  34. package/lib/public/css/mobile-nav.css +7 -0
  35. package/lib/public/css/notifications-center.css +3 -3
  36. package/lib/public/css/project-logs.css +15 -0
  37. package/lib/public/css/sidebar.css +7 -0
  38. package/lib/public/css/sticky-notes.css +29 -0
  39. package/lib/public/index.html +10 -0
  40. package/lib/public/modules/app-connection.js +9 -1
  41. package/lib/public/modules/app-favicon.js +10 -0
  42. package/lib/public/modules/app-messages.js +50 -12
  43. package/lib/public/modules/app-notifications.js +2 -0
  44. package/lib/public/modules/app-panels.js +77 -23
  45. package/lib/public/modules/app-projects.js +2 -0
  46. package/lib/public/modules/app-rate-limit.js +148 -61
  47. package/lib/public/modules/avatar-imprint.js +25 -70
  48. package/lib/public/modules/avatar.js +12 -4
  49. package/lib/public/modules/chat-render-runtime.js +20 -0
  50. package/lib/public/modules/clay-issue-links.js +24 -2
  51. package/lib/public/modules/clay-log-links.js +12 -1
  52. package/lib/public/modules/context-view-preference.js +143 -0
  53. package/lib/public/modules/default-vendor.js +116 -0
  54. package/lib/public/modules/filebrowser-tabs.js +1 -0
  55. package/lib/public/modules/filebrowser.js +45 -18
  56. package/lib/public/modules/issues-render.js +36 -10
  57. package/lib/public/modules/issues.js +95 -13
  58. package/lib/public/modules/markdown.js +3 -1
  59. package/lib/public/modules/pane-bridge.js +14 -8
  60. package/lib/public/modules/pane-issue-bridge.js +27 -0
  61. package/lib/public/modules/permission-hints.js +13 -0
  62. package/lib/public/modules/profile.js +9 -9
  63. package/lib/public/modules/project-activation.js +49 -0
  64. package/lib/public/modules/project-logs-render.js +13 -3
  65. package/lib/public/modules/project-logs.js +45 -15
  66. package/lib/public/modules/project-session-visibility-settings.js +138 -0
  67. package/lib/public/modules/project-settings.js +5 -0
  68. package/lib/public/modules/right-workbench.js +44 -0
  69. package/lib/public/modules/scheduled-result-client.js +33 -0
  70. package/lib/public/modules/scheduled-result-display.js +50 -0
  71. package/lib/public/modules/scheduled-result-notification.js +31 -0
  72. package/lib/public/modules/scheduled-task-presentation.js +15 -0
  73. package/lib/public/modules/scheduled-tasks.js +9 -21
  74. package/lib/public/modules/sidebar-mobile.js +51 -3
  75. package/lib/public/modules/sidebar-projects.js +14 -3
  76. package/lib/public/modules/sidebar-sessions.js +65 -5
  77. package/lib/public/modules/split-session-boundary.js +28 -0
  78. package/lib/public/modules/split-view.js +42 -7
  79. package/lib/public/modules/sticky-notes-browser.js +130 -18
  80. package/lib/public/modules/sticky-notes.js +2 -3
  81. package/lib/public/modules/terminal.js +4 -4
  82. package/lib/public/modules/thinking-lifecycle.js +4 -1
  83. package/lib/public/modules/tools.js +5 -1
  84. package/lib/scheduled-run-state.js +10 -2
  85. package/lib/scheduler.js +1 -0
  86. package/lib/sdk-bridge.js +97 -61
  87. package/lib/sdk-message-processor.js +44 -13
  88. package/lib/sdk-skill-discovery.js +6 -28
  89. package/lib/sdk-turn-queue.js +14 -0
  90. package/lib/server-context-view.js +53 -0
  91. package/lib/server-default-vendor.js +60 -0
  92. package/lib/server.js +31 -7
  93. package/lib/session-notes-mcp-server.js +3 -5
  94. package/lib/session-pair-factory.js +2 -2
  95. package/lib/session-title-generator.js +89 -0
  96. package/lib/session-title-policy.js +8 -2
  97. package/lib/session-visibility.js +62 -1
  98. package/lib/sessions.js +28 -1
  99. package/lib/user-presence.js +6 -0
  100. package/lib/users-context-view-preferences.js +65 -0
  101. package/lib/users-default-vendor-preferences.js +80 -0
  102. package/lib/users.js +12 -0
  103. package/lib/ws-schema.js +13 -0
  104. package/lib/yoke/acp-agent-profiles.js +15 -2
  105. package/lib/yoke/adapters/acp.js +3 -2
  106. package/lib/yoke/adapters/antigravity.js +2 -2
  107. package/lib/yoke/adapters/claude-worker.js +11 -12
  108. package/lib/yoke/adapters/claude.js +75 -22
  109. package/lib/yoke/adapters/codex.js +216 -29
  110. package/lib/yoke/adapters/kiro.js +6 -5
  111. package/lib/yoke/claude-context-usage.js +57 -0
  112. package/lib/yoke/claude-message-correlation.js +31 -0
  113. package/lib/yoke/claude-model-switch.js +15 -0
  114. package/lib/yoke/index.js +5 -1
  115. package/lib/yoke/mcp-bridge-server.js +18 -5
  116. package/lib/yoke/session-skill-catalog.js +45 -0
  117. package/lib/yoke/skill-discovery.js +60 -20
  118. package/lib/yoke/vendor-registry.js +1 -1
  119. package/package.json +5 -7
@@ -6,6 +6,7 @@ var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
6
6
  var updateSnooze = require("./update-snooze");
7
7
  var yoke = require("./yoke");
8
8
  var sessionVisibility = require("./session-visibility");
9
+ var contextUsageModule = require("./context-usage");
9
10
 
10
11
  function vendorSupportsTui(vendor) {
11
12
  var info = yoke.getVendorInfo(vendor);
@@ -82,6 +83,7 @@ function formatAskUserAnswerAsMessage(input, answers) {
82
83
  * getLatestVersion, setLatestVersion
83
84
  */
84
85
  function attachSessions(ctx) {
86
+ var contextUsage = contextUsageModule.attachContextUsage(ctx);
85
87
  var cwd = ctx.cwd;
86
88
  var slug = ctx.slug;
87
89
  var isMate = ctx.isMate;
@@ -108,6 +110,7 @@ function attachSessions(ctx) {
108
110
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
109
111
  var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
110
112
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
113
+ var hydrateSkillCatalog = ctx.hydrateSkillCatalog || function () { return []; };
111
114
  function stopSession(session, actor) {
112
115
  if (!session) return false;
113
116
  session._pendingMessageDrainPaused = true;
@@ -552,7 +555,7 @@ function attachSessions(ctx) {
552
555
  }
553
556
  sm.sweepBlankSessions();
554
557
  var sessionOpts = {
555
- sessionVisibility: sessionVisibility.normalize(msg.sessionVisibility),
558
+ sessionVisibility: "private",
556
559
  vendor: prepared.runtime.vendor,
557
560
  model: prepared.runtime.model,
558
561
  effort: prepared.runtime.effort || null,
@@ -568,6 +571,7 @@ function attachSessions(ctx) {
568
571
  }
569
572
 
570
573
  function handleSessionsMessage(ws, msg) {
574
+ if (contextUsage.handleMessage(ws, msg)) return true;
571
575
 
572
576
  if (msg.type === "push_subscribe") {
573
577
  var _pushUserId = ws._clayUser ? ws._clayUser.id : null;
@@ -615,7 +619,15 @@ function attachSessions(ctx) {
615
619
  sm.sweepBlankSessions();
616
620
  var sessionOpts = {};
617
621
  if (ws._clayUser && usersModule.isMultiUser()) sessionOpts.ownerId = ws._clayUser.id;
618
- sessionOpts.sessionVisibility = sessionVisibility.normalize(msg.sessionVisibility);
622
+ // Only ordinary project conversations may opt into this setting.
623
+ // Scheduled interviews and background/worker creation paths remain explicit.
624
+ var visibilityChoice = sessionVisibility.resolveNewSessionVisibility(getProjectAccess(), msg, isMate);
625
+ if (visibilityChoice.error) {
626
+ scheduledRequestError(ws, msg, visibilityChoice.error);
627
+ if (!msg.requestId) sendTo(ws, { type: "error", text: visibilityChoice.error });
628
+ return true;
629
+ }
630
+ sessionOpts.sessionVisibility = visibilityChoice.visibility;
619
631
  if (msg.vendor) sessionOpts.vendor = msg.vendor;
620
632
  // Mode resolution: vendors without a TUI session mode are always GUI.
621
633
  // TUI-capable sessions honor the explicit msg.mode if provided, otherwise
@@ -697,8 +709,7 @@ function attachSessions(ctx) {
697
709
  if (reusable) {
698
710
  if (sessionOpts.vendor && reusable.vendor !== sessionOpts.vendor) reusable.vendor = sessionOpts.vendor;
699
711
  if (!reusable.effort) reusable.effort = sessionOpts.effort;
700
- sm.setSessionVisibility(reusable.localId, sessionOpts.sessionVisibility);
701
- sessionVisibility.revokeViewers(ctx, reusable);
712
+ // Reusable blanks already exist; do not retroactively change them.
702
713
  sm.switchSession(reusable.localId, ws);
703
714
  newSess = reusable;
704
715
  } else {
@@ -731,6 +742,14 @@ function attachSessions(ctx) {
731
742
  return true;
732
743
  }
733
744
 
745
+ if (msg.type === "set_project_session_visibility_default") {
746
+ sessionVisibility.handleDefaultChange({
747
+ slug: slug, isMate: isMate, osUsers: osUsers, usersModule: usersModule, getProjectAccess: getProjectAccess,
748
+ opts: opts, sendTo: sendTo,
749
+ }, ws, msg);
750
+ return true;
751
+ }
752
+
734
753
  if (msg.type === "set_session_bookmark") {
735
754
  if (typeof msg.sessionId === "number") {
736
755
  var bookmarkTarget = sm.sessions.get(msg.sessionId);
@@ -981,6 +1000,8 @@ function attachSessions(ctx) {
981
1000
  if (msg.id && sm.sessions.has(msg.id) && msg.title) {
982
1001
  var s = sm.sessions.get(msg.id);
983
1002
  s.title = String(msg.title).substring(0, 100);
1003
+ s.titleProvisional = false;
1004
+ s.titleAutoGenerated = false;
984
1005
  s.titleManuallySet = true;
985
1006
  sm.saveSessionFile(s);
986
1007
  sm.notifySessionRenamed(s.localId);
@@ -1487,7 +1508,11 @@ function attachSessions(ctx) {
1487
1508
  } else {
1488
1509
  forkHistory = session.history.slice();
1489
1510
  }
1490
- var forked = sm.createSession({ vendor: session.vendor, ownerId: session.ownerId || null }, ws);
1511
+ var forked = sm.createSession({
1512
+ vendor: session.vendor,
1513
+ ownerId: session.ownerId || null,
1514
+ sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1515
+ }, ws);
1491
1516
  forked.cliSessionId = result.sessionId;
1492
1517
  forked.title = forkTitle;
1493
1518
  forked.history = forkHistory;
@@ -1505,7 +1530,14 @@ function attachSessions(ctx) {
1505
1530
  // Read history from CLI session files
1506
1531
  var cliSess = require("./cli-sessions");
1507
1532
  return cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId).then(function(history) {
1508
- var forked = sm.resumeSession(result.sessionId, { history: history, title: forkTitle }, ws);
1533
+ var forked = sm.resumeSession(result.sessionId, {
1534
+ history: history,
1535
+ title: forkTitle,
1536
+ vendor: session.vendor,
1537
+ ownerId: session.ownerId || null,
1538
+ sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
1539
+ sessionVisibilityExplicit: session.sessionVisibilityExplicit === true,
1540
+ }, ws);
1509
1541
  if (forked) {
1510
1542
  ws._clayActiveSession = forked.localId;
1511
1543
  sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
@@ -1679,6 +1711,7 @@ function attachSessions(ctx) {
1679
1711
  return true;
1680
1712
  }
1681
1713
 
1714
+ if (decision === "allow_always" && pending.suppressAlwaysAllowRule) decision = "allow";
1682
1715
  if (decision === "allow" || decision === "allow_always") {
1683
1716
  if (decision === "allow_always") {
1684
1717
  if (!session.allowedTools) session.allowedTools = {};
@@ -24,6 +24,7 @@ var fs = require("fs");
24
24
  * pendingMessageQueue,
25
25
  * loadContextSources, saveContextSources,
26
26
  * digestDmTurn, gateMemory,
27
+ * getProjectAccess, canAccessProjectSlug,
27
28
  * adapter - YOKE adapter instance
28
29
  */
29
30
  function attachUserMessage(ctx) {
@@ -55,6 +56,8 @@ function attachUserMessage(ctx) {
55
56
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
56
57
  var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
57
58
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
59
+ var getProjectAccess = ctx.getProjectAccess;
60
+ var canAccessProjectSlug = ctx.canAccessProjectSlug;
58
61
 
59
62
  var hydrateImageRefs = ctx.hydrateImageRefs;
60
63
  var saveImageFile = ctx.saveImageFile;
@@ -99,6 +102,21 @@ function attachUserMessage(ctx) {
99
102
  }
100
103
  }
101
104
 
105
+ function canHumanManageNotes(ws) {
106
+ if (!usersModule.isMultiUser()) return true;
107
+ if (!ws || !ws._clayUser || !ws._clayUser.id) return false;
108
+ var actor = usersModule.findUserById(ws._clayUser.id);
109
+ if (!actor) return false;
110
+ if (typeof canAccessProjectSlug === "function" && !canAccessProjectSlug(actor.id, slug)) return false;
111
+ var access = typeof getProjectAccess === "function" ? getProjectAccess() : null;
112
+ return !!(access && !access.error && usersModule.canAccessProject(actor.id, access));
113
+ }
114
+
115
+ function noteDeleteResult(ws, msg, ok, error) {
116
+ sendTo(ws, { type: "note_delete_result", projectSlug: slug, noteId: msg.id || null,
117
+ requestId: msg.requestId || null, ok: !!ok, error: error || null });
118
+ }
119
+
102
120
  // --------------- Main handler ---------------
103
121
 
104
122
  function handleUserMessage(ws, msg, forcedSession, internal) {
@@ -171,6 +189,31 @@ function attachUserMessage(ctx) {
171
189
  return true;
172
190
  }
173
191
 
192
+ if (msg.type === "note_delete_permanently") {
193
+ if (!msg.id || !canHumanManageNotes(ws)) {
194
+ noteDeleteResult(ws, msg, false, "You are not authorized to delete notes in this project.");
195
+ return true;
196
+ }
197
+ var current = nm.list().filter(function (item) { return item && item.id === msg.id; })[0];
198
+ if (!current) {
199
+ noteDeleteResult(ws, msg, false, "Note not found.");
200
+ return true;
201
+ }
202
+ if (!notesLifecycle.isClosed(current)) {
203
+ noteDeleteResult(ws, msg, false, "Only closed notes can be permanently deleted.");
204
+ return true;
205
+ }
206
+ var removed = nm.removeClosed(msg.id);
207
+ if (!removed) {
208
+ noteDeleteResult(ws, msg, false, "The note changed before it could be deleted.");
209
+ return true;
210
+ }
211
+ send({ type: "note_deleted", id: removed.id });
212
+ noteDeleteResult(ws, msg, true, null);
213
+ syncNotesKnowledge();
214
+ return true;
215
+ }
216
+
174
217
  if (msg.type === "note_list_request") {
175
218
  sendTo(ws, { type: "notes_list", notes: nm.list() });
176
219
  return true;
@@ -479,6 +522,10 @@ function attachUserMessage(ctx) {
479
522
  function recordSessionTitle() {
480
523
  if (!session.title) {
481
524
  session.title = (msg.text || "Image").substring(0, 50);
525
+ // This title is an optimistic first-message label, not a semantic or
526
+ // explicit title. Persist its provenance so startQuery can preserve
527
+ // eligibility for model generation after this function returns.
528
+ session.titleProvisional = true;
482
529
  sm.saveSessionFile(session);
483
530
  sm.broadcastSessionList();
484
531
  // Sync auto-title to SDK
package/lib/project.js CHANGED
@@ -46,6 +46,7 @@ var { attachVendorLogin } = require("./project-vendor-login");
46
46
  var { attachConnection } = require("./project-connection");
47
47
  var { attachProjectScheduledMessages } = require("./project-scheduled-messages");
48
48
  var { attachMcp } = require("./project-mcp");
49
+ var { createSessionSkillCatalog } = require("./yoke/session-skill-catalog");
49
50
  var { createLocalMcp } = require("./mcp-local");
50
51
  var { attachEmail: attachEmailModule } = require("./project-email");
51
52
  var { attachSessionSpawn } = require("./project-session-spawn");
@@ -256,11 +257,20 @@ function createProjectContext(opts) {
256
257
  var serverPort = opts.port || 2633;
257
258
  var serverTls = opts.tls || false;
258
259
  var serverAuthToken = opts.authToken || null;
260
+ var mcpBridgeToken = opts.mcpBridgeToken || crypto.randomBytes(32).toString("hex");
259
261
  var latestVersion = null;
260
262
  var sessionTitleMigrationScheduled = false;
261
263
 
262
264
  // --- YOKE adapters (multi-vendor, lazy init) ---
263
- var _yokeState = yoke.createAdapters({ cwd: cwd, slug: slug, osUsers: osUsers });
265
+ var _yokeState = yoke.createAdapters({
266
+ cwd: cwd,
267
+ slug: slug,
268
+ osUsers: osUsers,
269
+ clayPort: serverPort,
270
+ clayTls: serverTls,
271
+ clayAuthToken: serverAuthToken,
272
+ clayBridgeToken: mcpBridgeToken,
273
+ });
264
274
  var adapters = _yokeState.adapters;
265
275
  // A new project has no remembered vendor. Select the first installed
266
276
  // provider by the shared Claude -> Codex -> Kiro preference order.
@@ -555,6 +565,7 @@ function createProjectContext(opts) {
555
565
  // --- File/directory watcher engine (delegated to project-file-watch.js) ---
556
566
  var _fileWatch = attachFileWatch({
557
567
  requestAccess: _requestAccess, fsAsUser: fsAsUser,
568
+ osUsers: osUsers,
558
569
  cwd: cwd,
559
570
  send: send,
560
571
  sendTo: sendTo,
@@ -717,6 +728,15 @@ function createProjectContext(opts) {
717
728
  var nm = createNotesManager({ cwd: cwd, send: send, sendTo: sendTo });
718
729
  var _sessionNotes = attachSessionNotes({
719
730
  nm: nm,
731
+ canUseSession: function (session) {
732
+ if (!session || sm.sessions.get(session.localId) !== session) return false;
733
+ if (!usersModule.isMultiUser()) return true;
734
+ var owner = session.ownerId && usersModule.findUserById(session.ownerId);
735
+ if (!owner) return false;
736
+ var access = opts.getProjectAccess ? opts.getProjectAccess() : { visibility: "public", ownerId: projectOwnerId || null };
737
+ if (opts.canAccessProjectSlug && !opts.canAccessProjectSlug(owner.id, slug)) return false;
738
+ return usersModule.canAccessSession(owner.id, session, access) && (!osUsers || !!getLinuxUserForSession(session));
739
+ },
720
740
  send: send,
721
741
  isMate: isMate,
722
742
  broadcastWritten: function (message) {
@@ -774,6 +794,10 @@ function createProjectContext(opts) {
774
794
  var _issueWork = require("./project-issue-launch").attachIssueLaunch({
775
795
  sm: sm, resolveDefaultAi: opts.resolveDefaultAi, getSdk: function () { return sdk; },
776
796
  getLinuxUserForSession: getLinuxUserForSession, osUsers: osUsers,
797
+ getProjectAccess: function () {
798
+ if (isMate) return { visibility: "private", ownerId: projectOwnerId || null, allowedUsers: [] };
799
+ return opts.getProjectAccess ? opts.getProjectAccess() : { visibility: "public", ownerId: projectOwnerId || null };
800
+ },
777
801
  ensureProjectAccessForSession: ensureProjectAccessForSession,
778
802
  canStartWork: function (ws, actor) { return clients.has(ws) && ws.readyState === 1 && ws._clayUser === actor; },
779
803
  onProcessingChanged: onProcessingChanged, sendTo: sendTo,
@@ -1217,6 +1241,7 @@ function createProjectContext(opts) {
1217
1241
  clayPort: serverPort,
1218
1242
  clayTls: serverTls,
1219
1243
  clayAuthToken: serverAuthToken,
1244
+ clayBridgeToken: mcpBridgeToken,
1220
1245
  getWorkerPermissionRouter: function () {
1221
1246
  return isMate ? null : _sessionPair.workerPermission;
1222
1247
  },
@@ -1413,6 +1438,9 @@ function createProjectContext(opts) {
1413
1438
  canExecuteScheduledTask: function (record) {
1414
1439
  return !!(_scheduledTaskExecution && _scheduledTaskExecution.runnable(record).ok);
1415
1440
  },
1441
+ onScheduledRunTerminal: function (record, run) {
1442
+ if (_scheduledTaskExecution) _scheduledTaskExecution.recordTerminal(record, run);
1443
+ },
1416
1444
  onProcessingChanged: onProcessingChanged,
1417
1445
  hydrateImageRefs: hydrateImageRefs,
1418
1446
  });
@@ -1428,6 +1456,7 @@ function createProjectContext(opts) {
1428
1456
  getSdk: function () { return sdk; }, getLinuxUserForSession: getLinuxUserForSession,
1429
1457
  requiresLinuxUser: function () { return osUsers; },
1430
1458
  onProcessingChanged: onProcessingChanged,
1459
+ recordResult: function (result) { return _projectLogs.recordScheduledResult(result); },
1431
1460
  authorize: function (record) {
1432
1461
  return authorizeStoredOwner(record, {
1433
1462
  usersModule: usersModule, osUsers: osUsers,
@@ -1437,6 +1466,7 @@ function createProjectContext(opts) {
1437
1466
  });
1438
1467
  },
1439
1468
  });
1469
+ setTimeout(function () { _scheduledTaskExecution.syncTerminalResults(); }, 0);
1440
1470
 
1441
1471
  _scheduledTasks = attachScheduledTasks({
1442
1472
  cwd: cwd,
@@ -1583,8 +1613,8 @@ function createProjectContext(opts) {
1583
1613
  }
1584
1614
 
1585
1615
  // --- DM messages (delegated to server-level handler) ---
1586
- if (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 === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1587
- if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg);
1616
+ if (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") {
1617
+ if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
1588
1618
  return;
1589
1619
  }
1590
1620
  if (msg.type === "issue_reference_resolve" || msg.type === "home_clay_ask" || msg.type === "home_clay_session_resolve" || msg.type === "home_clay_log_resolve") {
@@ -1863,6 +1893,12 @@ function createProjectContext(opts) {
1863
1893
  }
1864
1894
 
1865
1895
  // --- Sessions/config/project handler (delegated to project-sessions.js) ---
1896
+ var _sessionSkillCatalog = createSessionSkillCatalog({
1897
+ cwd: cwd,
1898
+ osUsers: osUsers,
1899
+ getLinuxUser: getLinuxUserForSession,
1900
+ });
1901
+ sm.setSessionHydrator(_sessionSkillCatalog.hydrate);
1866
1902
  var _sessions = attachSessions({
1867
1903
  cwd: cwd,
1868
1904
  slug: slug,
@@ -1902,6 +1938,7 @@ function createProjectContext(opts) {
1902
1938
  getLinuxUserForSession: getLinuxUserForSession,
1903
1939
  ensureProjectAccessForSession: ensureProjectAccessForSession,
1904
1940
  getOsUserInfoForWs: getOsUserInfoForWs,
1941
+ hydrateSkillCatalog: _sessionSkillCatalog.hydrate,
1905
1942
  hydrateImageRefs: hydrateImageRefs,
1906
1943
  onProcessingChanged: onProcessingChanged,
1907
1944
  broadcastPresence: broadcastPresence,
@@ -1931,6 +1968,7 @@ function createProjectContext(opts) {
1931
1968
  saveContextSources: saveContextSources,
1932
1969
  _email: _email,
1933
1970
  _notifications: _notifications,
1971
+ _projectLogs: _projectLogs,
1934
1972
  });
1935
1973
 
1936
1974
  var _models = attachModels({
@@ -2030,6 +2068,8 @@ function createProjectContext(opts) {
2030
2068
  getSessionForWs: getSessionForWs,
2031
2069
  getLinuxUserForSession: getLinuxUserForSession,
2032
2070
  ensureProjectAccessForSession: ensureProjectAccessForSession,
2071
+ getProjectAccess: function () { return opts.getProjectAccess ? opts.getProjectAccess() : { visibility: "public", ownerId: projectOwnerId || null }; },
2072
+ canAccessProjectSlug: opts.canAccessProjectSlug || function () { return !usersModule.isMultiUser(); },
2033
2073
  getOsUserInfoForWs: getOsUserInfoForWs,
2034
2074
  hydrateImageRefs: hydrateImageRefs,
2035
2075
  saveImageFile: saveImageFile,
@@ -2112,6 +2152,12 @@ function createProjectContext(opts) {
2112
2152
  function getMcpBridgeHandler(sessionId, sessionOnly, queryGeneration) {
2113
2153
  var boundSession = Number.isInteger(sessionId) ? sm.sessions.get(sessionId) : null;
2114
2154
  var queryToolBridge = createSessionQueryToolBridge({ session: boundSession, sdk: sdk });
2155
+ function sessionQueryValid() {
2156
+ return !!boundSession
2157
+ && Number.isInteger(queryGeneration)
2158
+ && sm.sessions.get(boundSession.localId) === boundSession
2159
+ && Number(boundSession._sdkQueryGeneration || 0) === queryGeneration;
2160
+ }
2115
2161
  // Build set of local MCP server names to exclude (Codex handles these natively)
2116
2162
  var localMcpNames = {};
2117
2163
  try {
@@ -2126,6 +2172,7 @@ function createProjectContext(opts) {
2126
2172
  return {
2127
2173
  listTools: function () {
2128
2174
  var tools = [];
2175
+ if (sessionOnly && !sessionQueryValid()) return Promise.resolve(tools);
2129
2176
  var toJSONSchema;
2130
2177
  var zod;
2131
2178
  try { zod = require("zod"); toJSONSchema = zod.toJSONSchema; } catch (e) { /* fallback */ }
@@ -2251,6 +2298,9 @@ function createProjectContext(opts) {
2251
2298
  return Promise.resolve(tools);
2252
2299
  },
2253
2300
  callTool: function (serverName, toolName, args) {
2301
+ if (sessionOnly && !sessionQueryValid()) {
2302
+ return Promise.reject(new Error("Session tool unavailable or older query: " + serverName + "/" + toolName));
2303
+ }
2254
2304
  var queryCall = queryToolBridge.callTool(queryGeneration, serverName, toolName, args);
2255
2305
  if (queryCall) return queryCall;
2256
2306
  if (boundSession && serverName === "clay-sessions") {
@@ -2409,6 +2459,7 @@ function createProjectContext(opts) {
2409
2459
  getLatestVersion: function () { return latestVersion; },
2410
2460
  getTitle: function () { return title; },
2411
2461
  getProject: function () { return project; },
2462
+ hydrateSkillCatalog: _sessionSkillCatalog.hydrate,
2412
2463
  // Exposed so the first websocket connection can lazily warm up the
2413
2464
  // adapters for this project (see project-connection handleConnection).
2414
2465
  warmup: function (linuxUser) {
@@ -2633,6 +2684,9 @@ function createProjectContext(opts) {
2633
2684
  handleDisconnection: handleDisconnection,
2634
2685
  handleHTTP: handleHTTP,
2635
2686
  getMcpBridgeHandler: getMcpBridgeHandler,
2687
+ isMcpBridgeAuthenticated: function (req) {
2688
+ return !!(req && req.headers && req.headers["x-clay-bridge-token"] && req.headers["x-clay-bridge-token"] === mcpBridgeToken);
2689
+ },
2636
2690
  getStatus: getStatus,
2637
2691
  getSessionManager: function () { return sm; },
2638
2692
  createDelegatedSession: _delegatedSession.createDelegatedSession,
package/lib/public/app.js CHANGED
@@ -28,7 +28,7 @@ import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, add
28
28
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
29
29
  import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent, setScheduleBtnDisabled, setScheduleDelayMs, clearScheduleDelay } from './modules/input.js';
30
30
  import { initQrCode, triggerShare } from './modules/qrcode.js';
31
- import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
31
+ import { initFileBrowser, loadRootDirectory, reopenFileViewer, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
32
32
  import { initGitPanel } from './modules/git-panel.js';
33
33
  import { initWorkerPaneLock } from './modules/worker-pane-lock.js';
34
34
  import { initAutonomousRun } from './modules/autonomous-run.js';
@@ -36,7 +36,7 @@ import { initLoopInterview } from './modules/loop-interview.js';
36
36
  import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
37
37
  import { initContextSources, updateTerminalList, updateBrowserTabList, handleContextSourcesState, getActiveSources, hasActiveSources } from './modules/context-sources.js';
38
38
  import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, hideNotes, showNotes, isNotesVisible, createNote, setBrowserRefresh, toggleNotesTemporaryVisibility } from './modules/sticky-notes.js';
39
- import { initNotesBrowser, openNotesBrowser, closeNotesBrowser, isNotesBrowserOpen, renderNotesBrowser, registerExclusiveClosers } from './modules/sticky-notes-browser.js';
39
+ import { initNotesBrowser, openNotesBrowser, closeNotesBrowser, isNotesBrowserOpen, renderNotesBrowser } from './modules/sticky-notes-browser.js';
40
40
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme, getChatLayout } from './modules/theme.js';
41
41
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
42
42
  import { initServerSettings, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
@@ -71,6 +71,7 @@ import { rememberHomePrimarySurface } from './modules/home-surface.js';
71
71
  import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, handleFastModeState as _rlHandleFastModeState, resetRateLimitState } from './modules/app-rate-limit.js';
72
72
  import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
73
73
  import { initDefaultAi } from './modules/default-ai.js';
74
+ import { requestDefaultVendor } from './modules/default-vendor.js';
74
75
  import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity } from './modules/app-favicon.js';
75
76
  import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
76
77
  import { initSessionActions } from './modules/session-actions.js';
@@ -286,6 +287,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
286
287
  currentSlug: currentSlug,
287
288
  activeProjectSlug: null,
288
289
  sessionActivatedProjectSlug: null,
290
+ sessionListProjectSlug: null,
291
+ splitGroupsProjectSlug: null,
289
292
  socketPath: null,
290
293
  pendingHomeProjectSlug: null,
291
294
  currentProjectOwnerId: null,
@@ -323,6 +326,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
323
326
  connected: false,
324
327
  fileReadRequest: null,
325
328
  pendingFileNavigation: null,
329
+ rightWorkbenchOwner: null,
330
+ rightWorkbenchRevision: 0,
331
+ fileViewerOpenRevision: 0,
326
332
  cursorSharingEnabled: false,
327
333
  cursorSharingHydrated: false,
328
334
  pendingOutboundMessages: [],
@@ -381,6 +387,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
381
387
  projectLogsCanDelete: false,
382
388
  projectLogsUnread: 0,
383
389
  projectLogsSeenRevisions: {},
390
+ scheduledResultUnread: 0,
391
+ scheduledResultAutoFocusHandled: false,
392
+ scheduledResultFocusRef: null,
393
+ scheduledResultPendingReadRef: null,
394
+ scheduledResultSeenRefs: {},
395
+ scheduledResultSessionRequestId: null,
384
396
  issuesOpen: false,
385
397
  issuesView: "list",
386
398
  issuesSelected: null,
@@ -388,6 +400,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
388
400
  issuesRequests: {},
389
401
  issuesViewRequest: null,
390
402
  issuesViewGeneration: 0,
403
+ issuesUpdateAvailable: null,
391
404
  issuesCache: {},
392
405
  issuesEpoch: 0,
393
406
  issuesSearchTimer: null,
@@ -418,6 +431,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
418
431
  defaultAiState: { loading: false, saving: false, refreshRequestId: null, saveRequestId: null, catalogRequestIds: {}, catalogs: {}, preference: null, selection: null, installedVendors: [], accountAvailable: true, accountId: null, serverEpoch: null, canonicalRevision: 0, error: "" },
419
432
  defaultAiDraft: { vendor: "", model: "", effort: "" },
420
433
  defaultAiDraftDirty: false,
434
+ defaultVendorState: { loading: false, saving: false, getRequestId: null, saveRequestId: null, preference: null, preferencePresent: false, installedVendors: [], accountId: null, projectSlug: null, serverEpoch: null, canonicalRevision: 0, error: "" },
435
+ projectSessionVisibilityState: { active: null, pending: null },
421
436
 
422
437
  // dm
423
438
  dmTargetUser: null,
@@ -434,6 +449,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
434
449
  pendingShellCommandId: null,
435
450
  mateProjectSlug: null,
436
451
  myUserId: null,
452
+ contextViewPreferenceState: { mode: "off", canonicalMode: "off", preferencePresent: false, loading: false, saving: false, requestId: null, saveRequestId: null, pendingSaves: [], accountId: null, serverEpoch: null, rejectedEpoch: null, canonicalRevision: 0, error: "" },
453
+ contextViewOverride: null,
437
454
  isMultiUserMode: false,
438
455
  dmUnread: {},
439
456
  dmRemovedUsers: {},
@@ -469,6 +486,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
469
486
  lastVendor: "",
470
487
  // Static adapter metadata sent before any vendor is initialized.
471
488
  vendorInfo: {},
489
+ // Rate-limit data is projected only for the displayed session/vendor.
490
+ rateLimitState: {},
472
491
  // How Claude sessions open: "gui" (default) or "tui". The server sends
473
492
  // claude_open_mode_changed on connect; this seeds it beforehand so the
474
493
  // new-session menu doesn't flash the TUI-only entry.
@@ -544,7 +563,11 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
544
563
  newSessionBtn: newSessionBtn,
545
564
  headerTitleEl: headerTitleEl,
546
565
  showConfirm: showConfirm,
547
- onFilesTabOpen: function () { loadRootDirectory(); },
566
+ onFilesTabOpen: function () {
567
+ reopenFileViewer();
568
+ loadRootDirectory();
569
+ if (isSchedulerOpen()) closeScheduler();
570
+ },
548
571
  requestKnowledgeList: function () { requestKnowledgeList(); },
549
572
  switchProject: function (slug) { switchProject(slug); },
550
573
  openTerminal: function () { openTerminal(); },
@@ -1009,7 +1032,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1009
1032
  // the project Mate DM preference therefore hides only Mate shortcuts.
1010
1033
  if (document.body) document.body.classList.add('is-multi-user');
1011
1034
  }
1012
- if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); }
1035
+ if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); requestDefaultVendor(); }
1013
1036
  if (d.permissions) store.set({ permissions: d.permissions });
1014
1037
  if (d.mustChangePin) showForceChangePinOverlay();
1015
1038
  // Single-user mode: clear user strip skeletons immediately (no presence message will arrive)
@@ -1188,9 +1211,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1188
1211
  // The browser refreshes from note messages without the canvas module having
1189
1212
  // to import it, which would be a cycle.
1190
1213
  setBrowserRefresh(function () { if (isNotesBrowserOpen()) renderNotesBrowser(); });
1191
- // Opening the browser claims the single right workbench slot. Registered here
1192
- // so the browser module never imports the other tools.
1193
- registerExclusiveClosers([closeIssues, closeProjectLogs, closeScheduledTasks, closeFileViewer, closeTerminal]);
1194
1214
 
1195
1215
  // --- Sticky Notes sidebar button (create new note) ---
1196
1216
  var stickyNotesSidebarBtn = $("sticky-notes-sidebar-btn");
@@ -1229,10 +1249,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1229
1249
  }
1230
1250
 
1231
1251
  // Close the notes browser / scheduler panel when switching to other sidebar panels
1232
- var fileBrowserBtn = $("file-browser-btn");
1233
1252
  var gitPlacardMoreBtn = $("git-placard-more");
1234
1253
  var terminalSidebarBtn = $("terminal-sidebar-btn");
1235
- if (fileBrowserBtn) fileBrowserBtn.addEventListener("click", function () { closeIssues(); closeProjectLogs(); closeScheduledTasks(); if (isNotesBrowserOpen()) closeNotesBrowser(); if (isSchedulerOpen()) closeScheduler(); });
1236
1254
  if (gitPlacardMoreBtn) gitPlacardMoreBtn.addEventListener("click", function () { closeIssues(); closeProjectLogs(); closeScheduledTasks(); if (isNotesBrowserOpen()) closeNotesBrowser(); if (isSchedulerOpen()) closeScheduler(); });
1237
1255
  if (terminalSidebarBtn) terminalSidebarBtn.addEventListener("click", function () { closeIssues(); closeProjectLogs(); closeScheduledTasks(); if (isNotesBrowserOpen()) closeNotesBrowser(); if (isSchedulerOpen()) closeScheduler(); });
1238
1256
 
@@ -1,4 +1,4 @@
1
- /* Clay Imprints use one consistent, pressed-square silhouette everywhere. */
1
+ /* Local pixel avatars use one consistent, pressed-square silhouette everywhere. */
2
2
  .activity-inline-avatar,
3
3
  .ask-mate-avatar,
4
4
  .client-avatar,
@@ -512,8 +512,45 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
512
512
  .search-clay-activity-item small { color: var(--text-dimmer); font-size: 10px; line-height: 1.35; }
513
513
  @keyframes searchClaySpin { to { transform: rotate(360deg); } }
514
514
 
515
- .clayos-session-link,
516
- .clayos-log-link {
515
+ .clayos-record-link {
516
+ display: inline-flex;
517
+ align-items: center;
518
+ gap: 6px;
519
+ min-height: 28px;
520
+ margin: 2px 1px;
521
+ padding: 3px 8px 3px 6px;
522
+ border: 1px solid color-mix(in srgb, var(--record-color, var(--accent)) 32%, var(--border));
523
+ border-radius: 8px;
524
+ background: color-mix(in srgb, var(--record-color, var(--accent)) 7%, var(--bg-alt));
525
+ color: var(--text);
526
+ font: inherit;
527
+ font-size: 11px;
528
+ line-height: 1;
529
+ vertical-align: middle;
530
+ cursor: pointer;
531
+ }
532
+ .clayos-record-link::before {
533
+ content: "\2197";
534
+ display: grid;
535
+ place-items: center;
536
+ width: 17px;
537
+ height: 17px;
538
+ border-radius: 5px;
539
+ background: color-mix(in srgb, var(--record-color, var(--accent)) 14%, transparent);
540
+ color: var(--record-color, var(--accent));
541
+ font-size: 12px;
542
+ }
543
+ .clayos-record-link > span { font-weight: 620; }
544
+ .clayos-record-link > small { min-width: 0; max-width: 28em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-inline-start: 1px solid color-mix(in srgb, currentColor 22%, transparent); padding-inline-start: 6px; color: var(--text-muted); font-size: 11px; }
545
+ .clayos-record-link:hover { border-color: color-mix(in srgb, var(--record-color, var(--accent)) 58%, var(--border)); background: color-mix(in srgb, var(--record-color, var(--accent)) 11%, var(--bg-alt)); }
546
+ .clayos-record-link:focus-visible { outline: 2px solid var(--record-color, var(--accent)); outline-offset: 2px; }
547
+ .clayos-record-link.is-loading { opacity: 0.62; cursor: progress; }
548
+ .clayos-record-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
549
+ .clayos-record-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
550
+ .clayos-issue-link { --record-color: var(--link, var(--accent)); }
551
+ .clayos-log-link { --record-color: var(--accent); }
552
+
553
+ .clayos-session-link {
517
554
  display: inline-flex;
518
555
  align-items: center;
519
556
  gap: 6px;
@@ -530,8 +567,7 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
530
567
  vertical-align: middle;
531
568
  cursor: pointer;
532
569
  }
533
- .clayos-session-link::before,
534
- .clayos-log-link::before {
570
+ .clayos-session-link::before {
535
571
  content: "\2197";
536
572
  display: grid;
537
573
  place-items: center;
@@ -542,21 +578,13 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
542
578
  color: var(--accent);
543
579
  font-size: 12px;
544
580
  }
545
- .clayos-log-link::before { content: "\2261"; }
546
- .clayos-session-link > span,
547
- .clayos-log-link > span { font-weight: 620; }
548
- .clayos-session-link > small,
549
- .clayos-log-link > small { color: var(--text-muted); font-size: 10px; }
550
- .clayos-session-link:hover,
551
- .clayos-log-link:hover { border-color: color-mix(in srgb, var(--accent) 52%, var(--border)); background: color-mix(in srgb, var(--accent) 11%, var(--bg-alt)); }
552
- .clayos-session-link:focus-visible,
553
- .clayos-log-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
554
- .clayos-session-link.is-loading,
555
- .clayos-log-link.is-loading { opacity: 0.62; cursor: progress; }
556
- .clayos-session-link.is-loading::before,
557
- .clayos-log-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
558
- .clayos-session-link.is-error,
559
- .clayos-log-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
581
+ .clayos-session-link > span { font-weight: 620; }
582
+ .clayos-session-link > small { color: var(--text-muted); font-size: 10px; }
583
+ .clayos-session-link:hover { border-color: color-mix(in srgb, var(--accent) 52%, var(--border)); background: color-mix(in srgb, var(--accent) 11%, var(--bg-alt)); }
584
+ .clayos-session-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
585
+ .clayos-session-link.is-loading { opacity: 0.62; cursor: progress; }
586
+ .clayos-session-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
587
+ .clayos-session-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
560
588
 
561
589
  .search-clay-pending {
562
590
  align-self: flex-start;
@@ -913,3 +941,13 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
913
941
  font-family: inherit;
914
942
  color: var(--text-muted);
915
943
  }
944
+
945
+ .clayos-issue-link { max-width: min(100%, 30em); box-sizing: border-box; }
946
+ .clayos-issue-link::before,
947
+ .clayos-log-link::before { content: "\2261"; }
948
+ .clayos-issue-link::before { content: "\25CE"; flex: 0 0 17px; }
949
+ .clayos-issue-link[data-status="in_progress"]::before { color: var(--warning, var(--accent)); }
950
+ .clayos-issue-link[data-status="resolved"]::before { content: "\2713"; color: var(--success, var(--accent)); }
951
+ .clayos-issue-link[data-status="closed"]::before { content: "\00D7"; color: var(--text-dimmer); }
952
+ .clayos-issue-link > span { flex-shrink: 0; }
953
+ .clayos-log-link { max-width: 100%; box-sizing: border-box; }