agent-relay-orchestrator 0.36.2 → 0.38.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-orchestrator",
3
- "version": "0.36.2",
3
+ "version": "0.38.0",
4
4
  "description": "Agent Relay orchestrator — manages agent lifecycle across hosts",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "test": "bun test"
17
17
  },
18
18
  "dependencies": {
19
- "agent-relay-sdk": "0.2.22"
19
+ "agent-relay-sdk": "0.2.23"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/bun": "latest",
package/src/control.ts CHANGED
@@ -120,6 +120,7 @@ export function createControlHandler(
120
120
  push: command.params.push !== false,
121
121
  prTitle: typeof command.params.prTitle === "string" ? command.params.prTitle : undefined,
122
122
  prBody: typeof command.params.prBody === "string" ? command.params.prBody : undefined,
123
+ autoMerge: command.params.autoMerge === "on-green" || command.params.autoMerge === "on-approval" || command.params.autoMerge === "manual" ? command.params.autoMerge : undefined,
123
124
  });
124
125
  await relay.updateCommand(command.id, "succeeded", result as unknown as Record<string, unknown>);
125
126
  } else if (command.type === "workspace.deps-refresh") {
@@ -715,6 +715,11 @@ interface WorkspaceMergeInput {
715
715
  push?: boolean;
716
716
  prTitle?: string;
717
717
  prBody?: string;
718
+ /** Auto-merge policy for pr-strategy lands (#305).
719
+ * - "on-green": arm GitHub auto-merge after PR creation (default).
720
+ * - "on-approval": open PR, do NOT arm; reviewer pipeline arms it later.
721
+ * - "manual": open PR, do NOT arm (today's legacy behavior). */
722
+ autoMerge?: "on-green" | "on-approval" | "manual";
718
723
  }
719
724
 
720
725
  /** Behind-count of HEAD relative to `base`, from inside `worktreePath`. */
@@ -913,7 +918,12 @@ export function mergeWorkspace(input: WorkspaceMergeInput): WorkspaceMergeResult
913
918
  if (!input.worktreePath) return { strategy: "rebase-ff", merged: false, status: "review_requested", error: "worktreePath required", workspaceId: input.id };
914
919
  const worktreePath = resolve(input.worktreePath);
915
920
  const repoRoot = input.repoRoot ? resolve(input.repoRoot) : worktreePath;
916
- const branch = input.branch ?? shortBranch(git(["symbolic-ref", "--quiet", "--short", "HEAD"], worktreePath).stdout || undefined);
921
+ // Probe the live HEAD branch first it's the authoritative source. Fall back to the
922
+ // DB-recorded branch only when the live probe fails (detached HEAD, missing worktree, etc.).
923
+ // This fixes #232: a stale DB branch value (non-null mismatch) would pass through the
924
+ // `input.branch ?? ...` guard unchanged and cause git to attempt merging a non-existent ref.
925
+ const liveBranch = shortBranch(git(["symbolic-ref", "--quiet", "--short", "HEAD"], worktreePath).stdout || undefined);
926
+ const branch = liveBranch ?? input.branch;
917
927
  const preview = previewWorkspaceMerge({ worktreePath, baseRef: input.baseRef, baseSha: input.baseSha, strategy: input.strategy });
918
928
  const strategy = preview.strategy;
919
929
  const head = (field: Partial<WorkspaceMergeResult>): WorkspaceMergeResult => ({ workspaceId: input.id, strategy, merged: false, status: "review_requested", branch, baseRef: preview.baseRef, ...field });
@@ -995,15 +1005,45 @@ function mergePr(
995
1005
  const body = input.prBody || `Automated PR for agent workspace branch \`${branch}\`.`;
996
1006
  const args = ["pr", "create", "--head", branch, "--title", title, "--body", body];
997
1007
  if (base) args.push("--base", base);
998
- const proc = Bun.spawnSync(["gh", ...args], { cwd: worktreePath, stdin: "ignore", stdout: "pipe", stderr: "pipe" });
1008
+ // Pass process.env explicitly so runtime env mutations (e.g. test PATH injection)
1009
+ // are visible to the child process. Bun's default is the startup-time env snapshot.
1010
+ const proc = Bun.spawnSync(["gh", ...args], { cwd: worktreePath, stdin: "ignore", stdout: "pipe", stderr: "pipe", env: process.env });
999
1011
  const stdout = proc.stdout.toString().trim();
1000
1012
  if (proc.exitCode !== 0) {
1001
1013
  return head({ status: "review_requested", error: proc.stderr.toString().trim() || "gh pr create failed" });
1002
1014
  }
1003
1015
  const prUrl = stdout.split("\n").map((line) => line.trim()).find((line) => /^https?:\/\//.test(line));
1004
- // PR opened: work is on its way out but not landed. Keep the worktree/branch
1005
- // alive for the PR; record the plan with the URL.
1006
- return head({ status: "merge_planned", prUrl, error: undefined });
1016
+
1017
+ // Auto-merge policy (#305). Treat absent as "on-green" so new lands always terminate.
1018
+ const autoMerge = input.autoMerge ?? "on-green";
1019
+
1020
+ if (autoMerge === "on-approval") {
1021
+ // Reviewer pipeline arms auto-merge later — don't arm here.
1022
+ return head({ status: "merge_planned", prUrl, awaitingApproval: true, error: undefined });
1023
+ }
1024
+
1025
+ if (autoMerge === "manual") {
1026
+ // Legacy behavior: open the PR and stop — a human merges.
1027
+ return head({ status: "merge_planned", prUrl, error: undefined });
1028
+ }
1029
+
1030
+ // "on-green" (default): arm GitHub auto-merge. Use --merge (repo's merge style).
1031
+ // Never throw — if arming fails (repo has auto-merge disabled) we still return
1032
+ // merge_planned so the relay reconcile scan can finalize when the PR merges.
1033
+ const mergeTarget = prUrl ?? branch!;
1034
+ const armProc = Bun.spawnSync(["gh", "pr", "merge", mergeTarget, "--auto", "--merge"], {
1035
+ cwd: worktreePath,
1036
+ stdin: "ignore",
1037
+ stdout: "pipe",
1038
+ stderr: "pipe",
1039
+ env: process.env,
1040
+ });
1041
+ if (armProc.exitCode !== 0) {
1042
+ // Arm failed (e.g. repo has auto-merge disabled) — return merge_planned so the
1043
+ // reconcile scan still finalizes when the PR is merged by a human.
1044
+ return head({ status: "merge_planned", prUrl, autoMergeArmed: false, error: undefined });
1045
+ }
1046
+ return head({ status: "merge_planned", prUrl, autoMergeArmed: true, error: undefined });
1007
1047
  }
1008
1048
 
1009
1049
  // Identity stamped on the merge commit a no-ff land records (#287). The merge is a