clay-server 5.2.0-beta.6 → 5.2.0-beta.7

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 (108) hide show
  1. package/lib/clay-control-presentation-binding.js +96 -0
  2. package/lib/clay-control-presentation.js +128 -0
  3. package/lib/clay-control-target-projection.js +26 -0
  4. package/lib/clay-mate-chat.js +1 -0
  5. package/lib/clay-mate-knowledge.js +1 -1
  6. package/lib/clay-product-control-tools.js +1 -1
  7. package/lib/clay-product-control.js +22 -12
  8. package/lib/clay-project-records.js +2 -1
  9. package/lib/clay-scheduling.js +1 -1
  10. package/lib/clay-screen-controls.js +13 -5
  11. package/lib/clay-workspace-controls.js +2 -1
  12. package/lib/contextual-screen-snapshot.js +38 -0
  13. package/lib/mate-context-service.js +138 -0
  14. package/lib/mate-context-tools.js +21 -0
  15. package/lib/project-connection.js +8 -5
  16. package/lib/project-control-git-view.js +12 -0
  17. package/lib/project-filesystem.js +5 -1
  18. package/lib/project-http.js +3 -0
  19. package/lib/project-notifications.js +5 -4
  20. package/lib/project-product-control.js +3 -1
  21. package/lib/project-scheduled-messages.js +4 -4
  22. package/lib/project-scheduled-task-control.js +1 -1
  23. package/lib/project-session-document.js +20 -5
  24. package/lib/project-shared-browser.js +22 -5
  25. package/lib/project-studio-presentation.js +58 -0
  26. package/lib/project-user-message.js +36 -0
  27. package/lib/project-workspace-query.js +11 -1
  28. package/lib/project.js +24 -7
  29. package/lib/public/app.js +24 -0
  30. package/lib/public/appearance-bootstrap.js +2 -0
  31. package/lib/public/css/contextual-clay.css +240 -0
  32. package/lib/public/modules/app-favicon.js +7 -7
  33. package/lib/public/modules/app-message-router.js +5 -1
  34. package/lib/public/modules/app-messages.js +14 -1
  35. package/lib/public/modules/app-notifications.js +10 -3
  36. package/lib/public/modules/app-projects.js +3 -0
  37. package/lib/public/modules/clay-control-activity.js +214 -0
  38. package/lib/public/modules/clay-control-copy.js +36 -0
  39. package/lib/public/modules/clay-control-detail-targets.js +44 -0
  40. package/lib/public/modules/clay-control-object-targets.js +52 -0
  41. package/lib/public/modules/clay-control-target-visibility.js +12 -0
  42. package/lib/public/modules/clay-control-targets.js +44 -0
  43. package/lib/public/modules/clay-navigation-render.js +52 -0
  44. package/lib/public/modules/clay-owner-navigation.js +11 -2
  45. package/lib/public/modules/clay-session-links.js +31 -0
  46. package/lib/public/modules/clay-studio-bridge.js +20 -0
  47. package/lib/public/modules/clay-studio-document.js +67 -0
  48. package/lib/public/modules/clay-studio-render.js +144 -0
  49. package/lib/public/modules/clay-tool-presentation.js +4 -0
  50. package/lib/public/modules/command-palette.js +26 -31
  51. package/lib/public/modules/contextual-ask-session.js +26 -0
  52. package/lib/public/modules/contextual-clay-geometry.js +155 -0
  53. package/lib/public/modules/contextual-clay-session.js +130 -0
  54. package/lib/public/modules/contextual-clay-workspace.js +157 -0
  55. package/lib/public/modules/contextual-clay.js +237 -0
  56. package/lib/public/modules/contextual-composer.js +91 -0
  57. package/lib/public/modules/contextual-mate-context.js +115 -0
  58. package/lib/public/modules/contextual-mate-hint.js +40 -0
  59. package/lib/public/modules/contextual-mate-identity.js +23 -0
  60. package/lib/public/modules/contextual-mate-sessions.js +134 -0
  61. package/lib/public/modules/contextual-search.js +137 -0
  62. package/lib/public/modules/contextual-session-citations.js +58 -0
  63. package/lib/public/modules/filebrowser.js +20 -1
  64. package/lib/public/modules/generation-stop.js +8 -5
  65. package/lib/public/modules/git-panel.js +3 -1
  66. package/lib/public/modules/icon-strip-tooltip.js +2 -1
  67. package/lib/public/modules/input.js +35 -1
  68. package/lib/public/modules/issues-render.js +3 -2
  69. package/lib/public/modules/knowledge-db-workbench.js +4 -4
  70. package/lib/public/modules/markdown.js +2 -34
  71. package/lib/public/modules/mate-knowledge-workbench.js +1 -1
  72. package/lib/public/modules/message-delivery.js +10 -0
  73. package/lib/public/modules/pane-bridge.js +2 -2
  74. package/lib/public/modules/pending-message-queue.js +1 -0
  75. package/lib/public/modules/product-review.js +26 -0
  76. package/lib/public/modules/project-chat-identity.js +13 -4
  77. package/lib/public/modules/project-logs-render.js +3 -0
  78. package/lib/public/modules/project-mate-navigation.js +58 -21
  79. package/lib/public/modules/scheduled-tasks.js +2 -0
  80. package/lib/public/modules/search-clay-chat.js +5 -454
  81. package/lib/public/modules/shared-browser.js +2 -0
  82. package/lib/public/modules/sidebar-mobile.js +4 -3
  83. package/lib/public/modules/sidebar-projects.js +1 -0
  84. package/lib/public/modules/sidebar-sessions.js +1 -0
  85. package/lib/public/modules/split-view.js +7 -16
  86. package/lib/public/modules/terminal.js +9 -0
  87. package/lib/public/modules/tools.js +4 -1
  88. package/lib/public/modules/wireframe-workbench.js +5 -2
  89. package/lib/public/style.css +2 -0
  90. package/lib/sdk-bridge.js +4 -0
  91. package/lib/server-contextual-clay-settings.js +40 -0
  92. package/lib/server-contextual-mate-sessions.js +70 -0
  93. package/lib/server-home-chat.js +7 -1
  94. package/lib/server-home-clay-entry.js +23 -4
  95. package/lib/server-home-clay-session-links.js +12 -0
  96. package/lib/server-owner-ui.js +49 -8
  97. package/lib/server-settings.js +2 -0
  98. package/lib/server.js +17 -0
  99. package/lib/sessions.js +8 -1
  100. package/lib/terminal-manager.js +18 -11
  101. package/lib/users-contextual-clay-preferences.js +31 -0
  102. package/lib/users.js +3 -0
  103. package/lib/workspace-assignment-service.js +25 -0
  104. package/lib/workspace-query-mcp-server.js +1 -1
  105. package/lib/ws-request.js +1 -0
  106. package/lib/ws-schema.js +10 -0
  107. package/lib/yoke/claude-hook-installer.js +4 -0
  108. package/package.json +1 -1
@@ -1,8 +1,11 @@
1
+ import { observeNavigationRendering } from './clay-navigation-render.js';
2
+ import { dismissContextualClay } from './contextual-clay.js';
3
+ import { showClayNavigationActivity, clearClayControlActivity } from './clay-control-activity.js';
1
4
  import { store } from './store.js';
2
5
  import { getWs } from './ws-ref.js';
3
6
  import { navigateToSession } from './command-palette.js';
4
7
  function context() {
5
- return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('dmMode'), store.get('homeShellVisible'), store.get('homeChatMateId'), store.get('homeChatSessionId'), store.get('homeSubSurface')]);
8
+ return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('dmMode'), store.get('homeShellVisible'), store.get('homeChatMateId'), store.get('homeChatSessionId'), store.get('homeSubSurface'), (store.get('contextualClay') || {}).mode]);
6
9
  }
7
10
  export function handleOwnerNavigation(msg) {
8
11
  if (msg.type !== 'clay_navigation_offer' && msg.type !== 'clay_navigation_apply') return false;
@@ -19,9 +22,15 @@ export function handleOwnerNavigation(msg) {
19
22
  var accepted = offer && offer.id === msg.deliveryId && offer.socket === ws && offer.context === context() && !document.hidden && !store.get('paneMode');
20
23
  var status = 'refused';
21
24
  if (accepted && msg.target && typeof msg.target.projectSlug === 'string' && Number.isSafeInteger(msg.target.sessionId)) {
22
- try { navigateToSession(msg.target); status = 'accepted'; } catch (error) { status = 'failed'; }
25
+ try {
26
+ var contextual = store.get('contextualClay') || {};
27
+ if (msg.target.contextualSourceMateId && contextual.mateId === msg.target.contextualSourceMateId && contextual.mode === 'popover') dismissContextualClay(false);
28
+ showClayNavigationActivity(msg.target, false); navigateToSession(msg.target, null, msg.target.entryIndex); status = 'accepted';
29
+ } catch (error) { showClayNavigationActivity(msg.target, true); status = 'failed'; }
23
30
  }
31
+ if (status === 'refused') clearClayControlActivity();
24
32
  // This acknowledges navigator invocation only. Rendering may finish after reconnect.
25
33
  if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'clay_navigation_ack', deliveryId: msg.deliveryId, status: status }));
34
+ if (status === 'accepted') observeNavigationRendering(msg, ws);
26
35
  return true;
27
36
  }
@@ -0,0 +1,31 @@
1
+ import { parseClaySessionReference, renderClaySessionLink } from './clay-log-links.js';
2
+
3
+ // The prefix is a display compatibility label, never a navigation target.
4
+ var citationPattern = /\[([A-Za-z0-9][A-Za-z0-9_-]{0,79})\/(session:[A-Za-z0-9_-]{24}(?:#entry-[0-9]+)?)(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]/g;
5
+ function matches(text) {
6
+ var pattern = new RegExp(citationPattern.source, 'g'), results = [], match;
7
+ while ((match = pattern.exec(text))) if (parseClaySessionReference(match[2])) results.push({ start: match.index, end: pattern.lastIndex, prefix: match[1], ref: match[2], label: match[3] || '' });
8
+ return results;
9
+ }
10
+ function button(match) {
11
+ var template = document.createElement('template');
12
+ template.innerHTML = renderClaySessionLink(match.ref, match.label);
13
+ return template.content.firstElementChild;
14
+ }
15
+ export function enhanceSessionCitations(root) {
16
+ // Historical assistants sometimes wrapped a whole project-labelled citation
17
+ // in inline code. Only that complete legacy token is a compatibility citation.
18
+ // Canonical code samples, mixed inline code and all fenced code stay literal.
19
+ if (root.closest('.msg-assistant')) root.querySelectorAll('code').forEach(function (code) {
20
+ if (code.closest('pre, a, button')) return;
21
+ var text = code.textContent || '', found = matches(text);
22
+ if (found.length === 1 && found[0].prefix !== 'clayos' && found[0].start === 0 && found[0].end === text.length) code.replaceWith(button(found[0]));
23
+ });
24
+ var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT), nodes = [], node;
25
+ while ((node = walker.nextNode())) if (node.parentElement && !node.parentElement.closest('code, pre, a, button') && matches(node.nodeValue || '').length) nodes.push(node);
26
+ nodes.forEach(function (node) {
27
+ var text = node.nodeValue || '', fragment = document.createDocumentFragment(), offset = 0;
28
+ matches(text).forEach(function (match) { fragment.appendChild(document.createTextNode(text.slice(offset, match.start))); fragment.appendChild(button(match)); offset = match.end; });
29
+ fragment.appendChild(document.createTextNode(text.slice(offset))); node.replaceWith(fragment);
30
+ });
31
+ }
@@ -0,0 +1,20 @@
1
+ import { adoptDocumentSource, presentedDocument, initDocumentSource } from './clay-studio-document.js';
2
+ import { acceptStudioForward, trustedStudioFrame, initStudioReceipts } from './clay-studio-render.js';
3
+ import { presentMarkdownEdit, handleFileChanged } from './filebrowser.js';
4
+ import { openWireframe } from './wireframe-workbench.js';
5
+
6
+ export function initStudioBridge() {
7
+ initStudioReceipts(); initDocumentSource();
8
+ window.addEventListener('message', function (event) {
9
+ var data = event.data;
10
+ if (event.origin === location.origin && data && data.type === 'clay-studio-document-update' && data.message) {
11
+ var view = presentedDocument(data.message.path);
12
+ if (view && view.source === event.source && view.descriptor.token === data.token) handleFileChanged(data.message, view);
13
+ return;
14
+ }
15
+ if (!data || ['clay-pane-present-markdown', 'clay-pane-present-wireframe'].indexOf(data.type) < 0 || !data.message || !data.message.controlPresentation) return;
16
+ if (!trustedStudioFrame(event, data) || !acceptStudioForward(event, data)) return;
17
+ if (data.type === 'clay-pane-present-markdown') { adoptDocumentSource(event, data, trustedStudioFrame(event, data)); presentMarkdownEdit(data.message); }
18
+ else openWireframe(data.message);
19
+ });
20
+ }
@@ -0,0 +1,67 @@
1
+ import { showToast } from './utils.js';
2
+ import { store } from './store.js';
3
+ import { getWs } from './ws-ref.js';
4
+
5
+ function live(view) { var state = store.snap(); return view && state.connected && view.socket === getWs() && view.ownerId === (state.myUserId || null) && view.slug === state.currentSlug && view.sessionId === state.activeSessionId && view.origin === state.sessionOriginId && view.runtime === state.controlRuntimeId; }
6
+ export function forwardDocumentSource(message) {
7
+ var old = store.get('studioDocumentSource');
8
+ if (old) clearTimeout(old.timer);
9
+ if (old && live(old)) old.socket.send(JSON.stringify({ type: 'fs_unwatch' }));
10
+ var state = store.snap(), view = { token: crypto.randomUUID(), socket: getWs(), ownerId: state.myUserId || null, slug: state.currentSlug, sessionId: state.activeSessionId, origin: state.sessionOriginId, runtime: state.controlRuntimeId, path: message.path, expires: Date.now() + 300000 };
11
+ view.timer = setTimeout(function () { if (store.get('studioDocumentSource') === view) { if (live(view)) view.socket.send(JSON.stringify({ type: 'fs_unwatch' })); store.set({ studioDocumentSource: null }); } }, 300000);
12
+ store.set({ studioDocumentSource: view });
13
+ if (live(view)) view.socket.send(JSON.stringify({ type: 'fs_watch', path: view.path }));
14
+ return Object.assign({}, message, { studioDocument: { token: view.token, sourceSlug: view.slug, sourceSessionId: view.sessionId, sourceOrigin: view.origin, runtimeId: view.runtime, expires: view.expires, path: view.path } });
15
+ }
16
+ export function adoptDocumentSource(event, data, frame) {
17
+ var document = data.message && data.message.studioDocument;
18
+ if (!frame || !document || document.path !== data.message.path || document.sourceSlug !== data.studioSource.slug || document.sourceSessionId !== data.studioSource.sessionId || document.sourceOrigin !== data.studioSource.origin || document.runtimeId !== data.studioSource.runtime) return;
19
+ closePresentedDocument();
20
+ var socket = getWs(); if (socket && socket.readyState === 1) socket.send(JSON.stringify({ type: 'fs_unwatch' }));
21
+ store.set({ fileReadRequest: null, pendingFileNavigation: null });
22
+ var view = { descriptor: document, frame: frame, document: frame.contentDocument, source: event.source, owner: store.get('myUserId'), context: store.get('currentSlug') };
23
+ view.timer = setTimeout(function () { if (store.get('studioDocumentView') === view) store.set({ studioDocumentView: { descriptor: view.descriptor, invalidated: true } }); }, Math.max(0, document.expires - Date.now()));
24
+ store.set({ studioDocumentView: view });
25
+ }
26
+ export function presentedDocument(path) {
27
+ var view = store.get('studioDocumentView');
28
+ if (!view || view.invalidated || !view.frame || Date.now() > view.descriptor.expires || view.descriptor.path !== path) return null;
29
+ var bridge = view.frame.clayStudioSource, identity = bridge && bridge.document === view.frame.contentDocument && bridge.window === view.source && bridge.identity();
30
+ return identity && identity.connected && identity.ownerId === view.owner && identity.slug === view.descriptor.sourceSlug && identity.sessionId === view.descriptor.sourceSessionId && identity.origin === view.descriptor.sourceOrigin && identity.runtime === view.descriptor.runtimeId && view.document === view.frame.contentDocument && view.source === view.frame.contentWindow && view.owner === store.get('myUserId') && view.context === store.get('currentSlug') ? view : null;
31
+ }
32
+ export function refreshPresentedDocument(path) {
33
+ var stored = store.get('studioDocumentView');
34
+ if (!stored || stored.descriptor.path !== path) return false;
35
+ var view = presentedDocument(path);
36
+ if (!view) { showToast('The source connection changed. Reopen this document from its conversation.'); return true; }
37
+ view.source.postMessage({ type: 'clay-studio-document-refresh', token: view.descriptor.token }, location.origin);
38
+ return true;
39
+ }
40
+ export function closePresentedDocument() {
41
+ var view = store.get('studioDocumentView');
42
+ if (view) clearTimeout(view.timer);
43
+ if (view && view.frame && view.source === view.frame.contentWindow && view.document === view.frame.contentDocument) view.source.postMessage({ type: 'clay-studio-document-close', token: view.descriptor.token }, location.origin);
44
+ store.set({ studioDocumentView: null });
45
+ }
46
+ export function handleStudioDocumentMessage(message) {
47
+ var view = store.get('studioDocumentSource');
48
+ if (!live(view) || Date.now() > view.expires || message.path !== view.path) return false;
49
+ if (message.type !== 'fs_file_changed' && !(message.type === 'fs_read_result' && message.requestId === view.requestId)) return false;
50
+ window.parent.postMessage({ type: 'clay-studio-document-update', token: view.token, message: message }, location.origin);
51
+ return true;
52
+ }
53
+ export function initDocumentSource() {
54
+ window.addEventListener('message', function (event) {
55
+ var data = event.data, view = store.get('studioDocumentSource');
56
+ if (event.origin !== location.origin || event.source !== window.parent || !data || !view || data.token !== view.token || !live(view)) return;
57
+ if (data.type === 'clay-studio-document-close') { clearTimeout(view.timer); view.socket.send(JSON.stringify({ type: 'fs_unwatch' })); store.set({ studioDocumentSource: null }); }
58
+ if (data.type === 'clay-studio-document-refresh' && Date.now() <= view.expires) {
59
+ view.requestId = 'studio-document-' + crypto.randomUUID();
60
+ view.socket.send(JSON.stringify({ type: 'fs_read', path: view.path, requestId: view.requestId, projectSlug: view.slug, sessionId: String(view.sessionId), accountId: view.ownerId }));
61
+ }
62
+ });
63
+ function stop() { var view = store.get('studioDocumentSource'); if (view) clearTimeout(view.timer); if (live(view)) view.socket.send(JSON.stringify({ type: 'fs_unwatch' })); store.set({ studioDocumentSource: null }); var presented = store.get('studioDocumentView'); closePresentedDocument(); if (presented) store.set({ studioDocumentView: { descriptor: presented.descriptor, invalidated: true } }); }
64
+ store.subscribe(function (state, old) { if (!state.connected && old.connected || state.currentSlug !== old.currentSlug || state.activeSessionId !== old.activeSessionId || state.myUserId !== old.myUserId) stop(); });
65
+ document.addEventListener('contextual-ask-stopped', stop, true);
66
+ document.addEventListener('click', function (event) { if (event.target.closest('#generation-stop, #autonomous-run-stop, #send-btn.stop')) stop(); }, true);
67
+ }
@@ -0,0 +1,144 @@
1
+ import { forwardDocumentSource } from './clay-studio-document.js';
2
+ import { store } from './store.js';
3
+ import { getWs } from './ws-ref.js';
4
+ import { contextualMateStamp } from './contextual-mate-identity.js';
5
+ import { visibleTarget } from './clay-control-target-visibility.js';
6
+
7
+ function validEvidence(evidence) {
8
+ var uuid = /^[a-f0-9-]{36}$/;
9
+ return evidence && evidence.version === 1 && uuid.test(evidence.id) && uuid.test(evidence.token) && /^activity:[a-f0-9-]{36}$/.test(evidence.activityId) && Number.isSafeInteger(evidence.deadline) &&
10
+ evidence.target && ['document', 'sketch', 'browser'].indexOf(evidence.target.surface) >= 0 && typeof evidence.target.key === 'string' && evidence.target.key.length <= 400 && /^[a-f0-9]{64}$/.test(evidence.target.revision);
11
+ }
12
+ function source(evidence, state) {
13
+ return validEvidence(evidence) && evidence.mateId === state.activeProjectMateId && evidence.sourceSlug === 'mate-' + evidence.mateId && evidence.ownerId === (state.myUserId || null) && evidence.runtimeId === state.controlRuntimeId && evidence.sourceSlug === state.currentSlug && evidence.sourceSessionId === state.activeSessionId && evidence.sourceSessionOriginId === state.sessionOriginId &&
14
+ (state.cachedMatesList || []).some(function (mate) { return mate.id === evidence.mateId && mate.builtinKey === 'clay' && !mate.archived && !mate.lifecycleDeletion && contextualMateStamp(mate) === evidence.sourceMateStamp; });
15
+ }
16
+ function currentActivity(evidence) {
17
+ var activity = store.get('clayControlActivity'), forwarded = store.get('clayControlForwarded');
18
+ var message = store.get('paneMode') ? forwarded : activity && activity.message;
19
+ return message && message.id === evidence.activityId && message.mateId === evidence.mateId && message.sourceSlug === evidence.sourceSlug && message.sourceSessionId === evidence.sourceSessionId && message.sourceSessionOriginId === evidence.sourceSessionOriginId && message.sourceMateStamp === evidence.sourceMateStamp && message.sourceQueryGeneration === evidence.sourceQueryGeneration && message.phase !== 'clear' && !(store.get('clayControlReceipts') || []).some(function (receipt) { return receipt.id === evidence.activityId && receipt.stopped; });
20
+ }
21
+ function binding() {
22
+ return { window: window, document: document, identity: function () { var s = store.snap(); return { ownerId: s.myUserId || null, slug: s.currentSlug, sessionId: s.activeSessionId, origin: s.sessionOriginId, connected: s.connected, runtime: s.controlRuntimeId, mateId: s.activeProjectMateId }; } };
23
+ }
24
+ export function forwardStudioPresentation(message, type) {
25
+ if (!store.get('paneMode') || window.parent === window) return false;
26
+ var frame = window.frameElement;
27
+ if (!frame) return false;
28
+ frame.clayStudioSource = binding();
29
+ var evidence = message.controlPresentation;
30
+ if (evidence && (!source(evidence, store.snap()) || !currentActivity(evidence))) return true;
31
+ if (type === 'clay-pane-present-markdown') message = forwardDocumentSource(message);
32
+ var receipts = Object.assign({}, store.get('clayStudioRelays') || {});
33
+ Object.keys(receipts).forEach(function (id) { if (Date.now() > receipts[id].evidence.deadline) delete receipts[id]; });
34
+ if (Object.keys(receipts).length >= 100) return true;
35
+ if (evidence) receipts[evidence.id] = { evidence: evidence, socket: getWs() };
36
+ store.set({ clayStudioRelays: receipts });
37
+ window.parent.postMessage({ type: type, message: message, studioSource: frame.clayStudioSource.identity() }, location.origin);
38
+ return true;
39
+ }
40
+ export function trustedStudioFrame(event, data) {
41
+ if (event.origin !== location.origin || !data || !data.studioSource) return null;
42
+ var frame = Array.from(document.querySelectorAll('#contextual-clay iframe, .split-pane-frame')).find(function (item) { return item.contentWindow === event.source; });
43
+ var bridge = frame && frame.clayStudioSource, live = bridge && bridge.window === frame.contentWindow && bridge.document === frame.contentDocument && bridge.identity();
44
+ var expected = data.studioSource;
45
+ 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
+ if (new URL(frame.src, location.href).pathname !== '/p/' + encodeURIComponent(live.slug) + '/') return null;
47
+ if (frame.classList.contains('split-pane-frame')) {
48
+ var split = store.get('splitPanes');
49
+ if (!split || !split.panes.some(function (pane) { return pane.slug === live.slug && String(pane.sessionId) === String(live.sessionId); })) return null;
50
+ } else {
51
+ var conversation = frame.conversation, identity = conversation && conversation.document === frame.contentDocument && conversation.source === frame.contentWindow && conversation.identity();
52
+ if (!identity || !identity.connected || !identity.available || identity.ownerId !== live.ownerId || identity.sessionId !== live.sessionId || identity.sessionOriginId !== live.origin) return null;
53
+ }
54
+ var evidence = data.message && data.message.controlPresentation;
55
+ if (evidence && (!validEvidence(evidence) || evidence.sourceSlug !== 'mate-' + evidence.mateId || evidence.mateId !== live.mateId || !(store.get('cachedMatesList') || []).some(function (mate) { return mate.id === evidence.mateId && mate.builtinKey === 'clay' && !mate.archived && !mate.lifecycleDeletion && contextualMateStamp(mate) === evidence.sourceMateStamp; }) || evidence.sourceSlug !== live.slug || evidence.sourceSessionId !== live.sessionId || evidence.sourceSessionOriginId !== live.origin || evidence.ownerId !== live.ownerId || evidence.runtimeId !== live.runtime)) return null;
56
+ return frame;
57
+ }
58
+ export function acceptStudioForward(event, data) {
59
+ var frame = trustedStudioFrame(event, data), evidence = data.message && data.message.controlPresentation;
60
+ if (!frame || !evidence || !currentActivity(evidence) || Date.now() > evidence.deadline) return false;
61
+ var entries = Object.assign({}, store.get('clayStudioForwarded') || {});
62
+ Object.keys(entries).forEach(function (id) { if (Date.now() > entries[id].evidence.deadline) delete entries[id]; });
63
+ if (Object.keys(entries).length >= 100) return false;
64
+ entries[evidence.id] = { frame: frame, document: frame.contentDocument, window: event.source, data: data, evidence: evidence };
65
+ store.set({ clayStudioForwarded: entries });
66
+ return true;
67
+ }
68
+ function delivery(evidence) {
69
+ var socket = getWs(), owner = store.get('myUserId'), slug = store.get('currentSlug');
70
+ var relay = (store.get('clayStudioForwarded') || {})[evidence.id];
71
+ return {
72
+ valid: function () {
73
+ if (Date.now() > evidence.deadline || !currentActivity(evidence) || !store.get('connected') || owner !== store.get('myUserId') || slug !== store.get('currentSlug') || getWs() !== socket) return false;
74
+ return relay ? relay.document === relay.frame.contentDocument && relay.window === relay.frame.contentWindow && !!trustedStudioFrame({ origin: location.origin, source: relay.window }, relay.data) : source(evidence, store.snap());
75
+ },
76
+ send: function (visible) {
77
+ var message = { type: 'clay_studio_rendered', id: evidence.id, token: evidence.token, sessionOriginId: evidence.sourceSessionOriginId, revision: evidence.target.revision, visible: visible };
78
+ if (relay) { if (relay.document === relay.frame.contentDocument) relay.window.postMessage({ type: 'clay-studio-receipt', message: message }, location.origin); }
79
+ else if (getWs() === socket && socket && socket.readyState === 1) socket.send(JSON.stringify(message));
80
+ }
81
+ };
82
+ }
83
+ async function digest(value) {
84
+ var hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(value)));
85
+ return Array.from(new Uint8Array(hash)).map(function (byte) { return byte.toString(16).padStart(2, '0'); }).join('');
86
+ }
87
+ // Called only by the actual renderer after content has reached its real product surface.
88
+ export function registerStudioPaint(message, element, ready, contentElement) {
89
+ if (!element) return;
90
+ delete element.dataset.controlSurface;
91
+ var evidence = message.controlPresentation;
92
+ if (!validEvidence(evidence)) return;
93
+ var receipt = delivery(evidence), settled = false, frame, timer, stable = 0;
94
+ var entry = { painted: false, evidence: evidence, target: evidence.target, element: element, valid: receipt.valid };
95
+ element.dataset.controlSurface = evidence.id;
96
+ var surfaces = Object.assign({}, store.get('clayStudioSurfaces') || {});
97
+ Object.keys(surfaces).forEach(function (id) { if (!surfaces[id].element.isConnected || surfaces[id].element === element || Date.now() > surfaces[id].evidence.deadline + 6000) delete surfaces[id]; });
98
+ surfaces[evidence.id] = entry; store.set({ clayStudioSurfaces: surfaces });
99
+ function finish(visible) { if (settled) return; settled = true; clearTimeout(timer); cancelAnimationFrame(frame); document.removeEventListener('visibilitychange', hidden); receipt.send(visible); }
100
+ entry.cancel = function () { finish(false); };
101
+ function hidden() { if (document.hidden) finish(false); }
102
+ document.addEventListener('visibilitychange', hidden);
103
+ timer = setTimeout(function () { finish(false); }, Math.max(0, evidence.deadline - Date.now()));
104
+ var content = evidence.target.surface === 'document' ? { content: message.content, exists: message.exists, size: message.size } : evidence.target.surface === 'sketch' ? message.source : message.data;
105
+ Promise.all([Promise.resolve(ready), digest(content)]).then(function (values) {
106
+ if (values[1] !== evidence.target.revision) { finish(false); return; }
107
+ function inspect() {
108
+ if (settled) return;
109
+ if (!receipt.valid() || document.hidden || element.dataset.controlSurface !== evidence.id) { finish(false); return; }
110
+ stable = painted(contentElement || element) ? stable + 1 : 0;
111
+ if (stable >= 2) { entry.painted = true; finish(true); } else frame = requestAnimationFrame(inspect);
112
+ }
113
+ frame = requestAnimationFrame(inspect);
114
+ }).catch(function () { finish(false); });
115
+ }
116
+ function painted(element) {
117
+ if (!element || !element.isConnected || element.closest('[hidden], [inert], [aria-hidden="true"]')) return false;
118
+ var rect = element.getBoundingClientRect(), style = getComputedStyle(element);
119
+ if (style.visibility !== 'visible' || Number(style.opacity) === 0) return false;
120
+ for (var parent = element.parentElement; parent; parent = parent.parentElement) {
121
+ var ancestorStyle = getComputedStyle(parent);
122
+ if (ancestorStyle.visibility !== 'visible' || Number(ancestorStyle.opacity) === 0) return false;
123
+ }
124
+ var left = Math.max(0, rect.left), right = Math.min(innerWidth, rect.right), top = Math.max(0, rect.top), bottom = Math.min(innerHeight, rect.bottom);
125
+ if (right <= left || bottom <= top) return false;
126
+ return [0.2, 0.5, 0.8].some(function (x) { return [0.2, 0.5, 0.8].some(function (y) { var hit = document.elementFromPoint(left + (right - left) * x, top + (bottom - top) * y); return hit && element.contains(hit); }); });
127
+ }
128
+ export function studioControlTarget(target) {
129
+ var entries = store.get('clayStudioSurfaces') || {};
130
+ var entry = Object.values(entries).find(function (item) { return item.painted && item.target.surface === target.surface && item.target.key === target.key && item.target.revision === target.revision && item.target.runtimeId === target.runtimeId && item.target.sourceSessionOriginId === target.sourceSessionOriginId && item.target.sourceSessionId === target.sourceSessionId && item.target.sourceSlug === target.sourceSlug && item.element.dataset.controlSurface === item.evidence.id && item.valid(); });
131
+ return entry && entry.element;
132
+ }
133
+ export function initStudioReceipts() {
134
+ window.addEventListener('message', function (event) {
135
+ if (event.origin !== location.origin || event.source !== window.parent || !event.data || event.data.type !== 'clay-studio-receipt') return;
136
+ var message = event.data.message, entries = Object.assign({}, store.get('clayStudioRelays') || {}), entry = message && entries[message.id];
137
+ if (!entry || entry.evidence.token !== message.token || !source(entry.evidence, store.snap()) || getWs() !== entry.socket || entry.socket.readyState !== 1 || Date.now() > entry.evidence.deadline) return;
138
+ delete entries[message.id]; store.set({ clayStudioRelays: entries }); entry.socket.send(JSON.stringify(message));
139
+ });
140
+ function stop() { Object.values(store.get('clayStudioSurfaces') || {}).forEach(function (entry) { delete entry.element.dataset.controlSurface; if (entry.cancel) entry.cancel(); }); store.set({ clayStudioRelays: {}, clayStudioForwarded: {}, clayStudioSurfaces: {} }); }
141
+ document.addEventListener('contextual-ask-stopped', stop, true);
142
+ document.addEventListener('click', function (event) { if (event.target.closest('#generation-stop, #autonomous-run-stop, #send-btn.stop')) stop(); }, true);
143
+ store.subscribe(function (state, prior) { if (!state.connected && prior.connected || state.currentSlug !== prior.currentSlug || state.activeSessionId !== prior.activeSessionId || state.myUserId !== prior.myUserId) stop(); });
144
+ }
@@ -153,6 +153,10 @@ rows('clay-email', [
153
153
  ]);
154
154
  rows('clay-history', [['search_clay_history', 'Searching conversations', 'Conversations searched', 'Conversation search', 'query'], ['read_session', 'Reading conversation', 'Conversation read', 'Conversation'], ['list_recent_decisions', 'Checking recent decisions', 'Recent decisions checked', 'Recent decisions']]);
155
155
  rows('clay-workspace', [
156
+ ['read_current_context', 'Checking shared context', 'Shared context checked', 'Shared context'],
157
+ ['read_selected_context', 'Reading selected conversation', 'Selected conversation read', 'Selected conversation'],
158
+ ['open_context_conversation', 'Requesting conversation navigation', 'Navigation request returned', 'Conversation navigation', 'why'],
159
+ ['read_context_navigation_receipt', 'Checking navigation receipt', 'Navigation receipt checked', 'Navigation receipt'],
156
160
  ['read_owner_screen', 'Checking your screen context', 'Screen context request returned', 'Screen context'],
157
161
  ['inspect_owner_screen_change', 'Inspecting screen change', 'Screen change inspected', 'Screen control'],
158
162
  ['control_owner_screen', 'Requesting screen change', 'Screen change request returned', 'Screen control', 'why'],
@@ -1,8 +1,10 @@
1
+ import { forwardContextualSessionCitation, validContextualCitation, contextualCitationLink } from './contextual-session-citations.js';
2
+ import { dismissContextualClay } from './contextual-clay.js';
1
3
  import { mateAvatarUrl } from './avatar.js';
2
4
  import { escapeHtml } from './utils.js';
3
5
  import { refreshIcons } from './icons.js';
4
6
  import { openSearch as openSessionSearch } from './session-search.js';
5
- import { startSearchClayChat, attachSearchClayChat, detachSearchClayChat, getSearchClayChatSummary } from './search-clay-chat.js';
7
+ import { startSearchClayChat, retrySearchClayAsk } from './search-clay-chat.js';
6
8
  import { getWs } from './ws-ref.js';
7
9
  import { openProjectLog } from './project-logs.js';
8
10
  import { store } from './store.js';
@@ -29,7 +31,7 @@ var paletteEl = null, inputEl = null, resultsEl = null, footerEl = null;
29
31
  var activeIndex = -1, items = [], debounceTimer = null, abortCtrl = null;
30
32
  var pendingNav = null, cachedHomeData = [], cachedVersion = null;
31
33
  var pendingLogNav = null;
32
- var searchResults = [], searchQuery = "", searchPending = false, chatMode = false;
34
+ var searchResults = [], searchQuery = "", searchPending = false;
33
35
 
34
36
  export function initCommandPalette(value) {
35
37
  ctx = value;
@@ -62,6 +64,10 @@ function handleClaySessionLinkClick(event) {
62
64
  openProjectLog(link.dataset.logRef);
63
65
  return;
64
66
  }
67
+ if (link.classList.contains('clayos-session-link') && forwardContextualSessionCitation(link)) return;
68
+ requestClayRecordLink(link);
69
+ }
70
+ export function requestClayRecordLink(link, citation) {
65
71
  var ws = getWs();
66
72
  if (!ws || ws.readyState !== 1) {
67
73
  link.classList.add("is-error");
@@ -74,7 +80,7 @@ function handleClaySessionLinkClick(event) {
74
80
  else link.dataset.sessionRequest = requestId;
75
81
  if (!isLog) store.set({ sessionNavigationRequestId: requestId, sessionNavigationRequestContext: {
76
82
  projectSlug: store.get('currentSlug'),
77
- sessionId: store.get('activeSessionId')
83
+ sessionId: store.get('activeSessionId'), citation: citation || null
78
84
  } });
79
85
  link.classList.remove("is-error");
80
86
  link.classList.add("is-loading");
@@ -85,19 +91,24 @@ function handleClaySessionLinkClick(event) {
85
91
  } else {
86
92
  request.type = "home_clay_session_resolve";
87
93
  request.sessionRef = link.dataset.sessionRef;
94
+ if (citation) request.contextualSource = { mateId: citation.mateId, sessionId: citation.sessionId, sessionOriginId: citation.originId, mateStamp: citation.stamp };
88
95
  if (link.dataset.sessionEntryIndex !== undefined) request.entryIndex = Number(link.dataset.sessionEntryIndex);
89
96
  }
90
97
  ws.send(JSON.stringify(request));
98
+ return true;
91
99
  }
92
100
 
93
101
  export function handleClaySessionTarget(msg) {
94
102
  if (!msg || (msg.type !== "home_clay_session_target" && msg.type !== "home_clay_log_target")) return false;
95
103
  var isLog = msg.type === "home_clay_log_target";
96
104
  var links = document.querySelectorAll(isLog ? ".clayos-log-link[data-log-request]" : ".clayos-session-link[data-session-request]");
97
- var link = null;
105
+ var sourceContext = store.get('sessionNavigationRequestContext');
106
+ var citation = !isLog && sourceContext && sourceContext.citation;
107
+ var link = !isLog ? contextualCitationLink(citation, msg.requestId) : null;
98
108
  for (var i = 0; i < links.length; i++) {
99
109
  if ((isLog ? links[i].dataset.logRequest : links[i].dataset.sessionRequest) === msg.requestId) link = links[i];
100
110
  }
111
+ if (!isLog && link && link.ownerDocument !== document && !citation) { link.classList.remove('is-loading'); link.classList.add('is-error'); link.title = 'The view changed. Click again from the current conversation.'; delete link.dataset.sessionRequest; return true; }
101
112
  if (!isLog && store.get('sessionNavigationRequestId') !== msg.requestId) {
102
113
  if (link) {
103
114
  link.classList.remove("is-loading");
@@ -107,6 +118,7 @@ export function handleClaySessionTarget(msg) {
107
118
  }
108
119
  if (!isLog) {
109
120
  var requestContext = store.get('sessionNavigationRequestContext');
121
+ if (citation && (!validContextualCitation(citation) || msg.sessionRef !== citation.ref)) { if (link) { link.classList.remove('is-loading'); link.classList.add('is-error'); link.title = 'The view changed. Click again from the current conversation.'; delete link.dataset.sessionRequest; } store.set({ pendingSessionEntryNavigation: null, sessionNavigationRequestContext: null }); return true; }
110
122
  if (requestContext && (String(store.get('currentSlug')) !== String(requestContext.projectSlug) || String(store.get('activeSessionId')) !== String(requestContext.sessionId))) {
111
123
  if (link) {
112
124
  link.classList.remove("is-loading");
@@ -134,6 +146,7 @@ export function handleClaySessionTarget(msg) {
134
146
  navigateToLog(msg.target);
135
147
  return true;
136
148
  }
149
+ if (citation) { dismissContextualClay(false); if ((store.get('contextualClay') || {}).mode !== 'closed') return true; }
137
150
  navigateToSession({ projectSlug: msg.target.projectSlug, sessionId: msg.target.sessionId }, null, msg.target.entryIndex);
138
151
  return true;
139
152
  }
@@ -146,11 +159,6 @@ export function openCommandPalette() {
146
159
  var trigger = document.getElementById("cmd-palette-btn");
147
160
  if (trigger) trigger.style.visibility = "hidden";
148
161
  updateFooter();
149
- if (attachSearchClayChat(resultsEl, showSearch, closeCommandPalette)) {
150
- chatMode = true;
151
- paletteEl.classList.add("is-chatting");
152
- return;
153
- }
154
162
  showSearch();
155
163
  fetchHomeData();
156
164
  }
@@ -162,7 +170,6 @@ export function closeCommandPalette() {
162
170
  if (trigger) trigger.style.visibility = "";
163
171
  if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }
164
172
  if (abortCtrl) { abortCtrl.abort(); abortCtrl = null; }
165
- detachSearchClayChat();
166
173
  }
167
174
 
168
175
  export function setPaletteVersion(version) { cachedVersion = version; }
@@ -289,8 +296,6 @@ function buildDOM() {
289
296
  }
290
297
 
291
298
  function showSearch() {
292
- chatMode = false;
293
- paletteEl.classList.remove("is-chatting");
294
299
  inputEl.value = searchQuery;
295
300
  inputEl.disabled = false;
296
301
  inputEl.focus({ preventScroll: true });
@@ -354,7 +359,6 @@ function sessionIcon(session) {
354
359
  }
355
360
 
356
361
  function renderSearch() {
357
- if (chatMode) return;
358
362
  var query = searchQuery.toLowerCase();
359
363
  var sessions = query ? cachedHomeData.filter(function (item) {
360
364
  return (item.sessionTitle || "").toLowerCase().indexOf(query) !== -1 || (item.projectTitle || "").toLowerCase().indexOf(query) !== -1;
@@ -364,11 +368,10 @@ function renderSearch() {
364
368
  for (var r = 0; r < searchResults.length; r++) if (!seen[sessionKey(searchResults[r])]) { sessions.push(searchResults[r]); seen[sessionKey(searchResults[r])] = true; }
365
369
  items = [];
366
370
  var html = "", index = 0;
367
- var clayChat = getSearchClayChatSummary();
368
- if (clayChat) {
369
- items.push({ type: "resume-clay" });
370
- html += '<div class="cmd-palette-group-label">Current Clay chat</div>';
371
- html += renderItem(index++, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', "Return to Clay", escapeHtml(clayChat.processing ? "Clay is responding" : "Continue your current conversation"), clayChat.preview ? escapeHtml(clayChat.preview) : null, clayChat.updatedAt, " is-resume-clay");
371
+ var pendingAsk = store.get('contextualSearchAsk');
372
+ if (!query && pendingAsk && pendingAsk.ownerId === store.get('myUserId') && !pendingAsk.clientMessageId && /^(error|stopped)$/.test(pendingAsk.status)) {
373
+ items.push({ type: 'retry-clay-ask' });
374
+ html += renderItem(index++, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', 'Retry previous question', escapeHtml(pendingAsk.text), null, null, ' is-retry-clay-ask');
372
375
  }
373
376
  if (sessions.length) {
374
377
  html += '<div class="cmd-palette-group-label">' + (query ? "Conversations" : "Recent conversations") + '</div>';
@@ -383,8 +386,8 @@ function renderSearch() {
383
386
  if (query) {
384
387
  items.push({ type: "ask-clay", query: searchQuery });
385
388
  if (!sessions.length && !searchPending) html += '<div class="cmd-palette-exact-empty">No exact matches. Clay can search by meaning.</div>';
386
- html += '<div class="cmd-palette-group-label">' + (clayChat ? "New Clay chat" : "Ask Clay") + '</div>';
387
- html += renderItem(index, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', (clayChat ? 'Start a new Clay chat about “' : 'Ask Clay about “') + escapeHtml(searchQuery) + '”', "Search by meaning across your projects and conversations", null, null, " is-ask-clay");
389
+ html += '<div class="cmd-palette-group-label">Ask Clay</div>';
390
+ html += renderItem(index, '<img src="/clay-studio-symbol.png" width="22" height="22" alt="">', 'Ask Clay about “' + escapeHtml(searchQuery) + '”', "Start a new Clay conversation", null, null, " is-ask-clay");
388
391
  }
389
392
  if (!html) html = '<div class="cmd-palette-empty">No conversations yet.</div>';
390
393
  resultsEl.innerHTML = html;
@@ -417,20 +420,12 @@ function setActive(index, skipScroll) {
417
420
  }
418
421
 
419
422
  function beginClayChat(query) {
420
- if (chatMode) return;
421
- chatMode = true;
422
- paletteEl.classList.add("is-chatting");
423
- startSearchClayChat(query, resultsEl, showSearch, closeCommandPalette);
424
- }
425
-
426
- function resumeClayChat() {
427
- if (!attachSearchClayChat(resultsEl, showSearch, closeCommandPalette)) return;
428
- chatMode = true;
429
- paletteEl.classList.add("is-chatting");
423
+ if (!isCommandPaletteOpen()) return;
424
+ if (startSearchClayChat(query)) closeCommandPalette();
430
425
  }
431
426
 
432
427
  function activateItem(entry) {
433
- if (entry.type === "resume-clay") { resumeClayChat(); return; }
428
+ if (entry.type === 'retry-clay-ask') { if (retrySearchClayAsk()) closeCommandPalette(); return; }
434
429
  if (entry.type === "ask-clay") { beginClayChat(entry.query); return; }
435
430
  if (entry.type === "mate-session" || entry.type === "session") navigateToSession(entry.data, entry.query);
436
431
  }
@@ -0,0 +1,26 @@
1
+ import { store } from './store.js';
2
+ import { sendTextMessage } from './input.js';
3
+ import { pausePendingMessage } from './message-delivery.js';
4
+ import { stopGeneration } from './generation-stop.js';
5
+
6
+ export function submitContextualAsk(request) {
7
+ if (!request || typeof request.requestId !== 'string' || typeof request.text !== 'string') return null;
8
+ var current = store.get('contextualAskSubmission');
9
+ if (current && current.requestId === request.requestId) return current;
10
+ if (!request || request.ownerId !== store.get('myUserId') || request.sessionId !== store.get('paneSessionId') || request.sessionOriginId !== store.get('sessionOriginId') || !store.get('contextualSessionReady') || !store.get('connected') || store.get('contextualWriterSuspended') || !window.frameElement || window.frameElement.dataset.canonical !== 'true') return null;
11
+ // A fresh session has no user draft to replace. Refuse any ambiguous target.
12
+ if (document.getElementById('input').value || (store.get('pendingOutboundMessages') || []).length) return null;
13
+ store.set({ contextualAskSubmission: { requestId: request.requestId, status: 'sending' } });
14
+ sendTextMessage(request.text);
15
+ var outbound = (store.get('pendingOutboundMessages') || []).find(function (item) { return item.payload.text === request.text && item.sessionId === request.sessionId; });
16
+ var submission = { requestId: request.requestId, status: outbound ? 'sent' : 'unconfirmed', clientMessageId: outbound && outbound.payload.clientMessageId };
17
+ store.set({ contextualAskSubmission: submission });
18
+ return submission;
19
+ }
20
+
21
+ export function pauseContextualAsk() {
22
+ var current = store.get('contextualAskSubmission');
23
+ if (current && current.clientMessageId) pausePendingMessage(current.clientMessageId);
24
+ if (current) store.set({ contextualAskSubmission: Object.assign({}, current, { status: 'stopped' }) });
25
+ }
26
+ export function stopContextualAsk() { pauseContextualAsk(); return stopGeneration(); }