clay-server 4.1.0-beta.16 → 4.1.0-beta.18
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/issues-service.js +3 -1
- package/lib/notes.js +20 -3
- package/lib/os-users.js +6 -0
- package/lib/project-connection.js +5 -2
- package/lib/project-http.js +34 -37
- package/lib/project-sessions.js +1 -0
- package/lib/project-user-message.js +43 -0
- package/lib/project.js +21 -0
- package/lib/public/css/issues.css +17 -3
- package/lib/public/css/sticky-notes.css +29 -0
- package/lib/public/modules/app-messages.js +5 -0
- package/lib/public/modules/scheduled-tasks.js +1 -1
- package/lib/public/modules/sticky-notes-browser.js +126 -5
- package/lib/public/modules/sticky-notes.js +2 -3
- package/lib/sdk-bridge.js +63 -19
- package/lib/sdk-message-processor.js +19 -8
- package/lib/sdk-skill-discovery.js +6 -28
- package/lib/sessions.js +8 -1
- package/lib/yoke/adapters/acp.js +3 -2
- package/lib/yoke/adapters/antigravity.js +2 -2
- package/lib/yoke/adapters/claude.js +25 -7
- package/lib/yoke/adapters/codex.js +158 -16
- package/lib/yoke/adapters/kiro.js +6 -5
- package/lib/yoke/session-skill-catalog.js +45 -0
- package/lib/yoke/skill-discovery.js +60 -20
- package/package.json +1 -1
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// the canvas over the conversation; this pane lists them, splits them into Open
|
|
5
5
|
// and Closed, and is where a note is completed or brought back.
|
|
6
6
|
//
|
|
7
|
-
// Completion here is Close
|
|
8
|
-
//
|
|
9
|
-
// There is deliberately no destructive control on this surface at all.
|
|
7
|
+
// Completion here is Close. Permanent deletion is available only for closed
|
|
8
|
+
// notes and always requires an explicit, accessible confirmation.
|
|
10
9
|
|
|
11
10
|
import { store } from './store.js';
|
|
12
11
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
@@ -19,6 +18,10 @@ import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } fr
|
|
|
19
18
|
var panel = null;
|
|
20
19
|
var gridEl = null;
|
|
21
20
|
var tabsEl = null;
|
|
21
|
+
var deleteDialog = null;
|
|
22
|
+
var deleteTrigger = null;
|
|
23
|
+
var deleteRequestCounter = 0;
|
|
24
|
+
var deleteRequestTimer = null;
|
|
22
25
|
|
|
23
26
|
// Registered by app.js so this module never imports the other right-pane tools
|
|
24
27
|
// and cannot form an import cycle with them.
|
|
@@ -63,11 +66,15 @@ function ensurePanel() {
|
|
|
63
66
|
'</div>' +
|
|
64
67
|
'</header>' +
|
|
65
68
|
'<div id="notes-browser-tabs" class="notes-browser-tabs" role="tablist" aria-label="Sticky Notes state"></div>' +
|
|
66
|
-
'<div id="notes-browser-grid" class="notes-browser-grid" role="list"></div>'
|
|
69
|
+
'<div id="notes-browser-grid" class="notes-browser-grid" role="list"></div>' +
|
|
70
|
+
'<div id="notes-delete-dialog" class="notes-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="notes-delete-heading">' +
|
|
71
|
+
'<div class="notes-delete-dialog-card"><h2 id="notes-delete-heading">Delete this note permanently?</h2><p>This permanently removes the note. It cannot be restored.</p><p class="notes-delete-dialog-title"></p><p class="notes-delete-dialog-status" role="status"></p><div class="notes-delete-dialog-actions"><button type="button" data-action="cancel">Cancel</button><button type="button" class="danger" data-action="delete">Delete permanently</button></div></div>' +
|
|
72
|
+
'</div>';
|
|
67
73
|
panels.appendChild(panel);
|
|
68
74
|
|
|
69
75
|
gridEl = panel.querySelector("#notes-browser-grid");
|
|
70
76
|
tabsEl = panel.querySelector("#notes-browser-tabs");
|
|
77
|
+
deleteDialog = panel.querySelector("#notes-delete-dialog");
|
|
71
78
|
|
|
72
79
|
panel.querySelector("#notes-browser-close").addEventListener("click", closeNotesBrowser);
|
|
73
80
|
panel.querySelector("#notes-browser-wide").addEventListener("click", function () {
|
|
@@ -76,6 +83,29 @@ function ensurePanel() {
|
|
|
76
83
|
panel.querySelector("#notes-browser-fullscreen").addEventListener("click", function () {
|
|
77
84
|
applyWindowState(store.get('notesBrowserWide'), !store.get('notesBrowserFullscreen'));
|
|
78
85
|
});
|
|
86
|
+
deleteDialog.querySelector('[data-action="cancel"]').addEventListener("click", closeDeleteDialog);
|
|
87
|
+
deleteDialog.querySelector('[data-action="delete"]').addEventListener("click", confirmDelete);
|
|
88
|
+
deleteDialog.addEventListener("click", function (event) { if (event.target === deleteDialog) closeDeleteDialog(); });
|
|
89
|
+
deleteDialog.addEventListener("keydown", function (event) {
|
|
90
|
+
if (event.key === "Escape") {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
closeDeleteDialog(true);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (event.key !== "Tab") return;
|
|
97
|
+
var focusable = deleteDialog.querySelectorAll("button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])");
|
|
98
|
+
if (!focusable.length) return;
|
|
99
|
+
var first = focusable[0];
|
|
100
|
+
var last = focusable[focusable.length - 1];
|
|
101
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
last.focus();
|
|
104
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
first.focus();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
79
109
|
refreshIcons();
|
|
80
110
|
}
|
|
81
111
|
|
|
@@ -166,7 +196,8 @@ function buildCard(data) {
|
|
|
166
196
|
title.textContent = getTitle(data.text) || "Untitled";
|
|
167
197
|
header.appendChild(title);
|
|
168
198
|
|
|
169
|
-
//
|
|
199
|
+
// Closing and reopening are lifecycle actions; permanent deletion is offered
|
|
200
|
+
// separately below only because this card is known to be closed.
|
|
170
201
|
var action = document.createElement("button");
|
|
171
202
|
action.type = "button";
|
|
172
203
|
action.className = "notes-browser-card-action";
|
|
@@ -200,6 +231,14 @@ function buildCard(data) {
|
|
|
200
231
|
card.appendChild(body);
|
|
201
232
|
|
|
202
233
|
if (closed) {
|
|
234
|
+
var deleteAction = document.createElement("button");
|
|
235
|
+
deleteAction.type = "button";
|
|
236
|
+
deleteAction.className = "notes-browser-card-action notes-browser-card-delete";
|
|
237
|
+
deleteAction.innerHTML = iconHtml("trash-2") + "<span>Delete permanently</span>";
|
|
238
|
+
deleteAction.title = "Permanently delete this closed note";
|
|
239
|
+
deleteAction.setAttribute("aria-label", "Delete permanently: " + title.textContent);
|
|
240
|
+
deleteAction.addEventListener("click", function (e) { e.stopPropagation(); openDeleteDialog(data, deleteAction); });
|
|
241
|
+
header.appendChild(deleteAction);
|
|
203
242
|
var meta = document.createElement("p");
|
|
204
243
|
meta.className = "notes-browser-card-meta";
|
|
205
244
|
meta.textContent = closedMeta(data);
|
|
@@ -224,6 +263,77 @@ function buildCard(data) {
|
|
|
224
263
|
return card;
|
|
225
264
|
}
|
|
226
265
|
|
|
266
|
+
function nextDeleteRequestId() {
|
|
267
|
+
deleteRequestCounter++;
|
|
268
|
+
return "delete-note-" + Date.now() + "-" + deleteRequestCounter;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function accountKey() {
|
|
272
|
+
return store.get('myUserId') ? String(store.get('myUserId')) : "single";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function openDeleteDialog(data, trigger) {
|
|
276
|
+
if (!deleteDialog || !data || !isClosed(data)) return;
|
|
277
|
+
deleteTrigger = trigger || null;
|
|
278
|
+
store.set({ notesDeletePending: { noteId: data.id, requestId: null, projectSlug: store.get('currentSlug') || null, accountKey: accountKey() } });
|
|
279
|
+
deleteDialog.querySelector(".notes-delete-dialog-title").textContent = getTitle(data.text) || "Untitled";
|
|
280
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "";
|
|
281
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
282
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
283
|
+
deleteDialog.classList.remove("hidden");
|
|
284
|
+
deleteDialog.querySelector('[data-action="cancel"]').focus();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function closeDeleteDialog(force) {
|
|
288
|
+
var pending = store.get('notesDeletePending');
|
|
289
|
+
if (!force && pending && pending.requestId) return;
|
|
290
|
+
if (deleteRequestTimer) { clearTimeout(deleteRequestTimer); deleteRequestTimer = null; }
|
|
291
|
+
if (deleteDialog) deleteDialog.classList.add("hidden");
|
|
292
|
+
store.set({ notesDeletePending: null });
|
|
293
|
+
if (deleteTrigger && deleteTrigger.isConnected) deleteTrigger.focus();
|
|
294
|
+
deleteTrigger = null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function confirmDelete() {
|
|
298
|
+
var pending = store.get('notesDeletePending');
|
|
299
|
+
if (!pending || !deleteDialog) return;
|
|
300
|
+
if (!store.get('connected')) {
|
|
301
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "Sticky Notes are unavailable while disconnected.";
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
var requestId = nextDeleteRequestId();
|
|
305
|
+
store.set({ notesDeletePending: Object.assign({}, pending, { requestId: requestId }) });
|
|
306
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = true;
|
|
307
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = true;
|
|
308
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "Deleting permanently...";
|
|
309
|
+
send({ type: "note_delete_permanently", id: pending.noteId, requestId: requestId });
|
|
310
|
+
deleteRequestTimer = setTimeout(function () {
|
|
311
|
+
var active = store.get('notesDeletePending');
|
|
312
|
+
if (!deleteDialog || !active || active.requestId !== requestId) return;
|
|
313
|
+
store.set({ notesDeletePending: Object.assign({}, active, { requestId: null }) });
|
|
314
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
315
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
316
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = "The delete request timed out. Try again.";
|
|
317
|
+
deleteRequestTimer = null;
|
|
318
|
+
}, 10000);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function handleNoteDeleteResult(msg) {
|
|
322
|
+
var pending = store.get('notesDeletePending');
|
|
323
|
+
if (!deleteDialog || !msg || msg.projectSlug !== store.get('currentSlug') ||
|
|
324
|
+
msg.requestId !== (pending && pending.requestId) || !pending || msg.noteId !== pending.noteId ||
|
|
325
|
+
msg.projectSlug !== pending.projectSlug || pending.accountKey !== accountKey()) return;
|
|
326
|
+
if (deleteRequestTimer) { clearTimeout(deleteRequestTimer); deleteRequestTimer = null; }
|
|
327
|
+
if (msg.ok) {
|
|
328
|
+
closeDeleteDialog(true);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
store.set({ notesDeletePending: Object.assign({}, pending, { requestId: null }) });
|
|
332
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
333
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
334
|
+
deleteDialog.querySelector(".notes-delete-dialog-status").textContent = msg.error || "The note could not be deleted.";
|
|
335
|
+
}
|
|
336
|
+
|
|
227
337
|
function emptyState(tab) {
|
|
228
338
|
var empty = document.createElement("div");
|
|
229
339
|
empty.className = "notes-browser-empty";
|
|
@@ -242,6 +352,11 @@ function emptyState(tab) {
|
|
|
242
352
|
export function renderNotesBrowser() {
|
|
243
353
|
if (!gridEl) return;
|
|
244
354
|
var all = listNoteData();
|
|
355
|
+
var pending = store.get('notesDeletePending');
|
|
356
|
+
if (pending) {
|
|
357
|
+
var pendingCurrent = all.filter(function (item) { return item && item.id === pending.noteId; })[0];
|
|
358
|
+
if (!pendingCurrent || !isClosed(pendingCurrent)) closeDeleteDialog(true);
|
|
359
|
+
}
|
|
245
360
|
var open = openNotes(all);
|
|
246
361
|
var closed = closedNotes(all);
|
|
247
362
|
renderTabs(open.length, closed.length);
|
|
@@ -284,6 +399,9 @@ export function openNotesBrowser() {
|
|
|
284
399
|
}
|
|
285
400
|
|
|
286
401
|
export function closeNotesBrowser() {
|
|
402
|
+
// Reset destructive UI state even when the panel was already closed. Right
|
|
403
|
+
// workbench/project transitions can race with panel visibility updates.
|
|
404
|
+
closeDeleteDialog(true);
|
|
287
405
|
releaseRightWorkbench("notes-browser");
|
|
288
406
|
if (!store.get('notesBrowserOpen')) return;
|
|
289
407
|
if (panel) panel.classList.add("hidden");
|
|
@@ -304,10 +422,13 @@ export function initNotesBrowser() {
|
|
|
304
422
|
registerRightWorkbench("notes-browser", closeNotesBrowser);
|
|
305
423
|
document.addEventListener("keydown", function (e) {
|
|
306
424
|
if (e.key !== "Escape") return;
|
|
425
|
+
if (deleteDialog && !deleteDialog.classList.contains("hidden")) return;
|
|
307
426
|
if (!store.get('notesBrowserOpen')) return;
|
|
308
427
|
closeNotesBrowser();
|
|
309
428
|
});
|
|
310
429
|
store.subscribe(function (state, previous) {
|
|
430
|
+
if (state.connected !== previous.connected || state.myUserId !== previous.myUserId ||
|
|
431
|
+
state.isMultiUserMode !== previous.isMultiUserMode) closeDeleteDialog(true);
|
|
311
432
|
if (state.currentSlug === previous.currentSlug) return;
|
|
312
433
|
// A different project starts from a closed, bounded, Open-tab pane.
|
|
313
434
|
closeNotesBrowser();
|
|
@@ -424,13 +424,12 @@ export function handleNoteWritten(msg) {
|
|
|
424
424
|
// an older daemon on the other end of the socket still must not corrupt state.
|
|
425
425
|
export function handleNoteDeleted(msg) {
|
|
426
426
|
var entry = notes.get(msg.id);
|
|
427
|
-
if (
|
|
428
|
-
|
|
427
|
+
if (entry) entry.el.remove();
|
|
428
|
+
clearNoteTimers(msg.id);
|
|
429
429
|
notes.delete(msg.id);
|
|
430
430
|
updateBadge();
|
|
431
431
|
updateSidebarBadge();
|
|
432
432
|
|
|
433
|
-
clearNoteTimers(msg.id);
|
|
434
433
|
refreshBrowser();
|
|
435
434
|
}
|
|
436
435
|
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -7,6 +7,7 @@ var usersModule = require("./users");
|
|
|
7
7
|
var { getCodexConfig } = require("./codex-defaults");
|
|
8
8
|
var { getKiroConfig } = require("./kiro-defaults");
|
|
9
9
|
var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
|
|
10
|
+
var skillDiscovery = require("./yoke/skill-discovery");
|
|
10
11
|
var { isSafeBashSegment } = require("./safe-bash-commands");
|
|
11
12
|
var { createMessageQueue } = require("./sdk-message-queue");
|
|
12
13
|
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
@@ -162,6 +163,22 @@ function createSDKBridge(opts) {
|
|
|
162
163
|
}];
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
function getCodexSessionMcpServer(session, queryGeneration) {
|
|
167
|
+
if (!session || !Number.isInteger(queryGeneration)) return null;
|
|
168
|
+
var bridgeArgs = [
|
|
169
|
+
path.join(__dirname, "yoke", "mcp-bridge-server.js"),
|
|
170
|
+
"--port", String(clayPort),
|
|
171
|
+
"--slug", slug,
|
|
172
|
+
"--session", String(session.localId),
|
|
173
|
+
"--query-generation", String(queryGeneration),
|
|
174
|
+
"--session-only",
|
|
175
|
+
];
|
|
176
|
+
if (clayTls) bridgeArgs.push("--tls");
|
|
177
|
+
var bridgeEnv = {};
|
|
178
|
+
if (clayAuthToken) bridgeEnv.CLAY_AUTH_TOKEN = clayAuthToken;
|
|
179
|
+
return { name: "clay-session-tools", command: process.execPath, args: bridgeArgs, env: bridgeEnv };
|
|
180
|
+
}
|
|
181
|
+
|
|
165
182
|
function getFreshAuthState(force) {
|
|
166
183
|
var now = Date.now();
|
|
167
184
|
if (!force && _cachedFreshAuthState && now - _cachedFreshAuthAt < 15000) {
|
|
@@ -338,7 +355,7 @@ function createSDKBridge(opts) {
|
|
|
338
355
|
}
|
|
339
356
|
|
|
340
357
|
// --- Skill discovery (extracted to sdk-skill-discovery.js) ---
|
|
341
|
-
var skills = attachSkillDiscovery({ cwd: cwd });
|
|
358
|
+
var skills = attachSkillDiscovery({ cwd: cwd, getOptions: function () { return { env: getRuntimeEnv() }; } });
|
|
342
359
|
var discoverSkillDirs = skills.discoverSkillDirs;
|
|
343
360
|
var mergeSkills = skills.mergeSkills;
|
|
344
361
|
|
|
@@ -351,6 +368,19 @@ function createSDKBridge(opts) {
|
|
|
351
368
|
return owner && owner.linuxUser ? owner.linuxUser : null;
|
|
352
369
|
}
|
|
353
370
|
|
|
371
|
+
function getSessionSkillOptions(session) {
|
|
372
|
+
var runtimeEnv = getRuntimeEnv(session || {});
|
|
373
|
+
if (!osUsers) return { env: runtimeEnv };
|
|
374
|
+
var linuxUser = getSessionLinuxUser(session);
|
|
375
|
+
if (!linuxUser) return { disable: true };
|
|
376
|
+
try {
|
|
377
|
+
var info = require("./os-users").resolveOsUserInfo(linuxUser);
|
|
378
|
+
return Object.assign({}, skillDiscovery.optionsForOsUser(info), { env: runtimeEnv });
|
|
379
|
+
} catch (e) {
|
|
380
|
+
return { disable: true };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
354
384
|
var titleGenerator = sessionTitleGenerator.attachSessionTitleGenerator({
|
|
355
385
|
sm: sm,
|
|
356
386
|
cwd: cwd,
|
|
@@ -372,6 +402,7 @@ function createSDKBridge(opts) {
|
|
|
372
402
|
adapter: adapter,
|
|
373
403
|
saveImageFile: opts.saveImageFile,
|
|
374
404
|
getSessionLinuxUser: getSessionLinuxUser,
|
|
405
|
+
getSessionSkillOptions: getSessionSkillOptions,
|
|
375
406
|
onProcessingChanged: onProcessingChanged,
|
|
376
407
|
onTurnDone: onTurnDone,
|
|
377
408
|
onAutoTitle: function (session) { titleGenerator.generate(session); },
|
|
@@ -1004,7 +1035,8 @@ function createSDKBridge(opts) {
|
|
|
1004
1035
|
&& Number(session._sdkQueryGeneration || 0) === codexCatalogCandidate.generation
|
|
1005
1036
|
&& session.cliSessionId === msg.sessionId
|
|
1006
1037
|
&& (!sm.sessions || typeof sm.sessions.get !== "function" || sm.sessions.get(session.localId) === session)) {
|
|
1007
|
-
session.codexDynamicToolCatalogVersion = codexCatalogCandidate.version;
|
|
1038
|
+
if (codexCatalogCandidate.version) session.codexDynamicToolCatalogVersion = codexCatalogCandidate.version;
|
|
1039
|
+
if (codexCatalogCandidate.issuesVersion) session.codexIssuesToolCatalogVersion = codexCatalogCandidate.issuesVersion;
|
|
1008
1040
|
sm.saveSessionFile(session);
|
|
1009
1041
|
codexCatalogCandidate = null;
|
|
1010
1042
|
}
|
|
@@ -1574,7 +1606,11 @@ function createSDKBridge(opts) {
|
|
|
1574
1606
|
if (linuxUser) {
|
|
1575
1607
|
claudeOpts.linuxUser = linuxUser;
|
|
1576
1608
|
claudeOpts.singleTurn = !!session.singleTurn;
|
|
1577
|
-
|
|
1609
|
+
try {
|
|
1610
|
+
claudeOpts.originalHome = require("./os-users").resolveOsUserInfo(linuxUser).home;
|
|
1611
|
+
} catch (e) {
|
|
1612
|
+
claudeOpts.originalHome = null;
|
|
1613
|
+
}
|
|
1578
1614
|
claudeOpts.projectPath = session.cwd || null;
|
|
1579
1615
|
claudeOpts._perfT0 = t0;
|
|
1580
1616
|
// Pass previous worker state for reuse
|
|
@@ -1616,6 +1652,17 @@ function createSDKBridge(opts) {
|
|
|
1616
1652
|
// delivered by this provider.
|
|
1617
1653
|
var sessionToolDefs = getSessionToolDefs(session) || [];
|
|
1618
1654
|
var queryGeneration = Number(session._sdkQueryGeneration || 0);
|
|
1655
|
+
var hasIssueCatalog = false;
|
|
1656
|
+
for (var issueToolIndex = 0; issueToolIndex < sessionToolDefs.length; issueToolIndex++) {
|
|
1657
|
+
if (sessionToolDefs[issueToolIndex].name === "create_issue") {
|
|
1658
|
+
hasIssueCatalog = true;
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
var recoverCodexIssueTools = sessionAdapter.vendor === "codex"
|
|
1663
|
+
&& !!session.cliSessionId
|
|
1664
|
+
&& hasIssueCatalog
|
|
1665
|
+
&& Number(session.codexIssuesToolCatalogVersion || 0) < 1;
|
|
1619
1666
|
function queryContextAvailable() {
|
|
1620
1667
|
if (Number(session._sdkQueryGeneration || 0) !== queryGeneration) return false;
|
|
1621
1668
|
if (sm && sm.sessions && typeof sm.sessions.get === "function" && sm.sessions.get(session.localId) !== session) return false;
|
|
@@ -1750,6 +1797,7 @@ function createSDKBridge(opts) {
|
|
|
1750
1797
|
cwd: cwd,
|
|
1751
1798
|
linuxUser: linuxUser || undefined,
|
|
1752
1799
|
env: sessionRuntimeEnv,
|
|
1800
|
+
skillOptions: getSessionSkillOptions(session),
|
|
1753
1801
|
// appendSystemPrompt, NOT systemPrompt: a plain-string systemPrompt
|
|
1754
1802
|
// REPLACES the entire Claude Code system prompt (sdk.d.ts:2096), which
|
|
1755
1803
|
// would strip the driver session down to the two-sentence pair preset.
|
|
@@ -1771,6 +1819,9 @@ function createSDKBridge(opts) {
|
|
|
1771
1819
|
toolServers: mergedMcpServers,
|
|
1772
1820
|
toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
|
|
1773
1821
|
dynamicTools: sessionToolDefs,
|
|
1822
|
+
// Codex persists dynamic tools with a thread. A stale authorized Driver
|
|
1823
|
+
// gets this exact-query bridge; its handlers still reauthorize live.
|
|
1824
|
+
sessionMcpServer: recoverCodexIssueTools ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
|
|
1774
1825
|
callDynamicTool: function (toolName, args) {
|
|
1775
1826
|
var toolDef = findSessionToolDef(toolName);
|
|
1776
1827
|
if (toolDef && typeof toolDef.handler === "function") {
|
|
@@ -1957,6 +2008,12 @@ function createSDKBridge(opts) {
|
|
|
1957
2008
|
if (completeInterviewCatalog || requiredCatalogTools.report_scheduled_task_outcome) {
|
|
1958
2009
|
codexCatalogCandidate = { generation: queryGeneration, version: yoke.sessionTools.CODEX_CATALOG_VERSION };
|
|
1959
2010
|
}
|
|
2011
|
+
if (hasIssueCatalog) {
|
|
2012
|
+
codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, {
|
|
2013
|
+
generation: queryGeneration,
|
|
2014
|
+
issuesVersion: 1,
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
1960
2017
|
}
|
|
1961
2018
|
session.streamPromise = processQueryStream(session, codexCatalogCandidate).catch(function(err) {
|
|
1962
2019
|
});
|
|
@@ -2199,22 +2256,9 @@ function createSDKBridge(opts) {
|
|
|
2199
2256
|
slug: slug,
|
|
2200
2257
|
});
|
|
2201
2258
|
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
var seen = new Set();
|
|
2206
|
-
var combined = [];
|
|
2207
|
-
var all = result.slashCommands.concat(Array.from(sm.skillNames));
|
|
2208
|
-
for (var k = 0; k < all.length; k++) {
|
|
2209
|
-
if (!seen.has(all[k])) {
|
|
2210
|
-
seen.add(all[k]);
|
|
2211
|
-
combined.push(all[k]);
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
sm.slashCommands = combined;
|
|
2215
|
-
sm.setSlashCommandsForVendor(defaultVendor, combined);
|
|
2216
|
-
send({ type: "slash_commands", commands: combined, vendor: defaultVendor });
|
|
2217
|
-
}
|
|
2259
|
+
// Skill and slash-command catalogs are session-owned. The warmup has
|
|
2260
|
+
// no authenticated session target, so never publish its personal
|
|
2261
|
+
// inventory through the project-wide session manager or broadcast.
|
|
2218
2262
|
if (result.defaultModel) {
|
|
2219
2263
|
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
2220
2264
|
sm.defaultModelByVendor[defaultVendor] = sm.defaultModelByVendor[defaultVendor] || result.defaultModel;
|
|
@@ -28,6 +28,7 @@ function attachMessageProcessor(ctx) {
|
|
|
28
28
|
var mergeSkills = ctx.mergeSkills;
|
|
29
29
|
var saveImageFile = ctx.saveImageFile;
|
|
30
30
|
var getSessionLinuxUser = ctx.getSessionLinuxUser || function () { return null; };
|
|
31
|
+
var getSessionSkillOptions = ctx.getSessionSkillOptions || function () { return {}; };
|
|
31
32
|
|
|
32
33
|
function getMateIdForNotification() {
|
|
33
34
|
if (!isMate) return null;
|
|
@@ -208,21 +209,26 @@ function attachMessageProcessor(ctx) {
|
|
|
208
209
|
session.activeBackgroundTasks = [];
|
|
209
210
|
sendToSession(session, { type: "active_background_tasks", tasks: [] });
|
|
210
211
|
if (previousBackgroundTaskCount !== 0) sm.broadcastSessionList();
|
|
211
|
-
var fsSkills = discoverSkillDirs();
|
|
212
|
-
|
|
212
|
+
var fsSkills = discoverSkillDirs(getSessionSkillOptions(session));
|
|
213
|
+
session.skillNames = Array.from(mergeSkills(parsed.skills, fsSkills));
|
|
213
214
|
if (parsed.slashCommands) {
|
|
214
215
|
// Union: SDK slash_commands + merged skills (deduplicated)
|
|
216
|
+
session.vendorSlashCommands = parsed.slashCommands.filter(function (name) {
|
|
217
|
+
return session.skillNames.indexOf(name) === -1;
|
|
218
|
+
});
|
|
215
219
|
var seen = new Set();
|
|
216
220
|
var combined = [];
|
|
217
|
-
var all =
|
|
221
|
+
var all = session.vendorSlashCommands.concat(session.skillNames);
|
|
218
222
|
for (var k = 0; k < all.length; k++) {
|
|
219
223
|
if (!seen.has(all[k])) {
|
|
220
224
|
seen.add(all[k]);
|
|
221
225
|
combined.push(all[k]);
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
session.slashCommands = combined;
|
|
229
|
+
session.slashCommandsByVendor = session.slashCommandsByVendor || {};
|
|
230
|
+
session.slashCommandsByVendor[session.vendor || (adapter && adapter.vendor) || "claude"] = combined;
|
|
231
|
+
sendToSession(session, { type: "slash_commands", commands: combined, vendor: session.vendor || (adapter && adapter.vendor) || "claude" });
|
|
226
232
|
}
|
|
227
233
|
if (parsed.model) {
|
|
228
234
|
var initVendor = session.vendor || (adapter && adapter.vendor) || "claude";
|
|
@@ -899,15 +905,20 @@ function attachMessageProcessor(ctx) {
|
|
|
899
905
|
// init) and push a full replacement to the client.
|
|
900
906
|
var cmdSeen = new Set();
|
|
901
907
|
var cmdCombined = [];
|
|
902
|
-
|
|
908
|
+
session.vendorSlashCommands = (parsed.commandNames || []).filter(function (name) {
|
|
909
|
+
return (session.skillNames || []).indexOf(name) === -1;
|
|
910
|
+
});
|
|
911
|
+
var cmdAll = session.vendorSlashCommands.concat(session.skillNames || []);
|
|
903
912
|
for (var ci = 0; ci < cmdAll.length; ci++) {
|
|
904
913
|
if (!cmdSeen.has(cmdAll[ci])) {
|
|
905
914
|
cmdSeen.add(cmdAll[ci]);
|
|
906
915
|
cmdCombined.push(cmdAll[ci]);
|
|
907
916
|
}
|
|
908
917
|
}
|
|
909
|
-
|
|
910
|
-
|
|
918
|
+
session.slashCommands = cmdCombined;
|
|
919
|
+
session.slashCommandsByVendor = session.slashCommandsByVendor || {};
|
|
920
|
+
session.slashCommandsByVendor[session.vendor || (adapter && adapter.vendor) || "claude"] = cmdCombined;
|
|
921
|
+
sendToSession(session, { type: "slash_commands", commands: cmdCombined, vendor: session.vendor || (adapter && adapter.vendor) || "claude" });
|
|
911
922
|
|
|
912
923
|
} else if (parsed.yokeType === "worker_shutting_down") {
|
|
913
924
|
// Surface non-routine teardown reasons; "host_exit" is the normal close.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
var path = require("path");
|
|
1
|
+
var skillDiscovery = require("./yoke/skill-discovery");
|
|
3
2
|
|
|
4
3
|
// Split shell command on operators (&&, ||, ;, |) while respecting quotes
|
|
5
4
|
// and parentheses. Returns array of command segments.
|
|
@@ -79,34 +78,13 @@ function splitShellSegments(cmd) {
|
|
|
79
78
|
|
|
80
79
|
function attachSkillDiscovery(ctx) {
|
|
81
80
|
var cwd = ctx.cwd;
|
|
81
|
+
var getOptions = ctx.getOptions || function () { return {}; };
|
|
82
82
|
|
|
83
|
-
function discoverSkillDirs() {
|
|
83
|
+
function discoverSkillDirs(options) {
|
|
84
|
+
var discovered = skillDiscovery.discoverSkills(cwd, options || getOptions());
|
|
84
85
|
var skills = {};
|
|
85
|
-
var
|
|
86
|
-
path.
|
|
87
|
-
path.join(cwd, ".claude", "skills"),
|
|
88
|
-
];
|
|
89
|
-
for (var d = 0; d < dirs.length; d++) {
|
|
90
|
-
var base = dirs[d];
|
|
91
|
-
var entries;
|
|
92
|
-
try {
|
|
93
|
-
entries = fs.readdirSync(base, { withFileTypes: true });
|
|
94
|
-
} catch (e) {
|
|
95
|
-
continue; // directory doesn't exist
|
|
96
|
-
}
|
|
97
|
-
for (var i = 0; i < entries.length; i++) {
|
|
98
|
-
var entry = entries[i];
|
|
99
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
100
|
-
var skillDir = path.join(base, entry.name);
|
|
101
|
-
var skillMd = path.join(skillDir, "SKILL.md");
|
|
102
|
-
try {
|
|
103
|
-
fs.accessSync(skillMd, fs.constants.R_OK);
|
|
104
|
-
// project skills override global skills with same name
|
|
105
|
-
skills[entry.name] = skillDir;
|
|
106
|
-
} catch (e) {
|
|
107
|
-
// no SKILL.md, skip
|
|
108
|
-
}
|
|
109
|
-
}
|
|
86
|
+
for (var i = 0; i < discovered.length; i++) {
|
|
87
|
+
skills[discovered[i].name] = discovered[i].path.replace(/\/SKILL\.md$/, "");
|
|
110
88
|
}
|
|
111
89
|
return skills;
|
|
112
90
|
}
|
package/lib/sessions.js
CHANGED
|
@@ -24,6 +24,7 @@ function createSessionManager(opts) {
|
|
|
24
24
|
var sessionViewedListeners = [];
|
|
25
25
|
var onSessionRenamed = opts.onSessionRenamed || function () {};
|
|
26
26
|
var onSessionIdentityAssigned = opts.onSessionIdentityAssigned || function () {};
|
|
27
|
+
var onSessionHydrate = opts.onSessionHydrate || null;
|
|
27
28
|
|
|
28
29
|
// --- Multi-session state ---
|
|
29
30
|
var nextLocalId = 1;
|
|
@@ -162,6 +163,7 @@ function createSessionManager(opts) {
|
|
|
162
163
|
if (session.ownerId) metaObj.ownerId = session.ownerId;
|
|
163
164
|
if (session.vendor) metaObj.vendor = session.vendor;
|
|
164
165
|
if (session.codexDynamicToolCatalogVersion) metaObj.codexDynamicToolCatalogVersion = session.codexDynamicToolCatalogVersion;
|
|
166
|
+
if (session.codexIssuesToolCatalogVersion) metaObj.codexIssuesToolCatalogVersion = session.codexIssuesToolCatalogVersion;
|
|
165
167
|
if (session.model) metaObj.model = session.model;
|
|
166
168
|
if (session.effort) metaObj.effort = session.effort;
|
|
167
169
|
// Persist the session's "born" mode so TUI sessions reappear after a
|
|
@@ -317,6 +319,7 @@ function createSessionManager(opts) {
|
|
|
317
319
|
};
|
|
318
320
|
if (m.vendor) session.vendor = m.vendor;
|
|
319
321
|
session.codexDynamicToolCatalogVersion = Number(m.codexDynamicToolCatalogVersion || 0);
|
|
322
|
+
session.codexIssuesToolCatalogVersion = Number(m.codexIssuesToolCatalogVersion || 0);
|
|
320
323
|
session.model = m.model || deriveSessionModel(loaded[i].history);
|
|
321
324
|
session.effort = m.effort || null;
|
|
322
325
|
if (m.loop) session.loop = m.loop;
|
|
@@ -797,6 +800,7 @@ function createSessionManager(opts) {
|
|
|
797
800
|
function switchSession(localId, targetWs, transform) {
|
|
798
801
|
var session = sessions.get(localId);
|
|
799
802
|
if (!session) return;
|
|
803
|
+
if (typeof onSessionHydrate === "function") onSessionHydrate(session);
|
|
800
804
|
|
|
801
805
|
// Pane sockets (split-view iframes) pin their session via switch_session
|
|
802
806
|
// but must not move the project-global active pointer: reloads and
|
|
@@ -824,7 +828,9 @@ function createSessionManager(opts) {
|
|
|
824
828
|
var _vendorCaps = _capsByVendor[_sessionVendor] || {};
|
|
825
829
|
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null, vendor: session.vendor || null, model: session.model || null, effort: session.effort || null, hasHistory: (session.history && session.history.length > 0), capabilities: _vendorCaps, isProcessing: !!session.isProcessing, mode: session.mode || "gui", terminalId: typeof session.terminalId === "number" ? session.terminalId : null, runtimeMode: session.runtimeMode || null, runtimeTerminalId: typeof session.runtimeTerminalId === "number" ? session.runtimeTerminalId : null, tuiSuspended: !!session.tuiSuspended, dangerouslySkipPermissions: !!session.dangerouslySkipPermissions, permissionMode: session.permissionMode || null, autonomousRun: projectAutonomousRun(session.autonomousRun), loopInterviewBrief: session.loopInterviewBrief || null, loopInterviewHandoff: session.loopInterviewHandoff || null, scheduledTaskDraft: session.scheduledTaskDraft || null });
|
|
826
830
|
// Send vendor-specific slash commands
|
|
827
|
-
var _vendorCmds = slashCommandsByVendor
|
|
831
|
+
var _vendorCmds = (session.slashCommandsByVendor && session.slashCommandsByVendor[_sessionVendor])
|
|
832
|
+
|| session.slashCommands
|
|
833
|
+
|| [];
|
|
828
834
|
_send({ type: "slash_commands", commands: _vendorCmds, vendor: _sessionVendor });
|
|
829
835
|
broadcastSessionList();
|
|
830
836
|
replayHistory(session, undefined, targetWs, transform);
|
|
@@ -1288,6 +1294,7 @@ function createSessionManager(opts) {
|
|
|
1288
1294
|
}
|
|
1289
1295
|
|
|
1290
1296
|
return {
|
|
1297
|
+
setSessionHydrator: function (fn) { onSessionHydrate = typeof fn === "function" ? fn : null; },
|
|
1291
1298
|
get activeSessionId() { return activeSessionId; },
|
|
1292
1299
|
get nextLocalId() { return nextLocalId; },
|
|
1293
1300
|
get slashCommands() { return slashCommands; },
|
package/lib/yoke/adapters/acp.js
CHANGED
|
@@ -44,6 +44,7 @@ function createAcpAdapter(vendor, opts) {
|
|
|
44
44
|
var defaultModel = driver.defaultModel || cachedModels[0] || "auto";
|
|
45
45
|
var activeHandles = [];
|
|
46
46
|
var lastActiveAt = Date.now();
|
|
47
|
+
var skillEnv = opts.env || process.env;
|
|
47
48
|
|
|
48
49
|
function context(extra) {
|
|
49
50
|
return Object.assign({
|
|
@@ -96,7 +97,7 @@ function createAcpAdapter(vendor, opts) {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function readyResult() {
|
|
99
|
-
var skills = skillDiscovery.discoverSkills(cwd).map(function(skill) { return skill.name; });
|
|
100
|
+
var skills = skillDiscovery.discoverSkills(cwd, { env: skillEnv }).map(function(skill) { return skill.name; });
|
|
100
101
|
var result = {
|
|
101
102
|
models: cachedModels.slice(),
|
|
102
103
|
defaultModel: defaultModel,
|
|
@@ -220,7 +221,7 @@ function createAcpAdapter(vendor, opts) {
|
|
|
220
221
|
if (!initialized || !acp || !acp.started) await adapter.init(queryOpts);
|
|
221
222
|
if (shuttingDown) throw new Error(driver.displayName + " adapter is shutting down");
|
|
222
223
|
var controller = queryOpts.abortController || new AbortController();
|
|
223
|
-
var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || cwd);
|
|
224
|
+
var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || cwd, queryOpts.skillOptions || { env: queryOpts.env || skillEnv });
|
|
224
225
|
var skillIndex = skillDiscovery.buildSkillIndex(sharedSkills);
|
|
225
226
|
var acpOptions = (queryOpts.adapterOptions && queryOpts.adapterOptions.ACP) || {};
|
|
226
227
|
var handle = null;
|
|
@@ -353,12 +353,12 @@ function createAntigravityAdapter(opts) {
|
|
|
353
353
|
|
|
354
354
|
var adapter = {
|
|
355
355
|
vendor: "antigravity",
|
|
356
|
-
init: async function() {
|
|
356
|
+
init: async function(initOpts) {
|
|
357
357
|
if (!binaryPath) binaryPath = findBinary();
|
|
358
358
|
if (!binaryPath) throw new Error("Antigravity CLI binary not found: agy");
|
|
359
359
|
var fetched = opts._fetchModels ? await opts._fetchModels(binaryPath, cwd) : await fetchModels(binaryPath, cwd);
|
|
360
360
|
if (fetched && fetched.length) cachedModels = fetched;
|
|
361
|
-
var skills = skillDiscovery.discoverSkills(cwd).map(function(skill) { return skill.name; });
|
|
361
|
+
var skills = skillDiscovery.discoverSkills(cwd, { env: (initOpts && initOpts.env) || null }).map(function(skill) { return skill.name; });
|
|
362
362
|
return {
|
|
363
363
|
models: cachedModels.slice(),
|
|
364
364
|
defaultModel: cachedModels[0] || "auto",
|