@bastani/atomic 0.9.18-alpha.7 → 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.
@@ -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.throwIfWorkflowExitSelected();
11010
- return await durableBase.input(promptText);
11069
+ return await invoke(() => durableBase.input(promptText));
11011
11070
  },
11012
11071
  async confirm(message) {
11013
- input.throwIfWorkflowExitSelected();
11014
- return await durableBase.confirm(message);
11072
+ return await invoke(() => durableBase.confirm(message));
11015
11073
  },
11016
11074
  async select(message, options) {
11017
- input.throwIfWorkflowExitSelected();
11018
- return await durableBase.select(message, options);
11075
+ return await invoke(() => durableBase.select(message, options));
11019
11076
  },
11020
11077
  async editor(initial) {
11021
- input.throwIfWorkflowExitSelected();
11022
- return await durableBase.editor(initial);
11078
+ return await invoke(() => durableBase.editor(initial));
11023
11079
  },
11024
11080
  async custom(factory, options) {
11025
- input.throwIfWorkflowExitSelected();
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
- if (callerSignal) {
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: cachedStage.record,
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: cachedStage.record,
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: exit.exit,
18038
- ui: buildExitGatedUiContext({
18039
- opts,
18040
- throwIfWorkflowExitSelected: exit.throwIfWorkflowExitSelected,
18041
- durableUi: durableUiDeps,
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
- task: durableTask,
18059
- chain: createChainPrimitive({ runtime, task: durableTask }),
18060
- parallel: createParallelPrimitive({ runtime, task: durableTask }),
18061
- workflow: durableWorkflow,
18062
- tool,
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 (opts.deferWorkflowStart === true) {
18068
- await nextEventLoopTurn();
18069
- if (ownController.signal.aborted) {
18070
- await admittedTools.closeAndDrain();
18071
- const selectedExit = findWorkflowExitSignal(ownController.signal.reason, exitScope);
18072
- if (selectedExit !== undefined)
18073
- return await finalizers.finalizeWorkflowExit(selectedExit);
18074
- const parentExit = parentWorkflowExitAbortReason(ownController.signal.reason);
18075
- if (parentExit !== undefined)
18076
- return await finalizers.finalizeParentWorkflowExitCancellation(parentExit);
18077
- return finalizeKilled(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd);
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
- await admitDurableRootRun({
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
- const rawResult = await runWorkflowDefinitionCallback(def.name, runId, () => def.run(ctx));
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 (err) {
18139
- terminalEvents.selectFailure(err);
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,
@@ -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 also available without install scripts or a first-run download. npm-compatible package managers select a target-specific `@bastani/atomic-natives` package containing the runtime on Linux musl x64/ARM64 and Windows ARM64; standalone archives contain only their matching runtime. Both forms are offline at runtime. Windows ARM64 support is 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.
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