taskflow-core 0.2.9 → 0.2.10

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 (44) hide show
  1. package/README.md +26 -4
  2. package/dist/build-info.json +2 -2
  3. package/dist/detached-runner.js +4 -1
  4. package/dist/detached-runner.js.map +1 -1
  5. package/dist/exec/driver.d.ts +6 -0
  6. package/dist/exec/driver.d.ts.map +1 -1
  7. package/dist/exec/driver.js +3 -0
  8. package/dist/exec/driver.js.map +1 -1
  9. package/dist/exec/step-kinds.d.ts.map +1 -1
  10. package/dist/exec/step-kinds.js +10 -3
  11. package/dist/exec/step-kinds.js.map +1 -1
  12. package/dist/exec/step.d.ts +8 -0
  13. package/dist/exec/step.d.ts.map +1 -1
  14. package/dist/exec/step.js +17 -2
  15. package/dist/exec/step.js.map +1 -1
  16. package/dist/flowir/compile.d.ts.map +1 -1
  17. package/dist/flowir/compile.js +1 -0
  18. package/dist/flowir/compile.js.map +1 -1
  19. package/dist/flowir/phasefp.d.ts.map +1 -1
  20. package/dist/flowir/phasefp.js +1 -0
  21. package/dist/flowir/phasefp.js.map +1 -1
  22. package/dist/library/search.d.ts.map +1 -1
  23. package/dist/library/search.js +2 -2
  24. package/dist/library/search.js.map +1 -1
  25. package/dist/resume.d.ts.map +1 -1
  26. package/dist/resume.js +2 -0
  27. package/dist/resume.js.map +1 -1
  28. package/dist/runtime/phases/script.d.ts +13 -0
  29. package/dist/runtime/phases/script.d.ts.map +1 -1
  30. package/dist/runtime/phases/script.js +25 -1
  31. package/dist/runtime/phases/script.js.map +1 -1
  32. package/dist/runtime.d.ts +14 -3
  33. package/dist/runtime.d.ts.map +1 -1
  34. package/dist/runtime.js +53 -15
  35. package/dist/runtime.js.map +1 -1
  36. package/dist/schema.d.ts +1 -0
  37. package/dist/schema.d.ts.map +1 -1
  38. package/dist/schema.js +4 -0
  39. package/dist/schema.js.map +1 -1
  40. package/dist/store.d.ts +21 -14
  41. package/dist/store.d.ts.map +1 -1
  42. package/dist/store.js +578 -80
  43. package/dist/store.js.map +1 -1
  44. package/package.json +1 -1
package/dist/runtime.js CHANGED
@@ -600,18 +600,21 @@ function flowTreeUsesCwdBridge(def, loadFlow, seenUses = new Set()) {
600
600
  * after the root binding and cache policy were decided.
601
601
  */
602
602
  function snapshotFlowLoader(deps) {
603
- if (!deps.loadFlow || deps._flowLoaderSnapshot)
603
+ if ((!deps.loadSavedFlow && !deps.loadFlow) || deps._flowLoaderSnapshot)
604
604
  return deps;
605
- const source = deps.loadFlow;
605
+ const source = deps.loadSavedFlow
606
+ ?? ((name) => {
607
+ const def = deps.loadFlow?.(name);
608
+ return def === undefined ? undefined : { def };
609
+ });
606
610
  const snapshot = new Map();
607
- const loadFlow = (name) => {
611
+ const loadSavedFlow = (name) => {
608
612
  let entry = snapshot.get(name);
609
613
  if (!entry) {
610
614
  try {
611
615
  const loaded = source(name);
612
- // Loader-owned objects may be mutated asynchronously. Capability scan
613
- // and execution operate on a detached structured snapshot, never the
614
- // loader's live reference.
616
+ // Definition and provenance are detached and cached as one value, so
617
+ // mutable discovery cannot pair data from one flow with another path.
615
618
  entry = { ok: true, value: loaded === undefined ? undefined : structuredClone(loaded) };
616
619
  }
617
620
  catch (error) {
@@ -623,7 +626,8 @@ function snapshotFlowLoader(deps) {
623
626
  throw entry.error;
624
627
  return entry.value;
625
628
  };
626
- return { ...deps, loadFlow, _flowLoaderSnapshot: snapshot };
629
+ const loadFlow = (name) => loadSavedFlow(name)?.def;
630
+ return { ...deps, loadFlow, loadSavedFlow, _flowLoaderSnapshot: snapshot };
627
631
  }
628
632
  function sameDirectoryIdentity(a, b) {
629
633
  return !!a && !!b && a.canonicalPath === b.canonicalPath && a.device === b.device && a.inode === b.inode;
@@ -2154,7 +2158,7 @@ async function executePhaseInner(phase, state, deps, prior, emitProgress, _retry
2154
2158
  }
2155
2159
  // script — zero-token shell (spawn/timeout/size caps in runtime/phases/script.ts)
2156
2160
  if (type === "script") {
2157
- const { runScriptCommand, scriptResultToPhaseState, scriptSpawnErrorToPhaseState } = await import("./runtime/phases/script.js");
2161
+ const { resolveScriptCwd, runScriptCommand, scriptResultToPhaseState, scriptSpawnErrorToPhaseState } = await import("./runtime/phases/script.js");
2158
2162
  const cmd = phase.run;
2159
2163
  if (!cmd) {
2160
2164
  return {
@@ -2179,18 +2183,44 @@ async function executePhaseInner(phase, state, deps, prior, emitProgress, _retry
2179
2183
  if (stdinInterp?.missing.length)
2180
2184
  warnUnresolvedRefs(phase.id, stdinInterp.missing);
2181
2185
  const stdinInput = stdinInterp?.text;
2182
- const ck = cacheKeys(cc, [phase.id, JSON.stringify(interpRunText), stdinInput ?? ""]);
2186
+ const reads = readRefs.length ? readRefsToReads(readRefs, state) : undefined;
2187
+ let scriptCwd;
2188
+ const hasExplicitScriptCwd = deps._cwdOverride !== undefined || phase.cwd !== undefined;
2189
+ try {
2190
+ scriptCwd = resolveScriptCwd({
2191
+ executionCwd: effCwd,
2192
+ hasExplicitCwd: hasExplicitScriptCwd,
2193
+ scriptCwd: state.def.scriptCwd,
2194
+ flowSourceFile: state.flowSourceFile,
2195
+ flowSourceDirIdentity: state.flowSourceDirIdentity,
2196
+ });
2197
+ }
2198
+ catch (err) {
2199
+ return scriptSpawnErrorToPhaseState(phase.id, err, {
2200
+ inputHash: hashInput(phase.id, JSON.stringify(interpRunText), stdinInput ?? "", "script-cwd-unresolved"),
2201
+ reads,
2202
+ });
2203
+ }
2204
+ if (deps._cwdBoundary && !isPathWithin(deps._cwdBoundary, scriptCwd)) {
2205
+ return scriptSpawnErrorToPhaseState(phase.id, new Error("TF_CWD_BOUNDARY_ESCAPE: script cwd must remain inside the inherited cwd boundary"), {
2206
+ inputHash: hashInput(phase.id, JSON.stringify(interpRunText), stdinInput ?? "", "script-cwd-boundary-escape"),
2207
+ reads,
2208
+ });
2209
+ }
2210
+ const ck = cacheKeys(cc, [phase.id, JSON.stringify(interpRunText), stdinInput ?? "", `cwd:${scriptCwd}`]);
2183
2211
  const inputHash = ck.key;
2184
2212
  const cached = cachedPhase(cc, ck);
2185
2213
  if (cached)
2186
2214
  return cached;
2187
2215
  const SCRIPT_TIMEOUT_MS = phase.timeout ?? 60_000;
2188
- const reads = readRefs.length ? readRefsToReads(readRefs, state) : undefined;
2189
2216
  try {
2190
2217
  const invoke = () => runScriptCommand({
2191
2218
  interpRunText,
2192
2219
  arrayForm: Array.isArray(cmd),
2193
- cwd: effCwd,
2220
+ cwd: scriptCwd,
2221
+ cwdIdentity: !hasExplicitScriptCwd && state.def.scriptCwd === "flow"
2222
+ ? state.flowSourceDirIdentity
2223
+ : undefined,
2194
2224
  signal: deps.signal,
2195
2225
  stdinInput,
2196
2226
  timeoutMs: SCRIPT_TIMEOUT_MS,
@@ -2420,6 +2450,8 @@ async function executePhaseInner(phase, state, deps, prior, emitProgress, _retry
2420
2450
  }
2421
2451
  }
2422
2452
  let subDef;
2453
+ let subFlowSourceFile;
2454
+ let subFlowSourceDirIdentity;
2423
2455
  let name;
2424
2456
  let recursionKey; // identity used for cache key + recursion guard
2425
2457
  if (type === "expand" && !hasDef) {
@@ -2526,11 +2558,14 @@ async function executePhaseInner(phase, state, deps, prior, emitProgress, _retry
2526
2558
  const useName = phase.use;
2527
2559
  if (!useName)
2528
2560
  return failPhase(phase.id, `flow phase '${phase.id}' requires 'use' or 'def'`);
2529
- if (!deps.loadFlow)
2561
+ if (!deps.loadSavedFlow)
2530
2562
  return failPhase(phase.id, `flow phase '${phase.id}': no sub-flow loader available`);
2531
- subDef = deps.loadFlow(useName);
2532
- if (!subDef)
2563
+ const loaded = deps.loadSavedFlow(useName);
2564
+ if (!loaded)
2533
2565
  return failPhase(phase.id, `flow phase '${phase.id}': saved flow not found: '${useName}'`);
2566
+ subDef = loaded.def;
2567
+ subFlowSourceFile = loaded.filePath;
2568
+ subFlowSourceDirIdentity = loaded.sourceDirIdentity;
2534
2569
  name = useName;
2535
2570
  recursionKey = useName;
2536
2571
  }
@@ -2575,7 +2610,7 @@ async function executePhaseInner(phase, state, deps, prior, emitProgress, _retry
2575
2610
  // Every sub-flow cache identity includes the resolved definition. A saved
2576
2611
  // flow's name alone is insufficient: its contents can change without the
2577
2612
  // parent definition moving.
2578
- const flowIdentity = `${hasDef ? "def" : "flow"}:${name}:${JSON.stringify(subDef)}`;
2613
+ const flowIdentity = `${hasDef ? "def" : "flow"}:${name}:${JSON.stringify(subDef)}:source:${subFlowSourceFile ?? ""}:source-dir:${JSON.stringify(subFlowSourceDirIdentity ?? null)}`;
2579
2614
  const ck = cacheKeys(flowCc, [phase.id, flowIdentity, preRead, JSON.stringify(subArgs)]);
2580
2615
  const inputHash = ck.key;
2581
2616
  const cached = cachedPhase(flowCc, ck);
@@ -2606,6 +2641,8 @@ async function executePhaseInner(phase, state, deps, prior, emitProgress, _retry
2606
2641
  createdAt: Date.now(),
2607
2642
  updatedAt: Date.now(),
2608
2643
  cwd: effCwd,
2644
+ flowSourceFile: subFlowSourceFile,
2645
+ flowSourceDirIdentity: subFlowSourceDirIdentity,
2609
2646
  };
2610
2647
  // B8: pass this flow phase's preRead content to every sub-flow phase by
2611
2648
  // wrapping runTask — sub-phase preRead still gets prepended on top of it.
@@ -3840,6 +3877,7 @@ export async function executeTaskflow(state, deps) {
3840
3877
  verifiers: deps.verifiers,
3841
3878
  requestApproval: deps.requestApproval,
3842
3879
  loadFlow: deps.loadFlow,
3880
+ loadSavedFlow: deps.loadSavedFlow,
3843
3881
  _stack: deps._stack,
3844
3882
  _dynamic: deps._dynamic,
3845
3883
  });