opencode-swarm-plugin 0.12.16 → 0.12.18

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
@@ -21765,6 +21765,42 @@ var SwarmStatusSchema = exports_external.object({
21765
21765
  last_update: exports_external.string().datetime({ offset: true })
21766
21766
  });
21767
21767
  // src/beads.ts
21768
+ var beadsWorkingDirectory = null;
21769
+ function setBeadsWorkingDirectory(directory) {
21770
+ beadsWorkingDirectory = directory;
21771
+ }
21772
+ function getBeadsWorkingDirectory() {
21773
+ return beadsWorkingDirectory || process.cwd();
21774
+ }
21775
+ async function runBdCommand(args) {
21776
+ const cwd = getBeadsWorkingDirectory();
21777
+ const proc = Bun.spawn(["bd", ...args], {
21778
+ cwd,
21779
+ stdout: "pipe",
21780
+ stderr: "pipe"
21781
+ });
21782
+ const [stdout, stderr] = await Promise.all([
21783
+ new Response(proc.stdout).text(),
21784
+ new Response(proc.stderr).text()
21785
+ ]);
21786
+ const exitCode = await proc.exited;
21787
+ return { exitCode, stdout, stderr };
21788
+ }
21789
+ async function runGitCommand(args) {
21790
+ const cwd = getBeadsWorkingDirectory();
21791
+ const proc = Bun.spawn(["git", ...args], {
21792
+ cwd,
21793
+ stdout: "pipe",
21794
+ stderr: "pipe"
21795
+ });
21796
+ const [stdout, stderr] = await Promise.all([
21797
+ new Response(proc.stdout).text(),
21798
+ new Response(proc.stderr).text()
21799
+ ]);
21800
+ const exitCode = await proc.exited;
21801
+ return { exitCode, stdout, stderr };
21802
+ }
21803
+
21768
21804
  class BeadError extends Error {
21769
21805
  command;
21770
21806
  exitCode;
@@ -21844,11 +21880,11 @@ var beads_create = tool({
21844
21880
  async execute(args, ctx) {
21845
21881
  const validated = BeadCreateArgsSchema.parse(args);
21846
21882
  const cmdParts = buildCreateCommand(validated);
21847
- const result = await Bun.$`${cmdParts}`.quiet().nothrow();
21883
+ const result = await runBdCommand(cmdParts.slice(1));
21848
21884
  if (result.exitCode !== 0) {
21849
- throw new BeadError(`Failed to create bead: ${result.stderr.toString()}`, cmdParts.join(" "), result.exitCode, result.stderr.toString());
21885
+ throw new BeadError(`Failed to create bead: ${result.stderr}`, cmdParts.join(" "), result.exitCode, result.stderr);
21850
21886
  }
21851
- const stdout = result.stdout.toString().trim();
21887
+ const stdout = result.stdout.trim();
21852
21888
  if (!stdout) {
21853
21889
  throw new BeadError("bd create returned empty output", cmdParts.join(" "), 0, "Empty stdout");
21854
21890
  }
@@ -21880,11 +21916,11 @@ var beads_create_epic = tool({
21880
21916
  priority: 1,
21881
21917
  description: validated.epic_description
21882
21918
  });
21883
- const epicResult = await Bun.$`${epicCmd}`.quiet().nothrow();
21919
+ const epicResult = await runBdCommand(epicCmd.slice(1));
21884
21920
  if (epicResult.exitCode !== 0) {
21885
- throw new BeadError(`Failed to create epic: ${epicResult.stderr.toString()}`, epicCmd.join(" "), epicResult.exitCode);
21921
+ throw new BeadError(`Failed to create epic: ${epicResult.stderr}`, epicCmd.join(" "), epicResult.exitCode);
21886
21922
  }
21887
- const epic = parseBead(epicResult.stdout.toString());
21923
+ const epic = parseBead(epicResult.stdout);
21888
21924
  created.push(epic);
21889
21925
  for (const subtask of validated.subtasks) {
21890
21926
  const subtaskCmd = buildCreateCommand({
@@ -21893,11 +21929,11 @@ var beads_create_epic = tool({
21893
21929
  priority: subtask.priority ?? 2,
21894
21930
  parent_id: epic.id
21895
21931
  });
21896
- const subtaskResult = await Bun.$`${subtaskCmd}`.quiet().nothrow();
21932
+ const subtaskResult = await runBdCommand(subtaskCmd.slice(1));
21897
21933
  if (subtaskResult.exitCode !== 0) {
21898
- throw new BeadError(`Failed to create subtask: ${subtaskResult.stderr.toString()}`, subtaskCmd.join(" "), subtaskResult.exitCode);
21934
+ throw new BeadError(`Failed to create subtask: ${subtaskResult.stderr}`, subtaskCmd.join(" "), subtaskResult.exitCode);
21899
21935
  }
21900
- const subtaskBead = parseBead(subtaskResult.stdout.toString());
21936
+ const subtaskBead = parseBead(subtaskResult.stdout);
21901
21937
  created.push(subtaskBead);
21902
21938
  }
21903
21939
  const result = {
@@ -21911,19 +21947,18 @@ var beads_create_epic = tool({
21911
21947
  const rollbackErrors = [];
21912
21948
  for (const bead of created) {
21913
21949
  try {
21914
- const closeCmd = [
21915
- "bd",
21950
+ const closeArgs = [
21916
21951
  "close",
21917
21952
  bead.id,
21918
21953
  "--reason",
21919
21954
  "Rollback partial epic",
21920
21955
  "--json"
21921
21956
  ];
21922
- const rollbackResult = await Bun.$`${closeCmd}`.quiet().nothrow();
21957
+ const rollbackResult = await runBdCommand(closeArgs);
21923
21958
  if (rollbackResult.exitCode === 0) {
21924
21959
  rollbackCommands.push(`bd close ${bead.id} --reason "Rollback partial epic"`);
21925
21960
  } else {
21926
- rollbackErrors.push(`${bead.id}: exit ${rollbackResult.exitCode} - ${rollbackResult.stderr.toString().trim()}`);
21961
+ rollbackErrors.push(`${bead.id}: exit ${rollbackResult.exitCode} - ${rollbackResult.stderr.trim()}`);
21927
21962
  }
21928
21963
  } catch (rollbackError) {
21929
21964
  const errMsg = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
@@ -21978,11 +22013,11 @@ var beads_query = tool({
21978
22013
  cmd.push("--type", validated.type);
21979
22014
  }
21980
22015
  }
21981
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22016
+ const result = await runBdCommand(cmd.slice(1));
21982
22017
  if (result.exitCode !== 0) {
21983
- throw new BeadError(`Failed to query beads: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22018
+ throw new BeadError(`Failed to query beads: ${result.stderr}`, cmd.join(" "), result.exitCode);
21984
22019
  }
21985
- const beads = parseBeads(result.stdout.toString());
22020
+ const beads = parseBeads(result.stdout);
21986
22021
  const limited = beads.slice(0, validated.limit);
21987
22022
  return JSON.stringify(limited, null, 2);
21988
22023
  }
@@ -22008,11 +22043,11 @@ var beads_update = tool({
22008
22043
  cmd.push("-p", validated.priority.toString());
22009
22044
  }
22010
22045
  cmd.push("--json");
22011
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22046
+ const result = await runBdCommand(cmd.slice(1));
22012
22047
  if (result.exitCode !== 0) {
22013
- throw new BeadError(`Failed to update bead: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22048
+ throw new BeadError(`Failed to update bead: ${result.stderr}`, cmd.join(" "), result.exitCode);
22014
22049
  }
22015
- const bead = parseBead(result.stdout.toString());
22050
+ const bead = parseBead(result.stdout);
22016
22051
  return JSON.stringify(bead, null, 2);
22017
22052
  }
22018
22053
  });
@@ -22032,11 +22067,11 @@ var beads_close = tool({
22032
22067
  validated.reason,
22033
22068
  "--json"
22034
22069
  ];
22035
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22070
+ const result = await runBdCommand(cmd.slice(1));
22036
22071
  if (result.exitCode !== 0) {
22037
- throw new BeadError(`Failed to close bead: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22072
+ throw new BeadError(`Failed to close bead: ${result.stderr}`, cmd.join(" "), result.exitCode);
22038
22073
  }
22039
- const bead = parseBead(result.stdout.toString());
22074
+ const bead = parseBead(result.stdout);
22040
22075
  return `Closed ${bead.id}: ${validated.reason}`;
22041
22076
  }
22042
22077
  });
@@ -22046,12 +22081,17 @@ var beads_start = tool({
22046
22081
  id: tool.schema.string().describe("Bead ID")
22047
22082
  },
22048
22083
  async execute(args, ctx) {
22049
- const cmd = ["bd", "update", args.id, "--status", "in_progress", "--json"];
22050
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22084
+ const result = await runBdCommand([
22085
+ "update",
22086
+ args.id,
22087
+ "--status",
22088
+ "in_progress",
22089
+ "--json"
22090
+ ]);
22051
22091
  if (result.exitCode !== 0) {
22052
- throw new BeadError(`Failed to start bead: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22092
+ throw new BeadError(`Failed to start bead: ${result.stderr}`, `bd update ${args.id} --status in_progress --json`, result.exitCode);
22053
22093
  }
22054
- const bead = parseBead(result.stdout.toString());
22094
+ const bead = parseBead(result.stdout);
22055
22095
  return `Started: ${bead.id}`;
22056
22096
  }
22057
22097
  });
@@ -22059,12 +22099,11 @@ var beads_ready = tool({
22059
22099
  description: "Get the next ready bead (unblocked, highest priority)",
22060
22100
  args: {},
22061
22101
  async execute(args, ctx) {
22062
- const cmd = ["bd", "ready", "--json"];
22063
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22102
+ const result = await runBdCommand(["ready", "--json"]);
22064
22103
  if (result.exitCode !== 0) {
22065
- throw new BeadError(`Failed to get ready beads: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22104
+ throw new BeadError(`Failed to get ready beads: ${result.stderr}`, "bd ready --json", result.exitCode);
22066
22105
  }
22067
- const beads = parseBeads(result.stdout.toString());
22106
+ const beads = parseBeads(result.stdout);
22068
22107
  if (beads.length === 0) {
22069
22108
  return "No ready beads";
22070
22109
  }
@@ -22093,22 +22132,42 @@ var beads_sync = tool({
22093
22132
  }
22094
22133
  }
22095
22134
  };
22135
+ const flushResult = await withTimeout(runBdCommand(["sync", "--flush-only"]), TIMEOUT_MS, "bd sync --flush-only");
22136
+ if (flushResult.exitCode !== 0) {
22137
+ throw new BeadError(`Failed to flush beads: ${flushResult.stderr}`, "bd sync --flush-only", flushResult.exitCode);
22138
+ }
22139
+ const beadsStatusResult = await runGitCommand([
22140
+ "status",
22141
+ "--porcelain",
22142
+ ".beads/"
22143
+ ]);
22144
+ const hasChanges = beadsStatusResult.stdout.trim() !== "";
22145
+ if (hasChanges) {
22146
+ const addResult = await runGitCommand(["add", ".beads/"]);
22147
+ if (addResult.exitCode !== 0) {
22148
+ throw new BeadError(`Failed to stage beads: ${addResult.stderr}`, "git add .beads/", addResult.exitCode);
22149
+ }
22150
+ const commitResult = await withTimeout(runGitCommand(["commit", "-m", "chore: sync beads"]), TIMEOUT_MS, "git commit");
22151
+ if (commitResult.exitCode !== 0 && !commitResult.stdout.includes("nothing to commit")) {
22152
+ throw new BeadError(`Failed to commit beads: ${commitResult.stderr}`, "git commit", commitResult.exitCode);
22153
+ }
22154
+ }
22096
22155
  if (autoPull) {
22097
- const pullResult = await withTimeout(Bun.$`git pull --rebase`.quiet().nothrow(), TIMEOUT_MS, "git pull --rebase");
22156
+ const pullResult = await withTimeout(runGitCommand(["pull", "--rebase"]), TIMEOUT_MS, "git pull --rebase");
22098
22157
  if (pullResult.exitCode !== 0) {
22099
- throw new BeadError(`Failed to pull: ${pullResult.stderr.toString()}`, "git pull --rebase", pullResult.exitCode);
22158
+ throw new BeadError(`Failed to pull: ${pullResult.stderr}`, "git pull --rebase", pullResult.exitCode);
22159
+ }
22160
+ const importResult = await withTimeout(runBdCommand(["sync", "--import-only"]), TIMEOUT_MS, "bd sync --import-only");
22161
+ if (importResult.exitCode !== 0) {
22162
+ console.warn(`[beads] Import warning: ${importResult.stderr}`);
22100
22163
  }
22101
22164
  }
22102
- const syncResult = await withTimeout(Bun.$`bd sync`.quiet().nothrow(), TIMEOUT_MS, "bd sync");
22103
- if (syncResult.exitCode !== 0) {
22104
- throw new BeadError(`Failed to sync beads: ${syncResult.stderr.toString()}`, "bd sync", syncResult.exitCode);
22105
- }
22106
- const pushResult = await withTimeout(Bun.$`git push`.quiet().nothrow(), TIMEOUT_MS, "git push");
22165
+ const pushResult = await withTimeout(runGitCommand(["push"]), TIMEOUT_MS, "git push");
22107
22166
  if (pushResult.exitCode !== 0) {
22108
- throw new BeadError(`Failed to push: ${pushResult.stderr.toString()}`, "git push", pushResult.exitCode);
22167
+ throw new BeadError(`Failed to push: ${pushResult.stderr}`, "git push", pushResult.exitCode);
22109
22168
  }
22110
- const statusResult = await Bun.$`git status --porcelain`.quiet().nothrow();
22111
- const status = statusResult.stdout.toString().trim();
22169
+ const statusResult = await runGitCommand(["status", "--porcelain"]);
22170
+ const status = statusResult.stdout.trim();
22112
22171
  if (status !== "") {
22113
22172
  return `Beads synced and pushed, but working directory not clean:
22114
22173
  ${status}`;
@@ -22123,11 +22182,11 @@ var beads_link_thread = tool({
22123
22182
  thread_id: tool.schema.string().describe("Agent Mail thread ID")
22124
22183
  },
22125
22184
  async execute(args, ctx) {
22126
- const queryResult = await Bun.$`bd show ${args.bead_id} --json`.quiet().nothrow();
22185
+ const queryResult = await runBdCommand(["show", args.bead_id, "--json"]);
22127
22186
  if (queryResult.exitCode !== 0) {
22128
- throw new BeadError(`Failed to get bead: ${queryResult.stderr.toString()}`, `bd show ${args.bead_id} --json`, queryResult.exitCode);
22187
+ throw new BeadError(`Failed to get bead: ${queryResult.stderr}`, `bd show ${args.bead_id} --json`, queryResult.exitCode);
22129
22188
  }
22130
- const bead = parseBead(queryResult.stdout.toString());
22189
+ const bead = parseBead(queryResult.stdout);
22131
22190
  const existingDesc = bead.description || "";
22132
22191
  const threadMarker = `[thread:${args.thread_id}]`;
22133
22192
  if (existingDesc.includes(threadMarker)) {
@@ -22136,9 +22195,15 @@ var beads_link_thread = tool({
22136
22195
  const newDesc = existingDesc ? `${existingDesc}
22137
22196
 
22138
22197
  ${threadMarker}` : threadMarker;
22139
- const updateResult = await Bun.$`bd update ${args.bead_id} -d ${newDesc} --json`.quiet().nothrow();
22198
+ const updateResult = await runBdCommand([
22199
+ "update",
22200
+ args.bead_id,
22201
+ "-d",
22202
+ newDesc,
22203
+ "--json"
22204
+ ]);
22140
22205
  if (updateResult.exitCode !== 0) {
22141
- throw new BeadError(`Failed to update bead: ${updateResult.stderr.toString()}`, `bd update ${args.bead_id} -d ...`, updateResult.exitCode);
22206
+ throw new BeadError(`Failed to update bead: ${updateResult.stderr}`, `bd update ${args.bead_id} -d ...`, updateResult.exitCode);
22142
22207
  }
22143
22208
  return `Linked bead ${args.bead_id} to thread ${args.thread_id}`;
22144
22209
  }
@@ -26496,7 +26561,8 @@ async function resetStorage() {
26496
26561
 
26497
26562
  // src/index.ts
26498
26563
  var SwarmPlugin = async (input) => {
26499
- const { $ } = input;
26564
+ const { $, directory } = input;
26565
+ setBeadsWorkingDirectory(directory);
26500
26566
  let activeAgentMailState = null;
26501
26567
  async function releaseReservations() {
26502
26568
  if (!activeAgentMailState || activeAgentMailState.reservations.length === 0) {
@@ -26587,6 +26653,7 @@ export {
26587
26653
  swarmTools,
26588
26654
  structuredTools,
26589
26655
  setStorage,
26656
+ setBeadsWorkingDirectory,
26590
26657
  selectStrategy,
26591
26658
  resetToolCache,
26592
26659
  resetStorage,
@@ -26598,6 +26665,7 @@ export {
26598
26665
  getToolAvailability,
26599
26666
  getStorage,
26600
26667
  getSchemaByName,
26668
+ getBeadsWorkingDirectory,
26601
26669
  formatZodErrors,
26602
26670
  formatToolAvailability,
26603
26671
  formatSubtaskPromptV2,
package/dist/plugin.js CHANGED
@@ -21765,6 +21765,42 @@ var SwarmStatusSchema = exports_external.object({
21765
21765
  last_update: exports_external.string().datetime({ offset: true })
21766
21766
  });
21767
21767
  // src/beads.ts
21768
+ var beadsWorkingDirectory = null;
21769
+ function setBeadsWorkingDirectory(directory) {
21770
+ beadsWorkingDirectory = directory;
21771
+ }
21772
+ function getBeadsWorkingDirectory() {
21773
+ return beadsWorkingDirectory || process.cwd();
21774
+ }
21775
+ async function runBdCommand(args) {
21776
+ const cwd = getBeadsWorkingDirectory();
21777
+ const proc = Bun.spawn(["bd", ...args], {
21778
+ cwd,
21779
+ stdout: "pipe",
21780
+ stderr: "pipe"
21781
+ });
21782
+ const [stdout, stderr] = await Promise.all([
21783
+ new Response(proc.stdout).text(),
21784
+ new Response(proc.stderr).text()
21785
+ ]);
21786
+ const exitCode = await proc.exited;
21787
+ return { exitCode, stdout, stderr };
21788
+ }
21789
+ async function runGitCommand(args) {
21790
+ const cwd = getBeadsWorkingDirectory();
21791
+ const proc = Bun.spawn(["git", ...args], {
21792
+ cwd,
21793
+ stdout: "pipe",
21794
+ stderr: "pipe"
21795
+ });
21796
+ const [stdout, stderr] = await Promise.all([
21797
+ new Response(proc.stdout).text(),
21798
+ new Response(proc.stderr).text()
21799
+ ]);
21800
+ const exitCode = await proc.exited;
21801
+ return { exitCode, stdout, stderr };
21802
+ }
21803
+
21768
21804
  class BeadError extends Error {
21769
21805
  command;
21770
21806
  exitCode;
@@ -21844,11 +21880,11 @@ var beads_create = tool({
21844
21880
  async execute(args, ctx) {
21845
21881
  const validated = BeadCreateArgsSchema.parse(args);
21846
21882
  const cmdParts = buildCreateCommand(validated);
21847
- const result = await Bun.$`${cmdParts}`.quiet().nothrow();
21883
+ const result = await runBdCommand(cmdParts.slice(1));
21848
21884
  if (result.exitCode !== 0) {
21849
- throw new BeadError(`Failed to create bead: ${result.stderr.toString()}`, cmdParts.join(" "), result.exitCode, result.stderr.toString());
21885
+ throw new BeadError(`Failed to create bead: ${result.stderr}`, cmdParts.join(" "), result.exitCode, result.stderr);
21850
21886
  }
21851
- const stdout = result.stdout.toString().trim();
21887
+ const stdout = result.stdout.trim();
21852
21888
  if (!stdout) {
21853
21889
  throw new BeadError("bd create returned empty output", cmdParts.join(" "), 0, "Empty stdout");
21854
21890
  }
@@ -21880,11 +21916,11 @@ var beads_create_epic = tool({
21880
21916
  priority: 1,
21881
21917
  description: validated.epic_description
21882
21918
  });
21883
- const epicResult = await Bun.$`${epicCmd}`.quiet().nothrow();
21919
+ const epicResult = await runBdCommand(epicCmd.slice(1));
21884
21920
  if (epicResult.exitCode !== 0) {
21885
- throw new BeadError(`Failed to create epic: ${epicResult.stderr.toString()}`, epicCmd.join(" "), epicResult.exitCode);
21921
+ throw new BeadError(`Failed to create epic: ${epicResult.stderr}`, epicCmd.join(" "), epicResult.exitCode);
21886
21922
  }
21887
- const epic = parseBead(epicResult.stdout.toString());
21923
+ const epic = parseBead(epicResult.stdout);
21888
21924
  created.push(epic);
21889
21925
  for (const subtask of validated.subtasks) {
21890
21926
  const subtaskCmd = buildCreateCommand({
@@ -21893,11 +21929,11 @@ var beads_create_epic = tool({
21893
21929
  priority: subtask.priority ?? 2,
21894
21930
  parent_id: epic.id
21895
21931
  });
21896
- const subtaskResult = await Bun.$`${subtaskCmd}`.quiet().nothrow();
21932
+ const subtaskResult = await runBdCommand(subtaskCmd.slice(1));
21897
21933
  if (subtaskResult.exitCode !== 0) {
21898
- throw new BeadError(`Failed to create subtask: ${subtaskResult.stderr.toString()}`, subtaskCmd.join(" "), subtaskResult.exitCode);
21934
+ throw new BeadError(`Failed to create subtask: ${subtaskResult.stderr}`, subtaskCmd.join(" "), subtaskResult.exitCode);
21899
21935
  }
21900
- const subtaskBead = parseBead(subtaskResult.stdout.toString());
21936
+ const subtaskBead = parseBead(subtaskResult.stdout);
21901
21937
  created.push(subtaskBead);
21902
21938
  }
21903
21939
  const result = {
@@ -21911,19 +21947,18 @@ var beads_create_epic = tool({
21911
21947
  const rollbackErrors = [];
21912
21948
  for (const bead of created) {
21913
21949
  try {
21914
- const closeCmd = [
21915
- "bd",
21950
+ const closeArgs = [
21916
21951
  "close",
21917
21952
  bead.id,
21918
21953
  "--reason",
21919
21954
  "Rollback partial epic",
21920
21955
  "--json"
21921
21956
  ];
21922
- const rollbackResult = await Bun.$`${closeCmd}`.quiet().nothrow();
21957
+ const rollbackResult = await runBdCommand(closeArgs);
21923
21958
  if (rollbackResult.exitCode === 0) {
21924
21959
  rollbackCommands.push(`bd close ${bead.id} --reason "Rollback partial epic"`);
21925
21960
  } else {
21926
- rollbackErrors.push(`${bead.id}: exit ${rollbackResult.exitCode} - ${rollbackResult.stderr.toString().trim()}`);
21961
+ rollbackErrors.push(`${bead.id}: exit ${rollbackResult.exitCode} - ${rollbackResult.stderr.trim()}`);
21927
21962
  }
21928
21963
  } catch (rollbackError) {
21929
21964
  const errMsg = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
@@ -21978,11 +22013,11 @@ var beads_query = tool({
21978
22013
  cmd.push("--type", validated.type);
21979
22014
  }
21980
22015
  }
21981
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22016
+ const result = await runBdCommand(cmd.slice(1));
21982
22017
  if (result.exitCode !== 0) {
21983
- throw new BeadError(`Failed to query beads: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22018
+ throw new BeadError(`Failed to query beads: ${result.stderr}`, cmd.join(" "), result.exitCode);
21984
22019
  }
21985
- const beads = parseBeads(result.stdout.toString());
22020
+ const beads = parseBeads(result.stdout);
21986
22021
  const limited = beads.slice(0, validated.limit);
21987
22022
  return JSON.stringify(limited, null, 2);
21988
22023
  }
@@ -22008,11 +22043,11 @@ var beads_update = tool({
22008
22043
  cmd.push("-p", validated.priority.toString());
22009
22044
  }
22010
22045
  cmd.push("--json");
22011
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22046
+ const result = await runBdCommand(cmd.slice(1));
22012
22047
  if (result.exitCode !== 0) {
22013
- throw new BeadError(`Failed to update bead: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22048
+ throw new BeadError(`Failed to update bead: ${result.stderr}`, cmd.join(" "), result.exitCode);
22014
22049
  }
22015
- const bead = parseBead(result.stdout.toString());
22050
+ const bead = parseBead(result.stdout);
22016
22051
  return JSON.stringify(bead, null, 2);
22017
22052
  }
22018
22053
  });
@@ -22032,11 +22067,11 @@ var beads_close = tool({
22032
22067
  validated.reason,
22033
22068
  "--json"
22034
22069
  ];
22035
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22070
+ const result = await runBdCommand(cmd.slice(1));
22036
22071
  if (result.exitCode !== 0) {
22037
- throw new BeadError(`Failed to close bead: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22072
+ throw new BeadError(`Failed to close bead: ${result.stderr}`, cmd.join(" "), result.exitCode);
22038
22073
  }
22039
- const bead = parseBead(result.stdout.toString());
22074
+ const bead = parseBead(result.stdout);
22040
22075
  return `Closed ${bead.id}: ${validated.reason}`;
22041
22076
  }
22042
22077
  });
@@ -22046,12 +22081,17 @@ var beads_start = tool({
22046
22081
  id: tool.schema.string().describe("Bead ID")
22047
22082
  },
22048
22083
  async execute(args, ctx) {
22049
- const cmd = ["bd", "update", args.id, "--status", "in_progress", "--json"];
22050
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22084
+ const result = await runBdCommand([
22085
+ "update",
22086
+ args.id,
22087
+ "--status",
22088
+ "in_progress",
22089
+ "--json"
22090
+ ]);
22051
22091
  if (result.exitCode !== 0) {
22052
- throw new BeadError(`Failed to start bead: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22092
+ throw new BeadError(`Failed to start bead: ${result.stderr}`, `bd update ${args.id} --status in_progress --json`, result.exitCode);
22053
22093
  }
22054
- const bead = parseBead(result.stdout.toString());
22094
+ const bead = parseBead(result.stdout);
22055
22095
  return `Started: ${bead.id}`;
22056
22096
  }
22057
22097
  });
@@ -22059,12 +22099,11 @@ var beads_ready = tool({
22059
22099
  description: "Get the next ready bead (unblocked, highest priority)",
22060
22100
  args: {},
22061
22101
  async execute(args, ctx) {
22062
- const cmd = ["bd", "ready", "--json"];
22063
- const result = await Bun.$`${cmd}`.quiet().nothrow();
22102
+ const result = await runBdCommand(["ready", "--json"]);
22064
22103
  if (result.exitCode !== 0) {
22065
- throw new BeadError(`Failed to get ready beads: ${result.stderr.toString()}`, cmd.join(" "), result.exitCode);
22104
+ throw new BeadError(`Failed to get ready beads: ${result.stderr}`, "bd ready --json", result.exitCode);
22066
22105
  }
22067
- const beads = parseBeads(result.stdout.toString());
22106
+ const beads = parseBeads(result.stdout);
22068
22107
  if (beads.length === 0) {
22069
22108
  return "No ready beads";
22070
22109
  }
@@ -22093,22 +22132,42 @@ var beads_sync = tool({
22093
22132
  }
22094
22133
  }
22095
22134
  };
22135
+ const flushResult = await withTimeout(runBdCommand(["sync", "--flush-only"]), TIMEOUT_MS, "bd sync --flush-only");
22136
+ if (flushResult.exitCode !== 0) {
22137
+ throw new BeadError(`Failed to flush beads: ${flushResult.stderr}`, "bd sync --flush-only", flushResult.exitCode);
22138
+ }
22139
+ const beadsStatusResult = await runGitCommand([
22140
+ "status",
22141
+ "--porcelain",
22142
+ ".beads/"
22143
+ ]);
22144
+ const hasChanges = beadsStatusResult.stdout.trim() !== "";
22145
+ if (hasChanges) {
22146
+ const addResult = await runGitCommand(["add", ".beads/"]);
22147
+ if (addResult.exitCode !== 0) {
22148
+ throw new BeadError(`Failed to stage beads: ${addResult.stderr}`, "git add .beads/", addResult.exitCode);
22149
+ }
22150
+ const commitResult = await withTimeout(runGitCommand(["commit", "-m", "chore: sync beads"]), TIMEOUT_MS, "git commit");
22151
+ if (commitResult.exitCode !== 0 && !commitResult.stdout.includes("nothing to commit")) {
22152
+ throw new BeadError(`Failed to commit beads: ${commitResult.stderr}`, "git commit", commitResult.exitCode);
22153
+ }
22154
+ }
22096
22155
  if (autoPull) {
22097
- const pullResult = await withTimeout(Bun.$`git pull --rebase`.quiet().nothrow(), TIMEOUT_MS, "git pull --rebase");
22156
+ const pullResult = await withTimeout(runGitCommand(["pull", "--rebase"]), TIMEOUT_MS, "git pull --rebase");
22098
22157
  if (pullResult.exitCode !== 0) {
22099
- throw new BeadError(`Failed to pull: ${pullResult.stderr.toString()}`, "git pull --rebase", pullResult.exitCode);
22158
+ throw new BeadError(`Failed to pull: ${pullResult.stderr}`, "git pull --rebase", pullResult.exitCode);
22159
+ }
22160
+ const importResult = await withTimeout(runBdCommand(["sync", "--import-only"]), TIMEOUT_MS, "bd sync --import-only");
22161
+ if (importResult.exitCode !== 0) {
22162
+ console.warn(`[beads] Import warning: ${importResult.stderr}`);
22100
22163
  }
22101
22164
  }
22102
- const syncResult = await withTimeout(Bun.$`bd sync`.quiet().nothrow(), TIMEOUT_MS, "bd sync");
22103
- if (syncResult.exitCode !== 0) {
22104
- throw new BeadError(`Failed to sync beads: ${syncResult.stderr.toString()}`, "bd sync", syncResult.exitCode);
22105
- }
22106
- const pushResult = await withTimeout(Bun.$`git push`.quiet().nothrow(), TIMEOUT_MS, "git push");
22165
+ const pushResult = await withTimeout(runGitCommand(["push"]), TIMEOUT_MS, "git push");
22107
22166
  if (pushResult.exitCode !== 0) {
22108
- throw new BeadError(`Failed to push: ${pushResult.stderr.toString()}`, "git push", pushResult.exitCode);
22167
+ throw new BeadError(`Failed to push: ${pushResult.stderr}`, "git push", pushResult.exitCode);
22109
22168
  }
22110
- const statusResult = await Bun.$`git status --porcelain`.quiet().nothrow();
22111
- const status = statusResult.stdout.toString().trim();
22169
+ const statusResult = await runGitCommand(["status", "--porcelain"]);
22170
+ const status = statusResult.stdout.trim();
22112
22171
  if (status !== "") {
22113
22172
  return `Beads synced and pushed, but working directory not clean:
22114
22173
  ${status}`;
@@ -22123,11 +22182,11 @@ var beads_link_thread = tool({
22123
22182
  thread_id: tool.schema.string().describe("Agent Mail thread ID")
22124
22183
  },
22125
22184
  async execute(args, ctx) {
22126
- const queryResult = await Bun.$`bd show ${args.bead_id} --json`.quiet().nothrow();
22185
+ const queryResult = await runBdCommand(["show", args.bead_id, "--json"]);
22127
22186
  if (queryResult.exitCode !== 0) {
22128
- throw new BeadError(`Failed to get bead: ${queryResult.stderr.toString()}`, `bd show ${args.bead_id} --json`, queryResult.exitCode);
22187
+ throw new BeadError(`Failed to get bead: ${queryResult.stderr}`, `bd show ${args.bead_id} --json`, queryResult.exitCode);
22129
22188
  }
22130
- const bead = parseBead(queryResult.stdout.toString());
22189
+ const bead = parseBead(queryResult.stdout);
22131
22190
  const existingDesc = bead.description || "";
22132
22191
  const threadMarker = `[thread:${args.thread_id}]`;
22133
22192
  if (existingDesc.includes(threadMarker)) {
@@ -22136,9 +22195,15 @@ var beads_link_thread = tool({
22136
22195
  const newDesc = existingDesc ? `${existingDesc}
22137
22196
 
22138
22197
  ${threadMarker}` : threadMarker;
22139
- const updateResult = await Bun.$`bd update ${args.bead_id} -d ${newDesc} --json`.quiet().nothrow();
22198
+ const updateResult = await runBdCommand([
22199
+ "update",
22200
+ args.bead_id,
22201
+ "-d",
22202
+ newDesc,
22203
+ "--json"
22204
+ ]);
22140
22205
  if (updateResult.exitCode !== 0) {
22141
- throw new BeadError(`Failed to update bead: ${updateResult.stderr.toString()}`, `bd update ${args.bead_id} -d ...`, updateResult.exitCode);
22206
+ throw new BeadError(`Failed to update bead: ${updateResult.stderr}`, `bd update ${args.bead_id} -d ...`, updateResult.exitCode);
22142
22207
  }
22143
22208
  return `Linked bead ${args.bead_id} to thread ${args.thread_id}`;
22144
22209
  }
@@ -26111,7 +26176,8 @@ class InMemoryMaturityStorage {
26111
26176
 
26112
26177
  // src/index.ts
26113
26178
  var SwarmPlugin = async (input) => {
26114
- const { $ } = input;
26179
+ const { $, directory } = input;
26180
+ setBeadsWorkingDirectory(directory);
26115
26181
  let activeAgentMailState = null;
26116
26182
  async function releaseReservations() {
26117
26183
  if (!activeAgentMailState || activeAgentMailState.reservations.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.12.16",
3
+ "version": "0.12.18",
4
4
  "description": "Multi-agent swarm coordination for OpenCode with learning capabilities, beads integration, and Agent Mail",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/beads.ts CHANGED
@@ -9,9 +9,89 @@
9
9
  * - Validate all output with Zod schemas
10
10
  * - Throw typed errors on failure
11
11
  * - Support atomic epic creation with rollback hints
12
+ *
13
+ * IMPORTANT: Call setBeadsWorkingDirectory() before using tools to ensure
14
+ * bd commands run in the correct project directory.
12
15
  */
13
16
  import { tool } from "@opencode-ai/plugin";
14
17
  import { z } from "zod";
18
+
19
+ // ============================================================================
20
+ // Working Directory Configuration
21
+ // ============================================================================
22
+
23
+ /**
24
+ * Module-level working directory for bd commands.
25
+ * Set this via setBeadsWorkingDirectory() before using tools.
26
+ * If not set, commands run in process.cwd() which may be wrong for plugins.
27
+ */
28
+ let beadsWorkingDirectory: string | null = null;
29
+
30
+ /**
31
+ * Set the working directory for all beads commands.
32
+ * Call this from the plugin initialization with the project directory.
33
+ *
34
+ * @param directory - Absolute path to the project directory
35
+ */
36
+ export function setBeadsWorkingDirectory(directory: string): void {
37
+ beadsWorkingDirectory = directory;
38
+ }
39
+
40
+ /**
41
+ * Get the current working directory for beads commands.
42
+ * Returns the configured directory or process.cwd() as fallback.
43
+ */
44
+ export function getBeadsWorkingDirectory(): string {
45
+ return beadsWorkingDirectory || process.cwd();
46
+ }
47
+
48
+ /**
49
+ * Run a bd command in the correct working directory.
50
+ * Uses Bun.spawn with cwd option to ensure commands run in project directory.
51
+ */
52
+ async function runBdCommand(
53
+ args: string[],
54
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
55
+ const cwd = getBeadsWorkingDirectory();
56
+ const proc = Bun.spawn(["bd", ...args], {
57
+ cwd,
58
+ stdout: "pipe",
59
+ stderr: "pipe",
60
+ });
61
+
62
+ const [stdout, stderr] = await Promise.all([
63
+ new Response(proc.stdout).text(),
64
+ new Response(proc.stderr).text(),
65
+ ]);
66
+
67
+ const exitCode = await proc.exited;
68
+
69
+ return { exitCode, stdout, stderr };
70
+ }
71
+
72
+ /**
73
+ * Run a git command in the correct working directory.
74
+ */
75
+ async function runGitCommand(
76
+ args: string[],
77
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
78
+ const cwd = getBeadsWorkingDirectory();
79
+ const proc = Bun.spawn(["git", ...args], {
80
+ cwd,
81
+ stdout: "pipe",
82
+ stderr: "pipe",
83
+ });
84
+
85
+ const [stdout, stderr] = await Promise.all([
86
+ new Response(proc.stdout).text(),
87
+ new Response(proc.stderr).text(),
88
+ ]);
89
+
90
+ const exitCode = await proc.exited;
91
+
92
+ return { exitCode, stdout, stderr };
93
+ }
94
+
15
95
  import {
16
96
  BeadSchema,
17
97
  BeadCreateArgsSchema,
@@ -159,20 +239,20 @@ export const beads_create = tool({
159
239
  const validated = BeadCreateArgsSchema.parse(args);
160
240
  const cmdParts = buildCreateCommand(validated);
161
241
 
162
- // Execute command
163
- const result = await Bun.$`${cmdParts}`.quiet().nothrow();
242
+ // Execute command in the correct working directory
243
+ const result = await runBdCommand(cmdParts.slice(1)); // Remove 'bd' prefix
164
244
 
165
245
  if (result.exitCode !== 0) {
166
246
  throw new BeadError(
167
- `Failed to create bead: ${result.stderr.toString()}`,
247
+ `Failed to create bead: ${result.stderr}`,
168
248
  cmdParts.join(" "),
169
249
  result.exitCode,
170
- result.stderr.toString(),
250
+ result.stderr,
171
251
  );
172
252
  }
173
253
 
174
254
  // Validate output before parsing
175
- const stdout = result.stdout.toString().trim();
255
+ const stdout = result.stdout.trim();
176
256
  if (!stdout) {
177
257
  throw new BeadError(
178
258
  "bd create returned empty output",
@@ -231,17 +311,17 @@ export const beads_create_epic = tool({
231
311
  description: validated.epic_description,
232
312
  });
233
313
 
234
- const epicResult = await Bun.$`${epicCmd}`.quiet().nothrow();
314
+ const epicResult = await runBdCommand(epicCmd.slice(1)); // Remove 'bd' prefix
235
315
 
236
316
  if (epicResult.exitCode !== 0) {
237
317
  throw new BeadError(
238
- `Failed to create epic: ${epicResult.stderr.toString()}`,
318
+ `Failed to create epic: ${epicResult.stderr}`,
239
319
  epicCmd.join(" "),
240
320
  epicResult.exitCode,
241
321
  );
242
322
  }
243
323
 
244
- const epic = parseBead(epicResult.stdout.toString());
324
+ const epic = parseBead(epicResult.stdout);
245
325
  created.push(epic);
246
326
 
247
327
  // 2. Create subtasks
@@ -253,17 +333,17 @@ export const beads_create_epic = tool({
253
333
  parent_id: epic.id,
254
334
  });
255
335
 
256
- const subtaskResult = await Bun.$`${subtaskCmd}`.quiet().nothrow();
336
+ const subtaskResult = await runBdCommand(subtaskCmd.slice(1)); // Remove 'bd' prefix
257
337
 
258
338
  if (subtaskResult.exitCode !== 0) {
259
339
  throw new BeadError(
260
- `Failed to create subtask: ${subtaskResult.stderr.toString()}`,
340
+ `Failed to create subtask: ${subtaskResult.stderr}`,
261
341
  subtaskCmd.join(" "),
262
342
  subtaskResult.exitCode,
263
343
  );
264
344
  }
265
345
 
266
- const subtaskBead = parseBead(subtaskResult.stdout.toString());
346
+ const subtaskBead = parseBead(subtaskResult.stdout);
267
347
  created.push(subtaskBead);
268
348
  }
269
349
 
@@ -281,22 +361,21 @@ export const beads_create_epic = tool({
281
361
 
282
362
  for (const bead of created) {
283
363
  try {
284
- const closeCmd = [
285
- "bd",
364
+ const closeArgs = [
286
365
  "close",
287
366
  bead.id,
288
367
  "--reason",
289
368
  "Rollback partial epic",
290
369
  "--json",
291
370
  ];
292
- const rollbackResult = await Bun.$`${closeCmd}`.quiet().nothrow();
371
+ const rollbackResult = await runBdCommand(closeArgs);
293
372
  if (rollbackResult.exitCode === 0) {
294
373
  rollbackCommands.push(
295
374
  `bd close ${bead.id} --reason "Rollback partial epic"`,
296
375
  );
297
376
  } else {
298
377
  rollbackErrors.push(
299
- `${bead.id}: exit ${rollbackResult.exitCode} - ${rollbackResult.stderr.toString().trim()}`,
378
+ `${bead.id}: exit ${rollbackResult.exitCode} - ${rollbackResult.stderr.trim()}`,
300
379
  );
301
380
  }
302
381
  } catch (rollbackError) {
@@ -375,17 +454,17 @@ export const beads_query = tool({
375
454
  }
376
455
  }
377
456
 
378
- const result = await Bun.$`${cmd}`.quiet().nothrow();
457
+ const result = await runBdCommand(cmd.slice(1)); // Remove 'bd' prefix
379
458
 
380
459
  if (result.exitCode !== 0) {
381
460
  throw new BeadError(
382
- `Failed to query beads: ${result.stderr.toString()}`,
461
+ `Failed to query beads: ${result.stderr}`,
383
462
  cmd.join(" "),
384
463
  result.exitCode,
385
464
  );
386
465
  }
387
466
 
388
- const beads = parseBeads(result.stdout.toString());
467
+ const beads = parseBeads(result.stdout);
389
468
  const limited = beads.slice(0, validated.limit);
390
469
 
391
470
  return JSON.stringify(limited, null, 2);
@@ -427,17 +506,17 @@ export const beads_update = tool({
427
506
  }
428
507
  cmd.push("--json");
429
508
 
430
- const result = await Bun.$`${cmd}`.quiet().nothrow();
509
+ const result = await runBdCommand(cmd.slice(1)); // Remove 'bd' prefix
431
510
 
432
511
  if (result.exitCode !== 0) {
433
512
  throw new BeadError(
434
- `Failed to update bead: ${result.stderr.toString()}`,
513
+ `Failed to update bead: ${result.stderr}`,
435
514
  cmd.join(" "),
436
515
  result.exitCode,
437
516
  );
438
517
  }
439
518
 
440
- const bead = parseBead(result.stdout.toString());
519
+ const bead = parseBead(result.stdout);
441
520
  return JSON.stringify(bead, null, 2);
442
521
  },
443
522
  });
@@ -463,17 +542,17 @@ export const beads_close = tool({
463
542
  "--json",
464
543
  ];
465
544
 
466
- const result = await Bun.$`${cmd}`.quiet().nothrow();
545
+ const result = await runBdCommand(cmd.slice(1)); // Remove 'bd' prefix
467
546
 
468
547
  if (result.exitCode !== 0) {
469
548
  throw new BeadError(
470
- `Failed to close bead: ${result.stderr.toString()}`,
549
+ `Failed to close bead: ${result.stderr}`,
471
550
  cmd.join(" "),
472
551
  result.exitCode,
473
552
  );
474
553
  }
475
554
 
476
- const bead = parseBead(result.stdout.toString());
555
+ const bead = parseBead(result.stdout);
477
556
  return `Closed ${bead.id}: ${validated.reason}`;
478
557
  },
479
558
  });
@@ -488,19 +567,23 @@ export const beads_start = tool({
488
567
  id: tool.schema.string().describe("Bead ID"),
489
568
  },
490
569
  async execute(args, ctx) {
491
- const cmd = ["bd", "update", args.id, "--status", "in_progress", "--json"];
492
-
493
- const result = await Bun.$`${cmd}`.quiet().nothrow();
570
+ const result = await runBdCommand([
571
+ "update",
572
+ args.id,
573
+ "--status",
574
+ "in_progress",
575
+ "--json",
576
+ ]);
494
577
 
495
578
  if (result.exitCode !== 0) {
496
579
  throw new BeadError(
497
- `Failed to start bead: ${result.stderr.toString()}`,
498
- cmd.join(" "),
580
+ `Failed to start bead: ${result.stderr}`,
581
+ `bd update ${args.id} --status in_progress --json`,
499
582
  result.exitCode,
500
583
  );
501
584
  }
502
585
 
503
- const bead = parseBead(result.stdout.toString());
586
+ const bead = parseBead(result.stdout);
504
587
  return `Started: ${bead.id}`;
505
588
  },
506
589
  });
@@ -512,19 +595,17 @@ export const beads_ready = tool({
512
595
  description: "Get the next ready bead (unblocked, highest priority)",
513
596
  args: {},
514
597
  async execute(args, ctx) {
515
- const cmd = ["bd", "ready", "--json"];
516
-
517
- const result = await Bun.$`${cmd}`.quiet().nothrow();
598
+ const result = await runBdCommand(["ready", "--json"]);
518
599
 
519
600
  if (result.exitCode !== 0) {
520
601
  throw new BeadError(
521
- `Failed to get ready beads: ${result.stderr.toString()}`,
522
- cmd.join(" "),
602
+ `Failed to get ready beads: ${result.stderr}`,
603
+ "bd ready --json",
523
604
  result.exitCode,
524
605
  );
525
606
  }
526
607
 
527
- const beads = parseBeads(result.stdout.toString());
608
+ const beads = parseBeads(result.stdout);
528
609
 
529
610
  if (beads.length === 0) {
530
611
  return "No ready beads";
@@ -583,53 +664,101 @@ export const beads_sync = tool({
583
664
  }
584
665
  };
585
666
 
586
- // 1. Pull if requested
667
+ // 1. Flush beads to JSONL (doesn't use worktrees)
668
+ const flushResult = await withTimeout(
669
+ runBdCommand(["sync", "--flush-only"]),
670
+ TIMEOUT_MS,
671
+ "bd sync --flush-only",
672
+ );
673
+ if (flushResult.exitCode !== 0) {
674
+ throw new BeadError(
675
+ `Failed to flush beads: ${flushResult.stderr}`,
676
+ "bd sync --flush-only",
677
+ flushResult.exitCode,
678
+ );
679
+ }
680
+
681
+ // 2. Check if there are changes to commit
682
+ const beadsStatusResult = await runGitCommand([
683
+ "status",
684
+ "--porcelain",
685
+ ".beads/",
686
+ ]);
687
+ const hasChanges = beadsStatusResult.stdout.trim() !== "";
688
+
689
+ if (hasChanges) {
690
+ // 3. Stage .beads changes
691
+ const addResult = await runGitCommand(["add", ".beads/"]);
692
+ if (addResult.exitCode !== 0) {
693
+ throw new BeadError(
694
+ `Failed to stage beads: ${addResult.stderr}`,
695
+ "git add .beads/",
696
+ addResult.exitCode,
697
+ );
698
+ }
699
+
700
+ // 4. Commit
701
+ const commitResult = await withTimeout(
702
+ runGitCommand(["commit", "-m", "chore: sync beads"]),
703
+ TIMEOUT_MS,
704
+ "git commit",
705
+ );
706
+ if (
707
+ commitResult.exitCode !== 0 &&
708
+ !commitResult.stdout.includes("nothing to commit")
709
+ ) {
710
+ throw new BeadError(
711
+ `Failed to commit beads: ${commitResult.stderr}`,
712
+ "git commit",
713
+ commitResult.exitCode,
714
+ );
715
+ }
716
+ }
717
+
718
+ // 5. Pull if requested (with rebase to avoid merge commits)
587
719
  if (autoPull) {
588
720
  const pullResult = await withTimeout(
589
- Bun.$`git pull --rebase`.quiet().nothrow(),
721
+ runGitCommand(["pull", "--rebase"]),
590
722
  TIMEOUT_MS,
591
723
  "git pull --rebase",
592
724
  );
593
725
  if (pullResult.exitCode !== 0) {
594
726
  throw new BeadError(
595
- `Failed to pull: ${pullResult.stderr.toString()}`,
727
+ `Failed to pull: ${pullResult.stderr}`,
596
728
  "git pull --rebase",
597
729
  pullResult.exitCode,
598
730
  );
599
731
  }
600
- }
601
732
 
602
- // 2. Sync beads
603
- const syncResult = await withTimeout(
604
- Bun.$`bd sync`.quiet().nothrow(),
605
- TIMEOUT_MS,
606
- "bd sync",
607
- );
608
- if (syncResult.exitCode !== 0) {
609
- throw new BeadError(
610
- `Failed to sync beads: ${syncResult.stderr.toString()}`,
611
- "bd sync",
612
- syncResult.exitCode,
733
+ // 6. Import any changes from remote
734
+ const importResult = await withTimeout(
735
+ runBdCommand(["sync", "--import-only"]),
736
+ TIMEOUT_MS,
737
+ "bd sync --import-only",
613
738
  );
739
+ if (importResult.exitCode !== 0) {
740
+ // Non-fatal - just log warning
741
+ console.warn(`[beads] Import warning: ${importResult.stderr}`);
742
+ }
614
743
  }
615
744
 
616
- // 3. Push
745
+ // 7. Push
617
746
  const pushResult = await withTimeout(
618
- Bun.$`git push`.quiet().nothrow(),
747
+ runGitCommand(["push"]),
619
748
  TIMEOUT_MS,
620
749
  "git push",
621
750
  );
622
751
  if (pushResult.exitCode !== 0) {
623
752
  throw new BeadError(
624
- `Failed to push: ${pushResult.stderr.toString()}`,
753
+ `Failed to push: ${pushResult.stderr}`,
625
754
  "git push",
626
755
  pushResult.exitCode,
627
756
  );
628
757
  }
629
758
 
630
759
  // 4. Verify clean state
631
- const statusResult = await Bun.$`git status --porcelain`.quiet().nothrow();
632
- const status = statusResult.stdout.toString().trim();
760
+ const statusResult = await runGitCommand(["status", "--porcelain"]);
761
+ const status = statusResult.stdout.trim();
633
762
 
634
763
  if (status !== "") {
635
764
  return `Beads synced and pushed, but working directory not clean:\n${status}`;
@@ -651,19 +780,17 @@ export const beads_link_thread = tool({
651
780
  async execute(args, ctx) {
652
781
  // Update bead description to include thread link
653
782
  // This is a workaround since bd doesn't have native metadata support
654
- const queryResult = await Bun.$`bd show ${args.bead_id} --json`
655
- .quiet()
656
- .nothrow();
783
+ const queryResult = await runBdCommand(["show", args.bead_id, "--json"]);
657
784
 
658
785
  if (queryResult.exitCode !== 0) {
659
786
  throw new BeadError(
660
- `Failed to get bead: ${queryResult.stderr.toString()}`,
787
+ `Failed to get bead: ${queryResult.stderr}`,
661
788
  `bd show ${args.bead_id} --json`,
662
789
  queryResult.exitCode,
663
790
  );
664
791
  }
665
792
 
666
- const bead = parseBead(queryResult.stdout.toString());
793
+ const bead = parseBead(queryResult.stdout);
667
794
  const existingDesc = bead.description || "";
668
795
 
669
796
  // Add thread link if not already present
@@ -676,14 +803,17 @@ export const beads_link_thread = tool({
676
803
  ? `${existingDesc}\n\n${threadMarker}`
677
804
  : threadMarker;
678
805
 
679
- const updateResult =
680
- await Bun.$`bd update ${args.bead_id} -d ${newDesc} --json`
681
- .quiet()
682
- .nothrow();
806
+ const updateResult = await runBdCommand([
807
+ "update",
808
+ args.bead_id,
809
+ "-d",
810
+ newDesc,
811
+ "--json",
812
+ ]);
683
813
 
684
814
  if (updateResult.exitCode !== 0) {
685
815
  throw new BeadError(
686
- `Failed to update bead: ${updateResult.stderr.toString()}`,
816
+ `Failed to update bead: ${updateResult.stderr}`,
687
817
  `bd update ${args.bead_id} -d ...`,
688
818
  updateResult.exitCode,
689
819
  );
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import type { Plugin, PluginInput, Hooks } from "@opencode-ai/plugin";
24
24
 
25
- import { beadsTools } from "./beads";
25
+ import { beadsTools, setBeadsWorkingDirectory } from "./beads";
26
26
  import {
27
27
  agentMailTools,
28
28
  type AgentMailState,
@@ -48,7 +48,11 @@ import { repoCrawlTools } from "./repo-crawl";
48
48
  export const SwarmPlugin: Plugin = async (
49
49
  input: PluginInput,
50
50
  ): Promise<Hooks> => {
51
- const { $ } = input;
51
+ const { $, directory } = input;
52
+
53
+ // Set the working directory for beads commands
54
+ // This ensures bd runs in the project directory, not ~/.config/opencode
55
+ setBeadsWorkingDirectory(directory);
52
56
 
53
57
  /** Track active sessions for cleanup */
54
58
  let activeAgentMailState: AgentMailState | null = null;