@sideboard-ai/core 0.1.169 → 0.1.171

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 (35) hide show
  1. package/dist/{abletime-IC2V7CNB.js → abletime-7VW4EGPT.js} +2 -2
  2. package/dist/{abletime-ESVTHTFH.js → abletime-PCLL36HW.js} +2 -2
  3. package/dist/{agents-V7CF33IK.js → agents-7UTRNLXQ.js} +4 -4
  4. package/dist/{agents-LNFEVU2F.js → agents-CTWLBPCV.js} +4 -4
  5. package/dist/{app-settings-VMOTQORT.js → app-settings-663VQWUV.js} +1 -1
  6. package/dist/{app-settings-LF3TS7XS.js → app-settings-74HBAAH7.js} +1 -1
  7. package/dist/{chunk-P2VCMIZU.js → chunk-234EKD2F.js} +2 -2
  8. package/dist/{chunk-DHPZW2EA.js → chunk-2RM6BZQ2.js} +2 -2
  9. package/dist/{chunk-QHCSYWEC.js → chunk-3F7AXVIL.js} +30 -10
  10. package/dist/{chunk-BLSTB2AD.js → chunk-6X6KMUI2.js} +29 -10
  11. package/dist/{chunk-DKLGWRWC.js → chunk-AQPVBVJE.js} +1 -1
  12. package/dist/{chunk-NB7TEKCF.js → chunk-CMJ2FBBV.js} +1 -1
  13. package/dist/{chunk-7IUO67CW.js → chunk-J7LQVLFQ.js} +15 -8
  14. package/dist/{chunk-YMF5WCIA.js → chunk-OKZFTOM2.js} +15 -8
  15. package/dist/{chunk-Y7DJA3UW.js → chunk-PXNOT6NV.js} +1 -1
  16. package/dist/{chunk-ISMGVHP4.js → chunk-QYJS7LYE.js} +181 -207
  17. package/dist/{chunk-3C4B52TS.js → chunk-QYZYDO3F.js} +1 -1
  18. package/dist/{chunk-M3OIQZRS.js → chunk-ZTVBIBLB.js} +177 -138
  19. package/dist/{coordinator-prompt-JTDCEU74.js → coordinator-prompt-7CTI6HRH.js} +3 -3
  20. package/dist/{coordinator-prompt-RRUU2OYO.js → coordinator-prompt-PM6MPYYB.js} +3 -3
  21. package/dist/{global-workspace-ZF6EJAM3.js → global-workspace-CMYSFRCM.js} +3 -3
  22. package/dist/{global-workspace-4SI3CRRT.js → global-workspace-CRJ6ZVIY.js} +3 -3
  23. package/dist/index.cjs +205 -203
  24. package/dist/index.d.cts +49 -21
  25. package/dist/index.d.ts +49 -21
  26. package/dist/index.js +16 -14
  27. package/dist/mcp/run-stdio.cjs +197 -132
  28. package/dist/mcp/run-stdio.js +8 -8
  29. package/dist/{orchestrator-67BPEJZL.js → orchestrator-RW4IVITI.js} +5 -5
  30. package/dist/{orchestrator-FCH2PFPX.js → orchestrator-VPT4SXXD.js} +5 -5
  31. package/dist/{workspaces-NOC2MWQV.js → workspaces-L73MCDFA.js} +3 -3
  32. package/dist/{workspaces-A7C3WA54.js → workspaces-UYSD2STS.js} +3 -3
  33. package/dist/{worktree-GXO2VDQF.js → worktree-SQD3DBYC.js} +2 -2
  34. package/dist/{worktree-PODE23PD.js → worktree-WVEDT3FM.js} +2 -2
  35. package/package.json +1 -1
@@ -3427,7 +3427,7 @@ function normalizeClaude(raw) {
3427
3427
  const out = {};
3428
3428
  if (typeof source.executablePath === "string") {
3429
3429
  const path = source.executablePath.trim();
3430
- if (path) out.executablePath = path;
3430
+ if (path && (0, import_node_path16.isAbsolute)(path)) out.executablePath = path;
3431
3431
  }
3432
3432
  if (typeof source.chromeEnabled === "boolean") {
3433
3433
  out.chromeEnabled = source.chromeEnabled;
@@ -3440,17 +3440,24 @@ function normalizeCliExecutable(raw) {
3440
3440
  const out = {};
3441
3441
  if (typeof source.executablePath === "string") {
3442
3442
  const path = source.executablePath.trim();
3443
- if (path) out.executablePath = path;
3443
+ if (path && (0, import_node_path16.isAbsolute)(path)) out.executablePath = path;
3444
3444
  }
3445
3445
  return out;
3446
3446
  }
3447
+ function absoluteExecutablePath(value) {
3448
+ const path = value.trim();
3449
+ if (!(0, import_node_path16.isAbsolute)(path)) {
3450
+ throw new Error("Executable path must be absolute. Leave it empty to use the system CLI.");
3451
+ }
3452
+ return path;
3453
+ }
3447
3454
  function normalizeBrightsy(raw) {
3448
3455
  if (!raw || typeof raw !== "object") return {};
3449
3456
  const source = raw;
3450
3457
  const out = {};
3451
3458
  if (typeof source.executablePath === "string") {
3452
3459
  const path = source.executablePath.trim();
3453
- if (path) out.executablePath = path;
3460
+ if (path && (0, import_node_path16.isAbsolute)(path)) out.executablePath = path;
3454
3461
  }
3455
3462
  if (typeof source.cloudConnectEnabled === "boolean") {
3456
3463
  out.cloudConnectEnabled = source.cloudConnectEnabled;
@@ -3929,7 +3936,7 @@ function updateClaudeSettings(patch) {
3929
3936
  if (patch.executablePath == null || patch.executablePath.trim() === "") {
3930
3937
  delete claude.executablePath;
3931
3938
  } else {
3932
- claude.executablePath = patch.executablePath.trim();
3939
+ claude.executablePath = absoluteExecutablePath(patch.executablePath);
3933
3940
  }
3934
3941
  }
3935
3942
  if (typeof patch.chromeEnabled === "boolean") {
@@ -3944,7 +3951,7 @@ function updateBrightsySettings(patch) {
3944
3951
  if (patch.executablePath == null || patch.executablePath.trim() === "") {
3945
3952
  delete brightsy.executablePath;
3946
3953
  } else {
3947
- brightsy.executablePath = patch.executablePath.trim();
3954
+ brightsy.executablePath = absoluteExecutablePath(patch.executablePath);
3948
3955
  }
3949
3956
  }
3950
3957
  if (typeof patch.cloudConnectEnabled === "boolean") {
@@ -3969,7 +3976,7 @@ function updateCodexSettings(patch) {
3969
3976
  if (patch.executablePath == null || patch.executablePath.trim() === "") {
3970
3977
  delete codex.executablePath;
3971
3978
  } else {
3972
- codex.executablePath = patch.executablePath.trim();
3979
+ codex.executablePath = absoluteExecutablePath(patch.executablePath);
3973
3980
  }
3974
3981
  }
3975
3982
  return saveAppSettings({ ...current, codex });
@@ -3981,7 +3988,7 @@ function updateOpencodeSettings(patch) {
3981
3988
  if (patch.executablePath == null || patch.executablePath.trim() === "") {
3982
3989
  delete opencode.executablePath;
3983
3990
  } else {
3984
- opencode.executablePath = patch.executablePath.trim();
3991
+ opencode.executablePath = absoluteExecutablePath(patch.executablePath);
3985
3992
  }
3986
3993
  }
3987
3994
  return saveAppSettings({ ...current, opencode });
@@ -8445,22 +8452,38 @@ var init_brightsy_targets = __esm({
8445
8452
  // src/agents/turn-input.ts
8446
8453
  function normalizeTurnInput(input) {
8447
8454
  if (typeof input === "string") return { prompt: input };
8448
- return { prompt: input.prompt, cachedPrefix: input.cachedPrefix?.trim() || void 0 };
8455
+ return {
8456
+ prompt: input.prompt,
8457
+ cachedPrefix: input.cachedPrefix?.trim() || void 0,
8458
+ systemPrompt: input.systemPrompt?.trim() || void 0
8459
+ };
8449
8460
  }
8450
8461
  function dropCachedPrefixOnResume(input, sessionId) {
8451
8462
  const turn = normalizeTurnInput(input);
8452
- if (sessionId) return { prompt: turn.prompt };
8463
+ if (sessionId) {
8464
+ return turn.systemPrompt ? { prompt: turn.prompt, systemPrompt: turn.systemPrompt } : { prompt: turn.prompt };
8465
+ }
8453
8466
  return turn;
8454
8467
  }
8455
8468
  function flattenTurnInput(input) {
8456
- const { cachedPrefix, prompt } = normalizeTurnInput(input);
8457
- if (!cachedPrefix) return prompt;
8458
- return `${cachedPrefix}
8469
+ const { cachedPrefix, prompt, systemPrompt } = normalizeTurnInput(input);
8470
+ if (cachedPrefix) return `${cachedPrefix}
8459
8471
 
8460
8472
  ---
8461
8473
 
8462
8474
  Current request:
8463
8475
  ${prompt}`;
8476
+ if (systemPrompt) return `${systemPrompt}
8477
+
8478
+ ${prompt}`;
8479
+ return prompt;
8480
+ }
8481
+ function splitTurnInputForSystemPrompt(input) {
8482
+ const turn = normalizeTurnInput(input);
8483
+ return {
8484
+ promptText: flattenTurnInput({ cachedPrefix: turn.cachedPrefix, prompt: turn.prompt }),
8485
+ systemPrompt: turn.systemPrompt
8486
+ };
8464
8487
  }
8465
8488
  var init_turn_input = __esm({
8466
8489
  "src/agents/turn-input.ts"() {
@@ -10294,7 +10317,7 @@ var init_claude = __esm({
10294
10317
  const claude = resolveClaudeExecutable();
10295
10318
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
10296
10319
  const effective = dropCachedPrefixOnResume(input, sessionId);
10297
- const promptText = flattenTurnInput(effective);
10320
+ const { promptText, systemPrompt } = splitTurnInputForSystemPrompt(effective);
10298
10321
  const useStdin = promptText.length > CLAUDE_PROMPT_ARG_MAX;
10299
10322
  if (process.env.SIDEBOARD_DEBUG_CLAUDE_TURN === "1") {
10300
10323
  console.error(
@@ -10346,6 +10369,9 @@ var init_claude = __esm({
10346
10369
  "--permission-mode",
10347
10370
  mode.claude
10348
10371
  ];
10372
+ if (systemPrompt) {
10373
+ args.push("--append-system-prompt", systemPrompt);
10374
+ }
10349
10375
  const mcpConfigPath = writeMcpServersConfig(injectedServers);
10350
10376
  if (mcpConfigPath) {
10351
10377
  args.push("--mcp-config", mcpConfigPath);
@@ -12222,7 +12248,50 @@ function agentGitPrompt(action, opts) {
12222
12248
  return "Merge PR.";
12223
12249
  }
12224
12250
  }
12225
- var AGENT_GIT_ACTIONS;
12251
+ function gitActionExpansion(action, base) {
12252
+ switch (action) {
12253
+ case "commit-push":
12254
+ return "Commit any uncommitted work with a purpose-stating message, then push to origin (`git push -u origin HEAD`; this updates an existing PR when one is linked). Do not start a checks loop unless a goal was given.";
12255
+ case "create-draft":
12256
+ return "Commit, push, then create a draft PR with `gh pr create --draft -R <origin-owner/name>` (title/body from the change purpose). Update the existing PR instead if one is linked.";
12257
+ case "create-web":
12258
+ return "Commit, push, then `gh pr create --web -R <origin-owner/name>`.";
12259
+ case "resolve-conflicts":
12260
+ return `Fetch the PR base${base ? ` (\`${base}\`)` : ""}, merge it into this branch, resolve conflicts carefully, then commit and push until the PR is mergeable.`;
12261
+ case "merge":
12262
+ return "This phrase is the explicit ask to merge this thread's open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.";
12263
+ }
12264
+ }
12265
+ function expandCanonicalGitRequest(prompt) {
12266
+ const trimmed = prompt.trim();
12267
+ let action = null;
12268
+ let base = null;
12269
+ for (const candidate of AGENT_GIT_ACTIONS) {
12270
+ if (candidate === "resolve-conflicts") continue;
12271
+ if (agentGitPrompt(candidate) === trimmed) {
12272
+ action = candidate;
12273
+ break;
12274
+ }
12275
+ }
12276
+ if (!action) {
12277
+ const m = RESOLVE_CONFLICTS_RE.exec(trimmed);
12278
+ if (m) {
12279
+ action = "resolve-conflicts";
12280
+ base = m[1] ?? null;
12281
+ }
12282
+ }
12283
+ let expansion = action ? gitActionExpansion(action, base) : null;
12284
+ if (!expansion && trimmed === "Address review comments.") {
12285
+ expansion = "Read the PR review feedback (`gh pr view --comments`, `gh api` review comments), make the requested changes, commit, and push.";
12286
+ } else if (!expansion && FIX_CI_RE.test(trimmed)) {
12287
+ expansion = "Investigate that failing check (`gh pr checks`, `gh run view --log-failed`), fix it, commit, and push. Loop only if a goal to keep going until it passes was given.";
12288
+ }
12289
+ if (!expansion) return prompt;
12290
+ return `${trimmed}
12291
+
12292
+ (Sideboard git request \u2014 do not ask for clarification: ${expansion})`;
12293
+ }
12294
+ var AGENT_GIT_ACTIONS, RESOLVE_CONFLICTS_RE, FIX_CI_RE;
12226
12295
  var init_agent_git_actions = __esm({
12227
12296
  "src/git/agent-git-actions.ts"() {
12228
12297
  "use strict";
@@ -12233,6 +12302,8 @@ var init_agent_git_actions = __esm({
12233
12302
  "resolve-conflicts",
12234
12303
  "merge"
12235
12304
  ];
12305
+ RESOLVE_CONFLICTS_RE = /^Merge the remote branch(?: \(([^)]+)\))? into your branch and resolve conflicts\. Then, commit and push your changes\.$/;
12306
+ FIX_CI_RE = /^Fix CI: .+\.$/;
12236
12307
  }
12237
12308
  });
12238
12309
 
@@ -12470,47 +12541,82 @@ function formatToolPart(t, detail) {
12470
12541
  }
12471
12542
  function formatMessagesAsTranscript(messages, opts) {
12472
12543
  const tools = opts?.tools ?? "full";
12473
- const blocks = [];
12474
- for (const m of messages) {
12475
- if (m.role === "summary") {
12476
- blocks.push(`## Prior summary
12477
- ${m.text}`);
12478
- continue;
12479
- }
12480
- if (m.role === "user") {
12481
- blocks.push(`### User
12482
- ${m.text}`);
12483
- continue;
12484
- }
12485
- const bits = [];
12486
- if (m.text?.trim()) {
12487
- bits.push(`### Agent
12544
+ const thinking = opts?.thinking ?? true;
12545
+ return messages.map((m) => formatMessageBlock(m, { tools, thinking })).join("\n\n");
12546
+ }
12547
+ function formatMessageBlock(m, opts) {
12548
+ if (m.role === "summary") return `## Prior summary
12549
+ ${m.text}`;
12550
+ if (m.role === "user") return `### User
12551
+ ${m.text}`;
12552
+ const bits = [];
12553
+ if (m.text?.trim()) {
12554
+ bits.push(`### Agent
12488
12555
  ${m.text}`);
12489
- } else {
12490
- bits.push("### Agent");
12491
- }
12492
- const thinking = (m.parts ?? []).filter((p) => p.type === "thinking");
12493
- for (const th of thinking) {
12556
+ } else {
12557
+ bits.push("### Agent");
12558
+ }
12559
+ if (opts.thinking) {
12560
+ for (const th of m.parts ?? []) {
12494
12561
  if (th.type !== "thinking" || !th.text.trim()) continue;
12495
12562
  bits.push("Thinking:");
12496
12563
  bits.push(th.text);
12497
12564
  }
12498
- const toolParts = (m.parts ?? []).filter(
12499
- (p) => p.type === "tool"
12500
- );
12501
- if (tools !== "none" && toolParts.length > 0) {
12502
- if (tools === "summary") bits.push("Tools:");
12503
- for (const t of toolParts) {
12504
- bits.push(formatToolPart(t, tools));
12505
- }
12565
+ }
12566
+ const toolParts = (m.parts ?? []).filter(
12567
+ (p) => p.type === "tool"
12568
+ );
12569
+ if (opts.tools !== "none" && toolParts.length > 0) {
12570
+ if (opts.tools === "summary") bits.push("Tools:");
12571
+ for (const t of toolParts) {
12572
+ bits.push(formatToolPart(t, opts.tools));
12506
12573
  }
12507
- blocks.push(bits.join("\n"));
12508
12574
  }
12509
- return blocks.join("\n\n");
12575
+ return bits.join("\n");
12510
12576
  }
12511
12577
  function buildSessionSeed(messages, opts) {
12512
12578
  if (messages.length === 0) return null;
12513
- const body = formatMessagesAsTranscript(messages, { tools: opts?.tools ?? "full" });
12579
+ const tools = opts?.tools ?? "full";
12580
+ const fullCount = opts?.fullToolMessages ?? SEED_FULL_TOOL_MESSAGES;
12581
+ const maxChars = opts?.maxChars ?? SEED_MAX_CHARS;
12582
+ const olderTools = tools === "none" ? "none" : "summary";
12583
+ const fullFrom = Math.max(0, messages.length - fullCount);
12584
+ const blocks = messages.map(
12585
+ (m, i) => formatMessageBlock(m, {
12586
+ tools: i >= fullFrom ? tools : olderTools,
12587
+ thinking: false
12588
+ })
12589
+ );
12590
+ const keep = new Array(blocks.length).fill(false);
12591
+ let used = 0;
12592
+ for (let i = 0; i < blocks.length; i++) {
12593
+ if (messages[i].role === "summary") {
12594
+ keep[i] = true;
12595
+ used += blocks[i].length + 2;
12596
+ }
12597
+ }
12598
+ for (let i = blocks.length - 1; i >= 0; i--) {
12599
+ if (keep[i]) continue;
12600
+ const next = used + blocks[i].length + 2;
12601
+ if (next > maxChars && used > 0) break;
12602
+ keep[i] = true;
12603
+ used = next;
12604
+ }
12605
+ const dropped = keep.filter((k) => !k).length;
12606
+ const kept = [];
12607
+ let markerPlaced = false;
12608
+ for (let i = 0; i < blocks.length; i++) {
12609
+ if (!keep[i]) continue;
12610
+ if (dropped > 0 && !markerPlaced && messages[i].role !== "summary") {
12611
+ kept.push(`_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`);
12612
+ markerPlaced = true;
12613
+ }
12614
+ kept.push(blocks[i]);
12615
+ }
12616
+ if (dropped > 0 && !markerPlaced) {
12617
+ kept.push(`_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`);
12618
+ }
12619
+ const body = kept.join("\n\n");
12514
12620
  if (!body.trim()) return null;
12515
12621
  return [
12516
12622
  "Sideboard conversation context (restored after compaction or a new session):",
@@ -12563,7 +12669,7 @@ function findLastBrightsyContextSummary(messages) {
12563
12669
  function buildBrightsySessionSeed(messages) {
12564
12670
  const match = findLastBrightsyContextSummary(messages);
12565
12671
  const tail = match ? messages.slice(match.index + 1) : messages;
12566
- const body = formatMessagesAsTranscript(tail, { tools: "none" });
12672
+ const body = formatMessagesAsTranscript(tail, { tools: "none", thinking: false });
12567
12673
  if (!match && !body.trim()) return null;
12568
12674
  const blocks = [
12569
12675
  "Sideboard conversation context (restored after compaction or a new session):",
@@ -12630,7 +12736,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
12630
12736
  olderCount: older.length
12631
12737
  };
12632
12738
  }
12633
- var CONTEXT_COMPACT_CHARS, CONTEXT_KEEP_RECENT_CHARS, CONTEXT_KEEP_RECENT_MESSAGES, CONTEXT_MIN_MESSAGES, SESSION_RESET_OCCUPANCY_TOKENS, BRIGHTSY_SUMMARIZE_CONTEXT_TOOL;
12739
+ var CONTEXT_COMPACT_CHARS, CONTEXT_KEEP_RECENT_CHARS, CONTEXT_KEEP_RECENT_MESSAGES, CONTEXT_MIN_MESSAGES, SESSION_RESET_OCCUPANCY_TOKENS, SEED_MAX_CHARS, SEED_FULL_TOOL_MESSAGES, BRIGHTSY_SUMMARIZE_CONTEXT_TOOL;
12634
12740
  var init_context_compact = __esm({
12635
12741
  "src/composer/context-compact.ts"() {
12636
12742
  "use strict";
@@ -12643,6 +12749,8 @@ var init_context_compact = __esm({
12643
12749
  CONTEXT_KEEP_RECENT_MESSAGES = 12;
12644
12750
  CONTEXT_MIN_MESSAGES = 10;
12645
12751
  SESSION_RESET_OCCUPANCY_TOKENS = 75e4;
12752
+ SEED_MAX_CHARS = 6e4;
12753
+ SEED_FULL_TOOL_MESSAGES = 6;
12646
12754
  BRIGHTSY_SUMMARIZE_CONTEXT_TOOL = "summarize_context";
12647
12755
  }
12648
12756
  });
@@ -17761,67 +17869,37 @@ function formatWorktreeDirective(thread, opts) {
17761
17869
  lines.push(
17762
17870
  "- Always use this worktree's `origin` remote (`git remote get-url origin` from this cwd). Never push to or open PRs against `upstream` (template remotes)."
17763
17871
  );
17764
- lines.push(
17765
- "- Push with `git push -u origin HEAD` (or the current branch name). Do not `git push upstream`."
17766
- );
17767
- lines.push("");
17768
17872
  lines.push(formatGitAuthModeDirective(opts?.gitAuthMode ?? "auto"));
17769
17873
  lines.push(
17770
- "- Derive the PR title and body from what the changes actually do and why \u2014 inspect the diff/commits and the user request. Do not use the soccer-team worktree nickname or placeholder branch as the PR title."
17874
+ "- PR title/body and commit messages state what the change does and why (inspect the diff/commits and the user request) \u2014 never the worktree nickname or placeholder branch. Concise imperative title (Conventional Commits when it fits: feat:/fix:/chore:/docs:); body = intent, key changes, test notes."
17771
17875
  );
17772
17876
  lines.push(
17773
- "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
17774
- );
17775
- lines.push(
17776
- "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. Do not merge the PR unless this turn is a Merge PR request or the user explicitly asked. When merging, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
17877
+ "- Stay on this thread branch. Never push to main/master or merge locally into the main checkout. Merge the PR only when this turn is a Merge PR request or the user explicitly asked \u2014 via GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
17777
17878
  );
17778
17879
  if (thread.prUrl) {
17779
17880
  lines.push(
17780
17881
  `- A PR already exists (${thread.prUrl}). Update it (push + edit title/body if the purpose drifted) instead of opening a duplicate.`
17781
17882
  );
17782
- } else if (opts?.githubSlug) {
17783
- lines.push(
17784
- `- Prefer a draft PR first: \`gh pr create --draft -R ${opts.githubSlug}\` (or update via \`gh pr edit -R ${opts.githubSlug}\`) once the change set is coherent. Always pass \`-R ${opts.githubSlug}\` (this worktree's origin). Bare \`gh pr create\` may target upstream instead of origin on dual-remote checkouts. Mark ready for review only when asked. Title/body must reflect the change purpose, not the worktree name. If GitHub rejects the GraphQL body as too long, retry with a short \`--body-file\` (limit 65,536 characters) \u2014 do not paste a changelog or diff into \`--body\`.`
17785
- );
17786
17883
  } else {
17884
+ const slug = opts?.githubSlug ?? "<origin-owner/name>";
17885
+ const resolve = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
17787
17886
  lines.push(
17788
- "- Prefer a draft PR first: `gh pr create --draft -R <origin-owner/name>` (or update via `gh pr edit -R \u2026`) once the change set is coherent. Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree \u2014 never from `upstream`. Mark ready for review only when asked. Title/body must reflect the change purpose, not the worktree name. If GitHub rejects the GraphQL body as too long, retry with a short `--body-file` (limit 65,536 characters) \u2014 do not paste a changelog or diff into `--body`."
17887
+ `- Prefer a draft PR first: \`gh pr create --draft -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve} Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
17789
17888
  );
17790
17889
  }
17791
- lines.push("");
17792
- lines.push(
17793
- "Short git requests from the Sideboard UI are complete instructions \u2014 expand them using the rules above without asking for clarification:"
17794
- );
17795
- lines.push(
17796
- '- "Commit and push." \u2192 commit any uncommitted work with a purpose-stating message, then push to origin (updates an existing PR if one is linked). Do not start a checks loop unless they gave a goal.'
17797
- );
17798
- lines.push(
17799
- '- "Commit, push, and open a draft PR." \u2192 commit, push, then create a draft PR with `gh pr create --draft -R \u2026` (title/body from the change purpose).'
17800
- );
17801
- lines.push(
17802
- '- "Commit, push, and open a PR in the browser." \u2192 commit, push, then `gh pr create --web -R \u2026`.'
17803
- );
17804
- lines.push(
17805
- '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push. Loop only if they gave a goal to keep going until it passes.'
17806
- );
17807
- lines.push(
17808
- '- "Merge the remote branch (<base>) into your branch and resolve conflicts. Then, commit and push your changes." \u2192 fetch the PR base, merge it into this branch, resolve conflicts carefully, commit, and push until the PR is mergeable.'
17809
- );
17810
- lines.push(
17811
- '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
17812
- );
17813
17890
  lines.push(
17814
- '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub (this phrase is the explicit ask). If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
17891
+ '- Sideboard git buttons send short phrases ("Commit and push.", "Merge PR.", \u2026) with their meaning attached \u2014 act on them without asking for clarification. If the user gives a goal (Greptile 5/5, CI green, until checks pass), you get the watch-fix-push playbook with that request.'
17815
17892
  );
17816
17893
  lines.push("");
17817
- lines.push(formatPrGateDirective());
17818
- lines.push("");
17819
- lines.push(formatProcessGuideDirective());
17894
+ lines.push(formatProcessGuideDirective({ worktreePath: thread.worktreePath }));
17820
17895
  return lines.join("\n");
17821
17896
  }
17822
17897
  function formatWorktreeReminder() {
17823
17898
  return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push.";
17824
17899
  }
17900
+ function mentionsPrGoal(prompt) {
17901
+ return PR_GOAL_RE.test(prompt);
17902
+ }
17825
17903
  function issueTicketFromThread(thread, preferredSource = "github") {
17826
17904
  if (thread?.sourceType !== "ticket") return null;
17827
17905
  const ref = thread.sourceRef?.trim() ?? "";
@@ -17888,29 +17966,27 @@ function formatPrGateDirective() {
17888
17966
  "- Greptile 5/5: wait for the greptile check; read the latest `N/5` from the PR body or Greptile\u2019s most recently *updated* issue comment; also fetch unresolved review comments / the \u201CPrompt to fix all with AI\u201D block. Done = **5/5 and zero unresolved Greptile comments**. If Greptile is idle, comment `@greptile review`. Cap about 5 iterations, then report leftovers. Do not merge unless asked."
17889
17967
  ].join("\n");
17890
17968
  }
17891
- function formatProcessGuideDirective() {
17969
+ function formatProcessGuideDirective(opts) {
17970
+ const worktree = opts?.worktreePath?.trim();
17971
+ const hasGraphSkill = !!worktree && (0, import_node_fs46.existsSync)((0, import_node_path43.join)(worktree, ".claude/skills/graph-engineering/SKILL.md"));
17972
+ const hasReviewSkill = !!worktree && (0, import_node_fs46.existsSync)((0, import_node_path43.join)(worktree, ".claude/skills/review/SKILL.md"));
17892
17973
  return [
17893
17974
  "Process guides (recurring work only):",
17894
- "- Long jobs (pack, test, deploy, anything that may run more than ~30s): Sideboard always provides `/long-running`. Detach and wait; `stop_job` if hanging or wrong. Do not ask the human to poll.",
17895
- "- 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.",
17896
- "- 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.",
17897
- "- Merge-readiness notes: if `.claude/skills/review/SKILL.md` already exists, edit it. Otherwise write them to `.context/review.md` (copied from `.sideboard/review.md` when that file exists). Do not create a review skill.",
17898
- "- Do not write new skills under `.sideboard/skills/` (that folder only \u2014 other `.sideboard/` files such as settings.toml are fine). Point Codex/OpenCode at the Claude skill from `AGENTS.md`. Optional: symlink `.cursor/skills/<name>` to the Claude skill.",
17975
+ hasGraphSkill ? "- Migrations, ports, batch fixes, and other fan-out: follow `.claude/skills/graph-engineering/SKILL.md` (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch around it." : null,
17976
+ "- 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 it). Not under `.sideboard/skills/`. Point Codex/OpenCode at it from `AGENTS.md`.",
17977
+ hasReviewSkill ? "- Merge-readiness notes go in `.claude/skills/review/SKILL.md` (edit it; do not create another review skill)." : "- Merge-readiness notes go in `.context/review.md` (copied from `.sideboard/review.md` when that file exists). Do not create a review skill.",
17899
17978
  "- 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."
17900
- ].join("\n");
17979
+ ].filter(Boolean).join("\n");
17901
17980
  }
17902
17981
  function formatLongRunningDirective(opts) {
17903
17982
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
17904
17983
  return [
17905
- "Long-running jobs (mandatory when a command may run more than ~30s):",
17906
- "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.",
17907
- `Helper (same tool as \`scripts/detached-job.cjs\` when that file exists in the worktree): \`${invoke}\``,
17908
- `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
17909
- "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
17910
- `- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
17911
- `- If it is hanging, producing no useful output, or doing the wrong thing: MCP \`stop_job\` (same id, optional reason) or \`${invoke} stop <id>\`. Do not loop forever. Do not stop a pack/test/deploy that is clearly making progress.`,
17912
- "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false (or you stopped it).",
17913
- "- ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
17984
+ "Long-running jobs (mandatory when a command may run more than ~30s \u2014 pack, test, deploy, `gh pr checks --watch`):",
17985
+ "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. Detach instead, and never ask the human to poll.",
17986
+ `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree; same tool as \`scripts/detached-job.cjs\` when the worktree has it). If JSON says already-running, do not start again.`,
17987
+ "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running`, then loop Sideboard MCP `wait_for_job` (returns in ~45s). stillRunning \u2192 present the same id with `content=delta` only \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
17988
+ "- 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.",
17989
+ "- 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.",
17914
17990
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
17915
17991
  ].join("\n");
17916
17992
  }
@@ -17920,33 +17996,17 @@ function formatLongRunningReminder(opts) {
17920
17996
  }
17921
17997
  function formatArtifactDirective() {
17922
17998
  return [
17923
- "Sideboard side column (desktop UI):",
17924
- "claude.ai\u2019s \u201CArtifact\u201D tool does NOT exist in Claude Code. That is expected.",
17925
- "Do not unprompted-duplicate a payload. Chat markdown (including tables) already renders in the transcript \u2014 do not also call present_schema with those same rows just to display them. If the user asks for an editable / interactive table, call present_schema even if markdown already showed the data. Do not also call present_artifact for a document you already fenced in chat.",
17926
- "Documents (HTML/SVG/markdown):",
17927
- "1) Emit a fenced code block tagged `html` (preferred), `svg`, or `markdown` with the FULL document \u2014 Sideboard opens a side column. Example:",
17928
- "```html",
17929
- "<!DOCTYPE html><html><head><title>Demo</title></head><body><h1>Hi</h1></body></html>",
17930
- "```",
17931
- "2) Or call Sideboard MCP `present_artifact` with title, type (html|svg|markdown|react|log), and content \u2014 not both a fence and this tool for the same body.",
17932
- " type=log is append-only: same `artifact_id`, `content` = new lines only (plus optional status/phase). Do not resend the full log or wrap it in HTML.",
17933
- "CMS / JSON Schema forms & tables (Brightsy or any schema+schemaUi source):",
17934
- "3) Call Sideboard MCP `present_schema` when the user needs to filter, edit, publish, or persist rows \u2014 including after you already showed a markdown table, if they then ask for an editable table. If they only need to read the data, a markdown table is enough. When you do call it, pass title, mode (table|form), and either:",
17935
- " - datasource=brightsy + resource_id (record type UUID) after fetching types via Brightsy MCP, or",
17936
- " - datasource=inline + resource: { id, title, schema, schemaUi } and optional records/record.",
17937
- "Files / media browser (CMS file manager column):",
17938
- "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
17939
- " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
17940
- "Multiple-choice questions:",
17941
- "5) Call Sideboard MCP `ask_user` only when work is blocked on choosing among a few concrete options (approach forks, which API, auth vs cookies). First write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it). Include a description on every option. After calling, stop and wait for their next message with answers. If you are asking a real multiple-choice, use ask_user rather than chat bullets so Sideboard shows the composer picker.",
17942
- "Do not call ask_user for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat. If one option is the obvious default, proceed without asking.",
17943
- "Never say artifacts, CMS UI, or the Files column are unavailable. present_schema is for interactive list/edit/publish (use it when they ask to edit, even if chat already had a markdown table); present_files for storage UI; html fences for standalone pages; ask_user only for those blocked predefined-option questions."
17999
+ "Sideboard side column (desktop UI) \u2014 Sideboard MCP tools present_artifact, present_schema, present_files, ask_user (see their descriptions). claude.ai\u2019s \u201CArtifact\u201D tool does not exist here; never say artifacts, the CMS UI, or the Files column are unavailable.",
18000
+ "- Standalone documents: a fenced block tagged `html` (preferred), `svg`, or `markdown` containing the FULL document opens the side column by itself. Use that or present_artifact \u2014 never both for the same body.",
18001
+ "- present_artifact type=log appends: same artifact_id, content = new lines only (plus status/phase). Do not resend the full log or wrap it in HTML.",
18002
+ "- Data: a markdown table is enough to read. Call present_schema only when the user needs to filter/edit/publish/persist rows \u2014 including when they ask for an editable table after you already showed markdown. Never re-present rows you already wrote just to display them.",
18003
+ "- ask_user only when work is blocked on a few concrete options (approach fork, which API, auth vs cookies): first a short chat message explaining the decision and each option, then the call (description on every option), then stop and wait. Not for greetings, check-ins, or an invented menu of next tasks \u2014 reply in chat. If one option is the obvious default, proceed."
17944
18004
  ].join("\n");
17945
18005
  }
17946
18006
  function formatUiReminder() {
17947
18007
  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.";
17948
18008
  }
17949
- var import_node_fs46, import_node_path43, GITHUB_TICKET_REF, KEYED_TICKET_REF;
18009
+ var import_node_fs46, import_node_path43, PR_GOAL_RE, GITHUB_TICKET_REF, KEYED_TICKET_REF;
17950
18010
  var init_instructions = __esm({
17951
18011
  "src/agents/instructions.ts"() {
17952
18012
  "use strict";
@@ -17955,6 +18015,7 @@ var init_instructions = __esm({
17955
18015
  init_git_auth_mode();
17956
18016
  init_worktree_labels();
17957
18017
  init_detached_job_path();
18018
+ PR_GOAL_RE = /greptile|\bci\b.*\b(green|pass)|checks?\s+(pass|green)|until\s+.*\b(pass|green|merge|clean)|\bfix ci\b|watch[- ]fix[- ]push/i;
17958
18019
  GITHUB_TICKET_REF = /^(?:#?\d+|gh-\d+)$/i;
17959
18020
  KEYED_TICKET_REF = /^(?:[A-Z][A-Z0-9]{0,9}-\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
17960
18021
  }
@@ -19723,17 +19784,21 @@ var init_orchestrator = __esm({
19723
19784
  const slackReplyContext = formatSlackRepliesForTurn(
19724
19785
  pendingSlackExternalReplies(thread.messages)
19725
19786
  );
19726
- const agentPrompt = [
19727
- thread.planMode ? PLAN_MODE_INSTRUCTION : null,
19728
- orchestrationReminder,
19787
+ const turnReminders = [
19729
19788
  worktreeReminder,
19730
19789
  optionalServicesReminder,
19731
19790
  issueToolsReminder,
19732
19791
  viewerContextReminder,
19733
19792
  artifactReminder,
19734
- longRunningReminder,
19793
+ longRunningReminder
19794
+ ].filter(Boolean).join("\n\n") || void 0;
19795
+ const prGateDirective = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && mentionsPrGoal(expandedPrompt) ? formatPrGateDirective() : null;
19796
+ const agentPrompt = [
19797
+ thread.planMode ? PLAN_MODE_INSTRUCTION : null,
19798
+ orchestrationReminder,
19799
+ prGateDirective,
19735
19800
  slackReplyContext,
19736
- expandedPrompt
19801
+ expandCanonicalGitRequest(expandedPrompt)
19737
19802
  ].filter(Boolean).join("\n\n");
19738
19803
  if (thread.attachments.length > 0) {
19739
19804
  updateThread(threadId, { attachments: [] });
@@ -19823,7 +19888,7 @@ var init_orchestrator = __esm({
19823
19888
  const stderrTail = [];
19824
19889
  const handle = await spawnAgentTurn(
19825
19890
  fresh,
19826
- { cachedPrefix, prompt: agentPrompt },
19891
+ { cachedPrefix, prompt: agentPrompt, systemPrompt: turnReminders },
19827
19892
  (event) => {
19828
19893
  this.emit({ type: "turn_output", threadId, event });
19829
19894
  noteTurnLiveEvent(threadId, event);
@@ -19932,7 +19997,7 @@ var init_orchestrator = __esm({
19932
19997
  ].filter(Boolean).join("\n\n---\n\n");
19933
19998
  const retryHandle = await spawnAgentTurn(
19934
19999
  retryThread,
19935
- { cachedPrefix: retryPrefix, prompt: agentPrompt },
20000
+ { cachedPrefix: retryPrefix, prompt: agentPrompt, systemPrompt: turnReminders },
19936
20001
  (event) => {
19937
20002
  this.emit({ type: "turn_output", threadId, event });
19938
20003
  if (event.type === "session_id") {
@@ -37,12 +37,12 @@ import {
37
37
  syncBoardPins,
38
38
  updateSchedule,
39
39
  waitForDetachedJob
40
- } from "../chunk-M3OIQZRS.js";
40
+ } from "../chunk-ZTVBIBLB.js";
41
41
  import {
42
42
  isInternalAgentStatusText,
43
43
  listModelsForAgent,
44
44
  sideboardMcpProfile
45
- } from "../chunk-BLSTB2AD.js";
45
+ } from "../chunk-6X6KMUI2.js";
46
46
  import "../chunk-LZ6QMEQC.js";
47
47
  import "../chunk-S42XV45P.js";
48
48
  import {
@@ -57,7 +57,7 @@ import {
57
57
  searchAbleTimeTasks,
58
58
  toAbleTimeIssueInfo,
59
59
  updateAbleTimeTask
60
- } from "../chunk-DKLGWRWC.js";
60
+ } from "../chunk-AQPVBVJE.js";
61
61
  import {
62
62
  httpFetch
63
63
  } from "../chunk-6GN5WPYZ.js";
@@ -67,7 +67,7 @@ import {
67
67
  GLOBAL_WORKSPACE_ID,
68
68
  isCloudCoordinatorThread,
69
69
  listWorkspaces
70
- } from "../chunk-P2VCMIZU.js";
70
+ } from "../chunk-234EKD2F.js";
71
71
  import {
72
72
  canonicalizeRepoPath,
73
73
  formatGhLandError,
@@ -78,7 +78,7 @@ import {
78
78
  resolveListPrsOptions,
79
79
  resolveRepoRoot,
80
80
  warmGithubAgentAuth
81
- } from "../chunk-Y7DJA3UW.js";
81
+ } from "../chunk-PXNOT6NV.js";
82
82
  import "../chunk-D6JWYJ4P.js";
83
83
  import "../chunk-LZQULFIT.js";
84
84
  import "../chunk-SYFVUF2C.js";
@@ -98,7 +98,7 @@ import {
98
98
  resolveViewerProfileForRepo,
99
99
  saveLinearOAuth,
100
100
  writeViewerContext
101
- } from "../chunk-YMF5WCIA.js";
101
+ } from "../chunk-OKZFTOM2.js";
102
102
  import "../chunk-NSTQ6QKD.js";
103
103
  import "../chunk-KGZBYWZ3.js";
104
104
  import {
@@ -2981,7 +2981,7 @@ async function startMcpServer() {
2981
2981
  );
2982
2982
  }
2983
2983
  try {
2984
- const { maxConcurrentAgents } = await import("../app-settings-LF3TS7XS.js");
2984
+ const { maxConcurrentAgents } = await import("../app-settings-74HBAAH7.js");
2985
2985
  orch.setMaxConcurrent(maxConcurrentAgents());
2986
2986
  } catch {
2987
2987
  }
@@ -3290,7 +3290,7 @@ async function startMcpServer() {
3290
3290
  registerConnectedIssueVendorTools(server);
3291
3291
  registerScheduleTools(server);
3292
3292
  const { getCaffeinateHold, setCaffeinateHold } = await import("../caffeinate-hold-DBKMRZZO.js");
3293
- const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-LF3TS7XS.js");
3293
+ const { resolveNewThreadOptions, resolveThreadDefaults } = await import("../app-settings-74HBAAH7.js");
3294
3294
  const accountDefaults = resolveThreadDefaults();
3295
3295
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
3296
3296
  server.tool(