@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.
@@ -4177,6 +4177,35 @@ var init_claude = __esm({
4177
4177
  });
4178
4178
 
4179
4179
  // src/agents/codex.ts
4180
+ async function listCodexModels() {
4181
+ const now = Date.now();
4182
+ if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
4183
+ return cachedCodexModels.models;
4184
+ }
4185
+ const which = await run("which", ["codex"], { reject: false });
4186
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
4187
+ const listed = await run("codex", ["debug", "models"], { reject: false });
4188
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
4189
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
4190
+ }
4191
+ try {
4192
+ const parsed = JSON.parse(listed.stdout);
4193
+ const rows = Array.isArray(parsed.models) ? parsed.models : [];
4194
+ const preferred = rows.filter((m) => (m.visibility ?? "list") === "list");
4195
+ const source = preferred.length > 0 ? preferred : rows;
4196
+ const models = source.map((m) => ({
4197
+ id: (m.slug || "").trim(),
4198
+ displayName: (m.display_name || m.slug || "").trim(),
4199
+ description: m.description,
4200
+ priority: typeof m.priority === "number" ? m.priority : 999
4201
+ })).filter((m) => m.id).sort((a, b) => a.priority - b.priority).map(({ id, displayName, description }) => ({ id, displayName, description }));
4202
+ if (models.length === 0) return FALLBACK_CODEX_MODELS;
4203
+ cachedCodexModels = { at: now, models };
4204
+ return models;
4205
+ } catch {
4206
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
4207
+ }
4208
+ }
4180
4209
  function usageFromCodex(usage) {
4181
4210
  if (!usage) return null;
4182
4211
  const inputTokens = Number(usage.input_tokens ?? 0);
@@ -4200,7 +4229,7 @@ function codexConfigHasNetworkAccess() {
4200
4229
  }
4201
4230
  return false;
4202
4231
  }
4203
- var import_node_fs11, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, codexAdapter;
4232
+ var import_node_fs11, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
4204
4233
  var init_codex = __esm({
4205
4234
  "src/agents/codex.ts"() {
4206
4235
  "use strict";
@@ -4211,6 +4240,15 @@ var init_codex = __esm({
4211
4240
  init_turn_input();
4212
4241
  init_types();
4213
4242
  CODEX_PROMPT_ARG_MAX = 2e5;
4243
+ FALLBACK_CODEX_MODELS = [
4244
+ { id: "gpt-5.6-sol", displayName: "GPT-5.6 Sol" },
4245
+ { id: "gpt-5.6-terra", displayName: "GPT-5.6 Terra" },
4246
+ { id: "gpt-5.6-luna", displayName: "GPT-5.6 Luna" },
4247
+ { id: "gpt-5.5", displayName: "GPT-5.5" },
4248
+ { id: "gpt-5.2", displayName: "GPT-5.2" }
4249
+ ];
4250
+ cachedCodexModels = null;
4251
+ CODEX_MODEL_CACHE_MS = 5 * 60 * 1e3;
4214
4252
  codexAdapter = {
4215
4253
  kind: "codex",
4216
4254
  async detect() {
@@ -4256,6 +4294,7 @@ var init_codex = __esm({
4256
4294
  );
4257
4295
  }
4258
4296
  const mode = permissionMode(thread);
4297
+ const model = thread.model?.trim();
4259
4298
  const args = [
4260
4299
  "exec",
4261
4300
  ...sessionId ? ["resume", sessionId] : [],
@@ -4266,7 +4305,8 @@ var init_codex = __esm({
4266
4305
  "--sandbox",
4267
4306
  mode.codexSandbox,
4268
4307
  "--ask-for-approval",
4269
- "never"
4308
+ "never",
4309
+ ...model ? ["--model", model] : []
4270
4310
  ];
4271
4311
  return {
4272
4312
  file: "codex",
@@ -4447,6 +4487,35 @@ function resolveCursorApiKey() {
4447
4487
  if (fromEnv) return fromEnv;
4448
4488
  return (loadAppSettings().environment.CURSOR_API_KEY || "").trim();
4449
4489
  }
4490
+ function isCursorAutoModel(model) {
4491
+ const id = (model ?? "").trim().toLowerCase();
4492
+ return !id || id === "default" || id === "auto";
4493
+ }
4494
+ function resolveCursorModelId(model) {
4495
+ if (isCursorAutoModel(model)) return "default";
4496
+ return model.trim();
4497
+ }
4498
+ async function listCursorModels() {
4499
+ const now = Date.now();
4500
+ if (cachedModels && now - cachedModels.at < MODEL_CACHE_MS) {
4501
+ return cachedModels.models;
4502
+ }
4503
+ const apiKey = resolveCursorApiKey();
4504
+ if (!apiKey) return FALLBACK_CURSOR_MODELS;
4505
+ try {
4506
+ const listed = await import_sdk.Cursor.models.list({ apiKey });
4507
+ const models = listed.map((m) => ({
4508
+ id: m.id,
4509
+ displayName: m.displayName || m.id,
4510
+ description: m.description
4511
+ })).filter((m) => Boolean(m.id));
4512
+ if (models.length === 0) return FALLBACK_CURSOR_MODELS;
4513
+ cachedModels = { at: now, models };
4514
+ return models;
4515
+ } catch {
4516
+ return cachedModels?.models ?? FALLBACK_CURSOR_MODELS;
4517
+ }
4518
+ }
4450
4519
  function entryDir() {
4451
4520
  const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
4452
4521
  if (cjsDir) return cjsDir;
@@ -4478,7 +4547,7 @@ function cursorRunnerPath() {
4478
4547
  }
4479
4548
  return candidates[0];
4480
4549
  }
4481
- var import_node_fs12, import_node_module2, import_node_path12, import_node_url2, import_sdk, import_meta2, cursorAdapter;
4550
+ var import_node_fs12, import_node_module2, import_node_path12, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
4482
4551
  var init_cursor = __esm({
4483
4552
  "src/agents/cursor.ts"() {
4484
4553
  "use strict";
@@ -4493,6 +4562,13 @@ var init_cursor = __esm({
4493
4562
  init_turn_input();
4494
4563
  init_cursor_events();
4495
4564
  import_meta2 = {};
4565
+ FALLBACK_CURSOR_MODELS = [
4566
+ { id: "default", displayName: "Auto" },
4567
+ { id: "composer-2.5", displayName: "Composer 2.5" },
4568
+ { id: "composer-2", displayName: "Composer 2" }
4569
+ ];
4570
+ cachedModels = null;
4571
+ MODEL_CACHE_MS = 5 * 60 * 1e3;
4496
4572
  cursorAdapter = {
4497
4573
  kind: "cursor",
4498
4574
  async detect() {
@@ -4543,12 +4619,16 @@ var init_cursor = __esm({
4543
4619
  };
4544
4620
  const runner = cursorRunnerPath();
4545
4621
  const isTs = runner.endsWith(".ts");
4622
+ const whichNode = await run("which", ["node"], { reject: false });
4623
+ const nodeBin = whichNode.exitCode === 0 && whichNode.stdout.trim() ? whichNode.stdout.trim() : null;
4624
+ const file = nodeBin || process.execPath;
4546
4625
  return {
4547
- file: process.execPath,
4626
+ file,
4548
4627
  args: isTs ? ["--import", "tsx", runner] : [runner],
4549
4628
  cwd: thread.worktreePath,
4550
4629
  stdin: JSON.stringify(req),
4551
4630
  env: {
4631
+ ...nodeBin ? {} : { ELECTRON_RUN_AS_NODE: "1" },
4552
4632
  ...apiKey ? { CURSOR_API_KEY: apiKey } : {}
4553
4633
  }
4554
4634
  };
@@ -4573,6 +4653,46 @@ var init_cursor = __esm({
4573
4653
  });
4574
4654
 
4575
4655
  // src/agents/opencode.ts
4656
+ function displayNameFromOpencodeId(id) {
4657
+ const slash = id.indexOf("/");
4658
+ if (slash <= 0) return id;
4659
+ const provider = id.slice(0, slash);
4660
+ const name = id.slice(slash + 1);
4661
+ return `${provider} \xB7 ${name}`;
4662
+ }
4663
+ function sortOpencodeModelIds(ids) {
4664
+ return [...ids].sort((a, b) => {
4665
+ const aLatest = /latest|~/.test(a) ? 0 : 1;
4666
+ const bLatest = /latest|~/.test(b) ? 0 : 1;
4667
+ if (aLatest !== bLatest) return aLatest - bLatest;
4668
+ const aOc = a.startsWith("opencode/") ? 0 : 1;
4669
+ const bOc = b.startsWith("opencode/") ? 0 : 1;
4670
+ if (aOc !== bOc) return aOc - bOc;
4671
+ return a.localeCompare(b);
4672
+ });
4673
+ }
4674
+ async function listOpencodeModels() {
4675
+ const now = Date.now();
4676
+ if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
4677
+ return cachedOpencodeModels.models;
4678
+ }
4679
+ const which = await run("which", ["opencode"], { reject: false });
4680
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
4681
+ const listed = await run("opencode", ["models"], { reject: false });
4682
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
4683
+ return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
4684
+ }
4685
+ const ids = listed.stdout.split("\n").map((l) => l.trim()).filter((l) => /^[\w.~@+-]+\/[\w.~@+/-]+$/.test(l));
4686
+ const unique = [...new Set(ids)];
4687
+ if (unique.length === 0) return FALLBACK_OPENCODE_MODELS;
4688
+ const OPENCODE_PICKER_LIMIT = 60;
4689
+ const models = sortOpencodeModelIds(unique).slice(0, OPENCODE_PICKER_LIMIT).map((id) => ({
4690
+ id,
4691
+ displayName: displayNameFromOpencodeId(id)
4692
+ }));
4693
+ cachedOpencodeModels = { at: now, models };
4694
+ return models;
4695
+ }
4576
4696
  function usageFromOpencode(tokens) {
4577
4697
  if (!tokens) return null;
4578
4698
  const inputTokens = Number(tokens.input ?? 0);
@@ -4585,13 +4705,26 @@ function usageFromOpencode(tokens) {
4585
4705
  cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
4586
4706
  };
4587
4707
  }
4588
- var opencodeAdapter;
4708
+ var FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
4589
4709
  var init_opencode = __esm({
4590
4710
  "src/agents/opencode.ts"() {
4591
4711
  "use strict";
4592
4712
  init_run();
4593
4713
  init_turn_input();
4594
4714
  init_types();
4715
+ FALLBACK_OPENCODE_MODELS = [
4716
+ { id: "opencode/big-pickle", displayName: "opencode \xB7 big-pickle" },
4717
+ {
4718
+ id: "openrouter/~anthropic/claude-sonnet-latest",
4719
+ displayName: "openrouter \xB7 claude-sonnet-latest"
4720
+ },
4721
+ {
4722
+ id: "openrouter/~openai/gpt-latest",
4723
+ displayName: "openrouter \xB7 gpt-latest"
4724
+ }
4725
+ ];
4726
+ cachedOpencodeModels = null;
4727
+ OPENCODE_MODEL_CACHE_MS = 5 * 60 * 1e3;
4595
4728
  opencodeAdapter = {
4596
4729
  kind: "opencode",
4597
4730
  async detect() {
@@ -4623,6 +4756,7 @@ var init_opencode = __esm({
4623
4756
  const prompt = flattenTurnInput(input);
4624
4757
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
4625
4758
  const mode = permissionMode(thread);
4759
+ const model = thread.model?.trim();
4626
4760
  const args = [
4627
4761
  "run",
4628
4762
  "--dir",
@@ -4633,6 +4767,9 @@ var init_opencode = __esm({
4633
4767
  if (sessionId) {
4634
4768
  args.push("--session", sessionId);
4635
4769
  }
4770
+ if (model) {
4771
+ args.push("--model", model);
4772
+ }
4636
4773
  return {
4637
4774
  file: "opencode",
4638
4775
  args,
@@ -4760,6 +4897,183 @@ var init_opencode = __esm({
4760
4897
  }
4761
4898
  });
4762
4899
 
4900
+ // src/agents/install.ts
4901
+ function getAgentSetupInfo(agent) {
4902
+ return SETUP[agent];
4903
+ }
4904
+ function listAgentSetupInfo() {
4905
+ return Object.values(SETUP);
4906
+ }
4907
+ async function openInSystemTerminal(command) {
4908
+ ensureAgentPath();
4909
+ const trimmed = command.trim();
4910
+ if (!trimmed) throw new Error("Command is empty");
4911
+ if (process.platform === "darwin") {
4912
+ const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4913
+ const script = `tell application "Terminal" to do script "${escaped}"`;
4914
+ const result = await run("osascript", ["-e", script], { reject: false });
4915
+ if (result.exitCode !== 0) {
4916
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open Terminal");
4917
+ }
4918
+ await run("osascript", ["-e", 'tell application "Terminal" to activate'], {
4919
+ reject: false
4920
+ });
4921
+ return;
4922
+ }
4923
+ if (process.platform === "win32") {
4924
+ const result = await run(
4925
+ "cmd.exe",
4926
+ ["/c", "start", "cmd.exe", "/k", trimmed],
4927
+ { reject: false }
4928
+ );
4929
+ if (result.exitCode !== 0) {
4930
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open cmd");
4931
+ }
4932
+ return;
4933
+ }
4934
+ const candidates = [
4935
+ { file: "gnome-terminal", args: ["--", "bash", "-lc", `${trimmed}; exec bash`] },
4936
+ { file: "x-terminal-emulator", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
4937
+ { file: "konsole", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
4938
+ { file: "xterm", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] }
4939
+ ];
4940
+ for (const c of candidates) {
4941
+ const which = await run("which", [c.file], { reject: false });
4942
+ if (which.exitCode !== 0 || !which.stdout.trim()) continue;
4943
+ const result = await run(c.file, c.args, { reject: false });
4944
+ if (result.exitCode === 0) return;
4945
+ }
4946
+ throw new Error(
4947
+ `No terminal found to run: ${trimmed}. Install gnome-terminal (or similar), or run the command manually.`
4948
+ );
4949
+ }
4950
+ async function installAgent(agent) {
4951
+ const info = getAgentSetupInfo(agent);
4952
+ if (info.kind === "bundled-sdk" || info.kind === "api-key") {
4953
+ return {
4954
+ ok: true,
4955
+ message: info.summary
4956
+ };
4957
+ }
4958
+ if (!info.installCommand) {
4959
+ return { ok: false, message: `No install command for ${agent}` };
4960
+ }
4961
+ if (/curl\s| \|\s*bash/.test(info.installCommand) || !info.npmPackage) {
4962
+ await openInSystemTerminal(info.installCommand);
4963
+ return {
4964
+ ok: true,
4965
+ openedTerminal: true,
4966
+ command: info.installCommand,
4967
+ message: `Opened Terminal to run: ${info.installCommand}`
4968
+ };
4969
+ }
4970
+ ensureAgentPath();
4971
+ const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
4972
+ const ok = result.exitCode === 0;
4973
+ if (!ok) {
4974
+ try {
4975
+ await openInSystemTerminal(info.installCommand);
4976
+ return {
4977
+ ok: false,
4978
+ openedTerminal: true,
4979
+ command: info.installCommand,
4980
+ exitCode: result.exitCode,
4981
+ stdout: result.stdout,
4982
+ stderr: result.stderr,
4983
+ message: `npm install failed (exit ${result.exitCode}). Opened Terminal with: ${info.installCommand}`
4984
+ };
4985
+ } catch {
4986
+ return {
4987
+ ok: false,
4988
+ command: info.installCommand,
4989
+ exitCode: result.exitCode,
4990
+ stdout: result.stdout,
4991
+ stderr: result.stderr,
4992
+ message: result.stderr.trim() || result.stdout.trim() || `npm install failed (exit ${result.exitCode})`
4993
+ };
4994
+ }
4995
+ }
4996
+ return {
4997
+ ok: true,
4998
+ command: info.installCommand,
4999
+ exitCode: 0,
5000
+ stdout: result.stdout,
5001
+ stderr: result.stderr,
5002
+ message: `Installed ${info.npmPackage}`
5003
+ };
5004
+ }
5005
+ async function loginAgent(agent) {
5006
+ const info = getAgentSetupInfo(agent);
5007
+ if (!info.loginCommand) {
5008
+ return {
5009
+ ok: true,
5010
+ message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
5011
+ };
5012
+ }
5013
+ await openInSystemTerminal(info.loginCommand);
5014
+ return {
5015
+ ok: true,
5016
+ openedTerminal: true,
5017
+ command: info.loginCommand,
5018
+ message: `Opened Terminal to run: ${info.loginCommand}`
5019
+ };
5020
+ }
5021
+ var SETUP;
5022
+ var init_install = __esm({
5023
+ "src/agents/install.ts"() {
5024
+ "use strict";
5025
+ init_run();
5026
+ init_path();
5027
+ SETUP = {
5028
+ claude: {
5029
+ agent: "claude",
5030
+ kind: "cli",
5031
+ summary: "Install the Claude Code CLI, then complete login in a terminal.",
5032
+ docsUrl: "https://code.claude.com/docs/en/install",
5033
+ installCommand: "npm install -g @anthropic-ai/claude-code",
5034
+ loginCommand: "claude auth login",
5035
+ npmPackage: "@anthropic-ai/claude-code"
5036
+ },
5037
+ codex: {
5038
+ agent: "codex",
5039
+ kind: "cli",
5040
+ summary: "Install the Codex CLI, then run login in a terminal.",
5041
+ docsUrl: "https://github.com/openai/codex",
5042
+ installCommand: "npm install -g @openai/codex",
5043
+ loginCommand: "codex login",
5044
+ npmPackage: "@openai/codex"
5045
+ },
5046
+ opencode: {
5047
+ agent: "opencode",
5048
+ kind: "cli",
5049
+ summary: "Install OpenCode (curl installer or npm), then authenticate providers.",
5050
+ docsUrl: "https://opencode.ai/docs",
5051
+ // Prefer the official installer; npm package name is opencode-ai.
5052
+ installCommand: "curl -fsSL https://opencode.ai/install | bash",
5053
+ loginCommand: "opencode auth login",
5054
+ npmPackage: "opencode-ai@latest"
5055
+ },
5056
+ cursor: {
5057
+ agent: "cursor",
5058
+ kind: "bundled-sdk",
5059
+ summary: "No CLI install \u2014 Sideboard ships the Cursor SDK. Add a CURSOR_API_KEY from the Cursor dashboard.",
5060
+ docsUrl: "https://cursor.com/dashboard/integrations",
5061
+ installCommand: null,
5062
+ loginCommand: null
5063
+ },
5064
+ brightsy: {
5065
+ agent: "brightsy",
5066
+ kind: "cli",
5067
+ summary: "Install the Brightsy CLI, then run `brightsy login`.",
5068
+ docsUrl: "https://www.npmjs.com/package/@brightsy/cli",
5069
+ installCommand: "npm install -g @brightsy/cli",
5070
+ loginCommand: "brightsy login",
5071
+ npmPackage: "@brightsy/cli"
5072
+ }
5073
+ };
5074
+ }
5075
+ });
5076
+
4763
5077
  // src/agents/index.ts
4764
5078
  var agents_exports = {};
4765
5079
  __export(agents_exports, {
@@ -4774,10 +5088,20 @@ __export(agents_exports, {
4774
5088
  encodeBrightsyTarget: () => encodeBrightsyTarget,
4775
5089
  ensureAgentPath: () => ensureAgentPath,
4776
5090
  getAdapter: () => getAdapter,
5091
+ getAgentSetupInfo: () => getAgentSetupInfo,
5092
+ installAgent: () => installAgent,
5093
+ isCursorAutoModel: () => isCursorAutoModel,
5094
+ listAgentSetupInfo: () => listAgentSetupInfo,
4777
5095
  listBrightsyChatTargets: () => listBrightsyChatTargets,
5096
+ listCodexModels: () => listCodexModels,
5097
+ listCursorModels: () => listCursorModels,
5098
+ listOpencodeModels: () => listOpencodeModels,
5099
+ loginAgent: () => loginAgent,
5100
+ openInSystemTerminal: () => openInSystemTerminal,
4778
5101
  opencodeAdapter: () => opencodeAdapter,
4779
5102
  parseCursorRunnerLine: () => parseCursorRunnerLine,
4780
- permissionMode: () => permissionMode
5103
+ permissionMode: () => permissionMode,
5104
+ resolveCursorModelId: () => resolveCursorModelId
4781
5105
  });
4782
5106
  function getAdapter(kind) {
4783
5107
  return adapters[kind];
@@ -4801,8 +5125,10 @@ var init_agents = __esm({
4801
5125
  init_codex();
4802
5126
  init_cursor();
4803
5127
  init_cursor_events();
5128
+ init_cursor();
4804
5129
  init_opencode();
4805
5130
  init_path();
5131
+ init_install();
4806
5132
  adapters = {
4807
5133
  claude: claudeAdapter,
4808
5134
  codex: codexAdapter,
@@ -6310,10 +6636,10 @@ function createChatTab(input) {
6310
6636
  userSetTitle: true,
6311
6637
  ...binding,
6312
6638
  agent: input.agent ?? from.agent,
6313
- model: input.agent && input.agent !== from.agent ? null : from.model,
6639
+ model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
6314
6640
  fast: from.fast,
6315
6641
  planMode: from.planMode,
6316
- autonomy: from.autonomy,
6642
+ autonomy: input.autonomy ?? from.autonomy,
6317
6643
  attachments: input.attachments ?? [],
6318
6644
  status: "idle"
6319
6645
  });
@@ -8250,6 +8576,7 @@ var Orchestrator = class {
8250
8576
  ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
8251
8577
  ].filter(Boolean).join("\n\n---\n\n");
8252
8578
  try {
8579
+ let lastStderr = "";
8253
8580
  const handle = await spawnAgentTurn(
8254
8581
  fresh,
8255
8582
  { cachedPrefix, prompt: agentPrompt },
@@ -8258,6 +8585,9 @@ var Orchestrator = class {
8258
8585
  if (event.type === "session_id") {
8259
8586
  updateThread(threadId, { sessionId: event.data });
8260
8587
  }
8588
+ if (event.type === "stderr" && typeof event.data === "string" && event.data.trim()) {
8589
+ lastStderr = event.data.trim();
8590
+ }
8261
8591
  }
8262
8592
  );
8263
8593
  this.activeTurns.set(threadId, handle);
@@ -8303,7 +8633,12 @@ var Orchestrator = class {
8303
8633
  this.emit({ type: "status_changed", threadId, status: "stopped" });
8304
8634
  this.emit({ type: "turn_finished", threadId, exitCode: result.exitCode });
8305
8635
  } else {
8306
- setStatus(threadId, result.exitCode === 0 ? "idle" : "error", result.exitCode === 0 ? null : `exit ${result.exitCode}`);
8636
+ const failDetail = lastStderr ? `exit ${result.exitCode}: ${lastStderr.slice(0, 500)}` : `exit ${result.exitCode}`;
8637
+ setStatus(
8638
+ threadId,
8639
+ result.exitCode === 0 ? "idle" : "error",
8640
+ result.exitCode === 0 ? null : failDetail
8641
+ );
8307
8642
  this.emit({
8308
8643
  type: "status_changed",
8309
8644
  threadId,
@@ -8777,9 +9112,16 @@ var Orchestrator = class {
8777
9112
  if (patch.planMode !== void 0) next.planMode = patch.planMode;
8778
9113
  if (patch.model !== void 0) next.model = patch.model;
8779
9114
  if (patch.agent !== void 0) {
8780
- next.agent = patch.agent;
8781
- if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
8782
- next.sessionId = null;
9115
+ if (patch.agent !== thread.agent) {
9116
+ if (thread.messages.length > 0) {
9117
+ throw new Error(
9118
+ `Cannot switch agent provider mid-chat (${thread.agent} \u2192 ${patch.agent}). Start a new chat tab instead.`
9119
+ );
9120
+ }
9121
+ next.agent = patch.agent;
9122
+ if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
9123
+ next.sessionId = null;
9124
+ }
8783
9125
  }
8784
9126
  return updateThread(thread.id, next);
8785
9127
  }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "../chunk-RPSHBKLW.js";
4
+ } from "../chunk-OY47OEY2.js";
5
5
  import "../chunk-PER4N6LS.js";
6
6
  import "../chunk-Y2EWQ4TL.js";
7
7
  import "../chunk-BMB7WCGF.js";
8
- import "../chunk-WK6AK7NK.js";
8
+ import "../chunk-4YLTMPEO.js";
9
9
  import "../chunk-ILQK4P5R.js";
10
10
  import "../chunk-3DKGI32Q.js";
11
11
  import "../chunk-3WF3X46L.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",