@sideboard-ai/core 0.1.139 → 0.1.142

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 (63) hide show
  1. package/dist/{abletime-VTP4JR63.js → abletime-DI55AJX3.js} +2 -2
  2. package/dist/{abletime-DDTV7WO3.js → abletime-QEVAE67D.js} +2 -2
  3. package/dist/agents/cursor-runner.cjs +0 -0
  4. package/dist/agents/cursor-runner.js +1 -1
  5. package/dist/{agents-665S7Z3R.js → agents-E5AAMHDY.js} +11 -7
  6. package/dist/{agents-OC3XM7UE.js → agents-OEEOIKUB.js} +10 -6
  7. package/dist/{app-settings-U5RZKUXG.js → app-settings-WJDCJFSY.js} +3 -1
  8. package/dist/{app-settings-YQ7YX2CC.js → app-settings-ZCN632OJ.js} +3 -1
  9. package/dist/caffeinate-hold-NBRUWYXO.js +0 -0
  10. package/dist/{chunk-HLIHBGVO.js → chunk-4D4PEBGB.js} +2 -2
  11. package/dist/{chunk-DJJ3DTT4.js → chunk-4Q45TLZ5.js} +7 -7
  12. package/dist/chunk-6GN5WPYZ.js +0 -0
  13. package/dist/chunk-B3SJXYIJ.js +0 -0
  14. package/dist/chunk-BD2ICC4D.js +0 -0
  15. package/dist/{chunk-GKKHXZCT.js → chunk-BGQBELGJ.js} +201 -2
  16. package/dist/{chunk-2XXAAIN3.js → chunk-BOD5V52W.js} +1 -1
  17. package/dist/{chunk-23KCPND2.js → chunk-CG25RYQQ.js} +47 -40
  18. package/dist/chunk-ED4UPEJX.js +0 -0
  19. package/dist/chunk-EUXOHTUK.js +0 -0
  20. package/dist/{chunk-5XH5M6RA.js → chunk-FADNFPZO.js} +3 -3
  21. package/dist/{chunk-ZCLHAOFR.js → chunk-FGOD5MQ4.js} +479 -37
  22. package/dist/{chunk-SSBM4GZX.js → chunk-HC5N3BDL.js} +2 -2
  23. package/dist/{chunk-GJBHOJCZ.js → chunk-I77RYPOH.js} +201 -2
  24. package/dist/{chunk-YZQEJOAU.js → chunk-JUWVAMRS.js} +280 -34
  25. package/dist/chunk-KGZBYWZ3.js +0 -0
  26. package/dist/chunk-KLBOWJ74.js +0 -0
  27. package/dist/chunk-KPIYENTF.js +0 -0
  28. package/dist/chunk-N5PM7HGQ.js +0 -0
  29. package/dist/chunk-NSTQ6QKD.js +0 -0
  30. package/dist/{chunk-DKXZCIB2.js → chunk-O3JEC2UJ.js} +7 -7
  31. package/dist/{chunk-RTX3AY42.js → chunk-OHWN4JEL.js} +5 -3
  32. package/dist/{chunk-E2RE7P2S.js → chunk-R2W7A2UO.js} +3 -3
  33. package/dist/chunk-S42XV45P.js +0 -0
  34. package/dist/{chunk-7SYFWPOZ.js → chunk-UG5N7ET3.js} +5 -3
  35. package/dist/{chunk-CDJISVKN.js → chunk-VHTIHOHE.js} +47 -41
  36. package/dist/{chunk-AT346U23.js → chunk-VZJVKQHH.js} +1 -1
  37. package/dist/{chunk-FSCQXMGI.js → chunk-WT5HSFOU.js} +1 -0
  38. package/dist/connected-teams-IJUJQ7R6.js +0 -0
  39. package/dist/{coordinator-prompt-43O6EPA2.js → coordinator-prompt-2LD3C74O.js} +3 -3
  40. package/dist/{coordinator-prompt-4TXU6Q3R.js → coordinator-prompt-FBX7Y4EM.js} +3 -3
  41. package/dist/cursor-recover-NOQYOFRF.js +0 -0
  42. package/dist/{global-workspace-ZDYKHQ4O.js → global-workspace-NG7SU3GV.js} +4 -4
  43. package/dist/{global-workspace-VIU57E3Y.js → global-workspace-OCV77UYF.js} +4 -4
  44. package/dist/index.cjs +867 -126
  45. package/dist/index.d.cts +222 -60
  46. package/dist/index.d.ts +222 -60
  47. package/dist/index.js +100 -14
  48. package/dist/mcp/run-stdio.cjs +583 -109
  49. package/dist/mcp/run-stdio.js +12 -12
  50. package/dist/oauth-YJBAURIJ.js +0 -0
  51. package/dist/{orchestrator-ZCMYSDTC.js → orchestrator-4BEZJ2BU.js} +7 -7
  52. package/dist/{orchestrator-WEFXUHFX.js → orchestrator-I2T44CRP.js} +8 -8
  53. package/dist/paths-QCI3MTO5.js +0 -0
  54. package/dist/plan-file-PHVKUAEE.js +0 -0
  55. package/dist/run-USF6FRKV.js +0 -0
  56. package/dist/thread-store-57ZLHR3A.js +0 -0
  57. package/dist/title-4WAKWZRF.js +0 -0
  58. package/dist/{workspaces-4HYGNH4B.js → workspaces-4FUQW5LD.js} +5 -5
  59. package/dist/{workspaces-ALCTB65T.js → workspaces-PU5YHQ7Z.js} +5 -5
  60. package/dist/{worktree-GXD2NGOZ.js → worktree-GKLPPNWR.js} +2 -2
  61. package/dist/{worktree-NSNZODAM.js → worktree-Z22HTSCU.js} +2 -2
  62. package/package.json +9 -8
  63. package/LICENSE +0 -190
@@ -19,6 +19,7 @@ import {
19
19
  looksLikeInvalidAgentSession,
20
20
  looksLikeV8Oom,
21
21
  normalizeParseResult,
22
+ packagedMcpDir,
22
23
  parseBrightsyCliLine,
23
24
  parseSessionQuotaResetAt,
24
25
  partsToAssistantText,
@@ -31,13 +32,13 @@ import {
31
32
  summarizeTurnStderr,
32
33
  toolDescription,
33
34
  turnFailChatText
34
- } from "./chunk-23KCPND2.js";
35
+ } from "./chunk-CG25RYQQ.js";
35
36
  import {
36
37
  addWorkspace,
37
38
  ensureWorkspace,
38
39
  removeWorkspace,
39
40
  syncWorkspacesFromThreads
40
- } from "./chunk-HLIHBGVO.js";
41
+ } from "./chunk-4D4PEBGB.js";
41
42
  import {
42
43
  extractPresentedPlan,
43
44
  readPlanFile,
@@ -62,14 +63,14 @@ import {
62
63
  persistPendingFileAttachments,
63
64
  stageAbsolutePathsAsAttachments,
64
65
  stageBuffersAsAttachments
65
- } from "./chunk-5XH5M6RA.js";
66
+ } from "./chunk-FADNFPZO.js";
66
67
  import {
67
68
  SLACK_REPLY_FORMATTING,
68
69
  coordinatorSystemPrompt,
69
70
  coordinatorTurnReminder,
70
71
  enrichWorkspacesWithGithub,
71
72
  ensureGlobalCoordinatorCwd
72
- } from "./chunk-RTX3AY42.js";
73
+ } from "./chunk-OHWN4JEL.js";
73
74
  import {
74
75
  addPrStackLayer,
75
76
  allocateTeamName,
@@ -114,7 +115,7 @@ import {
114
115
  withRepoGitLock,
115
116
  worktreeDisplayLabelForGroup,
116
117
  worktreeNameFromPath
117
- } from "./chunk-DJJ3DTT4.js";
118
+ } from "./chunk-4Q45TLZ5.js";
118
119
  import {
119
120
  ATTACHMENTS_DIR,
120
121
  LEGACY_ATTACHMENTS_DIR
@@ -154,7 +155,7 @@ import {
154
155
  stripNestedElectronEnv,
155
156
  updateAdvancedSettings,
156
157
  writeSecureJson
157
- } from "./chunk-GKKHXZCT.js";
158
+ } from "./chunk-BGQBELGJ.js";
158
159
  import {
159
160
  writePrivateFile
160
161
  } from "./chunk-NSTQ6QKD.js";
@@ -278,7 +279,7 @@ function slackTokenFor(ws, kind = "read") {
278
279
  const token2 = ws.user_token?.trim();
279
280
  if (!token2) {
280
281
  throw new Error(
281
- `Slack search needs a user token for ${ws.team_name}. Reconnect via Account \u2192 Slack (browser) or paste an xoxp- token.`
282
+ `Slack search needs a user token for ${ws.team_name}. Reconnect via Settings \u2192 Remote \u2192 Slack (browser) or paste an xoxp- token.`
282
283
  );
283
284
  }
284
285
  return token2;
@@ -293,7 +294,7 @@ function requireSlackWorkspace(teamId) {
293
294
  const ws = getSlackWorkspace(teamId);
294
295
  if (!ws) {
295
296
  const connected = listSlackWorkspaces();
296
- const hint = connected.length === 0 ? "Connect a workspace in Account \u2192 Slack workspaces." : `Connected: ${connected.map((t) => `${t.team_name} (${t.team_id})`).join(", ")}`;
297
+ const hint = connected.length === 0 ? "Connect a workspace in Settings \u2192 Remote \u2192 Slack." : `Connected: ${connected.map((t) => `${t.team_name} (${t.team_id})`).join(", ")}`;
297
298
  throw new Error(`Unknown Slack team_id "${teamId}". ${hint}`);
298
299
  }
299
300
  return ws;
@@ -387,7 +388,7 @@ async function continueSourceThread(threadId, prompt) {
387
388
  await continueOnReply(threadId, prompt);
388
389
  return;
389
390
  }
390
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-ZCMYSDTC.js");
391
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-4BEZJ2BU.js");
391
392
  await getOrchestrator2().send(threadId, prompt);
392
393
  } catch {
393
394
  }
@@ -647,7 +648,7 @@ async function pollSlackOutboundWatches(opts) {
647
648
  }
648
649
 
649
650
  // src/orchestrator/orchestrator.ts
650
- import { existsSync as existsSync15 } from "fs";
651
+ import { existsSync as existsSync16 } from "fs";
651
652
 
652
653
  // src/agents/spawn.ts
653
654
  import { createInterface } from "readline";
@@ -728,9 +729,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
728
729
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
729
730
  );
730
731
  }
731
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-VIU57E3Y.js");
732
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-OCV77UYF.js");
732
733
  if (isGlobalThread2(thread)) {
733
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-4TXU6Q3R.js");
734
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-FBX7Y4EM.js");
734
735
  ensureGlobalCoordinatorCwd2(
735
736
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
736
737
  );
@@ -2810,7 +2811,7 @@ async function createThread(input, _onSetupLine) {
2810
2811
  });
2811
2812
  await requireAgent(resolved.agent);
2812
2813
  if (input.cowboy) {
2813
- const { cowboyModeEnabled } = await import("./app-settings-YQ7YX2CC.js");
2814
+ const { cowboyModeEnabled } = await import("./app-settings-ZCN632OJ.js");
2814
2815
  if (!cowboyModeEnabled()) {
2815
2816
  throw new Error(
2816
2817
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -2866,7 +2867,7 @@ async function createThread(input, _onSetupLine) {
2866
2867
  let sourceIsFork = false;
2867
2868
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
2868
2869
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
2869
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-VTP4JR63.js");
2870
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-DI55AJX3.js");
2870
2871
  const task = await ensureAbleTimeTask({
2871
2872
  title: title?.trim() || "Untitled work",
2872
2873
  description: input.prompt?.trim() || void 0
@@ -2939,7 +2940,7 @@ async function createThread(input, _onSetupLine) {
2939
2940
  return readThread(thread.id) ?? thread;
2940
2941
  }
2941
2942
  async function listLinearIssues(agent, repoPath) {
2942
- const { getAdapter: getAdapter2 } = await import("./agents-OC3XM7UE.js");
2943
+ const { getAdapter: getAdapter2 } = await import("./agents-OEEOIKUB.js");
2943
2944
  await requireAgent(agent, { requireLinear: true });
2944
2945
  const adapter = getAdapter2(agent);
2945
2946
  if (!adapter.listLinearIssues) {
@@ -3336,7 +3337,7 @@ async function adoptThread(input) {
3336
3337
  messages: input.messages ?? []
3337
3338
  });
3338
3339
  writeThread(thread);
3339
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4HYGNH4B.js");
3340
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4FUQW5LD.js");
3340
3341
  await ensureWorkspace2(repoPath);
3341
3342
  return thread;
3342
3343
  }
@@ -4666,6 +4667,93 @@ async function confirmLand(thread, opts) {
4666
4667
  import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
4667
4668
  import { homedir } from "os";
4668
4669
  import { join as join11 } from "path";
4670
+
4671
+ // src/skills/bundled/long-running.ts
4672
+ var LONG_RUNNING_SKILL_COMMAND = "long-running";
4673
+ var LONG_RUNNING_SKILL_NAME = "long-running";
4674
+ var BUNDLED_LONG_RUNNING_PATH = "bundled:long-running";
4675
+ var LONG_RUNNING_SKILL_DESCRIPTION = "Detach and wait on any long worktree job (pack, test, deploy) so a new chat turn does not SIGTERM it, and the human does not have to poll. Use when a command may run more than ~30s, or the user mentions wait / status / aborted builds.";
4676
+ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
4677
+
4678
+ A Sideboard or Cursor **worktree turn** SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. \`block_until_ms: 0\` is not enough \u2014 the child stays in that group.
4679
+
4680
+ Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as MCP \`wait_for_turn\`) until \`stillRunning\` is false.
4681
+
4682
+ ## Tool
4683
+
4684
+ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.js\`, that is the same tool.
4685
+
4686
+ \`\`\`bash
4687
+ # Start (exits in ~1s; job survives this turn)
4688
+ node <detached-job.js> start <id> -- <command> [args...]
4689
+
4690
+ # Wait \u2014 returns within ~45s even if the job is still going
4691
+ node <detached-job.js> wait <id>
4692
+
4693
+ # Block until the process exits (humans / a turn that will not be interrupted)
4694
+ node <detached-job.js> wait <id> --until-done
4695
+
4696
+ node <detached-job.js> status <id>
4697
+ \`\`\`
4698
+
4699
+ \`<id>\` is kebab-case (\`mac-release\`, \`core-test\`, \`fly-deploy\`). State is \`.sideboard/detached-jobs/<id>/\` (local scratch).
4700
+
4701
+ Wait JSON:
4702
+
4703
+ - \`stillRunning: true\` \u2192 exit 2 \u2192 **call wait again**. Progress is in \`progress\` / \`phase\`. Do not start a second job. Do not ping the user.
4704
+ - \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
4705
+ - \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
4706
+
4707
+ ## Sideboard UI (append-only log)
4708
+
4709
+ The side column **is** the live view. Use \`present_artifact\` **\`type=log\`** with a stable \`artifact_id\` (the job id). Each call **appends** \u2014 send only \`delta\` from wait JSON, not the full log and not HTML.
4710
+
4711
+ \`\`\`
4712
+ present_artifact
4713
+ title: <short title>
4714
+ type: log
4715
+ artifact_id: <id>
4716
+ content: <wait.delta> # new lines only; empty is ok
4717
+ status: running | ok | failed # from wait.status
4718
+ phase: <wait.phase>
4719
+ \`\`\`
4720
+
4721
+ After **start**, present once (\`status=running\`, empty or first lines). After **every wait**, present the same id with \`content=delta\`. Do **not** dump the log in chat. Do **not** also fence HTML.
4722
+
4723
+ Ad-hoc pid/log (legacy or another tool\u2019s files):
4724
+
4725
+ \`\`\`bash
4726
+ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
4727
+ \`\`\`
4728
+
4729
+ ## Agent loop
4730
+
4731
+ 1. \`start\` once. If JSON says \`already-running\`, do not start again.
4732
+ 2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
4733
+ 3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
4734
+ 4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
4735
+
4736
+ Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
4737
+ `;
4738
+
4739
+ // src/skills/discover.ts
4740
+ var BUNDLED_SKILL_PREFIX = "bundled:";
4741
+ function bundledSkills() {
4742
+ return [
4743
+ {
4744
+ id: BUNDLED_LONG_RUNNING_PATH,
4745
+ name: LONG_RUNNING_SKILL_NAME,
4746
+ command: LONG_RUNNING_SKILL_COMMAND,
4747
+ description: LONG_RUNNING_SKILL_DESCRIPTION.slice(0, 240),
4748
+ path: BUNDLED_LONG_RUNNING_PATH,
4749
+ source: "bundled"
4750
+ }
4751
+ ];
4752
+ }
4753
+ function bundledSkillBody(id) {
4754
+ if (id === LONG_RUNNING_SKILL_COMMAND) return LONG_RUNNING_SKILL_BODY;
4755
+ return null;
4756
+ }
4669
4757
  function toCommand(name) {
4670
4758
  return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4671
4759
  }
@@ -4783,7 +4871,13 @@ function discoverSkills(worktreePath) {
4783
4871
  scanSkillsDir(abs, "user", collected);
4784
4872
  }
4785
4873
  scanClaudePluginSkills(join11(home, ".claude/plugins"), collected);
4786
- const rank = { workspace: 0, user: 1, cli: 2 };
4874
+ collected.push(...bundledSkills());
4875
+ const rank = {
4876
+ workspace: 0,
4877
+ user: 1,
4878
+ cli: 2,
4879
+ bundled: 3
4880
+ };
4787
4881
  const byCommand = /* @__PURE__ */ new Map();
4788
4882
  for (const skill of collected) {
4789
4883
  const prev = byCommand.get(skill.command);
@@ -4794,6 +4888,14 @@ function discoverSkills(worktreePath) {
4794
4888
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
4795
4889
  }
4796
4890
  function readSkillBody(skillPath, maxChars = 12e3) {
4891
+ if (skillPath.startsWith(BUNDLED_SKILL_PREFIX)) {
4892
+ const body = bundledSkillBody(skillPath.slice(BUNDLED_SKILL_PREFIX.length));
4893
+ if (body) {
4894
+ return body.length > maxChars ? `${body.slice(0, maxChars)}
4895
+
4896
+ \u2026(truncated)` : body;
4897
+ }
4898
+ }
4797
4899
  const raw = readFileSync9(skillPath, "utf8");
4798
4900
  if (raw.startsWith("---")) {
4799
4901
  const end = raw.indexOf("\n---", 3);
@@ -4887,8 +4989,39 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
4887
4989
  }
4888
4990
 
4889
4991
  // src/agents/instructions.ts
4890
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
4891
- import { join as join12 } from "path";
4992
+ import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
4993
+ import { join as join13 } from "path";
4994
+
4995
+ // src/skills/detached-job-path.ts
4996
+ import { existsSync as existsSync13 } from "fs";
4997
+ import { dirname as dirname4, join as join12 } from "path";
4998
+ import { fileURLToPath } from "url";
4999
+ function packagedDetachedJobPath() {
5000
+ const dir = packagedMcpDir();
5001
+ if (!dir) return null;
5002
+ const script = join12(dir, "scripts", "detached-job.js");
5003
+ return existsSync13(script) ? script : null;
5004
+ }
5005
+ function resolveDetachedJobScript() {
5006
+ const packaged = packagedDetachedJobPath();
5007
+ if (packaged) return packaged;
5008
+ let dir = dirname4(fileURLToPath(import.meta.url));
5009
+ for (let i = 0; i < 8; i++) {
5010
+ const candidate = join12(dir, "scripts", "detached-job.js");
5011
+ if (existsSync13(candidate)) return candidate;
5012
+ const parent = dirname4(dir);
5013
+ if (parent === dir) break;
5014
+ dir = parent;
5015
+ }
5016
+ return null;
5017
+ }
5018
+ function formatDetachedJobInvoke(scriptPath) {
5019
+ const resolved = scriptPath === void 0 ? resolveDetachedJobScript() : scriptPath;
5020
+ if (resolved) return `node ${JSON.stringify(resolved)}`;
5021
+ return "node scripts/detached-job.js";
5022
+ }
5023
+
5024
+ // src/agents/instructions.ts
4892
5025
  function normPath(p) {
4893
5026
  return p.replace(/\/+$/, "");
4894
5027
  }
@@ -4996,6 +5129,7 @@ function formatWorktreeReminder() {
4996
5129
  function formatProcessGuideDirective() {
4997
5130
  return [
4998
5131
  "Process guides (recurring work only):",
5132
+ "- Long jobs (pack, test, deploy, anything that may run more than ~30s): Sideboard always provides `/long-running`. Detach and wait \u2014 do not ask the human to poll.",
4999
5133
  "- If `.claude/skills/graph-engineering/SKILL.md` exists, follow it (`/graph-engineering`) for migrations, ports, batch fixes, and other fan-out. Judge first; state on disk; grow the rulebook; do not patch around it.",
5000
5134
  "- If this same shape of work will happen again, write `.claude/skills/<kebab-name>/SKILL.md` in this worktree (Claude Code project skill). Sideboard `/name`, Claude Code, and `attach` all load that path. Do not leave the method only in chat.",
5001
5135
  "- Merge-readiness notes: create or edit `.claude/skills/review/SKILL.md` and commit it. That file is allowed. Do not use `.sideboard/review.md` for new notes (legacy; setup copies it into the skill).",
@@ -5003,6 +5137,23 @@ function formatProcessGuideDirective() {
5003
5137
  "- Skip a guide for a one-off. If a matching skill exists, follow it. Same miss twice \u2192 edit the skill, do not patch around it."
5004
5138
  ].join("\n");
5005
5139
  }
5140
+ function formatLongRunningDirective(opts) {
5141
+ const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5142
+ return [
5143
+ "Long-running jobs (mandatory when a command may run more than ~30s):",
5144
+ "A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
5145
+ `Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
5146
+ `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5147
+ "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
5148
+ `- Loop \`${invoke} wait <id>\` (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait again. Do not resend the full log or HTML.`,
5149
+ "- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
5150
+ "State: `.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5151
+ ].join("\n");
5152
+ }
5153
+ function formatLongRunningReminder(opts) {
5154
+ const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5155
+ return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
5156
+ }
5006
5157
  function formatArtifactDirective() {
5007
5158
  return [
5008
5159
  "Sideboard side column (desktop UI):",
@@ -5032,6 +5183,91 @@ function formatUiReminder() {
5032
5183
  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 or \u201Cwhat next?\u201D) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable.";
5033
5184
  }
5034
5185
 
5186
+ // src/integrations/optional-services.ts
5187
+ var OPTIONAL_SERVICES = [
5188
+ {
5189
+ id: "vercel",
5190
+ label: "Vercel",
5191
+ hint: "Deploys and project settings. Agents use the `vercel` CLI (`VERCEL_TOKEN`).",
5192
+ tokenPlaceholder: "token from vercel.com/account/tokens",
5193
+ tokenDocs: "https://vercel.com/account/tokens",
5194
+ envKey: "VERCEL_TOKEN",
5195
+ cli: "vercel",
5196
+ npmPackage: "vercel"
5197
+ },
5198
+ {
5199
+ id: "supabase",
5200
+ label: "Supabase",
5201
+ hint: "Database and project management. Agents use the `supabase` CLI (`SUPABASE_ACCESS_TOKEN`).",
5202
+ tokenPlaceholder: "sbp_\u2026",
5203
+ tokenDocs: "https://supabase.com/dashboard/account/tokens",
5204
+ envKey: "SUPABASE_ACCESS_TOKEN",
5205
+ cli: "supabase",
5206
+ npmPackage: "supabase"
5207
+ },
5208
+ {
5209
+ id: "posthog",
5210
+ label: "PostHog",
5211
+ hint: "Product analytics. No first-class CLI \u2014 agents use the HTTP API (`POSTHOG_PERSONAL_API_KEY`).",
5212
+ tokenPlaceholder: "phx_\u2026",
5213
+ tokenDocs: "https://app.posthog.com/settings/user-api-keys",
5214
+ envKey: "POSTHOG_PERSONAL_API_KEY",
5215
+ hostEnvKey: "POSTHOG_HOST",
5216
+ defaultHost: "https://us.posthog.com",
5217
+ hostPlaceholder: "https://us.posthog.com"
5218
+ },
5219
+ {
5220
+ id: "sentry",
5221
+ label: "Sentry",
5222
+ hint: "Error tracking. Agents use `sentry-cli` (`SENTRY_AUTH_TOKEN`).",
5223
+ tokenPlaceholder: "sntrys_\u2026 or auth token",
5224
+ tokenDocs: "https://sentry.io/settings/account/api/auth-tokens/",
5225
+ envKey: "SENTRY_AUTH_TOKEN",
5226
+ cli: "sentry-cli",
5227
+ npmPackage: "@sentry/cli",
5228
+ hostEnvKey: "SENTRY_URL",
5229
+ defaultHost: "https://sentry.io",
5230
+ hostPlaceholder: "https://sentry.io"
5231
+ }
5232
+ ];
5233
+ function optionalServiceConnected(id, integrations) {
5234
+ if (id === "vercel") return Boolean(integrations.vercelToken?.trim());
5235
+ if (id === "supabase") return Boolean(integrations.supabaseAccessToken?.trim());
5236
+ if (id === "posthog") return Boolean(integrations.posthogPersonalApiKey?.trim());
5237
+ return Boolean(integrations.sentryAuthToken?.trim());
5238
+ }
5239
+ function connectedOptionalServices(integrations) {
5240
+ return OPTIONAL_SERVICES.filter((spec) => optionalServiceConnected(spec.id, integrations));
5241
+ }
5242
+ function formatOptionalServicesDirective(integrations) {
5243
+ const connected = connectedOptionalServices(integrations);
5244
+ if (connected.length === 0) return null;
5245
+ const lines = [
5246
+ "Optional connectors (connected \u2014 tokens are already in this process env):"
5247
+ ];
5248
+ for (const spec of connected) {
5249
+ if (spec.cli) {
5250
+ const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
5251
+ lines.push(
5252
+ `- ${spec.label}: use the \`${spec.cli}\` CLI (\`${spec.envKey}\`${extras}). Do not add a ${spec.label} MCP.`
5253
+ );
5254
+ } else {
5255
+ const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
5256
+ lines.push(
5257
+ `- ${spec.label}: no first-class CLI \u2014 call the HTTP API with \`${spec.envKey}\`${extras}. Do not add a ${spec.label} MCP.`
5258
+ );
5259
+ }
5260
+ }
5261
+ lines.push(
5262
+ "If a CLI is missing, say so or use the HTTP API with the same token. The user can Install CLI from Settings \u2192 Connectors. Do not ask the user to install a vendor MCP."
5263
+ );
5264
+ return lines.join("\n");
5265
+ }
5266
+ function formatOptionalServicesReminder(integrations) {
5267
+ if (connectedOptionalServices(integrations).length === 0) return null;
5268
+ return "Settings \u2192 Connectors: prefer official CLIs (`vercel`, `supabase`, `sentry-cli`) or the PostHog HTTP API with injected env. Do not add vendor MCPs.";
5269
+ }
5270
+
5035
5271
  // src/threads/sync-branch.ts
5036
5272
  async function syncThreadBranchFromGit(threadId) {
5037
5273
  const thread = readThread(threadId);
@@ -5064,8 +5300,8 @@ async function syncThreadBranchFromGit(threadId) {
5064
5300
 
5065
5301
  // src/store/schedules.ts
5066
5302
  import { randomUUID as randomUUID4 } from "crypto";
5067
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5068
- import { join as join13 } from "path";
5303
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
5304
+ import { join as join14 } from "path";
5069
5305
  import { Cron } from "croner";
5070
5306
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
5071
5307
  var UNIT_MS = {
@@ -5075,7 +5311,7 @@ var UNIT_MS = {
5075
5311
  d: 864e5
5076
5312
  };
5077
5313
  function schedulesPath() {
5078
- return join13(appDataDir(), "schedules.json");
5314
+ return join14(appDataDir(), "schedules.json");
5079
5315
  }
5080
5316
  function parseDurationMs(every) {
5081
5317
  const m = every.trim().match(DURATION_RE);
@@ -5175,7 +5411,7 @@ function normalizeTask(raw) {
5175
5411
  }
5176
5412
  function readAll() {
5177
5413
  const path = schedulesPath();
5178
- if (!existsSync14(path)) return [];
5414
+ if (!existsSync15(path)) return [];
5179
5415
  try {
5180
5416
  const parsed = JSON.parse(readFileSync11(path, "utf8"));
5181
5417
  if (!Array.isArray(parsed)) return [];
@@ -5300,7 +5536,7 @@ function formatScheduledPrompt(name, prompt) {
5300
5536
  ${prompt}`;
5301
5537
  }
5302
5538
  async function defaultDeps() {
5303
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-ZCMYSDTC.js");
5539
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-4BEZJ2BU.js");
5304
5540
  const orch = getOrchestrator2();
5305
5541
  return {
5306
5542
  findThread: (id) => findThreadByRef(id),
@@ -5506,7 +5742,7 @@ var Orchestrator = class {
5506
5742
  }
5507
5743
  continue;
5508
5744
  }
5509
- if (!existsSync15(thread.worktreePath)) {
5745
+ if (!existsSync16(thread.worktreePath)) {
5510
5746
  setStatus(thread.id, "broken", "Worktree missing on disk");
5511
5747
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
5512
5748
  continue;
@@ -5525,7 +5761,7 @@ var Orchestrator = class {
5525
5761
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
5526
5762
  });
5527
5763
  }
5528
- const { autoCleanupOrphansEnabled } = await import("./app-settings-YQ7YX2CC.js");
5764
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-ZCN632OJ.js");
5529
5765
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
5530
5766
  await cleanupOrphanWorktrees({ repoPaths });
5531
5767
  }
@@ -5765,7 +6001,7 @@ var Orchestrator = class {
5765
6001
  }
5766
6002
  await setup;
5767
6003
  if (skipSetup) return;
5768
- const { autoRunAfterSetupEnabled } = await import("./app-settings-YQ7YX2CC.js");
6004
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-ZCN632OJ.js");
5769
6005
  if (autoRunAfterSetupEnabled()) {
5770
6006
  try {
5771
6007
  await this.startDev(threadId);
@@ -6027,7 +6263,9 @@ var Orchestrator = class {
6027
6263
  ].join("\n") : null
6028
6264
  ].filter(Boolean).join("\n") : null;
6029
6265
  const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
6266
+ const longRunningReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatLongRunningReminder() : null;
6030
6267
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
6268
+ const optionalServicesReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatOptionalServicesReminder(loadAppSettings().integrations) : null;
6031
6269
  const slackReplyContext = formatSlackRepliesForTurn(
6032
6270
  pendingSlackExternalReplies(thread.messages)
6033
6271
  );
@@ -6035,7 +6273,9 @@ var Orchestrator = class {
6035
6273
  thread.planMode ? PLAN_MODE_INSTRUCTION : null,
6036
6274
  orchestrationReminder,
6037
6275
  worktreeReminder,
6276
+ optionalServicesReminder,
6038
6277
  artifactReminder,
6278
+ longRunningReminder,
6039
6279
  slackReplyContext,
6040
6280
  expandedPrompt
6041
6281
  ].filter(Boolean).join("\n\n");
@@ -6055,7 +6295,7 @@ var Orchestrator = class {
6055
6295
  }
6056
6296
  const isBrightsy = fresh.agent === "brightsy";
6057
6297
  const isOrchestration = isOrchestratorThread(fresh);
6058
- const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-YQ7YX2CC.js");
6298
+ const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-ZCN632OJ.js");
6059
6299
  const gitAuthMode = getGithubGitAuthMode();
6060
6300
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
6061
6301
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -6064,6 +6304,8 @@ var Orchestrator = class {
6064
6304
  gitAuthMode
6065
6305
  });
6066
6306
  const artifactDirective = isBrightsy ? null : formatArtifactDirective();
6307
+ const longRunningDirective = isBrightsy || isOrchestration ? null : formatLongRunningDirective();
6308
+ const optionalServicesDirective = isBrightsy || isOrchestration ? null : formatOptionalServicesDirective(loadAppSettings().integrations);
6067
6309
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
6068
6310
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
6069
6311
  customPrompt: settings?.prompts?.renameBranch
@@ -6093,7 +6335,9 @@ var Orchestrator = class {
6093
6335
  const cachedPrefix = fresh.sessionId ? "" : [
6094
6336
  coordinatorDirective,
6095
6337
  worktreeDirective,
6338
+ optionalServicesDirective,
6096
6339
  artifactDirective,
6340
+ longRunningDirective,
6097
6341
  renameBranchDirective,
6098
6342
  seed
6099
6343
  ].filter(Boolean).join("\n\n---\n\n");
@@ -6200,7 +6444,9 @@ var Orchestrator = class {
6200
6444
  const retryPrefix = [
6201
6445
  coordinatorDirective,
6202
6446
  worktreeDirective,
6447
+ optionalServicesDirective,
6203
6448
  artifactDirective,
6449
+ longRunningDirective,
6204
6450
  renameBranchDirective,
6205
6451
  retrySeed
6206
6452
  ].filter(Boolean).join("\n\n---\n\n");
@@ -6877,7 +7123,7 @@ var Orchestrator = class {
6877
7123
  updateThread(thread.id, { title: meta.title });
6878
7124
  }
6879
7125
  }
6880
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-YQ7YX2CC.js");
7126
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-ZCN632OJ.js");
6881
7127
  const latest = this.requireThread(thread.id);
6882
7128
  if (!shouldAutoArchiveOnPrMerge({
6883
7129
  previousPrState: prevState || null,
@@ -7187,7 +7433,7 @@ var Orchestrator = class {
7187
7433
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7188
7434
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7189
7435
  try {
7190
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4HYGNH4B.js");
7436
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-4FUQW5LD.js");
7191
7437
  await ensureWorkspace2(thread.repoPath);
7192
7438
  } catch {
7193
7439
  }
@@ -7214,7 +7460,7 @@ var Orchestrator = class {
7214
7460
  await runArchiveScript(thread.repoPath, thread.worktreePath);
7215
7461
  } catch {
7216
7462
  }
7217
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-YQ7YX2CC.js");
7463
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-ZCN632OJ.js");
7218
7464
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
7219
7465
  await removeWorktree(thread.repoPath, thread.worktreePath, {
7220
7466
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -7235,13 +7481,13 @@ var Orchestrator = class {
7235
7481
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
7236
7482
  return restored2;
7237
7483
  }
7238
- if (!existsSync15(thread.worktreePath)) {
7484
+ if (!existsSync16(thread.worktreePath)) {
7239
7485
  if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
7240
7486
  throw new Error(
7241
7487
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
7242
7488
  );
7243
7489
  }
7244
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-NSNZODAM.js");
7490
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-Z22HTSCU.js");
7245
7491
  const slug = thread.worktreePath.split("/").pop();
7246
7492
  const dest = thread.worktreePath;
7247
7493
  await withRepoGitLock(thread.repoPath, async () => {
@@ -7349,7 +7595,7 @@ async function startOrchestration(opts) {
7349
7595
  sourceRef: "default",
7350
7596
  ...createOpts
7351
7597
  }).catch(async () => {
7352
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-NSNZODAM.js");
7598
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-Z22HTSCU.js");
7353
7599
  const repo = await resolveRepoRoot2(repoPath);
7354
7600
  const def = await resolveDefaultBranch2(repo);
7355
7601
  return orch.createThread({
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  getGithubGitAuthMode,
6
6
  getGithubPat
7
- } from "./chunk-GJBHOJCZ.js";
7
+ } from "./chunk-I77RYPOH.js";
8
8
  import {
9
9
  listThreads
10
10
  } from "./chunk-TUGKX5BD.js";
@@ -1130,33 +1130,33 @@ function formatGitAuthModeDirective(mode) {
1130
1130
  "- `git` and `gh` already authenticate in this process. Do not look for tokens in the environment, paste credentials into commands, or switch remotes to SSH.",
1131
1131
  "- Do not set GitHub token environment variables, pass `--with-token`, or run `gh auth login` from this turn.",
1132
1132
  "- Push with `git push -u origin HEAD`. Create/update PRs with `gh` as below.",
1133
- "- If git/gh fail with auth errors, tell the user to run `gh auth login` on this Mac (or set a PAT in Account \u2192 GitHub). Do not wait for a Keychain dialog."
1133
+ "- If git/gh fail with auth errors, tell the user to run `gh auth login` on this Mac (or set a PAT in Settings \u2192 Git). Do not wait for a Keychain dialog."
1134
1134
  ];
1135
1135
  switch (mode) {
1136
1136
  case "gh":
1137
1137
  return [
1138
- "Git authentication (Account \u2192 GitHub mode: gh CLI):",
1138
+ "Git authentication (Settings \u2192 Git mode: gh CLI):",
1139
1139
  "- This process rewrites `git@github.com:` and `ssh://git@github.com/` to HTTPS.",
1140
1140
  ...shared
1141
1141
  ].join("\n");
1142
1142
  case "ssh":
1143
1143
  return [
1144
- "Git authentication (Account \u2192 GitHub mode: SSH):",
1144
+ "Git authentication (Settings \u2192 Git mode: SSH):",
1145
1145
  "- Keep SSH remotes (`git@github.com:\u2026`). Do not rewrite them to HTTPS.",
1146
- "- SSH is batch-mode: it will not prompt for a Keychain password. If push fails with `Permission denied (publickey)`, tell the user to unlock ssh-agent or switch Account \u2192 GitHub to Auto / gh CLI \u2014 do not rewrite remotes yourself.",
1146
+ "- SSH is batch-mode: it will not prompt for a Keychain password. If push fails with `Permission denied (publickey)`, tell the user to unlock ssh-agent or switch Settings \u2192 Git to Auto / gh CLI \u2014 do not rewrite remotes yourself.",
1147
1147
  "- `gh` already authenticates for PRs/API (no token in the environment). Do not set GitHub token environment variables or run `gh auth login` from this turn.",
1148
1148
  "- Push with `git push -u origin HEAD`. Create/update PRs with `gh` as below."
1149
1149
  ].join("\n");
1150
1150
  case "token":
1151
1151
  return [
1152
- "Git authentication (Account \u2192 GitHub mode: personal access token):",
1152
+ "Git authentication (Settings \u2192 Git mode: personal access token):",
1153
1153
  "- This process rewrites GitHub SSH remotes to HTTPS.",
1154
1154
  ...shared
1155
1155
  ].join("\n");
1156
1156
  case "auto":
1157
1157
  default:
1158
1158
  return [
1159
- "Git authentication (Account \u2192 GitHub mode: auto):",
1159
+ "Git authentication (Settings \u2192 Git mode: auto):",
1160
1160
  "- This process rewrites GitHub SSH remotes to HTTPS so git/ssh never prompt the macOS Keychain (required for unattended orchestrator turns).",
1161
1161
  ...shared
1162
1162
  ].join("\n");