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,134 @@
|
|
|
1
|
+
// Saved conversations are independent of which compact renderers are mounted.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
import { contextualMate, contextualMateStamp, contextualFrame } from './contextual-mate-identity.js';
|
|
5
|
+
import { selectContextualConversation } from './contextual-clay.js';
|
|
6
|
+
import { pauseContextualSearchForSelection } from './contextual-search.js';
|
|
7
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
8
|
+
import { showToast } from './utils.js';
|
|
9
|
+
|
|
10
|
+
function selection() { var value = store.get('contextualClay') || {}; return JSON.stringify([value.mateId, value.sessionId, value.startingAsk]); }
|
|
11
|
+
function host() { return document.getElementById('contextual-clay'); }
|
|
12
|
+
function requestState(kind) { return store.get(kind === 'list' ? 'contextualConversationHistory' : 'contextualConversationRequest'); }
|
|
13
|
+
function save(kind, value) { var update = {}; update[kind === 'list' ? 'contextualConversationHistory' : 'contextualConversationRequest'] = value; store.set(update); }
|
|
14
|
+
function valid(request) { var mate = request && contextualMate(request.mateId); return request && request.ownerId === store.get('myUserId') && mate && contextualMateStamp(mate) === request.stamp; }
|
|
15
|
+
function guardDraft() {
|
|
16
|
+
var frame = contextualFrame();
|
|
17
|
+
try {
|
|
18
|
+
if (frame && frame.conversation) frame.conversation.checkDraft();
|
|
19
|
+
return true;
|
|
20
|
+
} catch (error) { showToast(error.message, 'warn'); return false; }
|
|
21
|
+
}
|
|
22
|
+
function send(kind, item, lookup) {
|
|
23
|
+
var mate = contextualMate((store.get('contextualClay') || {}).mateId), socket = getWs();
|
|
24
|
+
if (!mate) return;
|
|
25
|
+
var old = requestState(kind);
|
|
26
|
+
if (kind !== 'list' && old && old.status === 'loading') { showToast('Wait for the current conversation request to finish.', 'warn'); return; }
|
|
27
|
+
if (kind !== 'list' && !guardDraft()) return;
|
|
28
|
+
var request = lookup ? Object.assign({}, old) : { requestId: 'mate-conversations-' + crypto.randomUUID(), mateId: mate.id, stamp: contextualMateStamp(mate), ownerId: store.get('myUserId'), selection: selection(), kind: kind, item: item };
|
|
29
|
+
if (!valid(request)) return;
|
|
30
|
+
request.status = 'loading'; request.error = null; request.socket = socket;
|
|
31
|
+
save(kind, request);
|
|
32
|
+
if (!store.get('connected') || !socket || socket.readyState !== 1) { save(kind, Object.assign({}, request, { status: 'error', error: 'Reconnect to load or start a conversation.' })); return; }
|
|
33
|
+
var payload = { type: kind === 'list' ? 'home_mate_sessions_list' : kind === 'new' ? 'home_mate_new_session' : 'home_mate_session_open', presentation: 'contextual', mateId: request.mateId, mateStamp: request.stamp, requestId: request.requestId };
|
|
34
|
+
if (item) { payload.sessionId = item.id; payload.sessionOriginId = item.sessionOriginId; }
|
|
35
|
+
if (lookup && kind === 'new') payload.lookupOnly = true;
|
|
36
|
+
try { socket.send(JSON.stringify(payload)); }
|
|
37
|
+
catch (error) { save(kind, Object.assign({}, request, { status: 'error', error: 'The request is unconfirmed. Check again after reconnecting.' })); }
|
|
38
|
+
setTimeout(function () {
|
|
39
|
+
var current = requestState(kind);
|
|
40
|
+
if (current && current.requestId === request.requestId && current.status === 'loading') save(kind, Object.assign({}, current, { status: 'error', error: kind === 'new' ? 'Creation is unconfirmed. Check the same request or browse history.' : 'Conversation history did not respond. Try again.' }));
|
|
41
|
+
}, 15000);
|
|
42
|
+
}
|
|
43
|
+
function age(timestamp) {
|
|
44
|
+
if (!timestamp) return '';
|
|
45
|
+
var date = new Date(timestamp), today = new Date();
|
|
46
|
+
return date.toDateString() === today.toDateString() ? 'Today' : date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
47
|
+
}
|
|
48
|
+
function render() {
|
|
49
|
+
var surface = host(); if (!surface) return;
|
|
50
|
+
var mateId = (store.get('contextualClay') || {}).mateId;
|
|
51
|
+
var request = requestState('action'), history = requestState('list');
|
|
52
|
+
var busy = request && request.mateId === mateId && request.status === 'loading';
|
|
53
|
+
surface.querySelector('[data-mate-new]').disabled = !!busy;
|
|
54
|
+
surface.querySelector('[data-mate-new]').setAttribute('aria-busy', String(!!busy));
|
|
55
|
+
var notice = surface.querySelector('[data-conversation-notice]');
|
|
56
|
+
notice.hidden = !request || request.mateId !== mateId || request.status === 'done';
|
|
57
|
+
if (!notice.hidden) {
|
|
58
|
+
notice.querySelector('span').textContent = request.error || (request.kind === 'new' ? 'Starting a new chat…' : 'Opening conversation…');
|
|
59
|
+
notice.querySelector('button').hidden = request.status !== 'error';
|
|
60
|
+
notice.querySelector('button').textContent = request.kind === 'new' ? 'Check request' : 'Try again';
|
|
61
|
+
}
|
|
62
|
+
var list = surface.querySelector('[data-mate-session-list]');
|
|
63
|
+
if (!surface.querySelector('[data-mate-history]').open) return;
|
|
64
|
+
var key = JSON.stringify([history && history.requestId, history && history.status, history && history.error, mateId, (store.get('contextualClay') || {}).sessionId, !!busy]);
|
|
65
|
+
if (list.dataset.renderKey === key) return;
|
|
66
|
+
list.dataset.renderKey = key;
|
|
67
|
+
list.replaceChildren();
|
|
68
|
+
if (!history || history.mateId !== mateId || history.status !== 'done') {
|
|
69
|
+
var status = document.createElement('p'); status.setAttribute('role', 'status');
|
|
70
|
+
status.textContent = history && history.mateId === mateId && history.error || 'Loading conversations…'; list.appendChild(status);
|
|
71
|
+
if (history && history.status === 'error') { var retry = document.createElement('button'); retry.type = 'button'; retry.textContent = 'Try again'; retry.onclick = function () { send('list'); }; list.appendChild(retry); }
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!history.sessions.length) { var empty = document.createElement('p'); empty.textContent = 'No saved conversations yet.'; list.appendChild(empty); }
|
|
75
|
+
history.sessions.forEach(function (item) {
|
|
76
|
+
var button = document.createElement('button'); button.type = 'button'; button.dataset.sessionId = String(item.localId);
|
|
77
|
+
button.setAttribute('aria-current', String(String((store.get('contextualClay') || {}).sessionId) === String(item.localId)));
|
|
78
|
+
var title = document.createElement('span'); title.textContent = item.title || 'New conversation';
|
|
79
|
+
var detail = document.createElement('small'); detail.textContent = item.isProcessing ? 'Working' : age(item.lastActivity || item.createdAt);
|
|
80
|
+
button.append(title, detail); button.title = title.textContent; button.disabled = !!busy;
|
|
81
|
+
button.onclick = function () { send('open', item); };
|
|
82
|
+
list.appendChild(button);
|
|
83
|
+
});
|
|
84
|
+
if (history.truncated) { var limit = document.createElement('p'); limit.textContent = 'Showing the 500 most recent conversations.'; list.appendChild(limit); }
|
|
85
|
+
}
|
|
86
|
+
export function attachContextualConversations(surface) {
|
|
87
|
+
var actions = surface.querySelector('.contextual-clay-actions');
|
|
88
|
+
var tools = document.createElement('div'); tools.className = 'contextual-conversation-tools';
|
|
89
|
+
tools.innerHTML = '<button type="button" data-mate-new aria-label="New chat" title="New chat">' + iconHtml('square-pen') + '<span>New chat</span></button><details data-mate-history><summary role="button" aria-label="Conversation history" aria-haspopup="dialog" aria-expanded="false" aria-controls="contextual-conversation-history" title="Conversation history">' + iconHtml('history') + '</summary><div id="contextual-conversation-history" role="dialog" aria-label="Conversation history"><span class="contextual-history-label">Conversations</span><div data-mate-session-list></div></div></details>';
|
|
90
|
+
actions.prepend(tools);
|
|
91
|
+
var notice = document.createElement('div'); notice.dataset.conversationNotice = ''; notice.hidden = true;
|
|
92
|
+
notice.innerHTML = '<span role="status"></span><button type="button">Check request</button>';
|
|
93
|
+
surface.insertBefore(notice, surface.querySelector('.contextual-clay-body'));
|
|
94
|
+
tools.querySelector('[data-mate-new]').onclick = function () { send('new'); };
|
|
95
|
+
tools.querySelector('details').addEventListener('toggle', function () { this.querySelector('summary').setAttribute('aria-expanded', String(this.open)); if (this.open) send('list'); });
|
|
96
|
+
surface.addEventListener('pointerdown', function (event) { var menu = tools.querySelector('details'); if (menu.open && !menu.contains(event.target)) menu.open = false; });
|
|
97
|
+
|
|
98
|
+
notice.querySelector('button').onclick = function () { var value = requestState('action'); if (value) send(value.kind, value.item, true); };
|
|
99
|
+
tools.addEventListener('keydown', function (event) {
|
|
100
|
+
if (event.key === 'Tab') {
|
|
101
|
+
var menu = tools.querySelector('details'), items = Array.from(tools.querySelectorAll('[data-mate-session-list] button:not(:disabled)'));
|
|
102
|
+
if (menu.open && (event.shiftKey && event.target === menu.querySelector('summary') || !event.shiftKey && event.target === items[items.length - 1])) queueMicrotask(function () { menu.open = false; });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!/^Arrow(Down|Up)$/.test(event.key)) return;
|
|
106
|
+
var buttons = Array.from(tools.querySelectorAll('[data-mate-session-list] button:not(:disabled)'));
|
|
107
|
+
if (!buttons.length) return;
|
|
108
|
+
var index = buttons.indexOf(document.activeElement);
|
|
109
|
+
buttons[index < 0 ? (event.key === 'ArrowDown' ? 0 : buttons.length - 1) : (index + (event.key === 'ArrowDown' ? 1 : buttons.length - 1)) % buttons.length].focus(); event.preventDefault();
|
|
110
|
+
});
|
|
111
|
+
refreshIcons(); render();
|
|
112
|
+
}
|
|
113
|
+
export function handleContextualConversations(msg) {
|
|
114
|
+
if (msg.type !== 'contextual_mate_sessions_result') return false;
|
|
115
|
+
var kind = requestState('list') && requestState('list').requestId === msg.requestId ? 'list' : 'action';
|
|
116
|
+
var request = requestState(kind);
|
|
117
|
+
if (!request || request.status !== 'loading' || request.requestId !== msg.requestId || request.socket !== getWs() || !valid(request) || msg.mateId !== request.mateId || msg.mateStamp !== request.stamp) return true;
|
|
118
|
+
if (!msg.ok) { save(kind, Object.assign({}, request, { status: 'error', error: msg.error || 'Conversation unavailable.' })); return true; }
|
|
119
|
+
if (kind === 'list') { save(kind, Object.assign({}, request, { status: 'done', sessions: msg.sessions || [], truncated: msg.truncated })); return true; }
|
|
120
|
+
if (!Number.isSafeInteger(msg.sessionId) || msg.sessionId <= 0 || typeof msg.sessionOriginId !== 'string') return true;
|
|
121
|
+
if (request.selection !== selection() || (store.get('contextualClay') || {}).mode !== 'popover') { save(kind, Object.assign({}, request, { status: 'error', error: 'Your selection changed. The conversation remains in history.' })); return true; }
|
|
122
|
+
if (!guardDraft()) { save(kind, Object.assign({}, request, { status: 'error', error: 'Finish uploading or recording, then check this conversation again.' })); return true; }
|
|
123
|
+
pauseContextualSearchForSelection();
|
|
124
|
+
if (!selectContextualConversation(contextualMate(request.mateId), msg.sessionId, msg.sessionOriginId)) { save(kind, Object.assign({}, request, { status: 'error', error: 'This conversation could not be opened. Your draft is retained.' })); return true; }
|
|
125
|
+
surfaceClose(true); save(kind, Object.assign({}, request, { status: 'done' })); return true;
|
|
126
|
+
}
|
|
127
|
+
function surfaceClose(restoreFocus) { var surface = host(); if (surface) { var menu = surface.querySelector('[data-mate-history]'); var focused = menu.contains(document.activeElement); menu.open = false; if (focused || restoreFocus) menu.querySelector('summary').focus({ preventScroll: true }); } }
|
|
128
|
+
store.subscribe(function (value, previous) {
|
|
129
|
+
if (value.paneMode) return;
|
|
130
|
+
if (value.myUserId !== previous.myUserId) { store.set({ contextualConversationHistory: null, contextualConversationRequest: null }); return; }
|
|
131
|
+
if ((value.contextualClay || {}).mateId !== (previous.contextualClay || {}).mateId) surfaceClose();
|
|
132
|
+
if (previous.connected && !value.connected) ['list', 'action'].forEach(function (kind) { var request = requestState(kind); if (request && request.status === 'loading') save(kind, Object.assign({}, request, { status: 'error', error: 'Disconnected. Check again when connected; your conversation is retained.' })); });
|
|
133
|
+
render();
|
|
134
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { activateContextualMate } from './project-mate-navigation.js';
|
|
4
|
+
import { contextualMate, contextualMateStamp, contextualFrame } from './contextual-mate-identity.js';
|
|
5
|
+
import { showContextualAsk, enterComposer } from './contextual-clay.js';
|
|
6
|
+
import { showToast } from './utils.js';
|
|
7
|
+
|
|
8
|
+
function current() { return store.get('contextualSearchAsk'); }
|
|
9
|
+
function view() { return JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('activeSessionId'), store.get('homeShellVisible'), store.get('homeSubSurface'), store.get('dmMode'), store.get('rightWorkbenchOwner'), store.get('rightWorkbenchRevision')]); }
|
|
10
|
+
function patch(change) { store.set({ contextualSearchAsk: Object.assign({}, current(), change) }); }
|
|
11
|
+
function live(request) {
|
|
12
|
+
var mate = contextualMate(request.mateId);
|
|
13
|
+
return request.ownerId === store.get('myUserId') && mate && mate.builtinKey === 'clay' && contextualMateStamp(mate) === request.stamp;
|
|
14
|
+
}
|
|
15
|
+
function sendCreation() {
|
|
16
|
+
var request = current(), ws = getWs();
|
|
17
|
+
if (!request || !live(request)) { patch({ status: 'error', error: 'Clay is unavailable. Your question is retained.' }); return; }
|
|
18
|
+
if (!store.get('connected') || !ws || ws.readyState !== 1) { patch({ status: 'error', error: 'Reconnect, then retry. Your question is retained.' }); return; }
|
|
19
|
+
patch({ status: 'creating', socket: ws, view: view(), error: null });
|
|
20
|
+
try { ws.send(JSON.stringify({ type: 'home_clay_ask', presentation: 'contextual', requestId: request.requestId, text: request.text, sessionOriginId: request.sessionOriginId || null })); }
|
|
21
|
+
catch (error) { patch({ status: 'error', error: 'Creation is unconfirmed. Retry checks the same request.' }); }
|
|
22
|
+
setTimeout(function () { if (current() && current().requestId === request.requestId && current().status === 'creating') patch({ status: 'error', error: 'Conversation creation is unconfirmed. Retry checks the same request.' }); }, 15000);
|
|
23
|
+
}
|
|
24
|
+
export function pauseContextualSearchForSelection() {
|
|
25
|
+
if (current() && /^(creating|opening)$/.test(current().status)) stop();
|
|
26
|
+
}
|
|
27
|
+
function stop(execution) {
|
|
28
|
+
var request = current(), frame = request && request.sessionId && contextualFrame(request.mateId, request.sessionId);
|
|
29
|
+
if (frame && frame.conversation) { if (execution === true) frame.conversation.stopAsk(); else frame.conversation.pauseAsk(); }
|
|
30
|
+
patch({ status: 'stopped', error: request && request.clientMessageId ? 'Automatic delivery is paused. Inspect the conversation to confirm whether work stopped.' : 'Stopped before submission. Your question is retained; it will not be submitted automatically.' });
|
|
31
|
+
}
|
|
32
|
+
function resume() {
|
|
33
|
+
var request = current();
|
|
34
|
+
if (!request || !live(request)) return;
|
|
35
|
+
if (request.clientMessageId) { showToast('Inspect this conversation before retrying an uncertain message.', 'warn'); return; }
|
|
36
|
+
showContextualAsk(contextualMate(request.mateId), request.requestId, request.sessionId);
|
|
37
|
+
sendCreation();
|
|
38
|
+
}
|
|
39
|
+
function submit() {
|
|
40
|
+
var request = current();
|
|
41
|
+
if (!request || request.status !== 'opening') return;
|
|
42
|
+
if (!live(request) || request.view !== view()) { patch({ status: 'error', error: 'The view changed. Retry explicitly from the current view.' }); return; }
|
|
43
|
+
var frame = contextualFrame(request.mateId, request.sessionId);
|
|
44
|
+
if (!frame || frame.hidden || !frame.conversation || !frame.conversation.identity().ready) return;
|
|
45
|
+
var identity = frame.conversation.identity();
|
|
46
|
+
if (identity.ownerId !== request.ownerId || identity.sessionId !== request.sessionId || identity.sessionOriginId !== request.sessionOriginId || identity.mateId !== request.mateId) { patch({ status: 'error', error: 'The exact conversation identity changed. Nothing was submitted.' }); return; }
|
|
47
|
+
patch({ status: 'sending' });
|
|
48
|
+
var receipt = frame.conversation.submitAsk({ requestId: request.requestId, text: request.text, ownerId: request.ownerId, sessionId: request.sessionId, sessionOriginId: request.sessionOriginId });
|
|
49
|
+
if (!receipt || !receipt.clientMessageId) { patch({ status: 'error', error: 'Submission is unconfirmed. Inspect the retained question before retrying.' }); return; }
|
|
50
|
+
patch({ status: 'sent', clientMessageId: receipt.clientMessageId });
|
|
51
|
+
}
|
|
52
|
+
function render() {
|
|
53
|
+
var host = document.getElementById('contextual-clay');
|
|
54
|
+
if (!host) return;
|
|
55
|
+
var status = host.querySelector('[data-contextual-ask-status]');
|
|
56
|
+
if (!status) {
|
|
57
|
+
status = document.createElement('section'); status.dataset.contextualAskStatus = ''; status.setAttribute('role', 'region'); status.setAttribute('aria-label', 'Pending question');
|
|
58
|
+
status.innerHTML = '<div class="contextual-pending-question"><span>Pending question</span><blockquote data-ask-question></blockquote></div><div class="contextual-pending-progress"><p role="status" aria-live="polite" aria-atomic="true"></p><div><button type="button" data-ask-retry>Retry</button><button type="button" data-ask-stop>Stop</button><button type="button" data-ask-inspect>Open conversation</button></div></div>';
|
|
59
|
+
host.querySelector('.contextual-clay-body').appendChild(status);
|
|
60
|
+
status.querySelector('[data-ask-inspect]').addEventListener('click', function () { var request = current(); if (request && live(request)) activateContextualMate(request.mateId); });
|
|
61
|
+
status.querySelector('[data-ask-retry]').addEventListener('click', resume);
|
|
62
|
+
status.querySelector('[data-ask-stop]').addEventListener('click', function () { stop(true); });
|
|
63
|
+
host.addEventListener('contextual-clay-ready', submit);
|
|
64
|
+
host.addEventListener('contextual-clay-availability', function () {
|
|
65
|
+
var request = current(), frame = request && contextualFrame(request.mateId, request.sessionId);
|
|
66
|
+
if (request && request.status === 'opening' && frame && frame.conversation && frame.conversation.identity().available === false) patch({ status: 'error', error: 'This new conversation is unavailable. Your question is retained.' });
|
|
67
|
+
});
|
|
68
|
+
host.addEventListener('contextual-context-submitted', function (event) {
|
|
69
|
+
var request = current(), frame = request && contextualFrame(request.mateId, request.sessionId);
|
|
70
|
+
if (request && frame && frame.contentWindow === event.detail.source && request.clientMessageId && request.clientMessageId === event.detail.message.clientMessageId) patch({ status: 'acknowledged', acknowledged: true });
|
|
71
|
+
});
|
|
72
|
+
host.addEventListener('contextual-ask-stopped', function (event) { var request = current(), frame = request && contextualFrame(request.mateId, request.sessionId); if (frame && frame.contentWindow === event.detail.source) stop(); });
|
|
73
|
+
}
|
|
74
|
+
var focused = document.activeElement, ownedFocus = status.contains(focused);
|
|
75
|
+
var request = current(), selected = store.get('contextualClay') || {};
|
|
76
|
+
status.hidden = !request || selected.mateId !== request.mateId || request.acknowledged || request.status === 'acknowledged' || selected.sessionId && selected.sessionId !== request.sessionId;
|
|
77
|
+
if (request) {
|
|
78
|
+
status.querySelector('[data-ask-question]').textContent = request.text;
|
|
79
|
+
status.querySelector('p').textContent = request.error || (request.status === 'creating' ? 'Starting a new conversation. Your question is waiting.' : request.status === 'opening' ? 'Preparing this conversation. Nothing submitted yet.' : 'Waiting for confirmation. Delivery is not confirmed yet.');
|
|
80
|
+
status.querySelector('[data-ask-inspect]').hidden = !request.clientMessageId;
|
|
81
|
+
status.querySelector('[data-ask-retry]').hidden = request.status !== 'error' && request.status !== 'stopped' || !!request.clientMessageId;
|
|
82
|
+
status.querySelector('[data-ask-stop]').hidden = request.status === 'stopped';
|
|
83
|
+
}
|
|
84
|
+
host.dataset.askPending = String(!status.hidden);
|
|
85
|
+
var selectedFrame = contextualFrame();
|
|
86
|
+
host.querySelector('[data-clay-loading]').hidden = !status.hidden || !!(selectedFrame && selectedFrame.conversation && selectedFrame.conversation.identity().ready);
|
|
87
|
+
host.querySelectorAll('iframe').forEach(function (frame) { frame.inert = !status.hidden && frame.dataset.mateId === request.mateId && String(frame.dataset.sessionId) === String(request.sessionId); });
|
|
88
|
+
if (status.hidden) {
|
|
89
|
+
enterComposer();
|
|
90
|
+
if (ownedFocus && selectedFrame && selectedFrame.conversation && selectedFrame.conversation.identity().ready) selectedFrame.contentDocument.getElementById('input').focus({ preventScroll: true });
|
|
91
|
+
} else if (ownedFocus && focused.hidden) {
|
|
92
|
+
var nextControl = status.querySelector('[data-ask-retry]:not([hidden]), [data-ask-inspect]:not([hidden]), [data-ask-stop]:not([hidden])');
|
|
93
|
+
if (nextControl) nextControl.focus({ preventScroll: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function startContextualSearch(text) {
|
|
99
|
+
text = typeof text === 'string' ? text.trim().slice(0, 12000) : '';
|
|
100
|
+
var mate = (store.get('cachedMatesList') || []).find(function (item) { return item.builtinKey === 'clay' && contextualMate(item.id); });
|
|
101
|
+
if (!text || !mate) { showToast('Clay is unavailable. Your question remains in Search.', 'warn'); return false; }
|
|
102
|
+
var request = current();
|
|
103
|
+
if (request && /^(creating|opening|sending|sent)$/.test(request.status)) { showToast('Clay is still starting the previous question. This question remains in Search.', 'warn'); return false; }
|
|
104
|
+
var requestId = 'contextual-search-' + crypto.randomUUID();
|
|
105
|
+
if (!showContextualAsk(mate, requestId, null)) return false;
|
|
106
|
+
store.set({ contextualSearchAsk: { requestId: requestId, text: text, mateId: mate.id, ownerId: store.get('myUserId'), stamp: contextualMateStamp(mate), status: 'creating', view: view() } });
|
|
107
|
+
render(); sendCreation(); return true;
|
|
108
|
+
}
|
|
109
|
+
export function retryContextualSearch() {
|
|
110
|
+
var request = current();
|
|
111
|
+
if (!request || !live(request) || request.clientMessageId || !/^(error|stopped)$/.test(request.status)) return false;
|
|
112
|
+
resume(); return true;
|
|
113
|
+
}
|
|
114
|
+
export function handleContextualSearchMessage(msg) {
|
|
115
|
+
var request = current();
|
|
116
|
+
if (!request || request.requestId !== msg.requestId) return false;
|
|
117
|
+
if (msg.type === 'home_mate_error') { patch({ status: 'error', error: msg.text || 'The conversation could not be started. Your question is retained.' }); return true; }
|
|
118
|
+
if (msg.type !== 'home_clay_contextual_session') return false;
|
|
119
|
+
if (request.status !== 'creating' || request.socket !== getWs() || !live(request) || request.mateId !== msg.mateId || !Number.isSafeInteger(msg.sessionId) || msg.sessionId <= 0 || typeof msg.sessionOriginId !== 'string' || request.sessionOriginId && request.sessionOriginId !== msg.sessionOriginId) return true;
|
|
120
|
+
if (request.view !== view()) { patch({ sessionId: msg.sessionId, sessionOriginId: msg.sessionOriginId, status: 'error', error: 'The view changed. Retry explicitly from the current view.' }); return true; }
|
|
121
|
+
patch({ sessionId: msg.sessionId, sessionOriginId: msg.sessionOriginId, status: 'opening' });
|
|
122
|
+
showContextualAsk(contextualMate(request.mateId), request.requestId, msg.sessionId);
|
|
123
|
+
var frame = contextualFrame(request.mateId, msg.sessionId);
|
|
124
|
+
if (frame) frame.dataset.contextualTitle = request.text.slice(0, 80);
|
|
125
|
+
setTimeout(function () { if (current() && current().requestId === request.requestId && current().status === 'opening') patch({ status: 'error', error: 'The conversation is not ready. Retry checks this same conversation.' }); }, 15000);
|
|
126
|
+
submit(); return true;
|
|
127
|
+
}
|
|
128
|
+
store.subscribe(function (value, previous) {
|
|
129
|
+
if (current() && current().ownerId !== value.myUserId) { store.set({ contextualSearchAsk: null }); return; }
|
|
130
|
+
if (current() && !live(current()) && /^(creating|opening)$/.test(current().status)) patch({ status: 'error', error: 'Clay is no longer available. Your question is retained.' });
|
|
131
|
+
if (current() && previous.connected && !value.connected && /^(creating|opening)$/.test(current().status)) patch({ status: 'error', error: 'Disconnected. Retry checks the same new conversation; your question is retained.' });
|
|
132
|
+
render();
|
|
133
|
+
});
|
|
134
|
+
document.addEventListener('click', function (event) {
|
|
135
|
+
var request = current();
|
|
136
|
+
if (request && store.get('currentSlug') === 'mate-' + request.mateId && store.get('activeSessionId') === request.sessionId && event.target.closest('#generation-stop, #autonomous-run-stop, #send-btn.stop')) stop();
|
|
137
|
+
}, true);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Human citation clicks leave the pinned pane intact and use the parent viewer's
|
|
2
|
+
// existing authenticated reference resolver, never a project label as a target.
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { getWs } from './ws-ref.js';
|
|
5
|
+
import { contextualFrame, contextualMate, contextualMateStamp } from './contextual-mate-identity.js';
|
|
6
|
+
import { parseClaySessionReference } from './clay-log-links.js';
|
|
7
|
+
import { requestClayRecordLink } from './command-palette.js';
|
|
8
|
+
|
|
9
|
+
function capture() {
|
|
10
|
+
var frame = contextualFrame(), state = store.get('contextualClay') || {}, mate = contextualMate(state.mateId);
|
|
11
|
+
var identity = frame && frame.conversation && frame.conversation.identity();
|
|
12
|
+
if (!mate || state.mode !== 'popover' || !frame || frame.hidden || frame.inert || !identity || !identity.ready || frame.conversation.source !== frame.contentWindow || identity.ownerId !== store.get('myUserId') || identity.mateId !== mate.id || frame.dataset.identityStamp !== contextualMateStamp(mate)) return null;
|
|
13
|
+
var surface = document.getElementById('contextual-clay');
|
|
14
|
+
return { mateId: mate.id, stamp: contextualMateStamp(mate), sessionId: identity.sessionId, originId: identity.sessionOriginId, ownerId: identity.ownerId, view: surface.dataset.screenReference, socket: getWs() };
|
|
15
|
+
}
|
|
16
|
+
export function validContextualCitation(context) {
|
|
17
|
+
var live = capture();
|
|
18
|
+
return !!(context && live && live.socket === context.socket && live.mateId === context.mateId && live.stamp === context.stamp && live.sessionId === context.sessionId && live.originId === context.originId && live.ownerId === context.ownerId && live.view === context.view);
|
|
19
|
+
}
|
|
20
|
+
export function contextualCitationLink(context, requestId) {
|
|
21
|
+
var frames = Array.from(document.querySelectorAll('#contextual-clay iframe'));
|
|
22
|
+
for (var frame of frames) {
|
|
23
|
+
var mate = contextualMate(frame.dataset.mateId), identity = frame.conversation && frame.conversation.identity();
|
|
24
|
+
if (!mate || !identity || identity.ownerId !== store.get('myUserId') || frame.dataset.identityStamp !== contextualMateStamp(mate)) continue;
|
|
25
|
+
if (context && (identity.mateId !== context.mateId || identity.sessionId !== context.sessionId || identity.sessionOriginId !== context.originId || context.ownerId !== identity.ownerId)) continue;
|
|
26
|
+
var link = Array.from(frame.contentDocument.querySelectorAll('.clayos-session-link')).find(function (item) { return item.dataset.sessionRequest === requestId; });
|
|
27
|
+
if (link) return link;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export function attachContextualSessionCitations(surface) {
|
|
32
|
+
surface.resolveSessionCitation = function (source, link) {
|
|
33
|
+
var context = capture(), frame = contextualFrame();
|
|
34
|
+
if (!context || !frame || frame.contentWindow !== source || !link || link.ownerDocument !== frame.contentDocument || !link.closest('.msg-assistant') || !parseClaySessionReference(link.dataset.sessionRef || '')) return false;
|
|
35
|
+
context.ref = link.dataset.sessionRef;
|
|
36
|
+
return requestClayRecordLink(link, context);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function forwardContextualSessionCitation(link) {
|
|
40
|
+
if (!store.get('paneMode') || new URLSearchParams(location.search).get('contextual') !== '1') return false;
|
|
41
|
+
try {
|
|
42
|
+
var surface = window.parent.document.getElementById('contextual-clay');
|
|
43
|
+
if (surface && surface.resolveSessionCitation && surface.resolveSessionCitation(window, link)) return true;
|
|
44
|
+
} catch (error) { /* A changed authentication document cannot receive clicks. */ }
|
|
45
|
+
if (link.classList.contains('is-error')) return true;
|
|
46
|
+
link.classList.add('is-error'); link.title = 'This conversation or view changed. Reopen it before navigating.';
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Project transport replacement may discard an old reply before the palette
|
|
50
|
+
// sees it. Retire that pending UI receipt at the context change itself.
|
|
51
|
+
store.subscribe(function (value) {
|
|
52
|
+
var pending = value.sessionNavigationRequestContext;
|
|
53
|
+
if (value.paneMode || !pending || !pending.citation) return;
|
|
54
|
+
if (pending.projectSlug === value.currentSlug && pending.sessionId === value.activeSessionId && validContextualCitation(pending.citation)) return;
|
|
55
|
+
var link = contextualCitationLink(pending.citation, value.sessionNavigationRequestId);
|
|
56
|
+
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; }
|
|
57
|
+
store.set({ sessionNavigationRequestId: null, sessionNavigationRequestContext: null });
|
|
58
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { presentedDocument, refreshPresentedDocument, closePresentedDocument } from './clay-studio-document.js';
|
|
2
|
+
import { registerStudioPaint } from './clay-studio-render.js';
|
|
1
3
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
4
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
3
5
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf } from './markdown.js';
|
|
@@ -192,6 +194,9 @@ export function initFileBrowser(_ctx) {
|
|
|
192
194
|
|
|
193
195
|
document.getElementById("file-viewer-download").addEventListener("click", function () {
|
|
194
196
|
if (!currentFilePath) return;
|
|
197
|
+
if ((store.get("studioDocumentView") || {}).descriptor && store.get("studioDocumentView").descriptor.path === currentFilePath) {
|
|
198
|
+
var link = document.createElement('a'); link.href = URL.createObjectURL(new Blob([currentContent || ''], { type: 'text/markdown;charset=utf-8' })); link.download = currentFilePath.split('/').pop(); link.click(); setTimeout(function () { URL.revokeObjectURL(link.href); }, 1000); return;
|
|
199
|
+
}
|
|
195
200
|
downloadProjectFile(currentFilePath);
|
|
196
201
|
});
|
|
197
202
|
|
|
@@ -459,12 +464,14 @@ function handleTreeKeyDown(e) {
|
|
|
459
464
|
|
|
460
465
|
// --- File watch helpers ---
|
|
461
466
|
function sendWatch(filePath) {
|
|
467
|
+
if ((store.get("studioDocumentView") || {}).descriptor && store.get("studioDocumentView").descriptor.path === filePath) return;
|
|
462
468
|
if (ctx.ws && ctx.connected) {
|
|
463
469
|
ctx.ws.send(JSON.stringify({ type: "fs_watch", path: filePath }));
|
|
464
470
|
}
|
|
465
471
|
}
|
|
466
472
|
|
|
467
473
|
function sendUnwatch() {
|
|
474
|
+
if (store.get("studioDocumentView")) { closePresentedDocument(); return; }
|
|
468
475
|
if (ctx.ws && ctx.connected) {
|
|
469
476
|
ctx.ws.send(JSON.stringify({ type: "fs_unwatch" }));
|
|
470
477
|
}
|
|
@@ -532,6 +539,7 @@ export function resetFileBrowser() {
|
|
|
532
539
|
treeData = {};
|
|
533
540
|
currentContent = null;
|
|
534
541
|
currentFilePath = null;
|
|
542
|
+
store.set({ clayControlFileView: null });
|
|
535
543
|
isRendered = false;
|
|
536
544
|
currentIsMarkdown = false;
|
|
537
545
|
currentIsSvg = false;
|
|
@@ -568,6 +576,7 @@ export function openFile(filePath, opts) {
|
|
|
568
576
|
if (!state.connected) return false;
|
|
569
577
|
if (opts && opts.projectSlug && opts.projectSlug !== state.currentSlug) return false;
|
|
570
578
|
if (opts && opts.sessionId && String(opts.sessionId) !== String(state.activeSessionId)) return false;
|
|
579
|
+
closePresentedDocument();
|
|
571
580
|
var workbenchRevision = claimRightWorkbench("file-viewer");
|
|
572
581
|
if (followedFileChanged(filePath) || (opts && opts.diff && isFollowingMarkdown(filePath))) {
|
|
573
582
|
cancelMarkdownFollow();
|
|
@@ -635,6 +644,7 @@ export function presentMarkdownEdit(msg) {
|
|
|
635
644
|
content: typeof msg.content === "string" ? msg.content : "",
|
|
636
645
|
size: msg.size || 0,
|
|
637
646
|
});
|
|
647
|
+
registerStudioPaint(msg, document.querySelector("#file-viewer .file-viewer-header"), null, document.getElementById("file-viewer-body"));
|
|
638
648
|
}
|
|
639
649
|
|
|
640
650
|
function followedFileChanged(filePath) {
|
|
@@ -723,6 +733,7 @@ function requestDirectory(dirPath) {
|
|
|
723
733
|
}
|
|
724
734
|
|
|
725
735
|
function requestFileContent(filePath) {
|
|
736
|
+
if (refreshPresentedDocument(filePath)) return;
|
|
726
737
|
var requestId = "file-read-" + Date.now() + "-" + Math.random().toString(36).slice(2);
|
|
727
738
|
var state = store.snap();
|
|
728
739
|
store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null, workbenchRevision: store.get('rightWorkbenchRevision') || 0 } });
|
|
@@ -1148,6 +1159,7 @@ function renderEntries(container, entries, depth) {
|
|
|
1148
1159
|
// --- File viewer ---
|
|
1149
1160
|
|
|
1150
1161
|
function showFileContent(msg, expectedRevision) {
|
|
1162
|
+
var priorTarget = document.querySelector("#file-viewer .file-viewer-header"); if (priorTarget) delete priorTarget.dataset.controlSurface;
|
|
1151
1163
|
if (!updateFileViewerTab(msg.path, { content: msg.content })) return;
|
|
1152
1164
|
if (expectedRevision != null && !isRightWorkbenchCurrent("file-viewer", expectedRevision)) return;
|
|
1153
1165
|
var pathEl = document.getElementById("file-viewer-path");
|
|
@@ -1170,6 +1182,8 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1170
1182
|
pendingRefresh = false;
|
|
1171
1183
|
currentContent = null;
|
|
1172
1184
|
currentFilePath = msg.path;
|
|
1185
|
+
var sourceView = store.get('studioDocumentView'), sourceDescriptor = sourceView && sourceView.descriptor.path === msg.path ? sourceView.descriptor : null;
|
|
1186
|
+
store.set({ clayControlFileView: { path: msg.path, revision: msg.controlRevision || null, projectSlug: sourceDescriptor ? sourceDescriptor.sourceSlug : store.get('currentSlug'), runtimeId: sourceDescriptor ? sourceDescriptor.runtimeId : store.get('controlRuntimeId') } });
|
|
1173
1187
|
currentIsMarkdown = false;
|
|
1174
1188
|
currentIsSvg = false;
|
|
1175
1189
|
store.set({ fileViewerIsPlantUml: false, fileViewerIsHtml: false });
|
|
@@ -1279,6 +1293,8 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1279
1293
|
store.set({ pendingFileNavigation: null });
|
|
1280
1294
|
}
|
|
1281
1295
|
|
|
1296
|
+
var sourceView = presentedDocument(msg.path);
|
|
1297
|
+
if (sourceView) Array.from(bodyEl.querySelectorAll('.clay-file-link')).forEach(function (link) { link.dataset.fileProjectSlug = sourceView.descriptor.sourceSlug; link.dataset.fileSessionId = String(sourceView.descriptor.sourceSessionId); });
|
|
1282
1298
|
// Request edit history for this file (skip on auto-refresh)
|
|
1283
1299
|
if (!keepRenderState) {
|
|
1284
1300
|
historyVisible = false;
|
|
@@ -1361,7 +1377,9 @@ function pathsReferToSameFile(left, right) {
|
|
|
1361
1377
|
return a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
|
|
1362
1378
|
}
|
|
1363
1379
|
|
|
1364
|
-
export function handleFileChanged(msg) {
|
|
1380
|
+
export function handleFileChanged(msg, presentedSource) {
|
|
1381
|
+
var source = store.get('studioDocumentView');
|
|
1382
|
+
if (source && source.descriptor.path === msg.path && (!presentedSource || presentedDocument(msg.path) !== presentedSource)) return;
|
|
1365
1383
|
if (!msg.path || !pathsReferToSameFile(msg.path, currentFilePath)) return;
|
|
1366
1384
|
if (ctx.fileViewerEl.classList.contains("hidden")) return;
|
|
1367
1385
|
if (historyVisible || inlineDiffActive) return;
|
|
@@ -1602,6 +1620,7 @@ function formatSize(bytes) {
|
|
|
1602
1620
|
// --- File edit history ---
|
|
1603
1621
|
|
|
1604
1622
|
function requestFileHistory(filePath) {
|
|
1623
|
+
if ((store.get("studioDocumentView") || {}).descriptor && store.get("studioDocumentView").descriptor.path === filePath) return;
|
|
1605
1624
|
if (ctx.ws && ctx.connected) {
|
|
1606
1625
|
ctx.ws.send(JSON.stringify({ type: "fs_file_history", path: filePath }));
|
|
1607
1626
|
}
|
|
@@ -2,6 +2,13 @@ import { store } from './store.js';
|
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
3
|
import { isDriverOperatedView } from './worker-pane-lock.js';
|
|
4
4
|
|
|
5
|
+
export function stopGeneration() {
|
|
6
|
+
var ws = getWs();
|
|
7
|
+
if (!store.get('processing') || !store.get('connected') || !ws || ws.readyState !== 1) return false;
|
|
8
|
+
ws.send(JSON.stringify({ type: 'stop' }));
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
// Fallback Stop control for when Send cannot double as Stop: a draft sitting
|
|
6
13
|
// in the composer (Send stays Send so the draft can still go out), or a
|
|
7
14
|
// locked configured Worker pane (Send and the rest of the composer are
|
|
@@ -31,11 +38,7 @@ export function initGenerationStop() {
|
|
|
31
38
|
button.title = store.get('connected') ? 'Stop generating' : 'Reconnect to stop generating';
|
|
32
39
|
place();
|
|
33
40
|
}
|
|
34
|
-
button.addEventListener('click',
|
|
35
|
-
var ws = getWs();
|
|
36
|
-
if (!store.get('processing') || !store.get('connected') || !ws || ws.readyState !== 1) return;
|
|
37
|
-
ws.send(JSON.stringify({ type: 'stop' }));
|
|
38
|
-
});
|
|
41
|
+
button.addEventListener('click', stopGeneration);
|
|
39
42
|
store.subscribe(function (state, previous) {
|
|
40
43
|
if (state.processing !== previous.processing || state.connected !== previous.connected
|
|
41
44
|
|| state.splitGroups !== previous.splitGroups || state.activeSessionId !== previous.activeSessionId
|
|
@@ -103,7 +103,8 @@ function renderFileRow(file, staged) {
|
|
|
103
103
|
var actionTitle = staged ? "Unstage file — keep its changes" : "Stage file for commit";
|
|
104
104
|
var actionIcon = staged ? "minus" : "plus";
|
|
105
105
|
var actionsOpen = expandedFileActionsPath === file.path;
|
|
106
|
-
|
|
106
|
+
var control = currentStatus && currentStatus.controlView, view = control && control.files.find(function (item) { return item.path === file.path; });
|
|
107
|
+
return '<div data-control-git-path="' + escapeHtml(file.path) + '" data-control-revision="' + escapeHtml(view && view.revision || '') + '" data-control-runtime="' + escapeHtml(control && control.runtimeId || '') + '" class="git-file-row' + (actionsOpen ? ' active' : '') + '">' +
|
|
107
108
|
'<span class="git-file-status' + statusClass + '">' + escapeHtml(fileStatusLabel(file, staged)) + '</span>' +
|
|
108
109
|
'<div class="git-file-main" data-git-file-item="' + pathKey + '" tabindex="0" aria-label="File actions for ' + escapeHtml(parts.name) + '" aria-expanded="' + (actionsOpen ? 'true' : 'false') + '">' +
|
|
109
110
|
'<div class="git-file-name">' + escapeHtml(parts.name) + '</div>' +
|
|
@@ -282,6 +283,7 @@ export function refreshGitStatus() {
|
|
|
282
283
|
return requestJson(basePath + "api/git/status", { cache: "no-store" }).then(function (status) {
|
|
283
284
|
if (statusBasePath !== basePath) return status;
|
|
284
285
|
currentStatus = status;
|
|
286
|
+
store.set({ clayControlGitView: status.controlView ? Object.assign({ projectSlug: store.get('currentSlug') }, status.controlView) : null });
|
|
285
287
|
if (expandedFileActionsPath && !status.files.some(function (file) { return file.path === expandedFileActionsPath; })) {
|
|
286
288
|
expandedFileActionsPath = null;
|
|
287
289
|
}
|
|
@@ -81,7 +81,8 @@ export function showIconTooltipHtml(el, html, options) {
|
|
|
81
81
|
export function bindIconTooltip(el, resolveMetadata) {
|
|
82
82
|
function show() {
|
|
83
83
|
var metadata = tooltipMetadata(typeof resolveMetadata === "function" ? resolveMetadata() : resolveMetadata);
|
|
84
|
-
|
|
84
|
+
if (!metadata.text) { hideIconTooltip(); return; }
|
|
85
|
+
showIconTooltip(el, metadata.text, metadata);
|
|
85
86
|
}
|
|
86
87
|
el.addEventListener("mouseenter", show);
|
|
87
88
|
el.addEventListener("focus", show);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { contextualAccessReference } from './contextual-mate-context.js';
|
|
2
|
+
import { contextualScreenReference } from './contextual-clay-session.js';
|
|
1
3
|
import { stopSTT } from './stt-controller.js';
|
|
2
4
|
import { expectCreatedSession } from './session-create.js';
|
|
3
5
|
import { hasRecordedSpeechFor, requestRecordedSend } from './stt-recording.js';
|
|
@@ -120,6 +122,7 @@ function showPreThinkingForTurn() {
|
|
|
120
122
|
|
|
121
123
|
// --- Send ---
|
|
122
124
|
export function sendMessage() {
|
|
125
|
+
if (store.get('contextualWriterSuspended')) return;
|
|
123
126
|
if (ctx && requestRecordedSend(ctx.inputEl, sendMessage)) return;
|
|
124
127
|
stopSTT();
|
|
125
128
|
// Debate ended mode intercept: route to resume handler
|
|
@@ -349,7 +352,9 @@ export function sendMessage() {
|
|
|
349
352
|
return;
|
|
350
353
|
}
|
|
351
354
|
|
|
355
|
+
var screenReference = contextualScreenReference();
|
|
352
356
|
var payload = { type: "message", text: text || "" };
|
|
357
|
+
if (screenReference) payload.contextualSnapshot = screenReference;
|
|
353
358
|
if (images.length > 0) {
|
|
354
359
|
payload.images = images;
|
|
355
360
|
}
|
|
@@ -359,6 +364,8 @@ export function sendMessage() {
|
|
|
359
364
|
// Include selected vendor for session binding (server uses on first message)
|
|
360
365
|
var _selVendor = store.get("currentVendor") || null;
|
|
361
366
|
if (_selVendor) payload.vendor = _selVendor;
|
|
367
|
+
var contextRef = contextualAccessReference();
|
|
368
|
+
if (contextRef) payload.mateContextRef = contextRef;
|
|
362
369
|
if (!prepareAutonomousPayload(payload)) return;
|
|
363
370
|
ctx.currentMsgTs = Date.now();
|
|
364
371
|
sendAcknowledgedMessage(payload);
|
|
@@ -448,7 +455,9 @@ export function sendShellResultToAgent(msg) {
|
|
|
448
455
|
|
|
449
456
|
export function autoResize() {
|
|
450
457
|
ctx.inputEl.style.height = "auto";
|
|
451
|
-
ctx.inputEl.
|
|
458
|
+
var border = ctx.inputEl.offsetHeight - ctx.inputEl.clientHeight;
|
|
459
|
+
ctx.inputEl.style.height = Math.min(ctx.inputEl.scrollHeight + border, 120) + "px";
|
|
460
|
+
ctx.inputEl.style.overflowY = ctx.inputEl.scrollHeight > ctx.inputEl.clientHeight ? "auto" : "hidden";
|
|
452
461
|
// Defensive: sync send/stop button whenever input size changes
|
|
453
462
|
if (ctx.processing && ctx.setSendBtnMode) {
|
|
454
463
|
ctx.setSendBtnMode(hasSendableContent() ? "send" : "stop");
|
|
@@ -533,6 +542,30 @@ function removePendingImage(idx) {
|
|
|
533
542
|
renderInputPreviews();
|
|
534
543
|
}
|
|
535
544
|
|
|
545
|
+
// Move composer ownership between two authenticated views of the same session.
|
|
546
|
+
export function takeComposerDraft() {
|
|
547
|
+
if (uploadingCount) throw new Error('Wait for attachments to finish uploading before opening Clay.');
|
|
548
|
+
if (store.get('speechActive') || hasRecordedSpeechFor(ctx.inputEl)) throw new Error('Finish or discard the voice recording before changing Clay views.');
|
|
549
|
+
var input = ctx.inputEl;
|
|
550
|
+
var draft = { text: input.value, start: input.selectionStart, end: input.selectionEnd, direction: input.selectionDirection, images: pendingImages, pastes: pendingPastes, files: pendingFiles, delay: scheduleDelayMs };
|
|
551
|
+
input.value = '';
|
|
552
|
+
clearPendingImages();
|
|
553
|
+
clearScheduleDelay();
|
|
554
|
+
autoResize();
|
|
555
|
+
return draft;
|
|
556
|
+
}
|
|
557
|
+
export function restoreComposerDraft(draft) {
|
|
558
|
+
if (!draft) return;
|
|
559
|
+
pendingImages = draft.images || [];
|
|
560
|
+
pendingPastes = draft.pastes || [];
|
|
561
|
+
pendingFiles = draft.files || [];
|
|
562
|
+
ctx.inputEl.value = draft.text || '';
|
|
563
|
+
ctx.inputEl.setSelectionRange(draft.start || 0, draft.end || 0, draft.direction || 'none');
|
|
564
|
+
setScheduleDelayMs(draft.delay || 0);
|
|
565
|
+
renderInputPreviews();
|
|
566
|
+
autoResize();
|
|
567
|
+
}
|
|
568
|
+
|
|
536
569
|
export function clearPendingImages() {
|
|
537
570
|
pendingImages = [];
|
|
538
571
|
pendingPastes = [];
|
|
@@ -840,6 +873,7 @@ function sendInputSync() {
|
|
|
840
873
|
}
|
|
841
874
|
|
|
842
875
|
export function handleInputSync(text) {
|
|
876
|
+
if (store.get('contextualWriterSuspended')) return;
|
|
843
877
|
isRemoteInput = true;
|
|
844
878
|
ctx.inputEl.value = text;
|
|
845
879
|
autoResize();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
1
2
|
import { githubWorkMarkup } from './session-github.js';
|
|
2
3
|
import { issueGithubAction } from './issues-github.js';
|
|
3
4
|
import { escapeHtml } from './utils.js';
|
|
@@ -18,7 +19,7 @@ export function renderIssueList(result) {
|
|
|
18
19
|
var meta = [authorLine(entry), relativeTime(entry.updatedAt || entry.createdAt)];
|
|
19
20
|
if (entry.revision > 1) meta.push('v' + entry.revision);
|
|
20
21
|
if (entry.commentCount) meta.push(entry.commentCount + (entry.commentCount === 1 ? ' comment' : ' comments'));
|
|
21
|
-
return '<button type="button" class="issue-row" data-open-issue="' + esc(entry.ref) + '">' +
|
|
22
|
+
return '<button type="button" class="issue-row" data-control-runtime="' + esc(store.get("controlRuntimeId")) + '" data-control-revision="' + esc(entry.revision) + '" data-open-issue="' + esc(entry.ref) + '">' +
|
|
22
23
|
'<div class="issue-row-head"><span class="issue-type">' + esc(entry.type) + '</span>' + priorityChip(entry.priority) + '<strong>' + esc(entry.title) + '</strong></div>' +
|
|
23
24
|
(entry.summary ? '<p>' + esc(entry.summary) + '</p>' : '') +
|
|
24
25
|
'<div class="issue-row-meta">' + statusChip(entry.status) + '<span>' + esc(meta.filter(Boolean).join(' · ')) + '</span></div></button>';
|
|
@@ -35,7 +36,7 @@ export function renderIssueDetail(entry) {
|
|
|
35
36
|
var closed = entry.status === 'resolved' || entry.status === 'closed';
|
|
36
37
|
var time = entry.updatedAt || entry.createdAt;
|
|
37
38
|
var byline = [authorLine(entry), time ? new Date(time).toLocaleString() : '', 'v' + entry.revision].filter(Boolean).join(' · ');
|
|
38
|
-
return '<article class="issue-detail"><div class="issue-doc-chips"><span class="issue-type">' + esc(entry.type) + '</span>' + priorityChip(entry.priority) + statusChip(entry.status) + '</div>' +
|
|
39
|
+
return '<article class="issue-detail" data-control-ref="' + esc(entry.ref) + '" data-control-runtime="' + esc(store.get("controlRuntimeId")) + '" data-control-revision="' + esc(entry.revision) + '"><div class="issue-doc-chips"><span class="issue-type">' + esc(entry.type) + '</span>' + priorityChip(entry.priority) + statusChip(entry.status) + '</div>' +
|
|
39
40
|
githubWorkMarkup(entry, true) +
|
|
40
41
|
'<h2 class="issue-doc-title">' + esc(entry.title) + '</h2>' + prose(entry.summary, 'issue-doc-summary') + '<div class="issue-byline">' + esc(byline) + '</div>' +
|
|
41
42
|
'<div class="issue-actions">' + action('data-issue-work', 'play', 'Start work', ' class="issue-primary"' + (closed ? ' disabled title="Reopen this issue to start work"' : '')) +
|