@krapht/ork 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (51) hide show
  1. package/dist/__build_info__-Bsb1kjgF.mjs +340 -0
  2. package/dist/{store-CM3cERGl.mjs → attach-BHO3X3cf.mjs} +36 -91
  3. package/dist/{attach-E6NNyA3T.mjs → attach-CPespxlQ.mjs} +6 -5
  4. package/dist/bin.mjs +148 -1444
  5. package/dist/brief-za0ozN_E.mjs +58 -0
  6. package/dist/{builtin-files-n6o6oWpB.mjs → builtin-files-2bHf76x-.mjs} +1 -1
  7. package/dist/{chips-CyeB_rhR.mjs → chips-BqcT58qr.mjs} +1 -1
  8. package/dist/{completion-SNNNxnv9.mjs → completion-CQ2UYKuJ.mjs} +52 -11
  9. package/dist/context-BNqDw9z1.mjs +106 -0
  10. package/dist/{find-CjsNYAAf.mjs → find-BYLlszqj.mjs} +1 -1
  11. package/dist/follow-ups-B4GJ3Vzw.mjs +461 -0
  12. package/dist/{format-C60TFpRe.mjs → format-C8MsFj8_.mjs} +30 -16
  13. package/dist/{help-groups-CyfPWlST.mjs → help-groups-CQ98mLjF.mjs} +20 -6
  14. package/dist/{in-a-run-BYjTXaST.mjs → in-a-run-Bp0k0J09.mjs} +1 -1
  15. package/dist/{__build_info__-Da73_nBs.mjs → install-DWtXTq4y.mjs} +3 -227
  16. package/dist/{limits-C5QKZNLT.mjs → limits-B1qzS4cX.mjs} +1 -1
  17. package/dist/{mcp-bcctR-wc.mjs → mcp-BOrVkyhN.mjs} +6 -6
  18. package/dist/{once-DPQu5Q3H.mjs → once-C1fL3GVo.mjs} +4 -4
  19. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  20. package/dist/{output-CnRzrbUM.mjs → output-B1kwo0YU.mjs} +17 -1
  21. package/dist/{pause-BPvlDIvz.mjs → pause-DOMK-R8V.mjs} +4 -4
  22. package/dist/post-BzA6Qobq.mjs +44 -0
  23. package/dist/{relay-C8W0t_zc.mjs → relay-OWuu4fib.mjs} +2 -2
  24. package/dist/run--4-1xAF2.mjs +1451 -0
  25. package/dist/{run-CQIfq918.mjs → run-CiPsW9Kf.mjs} +1 -1
  26. package/dist/send-CnNNW-ot.mjs +105 -0
  27. package/dist/{src-CqRqsb2t.mjs → src-B9zWIVqo.mjs} +226 -44
  28. package/dist/{src-CGJGL1sZ.mjs → src-DQKz2eOH.mjs} +326 -81
  29. package/dist/{src-D93tHZ5B.mjs → src-DcrhgipG.mjs} +107 -47
  30. package/dist/{src-BeR6f70A.mjs → src-Dr_fTSu-.mjs} +5858 -3746
  31. package/dist/store-oKJ-cCf3.mjs +16 -0
  32. package/dist/ui/assets/app-Bdc6OBeZ.css +1 -0
  33. package/dist/ui/assets/app-Drp8Dm9y.js +111 -0
  34. package/dist/ui/assets/{index-CWwm8HTG.js → index-BtwkftKe.js} +2 -2
  35. package/dist/ui/index.html +1 -1
  36. package/dist/{ui-D2Ydofq0.mjs → ui-CzKlsCnm.mjs} +4 -4
  37. package/dist/{usage-6WIowblz.mjs → usage-DHGNk2JL.mjs} +13 -141
  38. package/dist/{context-DHudLA5e.mjs → validate-Bs_gg22g.mjs} +17 -107
  39. package/dist/{verdict-CIy8tAyX.mjs → verdict-CMvPCpWC.mjs} +2 -2
  40. package/dist/{watch-uOU_WeBI.mjs → watch-CCCfw3n7.mjs} +24 -8
  41. package/dist/{workflows-7tQdMnrA.mjs → workflows-CW28ZU-z.mjs} +3 -3
  42. package/dist/{worktrees-ZCII298F.mjs → worktrees-CyE8aF6j.mjs} +3 -5
  43. package/package.json +2 -2
  44. package/dist/clean-Bh4KZgzc.mjs +0 -91
  45. package/dist/decide-DuIHACvH.mjs +0 -50
  46. package/dist/list-BUb5Uc3H.mjs +0 -117
  47. package/dist/remove-BkFWGVYo.mjs +0 -201
  48. package/dist/report-CEN8VoUb.mjs +0 -140
  49. package/dist/send-CaZlGG5a.mjs +0 -163
  50. package/dist/ui/assets/app-DYRC0V4p.js +0 -111
  51. package/dist/ui/assets/app-QoHVi-mb.css +0 -1
@@ -1,12 +1,13 @@
1
- import { $t as serversIn, At as endedWith, Dr as integrationFor, Er as TOOL_PREFIX, Ht as envReadsOf, It as lowerChild, Kt as declaredOutputOf, Lt as startAt, M as sessionRefusals, Mt as stoppedEnd, Nr as AGENT_DRIVERS, Nt as calledWorkflows, Ot as runScope, Pt as slotClock, Qt as mcpNotText, Tr as INTEGRATIONS, Wt as sessionDriver, Zt as MCP_FILE, an as endFor, bn as renderTemplate, br as isToolNode, cr as programsRunBy, fr as markValue, gt as heldTimeout, jt as failedWith, kt as declaredProblem, lt as reportSchemaFor, nn as outcomeEnd, rn as END_DETAIL_LIMIT, rt as planReason, sr as beforeOf, st as planNodeSession, tn as endOf, vn as evaluate, wr as timeoutMs, yn as parseTemplate, yr as isClaudeNode } from "./src-BeR6f70A.mjs";
2
- import { t as runOnce } from "./run-CQIfq918.mjs";
1
+ import { $t as sessionDriver, Bn as outcomeEnd, Bt as stoppedEnd, Dt as reportSchemaFor, Ft as runScope, Gr as beforeOf, Ht as slotClock, It as calledWorkflows, Kr as programsRunBy, Kt as startAt, Nn as countedTokens, Pt as heldTimeout, Rt as endedWith, Tt as planNodeSession, Un as endFor, Vn as END_DETAIL_LIMIT, Vt as declaredProblem, Wt as lowerChild, Xr as markValue, Xt as envReadsOf, _i as AGENT_DRIVERS, ar as renderTemplate, ci as timeoutMs, di as integrationFor, ii as isToolNode, in as serversIn, ir as parseTemplate, li as INTEGRATIONS, nn as MCP_FILE, nt as sessionRefusals, ri as isClaudeNode, rn as mcpNotText, rr as evaluate, tn as declaredOutputOf, ui as TOOL_PREFIX, xt as planReason, zn as endOf, zt as failedWith } from "./src-Dr_fTSu-.mjs";
2
+ import { t as runOnce } from "./run-CiPsW9Kf.mjs";
3
+ import { n as holdGuard, r as orphanGuard, t as KILL_AFTER_MS } from "./orphan-guard-BFKg8VSd.mjs";
3
4
  import path from "node:path";
4
5
  import { accessSync, constants, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
6
  import { spawn } from "node:child_process";
7
+ import { Readable } from "node:stream";
6
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
9
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8
10
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
9
- import { Readable } from "node:stream";
10
11
  // ../nodes/src/call.ts
11
12
  /**
12
13
  * @file The `workflow.call` node type (RFC-004): renders what the node asks for
@@ -538,43 +539,36 @@ const afterLateTurn = (first, later) => {
538
539
  errors: first.errors
539
540
  };
540
541
  };
542
+ /** The least of the limits that are set; `undefined` with none. */
543
+ const least = (...limits) => {
544
+ const known = limits.filter((limit) => limit !== void 0);
545
+ return known.length === 0 ? void 0 : Math.min(...known);
546
+ };
547
+ /** A cap less what this activation used, beside what a quota says is left. */
548
+ const leftOf = (caps, used, quotaLeft) => least(...caps.map((cap) => cap === void 0 ? void 0 : cap - used), quotaLeft);
541
549
  /**
542
- * What another turn of a session may use. `spent` and `turns` are this
543
- * activation's, which neither the node's cap nor the run's budget has counted.
544
- * The quota has: the ledger took it as the session finished, so what it says is
545
- * left is left. `null` when nothing is, and the turn does not start.
550
+ * What another turn of a session may use. `spent`, `tokens` (counted, RFC-015)
551
+ * and `turns` are this activation's, which neither the node's cap nor the run's
552
+ * budget has counted. The quota has: the ledger took it as the session
553
+ * finished, so what it says is left is left. The run's tokens left have too:
554
+ * only the node's own `budgetTokens` takes `tokens` off. `null` when nothing
555
+ * is, and the turn does not start.
546
556
  */
547
- const lateLimits = (spec, { spent, turns }, runLeft, quotaLeft) => {
548
- const budgets = [...[spec.budgetUsd, runLeft].flatMap((limit) => limit === void 0 ? [] : [limit - spent]), ...quotaLeft === void 0 ? [] : [quotaLeft]];
549
- const budgetUsd = budgets.length === 0 ? void 0 : Math.min(...budgets);
550
- if (budgetUsd !== void 0 && budgetUsd <= 0) return null;
551
- const maxTurns = spec.maxTurns === void 0 ? void 0 : spec.maxTurns - turns;
552
- if (maxTurns !== void 0 && maxTurns <= 0) return null;
557
+ const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}) => {
558
+ const budgetUsd = leftOf([spec.budgetUsd, runLeft], used.spent, quotaLeft);
559
+ const budgetTokens = leftOf([spec.budgetTokens], used.tokens ?? 0, least(tokensLeft.run, tokensLeft.quota));
560
+ const maxTurns = spec.maxTurns === void 0 ? void 0 : spec.maxTurns - used.turns;
561
+ if ([
562
+ budgetUsd,
563
+ budgetTokens,
564
+ maxTurns
565
+ ].some((left) => (left ?? 1) <= 0)) return null;
553
566
  return {
554
567
  ...budgetUsd === void 0 ? {} : { budgetUsd },
568
+ ...budgetTokens === void 0 ? {} : { budgetTokens },
555
569
  ...maxTurns === void 0 ? {} : { maxTurns }
556
570
  };
557
571
  };
558
- /** What a session may spend as it starts: the least of the budget and any quota. */
559
- const budgetNow = (context) => {
560
- if (context.budgetLeftNow === void 0) return context.budgetLeftUsd;
561
- const known = [context.budgetLeftNow(), context.quota?.().leftUsd].flatMap((left) => left === void 0 ? [] : [left]);
562
- return known.length === 0 ? void 0 : Math.min(...known);
563
- };
564
- /**
565
- * What a node's first session may spend, set aside from the run's budget in the
566
- * same step that reads it (ADR-077), so a node starting beside it is never
567
- * offered the same part: the least of what is left and the node's own
568
- * `budgetUsd`. With nothing left, it waits for a worker running beside it to
569
- * finish, as long as one may.
570
- */
571
- const budgetToStart = async (context) => {
572
- const left = budgetNow(context);
573
- if (left !== void 0 && left <= 0) return await context.budgetFreed?.(context.signal) === true ? budgetToStart(context) : left;
574
- const own = isClaudeNode(context.node) ? context.node.budgetUsd ?? context.workflow.defaults?.budgetUsd : void 0;
575
- context.reserveBudget?.(own === void 0 || left === void 0 ? left : Math.min(own, left));
576
- return left;
577
- };
578
572
  /**
579
573
  * Whether a limit across runs, rather than the node's work, ended a node: a
580
574
  * session whose cap was a quota's and that ran into it, or one its plan refused
@@ -587,7 +581,7 @@ const limitWatch = (context) => {
587
581
  why = reason;
588
582
  },
589
583
  session: (asked) => {
590
- const left = context.quota?.().leftUsd;
584
+ const { leftUsd: left, leftTokens } = context.quota?.() ?? {};
591
585
  let refused;
592
586
  return {
593
587
  saw: (event) => {
@@ -595,7 +589,7 @@ const limitWatch = (context) => {
595
589
  },
596
590
  ended: (outcome) => {
597
591
  if (refused !== void 0 && (outcome?.report ?? null) === null) why = refused;
598
- const capped = left !== void 0 && asked.budgetUsd !== void 0 && asked.budgetUsd >= left;
592
+ const capped = left !== void 0 && asked.budgetUsd !== void 0 && asked.budgetUsd >= left || leftTokens !== void 0 && asked.budgetTokens !== void 0 && asked.budgetTokens >= leftTokens;
599
593
  const spent = outcome !== null && outcomeEnd(outcome).kind === "budget_limit";
600
594
  if (capped && spent) why = context.quota?.().blocked ?? "a session reached what was left of a quota";
601
595
  }
@@ -657,6 +651,61 @@ const sessionDriverOf = (context, plan, options) => {
657
651
  const unfit = plan.pinned === void 0 ? void 0 : refusalOn(context, driver, options.drivers ?? AGENT_DRIVERS);
658
652
  return unfit === void 0 ? driver : notStarted("invalid_node", unfit);
659
653
  };
654
+ // ../nodes/src/start-budgets.ts
655
+ /** What a session may spend as it starts: the least of the budget and any quota. */
656
+ const budgetNow = (context) => context.budgetLeftNow === void 0 ? context.budgetLeftUsd : least(context.budgetLeftNow(), context.quota?.().leftUsd);
657
+ /** The counted tokens a session may use as it starts: the run's and a quota's. */
658
+ const tokensNow = (context) => least(context.tokensLeftNow?.(), context.quota?.().leftTokens);
659
+ /**
660
+ * What is `left` now, with `own` (the node's cap) set aside through `reserve`
661
+ * in the same step; with nothing left, it waits for a worker's reserve to
662
+ * free.
663
+ */
664
+ const startWith = async (context, left, own, reserve) => {
665
+ const now = left();
666
+ if (now !== void 0 && now <= 0) {
667
+ context.reserveBudget?.(0);
668
+ context.reserveTokens?.(0);
669
+ return await context.budgetFreed?.(context.signal) === true ? startWith(context, left, own, reserve) : now;
670
+ }
671
+ reserve?.(own === void 0 || now === void 0 ? now : Math.min(own, now));
672
+ return now;
673
+ };
674
+ /**
675
+ * What a node's first session may spend, set aside from the run's budget in the
676
+ * same step that reads it (ADR-077), so a node starting beside it is never
677
+ * offered the same part: the least of what is left and the node's own
678
+ * `budgetUsd`. With nothing left, it waits for a worker running beside it to
679
+ * finish, as long as one may.
680
+ */
681
+ const budgetToStart = (context) => startWith(context, () => budgetNow(context), isClaudeNode(context.node) ? context.node.budgetUsd ?? context.workflow.defaults?.budgetUsd : void 0, context.reserveBudget);
682
+ /** `budgetToStart` in counted tokens: the run's `budgetTokens` and a quota's. */
683
+ const tokensToStart = (context) => startWith(context, () => tokensNow(context), isClaudeNode(context.node) ? context.node.budgetTokens ?? context.workflow.defaults?.budgetTokens : void 0, context.reserveTokens);
684
+ /**
685
+ * What a node's first session may spend, in dollars and in counted tokens, set
686
+ * into `planOptions`; the node's result instead when nothing is left of
687
+ * either.
688
+ */
689
+ const startBudgets = async (context, planOptions) => {
690
+ const usd = await budgetToStart(context);
691
+ if (usd !== void 0 && usd <= 0) return notStarted("budget_limit", "nothing is left of the run's budget");
692
+ if (usd !== void 0) planOptions.budgetLeftUsd = usd;
693
+ const waited = { done: false };
694
+ const tokens = await tokensToStart({
695
+ ...context,
696
+ budgetFreed: async (signal) => {
697
+ waited.done = true;
698
+ return await context.budgetFreed?.(signal) ?? false;
699
+ }
700
+ });
701
+ if (tokens !== void 0 && tokens <= 0) return notStarted("budget_limit", "nothing is left of the run's tokens");
702
+ if (waited.done) {
703
+ context.reserveTokens?.(0);
704
+ return startBudgets(context, planOptions);
705
+ }
706
+ if (tokens !== void 0) planOptions.budgetLeftTokens = tokens;
707
+ return null;
708
+ };
660
709
  // ../nodes/src/tool-output.ts
661
710
  /**
662
711
  * @file A tool node's structured result (`output: { format: json }`): the file
@@ -1742,9 +1791,8 @@ const claudeExecutor = (options) => async (context) => {
1742
1791
  }
1743
1792
  const blocked = context.quota?.().blocked;
1744
1793
  if (blocked !== void 0) return notStarted("plan_limit", blocked, blocked);
1745
- const budget = await budgetToStart(context);
1746
- if (budget !== void 0 && budget <= 0) return notStarted("budget_limit", "nothing is left of the run's budget");
1747
- if (budget !== void 0) planOptions.budgetLeftUsd = budget;
1794
+ const refused = await startBudgets(context, planOptions);
1795
+ if (refused !== null) return refused;
1748
1796
  const plan = planNodeSession(context.workflow, context.id, planOptions);
1749
1797
  if (!plan.ok) return notStarted("invalid_node", plan.error);
1750
1798
  const servers = namedMcpEntries(context.projectRoot, plan.mcp);
@@ -1769,12 +1817,14 @@ const claudeExecutor = (options) => async (context) => {
1769
1817
  if (context.mailbox !== void 0) ports.mailbox = context.mailbox;
1770
1818
  if (context.claims !== void 0) ports.claims = context.claims;
1771
1819
  if (context.feedback !== void 0) ports.feedback = context.feedback;
1820
+ if (context.followUps !== void 0) ports.followUps = context.followUps;
1772
1821
  const whole = context.round > 1 ? wholeInput(context.input) : void 0;
1773
1822
  if (whole !== void 0) ports.input = whole;
1774
1823
  const watch = limitWatch(context);
1775
1824
  const run = async (asked) => {
1776
1825
  const watching = watch.session(asked);
1777
1826
  context.reserveBudget?.(asked.budgetUsd);
1827
+ context.reserveTokens?.(asked.budgetTokens);
1778
1828
  const session = runtime.start(asked, ports);
1779
1829
  const stop = () => {
1780
1830
  session.interrupt();
@@ -1793,6 +1843,7 @@ const claudeExecutor = (options) => async (context) => {
1793
1843
  }
1794
1844
  } finally {
1795
1845
  context.reserveBudget?.(0);
1846
+ context.reserveTokens?.(0);
1796
1847
  disconnect?.();
1797
1848
  context.signal.removeEventListener("abort", stop);
1798
1849
  }
@@ -1808,8 +1859,12 @@ const claudeExecutor = (options) => async (context) => {
1808
1859
  }
1809
1860
  return lateLimits(spec, {
1810
1861
  spent: context.ownSpend?.(sessionId, usage.costUsd) ?? usage.costUsd,
1811
- turns: usage.turns
1812
- }, context.budgetLeftNow?.(), quota?.leftUsd);
1862
+ turns: usage.turns,
1863
+ tokens: countedTokens(usage)
1864
+ }, context.budgetLeftNow?.(), quota?.leftUsd, {
1865
+ run: context.tokensLeftNow?.(),
1866
+ quota: quota?.leftTokens
1867
+ });
1813
1868
  };
1814
1869
  const resumeWith = (outcome, prompt, limits) => run({
1815
1870
  ...spec,
@@ -2090,20 +2145,23 @@ const mcpCallExecutor = (options = {}) => async (context) => {
2090
2145
  * whatever it started: SIGTERM, then SIGKILL for what ignores that. A stop or
2091
2146
  * a timeout therefore leaves nothing running, and an escaped process that
2092
2147
  * keeps the output pipes open cannot hold the node. Off Windows it runs under
2093
- * `sh`, whose `times` says the CPU the program and its children used, and at
2094
- * the project's priority: `low` starts it below Ork's own (nice, and on macOS
2148
+ * `sh`, whose `times` says the CPU the program and its children used, beside
2149
+ * the orphan guard, which ends the group when `ork` dies, and at the
2150
+ * project's priority: `low` starts it below Ork's own (nice, and on macOS
2095
2151
  * utility QoS).
2096
2152
  */
2097
- /** How long a program may take to end after SIGTERM. */
2098
- const KILL_AFTER_MS = 2e3;
2099
2153
  /** How long to wait for the pipes to close after SIGKILL. */
2100
2154
  const CLOSE_AFTER_MS = 500;
2155
+ /** The fd of the orphan guard's pipe from `ork`. */
2156
+ const WATCH_FD = 4;
2101
2157
  /**
2102
- * Runs `$0 "$@"`, then writes the shell's `times` to fd 3: its children's user
2103
- * and system time, the program's whole tree that it waited for. A program that
2158
+ * Runs `$0 "$@"` beside the orphan guard, which ends the group if `ork` dies,
2159
+ * and stops the guard once the program ends, so its leftovers live on as
2160
+ * before. Then writes the shell's `times` to fd 3: its children's user and
2161
+ * system time, the program's whole tree that it waited for. A program that
2104
2162
  * isn't there says so on fd 3, as spawn's ENOENT would.
2105
2163
  */
2106
- const TIMED = String.raw`command -v "$0" >/dev/null 2>&1 || { echo ork-missing >&3; exit 127; }; "$0" "$@" 3>&-; s=$?; times >&3; exit $s`;
2164
+ const TIMED = String.raw`command -v "$0" >/dev/null 2>&1 || { echo ork-missing >&3; exit 127; }; ${orphanGuard(WATCH_FD)}w=$!; "$0" "$@" 3>&- ${String(WATCH_FD)}<&-; s=$?; kill -s KILL $w 2>/dev/null; times >&3; exit $s`;
2107
2165
  const MISSING = "ork-missing";
2108
2166
  /** The children's line of `times` (`0m1.250s 0m0.300s`), in milliseconds. */
2109
2167
  const cpuOfTimes = (text) => {
@@ -2144,10 +2202,12 @@ const spawnTool = (program, args, { cwd, signal, env, priority }) => {
2144
2202
  "ignore",
2145
2203
  "pipe",
2146
2204
  "pipe",
2205
+ "pipe",
2147
2206
  "pipe"
2148
2207
  ]
2149
2208
  });
2150
2209
  lowerChild(priority, child.pid);
2210
+ holdGuard(child.stdio[WATCH_FD]);
2151
2211
  const fd3 = child.stdio[3];
2152
2212
  if (fd3 instanceof Readable) fd3.setEncoding("utf8").on("data", (chunk) => {
2153
2213
  times.push(chunk);