clay-server 5.2.0-beta.5 → 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 (246) hide show
  1. package/lib/claude-hook-installer.js +1 -267
  2. package/lib/claude-jsonl-watcher.js +1 -204
  3. package/lib/clay-control-presentation-binding.js +96 -0
  4. package/lib/clay-control-presentation.js +128 -0
  5. package/lib/clay-control-target-projection.js +26 -0
  6. package/lib/clay-mate-chat.js +1 -0
  7. package/lib/clay-mate-knowledge.js +1 -1
  8. package/lib/clay-product-control-tools.js +1 -1
  9. package/lib/clay-product-control.js +22 -12
  10. package/lib/clay-project-records.js +2 -1
  11. package/lib/clay-scheduling.js +1 -1
  12. package/lib/clay-screen-controls.js +13 -5
  13. package/lib/clay-skill-install.js +1 -33
  14. package/lib/clay-workspace-controls.js +2 -1
  15. package/lib/cli-sessions.js +1 -300
  16. package/lib/codex-defaults.js +1 -28
  17. package/lib/context-usage.js +3 -2
  18. package/lib/contextual-screen-snapshot.js +38 -0
  19. package/lib/daemon.js +5 -55
  20. package/lib/default-ai-runtime.js +4 -8
  21. package/lib/email-mcp-server.js +1 -1
  22. package/lib/kiro-defaults.js +1 -22
  23. package/lib/mate-context-service.js +138 -0
  24. package/lib/mate-context-tools.js +21 -0
  25. package/lib/mcp-proxy.js +2 -32
  26. package/lib/mcp-query-bridge.js +1 -1
  27. package/lib/mcp-source-adapters.js +1 -91
  28. package/lib/mcp-source-context.js +1 -42
  29. package/lib/mcp-source-discovery.js +1 -169
  30. package/lib/mcp-source-plugins.js +1 -68
  31. package/lib/mcp-source-restrictions.js +1 -47
  32. package/lib/mcp-tool-policy.js +1 -26
  33. package/lib/os-user-diagnostics.js +2 -4
  34. package/lib/os-users.js +4 -65
  35. package/lib/project-connection.js +14 -10
  36. package/lib/project-control-git-view.js +12 -0
  37. package/lib/project-filesystem.js +5 -1
  38. package/lib/project-http.js +7 -4
  39. package/lib/project-mcp.js +2 -2
  40. package/lib/project-notifications.js +5 -4
  41. package/lib/project-pair-result-delivery.js +2 -2
  42. package/lib/project-pair-usage.js +12 -13
  43. package/lib/project-pending-message-queue.js +3 -1
  44. package/lib/project-product-control.js +3 -1
  45. package/lib/project-scheduled-messages.js +4 -4
  46. package/lib/project-scheduled-task-control.js +1 -1
  47. package/lib/project-session-document.js +20 -5
  48. package/lib/project-session-history-actions.js +5 -3
  49. package/lib/project-session-pair.js +1 -1
  50. package/lib/project-session-runtime-actions.js +2 -1
  51. package/lib/project-session-spawn.js +2 -3
  52. package/lib/project-session-vendor.js +2 -1
  53. package/lib/project-sessions.js +18 -26
  54. package/lib/project-shared-browser.js +22 -5
  55. package/lib/project-studio-presentation.js +58 -0
  56. package/lib/project-user-message.js +37 -0
  57. package/lib/project-vendor-login.js +6 -36
  58. package/lib/project-workspace-query.js +11 -1
  59. package/lib/project.js +24 -7
  60. package/lib/public/app.js +25 -0
  61. package/lib/public/appearance-bootstrap.js +2 -0
  62. package/lib/public/css/contextual-clay.css +240 -0
  63. package/lib/public/css/messages.css +6 -2
  64. package/lib/public/modules/app-favicon.js +18 -9
  65. package/lib/public/modules/app-header.js +4 -2
  66. package/lib/public/modules/app-loop-ui.js +2 -0
  67. package/lib/public/modules/app-message-router.js +5 -1
  68. package/lib/public/modules/app-messages.js +50 -6
  69. package/lib/public/modules/app-notifications.js +11 -5
  70. package/lib/public/modules/app-panels.js +61 -123
  71. package/lib/public/modules/app-projects.js +3 -0
  72. package/lib/public/modules/app-rate-limit.js +7 -27
  73. package/lib/public/modules/chat-render-runtime.js +3 -0
  74. package/lib/public/modules/clay-control-activity.js +214 -0
  75. package/lib/public/modules/clay-control-copy.js +36 -0
  76. package/lib/public/modules/clay-control-detail-targets.js +44 -0
  77. package/lib/public/modules/clay-control-object-targets.js +52 -0
  78. package/lib/public/modules/clay-control-target-visibility.js +12 -0
  79. package/lib/public/modules/clay-control-targets.js +44 -0
  80. package/lib/public/modules/clay-navigation-render.js +52 -0
  81. package/lib/public/modules/clay-owner-navigation.js +11 -2
  82. package/lib/public/modules/clay-session-links.js +31 -0
  83. package/lib/public/modules/clay-studio-bridge.js +20 -0
  84. package/lib/public/modules/clay-studio-document.js +67 -0
  85. package/lib/public/modules/clay-studio-render.js +144 -0
  86. package/lib/public/modules/clay-tool-presentation.js +4 -0
  87. package/lib/public/modules/command-palette.js +26 -31
  88. package/lib/public/modules/contextual-ask-session.js +26 -0
  89. package/lib/public/modules/contextual-clay-geometry.js +155 -0
  90. package/lib/public/modules/contextual-clay-session.js +130 -0
  91. package/lib/public/modules/contextual-clay-workspace.js +157 -0
  92. package/lib/public/modules/contextual-clay.js +237 -0
  93. package/lib/public/modules/contextual-composer.js +91 -0
  94. package/lib/public/modules/contextual-mate-context.js +115 -0
  95. package/lib/public/modules/contextual-mate-hint.js +40 -0
  96. package/lib/public/modules/contextual-mate-identity.js +23 -0
  97. package/lib/public/modules/contextual-mate-sessions.js +134 -0
  98. package/lib/public/modules/contextual-search.js +137 -0
  99. package/lib/public/modules/contextual-session-citations.js +58 -0
  100. package/lib/public/modules/filebrowser.js +20 -1
  101. package/lib/public/modules/generation-stop.js +8 -5
  102. package/lib/public/modules/git-panel.js +3 -1
  103. package/lib/public/modules/icon-strip-tooltip.js +2 -1
  104. package/lib/public/modules/input.js +35 -1
  105. package/lib/public/modules/issues-render.js +3 -2
  106. package/lib/public/modules/knowledge-db-workbench.js +4 -4
  107. package/lib/public/modules/markdown.js +2 -34
  108. package/lib/public/modules/mate-knowledge-workbench.js +1 -1
  109. package/lib/public/modules/mcp-connection-view.js +1 -1
  110. package/lib/public/modules/message-delivery.js +10 -0
  111. package/lib/public/modules/pane-bridge.js +2 -2
  112. package/lib/public/modules/pending-message-queue.js +1 -0
  113. package/lib/public/modules/permission-control.js +5 -4
  114. package/lib/public/modules/product-review.js +26 -0
  115. package/lib/public/modules/project-chat-identity.js +13 -4
  116. package/lib/public/modules/project-logs-render.js +3 -0
  117. package/lib/public/modules/project-mate-navigation.js +58 -21
  118. package/lib/public/modules/scheduled-tasks.js +2 -0
  119. package/lib/public/modules/scheduler.js +2 -0
  120. package/lib/public/modules/search-clay-chat.js +5 -454
  121. package/lib/public/modules/session-create-form.js +10 -10
  122. package/lib/public/modules/settings-defaults.js +27 -28
  123. package/lib/public/modules/shared-browser.js +2 -0
  124. package/lib/public/modules/sidebar-mobile.js +4 -3
  125. package/lib/public/modules/sidebar-projects.js +1 -0
  126. package/lib/public/modules/sidebar-sessions.js +1 -0
  127. package/lib/public/modules/split-view.js +7 -16
  128. package/lib/public/modules/terminal.js +9 -0
  129. package/lib/public/modules/tools.js +6 -2
  130. package/lib/public/modules/tui-grab.js +1 -2
  131. package/lib/public/modules/vendor-login-browser.js +5 -3
  132. package/lib/public/modules/vendor-login.js +2 -2
  133. package/lib/public/modules/vendor-priority.js +15 -1
  134. package/lib/public/modules/wireframe-workbench.js +5 -2
  135. package/lib/public/muse-avatar.svg +4 -0
  136. package/lib/public/style.css +2 -0
  137. package/lib/safe-bash-commands.js +3 -45
  138. package/lib/sdk-bridge.js +131 -395
  139. package/lib/sdk-foreground-admission.js +16 -0
  140. package/lib/sdk-mcp-descriptors.js +2 -37
  141. package/lib/sdk-message-processor.js +50 -17
  142. package/lib/sdk-quota-snapshot.js +1 -1
  143. package/lib/sdk-response-recovery.js +1 -155
  144. package/lib/sdk-worker.js +2 -677
  145. package/lib/server-contextual-clay-settings.js +40 -0
  146. package/lib/server-contextual-mate-sessions.js +70 -0
  147. package/lib/server-home-chat.js +7 -1
  148. package/lib/server-home-clay-entry.js +23 -4
  149. package/lib/server-home-clay-session-links.js +12 -0
  150. package/lib/server-owner-ui.js +49 -8
  151. package/lib/server-settings.js +2 -0
  152. package/lib/server.js +18 -1
  153. package/lib/session-fork-history.js +21 -0
  154. package/lib/session-hygiene.js +3 -35
  155. package/lib/session-permission-mode.js +4 -3
  156. package/lib/session-skill-scope.js +11 -0
  157. package/lib/sessions.js +24 -89
  158. package/lib/terminal-manager.js +18 -11
  159. package/lib/tool-llm.js +1 -1
  160. package/lib/tui-transcript-index.js +1 -132
  161. package/lib/users-contextual-clay-preferences.js +31 -0
  162. package/lib/users.js +3 -0
  163. package/lib/vendor-login-browser.js +1 -11
  164. package/lib/workspace-assignment-service.js +25 -0
  165. package/lib/workspace-query-mcp-server.js +1 -1
  166. package/lib/ws-request.js +1 -0
  167. package/lib/ws-schema.js +10 -0
  168. package/lib/yoke/acp-agent-profiles.js +5 -3
  169. package/lib/yoke/adapters/antigravity.js +3 -14
  170. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  171. package/lib/yoke/adapters/claude.js +7 -42
  172. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  173. package/lib/yoke/adapters/codex.js +1 -0
  174. package/lib/yoke/adapters/kiro.js +3 -1
  175. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  176. package/lib/yoke/adapters/muse.js +122 -0
  177. package/lib/yoke/auth-events.js +17 -0
  178. package/lib/yoke/claude-hook-installer.js +271 -0
  179. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  180. package/lib/yoke/claude-maintenance.js +55 -0
  181. package/lib/yoke/claude-permission-patterns.js +53 -0
  182. package/lib/yoke/claude-response-recovery.js +155 -0
  183. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  184. package/lib/yoke/clay-skill-install.js +33 -0
  185. package/lib/yoke/cli-sessions.js +300 -0
  186. package/lib/yoke/codex-app-server.js +7 -12
  187. package/lib/yoke/configuration.js +13 -0
  188. package/lib/yoke/discovery.js +70 -0
  189. package/lib/yoke/errors.js +8 -0
  190. package/lib/yoke/host-environment.js +7 -0
  191. package/lib/yoke/index.js +9 -51
  192. package/lib/yoke/interface.js +116 -0
  193. package/lib/yoke/kiro-acp-server.js +5 -5
  194. package/lib/yoke/legacy-sessions.js +89 -0
  195. package/lib/yoke/login-protocol.js +25 -0
  196. package/lib/yoke/maintenance.js +12 -0
  197. package/lib/yoke/mcp-proxy.js +32 -0
  198. package/lib/yoke/mcp-source-adapters.js +91 -0
  199. package/lib/yoke/mcp-source-context.js +42 -0
  200. package/lib/yoke/mcp-source-discovery.js +169 -0
  201. package/lib/yoke/mcp-source-plugins.js +68 -0
  202. package/lib/yoke/mcp-source-restrictions.js +47 -0
  203. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  204. package/lib/yoke/model-preferences.js +6 -0
  205. package/lib/yoke/muse-artifacts.js +36 -0
  206. package/lib/yoke/muse-controls.js +50 -0
  207. package/lib/yoke/muse-input.js +64 -0
  208. package/lib/yoke/muse-items.js +157 -0
  209. package/lib/yoke/muse-policy.js +52 -0
  210. package/lib/yoke/muse-query.js +428 -0
  211. package/lib/yoke/muse-runtime.js +211 -0
  212. package/lib/yoke/muse-services.js +79 -0
  213. package/lib/yoke/muse-skills.js +79 -0
  214. package/lib/yoke/muse-turns.js +71 -0
  215. package/lib/yoke/options/acp.js +2 -0
  216. package/lib/yoke/options/antigravity.js +2 -0
  217. package/lib/yoke/options/claude.js +21 -0
  218. package/lib/yoke/options/codex-defaults.js +29 -0
  219. package/lib/yoke/options/codex-settings.js +21 -0
  220. package/lib/yoke/options/codex.js +6 -0
  221. package/lib/yoke/options/kiro-defaults.js +22 -0
  222. package/lib/yoke/options/kiro.js +5 -0
  223. package/lib/yoke/options/muse.js +5 -0
  224. package/lib/yoke/provider-contract.js +5 -0
  225. package/lib/yoke/providers/antigravity-executable.js +8 -0
  226. package/lib/yoke/providers/claude-executable.js +40 -0
  227. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  228. package/lib/yoke/providers/claude-process.js +97 -0
  229. package/lib/yoke/providers/claude-provision.js +75 -0
  230. package/lib/yoke/providers/codex-errors.js +8 -0
  231. package/lib/yoke/providers/muse-executable.js +17 -0
  232. package/lib/yoke/provisioning.js +6 -0
  233. package/lib/yoke/query-options.js +10 -0
  234. package/lib/yoke/response-recovery.js +41 -0
  235. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  236. package/lib/yoke/sdk-server-factory.js +7 -0
  237. package/lib/yoke/session-history.js +8 -0
  238. package/lib/yoke/session-tool-transport.js +31 -13
  239. package/lib/yoke/settings.js +23 -0
  240. package/lib/yoke/source-locations.js +4 -0
  241. package/lib/yoke/title-query.js +18 -7
  242. package/lib/yoke/tui-transcript-index.js +132 -0
  243. package/lib/yoke/usage.js +48 -0
  244. package/lib/yoke/user-input.js +13 -0
  245. package/lib/yoke/vendor-registry.js +81 -1
  246. package/package.json +3 -2
@@ -0,0 +1,237 @@
1
+ import { attachContextualSessionCitations } from './contextual-session-citations.js';
2
+ import { attachContextualConversations } from './contextual-mate-sessions.js';
3
+ import { getCachedSessions } from './sidebar-sessions.js';
4
+ import { updateMateContext, recordMateContextSubmission } from './contextual-mate-context.js';
5
+ import { contextualMate, contextualMateStamp, contextualFrame, contextualTrigger } from './contextual-mate-identity.js';
6
+ import { initContextualMateHint } from './contextual-mate-hint.js';
7
+ import { getHomeMateName } from './home-mate-selection.js';
8
+ import { attachContextualGeometry, initContextualGeometry, positionContextualClay } from './contextual-clay-geometry.js';
9
+ import { abandonContextualNativeOrigin, beginContextualNativeOrigin, syncContextualNativeOrigin, finishContextualNativeOrigin, openContextualWorkspace, renderContextualWorkspaceBack, initContextualWorkspace } from './contextual-clay-workspace.js';
10
+ import { mateAvatarUrl } from './avatar.js';
11
+ import { iconHtml, refreshIcons } from './icons.js';
12
+ import { activateContextualMate, returnFromClay } from './project-mate-navigation.js';
13
+ import { initContextualSession } from './contextual-clay-session.js';
14
+ import { initClayControlActivity, clearClayControlActivity } from './clay-control-activity.js';
15
+ // The compact pane stays mounted; native navigation transfers draft ownership.
16
+ import { store } from './store.js';
17
+
18
+ function state() { return store.get('contextualClay') || { mode: 'closed' }; }
19
+ export function contextualClayMode() { return state().mode; }
20
+ function host() { return document.getElementById('contextual-clay'); }
21
+ function trigger() { return contextualTrigger(); }
22
+ export function enterComposer() {
23
+ var surface = host();
24
+ if (!surface || surface.dataset.focusEntry !== 'true') return;
25
+ var frame = contextualFrame();
26
+ if (!frame || frame.inert || !frame.conversation || !frame.conversation.identity().ready) return;
27
+ var input = frame.contentDocument && frame.contentDocument.getElementById('input');
28
+ if (contextualClayMode() !== 'popover' || document.activeElement !== trigger()) { delete surface.dataset.focusEntry; return; }
29
+ if (input && !input.closest('[inert]')) { delete surface.dataset.focusEntry; input.focus({ preventScroll: true }); }
30
+ }
31
+ function escapePopover(event) {
32
+ // Run after native inner menus have had their own Escape handler.
33
+ queueMicrotask(function () {
34
+ if (event.defaultPrevented || contextualClayMode() !== 'popover') return;
35
+ var context = host().querySelector('details[open]');
36
+ if (context) { context.open = false; context.querySelector('summary').focus(); return; }
37
+ event.preventDefault(); dismissContextualClay(true);
38
+ });
39
+ }
40
+
41
+ export function dismissContextualClay(restoreFocus) {
42
+ if (!finishContextualNativeOrigin()) return;
43
+ if (contextualClayMode() === 'opening') { returnFromClay(); return; }
44
+ var surface = host();
45
+ var ownedFocus = surface && (surface.contains(document.activeElement) || document.activeElement === surface);
46
+ var target = store.get('clayReturnTarget');
47
+ var missing = target && target.projectSlug === store.get('currentSlug') && !(store.get('projectsHubList') || []).some(function (project) { return project.slug === target.projectSlug && project.contentAccess !== false; });
48
+ store.set({ contextualClay: Object.assign({}, state(), { mode: 'closed' }) });
49
+ if (missing) returnFromClay();
50
+ // The return context survives a temporary popover dismissal.
51
+ if (restoreFocus && ownedFocus && trigger()) trigger().focus({ preventScroll: true });
52
+ }
53
+
54
+
55
+ function render() {
56
+ var surface = host();
57
+ var mode = contextualClayMode();
58
+ renderContextualWorkspaceBack();
59
+ if (!surface) return;
60
+ surface.hidden = mode === 'closed' || mode === 'workspace';
61
+ surface.dataset.mode = mode;
62
+ positionContextualClay();
63
+ surface.querySelector('[data-clay-expand]').hidden = mode !== 'popover';
64
+ surface.querySelector('[data-clay-close]').hidden = mode !== 'popover' && mode !== 'opening';
65
+ var mate = contextualMate(state().mateId);
66
+ if (mate) {
67
+ var name = getHomeMateName(mate);
68
+ surface.setAttribute('aria-label', name + ' conversation');
69
+ surface.querySelector('strong').textContent = name;
70
+ var header = surface.querySelector('header');
71
+ var mobile = window.matchMedia('(max-width: 768px)').matches;
72
+ header.tabIndex = mobile ? -1 : 0;
73
+ header.setAttribute('aria-label', mobile ? name + ' conversation' : 'Move ' + name + ' conversation. Use arrow keys; hold Shift for larger steps.');
74
+ header.title = mobile ? name : 'Drag to move · Arrow keys when focused';
75
+ surface.querySelectorAll('[data-clay-resize]').forEach(function (handle) { handle.setAttribute('aria-label', 'Resize ' + name + ' conversation. Use arrow keys; hold Shift for larger steps.'); });
76
+ surface.querySelector('.contextual-clay-avatar').src = mateAvatarUrl(mate, 28);
77
+ var expand = surface.querySelector('[data-clay-expand]');
78
+ expand.querySelector('span').textContent = 'Open ' + name;
79
+ expand.setAttribute('aria-label', 'Open ' + name); expand.title = 'Open ' + name + ' workspace';
80
+ surface.querySelector('[data-clay-close]').setAttribute('aria-label', 'Close ' + name + ' conversation');
81
+
82
+ }
83
+ var projectContext = (store.get('projectsHubList') || []).find(function (item) { return item.slug === store.get('currentSlug'); });
84
+ var contextSession = getCachedSessions().find(function (item) { return item.id === store.get('activeSessionId'); });
85
+ updateMateContext(surface, mate, contextualFrame(), { projectTitle: store.get('homeShellVisible') ? 'Home' : projectContext && (projectContext.title || projectContext.project || projectContext.slug), conversationTitle: contextSession && contextSession.title, projectSlug: store.get('homeShellVisible') ? null : store.get('currentSlug'), sessionId: store.get('homeShellVisible') ? null : store.get('activeSessionId'), screen: store.get('homeShellVisible') ? 'Home' : store.get('dmMode') ? 'Direct message' : 'Project', ownerId: store.get('myUserId') || null });
86
+ if (trigger()) trigger().setAttribute('aria-expanded', String(mode === 'popover'));
87
+ var selectedFrame = contextualFrame();
88
+ var connection = selectedFrame && selectedFrame.conversation;
89
+ surface.querySelector('[data-clay-loading]').hidden = !!(connection && connection.identity().ready) || surface.dataset.askPending === 'true';
90
+ surface.querySelectorAll('iframe').forEach(function (frame) { frame.hidden = frame !== selectedFrame; });
91
+ }
92
+
93
+ function createFrame(mate, sessionId) {
94
+ var surface = host();
95
+ var frame = document.createElement('iframe');
96
+ frame.title = getHomeMateName(mate) + ' conversation';
97
+ frame.dataset.mateId = mate.id;
98
+ frame.dataset.ownerId = store.get('myUserId') || '';
99
+ frame.dataset.identityStamp = contextualMateStamp(mate);
100
+ frame.dataset.canonical = String(mate.builtinKey === 'clay');
101
+ frame.hidden = true;
102
+ if (sessionId) frame.dataset.sessionId = String(sessionId);
103
+ var origin = store.get('contextualNativeOrigin');
104
+ frame.src = '/p/' + encodeURIComponent('mate-' + mate.id) + '/?pane=1&contextual=1' + (sessionId ? '&session=' + sessionId : origin && origin.mateId === mate.id ? '&session=' + origin.sessionId : '');
105
+ frame.addEventListener('load', function () {
106
+
107
+ try {
108
+ if (frame.contentWindow.location.pathname !== '/p/' + encodeURIComponent('mate-' + mate.id) + '/') return;
109
+ frame.contentDocument.addEventListener('click', function (event) {
110
+ if (frame === contextualFrame() && event.target.closest('#generation-stop, #autonomous-run-stop, #send-btn.stop')) clearClayControlActivity();
111
+ }, true);
112
+ } catch (error) { /* Authentication owns its document. */ }
113
+ if (frame === contextualFrame()) enterComposer();
114
+ });
115
+ surface.querySelector('.contextual-clay-body').appendChild(frame);
116
+ return frame;
117
+ }
118
+ function ensureSurface() {
119
+ if (!host()) {
120
+ var surface = document.createElement('section');
121
+ 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"><p data-clay-loading role="status">Opening conversation…</p></div>';
123
+ surface.addEventListener('contextual-clay-keydown', function (event) {
124
+ var detail = event.detail; var frame = contextualFrame();
125
+ if (!detail || !frame || detail.source !== frame.contentWindow) return;
126
+ if (detail.keyboardEvent.key === 'Escape' && !detail.keyboardEvent.defaultPrevented && contextualClayMode() === 'popover') escapePopover(detail.keyboardEvent);
127
+ });
128
+ surface.addEventListener('contextual-clay-ready', function () {
129
+ var frame = contextualFrame(), current = state();
130
+ var identity = frame && frame.conversation && frame.conversation.identity();
131
+ if (identity && identity.ready && identity.ownerId === store.get('myUserId') && identity.mateId === current.mateId && !current.sessionId && !current.startingAsk) {
132
+ var selections = Object.assign({}, store.get('contextualSessionSelection')); selections[current.mateId] = identity.sessionId;
133
+ store.set({ contextualSessionSelection: selections, contextualClay: Object.assign({}, current, { sessionId: identity.sessionId }) });
134
+ }
135
+ syncContextualNativeOrigin(); render();
136
+ });
137
+ surface.addEventListener('contextual-clay-availability', render);
138
+ surface.addEventListener('contextual-context-submitted', function (event) { recordMateContextSubmission(surface, event); });
139
+ document.body.appendChild(surface); attachContextualConversations(surface); attachContextualSessionCitations(surface); attachContextualGeometry(surface); refreshIcons();
140
+ surface.querySelector('[data-clay-close]').addEventListener('click', function () { dismissContextualClay(true); });
141
+ surface.querySelector('[data-clay-expand]').addEventListener('click', function () { var mate = contextualMate(state().mateId); if (mate) openContextualWorkspace(mate); });
142
+ }
143
+ }
144
+
145
+ export function showContextualAsk(mate, requestId, sessionId) {
146
+ if (!mate || !contextualMate(mate.id) || store.get('paneMode') || !finishContextualNativeOrigin()) return false;
147
+ ensureSurface();
148
+ if (sessionId && !contextualFrame(mate.id, sessionId)) createFrame(mate, sessionId);
149
+ var selections = Object.assign({}, store.get('contextualSessionSelection'));
150
+ if (sessionId) selections[mate.id] = sessionId;
151
+ store.set({ contextualSessionSelection: selections, contextualClay: { mode: 'popover', mateId: mate.id, sessionId: sessionId || null, startingAsk: sessionId ? null : requestId } });
152
+ host().querySelector('[data-clay-loading]').hidden = !!sessionId;
153
+ if (sessionId && contextualFrame()) contextualFrame().hidden = false;
154
+ return true;
155
+ }
156
+
157
+ export function selectContextualConversation(mate, sessionId, sessionOriginId) {
158
+ var existing = contextualFrame(mate.id, sessionId);
159
+ if (existing && existing.conversation && existing.conversation.identity().sessionOriginId !== sessionOriginId) return false;
160
+ var previous = state(), selections = store.get('contextualSessionSelection');
161
+ if (!showContextualAsk(mate, 'conversation-selection', sessionId)) return false;
162
+ var frame = contextualFrame(mate.id, sessionId);
163
+ if (!frame) return false;
164
+ frame.dataset.expectedSessionOrigin = sessionOriginId;
165
+ if (!beginContextualNativeOrigin(mate)) { store.set({ contextualClay: previous, contextualSessionSelection: selections }); return false; }
166
+ if (frame.conversation) frame.conversation.suspend(false);
167
+ syncContextualNativeOrigin();
168
+ return true;
169
+ }
170
+
171
+ export function cycleContextualClay(mate) {
172
+ if (store.get('paneMode') || !mate || !contextualMate(mate.id)) return;
173
+ var current = state();
174
+ if (current.mode === 'opening') return;
175
+ if (current.mode === 'closed') store.set({ contextualPrepared: null });
176
+ if (current.mode === 'popover' && current.mateId === mate.id) { openContextualWorkspace(mate); return; }
177
+ if (store.get('contextualNativeOrigin') && current.mateId !== mate.id && !finishContextualNativeOrigin()) return;
178
+ if (!store.get('contextualNativeOrigin') && !beginContextualNativeOrigin(mate)) return;
179
+ ensureSurface();
180
+ var frame = contextualFrame(mate.id) || createFrame(mate);
181
+ store.set({ contextualClay: { mode: 'popover', mateId: mate.id, sessionId: frame.dataset.sessionId ? Number(frame.dataset.sessionId) : null } });
182
+ if (frame.conversation) frame.conversation.suspend(false);
183
+ host().dataset.focusEntry = 'true'; syncContextualNativeOrigin(); enterComposer();
184
+ }
185
+
186
+ export function initContextualClay() {
187
+ if (store.get('paneMode')) {
188
+ initContextualSession();
189
+ return;
190
+ }
191
+ initContextualMateHint(activateContextualMate);
192
+ initContextualGeometry();
193
+ initClayControlActivity();
194
+ initContextualWorkspace();
195
+ document.addEventListener('pointerdown', function (event) {
196
+ if (contextualClayMode() === 'popover' && host() && !host().contains(event.target) && !(trigger() && trigger().contains(event.target)) && !event.target.closest('#contextual-mate-hint, .icon-strip-project-mate, .icon-strip-brand')) dismissContextualClay(false);
197
+ });
198
+ document.addEventListener('keydown', function (event) {
199
+ if (event.key === 'Escape' && !event.defaultPrevented && contextualClayMode() === 'popover') {
200
+ escapePopover(event);
201
+ }
202
+ });
203
+ // Keep the same trigger accessible when the desktop rail is hidden.
204
+ var entry = document.querySelector('.icon-strip-brand');
205
+ var rail = entry && entry.parentNode;
206
+ var next = entry && entry.nextSibling;
207
+ var mobile = window.matchMedia('(max-width: 768px)');
208
+ function placeTrigger() {
209
+ if (!entry) return;
210
+ document.body.classList.toggle('contextual-clay-mobile', mobile.matches);
211
+ entry.classList.toggle('contextual-clay-mobile-trigger', mobile.matches);
212
+ if (mobile.matches) document.body.prepend(entry);
213
+ else rail.insertBefore(entry, next && next.parentNode === rail ? next : rail.firstChild);
214
+ render();
215
+ }
216
+ mobile.addEventListener('change', placeTrigger);
217
+ window.addEventListener('resize', function () { if (host()) positionContextualClay(); });
218
+ placeTrigger();
219
+ store.subscribe(function (value, previous) {
220
+ var current = state();
221
+ if (host()) host().querySelectorAll('iframe').forEach(function (frame) {
222
+ var mate = contextualMate(frame.dataset.mateId);
223
+ if (frame.dataset.discarding === 'true') return;
224
+ if (frame.dataset.ownerId !== (value.myUserId || '') || !mate || frame.dataset.identityStamp !== contextualMateStamp(mate)) { frame.dataset.discarding = 'true'; frame.remove(); abandonContextualNativeOrigin(frame.dataset.mateId); }
225
+ else frame.title = getHomeMateName(mate) + ' conversation';
226
+ });
227
+ if (current.mateId && !current.startingAsk && !contextualFrame(current.mateId)) {
228
+ store.set({ contextualClay: { mode: 'closed', mateId: null } }); render(); return;
229
+ }
230
+ if (value.contextualMatePending && !value.contextualWorkspace && !value.pendingProjectSessionNavigation) {
231
+ var pending = value.contextualMatePending;
232
+ store.set({ contextualMatePending: null }); activateContextualMate(pending); return;
233
+ }
234
+ if (current.mode === 'popover' && (value.currentSlug !== previous.currentSlug || value.homeShellVisible !== previous.homeShellVisible || value.dmMode !== previous.dmMode)) dismissContextualClay(false);
235
+ render();
236
+ });
237
+ }
@@ -0,0 +1,91 @@
1
+ import { store } from './store.js';
2
+ import { iconHtml, refreshIcons } from './icons.js';
3
+
4
+ // Reparent the live controls: their permissions, upload and model handlers stay
5
+ // with the production composer. Only contextual panes use this presentation.
6
+ export function initContextualComposer() {
7
+ var row = document.getElementById('input-row');
8
+ var tools = document.getElementById('attach-wrap');
9
+ var config = document.getElementById('config-chip-wrap');
10
+ var model = document.getElementById('config-chip');
11
+ var modelPopover = document.getElementById('config-popover');
12
+ var button = document.createElement('button');
13
+ button.id = 'contextual-attach-btn'; button.type = 'button';
14
+ button.setAttribute('aria-label', 'Add attachments and context');
15
+ button.setAttribute('aria-haspopup', 'dialog');
16
+ button.setAttribute('aria-controls', 'contextual-attach-popover');
17
+ button.innerHTML = iconHtml('plus');
18
+ var menu = document.createElement('div');
19
+ menu.id = 'contextual-attach-popover'; menu.hidden = true;
20
+ menu.setAttribute('role', 'dialog'); menu.setAttribute('aria-label', 'Attachments and context');
21
+ menu.appendChild(tools); row.append(button, config, menu);
22
+ ['attach-file-btn', 'attach-image-btn'].forEach(function (id) {
23
+ var control = document.getElementById(id);
24
+ var label = document.createElement('span');
25
+ label.textContent = id === 'attach-file-btn' ? 'File' : 'Image'; control.appendChild(label);
26
+ control.addEventListener('click', function () { close(true); });
27
+ });
28
+ var contextLabel = document.createElement('span'); contextLabel.textContent = 'Context sources';
29
+ document.getElementById('context-sources-add').appendChild(contextLabel);
30
+ model.setAttribute('aria-label', 'Model and session settings');
31
+ model.setAttribute('aria-haspopup', 'dialog'); model.setAttribute('aria-controls', 'config-popover');
32
+ model.setAttribute('aria-expanded', 'false');
33
+ modelPopover.setAttribute('role', 'dialog'); modelPopover.setAttribute('aria-label', 'Model and session settings');
34
+ function position(popover, anchor) {
35
+ var rect = anchor.getBoundingClientRect();
36
+ popover.style.width = Math.min(280, window.innerWidth - 16) + 'px';
37
+ popover.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - Math.min(280, window.innerWidth - 16) - 8)) + 'px';
38
+ popover.style.bottom = Math.max(8, window.innerHeight - rect.top + 6) + 'px';
39
+ popover.style.maxHeight = Math.max(40, rect.top - 14) + 'px';
40
+ }
41
+ function close(restoreFocus) {
42
+ store.set({ contextualAttachmentMenuOpen: false });
43
+ if (restoreFocus) button.focus({ preventScroll: true });
44
+ }
45
+ function actions() { return Array.from(menu.querySelectorAll('button:not(:disabled)')).filter(function (item) { return item.getClientRects().length; }); }
46
+ button.addEventListener('click', function () {
47
+ var open = !store.get('contextualAttachmentMenuOpen');
48
+ modelPopover.classList.add('hidden'); model.classList.remove('active');
49
+ store.set({ contextualAttachmentMenuOpen: open });
50
+ if (open && actions()[0]) actions()[0].focus({ preventScroll: true });
51
+ });
52
+ document.addEventListener('pointerdown', function (event) {
53
+ if (!menu.contains(event.target) && !button.contains(event.target) && store.get('contextualAttachmentMenuOpen')) close(false);
54
+ });
55
+ document.addEventListener('keydown', function (event) {
56
+ if (event.key === 'Escape' && (!menu.hidden || !modelPopover.classList.contains('hidden'))) {
57
+ event.preventDefault(); event.stopImmediatePropagation();
58
+ var sources = document.getElementById('context-sources-picker');
59
+ if (!menu.hidden && !sources.classList.contains('hidden')) { sources.classList.add('hidden'); document.getElementById('context-sources-add').focus(); }
60
+ else if (!menu.hidden) close(true);
61
+ else { modelPopover.classList.add('hidden'); model.classList.remove('active'); model.focus({ preventScroll: true }); }
62
+ return;
63
+ }
64
+ if (menu.hidden || !menu.contains(event.target) || !/^(ArrowDown|ArrowUp|Home|End)$/.test(event.key)) return;
65
+ var items = actions(), index = items.indexOf(document.activeElement);
66
+ if (!items.length) return;
67
+ event.preventDefault();
68
+ items[event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 : (index + (event.key === 'ArrowUp' ? items.length - 1 : 1)) % items.length].focus();
69
+ }, true);
70
+ menu.addEventListener('focusout', function (event) {
71
+ if (menu.contains(event.relatedTarget) || event.relatedTarget === button) return;
72
+ setTimeout(function () { if (!menu.contains(document.activeElement) && document.activeElement !== button && !menu.hidden) close(false); }, 0);
73
+ });
74
+ model.addEventListener('click', function () { close(false); });
75
+ function placeMenus() { if (!menu.hidden) position(menu, button); if (!modelPopover.classList.contains('hidden')) position(modelPopover, model); }
76
+ new MutationObserver(function () {
77
+ model.setAttribute('aria-expanded', String(!modelPopover.classList.contains('hidden')));
78
+ placeMenus();
79
+ }).observe(modelPopover, { attributes: true, attributeFilter: ['class'] });
80
+ new ResizeObserver(placeMenus).observe(row);
81
+ window.addEventListener('resize', placeMenus);
82
+ store.subscribe(function (value) {
83
+ var open = !!value.contextualAttachmentMenuOpen && value.contextualSessionReady && !value.contextualWriterSuspended;
84
+ menu.hidden = !open; button.setAttribute('aria-expanded', String(open));
85
+ button.disabled = !value.contextualSessionReady || !!value.contextualWriterSuspended;
86
+ if (button.disabled) { modelPopover.classList.add('hidden'); model.classList.remove('active'); }
87
+ placeMenus();
88
+ });
89
+ store.set({ contextualAttachmentMenuOpen: false });
90
+ refreshIcons();
91
+ }
@@ -0,0 +1,115 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+
4
+ function identity(frame) {
5
+ try { return frame && frame.conversation && frame.conversation.identity(); } catch (error) { return null; }
6
+ }
7
+ function contextKey(frame, canonical) {
8
+ 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
+ }
11
+ export function updateMateContext(surface, mate, frame, snapshot) {
12
+ var current = identity(frame), ws = getWs(), key = contextKey(frame, mate && mate.builtinKey === 'clay'), prepared = store.get('contextualPrepared');
13
+ var canonical = mate && mate.builtinKey === 'clay';
14
+ var capabilities = surface.querySelector('.contextual-capabilities');
15
+ capabilities.hidden = !canonical;
16
+ capabilities.setAttribute('aria-label', 'Clay capabilities');
17
+ 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
+ var privateWorkspace = (store.get('currentSlug') || '').indexOf('mate-') === 0;
19
+ var available = !canonical && !privateWorkspace && current && current.ready && store.get('connected') && !store.get('homeShellVisible') && !store.get('dmMode') && !store.get('rightWorkbenchOwner') && ws && ws.readyState === 1;
20
+ var changed = !prepared || prepared.key !== key || prepared.socket !== ws;
21
+ if (changed) {
22
+ var requestId = 'context-' + crypto.randomUUID();
23
+ prepared = { key: key, requestId: requestId, socket: ws, mateId: current && current.mateId, status: available ? 'preparing' : 'unavailable' };
24
+ }
25
+ // Stamp the reference before publishing a new preparation: store subscribers
26
+ // may render synchronously, and the composer must never see the prior view ID.
27
+ snapshot = Object.assign({}, snapshot, { viewId: prepared.requestId });
28
+ surface.dataset.screenReference = JSON.stringify(snapshot);
29
+ if (changed) {
30
+ store.set({ contextualPrepared: prepared });
31
+ if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'mate_context_prepare', requestId: requestId, available: !!available, mateId: current && current.mateId, sessionId: current && current.sessionId }));
32
+ }
33
+ var shared = ((store.get('contextualSubmitted') || {})[mate && mate.id]);
34
+ var ready = prepared.status === 'ready' && prepared.expiresAt > Date.now();
35
+ var label = surface.querySelector('[data-clay-context]');
36
+ var detail = surface.querySelector('.contextual-clay-context p');
37
+ var summary = ready ? prepared.summary.project + ' / ' + prepared.summary.conversation : null;
38
+ var projectName = (ready && prepared.summary.project) || snapshot.projectTitle || snapshot.screen || 'Current view';
39
+ if (canonical) {
40
+ var currentSummary = (snapshot.projectTitle || snapshot.screen || 'Current view') + (snapshot.conversationTitle ? ' / ' + snapshot.conversationTitle : '');
41
+ var disconnected = !store.get('connected') || current && current.connected === false;
42
+ var sameSnapshot = shared && shared.viewId === prepared.requestId && shared.summary.project + (snapshot.conversationTitle ? ' / ' + shared.summary.conversation : '') === currentSummary;
43
+ var contextStatus = disconnected ? 'Disconnected' : current && current.available === false ? 'Unavailable' : !current || !current.ready ? 'Preparing' : sameSnapshot ? 'Shared' : shared ? 'New view' : 'Next message';
44
+ detail.textContent = "I can help manage your projects, coordinate work with Drivers, and use Studio’s tools. I’ll ask you to review actions when needed.\n"
45
+ + 'Current view: ' + currentSummary + '. ' + (disconnected ? 'Reconnect before sharing a new snapshot. ' : !current || !current.ready ? 'Wait for this conversation before sharing a new snapshot. ' : sameSnapshot ? 'This metadata snapshot was shared with your last message. Your next message captures the current view again. ' : 'Your next message includes this metadata snapshot. ')
46
+ + 'Clay receives project and conversation metadata, not continuous screen capture. Its authenticated product tools inspect supported objects separately; changes require their existing review.';
47
+ } else {
48
+ contextStatus = !store.get('connected') || current && current.connected === false ? 'Disconnected' : current && current.available === false ? 'Unavailable' : current && !current.ready || prepared.status === 'preparing' ? 'Preparing' : ready ? (shared && shared.viewId === prepared.requestId ? 'Shared' : shared ? 'New view' : 'Next message') : 'Unavailable';
49
+ detail.textContent = ready ? (shared && shared.viewId === prepared.requestId ? 'Shared with your last message: ' : 'With your next message: ') + summary + '. This Mate can read up to 20 text turns from this selected conversation and request to open it in this window after your review. Tool results, attachments and hidden entries are excluded.' : 'Current view: ' + projectName + '. This view has no supported conversation context. Open an accessible project conversation to share it with your next message.';
50
+ detail.textContent += '\nNo continuous screen capture. Issues, documents, other tabs, files and settings are unavailable through this context.';
51
+ }
52
+ if (prepared.status === 'expired' && !canonical) { contextStatus = 'Expired'; detail.textContent = 'This five-minute context has expired. Close and reopen this Mate to prepare a fresh snapshot. No previous navigation request will replay.'; }
53
+ if (shared) detail.textContent += '\nLast submitted: ' + shared.summary.project + ' / ' + shared.summary.conversation + ((canonical ? sameSnapshot : shared.viewId === prepared.requestId) ? '.' : canonical ? '. The view or connection has changed; that snapshot is stale.' : '. Your view has changed; that context is stale.');
54
+ var projectLabel = label.querySelector('[data-context-project]');
55
+ if (!projectLabel) { projectLabel = document.createElement('span'); projectLabel.dataset.contextProject = ''; var statusLabel = document.createElement('span'); statusLabel.dataset.contextStatus = ''; label.replaceChildren(projectLabel, statusLabel); }
56
+ projectLabel.textContent = projectName;
57
+ label.querySelector('[data-context-status]').textContent = ' · ' + contextStatus;
58
+ label.title = label.textContent;
59
+ surface.contextReference = function (source, session) {
60
+ var live = store.get('contextualPrepared');
61
+ if (!frame || frame.hidden || frame.contentWindow !== source || !session || !current || session.mateId !== current.mateId || session.sessionId !== current.sessionId || !session.ready || !live || live.key !== contextKey(frame, canonical) || live.socket !== getWs() || live.status !== 'ready' || live.expiresAt <= Date.now()) return null;
62
+ return live.contextRef;
63
+ };
64
+ }
65
+ export function handleMateContextMessage(msg) {
66
+ if (msg.type === 'mate_context_prepared') {
67
+ var pending = store.get('contextualPrepared');
68
+ if (pending && pending.requestId === msg.requestId && pending.socket === getWs()) {
69
+ store.set({ contextualPrepared: Object.assign({}, pending, msg, { status: msg.contextRef ? 'ready' : 'unavailable' }) });
70
+ if (msg.contextRef) setTimeout(function () {
71
+ var current = store.get('contextualPrepared');
72
+ if (current && current.contextRef === msg.contextRef) store.set({ contextualPrepared: Object.assign({}, current, { status: 'expired' }) });
73
+ }, Math.max(0, msg.expiresAt - Date.now()));
74
+ }
75
+ return true;
76
+ }
77
+ if ((msg.type === 'message_ack' || msg.type === 'user_message') && window.parent !== window && window.frameElement && window.frameElement.dataset.canonical === 'true') {
78
+ var recorded = msg.type === 'message_ack' ? msg.message : msg;
79
+ var outbound = (store.get('contextualSnapshotSubmissions') || []).find(function (item) { return item.clientMessageId === msg.clientMessageId; });
80
+ if (!outbound || outbound.confirmed || outbound.accountId !== (store.get('myUserId') || '_default') || !recorded || recorded.clientMessageId !== outbound.clientMessageId || !recorded.contextualSnapshot || msg.type === 'message_ack' && (msg.projectSlug !== outbound.projectSlug || String(msg.sessionId) !== String(outbound.sessionId)) || recorded.projectSlug !== outbound.projectSlug || String(recorded.sessionId) !== String(outbound.sessionId) || recorded.sessionOriginId !== outbound.sessionOriginId || recorded.from && recorded.from !== outbound.accountId || JSON.stringify(recorded.contextualSnapshot) !== JSON.stringify(outbound.snapshot)) return false;
81
+ var snapshot = recorded.contextualSnapshot;
82
+ store.set({ contextualSnapshotSubmissions: store.get('contextualSnapshotSubmissions').map(function (item) { return item === outbound ? Object.assign({}, item, { confirmed: true }) : item; }) });
83
+ try {
84
+ var host = window.parent.document.getElementById('contextual-clay');
85
+ if (host) host.dispatchEvent(new window.parent.CustomEvent('contextual-context-submitted', { detail: { source: window, message: { status: 'submitted', canonical: true, clientMessageId: msg.clientMessageId, viewId: snapshot.viewId, summary: { project: snapshot.projectTitle || snapshot.projectSlug || 'Home', conversation: snapshot.conversationTitle || snapshot.screen } } } }));
86
+ } catch (error) { /* A disconnected authentication view cannot share metadata. */ }
87
+ return false;
88
+ }
89
+ if (msg.type !== 'mate_context_shared') return false;
90
+ try {
91
+ var surface = window.parent.document.getElementById('contextual-clay');
92
+ if (surface && window.frameElement && surface.contains(window.frameElement)) surface.dispatchEvent(new window.parent.CustomEvent('contextual-context-submitted', { detail: { source: window, message: msg } }));
93
+ } catch (error) { /* A disconnected authentication view cannot share context. */ }
94
+ return true;
95
+ }
96
+ export function recordMateContextSubmission(surface, event) {
97
+ var frame = Array.from(surface.querySelectorAll('iframe')).find(function (item) { return item.contentWindow === event.detail.source; });
98
+ var pending = store.get('contextualPrepared'), msg = event.detail.message;
99
+ if (!frame || frame.dataset.ownerId !== (store.get('myUserId') || '') || (!msg.canonical && frame.hidden) || !pending) return;
100
+ if (msg.status === 'unavailable') {
101
+ if (pending.mateId === frame.dataset.mateId) store.set({ contextualPrepared: Object.assign({}, pending, { status: 'unavailable' }) });
102
+ return;
103
+ }
104
+ if (msg.status !== 'submitted' || !msg.summary || (msg.canonical ? frame.dataset.canonical !== 'true' || typeof msg.viewId !== 'string' : msg.contextRef !== pending.contextRef)) return;
105
+ var submitted = Object.assign({}, store.get('contextualSubmitted'));
106
+ submitted[frame.dataset.mateId] = { key: msg.canonical ? null : pending.key, viewId: msg.canonical ? msg.viewId : pending.requestId, summary: msg.summary };
107
+ store.set({ contextualSubmitted: submitted });
108
+ }
109
+ export function contextualAccessReference() {
110
+ if (window.parent === window || !store.get('paneMode')) return null;
111
+ try {
112
+ var surface = window.parent.document.getElementById('contextual-clay');
113
+ return surface && surface.contextReference && surface.contextReference(window, { mateId: window.frameElement.dataset.mateId, sessionId: store.get('paneSessionId'), ready: store.get('contextualSessionReady') && store.get('connected') });
114
+ } catch (error) { return null; }
115
+ }
@@ -0,0 +1,40 @@
1
+ import { store } from './store.js';
2
+ import { contextualMate, contextualTrigger } from './contextual-mate-identity.js';
3
+ import { getHomeMateName } from './home-mate-selection.js';
4
+
5
+ export function renderContextualMateHint() {
6
+ var state = store.get('contextualClay') || {};
7
+ var mate = contextualMate(state.mateId);
8
+ var active = state.mode === 'popover' && mate;
9
+ var narrow = window.matchMedia('(max-width: 768px)').matches;
10
+ var moved = document.querySelector('.contextual-mate-mobile-trigger');
11
+ if (moved && (!active || !narrow || moved.dataset.mateId !== mate.id)) {
12
+ moved.classList.remove('contextual-mate-mobile-trigger');
13
+ document.getElementById('icon-strip-projects').appendChild(moved);
14
+ }
15
+ var anchor = active && contextualTrigger(mate.id);
16
+ if (anchor && narrow && mate.builtinKey !== 'clay') {
17
+ anchor.classList.add('contextual-mate-mobile-trigger'); if (anchor.parentNode !== document.body) document.body.appendChild(anchor);
18
+ }
19
+ document.body.classList.toggle('contextual-mate-hint-mobile', !!active && narrow);
20
+ document.body.classList.toggle('contextual-mate-ordinary-mobile', !!active && narrow && mate.builtinKey !== 'clay');
21
+ document.querySelectorAll('[aria-describedby="contextual-mate-hint"]').forEach(function (item) { if (item !== anchor) item.removeAttribute('aria-describedby'); });
22
+ var hint = document.getElementById('contextual-mate-hint');
23
+ if (!hint || !active || !anchor || !anchor.getClientRects().length) { if (hint) hint.hidden = true; return null; }
24
+ anchor.setAttribute('aria-describedby', hint.id);
25
+ var name = getHomeMateName(mate);
26
+ hint.textContent = (window.matchMedia('(pointer: coarse)').matches ? 'Tap' : 'Click') + ' again to open ' + name;
27
+ hint.title = hint.textContent;
28
+ hint.setAttribute('aria-label', 'Open ' + name + ' workspace');
29
+ hint.hidden = false;
30
+ var rect = anchor.getBoundingClientRect();
31
+ hint.style.maxWidth = (narrow ? window.innerWidth - 64 : Math.min(260, window.innerWidth - rect.right - 24)) + 'px';
32
+ hint.style.left = (narrow ? 50 : rect.right + 8) + 'px';
33
+ hint.style.top = (narrow ? 8 : Math.max(8, Math.min(window.innerHeight - 38, rect.top + (rect.height - 28) / 2))) + 'px';
34
+ return hint.getBoundingClientRect();
35
+ }
36
+ export function initContextualMateHint(activate) {
37
+ var hint = document.createElement('button'); hint.type = 'button'; hint.id = 'contextual-mate-hint'; hint.hidden = true;
38
+ hint.addEventListener('click', function () { activate((store.get('contextualClay') || {}).mateId); });
39
+ document.body.appendChild(hint);
40
+ }
@@ -0,0 +1,23 @@
1
+ import { store } from './store.js';
2
+
3
+ export function contextualMate(id) {
4
+ var mate = (store.get('cachedMatesList') || []).find(function (item) { return item && item.id === id; });
5
+ if (!mate || mate.archived || mate.contentAccess === false || mate.accessible === false) return null;
6
+ var owner = store.get('myUserId');
7
+ if (owner && mate.createdBy && mate.createdBy !== owner) return null;
8
+ var project = (store.get('projectsHubList') || []).find(function (item) { return item.slug === 'mate-' + id; });
9
+ if (project && project.contentAccess === false) return null;
10
+ return mate;
11
+ }
12
+ export function contextualMateStamp(mate) { return JSON.stringify([mate.id, mate.createdBy || null, mate.createdAt || null, mate.builtinKey || null]); }
13
+ export function contextualFrame(id, sessionId) {
14
+ var mateId = id || (store.get('contextualClay') || {}).mateId;
15
+ var current = store.get('contextualClay') || {};
16
+ if (!sessionId && current.mateId === mateId && current.startingAsk) return null;
17
+ var selected = sessionId || (current.mateId === mateId && current.sessionId) || (store.get('contextualSessionSelection') || {})[mateId];
18
+ return Array.from(document.querySelectorAll('#contextual-clay iframe')).find(function (frame) { return frame.dataset.mateId === mateId && (!selected || String(selected) === frame.dataset.sessionId); }) || null;
19
+ }
20
+ export function contextualTrigger(id) {
21
+ var mate = contextualMate(id || (store.get('contextualClay') || {}).mateId);
22
+ return mate && mate.builtinKey !== 'clay' ? document.querySelector('.icon-strip-project-mate[data-mate-id="' + CSS.escape(mate.id) + '"]') : document.querySelector('.icon-strip-brand');
23
+ }