@bastani/atomic 0.9.42 → 0.9.43-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.
@@ -6491,8 +6491,14 @@ function registerSqlPoolStorage(journal, storage) {
6491
6491
  function sqlPoolStorage(journal) {
6492
6492
  return poolStorage.get(journal);
6493
6493
  }
6494
- var JOURNAL_READ_PAGE_SIZE = 256, versionReaders, poolStorage;
6494
+ var JOURNAL_READ_PAGE_SIZE = 256, NON_SNAPSHOT_EVENT_TYPES, versionReaders, poolStorage;
6495
6495
  var init_internal = __esm(() => {
6496
+ init_types();
6497
+ NON_SNAPSHOT_EVENT_TYPES = [
6498
+ RUN_EVENT_TYPES.checkpointRecorded,
6499
+ RUN_EVENT_TYPES.messageSettled,
6500
+ RUN_EVENT_TYPES.messageDelivered
6501
+ ];
6496
6502
  versionReaders = new WeakMap;
6497
6503
  poolStorage = new WeakMap;
6498
6504
  });
@@ -6800,6 +6806,7 @@ class JournalDurableBackend {
6800
6806
  projection = createJournalProjection();
6801
6807
  sequences = new Map;
6802
6808
  foldedAt = new Map;
6809
+ summarized = new Map;
6803
6810
  catalogToken;
6804
6811
  leases = new Map;
6805
6812
  executions = new Map;
@@ -6825,8 +6832,8 @@ class JournalDurableBackend {
6825
6832
  let backend;
6826
6833
  try {
6827
6834
  backend = new JournalDurableBackend(journal, undefined, options.ownsJournal ?? true);
6828
- for (const run of await journal.listRuns())
6829
- for await (const entry of journal.read(run.runId))
6835
+ for (const runId of options.runs ?? (await journal.listRuns()).map((run) => run.runId))
6836
+ for await (const entry of journal.read(runId))
6830
6837
  backend.fold(entry);
6831
6838
  return backend;
6832
6839
  } catch (error) {
@@ -6899,6 +6906,18 @@ class JournalDurableBackend {
6899
6906
  this.sequences.set(entry.runId, entry.seq);
6900
6907
  this.foldedAt.set(entry.runId, entry.at);
6901
6908
  }
6909
+ summarize(runId, tail) {
6910
+ const event = tail.snapshot.event;
6911
+ const handle = event.type === RUN_EVENT_TYPES.promptAsked || event.type === RUN_EVENT_TYPES.promptReleased ? event.payload.handle : event.payload;
6912
+ this.projection.deleteWorkflow(runId);
6913
+ this.prompts.delete(runId);
6914
+ this.projection.applyWorkflowSnapshot({
6915
+ ...handle,
6916
+ completedCheckpoints: handle.completedCheckpoints + tail.laterCheckpoints,
6917
+ ...tail.lastCheckpointAt === undefined ? {} : { updatedAt: tail.lastCheckpointAt }
6918
+ });
6919
+ this.summarized.set(runId, tail.version);
6920
+ }
6902
6921
  *entries(runId, fromSeq) {
6903
6922
  return yield* invoke(async () => {
6904
6923
  const entries = [];
@@ -6912,6 +6931,7 @@ class JournalDurableBackend {
6912
6931
  this.projection.deleteWorkflow(runId);
6913
6932
  this.sequences.delete(runId);
6914
6933
  this.foldedAt.delete(runId);
6934
+ this.summarized.delete(runId);
6915
6935
  this.prompts.delete(runId);
6916
6936
  for (const entry of entries)
6917
6937
  this.fold(entry);
@@ -7233,6 +7253,9 @@ class JournalDurableBackend {
7233
7253
  async hydrateWorkflow(runId) {
7234
7254
  await this.serialize(runId, () => this.rebuild(runId), false);
7235
7255
  }
7256
+ async catchUpWorkflow(runId) {
7257
+ await this.serialize(runId, () => this.extend(runId), false);
7258
+ }
7236
7259
  hydrateResumableWorkflows() {
7237
7260
  return this.serialize(this.catalogQueue, () => invoke(async () => {
7238
7261
  const journal = runJournalVersions(this.journal);
@@ -7246,16 +7269,33 @@ class JournalDurableBackend {
7246
7269
  if (token === this.catalogToken)
7247
7270
  return;
7248
7271
  const versions = await journal.runVersions();
7249
- for (const runId of new Set([...this.sequences.keys(), ...versions.keys()]))
7250
- await this.serialize(runId, () => this.catchUp(runId, versions.get(runId)), false);
7272
+ for (const runId of new Set([...this.sequences.keys(), ...this.summarized.keys(), ...versions.keys()]))
7273
+ await this.serialize(runId, () => this.catchUp(runId, versions.get(runId), journal), false);
7251
7274
  this.catalogToken = token;
7252
7275
  }));
7253
7276
  }
7254
- *catchUp(runId, version) {
7277
+ *catchUp(runId, version, journal) {
7255
7278
  const folded = this.sequences.get(runId) ?? 0;
7279
+ if (folded === 0 && version !== undefined) {
7280
+ const summarized = this.summarized.get(runId);
7281
+ if (summarized !== undefined && summarized.seq === version.seq && summarized.at === version.at)
7282
+ return;
7283
+ const tail = yield* invoke(() => journal.snapshotTail(runId));
7284
+ if (tail !== undefined)
7285
+ return this.summarize(runId, tail);
7286
+ if (summarized === undefined && version.seq === 0)
7287
+ return;
7288
+ return yield* this.rebuild(runId);
7289
+ }
7256
7290
  if (version !== undefined && version.seq === folded && version.at === (this.foldedAt.get(runId) ?? 0))
7257
7291
  return;
7258
- if (version !== undefined && folded > 0 && version.seq > folded) {
7292
+ if (version !== undefined && folded > 0 && version.seq > folded)
7293
+ return yield* this.extend(runId);
7294
+ yield* this.rebuild(runId);
7295
+ }
7296
+ *extend(runId) {
7297
+ const folded = this.sequences.get(runId) ?? 0;
7298
+ if (folded > 0) {
7259
7299
  const entries = yield* this.entries(runId, folded);
7260
7300
  if (entries[0]?.seq === folded && entries[0].at === this.foldedAt.get(runId)) {
7261
7301
  for (const entry of entries)
@@ -7283,6 +7323,7 @@ class JournalDurableBackend {
7283
7323
  this.leases.delete(runId);
7284
7324
  this.sequences.delete(runId);
7285
7325
  this.foldedAt.delete(runId);
7326
+ this.summarized.delete(runId);
7286
7327
  this.prompts.delete(runId);
7287
7328
  this.projection.deleteWorkflow(runId);
7288
7329
  }
@@ -7647,7 +7688,8 @@ class SqlRunJournal {
7647
7688
  this.legacy = legacy;
7648
7689
  registerRunJournalVersions(this, {
7649
7690
  changeToken: () => this.changeToken(),
7650
- runVersions: () => this.runVersions()
7691
+ runVersions: () => this.runVersions(),
7692
+ snapshotTail: (runId) => this.snapshotTail(runId)
7651
7693
  });
7652
7694
  registerSqlPoolStorage(this, {
7653
7695
  transaction: (body) => this.operation(() => this.database.transaction(body)),
@@ -7773,6 +7815,28 @@ class SqlRunJournal {
7773
7815
  ]));
7774
7816
  });
7775
7817
  }
7818
+ async snapshotTail(runId) {
7819
+ this.assertOpen();
7820
+ validateText("runId", runId);
7821
+ return this.operation(async () => {
7822
+ const events = await this.isUnconvertedLegacyRun(runId) ? JOURNAL_TABLES.events : this.tables.events;
7823
+ const later = `FROM ${events} AS later WHERE later.${JOURNAL_COLUMNS.runId} = snapshot.${JOURNAL_COLUMNS.runId} AND later.${JOURNAL_COLUMNS.seq} > snapshot.${JOURNAL_COLUMNS.seq} AND later.${JOURNAL_COLUMNS.type} = ?`;
7824
+ const row = await this.database.get(`SELECT ${EVENT_COLUMNS.map((column) => `snapshot.${column}`).join(", ")},
7825
+ latest.${JOURNAL_COLUMNS.seq} AS latest_seq, latest.${JOURNAL_COLUMNS.at} AS latest_at,
7826
+ (SELECT count(*) ${later}) AS later_checkpoints,
7827
+ (SELECT json_extract(later.${JOURNAL_COLUMNS.payload}, '$.completedAt') ${later} ORDER BY later.${JOURNAL_COLUMNS.seq} DESC LIMIT 1) AS last_checkpoint_at
7828
+ FROM (SELECT ${JOURNAL_COLUMNS.seq}, ${JOURNAL_COLUMNS.at} FROM ${events} WHERE ${JOURNAL_COLUMNS.runId} = ? ORDER BY ${JOURNAL_COLUMNS.seq} DESC LIMIT 1) AS latest,
7829
+ (SELECT ${EVENT_COLUMNS.join(", ")} FROM ${events} WHERE ${JOURNAL_COLUMNS.runId} = ? AND ${JOURNAL_COLUMNS.type} NOT IN (${NON_SNAPSHOT_EVENT_TYPES.map(() => "?").join(", ")}) ORDER BY ${JOURNAL_COLUMNS.seq} DESC LIMIT 1) AS snapshot`, RUN_EVENT_TYPES.checkpointRecorded, RUN_EVENT_TYPES.checkpointRecorded, runId, runId, ...NON_SNAPSHOT_EVENT_TYPES);
7830
+ if (row === undefined)
7831
+ return;
7832
+ return {
7833
+ version: { seq: journalCounter(runId, "seq", row.latest_seq), at: Number(row.latest_at) },
7834
+ snapshot: entryFromRow(row),
7835
+ laterCheckpoints: Number(row.later_checkpoints),
7836
+ ...row.last_checkpoint_at === null ? {} : { lastCheckpointAt: Number(row.last_checkpoint_at) }
7837
+ };
7838
+ });
7839
+ }
7776
7840
  async requireRun(tx, runId) {
7777
7841
  if (await tx.get(`SELECT ${JOURNAL_COLUMNS.runId} FROM ${this.tables.runs} WHERE ${JOURNAL_COLUMNS.runId} = ?`, runId))
7778
7842
  return;
@@ -7990,13 +8054,14 @@ class SqlRunJournal {
7990
8054
  return this.closePromise;
7991
8055
  }
7992
8056
  }
7993
- var STORE_NOW = "CAST(unixepoch('subsec') * 1000 AS INTEGER)";
8057
+ var STORE_NOW = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", EVENT_COLUMNS;
7994
8058
  var init_sql_run_journal = __esm(() => {
7995
8059
  init_internal();
7996
8060
  init_legacy_journal_tables();
7997
8061
  init_types();
7998
8062
  init_values();
7999
8063
  init_internal();
8064
+ EVENT_COLUMNS = [JOURNAL_COLUMNS.runId, JOURNAL_COLUMNS.seq, JOURNAL_COLUMNS.epoch, JOURNAL_COLUMNS.key, JOURNAL_COLUMNS.type, JOURNAL_COLUMNS.payload, JOURNAL_COLUMNS.at];
8000
8065
  });
8001
8066
 
8002
8067
  // dist/builtin/workflows/src/durable/journal/resolution.ts
@@ -8107,7 +8172,13 @@ async function initializeDurableBackend(options = {}) {
8107
8172
  const selected = owner.selectedBackends;
8108
8173
  const existing = selected.get(key);
8109
8174
  const entry = existing ?? {
8110
- ready: openSelectedRunJournal(selection, agentDir).then((journal) => JournalDurableBackend.open(journal, { ownsJournal })).then((opened) => {
8175
+ ready: openSelectedRunJournal(selection, agentDir).then((journal) => JournalDurableBackend.open(journal, { ownsJournal, runs: [] })).then(async (opened) => {
8176
+ try {
8177
+ await opened.hydrateResumableWorkflows();
8178
+ } catch (error) {
8179
+ await opened.close();
8180
+ throw error;
8181
+ }
8111
8182
  if (ownsJournal)
8112
8183
  opened.closeOnProcessExit();
8113
8184
  entry.close = () => opened.close();
@@ -63754,6 +63825,57 @@ var init_types2 = __esm(() => {
63754
63825
  });
63755
63826
  });
63756
63827
 
63828
+ // dist/builtin/workflows/src/shared/workflow-infrastructure-error.ts
63829
+ function isInfrastructureError(value2) {
63830
+ if (value2 instanceof WorkflowInfrastructureError)
63831
+ return true;
63832
+ if (typeof value2 !== "object" || value2 === null)
63833
+ return false;
63834
+ const candidate = value2;
63835
+ return candidate.name === WORKFLOW_INFRASTRUCTURE_ERROR_NAME && candidate.retryable === true && typeof candidate.code === "string";
63836
+ }
63837
+ function isRetryableWorkflowError(error) {
63838
+ let current = error;
63839
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined && current !== null; depth++) {
63840
+ if (isInfrastructureError(current))
63841
+ return true;
63842
+ current = current instanceof Error ? current.cause : undefined;
63843
+ }
63844
+ return false;
63845
+ }
63846
+ function workflowInfrastructureFault(message) {
63847
+ return FAULT_PATTERNS.find(([pattern]) => pattern.test(message))?.[1];
63848
+ }
63849
+ function asWorkflowInfrastructureError(error) {
63850
+ if (isInfrastructureError(error))
63851
+ return error;
63852
+ if (!(error instanceof Error))
63853
+ return;
63854
+ const code = workflowInfrastructureFault(error.message);
63855
+ return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
63856
+ }
63857
+ var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
63858
+ var init_workflow_infrastructure_error = __esm(() => {
63859
+ FAULT_PATTERNS = [
63860
+ [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
63861
+ [/Local worker retry limit/, "worker_retry_limit"],
63862
+ [/Local worker (?:startup timed out|exited before readiness)/, "worker_startup_failed"],
63863
+ [/Worker exited before delivery acknowledgement/, "worker_lost"],
63864
+ [/Stale work lease/, "stale_work_lease"],
63865
+ [/Intercom runtime no longer active/, "intercom_unavailable"],
63866
+ [/Storage unavailable/, "storage_unavailable"]
63867
+ ];
63868
+ WorkflowInfrastructureError = class WorkflowInfrastructureError extends Error {
63869
+ name = WORKFLOW_INFRASTRUCTURE_ERROR_NAME;
63870
+ retryable = true;
63871
+ code;
63872
+ constructor(code, message, options) {
63873
+ super(message, options);
63874
+ this.code = code;
63875
+ }
63876
+ };
63877
+ });
63878
+
63757
63879
  // dist/builtin/workflows/src/workflows/registry.ts
63758
63880
  function makeRegistry(store) {
63759
63881
  return {
@@ -63825,11 +63947,13 @@ __export(exports_sdk_surface, {
63825
63947
  StaleLeaseError: () => StaleLeaseError,
63826
63948
  StorageUnavailableError: () => StorageUnavailableError,
63827
63949
  WORKFLOW_HEARTBEAT_CUSTOM_TYPE: () => WORKFLOW_HEARTBEAT_CUSTOM_TYPE,
63950
+ WorkflowInfrastructureError: () => WorkflowInfrastructureError,
63828
63951
  cancellationRegistry: () => cancellationRegistry,
63829
63952
  createAgentSessionAdapter: () => createAgentSessionAdapter,
63830
63953
  createCancellationRegistry: () => createCancellationRegistry,
63831
63954
  createRegistry: () => createRegistry,
63832
63955
  createStore: () => createStore,
63956
+ isRetryableWorkflowError: () => isRetryableWorkflowError,
63833
63957
  keepContext: () => keepContext,
63834
63958
  loadWorkerWorkflow: () => loadWorkerWorkflow,
63835
63959
  normalizeWorkflowName: () => normalizeWorkflowName,
@@ -63856,6 +63980,7 @@ var init_sdk_surface = __esm(() => {
63856
63980
  init_worktree();
63857
63981
  init_store();
63858
63982
  init_types2();
63983
+ init_workflow_infrastructure_error();
63859
63984
  init_definitions();
63860
63985
  init_registry();
63861
63986
  init_types();
@@ -64308,7 +64433,7 @@ class SqlPoolQueue {
64308
64433
  });
64309
64434
  const row = await this.current(tx, lease, token);
64310
64435
  if (row.epoch !== lease.epoch || row.expires_at < now)
64311
- throw new Error("Stale work lease");
64436
+ throw new Error(STALE_WORK_LEASE);
64312
64437
  assertSession();
64313
64438
  }, action);
64314
64439
  }
@@ -64317,7 +64442,7 @@ class SqlPoolQueue {
64317
64442
  const { now } = await this.authority(tx, lease);
64318
64443
  const row = await this.current(tx, lease, token);
64319
64444
  if (row.epoch !== lease.epoch || row.expires_at < now)
64320
- throw new Error("Stale work lease");
64445
+ throw new Error(STALE_WORK_LEASE);
64321
64446
  });
64322
64447
  }
64323
64448
  async enqueue(lease, descriptor, boundary) {
@@ -64397,7 +64522,7 @@ class SqlPoolQueue {
64397
64522
  async current(tx, lease, token, allowCompleted = false) {
64398
64523
  const row = await tx.get(`SELECT * FROM ${this.work} WHERE id = ? AND root = ?`, token.id, lease.runId);
64399
64524
  if (row?.state !== "leased" && !(allowCompleted && row?.state === "completed") || row.epoch !== token.epoch || row.generation !== token.generation || row.worker !== token.worker)
64400
- throw new Error("Stale work lease");
64525
+ throw new Error(STALE_WORK_LEASE);
64401
64526
  return row;
64402
64527
  }
64403
64528
  async renew(lease, token, operationId, durationMs) {
@@ -64410,7 +64535,7 @@ class SqlPoolQueue {
64410
64535
  const { now } = await this.authority(tx, lease);
64411
64536
  const row = await this.current(tx, lease, token);
64412
64537
  if (row.epoch !== lease.epoch || row.expires_at < now)
64413
- throw new Error("Stale work lease");
64538
+ throw new Error(STALE_WORK_LEASE);
64414
64539
  const previous = await tx.get(`SELECT * FROM ${this.operations} WHERE work = ? AND generation = ? AND operation = ?`, token.id, token.generation, operationId);
64415
64540
  if (previous) {
64416
64541
  if (previous.request !== request)
@@ -64438,7 +64563,7 @@ class SqlPoolQueue {
64438
64563
  const { now, status } = await this.authority(tx, lease);
64439
64564
  const row = await this.current(tx, lease, token);
64440
64565
  if (row.epoch !== lease.epoch)
64441
- throw new Error("Stale work lease");
64566
+ throw new Error(STALE_WORK_LEASE);
64442
64567
  const previous = await tx.get(`SELECT * FROM ${this.operations} WHERE work = ? AND generation = ? AND operation = ?`, token.id, token.generation, operationId);
64443
64568
  if (previous) {
64444
64569
  if (previous.request !== request)
@@ -64446,7 +64571,7 @@ class SqlPoolQueue {
64446
64571
  return JSON.parse(previous.receipt);
64447
64572
  }
64448
64573
  if (row.expires_at < now)
64449
- throw new Error("Stale work lease");
64574
+ throw new Error(STALE_WORK_LEASE);
64450
64575
  if (!await this.admitsAttemptWrites(tx, lease, status))
64451
64576
  throw new Error("Pool root is not running");
64452
64577
  const checkpoint = mutation.checkpoint;
@@ -64486,7 +64611,7 @@ class SqlPoolQueue {
64486
64611
  const { now, status } = await this.authority(tx, lease);
64487
64612
  const row = await this.current(tx, lease, token, true);
64488
64613
  if (row.epoch !== lease.epoch)
64489
- throw new Error("Stale work lease");
64614
+ throw new Error(STALE_WORK_LEASE);
64490
64615
  if (row.state === "completed") {
64491
64616
  const previous = await tx.get(`SELECT result FROM ${this.work}_results WHERE work = ?`, token.id);
64492
64617
  if (previous?.result !== payload)
@@ -64494,7 +64619,7 @@ class SqlPoolQueue {
64494
64619
  return;
64495
64620
  }
64496
64621
  if (row.expires_at < now || !await this.admitsAttemptWrites(tx, lease, status))
64497
- throw new Error("Stale work lease");
64622
+ throw new Error(STALE_WORK_LEASE);
64498
64623
  if (JSON.parse(payload).runId !== fromRow(row).childRunId)
64499
64624
  throw new Error("Pool result identity mismatch");
64500
64625
  await tx.run(`INSERT INTO ${this.work}_results VALUES (?, ?)`, token.id, payload);
@@ -64519,6 +64644,40 @@ class SqlPoolQueue {
64519
64644
  return true;
64520
64645
  });
64521
64646
  }
64647
+ async extend(lease, token, durationMs) {
64648
+ positive(durationMs);
64649
+ lease = snapshotLease(lease);
64650
+ token = { ...token };
64651
+ return this.transaction(async (tx) => {
64652
+ const { now } = await this.authority(tx, lease);
64653
+ const row = await this.current(tx, lease, token);
64654
+ if (row.epoch !== lease.epoch)
64655
+ throw new Error(STALE_WORK_LEASE);
64656
+ const expiresAt = Math.max(Number(row.expires_at), expiry(now, durationMs));
64657
+ await tx.run(`UPDATE ${this.work} SET expires_at = ?, renewed_at = ? WHERE id = ?`, expiresAt, now, row.id);
64658
+ return { ...fromRow(row), expiresAt, renewedAt: now };
64659
+ });
64660
+ }
64661
+ async releaseExited(lease, token) {
64662
+ lease = snapshotLease(lease);
64663
+ token = { ...token };
64664
+ return this.transaction(async (tx) => {
64665
+ await this.authority(tx, lease);
64666
+ const row = await tx.get(`SELECT * FROM ${this.work} WHERE id = ? AND root = ?`, token.id, lease.runId);
64667
+ if (row?.state !== "leased" || row.generation !== token.generation || row.worker !== token.worker || row.epoch !== token.epoch)
64668
+ return false;
64669
+ await tx.run(`UPDATE ${this.work} SET state = 'ready', worker = NULL, expires_at = 0 WHERE id = ?`, row.id);
64670
+ return true;
64671
+ });
64672
+ }
64673
+ async releasePreviousIncarnations(lease) {
64674
+ lease = snapshotLease(lease);
64675
+ return this.transaction(async (tx) => {
64676
+ await this.authority(tx, lease);
64677
+ const released = await tx.run(`UPDATE ${this.work} SET state = 'ready', worker = NULL, expires_at = 0 WHERE root = ? AND state = 'leased' AND epoch < ?`, lease.runId, lease.epoch);
64678
+ return released.changes;
64679
+ });
64680
+ }
64522
64681
  async inspect(root) {
64523
64682
  return this.transaction(async (tx) => (await tx.all(`SELECT * FROM ${this.work} WHERE root = ? ORDER BY ready_at, rowid`, root)).map(fromRow));
64524
64683
  }
@@ -64529,7 +64688,7 @@ function openSqlPoolQueue(journal) {
64529
64688
  return Promise.reject(new Error("Worker pool requires a SQL run journal"));
64530
64689
  return SqlPoolQueue.open(storage);
64531
64690
  }
64532
- var STORE_NOW2 = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", MAX_WORKER_CLOCK_SKEW_MS = 1000;
64691
+ var STORE_NOW2 = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", MAX_WORKER_CLOCK_SKEW_MS = 1000, STALE_WORK_LEASE = "Stale work lease";
64533
64692
  var init_sql_pool_queue = __esm(() => {
64534
64693
  init_serializable();
64535
64694
  init_boundary_topology();
@@ -64617,6 +64776,9 @@ var init_host_options = __esm(() => {
64617
64776
  hostWorkerBindings = Symbol("host-worker-bindings");
64618
64777
  });
64619
64778
 
64779
+ // dist/builtin/workflows/src/workers/protocol.ts
64780
+ var WORKER_HEARTBEAT_INTERVAL_MS = 1000;
64781
+
64620
64782
  // dist/builtin/workflows/src/runs/foreground/pending-stage-delivery.ts
64621
64783
  function isWorkflowPendingStageDeliveryFailure(error) {
64622
64784
  if (error instanceof WorkflowPendingStageDeliveryFailedError)
@@ -65156,20 +65318,33 @@ var init_stage_handoff = __esm(() => {
65156
65318
  // dist/builtin/workflows/src/workers/local-pool.ts
65157
65319
  import { fork } from "node:child_process";
65158
65320
  import { randomUUID as randomUUID17 } from "node:crypto";
65159
- import { resolve as resolve13 } from "node:path";
65321
+ import { createWriteStream } from "node:fs";
65322
+ import { mkdir as mkdir6, rm as rm3, rmdir } from "node:fs/promises";
65323
+ import { dirname as dirname14, join as join29, resolve as resolve13 } from "node:path";
65160
65324
  import { setTimeout as delay2 } from "node:timers/promises";
65161
65325
  import { fileURLToPath as fileURLToPath6 } from "node:url";
65162
65326
  import { isBunBinary as isBunBinary2 } from "@bastani/atomic";
65327
+ function workerLogDirectory(journalPath, root) {
65328
+ return join29(dirname14(resolve13(journalPath)), "worker-logs", encodeURIComponent(root).replaceAll(".", "%2E"));
65329
+ }
65163
65330
 
65164
65331
  class LocalWorkerPool {
65165
65332
  backend;
65166
65333
  options;
65167
65334
  children = new Set;
65168
65335
  incarnations = new Map;
65336
+ readyWorkers = new Set;
65337
+ logs = new Map;
65338
+ logDirectory;
65169
65339
  cleanups = new Set;
65170
65340
  recovering;
65171
65341
  nextRecoveryAt = 0;
65172
65342
  controlAcks = new Map;
65343
+ lastHeard = new Map;
65344
+ lastTick = performance.now();
65345
+ rootStalledMs = 0;
65346
+ ticker;
65347
+ leaseMs;
65173
65348
  desiredControl;
65174
65349
  root;
65175
65350
  store;
@@ -65193,9 +65368,15 @@ class LocalWorkerPool {
65193
65368
  this.options = options;
65194
65369
  if (!Number.isSafeInteger(options.workers) || options.workers < 1)
65195
65370
  throw new Error("Invalid local worker count");
65371
+ if (options.startupTimeoutMs !== undefined && (!Number.isSafeInteger(options.startupTimeoutMs) || options.startupTimeoutMs < 1 || options.startupTimeoutMs > MAX_TIMER_DELAY_MS))
65372
+ throw new Error("Invalid local worker startup timeout");
65373
+ if (options.leaseMs !== undefined && (!Number.isSafeInteger(options.leaseMs) || options.leaseMs < MINIMUM_WORK_LEASE_MS || options.leaseMs > MAX_TIMER_DELAY_MS))
65374
+ throw new Error(`Invalid local worker lease: must be an integer from ${MINIMUM_WORK_LEASE_MS} to ${MAX_TIMER_DELAY_MS} ms`);
65375
+ this.leaseMs = options.leaseMs ?? DEFAULT_WORK_LEASE_MS;
65196
65376
  this.releaseBinding = bindChildWorkerDispatcher(backend, this, options.rootId);
65197
65377
  if (options.admission && options.rootId === undefined)
65198
65378
  workflowResourceAdmission.set(backend, options.admission);
65379
+ this.ticker = setInterval(() => this.observeResponsiveness(), ROOT_TICK_INTERVAL_MS).unref();
65199
65380
  }
65200
65381
  get workerPids() {
65201
65382
  return [...this.children].flatMap((child) => child.pid === undefined ? [] : [child.pid]);
@@ -65221,6 +65402,7 @@ class LocalWorkerPool {
65221
65402
  this.lease = { runId: root, holderId: state.holderId, epoch: state.epoch };
65222
65403
  const queue = await openSqlPoolQueue(this.backend.journal);
65223
65404
  await queue.initialize(this.lease, COORDINATOR_LEASE_MS);
65405
+ await queue.releasePreviousIncarnations(this.lease);
65224
65406
  for (;; ) {
65225
65407
  const intended = this.desiredControl ?? "running";
65226
65408
  await queue.setControl(this.lease, intended);
@@ -65235,8 +65417,8 @@ class LocalWorkerPool {
65235
65417
  if (!this.queue || !this.lease)
65236
65418
  return;
65237
65419
  const control = await this.queue.setControl(this.lease, state);
65238
- const deadline = Date.now() + WORK_LEASE_MS;
65239
- while ([...this.incarnations.keys()].some((worker) => (this.controlAcks.get(worker) ?? -1) < control.revision)) {
65420
+ const deadline = Date.now() + CONTROL_ACKNOWLEDGEMENT_TIMEOUT_MS;
65421
+ while ([...this.readyWorkers].some((worker) => (this.controlAcks.get(worker) ?? -1) < control.revision)) {
65240
65422
  if (this.desiredControl !== state)
65241
65423
  return;
65242
65424
  if (Date.now() >= deadline) {
@@ -65306,7 +65488,16 @@ class LocalWorkerPool {
65306
65488
  root,
65307
65489
  holder: "coordinator"
65308
65490
  });
65309
- await Promise.all(Array.from({ length: this.options.workers }, () => this.spawn(root)));
65491
+ this.logDirectory = workerLogDirectory(this.options.journalPath, root);
65492
+ await mkdir6(this.logDirectory, { recursive: true }).catch(() => {
65493
+ return;
65494
+ });
65495
+ const workers = Array.from({ length: this.options.workers }, () => this.spawn(root));
65496
+ try {
65497
+ await Promise.any(workers);
65498
+ } catch (error) {
65499
+ throw error.errors[0];
65500
+ }
65310
65501
  }
65311
65502
  spawn(root) {
65312
65503
  if (this.closed)
@@ -65327,26 +65518,56 @@ class LocalWorkerPool {
65327
65518
  let serial = Promise.resolve();
65328
65519
  let work;
65329
65520
  const deliveries = new Map;
65521
+ const logPath = join29(this.logDirectory, `${worker}.log`);
65522
+ this.logs.set(child, logPath);
65523
+ let logFailure;
65524
+ const log = createWriteStream(logPath, { flags: "a" });
65525
+ log.on("error", (error) => {
65526
+ logFailure = error.message;
65527
+ });
65330
65528
  let stderr = "";
65331
65529
  child.stderr?.on("data", (chunk) => {
65332
- stderr = (stderr + chunk.toString()).slice(-8192);
65530
+ stderr = (stderr + chunk.toString()).slice(-STDERR_TAIL_CHARACTERS);
65531
+ });
65532
+ child.stderr?.on("error", (error) => {
65533
+ logFailure ??= error.message;
65534
+ log.end();
65333
65535
  });
65536
+ child.stderr?.pipe(log);
65537
+ const diagnosed = (summary) => new Error(`${summary} (worker stderr log: ${logFailure === undefined ? logPath : `unavailable, ${logFailure}`})${stderr ? `: ${stderr}` : ""}`);
65538
+ const startupTimeoutMs = this.options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
65539
+ let timedOut = false;
65540
+ let terminated = false;
65541
+ const terminate = () => {
65542
+ if (terminated)
65543
+ return;
65544
+ terminated = true;
65545
+ child.kill("SIGKILL");
65546
+ };
65547
+ const replied = (error) => {
65548
+ if (error)
65549
+ terminate();
65550
+ };
65334
65551
  return new Promise((resolve, reject) => {
65335
65552
  const startup = setTimeout(() => {
65336
- this.failure = new Error("Local worker startup timed out");
65337
- reject(this.failure);
65338
- child.kill("SIGKILL");
65339
- }, 30000);
65340
- child.once("error", (error) => {
65553
+ timedOut = true;
65554
+ terminate();
65555
+ }, startupTimeoutMs);
65556
+ child.on("error", (error) => {
65341
65557
  clearTimeout(startup);
65342
65558
  reject(error);
65559
+ terminate();
65343
65560
  });
65561
+ const closed = new Promise((resolve) => child.once("close", () => resolve()));
65344
65562
  child.once("exit", () => {
65563
+ Promise.race([closed, delay2(STDERR_DRAIN_GRACE_MS)]).then(() => reject(diagnosed(timedOut ? `Local worker startup timed out after ${startupTimeoutMs} ms` : "Local worker exited before readiness")));
65345
65564
  this.children.delete(child);
65565
+ this.logs.delete(child);
65346
65566
  clearTimeout(startup);
65347
65567
  this.incarnations.delete(worker);
65348
- reject(new Error(`Local worker exited before readiness: ${stderr}`));
65568
+ this.readyWorkers.delete(worker);
65349
65569
  this.controlAcks.delete(worker);
65570
+ this.lastHeard.delete(worker);
65350
65571
  if (work)
65351
65572
  this.handoff?.fence(work);
65352
65573
  for (const delivery of deliveries.values())
@@ -65369,9 +65590,11 @@ class LocalWorkerPool {
65369
65590
  }
65370
65591
  if (this.closed)
65371
65592
  return;
65593
+ if (work)
65594
+ await this.queue.releaseExited(this.lease, work);
65372
65595
  const exhausted = work ? work.generation >= 3 : ++this.replacements > 3;
65373
65596
  if (exhausted)
65374
- this.failure = new Error(`Local worker retry limit: ${stderr}`);
65597
+ this.failure = diagnosed("Local worker retry limit");
65375
65598
  else
65376
65599
  await this.spawn(root);
65377
65600
  }).catch((error) => {
@@ -65381,6 +65604,8 @@ class LocalWorkerPool {
65381
65604
  cleanup.finally(() => this.cleanups.delete(cleanup));
65382
65605
  });
65383
65606
  child.on("message", (message) => {
65607
+ this.observeResponsiveness();
65608
+ this.lastHeard.set(worker, { at: performance.now(), rootStalled: this.rootStalledMs });
65384
65609
  if (message.kind === "delivery-ack") {
65385
65610
  const pending = deliveries.get(message.deliveryId);
65386
65611
  deliveries.delete(message.deliveryId);
@@ -65392,7 +65617,10 @@ class LocalWorkerPool {
65392
65617
  }
65393
65618
  const response = serial.then(async () => {
65394
65619
  if (message.kind === "ready") {
65620
+ if (terminated)
65621
+ return;
65395
65622
  ready = true;
65623
+ this.readyWorkers.add(worker);
65396
65624
  clearTimeout(startup);
65397
65625
  resolve();
65398
65626
  return;
@@ -65419,11 +65647,13 @@ class LocalWorkerPool {
65419
65647
  work = await queue.claim(lease, worker, this.sources.map((source) => ({
65420
65648
  definition: `${source.path}#${source.exportName}`,
65421
65649
  digest: source.digest
65422
- })), WORK_LEASE_MS);
65650
+ })), this.leaseMs);
65423
65651
  return work;
65424
65652
  }
65425
65653
  if (!work)
65426
65654
  throw new Error("Worker has no active attempt");
65655
+ if (message.kind === "renew" || work.expiresAt - Date.now() < this.leaseMs / 2)
65656
+ work = await queue.extend(lease, work, this.leaseMs);
65427
65657
  if (message.kind === "activity")
65428
65658
  return this.handoff?.publish(work, message.runs);
65429
65659
  if (message.kind === "route")
@@ -65444,10 +65674,8 @@ class LocalWorkerPool {
65444
65674
  });
65445
65675
  }));
65446
65676
  }
65447
- if (message.kind === "renew") {
65448
- work = await queue.renew(lease, work, randomUUID17(), WORK_LEASE_MS);
65677
+ if (message.kind === "renew")
65449
65678
  return;
65450
- }
65451
65679
  if (message.kind === "commit") {
65452
65680
  const receipt = await queue.commit(lease, work, message.operation, message.mutation);
65453
65681
  this.usageTotal = await this.usageLedger.total(root);
@@ -65472,13 +65700,7 @@ class LocalWorkerPool {
65472
65700
  });
65473
65701
  if (message.kind !== "delivery" && message.kind !== "delivery-ready")
65474
65702
  serial = response.then(() => {}, () => {});
65475
- response.then((value2) => {
65476
- if (child.connected)
65477
- child.send({ id: message.id, value: value2 });
65478
- }, (error) => {
65479
- if (child.connected)
65480
- child.send({ id: message.id, error: error.message });
65481
- });
65703
+ response.then((value2) => child.send({ id: message.id, value: value2 }, replied), (error) => child.send({ id: message.id, error: error.message }, replied));
65482
65704
  });
65483
65705
  });
65484
65706
  }
@@ -65488,14 +65710,22 @@ class LocalWorkerPool {
65488
65710
  if (this.closed || performance.now() < this.nextRecoveryAt)
65489
65711
  return Promise.resolve();
65490
65712
  this.recovering = (async () => {
65713
+ await new Promise((resolve) => setImmediate(resolve));
65491
65714
  await this.admission?.reconcile();
65492
65715
  const expiredBefore = Date.now();
65493
65716
  for (const work of await this.queue.inspect(root)) {
65494
65717
  if (work.state !== "leased" || work.expiresAt >= expiredBefore)
65495
65718
  continue;
65496
65719
  const child = work.worker === null ? undefined : this.incarnations.get(work.worker);
65497
- if (child)
65498
- await this.stop(child);
65720
+ if (child && work.worker !== null && this.isLive(work.worker, child)) {
65721
+ await this.queue.extend(this.lease, work, this.leaseMs).catch((error) => {
65722
+ if (error.message !== STALE_WORK_LEASE)
65723
+ throw error;
65724
+ });
65725
+ continue;
65726
+ }
65727
+ if (child && !await this.stop(child).then(() => true, () => false))
65728
+ continue;
65499
65729
  await this.queue.expire(this.lease, work);
65500
65730
  }
65501
65731
  })().finally(() => {
@@ -65504,6 +65734,18 @@ class LocalWorkerPool {
65504
65734
  });
65505
65735
  return this.recovering;
65506
65736
  }
65737
+ observeResponsiveness() {
65738
+ const now = performance.now();
65739
+ const gap = now - this.lastTick;
65740
+ if (gap > ROOT_STALL_THRESHOLD_MS)
65741
+ this.rootStalledMs += gap - ROOT_TICK_INTERVAL_MS;
65742
+ this.lastTick = now;
65743
+ }
65744
+ isLive(worker, child) {
65745
+ this.observeResponsiveness();
65746
+ const heard = this.lastHeard.get(worker);
65747
+ return child.exitCode === null && child.signalCode === null && child.connected && heard !== undefined && performance.now() - heard.at - (this.rootStalledMs - heard.rootStalled) < this.leaseMs;
65748
+ }
65507
65749
  stop(child) {
65508
65750
  if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null)
65509
65751
  return Promise.resolve();
@@ -65520,9 +65762,13 @@ class LocalWorkerPool {
65520
65762
  try {
65521
65763
  return await this.dispatch(definition, inputs, options);
65522
65764
  } catch (error) {
65523
- if (this.desiredControl !== "quit" || !findWorkflowGracefulQuit(error))
65524
- await this.close();
65525
- throw error;
65765
+ try {
65766
+ if (this.desiredControl !== "quit" || !findWorkflowGracefulQuit(error))
65767
+ await this.close();
65768
+ } catch (closeError) {
65769
+ throw asWorkflowInfrastructureError(closeError) ?? closeError;
65770
+ }
65771
+ throw asWorkflowInfrastructureError(error) ?? error;
65526
65772
  }
65527
65773
  }
65528
65774
  async dispatch(definition, inputs, options) {
@@ -65566,6 +65812,8 @@ class LocalWorkerPool {
65566
65812
  for (;; ) {
65567
65813
  options.signal?.throwIfAborted();
65568
65814
  if (this.closed) {
65815
+ if (this.failure)
65816
+ throw this.failure;
65569
65817
  if (this.desiredControl === "quit")
65570
65818
  throw new WorkflowGracefulQuitError(root, "worker pool teardown");
65571
65819
  throw new Error("Local worker pool is closed");
@@ -65592,19 +65840,28 @@ class LocalWorkerPool {
65592
65840
  }
65593
65841
  async dispose() {
65594
65842
  this.closed = true;
65843
+ clearInterval(this.ticker);
65595
65844
  this.releaseBinding();
65596
65845
  if (this.options.rootId === undefined)
65597
65846
  workflowResourceAdmission.delete(this.backend);
65598
65847
  let failure;
65848
+ let stopping = [];
65599
65849
  try {
65600
65850
  await this.recovering?.catch(() => {});
65601
- await Promise.all([...this.children].map((child) => this.stop(child)));
65851
+ stopping = [...this.children].map((child) => ({ child, log: this.logs.get(child) }));
65852
+ await Promise.all(stopping.map(({ child }) => this.stop(child)));
65602
65853
  await Promise.all([...this.cleanups]);
65603
65854
  if (this.cleanupFailure)
65604
65855
  throw this.cleanupFailure;
65605
65856
  } catch (error) {
65606
65857
  failure = error;
65607
65858
  }
65859
+ if (failure === undefined) {
65860
+ const stopped = stopping.flatMap(({ child, log }) => log !== undefined && (child.exitCode !== null || child.signalCode !== null) ? [log] : []);
65861
+ await Promise.all(stopped.map((log) => rm3(log, { force: true }).catch(() => {})));
65862
+ if (this.logDirectory !== undefined)
65863
+ await rmdir(this.logDirectory).catch(() => {});
65864
+ }
65608
65865
  try {
65609
65866
  await this.releaseRoutes?.();
65610
65867
  } catch (error) {
@@ -65614,7 +65871,7 @@ class LocalWorkerPool {
65614
65871
  throw failure;
65615
65872
  }
65616
65873
  }
65617
- var INTERNAL_WORKFLOW_WORKER_ARG = "--internal-workflow-worker", WORK_LEASE_MS = 5000, RECOVERY_INTERVAL_MS = 500, COORDINATOR_LEASE_MS;
65874
+ var INTERNAL_WORKFLOW_WORKER_ARG = "--internal-workflow-worker", DEFAULT_WORK_LEASE_MS = 30000, MINIMUM_WORK_LEASE_MS, CONTROL_ACKNOWLEDGEMENT_TIMEOUT_MS = 5000, RECOVERY_INTERVAL_MS = 500, ROOT_TICK_INTERVAL_MS = 100, ROOT_STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, DEFAULT_STARTUP_TIMEOUT_MS = 30000, MAX_TIMER_DELAY_MS = 2147483647, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000;
65618
65875
  var init_local_pool = __esm(() => {
65619
65876
  init_internal();
65620
65877
  init_sql_pool_queue();
@@ -65622,12 +65879,14 @@ var init_local_pool = __esm(() => {
65622
65879
  init_sql_run_journal();
65623
65880
  init_workflow_tool_abort();
65624
65881
  init_sdk_intercom();
65882
+ init_workflow_infrastructure_error();
65625
65883
  init_admission_binding();
65626
65884
  init_definitions();
65627
65885
  init_dispatch();
65628
65886
  init_host_options();
65629
65887
  init_resource_admission();
65630
65888
  init_stage_handoff();
65889
+ MINIMUM_WORK_LEASE_MS = 2 * WORKER_HEARTBEAT_INTERVAL_MS;
65631
65890
  COORDINATOR_LEASE_MS = 3 * STAGE_SESSION_HEARTBEAT_INTERVAL_MS;
65632
65891
  });
65633
65892
 
@@ -65679,6 +65938,8 @@ async function activatePublicPool(backend, options) {
65679
65938
  const cwd = options.cwd ?? process.cwd();
65680
65939
  const localWorkers = options.localWorkers && {
65681
65940
  ...options.localWorkers,
65941
+ startupTimeoutMs: undefined,
65942
+ leaseMs: undefined,
65682
65943
  definitions: options.localWorkers.definitions.map((source) => ({ ...source, path: resolve14(cwd, source.path) }))
65683
65944
  };
65684
65945
  const admission = options.admission && {
@@ -65707,7 +65968,9 @@ async function activatePublicPool(backend, options) {
65707
65968
  journalPath: backend.journal.path,
65708
65969
  workers: localWorkers.count,
65709
65970
  definitions,
65710
- admission: resources.admission ?? {}
65971
+ admission: resources.admission ?? {},
65972
+ startupTimeoutMs: options.localWorkers?.startupTimeoutMs,
65973
+ leaseMs: options.localWorkers?.leaseMs
65711
65974
  });
65712
65975
  try {
65713
65976
  pool.validateOptions(options);
@@ -66625,12 +66888,12 @@ function createChildWorkflowRunner(input) {
66625
66888
  const childExitStatus = isWorkflowExitStatus2(childRun.status) ? childRun.status : "blocked";
66626
66889
  if (!isWorkflowExitStatus2(childRun.status) && !childBudgetExceeded || childRun.status === "failed" && childRun.exited !== true) {
66627
66890
  const failedChildStage = childRun.stages.find((stage) => stage.failureKind !== undefined);
66628
- throw new Error(`atomic-workflows: child workflow "${childName}" (${child.name}) failed with status ${childRun.status}${childRun.error !== undefined ? `: ${childRun.error}` : ""}`, {
66629
- cause: {
66630
- ...failedChildStage?.failureKind !== undefined ? { code: failedChildStage.failureKind } : {},
66631
- ...failedChildStage?.failureMessage !== undefined ? { message: failedChildStage.failureMessage } : {}
66632
- }
66633
- });
66891
+ const message = `atomic-workflows: child workflow "${childName}" (${child.name}) failed with status ${childRun.status}${childRun.error !== undefined ? `: ${childRun.error}` : ""}`;
66892
+ const cause = {
66893
+ ...failedChildStage?.failureKind !== undefined ? { code: failedChildStage.failureKind } : {},
66894
+ ...failedChildStage?.failureMessage !== undefined ? { message: failedChildStage.failureMessage } : {}
66895
+ };
66896
+ throw new Error(message, { cause });
66634
66897
  }
66635
66898
  const outputs = selectWorkflowOutputs(child, childRun.result);
66636
66899
  const childExited = childBudgetExceeded || childRun.exited === true || childRun.status !== "completed";
@@ -66691,7 +66954,7 @@ function describeErrorWithCauses(error) {
66691
66954
  const messages = [];
66692
66955
  const visited = new Set;
66693
66956
  let current = error;
66694
- for (let depth = 0;current !== undefined && depth < MAX_CAUSE_DEPTH && !visited.has(current); depth++) {
66957
+ for (let depth = 0;current !== undefined && depth < MAX_CAUSE_DEPTH2 && !visited.has(current); depth++) {
66695
66958
  visited.add(current);
66696
66959
  const message = current instanceof Error ? current.message : String(current);
66697
66960
  if (!messages.includes(message))
@@ -66700,7 +66963,7 @@ function describeErrorWithCauses(error) {
66700
66963
  }
66701
66964
  return messages.join(": ");
66702
66965
  }
66703
- var MAX_CAUSE_DEPTH = 5;
66966
+ var MAX_CAUSE_DEPTH2 = 5;
66704
66967
 
66705
66968
  // dist/builtin/workflows/src/engine/run-durable-finalize.ts
66706
66969
  async function finalizeDurableTerminalStatus(input) {
@@ -67220,7 +67483,7 @@ function recordedOwners(boundary, reason) {
67220
67483
  const pending = [{ value: reason, depth: 0 }];
67221
67484
  while (pending.length > 0) {
67222
67485
  const { value: value2, depth } = pending.pop();
67223
- if (depth > MAX_CAUSE_DEPTH2)
67486
+ if (depth > MAX_CAUSE_DEPTH3)
67224
67487
  continue;
67225
67488
  if (isPrimitive(value2)) {
67226
67489
  if (depth === 0 || value2 !== undefined && value2 !== null) {
@@ -67542,7 +67805,7 @@ function containUiRejections(ui, scope) {
67542
67805
  custom: (factory, options) => scope.watch(ui.custom(factory, options))
67543
67806
  };
67544
67807
  }
67545
- var EVENT = "unhandledRejection", BOUNDARY_KEY, FLAG = "--unhandled-rejections", MAX_CAUSE_DEPTH2 = 8, MAX_PRIMITIVES_PER_RUN = 64, MAX_STOP_STRINGS = 64, ENDED_RUN_GRACE_MS = 30000, NATIVE_REPORTING_MODES, WATCHED_STAGE_METHODS;
67808
+ var EVENT = "unhandledRejection", BOUNDARY_KEY, FLAG = "--unhandled-rejections", MAX_CAUSE_DEPTH3 = 8, MAX_PRIMITIVES_PER_RUN = 64, MAX_STOP_STRINGS = 64, ENDED_RUN_GRACE_MS = 30000, NATIVE_REPORTING_MODES, WATCHED_STAGE_METHODS;
67546
67809
  var init_run_rejection_boundary = __esm(() => {
67547
67810
  init_prompt_callsite();
67548
67811
  init_workflow_source();
@@ -72290,8 +72553,8 @@ var init_stage_runner_controller = __esm(() => {
72290
72553
 
72291
72554
  // dist/builtin/workflows/src/runs/foreground/stage-runner-output.ts
72292
72555
  import { createHash as createHash4 } from "node:crypto";
72293
- import { mkdir as mkdir6, writeFile as writeFile10 } from "node:fs/promises";
72294
- import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute11, join as join29, resolve as resolve15 } from "node:path";
72556
+ import { mkdir as mkdir7, writeFile as writeFile10 } from "node:fs/promises";
72557
+ import { basename as basename5, dirname as dirname15, isAbsolute as isAbsolute11, join as join30, resolve as resolve15 } from "node:path";
72295
72558
  function normalizeMaxOutput2(maxOutput) {
72296
72559
  return {
72297
72560
  bytes: maxOutput?.bytes ?? DEFAULT_MAX_OUTPUT_BYTES,
@@ -72354,7 +72617,7 @@ function formatByteSize(bytes) {
72354
72617
  }
72355
72618
  function transcriptPath(runId, outputPath) {
72356
72619
  const digest = createHash4("sha256").update(outputPath).digest("hex").slice(0, 16);
72357
- return join29(workflowArtifactRunPath(runId), "transcripts", `${digest}-${basename5(outputPath)}.transcript.md`);
72620
+ return join30(workflowArtifactRunPath(runId), "transcripts", `${digest}-${basename5(outputPath)}.transcript.md`);
72358
72621
  }
72359
72622
  function appendContent(lines, content) {
72360
72623
  if (typeof content === "string") {
@@ -72474,7 +72737,7 @@ async function finalizePromptOutput(fullOutput, outputOptions, runtimeCwd, runId
72474
72737
  const transcriptFile = transcriptPath(runId, outputPath);
72475
72738
  const transcript = renderTranscript(messages, fullOutput);
72476
72739
  try {
72477
- await mkdir6(dirname14(outputPath), { recursive: true });
72740
+ await mkdir7(dirname15(outputPath), { recursive: true });
72478
72741
  await writeFile10(outputPath, fullOutput, "utf8");
72479
72742
  } catch (err) {
72480
72743
  return `${displayOutput}
@@ -72486,7 +72749,7 @@ ${err instanceof Error ? err.message : String(err)}`;
72486
72749
  let transcriptError;
72487
72750
  try {
72488
72751
  await ensureWorkflowArtifactRunDirectory(runId);
72489
- await mkdir6(dirname14(transcriptFile), { recursive: true });
72752
+ await mkdir7(dirname15(transcriptFile), { recursive: true });
72490
72753
  await writeFile10(transcriptFile, transcript, "utf8");
72491
72754
  transcriptAvailable = true;
72492
72755
  } catch (err) {
@@ -73395,7 +73658,7 @@ function reportsClosedSession(error) {
73395
73658
  }
73396
73659
  function isSessionClosedError(error) {
73397
73660
  let current = error;
73398
- for (let depth = 0;depth < MAX_CAUSE_DEPTH3 && current !== undefined; depth++) {
73661
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH4 && current !== undefined; depth++) {
73399
73662
  try {
73400
73663
  if (reportsClosedSession(current))
73401
73664
  return true;
@@ -73406,7 +73669,7 @@ function isSessionClosedError(error) {
73406
73669
  }
73407
73670
  return false;
73408
73671
  }
73409
- var MAX_CAUSE_DEPTH3 = 8;
73672
+ var MAX_CAUSE_DEPTH4 = 8;
73410
73673
  var init_session_closed_error = () => {};
73411
73674
 
73412
73675
  // dist/builtin/workflows/src/engine/workflow-activity.ts
@@ -88486,7 +88749,7 @@ function installCompactionHook(api, store) {
88486
88749
  });
88487
88750
  }
88488
88751
  // dist/builtin/workflows/src/extension/config-loader.ts
88489
- import { isAbsolute as isAbsolute12, join as join30 } from "node:path";
88752
+ import { isAbsolute as isAbsolute12, join as join31 } from "node:path";
88490
88753
  import { CONFIG_DIR_NAME as CONFIG_DIR_NAME2, getAgentDir as getAgentDir6 } from "@bastani/atomic";
88491
88754
  // dist/builtin/workflows/src/extension/lifecycle-notifications.ts
88492
88755
  init_returned_run_status();
@@ -89403,7 +89666,7 @@ function resolveWorkflowPaths(workflows, baseDir) {
89403
89666
  }
89404
89667
  return Object.fromEntries(Object.entries(workflows).map(([name, entry]) => [
89405
89668
  name,
89406
- isAbsolute12(entry.path) ? entry.path : join30(baseDir, entry.path)
89669
+ isAbsolute12(entry.path) ? entry.path : join31(baseDir, entry.path)
89407
89670
  ]));
89408
89671
  }
89409
89672
  function workflowAgentDirs(opts) {
@@ -89411,12 +89674,12 @@ function workflowAgentDirs(opts) {
89411
89674
  return opts.agentDirs;
89412
89675
  if (opts.homeDir !== undefined) {
89413
89676
  const homeDir = opts.homeDir;
89414
- return [join30(homeDir, CONFIG_DIR_NAME2, "agent")];
89677
+ return [join31(homeDir, CONFIG_DIR_NAME2, "agent")];
89415
89678
  }
89416
89679
  return [getAgentDir6()];
89417
89680
  }
89418
89681
  function toScopedDiscoveryConfig(globalConfig, projectConfig, opts) {
89419
- const globalBase = opts.agentDir ?? (opts.homeDir === undefined ? getAgentDir6() : join30(opts.homeDir, CONFIG_DIR_NAME2, "agent"));
89682
+ const globalBase = opts.agentDir ?? (opts.homeDir === undefined ? getAgentDir6() : join31(opts.homeDir, CONFIG_DIR_NAME2, "agent"));
89420
89683
  const projectBase = opts.projectRoot;
89421
89684
  const result = {};
89422
89685
  const projectWorkflows = resolveWorkflowPaths(projectConfig?.workflows, projectBase);
@@ -89426,7 +89689,7 @@ function toScopedDiscoveryConfig(globalConfig, projectConfig, opts) {
89426
89689
  if (hasWorkflows(globalConfig)) {
89427
89690
  const projectKeys = new Set(Object.keys(projectWorkflows ?? {}));
89428
89691
  const globalEntries = Object.entries(globalConfig.workflows).filter(([name]) => !projectKeys.has(name)).map(([name, entry]) => {
89429
- return [name, isAbsolute12(entry.path) ? entry.path : join30(globalBase, entry.path)];
89692
+ return [name, isAbsolute12(entry.path) ? entry.path : join31(globalBase, entry.path)];
89430
89693
  });
89431
89694
  if (globalEntries.length > 0) {
89432
89695
  result.globalWorkflows = Object.fromEntries(globalEntries);
@@ -89437,8 +89700,8 @@ function toScopedDiscoveryConfig(globalConfig, projectConfig, opts) {
89437
89700
  async function loadWorkflowConfig(opts = {}) {
89438
89701
  const projectRoot = opts.projectRoot ?? process.cwd();
89439
89702
  const diagnostics = [];
89440
- const globalCandidates = workflowAgentDirs(opts).map((agentDir) => join30(agentDir, "extensions", "workflow", "config.json"));
89441
- const projectCandidates = [join30(projectRoot, CONFIG_DIR_NAME2, "extensions", "workflow", "config.json")];
89703
+ const globalCandidates = workflowAgentDirs(opts).map((agentDir) => join31(agentDir, "extensions", "workflow", "config.json"));
89704
+ const projectCandidates = [join31(projectRoot, CONFIG_DIR_NAME2, "extensions", "workflow", "config.json")];
89442
89705
  let globalConfig = null;
89443
89706
  for (let i = globalCandidates.length - 1;i >= 0; i--) {
89444
89707
  const globalPath = globalCandidates[i];
@@ -91094,7 +91357,7 @@ init_possible_stages();
91094
91357
  import { registerExtensionContextRetirementEffect as registerExtensionContextRetirementEffect2 } from "@bastani/atomic";
91095
91358
 
91096
91359
  // dist/builtin/workflows/src/extension/discovery.ts
91097
- import { join as join32 } from "node:path";
91360
+ import { join as join33 } from "node:path";
91098
91361
  import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3, getAgentDir as getAgentDir7 } from "@bastani/atomic";
91099
91362
 
91100
91363
  // dist/builtin/workflows/builtin/index.ts
@@ -91115,12 +91378,12 @@ init_registry();
91115
91378
  // dist/builtin/workflows/src/extension/discovery-loaders.ts
91116
91379
  init_workflow_module_loader();
91117
91380
  import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
91118
- import { extname, isAbsolute as isAbsolute13, join as join31, resolve as resolve17 } from "node:path";
91381
+ import { extname, isAbsolute as isAbsolute13, join as join32, resolve as resolve17 } from "node:path";
91119
91382
  async function scanWorkflowDir(dir) {
91120
91383
  try {
91121
91384
  const entries = await readdir2(dir, { withFileTypes: true });
91122
91385
  const WORKFLOW_EXTS = new Set([".ts", ".js", ".mjs", ".cjs"]);
91123
- return entries.filter((e) => e.isFile() && WORKFLOW_EXTS.has(extname(e.name))).map((e) => join31(dir, e.name)).sort();
91386
+ return entries.filter((e) => e.isFile() && WORKFLOW_EXTS.has(extname(e.name))).map((e) => join32(dir, e.name)).sort();
91124
91387
  } catch {
91125
91388
  return null;
91126
91389
  }
@@ -91190,7 +91453,7 @@ function workflowAgentDirs2(options) {
91190
91453
  return options.agentDirs;
91191
91454
  if (options?.homeDir !== undefined) {
91192
91455
  const homeDir = options.homeDir;
91193
- return [join32(homeDir, CONFIG_DIR_NAME3, "agent")];
91456
+ return [join33(homeDir, CONFIG_DIR_NAME3, "agent")];
91194
91457
  }
91195
91458
  return [getAgentDir7()];
91196
91459
  }
@@ -91346,7 +91609,7 @@ async function discoverWorkflows(options) {
91346
91609
  registry = await applyBatch(candidates, registry, sources, diagnostics);
91347
91610
  }
91348
91611
  }
91349
- for (const dir of [join32(cwd, CONFIG_DIR_NAME3, "workflows")]) {
91612
+ for (const dir of [join33(cwd, CONFIG_DIR_NAME3, "workflows")]) {
91350
91613
  const candidates = await loadFromDir(dir, "project-local", diagnostics, getWorkflowHostModules);
91351
91614
  registry = await applyBatch(candidates, registry, sources, diagnostics);
91352
91615
  }
@@ -91358,7 +91621,7 @@ async function discoverWorkflows(options) {
91358
91621
  registry = await applyBatch(candidates, registry, sources, diagnostics);
91359
91622
  }
91360
91623
  }
91361
- for (const dir of agentDirs.map((agentDir) => join32(agentDir, "workflows")).reverse()) {
91624
+ for (const dir of agentDirs.map((agentDir) => join33(agentDir, "workflows")).reverse()) {
91362
91625
  const candidates = await loadFromDir(dir, "user-global", diagnostics, getWorkflowHostModules);
91363
91626
  registry = await applyBatch(candidates, registry, sources, diagnostics);
91364
91627
  }
@@ -91959,6 +92222,8 @@ function createDurableResumeRuntime(deps) {
91959
92222
  try {
91960
92223
  await backend.hydrateResumableWorkflows();
91961
92224
  await hydrateStoredWorkflowCandidates(backend);
92225
+ for (const entry of backend.listCompletedWorkflows())
92226
+ await backend.hydrateWorkflow(entry.workflowId);
91962
92227
  return await listOpenableCompletedWorkflows(backend, deps.adapters?.sessionStore);
91963
92228
  } finally {
91964
92229
  purgeSuppressedWorkflowRuns(backend, deps.store, deps.toolControlRegistry);
@@ -91969,8 +92234,11 @@ function createDurableResumeRuntime(deps) {
91969
92234
  const finish = workflowObservationRuntime(deps.store).beginRecovery();
91970
92235
  try {
91971
92236
  const backend = selectedBackend(workflowId);
91972
- const entry = resolveCatalogEntry(workflowId, catalog ?? []);
91973
- const handle = backend.getWorkflow(entry?.workflowId ?? workflowId);
92237
+ const entry = resolveCatalogEntry(workflowId, catalog ?? backend.listCompletedWorkflows());
92238
+ const id = entry?.workflowId ?? workflowId;
92239
+ if (entry !== undefined || isFullRunId(workflowId))
92240
+ await backend.hydrateWorkflow(id);
92241
+ const handle = backend.getWorkflow(id);
91974
92242
  return await openCompletedDurableWorkflow(workflowId, {
91975
92243
  durableBackend: backend,
91976
92244
  store: deps.store,
@@ -92227,19 +92495,19 @@ function createExtensionRuntime(opts = {}) {
92227
92495
  }
92228
92496
 
92229
92497
  // dist/builtin/workflows/src/extension/status-writer.ts
92230
- import { mkdir as mkdir7, rename as rename2, writeFile as writeFile11 } from "node:fs/promises";
92231
- import { dirname as dirname15, join as join33 } from "node:path";
92498
+ import { mkdir as mkdir8, rename as rename2, writeFile as writeFile11 } from "node:fs/promises";
92499
+ import { dirname as dirname16, join as join34 } from "node:path";
92232
92500
  import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@bastani/atomic";
92233
- var DEFAULT_STATUS_SUBPATH = join33(CONFIG_DIR_NAME4, "workflows", "status.json");
92501
+ var DEFAULT_STATUS_SUBPATH = join34(CONFIG_DIR_NAME4, "workflows", "status.json");
92234
92502
  function resolveStatusFilePath(config, opts = {}) {
92235
92503
  if (config.statusFilePath)
92236
92504
  return config.statusFilePath;
92237
92505
  const root = opts.projectRoot ?? process.cwd();
92238
- return join33(root, DEFAULT_STATUS_SUBPATH);
92506
+ return join34(root, DEFAULT_STATUS_SUBPATH);
92239
92507
  }
92240
92508
  async function atomicWriteJson(path, content) {
92241
- const dir = dirname15(path);
92242
- await mkdir7(dir, { recursive: true });
92509
+ const dir = dirname16(path);
92510
+ await mkdir8(dir, { recursive: true });
92243
92511
  const tmpPath = `${path}.tmp`;
92244
92512
  await writeFile11(tmpPath, content, "utf8");
92245
92513
  await rename2(tmpPath, path);
@@ -92524,7 +92792,7 @@ function createWorkflowHeartbeatDelivery(options) {
92524
92792
  }
92525
92793
 
92526
92794
  // dist/builtin/workflows/src/extension/workflow-heartbeat-scheduler.ts
92527
- var MAX_TIMER_DELAY_MS = 2147483647;
92795
+ var MAX_TIMER_DELAY_MS2 = 2147483647;
92528
92796
  var MS_PER_MINUTE = 60000;
92529
92797
  function createWorkflowHeartbeatSchedulerState() {
92530
92798
  return {
@@ -92753,7 +93021,7 @@ function installWorkflowHeartbeatScheduler(options) {
92753
93021
  if (next === undefined)
92754
93022
  return;
92755
93023
  const nextAt = next.scheduledAt;
92756
- const delay = Math.min(Math.max(1, Math.ceil(nextAt - now())), MAX_TIMER_DELAY_MS);
93024
+ const delay = Math.min(Math.max(1, Math.ceil(nextAt - now())), MAX_TIMER_DELAY_MS2);
92757
93025
  const handle = timers.setTimeout(() => {
92758
93026
  timerHandle = undefined;
92759
93027
  processDue();
@@ -93410,6 +93678,11 @@ function createWorkflowExtensionRuntimeState(pi, adapters, options = {}) {
93410
93678
  return targeted.call(runtimeRef.current, workflowIds);
93411
93679
  return runtimeRef.current.prepareDurableResumable();
93412
93680
  },
93681
+ get prepareDurableCatalog() {
93682
+ const runtime = runtimeRef.current;
93683
+ const catalog = runtime.prepareDurableCatalog;
93684
+ return catalog === undefined ? undefined : () => catalog.call(runtime);
93685
+ },
93413
93686
  prepareCompletedDurable() {
93414
93687
  return runtimeRef.current.prepareCompletedDurable?.() ?? Promise.resolve([]);
93415
93688
  },