@bastani/atomic 0.9.44-alpha.3 → 0.9.44-alpha.5

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 CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.44-alpha.5] - 2026-10-25
6
+
7
+ Large workflow fan-outs keep their Intercom routes and journal writes responsive under load.
8
+
9
+ - **A workflow host that keeps up:** route announcements are batched instead of rebuilt for every run on every store change, so heavy fan-outs no longer saturate the workflow host and time out Intercom sends ([#3939](https://github.com/bastani-inc/atomic/pull/3939))
10
+ - **Stages that start under journal contention:** a busy workflow journal no longer fails pooled stages with `Storage unavailable`; writers wait for the lock without blocking their event loop, and the SQLite reason stays in the error ([#3940](https://github.com/bastani-inc/atomic/pull/3940))
11
+
12
+ ### Fixed
13
+
14
+ - A local SQLite store (`node-sqlite`, including the workflow journal) now waits for another process's write lock instead of failing after the 5 second busy timeout. When the lock is busy, it retries the start of the write transaction for up to 30 seconds without blocking the event loop. Single statements outside a transaction still use the configured busy timeout. Other failures, and a lock still held at that point, fail at once. `Storage unavailable` errors now include the SQLite reason, such as `database is locked (SQLite code 5)`, so the reason survives when local workers pass an error to another process
15
+
16
+ ## [0.9.44-alpha.4] - 2026-10-25
17
+
18
+ Workflow journals stay small even when many stages write to the same run at once.
19
+
20
+ - **No more full-list rewrites under contention:** a pending-message write that loses a race with another writer rebuilds its change and retries instead of storing the whole pending list. Those rewrites drove journal growth and workflow host load under heavy fan-out ([#3929](https://github.com/bastani-inc/atomic/issues/3929), [#3937](https://github.com/bastani-inc/atomic/pull/3937))
21
+
5
22
  ## [0.9.44-alpha.3] - 2026-10-25
6
23
 
7
24
  Chatty workflow swarms stay fast and their journals stay small, and Atomic picks up a round of dependency security fixes.
@@ -4,6 +4,12 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.44-alpha.5] - 2026-10-25
8
+
9
+ ### Fixed
10
+
11
+ - A pooled workflow stage whose Intercom extension delivers queued messages at startup no longer fails with `Extension startup failed: …intercom…: Storage unavailable: node-sqlite at <journal>` while other processes are writing heavily to the workflow journal. The journal write now waits for the lock, and any remaining failure states its SQLite cause
12
+
7
13
  ## [0.9.44-alpha.3] - 2026-10-25
8
14
 
9
15
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.44-alpha.3",
3
+ "version": "0.9.44-alpha.5",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.44-alpha.3",
3
+ "version": "0.9.44-alpha.5",
4
4
  "private": true,
5
5
  "description": "Atomic's pi-compatible MCP integration for server connections, OAuth, tools, resources, and terminal management.",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.44-alpha.3",
3
+ "version": "0.9.44-alpha.5",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.44-alpha.3",
3
+ "version": "0.9.44-alpha.5",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.44-alpha.5] - 2026-10-25
10
+
11
+ ### Fixed
12
+
13
+ - A large workflow fan-out no longer pins the workflow host's main thread rebuilding Intercom routes. Every store change re-announced the route of every run (480 children in a load test), so under a stream of messages and delivery receipts the host spent most of its time on announcements and Intercom sends began to time out. Store changes now publish routes in batches about 25 ms apart, spaced further when needed so announcing takes at most about a fifth of the host's time, and a stage that waits for its route still gets the latest one first.
14
+
15
+ ## [0.9.44-alpha.4] - 2026-10-25
16
+
17
+ ### Fixed
18
+
19
+ - A pending-message write that loses a race with another writer to the same run (for example a stage's delivery receipt) no longer stores the run's whole list of pending stage messages in the journal. It is rebuilt as a change on top of the other writer's entry and retried, and only falls back to the full list after repeated conflicts. In a 160-worker fan-out, 2% of queued events had fallen back and written 93% of their bytes, up to 1.5 MB each and growing with the list ([#3929](https://github.com/bastani-inc/atomic/issues/3929))
20
+
9
21
  ## [0.9.44-alpha.3] - 2026-10-25
10
22
 
11
23
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.44-alpha.3",
3
+ "version": "0.9.44-alpha.5",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -7097,16 +7097,20 @@ class JournalDurableBackend {
7097
7097
  }
7098
7098
  *append(runId, event, options) {
7099
7099
  const lease = yield* this.ownership(runId);
7100
- const base = this.sequences.get(runId);
7101
7100
  const pinned = options?.expectSeq;
7102
- const encoded = base !== undefined && (pinned === undefined || pinned === base) && event.type !== RUN_EVENT_TYPES.runStarted ? encodeHandleEvent(event, this.projection.getWorkflow(runId)?.pendingStageMessages) : undefined;
7103
- if (encoded !== undefined) {
7101
+ for (let attempt = 0;attempt < CHANGE_APPEND_ATTEMPTS; attempt++) {
7102
+ const base = this.sequences.get(runId);
7103
+ const encoded = base !== undefined && (pinned === undefined || pinned === base) && event.type !== RUN_EVENT_TYPES.runStarted ? encodeHandleEvent(event, this.projection.getWorkflow(runId)?.pendingStageMessages) : undefined;
7104
+ if (encoded === undefined)
7105
+ break;
7104
7106
  try {
7105
7107
  return yield* this.write(runId, lease, encoded, { ...options, expectSeq: base });
7106
7108
  } catch (error) {
7107
7109
  if (!(error instanceof JournalCasConflictError) || pinned !== undefined)
7108
7110
  throw error;
7109
7111
  }
7112
+ for (const entry of yield* this.entries(runId, (this.sequences.get(runId) ?? 0) + 1))
7113
+ this.fold(entry);
7110
7114
  }
7111
7115
  yield* this.write(runId, lease, event, options);
7112
7116
  }
@@ -7787,7 +7791,7 @@ function decodePendingStageDeliveries(previous, messages) {
7787
7791
  function hasPendingStageDeliveryDelta(messages) {
7788
7792
  return messages?.some(isDeltaEncoded) === true;
7789
7793
  }
7790
- var PROCESS_HOLDER_ID, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError, entryJson;
7794
+ var PROCESS_HOLDER_ID, CHANGE_APPEND_ATTEMPTS = 4, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError, entryJson;
7791
7795
  var init_journal_backend = __esm(() => {
7792
7796
  init_workflow_instance_ownership_error();
7793
7797
  init_backend();
@@ -60859,6 +60863,27 @@ class GraphFrontierTracker {
60859
60863
  }
60860
60864
  }
60861
60865
 
60866
+ // dist/builtin/workflows/src/engine/session-closed-error.ts
60867
+ import { isStaleExtensionContextError as isStaleExtensionContextError6 } from "@bastani/atomic";
60868
+ function reportsClosedSession(error) {
60869
+ return "code" in error && error.code === "SessionClosed" || isStaleExtensionContextError6(error);
60870
+ }
60871
+ function isSessionClosedError(error) {
60872
+ let current = error;
60873
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined; depth++) {
60874
+ try {
60875
+ if (reportsClosedSession(current))
60876
+ return true;
60877
+ current = current.cause instanceof Error ? current.cause : undefined;
60878
+ } catch {
60879
+ return false;
60880
+ }
60881
+ }
60882
+ return false;
60883
+ }
60884
+ var MAX_CAUSE_DEPTH = 8;
60885
+ var init_session_closed_error = () => {};
60886
+
60862
60887
  // dist/builtin/workflows/src/shared/external-run-routes.ts
60863
60888
  function claimExternalRunRoute(store, runId, owner) {
60864
60889
  let owners = registry.owners.get(store);
@@ -61135,7 +61160,10 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
61135
61160
  };
61136
61161
  const announcedRoutes = new Map;
61137
61162
  const routeCompletions = new Map;
61138
- const disposeReadiness = registerWorkflowPendingStageRouteReadiness(activeStore, (runId) => routeCompletions.get(runId));
61163
+ const disposeReadiness = registerWorkflowPendingStageRouteReadiness(activeStore, (runId) => {
61164
+ announceIfPending();
61165
+ return routeCompletions.get(runId);
61166
+ });
61139
61167
  const announceRoutes = () => {
61140
61168
  if (disposed)
61141
61169
  return;
@@ -61232,11 +61260,53 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
61232
61260
  return;
61233
61261
  }).catch((error) => reportWarning(`atomic-workflows: pending stage delivery sweep failed: ${error.message}`));
61234
61262
  };
61235
- const unsubscribeStore = activeStore.subscribeInvalidation(announceRoutes);
61236
- announceRoutes();
61263
+ let announcementPending = false;
61264
+ let announcementTimer;
61265
+ let announcedAt = Number.NEGATIVE_INFINITY;
61266
+ let announcementCost = 0;
61267
+ const announceNow = () => {
61268
+ if (announcementTimer !== undefined)
61269
+ clearTimeout(announcementTimer);
61270
+ announcementTimer = undefined;
61271
+ announcementPending = false;
61272
+ const started = performance.now();
61273
+ announceRoutes();
61274
+ announcedAt = performance.now();
61275
+ announcementCost = announcedAt - started;
61276
+ };
61277
+ function announceIfPending() {
61278
+ if (announcementPending)
61279
+ announceNow();
61280
+ }
61281
+ const announceBatch = () => {
61282
+ try {
61283
+ announceNow();
61284
+ } catch (error) {
61285
+ if (error instanceof Error && isSessionClosedError(error)) {
61286
+ dispose();
61287
+ return;
61288
+ }
61289
+ try {
61290
+ reportWarning(`atomic-workflows: pending stage route announcement failed: ${String(error)}`);
61291
+ } catch {
61292
+ dispose();
61293
+ }
61294
+ }
61295
+ };
61296
+ const scheduleAnnouncement = () => {
61297
+ if (disposed || announcementTimer !== undefined)
61298
+ return;
61299
+ const idle = announcementCost / ROUTE_ANNOUNCEMENT_EVENT_LOOP_SHARE - announcementCost;
61300
+ const wait = Math.max(ROUTE_ANNOUNCEMENT_BATCH_MS, announcedAt + idle - performance.now());
61301
+ announcementPending = true;
61302
+ announcementTimer = setTimeout(announceBatch, wait);
61303
+ announcementTimer.unref?.();
61304
+ };
61305
+ const unsubscribeStore = activeStore.subscribeInvalidation(scheduleAnnouncement);
61306
+ announceNow();
61237
61307
  pi.on?.("session_start", () => {
61238
61308
  announcedRoutes.clear();
61239
- announceRoutes();
61309
+ announceNow();
61240
61310
  });
61241
61311
  const routePendingStageMessage = (payload) => {
61242
61312
  if (disposed || !isPendingStageMessageEvent(payload) || payload.handled)
@@ -61318,6 +61388,10 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
61318
61388
  });
61319
61389
  const dispose = () => {
61320
61390
  disposed = true;
61391
+ if (announcementTimer !== undefined)
61392
+ clearTimeout(announcementTimer);
61393
+ announcementTimer = undefined;
61394
+ announcementPending = false;
61321
61395
  disposeReadiness();
61322
61396
  disposeRouter();
61323
61397
  routeCompletions.clear();
@@ -61331,10 +61405,11 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
61331
61405
  pi.on?.("session_shutdown", dispose);
61332
61406
  return Object.assign(dispose, {
61333
61407
  async flush() {
61408
+ announceIfPending();
61334
61409
  await Promise.allSettled(routeCompletions.values());
61335
61410
  await sweepPromise;
61336
61411
  },
61337
- refresh: announceRoutes
61412
+ refresh: announceNow
61338
61413
  });
61339
61414
  }
61340
61415
  function stageRouteTarget(runs, rootRunId, runId, stageId, resolveBoundarySegments = (id) => workflowBoundarySegments(runs, id)) {
@@ -61809,11 +61884,12 @@ async function settleUndeliverablePendingStageMessages(activeStore, notify, owns
61809
61884
  }
61810
61885
  return settled;
61811
61886
  }
61812
- var PENDING_STAGE_ROUTE_EVENT = "atomic:workflow-pending-stage-route", PENDING_STAGE_MESSAGE_EVENT = "atomic:workflow-pending-stage-message", PENDING_STAGE_UNDELIVERABLE_EVENT = "atomic:workflow-pending-stage-undeliverable", STICKY_LIVE_DELIVERED_EVENT = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_ASK_REFUSAL = "Cannot ask a workflow stage whose session has not initialized. Use send; Atomic will queue the message until the stage session initializes.";
61887
+ var PENDING_STAGE_ROUTE_EVENT = "atomic:workflow-pending-stage-route", PENDING_STAGE_MESSAGE_EVENT = "atomic:workflow-pending-stage-message", PENDING_STAGE_UNDELIVERABLE_EVENT = "atomic:workflow-pending-stage-undeliverable", STICKY_LIVE_DELIVERED_EVENT = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_ASK_REFUSAL = "Cannot ask a workflow stage whose session has not initialized. Use send; Atomic will queue the message until the stage session initializes.", ROUTE_ANNOUNCEMENT_BATCH_MS = 25, ROUTE_ANNOUNCEMENT_EVENT_LOOP_SHARE = 0.2;
61813
61888
  var init_pending_stage_intercom = __esm(() => {
61814
61889
  init_execution_context_boundary();
61815
61890
  init_run_owner_backend();
61816
61891
  init_store_backend();
61892
+ init_session_closed_error();
61817
61893
  init_external_run_routes();
61818
61894
  init_intercom_group();
61819
61895
  init_pending_stage_route_capability();
@@ -64370,7 +64446,7 @@ function isInfrastructureError(value2) {
64370
64446
  }
64371
64447
  function isRetryableWorkflowError(error) {
64372
64448
  let current = error;
64373
- for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined && current !== null; depth++) {
64449
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH2 && current !== undefined && current !== null; depth++) {
64374
64450
  if (isInfrastructureError(current))
64375
64451
  return true;
64376
64452
  current = current instanceof Error ? current.cause : undefined;
@@ -64388,7 +64464,7 @@ function asWorkflowInfrastructureError(error) {
64388
64464
  const code = workflowInfrastructureFault(error.message);
64389
64465
  return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
64390
64466
  }
64391
- var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
64467
+ var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH2 = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
64392
64468
  var init_workflow_infrastructure_error = __esm(() => {
64393
64469
  FAULT_PATTERNS = [
64394
64470
  [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
@@ -68434,7 +68510,7 @@ function describeErrorWithCauses(error) {
68434
68510
  const messages = [];
68435
68511
  const visited = new Set;
68436
68512
  let current = error;
68437
- for (let depth = 0;current !== undefined && depth < MAX_CAUSE_DEPTH2 && !visited.has(current); depth++) {
68513
+ for (let depth = 0;current !== undefined && depth < MAX_CAUSE_DEPTH3 && !visited.has(current); depth++) {
68438
68514
  visited.add(current);
68439
68515
  const message = current instanceof Error ? current.message : String(current);
68440
68516
  if (!messages.includes(message))
@@ -68443,7 +68519,7 @@ function describeErrorWithCauses(error) {
68443
68519
  }
68444
68520
  return messages.join(": ");
68445
68521
  }
68446
- var MAX_CAUSE_DEPTH2 = 5;
68522
+ var MAX_CAUSE_DEPTH3 = 5;
68447
68523
 
68448
68524
  // dist/builtin/workflows/src/engine/run-durable-finalize.ts
68449
68525
  async function finalizeDurableTerminalStatus(input) {
@@ -68963,7 +69039,7 @@ function recordedOwners(boundary, reason) {
68963
69039
  const pending = [{ value: reason, depth: 0 }];
68964
69040
  while (pending.length > 0) {
68965
69041
  const { value: value2, depth } = pending.pop();
68966
- if (depth > MAX_CAUSE_DEPTH3)
69042
+ if (depth > MAX_CAUSE_DEPTH4)
68967
69043
  continue;
68968
69044
  if (isPrimitive(value2)) {
68969
69045
  if (depth === 0 || value2 !== undefined && value2 !== null) {
@@ -69285,7 +69361,7 @@ function containUiRejections(ui, scope) {
69285
69361
  custom: (factory, options) => scope.watch(ui.custom(factory, options))
69286
69362
  };
69287
69363
  }
69288
- 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;
69364
+ var EVENT = "unhandledRejection", BOUNDARY_KEY, FLAG = "--unhandled-rejections", MAX_CAUSE_DEPTH4 = 8, MAX_PRIMITIVES_PER_RUN = 64, MAX_STOP_STRINGS = 64, ENDED_RUN_GRACE_MS = 30000, NATIVE_REPORTING_MODES, WATCHED_STAGE_METHODS;
69289
69365
  var init_run_rejection_boundary = __esm(() => {
69290
69366
  init_prompt_callsite();
69291
69367
  init_workflow_source();
@@ -75131,27 +75207,6 @@ var init_runtime = __esm(() => {
75131
75207
  init_intercom_group();
75132
75208
  });
75133
75209
 
75134
- // dist/builtin/workflows/src/engine/session-closed-error.ts
75135
- import { isStaleExtensionContextError as isStaleExtensionContextError6 } from "@bastani/atomic";
75136
- function reportsClosedSession(error) {
75137
- return "code" in error && error.code === "SessionClosed" || isStaleExtensionContextError6(error);
75138
- }
75139
- function isSessionClosedError(error) {
75140
- let current = error;
75141
- for (let depth = 0;depth < MAX_CAUSE_DEPTH4 && current !== undefined; depth++) {
75142
- try {
75143
- if (reportsClosedSession(current))
75144
- return true;
75145
- current = current.cause instanceof Error ? current.cause : undefined;
75146
- } catch {
75147
- return false;
75148
- }
75149
- }
75150
- return false;
75151
- }
75152
- var MAX_CAUSE_DEPTH4 = 8;
75153
- var init_session_closed_error = () => {};
75154
-
75155
75210
  // dist/builtin/workflows/src/engine/workflow-activity.ts
75156
75211
  import { runCallback as runCallback4 } from "@bastani/atomic";
75157
75212
  function nextEventLoopTurn() {
@@ -1908,16 +1908,20 @@ class JournalDurableBackend {
1908
1908
  }
1909
1909
  *append(runId, event, options) {
1910
1910
  const lease = yield* this.ownership(runId);
1911
- const base = this.sequences.get(runId);
1912
1911
  const pinned = options?.expectSeq;
1913
- const encoded = base !== undefined && (pinned === undefined || pinned === base) && event.type !== RUN_EVENT_TYPES.runStarted ? encodeHandleEvent(event, this.projection.getWorkflow(runId)?.pendingStageMessages) : undefined;
1914
- if (encoded !== undefined) {
1912
+ for (let attempt = 0;attempt < CHANGE_APPEND_ATTEMPTS; attempt++) {
1913
+ const base = this.sequences.get(runId);
1914
+ const encoded = base !== undefined && (pinned === undefined || pinned === base) && event.type !== RUN_EVENT_TYPES.runStarted ? encodeHandleEvent(event, this.projection.getWorkflow(runId)?.pendingStageMessages) : undefined;
1915
+ if (encoded === undefined)
1916
+ break;
1915
1917
  try {
1916
1918
  return yield* this.write(runId, lease, encoded, { ...options, expectSeq: base });
1917
1919
  } catch (error) {
1918
1920
  if (!(error instanceof JournalCasConflictError) || pinned !== undefined)
1919
1921
  throw error;
1920
1922
  }
1923
+ for (const entry of yield* this.entries(runId, (this.sequences.get(runId) ?? 0) + 1))
1924
+ this.fold(entry);
1921
1925
  }
1922
1926
  yield* this.write(runId, lease, event, options);
1923
1927
  }
@@ -2598,7 +2602,7 @@ function decodePendingStageDeliveries(previous, messages) {
2598
2602
  function hasPendingStageDeliveryDelta(messages) {
2599
2603
  return messages?.some(isDeltaEncoded) === true;
2600
2604
  }
2601
- var PROCESS_HOLDER_ID, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError, entryJson;
2605
+ var PROCESS_HOLDER_ID, CHANGE_APPEND_ATTEMPTS = 4, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError, entryJson;
2602
2606
  var init_journal_backend = __esm(() => {
2603
2607
  init_workflow_instance_ownership_error();
2604
2608
  init_backend();
@@ -57397,6 +57401,27 @@ var init_executor = __esm(() => {
57397
57401
  init_executor_run();
57398
57402
  });
57399
57403
 
57404
+ // dist/builtin/workflows/src/engine/session-closed-error.ts
57405
+ import { isStaleExtensionContextError as isStaleExtensionContextError2 } from "@bastani/atomic";
57406
+ function reportsClosedSession(error) {
57407
+ return "code" in error && error.code === "SessionClosed" || isStaleExtensionContextError2(error);
57408
+ }
57409
+ function isSessionClosedError(error) {
57410
+ let current = error;
57411
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined; depth++) {
57412
+ try {
57413
+ if (reportsClosedSession(current))
57414
+ return true;
57415
+ current = current.cause instanceof Error ? current.cause : undefined;
57416
+ } catch {
57417
+ return false;
57418
+ }
57419
+ }
57420
+ return false;
57421
+ }
57422
+ var MAX_CAUSE_DEPTH = 8;
57423
+ var init_session_closed_error = () => {};
57424
+
57400
57425
  // dist/builtin/workflows/src/shared/external-run-routes.ts
57401
57426
  function claimExternalRunRoute(store, runId, owner) {
57402
57427
  let owners = registry.owners.get(store);
@@ -57891,7 +57916,10 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
57891
57916
  };
57892
57917
  const announcedRoutes = new Map;
57893
57918
  const routeCompletions = new Map;
57894
- const disposeReadiness = registerWorkflowPendingStageRouteReadiness(activeStore, (runId) => routeCompletions.get(runId));
57919
+ const disposeReadiness = registerWorkflowPendingStageRouteReadiness(activeStore, (runId) => {
57920
+ announceIfPending();
57921
+ return routeCompletions.get(runId);
57922
+ });
57895
57923
  const announceRoutes = () => {
57896
57924
  if (disposed)
57897
57925
  return;
@@ -57988,11 +58016,53 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
57988
58016
  return;
57989
58017
  }).catch((error) => reportWarning(`atomic-workflows: pending stage delivery sweep failed: ${error.message}`));
57990
58018
  };
57991
- const unsubscribeStore = activeStore.subscribeInvalidation(announceRoutes);
57992
- announceRoutes();
58019
+ let announcementPending = false;
58020
+ let announcementTimer;
58021
+ let announcedAt = Number.NEGATIVE_INFINITY;
58022
+ let announcementCost = 0;
58023
+ const announceNow = () => {
58024
+ if (announcementTimer !== undefined)
58025
+ clearTimeout(announcementTimer);
58026
+ announcementTimer = undefined;
58027
+ announcementPending = false;
58028
+ const started = performance.now();
58029
+ announceRoutes();
58030
+ announcedAt = performance.now();
58031
+ announcementCost = announcedAt - started;
58032
+ };
58033
+ function announceIfPending() {
58034
+ if (announcementPending)
58035
+ announceNow();
58036
+ }
58037
+ const announceBatch = () => {
58038
+ try {
58039
+ announceNow();
58040
+ } catch (error) {
58041
+ if (error instanceof Error && isSessionClosedError(error)) {
58042
+ dispose();
58043
+ return;
58044
+ }
58045
+ try {
58046
+ reportWarning(`atomic-workflows: pending stage route announcement failed: ${String(error)}`);
58047
+ } catch {
58048
+ dispose();
58049
+ }
58050
+ }
58051
+ };
58052
+ const scheduleAnnouncement = () => {
58053
+ if (disposed || announcementTimer !== undefined)
58054
+ return;
58055
+ const idle = announcementCost / ROUTE_ANNOUNCEMENT_EVENT_LOOP_SHARE - announcementCost;
58056
+ const wait = Math.max(ROUTE_ANNOUNCEMENT_BATCH_MS, announcedAt + idle - performance.now());
58057
+ announcementPending = true;
58058
+ announcementTimer = setTimeout(announceBatch, wait);
58059
+ announcementTimer.unref?.();
58060
+ };
58061
+ const unsubscribeStore = activeStore.subscribeInvalidation(scheduleAnnouncement);
58062
+ announceNow();
57993
58063
  pi.on?.("session_start", () => {
57994
58064
  announcedRoutes.clear();
57995
- announceRoutes();
58065
+ announceNow();
57996
58066
  });
57997
58067
  const routePendingStageMessage = (payload) => {
57998
58068
  if (disposed || !isPendingStageMessageEvent(payload) || payload.handled)
@@ -58074,6 +58144,10 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
58074
58144
  });
58075
58145
  const dispose = () => {
58076
58146
  disposed = true;
58147
+ if (announcementTimer !== undefined)
58148
+ clearTimeout(announcementTimer);
58149
+ announcementTimer = undefined;
58150
+ announcementPending = false;
58077
58151
  disposeReadiness();
58078
58152
  disposeRouter();
58079
58153
  routeCompletions.clear();
@@ -58087,10 +58161,11 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
58087
58161
  pi.on?.("session_shutdown", dispose);
58088
58162
  return Object.assign(dispose, {
58089
58163
  async flush() {
58164
+ announceIfPending();
58090
58165
  await Promise.allSettled(routeCompletions.values());
58091
58166
  await sweepPromise;
58092
58167
  },
58093
- refresh: announceRoutes
58168
+ refresh: announceNow
58094
58169
  });
58095
58170
  }
58096
58171
  function stageRouteTarget(runs, rootRunId, runId, stageId, resolveBoundarySegments = (id) => workflowBoundarySegments(runs, id)) {
@@ -58565,11 +58640,12 @@ async function settleUndeliverablePendingStageMessages(activeStore, notify, owns
58565
58640
  }
58566
58641
  return settled;
58567
58642
  }
58568
- var PENDING_STAGE_ROUTE_EVENT = "atomic:workflow-pending-stage-route", PENDING_STAGE_MESSAGE_EVENT = "atomic:workflow-pending-stage-message", PENDING_STAGE_UNDELIVERABLE_EVENT = "atomic:workflow-pending-stage-undeliverable", STICKY_LIVE_DELIVERED_EVENT = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_ASK_REFUSAL = "Cannot ask a workflow stage whose session has not initialized. Use send; Atomic will queue the message until the stage session initializes.";
58643
+ var PENDING_STAGE_ROUTE_EVENT = "atomic:workflow-pending-stage-route", PENDING_STAGE_MESSAGE_EVENT = "atomic:workflow-pending-stage-message", PENDING_STAGE_UNDELIVERABLE_EVENT = "atomic:workflow-pending-stage-undeliverable", STICKY_LIVE_DELIVERED_EVENT = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_ASK_REFUSAL = "Cannot ask a workflow stage whose session has not initialized. Use send; Atomic will queue the message until the stage session initializes.", ROUTE_ANNOUNCEMENT_BATCH_MS = 25, ROUTE_ANNOUNCEMENT_EVENT_LOOP_SHARE = 0.2;
58569
58644
  var init_pending_stage_intercom = __esm(() => {
58570
58645
  init_execution_context_boundary();
58571
58646
  init_run_owner_backend();
58572
58647
  init_store_backend();
58648
+ init_session_closed_error();
58573
58649
  init_external_run_routes();
58574
58650
  init_intercom_group();
58575
58651
  init_pending_stage_route_capability();
@@ -61126,7 +61202,7 @@ function isInfrastructureError(value2) {
61126
61202
  }
61127
61203
  function isRetryableWorkflowError(error) {
61128
61204
  let current = error;
61129
- for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined && current !== null; depth++) {
61205
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH2 && current !== undefined && current !== null; depth++) {
61130
61206
  if (isInfrastructureError(current))
61131
61207
  return true;
61132
61208
  current = current instanceof Error ? current.cause : undefined;
@@ -61144,7 +61220,7 @@ function asWorkflowInfrastructureError(error) {
61144
61220
  const code = workflowInfrastructureFault(error.message);
61145
61221
  return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
61146
61222
  }
61147
- var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
61223
+ var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH2 = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
61148
61224
  var init_workflow_infrastructure_error = __esm(() => {
61149
61225
  FAULT_PATTERNS = [
61150
61226
  [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
@@ -65165,7 +65241,7 @@ function describeErrorWithCauses(error) {
65165
65241
  const messages = [];
65166
65242
  const visited = new Set;
65167
65243
  let current = error;
65168
- for (let depth = 0;current !== undefined && depth < MAX_CAUSE_DEPTH2 && !visited.has(current); depth++) {
65244
+ for (let depth = 0;current !== undefined && depth < MAX_CAUSE_DEPTH3 && !visited.has(current); depth++) {
65169
65245
  visited.add(current);
65170
65246
  const message = current instanceof Error ? current.message : String(current);
65171
65247
  if (!messages.includes(message))
@@ -65174,7 +65250,7 @@ function describeErrorWithCauses(error) {
65174
65250
  }
65175
65251
  return messages.join(": ");
65176
65252
  }
65177
- var MAX_CAUSE_DEPTH2 = 5;
65253
+ var MAX_CAUSE_DEPTH3 = 5;
65178
65254
 
65179
65255
  // dist/builtin/workflows/src/shared/returned-run-status.ts
65180
65256
  function normalizeReturnedWorkflowStatus(status) {
@@ -66699,7 +66775,7 @@ function recordedOwners(boundary, reason) {
66699
66775
  const pending = [{ value: reason, depth: 0 }];
66700
66776
  while (pending.length > 0) {
66701
66777
  const { value: value2, depth } = pending.pop();
66702
- if (depth > MAX_CAUSE_DEPTH3)
66778
+ if (depth > MAX_CAUSE_DEPTH4)
66703
66779
  continue;
66704
66780
  if (isPrimitive(value2)) {
66705
66781
  if (depth === 0 || value2 !== undefined && value2 !== null) {
@@ -67021,7 +67097,7 @@ function containUiRejections(ui, scope) {
67021
67097
  custom: (factory, options) => scope.watch(ui.custom(factory, options))
67022
67098
  };
67023
67099
  }
67024
- 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;
67100
+ var EVENT = "unhandledRejection", BOUNDARY_KEY, FLAG = "--unhandled-rejections", MAX_CAUSE_DEPTH4 = 8, MAX_PRIMITIVES_PER_RUN = 64, MAX_STOP_STRINGS = 64, ENDED_RUN_GRACE_MS = 30000, NATIVE_REPORTING_MODES, WATCHED_STAGE_METHODS;
67025
67101
  var init_run_rejection_boundary = __esm(() => {
67026
67102
  init_prompt_callsite();
67027
67103
  init_workflow_source();
@@ -72912,27 +72988,6 @@ var init_runtime = __esm(() => {
72912
72988
  init_intercom_group();
72913
72989
  });
72914
72990
 
72915
- // dist/builtin/workflows/src/engine/session-closed-error.ts
72916
- import { isStaleExtensionContextError as isStaleExtensionContextError2 } from "@bastani/atomic";
72917
- function reportsClosedSession(error) {
72918
- return "code" in error && error.code === "SessionClosed" || isStaleExtensionContextError2(error);
72919
- }
72920
- function isSessionClosedError(error) {
72921
- let current = error;
72922
- for (let depth = 0;depth < MAX_CAUSE_DEPTH4 && current !== undefined; depth++) {
72923
- try {
72924
- if (reportsClosedSession(current))
72925
- return true;
72926
- current = current.cause instanceof Error ? current.cause : undefined;
72927
- } catch {
72928
- return false;
72929
- }
72930
- }
72931
- return false;
72932
- }
72933
- var MAX_CAUSE_DEPTH4 = 8;
72934
- var init_session_closed_error = () => {};
72935
-
72936
72991
  // dist/builtin/workflows/src/engine/workflow-activity.ts
72937
72992
  import { runCallback as runCallback4 } from "@bastani/atomic";
72938
72993
  function nextEventLoopTurn() {