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
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
|
};
|
package/lib/daemon.js
CHANGED
|
@@ -648,6 +648,29 @@ var relay = createServer({
|
|
|
648
648
|
}
|
|
649
649
|
return { ok: false, error: "Project not found" };
|
|
650
650
|
},
|
|
651
|
+
onGetProjectLastVendor: function (slug) {
|
|
652
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
653
|
+
if (config.projects[i].slug === slug) {
|
|
654
|
+
return { vendor: config.projects[i].lastVendor || null };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return { vendor: null };
|
|
658
|
+
},
|
|
659
|
+
onSetProjectLastVendor: function (slug, vendor) {
|
|
660
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
661
|
+
if (config.projects[i].slug === slug) {
|
|
662
|
+
if (config.projects[i].lastVendor === vendor) return { ok: true };
|
|
663
|
+
if (vendor) {
|
|
664
|
+
config.projects[i].lastVendor = vendor;
|
|
665
|
+
} else {
|
|
666
|
+
delete config.projects[i].lastVendor;
|
|
667
|
+
}
|
|
668
|
+
saveConfig(config);
|
|
669
|
+
return { ok: true };
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return { ok: false, error: "Project not found" };
|
|
673
|
+
},
|
|
651
674
|
onGetProjectMcpServers: function (slug) {
|
|
652
675
|
for (var i = 0; i < config.projects.length; i++) {
|
|
653
676
|
if (config.projects[i].slug === slug) {
|
|
@@ -968,21 +991,28 @@ var relay = createServer({
|
|
|
968
991
|
},
|
|
969
992
|
});
|
|
970
993
|
|
|
971
|
-
|
|
972
|
-
|
|
994
|
+
// Reclaim idle out-of-process agent runtimes. Any adapter that owns a child
|
|
995
|
+
// process may implement the optional shutdownIfIdle(ms); adapters without one
|
|
996
|
+
// (Claude, which runs in-process or under sdk-worker) are simply skipped.
|
|
997
|
+
// The CLAY_CODEX_* names are kept as aliases for backwards compatibility.
|
|
998
|
+
var IDLE_MS = Number(process.env.CLAY_ADAPTER_IDLE_MS) || Number(process.env.CLAY_CODEX_IDLE_MS) || 5 * 60 * 1000;
|
|
999
|
+
var REAP_MS = Number(process.env.CLAY_ADAPTER_REAPER_MS) || Number(process.env.CLAY_CODEX_REAPER_MS) || 60 * 1000;
|
|
973
1000
|
var reaperHandle = setInterval(function () {
|
|
974
1001
|
if (!relay || typeof relay.forEachProject !== "function") return;
|
|
975
1002
|
relay.forEachProject(function (ctx) {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1003
|
+
if (!ctx || !ctx.adapters) return;
|
|
1004
|
+
Object.keys(ctx.adapters).forEach(function (vendor) {
|
|
1005
|
+
try {
|
|
1006
|
+
var adapter = ctx.adapters[vendor];
|
|
1007
|
+
if (!adapter || typeof adapter.shutdownIfIdle !== "function") return;
|
|
1008
|
+
var result = adapter.shutdownIfIdle(IDLE_MS);
|
|
979
1009
|
if (result && typeof result.catch === "function") {
|
|
980
1010
|
result.catch(function (e) {
|
|
981
|
-
console.error("[daemon]
|
|
1011
|
+
console.error("[daemon] " + vendor + " idle reclaim failed:", e && e.message ? e.message : e);
|
|
982
1012
|
});
|
|
983
1013
|
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
1014
|
+
} catch (e) {}
|
|
1015
|
+
});
|
|
986
1016
|
});
|
|
987
1017
|
}, REAP_MS);
|
|
988
1018
|
if (reaperHandle && typeof reaperHandle.unref === "function") {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Kiro-specific default values. Single source of truth — do not duplicate
|
|
2
|
+
// elsewhere. Consumed by the server-side vendor plumbing and sent to clients
|
|
3
|
+
// via the config state so the UI renders the right controls.
|
|
4
|
+
|
|
5
|
+
var KIRO_DEFAULTS = {
|
|
6
|
+
// Kiro CLI 2.18.1 exposes the next-generation agent as the v3 engine. Its
|
|
7
|
+
// general-purpose mode id is "vibe" (displayed as "Default").
|
|
8
|
+
engine: "v3",
|
|
9
|
+
mode: "vibe",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function getKiroConfig(sm) {
|
|
13
|
+
return {
|
|
14
|
+
engine: (sm && sm.kiroEngine) || KIRO_DEFAULTS.engine,
|
|
15
|
+
mode: (sm && sm.kiroMode) || KIRO_DEFAULTS.mode,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
KIRO_DEFAULTS: KIRO_DEFAULTS,
|
|
21
|
+
getKiroConfig: getKiroConfig,
|
|
22
|
+
};
|
|
@@ -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.
|
|
@@ -143,6 +144,7 @@ function attachConnection(ctx) {
|
|
|
143
144
|
sendTo(ws, { type: "model_info", model: sm.currentModel, models: initialModels, vendor: initialVendor, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
144
145
|
}
|
|
145
146
|
sendTo(ws, { type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
147
|
+
sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
|
|
146
148
|
sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
|
|
147
149
|
sendTo(ws, { type: "term_list", terminals: tm.list() });
|
|
148
150
|
// Context sources sent after session is resolved (per-session storage)
|
|
@@ -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;
|
|
@@ -495,6 +503,17 @@ function attachSessions(ctx) {
|
|
|
495
503
|
newSess = sm.createSession(sessionOpts, ws);
|
|
496
504
|
}
|
|
497
505
|
ws._clayActiveSession = newSess.localId;
|
|
506
|
+
// Remember the vendor only when the client asked for one explicitly.
|
|
507
|
+
// new_session without a vendor (mate sidebar, notification banner,
|
|
508
|
+
// debate) falls through to the default adapter and must not clobber
|
|
509
|
+
// the user's remembered pick.
|
|
510
|
+
if (msg.vendor && sm.lastVendor !== msg.vendor) {
|
|
511
|
+
sm.lastVendor = msg.vendor;
|
|
512
|
+
if (typeof opts.onSetProjectLastVendor === "function") {
|
|
513
|
+
opts.onSetProjectLastVendor(slug, msg.vendor);
|
|
514
|
+
}
|
|
515
|
+
send({ type: "last_vendor", vendor: msg.vendor });
|
|
516
|
+
}
|
|
498
517
|
// Apply project-level email defaults to new session
|
|
499
518
|
if (typeof ctx._email === "object" && ctx._email.getEmailDefaults) {
|
|
500
519
|
var emailDefaults = ctx._email.getEmailDefaults();
|
|
@@ -614,7 +633,7 @@ function attachSessions(ctx) {
|
|
|
614
633
|
// for every viewer; only cold sessions follow the clicker's
|
|
615
634
|
// claudeOpenMode pref. Nothing is spawned here.
|
|
616
635
|
var xmTarget = sm.sessions.get(msg.id);
|
|
617
|
-
if (xmTarget && (xmTarget.vendor
|
|
636
|
+
if (xmTarget && (!xmTarget.vendor || vendorSupportsTui(xmTarget.vendor))) {
|
|
618
637
|
// Single source of truth: live runtime wins (tui stays tui, gui stays
|
|
619
638
|
// gui); only cold sessions follow the viewer's pref. No PTY is spawned
|
|
620
639
|
// on switch - born-TUI resumes lazily via the Resume bar
|
|
@@ -667,7 +686,7 @@ function attachSessions(ctx) {
|
|
|
667
686
|
if (msg.type === "resume_tui_session") {
|
|
668
687
|
if (msg.id && sm.sessions.has(msg.id)) {
|
|
669
688
|
var rtTarget = sm.sessions.get(msg.id);
|
|
670
|
-
var rtOk = rtTarget && (rtTarget.vendor
|
|
689
|
+
var rtOk = rtTarget && (!rtTarget.vendor || vendorSupportsTui(rtTarget.vendor)) &&
|
|
671
690
|
rtTarget.cliSessionId && tm;
|
|
672
691
|
if (rtOk) {
|
|
673
692
|
if (usersModule.isMultiUser() && ws._clayUser &&
|
|
@@ -695,7 +714,7 @@ function attachSessions(ctx) {
|
|
|
695
714
|
if (msg.type === "suspend_tui_session") {
|
|
696
715
|
if (msg.id && sm.sessions.has(msg.id)) {
|
|
697
716
|
var stTarget = sm.sessions.get(msg.id);
|
|
698
|
-
var stOk = stTarget && (stTarget.vendor
|
|
717
|
+
var stOk = stTarget && (!stTarget.vendor || vendorSupportsTui(stTarget.vendor));
|
|
699
718
|
if (stOk && (!usersModule.isMultiUser() || !ws._clayUser ||
|
|
700
719
|
usersModule.canAccessSession(ws._clayUser.id, stTarget, { visibility: "public" }))) {
|
|
701
720
|
if (tm) {
|
|
@@ -726,7 +745,7 @@ function attachSessions(ctx) {
|
|
|
726
745
|
var tprId = msg.id;
|
|
727
746
|
var tprSess = (tprId && sm.sessions.has(tprId)) ? sm.sessions.get(tprId) : null;
|
|
728
747
|
if (!tprSess || !tprSess.cliSessionId || tprSess.mode !== "tui") return true;
|
|
729
|
-
if (tprSess.vendor && tprSess.vendor
|
|
748
|
+
if (tprSess.vendor && !vendorSupportsTui(tprSess.vendor)) return true;
|
|
730
749
|
if (usersModule.isMultiUser() && ws._clayUser
|
|
731
750
|
&& !usersModule.canAccessSession(ws._clayUser.id, tprSess, { visibility: "public" })) {
|
|
732
751
|
return true;
|
|
@@ -1073,7 +1092,7 @@ function attachSessions(ctx) {
|
|
|
1073
1092
|
|
|
1074
1093
|
// Codex-specific settings (stored on sessionManager, passed to adapter via adapterOptions)
|
|
1075
1094
|
if (msg.type === "set_codex_approval") {
|
|
1076
|
-
sm.codexApproval = msg.approval
|
|
1095
|
+
sm.codexApproval = getCodexConfig({ codexApproval: msg.approval }).approval;
|
|
1077
1096
|
send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
|
|
1078
1097
|
return true;
|
|
1079
1098
|
}
|
package/lib/project.js
CHANGED
|
@@ -166,7 +166,7 @@ function createProjectContext(opts) {
|
|
|
166
166
|
var sessionTitleMigrationScheduled = false;
|
|
167
167
|
|
|
168
168
|
// --- YOKE adapters (multi-vendor, lazy init) ---
|
|
169
|
-
var _yokeState = yoke.createAdapters({ cwd: cwd, slug: slug });
|
|
169
|
+
var _yokeState = yoke.createAdapters({ cwd: cwd, slug: slug, osUsers: osUsers });
|
|
170
170
|
var adapters = _yokeState.adapters;
|
|
171
171
|
var defaultVendor = adapters.claude ? "claude" : Object.keys(adapters)[0] || "claude";
|
|
172
172
|
var adapter = adapters[defaultVendor] || null;
|
|
@@ -428,6 +428,12 @@ function createProjectContext(opts) {
|
|
|
428
428
|
var _srvEffort = typeof opts.onGetServerDefaultEffort === "function" ? opts.onGetServerDefaultEffort() : null;
|
|
429
429
|
sm.currentEffort = (_projEffort && _projEffort.effort) || (_srvEffort && _srvEffort.effort) || "medium";
|
|
430
430
|
|
|
431
|
+
// Last vendor the user started a session with in this project. Seeds the
|
|
432
|
+
// sidebar's "New session" button so it defaults to whatever they used last
|
|
433
|
+
// instead of always launching Claude.
|
|
434
|
+
var _projLastVendor = typeof opts.onGetProjectLastVendor === "function" ? opts.onGetProjectLastVendor(slug) : null;
|
|
435
|
+
sm.lastVendor = (_projLastVendor && _projLastVendor.vendor) || null;
|
|
436
|
+
|
|
431
437
|
var _projModel = typeof opts.onGetProjectDefaultModel === "function" ? opts.onGetProjectDefaultModel(slug) : null;
|
|
432
438
|
var _srvModel = typeof opts.onGetServerDefaultModel === "function" ? opts.onGetServerDefaultModel() : null;
|
|
433
439
|
sm._savedDefaultModel = (_projModel && _projModel.model) || (_srvModel && _srvModel.model) || null;
|
|
@@ -897,27 +903,36 @@ function createProjectContext(opts) {
|
|
|
897
903
|
(async function() {
|
|
898
904
|
if (msg.vendor) {
|
|
899
905
|
try {
|
|
906
|
+
var modelLinuxUser = getLinuxUserForWs(ws);
|
|
900
907
|
var vendorAdapter = adapters[msg.vendor] || null;
|
|
901
908
|
if (!vendorAdapter) {
|
|
902
909
|
vendorAdapter = await yoke.lazyCreateAdapter(adapters, msg.vendor, {
|
|
903
910
|
cwd: cwd,
|
|
911
|
+
linuxUser: modelLinuxUser || undefined,
|
|
904
912
|
clayPort: serverPort,
|
|
905
913
|
clayTls: serverTls,
|
|
906
914
|
clayAuthToken: serverAuthToken,
|
|
907
915
|
slug: slug,
|
|
908
916
|
});
|
|
909
|
-
}
|
|
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") {
|
|
910
921
|
// Init warms the adapter, but a slow/failed init must not block
|
|
911
922
|
// model listing (e.g. Codex models are a fixed list). Keep going
|
|
912
923
|
// to supportedModels() even if init throws.
|
|
913
924
|
try {
|
|
914
|
-
await vendorAdapter.init({
|
|
925
|
+
var readyResult = await vendorAdapter.init({
|
|
915
926
|
cwd: cwd,
|
|
916
927
|
clayPort: serverPort,
|
|
917
928
|
clayTls: serverTls,
|
|
918
929
|
clayAuthToken: serverAuthToken,
|
|
919
930
|
slug: slug,
|
|
920
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;
|
|
921
936
|
} catch (e) {
|
|
922
937
|
console.error("[project] " + msg.vendor + " init failed (continuing to model list):", e.message || e);
|
|
923
938
|
}
|
|
@@ -949,7 +964,8 @@ function createProjectContext(opts) {
|
|
|
949
964
|
}
|
|
950
965
|
}
|
|
951
966
|
}
|
|
952
|
-
|
|
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 || [] });
|
|
953
969
|
})();
|
|
954
970
|
return;
|
|
955
971
|
}
|
|
@@ -1528,14 +1544,30 @@ function createProjectContext(opts) {
|
|
|
1528
1544
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1529
1545
|
} catch (e) {}
|
|
1530
1546
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1547
|
+
// Shut down every adapter that owns a child process. shutdown() is
|
|
1548
|
+
// optional on the YOKE contract, so adapters without one are skipped.
|
|
1549
|
+
var shutdowns = [];
|
|
1550
|
+
if (adapters) {
|
|
1551
|
+
Object.keys(adapters).forEach(function(vendor) {
|
|
1552
|
+
var adapter = adapters[vendor];
|
|
1553
|
+
if (!adapter || typeof adapter.shutdown !== "function") return;
|
|
1554
|
+
// Shared adapter instances (e.g. Claude) are reused across projects,
|
|
1555
|
+
// so tearing one down here would kill other projects' sessions.
|
|
1556
|
+
if (adapter.shared) return;
|
|
1557
|
+
try {
|
|
1558
|
+
shutdowns.push(Promise.resolve(adapter.shutdown()).catch(function(err) {
|
|
1559
|
+
console.error("[project] " + vendor + " shutdown failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1560
|
+
return false;
|
|
1561
|
+
}));
|
|
1562
|
+
} catch (err) {
|
|
1563
|
+
console.error("[project] " + vendor + " shutdown threw for " + slug + ":", err && err.message ? err.message : err);
|
|
1564
|
+
}
|
|
1536
1565
|
});
|
|
1537
1566
|
}
|
|
1538
|
-
return
|
|
1567
|
+
if (!shutdowns.length) return Promise.resolve(true);
|
|
1568
|
+
return Promise.all(shutdowns).then(function(results) {
|
|
1569
|
+
return results.every(function(r) { return r !== false; });
|
|
1570
|
+
});
|
|
1539
1571
|
}
|
|
1540
1572
|
|
|
1541
1573
|
// --- Status info ---
|
package/lib/public/app.js
CHANGED
|
@@ -310,6 +310,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
310
310
|
// panels
|
|
311
311
|
currentModel: "",
|
|
312
312
|
currentModels: [],
|
|
313
|
+
// Project's last-used vendor; seeds the sidebar's "New session" button.
|
|
314
|
+
lastVendor: "",
|
|
315
|
+
// Static adapter metadata sent before any vendor is initialized.
|
|
316
|
+
vendorInfo: {},
|
|
317
|
+
// How Claude sessions open: "gui" (default) or "tui". The server sends
|
|
318
|
+
// claude_open_mode_changed on connect; this seeds it beforehand so the
|
|
319
|
+
// new-session menu doesn't flash the TUI-only entry.
|
|
320
|
+
claudeOpenMode: "gui",
|
|
313
321
|
currentMode: "default",
|
|
314
322
|
currentEffort: "medium",
|
|
315
323
|
currentBetas: [],
|
|
@@ -358,18 +358,16 @@
|
|
|
358
358
|
background: rgba(var(--overlay-rgb), 0.06);
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
/*
|
|
362
|
-
|
|
363
|
-
same way the desktop sidebar already does. */
|
|
361
|
+
/* Split "New session" row: main action on the last-used vendor + a chevron
|
|
362
|
+
that expands .mobile-vendor-list below it. Mirrors the desktop sidebar. */
|
|
364
363
|
.mobile-session-new-row {
|
|
365
364
|
display: flex;
|
|
366
365
|
gap: 6px;
|
|
367
366
|
padding: 6px 8px 8px;
|
|
368
|
-
border-bottom: 1px solid var(--border-subtle);
|
|
369
367
|
}
|
|
370
368
|
|
|
371
|
-
.mobile-session-new-row .mobile-session-new-
|
|
372
|
-
flex: 1 1
|
|
369
|
+
.mobile-session-new-row .mobile-session-new-main {
|
|
370
|
+
flex: 1 1 auto;
|
|
373
371
|
width: auto;
|
|
374
372
|
margin-bottom: 0;
|
|
375
373
|
padding: 10px 12px;
|
|
@@ -377,15 +375,61 @@
|
|
|
377
375
|
border-radius: 8px;
|
|
378
376
|
background: rgba(var(--overlay-rgb), 0.03);
|
|
379
377
|
color: var(--text);
|
|
380
|
-
justify-content: center;
|
|
381
378
|
gap: 8px;
|
|
382
379
|
}
|
|
383
|
-
.mobile-session-new-row .mobile-session-new-
|
|
380
|
+
.mobile-session-new-row .mobile-session-new-chevron {
|
|
381
|
+
flex: 0 0 auto;
|
|
382
|
+
width: 44px;
|
|
383
|
+
margin-bottom: 0;
|
|
384
|
+
padding: 10px 0;
|
|
385
|
+
border: 1px solid var(--border-subtle);
|
|
386
|
+
border-radius: 8px;
|
|
387
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
388
|
+
color: var(--text-muted);
|
|
389
|
+
justify-content: center;
|
|
390
|
+
gap: 0;
|
|
391
|
+
}
|
|
392
|
+
.mobile-session-new-row .mobile-session-new-chevron.expanded svg {
|
|
393
|
+
transform: rotate(180deg);
|
|
394
|
+
}
|
|
395
|
+
.mobile-session-new-row .mobile-session-new-main:active,
|
|
396
|
+
.mobile-session-new-row .mobile-session-new-chevron:active {
|
|
397
|
+
background: rgba(var(--overlay-rgb), 0.1);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.mobile-vendor-list {
|
|
401
|
+
display: flex;
|
|
402
|
+
flex-direction: column;
|
|
403
|
+
gap: 4px;
|
|
404
|
+
padding: 0 8px 8px;
|
|
384
405
|
border-bottom: 1px solid var(--border-subtle);
|
|
385
406
|
}
|
|
386
|
-
.mobile-
|
|
407
|
+
.mobile-vendor-list.hidden { display: none; }
|
|
408
|
+
|
|
409
|
+
.mobile-vendor-list .mobile-session-new-vendor {
|
|
410
|
+
width: 100%;
|
|
411
|
+
margin-bottom: 0;
|
|
412
|
+
padding: 10px 12px;
|
|
413
|
+
border: 1px solid var(--border-subtle);
|
|
414
|
+
border-radius: 8px;
|
|
415
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
416
|
+
color: var(--text);
|
|
417
|
+
font-weight: 500;
|
|
418
|
+
gap: 8px;
|
|
419
|
+
}
|
|
420
|
+
.mobile-vendor-list .mobile-session-new-vendor.active { font-weight: 700; }
|
|
421
|
+
/* Uninstalled vendors stay tappable: the tap opens the vendor's homepage. */
|
|
422
|
+
.mobile-vendor-list .mobile-session-new-vendor.disabled { opacity: 0.45; }
|
|
423
|
+
.mobile-vendor-list .mobile-session-new-vendor:active {
|
|
387
424
|
background: rgba(var(--overlay-rgb), 0.1);
|
|
388
425
|
}
|
|
426
|
+
.mobile-vendor-note {
|
|
427
|
+
margin-left: auto;
|
|
428
|
+
font-size: 12px;
|
|
429
|
+
font-weight: 500;
|
|
430
|
+
color: var(--text-dimmer);
|
|
431
|
+
}
|
|
432
|
+
|
|
389
433
|
.mobile-session-new-icon {
|
|
390
434
|
width: 18px;
|
|
391
435
|
height: 18px;
|
|
@@ -993,7 +993,7 @@
|
|
|
993
993
|
|
|
994
994
|
.session-top-actions {
|
|
995
995
|
display: grid;
|
|
996
|
-
grid-template-columns:
|
|
996
|
+
grid-template-columns: minmax(0, 1fr);
|
|
997
997
|
gap: 2px;
|
|
998
998
|
padding: 0 0 2px;
|
|
999
999
|
}
|
|
@@ -1240,6 +1240,29 @@
|
|
|
1240
1240
|
.session-ctx-item.session-ctx-delete { color: var(--error); }
|
|
1241
1241
|
.session-ctx-item.session-ctx-delete:hover { background: var(--error-8); }
|
|
1242
1242
|
|
|
1243
|
+
/* --- New-session vendor picker --- */
|
|
1244
|
+
.session-new-menu { min-width: 200px; }
|
|
1245
|
+
|
|
1246
|
+
.session-ctx-sep {
|
|
1247
|
+
height: 1px;
|
|
1248
|
+
margin: 4px 0;
|
|
1249
|
+
background: var(--border);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
.session-new-vendor .session-new-vendor-name { flex: 1 1 auto; min-width: 0; }
|
|
1253
|
+
.session-new-vendor.active { color: var(--text); font-weight: 600; }
|
|
1254
|
+
|
|
1255
|
+
/* Uninstalled vendors stay clickable on purpose: the click opens the
|
|
1256
|
+
vendor's homepage instead of creating a session. */
|
|
1257
|
+
.session-new-vendor.disabled { opacity: 0.45; }
|
|
1258
|
+
.session-new-vendor.disabled:hover { opacity: 0.7; }
|
|
1259
|
+
|
|
1260
|
+
.session-new-vendor-note {
|
|
1261
|
+
font-size: 11px;
|
|
1262
|
+
color: var(--text-dimmer);
|
|
1263
|
+
white-space: nowrap;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1243
1266
|
/* --- Session inline rename --- */
|
|
1244
1267
|
.session-rename-input {
|
|
1245
1268
|
width: 100%;
|
|
@@ -49,69 +49,10 @@ body.tui-suspended #tui-resume-bar {
|
|
|
49
49
|
.tui-resume-hint { display: none; }
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/* Policy
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
display: flex;
|
|
57
|
-
align-items: center;
|
|
58
|
-
gap: 8px;
|
|
59
|
-
flex-shrink: 0;
|
|
60
|
-
padding: 6px 10px;
|
|
61
|
-
margin-bottom: 4px;
|
|
62
|
-
border-radius: 6px;
|
|
63
|
-
background: rgba(80, 250, 123, 0.06);
|
|
64
|
-
border: 1px solid rgba(80, 250, 123, 0.15);
|
|
65
|
-
color: #b8b8b8;
|
|
66
|
-
font-size: 11px;
|
|
67
|
-
line-height: 1.3;
|
|
68
|
-
font-family: "Roboto Mono", Menlo, Monaco, monospace;
|
|
69
|
-
}
|
|
70
|
-
.tui-policy-icon {
|
|
71
|
-
color: #50fa7b;
|
|
72
|
-
font-size: 9px;
|
|
73
|
-
line-height: 1;
|
|
74
|
-
flex-shrink: 0;
|
|
75
|
-
}
|
|
76
|
-
.tui-policy-text {
|
|
77
|
-
flex: 1 1 auto;
|
|
78
|
-
min-width: 0;
|
|
79
|
-
}
|
|
80
|
-
.tui-policy-learn-more {
|
|
81
|
-
flex-shrink: 0;
|
|
82
|
-
padding: 2px 8px;
|
|
83
|
-
border: 1px solid rgba(80, 250, 123, 0.3);
|
|
84
|
-
border-radius: 4px;
|
|
85
|
-
background: transparent;
|
|
86
|
-
color: #50fa7b;
|
|
87
|
-
font: inherit;
|
|
88
|
-
font-size: 10px;
|
|
89
|
-
cursor: pointer;
|
|
90
|
-
transition: background 0.15s, border-color 0.15s;
|
|
91
|
-
}
|
|
92
|
-
.tui-policy-learn-more:hover {
|
|
93
|
-
background: rgba(80, 250, 123, 0.1);
|
|
94
|
-
border-color: rgba(80, 250, 123, 0.5);
|
|
95
|
-
}
|
|
96
|
-
.tui-policy-dismiss {
|
|
97
|
-
flex-shrink: 0;
|
|
98
|
-
width: 18px;
|
|
99
|
-
height: 18px;
|
|
100
|
-
padding: 0;
|
|
101
|
-
border: none;
|
|
102
|
-
border-radius: 4px;
|
|
103
|
-
background: transparent;
|
|
104
|
-
color: #888;
|
|
105
|
-
font-size: 14px;
|
|
106
|
-
line-height: 1;
|
|
107
|
-
cursor: pointer;
|
|
108
|
-
}
|
|
109
|
-
.tui-policy-dismiss:hover {
|
|
110
|
-
background: rgba(255, 255, 255, 0.06);
|
|
111
|
-
color: #ddd;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/* Policy info modal. Themed via CSS vars so it follows light/dark. */
|
|
52
|
+
/* Policy info modal. Themed via CSS vars so it follows light/dark.
|
|
53
|
+
The modal itself is gone; .tui-policy-modal-* rules stay because
|
|
54
|
+
What's New article bodies (lib/whats-new-content.js) still use the
|
|
55
|
+
-links class for their footer links. */
|
|
115
56
|
.tui-policy-modal-backdrop {
|
|
116
57
|
position: fixed;
|
|
117
58
|
inset: 0;
|
package/lib/public/index.html
CHANGED
|
@@ -513,6 +513,10 @@
|
|
|
513
513
|
<img src="/codex-avatar.png" class="vendor-toggle-icon" alt="Codex">
|
|
514
514
|
<span class="vendor-toggle-label">Codex</span>
|
|
515
515
|
</button>
|
|
516
|
+
<button id="vendor-btn-kiro" class="vendor-toggle-btn" data-vendor="kiro">
|
|
517
|
+
<img src="/kiro-avatar.svg" class="vendor-toggle-icon" alt="Kiro">
|
|
518
|
+
<span class="vendor-toggle-label">Kiro CLI</span>
|
|
519
|
+
</button>
|
|
516
520
|
</div>
|
|
517
521
|
<div id="active-vendor-indicator" class="hidden" title="Session vendor">
|
|
518
522
|
<img id="active-vendor-icon" alt="">
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Kiro CLI">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="kiroBg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#8b5cf6"/>
|
|
5
|
+
<stop offset="1" stop-color="#6d28d9"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="128" height="128" rx="28" fill="url(#kiroBg)"/>
|
|
9
|
+
<g fill="#ffffff">
|
|
10
|
+
<rect x="34" y="34" width="12" height="60" rx="3"/>
|
|
11
|
+
<rect x="52" y="60" width="42" height="12" rx="3" transform="rotate(-45 73 66)"/>
|
|
12
|
+
<rect x="52" y="60" width="42" height="12" rx="3" transform="rotate(45 73 66)"/>
|
|
13
|
+
</g>
|
|
14
|
+
</svg>
|