@sideboard-ai/core 0.1.30 → 0.1.32

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.
@@ -3,8 +3,30 @@
3
3
 
4
4
  // src/agents/cursor-runner.ts
5
5
  var import_sdk = require("@cursor/sdk");
6
+ var import_node_fs3 = require("fs");
7
+ var import_node_path4 = require("path");
6
8
  var import_node_readline = require("readline");
7
9
 
10
+ // src/store/paths.ts
11
+ var import_node_path2 = require("path");
12
+ var import_node_os2 = require("os");
13
+ var import_node_path3 = require("path");
14
+ var import_node_fs2 = require("fs");
15
+
16
+ // src/hook/settings.ts
17
+ var import_node_fs = require("fs");
18
+ var import_node_os = require("os");
19
+ var import_node_path = require("path");
20
+ var import_smol_toml = require("smol-toml");
21
+
22
+ // src/store/paths.ts
23
+ function appDataDir() {
24
+ const override = process.env.SIDEBOARD_APP_DATA?.trim();
25
+ const base = override ? override : process.platform === "darwin" ? (0, import_node_path3.join)((0, import_node_os2.homedir)(), "Library", "Application Support", "sideboard") : process.platform === "win32" ? (0, import_node_path3.join)(process.env.APPDATA ?? (0, import_node_path3.join)((0, import_node_os2.homedir)(), "AppData", "Roaming"), "sideboard") : (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".local", "share", "sideboard");
26
+ (0, import_node_fs2.mkdirSync)(base, { recursive: true });
27
+ return base;
28
+ }
29
+
8
30
  // src/agents/cursor-events.ts
9
31
  function usageFromCursor(usage) {
10
32
  if (!usage) return null;
@@ -82,12 +104,18 @@ function emit(event) {
82
104
  `);
83
105
  }
84
106
  function modelSelection(model, fast) {
85
- const id = model && model.trim() || "composer-2.5";
107
+ const raw = model && model.trim() || "";
108
+ const id = !raw || raw.toLowerCase() === "auto" || raw.toLowerCase() === "default" ? "default" : raw;
86
109
  if (fast) {
87
110
  return { id, params: [{ id: "fast", value: "true" }] };
88
111
  }
89
112
  return { id };
90
113
  }
114
+ function localAgentStore() {
115
+ const root = (0, import_node_path4.join)(appDataDir(), "cursor-sdk-store");
116
+ (0, import_node_fs3.mkdirSync)(root, { recursive: true });
117
+ return new import_sdk.JsonlLocalAgentStore(root);
118
+ }
91
119
  async function readStdinJson() {
92
120
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, crlfDelay: Infinity });
93
121
  const chunks = [];
@@ -118,17 +146,19 @@ async function main() {
118
146
  const apiKey = (req.apiKey || process.env.CURSOR_API_KEY || "").trim() || void 0;
119
147
  const model = modelSelection(req.model, Boolean(req.fast));
120
148
  const mode = req.planMode ? "plan" : "agent";
149
+ const store = localAgentStore();
150
+ const local = { cwd: req.cwd, store };
121
151
  try {
122
152
  const agent = req.agentId ? await import_sdk.Agent.resume(req.agentId, {
123
153
  apiKey,
124
154
  model,
125
155
  mode,
126
- local: { cwd: req.cwd }
156
+ local
127
157
  }) : await import_sdk.Agent.create({
128
158
  apiKey,
129
159
  model,
130
160
  mode,
131
- local: { cwd: req.cwd },
161
+ local,
132
162
  name: "Sideboard"
133
163
  });
134
164
  try {
@@ -2,21 +2,32 @@
2
2
  import {
3
3
  cursorSdkMessageToEvents
4
4
  } from "../chunk-3DKGI32Q.js";
5
+ import {
6
+ appDataDir
7
+ } from "../chunk-M37RITA6.js";
5
8
 
6
9
  // src/agents/cursor-runner.ts
7
- import { Agent, CursorAgentError } from "@cursor/sdk";
10
+ import { Agent, CursorAgentError, JsonlLocalAgentStore } from "@cursor/sdk";
11
+ import { mkdirSync } from "fs";
12
+ import { join } from "path";
8
13
  import { createInterface } from "readline";
9
14
  function emit(event) {
10
15
  process.stdout.write(`${JSON.stringify(event)}
11
16
  `);
12
17
  }
13
18
  function modelSelection(model, fast) {
14
- const id = model && model.trim() || "composer-2.5";
19
+ const raw = model && model.trim() || "";
20
+ const id = !raw || raw.toLowerCase() === "auto" || raw.toLowerCase() === "default" ? "default" : raw;
15
21
  if (fast) {
16
22
  return { id, params: [{ id: "fast", value: "true" }] };
17
23
  }
18
24
  return { id };
19
25
  }
26
+ function localAgentStore() {
27
+ const root = join(appDataDir(), "cursor-sdk-store");
28
+ mkdirSync(root, { recursive: true });
29
+ return new JsonlLocalAgentStore(root);
30
+ }
20
31
  async function readStdinJson() {
21
32
  const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
22
33
  const chunks = [];
@@ -47,17 +58,19 @@ async function main() {
47
58
  const apiKey = (req.apiKey || process.env.CURSOR_API_KEY || "").trim() || void 0;
48
59
  const model = modelSelection(req.model, Boolean(req.fast));
49
60
  const mode = req.planMode ? "plan" : "agent";
61
+ const store = localAgentStore();
62
+ const local = { cwd: req.cwd, store };
50
63
  try {
51
64
  const agent = req.agentId ? await Agent.resume(req.agentId, {
52
65
  apiKey,
53
66
  model,
54
67
  mode,
55
- local: { cwd: req.cwd }
68
+ local
56
69
  }) : await Agent.create({
57
70
  apiKey,
58
71
  model,
59
72
  mode,
60
- local: { cwd: req.cwd },
73
+ local,
61
74
  name: "Sideboard"
62
75
  });
63
76
  try {
@@ -8,10 +8,20 @@ import {
8
8
  decodeBrightsyTarget,
9
9
  encodeBrightsyTarget,
10
10
  getAdapter,
11
+ getAgentSetupInfo,
12
+ installAgent,
13
+ isCursorAutoModel,
14
+ listAgentSetupInfo,
11
15
  listBrightsyChatTargets,
16
+ listCodexModels,
17
+ listCursorModels,
18
+ listOpencodeModels,
19
+ loginAgent,
20
+ openInSystemTerminal,
12
21
  opencodeAdapter,
13
- permissionMode
14
- } from "./chunk-WK6AK7NK.js";
22
+ permissionMode,
23
+ resolveCursorModelId
24
+ } from "./chunk-4YLTMPEO.js";
15
25
  import "./chunk-ILQK4P5R.js";
16
26
  import {
17
27
  cursorSdkMessageToEvents,
@@ -34,8 +44,18 @@ export {
34
44
  encodeBrightsyTarget,
35
45
  ensureAgentPath,
36
46
  getAdapter,
47
+ getAgentSetupInfo,
48
+ installAgent,
49
+ isCursorAutoModel,
50
+ listAgentSetupInfo,
37
51
  listBrightsyChatTargets,
52
+ listCodexModels,
53
+ listCursorModels,
54
+ listOpencodeModels,
55
+ loginAgent,
56
+ openInSystemTerminal,
38
57
  opencodeAdapter,
39
58
  parseCursorRunnerLine,
40
- permissionMode
59
+ permissionMode,
60
+ resolveCursorModelId
41
61
  };
@@ -14,6 +14,7 @@ import {
14
14
  resolveClaudeExecutable
15
15
  } from "./chunk-3WF3X46L.js";
16
16
  import {
17
+ ensureAgentPath,
17
18
  run
18
19
  } from "./chunk-AJ6ROGD7.js";
19
20
 
@@ -933,6 +934,44 @@ import { existsSync as existsSync3, readFileSync } from "fs";
933
934
  import { homedir } from "os";
934
935
  import { join as join2 } from "path";
935
936
  var CODEX_PROMPT_ARG_MAX = 2e5;
937
+ var FALLBACK_CODEX_MODELS = [
938
+ { id: "gpt-5.6-sol", displayName: "GPT-5.6 Sol" },
939
+ { id: "gpt-5.6-terra", displayName: "GPT-5.6 Terra" },
940
+ { id: "gpt-5.6-luna", displayName: "GPT-5.6 Luna" },
941
+ { id: "gpt-5.5", displayName: "GPT-5.5" },
942
+ { id: "gpt-5.2", displayName: "GPT-5.2" }
943
+ ];
944
+ var cachedCodexModels = null;
945
+ var CODEX_MODEL_CACHE_MS = 5 * 60 * 1e3;
946
+ async function listCodexModels() {
947
+ const now = Date.now();
948
+ if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
949
+ return cachedCodexModels.models;
950
+ }
951
+ const which = await run("which", ["codex"], { reject: false });
952
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
953
+ const listed = await run("codex", ["debug", "models"], { reject: false });
954
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
955
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
956
+ }
957
+ try {
958
+ const parsed = JSON.parse(listed.stdout);
959
+ const rows = Array.isArray(parsed.models) ? parsed.models : [];
960
+ const preferred = rows.filter((m) => (m.visibility ?? "list") === "list");
961
+ const source = preferred.length > 0 ? preferred : rows;
962
+ const models = source.map((m) => ({
963
+ id: (m.slug || "").trim(),
964
+ displayName: (m.display_name || m.slug || "").trim(),
965
+ description: m.description,
966
+ priority: typeof m.priority === "number" ? m.priority : 999
967
+ })).filter((m) => m.id).sort((a, b) => a.priority - b.priority).map(({ id, displayName, description }) => ({ id, displayName, description }));
968
+ if (models.length === 0) return FALLBACK_CODEX_MODELS;
969
+ cachedCodexModels = { at: now, models };
970
+ return models;
971
+ } catch {
972
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
973
+ }
974
+ }
936
975
  function usageFromCodex(usage) {
937
976
  if (!usage) return null;
938
977
  const inputTokens = Number(usage.input_tokens ?? 0);
@@ -1001,6 +1040,7 @@ var codexAdapter = {
1001
1040
  );
1002
1041
  }
1003
1042
  const mode = permissionMode(thread);
1043
+ const model = thread.model?.trim();
1004
1044
  const args = [
1005
1045
  "exec",
1006
1046
  ...sessionId ? ["resume", sessionId] : [],
@@ -1011,7 +1051,8 @@ var codexAdapter = {
1011
1051
  "--sandbox",
1012
1052
  mode.codexSandbox,
1013
1053
  "--ask-for-approval",
1014
- "never"
1054
+ "never",
1055
+ ...model ? ["--model", model] : []
1015
1056
  ];
1016
1057
  return {
1017
1058
  file: "codex",
@@ -1097,11 +1138,47 @@ import { createRequire as createRequire2 } from "module";
1097
1138
  import { dirname as dirname2, join as join3 } from "path";
1098
1139
  import { fileURLToPath as fileURLToPath2 } from "url";
1099
1140
  import { Cursor } from "@cursor/sdk";
1141
+ var FALLBACK_CURSOR_MODELS = [
1142
+ { id: "default", displayName: "Auto" },
1143
+ { id: "composer-2.5", displayName: "Composer 2.5" },
1144
+ { id: "composer-2", displayName: "Composer 2" }
1145
+ ];
1146
+ var cachedModels = null;
1147
+ var MODEL_CACHE_MS = 5 * 60 * 1e3;
1100
1148
  function resolveCursorApiKey() {
1101
1149
  const fromEnv = (process.env.CURSOR_API_KEY || "").trim();
1102
1150
  if (fromEnv) return fromEnv;
1103
1151
  return (loadAppSettings().environment.CURSOR_API_KEY || "").trim();
1104
1152
  }
1153
+ function isCursorAutoModel(model) {
1154
+ const id = (model ?? "").trim().toLowerCase();
1155
+ return !id || id === "default" || id === "auto";
1156
+ }
1157
+ function resolveCursorModelId(model) {
1158
+ if (isCursorAutoModel(model)) return "default";
1159
+ return model.trim();
1160
+ }
1161
+ async function listCursorModels() {
1162
+ const now = Date.now();
1163
+ if (cachedModels && now - cachedModels.at < MODEL_CACHE_MS) {
1164
+ return cachedModels.models;
1165
+ }
1166
+ const apiKey = resolveCursorApiKey();
1167
+ if (!apiKey) return FALLBACK_CURSOR_MODELS;
1168
+ try {
1169
+ const listed = await Cursor.models.list({ apiKey });
1170
+ const models = listed.map((m) => ({
1171
+ id: m.id,
1172
+ displayName: m.displayName || m.id,
1173
+ description: m.description
1174
+ })).filter((m) => Boolean(m.id));
1175
+ if (models.length === 0) return FALLBACK_CURSOR_MODELS;
1176
+ cachedModels = { at: now, models };
1177
+ return models;
1178
+ } catch {
1179
+ return cachedModels?.models ?? FALLBACK_CURSOR_MODELS;
1180
+ }
1181
+ }
1105
1182
  function entryDir() {
1106
1183
  const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
1107
1184
  if (cjsDir) return cjsDir;
@@ -1183,12 +1260,16 @@ var cursorAdapter = {
1183
1260
  };
1184
1261
  const runner = cursorRunnerPath();
1185
1262
  const isTs = runner.endsWith(".ts");
1263
+ const whichNode = await run("which", ["node"], { reject: false });
1264
+ const nodeBin = whichNode.exitCode === 0 && whichNode.stdout.trim() ? whichNode.stdout.trim() : null;
1265
+ const file = nodeBin || process.execPath;
1186
1266
  return {
1187
- file: process.execPath,
1267
+ file,
1188
1268
  args: isTs ? ["--import", "tsx", runner] : [runner],
1189
1269
  cwd: thread.worktreePath,
1190
1270
  stdin: JSON.stringify(req),
1191
1271
  env: {
1272
+ ...nodeBin ? {} : { ELECTRON_RUN_AS_NODE: "1" },
1192
1273
  ...apiKey ? { CURSOR_API_KEY: apiKey } : {}
1193
1274
  }
1194
1275
  };
@@ -1211,6 +1292,59 @@ var cursorAdapter = {
1211
1292
  };
1212
1293
 
1213
1294
  // src/agents/opencode.ts
1295
+ var FALLBACK_OPENCODE_MODELS = [
1296
+ { id: "opencode/big-pickle", displayName: "opencode \xB7 big-pickle" },
1297
+ {
1298
+ id: "openrouter/~anthropic/claude-sonnet-latest",
1299
+ displayName: "openrouter \xB7 claude-sonnet-latest"
1300
+ },
1301
+ {
1302
+ id: "openrouter/~openai/gpt-latest",
1303
+ displayName: "openrouter \xB7 gpt-latest"
1304
+ }
1305
+ ];
1306
+ var cachedOpencodeModels = null;
1307
+ var OPENCODE_MODEL_CACHE_MS = 5 * 60 * 1e3;
1308
+ function displayNameFromOpencodeId(id) {
1309
+ const slash = id.indexOf("/");
1310
+ if (slash <= 0) return id;
1311
+ const provider = id.slice(0, slash);
1312
+ const name = id.slice(slash + 1);
1313
+ return `${provider} \xB7 ${name}`;
1314
+ }
1315
+ function sortOpencodeModelIds(ids) {
1316
+ return [...ids].sort((a, b) => {
1317
+ const aLatest = /latest|~/.test(a) ? 0 : 1;
1318
+ const bLatest = /latest|~/.test(b) ? 0 : 1;
1319
+ if (aLatest !== bLatest) return aLatest - bLatest;
1320
+ const aOc = a.startsWith("opencode/") ? 0 : 1;
1321
+ const bOc = b.startsWith("opencode/") ? 0 : 1;
1322
+ if (aOc !== bOc) return aOc - bOc;
1323
+ return a.localeCompare(b);
1324
+ });
1325
+ }
1326
+ async function listOpencodeModels() {
1327
+ const now = Date.now();
1328
+ if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
1329
+ return cachedOpencodeModels.models;
1330
+ }
1331
+ const which = await run("which", ["opencode"], { reject: false });
1332
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
1333
+ const listed = await run("opencode", ["models"], { reject: false });
1334
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
1335
+ return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
1336
+ }
1337
+ const ids = listed.stdout.split("\n").map((l) => l.trim()).filter((l) => /^[\w.~@+-]+\/[\w.~@+/-]+$/.test(l));
1338
+ const unique = [...new Set(ids)];
1339
+ if (unique.length === 0) return FALLBACK_OPENCODE_MODELS;
1340
+ const OPENCODE_PICKER_LIMIT = 60;
1341
+ const models = sortOpencodeModelIds(unique).slice(0, OPENCODE_PICKER_LIMIT).map((id) => ({
1342
+ id,
1343
+ displayName: displayNameFromOpencodeId(id)
1344
+ }));
1345
+ cachedOpencodeModels = { at: now, models };
1346
+ return models;
1347
+ }
1214
1348
  function usageFromOpencode(tokens) {
1215
1349
  if (!tokens) return null;
1216
1350
  const inputTokens = Number(tokens.input ?? 0);
@@ -1254,6 +1388,7 @@ var opencodeAdapter = {
1254
1388
  const prompt = flattenTurnInput(input);
1255
1389
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1256
1390
  const mode = permissionMode(thread);
1391
+ const model = thread.model?.trim();
1257
1392
  const args = [
1258
1393
  "run",
1259
1394
  "--dir",
@@ -1264,6 +1399,9 @@ var opencodeAdapter = {
1264
1399
  if (sessionId) {
1265
1400
  args.push("--session", sessionId);
1266
1401
  }
1402
+ if (model) {
1403
+ args.push("--model", model);
1404
+ }
1267
1405
  return {
1268
1406
  file: "opencode",
1269
1407
  args,
@@ -1389,6 +1527,175 @@ var opencodeAdapter = {
1389
1527
  }
1390
1528
  };
1391
1529
 
1530
+ // src/agents/install.ts
1531
+ var SETUP = {
1532
+ claude: {
1533
+ agent: "claude",
1534
+ kind: "cli",
1535
+ summary: "Install the Claude Code CLI, then complete login in a terminal.",
1536
+ docsUrl: "https://code.claude.com/docs/en/install",
1537
+ installCommand: "npm install -g @anthropic-ai/claude-code",
1538
+ loginCommand: "claude auth login",
1539
+ npmPackage: "@anthropic-ai/claude-code"
1540
+ },
1541
+ codex: {
1542
+ agent: "codex",
1543
+ kind: "cli",
1544
+ summary: "Install the Codex CLI, then run login in a terminal.",
1545
+ docsUrl: "https://github.com/openai/codex",
1546
+ installCommand: "npm install -g @openai/codex",
1547
+ loginCommand: "codex login",
1548
+ npmPackage: "@openai/codex"
1549
+ },
1550
+ opencode: {
1551
+ agent: "opencode",
1552
+ kind: "cli",
1553
+ summary: "Install OpenCode (curl installer or npm), then authenticate providers.",
1554
+ docsUrl: "https://opencode.ai/docs",
1555
+ // Prefer the official installer; npm package name is opencode-ai.
1556
+ installCommand: "curl -fsSL https://opencode.ai/install | bash",
1557
+ loginCommand: "opencode auth login",
1558
+ npmPackage: "opencode-ai@latest"
1559
+ },
1560
+ cursor: {
1561
+ agent: "cursor",
1562
+ kind: "bundled-sdk",
1563
+ summary: "No CLI install \u2014 Sideboard ships the Cursor SDK. Add a CURSOR_API_KEY from the Cursor dashboard.",
1564
+ docsUrl: "https://cursor.com/dashboard/integrations",
1565
+ installCommand: null,
1566
+ loginCommand: null
1567
+ },
1568
+ brightsy: {
1569
+ agent: "brightsy",
1570
+ kind: "cli",
1571
+ summary: "Install the Brightsy CLI, then run `brightsy login`.",
1572
+ docsUrl: "https://www.npmjs.com/package/@brightsy/cli",
1573
+ installCommand: "npm install -g @brightsy/cli",
1574
+ loginCommand: "brightsy login",
1575
+ npmPackage: "@brightsy/cli"
1576
+ }
1577
+ };
1578
+ function getAgentSetupInfo(agent) {
1579
+ return SETUP[agent];
1580
+ }
1581
+ function listAgentSetupInfo() {
1582
+ return Object.values(SETUP);
1583
+ }
1584
+ async function openInSystemTerminal(command) {
1585
+ ensureAgentPath();
1586
+ const trimmed = command.trim();
1587
+ if (!trimmed) throw new Error("Command is empty");
1588
+ if (process.platform === "darwin") {
1589
+ const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1590
+ const script = `tell application "Terminal" to do script "${escaped}"`;
1591
+ const result = await run("osascript", ["-e", script], { reject: false });
1592
+ if (result.exitCode !== 0) {
1593
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open Terminal");
1594
+ }
1595
+ await run("osascript", ["-e", 'tell application "Terminal" to activate'], {
1596
+ reject: false
1597
+ });
1598
+ return;
1599
+ }
1600
+ if (process.platform === "win32") {
1601
+ const result = await run(
1602
+ "cmd.exe",
1603
+ ["/c", "start", "cmd.exe", "/k", trimmed],
1604
+ { reject: false }
1605
+ );
1606
+ if (result.exitCode !== 0) {
1607
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open cmd");
1608
+ }
1609
+ return;
1610
+ }
1611
+ const candidates = [
1612
+ { file: "gnome-terminal", args: ["--", "bash", "-lc", `${trimmed}; exec bash`] },
1613
+ { file: "x-terminal-emulator", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
1614
+ { file: "konsole", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
1615
+ { file: "xterm", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] }
1616
+ ];
1617
+ for (const c of candidates) {
1618
+ const which = await run("which", [c.file], { reject: false });
1619
+ if (which.exitCode !== 0 || !which.stdout.trim()) continue;
1620
+ const result = await run(c.file, c.args, { reject: false });
1621
+ if (result.exitCode === 0) return;
1622
+ }
1623
+ throw new Error(
1624
+ `No terminal found to run: ${trimmed}. Install gnome-terminal (or similar), or run the command manually.`
1625
+ );
1626
+ }
1627
+ async function installAgent(agent) {
1628
+ const info = getAgentSetupInfo(agent);
1629
+ if (info.kind === "bundled-sdk" || info.kind === "api-key") {
1630
+ return {
1631
+ ok: true,
1632
+ message: info.summary
1633
+ };
1634
+ }
1635
+ if (!info.installCommand) {
1636
+ return { ok: false, message: `No install command for ${agent}` };
1637
+ }
1638
+ if (/curl\s| \|\s*bash/.test(info.installCommand) || !info.npmPackage) {
1639
+ await openInSystemTerminal(info.installCommand);
1640
+ return {
1641
+ ok: true,
1642
+ openedTerminal: true,
1643
+ command: info.installCommand,
1644
+ message: `Opened Terminal to run: ${info.installCommand}`
1645
+ };
1646
+ }
1647
+ ensureAgentPath();
1648
+ const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
1649
+ const ok = result.exitCode === 0;
1650
+ if (!ok) {
1651
+ try {
1652
+ await openInSystemTerminal(info.installCommand);
1653
+ return {
1654
+ ok: false,
1655
+ openedTerminal: true,
1656
+ command: info.installCommand,
1657
+ exitCode: result.exitCode,
1658
+ stdout: result.stdout,
1659
+ stderr: result.stderr,
1660
+ message: `npm install failed (exit ${result.exitCode}). Opened Terminal with: ${info.installCommand}`
1661
+ };
1662
+ } catch {
1663
+ return {
1664
+ ok: false,
1665
+ command: info.installCommand,
1666
+ exitCode: result.exitCode,
1667
+ stdout: result.stdout,
1668
+ stderr: result.stderr,
1669
+ message: result.stderr.trim() || result.stdout.trim() || `npm install failed (exit ${result.exitCode})`
1670
+ };
1671
+ }
1672
+ }
1673
+ return {
1674
+ ok: true,
1675
+ command: info.installCommand,
1676
+ exitCode: 0,
1677
+ stdout: result.stdout,
1678
+ stderr: result.stderr,
1679
+ message: `Installed ${info.npmPackage}`
1680
+ };
1681
+ }
1682
+ async function loginAgent(agent) {
1683
+ const info = getAgentSetupInfo(agent);
1684
+ if (!info.loginCommand) {
1685
+ return {
1686
+ ok: true,
1687
+ message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
1688
+ };
1689
+ }
1690
+ await openInSystemTerminal(info.loginCommand);
1691
+ return {
1692
+ ok: true,
1693
+ openedTerminal: true,
1694
+ command: info.loginCommand,
1695
+ message: `Opened Terminal to run: ${info.loginCommand}`
1696
+ };
1697
+ }
1698
+
1392
1699
  // src/agents/index.ts
1393
1700
  var adapters = {
1394
1701
  claude: claudeAdapter,
@@ -1429,9 +1736,19 @@ export {
1429
1736
  PLAN_MODE_INSTRUCTION,
1430
1737
  permissionMode,
1431
1738
  claudeAdapter,
1739
+ listCodexModels,
1432
1740
  codexAdapter,
1741
+ isCursorAutoModel,
1742
+ resolveCursorModelId,
1743
+ listCursorModels,
1433
1744
  cursorAdapter,
1745
+ listOpencodeModels,
1434
1746
  opencodeAdapter,
1747
+ getAgentSetupInfo,
1748
+ listAgentSetupInfo,
1749
+ openInSystemTerminal,
1750
+ installAgent,
1751
+ loginAgent,
1435
1752
  getAdapter,
1436
1753
  allAdapters
1437
1754
  };
@@ -25,7 +25,7 @@ import {
25
25
  allAdapters,
26
26
  getAdapter,
27
27
  parseBrightsyCliLine
28
- } from "./chunk-WK6AK7NK.js";
28
+ } from "./chunk-4YLTMPEO.js";
29
29
  import {
30
30
  childEnvWithAppSettings,
31
31
  getLinearApiKey,
@@ -2557,7 +2557,7 @@ async function createThread(input, onSetupLine) {
2557
2557
  return readThread(thread.id) ?? thread;
2558
2558
  }
2559
2559
  async function listLinearIssues(agent, repoPath) {
2560
- const { getAdapter: getAdapter2 } = await import("./agents-LJJRPW4Y.js");
2560
+ const { getAdapter: getAdapter2 } = await import("./agents-HWIPJLC3.js");
2561
2561
  await requireAgent(agent, { requireLinear: true });
2562
2562
  const adapter = getAdapter2(agent);
2563
2563
  if (!adapter.listLinearIssues) {
@@ -2637,10 +2637,10 @@ function createChatTab(input) {
2637
2637
  userSetTitle: true,
2638
2638
  ...binding,
2639
2639
  agent: input.agent ?? from.agent,
2640
- model: input.agent && input.agent !== from.agent ? null : from.model,
2640
+ model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
2641
2641
  fast: from.fast,
2642
2642
  planMode: from.planMode,
2643
- autonomy: from.autonomy,
2643
+ autonomy: input.autonomy ?? from.autonomy,
2644
2644
  attachments: input.attachments ?? [],
2645
2645
  status: "idle"
2646
2646
  });
@@ -3546,6 +3546,7 @@ var Orchestrator = class {
3546
3546
  ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
3547
3547
  ].filter(Boolean).join("\n\n---\n\n");
3548
3548
  try {
3549
+ let lastStderr = "";
3549
3550
  const handle = await spawnAgentTurn(
3550
3551
  fresh,
3551
3552
  { cachedPrefix, prompt: agentPrompt },
@@ -3554,6 +3555,9 @@ var Orchestrator = class {
3554
3555
  if (event.type === "session_id") {
3555
3556
  updateThread(threadId, { sessionId: event.data });
3556
3557
  }
3558
+ if (event.type === "stderr" && typeof event.data === "string" && event.data.trim()) {
3559
+ lastStderr = event.data.trim();
3560
+ }
3557
3561
  }
3558
3562
  );
3559
3563
  this.activeTurns.set(threadId, handle);
@@ -3599,7 +3603,12 @@ var Orchestrator = class {
3599
3603
  this.emit({ type: "status_changed", threadId, status: "stopped" });
3600
3604
  this.emit({ type: "turn_finished", threadId, exitCode: result.exitCode });
3601
3605
  } else {
3602
- setStatus(threadId, result.exitCode === 0 ? "idle" : "error", result.exitCode === 0 ? null : `exit ${result.exitCode}`);
3606
+ const failDetail = lastStderr ? `exit ${result.exitCode}: ${lastStderr.slice(0, 500)}` : `exit ${result.exitCode}`;
3607
+ setStatus(
3608
+ threadId,
3609
+ result.exitCode === 0 ? "idle" : "error",
3610
+ result.exitCode === 0 ? null : failDetail
3611
+ );
3603
3612
  this.emit({
3604
3613
  type: "status_changed",
3605
3614
  threadId,
@@ -4073,9 +4082,16 @@ var Orchestrator = class {
4073
4082
  if (patch.planMode !== void 0) next.planMode = patch.planMode;
4074
4083
  if (patch.model !== void 0) next.model = patch.model;
4075
4084
  if (patch.agent !== void 0) {
4076
- next.agent = patch.agent;
4077
- if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
4078
- next.sessionId = null;
4085
+ if (patch.agent !== thread.agent) {
4086
+ if (thread.messages.length > 0) {
4087
+ throw new Error(
4088
+ `Cannot switch agent provider mid-chat (${thread.agent} \u2192 ${patch.agent}). Start a new chat tab instead.`
4089
+ );
4090
+ }
4091
+ next.agent = patch.agent;
4092
+ if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
4093
+ next.sessionId = null;
4094
+ }
4079
4095
  }
4080
4096
  return updateThread(thread.id, next);
4081
4097
  }