This commit is contained in:
+2
-2
@@ -320,7 +320,7 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
||||
if (agent === "multi-work") throw new Error("multi-work is primary only");
|
||||
if (agent === "coordinate" && event.agent !== "multi-work")
|
||||
throw new Error(
|
||||
"coordinate cannot run as a subagent without a ready worktree binding",
|
||||
"coordinate cannot run as a subagent without a ready multi-work binding",
|
||||
);
|
||||
if (workerNames.some((name) => name === event.agent))
|
||||
throw new Error("Workers cannot delegate tasks");
|
||||
@@ -349,7 +349,7 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
||||
parent.location.directory !== binding.data.root.directory
|
||||
)
|
||||
throw new Error(
|
||||
"coordinate cannot run as a subagent without a ready worktree binding",
|
||||
"coordinate cannot run as a subagent without a ready multi-work binding",
|
||||
);
|
||||
}
|
||||
if (
|
||||
|
||||
+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, {
|
||||
|
||||
+6
-4
@@ -16,15 +16,17 @@ export const descriptions = {
|
||||
export const multiWorkGuide = `You are multi-work, a primary orchestrator in a verified Herdr root checkout.
|
||||
Read, plan, track todos and delegate; never implement or execute shell commands yourself.
|
||||
Use native subagent with agent fast-lo for focused local retrieval. Use agent coordinate
|
||||
for work in a new Herdr worktree, supplying descriptive human-readable names such as
|
||||
without worktree to edit concurrently in this root checkout, or isolate work in a new
|
||||
Herdr worktree by supplying descriptive human-readable names such as
|
||||
worktree: {base: "main", branch: "fix/login-timeout", label: "Fix login timeout"}, plus
|
||||
description and a complete prompt with scope, constraints and acceptance checks. The tool
|
||||
uses the requested branch and Herdr workspace label; when omitted, it creates the prior unique
|
||||
uses the requested branch and Herdr workspace label; when the branch is omitted, it creates the prior unique
|
||||
multi-work/<uuid> branch and lets Herdr choose its default label. It moves the coordinate child
|
||||
before its task begins, and preserves your root session. Dirty root changes are not copied.
|
||||
Resume an owned coordinate child using its sessionID and no worktree argument. Never switch
|
||||
its agent or location; naming fields are create-only. Only fast-lo and coordinate are permitted. Workers are leaves.
|
||||
Parallelize independent work with disjoint ownership; serialize dependencies. Native background
|
||||
its agent or assigned checkout; naming fields are create-only. Only fast-lo and coordinate are permitted. Workers are leaves.
|
||||
Root-local workers share one checkout, so give them disjoint file ownership and never assign
|
||||
overlapping edits concurrently. Parallelize independent work; serialize dependencies. Native background
|
||||
delegation returns immediately and notifies you when complete; do not poll or duplicate work.
|
||||
Reconcile results and report checkout, branch and child IDs. Retained resources on failure
|
||||
must not be deleted automatically. Never retry ambiguous creation failures blindly.
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ export default Plugin.define({
|
||||
return (
|
||||
<Show when={status()}>
|
||||
<Show when={props.centered} fallback={<Content />}>
|
||||
<box width="100%" alignItems="center">
|
||||
<box position="absolute" top={2} left={0} width="100%" alignItems="center">
|
||||
<Content />
|
||||
</box>
|
||||
</Show>
|
||||
|
||||
Reference in New Issue
Block a user