@sideboard-ai/core 0.1.24 → 0.1.31

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/dist/index.cjs CHANGED
@@ -4538,6 +4538,35 @@ var init_claude = __esm({
4538
4538
  });
4539
4539
 
4540
4540
  // src/agents/codex.ts
4541
+ async function listCodexModels() {
4542
+ const now = Date.now();
4543
+ if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
4544
+ return cachedCodexModels.models;
4545
+ }
4546
+ const which = await run("which", ["codex"], { reject: false });
4547
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
4548
+ const listed = await run("codex", ["debug", "models"], { reject: false });
4549
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
4550
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
4551
+ }
4552
+ try {
4553
+ const parsed = JSON.parse(listed.stdout);
4554
+ const rows = Array.isArray(parsed.models) ? parsed.models : [];
4555
+ const preferred = rows.filter((m) => (m.visibility ?? "list") === "list");
4556
+ const source = preferred.length > 0 ? preferred : rows;
4557
+ const models = source.map((m) => ({
4558
+ id: (m.slug || "").trim(),
4559
+ displayName: (m.display_name || m.slug || "").trim(),
4560
+ description: m.description,
4561
+ priority: typeof m.priority === "number" ? m.priority : 999
4562
+ })).filter((m) => m.id).sort((a, b) => a.priority - b.priority).map(({ id, displayName, description }) => ({ id, displayName, description }));
4563
+ if (models.length === 0) return FALLBACK_CODEX_MODELS;
4564
+ cachedCodexModels = { at: now, models };
4565
+ return models;
4566
+ } catch {
4567
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
4568
+ }
4569
+ }
4541
4570
  function usageFromCodex(usage) {
4542
4571
  if (!usage) return null;
4543
4572
  const inputTokens = Number(usage.input_tokens ?? 0);
@@ -4561,7 +4590,7 @@ function codexConfigHasNetworkAccess() {
4561
4590
  }
4562
4591
  return false;
4563
4592
  }
4564
- var import_node_fs12, import_node_os7, import_node_path12, CODEX_PROMPT_ARG_MAX, codexAdapter;
4593
+ var import_node_fs12, import_node_os7, import_node_path12, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
4565
4594
  var init_codex = __esm({
4566
4595
  "src/agents/codex.ts"() {
4567
4596
  "use strict";
@@ -4572,6 +4601,15 @@ var init_codex = __esm({
4572
4601
  init_turn_input();
4573
4602
  init_types();
4574
4603
  CODEX_PROMPT_ARG_MAX = 2e5;
4604
+ FALLBACK_CODEX_MODELS = [
4605
+ { id: "gpt-5.6-sol", displayName: "GPT-5.6 Sol" },
4606
+ { id: "gpt-5.6-terra", displayName: "GPT-5.6 Terra" },
4607
+ { id: "gpt-5.6-luna", displayName: "GPT-5.6 Luna" },
4608
+ { id: "gpt-5.5", displayName: "GPT-5.5" },
4609
+ { id: "gpt-5.2", displayName: "GPT-5.2" }
4610
+ ];
4611
+ cachedCodexModels = null;
4612
+ CODEX_MODEL_CACHE_MS = 5 * 60 * 1e3;
4575
4613
  codexAdapter = {
4576
4614
  kind: "codex",
4577
4615
  async detect() {
@@ -4617,6 +4655,7 @@ var init_codex = __esm({
4617
4655
  );
4618
4656
  }
4619
4657
  const mode = permissionMode(thread);
4658
+ const model = thread.model?.trim();
4620
4659
  const args = [
4621
4660
  "exec",
4622
4661
  ...sessionId ? ["resume", sessionId] : [],
@@ -4627,7 +4666,8 @@ var init_codex = __esm({
4627
4666
  "--sandbox",
4628
4667
  mode.codexSandbox,
4629
4668
  "--ask-for-approval",
4630
- "never"
4669
+ "never",
4670
+ ...model ? ["--model", model] : []
4631
4671
  ];
4632
4672
  return {
4633
4673
  file: "codex",
@@ -4808,6 +4848,35 @@ function resolveCursorApiKey() {
4808
4848
  if (fromEnv) return fromEnv;
4809
4849
  return (loadAppSettings().environment.CURSOR_API_KEY || "").trim();
4810
4850
  }
4851
+ function isCursorAutoModel(model) {
4852
+ const id = (model ?? "").trim().toLowerCase();
4853
+ return !id || id === "default" || id === "auto";
4854
+ }
4855
+ function resolveCursorModelId(model) {
4856
+ if (isCursorAutoModel(model)) return "default";
4857
+ return model.trim();
4858
+ }
4859
+ async function listCursorModels() {
4860
+ const now = Date.now();
4861
+ if (cachedModels && now - cachedModels.at < MODEL_CACHE_MS) {
4862
+ return cachedModels.models;
4863
+ }
4864
+ const apiKey = resolveCursorApiKey();
4865
+ if (!apiKey) return FALLBACK_CURSOR_MODELS;
4866
+ try {
4867
+ const listed = await import_sdk.Cursor.models.list({ apiKey });
4868
+ const models = listed.map((m) => ({
4869
+ id: m.id,
4870
+ displayName: m.displayName || m.id,
4871
+ description: m.description
4872
+ })).filter((m) => Boolean(m.id));
4873
+ if (models.length === 0) return FALLBACK_CURSOR_MODELS;
4874
+ cachedModels = { at: now, models };
4875
+ return models;
4876
+ } catch {
4877
+ return cachedModels?.models ?? FALLBACK_CURSOR_MODELS;
4878
+ }
4879
+ }
4811
4880
  function entryDir() {
4812
4881
  const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
4813
4882
  if (cjsDir) return cjsDir;
@@ -4839,7 +4908,7 @@ function cursorRunnerPath() {
4839
4908
  }
4840
4909
  return candidates[0];
4841
4910
  }
4842
- var import_node_fs13, import_node_module2, import_node_path13, import_node_url2, import_sdk, import_meta2, cursorAdapter;
4911
+ var import_node_fs13, import_node_module2, import_node_path13, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
4843
4912
  var init_cursor = __esm({
4844
4913
  "src/agents/cursor.ts"() {
4845
4914
  "use strict";
@@ -4854,6 +4923,13 @@ var init_cursor = __esm({
4854
4923
  init_turn_input();
4855
4924
  init_cursor_events();
4856
4925
  import_meta2 = {};
4926
+ FALLBACK_CURSOR_MODELS = [
4927
+ { id: "default", displayName: "Auto" },
4928
+ { id: "composer-2.5", displayName: "Composer 2.5" },
4929
+ { id: "composer-2", displayName: "Composer 2" }
4930
+ ];
4931
+ cachedModels = null;
4932
+ MODEL_CACHE_MS = 5 * 60 * 1e3;
4857
4933
  cursorAdapter = {
4858
4934
  kind: "cursor",
4859
4935
  async detect() {
@@ -4904,12 +4980,16 @@ var init_cursor = __esm({
4904
4980
  };
4905
4981
  const runner = cursorRunnerPath();
4906
4982
  const isTs = runner.endsWith(".ts");
4983
+ const whichNode = await run("which", ["node"], { reject: false });
4984
+ const nodeBin = whichNode.exitCode === 0 && whichNode.stdout.trim() ? whichNode.stdout.trim() : null;
4985
+ const file = nodeBin || process.execPath;
4907
4986
  return {
4908
- file: process.execPath,
4987
+ file,
4909
4988
  args: isTs ? ["--import", "tsx", runner] : [runner],
4910
4989
  cwd: thread.worktreePath,
4911
4990
  stdin: JSON.stringify(req),
4912
4991
  env: {
4992
+ ...nodeBin ? {} : { ELECTRON_RUN_AS_NODE: "1" },
4913
4993
  ...apiKey ? { CURSOR_API_KEY: apiKey } : {}
4914
4994
  }
4915
4995
  };
@@ -4934,6 +5014,46 @@ var init_cursor = __esm({
4934
5014
  });
4935
5015
 
4936
5016
  // src/agents/opencode.ts
5017
+ function displayNameFromOpencodeId(id) {
5018
+ const slash = id.indexOf("/");
5019
+ if (slash <= 0) return id;
5020
+ const provider = id.slice(0, slash);
5021
+ const name = id.slice(slash + 1);
5022
+ return `${provider} \xB7 ${name}`;
5023
+ }
5024
+ function sortOpencodeModelIds(ids) {
5025
+ return [...ids].sort((a, b) => {
5026
+ const aLatest = /latest|~/.test(a) ? 0 : 1;
5027
+ const bLatest = /latest|~/.test(b) ? 0 : 1;
5028
+ if (aLatest !== bLatest) return aLatest - bLatest;
5029
+ const aOc = a.startsWith("opencode/") ? 0 : 1;
5030
+ const bOc = b.startsWith("opencode/") ? 0 : 1;
5031
+ if (aOc !== bOc) return aOc - bOc;
5032
+ return a.localeCompare(b);
5033
+ });
5034
+ }
5035
+ async function listOpencodeModels() {
5036
+ const now = Date.now();
5037
+ if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
5038
+ return cachedOpencodeModels.models;
5039
+ }
5040
+ const which = await run("which", ["opencode"], { reject: false });
5041
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
5042
+ const listed = await run("opencode", ["models"], { reject: false });
5043
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5044
+ return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
5045
+ }
5046
+ const ids = listed.stdout.split("\n").map((l) => l.trim()).filter((l) => /^[\w.~@+-]+\/[\w.~@+/-]+$/.test(l));
5047
+ const unique = [...new Set(ids)];
5048
+ if (unique.length === 0) return FALLBACK_OPENCODE_MODELS;
5049
+ const OPENCODE_PICKER_LIMIT = 60;
5050
+ const models = sortOpencodeModelIds(unique).slice(0, OPENCODE_PICKER_LIMIT).map((id) => ({
5051
+ id,
5052
+ displayName: displayNameFromOpencodeId(id)
5053
+ }));
5054
+ cachedOpencodeModels = { at: now, models };
5055
+ return models;
5056
+ }
4937
5057
  function usageFromOpencode(tokens) {
4938
5058
  if (!tokens) return null;
4939
5059
  const inputTokens = Number(tokens.input ?? 0);
@@ -4946,13 +5066,26 @@ function usageFromOpencode(tokens) {
4946
5066
  cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
4947
5067
  };
4948
5068
  }
4949
- var opencodeAdapter;
5069
+ var FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
4950
5070
  var init_opencode = __esm({
4951
5071
  "src/agents/opencode.ts"() {
4952
5072
  "use strict";
4953
5073
  init_run();
4954
5074
  init_turn_input();
4955
5075
  init_types();
5076
+ FALLBACK_OPENCODE_MODELS = [
5077
+ { id: "opencode/big-pickle", displayName: "opencode \xB7 big-pickle" },
5078
+ {
5079
+ id: "openrouter/~anthropic/claude-sonnet-latest",
5080
+ displayName: "openrouter \xB7 claude-sonnet-latest"
5081
+ },
5082
+ {
5083
+ id: "openrouter/~openai/gpt-latest",
5084
+ displayName: "openrouter \xB7 gpt-latest"
5085
+ }
5086
+ ];
5087
+ cachedOpencodeModels = null;
5088
+ OPENCODE_MODEL_CACHE_MS = 5 * 60 * 1e3;
4956
5089
  opencodeAdapter = {
4957
5090
  kind: "opencode",
4958
5091
  async detect() {
@@ -4984,6 +5117,7 @@ var init_opencode = __esm({
4984
5117
  const prompt = flattenTurnInput(input);
4985
5118
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
4986
5119
  const mode = permissionMode(thread);
5120
+ const model = thread.model?.trim();
4987
5121
  const args = [
4988
5122
  "run",
4989
5123
  "--dir",
@@ -4994,6 +5128,9 @@ var init_opencode = __esm({
4994
5128
  if (sessionId) {
4995
5129
  args.push("--session", sessionId);
4996
5130
  }
5131
+ if (model) {
5132
+ args.push("--model", model);
5133
+ }
4997
5134
  return {
4998
5135
  file: "opencode",
4999
5136
  args,
@@ -5121,6 +5258,183 @@ var init_opencode = __esm({
5121
5258
  }
5122
5259
  });
5123
5260
 
5261
+ // src/agents/install.ts
5262
+ function getAgentSetupInfo(agent) {
5263
+ return SETUP[agent];
5264
+ }
5265
+ function listAgentSetupInfo() {
5266
+ return Object.values(SETUP);
5267
+ }
5268
+ async function openInSystemTerminal(command) {
5269
+ ensureAgentPath();
5270
+ const trimmed = command.trim();
5271
+ if (!trimmed) throw new Error("Command is empty");
5272
+ if (process.platform === "darwin") {
5273
+ const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5274
+ const script = `tell application "Terminal" to do script "${escaped}"`;
5275
+ const result = await run("osascript", ["-e", script], { reject: false });
5276
+ if (result.exitCode !== 0) {
5277
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open Terminal");
5278
+ }
5279
+ await run("osascript", ["-e", 'tell application "Terminal" to activate'], {
5280
+ reject: false
5281
+ });
5282
+ return;
5283
+ }
5284
+ if (process.platform === "win32") {
5285
+ const result = await run(
5286
+ "cmd.exe",
5287
+ ["/c", "start", "cmd.exe", "/k", trimmed],
5288
+ { reject: false }
5289
+ );
5290
+ if (result.exitCode !== 0) {
5291
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open cmd");
5292
+ }
5293
+ return;
5294
+ }
5295
+ const candidates = [
5296
+ { file: "gnome-terminal", args: ["--", "bash", "-lc", `${trimmed}; exec bash`] },
5297
+ { file: "x-terminal-emulator", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
5298
+ { file: "konsole", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
5299
+ { file: "xterm", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] }
5300
+ ];
5301
+ for (const c of candidates) {
5302
+ const which = await run("which", [c.file], { reject: false });
5303
+ if (which.exitCode !== 0 || !which.stdout.trim()) continue;
5304
+ const result = await run(c.file, c.args, { reject: false });
5305
+ if (result.exitCode === 0) return;
5306
+ }
5307
+ throw new Error(
5308
+ `No terminal found to run: ${trimmed}. Install gnome-terminal (or similar), or run the command manually.`
5309
+ );
5310
+ }
5311
+ async function installAgent(agent) {
5312
+ const info = getAgentSetupInfo(agent);
5313
+ if (info.kind === "bundled-sdk" || info.kind === "api-key") {
5314
+ return {
5315
+ ok: true,
5316
+ message: info.summary
5317
+ };
5318
+ }
5319
+ if (!info.installCommand) {
5320
+ return { ok: false, message: `No install command for ${agent}` };
5321
+ }
5322
+ if (/curl\s| \|\s*bash/.test(info.installCommand) || !info.npmPackage) {
5323
+ await openInSystemTerminal(info.installCommand);
5324
+ return {
5325
+ ok: true,
5326
+ openedTerminal: true,
5327
+ command: info.installCommand,
5328
+ message: `Opened Terminal to run: ${info.installCommand}`
5329
+ };
5330
+ }
5331
+ ensureAgentPath();
5332
+ const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
5333
+ const ok = result.exitCode === 0;
5334
+ if (!ok) {
5335
+ try {
5336
+ await openInSystemTerminal(info.installCommand);
5337
+ return {
5338
+ ok: false,
5339
+ openedTerminal: true,
5340
+ command: info.installCommand,
5341
+ exitCode: result.exitCode,
5342
+ stdout: result.stdout,
5343
+ stderr: result.stderr,
5344
+ message: `npm install failed (exit ${result.exitCode}). Opened Terminal with: ${info.installCommand}`
5345
+ };
5346
+ } catch {
5347
+ return {
5348
+ ok: false,
5349
+ command: info.installCommand,
5350
+ exitCode: result.exitCode,
5351
+ stdout: result.stdout,
5352
+ stderr: result.stderr,
5353
+ message: result.stderr.trim() || result.stdout.trim() || `npm install failed (exit ${result.exitCode})`
5354
+ };
5355
+ }
5356
+ }
5357
+ return {
5358
+ ok: true,
5359
+ command: info.installCommand,
5360
+ exitCode: 0,
5361
+ stdout: result.stdout,
5362
+ stderr: result.stderr,
5363
+ message: `Installed ${info.npmPackage}`
5364
+ };
5365
+ }
5366
+ async function loginAgent(agent) {
5367
+ const info = getAgentSetupInfo(agent);
5368
+ if (!info.loginCommand) {
5369
+ return {
5370
+ ok: true,
5371
+ message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
5372
+ };
5373
+ }
5374
+ await openInSystemTerminal(info.loginCommand);
5375
+ return {
5376
+ ok: true,
5377
+ openedTerminal: true,
5378
+ command: info.loginCommand,
5379
+ message: `Opened Terminal to run: ${info.loginCommand}`
5380
+ };
5381
+ }
5382
+ var SETUP;
5383
+ var init_install = __esm({
5384
+ "src/agents/install.ts"() {
5385
+ "use strict";
5386
+ init_run();
5387
+ init_path();
5388
+ SETUP = {
5389
+ claude: {
5390
+ agent: "claude",
5391
+ kind: "cli",
5392
+ summary: "Install the Claude Code CLI, then complete login in a terminal.",
5393
+ docsUrl: "https://code.claude.com/docs/en/install",
5394
+ installCommand: "npm install -g @anthropic-ai/claude-code",
5395
+ loginCommand: "claude auth login",
5396
+ npmPackage: "@anthropic-ai/claude-code"
5397
+ },
5398
+ codex: {
5399
+ agent: "codex",
5400
+ kind: "cli",
5401
+ summary: "Install the Codex CLI, then run login in a terminal.",
5402
+ docsUrl: "https://github.com/openai/codex",
5403
+ installCommand: "npm install -g @openai/codex",
5404
+ loginCommand: "codex login",
5405
+ npmPackage: "@openai/codex"
5406
+ },
5407
+ opencode: {
5408
+ agent: "opencode",
5409
+ kind: "cli",
5410
+ summary: "Install OpenCode (curl installer or npm), then authenticate providers.",
5411
+ docsUrl: "https://opencode.ai/docs",
5412
+ // Prefer the official installer; npm package name is opencode-ai.
5413
+ installCommand: "curl -fsSL https://opencode.ai/install | bash",
5414
+ loginCommand: "opencode auth login",
5415
+ npmPackage: "opencode-ai@latest"
5416
+ },
5417
+ cursor: {
5418
+ agent: "cursor",
5419
+ kind: "bundled-sdk",
5420
+ summary: "No CLI install \u2014 Sideboard ships the Cursor SDK. Add a CURSOR_API_KEY from the Cursor dashboard.",
5421
+ docsUrl: "https://cursor.com/dashboard/integrations",
5422
+ installCommand: null,
5423
+ loginCommand: null
5424
+ },
5425
+ brightsy: {
5426
+ agent: "brightsy",
5427
+ kind: "cli",
5428
+ summary: "Install the Brightsy CLI, then run `brightsy login`.",
5429
+ docsUrl: "https://www.npmjs.com/package/@brightsy/cli",
5430
+ installCommand: "npm install -g @brightsy/cli",
5431
+ loginCommand: "brightsy login",
5432
+ npmPackage: "@brightsy/cli"
5433
+ }
5434
+ };
5435
+ }
5436
+ });
5437
+
5124
5438
  // src/agents/index.ts
5125
5439
  var agents_exports = {};
5126
5440
  __export(agents_exports, {
@@ -5135,10 +5449,20 @@ __export(agents_exports, {
5135
5449
  encodeBrightsyTarget: () => encodeBrightsyTarget,
5136
5450
  ensureAgentPath: () => ensureAgentPath,
5137
5451
  getAdapter: () => getAdapter,
5452
+ getAgentSetupInfo: () => getAgentSetupInfo,
5453
+ installAgent: () => installAgent,
5454
+ isCursorAutoModel: () => isCursorAutoModel,
5455
+ listAgentSetupInfo: () => listAgentSetupInfo,
5138
5456
  listBrightsyChatTargets: () => listBrightsyChatTargets,
5457
+ listCodexModels: () => listCodexModels,
5458
+ listCursorModels: () => listCursorModels,
5459
+ listOpencodeModels: () => listOpencodeModels,
5460
+ loginAgent: () => loginAgent,
5461
+ openInSystemTerminal: () => openInSystemTerminal,
5139
5462
  opencodeAdapter: () => opencodeAdapter,
5140
5463
  parseCursorRunnerLine: () => parseCursorRunnerLine,
5141
- permissionMode: () => permissionMode
5464
+ permissionMode: () => permissionMode,
5465
+ resolveCursorModelId: () => resolveCursorModelId
5142
5466
  });
5143
5467
  function getAdapter(kind) {
5144
5468
  return adapters[kind];
@@ -5162,8 +5486,10 @@ var init_agents = __esm({
5162
5486
  init_codex();
5163
5487
  init_cursor();
5164
5488
  init_cursor_events();
5489
+ init_cursor();
5165
5490
  init_opencode();
5166
5491
  init_path();
5492
+ init_install();
5167
5493
  adapters = {
5168
5494
  claude: claudeAdapter,
5169
5495
  codex: codexAdapter,
@@ -5241,6 +5567,9 @@ __export(index_exports, {
5241
5567
  applyAppEnvironment: () => applyAppEnvironment,
5242
5568
  applyCompaction: () => applyCompaction,
5243
5569
  applyThreadIntoMain: () => applyThreadIntoMain,
5570
+ attachmentFromAbsolutePath: () => attachmentFromAbsolutePath,
5571
+ attachmentsFromBuffers: () => attachmentsFromBuffers,
5572
+ attachmentsFromWorktreePaths: () => attachmentsFromWorktreePaths,
5244
5573
  autoCleanupOrphansEnabled: () => autoCleanupOrphansEnabled,
5245
5574
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
5246
5575
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
@@ -5326,6 +5655,7 @@ __export(index_exports, {
5326
5655
  formatWorkspaceInventory: () => formatWorkspaceInventory,
5327
5656
  formatWorktreeDirective: () => formatWorktreeDirective,
5328
5657
  getAdapter: () => getAdapter,
5658
+ getAgentSetupInfo: () => getAgentSetupInfo,
5329
5659
  getBrightsySession: () => getBrightsySession,
5330
5660
  getDefaultRunScript: () => getDefaultRunScript,
5331
5661
  getDiff: () => getDiff,
@@ -5356,27 +5686,34 @@ __export(index_exports, {
5356
5686
  importConductorWorkspaceAsync: () => importConductorWorkspaceAsync,
5357
5687
  initializeGitRepository: () => initializeGitRepository,
5358
5688
  inspectGitWorktree: () => inspectGitWorktree,
5689
+ installAgent: () => installAgent,
5359
5690
  isBrightsyConnected: () => isBrightsyConnected,
5360
5691
  isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
5361
5692
  isCloudCoordinatorThread: () => isCloudCoordinatorThread,
5693
+ isCursorAutoModel: () => isCursorAutoModel,
5362
5694
  isDirty: () => isDirty,
5363
5695
  isGhRateLimitError: () => isGhRateLimitError,
5364
5696
  isGlobalRepoPath: () => isGlobalRepoPath,
5365
5697
  isGlobalThread: () => isGlobalThread,
5698
+ isImageFilePath: () => isImageFilePath,
5366
5699
  isLinearConnected: () => isLinearConnected,
5367
5700
  isOrchestratorThread: () => isOrchestratorThread,
5368
5701
  isPlaceholderBranch: () => isPlaceholderBranch,
5702
+ listAgentSetupInfo: () => listAgentSetupInfo,
5369
5703
  listBranchCommits: () => listBranchCommits,
5370
5704
  listBranches: () => listBranches,
5371
5705
  listBrightsyAccounts: () => listBrightsyAccounts,
5372
5706
  listBrightsyChatTargets: () => listBrightsyChatTargets,
5707
+ listCodexModels: () => listCodexModels,
5373
5708
  listConductorWorkspaces: () => listConductorWorkspaces,
5374
5709
  listConnectedBrightsyTeams: () => listConnectedBrightsyTeams,
5710
+ listCursorModels: () => listCursorModels,
5375
5711
  listGitHubIssues: () => listGitHubIssues,
5376
5712
  listGlobalThreads: () => listGlobalThreads,
5377
5713
  listIssues: () => listIssues,
5378
5714
  listLinearIssues: () => listLinearIssues,
5379
5715
  listLinearIssuesDirect: () => listLinearIssuesDirect,
5716
+ listOpencodeModels: () => listOpencodeModels,
5380
5717
  listPrs: () => listPrs,
5381
5718
  listRunScripts: () => listRunScripts,
5382
5719
  listThreads: () => listThreads,
@@ -5390,6 +5727,7 @@ __export(index_exports, {
5390
5727
  loadRepoSettings: () => loadRepoSettings,
5391
5728
  loadWorkspaceSettings: () => loadWorkspaceSettings,
5392
5729
  locksDir: () => locksDir,
5730
+ loginAgent: () => loginAgent,
5393
5731
  lookupSoccerTeam: () => lookupSoccerTeam,
5394
5732
  maxConcurrentAgents: () => maxConcurrentAgents,
5395
5733
  maybeCompactContext: () => maybeCompactContext,
@@ -5401,6 +5739,7 @@ __export(index_exports, {
5401
5739
  normalizeThread: () => normalizeThread,
5402
5740
  normalizeTurnInput: () => normalizeTurnInput,
5403
5741
  normalizeWorktreePath: () => normalizeWorktreePath,
5742
+ openInSystemTerminal: () => openInSystemTerminal,
5404
5743
  opencodeAdapter: () => opencodeAdapter,
5405
5744
  orchestrationTitleNeedsSoccerNickname: () => orchestrationTitleNeedsSoccerNickname,
5406
5745
  orchestratorSessionPoisonedByBuiltins: () => orchestratorSessionPoisonedByBuiltins,
@@ -5425,6 +5764,7 @@ __export(index_exports, {
5425
5764
  requireAgent: () => requireAgent,
5426
5765
  resolveClaudeExecutable: () => resolveClaudeExecutable,
5427
5766
  resolveConductorCursorAgentId: () => resolveConductorCursorAgentId,
5767
+ resolveCursorModelId: () => resolveCursorModelId,
5428
5768
  resolveDefaultBranch: () => resolveDefaultBranch,
5429
5769
  resolveDiffBaseRef: () => resolveDiffBaseRef,
5430
5770
  resolveEffectiveIssueSource: () => resolveEffectiveIssueSource,
@@ -5451,6 +5791,8 @@ __export(index_exports, {
5451
5791
  slugify: () => slugify,
5452
5792
  spawnAgentTurn: () => spawnAgentTurn,
5453
5793
  splitForCompaction: () => splitForCompaction,
5794
+ stageAbsolutePathsAsAttachments: () => stageAbsolutePathsAsAttachments,
5795
+ stageBuffersAsAttachments: () => stageBuffersAsAttachments,
5454
5796
  startDevServer: () => startDevServer,
5455
5797
  startMcpServer: () => startMcpServer,
5456
5798
  startOrchestration: () => startOrchestration,
@@ -7257,6 +7599,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
7257
7599
  parts.push("");
7258
7600
  parts.push(`## Attachment: ${att.name}`);
7259
7601
  parts.push(`Kind: ${att.kind}`);
7602
+ if (att.path) {
7603
+ parts.push(`Path in worktree: \`${att.path}\``);
7604
+ }
7260
7605
  parts.push("");
7261
7606
  parts.push(att.content);
7262
7607
  }
@@ -7342,10 +7687,239 @@ function buildDiffCommentAttachment(input) {
7342
7687
  id: input.id ?? `diff-comment-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
7343
7688
  name,
7344
7689
  kind: "diff-comment",
7690
+ path: input.path.trim(),
7345
7691
  content
7346
7692
  };
7347
7693
  }
7348
7694
 
7695
+ // src/composer/stage-files.ts
7696
+ var import_node_fs19 = require("fs");
7697
+ var import_node_path19 = require("path");
7698
+ var import_node_crypto2 = require("crypto");
7699
+ var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
7700
+ "png",
7701
+ "jpg",
7702
+ "jpeg",
7703
+ "gif",
7704
+ "webp",
7705
+ "svg",
7706
+ "bmp",
7707
+ "ico"
7708
+ ]);
7709
+ var IMAGE_MIME_BY_EXT = {
7710
+ png: "image/png",
7711
+ jpg: "image/jpeg",
7712
+ jpeg: "image/jpeg",
7713
+ gif: "image/gif",
7714
+ webp: "image/webp",
7715
+ svg: "image/svg+xml",
7716
+ bmp: "image/bmp",
7717
+ ico: "image/x-icon"
7718
+ };
7719
+ var ATTACHMENTS_DIR = ".sideboard/attachments";
7720
+ var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
7721
+ *
7722
+ !.gitignore
7723
+ `;
7724
+ var MAX_INLINE_BYTES = 4e5;
7725
+ var MAX_PREVIEW_BYTES = 5e6;
7726
+ function fileExtension(filePath) {
7727
+ const base = (0, import_node_path19.basename)(filePath).toLowerCase();
7728
+ return base.includes(".") ? base.split(".").pop() || "" : "";
7729
+ }
7730
+ function isImageFilePath(filePath) {
7731
+ return IMAGE_EXTENSIONS2.has(fileExtension(filePath));
7732
+ }
7733
+ function imageMimeType(filePath) {
7734
+ return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
7735
+ }
7736
+ function ensureAttachmentsDir(worktreePath) {
7737
+ const dir = (0, import_node_path19.join)(worktreePath, ATTACHMENTS_DIR);
7738
+ (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
7739
+ const gi = (0, import_node_path19.join)(dir, ".gitignore");
7740
+ if (!(0, import_node_fs19.existsSync)(gi)) {
7741
+ (0, import_node_fs19.writeFileSync)(gi, ATTACHMENTS_GITIGNORE, "utf8");
7742
+ }
7743
+ return dir;
7744
+ }
7745
+ function uniqueAttachmentName(dir, originalName) {
7746
+ const safe = originalName.replace(/[/\\]/g, "_") || "file";
7747
+ if (!(0, import_node_fs19.existsSync)((0, import_node_path19.join)(dir, safe))) return safe;
7748
+ const ext = (0, import_node_path19.extname)(safe);
7749
+ const stem = ext ? safe.slice(0, -ext.length) : safe;
7750
+ for (let i = 1; i < 1e4; i++) {
7751
+ const candidate = `${stem}-${i}${ext}`;
7752
+ if (!(0, import_node_fs19.existsSync)((0, import_node_path19.join)(dir, candidate))) return candidate;
7753
+ }
7754
+ return `${stem}-${(0, import_node_crypto2.randomUUID)()}${ext}`;
7755
+ }
7756
+ function previewDataUrlFromBuf(filePath, buf) {
7757
+ if (!isImageFilePath(filePath)) return void 0;
7758
+ if (buf.length > MAX_PREVIEW_BYTES) return void 0;
7759
+ return `data:${imageMimeType(filePath)};base64,${buf.toString("base64")}`;
7760
+ }
7761
+ function attachmentFromBuffer(name, buf, opts) {
7762
+ const previewDataUrl = previewDataUrlFromBuf(name, buf);
7763
+ if (isImageFilePath(name)) {
7764
+ const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
7765
+ return {
7766
+ id: (0, import_node_crypto2.randomUUID)(),
7767
+ name,
7768
+ kind: "file",
7769
+ path: opts.path,
7770
+ previewDataUrl,
7771
+ content: [
7772
+ `Image attached: ${pathHint}`,
7773
+ opts.path ? `Use the Read tool on \`${opts.path}\` to view this image.` : "The image is shown in the composer; copy it into the worktree if you need to inspect pixels."
7774
+ ].join("\n")
7775
+ };
7776
+ }
7777
+ if (buf.length > MAX_INLINE_BYTES) {
7778
+ return {
7779
+ id: (0, import_node_crypto2.randomUUID)(),
7780
+ name,
7781
+ kind: "file",
7782
+ path: opts.path,
7783
+ content: opts.path ? `(file too large to attach inline: \`${opts.path}\`, ${buf.length} bytes \u2014 use the Read tool)` : `(file too large to attach inline: ${opts.sourceLabel || name}, ${buf.length} bytes)`
7784
+ };
7785
+ }
7786
+ if (buf.includes(0)) {
7787
+ return {
7788
+ id: (0, import_node_crypto2.randomUUID)(),
7789
+ name,
7790
+ kind: "file",
7791
+ path: opts.path,
7792
+ content: opts.path ? `(binary file at \`${opts.path}\` \u2014 use tools to inspect)` : `(binary file attached by path only: ${opts.sourceLabel || name})`
7793
+ };
7794
+ }
7795
+ return {
7796
+ id: (0, import_node_crypto2.randomUUID)(),
7797
+ name,
7798
+ kind: "file",
7799
+ path: opts.path,
7800
+ content: buf.toString("utf8")
7801
+ };
7802
+ }
7803
+ function attachmentFromAbsolutePath(absolutePath) {
7804
+ const name = (0, import_node_path19.basename)(absolutePath);
7805
+ try {
7806
+ const st = (0, import_node_fs19.statSync)(absolutePath);
7807
+ if (!st.isFile()) {
7808
+ return {
7809
+ id: (0, import_node_crypto2.randomUUID)(),
7810
+ name,
7811
+ kind: "file",
7812
+ content: `(not a file: ${absolutePath})`
7813
+ };
7814
+ }
7815
+ const buf = (0, import_node_fs19.readFileSync)(absolutePath);
7816
+ return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
7817
+ } catch (err) {
7818
+ return {
7819
+ id: (0, import_node_crypto2.randomUUID)(),
7820
+ name,
7821
+ kind: "file",
7822
+ content: `(could not read ${absolutePath}: ${err instanceof Error ? err.message : String(err)})`
7823
+ };
7824
+ }
7825
+ }
7826
+ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
7827
+ if (absolutePaths.length === 0) return [];
7828
+ const dir = ensureAttachmentsDir(worktreePath);
7829
+ const out = [];
7830
+ for (const abs of absolutePaths) {
7831
+ const originalName = (0, import_node_path19.basename)(abs);
7832
+ try {
7833
+ const st = (0, import_node_fs19.statSync)(abs);
7834
+ if (!st.isFile()) continue;
7835
+ const name = uniqueAttachmentName(dir, originalName);
7836
+ const destAbs = (0, import_node_path19.join)(dir, name);
7837
+ (0, import_node_fs19.copyFileSync)(abs, destAbs);
7838
+ const rel = `${ATTACHMENTS_DIR}/${name}`;
7839
+ const buf = (0, import_node_fs19.readFileSync)(destAbs);
7840
+ out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
7841
+ } catch (err) {
7842
+ out.push({
7843
+ id: (0, import_node_crypto2.randomUUID)(),
7844
+ name: originalName,
7845
+ kind: "file",
7846
+ content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
7847
+ });
7848
+ }
7849
+ }
7850
+ return out;
7851
+ }
7852
+ function stageBuffersAsAttachments(worktreePath, buffers) {
7853
+ if (buffers.length === 0) return [];
7854
+ const dir = ensureAttachmentsDir(worktreePath);
7855
+ const out = [];
7856
+ for (const item of buffers) {
7857
+ const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
7858
+ try {
7859
+ const buf = Buffer.from(item.dataBase64, "base64");
7860
+ const name = uniqueAttachmentName(dir, originalName);
7861
+ const destAbs = (0, import_node_path19.join)(dir, name);
7862
+ (0, import_node_fs19.writeFileSync)(destAbs, buf);
7863
+ const rel = `${ATTACHMENTS_DIR}/${name}`;
7864
+ out.push(attachmentFromBuffer(name, buf, { path: rel }));
7865
+ } catch (err) {
7866
+ out.push({
7867
+ id: (0, import_node_crypto2.randomUUID)(),
7868
+ name: originalName,
7869
+ kind: "file",
7870
+ content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
7871
+ });
7872
+ }
7873
+ }
7874
+ return out;
7875
+ }
7876
+ function attachmentsFromBuffers(buffers) {
7877
+ return buffers.map((item) => {
7878
+ const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
7879
+ try {
7880
+ const buf = Buffer.from(item.dataBase64, "base64");
7881
+ return attachmentFromBuffer(name, buf, { sourceLabel: name });
7882
+ } catch (err) {
7883
+ return {
7884
+ id: (0, import_node_crypto2.randomUUID)(),
7885
+ name,
7886
+ kind: "file",
7887
+ content: `(could not attach ${name}: ${err instanceof Error ? err.message : String(err)})`
7888
+ };
7889
+ }
7890
+ });
7891
+ }
7892
+ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
7893
+ const out = [];
7894
+ for (const rel of relativePaths) {
7895
+ if (!rel || rel.includes("..") || rel.startsWith("/")) {
7896
+ out.push({
7897
+ id: (0, import_node_crypto2.randomUUID)(),
7898
+ name: (0, import_node_path19.basename)(rel) || "file",
7899
+ kind: "file",
7900
+ content: `(invalid path: ${rel})`
7901
+ });
7902
+ continue;
7903
+ }
7904
+ const name = (0, import_node_path19.basename)(rel);
7905
+ try {
7906
+ const abs = (0, import_node_path19.join)(worktreePath, rel);
7907
+ const st = (0, import_node_fs19.statSync)(abs);
7908
+ if (!st.isFile()) continue;
7909
+ const buf = (0, import_node_fs19.readFileSync)(abs);
7910
+ out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
7911
+ } catch (err) {
7912
+ out.push({
7913
+ id: (0, import_node_crypto2.randomUUID)(),
7914
+ name,
7915
+ kind: "file",
7916
+ content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
7917
+ });
7918
+ }
7919
+ }
7920
+ return out;
7921
+ }
7922
+
7349
7923
  // src/composer/summarize.ts
7350
7924
  init_run();
7351
7925
  init_path();
@@ -7784,14 +8358,14 @@ async function confirmLand(thread, opts) {
7784
8358
  }
7785
8359
 
7786
8360
  // src/threads/create.ts
7787
- var import_node_fs19 = require("fs");
8361
+ var import_node_fs20 = require("fs");
7788
8362
  init_worktree();
7789
8363
  init_thread_store();
7790
8364
  init_workspaces();
7791
8365
  async function createThread(input, onSetupLine) {
7792
8366
  await requireAgent(input.agent);
7793
8367
  const repoPath = await resolveRepoRoot(input.repoPath);
7794
- if (!(0, import_node_fs19.existsSync)(repoPath)) {
8368
+ if (!(0, import_node_fs20.existsSync)(repoPath)) {
7795
8369
  throw new Error(`Repo not found: ${repoPath}`);
7796
8370
  }
7797
8371
  let sourceRef = input.sourceRef;
@@ -7874,7 +8448,7 @@ async function listLinearIssues(agent, repoPath) {
7874
8448
  }
7875
8449
 
7876
8450
  // src/threads/chat-tabs.ts
7877
- var import_node_crypto2 = require("crypto");
8451
+ var import_node_crypto3 = require("crypto");
7878
8452
  init_teams();
7879
8453
  init_worktree_labels();
7880
8454
  init_global_workspace();
@@ -7930,7 +8504,7 @@ function forkMessageSlice(from, throughIndex) {
7930
8504
  function buildForkTranscriptAttachment(baseTitle, messages) {
7931
8505
  const title = baseTitle || "Chat";
7932
8506
  return {
7933
- id: (0, import_node_crypto2.randomUUID)(),
8507
+ id: (0, import_node_crypto3.randomUUID)(),
7934
8508
  name: `Transcript of ${title}.md`,
7935
8509
  kind: "transcript",
7936
8510
  content: formatTranscriptMarkdown(title, messages)
@@ -7949,10 +8523,10 @@ function createChatTab(input) {
7949
8523
  userSetTitle: true,
7950
8524
  ...binding,
7951
8525
  agent: input.agent ?? from.agent,
7952
- model: input.agent && input.agent !== from.agent ? null : from.model,
8526
+ model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
7953
8527
  fast: from.fast,
7954
8528
  planMode: from.planMode,
7955
- autonomy: from.autonomy,
8529
+ autonomy: input.autonomy ?? from.autonomy,
7956
8530
  attachments: input.attachments ?? [],
7957
8531
  status: "idle"
7958
8532
  });
@@ -7989,30 +8563,34 @@ async function forkThreadWorktree(input, onSetupLine) {
7989
8563
  repoPath: from.repoPath,
7990
8564
  agent: input.agent ?? from.agent,
7991
8565
  autonomy: from.autonomy,
8566
+ model: from.model,
8567
+ fast: from.fast,
8568
+ planMode: from.planMode,
7992
8569
  title: input.title?.trim() || void 0,
7993
- parentThreadId: from.id
8570
+ parentThreadId: from.id,
8571
+ attachments: [attachment]
7994
8572
  },
7995
8573
  onSetupLine
7996
8574
  );
7997
- return updateThread(thread.id, { attachments: [attachment] });
8575
+ return thread;
7998
8576
  }
7999
8577
 
8000
8578
  // src/threads/adopt.ts
8001
8579
  var import_node_child_process = require("child_process");
8002
- var import_node_fs20 = require("fs");
8580
+ var import_node_fs21 = require("fs");
8003
8581
  var import_node_os9 = require("os");
8004
- var import_node_path19 = require("path");
8582
+ var import_node_path20 = require("path");
8005
8583
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
8006
8584
  init_worktree();
8007
8585
  init_thread_store();
8008
- var CONDUCTOR_APP_SUPPORT = (0, import_node_path19.join)(
8586
+ var CONDUCTOR_APP_SUPPORT = (0, import_node_path20.join)(
8009
8587
  process.env.HOME ?? "",
8010
8588
  "Library",
8011
8589
  "Application Support",
8012
8590
  "com.conductor.app"
8013
8591
  );
8014
- var CONDUCTOR_DB = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
8015
- var CURSOR_SDK_STORE = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
8592
+ var CONDUCTOR_DB = (0, import_node_path20.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
8593
+ var CURSOR_SDK_STORE = (0, import_node_path20.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
8016
8594
  function mapAgentType(raw) {
8017
8595
  if (!raw) return null;
8018
8596
  const v = raw.toLowerCase();
@@ -8024,21 +8602,21 @@ function mapAgentType(raw) {
8024
8602
  return null;
8025
8603
  }
8026
8604
  function resolveConductorCursorAgentId(workspacePath) {
8027
- if (!workspacePath || !(0, import_node_fs20.existsSync)(CURSOR_SDK_STORE)) return null;
8605
+ if (!workspacePath || !(0, import_node_fs21.existsSync)(CURSOR_SDK_STORE)) return null;
8028
8606
  const normalized = workspacePath.replace(/\/$/, "");
8029
8607
  let best = null;
8030
8608
  let hashes;
8031
8609
  try {
8032
- hashes = (0, import_node_fs20.readdirSync)(CURSOR_SDK_STORE);
8610
+ hashes = (0, import_node_fs21.readdirSync)(CURSOR_SDK_STORE);
8033
8611
  } catch {
8034
8612
  return null;
8035
8613
  }
8036
8614
  for (const hash of hashes) {
8037
- const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
8038
- if (!(0, import_node_fs20.existsSync)(agentsFile)) continue;
8615
+ const agentsFile = (0, import_node_path20.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
8616
+ if (!(0, import_node_fs21.existsSync)(agentsFile)) continue;
8039
8617
  let text;
8040
8618
  try {
8041
- text = (0, import_node_fs20.readFileSync)(agentsFile, "utf8");
8619
+ text = (0, import_node_fs21.readFileSync)(agentsFile, "utf8");
8042
8620
  } catch {
8043
8621
  continue;
8044
8622
  }
@@ -8062,7 +8640,7 @@ function resolveConductorCursorAgentId(workspacePath) {
8062
8640
  return best?.agentId ?? null;
8063
8641
  }
8064
8642
  async function adoptThread(input) {
8065
- if (!(0, import_node_fs20.existsSync)(input.worktreePath)) {
8643
+ if (!(0, import_node_fs21.existsSync)(input.worktreePath)) {
8066
8644
  throw new Error(`Worktree not found: ${input.worktreePath}`);
8067
8645
  }
8068
8646
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -8089,18 +8667,18 @@ function conductorDbPath() {
8089
8667
  return CONDUCTOR_DB;
8090
8668
  }
8091
8669
  function listConductorWorkspaces() {
8092
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
8670
+ if (!(0, import_node_fs21.existsSync)(CONDUCTOR_DB)) {
8093
8671
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8094
8672
  }
8095
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
8096
- const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8673
+ const tmp = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
8674
+ const snapshot = (0, import_node_path20.join)(tmp, "conductor.db");
8097
8675
  try {
8098
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
8676
+ (0, import_node_fs21.copyFileSync)(CONDUCTOR_DB, snapshot);
8099
8677
  for (const suffix of ["-wal", "-shm"]) {
8100
8678
  const src = `${CONDUCTOR_DB}${suffix}`;
8101
- if ((0, import_node_fs20.existsSync)(src)) {
8679
+ if ((0, import_node_fs21.existsSync)(src)) {
8102
8680
  try {
8103
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
8681
+ (0, import_node_fs21.copyFileSync)(src, `${snapshot}${suffix}`);
8104
8682
  } catch {
8105
8683
  }
8106
8684
  }
@@ -8176,22 +8754,22 @@ function listConductorWorkspaces() {
8176
8754
  db.close();
8177
8755
  }
8178
8756
  } finally {
8179
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
8757
+ (0, import_node_fs21.rmSync)(tmp, { recursive: true, force: true });
8180
8758
  }
8181
8759
  }
8182
8760
  function importConductorWorkspace(workspaceId) {
8183
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
8761
+ if (!(0, import_node_fs21.existsSync)(CONDUCTOR_DB)) {
8184
8762
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8185
8763
  }
8186
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
8187
- const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8764
+ const tmp = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
8765
+ const snapshot = (0, import_node_path20.join)(tmp, "conductor.db");
8188
8766
  try {
8189
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
8767
+ (0, import_node_fs21.copyFileSync)(CONDUCTOR_DB, snapshot);
8190
8768
  for (const suffix of ["-wal", "-shm"]) {
8191
8769
  const src = `${CONDUCTOR_DB}${suffix}`;
8192
- if ((0, import_node_fs20.existsSync)(src)) {
8770
+ if ((0, import_node_fs21.existsSync)(src)) {
8193
8771
  try {
8194
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
8772
+ (0, import_node_fs21.copyFileSync)(src, `${snapshot}${suffix}`);
8195
8773
  } catch {
8196
8774
  }
8197
8775
  }
@@ -8209,7 +8787,7 @@ function importConductorWorkspace(workspaceId) {
8209
8787
  ).get(workspaceId);
8210
8788
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
8211
8789
  const worktreePath = String(row.workspacePath);
8212
- if (!(0, import_node_fs20.existsSync)(worktreePath)) {
8790
+ if (!(0, import_node_fs21.existsSync)(worktreePath)) {
8213
8791
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
8214
8792
  }
8215
8793
  let sessionId = null;
@@ -8272,7 +8850,7 @@ function importConductorWorkspace(workspaceId) {
8272
8850
  db.close();
8273
8851
  }
8274
8852
  } finally {
8275
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
8853
+ (0, import_node_fs21.rmSync)(tmp, { recursive: true, force: true });
8276
8854
  }
8277
8855
  }
8278
8856
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -8281,13 +8859,13 @@ async function importConductorWorkspaceAsync(workspaceId) {
8281
8859
 
8282
8860
  // src/orchestrator/orchestrator.ts
8283
8861
  var import_node_events = require("events");
8284
- var import_node_fs23 = require("fs");
8862
+ var import_node_fs24 = require("fs");
8285
8863
  init_agents();
8286
8864
  init_worktree();
8287
8865
 
8288
8866
  // src/git/orphan-cleanup.ts
8289
- var import_node_fs21 = require("fs");
8290
- var import_node_path20 = require("path");
8867
+ var import_node_fs22 = require("fs");
8868
+ var import_node_path21 = require("path");
8291
8869
  init_worktree();
8292
8870
  init_thread_store();
8293
8871
  init_paths();
@@ -8302,9 +8880,9 @@ async function findOrphanWorktrees(repoPaths) {
8302
8880
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
8303
8881
  );
8304
8882
  const homeRoot = sideboardWorkspacesDir();
8305
- if ((0, import_node_fs21.existsSync)(homeRoot)) {
8883
+ if ((0, import_node_fs22.existsSync)(homeRoot)) {
8306
8884
  try {
8307
- for (const entry of (0, import_node_fs21.readdirSync)(homeRoot, { withFileTypes: true })) {
8885
+ for (const entry of (0, import_node_fs22.readdirSync)(homeRoot, { withFileTypes: true })) {
8308
8886
  if (!entry.isDirectory()) continue;
8309
8887
  void entry;
8310
8888
  }
@@ -8314,7 +8892,7 @@ async function findOrphanWorktrees(repoPaths) {
8314
8892
  const orphans = [];
8315
8893
  const seen = /* @__PURE__ */ new Set();
8316
8894
  for (const repoPath of repos) {
8317
- if (!repoPath || !(0, import_node_fs21.existsSync)(repoPath)) continue;
8895
+ if (!repoPath || !(0, import_node_fs22.existsSync)(repoPath)) continue;
8318
8896
  try {
8319
8897
  const wts = await listWorktrees(repoPath);
8320
8898
  for (const wt of wts) {
@@ -8325,7 +8903,7 @@ async function findOrphanWorktrees(repoPaths) {
8325
8903
  seen.add(path);
8326
8904
  let mtimeMs = 0;
8327
8905
  try {
8328
- mtimeMs = (0, import_node_fs21.statSync)(path).mtimeMs;
8906
+ mtimeMs = (0, import_node_fs22.statSync)(path).mtimeMs;
8329
8907
  } catch {
8330
8908
  mtimeMs = 0;
8331
8909
  }
@@ -8335,16 +8913,16 @@ async function findOrphanWorktrees(repoPaths) {
8335
8913
  }
8336
8914
  try {
8337
8915
  const root = worktreesRoot(repoPath);
8338
- if ((0, import_node_fs21.existsSync)(root)) {
8339
- for (const entry of (0, import_node_fs21.readdirSync)(root, { withFileTypes: true })) {
8916
+ if ((0, import_node_fs22.existsSync)(root)) {
8917
+ for (const entry of (0, import_node_fs22.readdirSync)(root, { withFileTypes: true })) {
8340
8918
  if (!entry.isDirectory()) continue;
8341
- const path = (0, import_node_path20.join)(root, entry.name).replace(/\/$/, "");
8919
+ const path = (0, import_node_path21.join)(root, entry.name).replace(/\/$/, "");
8342
8920
  if (known.has(path) || seen.has(path)) continue;
8343
- if (!(0, import_node_fs21.existsSync)((0, import_node_path20.join)(path, ".git"))) continue;
8921
+ if (!(0, import_node_fs22.existsSync)((0, import_node_path21.join)(path, ".git"))) continue;
8344
8922
  seen.add(path);
8345
8923
  let mtimeMs = 0;
8346
8924
  try {
8347
- mtimeMs = (0, import_node_fs21.statSync)(path).mtimeMs;
8925
+ mtimeMs = (0, import_node_fs22.statSync)(path).mtimeMs;
8348
8926
  } catch {
8349
8927
  mtimeMs = Date.now();
8350
8928
  }
@@ -8490,8 +9068,8 @@ async function applyThreadIntoMain(thread, opts) {
8490
9068
  }
8491
9069
 
8492
9070
  // src/git/clone-repo.ts
8493
- var import_node_fs22 = require("fs");
8494
- var import_node_path21 = require("path");
9071
+ var import_node_fs23 = require("fs");
9072
+ var import_node_path22 = require("path");
8495
9073
  var import_execa6 = require("execa");
8496
9074
  init_paths();
8497
9075
  init_workspaces();
@@ -8501,12 +9079,12 @@ async function cloneRepoIntoSideboard(opts) {
8501
9079
  if (!url) throw new Error("Clone URL is required");
8502
9080
  let name = opts.name?.trim();
8503
9081
  if (!name) {
8504
- const leaf = (0, import_node_path21.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
9082
+ const leaf = (0, import_node_path22.basename)(url.replace(/\/$/, "").replace(/\.git$/, ""));
8505
9083
  name = leaf || "repo";
8506
9084
  }
8507
9085
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
8508
- const dest = (0, import_node_path21.join)(sideboardReposDir(), name);
8509
- if ((0, import_node_fs22.existsSync)(dest)) {
9086
+ const dest = (0, import_node_path22.join)(sideboardReposDir(), name);
9087
+ if ((0, import_node_fs23.existsSync)(dest)) {
8510
9088
  const repoPath2 = await resolveRepoRoot(dest);
8511
9089
  const workspace2 = await ensureWorkspace(repoPath2);
8512
9090
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -8618,7 +9196,7 @@ var Orchestrator = class {
8618
9196
  }
8619
9197
  continue;
8620
9198
  }
8621
- if (!(0, import_node_fs23.existsSync)(thread.worktreePath)) {
9199
+ if (!(0, import_node_fs24.existsSync)(thread.worktreePath)) {
8622
9200
  setStatus(thread.id, "broken", "Worktree missing on disk");
8623
9201
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
8624
9202
  continue;
@@ -8871,6 +9449,7 @@ var Orchestrator = class {
8871
9449
  ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
8872
9450
  ].filter(Boolean).join("\n\n---\n\n");
8873
9451
  try {
9452
+ let lastStderr = "";
8874
9453
  const handle = await spawnAgentTurn(
8875
9454
  fresh,
8876
9455
  { cachedPrefix, prompt: agentPrompt },
@@ -8879,6 +9458,9 @@ var Orchestrator = class {
8879
9458
  if (event.type === "session_id") {
8880
9459
  updateThread(threadId, { sessionId: event.data });
8881
9460
  }
9461
+ if (event.type === "stderr" && typeof event.data === "string" && event.data.trim()) {
9462
+ lastStderr = event.data.trim();
9463
+ }
8882
9464
  }
8883
9465
  );
8884
9466
  this.activeTurns.set(threadId, handle);
@@ -8924,7 +9506,12 @@ var Orchestrator = class {
8924
9506
  this.emit({ type: "status_changed", threadId, status: "stopped" });
8925
9507
  this.emit({ type: "turn_finished", threadId, exitCode: result.exitCode });
8926
9508
  } else {
8927
- setStatus(threadId, result.exitCode === 0 ? "idle" : "error", result.exitCode === 0 ? null : `exit ${result.exitCode}`);
9509
+ const failDetail = lastStderr ? `exit ${result.exitCode}: ${lastStderr.slice(0, 500)}` : `exit ${result.exitCode}`;
9510
+ setStatus(
9511
+ threadId,
9512
+ result.exitCode === 0 ? "idle" : "error",
9513
+ result.exitCode === 0 ? null : failDetail
9514
+ );
8928
9515
  this.emit({
8929
9516
  type: "status_changed",
8930
9517
  threadId,
@@ -9398,9 +9985,16 @@ var Orchestrator = class {
9398
9985
  if (patch.planMode !== void 0) next.planMode = patch.planMode;
9399
9986
  if (patch.model !== void 0) next.model = patch.model;
9400
9987
  if (patch.agent !== void 0) {
9401
- next.agent = patch.agent;
9402
- if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
9403
- next.sessionId = null;
9988
+ if (patch.agent !== thread.agent) {
9989
+ if (thread.messages.length > 0) {
9990
+ throw new Error(
9991
+ `Cannot switch agent provider mid-chat (${thread.agent} \u2192 ${patch.agent}). Start a new chat tab instead.`
9992
+ );
9993
+ }
9994
+ next.agent = patch.agent;
9995
+ if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
9996
+ next.sessionId = null;
9997
+ }
9404
9998
  }
9405
9999
  return updateThread(thread.id, next);
9406
10000
  }
@@ -9430,6 +10024,23 @@ var Orchestrator = class {
9430
10024
  setAttachments(threadRef, attachments) {
9431
10025
  return updateThread(this.requireThread(threadRef).id, { attachments });
9432
10026
  }
10027
+ /**
10028
+ * Stage OS / worktree files into composer attachments (copies external files
10029
+ * into `.sideboard/attachments/` so agents can Read images and binaries).
10030
+ */
10031
+ attachComposerFiles(threadRef, opts) {
10032
+ const thread = this.requireThread(threadRef);
10033
+ const fromAbs = stageAbsolutePathsAsAttachments(
10034
+ thread.worktreePath,
10035
+ opts.absolutePaths ?? []
10036
+ );
10037
+ const fromRel = attachmentsFromWorktreePaths(
10038
+ thread.worktreePath,
10039
+ opts.relativePaths ?? []
10040
+ );
10041
+ const fromBuf = stageBuffersAsAttachments(thread.worktreePath, opts.buffers ?? []);
10042
+ return [...fromAbs, ...fromRel, ...fromBuf];
10043
+ }
9433
10044
  listWorktreeChats(threadRef) {
9434
10045
  const thread = this.requireThread(threadRef);
9435
10046
  return threadsSharingWorktree(thread.worktreePath);
@@ -9489,7 +10100,7 @@ var Orchestrator = class {
9489
10100
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
9490
10101
  return setStatus(thread.id, "idle");
9491
10102
  }
9492
- if (!(0, import_node_fs23.existsSync)(thread.worktreePath)) {
10103
+ if (!(0, import_node_fs24.existsSync)(thread.worktreePath)) {
9493
10104
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
9494
10105
  const { execa: execa7 } = await import("execa");
9495
10106
  const slug = thread.worktreePath.split("/").pop();
@@ -9591,7 +10202,7 @@ init_coordinator_prompt();
9591
10202
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
9592
10203
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
9593
10204
  var import_zod = require("zod");
9594
- var import_node_path22 = require("path");
10205
+ var import_node_path23 = require("path");
9595
10206
  init_worktree();
9596
10207
  init_global_workspace();
9597
10208
 
@@ -9639,7 +10250,7 @@ async function startMcpServer() {
9639
10250
  async () => {
9640
10251
  const threads = orch.getThreads(true);
9641
10252
  const lines = threads.map((t) => {
9642
- const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path22.basename)(t.repoPath) || t.repoPath;
10253
+ const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path23.basename)(t.repoPath) || t.repoPath;
9643
10254
  return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}`;
9644
10255
  });
9645
10256
  return {
@@ -10535,6 +11146,9 @@ init_injected_mcp();
10535
11146
  applyAppEnvironment,
10536
11147
  applyCompaction,
10537
11148
  applyThreadIntoMain,
11149
+ attachmentFromAbsolutePath,
11150
+ attachmentsFromBuffers,
11151
+ attachmentsFromWorktreePaths,
10538
11152
  autoCleanupOrphansEnabled,
10539
11153
  autoRenameBranchEnabled,
10540
11154
  autoRunAfterSetupEnabled,
@@ -10620,6 +11234,7 @@ init_injected_mcp();
10620
11234
  formatWorkspaceInventory,
10621
11235
  formatWorktreeDirective,
10622
11236
  getAdapter,
11237
+ getAgentSetupInfo,
10623
11238
  getBrightsySession,
10624
11239
  getDefaultRunScript,
10625
11240
  getDiff,
@@ -10650,27 +11265,34 @@ init_injected_mcp();
10650
11265
  importConductorWorkspaceAsync,
10651
11266
  initializeGitRepository,
10652
11267
  inspectGitWorktree,
11268
+ installAgent,
10653
11269
  isBrightsyConnected,
10654
11270
  isBrightsyNdjsonLine,
10655
11271
  isCloudCoordinatorThread,
11272
+ isCursorAutoModel,
10656
11273
  isDirty,
10657
11274
  isGhRateLimitError,
10658
11275
  isGlobalRepoPath,
10659
11276
  isGlobalThread,
11277
+ isImageFilePath,
10660
11278
  isLinearConnected,
10661
11279
  isOrchestratorThread,
10662
11280
  isPlaceholderBranch,
11281
+ listAgentSetupInfo,
10663
11282
  listBranchCommits,
10664
11283
  listBranches,
10665
11284
  listBrightsyAccounts,
10666
11285
  listBrightsyChatTargets,
11286
+ listCodexModels,
10667
11287
  listConductorWorkspaces,
10668
11288
  listConnectedBrightsyTeams,
11289
+ listCursorModels,
10669
11290
  listGitHubIssues,
10670
11291
  listGlobalThreads,
10671
11292
  listIssues,
10672
11293
  listLinearIssues,
10673
11294
  listLinearIssuesDirect,
11295
+ listOpencodeModels,
10674
11296
  listPrs,
10675
11297
  listRunScripts,
10676
11298
  listThreads,
@@ -10684,6 +11306,7 @@ init_injected_mcp();
10684
11306
  loadRepoSettings,
10685
11307
  loadWorkspaceSettings,
10686
11308
  locksDir,
11309
+ loginAgent,
10687
11310
  lookupSoccerTeam,
10688
11311
  maxConcurrentAgents,
10689
11312
  maybeCompactContext,
@@ -10695,6 +11318,7 @@ init_injected_mcp();
10695
11318
  normalizeThread,
10696
11319
  normalizeTurnInput,
10697
11320
  normalizeWorktreePath,
11321
+ openInSystemTerminal,
10698
11322
  opencodeAdapter,
10699
11323
  orchestrationTitleNeedsSoccerNickname,
10700
11324
  orchestratorSessionPoisonedByBuiltins,
@@ -10719,6 +11343,7 @@ init_injected_mcp();
10719
11343
  requireAgent,
10720
11344
  resolveClaudeExecutable,
10721
11345
  resolveConductorCursorAgentId,
11346
+ resolveCursorModelId,
10722
11347
  resolveDefaultBranch,
10723
11348
  resolveDiffBaseRef,
10724
11349
  resolveEffectiveIssueSource,
@@ -10745,6 +11370,8 @@ init_injected_mcp();
10745
11370
  slugify,
10746
11371
  spawnAgentTurn,
10747
11372
  splitForCompaction,
11373
+ stageAbsolutePathsAsAttachments,
11374
+ stageBuffersAsAttachments,
10748
11375
  startDevServer,
10749
11376
  startMcpServer,
10750
11377
  startOrchestration,