clay-server 5.3.1-beta.1 → 5.3.1-beta.3
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/contextual-screen-snapshot.js +13 -4
- package/lib/project-shared-browser.js +69 -21
- package/lib/project.js +2 -0
- package/lib/public/css/issues.css +1 -1
- package/lib/public/css/linear.css +7 -24
- package/lib/public/css/menus.css +2 -2
- package/lib/public/css/notifications-center.css +2 -2
- package/lib/public/css/split-view-layout.css +0 -16
- package/lib/public/css/tool-pane.css +12 -0
- package/lib/public/modules/contextual-clay-session.js +2 -2
- package/lib/public/modules/contextual-mate-context.js +7 -3
- package/lib/public/modules/contextual-view-metadata.js +23 -0
- package/lib/public/modules/linear-panel.js +5 -5
- package/lib/public/modules/pane-activation.js +5 -1
- package/lib/public/modules/shared-browser-tabs.js +4 -2
- package/lib/public/modules/split-view-presentations.js +3 -1
- package/lib/public/modules/split-view-worker-scroll.js +1 -1
- package/lib/public/modules/split-view-workers.js +20 -51
- package/lib/public/modules/tool-pane-chrome.js +3 -0
- package/lib/public/modules/tool-pane-drag.js +1 -1
- package/lib/public/modules/tool-pane-layout.js +4 -3
- package/lib/public/modules/workbench-window-controls.js +39 -0
- package/lib/public/pane-assets/{app-DR227IBJ.js → app-BDYH6Y7R.js} +1 -1
- package/lib/public/pane-assets/{chunk-UQPN3SQX.js → chunk-42C2CZEP.js} +1 -1
- package/lib/public/pane-assets/chunk-46OOGVHU.js +1 -0
- package/lib/public/pane-assets/chunk-4OJW4FF4.js +1 -0
- package/lib/public/pane-assets/chunk-E6NRPGVH.js +2 -0
- package/lib/public/pane-assets/chunk-F7VIE6FJ.js +1 -0
- package/lib/public/pane-assets/chunk-FCJYJLVZ.js +1 -0
- package/lib/public/pane-assets/chunk-FEJDEXOW.js +1 -0
- package/lib/public/pane-assets/chunk-FQQMXMH7.js +168 -0
- package/lib/public/pane-assets/chunk-HEMCIILG.js +1 -0
- package/lib/public/pane-assets/chunk-HWLBH6AL.js +1 -0
- package/lib/public/pane-assets/{chunk-KWUKYV6X.js → chunk-KB4T5B2R.js} +1 -1
- package/lib/public/pane-assets/chunk-KF3LZRJK.js +1 -0
- package/lib/public/pane-assets/{chunk-MLNNDUPE.js → chunk-N5SIEIOG.js} +1 -1
- package/lib/public/pane-assets/{chunk-YU4R2HSO.js → chunk-QRVKAVH7.js} +1 -1
- package/lib/public/pane-assets/chunk-RV5O66TO.js +1 -0
- package/lib/public/pane-assets/{chunk-4KFYASLC.js → chunk-TMUCEZ7C.js} +1 -1
- package/lib/public/pane-assets/{chunk-3CQA7ZCS.js → chunk-TT3NECKK.js} +1 -1
- package/lib/public/pane-assets/{chunk-FK43VMMW.js → chunk-U5GH2UJM.js} +1 -1
- package/lib/public/pane-assets/{chunk-5P3D2RTQ.js → chunk-UMIYQYGM.js} +1 -1
- package/lib/public/pane-assets/chunk-VCNLKOVT.js +1 -0
- package/lib/public/pane-assets/{chunk-3VNXCTTY.js → chunk-X452YCE5.js} +1 -1
- package/lib/public/pane-assets/{chunk-LLBLEJM4.js → chunk-XI2DJM4H.js} +1 -1
- package/lib/public/pane-assets/chunk-YE45RR7W.js +1 -0
- package/lib/public/pane-assets/manifest.json +390 -388
- package/lib/public/pane-assets/style-VDIGFFLR.css +1 -0
- package/lib/shared-browser-workspace.js +28 -0
- package/lib/workbench-window-layout.js +7 -5
- package/package.json +1 -1
- package/lib/public/pane-assets/chunk-3SZABBN7.js +0 -1
- package/lib/public/pane-assets/chunk-A3MDESCE.js +0 -1
- package/lib/public/pane-assets/chunk-G6ZL6VPR.js +0 -2
- package/lib/public/pane-assets/chunk-GMASVOWL.js +0 -1
- package/lib/public/pane-assets/chunk-HQUGZW4P.js +0 -1
- package/lib/public/pane-assets/chunk-LA7KKFI2.js +0 -1
- package/lib/public/pane-assets/chunk-MOSMN3CU.js +0 -1
- package/lib/public/pane-assets/chunk-MUAMABNY.js +0 -1
- package/lib/public/pane-assets/chunk-TABMZ63N.js +0 -1
- package/lib/public/pane-assets/chunk-TEHDJOM4.js +0 -1
- package/lib/public/pane-assets/chunk-UNTZB4WP.js +0 -166
- package/lib/public/pane-assets/chunk-WQE7BLFG.js +0 -1
- package/lib/public/pane-assets/chunk-XFUKDMBX.js +0 -1
- package/lib/public/pane-assets/style-BSKKK54Q.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Bounded display metadata supplied by the viewer. Never an authority token.
|
|
2
2
|
var provenance = require('./session-provenance');
|
|
3
3
|
var normalizeClientMessageId = require('./project-message-delivery').normalizeClientMessageId;
|
|
4
|
-
var fields = { version: true, viewId: true, projectTitle: true, conversationTitle: true, projectSlug: true, sessionId: true, screen: true };
|
|
4
|
+
var fields = { version: true, viewId: true, projectTitle: true, conversationTitle: true, projectSlug: true, sessionId: true, screen: true, workspace: true };
|
|
5
5
|
function validate(value) {
|
|
6
6
|
if (!value || typeof value !== 'object' || Array.isArray(value) || value.version !== 1 || Object.keys(value).some(function (key) { return !Object.prototype.hasOwnProperty.call(fields, key); })) throw new Error('Invalid contextual snapshot.');
|
|
7
7
|
if (typeof value.viewId !== 'string' || !/^context-[A-Za-z0-9-]{1,90}$/.test(value.viewId)) throw new Error('Invalid snapshot view identity.');
|
|
@@ -10,7 +10,16 @@ function validate(value) {
|
|
|
10
10
|
});
|
|
11
11
|
if (value.sessionId !== null && (!Number.isSafeInteger(value.sessionId) || value.sessionId < 1)) throw new Error('Invalid snapshot conversation.');
|
|
12
12
|
if (['Home', 'Project', 'Direct message'].indexOf(value.screen) < 0) throw new Error('Invalid snapshot screen.');
|
|
13
|
-
|
|
13
|
+
if (value.workspace != null) validateWorkspace(value.workspace);
|
|
14
|
+
return JSON.parse(JSON.stringify(value));
|
|
15
|
+
}
|
|
16
|
+
function validateWorkspace(value) {
|
|
17
|
+
function exact(item, keys) { return item && typeof item === 'object' && !Array.isArray(item) && Object.keys(item).length === keys.length && keys.every(function (key) { return Object.prototype.hasOwnProperty.call(item, key); }); }
|
|
18
|
+
function label(value) { return value === null || typeof value === 'string' && value.length <= 160 && !/[\u0000-\u001f\u007f]/.test(value); }
|
|
19
|
+
if (!exact(value, ['name', 'sessions', 'workbenches']) || !label(value.name) || !Array.isArray(value.sessions) || value.sessions.length > 32 || !Array.isArray(value.workbenches) || value.workbenches.length > 16 ||
|
|
20
|
+
!value.workbenches.every(function (name) { return typeof name === 'string' && label(name); }) || !value.sessions.every(function (session) {
|
|
21
|
+
return exact(session, ['sessionId', 'title', 'selected', 'focused']) && Number.isSafeInteger(session.sessionId) && session.sessionId > 0 && label(session.title) && typeof session.selected === 'boolean' && typeof session.focused === 'boolean';
|
|
22
|
+
})) throw new Error('Invalid contextual workspace metadata.');
|
|
14
23
|
}
|
|
15
24
|
function attachContextualScreenSnapshot(ctx) {
|
|
16
25
|
function capture(ws, session, msg) {
|
|
@@ -19,11 +28,11 @@ function attachContextualScreenSnapshot(ctx) {
|
|
|
19
28
|
function assertLive() {
|
|
20
29
|
var owner = ctx.getProjectOwnerId(), actor = ws && ws._clayUser;
|
|
21
30
|
var mate = ctx.isMate && ctx.matesModule.getMate(ctx.matesModule.buildMateCtx(owner), ctx.mateId);
|
|
22
|
-
if (!mate || mate.
|
|
31
|
+
if (!mate || mate.archived || mate.lifecycleDeletion || ctx.sm.sessions.get(session.localId) !== session || session.hidden || session.destroying || session.spawn || provenance.isWorker(session) || session.delegated || session._delegatedBy || session._pairDelegation || session._mcpForeignInput || session._mentionInProgress || session.homeDebatePlanning || session.debateSetupMode || session.mateCreationMode || session.sessionVisibility === 'shared') throw new Error('Snapshots require a private owner Mate conversation.');
|
|
23
32
|
if (ctx.usersModule.isMultiUser() && (!actor || actor.id !== owner || session.ownerId !== owner || mate.createdBy !== owner || !ctx.usersModule.findUserById(owner) || !ctx.usersModule.canAccessSession(owner, session, ctx.getProjectAccess()))) throw new Error('Snapshot ownership is unavailable.');
|
|
24
33
|
if (!ctx.usersModule.isMultiUser() && (session.ownerId && session.ownerId !== owner || actor && actor.id !== owner && actor.id !== 'default')) throw new Error('Snapshot ownership changed.');
|
|
25
34
|
if (!normalizeClientMessageId(msg.clientMessageId) || !msg.sessionOriginId || msg.sessionOriginId !== session.sessionOriginId || String(msg.sessionId) !== String(session.localId) || msg.projectSlug !== ctx.slug || msg.accountId !== (actor && actor.id || '_default')) throw new Error('Snapshot delivery identity changed.');
|
|
26
|
-
var current = JSON.stringify([mate.id, mate.createdAt, mate.createdBy, session.sessionOriginId]);
|
|
35
|
+
var current = JSON.stringify([mate.id, mate.builtinKey, mate.createdAt, mate.createdBy, session.sessionOriginId]);
|
|
27
36
|
if (stamp && stamp !== current) throw new Error('Snapshot source identity changed.');
|
|
28
37
|
stamp = current;
|
|
29
38
|
}
|
|
@@ -3,6 +3,7 @@ var crypto = require("crypto");
|
|
|
3
3
|
var policy = require("./shared-browser-policy");
|
|
4
4
|
var browserTools = require("./shared-browser-tools");
|
|
5
5
|
var createRuntime = require("./shared-browser-runtime").createRuntime;
|
|
6
|
+
var workspace = require('./shared-browser-workspace');
|
|
6
7
|
|
|
7
8
|
function attachSharedBrowser(ctx) {
|
|
8
9
|
var storageStore = ctx.storageStore || require("./shared-browser-storage").attachStorageStore(require("./config").CONFIG_DIR);
|
|
@@ -28,7 +29,7 @@ function attachSharedBrowser(ctx) {
|
|
|
28
29
|
return identity ? JSON.stringify([identity.uid, identity.gid, identity.home]) : "default";
|
|
29
30
|
}
|
|
30
31
|
function recordAllowed(record, ws) {
|
|
31
|
-
try { return allowed(record.session, ws) && record.ownerId === record.session.ownerId && record.identity === identityKey(record.session); }
|
|
32
|
+
try { return allowed(record.session, ws) && record.ownerId === record.session.ownerId && record.identity === identityKey(record.session) && record.sourceStamp === workspace.sourceStamp(record.session); }
|
|
32
33
|
catch (e) { return false; }
|
|
33
34
|
}
|
|
34
35
|
function requireAccess(session, ws) {
|
|
@@ -42,11 +43,21 @@ function attachSharedBrowser(ctx) {
|
|
|
42
43
|
function sessionRecords(session) {
|
|
43
44
|
return Array.from(records.values()).filter(function (record) { return record.session === session && recordAllowed(record); });
|
|
44
45
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
46
|
+
function humanAllowed(session, record, ws) {
|
|
47
|
+
return allowed(session, ws) && recordAllowed(record, ws) &&
|
|
48
|
+
(record.session === session || workspace.linkedWorker(ctx, session, record.session));
|
|
49
|
+
}
|
|
50
|
+
function humanRecords(session, ws) {
|
|
51
|
+
return Array.from(records.values()).filter(function (record) { return humanAllowed(session, record, ws); });
|
|
52
|
+
}
|
|
53
|
+
function requireHumanRecord(session, record, ws) {
|
|
54
|
+
if (ctx.getSessionForWs(ws) !== session || !humanAllowed(session, record, ws)) throw new Error('Browser session expired');
|
|
55
|
+
}
|
|
56
|
+
function selectRecord(session, id, ws) {
|
|
57
|
+
var tabs = ws ? humanRecords(session, ws) : sessionRecords(session);
|
|
47
58
|
if (id) {
|
|
48
59
|
var record = records.get(id);
|
|
49
|
-
if (!record ||
|
|
60
|
+
if (!record || tabs.indexOf(record) < 0) throw new Error("Browser tab expired");
|
|
50
61
|
return record;
|
|
51
62
|
}
|
|
52
63
|
if (tabs.length > 1) throw new Error("Choose a browserId from status before operating a tab");
|
|
@@ -57,21 +68,28 @@ function attachSharedBrowser(ctx) {
|
|
|
57
68
|
if (payload) ctx.sendTo(ws, payload);
|
|
58
69
|
}
|
|
59
70
|
function sendState(ws, session, record, present, focus) {
|
|
60
|
-
if (!ws || ws.readyState !== 1 || !allowed(session, ws)
|
|
61
|
-
|
|
71
|
+
if (!ws || ws.readyState !== 1 || !allowed(session, ws)) return;
|
|
72
|
+
var tabs = humanRecords(session, ws);
|
|
73
|
+
if (!record || tabs.indexOf(record) < 0) record = tabs[0] || null;
|
|
74
|
+
function humanProjection(tab) { return Object.assign({}, projection(tab), workspace.describe(tab)); }
|
|
75
|
+
sendBrowserMessage(ws, session, { type: "shared_browser_state", sessionId: session.localId, browser: record ? humanProjection(record) : null,
|
|
76
|
+
browsers: tabs.map(humanProjection), present: !!present && record && record.session === session, focus: !!focus });
|
|
62
77
|
}
|
|
63
78
|
function broadcast(record, present) {
|
|
64
79
|
ctx.clients.forEach(function (ws) {
|
|
65
|
-
|
|
80
|
+
var session = ctx.getSessionForWs(ws);
|
|
81
|
+
if (session && (session.ownerId || null) === (record.ownerId || null)) sendState(ws, session, record, present);
|
|
66
82
|
});
|
|
67
83
|
}
|
|
68
84
|
function watching(record, ws) {
|
|
69
|
-
|
|
85
|
+
var viewer = viewers.get(ws);
|
|
86
|
+
return ws.readyState === 1 && viewer && viewer.id === record.id && ctx.getSessionForWs(ws) === viewer.session &&
|
|
87
|
+
viewer.sourceStamp === workspace.sourceStamp(viewer.session) && humanAllowed(viewer.session, record, ws);
|
|
70
88
|
}
|
|
71
89
|
function visibility(record) {
|
|
72
90
|
if (!record.runtime) return;
|
|
73
91
|
var visible = false;
|
|
74
|
-
viewers.forEach(function (
|
|
92
|
+
viewers.forEach(function (viewer, ws) { if (viewer.id === record.id && watching(record, ws)) visible = true; });
|
|
75
93
|
record.runtime.viewing(visible);
|
|
76
94
|
}
|
|
77
95
|
function end(record, reason) {
|
|
@@ -114,7 +132,10 @@ function attachSharedBrowser(ctx) {
|
|
|
114
132
|
record.frame = { type: "shared_browser_frame", sessionId: record.session.localId, browserId: record.id,
|
|
115
133
|
data: message.data, width: record.width, height: record.height };
|
|
116
134
|
viewers.forEach(function (id, ws) {
|
|
117
|
-
if (watching(record, ws) && ws.bufferedAmount < 1024 * 1024)
|
|
135
|
+
if (watching(record, ws) && ws.bufferedAmount < 1024 * 1024) {
|
|
136
|
+
var session = viewers.get(ws).session;
|
|
137
|
+
sendBrowserMessage(ws, session, Object.assign({}, record.frame, { sessionId: session.localId }));
|
|
138
|
+
}
|
|
118
139
|
});
|
|
119
140
|
visibility(record);
|
|
120
141
|
} else broadcast(record);
|
|
@@ -136,7 +157,7 @@ function attachSharedBrowser(ctx) {
|
|
|
136
157
|
var active = 0;
|
|
137
158
|
records.forEach(function (record) { if (record.runtime && record.ownerId === session.ownerId) active++; });
|
|
138
159
|
if (active >= 4) throw new Error("End another browser before opening more than four active tabs");
|
|
139
|
-
var record = { session: session, ownerId: session.ownerId, identity: identityKey(session), id: crypto.randomUUID(), phase: "starting", control: "agent", epoch: 0,
|
|
160
|
+
var record = { session: session, sourceStamp: workspace.sourceStamp(session), ownerId: session.ownerId, identity: identityKey(session), id: crypto.randomUUID(), phase: "starting", control: "agent", epoch: 0,
|
|
140
161
|
cookieOwner: ctx.isMate ? (ctx.ownerId || session.ownerId || "default") : null, incognito: temporary, url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
|
|
141
162
|
record.ready = new Promise(function (resolve, reject) { record.resolveReady = resolve; record.rejectReady = reject; });
|
|
142
163
|
record.ready.catch(function () {});
|
|
@@ -150,7 +171,8 @@ function attachSharedBrowser(ctx) {
|
|
|
150
171
|
broadcast(record, true);
|
|
151
172
|
return record;
|
|
152
173
|
}
|
|
153
|
-
async function changeControl(record, control) {
|
|
174
|
+
async function changeControl(record, control, validate) {
|
|
175
|
+
if (validate) validate();
|
|
154
176
|
if (!record.runtime) throw new Error("Open a browser first");
|
|
155
177
|
record.control = control;
|
|
156
178
|
activityHistory.interruptActivity(record); record.pointer = null;
|
|
@@ -161,8 +183,12 @@ function attachSharedBrowser(ctx) {
|
|
|
161
183
|
broadcast(record);
|
|
162
184
|
try {
|
|
163
185
|
await record.runtime.control(epoch);
|
|
164
|
-
if (record.runtime && record.epoch === epoch) { record.handoff = false; broadcast(record); }
|
|
165
186
|
} catch (e) { if (record.runtime && record.epoch === epoch) end(record, e.message); }
|
|
187
|
+
if (record.runtime && record.epoch === epoch) {
|
|
188
|
+
record.handoff = false;
|
|
189
|
+
if (validate) validate();
|
|
190
|
+
broadcast(record);
|
|
191
|
+
}
|
|
166
192
|
|
|
167
193
|
}
|
|
168
194
|
// Finish is a serialized boundary: it waits for earlier queued work in the browser worker, then
|
|
@@ -179,7 +205,8 @@ function attachSharedBrowser(ctx) {
|
|
|
179
205
|
record.touched = Date.now(); broadcast(record);
|
|
180
206
|
return projection(record);
|
|
181
207
|
}
|
|
182
|
-
async function run(session, record, actor, action, event, intent, presentationStart) {
|
|
208
|
+
async function run(session, record, actor, action, event, intent, presentationStart, validate) {
|
|
209
|
+
if (validate) validate();
|
|
183
210
|
requireAccess(session);
|
|
184
211
|
if (!record || !record.runtime || !recordAllowed(record)) throw new Error("Open a browser first");
|
|
185
212
|
if (record.handoff) throw new Error("Browser control is changing; wait for the handoff");
|
|
@@ -192,6 +219,7 @@ function attachSharedBrowser(ctx) {
|
|
|
192
219
|
await record.ready;
|
|
193
220
|
if (actor === "agent" && Number(session._sdkQueryGeneration || 0) !== generation) throw new Error("Browser request belongs to an older query");
|
|
194
221
|
requireAccess(session);
|
|
222
|
+
if (validate) validate();
|
|
195
223
|
if (!recordAllowed(record) || !record.runtime || record.epoch !== epoch) throw new Error("Browser control changed; retry after the handoff");
|
|
196
224
|
record.touched = Date.now();
|
|
197
225
|
var result;
|
|
@@ -203,6 +231,7 @@ function attachSharedBrowser(ctx) {
|
|
|
203
231
|
throw error;
|
|
204
232
|
}
|
|
205
233
|
requireAccess(session);
|
|
234
|
+
if (validate) validate();
|
|
206
235
|
if (actor === "agent" && Number(session._sdkQueryGeneration || 0) !== generation) throw new Error("Browser result belongs to an older query");
|
|
207
236
|
if (!recordAllowed(record) || records.get(record.id) !== record || record.epoch !== epoch) throw new Error("Browser control changed during the action");
|
|
208
237
|
if (result && result.url !== undefined) record.url = policy.visibleUrl(result.url);
|
|
@@ -268,7 +297,13 @@ function attachSharedBrowser(ctx) {
|
|
|
268
297
|
function handleMessage(ws, msg) {
|
|
269
298
|
if (!msg.type || msg.type.indexOf("shared_browser_") !== 0) return false;
|
|
270
299
|
var session = ctx.getSessionForWs(ws);
|
|
300
|
+
var requestStamp = workspace.sourceStamp(session), requestBinding = ws._clayPaneActivation;
|
|
271
301
|
Promise.resolve().then(async function () {
|
|
302
|
+
function validateRecord() {
|
|
303
|
+
if (requestStamp !== workspace.sourceStamp(session) || requestBinding !== ws._clayPaneActivation ||
|
|
304
|
+
requestBinding && (!ws._clayPaneActivationValid || !ws._clayPaneActivationValid())) throw new Error('Browser session changed');
|
|
305
|
+
requireHumanRecord(session, record, ws);
|
|
306
|
+
}
|
|
272
307
|
requireAccess(session, ws);
|
|
273
308
|
if (String(msg.sessionId) !== String(session.localId)) throw new Error("Browser session changed");
|
|
274
309
|
var record;
|
|
@@ -285,26 +320,35 @@ function attachSharedBrowser(ctx) {
|
|
|
285
320
|
if (msg.type === "shared_browser_open") {
|
|
286
321
|
var targetUrl = msg.url === undefined ? null : policy.browserUrl(msg.url);
|
|
287
322
|
if (targetUrl) record = await openLinkedPage(session, targetUrl, msg.incognito);
|
|
288
|
-
else
|
|
323
|
+
else {
|
|
324
|
+
var existing = msg.newTab !== true && msg.incognito === undefined && humanRecords(session, ws).filter(function (tab) { return !!tab.runtime; });
|
|
325
|
+
record = existing && (existing.find(function (tab) { return tab.session === session; }) || existing[0]) || create(session, "user", msg.newTab === true, msg.incognito);
|
|
326
|
+
}
|
|
289
327
|
sendState(ws, session, record, true, true); return;
|
|
290
328
|
}
|
|
291
|
-
record = selectRecord(session, msg.browserId);
|
|
329
|
+
record = selectRecord(session, msg.browserId, ws);
|
|
292
330
|
if (!record || !recordAllowed(record, ws) || msg.browserId !== record.id) throw new Error("Browser session expired");
|
|
331
|
+
validateRecord();
|
|
293
332
|
if (msg.type === "shared_browser_view") {
|
|
294
|
-
if (msg.visible) {
|
|
333
|
+
if (msg.visible) {
|
|
334
|
+
var previousViewer = viewers.get(ws), previous = previousViewer && records.get(previousViewer.id);
|
|
335
|
+
viewers.set(ws, { id: record.id, session: session, sourceStamp: workspace.sourceStamp(session) });
|
|
336
|
+
if (previous && previous !== record) visibility(previous);
|
|
337
|
+
if (record.frame) sendBrowserMessage(ws, session, Object.assign({}, record.frame, { sessionId: session.localId }));
|
|
338
|
+
}
|
|
295
339
|
else viewers.delete(ws);
|
|
296
340
|
visibility(record);
|
|
297
341
|
} else if (msg.type === "shared_browser_control") {
|
|
298
342
|
if (!["agent", "user"].includes(msg.control)) throw new Error("Invalid control mode");
|
|
299
|
-
await changeControl(record, msg.control);
|
|
343
|
+
await changeControl(record, msg.control, validateRecord);
|
|
300
344
|
} else if (msg.type === "shared_browser_close") {
|
|
301
345
|
end(record); records.delete(record.id);
|
|
302
346
|
sendState(ws, session, sessionRecords(session)[0]);
|
|
303
|
-
|
|
347
|
+
broadcast(record);
|
|
304
348
|
} else if (msg.type === "shared_browser_end") end(record);
|
|
305
349
|
else if (msg.type === "shared_browser_input") {
|
|
306
350
|
if (msg.epoch !== record.epoch) throw new Error("Browser control changed");
|
|
307
|
-
await run(session, record, "user", "action", policy.validateAction(msg.event));
|
|
351
|
+
await run(session, record, "user", "action", policy.validateAction(msg.event), null, null, validateRecord);
|
|
308
352
|
}
|
|
309
353
|
}).catch(function (e) {
|
|
310
354
|
if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message, requestId: msg.requestId });
|
|
@@ -312,7 +356,7 @@ function attachSharedBrowser(ctx) {
|
|
|
312
356
|
return true;
|
|
313
357
|
}
|
|
314
358
|
var sweep = setInterval(function () {
|
|
315
|
-
viewers.forEach(function (
|
|
359
|
+
viewers.forEach(function (viewer, ws) { var record = records.get(viewer.id); if (!record || !watching(record, ws)) viewers.delete(ws); });
|
|
316
360
|
records.forEach(function (record, key) {
|
|
317
361
|
if (!recordAllowed(record)) { end(record, "Browser session is no longer available"); records.delete(key); }
|
|
318
362
|
else if (Date.now() - record.touched > 30 * 60 * 1000) { end(record, "Browser expired after 30 minutes without activity"); records.delete(key); }
|
|
@@ -330,6 +374,10 @@ function attachSharedBrowser(ctx) {
|
|
|
330
374
|
});
|
|
331
375
|
}
|
|
332
376
|
return {
|
|
377
|
+
refresh: function () {
|
|
378
|
+
viewers.forEach(function (viewer, ws) { var record = records.get(viewer.id); if (!record || !watching(record, ws)) { viewers.delete(ws); if (record) visibility(record); } });
|
|
379
|
+
ctx.clients.forEach(function (ws) { var session = ctx.getSessionForWs(ws); if (session) sendState(ws, session); });
|
|
380
|
+
},
|
|
333
381
|
handleMessage: handleMessage,
|
|
334
382
|
getToolDefs: getToolDefs,
|
|
335
383
|
getSystemPrompt: function () { return browserTools.PROMPT; },
|
package/lib/project.js
CHANGED
|
@@ -692,6 +692,7 @@ function createProjectContext(opts) {
|
|
|
692
692
|
usersModule: usersModule,
|
|
693
693
|
multiWorkerFeature: multiWorkerGate,
|
|
694
694
|
onPairChanged: function (group) {
|
|
695
|
+
if (_sharedBrowser) _sharedBrowser.refresh();
|
|
695
696
|
if (!sdk || !group || !Array.isArray(group.members)) return;
|
|
696
697
|
for (var i = 0; i < group.members.length; i++) {
|
|
697
698
|
sdk.refreshSessionRuntime(sm.sessions.get(group.members[i]));
|
|
@@ -822,6 +823,7 @@ function createProjectContext(opts) {
|
|
|
822
823
|
getSessionForWs: getSessionForWs,
|
|
823
824
|
});
|
|
824
825
|
var _sharedBrowser = attachSharedBrowser({
|
|
826
|
+
getWorkerGroup: function (id) { return _splitGroups.store.groupForMember(id); },
|
|
825
827
|
studioPresentation: _studioPresentation,
|
|
826
828
|
sendToSession: sendToSession,
|
|
827
829
|
getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
.issue-evidence { padding-top: 18px; border-top: 1px solid var(--border-subtle); font-size: 13px; line-height: 1.65; }
|
|
51
51
|
#issues-panel .issue-discussion { margin-top: 30px; padding-top: 18px; border-top: 1px solid var(--border-subtle); }
|
|
52
52
|
.issue-discussion h3 > span { margin-left: 4px; font: 11px var(--font-mono); color: var(--text-dimmer); }
|
|
53
|
-
#issues-panel .issue-comment { padding: 10px 12px; margin-bottom: 8px; border: 1px solid var(--border-subtle); border-radius: 9px; background: var(--bg-alt); }
|
|
53
|
+
:is(#issues-panel, #linear-panel) .issue-comment { padding: 10px 12px; margin-bottom: 8px; border: 1px solid var(--border-subtle); border-radius: 9px; background: var(--bg-alt); }
|
|
54
54
|
.issue-comment-meta { margin-bottom: 5px; color: var(--text-dimmer); font: 10.5px/1.4 var(--font-mono); }
|
|
55
55
|
.issue-comment .markdown-body { font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
56
56
|
.issue-comment .markdown-body > :first-child { margin-top: 0; }
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/*
|
|
2
|
-
#linear-panel {
|
|
1
|
+
/* Issue-specific content; the shared workbench shell owns window geometry. */
|
|
2
|
+
#linear-panel { min-height: 0; display: flex; flex-direction: column; background: var(--bg); color: var(--text); overflow: hidden; }
|
|
3
3
|
#linear-panel.hidden { display: none; }
|
|
4
|
-
#linear-panel .linear-topbar { height: 48px; box-sizing: border-box; display: flex; align-items: center; gap: 12px; padding: 0 16px; border-bottom: 1px solid var(--border-subtle); flex: 0 0 auto; }
|
|
5
4
|
#linear-panel .linear-title { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; flex: 1; }
|
|
6
5
|
|
|
7
6
|
#linear-panel .linear-window-actions { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
|
|
@@ -9,8 +8,6 @@
|
|
|
9
8
|
#linear-panel button:hover { background: var(--bg-hover, rgba(128,128,128,.12)); color: var(--text); }
|
|
10
9
|
#linear-panel button:disabled { opacity: .5; cursor: default; }
|
|
11
10
|
#linear-panel :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
12
|
-
#linear-panel .linear-icon-btn { width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; padding: 0; border-radius: 7px; }
|
|
13
|
-
#linear-panel .linear-icon-btn .lucide { display: block; width: 15px; height: 15px; }
|
|
14
11
|
.linear-notice { margin: 0; padding: 0 16px; color: var(--text-muted); font-size: 12px; }
|
|
15
12
|
.linear-notice:not(:empty) { padding: 8px 16px; }
|
|
16
13
|
.linear-content { overflow: auto; min-height: 0; flex: 1; }
|
|
@@ -21,12 +18,9 @@
|
|
|
21
18
|
.linear-actions button { font-size: 12px; line-height: normal; }
|
|
22
19
|
#linear-panel a { color: var(--link); }
|
|
23
20
|
#linear-panel .issue-row { width: 100%; text-align: left; }
|
|
24
|
-
#linear-panel .issue-discussion article { padding: 14px 0; border-top: 1px solid var(--border-subtle); }
|
|
25
|
-
#linear-panel .issue-discussion article > strong { display: block; margin-bottom: 4px; font-size: 13px; }
|
|
26
21
|
#linear-panel .issue-doc-body, #linear-panel .issue-doc-body .md-content { overflow-wrap: anywhere; }
|
|
27
22
|
#linear-panel .issue-doc-body .md-content { font-size: 14px; line-height: 1.7; }
|
|
28
23
|
#linear-panel .issue-discussion .md-content { font-size: 13px; line-height: 1.6; }
|
|
29
|
-
#linear-panel .issue-detail .issue-doc-title { font-size: clamp(20px, 2vw, 26px); line-height: 1.2; }
|
|
30
24
|
#linear-panel .md-content > :first-child { margin-top: 0; }
|
|
31
25
|
#linear-panel .md-content > :last-child { margin-bottom: 0; }
|
|
32
26
|
#linear-panel .md-content h1, #linear-panel .md-content h2, #linear-panel .md-content h3 { margin: 1.3em 0 .5em; font-family: var(--font-display); }
|
|
@@ -36,7 +30,6 @@
|
|
|
36
30
|
#linear-panel .md-content pre { overflow-x: auto; max-width: 100%; }
|
|
37
31
|
#linear-panel .md-content table { display: block; max-width: 100%; overflow-x: auto; }
|
|
38
32
|
#linear-panel .md-content img { max-width: 100%; }
|
|
39
|
-
@media (max-width: 1023px) { #linear-panel { position: fixed; inset: 0; z-index: 300; width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; padding-top: var(--safe-top, 0px); padding-bottom: var(--safe-bottom, 0px); box-sizing: border-box; } #linear-panel button { min-height: 36px; } #linear-panel .linear-icon-btn { width: 36px; } }
|
|
40
33
|
@media (max-width: 767px) { #linear-panel .issue-detail { padding: 20px 16px 40px; } }
|
|
41
34
|
.session-linear-link { max-width: 360px; min-width: 0; flex-shrink: 1; }
|
|
42
35
|
.session-linear-link > span { white-space: nowrap; }
|
|
@@ -49,22 +42,15 @@
|
|
|
49
42
|
#header-github-links:has(.session-linear-link) { max-width: 170px; }
|
|
50
43
|
#header-github-links .linear-work-title, #header-github-links .linear-work-state { display: none; }
|
|
51
44
|
}
|
|
52
|
-
#linear-panel .linear-session-pills { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
53
|
-
#linear-panel .linear-session-pill { display: inline-flex; align-items: flex-start; gap: 7px; max-width: 100%; box-sizing: border-box; padding: 6px 12px; border-radius: 999px; text-align: left; font-size: 13px; line-height: 1.4; color: var(--text); background: var(--bg-alt); border: 1px solid var(--border); }
|
|
54
|
-
#linear-panel .linear-session-pill .lucide { width: 14px; height: 14px; flex: none; margin-top: 2px; color: var(--accent); }
|
|
55
|
-
#linear-panel .linear-session-pill > span { min-width: 0; overflow-wrap: anywhere; }
|
|
56
|
-
#linear-panel .linear-session-pill:hover { background: color-mix(in srgb, var(--accent) 10%, var(--bg-alt)); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
|
|
57
|
-
#linear-panel .linear-session-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
58
45
|
.linear-work-brand { display: inline-flex; align-items: center; flex: none; color: inherit; }
|
|
59
46
|
.linear-work-brand svg { width: 13px; height: 13px; }
|
|
60
47
|
|
|
61
|
-
#linear-panel .linear-title .linear-work-brand svg { width:
|
|
48
|
+
#linear-panel .linear-title .linear-work-brand svg { width: 14px; height: 14px; }
|
|
62
49
|
#linear-panel a.linear-icon-btn { color: var(--text-secondary); text-decoration: none; border: 1px solid var(--border); background: var(--bg-alt); box-sizing: border-box; }
|
|
63
50
|
#linear-panel a.linear-icon-btn:hover { background: var(--bg-hover, rgba(128,128,128,.12)); color: var(--text); }
|
|
64
51
|
#linear-panel [data-linear-external][hidden] { display: none; }
|
|
65
52
|
#linear-panel .linear-conversations { margin: 22px 0 26px; }
|
|
66
53
|
#linear-panel .linear-conversations h3 { font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 0 0 10px; }
|
|
67
|
-
#linear-panel .linear-action-chip { background: transparent; border-style: dashed; color: var(--text-secondary); }
|
|
68
54
|
#linear-panel .linear-sharing-hint { font-size: 11px; line-height: 1.5; color: var(--text-muted); margin: 10px 0 0; }
|
|
69
55
|
@media (max-width: 1023px) { #linear-panel a.linear-icon-btn { height: 36px; } }
|
|
70
56
|
|
|
@@ -73,19 +59,16 @@
|
|
|
73
59
|
#linear-panel .linear-issue-id { padding: 4px 7px; border-radius: 5px; background: var(--bg-alt); color: var(--text-secondary); font-family: var(--font-mono); font-weight: 500; letter-spacing: .015em; overflow-wrap: anywhere; }
|
|
74
60
|
#linear-panel .linear-issue-status { padding: 3px 8px; border: 1px solid var(--border); border-radius: 999px; color: var(--text-secondary); overflow-wrap: anywhere; }
|
|
75
61
|
|
|
76
|
-
#linear-panel .linear-title { min-width: 0; gap:
|
|
62
|
+
#linear-panel .linear-title { min-width: 0; gap: 7px; height: auto; font-size: 11px; line-height: 20px; font-weight: 600; }
|
|
77
63
|
#linear-panel .linear-window-actions { flex-shrink: 0; }
|
|
78
|
-
#linear-panel .linear-header-issue { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; height:
|
|
64
|
+
#linear-panel .linear-header-issue { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; height: 24px; box-sizing: border-box; padding: 0 5px; border-radius: 5px; color: var(--text-secondary); text-decoration: none; font: inherit; }
|
|
79
65
|
#linear-panel .linear-header-issue .lucide { display: block; width: 12px; height: 12px; color: inherit; }
|
|
80
66
|
#linear-panel .linear-header-issue:hover { color: var(--text); background: var(--bg-alt); }
|
|
81
|
-
#linear-panel .linear-window-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size:
|
|
67
|
+
#linear-panel .linear-window-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; line-height: 20px; font-weight: 500; }
|
|
82
68
|
|
|
83
|
-
#linear-panel .
|
|
84
|
-
#linear-panel .linear-comment > header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; font-size: 13px; }
|
|
69
|
+
#linear-panel .linear-comment > header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 5px; }
|
|
85
70
|
#linear-panel .linear-comment > header strong { font-weight: 600; }
|
|
86
|
-
#linear-panel .linear-comment time { color: var(--text-muted); font-size: 11px; }
|
|
87
71
|
#linear-panel .linear-comment .issue-doc-body { margin: 0; }
|
|
88
|
-
@media (max-width: 767px) { #linear-panel .issue-discussion .linear-comment { padding: 12px; } }
|
|
89
72
|
|
|
90
73
|
#linear-panel .linear-title > .linear-work-brand { height: 20px; align-items: center; }
|
|
91
74
|
#linear-panel .linear-title > .linear-work-brand svg { display: block; }
|
package/lib/public/css/menus.css
CHANGED
|
@@ -258,22 +258,6 @@ body.split-view-resizing .split-pane-frame { pointer-events: none; }
|
|
|
258
258
|
.split-view-worker-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
259
259
|
.split-view-worker-identity { flex-shrink: 0; white-space: nowrap; }
|
|
260
260
|
.split-view-worker-generation { flex-shrink: 0; color: var(--text-dimmer); white-space: nowrap; }
|
|
261
|
-
.split-view-worker-controls {
|
|
262
|
-
appearance: none;
|
|
263
|
-
flex: 0 0 auto;
|
|
264
|
-
height: 22px;
|
|
265
|
-
margin-left: auto;
|
|
266
|
-
padding: 3px 6px;
|
|
267
|
-
border: 1px solid transparent;
|
|
268
|
-
border-radius: 5px;
|
|
269
|
-
background: transparent;
|
|
270
|
-
color: var(--text-muted);
|
|
271
|
-
font: inherit;
|
|
272
|
-
font-size: 10px;
|
|
273
|
-
cursor: pointer;
|
|
274
|
-
}
|
|
275
|
-
.split-view-worker-controls:hover { background: var(--sidebar-hover); color: var(--text); }
|
|
276
|
-
.split-view-worker-controls:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
277
261
|
.split-view-worker-dot { width: 5px; height: 5px; flex: 0 0 5px; border-radius: 50%; background: var(--text-dimmer); }
|
|
278
262
|
.split-view-worker-dot[data-state="working"] { background: var(--brand-green); }
|
|
279
263
|
.split-view-worker-dot[data-state="waiting"] { background: var(--brand-gold); }
|
|
@@ -122,3 +122,15 @@
|
|
|
122
122
|
#main-panels > .workbench-pane-shell > .tool-pane-header .workbench-pane-actions button[aria-pressed="true"] { background: var(--accent-bg); color: var(--accent); }
|
|
123
123
|
|
|
124
124
|
.file-instance:not([data-workbench-placement="docked"]) [data-action="pin"] { display: none; }
|
|
125
|
+
|
|
126
|
+
/* Related conversations and actions share the compact workbench control recipe. */
|
|
127
|
+
.workbench-related-links { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
128
|
+
#main-panels .workbench-related-link { display: inline-flex; align-items: center; gap: 6px; min-height: 28px; max-width: 100%; box-sizing: border-box; padding: 3px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-alt); color: var(--text-secondary); text-align: left; font-size: 12px; line-height: 20px; }
|
|
129
|
+
#main-panels .workbench-related-link > span { min-width: 0; overflow-wrap: anywhere; }
|
|
130
|
+
#main-panels .workbench-related-link svg { width: 13px; height: 13px; flex: none; }
|
|
131
|
+
#main-panels .workbench-related-link:hover { background: var(--sidebar-hover); color: var(--text); }
|
|
132
|
+
@media (max-width: 1023px) {
|
|
133
|
+
#main-panels > .workbench-pane-shell { position: fixed; inset: 0; z-index: 300; width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; padding-top: var(--safe-top, 0px); padding-bottom: var(--safe-bottom, 0px); box-sizing: border-box; }
|
|
134
|
+
#main-panels > .workbench-pane-shell [data-workbench-window] { display: none; }
|
|
135
|
+
#main-panels .workbench-related-link { min-height: 36px; }
|
|
136
|
+
}
|
|
@@ -146,9 +146,9 @@ export function contextualScreenReference() {
|
|
|
146
146
|
if (!enabled() || window.parent === window) return null;
|
|
147
147
|
try {
|
|
148
148
|
var surface = window.parent.document.getElementById('contextual-clay');
|
|
149
|
-
if (!surface || !window.frameElement || window.frameElement.hidden ||
|
|
149
|
+
if (!surface || !window.frameElement || window.frameElement.hidden || !surface.contains(window.frameElement)) return null;
|
|
150
150
|
var reference = JSON.parse(surface.dataset.screenReference || 'null');
|
|
151
151
|
if (!reference || reference.ownerId !== (store.get('myUserId') || null)) return null;
|
|
152
|
-
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) };
|
|
152
|
+
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), workspace: reference.workspace || null };
|
|
153
153
|
} catch (error) { return null; }
|
|
154
154
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { contextualViewMetadata } from './contextual-view-metadata.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
import { getWs } from './ws-ref.js';
|
|
3
4
|
import { contextualFrame } from './contextual-mate-identity.js';
|
|
@@ -7,7 +8,7 @@ function identity(frame) {
|
|
|
7
8
|
}
|
|
8
9
|
function contextKey(frame, canonical) {
|
|
9
10
|
var current = identity(frame);
|
|
10
|
-
return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('sessionOriginId'), store.get('activeProjectSlug'), store.get('sessionActivatedProjectSlug'), store.get('replayingHistory'), (store.get('contextualClay') || {}).mode, store.get('homeShellVisible'), store.get('projectsHubVisible'), store.get('dmMode'), store.get('rightWorkbenchOwner'), store.get('homeSubSurface'), store.get('rightWorkbenchRevision'), store.get('myUserId'), store.get('connected'), current && current.mateId, current && current.sessionId, current && (canonical ? current.connected : current.ready), canonical && current && current.sessionOriginId]);
|
|
11
|
+
return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('sessionOriginId'), store.get('activeProjectSlug'), store.get('sessionActivatedProjectSlug'), store.get('replayingHistory'), (store.get('contextualClay') || {}).mode, store.get('homeShellVisible'), store.get('projectsHubVisible'), store.get('dmMode'), store.get('rightWorkbenchOwner'), store.get('homeSubSurface'), store.get('rightWorkbenchRevision'), store.get('projectSplitViewActiveId'), JSON.stringify(contextualViewMetadata()), store.get('myUserId'), store.get('connected'), current && current.mateId, current && current.sessionId, current && (canonical ? current.connected : current.ready), canonical && current && current.sessionOriginId]);
|
|
11
12
|
}
|
|
12
13
|
export function updateMateContext(surface, mate, frame, snapshot) {
|
|
13
14
|
var current = identity(frame), ws = getWs(), key = contextKey(frame, mate && mate.builtinKey === 'clay'), prepared = store.get('contextualPrepared');
|
|
@@ -18,7 +19,7 @@ export function updateMateContext(surface, mate, frame, snapshot) {
|
|
|
18
19
|
capabilities.textContent = "I can control Clay Studio for you—manage projects, coordinate work, set up schedules, and navigate your workspace. Just tell me what you’d like done.";
|
|
19
20
|
var privateWorkspace = (store.get('currentSlug') || '').indexOf('mate-') === 0;
|
|
20
21
|
var parentReady = store.get('activeProjectSlug') === store.get('currentSlug') && store.get('sessionActivatedProjectSlug') === store.get('currentSlug') && !store.get('replayingHistory');
|
|
21
|
-
var available = (store.get('contextualClay') || {}).mode === 'popover' && parentReady && !canonical && !privateWorkspace && current && current.ready && store.get('connected') && !store.get('homeShellVisible') && !store.get('projectsHubVisible') && !store.get('dmMode') &&
|
|
22
|
+
var available = (store.get('contextualClay') || {}).mode === 'popover' && parentReady && !canonical && !privateWorkspace && current && current.ready && store.get('connected') && !store.get('homeShellVisible') && !store.get('projectsHubVisible') && !store.get('dmMode') && ws && ws.readyState === 1;
|
|
22
23
|
var changed = !prepared || prepared.key !== key || prepared.socket !== ws;
|
|
23
24
|
if (changed) {
|
|
24
25
|
var requestId = 'context-' + crypto.randomUUID();
|
|
@@ -26,7 +27,7 @@ export function updateMateContext(surface, mate, frame, snapshot) {
|
|
|
26
27
|
}
|
|
27
28
|
// Stamp the reference before publishing a new preparation: store subscribers
|
|
28
29
|
// may render synchronously, and the composer must never see the prior view ID.
|
|
29
|
-
snapshot = Object.assign({}, snapshot, { viewId: prepared.requestId });
|
|
30
|
+
snapshot = Object.assign({}, snapshot, { viewId: prepared.requestId, workspace: contextualViewMetadata() });
|
|
30
31
|
surface.dataset.screenReference = JSON.stringify(snapshot);
|
|
31
32
|
if (changed) {
|
|
32
33
|
store.set({ contextualPrepared: prepared });
|
|
@@ -37,6 +38,7 @@ export function updateMateContext(surface, mate, frame, snapshot) {
|
|
|
37
38
|
var label = surface.querySelector('[data-clay-context]');
|
|
38
39
|
var detail = surface.querySelector('.contextual-clay-context p');
|
|
39
40
|
var summary = ready ? prepared.summary.project + ' / ' + prepared.summary.conversation : null;
|
|
41
|
+
var viewName = snapshot.workspace && snapshot.workspace.name;
|
|
40
42
|
var projectName = (ready && prepared.summary.project) || snapshot.projectTitle || snapshot.screen || 'Current view';
|
|
41
43
|
if (canonical) {
|
|
42
44
|
var currentSummary = (snapshot.projectTitle || snapshot.screen || 'Current view') + (snapshot.conversationTitle ? ' / ' + snapshot.conversationTitle : '');
|
|
@@ -51,6 +53,8 @@ export function updateMateContext(surface, mate, frame, snapshot) {
|
|
|
51
53
|
detail.textContent = ready ? (shared && shared.viewId === prepared.requestId ? 'Shared with your last message: ' : 'With your next message: ') + summary + '. This Mate can read up to 20 text turns from this selected conversation and request to open it in this window after your review. Tool results, attachments and hidden entries are excluded.' : 'Current view: ' + projectName + '. This view has no supported conversation context. Open an accessible project conversation to share it with your next message.';
|
|
52
54
|
detail.textContent += '\nNo continuous screen capture. Issues, documents, other tabs, files and settings are unavailable through this context.';
|
|
53
55
|
}
|
|
56
|
+
if (viewName) detail.textContent += '\nView: ' + viewName + '.';
|
|
57
|
+
detail.textContent += '\nEach message also includes current View, session titles and open workbench names as display metadata. Workbench contents are not included.';
|
|
54
58
|
if (prepared.status === 'expired' && !canonical) { contextStatus = 'Expired'; detail.textContent = 'This five-minute context has expired. Close and reopen this Mate to prepare a fresh snapshot. No previous navigation request will replay.'; }
|
|
55
59
|
if (shared) detail.textContent += '\nLast submitted: ' + shared.summary.project + ' / ' + shared.summary.conversation + ((canonical ? sameSnapshot : shared.viewId === prepared.requestId) ? '.' : canonical ? '. The view or connection has changed; that snapshot is stale.' : '. Your view has changed; that context is stale.');
|
|
56
60
|
var projectLabel = label.querySelector('[data-context-project]');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getCachedSessions } from './sidebar-sessions.js';
|
|
3
|
+
|
|
4
|
+
function label(value) { return typeof value === 'string' ? value.replace(/[\u0000-\u001f\u007f]/g, ' ').slice(0, 160) : null; }
|
|
5
|
+
|
|
6
|
+
// Display labels only. Conversation access remains bound to the authenticated
|
|
7
|
+
// selected session; this snapshot never grants access to another pane or tool.
|
|
8
|
+
export function contextualViewMetadata() {
|
|
9
|
+
var state = store.snap();
|
|
10
|
+
if (state.paneMode || state.homeShellVisible || state.projectsHubVisible || state.dmMode ||
|
|
11
|
+
state.activeProjectSlug !== state.currentSlug || state.sessionActivatedProjectSlug !== state.currentSlug || state.replayingHistory) return null;
|
|
12
|
+
var view = (state.projectSplitViews || []).find(function (item) { return item.id === state.projectSplitViewActiveId; });
|
|
13
|
+
var sessions = getCachedSessions() || [];
|
|
14
|
+
var refs = view ? view.sessions || [] : sessions.filter(function (item) { return item.id === state.activeSessionId; });
|
|
15
|
+
var owners = (state.rightWorkbenchDockedOwners || []).concat(state.rightWorkbenchOwner || []).filter(function (name, index, all) { return all.indexOf(name) === index; });
|
|
16
|
+
return { name: label(view && view.name),
|
|
17
|
+
sessions: refs.slice(0, 32).map(function (ref) {
|
|
18
|
+
var session = sessions.find(function (item) { return item.id === ref.id && (!ref.sessionOriginId || item.sessionOriginId === ref.sessionOriginId); });
|
|
19
|
+
return session ? { sessionId: session.id, title: label(session.title), selected: session.id === state.activeSessionId,
|
|
20
|
+
focused: !!state.projectSplitViewFocusedOrigin && session.sessionOriginId === state.projectSplitViewFocusedOrigin } : null;
|
|
21
|
+
}).filter(Boolean),
|
|
22
|
+
workbenches: owners.slice(0, 16).map(label) };
|
|
23
|
+
}
|
|
@@ -70,7 +70,7 @@ function chronologicalComments(comments) {
|
|
|
70
70
|
function commentMarkup(comment) {
|
|
71
71
|
var date = new Date(comment.createdAt);
|
|
72
72
|
var timestamp = Number.isFinite(date.getTime()) ? '<time datetime="' + esc(date.toISOString()) + '" title="' + esc(date.toLocaleString()) + '">' + esc(date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })) + '</time>' : '';
|
|
73
|
-
return '<article class="linear-comment"><header><strong>' + esc(comment.author) + '</strong>' + timestamp + '</header><div class="issue-doc-body">' + prose(comment.body) + '</div></article>';
|
|
73
|
+
return '<article class="issue-comment linear-comment"><header class="issue-comment-meta"><strong>' + esc(comment.author) + '</strong>' + timestamp + '</header><div class="issue-doc-body">' + prose(comment.body) + '</div></article>';
|
|
74
74
|
}
|
|
75
75
|
function renderIssue() {
|
|
76
76
|
var issue = store.get('linearIssue'), sessions = store.get('linearMatches') || [];
|
|
@@ -80,10 +80,10 @@ function renderIssue() {
|
|
|
80
80
|
var linked = !!(active && (active.linearLinks || []).some(function (item) { return item.url === issue.url; }));
|
|
81
81
|
var prs = active && linked ? (active.githubLinks || []).filter(function (item) { return item.kind === 'pr' && /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/[1-9][0-9]*$/.test(item.url); }) : [];
|
|
82
82
|
content().innerHTML = '<article class="issue-detail"><div class="linear-issue-meta"><span class="linear-issue-id" aria-label="Issue identifier">' + esc(issue.identifier) + '</span>' + (issue.stateName ? '<span class="linear-issue-status" aria-label="Status">' + esc(issue.stateName) + '</span>' : '') + '</div><h2 class="issue-doc-title">' + esc(issue.title) + '</h2>' +
|
|
83
|
-
'<section class="linear-conversations"><h3>Linked conversations</h3><div class="linear-session-pills">' +
|
|
84
|
-
sessions.map(function (item) { return '<button type="button" class="linear-session-pill" data-linear-session="' + esc(item.sessionId) + '"><i data-lucide="message-square"></i><span>' + esc(item.title || 'Open conversation') + '</span></button>'; }).join('') +
|
|
85
|
-
'<button type="button" class="linear-session-pill linear-action-chip" data-linear-link' + (!store.get('activeSessionId') ? ' disabled' : '') + '><i data-lucide="' + (linked ? 'unlink' : 'plus') + '"></i><span>' + (linked ? 'Unlink this conversation' : 'Link this conversation') + '</span></button>' +
|
|
86
|
-
(!sessions.length ? '<button type="button" class="linear-session-pill linear-action-chip" data-linear-start><i data-lucide="plus"></i><span>Start work</span></button>' : '') +
|
|
83
|
+
'<section class="linear-conversations"><h3>Linked conversations</h3><div class="workbench-related-links linear-session-pills">' +
|
|
84
|
+
sessions.map(function (item) { return '<button type="button" class="workbench-related-link linear-session-pill" data-linear-session="' + esc(item.sessionId) + '"><i data-lucide="message-square"></i><span>' + esc(item.title || 'Open conversation') + '</span></button>'; }).join('') +
|
|
85
|
+
'<button type="button" class="workbench-related-link linear-session-pill linear-action-chip" data-linear-link' + (!store.get('activeSessionId') ? ' disabled' : '') + '><i data-lucide="' + (linked ? 'unlink' : 'plus') + '"></i><span>' + (linked ? 'Unlink this conversation' : 'Link this conversation') + '</span></button>' +
|
|
86
|
+
(!sessions.length ? '<button type="button" class="workbench-related-link linear-session-pill linear-action-chip" data-linear-start><i data-lucide="plus"></i><span>Start work</span></button>' : '') +
|
|
87
87
|
'</div><p class="linear-sharing-hint">Linked issue titles and status are visible to conversation viewers.</p></section>' +
|
|
88
88
|
(prs.length ? '<section><h3>GitHub pull requests</h3>' + prs.map(function (pr) { return '<a target="_blank" rel="noopener noreferrer" href="' + esc(pr.url) + '">PR #' + esc(pr.number) + ' · ' + esc(pr.title) + '</a>'; }).join('<br>') + '</section>' : '') +
|
|
89
89
|
'<div class="issue-doc-body">' + prose(issue.description) + '</div><section class="issue-discussion"><h3>Comments</h3>' +
|
|
@@ -111,7 +111,7 @@ export function filterPaneActivation(message) {
|
|
|
111
111
|
var pending = store.get('panePendingBinding'), committed = store.get('paneCommittedBinding');
|
|
112
112
|
if (message.type === 'pane_activation_rejected') { failed(message.requestId); return true; }
|
|
113
113
|
if (!pending && !committed) return !!message.paneActivation;
|
|
114
|
-
if (['session_list', 'split_groups', 'session_presence', 'pong', 'account_auth_required', 'project_removed', 'project_access_revoked'].indexOf(message.type) >= 0 && !message.paneActivation) return false;
|
|
114
|
+
if (['session_list', 'split_groups', 'session_presence', 'mate_list', 'mate_updated', 'mate_deleted', 'pong', 'account_auth_required', 'project_removed', 'project_access_revoked'].indexOf(message.type) >= 0 && !message.paneActivation) return false;
|
|
115
115
|
var expected = pending || committed;
|
|
116
116
|
if (!samePaneBinding(expected, message.paneActivation)) return true;
|
|
117
117
|
if (message.type === 'pane_activation_started') return true;
|
|
@@ -139,6 +139,10 @@ export function filterPaneActivation(message) {
|
|
|
139
139
|
clearDeadline();
|
|
140
140
|
store.set({ paneCommittedBinding: expected, panePendingBinding: null, paneReady: true });
|
|
141
141
|
block(false);
|
|
142
|
+
// Initial owner metadata may have arrived during activation. Read it again
|
|
143
|
+
// after committing; session-bound actions still require their live gesture.
|
|
144
|
+
var socket = getWs();
|
|
145
|
+
if (socket && socket.readyState === 1) socket.send(JSON.stringify({ type: 'mate_list' }));
|
|
142
146
|
if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:pane-ready'); performance.mark('clay:pane-ready'); }
|
|
143
147
|
var frame = window.frameElement;
|
|
144
148
|
if (frame) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-ready'));
|
|
@@ -5,7 +5,7 @@ export function renderBrowserTabs(el, select, send) {
|
|
|
5
5
|
var bar = el.querySelector('.shared-browser-tabs');
|
|
6
6
|
var tabs = store.get('sharedBrowsers') || [];
|
|
7
7
|
var selected = store.get('sharedBrowser');
|
|
8
|
-
var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase, tab.incognito, tab.personalCookiesAvailable]; }), selected && selected.id, store.get('connected')]);
|
|
8
|
+
var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase, tab.incognito, tab.personalCookiesAvailable, tab.sourceSessionId, tab.sourceRole, tab.sourceTitle, tab.sourceGeneration]; }), selected && selected.id, store.get('connected')]);
|
|
9
9
|
if (bar.dataset.key === key) return;
|
|
10
10
|
var focused = bar.contains(document.activeElement) ? document.activeElement : null;
|
|
11
11
|
var focusedId = focused && focused.dataset.browserId, focusedAction = focused && focused.dataset.tabAction;
|
|
@@ -23,8 +23,10 @@ export function renderBrowserTabs(el, select, send) {
|
|
|
23
23
|
button.setAttribute('aria-selected', String(active)); button.tabIndex = active ? 0 : -1;
|
|
24
24
|
var title = 'New tab';
|
|
25
25
|
try { if (tab.url) title = new URL(tab.url).hostname || 'New tab'; } catch (e) {}
|
|
26
|
+
var source = tab.sourceRole === 'worker' ? 'Worker #' + tab.sourceSessionId : '';
|
|
27
|
+
if (source) title = source + ' · ' + title;
|
|
26
28
|
var mode = tab.phase === 'ended' ? 'Ended' : tab.phase === 'starting' ? 'Starting' : tab.control === 'agent' ? 'You and Clay can control this tab' : 'Only you can control this tab. Clay is paused.';
|
|
27
|
-
var description = title + ' · ' + (tab.incognito === false ? 'Personal browser' : 'Incognito') + ' · ' + mode;
|
|
29
|
+
var description = title + (source ? ' · ' + tab.sourceTitle + ' · Gen ' + tab.sourceGeneration : '') + ' · ' + (tab.incognito === false ? 'Personal browser' : 'Incognito') + ' · ' + mode;
|
|
28
30
|
var label = document.createElement('span'); label.className = 'shared-browser-tab-label'; label.textContent = title;
|
|
29
31
|
button.innerHTML = iconHtml(tab.personalCookiesAvailable && tab.incognito ? 'glasses' : 'globe');
|
|
30
32
|
button.appendChild(label); button.setAttribute('aria-label', description); button.setAttribute('data-tip', description);
|