@krapht/ork 1.0.0-alpha.5 → 1.0.0-alpha.7

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 (32) hide show
  1. package/dist/{__build_info__-Dl8yTEu-.mjs → __build_info__-BGoaUwLd.mjs} +72 -3
  2. package/dist/bin.mjs +149 -44
  3. package/dist/{clean-DYVmLkd4.mjs → clean-BPITM-6p.mjs} +1 -1
  4. package/dist/{completion-BVx917Yr.mjs → completion-CCn8xWmO.mjs} +5 -5
  5. package/dist/{context-CepHRoj9.mjs → context-CZNM454F.mjs} +3 -3
  6. package/dist/{decide-CK_-lI5V.mjs → decide-DsA6kOT7.mjs} +1 -1
  7. package/dist/{find-DWiDJ5Lc.mjs → find-ntyniv2E.mjs} +1 -1
  8. package/dist/{format-yBEAXZhk.mjs → format-DaROJUgG.mjs} +34 -5
  9. package/dist/{in-a-run-CKFIeMpU.mjs → in-a-run-D_fnj1Mw.mjs} +1 -1
  10. package/dist/{limits-CwzPe9wZ.mjs → limits-BKzVMwkt.mjs} +1 -1
  11. package/dist/{list-BE02pCZ9.mjs → list-D9HPWLPH.mjs} +1 -1
  12. package/dist/{mcp-DMuBbUHU.mjs → mcp-BFl3cIDp.mjs} +6 -6
  13. package/dist/{output-BhYkshYq.mjs → output-CwqlG1Le.mjs} +11 -2
  14. package/dist/{pause-C_Q0W6iX.mjs → pause-DhgKQORW.mjs} +4 -4
  15. package/dist/{relay-COCvG-0I.mjs → relay-Cp8JGrdv.mjs} +2 -2
  16. package/dist/{remove-RVcX1KqP.mjs → remove-CSpT_nJH.mjs} +3 -3
  17. package/dist/{send-OVpRei_5.mjs → send-CHiYkM6X.mjs} +3 -3
  18. package/dist/{src-Do_MhwiV.mjs → src-BsLHKQRc.mjs} +3903 -3204
  19. package/dist/{src-QEycKJWk.mjs → src-BwnitlJh.mjs} +8 -77
  20. package/dist/{src-HxtJOMRr.mjs → src-CUeMUVwW.mjs} +40 -23
  21. package/dist/{src-CxZr4Bp2.mjs → src-DhGLtYR-.mjs} +54 -21
  22. package/dist/ui/assets/{app-DzI77WV9.js → app-D4CoH-lh.js} +8 -8
  23. package/dist/ui/assets/app-Dt_tjFj6.css +1 -0
  24. package/dist/ui/assets/{index-2G_6XIIv.js → index-DxDGdEwz.js} +2 -2
  25. package/dist/ui/index.html +1 -1
  26. package/dist/{ui-Dz_qfUOz.mjs → ui-BxE0L7V0.mjs} +4 -4
  27. package/dist/{usage-BcLUZ1xw.mjs → usage-BuzDsEhq.mjs} +5 -5
  28. package/dist/{watch-Buyn0Mhz.mjs → watch-CCj0vU3B.mjs} +6 -6
  29. package/dist/{workflows-1sntXqSd.mjs → workflows-BNCDD6J-.mjs} +3 -3
  30. package/dist/{worktrees-B_Go_A9w.mjs → worktrees-DBBcaimX.mjs} +10 -4
  31. package/package.json +1 -1
  32. package/dist/ui/assets/app-pTYGrAbI.css +0 -1
@@ -1,86 +1,17 @@
1
- import { B as linkNodes, Bn as readDocument, Cn as placeFiles, Ct as walkChecks, En as runDirOf, G as requestContinue, Gn as EdgeInputSchema, Gt as planPost, Hn as parseWorkflow, In as skillsIn, J as tellLiveRun, Jn as expandEdge, K as requestPause, Kt as runGh, Ln as WorkflowEditSchema, Nt as nodeSays, On as workflowDirOf, Rn as applyEdits, St as checkWorkflowText, U as unlinkNodes, Vn as renderJsonSchema, Wt as DISCUSSIONS, Xn as NodeSchema, _t as runStatus, bt as processAlive, ct as explainWorkflowText, d as runningRunIds, f as runningScript, gt as pausedOf, ht as liveStatus, jn as LESSON_CAP_TOKENS, kn as inside, l as planUpgrade, nn as readCondition, nt as resumable, o as limitsReport, ot as detachRun, p as detectInstaller, q as requestStop, qt as sendPost, r as capsHere, st as followEvents, tt as detachResume, u as releaseNotes, v as triggersReport, vn as loadAgents, vt as runTree, wn as placeFor, wt as walkSummary, zn as newWorkflowText } from "./src-Do_MhwiV.mjs";
2
- import { C as feedbackActions, S as placeOf$1, _ as previewPost, a as registryShown, b as acceptItem, c as checkDue, f as readUpdateCache, g as nodeSession, h as writeUpdateCache, i as registryOf, l as fetchLatest, m as updateCachePath, o as updateSwitchOff, p as refreshUpdateCache, s as runInstall, u as newerVersion, w as openFixRun, x as lessonPlaceOf, y as recordNote } from "./__build_info__-Dl8yTEu-.mjs";
3
- import { n as pendingDecisions, r as builtinProblem, t as answerDecision } from "./decide-CK_-lI5V.mjs";
4
- import { n as newRunId, t as findRun } from "./find-DWiDJ5Lc.mjs";
5
- import { c as openStore, l as storePath, s as nodeOf } from "./output-BhYkshYq.mjs";
1
+ import { Bn as readDocument, Cn as placeFiles, En as runDirOf, Et as processAlive, G as requestPause, Gt as planPost, H as unlinkNodes, In as skillsIn, K as requestStop, Kt as runGh, Ln as WorkflowEditSchema, Nt as nodeSays, On as workflowDirOf, Qn as NodeSchema, Rn as applyEdits, St as runStatus, Un as renderJsonSchema, W as requestContinue, Wn as parseWorkflow, Wt as DISCUSSIONS, Xn as expandEdge, at as followEvents, bt as liveStatus, d as runningRunIds, et as detachResume, f as runningScript, ht as walkSummary, it as detachRun, jn as LESSON_CAP_TOKENS, kn as inside, l as planUpgrade, mt as walkChecks, nn as readCondition, o as limitsReport, ot as explainWorkflowText, p as detectInstaller, pt as checkWorkflowText, q as tellLiveRun, qn as EdgeInputSchema, qt as sendPost, r as capsHere, tt as resumable, u as releaseNotes, v as triggersReport, vn as loadAgents, wn as placeFor, xt as pausedOf, z as linkNodes, zn as newWorkflowText } from "./src-BsLHKQRc.mjs";
2
+ import { C as placeOf$1, S as lessonPlaceOf, T as openFixRun, _ as nodeSession, a as registryShown, b as recordNote, c as checkDue, f as readUpdateCache, g as activeTree, h as writeUpdateCache, i as registryOf, l as fetchLatest, m as updateCachePath, o as updateSwitchOff, p as refreshUpdateCache, s as runInstall, u as newerVersion, v as previewPost, w as feedbackActions, x as acceptItem } from "./__build_info__-BGoaUwLd.mjs";
3
+ import { n as pendingDecisions, r as builtinProblem, t as answerDecision } from "./decide-DsA6kOT7.mjs";
4
+ import { n as newRunId, t as findRun } from "./find-ntyniv2E.mjs";
5
+ import { c as openStore, l as storePath, s as isNodeEvent } from "./output-CwqlG1Le.mjs";
6
6
  import { n as usageReport } from "./report-DK4LGqwF.mjs";
7
- import { a as planClean, i as keyOf, o as stepActs, r as cleanStep, s as stepPrunes, t as applyClean } from "./clean-DYVmLkd4.mjs";
8
- import { a as readWorktrees, c as planLine, i as projectHead, l as removeRefusal, n as lostWork, o as labelOf, r as planRemove, s as mergeStateOf, t as applyRemove, u as stateParts } from "./remove-RVcX1KqP.mjs";
7
+ import { a as planClean, i as keyOf, o as stepActs, r as cleanStep, s as stepPrunes, t as applyClean } from "./clean-BPITM-6p.mjs";
8
+ import { a as readWorktrees, c as planLine, i as projectHead, l as removeRefusal, n as lostWork, o as labelOf, r as planRemove, s as mergeStateOf, t as applyRemove, u as stateParts } from "./remove-CSpT_nJH.mjs";
9
9
  import path from "node:path";
10
10
  import { z } from "zod";
11
11
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
12
12
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
13
13
  import { homedir } from "node:os";
14
14
  import { createServer } from "node:http";
15
- // ../core/src/store/activity.ts
16
- const LONGEST_LINE = 120;
17
- /**
18
- * The fields of a tool's input that say what it's working on, best first. Only
19
- * these are shown, so what else a tool was given stays in the logs.
20
- */
21
- const TARGETS = [
22
- "file_path",
23
- "notebook_path",
24
- "path",
25
- "url",
26
- "pattern",
27
- "query",
28
- "command"
29
- ];
30
- const oneLine = (text) => {
31
- const line = text.replaceAll(/\s+/gu, " ").trim();
32
- return line.length > LONGEST_LINE ? `${line.slice(0, 119)}…` : line;
33
- };
34
- const toolLine = ({ name, input }) => {
35
- const target = TARGETS.map((field) => input[field]).find((value) => typeof value === "string" && value.trim() !== "");
36
- if (typeof target !== "string") return name;
37
- return oneLine(target.startsWith(`${name} `) ? target : `${name} ${target}`);
38
- };
39
- /** What a finished node said, or why it failed. */
40
- const finishedNow = (ok, error, summary) => {
41
- if (!ok) return error === void 0 ? void 0 : oneLine(error);
42
- return typeof summary === "string" && summary.trim() !== "" ? oneLine(summary) : void 0;
43
- };
44
- /** Each node's activity, by id; a node that never started has none. */
45
- const runActivity = (stored) => {
46
- const nodes = /* @__PURE__ */ new Map();
47
- const of = (id) => {
48
- const found = nodes.get(id);
49
- if (found !== void 0) return found;
50
- const fresh = {};
51
- nodes.set(id, fresh);
52
- return fresh;
53
- };
54
- for (const { at, event } of stored) if (event.type === "node.started") {
55
- const node = of(event.id);
56
- node.startedAt ??= at;
57
- delete node.finishedAt;
58
- delete node.now;
59
- } else if (event.type === "node.event") {
60
- if (event.event.type === "tool.called") of(event.id).now = toolLine(event.event);
61
- } else if (event.type === "decision.opened") of(event.node).now = oneLine(`Asked: ${event.question}`);
62
- else if (event.type === "decision.answered") of(event.node).now = oneLine(`Answered: ${event.answer}`);
63
- else if (event.type === "decision.withdrawn") delete of(event.node).now;
64
- else if (event.type === "node.finished") {
65
- const node = of(event.id);
66
- node.finishedAt = at;
67
- const now = finishedNow(event.ok, event.error, event.output["summary"]);
68
- if (now === void 0) delete node.now;
69
- else node.now = now;
70
- }
71
- return nodes;
72
- };
73
- /** The ownership tree, with what each node is doing and when it ran. */
74
- const activeTree = (stored) => {
75
- const activity = runActivity(stored);
76
- const dress = (node) => {
77
- Object.assign(node, activity.get(node.id));
78
- for (const child of node.children) dress(child);
79
- };
80
- const roots = runTree(stored.map(({ event }) => event));
81
- for (const root of roots) dress(root);
82
- return roots;
83
- };
84
15
  // ../server/src/contract-builder.ts
85
16
  /** A new workflow's name, as its file and its `name:` take it. */
86
17
  const NewWorkflowSchema = z.strictObject({ name: z.string().min(1) });
@@ -1623,7 +1554,7 @@ const readRoutes = (context) => ({
1623
1554
  events: ({ params, query }) => {
1624
1555
  const found = runOf(context, params.id);
1625
1556
  if (!found.ok) return found.refusal;
1626
- const events = context.store.events(found.run.id, query.after ?? 0).filter((s) => query.node === void 0 || nodeOf(s.event) === query.node);
1557
+ const events = context.store.events(found.run.id, query.after ?? 0).filter((s) => query.node === void 0 || isNodeEvent(s.event, query.node));
1627
1558
  const newest = query.last === void 0 ? events : events.slice(-query.last);
1628
1559
  return ok(query.limit === void 0 ? newest : newest.slice(0, query.limit));
1629
1560
  },
@@ -1,4 +1,4 @@
1
- import { $n as isToolNode, $t as evaluate, At as runScope, Bt as serversIn, F as declaredProblem, Mt as envReadsOf, Ot as heldTimeout, Pt as declaredOutputOf, Rt as MCP_FILE, Wn as markValue, Zn as beforeOf, ar as integrationFor, en as parseTemplate, er as programsRunBy, ft as sessionDriver, ir as TOOL_PREFIX, jt as calledWorkflows, mt as reportSchemaFor, nr as timeoutMs, rr as INTEGRATIONS, s as planReason, tn as renderTemplate, ut as planNodeSession, zt as mcpNotText } from "./src-Do_MhwiV.mjs";
1
+ import { $t as evaluate, At as declaredProblem, Bt as serversIn, Hn as programsRunBy, Kn as markValue, Mt as envReadsOf, Ot as runScope, Pt as declaredOutputOf, Rt as MCP_FILE, Vn as beforeOf, ar as integrationFor, ct as planNodeSession, en as parseTemplate, er as isToolNode, ft as reportSchemaFor, ir as TOOL_PREFIX, jt as calledWorkflows, nr as timeoutMs, rr as INTEGRATIONS, s as planReason, tn as renderTemplate, ut as sessionDriver, yt as heldTimeout, zt as mcpNotText } from "./src-BsLHKQRc.mjs";
2
2
  import path from "node:path";
3
3
  import { accessSync, constants, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { spawn } from "node:child_process";
@@ -35,7 +35,7 @@ const callExecutor = (context) => {
35
35
  output: { status: "fail" },
36
36
  error: "This run cannot call workflows."
37
37
  });
38
- const scope = runScope(context.inputs, context.outputs, context.runId);
38
+ const scope = runScope(context.inputs, context.outputs, context.runId, context.item);
39
39
  const render = (text) => {
40
40
  const template = parseTemplate(text);
41
41
  return template.ok ? renderTemplate(template.parts, scope) : text;
@@ -61,6 +61,32 @@ const callExecutor = (context) => {
61
61
  }
62
62
  }, context.signal).then(limitedCall);
63
63
  };
64
+ // ../nodes/src/claude-plan.ts
65
+ /**
66
+ * @file What a Claude node's session is planned with, read from the node's
67
+ * context.
68
+ */
69
+ /** The plan options every session of a node starts from. */
70
+ const planOptionsOf = (context, inPlace) => {
71
+ const planOptions = {
72
+ cwd: context.cwd,
73
+ inputs: context.inputs,
74
+ outputs: context.outputs,
75
+ inPlace,
76
+ workflowDir: context.workflowDir,
77
+ projectRoot: context.projectRoot,
78
+ ...context.orkDir === void 0 ? {} : { orkDir: context.orkDir },
79
+ pluginDir: path.join(context.runDir, "plugins", context.id)
80
+ };
81
+ if (context.checkout !== void 0) planOptions.checkout = context.checkout;
82
+ if (context.brief !== void 0) planOptions.brief = context.brief;
83
+ if (context.template !== void 0) planOptions.template = context.template;
84
+ if (context.item !== void 0) planOptions.item = context.item;
85
+ if (context.ownership !== void 0) planOptions.ownership = context.ownership;
86
+ if (context.mailbox !== void 0) planOptions.mailbox = context.mailbox;
87
+ if (context.claims !== void 0) planOptions.claims = context.claims;
88
+ return planOptions;
89
+ };
64
90
  // ../nodes/src/claude-result.ts
65
91
  /**
66
92
  * @file A finished session as a node's result: its report is the output, held
@@ -991,7 +1017,9 @@ const IDENTITY_KEYS = /* @__PURE__ */ new Set([
991
1017
  "ORK_WORKFLOW",
992
1018
  "ORK_BIN",
993
1019
  OUTPUT_ENV,
994
- STATE_ENV
1020
+ STATE_ENV,
1021
+ "ORK_ITEM",
1022
+ "ORK_INDEX"
995
1023
  ]);
996
1024
  /**
997
1025
  * `env` without Ork's identity variables. A run started by a program in another
@@ -1019,7 +1047,11 @@ const identityOf = (context, orkBin) => ({
1019
1047
  ORK_RUN_DIR: context.runDir,
1020
1048
  ORK_PROJECT: context.projectRoot,
1021
1049
  ORK_WORKFLOW: context.workflow.name,
1022
- ...orkBin === void 0 ? {} : { ORK_BIN: orkBin }
1050
+ ...orkBin === void 0 ? {} : { ORK_BIN: orkBin },
1051
+ ...context.item === void 0 ? {} : {
1052
+ ORK_ITEM: typeof context.item.value === "string" ? context.item.value : JSON.stringify(context.item.value),
1053
+ ORK_INDEX: String(context.item.index)
1054
+ }
1023
1055
  });
1024
1056
  /** The variables a workflow's nodes read that no `plainEnv` excuses. */
1025
1057
  const secretNamesOf = (workflow) => Object.values(workflow.nodes).flatMap((node) => programsRunBy(node, workflow.defaults)).flatMap(({ program }) => {
@@ -1061,7 +1093,7 @@ const secretsOf = (workflow, environ, also = []) => valuesOf(namesToMask(workflo
1061
1093
  const renderEnv = (node, context, environ) => {
1062
1094
  const values = {};
1063
1095
  const scope = {
1064
- ...runScope(context.inputs, context.outputs, context.runId),
1096
+ ...runScope(context.inputs, context.outputs, context.runId, context.item),
1065
1097
  output: { env: environ }
1066
1098
  };
1067
1099
  for (const [key, text] of Object.entries(node.env ?? {})) {
@@ -1151,7 +1183,7 @@ const render = (text, context) => {
1151
1183
  };
1152
1184
  return {
1153
1185
  ok: true,
1154
- value: renderTemplate(template.parts, runScope(context.inputs, context.outputs, context.runId))
1186
+ value: renderTemplate(template.parts, runScope(context.inputs, context.outputs, context.runId, context.item))
1155
1187
  };
1156
1188
  };
1157
1189
  /** The program and arguments a tool node runs, before the allow check. */
@@ -1538,22 +1570,7 @@ const specFor = (spec, context) => {
1538
1570
  };
1539
1571
  /** Runs a `claude.leader` or `claude.worker` as one session. */
1540
1572
  const claudeExecutor = (options) => async (context) => {
1541
- const planOptions = {
1542
- cwd: context.cwd,
1543
- inputs: context.inputs,
1544
- outputs: context.outputs,
1545
- inPlace: options.inPlace,
1546
- workflowDir: context.workflowDir,
1547
- projectRoot: context.projectRoot,
1548
- ...context.orkDir === void 0 ? {} : { orkDir: context.orkDir },
1549
- pluginDir: path.join(context.runDir, "plugins", context.id)
1550
- };
1551
- if (context.checkout !== void 0) planOptions.checkout = context.checkout;
1552
- if (context.brief !== void 0) planOptions.brief = context.brief;
1553
- if (context.template !== void 0) planOptions.template = context.template;
1554
- if (context.ownership !== void 0) planOptions.ownership = context.ownership;
1555
- if (context.mailbox !== void 0) planOptions.mailbox = context.mailbox;
1556
- if (context.claims !== void 0) planOptions.claims = context.claims;
1573
+ const planOptions = planOptionsOf(context, options.inPlace);
1557
1574
  const before = beforeOf(context.node, context.workflow.defaults);
1558
1575
  if (before !== void 0) {
1559
1576
  const ran = await runStep(options.steps ?? noSteps, context, before, {
@@ -1853,7 +1870,7 @@ const mcpCallExecutor = (options = {}) => async (context) => {
1853
1870
  }, node.plainEnv);
1854
1871
  if (!expanded.ok) return failed(expanded.error);
1855
1872
  const scrub = scrubberOf([...secretsOf(context.workflow, environ, options.maskedNames), ...expanded.value.values]);
1856
- const scope = runScope(context.inputs, context.outputs, context.runId);
1873
+ const scope = runScope(context.inputs, context.outputs, context.runId, context.item);
1857
1874
  const args = Object.fromEntries(Object.entries(node.arguments ?? {}).map(([key, value]) => [key, renderValue(value, scope)]));
1858
1875
  const name = `${node.server}.${node.tool}`;
1859
1876
  context.emit({
@@ -1,4 +1,4 @@
1
- import { Ht as FEEDBACK_SEVERITIES, Ut as FEEDBACK_TARGETS, Vt as FEEDBACK_KINDS, pt as parseReport } from "./src-Do_MhwiV.mjs";
1
+ import { Ht as FEEDBACK_SEVERITIES, Ut as FEEDBACK_TARGETS, Vt as FEEDBACK_KINDS, dt as parseReport } from "./src-BsLHKQRc.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createInterface } from "node:readline";
@@ -336,20 +336,45 @@ const text = (value) => ({ content: [{
336
336
  type: "text",
337
337
  text: value
338
338
  }] });
339
- const defineTool = (definition) => {
340
- const schema = z.object(definition.shape);
341
- return {
342
- name: definition.name,
343
- bind: (context) => definition.available?.(context) ?? true ? {
339
+ const issueAt = (issue) => {
340
+ const where = z.core.toDotPath(issue.path);
341
+ return where === "" ? "" : `${where}: `;
342
+ };
343
+ /** Whether `issue` is about a field of the object `parent` is about. */
344
+ const isFieldOf = (issue, parent) => issue.path.length === parent.path.length + 1 && parent.path.every((key, index) => key === issue.path[index]);
345
+ const expectedField = (issue) => `; the field is \`${String(issue.path.at(-1))}\`${issue.code === "invalid_value" ? `, one of: ${issue.values.join(", ")}` : ""}`;
346
+ /**
347
+ * A failed parse as text a model can act on. A stray key is reported with the
348
+ * fields its object lacks, read from the schema's own issues: a wrong name is
349
+ * otherwise only a "missing" error that hides what was sent.
350
+ */
351
+ const readableIssues = (issues) => {
352
+ const strays = issues.filter((issue) => issue.code === "unrecognized_keys");
353
+ const lacking = issues.filter((issue) => issue.input === void 0 && strays.some((stray) => isFieldOf(issue, stray)));
354
+ return [...strays.map((stray) => `${issueAt(stray)}unknown field ${stray.keys.map((key) => `\`${key}\``).join(", ")}${lacking.filter((issue) => isFieldOf(issue, stray)).map((issue) => expectedField(issue)).join("")}`), ...issues.filter((issue) => issue.code !== "unrecognized_keys" && !lacking.includes(issue)).map((issue) => `${issueAt(issue)}${issue.message}`)].join("\n");
355
+ };
356
+ const defineTool = (definition) => ({
357
+ name: definition.name,
358
+ bind: (context) => {
359
+ if (!(definition.available?.(context) ?? true)) return null;
360
+ const shape = typeof definition.shape === "function" ? definition.shape(context) : definition.shape;
361
+ const schema = z.object(shape);
362
+ return {
344
363
  name: definition.name,
345
- description: definition.description,
346
- shape: definition.shape,
364
+ description: typeof definition.description === "function" ? definition.description(context) : definition.description,
365
+ shape,
347
366
  schema,
348
367
  preapproved: definition.preapproved ?? true,
349
- call: (args) => definition.run(context, schema.parse(args))
350
- } : null
351
- };
352
- };
368
+ call: (args) => {
369
+ const parsed = schema.safeParse(args, { reportInput: true });
370
+ return parsed.success ? definition.run(context, parsed.data) : Promise.resolve({
371
+ ...text(readableIssues(parsed.error.issues)),
372
+ isError: true
373
+ });
374
+ }
375
+ };
376
+ }
377
+ });
353
378
  // ../runtime-claude/src/mcp-http.ts
354
379
  /**
355
380
  * @file A minimal MCP server over the streamable HTTP transport, for the
@@ -618,13 +643,18 @@ const delegationText = (delegation) => text(JSON.stringify({
618
643
  reports: delegation.reports.map(reportLine),
619
644
  ...delegation.notes.length > 0 ? { notes: delegation.notes } : {}
620
645
  }));
646
+ /** Why a child the leader owns isn't offered, so it doesn't go looking. */
647
+ const fannedNote = (fanned) => fanned.length === 0 ? "" : ` Not listed: ${fanned.join(", ")}; such a node runs once per item of a list, from its edges, so it can't be delegated to.`;
621
648
  const delegateTool = defineTool({
622
649
  name: "delegate",
623
- description: "Hand tasks to nodes you own. Tasks in one call run in parallel; the call returns every child's report when all are done. Ork merges a passing child's branch into yours.",
624
- shape: { tasks: z.array(z.object({
625
- child: z.string().describe("The id of a node you own."),
626
- brief: BRIEF
627
- })).min(1) },
650
+ description: ({ ports }) => `Hand tasks to nodes you own. Tasks in one call run in parallel; the call returns every child's report when all are done. Ork merges a passing child's branch into yours.${fannedNote(ports.ownership?.fanned ?? [])}`,
651
+ shape: ({ ports }) => {
652
+ const ids = ports.ownership?.children.map((child) => child.id) ?? [];
653
+ return { tasks: z.array(z.strictObject({
654
+ child: z.enum(ids).describe(`The id of a node you own: ${ids.join(", ")}.`),
655
+ brief: BRIEF
656
+ })).min(1) };
657
+ },
628
658
  available: ({ ports }) => (ports.ownership?.children.length ?? 0) > 0,
629
659
  run: async ({ ports }, args) => {
630
660
  if (ports.ownership === void 0) return text("You own no nodes.");
@@ -634,10 +664,13 @@ const delegateTool = defineTool({
634
664
  const spawnWorkerTool = defineTool({
635
665
  name: "spawn_worker",
636
666
  description: "Start new instances of a child worker for work that splits into independent parts. Each runs in its own worktree; the call returns their reports when all are done.",
637
- shape: { tasks: z.array(z.object({
638
- template: z.string().describe("The id of a child worker you own."),
639
- brief: BRIEF
640
- })).min(1) },
667
+ shape: ({ ports }) => {
668
+ const ids = ports.ownership?.templates ?? [];
669
+ return { tasks: z.array(z.strictObject({
670
+ template: z.enum(ids).describe(`The id of a child worker you own: ${ids.join(", ")}.`),
671
+ brief: BRIEF
672
+ })).min(1) };
673
+ },
641
674
  available: ({ ports }) => ports.ownership !== void 0 && ports.ownership.templates.length > 0 && ports.ownership.workersLeft() > 0,
642
675
  run: async ({ ports }, args) => {
643
676
  if (ports.ownership === void 0) return text("You own no workers.");