@sideboard-ai/core 0.1.73 → 0.1.74

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 (26) hide show
  1. package/dist/agents/cursor-runner.cjs +1 -1
  2. package/dist/agents/cursor-runner.js +1 -1
  3. package/dist/{agents-77GE7VRW.js → agents-2S2JVLZ6.js} +3 -3
  4. package/dist/{agents-GUFUYXKP.js → agents-H7M5CQY2.js} +4 -4
  5. package/dist/{chunk-POW7JCB5.js → chunk-2NKSHIRI.js} +1 -1
  6. package/dist/{chunk-WSFZPOPH.js → chunk-5KJMNNCB.js} +17 -11
  7. package/dist/{chunk-NF6Y4GTE.js → chunk-77W62MTX.js} +1 -1
  8. package/dist/{chunk-CYKPUTXN.js → chunk-DG3S2UXP.js} +1 -1
  9. package/dist/{chunk-CXT2PLO7.js → chunk-GJ2LZQJI.js} +1 -1
  10. package/dist/{chunk-V4ACEJX2.js → chunk-HCWAIEBU.js} +11 -10
  11. package/dist/{chunk-AFW3M6LU.js → chunk-KBKPVKYZ.js} +17 -11
  12. package/dist/{chunk-3WJAUKIL.js → chunk-SEOICVGB.js} +1 -1
  13. package/dist/{chunk-6RFPKZNC.js → chunk-XA2FQJTN.js} +11 -10
  14. package/dist/{coordinator-prompt-4U2QNHGT.js → coordinator-prompt-3XXSG7M2.js} +1 -1
  15. package/dist/{coordinator-prompt-ZHBDHMZB.js → coordinator-prompt-RTUCCPCI.js} +1 -1
  16. package/dist/{global-workspace-S3B6ESZS.js → global-workspace-CSIS62Z4.js} +2 -2
  17. package/dist/{global-workspace-VF56FTPY.js → global-workspace-PJU6HISJ.js} +2 -2
  18. package/dist/index.cjs +155 -32
  19. package/dist/index.d.cts +34 -3
  20. package/dist/index.d.ts +34 -3
  21. package/dist/index.js +134 -23
  22. package/dist/mcp/run-stdio.cjs +141 -32
  23. package/dist/mcp/run-stdio.js +124 -22
  24. package/dist/{workspaces-ZJ45O4CD.js → workspaces-L4TXMUNM.js} +3 -3
  25. package/dist/{workspaces-R66324S7.js → workspaces-W72KZL4B.js} +3 -3
  26. package/package.json +1 -1
@@ -4312,7 +4312,7 @@ function coordinatorGreenfieldPlaybook(reposDir) {
4312
4312
  "- Examples:",
4313
4313
  ` - Clone: \`git clone <url> ${reposDir}/<name>\``,
4314
4314
  ` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
4315
- "- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 send_to_thread (`gh pr create --draft -R <origin-owner/name>`).",
4315
+ "- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 ask_git create-draft (or send_to_thread `gh pr create --draft -R <origin-owner/name>`).",
4316
4316
  "- Always target the child worktree's **origin** (`github:` slug from list_workspaces / `git remote get-url origin` in that worktree). Never open PRs against `upstream`.",
4317
4317
  "- Do coding work in the child worktree thread, not by editing files in this home cwd."
4318
4318
  ].join("\n");
@@ -4335,8 +4335,8 @@ function coordinatorTurnReminder(opts) {
4335
4335
  goal ? `- Goal / title: ${goal}` : null,
4336
4336
  accountDefaultsPlaybookLine(),
4337
4337
  `- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
4338
- "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.",
4339
- "- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 draft PR.",
4338
+ "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Land with ask_git (commit-push / create-draft / merge) on the child, then wait_for_turn \u2014 never git/gh from this cwd.",
4339
+ "- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
4340
4340
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
4341
4341
  "- If they will wait on Slack or leave the Mac, call set_caffeinate enabled=true. When they say they are done / wrapping up / going to sleep, call set_caffeinate enabled=false."
4342
4342
  ].filter(Boolean).join("\n");
@@ -4389,9 +4389,9 @@ function ensureGlobalCoordinatorCwd(opts) {
4389
4389
  orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
4390
4390
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
4391
4391
  "Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
4392
- "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn.",
4393
- "Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 draft PR via worktree agent.",
4394
- "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
4392
+ "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn.",
4393
+ "Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft.",
4394
+ "Always ask worktree agents to commit, push, open draft PRs, and merge (`ask_git` / `send_to_thread`). The worktree agent runs git/gh; never merge from this orchestration cwd."
4395
4395
  ].join("\n");
4396
4396
  try {
4397
4397
  (0, import_node_fs11.writeFileSync)((0, import_node_path12.join)(dir, "CLAUDE.md"), `${body}
@@ -4428,8 +4428,8 @@ function coordinatorSystemPrompt(opts) {
4428
4428
  "When creating threads, pass the correct repoPath for the target workspace.",
4429
4429
  `YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit parentThreadId (Sideboard binds it). Never invent a uuid.`,
4430
4430
  "Omit agent/model on create_thread unless you need to override Account defaults.",
4431
- "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 send_to_thread (ask for `gh pr create --draft -R <origin-owner/name>` using the workspace github slug) \u2192 wait_for_turn. Never target upstream. Never open PRs from the orchestrator.",
4432
- "Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 draft PR via worktree agent.",
4431
+ "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn. Never target upstream. Never git/gh from this orchestration cwd.",
4432
+ "Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
4433
4433
  `Goal: ${opts.goal}`,
4434
4434
  "Registered workspaces:",
4435
4435
  formatWorkspaceInventory(opts.workspaces)
@@ -4472,8 +4472,9 @@ var init_coordinator_prompt = __esm({
4472
4472
  "Inspect / review / PRs:",
4473
4473
  "- get_diff \u2014 compact diff summary",
4474
4474
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
4475
- "- Ask the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (workspace `github:` slug / that worktree's origin \u2014 never upstream). Do not open PRs from the orchestrator yourself.",
4476
- "Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
4475
+ "- ask_git \u2014 tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge (`Merge PR.`). You only queue that prompt \u2014 the worktree agent runs git/gh (including `gh pr merge`). Then wait_for_turn. Prefer this over paraphrasing.",
4476
+ '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
4477
+ "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4477
4478
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
4478
4479
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
4479
4480
  ].join("\n");
@@ -5684,12 +5685,14 @@ function usageFromClaude(usage) {
5684
5685
  if (!usage) return null;
5685
5686
  const inputTokens = Number(usage.input_tokens ?? 0);
5686
5687
  const outputTokens = Number(usage.output_tokens ?? 0);
5687
- if (!inputTokens && !outputTokens) return null;
5688
+ const cacheReadTokens = Number(usage.cache_read_input_tokens ?? 0);
5689
+ const cacheWriteTokens = Number(usage.cache_creation_input_tokens ?? 0);
5690
+ if (!inputTokens && !outputTokens && !cacheReadTokens && !cacheWriteTokens) return null;
5688
5691
  return {
5689
5692
  inputTokens,
5690
5693
  outputTokens,
5691
- cacheReadTokens: usage.cache_read_input_tokens ? Number(usage.cache_read_input_tokens) : void 0,
5692
- cacheWriteTokens: usage.cache_creation_input_tokens ? Number(usage.cache_creation_input_tokens) : void 0
5694
+ cacheReadTokens: cacheReadTokens || void 0,
5695
+ cacheWriteTokens: cacheWriteTokens || void 0
5693
5696
  };
5694
5697
  }
5695
5698
  function claudeResultErrorDetail(obj) {
@@ -5942,8 +5945,12 @@ var init_claude = __esm({
5942
5945
  return { type: "session_id", data: obj.session_id };
5943
5946
  }
5944
5947
  if (obj.type === "assistant" || obj.type === "user") {
5945
- const content = obj.message?.content;
5946
- const events = eventsFromContentBlocks(content);
5948
+ const message = obj.message;
5949
+ const events = eventsFromContentBlocks(message?.content);
5950
+ if (obj.type === "assistant") {
5951
+ const usage = usageFromClaude(message?.usage);
5952
+ if (usage) events.push({ type: "usage", data: usage, scope: "request" });
5953
+ }
5947
5954
  if (events.length === 0) return null;
5948
5955
  return events.length === 1 ? events[0] : events;
5949
5956
  }
@@ -5988,7 +5995,7 @@ var init_claude = __esm({
5988
5995
  if (typeof text2 === "string" && text2) events.push({ type: "stdout", data: text2 });
5989
5996
  }
5990
5997
  const usage = usageFromClaude(obj.usage);
5991
- if (usage) events.push({ type: "usage", data: usage });
5998
+ if (usage) events.push({ type: "usage", data: usage, scope: "turn" });
5992
5999
  if (events.length === 0) return null;
5993
6000
  return events.length === 1 ? events[0] : events;
5994
6001
  }
@@ -6312,7 +6319,7 @@ var init_codex = __esm({
6312
6319
  }
6313
6320
  if (type === "turn.completed" || type === "turn_completed") {
6314
6321
  const usage = usageFromCodex(obj.usage);
6315
- return usage ? { type: "usage", data: usage } : null;
6322
+ return usage ? { type: "usage", data: usage, scope: "turn" } : null;
6316
6323
  }
6317
6324
  if (typeof obj.content === "string" && obj.content.trim()) {
6318
6325
  return { type: "stdout", data: obj.content };
@@ -6511,7 +6518,7 @@ function cursorSdkMessageToEvents(msg) {
6511
6518
  }
6512
6519
  if (msg.type === "usage") {
6513
6520
  const usage = usageFromCursor(msg.usage);
6514
- if (usage) return [{ type: "usage", data: usage }];
6521
+ if (usage) return [{ type: "usage", data: usage, scope: "request" }];
6515
6522
  }
6516
6523
  if (msg.type === "status" && msg.status === "ERROR") {
6517
6524
  const rawMessage = msg.message;
@@ -6933,7 +6940,7 @@ var init_opencode = __esm({
6933
6940
  const usage = usageFromOpencode(
6934
6941
  part?.tokens ?? obj.tokens
6935
6942
  );
6936
- return usage ? { type: "usage", data: usage } : null;
6943
+ return usage ? { type: "usage", data: usage, scope: "request" } : null;
6937
6944
  }
6938
6945
  return null;
6939
6946
  } catch {
@@ -8681,14 +8688,28 @@ function sumOptional(a, b) {
8681
8688
  if (a == null && b == null) return void 0;
8682
8689
  return (a ?? 0) + (b ?? 0);
8683
8690
  }
8691
+ function requestOccupancy(u) {
8692
+ return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
8693
+ }
8684
8694
  function mergeUsage(a, b) {
8685
8695
  return {
8686
8696
  inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
8687
8697
  outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
8688
8698
  cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
8689
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens)
8699
+ cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
8700
+ lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
8690
8701
  };
8691
8702
  }
8703
+ function applyTurnUsage(current, incoming, scope = "request") {
8704
+ if (scope === "turn") {
8705
+ return {
8706
+ ...incoming,
8707
+ lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
8708
+ };
8709
+ }
8710
+ const merged = mergeUsage(current, incoming);
8711
+ return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
8712
+ }
8692
8713
 
8693
8714
  // src/agents/spawn.ts
8694
8715
  async function spawnAgentTurn(thread, input, onEvent) {
@@ -8762,7 +8783,7 @@ async function spawnAgentTurn(thread, input, onEvent) {
8762
8783
  continue;
8763
8784
  }
8764
8785
  if (parsed.type === "usage") {
8765
- usage = mergeUsage(usage, parsed.data);
8786
+ usage = applyTurnUsage(usage, parsed.data, parsed.scope ?? "request");
8766
8787
  onEvent(parsed);
8767
8788
  continue;
8768
8789
  }
@@ -8807,6 +8828,31 @@ init_agents();
8807
8828
  init_worktree();
8808
8829
  init_stack();
8809
8830
 
8831
+ // src/git/agent-git-actions.ts
8832
+ var AGENT_GIT_ACTIONS = [
8833
+ "commit-push",
8834
+ "create-draft",
8835
+ "create-web",
8836
+ "resolve-conflicts",
8837
+ "merge"
8838
+ ];
8839
+ function agentGitPrompt(action, opts) {
8840
+ switch (action) {
8841
+ case "commit-push":
8842
+ return "Commit and push.";
8843
+ case "create-draft":
8844
+ return "Commit, push, and open a draft PR.";
8845
+ case "create-web":
8846
+ return "Commit, push, and open a PR in the browser.";
8847
+ case "resolve-conflicts": {
8848
+ const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
8849
+ return base ? `Merge origin/${base} into this branch. Then push.` : "Fix merge conflicts.";
8850
+ }
8851
+ case "merge":
8852
+ return "Merge PR.";
8853
+ }
8854
+ }
8855
+
8810
8856
  // src/git/pr-merge-archive.ts
8811
8857
  function normalizePrState(state) {
8812
8858
  return (state ?? "").trim().toUpperCase();
@@ -11422,10 +11468,10 @@ async function getDiff(worktreePath, repoPath, opts) {
11422
11468
  listBranchCommits(worktreePath, repoPath, { base: baseLabel }),
11423
11469
  isDirty(worktreePath),
11424
11470
  countUnpushedCommits(worktreePath)
11425
- ]).then(([scopeStats, commits, dirty, unpushed]) => ({
11471
+ ]).then(([scopeStats, commits, dirty2, unpushed]) => ({
11426
11472
  scopeStats,
11427
11473
  commits,
11428
- dirty,
11474
+ dirty: dirty2,
11429
11475
  unpushed
11430
11476
  })) : Promise.resolve({
11431
11477
  scopeStats: emptyScopeStats(),
@@ -11502,13 +11548,14 @@ async function getDiff(worktreePath, repoPath, opts) {
11502
11548
  }
11503
11549
  }
11504
11550
  const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
11551
+ const dirty = includeMeta ? meta.dirty : scope === "commits" ? false : files.length > 0;
11505
11552
  return {
11506
11553
  scope,
11507
11554
  commitSha: scope === "commits" ? commitSha : null,
11508
11555
  base: labelBase,
11509
11556
  files,
11510
11557
  stat: formatStat(files),
11511
- dirty: fileOnly || !includeMeta ? files.length > 0 : meta.dirty,
11558
+ dirty,
11512
11559
  unpushed: meta.unpushed,
11513
11560
  hasLastTurnBase,
11514
11561
  commits: meta.commits,
@@ -12224,7 +12271,7 @@ function formatRenameBranchDirective(thread, opts) {
12224
12271
  "- Rename the git branch to a short kebab-case name that describes this task (what you are changing), e.g. `fix/panel-width` or `feat/dark-mode`:",
12225
12272
  " `git branch -m <new-name>`",
12226
12273
  "- Prefer Conventional Commits style prefixes when they fit (`fix/`, `feat/`, `chore/`, `docs/`).",
12227
- "- Never push or merge to main/master from here."
12274
+ "- Never push this placeholder to main/master."
12228
12275
  ];
12229
12276
  const custom = opts?.customPrompt?.trim();
12230
12277
  if (custom) {
@@ -12267,7 +12314,7 @@ function formatWorktreeDirective(thread, opts) {
12267
12314
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
12268
12315
  );
12269
12316
  lines.push(
12270
- "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch; never push or merge to main/master from here."
12317
+ "- 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. When asked to merge the PR, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
12271
12318
  );
12272
12319
  if (thread.prUrl) {
12273
12320
  lines.push(
@@ -12299,13 +12346,13 @@ function formatWorktreeDirective(thread, opts) {
12299
12346
  '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push.'
12300
12347
  );
12301
12348
  lines.push(
12302
- '- "Update the branch." / "Fix merge conflicts." \u2192 sync with the PR base (merge or rebase), resolve conflicts carefully, commit, and push until the PR is mergeable.'
12349
+ '- "Update the branch." / "Fix merge conflicts." / "Merge origin/<base> into this branch. Then push." \u2192 sync with the PR base (merge or rebase), resolve conflicts carefully, commit, and push until the PR is mergeable.'
12303
12350
  );
12304
12351
  lines.push(
12305
12352
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
12306
12353
  );
12307
12354
  lines.push(
12308
- '- "Merge PR." \u2192 merge this thread\'s open pull request with `gh pr merge` (respect repo defaults / squash vs merge); do not force-push main/master.'
12355
+ '- "Merge PR." \u2192 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.'
12309
12356
  );
12310
12357
  return lines.join("\n");
12311
12358
  }
@@ -13745,6 +13792,36 @@ var Orchestrator = class {
13745
13792
  this.emit({ type: "status_changed", threadId: tab.id, status: tab.status });
13746
13793
  return tab;
13747
13794
  }
13795
+ /**
13796
+ * Queue a desktop-git-button prompt on a worktree agent (commit/push/PR/merge).
13797
+ * Orchestrators use this instead of running git/gh from the synthetic home.
13798
+ */
13799
+ async askGit(threadRef, action) {
13800
+ if (!AGENT_GIT_ACTIONS.includes(action)) {
13801
+ throw new Error(`Unknown git action: ${action}`);
13802
+ }
13803
+ const thread = this.requireThread(threadRef);
13804
+ this.assertNotGlobal(thread, "ask_git");
13805
+ if (isOrchestratorThread(thread)) {
13806
+ throw new Error(
13807
+ "ask_git targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
13808
+ );
13809
+ }
13810
+ if (action === "merge" && !thread.prUrl) {
13811
+ throw new Error(
13812
+ "No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
13813
+ );
13814
+ }
13815
+ let prBase;
13816
+ if (action === "resolve-conflicts") {
13817
+ try {
13818
+ const details = await this.getPrDetails(threadRef);
13819
+ prBase = details?.baseRefName?.trim() || void 0;
13820
+ } catch {
13821
+ }
13822
+ }
13823
+ return this.send(threadRef, agentGitPrompt(action, { prBase }));
13824
+ }
13748
13825
  setThreadOptions(threadRef, patch) {
13749
13826
  const thread = this.requireThread(threadRef);
13750
13827
  const next = {};
@@ -15078,7 +15155,7 @@ async function startMcpServer() {
15078
15155
  );
15079
15156
  server.tool(
15080
15157
  "send_to_thread",
15081
- "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
15158
+ "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
15082
15159
  {
15083
15160
  ref: import_zod2.z.string(),
15084
15161
  prompt: import_zod2.z.string(),
@@ -15109,7 +15186,7 @@ async function startMcpServer() {
15109
15186
  );
15110
15187
  server.tool(
15111
15188
  "wait_for_turn",
15112
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
15189
+ "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
15113
15190
  {
15114
15191
  ref: import_zod2.z.string(),
15115
15192
  timeoutMs: import_zod2.z.number().optional()
@@ -15174,7 +15251,7 @@ async function startMcpServer() {
15174
15251
  );
15175
15252
  server.tool(
15176
15253
  "archive_thread",
15177
- "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators open PRs only by asking the worktree agent.",
15254
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
15178
15255
  { ref: import_zod2.z.string() },
15179
15256
  async ({ ref }) => {
15180
15257
  const t = orch.getThread(ref);
@@ -15280,6 +15357,38 @@ async function startMcpServer() {
15280
15357
  }
15281
15358
  }
15282
15359
  );
15360
+ server.tool(
15361
+ "ask_git",
15362
+ "Tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge the linked PR \u2014 same short prompts as the desktop git buttons. The worktree agent runs git/gh (`gh pr merge`); this only queues the prompt. Pass a worktree thread ref (not the orchestrator). Then wait_for_turn / get_turn_result. Do not run git or gh from the orchestration cwd.",
15363
+ {
15364
+ ref: import_zod2.z.string().describe("Worktree thread id/ref"),
15365
+ action: import_zod2.z.enum(AGENT_GIT_ACTIONS).describe(
15366
+ "commit-push | create-draft | create-web | resolve-conflicts | merge"
15367
+ )
15368
+ },
15369
+ async ({ ref, action }) => {
15370
+ try {
15371
+ const thread = await orch.askGit(ref, action);
15372
+ return {
15373
+ content: [
15374
+ {
15375
+ type: "text",
15376
+ text: JSON.stringify({
15377
+ id: thread.id,
15378
+ status: thread.status,
15379
+ queueLength: thread.queue.length,
15380
+ action,
15381
+ link: `sideboard://thread/${thread.id}`
15382
+ })
15383
+ }
15384
+ ]
15385
+ };
15386
+ } catch (err) {
15387
+ const message = err instanceof Error ? err.message : String(err);
15388
+ return { content: [{ type: "text", text: message }], isError: true };
15389
+ }
15390
+ }
15391
+ );
15283
15392
  const agentEnum = import_zod2.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
15284
15393
  server.tool(
15285
15394
  "list_models",
@@ -15551,7 +15660,7 @@ async function startMcpServer() {
15551
15660
  );
15552
15661
  server.tool(
15553
15662
  "get_pr_stack",
15554
- "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before mergePr on stacked PRs.",
15663
+ "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs.",
15555
15664
  { ref: import_zod2.z.string() },
15556
15665
  async ({ ref }) => {
15557
15666
  const stack = await orch.getPrStack(ref);