@sideboard-ai/core 0.1.79 → 0.1.81

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.
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-PW5YHHP3.js";
7
+ } from "./chunk-K553XK7M.js";
8
8
  import {
9
9
  BRIGHTSY_MCP_ALLOWED_TOOLS,
10
10
  CLAUDE_MODEL_CATALOG,
@@ -13,12 +13,14 @@ import {
13
13
  SIDEBOARD_MCP_ALLOWED_TOOLS,
14
14
  SIDEBOARD_MCP_PROFILE_ENV,
15
15
  allAdapters,
16
+ applyTurnUsage,
16
17
  brightsyAdapter,
17
18
  brightsyMcpAllowedTools,
18
19
  buildCachedUserContent,
19
20
  buildClaudeStreamJsonUserMessage,
20
21
  claudeAdapter,
21
22
  codexAdapter,
23
+ contextTokens,
22
24
  countCacheControlBlocks,
23
25
  cursorAdapter,
24
26
  decodeBrightsyTarget,
@@ -26,6 +28,7 @@ import {
26
28
  encodeBrightsyTarget,
27
29
  findInvalidCacheControlTtlOrder,
28
30
  flattenTurnInput,
31
+ fromInclusiveInputUsage,
29
32
  getAdapter,
30
33
  getAgentSetupInfo,
31
34
  installAgent,
@@ -41,6 +44,7 @@ import {
41
44
  loginAgent,
42
45
  mcpAllowTools,
43
46
  mcpAuthWarnings,
47
+ mergeUsage,
44
48
  normalizeTurnInput,
45
49
  openInSystemTerminal,
46
50
  opencodeAdapter,
@@ -50,6 +54,7 @@ import {
50
54
  permissionMode,
51
55
  prepareTerminalCommand,
52
56
  promptMentionsBrightsy,
57
+ requestOccupancy,
53
58
  resolveCursorModelId,
54
59
  resolveLoginCommand,
55
60
  resolveQuotaFallbackAgent,
@@ -57,8 +62,9 @@ import {
57
62
  shouldInjectBrightsyMcp,
58
63
  sideboardMcpProfile,
59
64
  threadRequestsBrightsyMcp,
65
+ totalTokens,
60
66
  writeInjectedMcpConfig
61
- } from "./chunk-SHCR6RPU.js";
67
+ } from "./chunk-XNECBC6T.js";
62
68
  import {
63
69
  CLOUD_COORDINATOR_BUSY_REPLY,
64
70
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -85,7 +91,7 @@ import {
85
91
  parseForceStopMessage,
86
92
  slackCoordinatorSourceRef,
87
93
  takenTeamSlugsForOrchestration
88
- } from "./chunk-5LXWTU3J.js";
94
+ } from "./chunk-6F2TQFJE.js";
89
95
  import {
90
96
  COORDINATOR_TOOL_PLAYBOOK,
91
97
  SLACK_REPLY_FORMATTING,
@@ -94,7 +100,7 @@ import {
94
100
  enrichWorkspacesWithGithub,
95
101
  ensureGlobalCoordinatorCwd,
96
102
  formatWorkspaceInventory
97
- } from "./chunk-HCGEFU3J.js";
103
+ } from "./chunk-CQBQWX73.js";
98
104
  import {
99
105
  brightsyConfigPath,
100
106
  brightsyMcpServerName,
@@ -1296,41 +1302,6 @@ function normalizeParseResult(parsed) {
1296
1302
  return Array.isArray(parsed) ? parsed : [parsed];
1297
1303
  }
1298
1304
 
1299
- // src/agents/usage.ts
1300
- function sumOptional(a, b) {
1301
- if (a == null && b == null) return void 0;
1302
- return (a ?? 0) + (b ?? 0);
1303
- }
1304
- function requestOccupancy(u) {
1305
- return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
1306
- }
1307
- function mergeUsage(a, b) {
1308
- return {
1309
- inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
1310
- outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
1311
- cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
1312
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
1313
- lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
1314
- };
1315
- }
1316
- function applyTurnUsage(current, incoming, scope = "request") {
1317
- if (scope === "turn") {
1318
- return {
1319
- ...incoming,
1320
- lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
1321
- };
1322
- }
1323
- const merged = mergeUsage(current, incoming);
1324
- return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
1325
- }
1326
- function totalTokens(u) {
1327
- return u.inputTokens + u.outputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
1328
- }
1329
- function contextTokens(u) {
1330
- if (u.lastRequestTokens != null && u.lastRequestTokens > 0) return u.lastRequestTokens;
1331
- return requestOccupancy(u);
1332
- }
1333
-
1334
1305
  // src/agents/spawn.ts
1335
1306
  async function spawnAgentTurn(thread, input, onEvent) {
1336
1307
  ensureAgentPath();
@@ -1339,9 +1310,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
1339
1310
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
1340
1311
  );
1341
1312
  }
1342
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-VG44RZUH.js");
1313
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-DZM4ZFGU.js");
1343
1314
  if (isGlobalThread2(thread)) {
1344
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-2OVON2LQ.js");
1315
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-JQF72JKX.js");
1345
1316
  ensureGlobalCoordinatorCwd2(
1346
1317
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
1347
1318
  );
@@ -1508,7 +1479,7 @@ function formatWorktreeDirective(thread, opts) {
1508
1479
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
1509
1480
  );
1510
1481
  lines.push(
1511
- "- 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`)."
1482
+ "- 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`)."
1512
1483
  );
1513
1484
  if (thread.prUrl) {
1514
1485
  lines.push(
@@ -1546,7 +1517,7 @@ function formatWorktreeDirective(thread, opts) {
1546
1517
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
1547
1518
  );
1548
1519
  lines.push(
1549
- '- "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.'
1520
+ '- "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.'
1550
1521
  );
1551
1522
  return lines.join("\n");
1552
1523
  }
@@ -1589,10 +1560,11 @@ var FILES_BY_AGENT = {
1589
1560
  var MAX_CHARS_PER_FILE = 48e3;
1590
1561
  function loadAgentInstructions(worktreePath, agent) {
1591
1562
  const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
1592
- const seen = /* @__PURE__ */ new Set();
1563
+ const seenPaths = /* @__PURE__ */ new Set();
1564
+ const seenBodies = /* @__PURE__ */ new Set();
1593
1565
  const out = [];
1594
1566
  for (const rel of candidates) {
1595
- if (seen.has(rel)) continue;
1567
+ if (seenPaths.has(rel)) continue;
1596
1568
  const abs = join(worktreePath, rel);
1597
1569
  if (!existsSync(abs)) continue;
1598
1570
  try {
@@ -1604,7 +1576,10 @@ function loadAgentInstructions(worktreePath, agent) {
1604
1576
 
1605
1577
  \u2026(truncated)`;
1606
1578
  }
1607
- seen.add(rel);
1579
+ const body = content.trim().replace(/\r\n/g, "\n");
1580
+ if (seenBodies.has(body)) continue;
1581
+ seenPaths.add(rel);
1582
+ seenBodies.add(body);
1608
1583
  out.push({ relativePath: rel, content });
1609
1584
  } catch {
1610
1585
  }
@@ -3838,7 +3813,7 @@ async function createThread(input, _onSetupLine) {
3838
3813
  return readThread(thread.id) ?? thread;
3839
3814
  }
3840
3815
  async function listLinearIssues(agent, repoPath) {
3841
- const { getAdapter: getAdapter2 } = await import("./agents-EWPHOM6Y.js");
3816
+ const { getAdapter: getAdapter2 } = await import("./agents-UQ7D6U6L.js");
3842
3817
  await requireAgent(agent, { requireLinear: true });
3843
3818
  const adapter = getAdapter2(agent);
3844
3819
  if (!adapter.listLinearIssues) {
@@ -4381,7 +4356,7 @@ async function adoptThread(input) {
4381
4356
  messages: input.messages ?? []
4382
4357
  });
4383
4358
  writeThread(thread);
4384
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TYPJNUSM.js");
4359
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-QLRCOXAY.js");
4385
4360
  await ensureWorkspace2(repoPath);
4386
4361
  return thread;
4387
4362
  }
@@ -7336,7 +7311,7 @@ var Orchestrator = class {
7336
7311
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7337
7312
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7338
7313
  try {
7339
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TYPJNUSM.js");
7314
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-QLRCOXAY.js");
7340
7315
  await ensureWorkspace2(thread.repoPath);
7341
7316
  } catch {
7342
7317
  }
@@ -8608,7 +8583,7 @@ async function startMcpServer() {
8608
8583
  );
8609
8584
  server.tool(
8610
8585
  "send_to_thread",
8611
- "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.",
8586
+ '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, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. 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.',
8612
8587
  {
8613
8588
  ref: z3.string(),
8614
8589
  prompt: z3.string(),
@@ -8704,7 +8679,7 @@ async function startMcpServer() {
8704
8679
  );
8705
8680
  server.tool(
8706
8681
  "archive_thread",
8707
- "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).",
8682
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
8708
8683
  { ref: z3.string() },
8709
8684
  async ({ ref }) => {
8710
8685
  const t = orch.getThread(ref);
@@ -8812,7 +8787,7 @@ async function startMcpServer() {
8812
8787
  );
8813
8788
  server.tool(
8814
8789
  "ask_git",
8815
- "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). Do not run git or gh from the orchestration cwd.",
8790
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
8816
8791
  {
8817
8792
  ref: z3.string().describe("Worktree thread id/ref"),
8818
8793
  action: z3.enum(AGENT_GIT_ACTIONS).describe(
@@ -9113,7 +9088,7 @@ async function startMcpServer() {
9113
9088
  );
9114
9089
  server.tool(
9115
9090
  "get_pr_stack",
9116
- "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.",
9091
+ "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 (and only merge when the user explicitly asked).",
9117
9092
  { ref: z3.string() },
9118
9093
  async ({ ref }) => {
9119
9094
  const stack = await orch.getPrStack(ref);
@@ -11358,6 +11333,7 @@ export {
11358
11333
  formatWorkspaceInventory,
11359
11334
  formatWorktreeDirective,
11360
11335
  formatWorktreeReminder,
11336
+ fromInclusiveInputUsage,
11361
11337
  getAdapter,
11362
11338
  getAgentSetupInfo,
11363
11339
  getBrightsySession,
@@ -4489,7 +4489,7 @@ function coordinatorTurnReminder(opts) {
4489
4489
  goal ? `- Goal / title: ${goal}` : null,
4490
4490
  accountDefaultsPlaybookLine(),
4491
4491
  `- 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.`,
4492
- "- 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.",
4492
+ "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
4493
4493
  "- 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.",
4494
4494
  "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
4495
4495
  "- 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. Closing this chat also turns it off."
@@ -4543,9 +4543,9 @@ function ensureGlobalCoordinatorCwd(opts) {
4543
4543
  orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
4544
4544
  "Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
4545
4545
  "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.",
4546
- "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.",
4546
+ "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. Merge only if the user explicitly asked (`ask_git` merge).",
4547
4547
  "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.",
4548
- "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."
4548
+ "Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
4549
4549
  ].join("\n");
4550
4550
  try {
4551
4551
  (0, import_node_fs11.writeFileSync)((0, import_node_path12.join)(dir, "CLAUDE.md"), `${body}
@@ -4558,7 +4558,6 @@ function ensureGlobalCoordinatorCwd(opts) {
4558
4558
  }
4559
4559
  function coordinatorSystemPrompt(opts) {
4560
4560
  const audience = opts.audience ?? "cloud";
4561
- const reposDir = sideboardReposDir();
4562
4561
  const intro = audience === "cloud" ? [
4563
4562
  "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
4564
4563
  "Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
@@ -4576,14 +4575,8 @@ function coordinatorSystemPrompt(opts) {
4576
4575
  ...intro,
4577
4576
  "You operate across ALL registered workspaces below.",
4578
4577
  "You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
4579
- COORDINATOR_TOOL_PLAYBOOK,
4580
- accountDefaultsPlaybookLine(),
4581
- coordinatorGreenfieldPlaybook(reposDir),
4582
- "When creating threads, pass the correct repoPath for the target workspace.",
4578
+ "Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
4583
4579
  `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.`,
4584
- "Omit agent/model on create_thread unless you need to override Account defaults.",
4585
- "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.",
4586
- "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.",
4587
4580
  `Goal: ${opts.goal}`,
4588
4581
  "Registered workspaces:",
4589
4582
  formatWorkspaceInventory(opts.workspaces)
@@ -4628,6 +4621,7 @@ var init_coordinator_prompt = __esm({
4628
4621
  "- get_diff \u2014 compact diff summary",
4629
4622
  '- 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',
4630
4623
  "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
4624
+ '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
4631
4625
  '- 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.',
4632
4626
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4633
4627
  "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.",
@@ -5065,6 +5059,51 @@ var init_connected_teams = __esm({
5065
5059
  }
5066
5060
  });
5067
5061
 
5062
+ // src/agents/usage.ts
5063
+ function sumOptional(a, b) {
5064
+ if (a == null && b == null) return void 0;
5065
+ return (a ?? 0) + (b ?? 0);
5066
+ }
5067
+ function fromInclusiveInputUsage(opts) {
5068
+ const totalInput = Number(opts.inputTokens) || 0;
5069
+ const outputTokens = Number(opts.outputTokens) || 0;
5070
+ const cached = Number(opts.cachedInputTokens) || 0;
5071
+ if (!totalInput && !outputTokens) return null;
5072
+ const cacheReadTokens = cached > 0 ? Math.min(cached, totalInput) : 0;
5073
+ return {
5074
+ inputTokens: Math.max(0, totalInput - cacheReadTokens),
5075
+ outputTokens,
5076
+ cacheReadTokens: cacheReadTokens || void 0
5077
+ };
5078
+ }
5079
+ function requestOccupancy(u) {
5080
+ return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
5081
+ }
5082
+ function mergeUsage(a, b) {
5083
+ return {
5084
+ inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
5085
+ outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
5086
+ cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
5087
+ cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
5088
+ lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
5089
+ };
5090
+ }
5091
+ function applyTurnUsage(current, incoming, scope = "request") {
5092
+ if (scope === "turn") {
5093
+ return {
5094
+ ...incoming,
5095
+ lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
5096
+ };
5097
+ }
5098
+ const merged = mergeUsage(current, incoming);
5099
+ return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
5100
+ }
5101
+ var init_usage = __esm({
5102
+ "src/agents/usage.ts"() {
5103
+ "use strict";
5104
+ }
5105
+ });
5106
+
5068
5107
  // src/agents/brightsy-targets.ts
5069
5108
  function encodeBrightsyTarget(type, id, accountId) {
5070
5109
  if (accountId) return `team:${accountId}:${type}:${id}`;
@@ -5129,15 +5168,11 @@ var init_turn_input = __esm({
5129
5168
  // src/agents/brightsy.ts
5130
5169
  function usageFromBrightsy(usage) {
5131
5170
  if (!usage) return null;
5132
- const inputTokens = Number(usage.prompt_tokens ?? 0);
5133
- const outputTokens = Number(usage.completion_tokens ?? 0);
5134
- if (!inputTokens && !outputTokens) return null;
5135
- const cached = Number(usage.prompt_tokens_details?.cached_tokens ?? 0);
5136
- return {
5137
- inputTokens,
5138
- outputTokens,
5139
- cacheReadTokens: cached || void 0
5140
- };
5171
+ return fromInclusiveInputUsage({
5172
+ inputTokens: Number(usage.prompt_tokens ?? 0),
5173
+ outputTokens: Number(usage.completion_tokens ?? 0),
5174
+ cachedInputTokens: Number(usage.prompt_tokens_details?.cached_tokens ?? 0)
5175
+ });
5141
5176
  }
5142
5177
  function parseBrightsyCliLine(line) {
5143
5178
  const trimmed = line.trim();
@@ -5353,6 +5388,7 @@ var init_brightsy = __esm({
5353
5388
  init_connected_teams();
5354
5389
  init_config();
5355
5390
  init_app_settings();
5391
+ init_usage();
5356
5392
  init_brightsy_targets();
5357
5393
  init_error_detail();
5358
5394
  init_turn_input();
@@ -6232,14 +6268,11 @@ async function listCodexModels() {
6232
6268
  }
6233
6269
  function usageFromCodex(usage) {
6234
6270
  if (!usage) return null;
6235
- const inputTokens = Number(usage.input_tokens ?? 0);
6236
- const outputTokens = Number(usage.output_tokens ?? 0) + Number(usage.reasoning_output_tokens ?? 0);
6237
- if (!inputTokens && !outputTokens) return null;
6238
- return {
6239
- inputTokens,
6240
- outputTokens,
6241
- cacheReadTokens: usage.cached_input_tokens ? Number(usage.cached_input_tokens) : void 0
6242
- };
6271
+ return fromInclusiveInputUsage({
6272
+ inputTokens: Number(usage.input_tokens ?? 0),
6273
+ outputTokens: Number(usage.output_tokens ?? 0),
6274
+ cachedInputTokens: Number(usage.cached_input_tokens ?? 0)
6275
+ });
6243
6276
  }
6244
6277
  function codexConfigHasNetworkAccess() {
6245
6278
  const candidates = [
@@ -6299,6 +6332,7 @@ var init_codex = __esm({
6299
6332
  init_app_settings();
6300
6333
  init_global_workspace();
6301
6334
  init_error_detail();
6335
+ init_usage();
6302
6336
  init_injected_mcp();
6303
6337
  init_turn_input();
6304
6338
  init_types();
@@ -8899,36 +8933,7 @@ function normalizeParseResult(parsed) {
8899
8933
 
8900
8934
  // src/agents/spawn.ts
8901
8935
  init_path();
8902
-
8903
- // src/agents/usage.ts
8904
- function sumOptional(a, b) {
8905
- if (a == null && b == null) return void 0;
8906
- return (a ?? 0) + (b ?? 0);
8907
- }
8908
- function requestOccupancy(u) {
8909
- return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
8910
- }
8911
- function mergeUsage(a, b) {
8912
- return {
8913
- inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
8914
- outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
8915
- cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
8916
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
8917
- lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
8918
- };
8919
- }
8920
- function applyTurnUsage(current, incoming, scope = "request") {
8921
- if (scope === "turn") {
8922
- return {
8923
- ...incoming,
8924
- lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
8925
- };
8926
- }
8927
- const merged = mergeUsage(current, incoming);
8928
- return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
8929
- }
8930
-
8931
- // src/agents/spawn.ts
8936
+ init_usage();
8932
8937
  async function spawnAgentTurn(thread, input, onEvent) {
8933
8938
  ensureAgentPath();
8934
8939
  if (!thread.worktreePath?.trim()) {
@@ -12553,7 +12558,7 @@ function formatWorktreeDirective(thread, opts) {
12553
12558
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
12554
12559
  );
12555
12560
  lines.push(
12556
- "- 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`)."
12561
+ "- 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`)."
12557
12562
  );
12558
12563
  if (thread.prUrl) {
12559
12564
  lines.push(
@@ -12591,7 +12596,7 @@ function formatWorktreeDirective(thread, opts) {
12591
12596
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
12592
12597
  );
12593
12598
  lines.push(
12594
- '- "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.'
12599
+ '- "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.'
12595
12600
  );
12596
12601
  return lines.join("\n");
12597
12602
  }
@@ -15799,7 +15804,7 @@ async function startMcpServer() {
15799
15804
  );
15800
15805
  server.tool(
15801
15806
  "send_to_thread",
15802
- "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.",
15807
+ '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, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. 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.',
15803
15808
  {
15804
15809
  ref: import_zod3.z.string(),
15805
15810
  prompt: import_zod3.z.string(),
@@ -15895,7 +15900,7 @@ async function startMcpServer() {
15895
15900
  );
15896
15901
  server.tool(
15897
15902
  "archive_thread",
15898
- "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).",
15903
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
15899
15904
  { ref: import_zod3.z.string() },
15900
15905
  async ({ ref }) => {
15901
15906
  const t = orch.getThread(ref);
@@ -16003,7 +16008,7 @@ async function startMcpServer() {
16003
16008
  );
16004
16009
  server.tool(
16005
16010
  "ask_git",
16006
- "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). Do not run git or gh from the orchestration cwd.",
16011
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
16007
16012
  {
16008
16013
  ref: import_zod3.z.string().describe("Worktree thread id/ref"),
16009
16014
  action: import_zod3.z.enum(AGENT_GIT_ACTIONS).describe(
@@ -16304,7 +16309,7 @@ async function startMcpServer() {
16304
16309
  );
16305
16310
  server.tool(
16306
16311
  "get_pr_stack",
16307
- "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.",
16312
+ "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 (and only merge when the user explicitly asked).",
16308
16313
  { ref: import_zod3.z.string() },
16309
16314
  async ({ ref }) => {
16310
16315
  const stack = await orch.getPrStack(ref);
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  PLAN_MODE_INSTRUCTION,
5
+ applyTurnUsage,
5
6
  fallbackTurnFailDetail,
6
7
  formatTurnExitError,
7
8
  getAdapter,
@@ -16,14 +17,14 @@ import {
16
17
  resolveQuotaFallbackAgent,
17
18
  sideboardMcpProfile,
18
19
  summarizeTurnStderr
19
- } from "../chunk-P33ZQ7ZC.js";
20
+ } from "../chunk-4AVU4QXO.js";
20
21
  import "../chunk-VROPG6QF.js";
21
22
  import {
22
23
  addWorkspace,
23
24
  ensureWorkspace,
24
25
  removeWorkspace,
25
26
  syncWorkspacesFromThreads
26
- } from "../chunk-5VTWBI3I.js";
27
+ } from "../chunk-TDJ43HUD.js";
27
28
  import {
28
29
  extractPresentedPlan,
29
30
  readPlanFile,
@@ -41,14 +42,14 @@ import {
41
42
  isGlobalThread,
42
43
  isOrchestratorThread,
43
44
  orchestratorSessionPoisonedByBuiltins
44
- } from "../chunk-6T2SKGCS.js";
45
+ } from "../chunk-P5VDPGAN.js";
45
46
  import {
46
47
  SLACK_REPLY_FORMATTING,
47
48
  coordinatorSystemPrompt,
48
49
  coordinatorTurnReminder,
49
50
  enrichWorkspacesWithGithub,
50
51
  ensureGlobalCoordinatorCwd
51
- } from "../chunk-HSLQXL6M.js";
52
+ } from "../chunk-4HJK57XU.js";
52
53
  import {
53
54
  addPrStackLayer,
54
55
  allocateTeamName,
@@ -882,34 +883,6 @@ function normalizeParseResult(parsed) {
882
883
  return Array.isArray(parsed) ? parsed : [parsed];
883
884
  }
884
885
 
885
- // src/agents/usage.ts
886
- function sumOptional(a, b) {
887
- if (a == null && b == null) return void 0;
888
- return (a ?? 0) + (b ?? 0);
889
- }
890
- function requestOccupancy(u) {
891
- return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
892
- }
893
- function mergeUsage(a, b) {
894
- return {
895
- inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
896
- outputTokens: (a?.outputTokens ?? 0) + b.outputTokens,
897
- cacheReadTokens: sumOptional(a?.cacheReadTokens, b.cacheReadTokens),
898
- cacheWriteTokens: sumOptional(a?.cacheWriteTokens, b.cacheWriteTokens),
899
- lastRequestTokens: b.lastRequestTokens ?? a?.lastRequestTokens
900
- };
901
- }
902
- function applyTurnUsage(current, incoming, scope = "request") {
903
- if (scope === "turn") {
904
- return {
905
- ...incoming,
906
- lastRequestTokens: current?.lastRequestTokens ?? requestOccupancy(incoming)
907
- };
908
- }
909
- const merged = mergeUsage(current, incoming);
910
- return { ...merged, lastRequestTokens: requestOccupancy(incoming) };
911
- }
912
-
913
886
  // src/agents/spawn.ts
914
887
  async function spawnAgentTurn(thread, input, onEvent) {
915
888
  ensureAgentPath();
@@ -918,9 +891,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
918
891
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
919
892
  );
920
893
  }
921
- const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-RI367AM6.js");
894
+ const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-NUMUJRWG.js");
922
895
  if (isGlobalThread2(thread)) {
923
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-K2R34A5T.js");
896
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-NYNEFKIV.js");
924
897
  ensureGlobalCoordinatorCwd2(
925
898
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
926
899
  );
@@ -1852,7 +1825,7 @@ async function createThread(input, _onSetupLine) {
1852
1825
  return readThread(thread.id) ?? thread;
1853
1826
  }
1854
1827
  async function listLinearIssues(agent, repoPath) {
1855
- const { getAdapter: getAdapter2 } = await import("../agents-GRAPUXWW.js");
1828
+ const { getAdapter: getAdapter2 } = await import("../agents-EBKJ55PN.js");
1856
1829
  await requireAgent(agent, { requireLinear: true });
1857
1830
  const adapter = getAdapter2(agent);
1858
1831
  if (!adapter.listLinearIssues) {
@@ -2716,7 +2689,7 @@ async function adoptThread(input) {
2716
2689
  messages: input.messages ?? []
2717
2690
  });
2718
2691
  writeThread(thread);
2719
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-BQWQEZWE.js");
2692
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-SUU2GO3A.js");
2720
2693
  await ensureWorkspace2(repoPath);
2721
2694
  return thread;
2722
2695
  }
@@ -4487,7 +4460,7 @@ function formatWorktreeDirective(thread, opts) {
4487
4460
  "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
4488
4461
  );
4489
4462
  lines.push(
4490
- "- 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`)."
4463
+ "- 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`)."
4491
4464
  );
4492
4465
  if (thread.prUrl) {
4493
4466
  lines.push(
@@ -4525,7 +4498,7 @@ function formatWorktreeDirective(thread, opts) {
4525
4498
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
4526
4499
  );
4527
4500
  lines.push(
4528
- '- "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.'
4501
+ '- "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.'
4529
4502
  );
4530
4503
  return lines.join("\n");
4531
4504
  }
@@ -6122,7 +6095,7 @@ var Orchestrator = class {
6122
6095
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
6123
6096
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
6124
6097
  try {
6125
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-BQWQEZWE.js");
6098
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-SUU2GO3A.js");
6126
6099
  await ensureWorkspace2(thread.repoPath);
6127
6100
  } catch {
6128
6101
  }
@@ -7704,7 +7677,7 @@ async function startMcpServer() {
7704
7677
  );
7705
7678
  server.tool(
7706
7679
  "send_to_thread",
7707
- "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.",
7680
+ '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, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. 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.',
7708
7681
  {
7709
7682
  ref: z3.string(),
7710
7683
  prompt: z3.string(),
@@ -7800,7 +7773,7 @@ async function startMcpServer() {
7800
7773
  );
7801
7774
  server.tool(
7802
7775
  "archive_thread",
7803
- "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).",
7776
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
7804
7777
  { ref: z3.string() },
7805
7778
  async ({ ref }) => {
7806
7779
  const t = orch.getThread(ref);
@@ -7908,7 +7881,7 @@ async function startMcpServer() {
7908
7881
  );
7909
7882
  server.tool(
7910
7883
  "ask_git",
7911
- "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). Do not run git or gh from the orchestration cwd.",
7884
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). action=merge only when the user explicitly asked to merge that PR. Do not run git or gh from the orchestration cwd.",
7912
7885
  {
7913
7886
  ref: z3.string().describe("Worktree thread id/ref"),
7914
7887
  action: z3.enum(AGENT_GIT_ACTIONS).describe(
@@ -8209,7 +8182,7 @@ async function startMcpServer() {
8209
8182
  );
8210
8183
  server.tool(
8211
8184
  "get_pr_stack",
8212
- "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.",
8185
+ "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 (and only merge when the user explicitly asked).",
8213
8186
  { ref: z3.string() },
8214
8187
  async ({ ref }) => {
8215
8188
  const stack = await orch.getPrStack(ref);