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

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 (48) hide show
  1. package/dist/{__build_info__-C1Nm0hlJ.mjs → __build_info__-dPf4cEGL.mjs} +3 -134
  2. package/dist/{attach-EP374oMP.mjs → attach-BQl8nQz2.mjs} +5 -5
  3. package/dist/{attach-RQx__RzH.mjs → attach-DhZQ87Nz.mjs} +49 -224
  4. package/dist/bin.mjs +204 -154
  5. package/dist/{brief-Bs3cMQU0.mjs → brief-BQbeoPMc.mjs} +19 -9
  6. package/dist/{builtin-files-yYaNTQRU.mjs → builtin-files-CWL3JEo-.mjs} +1 -1
  7. package/dist/{command-tree-BkSYWmYJ.mjs → command-tree-B6uvMJq1.mjs} +1 -1
  8. package/dist/{completion-DIXWlnAl.mjs → completion-DKPuD_Rw.mjs} +47 -14
  9. package/dist/{context-Dq-VM_sq.mjs → context-Bw6R_EYq.mjs} +3 -3
  10. package/dist/detached-BQlbmqR0.mjs +38 -0
  11. package/dist/{find-k489laBa.mjs → find-B30-rd-u.mjs} +1 -1
  12. package/dist/flags-ByYBx-Xb.mjs +170 -0
  13. package/dist/{follow-ups-DD8NTPe6.mjs → follow-ups-Cxca1Jgg.mjs} +24 -18
  14. package/dist/{format-DQc9A_ei.mjs → format-C-nEZv-P.mjs} +27 -11
  15. package/dist/{help-groups-hB6kY34d.mjs → help-groups-BpRgBSTh.mjs} +13 -7
  16. package/dist/{in-a-run-D_9GlAlS.mjs → in-a-run-CZFzoT3R.mjs} +2 -2
  17. package/dist/{install-PVlV2QPq.mjs → install-CsqK1avz.mjs} +2 -183
  18. package/dist/{limits-CHqBO7wb.mjs → limits-CXh052II.mjs} +1 -1
  19. package/dist/{mcp-D38Ajb0b.mjs → mcp-B4wME9C4.mjs} +6 -6
  20. package/dist/{once-DuUzpzL2.mjs → once-BKn3pOtg.mjs} +3 -3
  21. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  22. package/dist/{pause-Bx3ePGrn.mjs → pause-B1grR1cl.mjs} +3 -3
  23. package/dist/{print-n2qetUHc.mjs → print-Bd8MWVJk.mjs} +1 -1
  24. package/dist/{project-records-Cune1l6I.mjs → project-records-BOEVs69i.mjs} +1 -1
  25. package/dist/{parse-BzWpMN-a.mjs → refusals-DQQX3AIb.mjs} +10 -1
  26. package/dist/{relay-D7RN4m68.mjs → relay-CeNiq3GT.mjs} +1 -1
  27. package/dist/{run-OOWQIKMJ.mjs → run-C0C54UiU.mjs} +116 -137
  28. package/dist/{run-BfxdsQJk.mjs → run-D6yU2rLf.mjs} +1 -1
  29. package/dist/{send-CKth_vgn.mjs → send-DqgF58hb.mjs} +2 -2
  30. package/dist/{src-DLsFsmOD.mjs → src-Ciy7i3se.mjs} +882 -71
  31. package/dist/{src-BHod38UQ.mjs → src-D2qpd-G7.mjs} +394 -247
  32. package/dist/{src-DmmrX2_C.mjs → src-DP02RP9O.mjs} +227 -180
  33. package/dist/{src-DOjTY2ET.mjs → src-n-u4TGev.mjs} +186 -43
  34. package/dist/{store-DACH768X.mjs → store-2KXzMCEN.mjs} +1 -1
  35. package/dist/ui/assets/app-C5vO1M-l.css +1 -0
  36. package/dist/ui/assets/{app-CN3acQ-1.js → app-ICEKpy_C.js} +21 -20
  37. package/dist/ui/assets/{index-B2uGxA4W.js → index-BGaix5wh.js} +2 -2
  38. package/dist/ui/index.html +1 -1
  39. package/dist/{ui-CJ-BX12n.mjs → ui-CAvygxTb.mjs} +53 -24
  40. package/dist/{usage-DxZ6JGd4.mjs → usage-BFBco5OS.mjs} +7 -7
  41. package/dist/{validate-BS0nCccW.mjs → validate-UrmC5AG1.mjs} +5 -5
  42. package/dist/{watch-Dp7IB4FR.mjs → watch-DTxkgB8R.mjs} +5 -5
  43. package/dist/{workflows-Djo1edlK.mjs → workflows-8GV-SeRm.mjs} +28 -4
  44. package/dist/{worktrees-Drof_Nyx.mjs → worktrees-CUdUvZlO.mjs} +9 -6
  45. package/package.json +1 -1
  46. package/dist/flags-B4ZZWspN.mjs +0 -125
  47. package/dist/post-2CaJz_mV.mjs +0 -46
  48. package/dist/ui/assets/app-9Q-62Ywz.css +0 -1
@@ -1,5 +1,5 @@
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";
1
+ import { At as runScope, Di as TOOL_PREFIX, Ei as INTEGRATIONS, Ft as stoppedEnd, It as declaredProblem, Lt as slotClock, Nt as endedWith, Oi as integrationFor, Pi as AGENT_DRIVERS, Pt as failedWith, Q as sessionRefusals, St as reportSchemaFor, Ti as timeoutMs, Un as countedTokens, Vt as startAt, Wr as placeFor, _t as planReason, an as envReadsOf, bi as isClaudeNode, br as parseTemplate, bt as planNodeSession, ci as beforeOf, cn as sessionDriver, di as parseWorkflow, dn as MCP_FILE, er as endOf, fn as mcpNotText, ir as endFor, jt as calledWorkflows, kt as heldTimeout, li as programsRunBy, nr as END_DETAIL_LIMIT, pi as markValue, pn as serversIn, tr as outcomeEnd, un as declaredOutputOf, xi as isToolNode, xr as renderTemplate, yr as evaluate, z as storePath, zt as lowerChild } from "./src-Ciy7i3se.mjs";
2
+ import { t as runOnce } from "./run-D6yU2rLf.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
5
  import { accessSync, constants, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
@@ -8,6 +8,169 @@ import { Readable } from "node:stream";
8
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
9
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
10
10
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
11
+ // ../core/src/project/environ.ts
12
+ /** A variable of the user's. */
13
+ const readEnv = (environ, name) => Object.hasOwn(environ, name) ? environ[name] : void 0;
14
+ // ../core/src/store/scrub.ts
15
+ /**
16
+ * @file Masking the values of the user's secret variables in text Ork stores:
17
+ * what a tool node's program prints, and a run's own messages. A plain
18
+ * search, so a value of any size or shape takes time in step with the text:
19
+ * it looks in the text as it is, and with JSON's escapes decoded once and
20
+ * twice, so that it does not matter which characters an encoder chose to
21
+ * escape. It masks the value, and the pieces programs print on their own:
22
+ * each line, and each value of a JSON document. Other encodings, such as
23
+ * base64, are out of its reach (L81).
24
+ */
25
+ /** Shorter values than this would mask ordinary text: `1`, `true`, a word. */
26
+ const MIN_SECRET_LENGTH = 8;
27
+ const SHORT_ESCAPES = {
28
+ "\"": "\"",
29
+ "\\": "\\",
30
+ "/": "/",
31
+ b: "\b",
32
+ f: "\f",
33
+ n: "\n",
34
+ r: "\r",
35
+ t: " "
36
+ };
37
+ /** The unit an escape at `at` stands for, and how long it is written. */
38
+ const escapeAt = (text, at) => {
39
+ const next = text.charAt(at + 1);
40
+ const short = SHORT_ESCAPES[next];
41
+ if (short !== void 0) return {
42
+ unit: short,
43
+ width: 2
44
+ };
45
+ if (next !== "u") return null;
46
+ const hex = text.slice(at + 2, at + 6);
47
+ return /^[\da-f]{4}$/iu.test(hex) ? {
48
+ unit: String.fromCodePoint(Number(`0x${hex}`)),
49
+ width: 6
50
+ } : null;
51
+ };
52
+ /** The last mark at or before `index`, by a binary search. */
53
+ const lastMark = (marks, index) => {
54
+ let low = 0;
55
+ let high = marks.length - 1;
56
+ while (low < high) {
57
+ const middle = Math.ceil((low + high) / 2);
58
+ if ((marks[middle] ?? 0) <= index) low = middle;
59
+ else high = middle - 1;
60
+ }
61
+ return low;
62
+ };
63
+ /** The view with its escapes decoded; `null` when it has none. */
64
+ const decoded = (view) => {
65
+ const { text } = view;
66
+ const parts = [];
67
+ const decodedAt = [0];
68
+ const sourceAt = [0];
69
+ let from = 0;
70
+ let length = 0;
71
+ let at = text.indexOf("\\");
72
+ while (at !== -1) {
73
+ const escape = escapeAt(text, at);
74
+ if (escape === null) at = text.indexOf("\\", at + 1);
75
+ else {
76
+ parts.push(text.slice(from, at), escape.unit);
77
+ length += at - from;
78
+ decodedAt.push(length);
79
+ sourceAt.push(at);
80
+ length += 1;
81
+ from = at + escape.width;
82
+ decodedAt.push(length);
83
+ sourceAt.push(from);
84
+ at = text.indexOf("\\", from);
85
+ }
86
+ }
87
+ if (from === 0) return null;
88
+ parts.push(text.slice(from));
89
+ return {
90
+ text: parts.join(""),
91
+ toSource: (index) => {
92
+ const mark = lastMark(decodedAt, index);
93
+ return view.toSource((sourceAt[mark] ?? 0) + (index - (decodedAt[mark] ?? 0)));
94
+ }
95
+ };
96
+ };
97
+ /** The text as it is, then decoded as far as JSON inside JSON goes. */
98
+ const viewsOf = (text) => {
99
+ const raw = {
100
+ text,
101
+ toSource: (index) => index
102
+ };
103
+ const once = decoded(raw);
104
+ const twice = once === null ? null : decoded(once);
105
+ return [
106
+ raw,
107
+ ...once === null ? [] : [once],
108
+ ...twice === null ? [] : [twice]
109
+ ];
110
+ };
111
+ /** Where each secret is in the text, overlapping ones joined. */
112
+ const rangesOf = (text, secrets) => {
113
+ const found = [];
114
+ for (const view of viewsOf(text)) for (const secret of secrets) {
115
+ let at = view.text.indexOf(secret);
116
+ while (at !== -1) {
117
+ found.push([view.toSource(at), view.toSource(at + secret.length)]);
118
+ at = view.text.indexOf(secret, at + secret.length);
119
+ }
120
+ }
121
+ const joined = [];
122
+ for (const [start, end] of found.toSorted((a, b) => a[0] - b[0])) {
123
+ const last = joined.at(-1);
124
+ if (last !== void 0 && start < last[1]) last[1] = Math.max(last[1], end);
125
+ else joined.push([start, end]);
126
+ }
127
+ return joined;
128
+ };
129
+ /** The strings and numbers inside a parsed JSON document. */
130
+ const leavesOf = (value) => {
131
+ if (typeof value === "string") return [value];
132
+ if (typeof value === "number") return [String(value)];
133
+ if (typeof value === "object" && value !== null) return Object.values(value).flatMap((item) => leavesOf(item));
134
+ return [];
135
+ };
136
+ /** A value's leaves, when it is a JSON object or array. */
137
+ const jsonLeavesOf = (value) => {
138
+ if (!/^\s*[[{]/u.test(value)) return [];
139
+ try {
140
+ return leavesOf(JSON.parse(value));
141
+ } catch {
142
+ return [];
143
+ }
144
+ };
145
+ /** A value, and the pieces of it a program may well print on their own. */
146
+ const formsOf = (value) => [
147
+ value,
148
+ value.trim(),
149
+ ...value.split(/\r?\n|\r/u).map((line) => line.trim()),
150
+ ...jsonLeavesOf(value)
151
+ ];
152
+ /** Replaces the values, in any form JSON writes them, with `[masked]`. */
153
+ const scrubberOf = (values) => {
154
+ const secrets = [...new Set(values.flatMap((value) => formsOf(value)))].filter((secret) => secret.trim().length >= MIN_SECRET_LENGTH);
155
+ return (text) => {
156
+ if (secrets.length === 0) return text;
157
+ const parts = [];
158
+ let from = 0;
159
+ for (const [start, end] of rangesOf(text, secrets)) {
160
+ parts.push(text.slice(from, start), "[masked]");
161
+ from = end;
162
+ }
163
+ parts.push(text.slice(from));
164
+ return parts.join("");
165
+ };
166
+ };
167
+ /** The values of the variables named that are set. */
168
+ const envValuesOf = (names, environ) => names.flatMap((name) => {
169
+ const value = readEnv(environ, name);
170
+ return value === void 0 ? [] : [value];
171
+ });
172
+ /** What a run masks in a message of its own: the named variables' values. */
173
+ const runScrubber = (names, environ) => scrubberOf(envValuesOf(names, environ));
11
174
  // ../nodes/src/call.ts
12
175
  /**
13
176
  * @file The `workflow.call` node type (RFC-004): renders what the node asks for
@@ -166,9 +329,6 @@ const resultOf = (outcome, declared) => {
166
329
  ...ran
167
330
  }, endFor(ok ? "done" : "reported_failure"));
168
331
  };
169
- // ../nodes/src/environ.ts
170
- /** A variable of the user's. */
171
- const readEnv = (environ, name) => Object.hasOwn(environ, name) ? environ[name] : void 0;
172
332
  // ../nodes/src/mcp-servers.ts
173
333
  /**
174
334
  * @file Reading an MCP server's definition from the project's `.mcp.json`, the
@@ -1043,6 +1203,37 @@ const runUnlessPassed = async ({ node, context, cwd, shown, environ, execute })
1043
1203
  context.skipped?.(shown, once.pass);
1044
1204
  return null;
1045
1205
  };
1206
+ // ../nodes/src/tool-dash.ts
1207
+ /**
1208
+ * @file The leading-`-` guard on a tool node's command: an argument that starts
1209
+ * with a template reading `nodes.*.output` and renders to a leading `-` would
1210
+ * read as an option of the program, so it is refused unless a `--` comes
1211
+ * before it. A literal `-x` and `--flag={{…}}` are not affected. Also how a
1212
+ * tool node renders one argument (or its `cwd`) with that guard.
1213
+ */
1214
+ /** The error for a rendered argument that reads as an option, or `undefined`. */
1215
+ const leadingDashError = (parts, value) => {
1216
+ const first = parts[0];
1217
+ if (!value.startsWith("-") || typeof first !== "object" || first.expr.kind !== "path" || first.expr.segments[0] !== "nodes" || first.expr.segments[2] !== "output") return;
1218
+ return `argument from nodes.${String(first.expr.segments[1])}.output starts with \`-\`; put \`--\` before it in the command`;
1219
+ };
1220
+ /** One templated argument, rendered; `guardDash` refuses a leading `-`. */
1221
+ const renderArg = (text, context, guardDash = false) => {
1222
+ const template = parseTemplate(text);
1223
+ if (!template.ok) return {
1224
+ ok: false,
1225
+ error: template.message
1226
+ };
1227
+ const value = renderTemplate(template.parts, runScope(context.inputs, context.outputs, context.runId, context.item));
1228
+ const error = guardDash ? leadingDashError(template.parts, value) : void 0;
1229
+ return error === void 0 ? {
1230
+ ok: true,
1231
+ value
1232
+ } : {
1233
+ ok: false,
1234
+ error
1235
+ };
1236
+ };
1046
1237
  // ../nodes/src/tool-ends.ts
1047
1238
  /**
1048
1239
  * @file How a tool node ended (specs/runtime/session-ends.md): a node that
@@ -1156,158 +1347,6 @@ const storeGuarded = (env, store) => {
1156
1347
  * that holds.
1157
1348
  */
1158
1349
  const guardOf = (env, store = storePath()) => guardedStoresIn(env) === "all" ? {} : { [GUARDED_STORES_ENV]: JSON.stringify([.../* @__PURE__ */ new Set([...listIn(env) ?? [], realStore(store)])]) };
1159
- // ../nodes/src/tool-scrub.ts
1160
- /**
1161
- * @file Masking a secret in what a program prints. A plain search, so a value
1162
- * of any size or shape takes time in step with the text: it looks in the text
1163
- * as it is, and with JSON's escapes decoded once and twice, so that it does
1164
- * not matter which characters an encoder chose to escape. It masks the value,
1165
- * and the pieces programs print on their own: each line, and each value of a
1166
- * JSON document. Other encodings, such as base64, are out of its reach
1167
- * (L81).
1168
- */
1169
- /** Shorter values than this would mask ordinary text: `1`, `true`, a word. */
1170
- const MIN_SECRET_LENGTH = 8;
1171
- const SHORT_ESCAPES = {
1172
- "\"": "\"",
1173
- "\\": "\\",
1174
- "/": "/",
1175
- b: "\b",
1176
- f: "\f",
1177
- n: "\n",
1178
- r: "\r",
1179
- t: " "
1180
- };
1181
- /** The unit an escape at `at` stands for, and how long it is written. */
1182
- const escapeAt = (text, at) => {
1183
- const next = text.charAt(at + 1);
1184
- const short = SHORT_ESCAPES[next];
1185
- if (short !== void 0) return {
1186
- unit: short,
1187
- width: 2
1188
- };
1189
- if (next !== "u") return null;
1190
- const hex = text.slice(at + 2, at + 6);
1191
- return /^[\da-f]{4}$/iu.test(hex) ? {
1192
- unit: String.fromCodePoint(Number(`0x${hex}`)),
1193
- width: 6
1194
- } : null;
1195
- };
1196
- /** The last mark at or before `index`, by a binary search. */
1197
- const lastMark = (marks, index) => {
1198
- let low = 0;
1199
- let high = marks.length - 1;
1200
- while (low < high) {
1201
- const middle = Math.ceil((low + high) / 2);
1202
- if ((marks[middle] ?? 0) <= index) low = middle;
1203
- else high = middle - 1;
1204
- }
1205
- return low;
1206
- };
1207
- /** The view with its escapes decoded; `null` when it has none. */
1208
- const decoded = (view) => {
1209
- const { text } = view;
1210
- const parts = [];
1211
- const decodedAt = [0];
1212
- const sourceAt = [0];
1213
- let from = 0;
1214
- let length = 0;
1215
- let at = text.indexOf("\\");
1216
- while (at !== -1) {
1217
- const escape = escapeAt(text, at);
1218
- if (escape === null) at = text.indexOf("\\", at + 1);
1219
- else {
1220
- parts.push(text.slice(from, at), escape.unit);
1221
- length += at - from;
1222
- decodedAt.push(length);
1223
- sourceAt.push(at);
1224
- length += 1;
1225
- from = at + escape.width;
1226
- decodedAt.push(length);
1227
- sourceAt.push(from);
1228
- at = text.indexOf("\\", from);
1229
- }
1230
- }
1231
- if (from === 0) return null;
1232
- parts.push(text.slice(from));
1233
- return {
1234
- text: parts.join(""),
1235
- toSource: (index) => {
1236
- const mark = lastMark(decodedAt, index);
1237
- return view.toSource((sourceAt[mark] ?? 0) + (index - (decodedAt[mark] ?? 0)));
1238
- }
1239
- };
1240
- };
1241
- /** The text as it is, then decoded as far as JSON inside JSON goes. */
1242
- const viewsOf = (text) => {
1243
- const raw = {
1244
- text,
1245
- toSource: (index) => index
1246
- };
1247
- const once = decoded(raw);
1248
- const twice = once === null ? null : decoded(once);
1249
- return [
1250
- raw,
1251
- ...once === null ? [] : [once],
1252
- ...twice === null ? [] : [twice]
1253
- ];
1254
- };
1255
- /** Where each secret is in the text, overlapping ones joined. */
1256
- const rangesOf = (text, secrets) => {
1257
- const found = [];
1258
- for (const view of viewsOf(text)) for (const secret of secrets) {
1259
- let at = view.text.indexOf(secret);
1260
- while (at !== -1) {
1261
- found.push([view.toSource(at), view.toSource(at + secret.length)]);
1262
- at = view.text.indexOf(secret, at + secret.length);
1263
- }
1264
- }
1265
- const joined = [];
1266
- for (const [start, end] of found.toSorted((a, b) => a[0] - b[0])) {
1267
- const last = joined.at(-1);
1268
- if (last !== void 0 && start < last[1]) last[1] = Math.max(last[1], end);
1269
- else joined.push([start, end]);
1270
- }
1271
- return joined;
1272
- };
1273
- /** The strings and numbers inside a parsed JSON document. */
1274
- const leavesOf = (value) => {
1275
- if (typeof value === "string") return [value];
1276
- if (typeof value === "number") return [String(value)];
1277
- if (typeof value === "object" && value !== null) return Object.values(value).flatMap((item) => leavesOf(item));
1278
- return [];
1279
- };
1280
- /** A value's leaves, when it is a JSON object or array. */
1281
- const jsonLeavesOf = (value) => {
1282
- if (!/^\s*[[{]/u.test(value)) return [];
1283
- try {
1284
- return leavesOf(JSON.parse(value));
1285
- } catch {
1286
- return [];
1287
- }
1288
- };
1289
- /** A value, and the pieces of it a program may well print on their own. */
1290
- const formsOf = (value) => [
1291
- value,
1292
- value.trim(),
1293
- ...value.split(/\r?\n|\r/u).map((line) => line.trim()),
1294
- ...jsonLeavesOf(value)
1295
- ];
1296
- /** Replaces the values, in any form JSON writes them, with `[masked]`. */
1297
- const scrubberOf = (values) => {
1298
- const secrets = [...new Set(values.flatMap((value) => formsOf(value)))].filter((secret) => secret.trim().length >= MIN_SECRET_LENGTH);
1299
- return (text) => {
1300
- if (secrets.length === 0) return text;
1301
- const parts = [];
1302
- let from = 0;
1303
- for (const [start, end] of rangesOf(text, secrets)) {
1304
- parts.push(text.slice(from, start), "[masked]");
1305
- from = end;
1306
- }
1307
- parts.push(text.slice(from));
1308
- return parts.join("");
1309
- };
1310
- };
1311
1350
  // ../nodes/src/tool-env.ts
1312
1351
  /**
1313
1352
  * @file What a tool node's program sees besides what it inherits: who it is
@@ -1386,6 +1425,23 @@ const inheritedNames = (environ) => (environ[MASKED_ENV] ?? "").split(",").filte
1386
1425
  */
1387
1426
  const maskedNamesOfRun = (workflow, own, registered, environ = process.env) => [...new Set([workflow, ...calledWorkflows(workflow, own, registered, { everyCall: true })].flatMap((one) => secretNamesOf(one)).concat(mcpVariablesOf(workflow, own.root), inheritedNames(environ)))];
1388
1427
  /**
1428
+ * The env-value scrubber of a recorded run, from its workflow file as it is
1429
+ * now, for what is written to it from outside the run (a person's note);
1430
+ * `undefined` when the file is gone or no longer parses.
1431
+ */
1432
+ const scrubberOfRun = (run, registered, environ = process.env) => {
1433
+ let text;
1434
+ try {
1435
+ text = readFileSync(run.file, "utf8");
1436
+ } catch {
1437
+ return;
1438
+ }
1439
+ const parsed = parseWorkflow(text);
1440
+ if (!parsed.ok) return void 0;
1441
+ const own = placeFor(path.dirname(run.file), registered);
1442
+ return runScrubber(maskedNamesOfRun(parsed.workflow, own, registered, environ), environ);
1443
+ };
1444
+ /**
1389
1445
  * A program learns the names too, never their values: `ork send` masks with
1390
1446
  * them before it writes to the store, and a run the program starts passes them
1391
1447
  * on.
@@ -1395,18 +1451,12 @@ const namesToMask = (workflow, environ, also = []) => [.../* @__PURE__ */ new Se
1395
1451
  ...also,
1396
1452
  ...inheritedNames(environ)
1397
1453
  ])];
1398
- const valuesOf = (names, environ) => names.flatMap((name) => {
1399
- const value = readEnv(environ, name);
1400
- return value === void 0 ? [] : [value];
1401
- });
1402
1454
  /** What a process in a program masks: what its run told it to. */
1403
- const inheritedScrubber = (environ) => scrubberOf(valuesOf(inheritedNames(environ), environ));
1404
- /** What a run masks in a message of its own: the same names' values. */
1405
- const runScrubber = (names, environ) => scrubberOf(valuesOf(names, environ));
1455
+ const inheritedScrubber = (environ) => scrubberOf(envValuesOf(inheritedNames(environ), environ));
1406
1456
  /** What a run tells a run it calls: names, never values. */
1407
1457
  const maskedEnv = (names) => names.length === 0 ? {} : { [MASKED_ENV]: names.join(",") };
1408
1458
  /** The values to mask: what the workflow reads, and the names given. */
1409
- const secretsOf = (workflow, environ, also = []) => valuesOf(namesToMask(workflow, environ, also), environ);
1459
+ const secretsOf = (workflow, environ, also = []) => envValuesOf(namesToMask(workflow, environ, also), environ);
1410
1460
  /** A node's `env:`, rendered. */
1411
1461
  const renderEnv = (node, context, environ) => {
1412
1462
  const values = {};
@@ -1490,21 +1540,16 @@ const clearStateFiles = (runDir, warn) => {
1490
1540
  /** How much of stdout and stderr a node's output keeps. */
1491
1541
  const OUTPUT_LIMIT = 65536;
1492
1542
  const ERROR_LIMIT = 1024;
1543
+ /** The interpreters whose script's commands a heavy check reads. */
1544
+ const SHELLS = /* @__PURE__ */ new Set([
1545
+ "sh",
1546
+ "bash",
1547
+ "zsh"
1548
+ ]);
1493
1549
  /** Whether `args` starts with one of the allowed prefixes (`"pr merge"`). */
1494
1550
  const isAllowed = (args, allow) => allow === void 0 || allow.some((prefix) => {
1495
1551
  return prefix.trim().split(/\s+/u).every((word, i) => args[i] === word);
1496
1552
  });
1497
- const render = (text, context) => {
1498
- const template = parseTemplate(text);
1499
- if (!template.ok) return {
1500
- ok: false,
1501
- error: template.message
1502
- };
1503
- return {
1504
- ok: true,
1505
- value: renderTemplate(template.parts, runScope(context.inputs, context.outputs, context.runId, context.item))
1506
- };
1507
- };
1508
1553
  /** The program and arguments a tool node runs, before the allow check. */
1509
1554
  const commandOf = (node, context) => {
1510
1555
  const line = commandLineOf(node);
@@ -1515,12 +1560,14 @@ const commandOf = (node, context) => {
1515
1560
  const tokens = tokenize(line);
1516
1561
  if (!tokens.ok) return tokens;
1517
1562
  const args = [];
1563
+ let afterSeparator = false;
1518
1564
  for (const token of tokens.args) {
1519
1565
  if (node.script !== void 0) {
1520
1566
  args.push(token);
1521
1567
  continue;
1522
1568
  }
1523
- const rendered = render(token, context);
1569
+ const rendered = renderArg(token, context, !afterSeparator);
1570
+ afterSeparator ||= token === "--";
1524
1571
  if (!rendered.ok) return rendered;
1525
1572
  args.push(rendered.value);
1526
1573
  }
@@ -1570,7 +1617,7 @@ const toolExecutor = (given, options = {}) => async (context) => {
1570
1617
  if (!isAllowed(checked, allow)) return failed$1(scrub(`${markValue(checked.join(" "))} is not in the node's allow list (${(allow ?? []).map((entry) => markValue(entry)).join(", ")})`));
1571
1618
  let cwd = context.cwd;
1572
1619
  if (node.cwd !== void 0) {
1573
- const rendered = render(node.cwd, context);
1620
+ const rendered = renderArg(node.cwd, context);
1574
1621
  if (!rendered.ok) return failed$1(rendered.error);
1575
1622
  cwd = path.resolve(context.cwd, rendered.value);
1576
1623
  }
@@ -1592,7 +1639,7 @@ const toolExecutor = (given, options = {}) => async (context) => {
1592
1639
  ...node.script === void 0 ? [] : ["<script>"]
1593
1640
  ].join(" "));
1594
1641
  const execute = async () => {
1595
- const release = await context.machine?.slot([command.program, ...command.args].join(" "), context.signal);
1642
+ const release = await context.machine?.slot([command.program, ...command.args].join(" "), context.signal, node.script !== void 0 && SHELLS.has(path.basename(command.program)) ? { script: node.script } : {});
1596
1643
  context.emit({
1597
1644
  type: "tool.called",
1598
1645
  id: context.id,
@@ -2515,4 +2562,4 @@ const missingPrograms = (workflow, installed = (program) => onPath(program)) =>
2515
2562
  }
2516
2563
  return [...missing.values()];
2517
2564
  };
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 };
2565
+ export { clearOutputFiles as _, builtinExecutors as a, runScrubber as b, 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, scrubberOfRun as p, missingPrograms as r, mcpCallExecutor as s, envProblems as t, inheritedScrubber as u, namedMcpEntries as v, resultOf as y };