@sideboard-ai/core 0.1.228 → 0.1.230

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.
Files changed (48) hide show
  1. package/dist/{abletime-D6773P33.js → abletime-BGVOCEOU.js} +4 -4
  2. package/dist/{abletime-BF253OZT.js → abletime-WZNDTRVL.js} +4 -4
  3. package/dist/{abletime-oauth-KQPVVBKA.js → abletime-oauth-6M5GWEPX.js} +3 -3
  4. package/dist/{abletime-oauth-H4672PRH.js → abletime-oauth-ZLU2EYRK.js} +3 -3
  5. package/dist/{abletime-rest-DAFPXZ7H.js → abletime-rest-F7XG5NGY.js} +3 -3
  6. package/dist/{abletime-rest-OPEVV3XC.js → abletime-rest-WNHIATB3.js} +3 -3
  7. package/dist/{agents-DV5UROWB.js → agents-ENAQHQRZ.js} +5 -5
  8. package/dist/{agents-QQ77D7VT.js → agents-TCTEAK6M.js} +5 -5
  9. package/dist/{app-settings-5RQMQKSB.js → app-settings-KBL2G7M4.js} +7 -1
  10. package/dist/{app-settings-V2BBCX2H.js → app-settings-UXWCPDEA.js} +7 -1
  11. package/dist/{chunk-NFWPDEN4.js → chunk-4CZ25DO2.js} +2 -2
  12. package/dist/{chunk-3IMSQNTS.js → chunk-4VGCG2HV.js} +17 -0
  13. package/dist/{chunk-SQHRPJ7P.js → chunk-6L25K7BE.js} +14 -4
  14. package/dist/{chunk-HSYE5ID6.js → chunk-AS4UN7QL.js} +5 -5
  15. package/dist/{chunk-3NWUHXU3.js → chunk-C774RJJA.js} +3 -3
  16. package/dist/{chunk-VUW3VKLW.js → chunk-FA4CPYCY.js} +17 -4
  17. package/dist/{chunk-CLI5DVU4.js → chunk-GHRZNPBK.js} +2 -2
  18. package/dist/{chunk-N2VST3NL.js → chunk-HZFLKVGC.js} +3 -3
  19. package/dist/{chunk-72XUASTH.js → chunk-HZLZ34FK.js} +157 -26
  20. package/dist/{chunk-EWHJNJHV.js → chunk-I5NC7DWO.js} +38 -0
  21. package/dist/{chunk-WHYJL7CP.js → chunk-JJRRLCCS.js} +156 -25
  22. package/dist/{chunk-646I442S.js → chunk-MVLXDYN5.js} +5 -5
  23. package/dist/{chunk-KTZESHYH.js → chunk-N5TX66CD.js} +2 -2
  24. package/dist/{chunk-RRW2XWAY.js → chunk-P6ZM5QJW.js} +2 -2
  25. package/dist/{chunk-THLVCXLF.js → chunk-RGNPPRZZ.js} +3 -3
  26. package/dist/{chunk-Y5JES2VV.js → chunk-RZTRWN2V.js} +38 -0
  27. package/dist/{chunk-SOGGKYFV.js → chunk-SWNEA5OZ.js} +3 -3
  28. package/dist/{chunk-P2EAAM3H.js → chunk-UNTOZA3C.js} +3 -3
  29. package/dist/{chunk-HY2GFZ5S.js → chunk-XJETIO4K.js} +17 -0
  30. package/dist/{coordinator-prompt-JLMQVORO.js → coordinator-prompt-DFXYTM6D.js} +4 -4
  31. package/dist/{coordinator-prompt-VZ7OSRLD.js → coordinator-prompt-YKLXQTWH.js} +4 -4
  32. package/dist/{global-workspace-3FDAUNN2.js → global-workspace-4SPANNJT.js} +4 -4
  33. package/dist/{global-workspace-QPSJEXGR.js → global-workspace-EYZKYHAO.js} +4 -4
  34. package/dist/index.cjs +992 -634
  35. package/dist/index.d.cts +120 -6
  36. package/dist/index.d.ts +120 -6
  37. package/dist/index.js +207 -68
  38. package/dist/mcp/run-stdio.cjs +289 -50
  39. package/dist/mcp/run-stdio.js +87 -51
  40. package/dist/{orchestrator-6XEWQWSM.js → orchestrator-HMJPNUN5.js} +8 -6
  41. package/dist/{orchestrator-QUJORSTA.js → orchestrator-ZT3SJVKF.js} +8 -6
  42. package/dist/{thread-store-KDPPXJJ3.js → thread-store-FBK5SZ5P.js} +1 -1
  43. package/dist/{thread-store-N6WQIWZ4.js → thread-store-IKJD4EOK.js} +1 -1
  44. package/dist/{workspaces-76W7IEKR.js → workspaces-73Y7IJY2.js} +4 -4
  45. package/dist/{workspaces-TDBA64II.js → workspaces-FQCSCTNH.js} +4 -4
  46. package/dist/{worktree-7V67XTEO.js → worktree-36VEFUJN.js} +3 -3
  47. package/dist/{worktree-I573UOPD.js → worktree-DOPRKG7K.js} +3 -3
  48. package/package.json +1 -1
@@ -861,6 +861,22 @@ function resolveThreadEffort(raw) {
861
861
  if (raw.fast) return "low";
862
862
  return "high";
863
863
  }
864
+ function normalizeRunScriptRequest(raw) {
865
+ if (!raw || typeof raw !== "object") return void 0;
866
+ const req = raw;
867
+ if (req.op !== "start" && req.op !== "stop") return void 0;
868
+ if (typeof req.requestId !== "string" || !req.requestId.trim()) return void 0;
869
+ if (typeof req.requestedAt !== "string" || !req.requestedAt.trim()) return void 0;
870
+ return {
871
+ op: req.op,
872
+ scriptName: req.scriptName ?? null,
873
+ requestId: req.requestId,
874
+ requestedAt: req.requestedAt,
875
+ claimedAt: req.claimedAt ?? null,
876
+ fulfilledAt: req.fulfilledAt ?? null,
877
+ error: req.error ?? null
878
+ };
879
+ }
864
880
  function normalizeThread(raw) {
865
881
  return {
866
882
  ...raw,
@@ -884,6 +900,7 @@ function normalizeThread(raw) {
884
900
  stackLayer: raw.stackLayer ?? null,
885
901
  userSetTitle: Boolean(raw.userSetTitle),
886
902
  activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
903
+ runScriptRequest: normalizeRunScriptRequest(raw.runScriptRequest),
887
904
  quotaResumeAt: raw.quotaResumeAt ?? null,
888
905
  quotaContinuedFromId: raw.quotaContinuedFromId ?? null,
889
906
  archivedAt: typeof raw.archivedAt === "string" && raw.archivedAt.trim() ? raw.archivedAt.trim() : void 0
@@ -3787,12 +3804,14 @@ var init_issue_source_labels = __esm({
3787
3804
  // src/store/app-settings.ts
3788
3805
  var app_settings_exports = {};
3789
3806
  __export(app_settings_exports, {
3807
+ AGENT_DONE_SOUNDS: () => AGENT_DONE_SOUNDS,
3790
3808
  FOLLOW_UP_BEHAVIORS: () => FOLLOW_UP_BEHAVIORS,
3791
3809
  GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
3792
3810
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
3793
3811
  ISSUE_SOURCE_LABELS: () => ISSUE_SOURCE_LABELS,
3794
3812
  PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
3795
3813
  USAGE_ON_LIMIT_VALUES: () => USAGE_ON_LIMIT_VALUES,
3814
+ agentDoneSound: () => agentDoneSound,
3796
3815
  appSettingsPath: () => appSettingsPath,
3797
3816
  applyAppEnvironment: () => applyAppEnvironment,
3798
3817
  autoArchiveOnMergeEnabled: () => autoArchiveOnMergeEnabled,
@@ -3840,6 +3859,7 @@ __export(app_settings_exports, {
3840
3859
  historyMaxCount: () => historyMaxCount,
3841
3860
  historyMaxDays: () => historyMaxDays,
3842
3861
  isAbleTimeConnected: () => isAbleTimeConnected,
3862
+ isAgentDoneSound: () => isAgentDoneSound,
3843
3863
  isIssueSourceConnected: () => isIssueSourceConnected,
3844
3864
  isLinearConnected: () => isLinearConnected,
3845
3865
  isUsageOnLimit: () => isUsageOnLimit,
@@ -3881,6 +3901,9 @@ __export(app_settings_exports, {
3881
3901
  usageOnLimit: () => usageOnLimit,
3882
3902
  writeViewerContext: () => writeViewerContext
3883
3903
  });
3904
+ function isAgentDoneSound(value) {
3905
+ return AGENT_DONE_SOUNDS.includes(value);
3906
+ }
3884
3907
  function emptyPublicIntegrations() {
3885
3908
  return {
3886
3909
  hasLinearApiKey: false,
@@ -4311,6 +4334,13 @@ function normalizeAdvanced(raw) {
4311
4334
  if (source.followUpBehavior === "queue" || source.followUpBehavior === "steer") {
4312
4335
  out.followUpBehavior = source.followUpBehavior;
4313
4336
  }
4337
+ if (isAgentDoneSound(source.agentDoneSound)) {
4338
+ out.agentDoneSound = source.agentDoneSound;
4339
+ }
4340
+ if (typeof source.agentDoneCustomSoundName === "string") {
4341
+ const name = source.agentDoneCustomSoundName.trim();
4342
+ if (name) out.agentDoneCustomSoundName = name.slice(0, 200);
4343
+ }
4314
4344
  return out;
4315
4345
  }
4316
4346
  function normalizeSettings(raw) {
@@ -5221,6 +5251,16 @@ function updateAdvancedSettings(patch) {
5221
5251
  if (patch.followUpBehavior === "queue" || patch.followUpBehavior === "steer") {
5222
5252
  advanced.followUpBehavior = patch.followUpBehavior;
5223
5253
  }
5254
+ if (isAgentDoneSound(patch.agentDoneSound)) {
5255
+ advanced.agentDoneSound = patch.agentDoneSound;
5256
+ }
5257
+ if (patch.agentDoneCustomSoundName === null || patch.agentDoneCustomSoundName === "") {
5258
+ delete advanced.agentDoneCustomSoundName;
5259
+ } else if (typeof patch.agentDoneCustomSoundName === "string") {
5260
+ const name = patch.agentDoneCustomSoundName.trim();
5261
+ if (name) advanced.agentDoneCustomSoundName = name.slice(0, 200);
5262
+ else delete advanced.agentDoneCustomSoundName;
5263
+ }
5224
5264
  return saveAppSettings({ ...current, advanced });
5225
5265
  }
5226
5266
  function autoRenameBranchEnabled(settings = loadAppSettings()) {
@@ -5288,6 +5328,9 @@ function orchestrationQuotaFallbackAgent(settings = loadAppSettings()) {
5288
5328
  function followUpBehavior(settings = loadAppSettings()) {
5289
5329
  return settings.advanced.followUpBehavior === "queue" ? "queue" : "steer";
5290
5330
  }
5331
+ function agentDoneSound(settings = loadAppSettings()) {
5332
+ return isAgentDoneSound(settings.advanced.agentDoneSound) ? settings.advanced.agentDoneSound : "none";
5333
+ }
5291
5334
  function maxConcurrentAgents(settings = loadAppSettings()) {
5292
5335
  const n = settings.advanced.maxConcurrent;
5293
5336
  if (typeof n === "number" && Number.isFinite(n)) {
@@ -5375,7 +5418,7 @@ function childEnvWithAppSettings(extra) {
5375
5418
  function harnessEnvKey(harness) {
5376
5419
  return HARNESS_ENV_KEYS[harness];
5377
5420
  }
5378
- var import_node_crypto3, import_node_fs15, import_node_os5, import_node_path18, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
5421
+ var import_node_crypto3, import_node_fs15, import_node_os5, import_node_path18, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, AGENT_DONE_SOUNDS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
5379
5422
  var init_app_settings = __esm({
5380
5423
  "src/store/app-settings.ts"() {
5381
5424
  "use strict";
@@ -5412,6 +5455,18 @@ var init_app_settings = __esm({
5412
5455
  ]);
5413
5456
  GITHUB_GIT_AUTH_MODES = ["auto", "gh", "ssh", "token"];
5414
5457
  FOLLOW_UP_BEHAVIORS = ["steer", "queue"];
5458
+ AGENT_DONE_SOUNDS = [
5459
+ "none",
5460
+ "goal",
5461
+ "bell",
5462
+ "level",
5463
+ "coin",
5464
+ "fanfare",
5465
+ "magic",
5466
+ "rooster",
5467
+ "whistle",
5468
+ "custom"
5469
+ ];
5415
5470
  CLOUD_CONNECT_AGENTS = /* @__PURE__ */ new Set([
5416
5471
  "claude",
5417
5472
  "codex",
@@ -10242,6 +10297,9 @@ var init_injected_mcp = __esm({
10242
10297
  "mcp__sideboard__present_plan",
10243
10298
  "mcp__sideboard__wait_for_job",
10244
10299
  "mcp__sideboard__stop_job",
10300
+ "mcp__sideboard__list_run_scripts",
10301
+ "mcp__sideboard__run_dev_script",
10302
+ "mcp__sideboard__stop_dev_script",
10245
10303
  "mcp__sideboard__get_viewer_context",
10246
10304
  "mcp__sideboard__update_viewer_context"
10247
10305
  ];
@@ -10451,6 +10509,13 @@ function toolDescription(name, input) {
10451
10509
  if (/stop_job$/i.test(name)) {
10452
10510
  return str2(input?.id) ? `Stop ${str2(input?.id)}` : "Stop job";
10453
10511
  }
10512
+ if (/list_run_scripts$/i.test(name)) return "List run scripts";
10513
+ if (/run_dev_script$/i.test(name)) {
10514
+ return str2(input?.name) ? `Start ${str2(input?.name)}` : "Start Dev script";
10515
+ }
10516
+ if (/stop_dev_script$/i.test(name)) {
10517
+ return str2(input?.name) ? `Stop ${str2(input?.name)}` : "Stop Dev script";
10518
+ }
10454
10519
  if (isSubagentToolName(name)) {
10455
10520
  const desc = str2(input?.description);
10456
10521
  const sub = asRecord(input?.subagentType);
@@ -15211,6 +15276,16 @@ var init_clone_repo = __esm({
15211
15276
  }
15212
15277
  });
15213
15278
 
15279
+ // src/types/thread.ts
15280
+ function isUnclaimedRunScriptRequest(req) {
15281
+ return Boolean(req && !req.claimedAt && !req.fulfilledAt && !req.error);
15282
+ }
15283
+ var init_thread = __esm({
15284
+ "src/types/thread.ts"() {
15285
+ "use strict";
15286
+ }
15287
+ });
15288
+
15214
15289
  // src/store/desktop-host.ts
15215
15290
  function desktopHostPidPath() {
15216
15291
  return (0, import_node_path43.join)(appDataDir(), "desktop-host.pid");
@@ -20529,12 +20604,13 @@ function formatLongRunningDirective(opts) {
20529
20604
  "- Loop Sideboard MCP `wait_for_job` (returns in ~45s). The desktop opens a `type=log` pane from wait/stop (and shell detached-job JSON) automatically \u2014 you may still `present_artifact` with `artifact_id=<id>` for a custom title. stillRunning \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
20530
20605
  "- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
20531
20606
  "- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
20532
- "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
20607
+ "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available).",
20608
+ "Project Dev / run scripts (`.sideboard` `[scripts.run.*]`, same as the desktop Dev button): use MCP `list_run_scripts` / `run_dev_script` / `stop_dev_script` \u2014 logs and Stop live in the thread UI. Do not detached-job or shell-spawn that same command."
20533
20609
  ].join("\n");
20534
20610
  }
20535
20611
  function formatLongRunningReminder(opts) {
20536
20612
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
20537
- return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait). The log pane updates from wait JSON; present_artifact type=log is optional. stop_job if hanging or wrong. Do not say you will let the user know later \u2014 stay in the turn.`;
20613
+ return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait). The log pane updates from wait JSON; present_artifact type=log is optional. stop_job if hanging or wrong. Do not say you will let the user know later \u2014 stay in the turn. Project Dev button: list_run_scripts / run_dev_script / stop_dev_script (UI-connected \u2014 not detached-job).`;
20538
20614
  }
20539
20615
  function formatArtifactDirective() {
20540
20616
  return [
@@ -20546,7 +20622,7 @@ function formatArtifactDirective() {
20546
20622
  ].join("\n");
20547
20623
  }
20548
20624
  function formatUiReminder() {
20549
- return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos, \u201Cwhat next?\u201D, or after a review) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable.";
20625
+ return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos, \u201Cwhat next?\u201D, or after a review) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable. Dev / preview: run_dev_script (same as the Dev button); stop_dev_script to stop.";
20550
20626
  }
20551
20627
  var import_node_fs50, import_node_path49, PR_GOAL_RE, GITHUB_TICKET_REF, KEYED_TICKET_REF;
20552
20628
  var init_instructions = __esm({
@@ -21478,6 +21554,7 @@ __export(orchestrator_exports, {
21478
21554
  LIGHT_RECONCILE_THROTTLE_MS: () => LIGHT_RECONCILE_THROTTLE_MS,
21479
21555
  LIVE_TURN_SPAWN_GRACE_MS: () => LIVE_TURN_SPAWN_GRACE_MS,
21480
21556
  Orchestrator: () => Orchestrator,
21557
+ RUN_SCRIPT_DESKTOP_ADOPT_MS: () => RUN_SCRIPT_DESKTOP_ADOPT_MS,
21481
21558
  getOrchestrator: () => getOrchestrator,
21482
21559
  isPidAlive: () => isPidAlive,
21483
21560
  resolveOrchChildFollowUp: () => resolveOrchChildFollowUp,
@@ -21589,12 +21666,13 @@ async function startOrchestration(opts) {
21589
21666
  }
21590
21667
  return updated;
21591
21668
  }
21592
- var import_node_events, import_node_fs55, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, LIGHT_RECONCILE_THROTTLE_MS, Orchestrator, singleton;
21669
+ var import_node_events, import_node_crypto12, import_node_fs55, LIVE_TURN_SPAWN_GRACE_MS, RUN_SCRIPT_DESKTOP_ADOPT_MS, STALE_AGENT_PID_WAIT_MS, LIGHT_RECONCILE_THROTTLE_MS, Orchestrator, singleton;
21593
21670
  var init_orchestrator = __esm({
21594
21671
  "src/orchestrator/orchestrator.ts"() {
21595
21672
  "use strict";
21596
21673
  import_node_events = require("events");
21597
21674
  init_outbound_watch();
21675
+ import_node_crypto12 = require("crypto");
21598
21676
  import_node_fs55 = require("fs");
21599
21677
  init_error_detail();
21600
21678
  init_run();
@@ -21612,6 +21690,7 @@ var init_orchestrator = __esm({
21612
21690
  init_history_cleanup();
21613
21691
  init_apply_into_main();
21614
21692
  init_clone_repo();
21693
+ init_thread();
21615
21694
  init_message_parts();
21616
21695
  init_usage();
21617
21696
  init_thread_store();
@@ -21657,6 +21736,7 @@ var init_orchestrator = __esm({
21657
21736
  init_schedule_runner();
21658
21737
  init_coordinator_prompt();
21659
21738
  LIVE_TURN_SPAWN_GRACE_MS = 15e3;
21739
+ RUN_SCRIPT_DESKTOP_ADOPT_MS = 2e4;
21660
21740
  STALE_AGENT_PID_WAIT_MS = 2500;
21661
21741
  LIGHT_RECONCILE_THROTTLE_MS = 1e4;
21662
21742
  Orchestrator = class {
@@ -21695,6 +21775,11 @@ var init_orchestrator = __esm({
21695
21775
  jobContinueNudged = /* @__PURE__ */ new Set();
21696
21776
  /** In-flight startDev per worktree run key — agent MCP + UI Start must not double-spawn. */
21697
21777
  startingDev = /* @__PURE__ */ new Map();
21778
+ /**
21779
+ * MCP/CLI poll budget while Electron main adopts `runScriptRequest`.
21780
+ * Tests shorten this so a missing desktop does not wait 20s.
21781
+ */
21782
+ runScriptAdoptTimeoutMs = RUN_SCRIPT_DESKTOP_ADOPT_MS;
21698
21783
  maxConcurrent;
21699
21784
  runningCount = 0;
21700
21785
  constructor(opts) {
@@ -21864,6 +21949,58 @@ var init_orchestrator = __esm({
21864
21949
  this.armDrain(thread.id);
21865
21950
  }
21866
21951
  }
21952
+ this.adoptPersistedRunScripts();
21953
+ }
21954
+ /**
21955
+ * Adopt run-script start/stop persisted by MCP/CLI while this process owns
21956
+ * live children (desktop host). Spawning in stdio leaves the Run tab on
21957
+ * "Starting…" with no logs — `run_output` never reaches the renderer.
21958
+ */
21959
+ adoptPersistedRunScripts() {
21960
+ if (!this.shouldOwnRunScripts()) return;
21961
+ for (const thread of listThreads()) {
21962
+ if (thread.status === "archived") continue;
21963
+ const req = thread.runScriptRequest;
21964
+ if (!isUnclaimedRunScriptRequest(req) || !req) continue;
21965
+ const claimedAt = (/* @__PURE__ */ new Date()).toISOString();
21966
+ const claimed = { ...req, claimedAt };
21967
+ updateThread(thread.id, { runScriptRequest: claimed });
21968
+ void this.applyRunScriptRequest(thread.id, claimed);
21969
+ }
21970
+ }
21971
+ async applyRunScriptRequest(threadId, req) {
21972
+ try {
21973
+ if (req.op === "start") {
21974
+ await this.startDev(threadId, req.scriptName ?? void 0);
21975
+ } else {
21976
+ await this.stopDev(threadId, req.scriptName ?? void 0);
21977
+ }
21978
+ const latest = readThread(threadId);
21979
+ const current = latest?.runScriptRequest;
21980
+ if (current?.requestId === req.requestId && !current.error) {
21981
+ updateThread(threadId, {
21982
+ runScriptRequest: {
21983
+ ...current,
21984
+ fulfilledAt: (/* @__PURE__ */ new Date()).toISOString()
21985
+ }
21986
+ });
21987
+ }
21988
+ } catch (err) {
21989
+ const message = err instanceof Error ? err.message : String(err);
21990
+ const latest = readThread(threadId);
21991
+ const current = latest?.runScriptRequest;
21992
+ if (!current || current.requestId !== req.requestId) return;
21993
+ updateThread(threadId, {
21994
+ runScriptRequest: { ...current, error: message }
21995
+ });
21996
+ }
21997
+ }
21998
+ /**
21999
+ * Same rule as agent-queue drain: MCP stdio must not own Electron children
22000
+ * while Sideboard.app is alive.
22001
+ */
22002
+ shouldOwnRunScripts() {
22003
+ return thisProcessShouldDrainAgentQueues();
21867
22004
  }
21868
22005
  /**
21869
22006
  * Mid-session: a worktree can sit at `running` after the agent process dies
@@ -22912,6 +23049,13 @@ var init_orchestrator = __esm({
22912
23049
  "No run script found for this worktree. Add [scripts.run.*] in the worktree\u2019s .sideboard/settings.toml or .conductor/settings.toml (or settings.local.toml on the main repo)."
22913
23050
  );
22914
23051
  }
23052
+ if (!this.shouldOwnRunScripts()) {
23053
+ const result = await this.requestDesktopRunScript(thread, "start", resolvedName);
23054
+ if (!result) {
23055
+ throw new Error("Desktop did not return a run script port.");
23056
+ }
23057
+ return result;
23058
+ }
22915
23059
  const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
22916
23060
  const inFlight = this.startingDev.get(runKey);
22917
23061
  if (inFlight) return inFlight;
@@ -23034,8 +23178,65 @@ var init_orchestrator = __esm({
23034
23178
  });
23035
23179
  return { port: handle.port, scriptName: resolvedName, ports: handle.ports };
23036
23180
  }
23037
- stopDev(threadRef, scriptName) {
23181
+ /**
23182
+ * Persist a run-script intent for the desktop host (MCP/CLI while the board
23183
+ * is alive). Polls until Electron main adopts and fulfills it.
23184
+ */
23185
+ async requestDesktopRunScript(thread, op, scriptName) {
23186
+ const latest = readThread(thread.id) ?? thread;
23187
+ const pending = latest.runScriptRequest;
23188
+ const claimedAt = pending?.claimedAt;
23189
+ const staleClaim = typeof claimedAt === "string" && Date.now() - Date.parse(claimedAt) > this.runScriptAdoptTimeoutMs;
23190
+ const sameIntent = pending && pending.op === op && (pending.scriptName ?? null) === (scriptName ?? null) && !pending.error && !pending.fulfilledAt && !staleClaim;
23191
+ const request = sameIntent ? pending : {
23192
+ op,
23193
+ scriptName: scriptName ?? null,
23194
+ requestId: (0, import_node_crypto12.randomUUID)(),
23195
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString()
23196
+ };
23197
+ if (!sameIntent) {
23198
+ updateThread(thread.id, { runScriptRequest: request });
23199
+ }
23200
+ return this.waitForDesktopRunScript(thread.id, request, op, scriptName);
23201
+ }
23202
+ async waitForDesktopRunScript(threadId, request, op, scriptName) {
23203
+ const deadline = Date.now() + this.runScriptAdoptTimeoutMs;
23204
+ while (Date.now() < deadline) {
23205
+ const latest = readThread(threadId);
23206
+ if (!latest) {
23207
+ throw new Error("Thread disappeared while waiting for the desktop run script.");
23208
+ }
23209
+ const req = latest.runScriptRequest;
23210
+ if (req?.requestId === request.requestId && req.error) {
23211
+ throw new Error(req.error);
23212
+ }
23213
+ const shared = mergeWorktreeActiveRuns(threadsSharingWorktree(latest.worktreePath));
23214
+ const run2 = scriptName ? shared.activeRuns.find((r) => r.scriptName === scriptName) : shared.activeRuns[0];
23215
+ const fulfilled = req?.requestId === request.requestId && Boolean(req.fulfilledAt);
23216
+ const requestGone = !req || req.requestId !== request.requestId;
23217
+ if (op === "start") {
23218
+ if (run2 && (fulfilled || requestGone || run2.startedAt >= request.requestedAt)) {
23219
+ return { port: run2.port, scriptName: run2.scriptName, ports: run2.ports };
23220
+ }
23221
+ if (fulfilled && !run2) {
23222
+ throw new Error("Desktop reported the run script started, but it is not active.");
23223
+ }
23224
+ } else {
23225
+ const gone = scriptName ? !run2 : shared.activeRuns.length === 0;
23226
+ if (fulfilled || gone) return null;
23227
+ }
23228
+ await new Promise((r) => setTimeout(r, 40));
23229
+ }
23230
+ throw new Error(
23231
+ op === "start" ? "Desktop did not start the run script in time. Is Sideboard.app running this worktree?" : "Desktop did not stop the run script in time."
23232
+ );
23233
+ }
23234
+ async stopDev(threadRef, scriptName) {
23038
23235
  const thread = this.requireThread(threadRef);
23236
+ if (!this.shouldOwnRunScripts()) {
23237
+ await this.requestDesktopRunScript(thread, "stop", scriptName);
23238
+ return;
23239
+ }
23039
23240
  const siblings = threadsSharingWorktree(thread.worktreePath);
23040
23241
  const shared = mergeWorktreeActiveRuns(siblings);
23041
23242
  if (scriptName) {
@@ -24029,7 +24230,7 @@ init_system_ca();
24029
24230
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
24030
24231
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
24031
24232
  var import_zod9 = require("zod");
24032
- var import_node_crypto14 = require("crypto");
24233
+ var import_node_crypto15 = require("crypto");
24033
24234
  var import_node_path55 = require("path");
24034
24235
  init_orchestrator();
24035
24236
  init_worktree();
@@ -24456,7 +24657,7 @@ async function createGitHubIssue(input, opts) {
24456
24657
  init_fetch();
24457
24658
 
24458
24659
  // src/integrations/linear-oauth.ts
24459
- var import_node_crypto12 = require("crypto");
24660
+ var import_node_crypto13 = require("crypto");
24460
24661
  var import_node_http2 = require("http");
24461
24662
  init_app_settings();
24462
24663
  init_fetch();
@@ -25730,6 +25931,23 @@ function mcpArchiveBlockedReason(thread) {
25730
25931
 
25731
25932
  // src/mcp/server.ts
25732
25933
  init_profile();
25934
+
25935
+ // src/mcp/run-script-ref.ts
25936
+ init_chat_tabs();
25937
+ function resolveRunScriptThreadRef(ref) {
25938
+ const explicit = ref?.trim();
25939
+ if (explicit) return explicit;
25940
+ const siblings = threadsSharingWorktree(process.cwd());
25941
+ const thread = siblings[0];
25942
+ if (!thread) {
25943
+ throw new Error(
25944
+ "No Sideboard thread for this cwd. Pass ref (thread id), or call from a worktree chat."
25945
+ );
25946
+ }
25947
+ return thread.id;
25948
+ }
25949
+
25950
+ // src/mcp/server.ts
25733
25951
  init_wait_for_turn();
25734
25952
  init_wait_for_job();
25735
25953
  init_message_parts();
@@ -27439,7 +27657,7 @@ init_worktree();
27439
27657
  init_paths();
27440
27658
 
27441
27659
  // src/board/board-pins.ts
27442
- var import_node_crypto13 = require("crypto");
27660
+ var import_node_crypto14 = require("crypto");
27443
27661
  var import_node_fs56 = require("fs");
27444
27662
  var import_node_path53 = require("path");
27445
27663
  init_paths();
@@ -28161,13 +28379,73 @@ async function startMcpServer() {
28161
28379
  "Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
28162
28380
  {
28163
28381
  id: import_zod9.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
28164
- reason: import_zod9.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
28382
+ reason: import_zod9.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the answer)")
28165
28383
  },
28166
28384
  async ({ id, reason }) => {
28167
28385
  const result = await stopDetachedJob(process.cwd(), id, { reason });
28168
28386
  return mcpJson(result);
28169
28387
  }
28170
28388
  );
28389
+ server.tool(
28390
+ "list_run_scripts",
28391
+ "List .sideboard/.conductor run scripts for this thread (same menu as the desktop Dev button) and which are active. Worktree turns may omit ref (uses cwd).",
28392
+ {
28393
+ ref: import_zod9.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd).")
28394
+ },
28395
+ async ({ ref }) => {
28396
+ try {
28397
+ const threadRef = resolveRunScriptThreadRef(ref);
28398
+ const scripts = orch.listThreadRunScripts(threadRef);
28399
+ const active = orch.getActiveRuns(threadRef);
28400
+ return mcpJson({ scripts, active, ref: threadRef });
28401
+ } catch (err) {
28402
+ const message = err instanceof Error ? err.message : String(err);
28403
+ return { content: [{ type: "text", text: message }], isError: true };
28404
+ }
28405
+ }
28406
+ );
28407
+ server.tool(
28408
+ "run_dev_script",
28409
+ "Start a .sideboard/.conductor run script (same as the desktop Dev / play button). Logs and Stop appear in the thread UI \u2014 do not detached-job or shell-spawn the same command. Returns the allocated port. Worktree turns may omit ref (uses cwd). Omit name for the default script.",
28410
+ {
28411
+ ref: import_zod9.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
28412
+ name: import_zod9.z.string().optional().describe("Script name from list_run_scripts (default script if omitted).")
28413
+ },
28414
+ async ({ ref, name }) => {
28415
+ try {
28416
+ const threadRef = resolveRunScriptThreadRef(ref);
28417
+ const result = await orch.startDev(threadRef, name);
28418
+ return mcpJson({
28419
+ port: result.port,
28420
+ scriptName: result.scriptName,
28421
+ ports: result.ports,
28422
+ url: `http://localhost:${result.port}`,
28423
+ ref: threadRef
28424
+ });
28425
+ } catch (err) {
28426
+ const message = err instanceof Error ? err.message : String(err);
28427
+ return { content: [{ type: "text", text: message }], isError: true };
28428
+ }
28429
+ }
28430
+ );
28431
+ server.tool(
28432
+ "stop_dev_script",
28433
+ "Stop a run script started with run_dev_script or the desktop Dev button (same as UI Stop). Omit name to stop all scripts on this worktree. Worktree turns may omit ref (uses cwd).",
28434
+ {
28435
+ ref: import_zod9.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
28436
+ name: import_zod9.z.string().optional().describe("Script name to stop; omit to stop all active scripts.")
28437
+ },
28438
+ async ({ ref, name }) => {
28439
+ try {
28440
+ const threadRef = resolveRunScriptThreadRef(ref);
28441
+ await orch.stopDev(threadRef, name);
28442
+ return mcpJson({ ok: true, ref: threadRef, stopped: name ?? "all" });
28443
+ } catch (err) {
28444
+ const message = err instanceof Error ? err.message : String(err);
28445
+ return { content: [{ type: "text", text: message }], isError: true };
28446
+ }
28447
+ }
28448
+ );
28171
28449
  if (!worktreeProfile) {
28172
28450
  registerSlackTools(server);
28173
28451
  registerConnectedIssueVendorTools(server);
@@ -28288,7 +28566,7 @@ async function startMcpServer() {
28288
28566
  const ident = issue?.identifier ?? ref.trim();
28289
28567
  const attachments = issue ? [
28290
28568
  {
28291
- id: (0, import_node_crypto14.randomUUID)(),
28569
+ id: (0, import_node_crypto15.randomUUID)(),
28292
28570
  name: issue.identifier,
28293
28571
  kind: "issue",
28294
28572
  content: [
@@ -28646,45 +28924,6 @@ async function startMcpServer() {
28646
28924
  }
28647
28925
  }
28648
28926
  );
28649
- server.tool(
28650
- "run_dev_script",
28651
- "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
28652
- {
28653
- ref: import_zod9.z.string(),
28654
- name: import_zod9.z.string().optional()
28655
- },
28656
- async ({ ref, name }) => {
28657
- const result = await orch.startDev(ref, name);
28658
- return mcpJson({
28659
- port: result.port,
28660
- scriptName: result.scriptName,
28661
- ports: result.ports,
28662
- url: `http://localhost:${result.port}`
28663
- });
28664
- }
28665
- );
28666
- server.tool(
28667
- "list_run_scripts",
28668
- "List named run scripts available for a thread",
28669
- { ref: import_zod9.z.string() },
28670
- async ({ ref }) => {
28671
- const scripts = orch.listThreadRunScripts(ref);
28672
- const active = orch.getActiveRuns(ref);
28673
- return mcpJson({ scripts, active });
28674
- }
28675
- );
28676
- server.tool(
28677
- "stop_dev_script",
28678
- "Stop a running script for a thread (all scripts if name omitted)",
28679
- {
28680
- ref: import_zod9.z.string(),
28681
- name: import_zod9.z.string().optional()
28682
- },
28683
- async ({ ref, name }) => {
28684
- orch.stopDev(ref, name);
28685
- return { content: [{ type: "text", text: "ok" }] };
28686
- }
28687
- );
28688
28927
  server.tool(
28689
28928
  "run_setup",
28690
28929
  "Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",