@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.
- package/dist/{__build_info__-C1Nm0hlJ.mjs → __build_info__-dPf4cEGL.mjs} +3 -134
- package/dist/{attach-EP374oMP.mjs → attach-BQl8nQz2.mjs} +5 -5
- package/dist/{attach-RQx__RzH.mjs → attach-DhZQ87Nz.mjs} +49 -224
- package/dist/bin.mjs +204 -154
- package/dist/{brief-Bs3cMQU0.mjs → brief-BQbeoPMc.mjs} +19 -9
- package/dist/{builtin-files-yYaNTQRU.mjs → builtin-files-CWL3JEo-.mjs} +1 -1
- package/dist/{command-tree-BkSYWmYJ.mjs → command-tree-B6uvMJq1.mjs} +1 -1
- package/dist/{completion-DIXWlnAl.mjs → completion-DKPuD_Rw.mjs} +47 -14
- package/dist/{context-Dq-VM_sq.mjs → context-Bw6R_EYq.mjs} +3 -3
- package/dist/detached-BQlbmqR0.mjs +38 -0
- package/dist/{find-k489laBa.mjs → find-B30-rd-u.mjs} +1 -1
- package/dist/flags-ByYBx-Xb.mjs +170 -0
- package/dist/{follow-ups-DD8NTPe6.mjs → follow-ups-Cxca1Jgg.mjs} +24 -18
- package/dist/{format-DQc9A_ei.mjs → format-C-nEZv-P.mjs} +27 -11
- package/dist/{help-groups-hB6kY34d.mjs → help-groups-BpRgBSTh.mjs} +13 -7
- package/dist/{in-a-run-D_9GlAlS.mjs → in-a-run-CZFzoT3R.mjs} +2 -2
- package/dist/{install-PVlV2QPq.mjs → install-CsqK1avz.mjs} +2 -183
- package/dist/{limits-CHqBO7wb.mjs → limits-CXh052II.mjs} +1 -1
- package/dist/{mcp-D38Ajb0b.mjs → mcp-B4wME9C4.mjs} +6 -6
- package/dist/{once-DuUzpzL2.mjs → once-BKn3pOtg.mjs} +3 -3
- package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
- package/dist/{pause-Bx3ePGrn.mjs → pause-B1grR1cl.mjs} +3 -3
- package/dist/{print-n2qetUHc.mjs → print-Bd8MWVJk.mjs} +1 -1
- package/dist/{project-records-Cune1l6I.mjs → project-records-BOEVs69i.mjs} +1 -1
- package/dist/{parse-BzWpMN-a.mjs → refusals-DQQX3AIb.mjs} +10 -1
- package/dist/{relay-D7RN4m68.mjs → relay-CeNiq3GT.mjs} +1 -1
- package/dist/{run-OOWQIKMJ.mjs → run-C0C54UiU.mjs} +116 -137
- package/dist/{run-BfxdsQJk.mjs → run-D6yU2rLf.mjs} +1 -1
- package/dist/{send-CKth_vgn.mjs → send-DqgF58hb.mjs} +2 -2
- package/dist/{src-DLsFsmOD.mjs → src-Ciy7i3se.mjs} +882 -71
- package/dist/{src-BHod38UQ.mjs → src-D2qpd-G7.mjs} +394 -247
- package/dist/{src-DmmrX2_C.mjs → src-DP02RP9O.mjs} +227 -180
- package/dist/{src-DOjTY2ET.mjs → src-n-u4TGev.mjs} +186 -43
- package/dist/{store-DACH768X.mjs → store-2KXzMCEN.mjs} +1 -1
- package/dist/ui/assets/app-C5vO1M-l.css +1 -0
- package/dist/ui/assets/{app-CN3acQ-1.js → app-ICEKpy_C.js} +21 -20
- package/dist/ui/assets/{index-B2uGxA4W.js → index-BGaix5wh.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CJ-BX12n.mjs → ui-CAvygxTb.mjs} +53 -24
- package/dist/{usage-DxZ6JGd4.mjs → usage-BFBco5OS.mjs} +7 -7
- package/dist/{validate-BS0nCccW.mjs → validate-UrmC5AG1.mjs} +5 -5
- package/dist/{watch-Dp7IB4FR.mjs → watch-DTxkgB8R.mjs} +5 -5
- package/dist/{workflows-Djo1edlK.mjs → workflows-8GV-SeRm.mjs} +28 -4
- package/dist/{worktrees-Drof_Nyx.mjs → worktrees-CUdUvZlO.mjs} +9 -6
- package/package.json +1 -1
- package/dist/flags-B4ZZWspN.mjs +0 -125
- package/dist/post-2CaJz_mV.mjs +0 -46
- package/dist/ui/assets/app-9Q-62Ywz.css +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as runOnce } from "./run-
|
|
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(
|
|
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 = []) =>
|
|
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 =
|
|
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 =
|
|
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,
|
|
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 };
|