clay-server 2.47.0-beta.6 → 3.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/lib/daemon.js +6 -27
  2. package/lib/project-connection.js +21 -8
  3. package/lib/project-session-pair.js +312 -0
  4. package/lib/project-session-spawn.js +8 -4
  5. package/lib/project-sessions.js +108 -10
  6. package/lib/project-user-message.js +18 -8
  7. package/lib/project.js +33 -0
  8. package/lib/public/app.js +20 -0
  9. package/lib/public/css/menus.css +83 -1
  10. package/lib/public/css/messages.css +14 -0
  11. package/lib/public/css/pane.css +420 -0
  12. package/lib/public/css/sidebar.css +40 -0
  13. package/lib/public/css/sticky-notes.css +2 -0
  14. package/lib/public/index.html +4 -0
  15. package/lib/public/modules/app-connection.js +6 -0
  16. package/lib/public/modules/app-favicon.js +5 -0
  17. package/lib/public/modules/app-header.js +101 -6
  18. package/lib/public/modules/app-messages.js +59 -6
  19. package/lib/public/modules/app-panels.js +49 -27
  20. package/lib/public/modules/app-rendering.js +11 -4
  21. package/lib/public/modules/pane-bridge.js +41 -0
  22. package/lib/public/modules/pane-session.js +18 -0
  23. package/lib/public/modules/sidebar-sessions.js +175 -1
  24. package/lib/public/modules/sidebar.js +2 -0
  25. package/lib/public/modules/split-group-helpers.js +18 -0
  26. package/lib/public/modules/split-pair-ui.js +324 -0
  27. package/lib/public/modules/split-view.js +499 -0
  28. package/lib/public/modules/sticky-notes.js +32 -91
  29. package/lib/public/style.css +1 -0
  30. package/lib/sdk-bridge.js +111 -20
  31. package/lib/sdk-message-processor.js +2 -2
  32. package/lib/server.js +24 -4
  33. package/lib/session-hygiene.js +100 -0
  34. package/lib/session-pair-mcp-server.js +28 -0
  35. package/lib/session-split-groups.js +298 -0
  36. package/lib/sessions.js +163 -34
  37. package/lib/ws-request.js +14 -0
  38. package/lib/ws-schema.js +13 -0
  39. package/lib/yoke/adapters/claude.js +32 -0
  40. package/lib/yoke/adapters/codex.js +100 -77
  41. package/lib/yoke/adapters/kiro.js +34 -35
  42. package/lib/yoke/codex-app-server.js +56 -6
  43. package/lib/yoke/index.js +1 -0
  44. package/lib/yoke/skill-discovery.js +196 -0
  45. package/lib/yoke/vendor-registry.js +34 -0
  46. package/package.json +2 -2
@@ -0,0 +1,18 @@
1
+ export function groupedSessionIds(groups) {
2
+ var ids = new Set();
3
+ var list = groups || [];
4
+ for (var i = 0; i < list.length; i++) {
5
+ var members = list[i].members || [];
6
+ for (var j = 0; j < members.length; j++) ids.add(members[j]);
7
+ }
8
+ return ids;
9
+ }
10
+
11
+ export function findSplitGroup(groups, memberIds) {
12
+ var list = groups || [];
13
+ for (var i = 0; i < list.length; i++) {
14
+ var members = list[i].members || [];
15
+ if (members.length === 2 && members[0] === memberIds[0] && members[1] === memberIds[1]) return list[i];
16
+ }
17
+ return null;
18
+ }
@@ -0,0 +1,324 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER } from './app-rendering.js';
4
+ import { effortLevelsFor, effortDisplayName } from './app-panels.js';
5
+ import { showToast } from './utils.js';
6
+
7
+ var pairDialog = null;
8
+ // Set when the dialog was opened via "Add Worker" on an existing session:
9
+ // that session becomes the Driver and the dialog only configures the Worker.
10
+ var pendingDriver = null;
11
+
12
+ function optionValue(entry) {
13
+ if (typeof entry === "string") return entry;
14
+ return entry && (entry.value || entry.id) || "";
15
+ }
16
+
17
+ function optionLabel(entry) {
18
+ if (typeof entry === "string") return entry;
19
+ return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
20
+ }
21
+
22
+ function fillModels(select, vendor, options) {
23
+ var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
24
+ select.innerHTML = "";
25
+ var automatic = document.createElement("option");
26
+ automatic.value = "";
27
+ automatic.textContent = "Automatic";
28
+ select.appendChild(automatic);
29
+ for (var i = 0; i < models.length; i++) {
30
+ var option = document.createElement("option");
31
+ option.value = optionValue(models[i]);
32
+ option.textContent = optionLabel(models[i]);
33
+ select.appendChild(option);
34
+ }
35
+ }
36
+
37
+ function closePairDialog() {
38
+ if (pairDialog) pairDialog.remove();
39
+ pairDialog = null;
40
+ }
41
+
42
+ function buildVendorSelect(installed, preferred) {
43
+ var select = document.createElement("select");
44
+ select.className = "wt-modal-input";
45
+ for (var i = 0; i < VENDOR_ORDER.length; i++) {
46
+ if (installed.indexOf(VENDOR_ORDER[i]) === -1) continue;
47
+ var option = document.createElement("option");
48
+ option.value = VENDOR_ORDER[i];
49
+ option.textContent = VENDOR_NAMES[VENDOR_ORDER[i]] || VENDOR_ORDER[i];
50
+ select.appendChild(option);
51
+ }
52
+ if (preferred && installed.indexOf(preferred) !== -1) select.value = preferred;
53
+ return select;
54
+ }
55
+
56
+ // Effort levels are vendor- and model-specific (codex: minimal..xhigh,
57
+ // claude/kiro: low..max, plus per-model supportedEffortLevels overrides), so
58
+ // options are rebuilt whenever the role's vendor or model changes.
59
+ function fillEffortOptions(select, vendor, options, modelValue) {
60
+ var previous = select.value;
61
+ var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
62
+ var levels = effortLevelsFor(vendor, models, modelValue);
63
+ select.innerHTML = "";
64
+ var automatic = document.createElement("option");
65
+ automatic.value = "";
66
+ automatic.textContent = "Default";
67
+ select.appendChild(automatic);
68
+ for (var i = 0; i < levels.length; i++) {
69
+ var option = document.createElement("option");
70
+ option.value = levels[i];
71
+ option.textContent = effortDisplayName(levels[i]);
72
+ select.appendChild(option);
73
+ }
74
+ select.value = levels.indexOf(previous) !== -1 ? previous : "";
75
+ }
76
+
77
+ function buildEffortSelect() {
78
+ var select = document.createElement("select");
79
+ select.className = "wt-modal-input";
80
+ return select;
81
+ }
82
+
83
+ function fieldLabel(text, control) {
84
+ var label = document.createElement("label");
85
+ label.className = "wt-modal-label";
86
+ label.textContent = text;
87
+ var wrap = document.createDocumentFragment();
88
+ wrap.appendChild(label);
89
+ wrap.appendChild(control);
90
+ return { label: label, fragment: wrap };
91
+ }
92
+
93
+ // One role column: heading + Vendor/Model/Effort fields on the standard
94
+ // wt-modal form controls. Effort visibility follows the selected vendor's
95
+ // capability map (effort === false hides the field).
96
+ function roleColumn(role, description, vendorSelect, modelSelect, effortSelect, options) {
97
+ var col = document.createElement("section");
98
+ col.className = "pair-modal-role";
99
+ var heading = document.createElement("div");
100
+ heading.className = "pair-modal-role-heading";
101
+ heading.innerHTML = '<span class="pair-role-dot pair-role-' + role.toLowerCase() + '"></span><div><strong>' + role + '</strong><span>' + description + '</span></div>';
102
+ col.appendChild(heading);
103
+ var vendorField = fieldLabel("Vendor", vendorSelect);
104
+ col.appendChild(vendorField.fragment);
105
+ var modelField = fieldLabel("Model", modelSelect);
106
+ col.appendChild(modelField.fragment);
107
+ var effortField = fieldLabel("Reasoning effort", effortSelect);
108
+ col.appendChild(effortField.fragment);
109
+ function syncEffort() {
110
+ var capabilities = (options.capabilitiesByVendor && options.capabilitiesByVendor[vendorSelect.value]) || {};
111
+ var hidden = capabilities.effort === false;
112
+ effortField.label.style.display = hidden ? "none" : "";
113
+ effortSelect.style.display = hidden ? "none" : "";
114
+ }
115
+ vendorSelect.addEventListener("change", syncEffort);
116
+ syncEffort();
117
+ return col;
118
+ }
119
+
120
+ export function openPairDialog(driverInfo) {
121
+ if (!getWs() || getWs().readyState !== 1) return;
122
+ pendingDriver = driverInfo && Number.isInteger(driverInfo.sessionId) ? driverInfo : null;
123
+ getWs().send(JSON.stringify({ type: "pair_session_options" }));
124
+ }
125
+
126
+ export function showPairDialog(options) {
127
+ closePairDialog();
128
+ var driverInfo = pendingDriver;
129
+ pendingDriver = null;
130
+ var installed = options.installedVendors || [];
131
+ if (installed.length < 1) {
132
+ showToast("Install a coding agent before creating a pair.", "error");
133
+ return;
134
+ }
135
+ // Standard app modal pattern (wt-modal): overlay + centered modal with
136
+ // title, wt-modal-label/-input fields, and a wt-modal-actions row.
137
+ var container = document.createElement("div");
138
+ var overlay = document.createElement("div");
139
+ overlay.className = "wt-modal-overlay";
140
+ container.appendChild(overlay);
141
+ var modal = document.createElement("div");
142
+ modal.className = "wt-modal pair-modal";
143
+ modal.setAttribute("role", "dialog");
144
+ modal.setAttribute("aria-modal", "true");
145
+
146
+ var title = document.createElement("div");
147
+ title.className = "wt-modal-title";
148
+ title.textContent = driverInfo ? "Add Worker" : "Pair session";
149
+ modal.appendChild(title);
150
+ var desc = document.createElement("div");
151
+ desc.className = "pair-modal-desc";
152
+ desc.textContent = driverInfo
153
+ ? "This session becomes the Driver; a new Worker opens beside it."
154
+ : "One Driver plans and directs a visible Worker session.";
155
+ modal.appendChild(desc);
156
+
157
+ var driverVendor = buildVendorSelect(installed, installed.indexOf("claude") !== -1 ? "claude" : installed[0]);
158
+ var workerDefault = options.lastVendor && options.lastVendor !== driverVendor.value ? options.lastVendor : (installed.indexOf("codex") !== -1 ? "codex" : installed[0]);
159
+ var workerVendor = buildVendorSelect(installed, workerDefault);
160
+ var driverModel = document.createElement("select");
161
+ driverModel.className = "wt-modal-input";
162
+ var workerModel = document.createElement("select");
163
+ workerModel.className = "wt-modal-input";
164
+ var driverEffort = buildEffortSelect();
165
+ var workerEffort = buildEffortSelect();
166
+ function refreshDriverEffort() { fillEffortOptions(driverEffort, driverVendor.value, options, driverModel.value); }
167
+ function refreshWorkerEffort() { fillEffortOptions(workerEffort, workerVendor.value, options, workerModel.value); }
168
+ fillModels(driverModel, driverVendor.value, options);
169
+ fillModels(workerModel, workerVendor.value, options);
170
+ refreshDriverEffort();
171
+ refreshWorkerEffort();
172
+ workerEffort.value = "medium";
173
+ if (workerEffort.value !== "medium") workerEffort.value = "";
174
+ driverVendor.addEventListener("change", function () { fillModels(driverModel, driverVendor.value, options); refreshDriverEffort(); });
175
+ workerVendor.addEventListener("change", function () { fillModels(workerModel, workerVendor.value, options); refreshWorkerEffort(); });
176
+ driverModel.addEventListener("change", refreshDriverEffort);
177
+ workerModel.addEventListener("change", refreshWorkerEffort);
178
+
179
+ var roles = document.createElement("div");
180
+ roles.className = "pair-modal-roles";
181
+ if (driverInfo) {
182
+ var driverCard = document.createElement("section");
183
+ driverCard.className = "pair-modal-role";
184
+ var driverHeading = document.createElement("div");
185
+ driverHeading.className = "pair-modal-role-heading";
186
+ driverHeading.innerHTML = '<span class="pair-role-dot pair-role-driver"></span><div><strong>Driver</strong><span>Plans, delegates, and integrates</span></div>';
187
+ driverCard.appendChild(driverHeading);
188
+ var driverCurrent = document.createElement("div");
189
+ driverCurrent.className = "pair-modal-current";
190
+ var driverAvatar = document.createElement("img");
191
+ driverAvatar.className = "session-vendor-icon";
192
+ driverAvatar.src = VENDOR_AVATARS[driverInfo.vendor] || VENDOR_AVATARS.claude;
193
+ driverAvatar.alt = "";
194
+ driverCurrent.appendChild(driverAvatar);
195
+ var driverName = document.createElement("span");
196
+ driverName.textContent = driverInfo.title || "Current session";
197
+ driverCurrent.appendChild(driverName);
198
+ driverCard.appendChild(driverCurrent);
199
+ roles.appendChild(driverCard);
200
+ } else {
201
+ roles.appendChild(roleColumn("Driver", "Plans, delegates, and integrates", driverVendor, driverModel, driverEffort, options));
202
+ }
203
+ roles.appendChild(roleColumn("Worker", "Executes delegated tasks", workerVendor, workerModel, workerEffort, options));
204
+ modal.appendChild(roles);
205
+
206
+ var actions = document.createElement("div");
207
+ actions.className = "wt-modal-actions";
208
+ var cancelBtn = document.createElement("button");
209
+ cancelBtn.type = "button";
210
+ cancelBtn.className = "wt-modal-btn";
211
+ cancelBtn.textContent = "Cancel";
212
+ var createBtn = document.createElement("button");
213
+ createBtn.type = "button";
214
+ createBtn.className = "wt-modal-btn primary";
215
+ createBtn.textContent = driverInfo ? "Add Worker" : "Create pair";
216
+ actions.appendChild(cancelBtn);
217
+ actions.appendChild(createBtn);
218
+ modal.appendChild(actions);
219
+
220
+ container.appendChild(modal);
221
+ document.body.appendChild(container);
222
+ pairDialog = container;
223
+
224
+ cancelBtn.addEventListener("click", closePairDialog);
225
+ overlay.addEventListener("click", closePairDialog);
226
+ document.addEventListener("keydown", function onKey(event) {
227
+ if (event.key === "Escape" && pairDialog === container) {
228
+ document.removeEventListener("keydown", onKey);
229
+ closePairDialog();
230
+ }
231
+ });
232
+ createBtn.addEventListener("click", function () {
233
+ getWs().send(JSON.stringify({
234
+ type: "pair_session_create",
235
+ driver: driverInfo
236
+ ? { sessionId: driverInfo.sessionId }
237
+ : { vendor: driverVendor.value, model: driverModel.value, effort: driverEffort.value },
238
+ worker: { vendor: workerVendor.value, model: workerModel.value, effort: workerEffort.value },
239
+ }));
240
+ this.disabled = true;
241
+ this.textContent = "Creating…";
242
+ });
243
+ }
244
+
245
+ export function handlePairCreated(msg) {
246
+ if (!msg.ok) {
247
+ showToast(msg.error || "Could not create the pair.", "error");
248
+ return null;
249
+ }
250
+ closePairDialog();
251
+ return msg.group || null;
252
+ }
253
+
254
+ export function handleSplitDelegation(msg) {
255
+ var states = Object.assign({}, store.get('splitDelegations') || {});
256
+ if (msg.active) states[msg.groupId] = msg;
257
+ else delete states[msg.groupId];
258
+ store.set({ splitDelegations: states });
259
+ }
260
+
261
+ function sendSetPair(groupId, driverId) {
262
+ if (getWs() && getWs().readyState === 1) {
263
+ getWs().send(JSON.stringify({ type: "split_group_set_pair", id: groupId, driverId: driverId }));
264
+ }
265
+ }
266
+
267
+ export function syncPairChrome(host, split) {
268
+ if (!host || !split || !split.groupId) return;
269
+ var groups = store.get('splitGroups') || [];
270
+ var group = null;
271
+ for (var i = 0; i < groups.length; i++) {
272
+ if (groups[i].id === split.groupId) { group = groups[i]; break; }
273
+ }
274
+ var oldBadges = host.querySelectorAll(".split-pair-role, .split-pair-live, .split-pair-set-driver");
275
+ for (var bi = 0; bi < oldBadges.length; bi++) oldBadges[bi].remove();
276
+ host.classList.remove("split-delegating", "split-direction-right", "split-direction-left");
277
+ if (!group) return;
278
+ var headers = host.querySelectorAll(".split-pane-header");
279
+ if (!group.pair) {
280
+ // Ad-hoc split: offer a hover-revealed "Set Driver" action per pane.
281
+ for (var ai = 0; ai < split.panes.length && ai < headers.length; ai++) {
282
+ (function (paneSessionId, header) {
283
+ var setBtn = document.createElement("button");
284
+ setBtn.type = "button";
285
+ setBtn.className = "split-pair-set-driver";
286
+ setBtn.textContent = "Set Driver";
287
+ setBtn.title = "Make this session the Driver; the other pane becomes its Worker";
288
+ setBtn.addEventListener("click", function () { sendSetPair(group.id, paneSessionId); });
289
+ var titleEl = header.querySelector(".split-pane-title");
290
+ header.insertBefore(setBtn, titleEl ? titleEl.nextSibling : null);
291
+ })(split.panes[ai].sessionId, headers[ai]);
292
+ }
293
+ return;
294
+ }
295
+ for (var pi = 0; pi < split.panes.length && pi < headers.length; pi++) {
296
+ (function (sessionId, header) {
297
+ var isDriver = sessionId === group.pair.driverId;
298
+ var role = isDriver ? "Driver" : "Worker";
299
+ var badge = document.createElement("button");
300
+ badge.type = "button";
301
+ badge.className = "split-pair-role split-pair-role-" + role.toLowerCase();
302
+ badge.textContent = role;
303
+ badge.title = isDriver
304
+ ? "Driver — click to clear the Driver/Worker roles"
305
+ : "Worker — click to make this session the Driver instead";
306
+ badge.addEventListener("click", function () {
307
+ sendSetPair(group.id, isDriver ? null : sessionId);
308
+ });
309
+ var title = header.querySelector(".split-pane-title");
310
+ header.insertBefore(badge, title ? title.nextSibling : null);
311
+ })(split.panes[pi].sessionId, headers[pi]);
312
+ }
313
+ var active = (store.get('splitDelegations') || {})[group.id];
314
+ if (!active || !active.active) return;
315
+ host.classList.add("split-delegating");
316
+ var fromIndex = split.panes[0].sessionId === active.from ? 0 : 1;
317
+ host.classList.add(fromIndex === 0 ? "split-direction-right" : "split-direction-left");
318
+ for (var si = 0; si < split.panes.length && si < headers.length; si++) {
319
+ var live = document.createElement("span");
320
+ live.className = "split-pair-live";
321
+ live.textContent = split.panes[si].sessionId === active.to ? "Following direction" : "Waiting";
322
+ headers[si].appendChild(live);
323
+ }
324
+ }