agent-relay-orchestrator 0.35.1 → 0.35.3
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 +1 -1
- package/src/control.ts +1 -0
- package/src/spawn.ts +8 -1
- package/src/workspace-probe.ts +40 -9
package/package.json
CHANGED
package/src/control.ts
CHANGED
|
@@ -216,6 +216,7 @@ function spawnOptionsFromRecord(source: Record<string, any>, config: Orchestrato
|
|
|
216
216
|
spawnRequestId: typeof source.spawnRequestId === "string" ? source.spawnRequestId : undefined,
|
|
217
217
|
automationId: typeof source.automationId === "string" ? source.automationId : undefined,
|
|
218
218
|
automationRunId: typeof source.automationRunId === "string" ? source.automationRunId : undefined,
|
|
219
|
+
requestedVia: typeof source.requestedVia === "string" ? source.requestedVia : undefined,
|
|
219
220
|
};
|
|
220
221
|
}
|
|
221
222
|
|
package/src/spawn.ts
CHANGED
|
@@ -37,6 +37,9 @@ export interface SpawnOptions {
|
|
|
37
37
|
spawnRequestId?: string;
|
|
38
38
|
automationId?: string;
|
|
39
39
|
automationRunId?: string;
|
|
40
|
+
/** How the spawn was requested (`mcp` = an agent via the MCP surface, else dashboard/CLI). Drives
|
|
41
|
+
* the origin tag so an MCP-spawned worker isn't mislabeled `dashboard-spawned` (#330). */
|
|
42
|
+
requestedVia?: string;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
interface SessionInfo {
|
|
@@ -222,7 +225,11 @@ export function buildEnv(opts: SpawnOptions & { label: string; agentId: string }
|
|
|
222
225
|
AGENT_RELAY_APPROVAL: opts.approvalMode || "guarded",
|
|
223
226
|
...(opts.profile ? { AGENT_RELAY_AGENT_PROFILE: opts.profile } : {}),
|
|
224
227
|
...(opts.agentProfile ? { AGENT_RELAY_AGENT_PROFILE_JSON: JSON.stringify(opts.agentProfile) } : {}),
|
|
225
|
-
|
|
228
|
+
// #330 — tag by TRUE origin. An MCP spawn (an agent spawning a helper) is `agent-spawned`, not
|
|
229
|
+
// `dashboard-spawned`; the old blanket `dashboard-spawned` mislabeled every headless spawn as
|
|
230
|
+
// dashboard-originated. Dashboard/CLI/automation spawns (no `requestedVia: "mcp"`) keep the
|
|
231
|
+
// `dashboard-spawned` tag the smoke test and UI filter on.
|
|
232
|
+
AGENT_RELAY_TAGS: [...new Set(["headless", opts.requestedVia === "mcp" ? "agent-spawned" : "dashboard-spawned", config.hostname, ...(opts.tags ?? [])])].join(","),
|
|
226
233
|
AGENT_RELAY_CAPS: [...new Set(opts.capabilities ?? [])].join(","),
|
|
227
234
|
AGENT_RELAY_CAPABILITIES: [...new Set(opts.capabilities ?? [])].join(","),
|
|
228
235
|
AGENT_RELAY_HEADLESS: "1",
|
package/src/workspace-probe.ts
CHANGED
|
@@ -90,6 +90,14 @@ export async function resolveSpawnWorkspace(input: WorkspaceResolutionInput): Pr
|
|
|
90
90
|
const requestedMode = input.workspaceMode ?? "inherit";
|
|
91
91
|
const sourceCwd = resolve(input.cwd);
|
|
92
92
|
const probe = await probeWorkspace(sourceCwd);
|
|
93
|
+
// #328 backstop — an EXPLICIT `isolated` request needs a git repo to branch off. Without one,
|
|
94
|
+
// the old code silently downgraded to `shared` (no branch, no worktree, no signal), so a worker
|
|
95
|
+
// asked for an isolated branch instead edited a live tree. Fail loud and actionable instead of
|
|
96
|
+
// fabricating a different mode than the caller asked for. (`inherit`→isolated, from a policy or
|
|
97
|
+
// automation in a non-repo dir, keeps its legitimate shared fallback below.)
|
|
98
|
+
if (requestedMode === "isolated" && !probe.isGitRepo) {
|
|
99
|
+
throw new Error(`workspaceMode "isolated" requires a git repo, but ${sourceCwd} is not one. Pass an explicit cwd under a repo, or omit cwd to inherit the caller's.`);
|
|
100
|
+
}
|
|
93
101
|
const inheritedMode = input.policyName || input.automationRunId ? "isolated" : "shared";
|
|
94
102
|
const mode: WorkspaceMode = requestedMode === "inherit" ? inheritedMode : requestedMode;
|
|
95
103
|
if (mode !== "isolated" || !probe.isGitRepo || !probe.repoRoot) {
|
|
@@ -915,7 +923,7 @@ export function mergeWorkspace(input: WorkspaceMergeInput): WorkspaceMergeResult
|
|
|
915
923
|
// Nothing to land (ahead=0, clean): the branch tree is already in base. Resolve it
|
|
916
924
|
// to a terminal state so it leaves the steward queue instead of looping forever in
|
|
917
925
|
// review_requested (#230). Reclaim the spent worktree/branch when the owner is gone.
|
|
918
|
-
if (preview.noop) return resolveNoopMerge(input, worktreePath, repoRoot, branch, head);
|
|
926
|
+
if (preview.noop) return resolveNoopMerge(input, worktreePath, repoRoot, branch, preview, head);
|
|
919
927
|
if (preview.reason) return head({ status: "review_requested", error: preview.reason });
|
|
920
928
|
if (preview.conflict) return head({ conflict: true, status: "conflict", error: "merge would conflict with base" });
|
|
921
929
|
|
|
@@ -924,22 +932,45 @@ export function mergeWorkspace(input: WorkspaceMergeInput): WorkspaceMergeResult
|
|
|
924
932
|
}
|
|
925
933
|
|
|
926
934
|
/**
|
|
927
|
-
* Resolve a no-op land (#230): ahead=0 with a clean worktree, so the branch's tree
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
935
|
+
* Resolve a no-op land (#230): ahead=0 with a clean worktree, so the branch's tree is
|
|
936
|
+
* already contained in base. Nothing to merge; no unmerged work to lose either way.
|
|
937
|
+
* Liveness splits the outcome, mirroring the real-land path in {@link mergeRebaseFf}:
|
|
938
|
+
* - Live owner (`deleteBranch === false`, from owner liveness #204): recycle onto a
|
|
939
|
+
* fresh branch cut from base and return to `active` (land-and-continue, #206). A noop
|
|
940
|
+
* land must NOT brick a still-connected session — terminal `merged` here strands the
|
|
941
|
+
* agent with no checkout once cleanup reclaims the worktree (#327).
|
|
942
|
+
* - Gone owner: reclaim the spent worktree/branch and go terminal `merged`.
|
|
934
943
|
*/
|
|
935
944
|
function resolveNoopMerge(
|
|
936
945
|
input: WorkspaceMergeInput,
|
|
937
946
|
worktreePath: string,
|
|
938
947
|
repoRoot: string,
|
|
939
948
|
branch: string | undefined,
|
|
949
|
+
preview: WorkspaceMergePreview,
|
|
940
950
|
head: (field: Partial<WorkspaceMergeResult>) => WorkspaceMergeResult,
|
|
941
951
|
): WorkspaceMergeResult {
|
|
942
|
-
|
|
952
|
+
// Live owner (#327): recycle-to-continue instead of bricking the session.
|
|
953
|
+
if (input.deleteBranch === false) {
|
|
954
|
+
const base = preview.baseRef;
|
|
955
|
+
if (base && branch) {
|
|
956
|
+
const fresh = nextBranchName(repoRoot, branch);
|
|
957
|
+
if (git(["checkout", "-B", fresh, base], worktreePath).ok) {
|
|
958
|
+
// Old branch's tree is already in base (that's what noop means) — safe to drop.
|
|
959
|
+
const oldDeleted = git(["branch", "-D", branch], repoRoot).ok;
|
|
960
|
+
const baseSha = git(["rev-parse", base], worktreePath).stdout || undefined;
|
|
961
|
+
// Recycled onto base, which may declare deps the symlinked node_modules lacks (#51).
|
|
962
|
+
const depsRefresh = refreshWorkspaceDeps(repoRoot, worktreePath);
|
|
963
|
+
const reportDeps = depsRefresh.refreshed || depsRefresh.stale || depsRefresh.error;
|
|
964
|
+
// merged:false → no `branch.landed` notice (nothing landed); newBranch makes the
|
|
965
|
+
// relay repoint the row and return it to `active` rather than terminal `merged`.
|
|
966
|
+
return head({ merged: false, noop: true, status: "active", baseSha, worktreeRemoved: false, branch: fresh, newBranch: fresh, branchDeleted: oldDeleted, ...(reportDeps ? { depsRefresh } : {}), error: undefined });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// No base or checkout failed — stay live on the current branch, don't strand at `merged`.
|
|
970
|
+
return head({ merged: false, noop: true, status: "active", worktreeRemoved: false, branchDeleted: false, error: undefined });
|
|
971
|
+
}
|
|
972
|
+
// Owner is gone — reclaim the spent worktree/branch and go terminal.
|
|
973
|
+
if (branch) {
|
|
943
974
|
const removed = git(["worktree", "remove", "--force", worktreePath], repoRoot);
|
|
944
975
|
const worktreeRemoved = removed.ok;
|
|
945
976
|
const branchDeleted = worktreeRemoved ? git(["branch", "-D", branch], repoRoot).ok : false;
|