@sideboard-ai/core 0.1.42 → 0.1.44

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.
@@ -431,14 +431,44 @@ var init_paths = __esm({
431
431
  }
432
432
  });
433
433
 
434
+ // src/types/thinking-effort.ts
435
+ function normalizeThinkingEffort(value) {
436
+ if (typeof value !== "string") return null;
437
+ const v = value.trim().toLowerCase();
438
+ if (v === "normal") return "medium";
439
+ if (EFFORT_SET.has(v)) return v;
440
+ return null;
441
+ }
442
+ var THINKING_EFFORTS, EFFORT_SET;
443
+ var init_thinking_effort = __esm({
444
+ "src/types/thinking-effort.ts"() {
445
+ "use strict";
446
+ THINKING_EFFORTS = [
447
+ "low",
448
+ "medium",
449
+ "high",
450
+ "xhigh",
451
+ "max"
452
+ ];
453
+ EFFORT_SET = new Set(THINKING_EFFORTS);
454
+ }
455
+ });
456
+
434
457
  // src/store/thread-store.ts
435
458
  function nowIso() {
436
459
  return (/* @__PURE__ */ new Date()).toISOString();
437
460
  }
461
+ function resolveThreadEffort(raw) {
462
+ const fromField = normalizeThinkingEffort(raw.effort);
463
+ if (fromField) return fromField;
464
+ if (raw.fast) return "low";
465
+ return "high";
466
+ }
438
467
  function normalizeThread(raw) {
439
468
  return {
440
469
  ...raw,
441
470
  model: raw.model ?? null,
471
+ effort: resolveThreadEffort(raw),
442
472
  fast: Boolean(raw.fast),
443
473
  planMode: Boolean(raw.planMode),
444
474
  autonomy: raw.autonomy ?? "default",
@@ -457,6 +487,7 @@ function createEmptyThread(partial) {
457
487
  sessionId: partial.sessionId ?? null,
458
488
  autonomy: partial.autonomy ?? "default",
459
489
  model: partial.model ?? null,
490
+ effort: partial.effort ?? "high",
460
491
  fast: partial.fast ?? false,
461
492
  planMode: partial.planMode ?? false,
462
493
  sourceIsFork: partial.sourceIsFork ?? false,
@@ -564,6 +595,7 @@ var init_thread_store = __esm({
564
595
  import_node_crypto = require("crypto");
565
596
  import_node_fs3 = require("fs");
566
597
  import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
598
+ init_thinking_effort();
567
599
  init_paths();
568
600
  }
569
601
  });
@@ -2590,6 +2622,8 @@ __export(app_settings_exports, {
2590
2622
  claudeUserSettingsPath: () => claudeUserSettingsPath,
2591
2623
  deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
2592
2624
  getDefaultAgent: () => getDefaultAgent,
2625
+ getDefaultEffort: () => getDefaultEffort,
2626
+ getDefaultFast: () => getDefaultFast,
2593
2627
  getDefaultModel: () => getDefaultModel,
2594
2628
  getIssueSource: () => getIssueSource,
2595
2629
  getLinearApiKey: () => getLinearApiKey,
@@ -2663,6 +2697,12 @@ function normalizeDefaults(raw) {
2663
2697
  const model = source.model.trim();
2664
2698
  if (model) out.model = model;
2665
2699
  }
2700
+ if (normalizeThinkingEffort(source.effort)) {
2701
+ out.effort = normalizeThinkingEffort(source.effort);
2702
+ }
2703
+ if (typeof source.fast === "boolean") {
2704
+ out.fast = source.fast;
2705
+ }
2666
2706
  return out;
2667
2707
  }
2668
2708
  function normalizeAdvanced(raw) {
@@ -2841,6 +2881,21 @@ function updateDefaultsSettings(patch) {
2841
2881
  defaults.model = patch.model.trim();
2842
2882
  }
2843
2883
  }
2884
+ if ("effort" in patch) {
2885
+ if (patch.effort == null) {
2886
+ delete defaults.effort;
2887
+ } else {
2888
+ const effort = normalizeThinkingEffort(patch.effort);
2889
+ if (effort) defaults.effort = effort;
2890
+ }
2891
+ }
2892
+ if ("fast" in patch) {
2893
+ if (patch.fast == null) {
2894
+ delete defaults.fast;
2895
+ } else {
2896
+ defaults.fast = Boolean(patch.fast);
2897
+ }
2898
+ }
2844
2899
  return saveAppSettings({ ...current, defaults });
2845
2900
  }
2846
2901
  function getDefaultAgent(settings = loadAppSettings()) {
@@ -2850,10 +2905,18 @@ function getDefaultModel(settings = loadAppSettings()) {
2850
2905
  const model = settings.defaults.model?.trim();
2851
2906
  return model || null;
2852
2907
  }
2908
+ function getDefaultEffort(settings = loadAppSettings()) {
2909
+ return normalizeThinkingEffort(settings.defaults.effort) ?? "high";
2910
+ }
2911
+ function getDefaultFast(settings = loadAppSettings()) {
2912
+ return settings.defaults.fast === true;
2913
+ }
2853
2914
  function resolveThreadDefaults(settings = loadAppSettings()) {
2854
2915
  return {
2855
2916
  agent: getDefaultAgent(settings),
2856
- model: getDefaultModel(settings)
2917
+ model: getDefaultModel(settings),
2918
+ effort: getDefaultEffort(settings),
2919
+ fast: getDefaultFast(settings)
2857
2920
  };
2858
2921
  }
2859
2922
  function isLinearConnected(settings = loadAppSettings()) {
@@ -2977,6 +3040,7 @@ var init_app_settings = __esm({
2977
3040
  import_node_fs5 = require("fs");
2978
3041
  import_node_os4 = require("os");
2979
3042
  import_node_path6 = require("path");
3043
+ init_thinking_effort();
2980
3044
  init_paths();
2981
3045
  HARNESS_ENV_KEYS = {
2982
3046
  claude: "ANTHROPIC_API_KEY",
@@ -3171,8 +3235,9 @@ var init_coordinator_prompt = __esm({
3171
3235
  "Setup / run:",
3172
3236
  "- run_setup \u2014 re-run worktree setup",
3173
3237
  "- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
3174
- "Inspect / PRs:",
3238
+ "Inspect / review / PRs:",
3175
3239
  "- get_diff \u2014 compact diff summary",
3240
+ "- request_review \u2014 open a Review chat tab on a worktree thread (merge-readiness recommendation); then wait_for_turn / get_turn_result on the returned id",
3176
3241
  "- Ask the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (workspace `github:` slug / that worktree's origin \u2014 never upstream). Do not open PRs from the orchestrator yourself.",
3177
3242
  "Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
3178
3243
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
@@ -3272,6 +3337,7 @@ function createGlobalChat(opts) {
3272
3337
  agent: opts.agent,
3273
3338
  autonomy: opts.autonomy ?? "default",
3274
3339
  model: opts.model ?? null,
3340
+ effort: opts.effort ?? "high",
3275
3341
  fast: Boolean(opts.fast),
3276
3342
  planMode: Boolean(opts.planMode),
3277
3343
  attachments: opts.attachments ?? [],
@@ -4382,9 +4448,8 @@ var init_claude = __esm({
4382
4448
  if (thread.model) {
4383
4449
  args.push("--model", thread.model);
4384
4450
  }
4385
- if (thread.fast) {
4386
- args.push("--effort", "low");
4387
- }
4451
+ const effort = thread.effort ?? (thread.fast ? "low" : "high");
4452
+ args.push("--effort", effort);
4388
4453
  if (sessionId) {
4389
4454
  args.push("--resume", sessionId);
4390
4455
  }
@@ -4972,6 +5037,7 @@ var init_cursor = __esm({
4972
5037
  cwd: thread.worktreePath,
4973
5038
  agentId,
4974
5039
  model: thread.model,
5040
+ effort: thread.effort,
4975
5041
  fast: thread.fast,
4976
5042
  planMode: thread.planMode,
4977
5043
  apiKey
@@ -5625,10 +5691,10 @@ __export(cursor_recover_exports, {
5625
5691
  function recoverFinishedCursorRun(opts) {
5626
5692
  const agentId = opts.agentId.trim();
5627
5693
  if (!agentId) return null;
5628
- const runsPath = (0, import_node_path23.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
5629
- if (!(0, import_node_fs24.existsSync)(runsPath)) return null;
5694
+ const runsPath = (0, import_node_path24.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
5695
+ if (!(0, import_node_fs25.existsSync)(runsPath)) return null;
5630
5696
  try {
5631
- const lines = (0, import_node_fs24.readFileSync)(runsPath, "utf8").split("\n");
5697
+ const lines = (0, import_node_fs25.readFileSync)(runsPath, "utf8").split("\n");
5632
5698
  let best = null;
5633
5699
  for (const line of lines) {
5634
5700
  const trimmed = line.trim();
@@ -5654,12 +5720,12 @@ function recoverFinishedCursorRun(opts) {
5654
5720
  return null;
5655
5721
  }
5656
5722
  }
5657
- var import_node_fs24, import_node_path23;
5723
+ var import_node_fs25, import_node_path24;
5658
5724
  var init_cursor_recover = __esm({
5659
5725
  "src/agents/cursor-recover.ts"() {
5660
5726
  "use strict";
5661
- import_node_fs24 = require("fs");
5662
- import_node_path23 = require("path");
5727
+ import_node_fs25 = require("fs");
5728
+ import_node_path24 = require("path");
5663
5729
  init_paths();
5664
5730
  }
5665
5731
  });
@@ -5668,11 +5734,11 @@ var init_cursor_recover = __esm({
5668
5734
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
5669
5735
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
5670
5736
  var import_zod = require("zod");
5671
- var import_node_path24 = require("path");
5737
+ var import_node_path25 = require("path");
5672
5738
 
5673
5739
  // src/orchestrator/orchestrator.ts
5674
5740
  var import_node_events = require("events");
5675
- var import_node_fs25 = require("fs");
5741
+ var import_node_fs26 = require("fs");
5676
5742
  init_error_detail();
5677
5743
 
5678
5744
  // src/agents/spawn.ts
@@ -6703,6 +6769,7 @@ async function createThread(input, onSetupLine) {
6703
6769
  agent: input.agent,
6704
6770
  autonomy: input.autonomy ?? "default",
6705
6771
  model: input.model ?? null,
6772
+ effort: input.effort ?? "high",
6706
6773
  fast: Boolean(input.fast),
6707
6774
  planMode: Boolean(input.planMode),
6708
6775
  attachments: input.attachments ?? [],
@@ -7094,7 +7161,8 @@ function createChatTab(input) {
7094
7161
  ...binding,
7095
7162
  agent: input.agent ?? from.agent,
7096
7163
  model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
7097
- fast: from.fast,
7164
+ effort: input.effort !== void 0 ? input.effort : from.effort,
7165
+ fast: input.fast !== void 0 ? Boolean(input.fast) : from.fast,
7098
7166
  planMode: from.planMode,
7099
7167
  autonomy: input.autonomy ?? from.autonomy,
7100
7168
  attachments: input.attachments ?? [],
@@ -7115,6 +7183,58 @@ function forkChatTab(input) {
7115
7183
  });
7116
7184
  }
7117
7185
 
7186
+ // src/review/request-review.ts
7187
+ var import_node_crypto3 = require("crypto");
7188
+ var import_node_fs19 = require("fs");
7189
+ var import_node_path18 = require("path");
7190
+ init_global_workspace();
7191
+ init_thread_store();
7192
+ var REVIEW_REQUEST_PATH = ".sideboard/attachments/Review request.md";
7193
+ var REVIEW_REQUEST_NAME = "Review request.md";
7194
+ var REVIEW_REQUEST_PREFILL = `Please review the changes in this workspace and recommend whether they are ready to merge.
7195
+
7196
+ Start with a **Recommendation**: Approve, Approve with nits, Request changes, or Needs more information \u2014 and say why in 1\u20133 sentences. Then list blocking findings vs nits (findings may be empty).`;
7197
+ function buildReviewRequestAttachment(content) {
7198
+ return {
7199
+ id: (0, import_node_crypto3.randomUUID)(),
7200
+ name: REVIEW_REQUEST_NAME,
7201
+ kind: "file",
7202
+ path: REVIEW_REQUEST_PATH,
7203
+ content
7204
+ };
7205
+ }
7206
+ function readExistingReviewRequestFile(worktreePath) {
7207
+ const abs = (0, import_node_path18.join)(worktreePath, REVIEW_REQUEST_PATH);
7208
+ if (!(0, import_node_fs19.existsSync)(abs)) return null;
7209
+ try {
7210
+ const content = (0, import_node_fs19.readFileSync)(abs, "utf8");
7211
+ return content.trim() ? content : null;
7212
+ } catch {
7213
+ return null;
7214
+ }
7215
+ }
7216
+ async function requestReview(threadRef, send) {
7217
+ const from = findThreadByRef(threadRef);
7218
+ if (!from) throw new Error(`Thread not found: ${threadRef}`);
7219
+ if (isOrchestratorThread(from)) {
7220
+ throw new Error(
7221
+ "request_review targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
7222
+ );
7223
+ }
7224
+ if (from.status === "archived") {
7225
+ throw new Error(`Thread is archived: ${from.id}`);
7226
+ }
7227
+ const existing = readExistingReviewRequestFile(from.worktreePath);
7228
+ const attachments = existing ? [buildReviewRequestAttachment(existing)] : [];
7229
+ const tab = createChatTab({
7230
+ fromThreadId: from.id,
7231
+ title: "Review",
7232
+ attachments
7233
+ });
7234
+ const started = await send(tab.id, REVIEW_REQUEST_PREFILL);
7235
+ return { tab: started, from };
7236
+ }
7237
+
7118
7238
  // src/threads/fork-worktree.ts
7119
7239
  init_thread_store();
7120
7240
  function requireThread2(idOrRef) {
@@ -7134,6 +7254,7 @@ async function forkThreadWorktree(input, onSetupLine) {
7134
7254
  agent: input.agent ?? from.agent,
7135
7255
  autonomy: from.autonomy,
7136
7256
  model: from.model,
7257
+ effort: from.effort,
7137
7258
  fast: from.fast,
7138
7259
  planMode: from.planMode,
7139
7260
  title: input.title?.trim() || void 0,
@@ -7147,20 +7268,20 @@ async function forkThreadWorktree(input, onSetupLine) {
7147
7268
 
7148
7269
  // src/threads/adopt.ts
7149
7270
  var import_node_child_process = require("child_process");
7150
- var import_node_fs19 = require("fs");
7271
+ var import_node_fs20 = require("fs");
7151
7272
  var import_node_os8 = require("os");
7152
- var import_node_path18 = require("path");
7273
+ var import_node_path19 = require("path");
7153
7274
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
7154
7275
  init_worktree();
7155
7276
  init_thread_store();
7156
- var CONDUCTOR_APP_SUPPORT = (0, import_node_path18.join)(
7277
+ var CONDUCTOR_APP_SUPPORT = (0, import_node_path19.join)(
7157
7278
  process.env.HOME ?? "",
7158
7279
  "Library",
7159
7280
  "Application Support",
7160
7281
  "com.conductor.app"
7161
7282
  );
7162
- var CONDUCTOR_DB = (0, import_node_path18.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
7163
- var CURSOR_SDK_STORE = (0, import_node_path18.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
7283
+ var CONDUCTOR_DB = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
7284
+ var CURSOR_SDK_STORE = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
7164
7285
  function mapAgentType(raw) {
7165
7286
  if (!raw) return null;
7166
7287
  const v = raw.toLowerCase();
@@ -7172,21 +7293,21 @@ function mapAgentType(raw) {
7172
7293
  return null;
7173
7294
  }
7174
7295
  function resolveConductorCursorAgentId(workspacePath) {
7175
- if (!workspacePath || !(0, import_node_fs19.existsSync)(CURSOR_SDK_STORE)) return null;
7296
+ if (!workspacePath || !(0, import_node_fs20.existsSync)(CURSOR_SDK_STORE)) return null;
7176
7297
  const normalized = workspacePath.replace(/\/$/, "");
7177
7298
  let best = null;
7178
7299
  let hashes;
7179
7300
  try {
7180
- hashes = (0, import_node_fs19.readdirSync)(CURSOR_SDK_STORE);
7301
+ hashes = (0, import_node_fs20.readdirSync)(CURSOR_SDK_STORE);
7181
7302
  } catch {
7182
7303
  return null;
7183
7304
  }
7184
7305
  for (const hash of hashes) {
7185
- const agentsFile = (0, import_node_path18.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
7186
- if (!(0, import_node_fs19.existsSync)(agentsFile)) continue;
7306
+ const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
7307
+ if (!(0, import_node_fs20.existsSync)(agentsFile)) continue;
7187
7308
  let text;
7188
7309
  try {
7189
- text = (0, import_node_fs19.readFileSync)(agentsFile, "utf8");
7310
+ text = (0, import_node_fs20.readFileSync)(agentsFile, "utf8");
7190
7311
  } catch {
7191
7312
  continue;
7192
7313
  }
@@ -7210,7 +7331,7 @@ function resolveConductorCursorAgentId(workspacePath) {
7210
7331
  return best?.agentId ?? null;
7211
7332
  }
7212
7333
  async function adoptThread(input) {
7213
- if (!(0, import_node_fs19.existsSync)(input.worktreePath)) {
7334
+ if (!(0, import_node_fs20.existsSync)(input.worktreePath)) {
7214
7335
  throw new Error(`Worktree not found: ${input.worktreePath}`);
7215
7336
  }
7216
7337
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -7234,18 +7355,18 @@ async function adoptThread(input) {
7234
7355
  return thread;
7235
7356
  }
7236
7357
  function listConductorWorkspaces() {
7237
- if (!(0, import_node_fs19.existsSync)(CONDUCTOR_DB)) {
7358
+ if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
7238
7359
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
7239
7360
  }
7240
- const tmp = (0, import_node_fs19.mkdtempSync)((0, import_node_path18.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
7241
- const snapshot = (0, import_node_path18.join)(tmp, "conductor.db");
7361
+ const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
7362
+ const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
7242
7363
  try {
7243
- (0, import_node_fs19.copyFileSync)(CONDUCTOR_DB, snapshot);
7364
+ (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
7244
7365
  for (const suffix of ["-wal", "-shm"]) {
7245
7366
  const src = `${CONDUCTOR_DB}${suffix}`;
7246
- if ((0, import_node_fs19.existsSync)(src)) {
7367
+ if ((0, import_node_fs20.existsSync)(src)) {
7247
7368
  try {
7248
- (0, import_node_fs19.copyFileSync)(src, `${snapshot}${suffix}`);
7369
+ (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
7249
7370
  } catch {
7250
7371
  }
7251
7372
  }
@@ -7321,22 +7442,22 @@ function listConductorWorkspaces() {
7321
7442
  db.close();
7322
7443
  }
7323
7444
  } finally {
7324
- (0, import_node_fs19.rmSync)(tmp, { recursive: true, force: true });
7445
+ (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
7325
7446
  }
7326
7447
  }
7327
7448
  function importConductorWorkspace(workspaceId) {
7328
- if (!(0, import_node_fs19.existsSync)(CONDUCTOR_DB)) {
7449
+ if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
7329
7450
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
7330
7451
  }
7331
- const tmp = (0, import_node_fs19.mkdtempSync)((0, import_node_path18.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
7332
- const snapshot = (0, import_node_path18.join)(tmp, "conductor.db");
7452
+ const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
7453
+ const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
7333
7454
  try {
7334
- (0, import_node_fs19.copyFileSync)(CONDUCTOR_DB, snapshot);
7455
+ (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
7335
7456
  for (const suffix of ["-wal", "-shm"]) {
7336
7457
  const src = `${CONDUCTOR_DB}${suffix}`;
7337
- if ((0, import_node_fs19.existsSync)(src)) {
7458
+ if ((0, import_node_fs20.existsSync)(src)) {
7338
7459
  try {
7339
- (0, import_node_fs19.copyFileSync)(src, `${snapshot}${suffix}`);
7460
+ (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
7340
7461
  } catch {
7341
7462
  }
7342
7463
  }
@@ -7354,7 +7475,7 @@ function importConductorWorkspace(workspaceId) {
7354
7475
  ).get(workspaceId);
7355
7476
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
7356
7477
  const worktreePath = String(row.workspacePath);
7357
- if (!(0, import_node_fs19.existsSync)(worktreePath)) {
7478
+ if (!(0, import_node_fs20.existsSync)(worktreePath)) {
7358
7479
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
7359
7480
  }
7360
7481
  let sessionId = null;
@@ -7417,7 +7538,7 @@ function importConductorWorkspace(workspaceId) {
7417
7538
  db.close();
7418
7539
  }
7419
7540
  } finally {
7420
- (0, import_node_fs19.rmSync)(tmp, { recursive: true, force: true });
7541
+ (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
7421
7542
  }
7422
7543
  }
7423
7544
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -7428,12 +7549,12 @@ async function importConductorWorkspaceAsync(workspaceId) {
7428
7549
  init_worktree();
7429
7550
 
7430
7551
  // src/diff/diff.ts
7431
- var import_node_fs20 = require("fs");
7432
- var import_node_path19 = require("path");
7552
+ var import_node_fs21 = require("fs");
7553
+ var import_node_path20 = require("path");
7433
7554
  init_run();
7434
7555
  init_worktree();
7435
7556
  async function inspectGitWorktree(worktreePath) {
7436
- if (!worktreePath || !(0, import_node_fs20.existsSync)(worktreePath)) return "missing_worktree";
7557
+ if (!worktreePath || !(0, import_node_fs21.existsSync)(worktreePath)) return "missing_worktree";
7437
7558
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
7438
7559
  reject: false
7439
7560
  });
@@ -7441,7 +7562,7 @@ async function inspectGitWorktree(worktreePath) {
7441
7562
  return "ok";
7442
7563
  }
7443
7564
  async function initializeGitRepository(worktreePath) {
7444
- if (!worktreePath || !(0, import_node_fs20.existsSync)(worktreePath)) {
7565
+ if (!worktreePath || !(0, import_node_fs21.existsSync)(worktreePath)) {
7445
7566
  throw new Error("Worktree not found");
7446
7567
  }
7447
7568
  const status = await inspectGitWorktree(worktreePath);
@@ -7873,8 +7994,8 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
7873
7994
  function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
7874
7995
  assertSafeRelativePath(relativePath);
7875
7996
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
7876
- const abs = (0, import_node_path19.join)(worktreePath, relativePath);
7877
- const st = (0, import_node_fs20.statSync)(abs);
7997
+ const abs = (0, import_node_path20.join)(worktreePath, relativePath);
7998
+ const st = (0, import_node_fs21.statSync)(abs);
7878
7999
  if (!st.isFile()) {
7879
8000
  throw new Error(`Not a file: ${relativePath}`);
7880
8001
  }
@@ -7883,7 +8004,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
7883
8004
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
7884
8005
  );
7885
8006
  }
7886
- const buf = (0, import_node_fs20.readFileSync)(abs);
8007
+ const buf = (0, import_node_fs21.readFileSync)(abs);
7887
8008
  return {
7888
8009
  path: relativePath,
7889
8010
  contentBase64: buf.toString("base64"),
@@ -7893,12 +8014,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
7893
8014
  function readWorktreeFile(worktreePath, relativePath, opts) {
7894
8015
  assertSafeRelativePath(relativePath);
7895
8016
  const maxBytes = opts?.maxBytes ?? 2e5;
7896
- const abs = (0, import_node_path19.join)(worktreePath, relativePath);
7897
- const st = (0, import_node_fs20.statSync)(abs);
8017
+ const abs = (0, import_node_path20.join)(worktreePath, relativePath);
8018
+ const st = (0, import_node_fs21.statSync)(abs);
7898
8019
  if (!st.isFile()) {
7899
8020
  throw new Error(`Not a file: ${relativePath}`);
7900
8021
  }
7901
- const buf = (0, import_node_fs20.readFileSync)(abs);
8022
+ const buf = (0, import_node_fs21.readFileSync)(abs);
7902
8023
  if (isImageRelativePath(relativePath)) {
7903
8024
  const maxImageBytes = Math.max(maxBytes, 15e6);
7904
8025
  const truncated2 = buf.length > maxImageBytes;
@@ -7941,9 +8062,9 @@ function assertSafeRelativePath(relativePath) {
7941
8062
  }
7942
8063
  function writeWorktreeFile(worktreePath, relativePath, content) {
7943
8064
  assertSafeRelativePath(relativePath);
7944
- const abs = (0, import_node_path19.join)(worktreePath, relativePath);
7945
- (0, import_node_fs20.mkdirSync)((0, import_node_path19.dirname)(abs), { recursive: true });
7946
- (0, import_node_fs20.writeFileSync)(abs, content, "utf8");
8065
+ const abs = (0, import_node_path20.join)(worktreePath, relativePath);
8066
+ (0, import_node_fs21.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
8067
+ (0, import_node_fs21.writeFileSync)(abs, content, "utf8");
7947
8068
  return { path: relativePath };
7948
8069
  }
7949
8070
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -8122,9 +8243,9 @@ async function confirmLand(thread, opts) {
8122
8243
  }
8123
8244
 
8124
8245
  // src/skills/discover.ts
8125
- var import_node_fs21 = require("fs");
8246
+ var import_node_fs22 = require("fs");
8126
8247
  var import_node_os9 = require("os");
8127
- var import_node_path20 = require("path");
8248
+ var import_node_path21 = require("path");
8128
8249
  function toCommand(name) {
8129
8250
  return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8130
8251
  }
@@ -8156,7 +8277,7 @@ function parseFrontmatter(content) {
8156
8277
  }
8157
8278
  function readSkill(skillMd, source) {
8158
8279
  try {
8159
- const content = (0, import_node_fs21.readFileSync)(skillMd, "utf8");
8280
+ const content = (0, import_node_fs22.readFileSync)(skillMd, "utf8");
8160
8281
  const { name: fmName, description } = parseFrontmatter(content);
8161
8282
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
8162
8283
  const name = fmName || dirName;
@@ -8175,19 +8296,19 @@ function readSkill(skillMd, source) {
8175
8296
  }
8176
8297
  }
8177
8298
  function scanSkillsDir(dir, source, out) {
8178
- if (!(0, import_node_fs21.existsSync)(dir)) return;
8299
+ if (!(0, import_node_fs22.existsSync)(dir)) return;
8179
8300
  let entries;
8180
8301
  try {
8181
- entries = (0, import_node_fs21.readdirSync)(dir);
8302
+ entries = (0, import_node_fs22.readdirSync)(dir);
8182
8303
  } catch {
8183
8304
  return;
8184
8305
  }
8185
8306
  for (const entry of entries) {
8186
8307
  if (entry.startsWith(".")) continue;
8187
- const skillMd = (0, import_node_path20.join)(dir, entry, "SKILL.md");
8188
- if (!(0, import_node_fs21.existsSync)(skillMd)) continue;
8308
+ const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
8309
+ if (!(0, import_node_fs22.existsSync)(skillMd)) continue;
8189
8310
  try {
8190
- if (!(0, import_node_fs21.statSync)(skillMd).isFile()) continue;
8311
+ if (!(0, import_node_fs22.statSync)(skillMd).isFile()) continue;
8191
8312
  } catch {
8192
8313
  continue;
8193
8314
  }
@@ -8196,24 +8317,24 @@ function scanSkillsDir(dir, source, out) {
8196
8317
  }
8197
8318
  }
8198
8319
  function scanClaudePluginSkills(pluginsRoot, out) {
8199
- if (!(0, import_node_fs21.existsSync)(pluginsRoot)) return;
8320
+ if (!(0, import_node_fs22.existsSync)(pluginsRoot)) return;
8200
8321
  const walk = (dir, depth, lookingForSkillsDir) => {
8201
8322
  if (depth > 7) return;
8202
8323
  let entries;
8203
8324
  try {
8204
- entries = (0, import_node_fs21.readdirSync)(dir);
8325
+ entries = (0, import_node_fs22.readdirSync)(dir);
8205
8326
  } catch {
8206
8327
  return;
8207
8328
  }
8208
8329
  if (lookingForSkillsDir && entries.includes("SKILL.md")) {
8209
- const skill = readSkill((0, import_node_path20.join)(dir, "SKILL.md"), "cli");
8330
+ const skill = readSkill((0, import_node_path21.join)(dir, "SKILL.md"), "cli");
8210
8331
  if (skill) out.push(skill);
8211
8332
  }
8212
8333
  for (const entry of entries) {
8213
8334
  if (entry === "node_modules" || entry === ".git") continue;
8214
- const full = (0, import_node_path20.join)(dir, entry);
8335
+ const full = (0, import_node_path21.join)(dir, entry);
8215
8336
  try {
8216
- if (!(0, import_node_fs21.statSync)(full).isDirectory()) continue;
8337
+ if (!(0, import_node_fs22.statSync)(full).isDirectory()) continue;
8217
8338
  } catch {
8218
8339
  continue;
8219
8340
  }
@@ -8231,17 +8352,17 @@ function discoverSkills(worktreePath) {
8231
8352
  const home = (0, import_node_os9.homedir)();
8232
8353
  const collected = [];
8233
8354
  for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
8234
- scanSkillsDir((0, import_node_path20.join)(worktreePath, rel), "workspace", collected);
8355
+ scanSkillsDir((0, import_node_path21.join)(worktreePath, rel), "workspace", collected);
8235
8356
  }
8236
8357
  for (const abs of [
8237
- (0, import_node_path20.join)(home, ".claude/skills"),
8238
- (0, import_node_path20.join)(home, ".cursor/skills"),
8239
- (0, import_node_path20.join)(home, ".sideboard/skills"),
8240
- (0, import_node_path20.join)(home, ".brightsy/skills")
8358
+ (0, import_node_path21.join)(home, ".claude/skills"),
8359
+ (0, import_node_path21.join)(home, ".cursor/skills"),
8360
+ (0, import_node_path21.join)(home, ".sideboard/skills"),
8361
+ (0, import_node_path21.join)(home, ".brightsy/skills")
8241
8362
  ]) {
8242
8363
  scanSkillsDir(abs, "user", collected);
8243
8364
  }
8244
- scanClaudePluginSkills((0, import_node_path20.join)(home, ".claude/plugins"), collected);
8365
+ scanClaudePluginSkills((0, import_node_path21.join)(home, ".claude/plugins"), collected);
8245
8366
  const rank = { workspace: 0, user: 1, cli: 2 };
8246
8367
  const byCommand = /* @__PURE__ */ new Map();
8247
8368
  for (const skill of collected) {
@@ -8253,7 +8374,7 @@ function discoverSkills(worktreePath) {
8253
8374
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
8254
8375
  }
8255
8376
  function readSkillBody(skillPath, maxChars = 12e3) {
8256
- const raw = (0, import_node_fs21.readFileSync)(skillPath, "utf8");
8377
+ const raw = (0, import_node_fs22.readFileSync)(skillPath, "utf8");
8257
8378
  if (raw.startsWith("---")) {
8258
8379
  const end = raw.indexOf("\n---", 3);
8259
8380
  if (end >= 0) {
@@ -8346,9 +8467,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
8346
8467
  }
8347
8468
 
8348
8469
  // src/composer/stage-files.ts
8349
- var import_node_fs22 = require("fs");
8350
- var import_node_path21 = require("path");
8351
- var import_node_crypto3 = require("crypto");
8470
+ var import_node_fs23 = require("fs");
8471
+ var import_node_path22 = require("path");
8472
+ var import_node_crypto4 = require("crypto");
8352
8473
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
8353
8474
  "png",
8354
8475
  "jpg",
@@ -8377,7 +8498,7 @@ var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local on
8377
8498
  var MAX_INLINE_BYTES = 4e5;
8378
8499
  var MAX_PREVIEW_BYTES = 5e6;
8379
8500
  function fileExtension(filePath) {
8380
- const base = (0, import_node_path21.basename)(filePath).toLowerCase();
8501
+ const base = (0, import_node_path22.basename)(filePath).toLowerCase();
8381
8502
  return base.includes(".") ? base.split(".").pop() || "" : "";
8382
8503
  }
8383
8504
  function isImageFilePath(filePath) {
@@ -8387,24 +8508,24 @@ function imageMimeType(filePath) {
8387
8508
  return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
8388
8509
  }
8389
8510
  function ensureAttachmentsDir(worktreePath) {
8390
- const dir = (0, import_node_path21.join)(worktreePath, ATTACHMENTS_DIR);
8391
- (0, import_node_fs22.mkdirSync)(dir, { recursive: true });
8392
- const gi = (0, import_node_path21.join)(dir, ".gitignore");
8393
- if (!(0, import_node_fs22.existsSync)(gi)) {
8394
- (0, import_node_fs22.writeFileSync)(gi, ATTACHMENTS_GITIGNORE, "utf8");
8511
+ const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
8512
+ (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
8513
+ const gi = (0, import_node_path22.join)(dir, ".gitignore");
8514
+ if (!(0, import_node_fs23.existsSync)(gi)) {
8515
+ (0, import_node_fs23.writeFileSync)(gi, ATTACHMENTS_GITIGNORE, "utf8");
8395
8516
  }
8396
8517
  return dir;
8397
8518
  }
8398
8519
  function uniqueAttachmentName(dir, originalName) {
8399
8520
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
8400
- if (!(0, import_node_fs22.existsSync)((0, import_node_path21.join)(dir, safe))) return safe;
8401
- const ext = (0, import_node_path21.extname)(safe);
8521
+ if (!(0, import_node_fs23.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
8522
+ const ext = (0, import_node_path22.extname)(safe);
8402
8523
  const stem = ext ? safe.slice(0, -ext.length) : safe;
8403
8524
  for (let i = 1; i < 1e4; i++) {
8404
8525
  const candidate = `${stem}-${i}${ext}`;
8405
- if (!(0, import_node_fs22.existsSync)((0, import_node_path21.join)(dir, candidate))) return candidate;
8526
+ if (!(0, import_node_fs23.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
8406
8527
  }
8407
- return `${stem}-${(0, import_node_crypto3.randomUUID)()}${ext}`;
8528
+ return `${stem}-${(0, import_node_crypto4.randomUUID)()}${ext}`;
8408
8529
  }
8409
8530
  function previewDataUrlFromBuf(filePath, buf) {
8410
8531
  if (!isImageFilePath(filePath)) return void 0;
@@ -8416,7 +8537,7 @@ function attachmentFromBuffer(name, buf, opts) {
8416
8537
  if (isImageFilePath(name)) {
8417
8538
  const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
8418
8539
  return {
8419
- id: (0, import_node_crypto3.randomUUID)(),
8540
+ id: (0, import_node_crypto4.randomUUID)(),
8420
8541
  name,
8421
8542
  kind: "file",
8422
8543
  path: opts.path,
@@ -8429,7 +8550,7 @@ function attachmentFromBuffer(name, buf, opts) {
8429
8550
  }
8430
8551
  if (buf.length > MAX_INLINE_BYTES) {
8431
8552
  return {
8432
- id: (0, import_node_crypto3.randomUUID)(),
8553
+ id: (0, import_node_crypto4.randomUUID)(),
8433
8554
  name,
8434
8555
  kind: "file",
8435
8556
  path: opts.path,
@@ -8438,7 +8559,7 @@ function attachmentFromBuffer(name, buf, opts) {
8438
8559
  }
8439
8560
  if (buf.includes(0)) {
8440
8561
  return {
8441
- id: (0, import_node_crypto3.randomUUID)(),
8562
+ id: (0, import_node_crypto4.randomUUID)(),
8442
8563
  name,
8443
8564
  kind: "file",
8444
8565
  path: opts.path,
@@ -8446,7 +8567,7 @@ function attachmentFromBuffer(name, buf, opts) {
8446
8567
  };
8447
8568
  }
8448
8569
  return {
8449
- id: (0, import_node_crypto3.randomUUID)(),
8570
+ id: (0, import_node_crypto4.randomUUID)(),
8450
8571
  name,
8451
8572
  kind: "file",
8452
8573
  path: opts.path,
@@ -8458,19 +8579,19 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
8458
8579
  const dir = ensureAttachmentsDir(worktreePath);
8459
8580
  const out = [];
8460
8581
  for (const abs of absolutePaths) {
8461
- const originalName = (0, import_node_path21.basename)(abs);
8582
+ const originalName = (0, import_node_path22.basename)(abs);
8462
8583
  try {
8463
- const st = (0, import_node_fs22.statSync)(abs);
8584
+ const st = (0, import_node_fs23.statSync)(abs);
8464
8585
  if (!st.isFile()) continue;
8465
8586
  const name = uniqueAttachmentName(dir, originalName);
8466
- const destAbs = (0, import_node_path21.join)(dir, name);
8467
- (0, import_node_fs22.copyFileSync)(abs, destAbs);
8587
+ const destAbs = (0, import_node_path22.join)(dir, name);
8588
+ (0, import_node_fs23.copyFileSync)(abs, destAbs);
8468
8589
  const rel = `${ATTACHMENTS_DIR}/${name}`;
8469
- const buf = (0, import_node_fs22.readFileSync)(destAbs);
8590
+ const buf = (0, import_node_fs23.readFileSync)(destAbs);
8470
8591
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
8471
8592
  } catch (err) {
8472
8593
  out.push({
8473
- id: (0, import_node_crypto3.randomUUID)(),
8594
+ id: (0, import_node_crypto4.randomUUID)(),
8474
8595
  name: originalName,
8475
8596
  kind: "file",
8476
8597
  content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
@@ -8488,13 +8609,13 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
8488
8609
  try {
8489
8610
  const buf = Buffer.from(item.dataBase64, "base64");
8490
8611
  const name = uniqueAttachmentName(dir, originalName);
8491
- const destAbs = (0, import_node_path21.join)(dir, name);
8492
- (0, import_node_fs22.writeFileSync)(destAbs, buf);
8612
+ const destAbs = (0, import_node_path22.join)(dir, name);
8613
+ (0, import_node_fs23.writeFileSync)(destAbs, buf);
8493
8614
  const rel = `${ATTACHMENTS_DIR}/${name}`;
8494
8615
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
8495
8616
  } catch (err) {
8496
8617
  out.push({
8497
- id: (0, import_node_crypto3.randomUUID)(),
8618
+ id: (0, import_node_crypto4.randomUUID)(),
8498
8619
  name: originalName,
8499
8620
  kind: "file",
8500
8621
  content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
@@ -8508,23 +8629,23 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
8508
8629
  for (const rel of relativePaths) {
8509
8630
  if (!rel || rel.includes("..") || rel.startsWith("/")) {
8510
8631
  out.push({
8511
- id: (0, import_node_crypto3.randomUUID)(),
8512
- name: (0, import_node_path21.basename)(rel) || "file",
8632
+ id: (0, import_node_crypto4.randomUUID)(),
8633
+ name: (0, import_node_path22.basename)(rel) || "file",
8513
8634
  kind: "file",
8514
8635
  content: `(invalid path: ${rel})`
8515
8636
  });
8516
8637
  continue;
8517
8638
  }
8518
- const name = (0, import_node_path21.basename)(rel);
8639
+ const name = (0, import_node_path22.basename)(rel);
8519
8640
  try {
8520
- const abs = (0, import_node_path21.join)(worktreePath, rel);
8521
- const st = (0, import_node_fs22.statSync)(abs);
8641
+ const abs = (0, import_node_path22.join)(worktreePath, rel);
8642
+ const st = (0, import_node_fs23.statSync)(abs);
8522
8643
  if (!st.isFile()) continue;
8523
- const buf = (0, import_node_fs22.readFileSync)(abs);
8644
+ const buf = (0, import_node_fs23.readFileSync)(abs);
8524
8645
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
8525
8646
  } catch (err) {
8526
8647
  out.push({
8527
- id: (0, import_node_crypto3.randomUUID)(),
8648
+ id: (0, import_node_crypto4.randomUUID)(),
8528
8649
  name,
8529
8650
  kind: "file",
8530
8651
  content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
@@ -8535,8 +8656,8 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
8535
8656
  }
8536
8657
 
8537
8658
  // src/agents/instructions.ts
8538
- var import_node_fs23 = require("fs");
8539
- var import_node_path22 = require("path");
8659
+ var import_node_fs24 = require("fs");
8660
+ var import_node_path23 = require("path");
8540
8661
  init_worktree_labels();
8541
8662
  function normPath2(p) {
8542
8663
  return p.replace(/\/+$/, "");
@@ -8675,11 +8796,11 @@ function loadAgentInstructions(worktreePath, agent) {
8675
8796
  const out = [];
8676
8797
  for (const rel of candidates) {
8677
8798
  if (seen.has(rel)) continue;
8678
- const abs = (0, import_node_path22.join)(worktreePath, rel);
8679
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
8799
+ const abs = (0, import_node_path23.join)(worktreePath, rel);
8800
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
8680
8801
  try {
8681
- if (!(0, import_node_fs23.statSync)(abs).isFile()) continue;
8682
- let content = (0, import_node_fs23.readFileSync)(abs, "utf8");
8802
+ if (!(0, import_node_fs24.statSync)(abs).isFile()) continue;
8803
+ let content = (0, import_node_fs24.readFileSync)(abs, "utf8");
8683
8804
  if (!content.trim()) continue;
8684
8805
  if (content.length > MAX_CHARS_PER_FILE) {
8685
8806
  content = `${content.slice(0, MAX_CHARS_PER_FILE)}
@@ -8829,7 +8950,7 @@ var Orchestrator = class {
8829
8950
  }
8830
8951
  continue;
8831
8952
  }
8832
- if (!(0, import_node_fs25.existsSync)(thread.worktreePath)) {
8953
+ if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
8833
8954
  setStatus(thread.id, "broken", "Worktree missing on disk");
8834
8955
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
8835
8956
  continue;
@@ -9727,10 +9848,20 @@ var Orchestrator = class {
9727
9848
  setAutonomy(threadRef, autonomy) {
9728
9849
  return this.setThreadOptions(threadRef, { autonomy });
9729
9850
  }
9851
+ /**
9852
+ * Open a Review chat tab on a worktree thread (same as the desktop Review button)
9853
+ * and send the merge-readiness prefill.
9854
+ */
9855
+ async requestReview(threadRef) {
9856
+ const { tab } = await requestReview(threadRef, (ref, prompt) => this.send(ref, prompt));
9857
+ this.emit({ type: "status_changed", threadId: tab.id, status: tab.status });
9858
+ return tab;
9859
+ }
9730
9860
  setThreadOptions(threadRef, patch) {
9731
9861
  const thread = this.requireThread(threadRef);
9732
9862
  const next = {};
9733
9863
  if (patch.autonomy !== void 0) next.autonomy = patch.autonomy;
9864
+ if (patch.effort !== void 0) next.effort = patch.effort;
9734
9865
  if (patch.fast !== void 0) next.fast = patch.fast;
9735
9866
  if (patch.planMode !== void 0) next.planMode = patch.planMode;
9736
9867
  if (patch.model !== void 0) next.model = patch.model;
@@ -9850,7 +9981,7 @@ var Orchestrator = class {
9850
9981
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
9851
9982
  return setStatus(thread.id, "idle");
9852
9983
  }
9853
- if (!(0, import_node_fs25.existsSync)(thread.worktreePath)) {
9984
+ if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
9854
9985
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
9855
9986
  const { execa: execa7 } = await import("execa");
9856
9987
  const slug = thread.worktreePath.split("/").pop();
@@ -10068,7 +10199,7 @@ async function startMcpServer() {
10068
10199
  async () => {
10069
10200
  const threads = orch.getThreads(true);
10070
10201
  const lines = threads.map((t) => {
10071
- const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path24.basename)(t.repoPath) || t.repoPath;
10202
+ const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path25.basename)(t.repoPath) || t.repoPath;
10072
10203
  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}` : ""}`;
10073
10204
  });
10074
10205
  return {
@@ -10411,6 +10542,34 @@ async function startMcpServer() {
10411
10542
  };
10412
10543
  }
10413
10544
  );
10545
+ server.tool(
10546
+ "request_review",
10547
+ "Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, optionally attaches .sideboard/attachments/Review request.md when present, and asks for Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.",
10548
+ { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
10549
+ async ({ ref }) => {
10550
+ try {
10551
+ const tab = await orch.requestReview(ref);
10552
+ const from = orch.getThread(ref);
10553
+ return {
10554
+ content: [
10555
+ {
10556
+ type: "text",
10557
+ text: JSON.stringify({
10558
+ id: tab.id,
10559
+ title: tab.title,
10560
+ status: tab.status,
10561
+ fromThreadId: from?.id ?? ref,
10562
+ link: `sideboard://thread/${tab.id}`
10563
+ })
10564
+ }
10565
+ ]
10566
+ };
10567
+ } catch (err) {
10568
+ const message = err instanceof Error ? err.message : String(err);
10569
+ return { content: [{ type: "text", text: message }], isError: true };
10570
+ }
10571
+ }
10572
+ );
10414
10573
  server.tool(
10415
10574
  "run_dev_script",
10416
10575
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",