clay-server 5.2.0-beta.9 → 5.2.1-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 (109) hide show
  1. package/README.md +0 -4
  2. package/lib/clay-product-control-tools.js +1 -0
  3. package/lib/clay-product-control.js +14 -11
  4. package/lib/clay-vendor-usage.js +44 -0
  5. package/lib/github-star-count.js +147 -0
  6. package/lib/notes-checklist.js +43 -1
  7. package/lib/notes.js +2 -1
  8. package/lib/project-session-notes.js +33 -8
  9. package/lib/project-sessions.js +63 -24
  10. package/lib/project-start-protocol.js +29 -0
  11. package/lib/project-vendor-login.js +2 -0
  12. package/lib/project.js +5 -1
  13. package/lib/public/app.js +30 -18
  14. package/lib/public/css/command-palette.css +12 -2
  15. package/lib/public/css/contextual-clay.css +1 -1
  16. package/lib/public/css/filebrowser.css +4 -5
  17. package/lib/public/css/mate-loading-scene.css +21 -0
  18. package/lib/public/css/messages.css +30 -11
  19. package/lib/public/css/overlays.css +67 -256
  20. package/lib/public/css/project-mate-creation.css +5 -6
  21. package/lib/public/css/project-start.css +108 -0
  22. package/lib/public/css/projects-hub.css +53 -14
  23. package/lib/public/css/right-workbench.css +28 -0
  24. package/lib/public/css/settings-shell.css +103 -0
  25. package/lib/public/css/sidebar.css +7 -0
  26. package/lib/public/css/title-bar.css +99 -0
  27. package/lib/public/index.html +54 -66
  28. package/lib/public/modules/account-transport-settings.js +8 -2
  29. package/lib/public/modules/admin-settings-email.js +7 -2
  30. package/lib/public/modules/admin-settings-invitations.js +2 -2
  31. package/lib/public/modules/admin-settings-projects.js +2 -2
  32. package/lib/public/modules/admin-settings-ui.js +20 -4
  33. package/lib/public/modules/admin-settings-users.js +4 -4
  34. package/lib/public/modules/app-home-hub.js +2 -1
  35. package/lib/public/modules/app-messages.js +10 -2
  36. package/lib/public/modules/app-projects.js +1 -407
  37. package/lib/public/modules/app-rate-limit.js +12 -1
  38. package/lib/public/modules/clay-tool-presentation.js +1 -0
  39. package/lib/public/modules/contextual-clay.js +3 -1
  40. package/lib/public/modules/github-star.js +66 -0
  41. package/lib/public/modules/home-surface-boot.js +0 -1
  42. package/lib/public/modules/mate-loading-scene.js +17 -0
  43. package/lib/public/modules/pair-result-status.js +89 -39
  44. package/lib/public/modules/pane-appearance-bridge.js +59 -0
  45. package/lib/public/modules/profile.js +11 -3
  46. package/lib/public/modules/project-mate-creation-workspace.js +13 -4
  47. package/lib/public/modules/project-session-visibility-settings.js +19 -16
  48. package/lib/public/modules/project-settings-environment.js +12 -1
  49. package/lib/public/modules/project-settings.js +119 -129
  50. package/lib/public/modules/project-start-dialog.js +130 -0
  51. package/lib/public/modules/project-start-folder-state.js +78 -0
  52. package/lib/public/modules/project-start-folder-view.js +190 -0
  53. package/lib/public/modules/project-start-forms.js +66 -0
  54. package/lib/public/modules/project-start-path.js +60 -0
  55. package/lib/public/modules/project-start-state.js +54 -0
  56. package/lib/public/modules/project-start-submit.js +67 -0
  57. package/lib/public/modules/projects-hub-view.js +32 -44
  58. package/lib/public/modules/projects-hub.js +39 -28
  59. package/lib/public/modules/server-settings.js +36 -91
  60. package/lib/public/modules/settings-project-target.js +56 -0
  61. package/lib/public/modules/settings-select.js +1 -1
  62. package/lib/public/modules/settings-shell-registry.js +88 -0
  63. package/lib/public/modules/settings-shell-view.js +104 -0
  64. package/lib/public/modules/settings-shell.js +342 -0
  65. package/lib/public/modules/sidebar-mobile.js +8 -30
  66. package/lib/public/modules/sidebar-projects.js +2 -1
  67. package/lib/public/modules/theme.js +31 -3
  68. package/lib/public/modules/topbar-workbench-controls.js +102 -0
  69. package/lib/public/modules/user-settings-integrations.js +2 -1
  70. package/lib/public/modules/user-settings-layout.js +12 -34
  71. package/lib/public/modules/user-settings-profile.js +1 -1
  72. package/lib/public/modules/user-settings.js +18 -85
  73. package/lib/public/modules/vendor-usage-format.js +93 -0
  74. package/lib/public/modules/vendor-usage-list.js +45 -0
  75. package/lib/public/modules/vendor-usage-model.js +215 -0
  76. package/lib/public/modules/vendor-usage-overview.js +323 -0
  77. package/lib/public/style.css +13 -10
  78. package/lib/public/welcome-art/conversation-desk.png +0 -0
  79. package/lib/public/welcome-art/mate-clay.png +0 -0
  80. package/lib/public/welcome-art/project-clay.png +0 -0
  81. package/lib/sdk-bridge.js +6 -1
  82. package/lib/sdk-message-processor.js +9 -1
  83. package/lib/sdk-quota-snapshot.js +20 -2
  84. package/lib/server-github-stars.js +39 -0
  85. package/lib/server-global-ws.js +21 -17
  86. package/lib/server.js +11 -0
  87. package/lib/session-notes-mcp-server.js +26 -4
  88. package/lib/session-notes-writes.js +88 -0
  89. package/lib/session-visibility.js +20 -8
  90. package/lib/sessions.js +12 -2
  91. package/lib/vendor-usage-acp-readers.js +97 -0
  92. package/lib/vendor-usage-capabilities.js +88 -0
  93. package/lib/vendor-usage-http-readers.js +214 -0
  94. package/lib/vendor-usage-metrics.js +47 -0
  95. package/lib/vendor-usage-native-readers.js +106 -0
  96. package/lib/vendor-usage-overview.js +406 -0
  97. package/lib/vendor-usage-service.js +281 -0
  98. package/lib/ws-schema.js +4 -2
  99. package/lib/yoke/adapters/claude.js +12 -0
  100. package/lib/yoke/adapters/codex-query-contract.js +1 -0
  101. package/lib/yoke/adapters/codex.js +45 -12
  102. package/lib/yoke/claude-hook-installer.js +1 -0
  103. package/lib/yoke/codex-dynamic-tool-names.js +80 -0
  104. package/lib/yoke/codex-model-catalog.js +37 -3
  105. package/lib/yoke/muse-services.js +16 -3
  106. package/lib/yoke/providers/claude-model-descriptions.js +22 -2
  107. package/lib/yoke/vendor-registry.js +6 -2
  108. package/package.json +2 -1
  109. package/lib/public/modules/server-settings-navigation.js +0 -39
@@ -0,0 +1,323 @@
1
+ // vendor-usage-overview.js - Top-bar usage overview for every tracked vendor.
2
+ //
3
+ // This is a read-only projection of the owner-scoped `vendor_usage_overview`
4
+ // message. It sits beside the existing active-session usage pill and never
5
+ // touches rejection, warning or scheduling state in app-rate-limit.js.
6
+
7
+ import { store } from './store.js';
8
+ import { getWs } from './ws-ref.js';
9
+ import { iconHtml, refreshIcons } from './icons.js';
10
+ import { overviewVendors, allVendors } from './vendor-usage-model.js';
11
+ import { fillListPopover } from './vendor-usage-list.js';
12
+
13
+ var LIST_KEY = "__all";
14
+
15
+ var groupEl = null;
16
+ var records = {};
17
+ var tickTimer = null;
18
+ var outsideHandler = null;
19
+ var keyHandler = null;
20
+ var blurHandler = null;
21
+ var requestCounter = 0;
22
+ var pendingRequestId = null;
23
+
24
+ // `refresh` asks the server to read account usage for vendors that support it;
25
+ // the server caches, so repeating it is cheap. Replies echo the request id.
26
+ function requestOverview(refresh) {
27
+ var ws = getWs();
28
+ if (!ws || ws.readyState !== 1) return;
29
+ pendingRequestId = "vu-" + (++requestCounter) + "-" + Date.now().toString(36);
30
+ var message = { type: "vendor_usage_get", slug: store.get('currentSlug') || undefined, requestId: pendingRequestId };
31
+ if (refresh) message.refresh = true;
32
+ ws.send(JSON.stringify(message));
33
+ }
34
+
35
+ // A reply is shown only for the project, user and request it was asked for.
36
+ // A delayed reply from before a project or account change is dropped.
37
+ export function handleVendorUsageOverview(msg) {
38
+ if (!msg || msg.error) return;
39
+ var slug = store.get('currentSlug');
40
+ if (slug && msg.slug !== slug) return;
41
+ if ((msg.userId || null) !== (store.get('myUserId') || null)) return;
42
+ if (msg.requestId !== undefined && msg.requestId !== pendingRequestId) return;
43
+ var vendors = Array.isArray(msg.vendors) ? msg.vendors : [];
44
+ store.set({ vendorUsageOverview: { slug: msg.slug, serverNowMs: msg.serverNowMs, receivedAtMs: Date.now(), vendors: vendors } });
45
+ }
46
+
47
+ function el(tag, className, text) {
48
+ var node = document.createElement(tag);
49
+ if (className) node.className = className;
50
+ if (text !== undefined) node.textContent = text;
51
+ return node;
52
+ }
53
+
54
+ function closePopover(restoreFocus) {
55
+ var open = store.get('vendorUsageOpen');
56
+ if (!open) return;
57
+ store.set({ vendorUsageOpen: null });
58
+ var record = records[open];
59
+ if (restoreFocus && record && typeof record.chip.focus === "function") record.chip.focus();
60
+ }
61
+
62
+ function sourceNote(view) {
63
+ var note = view.lastObservedText ? "Latest report " + view.lastObservedText : "Not observed yet in this project";
64
+ if (view.sourceSessionId !== null && view.sourceSessionId !== undefined) note += " from session #" + view.sourceSessionId;
65
+ note += ".";
66
+ if (view.otherSourceCount > 0) note += " " + view.otherSourceCount + " other session" + (view.otherSourceCount === 1 ? "" : "s") + " reported separately and are not combined.";
67
+ return note;
68
+ }
69
+
70
+ function ensurePopover(record, view) {
71
+ if (!record.pop) {
72
+ var pop = el("div", "vendor-usage-popover");
73
+ pop.setAttribute("role", "dialog");
74
+ record.title = el("div", "vendor-usage-popover-title");
75
+ record.rows = el("div", "vendor-usage-rows");
76
+ record.notes = el("div", "vendor-usage-notes");
77
+ pop.appendChild(record.title);
78
+ pop.appendChild(record.rows);
79
+ pop.appendChild(record.notes);
80
+ // The link is created once and only updated, so keyboard focus on it
81
+ // survives refreshes.
82
+ record.link = el("a", "vendor-usage-link");
83
+ record.link.target = "_blank";
84
+ record.link.rel = "noopener";
85
+ record.linkLabel = el("span", "", "Open usage dashboard");
86
+ record.link.appendChild(record.linkLabel);
87
+ record.link.insertAdjacentHTML("beforeend", iconHtml("external-link"));
88
+ pop.appendChild(record.link);
89
+ record.item.appendChild(pop);
90
+ record.pop = pop;
91
+ }
92
+ record.pop.setAttribute("aria-label", view.name + " usage");
93
+ record.title.textContent = view.name;
94
+ record.rows.textContent = "";
95
+ view.rows.forEach(function (row) {
96
+ var line = el("div", "vendor-usage-row" + (row.warning ? " warning" : "") + (row.stale ? " stale" : ""));
97
+ line.appendChild(el("span", "vendor-usage-row-label", row.label));
98
+ var parts = [];
99
+ if (row.source === "account") { if (row.value) parts.push(row.value); } else if (row.percentText) parts.push(row.percentText);
100
+ if (row.resetText) parts.push("resets in " + row.resetText);
101
+ var value = el("span", "vendor-usage-row-value");
102
+ value.appendChild(el("span", "", parts.length ? parts.join(" \u00b7 ") : "Limit window"));
103
+ var origin = row.source === "account" ? "account, " : (row.origin === "history" ? "session record, " : "session, ");
104
+ value.appendChild(el("span", "vendor-usage-row-age", origin + row.observedText + (row.stale ? " (stale)" : "") + (row.origin === "history" ? " (earlier session record)" : "")));
105
+ line.appendChild(value);
106
+ record.rows.appendChild(line);
107
+ });
108
+ record.notes.textContent = "";
109
+ if (view.status) record.notes.appendChild(el("div", "vendor-usage-note", view.status));
110
+ if (view.accountCount > 0 && view.accountSource) record.notes.appendChild(el("div", "vendor-usage-note", "Account figures: " + view.accountSource + ". Units are the vendor's own and are not converted."));
111
+ if (view.observedCount > 0) {
112
+ record.notes.appendChild(el("div", "vendor-usage-note", sourceNote(view)));
113
+ record.notes.appendChild(el("div", "vendor-usage-note", "Session rows come from your own sessions in this project; another account or session may differ."));
114
+ }
115
+ if (view.href) {
116
+ if (record.link.getAttribute("href") !== view.href) record.link.href = view.href;
117
+ record.link.title = view.linkTitle || "";
118
+ record.link.style.display = "";
119
+ } else {
120
+ record.link.style.display = "none";
121
+ }
122
+ }
123
+
124
+ function removePopover(record) {
125
+ if (record.pop) { record.pop.remove(); record.pop = null; }
126
+ }
127
+
128
+ // The popover is absolutely positioned under its chip, right-aligned; a measured
129
+ // shift keeps it inside the viewport.
130
+ function placePopover(pop) {
131
+ if (typeof pop.getBoundingClientRect !== "function") return;
132
+ pop.style.marginRight = "";
133
+ var bounds = pop.getBoundingClientRect();
134
+ var viewport = document.documentElement.clientWidth;
135
+ var shift = bounds.left < 8 ? 8 - bounds.left : Math.min(0, viewport - 8 - bounds.right);
136
+ if (shift) pop.style.marginRight = (-shift) + "px";
137
+ }
138
+
139
+ function shellWindow() { return typeof window !== "undefined" ? window : null; }
140
+
141
+ function bindDismissal(open) {
142
+ if (open && !outsideHandler) {
143
+ outsideHandler = function (event) {
144
+ if (groupEl && !groupEl.contains(event.target)) closePopover(false);
145
+ };
146
+ keyHandler = function (event) {
147
+ if (event.key === "Escape") { closePopover(true); }
148
+ };
149
+ // A click inside a split pane or contextual iframe never reaches this
150
+ // document; focus moving into the frame blurs the shell window instead.
151
+ blurHandler = function () {
152
+ if (document.activeElement && document.activeElement.tagName === "IFRAME") closePopover(false);
153
+ };
154
+ document.addEventListener("pointerdown", outsideHandler, true);
155
+ document.addEventListener("keydown", keyHandler, true);
156
+ if (shellWindow()) shellWindow().addEventListener("blur", blurHandler);
157
+ } else if (!open && outsideHandler) {
158
+ document.removeEventListener("pointerdown", outsideHandler, true);
159
+ document.removeEventListener("keydown", keyHandler, true);
160
+ if (blurHandler && shellWindow()) shellWindow().removeEventListener("blur", blurHandler);
161
+ outsideHandler = null;
162
+ keyHandler = null;
163
+ blurHandler = null;
164
+ }
165
+ }
166
+
167
+ function removeGroup() {
168
+ if (groupEl) { groupEl.remove(); groupEl = null; }
169
+ records = {};
170
+ bindDismissal(false);
171
+ if (tickTimer) { clearInterval(tickTimer); tickTimer = null; }
172
+ }
173
+
174
+ function createRecord(view) {
175
+ var item = el("span", "vendor-usage-item");
176
+ var chip = el("button", "top-bar-pill vendor-usage-chip");
177
+ chip.type = "button";
178
+ chip.setAttribute("data-vendor", view.vendor);
179
+ chip.setAttribute("aria-haspopup", "dialog");
180
+ var record = { item: item, chip: chip, icon: null, text: el("span", "vendor-usage-chip-text"), pop: null };
181
+ chip.appendChild(record.text);
182
+ chip.addEventListener("click", function () {
183
+ var opening = store.get('vendorUsageOpen') !== view.vendor;
184
+ store.set({ vendorUsageOpen: opening ? view.vendor : null });
185
+ if (opening) requestOverview(true);
186
+ });
187
+ item.appendChild(chip);
188
+ return record;
189
+ }
190
+
191
+ function updateChip(record, view, open) {
192
+ record.chip.className = "top-bar-pill vendor-usage-chip " + (view.warning ? "pill-warning" : "pill-dim") + (view.known ? "" : " unknown") + (view.stale ? " stale" : "");
193
+ record.chip.setAttribute("aria-expanded", open ? "true" : "false");
194
+ record.chip.setAttribute("aria-label", view.summary + ". Show details");
195
+ record.chip.title = view.summary;
196
+ if (view.icon) {
197
+ if (!record.icon) {
198
+ record.icon = el("img", "usage-check-vendor-icon");
199
+ record.icon.alt = "";
200
+ record.chip.insertBefore(record.icon, record.text);
201
+ }
202
+ if (record.icon.getAttribute("src") !== view.icon) record.icon.setAttribute("src", view.icon);
203
+ }
204
+ if (record.text.textContent !== view.text) record.text.textContent = view.text;
205
+ }
206
+
207
+ function updateListChip(record, listViews, open) {
208
+ var known = listViews.filter(function (view) { return view.known; }).length;
209
+ var summary = "Usage for " + listViews.length + " vendor" + (listViews.length === 1 ? "" : "s") + ", " + known + " with figures";
210
+ record.chip.className = "top-bar-pill vendor-usage-chip vendor-usage-list-chip pill-dim";
211
+ record.chip.setAttribute("aria-expanded", open ? "true" : "false");
212
+ record.chip.setAttribute("aria-label", summary + ". Show all vendors");
213
+ record.chip.title = summary;
214
+ if (!record.icon) {
215
+ record.icon = el("span", "vendor-usage-list-icon");
216
+ record.icon.insertAdjacentHTML("beforeend", iconHtml("gauge"));
217
+ record.chip.insertBefore(record.icon, record.text);
218
+ }
219
+ var text = known + "/" + listViews.length;
220
+ if (record.text.textContent !== text) record.text.textContent = text;
221
+ }
222
+
223
+ function ensureListPopover(record, listViews) {
224
+ if (!record.pop) {
225
+ var pop = el("div", "vendor-usage-popover vendor-usage-list-popover");
226
+ pop.setAttribute("role", "dialog");
227
+ pop.setAttribute("aria-label", "Usage by vendor");
228
+ record.title = el("div", "vendor-usage-popover-title", "Usage by vendor");
229
+ record.rows = el("div", "vendor-usage-rows");
230
+ pop.appendChild(record.title);
231
+ pop.appendChild(record.rows);
232
+ record.item.appendChild(pop);
233
+ record.pop = pop;
234
+ }
235
+ fillListPopover(record.rows, listViews, el);
236
+ }
237
+
238
+ // Updates stable elements in place. Chips, the popover and its link are never
239
+ // recreated while they stay relevant, so focus survives refreshes.
240
+ export function renderVendorUsageOverview() {
241
+ var actions = document.querySelector("#top-bar .top-bar-actions");
242
+ if (!actions) return;
243
+ var overview = store.get('vendorUsageOverview');
244
+ var info = store.get('vendorInfo');
245
+ var views = overviewVendors(info, overview, Date.now());
246
+ var listViews = allVendors(info, overview, Date.now());
247
+ if (listViews.length === 0) {
248
+ if (store.get('vendorUsageOpen')) store.set({ vendorUsageOpen: null });
249
+ removeGroup();
250
+ return;
251
+ }
252
+ var open = store.get('vendorUsageOpen');
253
+ if (open && open !== LIST_KEY && !views.some(function (view) { return view.vendor === open; })) {
254
+ open = null;
255
+ store.set({ vendorUsageOpen: null });
256
+ }
257
+ if (!groupEl) {
258
+ groupEl = el("div", "vendor-usage-overview");
259
+ groupEl.id = "vendor-usage-overview";
260
+ groupEl.setAttribute("role", "group");
261
+ groupEl.setAttribute("aria-label", "Vendor usage");
262
+ }
263
+ var anchor = document.getElementById("rate-limit-usage-link") || document.getElementById("skip-perms-pill");
264
+ if (groupEl.parentNode !== actions || groupEl.nextSibling !== anchor) actions.insertBefore(groupEl, anchor);
265
+ var present = {};
266
+ views.forEach(function (view, index) {
267
+ present[view.vendor] = true;
268
+ var record = records[view.vendor] || (records[view.vendor] = createRecord(view));
269
+ if (groupEl.children[index] !== record.item) groupEl.insertBefore(record.item, groupEl.children[index] || null);
270
+ updateChip(record, view, open === view.vendor);
271
+ if (open === view.vendor) {
272
+ var created = !record.pop;
273
+ ensurePopover(record, view);
274
+ placePopover(record.pop);
275
+ if (created) bindDismissal(true);
276
+ } else {
277
+ removePopover(record);
278
+ }
279
+ });
280
+ // The list entry is always last and reaches every installed vendor.
281
+ present[LIST_KEY] = true;
282
+ var listRecord = records[LIST_KEY] || (records[LIST_KEY] = createRecord({ vendor: LIST_KEY }));
283
+ if (groupEl.children[views.length] !== listRecord.item) groupEl.insertBefore(listRecord.item, groupEl.children[views.length] || null);
284
+ updateListChip(listRecord, listViews, open === LIST_KEY);
285
+ if (open === LIST_KEY) {
286
+ var listCreated = !listRecord.pop;
287
+ ensureListPopover(listRecord, listViews);
288
+ placePopover(listRecord.pop);
289
+ if (listCreated) bindDismissal(true);
290
+ } else {
291
+ removePopover(listRecord);
292
+ }
293
+ Object.keys(records).forEach(function (vendor) {
294
+ if (present[vendor]) return;
295
+ records[vendor].item.remove();
296
+ delete records[vendor];
297
+ });
298
+ if (!open) bindDismissal(false);
299
+ refreshIcons();
300
+ // Relative times only change slowly; one light timer keeps them honest.
301
+ if (!tickTimer) tickTimer = setInterval(renderVendorUsageOverview, 30000);
302
+ }
303
+
304
+ export function initVendorUsageOverview() {
305
+ store.subscribe(function (state, prev) {
306
+ var identityChanged = state.currentSlug !== prev.currentSlug || state.myUserId !== prev.myUserId;
307
+ if (identityChanged) {
308
+ // Another project or account must start unknown and ignore any reply
309
+ // that was requested before the change.
310
+ pendingRequestId = null;
311
+ if (state.vendorUsageOverview) {
312
+ store.set({ vendorUsageOverview: null, vendorUsageOpen: null });
313
+ if (state.connected) requestOverview(true);
314
+ return;
315
+ }
316
+ }
317
+ if (state.connected && (!prev.connected || identityChanged)) requestOverview(true);
318
+ if (state.vendorUsageOverview !== prev.vendorUsageOverview || state.vendorInfo !== prev.vendorInfo
319
+ || state.currentVendor !== prev.currentVendor || state.vendorUsageOpen !== prev.vendorUsageOpen) {
320
+ renderVendorUsageOverview();
321
+ }
322
+ });
323
+ }
@@ -6,23 +6,23 @@
6
6
  @import url("css/linear.css");
7
7
  @import url("css/base.css?v=20260828-type1");
8
8
  @import url("css/icon-strip.css?v=20260828-surfaces4");
9
- @import url("css/title-bar.css?v=20260828-project-header1");
9
+ @import url("css/title-bar.css?v=20261018-workbench-controls2");
10
10
  @import url("css/clay-workspace.css?v=20261010-tint");
11
11
  @import url("css/pane.css");
12
12
  @import url("css/worker-proposal.css");
13
13
  @import url("css/workspace-assignment.css");
14
14
  @import url("css/session-actions.css?v=20260828-contrast1");
15
- @import url("css/sidebar.css?v=20260828-session-list3");
15
+ @import url("css/sidebar.css?v=20261018-quick-appearance");
16
16
  @import url("css/session-folders.css");
17
- @import url("css/overlays.css?v=20260827-brand3");
17
+ @import url("css/overlays.css?v=20261018-topbar-gap");
18
18
  @import url("css/menus.css");
19
- @import url("css/messages.css?v=20260828-delegated-muted1");
19
+ @import url("css/messages.css?v=20261018-worker-notice");
20
20
  @import url("css/generated-images.css?v=20260829-save-details1");
21
21
  @import url("css/rewind.css");
22
22
  @import url("css/elicitation-form.css");
23
23
  @import url("css/input.css?v=20261002-stop-inline1");
24
24
  @import url("css/pending-message-queue.css?v=20260914-queue1");
25
- @import url("css/filebrowser.css?v=20260829-terminal-canvas1");
25
+ @import url("css/filebrowser.css?v=20261018-workbench-region");
26
26
  @import url("css/git-panel.css");
27
27
  @import url("css/git-placard.css");
28
28
  @import url("css/diff.css");
@@ -53,7 +53,7 @@
53
53
  @import url("css/session-search.css");
54
54
  @import url("css/tooltip.css");
55
55
  @import url("css/mates.css?v=20260407");
56
- @import url("css/command-palette.css?v=20260910-ask-clay-controls1");
56
+ @import url("css/command-palette.css?v=20261018-topbar-gap");
57
57
  @import url("css/mention.css");
58
58
  @import url("css/debate.css");
59
59
  @import url("css/home-debate-planning.css");
@@ -61,21 +61,22 @@
61
61
  @import url("css/home-mate-settings.css");
62
62
  @import url("css/mate-avatar-gallery.css");
63
63
  @import url("css/home-mate-model-picker.css");
64
- @import url("css/project-mate-creation.css");
64
+ @import url("css/project-mate-creation.css?v=20261018-clay-choices");
65
65
  @import url("css/home-debate-live.css");
66
66
  @import url("css/notifications-center.css");
67
67
  @import url("css/tui-attention.css?v=20260829-terminal-canvas1");
68
68
  @import url("css/pwa-mobile.css?v=20260824f");
69
69
  @import url("css/avatar-imprints.css?v=20260829-imprints1");
70
70
 
71
- @import url("css/projects-hub.css");
71
+ @import url("css/projects-hub.css?v=20261018-welcome-art");
72
+ @import url("css/project-start.css");
72
73
 
73
74
  @import url("./css/shared-browser.css");
74
75
 
75
76
  @import url("./css/mcp-skills-workbench.css");
76
77
  @import url("css/skills-workbench.css");
77
78
 
78
- @import url("css/right-workbench.css");
79
+ @import url("css/right-workbench.css?v=20261018-workbench-region");
79
80
 
80
81
  @import url("css/mate-conversation.css");
81
82
 
@@ -88,7 +89,9 @@
88
89
  @import url("css/server-settings-system.css");
89
90
 
90
91
  @import url("css/project-settings-modal.css");
92
+ @import url("css/settings-shell.css");
91
93
 
92
94
  @import url("css/clay-tool-activity.css");
93
95
 
94
- @import url("css/contextual-clay.css");
96
+ @import url("css/contextual-clay.css?v=20261018-loading-scene");
97
+ @import url("css/mate-loading-scene.css");
package/lib/sdk-bridge.js CHANGED
@@ -731,6 +731,7 @@ function createSDKBridge(opts) {
731
731
  "mcp__clay-workspace__read_workspace_receipt": true,
732
732
  "mcp__clay-workspace__inspect_workspace_change": true,
733
733
  "mcp__clay-workspace__control_project_workspace": true,
734
+ "mcp__clay-workspace__read_vendor_usage": true,
734
735
  "mcp__clay-workspace__read_project_records": true,
735
736
  "mcp__clay-workspace__inspect_project_record_change": true,
736
737
  "mcp__clay-workspace__control_project_records": true,
@@ -1734,6 +1735,9 @@ function createSDKBridge(opts) {
1734
1735
  var hasSharedBrowserCatalog = sessionToolDefs.some(function (definition) { return definition.name === "shared_browser"; });
1735
1736
  var recoverBrowserTools = needsCatalogRecovery("browser", 6) && hasSharedBrowserCatalog;
1736
1737
  var recoverIssueTools = needsCatalogRecovery("issues", 1) && hasIssueCatalog;
1738
+ var notesCatalogVersion = require("./session-notes-mcp-server").CATALOG_VERSION;
1739
+ var hasNotesCatalog = sessionToolDefs.some(function (definition) { return definition.name === "write_note"; });
1740
+ var recoverNoteTools = needsCatalogRecovery("notes", notesCatalogVersion) && hasNotesCatalog;
1737
1741
  function queryContextAvailable() {
1738
1742
  if (queryRetired()) return false;
1739
1743
  if (Number(session._sdkQueryGeneration || 0) !== queryGeneration) return false;
@@ -1929,7 +1933,7 @@ function createSDKBridge(opts) {
1929
1933
  dynamicTools: sessionToolDefs,
1930
1934
  // Conversation-scoped catalogs persist definitions. A stale authorized Driver
1931
1935
  // gets this exact-query bridge; its handlers still reauthorize live.
1932
- sessionMcpServer: (recoverMateSchedules || recoverIssueTools || recoverQuestionTools || recoverBrowserTools || recoverLinearTools || recoverYouTools || recoverExternalTools) ? getSessionMcpServer(session, queryGeneration) : undefined,
1936
+ sessionMcpServer: (recoverMateSchedules || recoverIssueTools || recoverQuestionTools || recoverBrowserTools || recoverLinearTools || recoverYouTools || recoverExternalTools || recoverNoteTools) ? getSessionMcpServer(session, queryGeneration) : undefined,
1933
1937
  onRecoveryAdmission: function(waiting) {
1934
1938
  if (!queryContextAvailable()) return;
1935
1939
  if (waiting) session._providerRecoveryWaiting = queryGeneration;
@@ -2177,6 +2181,7 @@ function createSDKBridge(opts) {
2177
2181
  if (hasLinearCatalog) markCatalog("linear", 1);
2178
2182
  if (hasSharedBrowserCatalog) markCatalog("browser", 6);
2179
2183
  if (hasIssueCatalog) markCatalog("issues", 1);
2184
+ if (hasNotesCatalog) markCatalog("notes", notesCatalogVersion);
2180
2185
  }
2181
2186
  session.streamPromise = processQueryStream(session, catalogCandidate, queryContract).catch(function(err) {
2182
2187
  });
@@ -46,6 +46,10 @@ function attachMessageProcessor(ctx) {
46
46
  sm.sendAndRecord(session, obj, clientObj);
47
47
  }
48
48
 
49
+ function observeQuota(session, message, observedAtMs) {
50
+ if (typeof sm.notifyQuotaObserved === "function") sm.notifyQuotaObserved(session, message, observedAtMs);
51
+ }
52
+
49
53
  function recordTurnActivity(session, activity) {
50
54
  // An authoritative input card owns progress until its request settles.
51
55
  var awaitingInput = ["pendingPermissions", "pendingAskUser", "pendingElicitations", "pendingUserDialogs"].some(function (key) {
@@ -892,7 +896,7 @@ function attachMessageProcessor(ctx) {
892
896
  } else if (parsed.yokeType === "rate_limit") {
893
897
  var info = parsed.rateLimitInfo;
894
898
  if (info.source === "account_snapshot") {
895
- quotaSnapshots.recordQuotaSnapshot(session, info, sendAndRecord);
899
+ quotaSnapshots.recordQuotaSnapshot(session, info, sendAndRecord, observeQuota);
896
900
  return;
897
901
  }
898
902
  console.log("[sdk-bridge] rate_limit_event for session " + session.localId + ": status=" + info.status + " resetsAt=" + info.resetsAt + " isUsingOverage=" + info.isUsingOverage + " isProcessing=" + session.isProcessing);
@@ -907,6 +911,7 @@ function attachMessageProcessor(ctx) {
907
911
  resetsAt: info.resetsAt * 1000,
908
912
  status: info.status,
909
913
  });
914
+ observeQuota(session, { type: "rate_limit_usage", vendor: session.vendor || "claude", rateLimitType: info.rateLimitType, resetsAt: info.resetsAt * 1000, status: info.status }, Date.now());
910
915
  }
911
916
 
912
917
  // Warning/rejection handling (existing behavior)
@@ -925,6 +930,9 @@ function attachMessageProcessor(ctx) {
925
930
  overageResetsAt: info.overageResetsAt ? info.overageResetsAt * 1000 : null,
926
931
  overageDisabledReason: info.overageDisabledReason || null,
927
932
  });
933
+ if (info.rateLimitType && info.utilization) {
934
+ observeQuota(session, { type: "rate_limit", vendor: session.vendor || "claude", rateLimitType: info.rateLimitType, utilization: info.utilization }, Date.now());
935
+ }
928
936
  // Track rejection for auto-continue / scheduled message support
929
937
  if (info.status === "rejected" && info.resetsAt) {
930
938
  session.rateLimitResetsAt = info.resetsAt * 1000;
@@ -1,4 +1,4 @@
1
- function recordQuotaSnapshot(session, info, sendAndRecord) {
1
+ function recordQuotaSnapshot(session, info, sendAndRecord, observe) {
2
2
  var message = {
3
3
  type: "rate_limit_usage",
4
4
  sessionId: session.localId,
@@ -10,6 +10,9 @@ function recordQuotaSnapshot(session, info, sendAndRecord) {
10
10
  }),
11
11
  };
12
12
  if (Number.isFinite(info.observedAtMs)) message.observedAtMs = info.observedAtMs;
13
+ // The overview records every live observation, including an unchanged one:
14
+ // deduplication below only avoids re-recording identical history.
15
+ if (typeof observe === "function") observe(session, message, Number.isFinite(info.observedAtMs) ? info.observedAtMs : Date.now());
13
16
  var snapshots = session._quotaSnapshots || (session._quotaSnapshots = Object.create(null));
14
17
  var key = JSON.stringify([session.ownerId, session.vendor, info.limitId]);
15
18
  var serialized = JSON.stringify(message);
@@ -18,7 +21,22 @@ function recordQuotaSnapshot(session, info, sendAndRecord) {
18
21
  sendAndRecord(session, message);
19
22
  }
20
23
 
24
+ // Observation listeners feed the owner-scoped vendor overview. They never
25
+ // influence rejection handling or scheduling.
26
+ function attachQuotaObservers(sm) {
27
+ // Idempotent: the processor and the overview may attach in either order.
28
+ if (typeof sm.addOnQuotaObserved === "function") return;
29
+ var listeners = [];
30
+ sm.addOnQuotaObserved = function (fn) { if (typeof fn === "function") listeners.push(fn); };
31
+ sm.notifyQuotaObserved = function (session, message, observedAtMs) {
32
+ for (var i = 0; i < listeners.length; i++) {
33
+ try { listeners[i](session, message, observedAtMs); } catch (e) { /* the overview is optional */ }
34
+ }
35
+ };
36
+ }
37
+
21
38
  function attachQuotaSnapshots(sm) {
39
+ attachQuotaObservers(sm);
22
40
  if (typeof sm.addOnSessionViewed !== "function") return;
23
41
  sm.addOnSessionViewed(function (session, ws, send) {
24
42
  var seen = Object.create(null);
@@ -34,4 +52,4 @@ function attachQuotaSnapshots(sm) {
34
52
  });
35
53
  }
36
54
 
37
- module.exports = { recordQuotaSnapshot: recordQuotaSnapshot, attachQuotaSnapshots: attachQuotaSnapshots };
55
+ module.exports = { recordQuotaSnapshot: recordQuotaSnapshot, attachQuotaSnapshots: attachQuotaSnapshots, attachQuotaObservers: attachQuotaObservers };
@@ -0,0 +1,39 @@
1
+ // server-github-stars.js - GET /api/github-stars for the top-bar Star link.
2
+ //
3
+ // Read-only. Returns the cached public star count of the fixed Clay
4
+ // repository (null when not yet known). No request input reaches GitHub, and
5
+ // nothing about the caller is sent or stored.
6
+
7
+ var githubStarCount = require("./github-star-count");
8
+
9
+ function attachGithubStars(ctx) {
10
+ var cache = ctx.cache || githubStarCount.createGithubStarCount();
11
+
12
+ function json(res, status, body) {
13
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
14
+ res.end(JSON.stringify(body));
15
+ }
16
+
17
+ function handleRequest(req, res, url) {
18
+ if (url !== "/api/github-stars") return false;
19
+ if (req.method !== "GET") {
20
+ res.writeHead(405, { "Content-Type": "application/json", "Allow": "GET" });
21
+ res.end(JSON.stringify({ error: "Method not allowed." }));
22
+ return true;
23
+ }
24
+ if (!ctx.isRequestAuthed(req)) {
25
+ json(res, 401, { error: "Sign in to continue." });
26
+ return true;
27
+ }
28
+ cache.get().then(function (snapshot) {
29
+ json(res, 200, snapshot);
30
+ }, function () {
31
+ json(res, 200, { repo: githubStarCount.REPO, stars: null, fetchedAt: null });
32
+ });
33
+ return true;
34
+ }
35
+
36
+ return { handleRequest: handleRequest };
37
+ }
38
+
39
+ module.exports = { attachGithubStars: attachGithubStars };