@bastani/atomic 0.9.18 → 0.9.19-alpha.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/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +10 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +30383 -169
- package/dist/builtin/workflows/src/index.js +322 -86
- package/docs/quickstart.md +1 -1
- package/docs/workflows/operations.md +17 -4
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
|
@@ -5387,8 +5387,8 @@ function pendingStageIdForReplay(backend, workflowId, replayKey, stageName) {
|
|
|
5387
5387
|
return candidates.size === 1 ? candidates.values().next().value : undefined;
|
|
5388
5388
|
}
|
|
5389
5389
|
function createDurableStagePrimitive(input) {
|
|
5390
|
-
return (name, options) => {
|
|
5391
|
-
const replayKey = input.nextReplayKey(name);
|
|
5390
|
+
return (name, options, reservedReplayKey) => {
|
|
5391
|
+
const replayKey = reservedReplayKey ?? input.nextReplayKey(name);
|
|
5392
5392
|
const cached = stageCheckpointWithOutput(input.backend, input.workflowId, replayKey);
|
|
5393
5393
|
if (cached !== undefined) {
|
|
5394
5394
|
input.recordCachedStage?.(name, replayKey, cached);
|
|
@@ -6467,6 +6467,11 @@ function classifyWorkflowFailure(error) {
|
|
|
6467
6467
|
}
|
|
6468
6468
|
return failureForDecision(unknownDecision(), message, error);
|
|
6469
6469
|
}
|
|
6470
|
+
// dist/builtin/workflows/src/durable/workflow-status-transition.ts
|
|
6471
|
+
async function transitionDurableWorkflowStatus(backend, workflowId, expectedStatuses, status, pendingPrompts, resumable) {
|
|
6472
|
+
return await backend.transitionWorkflowStatus(workflowId, expectedStatuses, status, pendingPrompts, resumable);
|
|
6473
|
+
}
|
|
6474
|
+
|
|
6470
6475
|
// dist/builtin/workflows/src/durable/dbos-process-owner.ts
|
|
6471
6476
|
var DBOS_PROCESS_OWNER_KEY = Symbol.for("atomic-workflows/dbos-process-owner@1");
|
|
6472
6477
|
function emptyOwner2() {
|
|
@@ -9748,6 +9753,7 @@ function isTerminalStage(stage) {
|
|
|
9748
9753
|
function createStageScheduler(input) {
|
|
9749
9754
|
const releaseBarriers = new Map;
|
|
9750
9755
|
const cascadePauseOwners = new Map;
|
|
9756
|
+
let runBarrier;
|
|
9751
9757
|
const makeReleaseBarrier = () => {
|
|
9752
9758
|
const resolver = Promise.withResolvers();
|
|
9753
9759
|
resolver.promise.catch(() => {});
|
|
@@ -9869,6 +9875,8 @@ function createStageScheduler(input) {
|
|
|
9869
9875
|
}
|
|
9870
9876
|
};
|
|
9871
9877
|
const rejectReleaseBarriers = (reason) => {
|
|
9878
|
+
runBarrier?.reject(reason);
|
|
9879
|
+
runBarrier = undefined;
|
|
9872
9880
|
cascadePauseOwners.clear();
|
|
9873
9881
|
for (const [stageId, barrier] of releaseBarriers) {
|
|
9874
9882
|
releaseBarriers.delete(stageId);
|
|
@@ -9891,6 +9899,19 @@ function createStageScheduler(input) {
|
|
|
9891
9899
|
};
|
|
9892
9900
|
return {
|
|
9893
9901
|
tracker: input.tracker,
|
|
9902
|
+
isRunPaused: () => runBarrier !== undefined,
|
|
9903
|
+
pauseRun: () => {
|
|
9904
|
+
runBarrier ??= makeReleaseBarrier();
|
|
9905
|
+
},
|
|
9906
|
+
releaseRun: () => {
|
|
9907
|
+
const barrier = runBarrier;
|
|
9908
|
+
runBarrier = undefined;
|
|
9909
|
+
barrier?.resolve();
|
|
9910
|
+
},
|
|
9911
|
+
waitForRunRelease: async () => {
|
|
9912
|
+
while (runBarrier !== undefined)
|
|
9913
|
+
await runBarrier.promise;
|
|
9914
|
+
},
|
|
9894
9915
|
stageById,
|
|
9895
9916
|
setStageParentIds,
|
|
9896
9917
|
descendantsOf,
|
|
@@ -10190,6 +10211,24 @@ function createRunLimiter(defaultConcurrency) {
|
|
|
10190
10211
|
return new ConcurrencyLimiter(defaultConcurrency ?? 4);
|
|
10191
10212
|
}
|
|
10192
10213
|
|
|
10214
|
+
// dist/builtin/workflows/src/shared/abort.ts
|
|
10215
|
+
function raceAbort2(operation, signal) {
|
|
10216
|
+
if (signal === undefined)
|
|
10217
|
+
return operation;
|
|
10218
|
+
if (signal.aborted) {
|
|
10219
|
+
operation.catch(() => {});
|
|
10220
|
+
return Promise.reject(signal.reason ?? new DOMException("Workflow request aborted", "AbortError"));
|
|
10221
|
+
}
|
|
10222
|
+
const abort = Promise.withResolvers();
|
|
10223
|
+
const onAbort = () => {
|
|
10224
|
+
abort.reject(signal.reason ?? new DOMException("Workflow request aborted", "AbortError"));
|
|
10225
|
+
};
|
|
10226
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
10227
|
+
return Promise.race([operation, abort.promise]).finally(() => {
|
|
10228
|
+
signal.removeEventListener("abort", onAbort);
|
|
10229
|
+
});
|
|
10230
|
+
}
|
|
10231
|
+
|
|
10193
10232
|
// dist/builtin/workflows/src/engine/primitives/chain.ts
|
|
10194
10233
|
function createChainPrimitive(input) {
|
|
10195
10234
|
return async (steps, options = {}) => {
|
|
@@ -10896,6 +10935,8 @@ function wrapUiWithDurable(base, deps) {
|
|
|
10896
10935
|
return {
|
|
10897
10936
|
async input(promptText) {
|
|
10898
10937
|
const callerStack = new Error().stack;
|
|
10938
|
+
for (let wait = deps.beforeCall?.();wait !== undefined; wait = deps.beforeCall?.())
|
|
10939
|
+
await wait;
|
|
10899
10940
|
const identity = nextIdentity("input", promptText, callerStack);
|
|
10900
10941
|
const hit = cached(identity);
|
|
10901
10942
|
if (typeof hit === "string") {
|
|
@@ -10915,6 +10956,8 @@ function wrapUiWithDurable(base, deps) {
|
|
|
10915
10956
|
},
|
|
10916
10957
|
async confirm(message) {
|
|
10917
10958
|
const callerStack = new Error().stack;
|
|
10959
|
+
for (let wait = deps.beforeCall?.();wait !== undefined; wait = deps.beforeCall?.())
|
|
10960
|
+
await wait;
|
|
10918
10961
|
const identity = nextIdentity("confirm", message, callerStack);
|
|
10919
10962
|
const hit = cached(identity);
|
|
10920
10963
|
if (typeof hit === "boolean") {
|
|
@@ -10934,6 +10977,8 @@ function wrapUiWithDurable(base, deps) {
|
|
|
10934
10977
|
},
|
|
10935
10978
|
async select(message, options) {
|
|
10936
10979
|
const callerStack = new Error().stack;
|
|
10980
|
+
for (let wait = deps.beforeCall?.();wait !== undefined; wait = deps.beforeCall?.())
|
|
10981
|
+
await wait;
|
|
10937
10982
|
const identity = nextIdentity("select", message, callerStack, [...options]);
|
|
10938
10983
|
const hit = cached(identity);
|
|
10939
10984
|
if (typeof hit === "string") {
|
|
@@ -10953,6 +10998,8 @@ function wrapUiWithDurable(base, deps) {
|
|
|
10953
10998
|
},
|
|
10954
10999
|
async editor(initial) {
|
|
10955
11000
|
const callerStack = new Error().stack;
|
|
11001
|
+
for (let wait = deps.beforeCall?.();wait !== undefined; wait = deps.beforeCall?.())
|
|
11002
|
+
await wait;
|
|
10956
11003
|
const identity = nextIdentity("editor", initial ?? "", callerStack, initial ?? null);
|
|
10957
11004
|
const hit = cached(identity);
|
|
10958
11005
|
if (typeof hit === "string") {
|
|
@@ -10972,6 +11019,8 @@ function wrapUiWithDurable(base, deps) {
|
|
|
10972
11019
|
},
|
|
10973
11020
|
async custom(factory, options) {
|
|
10974
11021
|
const callerStack = new Error().stack;
|
|
11022
|
+
for (let wait = deps.beforeCall?.();wait !== undefined; wait = deps.beforeCall?.())
|
|
11023
|
+
await wait;
|
|
10975
11024
|
const replayIdentity = options?.replayIdentity ?? factory?.name ?? "custom";
|
|
10976
11025
|
const identity = nextIdentity("custom", replayIdentity, callerStack, { replayIdentity });
|
|
10977
11026
|
const hit = cachedCustom(identity);
|
|
@@ -11004,26 +11053,32 @@ function buildExitGatedUiContext(input) {
|
|
|
11004
11053
|
const base = input.opts.usePromptNodesForUi === true ? input.baseFromPromptNodes() : input.opts.executionMode === "non_interactive" && input.opts.ui === undefined ? makeHeadlessUnavailableUIContext() : normalizeUIContext(input.opts.ui);
|
|
11005
11054
|
const promptNodeReplay = input.opts.usePromptNodesForUi === true && input.opts.continuation !== undefined;
|
|
11006
11055
|
const durableBase = input.durableUi !== undefined && !promptNodeReplay ? wrapUiWithDurable(base, input.durableUi) : base;
|
|
11056
|
+
const invoke = (call) => {
|
|
11057
|
+
input.throwIfWorkflowExitSelected();
|
|
11058
|
+
if (!promptNodeReplay || input.durableUi?.beforeCall === undefined)
|
|
11059
|
+
return call();
|
|
11060
|
+
return withPromptCallerStack(new Error().stack, async () => {
|
|
11061
|
+
for (let wait = input.durableUi?.beforeCall?.();wait !== undefined; wait = input.durableUi?.beforeCall?.())
|
|
11062
|
+
await wait;
|
|
11063
|
+
input.throwIfWorkflowExitSelected();
|
|
11064
|
+
return call();
|
|
11065
|
+
});
|
|
11066
|
+
};
|
|
11007
11067
|
return {
|
|
11008
11068
|
async input(promptText) {
|
|
11009
|
-
input
|
|
11010
|
-
return await durableBase.input(promptText);
|
|
11069
|
+
return await invoke(() => durableBase.input(promptText));
|
|
11011
11070
|
},
|
|
11012
11071
|
async confirm(message) {
|
|
11013
|
-
|
|
11014
|
-
return await durableBase.confirm(message);
|
|
11072
|
+
return await invoke(() => durableBase.confirm(message));
|
|
11015
11073
|
},
|
|
11016
11074
|
async select(message, options) {
|
|
11017
|
-
|
|
11018
|
-
return await durableBase.select(message, options);
|
|
11075
|
+
return await invoke(() => durableBase.select(message, options));
|
|
11019
11076
|
},
|
|
11020
11077
|
async editor(initial) {
|
|
11021
|
-
|
|
11022
|
-
return await durableBase.editor(initial);
|
|
11078
|
+
return await invoke(() => durableBase.editor(initial));
|
|
11023
11079
|
},
|
|
11024
11080
|
async custom(factory, options) {
|
|
11025
|
-
|
|
11026
|
-
return await durableBase.custom(factory, options);
|
|
11081
|
+
return await invoke(() => durableBase.custom(factory, options));
|
|
11027
11082
|
}
|
|
11028
11083
|
};
|
|
11029
11084
|
}
|
|
@@ -12304,6 +12359,61 @@ function workflowChildRunId(checkpoint) {
|
|
|
12304
12359
|
return (parseWorkflowChildResult(checkpoint.output) ?? (hasCurrentIdentity ? undefined : parseLegacyWorkflowChildResult(checkpoint.output)))?.runId;
|
|
12305
12360
|
}
|
|
12306
12361
|
|
|
12362
|
+
// dist/builtin/workflows/src/engine/run-paused-stage.ts
|
|
12363
|
+
function deferStageUntilRunRelease(input) {
|
|
12364
|
+
let stage;
|
|
12365
|
+
const current = () => {
|
|
12366
|
+
input.signal.throwIfAborted();
|
|
12367
|
+
if (input.isPaused())
|
|
12368
|
+
throw new Error(`Workflow is paused; resume before synchronous access to stage ${input.name}`);
|
|
12369
|
+
stage ??= input.create();
|
|
12370
|
+
return stage;
|
|
12371
|
+
};
|
|
12372
|
+
const invoke = async (call) => {
|
|
12373
|
+
while (input.isPaused())
|
|
12374
|
+
await input.waitForRelease();
|
|
12375
|
+
return call(current());
|
|
12376
|
+
};
|
|
12377
|
+
return {
|
|
12378
|
+
name: input.name,
|
|
12379
|
+
prompt: (...args) => invoke((stage2) => stage2.prompt(...args)),
|
|
12380
|
+
complete: (...args) => invoke((stage2) => stage2.complete(...args)),
|
|
12381
|
+
sendUserMessage: (...args) => invoke((stage2) => stage2.sendUserMessage(...args)),
|
|
12382
|
+
steer: (...args) => invoke((stage2) => stage2.steer(...args)),
|
|
12383
|
+
followUp: (...args) => invoke((stage2) => stage2.followUp(...args)),
|
|
12384
|
+
subscribe: (...args) => current().subscribe(...args),
|
|
12385
|
+
get sessionFile() {
|
|
12386
|
+
return current().sessionFile;
|
|
12387
|
+
},
|
|
12388
|
+
get sessionId() {
|
|
12389
|
+
return current().sessionId;
|
|
12390
|
+
},
|
|
12391
|
+
setModel: (...args) => invoke((stage2) => stage2.setModel(...args)),
|
|
12392
|
+
setThinkingLevel: (...args) => current().setThinkingLevel(...args),
|
|
12393
|
+
cycleModel: () => invoke((stage2) => stage2.cycleModel()),
|
|
12394
|
+
cycleThinkingLevel: () => current().cycleThinkingLevel(),
|
|
12395
|
+
get agent() {
|
|
12396
|
+
return current().agent;
|
|
12397
|
+
},
|
|
12398
|
+
get model() {
|
|
12399
|
+
return current().model;
|
|
12400
|
+
},
|
|
12401
|
+
get thinkingLevel() {
|
|
12402
|
+
return current().thinkingLevel;
|
|
12403
|
+
},
|
|
12404
|
+
get messages() {
|
|
12405
|
+
return current().messages;
|
|
12406
|
+
},
|
|
12407
|
+
get isStreaming() {
|
|
12408
|
+
return current().isStreaming;
|
|
12409
|
+
},
|
|
12410
|
+
navigateTree: (...args) => invoke((stage2) => stage2.navigateTree(...args)),
|
|
12411
|
+
compact: () => invoke((stage2) => stage2.compact()),
|
|
12412
|
+
abortCompaction: () => current().abortCompaction(),
|
|
12413
|
+
abort: () => invoke((stage2) => stage2.abort())
|
|
12414
|
+
};
|
|
12415
|
+
}
|
|
12416
|
+
|
|
12307
12417
|
// dist/builtin/workflows/src/engine/run-returned-status.ts
|
|
12308
12418
|
function classifyReturnedRunStatus(result, runSnapshot) {
|
|
12309
12419
|
const structuredFailure = runSnapshot !== undefined ? structuredRecoverableWorkflowFailure(runSnapshot) : undefined;
|
|
@@ -12459,6 +12569,7 @@ function createToolAdmissionBoundary() {
|
|
|
12459
12569
|
function createToolControlRegistry() {
|
|
12460
12570
|
const byRun = new Map;
|
|
12461
12571
|
const admissionByRun = new Map;
|
|
12572
|
+
const runControls = new Map;
|
|
12462
12573
|
const makeHandle = (registration) => ({
|
|
12463
12574
|
runId: registration.runId,
|
|
12464
12575
|
nodeId: registration.nodeId,
|
|
@@ -12517,9 +12628,18 @@ function createToolControlRegistry() {
|
|
|
12517
12628
|
admissionBoundary(runId) {
|
|
12518
12629
|
return admissionByRun.get(runId);
|
|
12519
12630
|
},
|
|
12631
|
+
registerRun(runId, handle) {
|
|
12632
|
+
runControls.set(runId, handle);
|
|
12633
|
+
return () => {
|
|
12634
|
+
if (runControls.get(runId) === handle)
|
|
12635
|
+
runControls.delete(runId);
|
|
12636
|
+
};
|
|
12637
|
+
},
|
|
12638
|
+
runControl: (runId) => runControls.get(runId),
|
|
12520
12639
|
clear() {
|
|
12521
12640
|
byRun.clear();
|
|
12522
12641
|
admissionByRun.clear();
|
|
12642
|
+
runControls.clear();
|
|
12523
12643
|
}
|
|
12524
12644
|
};
|
|
12525
12645
|
}
|
|
@@ -12576,7 +12696,7 @@ function createAdmittedToolExecutionTracker(options = {}) {
|
|
|
12576
12696
|
};
|
|
12577
12697
|
return {
|
|
12578
12698
|
track(execution) {
|
|
12579
|
-
if (state === "CLOSED") {
|
|
12699
|
+
if (state === "CLOSED" || options.signal?.aborted) {
|
|
12580
12700
|
const error = new Error("atomic-workflows: ctx.tool admission is closed for this run");
|
|
12581
12701
|
execution.catch(() => {
|
|
12582
12702
|
return;
|
|
@@ -12718,6 +12838,7 @@ function compatibleReplayedToolParents(tracker, translated, inferredParents, isR
|
|
|
12718
12838
|
function createTrackedToolPrimitive(input) {
|
|
12719
12839
|
let observedQuit;
|
|
12720
12840
|
const admittedTools = createAdmittedToolExecutionTracker({
|
|
12841
|
+
signal: input.controller.signal,
|
|
12721
12842
|
onFailureObserved: ({ error, nodeId }) => {
|
|
12722
12843
|
input.terminalEvents.selectFailure(error, nodeId);
|
|
12723
12844
|
},
|
|
@@ -17629,14 +17750,7 @@ async function run(def, inputs, opts = {}) {
|
|
|
17629
17750
|
terminalEvents.selectCancellation(ownController.signal.reason);
|
|
17630
17751
|
}, { once: true });
|
|
17631
17752
|
const callerSignal = opts.signal;
|
|
17632
|
-
|
|
17633
|
-
if (callerSignal.aborted)
|
|
17634
|
-
ownController.abort(callerSignal.reason);
|
|
17635
|
-
else
|
|
17636
|
-
callerSignal.addEventListener("abort", () => {
|
|
17637
|
-
ownController.abort(callerSignal.reason);
|
|
17638
|
-
}, { once: true });
|
|
17639
|
-
}
|
|
17753
|
+
const onCallerAbort = () => ownController.abort(callerSignal?.reason);
|
|
17640
17754
|
const exit = createWorkflowExitManager({ runId, exitScope, controller: ownController });
|
|
17641
17755
|
const backendView = opts.durableBackend ?? getDurableBackend();
|
|
17642
17756
|
const rootBackend = opts.durableRootBackend ?? backendView;
|
|
@@ -17730,28 +17844,7 @@ async function run(def, inputs, opts = {}) {
|
|
|
17730
17844
|
classifiedFailures.set(error, classified);
|
|
17731
17845
|
return classified;
|
|
17732
17846
|
};
|
|
17733
|
-
activeStore.recordRunStart(runSnapshot);
|
|
17734
17847
|
const ownsCancellationRegistration = opts.signal === undefined && opts.cancellation !== undefined;
|
|
17735
|
-
if (ownsCancellationRegistration)
|
|
17736
|
-
opts.cancellation?.register(runId, ownController);
|
|
17737
|
-
opts.onRunStart?.(runSnapshot);
|
|
17738
|
-
if (opts.persistence) {
|
|
17739
|
-
appendRunStart(opts.persistence, {
|
|
17740
|
-
runId,
|
|
17741
|
-
name: def.name,
|
|
17742
|
-
inputs: resolvedInputs,
|
|
17743
|
-
...runSnapshot.parentRunId !== undefined ? { parentRunId: runSnapshot.parentRunId } : {},
|
|
17744
|
-
...runSnapshot.parentStageId !== undefined ? { parentStageId: runSnapshot.parentStageId } : {},
|
|
17745
|
-
...runSnapshot.rootRunId !== undefined ? { rootRunId: runSnapshot.rootRunId } : {},
|
|
17746
|
-
...runSnapshot.resumedFromRunId !== undefined ? { resumedFromRunId: runSnapshot.resumedFromRunId } : {},
|
|
17747
|
-
...runSnapshot.origin !== undefined ? { origin: runSnapshot.origin } : {},
|
|
17748
|
-
...runSnapshot.resumeFromStageId !== undefined ? { resumeFromStageId: runSnapshot.resumeFromStageId } : {},
|
|
17749
|
-
...runSnapshot.accumulatedDurationMs !== undefined ? { accumulatedDurationMs: runSnapshot.accumulatedDurationMs } : {},
|
|
17750
|
-
...runSnapshot.budget !== undefined ? { budget: runSnapshot.budget } : {},
|
|
17751
|
-
...runSnapshot.budgetState !== undefined ? { budgetState: runSnapshot.budgetState } : {},
|
|
17752
|
-
ts: runSnapshot.startedAt
|
|
17753
|
-
});
|
|
17754
|
-
}
|
|
17755
17848
|
const tracker = new GraphFrontierTracker;
|
|
17756
17849
|
const inputConcurrency = resolveInputConcurrency(def.inputs, resolvedInputs);
|
|
17757
17850
|
const inputRuntimeDefaults = resolveInputRuntimeDefaults(def, resolvedInputs), gitWorktreeSetupCacheOwner = createGitWorktreeSetupCacheOwner(opts.gitWorktreeSetupCache);
|
|
@@ -17773,6 +17866,17 @@ async function run(def, inputs, opts = {}) {
|
|
|
17773
17866
|
tracker,
|
|
17774
17867
|
stageRegistry: () => stageRegistry
|
|
17775
17868
|
});
|
|
17869
|
+
const waitForRunRelease = async () => {
|
|
17870
|
+
await scheduler.waitForRunRelease();
|
|
17871
|
+
ownController.signal.throwIfAborted();
|
|
17872
|
+
};
|
|
17873
|
+
const whenRunning = (call) => {
|
|
17874
|
+
if (scheduler.isRunPaused())
|
|
17875
|
+
return waitForRunRelease().then(() => whenRunning(call));
|
|
17876
|
+
if (ownController.signal.aborted)
|
|
17877
|
+
return Promise.reject(ownController.signal.reason);
|
|
17878
|
+
return call();
|
|
17879
|
+
};
|
|
17776
17880
|
ownController.signal.addEventListener("abort", () => scheduler.rejectReleaseBarriers(ownController.signal.reason ?? new Error("atomic-workflows: run aborted")), { once: true });
|
|
17777
17881
|
const finalizers = createRunFinalizers({
|
|
17778
17882
|
def,
|
|
@@ -17974,6 +18078,10 @@ async function run(def, inputs, opts = {}) {
|
|
|
17974
18078
|
workflowId: runId,
|
|
17975
18079
|
backend: durableBackend,
|
|
17976
18080
|
nextCheckpointId: checkpointIdGenerator,
|
|
18081
|
+
beforeCall: () => {
|
|
18082
|
+
ownController.signal.throwIfAborted();
|
|
18083
|
+
return scheduler.isRunPaused() ? waitForRunRelease() : undefined;
|
|
18084
|
+
},
|
|
17977
18085
|
...opts.usePromptNodesForUi === true ? {
|
|
17978
18086
|
onReplay: async (request) => getPromptNodeUi().replayDurable(request)
|
|
17979
18087
|
} : {}
|
|
@@ -17987,6 +18095,10 @@ async function run(def, inputs, opts = {}) {
|
|
|
17987
18095
|
completedStageReplayKeys,
|
|
17988
18096
|
sourceToReplayedNodeIds: sourceToContinuationNodeIds
|
|
17989
18097
|
});
|
|
18098
|
+
const recordCachedStage = (...args) => {
|
|
18099
|
+
ownController.signal.throwIfAborted();
|
|
18100
|
+
cachedStage.record(...args);
|
|
18101
|
+
};
|
|
17990
18102
|
const durableIntercomGroup = (replayKey, stageId) => {
|
|
17991
18103
|
const stages = activeStore.runs().find((candidate) => candidate.id === runId)?.stages ?? [];
|
|
17992
18104
|
return stages.find((stage) => stageId !== undefined && stage.id === stageId || stage.replayKey === replayKey)?.intercomGroup;
|
|
@@ -17998,7 +18110,7 @@ async function run(def, inputs, opts = {}) {
|
|
|
17998
18110
|
nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName),
|
|
17999
18111
|
durableIntercomGroup,
|
|
18000
18112
|
task: taskRunners.task,
|
|
18001
|
-
recordCachedTask:
|
|
18113
|
+
recordCachedTask: recordCachedStage,
|
|
18002
18114
|
signal: ownController.signal,
|
|
18003
18115
|
registerTailControl: (registration) => {
|
|
18004
18116
|
registration.controller.signal.addEventListener("abort", () => {
|
|
@@ -18024,60 +18136,152 @@ async function run(def, inputs, opts = {}) {
|
|
|
18024
18136
|
setChildDurableInvocation: (invocation) => {
|
|
18025
18137
|
pendingChildDurableInvocation = invocation;
|
|
18026
18138
|
},
|
|
18027
|
-
recordCachedStage
|
|
18139
|
+
recordCachedStage,
|
|
18028
18140
|
runTopology: durableRunTopology(runSnapshot),
|
|
18029
18141
|
workflow: workflow2
|
|
18030
18142
|
});
|
|
18143
|
+
const pendingChildWorkflows = new Set;
|
|
18144
|
+
const durableStage = createDurableStagePrimitive({
|
|
18145
|
+
workflowId: runId,
|
|
18146
|
+
backend: durableBackend,
|
|
18147
|
+
nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName),
|
|
18148
|
+
durableIntercomGroup,
|
|
18149
|
+
recordCachedStage,
|
|
18150
|
+
stage: (name, options, replayKey) => {
|
|
18151
|
+
const stage = runtime.stage(name, options);
|
|
18152
|
+
const stageId = activeStore.runs().find((r) => r.id === runId)?.stages.at(-1)?.id;
|
|
18153
|
+
if (stageId !== undefined)
|
|
18154
|
+
completedStageReplayKeys.set(stageId, replayKey);
|
|
18155
|
+
return stage;
|
|
18156
|
+
}
|
|
18157
|
+
});
|
|
18158
|
+
const gatedTask = (...args) => whenRunning(() => durableTask(...args));
|
|
18159
|
+
const chain = createChainPrimitive({ runtime, task: gatedTask });
|
|
18160
|
+
const parallel = createParallelPrimitive({ runtime, task: gatedTask });
|
|
18161
|
+
const ui = buildExitGatedUiContext({
|
|
18162
|
+
opts,
|
|
18163
|
+
throwIfWorkflowExitSelected: exit.throwIfWorkflowExitSelected,
|
|
18164
|
+
durableUi: durableUiDeps,
|
|
18165
|
+
baseFromPromptNodes: getPromptNodeUi
|
|
18166
|
+
});
|
|
18167
|
+
let pausedExit;
|
|
18031
18168
|
const ctx = {
|
|
18032
18169
|
inputs: resolvedInputs,
|
|
18033
18170
|
runId,
|
|
18034
18171
|
get cwd() {
|
|
18035
18172
|
return resolveWorkflowCwd();
|
|
18036
18173
|
},
|
|
18037
|
-
exit:
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
|
|
18041
|
-
|
|
18042
|
-
baseFromPromptNodes: getPromptNodeUi
|
|
18043
|
-
}),
|
|
18044
|
-
stage: createDurableStagePrimitive({
|
|
18045
|
-
workflowId: runId,
|
|
18046
|
-
backend: durableBackend,
|
|
18047
|
-
nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName),
|
|
18048
|
-
durableIntercomGroup,
|
|
18049
|
-
recordCachedStage: cachedStage.record,
|
|
18050
|
-
stage: (name, options, replayKey) => {
|
|
18051
|
-
const stage = runtime.stage(name, options);
|
|
18052
|
-
const stageId = activeStore.runs().find((r) => r.id === runId)?.stages.at(-1)?.id;
|
|
18053
|
-
if (stageId !== undefined)
|
|
18054
|
-
completedStageReplayKeys.set(stageId, replayKey);
|
|
18055
|
-
return stage;
|
|
18174
|
+
exit: (options) => {
|
|
18175
|
+
if (scheduler.isRunPaused()) {
|
|
18176
|
+
pausedExit ??= whenRunning(async () => exit.exit(options));
|
|
18177
|
+
pausedExit.catch(() => {});
|
|
18178
|
+
throw new Error("Workflow exit is waiting for explicit resume");
|
|
18056
18179
|
}
|
|
18180
|
+
return exit.exit(options);
|
|
18181
|
+
},
|
|
18182
|
+
ui,
|
|
18183
|
+
stage: (name, options) => {
|
|
18184
|
+
ownController.signal.throwIfAborted();
|
|
18185
|
+
if (!scheduler.isRunPaused())
|
|
18186
|
+
return durableStage(name, options);
|
|
18187
|
+
const replayKey = stageReplayKeyGenerator(name);
|
|
18188
|
+
return deferStageUntilRunRelease({
|
|
18189
|
+
name,
|
|
18190
|
+
create: () => durableStage(name, options, replayKey),
|
|
18191
|
+
isPaused: scheduler.isRunPaused,
|
|
18192
|
+
waitForRelease: waitForRunRelease,
|
|
18193
|
+
signal: ownController.signal
|
|
18194
|
+
});
|
|
18195
|
+
},
|
|
18196
|
+
task: gatedTask,
|
|
18197
|
+
chain: (...args) => whenRunning(() => chain(...args)),
|
|
18198
|
+
parallel: (...args) => whenRunning(() => parallel(...args)),
|
|
18199
|
+
workflow: (...args) => whenRunning(() => {
|
|
18200
|
+
const pending = durableWorkflow(...args);
|
|
18201
|
+
pendingChildWorkflows.add(pending);
|
|
18202
|
+
const settled = () => {
|
|
18203
|
+
pendingChildWorkflows.delete(pending);
|
|
18204
|
+
};
|
|
18205
|
+
pending.then(settled, settled);
|
|
18206
|
+
return pending;
|
|
18057
18207
|
}),
|
|
18058
|
-
|
|
18059
|
-
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18208
|
+
tool: (...args) => {
|
|
18209
|
+
if (!scheduler.isRunPaused())
|
|
18210
|
+
return tool(...args);
|
|
18211
|
+
const pending = whenRunning(() => tool(...args));
|
|
18212
|
+
pending.catch(() => {});
|
|
18213
|
+
return pending;
|
|
18214
|
+
},
|
|
18063
18215
|
...opts.models !== undefined ? { models: opts.models } : {}
|
|
18064
18216
|
};
|
|
18217
|
+
const runtimeSettled = Promise.withResolvers();
|
|
18218
|
+
let pausePersistence;
|
|
18219
|
+
const persistRunControl = async (status) => {
|
|
18220
|
+
ownController.signal.throwIfAborted();
|
|
18221
|
+
if (opts.parentRun !== undefined || durableBackend.getWorkflow(runId) === undefined)
|
|
18222
|
+
return;
|
|
18223
|
+
if (!await transitionDurableWorkflowStatus(durableBackend, runId, ["running", "paused"], status, undefined, true)) {
|
|
18224
|
+
throw new Error(`Workflow ${runId} refused the durable ${status} transition`);
|
|
18225
|
+
}
|
|
18226
|
+
recordRunTimingCheckpoint(durableBackend, runSnapshot);
|
|
18227
|
+
await durableBackend.flush(runId);
|
|
18228
|
+
};
|
|
18229
|
+
const unregisterRunControl = toolControls.registerRun(runId, {
|
|
18230
|
+
get paused() {
|
|
18231
|
+
return scheduler.isRunPaused();
|
|
18232
|
+
},
|
|
18233
|
+
pause: () => {
|
|
18234
|
+
ownController.signal.throwIfAborted();
|
|
18235
|
+
scheduler.pauseRun();
|
|
18236
|
+
activeStore.recordRunPaused(runId, undefined, { resumable: true });
|
|
18237
|
+
pausePersistence = persistRunControl("paused");
|
|
18238
|
+
return pausePersistence;
|
|
18239
|
+
},
|
|
18240
|
+
resume: async () => {
|
|
18241
|
+
await pausePersistence;
|
|
18242
|
+
await persistRunControl("running");
|
|
18243
|
+
ownController.signal.throwIfAborted();
|
|
18244
|
+
activeStore.recordRunResumed(runId, undefined, { source: "run_control" });
|
|
18245
|
+
scheduler.releaseRun();
|
|
18246
|
+
},
|
|
18247
|
+
quit: () => {
|
|
18248
|
+
ownController.abort(new WorkflowGracefulQuitError(runId, "workflow runtime"));
|
|
18249
|
+
return runtimeSettled.promise;
|
|
18250
|
+
}
|
|
18251
|
+
});
|
|
18065
18252
|
terminalEvents.register();
|
|
18066
18253
|
try {
|
|
18067
|
-
if (
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18254
|
+
if (callerSignal?.aborted)
|
|
18255
|
+
onCallerAbort();
|
|
18256
|
+
else
|
|
18257
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
18258
|
+
activeStore.recordRunStart(runSnapshot);
|
|
18259
|
+
if (ownsCancellationRegistration)
|
|
18260
|
+
opts.cancellation?.register(runId, ownController);
|
|
18261
|
+
opts.onRunStart?.(runSnapshot);
|
|
18262
|
+
if (opts.persistence) {
|
|
18263
|
+
appendRunStart(opts.persistence, {
|
|
18264
|
+
runId,
|
|
18265
|
+
name: def.name,
|
|
18266
|
+
inputs: resolvedInputs,
|
|
18267
|
+
...runSnapshot.parentRunId !== undefined ? { parentRunId: runSnapshot.parentRunId } : {},
|
|
18268
|
+
...runSnapshot.parentStageId !== undefined ? { parentStageId: runSnapshot.parentStageId } : {},
|
|
18269
|
+
...runSnapshot.rootRunId !== undefined ? { rootRunId: runSnapshot.rootRunId } : {},
|
|
18270
|
+
...runSnapshot.resumedFromRunId !== undefined ? { resumedFromRunId: runSnapshot.resumedFromRunId } : {},
|
|
18271
|
+
...runSnapshot.origin !== undefined ? { origin: runSnapshot.origin } : {},
|
|
18272
|
+
...runSnapshot.resumeFromStageId !== undefined ? { resumeFromStageId: runSnapshot.resumeFromStageId } : {},
|
|
18273
|
+
...runSnapshot.accumulatedDurationMs !== undefined ? { accumulatedDurationMs: runSnapshot.accumulatedDurationMs } : {},
|
|
18274
|
+
...runSnapshot.budget !== undefined ? { budget: runSnapshot.budget } : {},
|
|
18275
|
+
...runSnapshot.budgetState !== undefined ? { budgetState: runSnapshot.budgetState } : {},
|
|
18276
|
+
ts: runSnapshot.startedAt
|
|
18277
|
+
});
|
|
18079
18278
|
}
|
|
18080
|
-
|
|
18279
|
+
if (opts.deferWorkflowStart === true)
|
|
18280
|
+
await raceAbort2(nextEventLoopTurn(), ownController.signal);
|
|
18281
|
+
while (scheduler.isRunPaused())
|
|
18282
|
+
await waitForRunRelease();
|
|
18283
|
+
ownController.signal.throwIfAborted();
|
|
18284
|
+
await raceAbort2(admitDurableRootRun({
|
|
18081
18285
|
backend: durableBackend,
|
|
18082
18286
|
runId,
|
|
18083
18287
|
isChildRun: opts.parentRun !== undefined,
|
|
@@ -18085,7 +18289,10 @@ async function run(def, inputs, opts = {}) {
|
|
|
18085
18289
|
...durableRootRegistration,
|
|
18086
18290
|
...workflowInvocationMetadata(inputRuntimeDefaults, workflowInvocationCwd, gitWorktreeSetupCache, runSnapshot.origin)
|
|
18087
18291
|
}
|
|
18088
|
-
});
|
|
18292
|
+
}), ownController.signal);
|
|
18293
|
+
while (scheduler.isRunPaused())
|
|
18294
|
+
await waitForRunRelease();
|
|
18295
|
+
ownController.signal.throwIfAborted();
|
|
18089
18296
|
if (opts.deferWorkflowStart === true)
|
|
18090
18297
|
opts.onWorkflowStartReady?.();
|
|
18091
18298
|
const sourceFrontierStage = opts.continuation?.source;
|
|
@@ -18093,8 +18300,17 @@ async function run(def, inputs, opts = {}) {
|
|
|
18093
18300
|
const startupFrontierStage = sourceFrontierStage?.stages.find((stage) => stage.id === sourceFrontierId)?.name ?? "workflow frontier";
|
|
18094
18301
|
if (budget.enabled)
|
|
18095
18302
|
await budget.stopAtBoundaryAsync(startupFrontierStage);
|
|
18096
|
-
|
|
18303
|
+
while (scheduler.isRunPaused())
|
|
18304
|
+
await waitForRunRelease();
|
|
18305
|
+
ownController.signal.throwIfAborted();
|
|
18306
|
+
const rawResult = await raceAbort2(runWorkflowDefinitionCallback(def.name, runId, () => def.run(ctx)), ownController.signal);
|
|
18307
|
+
while (scheduler.isRunPaused())
|
|
18308
|
+
await waitForRunRelease();
|
|
18097
18309
|
await admittedTools.closeAndDrain();
|
|
18310
|
+
while (scheduler.isRunPaused())
|
|
18311
|
+
await waitForRunRelease();
|
|
18312
|
+
if (pausedExit !== undefined)
|
|
18313
|
+
await pausedExit;
|
|
18098
18314
|
budget.rethrowIfSystemOwnedStop(runSnapshot.stages.at(-1)?.name ?? startupFrontierStage);
|
|
18099
18315
|
const normalTerminalEvent = terminalEvents.winner();
|
|
18100
18316
|
if (normalTerminalEvent?.kind === "cancellation") {
|
|
@@ -18115,7 +18331,10 @@ async function run(def, inputs, opts = {}) {
|
|
|
18115
18331
|
const result = normalizeWorkflowRunOutput(def.name, rawResult);
|
|
18116
18332
|
assertWorkflowRunOutputs(def.name, result, def.outputs);
|
|
18117
18333
|
assertWorkflowCreatedExecution(runSnapshot);
|
|
18118
|
-
await durableBackend.flush(runId);
|
|
18334
|
+
await raceAbort2(durableBackend.flush(runId), ownController.signal);
|
|
18335
|
+
while (scheduler.isRunPaused())
|
|
18336
|
+
await waitForRunRelease();
|
|
18337
|
+
ownController.signal.throwIfAborted();
|
|
18119
18338
|
const returned = classifyReturnedRunStatus(result, runSnapshot);
|
|
18120
18339
|
if (returned.status === "completed")
|
|
18121
18340
|
assertFrontierConsumed();
|
|
@@ -18135,12 +18354,26 @@ async function run(def, inputs, opts = {}) {
|
|
|
18135
18354
|
durableBackend.setWorkflowStatus(runId, returned.status, undefined, returned.metadata?.resumable);
|
|
18136
18355
|
await durableBackend.flush(runId);
|
|
18137
18356
|
return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status: returned.status, result, error: returned.error }, opts.onRunEnd);
|
|
18138
|
-
} catch (
|
|
18139
|
-
|
|
18357
|
+
} catch (error) {
|
|
18358
|
+
let err = error;
|
|
18359
|
+
try {
|
|
18360
|
+
while (scheduler.isRunPaused())
|
|
18361
|
+
await waitForRunRelease();
|
|
18362
|
+
if (pausedExit !== undefined)
|
|
18363
|
+
await pausedExit;
|
|
18364
|
+
} catch (stop) {
|
|
18365
|
+
err = stop;
|
|
18366
|
+
}
|
|
18367
|
+
const selectedTerminalEvent = terminalEvents.selectFailure(err);
|
|
18368
|
+
if (selectedTerminalEvent.kind === "failure" && ownController.signal.aborted && Object.is(err, ownController.signal.reason)) {
|
|
18369
|
+
err = selectedTerminalEvent.error;
|
|
18370
|
+
}
|
|
18140
18371
|
const gracefulQuit = observedQuitCancellation() ?? observedTaskTailQuit ?? findWorkflowGracefulQuit(err) ?? findWorkflowGracefulQuit(ownController.signal.reason);
|
|
18141
18372
|
if (gracefulQuit !== undefined)
|
|
18142
18373
|
return suspendForGracefulQuit(gracefulQuit);
|
|
18143
18374
|
await admittedTools.closeAndDrain();
|
|
18375
|
+
if (ownController.signal.aborted)
|
|
18376
|
+
await Promise.allSettled([...pendingChildWorkflows]);
|
|
18144
18377
|
if (err instanceof WorkflowBudgetExceededError) {
|
|
18145
18378
|
const pendingBudgetError = await budget.awaitPendingWrapUp();
|
|
18146
18379
|
const selectedBudgetError = pendingBudgetError ?? err;
|
|
@@ -18209,6 +18442,9 @@ async function run(def, inputs, opts = {}) {
|
|
|
18209
18442
|
onRunEnd: opts.onRunEnd
|
|
18210
18443
|
});
|
|
18211
18444
|
} finally {
|
|
18445
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
18446
|
+
runtimeSettled.resolve();
|
|
18447
|
+
unregisterRunControl();
|
|
18212
18448
|
try {
|
|
18213
18449
|
await finalizeDurableTerminalStatus({
|
|
18214
18450
|
runId,
|
package/docs/quickstart.md
CHANGED
|
@@ -32,7 +32,7 @@ bun add -g @bastani/atomic
|
|
|
32
32
|
|
|
33
33
|
Atomic does not require package install scripts. Add `--ignore-scripts` if you want to disable dependency lifecycle scripts during a package install.
|
|
34
34
|
|
|
35
|
-
Embedded PostgreSQL is
|
|
35
|
+
Embedded PostgreSQL is available without install scripts or a first-run download on Linux x64/ARM64 (glibc and musl), macOS x64/ARM64, and Windows x64/ARM64. npm-compatible package managers select the matching `@bastani/atomic-natives` leaf containing the runtime; standalone archives carry a target-selected runtime and resolve its binaries directly from the extracted installation. Keep the complete archive directory, including `node_modules`, libraries and licenses. Older upstream optional packages may also remain in npm installations for compatibility, but the native leaf takes precedence. Windows ARM64 uses Windows x64 PostgreSQL under Windows 11's x64 emulation, not native PostgreSQL ARM64, and requires the Microsoft Visual C++ x64 v14 Redistributable. Windows 10 on ARM cannot run this x64 runtime; Windows ARM64 execution still needs hardware validation.
|
|
36
36
|
|
|
37
37
|
### Release archive
|
|
38
38
|
|