clay-server 2.47.0-beta.2 → 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/project-connection.js +2 -1
- package/lib/project-notifications.js +3 -1
- package/lib/project-sessions.js +17 -9
- package/lib/project.js +11 -3
- package/lib/public/app.js +2 -0
- package/lib/public/modules/app-messages.js +3 -2
- package/lib/public/modules/app-notifications.js +12 -4
- package/lib/public/modules/app-panels.js +6 -3
- package/lib/public/modules/app-rate-limit.js +41 -13
- package/lib/public/modules/app-rendering.js +2 -0
- package/lib/public/modules/input.js +4 -5
- package/lib/public/modules/tui-grab.js +12 -3
- package/lib/sdk-bridge.js +50 -26
- package/lib/sdk-message-processor.js +4 -6
- package/lib/yoke/adapters/claude.js +25 -0
- package/lib/yoke/adapters/codex.js +34 -21
- package/lib/yoke/adapters/kiro.js +33 -19
- package/lib/yoke/index.js +18 -6
- package/lib/yoke/vendor-registry.js +55 -0
- package/package.json +3 -3
package/lib/codex-defaults.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
var CODEX_DEFAULTS = {
|
|
2
|
-
approval: "on-
|
|
2
|
+
approval: "on-request",
|
|
3
3
|
sandbox: "danger-full-access",
|
|
4
4
|
webSearch: "live",
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
var CODEX_APPROVAL_POLICIES = ["untrusted", "on-request", "granular", "never"];
|
|
8
|
+
|
|
9
|
+
function normalizeCodexApproval(value) {
|
|
10
|
+
if (value === "on-failure") return CODEX_DEFAULTS.approval;
|
|
11
|
+
if (CODEX_APPROVAL_POLICIES.indexOf(value) === -1) return CODEX_DEFAULTS.approval;
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
function getCodexConfig(sm) {
|
|
8
16
|
return {
|
|
9
|
-
approval: (sm && sm.codexApproval)
|
|
17
|
+
approval: normalizeCodexApproval(sm && sm.codexApproval),
|
|
10
18
|
sandbox: (sm && sm.codexSandbox) || CODEX_DEFAULTS.sandbox,
|
|
11
19
|
webSearch: (sm && sm.codexWebSearch) || CODEX_DEFAULTS.webSearch,
|
|
12
20
|
};
|
|
@@ -14,5 +22,7 @@ function getCodexConfig(sm) {
|
|
|
14
22
|
|
|
15
23
|
module.exports = {
|
|
16
24
|
CODEX_DEFAULTS: CODEX_DEFAULTS,
|
|
25
|
+
CODEX_APPROVAL_POLICIES: CODEX_APPROVAL_POLICIES,
|
|
26
|
+
normalizeCodexApproval: normalizeCodexApproval,
|
|
17
27
|
getCodexConfig: getCodexConfig,
|
|
18
28
|
};
|
|
@@ -4,6 +4,7 @@ var usersModule = require("./users");
|
|
|
4
4
|
var userPresence = require("./user-presence");
|
|
5
5
|
var emailAccounts = require("./email-accounts");
|
|
6
6
|
var { getCodexConfig } = require("./codex-defaults");
|
|
7
|
+
var yoke = require("./yoke");
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Attach connection/disconnection handlers to a project context.
|
|
@@ -132,7 +133,7 @@ function attachConnection(ctx) {
|
|
|
132
133
|
var restoredActive = restoredState.active;
|
|
133
134
|
var initialVendor = (restoredActive && restoredActive.vendor) || sm.defaultVendor || "claude";
|
|
134
135
|
var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
|
|
135
|
-
sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked });
|
|
136
|
+
sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: yoke.VENDOR_REGISTRY });
|
|
136
137
|
// Update notifications are pushed on a scheduled interval (see
|
|
137
138
|
// scheduleUpdateBroadcast). We no longer push on connect to avoid
|
|
138
139
|
// re-triggering the banner on every page refresh.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
var fs = require("fs");
|
|
6
6
|
var path = require("path");
|
|
7
7
|
var config = require("./config");
|
|
8
|
+
var yoke = require("./yoke");
|
|
8
9
|
|
|
9
10
|
var NOTIF_FILE = path.join(config.CONFIG_DIR, "notifications.json");
|
|
10
11
|
var REMINDER_INTERVAL = 60 * 60 * 1000; // 1 hour
|
|
@@ -21,7 +22,8 @@ function generateId() {
|
|
|
21
22
|
var formatters = {
|
|
22
23
|
auth_required: function (data) {
|
|
23
24
|
var vendor = data.vendor || "claude";
|
|
24
|
-
var
|
|
25
|
+
var vendorInfo = yoke.getVendorInfo(vendor);
|
|
26
|
+
var title = data.title || (((vendorInfo && vendorInfo.displayName) || "Claude Code") + " is not logged in");
|
|
25
27
|
return {
|
|
26
28
|
type: "auth_required",
|
|
27
29
|
title: title,
|
package/lib/project-sessions.js
CHANGED
|
@@ -3,6 +3,12 @@ var path = require("path");
|
|
|
3
3
|
var crypto = require("crypto");
|
|
4
4
|
var { execFileSync } = require("child_process");
|
|
5
5
|
var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
|
|
6
|
+
var yoke = require("./yoke");
|
|
7
|
+
|
|
8
|
+
function vendorSupportsTui(vendor) {
|
|
9
|
+
var info = yoke.getVendorInfo(vendor);
|
|
10
|
+
return !!(info && info.sessionModes.indexOf("tui") !== -1);
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
// Format a user's answer to an ask_user_questions card as a plain user
|
|
8
14
|
// message so the MCP path can feed it back to the agent on the next turn.
|
|
@@ -274,6 +280,8 @@ function attachSessions(ctx) {
|
|
|
274
280
|
var sid = session.cliSessionId;
|
|
275
281
|
var localId = session.localId;
|
|
276
282
|
var resumeSkip = session.dangerouslySkipPermissions ? " --dangerously-skip-permissions" : "";
|
|
283
|
+
// Command construction is Claude-specific. Generalize this before any
|
|
284
|
+
// other vendor declares "tui" in the YOKE registry.
|
|
277
285
|
var cmd = "claude --resume " + sid + resumeSkip + "; exit\n";
|
|
278
286
|
var term = tm.create(80, 24, getOsUserInfoForWs(ws), ws, {
|
|
279
287
|
initialInput: cmd,
|
|
@@ -346,7 +354,7 @@ function attachSessions(ctx) {
|
|
|
346
354
|
// born-TUI session shows the same read-only + Resume view as a fresh click.
|
|
347
355
|
function resolveSessionForView(session, ws) {
|
|
348
356
|
if (!session) return;
|
|
349
|
-
if (session.vendor && session.vendor
|
|
357
|
+
if (session.vendor && !vendorSupportsTui(session.vendor)) { session.tuiSuspended = false; return; }
|
|
350
358
|
var pref = getClaudeOpenModeForWs(ws);
|
|
351
359
|
// A LIVE runtime always wins over the viewer's claudeOpenMode pref:
|
|
352
360
|
// another user (or this user in another tab) may be in the session right
|
|
@@ -424,13 +432,13 @@ function attachSessions(ctx) {
|
|
|
424
432
|
if (ws._clayUser && usersModule.isMultiUser()) sessionOpts.ownerId = ws._clayUser.id;
|
|
425
433
|
if (msg.sessionVisibility) sessionOpts.sessionVisibility = msg.sessionVisibility;
|
|
426
434
|
if (msg.vendor) sessionOpts.vendor = msg.vendor;
|
|
427
|
-
// Mode resolution:
|
|
428
|
-
//
|
|
435
|
+
// Mode resolution: vendors without a TUI session mode are always GUI.
|
|
436
|
+
// TUI-capable sessions honor the explicit msg.mode if provided, otherwise
|
|
429
437
|
// fall back to the user's claudeOpenMode preference. This is what
|
|
430
438
|
// makes the sidebar's "Claude" icon button create the right kind of
|
|
431
439
|
// session without the client needing to know the preference.
|
|
432
440
|
var requestedMode;
|
|
433
|
-
if (msg.vendor
|
|
441
|
+
if (msg.vendor && !vendorSupportsTui(msg.vendor)) {
|
|
434
442
|
requestedMode = "gui";
|
|
435
443
|
} else if (msg.mode === "tui" || msg.mode === "gui") {
|
|
436
444
|
requestedMode = msg.mode;
|
|
@@ -625,7 +633,7 @@ function attachSessions(ctx) {
|
|
|
625
633
|
// for every viewer; only cold sessions follow the clicker's
|
|
626
634
|
// claudeOpenMode pref. Nothing is spawned here.
|
|
627
635
|
var xmTarget = sm.sessions.get(msg.id);
|
|
628
|
-
if (xmTarget && (xmTarget.vendor
|
|
636
|
+
if (xmTarget && (!xmTarget.vendor || vendorSupportsTui(xmTarget.vendor))) {
|
|
629
637
|
// Single source of truth: live runtime wins (tui stays tui, gui stays
|
|
630
638
|
// gui); only cold sessions follow the viewer's pref. No PTY is spawned
|
|
631
639
|
// on switch - born-TUI resumes lazily via the Resume bar
|
|
@@ -678,7 +686,7 @@ function attachSessions(ctx) {
|
|
|
678
686
|
if (msg.type === "resume_tui_session") {
|
|
679
687
|
if (msg.id && sm.sessions.has(msg.id)) {
|
|
680
688
|
var rtTarget = sm.sessions.get(msg.id);
|
|
681
|
-
var rtOk = rtTarget && (rtTarget.vendor
|
|
689
|
+
var rtOk = rtTarget && (!rtTarget.vendor || vendorSupportsTui(rtTarget.vendor)) &&
|
|
682
690
|
rtTarget.cliSessionId && tm;
|
|
683
691
|
if (rtOk) {
|
|
684
692
|
if (usersModule.isMultiUser() && ws._clayUser &&
|
|
@@ -706,7 +714,7 @@ function attachSessions(ctx) {
|
|
|
706
714
|
if (msg.type === "suspend_tui_session") {
|
|
707
715
|
if (msg.id && sm.sessions.has(msg.id)) {
|
|
708
716
|
var stTarget = sm.sessions.get(msg.id);
|
|
709
|
-
var stOk = stTarget && (stTarget.vendor
|
|
717
|
+
var stOk = stTarget && (!stTarget.vendor || vendorSupportsTui(stTarget.vendor));
|
|
710
718
|
if (stOk && (!usersModule.isMultiUser() || !ws._clayUser ||
|
|
711
719
|
usersModule.canAccessSession(ws._clayUser.id, stTarget, { visibility: "public" }))) {
|
|
712
720
|
if (tm) {
|
|
@@ -737,7 +745,7 @@ function attachSessions(ctx) {
|
|
|
737
745
|
var tprId = msg.id;
|
|
738
746
|
var tprSess = (tprId && sm.sessions.has(tprId)) ? sm.sessions.get(tprId) : null;
|
|
739
747
|
if (!tprSess || !tprSess.cliSessionId || tprSess.mode !== "tui") return true;
|
|
740
|
-
if (tprSess.vendor && tprSess.vendor
|
|
748
|
+
if (tprSess.vendor && !vendorSupportsTui(tprSess.vendor)) return true;
|
|
741
749
|
if (usersModule.isMultiUser() && ws._clayUser
|
|
742
750
|
&& !usersModule.canAccessSession(ws._clayUser.id, tprSess, { visibility: "public" })) {
|
|
743
751
|
return true;
|
|
@@ -1084,7 +1092,7 @@ function attachSessions(ctx) {
|
|
|
1084
1092
|
|
|
1085
1093
|
// Codex-specific settings (stored on sessionManager, passed to adapter via adapterOptions)
|
|
1086
1094
|
if (msg.type === "set_codex_approval") {
|
|
1087
|
-
sm.codexApproval = msg.approval
|
|
1095
|
+
sm.codexApproval = getCodexConfig({ codexApproval: msg.approval }).approval;
|
|
1088
1096
|
send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
|
|
1089
1097
|
return true;
|
|
1090
1098
|
}
|
package/lib/project.js
CHANGED
|
@@ -914,18 +914,25 @@ function createProjectContext(opts) {
|
|
|
914
914
|
clayAuthToken: serverAuthToken,
|
|
915
915
|
slug: slug,
|
|
916
916
|
});
|
|
917
|
-
}
|
|
917
|
+
}
|
|
918
|
+
var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[msg.vendor]
|
|
919
|
+
|| !sm.modelsByVendor || !sm.modelsByVendor[msg.vendor];
|
|
920
|
+
if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
|
|
918
921
|
// Init warms the adapter, but a slow/failed init must not block
|
|
919
922
|
// model listing (e.g. Codex models are a fixed list). Keep going
|
|
920
923
|
// to supportedModels() even if init throws.
|
|
921
924
|
try {
|
|
922
|
-
await vendorAdapter.init({
|
|
925
|
+
var readyResult = await vendorAdapter.init({
|
|
923
926
|
cwd: cwd,
|
|
924
927
|
clayPort: serverPort,
|
|
925
928
|
clayTls: serverTls,
|
|
926
929
|
clayAuthToken: serverAuthToken,
|
|
927
930
|
slug: slug,
|
|
928
931
|
});
|
|
932
|
+
sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
|
|
933
|
+
sm.capabilitiesByVendor[msg.vendor] = readyResult.capabilities || {};
|
|
934
|
+
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
935
|
+
if (Array.isArray(readyResult.models)) sm.modelsByVendor[msg.vendor] = readyResult.models;
|
|
929
936
|
} catch (e) {
|
|
930
937
|
console.error("[project] " + msg.vendor + " init failed (continuing to model list):", e.message || e);
|
|
931
938
|
}
|
|
@@ -957,7 +964,8 @@ function createProjectContext(opts) {
|
|
|
957
964
|
}
|
|
958
965
|
}
|
|
959
966
|
}
|
|
960
|
-
|
|
967
|
+
var vendorCapabilities = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[msg.vendor]) || {};
|
|
968
|
+
sendTo(ws, { type: "model_info", model: modelToSend, models: vendorModels, vendor: msg.vendor, capabilities: vendorCapabilities, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
961
969
|
})();
|
|
962
970
|
return;
|
|
963
971
|
}
|
package/lib/public/app.js
CHANGED
|
@@ -312,6 +312,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
312
312
|
currentModels: [],
|
|
313
313
|
// Project's last-used vendor; seeds the sidebar's "New session" button.
|
|
314
314
|
lastVendor: "",
|
|
315
|
+
// Static adapter metadata sent before any vendor is initialized.
|
|
316
|
+
vendorInfo: {},
|
|
315
317
|
// How Claude sessions open: "gui" (default) or "tui". The server sends
|
|
316
318
|
// claude_open_mode_changed on connect; this seeds it beforehand so the
|
|
317
319
|
// new-session menu doesn't flash the TUI-only entry.
|
|
@@ -289,7 +289,7 @@ export function processMessage(msg) {
|
|
|
289
289
|
// host lives on document.body (it's position: fixed), so it
|
|
290
290
|
// survives project navigation unless we detach explicitly here.
|
|
291
291
|
detachTuiView();
|
|
292
|
-
store.set({ projectName: msg.project || msg.cwd });
|
|
292
|
+
store.set({ projectName: msg.project || msg.cwd, vendorInfo: msg.vendors || {} });
|
|
293
293
|
if (msg.cwd) store.set({ cwd: msg.cwd });
|
|
294
294
|
if (msg.slug) store.set({ currentSlug: msg.slug });
|
|
295
295
|
try { var _is = store.snap(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
|
|
@@ -464,6 +464,7 @@ export function processMessage(msg) {
|
|
|
464
464
|
if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
|
|
465
465
|
if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
|
|
466
466
|
if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
|
|
467
|
+
if (msg.capabilities) _miUpdate.vendorCapabilities = msg.capabilities;
|
|
467
468
|
store.set(_miUpdate);
|
|
468
469
|
updateSettingsModels(_modelVal, msg.models || []);
|
|
469
470
|
break;
|
|
@@ -1359,7 +1360,7 @@ export function processMessage(msg) {
|
|
|
1359
1360
|
var _lm = store.get('pendingLoginModal');
|
|
1360
1361
|
store.set({ pendingLoginModal: null });
|
|
1361
1362
|
openTuiModal(msg.id, _lm.slug, {
|
|
1362
|
-
sessionTitle: (_lm.vendor
|
|
1363
|
+
sessionTitle: (VENDOR_NAMES[_lm.vendor] || "Claude Code") + " login",
|
|
1363
1364
|
projectName: _lm.slug,
|
|
1364
1365
|
compact: true,
|
|
1365
1366
|
});
|
|
@@ -19,6 +19,14 @@ var bannerContainer = null;
|
|
|
19
19
|
var bellBtn = null;
|
|
20
20
|
var badgeEl = null;
|
|
21
21
|
|
|
22
|
+
function getVendorLoginCommand(vendor) {
|
|
23
|
+
var vendors = store.get('vendorInfo') || {};
|
|
24
|
+
var info = vendors[vendor];
|
|
25
|
+
if (info && info.loginCommand) return info.loginCommand;
|
|
26
|
+
var fallbacks = { codex: "codex login --device-auth", claude: "claude login" };
|
|
27
|
+
return fallbacks[vendor] || fallbacks.claude;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
// --- Pending TUI attention tracking ---
|
|
23
31
|
// Mirrors the icon-shake / favicon-blink behavior the SDK side already gets
|
|
24
32
|
// from `pendingPermissions` on project status broadcasts. The notification
|
|
@@ -252,7 +260,7 @@ function showBanner(notif, autoDismissMs) {
|
|
|
252
260
|
removeBanner(banner);
|
|
253
261
|
dismissNotif(notif.id);
|
|
254
262
|
var authMeta = notif.meta || {};
|
|
255
|
-
startLoginInModal(authMeta.loginCommand || (
|
|
263
|
+
startLoginInModal(authMeta.loginCommand || getVendorLoginCommand(authMeta.vendor || "claude"), authMeta.vendor || "claude");
|
|
256
264
|
showLoginReminderBanner();
|
|
257
265
|
});
|
|
258
266
|
}
|
|
@@ -332,7 +340,7 @@ function startLoginInModal(loginCommand, vendor) {
|
|
|
332
340
|
if (authReminderVisible) return;
|
|
333
341
|
var ws = getWs();
|
|
334
342
|
if (!ws || ws.readyState !== 1) return;
|
|
335
|
-
var cmd = loginCommand || (vendor
|
|
343
|
+
var cmd = loginCommand || getVendorLoginCommand(vendor);
|
|
336
344
|
var slug = currentProjectSlug();
|
|
337
345
|
if (!slug) { startLoginCommand(cmd); return; }
|
|
338
346
|
store.set({ pendingLoginModal: { slug: slug, vendor: vendor || "claude" } });
|
|
@@ -366,7 +374,7 @@ export function autoStartLoginIfNeeded(msg) {
|
|
|
366
374
|
if (authReminderVisible) return false;
|
|
367
375
|
var vendor = msg.vendor || "claude";
|
|
368
376
|
var cmd = msg.loginCommand
|
|
369
|
-
|| (vendor
|
|
377
|
+
|| getVendorLoginCommand(vendor);
|
|
370
378
|
startLoginInModal(cmd, vendor);
|
|
371
379
|
showLoginReminderBanner();
|
|
372
380
|
return true;
|
|
@@ -426,7 +434,7 @@ function showLoginReminderBanner() {
|
|
|
426
434
|
export function showAuthRequiredBanner(msg) {
|
|
427
435
|
if (!bannerContainer) return;
|
|
428
436
|
var vendor = (msg && (msg.vendor || (msg.meta && msg.meta.vendor))) || "claude";
|
|
429
|
-
var loginCommand = (msg && (msg.loginCommand || (msg.meta && msg.meta.loginCommand))) || (vendor
|
|
437
|
+
var loginCommand = (msg && (msg.loginCommand || (msg.meta && msg.meta.loginCommand))) || getVendorLoginCommand(vendor);
|
|
430
438
|
activeAuthRequiredMsg = Object.assign({}, msg || {}, {
|
|
431
439
|
id: (msg && msg.id) || ("_auth_" + Date.now()),
|
|
432
440
|
type: "auth_required",
|
|
@@ -93,7 +93,7 @@ var EFFORT_LEVELS_BY_VENDOR = {
|
|
|
93
93
|
var THINKING_OPTIONS = ["disabled", "adaptive", "budget"];
|
|
94
94
|
var CODEX_APPROVAL_OPTIONS = [
|
|
95
95
|
{ value: "never", label: "Auto" },
|
|
96
|
-
{ value: "
|
|
96
|
+
{ value: "untrusted", label: "Untrusted" },
|
|
97
97
|
{ value: "on-request", label: "Ask" },
|
|
98
98
|
];
|
|
99
99
|
var CODEX_SANDBOX_OPTIONS = [
|
|
@@ -641,11 +641,14 @@ export function updateConfigChip() {
|
|
|
641
641
|
rebuildModeList();
|
|
642
642
|
rebuildEffortBar();
|
|
643
643
|
|
|
644
|
-
//
|
|
644
|
+
// MODE remains Claude-specific until adapter modes become capabilities.
|
|
645
645
|
var isClaude = vendor === "claude";
|
|
646
|
-
// MODE, THINKING, BETA are Claude-only
|
|
647
646
|
if (configModeList && configModeList.parentElement) configModeList.parentElement.style.display = isClaude ? "" : "none";
|
|
648
647
|
rebuildThinkingSection();
|
|
648
|
+
// capabilities.thinking means "emits a thinking stream" (true for every
|
|
649
|
+
// vendor), NOT "accepts thinking config". The adaptive/extended toggle and
|
|
650
|
+
// budget only feed Claude queries (sm.currentThinking), so the section
|
|
651
|
+
// stays Claude-only until a dedicated thinkingConfig capability exists.
|
|
649
652
|
if (configThinkingSection) configThinkingSection.style.display = isClaude ? "" : "none";
|
|
650
653
|
// BETA section deprecated (1M context is now standard)
|
|
651
654
|
if (configBetaSection) configBetaSection.style.display = "none";
|
|
@@ -23,20 +23,37 @@ var fastModeIndicatorEl = null;
|
|
|
23
23
|
// --- Internal helpers ---
|
|
24
24
|
|
|
25
25
|
function getVendorUsageMeta(vendor) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
var vendors = store.get('vendorInfo') || {};
|
|
27
|
+
var info = vendors[vendor];
|
|
28
|
+
if (info && info.usageDashboard) return info.usageDashboard;
|
|
29
|
+
var fallbacks = {
|
|
30
|
+
codex: {
|
|
28
31
|
icon: "/codex-avatar.png",
|
|
29
32
|
alt: "Codex",
|
|
30
33
|
href: "https://chatgpt.com/admin/usage",
|
|
31
34
|
title: "Check usage on ChatGPT",
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
},
|
|
36
|
+
claude: {
|
|
37
|
+
icon: "/claude-code-avatar.png",
|
|
38
|
+
alt: "Claude Code",
|
|
39
|
+
href: "https://claude.ai/settings/usage",
|
|
40
|
+
title: "Check usage on claude.ai",
|
|
41
|
+
},
|
|
39
42
|
};
|
|
43
|
+
return fallbacks[vendor] || fallbacks.claude;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function vendorTracksRateLimits(vendor) {
|
|
47
|
+
var vendors = store.get('vendorInfo') || {};
|
|
48
|
+
var info = vendors[vendor];
|
|
49
|
+
if (info) return info.rateLimitTracking !== false;
|
|
50
|
+
var legacyTrackedVendors = ["claude", "codex"];
|
|
51
|
+
return legacyTrackedVendors.indexOf(vendor) !== -1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function vendorSupportsScheduledMessages(vendor) {
|
|
55
|
+
var scheduledMessageVendors = ["claude"];
|
|
56
|
+
return scheduledMessageVendors.indexOf(vendor) !== -1;
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
function rateLimitTypeLabel(type) {
|
|
@@ -178,8 +195,13 @@ function tickRateLimitUsage() {
|
|
|
178
195
|
|
|
179
196
|
export function initRateLimit() {
|
|
180
197
|
store.subscribe(function(state, prev) {
|
|
181
|
-
if (state.currentVendor !== prev.currentVendor && state.currentVendor
|
|
182
|
-
clearScheduleDelay();
|
|
198
|
+
if (state.currentVendor !== prev.currentVendor && state.currentVendor) {
|
|
199
|
+
if (!vendorSupportsScheduledMessages(state.currentVendor)) clearScheduleDelay();
|
|
200
|
+
}
|
|
201
|
+
if (state.currentVendor !== prev.currentVendor || state.vendorInfo !== prev.vendorInfo) {
|
|
202
|
+
if (rateLimitUsageEl) {
|
|
203
|
+
rateLimitUsageEl.style.display = vendorTracksRateLimits(state.currentVendor || "claude") ? "" : "none";
|
|
204
|
+
}
|
|
183
205
|
}
|
|
184
206
|
});
|
|
185
207
|
}
|
|
@@ -203,7 +225,7 @@ export function handleRateLimitEvent(msg) {
|
|
|
203
225
|
if (rateLimitResetTimer) clearTimeout(rateLimitResetTimer);
|
|
204
226
|
// Auto-switch input to schedule mode: any message typed will be queued for after reset
|
|
205
227
|
var delayUntilReset = msg.resetsAt - Date.now();
|
|
206
|
-
if (delayUntilReset > 0 && (store.get('currentVendor') || "claude")
|
|
228
|
+
if (delayUntilReset > 0 && vendorSupportsScheduledMessages(store.get('currentVendor') || "claude")) {
|
|
207
229
|
setScheduleDelayMs(delayUntilReset + 60000); // +1min buffer after reset
|
|
208
230
|
}
|
|
209
231
|
rateLimitResetTimer = setTimeout(function () {
|
|
@@ -222,6 +244,11 @@ export function handleRateLimitEvent(msg) {
|
|
|
222
244
|
}
|
|
223
245
|
|
|
224
246
|
export function updateRateLimitUsage(msg) {
|
|
247
|
+
var activeVendor = store.get('currentVendor') || "claude";
|
|
248
|
+
if (!vendorTracksRateLimits(activeVendor)) {
|
|
249
|
+
if (rateLimitUsageEl) rateLimitUsageEl.style.display = "none";
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
225
252
|
if (msg.rateLimitType && msg.resetsAt) {
|
|
226
253
|
rateLimitResetState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
|
|
227
254
|
}
|
|
@@ -238,6 +265,7 @@ export function updateRateLimitUsage(msg) {
|
|
|
238
265
|
var ref = document.getElementById("skip-perms-pill");
|
|
239
266
|
topBarActions.insertBefore(rateLimitUsageEl, ref);
|
|
240
267
|
}
|
|
268
|
+
rateLimitUsageEl.style.display = "";
|
|
241
269
|
|
|
242
270
|
// Build label from available reset times
|
|
243
271
|
var parts = [];
|
|
@@ -251,7 +279,7 @@ export function updateRateLimitUsage(msg) {
|
|
|
251
279
|
}
|
|
252
280
|
|
|
253
281
|
var label = parts.length > 0 ? parts.join(" · ") : "Check usage";
|
|
254
|
-
var vendor =
|
|
282
|
+
var vendor = activeVendor;
|
|
255
283
|
var meta = getVendorUsageMeta(vendor);
|
|
256
284
|
rateLimitUsageEl.href = meta.href;
|
|
257
285
|
rateLimitUsageEl.title = meta.title;
|
|
@@ -14,6 +14,8 @@ import { sendMessage, hasSendableContent } from './input.js';
|
|
|
14
14
|
import { getChatLayout } from './theme.js';
|
|
15
15
|
import { getScheduledMsgEl } from './app-rate-limit.js';
|
|
16
16
|
|
|
17
|
+
// Keep these client constants aligned with lib/yoke/vendor-registry.js. The
|
|
18
|
+
// browser cannot import the server's CommonJS registry directly.
|
|
17
19
|
export var VENDOR_AVATARS = {
|
|
18
20
|
claude: "/claude-code-avatar.png",
|
|
19
21
|
codex: "/codex-avatar.png",
|
|
@@ -5,6 +5,7 @@ import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible
|
|
|
5
5
|
import { store } from './store.js';
|
|
6
6
|
import { mateAvatarUrl } from './avatar.js';
|
|
7
7
|
import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
|
|
8
|
+
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
8
9
|
|
|
9
10
|
var ctx;
|
|
10
11
|
|
|
@@ -300,11 +301,9 @@ export function sendMessage() {
|
|
|
300
301
|
_vtw2.classList.remove("locked");
|
|
301
302
|
}
|
|
302
303
|
if (_avi && _avIcon) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
_avIcon.alt = _vendorNames[_committedVendor] || _vendorNames.claude;
|
|
307
|
-
_avi.title = (_vendorNames[_committedVendor] || _vendorNames.claude) + " session";
|
|
304
|
+
_avIcon.src = VENDOR_AVATARS[_committedVendor] || VENDOR_AVATARS.claude;
|
|
305
|
+
_avIcon.alt = VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude;
|
|
306
|
+
_avi.title = (VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude) + " session";
|
|
308
307
|
_avi.classList.remove("hidden");
|
|
309
308
|
}
|
|
310
309
|
} else if (_vtw2) {
|
|
@@ -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
|
@@ -10,6 +10,7 @@ var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discover
|
|
|
10
10
|
var { isSafeBashSegment } = require("./safe-bash-commands");
|
|
11
11
|
var { createMessageQueue } = require("./sdk-message-queue");
|
|
12
12
|
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
13
|
+
var yoke = require("./yoke");
|
|
13
14
|
|
|
14
15
|
// Extract serializable tool descriptors from MCP server instances.
|
|
15
16
|
// Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
|
|
@@ -127,7 +128,6 @@ function createSDKBridge(opts) {
|
|
|
127
128
|
var _cachedFreshAuthAt = 0;
|
|
128
129
|
|
|
129
130
|
function getFreshAuthState(force) {
|
|
130
|
-
var yoke = require("./yoke");
|
|
131
131
|
var now = Date.now();
|
|
132
132
|
if (!force && _cachedFreshAuthState && now - _cachedFreshAuthAt < 15000) {
|
|
133
133
|
return _cachedFreshAuthState;
|
|
@@ -150,10 +150,13 @@ function createSDKBridge(opts) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
function getLoginCommand(vendor) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
function notifyAuthRequired(session, title, body, authLinuxUser, canAutoLogin, loginCommand) {
|
|
@@ -220,15 +223,25 @@ function createSDKBridge(opts) {
|
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
function sendModelInfoForVendor(vendor, model) {
|
|
226
|
+
var resolvedVendor = vendor || (adapter && adapter.vendor) || "claude";
|
|
223
227
|
send({
|
|
224
228
|
type: "model_info",
|
|
225
229
|
model: model || "",
|
|
226
|
-
models: getModelsForVendor(
|
|
227
|
-
vendor:
|
|
230
|
+
models: getModelsForVendor(resolvedVendor),
|
|
231
|
+
vendor: resolvedVendor,
|
|
232
|
+
capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[resolvedVendor]) || {},
|
|
228
233
|
availableVendors: sm.availableVendors || [],
|
|
229
234
|
installedVendors: sm.installedVendors || [],
|
|
230
235
|
});
|
|
231
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
|
+
}
|
|
232
245
|
var onTurnDone = opts.onTurnDone || null;
|
|
233
246
|
|
|
234
247
|
// --- Idle session reaper ---
|
|
@@ -869,7 +882,8 @@ function createSDKBridge(opts) {
|
|
|
869
882
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
870
883
|
|| !!authLinuxUser
|
|
871
884
|
|| (authUser && authUser.role === "admin");
|
|
872
|
-
var
|
|
885
|
+
var authInfo = yoke.getVendorInfo(session.vendor);
|
|
886
|
+
var authVendorLabel = (authInfo && authInfo.displayName) || "Claude Code";
|
|
873
887
|
var authTitle = authVendorLabel + " is not logged in.";
|
|
874
888
|
var authMsg = {
|
|
875
889
|
type: "auth_required",
|
|
@@ -1070,10 +1084,9 @@ function createSDKBridge(opts) {
|
|
|
1070
1084
|
async function startQuery(session, text, images, linuxUser) {
|
|
1071
1085
|
async function ensureVendorReady(vendor) {
|
|
1072
1086
|
if (!vendor) return null;
|
|
1073
|
-
if (linuxUser && vendor
|
|
1087
|
+
if (linuxUser && !supportsOsUserIsolation(vendor)) return null;
|
|
1074
1088
|
var vendorAdapter = adapters[vendor] || null;
|
|
1075
1089
|
if (!vendorAdapter) {
|
|
1076
|
-
var yoke = require("./yoke");
|
|
1077
1090
|
vendorAdapter = await yoke.lazyCreateAdapter(adapters, vendor, {
|
|
1078
1091
|
cwd: cwd,
|
|
1079
1092
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
@@ -1083,8 +1096,11 @@ function createSDKBridge(opts) {
|
|
|
1083
1096
|
clayAuthToken: clayAuthToken,
|
|
1084
1097
|
slug: slug,
|
|
1085
1098
|
});
|
|
1086
|
-
}
|
|
1087
|
-
|
|
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({
|
|
1088
1104
|
cwd: cwd,
|
|
1089
1105
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
1090
1106
|
linuxUser: linuxUser || undefined,
|
|
@@ -1093,6 +1109,7 @@ function createSDKBridge(opts) {
|
|
|
1093
1109
|
clayAuthToken: clayAuthToken,
|
|
1094
1110
|
slug: slug,
|
|
1095
1111
|
});
|
|
1112
|
+
rememberAdapterReady(vendor, readyResult);
|
|
1096
1113
|
}
|
|
1097
1114
|
if (vendorAdapter) {
|
|
1098
1115
|
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
@@ -1100,6 +1117,11 @@ function createSDKBridge(opts) {
|
|
|
1100
1117
|
if (!sm.modelsByVendor[vendor] && typeof vendorAdapter.supportedModels === "function") {
|
|
1101
1118
|
sm.modelsByVendor[vendor] = await vendorAdapter.supportedModels();
|
|
1102
1119
|
}
|
|
1120
|
+
var vendorModels = getModelsForVendor(vendor);
|
|
1121
|
+
var modelForVendor = resolveModelInList(vendorModels, sm.currentModel)
|
|
1122
|
+
|| modelEntryValue(vendorModels[0])
|
|
1123
|
+
|| "";
|
|
1124
|
+
sendModelInfoForVendor(vendor, modelForVendor);
|
|
1103
1125
|
}
|
|
1104
1126
|
return vendorAdapter;
|
|
1105
1127
|
}
|
|
@@ -1108,11 +1130,13 @@ function createSDKBridge(opts) {
|
|
|
1108
1130
|
// OS-isolated session would expose the daemon user's HOME and Kiro
|
|
1109
1131
|
// credentials to another user, so refuse until the adapter has a
|
|
1110
1132
|
// per-user worker path.
|
|
1111
|
-
if (linuxUser && session.vendor
|
|
1112
|
-
|
|
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");
|
|
1113
1137
|
sendAndRecord(session, {
|
|
1114
1138
|
type: "error",
|
|
1115
|
-
text: "
|
|
1139
|
+
text: isolatedVendorName + " is not available for OS-isolated users yet.",
|
|
1116
1140
|
});
|
|
1117
1141
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
1118
1142
|
return;
|
|
@@ -1139,7 +1163,9 @@ function createSDKBridge(opts) {
|
|
|
1139
1163
|
}
|
|
1140
1164
|
// If still not available after lazy check, send auth_required
|
|
1141
1165
|
if (session.vendor && !adapters[session.vendor]) {
|
|
1142
|
-
var
|
|
1166
|
+
var missingVendorInfo = yoke.getVendorInfo(session.vendor);
|
|
1167
|
+
var vendorName = (missingVendorInfo && missingVendorInfo.displayName)
|
|
1168
|
+
|| session.vendor.charAt(0).toUpperCase() + session.vendor.slice(1);
|
|
1143
1169
|
var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
|
|
1144
1170
|
var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
|
|
1145
1171
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
@@ -1520,25 +1546,26 @@ function createSDKBridge(opts) {
|
|
|
1520
1546
|
}
|
|
1521
1547
|
|
|
1522
1548
|
// Claude: check if binary is in PATH
|
|
1523
|
-
if (tryLookup("claude")) result.push("claude");
|
|
1549
|
+
if (tryLookup(yoke.getVendorInfo("claude").binaryName)) result.push("claude");
|
|
1524
1550
|
|
|
1525
1551
|
// Codex: check bundled binary or PATH
|
|
1526
1552
|
var codexBin = null;
|
|
1527
1553
|
try {
|
|
1528
1554
|
codexBin = require("./yoke/codex-app-server").findCodexPath();
|
|
1529
1555
|
} catch (e) {}
|
|
1530
|
-
if ((codexBin && fs.existsSync(codexBin)) || tryLookup("codex")) result.push("codex");
|
|
1556
|
+
if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
|
|
1531
1557
|
|
|
1532
1558
|
// Kiro has no per-user ACP spawn path yet. Do not advertise the daemon's
|
|
1533
1559
|
// binary to an OS-isolated user, even if that user also has Kiro installed.
|
|
1534
|
-
|
|
1560
|
+
var kiroInfo = yoke.getVendorInfo("kiro");
|
|
1561
|
+
if (!linuxUser || kiroInfo.osUserIsolation) {
|
|
1535
1562
|
var kiroBin = null;
|
|
1536
1563
|
try {
|
|
1537
1564
|
kiroBin = require("./yoke/kiro-acp-server").findKiroPath();
|
|
1538
1565
|
} catch (e) {}
|
|
1539
|
-
if ((kiroBin && fs.existsSync(kiroBin)) || tryLookup(
|
|
1566
|
+
if ((kiroBin && fs.existsSync(kiroBin)) || tryLookup(kiroInfo.binaryName)) result.push("kiro");
|
|
1540
1567
|
} else {
|
|
1541
|
-
console.log("[sdk-bridge]
|
|
1568
|
+
console.log("[sdk-bridge] " + kiroInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
|
|
1542
1569
|
}
|
|
1543
1570
|
|
|
1544
1571
|
return result;
|
|
@@ -1546,7 +1573,7 @@ function createSDKBridge(opts) {
|
|
|
1546
1573
|
|
|
1547
1574
|
function getAvailableVendors(linuxUser) {
|
|
1548
1575
|
return Object.keys(adapters).filter(function(vendor) {
|
|
1549
|
-
return !(linuxUser && vendor
|
|
1576
|
+
return !(linuxUser && !supportsOsUserIsolation(vendor));
|
|
1550
1577
|
});
|
|
1551
1578
|
}
|
|
1552
1579
|
|
|
@@ -1591,10 +1618,7 @@ function createSDKBridge(opts) {
|
|
|
1591
1618
|
}
|
|
1592
1619
|
sm.availableModels = result.models || [];
|
|
1593
1620
|
// Store per-vendor models and capabilities
|
|
1594
|
-
|
|
1595
|
-
sm.modelsByVendor[defaultVendor] = result.models || [];
|
|
1596
|
-
sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
|
|
1597
|
-
sm.capabilitiesByVendor[defaultVendor] = result.capabilities || {};
|
|
1621
|
+
rememberAdapterReady(defaultVendor, result);
|
|
1598
1622
|
} catch (e) {
|
|
1599
1623
|
if (e && e.name !== "AbortError" && !(e.message && e.message.indexOf("aborted") !== -1)) {
|
|
1600
1624
|
send({ type: "error", text: "Failed to load " + defaultVendor + " SDK: " + (e.message || e) });
|
|
@@ -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,12 +51,9 @@ function attachMessageProcessor(ctx) {
|
|
|
50
51
|
var canAutoLogin = !usersModule.isMultiUser()
|
|
51
52
|
|| !!authLinuxUser
|
|
52
53
|
|| (authUser && authUser.role === "admin");
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
var loginCommand = session.vendor === "codex"
|
|
57
|
-
? "codex login --device-auth"
|
|
58
|
-
: (session.vendor === "kiro" ? "kiro-cli login" : "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";
|
|
59
57
|
var _nmLogin = getNotificationsModule();
|
|
60
58
|
sendAndRecord(session, {
|
|
61
59
|
type: "auth_required",
|
|
@@ -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
|
};
|
|
@@ -597,6 +597,27 @@ function flattenEvent(notification, state) {
|
|
|
597
597
|
return events;
|
|
598
598
|
}
|
|
599
599
|
|
|
600
|
+
function createEventState(model) {
|
|
601
|
+
return {
|
|
602
|
+
blockCounter: 0,
|
|
603
|
+
threadId: null,
|
|
604
|
+
turnStarted: false,
|
|
605
|
+
lastUsage: null,
|
|
606
|
+
lastInputTokens: null,
|
|
607
|
+
done: false,
|
|
608
|
+
aborted: false,
|
|
609
|
+
loopStarted: false,
|
|
610
|
+
model: model || "gpt-5.6-terra",
|
|
611
|
+
textBlocks: {},
|
|
612
|
+
textLengths: {},
|
|
613
|
+
thinkingBlocks: {},
|
|
614
|
+
thinkingLengths: {},
|
|
615
|
+
toolBlocks: {},
|
|
616
|
+
commandInputs: {},
|
|
617
|
+
planTexts: {},
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
600
621
|
// --- QueryHandle ---
|
|
601
622
|
|
|
602
623
|
function createCodexQueryHandle(appServer, queryOpts) {
|
|
@@ -611,25 +632,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
611
632
|
return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
|
|
612
633
|
}
|
|
613
634
|
|
|
614
|
-
var state =
|
|
615
|
-
blockCounter: 0,
|
|
616
|
-
threadId: null,
|
|
617
|
-
turnStarted: false,
|
|
618
|
-
lastUsage: null,
|
|
619
|
-
lastInputTokens: null, // from thread/tokenUsage/updated
|
|
620
|
-
done: false,
|
|
621
|
-
aborted: false,
|
|
622
|
-
loopStarted: false,
|
|
623
|
-
model: queryOpts.model || "gpt-5.6-terra",
|
|
624
|
-
// Track incremental text deltas
|
|
625
|
-
textBlocks: {}, // itemId -> true (text_start sent)
|
|
626
|
-
textLengths: {}, // itemId -> last sent length
|
|
627
|
-
thinkingBlocks: {}, // itemId -> blockId
|
|
628
|
-
thinkingLengths: {}, // itemId -> last sent length
|
|
629
|
-
toolBlocks: {}, // itemId -> blockId (for tool_start dedup)
|
|
630
|
-
commandInputs: {}, // itemId -> command captured from approval/start events
|
|
631
|
-
planTexts: {}, // itemId -> streamed plan text
|
|
632
|
-
};
|
|
635
|
+
var state = createEventState(queryOpts.model);
|
|
633
636
|
|
|
634
637
|
// Internal event buffer for async iterator
|
|
635
638
|
var eventBuffer = [];
|
|
@@ -867,7 +870,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
867
870
|
var threadParams = {
|
|
868
871
|
model: queryOpts.model || "gpt-5.6-terra",
|
|
869
872
|
sandbox: queryOpts.sandboxMode || "workspace-write",
|
|
870
|
-
approvalPolicy: queryOpts.approvalPolicy || "on-
|
|
873
|
+
approvalPolicy: queryOpts.approvalPolicy || "on-request",
|
|
871
874
|
cwd: queryOpts.cwd,
|
|
872
875
|
skipGitRepoCheck: true,
|
|
873
876
|
};
|
|
@@ -1139,6 +1142,11 @@ function createCodexAdapter(opts) {
|
|
|
1139
1142
|
slashCommands: skillNames || [],
|
|
1140
1143
|
fastModeState: null,
|
|
1141
1144
|
capabilities: {
|
|
1145
|
+
effort: true,
|
|
1146
|
+
fork: true,
|
|
1147
|
+
rollback: true,
|
|
1148
|
+
sessionListing: false,
|
|
1149
|
+
sessionRename: false,
|
|
1142
1150
|
thinking: true,
|
|
1143
1151
|
betas: false,
|
|
1144
1152
|
rewind: false,
|
|
@@ -1463,7 +1471,7 @@ function createCodexAdapter(opts) {
|
|
|
1463
1471
|
if (queryOpts.toolPolicy === "allow-all") {
|
|
1464
1472
|
handleOpts.approvalPolicy = "never";
|
|
1465
1473
|
} else {
|
|
1466
|
-
handleOpts.approvalPolicy = codexOpts.approvalPolicy || "on-
|
|
1474
|
+
handleOpts.approvalPolicy = codexOpts.approvalPolicy || "on-request";
|
|
1467
1475
|
}
|
|
1468
1476
|
|
|
1469
1477
|
// Sandbox mode
|
|
@@ -1590,4 +1598,9 @@ function createCodexAdapter(opts) {
|
|
|
1590
1598
|
|
|
1591
1599
|
module.exports = {
|
|
1592
1600
|
createCodexAdapter: createCodexAdapter,
|
|
1601
|
+
contractTestKit: {
|
|
1602
|
+
createEventState: createEventState,
|
|
1603
|
+
createQueryHandle: createCodexQueryHandle,
|
|
1604
|
+
normalizeEvent: flattenEvent,
|
|
1605
|
+
},
|
|
1593
1606
|
};
|
|
@@ -377,30 +377,19 @@ function finalToolContent(state, callId, update) {
|
|
|
377
377
|
return extractRawOutput(update.rawOutput);
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
var abortController = queryOpts.abortController;
|
|
384
|
-
var systemPrompt = queryOpts.systemPrompt || "";
|
|
385
|
-
var canUseTool = queryOpts.canUseTool || null;
|
|
386
|
-
var onFinished = queryOpts.onFinished || null;
|
|
387
|
-
|
|
388
|
-
function isCancelled() {
|
|
389
|
-
return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
var state = {
|
|
380
|
+
function createEventState(opts) {
|
|
381
|
+
opts = opts || {};
|
|
382
|
+
return {
|
|
393
383
|
blockCounter: 0,
|
|
394
|
-
sessionId:
|
|
395
|
-
model:
|
|
396
|
-
engine:
|
|
384
|
+
sessionId: opts.resumeSessionId || null,
|
|
385
|
+
model: opts.model || "auto",
|
|
386
|
+
engine: opts.engine || KIRO_DEFAULTS.engine,
|
|
397
387
|
lastInputTokens: null,
|
|
398
|
-
contextWindow:
|
|
388
|
+
contextWindow: opts.contextWindow || null,
|
|
399
389
|
done: false,
|
|
400
390
|
aborted: false,
|
|
401
391
|
loopStarted: false,
|
|
402
392
|
loadingSession: false,
|
|
403
|
-
// per-turn block tracking (reset each turn)
|
|
404
393
|
textBlockOpen: false,
|
|
405
394
|
textBlockId: null,
|
|
406
395
|
thinkBlockOpen: false,
|
|
@@ -409,6 +398,21 @@ function createKiroQueryHandle(acp, queryOpts) {
|
|
|
409
398
|
toolMeta: {},
|
|
410
399
|
toolContent: {},
|
|
411
400
|
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// --- QueryHandle ---
|
|
404
|
+
|
|
405
|
+
function createKiroQueryHandle(acp, queryOpts) {
|
|
406
|
+
var abortController = queryOpts.abortController;
|
|
407
|
+
var systemPrompt = queryOpts.systemPrompt || "";
|
|
408
|
+
var canUseTool = queryOpts.canUseTool || null;
|
|
409
|
+
var onFinished = queryOpts.onFinished || null;
|
|
410
|
+
|
|
411
|
+
function isCancelled() {
|
|
412
|
+
return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
var state = createEventState(queryOpts);
|
|
412
416
|
|
|
413
417
|
// Async iterator plumbing
|
|
414
418
|
var eventBuffer = [];
|
|
@@ -864,6 +868,11 @@ function createKiroAdapter(opts) {
|
|
|
864
868
|
slashCommands: skillNames || [],
|
|
865
869
|
fastModeState: null,
|
|
866
870
|
capabilities: {
|
|
871
|
+
effort: false,
|
|
872
|
+
fork: false,
|
|
873
|
+
rollback: false,
|
|
874
|
+
sessionListing: false,
|
|
875
|
+
sessionRename: false,
|
|
867
876
|
thinking: true,
|
|
868
877
|
betas: false,
|
|
869
878
|
rewind: false,
|
|
@@ -872,7 +881,7 @@ function createKiroAdapter(opts) {
|
|
|
872
881
|
elicitation: false,
|
|
873
882
|
fileCheckpointing: false,
|
|
874
883
|
contextCompacting: true,
|
|
875
|
-
toolPolicy: ["ask"
|
|
884
|
+
toolPolicy: ["ask"],
|
|
876
885
|
},
|
|
877
886
|
};
|
|
878
887
|
}
|
|
@@ -1153,4 +1162,9 @@ function createKiroAdapter(opts) {
|
|
|
1153
1162
|
|
|
1154
1163
|
module.exports = {
|
|
1155
1164
|
createKiroAdapter: createKiroAdapter,
|
|
1165
|
+
contractTestKit: {
|
|
1166
|
+
createEventState: createEventState,
|
|
1167
|
+
createQueryHandle: createKiroQueryHandle,
|
|
1168
|
+
normalizeEvent: flattenUpdate,
|
|
1169
|
+
},
|
|
1156
1170
|
};
|
package/lib/yoke/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var iface = require("./interface");
|
|
5
5
|
var instructions = require("./instructions");
|
|
6
|
+
var vendorRegistry = require("./vendor-registry");
|
|
6
7
|
var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
|
|
7
8
|
var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
|
|
8
9
|
var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
|
|
@@ -45,6 +46,8 @@ function wrapCreateQuery(adapter, defaultCwd) {
|
|
|
45
46
|
* @returns {Adapter}
|
|
46
47
|
*/
|
|
47
48
|
function createAdapter(opts) {
|
|
49
|
+
// Adding a vendor here also requires a registry entry and an update to the
|
|
50
|
+
// completeness list in test/yoke-vendor-registry.test.js.
|
|
48
51
|
var vendor = (opts && opts.vendor) || "claude";
|
|
49
52
|
var adapter;
|
|
50
53
|
if (vendor === "claude") {
|
|
@@ -284,7 +287,12 @@ function createAdapters(opts) {
|
|
|
284
287
|
var auth = { claude: false, codex: false, kiro: false };
|
|
285
288
|
var adapters = {};
|
|
286
289
|
|
|
287
|
-
|
|
290
|
+
function supportsConfiguredIsolation(vendor) {
|
|
291
|
+
var info = vendorRegistry.getVendorInfo(vendor);
|
|
292
|
+
return !opts.osUsers || !info || info.osUserIsolation;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (installed.claude && supportsConfiguredIsolation("claude")) {
|
|
288
296
|
try {
|
|
289
297
|
if (!_sharedClaudeAdapter) {
|
|
290
298
|
_sharedClaudeAdapter = createAdapter({ vendor: "claude", cwd: opts.cwd });
|
|
@@ -301,7 +309,7 @@ function createAdapters(opts) {
|
|
|
301
309
|
}
|
|
302
310
|
}
|
|
303
311
|
|
|
304
|
-
if (installed.codex) {
|
|
312
|
+
if (installed.codex && supportsConfiguredIsolation("codex")) {
|
|
305
313
|
try {
|
|
306
314
|
adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug });
|
|
307
315
|
auth.codex = true;
|
|
@@ -311,7 +319,8 @@ function createAdapters(opts) {
|
|
|
311
319
|
}
|
|
312
320
|
}
|
|
313
321
|
|
|
314
|
-
|
|
322
|
+
var kiroInfo = vendorRegistry.getVendorInfo("kiro");
|
|
323
|
+
if (installed.kiro && supportsConfiguredIsolation("kiro")) {
|
|
315
324
|
try {
|
|
316
325
|
adapters.kiro = createAdapter({ vendor: "kiro", cwd: opts.cwd, slug: opts.slug });
|
|
317
326
|
auth.kiro = true;
|
|
@@ -320,7 +329,7 @@ function createAdapters(opts) {
|
|
|
320
329
|
console.error("[yoke] Failed to create adapter for kiro:", e.message);
|
|
321
330
|
}
|
|
322
331
|
} else if (installed.kiro && opts.osUsers) {
|
|
323
|
-
console.log("[yoke]
|
|
332
|
+
console.log("[yoke] " + kiroInfo.displayName + " adapter disabled: OS-user isolation requires per-user spawning");
|
|
324
333
|
}
|
|
325
334
|
|
|
326
335
|
return { adapters: adapters, auth: auth };
|
|
@@ -334,8 +343,9 @@ function createAdapters(opts) {
|
|
|
334
343
|
async function lazyCreateAdapter(adapters, vendor, opts) {
|
|
335
344
|
opts = opts || {};
|
|
336
345
|
|
|
337
|
-
|
|
338
|
-
|
|
346
|
+
var vendorInfo = vendorRegistry.getVendorInfo(vendor);
|
|
347
|
+
if (vendorInfo && !vendorInfo.osUserIsolation && (opts.osUsers || opts.linuxUser)) {
|
|
348
|
+
console.log("[yoke] Refusing lazy " + vendorInfo.displayName + " adapter creation for OS-isolated user " + (opts.linuxUser || "unknown"));
|
|
339
349
|
return null;
|
|
340
350
|
}
|
|
341
351
|
|
|
@@ -365,6 +375,8 @@ module.exports = {
|
|
|
365
375
|
checkAuth: checkAuth,
|
|
366
376
|
checkInstalled: checkInstalled,
|
|
367
377
|
invalidateAuthCache: invalidateAuthCache,
|
|
378
|
+
VENDOR_REGISTRY: vendorRegistry.VENDOR_REGISTRY,
|
|
379
|
+
getVendorInfo: vendorRegistry.getVendorInfo,
|
|
368
380
|
TOOL_POLICIES: iface.TOOL_POLICIES,
|
|
369
381
|
validateAdapter: iface.validateAdapter,
|
|
370
382
|
validateQueryHandle: iface.validateQueryHandle,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Static, init-free facts about each vendor YOKE supports. Host code that
|
|
2
|
+
// needs metadata before adapter initialization should read it from here.
|
|
3
|
+
// Do not import adapters into this module.
|
|
4
|
+
|
|
5
|
+
var VENDOR_REGISTRY = {
|
|
6
|
+
claude: {
|
|
7
|
+
displayName: "Claude Code",
|
|
8
|
+
loginCommand: "claude login",
|
|
9
|
+
binaryName: "claude",
|
|
10
|
+
avatar: "/claude-code-avatar.png",
|
|
11
|
+
sessionModes: ["gui", "tui"],
|
|
12
|
+
osUserIsolation: true,
|
|
13
|
+
usageDashboard: {
|
|
14
|
+
icon: "/claude-code-avatar.png",
|
|
15
|
+
alt: "Claude Code",
|
|
16
|
+
href: "https://claude.ai/settings/usage",
|
|
17
|
+
title: "Check usage on claude.ai",
|
|
18
|
+
},
|
|
19
|
+
rateLimitTracking: true,
|
|
20
|
+
},
|
|
21
|
+
codex: {
|
|
22
|
+
displayName: "Codex",
|
|
23
|
+
loginCommand: "codex login --device-auth",
|
|
24
|
+
binaryName: "codex",
|
|
25
|
+
avatar: "/codex-avatar.png",
|
|
26
|
+
sessionModes: ["gui"],
|
|
27
|
+
osUserIsolation: true,
|
|
28
|
+
usageDashboard: {
|
|
29
|
+
icon: "/codex-avatar.png",
|
|
30
|
+
alt: "Codex",
|
|
31
|
+
href: "https://chatgpt.com/admin/usage",
|
|
32
|
+
title: "Check usage on ChatGPT",
|
|
33
|
+
},
|
|
34
|
+
rateLimitTracking: true,
|
|
35
|
+
},
|
|
36
|
+
kiro: {
|
|
37
|
+
displayName: "Kiro CLI",
|
|
38
|
+
loginCommand: "kiro-cli login",
|
|
39
|
+
binaryName: "kiro-cli",
|
|
40
|
+
avatar: "/kiro-avatar.svg",
|
|
41
|
+
sessionModes: ["gui"],
|
|
42
|
+
osUserIsolation: false,
|
|
43
|
+
usageDashboard: null,
|
|
44
|
+
rateLimitTracking: false,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function getVendorInfo(vendor) {
|
|
49
|
+
return VENDOR_REGISTRY[vendor] || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
VENDOR_REGISTRY: VENDOR_REGISTRY,
|
|
54
|
+
getVendorInfo: getVendorInfo,
|
|
55
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "2.47.0-beta.
|
|
3
|
+
"version": "2.47.0-beta.3",
|
|
4
4
|
"description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clay-server": "./bin/cli.js",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"homepage": "https://github.com/chadbyte/clay#readme",
|
|
50
50
|
"author": "Chad",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
52
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.233",
|
|
53
53
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
54
|
-
"@openai/codex": "^0.
|
|
54
|
+
"@openai/codex": "^0.147.0",
|
|
55
55
|
"imapflow": "^1.3.1",
|
|
56
56
|
"nodemailer": "^6.10.1",
|
|
57
57
|
"qrcode-terminal": "^0.12.0",
|