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

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 (50) hide show
  1. package/README.md +10 -4
  2. package/dist/{__build_info__-Bsb1kjgF.mjs → __build_info__-C1Nm0hlJ.mjs} +25 -6
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/{attach-CPespxlQ.mjs → attach-EP374oMP.mjs} +5 -5
  5. package/dist/{attach-BHO3X3cf.mjs → attach-RQx__RzH.mjs} +46 -8
  6. package/dist/bin.mjs +238 -369
  7. package/dist/brief-Bs3cMQU0.mjs +100 -0
  8. package/dist/{builtin-files-2bHf76x-.mjs → builtin-files-yYaNTQRU.mjs} +1 -1
  9. package/dist/command-tree-BkSYWmYJ.mjs +63 -0
  10. package/dist/{completion-CQ2UYKuJ.mjs → completion-DIXWlnAl.mjs} +87 -6
  11. package/dist/{context-BNqDw9z1.mjs → context-Dq-VM_sq.mjs} +3 -3
  12. package/dist/{find-BYLlszqj.mjs → find-k489laBa.mjs} +2 -2
  13. package/dist/flags-B4ZZWspN.mjs +125 -0
  14. package/dist/{follow-ups-B4GJ3Vzw.mjs → follow-ups-DD8NTPe6.mjs} +59 -24
  15. package/dist/{format-C8MsFj8_.mjs → format-DQc9A_ei.mjs} +17 -4
  16. package/dist/{help-groups-CQ98mLjF.mjs → help-groups-hB6kY34d.mjs} +1 -63
  17. package/dist/in-a-run-D_9GlAlS.mjs +27 -0
  18. package/dist/{install-DWtXTq4y.mjs → install-PVlV2QPq.mjs} +1 -1
  19. package/dist/{limits-B1qzS4cX.mjs → limits-CHqBO7wb.mjs} +1 -1
  20. package/dist/{mcp-BOrVkyhN.mjs → mcp-D38Ajb0b.mjs} +6 -6
  21. package/dist/{once-C1fL3GVo.mjs → once-DuUzpzL2.mjs} +3 -3
  22. package/dist/parse-BzWpMN-a.mjs +107 -0
  23. package/dist/{pause-DOMK-R8V.mjs → pause-Bx3ePGrn.mjs} +3 -3
  24. package/dist/{post-BzA6Qobq.mjs → post-2CaJz_mV.mjs} +5 -3
  25. package/dist/print-n2qetUHc.mjs +19 -0
  26. package/dist/project-records-Cune1l6I.mjs +230 -0
  27. package/dist/{relay-OWuu4fib.mjs → relay-D7RN4m68.mjs} +1 -1
  28. package/dist/{run-CiPsW9Kf.mjs → run-BfxdsQJk.mjs} +1 -1
  29. package/dist/{run--4-1xAF2.mjs → run-OOWQIKMJ.mjs} +33 -20
  30. package/dist/{send-CnNNW-ot.mjs → send-CKth_vgn.mjs} +2 -2
  31. package/dist/{src-B9zWIVqo.mjs → src-BHod38UQ.mjs} +104 -72
  32. package/dist/{src-Dr_fTSu-.mjs → src-DLsFsmOD.mjs} +790 -502
  33. package/dist/{src-DQKz2eOH.mjs → src-DOjTY2ET.mjs} +238 -79
  34. package/dist/{src-DcrhgipG.mjs → src-DmmrX2_C.mjs} +162 -58
  35. package/dist/{store-oKJ-cCf3.mjs → store-DACH768X.mjs} +1 -1
  36. package/dist/ui/assets/app-9Q-62Ywz.css +1 -0
  37. package/dist/ui/assets/app-CN3acQ-1.js +111 -0
  38. package/dist/ui/assets/{index-BtwkftKe.js → index-B2uGxA4W.js} +2 -2
  39. package/dist/ui/index.html +1 -1
  40. package/dist/{ui-CzKlsCnm.mjs → ui-CJ-BX12n.mjs} +3 -3
  41. package/dist/{usage-DHGNk2JL.mjs → usage-DxZ6JGd4.mjs} +62 -107
  42. package/dist/{validate-Bs_gg22g.mjs → validate-BS0nCccW.mjs} +35 -8
  43. package/dist/{watch-CCCfw3n7.mjs → watch-Dp7IB4FR.mjs} +5 -5
  44. package/dist/{workflows-CW28ZU-z.mjs → workflows-Djo1edlK.mjs} +2 -2
  45. package/dist/{worktrees-CyE8aF6j.mjs → worktrees-Drof_Nyx.mjs} +49 -20
  46. package/package.json +8 -4
  47. package/dist/brief-za0ozN_E.mjs +0 -58
  48. package/dist/in-a-run-Bp0k0J09.mjs +0 -24
  49. package/dist/ui/assets/app-Bdc6OBeZ.css +0 -1
  50. package/dist/ui/assets/app-Drp8Dm9y.js +0 -111
@@ -1,8 +1,8 @@
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";
1
+ import { $r as beforeOf, At as endedWith, Bn as countedTokens, Dt as runScope, Et as heldTimeout, It as lowerChild, Jn as END_DETAIL_LIMIT, Kn as endOf, Mt as stoppedEnd, Nt as declaredProblem, Ot as calledWorkflows, Pt as slotClock, R as storePath, Rt as startAt, Ti as AGENT_DRIVERS, Xn as endFor, Y as sessionRefusals, _i as INTEGRATIONS, _t as planNodeSession, an as sessionDriver, cn as MCP_FILE, di as isClaudeNode, dr as parseTemplate, ei as programsRunBy, fi as isToolNode, fr as renderTemplate, gi as timeoutMs, ii as markValue, jt as failedWith, ln as mcpNotText, mt as planReason, nn as envReadsOf, qn as outcomeEnd, sn as declaredOutputOf, un as serversIn, ur as evaluate, vi as TOOL_PREFIX, yi as integrationFor, yt as reportSchemaFor } from "./src-DLsFsmOD.mjs";
2
+ import { t as runOnce } from "./run-BfxdsQJk.mjs";
3
3
  import { n as holdGuard, r as orphanGuard, t as KILL_AFTER_MS } from "./orphan-guard-BFKg8VSd.mjs";
4
4
  import path from "node:path";
5
- import { accessSync, constants, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { accessSync, constants, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { spawn } from "node:child_process";
7
7
  import { Readable } from "node:stream";
8
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -60,7 +60,9 @@ const callExecutor = (context) => {
60
60
  // ../nodes/src/claude-plan.ts
61
61
  /**
62
62
  * @file What a Claude node's session is planned with, read from the node's
63
- * context.
63
+ * context, and what it is told beyond its plan: the note of a sent-back input
64
+ * on re-entry, a resumed leader's message, and the turn for messages that
65
+ * came in as it finished.
64
66
  */
65
67
  /** The plan options every session of a node starts from. */
66
68
  const planOptionsOf = (context, inPlace) => {
@@ -83,6 +85,30 @@ const planOptionsOf = (context, inPlace) => {
83
85
  if (context.claims !== void 0) planOptions.claims = context.claims;
84
86
  return planOptions;
85
87
  };
88
+ const MAX_INPUT = 8192;
89
+ const inputJson = (input) => JSON.stringify(input ?? {}, void 0, 2);
90
+ /** What a sent-back input is whole, for `read_input`, when its note cuts it. */
91
+ const wholeInput = (input) => {
92
+ const json = inputJson(input);
93
+ return json.length > MAX_INPUT ? json : void 0;
94
+ };
95
+ /** A re-entry's note of what was sent back; `read_input` has a cut rest. */
96
+ const reentryNote = (round, input) => {
97
+ const json = inputJson(input);
98
+ const cut = json.length > MAX_INPUT;
99
+ const body = cut ? `${json.slice(0, MAX_INPUT)}\n…` : json;
100
+ const rest = cut ? `\n\nThat is the first ${String(MAX_INPUT)} of its ${String(json.length)} characters. Call read_input for all of it before you act on it.` : "";
101
+ return `\n\n## Round ${String(round)}\n\nThe workflow sent this task back to you with:\n\n\`\`\`json\n${body}\n\`\`\`${rest}`;
102
+ };
103
+ /**
104
+ * What a resumed leader is told: it already has its brief and its earlier work,
105
+ * so only the new round, and the new task when its owner sent one.
106
+ */
107
+ const resumeMessage = (round, input, brief) => [brief === void 0 ? sentBack(round, input) : `## New task from \`${brief.from}\`\n\n${brief.text}`, "End with a new structured report."].join("\n\n");
108
+ /** A first-round resume is a session the run was cut off in (`ork resume`). */
109
+ const sentBack = (round, input) => round === 1 ? "Ork was stopped while you worked on this task, and the run is resumed. Your earlier turns are above: check what you finished, and carry on from there." : `The workflow sent your task back to you.${reentryNote(round, input)}`;
110
+ /** What a session is told when messages came in as it finished. */
111
+ const lateMessage = (count) => `[ork] ${String(count)} message${count === 1 ? "" : "s"} came in while you finished. Call read_inbox, act on ${count === 1 ? "it" : "them"} if needed, and end with a new structured report.`;
86
112
  // ../nodes/src/claude-result.ts
87
113
  /**
88
114
  * @file A finished session as a node's result: its report is the output, held
@@ -552,12 +578,13 @@ const leftOf = (caps, used, quotaLeft) => least(...caps.map((cap) => cap === voi
552
578
  * budget has counted. The quota has: the ledger took it as the session
553
579
  * finished, so what it says is left is left. The run's tokens left have too:
554
580
  * only the node's own `budgetTokens` takes `tokens` off. `null` when nothing
555
- * is, and the turn does not start.
581
+ * is, and the turn does not start. `turnsAtLeast` lifts the turns left to that
582
+ * many (a failed check's resume: `CHECK_RETRY_TURNS`).
556
583
  */
557
- const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}) => {
584
+ const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}, turnsAtLeast = 0) => {
558
585
  const budgetUsd = leftOf([spec.budgetUsd, runLeft], used.spent, quotaLeft);
559
586
  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;
587
+ const maxTurns = spec.maxTurns === void 0 ? void 0 : Math.max(spec.maxTurns - used.turns, turnsAtLeast);
561
588
  if ([
562
589
  budgetUsd,
563
590
  budgetTokens,
@@ -569,18 +596,36 @@ const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}) => {
569
596
  ...maxTurns === void 0 ? {} : { maxTurns }
570
597
  };
571
598
  };
599
+ const reached = (cap, used) => cap !== void 0 && used >= cap;
600
+ /**
601
+ * Why the run's budget stopped a session that reached its cap, if it did. A
602
+ * unit blames the run when the session reached it, or when it is the only cap
603
+ * the session had; otherwise the node's own cap in the other unit may have
604
+ * stopped it. A resumed turn's dollars are its own: its cost is the session's
605
+ * total (ADR-046) while its cap is what was left, so `priorCostUsd` comes off,
606
+ * read as `ownSpend` reads it (a lower total counted again from zero).
607
+ */
608
+ const runSpent = (run, asked, outcome, priorCostUsd) => {
609
+ const total = outcome.usage.costUsd;
610
+ const turnCostUsd = total < priorCostUsd ? total : total - priorCostUsd;
611
+ if (run.tokens !== void 0 && (reached(asked.budgetTokens, countedTokens(outcome.usage)) || asked.budgetUsd === void 0)) return `the run's budget of ${String(run.tokens)} tokens is spent`;
612
+ return run.usd !== void 0 && (reached(asked.budgetUsd, turnCostUsd) || asked.budgetTokens === void 0) ? `the run's budget of $${String(run.usd)} is spent` : void 0;
613
+ };
572
614
  /**
573
615
  * Whether a limit across runs, rather than the node's work, ended a node: a
574
616
  * session whose cap was a quota's and that ran into it, or one its plan refused
575
- * and that gave no report. A node that still succeeded is left alone.
617
+ * and that gave no report. A session whose cap was the run's budget and that
618
+ * ran into it stops the run as `budget_exhausted` (`run`, from `runCapsOf`). A
619
+ * node that still succeeded is left alone.
576
620
  */
577
- const limitWatch = (context) => {
621
+ const limitWatch = (context, run = {}) => {
578
622
  let why;
623
+ let runWhy;
579
624
  return {
580
625
  stoppedBy: (reason) => {
581
626
  why = reason;
582
627
  },
583
- session: (asked) => {
628
+ session: (asked, priorCostUsd = 0) => {
584
629
  const { leftUsd: left, leftTokens } = context.quota?.() ?? {};
585
630
  let refused;
586
631
  return {
@@ -592,6 +637,7 @@ const limitWatch = (context) => {
592
637
  const capped = left !== void 0 && asked.budgetUsd !== void 0 && asked.budgetUsd >= left || leftTokens !== void 0 && asked.budgetTokens !== void 0 && asked.budgetTokens >= leftTokens;
593
638
  const spent = outcome !== null && outcomeEnd(outcome).kind === "budget_limit";
594
639
  if (capped && spent) why = context.quota?.().blocked ?? "a session reached what was left of a quota";
640
+ else if (spent) runWhy = runSpent(run, asked, outcome, priorCostUsd);
595
641
  }
596
642
  };
597
643
  },
@@ -601,6 +647,11 @@ const limitWatch = (context) => {
601
647
  ...endedWith(result, endFor("plan_limit", {}, why)),
602
648
  limited: why
603
649
  };
650
+ if (runWhy !== void 0) return {
651
+ ...result,
652
+ limited: runWhy,
653
+ stopsAs: "budget_exhausted"
654
+ };
604
655
  const { end } = result;
605
656
  return end?.kind === "plan_limit" ? {
606
657
  ...result,
@@ -678,9 +729,31 @@ const startWith = async (context, left, own, reserve) => {
678
729
  * `budgetUsd`. With nothing left, it waits for a worker running beside it to
679
730
  * finish, as long as one may.
680
731
  */
681
- const budgetToStart = (context) => startWith(context, () => budgetNow(context), isClaudeNode(context.node) ? context.node.budgetUsd ?? context.workflow.defaults?.budgetUsd : void 0, context.reserveBudget);
732
+ const budgetToStart = (context) => startWith(context, () => budgetNow(context), ownCaps(context).usd, context.reserveBudget);
733
+ /** The node's own caps: its `budgetUsd` and `budgetTokens`, else the defaults'. */
734
+ const ownCaps = (context) => isClaudeNode(context.node) ? {
735
+ usd: context.node.budgetUsd ?? context.workflow.defaults?.budgetUsd,
736
+ tokens: context.node.budgetTokens ?? context.workflow.defaults?.budgetTokens
737
+ } : {};
738
+ /**
739
+ * Where the run's budget, not the node's own cap, caps the node's first
740
+ * session: the run has a budget in that unit, and what `startBudgets` left the
741
+ * session is below the node's own cap, or it has none. A later turn of the
742
+ * session keeps them: both its caps go down by what it used, and the run's by
743
+ * what others used too.
744
+ */
745
+ const runCapsOf = (context, planOptions) => {
746
+ const own = ownCaps(context);
747
+ const limits = context.workflow.limits;
748
+ const caps = {};
749
+ const usd = planOptions.budgetLeftUsd;
750
+ if (context.budgetLeftNow?.() !== void 0 && limits?.budgetUsd !== void 0 && usd !== void 0 && usd < (own.usd ?? Infinity)) caps.usd = limits.budgetUsd;
751
+ const tokens = planOptions.budgetLeftTokens;
752
+ if (context.tokensLeftNow?.() !== void 0 && limits?.budgetTokens !== void 0 && tokens !== void 0 && tokens < (own.tokens ?? Infinity)) caps.tokens = limits.budgetTokens;
753
+ return caps;
754
+ };
682
755
  /** `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);
756
+ const tokensToStart = (context) => startWith(context, () => tokensNow(context), ownCaps(context).tokens, context.reserveTokens);
684
757
  /**
685
758
  * What a node's first session may spend, in dollars and in counted tokens, set
686
759
  * into `planOptions`; the node's result instead when nothing is left of
@@ -1027,6 +1100,62 @@ const skippedCheck = (outputFile) => {
1027
1100
  }
1028
1101
  }, endFor("done"));
1029
1102
  };
1103
+ // ../nodes/src/run-marker.ts
1104
+ /**
1105
+ * @file The marker a run leaves on its sessions and programs (ADR-047), and the
1106
+ * stores it guards: the commands that act as the user refuse in the stores of
1107
+ * the runs that started a process, and everywhere under a marker that lists
1108
+ * none, which an older `ork` sets.
1109
+ */
1110
+ /** Set for a program in a run. */
1111
+ const RUN_ENV = "ORK_RUN";
1112
+ /** Set for a Claude session. It says no more than that: no run, no node. */
1113
+ const SESSION_ENV = "ORK_SESSION";
1114
+ /**
1115
+ * The stores whose runs started a session or program, as a JSON array of real
1116
+ * paths: the commands that act as the user refuse only in these.
1117
+ */
1118
+ const GUARDED_STORES_ENV = "ORK_GUARDED_STORES";
1119
+ /**
1120
+ * The variable that says a program in a run or a session started this process,
1121
+ * or `null` when none does.
1122
+ */
1123
+ const runMarkerIn = (env) => ["ORK_RUN", "ORK_SESSION"].find((name) => env[name] !== void 0) ?? null;
1124
+ /** The stores `env` lists, or `undefined` when it lists none or garbles it. */
1125
+ const listIn = (env) => {
1126
+ const text = env[GUARDED_STORES_ENV];
1127
+ if (text === void 0) return void 0;
1128
+ try {
1129
+ const value = JSON.parse(text);
1130
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
1131
+ } catch {
1132
+ return;
1133
+ }
1134
+ };
1135
+ /** A store file's real path, so a symlinked `~/.ork` is the same store. */
1136
+ const realStore = (store) => {
1137
+ try {
1138
+ return realpathSync(store);
1139
+ } catch {
1140
+ return path.resolve(store);
1141
+ }
1142
+ };
1143
+ /**
1144
+ * The stores a run that started this process is in: `null` when no run did, and
1145
+ * `"all"` when one did but listed none (an older `ork`), which fails closed.
1146
+ */
1147
+ const guardedStoresIn = (env) => runMarkerIn(env) === null ? null : listIn(env) ?? "all";
1148
+ /** Whether a run in `store` (a store file) started this process. */
1149
+ const storeGuarded = (env, store) => {
1150
+ const guarded = guardedStoresIn(env);
1151
+ return guarded === "all" || guarded !== null && guarded.includes(realStore(store));
1152
+ };
1153
+ /**
1154
+ * What a run in `store` (default: the user's) tells its sessions and programs:
1155
+ * the stores it inherited, and its own. Nothing when it inherited "all", so
1156
+ * that holds.
1157
+ */
1158
+ const guardOf = (env, store = storePath()) => guardedStoresIn(env) === "all" ? {} : { [GUARDED_STORES_ENV]: JSON.stringify([.../* @__PURE__ */ new Set([...listIn(env) ?? [], realStore(store)])]) };
1030
1159
  // ../nodes/src/tool-scrub.ts
1031
1160
  /**
1032
1161
  * @file Masking a secret in what a program prints. A plain search, so a value
@@ -1190,16 +1319,13 @@ const scrubberOf = (values) => {
1190
1319
  const OUTPUT_ENV = "ORK_OUTPUT";
1191
1320
  /** A file a `watch:` node's program keeps between looks (RFC-011). */
1192
1321
  const STATE_ENV = "ORK_STATE";
1193
- /** Set for a program in a run. */
1194
- const RUN_ENV = "ORK_RUN";
1195
- /** Set for a Claude session. It says no more than that: no run, no node. */
1196
- const SESSION_ENV = "ORK_SESSION";
1197
1322
  /** The variables a caller's run reads, for a run it calls to mask too. */
1198
1323
  const MASKED_ENV = "ORK_MASKED";
1199
1324
  /** Every variable Ork sets itself; a workflow's `env:` cannot set them. */
1200
1325
  const IDENTITY_KEYS = /* @__PURE__ */ new Set([
1201
1326
  RUN_ENV,
1202
1327
  SESSION_ENV,
1328
+ GUARDED_STORES_ENV,
1203
1329
  MASKED_ENV,
1204
1330
  "ORK_NODE",
1205
1331
  "ORK_ROUND",
@@ -1218,20 +1344,21 @@ const IDENTITY_KEYS = /* @__PURE__ */ new Set([
1218
1344
  */
1219
1345
  const withoutIdentity = (env) => Object.fromEntries(Object.entries(env).flatMap(([key, value]) => value === void 0 || IDENTITY_KEYS.has(key) ? [] : [[key, value]]));
1220
1346
  /**
1221
- * What a Claude session runs with: no identity, only the marker that stops its
1222
- * Bash from speaking as the user through `ork chat`.
1347
+ * What a Claude session of a run in `store` (default: the user's) runs with: no
1348
+ * identity, only the marker that stops its Bash from speaking as the user
1349
+ * through `ork chat`, and the stores it must not do that in.
1223
1350
  */
1224
- const sessionEnv = (env) => ({
1351
+ const sessionEnv = (env, store) => ({
1225
1352
  ...withoutIdentity(env),
1226
- [SESSION_ENV]: "1"
1353
+ [SESSION_ENV]: "1",
1354
+ ...guardOf(env, store)
1227
1355
  });
1228
1356
  /**
1229
- * The variable that says a program in a run or a session started this process,
1230
- * or `null` when none does.
1357
+ * Who a node's program is, and the stores its run guards (none listed without
1358
+ * `store`, which guards every one). It is attribution, not a boundary.
1231
1359
  */
1232
- const runMarkerIn = (env) => ["ORK_RUN", SESSION_ENV].find((name) => env[name] !== void 0) ?? null;
1233
- /** Who a node's program is. It is attribution, not a security boundary. */
1234
- const identityOf = (context, orkBin) => ({
1360
+ const identityOf = (context, orkBin, environ = {}, store) => ({
1361
+ ...store === void 0 ? {} : guardOf(environ, store),
1235
1362
  [RUN_ENV]: context.runId,
1236
1363
  ORK_NODE: context.id,
1237
1364
  ORK_ROUND: String(context.round),
@@ -1453,7 +1580,7 @@ const toolExecutor = (given, options = {}) => async (context) => {
1453
1580
  const outputFile = node.output?.format === "json" ? prepareOutputFile(context.runDir, context.id, context.round) : void 0;
1454
1581
  const env = {
1455
1582
  ...nodeEnv.values,
1456
- ...identityOf(context, options.orkBin),
1583
+ ...identityOf(context, options.orkBin, environ, options.store),
1457
1584
  ...maskedEnv(namesToMask(context.workflow, environ, options.maskedNames)),
1458
1585
  ...outputFile === void 0 ? {} : { [OUTPUT_ENV]: outputFile },
1459
1586
  ...node.watch === void 0 ? {} : { [STATE_ENV]: prepareStateFile(context.runDir, context.id) }
@@ -1730,30 +1857,6 @@ const checkedResult = (result, { check, resumes }) => {
1730
1857
  * through the session's ports, and resumes its session on re-entry
1731
1858
  * (ADR-019).
1732
1859
  */
1733
- const MAX_INPUT = 8192;
1734
- const inputJson = (input) => JSON.stringify(input ?? {}, void 0, 2);
1735
- /** What a sent-back input is whole, for `read_input`, when its note cuts it. */
1736
- const wholeInput = (input) => {
1737
- const json = inputJson(input);
1738
- return json.length > MAX_INPUT ? json : void 0;
1739
- };
1740
- /** A re-entry's note of what was sent back; `read_input` has a cut rest. */
1741
- const reentryNote = (round, input) => {
1742
- const json = inputJson(input);
1743
- const cut = json.length > MAX_INPUT;
1744
- const body = cut ? `${json.slice(0, MAX_INPUT)}\n…` : json;
1745
- const rest = cut ? `\n\nThat is the first ${String(MAX_INPUT)} of its ${String(json.length)} characters. Call read_input for all of it before you act on it.` : "";
1746
- return `\n\n## Round ${String(round)}\n\nThe workflow sent this task back to you with:\n\n\`\`\`json\n${body}\n\`\`\`${rest}`;
1747
- };
1748
- /**
1749
- * What a resumed leader is told: it already has its brief and its earlier work,
1750
- * so only the new round, and the new task when its owner sent one.
1751
- */
1752
- const resumeMessage = (round, input, brief) => [brief === void 0 ? sentBack(round, input) : `## New task from \`${brief.from}\`\n\n${brief.text}`, "End with a new structured report."].join("\n\n");
1753
- /** A first-round resume is a session the run was cut off in (`ork resume`). */
1754
- const sentBack = (round, input) => round === 1 ? "Ork was stopped while you worked on this task, and the run is resumed. Your earlier turns are above: check what you finished, and carry on from there." : `The workflow sent your task back to you.${reentryNote(round, input)}`;
1755
- /** What a session is told when messages came in as it finished. */
1756
- const lateMessage = (count) => `[ork] ${String(count)} message${count === 1 ? "" : "s"} came in while you finished. Call read_inbox, act on ${count === 1 ? "it" : "them"} if needed, and end with a new structured report.`;
1757
1860
  /** A re-entered leader resumes; anyone else starts with the full brief. */
1758
1861
  const specFor = (spec, context) => {
1759
1862
  if (context.resume !== void 0) return {
@@ -1820,9 +1923,9 @@ const claudeExecutor = (options) => async (context) => {
1820
1923
  if (context.followUps !== void 0) ports.followUps = context.followUps;
1821
1924
  const whole = context.round > 1 ? wholeInput(context.input) : void 0;
1822
1925
  if (whole !== void 0) ports.input = whole;
1823
- const watch = limitWatch(context);
1824
- const run = async (asked) => {
1825
- const watching = watch.session(asked);
1926
+ const watch = limitWatch(context, runCapsOf(context, planOptions));
1927
+ const run = async (asked, prior = 0) => {
1928
+ const watching = watch.session(asked, prior);
1826
1929
  context.reserveBudget?.(asked.budgetUsd);
1827
1930
  context.reserveTokens?.(asked.budgetTokens);
1828
1931
  const session = runtime.start(asked, ports);
@@ -1851,7 +1954,7 @@ const claudeExecutor = (options) => async (context) => {
1851
1954
  return outcome;
1852
1955
  };
1853
1956
  const first = await run(spec);
1854
- const limitsAfter = ({ sessionId, usage }) => {
1957
+ const limitsAfter = ({ sessionId, usage }, turnsAtLeast = 0) => {
1855
1958
  const quota = context.quota?.();
1856
1959
  if (quota?.blocked !== void 0) {
1857
1960
  watch.stoppedBy(quota.blocked);
@@ -1864,14 +1967,14 @@ const claudeExecutor = (options) => async (context) => {
1864
1967
  }, context.budgetLeftNow?.(), quota?.leftUsd, {
1865
1968
  run: context.tokensLeftNow?.(),
1866
1969
  quota: quota?.leftTokens
1867
- });
1970
+ }, turnsAtLeast);
1868
1971
  };
1869
1972
  const resumeWith = (outcome, prompt, limits) => run({
1870
1973
  ...spec,
1871
1974
  ...limits,
1872
1975
  resume: outcome.sessionId,
1873
1976
  prompt
1874
- });
1977
+ }, outcome.usage.costUsd);
1875
1978
  const lateTurn = (current) => {
1876
1979
  const late = context.mailbox?.pending() ?? 0;
1877
1980
  const limits = limitsAfter(current);
@@ -1897,7 +2000,7 @@ const claudeExecutor = (options) => async (context) => {
1897
2000
  },
1898
2001
  late: lateTurn,
1899
2002
  resume: async (current, check) => {
1900
- const limits = limitsAfter(current);
2003
+ const limits = limitsAfter(current, 20);
1901
2004
  if (limits === null) return null;
1902
2005
  await context.hold?.wait("start", context.signal);
1903
2006
  return context.signal.aborted ? null : resumeWith(current, checkFailedMessage(check, {
@@ -2271,6 +2374,7 @@ const spawnTool = (program, args, { cwd, signal, env, priority }) => {
2271
2374
  const builtinExecutors = (options) => {
2272
2375
  const tool = toolExecutor(options.tools ?? spawnTool, {
2273
2376
  ...options.orkBin === void 0 ? {} : { orkBin: options.orkBin },
2377
+ ...options.store === void 0 ? {} : { store: options.store },
2274
2378
  ...options.maskedNames === void 0 ? {} : { maskedNames: options.maskedNames }
2275
2379
  });
2276
2380
  const claude = claudeExecutor({
@@ -2411,4 +2515,4 @@ const missingPrograms = (workflow, installed = (program) => onPath(program)) =>
2411
2515
  }
2412
2516
  return [...missing.values()];
2413
2517
  };
2414
- export { namedMcpEntries as _, builtinExecutors as a, toolExecutor as c, maskedEnv as d, maskedNamesOfRun as f, clearOutputFiles as g, sessionEnv as h, onPath as i, clearStateFiles as l, runScrubber as m, missingProgramMessage as n, spawnTool as o, runMarkerIn as p, missingPrograms as r, mcpCallExecutor as s, envProblems as t, inheritedScrubber as u, resultOf as v };
2518
+ export { clearOutputFiles as _, builtinExecutors as a, toolExecutor as c, maskedEnv as d, maskedNamesOfRun as f, storeGuarded as g, runMarkerIn as h, onPath as i, clearStateFiles as l, sessionEnv as m, missingProgramMessage as n, spawnTool as o, runScrubber as p, missingPrograms as r, mcpCallExecutor as s, envProblems as t, inheritedScrubber as u, namedMcpEntries as v, resultOf as y };
@@ -1,4 +1,4 @@
1
- import { U as openStore } from "./src-Dr_fTSu-.mjs";
1
+ import { L as openStore } from "./src-DLsFsmOD.mjs";
2
2
  // src/store.ts
3
3
  /**
4
4
  * @file Opening the user's store (`~/.ork/state.db`) for the length of one