@agentproto/workflow-runtime 0.13.0 → 0.13.1
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 +51 -14
- package/dist/index.d.ts +28 -5
- package/dist/index.mjs +102 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -301,9 +301,13 @@ try {
|
|
|
301
301
|
|
|
302
302
|
This is a manual retry, not a resumable run object — `runWorkflow` has no
|
|
303
303
|
notion of "the run that failed"; the cacheKey is just a namespace the caller
|
|
304
|
-
re-supplies. A first-class `run.retry
|
|
305
|
-
|
|
306
|
-
|
|
304
|
+
re-supplies. A first-class `run.retry` verb that resumes a named run without
|
|
305
|
+
the caller re-threading `workflow`/`input`/`cacheKey` by hand — AIP-58 P5 —
|
|
306
|
+
is a HOST concern, not something this transport-agnostic package implements
|
|
307
|
+
itself: see `@agentproto/runtime`'s `WorkflowRunner.retry()` / the
|
|
308
|
+
`workflow_retry` MCP tool, which owns runId allocation and an always-on
|
|
309
|
+
internal journal (so it works even when the original run never passed a
|
|
310
|
+
`cacheKey` at all — every step it runs is journaled internally either way).
|
|
307
311
|
|
|
308
312
|
### Run workspace (AIP-58 §4)
|
|
309
313
|
|
|
@@ -347,12 +351,18 @@ steps:
|
|
|
347
351
|
|
|
348
352
|
`path` is read relative to `$run.workspace` (absolute paths, and any path
|
|
349
353
|
that would resolve OUTSIDE the workspace, throw). The step hashes
|
|
350
|
-
(`sha256`) and sizes the file, copies it to `artifactsDir/<
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
(`sha256`) and sizes the file, copies it to `artifactsDir/<basename of path,
|
|
355
|
+
sanitized>` — `path: "briefs/latest.md"` above lands at
|
|
356
|
+
`artifacts/latest.md`, keeping the extension, NOT the bare key
|
|
357
|
+
(`artifacts/brief`) — and binds/reports an `ArtifactEntry` — `{ key, path:
|
|
358
|
+
"artifacts/<basename>", sha256, size, stepId, contentType? }` (`path` here is
|
|
359
|
+
relative to the RUN WORKSPACE ROOT, the parent of `$run.workspace` itself —
|
|
360
|
+
not the source location; always read it back rather than assuming a name).
|
|
361
|
+
If two keys' files share a basename, the second one claimed is prefixed with
|
|
362
|
+
its own sanitized key (`artifacts/<key>-<basename>`) instead of silently
|
|
363
|
+
overwriting the first — deterministic, same result run to run. Pass
|
|
364
|
+
`onArtifact` to `runWorkflow` to observe every one recorded, cache hit or
|
|
365
|
+
fresh.
|
|
356
366
|
|
|
357
367
|
A declarative WORKFLOW.md manifest may instead declare **`outputsFiles`**
|
|
358
368
|
(AIP-16, amended by AIP-58 §4 with `required`) at the top level — checked
|
|
@@ -400,11 +410,38 @@ relocates (copies) the file from the ORIGINAL run's `artifactsDir` into the
|
|
|
400
410
|
CURRENT run's own before returning — the two runs' workspaces stay fully
|
|
401
411
|
disjoint on disk; only the *bytes* are reused, exactly the same way
|
|
402
412
|
`run.replay`'s journal-sourced step reuse is specified to work (AIP-58 §6).
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
413
|
+
|
|
414
|
+
A plain cacheable `tool`/`agent` step gets the same treatment, not a narrower
|
|
415
|
+
one (this used to be a documented gap — see "Cache key vs. the run
|
|
416
|
+
workspace" below for why it had to stop being one):
|
|
417
|
+
|
|
418
|
+
- **Hashing ignores the workspace's identity.** `hashResolvedInputs`
|
|
419
|
+
replaces every occurrence of `ctx.workspace` inside the serialized
|
|
420
|
+
resolved input/prompt (including trailing subpaths, e.g.
|
|
421
|
+
`<workspace>/cleaned/out.txt`) with a stable placeholder before hashing.
|
|
422
|
+
Two runs of the same logical step under the same `cacheKey` hash
|
|
423
|
+
identically even though AIP-58 §4 gives each one a fresh, disjoint
|
|
424
|
+
workspace directory — without this, ANY step whose input/prompt names
|
|
425
|
+
`$run.workspace` / `{{run.workspace}}` / `_workflowFsRoot` could never hit
|
|
426
|
+
the journal at all.
|
|
427
|
+
- **A hit relocates forward.** On a hit, every workspace-relative file/
|
|
428
|
+
directory the entry recorded (`StepCacheEntry.workspaceFiles` — collected,
|
|
429
|
+
best-effort, from every string in the step's output that resolved to a
|
|
430
|
+
real path under the ORIGINAL run's workspace) is copied into the matching
|
|
431
|
+
path under the CURRENT run's own; the recorded output's path strings are
|
|
432
|
+
then rewritten from the original workspace onto the current one. A
|
|
433
|
+
downstream step whose input reads one of those paths (`$steps.<id>.path`)
|
|
434
|
+
finds the bytes there, not just the (by-then-gone) original run's.
|
|
435
|
+
Best-effort, same posture as the `artifact` step's own relocation: a
|
|
436
|
+
source already swept by a host's `scratch/` retention policy is not this
|
|
437
|
+
run's problem to recover.
|
|
438
|
+
- **Still no cross-step content hashing.** This closes the *workspace-path*
|
|
439
|
+
gap only — the journal still hashes each step's own resolved inputs, not
|
|
440
|
+
a transitive hash of everything upstream. A step whose resolved input is
|
|
441
|
+
an opaque reference that stays textually identical across runs (a fixed
|
|
442
|
+
relative filename, an id) replays from cache on that basis alone,
|
|
443
|
+
regardless of whether the value behind that reference changed upstream —
|
|
444
|
+
same limitation any resolved-input-hash cache has, workspace or not.
|
|
408
445
|
|
|
409
446
|
### Step lifecycle callbacks
|
|
410
447
|
|
package/dist/index.d.ts
CHANGED
|
@@ -431,7 +431,11 @@ interface AgentStep {
|
|
|
431
431
|
interface ArtifactStep {
|
|
432
432
|
kind: "artifact";
|
|
433
433
|
id: string;
|
|
434
|
-
/** Artifact key —
|
|
434
|
+
/** Artifact key — identifies this artifact for `workflow_artifact_get`/
|
|
435
|
+
* `workflow_publish`. Its on-disk filename under `artifactsDir` is
|
|
436
|
+
* `path`'s own basename (sanitized), not the key — see {@link
|
|
437
|
+
* ArtifactEntry.path} — unless another key's file shares the same
|
|
438
|
+
* basename, in which case the key disambiguates it (F42). */
|
|
435
439
|
key: Selector<string> | string;
|
|
436
440
|
/** Path to the source file. Relative to `$run.workspace`; MUST resolve
|
|
437
441
|
* inside it (an absolute path or a `..`-escaping relative one throws). */
|
|
@@ -440,10 +444,15 @@ interface ArtifactStep {
|
|
|
440
444
|
}
|
|
441
445
|
/**
|
|
442
446
|
* AIP-58 §4/§1 `ArtifactEntry` — one run-scoped copy of a declared output
|
|
443
|
-
* file. `path` is
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
+
* file. `path` is `"artifacts/<basename>"` (F42: the declared source file's
|
|
448
|
+
* OWN basename, sanitized — e.g. `outputsFiles.pdf: {path: transcript.pdf}`
|
|
449
|
+
* ⇒ `artifacts/transcript.pdf` — keeping the extension, unlike the bare key),
|
|
450
|
+
* relative to the RUN WORKSPACE ROOT (`<runsRoot>/<runId>/`, the parent of
|
|
451
|
+
* `$run.workspace` itself) — never the original in-workspace location the
|
|
452
|
+
* file was read from. Two keys whose files share a basename get the SECOND
|
|
453
|
+
* one's name prefixed with its own sanitized key instead (deterministic,
|
|
454
|
+
* never a silent overwrite) — always read `path` back rather than assuming
|
|
455
|
+
* `artifacts/<key>` or `artifacts/<basename(path)>`.
|
|
447
456
|
*/
|
|
448
457
|
interface ArtifactEntry {
|
|
449
458
|
key: string;
|
|
@@ -691,6 +700,20 @@ interface StepCacheEntry {
|
|
|
691
700
|
* §4 forbids two runs sharing a workspace) copies the file forward from
|
|
692
701
|
* here into the new run's own `artifactsDir` instead of re-declaring it. */
|
|
693
702
|
artifactsDirAtCache?: string;
|
|
703
|
+
/** Set only for a cacheable `tool`/`agent` step (see `hashResolvedInputs`/
|
|
704
|
+
* `buildCacheEntry` in `run-workflow.ts`) whenever the host wires a run
|
|
705
|
+
* workspace: the absolute `$run.workspace` / `_workflowFsRoot` path this
|
|
706
|
+
* entry was cached under. A hit in a LATER run (a different workspace —
|
|
707
|
+
* AIP-58 §4 forbids two runs sharing one) rewrites `output`'s path
|
|
708
|
+
* strings from here onto the new run's own, and relocates
|
|
709
|
+
* {@link workspaceFiles} the same way. */
|
|
710
|
+
workspaceAtCache?: string;
|
|
711
|
+
/** Workspace-relative files/directories (under {@link workspaceAtCache})
|
|
712
|
+
* this entry's `output` pointed at and that existed on disk when the
|
|
713
|
+
* entry was written — copied forward into a later cache-hit run's own
|
|
714
|
+
* workspace so a downstream step reading one of these paths finds the
|
|
715
|
+
* bytes there too, not just in the original (by-then-gone) run's. */
|
|
716
|
+
workspaceFiles?: readonly string[];
|
|
694
717
|
}
|
|
695
718
|
/** Opt-in journal for cacheable steps. Host-injected; file-backed in the runtime. */
|
|
696
719
|
interface StepCache {
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runTool } from '@agentproto/driver';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import { execFile } from 'child_process';
|
|
4
|
-
import { stat, readFile, mkdir, writeFile, appendFile, readdir, copyFile } from 'fs/promises';
|
|
4
|
+
import { stat, readFile, mkdir, writeFile, appendFile, readdir, copyFile, cp } from 'fs/promises';
|
|
5
5
|
import { join, dirname, isAbsolute, basename, resolve, relative } from 'path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { assertKnownStepRefs } from '@agentproto/workflow';
|
|
@@ -1131,8 +1131,74 @@ function spentUsd(state) {
|
|
|
1131
1131
|
for (const c of state.costBySession.values()) total += c;
|
|
1132
1132
|
return total;
|
|
1133
1133
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1134
|
+
var WORKSPACE_HASH_PLACEHOLDER = "\0$run.workspace\0";
|
|
1135
|
+
function hashResolvedInputs(kind, resolved, workspace) {
|
|
1136
|
+
const serialized = `${kind}\0${JSON.stringify(resolved) ?? "undefined"}`;
|
|
1137
|
+
const normalized = workspace ? serialized.split(workspace).join(WORKSPACE_HASH_PLACEHOLDER) : serialized;
|
|
1138
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
1139
|
+
}
|
|
1140
|
+
function rewriteWorkspacePaths(value, from, to) {
|
|
1141
|
+
if (typeof value === "string") return value.split(from).join(to);
|
|
1142
|
+
if (Array.isArray(value)) return value.map((v) => rewriteWorkspacePaths(v, from, to));
|
|
1143
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
1144
|
+
const out = {};
|
|
1145
|
+
for (const [k, v] of Object.entries(value)) out[k] = rewriteWorkspacePaths(v, from, to);
|
|
1146
|
+
return out;
|
|
1147
|
+
}
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
function collectWorkspaceCandidates(value, workspace, out) {
|
|
1151
|
+
if (typeof value === "string") {
|
|
1152
|
+
if (value === workspace || value.startsWith(`${workspace}/`)) {
|
|
1153
|
+
const rel = relative(workspace, value);
|
|
1154
|
+
if (rel !== "" && !rel.startsWith("..") && !isAbsolute(rel)) out.add(rel);
|
|
1155
|
+
}
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (Array.isArray(value)) {
|
|
1159
|
+
for (const v of value) collectWorkspaceCandidates(v, workspace, out);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
1163
|
+
for (const v of Object.values(value)) collectWorkspaceCandidates(v, workspace, out);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async function collectWorkspaceFiles(out, workspace) {
|
|
1167
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1168
|
+
collectWorkspaceCandidates(out, workspace, candidates);
|
|
1169
|
+
const files = [];
|
|
1170
|
+
for (const rel of candidates) {
|
|
1171
|
+
try {
|
|
1172
|
+
await stat(join(workspace, rel));
|
|
1173
|
+
files.push(rel);
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return files;
|
|
1178
|
+
}
|
|
1179
|
+
async function buildCacheEntry(ctx, out, hash) {
|
|
1180
|
+
if (ctx.workspace === void 0) return { output: out, resolvedInputHash: hash };
|
|
1181
|
+
const workspaceFiles = await collectWorkspaceFiles(out, ctx.workspace);
|
|
1182
|
+
return {
|
|
1183
|
+
output: out,
|
|
1184
|
+
resolvedInputHash: hash,
|
|
1185
|
+
workspaceAtCache: ctx.workspace,
|
|
1186
|
+
...workspaceFiles.length > 0 ? { workspaceFiles } : {}
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
async function relocateCachedOutput(ctx, entry) {
|
|
1190
|
+
const from = entry.workspaceAtCache;
|
|
1191
|
+
const to = ctx.workspace;
|
|
1192
|
+
if (from === void 0 || to === void 0 || from === to) return entry.output;
|
|
1193
|
+
for (const rel of entry.workspaceFiles ?? []) {
|
|
1194
|
+
const dest = join(to, rel);
|
|
1195
|
+
try {
|
|
1196
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1197
|
+
await cp(join(from, rel), dest, { recursive: true });
|
|
1198
|
+
} catch {
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return rewriteWorkspacePaths(entry.output, from, to);
|
|
1136
1202
|
}
|
|
1137
1203
|
function stepJournalKey(ctx, step) {
|
|
1138
1204
|
return `${ctx.cacheKey}\0${step.id}\0${step.kind}${ctx.cacheKeySuffix ?? ""}`;
|
|
@@ -1149,10 +1215,10 @@ function completeStep(ctx, stepId, out) {
|
|
|
1149
1215
|
}
|
|
1150
1216
|
async function readStepCache(ctx, step, resolvedInputs) {
|
|
1151
1217
|
const key = stepJournalKey(ctx, step);
|
|
1152
|
-
const hash = hashResolvedInputs(step.kind, resolvedInputs);
|
|
1218
|
+
const hash = hashResolvedInputs(step.kind, resolvedInputs, ctx.workspace);
|
|
1153
1219
|
const entry = await ctx.cache.get(key);
|
|
1154
1220
|
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
1155
|
-
return { hit: true, output: entry
|
|
1221
|
+
return { hit: true, output: await relocateCachedOutput(ctx, entry) };
|
|
1156
1222
|
}
|
|
1157
1223
|
return { hit: false, key, hash };
|
|
1158
1224
|
}
|
|
@@ -1509,7 +1575,7 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1509
1575
|
if (c.hit) return cacheHit(ctx, step, c.output);
|
|
1510
1576
|
ctx.onStepStart?.(step.id);
|
|
1511
1577
|
const out = await runIt();
|
|
1512
|
-
await ctx.cache.set(c.key,
|
|
1578
|
+
await ctx.cache.set(c.key, await buildCacheEntry(ctx, out, c.hash));
|
|
1513
1579
|
return out;
|
|
1514
1580
|
}
|
|
1515
1581
|
case "transform":
|
|
@@ -1681,7 +1747,8 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1681
1747
|
cacheKey: ctx.cacheKey,
|
|
1682
1748
|
runGateCommand: ctx.runGateCommand,
|
|
1683
1749
|
onGateReport: ctx.onGateReport,
|
|
1684
|
-
spawned: ctx.spawned
|
|
1750
|
+
spawned: ctx.spawned,
|
|
1751
|
+
usedArtifactNames: ctx.usedArtifactNames
|
|
1685
1752
|
});
|
|
1686
1753
|
return child.output;
|
|
1687
1754
|
}
|
|
@@ -1697,7 +1764,7 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1697
1764
|
const c = await readStepCache(ctx, step, resolved);
|
|
1698
1765
|
if (c.hit) return cacheHit(ctx, step, c.output);
|
|
1699
1766
|
const out = await execAgentStep(step, ctx, b);
|
|
1700
|
-
await ctx.cache.set(c.key,
|
|
1767
|
+
await ctx.cache.set(c.key, await buildCacheEntry(ctx, out, c.hash));
|
|
1701
1768
|
return out;
|
|
1702
1769
|
}
|
|
1703
1770
|
case "gate":
|
|
@@ -1706,11 +1773,24 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1706
1773
|
return execArtifactStep(step, ctx, b);
|
|
1707
1774
|
}
|
|
1708
1775
|
}
|
|
1709
|
-
function
|
|
1710
|
-
const cleaned =
|
|
1776
|
+
function sanitizeArtifactFilename(name) {
|
|
1777
|
+
const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1711
1778
|
return cleaned.length > 0 ? cleaned : "artifact";
|
|
1712
1779
|
}
|
|
1713
|
-
|
|
1780
|
+
function reserveArtifactDestName(rawPath, key, usedNames) {
|
|
1781
|
+
const base = sanitizeArtifactFilename(basename(rawPath));
|
|
1782
|
+
let candidate = base;
|
|
1783
|
+
if (usedNames.has(candidate)) {
|
|
1784
|
+
const keyPart = sanitizeArtifactFilename(key);
|
|
1785
|
+
candidate = `${keyPart}-${base}`;
|
|
1786
|
+
for (let n = 2; usedNames.has(candidate); n++) {
|
|
1787
|
+
candidate = `${keyPart}-${n}-${base}`;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
usedNames.add(candidate);
|
|
1791
|
+
return candidate;
|
|
1792
|
+
}
|
|
1793
|
+
async function copyIntoArtifacts(stepId, key, rawPath, workspace, artifactsDir, contentType, usedNames) {
|
|
1714
1794
|
const abs = isAbsolute(rawPath) ? resolve(rawPath) : resolve(workspace, rawPath);
|
|
1715
1795
|
const rel = relative(workspace, abs);
|
|
1716
1796
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
@@ -1718,7 +1798,7 @@ async function copyIntoArtifacts(stepId, key, rawPath, workspace, artifactsDir,
|
|
|
1718
1798
|
}
|
|
1719
1799
|
const buf = await readFile(abs);
|
|
1720
1800
|
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
1721
|
-
const destName =
|
|
1801
|
+
const destName = reserveArtifactDestName(rawPath, key, usedNames);
|
|
1722
1802
|
await mkdir(artifactsDir, { recursive: true });
|
|
1723
1803
|
await writeFile(join(artifactsDir, destName), buf);
|
|
1724
1804
|
return {
|
|
@@ -1742,12 +1822,13 @@ async function execArtifactStep(step, ctx, b) {
|
|
|
1742
1822
|
const resolvedInputs = { key, path: rawPath };
|
|
1743
1823
|
const cacheOn = ctx.cache !== void 0 && ctx.cacheKey !== void 0;
|
|
1744
1824
|
const journalKey = cacheOn ? stepJournalKey(ctx, step) : void 0;
|
|
1745
|
-
const hash = cacheOn ? hashResolvedInputs(step.kind, resolvedInputs) : void 0;
|
|
1825
|
+
const hash = cacheOn ? hashResolvedInputs(step.kind, resolvedInputs, ctx.workspace) : void 0;
|
|
1746
1826
|
if (cacheOn) {
|
|
1747
1827
|
const entry = await ctx.cache.get(journalKey);
|
|
1748
1828
|
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
1749
1829
|
const out2 = entry.output;
|
|
1750
|
-
const destName =
|
|
1830
|
+
const destName = out2.path.slice("artifacts/".length);
|
|
1831
|
+
ctx.usedArtifactNames?.add(destName);
|
|
1751
1832
|
const destAbs = join(artifactsDir, destName);
|
|
1752
1833
|
const srcAbs = join(entry.artifactsDirAtCache ?? artifactsDir, destName);
|
|
1753
1834
|
if (srcAbs !== destAbs) {
|
|
@@ -1762,7 +1843,7 @@ async function execArtifactStep(step, ctx, b) {
|
|
|
1762
1843
|
}
|
|
1763
1844
|
}
|
|
1764
1845
|
ctx.onStepStart?.(step.id);
|
|
1765
|
-
const out = await copyIntoArtifacts(step.id, key, rawPath, workspace, artifactsDir, contentType);
|
|
1846
|
+
const out = await copyIntoArtifacts(step.id, key, rawPath, workspace, artifactsDir, contentType, ctx.usedArtifactNames);
|
|
1766
1847
|
if (cacheOn) {
|
|
1767
1848
|
await ctx.cache.set(journalKey, { output: out, resolvedInputHash: hash, artifactsDirAtCache: artifactsDir });
|
|
1768
1849
|
}
|
|
@@ -1780,7 +1861,7 @@ async function checkOutputsFiles(outputsFiles, ctx, workflowId, lastStepId) {
|
|
|
1780
1861
|
const abs = isAbsolute(rawPath) ? rawPath : resolve(ctx.workspace, rawPath);
|
|
1781
1862
|
let out;
|
|
1782
1863
|
try {
|
|
1783
|
-
out = await copyIntoArtifacts(lastStepId ?? workflowId, key, abs, ctx.workspace, ctx.artifactsDir, contract.contentType);
|
|
1864
|
+
out = await copyIntoArtifacts(lastStepId ?? workflowId, key, abs, ctx.workspace, ctx.artifactsDir, contract.contentType, ctx.usedArtifactNames);
|
|
1784
1865
|
} catch {
|
|
1785
1866
|
if (contract.required === true) {
|
|
1786
1867
|
throw new MissingArtifactError(key, lastStepId);
|
|
@@ -1811,7 +1892,11 @@ async function runFinally(steps, ctx, bodyFailed) {
|
|
|
1811
1892
|
async function runWorkflowInner(workflow, input, hooks, maxTotalCostUsd) {
|
|
1812
1893
|
const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd, cachedHits: /* @__PURE__ */ new Set() };
|
|
1813
1894
|
const ownsScope = hooks.spawned === void 0;
|
|
1814
|
-
const ctx = {
|
|
1895
|
+
const ctx = {
|
|
1896
|
+
state,
|
|
1897
|
+
...hooks,
|
|
1898
|
+
...ownsScope ? { spawned: [], usedArtifactNames: /* @__PURE__ */ new Set() } : {}
|
|
1899
|
+
};
|
|
1815
1900
|
let lastId;
|
|
1816
1901
|
let bodyFailed = false;
|
|
1817
1902
|
try {
|