clay-server 4.1.0-beta.8 → 4.1.0

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/issues-mcp-server.js +1 -1
  3. package/lib/issues-service.js +3 -1
  4. package/lib/loop-registry-occurrence.js +1 -0
  5. package/lib/notes.js +20 -3
  6. package/lib/os-users.js +6 -0
  7. package/lib/project-connection.js +14 -3
  8. package/lib/project-file-path.js +13 -0
  9. package/lib/project-file-watch.js +19 -8
  10. package/lib/project-filesystem.js +10 -16
  11. package/lib/project-http.js +53 -48
  12. package/lib/project-logs-mcp-server.js +8 -8
  13. package/lib/project-logs-schema.js +1 -1
  14. package/lib/project-logs-service.js +46 -13
  15. package/lib/project-logs-store.js +17 -15
  16. package/lib/project-logs.js +70 -0
  17. package/lib/project-loop.js +1 -0
  18. package/lib/project-notifications.js +1 -1
  19. package/lib/project-pending-message-queue.js +71 -7
  20. package/lib/project-scheduled-messages.js +235 -52
  21. package/lib/project-scheduled-result-store.js +131 -0
  22. package/lib/project-scheduled-task-execution.js +35 -2
  23. package/lib/project-session-notes.js +11 -4
  24. package/lib/project-sessions.js +7 -0
  25. package/lib/project-user-message.js +72 -5
  26. package/lib/project.js +60 -5
  27. package/lib/public/app.js +27 -12
  28. package/lib/public/css/avatar-imprints.css +1 -1
  29. package/lib/public/css/command-palette.css +20 -0
  30. package/lib/public/css/git-panel.css +13 -12
  31. package/lib/public/css/git-placard.css +4 -4
  32. package/lib/public/css/input.css +0 -109
  33. package/lib/public/css/issues.css +78 -31
  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/pending-message-queue.css +3 -0
  37. package/lib/public/css/project-logs.css +15 -0
  38. package/lib/public/css/sidebar.css +7 -0
  39. package/lib/public/css/sticky-notes.css +29 -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 +49 -37
  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 +3 -0
  46. package/lib/public/modules/app-rate-limit.js +148 -243
  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 -2
  50. package/lib/public/modules/clay-issue-links.js +23 -2
  51. package/lib/public/modules/clay-log-links.js +11 -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/input.js +81 -20
  57. package/lib/public/modules/issues-render.js +36 -10
  58. package/lib/public/modules/issues.js +95 -13
  59. package/lib/public/modules/markdown.js +3 -1
  60. package/lib/public/modules/pane-bridge.js +14 -8
  61. package/lib/public/modules/pane-issue-bridge.js +27 -0
  62. package/lib/public/modules/pending-message-queue.js +43 -1
  63. package/lib/public/modules/permission-hints.js +13 -0
  64. package/lib/public/modules/profile.js +9 -9
  65. package/lib/public/modules/project-activation.js +49 -0
  66. package/lib/public/modules/project-logs-render.js +13 -3
  67. package/lib/public/modules/project-logs.js +45 -15
  68. package/lib/public/modules/right-workbench.js +44 -0
  69. package/lib/public/modules/scheduled-message-state.js +12 -36
  70. package/lib/public/modules/scheduled-result-client.js +33 -0
  71. package/lib/public/modules/scheduled-result-display.js +50 -0
  72. package/lib/public/modules/scheduled-result-notification.js +31 -0
  73. package/lib/public/modules/scheduled-task-presentation.js +15 -0
  74. package/lib/public/modules/scheduled-tasks.js +9 -21
  75. package/lib/public/modules/sidebar-mobile.js +51 -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 +23 -6
  93. package/lib/session-notes-mcp-server.js +3 -5
  94. package/lib/session-pair-factory.js +2 -2
  95. package/lib/session-pair-mcp-server.js +5 -4
  96. package/lib/session-title-generator.js +89 -0
  97. package/lib/session-title-policy.js +8 -2
  98. package/lib/sessions.js +26 -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 +11 -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
@@ -1,5 +1,6 @@
1
1
  var sessionNotesMcp = require("./session-notes-mcp-server");
2
2
  var lifecycle = require("./notes-lifecycle");
3
+ var driverEligibility = require("./session-driver-eligibility");
3
4
 
4
5
  var MAX_NOTE_TEXT_CHARS = 20000;
5
6
  var MAX_ACTIVE_NOTES = 20;
@@ -172,13 +173,19 @@ function attachSessionNotes(ctx) {
172
173
  return toolResult(publicNote(created));
173
174
  }
174
175
 
175
- // A session may only act on notes it created. Ownership is checked against the
176
- // note's recorded origin and the server-bound caller, never against anything
177
- // in the arguments.
176
+ // Cross-session closure requires a live, authorized Project Driver.
177
+ // Reopening retains the existing creator-only permission.
178
178
  function ownedNote(args, caller, verb) {
179
+ var authorized = typeof ctx.canUseSession === "function" && ctx.canUseSession(caller);
180
+ if (ctx.canUseSession && !authorized) {
181
+ return { error: "this session no longer has access to project notes" };
182
+ }
179
183
  var note = findNote(nm.list() || [], args && args.id);
180
184
  if (!note) return { error: "note not found: " + ((args && args.id) || "unknown") };
181
- if (!note.origin || note.origin.sessionId !== caller.localId) {
185
+ var driverCanClose = verb === "close" && !ctx.isMate &&
186
+ driverEligibility.isEligibleDriverSession(caller) &&
187
+ authorized;
188
+ if (!driverCanClose && (!note.origin || note.origin.sessionId !== caller.localId)) {
182
189
  return { error: "this session can only " + verb + " notes it created" };
183
190
  }
184
191
  return { note: note };
@@ -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;
@@ -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;
@@ -981,6 +985,8 @@ function attachSessions(ctx) {
981
985
  if (msg.id && sm.sessions.has(msg.id) && msg.title) {
982
986
  var s = sm.sessions.get(msg.id);
983
987
  s.title = String(msg.title).substring(0, 100);
988
+ s.titleProvisional = false;
989
+ s.titleAutoGenerated = false;
984
990
  s.titleManuallySet = true;
985
991
  sm.saveSessionFile(s);
986
992
  sm.notifySessionRenamed(s.localId);
@@ -1679,6 +1685,7 @@ function attachSessions(ctx) {
1679
1685
  return true;
1680
1686
  }
1681
1687
 
1688
+ if (decision === "allow_always" && pending.suppressAlwaysAllowRule) decision = "allow";
1682
1689
  if (decision === "allow" || decision === "allow_always") {
1683
1690
  if (decision === "allow_always") {
1684
1691
  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;
@@ -303,22 +346,42 @@ function attachUserMessage(ctx) {
303
346
  // --- Schedule message for after rate limit resets ---
304
347
  if (msg.type === "schedule_message") {
305
348
  var schedSession = getSessionForWs(ws);
306
- if (!schedSession || !msg.text || !msg.resetsAt) return true;
307
- scheduleMessage(schedSession, msg.text, msg.resetsAt, ws._clayUser || null, ws);
349
+ var transportAccountId = (ws && ws._clayUser && ws._clayUser.id) ? String(ws._clayUser.id) : "default";
350
+ if (!schedSession || msg.projectSlug !== slug || String(msg.sessionId) !== String(schedSession && schedSession.localId) ||
351
+ (msg.accountId || "default") !== transportAccountId) {
352
+ if (msg.requestId) sendTo(ws, { type: "schedule_message_result", requestId: msg.requestId,
353
+ projectSlug: msg.projectSlug || slug, sessionId: msg.sessionId == null ? null : msg.sessionId,
354
+ accountId: transportAccountId, ok: false, error: "The scheduling session or account is stale.", scheduledMessage: null });
355
+ return true;
356
+ }
357
+ scheduleMessage(schedSession, msg.text || "", msg.resetsAt, ws._clayUser || null, ws,
358
+ { images: msg.images, pastes: msg.pastes }, msg.requestId || null, transportAccountId);
308
359
  return true;
309
360
  }
310
361
 
311
362
  if (msg.type === "cancel_scheduled_message") {
312
363
  var cancelSession = getSessionForWs(ws);
313
364
  if (!cancelSession) return true;
314
- cancelScheduledMessage(cancelSession, ws._clayUser || null, ws, { jobId: msg.jobId, revision: msg.revision });
365
+ if (msg.projectSlug && msg.projectSlug !== slug) return true;
366
+ if (msg.sessionId != null && String(msg.sessionId) !== String(cancelSession.localId)) return true;
367
+ cancelScheduledMessage(cancelSession, ws._clayUser || null, ws, { jobId: msg.jobId, revision: msg.revision, queueRevision: msg.queueRevision, requestId: msg.requestId });
315
368
  return true;
316
369
  }
317
370
 
318
371
  if (msg.type === "send_scheduled_now") {
319
372
  var nowSession = getSessionForWs(ws);
320
- if (!nowSession || !nowSession.scheduledMessage) return true;
321
- sendScheduledMessageNow(nowSession, ws._clayUser || null, ws, { jobId: msg.jobId, revision: msg.revision });
373
+ var requestedContext = { projectSlug: slug, sessionId: nowSession ? nowSession.localId : msg.sessionId, revision: pendingMessageQueue ? pendingMessageQueue.getRevision() : 0, items: [], paused: false };
374
+ var controlResult;
375
+ if (!nowSession) controlResult = Object.assign({}, requestedContext, { ok: false, error: "The session is no longer available." });
376
+ else if (msg.projectSlug && msg.projectSlug !== slug) controlResult = Object.assign({}, requestedContext, { ok: false, error: "Project changed" });
377
+ else if (msg.sessionId != null && String(msg.sessionId) !== String(nowSession.localId)) controlResult = Object.assign({}, requestedContext, { ok: false, error: "Session changed" });
378
+ else if (!nowSession.scheduledMessage) controlResult = Object.assign({}, requestedContext, { ok: false, error: "The scheduled message is no longer available." });
379
+ else {
380
+ var sentNow = sendScheduledMessageNow(nowSession, ws._clayUser || null, ws, { jobId: msg.jobId, revision: msg.revision, queueRevision: msg.queueRevision, requestId: msg.requestId });
381
+ controlResult = pendingMessageQueue ? pendingMessageQueue.hydrate(nowSession, ws._clayUser || null) : { ok: sentNow === true, projectSlug: slug, sessionId: nowSession.localId, revision: 0, items: [], paused: false };
382
+ if (sentNow !== true) controlResult = Object.assign({}, controlResult, { ok: false, error: "Could not send the scheduled message." });
383
+ }
384
+ if (msg.requestId) sendTo(ws, { type: "pending_message_result", requestId: msg.requestId, result: controlResult });
322
385
  return true;
323
386
  }
324
387
 
@@ -459,6 +522,10 @@ function attachUserMessage(ctx) {
459
522
  function recordSessionTitle() {
460
523
  if (!session.title) {
461
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;
462
529
  sm.saveSessionFile(session);
463
530
  sm.broadcastSessionList();
464
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,
@@ -634,6 +645,8 @@ function createProjectContext(opts) {
634
645
  var access = opts.getProjectAccess ? opts.getProjectAccess() : { visibility: "public", ownerId: projectOwnerId || null };
635
646
  return !usersModule.isMultiUser() || !!(freshActor && (!opts.canAccessProjectSlug || opts.canAccessProjectSlug(freshActor.id, slug)) && usersModule.canAccessSession(freshActor.id, session, access));
636
647
  },
648
+ onItemCancelled: function (item) { return _scheduledMessages && _scheduledMessages.onQueueCancelled(item); },
649
+ onItemConsumed: function (item) { return _scheduledMessages && _scheduledMessages.onQueueConsumed(item); },
637
650
  });
638
651
 
639
652
  var sdk = null;
@@ -715,6 +728,15 @@ function createProjectContext(opts) {
715
728
  var nm = createNotesManager({ cwd: cwd, send: send, sendTo: sendTo });
716
729
  var _sessionNotes = attachSessionNotes({
717
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
+ },
718
740
  send: send,
719
741
  isMate: isMate,
720
742
  broadcastWritten: function (message) {
@@ -1215,6 +1237,7 @@ function createProjectContext(opts) {
1215
1237
  clayPort: serverPort,
1216
1238
  clayTls: serverTls,
1217
1239
  clayAuthToken: serverAuthToken,
1240
+ clayBridgeToken: mcpBridgeToken,
1218
1241
  getWorkerPermissionRouter: function () {
1219
1242
  return isMate ? null : _sessionPair.workerPermission;
1220
1243
  },
@@ -1360,6 +1383,8 @@ function createProjectContext(opts) {
1360
1383
  isDriverOperatedSession: function (session) {
1361
1384
  return !isMate && _sessionPair.workerPermission.isDriverOperated(session);
1362
1385
  },
1386
+ pendingMessageQueue: _pendingMessageQueue,
1387
+ consumePendingMessage: function (session) { return _userMessage && _userMessage.consumePendingMessage(session); },
1363
1388
  });
1364
1389
 
1365
1390
  // --- Loop engine (delegated to project-loop.js) ---
@@ -1409,6 +1434,9 @@ function createProjectContext(opts) {
1409
1434
  canExecuteScheduledTask: function (record) {
1410
1435
  return !!(_scheduledTaskExecution && _scheduledTaskExecution.runnable(record).ok);
1411
1436
  },
1437
+ onScheduledRunTerminal: function (record, run) {
1438
+ if (_scheduledTaskExecution) _scheduledTaskExecution.recordTerminal(record, run);
1439
+ },
1412
1440
  onProcessingChanged: onProcessingChanged,
1413
1441
  hydrateImageRefs: hydrateImageRefs,
1414
1442
  });
@@ -1424,6 +1452,7 @@ function createProjectContext(opts) {
1424
1452
  getSdk: function () { return sdk; }, getLinuxUserForSession: getLinuxUserForSession,
1425
1453
  requiresLinuxUser: function () { return osUsers; },
1426
1454
  onProcessingChanged: onProcessingChanged,
1455
+ recordResult: function (result) { return _projectLogs.recordScheduledResult(result); },
1427
1456
  authorize: function (record) {
1428
1457
  return authorizeStoredOwner(record, {
1429
1458
  usersModule: usersModule, osUsers: osUsers,
@@ -1433,6 +1462,7 @@ function createProjectContext(opts) {
1433
1462
  });
1434
1463
  },
1435
1464
  });
1465
+ setTimeout(function () { _scheduledTaskExecution.syncTerminalResults(); }, 0);
1436
1466
 
1437
1467
  _scheduledTasks = attachScheduledTasks({
1438
1468
  cwd: cwd,
@@ -1544,8 +1574,8 @@ function createProjectContext(opts) {
1544
1574
  }
1545
1575
 
1546
1576
  // --- Schedule / cancel a message (used by WS handler and auto-continue) ---
1547
- function scheduleMessage(session, text, resetsAt, actorUser, targetWs) {
1548
- return _scheduledMessages ? _scheduledMessages.schedule(session, text, resetsAt, actorUser, targetWs) : null;
1577
+ function scheduleMessage(session, text, resetsAt, actorUser, targetWs, attachments, requestId, transportAccountId) {
1578
+ return _scheduledMessages ? _scheduledMessages.schedule(session, text, resetsAt, actorUser, targetWs, attachments, requestId, transportAccountId) : null;
1549
1579
  }
1550
1580
 
1551
1581
  function cancelScheduledMessage(session, actorUser, targetWs, correlation) {
@@ -1579,8 +1609,8 @@ function createProjectContext(opts) {
1579
1609
  }
1580
1610
 
1581
1611
  // --- DM messages (delegated to server-level handler) ---
1582
- 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") {
1583
- if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg);
1612
+ 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") {
1613
+ if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
1584
1614
  return;
1585
1615
  }
1586
1616
  if (msg.type === "issue_reference_resolve" || msg.type === "home_clay_ask" || msg.type === "home_clay_session_resolve" || msg.type === "home_clay_log_resolve") {
@@ -1859,6 +1889,12 @@ function createProjectContext(opts) {
1859
1889
  }
1860
1890
 
1861
1891
  // --- Sessions/config/project handler (delegated to project-sessions.js) ---
1892
+ var _sessionSkillCatalog = createSessionSkillCatalog({
1893
+ cwd: cwd,
1894
+ osUsers: osUsers,
1895
+ getLinuxUser: getLinuxUserForSession,
1896
+ });
1897
+ sm.setSessionHydrator(_sessionSkillCatalog.hydrate);
1862
1898
  var _sessions = attachSessions({
1863
1899
  cwd: cwd,
1864
1900
  slug: slug,
@@ -1898,6 +1934,7 @@ function createProjectContext(opts) {
1898
1934
  getLinuxUserForSession: getLinuxUserForSession,
1899
1935
  ensureProjectAccessForSession: ensureProjectAccessForSession,
1900
1936
  getOsUserInfoForWs: getOsUserInfoForWs,
1937
+ hydrateSkillCatalog: _sessionSkillCatalog.hydrate,
1901
1938
  hydrateImageRefs: hydrateImageRefs,
1902
1939
  onProcessingChanged: onProcessingChanged,
1903
1940
  broadcastPresence: broadcastPresence,
@@ -1927,6 +1964,7 @@ function createProjectContext(opts) {
1927
1964
  saveContextSources: saveContextSources,
1928
1965
  _email: _email,
1929
1966
  _notifications: _notifications,
1967
+ _projectLogs: _projectLogs,
1930
1968
  });
1931
1969
 
1932
1970
  var _models = attachModels({
@@ -1996,6 +2034,7 @@ function createProjectContext(opts) {
1996
2034
  beginHumanPairTurn: function (session) { return _sessionPair.beginHumanTurn(session); },
1997
2035
  authorizePendingDispatch: function (session, actor, item) {
1998
2036
  if (!session || sm.sessions.get(session.localId) !== session || !item || item.projectSlug !== slug) return false;
2037
+ if (_scheduledMessages && !_scheduledMessages.authorizePending(item, session)) return false;
1999
2038
  if (!isMate && _sessionPair.workerPermission && _sessionPair.workerPermission.isDriverOperated(session)) return false;
2000
2039
  if (!usersModule.isMultiUser()) return item.actorId === "default" || !!(actor && String(actor.id) === item.actorId);
2001
2040
  var freshActor = actor && actor.id && usersModule.findUserById(actor.id);
@@ -2025,6 +2064,8 @@ function createProjectContext(opts) {
2025
2064
  getSessionForWs: getSessionForWs,
2026
2065
  getLinuxUserForSession: getLinuxUserForSession,
2027
2066
  ensureProjectAccessForSession: ensureProjectAccessForSession,
2067
+ getProjectAccess: function () { return opts.getProjectAccess ? opts.getProjectAccess() : { visibility: "public", ownerId: projectOwnerId || null }; },
2068
+ canAccessProjectSlug: opts.canAccessProjectSlug || function () { return !usersModule.isMultiUser(); },
2028
2069
  getOsUserInfoForWs: getOsUserInfoForWs,
2029
2070
  hydrateImageRefs: hydrateImageRefs,
2030
2071
  saveImageFile: saveImageFile,
@@ -2107,6 +2148,12 @@ function createProjectContext(opts) {
2107
2148
  function getMcpBridgeHandler(sessionId, sessionOnly, queryGeneration) {
2108
2149
  var boundSession = Number.isInteger(sessionId) ? sm.sessions.get(sessionId) : null;
2109
2150
  var queryToolBridge = createSessionQueryToolBridge({ session: boundSession, sdk: sdk });
2151
+ function sessionQueryValid() {
2152
+ return !!boundSession
2153
+ && Number.isInteger(queryGeneration)
2154
+ && sm.sessions.get(boundSession.localId) === boundSession
2155
+ && Number(boundSession._sdkQueryGeneration || 0) === queryGeneration;
2156
+ }
2110
2157
  // Build set of local MCP server names to exclude (Codex handles these natively)
2111
2158
  var localMcpNames = {};
2112
2159
  try {
@@ -2121,6 +2168,7 @@ function createProjectContext(opts) {
2121
2168
  return {
2122
2169
  listTools: function () {
2123
2170
  var tools = [];
2171
+ if (sessionOnly && !sessionQueryValid()) return Promise.resolve(tools);
2124
2172
  var toJSONSchema;
2125
2173
  var zod;
2126
2174
  try { zod = require("zod"); toJSONSchema = zod.toJSONSchema; } catch (e) { /* fallback */ }
@@ -2246,6 +2294,9 @@ function createProjectContext(opts) {
2246
2294
  return Promise.resolve(tools);
2247
2295
  },
2248
2296
  callTool: function (serverName, toolName, args) {
2297
+ if (sessionOnly && !sessionQueryValid()) {
2298
+ return Promise.reject(new Error("Session tool unavailable or older query: " + serverName + "/" + toolName));
2299
+ }
2249
2300
  var queryCall = queryToolBridge.callTool(queryGeneration, serverName, toolName, args);
2250
2301
  if (queryCall) return queryCall;
2251
2302
  if (boundSession && serverName === "clay-sessions") {
@@ -2404,6 +2455,7 @@ function createProjectContext(opts) {
2404
2455
  getLatestVersion: function () { return latestVersion; },
2405
2456
  getTitle: function () { return title; },
2406
2457
  getProject: function () { return project; },
2458
+ hydrateSkillCatalog: _sessionSkillCatalog.hydrate,
2407
2459
  // Exposed so the first websocket connection can lazily warm up the
2408
2460
  // adapters for this project (see project-connection handleConnection).
2409
2461
  warmup: function (linuxUser) {
@@ -2628,6 +2680,9 @@ function createProjectContext(opts) {
2628
2680
  handleDisconnection: handleDisconnection,
2629
2681
  handleHTTP: handleHTTP,
2630
2682
  getMcpBridgeHandler: getMcpBridgeHandler,
2683
+ isMcpBridgeAuthenticated: function (req) {
2684
+ return !!(req && req.headers && req.headers["x-clay-bridge-token"] && req.headers["x-clay-bridge-token"] === mcpBridgeToken);
2685
+ },
2631
2686
  getStatus: getStatus,
2632
2687
  getSessionManager: function () { return sm; },
2633
2688
  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';
@@ -68,9 +68,10 @@ import { initHomeSidebar } from './modules/home-sidebar.js';
68
68
  import { openHomeMateAction } from './modules/home-mate-chat.js';
69
69
  import { initHomeSurfaceBoot } from './modules/home-surface-boot.js';
70
70
  import { rememberHomePrimarySurface } from './modules/home-surface.js';
71
- import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, addScheduledMessageBubble as _rlAddScheduledMessageBubble, removeScheduledMessageBubble as _rlRemoveScheduledMessageBubble, handleFastModeState as _rlHandleFastModeState, getScheduledMsgEl, resetRateLimitState } from './modules/app-rate-limit.js';
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,7 @@ 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: "" },
421
435
 
422
436
  // dm
423
437
  dmTargetUser: null,
@@ -434,6 +448,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
434
448
  pendingShellCommandId: null,
435
449
  mateProjectSlug: null,
436
450
  myUserId: null,
451
+ 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: "" },
452
+ contextViewOverride: null,
437
453
  isMultiUserMode: false,
438
454
  dmUnread: {},
439
455
  dmRemovedUsers: {},
@@ -469,6 +485,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
469
485
  lastVendor: "",
470
486
  // Static adapter metadata sent before any vendor is initialized.
471
487
  vendorInfo: {},
488
+ // Rate-limit data is projected only for the displayed session/vendor.
489
+ rateLimitState: {},
472
490
  // How Claude sessions open: "gui" (default) or "tui". The server sends
473
491
  // claude_open_mode_changed on connect; this seeds it beforehand so the
474
492
  // new-session menu doesn't flash the TUI-only entry.
@@ -544,7 +562,11 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
544
562
  newSessionBtn: newSessionBtn,
545
563
  headerTitleEl: headerTitleEl,
546
564
  showConfirm: showConfirm,
547
- onFilesTabOpen: function () { loadRootDirectory(); },
565
+ onFilesTabOpen: function () {
566
+ reopenFileViewer();
567
+ loadRootDirectory();
568
+ if (isSchedulerOpen()) closeScheduler();
569
+ },
548
570
  requestKnowledgeList: function () { requestKnowledgeList(); },
549
571
  switchProject: function (slug) { switchProject(slug); },
550
572
  openTerminal: function () { openTerminal(); },
@@ -844,8 +866,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
844
866
  // --- Rate Limit / Scheduled Messages / Fast Mode (delegated to app-rate-limit.js) ---
845
867
  function handleRateLimitEvent(msg) { _rlHandleRateLimitEvent(msg); }
846
868
  function updateRateLimitUsage(msg) { _rlUpdateRateLimitUsage(msg); }
847
- function addScheduledMessageBubble(text, resetsAt) { _rlAddScheduledMessageBubble(text, resetsAt); }
848
- function removeScheduledMessageBubble() { _rlRemoveScheduledMessageBubble(); }
849
869
  function handleFastModeState(state) { _rlHandleFastModeState(state); }
850
870
 
851
871
  // --- Prompt suggestion chips (delegated to app-rendering.js) ---
@@ -1011,7 +1031,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1011
1031
  // the project Mate DM preference therefore hides only Mate shortcuts.
1012
1032
  if (document.body) document.body.classList.add('is-multi-user');
1013
1033
  }
1014
- if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); }
1034
+ if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); requestDefaultVendor(); }
1015
1035
  if (d.permissions) store.set({ permissions: d.permissions });
1016
1036
  if (d.mustChangePin) showForceChangePinOverlay();
1017
1037
  // Single-user mode: clear user strip skeletons immediately (no presence message will arrive)
@@ -1190,9 +1210,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1190
1210
  // The browser refreshes from note messages without the canvas module having
1191
1211
  // to import it, which would be a cycle.
1192
1212
  setBrowserRefresh(function () { if (isNotesBrowserOpen()) renderNotesBrowser(); });
1193
- // Opening the browser claims the single right workbench slot. Registered here
1194
- // so the browser module never imports the other tools.
1195
- registerExclusiveClosers([closeIssues, closeProjectLogs, closeScheduledTasks, closeFileViewer, closeTerminal]);
1196
1213
 
1197
1214
  // --- Sticky Notes sidebar button (create new note) ---
1198
1215
  var stickyNotesSidebarBtn = $("sticky-notes-sidebar-btn");
@@ -1231,10 +1248,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1231
1248
  }
1232
1249
 
1233
1250
  // Close the notes browser / scheduler panel when switching to other sidebar panels
1234
- var fileBrowserBtn = $("file-browser-btn");
1235
1251
  var gitPlacardMoreBtn = $("git-placard-more");
1236
1252
  var terminalSidebarBtn = $("terminal-sidebar-btn");
1237
- if (fileBrowserBtn) fileBrowserBtn.addEventListener("click", function () { closeIssues(); closeProjectLogs(); closeScheduledTasks(); if (isNotesBrowserOpen()) closeNotesBrowser(); if (isSchedulerOpen()) closeScheduler(); });
1238
1253
  if (gitPlacardMoreBtn) gitPlacardMoreBtn.addEventListener("click", function () { closeIssues(); closeProjectLogs(); closeScheduledTasks(); if (isNotesBrowserOpen()) closeNotesBrowser(); if (isSchedulerOpen()) closeScheduler(); });
1239
1254
  if (terminalSidebarBtn) terminalSidebarBtn.addEventListener("click", function () { closeIssues(); closeProjectLogs(); closeScheduledTasks(); if (isNotesBrowserOpen()) closeNotesBrowser(); if (isSchedulerOpen()) closeScheduler(); });
1240
1255
 
@@ -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,
@@ -513,6 +513,7 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
513
513
  @keyframes searchClaySpin { to { transform: rotate(360deg); } }
514
514
 
515
515
  .clayos-session-link,
516
+ .clayos-issue-link,
516
517
  .clayos-log-link {
517
518
  display: inline-flex;
518
519
  align-items: center;
@@ -531,6 +532,7 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
531
532
  cursor: pointer;
532
533
  }
533
534
  .clayos-session-link::before,
535
+ .clayos-issue-link::before,
534
536
  .clayos-log-link::before {
535
537
  content: "\2197";
536
538
  display: grid;
@@ -542,20 +544,28 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
542
544
  color: var(--accent);
543
545
  font-size: 12px;
544
546
  }
547
+ .clayos-issue-link::before,
545
548
  .clayos-log-link::before { content: "\2261"; }
546
549
  .clayos-session-link > span,
550
+ .clayos-issue-link > span,
547
551
  .clayos-log-link > span { font-weight: 620; }
548
552
  .clayos-session-link > small,
553
+ .clayos-issue-link > small,
549
554
  .clayos-log-link > small { color: var(--text-muted); font-size: 10px; }
550
555
  .clayos-session-link:hover,
556
+ .clayos-issue-link:hover,
551
557
  .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
558
  .clayos-session-link:focus-visible,
559
+ .clayos-issue-link:focus-visible,
553
560
  .clayos-log-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
554
561
  .clayos-session-link.is-loading,
562
+ .clayos-issue-link.is-loading,
555
563
  .clayos-log-link.is-loading { opacity: 0.62; cursor: progress; }
556
564
  .clayos-session-link.is-loading::before,
565
+ .clayos-issue-link.is-loading::before,
557
566
  .clayos-log-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
558
567
  .clayos-session-link.is-error,
568
+ .clayos-issue-link.is-error,
559
569
  .clayos-log-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
560
570
 
561
571
  .search-clay-pending {
@@ -913,3 +923,13 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
913
923
  font-family: inherit;
914
924
  color: var(--text-muted);
915
925
  }
926
+
927
+ .clayos-issue-link { max-width: min(100%, 30em); box-sizing: border-box; }
928
+ .clayos-issue-link::before { content: "\25CE"; flex: 0 0 17px; }
929
+ .clayos-issue-link[data-status="in_progress"]::before { color: #bd923f; }
930
+ .clayos-issue-link[data-status="resolved"]::before { content: "\2713"; color: var(--brand-green); }
931
+ .clayos-issue-link[data-status="closed"]::before { content: "\00D7"; color: var(--text-dimmer); }
932
+ .clayos-issue-link > span { flex-shrink: 0; }
933
+ .clayos-issue-link > small { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
934
+ .clayos-log-link { max-width: 100%; box-sizing: border-box; }
935
+ .clayos-log-link > small { min-width: 0; overflow-wrap: anywhere; line-height: 1.35; }