@withone/cli 2.12.0 → 2.14.0

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.
@@ -29,7 +29,7 @@ import {
29
29
  writeAnalyticsQueue,
30
30
  writeUsageLog,
31
31
  writeUsageState
32
- } from "./chunk-EWHFGWB3.js";
32
+ } from "./chunk-XUAUDMQE.js";
33
33
 
34
34
  // src/lib/flow-runner.ts
35
35
  import fs5 from "fs";
@@ -40,7 +40,7 @@ import crypto from "crypto";
40
40
  import fs4 from "fs";
41
41
  import os2 from "os";
42
42
  import path5 from "path";
43
- import { exec, spawn } from "child_process";
43
+ import { exec, spawn as spawn2 } from "child_process";
44
44
  import { promisify } from "util";
45
45
 
46
46
  // src/lib/install-context.ts
@@ -1506,8 +1506,8 @@ var FLOW_SCHEMA = {
1506
1506
  configKey: "code",
1507
1507
  description: "JS code \u2014 inline source or an external .mjs module under the flow's lib/ folder",
1508
1508
  fields: {
1509
- source: { type: "string", required: false, description: 'Inline JS function body (flow context as $, supports await). Mutually exclusive with "module".' },
1510
- module: { type: "string", required: false, description: 'Relative path to a .mjs file under the flow folder (e.g. "lib/normalize.mjs"). Reads $ from stdin as JSON, writes result to stdout as JSON. Mutually exclusive with "source".' }
1509
+ source: { type: "string", required: false, description: 'Inline JS function body (flow context as $, supports await). Runs in the sandbox: no files, programs, network or timers. Mutually exclusive with "module".' },
1510
+ module: { type: "string", required: false, description: 'Relative path to a .mjs file under the flow folder (e.g. "lib/normalize.mjs"). Reads $ from stdin as JSON, writes result to stdout as JSON. Mutually exclusive with "source". Runs with full Node access and requires --allow-code.' }
1511
1511
  },
1512
1512
  example: {
1513
1513
  id: "processData",
@@ -1735,7 +1735,7 @@ function generateFlowGuide() {
1735
1735
  Workflows live in \`.one/flows/\` (relative to your current working directory \u2014 the CLI does NOT walk up parent directories or fall back to a global location) and chain actions across platforms. Two layouts are supported:
1736
1736
 
1737
1737
  - **Folder layout (REQUIRED for new flows)** \u2014 \`.one/flows/<key>/flow.json\`, with an optional \`lib/\` subfolder for JavaScript modules. This is like a skill: the folder groups the JSON spec with any JavaScript modules it needs, so the whole flow is shareable. **Always create new flows in this layout.**
1738
- - **Single-file layout (DEPRECATED)** \u2014 \`.one/flows/<key>.flow.json\`. Still loads and runs for backward compatibility, but is deprecated. Do not create new flows in this layout. When editing an existing single-file flow, migrate it to the folder layout: move \`<key>.flow.json\` to \`<key>/flow.json\` and extract any non-trivial \`code.source\` blocks into \`<key>/lib/*.mjs\` modules.
1738
+ - **Single-file layout (DEPRECATED)** \u2014 \`.one/flows/<key>.flow.json\`. Still loads and runs for backward compatibility, but is deprecated. Do not create new flows in this layout. When editing an existing single-file flow, migrate it to the folder layout: move \`<key>.flow.json\` to \`<key>/flow.json\`. Move a \`code.source\` block into a \`<key>/lib/*.mjs\` module only if it needs files, network or timers: modules run with full Node access and make the flow require \`--allow-code\`.
1739
1739
 
1740
1740
  **Subdirectory groups** \u2014 Flows can be organized into subdirectories: \`.one/flows/<group>/<key>/flow.json\`. For example:
1741
1741
  \`\`\`
@@ -1755,16 +1755,17 @@ When resolving a flow by key, the CLI checks the folder layout first, then the d
1755
1755
  **Agents: always inspect a flow before running it.** Nothing about a flow's runtime requirements is guessable from its name. Before \`flow execute\`, do one of these:
1756
1756
 
1757
1757
  1. Run \`one --agent flow list\` \u2014 the JSON output includes \`requiresBash\`, \`usesCodeModules\`, \`inputs\` (with \`autoResolvable\` flags), \`stepTypes\`, and the flow's \`description\`. This is the fastest path.
1758
- 2. Read the flow's \`description\` field directly from the JSON. Flow authors are required (see "Author conventions" below) to state any \`--allow-bash\` requirement and any non-auto-resolving inputs in the description.
1758
+ 2. Read the flow's \`description\` field directly from the JSON. Flow authors are required (see "Author conventions" below) to state any \`--allow-bash\` or \`--allow-code\` requirement and any non-auto-resolving inputs in the description.
1759
1759
  3. Run \`one --agent flow execute <key> --dry-run\` to see the resolved inputs and step plan without side effects.
1760
1760
 
1761
- If you skip this step you will hit errors like *"Workflow X contains bash steps. Re-run with --allow-bash."* \u2014 the CLI now pre-flights and fails fast, so you won't waste a long run, but the error is still avoidable by reading first.
1761
+ If you skip this step you will hit errors like *"Workflow X contains bash steps. Re-run with --allow-bash."* (or the same for \`code.module\` steps and \`--allow-code\`) \u2014 the CLI now pre-flights and fails fast, so you won't waste a long run, but the error is still avoidable by reading first.
1762
1762
 
1763
1763
  ## Author conventions \u2014 WRITE flows that are safe to execute blind
1764
1764
 
1765
1765
  When you create a flow, its \`description\` field is the contract with future executors (human or agent). It MUST state:
1766
1766
 
1767
1767
  - **\`--allow-bash\` if any step is type \`bash\`.** Example: *"Fetches recent Gmail threads and summarizes them with Claude Haiku. Requires \`--allow-bash\`."*
1768
+ - **\`--allow-code\` if any code step uses \`code.module\`.** Modules run with full Node access, so they are gated like bash.
1768
1769
  - **Every input that does NOT have a \`connection\` hint.** Connection inputs auto-resolve when exactly one matching connection exists; everything else must be passed via \`-i name=value\` and the description must name it.
1769
1770
  - **Any files/directories the flow writes to** so operators know what will be modified on disk.
1770
1771
 
@@ -1782,6 +1783,7 @@ one --agent flow execute <key> -i name=value # Execute (bare
1782
1783
  one --agent flow execute <group/key> -i name=value # Execute (namespaced key)
1783
1784
  one --agent flow execute <key> --dry-run --mock # Test with mock data
1784
1785
  one --agent flow execute <key> --allow-bash # Enable bash steps
1786
+ one --agent flow execute <key> --allow-code # Enable code.module steps (full Node)
1785
1787
  one --agent flow runs [flowKey] # List past runs
1786
1788
  one --agent flow resume <runId> # Resume failed run
1787
1789
  one --agent flow scaffold [template] # Generate a starter template
@@ -1828,7 +1830,7 @@ If you're editing an existing \`.one/flows/<key>.flow.json\`, migrate it \u2014
1828
1830
 
1829
1831
  1. \`mkdir -p .one/flows/<key>/lib\`
1830
1832
  2. Move the file: \`mv .one/flows/<key>.flow.json .one/flows/<key>/flow.json\`
1831
- 3. For each non-trivial \`code\` step with inline \`source\`, extract it into \`lib/<step-id>.mjs\` (see translation pattern below) and swap the step config from \`{ "source": "..." }\` to \`{ "module": "lib/<step-id>.mjs" }\`. One-liners can stay inline.
1833
+ 3. Leave inline \`code.source\` in place unless it needs files, network or timers; it runs in the sandbox and needs no flag. If it does need them, extract it into \`lib/<step-id>.mjs\` (see translation pattern below) and swap the step config to \`{ "module": "lib/<step-id>.mjs" }\`. That gives it full Node access and makes the flow require \`--allow-code\`.
1832
1834
  4. Validate: \`one --agent flow validate <key>\`.
1833
1835
  5. Run it and confirm behavior is unchanged.
1834
1836
 
@@ -2196,13 +2198,272 @@ Set timeout to at least 180000ms (3 min). Run Claude-heavy flows sequentially, n
2196
2198
 
2197
2199
  - Connection keys are **inputs**, not hardcoded
2198
2200
  - Action IDs in examples are placeholders \u2014 always use \`actions find\`
2199
- - Inline \`code.source\` steps allow \`require('crypto' | 'buffer' | 'url' | 'path')\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked
2200
- - For anything beyond one-liners, use \`code.module\` to point at a \`.mjs\` file in the flow's \`lib/\` folder \u2014 runs as a child \`node\` process with full Node APIs, reads \`$\` from stdin, writes JSON to stdout
2201
+ - Transforms, conditions, \`if\`/\`unless\` and inline \`code.source\` run in a sandbox (separate process, no files, programs, network or environment of its own). \`code.source\` allows \`require('crypto' | 'buffer' | 'url' | 'path')\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked. \`$\` is a copy: return a value, don't mutate \`$\`
2202
+ - When a step needs files, network or timers, use \`code.module\` to point at a \`.mjs\` file in the flow's \`lib/\` folder \u2014 runs as a child \`node\` process with full Node APIs, reads \`$\` from stdin, writes JSON to stdout. Requires \`--allow-code\`
2201
2203
  - Bash steps require \`--allow-bash\` flag
2202
2204
  - State is persisted after every step \u2014 resume picks up where it left off`);
2203
2205
  return sections.join("\n");
2204
2206
  }
2205
2207
 
2208
+ // src/lib/flow-sandbox.ts
2209
+ import { spawn } from "child_process";
2210
+ var syncTimeoutMs = 3e4;
2211
+ var callTimeoutMs = 6e4;
2212
+ var SANDBOX_NODE_FLAGS = ["--permission", "--disallow-code-generation-from-strings"];
2213
+ function sandboxEnv() {
2214
+ if (process.platform === "win32" && process.env.SystemRoot) return { SystemRoot: process.env.SystemRoot };
2215
+ return {};
2216
+ }
2217
+ var BOOTSTRAP = String.raw`
2218
+ 'use strict';
2219
+ const vm = require('node:vm');
2220
+ const proc = process;
2221
+ const MODULES = {
2222
+ buffer: require('node:buffer'),
2223
+ crypto: require('node:crypto'),
2224
+ url: require('node:url'),
2225
+ path: require('node:path'),
2226
+ };
2227
+ const BLOCKED = new Set(['fs', 'http', 'https', 'net', 'child_process', 'process', 'os',
2228
+ 'cluster', 'dgram', 'tls', 'vm', 'worker_threads']);
2229
+ const sandboxRequire = async (name) => {
2230
+ const clean = String(name).replace(/^node:/, '');
2231
+ if (BLOCKED.has(clean)) throw new Error('Module "' + name + '" is blocked in code steps');
2232
+ if (!Object.prototype.hasOwnProperty.call(MODULES, clean)) {
2233
+ throw new Error('Module "' + name + '" not available. Allowed: ' + Object.keys(MODULES).join(', '));
2234
+ }
2235
+ return MODULES[clean];
2236
+ };
2237
+ const SYNC_TIMEOUT = Number(proc.argv[proc.argv.length - 1]) || 30000;
2238
+ const send = proc.send.bind(proc);
2239
+
2240
+ for (const k of ['process', 'require', 'module', 'exports', '__filename', '__dirname',
2241
+ 'fetch', 'WebSocket', 'EventSource', 'Request', 'Response', 'Headers', 'FormData',
2242
+ 'setTimeout', 'setInterval', 'setImmediate', 'clearTimeout', 'clearInterval',
2243
+ 'clearImmediate', 'navigator', 'global']) {
2244
+ try { delete globalThis[k]; } catch (_) { /* non-configurable: leave it */ }
2245
+ }
2246
+
2247
+ const describe = (err) => {
2248
+ if (err && typeof err === 'object') {
2249
+ return { name: String(err.name || 'Error'), message: String(err.message), stack: String(err.stack || '') };
2250
+ }
2251
+ return { name: 'Error', message: String(err), stack: '' };
2252
+ };
2253
+
2254
+ const reply = (id, value) => {
2255
+ try {
2256
+ send({ id, ok: true, value });
2257
+ } catch (err) {
2258
+ send({ id, ok: false, error: { name: 'Error', stack: '', message:
2259
+ 'returned a value that cannot be copied out of the sandbox (functions, symbols and class instances with private state cannot be passed between steps): ' + (err && err.message) } });
2260
+ }
2261
+ };
2262
+
2263
+ // The sandbox's copy of $. The host sends only the parts that changed since
2264
+ // the last call (see syncFor in flow-sandbox.ts), so a loop over a large
2265
+ // step output doesn't copy that output again on every iteration.
2266
+ let store = { input: {}, env: {}, steps: {}, loop: {} };
2267
+ const applySync = (sync) => {
2268
+ if (sync.reset) store = { input: {}, env: {}, steps: {}, loop: {} };
2269
+ if ('input' in sync) store.input = sync.input;
2270
+ if ('env' in sync) store.env = sync.env;
2271
+ if ('loop' in sync) store.loop = sync.loop;
2272
+ if (sync.steps) for (const k of Object.keys(sync.steps)) store.steps[k] = sync.steps[k];
2273
+ if (sync.drop) for (const k of sync.drop) delete store.steps[k];
2274
+ };
2275
+
2276
+ proc.on('message', async (msg) => {
2277
+ const { id, kind, code, sync, filename } = msg;
2278
+ try {
2279
+ applySync(sync);
2280
+ globalThis.__one_ctx = store;
2281
+ globalThis.__one_require = sandboxRequire;
2282
+ let value;
2283
+ if (kind === 'expr') {
2284
+ const script = new vm.Script('(function ($) { return (' + code + '\n) })(__one_ctx)', { filename: filename || 'expression' });
2285
+ value = script.runInThisContext({ timeout: SYNC_TIMEOUT });
2286
+ } else {
2287
+ // Same wrapper shape as new AsyncFunction('$', 'require', body), so
2288
+ // stack line numbers are offset by exactly 2, as the engine expects.
2289
+ const script = new vm.Script('(async function anonymous($,require\n) {\n' + code + '\n})(__one_ctx, __one_require)', { filename });
2290
+ value = await script.runInThisContext({ timeout: SYNC_TIMEOUT });
2291
+ }
2292
+ reply(id, value);
2293
+ } catch (err) {
2294
+ send({ id, ok: false, error: describe(err) });
2295
+ } finally {
2296
+ delete globalThis.__one_ctx;
2297
+ delete globalThis.__one_require;
2298
+ }
2299
+ });
2300
+ send({ ready: true });
2301
+ `;
2302
+ var child = null;
2303
+ var ready = null;
2304
+ var nextId = 1;
2305
+ var pending2 = /* @__PURE__ */ new Map();
2306
+ var ERROR_CTORS = {
2307
+ TypeError,
2308
+ SyntaxError,
2309
+ ReferenceError,
2310
+ RangeError,
2311
+ EvalError,
2312
+ URIError
2313
+ };
2314
+ function rebuildError(e) {
2315
+ const Ctor = ERROR_CTORS[e.name] ?? Error;
2316
+ const err = new Ctor(e.message);
2317
+ if (!ERROR_CTORS[e.name] && e.name && e.name !== "Error") err.name = e.name;
2318
+ if (e.stack) err.stack = e.stack;
2319
+ return err;
2320
+ }
2321
+ function setRef(on) {
2322
+ if (!child) return;
2323
+ const channel = child.channel;
2324
+ if (on) {
2325
+ child.ref();
2326
+ channel?.ref();
2327
+ } else {
2328
+ child.unref();
2329
+ channel?.unref();
2330
+ }
2331
+ }
2332
+ function failAll(proc, err) {
2333
+ for (const [id, p] of pending2) {
2334
+ if (p.proc !== proc) continue;
2335
+ clearTimeout(p.timer);
2336
+ pending2.delete(id);
2337
+ p.reject(err);
2338
+ }
2339
+ }
2340
+ function start() {
2341
+ if (ready) return ready;
2342
+ const proc = spawn(
2343
+ process.execPath,
2344
+ [...SANDBOX_NODE_FLAGS, "-e", BOOTSTRAP, "--", String(syncTimeoutMs)],
2345
+ {
2346
+ stdio: ["ignore", "inherit", "inherit", "ipc"],
2347
+ serialization: "advanced",
2348
+ env: sandboxEnv(),
2349
+ windowsHide: true
2350
+ }
2351
+ );
2352
+ child = proc;
2353
+ ready = new Promise((resolve2, reject) => {
2354
+ const onReady = (msg) => {
2355
+ if (msg && typeof msg === "object" && msg.ready) {
2356
+ proc.off("message", onReady);
2357
+ resolve2();
2358
+ }
2359
+ };
2360
+ proc.on("message", onReady);
2361
+ proc.once("error", (err) => reject(new Error(`Could not start the flow code sandbox: ${err.message}`)));
2362
+ proc.once("exit", (code, signal) => {
2363
+ reject(new Error(`Flow code sandbox exited before it was ready (code ${code}, signal ${signal})`));
2364
+ });
2365
+ });
2366
+ proc.on("message", (msg) => {
2367
+ if (typeof msg?.id !== "number") return;
2368
+ const p = pending2.get(msg.id);
2369
+ if (!p) return;
2370
+ clearTimeout(p.timer);
2371
+ pending2.delete(msg.id);
2372
+ if (pending2.size === 0) setRef(false);
2373
+ if (msg.ok) p.resolve(msg.value);
2374
+ else p.reject(rebuildError(msg.error ?? { name: "Error", message: "unknown sandbox error", stack: "" }));
2375
+ });
2376
+ proc.on("exit", (code, signal) => {
2377
+ if (child === proc) {
2378
+ child = null;
2379
+ ready = null;
2380
+ }
2381
+ failAll(proc, new Error(`Flow code sandbox stopped (code ${code}, signal ${signal})`));
2382
+ });
2383
+ proc.on("error", (err) => {
2384
+ if (child === proc) {
2385
+ child = null;
2386
+ ready = null;
2387
+ }
2388
+ failAll(proc, err);
2389
+ });
2390
+ return ready;
2391
+ }
2392
+ var synced = null;
2393
+ function syncFor(proc, context) {
2394
+ const prev = synced && synced.proc === proc && synced.context === context && synced.stepsObj === context.steps ? synced : null;
2395
+ const sync = {};
2396
+ if (!prev) sync.reset = true;
2397
+ if (!prev || prev.input !== context.input) sync.input = context.input;
2398
+ if (!prev || prev.env !== context.env) sync.env = context.env;
2399
+ if (!prev || prev.loop !== context.loop) sync.loop = context.loop;
2400
+ const stepUpdates = {};
2401
+ let anyStep = false;
2402
+ const nextSteps = /* @__PURE__ */ new Map();
2403
+ for (const [k, v] of Object.entries(context.steps)) {
2404
+ nextSteps.set(k, v);
2405
+ if (!prev || prev.steps.get(k) !== v) {
2406
+ stepUpdates[k] = v;
2407
+ anyStep = true;
2408
+ }
2409
+ }
2410
+ if (anyStep) sync.steps = stepUpdates;
2411
+ if (prev) {
2412
+ const drop = [...prev.steps.keys()].filter((k) => !nextSteps.has(k));
2413
+ if (drop.length) sync.drop = drop;
2414
+ }
2415
+ const commit = () => {
2416
+ synced = {
2417
+ proc,
2418
+ context,
2419
+ stepsObj: context.steps,
2420
+ input: context.input,
2421
+ env: context.env,
2422
+ loop: context.loop,
2423
+ steps: nextSteps
2424
+ };
2425
+ };
2426
+ return { sync, commit };
2427
+ }
2428
+ async function call(kind, code, context, filename) {
2429
+ await start();
2430
+ const proc = child;
2431
+ const id = nextId++;
2432
+ return new Promise((resolve2, reject) => {
2433
+ const timer = setTimeout(() => {
2434
+ pending2.delete(id);
2435
+ reject(new Error(`timed out after ${callTimeoutMs / 1e3}s`));
2436
+ if (child === proc) {
2437
+ child = null;
2438
+ ready = null;
2439
+ }
2440
+ proc.kill();
2441
+ }, callTimeoutMs);
2442
+ timer.unref();
2443
+ pending2.set(id, { resolve: resolve2, reject, timer, proc });
2444
+ setRef(true);
2445
+ const { sync, commit } = syncFor(proc, context);
2446
+ try {
2447
+ proc.send({ id, kind, code, sync, filename });
2448
+ commit();
2449
+ } catch (err) {
2450
+ synced = null;
2451
+ clearTimeout(timer);
2452
+ pending2.delete(id);
2453
+ if (pending2.size === 0) setRef(false);
2454
+ reject(new Error(
2455
+ `the flow context contains a value that cannot be passed to the sandbox: ${err.message}`
2456
+ ));
2457
+ }
2458
+ });
2459
+ }
2460
+ function sandboxEvaluate(expr, context) {
2461
+ return call("expr", expr, context, "expression");
2462
+ }
2463
+ function sandboxRunCode(source, context, filename) {
2464
+ return call("code", source, context, filename);
2465
+ }
2466
+
2206
2467
  // src/lib/flow-engine.ts
2207
2468
  var execAsync = promisify(exec);
2208
2469
  function sleep2(ms) {
@@ -2456,13 +2717,12 @@ function resolveValue(value, context) {
2456
2717
  }
2457
2718
  return value;
2458
2719
  }
2459
- function evaluateExpression(expr, context) {
2460
- const fn = new Function("$", `return (${expr})`);
2461
- return fn(context);
2720
+ async function evaluateExpression(expr, context) {
2721
+ return sandboxEvaluate(expr, context);
2462
2722
  }
2463
- function evaluateCondition(expr, context) {
2723
+ async function evaluateCondition(expr, context) {
2464
2724
  try {
2465
- return Boolean(evaluateExpression(expr, context));
2725
+ return Boolean(await evaluateExpression(expr, context));
2466
2726
  } catch (err) {
2467
2727
  if (err instanceof TypeError) return false;
2468
2728
  throw err;
@@ -2538,13 +2798,13 @@ function stepInterpolationSource(step) {
2538
2798
  return {};
2539
2799
  }
2540
2800
  }
2541
- function dryResolveStep(step, context) {
2801
+ async function dryResolveStep(step, context) {
2542
2802
  const base = { stepId: step.id, name: step.name, type: step.type };
2543
2803
  const expr = step.type === "transform" ? step.transform?.expression : step.type === "condition" ? step.condition?.expression : step.type === "while" ? step.while?.condition : void 0;
2544
2804
  if (step.type === "transform" || step.type === "condition" || step.type === "while") {
2545
2805
  if (!expr) return { ...base, references: [] };
2546
2806
  try {
2547
- return { ...base, references: [], resolved: evaluateExpression(expr, context) };
2807
+ return { ...base, references: [], resolved: await evaluateExpression(expr, context) };
2548
2808
  } catch (err) {
2549
2809
  if (err instanceof TypeError) {
2550
2810
  const deps = expressionDeferredOn(expr, context);
@@ -2577,57 +2837,23 @@ function dryResolveStep(step, context) {
2577
2837
  }
2578
2838
  return { ...base, references, resolved };
2579
2839
  }
2580
- function dryResolveAllSteps(steps, context) {
2840
+ async function dryResolveAllSteps(steps, context) {
2581
2841
  const out = [];
2582
- const visit = (list) => {
2842
+ const visit = async (list) => {
2583
2843
  for (const step of list) {
2584
- out.push(dryResolveStep(step, context));
2844
+ out.push(await dryResolveStep(step, context));
2585
2845
  const config = step;
2586
2846
  for (const { configKey, fieldName } of getNestedStepsKeys()) {
2587
2847
  const block = config[configKey];
2588
2848
  if (block && Array.isArray(block[fieldName])) {
2589
- visit(block[fieldName]);
2849
+ await visit(block[fieldName]);
2590
2850
  }
2591
2851
  }
2592
2852
  }
2593
2853
  };
2594
- visit(steps);
2854
+ await visit(steps);
2595
2855
  return out;
2596
2856
  }
2597
- var ALLOWED_MODULES = {
2598
- buffer: () => import("buffer"),
2599
- crypto: () => import("crypto"),
2600
- url: () => import("url"),
2601
- path: () => import("path")
2602
- };
2603
- var BLOCKED_MODULES = /* @__PURE__ */ new Set([
2604
- "fs",
2605
- "http",
2606
- "https",
2607
- "net",
2608
- "child_process",
2609
- "process",
2610
- "os",
2611
- "cluster",
2612
- "dgram",
2613
- "tls",
2614
- "vm",
2615
- "worker_threads"
2616
- ]);
2617
- function createSandboxedRequire() {
2618
- const cache = {};
2619
- return async (moduleName) => {
2620
- const clean = moduleName.replace(/^node:/, "");
2621
- if (BLOCKED_MODULES.has(clean)) {
2622
- throw new Error(`Module "${moduleName}" is blocked in code steps`);
2623
- }
2624
- if (!ALLOWED_MODULES[clean]) {
2625
- throw new Error(`Module "${moduleName}" not available. Allowed: ${Object.keys(ALLOWED_MODULES).join(", ")}`);
2626
- }
2627
- if (!cache[clean]) cache[clean] = await ALLOWED_MODULES[clean]();
2628
- return cache[clean];
2629
- };
2630
- }
2631
2857
  function stripCodeFences(text) {
2632
2858
  const trimmed = text.trim();
2633
2859
  const match = trimmed.match(/^```(?:\w*)\s*\n([\s\S]*?)\n\s*```\s*$/);
@@ -2637,9 +2863,9 @@ function stripToJson(text) {
2637
2863
  const s = stripCodeFences(text);
2638
2864
  const candidates = [s.indexOf("{"), s.indexOf("[")].filter((i) => i >= 0);
2639
2865
  if (candidates.length === 0) return s;
2640
- const start = Math.min(...candidates);
2866
+ const start2 = Math.min(...candidates);
2641
2867
  const end = Math.max(s.lastIndexOf("}"), s.lastIndexOf("]"));
2642
- return end > start ? s.slice(start, end + 1) : s;
2868
+ return end > start2 ? s.slice(start2, end + 1) : s;
2643
2869
  }
2644
2870
  function unwrapClaudeEnvelope(stdout, stepId) {
2645
2871
  const trimmed = stdout.trim();
@@ -2727,8 +2953,8 @@ async function executeActionStep(step, context, api, permissions, allowedActionI
2727
2953
  output: result.responseData
2728
2954
  };
2729
2955
  }
2730
- function executeTransformStep(step, context) {
2731
- const output = evaluateExpression(step.transform.expression, context);
2956
+ async function executeTransformStep(step, context) {
2957
+ const output = await evaluateExpression(step.transform.expression, context);
2732
2958
  return { status: "success", output, response: output };
2733
2959
  }
2734
2960
  async function executeCodeStep(step, context, options) {
@@ -2740,15 +2966,9 @@ async function executeCodeStep(step, context, options) {
2740
2966
  if (typeof config.source !== "string") {
2741
2967
  throw new Error(`Code step "${step.id}" must define either "source" or "module"`);
2742
2968
  }
2743
- const AsyncFunction = Object.getPrototypeOf(async function() {
2744
- }).constructor;
2745
- const sandboxedRequire = createSandboxedRequire();
2746
2969
  const sourceURL = `code:${step.id}`;
2747
- const taggedSource = `${config.source}
2748
- //# sourceURL=${sourceURL}`;
2749
- const fn = new AsyncFunction("$", "require", taggedSource);
2750
2970
  try {
2751
- const output = await fn(context, sandboxedRequire);
2971
+ const output = await sandboxRunCode(config.source, context, sourceURL);
2752
2972
  return { status: "success", output, response: output };
2753
2973
  } catch (err) {
2754
2974
  throw rewriteCodeStepError(err, step.id, config.source, sourceURL);
@@ -2780,6 +3000,9 @@ function rewriteCodeStepError(err, stepId, source, sourceURL) {
2780
3000
  return err;
2781
3001
  }
2782
3002
  async function executeCodeModule(stepId, modulePath, context, options) {
3003
+ if (!options.allowCode) {
3004
+ throw new Error(`Code step "${stepId}" runs module "${modulePath}" with full Node access and requires the --allow-code flag`);
3005
+ }
2783
3006
  const rootDir = options.rootDir;
2784
3007
  if (!rootDir) {
2785
3008
  throw new Error(`Code step "${stepId}" uses module "${modulePath}" but no flow rootDir is available. Flows that use code modules must be loaded via loadFlowWithMeta.`);
@@ -2799,16 +3022,16 @@ async function executeCodeModule(stepId, modulePath, context, options) {
2799
3022
  void _omitEnv;
2800
3023
  const stdinPayload = JSON.stringify(safeContext);
2801
3024
  return await new Promise((resolve2, reject) => {
2802
- const child = spawn(process.execPath, [absPath], {
3025
+ const child2 = spawn2(process.execPath, [absPath], {
2803
3026
  cwd: rootDir,
2804
3027
  stdio: ["pipe", "pipe", "pipe"]
2805
3028
  });
2806
3029
  const stdoutChunks = [];
2807
3030
  const stderrChunks = [];
2808
- child.stdout.on("data", (c) => stdoutChunks.push(c));
2809
- child.stderr.on("data", (c) => stderrChunks.push(c));
2810
- child.on("error", (err) => reject(err));
2811
- child.on("close", (code) => {
3031
+ child2.stdout.on("data", (c) => stdoutChunks.push(c));
3032
+ child2.stderr.on("data", (c) => stderrChunks.push(c));
3033
+ child2.on("error", (err) => reject(err));
3034
+ child2.on("close", (code) => {
2812
3035
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
2813
3036
  const stderr = Buffer.concat(stderrChunks).toString("utf-8");
2814
3037
  if (code !== 0) {
@@ -2826,13 +3049,13 @@ async function executeCodeModule(stepId, modulePath, context, options) {
2826
3049
  reject(new Error(`Code module "${modulePath}" did not print valid JSON to stdout: ${err.message}`));
2827
3050
  }
2828
3051
  });
2829
- child.stdin.write(stdinPayload);
2830
- child.stdin.end();
3052
+ child2.stdin.write(stdinPayload);
3053
+ child2.stdin.end();
2831
3054
  });
2832
3055
  }
2833
3056
  async function executeConditionStep(step, context, api, permissions, allowedActionIds, options, flowStack) {
2834
3057
  const condition = step.condition;
2835
- const result = evaluateCondition(condition.expression, context);
3058
+ const result = await evaluateCondition(condition.expression, context);
2836
3059
  const branch = result ? condition.then : condition.else || [];
2837
3060
  const branchResults = await executeSteps(branch, context, api, permissions, allowedActionIds, options, void 0, flowStack);
2838
3061
  return {
@@ -2975,7 +3198,7 @@ async function executeWhileStep(step, context, api, permissions, allowedActionId
2975
3198
  };
2976
3199
  for (let iteration = 0; iteration < maxIterations; iteration++) {
2977
3200
  if (iteration > 0) {
2978
- const conditionResult = evaluateCondition(config.condition, context);
3201
+ const conditionResult = await evaluateCondition(config.condition, context);
2979
3202
  if (!conditionResult) break;
2980
3203
  }
2981
3204
  await executeSteps(config.steps, context, api, permissions, allowedActionIds, options, void 0, flowStack);
@@ -3000,7 +3223,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
3000
3223
  if (flowStack.includes(resolvedKey)) {
3001
3224
  throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
3002
3225
  }
3003
- const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-6QTY3U2C.js");
3226
+ const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-SRO6YL62.js");
3004
3227
  const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
3005
3228
  const subContext = await executeFlow(
3006
3229
  subFlow,
@@ -3197,7 +3420,7 @@ function checkRequires(step, context) {
3197
3420
  }
3198
3421
  async function executeSingleStep(step, context, api, permissions, allowedActionIds, options, flowStack = []) {
3199
3422
  if (step.if) {
3200
- const condResult = evaluateCondition(step.if, context);
3423
+ const condResult = await evaluateCondition(step.if, context);
3201
3424
  if (!condResult) {
3202
3425
  const result = { status: "skipped" };
3203
3426
  context.steps[step.id] = result;
@@ -3205,7 +3428,7 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
3205
3428
  }
3206
3429
  }
3207
3430
  if (step.unless) {
3208
- const condResult = evaluateCondition(step.unless, context);
3431
+ const condResult = await evaluateCondition(step.unless, context);
3209
3432
  if (condResult) {
3210
3433
  const result = { status: "skipped" };
3211
3434
  context.steps[step.id] = result;
@@ -3269,7 +3492,7 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
3269
3492
  case "action":
3270
3493
  return await executeActionStep(step, context, api, permissions, allowedActionIds, options);
3271
3494
  case "transform":
3272
- return executeTransformStep(step, context);
3495
+ return await executeTransformStep(step, context);
3273
3496
  case "code":
3274
3497
  return await executeCodeStep(step, context, options);
3275
3498
  case "condition":
@@ -3366,7 +3589,7 @@ async function executeSteps(steps, context, api, permissions, allowedActionIds,
3366
3589
  if (isDryResolve && options.stopAfter === step.id) {
3367
3590
  options.onEvent?.({
3368
3591
  event: "step:dry-resolve",
3369
- ...dryResolveStep(step, context),
3592
+ ...await dryResolveStep(step, context),
3370
3593
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3371
3594
  });
3372
3595
  throw new FlowStopSignal(step.id);
@@ -3483,7 +3706,7 @@ async function executeFlow(flow, inputs, api, permissions, allowedActionIds, opt
3483
3706
  event: "flow:dry-run",
3484
3707
  flowKey: flow.key,
3485
3708
  resolvedInputs,
3486
- steps: dryResolveAllSteps(flow.steps, context),
3709
+ steps: await dryResolveAllSteps(flow.steps, context),
3487
3710
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3488
3711
  });
3489
3712
  return context;
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  okJson,
6
6
  requireMemoryInit
7
- } from "./chunk-J7JWBH45.js";
7
+ } from "./chunk-B5YDLVY5.js";
8
8
  import {
9
9
  isAgentMode,
10
10
  note
@@ -12,7 +12,7 @@ import {
12
12
  import {
13
13
  getBackend,
14
14
  upsertRecord
15
- } from "./chunk-6IRGRC3G.js";
15
+ } from "./chunk-4H5L3SBY.js";
16
16
 
17
17
  // src/commands/mem/migrate.ts
18
18
  import fs4 from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  requireMemoryInit
3
- } from "./chunk-J7JWBH45.js";
3
+ } from "./chunk-B5YDLVY5.js";
4
4
  import {
5
5
  error,
6
6
  isAgentMode,
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-Q6PWTATS.js";
9
9
  import {
10
10
  getBackend
11
- } from "./chunk-6IRGRC3G.js";
11
+ } from "./chunk-4H5L3SBY.js";
12
12
 
13
13
  // src/commands/mem/sql.ts
14
14
  async function memSqlCommand(sql) {