clay-server 5.2.0-beta.6 → 5.2.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/clay-control-presentation-binding.js +96 -0
- package/lib/clay-control-presentation.js +128 -0
- package/lib/clay-control-target-projection.js +26 -0
- package/lib/clay-mate-chat.js +1 -0
- package/lib/clay-mate-knowledge.js +1 -1
- package/lib/clay-product-control-tools.js +1 -1
- package/lib/clay-product-control.js +22 -12
- package/lib/clay-project-records.js +2 -1
- package/lib/clay-scheduling.js +1 -1
- package/lib/clay-screen-controls.js +13 -5
- package/lib/clay-workspace-controls.js +2 -1
- package/lib/contextual-screen-snapshot.js +38 -0
- package/lib/mate-context-service.js +138 -0
- package/lib/mate-context-tools.js +21 -0
- package/lib/project-connection.js +8 -5
- package/lib/project-control-git-view.js +12 -0
- package/lib/project-filesystem.js +5 -1
- package/lib/project-http.js +3 -0
- package/lib/project-notifications.js +5 -4
- package/lib/project-product-control.js +3 -1
- package/lib/project-scheduled-messages.js +4 -4
- package/lib/project-scheduled-task-control.js +1 -1
- package/lib/project-session-document.js +20 -5
- package/lib/project-shared-browser.js +22 -5
- package/lib/project-studio-presentation.js +58 -0
- package/lib/project-user-message.js +36 -0
- package/lib/project-workspace-query.js +11 -1
- package/lib/project.js +24 -7
- package/lib/public/app.js +24 -0
- package/lib/public/appearance-bootstrap.js +2 -0
- package/lib/public/css/contextual-clay.css +240 -0
- package/lib/public/modules/app-favicon.js +7 -7
- package/lib/public/modules/app-message-router.js +5 -1
- package/lib/public/modules/app-messages.js +14 -1
- package/lib/public/modules/app-notifications.js +10 -3
- package/lib/public/modules/app-projects.js +3 -0
- package/lib/public/modules/clay-control-activity.js +214 -0
- package/lib/public/modules/clay-control-copy.js +36 -0
- package/lib/public/modules/clay-control-detail-targets.js +44 -0
- package/lib/public/modules/clay-control-object-targets.js +52 -0
- package/lib/public/modules/clay-control-target-visibility.js +12 -0
- package/lib/public/modules/clay-control-targets.js +44 -0
- package/lib/public/modules/clay-navigation-render.js +52 -0
- package/lib/public/modules/clay-owner-navigation.js +11 -2
- package/lib/public/modules/clay-session-links.js +31 -0
- package/lib/public/modules/clay-studio-bridge.js +20 -0
- package/lib/public/modules/clay-studio-document.js +67 -0
- package/lib/public/modules/clay-studio-render.js +144 -0
- package/lib/public/modules/clay-tool-presentation.js +4 -0
- package/lib/public/modules/command-palette.js +26 -31
- package/lib/public/modules/contextual-ask-session.js +26 -0
- package/lib/public/modules/contextual-clay-geometry.js +155 -0
- package/lib/public/modules/contextual-clay-session.js +130 -0
- package/lib/public/modules/contextual-clay-workspace.js +157 -0
- package/lib/public/modules/contextual-clay.js +237 -0
- package/lib/public/modules/contextual-composer.js +91 -0
- package/lib/public/modules/contextual-mate-context.js +115 -0
- package/lib/public/modules/contextual-mate-hint.js +40 -0
- package/lib/public/modules/contextual-mate-identity.js +23 -0
- package/lib/public/modules/contextual-mate-sessions.js +134 -0
- package/lib/public/modules/contextual-search.js +137 -0
- package/lib/public/modules/contextual-session-citations.js +58 -0
- package/lib/public/modules/filebrowser.js +20 -1
- package/lib/public/modules/generation-stop.js +8 -5
- package/lib/public/modules/git-panel.js +3 -1
- package/lib/public/modules/icon-strip-tooltip.js +2 -1
- package/lib/public/modules/input.js +35 -1
- package/lib/public/modules/issues-render.js +3 -2
- package/lib/public/modules/knowledge-db-workbench.js +4 -4
- package/lib/public/modules/markdown.js +2 -34
- package/lib/public/modules/mate-knowledge-workbench.js +1 -1
- package/lib/public/modules/message-delivery.js +10 -0
- package/lib/public/modules/pane-bridge.js +2 -2
- package/lib/public/modules/pending-message-queue.js +1 -0
- package/lib/public/modules/product-review.js +26 -0
- package/lib/public/modules/project-chat-identity.js +13 -4
- package/lib/public/modules/project-logs-render.js +3 -0
- package/lib/public/modules/project-mate-navigation.js +58 -21
- package/lib/public/modules/scheduled-tasks.js +2 -0
- package/lib/public/modules/search-clay-chat.js +5 -454
- package/lib/public/modules/shared-browser.js +2 -0
- package/lib/public/modules/sidebar-mobile.js +4 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-sessions.js +1 -0
- package/lib/public/modules/split-view.js +7 -16
- package/lib/public/modules/stt.js +7 -2
- package/lib/public/modules/terminal.js +9 -0
- package/lib/public/modules/tools.js +4 -1
- package/lib/public/modules/wireframe-workbench.js +5 -2
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +4 -0
- package/lib/server-contextual-clay-settings.js +40 -0
- package/lib/server-contextual-mate-sessions.js +70 -0
- package/lib/server-home-chat.js +7 -1
- package/lib/server-home-clay-entry.js +23 -4
- package/lib/server-home-clay-session-links.js +12 -0
- package/lib/server-owner-ui.js +49 -8
- package/lib/server-settings.js +2 -0
- package/lib/server.js +17 -0
- package/lib/sessions.js +10 -3
- package/lib/terminal-manager.js +18 -11
- package/lib/users-contextual-clay-preferences.js +31 -0
- package/lib/users.js +3 -0
- package/lib/workspace-assignment-service.js +25 -0
- package/lib/workspace-query-mcp-server.js +1 -1
- package/lib/ws-request.js +1 -0
- package/lib/ws-schema.js +10 -0
- package/lib/yoke/claude-hook-installer.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { contextualTrigger } from './contextual-mate-identity.js';
|
|
2
|
+
import { renderContextualMateHint } from './contextual-mate-hint.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { showToast } from './utils.js';
|
|
5
|
+
|
|
6
|
+
var endpoint = '/api/user/contextual-clay-geometry';
|
|
7
|
+
function state() { return store.get('contextualClayGeometry') || {}; }
|
|
8
|
+
function surface() { return document.getElementById('contextual-clay'); }
|
|
9
|
+
function narrow() { return window.matchMedia('(max-width: 768px)').matches; }
|
|
10
|
+
function rectValue(rect) { return { left: rect.left, top: rect.top, width: rect.width, height: rect.height }; }
|
|
11
|
+
function bounded(value) {
|
|
12
|
+
return { left: Math.round(Math.max(0, Math.min(16384, value.left))), top: Math.round(Math.max(0, Math.min(16384, value.top))), width: Math.round(Math.max(340, Math.min(2560, value.width))), height: Math.round(Math.max(360, Math.min(2160, value.height))) };
|
|
13
|
+
}
|
|
14
|
+
function setState(patch) { store.set({ contextualClayGeometry: Object.assign({}, state(), patch) }); }
|
|
15
|
+
function defaultGeometry() {
|
|
16
|
+
var trigger = contextualTrigger();
|
|
17
|
+
var anchor = trigger && trigger.getBoundingClientRect();
|
|
18
|
+
return { left: anchor ? anchor.right + 8 : 64, top: anchor ? anchor.top : 38, width: 480, height: 640 };
|
|
19
|
+
}
|
|
20
|
+
export function positionContextualClay() {
|
|
21
|
+
var element = surface();
|
|
22
|
+
var hint = renderContextualMateHint();
|
|
23
|
+
if (!element || narrow() || (store.get('contextualClay') || {}).mode === 'opening') return;
|
|
24
|
+
var desired = state().preview || state().desired || defaultGeometry();
|
|
25
|
+
var width = Math.min(desired.width, window.innerWidth - 16);
|
|
26
|
+
var height = Math.min(desired.height, window.innerHeight - 16);
|
|
27
|
+
var left = Math.max(8, Math.min(desired.left, window.innerWidth - width - 8));
|
|
28
|
+
var top = Math.max(8, Math.min(desired.top, window.innerHeight - height - 8));
|
|
29
|
+
if (hint && left < hint.right + 8 && left + width > hint.left && top < hint.bottom + 8 && top + height > hint.top) {
|
|
30
|
+
left = hint.right + 8; width = Math.min(width, window.innerWidth - left - 8);
|
|
31
|
+
}
|
|
32
|
+
element.style.setProperty('--contextual-left', left + 'px');
|
|
33
|
+
element.style.setProperty('--contextual-top', top + 'px');
|
|
34
|
+
element.style.setProperty('--contextual-width', width + 'px');
|
|
35
|
+
element.style.setProperty('--contextual-height', height + 'px');
|
|
36
|
+
}
|
|
37
|
+
async function flushSave() {
|
|
38
|
+
var current = state();
|
|
39
|
+
if (current.saving || !current.pending) return;
|
|
40
|
+
var owner = store.get('myUserId'); var value = current.pending;
|
|
41
|
+
setState({ saving: true, pending: null });
|
|
42
|
+
try {
|
|
43
|
+
var response = await fetch(endpoint, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Clay-Settings': '1' }, body: JSON.stringify(value) });
|
|
44
|
+
if (!response.ok) throw new Error('Could not save popover position and size.');
|
|
45
|
+
if (store.get('myUserId') === owner) setState({ saved: value });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (store.get('myUserId') === owner) showToast('Popover position and size could not be saved. Adjust again to retry.', 'error');
|
|
48
|
+
} finally {
|
|
49
|
+
if (store.get('myUserId') === owner) { setState({ saving: false }); flushSave(); }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function commit(value) {
|
|
53
|
+
var next = bounded(value);
|
|
54
|
+
setState({ desired: next, pending: next, revision: (state().revision || 0) + 1 });
|
|
55
|
+
positionContextualClay(); flushSave();
|
|
56
|
+
}
|
|
57
|
+
function finish(cancel) {
|
|
58
|
+
var drag = state().interaction;
|
|
59
|
+
if (!drag) return;
|
|
60
|
+
var value = state().preview;
|
|
61
|
+
setState({ interaction: null, preview: null });
|
|
62
|
+
var element = surface();
|
|
63
|
+
if (element) {
|
|
64
|
+
element.classList.remove('contextual-clay-manipulating');
|
|
65
|
+
var handle = drag.edge ? element.querySelector('[data-clay-resize="' + drag.edge + '"]') : element.querySelector('header');
|
|
66
|
+
if (handle && handle.hasPointerCapture(drag.pointerId)) handle.releasePointerCapture(drag.pointerId);
|
|
67
|
+
}
|
|
68
|
+
if (!cancel && value) {
|
|
69
|
+
if (!drag.edge && drag.desired) { value.width = drag.desired.width; value.height = drag.desired.height; }
|
|
70
|
+
commit(value);
|
|
71
|
+
}
|
|
72
|
+
else positionContextualClay();
|
|
73
|
+
}
|
|
74
|
+
function move(event) {
|
|
75
|
+
var drag = state().interaction;
|
|
76
|
+
if (!drag || event.pointerId !== drag.pointerId) return;
|
|
77
|
+
var dx = event.clientX - drag.x; var dy = event.clientY - drag.y;
|
|
78
|
+
if (!state().preview && Math.hypot(dx, dy) < 3) return;
|
|
79
|
+
var value = Object.assign({}, drag.start);
|
|
80
|
+
if (drag.edge) {
|
|
81
|
+
if (drag.edge.indexOf('e') >= 0) value.width = Math.max(340, Math.min(2560, window.innerWidth - value.left - 8, value.width + dx));
|
|
82
|
+
if (drag.edge.indexOf('s') >= 0) value.height = Math.max(360, Math.min(2160, window.innerHeight - value.top - 8, value.height + dy));
|
|
83
|
+
} else {
|
|
84
|
+
value.left = Math.max(8, Math.min(window.innerWidth - value.width - 8, value.left + dx));
|
|
85
|
+
value.top = Math.max(8, Math.min(window.innerHeight - value.height - 8, value.top + dy));
|
|
86
|
+
}
|
|
87
|
+
setState({ preview: value }); positionContextualClay();
|
|
88
|
+
}
|
|
89
|
+
async function loadPreference(owner) {
|
|
90
|
+
setState({ owner: owner, desired: null, revision: 0, pending: null, saving: false });
|
|
91
|
+
try {
|
|
92
|
+
var response = await fetch(endpoint);
|
|
93
|
+
if (!response.ok) throw new Error('Preference unavailable');
|
|
94
|
+
var data = await response.json();
|
|
95
|
+
if (store.get('myUserId') === owner && !state().revision && !state().interaction) {
|
|
96
|
+
setState({ desired: data.geometry, saved: data.geometry }); positionContextualClay();
|
|
97
|
+
}
|
|
98
|
+
} catch (error) { /* Keep the usable anchored default when preferences are unavailable. */ }
|
|
99
|
+
}
|
|
100
|
+
export function attachContextualGeometry(element) {
|
|
101
|
+
var header = element.querySelector('header');
|
|
102
|
+
header.tabIndex = 0;
|
|
103
|
+
header.setAttribute('aria-label', 'Move Clay conversation. Use arrow keys to move; hold Shift for larger steps.');
|
|
104
|
+
header.title = 'Drag to move · Arrow keys when focused';
|
|
105
|
+
['e', 's', 'se'].forEach(function (edge) {
|
|
106
|
+
var handle = document.createElement('button');
|
|
107
|
+
handle.type = 'button'; handle.dataset.clayResize = edge;
|
|
108
|
+
handle.setAttribute('aria-label', 'Resize Clay conversation' + (edge === 'e' ? ' width' : edge === 's' ? ' height' : '') + '. Use arrow keys; hold Shift for larger steps.');
|
|
109
|
+
handle.title = 'Drag to resize · Arrow keys when focused';
|
|
110
|
+
element.appendChild(handle);
|
|
111
|
+
});
|
|
112
|
+
element.addEventListener('pointerdown', function (event) {
|
|
113
|
+
var handle = event.target.closest('[data-clay-resize]');
|
|
114
|
+
var heading = event.target.closest('header');
|
|
115
|
+
if (narrow() || event.button !== 0 || (!handle && (!heading || event.target.closest('button, details, a, input')))) return;
|
|
116
|
+
if ((store.get('contextualClay') || {}).mode !== 'popover') return;
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
var target = handle || header;
|
|
119
|
+
setState({ interaction: { pointerId: event.pointerId, x: event.clientX, y: event.clientY, start: rectValue(element.getBoundingClientRect()), desired: state().desired, edge: handle ? handle.dataset.clayResize : null } });
|
|
120
|
+
element.classList.add('contextual-clay-manipulating');
|
|
121
|
+
target.setPointerCapture(event.pointerId);
|
|
122
|
+
});
|
|
123
|
+
element.addEventListener('pointermove', move);
|
|
124
|
+
element.addEventListener('pointerup', function (event) { if (state().interaction && event.pointerId === state().interaction.pointerId) { move(event); finish(false); } });
|
|
125
|
+
element.addEventListener('pointercancel', function () { finish(true); });
|
|
126
|
+
element.addEventListener('lostpointercapture', function () { finish(true); });
|
|
127
|
+
element.addEventListener('keydown', function (event) {
|
|
128
|
+
if (narrow() || ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].indexOf(event.key) < 0) return;
|
|
129
|
+
var edge = event.target.dataset.clayResize;
|
|
130
|
+
if (event.target !== header && !edge) return;
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
var value = rectValue(element.getBoundingClientRect()); var step = event.shiftKey ? 32 : 8;
|
|
133
|
+
var horizontal = event.key === 'ArrowLeft' || event.key === 'ArrowRight';
|
|
134
|
+
var delta = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -step : step;
|
|
135
|
+
if (edge) {
|
|
136
|
+
if (horizontal && edge.indexOf('e') >= 0) value.width = Math.min(window.innerWidth - value.left - 8, value.width + delta);
|
|
137
|
+
if (!horizontal && edge.indexOf('s') >= 0) value.height = Math.min(window.innerHeight - value.top - 8, value.height + delta);
|
|
138
|
+
} else if (horizontal) value.left = Math.max(8, Math.min(window.innerWidth - value.width - 8, value.left + delta));
|
|
139
|
+
else value.top = Math.max(8, Math.min(window.innerHeight - value.height - 8, value.top + delta));
|
|
140
|
+
if (!edge && state().desired) { value.width = state().desired.width; value.height = state().desired.height; }
|
|
141
|
+
commit(value);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function initContextualGeometry() {
|
|
145
|
+
store.subscribe(function (value, previous) {
|
|
146
|
+
if (value.myUserId && value.myUserId !== previous.myUserId) { finish(true); loadPreference(value.myUserId); }
|
|
147
|
+
if ((value.contextualClay || {}).mode !== (previous.contextualClay || {}).mode) finish(true);
|
|
148
|
+
});
|
|
149
|
+
document.addEventListener('keydown', function (event) {
|
|
150
|
+
if (event.key === 'Escape' && state().interaction) { event.preventDefault(); event.stopImmediatePropagation(); finish(true); }
|
|
151
|
+
}, true);
|
|
152
|
+
window.addEventListener('blur', function () { finish(true); });
|
|
153
|
+
window.addEventListener('resize', function () { finish(true); positionContextualClay(); });
|
|
154
|
+
if (store.get('myUserId')) loadPreference(store.get('myUserId'));
|
|
155
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Session identity remains pinned across transport recovery and presentation.
|
|
2
|
+
import { autoResize, takeComposerDraft, restoreComposerDraft } from './input.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { withdrawPaneControlActivity } from './clay-control-activity.js';
|
|
5
|
+
import { initContextualComposer } from './contextual-composer.js';
|
|
6
|
+
import { submitContextualAsk, pauseContextualAsk, stopContextualAsk } from './contextual-ask-session.js';
|
|
7
|
+
|
|
8
|
+
// Session replay and authenticated account bootstrap complete independently.
|
|
9
|
+
function submissionReady() { var expected = window.frameElement && window.frameElement.dataset.expectedSessionOrigin; return (!expected || expected === store.get('sessionOriginId')) && !!(store.get('contextualSessionReady') && store.get('connected') && store.get('myUserId') && store.get('sessionOriginId') && store.get('activeSessionId') === store.get('paneSessionId')); }
|
|
10
|
+
function enabled() { return store.get('paneMode') && new URLSearchParams(location.search).get('contextual') === '1'; }
|
|
11
|
+
function renderSessionAvailability() {
|
|
12
|
+
var input = document.getElementById('input');
|
|
13
|
+
var frame = window.frameElement;
|
|
14
|
+
var mate = frame && (store.get('cachedMatesList') || []).find(function (item) { return item.id === frame.dataset.mateId; });
|
|
15
|
+
var name = mate && ((mate.profile || {}).displayName || mate.displayName || mate.name);
|
|
16
|
+
if (input && name) { input.setAttribute('aria-label', 'Message ' + name); if (name.length > 32) input.placeholder = 'Message this Mate…'; }
|
|
17
|
+
var wrapper = document.getElementById('input-wrapper');
|
|
18
|
+
if (wrapper) wrapper.inert = !submissionReady();
|
|
19
|
+
try {
|
|
20
|
+
var surface = window.parent.document.getElementById('contextual-clay');
|
|
21
|
+
var anchor = window.parent.document.querySelector('[aria-describedby="contextual-mate-hint"]') || window.parent.document.querySelector('.icon-strip-brand');
|
|
22
|
+
if (submissionReady() && surface && surface.dataset.focusEntry === 'true' && !window.frameElement.hidden && !window.frameElement.inert) {
|
|
23
|
+
delete surface.dataset.focusEntry;
|
|
24
|
+
if (surface.dataset.mode === 'popover' && window.parent.document.activeElement === anchor) document.getElementById('input').focus({ preventScroll: true });
|
|
25
|
+
}
|
|
26
|
+
} catch (error) { /* Cross-origin authentication owns focus. */ }
|
|
27
|
+
if (store.get('contextualSessionReady') && window.frameElement && !window.frameElement.hidden) {
|
|
28
|
+
window.frameElement.dataset.sessionId = String(store.get('paneSessionId'));
|
|
29
|
+
var parentSurface = window.parent.document.getElementById('contextual-clay');
|
|
30
|
+
if (parentSurface) parentSurface.dispatchEvent(new window.parent.CustomEvent('contextual-clay-ready'));
|
|
31
|
+
}
|
|
32
|
+
var warning = document.getElementById('contextual-session-warning');
|
|
33
|
+
if (!warning && store.get('contextualSessionUnavailable')) {
|
|
34
|
+
warning = document.createElement('p');
|
|
35
|
+
warning.id = 'contextual-session-warning'; warning.setAttribute('role', 'status');
|
|
36
|
+
warning.textContent = 'This Mate conversation is no longer available. Your draft is still here.';
|
|
37
|
+
document.getElementById('input-area').prepend(warning);
|
|
38
|
+
}
|
|
39
|
+
if (warning) warning.hidden = !store.get('contextualSessionUnavailable');
|
|
40
|
+
}
|
|
41
|
+
export function handleContextualSessionMessage(msg) {
|
|
42
|
+
if (!enabled()) return;
|
|
43
|
+
if (msg.type === 'session_list' && store.get('paneSessionId') && !(msg.sessions || []).some(function (session) { return session.id === store.get('paneSessionId'); })) {
|
|
44
|
+
store.set({ contextualSessionReady: false, contextualSessionUnavailable: true });
|
|
45
|
+
}
|
|
46
|
+
if (msg.type === 'session_switched') {
|
|
47
|
+
var pin = store.get('paneSessionId');
|
|
48
|
+
if (pin && msg.id !== pin) return true;
|
|
49
|
+
var path = store.get('wsPath').replace(/&session=\d+/g, '');
|
|
50
|
+
store.set({ contextualSessionReady: false, contextualSessionReceived: true, contextualSessionUnavailable: false, paneSessionId: msg.id, wsPath: path + '&session=' + msg.id });
|
|
51
|
+
}
|
|
52
|
+
if (msg.type === 'history_done' && store.get('contextualSessionReceived')) store.set({ contextualSessionReady: true });
|
|
53
|
+
renderSessionAvailability();
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
export function initContextualSession() {
|
|
57
|
+
if (!enabled()) return;
|
|
58
|
+
document.body.classList.add('contextual-chat-pane');
|
|
59
|
+
initContextualComposer();
|
|
60
|
+
// Bind at app readiness rather than iframe load: slow assets must not leave
|
|
61
|
+
// an already usable composer without its presentation keyboard handling.
|
|
62
|
+
document.addEventListener('keydown', function (event) {
|
|
63
|
+
if (event.key !== 'Escape') return;
|
|
64
|
+
try {
|
|
65
|
+
var surface = window.parent.document.getElementById('contextual-clay');
|
|
66
|
+
if (surface && window.frameElement && surface.contains(window.frameElement)) surface.dispatchEvent(new window.parent.CustomEvent('contextual-clay-keydown', { detail: { source: window, keyboardEvent: event } }));
|
|
67
|
+
} catch (error) { /* Authentication navigation owns its document. */ }
|
|
68
|
+
});
|
|
69
|
+
var surfaceBridge = window.parent.document.getElementById('contextual-clay');
|
|
70
|
+
if (surfaceBridge && window.frameElement && surfaceBridge.contains(window.frameElement)) window.frameElement.conversation = {
|
|
71
|
+
source: window, document: document,
|
|
72
|
+
submitAsk: submitContextualAsk,
|
|
73
|
+
pauseAsk: pauseContextualAsk,
|
|
74
|
+
stopAsk: stopContextualAsk,
|
|
75
|
+
identity: function () { return { mateId: store.get('activeProjectMateId'), viewerRef: store.get('clayControlViewerRef'), sessionId: store.get('paneSessionId'), sessionOriginId: store.get('sessionOriginId'), ownerId: store.get('myUserId'), connected: store.get('connected'), available: !store.get('contextualSessionUnavailable'), ready: submissionReady() }; },
|
|
76
|
+
suspend: function (value) { store.set({ contextualWriterSuspended: value }); document.getElementById('input').readOnly = value; },
|
|
77
|
+
checkDraft: function () { var draft = takeComposerDraft(); restoreComposerDraft(draft); },
|
|
78
|
+
take: function () { var draft = takeComposerDraft(); var drafts = Object.assign({}, store.get('sessionDrafts')); delete drafts[store.get('paneSessionId')]; store.set({ sessionDrafts: drafts }); return draft; },
|
|
79
|
+
restore: restoreComposerDraft
|
|
80
|
+
};
|
|
81
|
+
document.addEventListener('click', function (event) {
|
|
82
|
+
if (event.target.closest('#generation-stop, #autonomous-run-stop, #send-btn.stop')) {
|
|
83
|
+
pauseContextualAsk();
|
|
84
|
+
var askHost = window.parent.document.getElementById('contextual-clay');
|
|
85
|
+
if (askHost) askHost.dispatchEvent(new window.parent.CustomEvent('contextual-ask-stopped', { detail: { source: window } }));
|
|
86
|
+
}
|
|
87
|
+
if (store.get('contextualWriterSuspended') && event.target.closest('#send-btn, #attach-wrap, #contextual-attach-btn, #config-chip-wrap, .ask-user-container')) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
88
|
+
}, true);
|
|
89
|
+
store.set({ contextualSessionReady: false, contextualSessionUnavailable: false });
|
|
90
|
+
var width = 0;
|
|
91
|
+
new ResizeObserver(function (entries) {
|
|
92
|
+
var next = entries[0].contentRect.width;
|
|
93
|
+
if (next === width || next === 0) return;
|
|
94
|
+
width = next;
|
|
95
|
+
autoResize();
|
|
96
|
+
}).observe(document.getElementById('input-textarea-wrap'));
|
|
97
|
+
function guardPendingReview(event) {
|
|
98
|
+
if ((!store.get('contextualSessionReady') || store.get('contextualWriterSuspended')) && event.target.closest('.ask-user-submit, .ask-user-other input') && (event.type === 'click' || event.key === 'Enter')) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
99
|
+
}
|
|
100
|
+
document.addEventListener('click', guardPendingReview, true);
|
|
101
|
+
document.addEventListener('keydown', guardPendingReview, true);
|
|
102
|
+
store.subscribe(function (value, previous) {
|
|
103
|
+
if (value.connected !== previous.connected || value.contextualSessionReady !== previous.contextualSessionReady || value.contextualSessionUnavailable !== previous.contextualSessionUnavailable) {
|
|
104
|
+
var parentSurface = window.parent.document.getElementById('contextual-clay');
|
|
105
|
+
if (parentSurface && window.frameElement && !window.frameElement.hidden && parentSurface.contains(window.frameElement)) parentSurface.dispatchEvent(new window.parent.CustomEvent('contextual-clay-availability'));
|
|
106
|
+
}
|
|
107
|
+
if (!value.connected && previous.connected) {
|
|
108
|
+
withdrawPaneControlActivity();
|
|
109
|
+
var input = document.getElementById('input');
|
|
110
|
+
var drafts = Object.assign({}, value.sessionDrafts);
|
|
111
|
+
if (value.paneSessionId && input) drafts[value.paneSessionId] = input.value;
|
|
112
|
+
store.set({ contextualSessionReady: false, contextualSessionReceived: false, sessionDrafts: drafts });
|
|
113
|
+
}
|
|
114
|
+
renderSessionAvailability();
|
|
115
|
+
});
|
|
116
|
+
renderSessionAvailability();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Capture bounded metadata separately from user attachments. This is
|
|
120
|
+
// untrusted reference data, never approval or a target for an existing action.
|
|
121
|
+
export function contextualScreenReference() {
|
|
122
|
+
if (!enabled() || window.parent === window) return null;
|
|
123
|
+
try {
|
|
124
|
+
var surface = window.parent.document.getElementById('contextual-clay');
|
|
125
|
+
if (!surface || !window.frameElement || window.frameElement.hidden || window.frameElement.dataset.canonical !== 'true' || !surface.contains(window.frameElement)) return null;
|
|
126
|
+
var reference = JSON.parse(surface.dataset.screenReference || 'null');
|
|
127
|
+
if (!reference || reference.ownerId !== (store.get('myUserId') || null)) return null;
|
|
128
|
+
return { version: 1, viewId: typeof reference.viewId === 'string' ? reference.viewId.slice(0, 100) : null, projectTitle: typeof reference.projectTitle === 'string' ? reference.projectTitle.slice(0, 160) : null, conversationTitle: typeof reference.conversationTitle === 'string' ? reference.conversationTitle.slice(0, 160) : null, projectSlug: typeof reference.projectSlug === 'string' ? reference.projectSlug.slice(0, 160) : null, sessionId: Number.isSafeInteger(reference.sessionId) ? reference.sessionId : null, screen: String(reference.screen || '').slice(0, 40) };
|
|
129
|
+
} catch (error) { return null; }
|
|
130
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { contextualFrame, contextualMate } from './contextual-mate-identity.js';
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { openMateWorkspace, returnFromClay } from './project-mate-navigation.js';
|
|
4
|
+
import { takeComposerDraft, restoreComposerDraft, autoResize } from './input.js';
|
|
5
|
+
import { showToast } from './utils.js';
|
|
6
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
7
|
+
import { getHomeMateName } from './home-mate-selection.js';
|
|
8
|
+
|
|
9
|
+
function bridge(mateId, sessionId) {
|
|
10
|
+
var surface = document.getElementById('contextual-clay');
|
|
11
|
+
var frame = contextualFrame(mateId, sessionId);
|
|
12
|
+
var connection = surface && frame && frame.conversation;
|
|
13
|
+
if (!connection || !contextualMate(frame.dataset.mateId) || connection.source !== frame.contentWindow) return null;
|
|
14
|
+
var identity = connection.identity();
|
|
15
|
+
return identity.ownerId === store.get('myUserId') && identity.mateId === frame.dataset.mateId ? connection : null;
|
|
16
|
+
}
|
|
17
|
+
function lockNative(value) {
|
|
18
|
+
['messages', 'input-wrapper'].forEach(function (id) { var element = document.getElementById(id); if (element) element.inert = value; });
|
|
19
|
+
}
|
|
20
|
+
// A direct/native Mate entry also lends its composer to one pane at a time.
|
|
21
|
+
export function beginContextualNativeOrigin(mate) {
|
|
22
|
+
if (store.get('currentSlug') !== 'mate-' + mate.id) return true;
|
|
23
|
+
var existing = contextualFrame(mate.id);
|
|
24
|
+
if (existing && existing.dataset.sessionId && Number(existing.dataset.sessionId) !== store.get('activeSessionId')) return true;
|
|
25
|
+
if (existing && existing.conversation && existing.conversation.identity().sessionId !== store.get('activeSessionId')) return true;
|
|
26
|
+
try {
|
|
27
|
+
var draft = takeComposerDraft();
|
|
28
|
+
store.set({ contextualNativeOrigin: { mateId: mate.id, slug: store.get('currentSlug'), sessionId: store.get('activeSessionId'), draft: draft, stage: 'waiting' } });
|
|
29
|
+
lockNative(true); return true;
|
|
30
|
+
} catch (error) { showToast(error.message, 'error'); return false; }
|
|
31
|
+
}
|
|
32
|
+
export function syncContextualNativeOrigin() {
|
|
33
|
+
var origin = store.get('contextualNativeOrigin'); var connection = origin && bridge(origin.mateId, origin.sessionId);
|
|
34
|
+
if (!origin || origin.stage !== 'waiting' || !connection || !connection.identity().ready || connection.identity().mateId !== origin.mateId || connection.identity().sessionId !== origin.sessionId) return;
|
|
35
|
+
store.set({ contextualNativeOrigin: Object.assign({}, origin, { draft: null, stage: 'pane' }) });
|
|
36
|
+
connection.suspend(false); connection.restore(origin.draft);
|
|
37
|
+
}
|
|
38
|
+
export function abandonContextualNativeOrigin(mateId) {
|
|
39
|
+
var origin = store.get('contextualNativeOrigin');
|
|
40
|
+
if (!origin || origin.mateId !== mateId) return;
|
|
41
|
+
// A removed/revoked identity cannot receive a draft or trap later navigation.
|
|
42
|
+
store.set({ contextualNativeOrigin: null }); lockNative(false);
|
|
43
|
+
showToast('This Mate conversation is no longer available.', 'warn');
|
|
44
|
+
}
|
|
45
|
+
export function finishContextualNativeOrigin() {
|
|
46
|
+
var origin = store.get('contextualNativeOrigin');
|
|
47
|
+
if (!origin) return true;
|
|
48
|
+
try {
|
|
49
|
+
var connection = bridge(origin.mateId, origin.sessionId);
|
|
50
|
+
var draft = origin.stage === 'waiting' ? origin.draft : connection && connection.take();
|
|
51
|
+
if (!draft) throw new Error('Wait for this Mate conversation to reconnect.');
|
|
52
|
+
if (connection) connection.suspend(true);
|
|
53
|
+
restoreComposerDraft(draft); store.set({ contextualNativeOrigin: null }); lockNative(false); return true;
|
|
54
|
+
} catch (error) { showToast(error.message, 'error'); return false; }
|
|
55
|
+
}
|
|
56
|
+
export function openContextualWorkspace(clay) {
|
|
57
|
+
var connection = bridge();
|
|
58
|
+
var identity = connection && connection.identity();
|
|
59
|
+
if (!contextualMate(clay.id) || !identity || identity.mateId !== clay.id || !identity.ready) { showToast('Wait for this Mate conversation to reconnect.', 'error'); return; }
|
|
60
|
+
var origin = store.get('contextualNativeOrigin');
|
|
61
|
+
if (origin && origin.mateId === clay.id && origin.sessionId === identity.sessionId) {
|
|
62
|
+
if (!finishContextualNativeOrigin()) return;
|
|
63
|
+
var existingHandoff = store.get('contextualWorkspace');
|
|
64
|
+
if (existingHandoff && existingHandoff.slug === origin.slug && existingHandoff.sessionId === origin.sessionId) { store.set({ contextualClay: { mode: 'workspace', mateId: clay.id, sessionId: origin.sessionId } }); return; }
|
|
65
|
+
var target = store.get('clayReturnTarget');
|
|
66
|
+
store.set({ contextualWorkspace: { slug: origin.slug, sessionId: origin.sessionId, stage: 'workspace', projectDraft: null, returnSlug: target && target.projectSlug, returnSessionId: target && target.sessionId }, contextualClay: { mode: 'workspace', mateId: clay.id } });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (origin && !finishContextualNativeOrigin()) return;
|
|
70
|
+
try {
|
|
71
|
+
var projectDraft = takeComposerDraft();
|
|
72
|
+
var draft;
|
|
73
|
+
try { draft = connection.take(); } catch (error) { restoreComposerDraft(projectDraft); throw error; }
|
|
74
|
+
connection.suspend(true);
|
|
75
|
+
store.set({ contextualWorkspace: { slug: 'mate-' + clay.id, sessionId: identity.sessionId, draft: draft, projectDraft: projectDraft, stage: 'opening', returnSlug: store.get('currentSlug'), returnSessionId: store.get('activeSessionId'), returnWorkspace: store.get('contextualWorkspace') }, contextualClay: { mode: 'opening', mateId: clay.id } });
|
|
76
|
+
lockNative(true);
|
|
77
|
+
openMateWorkspace(clay.id, identity.sessionId);
|
|
78
|
+
} catch (error) { showToast(error.message, 'error'); }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Called by the existing return service before it changes the route/session.
|
|
82
|
+
export function finishContextualWorkspace() {
|
|
83
|
+
var handoff = store.get('contextualWorkspace');
|
|
84
|
+
if (!handoff || handoff.stage === 'returning') return;
|
|
85
|
+
var connection = bridge(handoff.slug.slice(5), handoff.sessionId);
|
|
86
|
+
if (!connection) { store.set({ contextualWorkspace: null, contextualClay: { mode: 'closed' } }); lockNative(false); return true; }
|
|
87
|
+
try {
|
|
88
|
+
var draft = handoff.draft;
|
|
89
|
+
if (handoff.stage === 'workspace' && store.get('currentSlug') === handoff.slug && store.get('activeSessionId') === handoff.sessionId) draft = takeComposerDraft();
|
|
90
|
+
connection.restore(draft);
|
|
91
|
+
connection.suspend(false);
|
|
92
|
+
store.set({ contextualWorkspace: Object.assign({}, handoff, { draft: null, stage: 'returning' }), contextualClay: Object.assign({}, store.get('contextualClay'), { mode: 'closed' }) });
|
|
93
|
+
lockNative(true);
|
|
94
|
+
return true;
|
|
95
|
+
} catch (error) { showToast(error.message, 'error'); return false; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function handleContextualWorkspaceMessage(msg) {
|
|
99
|
+
var handoff = store.get('contextualWorkspace');
|
|
100
|
+
if (!handoff || store.get('paneMode')) return;
|
|
101
|
+
if (msg.type === 'session_switched' && handoff.stage.indexOf('workspace') === 0 && store.get('currentSlug') === handoff.slug) {
|
|
102
|
+
var saved = handoff.draft;
|
|
103
|
+
if (store.get('activeSessionId') === handoff.sessionId) saved = takeComposerDraft();
|
|
104
|
+
var exact = msg.id === handoff.sessionId;
|
|
105
|
+
store.set({ contextualWorkspace: Object.assign({}, handoff, { draft: saved, stage: exact ? 'opening' : 'workspace-other' }) });
|
|
106
|
+
if (exact) lockNative(true);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (msg.type !== 'history_done') return;
|
|
110
|
+
if (handoff.stage === 'opening' && store.get('currentSlug') === handoff.slug && store.get('activeSessionId') === handoff.sessionId) {
|
|
111
|
+
restoreComposerDraft(handoff.draft);
|
|
112
|
+
store.set({ contextualWorkspace: Object.assign({}, handoff, { draft: null, stage: 'workspace' }), contextualClay: Object.assign({}, store.get('contextualClay'), { mode: 'workspace' }) });
|
|
113
|
+
lockNative(false);
|
|
114
|
+
} else if (handoff.stage === 'returning' && (store.get('currentSlug') !== handoff.slug || store.get('activeSessionId') !== handoff.sessionId)) {
|
|
115
|
+
if (handoff.projectDraft && store.get('currentSlug') === handoff.returnSlug && store.get('activeSessionId') === handoff.returnSessionId) restoreComposerDraft(handoff.projectDraft);
|
|
116
|
+
var restored = handoff.returnWorkspace && store.get('currentSlug') === handoff.returnSlug && store.get('activeSessionId') === handoff.returnSessionId ? handoff.returnWorkspace : null;
|
|
117
|
+
store.set({ contextualWorkspace: restored, contextualClay: restored ? { mode: 'workspace', mateId: restored.slug.slice(5), sessionId: restored.sessionId } : store.get('contextualClay') });
|
|
118
|
+
lockNative(false);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function renderContextualWorkspaceBack() {
|
|
122
|
+
var handoff = store.get('contextualWorkspace');
|
|
123
|
+
document.body.classList.toggle('contextual-clay-native', !!handoff && handoff.stage === 'workspace' && store.get('currentSlug') === handoff.slug);
|
|
124
|
+
var button = document.getElementById('contextual-workspace-back');
|
|
125
|
+
if (!button) {
|
|
126
|
+
button = document.createElement('button'); button.id = 'contextual-workspace-back'; button.type = 'button';
|
|
127
|
+
button.innerHTML = iconHtml('arrow-left') + '<span>Back to project</span>';
|
|
128
|
+
button.addEventListener('click', returnFromClay);
|
|
129
|
+
document.getElementById('header-left').prepend(button);
|
|
130
|
+
refreshIcons();
|
|
131
|
+
}
|
|
132
|
+
var returnMate = handoff && handoff.returnSlug && handoff.returnSlug.indexOf('mate-') === 0 && contextualMate(handoff.returnSlug.slice(5));
|
|
133
|
+
button.querySelector('span').textContent = returnMate ? 'Back to ' + getHomeMateName(returnMate) : 'Back to project';
|
|
134
|
+
button.hidden = !handoff || handoff.stage.indexOf('workspace') !== 0 || store.get('currentSlug') !== handoff.slug;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function prepareContextualWorkspaceDeparture(slug) {
|
|
138
|
+
var origin = store.get('contextualNativeOrigin');
|
|
139
|
+
if (origin && slug !== origin.slug && !finishContextualNativeOrigin()) return false;
|
|
140
|
+
var handoff = store.get('contextualWorkspace');
|
|
141
|
+
if (handoff && handoff.stage.indexOf('workspace') === 0 && slug !== handoff.slug) return finishContextualWorkspace();
|
|
142
|
+
}
|
|
143
|
+
export function initContextualWorkspace() {
|
|
144
|
+
var width = 0;
|
|
145
|
+
new ResizeObserver(function (entries) {
|
|
146
|
+
var next = entries[0].contentRect.width;
|
|
147
|
+
if (next === width || !next || !store.get('contextualWorkspace')) return;
|
|
148
|
+
width = next; autoResize();
|
|
149
|
+
}).observe(document.getElementById('input-textarea-wrap'));
|
|
150
|
+
store.subscribe(function (value, previous) {
|
|
151
|
+
var handoff = value.contextualWorkspace;
|
|
152
|
+
if (!handoff || handoff.stage !== 'workspace' || !previous.connected || value.connected || value.currentSlug !== handoff.slug) return;
|
|
153
|
+
var draft = takeComposerDraft();
|
|
154
|
+
store.set({ contextualWorkspace: Object.assign({}, handoff, { draft: draft, stage: 'opening' }), pendingProjectSessionNavigation: { projectSlug: handoff.slug, sessionId: handoff.sessionId } });
|
|
155
|
+
lockNative(true);
|
|
156
|
+
});
|
|
157
|
+
}
|