@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (30) hide show
  1. package/dist/{__build_info__-fy6L1Mrd.mjs → __build_info__-1i1IwLg7.mjs} +2 -2
  2. package/dist/bin.mjs +72 -32
  3. package/dist/{clean-DaHjs__2.mjs → clean-B1zPxq6A.mjs} +1 -1
  4. package/dist/{completion-CSU5vkEt.mjs → completion-BY-frez8.mjs} +5 -5
  5. package/dist/{context-DT8MfEIR.mjs → context-CD5TCD1D.mjs} +3 -3
  6. package/dist/{decide-CGyx9rv7.mjs → decide-CBhYcjwI.mjs} +1 -1
  7. package/dist/{find-D6Gj1sxg.mjs → find-BgZjVKhv.mjs} +1 -1
  8. package/dist/{format-Dnfv8zMT.mjs → format-Creyq4U3.mjs} +2 -2
  9. package/dist/{in-a-run-pLAZeNys.mjs → in-a-run-CsSBQQ2q.mjs} +1 -1
  10. package/dist/{limits-B1-DYc3d.mjs → limits-D_HxH0CK.mjs} +1 -1
  11. package/dist/{list-BrH8KRSJ.mjs → list-CzzpVEpM.mjs} +1 -1
  12. package/dist/{mcp-DE_EmyVm.mjs → mcp-DYmQt372.mjs} +6 -6
  13. package/dist/{output-BLQWD6MJ.mjs → output-CuivUcDa.mjs} +1 -1
  14. package/dist/{pause-CL6N_Xrj.mjs → pause-W6MzQqg8.mjs} +4 -4
  15. package/dist/{relay-0SD0erwf.mjs → relay-CsdoZUXJ.mjs} +2 -2
  16. package/dist/{remove-BeGdqroy.mjs → remove-DLfDjBdF.mjs} +3 -3
  17. package/dist/{send-C8tunWhv.mjs → send-Bw2FI_Sv.mjs} +3 -3
  18. package/dist/{src-Dujmvv4s.mjs → src-BRB5EQyC.mjs} +235 -32
  19. package/dist/{src-DOcK8Jfp.mjs → src-Bc9yaAiZ.mjs} +159 -161
  20. package/dist/{src-CCUKRX51.mjs → src-CrYCkU9J.mjs} +16 -8
  21. package/dist/{src-BbyEoMn4.mjs → src-CsjumY1t.mjs} +7 -7
  22. package/dist/ui/assets/{app-T7wVAhgx.js → app-m9Gfrg_S.js} +1 -1
  23. package/dist/ui/assets/{index-9dFha9Qn.js → index-UEC5ZLYw.js} +2 -2
  24. package/dist/ui/index.html +1 -1
  25. package/dist/{ui-CTmO2Xzu.mjs → ui-wX050aH1.mjs} +4 -4
  26. package/dist/{usage-DYUmP0i4.mjs → usage-C96RDH-G.mjs} +5 -5
  27. package/dist/{watch-CtbgDAcz.mjs → watch-vdRobJil.mjs} +6 -6
  28. package/dist/{workflows-D1_WVukk.mjs → workflows-B28q8zMY.mjs} +3 -3
  29. package/dist/{worktrees-DdMYoNOC.mjs → worktrees-CKDRkl6b.mjs} +5 -5
  30. package/package.json +1 -1
@@ -8,6 +8,65 @@ import { createConnection, createServer } from "node:net";
8
8
  import { createInterface } from "node:readline";
9
9
  import { execFile, execFileSync, spawn } from "node:child_process";
10
10
  import { setImmediate, setTimeout as setTimeout$1 } from "node:timers/promises";
11
+ // ../core/src/graph/diagnostics.ts
12
+ const error = (code, message, path) => ({
13
+ severity: "error",
14
+ code,
15
+ message,
16
+ path
17
+ });
18
+ const warning = (code, message, path) => ({
19
+ severity: "warning",
20
+ code,
21
+ message,
22
+ path
23
+ });
24
+ // ../core/src/experiments.ts
25
+ /** The only list: an entry is added by the change that adds its experiment. */
26
+ const EXPERIMENTS = [];
27
+ const EXPERIMENTAL_ENV = "ORK_EXPERIMENTAL";
28
+ /** The registry's names, for a message. */
29
+ const knownExperiments = (registry = EXPERIMENTS) => registry.map(({ name }) => name).join(", ") || "none";
30
+ const envNames = (env) => [...new Set((env[EXPERIMENTAL_ENV] ?? "").split(",").map((name) => name.trim()).filter((name) => name !== ""))];
31
+ /**
32
+ * What is on, from the project file and the environment together. Any name the
33
+ * registry doesn't have makes it an error instead: a typo must not look like a
34
+ * running experiment.
35
+ */
36
+ const experimentsOn = ({ env = process.env, project, where, registry = EXPERIMENTS } = {}) => {
37
+ const inProject = project?.experimental ?? [];
38
+ const listed = [...[...new Set(inProject)].map((name) => ({
39
+ name,
40
+ source: "project",
41
+ at: where?.(["experimental", inProject.indexOf(name)]) ?? "project.yaml"
42
+ })), ...envNames(env).map((name) => ({
43
+ name,
44
+ source: "environment",
45
+ at: EXPERIMENTAL_ENV
46
+ }))];
47
+ const unknown = listed.filter(({ name }) => !registry.some((entry) => entry.name === name)).map(({ name, source, at }) => ({
48
+ name,
49
+ source,
50
+ where: at,
51
+ message: `${at}${source === "project" ? ":" : ""} names an unknown experiment \`${name}\`; known: ${knownExperiments(registry)}`
52
+ }));
53
+ if (unknown.length > 0) return {
54
+ ok: false,
55
+ unknown
56
+ };
57
+ return {
58
+ ok: true,
59
+ on: [...new Set(listed.map(({ name }) => name))].map((name) => ({
60
+ name,
61
+ sources: listed.filter((l) => l.name === name).map((l) => l.source)
62
+ }))
63
+ };
64
+ };
65
+ /** Unknown names as errors, for the checks every workflow goes through. */
66
+ const experimentDiagnostics = (options) => {
67
+ const state = experimentsOn(options);
68
+ return state.ok ? [] : state.unknown.map(({ message }) => error("experiment.unknown", message, []));
69
+ };
11
70
  // ../integrations/src/integration.ts
12
71
  /** Declares an integration with its option types checked. */
13
72
  const defineIntegration = (integration) => integration;
@@ -400,6 +459,7 @@ const PracticesSchema = z.strictObject({
400
459
  ])]).optional().describe("Smallest complete change. `true` is `full`. Default: `full`.")
401
460
  });
402
461
  const TurnsSchema = z.number().int().positive();
462
+ const McpNamesSchema = z.array(z.string().min(1)).optional().describe("MCP servers from the project's `.mcp.json` this node's sessions load, beside Ork's own. Default: none.");
403
463
  const ToolRulesSchema = z.strictObject({
404
464
  allow: z.array(z.string()).optional(),
405
465
  deny: z.array(z.string()).optional(),
@@ -553,6 +613,7 @@ const claudeFields = {
553
613
  const ClaudeLeaderNodeSchema = z.strictObject({
554
614
  type: z.literal("claude.leader"),
555
615
  ...claudeFields,
616
+ mcp: McpNamesSchema,
556
617
  ...notifyFields,
557
618
  ...stepFields,
558
619
  maxWorkers: z.number().int().nonnegative().optional().describe("Cap on dynamically spawned workers."),
@@ -561,6 +622,7 @@ const ClaudeLeaderNodeSchema = z.strictObject({
561
622
  const ClaudeWorkerNodeSchema = z.strictObject({
562
623
  type: z.literal("claude.worker"),
563
624
  ...claudeFields,
625
+ mcp: McpNamesSchema,
564
626
  ...notifyFields,
565
627
  ...stepFields
566
628
  });
@@ -863,6 +925,7 @@ const DefaultsSchema = z.strictObject({
863
925
  maxTurns: TurnsSchema.optional(),
864
926
  timeout: TimeoutSchema.optional().describe("For tool and Claude nodes that set none; a gate's own timeout is separate."),
865
927
  practices: PracticesSchema.optional(),
928
+ mcp: McpNamesSchema,
866
929
  before: stepFields.before
867
930
  });
868
931
  /** What the placeholders in a git template may name. */
@@ -894,7 +957,8 @@ const ProjectSchema = z.strictObject({
894
957
  defaults: DefaultsSchema.optional().describe("Defaults for every workflow in the project; a workflow's own win."),
895
958
  quotas: QuotasSchema.optional().describe("Most this project may spend on this machine. The user's own caps apply too."),
896
959
  calls: z.strictObject({ accept: z.array(z.string()).describe("The registered projects whose workflows may call this one's.") }).optional().describe("Default: any registered project may call this one's workflows."),
897
- context: z.array(z.string()).optional().describe("Context files every node reads, relative to the project root; they come before the workflow's.")
960
+ context: z.array(z.string()).optional().describe("Context files every node reads, relative to the project root; they come before the workflow's."),
961
+ experimental: z.array(z.string()).optional().describe("Experimental features this project turns on.")
898
962
  });
899
963
  /** `~/.ork/settings.yaml`: the user's own settings, and never a credential. */
900
964
  const UserSettingsSchema = z.strictObject({
@@ -1223,7 +1287,7 @@ const renderJsonSchema = () => {
1223
1287
  * model the serializer follows, so that a document edit puts a new key where
1224
1288
  * the serializer would and a canonical file stays canonical (RFC-008).
1225
1289
  */
1226
- const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1290
+ const isRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1227
1291
  const unwrapped = (schema) => {
1228
1292
  let current = schema;
1229
1293
  while (current instanceof z.ZodOptional || current instanceof z.ZodNullable || current instanceof z.ZodDefault) current = current.unwrap();
@@ -1244,8 +1308,8 @@ const declaresType = (object, type) => {
1244
1308
  /** The object `value` is, among those `schema` allows. */
1245
1309
  const objectFor = (schema, value) => {
1246
1310
  const objects = objectsIn(schema);
1247
- const keys = isRecord$2(value) ? Object.keys(value) : [];
1248
- const type = isRecord$2(value) ? value["type"] : void 0;
1311
+ const keys = isRecord$3(value) ? Object.keys(value) : [];
1312
+ const type = isRecord$3(value) ? value["type"] : void 0;
1249
1313
  return objects.find((object) => declaresType(object, type)) ?? objects.find((object) => keys.every((key) => key in object.shape)) ?? objects[0];
1250
1314
  };
1251
1315
  /** The keys `schema` declares for `value`, in canonical order. */
@@ -1261,14 +1325,14 @@ const childOf = (schema, key, value) => {
1261
1325
  /** `value` with every object's keys in canonical order and no undefined. */
1262
1326
  const inCanonicalOrder = (value, schema) => {
1263
1327
  if (Array.isArray(value)) return value.map((item, index) => inCanonicalOrder(item, childOf(schema, index, value)));
1264
- if (!isRecord$2(value)) return value;
1328
+ if (!isRecord$3(value)) return value;
1265
1329
  const order = keysOf(schema, value);
1266
1330
  return Object.fromEntries([...order.filter((known) => known in value), ...Object.keys(value).filter((other) => !order.includes(other))].filter((key) => value[key] !== void 0).map((key) => [key, inCanonicalOrder(value[key], childOf(schema, key, value))]));
1267
1331
  };
1268
1332
  /** Two plain values alike, whatever the order of their keys. */
1269
1333
  const sameValue$1 = (a, b) => {
1270
1334
  if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => sameValue$1(x, b[i]));
1271
- if (isRecord$2(a) && isRecord$2(b)) {
1335
+ if (isRecord$3(a) && isRecord$3(b)) {
1272
1336
  const left = Object.entries(a);
1273
1337
  return left.length === Object.keys(b).length && left.every(([key, value]) => sameValue$1(value, b[key]));
1274
1338
  }
@@ -1435,7 +1499,7 @@ const entryOf = (doc, raw) => {
1435
1499
  }
1436
1500
  if (!isMap(item)) return void 0;
1437
1501
  const value = item.toJS(doc);
1438
- if (!isRecord$2(value)) return void 0;
1502
+ if (!isRecord$3(value)) return void 0;
1439
1503
  const { from, to, when, maxRounds, ...rest } = value;
1440
1504
  const whenIsText = typeof when === "string";
1441
1505
  const roundsIsNumber = typeof maxRounds === "number";
@@ -1653,7 +1717,7 @@ const withOptions = (doc, item, next) => {
1653
1717
  const custom = Object.keys(next.extra).length > 0;
1654
1718
  if (!isMap(item) || !hasOptions && !custom && !hasComment(item)) return authored(next);
1655
1719
  const value = item.toJS(doc);
1656
- const { when: _when, maxRounds: _rounds, ...rest } = isRecord$2(value) ? value : {};
1720
+ const { when: _when, maxRounds: _rounds, ...rest } = isRecord$3(value) ? value : {};
1657
1721
  return {
1658
1722
  ...rest,
1659
1723
  ...next.when === void 0 ? {} : { when: next.when },
@@ -2394,19 +2458,6 @@ const parseSkillRef = (ref) => {
2394
2458
  message: `\`${ref}\` is neither a built-in (ork:<name>) nor a path to a skill folder (./…); project skills load by themselves`
2395
2459
  };
2396
2460
  };
2397
- // ../core/src/graph/diagnostics.ts
2398
- const error = (code, message, path) => ({
2399
- severity: "error",
2400
- code,
2401
- message,
2402
- path
2403
- });
2404
- const warning = (code, message, path) => ({
2405
- severity: "warning",
2406
- code,
2407
- message,
2408
- path
2409
- });
2410
2461
  // ../core/src/practices/caveman.ts
2411
2462
  const CAVEMAN = `## Practice: caveman (terse voice)
2412
2463
 
@@ -4516,6 +4567,119 @@ const FEEDBACK_SEVERITIES = [
4516
4567
  "medium",
4517
4568
  "high"
4518
4569
  ];
4570
+ // ../core/src/project/mcp-file.ts
4571
+ /**
4572
+ * @file The project's `.mcp.json`, the file Claude Code reads too, so a server
4573
+ * is defined in one place: reading its servers, and the compiler's check of
4574
+ * the servers a Claude node names (`mcp:`, ADR-067). A session's servers are
4575
+ * read with the file's text left as written, so no value leaves the file.
4576
+ */
4577
+ const MCP_FILE = ".mcp.json";
4578
+ /** Ork's own MCP server: a project can't redefine it. */
4579
+ const ORK_MCP_NAME = "ork";
4580
+ const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4581
+ /** The file's `mcpServers`; `{}` when there is no file. */
4582
+ const serversIn = (root) => {
4583
+ const file = path.join(root, MCP_FILE);
4584
+ if (!existsSync(file)) return {
4585
+ ok: true,
4586
+ value: {}
4587
+ };
4588
+ try {
4589
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
4590
+ const servers = isRecord$2(parsed) ? parsed["mcpServers"] : void 0;
4591
+ return {
4592
+ ok: true,
4593
+ value: isRecord$2(servers) ? servers : {}
4594
+ };
4595
+ } catch (error) {
4596
+ const reason = error instanceof SyntaxError ? "it is not valid JSON" : error instanceof Error ? error.message : String(error);
4597
+ return {
4598
+ ok: false,
4599
+ error: `${MCP_FILE} can't be read: ${reason}`
4600
+ };
4601
+ }
4602
+ };
4603
+ /** A server's field that holds something other than text: it would be dropped. */
4604
+ const mcpNotText = (server) => [
4605
+ "args",
4606
+ "env",
4607
+ "headers"
4608
+ ].find((field) => {
4609
+ const value = server[field];
4610
+ return (Array.isArray(value) ? value : isRecord$2(value) ? Object.values(value) : []).some((item) => typeof item !== "string");
4611
+ });
4612
+ /** Why an entry can't be loaded, whatever its name; its values aren't quoted. */
4613
+ const entryProblem = (server) => {
4614
+ if (!isRecord$2(server)) return "it is not an object";
4615
+ if (server["args"] !== void 0 && !Array.isArray(server["args"])) return "its args is not a list";
4616
+ const notRecord = ["env", "headers"].find((field) => server[field] !== void 0 && !isRecord$2(server[field]));
4617
+ if (notRecord !== void 0) return `its ${notRecord} is not an object`;
4618
+ const field = mcpNotText(server);
4619
+ if (field !== void 0) return `its ${field} holds a value that isn't text`;
4620
+ return typeof server["command"] === "string" || typeof server["url"] === "string" ? void 0 : "it has neither a command nor a url";
4621
+ };
4622
+ /** What the compiler checks a workflow's `mcp:` names against. */
4623
+ const mcpFileOptions = (root) => {
4624
+ const read = serversIn(root);
4625
+ if (!read.ok) return {
4626
+ mcpServers: [],
4627
+ mcpFileProblem: read.error
4628
+ };
4629
+ if (!existsSync(path.join(root, ".mcp.json"))) return {
4630
+ mcpServers: [],
4631
+ mcpFileProblem: `there is no ${MCP_FILE}`
4632
+ };
4633
+ return {
4634
+ mcpServers: Object.keys(read.value),
4635
+ mcpMalformed: Object.fromEntries(Object.entries(read.value).flatMap(([name, server]) => {
4636
+ const problem = entryProblem(server);
4637
+ return problem === void 0 ? [] : [[name, problem]];
4638
+ }))
4639
+ };
4640
+ };
4641
+ /** The error for one name a node gave, or nothing when it can load. */
4642
+ const nameProblem = (id, name, options, known) => {
4643
+ if (name === ORK_MCP_NAME) return {
4644
+ code: "mcp.reserved",
4645
+ message: `\`${id}\`: \`${ORK_MCP_NAME}\` is Ork's own server, name another`
4646
+ };
4647
+ if (!known.includes(name)) return {
4648
+ code: "mcp.unknown",
4649
+ message: `\`${id}\`: no MCP server \`${name}\`${options.mcpFileProblem === void 0 ? ` in ${MCP_FILE}, which has ${known.length === 0 ? "none" : known.join(", ")}` : `: ${options.mcpFileProblem}`}`
4650
+ };
4651
+ const problem = Object.hasOwn(options.mcpMalformed ?? {}, name) ? options.mcpMalformed?.[name] : void 0;
4652
+ return problem === void 0 ? void 0 : {
4653
+ code: "mcp.malformed",
4654
+ message: `\`${id}\`: MCP server \`${name}\` in ${MCP_FILE} is malformed: ${problem}`
4655
+ };
4656
+ };
4657
+ /** Every server a Claude node's sessions would load exists and can load. */
4658
+ const checkMcpServers = (workflow, options) => {
4659
+ const known = options.mcpServers;
4660
+ if (known === void 0) return [];
4661
+ return Object.entries(workflow.nodes).flatMap(([id, node]) => {
4662
+ if (node.type !== "claude.leader" && node.type !== "claude.worker") return [];
4663
+ const own = node.mcp !== void 0;
4664
+ return (node.mcp ?? workflow.defaults?.mcp ?? []).flatMap((name, index) => {
4665
+ const problem = nameProblem(id, name, options, known);
4666
+ return problem === void 0 ? [] : [{
4667
+ severity: "error",
4668
+ ...problem,
4669
+ path: own ? [
4670
+ "nodes",
4671
+ id,
4672
+ "mcp",
4673
+ index
4674
+ ] : [
4675
+ "defaults",
4676
+ "mcp",
4677
+ index
4678
+ ]
4679
+ }];
4680
+ });
4681
+ });
4682
+ };
4519
4683
  // ../core/src/project/templates.ts
4520
4684
  /**
4521
4685
  * @file Git name templates (`ork/{run}/{node}`, `{inputs.type}/{inputs.slug}`):
@@ -4561,11 +4725,15 @@ const PROJECT_FILE = ".ork/project.yaml";
4561
4725
  const loadProject = (projectRoot, file = path.join(projectRoot, PROJECT_FILE)) => {
4562
4726
  if (!existsSync(file)) return { diagnostics: [] };
4563
4727
  const parsed = parseProject(readFileSync(file, "utf8"));
4728
+ const label = shownPath(file, projectRoot);
4564
4729
  if (parsed.ok) return {
4565
4730
  project: parsed.project,
4566
- diagnostics: []
4731
+ diagnostics: [],
4732
+ where: (at) => {
4733
+ const position = parsed.locate(at);
4734
+ return position === null ? label : `${label}:${position.line}:${position.column}`;
4735
+ }
4567
4736
  };
4568
- const label = shownPath(file, projectRoot);
4569
4737
  return { diagnostics: parsed.diagnostics.map((d) => {
4570
4738
  const at = d.line === void 0 ? "" : `:${d.line}:${d.column ?? 1}`;
4571
4739
  return error(d.code, `${label}${at}: ${d.message}`, []);
@@ -5891,6 +6059,7 @@ const compileWorkflow = (authored, options = {}) => {
5891
6059
  ...rules.diagnostics,
5892
6060
  ...channels.diagnostics,
5893
6061
  ...checkRules(workflow),
6062
+ ...checkMcpServers(workflow, options),
5894
6063
  ...checkReach(workflow, edges, entries)
5895
6064
  ];
5896
6065
  diagnostics.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
@@ -9022,12 +9191,17 @@ const checkWorkflowText = (text, { place, workflowDir, registered, home }) => {
9022
9191
  catalog,
9023
9192
  workflowDir,
9024
9193
  projectRoot,
9025
- ...loaded.project === void 0 ? {} : { project: loaded.project }
9194
+ ...loaded.project === void 0 ? {} : { project: loaded.project },
9195
+ ...mcpFileOptions(projectRoot)
9026
9196
  });
9027
9197
  const { graph } = compiled;
9028
9198
  const diagnostics = [];
9029
9199
  for (const diagnostic of [
9030
9200
  ...loaded.diagnostics,
9201
+ ...experimentDiagnostics({
9202
+ project: loaded.project,
9203
+ where: loaded.where
9204
+ }),
9031
9205
  ...compiled.diagnostics,
9032
9206
  ...checkCallTargets(parsed.workflow, place, registered),
9033
9207
  ...graph === null ? [] : contextDiagnostics(graph.workflow, {
@@ -9612,10 +9786,11 @@ const roleSection = (workflow, node) => {
9612
9786
  return role === void 0 || role === "" ? [] : [`# Your role: ${node.agent}\n\n${role}`];
9613
9787
  };
9614
9788
  /** The node's own tool rules, the project's and workflow's, and isolation's. */
9615
- const toolRules = (node, readonly, rules) => ({
9789
+ const toolRules = (node, readonly, rules, mcp) => ({
9616
9790
  allow: [.../* @__PURE__ */ new Set([
9617
9791
  ...rules?.permissions?.allow ?? [],
9618
9792
  ...node.tools?.allow ?? [],
9793
+ ...mcp.map((server) => `mcp__${server.replaceAll(/[^\w-]/gu, "_")}`),
9619
9794
  ...readonly ? READ_ONLY_COMMANDS : []
9620
9795
  ])],
9621
9796
  deny: [.../* @__PURE__ */ new Set([
@@ -9645,6 +9820,7 @@ const planNodeSession = (workflow, id, options) => {
9645
9820
  error: `\`${id}\` is a sub-agent; it runs inside \`${node.parent}\`'s session`
9646
9821
  };
9647
9822
  const defaults = workflow.defaults ?? {};
9823
+ const mcp = node.mcp ?? defaults.mcp ?? [];
9648
9824
  const isolation = node.isolation ?? defaults.isolation ?? "worktree";
9649
9825
  const ownWorktree = options.checkout?.own === true;
9650
9826
  if ((isolation === "worktree" && !ownWorktree || isolation === "continue" && options.checkout === void 0) && options.inPlace !== true) return {
@@ -9688,7 +9864,7 @@ const planNodeSession = (workflow, id, options) => {
9688
9864
  ...contextSections(context.files, "node")
9689
9865
  ].join("\n\n"),
9690
9866
  settingSources: ["project", "local"],
9691
- tools: toolRules(node, isolation === "readonly", workflow.rules),
9867
+ tools: toolRules(node, isolation === "readonly", workflow.rules, mcp),
9692
9868
  permissionMode: "default",
9693
9869
  reportSchema: reportSchemaFor(node)
9694
9870
  };
@@ -9714,6 +9890,7 @@ const planNodeSession = (workflow, id, options) => {
9714
9890
  ok: true,
9715
9891
  spec,
9716
9892
  driver: namedDriver(node, workflow),
9893
+ mcp,
9717
9894
  warnings,
9718
9895
  context
9719
9896
  };
@@ -11383,7 +11560,7 @@ const runGit = (cwd, args) => new Promise((resolve) => {
11383
11560
  resolve({
11384
11561
  code: error === null ? 0 : typeof error.code === "number" ? error.code : -1,
11385
11562
  stdout,
11386
- stderr: stderr || (error?.message ?? "")
11563
+ stderr: error !== null && !existsSync(cwd) ? `the folder ${cwd} doesn't exist` : stderr || (error?.message ?? "")
11387
11564
  });
11388
11565
  });
11389
11566
  });
@@ -12560,6 +12737,14 @@ const upstreamPlace = (graph, id, placeOf, root) => {
12560
12737
  return other === void 0 ? only : void 0;
12561
12738
  };
12562
12739
  // ../core/src/worktree/handoff.ts
12740
+ /**
12741
+ * @file A node's worktree under the project's git rules (ADR-032): opening it
12742
+ * (named by the rules, on a branch or detached, taking its owner's work),
12743
+ * bringing it up to its owner's work on a later run, and handing a passing
12744
+ * child's work to its owner: a `--no-ff` merge, or a patch applied
12745
+ * uncommitted. Also what a session is told about its checkout, and the
12746
+ * artifacts carried into a new worktree.
12747
+ */
12563
12748
  /** Where a child's work goes: its owner's branch, or its folder. */
12564
12749
  const nameOf = (worktree) => worktree.branch ?? worktree.path;
12565
12750
  const headTree = async (worktree) => (await gitOk(worktree, ["rev-parse", "HEAD^{tree}"])).trim();
@@ -12611,6 +12796,16 @@ const openWorktree = async (options) => {
12611
12796
  };
12612
12797
  };
12613
12798
  /**
12799
+ * Whether a node's worktree is still on disk. One removed outside Ork (an agent
12800
+ * ran `git worktree remove`) is forgotten by git too, so it can be opened again
12801
+ * from its owner's work rather than failing the node's next run.
12802
+ */
12803
+ const stillThere = async (root, worktree) => {
12804
+ if (existsSync(worktree.path)) return true;
12805
+ await gitOk(root, ["worktree", "prune"]);
12806
+ return false;
12807
+ };
12808
+ /**
12614
12809
  * A later run (a loop, another delegation) starts from the owner's work. With
12615
12810
  * commits, the branch fast-forwards when it has nothing of its own; under
12616
12811
  * `commit: never`, a child whose work reached its owner takes the owner's
@@ -12625,6 +12820,15 @@ const refreshWorktree = async (rules, worktree, host, handedOver) => {
12625
12820
  worktree.snapshot = await syncFrom(worktree.path, host.path);
12626
12821
  return true;
12627
12822
  };
12823
+ /**
12824
+ * A node's worktree when it runs again: `gone` when its folder was removed
12825
+ * outside Ork (it is opened anew), `synced` when it took its owner's work, or
12826
+ * `same`.
12827
+ */
12828
+ const reenter = async (root, rules, worktree, host, handedOver) => {
12829
+ if (!await stillThere(root, worktree)) return "gone";
12830
+ return host !== void 0 && await refreshWorktree(rules, worktree, host, handedOver) ? "synced" : "same";
12831
+ };
12628
12832
  /** Hands a passing child's work to its owner's worktree: merged or patched. */
12629
12833
  const handOver = async (rules, child, owner, message) => {
12630
12834
  if (rules.merge === "patch" || child.branch === void 0) {
@@ -12789,10 +12993,9 @@ const createWorkspace = (options) => {
12789
12993
  const host = hostOf(id);
12790
12994
  const hosted = host === void 0 ? void 0 : worktrees.get(host);
12791
12995
  const existing = worktrees.get(id);
12792
- if (existing !== void 0) {
12793
- if (hosted !== void 0 && await refreshWorktree(rules, existing, hosted, merged.has(id))) event(id, "synced", existing, `from ${nameOf(hosted)}`);
12794
- return existing;
12795
- }
12996
+ const again = existing === void 0 ? "new" : await reenter(root, rules, existing, hosted, merged.has(id));
12997
+ if (again === "synced" && existing !== void 0 && hosted !== void 0) event(id, "synced", existing, `from ${nameOf(hosted)}`);
12998
+ if (existing !== void 0 && again !== "gone") return existing;
12796
12999
  const { worktree, copied } = await openWorktree({
12797
13000
  rules,
12798
13001
  root,
@@ -12937,4 +13140,4 @@ const createWorkspace = (options) => {
12937
13140
  close
12938
13141
  };
12939
13142
  };
12940
- export { text as $, timeoutMs as $n, parseTemplate as $t, gitOk as A, guardedBase as An, runScope as At, linkNodes as B, serializeWorkflow as Bn, FEEDBACK_TARGETS as Bt, workflowTextHash as C, runDirOf as Cn, walkChecks as Ct, inspectProject as D, projectRootOf as Dn, userMessageText as Dt, BRANCH_PREFIX as E, inside as En, MESSAGE_LIMIT as Et, declaredProblem as F, applyEdits as Fn, PROJECT_FILE as Ft, requestContinue as G, expandEdge as Gn, publishPost as Gt, tellNode as H, EdgeInputSchema as Hn, planPost as Ht, createCalls as I, newWorkflowText as In, gitRulesOf as It, tellLiveRun as J, beforeOf as Jn, normalizeTitle as Jt, requestPause as K, settingsOf as Kn, createFeedback as Kt, addressOf as L, readDocument as Ln, loadProject as Lt, clearGitLocalEnv as M, writePlainFile as Mn, envReadsOf as Mt, parseInputPairs as N, skillsIn as Nn, nodeSays as Nt, removeWorktree as O, LESSON_CAP_TOKENS as On, heldTimeout as Ot, resolveInputs as P, WorkflowEditSchema as Pn, declaredOutputOf as Pt, inTransaction as Q, NodeIdSchema as Qn, evaluate as Qt, endLink as R, renderJsonSchema as Rn, FEEDBACK_KINDS as Rt, readProjectTriggers as S, projectsHome as Sn, checkWorkflowText as St, watchingPid as T, workflowDirOf as Tn, startRun as Tt, unlinkNodes as U, WorkflowSchema as Un, runGh as Ut, peerOf as V, markValue as Vn, DISCUSSIONS as Vt, flattenTree as W, CheckReportSchema as Wn, sendPost as Wt, usageQueries as X, isToolNode as Xn, maskReplacer as Xt, backfillUsage as Y, isClaudeNode as Yn, targetOf as Yt, eventOf as Z, programsRunBy as Zn, maskSecrets as Zt, lateTriggers as _, hasInRepoFiles as _n, runStatus as _t, limitsNow as a, wakeRun as an, CLAUDE_DRIVERS as ar, DETACHED_ENV as at, triggerStanding as b, placeFiles as bn, processAlive as bt, usd as c, planMoveInto as cn, explainWorkflowText as ct, runningRunIds as d, toolsInUse as dn, namingNoDriver as dt, renderTemplate as en, INTEGRATIONS as er, adoptedCalls as et, runningScript as f, createAgent as fn, sessionDriver as ft, parseVersion as g, PROJECT_MODES as gn, pausedOf as gt, isNewer as h, loadAgents as hn, liveStatus as ht, NOT_STARTED as i, socketRoom as in, ORK_LAYOUT as ir, replayLoopAnswers as it, lines as j, plainPathInside as jn, calledWorkflows as jt, git as k, estimateTokens as kn, scriptContactsOf as kt, planUpgrade as l, linkTools as ln, explainSession as lt, compareVersions as m, catalogAgent as mn, reportSchemaFor as mt, resolveBase as n, serveRun as nn, integrationFor as nr, resumable as nt, limitsReport as o, planAgentMigration as on, firstLine as or, detachRun as ot, detectInstaller as p, deleteAgent as pn, parseReport as pt, requestStop as q, NodeSchema as qn, fingerprint as qt, capsHere as r, socketPath as rn, AGENT_TOOL_LAYOUTS as rr, createReplay as rt, planReason as s, applySteps as sn, followEvents as st, createWorkspace as t, readCondition as tn, TOOL_PREFIX as tr, detachResume as tt, releaseNotes as u, toolLinks as un, planNodeSession as ut, triggersReport as v, inRepoPlace as vn, runTree as vt, takeWatchLock as w, shownPath as wn, walkSummary as wt, declaredTriggers as x, placeFor as xn, stillTheSameProcess as xt, createWatcher as y, newProjectId as yn, ownProcessStart as yt, endLinksOf as z, parseWorkflow as zn, FEEDBACK_SEVERITIES as zt };
13143
+ export { text as $, isToolNode as $n, maskReplacer as $t, gitOk as A, projectRootOf as An, runScope as At, linkNodes as B, readDocument as Bn, serversIn as Bt, workflowTextHash as C, placeFiles as Cn, walkChecks as Ct, inspectProject as D, shownPath as Dn, userMessageText as Dt, BRANCH_PREFIX as E, runDirOf as En, MESSAGE_LIMIT as Et, declaredProblem as F, writePlainFile as Fn, PROJECT_FILE as Ft, requestContinue as G, EdgeInputSchema as Gn, planPost as Gt, tellNode as H, parseWorkflow as Hn, FEEDBACK_SEVERITIES as Ht, createCalls as I, skillsIn as In, gitRulesOf as It, tellLiveRun as J, expandEdge as Jn, publishPost as Jt, requestPause as K, WorkflowSchema as Kn, runGh as Kt, addressOf as L, WorkflowEditSchema as Ln, loadProject as Lt, clearGitLocalEnv as M, estimateTokens as Mn, envReadsOf as Mt, parseInputPairs as N, guardedBase as Nn, nodeSays as Nt, removeWorktree as O, workflowDirOf as On, heldTimeout as Ot, resolveInputs as P, plainPathInside as Pn, declaredOutputOf as Pt, inTransaction as Q, isClaudeNode as Qn, targetOf as Qt, endLink as R, applyEdits as Rn, MCP_FILE as Rt, readProjectTriggers as S, newProjectId as Sn, checkWorkflowText as St, watchingPid as T, projectsHome as Tn, startRun as Tt, unlinkNodes as U, serializeWorkflow as Un, FEEDBACK_TARGETS as Ut, peerOf as V, renderJsonSchema as Vn, FEEDBACK_KINDS as Vt, flattenTree as W, markValue as Wn, DISCUSSIONS as Wt, usageQueries as X, NodeSchema as Xn, fingerprint as Xt, backfillUsage as Y, settingsOf as Yn, createFeedback as Yt, eventOf as Z, beforeOf as Zn, normalizeTitle as Zt, lateTriggers as _, catalogAgent as _n, runStatus as _t, limitsNow as a, serveRun as an, integrationFor as ar, DETACHED_ENV as at, triggerStanding as b, hasInRepoFiles as bn, processAlive as bt, usd as c, wakeRun as cn, CLAUDE_DRIVERS as cr, explainWorkflowText as ct, runningRunIds as d, planMoveInto as dn, experimentsOn as dr, namingNoDriver as dt, maskSecrets as en, programsRunBy as er, adoptedCalls as et, runningScript as f, linkTools as fn, knownExperiments as fr, sessionDriver as ft, parseVersion as g, deleteAgent as gn, pausedOf as gt, isNewer as h, createAgent as hn, liveStatus as ht, NOT_STARTED as i, readCondition as in, TOOL_PREFIX as ir, replayLoopAnswers as it, lines as j, LESSON_CAP_TOKENS as jn, calledWorkflows as jt, git as k, inside as kn, scriptContactsOf as kt, planUpgrade as l, planAgentMigration as ln, firstLine as lr, explainSession as lt, compareVersions as m, toolsInUse as mn, reportSchemaFor as mt, resolveBase as n, parseTemplate as nn, timeoutMs as nr, resumable as nt, limitsReport as o, socketPath as on, AGENT_TOOL_LAYOUTS as or, detachRun as ot, detectInstaller as p, toolLinks as pn, parseReport as pt, requestStop as q, CheckReportSchema as qn, sendPost as qt, capsHere as r, renderTemplate as rn, INTEGRATIONS as rr, createReplay as rt, planReason as s, socketRoom as sn, ORK_LAYOUT as sr, followEvents as st, createWorkspace as t, evaluate as tn, NodeIdSchema as tr, detachResume as tt, releaseNotes as u, applySteps as un, EXPERIMENTS as ur, planNodeSession as ut, triggersReport as v, loadAgents as vn, runTree as vt, takeWatchLock as w, placeFor as wn, walkSummary as wt, declaredTriggers as x, inRepoPlace as xn, stillTheSameProcess as xt, createWatcher as y, PROJECT_MODES as yn, ownProcessStart as yt, endLinksOf as z, newWorkflowText as zn, mcpNotText as zt };