@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,13 +1,13 @@
1
- import { $ as resolveInputs, $n as createFeedback, $t as sessionDriver, At as walkSummary, Cn as liveStatus, Er as placeFiles, Fn as gitRulesOf, Gr as beforeOf, In as loadProject, Ln as machineRulesOf, Mr as inside, Nt as userMessageText, On as templateOf, Q as parseInputPairs, Rn as createSignals, Tt as planNodeSession, U as openStore, X as createReplay, Y as inspectProject, Yn as runGh, Z as replayLoopAnswers, _i as AGENT_DRIVERS, an as createFollowUps, ar as renderTemplate, cr as socketPath, dn as deliverFollowUp, dr as maskReplacer, dt as createCalls, ft as adoptedCalls, gn as endLinksOf, gt as detachRun, ht as DETACHED_ENV, ir as parseTemplate, jt as startRun, kr as runDirOf, m as resolveBase, mi as readyDriver, n as resumedOptions, on as checkFollowUpsDue, qt as git, ri as isClaudeNode, si as retiredDriverHint, sr as serveRun, st as createRelay, t as createWorkspace, un as takeFollowUps, vt as NOT_STARTED, zn as endOf } from "./src-Dr_fTSu-.mjs";
2
- import { a as formatEvent, c as createTerminalHuman, o as formatRunEvent, s as callsShown, t as attachRun, u as createDecisionQueue } from "./attach-BHO3X3cf.mjs";
3
- import { h as nodeSession, n as ORK_VERSION, v as findFollowUp } from "./__build_info__-Bsb1kjgF.mjs";
4
- import { n as newRunId } from "./find-BYLlszqj.mjs";
1
+ import { $r as beforeOf, $t as git, An as liveStatus, Br as inside, Cn as endLinksOf, Ct as startRun, Ei as DRIVER_IDS, Fn as templateOf, G as parseInputPairs, Gn as createSignals, Hn as gitRulesOf, K as resolveInputs, Kn as endOf, L as openStore, Lr as runDirOf, Pr as placeFiles, Qt as inspectProject, R as storePath, Si as readyDriver, St as walkSummary, Ti as AGENT_DRIVERS, Tt as userMessageText, U as createReplay, Un as loadProject, W as replayLoopAnswers, Wn as machineRulesOf, _n as takeFollowUps, _t as planNodeSession, an as sessionDriver, at as adoptedCalls, ct as DETACHED_ENV, di as isClaudeNode, dr as parseTemplate, dt as NOT_STARTED, et as createRelay, fn as createFollowUps, fr as renderTemplate, hi as retiredDriverHint, hr as socketPath, it as createCalls, lt as detachRun, m as resolveBase, mr as serveRun, n as resumedOptions, nr as runGh, or as createFeedback, pn as checkFollowUpsDue, t as createWorkspace, vn as deliverFollowUp, vr as maskReplacer } from "./src-DLsFsmOD.mjs";
2
+ import { c as callsShown, d as createDecisionQueue, l as createTerminalHuman, o as formatEvent, s as formatRunEvent, t as attachRun } from "./attach-RQx__RzH.mjs";
3
+ import { h as nodeSession, n as ORK_VERSION, v as findFollowUp } from "./__build_info__-C1Nm0hlJ.mjs";
4
+ import { n as newRunId } from "./find-k489laBa.mjs";
5
5
  import { c as write, i as detachHint, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
6
- import { _ as namedMcpEntries, a as builtinExecutors, d as maskedEnv, f as maskedNamesOfRun, g as clearOutputFiles, h as sessionEnv, i as onPath, l as clearStateFiles, m as runScrubber, n as missingProgramMessage, r as missingPrograms, t as envProblems, v as resultOf } from "./src-DcrhgipG.mjs";
7
- import { i as refusedBuiltin, o as workflowArg } from "./workflows-CW28ZU-z.mjs";
8
- import { a as formatReport, c as checksHere, d as unfitLines, i as walkLoaded, l as chooseReadyDriver, o as warningLines, s as budgetWarningLines, t as loadWorkflow, u as runReadyDriver } from "./validate-Bs_gg22g.mjs";
6
+ import { _ as clearOutputFiles, a as builtinExecutors, d as maskedEnv, f as maskedNamesOfRun, i as onPath, l as clearStateFiles, m as sessionEnv, n as missingProgramMessage, p as runScrubber, r as missingPrograms, t as envProblems, v as namedMcpEntries, y as resultOf } from "./src-DmmrX2_C.mjs";
7
+ import { i as refusedBuiltin, o as workflowArg } from "./workflows-Djo1edlK.mjs";
8
+ import { a as formatReport, c as checksHere, d as runReadyDriver, f as unfitLines, i as walkLoaded, l as chooseReadyDriver, o as warningLines, s as budgetWarningLines, t as loadWorkflow, u as overrideRefusal } from "./validate-BS0nCccW.mjs";
9
9
  import { a as createView, o as styler } from "./verdict-CMvPCpWC.mjs";
10
- import { i as runLimits, r as limitsRefusal, t as chatNotice } from "./limits-B1qzS4cX.mjs";
10
+ import { i as runLimits, r as limitsRefusal, t as chatNotice } from "./limits-CHqBO7wb.mjs";
11
11
  import path from "node:path";
12
12
  import { appendFileSync, existsSync, linkSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
13
13
  import { createHash, randomUUID } from "node:crypto";
@@ -478,9 +478,17 @@ const followUpSink = (options) => (input, at) => {
478
478
  ...options.gh === void 0 ? {} : { gh: options.gh }
479
479
  });
480
480
  };
481
+ /** Gh's own words when it has no login. */
482
+ const SIGNED_OUT = /not logged in|gh auth login/iu;
483
+ /**
484
+ * A gh error's first line, without a trailing period, so the notice is one
485
+ * line.
486
+ */
487
+ const oneLine = (error) => (error.trim().split("\n")[0] ?? "").trim().replace(/\.$/u, "");
481
488
  /**
482
489
  * Moves the project's follow-ups whose time has come to due, as every `ork
483
- * follow-ups` call does first. `gh` failing only warns, through `warn`.
490
+ * follow-ups` call does first. `gh` failing only warns, through `warn`: one
491
+ * line per call, however many refs failed.
484
492
  */
485
493
  const refreshDue = async (store, project, warn, gh = runGh) => {
486
494
  try {
@@ -489,7 +497,8 @@ const refreshDue = async (store, project, warn, gh = runGh) => {
489
497
  project,
490
498
  gh
491
499
  });
492
- for (const error of checked.errors) warn(`couldn't check a ref: ${error}`);
500
+ const [first] = checked.errors;
501
+ if (first !== void 0) warn(checked.errors.some((error) => SIGNED_OUT.test(error)) ? "Can't check PRs: `gh` isn't signed in. After-merge follow-ups stay waiting; run `gh auth login` yourself." : `Can't check PRs: ${oneLine(first)}. After-merge follow-ups stay waiting.`);
493
502
  return checked.due;
494
503
  } catch (error) {
495
504
  warn(`couldn't check what is due: ${error instanceof Error ? error.message : String(error)}`);
@@ -610,6 +619,10 @@ const runNode = async (options) => {
610
619
  named: plan.driver,
611
620
  pinned: plan.pinned
612
621
  });
622
+ if (options.driver !== void 0 && named === options.driver) {
623
+ const refusal = await overrideRefusal(options.driver, options.driverChecks);
624
+ if (refusal.length > 0) return failed(refusal);
625
+ }
613
626
  const ready = named === void 0 ? chooseReadyDriver([options.nodeId], true, await (options.driverChecks ?? checksHere)()) : null;
614
627
  if (ready?.ok === false) return failed(ready.lines);
615
628
  if (ready?.line !== void 0) options.err(ready.line);
@@ -1149,7 +1162,8 @@ const runWorkflowFile = async (options) => {
1149
1162
  const executorOptions = {
1150
1163
  runtimeFor: (driver) => options.runtime(driver, placeFiles(place).runs),
1151
1164
  inPlace: options.inPlace,
1152
- maskedNames
1165
+ maskedNames,
1166
+ store: storePath(options.home)
1153
1167
  };
1154
1168
  if (options.driver !== void 0) executorOptions.driver = options.driver;
1155
1169
  if (ready.readyDriver !== void 0) executorOptions.readyDriver = ready.readyDriver;
@@ -1238,7 +1252,7 @@ const isDriver = (value) => AGENT_DRIVERS.some((d) => d.id === value);
1238
1252
  /** Why `--driver` refuses a value: a retired id names its replacement. */
1239
1253
  const badDriver = (value) => `--${retiredDriverHint(value) ?? `driver must be one of ${AGENT_DRIVERS.map((d) => d.id).join(", ")}, got \`${value}\``}`;
1240
1254
  /** Loads the drivers only when a run starts, so other commands stay fast. */
1241
- const loadRuntime = async (driver, runs) => (await import("./src-B9zWIVqo.mjs")).createRuntime(driver, sessionEnv(process.env), runs);
1255
+ const loadRuntime = async (driver, runs) => (await import("./src-BHod38UQ.mjs")).createRuntime(driver, sessionEnv(process.env), runs);
1242
1256
  /**
1243
1257
  * Ctrl-C asks the run to stop, and so does a terminal closing or a polite kill,
1244
1258
  * so that the run ends tidily and leaves no result unscrubbed.
@@ -1289,11 +1303,11 @@ const run = defineCommand({
1289
1303
  },
1290
1304
  "follow-ups": {
1291
1305
  type: "string",
1292
- description: "Follow-up ids an agent does (repeatable or comma-separated): they become the run's brief, done when it publishes"
1306
+ description: "Follow-up ids an agent does, or `due` for every due one (repeatable or comma-separated): they become the run's brief, done when it publishes"
1293
1307
  },
1294
1308
  driver: {
1295
1309
  type: "string",
1296
- description: "claude-code (default: the driver ready here)"
1310
+ description: `${DRIVER_IDS.join(", ")} (default: the driver ready here)`
1297
1311
  },
1298
1312
  "in-place": {
1299
1313
  type: "boolean",
@@ -1360,8 +1374,9 @@ const run = defineCommand({
1360
1374
  process.exitCode = 1;
1361
1375
  return;
1362
1376
  }
1377
+ const { prepareFollowUps: prepare, followUpsFlagFine: flagFine } = await import("./brief-Bs3cMQU0.mjs");
1363
1378
  const cwd = process.cwd();
1364
- if (refusedBuiltin([args.workflow], cwd)) return;
1379
+ if (refusedBuiltin([args.workflow], cwd) || !flagFine(rawArgs)) return;
1365
1380
  const target = workflowArg(args.workflow, cwd);
1366
1381
  const store = openStore();
1367
1382
  if (args.detach) {
@@ -1393,9 +1408,8 @@ const run = defineCommand({
1393
1408
  }
1394
1409
  return;
1395
1410
  }
1396
- const { prepareFollowUps } = await import("./brief-za0ozN_E.mjs");
1397
- const taking = prepareFollowUps(store, rawArgs, args["run-id"], target);
1398
- if (taking === void 0) {
1411
+ const taken = await prepare(store, rawArgs, args["run-id"], target);
1412
+ if (taken === void 0) {
1399
1413
  store.close();
1400
1414
  return;
1401
1415
  }
@@ -1403,7 +1417,7 @@ const run = defineCommand({
1403
1417
  const common = {
1404
1418
  file: target.file,
1405
1419
  cwd,
1406
- inputs: taking.inputs,
1420
+ ...taken,
1407
1421
  inPlace: args["in-place"],
1408
1422
  ...args["machine-claude"] ? { machineClaude: true } : {},
1409
1423
  json: args.json,
@@ -1411,7 +1425,6 @@ const run = defineCommand({
1411
1425
  runtime: loadRuntime,
1412
1426
  store,
1413
1427
  ...human === void 0 ? {} : { human },
1414
- ...taking.runId === void 0 ? {} : { runId: taking.runId },
1415
1428
  out: write,
1416
1429
  err: writeErr,
1417
1430
  ...args.driver === void 0 ? {} : { driver: args.driver }
@@ -1,6 +1,6 @@
1
- import { U as openStore, at as scriptRefusal, it as TOO_LONG, ot as sendFromScript } from "./src-Dr_fTSu-.mjs";
1
+ import { $ as sendFromScript, L as openStore, Q as scriptRefusal, Z as TOO_LONG } from "./src-DLsFsmOD.mjs";
2
2
  import { l as writeErr } from "./output-B1kwo0YU.mjs";
3
- import { u as inheritedScrubber } from "./src-DcrhgipG.mjs";
3
+ import { u as inheritedScrubber } from "./src-DmmrX2_C.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  // src/commands/send.ts
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { Bn as outcomeEnd, Et as parseReport, Gn as FEEDBACK_SEVERITIES, Gt as programFound, Hn as END_KINDS, Kn as FEEDBACK_TARGETS, Kt as startAt, Nn as countedTokens, Un as endFor, Wn as FEEDBACK_KINDS, Wt as lowerChild, _i as AGENT_DRIVERS, fr as maskSecrets, ur as MASK } from "./src-Dr_fTSu-.mjs";
1
+ import { $n as FEEDBACK_TARGETS, Bn as countedTokens, It as lowerChild, Lt as programFound, Qn as FEEDBACK_SEVERITIES, Rt as startAt, Ti as AGENT_DRIVERS, Xn as endFor, Yn as END_KINDS, Zn as FEEDBACK_KINDS, _r as MASK, dn as FOLLOW_UP_KINDS, qn as outcomeEnd, vt as parseReport, yr as maskSecrets } from "./src-DLsFsmOD.mjs";
2
2
  import { n as holdGuard, r as orphanGuard } from "./orphan-guard-BFKg8VSd.mjs";
3
3
  import path from "node:path";
4
4
  import { z } from "zod";
@@ -9,13 +9,6 @@ import { createInterface } from "node:readline";
9
9
  import { execFile, spawn } from "node:child_process";
10
10
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
11
  import { createServer } from "node:http";
12
- // ../core/src/follow-ups/types.ts
13
- const FOLLOW_UP_KINDS = [
14
- "change",
15
- "message",
16
- "task",
17
- "check"
18
- ];
19
12
  // ../runtime/src/hold.ts
20
13
  /**
21
14
  * Waits at a tool call while `hold` is held: "go" once it is released within
@@ -456,8 +449,10 @@ const classifyProviderError = (text, status) => {
456
449
  * it can see (a turn cap, an exit code, a provider's error), and the report
457
450
  * the session handed in decides the rest. The runner then writes the end in
458
451
  * the node's numbers, its detail scrubbed with the feedback redactor. A
459
- * session the driver could not run ends as `driverFailure` says, and one that
460
- * 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.
461
456
  */
462
457
  /** The end a report gives: done, a reported failure, or none handed in. */
463
458
  const reportEnd = (report) => {
@@ -482,9 +477,15 @@ const errorEnd = (text, fallback = "unknown", status) => endFor(classifyProvider
482
477
  */
483
478
  const settleEnd = (outcome, facts, scrub) => {
484
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
+ };
485
486
  return {
486
487
  ...outcome,
487
- end: endFor(end.kind, facts, end.detail, scrub)
488
+ end: endFor(end.kind, used, end.detail, scrub)
488
489
  };
489
490
  };
490
491
  /**
@@ -517,6 +518,17 @@ const tokenCapOutcome = ({ budgetTokens }, usage, sessionId) => budgetTokens ===
517
518
  usage,
518
519
  errors: [`Reached the token budget (${String(budgetTokens)})`]
519
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
+ };
520
532
  /**
521
533
  * The kind of a failure to run the driver: its program missing, else what a
522
534
  * provider's words in it say, else a crash.
@@ -527,27 +539,32 @@ const failureKind = (error) => {
527
539
  if (/\bENOENT\b/u.test(text)) return "tool_missing";
528
540
  return classifyProviderError(text)?.kind ?? "driver_crashed";
529
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
+ };
530
554
  /**
531
555
  * A finished event for a session the driver could not run, ended as `kind` with
532
- * `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).
533
558
  */
534
- const driverFailure = (message, kind, scope) => ({
559
+ const driverFailure = (message, kind, scope, spent = NOTHING_SPENT) => ({
535
560
  type: "session.finished",
536
561
  outcome: {
537
562
  subtype: "error_driver",
538
563
  end: endFor(kind, scope.facts, kind === "interrupted" ? void 0 : message, scope.scrub),
539
- sessionId: "",
564
+ sessionId: spent.sessionId,
540
565
  report: null,
541
566
  text: "",
542
- usage: {
543
- costUsd: 0,
544
- inputTokens: 0,
545
- outputTokens: 0,
546
- cacheReadTokens: 0,
547
- cacheWriteTokens: 0,
548
- turns: 0,
549
- durationMs: 0
550
- },
567
+ usage: spent.usage,
551
568
  errors: [message]
552
569
  }
553
570
  });
@@ -608,7 +625,6 @@ const spawnLeg = (start) => {
608
625
  * Windows the process ends with `ork`, whatever ends `ork` (the orphan
609
626
  * guard).
610
627
  */
611
- const REASON_LIMIT = 500;
612
628
  /** Marks a session's start with the driver that runs it. */
613
629
  const withDriver = (event, driver) => event.type === "session.started" ? {
614
630
  ...event,
@@ -622,10 +638,11 @@ const settled = (outcome, input) => settleEnd(input.reported === void 0 ? outcom
622
638
  ...outcome,
623
639
  report: input.reported()
624
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);
625
643
  /** The session's end at its token cap, its earlier legs' usage included. */
626
644
  const tokenCapEnd = (input, used, sessionId) => {
627
- const usage = used === void 0 || input.carried === void 0 ? used : input.adapter.usage.combine(input.carried, used);
628
- const capped = tokenCapOutcome(input.scope.facts, usage, sessionId);
645
+ const capped = tokenCapOutcome(input.scope.facts, used === void 0 ? void 0 : withCarried(input, used), sessionId);
629
646
  return capped === void 0 ? void 0 : {
630
647
  type: "session.finished",
631
648
  outcome: settled(capped, input)
@@ -645,37 +662,36 @@ const legStep = (decoded, input) => {
645
662
  const events = [];
646
663
  let ended;
647
664
  for (const said of decoded.events) {
648
- 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 = {
649
673
  ...said,
650
- outcome: settled(said.outcome, input)
651
- } : said;
652
- if (event.type === "session.started" && carried !== void 0) continue;
653
- if (event.type !== "session.finished") events.push(withDriver(event, adapter.driver));
654
- 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 = {
655
681
  kind: "deferred",
656
- sessionId: event.outcome.sessionId,
657
- usage: event.outcome.usage,
658
- 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
659
691
  };
660
692
  else {
661
- const usage = carried === void 0 ? event.outcome.usage : adapter.usage.combine(carried, event.outcome.usage);
662
- const last = {
663
- ...event,
664
- outcome: {
665
- ...event.outcome,
666
- usage
667
- }
668
- };
669
- if (input.withhold() && wentWell(event.outcome.end?.kind)) ended = {
670
- kind: "continued",
671
- sessionId: event.outcome.sessionId,
672
- usage: event.outcome.usage,
673
- last
674
- };
675
- else {
676
- events.push(last);
677
- ended = { kind: "finished" };
678
- }
693
+ events.push(last);
694
+ ended = { kind: "finished" };
679
695
  }
680
696
  }
681
697
  return ended === void 0 ? { events } : {
@@ -683,16 +699,6 @@ const legStep = (decoded, input) => {
683
699
  ended
684
700
  };
685
701
  };
686
- /** Noise a Node crash ends stderr with, or a stack frame: never the reason. */
687
- const isNoise = (line) => /^Node\.js v\d/u.test(line) || /^\s+at /u.test(line);
688
- /**
689
- * Why a process ended without a result: the last block of its stderr as one
690
- * masked, capped line (the reason reaches the terminal, which is not masked).
691
- */
692
- const failureReason = (name, stderr, code) => {
693
- 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 !== "");
694
- return block === void 0 ? `${name} exited with code ${String(code)}` : maskSecrets(block).slice(0, REASON_LIMIT);
695
- };
696
702
  /** Runs one process of a session, yielding its events; returns how it ended. */
697
703
  const runLeg = async function* (input) {
698
704
  const child = spawnLeg(input);
@@ -728,11 +734,14 @@ const runLeg = async function* (input) {
728
734
  if (input.adapter.command.input === void 0) child.stdin.end();
729
735
  let outcome;
730
736
  let sessionId = "";
737
+ /** What this leg has used so far, as the last usage step said. */
738
+ let used;
731
739
  const { decoder } = input;
732
740
  const decode = decoder?.decode ?? input.adapter.decode;
733
741
  try {
734
742
  for await (const line of createInterface({ input: child.stdout })) {
735
743
  const decoded = decode(line);
744
+ used = decoded.usage ?? used;
736
745
  const { events, ended } = legStep(decoded, input);
737
746
  for (const event of decoded.events) if (event.type === "session.started") sessionId = event.sessionId;
738
747
  yield* events;
@@ -760,11 +769,18 @@ const runLeg = async function* (input) {
760
769
  reason: `could not start ${input.bin}: ${end.message}`,
761
770
  end: failureKind(end)
762
771
  };
763
- if (stopped || end.signal !== null) return {
764
- kind: "failed",
765
- reason: "interrupted",
766
- end: "interrupted"
767
- };
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
+ }
768
784
  const reason = failureReason(input.adapter.command.bin, stderr, end.code);
769
785
  return {
770
786
  kind: "failed",
@@ -850,6 +866,7 @@ const releaseTool = defineTool({
850
866
  });
851
867
  // ../runtime/src/tools/delegate.ts
852
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.");
853
870
  /** What the leader reads back: each report without the usage details. */
854
871
  const reportLine = (report) => {
855
872
  const { usage, ...output } = report.output;
@@ -875,7 +892,8 @@ const delegateTool = defineTool({
875
892
  const ids = ports.ownership?.children.map((child) => child.id) ?? [];
876
893
  return { tasks: z.array(z.strictObject({
877
894
  child: z.enum(ids).describe(`The id of a node you own: ${ids.join(", ")}.`),
878
- brief: BRIEF
895
+ brief: BRIEF,
896
+ description: DESCRIPTION
879
897
  })).min(1) };
880
898
  },
881
899
  available: ({ ports }) => (ports.ownership?.children.length ?? 0) > 0,
@@ -891,7 +909,8 @@ const spawnWorkerTool = defineTool({
891
909
  const ids = ports.ownership?.templates ?? [];
892
910
  return { tasks: z.array(z.strictObject({
893
911
  template: z.enum(ids).describe(`The id of a child worker you own: ${ids.join(", ")}.`),
894
- brief: BRIEF
912
+ brief: BRIEF,
913
+ description: DESCRIPTION
895
914
  })).min(1) };
896
915
  },
897
916
  available: ({ ports }) => ports.ownership !== void 0 && ports.ownership.templates.length > 0 && ports.ownership.workersLeft() > 0,
@@ -959,7 +978,7 @@ const followUpTool = defineTool({
959
978
  text: z.string().min(1).max(4e3)
960
979
  }).optional().describe("For a message: what the maintainer will send."),
961
980
  steps: z.array(z.strictObject({
962
- text: z.string().min(1),
981
+ text: z.string().trim().min(1),
963
982
  command: z.string().optional().describe("Shown to the maintainer to copy; Ork never runs it.")
964
983
  })).max(20).optional().describe("For a task or check: what to do, in order.")
965
984
  },
@@ -1335,7 +1354,9 @@ const createSessionRuntime = (adapter, options = {}) => ({
1335
1354
  }
1336
1355
  const leg = yield* runLeg(legOf(legSpec, spent));
1337
1356
  if (leg.kind === "failed") {
1338
- 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);
1339
1360
  return;
1340
1361
  }
1341
1362
  if (leg.kind === "finished") return;
@@ -1612,8 +1633,19 @@ const cacheEnv = (spec) => spec.promptCache === "1h" ? { [PROMPT_CACHE_1H]: "1"
1612
1633
  const strictMcpOf = (spec) => spec.tools.strictMcp === true || spec.machineClaude !== true;
1613
1634
  /** The variables that keep the machine's auto-memory out of a session. */
1614
1635
  const memoryEnv = (spec) => spec.machineClaude === true ? {} : { [NO_AUTO_MEMORY]: "1" };
1636
+ /** Claude Code's default timeout for a Bash call, in milliseconds. */
1637
+ const BASH_TIMEOUT = "BASH_DEFAULT_TIMEOUT_MS";
1638
+ /**
1639
+ * Ten minutes, Claude Code's own most for a Bash call. At its default of two, a
1640
+ * longer command (a typecheck of the workspace) moves to the background, and
1641
+ * the session then polls it with `sleep`, `until` or `Monitor`, which ask.
1642
+ */
1643
+ const BASH_TIMEOUT_MS = 6e5;
1644
+ /** The variables that let a session's Bash calls run to the end in front. */
1645
+ const bashEnv = () => ({ [BASH_TIMEOUT]: String(BASH_TIMEOUT_MS) });
1615
1646
  /** Everything a session adds to its driver's environment. */
1616
1647
  const driverEnv = (spec) => ({
1648
+ ...bashEnv(),
1617
1649
  ...cacheEnv(spec),
1618
1650
  ...memoryEnv(spec)
1619
1651
  });