clay-server 2.47.0-beta.1 → 2.47.0-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/codex-defaults.js +12 -2
- package/lib/daemon.js +38 -8
- package/lib/kiro-defaults.js +22 -0
- package/lib/project-connection.js +3 -1
- package/lib/project-notifications.js +3 -1
- package/lib/project-sessions.js +28 -9
- package/lib/project.js +42 -10
- package/lib/public/app.js +8 -0
- package/lib/public/css/mobile-nav.css +53 -9
- package/lib/public/css/sidebar.css +24 -1
- package/lib/public/css/tui-attention.css +4 -63
- package/lib/public/index.html +4 -0
- package/lib/public/kiro-avatar.svg +14 -0
- package/lib/public/modules/app-messages.js +14 -12
- package/lib/public/modules/app-notifications.js +12 -4
- package/lib/public/modules/app-panels.js +10 -4
- package/lib/public/modules/app-rate-limit.js +41 -13
- package/lib/public/modules/app-rendering.js +14 -0
- package/lib/public/modules/input.js +4 -3
- package/lib/public/modules/mate-sidebar.js +3 -3
- package/lib/public/modules/session-tui-view.js +4 -40
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +56 -23
- package/lib/public/modules/sidebar-sessions.js +126 -63
- package/lib/public/modules/tools.js +1 -1
- package/lib/public/modules/tui-grab.js +12 -3
- package/lib/sdk-bridge.js +90 -21
- package/lib/sdk-message-processor.js +4 -4
- package/lib/server.js +4 -0
- package/lib/users-preferences.js +3 -1
- package/lib/yoke/adapters/claude.js +25 -0
- package/lib/yoke/adapters/codex.js +34 -21
- package/lib/yoke/adapters/kiro.js +1170 -0
- package/lib/yoke/index.js +70 -6
- package/lib/yoke/kiro-acp-server.js +328 -0
- package/lib/yoke/vendor-registry.js +55 -0
- package/package.json +4 -3
|
@@ -12,6 +12,7 @@ import { dismissOverlayPanels, closeSidebar, updatePageTitle, spawnDustParticles
|
|
|
12
12
|
import { showConfirm } from './app-misc.js';
|
|
13
13
|
import { getUpcomingSchedules } from './scheduler.js';
|
|
14
14
|
import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
15
|
+
import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES } from './app-rendering.js';
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
// --- Session state ---
|
|
@@ -292,85 +293,140 @@ function appendSessionCloseButton(el, session) {
|
|
|
292
293
|
el.appendChild(closeBtn);
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
// Resolve the vendor the "New session" button should launch: the project's
|
|
297
|
+
// last-used vendor when its CLI is still installed, else the first installed
|
|
298
|
+
// vendor, else Claude (so the button always has a sane label to render).
|
|
299
|
+
export function resolveDefaultVendor() {
|
|
300
|
+
var installed = store.get('installedVendors') || [];
|
|
301
|
+
var last = store.get('lastVendor') || "";
|
|
302
|
+
if (last && installed.indexOf(last) !== -1) return last;
|
|
303
|
+
for (var i = 0; i < VENDOR_ORDER.length; i++) {
|
|
304
|
+
if (installed.indexOf(VENDOR_ORDER[i]) !== -1) return VENDOR_ORDER[i];
|
|
305
|
+
}
|
|
306
|
+
return "claude";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Single entry point for session creation from the sidebar. Optimistically
|
|
310
|
+
// bind the composer to the requested vendor before the server round-trip.
|
|
311
|
+
// Otherwise vendorSelectionLocked from the previous session can reject the
|
|
312
|
+
// new config_state and leave a Kiro/Codex session rendered as Claude.
|
|
313
|
+
export function startNewSession(vendor, extra) {
|
|
314
|
+
if (!getWs() || !store.get('connected')) return;
|
|
315
|
+
var payload = { type: "new_session", vendor: vendor };
|
|
316
|
+
if (extra) {
|
|
317
|
+
var keys = Object.keys(extra);
|
|
318
|
+
for (var i = 0; i < keys.length; i++) payload[keys[i]] = extra[keys[i]];
|
|
319
|
+
}
|
|
320
|
+
getWs().send(JSON.stringify(payload));
|
|
321
|
+
store.set({
|
|
322
|
+
lastVendor: vendor,
|
|
323
|
+
currentVendor: vendor,
|
|
324
|
+
currentModel: "",
|
|
325
|
+
currentModels: [],
|
|
326
|
+
vendorSelectionLocked: true,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
295
330
|
function renderSessionTopActions() {
|
|
296
331
|
var wrap = document.createElement("div");
|
|
297
332
|
wrap.className = "session-top-actions";
|
|
298
333
|
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
334
|
+
// Single split button. The main half creates a session with the project's
|
|
335
|
+
// last-used vendor (for Claude the server applies the claudeOpenMode pref);
|
|
336
|
+
// the chevron opens the vendor picker plus alternate launch modes.
|
|
337
|
+
var defaultVendor = resolveDefaultVendor();
|
|
338
|
+
|
|
339
|
+
var cell = document.createElement("div");
|
|
340
|
+
cell.className = "session-top-action-split";
|
|
341
|
+
|
|
342
|
+
var mainBtn = document.createElement("button");
|
|
343
|
+
mainBtn.className = "session-top-action split-main";
|
|
344
|
+
mainBtn.type = "button";
|
|
345
|
+
mainBtn.title = "New " + (VENDOR_NAMES[defaultVendor] || defaultVendor) + " session";
|
|
346
|
+
mainBtn.innerHTML = '<img src="' + (VENDOR_AVATARS[defaultVendor] || VENDOR_AVATARS.claude) +
|
|
347
|
+
'" class="session-top-action-icon" alt=""><span>New session</span>';
|
|
348
|
+
mainBtn.addEventListener("click", function () {
|
|
349
|
+
startNewSession(defaultVendor);
|
|
314
350
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
var
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
351
|
+
cell.appendChild(mainBtn);
|
|
352
|
+
|
|
353
|
+
var chevron = document.createElement("button");
|
|
354
|
+
chevron.className = "session-top-action split-chevron";
|
|
355
|
+
chevron.type = "button";
|
|
356
|
+
chevron.title = "Choose a vendor or launch mode";
|
|
357
|
+
chevron.setAttribute("aria-label", "Choose a vendor or launch mode");
|
|
358
|
+
chevron.innerHTML = iconHtml("chevron-down");
|
|
359
|
+
chevron.addEventListener("click", function (e) {
|
|
324
360
|
e.preventDefault();
|
|
325
361
|
e.stopPropagation();
|
|
326
362
|
if (sessionCtxMenu) { closeSessionCtxMenu(); return; }
|
|
327
|
-
|
|
363
|
+
showNewSessionMenu(chevron, defaultVendor);
|
|
328
364
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
wrap.appendChild(claudeCell);
|
|
332
|
-
|
|
333
|
-
// Codex: always GUI (no TUI adapter for Codex).
|
|
334
|
-
var codexBtn = document.createElement("button");
|
|
335
|
-
codexBtn.className = "session-top-action";
|
|
336
|
-
codexBtn.type = "button";
|
|
337
|
-
codexBtn.title = "New Codex session";
|
|
338
|
-
codexBtn.innerHTML = '<img src="/codex-avatar.png" class="session-top-action-icon" alt=""><span>Codex</span>';
|
|
339
|
-
codexBtn.addEventListener("click", function () {
|
|
340
|
-
if (getWs() && store.get('connected')) {
|
|
341
|
-
getWs().send(JSON.stringify({ type: "new_session", vendor: "codex" }));
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
wrap.appendChild(codexBtn);
|
|
365
|
+
cell.appendChild(chevron);
|
|
345
366
|
|
|
367
|
+
wrap.appendChild(cell);
|
|
346
368
|
return wrap;
|
|
347
369
|
}
|
|
348
370
|
|
|
349
|
-
// Dropdown anchored to the
|
|
350
|
-
//
|
|
351
|
-
function
|
|
371
|
+
// Dropdown anchored to the split-button chevron. Reuses the session-ctx-menu
|
|
372
|
+
// element/var so the global document click handler closes it.
|
|
373
|
+
function showNewSessionMenu(anchorBtn, defaultVendor) {
|
|
352
374
|
closeSessionCtxMenu();
|
|
353
375
|
|
|
354
376
|
var menu = document.createElement("div");
|
|
355
|
-
menu.className = "session-ctx-menu";
|
|
377
|
+
menu.className = "session-ctx-menu session-new-menu";
|
|
378
|
+
var installed = store.get('installedVendors') || [];
|
|
379
|
+
|
|
380
|
+
// Every known vendor is listed. Uninstalled ones stay visible but disabled,
|
|
381
|
+
// and clicking one opens the vendor's homepage so the user can install it.
|
|
382
|
+
for (var i = 0; i < VENDOR_ORDER.length; i++) {
|
|
383
|
+
(function (vendor) {
|
|
384
|
+
var isInstalled = installed.indexOf(vendor) !== -1;
|
|
385
|
+
var name = VENDOR_NAMES[vendor] || vendor;
|
|
386
|
+
var item = document.createElement("button");
|
|
387
|
+
item.className = "session-ctx-item session-new-vendor";
|
|
388
|
+
if (!isInstalled) item.classList.add("disabled");
|
|
389
|
+
if (vendor === defaultVendor) item.classList.add("active");
|
|
390
|
+
item.type = "button";
|
|
391
|
+
item.innerHTML = '<img src="' + VENDOR_AVATARS[vendor] + '" class="session-top-action-icon" alt="">' +
|
|
392
|
+
'<span class="session-new-vendor-name">' + name + '</span>' +
|
|
393
|
+
(isInstalled ? "" : '<span class="session-new-vendor-note">Not installed</span>' + iconHtml("external-link"));
|
|
394
|
+
item.title = isInstalled
|
|
395
|
+
? "New " + name + " session"
|
|
396
|
+
: name + " is not installed - open " + VENDOR_HOMEPAGES[vendor];
|
|
397
|
+
item.addEventListener("click", function (e) {
|
|
398
|
+
e.stopPropagation();
|
|
399
|
+
closeSessionCtxMenu();
|
|
400
|
+
if (!isInstalled) {
|
|
401
|
+
window.open(VENDOR_HOMEPAGES[vendor], "_blank", "noopener");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
startNewSession(vendor);
|
|
405
|
+
});
|
|
406
|
+
menu.appendChild(item);
|
|
407
|
+
})(VENDOR_ORDER[i]);
|
|
408
|
+
}
|
|
356
409
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
410
|
+
// Claude-only launch mode: a bare TUI shell with permission prompts off.
|
|
411
|
+
// Hidden in GUI mode (the default) because the item only ever produces a
|
|
412
|
+
// terminal session, which contradicts what the rest of the menu creates.
|
|
413
|
+
if (installed.indexOf("claude") !== -1 && store.get('claudeOpenMode') === "tui") {
|
|
414
|
+
var sep = document.createElement("div");
|
|
415
|
+
sep.className = "session-ctx-sep";
|
|
416
|
+
menu.appendChild(sep);
|
|
417
|
+
|
|
418
|
+
var skipItem = document.createElement("button");
|
|
419
|
+
skipItem.className = "session-ctx-item";
|
|
420
|
+
skipItem.type = "button";
|
|
421
|
+
skipItem.innerHTML = iconHtml("shield-off") + " <span>Skip permissions (TUI)</span>";
|
|
422
|
+
skipItem.title = "Start a terminal session with --dangerously-skip-permissions";
|
|
423
|
+
skipItem.addEventListener("click", function (e) {
|
|
424
|
+
e.stopPropagation();
|
|
425
|
+
closeSessionCtxMenu();
|
|
426
|
+
startNewSession("claude", { mode: "tui", dangerouslySkipPermissions: true });
|
|
427
|
+
});
|
|
428
|
+
menu.appendChild(skipItem);
|
|
429
|
+
}
|
|
374
430
|
|
|
375
431
|
document.body.appendChild(menu);
|
|
376
432
|
sessionCtxMenu = menu;
|
|
@@ -528,6 +584,14 @@ export function initSidebarSessions() {
|
|
|
528
584
|
syncHeaderSearchUi();
|
|
529
585
|
}
|
|
530
586
|
|
|
587
|
+
// The "New session" split button renders from lastVendor + installedVendors,
|
|
588
|
+
// so a late-arriving vendor detection or a vendor switch has to repaint it.
|
|
589
|
+
store.subscribe(function (state, prev) {
|
|
590
|
+
if (state.lastVendor !== prev.lastVendor || state.installedVendors !== prev.installedVendors) {
|
|
591
|
+
renderSessionList(null);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
531
595
|
// --- Resume session picker ---
|
|
532
596
|
// --- Schedule countdown timer ---
|
|
533
597
|
startCountdownTimer();
|
|
@@ -1411,4 +1475,3 @@ function relativeTime(isoString) {
|
|
|
1411
1475
|
if (days < 30) return days + "d ago";
|
|
1412
1476
|
return new Date(isoString).toLocaleDateString();
|
|
1413
1477
|
}
|
|
1414
|
-
|
|
@@ -814,7 +814,7 @@ function resolvePermissionIdentity(mateId, vendor) {
|
|
|
814
814
|
}
|
|
815
815
|
}
|
|
816
816
|
// Project chat: use vendor name and avatar
|
|
817
|
-
var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png" };
|
|
817
|
+
var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png", kiro: "/kiro-avatar.svg" };
|
|
818
818
|
var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
|
|
819
819
|
return {
|
|
820
820
|
name: vendorName,
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
// Codex sessions never receive a transcript_state (no JSONL), so the
|
|
25
25
|
// overlay stays disabled — selection copy still works normally.
|
|
26
26
|
|
|
27
|
+
import { store } from './store.js';
|
|
28
|
+
|
|
27
29
|
import { copyToClipboard, showToast } from './utils.js';
|
|
28
30
|
import { getWs } from './ws-ref.js';
|
|
29
31
|
|
|
@@ -629,9 +631,16 @@ function requestTranscript(localId) {
|
|
|
629
631
|
export function attachTuiGrab(xterm, containerEl, localId, opts) {
|
|
630
632
|
if (!xterm || !containerEl || !localId) return;
|
|
631
633
|
clearActive();
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
634
|
+
// Vendors without TUI sessions have no transcript to match against.
|
|
635
|
+
if (opts && opts.vendor) {
|
|
636
|
+
var vendors = store.get('vendorInfo') || {};
|
|
637
|
+
var info = vendors[opts.vendor];
|
|
638
|
+
var legacyTuiVendors = ["claude"];
|
|
639
|
+
var supportsTui = info
|
|
640
|
+
? Array.isArray(info.sessionModes) && info.sessionModes.indexOf("tui") !== -1
|
|
641
|
+
: legacyTuiVendors.indexOf(opts.vendor) !== -1;
|
|
642
|
+
if (!supportsTui) return;
|
|
643
|
+
}
|
|
635
644
|
|
|
636
645
|
var overlayEl = buildOverlay(containerEl);
|
|
637
646
|
active = {
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -5,10 +5,12 @@ var execSync = require("child_process").execSync;
|
|
|
5
5
|
var execFileSync = require("child_process").execFileSync;
|
|
6
6
|
var usersModule = require("./users");
|
|
7
7
|
var { getCodexConfig } = require("./codex-defaults");
|
|
8
|
+
var { getKiroConfig } = require("./kiro-defaults");
|
|
8
9
|
var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
|
|
9
10
|
var { isSafeBashSegment } = require("./safe-bash-commands");
|
|
10
11
|
var { createMessageQueue } = require("./sdk-message-queue");
|
|
11
12
|
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
13
|
+
var yoke = require("./yoke");
|
|
12
14
|
|
|
13
15
|
// Extract serializable tool descriptors from MCP server instances.
|
|
14
16
|
// Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
|
|
@@ -126,7 +128,6 @@ function createSDKBridge(opts) {
|
|
|
126
128
|
var _cachedFreshAuthAt = 0;
|
|
127
129
|
|
|
128
130
|
function getFreshAuthState(force) {
|
|
129
|
-
var yoke = require("./yoke");
|
|
130
131
|
var now = Date.now();
|
|
131
132
|
if (!force && _cachedFreshAuthState && now - _cachedFreshAuthAt < 15000) {
|
|
132
133
|
return _cachedFreshAuthState;
|
|
@@ -149,9 +150,13 @@ function createSDKBridge(opts) {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
function getLoginCommand(vendor) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
var info = yoke.getVendorInfo(vendor);
|
|
154
|
+
return (info && info.loginCommand) || "claude login";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function supportsOsUserIsolation(vendor) {
|
|
158
|
+
var info = yoke.getVendorInfo(vendor);
|
|
159
|
+
return !info || info.osUserIsolation !== false;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
function notifyAuthRequired(session, title, body, authLinuxUser, canAutoLogin, loginCommand) {
|
|
@@ -218,15 +223,25 @@ function createSDKBridge(opts) {
|
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
function sendModelInfoForVendor(vendor, model) {
|
|
226
|
+
var resolvedVendor = vendor || (adapter && adapter.vendor) || "claude";
|
|
221
227
|
send({
|
|
222
228
|
type: "model_info",
|
|
223
229
|
model: model || "",
|
|
224
|
-
models: getModelsForVendor(
|
|
225
|
-
vendor:
|
|
230
|
+
models: getModelsForVendor(resolvedVendor),
|
|
231
|
+
vendor: resolvedVendor,
|
|
232
|
+
capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[resolvedVendor]) || {},
|
|
226
233
|
availableVendors: sm.availableVendors || [],
|
|
227
234
|
installedVendors: sm.installedVendors || [],
|
|
228
235
|
});
|
|
229
236
|
}
|
|
237
|
+
|
|
238
|
+
function rememberAdapterReady(vendor, result) {
|
|
239
|
+
if (!vendor || !result) return;
|
|
240
|
+
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
241
|
+
sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
|
|
242
|
+
if (Array.isArray(result.models)) sm.modelsByVendor[vendor] = result.models;
|
|
243
|
+
sm.capabilitiesByVendor[vendor] = result.capabilities || {};
|
|
244
|
+
}
|
|
230
245
|
var onTurnDone = opts.onTurnDone || null;
|
|
231
246
|
|
|
232
247
|
// --- Idle session reaper ---
|
|
@@ -799,7 +814,7 @@ function createSDKBridge(opts) {
|
|
|
799
814
|
onProcessingChanged();
|
|
800
815
|
send({ type: "status", processing: false });
|
|
801
816
|
sendAndRecord(session, { type: "thinking_stop" });
|
|
802
|
-
var interruptMsg = (session.vendor
|
|
817
|
+
var interruptMsg = (session.vendor && session.vendor !== "claude")
|
|
803
818
|
? "\u25a0 Conversation interrupted - tell the model what to do differently."
|
|
804
819
|
: "Interrupted \u00b7 What should Claude do instead?";
|
|
805
820
|
sendAndRecord(session, { type: "info", text: interruptMsg });
|
|
@@ -813,7 +828,7 @@ function createSDKBridge(opts) {
|
|
|
813
828
|
if (err.name === "AbortError" || (myAbortController && myAbortController.signal.aborted) || session.taskStopRequested) {
|
|
814
829
|
if (!session.destroying) {
|
|
815
830
|
sendAndRecord(session, { type: "thinking_stop" });
|
|
816
|
-
var interruptMsg2 = (session.vendor
|
|
831
|
+
var interruptMsg2 = (session.vendor && session.vendor !== "claude")
|
|
817
832
|
? "\u25a0 Conversation interrupted - tell the model what to do differently."
|
|
818
833
|
: "Interrupted \u00b7 What should Claude do instead?";
|
|
819
834
|
sendAndRecord(session, { type: "info", text: interruptMsg2 });
|
|
@@ -867,7 +882,9 @@ function createSDKBridge(opts) {
|
|
|
867
882
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
868
883
|
|| !!authLinuxUser
|
|
869
884
|
|| (authUser && authUser.role === "admin");
|
|
870
|
-
var
|
|
885
|
+
var authInfo = yoke.getVendorInfo(session.vendor);
|
|
886
|
+
var authVendorLabel = (authInfo && authInfo.displayName) || "Claude Code";
|
|
887
|
+
var authTitle = authVendorLabel + " is not logged in.";
|
|
871
888
|
var authMsg = {
|
|
872
889
|
type: "auth_required",
|
|
873
890
|
text: authTitle,
|
|
@@ -1067,9 +1084,9 @@ function createSDKBridge(opts) {
|
|
|
1067
1084
|
async function startQuery(session, text, images, linuxUser) {
|
|
1068
1085
|
async function ensureVendorReady(vendor) {
|
|
1069
1086
|
if (!vendor) return null;
|
|
1087
|
+
if (linuxUser && !supportsOsUserIsolation(vendor)) return null;
|
|
1070
1088
|
var vendorAdapter = adapters[vendor] || null;
|
|
1071
1089
|
if (!vendorAdapter) {
|
|
1072
|
-
var yoke = require("./yoke");
|
|
1073
1090
|
vendorAdapter = await yoke.lazyCreateAdapter(adapters, vendor, {
|
|
1074
1091
|
cwd: cwd,
|
|
1075
1092
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
@@ -1079,8 +1096,11 @@ function createSDKBridge(opts) {
|
|
|
1079
1096
|
clayAuthToken: clayAuthToken,
|
|
1080
1097
|
slug: slug,
|
|
1081
1098
|
});
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1099
|
+
}
|
|
1100
|
+
var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[vendor]
|
|
1101
|
+
|| !sm.modelsByVendor || !sm.modelsByVendor[vendor];
|
|
1102
|
+
if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
|
|
1103
|
+
var readyResult = await vendorAdapter.init({
|
|
1084
1104
|
cwd: cwd,
|
|
1085
1105
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1086
1106
|
linuxUser: linuxUser || undefined,
|
|
@@ -1089,17 +1109,39 @@ function createSDKBridge(opts) {
|
|
|
1089
1109
|
clayAuthToken: clayAuthToken,
|
|
1090
1110
|
slug: slug,
|
|
1091
1111
|
});
|
|
1112
|
+
rememberAdapterReady(vendor, readyResult);
|
|
1092
1113
|
}
|
|
1093
1114
|
if (vendorAdapter) {
|
|
1094
|
-
sm.availableVendors =
|
|
1115
|
+
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
1095
1116
|
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1096
1117
|
if (!sm.modelsByVendor[vendor] && typeof vendorAdapter.supportedModels === "function") {
|
|
1097
1118
|
sm.modelsByVendor[vendor] = await vendorAdapter.supportedModels();
|
|
1098
1119
|
}
|
|
1120
|
+
var vendorModels = getModelsForVendor(vendor);
|
|
1121
|
+
var modelForVendor = resolveModelInList(vendorModels, sm.currentModel)
|
|
1122
|
+
|| modelEntryValue(vendorModels[0])
|
|
1123
|
+
|| "";
|
|
1124
|
+
sendModelInfoForVendor(vendor, modelForVendor);
|
|
1099
1125
|
}
|
|
1100
1126
|
return vendorAdapter;
|
|
1101
1127
|
}
|
|
1102
1128
|
|
|
1129
|
+
// Kiro ACP currently spawns as the daemon user. Running it for an
|
|
1130
|
+
// OS-isolated session would expose the daemon user's HOME and Kiro
|
|
1131
|
+
// credentials to another user, so refuse until the adapter has a
|
|
1132
|
+
// per-user worker path.
|
|
1133
|
+
if (linuxUser && !supportsOsUserIsolation(session.vendor)) {
|
|
1134
|
+
var isolatedVendorInfo = yoke.getVendorInfo(session.vendor);
|
|
1135
|
+
var isolatedVendorName = (isolatedVendorInfo && isolatedVendorInfo.displayName) || session.vendor || "This vendor";
|
|
1136
|
+
console.error("[sdk-bridge] Refusing " + isolatedVendorName + " for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
|
|
1137
|
+
sendAndRecord(session, {
|
|
1138
|
+
type: "error",
|
|
1139
|
+
text: isolatedVendorName + " is not available for OS-isolated users yet.",
|
|
1140
|
+
});
|
|
1141
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1103
1145
|
// If vendor is set but adapter not ready, try lazy creation (user may have logged in)
|
|
1104
1146
|
if (session.vendor && !adapters[session.vendor]) {
|
|
1105
1147
|
var lazyAdapter = await ensureVendorReady(session.vendor);
|
|
@@ -1121,7 +1163,9 @@ function createSDKBridge(opts) {
|
|
|
1121
1163
|
}
|
|
1122
1164
|
// If still not available after lazy check, send auth_required
|
|
1123
1165
|
if (session.vendor && !adapters[session.vendor]) {
|
|
1124
|
-
var
|
|
1166
|
+
var missingVendorInfo = yoke.getVendorInfo(session.vendor);
|
|
1167
|
+
var vendorName = (missingVendorInfo && missingVendorInfo.displayName)
|
|
1168
|
+
|| session.vendor.charAt(0).toUpperCase() + session.vendor.slice(1);
|
|
1125
1169
|
var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
|
|
1126
1170
|
var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
|
|
1127
1171
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
@@ -1292,6 +1336,7 @@ function createSDKBridge(opts) {
|
|
|
1292
1336
|
}
|
|
1293
1337
|
|
|
1294
1338
|
var codexConfig = getCodexConfig(sm);
|
|
1339
|
+
var kiroConfig = getKiroConfig(sm);
|
|
1295
1340
|
var mergedMcpServers = mergeMcpServers(getMcpServers(), getRemoteMcpServers) || undefined;
|
|
1296
1341
|
|
|
1297
1342
|
// Derive an explicit session title for fresh queries so the SDK records
|
|
@@ -1346,6 +1391,14 @@ function createSDKBridge(opts) {
|
|
|
1346
1391
|
sandboxMode: codexConfig.sandbox,
|
|
1347
1392
|
webSearchMode: codexConfig.webSearch,
|
|
1348
1393
|
},
|
|
1394
|
+
KIRO: {
|
|
1395
|
+
engine: kiroConfig.engine,
|
|
1396
|
+
mode: kiroConfig.mode,
|
|
1397
|
+
// Kiro reads its native ~/.kiro/settings/mcp.json. Clay-managed MCP
|
|
1398
|
+
// servers are intentionally not forwarded until the ACP entry shape
|
|
1399
|
+
// and duplicate-registration behavior are verified against CLI 3.x.
|
|
1400
|
+
mcpServers: [],
|
|
1401
|
+
},
|
|
1349
1402
|
},
|
|
1350
1403
|
};
|
|
1351
1404
|
|
|
@@ -1493,18 +1546,37 @@ function createSDKBridge(opts) {
|
|
|
1493
1546
|
}
|
|
1494
1547
|
|
|
1495
1548
|
// Claude: check if binary is in PATH
|
|
1496
|
-
if (tryLookup("claude")) result.push("claude");
|
|
1549
|
+
if (tryLookup(yoke.getVendorInfo("claude").binaryName)) result.push("claude");
|
|
1497
1550
|
|
|
1498
1551
|
// Codex: check bundled binary or PATH
|
|
1499
1552
|
var codexBin = null;
|
|
1500
1553
|
try {
|
|
1501
1554
|
codexBin = require("./yoke/codex-app-server").findCodexPath();
|
|
1502
1555
|
} catch (e) {}
|
|
1503
|
-
if ((codexBin && fs.existsSync(codexBin)) || tryLookup("codex")) result.push("codex");
|
|
1556
|
+
if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
|
|
1557
|
+
|
|
1558
|
+
// Kiro has no per-user ACP spawn path yet. Do not advertise the daemon's
|
|
1559
|
+
// binary to an OS-isolated user, even if that user also has Kiro installed.
|
|
1560
|
+
var kiroInfo = yoke.getVendorInfo("kiro");
|
|
1561
|
+
if (!linuxUser || kiroInfo.osUserIsolation) {
|
|
1562
|
+
var kiroBin = null;
|
|
1563
|
+
try {
|
|
1564
|
+
kiroBin = require("./yoke/kiro-acp-server").findKiroPath();
|
|
1565
|
+
} catch (e) {}
|
|
1566
|
+
if ((kiroBin && fs.existsSync(kiroBin)) || tryLookup(kiroInfo.binaryName)) result.push("kiro");
|
|
1567
|
+
} else {
|
|
1568
|
+
console.log("[sdk-bridge] " + kiroInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
|
|
1569
|
+
}
|
|
1504
1570
|
|
|
1505
1571
|
return result;
|
|
1506
1572
|
}
|
|
1507
1573
|
|
|
1574
|
+
function getAvailableVendors(linuxUser) {
|
|
1575
|
+
return Object.keys(adapters).filter(function(vendor) {
|
|
1576
|
+
return !(linuxUser && !supportsOsUserIsolation(vendor));
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1508
1580
|
// SDK warmup: initialize all available adapters and collect models.
|
|
1509
1581
|
// The default adapter is initialized first for slash_commands and skills.
|
|
1510
1582
|
// Passes linuxUser to adapter for worker-based warmup when OS isolation is needed.
|
|
@@ -1546,10 +1618,7 @@ function createSDKBridge(opts) {
|
|
|
1546
1618
|
}
|
|
1547
1619
|
sm.availableModels = result.models || [];
|
|
1548
1620
|
// Store per-vendor models and capabilities
|
|
1549
|
-
|
|
1550
|
-
sm.modelsByVendor[defaultVendor] = result.models || [];
|
|
1551
|
-
sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
|
|
1552
|
-
sm.capabilitiesByVendor[defaultVendor] = result.capabilities || {};
|
|
1621
|
+
rememberAdapterReady(defaultVendor, result);
|
|
1553
1622
|
} catch (e) {
|
|
1554
1623
|
if (e && e.name !== "AbortError" && !(e.message && e.message.indexOf("aborted") !== -1)) {
|
|
1555
1624
|
send({ type: "error", text: "Failed to load " + defaultVendor + " SDK: " + (e.message || e) });
|
|
@@ -1568,7 +1637,7 @@ function createSDKBridge(opts) {
|
|
|
1568
1637
|
|
|
1569
1638
|
// Detect installed vendors per-user (binary existence check)
|
|
1570
1639
|
sm.installedVendors = detectInstalledVendors(linuxUser);
|
|
1571
|
-
sm.availableVendors =
|
|
1640
|
+
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
1572
1641
|
|
|
1573
1642
|
// Send initial state to client
|
|
1574
1643
|
send({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var usersModule = require("./users");
|
|
2
|
+
var yoke = require("./yoke");
|
|
2
3
|
|
|
3
4
|
function attachMessageProcessor(ctx) {
|
|
4
5
|
var sm = ctx.sm;
|
|
@@ -50,10 +51,9 @@ function attachMessageProcessor(ctx) {
|
|
|
50
51
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
51
52
|
|| !!authLinuxUser
|
|
52
53
|
|| (authUser && authUser.role === "admin");
|
|
53
|
-
var
|
|
54
|
-
var
|
|
55
|
-
|
|
56
|
-
: "claude login";
|
|
54
|
+
var vendorInfo = yoke.getVendorInfo(session.vendor);
|
|
55
|
+
var authTitle = ((vendorInfo && vendorInfo.displayName) || "Claude Code") + " is not logged in.";
|
|
56
|
+
var loginCommand = (vendorInfo && vendorInfo.loginCommand) || "claude login";
|
|
57
57
|
var _nmLogin = getNotificationsModule();
|
|
58
58
|
sendAndRecord(session, {
|
|
59
59
|
type: "auth_required",
|
package/lib/server.js
CHANGED
|
@@ -148,6 +148,8 @@ function createServer(opts) {
|
|
|
148
148
|
var onSetServerDefaultMode = opts.onSetServerDefaultMode || null;
|
|
149
149
|
var onGetProjectDefaultMode = opts.onGetProjectDefaultMode || null;
|
|
150
150
|
var onSetProjectDefaultMode = opts.onSetProjectDefaultMode || null;
|
|
151
|
+
var onGetProjectLastVendor = opts.onGetProjectLastVendor || null;
|
|
152
|
+
var onSetProjectLastVendor = opts.onSetProjectLastVendor || null;
|
|
151
153
|
var onGetProjectMcpServers = opts.onGetProjectMcpServers || null;
|
|
152
154
|
var onSetProjectMcpServers = opts.onSetProjectMcpServers || null;
|
|
153
155
|
var onGetDaemonConfig = opts.onGetDaemonConfig || null;
|
|
@@ -1253,6 +1255,8 @@ function createServer(opts) {
|
|
|
1253
1255
|
onSetServerDefaultMode: onSetServerDefaultMode,
|
|
1254
1256
|
onGetProjectDefaultMode: onGetProjectDefaultMode,
|
|
1255
1257
|
onSetProjectDefaultMode: onSetProjectDefaultMode,
|
|
1258
|
+
onGetProjectLastVendor: onGetProjectLastVendor,
|
|
1259
|
+
onSetProjectLastVendor: onSetProjectLastVendor,
|
|
1256
1260
|
onGetProjectMcpServers: onGetProjectMcpServers,
|
|
1257
1261
|
onSetProjectMcpServers: onSetProjectMcpServers,
|
|
1258
1262
|
onGetDaemonConfig: onGetDaemonConfig,
|
package/lib/users-preferences.js
CHANGED
|
@@ -274,7 +274,9 @@ function attachPreferences(deps) {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
function setClaudeOpenMode(userId, mode) {
|
|
277
|
-
|
|
277
|
+
// GUI is the default in every other resolution path, so an unrecognized
|
|
278
|
+
// value must land on GUI too. Only an explicit "tui" opts into terminal.
|
|
279
|
+
var normalized = (mode === "tui") ? "tui" : "gui";
|
|
278
280
|
var data = loadUsers();
|
|
279
281
|
for (var i = 0; i < data.users.length; i++) {
|
|
280
282
|
if (data.users[i].id === userId) {
|
|
@@ -1180,6 +1180,11 @@ function createClaudeAdapter(opts) {
|
|
|
1180
1180
|
slashCommands: [],
|
|
1181
1181
|
fastModeState: null,
|
|
1182
1182
|
capabilities: {
|
|
1183
|
+
effort: true,
|
|
1184
|
+
fork: true,
|
|
1185
|
+
rollback: false,
|
|
1186
|
+
sessionListing: true,
|
|
1187
|
+
sessionRename: true,
|
|
1183
1188
|
thinking: true,
|
|
1184
1189
|
betas: true,
|
|
1185
1190
|
rewind: true,
|
|
@@ -1556,6 +1561,11 @@ function createClaudeAdapter(opts) {
|
|
|
1556
1561
|
slashCommands: r.slashCommands || [],
|
|
1557
1562
|
fastModeState: r.fastModeState || null,
|
|
1558
1563
|
capabilities: {
|
|
1564
|
+
effort: true,
|
|
1565
|
+
fork: true,
|
|
1566
|
+
rollback: false,
|
|
1567
|
+
sessionListing: true,
|
|
1568
|
+
sessionRename: true,
|
|
1559
1569
|
thinking: true,
|
|
1560
1570
|
betas: true,
|
|
1561
1571
|
rewind: true,
|
|
@@ -1628,6 +1638,11 @@ function createClaudeAdapter(opts) {
|
|
|
1628
1638
|
slashCommands: [],
|
|
1629
1639
|
fastModeState: null,
|
|
1630
1640
|
capabilities: {
|
|
1641
|
+
effort: true,
|
|
1642
|
+
fork: true,
|
|
1643
|
+
rollback: false,
|
|
1644
|
+
sessionListing: true,
|
|
1645
|
+
sessionRename: true,
|
|
1631
1646
|
thinking: true,
|
|
1632
1647
|
betas: true,
|
|
1633
1648
|
rewind: true,
|
|
@@ -1708,6 +1723,11 @@ function createClaudeAdapter(opts) {
|
|
|
1708
1723
|
slashCommands: r.slashCommands || [],
|
|
1709
1724
|
fastModeState: r.fastModeState || null,
|
|
1710
1725
|
capabilities: {
|
|
1726
|
+
effort: true,
|
|
1727
|
+
fork: true,
|
|
1728
|
+
rollback: false,
|
|
1729
|
+
sessionListing: true,
|
|
1730
|
+
sessionRename: true,
|
|
1711
1731
|
thinking: true,
|
|
1712
1732
|
betas: true,
|
|
1713
1733
|
rewind: true,
|
|
@@ -1737,4 +1757,9 @@ function createClaudeAdapter(opts) {
|
|
|
1737
1757
|
module.exports = {
|
|
1738
1758
|
createClaudeAdapter: createClaudeAdapter,
|
|
1739
1759
|
createMessageQueue: createMessageQueue,
|
|
1760
|
+
contractTestKit: {
|
|
1761
|
+
createMessageQueue: createMessageQueue,
|
|
1762
|
+
createQueryHandle: createQueryHandle,
|
|
1763
|
+
normalizeEvent: flattenEvent,
|
|
1764
|
+
},
|
|
1740
1765
|
};
|