@krapht/ork 1.0.0-alpha.12 → 1.0.0-alpha.13
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__-BJbE3Zvp.mjs → __build_info__-Da73_nBs.mjs} +3 -3
- package/dist/{attach-BmV5ckb9.mjs → attach-E6NNyA3T.mjs} +4 -4
- package/dist/bin.mjs +114 -55
- package/dist/{builtin-files-w0YULJ4c.mjs → builtin-files-n6o6oWpB.mjs} +2 -2
- package/dist/{clean-D8GI_2K-.mjs → clean-Bh4KZgzc.mjs} +1 -1
- package/dist/{completion-CzElB8nS.mjs → completion-SNNNxnv9.mjs} +6 -6
- package/dist/{context-BAwgVSX5.mjs → context-DHudLA5e.mjs} +4 -4
- package/dist/{decide-DlbyctOE.mjs → decide-DuIHACvH.mjs} +1 -1
- package/dist/{find-p05wlbmu.mjs → find-CjsNYAAf.mjs} +1 -1
- package/dist/{format-Dni98cbh.mjs → format-C60TFpRe.mjs} +1 -1
- package/dist/{help-groups-DYP_zMRP.mjs → help-groups-CyfPWlST.mjs} +4 -0
- package/dist/{in-a-run-BI-hLIl_.mjs → in-a-run-BYjTXaST.mjs} +1 -1
- package/dist/{limits-CElmhowV.mjs → limits-C5QKZNLT.mjs} +2 -2
- package/dist/{list-zW2xKuGr.mjs → list-BUb5Uc3H.mjs} +1 -1
- package/dist/{mcp-CM3b65AZ.mjs → mcp-bcctR-wc.mjs} +6 -6
- package/dist/once-DPQu5Q3H.mjs +76 -0
- package/dist/{pause-BuS83WAI.mjs → pause-BPvlDIvz.mjs} +3 -3
- package/dist/{relay-DAsJA8wF.mjs → relay-C8W0t_zc.mjs} +1 -1
- package/dist/{remove-Dh7D_hVn.mjs → remove-BkFWGVYo.mjs} +3 -3
- package/dist/run-CQIfq918.mjs +192 -0
- package/dist/{send-MFEQZH31.mjs → send-CaZlGG5a.mjs} +2 -2
- package/dist/{src-CdDTQLdQ.mjs → src-BeR6f70A.mjs} +424 -206
- package/dist/{src-S9yK2OHU.mjs → src-CGJGL1sZ.mjs} +7 -7
- package/dist/{src-D1QedD5y.mjs → src-CqRqsb2t.mjs} +44 -7
- package/dist/{src-BfxkJOYm.mjs → src-D93tHZ5B.mjs} +110 -35
- package/dist/{store-B2OaE4ID.mjs → store-CM3cERGl.mjs} +14 -5
- package/dist/ui/THIRD_PARTY_LICENSES.txt +18 -0
- package/dist/ui/assets/app-DYRC0V4p.js +111 -0
- package/dist/ui/assets/{index-BL7Ad0OA.js → index-CWwm8HTG.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-C4Ys26oO.mjs → ui-D2Ydofq0.mjs} +3 -3
- package/dist/{usage-C2zx-X6q.mjs → usage-6WIowblz.mjs} +4 -4
- package/dist/{watch-DzCeqM05.mjs → watch-uOU_WeBI.mjs} +5 -5
- package/dist/{workflows-Rr5w90A0.mjs → workflows-7tQdMnrA.mjs} +2 -2
- package/dist/{worktrees-f6SAbum5.mjs → worktrees-ZCII298F.mjs} +3 -3
- package/package.json +1 -1
- package/dist/ui/assets/app-CAoZtku5.js +0 -75
|
@@ -175,7 +175,8 @@ const claudeCode = {
|
|
|
175
175
|
midTurnMessages: true,
|
|
176
176
|
reportSchema: true,
|
|
177
177
|
turnLimit: true,
|
|
178
|
-
mcpServers: true
|
|
178
|
+
mcpServers: true,
|
|
179
|
+
subagents: true
|
|
179
180
|
},
|
|
180
181
|
proven: true,
|
|
181
182
|
onePerCheckout: false,
|
|
@@ -257,7 +258,8 @@ const AGENT_DRIVERS = [
|
|
|
257
258
|
midTurnMessages: false,
|
|
258
259
|
reportSchema: false,
|
|
259
260
|
turnLimit: true,
|
|
260
|
-
mcpServers: true
|
|
261
|
+
mcpServers: true,
|
|
262
|
+
subagents: false
|
|
261
263
|
}
|
|
262
264
|
}),
|
|
263
265
|
installedCli("command-code", "cmd", "cmd login", {
|
|
@@ -271,7 +273,8 @@ const AGENT_DRIVERS = [
|
|
|
271
273
|
midTurnMessages: false,
|
|
272
274
|
reportSchema: false,
|
|
273
275
|
turnLimit: true,
|
|
274
|
-
mcpServers: false
|
|
276
|
+
mcpServers: false,
|
|
277
|
+
subagents: false
|
|
275
278
|
}
|
|
276
279
|
})
|
|
277
280
|
];
|
|
@@ -624,7 +627,7 @@ const nodeBaseFields = {
|
|
|
624
627
|
};
|
|
625
628
|
/** A message a workflow declares, sent when its node finishes (RFC-005). */
|
|
626
629
|
const NotifyRuleSchema = z.strictObject({
|
|
627
|
-
to: NodeIdSchema.describe("The `
|
|
630
|
+
to: NodeIdSchema.describe("The `agent.leader` or `agent.worker` to write to."),
|
|
628
631
|
when: z.string().optional().describe("Send only when this holds. It reads what an edge's when: does, plus ok and error. Default: every finish."),
|
|
629
632
|
body: z.string().optional().describe("The message, a template. Default: Ork's one line on the result.")
|
|
630
633
|
});
|
|
@@ -663,12 +666,17 @@ const runFields = {
|
|
|
663
666
|
script: z.string().optional().describe("The text of a short program, instead of command. It takes no templates: values arrive through env:, as $NAME, so a value cannot become code."),
|
|
664
667
|
interpreter: z.string().optional().describe("What runs `script:`: a program and arguments. Default: `sh -eu`.")
|
|
665
668
|
};
|
|
669
|
+
/** `once:` on a `verify:` and on a tool node. */
|
|
670
|
+
const onceField = z.boolean().optional().describe("Skip the command when the same check already passed on identical files (a hash of the working tree, the command, Node's version); a pass is recorded. Default: false.");
|
|
666
671
|
const StepSchema = z.strictObject({
|
|
667
672
|
...runFields,
|
|
668
673
|
...toolProgramFields,
|
|
669
674
|
timeout: TimeoutSchema.optional().describe("How long the program may run, e.g. 5m; then it is killed and the step fails.")
|
|
670
675
|
});
|
|
671
|
-
const VerifySchema = StepSchema.extend({
|
|
676
|
+
const VerifySchema = StepSchema.extend({
|
|
677
|
+
attempts: z.number().int().min(0).max(5).optional().describe("How many times a failed check resumes the session to fix it before the node fails. Default: 1."),
|
|
678
|
+
once: onceField
|
|
679
|
+
}).describe("A program that must pass before the node's report is accepted. It runs in the node's checkout.");
|
|
672
680
|
/** The fields a leader or worker takes its steps from. */
|
|
673
681
|
const stepFields = {
|
|
674
682
|
before: StepSchema.optional().describe("A program to run in the node's checkout before its session starts: if it fails, the node fails and no model turn is spent. Its prompt can read {{before.stdout}}, {{before.json.*}}."),
|
|
@@ -687,6 +695,7 @@ const toolFields = {
|
|
|
687
695
|
cwd: z.string().optional(),
|
|
688
696
|
timeout: TimeoutSchema.optional().describe("How long the command may run, e.g. 5m; then it is killed and the node fails. With watch:, how long one look may take."),
|
|
689
697
|
watch: WatchSchema.optional().describe("Run the command again and again until a condition holds, with no model turn."),
|
|
698
|
+
once: onceField,
|
|
690
699
|
...toolProgramFields,
|
|
691
700
|
...notifyFields,
|
|
692
701
|
...forEachFields
|
|
@@ -774,7 +783,7 @@ const claudeFields = {
|
|
|
774
783
|
...agentSettings
|
|
775
784
|
};
|
|
776
785
|
const ClaudeLeaderNodeSchema = z.strictObject({
|
|
777
|
-
type: z.literal("
|
|
786
|
+
type: z.literal("agent.leader"),
|
|
778
787
|
...claudeFields,
|
|
779
788
|
mcp: McpNamesSchema,
|
|
780
789
|
...notifyFields,
|
|
@@ -783,7 +792,7 @@ const ClaudeLeaderNodeSchema = z.strictObject({
|
|
|
783
792
|
questions: z.enum(["owner", "user"]).optional().describe("Who a child's ask_user goes to first: `owner` asks a fork of this leader's session, which answers when what it was given settles it or passes it on; `user` (the default) asks the person.")
|
|
784
793
|
});
|
|
785
794
|
const ClaudeWorkerNodeSchema = z.strictObject({
|
|
786
|
-
type: z.literal("
|
|
795
|
+
type: z.literal("agent.worker"),
|
|
787
796
|
...claudeFields,
|
|
788
797
|
mcp: McpNamesSchema,
|
|
789
798
|
...notifyFields,
|
|
@@ -791,7 +800,7 @@ const ClaudeWorkerNodeSchema = z.strictObject({
|
|
|
791
800
|
...stepFields
|
|
792
801
|
});
|
|
793
802
|
const ClaudeSubagentNodeSchema = z.strictObject({
|
|
794
|
-
type: z.literal("
|
|
803
|
+
type: z.literal("agent.subagent"),
|
|
795
804
|
...claudeFields,
|
|
796
805
|
parent: NodeIdSchema.describe("The Claude node whose session hosts this sub-agent."),
|
|
797
806
|
description: z.string().describe("When the parent should use this sub-agent.")
|
|
@@ -863,9 +872,9 @@ const NodeSchema = z.discriminatedUnion("type", [
|
|
|
863
872
|
]);
|
|
864
873
|
const isToolNode = (node) => node.type.startsWith(TOOL_PREFIX);
|
|
865
874
|
const CLAUDE_NODE_TYPES = [
|
|
866
|
-
"
|
|
867
|
-
"
|
|
868
|
-
"
|
|
875
|
+
"agent.leader",
|
|
876
|
+
"agent.worker",
|
|
877
|
+
"agent.subagent"
|
|
869
878
|
];
|
|
870
879
|
const isClaudeNode = (node) => CLAUDE_NODE_TYPES.includes(node.type);
|
|
871
880
|
// ../schema/src/agents.ts
|
|
@@ -1199,6 +1208,16 @@ const markValue = (value) => {
|
|
|
1199
1208
|
const pad = line.startsWith("`") || line.endsWith("`") ? " " : "";
|
|
1200
1209
|
return `${fence}${pad}${line}${pad}${fence}`;
|
|
1201
1210
|
};
|
|
1211
|
+
// ../schema/src/retired-types.ts
|
|
1212
|
+
/**
|
|
1213
|
+
* The node types RFC-015 renamed, and the one that replaces each: refused in a
|
|
1214
|
+
* workflow with the way out (`ork migrate`), never read as the new one.
|
|
1215
|
+
*/
|
|
1216
|
+
const RETIRED_NODE_TYPES = {
|
|
1217
|
+
"claude.leader": "agent.leader",
|
|
1218
|
+
"claude.worker": "agent.worker",
|
|
1219
|
+
"claude.subagent": "agent.subagent"
|
|
1220
|
+
};
|
|
1202
1221
|
// ../schema/src/issue-messages.ts
|
|
1203
1222
|
const NODE_TYPES = NodeSchema.options.map((o) => o.shape.type.value);
|
|
1204
1223
|
const isMap$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1221,6 +1240,8 @@ const listRuleMessage = (issue, value) => issue.code === "invalid_union" && isMa
|
|
|
1221
1240
|
/** The text for a node whose `type` is missing or unknown, else `null`. */
|
|
1222
1241
|
const nodeTypeMessage = (issue, value) => {
|
|
1223
1242
|
if (issue.code !== "invalid_union" || issue.discriminator !== "type") return null;
|
|
1243
|
+
const renamed = typeof value === "string" && Object.hasOwn(RETIRED_NODE_TYPES, value) ? RETIRED_NODE_TYPES[value] : void 0;
|
|
1244
|
+
if (renamed !== void 0) return `${markValue(String(value))} was renamed to ${markValue(renamed)}; run \`ork migrate\``;
|
|
1224
1245
|
const types = NODE_TYPES.map((type) => markValue(type)).join(", ");
|
|
1225
1246
|
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}`;
|
|
1226
1247
|
};
|
|
@@ -1448,6 +1469,22 @@ const renderJsonSchema = () => {
|
|
|
1448
1469
|
title: "Ork workflow"
|
|
1449
1470
|
}, void 0, 2)}\n`;
|
|
1450
1471
|
};
|
|
1472
|
+
// ../schema/src/model-driver.ts
|
|
1473
|
+
/**
|
|
1474
|
+
* The text before the first `.` is the driver only when it is one of `drivers`
|
|
1475
|
+
* (ids have no dots); the rest goes to the tool as it is. Any other value is a
|
|
1476
|
+
* plain model for the run's driver, so `deepseek/deepseek-v4.1-flash` stays
|
|
1477
|
+
* plain.
|
|
1478
|
+
*/
|
|
1479
|
+
const driverOf = (model, drivers) => {
|
|
1480
|
+
const dot = model.indexOf(".");
|
|
1481
|
+
if (dot <= 0) return { model };
|
|
1482
|
+
const id = model.slice(0, dot);
|
|
1483
|
+
return drivers.includes(id) ? {
|
|
1484
|
+
driver: id,
|
|
1485
|
+
model: model.slice(dot + 1)
|
|
1486
|
+
} : { model };
|
|
1487
|
+
};
|
|
1451
1488
|
// ../schema/src/node-programs.ts
|
|
1452
1489
|
/**
|
|
1453
1490
|
* @file Which programs a node runs: its own, its `before:` and its `verify:`,
|
|
@@ -1475,7 +1512,7 @@ const programsOf = (node) => {
|
|
|
1475
1512
|
}] : []];
|
|
1476
1513
|
};
|
|
1477
1514
|
/** The `before:` a leader or worker runs: its own, or the workflow's default. */
|
|
1478
|
-
const beforeOf = (node, defaults) => node.type === "
|
|
1515
|
+
const beforeOf = (node, defaults) => node.type === "agent.leader" || node.type === "agent.worker" ? node.before ?? defaults?.before : void 0;
|
|
1479
1516
|
/** Each program a node runs, with what it takes from the workflow's defaults. */
|
|
1480
1517
|
const programsRunBy = (node, defaults) => {
|
|
1481
1518
|
const inherited = beforeOf(node, defaults);
|
|
@@ -1484,6 +1521,55 @@ const programsRunBy = (node, defaults) => {
|
|
|
1484
1521
|
program: inherited
|
|
1485
1522
|
}, ...programsOf(node)] : programsOf(node);
|
|
1486
1523
|
};
|
|
1524
|
+
// ../schema/src/rename-types.ts
|
|
1525
|
+
/**
|
|
1526
|
+
* @file Rewrites a workflow file that names the node types RFC-015 renamed
|
|
1527
|
+
* (`claude.worker` and the others), for `ork migrate`: the types are renamed
|
|
1528
|
+
* in the YAML document, so the file keeps its comments and its own layout,
|
|
1529
|
+
* and the result is checked against the workflow schema. Models and every
|
|
1530
|
+
* other value are left as they are.
|
|
1531
|
+
*/
|
|
1532
|
+
/**
|
|
1533
|
+
* The file with its retired node types renamed, keeping comments and layout,
|
|
1534
|
+
* and with how many of each; `null` when it names none (nothing to do, so a
|
|
1535
|
+
* second run changes nothing). A file that, once renamed, does not validate is
|
|
1536
|
+
* not rewritten: `{ ok: false }` says why.
|
|
1537
|
+
*/
|
|
1538
|
+
const renameNodeTypes = (text) => {
|
|
1539
|
+
const doc = parseDocument(text);
|
|
1540
|
+
const nodes = doc.errors.length === 0 ? doc.get("nodes", true) : void 0;
|
|
1541
|
+
const renamed = {};
|
|
1542
|
+
if (isMap(nodes)) for (const pair of nodes.items) {
|
|
1543
|
+
const type = isMap(pair.value) ? pair.value.get("type", true) : void 0;
|
|
1544
|
+
if (isScalar(type) && typeof type.value === "string" && Object.hasOwn(RETIRED_NODE_TYPES, type.value)) {
|
|
1545
|
+
const old = type.value;
|
|
1546
|
+
type.value = RETIRED_NODE_TYPES[old] ?? old;
|
|
1547
|
+
renamed[old] = (renamed[old] ?? 0) + 1;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
if (Object.keys(renamed).length === 0) return doc.errors.length > 0 && /claude\.(worker|leader|subagent)/u.test(text) ? {
|
|
1551
|
+
ok: false,
|
|
1552
|
+
reason: doc.errors[0]?.message.split("\n")[0] ?? "not YAML"
|
|
1553
|
+
} : null;
|
|
1554
|
+
const body = doc.toString({
|
|
1555
|
+
lineWidth: 0,
|
|
1556
|
+
indent: 2
|
|
1557
|
+
});
|
|
1558
|
+
const parsed = parseWorkflow(body);
|
|
1559
|
+
if (!parsed.ok) {
|
|
1560
|
+
const first = parsed.diagnostics[0];
|
|
1561
|
+
return {
|
|
1562
|
+
ok: false,
|
|
1563
|
+
reason: `${first?.line === void 0 ? "" : `line ${String(first.line)}: `}${first?.message ?? "does not validate"}`
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
const header = `# yaml-language-server: $schema=${SCHEMA_URL}\n`;
|
|
1567
|
+
return {
|
|
1568
|
+
ok: true,
|
|
1569
|
+
text: body.includes("yaml-language-server") ? body : header + body,
|
|
1570
|
+
renamed
|
|
1571
|
+
};
|
|
1572
|
+
};
|
|
1487
1573
|
// ../schema/src/edit-order.ts
|
|
1488
1574
|
/**
|
|
1489
1575
|
* @file The canonical order of keys anywhere in a workflow, read from the Zod
|
|
@@ -2358,7 +2444,7 @@ const fieldNames = (doc, id, field) => String(doc.getIn([
|
|
|
2358
2444
|
"nodes",
|
|
2359
2445
|
id,
|
|
2360
2446
|
"type"
|
|
2361
|
-
])).startsWith("
|
|
2447
|
+
])).startsWith("agent.") ? [field] : [`json.${field}`, field];
|
|
2362
2448
|
const asValues = (raw) => {
|
|
2363
2449
|
if (!Array.isArray(raw)) return [];
|
|
2364
2450
|
return raw.map((item) => typeof item === "object" && item !== null && "id" in item ? item.id : item);
|
|
@@ -3734,7 +3820,7 @@ const resolveContext = (options) => {
|
|
|
3734
3820
|
const contextDiagnostics = (workflow, options) => {
|
|
3735
3821
|
const seen = /* @__PURE__ */ new Map();
|
|
3736
3822
|
for (const [nodeId, node] of Object.entries(workflow.nodes)) {
|
|
3737
|
-
if (!isClaudeNode(node) || node.type === "
|
|
3823
|
+
if (!isClaudeNode(node) || node.type === "agent.subagent") continue;
|
|
3738
3824
|
const stack = resolveContext({
|
|
3739
3825
|
...options,
|
|
3740
3826
|
workflow,
|
|
@@ -5283,7 +5369,7 @@ const checkMcpServers = (workflow, options) => {
|
|
|
5283
5369
|
const known = options.mcpServers;
|
|
5284
5370
|
if (known === void 0) return [];
|
|
5285
5371
|
return Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
5286
|
-
if (node.type !== "
|
|
5372
|
+
if (node.type !== "agent.leader" && node.type !== "agent.worker") return [];
|
|
5287
5373
|
const own = node.mcp !== void 0;
|
|
5288
5374
|
return (node.mcp ?? workflow.defaults?.mcp ?? []).flatMap((name, index) => {
|
|
5289
5375
|
const problem = nameProblem(id, name, options, known);
|
|
@@ -5494,10 +5580,10 @@ const manyWorktrees = (workflow) => {
|
|
|
5494
5580
|
let count = 0;
|
|
5495
5581
|
for (const node of Object.values(workflow.nodes)) {
|
|
5496
5582
|
if (!isClaudeNode(node)) continue;
|
|
5497
|
-
if (node.type === "
|
|
5583
|
+
if (node.type === "agent.leader" && (node.maxWorkers ?? 0) > 0) return true;
|
|
5498
5584
|
const isolation = node.isolation ?? workflow.defaults?.isolation ?? "worktree";
|
|
5499
5585
|
const fanned = "forEach" in node && node.forEach !== void 0;
|
|
5500
|
-
if (node.type !== "
|
|
5586
|
+
if (node.type !== "agent.subagent" && isolation === "worktree") count += fanned ? 2 : 1;
|
|
5501
5587
|
}
|
|
5502
5588
|
return count > 1;
|
|
5503
5589
|
};
|
|
@@ -5580,7 +5666,7 @@ const gatheredSays = (item) => {
|
|
|
5580
5666
|
* passes its input through, so any field may exist.
|
|
5581
5667
|
*/
|
|
5582
5668
|
const OUTPUT_FIELDS = {
|
|
5583
|
-
"
|
|
5669
|
+
"agent.leader": [
|
|
5584
5670
|
"status",
|
|
5585
5671
|
"summary",
|
|
5586
5672
|
"artifacts",
|
|
@@ -5588,7 +5674,7 @@ const OUTPUT_FIELDS = {
|
|
|
5588
5674
|
"worktree",
|
|
5589
5675
|
"usage"
|
|
5590
5676
|
],
|
|
5591
|
-
"
|
|
5677
|
+
"agent.worker": [
|
|
5592
5678
|
"status",
|
|
5593
5679
|
"summary",
|
|
5594
5680
|
"artifacts",
|
|
@@ -5596,7 +5682,7 @@ const OUTPUT_FIELDS = {
|
|
|
5596
5682
|
"worktree",
|
|
5597
5683
|
"usage"
|
|
5598
5684
|
],
|
|
5599
|
-
"
|
|
5685
|
+
"agent.subagent": [
|
|
5600
5686
|
"status",
|
|
5601
5687
|
"summary",
|
|
5602
5688
|
"artifacts"
|
|
@@ -5790,7 +5876,7 @@ const itemSays = (node) => {
|
|
|
5790
5876
|
canFail: true,
|
|
5791
5877
|
canDeclare: false
|
|
5792
5878
|
};
|
|
5793
|
-
if (!node.type.startsWith("
|
|
5879
|
+
if (!node.type.startsWith("agent.")) return NOTHING;
|
|
5794
5880
|
const declared = declaredFields(node, "").map((field) => withFail(field));
|
|
5795
5881
|
return {
|
|
5796
5882
|
fields: [
|
|
@@ -5994,7 +6080,7 @@ const compileEdges = (workflow) => {
|
|
|
5994
6080
|
diagnostics.push(error("edge.unknown-node", `edge \`${label}\`: unknown node ${missing.map((m) => `\`${m}\``).join(", ")}`, path));
|
|
5995
6081
|
continue;
|
|
5996
6082
|
}
|
|
5997
|
-
const subagent = [edge.from, edge.to].find((id) => workflow.nodes[id]?.type === "
|
|
6083
|
+
const subagent = [edge.from, edge.to].find((id) => workflow.nodes[id]?.type === "agent.subagent");
|
|
5998
6084
|
if (subagent !== void 0) {
|
|
5999
6085
|
diagnostics.push(error("edge.subagent", `edge \`${label}\`: \`${subagent}\` is a sub-agent; it runs inside its parent's session and takes no edges`, path));
|
|
6000
6086
|
continue;
|
|
@@ -6189,14 +6275,14 @@ const envOutsideEnv = (segments, path) => error("template.env", `\`${describePat
|
|
|
6189
6275
|
/**
|
|
6190
6276
|
* Ownership rules:
|
|
6191
6277
|
*
|
|
6192
|
-
* -
|
|
6278
|
+
* - An `agent.subagent` is owned by an `agent.leader` or `agent.worker` (it runs
|
|
6193
6279
|
* inside that node's session);
|
|
6194
|
-
* - Every other owned node is owned by
|
|
6280
|
+
* - Every other owned node is owned by an `agent.leader`;
|
|
6195
6281
|
* - The tree has no cycles;
|
|
6196
6282
|
* - A node that sets `isolation: continue` neither owns nor is owned: it works in
|
|
6197
6283
|
* the worktree its input came from, outside the tree's merges.
|
|
6198
6284
|
* - A leader's `questions:` is about its children's questions, so a leader with
|
|
6199
|
-
* no `
|
|
6285
|
+
* no `agent.leader` or `agent.worker` child can't set it.
|
|
6200
6286
|
*/
|
|
6201
6287
|
const checkOwnership = (workflow) => {
|
|
6202
6288
|
const diagnostics = [];
|
|
@@ -6211,9 +6297,9 @@ const checkOwnership = (workflow) => {
|
|
|
6211
6297
|
const owner = nodes[node.parent];
|
|
6212
6298
|
if (node.parent === id) diagnostics.push(error("tree.self", `\`${id}\` cannot own itself`, path));
|
|
6213
6299
|
else if (owner === void 0) diagnostics.push(error("tree.unknown-parent", `unknown parent \`${node.parent}\``, path));
|
|
6214
|
-
else if (node.type === "
|
|
6215
|
-
if (owner.type !== "
|
|
6216
|
-
} else if (owner.type !== "
|
|
6300
|
+
else if (node.type === "agent.subagent") {
|
|
6301
|
+
if (owner.type !== "agent.leader" && owner.type !== "agent.worker") diagnostics.push(error("tree.owner-kind", `a sub-agent runs inside an \`agent.leader\` or \`agent.worker\` session, not a \`${owner.type}\``, path));
|
|
6302
|
+
} else if (owner.type !== "agent.leader") diagnostics.push(error("tree.owner-kind", `only an agent.leader can own a \`${node.type}\` (\`${node.parent}\` is a \`${owner.type}\`)`, path));
|
|
6217
6303
|
}
|
|
6218
6304
|
for (const [id, node] of Object.entries(nodes)) {
|
|
6219
6305
|
if (!("isolation" in node) || node.isolation !== "continue") continue;
|
|
@@ -6225,8 +6311,8 @@ const checkOwnership = (workflow) => {
|
|
|
6225
6311
|
]));
|
|
6226
6312
|
}
|
|
6227
6313
|
for (const [id, node] of Object.entries(nodes)) {
|
|
6228
|
-
if (node.type !== "
|
|
6229
|
-
if (!Object.values(nodes).some((other) => other.parent === id && (other.type === "
|
|
6314
|
+
if (node.type !== "agent.leader" || node.questions === void 0) continue;
|
|
6315
|
+
if (!Object.values(nodes).some((other) => other.parent === id && (other.type === "agent.leader" || other.type === "agent.worker"))) diagnostics.push(error("questions.no-children", `\`${id}\` sets \`questions\`, but owns no \`agent.leader\` or \`agent.worker\` whose questions it could take`, [
|
|
6230
6316
|
"nodes",
|
|
6231
6317
|
id,
|
|
6232
6318
|
"questions"
|
|
@@ -6405,7 +6491,7 @@ const sessionsIn = (workflow, members) => {
|
|
|
6405
6491
|
const parent = workflow.nodes[id]?.parent;
|
|
6406
6492
|
return parent !== void 0 && (members.has(parent) || ownedByMember(parent));
|
|
6407
6493
|
};
|
|
6408
|
-
return Object.entries(workflow.nodes).filter(([id, node]) => isClaudeNode(node) && node.type !== "
|
|
6494
|
+
return Object.entries(workflow.nodes).filter(([id, node]) => isClaudeNode(node) && node.type !== "agent.subagent" && (members.has(id) || ownedByMember(id))).reduce((sum, [, node]) => sum + itemCountOf(node), 0);
|
|
6409
6495
|
};
|
|
6410
6496
|
/** Plain edges beside a loop edge. A first-match router's is its fallback. */
|
|
6411
6497
|
const besideLoops = (workflow, edges, loops) => {
|
|
@@ -6437,6 +6523,71 @@ const checkGroup = (workflow, edges, component) => {
|
|
|
6437
6523
|
};
|
|
6438
6524
|
/** What's wrong with the shape of each group of nodes that loop together. */
|
|
6439
6525
|
const checkLoops = (workflow, edges) => stronglyConnected(Object.keys(workflow.nodes), edges).flatMap((component) => checkGroup(workflow, edges, component));
|
|
6526
|
+
// ../core/src/run/driver.ts
|
|
6527
|
+
/**
|
|
6528
|
+
* @file Which agent driver a session runs on, one rule for every caller (`ork
|
|
6529
|
+
* run`, `--node`, `ork chat`, `ork resume`, called and detached runs): the
|
|
6530
|
+
* driver its model names (`claude-code.opus`, RFC-015), else `--driver`, else
|
|
6531
|
+
* the node's (or its agent's), else the workflow's `defaults.driver` (the
|
|
6532
|
+
* project's included), else the one ready on this machine when the run
|
|
6533
|
+
* started. No driver is written into a workflow for lack of one.
|
|
6534
|
+
*/
|
|
6535
|
+
/** The node types that start a session of their own, and so need a driver. */
|
|
6536
|
+
const SESSION_TYPES$5 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
6537
|
+
/**
|
|
6538
|
+
* A `model` value read once: the driver it names, if any, and what that tool
|
|
6539
|
+
* receives. A plain model (`deepseek/deepseek-v4.1-flash`) names none.
|
|
6540
|
+
*/
|
|
6541
|
+
const modelDriver = (model) => {
|
|
6542
|
+
const parsed = driverOf(model, DRIVER_IDS);
|
|
6543
|
+
return {
|
|
6544
|
+
driver: DRIVER_IDS.find((id) => id === parsed.driver),
|
|
6545
|
+
model: parsed.model
|
|
6546
|
+
};
|
|
6547
|
+
};
|
|
6548
|
+
/**
|
|
6549
|
+
* The model a node asks for as written: its own (or its agent's), else the
|
|
6550
|
+
* default.
|
|
6551
|
+
*/
|
|
6552
|
+
const writtenModel = (node, workflow) => isClaudeNode(node) ? node.model ?? workflow.defaults?.model : void 0;
|
|
6553
|
+
/**
|
|
6554
|
+
* The driver a node's model pins it to, as written. A template
|
|
6555
|
+
* (`{{item.model}}`) pins nothing here: each item's plan reads what it
|
|
6556
|
+
* renders.
|
|
6557
|
+
*/
|
|
6558
|
+
const pinnedDriver = (node, workflow) => {
|
|
6559
|
+
const model = writtenModel(node, workflow);
|
|
6560
|
+
return model === void 0 ? void 0 : modelDriver(model).driver;
|
|
6561
|
+
};
|
|
6562
|
+
/** The driver a workflow names for a node, if it names one. */
|
|
6563
|
+
const namedDriver = (node, workflow) => ("driver" in node ? node.driver : void 0) ?? workflow.defaults?.driver;
|
|
6564
|
+
/**
|
|
6565
|
+
* Why a node's own `driver` and the driver its model names disagree, or
|
|
6566
|
+
* `undefined` when they agree or either is absent. `model` is the value as
|
|
6567
|
+
* written, or as an item rendered it.
|
|
6568
|
+
*/
|
|
6569
|
+
const driverContradiction = (node, model) => {
|
|
6570
|
+
const own = "driver" in node ? node.driver : void 0;
|
|
6571
|
+
if (own === void 0 || model === void 0) return void 0;
|
|
6572
|
+
const pinned = modelDriver(model).driver;
|
|
6573
|
+
return pinned === void 0 || pinned === own ? void 0 : `model \`${model}\` names driver \`${pinned}\`, but the node sets \`driver: ${own}\`; remove one, or make them agree`;
|
|
6574
|
+
};
|
|
6575
|
+
/** The session nodes that name no driver, which take the one ready here. */
|
|
6576
|
+
const namingNoDriver = (workflow) => Object.entries(workflow.nodes).filter(([, node]) => SESSION_TYPES$5.has(node.type) && pinnedDriver(node, workflow) === void 0 && namedDriver(node, workflow) === void 0).map(([id]) => id);
|
|
6577
|
+
/**
|
|
6578
|
+
* The driver a session runs on: `pinned` (its model's prefix), else `override`
|
|
6579
|
+
* (`--driver`), else what the workflow names, else `ready`, the driver the run
|
|
6580
|
+
* found ready when it started (and recorded, for a resume). `undefined` only
|
|
6581
|
+
* when none applies, which a run refuses before it starts.
|
|
6582
|
+
*/
|
|
6583
|
+
const sessionDriver = (choice) => choice.pinned ?? choice.override ?? choice.named ?? choice.ready;
|
|
6584
|
+
/** `sessionDriver` for a node of the workflow, as written. */
|
|
6585
|
+
const nodeDriver = (node, workflow, choice) => sessionDriver({
|
|
6586
|
+
pinned: pinnedDriver(node, workflow),
|
|
6587
|
+
override: choice.override,
|
|
6588
|
+
named: namedDriver(node, workflow),
|
|
6589
|
+
ready: choice.ready
|
|
6590
|
+
});
|
|
6440
6591
|
// ../core/src/graph/gather.ts
|
|
6441
6592
|
/** Whether a declared field is a list: `{ type: array }` or `{ list: … }`. */
|
|
6442
6593
|
const isList = (rule) => listShape(rule) !== void 0 || typeof rule === "object" && rule !== null && "type" in rule && rule.type === "array";
|
|
@@ -6623,7 +6774,7 @@ const optionId = (option) => typeof option === "string" ? option : option.id;
|
|
|
6623
6774
|
* another would fail on every run. It can narrow them, to fail on the rest.
|
|
6624
6775
|
*/
|
|
6625
6776
|
const statusProblems = (id, node) => {
|
|
6626
|
-
if (!isClaudeNode(node) || node.type === "
|
|
6777
|
+
if (!isClaudeNode(node) || node.type === "agent.subagent") return [];
|
|
6627
6778
|
const rule = declaredOutputOf(node)?.["status"];
|
|
6628
6779
|
const list = typeof rule === "object" && rule !== null && "enum" in rule ? rule.enum : rule;
|
|
6629
6780
|
if (!Array.isArray(list)) return [];
|
|
@@ -6636,6 +6787,19 @@ const statusProblems = (id, node) => {
|
|
|
6636
6787
|
"status"
|
|
6637
6788
|
]));
|
|
6638
6789
|
};
|
|
6790
|
+
/**
|
|
6791
|
+
* A node's `driver` and the driver its model names (`claude-code.opus`) must
|
|
6792
|
+
* agree; the model may be the workflow's `defaults.model`.
|
|
6793
|
+
*/
|
|
6794
|
+
const modelDriverProblems = (id, node, workflow) => {
|
|
6795
|
+
const own = isClaudeNode(node) ? node.model : void 0;
|
|
6796
|
+
const problem = driverContradiction(node, writtenModel(node, workflow));
|
|
6797
|
+
return problem === void 0 ? [] : [error("model.driver", own === void 0 ? `${problem} (the model is \`defaults.model\`)` : problem, [
|
|
6798
|
+
"nodes",
|
|
6799
|
+
id,
|
|
6800
|
+
own === void 0 ? "driver" : "model"
|
|
6801
|
+
])];
|
|
6802
|
+
};
|
|
6639
6803
|
/** Per-type checks that need the rest of the graph. */
|
|
6640
6804
|
const checkNodes = (workflow, edges) => {
|
|
6641
6805
|
const diagnostics = [...checkScripts(workflow), ...checkItemSettings(workflow, edges)];
|
|
@@ -6643,8 +6807,8 @@ const checkNodes = (workflow, edges) => {
|
|
|
6643
6807
|
const path = ["nodes", id];
|
|
6644
6808
|
const incoming = edges.filter((e) => e.to === id);
|
|
6645
6809
|
const outgoing = edges.filter((e) => e.from === id);
|
|
6646
|
-
if (node.type === "
|
|
6647
|
-
diagnostics.push(...statusProblems(id, node), ...gatherProblems(id, node));
|
|
6810
|
+
if (node.type === "agent.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"]));
|
|
6811
|
+
diagnostics.push(...statusProblems(id, node), ...gatherProblems(id, node), ...modelDriverProblems(id, node, workflow));
|
|
6648
6812
|
if (node.type === "control.join") {
|
|
6649
6813
|
const sources = new Set(incoming.map((e) => e.from)).size;
|
|
6650
6814
|
if (sources < 2) diagnostics.push(warning("join.inputs", `join \`${id}\` has ${sources} incoming node(s); a join waits for two or more`, path));
|
|
@@ -6696,10 +6860,10 @@ const checkChannels = (workflow) => {
|
|
|
6696
6860
|
}
|
|
6697
6861
|
const bad = [a, b].filter((id) => {
|
|
6698
6862
|
const node = workflow.nodes[id];
|
|
6699
|
-
return !(node?.type === "
|
|
6863
|
+
return !(node?.type === "agent.leader" || node?.type === "agent.worker" || node !== void 0 && isToolNode(node) || node?.type === "workflow.call" && node.node !== void 0);
|
|
6700
6864
|
});
|
|
6701
6865
|
if (bad.length > 0) {
|
|
6702
|
-
diagnostics.push(error("message.endpoint", `${bad.map((id) => `\`${id}\``).join(", ")}: channels connect \`
|
|
6866
|
+
diagnostics.push(error("message.endpoint", `${bad.map((id) => `\`${id}\``).join(", ")}: channels connect \`agent.leader\` and \`agent.worker\` nodes, a tool node, and a \`workflow.call\` that names a node`, path));
|
|
6703
6867
|
continue;
|
|
6704
6868
|
}
|
|
6705
6869
|
const nodeA = workflow.nodes[a];
|
|
@@ -6708,9 +6872,9 @@ const checkChannels = (workflow) => {
|
|
|
6708
6872
|
diagnostics.push(error("message.endpoint", `\`${a}\`, \`${b}\`: a channel needs a session on this side, and a \`workflow.call\` has none`, path));
|
|
6709
6873
|
continue;
|
|
6710
6874
|
}
|
|
6711
|
-
const session = (node) => node?.type === "
|
|
6875
|
+
const session = (node) => node?.type === "agent.leader" || node?.type === "agent.worker";
|
|
6712
6876
|
if ((nodeA !== void 0 && isToolNode(nodeA) || nodeB !== void 0 && isToolNode(nodeB)) && !session(nodeA) && !session(nodeB)) {
|
|
6713
|
-
diagnostics.push(error("message.endpoint", `\`${a}\`, \`${b}\`: a tool node writes to a session, so the other side must be
|
|
6877
|
+
diagnostics.push(error("message.endpoint", `\`${a}\`, \`${b}\`: a tool node writes to a session, so the other side must be an \`agent.leader\` or \`agent.worker\``, path));
|
|
6714
6878
|
continue;
|
|
6715
6879
|
}
|
|
6716
6880
|
if (nodeA?.parent === b || nodeB?.parent === a) diagnostics.push(warning("message.implicit", [nodeA, nodeB].some((node) => node !== void 0 && isToolNode(node)) ? "a tool node can always write to its owner; this channel is not needed" : "a parent and its child can always message each other; this channel is not needed", path));
|
|
@@ -7072,7 +7236,7 @@ const checkFlow = (workflow, edges, rules) => [
|
|
|
7072
7236
|
...checkConditions(workflow, edges, rules)
|
|
7073
7237
|
];
|
|
7074
7238
|
// ../core/src/graph/notify.ts
|
|
7075
|
-
const RECEIVERS = /* @__PURE__ */ new Set(["
|
|
7239
|
+
const RECEIVERS = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
7076
7240
|
const compileRules = (workflow) => {
|
|
7077
7241
|
const rules = /* @__PURE__ */ new Map();
|
|
7078
7242
|
const diagnostics = [];
|
|
@@ -7090,7 +7254,7 @@ const compileRules = (workflow) => {
|
|
|
7090
7254
|
if (hidden.length > 0 && (rule.when !== void 0 || rule.body !== void 0)) diagnostics.push(warning("notify.shadowed", `in a rule, ${hidden.join(" and ")} mean how the node ended, not its field; read that as nodes.${id}.output.${hidden[0] ?? ""}`, path));
|
|
7091
7255
|
const target = workflow.nodes[rule.to];
|
|
7092
7256
|
if (rule.to === id) diagnostics.push(error("notify.self", "a node cannot notify itself", [...path, "to"]));
|
|
7093
|
-
else if (target === void 0 || !RECEIVERS.has(target.type)) diagnostics.push(error("notify.target", `\`${rule.to}\`: a rule goes to
|
|
7257
|
+
else if (target === void 0 || !RECEIVERS.has(target.type)) diagnostics.push(error("notify.target", `\`${rule.to}\`: a rule goes to an \`agent.leader\` or \`agent.worker\`, which has a session to read it`, [...path, "to"]));
|
|
7094
7258
|
if (target !== void 0 && forEachOf$1(target) !== void 0) diagnostics.push(error("forEach.notify", `\`${rule.to}\` has \`forEach\`: a rule goes to one session, and its items don't exist until its list is resolved.`, [...path, "to"]));
|
|
7095
7259
|
const entry = { to: rule.to };
|
|
7096
7260
|
if (rule.when !== void 0) {
|
|
@@ -7173,6 +7337,101 @@ const compileWorkflow = (authored, options = {}) => {
|
|
|
7173
7337
|
diagnostics
|
|
7174
7338
|
};
|
|
7175
7339
|
};
|
|
7340
|
+
// ../core/src/worktree/git-env.ts
|
|
7341
|
+
/**
|
|
7342
|
+
* @file Git's local environment variables, the ones a git hook sets (`GIT_DIR`,
|
|
7343
|
+
* `GIT_INDEX_FILE`, …), and an environment without them. Ork finds a project
|
|
7344
|
+
* by its path, never through these, so an `ork` started from a hook clears
|
|
7345
|
+
* them at its entry, and core's git runner leaves them out for callers that
|
|
7346
|
+
* don't start there (specs/worktrees.md).
|
|
7347
|
+
*/
|
|
7348
|
+
/**
|
|
7349
|
+
* What `git rev-parse --local-env-vars` prints (git 2.39.5). A fixed list, so
|
|
7350
|
+
* no git is spawned at every start; a test fails when the installed git names
|
|
7351
|
+
* one that is missing here.
|
|
7352
|
+
*/
|
|
7353
|
+
const GIT_LOCAL_ENV = [
|
|
7354
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
7355
|
+
"GIT_CONFIG",
|
|
7356
|
+
"GIT_CONFIG_PARAMETERS",
|
|
7357
|
+
"GIT_CONFIG_COUNT",
|
|
7358
|
+
"GIT_OBJECT_DIRECTORY",
|
|
7359
|
+
"GIT_DIR",
|
|
7360
|
+
"GIT_WORK_TREE",
|
|
7361
|
+
"GIT_IMPLICIT_WORK_TREE",
|
|
7362
|
+
"GIT_GRAFT_FILE",
|
|
7363
|
+
"GIT_INDEX_FILE",
|
|
7364
|
+
"GIT_NO_REPLACE_OBJECTS",
|
|
7365
|
+
"GIT_REPLACE_REF_BASE",
|
|
7366
|
+
"GIT_PREFIX",
|
|
7367
|
+
"GIT_INTERNAL_SUPER_PREFIX",
|
|
7368
|
+
"GIT_SHALLOW_FILE",
|
|
7369
|
+
"GIT_COMMON_DIR"
|
|
7370
|
+
];
|
|
7371
|
+
/** A copy of `env` without git's local variables. */
|
|
7372
|
+
const withoutGitLocalEnv = (env) => {
|
|
7373
|
+
const clean = { ...env };
|
|
7374
|
+
for (const name of GIT_LOCAL_ENV) delete clean[name];
|
|
7375
|
+
return clean;
|
|
7376
|
+
};
|
|
7377
|
+
/** Removes git's local variables from `env` in place (the CLI's entry). */
|
|
7378
|
+
const clearGitLocalEnv = (env = process.env) => {
|
|
7379
|
+
for (const name of GIT_LOCAL_ENV) delete env[name];
|
|
7380
|
+
};
|
|
7381
|
+
// ../core/src/worktree/git.ts
|
|
7382
|
+
/**
|
|
7383
|
+
* @file A minimal `git` runner for the worktree manager: arguments without a
|
|
7384
|
+
* shell, results that never throw, and none of git's local variables (a
|
|
7385
|
+
* hook's `GIT_DIR` or `GIT_INDEX_FILE`), so it acts on the repository at
|
|
7386
|
+
* `cwd` (specs/worktrees.md). Commands in one folder run one at a time: two
|
|
7387
|
+
* children handed their owner's work at once both stage it (`git add -A`),
|
|
7388
|
+
* and git refuses the second while the first holds the index's lock.
|
|
7389
|
+
*/
|
|
7390
|
+
/** The last command queued in each folder, so the next one waits for it. */
|
|
7391
|
+
const queues = /* @__PURE__ */ new Map();
|
|
7392
|
+
/** Runs `task` after every command queued earlier in the same folder. */
|
|
7393
|
+
const inTurn = (cwd, task) => {
|
|
7394
|
+
const key = path.resolve(cwd);
|
|
7395
|
+
const next = (queues.get(key) ?? Promise.resolve()).then(task, task);
|
|
7396
|
+
queues.set(key, next);
|
|
7397
|
+
next.finally(() => {
|
|
7398
|
+
if (queues.get(key) === next) queues.delete(key);
|
|
7399
|
+
});
|
|
7400
|
+
return next;
|
|
7401
|
+
};
|
|
7402
|
+
const runGit = (cwd, args, env) => new Promise((resolve) => {
|
|
7403
|
+
execFile("git", args, {
|
|
7404
|
+
cwd,
|
|
7405
|
+
env: {
|
|
7406
|
+
...withoutGitLocalEnv(process.env),
|
|
7407
|
+
...env
|
|
7408
|
+
},
|
|
7409
|
+
maxBuffer: 33554432,
|
|
7410
|
+
encoding: "utf8"
|
|
7411
|
+
}, (error, stdout, stderr) => {
|
|
7412
|
+
resolve({
|
|
7413
|
+
code: error === null ? 0 : typeof error.code === "number" ? error.code : -1,
|
|
7414
|
+
stdout,
|
|
7415
|
+
stderr: error !== null && !existsSync(cwd) ? `the folder ${cwd} doesn't exist` : stderr || (error?.message ?? "")
|
|
7416
|
+
});
|
|
7417
|
+
});
|
|
7418
|
+
});
|
|
7419
|
+
/**
|
|
7420
|
+
* Runs `git` with arguments (no shell) in `cwd`. Never throws. `env` adds
|
|
7421
|
+
* variables, such as a temporary `GIT_INDEX_FILE`.
|
|
7422
|
+
*/
|
|
7423
|
+
const git = (cwd, args, env = {}) => inTurn(cwd, () => runGit(cwd, args, env));
|
|
7424
|
+
/** Like `git`, but rejects with git's message when the command fails. */
|
|
7425
|
+
const gitOk = async (cwd, args, env = {}) => {
|
|
7426
|
+
const result = await git(cwd, args, env);
|
|
7427
|
+
if (result.code !== 0) {
|
|
7428
|
+
const reason = result.stderr.trim().split("\n").at(-1) ?? "";
|
|
7429
|
+
throw new Error(`git ${args[0] ?? ""} failed: ${reason}`);
|
|
7430
|
+
}
|
|
7431
|
+
return result.stdout;
|
|
7432
|
+
};
|
|
7433
|
+
/** Output lines without the trailing empty one. */
|
|
7434
|
+
const lines = (text) => text.split("\n").filter((line) => line !== "");
|
|
7176
7435
|
// ../core/src/machine/priority.ts
|
|
7177
7436
|
/**
|
|
7178
7437
|
* @file Starting a child at the project's priority (`priority:`): `low`, the
|
|
@@ -7382,7 +7641,7 @@ const checkCallTargets = (workflow, own, registered) => Object.entries(workflow.
|
|
|
7382
7641
|
const found = [];
|
|
7383
7642
|
if (node.node !== void 0) {
|
|
7384
7643
|
const type = called.nodes[node.node]?.type;
|
|
7385
|
-
if (type !== "
|
|
7644
|
+
if (type !== "agent.leader" && type !== "agent.worker") found.push(error("call.node", `call \`${id}\`: \`${node.node}\` is not a leader or worker of \`${node.workflow}\``, [...at, "node"]));
|
|
7386
7645
|
}
|
|
7387
7646
|
for (const [name, input] of Object.entries(called.inputs ?? {})) if (input.required === true && input.default === void 0 && node.inputs?.[name] === void 0) found.push(error("call.input", `call \`${id}\`: \`${node.workflow}\` requires the input \`${name}\``, [...at, "inputs"]));
|
|
7388
7647
|
return found;
|
|
@@ -8085,7 +8344,7 @@ const createFanOut = (deps) => {
|
|
|
8085
8344
|
};
|
|
8086
8345
|
// ../core/src/run/delegation.ts
|
|
8087
8346
|
/** Node types a leader can hand work to. */
|
|
8088
|
-
const DELEGABLE = /* @__PURE__ */ new Set(["
|
|
8347
|
+
const DELEGABLE = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
8089
8348
|
/** A child a leader delegated to: waits for a slot, runs, reports back. */
|
|
8090
8349
|
const createRunChild = (hooks) => async (child, brief) => {
|
|
8091
8350
|
await hooks.slots.take();
|
|
@@ -8140,9 +8399,9 @@ const createOwnership = (hooks) => {
|
|
|
8140
8399
|
if (node.description !== void 0) child.description = node.description;
|
|
8141
8400
|
return [child];
|
|
8142
8401
|
});
|
|
8143
|
-
const templates = children.filter((c) => c.type === "
|
|
8402
|
+
const templates = children.filter((c) => c.type === "agent.worker").map((c) => c.id);
|
|
8144
8403
|
const leaderNode = owner?.node;
|
|
8145
|
-
const maxWorkers = leaderNode?.type === "
|
|
8404
|
+
const maxWorkers = leaderNode?.type === "agent.leader" ? leaderNode.maxWorkers ?? 0 : 0;
|
|
8146
8405
|
const spawned = [];
|
|
8147
8406
|
/** Runs the accepted jobs in parallel; the slot goes to them meanwhile. */
|
|
8148
8407
|
const runJobs = async (jobs) => {
|
|
@@ -8282,7 +8541,7 @@ const nodeContext = (deps, activation, cwd, round, clock) => {
|
|
|
8282
8541
|
budgetLeftUsd: deps.spend.left(),
|
|
8283
8542
|
budgetLeftNow: deps.spend.budgetLeft,
|
|
8284
8543
|
reserveBudget: (usd) => {
|
|
8285
|
-
deps.spend.reserve(id, usd, deps.nodeOf(activation)?.type !== "
|
|
8544
|
+
deps.spend.reserve(id, usd, deps.nodeOf(activation)?.type !== "agent.leader");
|
|
8286
8545
|
},
|
|
8287
8546
|
budgetFreed: (signal) => deps.spend.freed(id, signal),
|
|
8288
8547
|
quota: deps.spend.quota,
|
|
@@ -8303,7 +8562,16 @@ const nodeContext = (deps, activation, cwd, round, clock) => {
|
|
|
8303
8562
|
event
|
|
8304
8563
|
});
|
|
8305
8564
|
},
|
|
8306
|
-
signal: deps.stops.signalFor(id, template)
|
|
8565
|
+
signal: deps.stops.signalFor(id, template),
|
|
8566
|
+
skipped: (check, { at, who }) => {
|
|
8567
|
+
options.onEvent({
|
|
8568
|
+
type: "verify.skipped",
|
|
8569
|
+
id,
|
|
8570
|
+
check,
|
|
8571
|
+
at,
|
|
8572
|
+
who
|
|
8573
|
+
});
|
|
8574
|
+
}
|
|
8307
8575
|
};
|
|
8308
8576
|
const owner = ownerPort(deps, id, template);
|
|
8309
8577
|
if (owner !== void 0) context.owner = owner;
|
|
@@ -8314,7 +8582,7 @@ const nodeContext = (deps, activation, cwd, round, clock) => {
|
|
|
8314
8582
|
if (activation.item !== void 0) context.item = activation.item;
|
|
8315
8583
|
const resume = options.replay?.cutOff?.(activation) ?? resumeOf(deps.nodeOf(activation), deps.sessionIds.get(id) ?? options.replay?.previousSession?.(activation));
|
|
8316
8584
|
if (resume !== void 0) context.resume = resume;
|
|
8317
|
-
if (deps.nodeOf(activation)?.type === "
|
|
8585
|
+
if (deps.nodeOf(activation)?.type === "agent.leader") context.ownership = createOwnership({
|
|
8318
8586
|
graph,
|
|
8319
8587
|
leader: id,
|
|
8320
8588
|
runChild: deps.runChild,
|
|
@@ -8357,12 +8625,12 @@ const ownerPort = (deps, id, template) => deps.ports.owners.portFor(id, template
|
|
|
8357
8625
|
*/
|
|
8358
8626
|
const resumeOf = (node, sessionId) => {
|
|
8359
8627
|
if (node === void 0 || !isClaudeNode(node)) return void 0;
|
|
8360
|
-
if (node.type === "
|
|
8361
|
-
return (node.session ?? (node.type === "
|
|
8628
|
+
if (node.type === "agent.subagent") return void 0;
|
|
8629
|
+
return (node.session ?? (node.type === "agent.leader" ? "resume" : "fresh")) === "resume" ? sessionId : void 0;
|
|
8362
8630
|
};
|
|
8363
8631
|
// ../core/src/run/contacts.ts
|
|
8364
|
-
const SESSION_TYPES$
|
|
8365
|
-
const isSession$1 = (node) => SESSION_TYPES$
|
|
8632
|
+
const SESSION_TYPES$4 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
8633
|
+
const isSession$1 = (node) => SESSION_TYPES$4.has(node?.type ?? "");
|
|
8366
8634
|
const isTool = (node) => node?.type.startsWith("tool.") ?? false;
|
|
8367
8635
|
const channelPeersOf = (id, channels) => channels.flatMap(([a, b]) => {
|
|
8368
8636
|
if (a === id) return [b];
|
|
@@ -9260,7 +9528,7 @@ const NEVER = new Promise(() => {});
|
|
|
9260
9528
|
/** For a rejection no one is left to hear. */
|
|
9261
9529
|
const ignore = () => {};
|
|
9262
9530
|
/** The timeout that applies to a node; a gate's own means something else. */
|
|
9263
|
-
const timeoutOf = (node, workflow) => (isToolNode(node) || isClaudeNode(node) || node.type === "workflow.call" || node.type === "mcp.call") && node.type !== "
|
|
9531
|
+
const timeoutOf = (node, workflow) => (isToolNode(node) || isClaudeNode(node) || node.type === "workflow.call" || node.type === "mcp.call") && node.type !== "agent.subagent" ? node.timeout ?? workflow.defaults?.timeout : void 0;
|
|
9264
9532
|
const createClock = (options) => {
|
|
9265
9533
|
const { graph, holds, running } = options;
|
|
9266
9534
|
/** Nodes between two looks of a `watch:`. */
|
|
@@ -9754,7 +10022,7 @@ const createOwnerQuestions = ({ ownerOf, deliverTo, onEvent }) => {
|
|
|
9754
10022
|
};
|
|
9755
10023
|
};
|
|
9756
10024
|
// ../core/src/run/bus.ts
|
|
9757
|
-
const SESSION_TYPES$
|
|
10025
|
+
const SESSION_TYPES$3 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
9758
10026
|
const createBus = ({ graph, onEvent, mail, relay, slots }) => {
|
|
9759
10027
|
/** Spawned instance -> the declared node it is an instance of. */
|
|
9760
10028
|
const templates = /* @__PURE__ */ new Map();
|
|
@@ -9773,7 +10041,7 @@ const createBus = ({ graph, onEvent, mail, relay, slots }) => {
|
|
|
9773
10041
|
/** Sessions in `wait`: called when a message arrives or the session ends. */
|
|
9774
10042
|
const waiters = /* @__PURE__ */ new Map();
|
|
9775
10043
|
const declared = (id) => graph.nodes.get(templates.get(id) ?? id);
|
|
9776
|
-
const hasSession = (id) => SESSION_TYPES$
|
|
10044
|
+
const hasSession = (id) => SESSION_TYPES$3.has(graph.nodes.get(id)?.node.type ?? "");
|
|
9777
10045
|
const inbox = (id) => {
|
|
9778
10046
|
const existing = inboxes.get(id);
|
|
9779
10047
|
if (existing !== void 0) return existing;
|
|
@@ -9960,7 +10228,7 @@ const createBus = ({ graph, onEvent, mail, relay, slots }) => {
|
|
|
9960
10228
|
* notice when it is released; a node's claims are released when it finishes.
|
|
9961
10229
|
* Claims are advisory: they steer agents, they do not lock files.
|
|
9962
10230
|
*/
|
|
9963
|
-
const SESSION_TYPES$
|
|
10231
|
+
const SESSION_TYPES$2 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
9964
10232
|
const WHOLE = ".";
|
|
9965
10233
|
/**
|
|
9966
10234
|
* A resource as the run compares it: a path from the project root (an absolute
|
|
@@ -9985,7 +10253,7 @@ const writersOf = (graph) => {
|
|
|
9985
10253
|
const isolation = graph.workflow.defaults?.isolation ?? "worktree";
|
|
9986
10254
|
const writers = /* @__PURE__ */ new Set();
|
|
9987
10255
|
for (const [id, { node }] of graph.nodes) {
|
|
9988
|
-
if (!SESSION_TYPES$
|
|
10256
|
+
if (!SESSION_TYPES$2.has(node.type)) continue;
|
|
9989
10257
|
if ("isolation" in node && (node.isolation ?? isolation) === "readonly") continue;
|
|
9990
10258
|
writers.add(id);
|
|
9991
10259
|
}
|
|
@@ -9994,7 +10262,7 @@ const writersOf = (graph) => {
|
|
|
9994
10262
|
const spawnable = (graph, writers) => {
|
|
9995
10263
|
let count = 0;
|
|
9996
10264
|
for (const { node, children } of graph.nodes.values()) {
|
|
9997
|
-
if (node.type !== "
|
|
10265
|
+
if (node.type !== "agent.leader") continue;
|
|
9998
10266
|
if (children.some((child) => writers.has(child))) count += node.maxWorkers ?? 0;
|
|
9999
10267
|
}
|
|
10000
10268
|
return count;
|
|
@@ -10086,7 +10354,7 @@ const createClaims = ({ graph, onEvent, notify }) => {
|
|
|
10086
10354
|
};
|
|
10087
10355
|
// ../core/src/run/owners.ts
|
|
10088
10356
|
/** The node types whose sessions can ask (`ask_user`). */
|
|
10089
|
-
const ASKING = /* @__PURE__ */ new Set(["
|
|
10357
|
+
const ASKING = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
10090
10358
|
const createOwners = (graph) => {
|
|
10091
10359
|
const sessions = /* @__PURE__ */ new Map();
|
|
10092
10360
|
/** The leader that takes `id`'s questions first, if one does. */
|
|
@@ -10094,7 +10362,7 @@ const createOwners = (graph) => {
|
|
|
10094
10362
|
const node = graph.nodes.get(template ?? id)?.node;
|
|
10095
10363
|
if (node?.parent === void 0 || !ASKING.has(node.type)) return void 0;
|
|
10096
10364
|
const owner = graph.nodes.get(node.parent)?.node;
|
|
10097
|
-
return owner?.type === "
|
|
10365
|
+
return owner?.type === "agent.leader" && owner.questions === "owner" ? node.parent : void 0;
|
|
10098
10366
|
};
|
|
10099
10367
|
return {
|
|
10100
10368
|
/** Notes `id`'s own sessions as they start and end; forks aren't its own. */
|
|
@@ -11608,7 +11876,7 @@ const parseReport = (value) => {
|
|
|
11608
11876
|
/**
|
|
11609
11877
|
* @file What a Claude node is asked: its brief, rendered against the run's
|
|
11610
11878
|
* inputs and outputs, the model and effort it runs on (a fanned node's from
|
|
11611
|
-
* its item), and its `
|
|
11879
|
+
* its item), and its `agent.subagent` children as Claude Code subagents of
|
|
11612
11880
|
* its session (the SDK's `agents`, the CLI's `--agents`). A subagent's role
|
|
11613
11881
|
* (its agent's instructions), `instructions` and `prompt` are its system
|
|
11614
11882
|
* prompt, and its `description` says when the session hands it work with the
|
|
@@ -11666,7 +11934,7 @@ const rendered = (text, inputs, outputs) => {
|
|
|
11666
11934
|
/** The subagents of `owner`'s session, by node id, or none. */
|
|
11667
11935
|
const subagentsOf = (workflow, owner, inputs, outputs = {}) => {
|
|
11668
11936
|
const entries = Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
11669
|
-
if (node.type !== "
|
|
11937
|
+
if (node.type !== "agent.subagent" || node.parent !== owner) return [];
|
|
11670
11938
|
if (!isClaudeNode(node)) return [];
|
|
11671
11939
|
const prompt = [
|
|
11672
11940
|
node.agent === void 0 ? "" : workflow.agents?.[node.agent]?.instructions?.trim() ?? "",
|
|
@@ -11677,7 +11945,7 @@ const subagentsOf = (workflow, owner, inputs, outputs = {}) => {
|
|
|
11677
11945
|
description: node.description,
|
|
11678
11946
|
prompt: prompt === "" ? node.description : prompt
|
|
11679
11947
|
};
|
|
11680
|
-
if (node.model !== void 0) spec.model = node.model;
|
|
11948
|
+
if (node.model !== void 0) spec.model = modelDriver(node.model).model;
|
|
11681
11949
|
if (node.maxTurns !== void 0) spec.maxTurns = node.maxTurns;
|
|
11682
11950
|
if (node.tools?.only !== void 0) spec.tools = node.tools.only;
|
|
11683
11951
|
if (node.tools?.deny !== void 0) spec.disallowedTools = node.tools.deny;
|
|
@@ -11685,20 +11953,6 @@ const subagentsOf = (workflow, owner, inputs, outputs = {}) => {
|
|
|
11685
11953
|
});
|
|
11686
11954
|
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
11687
11955
|
};
|
|
11688
|
-
// ../core/src/run/driver.ts
|
|
11689
|
-
/** The node types that start a session of their own, and so need a driver. */
|
|
11690
|
-
const SESSION_TYPES$2 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
|
|
11691
|
-
/** The driver a workflow names for a node, if it names one. */
|
|
11692
|
-
const namedDriver = (node, workflow) => ("driver" in node ? node.driver : void 0) ?? workflow.defaults?.driver;
|
|
11693
|
-
/** The session nodes that name no driver, which take the one ready here. */
|
|
11694
|
-
const namingNoDriver = (workflow) => Object.entries(workflow.nodes).filter(([, node]) => SESSION_TYPES$2.has(node.type) && namedDriver(node, workflow) === void 0).map(([id]) => id);
|
|
11695
|
-
/**
|
|
11696
|
-
* The driver a session runs on: `override` (`--driver`), else what the workflow
|
|
11697
|
-
* names, else `ready`, the driver the run found ready when it started (and
|
|
11698
|
-
* recorded, for a resume). `undefined` only when none applies, which a run
|
|
11699
|
-
* refuses before it starts.
|
|
11700
|
-
*/
|
|
11701
|
-
const sessionDriver = (choice) => choice.override ?? choice.named ?? choice.ready;
|
|
11702
11956
|
// ../core/src/run/harness.ts
|
|
11703
11957
|
const CLAIMS_LINE = "- Other nodes may change this project at the same time. Before you edit a file or folder, `claim` it (a path from the project root); if someone holds it, work on something else or ask them. `release` what you are done with; Ork releases the rest when you finish.";
|
|
11704
11958
|
const HANDOFF = {
|
|
@@ -11823,7 +12077,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11823
12077
|
ok: false,
|
|
11824
12078
|
error: `\`${id}\` is a \`${node.type}\`, not a Claude node`
|
|
11825
12079
|
};
|
|
11826
|
-
if (node.type === "
|
|
12080
|
+
if (node.type === "agent.subagent") return {
|
|
11827
12081
|
ok: false,
|
|
11828
12082
|
error: `\`${id}\` is a sub-agent; it runs inside \`${node.parent}\`'s session`
|
|
11829
12083
|
};
|
|
@@ -11840,6 +12094,12 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11840
12094
|
ok: false,
|
|
11841
12095
|
error: models.error
|
|
11842
12096
|
};
|
|
12097
|
+
const contradiction = driverContradiction(node, models.model);
|
|
12098
|
+
if (contradiction !== void 0) return {
|
|
12099
|
+
ok: false,
|
|
12100
|
+
error: `\`${id}\`: ${contradiction}`
|
|
12101
|
+
};
|
|
12102
|
+
const model = models.model === void 0 ? void 0 : modelDriver(models.model);
|
|
11843
12103
|
const warnings = [];
|
|
11844
12104
|
const brief = renderBrief(node.prompt, options.inputs, options.outputs, options.before, options.item);
|
|
11845
12105
|
if (!brief.ok) return {
|
|
@@ -11881,13 +12141,13 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11881
12141
|
permissionMode: "default",
|
|
11882
12142
|
reportSchema: reportSchemaFor(node)
|
|
11883
12143
|
};
|
|
11884
|
-
if (node.type === "
|
|
12144
|
+
if (node.type === "agent.leader") spec.promptCache = "1h";
|
|
11885
12145
|
const agents = subagentsOf(workflow, declared, options.inputs, options.outputs);
|
|
11886
12146
|
if (agents !== void 0) {
|
|
11887
12147
|
spec.agents = agents;
|
|
11888
12148
|
spec.tools.only?.push(AGENT_TOOL);
|
|
11889
12149
|
}
|
|
11890
|
-
if (
|
|
12150
|
+
if (model !== void 0) spec.model = model.model;
|
|
11891
12151
|
if (models.effort !== void 0) spec.effort = models.effort;
|
|
11892
12152
|
const maxTurns = node.maxTurns ?? defaults.maxTurns;
|
|
11893
12153
|
if (maxTurns !== void 0) spec.maxTurns = maxTurns;
|
|
@@ -11901,6 +12161,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11901
12161
|
ok: true,
|
|
11902
12162
|
spec,
|
|
11903
12163
|
driver: namedDriver(node, workflow),
|
|
12164
|
+
pinned: model?.driver,
|
|
11904
12165
|
mcp,
|
|
11905
12166
|
warnings,
|
|
11906
12167
|
context
|
|
@@ -12556,6 +12817,7 @@ const resumeState = (events) => {
|
|
|
12556
12817
|
let ended;
|
|
12557
12818
|
/** Nodes whose last activation did not finish well, and its session. */
|
|
12558
12819
|
const unfinished = /* @__PURE__ */ new Map();
|
|
12820
|
+
const places = /* @__PURE__ */ new Map();
|
|
12559
12821
|
/** A new run of `id`'s `round` replaces what is recorded there and after. */
|
|
12560
12822
|
const supersede = (id, round) => {
|
|
12561
12823
|
fanOuts.items.get(id)?.delete(round);
|
|
@@ -12592,7 +12854,10 @@ const resumeState = (events) => {
|
|
|
12592
12854
|
const inner = event.event;
|
|
12593
12855
|
const current = open.get(event.id);
|
|
12594
12856
|
if (current === void 0 || inner.fork === true) continue;
|
|
12595
|
-
if (inner.type === "session.started")
|
|
12857
|
+
if (inner.type === "session.started") {
|
|
12858
|
+
current.sessionId = inner.sessionId;
|
|
12859
|
+
if (inner.cwd !== void 0) places.set(event.id, inner.cwd);
|
|
12860
|
+
}
|
|
12596
12861
|
if (inner.type === "session.finished" && inner.outcome.sessionId) current.sessionId = inner.outcome.sessionId;
|
|
12597
12862
|
continue;
|
|
12598
12863
|
}
|
|
@@ -12685,7 +12950,8 @@ const resumeState = (events) => {
|
|
|
12685
12950
|
succeeded,
|
|
12686
12951
|
cutOff: cut ? unfinished : /* @__PURE__ */ new Map(),
|
|
12687
12952
|
mail: mailOf(events),
|
|
12688
|
-
earlier: earlierSpend(events)
|
|
12953
|
+
earlier: earlierSpend(events),
|
|
12954
|
+
places
|
|
12689
12955
|
};
|
|
12690
12956
|
};
|
|
12691
12957
|
// ../core/src/remote/resume.ts
|
|
@@ -13165,7 +13431,7 @@ const requestStop = (store, run, home, node) => request(store, run, withNode("st
|
|
|
13165
13431
|
const requestPause = (store, run, home, node) => request(store, run, withNode("pause", node), pauseRefusal(store, run, node), home);
|
|
13166
13432
|
/** Releases a pause: the run's (and with it every node's), or one node's. */
|
|
13167
13433
|
const requestContinue = (store, run, home, node) => request(store, run, withNode("continue", node), continueRefusal(store, run, node), home);
|
|
13168
|
-
const SESSION_TYPES$1 = /* @__PURE__ */ new Set(["
|
|
13434
|
+
const SESSION_TYPES$1 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
13169
13435
|
const addressOf = ({ run, node }) => `${run}/${node}`;
|
|
13170
13436
|
/** The other side of `link` from `run`'s `node`, or `undefined` if not on it. */
|
|
13171
13437
|
const peerOf = (link, run, node) => {
|
|
@@ -13547,7 +13813,7 @@ const createCalls = (options) => {
|
|
|
13547
13813
|
};
|
|
13548
13814
|
};
|
|
13549
13815
|
// ../core/src/run/driver-queues.ts
|
|
13550
|
-
const isSession = (node) => node.type === "
|
|
13816
|
+
const isSession = (node) => node.type === "agent.leader" || node.type === "agent.worker";
|
|
13551
13817
|
const isolationOf = (node, workflow) => ("isolation" in node ? node.isolation : void 0) ?? workflow.defaults?.isolation ?? "worktree";
|
|
13552
13818
|
/**
|
|
13553
13819
|
* Where a session works: the node whose worktree it uses, `null` for the
|
|
@@ -13619,22 +13885,18 @@ const messageOf$1 = (driver, found) => {
|
|
|
13619
13885
|
};
|
|
13620
13886
|
/**
|
|
13621
13887
|
* The checkouts where sessions on a one-per-checkout driver could queue: the
|
|
13622
|
-
* driver each node takes is as for `driverRefusals` (
|
|
13623
|
-
* workflow names, else `ready`).
|
|
13888
|
+
* driver each node takes is as for `driverRefusals` (its model's prefix, else
|
|
13889
|
+
* `override`, else what the workflow names, else `ready`).
|
|
13624
13890
|
*/
|
|
13625
13891
|
const driverQueues = (workflow, drivers, choice) => drivers.filter((driver) => driver.onePerCheckout).flatMap((driver) => checkoutContention(workflow, (id) => {
|
|
13626
13892
|
const node = workflow.nodes[id];
|
|
13627
|
-
return node !== void 0 &&
|
|
13628
|
-
override: choice.override,
|
|
13629
|
-
named: namedDriver(node, workflow),
|
|
13630
|
-
ready: choice.ready
|
|
13631
|
-
}) === driver.id;
|
|
13893
|
+
return node !== void 0 && nodeDriver(node, workflow, choice) === driver.id;
|
|
13632
13894
|
}).map((found) => Object.assign({}, found, {
|
|
13633
13895
|
driver: driver.id,
|
|
13634
13896
|
message: messageOf$1(driver.id, found)
|
|
13635
13897
|
})));
|
|
13636
13898
|
// ../core/src/run/driver-needs.ts
|
|
13637
|
-
const SESSION_TYPES = /* @__PURE__ */ new Set(["
|
|
13899
|
+
const SESSION_TYPES = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
13638
13900
|
/** The node's turn limit and dollar budget, its own or the workflow's default. */
|
|
13639
13901
|
const limitsOf = (node, workflow) => {
|
|
13640
13902
|
const own = isClaudeNode(node) ? node : void 0;
|
|
@@ -13648,26 +13910,28 @@ const limitsOf = (node, workflow) => {
|
|
|
13648
13910
|
* brings their questions to it).
|
|
13649
13911
|
*/
|
|
13650
13912
|
const askOfTools = (id, node, workflow) => {
|
|
13651
|
-
if (node.type !== "
|
|
13652
|
-
return Object.values(workflow.nodes).some((child) => child.parent === id && (child.type === "
|
|
13913
|
+
if (node.type !== "agent.leader") return void 0;
|
|
13914
|
+
return Object.values(workflow.nodes).some((child) => child.parent === id && (child.type === "agent.leader" || child.type === "agent.worker")) ? "delegates to the nodes it owns" : void 0;
|
|
13653
13915
|
};
|
|
13654
13916
|
/**
|
|
13655
|
-
* What
|
|
13656
|
-
*
|
|
13657
|
-
* with no driver to check is left out; the run refuses that on its own. An
|
|
13658
|
-
* owner and a node it owns sharing a checkout on a one-per-checkout driver are
|
|
13659
|
-
* refused too: the owner's session holds the checkout while the other waits.
|
|
13917
|
+
* What a session's sub-agents ask of its driver: that it runs subagents at all,
|
|
13918
|
+
* and that a sub-agent's model names no other driver.
|
|
13660
13919
|
*/
|
|
13661
|
-
const
|
|
13662
|
-
if (
|
|
13663
|
-
|
|
13664
|
-
const
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
});
|
|
13669
|
-
|
|
13670
|
-
|
|
13920
|
+
const subagentRefusals = (id, workflow, driver) => Object.entries(workflow.nodes).flatMap(([child, node]) => {
|
|
13921
|
+
if (node.type !== "agent.subagent" || node.parent !== id) return [];
|
|
13922
|
+
const runs = `sub-agent \`${child}\` runs in \`${id}\`'s session on driver \`${driver.id}\``;
|
|
13923
|
+
const pinned = node.model === void 0 ? void 0 : modelDriver(node.model).driver;
|
|
13924
|
+
return [...driver.capabilities.subagents ? [] : [`${runs}, which does not run subagents`], ...pinned === void 0 || pinned === driver.id ? [] : [`${runs}, but its model \`${node.model ?? ""}\` names driver \`${pinned}\``]].map((message) => ({
|
|
13925
|
+
node: child,
|
|
13926
|
+
message
|
|
13927
|
+
}));
|
|
13928
|
+
});
|
|
13929
|
+
/**
|
|
13930
|
+
* What stops one session node from running on `driver`, its sub-agents
|
|
13931
|
+
* included. A run checks each `forEach` item's this way too, when the model it
|
|
13932
|
+
* rendered names the driver.
|
|
13933
|
+
*/
|
|
13934
|
+
const sessionRefusals = (id, node, workflow, driver) => {
|
|
13671
13935
|
const { maxTurns, budgetUsd } = limitsOf(node, workflow);
|
|
13672
13936
|
const refuse = (message) => ({
|
|
13673
13937
|
node: id,
|
|
@@ -13681,7 +13945,22 @@ const driverRefusals = (workflow, drivers, choice) => [...Object.entries(workflo
|
|
|
13681
13945
|
if ((("mcp" in node ? node.mcp : void 0) ?? workflow.defaults?.mcp ?? []).length > 0 && !driver.capabilities.mcpServers) refusals.push(refuse(`names MCP servers (\`mcp\`), but driver \`${driver.id}\` does not load a node's own MCP servers`));
|
|
13682
13946
|
const ask = askOfTools(id, node, workflow);
|
|
13683
13947
|
if (ask !== void 0 && !driver.capabilities.questions) refusals.push(refuse(`${ask}, but driver \`${driver.id}\` does not reach Ork's tools (ask_user, delegate, messages)`));
|
|
13684
|
-
return refusals;
|
|
13948
|
+
return [...refusals, ...subagentRefusals(id, workflow, driver)];
|
|
13949
|
+
};
|
|
13950
|
+
/**
|
|
13951
|
+
* What stops each session node (or only `only`) from running on its driver: its
|
|
13952
|
+
* model's prefix, else `override` (`--driver`), else what the workflow names,
|
|
13953
|
+
* else `ready`. A node with no driver to check is left out; the run refuses
|
|
13954
|
+
* that on its own. An owner and a node it owns sharing a checkout on a
|
|
13955
|
+
* one-per-checkout driver are refused too: the owner's session holds the
|
|
13956
|
+
* checkout while the other waits.
|
|
13957
|
+
*/
|
|
13958
|
+
const driverRefusals = (workflow, drivers, choice) => [...Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
13959
|
+
if (!SESSION_TYPES.has(node.type)) return [];
|
|
13960
|
+
if (choice.only !== void 0 && choice.only !== id) return [];
|
|
13961
|
+
const driverId = nodeDriver(node, workflow, choice);
|
|
13962
|
+
const driver = drivers.find((d) => d.id === driverId);
|
|
13963
|
+
return driver === void 0 ? [] : sessionRefusals(id, node, workflow, driver);
|
|
13685
13964
|
}), ...driverQueues(workflow, drivers, choice).flatMap(({ owner, nodes, message }) => owner === void 0 || choice.only !== void 0 && !nodes.includes(choice.only) ? [] : [{
|
|
13686
13965
|
node: owner,
|
|
13687
13966
|
message
|
|
@@ -13774,95 +14053,6 @@ const draftQueries = (db) => {
|
|
|
13774
14053
|
}
|
|
13775
14054
|
};
|
|
13776
14055
|
};
|
|
13777
|
-
// ../core/src/worktree/git-env.ts
|
|
13778
|
-
/**
|
|
13779
|
-
* @file Git's local environment variables, the ones a git hook sets (`GIT_DIR`,
|
|
13780
|
-
* `GIT_INDEX_FILE`, …), and an environment without them. Ork finds a project
|
|
13781
|
-
* by its path, never through these, so an `ork` started from a hook clears
|
|
13782
|
-
* them at its entry, and core's git runner leaves them out for callers that
|
|
13783
|
-
* don't start there (specs/worktrees.md).
|
|
13784
|
-
*/
|
|
13785
|
-
/**
|
|
13786
|
-
* What `git rev-parse --local-env-vars` prints (git 2.39.5). A fixed list, so
|
|
13787
|
-
* no git is spawned at every start; a test fails when the installed git names
|
|
13788
|
-
* one that is missing here.
|
|
13789
|
-
*/
|
|
13790
|
-
const GIT_LOCAL_ENV = [
|
|
13791
|
-
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
13792
|
-
"GIT_CONFIG",
|
|
13793
|
-
"GIT_CONFIG_PARAMETERS",
|
|
13794
|
-
"GIT_CONFIG_COUNT",
|
|
13795
|
-
"GIT_OBJECT_DIRECTORY",
|
|
13796
|
-
"GIT_DIR",
|
|
13797
|
-
"GIT_WORK_TREE",
|
|
13798
|
-
"GIT_IMPLICIT_WORK_TREE",
|
|
13799
|
-
"GIT_GRAFT_FILE",
|
|
13800
|
-
"GIT_INDEX_FILE",
|
|
13801
|
-
"GIT_NO_REPLACE_OBJECTS",
|
|
13802
|
-
"GIT_REPLACE_REF_BASE",
|
|
13803
|
-
"GIT_PREFIX",
|
|
13804
|
-
"GIT_INTERNAL_SUPER_PREFIX",
|
|
13805
|
-
"GIT_SHALLOW_FILE",
|
|
13806
|
-
"GIT_COMMON_DIR"
|
|
13807
|
-
];
|
|
13808
|
-
/** A copy of `env` without git's local variables. */
|
|
13809
|
-
const withoutGitLocalEnv = (env) => {
|
|
13810
|
-
const clean = { ...env };
|
|
13811
|
-
for (const name of GIT_LOCAL_ENV) delete clean[name];
|
|
13812
|
-
return clean;
|
|
13813
|
-
};
|
|
13814
|
-
/** Removes git's local variables from `env` in place (the CLI's entry). */
|
|
13815
|
-
const clearGitLocalEnv = (env = process.env) => {
|
|
13816
|
-
for (const name of GIT_LOCAL_ENV) delete env[name];
|
|
13817
|
-
};
|
|
13818
|
-
// ../core/src/worktree/git.ts
|
|
13819
|
-
/**
|
|
13820
|
-
* @file A minimal `git` runner for the worktree manager: arguments without a
|
|
13821
|
-
* shell, results that never throw, and none of git's local variables (a
|
|
13822
|
-
* hook's `GIT_DIR` or `GIT_INDEX_FILE`), so it acts on the repository at
|
|
13823
|
-
* `cwd` (specs/worktrees.md). Commands in one folder run one at a time: two
|
|
13824
|
-
* children handed their owner's work at once both stage it (`git add -A`),
|
|
13825
|
-
* and git refuses the second while the first holds the index's lock.
|
|
13826
|
-
*/
|
|
13827
|
-
/** The last command queued in each folder, so the next one waits for it. */
|
|
13828
|
-
const queues = /* @__PURE__ */ new Map();
|
|
13829
|
-
/** Runs `task` after every command queued earlier in the same folder. */
|
|
13830
|
-
const inTurn = (cwd, task) => {
|
|
13831
|
-
const key = path.resolve(cwd);
|
|
13832
|
-
const next = (queues.get(key) ?? Promise.resolve()).then(task, task);
|
|
13833
|
-
queues.set(key, next);
|
|
13834
|
-
next.finally(() => {
|
|
13835
|
-
if (queues.get(key) === next) queues.delete(key);
|
|
13836
|
-
});
|
|
13837
|
-
return next;
|
|
13838
|
-
};
|
|
13839
|
-
const runGit = (cwd, args) => new Promise((resolve) => {
|
|
13840
|
-
execFile("git", args, {
|
|
13841
|
-
cwd,
|
|
13842
|
-
env: withoutGitLocalEnv(process.env),
|
|
13843
|
-
maxBuffer: 33554432,
|
|
13844
|
-
encoding: "utf8"
|
|
13845
|
-
}, (error, stdout, stderr) => {
|
|
13846
|
-
resolve({
|
|
13847
|
-
code: error === null ? 0 : typeof error.code === "number" ? error.code : -1,
|
|
13848
|
-
stdout,
|
|
13849
|
-
stderr: error !== null && !existsSync(cwd) ? `the folder ${cwd} doesn't exist` : stderr || (error?.message ?? "")
|
|
13850
|
-
});
|
|
13851
|
-
});
|
|
13852
|
-
});
|
|
13853
|
-
/** Runs `git` with arguments (no shell) in `cwd`. Never throws. */
|
|
13854
|
-
const git = (cwd, args) => inTurn(cwd, () => runGit(cwd, args));
|
|
13855
|
-
/** Like `git`, but rejects with git's message when the command fails. */
|
|
13856
|
-
const gitOk = async (cwd, args) => {
|
|
13857
|
-
const result = await git(cwd, args);
|
|
13858
|
-
if (result.code !== 0) {
|
|
13859
|
-
const reason = result.stderr.trim().split("\n").at(-1) ?? "";
|
|
13860
|
-
throw new Error(`git ${args[0] ?? ""} failed: ${reason}`);
|
|
13861
|
-
}
|
|
13862
|
-
return result.stdout;
|
|
13863
|
-
};
|
|
13864
|
-
/** Output lines without the trailing empty one. */
|
|
13865
|
-
const lines = (text) => text.split("\n").filter((line) => line !== "");
|
|
13866
14056
|
// ../core/src/worktree/manager.ts
|
|
13867
14057
|
/**
|
|
13868
14058
|
* @file Ork's git worktrees: one per `worktree` node, beside the clone, by
|
|
@@ -15957,6 +16147,36 @@ const syncFrom = async (worktree, from) => {
|
|
|
15957
16147
|
if (!applied.ok) throw new Error(`could not take ${from}'s changes: ${applied.error}`);
|
|
15958
16148
|
return snapshot(worktree);
|
|
15959
16149
|
};
|
|
16150
|
+
// ../core/src/worktree/resumed.ts
|
|
16151
|
+
/**
|
|
16152
|
+
* @file What a resumed run's workspace starts from: the worktrees the run left
|
|
16153
|
+
* whose folders are still there, and where each replayed node worked. A
|
|
16154
|
+
* replayed node is never prepared again, so without this a `continue` or
|
|
16155
|
+
* `readonly` node after it would not find the worktree its input came from.
|
|
16156
|
+
*/
|
|
16157
|
+
/**
|
|
16158
|
+
* Adopts the worktrees still there, and records where replayed nodes worked: an
|
|
16159
|
+
* adopted worktree's own node there, and a node without one (a `readonly`
|
|
16160
|
+
* reviewer, a fanned item) where its last session ran, when that is one of
|
|
16161
|
+
* them.
|
|
16162
|
+
*/
|
|
16163
|
+
const seedResumed = (adopt, places, into) => {
|
|
16164
|
+
for (const left of adopt) {
|
|
16165
|
+
if (!existsSync(left.path)) continue;
|
|
16166
|
+
const { id, merged: wasMerged, ...worktree } = left;
|
|
16167
|
+
into.worktrees.set(id, worktree);
|
|
16168
|
+
into.placeOf.set(id, worktree.path);
|
|
16169
|
+
if (wasMerged) into.merged.add(id);
|
|
16170
|
+
}
|
|
16171
|
+
const adopted = new Set([...into.worktrees.values()].map((w) => w.path));
|
|
16172
|
+
for (const [id, place] of places) if (!into.placeOf.has(id) && adopted.has(place)) into.placeOf.set(id, place);
|
|
16173
|
+
};
|
|
16174
|
+
/** The workspace options for a resumed run; none for a new one. */
|
|
16175
|
+
const resumedOptions = (resume) => resume === void 0 ? {} : {
|
|
16176
|
+
adopt: resume.worktrees,
|
|
16177
|
+
places: resume.places,
|
|
16178
|
+
known: resume.branches
|
|
16179
|
+
};
|
|
15960
16180
|
// ../core/src/worktree/upstream.ts
|
|
15961
16181
|
/** Where `id` worked: its own place, or for a fanned node each item's. */
|
|
15962
16182
|
const placesOf = (id, placeOf) => {
|
|
@@ -16233,13 +16453,11 @@ const createWorkspace = (options) => {
|
|
|
16233
16453
|
const templateOf = /* @__PURE__ */ new Map();
|
|
16234
16454
|
/** A `continue` node → the node whose worktree it works in this time. */
|
|
16235
16455
|
const continued = /* @__PURE__ */ new Map();
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
if (wasMerged) merged.add(id);
|
|
16242
|
-
}
|
|
16456
|
+
seedResumed(options.adopt ?? [], options.places ?? /* @__PURE__ */ new Map(), {
|
|
16457
|
+
worktrees,
|
|
16458
|
+
placeOf,
|
|
16459
|
+
merged
|
|
16460
|
+
});
|
|
16243
16461
|
const nodeOf = (id) => graph.nodes.get(templateOf.get(id) ?? id)?.node;
|
|
16244
16462
|
const ownerOf = (id) => nodeOf(id)?.parent;
|
|
16245
16463
|
const isolationOf = (id) => {
|
|
@@ -16401,4 +16619,4 @@ const createWorkspace = (options) => {
|
|
|
16401
16619
|
close
|
|
16402
16620
|
};
|
|
16403
16621
|
};
|
|
16404
|
-
export {
|
|
16622
|
+
export { followEvents as $, plainPathInside as $n, serversIn as $t, resolveInputs as A, applySteps as An, readyDriver as Ar, endedWith as At, tellNode as B, inRepoPlace as Bn, lines as Bt, isNodeEvent as C, socketPath as Cn, retiredDriverHint as Cr, processAlive as Ct, inspectProject as D, maskReplacer as Dn, integrationFor as Dr, scriptContactsOf as Dt, BRANCH_PREFIX as E, MASK as En, TOOL_PREFIX as Er, nestItems as Et, addressOf as F, createAgent as Fn, firstLine as Fr, slotWaitLine as Ft, requestStop as G, runDirOf as Gn, nodeSays as Gt, flattenTree as H, placeFiles as Hn, envReadsOf as Ht, endLink as I, deleteAgent as In, EXPERIMENTS as Ir, lowerChild as It, detachResume as J, inside as Jn, gitRulesOf as Jt, tellLiveRun as K, shownPath as Kn, declaredOutputOf as Kt, endLinksOf as L, catalogAgent as Ln, experimentsOn as Lr, startAt as Lt, sessionRefusals as M, linkTools as Mn, ORK_LAYOUT as Mr, stoppedEnd as Mt, driverQueues as N, toolLinks as Nn, AGENT_DRIVERS as Nr, calledWorkflows as Nt, removeWorktree as O, maskSecrets as On, PREFERRED_DRIVER as Or, runScope as Ot, createCalls as P, toolsInUse as Pn, DRIVER_IDS as Pr, slotClock as Pt, detachRun as Q, guardedBase as Qn, mcpNotText as Qt, linkNodes as R, loadAgents as Rn, knownExperiments as Rr, git as Rt, activeTree as S, serveRun as Sn, NodeIdSchema as Sr, templateOf as St, storePath as T, wakeRun as Tn, INTEGRATIONS as Tr, isItem as Tt, requestContinue as U, placeFor as Un, namingNoDriver as Ut, unlinkNodes as V, newProjectId as Vn, clearGitLocalEnv as Vt, requestPause as W, projectsHome as Wn, sessionDriver as Wt, listKey as X, LESSON_CAP_TOKENS as Xn, machineRulesOf as Xt, resumable as Y, projectRootOf as Yn, loadProject as Yt, DETACHED_ENV as Z, estimateTokens as Zn, MCP_FILE as Zt, declaredTriggers as _, targetOf as _n, settingsOf as _r, liveStatus as _t, planUpgrade as a, endFor as an, readDocument as ar, explainWorkflowText as at, takeWatchLock as b, renderTemplate as bn, isToolNode as br, runTree as bt, runningScript as c, FEEDBACK_TARGETS as cn, programsRunBy as cr, parseReport as ct, isNewer as d, runGh as dn, serializeWorkflow as dr, walkChecks as dt, createSignals as en, writePlainFile as er, NOT_STARTED as et, parseVersion as f, sendPost as fn, markValue as fr, walkSummary as ft, triggerStanding as g, normalizeTitle as gn, expandEdge as gr, heldTimeout as gt, createWatcher as h, fingerprint as hn, CheckReportSchema as hr, userMessageText as ht, capsHere as i, END_KINDS as in, newWorkflowText as ir, usd as it, driverRefusals as j, planMoveInto as jn, AGENT_TOOL_LAYOUTS as jr, failedWith as jt, parseInputPairs as k, planAgentMigration as kn, driverChecks as kr, declaredProblem as kt, detectInstaller as l, DISCUSSIONS as ln, renderJsonSchema as lr, reportSchemaFor as lt, triggersReport as m, createFeedback as mn, WorkflowSchema as mr, MESSAGE_LIMIT as mt, resumedOptions as n, outcomeEnd as nn, WorkflowEditSchema as nr, limitsReport as nt, releaseNotes as o, FEEDBACK_KINDS as on, renameNodeTypes as or, explainSession as ot, lateTriggers as p, publishPost as pn, EdgeInputSchema as pr, startRun as pt, adoptedCalls as q, workflowDirOf as qn, PROJECT_FILE as qt, resolveBase as r, END_DETAIL_LIMIT as rn, applyEdits as rr, planReason as rt, runningRunIds as s, FEEDBACK_SEVERITIES as sn, beforeOf as sr, planNodeSession as st, createWorkspace as t, endOf as tn, skillsIn as tr, limitsNow as tt, compareVersions as u, planPost as un, parseWorkflow as ur, checkWorkflowText as ut, readProjectTriggers as v, evaluate as vn, NodeSchema as vr, pausedOf as vt, openStore as w, socketRoom as wn, timeoutMs as wr, stillTheSameProcess as wt, watchingPid as x, readCondition as xn, DriverSchema as xr, slotWaitsOf as xt, workflowTextHash as y, parseTemplate as yn, isClaudeNode as yr, runStatus as yt, peerOf as z, hasInRepoFiles as zn, gitOk as zt };
|