@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.
- package/README.md +10 -4
- package/dist/__build_info__-DcRdK0Du.mjs +359 -0
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
- package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
- package/dist/bin.mjs +328 -1734
- package/dist/brief-BvMtIFio.mjs +58 -0
- package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
- package/dist/command-tree-BkSYWmYJ.mjs +63 -0
- package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
- package/dist/context-BN9A2Nnu.mjs +106 -0
- package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
- package/dist/flags-CdgIH1L6.mjs +230 -0
- package/dist/follow-ups-DxCsOby1.mjs +496 -0
- package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
- package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
- package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
- package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
- package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
- package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
- package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
- package/dist/post-C9soCx7i.mjs +46 -0
- package/dist/print-BaWiWf4v.mjs +19 -0
- package/dist/project-records-BRiyQzUo.mjs +230 -0
- package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
- package/dist/run-B-j3Qxqc.mjs +1464 -0
- package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
- package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
- package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
- package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
- package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
- package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
- package/dist/store-Dyr1mP8K.mjs +16 -0
- package/dist/ui/assets/app-BPSCxb_l.css +1 -0
- package/dist/ui/assets/app-CnNRowFn.js +111 -0
- package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
- package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
- package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
- package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
- package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
- package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
- package/package.json +8 -4
- package/dist/ui/assets/app-D_nAPnHM.css +0 -1
- package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { $
|
|
2
|
-
import { t as runOnce } from "./run-
|
|
1
|
+
import { $r as programsRunBy, At as endedWith, Dt as runScope, Et as heldTimeout, Gn as endOf, It as lowerChild, Kn as outcomeEnd, Mt as stoppedEnd, Nt as declaredProblem, Ot as calledWorkflows, Pt as slotClock, Qr as beforeOf, Rt as startAt, Y as sessionRefusals, Yn as endFor, _i as TOOL_PREFIX, _t as planNodeSession, cn as mcpNotText, di as isToolNode, dr as renderTemplate, gi as INTEGRATIONS, hi as timeoutMs, in as sessionDriver, jt as failedWith, ln as serversIn, lr as evaluate, mt as planReason, on as declaredOutputOf, qn as END_DETAIL_LIMIT, ri as markValue, sn as MCP_FILE, tn as envReadsOf, ui as isClaudeNode, ur as parseTemplate, vi as integrationFor, wi as AGENT_DRIVERS, yt as reportSchemaFor, zn as countedTokens } from "./src-DN-RPsil.mjs";
|
|
2
|
+
import { t as runOnce } from "./run-D_u9V_Ve.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
|
|
@@ -59,7 +60,9 @@ const callExecutor = (context) => {
|
|
|
59
60
|
// ../nodes/src/claude-plan.ts
|
|
60
61
|
/**
|
|
61
62
|
* @file What a Claude node's session is planned with, read from the node's
|
|
62
|
-
* 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.
|
|
63
66
|
*/
|
|
64
67
|
/** The plan options every session of a node starts from. */
|
|
65
68
|
const planOptionsOf = (context, inPlace) => {
|
|
@@ -82,6 +85,30 @@ const planOptionsOf = (context, inPlace) => {
|
|
|
82
85
|
if (context.claims !== void 0) planOptions.claims = context.claims;
|
|
83
86
|
return planOptions;
|
|
84
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.`;
|
|
85
112
|
// ../nodes/src/claude-result.ts
|
|
86
113
|
/**
|
|
87
114
|
* @file A finished session as a node's result: its report is the output, held
|
|
@@ -551,12 +578,13 @@ const leftOf = (caps, used, quotaLeft) => least(...caps.map((cap) => cap === voi
|
|
|
551
578
|
* budget has counted. The quota has: the ledger took it as the session
|
|
552
579
|
* finished, so what it says is left is left. The run's tokens left have too:
|
|
553
580
|
* only the node's own `budgetTokens` takes `tokens` off. `null` when nothing
|
|
554
|
-
* 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`).
|
|
555
583
|
*/
|
|
556
|
-
const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}) => {
|
|
584
|
+
const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}, turnsAtLeast = 0) => {
|
|
557
585
|
const budgetUsd = leftOf([spec.budgetUsd, runLeft], used.spent, quotaLeft);
|
|
558
586
|
const budgetTokens = leftOf([spec.budgetTokens], used.tokens ?? 0, least(tokensLeft.run, tokensLeft.quota));
|
|
559
|
-
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);
|
|
560
588
|
if ([
|
|
561
589
|
budgetUsd,
|
|
562
590
|
budgetTokens,
|
|
@@ -568,18 +596,36 @@ const lateLimits = (spec, used, runLeft, quotaLeft, tokensLeft = {}) => {
|
|
|
568
596
|
...maxTurns === void 0 ? {} : { maxTurns }
|
|
569
597
|
};
|
|
570
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
|
+
};
|
|
571
614
|
/**
|
|
572
615
|
* Whether a limit across runs, rather than the node's work, ended a node: a
|
|
573
616
|
* session whose cap was a quota's and that ran into it, or one its plan refused
|
|
574
|
-
* and that gave no report. A
|
|
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.
|
|
575
620
|
*/
|
|
576
|
-
const limitWatch = (context) => {
|
|
621
|
+
const limitWatch = (context, run = {}) => {
|
|
577
622
|
let why;
|
|
623
|
+
let runWhy;
|
|
578
624
|
return {
|
|
579
625
|
stoppedBy: (reason) => {
|
|
580
626
|
why = reason;
|
|
581
627
|
},
|
|
582
|
-
session: (asked) => {
|
|
628
|
+
session: (asked, priorCostUsd = 0) => {
|
|
583
629
|
const { leftUsd: left, leftTokens } = context.quota?.() ?? {};
|
|
584
630
|
let refused;
|
|
585
631
|
return {
|
|
@@ -591,6 +637,7 @@ const limitWatch = (context) => {
|
|
|
591
637
|
const capped = left !== void 0 && asked.budgetUsd !== void 0 && asked.budgetUsd >= left || leftTokens !== void 0 && asked.budgetTokens !== void 0 && asked.budgetTokens >= leftTokens;
|
|
592
638
|
const spent = outcome !== null && outcomeEnd(outcome).kind === "budget_limit";
|
|
593
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);
|
|
594
641
|
}
|
|
595
642
|
};
|
|
596
643
|
},
|
|
@@ -600,6 +647,11 @@ const limitWatch = (context) => {
|
|
|
600
647
|
...endedWith(result, endFor("plan_limit", {}, why)),
|
|
601
648
|
limited: why
|
|
602
649
|
};
|
|
650
|
+
if (runWhy !== void 0) return {
|
|
651
|
+
...result,
|
|
652
|
+
limited: runWhy,
|
|
653
|
+
stopsAs: "budget_exhausted"
|
|
654
|
+
};
|
|
603
655
|
const { end } = result;
|
|
604
656
|
return end?.kind === "plan_limit" ? {
|
|
605
657
|
...result,
|
|
@@ -677,9 +729,31 @@ const startWith = async (context, left, own, reserve) => {
|
|
|
677
729
|
* `budgetUsd`. With nothing left, it waits for a worker running beside it to
|
|
678
730
|
* finish, as long as one may.
|
|
679
731
|
*/
|
|
680
|
-
const budgetToStart = (context) => startWith(context, () => budgetNow(context),
|
|
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
|
+
};
|
|
681
755
|
/** `budgetToStart` in counted tokens: the run's `budgetTokens` and a quota's. */
|
|
682
|
-
const tokensToStart = (context) => startWith(context, () => tokensNow(context),
|
|
756
|
+
const tokensToStart = (context) => startWith(context, () => tokensNow(context), ownCaps(context).tokens, context.reserveTokens);
|
|
683
757
|
/**
|
|
684
758
|
* What a node's first session may spend, in dollars and in counted tokens, set
|
|
685
759
|
* into `planOptions`; the node's result instead when nothing is left of
|
|
@@ -1729,30 +1803,6 @@ const checkedResult = (result, { check, resumes }) => {
|
|
|
1729
1803
|
* through the session's ports, and resumes its session on re-entry
|
|
1730
1804
|
* (ADR-019).
|
|
1731
1805
|
*/
|
|
1732
|
-
const MAX_INPUT = 8192;
|
|
1733
|
-
const inputJson = (input) => JSON.stringify(input ?? {}, void 0, 2);
|
|
1734
|
-
/** What a sent-back input is whole, for `read_input`, when its note cuts it. */
|
|
1735
|
-
const wholeInput = (input) => {
|
|
1736
|
-
const json = inputJson(input);
|
|
1737
|
-
return json.length > MAX_INPUT ? json : void 0;
|
|
1738
|
-
};
|
|
1739
|
-
/** A re-entry's note of what was sent back; `read_input` has a cut rest. */
|
|
1740
|
-
const reentryNote = (round, input) => {
|
|
1741
|
-
const json = inputJson(input);
|
|
1742
|
-
const cut = json.length > MAX_INPUT;
|
|
1743
|
-
const body = cut ? `${json.slice(0, MAX_INPUT)}\n…` : json;
|
|
1744
|
-
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.` : "";
|
|
1745
|
-
return `\n\n## Round ${String(round)}\n\nThe workflow sent this task back to you with:\n\n\`\`\`json\n${body}\n\`\`\`${rest}`;
|
|
1746
|
-
};
|
|
1747
|
-
/**
|
|
1748
|
-
* What a resumed leader is told: it already has its brief and its earlier work,
|
|
1749
|
-
* so only the new round, and the new task when its owner sent one.
|
|
1750
|
-
*/
|
|
1751
|
-
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");
|
|
1752
|
-
/** A first-round resume is a session the run was cut off in (`ork resume`). */
|
|
1753
|
-
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)}`;
|
|
1754
|
-
/** What a session is told when messages came in as it finished. */
|
|
1755
|
-
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.`;
|
|
1756
1806
|
/** A re-entered leader resumes; anyone else starts with the full brief. */
|
|
1757
1807
|
const specFor = (spec, context) => {
|
|
1758
1808
|
if (context.resume !== void 0) return {
|
|
@@ -1816,11 +1866,12 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1816
1866
|
if (context.mailbox !== void 0) ports.mailbox = context.mailbox;
|
|
1817
1867
|
if (context.claims !== void 0) ports.claims = context.claims;
|
|
1818
1868
|
if (context.feedback !== void 0) ports.feedback = context.feedback;
|
|
1869
|
+
if (context.followUps !== void 0) ports.followUps = context.followUps;
|
|
1819
1870
|
const whole = context.round > 1 ? wholeInput(context.input) : void 0;
|
|
1820
1871
|
if (whole !== void 0) ports.input = whole;
|
|
1821
|
-
const watch = limitWatch(context);
|
|
1822
|
-
const run = async (asked) => {
|
|
1823
|
-
const watching = watch.session(asked);
|
|
1872
|
+
const watch = limitWatch(context, runCapsOf(context, planOptions));
|
|
1873
|
+
const run = async (asked, prior = 0) => {
|
|
1874
|
+
const watching = watch.session(asked, prior);
|
|
1824
1875
|
context.reserveBudget?.(asked.budgetUsd);
|
|
1825
1876
|
context.reserveTokens?.(asked.budgetTokens);
|
|
1826
1877
|
const session = runtime.start(asked, ports);
|
|
@@ -1849,7 +1900,7 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1849
1900
|
return outcome;
|
|
1850
1901
|
};
|
|
1851
1902
|
const first = await run(spec);
|
|
1852
|
-
const limitsAfter = ({ sessionId, usage }) => {
|
|
1903
|
+
const limitsAfter = ({ sessionId, usage }, turnsAtLeast = 0) => {
|
|
1853
1904
|
const quota = context.quota?.();
|
|
1854
1905
|
if (quota?.blocked !== void 0) {
|
|
1855
1906
|
watch.stoppedBy(quota.blocked);
|
|
@@ -1862,14 +1913,14 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1862
1913
|
}, context.budgetLeftNow?.(), quota?.leftUsd, {
|
|
1863
1914
|
run: context.tokensLeftNow?.(),
|
|
1864
1915
|
quota: quota?.leftTokens
|
|
1865
|
-
});
|
|
1916
|
+
}, turnsAtLeast);
|
|
1866
1917
|
};
|
|
1867
1918
|
const resumeWith = (outcome, prompt, limits) => run({
|
|
1868
1919
|
...spec,
|
|
1869
1920
|
...limits,
|
|
1870
1921
|
resume: outcome.sessionId,
|
|
1871
1922
|
prompt
|
|
1872
|
-
});
|
|
1923
|
+
}, outcome.usage.costUsd);
|
|
1873
1924
|
const lateTurn = (current) => {
|
|
1874
1925
|
const late = context.mailbox?.pending() ?? 0;
|
|
1875
1926
|
const limits = limitsAfter(current);
|
|
@@ -1895,7 +1946,7 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1895
1946
|
},
|
|
1896
1947
|
late: lateTurn,
|
|
1897
1948
|
resume: async (current, check) => {
|
|
1898
|
-
const limits = limitsAfter(current);
|
|
1949
|
+
const limits = limitsAfter(current, 20);
|
|
1899
1950
|
if (limits === null) return null;
|
|
1900
1951
|
await context.hold?.wait("start", context.signal);
|
|
1901
1952
|
return context.signal.aborted ? null : resumeWith(current, checkFailedMessage(check, {
|
|
@@ -2143,20 +2194,23 @@ const mcpCallExecutor = (options = {}) => async (context) => {
|
|
|
2143
2194
|
* whatever it started: SIGTERM, then SIGKILL for what ignores that. A stop or
|
|
2144
2195
|
* a timeout therefore leaves nothing running, and an escaped process that
|
|
2145
2196
|
* keeps the output pipes open cannot hold the node. Off Windows it runs under
|
|
2146
|
-
* `sh`, whose `times` says the CPU the program and its children used,
|
|
2147
|
-
* the
|
|
2197
|
+
* `sh`, whose `times` says the CPU the program and its children used, beside
|
|
2198
|
+
* the orphan guard, which ends the group when `ork` dies, and at the
|
|
2199
|
+
* project's priority: `low` starts it below Ork's own (nice, and on macOS
|
|
2148
2200
|
* utility QoS).
|
|
2149
2201
|
*/
|
|
2150
|
-
/** How long a program may take to end after SIGTERM. */
|
|
2151
|
-
const KILL_AFTER_MS = 2e3;
|
|
2152
2202
|
/** How long to wait for the pipes to close after SIGKILL. */
|
|
2153
2203
|
const CLOSE_AFTER_MS = 500;
|
|
2204
|
+
/** The fd of the orphan guard's pipe from `ork`. */
|
|
2205
|
+
const WATCH_FD = 4;
|
|
2154
2206
|
/**
|
|
2155
|
-
* Runs `$0 "$@"
|
|
2156
|
-
* and
|
|
2207
|
+
* Runs `$0 "$@"` beside the orphan guard, which ends the group if `ork` dies,
|
|
2208
|
+
* and stops the guard once the program ends, so its leftovers live on as
|
|
2209
|
+
* before. Then writes the shell's `times` to fd 3: its children's user and
|
|
2210
|
+
* system time, the program's whole tree that it waited for. A program that
|
|
2157
2211
|
* isn't there says so on fd 3, as spawn's ENOENT would.
|
|
2158
2212
|
*/
|
|
2159
|
-
const TIMED = String.raw`command -v "$0" >/dev/null 2>&1 || { echo ork-missing >&3; exit 127; }; "$0" "$@" 3
|
|
2213
|
+
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`;
|
|
2160
2214
|
const MISSING = "ork-missing";
|
|
2161
2215
|
/** The children's line of `times` (`0m1.250s 0m0.300s`), in milliseconds. */
|
|
2162
2216
|
const cpuOfTimes = (text) => {
|
|
@@ -2197,10 +2251,12 @@ const spawnTool = (program, args, { cwd, signal, env, priority }) => {
|
|
|
2197
2251
|
"ignore",
|
|
2198
2252
|
"pipe",
|
|
2199
2253
|
"pipe",
|
|
2254
|
+
"pipe",
|
|
2200
2255
|
"pipe"
|
|
2201
2256
|
]
|
|
2202
2257
|
});
|
|
2203
2258
|
lowerChild(priority, child.pid);
|
|
2259
|
+
holdGuard(child.stdio[WATCH_FD]);
|
|
2204
2260
|
const fd3 = child.stdio[3];
|
|
2205
2261
|
if (fd3 instanceof Readable) fd3.setEncoding("utf8").on("data", (chunk) => {
|
|
2206
2262
|
times.push(chunk);
|