clay-server 5.2.0-beta.8 → 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/pane-pending-interactions.js +20 -0
- package/lib/pane-session-activation.js +114 -0
- package/lib/pane-session-transport.js +26 -0
- package/lib/project-session-notes.js +33 -8
- package/lib/project-sessions.js +78 -29
- package/lib/project-start-protocol.js +29 -0
- package/lib/project-vendor-login.js +2 -0
- package/lib/project.js +16 -4
- package/lib/public/app.js +41 -18
- package/lib/public/css/contextual-clay.css +2 -2
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/input.css +6 -0
- 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/sticky-notes.css +37 -11
- 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-connection.js +3 -0
- package/lib/public/modules/app-home-hub.js +2 -1
- package/lib/public/modules/app-message-router.js +13 -0
- package/lib/public/modules/app-messages.js +35 -7
- package/lib/public/modules/app-misc.js +3 -1
- package/lib/public/modules/app-projects.js +6 -407
- package/lib/public/modules/app-rate-limit.js +12 -1
- package/lib/public/modules/clay-owner-navigation.js +0 -3
- package/lib/public/modules/clay-studio-render.js +3 -1
- package/lib/public/modules/clay-tool-presentation.js +1 -0
- package/lib/public/modules/command-palette.js +52 -14
- package/lib/public/modules/contextual-clay-workspace.js +14 -2
- package/lib/public/modules/contextual-clay.js +19 -4
- package/lib/public/modules/contextual-mate-context.js +6 -4
- package/lib/public/modules/elicitation-form.js +5 -0
- package/lib/public/modules/github-star.js +66 -0
- package/lib/public/modules/history-replay-batch.js +8 -0
- package/lib/public/modules/home-surface-boot.js +0 -1
- package/lib/public/modules/input.js +55 -11
- package/lib/public/modules/mate-loading-scene.js +17 -0
- package/lib/public/modules/message-delivery.js +10 -2
- package/lib/public/modules/model-picker.js +2 -0
- package/lib/public/modules/pane-activation.js +214 -0
- package/lib/public/modules/pane-appearance-bridge.js +59 -0
- package/lib/public/modules/pane-binding.js +82 -0
- package/lib/public/modules/pane-bridge.js +5 -2
- package/lib/public/modules/pane-browser-bridge.js +5 -3
- package/lib/public/modules/pane-file-bridge.js +4 -0
- package/lib/public/modules/pane-issue-bridge.js +4 -1
- 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/rewind.js +5 -0
- package/lib/public/modules/server-settings.js +36 -91
- package/lib/public/modules/session-actions.js +5 -2
- package/lib/public/modules/session-context-menu.js +5 -0
- package/lib/public/modules/session-rich-drafts.js +21 -0
- package/lib/public/modules/session-switch-defer.js +67 -0
- 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/split-group-restore.js +68 -0
- package/lib/public/modules/split-pair-ui.js +1 -0
- package/lib/public/modules/split-pane-reconciler.js +15 -0
- package/lib/public/modules/split-pane-renderer.js +87 -6
- package/lib/public/modules/split-session-boundary.js +1 -1
- package/lib/public/modules/split-view.js +51 -52
- package/lib/public/modules/sticky-notes-checklist.js +51 -6
- package/lib/public/modules/stt-controller.js +4 -1
- package/lib/public/modules/stt-recording.js +1 -1
- package/lib/public/modules/theme.js +31 -3
- package/lib/public/modules/tools.js +18 -2
- 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 +27 -10
- 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 +5 -3
- package/lib/public/modules/server-settings-navigation.js +0 -39
|
@@ -238,9 +238,16 @@ function tickRateLimitUsage() {
|
|
|
238
238
|
renderRateLimitUsage();
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
// Once the server's vendor overview has answered, every vendor including the
|
|
242
|
+
// displayed one is shown by the overview chips, so this session-scoped link
|
|
243
|
+
// stands down. Rejection, warning and scheduling state is untouched.
|
|
244
|
+
function overviewOwnsUsageDisplay() {
|
|
245
|
+
return !!store.get('vendorUsageOverview');
|
|
246
|
+
}
|
|
247
|
+
|
|
241
248
|
function renderRateLimitUsage() {
|
|
242
249
|
var activeVendor = store.get('currentVendor') || "claude";
|
|
243
|
-
if (!vendorTracksRateLimits(activeVendor)) {
|
|
250
|
+
if (!vendorTracksRateLimits(activeVendor) || overviewOwnsUsageDisplay()) {
|
|
244
251
|
if (rateLimitUsageEl) {
|
|
245
252
|
rateLimitUsageEl.remove();
|
|
246
253
|
rateLimitUsageEl = null;
|
|
@@ -320,6 +327,10 @@ export function initRateLimit() {
|
|
|
320
327
|
|| state.currentSlug !== prev.currentSlug || state.myUserId !== prev.myUserId) {
|
|
321
328
|
resetRateLimitState();
|
|
322
329
|
}
|
|
330
|
+
if (state.vendorUsageOverview !== prev.vendorUsageOverview && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
|
|
331
|
+
&& state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
|
|
332
|
+
renderRateLimitUsage();
|
|
333
|
+
}
|
|
323
334
|
if (state.vendorInfo !== prev.vendorInfo && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
|
|
324
335
|
&& state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
|
|
325
336
|
renderRateLimitUsage();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { observeNavigationRendering } from './clay-navigation-render.js';
|
|
2
|
-
import { dismissContextualClay } from './contextual-clay.js';
|
|
3
2
|
import { showClayNavigationActivity, clearClayControlActivity } from './clay-control-activity.js';
|
|
4
3
|
import { store } from './store.js';
|
|
5
4
|
import { getWs } from './ws-ref.js';
|
|
@@ -23,8 +22,6 @@ export function handleOwnerNavigation(msg) {
|
|
|
23
22
|
var status = 'refused';
|
|
24
23
|
if (accepted && msg.target && typeof msg.target.projectSlug === 'string' && Number.isSafeInteger(msg.target.sessionId)) {
|
|
25
24
|
try {
|
|
26
|
-
var contextual = store.get('contextualClay') || {};
|
|
27
|
-
if (msg.target.contextualSourceMateId && contextual.mateId === msg.target.contextualSourceMateId && contextual.mode === 'popover') dismissContextualClay(false);
|
|
28
25
|
showClayNavigationActivity(msg.target, false); navigateToSession(msg.target, null, msg.target.entryIndex); status = 'accepted';
|
|
29
26
|
} catch (error) { showClayNavigationActivity(msg.target, true); status = 'failed'; }
|
|
30
27
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forwardDocumentSource } from './clay-studio-document.js';
|
|
2
2
|
import { store } from './store.js';
|
|
3
|
+
import { paneMessageBinding, livePaneBinding } from './pane-binding.js';
|
|
3
4
|
import { getWs } from './ws-ref.js';
|
|
4
5
|
import { contextualMateStamp } from './contextual-mate-identity.js';
|
|
5
6
|
import { visibleTarget } from './clay-control-target-visibility.js';
|
|
@@ -34,7 +35,7 @@ export function forwardStudioPresentation(message, type) {
|
|
|
34
35
|
if (Object.keys(receipts).length >= 100) return true;
|
|
35
36
|
if (evidence) receipts[evidence.id] = { evidence: evidence, socket: getWs() };
|
|
36
37
|
store.set({ clayStudioRelays: receipts });
|
|
37
|
-
window.parent.postMessage({ type: type, message: message, studioSource: frame.clayStudioSource.identity() }, location.origin);
|
|
38
|
+
window.parent.postMessage({ type: type, message: message, paneBinding: paneMessageBinding(), studioSource: frame.clayStudioSource.identity() }, location.origin);
|
|
38
39
|
return true;
|
|
39
40
|
}
|
|
40
41
|
export function trustedStudioFrame(event, data) {
|
|
@@ -45,6 +46,7 @@ export function trustedStudioFrame(event, data) {
|
|
|
45
46
|
if (!live || !live.connected || live.ownerId !== (store.get('myUserId') || null) || live.ownerId !== expected.ownerId || live.slug !== expected.slug || live.sessionId !== expected.sessionId || live.origin !== expected.origin || live.runtime !== expected.runtime) return null;
|
|
46
47
|
if (new URL(frame.src, location.href).pathname !== '/p/' + encodeURIComponent(live.slug) + '/') return null;
|
|
47
48
|
if (frame.classList.contains('split-pane-frame')) {
|
|
49
|
+
if (frame.clayPane && !livePaneBinding(frame, data)) return null;
|
|
48
50
|
var split = store.get('splitPanes');
|
|
49
51
|
if (!split || !split.panes.some(function (pane) { return pane.slug === live.slug && String(pane.sessionId) === String(live.sessionId); })) return null;
|
|
50
52
|
} else {
|
|
@@ -172,6 +172,7 @@ rows('clay-workspace', [
|
|
|
172
172
|
['inspect_mate_chat_change', 'Inspecting Mate conversation change', 'Mate conversation change inspected', 'Mate conversation'],
|
|
173
173
|
['control_mate_chat', 'Requesting Mate conversation change', 'Mate conversation request returned', 'Mate conversation', 'why', 'mate-chat'],
|
|
174
174
|
['control_schedules', 'Requesting schedule change', 'Schedule change request returned', 'Scheduling', 'why', 'schedule-control'],
|
|
175
|
+
['read_vendor_usage', 'Reading vendor usage', 'Vendor usage read', 'Vendor usage', 'projectSlug'],
|
|
175
176
|
['read_project_records', 'Reading project records', 'Project records read', 'Project records', 'projectSlug'],
|
|
176
177
|
['inspect_project_record_change', 'Inspecting record change', 'Record change inspected', 'Project records', 'projectSlug'],
|
|
177
178
|
['control_project_records', 'Requesting record change', 'Record change request returned', 'Project records', 'why'],
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { forwardContextualSessionCitation, validContextualCitation, contextualCitationLink } from './contextual-session-citations.js';
|
|
2
|
-
import { dismissContextualClay } from './contextual-clay.js';
|
|
3
2
|
import { mateAvatarUrl } from './avatar.js';
|
|
4
3
|
import { escapeHtml } from './utils.js';
|
|
5
4
|
import { refreshIcons } from './icons.js';
|
|
@@ -146,7 +145,6 @@ export function handleClaySessionTarget(msg) {
|
|
|
146
145
|
navigateToLog(msg.target);
|
|
147
146
|
return true;
|
|
148
147
|
}
|
|
149
|
-
if (citation) { dismissContextualClay(false); if ((store.get('contextualClay') || {}).mode !== 'closed') return true; }
|
|
150
148
|
navigateToSession({ projectSlug: msg.target.projectSlug, sessionId: msg.target.sessionId }, null, msg.target.entryIndex);
|
|
151
149
|
return true;
|
|
152
150
|
}
|
|
@@ -174,24 +172,36 @@ export function closeCommandPalette() {
|
|
|
174
172
|
|
|
175
173
|
export function setPaletteVersion(version) { cachedVersion = version; }
|
|
176
174
|
|
|
177
|
-
export function handlePaletteSessionSwitch() {
|
|
175
|
+
export function handlePaletteSessionSwitch(authoritative) {
|
|
178
176
|
if (pendingLogNav && ctx.currentSlug && ctx.currentSlug() === pendingLogNav.projectSlug) {
|
|
179
177
|
var logNav = pendingLogNav;
|
|
180
178
|
pendingLogNav = null;
|
|
181
179
|
openProjectLog(logNav.ref);
|
|
182
180
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
pendingNav
|
|
186
|
-
|
|
187
|
-
|
|
181
|
+
// session_list precedes the connection's authoritative session_switched.
|
|
182
|
+
// Wait for that identity instead of requesting a duplicate initial replay.
|
|
183
|
+
if (authoritative === true && pendingNav && store.get('activeSessionId')) {
|
|
184
|
+
var nav = pendingNav;
|
|
185
|
+
pendingNav = null;
|
|
186
|
+
if (ctx.currentSlug && ctx.currentSlug() === nav.slug) {
|
|
187
|
+
navigateToSession({ projectSlug: nav.slug, sessionId: nav.sessionId }, nav.query, nav.entryIndex);
|
|
188
|
+
}
|
|
188
189
|
}
|
|
190
|
+
var pending = store.get('pendingSessionEntryNavigation');
|
|
191
|
+
if (pending && isActiveEntryNavigation(pending)) pending.awaitingTarget = false;
|
|
192
|
+
var highlight = store.get('sessionEntryHighlight');
|
|
193
|
+
if (highlight && !isCurrentEntryHighlight(highlight)) store.set({ sessionEntryHighlight: null });
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
export function handlePaletteHistoryDone() {
|
|
192
197
|
var pending = store.get('pendingSessionEntryNavigation');
|
|
193
|
-
if (!pending)
|
|
198
|
+
if (!pending) {
|
|
199
|
+
var highlight = store.get('sessionEntryHighlight');
|
|
200
|
+
if (highlight && isCurrentEntryHighlight(highlight)) jumpToSessionEntry(highlight.entryIndex, highlight);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
194
203
|
if (!isActiveEntryNavigation(pending)) {
|
|
204
|
+
if (pending.awaitingTarget) return;
|
|
195
205
|
store.set({ pendingSessionEntryNavigation: null });
|
|
196
206
|
return;
|
|
197
207
|
}
|
|
@@ -201,18 +211,26 @@ export function handlePaletteHistoryDone() {
|
|
|
201
211
|
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: "load_more_history", before: historyFrom, target: pending.entryIndex }));
|
|
202
212
|
return;
|
|
203
213
|
}
|
|
204
|
-
jumpToSessionEntry(pending.entryIndex);
|
|
214
|
+
jumpToSessionEntry(pending.entryIndex, pending);
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
export function handlePaletteHistoryPrepended() {
|
|
208
218
|
var pending = store.get('pendingSessionEntryNavigation');
|
|
209
219
|
if (!pending) return;
|
|
210
220
|
if (!isActiveEntryNavigation(pending)) {
|
|
221
|
+
if (pending.awaitingTarget) return;
|
|
211
222
|
store.set({ pendingSessionEntryNavigation: null });
|
|
212
223
|
return;
|
|
213
224
|
}
|
|
214
225
|
requestAnimationFrame(function () {
|
|
215
|
-
if (store.get('pendingSessionEntryNavigation')
|
|
226
|
+
if (store.get('pendingSessionEntryNavigation') !== pending) return;
|
|
227
|
+
// This frame was scheduled for an already active target. A later
|
|
228
|
+
// navigation cancels it even if its initial awaiting flag was retained.
|
|
229
|
+
if (!isActiveEntryNavigation(pending)) {
|
|
230
|
+
store.set({ pendingSessionEntryNavigation: null });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
handlePaletteHistoryDone();
|
|
216
234
|
});
|
|
217
235
|
}
|
|
218
236
|
|
|
@@ -220,6 +238,11 @@ function isActiveEntryNavigation(pending) {
|
|
|
220
238
|
return String(store.get('currentSlug') || (ctx && ctx.currentSlug ? ctx.currentSlug() : "")) === String(pending.projectSlug) && String(store.get('activeSessionId')) === String(pending.sessionId);
|
|
221
239
|
}
|
|
222
240
|
|
|
241
|
+
function isCurrentEntryHighlight(highlight) {
|
|
242
|
+
return highlight.expiresAt > Date.now() && isActiveEntryNavigation(highlight)
|
|
243
|
+
&& highlight.sessionOriginId === (store.get('sessionOriginId') || null);
|
|
244
|
+
}
|
|
245
|
+
|
|
223
246
|
function jumpToSessionEntry(entryIndex, pending) {
|
|
224
247
|
if (pending && !isActiveEntryNavigation(pending)) return;
|
|
225
248
|
var messages = document.querySelectorAll("#messages [data-history-index], #messages [data-history-indexes]");
|
|
@@ -228,17 +251,31 @@ function jumpToSessionEntry(entryIndex, pending) {
|
|
|
228
251
|
var indexes = (messages[i].dataset.historyIndexes || messages[i].dataset.historyIndex || "").split(",");
|
|
229
252
|
if (indexes.indexOf(String(entryIndex)) !== -1) { target = messages[i]; break; }
|
|
230
253
|
}
|
|
231
|
-
store.set({ pendingSessionEntryNavigation: null });
|
|
232
254
|
if (!target) return;
|
|
255
|
+
store.set({ pendingSessionEntryNavigation: null });
|
|
233
256
|
disarmStickyBottom();
|
|
234
257
|
var oldTargets = document.querySelectorAll("#messages .session-entry-target");
|
|
235
258
|
for (var oldIndex = 0; oldIndex < oldTargets.length; oldIndex++) oldTargets[oldIndex].classList.remove("session-entry-target");
|
|
236
259
|
var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
260
|
+
// A repeated authoritative replay replaces the DOM after the initial jump.
|
|
261
|
+
// Retain only this exact origin for the original feedback lifetime.
|
|
262
|
+
var highlight = pending && pending.expiresAt ? pending : {
|
|
263
|
+
projectSlug: store.get('currentSlug'), sessionId: store.get('activeSessionId'),
|
|
264
|
+
sessionOriginId: store.get('sessionOriginId') || null, entryIndex: entryIndex,
|
|
265
|
+
expiresAt: Date.now() + (reducedMotion ? 700 : 1800),
|
|
266
|
+
};
|
|
267
|
+
store.set({ sessionEntryHighlight: highlight });
|
|
237
268
|
target.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "center" });
|
|
238
269
|
target.classList.remove("session-entry-target");
|
|
239
270
|
void target.offsetWidth;
|
|
240
271
|
target.classList.add("session-entry-target");
|
|
241
|
-
|
|
272
|
+
target.sessionEntryHighlight = highlight;
|
|
273
|
+
setTimeout(function () {
|
|
274
|
+
if (target.sessionEntryHighlight !== highlight) return;
|
|
275
|
+
target.classList.remove("session-entry-target");
|
|
276
|
+
delete target.sessionEntryHighlight;
|
|
277
|
+
if (store.get('sessionEntryHighlight') === highlight) store.set({ sessionEntryHighlight: null });
|
|
278
|
+
}, Math.max(0, highlight.expiresAt - Date.now()));
|
|
242
279
|
}
|
|
243
280
|
|
|
244
281
|
function navigateToLog(target) {
|
|
@@ -433,8 +470,9 @@ function activateItem(entry) {
|
|
|
433
470
|
export function navigateToSession(item, query, entryIndex) {
|
|
434
471
|
closeCommandPalette();
|
|
435
472
|
pendingNav = null;
|
|
473
|
+
store.set({ sessionEntryHighlight: null });
|
|
436
474
|
var hasEntry = entryIndex !== null && entryIndex !== undefined && Number.isSafeInteger(Number(entryIndex)) && Number(entryIndex) >= 0;
|
|
437
|
-
store.set({ pendingSessionEntryNavigation: hasEntry ? { projectSlug: item.projectSlug, sessionId: item.sessionId, entryIndex: Number(entryIndex) } : null });
|
|
475
|
+
store.set({ pendingSessionEntryNavigation: hasEntry ? { projectSlug: item.projectSlug, sessionId: item.sessionId, entryIndex: Number(entryIndex), awaitingTarget: true } : null });
|
|
438
476
|
if (ctx.currentSlug && ctx.currentSlug() === item.projectSlug) {
|
|
439
477
|
var alreadyActive = String(store.get('activeSessionId')) === String(item.sessionId);
|
|
440
478
|
if (alreadyActive) {
|
|
@@ -89,7 +89,9 @@ export function finishContextualWorkspace() {
|
|
|
89
89
|
if (handoff.stage === 'workspace' && store.get('currentSlug') === handoff.slug && store.get('activeSessionId') === handoff.sessionId) draft = takeComposerDraft();
|
|
90
90
|
connection.restore(draft);
|
|
91
91
|
connection.suspend(false);
|
|
92
|
-
|
|
92
|
+
var presentation = store.get('contextualClay') || {};
|
|
93
|
+
var keepOverlay = presentation.mode === 'popover' && connection !== bridge();
|
|
94
|
+
store.set({ contextualWorkspace: Object.assign({}, handoff, { draft: null, stage: 'returning' }), contextualClay: keepOverlay ? presentation : Object.assign({}, presentation, { mode: 'closed' }) });
|
|
93
95
|
lockNative(true);
|
|
94
96
|
return true;
|
|
95
97
|
} catch (error) { showToast(error.message, 'error'); return false; }
|
|
@@ -136,7 +138,17 @@ export function renderContextualWorkspaceBack() {
|
|
|
136
138
|
|
|
137
139
|
export function prepareContextualWorkspaceDeparture(slug) {
|
|
138
140
|
var origin = store.get('contextualNativeOrigin');
|
|
139
|
-
if (origin && slug !== origin.slug
|
|
141
|
+
if (origin && slug !== origin.slug) {
|
|
142
|
+
if ((store.get('contextualClay') || {}).mode === 'popover') {
|
|
143
|
+
var connection = bridge(origin.mateId, origin.sessionId);
|
|
144
|
+
try {
|
|
145
|
+
if (origin.stage !== 'pane' || !connection || !connection.identity().ready) throw new Error('Wait for this Mate conversation to reconnect.');
|
|
146
|
+
// The pane keeps its composer when its former native parent changes.
|
|
147
|
+
connection.checkDraft();
|
|
148
|
+
store.set({ contextualNativeOrigin: null }); lockNative(false);
|
|
149
|
+
} catch (error) { showToast(error.message, 'error'); return false; }
|
|
150
|
+
} else if (!finishContextualNativeOrigin()) return false;
|
|
151
|
+
}
|
|
140
152
|
var handoff = store.get('contextualWorkspace');
|
|
141
153
|
if (handoff && handoff.stage.indexOf('workspace') === 0 && slug !== handoff.slug) return finishContextualWorkspace();
|
|
142
154
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mateLoadingMarkup, updateMateLoadingScene } from './mate-loading-scene.js';
|
|
1
2
|
import { attachContextualSessionCitations } from './contextual-session-citations.js';
|
|
2
3
|
import { attachContextualConversations } from './contextual-mate-sessions.js';
|
|
3
4
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
@@ -34,6 +35,8 @@ function escapePopover(event) {
|
|
|
34
35
|
if (event.defaultPrevented || contextualClayMode() !== 'popover') return;
|
|
35
36
|
var context = host().querySelector('details[open]');
|
|
36
37
|
if (context) { context.open = false; context.querySelector('summary').focus(); return; }
|
|
38
|
+
var focused = document.activeElement;
|
|
39
|
+
if (!host().contains(focused) && focused !== trigger()) return;
|
|
37
40
|
event.preventDefault(); dismissContextualClay(true);
|
|
38
41
|
});
|
|
39
42
|
}
|
|
@@ -65,6 +68,7 @@ function render() {
|
|
|
65
68
|
var mate = contextualMate(state().mateId);
|
|
66
69
|
if (mate) {
|
|
67
70
|
var name = getHomeMateName(mate);
|
|
71
|
+
updateMateLoadingScene(surface.querySelector('[data-clay-loading]'), name, mateAvatarUrl(mate, 96));
|
|
68
72
|
surface.setAttribute('aria-label', name + ' conversation');
|
|
69
73
|
surface.querySelector('strong').textContent = name;
|
|
70
74
|
var header = surface.querySelector('header');
|
|
@@ -82,7 +86,16 @@ function render() {
|
|
|
82
86
|
}
|
|
83
87
|
var projectContext = (store.get('projectsHubList') || []).find(function (item) { return item.slug === store.get('currentSlug'); });
|
|
84
88
|
var contextSession = getCachedSessions().find(function (item) { return item.id === store.get('activeSessionId'); });
|
|
85
|
-
|
|
89
|
+
var screen = store.get('homeShellVisible') || store.get('projectsHubVisible') ? 'Home' : store.get('dmMode') ? 'Direct message' : 'Project';
|
|
90
|
+
var projectView = screen === 'Project';
|
|
91
|
+
var parentReady = store.get('activeProjectSlug') === store.get('currentSlug') && store.get('sessionActivatedProjectSlug') === store.get('currentSlug') && !store.get('replayingHistory');
|
|
92
|
+
updateMateContext(surface, mate, contextualFrame(), {
|
|
93
|
+
projectTitle: projectView ? projectContext && (projectContext.title || projectContext.project || projectContext.slug) : store.get('projectsHubVisible') ? 'Projects' : screen,
|
|
94
|
+
conversationTitle: projectView && parentReady && contextSession ? contextSession.title : null,
|
|
95
|
+
projectSlug: projectView && parentReady ? store.get('currentSlug') : null,
|
|
96
|
+
sessionId: projectView && parentReady ? store.get('activeSessionId') : null,
|
|
97
|
+
screen: screen, ownerId: store.get('myUserId') || null
|
|
98
|
+
});
|
|
86
99
|
if (trigger()) trigger().setAttribute('aria-expanded', String(mode === 'popover'));
|
|
87
100
|
var selectedFrame = contextualFrame();
|
|
88
101
|
var connection = selectedFrame && selectedFrame.conversation;
|
|
@@ -119,7 +132,7 @@ function ensureSurface() {
|
|
|
119
132
|
if (!host()) {
|
|
120
133
|
var surface = document.createElement('section');
|
|
121
134
|
surface.id = 'contextual-clay'; surface.setAttribute('role', 'dialog');
|
|
122
|
-
surface.innerHTML = '<header><img class="contextual-clay-avatar" alt=""><div class="contextual-clay-identity"><strong></strong><div class="contextual-context-line"><span data-clay-context></span><details class="contextual-clay-context"><summary aria-label="What this Mate can see" title="What this Mate can see">' + iconHtml('circle-help') + '</summary><p></p></details></div></div><div class="contextual-clay-actions"><button type="button" data-clay-expand>' + iconHtml('arrow-up-right') + '<span></span></button><button type="button" data-clay-close title="Close">' + iconHtml('x') + '</button></div></header><p class="contextual-capabilities" role="note" aria-label="Conversation capabilities"></p><div class="contextual-clay-body"
|
|
135
|
+
surface.innerHTML = '<header><img class="contextual-clay-avatar" alt=""><div class="contextual-clay-identity"><strong></strong><div class="contextual-context-line"><span data-clay-context></span><details class="contextual-clay-context"><summary aria-label="What this Mate can see" title="What this Mate can see">' + iconHtml('circle-help') + '</summary><p></p></details></div></div><div class="contextual-clay-actions"><button type="button" data-clay-expand>' + iconHtml('arrow-up-right') + '<span></span></button><button type="button" data-clay-close title="Close">' + iconHtml('x') + '</button></div></header><p class="contextual-capabilities" role="note" aria-label="Conversation capabilities"></p><div class="contextual-clay-body">' + mateLoadingMarkup() + '</div>';
|
|
123
136
|
surface.addEventListener('contextual-clay-keydown', function (event) {
|
|
124
137
|
var detail = event.detail; var frame = contextualFrame();
|
|
125
138
|
if (!detail || !frame || detail.source !== frame.contentWindow) return;
|
|
@@ -193,7 +206,7 @@ export function initContextualClay() {
|
|
|
193
206
|
initClayControlActivity();
|
|
194
207
|
initContextualWorkspace();
|
|
195
208
|
document.addEventListener('pointerdown', function (event) {
|
|
196
|
-
if (
|
|
209
|
+
if (host() && !host().contains(event.target)) host().querySelectorAll('details[open]').forEach(function (menu) { menu.open = false; });
|
|
197
210
|
});
|
|
198
211
|
document.addEventListener('keydown', function (event) {
|
|
199
212
|
if (event.key === 'Escape' && !event.defaultPrevented && contextualClayMode() === 'popover') {
|
|
@@ -231,7 +244,9 @@ export function initContextualClay() {
|
|
|
231
244
|
var pending = value.contextualMatePending;
|
|
232
245
|
store.set({ contextualMatePending: null }); activateContextualMate(pending); return;
|
|
233
246
|
}
|
|
234
|
-
|
|
247
|
+
// Ordinary navigation refreshes context without replacing the conversation.
|
|
248
|
+
// Direct navigation into a native Mate workspace ends the floating surface.
|
|
249
|
+
if (current.mode === 'popover' && value.currentSlug !== previous.currentSlug && (value.currentSlug || '').indexOf('mate-') === 0) dismissContextualClay(false);
|
|
235
250
|
render();
|
|
236
251
|
});
|
|
237
252
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { contextualFrame } from './contextual-mate-identity.js';
|
|
3
4
|
|
|
4
5
|
function identity(frame) {
|
|
5
6
|
try { return frame && frame.conversation && frame.conversation.identity(); } catch (error) { return null; }
|
|
6
7
|
}
|
|
7
8
|
function contextKey(frame, canonical) {
|
|
8
9
|
var current = identity(frame);
|
|
9
|
-
return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('homeShellVisible'), store.get('dmMode'), store.get('rightWorkbenchOwner'), store.get('homeSubSurface'), store.get('rightWorkbenchRevision'), store.get('myUserId'), store.get('connected'), current && current.mateId, current && current.sessionId, current && (canonical ? current.connected : current.ready), canonical && current && current.sessionOriginId]);
|
|
10
|
+
return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('sessionOriginId'), store.get('activeProjectSlug'), store.get('sessionActivatedProjectSlug'), store.get('replayingHistory'), (store.get('contextualClay') || {}).mode, store.get('homeShellVisible'), store.get('projectsHubVisible'), store.get('dmMode'), store.get('rightWorkbenchOwner'), store.get('homeSubSurface'), store.get('rightWorkbenchRevision'), store.get('myUserId'), store.get('connected'), current && current.mateId, current && current.sessionId, current && (canonical ? current.connected : current.ready), canonical && current && current.sessionOriginId]);
|
|
10
11
|
}
|
|
11
12
|
export function updateMateContext(surface, mate, frame, snapshot) {
|
|
12
13
|
var current = identity(frame), ws = getWs(), key = contextKey(frame, mate && mate.builtinKey === 'clay'), prepared = store.get('contextualPrepared');
|
|
@@ -16,11 +17,12 @@ export function updateMateContext(surface, mate, frame, snapshot) {
|
|
|
16
17
|
capabilities.setAttribute('aria-label', 'Clay capabilities');
|
|
17
18
|
capabilities.textContent = "I can control Clay Studio for you—manage projects, coordinate work, set up schedules, and navigate your workspace. Just tell me what you’d like done.";
|
|
18
19
|
var privateWorkspace = (store.get('currentSlug') || '').indexOf('mate-') === 0;
|
|
19
|
-
var
|
|
20
|
+
var parentReady = store.get('activeProjectSlug') === store.get('currentSlug') && store.get('sessionActivatedProjectSlug') === store.get('currentSlug') && !store.get('replayingHistory');
|
|
21
|
+
var available = (store.get('contextualClay') || {}).mode === 'popover' && parentReady && !canonical && !privateWorkspace && current && current.ready && store.get('connected') && !store.get('homeShellVisible') && !store.get('projectsHubVisible') && !store.get('dmMode') && !store.get('rightWorkbenchOwner') && ws && ws.readyState === 1;
|
|
20
22
|
var changed = !prepared || prepared.key !== key || prepared.socket !== ws;
|
|
21
23
|
if (changed) {
|
|
22
24
|
var requestId = 'context-' + crypto.randomUUID();
|
|
23
|
-
prepared = { key: key, requestId: requestId, socket: ws, mateId: current && current.mateId, status: available ? 'preparing' : 'unavailable' };
|
|
25
|
+
prepared = { key: key, requestId: requestId, socket: ws, mateId: current && current.mateId, canonical: !!canonical, status: available ? 'preparing' : 'unavailable' };
|
|
24
26
|
}
|
|
25
27
|
// Stamp the reference before publishing a new preparation: store subscribers
|
|
26
28
|
// may render synchronously, and the composer must never see the prior view ID.
|
|
@@ -65,7 +67,7 @@ export function updateMateContext(surface, mate, frame, snapshot) {
|
|
|
65
67
|
export function handleMateContextMessage(msg) {
|
|
66
68
|
if (msg.type === 'mate_context_prepared') {
|
|
67
69
|
var pending = store.get('contextualPrepared');
|
|
68
|
-
if (pending && pending.requestId === msg.requestId && pending.socket === getWs()) {
|
|
70
|
+
if (pending && pending.requestId === msg.requestId && pending.socket === getWs() && pending.key === contextKey(contextualFrame(), pending.canonical)) {
|
|
69
71
|
store.set({ contextualPrepared: Object.assign({}, pending, msg, { status: msg.contextRef ? 'ready' : 'unavailable' }) });
|
|
70
72
|
if (msg.contextRef) setTimeout(function () {
|
|
71
73
|
var current = store.get('contextualPrepared');
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { escapeHtml } from './utils.js';
|
|
5
5
|
import { iconHtml } from './icons.js';
|
|
6
6
|
import { getWs } from './ws-ref.js';
|
|
7
|
+
import { store } from './store.js';
|
|
8
|
+
import { currentPaneReview } from './pane-binding.js';
|
|
7
9
|
import { fieldInfo, patternIsBounded, validateForm } from './elicitation-schema.js';
|
|
8
10
|
|
|
9
11
|
var UNSET = "__clay_unset__";
|
|
@@ -154,12 +156,14 @@ function showFormError(container, message) {
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
function send(container, action, content, onSent) {
|
|
159
|
+
if (!currentPaneReview(container)) return false;
|
|
157
160
|
var ws = getWs();
|
|
158
161
|
if (!ws || ws.readyState !== 1) {
|
|
159
162
|
showFormError(container, "Not connected. Try again when the connection is restored.");
|
|
160
163
|
return false;
|
|
161
164
|
}
|
|
162
165
|
var msg = { type: "elicitation_response", requestId: container.dataset.requestId, action: action };
|
|
166
|
+
if (container.paneBinding) msg.paneBinding = container.paneBinding;
|
|
163
167
|
if (action === "accept") msg.content = content;
|
|
164
168
|
ws.send(JSON.stringify(msg));
|
|
165
169
|
setBusy(container, true);
|
|
@@ -177,6 +181,7 @@ function actionButton(label, className, handler) {
|
|
|
177
181
|
|
|
178
182
|
export function buildElicitationCard(msg, onSent) {
|
|
179
183
|
var container = el("div", "permission-container elicitation-container");
|
|
184
|
+
container.paneBinding = store.get('panePendingBinding') || store.get('paneCommittedBinding') || null;
|
|
180
185
|
container.dataset.requestId = msg.requestId;
|
|
181
186
|
var header = el("div", "permission-header");
|
|
182
187
|
header.innerHTML = '<span class="permission-icon">' + iconHtml("key") + '</span>' +
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// github-star.js - Public star count beside the top-bar GitHub Star link.
|
|
2
|
+
//
|
|
3
|
+
// The link itself is static markup. This module only fills in the count read
|
|
4
|
+
// from the server's cached GET /api/github-stars. An unknown count leaves the
|
|
5
|
+
// count segment hidden; nothing is invented, and the module never writes to
|
|
6
|
+
// GitHub or sends anything about the user.
|
|
7
|
+
|
|
8
|
+
import { store } from './store.js';
|
|
9
|
+
|
|
10
|
+
var REFRESH_MS = 60 * 60 * 1000;
|
|
11
|
+
var MOBILE_QUERY = "(max-width: 768px)";
|
|
12
|
+
|
|
13
|
+
var timer = null;
|
|
14
|
+
var inflight = false;
|
|
15
|
+
|
|
16
|
+
// 999 -> "999", 1234 -> "1.2k", 12500 -> "12.5k", 1200000 -> "1.2m"
|
|
17
|
+
export function formatStarCount(n) {
|
|
18
|
+
if (typeof n !== "number" || !isFinite(n) || n < 0) return "";
|
|
19
|
+
n = Math.floor(n);
|
|
20
|
+
if (n < 1000) return String(n);
|
|
21
|
+
var units = [[1000000, "m"], [1000, "k"]];
|
|
22
|
+
for (var i = 0; i < units.length; i++) {
|
|
23
|
+
if (n >= units[i][0]) {
|
|
24
|
+
var v = Math.floor(n / units[i][0] * 10) / 10;
|
|
25
|
+
return (v % 1 === 0 ? String(v) : v.toFixed(1)) + units[i][1];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return String(n);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function render() {
|
|
32
|
+
var count = store.get("githubStarCount");
|
|
33
|
+
var countEl = document.getElementById("github-star-count");
|
|
34
|
+
var linkEl = document.getElementById("github-star-link");
|
|
35
|
+
if (!countEl || !linkEl) return;
|
|
36
|
+
var text = typeof count === "number" ? formatStarCount(count) : "";
|
|
37
|
+
countEl.textContent = text;
|
|
38
|
+
countEl.classList.toggle("hidden", !text);
|
|
39
|
+
linkEl.setAttribute("aria-label", text ? "Star Clay on GitHub, " + count + " stars" : "Star Clay on GitHub");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isMobile() {
|
|
43
|
+
return !!(window.matchMedia && window.matchMedia(MOBILE_QUERY).matches);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function load() {
|
|
47
|
+
if (inflight || isMobile()) return Promise.resolve();
|
|
48
|
+
inflight = true;
|
|
49
|
+
return fetch("/api/github-stars", { credentials: "same-origin" }).then(function (res) {
|
|
50
|
+
return res.ok ? res.json() : null;
|
|
51
|
+
}).then(function (data) {
|
|
52
|
+
// Keep the last known count when the server has none or the call failed.
|
|
53
|
+
if (data && typeof data.stars === "number") {
|
|
54
|
+
store.set({ githubStarCount: data.stars });
|
|
55
|
+
render();
|
|
56
|
+
}
|
|
57
|
+
}).catch(function () {}).then(function () { inflight = false; });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function initGithubStar() {
|
|
61
|
+
if (!document.getElementById("github-star")) return;
|
|
62
|
+
render();
|
|
63
|
+
load();
|
|
64
|
+
if (timer) clearInterval(timer);
|
|
65
|
+
timer = setInterval(load, REFRESH_MS);
|
|
66
|
+
}
|
|
@@ -27,6 +27,14 @@ function canCombine(previous, message) {
|
|
|
27
27
|
// can differ; the first token still supplies the message's display time.
|
|
28
28
|
var keys = Object.keys(previous).concat(Object.keys(message));
|
|
29
29
|
return keys.every(function (key) {
|
|
30
|
+
// Socket JSON gives each event its own activation object. Compare every
|
|
31
|
+
// field, including future metadata, without weakening replay boundaries.
|
|
32
|
+
if (key === 'paneActivation' && previous[key] && message[key]) {
|
|
33
|
+
var before = previous[key], after = message[key];
|
|
34
|
+
return Object.keys(before).concat(Object.keys(after)).every(function (field) {
|
|
35
|
+
return before[field] === after[field];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
30
38
|
return key === 'text' || key === '_ts' || key === '_historyIndex' || key === '_historyIndexes' || previous[key] === message[key];
|
|
31
39
|
});
|
|
32
40
|
}
|
|
@@ -11,7 +11,6 @@ function explicitProjectPath(pathname) {
|
|
|
11
11
|
export function resolveHomeBootDestination(options) {
|
|
12
12
|
if (!options) return "wait";
|
|
13
13
|
if (options.paneMode || explicitProjectPath(options.pathname)) return "project";
|
|
14
|
-
if (/^\/projects\/?$/.test(options.pathname || "")) return "projects";
|
|
15
14
|
// The server-selected project embedded in the root shell is authoritative.
|
|
16
15
|
// Home is entered only through an explicit in-app action; reloading `/`
|
|
17
16
|
// returns to the project workspace even if Home was the last open surface.
|