@@ -10,6 +10,7 @@ import { z } from "zod";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { Herdr, rootSchema } from "./herdr.js";
|
||||
import { multiWorkGuide } from "../agents/routing.js";
|
||||
import { matches } from "../agents/wildcard.js";
|
||||
|
||||
export const bindingSchema = z
|
||||
.object({
|
||||
@@ -62,14 +63,8 @@ const readTools = new Set([
|
||||
export const childProtocol =
|
||||
"Bound child sessions use stanp-opencode multi-work protocol 1 and must remain in their assigned worktree.";
|
||||
|
||||
function matches(value: string, pattern: string) {
|
||||
return new RegExp(
|
||||
`^${pattern
|
||||
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
||||
.replace(/\*/g, ".*")
|
||||
.replace(/\?/g, ".")}$`,
|
||||
"s",
|
||||
).test(value);
|
||||
function present(value: string | undefined): value is string {
|
||||
return value !== undefined && value !== "";
|
||||
}
|
||||
|
||||
export const installMultiWork = (
|
||||
@@ -158,6 +153,20 @@ export const installMultiWork = (
|
||||
return binding;
|
||||
});
|
||||
|
||||
const verifyLocalChild = (
|
||||
sessionID: Session.ID,
|
||||
parent: { id: string; location: { directory: string } },
|
||||
) =>
|
||||
Effect.gen(function* () {
|
||||
const child = yield* context.session.get({ sessionID });
|
||||
if (
|
||||
child.parentID !== parent.id ||
|
||||
child.agent !== "fast-lo" ||
|
||||
child.location.directory !== parent.location.directory
|
||||
)
|
||||
throw new Error("Local fast-lo child binding changed");
|
||||
});
|
||||
|
||||
// Commands can create children without going through the subagent tool.
|
||||
yield* context.session.hook("prompt", (event) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -192,12 +201,12 @@ export const installMultiWork = (
|
||||
if (event.agent === "coordinate" && session.parentID)
|
||||
yield* validate(event.sessionID);
|
||||
if (event.agent !== "multi-work") {
|
||||
const tool = event.tools.subagent;
|
||||
const tool = event.tools["subagent"];
|
||||
if (tool) {
|
||||
const properties = z
|
||||
.record(z.string(), z.unknown())
|
||||
.parse(tool.input.properties ?? {});
|
||||
delete properties.worktree;
|
||||
.parse(tool.input["properties"] ?? {});
|
||||
delete properties["worktree"];
|
||||
tool.input = { ...tool.input, properties };
|
||||
}
|
||||
return;
|
||||
@@ -211,13 +220,13 @@ export const installMultiWork = (
|
||||
);
|
||||
if (!root)
|
||||
throw new Error("multi-work requires a live, unambiguous Herdr root checkout");
|
||||
const tool = event.tools.subagent;
|
||||
const tool = event.tools["subagent"];
|
||||
if (tool) {
|
||||
tool.input = z.toJSONSchema(inputSchema);
|
||||
const properties = z
|
||||
.record(z.string(), z.unknown())
|
||||
.parse(tool.input.properties);
|
||||
tool.input.properties = {
|
||||
.parse(tool.input["properties"]);
|
||||
tool.input["properties"] = {
|
||||
...properties,
|
||||
agent: { type: "string", enum: ["fast-lo", "coordinate"] },
|
||||
};
|
||||
@@ -238,7 +247,7 @@ export const installMultiWork = (
|
||||
if (input.agent === "multi-work")
|
||||
throw new Error("multi-work is primary only");
|
||||
if (
|
||||
input.sessionID &&
|
||||
present(input.sessionID) &&
|
||||
(yield* context.storage.get(bindingKey(input.sessionID))) !==
|
||||
undefined &&
|
||||
(call.agent !== "multi-work" || input.agent !== "coordinate")
|
||||
@@ -261,22 +270,13 @@ export const installMultiWork = (
|
||||
);
|
||||
if (input.agent === "fast-lo") {
|
||||
if (input.worktree) throw new Error("fast-lo is local retrieval only");
|
||||
if (input.sessionID) {
|
||||
const child = yield* context.session.get({
|
||||
sessionID: Session.ID.make(input.sessionID),
|
||||
});
|
||||
if (
|
||||
child.parentID !== parent.id ||
|
||||
child.agent !== "fast-lo" ||
|
||||
child.location.directory !== parent.location.directory
|
||||
)
|
||||
throw new Error("Local fast-lo child binding changed");
|
||||
}
|
||||
if (present(input.sessionID))
|
||||
yield* verifyLocalChild(Session.ID.make(input.sessionID), parent);
|
||||
return yield* native.execute(input, call);
|
||||
}
|
||||
if (input.agent !== "coordinate")
|
||||
throw new Error("multi-work delegates only to fast-lo or coordinate");
|
||||
if (input.sessionID && input.worktree !== undefined)
|
||||
if (present(input.sessionID) && input.worktree !== undefined)
|
||||
throw new Error(
|
||||
"Coordinate resumes require only sessionID; worktree is create-only",
|
||||
);
|
||||
@@ -287,7 +287,7 @@ export const installMultiWork = (
|
||||
return yield* Effect.gen(function* () {
|
||||
let initialized = false;
|
||||
let placement: z.infer<typeof bindingSchema> | undefined;
|
||||
if (input.sessionID) {
|
||||
if (present(input.sessionID)) {
|
||||
const binding = yield* validate(Session.ID.make(input.sessionID));
|
||||
if (binding.owner !== call.sessionID)
|
||||
throw new Error("Cannot resume another parent's binding");
|
||||
@@ -336,7 +336,7 @@ export const installMultiWork = (
|
||||
}
|
||||
const { worktree, ...args } = input;
|
||||
const callKey = `multi-work-call:${call.sessionID}:${call.id}`;
|
||||
if (!input.sessionID) {
|
||||
if (!present(input.sessionID)) {
|
||||
const previous = yield* context.storage.get(callKey);
|
||||
if (previous !== undefined)
|
||||
throw new Error(
|
||||
@@ -350,7 +350,7 @@ export const installMultiWork = (
|
||||
// Native 2.0.22 awaits this update after creation and before prompt admission.
|
||||
yield* call.progress(metadata);
|
||||
if (initialized) return;
|
||||
const id = z.string().parse(metadata.sessionID);
|
||||
const id = z.string().parse(metadata["sessionID"]);
|
||||
if (busy.has(id))
|
||||
throw new Error(
|
||||
"This child is already being delegated",
|
||||
@@ -433,7 +433,7 @@ export const installMultiWork = (
|
||||
coordinator?.mode === "all" &&
|
||||
coordinator.system?.includes(
|
||||
childProtocol,
|
||||
) &&
|
||||
) === true &&
|
||||
[
|
||||
"coordinate",
|
||||
"fast-lo",
|
||||
@@ -503,7 +503,7 @@ export const installMultiWork = (
|
||||
Effect.ensuring(
|
||||
Effect.sync(() => {
|
||||
busy.delete(lock);
|
||||
if (childLock) busy.delete(childLock);
|
||||
if (present(childLock)) busy.delete(childLock);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user