This commit is contained in:
+87
-51
@@ -9,16 +9,24 @@ import { randomUUID } from "node:crypto";
|
||||
import { Herdr, rootSchema } from "./herdr.js";
|
||||
import { multiWorkGuide } from "./routing.js";
|
||||
|
||||
export const bindingSchema = z.object({
|
||||
owner: z.string(),
|
||||
child: z.string(),
|
||||
projectID: z.string(),
|
||||
root: rootSchema,
|
||||
directory: z.string(),
|
||||
workspaceID: z.string(),
|
||||
branch: z.string(),
|
||||
ready: z.boolean(),
|
||||
});
|
||||
export const bindingSchema = z
|
||||
.object({
|
||||
owner: z.string(),
|
||||
child: z.string(),
|
||||
projectID: z.string(),
|
||||
root: rootSchema,
|
||||
directory: z.string(),
|
||||
workspaceID: z.string(),
|
||||
branch: z.string(),
|
||||
ready: z.boolean(),
|
||||
})
|
||||
.refine(
|
||||
(binding) =>
|
||||
binding.directory === binding.root.directory
|
||||
? binding.workspaceID === binding.root.workspaceID && binding.branch === "root"
|
||||
: binding.workspaceID !== binding.root.workspaceID,
|
||||
{ message: "Invalid multi-work placement metadata" },
|
||||
);
|
||||
export const bindingKey = (id: string) => `multi-work:${id}`;
|
||||
const inputSchema = z
|
||||
.object({
|
||||
@@ -98,7 +106,7 @@ export const installMultiWork = (
|
||||
agent.name = Agent.Name.make("multi-work");
|
||||
agent.mode = "primary";
|
||||
agent.description =
|
||||
"Coordinate independent tasks in Herdr worktrees; retrieve local facts with fast-lo.";
|
||||
"Coordinate independent tasks locally or in Herdr worktrees; retrieve local facts with fast-lo.";
|
||||
agent.system = multiWorkGuide;
|
||||
agent.permissions = [
|
||||
...coordinate.permissions.filter((rule) => rule.action !== "subagent"),
|
||||
@@ -137,14 +145,14 @@ export const installMultiWork = (
|
||||
child.projectID !== binding.projectID ||
|
||||
child.location.directory !== binding.directory
|
||||
)
|
||||
throw new Error("Coordinate child does not match its ready worktree binding");
|
||||
throw new Error("Coordinate child does not match its ready multi-work binding");
|
||||
const parent = yield* context.session.get({ sessionID: child.parentID });
|
||||
if (
|
||||
parent.agent !== "multi-work" ||
|
||||
parent.projectID !== binding.projectID ||
|
||||
parent.location.directory !== binding.root.directory
|
||||
)
|
||||
throw new Error("Worktree owner changed agent or location");
|
||||
throw new Error("Binding owner changed agent or location");
|
||||
return binding;
|
||||
});
|
||||
|
||||
@@ -184,7 +192,7 @@ export const installMultiWork = (
|
||||
agent: { type: "string", enum: ["fast-lo", "coordinate"] },
|
||||
};
|
||||
tool.description =
|
||||
"Delegate to fast-lo locally, or coordinate in a new Herdr worktree using worktree: {base, branch?, label?}. Prefer descriptive human-readable branch and label values, for example {base:'main',branch:'fix/login-timeout',label:'Fix login timeout'}. Continue an owned coordinate child with sessionID and no worktree argument; naming fields are create-only. Native background and results apply.";
|
||||
"Delegate to fast-lo for retrieval, or coordinate in this root checkout by omitting worktree. To isolate new coordinate work, use worktree: {base, branch?, label?}. Continue an owned coordinate child with sessionID and no worktree argument. Native background and results apply.";
|
||||
}
|
||||
}).pipe(Effect.orDie),
|
||||
);
|
||||
@@ -208,9 +216,7 @@ export const installMultiWork = (
|
||||
throw new Error("A bound coordinate child cannot be repurposed");
|
||||
if (call.agent !== "multi-work") {
|
||||
if (input.worktree || input.agent === "coordinate")
|
||||
throw new Error(
|
||||
"Only multi-work may initialize coordinate worktrees",
|
||||
);
|
||||
throw new Error("Only multi-work may delegate to coordinate");
|
||||
return yield* native.execute(input, call);
|
||||
}
|
||||
const parent = yield* context.session.get({ sessionID: call.sessionID });
|
||||
@@ -240,29 +246,9 @@ export const installMultiWork = (
|
||||
}
|
||||
if (input.agent !== "coordinate")
|
||||
throw new Error("multi-work delegates only to fast-lo or coordinate");
|
||||
if (
|
||||
input.sessionID
|
||||
? input.worktree !== undefined
|
||||
: input.worktree === undefined
|
||||
)
|
||||
if (input.sessionID && input.worktree !== undefined)
|
||||
throw new Error(
|
||||
"New coordinate work requires worktree: {base}; resumes require only sessionID",
|
||||
);
|
||||
const { data: agent } = yield* context.agent.get({
|
||||
agentID: call.agent,
|
||||
location: { directory: parent.location.directory },
|
||||
});
|
||||
const permission = [
|
||||
...agent.permissions,
|
||||
...(parent.permissions ?? []),
|
||||
].findLast(
|
||||
(rule) =>
|
||||
matches("herdr_worktree", rule.action) &&
|
||||
matches(root.directory, rule.resource),
|
||||
)?.effect;
|
||||
if (permission !== "allow")
|
||||
throw new Error(
|
||||
`herdr_worktree requires explicit allow; custom approval is unavailable (${permission ?? "ask"})`,
|
||||
"Coordinate resumes require only sessionID; worktree is create-only",
|
||||
);
|
||||
const lock = input.sessionID ?? `${call.sessionID}:${call.id}`;
|
||||
if (busy.has(lock)) throw new Error("This delegation is already active");
|
||||
@@ -274,17 +260,48 @@ export const installMultiWork = (
|
||||
if (input.sessionID) {
|
||||
const binding = yield* validate(Session.ID.make(input.sessionID));
|
||||
if (binding.owner !== call.sessionID)
|
||||
throw new Error("Cannot resume another parent's worktree");
|
||||
yield* Effect.tryPromise((signal) =>
|
||||
herdr.verify(
|
||||
binding.directory,
|
||||
binding.workspaceID,
|
||||
binding.root,
|
||||
binding.branch,
|
||||
signal,
|
||||
),
|
||||
);
|
||||
throw new Error("Cannot resume another parent's binding");
|
||||
placement = binding;
|
||||
}
|
||||
if (
|
||||
input.worktree ||
|
||||
(placement && placement.directory !== placement.root.directory)
|
||||
) {
|
||||
const { data: agent } = yield* context.agent.get({
|
||||
agentID: call.agent,
|
||||
location: { directory: parent.location.directory },
|
||||
});
|
||||
const permission = [
|
||||
...agent.permissions,
|
||||
...(parent.permissions ?? []),
|
||||
].findLast(
|
||||
(rule) =>
|
||||
matches("herdr_worktree", rule.action) &&
|
||||
matches(root.directory, rule.resource),
|
||||
)?.effect;
|
||||
if (permission !== "allow")
|
||||
throw new Error(
|
||||
`herdr_worktree requires explicit allow; custom approval is unavailable (${permission ?? "ask"})`,
|
||||
);
|
||||
}
|
||||
if (placement) {
|
||||
const binding = placement;
|
||||
if (
|
||||
root.directory !== binding.root.directory ||
|
||||
root.workspaceID !== binding.root.workspaceID ||
|
||||
root.repo !== binding.root.repo
|
||||
)
|
||||
throw new Error("Bound Herdr root is no longer available");
|
||||
if (binding.directory !== binding.root.directory)
|
||||
yield* Effect.tryPromise((signal) =>
|
||||
herdr.verify(
|
||||
binding.directory,
|
||||
binding.workspaceID,
|
||||
binding.root,
|
||||
binding.branch,
|
||||
signal,
|
||||
),
|
||||
);
|
||||
initialized = true;
|
||||
}
|
||||
const { worktree, ...args } = input;
|
||||
@@ -310,8 +327,6 @@ export const installMultiWork = (
|
||||
);
|
||||
childLock = id;
|
||||
busy.add(id);
|
||||
if (!worktree)
|
||||
throw new Error("Missing worktree initialization");
|
||||
const sessionID = Session.ID.make(id);
|
||||
const child = yield* context.session.get({ sessionID });
|
||||
if (
|
||||
@@ -323,6 +338,27 @@ export const installMultiWork = (
|
||||
throw new Error(
|
||||
"Native executor returned an unexpected child",
|
||||
);
|
||||
if (!worktree) {
|
||||
const binding = {
|
||||
owner: call.sessionID,
|
||||
child: id,
|
||||
projectID: parent.projectID,
|
||||
root,
|
||||
directory: root.directory,
|
||||
workspaceID: root.workspaceID,
|
||||
branch: "root",
|
||||
ready: true,
|
||||
};
|
||||
yield* context.storage.set(callKey, {
|
||||
child: id,
|
||||
branch: binding.branch,
|
||||
root,
|
||||
});
|
||||
yield* context.storage.set(bindingKey(id), binding);
|
||||
placement = binding;
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
const branch =
|
||||
worktree.branch ?? `multi-work/${randomUUID()}`;
|
||||
yield* context.storage.set(callKey, {
|
||||
|
||||
Reference in New Issue
Block a user