@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.
Files changed (37) hide show
  1. package/dist/{__build_info__-BJbE3Zvp.mjs → __build_info__-Da73_nBs.mjs} +3 -3
  2. package/dist/{attach-BmV5ckb9.mjs → attach-E6NNyA3T.mjs} +4 -4
  3. package/dist/bin.mjs +114 -55
  4. package/dist/{builtin-files-w0YULJ4c.mjs → builtin-files-n6o6oWpB.mjs} +2 -2
  5. package/dist/{clean-D8GI_2K-.mjs → clean-Bh4KZgzc.mjs} +1 -1
  6. package/dist/{completion-CzElB8nS.mjs → completion-SNNNxnv9.mjs} +6 -6
  7. package/dist/{context-BAwgVSX5.mjs → context-DHudLA5e.mjs} +4 -4
  8. package/dist/{decide-DlbyctOE.mjs → decide-DuIHACvH.mjs} +1 -1
  9. package/dist/{find-p05wlbmu.mjs → find-CjsNYAAf.mjs} +1 -1
  10. package/dist/{format-Dni98cbh.mjs → format-C60TFpRe.mjs} +1 -1
  11. package/dist/{help-groups-DYP_zMRP.mjs → help-groups-CyfPWlST.mjs} +4 -0
  12. package/dist/{in-a-run-BI-hLIl_.mjs → in-a-run-BYjTXaST.mjs} +1 -1
  13. package/dist/{limits-CElmhowV.mjs → limits-C5QKZNLT.mjs} +2 -2
  14. package/dist/{list-zW2xKuGr.mjs → list-BUb5Uc3H.mjs} +1 -1
  15. package/dist/{mcp-CM3b65AZ.mjs → mcp-bcctR-wc.mjs} +6 -6
  16. package/dist/once-DPQu5Q3H.mjs +76 -0
  17. package/dist/{pause-BuS83WAI.mjs → pause-BPvlDIvz.mjs} +3 -3
  18. package/dist/{relay-DAsJA8wF.mjs → relay-C8W0t_zc.mjs} +1 -1
  19. package/dist/{remove-Dh7D_hVn.mjs → remove-BkFWGVYo.mjs} +3 -3
  20. package/dist/run-CQIfq918.mjs +192 -0
  21. package/dist/{send-MFEQZH31.mjs → send-CaZlGG5a.mjs} +2 -2
  22. package/dist/{src-CdDTQLdQ.mjs → src-BeR6f70A.mjs} +424 -206
  23. package/dist/{src-S9yK2OHU.mjs → src-CGJGL1sZ.mjs} +7 -7
  24. package/dist/{src-D1QedD5y.mjs → src-CqRqsb2t.mjs} +44 -7
  25. package/dist/{src-BfxkJOYm.mjs → src-D93tHZ5B.mjs} +110 -35
  26. package/dist/{store-B2OaE4ID.mjs → store-CM3cERGl.mjs} +14 -5
  27. package/dist/ui/THIRD_PARTY_LICENSES.txt +18 -0
  28. package/dist/ui/assets/app-DYRC0V4p.js +111 -0
  29. package/dist/ui/assets/{index-BL7Ad0OA.js → index-CWwm8HTG.js} +2 -2
  30. package/dist/ui/index.html +1 -1
  31. package/dist/{ui-C4Ys26oO.mjs → ui-D2Ydofq0.mjs} +3 -3
  32. package/dist/{usage-C2zx-X6q.mjs → usage-6WIowblz.mjs} +4 -4
  33. package/dist/{watch-DzCeqM05.mjs → watch-uOU_WeBI.mjs} +5 -5
  34. package/dist/{workflows-Rr5w90A0.mjs → workflows-7tQdMnrA.mjs} +2 -2
  35. package/dist/{worktrees-f6SAbum5.mjs → worktrees-ZCII298F.mjs} +3 -3
  36. package/package.json +1 -1
  37. 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 `claude.leader` or `claude.worker` to write to."),
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({ 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.") }).describe("A program that must pass before the node's report is accepted. It runs in the node's checkout.");
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("claude.leader"),
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("claude.worker"),
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("claude.subagent"),
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
- "claude.leader",
867
- "claude.worker",
868
- "claude.subagent"
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 === "claude.leader" || node.type === "claude.worker" ? node.before ?? defaults?.before : void 0;
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("claude.") ? [field] : [`json.${field}`, field];
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 === "claude.subagent") continue;
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 !== "claude.leader" && node.type !== "claude.worker") return [];
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 === "claude.leader" && (node.maxWorkers ?? 0) > 0) return true;
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 !== "claude.subagent" && isolation === "worktree") count += fanned ? 2 : 1;
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
- "claude.leader": [
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
- "claude.worker": [
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
- "claude.subagent": [
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("claude.")) return NOTHING;
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 === "claude.subagent");
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
- * - A `claude.subagent` is owned by a `claude.leader` or `claude.worker` (it runs
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 a `claude.leader`;
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 `claude.leader` or `claude.worker` child can't set it.
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 === "claude.subagent") {
6215
- if (owner.type !== "claude.leader" && owner.type !== "claude.worker") diagnostics.push(error("tree.owner-kind", `a sub-agent runs inside a \`claude.leader\` or \`claude.worker\` session, not a \`${owner.type}\``, path));
6216
- } else if (owner.type !== "claude.leader") diagnostics.push(error("tree.owner-kind", `only a claude.leader can own a \`${node.type}\` (\`${node.parent}\` is a \`${owner.type}\`)`, path));
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 !== "claude.leader" || node.questions === void 0) continue;
6229
- if (!Object.values(nodes).some((other) => other.parent === id && (other.type === "claude.leader" || other.type === "claude.worker"))) diagnostics.push(error("questions.no-children", `\`${id}\` sets \`questions\`, but owns no \`claude.leader\` or \`claude.worker\` whose questions it could take`, [
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 !== "claude.subagent" && (members.has(id) || ownedByMember(id))).reduce((sum, [, node]) => sum + itemCountOf(node), 0);
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 === "claude.subagent") return [];
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 === "claude.subagent" && node.timeout !== void 0) diagnostics.push(warning("timeout.subagent", `sub-agent \`${id}\` runs inside its parent's session, so its timeout has no effect; set one on the parent`, [...path, "timeout"]));
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 === "claude.leader" || node?.type === "claude.worker" || node !== void 0 && isToolNode(node) || node?.type === "workflow.call" && node.node !== void 0);
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 \`claude.leader\` and \`claude.worker\` nodes, a tool node, and a \`workflow.call\` that names a node`, path));
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 === "claude.leader" || node?.type === "claude.worker";
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 a \`claude.leader\` or \`claude.worker\``, path));
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(["claude.leader", "claude.worker"]);
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 a \`claude.leader\` or \`claude.worker\`, which has a session to read it`, [...path, "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 !== "claude.leader" && type !== "claude.worker") found.push(error("call.node", `call \`${id}\`: \`${node.node}\` is not a leader or worker of \`${node.workflow}\``, [...at, "node"]));
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(["claude.leader", "claude.worker"]);
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 === "claude.worker").map((c) => c.id);
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 === "claude.leader" ? leaderNode.maxWorkers ?? 0 : 0;
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 !== "claude.leader");
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 === "claude.leader") context.ownership = createOwnership({
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 === "claude.subagent") return void 0;
8361
- return (node.session ?? (node.type === "claude.leader" ? "resume" : "fresh")) === "resume" ? sessionId : void 0;
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$5 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
8365
- const isSession$1 = (node) => SESSION_TYPES$5.has(node?.type ?? "");
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 !== "claude.subagent" ? node.timeout ?? workflow.defaults?.timeout : void 0;
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$4 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
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$4.has(graph.nodes.get(id)?.node.type ?? "");
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$3 = /* @__PURE__ */ new Set(["claude.leader", "claude.worker"]);
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$3.has(node.type)) continue;
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 !== "claude.leader") continue;
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(["claude.leader", "claude.worker"]);
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 === "claude.leader" && owner.questions === "owner" ? node.parent : void 0;
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 `claude.subagent` children as Claude Code subagents of
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 !== "claude.subagent" || node.parent !== owner) return [];
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 === "claude.subagent") return {
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 === "claude.leader") spec.promptCache = "1h";
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 (models.model !== void 0) spec.model = models.model;
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") current.sessionId = inner.sessionId;
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(["claude.leader", "claude.worker"]);
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 === "claude.leader" || node.type === "claude.worker";
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` (`override`, else what the
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 && sessionDriver({
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(["claude.leader", "claude.worker"]);
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 !== "claude.leader") return void 0;
13652
- return Object.values(workflow.nodes).some((child) => child.parent === id && (child.type === "claude.leader" || child.type === "claude.worker")) ? "delegates to the nodes it owns" : void 0;
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 stops each session node (or only `only`) from running on its driver:
13656
- * `override` (`--driver`), else what the workflow names, else `ready`. A node
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 driverRefusals = (workflow, drivers, choice) => [...Object.entries(workflow.nodes).flatMap(([id, node]) => {
13662
- if (!SESSION_TYPES.has(node.type)) return [];
13663
- if (choice.only !== void 0 && choice.only !== id) return [];
13664
- const driverId = sessionDriver({
13665
- override: choice.override,
13666
- named: namedDriver(node, workflow),
13667
- ready: choice.ready
13668
- });
13669
- const driver = drivers.find((d) => d.id === driverId);
13670
- if (driver === void 0) return [];
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
- for (const left of options.adopt ?? []) {
16237
- if (!existsSync(left.path)) continue;
16238
- const { id, merged: wasMerged, ...worktree } = left;
16239
- worktrees.set(id, worktree);
16240
- placeOf.set(id, worktree.path);
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 { DETACHED_ENV as $, skillsIn as $n, endOf as $t, lines as A, linkTools as An, AGENT_DRIVERS as Ar, nestItems as At, linkNodes as B, placeFiles as Bn, lowerChild as Bt, openStore as C, wakeRun as Cn, TOOL_PREFIX as Cr, runStatus as Ct, removeWorktree as D, planAgentMigration as Dn, readyDriver as Dr, processAlive as Dt, inspectProject as E, maskSecrets as En, driverChecks as Er, templateOf as Et, driverQueues as F, catalogAgent as Fn, knownExperiments as Fr, failedWith as Ft, requestContinue as G, workflowDirOf as Gn, PROJECT_FILE as Gt, tellNode as H, projectsHome as Hn, envReadsOf as Ht, createCalls as I, loadAgents as In, stoppedEnd as It, tellLiveRun as J, LESSON_CAP_TOKENS as Jn, machineRulesOf as Jt, requestPause as K, inside as Kn, gitRulesOf as Kt, addressOf as L, hasInRepoFiles as Ln, calledWorkflows as Lt, parseInputPairs as M, toolsInUse as Mn, firstLine as Mr, runScope as Mt, resolveInputs as N, createAgent as Nn, EXPERIMENTS as Nr, declaredProblem as Nt, git as O, applySteps as On, AGENT_TOOL_LAYOUTS as Or, stillTheSameProcess as Ot, driverRefusals as P, deleteAgent as Pn, experimentsOn as Pr, endedWith as Pt, listKey as Q, writePlainFile as Qn, createSignals as Qt, endLink as R, inRepoPlace as Rn, slotClock as Rt, isNodeEvent as S, socketRoom as Sn, INTEGRATIONS as Sr, pausedOf as St, BRANCH_PREFIX as T, maskReplacer as Tn, PREFERRED_DRIVER as Tr, slotWaitsOf as Tt, unlinkNodes as U, runDirOf as Un, nodeSays as Ut, peerOf as V, placeFor as Vn, startAt as Vt, flattenTree as W, shownPath as Wn, declaredOutputOf as Wt, detachResume as X, guardedBase as Xn, mcpNotText as Xt, adoptedCalls as Y, estimateTokens as Yn, MCP_FILE as Yt, resumable as Z, plainPathInside as Zn, serversIn as Zt, readProjectTriggers as _, parseTemplate as _n, isToolNode as _r, startRun as _t, releaseNotes as a, FEEDBACK_SEVERITIES as an, programsRunBy as ar, planReason as at, watchingPid as b, serveRun as bn, retiredDriverHint as br, heldTimeout as bt, detectInstaller as c, planPost as cn, serializeWorkflow as cr, explainSession as ct, parseVersion as d, publishPost as dn, WorkflowSchema as dr, sessionDriver as dt, outcomeEnd as en, WorkflowEditSchema as er, detachRun as et, lateTriggers as f, createFeedback as fn, CheckReportSchema as fr, parseReport as ft, declaredTriggers as g, evaluate as gn, isClaudeNode as gr, walkSummary as gt, triggerStanding as h, targetOf as hn, NodeSchema as hr, walkChecks as ht, planUpgrade as i, FEEDBACK_KINDS as in, beforeOf as ir, limitsReport as it, clearGitLocalEnv as j, toolLinks as jn, DRIVER_IDS as jr, scriptContactsOf as jt, gitOk as k, planMoveInto as kn, ORK_LAYOUT as kr, isItem as kt, compareVersions as l, runGh as ln, markValue as lr, planNodeSession as lt, createWatcher as m, normalizeTitle as mn, settingsOf as mr, checkWorkflowText as mt, resolveBase as n, END_KINDS as nn, newWorkflowText as nr, NOT_STARTED as nt, runningRunIds as o, FEEDBACK_TARGETS as on, renderJsonSchema as or, usd as ot, triggersReport as p, fingerprint as pn, expandEdge as pr, reportSchemaFor as pt, requestStop as q, projectRootOf as qn, loadProject as qt, capsHere as r, endFor as rn, readDocument as rr, limitsNow as rt, runningScript as s, DISCUSSIONS as sn, parseWorkflow as sr, explainWorkflowText as st, createWorkspace as t, END_DETAIL_LIMIT as tn, applyEdits as tr, followEvents as tt, isNewer as u, sendPost as un, EdgeInputSchema as ur, namingNoDriver as ut, workflowTextHash as v, renderTemplate as vn, DriverSchema as vr, MESSAGE_LIMIT as vt, storePath as w, MASK as wn, integrationFor as wr, runTree as wt, activeTree as x, socketPath as xn, timeoutMs as xr, liveStatus as xt, takeWatchLock as y, readCondition as yn, NodeIdSchema as yr, userMessageText as yt, endLinksOf as z, newProjectId as zn, slotWaitLine as zt };
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 };