feat: multi-work is able to delegate to root
CI / verify (pull_request) Successful in 9s

This commit is contained in:
2026-09-18 20:13:54 +02:00
parent 8b6836f5a4
commit 52e98c9d78
9 changed files with 376 additions and 88 deletions
+151 -1
View File
@@ -14,6 +14,8 @@ import { bindingKey, bindingSchema, installMultiWork } from "../src/multi-work.j
class FixtureHerdr extends Herdr {
eligible = true;
rootWorkspaceID = "w1";
rootRepo = "/root/.git";
creations = 0;
verified = 0;
delayCreation = false;
@@ -24,7 +26,7 @@ class FixtureHerdr extends Herdr {
creationStarted = Promise.withResolvers<void>();
override async root(directory: string) {
return this.eligible && directory === "/root"
? { directory, workspaceID: "w1", repo: "/root/.git" }
? { directory, workspaceID: this.rootWorkspaceID, repo: this.rootRepo }
: undefined;
}
override async create(
@@ -366,6 +368,128 @@ test("local fast-lo uses native delegation without a worktree", async () => {
);
});
test("coordinate can create and resume a root-local background child without Herdr creation or movement", async () => {
const f = fixture();
f.parent.permissions = [{ action: "herdr_worktree", resource: "*", effect: "deny" }];
await Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
yield* f.install;
const created = yield* f.execute({
agent: "coordinate",
description: "local edit",
prompt: "edit assigned files",
background: true,
});
expect(created.metadata).toMatchObject({
sessionID: "ses_child1",
status: "running",
directory: "/root",
workspaceID: "w1",
branch: "root",
});
expect(f.herdr.creations).toBe(0);
expect(f.order).toEqual(["create", "prompt:/root"]);
expect(bindingSchema.parse(f.storage.get(bindingKey("ses_child1")))).toMatchObject({
owner: "ses_parent",
directory: "/root",
workspaceID: "w1",
branch: "root",
ready: true,
});
yield* f.execute({
agent: "coordinate",
description: "resume local",
prompt: "continue",
sessionID: "ses_child1",
});
expect(f.herdr.creations).toBe(0);
expect(f.herdr.verified).toBe(0);
}),
),
);
});
test("root-local resume rejects location, binding, and live-root changes", async () => {
for (const tamper of ["session", "workspace", "root", "branch", "live-root"] as const) {
const f = fixture();
await Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
yield* f.install;
yield* f.execute({
agent: "coordinate",
description: "local edit",
prompt: "work",
});
if (tamper === "session")
f.sessions.get("ses_child1")!.location.directory = "/other";
else if (tamper === "live-root") f.herdr.rootWorkspaceID = "foreign";
else {
const binding = bindingSchema.parse(
f.storage.get(bindingKey("ses_child1")),
);
const changed = { ...binding };
if (tamper === "workspace") changed.workspaceID = "foreign";
if (tamper === "root") {
changed.root = { ...binding.root, workspaceID: "foreign" };
changed.workspaceID = "foreign";
}
if (tamper === "branch") changed.branch = "main";
f.storage.set(bindingKey("ses_child1"), changed);
}
expect(
(yield* Effect.exit(
f.execute({
agent: "coordinate",
description: "resume",
prompt: "continue",
sessionID: "ses_child1",
}),
))._tag,
).toBe("Failure");
expect(f.prompts).toBe(1);
}),
),
);
}
});
test("interrupted root-local creation retains its binding and cannot be blindly replayed", async () => {
const f = fixture();
f.hold();
const controller = new AbortController();
const running = Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
yield* f.install;
return yield* f.execute({
agent: "coordinate",
description: "local edit",
prompt: "work",
});
}),
),
{ signal: controller.signal },
);
await f.entered.promise;
controller.abort();
await expect(running).rejects.toBeDefined();
expect(bindingSchema.parse(f.storage.get(bindingKey("ses_child1"))).ready).toBe(true);
expect(f.storage.has(`multi-work-call:${f.call.sessionID}:${f.call.id}`)).toBe(true);
const replay = await Effect.runPromise(
Effect.exit(
f.execute({
agent: "coordinate",
description: "local edit",
prompt: "work",
}),
),
);
expect(replay._tag).toBe("Failure");
expect(f.prompts).toBe(1);
});
test("custom branch and workspace label pass through delegation", async () => {
const f = fixture();
await Effect.runPromise(
@@ -623,6 +747,32 @@ test("configured build restrictions remain effective for worktree preparation",
);
});
test("worktree resumes retain the herdr_worktree permission guard", async () => {
const f = fixture();
await Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
yield* f.install;
yield* f.invoke();
f.parent.permissions = [
{ action: "herdr_worktree", resource: "*", effect: "deny" },
];
const resumed = yield* Effect.exit(
f.execute({
agent: "coordinate",
description: "resume",
prompt: "continue",
sessionID: "ses_child1",
}),
);
expect(resumed._tag).toBe("Failure");
expect(f.herdr.verified).toBe(0);
expect(f.prompts).toBe(1);
}),
),
);
});
test("failed initialization records the attempt and never blindly recreates on replay", async () => {
const f = fixture();
f.missingDestination();