@krapht/ork 1.0.0-alpha.14 → 1.0.0-alpha.16

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 (48) hide show
  1. package/README.md +10 -4
  2. package/dist/__build_info__-DcRdK0Du.mjs +359 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
  5. package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
  6. package/dist/bin.mjs +328 -1734
  7. package/dist/brief-BvMtIFio.mjs +58 -0
  8. package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
  11. package/dist/context-BN9A2Nnu.mjs +106 -0
  12. package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
  13. package/dist/flags-CdgIH1L6.mjs +230 -0
  14. package/dist/follow-ups-DxCsOby1.mjs +496 -0
  15. package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
  16. package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
  17. package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
  18. package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
  19. package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
  20. package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
  21. package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
  22. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  23. package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
  24. package/dist/post-C9soCx7i.mjs +46 -0
  25. package/dist/print-BaWiWf4v.mjs +19 -0
  26. package/dist/project-records-BRiyQzUo.mjs +230 -0
  27. package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
  28. package/dist/run-B-j3Qxqc.mjs +1464 -0
  29. package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
  30. package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
  31. package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
  32. package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
  33. package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
  34. package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
  35. package/dist/store-Dyr1mP8K.mjs +16 -0
  36. package/dist/ui/assets/app-BPSCxb_l.css +1 -0
  37. package/dist/ui/assets/app-CnNRowFn.js +111 -0
  38. package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
  39. package/dist/ui/index.html +1 -1
  40. package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
  41. package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
  42. package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
  43. package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
  44. package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
  45. package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
  46. package/package.json +8 -4
  47. package/dist/ui/assets/app-D_nAPnHM.css +0 -1
  48. package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
@@ -1,4 +1,4 @@
1
- import { dn as git, fn as gitOk } from "./src-tsuZRu0H.mjs";
1
+ import { $t as gitOk, Qt as git } from "./src-DN-RPsil.mjs";
2
2
  import path from "node:path";
3
3
  import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { createHash, randomUUID } from "node:crypto";
@@ -1,6 +1,6 @@
1
- import { U as openStore, at as scriptRefusal, it as TOO_LONG, ot as sendFromScript } from "./src-tsuZRu0H.mjs";
1
+ import { $ as sendFromScript, L as openStore, Q as scriptRefusal, Z as TOO_LONG } from "./src-DN-RPsil.mjs";
2
2
  import { l as writeErr } from "./output-B1kwo0YU.mjs";
3
- import { u as inheritedScrubber } from "./src-C7z_6fhn.mjs";
3
+ import { u as inheritedScrubber } from "./src-DHDQ0itC.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  // src/commands/send.ts
6
6
  /**
@@ -1,4 +1,5 @@
1
- import { $n as maskSecrets, $t as countedTokens, An as END_KINDS, Mn as FEEDBACK_KINDS, Nn as FEEDBACK_SEVERITIES, On as outcomeEnd, Pn as FEEDBACK_TARGETS, Pt as parseReport, Zn as MASK, ii as AGENT_DRIVERS, jn as endFor, ln as lowerChild, un as startAt } from "./src-tsuZRu0H.mjs";
1
+ import { It as lowerChild, Jn as END_KINDS, Kn as outcomeEnd, Lt as programFound, Qn as FEEDBACK_TARGETS, Rt as startAt, Xn as FEEDBACK_KINDS, Yn as endFor, Zn as FEEDBACK_SEVERITIES, gr as MASK, un as FOLLOW_UP_KINDS, vr as maskSecrets, vt as parseReport, wi as AGENT_DRIVERS, zn as countedTokens } from "./src-DN-RPsil.mjs";
2
+ import { n as holdGuard, r as orphanGuard } from "./orphan-guard-BFKg8VSd.mjs";
2
3
  import path from "node:path";
3
4
  import { z } from "zod";
4
5
  import { existsSync, linkSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
@@ -448,8 +449,10 @@ const classifyProviderError = (text, status) => {
448
449
  * it can see (a turn cap, an exit code, a provider's error), and the report
449
450
  * the session handed in decides the rest. The runner then writes the end in
450
451
  * the node's numbers, its detail scrubbed with the feedback redactor. A
451
- * session the driver could not run ends as `driverFailure` says, and one that
452
- * reached its token cap as `tokenCapOutcome` says.
452
+ * session the driver could not run (or that was stopped, with what it had
453
+ * spent) ends as `driverFailure` says, one whose process died as
454
+ * `failureReason` explains, and one that reached its token cap as
455
+ * `tokenCapOutcome` says.
453
456
  */
454
457
  /** The end a report gives: done, a reported failure, or none handed in. */
455
458
  const reportEnd = (report) => {
@@ -474,9 +477,15 @@ const errorEnd = (text, fallback = "unknown", status) => endFor(classifyProvider
474
477
  */
475
478
  const settleEnd = (outcome, facts, scrub) => {
476
479
  const end = outcome.report === null ? outcome.end ?? outcomeEnd(outcome) : reportEnd(outcome.report);
480
+ const { usage } = outcome;
481
+ const used = {
482
+ ...facts,
483
+ usedTokens: countedTokens(usage),
484
+ ...usage.costUsd > 0 ? { usedUsd: usage.costUsd } : {}
485
+ };
477
486
  return {
478
487
  ...outcome,
479
- end: endFor(end.kind, facts, end.detail, scrub)
488
+ end: endFor(end.kind, used, end.detail, scrub)
480
489
  };
481
490
  };
482
491
  /**
@@ -509,6 +518,17 @@ const tokenCapOutcome = ({ budgetTokens }, usage, sessionId) => budgetTokens ===
509
518
  usage,
510
519
  errors: [`Reached the token budget (${String(budgetTokens)})`]
511
520
  };
521
+ const REASON_LIMIT = 500;
522
+ /** Noise a Node crash ends stderr with, or a stack frame: never the reason. */
523
+ const isNoise = (line) => /^Node\.js v\d/u.test(line) || /^\s+at /u.test(line);
524
+ /**
525
+ * Why a process ended without a result: the last block of its stderr as one
526
+ * masked, capped line (the reason reaches the terminal, which is not masked).
527
+ */
528
+ const failureReason = (name, stderr, code) => {
529
+ const block = stderr.split("\n").filter((line) => !isNoise(line)).join("\n").split(/\n\s*\n/u).map((text) => text.trim().replaceAll(/\s*\n\s*/gu, " ")).findLast((text) => text !== "");
530
+ return block === void 0 ? `${name} exited with code ${String(code)}` : maskSecrets(block).slice(0, REASON_LIMIT);
531
+ };
512
532
  /**
513
533
  * The kind of a failure to run the driver: its program missing, else what a
514
534
  * provider's words in it say, else a crash.
@@ -519,39 +539,92 @@ const failureKind = (error) => {
519
539
  if (/\bENOENT\b/u.test(text)) return "tool_missing";
520
540
  return classifyProviderError(text)?.kind ?? "driver_crashed";
521
541
  };
542
+ const NOTHING_SPENT = {
543
+ usage: {
544
+ costUsd: 0,
545
+ inputTokens: 0,
546
+ outputTokens: 0,
547
+ cacheReadTokens: 0,
548
+ cacheWriteTokens: 0,
549
+ turns: 0,
550
+ durationMs: 0
551
+ },
552
+ sessionId: ""
553
+ };
522
554
  /**
523
555
  * A finished event for a session the driver could not run, ended as `kind` with
524
- * `message` (Ork's words or the tool's, masked) as its detail.
556
+ * `message` (Ork's words or the tool's, masked) as its detail; a stopped one
557
+ * keeps what it had `spent` (its tokens decoded so far, dollars $0).
525
558
  */
526
- const driverFailure = (message, kind, scope) => ({
559
+ const driverFailure = (message, kind, scope, spent = NOTHING_SPENT) => ({
527
560
  type: "session.finished",
528
561
  outcome: {
529
562
  subtype: "error_driver",
530
563
  end: endFor(kind, scope.facts, kind === "interrupted" ? void 0 : message, scope.scrub),
531
- sessionId: "",
564
+ sessionId: spent.sessionId,
532
565
  report: null,
533
566
  text: "",
534
- usage: {
535
- costUsd: 0,
536
- inputTokens: 0,
537
- outputTokens: 0,
538
- cacheReadTokens: 0,
539
- cacheWriteTokens: 0,
540
- turns: 0,
541
- durationMs: 0
542
- },
567
+ usage: spent.usage,
543
568
  errors: [message]
544
569
  }
545
570
  });
571
+ // ../runtime/src/leg-spawn.ts
572
+ /**
573
+ * @file Starting one process (leg) of a session: at the project's priority, and
574
+ * off Windows in its own process group beside the orphan guard, so it and
575
+ * what it started end with `ork`, whatever ends `ork`. The process's pid, and
576
+ * the SIGINT that stops it, stay the driver's own.
577
+ */
578
+ /** The fd of the orphan guard's pipe from `ork`. */
579
+ const WATCH_FD = 3;
580
+ /**
581
+ * Starts the orphan guard, then becomes the driver. The pipe is closed by a
582
+ * bare `exec`: macOS's `sh` keeps a copy of a descriptor an `exec` command
583
+ * closes, which the driver and its leftovers would then inherit.
584
+ */
585
+ const GUARDED = `${orphanGuard(WATCH_FD)}exec ${String(WATCH_FD)}<&-; exec "$0" "$@"`;
586
+ const piped = (child) => child.stdin !== null && child.stdout !== null && child.stderr !== null;
587
+ /**
588
+ * Spawns the leg's process. A program that isn't there is spawned bare, so it
589
+ * fails as spawn's ENOENT; the guard is released once the driver exits, so a
590
+ * session that ends normally leaves its leftovers alone.
591
+ */
592
+ const spawnLeg = (start) => {
593
+ const [bin, args] = startAt(start.priority, start.bin, start.args, start.env);
594
+ const guarded = process.platform !== "win32" && programFound(start.bin, start.env);
595
+ const child = spawn(guarded ? "/bin/sh" : bin, guarded ? [
596
+ "-c",
597
+ GUARDED,
598
+ bin,
599
+ ...args
600
+ ] : args, {
601
+ cwd: start.cwd,
602
+ detached: guarded,
603
+ env: { ...start.env },
604
+ stdio: [
605
+ "pipe",
606
+ "pipe",
607
+ "pipe",
608
+ guarded ? "pipe" : "ignore"
609
+ ]
610
+ });
611
+ if (!piped(child)) throw new Error(`${start.bin} was started without pipes`);
612
+ if (guarded) {
613
+ const release = holdGuard(child.stdio[WATCH_FD]);
614
+ child.once("exit", release);
615
+ }
616
+ return child;
617
+ };
546
618
  // ../runtime/src/session-leg.ts
547
619
  /**
548
620
  * @file One process (leg) of a session the runner drives: it writes the queued
549
621
  * stdin lines, yields the adapter's decoded events, and says how it ended. A
550
622
  * session is one leg, or several when a paused node's tool call was deferred
551
623
  * or a message waits for a tool that takes none mid-session; a resumed leg
552
- * carries what the legs before it spent, and its start is not news.
624
+ * carries what the legs before it spent, and its start is not news. Off
625
+ * Windows the process ends with `ork`, whatever ends `ork` (the orphan
626
+ * guard).
553
627
  */
554
- const REASON_LIMIT = 500;
555
628
  /** Marks a session's start with the driver that runs it. */
556
629
  const withDriver = (event, driver) => event.type === "session.started" ? {
557
630
  ...event,
@@ -565,10 +638,11 @@ const settled = (outcome, input) => settleEnd(input.reported === void 0 ? outcom
565
638
  ...outcome,
566
639
  report: input.reported()
567
640
  }, input.scope.facts, input.scope.scrub);
641
+ /** What this leg `used`, with what the legs before it spent. */
642
+ const withCarried = (input, used) => used === void 0 || input.carried === void 0 ? used ?? input.carried : input.adapter.usage.combine(input.carried, used);
568
643
  /** The session's end at its token cap, its earlier legs' usage included. */
569
644
  const tokenCapEnd = (input, used, sessionId) => {
570
- const usage = used === void 0 || input.carried === void 0 ? used : input.adapter.usage.combine(input.carried, used);
571
- const capped = tokenCapOutcome(input.scope.facts, usage, sessionId);
645
+ const capped = tokenCapOutcome(input.scope.facts, used === void 0 ? void 0 : withCarried(input, used), sessionId);
572
646
  return capped === void 0 ? void 0 : {
573
647
  type: "session.finished",
574
648
  outcome: settled(capped, input)
@@ -588,37 +662,36 @@ const legStep = (decoded, input) => {
588
662
  const events = [];
589
663
  let ended;
590
664
  for (const said of decoded.events) {
591
- const event = said.type === "session.finished" ? {
665
+ if (said.type === "session.started" && carried !== void 0) continue;
666
+ if (said.type !== "session.finished") {
667
+ events.push(withDriver(said, adapter.driver));
668
+ continue;
669
+ }
670
+ const own = said.outcome.usage;
671
+ const usage = carried === void 0 ? own : adapter.usage.combine(carried, own);
672
+ const last = {
592
673
  ...said,
593
- outcome: settled(said.outcome, input)
594
- } : said;
595
- if (event.type === "session.started" && carried !== void 0) continue;
596
- if (event.type !== "session.finished") events.push(withDriver(event, adapter.driver));
597
- else if (deferred) ended = {
674
+ outcome: settled({
675
+ ...said.outcome,
676
+ usage
677
+ }, input)
678
+ };
679
+ const { sessionId, end } = last.outcome;
680
+ if (deferred) ended = {
598
681
  kind: "deferred",
599
- sessionId: event.outcome.sessionId,
600
- usage: event.outcome.usage,
601
- last: event
682
+ sessionId,
683
+ usage: own,
684
+ last
685
+ };
686
+ else if (input.withhold() && wentWell(end?.kind)) ended = {
687
+ kind: "continued",
688
+ sessionId,
689
+ usage: own,
690
+ last
602
691
  };
603
692
  else {
604
- const usage = carried === void 0 ? event.outcome.usage : adapter.usage.combine(carried, event.outcome.usage);
605
- const last = {
606
- ...event,
607
- outcome: {
608
- ...event.outcome,
609
- usage
610
- }
611
- };
612
- if (input.withhold() && wentWell(event.outcome.end?.kind)) ended = {
613
- kind: "continued",
614
- sessionId: event.outcome.sessionId,
615
- usage: event.outcome.usage,
616
- last
617
- };
618
- else {
619
- events.push(last);
620
- ended = { kind: "finished" };
621
- }
693
+ events.push(last);
694
+ ended = { kind: "finished" };
622
695
  }
623
696
  }
624
697
  return ended === void 0 ? { events } : {
@@ -626,28 +699,9 @@ const legStep = (decoded, input) => {
626
699
  ended
627
700
  };
628
701
  };
629
- /** Noise a Node crash ends stderr with, or a stack frame: never the reason. */
630
- const isNoise = (line) => /^Node\.js v\d/u.test(line) || /^\s+at /u.test(line);
631
- /**
632
- * Why a process ended without a result: the last block of its stderr as one
633
- * masked, capped line (the reason reaches the terminal, which is not masked).
634
- */
635
- const failureReason = (name, stderr, code) => {
636
- const block = stderr.split("\n").filter((line) => !isNoise(line)).join("\n").split(/\n\s*\n/u).map((text) => text.trim().replaceAll(/\s*\n\s*/gu, " ")).findLast((text) => text !== "");
637
- return block === void 0 ? `${name} exited with code ${String(code)}` : maskSecrets(block).slice(0, REASON_LIMIT);
638
- };
639
702
  /** Runs one process of a session, yielding its events; returns how it ended. */
640
703
  const runLeg = async function* (input) {
641
- const [bin, args] = startAt(input.priority, input.bin, input.args, input.env);
642
- const child = spawn(bin, args, {
643
- cwd: input.cwd,
644
- env: { ...input.env },
645
- stdio: [
646
- "pipe",
647
- "pipe",
648
- "pipe"
649
- ]
650
- });
704
+ const child = spawnLeg(input);
651
705
  lowerChild(input.priority, child.pid);
652
706
  const exited = new Promise((resolve) => {
653
707
  child.on("close", (code, signal) => {
@@ -680,11 +734,14 @@ const runLeg = async function* (input) {
680
734
  if (input.adapter.command.input === void 0) child.stdin.end();
681
735
  let outcome;
682
736
  let sessionId = "";
737
+ /** What this leg has used so far, as the last usage step said. */
738
+ let used;
683
739
  const { decoder } = input;
684
740
  const decode = decoder?.decode ?? input.adapter.decode;
685
741
  try {
686
742
  for await (const line of createInterface({ input: child.stdout })) {
687
743
  const decoded = decode(line);
744
+ used = decoded.usage ?? used;
688
745
  const { events, ended } = legStep(decoded, input);
689
746
  for (const event of decoded.events) if (event.type === "session.started") sessionId = event.sessionId;
690
747
  yield* events;
@@ -712,11 +769,18 @@ const runLeg = async function* (input) {
712
769
  reason: `could not start ${input.bin}: ${end.message}`,
713
770
  end: failureKind(end)
714
771
  };
715
- if (stopped || end.signal !== null) return {
716
- kind: "failed",
717
- reason: "interrupted",
718
- end: "interrupted"
719
- };
772
+ if (stopped || end.signal !== null) {
773
+ const usage = withCarried(input, used);
774
+ return {
775
+ kind: "failed",
776
+ reason: "interrupted",
777
+ end: "interrupted",
778
+ ...usage === void 0 ? {} : { spent: {
779
+ usage,
780
+ sessionId
781
+ } }
782
+ };
783
+ }
720
784
  const reason = failureReason(input.adapter.command.bin, stderr, end.code);
721
785
  return {
722
786
  kind: "failed",
@@ -802,6 +866,7 @@ const releaseTool = defineTool({
802
866
  });
803
867
  // ../runtime/src/tools/delegate.ts
804
868
  const BRIEF = z.string().min(1).describe("Self-contained: the goal, the files that matter, and what done looks like. The child does not see your conversation.");
869
+ const DESCRIPTION = z.string().exactOptional().describe("A few words naming this task; shown as the child's label.");
805
870
  /** What the leader reads back: each report without the usage details. */
806
871
  const reportLine = (report) => {
807
872
  const { usage, ...output } = report.output;
@@ -827,7 +892,8 @@ const delegateTool = defineTool({
827
892
  const ids = ports.ownership?.children.map((child) => child.id) ?? [];
828
893
  return { tasks: z.array(z.strictObject({
829
894
  child: z.enum(ids).describe(`The id of a node you own: ${ids.join(", ")}.`),
830
- brief: BRIEF
895
+ brief: BRIEF,
896
+ description: DESCRIPTION
831
897
  })).min(1) };
832
898
  },
833
899
  available: ({ ports }) => (ports.ownership?.children.length ?? 0) > 0,
@@ -843,7 +909,8 @@ const spawnWorkerTool = defineTool({
843
909
  const ids = ports.ownership?.templates ?? [];
844
910
  return { tasks: z.array(z.strictObject({
845
911
  template: z.enum(ids).describe(`The id of a child worker you own: ${ids.join(", ")}.`),
846
- brief: BRIEF
912
+ brief: BRIEF,
913
+ description: DESCRIPTION
847
914
  })).min(1) };
848
915
  },
849
916
  available: ({ ports }) => ports.ownership !== void 0 && ports.ownership.templates.length > 0 && ports.ownership.workersLeft() > 0,
@@ -884,6 +951,74 @@ const feedbackTool = defineTool({
884
951
  return Promise.resolve(text(sent.ok ? "Recorded. Thank you; carry on with your task." : `Not recorded: ${sent.error}.`));
885
952
  }
886
953
  });
954
+ // ../runtime/src/tools/follow-up.ts
955
+ /**
956
+ * @file `follow_up`: a session's way to leave work the change leaves for later
957
+ * (a change for a later run, a note for another project, a message a person
958
+ * must send, a task or a check for the maintainer). Ork keeps it where the
959
+ * project's `followUps` rule says, merges duplicates, caps it per session,
960
+ * and never sends or runs anything on its own.
961
+ */
962
+ const isFor = (value) => /^(?:agent|me|project:\S.*|person:\S.*)$/u.test(value);
963
+ const isDue = (value) => /^(?:now|after-merge|after-deploy|\d{4}-\d{2}-\d{2})$/u.test(value);
964
+ const followUpTool = defineTool({
965
+ name: "follow_up",
966
+ description: "Leave work this change leaves for later and that is not yours to do now: a change for a later run (for: agent), something for another registered project (for: project:<name>), a message a person must get (for: person:<name>, kind: message, with a draft), or a task or check for the maintainer (for: me, with steps). Ork keeps it, merges duplicates, and sends or runs nothing itself. Not for problems with Ork or the workflow (use feedback), nor for what you can finish now.",
967
+ shape: {
968
+ title: z.string().min(1).max(200).describe("One line."),
969
+ body: z.string().max(4e3).optional().describe("What and why, briefly."),
970
+ for: z.string().refine(isFor, "agent, me, project:<name> or person:<name>").describe("Who acts: agent, me, project:<name> or person:<name>."),
971
+ kind: z.enum(FOLLOW_UP_KINDS),
972
+ due: z.string().refine(isDue, "now, after-merge, after-deploy or YYYY-MM-DD").describe("now, after-merge, after-deploy, or a YYYY-MM-DD date."),
973
+ where: z.array(z.string()).max(20).optional().describe("Files it is about, relative to the project root."),
974
+ ref: z.string().min(1).optional().describe("The pull request or branch an after-merge follow-up waits on, when you know it."),
975
+ draft: z.strictObject({
976
+ to: z.string().min(1),
977
+ channel: z.string().optional().describe("github (to: an issue or PR URL), email, slack… A hint for the maintainer."),
978
+ text: z.string().min(1).max(4e3)
979
+ }).optional().describe("For a message: what the maintainer will send."),
980
+ steps: z.array(z.strictObject({
981
+ text: z.string().trim().min(1),
982
+ command: z.string().optional().describe("Shown to the maintainer to copy; Ork never runs it.")
983
+ })).max(20).optional().describe("For a task or check: what to do, in order.")
984
+ },
985
+ available: ({ ports }) => ports.followUps !== void 0,
986
+ run: async ({ ports }, args) => {
987
+ const port = ports.followUps;
988
+ if (port === void 0) return text("No follow-ups here.");
989
+ if (!isFor(args.for) || !isDue(args.due)) return text("Not kept.");
990
+ const input = {
991
+ title: args.title,
992
+ for: args.for,
993
+ kind: args.kind,
994
+ due: args.due,
995
+ ...args.body === void 0 ? {} : { body: args.body },
996
+ ...args.where === void 0 ? {} : { where: args.where },
997
+ ...args.ref === void 0 ? {} : { ref: args.ref },
998
+ ...args.draft === void 0 ? {} : { draft: {
999
+ to: args.draft.to,
1000
+ text: args.draft.text,
1001
+ ...args.draft.channel === void 0 ? {} : { channel: args.draft.channel }
1002
+ } },
1003
+ ...args.steps === void 0 ? {} : { steps: args.steps.map((step) => ({
1004
+ text: step.text,
1005
+ ...step.command === void 0 ? {} : { command: step.command }
1006
+ })) }
1007
+ };
1008
+ const result = await port.add(input);
1009
+ if (!result.ok) return {
1010
+ ...text(`Not kept: ${result.error}.`),
1011
+ isError: true
1012
+ };
1013
+ const { followUp, merged, note } = result.added;
1014
+ return text([
1015
+ merged ? `Merged into follow-up ${followUp.id}, which was already kept.` : `Kept as follow-up ${followUp.id}.`,
1016
+ ...note === void 0 ? [] : [note],
1017
+ ...result.relayed === void 0 ? [] : [`Also sent to ${result.relayed.run}/${result.relayed.node}, a live run of that project.`],
1018
+ "Carry on with your task."
1019
+ ].join(" "));
1020
+ }
1021
+ });
887
1022
  // ../runtime/src/tools/input.ts
888
1023
  /**
889
1024
  * @file `read_input`: what the workflow sent a node back with, whole. A loop's
@@ -1074,6 +1209,7 @@ const ORK_TOOLS = [
1074
1209
  claimTool,
1075
1210
  releaseTool,
1076
1211
  feedbackTool,
1212
+ followUpTool,
1077
1213
  {
1078
1214
  name: REPORT_TOOL$1,
1079
1215
  bind: (context) => context.report === void 0 || checksItsReport(context.driver) ? null : bound(context.report)
@@ -1218,7 +1354,9 @@ const createSessionRuntime = (adapter, options = {}) => ({
1218
1354
  }
1219
1355
  const leg = yield* runLeg(legOf(legSpec, spent));
1220
1356
  if (leg.kind === "failed") {
1221
- yield driverFailure(leg.reason, leg.end, scope);
1357
+ const { spent: stopped } = leg;
1358
+ if (stopped?.sessionId === "") stopped.sessionId = legSpec.resume ?? "";
1359
+ yield driverFailure(leg.reason, leg.end, scope, stopped);
1222
1360
  return;
1223
1361
  }
1224
1362
  if (leg.kind === "finished") return;