@krapht/ork 1.0.0-alpha.11 → 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__-CIchF6nr.mjs → __build_info__-Da73_nBs.mjs} +4 -3
- package/dist/attach-E6NNyA3T.mjs +89 -0
- package/dist/bin.mjs +213 -1230
- package/dist/{decide-B6PnKeqo.mjs → builtin-files-n6o6oWpB.mjs} +3 -51
- package/dist/{chips-BA7SrrH5.mjs → chips-CyeB_rhR.mjs} +1 -1
- package/dist/{clean-Di-KT7Mf.mjs → clean-Bh4KZgzc.mjs} +1 -1
- package/dist/{completion-C5D0sEs5.mjs → completion-SNNNxnv9.mjs} +7 -6
- package/dist/{context-BaNshWr9.mjs → context-DHudLA5e.mjs} +6 -6
- package/dist/decide-DuIHACvH.mjs +50 -0
- package/dist/{find-BicBbGMg.mjs → find-CjsNYAAf.mjs} +1 -1
- package/dist/{format-lWhN-jYR.mjs → format-C60TFpRe.mjs} +5 -85
- package/dist/{help-groups-BwYoAWsh.mjs → help-groups-CyfPWlST.mjs} +8 -0
- package/dist/{in-a-run-CgkVouyG.mjs → in-a-run-BYjTXaST.mjs} +4 -2
- package/dist/{limits-jZQp9ZJC.mjs → limits-C5QKZNLT.mjs} +2 -2
- package/dist/{list-CEaJ07jd.mjs → list-BUb5Uc3H.mjs} +1 -1
- package/dist/{mcp-DYgVA9f5.mjs → mcp-bcctR-wc.mjs} +6 -6
- package/dist/once-DPQu5Q3H.mjs +76 -0
- package/dist/{output-CM95njYt.mjs → output-CnRzrbUM.mjs} +3 -1
- package/dist/{pause-D6PuSlNl.mjs → pause-BPvlDIvz.mjs} +4 -4
- package/dist/{relay-DNx7be9k.mjs → relay-C8W0t_zc.mjs} +2 -2
- package/dist/{remove-CpMb4NDl.mjs → remove-BkFWGVYo.mjs} +3 -3
- package/dist/run-CQIfq918.mjs +192 -0
- package/dist/{send-CLs_fyg7.mjs → send-CaZlGG5a.mjs} +3 -3
- package/dist/{src-D6_xjm-x.mjs → src-BeR6f70A.mjs} +595 -228
- package/dist/{src-B6KvTm7B.mjs → src-CGJGL1sZ.mjs} +7 -6
- package/dist/{src-BnZgZGrf.mjs → src-CqRqsb2t.mjs} +44 -7
- package/dist/{src-9iGkjnsy.mjs → src-D93tHZ5B.mjs} +110 -35
- package/dist/store-CM3cERGl.mjs +1365 -0
- package/dist/ui/THIRD_PARTY_LICENSES.txt +18 -0
- package/dist/ui/assets/app-DYRC0V4p.js +111 -0
- package/dist/ui/assets/{index-By_8KKG6.js → index-CWwm8HTG.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CIci4UBW.mjs → ui-D2Ydofq0.mjs} +4 -4
- package/dist/{usage-GceBGMjU.mjs → usage-6WIowblz.mjs} +6 -6
- package/dist/verdict-CIy8tAyX.mjs +84 -0
- package/dist/{watch-BmoybMQg.mjs → watch-uOU_WeBI.mjs} +7 -7
- package/dist/{workflows-DZvW3i1e.mjs → workflows-7tQdMnrA.mjs} +3 -3
- package/dist/{worktrees-Be51E2rB.mjs → worktrees-ZCII298F.mjs} +5 -5
- package/package.json +1 -1
- package/dist/ui/assets/app-CxURph7a.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
|
];
|
|
@@ -544,13 +547,14 @@ const DriverSchema = z.enum(DRIVER_IDS, { error: (issue) => retiredDriverHint(is
|
|
|
544
547
|
/** `opus`, `sonnet`, `haiku`, or a full model id such as `claude-opus-5-5`. */
|
|
545
548
|
const ModelSchema = z.string().min(1);
|
|
546
549
|
/** How hard the model thinks; Claude Code's own default when unset. */
|
|
547
|
-
const
|
|
550
|
+
const EFFORTS = [
|
|
548
551
|
"low",
|
|
549
552
|
"medium",
|
|
550
553
|
"high",
|
|
551
554
|
"xhigh",
|
|
552
555
|
"max"
|
|
553
|
-
]
|
|
556
|
+
];
|
|
557
|
+
const EffortSchema = z.enum(EFFORTS).describe("Reasoning effort. Default: Claude Code's for the model.");
|
|
554
558
|
const IsolationSchema = z.enum([
|
|
555
559
|
"worktree",
|
|
556
560
|
"continue",
|
|
@@ -623,7 +627,7 @@ const nodeBaseFields = {
|
|
|
623
627
|
};
|
|
624
628
|
/** A message a workflow declares, sent when its node finishes (RFC-005). */
|
|
625
629
|
const NotifyRuleSchema = z.strictObject({
|
|
626
|
-
to: NodeIdSchema.describe("The `
|
|
630
|
+
to: NodeIdSchema.describe("The `agent.leader` or `agent.worker` to write to."),
|
|
627
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."),
|
|
628
632
|
body: z.string().optional().describe("The message, a template. Default: Ork's one line on the result.")
|
|
629
633
|
});
|
|
@@ -662,12 +666,17 @@ const runFields = {
|
|
|
662
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."),
|
|
663
667
|
interpreter: z.string().optional().describe("What runs `script:`: a program and arguments. Default: `sh -eu`.")
|
|
664
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.");
|
|
665
671
|
const StepSchema = z.strictObject({
|
|
666
672
|
...runFields,
|
|
667
673
|
...toolProgramFields,
|
|
668
674
|
timeout: TimeoutSchema.optional().describe("How long the program may run, e.g. 5m; then it is killed and the step fails.")
|
|
669
675
|
});
|
|
670
|
-
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.");
|
|
671
680
|
/** The fields a leader or worker takes its steps from. */
|
|
672
681
|
const stepFields = {
|
|
673
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.*}}."),
|
|
@@ -686,6 +695,7 @@ const toolFields = {
|
|
|
686
695
|
cwd: z.string().optional(),
|
|
687
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."),
|
|
688
697
|
watch: WatchSchema.optional().describe("Run the command again and again until a condition holds, with no model turn."),
|
|
698
|
+
once: onceField,
|
|
689
699
|
...toolProgramFields,
|
|
690
700
|
...notifyFields,
|
|
691
701
|
...forEachFields
|
|
@@ -720,13 +730,20 @@ const TOOL_NODE_SCHEMAS = INTEGRATIONS.map((i) => toolNodeSchema(i));
|
|
|
720
730
|
const base = nodeBaseFields;
|
|
721
731
|
/** A Claude node's output field that is not a list: a rule or JSON Schema. */
|
|
722
732
|
const NotListSchema = z.unknown().refine((value) => typeof value !== "object" || value === null || !("list" in value), { message: LIST_RULE_HINT });
|
|
733
|
+
const NODE_EFFORT_HINT = "expected low, medium, high, xhigh or max, or {{item.<field>}}";
|
|
734
|
+
/**
|
|
735
|
+
* A Claude node's effort: a level, or on a `forEach` node a template reading
|
|
736
|
+
* its item (`{{item.effort}}`), which the compiler checks and each item
|
|
737
|
+
* renders.
|
|
738
|
+
*/
|
|
739
|
+
const NodeEffortSchema = z.union([EffortSchema, z.string().regex(/\{\{.*\}\}/su, { error: NODE_EFFORT_HINT })], { error: NODE_EFFORT_HINT }).describe("Reasoning effort, or {{item.<field>}} on a forEach node. Default: Claude Code's for the model.");
|
|
723
740
|
/**
|
|
724
741
|
* What an agent brings to the node it fills, and what a plain Claude node sets
|
|
725
742
|
* itself. A node's own value wins over its agent's.
|
|
726
743
|
*/
|
|
727
744
|
const agentSettings = {
|
|
728
745
|
model: ModelSchema.optional(),
|
|
729
|
-
effort:
|
|
746
|
+
effort: NodeEffortSchema.optional(),
|
|
730
747
|
driver: DriverSchema.optional(),
|
|
731
748
|
skills: z.array(z.string()).optional().describe("Node-layer skills (highest precedence)."),
|
|
732
749
|
context: z.array(z.string()).optional().describe("Node-layer context files."),
|
|
@@ -766,7 +783,7 @@ const claudeFields = {
|
|
|
766
783
|
...agentSettings
|
|
767
784
|
};
|
|
768
785
|
const ClaudeLeaderNodeSchema = z.strictObject({
|
|
769
|
-
type: z.literal("
|
|
786
|
+
type: z.literal("agent.leader"),
|
|
770
787
|
...claudeFields,
|
|
771
788
|
mcp: McpNamesSchema,
|
|
772
789
|
...notifyFields,
|
|
@@ -775,7 +792,7 @@ const ClaudeLeaderNodeSchema = z.strictObject({
|
|
|
775
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.")
|
|
776
793
|
});
|
|
777
794
|
const ClaudeWorkerNodeSchema = z.strictObject({
|
|
778
|
-
type: z.literal("
|
|
795
|
+
type: z.literal("agent.worker"),
|
|
779
796
|
...claudeFields,
|
|
780
797
|
mcp: McpNamesSchema,
|
|
781
798
|
...notifyFields,
|
|
@@ -783,7 +800,7 @@ const ClaudeWorkerNodeSchema = z.strictObject({
|
|
|
783
800
|
...stepFields
|
|
784
801
|
});
|
|
785
802
|
const ClaudeSubagentNodeSchema = z.strictObject({
|
|
786
|
-
type: z.literal("
|
|
803
|
+
type: z.literal("agent.subagent"),
|
|
787
804
|
...claudeFields,
|
|
788
805
|
parent: NodeIdSchema.describe("The Claude node whose session hosts this sub-agent."),
|
|
789
806
|
description: z.string().describe("When the parent should use this sub-agent.")
|
|
@@ -855,9 +872,9 @@ const NodeSchema = z.discriminatedUnion("type", [
|
|
|
855
872
|
]);
|
|
856
873
|
const isToolNode = (node) => node.type.startsWith(TOOL_PREFIX);
|
|
857
874
|
const CLAUDE_NODE_TYPES = [
|
|
858
|
-
"
|
|
859
|
-
"
|
|
860
|
-
"
|
|
875
|
+
"agent.leader",
|
|
876
|
+
"agent.worker",
|
|
877
|
+
"agent.subagent"
|
|
861
878
|
];
|
|
862
879
|
const isClaudeNode = (node) => CLAUDE_NODE_TYPES.includes(node.type);
|
|
863
880
|
// ../schema/src/agents.ts
|
|
@@ -1191,6 +1208,16 @@ const markValue = (value) => {
|
|
|
1191
1208
|
const pad = line.startsWith("`") || line.endsWith("`") ? " " : "";
|
|
1192
1209
|
return `${fence}${pad}${line}${pad}${fence}`;
|
|
1193
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
|
+
};
|
|
1194
1221
|
// ../schema/src/issue-messages.ts
|
|
1195
1222
|
const NODE_TYPES = NodeSchema.options.map((o) => o.shape.type.value);
|
|
1196
1223
|
const isMap$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1213,6 +1240,8 @@ const listRuleMessage = (issue, value) => issue.code === "invalid_union" && isMa
|
|
|
1213
1240
|
/** The text for a node whose `type` is missing or unknown, else `null`. */
|
|
1214
1241
|
const nodeTypeMessage = (issue, value) => {
|
|
1215
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\``;
|
|
1216
1245
|
const types = NODE_TYPES.map((type) => markValue(type)).join(", ");
|
|
1217
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}`;
|
|
1218
1247
|
};
|
|
@@ -1440,6 +1469,22 @@ const renderJsonSchema = () => {
|
|
|
1440
1469
|
title: "Ork workflow"
|
|
1441
1470
|
}, void 0, 2)}\n`;
|
|
1442
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
|
+
};
|
|
1443
1488
|
// ../schema/src/node-programs.ts
|
|
1444
1489
|
/**
|
|
1445
1490
|
* @file Which programs a node runs: its own, its `before:` and its `verify:`,
|
|
@@ -1467,7 +1512,7 @@ const programsOf = (node) => {
|
|
|
1467
1512
|
}] : []];
|
|
1468
1513
|
};
|
|
1469
1514
|
/** The `before:` a leader or worker runs: its own, or the workflow's default. */
|
|
1470
|
-
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;
|
|
1471
1516
|
/** Each program a node runs, with what it takes from the workflow's defaults. */
|
|
1472
1517
|
const programsRunBy = (node, defaults) => {
|
|
1473
1518
|
const inherited = beforeOf(node, defaults);
|
|
@@ -1476,6 +1521,55 @@ const programsRunBy = (node, defaults) => {
|
|
|
1476
1521
|
program: inherited
|
|
1477
1522
|
}, ...programsOf(node)] : programsOf(node);
|
|
1478
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
|
+
};
|
|
1479
1573
|
// ../schema/src/edit-order.ts
|
|
1480
1574
|
/**
|
|
1481
1575
|
* @file The canonical order of keys anywhere in a workflow, read from the Zod
|
|
@@ -2350,7 +2444,7 @@ const fieldNames = (doc, id, field) => String(doc.getIn([
|
|
|
2350
2444
|
"nodes",
|
|
2351
2445
|
id,
|
|
2352
2446
|
"type"
|
|
2353
|
-
])).startsWith("
|
|
2447
|
+
])).startsWith("agent.") ? [field] : [`json.${field}`, field];
|
|
2354
2448
|
const asValues = (raw) => {
|
|
2355
2449
|
if (!Array.isArray(raw)) return [];
|
|
2356
2450
|
return raw.map((item) => typeof item === "object" && item !== null && "id" in item ? item.id : item);
|
|
@@ -3726,7 +3820,7 @@ const resolveContext = (options) => {
|
|
|
3726
3820
|
const contextDiagnostics = (workflow, options) => {
|
|
3727
3821
|
const seen = /* @__PURE__ */ new Map();
|
|
3728
3822
|
for (const [nodeId, node] of Object.entries(workflow.nodes)) {
|
|
3729
|
-
if (!isClaudeNode(node) || node.type === "
|
|
3823
|
+
if (!isClaudeNode(node) || node.type === "agent.subagent") continue;
|
|
3730
3824
|
const stack = resolveContext({
|
|
3731
3825
|
...options,
|
|
3732
3826
|
workflow,
|
|
@@ -5275,7 +5369,7 @@ const checkMcpServers = (workflow, options) => {
|
|
|
5275
5369
|
const known = options.mcpServers;
|
|
5276
5370
|
if (known === void 0) return [];
|
|
5277
5371
|
return Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
5278
|
-
if (node.type !== "
|
|
5372
|
+
if (node.type !== "agent.leader" && node.type !== "agent.worker") return [];
|
|
5279
5373
|
const own = node.mcp !== void 0;
|
|
5280
5374
|
return (node.mcp ?? workflow.defaults?.mcp ?? []).flatMap((name, index) => {
|
|
5281
5375
|
const problem = nameProblem(id, name, options, known);
|
|
@@ -5486,10 +5580,10 @@ const manyWorktrees = (workflow) => {
|
|
|
5486
5580
|
let count = 0;
|
|
5487
5581
|
for (const node of Object.values(workflow.nodes)) {
|
|
5488
5582
|
if (!isClaudeNode(node)) continue;
|
|
5489
|
-
if (node.type === "
|
|
5583
|
+
if (node.type === "agent.leader" && (node.maxWorkers ?? 0) > 0) return true;
|
|
5490
5584
|
const isolation = node.isolation ?? workflow.defaults?.isolation ?? "worktree";
|
|
5491
5585
|
const fanned = "forEach" in node && node.forEach !== void 0;
|
|
5492
|
-
if (node.type !== "
|
|
5586
|
+
if (node.type !== "agent.subagent" && isolation === "worktree") count += fanned ? 2 : 1;
|
|
5493
5587
|
}
|
|
5494
5588
|
return count > 1;
|
|
5495
5589
|
};
|
|
@@ -5572,7 +5666,7 @@ const gatheredSays = (item) => {
|
|
|
5572
5666
|
* passes its input through, so any field may exist.
|
|
5573
5667
|
*/
|
|
5574
5668
|
const OUTPUT_FIELDS = {
|
|
5575
|
-
"
|
|
5669
|
+
"agent.leader": [
|
|
5576
5670
|
"status",
|
|
5577
5671
|
"summary",
|
|
5578
5672
|
"artifacts",
|
|
@@ -5580,7 +5674,7 @@ const OUTPUT_FIELDS = {
|
|
|
5580
5674
|
"worktree",
|
|
5581
5675
|
"usage"
|
|
5582
5676
|
],
|
|
5583
|
-
"
|
|
5677
|
+
"agent.worker": [
|
|
5584
5678
|
"status",
|
|
5585
5679
|
"summary",
|
|
5586
5680
|
"artifacts",
|
|
@@ -5588,7 +5682,7 @@ const OUTPUT_FIELDS = {
|
|
|
5588
5682
|
"worktree",
|
|
5589
5683
|
"usage"
|
|
5590
5684
|
],
|
|
5591
|
-
"
|
|
5685
|
+
"agent.subagent": [
|
|
5592
5686
|
"status",
|
|
5593
5687
|
"summary",
|
|
5594
5688
|
"artifacts"
|
|
@@ -5782,7 +5876,7 @@ const itemSays = (node) => {
|
|
|
5782
5876
|
canFail: true,
|
|
5783
5877
|
canDeclare: false
|
|
5784
5878
|
};
|
|
5785
|
-
if (!node.type.startsWith("
|
|
5879
|
+
if (!node.type.startsWith("agent.")) return NOTHING;
|
|
5786
5880
|
const declared = declaredFields(node, "").map((field) => withFail(field));
|
|
5787
5881
|
return {
|
|
5788
5882
|
fields: [
|
|
@@ -5986,7 +6080,7 @@ const compileEdges = (workflow) => {
|
|
|
5986
6080
|
diagnostics.push(error("edge.unknown-node", `edge \`${label}\`: unknown node ${missing.map((m) => `\`${m}\``).join(", ")}`, path));
|
|
5987
6081
|
continue;
|
|
5988
6082
|
}
|
|
5989
|
-
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");
|
|
5990
6084
|
if (subagent !== void 0) {
|
|
5991
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));
|
|
5992
6086
|
continue;
|
|
@@ -6181,14 +6275,14 @@ const envOutsideEnv = (segments, path) => error("template.env", `\`${describePat
|
|
|
6181
6275
|
/**
|
|
6182
6276
|
* Ownership rules:
|
|
6183
6277
|
*
|
|
6184
|
-
* -
|
|
6278
|
+
* - An `agent.subagent` is owned by an `agent.leader` or `agent.worker` (it runs
|
|
6185
6279
|
* inside that node's session);
|
|
6186
|
-
* - Every other owned node is owned by
|
|
6280
|
+
* - Every other owned node is owned by an `agent.leader`;
|
|
6187
6281
|
* - The tree has no cycles;
|
|
6188
6282
|
* - A node that sets `isolation: continue` neither owns nor is owned: it works in
|
|
6189
6283
|
* the worktree its input came from, outside the tree's merges.
|
|
6190
6284
|
* - A leader's `questions:` is about its children's questions, so a leader with
|
|
6191
|
-
* no `
|
|
6285
|
+
* no `agent.leader` or `agent.worker` child can't set it.
|
|
6192
6286
|
*/
|
|
6193
6287
|
const checkOwnership = (workflow) => {
|
|
6194
6288
|
const diagnostics = [];
|
|
@@ -6203,9 +6297,9 @@ const checkOwnership = (workflow) => {
|
|
|
6203
6297
|
const owner = nodes[node.parent];
|
|
6204
6298
|
if (node.parent === id) diagnostics.push(error("tree.self", `\`${id}\` cannot own itself`, path));
|
|
6205
6299
|
else if (owner === void 0) diagnostics.push(error("tree.unknown-parent", `unknown parent \`${node.parent}\``, path));
|
|
6206
|
-
else if (node.type === "
|
|
6207
|
-
if (owner.type !== "
|
|
6208
|
-
} 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));
|
|
6209
6303
|
}
|
|
6210
6304
|
for (const [id, node] of Object.entries(nodes)) {
|
|
6211
6305
|
if (!("isolation" in node) || node.isolation !== "continue") continue;
|
|
@@ -6217,8 +6311,8 @@ const checkOwnership = (workflow) => {
|
|
|
6217
6311
|
]));
|
|
6218
6312
|
}
|
|
6219
6313
|
for (const [id, node] of Object.entries(nodes)) {
|
|
6220
|
-
if (node.type !== "
|
|
6221
|
-
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`, [
|
|
6222
6316
|
"nodes",
|
|
6223
6317
|
id,
|
|
6224
6318
|
"questions"
|
|
@@ -6397,7 +6491,7 @@ const sessionsIn = (workflow, members) => {
|
|
|
6397
6491
|
const parent = workflow.nodes[id]?.parent;
|
|
6398
6492
|
return parent !== void 0 && (members.has(parent) || ownedByMember(parent));
|
|
6399
6493
|
};
|
|
6400
|
-
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);
|
|
6401
6495
|
};
|
|
6402
6496
|
/** Plain edges beside a loop edge. A first-match router's is its fallback. */
|
|
6403
6497
|
const besideLoops = (workflow, edges, loops) => {
|
|
@@ -6429,6 +6523,71 @@ const checkGroup = (workflow, edges, component) => {
|
|
|
6429
6523
|
};
|
|
6430
6524
|
/** What's wrong with the shape of each group of nodes that loop together. */
|
|
6431
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
|
+
});
|
|
6432
6591
|
// ../core/src/graph/gather.ts
|
|
6433
6592
|
/** Whether a declared field is a list: `{ type: array }` or `{ list: … }`. */
|
|
6434
6593
|
const isList = (rule) => listShape(rule) !== void 0 || typeof rule === "object" && rule !== null && "type" in rule && rule.type === "array";
|
|
@@ -6449,6 +6608,122 @@ const gatherProblems = (id, node) => {
|
|
|
6449
6608
|
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])];
|
|
6450
6609
|
});
|
|
6451
6610
|
};
|
|
6611
|
+
// ../core/src/graph/item-settings.ts
|
|
6612
|
+
/**
|
|
6613
|
+
* @file A fanned node's `model` and `effort` from its item (`{{item.model}}`):
|
|
6614
|
+
* the compiler's checks (only on a `forEach` node, reading only `item`, and
|
|
6615
|
+
* every written item's value), and the rendering each item's session plan
|
|
6616
|
+
* uses, which checks an item from `from:` when it runs.
|
|
6617
|
+
*/
|
|
6618
|
+
const FIELDS$1 = ["model", "effort"];
|
|
6619
|
+
/**
|
|
6620
|
+
* The parts of a template that reads something, or `null` for a plain value (or
|
|
6621
|
+
* one that does not parse, which the compiler reports).
|
|
6622
|
+
*/
|
|
6623
|
+
const templateOf$1 = (text) => {
|
|
6624
|
+
const parsed = parseTemplate(text);
|
|
6625
|
+
if (!parsed.ok) return null;
|
|
6626
|
+
return parsed.parts.some((part) => typeof part !== "string") ? parsed.parts : null;
|
|
6627
|
+
};
|
|
6628
|
+
const isEffort = (text) => EFFORTS.includes(text);
|
|
6629
|
+
/** Checks what one item's template rendered to. */
|
|
6630
|
+
const checkRendered = (field, template, rendered) => {
|
|
6631
|
+
const text = rendered.trim();
|
|
6632
|
+
if (text === "") return {
|
|
6633
|
+
ok: true,
|
|
6634
|
+
value: void 0
|
|
6635
|
+
};
|
|
6636
|
+
return (field === "effort" ? isEffort(text) : /^[^\s{}"]+$/u.test(text)) ? {
|
|
6637
|
+
ok: true,
|
|
6638
|
+
value: text
|
|
6639
|
+
} : {
|
|
6640
|
+
ok: false,
|
|
6641
|
+
error: `\`${field}: ${template}\` gave \`${text}\`, which is not ${field === "effort" ? `an effort level (${EFFORTS.join(", ")})` : "a model name"}`
|
|
6642
|
+
};
|
|
6643
|
+
};
|
|
6644
|
+
/**
|
|
6645
|
+
* A node's `model` or `effort` for one item: a plain value as written, a
|
|
6646
|
+
* template rendered with the item. Without an item, a template renders empty.
|
|
6647
|
+
*/
|
|
6648
|
+
const itemSetting = (field, value, scope) => {
|
|
6649
|
+
if (value === void 0) return {
|
|
6650
|
+
ok: true,
|
|
6651
|
+
value: void 0
|
|
6652
|
+
};
|
|
6653
|
+
const parts = templateOf$1(value);
|
|
6654
|
+
if (parts === null) return {
|
|
6655
|
+
ok: true,
|
|
6656
|
+
value
|
|
6657
|
+
};
|
|
6658
|
+
return checkRendered(field, value, renderTemplate(parts, scope));
|
|
6659
|
+
};
|
|
6660
|
+
/** `itemSetting` for `effort`, as an effort level. */
|
|
6661
|
+
const itemEffort = (value, scope) => {
|
|
6662
|
+
const given = itemSetting("effort", value, scope);
|
|
6663
|
+
if (!given.ok) return given;
|
|
6664
|
+
if (given.value === void 0) return {
|
|
6665
|
+
ok: true,
|
|
6666
|
+
value: void 0
|
|
6667
|
+
};
|
|
6668
|
+
return isEffort(given.value) ? {
|
|
6669
|
+
ok: true,
|
|
6670
|
+
value: given.value
|
|
6671
|
+
} : {
|
|
6672
|
+
ok: false,
|
|
6673
|
+
error: `\`effort\` is \`${given.value}\``
|
|
6674
|
+
};
|
|
6675
|
+
};
|
|
6676
|
+
/** A `model` or `effort` template's problems on one node. */
|
|
6677
|
+
const checkNodeField = (workflow, says, id, field) => {
|
|
6678
|
+
const node = workflow.nodes[id];
|
|
6679
|
+
const value = node === void 0 ? void 0 : Reflect.get(node, field);
|
|
6680
|
+
if (node === void 0 || typeof value !== "string") return [];
|
|
6681
|
+
const path = [
|
|
6682
|
+
"nodes",
|
|
6683
|
+
id,
|
|
6684
|
+
field
|
|
6685
|
+
];
|
|
6686
|
+
const parsed = parseTemplate(value);
|
|
6687
|
+
if (!parsed.ok) return [error("template.syntax", `${parsed.message} at column ${parsed.offset + 1}`, path)];
|
|
6688
|
+
if (parsed.parts.every((part) => typeof part === "string")) return [];
|
|
6689
|
+
const each = forEachOf$1(node);
|
|
6690
|
+
if (each === void 0) return [error(`${field}.template`, `\`${field}\` may be a template only on a node with \`forEach\`, reading its item, e.g. \`{{item.${field}}}\``, path)];
|
|
6691
|
+
const reads = parsed.parts.flatMap((part) => typeof part === "string" ? [] : collectPaths(part.expr));
|
|
6692
|
+
const outside = reads.filter(([root]) => root !== "item");
|
|
6693
|
+
if (outside.length > 0) return outside.map((segments) => error(`${field}.template`, `\`${describePath(segments)}\`: a \`${field}\` template reads only the item, e.g. \`{{item.${field}}}\``, path));
|
|
6694
|
+
const scope = { fields: itemFieldsOf(node, says) };
|
|
6695
|
+
const fields = reads.flatMap((segments) => checkItemRead(segments, scope, path));
|
|
6696
|
+
if (fields.length > 0 || !("items" in each)) return fields;
|
|
6697
|
+
return each.items.flatMap((item, index) => {
|
|
6698
|
+
const given = checkRendered(field, value, renderTemplate(parsed.parts, {
|
|
6699
|
+
inputs: {},
|
|
6700
|
+
nodes: {},
|
|
6701
|
+
item,
|
|
6702
|
+
index
|
|
6703
|
+
}));
|
|
6704
|
+
return given.ok ? [] : [error(`${field}.item`, `item ${String(index)}: ${given.error}`, [
|
|
6705
|
+
"nodes",
|
|
6706
|
+
id,
|
|
6707
|
+
"forEach",
|
|
6708
|
+
"items",
|
|
6709
|
+
index
|
|
6710
|
+
])];
|
|
6711
|
+
});
|
|
6712
|
+
};
|
|
6713
|
+
/**
|
|
6714
|
+
* `model` and `effort` templates: only on a `forEach` node, reading only
|
|
6715
|
+
* `item`, and every written item's value a model name or an effort level.
|
|
6716
|
+
* `defaults:` belong to no node, so they take no template.
|
|
6717
|
+
*/
|
|
6718
|
+
const checkItemSettings = (workflow, edges) => {
|
|
6719
|
+
const says = nodeSays(workflow.nodes, edges);
|
|
6720
|
+
const defaults = FIELDS$1.flatMap((field) => {
|
|
6721
|
+
const value = workflow.defaults?.[field];
|
|
6722
|
+
return value !== void 0 && templateOf$1(value) !== null ? [error(`${field}.template`, `\`defaults.${field}\` applies to every node, so it can't read an item; set the template on the \`forEach\` node`, ["defaults", field])] : [];
|
|
6723
|
+
});
|
|
6724
|
+
const nodes = Object.entries(workflow.nodes).flatMap(([id, node]) => isClaudeNode(node) ? FIELDS$1.flatMap((field) => checkNodeField(workflow, says, id, field)) : []);
|
|
6725
|
+
return [...defaults, ...nodes];
|
|
6726
|
+
};
|
|
6452
6727
|
// ../core/src/graph/script.ts
|
|
6453
6728
|
/**
|
|
6454
6729
|
* @file Checks on the programs a workflow declares (RFC-006): a tool node's and
|
|
@@ -6499,7 +6774,7 @@ const optionId = (option) => typeof option === "string" ? option : option.id;
|
|
|
6499
6774
|
* another would fail on every run. It can narrow them, to fail on the rest.
|
|
6500
6775
|
*/
|
|
6501
6776
|
const statusProblems = (id, node) => {
|
|
6502
|
-
if (!isClaudeNode(node) || node.type === "
|
|
6777
|
+
if (!isClaudeNode(node) || node.type === "agent.subagent") return [];
|
|
6503
6778
|
const rule = declaredOutputOf(node)?.["status"];
|
|
6504
6779
|
const list = typeof rule === "object" && rule !== null && "enum" in rule ? rule.enum : rule;
|
|
6505
6780
|
if (!Array.isArray(list)) return [];
|
|
@@ -6512,15 +6787,28 @@ const statusProblems = (id, node) => {
|
|
|
6512
6787
|
"status"
|
|
6513
6788
|
]));
|
|
6514
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
|
+
};
|
|
6515
6803
|
/** Per-type checks that need the rest of the graph. */
|
|
6516
6804
|
const checkNodes = (workflow, edges) => {
|
|
6517
|
-
const diagnostics = checkScripts(workflow);
|
|
6805
|
+
const diagnostics = [...checkScripts(workflow), ...checkItemSettings(workflow, edges)];
|
|
6518
6806
|
for (const [id, node] of Object.entries(workflow.nodes)) {
|
|
6519
6807
|
const path = ["nodes", id];
|
|
6520
6808
|
const incoming = edges.filter((e) => e.to === id);
|
|
6521
6809
|
const outgoing = edges.filter((e) => e.from === id);
|
|
6522
|
-
if (node.type === "
|
|
6523
|
-
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));
|
|
6524
6812
|
if (node.type === "control.join") {
|
|
6525
6813
|
const sources = new Set(incoming.map((e) => e.from)).size;
|
|
6526
6814
|
if (sources < 2) diagnostics.push(warning("join.inputs", `join \`${id}\` has ${sources} incoming node(s); a join waits for two or more`, path));
|
|
@@ -6572,10 +6860,10 @@ const checkChannels = (workflow) => {
|
|
|
6572
6860
|
}
|
|
6573
6861
|
const bad = [a, b].filter((id) => {
|
|
6574
6862
|
const node = workflow.nodes[id];
|
|
6575
|
-
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);
|
|
6576
6864
|
});
|
|
6577
6865
|
if (bad.length > 0) {
|
|
6578
|
-
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));
|
|
6579
6867
|
continue;
|
|
6580
6868
|
}
|
|
6581
6869
|
const nodeA = workflow.nodes[a];
|
|
@@ -6584,9 +6872,9 @@ const checkChannels = (workflow) => {
|
|
|
6584
6872
|
diagnostics.push(error("message.endpoint", `\`${a}\`, \`${b}\`: a channel needs a session on this side, and a \`workflow.call\` has none`, path));
|
|
6585
6873
|
continue;
|
|
6586
6874
|
}
|
|
6587
|
-
const session = (node) => node?.type === "
|
|
6875
|
+
const session = (node) => node?.type === "agent.leader" || node?.type === "agent.worker";
|
|
6588
6876
|
if ((nodeA !== void 0 && isToolNode(nodeA) || nodeB !== void 0 && isToolNode(nodeB)) && !session(nodeA) && !session(nodeB)) {
|
|
6589
|
-
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));
|
|
6590
6878
|
continue;
|
|
6591
6879
|
}
|
|
6592
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));
|
|
@@ -6948,7 +7236,7 @@ const checkFlow = (workflow, edges, rules) => [
|
|
|
6948
7236
|
...checkConditions(workflow, edges, rules)
|
|
6949
7237
|
];
|
|
6950
7238
|
// ../core/src/graph/notify.ts
|
|
6951
|
-
const RECEIVERS = /* @__PURE__ */ new Set(["
|
|
7239
|
+
const RECEIVERS = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
6952
7240
|
const compileRules = (workflow) => {
|
|
6953
7241
|
const rules = /* @__PURE__ */ new Map();
|
|
6954
7242
|
const diagnostics = [];
|
|
@@ -6966,7 +7254,7 @@ const compileRules = (workflow) => {
|
|
|
6966
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));
|
|
6967
7255
|
const target = workflow.nodes[rule.to];
|
|
6968
7256
|
if (rule.to === id) diagnostics.push(error("notify.self", "a node cannot notify itself", [...path, "to"]));
|
|
6969
|
-
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"]));
|
|
6970
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"]));
|
|
6971
7259
|
const entry = { to: rule.to };
|
|
6972
7260
|
if (rule.when !== void 0) {
|
|
@@ -7049,6 +7337,101 @@ const compileWorkflow = (authored, options = {}) => {
|
|
|
7049
7337
|
diagnostics
|
|
7050
7338
|
};
|
|
7051
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 !== "");
|
|
7052
7435
|
// ../core/src/machine/priority.ts
|
|
7053
7436
|
/**
|
|
7054
7437
|
* @file Starting a child at the project's priority (`priority:`): `low`, the
|
|
@@ -7090,11 +7473,13 @@ const startAt = (priority, bin, args, env = process.env) => {
|
|
|
7090
7473
|
return [first, [...rest, ...args]];
|
|
7091
7474
|
};
|
|
7092
7475
|
/**
|
|
7093
|
-
* Lowers a started child's priority for `low
|
|
7094
|
-
*
|
|
7476
|
+
* Lowers a started child's priority for `low` where {@link startAt} had no
|
|
7477
|
+
* `nice` to start it under (Windows), best effort; what it starts later
|
|
7478
|
+
* inherits it. Under `nice` it does nothing: `nice` lowers relative to the
|
|
7479
|
+
* priority it finds, so lowering here too could land first and the two add up.
|
|
7095
7480
|
*/
|
|
7096
7481
|
const lowerChild = (priority, pid) => {
|
|
7097
|
-
if (priority !== "low" || pid === void 0) return;
|
|
7482
|
+
if (priority !== "low" || pid === void 0 || existsSync(NICE)) return;
|
|
7098
7483
|
try {
|
|
7099
7484
|
setPriority(pid, constants$1.priority.PRIORITY_BELOW_NORMAL);
|
|
7100
7485
|
} catch {}
|
|
@@ -7256,7 +7641,7 @@ const checkCallTargets = (workflow, own, registered) => Object.entries(workflow.
|
|
|
7256
7641
|
const found = [];
|
|
7257
7642
|
if (node.node !== void 0) {
|
|
7258
7643
|
const type = called.nodes[node.node]?.type;
|
|
7259
|
-
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"]));
|
|
7260
7645
|
}
|
|
7261
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"]));
|
|
7262
7647
|
return found;
|
|
@@ -7959,7 +8344,7 @@ const createFanOut = (deps) => {
|
|
|
7959
8344
|
};
|
|
7960
8345
|
// ../core/src/run/delegation.ts
|
|
7961
8346
|
/** Node types a leader can hand work to. */
|
|
7962
|
-
const DELEGABLE = /* @__PURE__ */ new Set(["
|
|
8347
|
+
const DELEGABLE = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
7963
8348
|
/** A child a leader delegated to: waits for a slot, runs, reports back. */
|
|
7964
8349
|
const createRunChild = (hooks) => async (child, brief) => {
|
|
7965
8350
|
await hooks.slots.take();
|
|
@@ -8014,9 +8399,9 @@ const createOwnership = (hooks) => {
|
|
|
8014
8399
|
if (node.description !== void 0) child.description = node.description;
|
|
8015
8400
|
return [child];
|
|
8016
8401
|
});
|
|
8017
|
-
const templates = children.filter((c) => c.type === "
|
|
8402
|
+
const templates = children.filter((c) => c.type === "agent.worker").map((c) => c.id);
|
|
8018
8403
|
const leaderNode = owner?.node;
|
|
8019
|
-
const maxWorkers = leaderNode?.type === "
|
|
8404
|
+
const maxWorkers = leaderNode?.type === "agent.leader" ? leaderNode.maxWorkers ?? 0 : 0;
|
|
8020
8405
|
const spawned = [];
|
|
8021
8406
|
/** Runs the accepted jobs in parallel; the slot goes to them meanwhile. */
|
|
8022
8407
|
const runJobs = async (jobs) => {
|
|
@@ -8156,7 +8541,7 @@ const nodeContext = (deps, activation, cwd, round, clock) => {
|
|
|
8156
8541
|
budgetLeftUsd: deps.spend.left(),
|
|
8157
8542
|
budgetLeftNow: deps.spend.budgetLeft,
|
|
8158
8543
|
reserveBudget: (usd) => {
|
|
8159
|
-
deps.spend.reserve(id, usd, deps.nodeOf(activation)?.type !== "
|
|
8544
|
+
deps.spend.reserve(id, usd, deps.nodeOf(activation)?.type !== "agent.leader");
|
|
8160
8545
|
},
|
|
8161
8546
|
budgetFreed: (signal) => deps.spend.freed(id, signal),
|
|
8162
8547
|
quota: deps.spend.quota,
|
|
@@ -8177,7 +8562,16 @@ const nodeContext = (deps, activation, cwd, round, clock) => {
|
|
|
8177
8562
|
event
|
|
8178
8563
|
});
|
|
8179
8564
|
},
|
|
8180
|
-
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
|
+
}
|
|
8181
8575
|
};
|
|
8182
8576
|
const owner = ownerPort(deps, id, template);
|
|
8183
8577
|
if (owner !== void 0) context.owner = owner;
|
|
@@ -8188,7 +8582,7 @@ const nodeContext = (deps, activation, cwd, round, clock) => {
|
|
|
8188
8582
|
if (activation.item !== void 0) context.item = activation.item;
|
|
8189
8583
|
const resume = options.replay?.cutOff?.(activation) ?? resumeOf(deps.nodeOf(activation), deps.sessionIds.get(id) ?? options.replay?.previousSession?.(activation));
|
|
8190
8584
|
if (resume !== void 0) context.resume = resume;
|
|
8191
|
-
if (deps.nodeOf(activation)?.type === "
|
|
8585
|
+
if (deps.nodeOf(activation)?.type === "agent.leader") context.ownership = createOwnership({
|
|
8192
8586
|
graph,
|
|
8193
8587
|
leader: id,
|
|
8194
8588
|
runChild: deps.runChild,
|
|
@@ -8231,12 +8625,12 @@ const ownerPort = (deps, id, template) => deps.ports.owners.portFor(id, template
|
|
|
8231
8625
|
*/
|
|
8232
8626
|
const resumeOf = (node, sessionId) => {
|
|
8233
8627
|
if (node === void 0 || !isClaudeNode(node)) return void 0;
|
|
8234
|
-
if (node.type === "
|
|
8235
|
-
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;
|
|
8236
8630
|
};
|
|
8237
8631
|
// ../core/src/run/contacts.ts
|
|
8238
|
-
const SESSION_TYPES$
|
|
8239
|
-
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 ?? "");
|
|
8240
8634
|
const isTool = (node) => node?.type.startsWith("tool.") ?? false;
|
|
8241
8635
|
const channelPeersOf = (id, channels) => channels.flatMap(([a, b]) => {
|
|
8242
8636
|
if (a === id) return [b];
|
|
@@ -9134,7 +9528,7 @@ const NEVER = new Promise(() => {});
|
|
|
9134
9528
|
/** For a rejection no one is left to hear. */
|
|
9135
9529
|
const ignore = () => {};
|
|
9136
9530
|
/** The timeout that applies to a node; a gate's own means something else. */
|
|
9137
|
-
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;
|
|
9138
9532
|
const createClock = (options) => {
|
|
9139
9533
|
const { graph, holds, running } = options;
|
|
9140
9534
|
/** Nodes between two looks of a `watch:`. */
|
|
@@ -9628,7 +10022,7 @@ const createOwnerQuestions = ({ ownerOf, deliverTo, onEvent }) => {
|
|
|
9628
10022
|
};
|
|
9629
10023
|
};
|
|
9630
10024
|
// ../core/src/run/bus.ts
|
|
9631
|
-
const SESSION_TYPES$
|
|
10025
|
+
const SESSION_TYPES$3 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
9632
10026
|
const createBus = ({ graph, onEvent, mail, relay, slots }) => {
|
|
9633
10027
|
/** Spawned instance -> the declared node it is an instance of. */
|
|
9634
10028
|
const templates = /* @__PURE__ */ new Map();
|
|
@@ -9647,7 +10041,7 @@ const createBus = ({ graph, onEvent, mail, relay, slots }) => {
|
|
|
9647
10041
|
/** Sessions in `wait`: called when a message arrives or the session ends. */
|
|
9648
10042
|
const waiters = /* @__PURE__ */ new Map();
|
|
9649
10043
|
const declared = (id) => graph.nodes.get(templates.get(id) ?? id);
|
|
9650
|
-
const hasSession = (id) => SESSION_TYPES$
|
|
10044
|
+
const hasSession = (id) => SESSION_TYPES$3.has(graph.nodes.get(id)?.node.type ?? "");
|
|
9651
10045
|
const inbox = (id) => {
|
|
9652
10046
|
const existing = inboxes.get(id);
|
|
9653
10047
|
if (existing !== void 0) return existing;
|
|
@@ -9834,7 +10228,7 @@ const createBus = ({ graph, onEvent, mail, relay, slots }) => {
|
|
|
9834
10228
|
* notice when it is released; a node's claims are released when it finishes.
|
|
9835
10229
|
* Claims are advisory: they steer agents, they do not lock files.
|
|
9836
10230
|
*/
|
|
9837
|
-
const SESSION_TYPES$
|
|
10231
|
+
const SESSION_TYPES$2 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
9838
10232
|
const WHOLE = ".";
|
|
9839
10233
|
/**
|
|
9840
10234
|
* A resource as the run compares it: a path from the project root (an absolute
|
|
@@ -9859,7 +10253,7 @@ const writersOf = (graph) => {
|
|
|
9859
10253
|
const isolation = graph.workflow.defaults?.isolation ?? "worktree";
|
|
9860
10254
|
const writers = /* @__PURE__ */ new Set();
|
|
9861
10255
|
for (const [id, { node }] of graph.nodes) {
|
|
9862
|
-
if (!SESSION_TYPES$
|
|
10256
|
+
if (!SESSION_TYPES$2.has(node.type)) continue;
|
|
9863
10257
|
if ("isolation" in node && (node.isolation ?? isolation) === "readonly") continue;
|
|
9864
10258
|
writers.add(id);
|
|
9865
10259
|
}
|
|
@@ -9868,7 +10262,7 @@ const writersOf = (graph) => {
|
|
|
9868
10262
|
const spawnable = (graph, writers) => {
|
|
9869
10263
|
let count = 0;
|
|
9870
10264
|
for (const { node, children } of graph.nodes.values()) {
|
|
9871
|
-
if (node.type !== "
|
|
10265
|
+
if (node.type !== "agent.leader") continue;
|
|
9872
10266
|
if (children.some((child) => writers.has(child))) count += node.maxWorkers ?? 0;
|
|
9873
10267
|
}
|
|
9874
10268
|
return count;
|
|
@@ -9960,7 +10354,7 @@ const createClaims = ({ graph, onEvent, notify }) => {
|
|
|
9960
10354
|
};
|
|
9961
10355
|
// ../core/src/run/owners.ts
|
|
9962
10356
|
/** The node types whose sessions can ask (`ask_user`). */
|
|
9963
|
-
const ASKING = /* @__PURE__ */ new Set(["
|
|
10357
|
+
const ASKING = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
9964
10358
|
const createOwners = (graph) => {
|
|
9965
10359
|
const sessions = /* @__PURE__ */ new Map();
|
|
9966
10360
|
/** The leader that takes `id`'s questions first, if one does. */
|
|
@@ -9968,7 +10362,7 @@ const createOwners = (graph) => {
|
|
|
9968
10362
|
const node = graph.nodes.get(template ?? id)?.node;
|
|
9969
10363
|
if (node?.parent === void 0 || !ASKING.has(node.type)) return void 0;
|
|
9970
10364
|
const owner = graph.nodes.get(node.parent)?.node;
|
|
9971
|
-
return owner?.type === "
|
|
10365
|
+
return owner?.type === "agent.leader" && owner.questions === "owner" ? node.parent : void 0;
|
|
9972
10366
|
};
|
|
9973
10367
|
return {
|
|
9974
10368
|
/** Notes `id`'s own sessions as they start and end; forks aren't its own. */
|
|
@@ -11481,12 +11875,13 @@ const parseReport = (value) => {
|
|
|
11481
11875
|
// ../core/src/run/brief.ts
|
|
11482
11876
|
/**
|
|
11483
11877
|
* @file What a Claude node is asked: its brief, rendered against the run's
|
|
11484
|
-
* inputs and outputs, and
|
|
11485
|
-
*
|
|
11486
|
-
*
|
|
11487
|
-
* its
|
|
11488
|
-
*
|
|
11489
|
-
* worktree or edges
|
|
11878
|
+
* inputs and outputs, the model and effort it runs on (a fanned node's from
|
|
11879
|
+
* its item), and its `agent.subagent` children as Claude Code subagents of
|
|
11880
|
+
* its session (the SDK's `agents`, the CLI's `--agents`). A subagent's role
|
|
11881
|
+
* (its agent's instructions), `instructions` and `prompt` are its system
|
|
11882
|
+
* prompt, and its `description` says when the session hands it work with the
|
|
11883
|
+
* Agent tool; it runs inside that session, with no process, worktree or edges
|
|
11884
|
+
* of its own.
|
|
11490
11885
|
*/
|
|
11491
11886
|
/** The tool a session hands a subagent work with. */
|
|
11492
11887
|
const AGENT_TOOL = "Agent";
|
|
@@ -11515,6 +11910,23 @@ const renderBrief = (text, inputs, outputs, before, item) => {
|
|
|
11515
11910
|
warnings
|
|
11516
11911
|
};
|
|
11517
11912
|
};
|
|
11913
|
+
/**
|
|
11914
|
+
* The model and effort a session asks for: the node's own, rendered with its
|
|
11915
|
+
* item on a fanned node (`{{item.model}}`), else the workflow's defaults. An
|
|
11916
|
+
* item's value that is no model name or effort level fails it.
|
|
11917
|
+
*/
|
|
11918
|
+
const modelOf = (node, defaults, run) => {
|
|
11919
|
+
const scope = runScope(run.inputs, run.outputs ?? {}, void 0, run.item);
|
|
11920
|
+
const model = itemSetting("model", node.model, scope);
|
|
11921
|
+
if (!model.ok) return model;
|
|
11922
|
+
const effort = itemEffort(node.effort, scope);
|
|
11923
|
+
if (!effort.ok) return effort;
|
|
11924
|
+
return {
|
|
11925
|
+
ok: true,
|
|
11926
|
+
model: model.value ?? defaults.model,
|
|
11927
|
+
effort: effort.value ?? defaults.effort
|
|
11928
|
+
};
|
|
11929
|
+
};
|
|
11518
11930
|
const rendered = (text, inputs, outputs) => {
|
|
11519
11931
|
const brief = renderBrief(text, inputs, outputs);
|
|
11520
11932
|
return brief.ok ? brief.text : (text ?? "").trim();
|
|
@@ -11522,7 +11934,7 @@ const rendered = (text, inputs, outputs) => {
|
|
|
11522
11934
|
/** The subagents of `owner`'s session, by node id, or none. */
|
|
11523
11935
|
const subagentsOf = (workflow, owner, inputs, outputs = {}) => {
|
|
11524
11936
|
const entries = Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
11525
|
-
if (node.type !== "
|
|
11937
|
+
if (node.type !== "agent.subagent" || node.parent !== owner) return [];
|
|
11526
11938
|
if (!isClaudeNode(node)) return [];
|
|
11527
11939
|
const prompt = [
|
|
11528
11940
|
node.agent === void 0 ? "" : workflow.agents?.[node.agent]?.instructions?.trim() ?? "",
|
|
@@ -11533,7 +11945,7 @@ const subagentsOf = (workflow, owner, inputs, outputs = {}) => {
|
|
|
11533
11945
|
description: node.description,
|
|
11534
11946
|
prompt: prompt === "" ? node.description : prompt
|
|
11535
11947
|
};
|
|
11536
|
-
if (node.model !== void 0) spec.model = node.model;
|
|
11948
|
+
if (node.model !== void 0) spec.model = modelDriver(node.model).model;
|
|
11537
11949
|
if (node.maxTurns !== void 0) spec.maxTurns = node.maxTurns;
|
|
11538
11950
|
if (node.tools?.only !== void 0) spec.tools = node.tools.only;
|
|
11539
11951
|
if (node.tools?.deny !== void 0) spec.disallowedTools = node.tools.deny;
|
|
@@ -11541,20 +11953,6 @@ const subagentsOf = (workflow, owner, inputs, outputs = {}) => {
|
|
|
11541
11953
|
});
|
|
11542
11954
|
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
11543
11955
|
};
|
|
11544
|
-
// ../core/src/run/driver.ts
|
|
11545
|
-
/** The node types that start a session of their own, and so need a driver. */
|
|
11546
|
-
const SESSION_TYPES$2 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
|
|
11547
|
-
/** The driver a workflow names for a node, if it names one. */
|
|
11548
|
-
const namedDriver = (node, workflow) => ("driver" in node ? node.driver : void 0) ?? workflow.defaults?.driver;
|
|
11549
|
-
/** The session nodes that name no driver, which take the one ready here. */
|
|
11550
|
-
const namingNoDriver = (workflow) => Object.entries(workflow.nodes).filter(([, node]) => SESSION_TYPES$2.has(node.type) && namedDriver(node, workflow) === void 0).map(([id]) => id);
|
|
11551
|
-
/**
|
|
11552
|
-
* The driver a session runs on: `override` (`--driver`), else what the workflow
|
|
11553
|
-
* names, else `ready`, the driver the run found ready when it started (and
|
|
11554
|
-
* recorded, for a resume). `undefined` only when none applies, which a run
|
|
11555
|
-
* refuses before it starts.
|
|
11556
|
-
*/
|
|
11557
|
-
const sessionDriver = (choice) => choice.override ?? choice.named ?? choice.ready;
|
|
11558
11956
|
// ../core/src/run/harness.ts
|
|
11559
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.";
|
|
11560
11958
|
const HANDOFF = {
|
|
@@ -11679,7 +12077,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11679
12077
|
ok: false,
|
|
11680
12078
|
error: `\`${id}\` is a \`${node.type}\`, not a Claude node`
|
|
11681
12079
|
};
|
|
11682
|
-
if (node.type === "
|
|
12080
|
+
if (node.type === "agent.subagent") return {
|
|
11683
12081
|
ok: false,
|
|
11684
12082
|
error: `\`${id}\` is a sub-agent; it runs inside \`${node.parent}\`'s session`
|
|
11685
12083
|
};
|
|
@@ -11691,6 +12089,17 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11691
12089
|
ok: false,
|
|
11692
12090
|
error: `\`${id}\` uses worktree isolation, which a run on its own does not set up; run the whole workflow, or pass \`--in-place\` to run it in this checkout`
|
|
11693
12091
|
};
|
|
12092
|
+
const models = modelOf(node, defaults, options);
|
|
12093
|
+
if (!models.ok) return {
|
|
12094
|
+
ok: false,
|
|
12095
|
+
error: models.error
|
|
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);
|
|
11694
12103
|
const warnings = [];
|
|
11695
12104
|
const brief = renderBrief(node.prompt, options.inputs, options.outputs, options.before, options.item);
|
|
11696
12105
|
if (!brief.ok) return {
|
|
@@ -11732,16 +12141,14 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11732
12141
|
permissionMode: "default",
|
|
11733
12142
|
reportSchema: reportSchemaFor(node)
|
|
11734
12143
|
};
|
|
11735
|
-
if (node.type === "
|
|
12144
|
+
if (node.type === "agent.leader") spec.promptCache = "1h";
|
|
11736
12145
|
const agents = subagentsOf(workflow, declared, options.inputs, options.outputs);
|
|
11737
12146
|
if (agents !== void 0) {
|
|
11738
12147
|
spec.agents = agents;
|
|
11739
12148
|
spec.tools.only?.push(AGENT_TOOL);
|
|
11740
12149
|
}
|
|
11741
|
-
|
|
11742
|
-
if (
|
|
11743
|
-
const effort = node.effort ?? defaults.effort;
|
|
11744
|
-
if (effort !== void 0) spec.effort = effort;
|
|
12150
|
+
if (model !== void 0) spec.model = model.model;
|
|
12151
|
+
if (models.effort !== void 0) spec.effort = models.effort;
|
|
11745
12152
|
const maxTurns = node.maxTurns ?? defaults.maxTurns;
|
|
11746
12153
|
if (maxTurns !== void 0) spec.maxTurns = maxTurns;
|
|
11747
12154
|
const budgets = [node.budgetUsd ?? defaults.budgetUsd, options.budgetLeftUsd].filter((b) => b !== void 0);
|
|
@@ -11754,6 +12161,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
11754
12161
|
ok: true,
|
|
11755
12162
|
spec,
|
|
11756
12163
|
driver: namedDriver(node, workflow),
|
|
12164
|
+
pinned: model?.driver,
|
|
11757
12165
|
mcp,
|
|
11758
12166
|
warnings,
|
|
11759
12167
|
context
|
|
@@ -12063,18 +12471,16 @@ const followEvents = async (store, runId, onEvent, options) => {
|
|
|
12063
12471
|
seq: options.after ?? 0,
|
|
12064
12472
|
finished: false
|
|
12065
12473
|
};
|
|
12066
|
-
const done = new AbortController();
|
|
12067
12474
|
const emit = (stored) => {
|
|
12068
12475
|
if (stored.seq <= seen.seq) return;
|
|
12069
12476
|
seen.seq = stored.seq;
|
|
12070
12477
|
onEvent(stored);
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
}
|
|
12478
|
+
const { type } = stored.event;
|
|
12479
|
+
if (type === "run.finished") seen.finished = true;
|
|
12480
|
+
else if (type === "run.started" || type === "run.resumed") seen.finished = false;
|
|
12075
12481
|
};
|
|
12076
12482
|
const aborted = () => options.signal?.aborted === true;
|
|
12077
|
-
/** Takes what the store has; true once
|
|
12483
|
+
/** Takes what the store has; true once the run's latest go has finished. */
|
|
12078
12484
|
const drain = () => {
|
|
12079
12485
|
for (const stored of store.events(runId, seen.seq)) emit(stored);
|
|
12080
12486
|
return seen.finished;
|
|
@@ -12083,7 +12489,11 @@ const followEvents = async (store, runId, onEvent, options) => {
|
|
|
12083
12489
|
if (drain()) return "finished";
|
|
12084
12490
|
if (!options.follow) return "stored";
|
|
12085
12491
|
if (aborted()) return "aborted";
|
|
12086
|
-
const
|
|
12492
|
+
const done = new AbortController();
|
|
12493
|
+
const via = await followSocket(socketPath(runId, options.home), seen.seq, (stored) => {
|
|
12494
|
+
emit(stored);
|
|
12495
|
+
if (seen.finished) done.abort();
|
|
12496
|
+
}, options.signal === void 0 ? done.signal : AbortSignal.any([options.signal, done.signal]));
|
|
12087
12497
|
if (seen.finished) return "finished";
|
|
12088
12498
|
if (aborted()) return "aborted";
|
|
12089
12499
|
if (via === "ended") continue;
|
|
@@ -12407,6 +12817,7 @@ const resumeState = (events) => {
|
|
|
12407
12817
|
let ended;
|
|
12408
12818
|
/** Nodes whose last activation did not finish well, and its session. */
|
|
12409
12819
|
const unfinished = /* @__PURE__ */ new Map();
|
|
12820
|
+
const places = /* @__PURE__ */ new Map();
|
|
12410
12821
|
/** A new run of `id`'s `round` replaces what is recorded there and after. */
|
|
12411
12822
|
const supersede = (id, round) => {
|
|
12412
12823
|
fanOuts.items.get(id)?.delete(round);
|
|
@@ -12443,7 +12854,10 @@ const resumeState = (events) => {
|
|
|
12443
12854
|
const inner = event.event;
|
|
12444
12855
|
const current = open.get(event.id);
|
|
12445
12856
|
if (current === void 0 || inner.fork === true) continue;
|
|
12446
|
-
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
|
+
}
|
|
12447
12861
|
if (inner.type === "session.finished" && inner.outcome.sessionId) current.sessionId = inner.outcome.sessionId;
|
|
12448
12862
|
continue;
|
|
12449
12863
|
}
|
|
@@ -12536,7 +12950,8 @@ const resumeState = (events) => {
|
|
|
12536
12950
|
succeeded,
|
|
12537
12951
|
cutOff: cut ? unfinished : /* @__PURE__ */ new Map(),
|
|
12538
12952
|
mail: mailOf(events),
|
|
12539
|
-
earlier: earlierSpend(events)
|
|
12953
|
+
earlier: earlierSpend(events),
|
|
12954
|
+
places
|
|
12540
12955
|
};
|
|
12541
12956
|
};
|
|
12542
12957
|
// ../core/src/remote/resume.ts
|
|
@@ -13016,7 +13431,7 @@ const requestStop = (store, run, home, node) => request(store, run, withNode("st
|
|
|
13016
13431
|
const requestPause = (store, run, home, node) => request(store, run, withNode("pause", node), pauseRefusal(store, run, node), home);
|
|
13017
13432
|
/** Releases a pause: the run's (and with it every node's), or one node's. */
|
|
13018
13433
|
const requestContinue = (store, run, home, node) => request(store, run, withNode("continue", node), continueRefusal(store, run, node), home);
|
|
13019
|
-
const SESSION_TYPES$1 = /* @__PURE__ */ new Set(["
|
|
13434
|
+
const SESSION_TYPES$1 = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
13020
13435
|
const addressOf = ({ run, node }) => `${run}/${node}`;
|
|
13021
13436
|
/** The other side of `link` from `run`'s `node`, or `undefined` if not on it. */
|
|
13022
13437
|
const peerOf = (link, run, node) => {
|
|
@@ -13398,7 +13813,7 @@ const createCalls = (options) => {
|
|
|
13398
13813
|
};
|
|
13399
13814
|
};
|
|
13400
13815
|
// ../core/src/run/driver-queues.ts
|
|
13401
|
-
const isSession = (node) => node.type === "
|
|
13816
|
+
const isSession = (node) => node.type === "agent.leader" || node.type === "agent.worker";
|
|
13402
13817
|
const isolationOf = (node, workflow) => ("isolation" in node ? node.isolation : void 0) ?? workflow.defaults?.isolation ?? "worktree";
|
|
13403
13818
|
/**
|
|
13404
13819
|
* Where a session works: the node whose worktree it uses, `null` for the
|
|
@@ -13470,22 +13885,18 @@ const messageOf$1 = (driver, found) => {
|
|
|
13470
13885
|
};
|
|
13471
13886
|
/**
|
|
13472
13887
|
* The checkouts where sessions on a one-per-checkout driver could queue: the
|
|
13473
|
-
* driver each node takes is as for `driverRefusals` (
|
|
13474
|
-
* 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`).
|
|
13475
13890
|
*/
|
|
13476
13891
|
const driverQueues = (workflow, drivers, choice) => drivers.filter((driver) => driver.onePerCheckout).flatMap((driver) => checkoutContention(workflow, (id) => {
|
|
13477
13892
|
const node = workflow.nodes[id];
|
|
13478
|
-
return node !== void 0 &&
|
|
13479
|
-
override: choice.override,
|
|
13480
|
-
named: namedDriver(node, workflow),
|
|
13481
|
-
ready: choice.ready
|
|
13482
|
-
}) === driver.id;
|
|
13893
|
+
return node !== void 0 && nodeDriver(node, workflow, choice) === driver.id;
|
|
13483
13894
|
}).map((found) => Object.assign({}, found, {
|
|
13484
13895
|
driver: driver.id,
|
|
13485
13896
|
message: messageOf$1(driver.id, found)
|
|
13486
13897
|
})));
|
|
13487
13898
|
// ../core/src/run/driver-needs.ts
|
|
13488
|
-
const SESSION_TYPES = /* @__PURE__ */ new Set(["
|
|
13899
|
+
const SESSION_TYPES = /* @__PURE__ */ new Set(["agent.leader", "agent.worker"]);
|
|
13489
13900
|
/** The node's turn limit and dollar budget, its own or the workflow's default. */
|
|
13490
13901
|
const limitsOf = (node, workflow) => {
|
|
13491
13902
|
const own = isClaudeNode(node) ? node : void 0;
|
|
@@ -13499,26 +13910,28 @@ const limitsOf = (node, workflow) => {
|
|
|
13499
13910
|
* brings their questions to it).
|
|
13500
13911
|
*/
|
|
13501
13912
|
const askOfTools = (id, node, workflow) => {
|
|
13502
|
-
if (node.type !== "
|
|
13503
|
-
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;
|
|
13504
13915
|
};
|
|
13505
13916
|
/**
|
|
13506
|
-
* What
|
|
13507
|
-
*
|
|
13508
|
-
* with no driver to check is left out; the run refuses that on its own. An
|
|
13509
|
-
* owner and a node it owns sharing a checkout on a one-per-checkout driver are
|
|
13510
|
-
* 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.
|
|
13511
13919
|
*/
|
|
13512
|
-
const
|
|
13513
|
-
if (
|
|
13514
|
-
|
|
13515
|
-
const
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
});
|
|
13520
|
-
|
|
13521
|
-
|
|
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) => {
|
|
13522
13935
|
const { maxTurns, budgetUsd } = limitsOf(node, workflow);
|
|
13523
13936
|
const refuse = (message) => ({
|
|
13524
13937
|
node: id,
|
|
@@ -13532,7 +13945,22 @@ const driverRefusals = (workflow, drivers, choice) => [...Object.entries(workflo
|
|
|
13532
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`));
|
|
13533
13946
|
const ask = askOfTools(id, node, workflow);
|
|
13534
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)`));
|
|
13535
|
-
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);
|
|
13536
13964
|
}), ...driverQueues(workflow, drivers, choice).flatMap(({ owner, nodes, message }) => owner === void 0 || choice.only !== void 0 && !nodes.includes(choice.only) ? [] : [{
|
|
13537
13965
|
node: owner,
|
|
13538
13966
|
message
|
|
@@ -13625,95 +14053,6 @@ const draftQueries = (db) => {
|
|
|
13625
14053
|
}
|
|
13626
14054
|
};
|
|
13627
14055
|
};
|
|
13628
|
-
// ../core/src/worktree/git-env.ts
|
|
13629
|
-
/**
|
|
13630
|
-
* @file Git's local environment variables, the ones a git hook sets (`GIT_DIR`,
|
|
13631
|
-
* `GIT_INDEX_FILE`, …), and an environment without them. Ork finds a project
|
|
13632
|
-
* by its path, never through these, so an `ork` started from a hook clears
|
|
13633
|
-
* them at its entry, and core's git runner leaves them out for callers that
|
|
13634
|
-
* don't start there (specs/worktrees.md).
|
|
13635
|
-
*/
|
|
13636
|
-
/**
|
|
13637
|
-
* What `git rev-parse --local-env-vars` prints (git 2.39.5). A fixed list, so
|
|
13638
|
-
* no git is spawned at every start; a test fails when the installed git names
|
|
13639
|
-
* one that is missing here.
|
|
13640
|
-
*/
|
|
13641
|
-
const GIT_LOCAL_ENV = [
|
|
13642
|
-
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
13643
|
-
"GIT_CONFIG",
|
|
13644
|
-
"GIT_CONFIG_PARAMETERS",
|
|
13645
|
-
"GIT_CONFIG_COUNT",
|
|
13646
|
-
"GIT_OBJECT_DIRECTORY",
|
|
13647
|
-
"GIT_DIR",
|
|
13648
|
-
"GIT_WORK_TREE",
|
|
13649
|
-
"GIT_IMPLICIT_WORK_TREE",
|
|
13650
|
-
"GIT_GRAFT_FILE",
|
|
13651
|
-
"GIT_INDEX_FILE",
|
|
13652
|
-
"GIT_NO_REPLACE_OBJECTS",
|
|
13653
|
-
"GIT_REPLACE_REF_BASE",
|
|
13654
|
-
"GIT_PREFIX",
|
|
13655
|
-
"GIT_INTERNAL_SUPER_PREFIX",
|
|
13656
|
-
"GIT_SHALLOW_FILE",
|
|
13657
|
-
"GIT_COMMON_DIR"
|
|
13658
|
-
];
|
|
13659
|
-
/** A copy of `env` without git's local variables. */
|
|
13660
|
-
const withoutGitLocalEnv = (env) => {
|
|
13661
|
-
const clean = { ...env };
|
|
13662
|
-
for (const name of GIT_LOCAL_ENV) delete clean[name];
|
|
13663
|
-
return clean;
|
|
13664
|
-
};
|
|
13665
|
-
/** Removes git's local variables from `env` in place (the CLI's entry). */
|
|
13666
|
-
const clearGitLocalEnv = (env = process.env) => {
|
|
13667
|
-
for (const name of GIT_LOCAL_ENV) delete env[name];
|
|
13668
|
-
};
|
|
13669
|
-
// ../core/src/worktree/git.ts
|
|
13670
|
-
/**
|
|
13671
|
-
* @file A minimal `git` runner for the worktree manager: arguments without a
|
|
13672
|
-
* shell, results that never throw, and none of git's local variables (a
|
|
13673
|
-
* hook's `GIT_DIR` or `GIT_INDEX_FILE`), so it acts on the repository at
|
|
13674
|
-
* `cwd` (specs/worktrees.md). Commands in one folder run one at a time: two
|
|
13675
|
-
* children handed their owner's work at once both stage it (`git add -A`),
|
|
13676
|
-
* and git refuses the second while the first holds the index's lock.
|
|
13677
|
-
*/
|
|
13678
|
-
/** The last command queued in each folder, so the next one waits for it. */
|
|
13679
|
-
const queues = /* @__PURE__ */ new Map();
|
|
13680
|
-
/** Runs `task` after every command queued earlier in the same folder. */
|
|
13681
|
-
const inTurn = (cwd, task) => {
|
|
13682
|
-
const key = path.resolve(cwd);
|
|
13683
|
-
const next = (queues.get(key) ?? Promise.resolve()).then(task, task);
|
|
13684
|
-
queues.set(key, next);
|
|
13685
|
-
next.finally(() => {
|
|
13686
|
-
if (queues.get(key) === next) queues.delete(key);
|
|
13687
|
-
});
|
|
13688
|
-
return next;
|
|
13689
|
-
};
|
|
13690
|
-
const runGit = (cwd, args) => new Promise((resolve) => {
|
|
13691
|
-
execFile("git", args, {
|
|
13692
|
-
cwd,
|
|
13693
|
-
env: withoutGitLocalEnv(process.env),
|
|
13694
|
-
maxBuffer: 33554432,
|
|
13695
|
-
encoding: "utf8"
|
|
13696
|
-
}, (error, stdout, stderr) => {
|
|
13697
|
-
resolve({
|
|
13698
|
-
code: error === null ? 0 : typeof error.code === "number" ? error.code : -1,
|
|
13699
|
-
stdout,
|
|
13700
|
-
stderr: error !== null && !existsSync(cwd) ? `the folder ${cwd} doesn't exist` : stderr || (error?.message ?? "")
|
|
13701
|
-
});
|
|
13702
|
-
});
|
|
13703
|
-
});
|
|
13704
|
-
/** Runs `git` with arguments (no shell) in `cwd`. Never throws. */
|
|
13705
|
-
const git = (cwd, args) => inTurn(cwd, () => runGit(cwd, args));
|
|
13706
|
-
/** Like `git`, but rejects with git's message when the command fails. */
|
|
13707
|
-
const gitOk = async (cwd, args) => {
|
|
13708
|
-
const result = await git(cwd, args);
|
|
13709
|
-
if (result.code !== 0) {
|
|
13710
|
-
const reason = result.stderr.trim().split("\n").at(-1) ?? "";
|
|
13711
|
-
throw new Error(`git ${args[0] ?? ""} failed: ${reason}`);
|
|
13712
|
-
}
|
|
13713
|
-
return result.stdout;
|
|
13714
|
-
};
|
|
13715
|
-
/** Output lines without the trailing empty one. */
|
|
13716
|
-
const lines = (text) => text.split("\n").filter((line) => line !== "");
|
|
13717
14056
|
// ../core/src/worktree/manager.ts
|
|
13718
14057
|
/**
|
|
13719
14058
|
* @file Ork's git worktrees: one per `worktree` node, beside the clone, by
|
|
@@ -15808,6 +16147,36 @@ const syncFrom = async (worktree, from) => {
|
|
|
15808
16147
|
if (!applied.ok) throw new Error(`could not take ${from}'s changes: ${applied.error}`);
|
|
15809
16148
|
return snapshot(worktree);
|
|
15810
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
|
+
};
|
|
15811
16180
|
// ../core/src/worktree/upstream.ts
|
|
15812
16181
|
/** Where `id` worked: its own place, or for a fanned node each item's. */
|
|
15813
16182
|
const placesOf = (id, placeOf) => {
|
|
@@ -16084,13 +16453,11 @@ const createWorkspace = (options) => {
|
|
|
16084
16453
|
const templateOf = /* @__PURE__ */ new Map();
|
|
16085
16454
|
/** A `continue` node → the node whose worktree it works in this time. */
|
|
16086
16455
|
const continued = /* @__PURE__ */ new Map();
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
if (wasMerged) merged.add(id);
|
|
16093
|
-
}
|
|
16456
|
+
seedResumed(options.adopt ?? [], options.places ?? /* @__PURE__ */ new Map(), {
|
|
16457
|
+
worktrees,
|
|
16458
|
+
placeOf,
|
|
16459
|
+
merged
|
|
16460
|
+
});
|
|
16094
16461
|
const nodeOf = (id) => graph.nodes.get(templateOf.get(id) ?? id)?.node;
|
|
16095
16462
|
const ownerOf = (id) => nodeOf(id)?.parent;
|
|
16096
16463
|
const isolationOf = (id) => {
|
|
@@ -16252,4 +16619,4 @@ const createWorkspace = (options) => {
|
|
|
16252
16619
|
close
|
|
16253
16620
|
};
|
|
16254
16621
|
};
|
|
16255
|
-
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 };
|