taskflow-core 0.2.8 → 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.
- package/README.md +53 -12
- package/dist/build-info.json +2 -2
- package/dist/detached-runner.js +4 -1
- package/dist/detached-runner.js.map +1 -1
- package/dist/exec/driver.d.ts +6 -0
- package/dist/exec/driver.d.ts.map +1 -1
- package/dist/exec/driver.js +4 -0
- package/dist/exec/driver.js.map +1 -1
- package/dist/exec/step-kinds.d.ts.map +1 -1
- package/dist/exec/step-kinds.js +10 -3
- package/dist/exec/step-kinds.js.map +1 -1
- package/dist/exec/step.d.ts +9 -0
- package/dist/exec/step.d.ts.map +1 -1
- package/dist/exec/step.js +23 -4
- package/dist/exec/step.js.map +1 -1
- package/dist/final-output.d.ts +6 -5
- package/dist/final-output.d.ts.map +1 -1
- package/dist/final-output.js +14 -10
- package/dist/final-output.js.map +1 -1
- package/dist/flowir/compile.d.ts.map +1 -1
- package/dist/flowir/compile.js +1 -0
- package/dist/flowir/compile.js.map +1 -1
- package/dist/flowir/phasefp.d.ts.map +1 -1
- package/dist/flowir/phasefp.js +1 -0
- package/dist/flowir/phasefp.js.map +1 -1
- package/dist/library/search.d.ts.map +1 -1
- package/dist/library/search.js +2 -2
- package/dist/library/search.js.map +1 -1
- package/dist/resources/baseline.d.ts +1 -1
- package/dist/resources/baseline.d.ts.map +1 -1
- package/dist/resources/baseline.js +1 -1
- package/dist/resources/baseline.js.map +1 -1
- package/dist/resume.d.ts.map +1 -1
- package/dist/resume.js +2 -0
- package/dist/resume.js.map +1 -1
- package/dist/runner-core.d.ts +17 -1
- package/dist/runner-core.d.ts.map +1 -1
- package/dist/runner-core.js +33 -12
- package/dist/runner-core.js.map +1 -1
- package/dist/runtime/phases/script.d.ts +13 -0
- package/dist/runtime/phases/script.d.ts.map +1 -1
- package/dist/runtime/phases/script.js +25 -1
- package/dist/runtime/phases/script.js.map +1 -1
- package/dist/runtime.d.ts +17 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +60 -22
- package/dist/runtime.js.map +1 -1
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +4 -0
- package/dist/schema.js.map +1 -1
- package/dist/store.d.ts +24 -14
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +606 -93
- package/dist/store.js.map +1 -1
- package/package.json +2 -2
package/dist/runtime.js
CHANGED
|
@@ -13,7 +13,7 @@ import * as path from "node:path";
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import { coerceArray, evaluateCondition, interpolate, interpolateValue, safeParse, tryEvaluateCondition } from "./interpolate.js";
|
|
15
15
|
import { contractViolations } from "./contract.js";
|
|
16
|
-
import { isFailed, isTransientError, mapWithConcurrencyLimit, PHASE_TIMEOUT_ABORT_GRACE_MS, sanitizeErrorMessage } from "./runner-core.js";
|
|
16
|
+
import { failedResultDisplayOutput, hasMeaningfulFailedOutput, isFailed, isTransientError, mapWithConcurrencyLimit, PHASE_TIMEOUT_ABORT_GRACE_MS, sanitizeErrorMessage } from "./runner-core.js";
|
|
17
17
|
/** Default runner used when no host injected one: fail loudly rather than
|
|
18
18
|
* silently spawn anything (core is host-neutral and cannot spawn pi/codex). */
|
|
19
19
|
const noRunnerInjected = async (_cwd, _agents, agentName, task) => ({
|
|
@@ -105,16 +105,14 @@ function buildInterpolationContext(state, previousOutput, locals, onRead, reflex
|
|
|
105
105
|
function resultToPhaseState(id, r, inputHash, parseJson) {
|
|
106
106
|
const failed = isFailed(r);
|
|
107
107
|
const attempts = attemptsOf(r);
|
|
108
|
-
// For failed phases, embed the error info in the output so downstream
|
|
109
|
-
// phases (and the user) can see what went wrong. The raw r.output is
|
|
110
|
-
// often a useless placeholder like "(upstream error: subagent failed)".
|
|
111
108
|
const output = failed
|
|
112
|
-
? r
|
|
109
|
+
? failedResultDisplayOutput(r)
|
|
113
110
|
: r.output;
|
|
114
111
|
return {
|
|
115
112
|
id,
|
|
116
113
|
status: failed ? "failed" : "done",
|
|
117
114
|
output,
|
|
115
|
+
partialOutput: failed && hasMeaningfulFailedOutput(r) ? true : undefined,
|
|
118
116
|
json: parseJson && !failed ? safeParse(r.output) : undefined,
|
|
119
117
|
usage: r.usage,
|
|
120
118
|
model: r.model,
|
|
@@ -304,7 +302,8 @@ function mergePhaseState(id, results, inputHash, parseJson) {
|
|
|
304
302
|
// which model produced the merged output.
|
|
305
303
|
const model = ran.find((r) => r.model !== undefined)?.model;
|
|
306
304
|
// Combine outputs as a labelled list; also expose a JSON array of outputs.
|
|
307
|
-
// For failed items,
|
|
305
|
+
// For failed items, preserve a real partial body; replace only the shared
|
|
306
|
+
// transport placeholder with the concise diagnostic.
|
|
308
307
|
// Labels are positionally aligned to the ORIGINAL `over` array: we iterate
|
|
309
308
|
// over ALL results (including budget-skipped, which are filtered to null) and
|
|
310
309
|
// use `results.length` as N, so item k's label reads `[k/N]` matching its
|
|
@@ -316,7 +315,7 @@ function mergePhaseState(id, results, inputHash, parseJson) {
|
|
|
316
315
|
if (r.stopReason === "budget-skipped")
|
|
317
316
|
return null;
|
|
318
317
|
const label = `### [${i + 1}/${results.length}] ${r.agent}${isFailed(r) ? " (failed)" : ""}`;
|
|
319
|
-
const content = isFailed(r) ? (r
|
|
318
|
+
const content = isFailed(r) ? failedResultDisplayOutput(r) : r.output;
|
|
320
319
|
return `${label}\n\n${content}`;
|
|
321
320
|
})
|
|
322
321
|
.filter((x) => x !== null)
|
|
@@ -332,6 +331,7 @@ function mergePhaseState(id, results, inputHash, parseJson) {
|
|
|
332
331
|
id,
|
|
333
332
|
status: anyFailed ? "failed" : "done",
|
|
334
333
|
output: combinedText,
|
|
334
|
+
partialOutput: ran.some((r) => isFailed(r) && hasMeaningfulFailedOutput(r)) ? true : undefined,
|
|
335
335
|
json: jsonArray,
|
|
336
336
|
usage,
|
|
337
337
|
model,
|
|
@@ -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.
|
|
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
|
|
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
|
-
//
|
|
613
|
-
//
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
2561
|
+
if (!deps.loadSavedFlow)
|
|
2530
2562
|
return failPhase(phase.id, `flow phase '${phase.id}': no sub-flow loader available`);
|
|
2531
|
-
|
|
2532
|
-
if (!
|
|
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
|
});
|