@krapht/ork 1.0.0-alpha.10 → 1.0.0-alpha.11
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/dist/{__build_info__-DFF18K8y.mjs → __build_info__-CIchF6nr.mjs} +3 -72
- package/dist/bin.mjs +1066 -387
- package/dist/chips-BA7SrrH5.mjs +43 -0
- package/dist/{clean-DVaEH9e7.mjs → clean-Di-KT7Mf.mjs} +1 -1
- package/dist/{completion-Dv5kf598.mjs → completion-C5D0sEs5.mjs} +22 -15
- package/dist/{context-CEaKOVmG.mjs → context-BaNshWr9.mjs} +43 -31
- package/dist/{decide-3l9q8UtX.mjs → decide-B6PnKeqo.mjs} +1 -1
- package/dist/{find-DEjjOlTG.mjs → find-BicBbGMg.mjs} +1 -1
- package/dist/{format-Cb5_m-Qd.mjs → format-lWhN-jYR.mjs} +130 -35
- package/dist/{in-a-run-BS9Nc-JW.mjs → in-a-run-CgkVouyG.mjs} +1 -1
- package/dist/{limits-BSwEjQST.mjs → limits-jZQp9ZJC.mjs} +1 -1
- package/dist/{list-Qxr6rwsY.mjs → list-CEaJ07jd.mjs} +1 -1
- package/dist/{mcp-CslnG5wf.mjs → mcp-DYgVA9f5.mjs} +6 -6
- package/dist/output-CM95njYt.mjs +421 -0
- package/dist/{pause-D7OVB5Ac.mjs → pause-D6PuSlNl.mjs} +4 -4
- package/dist/{relay-EceRIebO.mjs → relay-DNx7be9k.mjs} +2 -2
- package/dist/{remove-0IQZVWmI.mjs → remove-CpMb4NDl.mjs} +3 -3
- package/dist/{report-DK4LGqwF.mjs → report-CEN8VoUb.mjs} +31 -20
- package/dist/{send-C0uwfUYi.mjs → send-CLs_fyg7.mjs} +3 -3
- package/dist/{src-cmmXKHn1.mjs → src-9iGkjnsy.mjs} +234 -150
- package/dist/{src-i5k2Y-kf.mjs → src-B6KvTm7B.mjs} +78 -11
- package/dist/{src-oaJYIddp.mjs → src-BnZgZGrf.mjs} +523 -81
- package/dist/{src-Df6KOIDC.mjs → src-D6_xjm-x.mjs} +1722 -825
- package/dist/ui/assets/{app-qs8Lofv6.js → app-CxURph7a.js} +16 -16
- package/dist/ui/assets/app-QoHVi-mb.css +1 -0
- package/dist/ui/assets/{index-DV1ZaOk-.js → index-By_8KKG6.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CBkHJdqk.mjs → ui-CIci4UBW.mjs} +4 -4
- package/dist/{usage-Cd8pEYyK.mjs → usage-GceBGMjU.mjs} +43 -32
- package/dist/{watch-DW5R2OPO.mjs → watch-BmoybMQg.mjs} +28 -16
- package/dist/{workflows-BT1gDRIV.mjs → workflows-DZvW3i1e.mjs} +3 -3
- package/dist/{worktrees-g1MQMGie.mjs → worktrees-Be51E2rB.mjs} +11 -10
- package/package.json +1 -1
- package/dist/output-Bqljtmp_.mjs +0 -40
- package/dist/ui/assets/app-C8uhHVYp.css +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Bt as
|
|
1
|
+
import { $t as endOf, Bt as lowerChild, Cr as TOOL_PREFIX, Ft as failedWith, Ht as envReadsOf, It as stoppedEnd, Lt as calledWorkflows, Mt as runScope, Nt as declaredProblem, Pt as endedWith, Rt as slotClock, Sr as INTEGRATIONS, Vt as startAt, Wt as declaredOutputOf, Xt as mcpNotText, Yt as MCP_FILE, Zt as serversIn, _n as parseTemplate, _r as isToolNode, ar as programsRunBy, at as planReason, bt as heldTimeout, dt as sessionDriver, en as outcomeEnd, gn as evaluate, gr as isClaudeNode, ir as beforeOf, lr as markValue, lt as planNodeSession, pt as reportSchemaFor, rn as endFor, tn as END_DETAIL_LIMIT, vn as renderTemplate, wr as integrationFor, xr as timeoutMs } from "./src-D6_xjm-x.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { accessSync, constants, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
6
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
7
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8
|
+
import { Readable } from "node:stream";
|
|
8
9
|
// ../nodes/src/call.ts
|
|
9
10
|
/**
|
|
10
11
|
* @file The `workflow.call` node type (RFC-004): renders what the node asks for
|
|
@@ -18,23 +19,16 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
18
19
|
*/
|
|
19
20
|
const limitedCall = (result) => {
|
|
20
21
|
if (result.ok) return result;
|
|
21
|
-
|
|
22
|
+
const why = result.end?.detail ?? result.error;
|
|
23
|
+
return result.end?.kind === "plan_limit" || (why ?? "").includes("Not started: ") || result.output["runStatus"] === "quota_exhausted" ? {
|
|
22
24
|
...result,
|
|
23
|
-
limited:
|
|
25
|
+
limited: why ?? "the called run reached a limit"
|
|
24
26
|
} : result;
|
|
25
27
|
};
|
|
26
28
|
const callExecutor = (context) => {
|
|
27
29
|
const { node, calls } = context;
|
|
28
|
-
if (node.type !== "workflow.call") return Promise.resolve({
|
|
29
|
-
|
|
30
|
-
output: {},
|
|
31
|
-
error: `${context.id} is not a call`
|
|
32
|
-
});
|
|
33
|
-
if (calls === void 0) return Promise.resolve({
|
|
34
|
-
ok: false,
|
|
35
|
-
output: { status: "fail" },
|
|
36
|
-
error: "This run cannot call workflows."
|
|
37
|
-
});
|
|
30
|
+
if (node.type !== "workflow.call") return Promise.resolve(failedWith(endFor("unknown", {}, `${context.id} is not a call`), {}));
|
|
31
|
+
if (calls === void 0) return Promise.resolve(failedWith(endFor("invalid_node", { reason: "this run cannot call workflows" }), { status: "fail" }));
|
|
38
32
|
const scope = runScope(context.inputs, context.outputs, context.runId, context.item);
|
|
39
33
|
const render = (text) => {
|
|
40
34
|
const template = parseTemplate(text);
|
|
@@ -90,30 +84,27 @@ const planOptionsOf = (context, inPlace) => {
|
|
|
90
84
|
// ../nodes/src/claude-result.ts
|
|
91
85
|
/**
|
|
92
86
|
* @file A finished session as a node's result: its report is the output, held
|
|
93
|
-
* to what the node declares (RFC-010)
|
|
87
|
+
* to what the node declares (RFC-010), and how it ended is its end kind
|
|
88
|
+
* (specs/runtime/session-ends.md).
|
|
94
89
|
*/
|
|
90
|
+
/** How a session with no report ended; a driver's `done` without one is not. */
|
|
91
|
+
const withoutReport = (outcome) => {
|
|
92
|
+
const end = outcomeEnd(outcome);
|
|
93
|
+
return end.kind === "done" || end.kind === "reported_failure" ? endFor("no_report", {}, end.detail) : end;
|
|
94
|
+
};
|
|
95
95
|
/** A finished session as a node result: its report is the node's output. */
|
|
96
96
|
const resultOf = (outcome, declared) => {
|
|
97
|
-
if (outcome === null) return {
|
|
98
|
-
ok: false,
|
|
99
|
-
output: {
|
|
100
|
-
status: "fail",
|
|
101
|
-
summary: "the session ended without a result"
|
|
102
|
-
},
|
|
103
|
-
error: "the session ended without a result"
|
|
104
|
-
};
|
|
97
|
+
if (outcome === null) return failedWith(endFor("driver_crashed", {}, "the session ended without a result"));
|
|
105
98
|
const { report, usage } = outcome;
|
|
106
99
|
if (report === null) {
|
|
107
|
-
const
|
|
100
|
+
const end = withoutReport(outcome);
|
|
108
101
|
return {
|
|
109
|
-
|
|
110
|
-
output: {
|
|
102
|
+
...failedWith(end, {
|
|
111
103
|
status: "fail",
|
|
112
|
-
summary:
|
|
104
|
+
summary: end.message,
|
|
113
105
|
usage
|
|
114
|
-
},
|
|
106
|
+
}),
|
|
115
107
|
usage,
|
|
116
|
-
error: `ended without a report (${outcome.subtype}): ${reason}`,
|
|
117
108
|
sessionId: outcome.sessionId
|
|
118
109
|
};
|
|
119
110
|
}
|
|
@@ -127,7 +118,7 @@ const resultOf = (outcome, declared) => {
|
|
|
127
118
|
};
|
|
128
119
|
if (report.branch !== void 0) output["branch"] = report.branch;
|
|
129
120
|
const problem = declaredProblem(declared, output);
|
|
130
|
-
if (problem !== null) return {
|
|
121
|
+
if (problem !== null) return endedWith({
|
|
131
122
|
ok: false,
|
|
132
123
|
output: {
|
|
133
124
|
status: "fail",
|
|
@@ -137,17 +128,15 @@ const resultOf = (outcome, declared) => {
|
|
|
137
128
|
...report.branch === void 0 ? {} : { branch: report.branch }
|
|
138
129
|
},
|
|
139
130
|
usage,
|
|
140
|
-
error: problem,
|
|
141
131
|
...ran
|
|
142
|
-
};
|
|
143
|
-
const
|
|
144
|
-
|
|
132
|
+
}, endFor("invalid_report", { reason: problem }));
|
|
133
|
+
const ok = report.status !== "fail";
|
|
134
|
+
return endedWith({
|
|
135
|
+
ok,
|
|
145
136
|
output,
|
|
146
137
|
usage,
|
|
147
138
|
...ran
|
|
148
|
-
};
|
|
149
|
-
if (!result.ok) result.error = report.summary;
|
|
150
|
-
return result;
|
|
139
|
+
}, endFor(ok ? "done" : "reported_failure"));
|
|
151
140
|
};
|
|
152
141
|
// ../nodes/src/environ.ts
|
|
153
142
|
/** A variable of the user's. */
|
|
@@ -537,13 +526,16 @@ const withUsageOf = (first, later) => {
|
|
|
537
526
|
*/
|
|
538
527
|
const afterLateTurn = (first, later) => {
|
|
539
528
|
const merged = withUsageOf(first, later);
|
|
540
|
-
|
|
541
|
-
|
|
529
|
+
if (later.report !== null) return merged;
|
|
530
|
+
const { end: _later, ...rest } = merged;
|
|
531
|
+
return {
|
|
532
|
+
...rest,
|
|
542
533
|
report: first.report,
|
|
543
534
|
subtype: first.subtype,
|
|
535
|
+
...first.end === void 0 ? {} : { end: first.end },
|
|
544
536
|
text: first.text,
|
|
545
537
|
errors: first.errors
|
|
546
|
-
}
|
|
538
|
+
};
|
|
547
539
|
};
|
|
548
540
|
/**
|
|
549
541
|
* What another turn of a session may use. `spent` and `turns` are this
|
|
@@ -602,24 +594,38 @@ const limitWatch = (context) => {
|
|
|
602
594
|
},
|
|
603
595
|
ended: (outcome) => {
|
|
604
596
|
if (refused !== void 0 && (outcome?.report ?? null) === null) why = refused;
|
|
605
|
-
|
|
597
|
+
const capped = left !== void 0 && asked.budgetUsd !== void 0 && asked.budgetUsd >= left;
|
|
598
|
+
const spent = outcome !== null && outcomeEnd(outcome).kind === "budget_limit";
|
|
599
|
+
if (capped && spent) why = context.quota?.().blocked ?? "a session reached what was left of a quota";
|
|
606
600
|
}
|
|
607
601
|
};
|
|
608
602
|
},
|
|
609
|
-
result: (result) =>
|
|
610
|
-
|
|
611
|
-
|
|
603
|
+
result: (result) => {
|
|
604
|
+
if (result.ok) return result;
|
|
605
|
+
if (why !== void 0) return {
|
|
606
|
+
...endedWith(result, endFor("plan_limit", {}, why)),
|
|
607
|
+
limited: why
|
|
608
|
+
};
|
|
609
|
+
const { end } = result;
|
|
610
|
+
return end?.kind === "plan_limit" ? {
|
|
611
|
+
...result,
|
|
612
|
+
limited: end.message
|
|
613
|
+
} : result;
|
|
612
614
|
}
|
|
613
615
|
};
|
|
614
616
|
};
|
|
615
|
-
/**
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
617
|
+
/**
|
|
618
|
+
* No session started: nothing was left for one, or nothing to start it. Its
|
|
619
|
+
* summary keeps Ork's reason, `end.detail` too; the message is the kind's.
|
|
620
|
+
*/
|
|
621
|
+
const notStarted = (kind, reason, limited) => ({
|
|
622
|
+
...failedWith(endFor(kind, {
|
|
623
|
+
reason,
|
|
624
|
+
program: "an agent driver"
|
|
625
|
+
}, reason), {
|
|
620
626
|
status: "fail",
|
|
621
627
|
summary: `not started: ${reason}`
|
|
622
|
-
},
|
|
628
|
+
}),
|
|
623
629
|
...limited === void 0 ? {} : { limited }
|
|
624
630
|
});
|
|
625
631
|
// ../nodes/src/tool-output.ts
|
|
@@ -864,6 +870,51 @@ const tokenize = (command) => {
|
|
|
864
870
|
args
|
|
865
871
|
};
|
|
866
872
|
};
|
|
873
|
+
// ../nodes/src/tool-ends.ts
|
|
874
|
+
/**
|
|
875
|
+
* @file How a tool node ended (specs/runtime/session-ends.md): a node that
|
|
876
|
+
* could not run its command, JSON that broke its declared output, or how a
|
|
877
|
+
* command that ran exited: done, stopped, never started, signed out, or
|
|
878
|
+
* failed. Ork's message names the program; its stderr is the detail.
|
|
879
|
+
*/
|
|
880
|
+
/** A node that could not run its command: its own definition is wrong. */
|
|
881
|
+
const failed$1 = (error, kind = "invalid_node") => endedWith({
|
|
882
|
+
ok: false,
|
|
883
|
+
output: {
|
|
884
|
+
exitCode: -1,
|
|
885
|
+
stdout: "",
|
|
886
|
+
stderr: error
|
|
887
|
+
}
|
|
888
|
+
}, endFor(kind, { reason: error }, error));
|
|
889
|
+
/** JSON that broke what the node declares, or that it could not read. */
|
|
890
|
+
const invalidOutput = (output, reason) => endedWith({
|
|
891
|
+
ok: false,
|
|
892
|
+
output
|
|
893
|
+
}, endFor("invalid_output", { reason }));
|
|
894
|
+
/**
|
|
895
|
+
* How a command that ran and exited non-zero ended: stopped, never started, a
|
|
896
|
+
* sign-in its program asks for (`signedOut`), or a plain failure.
|
|
897
|
+
*/
|
|
898
|
+
const exitKind = (run, context, signedOut) => {
|
|
899
|
+
if (context.signal.aborted) return "interrupted";
|
|
900
|
+
if (run.exitCode === -1 && /\bENOENT\b/u.test(run.stderr)) return "tool_missing";
|
|
901
|
+
return signedOut?.(run) === true ? "auth" : "command_failed";
|
|
902
|
+
};
|
|
903
|
+
/** How a command that ran ended, by its exit code; `program` is scrubbed. */
|
|
904
|
+
const exitEnd = (output, run, context, program, signedOut) => {
|
|
905
|
+
if (run.exitCode === 0) return endedWith({
|
|
906
|
+
ok: true,
|
|
907
|
+
output
|
|
908
|
+
}, endFor("done"));
|
|
909
|
+
const end = endFor(exitKind(run, context, signedOut), {
|
|
910
|
+
program,
|
|
911
|
+
exitCode: run.exitCode
|
|
912
|
+
}, run.stderr.trim().slice(-END_DETAIL_LIMIT));
|
|
913
|
+
return endedWith({
|
|
914
|
+
ok: false,
|
|
915
|
+
output
|
|
916
|
+
}, end);
|
|
917
|
+
};
|
|
867
918
|
// ../nodes/src/tool-scrub.ts
|
|
868
919
|
/**
|
|
869
920
|
* @file Masking a secret in what a program prints. A plain search, so a value
|
|
@@ -1265,19 +1316,10 @@ const capped = (text, file) => {
|
|
|
1265
1316
|
* what a session is told. All of it is kept in the output's fields and files.
|
|
1266
1317
|
*/
|
|
1267
1318
|
const clipError = (text) => text.length <= ERROR_LIMIT ? text : `${text.slice(0, ERROR_LIMIT)}…`;
|
|
1268
|
-
const failed$1 = (error) => ({
|
|
1269
|
-
ok: false,
|
|
1270
|
-
output: {
|
|
1271
|
-
exitCode: -1,
|
|
1272
|
-
stdout: "",
|
|
1273
|
-
stderr: error
|
|
1274
|
-
},
|
|
1275
|
-
error
|
|
1276
|
-
});
|
|
1277
1319
|
/** Runs a user's CLI through its integration's adapter, with its own login. */
|
|
1278
1320
|
const toolExecutor = (given, options = {}) => async (context) => {
|
|
1279
1321
|
const { node } = context;
|
|
1280
|
-
if (!isToolNode(node)) return failed$1(`${context.id} is not a tool
|
|
1322
|
+
if (!isToolNode(node)) return failed$1(`${context.id} is not a tool`, "unknown");
|
|
1281
1323
|
const environ = options.environ ?? process.env;
|
|
1282
1324
|
const scrub = scrubberOf(secretsOf(context.workflow, environ, options.maskedNames));
|
|
1283
1325
|
const command = commandOf(node, context);
|
|
@@ -1304,6 +1346,7 @@ const toolExecutor = (given, options = {}) => async (context) => {
|
|
|
1304
1346
|
...node.watch === void 0 ? {} : { [STATE_ENV]: prepareStateFile(context.runDir, context.id) }
|
|
1305
1347
|
};
|
|
1306
1348
|
const names = Object.keys(nodeEnv.values);
|
|
1349
|
+
const release = await context.machine?.slot([command.program, ...command.args].join(" "), context.signal);
|
|
1307
1350
|
context.emit({
|
|
1308
1351
|
type: "tool.called",
|
|
1309
1352
|
id: context.id,
|
|
@@ -1318,11 +1361,13 @@ const toolExecutor = (given, options = {}) => async (context) => {
|
|
|
1318
1361
|
}
|
|
1319
1362
|
});
|
|
1320
1363
|
const scriptFile = node.script === void 0 ? void 0 : writeScriptFile(context.runDir, context.id, context.round, node.script);
|
|
1364
|
+
const priority = context.machine?.priority;
|
|
1321
1365
|
const raw = await runner(command.program, scriptFile === void 0 ? command.args : [...command.args, scriptFile], {
|
|
1322
1366
|
cwd,
|
|
1323
1367
|
signal: context.signal,
|
|
1324
|
-
env
|
|
1325
|
-
|
|
1368
|
+
env,
|
|
1369
|
+
...priority === void 0 ? {} : { priority }
|
|
1370
|
+
}).finally(() => release?.());
|
|
1326
1371
|
if (scriptFile !== void 0) removeOutputFile(scriptFile);
|
|
1327
1372
|
const run = {
|
|
1328
1373
|
exitCode: raw.exitCode,
|
|
@@ -1359,30 +1404,14 @@ const toolExecutor = (given, options = {}) => async (context) => {
|
|
|
1359
1404
|
context.emit({
|
|
1360
1405
|
type: "tool.result",
|
|
1361
1406
|
id: context.id,
|
|
1362
|
-
isError: run.exitCode !== 0 || json?.ok === false || declared !== null
|
|
1407
|
+
isError: run.exitCode !== 0 || json?.ok === false || declared !== null,
|
|
1408
|
+
...raw.cpu === void 0 ? {} : { cpu: raw.cpu }
|
|
1363
1409
|
});
|
|
1364
|
-
if (json?.ok === false) return
|
|
1365
|
-
|
|
1366
|
-
output,
|
|
1367
|
-
error: `${context.id}: ${json.error}`
|
|
1368
|
-
};
|
|
1369
|
-
if (declared !== null) return {
|
|
1370
|
-
ok: false,
|
|
1371
|
-
output,
|
|
1372
|
-
error: declared
|
|
1373
|
-
};
|
|
1410
|
+
if (json?.ok === false) return invalidOutput(output, json.error);
|
|
1411
|
+
if (declared !== null) return invalidOutput(output, declared);
|
|
1374
1412
|
if (json !== null) output["json"] = json.json;
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
output
|
|
1378
|
-
};
|
|
1379
|
-
const last = run.stderr.trim().split("\n").at(-1) ?? "";
|
|
1380
|
-
return {
|
|
1381
|
-
ok: false,
|
|
1382
|
-
transient: true,
|
|
1383
|
-
output,
|
|
1384
|
-
error: `${scrub(command.program)} exited with ${String(run.exitCode)}${last === "" ? "" : `: ${clipError(last)}`}`
|
|
1385
|
-
};
|
|
1413
|
+
const signedOut = integration?.signedOut;
|
|
1414
|
+
return exitEnd(output, run, context, scrub(command.program), signedOut);
|
|
1386
1415
|
};
|
|
1387
1416
|
// ../nodes/src/verify.ts
|
|
1388
1417
|
/**
|
|
@@ -1394,15 +1423,17 @@ const toolExecutor = (given, options = {}) => async (context) => {
|
|
|
1394
1423
|
/** A setup that failed: no session started, so nothing was spent on it. */
|
|
1395
1424
|
const beforeFailed = (ran) => {
|
|
1396
1425
|
const error = `its before step failed: ${ran.error ?? "no reason given"}`;
|
|
1397
|
-
return {
|
|
1426
|
+
return endedWith({
|
|
1398
1427
|
ok: false,
|
|
1399
|
-
error,
|
|
1400
1428
|
output: {
|
|
1401
1429
|
status: "fail",
|
|
1402
1430
|
summary: error,
|
|
1403
1431
|
before: ran.output
|
|
1404
1432
|
}
|
|
1405
|
-
}
|
|
1433
|
+
}, endOf({
|
|
1434
|
+
...ran,
|
|
1435
|
+
ok: false
|
|
1436
|
+
}));
|
|
1406
1437
|
};
|
|
1407
1438
|
/** With nothing to run a step, a report cannot be accepted. */
|
|
1408
1439
|
const noSteps = () => Promise.resolve({
|
|
@@ -1423,20 +1454,23 @@ const runStep = async (tool, given, { timeout, ...fields }, step) => {
|
|
|
1423
1454
|
type: "tool.shell",
|
|
1424
1455
|
...fields
|
|
1425
1456
|
};
|
|
1426
|
-
const context = {
|
|
1427
|
-
...given,
|
|
1428
|
-
step
|
|
1429
|
-
};
|
|
1430
1457
|
const ms = timeoutMs(timeout);
|
|
1431
1458
|
if (ms === void 0) return tool({
|
|
1432
|
-
...
|
|
1459
|
+
...given,
|
|
1460
|
+
step,
|
|
1433
1461
|
node
|
|
1434
1462
|
});
|
|
1463
|
+
const { clock, machine } = slotClock(given.hold, given.machine);
|
|
1464
|
+
const context = {
|
|
1465
|
+
...given,
|
|
1466
|
+
step,
|
|
1467
|
+
...machine === void 0 ? {} : { machine }
|
|
1468
|
+
};
|
|
1435
1469
|
const limit = new AbortController();
|
|
1436
1470
|
const finished = new AbortController();
|
|
1437
1471
|
let expired = false;
|
|
1438
1472
|
const watch = async () => {
|
|
1439
|
-
if (await heldTimeout(
|
|
1473
|
+
if (await heldTimeout(clock, ms, AbortSignal.any([context.signal, finished.signal]))) {
|
|
1440
1474
|
expired = true;
|
|
1441
1475
|
limit.abort();
|
|
1442
1476
|
}
|
|
@@ -1547,17 +1581,19 @@ const checkedResult = (result, { check, resumes }) => {
|
|
|
1547
1581
|
};
|
|
1548
1582
|
const reason = `its check failed: ${check.error ?? "no reason given"}`;
|
|
1549
1583
|
const error = result.ok ? reason : `${reason} (then: ${result.error ?? "no report"})`;
|
|
1550
|
-
return {
|
|
1584
|
+
return endedWith({
|
|
1551
1585
|
...result,
|
|
1552
1586
|
ok: false,
|
|
1553
|
-
error,
|
|
1554
1587
|
output: {
|
|
1555
1588
|
...result.output,
|
|
1556
1589
|
status: "fail",
|
|
1557
1590
|
summary: error,
|
|
1558
1591
|
verify
|
|
1559
1592
|
}
|
|
1560
|
-
}
|
|
1593
|
+
}, endOf(result.ok ? {
|
|
1594
|
+
...check,
|
|
1595
|
+
ok: false
|
|
1596
|
+
} : result));
|
|
1561
1597
|
};
|
|
1562
1598
|
// ../nodes/src/claude.ts
|
|
1563
1599
|
/**
|
|
@@ -1615,28 +1651,26 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1615
1651
|
});
|
|
1616
1652
|
if (!ran.ok) return beforeFailed(ran);
|
|
1617
1653
|
await context.hold?.wait("start", context.signal);
|
|
1618
|
-
if (context.signal.aborted)
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1654
|
+
if (context.signal.aborted) {
|
|
1655
|
+
const end = stoppedEnd(context.signal);
|
|
1656
|
+
return beforeFailed({
|
|
1657
|
+
...ran,
|
|
1658
|
+
ok: false,
|
|
1659
|
+
error: end.message,
|
|
1660
|
+
end
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1622
1663
|
planOptions.before = ran.output;
|
|
1623
1664
|
}
|
|
1624
1665
|
const blocked = context.quota?.().blocked;
|
|
1625
|
-
if (blocked !== void 0) return notStarted(blocked, blocked);
|
|
1666
|
+
if (blocked !== void 0) return notStarted("plan_limit", blocked, blocked);
|
|
1626
1667
|
const budget = await budgetToStart(context);
|
|
1627
|
-
if (budget !== void 0 && budget <= 0) return notStarted("nothing is left of the run's budget");
|
|
1668
|
+
if (budget !== void 0 && budget <= 0) return notStarted("budget_limit", "nothing is left of the run's budget");
|
|
1628
1669
|
if (budget !== void 0) planOptions.budgetLeftUsd = budget;
|
|
1629
1670
|
const plan = planNodeSession(context.workflow, context.id, planOptions);
|
|
1630
|
-
if (!plan.ok) return
|
|
1631
|
-
ok: false,
|
|
1632
|
-
output: {
|
|
1633
|
-
status: "fail",
|
|
1634
|
-
summary: plan.error
|
|
1635
|
-
},
|
|
1636
|
-
error: plan.error
|
|
1637
|
-
};
|
|
1671
|
+
if (!plan.ok) return notStarted("invalid_node", plan.error);
|
|
1638
1672
|
const servers = namedMcpEntries(context.projectRoot, plan.mcp);
|
|
1639
|
-
if (!servers.ok) return notStarted(servers.error);
|
|
1673
|
+
if (!servers.ok) return notStarted("invalid_node", servers.error);
|
|
1640
1674
|
const spec = {
|
|
1641
1675
|
...specFor(plan.spec, context),
|
|
1642
1676
|
...servers.value
|
|
@@ -1647,7 +1681,7 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1647
1681
|
named: plan.driver,
|
|
1648
1682
|
ready: options.readyDriver
|
|
1649
1683
|
});
|
|
1650
|
-
if (driver === void 0) return notStarted("no agent driver is ready; see `ork doctor`");
|
|
1684
|
+
if (driver === void 0) return notStarted("tool_missing", "no agent driver is ready; see `ork doctor`");
|
|
1651
1685
|
const runtime = await options.runtimeFor(driver);
|
|
1652
1686
|
const ports = { human: context.owner === void 0 ? withdrawnOnStop(context) : ownerFirst(context, context.owner, {
|
|
1653
1687
|
runtimeFor: options.runtimeFor,
|
|
@@ -1656,6 +1690,7 @@ const claudeExecutor = (options) => async (context) => {
|
|
|
1656
1690
|
inPlace: options.inPlace
|
|
1657
1691
|
}) };
|
|
1658
1692
|
if (context.hold !== void 0) ports.hold = context.hold;
|
|
1693
|
+
if (context.machine !== void 0) ports.machine = context.machine;
|
|
1659
1694
|
if (context.ownership !== void 0) ports.ownership = context.ownership;
|
|
1660
1695
|
if (context.mailbox !== void 0) ports.mailbox = context.mailbox;
|
|
1661
1696
|
if (context.claims !== void 0) ports.claims = context.claims;
|
|
@@ -1814,21 +1849,22 @@ const gateExecutor = async (context) => {
|
|
|
1814
1849
|
})];
|
|
1815
1850
|
try {
|
|
1816
1851
|
const output = await Promise.race([answer, ...expired]);
|
|
1817
|
-
if (!options.some((o) => o.id === output.choice))
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1852
|
+
if (!options.some((o) => o.id === output.choice)) {
|
|
1853
|
+
const facts = node.timeout === void 0 ? {} : { timeout: node.timeout };
|
|
1854
|
+
return endedWith({
|
|
1855
|
+
ok: false,
|
|
1856
|
+
output
|
|
1857
|
+
}, endFor("timed_out", facts, "no one answered the gate"));
|
|
1858
|
+
}
|
|
1822
1859
|
return {
|
|
1823
1860
|
ok: true,
|
|
1824
1861
|
output
|
|
1825
1862
|
};
|
|
1826
1863
|
} catch {
|
|
1827
|
-
return {
|
|
1864
|
+
return endedWith({
|
|
1828
1865
|
ok: false,
|
|
1829
|
-
output: {}
|
|
1830
|
-
|
|
1831
|
-
};
|
|
1866
|
+
output: {}
|
|
1867
|
+
}, endFor("interrupted", {}, "the gate was withdrawn"));
|
|
1832
1868
|
} finally {
|
|
1833
1869
|
answered.abort();
|
|
1834
1870
|
}
|
|
@@ -1843,18 +1879,16 @@ const gateExecutor = async (context) => {
|
|
|
1843
1879
|
*/
|
|
1844
1880
|
/** A node's `timeout:` is what limits a call; this only lifts the SDK's 60 s. */
|
|
1845
1881
|
const NO_LIMIT_MS = 864e5;
|
|
1846
|
-
const failed = (
|
|
1847
|
-
ok: false,
|
|
1848
|
-
output,
|
|
1849
|
-
error
|
|
1850
|
-
});
|
|
1851
|
-
/** A failure another try might not have, so a `watch:` looks again. */
|
|
1852
|
-
const missed = (error, output = {}) => ({
|
|
1882
|
+
const failed = (kind, facts, detail, output = {}) => endedWith({
|
|
1853
1883
|
ok: false,
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1884
|
+
output
|
|
1885
|
+
}, endFor(kind, facts, detail));
|
|
1886
|
+
/** How a call that threw ended: a sign-in, a server that won't start, or else. */
|
|
1887
|
+
const thrownKind = (error) => {
|
|
1888
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
1889
|
+
if (code === 401 || error?.constructor.name === "UnauthorizedError") return "auth";
|
|
1890
|
+
return code === "ENOENT" ? "tool_missing" : "command_failed";
|
|
1891
|
+
};
|
|
1858
1892
|
/**
|
|
1859
1893
|
* A string's templates rendered. A string that is one `{{ … }}` and nothing
|
|
1860
1894
|
* else keeps its value's type, so a tool that asks for a number gets one.
|
|
@@ -1898,17 +1932,20 @@ const reasonOf = (error, server) => {
|
|
|
1898
1932
|
};
|
|
1899
1933
|
const mcpCallExecutor = (options = {}) => async (context) => {
|
|
1900
1934
|
const { node } = context;
|
|
1901
|
-
if (node.type !== "mcp.call")
|
|
1935
|
+
if (node.type !== "mcp.call") {
|
|
1936
|
+
const said = `${context.id} is not an MCP call`;
|
|
1937
|
+
return failed("unknown", {}, said);
|
|
1938
|
+
}
|
|
1902
1939
|
const environ = options.environ ?? process.env;
|
|
1903
1940
|
const declared = readMcpServer(context.projectRoot, node.server);
|
|
1904
|
-
if (!declared.ok) return failed(declared.error);
|
|
1941
|
+
if (!declared.ok) return failed("invalid_node", { reason: declared.error });
|
|
1905
1942
|
const nodeEnv = renderEnv(node, context, environ);
|
|
1906
|
-
if (!nodeEnv.ok) return failed(nodeEnv.error);
|
|
1943
|
+
if (!nodeEnv.ok) return failed("invalid_node", { reason: nodeEnv.error });
|
|
1907
1944
|
const expanded = expandServer(declared.value, {
|
|
1908
1945
|
...environ,
|
|
1909
1946
|
...nodeEnv.values
|
|
1910
1947
|
}, node.plainEnv);
|
|
1911
|
-
if (!expanded.ok) return failed(expanded.error);
|
|
1948
|
+
if (!expanded.ok) return failed("invalid_node", { reason: expanded.error });
|
|
1912
1949
|
const scrub = scrubberOf([...secretsOf(context.workflow, environ, options.maskedNames), ...expanded.value.values]);
|
|
1913
1950
|
const scope = runScope(context.inputs, context.outputs, context.runId, context.item);
|
|
1914
1951
|
const args = Object.fromEntries(Object.entries(node.arguments ?? {}).map(([key, value]) => [key, renderValue(value, scope)]));
|
|
@@ -1951,19 +1988,22 @@ const mcpCallExecutor = (options = {}) => async (context) => {
|
|
|
1951
1988
|
id: context.id,
|
|
1952
1989
|
isError: reported || tooLong || problem !== null
|
|
1953
1990
|
});
|
|
1954
|
-
if (reported) return
|
|
1955
|
-
if (tooLong)
|
|
1956
|
-
|
|
1991
|
+
if (reported) return failed("command_failed", { program: name }, said, output);
|
|
1992
|
+
if (tooLong) {
|
|
1993
|
+
const reason = `${name}'s result is over 64 KiB`;
|
|
1994
|
+
return failed("invalid_output", { reason }, void 0, output);
|
|
1995
|
+
}
|
|
1996
|
+
return problem === null ? endedWith({
|
|
1957
1997
|
ok: true,
|
|
1958
1998
|
output
|
|
1959
|
-
} : failed(problem, output);
|
|
1999
|
+
}, endFor("done")) : failed("invalid_output", { reason: problem }, void 0, output);
|
|
1960
2000
|
} catch (error) {
|
|
1961
2001
|
context.emit({
|
|
1962
2002
|
type: "tool.result",
|
|
1963
2003
|
id: context.id,
|
|
1964
2004
|
isError: true
|
|
1965
2005
|
});
|
|
1966
|
-
return
|
|
2006
|
+
return failed(thrownKind(error), { program: `MCP server \`${node.server}\`` }, clipError(scrub(reasonOf(error, node.server))));
|
|
1967
2007
|
} finally {
|
|
1968
2008
|
await client.close().catch(() => {});
|
|
1969
2009
|
}
|
|
@@ -1974,13 +2014,39 @@ const mcpCallExecutor = (options = {}) => async (context) => {
|
|
|
1974
2014
|
* text. A program runs in its own process group, so aborting it reaches
|
|
1975
2015
|
* whatever it started: SIGTERM, then SIGKILL for what ignores that. A stop or
|
|
1976
2016
|
* a timeout therefore leaves nothing running, and an escaped process that
|
|
1977
|
-
* keeps the output pipes open cannot hold the node.
|
|
2017
|
+
* keeps the output pipes open cannot hold the node. Off Windows it runs under
|
|
2018
|
+
* `sh`, whose `times` says the CPU the program and its children used, and at
|
|
2019
|
+
* the project's priority: `low` starts it below Ork's own (nice, and on macOS
|
|
2020
|
+
* utility QoS).
|
|
1978
2021
|
*/
|
|
1979
2022
|
/** How long a program may take to end after SIGTERM. */
|
|
1980
2023
|
const KILL_AFTER_MS = 2e3;
|
|
1981
2024
|
/** How long to wait for the pipes to close after SIGKILL. */
|
|
1982
2025
|
const CLOSE_AFTER_MS = 500;
|
|
1983
|
-
|
|
2026
|
+
/**
|
|
2027
|
+
* Runs `$0 "$@"`, then writes the shell's `times` to fd 3: its children's user
|
|
2028
|
+
* and system time, the program's whole tree that it waited for. A program that
|
|
2029
|
+
* isn't there says so on fd 3, as spawn's ENOENT would.
|
|
2030
|
+
*/
|
|
2031
|
+
const TIMED = String.raw`command -v "$0" >/dev/null 2>&1 || { echo ork-missing >&3; exit 127; }; "$0" "$@" 3>&-; s=$?; times >&3; exit $s`;
|
|
2032
|
+
const MISSING = "ork-missing";
|
|
2033
|
+
/** The children's line of `times` (`0m1.250s 0m0.300s`), in milliseconds. */
|
|
2034
|
+
const cpuOfTimes = (text) => {
|
|
2035
|
+
const [, , user, system] = [...text.matchAll(/(\d+)m([\d.]+)s/gu)].map(([, m = "0", s = "0"]) => Number(m) * 60 + Number(s));
|
|
2036
|
+
return user === void 0 || system === void 0 ? void 0 : {
|
|
2037
|
+
userMs: Math.round(user * 1e3),
|
|
2038
|
+
systemMs: Math.round(system * 1e3)
|
|
2039
|
+
};
|
|
2040
|
+
};
|
|
2041
|
+
const timed = process.platform !== "win32";
|
|
2042
|
+
/** What to start: the program, under `sh` to time it, at the priority. */
|
|
2043
|
+
const startOf = (program, args, priority) => timed ? startAt(priority, "sh", [
|
|
2044
|
+
"-c",
|
|
2045
|
+
TIMED,
|
|
2046
|
+
program,
|
|
2047
|
+
...args
|
|
2048
|
+
]) : [program, [...args]];
|
|
2049
|
+
const spawnTool = (program, args, { cwd, signal, env, priority }) => {
|
|
1984
2050
|
if (signal.aborted) return Promise.resolve({
|
|
1985
2051
|
exitCode: -1,
|
|
1986
2052
|
stdout: "",
|
|
@@ -1990,7 +2056,9 @@ const spawnTool = (program, args, { cwd, signal, env }) => {
|
|
|
1990
2056
|
const stdout = [];
|
|
1991
2057
|
const stderr = [];
|
|
1992
2058
|
const timers = [];
|
|
1993
|
-
const
|
|
2059
|
+
const times = [];
|
|
2060
|
+
const [bin, argv] = startOf(program, args, priority);
|
|
2061
|
+
const child = spawn(bin, argv, {
|
|
1994
2062
|
cwd,
|
|
1995
2063
|
detached: true,
|
|
1996
2064
|
env: {
|
|
@@ -2000,9 +2068,15 @@ const spawnTool = (program, args, { cwd, signal, env }) => {
|
|
|
2000
2068
|
stdio: [
|
|
2001
2069
|
"ignore",
|
|
2002
2070
|
"pipe",
|
|
2071
|
+
"pipe",
|
|
2003
2072
|
"pipe"
|
|
2004
2073
|
]
|
|
2005
2074
|
});
|
|
2075
|
+
lowerChild(priority, child.pid);
|
|
2076
|
+
const fd3 = child.stdio[3];
|
|
2077
|
+
if (fd3 instanceof Readable) fd3.setEncoding("utf8").on("data", (chunk) => {
|
|
2078
|
+
times.push(chunk);
|
|
2079
|
+
});
|
|
2006
2080
|
const killGroup = (kill) => {
|
|
2007
2081
|
if (child.pid === void 0) return;
|
|
2008
2082
|
try {
|
|
@@ -2014,11 +2088,21 @@ const spawnTool = (program, args, { cwd, signal, env }) => {
|
|
|
2014
2088
|
signal.removeEventListener("abort", abort);
|
|
2015
2089
|
resolve(run);
|
|
2016
2090
|
};
|
|
2017
|
-
const collected = (exitCode) =>
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2091
|
+
const collected = (exitCode) => {
|
|
2092
|
+
const said = times.join("");
|
|
2093
|
+
if (said.includes(MISSING)) return {
|
|
2094
|
+
exitCode: -1,
|
|
2095
|
+
stdout: stdout.join(""),
|
|
2096
|
+
stderr: `could not run ${program}: spawn ${program} ENOENT`
|
|
2097
|
+
};
|
|
2098
|
+
const cpu = cpuOfTimes(said);
|
|
2099
|
+
return {
|
|
2100
|
+
exitCode,
|
|
2101
|
+
stdout: stdout.join(""),
|
|
2102
|
+
stderr: stderr.join(""),
|
|
2103
|
+
...cpu === void 0 ? {} : { cpu }
|
|
2104
|
+
};
|
|
2105
|
+
};
|
|
2022
2106
|
const abort = () => {
|
|
2023
2107
|
killGroup("SIGTERM");
|
|
2024
2108
|
timers.push(setTimeout(() => {
|
|
@@ -2029,10 +2113,10 @@ const spawnTool = (program, args, { cwd, signal, env }) => {
|
|
|
2029
2113
|
}, KILL_AFTER_MS));
|
|
2030
2114
|
};
|
|
2031
2115
|
signal.addEventListener("abort", abort, { once: true });
|
|
2032
|
-
child.stdout
|
|
2116
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
2033
2117
|
stdout.push(chunk);
|
|
2034
2118
|
});
|
|
2035
|
-
child.stderr
|
|
2119
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
2036
2120
|
stderr.push(chunk);
|
|
2037
2121
|
});
|
|
2038
2122
|
child.on("error", (error) => {
|