clay-server 5.2.0-beta.9 → 5.2.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.
- package/README.md +0 -4
- package/lib/clay-product-control-tools.js +1 -0
- package/lib/clay-product-control.js +14 -11
- package/lib/clay-vendor-usage.js +44 -0
- package/lib/github-star-count.js +147 -0
- package/lib/notes-checklist.js +43 -1
- package/lib/notes.js +2 -1
- package/lib/project-session-notes.js +33 -8
- package/lib/project-sessions.js +63 -24
- package/lib/project-start-protocol.js +29 -0
- package/lib/project-vendor-login.js +2 -0
- package/lib/project.js +5 -1
- package/lib/public/app.js +27 -18
- package/lib/public/css/contextual-clay.css +1 -1
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/mate-loading-scene.css +21 -0
- package/lib/public/css/overlays.css +64 -256
- package/lib/public/css/project-mate-creation.css +5 -6
- package/lib/public/css/project-start.css +108 -0
- package/lib/public/css/projects-hub.css +53 -14
- package/lib/public/css/settings-shell.css +103 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/title-bar.css +83 -0
- package/lib/public/index.html +50 -63
- package/lib/public/modules/account-transport-settings.js +8 -2
- package/lib/public/modules/admin-settings-email.js +7 -2
- package/lib/public/modules/admin-settings-invitations.js +2 -2
- package/lib/public/modules/admin-settings-projects.js +2 -2
- package/lib/public/modules/admin-settings-ui.js +20 -4
- package/lib/public/modules/admin-settings-users.js +4 -4
- package/lib/public/modules/app-home-hub.js +2 -1
- package/lib/public/modules/app-messages.js +10 -2
- package/lib/public/modules/app-projects.js +1 -407
- package/lib/public/modules/app-rate-limit.js +12 -1
- package/lib/public/modules/clay-tool-presentation.js +1 -0
- package/lib/public/modules/contextual-clay.js +3 -1
- package/lib/public/modules/github-star.js +66 -0
- package/lib/public/modules/home-surface-boot.js +0 -1
- package/lib/public/modules/mate-loading-scene.js +17 -0
- package/lib/public/modules/pane-appearance-bridge.js +59 -0
- package/lib/public/modules/profile.js +11 -3
- package/lib/public/modules/project-mate-creation-workspace.js +13 -4
- package/lib/public/modules/project-session-visibility-settings.js +19 -16
- package/lib/public/modules/project-settings-environment.js +12 -1
- package/lib/public/modules/project-settings.js +119 -129
- package/lib/public/modules/project-start-dialog.js +130 -0
- package/lib/public/modules/project-start-folder-state.js +78 -0
- package/lib/public/modules/project-start-folder-view.js +190 -0
- package/lib/public/modules/project-start-forms.js +66 -0
- package/lib/public/modules/project-start-path.js +60 -0
- package/lib/public/modules/project-start-state.js +54 -0
- package/lib/public/modules/project-start-submit.js +67 -0
- package/lib/public/modules/projects-hub-view.js +32 -44
- package/lib/public/modules/projects-hub.js +39 -28
- package/lib/public/modules/server-settings.js +36 -91
- package/lib/public/modules/settings-project-target.js +56 -0
- package/lib/public/modules/settings-select.js +1 -1
- package/lib/public/modules/settings-shell-registry.js +88 -0
- package/lib/public/modules/settings-shell-view.js +104 -0
- package/lib/public/modules/settings-shell.js +342 -0
- package/lib/public/modules/sidebar-mobile.js +8 -30
- package/lib/public/modules/sidebar-projects.js +2 -1
- package/lib/public/modules/theme.js +31 -3
- package/lib/public/modules/user-settings-integrations.js +2 -1
- package/lib/public/modules/user-settings-layout.js +12 -34
- package/lib/public/modules/user-settings-profile.js +1 -1
- package/lib/public/modules/user-settings.js +18 -85
- package/lib/public/modules/vendor-usage-format.js +93 -0
- package/lib/public/modules/vendor-usage-list.js +45 -0
- package/lib/public/modules/vendor-usage-model.js +215 -0
- package/lib/public/modules/vendor-usage-overview.js +323 -0
- package/lib/public/style.css +10 -7
- package/lib/public/welcome-art/conversation-desk.png +0 -0
- package/lib/public/welcome-art/mate-clay.png +0 -0
- package/lib/public/welcome-art/project-clay.png +0 -0
- package/lib/sdk-bridge.js +6 -1
- package/lib/sdk-message-processor.js +9 -1
- package/lib/sdk-quota-snapshot.js +20 -2
- package/lib/server-github-stars.js +39 -0
- package/lib/server-global-ws.js +21 -17
- package/lib/server.js +11 -0
- package/lib/session-notes-mcp-server.js +26 -4
- package/lib/session-notes-writes.js +88 -0
- package/lib/session-visibility.js +20 -8
- package/lib/sessions.js +12 -2
- package/lib/vendor-usage-acp-readers.js +97 -0
- package/lib/vendor-usage-capabilities.js +88 -0
- package/lib/vendor-usage-http-readers.js +214 -0
- package/lib/vendor-usage-metrics.js +47 -0
- package/lib/vendor-usage-native-readers.js +106 -0
- package/lib/vendor-usage-overview.js +406 -0
- package/lib/vendor-usage-service.js +281 -0
- package/lib/ws-schema.js +4 -2
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex-query-contract.js +1 -0
- package/lib/yoke/adapters/codex.js +45 -12
- package/lib/yoke/claude-hook-installer.js +1 -0
- package/lib/yoke/codex-dynamic-tool-names.js +80 -0
- package/lib/yoke/codex-model-catalog.js +37 -3
- package/lib/yoke/muse-services.js +16 -3
- package/lib/yoke/providers/claude-model-descriptions.js +22 -2
- package/lib/yoke/vendor-registry.js +6 -2
- package/package.json +2 -1
- 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
|
+
}
|
package/lib/public/style.css
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
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=
|
|
9
|
+
@import url("css/title-bar.css?v=20261018-actions-layer");
|
|
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=
|
|
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=
|
|
17
|
+
@import url("css/overlays.css?v=20261017-usage2");
|
|
18
18
|
@import url("css/menus.css");
|
|
19
19
|
@import url("css/messages.css?v=20260828-delegated-muted1");
|
|
20
20
|
@import url("css/generated-images.css?v=20260829-save-details1");
|
|
@@ -22,7 +22,7 @@
|
|
|
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=
|
|
25
|
+
@import url("css/filebrowser.css?v=20261018-browser-header1");
|
|
26
26
|
@import url("css/git-panel.css");
|
|
27
27
|
@import url("css/git-placard.css");
|
|
28
28
|
@import url("css/diff.css");
|
|
@@ -61,14 +61,15 @@
|
|
|
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
|
|
|
@@ -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");
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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 };
|
package/lib/server-global-ws.js
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
var fs = require("fs");
|
|
20
20
|
var path = require("path");
|
|
21
21
|
var config = require("./config");
|
|
22
|
+
var startProtocol = require("./project-start-protocol");
|
|
23
|
+
var startReply = startProtocol.withRequestId;
|
|
24
|
+
var browseMeta = startProtocol.browseMeta;
|
|
22
25
|
|
|
23
26
|
// Mirrored from lib/project.js — keep in sync if entries change there.
|
|
24
27
|
var IGNORED_DIRS = new Set([
|
|
@@ -79,8 +82,9 @@ function attachGlobalWs(opts) {
|
|
|
79
82
|
parentDir = path.dirname(absTarget);
|
|
80
83
|
prefix = path.basename(absTarget).toLowerCase();
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
var canList = function (candidate) { return !opts.canUsePath || opts.canUsePath(ws._clayUser && ws._clayUser.id, candidate); };
|
|
86
|
+
if (!canList(parentDir)) {
|
|
87
|
+
sendTo(ws, startReply(msg, { type: "browse_dir_result", path: msg.path, entries: [], error: "Directory access is not permitted" }));
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
86
90
|
try {
|
|
@@ -96,9 +100,9 @@ function attachGlobalWs(opts) {
|
|
|
96
100
|
dirEntries.push({ name: d.name, path: path.join(parentDir, d.name) });
|
|
97
101
|
}
|
|
98
102
|
dirEntries.sort(function (a, b) { return a.name.localeCompare(b.name); });
|
|
99
|
-
sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: dirEntries });
|
|
103
|
+
sendTo(ws, Object.assign(browseMeta(msg, parentDir, prefix, canList), { type: "browse_dir_result", path: msg.path, entries: dirEntries }));
|
|
100
104
|
} catch (e) {
|
|
101
|
-
sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: [], error: e.message });
|
|
105
|
+
sendTo(ws, startReply(msg, { type: "browse_dir_result", path: msg.path, entries: [], error: e.message }));
|
|
102
106
|
}
|
|
103
107
|
return;
|
|
104
108
|
}
|
|
@@ -108,24 +112,24 @@ function attachGlobalWs(opts) {
|
|
|
108
112
|
var addPath = (msg.path || "").replace(/^~/, config.REAL_HOME);
|
|
109
113
|
var addAbs = path.resolve(addPath);
|
|
110
114
|
if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, addAbs)) {
|
|
111
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Directory access is not permitted" });
|
|
115
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Directory access is not permitted" }));
|
|
112
116
|
return;
|
|
113
117
|
}
|
|
114
118
|
try {
|
|
115
119
|
var addStat = fs.statSync(addAbs);
|
|
116
120
|
if (!addStat.isDirectory()) {
|
|
117
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Not a directory" });
|
|
121
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not a directory" }));
|
|
118
122
|
return;
|
|
119
123
|
}
|
|
120
124
|
} catch (e) {
|
|
121
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Directory not found" });
|
|
125
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Directory not found" }));
|
|
122
126
|
return;
|
|
123
127
|
}
|
|
124
128
|
if (typeof onAddProject === "function") {
|
|
125
129
|
var result = onAddProject(addAbs, ws._clayUser);
|
|
126
|
-
sendTo(ws, Object.assign({}, result, { type: "add_project_result" }));
|
|
130
|
+
sendTo(ws, startReply(msg, Object.assign({}, result, { type: "add_project_result" })));
|
|
127
131
|
} else {
|
|
128
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
|
|
132
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not supported" }));
|
|
129
133
|
}
|
|
130
134
|
return;
|
|
131
135
|
}
|
|
@@ -135,7 +139,7 @@ function attachGlobalWs(opts) {
|
|
|
135
139
|
if (ws._clayUser && usersModule) {
|
|
136
140
|
var cpPerms = usersModule.getEffectivePermissions(ws._clayUser, osUsers);
|
|
137
141
|
if (!cpPerms.createProject) {
|
|
138
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "You do not have permission to create projects" });
|
|
142
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "You do not have permission to create projects" }));
|
|
139
143
|
return;
|
|
140
144
|
}
|
|
141
145
|
}
|
|
@@ -144,14 +148,14 @@ function attachGlobalWs(opts) {
|
|
|
144
148
|
if (msg.type === "create_project") {
|
|
145
149
|
var createName = (msg.name || "").trim();
|
|
146
150
|
if (!createName || !/^[a-zA-Z0-9_-]+$/.test(createName)) {
|
|
147
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Invalid name. Use only letters, numbers, dashes, and underscores." });
|
|
151
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Invalid name. Use only letters, numbers, dashes, and underscores." }));
|
|
148
152
|
return;
|
|
149
153
|
}
|
|
150
154
|
if (typeof onCreateProject === "function") {
|
|
151
155
|
var createResult = onCreateProject(createName, ws._clayUser);
|
|
152
|
-
sendTo(ws, Object.assign({}, createResult, { type: "add_project_result" }));
|
|
156
|
+
sendTo(ws, startReply(msg, Object.assign({}, createResult, { type: "add_project_result" })));
|
|
153
157
|
} else {
|
|
154
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
|
|
158
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not supported" }));
|
|
155
159
|
}
|
|
156
160
|
return;
|
|
157
161
|
}
|
|
@@ -159,16 +163,16 @@ function attachGlobalWs(opts) {
|
|
|
159
163
|
if (msg.type === "clone_project") {
|
|
160
164
|
var cloneUrl = (msg.url || "").trim();
|
|
161
165
|
if (!cloneUrl || (!/^https?:\/\//.test(cloneUrl) && !/^git@/.test(cloneUrl))) {
|
|
162
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Invalid URL. Use https:// or git@ format." });
|
|
166
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Invalid URL. Use https:// or git@ format." }));
|
|
163
167
|
return;
|
|
164
168
|
}
|
|
165
|
-
sendTo(ws, { type: "clone_project_progress", status: "cloning" });
|
|
169
|
+
sendTo(ws, startReply(msg, { type: "clone_project_progress", status: "cloning" }));
|
|
166
170
|
if (typeof onCloneProject === "function") {
|
|
167
171
|
onCloneProject(cloneUrl, ws._clayUser, function (cloneResult) {
|
|
168
|
-
sendTo(ws, { type: "add_project_result", ok: cloneResult.ok, slug: cloneResult.slug, error: cloneResult.error });
|
|
172
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: cloneResult.ok, slug: cloneResult.slug, error: cloneResult.error }));
|
|
169
173
|
});
|
|
170
174
|
} else {
|
|
171
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
|
|
175
|
+
sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not supported" }));
|
|
172
176
|
}
|
|
173
177
|
return;
|
|
174
178
|
}
|