@krapht/ork 1.0.0-alpha.7 → 1.0.0-alpha.9
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/dist/{__build_info__-BGoaUwLd.mjs → __build_info__-y1TPvZvp.mjs} +2 -2
- package/dist/bin.mjs +598 -51
- package/dist/{clean-BPITM-6p.mjs → clean-BRUXyk87.mjs} +5 -2
- package/dist/{completion-CCn8xWmO.mjs → completion-cpZHtv50.mjs} +5 -5
- package/dist/{context-CZNM454F.mjs → context-Dbay8l7P.mjs} +8 -7
- package/dist/{decide-DsA6kOT7.mjs → decide-BphP3ONM.mjs} +1 -1
- package/dist/{find-ntyniv2E.mjs → find-BXZjliwr.mjs} +1 -1
- package/dist/format-DeT7vL-8.mjs +191 -0
- package/dist/{in-a-run-D_fnj1Mw.mjs → in-a-run-C4E9HfED.mjs} +1 -1
- package/dist/{limits-BKzVMwkt.mjs → limits-CBeoYrUH.mjs} +1 -1
- package/dist/{list-D9HPWLPH.mjs → list-BmxgNyd3.mjs} +11 -4
- package/dist/{mcp-BFl3cIDp.mjs → mcp-DTreb7Nq.mjs} +6 -6
- package/dist/{output-CwqlG1Le.mjs → output-BVkI6ZVv.mjs} +1 -1
- package/dist/{pause-DhgKQORW.mjs → pause-h66SC4rV.mjs} +4 -4
- package/dist/{relay-Cp8JGrdv.mjs → relay-BxRhxL6n.mjs} +2 -2
- package/dist/{remove-CSpT_nJH.mjs → remove-CYGd9Csj.mjs} +14 -7
- package/dist/{send-CHiYkM6X.mjs → send-BskOwN4p.mjs} +3 -3
- package/dist/{src-BsLHKQRc.mjs → src-BwFxTank.mjs} +335 -107
- package/dist/{src-CUeMUVwW.mjs → src-W1i5Bl6N.mjs} +23 -9
- package/dist/{src-DhGLtYR-.mjs → src-bOImrq43.mjs} +30 -11
- package/dist/{src-BwnitlJh.mjs → src-xd9DbQw8.mjs} +8 -8
- package/dist/ui/assets/{app-D4CoH-lh.js → app-BMg0OOv4.js} +19 -19
- package/dist/ui/assets/app-C8uhHVYp.css +1 -0
- package/dist/ui/assets/{index-DxDGdEwz.js → index-DY1q4SDl.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-BxE0L7V0.mjs → ui-B46Z3e9E.mjs} +4 -4
- package/dist/{usage-BuzDsEhq.mjs → usage-DpHXbQEu.mjs} +5 -5
- package/dist/{watch-CCj0vU3B.mjs → watch-CJO1ee6Y.mjs} +6 -6
- package/dist/{workflows-BNCDD6J-.mjs → workflows-BJdSHnkr.mjs} +4 -3
- package/dist/{worktrees-DBBcaimX.mjs → worktrees-Ccf2lUhE.mjs} +72 -19
- package/package.json +1 -1
- package/dist/format-DaROJUgG.mjs +0 -662
- package/dist/ui/assets/app-Dt_tjFj6.css +0 -1
|
@@ -608,7 +608,10 @@ const agentSettings = {
|
|
|
608
608
|
practices: PracticesSchema.optional(),
|
|
609
609
|
tools: ToolRulesSchema.optional(),
|
|
610
610
|
session: z.enum(["fresh", "resume"]).optional().describe("When the node runs again (a loop, another delegation): resume its session, or start a fresh one with the new input. Default: resume for a leader, fresh for a worker."),
|
|
611
|
-
output: z.strictObject({
|
|
611
|
+
output: z.strictObject({
|
|
612
|
+
schema: z.record(z.string(), z.unknown()).optional().describe("Extra structured fields the node must report, e.g. { verdict: [approve, escalate] }."),
|
|
613
|
+
gather: z.array(z.string().min(1)).min(1).optional().describe("List fields kept across rounds: each round's entries are added to the earlier rounds' instead of replacing them, e.g. [decisions].")
|
|
614
|
+
}).optional()
|
|
612
615
|
};
|
|
613
616
|
/** The settings keys an agent can bring, in canonical order. */
|
|
614
617
|
const AGENT_SETTING_KEYS = [
|
|
@@ -858,7 +861,7 @@ const serializeAgent = (name, agent) => {
|
|
|
858
861
|
// ../schema/src/edges.ts
|
|
859
862
|
const ARROW = "->";
|
|
860
863
|
const splitList = (side) => side.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
861
|
-
const asList$
|
|
864
|
+
const asList$2 = (value) => Array.isArray(value) ? value : [value];
|
|
862
865
|
/**
|
|
863
866
|
* Expands `"a, b -> c, d"` (or the object form) into the cartesian product of
|
|
864
867
|
* edges.
|
|
@@ -866,7 +869,7 @@ const asList$1 = (value) => Array.isArray(value) ? value : [value];
|
|
|
866
869
|
const expandEdge = (input, source) => {
|
|
867
870
|
if (typeof input !== "string") return {
|
|
868
871
|
ok: true,
|
|
869
|
-
edges: asList$
|
|
872
|
+
edges: asList$2(input.from).flatMap((from) => asList$2(input.to).map((to) => {
|
|
870
873
|
const edge = {
|
|
871
874
|
from,
|
|
872
875
|
to,
|
|
@@ -907,7 +910,7 @@ const formatEdge = (input) => {
|
|
|
907
910
|
const [left = "", right = ""] = input.split(ARROW);
|
|
908
911
|
return `${splitList(left).join(", ")} ${ARROW} ${splitList(right).join(", ")}`;
|
|
909
912
|
}
|
|
910
|
-
if (input.when === void 0 && input.maxRounds === void 0) return `${asList$
|
|
913
|
+
if (input.when === void 0 && input.maxRounds === void 0) return `${asList$2(input.from).join(", ")} ${ARROW} ${asList$2(input.to).join(", ")}`;
|
|
911
914
|
return input;
|
|
912
915
|
};
|
|
913
916
|
/** Settings every Claude node takes unless it (or its agent) sets its own. */
|
|
@@ -1055,6 +1058,31 @@ const markValue = (value) => {
|
|
|
1055
1058
|
const pad = line.startsWith("`") || line.endsWith("`") ? " " : "";
|
|
1056
1059
|
return `${fence}${pad}${line}${pad}${fence}`;
|
|
1057
1060
|
};
|
|
1061
|
+
// ../schema/src/issue-messages.ts
|
|
1062
|
+
const NODE_TYPES = NodeSchema.options.map((o) => o.shape.type.value);
|
|
1063
|
+
const isMap$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1064
|
+
/** The text for an issue at or under a `forEach`, else `null`. */
|
|
1065
|
+
const forEachMessage = (issue, path, value) => {
|
|
1066
|
+
const key = path.at(-1);
|
|
1067
|
+
if (issue.code === "too_small" && path.at(-2) === "forEach") {
|
|
1068
|
+
if (key === "items") return "`forEach.items` is empty: list at least one item, or use `from`";
|
|
1069
|
+
if (key === "maxItems") return "`forEach.maxItems` must be at least 1: the most items to run before Ork asks";
|
|
1070
|
+
}
|
|
1071
|
+
if (issue.code !== "invalid_union" || key !== "forEach") return null;
|
|
1072
|
+
if (!isMap$1(value)) return "`forEach` takes a map: `items: [...]` for a list written here, or `from:` with `maxItems:` for an earlier node's";
|
|
1073
|
+
if ("items" in value && "from" in value) return "`forEach` takes `items` or `from`, not both";
|
|
1074
|
+
if ("from" in value) return "`forEach` with `from` needs `maxItems`: the most items to run before Ork asks";
|
|
1075
|
+
if (!("items" in value)) return "`forEach` needs `items` or `from`: a list written here, or an earlier node's";
|
|
1076
|
+
return null;
|
|
1077
|
+
};
|
|
1078
|
+
/** The text for a `list` rule that is not a map of fields, else `null`. */
|
|
1079
|
+
const listRuleMessage = (issue, value) => issue.code === "invalid_union" && isMap$1(value) && "list" in value ? "`list` takes a map of each item's fields, e.g. `list: { title: string }` (`list: {}` for items of any shape)" : null;
|
|
1080
|
+
/** The text for a node whose `type` is missing or unknown, else `null`. */
|
|
1081
|
+
const nodeTypeMessage = (issue, value) => {
|
|
1082
|
+
if (issue.code !== "invalid_union" || issue.discriminator !== "type") return null;
|
|
1083
|
+
const types = NODE_TYPES.map((type) => markValue(type)).join(", ");
|
|
1084
|
+
return value === void 0 ? `node needs a \`type\`: one of ${types}` : `unknown node type ${markValue(typeof value === "string" ? value : JSON.stringify(value) ?? "")}: expected one of ${types}`;
|
|
1085
|
+
};
|
|
1058
1086
|
// ../schema/src/newer-version.ts
|
|
1059
1087
|
/**
|
|
1060
1088
|
* A file with a higher `schemaVersion` than this Ork reads was made by a newer
|
|
@@ -1075,7 +1103,6 @@ const newerVersion = (issues, raw) => {
|
|
|
1075
1103
|
* diagnostics, and the workflow's byte-stable canonical serializer.
|
|
1076
1104
|
*/
|
|
1077
1105
|
const SCHEMA_URL = "https://raw.githubusercontent.com/mimshins/ork/main/packages/schema/workflow.schema.json";
|
|
1078
|
-
const NODE_TYPES = NodeSchema.options.map((o) => o.shape.type.value);
|
|
1079
1106
|
const createLocate = (doc, counter) => (path) => {
|
|
1080
1107
|
for (let depth = path.length; depth >= 0; depth -= 1) {
|
|
1081
1108
|
const node = doc.getIn(path.slice(0, depth), true);
|
|
@@ -1090,6 +1117,12 @@ const createLocate = (doc, counter) => (path) => {
|
|
|
1090
1117
|
return null;
|
|
1091
1118
|
};
|
|
1092
1119
|
const toPath = (path) => path.map((p) => typeof p === "number" ? p : String(p));
|
|
1120
|
+
/** The value at a path of the raw document, or undefined. */
|
|
1121
|
+
const valueAt = (raw, path) => {
|
|
1122
|
+
let value = raw;
|
|
1123
|
+
for (const segment of path) value = value !== null && typeof value === "object" ? Reflect.get(value, segment) : void 0;
|
|
1124
|
+
return value;
|
|
1125
|
+
};
|
|
1093
1126
|
const describeIssue = (issue, raw, path) => {
|
|
1094
1127
|
if (issue.code === "unrecognized_keys") {
|
|
1095
1128
|
const [first] = issue.keys;
|
|
@@ -1098,14 +1131,12 @@ const describeIssue = (issue, raw, path) => {
|
|
|
1098
1131
|
path: first === void 0 ? path : [...path, first]
|
|
1099
1132
|
};
|
|
1100
1133
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1134
|
+
const value = valueAt(raw, path);
|
|
1135
|
+
const said = forEachMessage(issue, path, value) ?? listRuleMessage(issue, value) ?? nodeTypeMessage(issue, value);
|
|
1136
|
+
if (said !== null) return {
|
|
1137
|
+
message: said,
|
|
1138
|
+
path
|
|
1139
|
+
};
|
|
1109
1140
|
return {
|
|
1110
1141
|
message: issue.message.replaceAll(/"([^"`\n]+)"/gu, "`$1`"),
|
|
1111
1142
|
path
|
|
@@ -4194,7 +4225,7 @@ const resolvePath = (segments, scope) => {
|
|
|
4194
4225
|
};
|
|
4195
4226
|
/** Truthiness as expressions see it: an empty list is false. */
|
|
4196
4227
|
const truthy = (value) => Array.isArray(value) ? value.length > 0 : Boolean(value);
|
|
4197
|
-
const asList = (value) => Array.isArray(value) ? value : [];
|
|
4228
|
+
const asList$1 = (value) => Array.isArray(value) ? value : [];
|
|
4198
4229
|
const order = (op, l, r) => {
|
|
4199
4230
|
if (op === "<") return l < r;
|
|
4200
4231
|
if (op === "<=") return l <= r;
|
|
@@ -4222,7 +4253,7 @@ const evaluate = (expr, scope) => {
|
|
|
4222
4253
|
case "call": {
|
|
4223
4254
|
const args = expr.args.map((arg) => evaluate(arg, scope));
|
|
4224
4255
|
const [subject, value] = args;
|
|
4225
|
-
const list = asList(subject);
|
|
4256
|
+
const list = asList$1(subject);
|
|
4226
4257
|
const matches = (item) => args.length > 1 ? item === value : truthy(item);
|
|
4227
4258
|
switch (expr.name) {
|
|
4228
4259
|
case "any": return list.some((item) => matches(item));
|
|
@@ -4328,11 +4359,21 @@ const readableText = (value, before) => {
|
|
|
4328
4359
|
* Fills a parsed template. `readable` is for text a person reads (a gate's
|
|
4329
4360
|
* `question` and `document`); scripts and prompts keep JSON (ADR-047).
|
|
4330
4361
|
*/
|
|
4331
|
-
const renderTemplate = (parts, scope, { readable = false } = {}) =>
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4362
|
+
const renderTemplate = (parts, scope, { readable = false } = {}) => {
|
|
4363
|
+
let text = "";
|
|
4364
|
+
let afterList = false;
|
|
4365
|
+
for (const part of parts) {
|
|
4366
|
+
const value = typeof part === "string" ? part : evaluate(part.expr, scope);
|
|
4367
|
+
const piece = readable ? readableText(value, text) : stringify$1(value);
|
|
4368
|
+
if (piece.trim() === "") {
|
|
4369
|
+
text += piece;
|
|
4370
|
+
continue;
|
|
4371
|
+
}
|
|
4372
|
+
text = afterList && !isContainer(value) ? `${text.trimEnd()}\n\n${piece.trimStart()}` : text + piece;
|
|
4373
|
+
afterList = readable && isContainer(value) && depthOf(value) <= 2;
|
|
4374
|
+
}
|
|
4375
|
+
return text;
|
|
4376
|
+
};
|
|
4336
4377
|
// ../core/src/feedback/fingerprint.ts
|
|
4337
4378
|
/**
|
|
4338
4379
|
* @file Feedback fingerprints: records about the same thing (same target, kind,
|
|
@@ -5055,6 +5096,8 @@ const REPORT_STATUSES = [
|
|
|
5055
5096
|
];
|
|
5056
5097
|
/** What a node declares of its output (RFC-010), if it does. */
|
|
5057
5098
|
const declaredOutputOf = (node) => "output" in node ? node.output?.schema : void 0;
|
|
5099
|
+
/** The report fields a node keeps across its rounds (ADR-073), if any. */
|
|
5100
|
+
const gatheredFieldsOf = (node) => node !== void 0 && isClaudeNode(node) ? node.output?.gather ?? [] : [];
|
|
5058
5101
|
/** What a node with `forEach` reports instead of its type's fields (RFC-013). */
|
|
5059
5102
|
const GATHERED_FIELDS = [
|
|
5060
5103
|
"items",
|
|
@@ -5686,6 +5729,11 @@ const ownersOf = (workflow, id) => {
|
|
|
5686
5729
|
return owners;
|
|
5687
5730
|
};
|
|
5688
5731
|
// ../core/src/graph/for-each.ts
|
|
5732
|
+
/**
|
|
5733
|
+
* @file The checks on `forEach` (RFC-013): where its list comes from, and where
|
|
5734
|
+
* `{{item}}` and `{{index}}` may be read and what `{{item.<field>}}` may
|
|
5735
|
+
* name.
|
|
5736
|
+
*/
|
|
5689
5737
|
const isItemRoot = (root) => ITEM_ROOTS.includes(root);
|
|
5690
5738
|
/**
|
|
5691
5739
|
* Nodes whose output exists before `id` runs: its flow ancestors and its
|
|
@@ -5701,7 +5749,7 @@ const upstreamOf = (workflow, edges, id) => {
|
|
|
5701
5749
|
};
|
|
5702
5750
|
/** The field a path under `nodes.<id>.output` reads, as `says.ts` names it. */
|
|
5703
5751
|
const fieldOfPath = (segments) => segments.slice(3).join(".");
|
|
5704
|
-
const isList = (said) => said.items !== void 0 || said.kind === "statuses";
|
|
5752
|
+
const isList$1 = (said) => said.items !== void 0 || said.kind === "statuses";
|
|
5705
5753
|
/** What a node's `from` list's items have, or `undefined` if nothing says. */
|
|
5706
5754
|
const sourceFields = (segments, says) => {
|
|
5707
5755
|
const [root, id] = segments;
|
|
@@ -5745,6 +5793,11 @@ const checkFiles = (id, key, files) => files.flatMap((file, index) => {
|
|
|
5745
5793
|
index
|
|
5746
5794
|
]))) : [];
|
|
5747
5795
|
});
|
|
5796
|
+
/**
|
|
5797
|
+
* Whether one activation's parsed JSON is at `output.json` (a program, an MCP
|
|
5798
|
+
* call).
|
|
5799
|
+
*/
|
|
5800
|
+
const readsJson = (node) => (isToolNode(node) || node.type === "mcp.call") && forEachOf$1(node) === void 0;
|
|
5748
5801
|
/** The source of one `from`: it exists, runs first, and is a declared list. */
|
|
5749
5802
|
const checkSource = (workflow, edges, says, id, segments, path) => {
|
|
5750
5803
|
const named = describePath(segments);
|
|
@@ -5759,8 +5812,15 @@ const checkSource = (workflow, edges, says, id, segments, path) => {
|
|
|
5759
5812
|
if (source === id || !ancestorsOf(id, first).has(String(source))) return [error("forEach.order", `\`${named}\`: \`${String(source)}\` does not run before \`${id}\`, so its list can't exist when \`${id}\` starts`, path)];
|
|
5760
5813
|
const field = fieldOfPath(segments);
|
|
5761
5814
|
const said = says[String(source)]?.fields.find(({ field: name }) => name === field);
|
|
5762
|
-
if (field === "" || said !== void 0 && !isList(said)) return [error("forEach.not-list", `\`${named}\` is not a list: \`${String(source)}\` declares ${field === "" ? "its whole output" : `\`${field}\``} as something else; declare it as \`{ list: { … } }\``, path)];
|
|
5763
|
-
|
|
5815
|
+
if (field === "" || said !== void 0 && !isList$1(said)) return [error("forEach.not-list", `\`${named}\` is not a list: \`${String(source)}\` declares ${field === "" ? "its whole output" : `\`${field}\``} as something else; declare it as \`{ list: { … } }\``, path)];
|
|
5816
|
+
if (said !== void 0) return [];
|
|
5817
|
+
const add = `add \`{ list: { … } }\` to \`${String(source)}\`'s \`output.schema\``;
|
|
5818
|
+
const node = workflow.nodes[String(source)];
|
|
5819
|
+
if (node !== void 0 && readsJson(node) && !field.startsWith("json.")) {
|
|
5820
|
+
const declared = says[String(source)]?.fields.some(({ field: name }) => name === `json.${field}`);
|
|
5821
|
+
return [warning("forEach.undeclared", `\`${named}\`: a tool's JSON is read as \`nodes.${String(source)}.output.json.${field}\`${declared === true ? "" : `; ${add}`}`, path)];
|
|
5822
|
+
}
|
|
5823
|
+
return [warning("forEach.undeclared", `\`${named}\`: nothing declares this list, so a run can't check it or its items; ${add}`, path)];
|
|
5764
5824
|
};
|
|
5765
5825
|
/** Every `forEach`'s source, and `{{item}}` in the lists no template reads. */
|
|
5766
5826
|
const checkForEach = (workflow, edges) => {
|
|
@@ -5839,6 +5899,26 @@ const checkGroup = (workflow, edges, component) => {
|
|
|
5839
5899
|
};
|
|
5840
5900
|
/** What's wrong with the shape of each group of nodes that loop together. */
|
|
5841
5901
|
const checkLoops = (workflow, edges) => stronglyConnected(Object.keys(workflow.nodes), edges).flatMap((component) => checkGroup(workflow, edges, component));
|
|
5902
|
+
// ../core/src/graph/gather.ts
|
|
5903
|
+
/** Whether a declared field is a list: `{ type: array }` or `{ list: … }`. */
|
|
5904
|
+
const isList = (rule) => listShape(rule) !== void 0 || typeof rule === "object" && rule !== null && "type" in rule && rule.type === "array";
|
|
5905
|
+
const gatherProblems = (id, node) => {
|
|
5906
|
+
const fields = gatheredFieldsOf(node);
|
|
5907
|
+
if (fields.length === 0) return [];
|
|
5908
|
+
const path = [
|
|
5909
|
+
"nodes",
|
|
5910
|
+
id,
|
|
5911
|
+
"output",
|
|
5912
|
+
"gather"
|
|
5913
|
+
];
|
|
5914
|
+
if (forEachOf$1(node) !== void 0) return [error("output.gather", `\`${id}\` runs once per item and reports its items' results, so it has no fields of its own to keep across rounds`, path)];
|
|
5915
|
+
const declared = declaredOutputOf(node) ?? {};
|
|
5916
|
+
return fields.flatMap((field, index) => {
|
|
5917
|
+
const rule = declared[field];
|
|
5918
|
+
if (rule === void 0) return [error("output.gather", `\`${field}\` isn't in \`${id}\`'s \`output.schema\`; declare it as a list to keep it across rounds`, [...path, index])];
|
|
5919
|
+
return isList(rule) ? [] : [error("output.gather", `\`${field}\` isn't declared as a list (\`{ type: array }\` or \`{ list: … }\`), so its rounds can't be added together`, [...path, index])];
|
|
5920
|
+
});
|
|
5921
|
+
};
|
|
5842
5922
|
// ../core/src/graph/script.ts
|
|
5843
5923
|
/**
|
|
5844
5924
|
* @file Checks on the programs a workflow declares (RFC-006): a tool node's and
|
|
@@ -5910,7 +5990,7 @@ const checkNodes = (workflow, edges) => {
|
|
|
5910
5990
|
const incoming = edges.filter((e) => e.to === id);
|
|
5911
5991
|
const outgoing = edges.filter((e) => e.from === id);
|
|
5912
5992
|
if (node.type === "claude.subagent" && node.timeout !== void 0) diagnostics.push(warning("timeout.subagent", `sub-agent \`${id}\` runs inside its parent's session, so its timeout has no effect; set one on the parent`, [...path, "timeout"]));
|
|
5913
|
-
diagnostics.push(...statusProblems(id, node));
|
|
5993
|
+
diagnostics.push(...statusProblems(id, node), ...gatherProblems(id, node));
|
|
5914
5994
|
if (node.type === "control.join") {
|
|
5915
5995
|
const sources = new Set(incoming.map((e) => e.from)).size;
|
|
5916
5996
|
if (sources < 2) diagnostics.push(warning("join.inputs", `join \`${id}\` has ${sources} incoming node(s); a join waits for two or more`, path));
|
|
@@ -6823,14 +6903,23 @@ const createLoops = (options) => {
|
|
|
6823
6903
|
};
|
|
6824
6904
|
/**
|
|
6825
6905
|
* What a list over its cap runs: asked of a live owner, then the human. All
|
|
6826
|
-
* of it, the first `maxItems`, or `null` to stop.
|
|
6906
|
+
* of it, the first `maxItems`, or `null` to stop. A stop (`signal`) ends the
|
|
6907
|
+
* wait and withdraws the question; the list comes back whole, and nothing of
|
|
6908
|
+
* it starts because the stop also bars its items.
|
|
6827
6909
|
*/
|
|
6828
|
-
const overList = async (node, list, maxItems, round) => {
|
|
6910
|
+
const overList = async (node, list, maxItems, round, signal) => {
|
|
6829
6911
|
const question = eachQuestion(node, list.length, maxItems, round);
|
|
6830
|
-
const
|
|
6912
|
+
const asking = async () => await options.bus?.askOwner(node, question) ?? (signal.aborted ? null : options.human.ask({
|
|
6831
6913
|
nodeId: node,
|
|
6832
|
-
...question
|
|
6833
|
-
|
|
6914
|
+
...question,
|
|
6915
|
+
signal
|
|
6916
|
+
}));
|
|
6917
|
+
const stopped = Promise.withResolvers();
|
|
6918
|
+
signal.addEventListener("abort", () => {
|
|
6919
|
+
stopped.resolve(null);
|
|
6920
|
+
}, { once: true });
|
|
6921
|
+
const choice = await Promise.race([asking(), stopped.promise]);
|
|
6922
|
+
if (signal.aborted) return list;
|
|
6834
6923
|
if (choice === EACH_CHOICES.first) return list.slice(0, maxItems);
|
|
6835
6924
|
return choice === EACH_CHOICES.all ? list : null;
|
|
6836
6925
|
};
|
|
@@ -6911,6 +7000,40 @@ const declaredProblem = (declared, value) => {
|
|
|
6911
7000
|
* still masks to the record is the file's, with its real values.
|
|
6912
7001
|
*/
|
|
6913
7002
|
const unmasked = (forEach, recorded) => "items" in forEach && JSON.stringify(forEach.items, maskReplacer) === JSON.stringify(recorded) ? [...forEach.items] : recorded;
|
|
7003
|
+
// ../core/src/run/contacts.ts
|
|
7004
|
+
const SESSION_TYPES$4 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
|
|
7005
|
+
const isSession = (node) => SESSION_TYPES$4.has(node?.type ?? "");
|
|
7006
|
+
const isTool = (node) => node?.type.startsWith("tool.") ?? false;
|
|
7007
|
+
const channelPeersOf = (id, channels) => channels.flatMap(([a, b]) => {
|
|
7008
|
+
if (a === id) return [b];
|
|
7009
|
+
return b === id ? [a] : [];
|
|
7010
|
+
});
|
|
7011
|
+
/** Every declared node, with its owner, as a run records them. */
|
|
7012
|
+
const declaredNodes = (graph) => [...graph.nodes.values()].map(({ id, node }) => node.parent === void 0 ? {
|
|
7013
|
+
id,
|
|
7014
|
+
type: node.type
|
|
7015
|
+
} : {
|
|
7016
|
+
id,
|
|
7017
|
+
type: node.type,
|
|
7018
|
+
parent: node.parent
|
|
7019
|
+
});
|
|
7020
|
+
/** Whom a session may write to, by how it knows them. */
|
|
7021
|
+
const contactsOf = (id, nodes, channels) => {
|
|
7022
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
7023
|
+
return {
|
|
7024
|
+
owner: byId.get(id)?.parent,
|
|
7025
|
+
children: nodes.filter((node) => node.parent === id && isSession(node)).map((node) => node.id),
|
|
7026
|
+
channels: channelPeersOf(id, channels).filter((peer) => !isTool(byId.get(peer)))
|
|
7027
|
+
};
|
|
7028
|
+
};
|
|
7029
|
+
/** Whom a program in the tool node `id` may write to. */
|
|
7030
|
+
const scriptContactsOf = (id, nodes, channels) => {
|
|
7031
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
7032
|
+
const owner = byId.get(id)?.parent;
|
|
7033
|
+
return [.../* @__PURE__ */ new Set([...owner !== void 0 && isSession(byId.get(owner)) ? [owner] : [], ...channelPeersOf(id, channels).filter((peer) => isSession(byId.get(peer)))])];
|
|
7034
|
+
};
|
|
7035
|
+
/** Whether a program in a tool node may write to the session `id`. */
|
|
7036
|
+
const hearsFromScripts = (id, nodes, channels) => nodes.some((node) => isTool(node) && scriptContactsOf(node.id, nodes, channels).includes(id));
|
|
6914
7037
|
// ../core/src/usage/spend.ts
|
|
6915
7038
|
/** What a report would add to the session's earlier ones, counting nothing. */
|
|
6916
7039
|
const unreported = (reported, key, total) => {
|
|
@@ -6966,46 +7089,7 @@ const earlierSpend = (events) => {
|
|
|
6966
7089
|
forks
|
|
6967
7090
|
};
|
|
6968
7091
|
};
|
|
6969
|
-
// ../core/src/run/
|
|
6970
|
-
const SESSION_TYPES$4 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
|
|
6971
|
-
const isSession = (node) => SESSION_TYPES$4.has(node?.type ?? "");
|
|
6972
|
-
const isTool = (node) => node?.type.startsWith("tool.") ?? false;
|
|
6973
|
-
const channelPeersOf = (id, channels) => channels.flatMap(([a, b]) => {
|
|
6974
|
-
if (a === id) return [b];
|
|
6975
|
-
return b === id ? [a] : [];
|
|
6976
|
-
});
|
|
6977
|
-
/** Every declared node, with its owner, as a run records them. */
|
|
6978
|
-
const declaredNodes = (graph) => [...graph.nodes.values()].map(({ id, node }) => node.parent === void 0 ? {
|
|
6979
|
-
id,
|
|
6980
|
-
type: node.type
|
|
6981
|
-
} : {
|
|
6982
|
-
id,
|
|
6983
|
-
type: node.type,
|
|
6984
|
-
parent: node.parent
|
|
6985
|
-
});
|
|
6986
|
-
/** Whom a session may write to, by how it knows them. */
|
|
6987
|
-
const contactsOf = (id, nodes, channels) => {
|
|
6988
|
-
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
6989
|
-
return {
|
|
6990
|
-
owner: byId.get(id)?.parent,
|
|
6991
|
-
children: nodes.filter((node) => node.parent === id && isSession(node)).map((node) => node.id),
|
|
6992
|
-
channels: channelPeersOf(id, channels).filter((peer) => !isTool(byId.get(peer)))
|
|
6993
|
-
};
|
|
6994
|
-
};
|
|
6995
|
-
/** Whom a program in the tool node `id` may write to. */
|
|
6996
|
-
const scriptContactsOf = (id, nodes, channels) => {
|
|
6997
|
-
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
6998
|
-
const owner = byId.get(id)?.parent;
|
|
6999
|
-
return [.../* @__PURE__ */ new Set([...owner !== void 0 && isSession(byId.get(owner)) ? [owner] : [], ...channelPeersOf(id, channels).filter((peer) => isSession(byId.get(peer)))])];
|
|
7000
|
-
};
|
|
7001
|
-
/** Whether a program in a tool node may write to the session `id`. */
|
|
7002
|
-
const hearsFromScripts = (id, nodes, channels) => nodes.some((node) => isTool(node) && scriptContactsOf(node.id, nodes, channels).includes(id));
|
|
7003
|
-
// ../core/src/run/results.ts
|
|
7004
|
-
/** Control nodes pass their input through; a join's input is its output. */
|
|
7005
|
-
const passThrough = (input) => ({
|
|
7006
|
-
output: input ?? {},
|
|
7007
|
-
ok: true
|
|
7008
|
-
});
|
|
7092
|
+
// ../core/src/run/spending.ts
|
|
7009
7093
|
/**
|
|
7010
7094
|
* A result with only what this activation spent (ADR-046): a session's report
|
|
7011
7095
|
* holds its total so far, while its tokens are this process's own. `reported`
|
|
@@ -7031,11 +7115,23 @@ const withOwnSpend = (id, result, reported) => {
|
|
|
7031
7115
|
const createSpend = (budgetUsd, earlier, limits) => {
|
|
7032
7116
|
const reported = new Map(earlier?.reported);
|
|
7033
7117
|
let spent = earlier?.total ?? 0;
|
|
7034
|
-
|
|
7118
|
+
/** What running sessions last reported, not yet in `spent`, by `node/session`. */
|
|
7119
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
7120
|
+
const committed = () => spent + [...inFlight.values()].reduce((sum, cost) => sum + cost, 0);
|
|
7121
|
+
const budgetLeft = () => budgetUsd === void 0 ? void 0 : budgetUsd - committed();
|
|
7035
7122
|
return {
|
|
7036
7123
|
total: () => spent,
|
|
7037
7124
|
/** What a session has spent in this activation, from its total so far. */
|
|
7038
7125
|
ownOf: (id, sessionId, total) => unreported(reported, `${id}/${sessionId}`, total),
|
|
7126
|
+
/** A running session said what it has spent so far. */
|
|
7127
|
+
report: (id, sessionId, total) => {
|
|
7128
|
+
const key = `${id}/${sessionId}`;
|
|
7129
|
+
inFlight.set(key, unreported(reported, key, total));
|
|
7130
|
+
},
|
|
7131
|
+
/** The node ended: `count` has what its sessions last reported. */
|
|
7132
|
+
settled: (id) => {
|
|
7133
|
+
for (const key of inFlight.keys()) if (key.startsWith(`${id}/`)) inFlight.delete(key);
|
|
7134
|
+
},
|
|
7039
7135
|
/** What a session may spend: the least of the budget and any quota. */
|
|
7040
7136
|
left: () => {
|
|
7041
7137
|
const known = [budgetLeft(), limits?.().leftUsd].filter((left) => left !== void 0);
|
|
@@ -7054,7 +7150,7 @@ const createSpend = (budgetUsd, earlier, limits) => {
|
|
|
7054
7150
|
status: "quota_exhausted",
|
|
7055
7151
|
reason: blocked
|
|
7056
7152
|
};
|
|
7057
|
-
return budgetUsd !== void 0 &&
|
|
7153
|
+
return budgetUsd !== void 0 && committed() >= budgetUsd ? {
|
|
7058
7154
|
status: "budget_exhausted",
|
|
7059
7155
|
reason: `the run's budget of $${String(budgetUsd)} is spent`
|
|
7060
7156
|
} : null;
|
|
@@ -7073,6 +7169,56 @@ const createSpend = (budgetUsd, earlier, limits) => {
|
|
|
7073
7169
|
};
|
|
7074
7170
|
/** A run's spending, from what the scheduler is given. */
|
|
7075
7171
|
const spendOf = (budgetUsd, options) => createSpend(budgetUsd, options.replay?.earlier, options.limits);
|
|
7172
|
+
// ../core/src/run/gather.ts
|
|
7173
|
+
const asList = (value) => Array.isArray(value) ? value : [];
|
|
7174
|
+
/** `before` then `now`, leaving out an entry already there. */
|
|
7175
|
+
const joined = (before, now) => {
|
|
7176
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7177
|
+
const out = [];
|
|
7178
|
+
for (const entry of [...asList(before), ...asList(now)]) {
|
|
7179
|
+
const key = JSON.stringify(entry);
|
|
7180
|
+
if (seen.has(key)) continue;
|
|
7181
|
+
seen.add(key);
|
|
7182
|
+
out.push(entry);
|
|
7183
|
+
}
|
|
7184
|
+
return out;
|
|
7185
|
+
};
|
|
7186
|
+
/**
|
|
7187
|
+
* `result` with each gathered field holding every round's entries so far.
|
|
7188
|
+
* `previous` is the node's output from its last round, already gathered.
|
|
7189
|
+
*/
|
|
7190
|
+
const joinRounds = (fields, previous, result) => {
|
|
7191
|
+
if (fields.length === 0 || previous === void 0) return result;
|
|
7192
|
+
const output = { ...result.output };
|
|
7193
|
+
for (const field of fields) output[field] = joined(previous[field], result.output[field]);
|
|
7194
|
+
return {
|
|
7195
|
+
...result,
|
|
7196
|
+
output
|
|
7197
|
+
};
|
|
7198
|
+
};
|
|
7199
|
+
/** `result` with `node`'s `output.gather` fields joined to its earlier rounds. */
|
|
7200
|
+
const gatherRounds = (node, previous, result) => joinRounds(gatheredFieldsOf(node), previous, result);
|
|
7201
|
+
/**
|
|
7202
|
+
* What `activation`'s rounds reported so far, to gather onto. A spawned
|
|
7203
|
+
* instance gathers nothing earlier: its id (`impl-2`) is reused by a different
|
|
7204
|
+
* worker in the leader's next round. A delegated child that hasn't run since a
|
|
7205
|
+
* resume starts from what it reported before it, since its leader's recorded
|
|
7206
|
+
* rounds are handed back whole and never run it again.
|
|
7207
|
+
*/
|
|
7208
|
+
const previousOf = (activation, current, replay) => {
|
|
7209
|
+
const { brief, template, id } = activation;
|
|
7210
|
+
if (brief !== void 0 && template !== void 0) return void 0;
|
|
7211
|
+
if (current !== void 0 || brief === void 0) return current;
|
|
7212
|
+
return replay?.lastOutput?.(id);
|
|
7213
|
+
};
|
|
7214
|
+
/** Gathers each activation's result onto what its node reported before. */
|
|
7215
|
+
const createGatherer = (outputs, replay, nodeOf) => (activation, result) => gatherRounds(nodeOf(activation), previousOf(activation, outputs[activation.id], replay), result);
|
|
7216
|
+
// ../core/src/run/results.ts
|
|
7217
|
+
/** Control nodes pass their input through; a join's input is its output. */
|
|
7218
|
+
const passThrough = (input) => ({
|
|
7219
|
+
output: input ?? {},
|
|
7220
|
+
ok: true
|
|
7221
|
+
});
|
|
7076
7222
|
const recordRun = (entry, result) => {
|
|
7077
7223
|
entry.runs += 1;
|
|
7078
7224
|
entry.ok = result.ok;
|
|
@@ -7270,10 +7416,10 @@ const sourceOf = (forEach, scope) => {
|
|
|
7270
7416
|
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7271
7417
|
/**
|
|
7272
7418
|
* What `results` keeps of an item: its declared fields (a program's are under
|
|
7273
|
-
* `json`), its summary,
|
|
7274
|
-
* stays in the item's own `node.finished`.
|
|
7419
|
+
* `json`), its summary, Ork's own branch and worktree, and how a program
|
|
7420
|
+
* failed. The full output stays in the item's own `node.finished`.
|
|
7275
7421
|
*/
|
|
7276
|
-
const entryOf = (declared, output) => {
|
|
7422
|
+
const entryOf = (declared, { ok, output, error }) => {
|
|
7277
7423
|
const json = isRecord$1(output["json"]) ? output["json"] : {};
|
|
7278
7424
|
const fields = Object.keys(declared ?? {}).map((field) => [field, Object.hasOwn(json, field) ? json[field] : output[field]]);
|
|
7279
7425
|
const own = [
|
|
@@ -7282,7 +7428,12 @@ const entryOf = (declared, output) => {
|
|
|
7282
7428
|
"worktree",
|
|
7283
7429
|
"merge"
|
|
7284
7430
|
].map((field) => [field, output[field]]);
|
|
7285
|
-
|
|
7431
|
+
const failed = !ok && typeof output["exitCode"] === "number" ? [["exitCode", output["exitCode"]], ["error", error]] : [];
|
|
7432
|
+
return Object.fromEntries([
|
|
7433
|
+
...fields,
|
|
7434
|
+
...own,
|
|
7435
|
+
...failed
|
|
7436
|
+
].filter(([, value]) => value !== void 0));
|
|
7286
7437
|
};
|
|
7287
7438
|
/** Whether an item passed but its work could not reach its owner. */
|
|
7288
7439
|
const mergeFailed = ({ output }) => isRecord$1(output["merge"]) && output["merge"]["ok"] === false;
|
|
@@ -7326,13 +7477,18 @@ const createFanOut = (deps) => {
|
|
|
7326
7477
|
};
|
|
7327
7478
|
if (!listed.ok) return failure(listed.error);
|
|
7328
7479
|
let { list } = listed;
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7480
|
+
const recordList = () => {
|
|
7481
|
+
options.onEvent({
|
|
7482
|
+
type: "each.listed",
|
|
7483
|
+
node: id,
|
|
7484
|
+
round,
|
|
7485
|
+
count: list.length,
|
|
7486
|
+
list,
|
|
7487
|
+
...recorded === null && options.replay !== void 0 ? { anew: true } : {}
|
|
7488
|
+
});
|
|
7489
|
+
};
|
|
7490
|
+
recordList();
|
|
7491
|
+
const signal = stops.signalFor(id);
|
|
7336
7492
|
if ("maxItems" in forEach && list.length > forEach.maxItems) {
|
|
7337
7493
|
options.onEvent({
|
|
7338
7494
|
type: "each.exceeded",
|
|
@@ -7341,22 +7497,23 @@ const createFanOut = (deps) => {
|
|
|
7341
7497
|
count: list.length,
|
|
7342
7498
|
maxItems: forEach.maxItems
|
|
7343
7499
|
});
|
|
7344
|
-
const chosen = await deps.loops.overList(id, list, forEach.maxItems, round);
|
|
7500
|
+
const chosen = await deps.loops.overList(id, list, forEach.maxItems, round, signal);
|
|
7345
7501
|
if (chosen === null) {
|
|
7346
7502
|
const reason = `the list of ${id} has ${String(list.length)} items, over maxItems ${String(forEach.maxItems)}`;
|
|
7347
|
-
return {
|
|
7503
|
+
return stops.ended(id, {
|
|
7348
7504
|
...failure(reason),
|
|
7349
7505
|
limited: reason,
|
|
7350
7506
|
stopsAs: "failed"
|
|
7351
|
-
};
|
|
7507
|
+
});
|
|
7352
7508
|
}
|
|
7509
|
+
const narrowed = chosen.length !== list.length;
|
|
7353
7510
|
list = chosen;
|
|
7511
|
+
if (narrowed) recordList();
|
|
7354
7512
|
}
|
|
7355
7513
|
const node = nodeOf(activation);
|
|
7356
7514
|
const session = node !== void 0 && needsSlot(node);
|
|
7357
7515
|
const spends = node !== void 0 && spendsBudget(node);
|
|
7358
7516
|
const declared = node === void 0 ? void 0 : declaredOutputOf(node);
|
|
7359
|
-
const signal = stops.signalFor(id);
|
|
7360
7517
|
const results = list.map(() => ({}));
|
|
7361
7518
|
const statuses = list.map(() => "skipped");
|
|
7362
7519
|
const failures = [];
|
|
@@ -7378,7 +7535,7 @@ const createFanOut = (deps) => {
|
|
|
7378
7535
|
},
|
|
7379
7536
|
round
|
|
7380
7537
|
});
|
|
7381
|
-
results[index] = entryOf(declared, result
|
|
7538
|
+
results[index] = entryOf(declared, result);
|
|
7382
7539
|
statuses[index] = statusOf(result);
|
|
7383
7540
|
if (!result.ok) failures.push(`${itemId}: ${result.error ?? "failed"}`);
|
|
7384
7541
|
else if (mergeFailed(result)) failures.push(`${itemId}: its work could not be merged`);
|
|
@@ -7614,6 +7771,10 @@ const nodeContext = (deps, activation, cwd, round) => {
|
|
|
7614
7771
|
...options.calls === void 0 ? {} : { calls: options.calls },
|
|
7615
7772
|
emit: (event) => {
|
|
7616
7773
|
deps.ports.owners.seen(id, event, cwd);
|
|
7774
|
+
if (event.type === "session.finished" && event.fork !== true) {
|
|
7775
|
+
const { sessionId, usage } = event.outcome;
|
|
7776
|
+
deps.spend.report(id, sessionId, usage.costUsd);
|
|
7777
|
+
}
|
|
7617
7778
|
options.onEvent({
|
|
7618
7779
|
type: "node.event",
|
|
7619
7780
|
id,
|
|
@@ -7675,6 +7836,27 @@ const resumeOf = (node, sessionId) => {
|
|
|
7675
7836
|
if (node.type === "claude.subagent") return void 0;
|
|
7676
7837
|
return (node.session ?? (node.type === "claude.leader" ? "resume" : "fresh")) === "resume" ? sessionId : void 0;
|
|
7677
7838
|
};
|
|
7839
|
+
// ../core/src/store/nest-items.ts
|
|
7840
|
+
/** Whether a tree node is an item of a fanned node (`build.2`). */
|
|
7841
|
+
const isItem = (node) => node.template !== void 0 && /\.\d+$/u.test(node.id);
|
|
7842
|
+
/**
|
|
7843
|
+
* Moves each item of a node with `forEach` (`build.2`) under its node, in list
|
|
7844
|
+
* order, from beside it or under its owner, where the events put it.
|
|
7845
|
+
*/
|
|
7846
|
+
const nestItems = (roots) => {
|
|
7847
|
+
const all = [];
|
|
7848
|
+
const collect = (node) => {
|
|
7849
|
+
all.push(node);
|
|
7850
|
+
for (const child of node.children) collect(child);
|
|
7851
|
+
};
|
|
7852
|
+
for (const root of roots) collect(root);
|
|
7853
|
+
const items = all.filter((node) => isItem(node));
|
|
7854
|
+
const home = new Map(all.map((node) => [node.id, node]));
|
|
7855
|
+
const isMoved = (node) => items.includes(node) && home.has(node.template ?? "");
|
|
7856
|
+
for (const node of all) node.children = node.children.filter((c) => !isMoved(c));
|
|
7857
|
+
for (const item of items) home.get(item.template ?? "")?.children.push(item);
|
|
7858
|
+
return roots.filter((root) => !isMoved(root));
|
|
7859
|
+
};
|
|
7678
7860
|
// ../core/src/store/process-age.ts
|
|
7679
7861
|
/**
|
|
7680
7862
|
* @file Telling a process from a later one that got its id. A process id comes
|
|
@@ -7847,10 +8029,14 @@ const templateOf = (id, declared) => {
|
|
|
7847
8029
|
const base = /^(?<base>.+)[-.]\d+$/u.exec(id)?.groups?.["base"];
|
|
7848
8030
|
return base !== void 0 && declared.has(base) ? base : void 0;
|
|
7849
8031
|
};
|
|
8032
|
+
const setItemsWaiting = (nodes, of) => {
|
|
8033
|
+
for (const node of nodes) if (node.template === of && isItem(node)) node.state = "waiting";
|
|
8034
|
+
};
|
|
7850
8035
|
/** Folds a run's events into its ownership tree; roots in declared order. */
|
|
7851
8036
|
const runTree = (events) => {
|
|
7852
8037
|
const nodes = /* @__PURE__ */ new Map();
|
|
7853
8038
|
const declared = /* @__PURE__ */ new Map();
|
|
8039
|
+
const listedRounds = /* @__PURE__ */ new Map();
|
|
7854
8040
|
for (const event of events) if (event.type === "run.started") for (const info of event.nodes) {
|
|
7855
8041
|
if (nodes.has(info.id)) continue;
|
|
7856
8042
|
const node = blank(info.id, info.type, info.parent);
|
|
@@ -7911,6 +8097,13 @@ const runTree = (events) => {
|
|
|
7911
8097
|
} else if (event.type === "node.skipped") {
|
|
7912
8098
|
const node = nodes.get(event.id);
|
|
7913
8099
|
if (node !== void 0) node.state = "skipped";
|
|
8100
|
+
} else if (event.type === "each.listed") {
|
|
8101
|
+
const node = nodes.get(event.node);
|
|
8102
|
+
if (node !== void 0) node.listed = event.count;
|
|
8103
|
+
if (event.round > (listedRounds.get(event.node) ?? 0)) {
|
|
8104
|
+
listedRounds.set(event.node, event.round);
|
|
8105
|
+
setItemsWaiting(nodes.values(), event.node);
|
|
8106
|
+
}
|
|
7914
8107
|
} else if (event.type === "call.started") {
|
|
7915
8108
|
const node = nodes.get(event.node);
|
|
7916
8109
|
if (node !== void 0) node.calledRun = event.run;
|
|
@@ -9338,6 +9531,7 @@ const startRun = (options) => {
|
|
|
9338
9531
|
loops
|
|
9339
9532
|
})
|
|
9340
9533
|
});
|
|
9534
|
+
const gather = createGatherer(outputs, options.replay, nodeOf);
|
|
9341
9535
|
/** Runs one activation and records it; routing is the caller's. */
|
|
9342
9536
|
const perform = async (activation) => {
|
|
9343
9537
|
const { id } = activation;
|
|
@@ -9350,8 +9544,9 @@ const startRun = (options) => {
|
|
|
9350
9544
|
const agent = graph.agents.get(activation.template ?? id);
|
|
9351
9545
|
onEvent(startedEvent(activation, type, round, agent));
|
|
9352
9546
|
}
|
|
9353
|
-
const result = spend.count(id, stops.ended(id, recorded ?? await execute(activation, round)), recorded !== null);
|
|
9547
|
+
const result = gather(activation, spend.count(id, stops.ended(id, recorded ?? await execute(activation, round)), recorded !== null));
|
|
9354
9548
|
if (result.limited !== void 0) stop(result.stopsAs ?? "quota_exhausted", result.limited);
|
|
9549
|
+
spend.settled(id);
|
|
9355
9550
|
holds.finished(id);
|
|
9356
9551
|
ports.finished(id);
|
|
9357
9552
|
clock.leave(id);
|
|
@@ -11171,10 +11366,11 @@ const mailOf = (events) => {
|
|
|
11171
11366
|
* (ADR-026). Each node's recorded results, in order, up to its first that
|
|
11172
11367
|
* failed or never finished, are handed back by the replay, so the scheduler
|
|
11173
11368
|
* rebuilds outputs, rounds, joins and loop counts without running them; the
|
|
11174
|
-
* rest run again, and so does everything a new result feeds.
|
|
11175
|
-
*
|
|
11176
|
-
*
|
|
11177
|
-
*
|
|
11369
|
+
* rest run again, and so does everything a new result feeds. A run resumed
|
|
11370
|
+
* more than once reads as one history: a later result replaces the earlier
|
|
11371
|
+
* one at its place. Also: the run's inputs and settings, its worktrees, the
|
|
11372
|
+
* answers to its loops, and a fanned node's lists and its items' results by
|
|
11373
|
+
* round (RFC-013). `replay.ts` hands them back.
|
|
11178
11374
|
*/
|
|
11179
11375
|
const listKey = (node, round) => `${node}/${String(round)}`;
|
|
11180
11376
|
/** How a run ended that cut its working nodes off, rather than failing them. */
|
|
@@ -11184,6 +11380,8 @@ const CUTTING = /* @__PURE__ */ new Set([
|
|
|
11184
11380
|
"quota_exhausted",
|
|
11185
11381
|
"timed_out"
|
|
11186
11382
|
]);
|
|
11383
|
+
/** Whether `id` is an item (`<node>.<index>`) of `node`. */
|
|
11384
|
+
const isItemOf = (id, node) => id.startsWith(`${node}.`) && /^\d+$/u.test(id.slice(node.length + 1));
|
|
11187
11385
|
/** Reads a run's events back into what `ork resume` needs. */
|
|
11188
11386
|
const resumeState = (events) => {
|
|
11189
11387
|
const history = /* @__PURE__ */ new Map();
|
|
@@ -11206,18 +11404,36 @@ const resumeState = (events) => {
|
|
|
11206
11404
|
let ended;
|
|
11207
11405
|
/** Nodes whose last activation did not finish well, and its session. */
|
|
11208
11406
|
const unfinished = /* @__PURE__ */ new Map();
|
|
11407
|
+
/** A new run of `id`'s `round` replaces what is recorded there and after. */
|
|
11408
|
+
const supersede = (id, round) => {
|
|
11409
|
+
fanOuts.items.get(id)?.delete(round);
|
|
11410
|
+
const kept = history.get(id)?.slice(0, round - 1);
|
|
11411
|
+
if (kept === void 0) return;
|
|
11412
|
+
if (kept.length === 0) history.delete(id);
|
|
11413
|
+
else history.set(id, kept);
|
|
11414
|
+
};
|
|
11415
|
+
/** A list resolved anew: what its items did came from another list. */
|
|
11416
|
+
const forgetItems = (node, round) => {
|
|
11417
|
+
for (const [id, rounds] of fanOuts.items) if (isItemOf(id, node)) rounds.delete(round);
|
|
11418
|
+
for (const id of unfinished.keys()) if (isItemOf(id, node)) unfinished.delete(id);
|
|
11419
|
+
};
|
|
11209
11420
|
for (const event of events) {
|
|
11210
11421
|
if (event.type === "run.started") {
|
|
11211
11422
|
inputs = event.inputs ?? inputs;
|
|
11212
11423
|
settings = event.settings ?? settings;
|
|
11424
|
+
closed.clear();
|
|
11213
11425
|
continue;
|
|
11214
11426
|
}
|
|
11215
11427
|
if (event.type === "node.started") {
|
|
11216
|
-
if (event.owner === void 0)
|
|
11428
|
+
if (event.owner === void 0) {
|
|
11429
|
+
open.set(event.id, event.item === void 0 ? {} : { item: true });
|
|
11430
|
+
supersede(event.id, event.round);
|
|
11431
|
+
}
|
|
11217
11432
|
continue;
|
|
11218
11433
|
}
|
|
11219
11434
|
if (event.type === "each.listed") {
|
|
11220
11435
|
fanOuts.lists.set(listKey(event.node, event.round), event.list);
|
|
11436
|
+
if (event.anew === true) forgetItems(event.node, event.round);
|
|
11221
11437
|
continue;
|
|
11222
11438
|
}
|
|
11223
11439
|
if (event.type === "node.event") {
|
|
@@ -11237,6 +11453,7 @@ const resumeState = (events) => {
|
|
|
11237
11453
|
if (current.sessionId !== void 0) unfinished.set(event.id, current.sessionId);
|
|
11238
11454
|
continue;
|
|
11239
11455
|
}
|
|
11456
|
+
unfinished.delete(event.id);
|
|
11240
11457
|
const result = {
|
|
11241
11458
|
ok: true,
|
|
11242
11459
|
output: event.output
|
|
@@ -13442,6 +13659,13 @@ const syncFrom = async (worktree, from) => {
|
|
|
13442
13659
|
return snapshot(worktree);
|
|
13443
13660
|
};
|
|
13444
13661
|
// ../core/src/worktree/upstream.ts
|
|
13662
|
+
/** Where `id` worked: its own place, or for a fanned node each item's. */
|
|
13663
|
+
const placesOf = (id, placeOf) => {
|
|
13664
|
+
const own = placeOf.get(id);
|
|
13665
|
+
if (own !== void 0) return [own];
|
|
13666
|
+
const item = `${id}.`;
|
|
13667
|
+
return [...placeOf].filter(([other]) => other.startsWith(item)).map(([, place]) => place);
|
|
13668
|
+
};
|
|
13445
13669
|
/**
|
|
13446
13670
|
* The one place `id`'s upstream nodes worked in other than `root`, or
|
|
13447
13671
|
* `undefined` when there is none or they disagree. `placeOf` is where each node
|
|
@@ -13460,8 +13684,7 @@ const upstreamPlace = (graph, id, placeOf, root) => {
|
|
|
13460
13684
|
queue.push(...(node?.incoming ?? []).map((e) => e.from));
|
|
13461
13685
|
continue;
|
|
13462
13686
|
}
|
|
13463
|
-
const place
|
|
13464
|
-
if (place !== void 0 && place !== root) places.add(place);
|
|
13687
|
+
for (const place of placesOf(from, placeOf)) if (place !== root) places.add(place);
|
|
13465
13688
|
}
|
|
13466
13689
|
const [only, other] = places;
|
|
13467
13690
|
return other === void 0 ? only : void 0;
|
|
@@ -13667,6 +13890,18 @@ const closeWorktrees = async (options) => {
|
|
|
13667
13890
|
}
|
|
13668
13891
|
return kept;
|
|
13669
13892
|
};
|
|
13893
|
+
// ../core/src/worktree/conflict.ts
|
|
13894
|
+
const withConflict = (result, conflicts, into) => ({
|
|
13895
|
+
...result,
|
|
13896
|
+
output: {
|
|
13897
|
+
...result.output,
|
|
13898
|
+
merge: {
|
|
13899
|
+
ok: false,
|
|
13900
|
+
conflicts,
|
|
13901
|
+
into
|
|
13902
|
+
}
|
|
13903
|
+
}
|
|
13904
|
+
});
|
|
13670
13905
|
// ../core/src/worktree/workspace.ts
|
|
13671
13906
|
/**
|
|
13672
13907
|
* @file Where each node of a run works (ADR-020), under the project's git rules
|
|
@@ -13679,6 +13914,7 @@ const closeWorktrees = async (options) => {
|
|
|
13679
13914
|
* its owner: a merge, a patch, or not at all. A spawned worker works like the
|
|
13680
13915
|
* declared child it is an instance of.
|
|
13681
13916
|
*/
|
|
13917
|
+
const ITEM_FAILED = "item failed; kept for inspection";
|
|
13682
13918
|
const createWorkspace = (options) => {
|
|
13683
13919
|
const { graph, runId, root, head, onEvent } = options;
|
|
13684
13920
|
const rules = gitRulesOf(graph.workflow);
|
|
@@ -13826,6 +14062,8 @@ const createWorkspace = (options) => {
|
|
|
13826
14062
|
}
|
|
13827
14063
|
const next = await settle(id, id, worktree, result);
|
|
13828
14064
|
outputs.set(id, next.output);
|
|
14065
|
+
if (!next.ok && templateOf.has(id) && !unmerged.has(id)) unmerged.set(id, ITEM_FAILED);
|
|
14066
|
+
if (next.ok && unmerged.get(id) === ITEM_FAILED) unmerged.delete(id);
|
|
13829
14067
|
const owner = ownerOf(id);
|
|
13830
14068
|
const ownerTree = owner === void 0 ? void 0 : worktrees.get(owner);
|
|
13831
14069
|
if (!next.ok || owner === void 0 || ownerTree === void 0) return next;
|
|
@@ -13843,17 +14081,7 @@ const createWorkspace = (options) => {
|
|
|
13843
14081
|
const files = outcome.conflicts.join(", ");
|
|
13844
14082
|
unmerged.set(id, `merge conflict${files === "" ? "" : ` in ${files}`}`);
|
|
13845
14083
|
event(id, "conflict", worktree, files === "" ? outcome.error : files);
|
|
13846
|
-
return
|
|
13847
|
-
...next,
|
|
13848
|
-
output: {
|
|
13849
|
-
...next.output,
|
|
13850
|
-
merge: {
|
|
13851
|
-
ok: false,
|
|
13852
|
-
conflicts: outcome.conflicts,
|
|
13853
|
-
into: nameOf(ownerTree)
|
|
13854
|
-
}
|
|
13855
|
-
}
|
|
13856
|
-
};
|
|
14084
|
+
return withConflict(next, outcome.conflicts, nameOf(ownerTree));
|
|
13857
14085
|
};
|
|
13858
14086
|
const close = () => closeWorktrees({
|
|
13859
14087
|
root,
|
|
@@ -13873,4 +14101,4 @@ const createWorkspace = (options) => {
|
|
|
13873
14101
|
close
|
|
13874
14102
|
};
|
|
13875
14103
|
};
|
|
13876
|
-
export { adoptedCalls as $,
|
|
14104
|
+
export { adoptedCalls as $, settingsOf as $n, normalizeTitle as $t, gitOk as A, workflowDirOf as An, runScope as At, peerOf as B, applyEdits as Bn, MCP_FILE as Bt, workflowTextHash as C, inRepoPlace as Cn, runTree as Ct, inspectProject as D, projectsHome as Dn, stillTheSameProcess as Dt, BRANCH_PREFIX as E, placeFor as En, processAlive as Et, createCalls as F, guardedBase as Fn, nodeSays as Ft, requestPause as G, renderJsonSchema as Gn, FEEDBACK_TARGETS as Gt, unlinkNodes as H, readDocument as Hn, serversIn as Ht, addressOf as I, plainPathInside as In, declaredOutputOf as It, backfillUsage as J, markValue as Jn, runGh as Jt, requestStop as K, parseWorkflow as Kn, DISCUSSIONS as Kt, endLink as L, writePlainFile as Ln, PROJECT_FILE as Lt, clearGitLocalEnv as M, projectRootOf as Mn, declaredProblem as Mt, parseInputPairs as N, LESSON_CAP_TOKENS as Nn, calledWorkflows as Nt, removeWorktree as O, runDirOf as On, isItem as Ot, resolveInputs as P, estimateTokens as Pn, envReadsOf as Pt, text as Q, expandEdge as Qn, fingerprint as Qt, endLinksOf as R, skillsIn as Rn, gitRulesOf as Rt, readProjectTriggers as S, hasInRepoFiles as Sn, runStatus as St, watchingPid as T, placeFiles as Tn, ownProcessStart as Tt, flattenTree as U, beforeOf as Un, FEEDBACK_KINDS as Ut, tellNode as V, newWorkflowText as Vn, mcpNotText as Vt, requestContinue as W, programsRunBy as Wn, FEEDBACK_SEVERITIES as Wt, eventOf as X, WorkflowSchema as Xn, publishPost as Xt, usageQueries as Y, EdgeInputSchema as Yn, sendPost as Yt, inTransaction as Z, CheckReportSchema as Zn, createFeedback as Zt, lateTriggers as _, createAgent as _n, MESSAGE_LIMIT as _t, limitsNow as a, serveRun as an, INTEGRATIONS as ar, followEvents as at, triggerStanding as b, loadAgents as bn, liveStatus as bt, usd as c, wakeRun as cn, AGENT_TOOL_LAYOUTS as cr, planNodeSession as ct, runningRunIds as d, planAgentMigration as dn, firstLine as dr, parseReport as dt, targetOf as en, NodeSchema as er, detachResume as et, runningScript as f, applySteps as fn, EXPERIMENTS as fr, reportSchemaFor as ft, parseVersion as g, toolsInUse as gn, startRun as gt, isNewer as h, toolLinks as hn, walkSummary as ht, NOT_STARTED as i, readCondition as in, timeoutMs as ir, detachRun as it, lines as j, inside as jn, scriptContactsOf as jt, git as k, shownPath as kn, nestItems as kt, planUpgrade as l, maskReplacer as ln, ORK_LAYOUT as lr, namingNoDriver as lt, compareVersions as m, linkTools as mn, knownExperiments as mr, walkChecks as mt, resolveBase as n, parseTemplate as nn, isToolNode as nr, listKey as nt, limitsReport as o, socketPath as on, TOOL_PREFIX as or, explainWorkflowText as ot, detectInstaller as p, planMoveInto as pn, experimentsOn as pr, checkWorkflowText as pt, tellLiveRun as q, serializeWorkflow as qn, planPost as qt, capsHere as r, renderTemplate as rn, NodeIdSchema as rr, DETACHED_ENV as rt, planReason as s, socketRoom as sn, integrationFor as sr, explainSession as st, createWorkspace as t, evaluate as tn, isClaudeNode as tr, resumable as tt, releaseNotes as u, maskSecrets as un, CLAUDE_DRIVERS as ur, sessionDriver as ut, triggersReport as v, deleteAgent as vn, userMessageText as vt, takeWatchLock as w, newProjectId as wn, templateOf as wt, declaredTriggers as x, PROJECT_MODES as xn, pausedOf as xt, createWatcher as y, catalogAgent as yn, heldTimeout as yt, linkNodes as z, WorkflowEditSchema as zn, loadProject as zt };
|