@bastani/atomic 0.9.57 → 0.9.58-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.
Files changed (117) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/CHANGELOG.md +12 -0
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/subagents/src/extension/index.bundle.mjs +39 -5
  7. package/dist/builtin/web-access/CHANGELOG.md +12 -0
  8. package/dist/builtin/web-access/index.bundle.mjs +2 -2
  9. package/dist/builtin/web-access/package.json +1 -1
  10. package/dist/builtin/workflows/CHANGELOG.md +42 -0
  11. package/dist/builtin/workflows/package.json +1 -1
  12. package/dist/builtin/workflows/src/extension/index.bundle.mjs +885 -228
  13. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +933 -175
  14. package/dist/builtin/workflows/src/index.js +630 -162
  15. package/dist/builtin/workflows/src/local-worker-entry.mjs +933 -175
  16. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -1
  17. package/dist/builtin/workflows/src/shared/workflow-infrastructure-error.d.ts +13 -0
  18. package/dist/builtin/workflows/src/workers/public-options.d.ts +10 -0
  19. package/dist/core/agent-session-persistent-custom-messages.d.ts +5 -0
  20. package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
  21. package/dist/core/agent-session-persistent-custom-messages.js +16 -0
  22. package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
  23. package/dist/core/agent-session-tasks.d.ts.map +1 -1
  24. package/dist/core/agent-session-tasks.js +20 -0
  25. package/dist/core/agent-session-tasks.js.map +1 -1
  26. package/dist/core/settings-defaults.d.ts +4 -0
  27. package/dist/core/settings-defaults.d.ts.map +1 -1
  28. package/dist/core/settings-defaults.js +4 -0
  29. package/dist/core/settings-defaults.js.map +1 -1
  30. package/dist/core/settings-manager-resource-accessors.d.ts +2 -0
  31. package/dist/core/settings-manager-resource-accessors.d.ts.map +1 -1
  32. package/dist/core/settings-manager-resource-accessors.js +6 -0
  33. package/dist/core/settings-manager-resource-accessors.js.map +1 -1
  34. package/dist/core/settings-schema.d.ts +9 -0
  35. package/dist/core/settings-schema.d.ts.map +1 -1
  36. package/dist/core/settings-schema.js +11 -0
  37. package/dist/core/settings-schema.js.map +1 -1
  38. package/dist/core/settings-types.d.ts +1 -1
  39. package/dist/core/settings-types.d.ts.map +1 -1
  40. package/dist/core/settings-types.js.map +1 -1
  41. package/dist/core/tasks/agent-adapter.d.ts +10 -1
  42. package/dist/core/tasks/agent-adapter.d.ts.map +1 -1
  43. package/dist/core/tasks/agent-adapter.js +20 -0
  44. package/dist/core/tasks/agent-adapter.js.map +1 -1
  45. package/dist/core/tasks/contracts.d.ts +6 -0
  46. package/dist/core/tasks/contracts.d.ts.map +1 -1
  47. package/dist/core/tasks/contracts.js.map +1 -1
  48. package/dist/core/tasks/heartbeat-notice.d.ts +64 -0
  49. package/dist/core/tasks/heartbeat-notice.d.ts.map +1 -0
  50. package/dist/core/tasks/heartbeat-notice.js +98 -0
  51. package/dist/core/tasks/heartbeat-notice.js.map +1 -0
  52. package/dist/core/tasks/heartbeat.d.ts +75 -0
  53. package/dist/core/tasks/heartbeat.d.ts.map +1 -0
  54. package/dist/core/tasks/heartbeat.js +440 -0
  55. package/dist/core/tasks/heartbeat.js.map +1 -0
  56. package/dist/core/tasks/supervisor.d.ts +2 -0
  57. package/dist/core/tasks/supervisor.d.ts.map +1 -1
  58. package/dist/core/tasks/supervisor.js +32 -4
  59. package/dist/core/tasks/supervisor.js.map +1 -1
  60. package/dist/core/tasks/task-view-source.d.ts +72 -0
  61. package/dist/core/tasks/task-view-source.d.ts.map +1 -0
  62. package/dist/core/tasks/task-view-source.js +155 -0
  63. package/dist/core/tasks/task-view-source.js.map +1 -0
  64. package/dist/core/tools/bash-pty-native.d.ts +2 -0
  65. package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
  66. package/dist/core/tools/bash-pty-native.js +1 -0
  67. package/dist/core/tools/bash-pty-native.js.map +1 -1
  68. package/dist/core/tools/bash.d.ts +3 -0
  69. package/dist/core/tools/bash.d.ts.map +1 -1
  70. package/dist/core/tools/bash.js +20 -2
  71. package/dist/core/tools/bash.js.map +1 -1
  72. package/dist/core/tools/powershell.d.ts +16 -8
  73. package/dist/core/tools/powershell.d.ts.map +1 -1
  74. package/dist/index.d.ts +5 -3
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +4 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  79. package/dist/modes/interactive/components/chat-session-host-events.js +7 -0
  80. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  81. package/dist/modes/interactive/components/chat-session-host-state.d.ts +3 -0
  82. package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/chat-session-host-state.js +4 -0
  84. package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
  85. package/dist/modes/interactive/components/chat-session-host-types.d.ts +11 -0
  86. package/dist/modes/interactive/components/chat-session-host-types.d.ts.map +1 -1
  87. package/dist/modes/interactive/components/chat-session-host-types.js.map +1 -1
  88. package/dist/modes/interactive/components/chat-session-host.d.ts +2 -0
  89. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/chat-session-host.js +15 -8
  91. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  92. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  93. package/dist/modes/interactive/components/custom-message.js +12 -0
  94. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  95. package/dist/modes/interactive/components/task-detail.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/task-detail.js +5 -0
  97. package/dist/modes/interactive/components/task-detail.js.map +1 -1
  98. package/dist/modes/interactive/components/task-heartbeat-message.d.ts +14 -0
  99. package/dist/modes/interactive/components/task-heartbeat-message.d.ts.map +1 -0
  100. package/dist/modes/interactive/components/task-heartbeat-message.js +76 -0
  101. package/dist/modes/interactive/components/task-heartbeat-message.js.map +1 -0
  102. package/dist/modes/interactive/components/task-inspector.d.ts +6 -3
  103. package/dist/modes/interactive/components/task-inspector.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/task-inspector.js +53 -100
  105. package/dist/modes/interactive/components/task-inspector.js.map +1 -1
  106. package/dist/modes/interactive/skill-command-autocomplete.d.ts +22 -2
  107. package/dist/modes/interactive/skill-command-autocomplete.d.ts.map +1 -1
  108. package/dist/modes/interactive/skill-command-autocomplete.js +28 -9
  109. package/dist/modes/interactive/skill-command-autocomplete.js.map +1 -1
  110. package/docs/background-tasks.md +65 -1
  111. package/docs/settings.md +12 -0
  112. package/docs/subagents.md +2 -0
  113. package/docs/tools.md +2 -1
  114. package/docs/workflows/operations.md +5 -3
  115. package/npm-shrinkwrap.json +32 -32
  116. package/package.json +3 -3
  117. package/schemas/settings.schema.json +24 -0
@@ -2413,6 +2413,75 @@ function redactSensitiveText(value2) {
2413
2413
  return value2.replace(/(sk-[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+/g, redactedSecretReplacement("$1")).replace(/\b(authorization\s*:\s*bearer\s+)[^\s,;]+/gi, "$1[redacted]").replace(/\b(bearer\s+)[A-Za-z0-9._~+/-]{8,}=*/gi, "$1[redacted]").replace(/((?:api[_-]?key|token|credential|secret)\s*[:=]\s*)[^\s,;]+/gi, "$1[redacted]");
2414
2414
  }
2415
2415
 
2416
+ // dist/builtin/workflows/src/shared/workflow-infrastructure-error.ts
2417
+ function isInfrastructureError(value2) {
2418
+ if (value2 instanceof WorkflowInfrastructureError)
2419
+ return true;
2420
+ if (typeof value2 !== "object" || value2 === null)
2421
+ return false;
2422
+ const candidate = value2;
2423
+ return candidate.name === WORKFLOW_INFRASTRUCTURE_ERROR_NAME && candidate.retryable === true && typeof candidate.code === "string";
2424
+ }
2425
+ function isRetryableWorkflowError(error) {
2426
+ let current = error;
2427
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined && current !== null; depth++) {
2428
+ if (isInfrastructureError(current))
2429
+ return true;
2430
+ current = current instanceof Error ? current.cause : undefined;
2431
+ }
2432
+ return false;
2433
+ }
2434
+ function workflowInfrastructureFault(message) {
2435
+ return FAULT_PATTERNS.find(([pattern]) => pattern.test(message))?.[1];
2436
+ }
2437
+ function asWorkflowInfrastructureError(error) {
2438
+ if (isInfrastructureError(error))
2439
+ return error;
2440
+ if (!(error instanceof Error))
2441
+ return;
2442
+ const code = workflowInfrastructureFault(error.message);
2443
+ return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
2444
+ }
2445
+ function failedOnInfrastructureFault(result) {
2446
+ if (result.status !== "failed")
2447
+ return false;
2448
+ const names = (value2) => typeof value2 === "string" && (WORKFLOW_INFRASTRUCTURE_FAULTS.has(value2) || workflowInfrastructureFault(value2) !== undefined);
2449
+ const failure = result.failure;
2450
+ if (names(failure?.code) || names(result.error))
2451
+ return true;
2452
+ return Array.isArray(result.stages) && result.stages.some((stage) => stage?.status === "failed" && (names(stage.failureCode) || names(stage.error)));
2453
+ }
2454
+ var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", WORKFLOW_INFRASTRUCTURE_FAULTS, MAX_CAUSE_DEPTH = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
2455
+ var init_workflow_infrastructure_error = __esm(() => {
2456
+ WORKFLOW_INFRASTRUCTURE_FAULTS = new Set([
2457
+ "worker_lost",
2458
+ "worker_cleanup_unconfirmed",
2459
+ "worker_retry_limit",
2460
+ "worker_startup_failed",
2461
+ "stale_work_lease",
2462
+ "intercom_unavailable",
2463
+ "storage_unavailable"
2464
+ ]);
2465
+ FAULT_PATTERNS = [
2466
+ [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
2467
+ [/Local worker retry limit/, "worker_retry_limit"],
2468
+ [/Local worker (?:startup timed out|exited before readiness)/, "worker_startup_failed"],
2469
+ [/Worker exited before delivery acknowledgement|Local worker pool coordinator exited/, "worker_lost"],
2470
+ [/Stale work lease|Stale resource admission attempt/, "stale_work_lease"],
2471
+ [/Intercom runtime no longer active/, "intercom_unavailable"],
2472
+ [/Storage unavailable/, "storage_unavailable"]
2473
+ ];
2474
+ WorkflowInfrastructureError = class WorkflowInfrastructureError extends Error {
2475
+ name = WORKFLOW_INFRASTRUCTURE_ERROR_NAME;
2476
+ retryable = true;
2477
+ code;
2478
+ constructor(code, message, options) {
2479
+ super(message, options);
2480
+ this.code = code;
2481
+ }
2482
+ };
2483
+ });
2484
+
2416
2485
  // dist/builtin/workflows/src/shared/workflow-failures-decisions.ts
2417
2486
  function authDecision(code) {
2418
2487
  if (code === "invalid_api_key") {
@@ -2501,6 +2570,16 @@ function cancelledDecision() {
2501
2570
  disposition: "terminal_killed"
2502
2571
  };
2503
2572
  }
2573
+ function infrastructureFaultDecision(code) {
2574
+ return {
2575
+ kind: "unknown",
2576
+ code,
2577
+ retryable: true,
2578
+ resumable: true,
2579
+ recoverability: "recoverable",
2580
+ disposition: "terminal_failed"
2581
+ };
2582
+ }
2504
2583
  function unknownDecision() {
2505
2584
  return {
2506
2585
  kind: "unknown",
@@ -2528,6 +2607,8 @@ function strongDecisionFromNormalizedCode(normalized, retryAfterMs) {
2528
2607
  disposition: "active_blocked"
2529
2608
  };
2530
2609
  }
2610
+ if (WORKFLOW_INFRASTRUCTURE_FAULTS.has(normalized))
2611
+ return infrastructureFaultDecision(normalized);
2531
2612
  if (CANCELLED_CODES.has(normalized))
2532
2613
  return cancelledDecision();
2533
2614
  if (INVALID_API_KEY_CODES.has(normalized))
@@ -2640,6 +2721,9 @@ function classificationFromNormalizedCode(normalized, retryAfterMs, source, mess
2640
2721
  return weak !== undefined ? { weak: classificationForDecision(weak, source, message, "weak_signal") } : {};
2641
2722
  }
2642
2723
  function fallbackDecisionFromMessage(message, name) {
2724
+ const fault = workflowInfrastructureFault(message);
2725
+ if (fault !== undefined)
2726
+ return infrastructureFaultDecision(fault);
2643
2727
  const tokens = tokenize(message);
2644
2728
  const decision = decisionFromMessageTokens(tokens, name);
2645
2729
  const providerAuthDecision = classifyFallbackProviderAuthMessage(message, tokens);
@@ -2654,6 +2738,7 @@ function fallbackDecisionFromMessage(message, name) {
2654
2738
  var INVALID_API_KEY_CODES, LOGIN_REQUIRED_CODES, MISSING_API_KEY_CODES, RATE_LIMIT_CODES, QUOTA_LIMIT_CODES, CANCELLED_CODES, PROVIDER_UNAVAILABLE_CODES, UNKNOWN_MODEL_CODES, FORBIDDEN_CONFIG_CODES, REQUEST_AUTH_TIMEOUT_PHRASES, LOGIN_REQUIRED_PHRASES, LOCAL_LOGIN_REQUIRED_PHRASES, PROVIDER_AUTH_FALLBACK_PHRASES, MISSING_API_KEY_PHRASES, INVALID_API_KEY_PHRASES, INVALID_API_KEY_CONTEXT, HTTP_RATE_LIMIT_PHRASES, RATE_LIMIT_PHRASES, QUOTA_LIMIT_PHRASES, CANCELLED_PHRASES, UNKNOWN_MODEL_PHRASES, FORBIDDEN_CONFIG_PHRASES, PROVIDER_UNAVAILABLE_PHRASES, AUTH_CONTEXT, MODEL_PROVIDER_CONTEXT, PROVIDER_CONTEXT, STATUS_MESSAGE_REFINEMENT_CODES, BROAD_AUTH_MESSAGE_REFINEMENT_CODES, STATUS_RELATED_MESSAGE_REFINEMENT_CODES;
2655
2739
  var init_workflow_failures_decisions = __esm(() => {
2656
2740
  init_workflow_failures_contract();
2741
+ init_workflow_infrastructure_error();
2657
2742
  INVALID_API_KEY_CODES = new Set([
2658
2743
  "401",
2659
2744
  "invalid_api_key",
@@ -2841,6 +2926,13 @@ function decisionOfRecordedCode(code, retryAfterMs) {
2841
2926
  case "cancelled":
2842
2927
  return cancelledDecision();
2843
2928
  case "pending_stage_delivery_failed":
2929
+ case "worker_lost":
2930
+ case "worker_cleanup_unconfirmed":
2931
+ case "worker_retry_limit":
2932
+ case "worker_startup_failed":
2933
+ case "stale_work_lease":
2934
+ case "intercom_unavailable":
2935
+ case "storage_unavailable":
2844
2936
  return strongDecisionFromNormalizedCode(code, retryAfterMs);
2845
2937
  case "unknown":
2846
2938
  return;
@@ -11809,7 +11901,7 @@ class JournalDurableBackend {
11809
11901
  timer;
11810
11902
  state = "open";
11811
11903
  closing;
11812
- renewing;
11904
+ renewing = new Map;
11813
11905
  mutationContext = new AsyncLocalStorage3;
11814
11906
  processExitCleanup;
11815
11907
  unregisterExecutionStorages = registerExecutionStorages(this.executionContext, this.mutationContext);
@@ -12530,27 +12622,21 @@ class JournalDurableBackend {
12530
12622
  yield this.enqueue(runId, () => this.deleteOwned(runId));
12531
12623
  }
12532
12624
  renewLeases() {
12533
- if (this.renewing !== undefined)
12534
- return this.renewing;
12535
12625
  if (this.state !== "open")
12536
12626
  return Promise.resolve();
12537
- this.renewing = this.renewOwnedLeases().finally(() => {
12538
- this.renewing = undefined;
12539
- });
12540
- return this.renewing;
12541
- }
12542
- async renewOwnedLeases() {
12543
12627
  for (const [runId, lease] of this.leases) {
12544
- if (this.state !== "open")
12545
- return;
12546
- try {
12547
- await this.serialize(runId, () => this.renewLease(runId, lease), false);
12548
- } catch (error) {
12549
- if (error instanceof Error)
12550
- this.lost.set(runId, error);
12628
+ if (this.renewing.has(runId))
12629
+ continue;
12630
+ const renewal = this.serialize(runId, () => this.renewLease(runId, lease), false).catch((error) => {
12631
+ this.lost.set(runId, error);
12551
12632
  this.dropLease(runId);
12552
- }
12633
+ }).finally(() => {
12634
+ if (this.renewing.get(runId) === renewal)
12635
+ this.renewing.delete(runId);
12636
+ });
12637
+ this.renewing.set(runId, renewal);
12553
12638
  }
12639
+ return Promise.all(this.renewing.values()).then(() => {});
12554
12640
  }
12555
12641
  *renewLease(runId, lease) {
12556
12642
  if (this.leases.get(runId) !== lease || this.lost.has(runId))
@@ -12558,9 +12644,10 @@ class JournalDurableBackend {
12558
12644
  try {
12559
12645
  yield* invoke(() => this.journal.renew(lease));
12560
12646
  } catch (error) {
12647
+ if (!(error instanceof StaleLeaseError))
12648
+ return;
12561
12649
  if (this.leases.get(runId) === lease) {
12562
- if (error instanceof Error)
12563
- this.lost.set(runId, yield* this.mapOwnership(error, runId));
12650
+ this.lost.set(runId, yield* this.mapOwnership(error, runId));
12564
12651
  this.dropLease(runId);
12565
12652
  }
12566
12653
  }
@@ -12654,7 +12741,7 @@ class JournalDurableBackend {
12654
12741
  }
12655
12742
  async drainAndClose() {
12656
12743
  try {
12657
- await this.renewing;
12744
+ await Promise.all(this.renewing.values());
12658
12745
  while (this.pending.size > 0)
12659
12746
  await Promise.all(this.pending.values());
12660
12747
  for (const runId of this.leases.keys())
@@ -17029,7 +17116,7 @@ class SqlResourceAdmission {
17029
17116
  for (const value2 of Object.values(limits ?? {}))
17030
17117
  if (!Number.isSafeInteger(value2) || value2 < 1)
17031
17118
  throw new Error("Invalid resource limit");
17032
- for (const value2 of Object.values(options.host ?? {}))
17119
+ for (const value2 of [...Object.values(options.host ?? {}), options.stageStartMaxLoadPerCpu ?? 0])
17033
17120
  if (!Number.isFinite(value2) || value2 < 0)
17034
17121
  throw new Error("Invalid host admission threshold");
17035
17122
  const admission = new SqlResourceAdmission(storage, {
@@ -17047,6 +17134,8 @@ class SqlResourceAdmission {
17047
17134
  for (const column of ["owner", "attempt", "process_identity"])
17048
17135
  if (!columns.some((entry) => entry.name === column))
17049
17136
  await tx.exec(`ALTER TABLE ${admission.permits} ADD COLUMN ${column} TEXT`);
17137
+ if (!columns.some((entry) => entry.name === "granted_at"))
17138
+ await tx.exec(`ALTER TABLE ${admission.permits} ADD COLUMN granted_at INTEGER`);
17050
17139
  await tx.exec(`CREATE INDEX IF NOT EXISTS ${admission.permits}_owner ON ${admission.permits}(owner, state);
17051
17140
  CREATE INDEX IF NOT EXISTS ${admission.permits}_state ON ${admission.permits}(state)`);
17052
17141
  if (!(await tx.all(`PRAGMA table_info(${admission.owners})`)).some((entry) => entry.name === "boot"))
@@ -17076,14 +17165,24 @@ class SqlResourceAdmission {
17076
17165
  await admission.reconcile();
17077
17166
  return admission;
17078
17167
  }
17168
+ sampleHost() {
17169
+ return this.options.sampleHost?.() ?? {
17170
+ freeMemoryBytes: freemem(),
17171
+ load: process.platform === "win32" ? undefined : loadavg()[0] / cpus().length
17172
+ };
17173
+ }
17174
+ stageStartPressure(kind, delegated) {
17175
+ const threshold = this.options.stageStartMaxLoadPerCpu;
17176
+ if (kind !== "session" || delegated === true || threshold === undefined || threshold === 0)
17177
+ return false;
17178
+ const load = this.sampleHost().load;
17179
+ return load !== undefined && load > threshold;
17180
+ }
17079
17181
  hostReason() {
17080
17182
  const host = this.options.host;
17081
17183
  if (!host)
17082
17184
  return;
17083
- const sample = this.options.sampleHost?.() ?? {
17084
- freeMemoryBytes: freemem(),
17085
- load: process.platform === "win32" ? undefined : loadavg()[0] / cpus().length
17086
- };
17185
+ const sample = this.sampleHost();
17087
17186
  if (host.minFreeMemoryBytes !== undefined) {
17088
17187
  if (sample.freeMemoryBytes === undefined)
17089
17188
  return "host memory unsupported";
@@ -17097,21 +17196,32 @@ class SqlResourceAdmission {
17097
17196
  return "host load";
17098
17197
  }
17099
17198
  }
17100
- async assertCurrentAttempt(tx) {
17199
+ async attemptStanding(tx) {
17101
17200
  const { root, attempt } = this.options;
17102
17201
  if (!attempt)
17103
- return;
17104
- const work = await tx.get(`SELECT 1 AS valid FROM ${this.storage.tables.runs}_work w JOIN ${this.storage.tables.leases} l ON l.run_id = w.root
17202
+ return "current";
17203
+ const work = await tx.get(`SELECT (w.expires_at < ${NOW_MS} OR l.renewed_at + p.timeout < ${NOW_MS}) AS late
17204
+ FROM ${this.storage.tables.runs}_work w JOIN ${this.storage.tables.leases} l ON l.run_id = w.root
17105
17205
  JOIN ${this.storage.tables.runs}_pool p ON p.root = w.root JOIN ${this.storage.tables.runs} r ON r.run_id = w.root
17106
17206
  WHERE w.id = ? AND w.root = ? AND w.generation = ? AND w.epoch = ? AND w.state = 'leased' AND r.status IN ('running', 'paused')
17107
- AND w.expires_at >= CAST(unixepoch('subsec') * 1000 AS INTEGER)
17108
- AND l.epoch = w.epoch AND l.renewed_at + p.timeout >= CAST(unixepoch('subsec') * 1000 AS INTEGER)`, attempt.id, root, attempt.generation, attempt.epoch);
17207
+ AND l.epoch = w.epoch`, attempt.id, root, attempt.generation, attempt.epoch);
17109
17208
  if (!work)
17209
+ return "stale";
17210
+ return Number(work.late) === 1 ? "late" : "current";
17211
+ }
17212
+ async assertCurrentAttempt(tx) {
17213
+ const standing = await this.attemptStanding(tx);
17214
+ if (standing === "stale")
17110
17215
  throw new Error("Stale resource admission attempt");
17216
+ return standing === "current";
17111
17217
  }
17112
17218
  async validate(signal) {
17113
- signal?.throwIfAborted();
17114
- await this.storage.transaction((tx) => this.assertCurrentAttempt(tx));
17219
+ for (;; ) {
17220
+ signal?.throwIfAborted();
17221
+ if (await this.storage.transaction((tx) => this.assertCurrentAttempt(tx)))
17222
+ return;
17223
+ await delay(LATE_ATTEMPT_POLL_MS, undefined, { signal });
17224
+ }
17115
17225
  }
17116
17226
  async acquire(kind, signal, request) {
17117
17227
  signal?.throwIfAborted();
@@ -17145,11 +17255,19 @@ class SqlResourceAdmission {
17145
17255
  this.nextReconciliation = performance.now() + 1000;
17146
17256
  }
17147
17257
  const hostReason = this.hostReason();
17258
+ const pressure = this.stageStartPressure(kind, request?.delegated);
17148
17259
  const granted = await this.storage.transaction(async (tx) => {
17149
17260
  await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
17150
17261
  const { root } = this.options;
17151
- await this.assertCurrentAttempt(tx);
17262
+ if (!await this.assertCurrentAttempt(tx)) {
17263
+ const waiting = await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE operation = ? AND state = 'waiting'`, LATE_ATTEMPT_REASON, operation);
17264
+ if (waiting.changes !== 1)
17265
+ throw new Error("Resource admission holder was stopped");
17266
+ return false;
17267
+ }
17152
17268
  let reason = hostReason;
17269
+ if (pressure && (await tx.get(`SELECT 1 AS recent FROM ${this.permits} WHERE root = ? AND kind = 'session' AND granted_at >= ${NOW_MS} - ? LIMIT 1`, root, STAGE_START_SPACING_MS) || await tx.get(`SELECT 1 AS older FROM ${this.permits} WHERE root = ? AND kind = 'session' AND state = 'waiting' AND reason = ? AND ticket < (SELECT ticket FROM ${this.permits} WHERE operation = ?) LIMIT 1`, root, STAGE_START_PRESSURE_REASON, operation)))
17270
+ reason ??= STAGE_START_PRESSURE_REASON;
17153
17271
  const controls = `${this.storage.tables.runs}_pool_control`;
17154
17272
  if (await tx.get("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", controls)) {
17155
17273
  const control = await tx.get(`SELECT state FROM ${controls} WHERE root = ?`, root);
@@ -17164,13 +17282,14 @@ class SqlResourceAdmission {
17164
17282
  const rootClause = scope === "user" ? "" : "AND root = ?";
17165
17283
  const args = scope === "user" ? [kind] : [kind, root];
17166
17284
  const count = await tx.get(`SELECT COUNT(*) AS count FROM ${this.permits} WHERE kind = ? ${rootClause} AND state = 'granted'`, ...args);
17167
- const older = await tx.get(`SELECT 1 AS waiting FROM ${this.permits} WHERE kind = ? ${rootClause} AND state = 'waiting' AND ticket < (SELECT ticket FROM ${this.permits} WHERE operation = ?) LIMIT 1`, ...args, operation);
17285
+ const older = await tx.get(`SELECT 1 AS waiting FROM ${this.permits} WHERE kind = ? ${rootClause} AND state = 'waiting' AND reason IS NOT ? AND ticket < (SELECT ticket FROM ${this.permits} WHERE operation = ?) LIMIT 1`, ...args, STAGE_START_PRESSURE_REASON, operation);
17168
17286
  if (Number(count?.count) >= limit || older)
17169
17287
  reason ??= `${scope} ${kind}`;
17170
17288
  }
17171
17289
  if (reason && request?.delegated)
17172
17290
  throw new Error(`Delegated session cannot be admitted without capacity headroom: ${reason}`);
17173
- const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ? WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, operation);
17291
+ const stageStart = kind === "session" && request?.delegated !== true;
17292
+ const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ?, granted_at = CASE WHEN ? IS NULL AND ? THEN ${NOW_MS} ELSE NULL END WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, reason ?? null, stageStart ? 1 : 0, operation);
17174
17293
  if (updated.changes !== 1)
17175
17294
  throw new Error("Resource admission holder was stopped");
17176
17295
  return !reason;
@@ -17330,7 +17449,7 @@ async function reapProcessGroups(journal, root, scope) {
17330
17449
  return { survivors: [], unprobeable: [] };
17331
17450
  return settleProcessGroups(storage, root, scope, true);
17332
17451
  }
17333
- var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)";
17452
+ var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
17334
17453
  var init_resource_admission = __esm(() => {
17335
17454
  init_internal();
17336
17455
  init_sql_run_journal();
@@ -59903,14 +60022,23 @@ class SqlPoolQueue {
59903
60022
  }
59904
60023
  await tx.run(`UPDATE ${this.meta} SET version = ?`, POOL_SCHEMA_VERSION);
59905
60024
  }
60025
+ leaseRow(tx, lease) {
60026
+ return tx.get(`SELECT l.*, r.status, p.timeout, ${STORE_NOW2} AS now FROM ${this.journal.leases} l JOIN ${this.journal.runs} r ON r.run_id = l.run_id LEFT JOIN ${this.roots} p ON p.root = l.run_id WHERE l.run_id = ?`, lease.runId);
60027
+ }
59906
60028
  async authority(tx, lease, timeout) {
59907
- const row = await tx.get(`SELECT l.*, r.status, p.timeout, ${STORE_NOW2} AS now FROM ${this.journal.leases} l JOIN ${this.journal.runs} r ON r.run_id = l.run_id LEFT JOIN ${this.roots} p ON p.root = l.run_id WHERE l.run_id = ?`, lease.runId);
60029
+ const row = await this.leaseRow(tx, lease);
59908
60030
  if (row?.timeout === null && timeout === undefined)
59909
60031
  throw new Error("Pool not initialized");
59910
60032
  if (!row || row.epoch !== lease.epoch || row.holder_id !== lease.holderId || isWorkflowLeaseExpired(row.renewed_at, row.timeout ?? timeout ?? 0, row.now))
59911
60033
  throw new StaleLeaseError(lease.runId, lease.epoch, Number(row?.epoch ?? 0));
59912
60034
  return row;
59913
60035
  }
60036
+ async rootLease(lease, timeout) {
60037
+ const row = await this.storage.read((executor) => this.leaseRow(executor, lease));
60038
+ if (!row || row.epoch !== lease.epoch || row.holder_id !== lease.holderId)
60039
+ return "lost";
60040
+ return isWorkflowLeaseExpired(row.renewed_at, row.timeout ?? timeout, row.now) ? "late" : "current";
60041
+ }
59914
60042
  async initialize(lease, coordinatorTimeoutMs) {
59915
60043
  positive(coordinatorTimeoutMs);
59916
60044
  lease = snapshotLease(lease);
@@ -59922,6 +60050,9 @@ class SqlPoolQueue {
59922
60050
  throw new Error("Pool configuration is immutable");
59923
60051
  });
59924
60052
  }
60053
+ async initialized(root) {
60054
+ return await this.storage.read((executor) => executor.get(`SELECT 1 AS held FROM ${this.roots} WHERE root = ?`, root)) !== undefined;
60055
+ }
59925
60056
  async control(lease) {
59926
60057
  return this.transaction(async (tx) => {
59927
60058
  await this.authority(tx, lease);
@@ -60316,6 +60447,21 @@ class SqlPoolQueue {
60316
60447
  return row ? JSON.parse(String(row.result)) : undefined;
60317
60448
  });
60318
60449
  }
60450
+ async reopenCompleted(lease, id, rerun) {
60451
+ lease = snapshotLease(lease);
60452
+ return this.transaction(async (tx) => {
60453
+ await this.authority(tx, lease);
60454
+ const row = await tx.get(`SELECT * FROM ${this.work} WHERE id = ? AND root = ?`, id, lease.runId);
60455
+ if (row?.state !== "completed" || row.epoch >= lease.epoch)
60456
+ return;
60457
+ const stored = await tx.get(`SELECT result FROM ${this.work}_results WHERE work = ?`, id);
60458
+ if (!stored || !rerun(JSON.parse(String(stored.result))))
60459
+ return;
60460
+ await tx.run(`DELETE FROM ${this.work}_results WHERE work = ?`, id);
60461
+ await tx.run(`UPDATE ${this.work} SET state = 'ready', worker = NULL, expires_at = 0, epoch = ? WHERE id = ?`, lease.epoch, id);
60462
+ return fromRow({ ...row, state: "ready", worker: null, expires_at: 0, epoch: lease.epoch });
60463
+ });
60464
+ }
60319
60465
  async expire(lease, token) {
60320
60466
  lease = snapshotLease(lease);
60321
60467
  token = { ...token };
@@ -60437,7 +60583,7 @@ function reportsClosedSession(error) {
60437
60583
  }
60438
60584
  function isSessionClosedError(error) {
60439
60585
  let current = error;
60440
- for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined; depth++) {
60586
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH2 && current !== undefined; depth++) {
60441
60587
  try {
60442
60588
  if (reportsClosedSession(current))
60443
60589
  return true;
@@ -60448,7 +60594,7 @@ function isSessionClosedError(error) {
60448
60594
  }
60449
60595
  return false;
60450
60596
  }
60451
- var MAX_CAUSE_DEPTH = 8;
60597
+ var MAX_CAUSE_DEPTH2 = 8;
60452
60598
  var init_session_closed_error = () => {};
60453
60599
 
60454
60600
  // dist/builtin/workflows/src/shared/external-run-routes.ts
@@ -64772,57 +64918,6 @@ var init_sdk_intercom = __esm(() => {
64772
64918
  owners2 = new WeakMap;
64773
64919
  });
64774
64920
 
64775
- // dist/builtin/workflows/src/shared/workflow-infrastructure-error.ts
64776
- function isInfrastructureError(value2) {
64777
- if (value2 instanceof WorkflowInfrastructureError)
64778
- return true;
64779
- if (typeof value2 !== "object" || value2 === null)
64780
- return false;
64781
- const candidate = value2;
64782
- return candidate.name === WORKFLOW_INFRASTRUCTURE_ERROR_NAME && candidate.retryable === true && typeof candidate.code === "string";
64783
- }
64784
- function isRetryableWorkflowError(error) {
64785
- let current = error;
64786
- for (let depth = 0;depth < MAX_CAUSE_DEPTH2 && current !== undefined && current !== null; depth++) {
64787
- if (isInfrastructureError(current))
64788
- return true;
64789
- current = current instanceof Error ? current.cause : undefined;
64790
- }
64791
- return false;
64792
- }
64793
- function workflowInfrastructureFault(message) {
64794
- return FAULT_PATTERNS.find(([pattern]) => pattern.test(message))?.[1];
64795
- }
64796
- function asWorkflowInfrastructureError(error) {
64797
- if (isInfrastructureError(error))
64798
- return error;
64799
- if (!(error instanceof Error))
64800
- return;
64801
- const code = workflowInfrastructureFault(error.message);
64802
- return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
64803
- }
64804
- var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH2 = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
64805
- var init_workflow_infrastructure_error = __esm(() => {
64806
- FAULT_PATTERNS = [
64807
- [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
64808
- [/Local worker retry limit/, "worker_retry_limit"],
64809
- [/Local worker (?:startup timed out|exited before readiness)/, "worker_startup_failed"],
64810
- [/Worker exited before delivery acknowledgement|Local worker pool coordinator exited/, "worker_lost"],
64811
- [/Stale work lease/, "stale_work_lease"],
64812
- [/Intercom runtime no longer active/, "intercom_unavailable"],
64813
- [/Storage unavailable/, "storage_unavailable"]
64814
- ];
64815
- WorkflowInfrastructureError = class WorkflowInfrastructureError extends Error {
64816
- name = WORKFLOW_INFRASTRUCTURE_ERROR_NAME;
64817
- retryable = true;
64818
- code;
64819
- constructor(code, message, options) {
64820
- super(message, options);
64821
- this.code = code;
64822
- }
64823
- };
64824
- });
64825
-
64826
64921
  // dist/builtin/workflows/src/workers/coordinator-protocol.ts
64827
64922
  var POOL_COORDINATOR_ROLE = "pool-coordinator", POOL_SUMMARY_INTERVAL_MS = 250;
64828
64923
 
@@ -65106,6 +65201,16 @@ class RateLimitObservationOutbox {
65106
65201
  }
65107
65202
  var RATE_LIMIT_SHARE_INTERVAL_MS = 250;
65108
65203
 
65204
+ // dist/builtin/workflows/src/workers/stage-session-commands.ts
65205
+ function runsWhilePaused(command) {
65206
+ if (command.kind === "abort-bash" || command.kind === "abort-compaction")
65207
+ return true;
65208
+ if (command.kind !== "task")
65209
+ return false;
65210
+ const action = command.action.kind;
65211
+ return action === "cancel" || action === "output" || action === "detail" || action === "transcript";
65212
+ }
65213
+
65109
65214
  // dist/builtin/workflows/src/workers/stage-watch-coalesce.ts
65110
65215
  function streamedDelta(event) {
65111
65216
  if (event.type !== "message_update")
@@ -65432,6 +65537,7 @@ class PoolCoordinator {
65432
65537
  closing;
65433
65538
  replacements = 0;
65434
65539
  rootLeaseLost = false;
65540
+ lateRoot;
65435
65541
  attemptBase = new Map;
65436
65542
  constructor(port) {
65437
65543
  this.port = port;
@@ -65445,6 +65551,47 @@ class PoolCoordinator {
65445
65551
  await this.journal?.close().catch(() => {});
65446
65552
  await this.durable?.close().catch(() => {});
65447
65553
  }
65554
+ async whileRootLate(queue, action, resume) {
65555
+ for (let waited = false;; ) {
65556
+ try {
65557
+ if (waited)
65558
+ await resume?.();
65559
+ return await action();
65560
+ } catch (error) {
65561
+ if (!(error instanceof Error) || !isRootLeaseLost(error) || !await this.rootRenewal(queue))
65562
+ throw error;
65563
+ waited = true;
65564
+ }
65565
+ }
65566
+ }
65567
+ rootRenewal(queue) {
65568
+ this.lateRoot ??= this.awaitRootRenewal(queue).finally(() => {
65569
+ this.lateRoot = undefined;
65570
+ });
65571
+ return this.lateRoot;
65572
+ }
65573
+ async awaitRootRenewal(queue) {
65574
+ const lease = this.lease;
65575
+ const since = performance.now();
65576
+ let late = false;
65577
+ try {
65578
+ for (;; ) {
65579
+ if (this.closed)
65580
+ return false;
65581
+ const lost = await this.remote?.fresh(lease).then(() => false, () => true);
65582
+ if (lost)
65583
+ return false;
65584
+ const standing = await queue.rootLease(lease, COORDINATOR_LEASE_MS);
65585
+ if (standing !== "late")
65586
+ return standing === "current";
65587
+ late = true;
65588
+ await delay2(LATE_ROOT_POLL_MS);
65589
+ }
65590
+ } finally {
65591
+ if (late)
65592
+ this.stalledMs += performance.now() - since;
65593
+ }
65594
+ }
65448
65595
  receive(message) {
65449
65596
  if (message.kind === "request-reply") {
65450
65597
  const pending = this.requests.get(message.id);
@@ -65513,6 +65660,8 @@ class PoolCoordinator {
65513
65660
  return this.stageAnswer(command.runId, command.stageId, command.answer);
65514
65661
  case "stage-pause":
65515
65662
  return this.stageControl(command.watch, { op: "pause" });
65663
+ case "stage-session":
65664
+ return this.stageSession(command.watch, command.command);
65516
65665
  case "stage-resume":
65517
65666
  return this.stageControl(command.watch, {
65518
65667
  op: "resume",
@@ -65542,8 +65691,8 @@ class PoolCoordinator {
65542
65691
  this.usageTotal = await this.usageLedger.total(root);
65543
65692
  this.lease = lease;
65544
65693
  const queue = await openSqlPoolQueue(this.journal, this.remote);
65545
- await queue.initialize(lease, COORDINATOR_LEASE_MS);
65546
- await queue.drainOutbox(lease);
65694
+ await this.whileRootLate(queue, () => queue.initialize(lease, COORDINATOR_LEASE_MS));
65695
+ await this.whileRootLate(queue, () => queue.drainOutbox(lease));
65547
65696
  if (this.remote !== undefined) {
65548
65697
  const remote = this.remote;
65549
65698
  this.mirrorTimer = setInterval(() => void remote.fresh(lease, 0).catch(() => {}), MIRROR_INTERVAL_MS).unref();
@@ -65552,10 +65701,10 @@ class PoolCoordinator {
65552
65701
  for (const work of await queue.inspect(root))
65553
65702
  if (work.state === "leased" && work.epoch < lease.epoch && (await unconfirmedProcessGroups(this.journal, root, { attempt: work })).length > 0)
65554
65703
  retained.push(work);
65555
- await queue.releasePreviousIncarnations(lease, retained.map((work) => work.id));
65704
+ await this.whileRootLate(queue, () => queue.releasePreviousIncarnations(lease, retained.map((work) => work.id)));
65556
65705
  for (;; ) {
65557
65706
  const intended = this.desiredControl ?? "running";
65558
- await queue.setControl(lease, intended);
65707
+ await this.whileRootLate(queue, () => queue.setControl(lease, intended));
65559
65708
  if (intended === (this.desiredControl ?? "running"))
65560
65709
  break;
65561
65710
  }
@@ -65591,7 +65740,7 @@ class PoolCoordinator {
65591
65740
  return this.retainedTransition(async () => {
65592
65741
  const record = this.retained.get(work.id);
65593
65742
  this.retained.delete(work.id);
65594
- await this.queue.releaseRetained(this.lease, work, record?.enqueued === true).catch((error) => {
65743
+ await this.whileRootLate(this.queue, () => this.queue.releaseRetained(this.lease, work, record?.enqueued === true)).catch((error) => {
65595
65744
  if (error.message !== STALE_WORK_LEASE)
65596
65745
  throw error;
65597
65746
  });
@@ -65605,20 +65754,29 @@ class PoolCoordinator {
65605
65754
  async control(state) {
65606
65755
  this.desiredControl = state;
65607
65756
  if (!this.queue || !this.lease)
65608
- return;
65609
- const control = await this.queue.setControl(this.lease, state);
65610
- const deadline = Date.now() + CONTROL_ACKNOWLEDGEMENT_TIMEOUT_MS;
65611
- while ([...this.readyWorkers].some((worker) => (this.controlAcks.get(worker) ?? -1) < control.revision)) {
65757
+ return 0;
65758
+ const queue = this.queue;
65759
+ const lease = this.lease;
65760
+ const control = await this.whileRootLate(queue, () => queue.setControl(lease, state));
65761
+ const unacknowledged = () => [...this.readyWorkers].filter((worker) => (this.controlAcks.get(worker) ?? -1) < control.revision).length;
65762
+ let pending = unacknowledged();
65763
+ let progressAt = performance.now();
65764
+ let stalledAt = this.stalledMs;
65765
+ while (pending > 0) {
65612
65766
  if (this.desiredControl !== state)
65613
- return;
65614
- if (Date.now() >= deadline) {
65615
- if (state === "quit")
65616
- break;
65617
- throw new Error("Worker control acknowledgement unresolved");
65618
- }
65767
+ return pending;
65619
65768
  await delay2(25);
65769
+ this.observeResponsiveness();
65770
+ const now = unacknowledged();
65771
+ if (now < pending) {
65772
+ pending = now;
65773
+ progressAt = performance.now();
65774
+ stalledAt = this.stalledMs;
65775
+ } else if (performance.now() - progressAt - (this.stalledMs - stalledAt) >= CONTROL_ACKNOWLEDGEMENT_STALL_MS)
65776
+ break;
65620
65777
  }
65621
65778
  this.flush();
65779
+ return pending;
65622
65780
  }
65623
65781
  async start(sources, cwd, publish, rootRun) {
65624
65782
  if (this.closed)
@@ -65646,7 +65804,11 @@ class PoolCoordinator {
65646
65804
  return;
65647
65805
  });
65648
65806
  this.recoveryTimer = setInterval(() => {
65649
- this.recoverExpired().catch((error) => this.fail(error));
65807
+ this.recoverExpired().catch(async (error) => {
65808
+ if (this.queue && isRootLeaseLost(error) && await this.rootRenewal(this.queue).catch(() => false))
65809
+ return;
65810
+ this.fail(error);
65811
+ });
65650
65812
  }, RECOVERY_INTERVAL_MS).unref();
65651
65813
  const workers = Array.from({ length: this.options.workers }, () => this.spawn());
65652
65814
  try {
@@ -65663,7 +65825,9 @@ class PoolCoordinator {
65663
65825
  const queue = this.queue;
65664
65826
  const lease = this.lease;
65665
65827
  const work = await this.retainedTransition(async () => {
65666
- const enqueued = await queue.enqueue(lease, command.descriptor, command.boundary);
65828
+ let enqueued = await this.whileRootLate(queue, () => queue.enqueue(lease, command.descriptor, command.boundary));
65829
+ if (enqueued.state === "completed" && enqueued.epoch < lease.epoch)
65830
+ enqueued = await this.whileRootLate(queue, () => queue.reopenCompleted(lease, enqueued.id, failedOnInfrastructureFault)) ?? enqueued;
65667
65831
  const retained = this.retained.get(enqueued.id);
65668
65832
  if (retained)
65669
65833
  retained.enqueued = true;
@@ -65676,7 +65840,9 @@ class PoolCoordinator {
65676
65840
  }
65677
65841
  async transport(request, payload) {
65678
65842
  const target = this.deliveryRequests.get(request);
65679
- if (target === undefined || !this.incarnations.has(target.slot.worker))
65843
+ if (target === undefined)
65844
+ throw new Error("Pooled stage delivery request already answered");
65845
+ if (!this.incarnations.has(target.slot.worker))
65680
65846
  throw new Error("Worker exited before delivery acknowledgement");
65681
65847
  const { slot, workerRequest } = target;
65682
65848
  await new Promise((resolve, reject) => {
@@ -65904,6 +66070,34 @@ class PoolCoordinator {
65904
66070
  return { ok: false, refusal: "StaleAttempt" };
65905
66071
  return { ok: true, ...result.action === undefined ? {} : { action: result.action } };
65906
66072
  }
66073
+ async stageSession(id, command) {
66074
+ const watch = this.watches.get(id);
66075
+ if (watch === undefined || !watch.open)
66076
+ return {
66077
+ ok: false,
66078
+ refusal: this.endedWatches.get(id) === "stage_finished" ? "StageNotLive" : "StaleAttempt"
66079
+ };
66080
+ const { slot, attempt } = watch;
66081
+ if (slot.work?.id !== attempt.workId || slot.work.generation !== attempt.generation)
66082
+ return { ok: false, refusal: "StaleAttempt" };
66083
+ if (this.desiredControl !== undefined && this.desiredControl !== "running" && !runsWhilePaused(command))
66084
+ return { ok: false, refusal: "PoolPausedOrQuitting" };
66085
+ const result = await this.stageCommand(slot, { attempt, op: "session", watch: id, command });
66086
+ if (result === undefined)
66087
+ return { ok: false, refusal: "StaleAttempt" };
66088
+ if (result.error !== undefined)
66089
+ throw new Error(result.error);
66090
+ const refusal = result.refusal;
66091
+ if (refusal === "StageNotLive" || refusal === "PoolPausedOrQuitting")
66092
+ return { ok: false, refusal };
66093
+ if (refusal !== undefined)
66094
+ return { ok: false, refusal: "StaleAttempt" };
66095
+ return {
66096
+ ok: true,
66097
+ ...result.bash === undefined ? {} : { bash: result.bash },
66098
+ ...result.task === undefined ? {} : { task: result.task }
66099
+ };
66100
+ }
65907
66101
  stageCommand(slot, command) {
65908
66102
  if (!this.incarnations.has(slot.worker)) {
65909
66103
  tracePooledWatch("coordinator.command.no-worker", { op: command.op, worker: slot.worker });
@@ -66019,7 +66213,8 @@ class PoolCoordinator {
66019
66213
  root,
66020
66214
  definitions: this.sources,
66021
66215
  admission: options.admission,
66022
- ...this.promptNodes ? { promptNodes: true } : {}
66216
+ ...this.promptNodes ? { promptNodes: true } : {},
66217
+ ...options.stageStartMaxLoadPerCpu === undefined ? {} : { stageStartMaxLoadPerCpu: options.stageStartMaxLoadPerCpu }
66023
66218
  })
66024
66219
  ], {
66025
66220
  stdio: ["ignore", "ignore", "pipe", "ipc"],
@@ -66140,7 +66335,10 @@ class PoolCoordinator {
66140
66335
  if (this.closed)
66141
66336
  return;
66142
66337
  const exhausted = work !== undefined && this.attempts(work) >= MAX_CHILD_ATTEMPTS;
66143
- const settled = work !== undefined && (exhausted ? await this.queue.failExited(this.lease, work) : await this.queue.releaseExited(this.lease, work));
66338
+ const queue = this.queue;
66339
+ const lease = this.lease;
66340
+ const attempt = work;
66341
+ const settled = attempt !== undefined && await this.whileRootLate(queue, () => exhausted ? queue.failExited(lease, attempt) : queue.releaseExited(lease, attempt));
66144
66342
  const superseded = work !== undefined && !settled && await this.superseded(work);
66145
66343
  if (work !== undefined && exhausted && !superseded)
66146
66344
  this.failWork(work, diagnosed(childRetryLimit(work, "lost its worker")));
@@ -66202,21 +66400,40 @@ class PoolCoordinator {
66202
66400
  await this.starting;
66203
66401
  const queue = this.queue;
66204
66402
  const lease = this.lease;
66403
+ const renewAttempt = async () => {
66404
+ const attempt = slot.work;
66405
+ if (attempt === undefined)
66406
+ return;
66407
+ const renewed = await queue.extend(lease, attempt, options.leaseMs);
66408
+ if (slot.work === attempt)
66409
+ slot.work = renewed;
66410
+ };
66411
+ const current = async (action) => {
66412
+ try {
66413
+ return await action();
66414
+ } catch (error) {
66415
+ if (!(error instanceof Error) || error.message !== STALE_WORK_LEASE || slot.work === undefined)
66416
+ throw error;
66417
+ await renewAttempt();
66418
+ return action();
66419
+ }
66420
+ };
66421
+ const leased = (action) => this.whileRootLate(queue, () => current(action), renewAttempt);
66205
66422
  if (message.kind === "control")
66206
- return queue.control(lease);
66423
+ return leased(() => queue.control(lease));
66207
66424
  if (message.kind === "fenced") {
66208
66425
  const fenced = slot.work;
66209
66426
  slot.work = undefined;
66210
66427
  if (fenced && !await this.superseded(fenced)) {
66211
66428
  if (this.attempts(fenced) < MAX_CHILD_ATTEMPTS)
66212
- await queue.releaseExited(lease, fenced);
66213
- else if (await queue.failExited(lease, fenced))
66429
+ await leased(() => queue.releaseExited(lease, fenced));
66430
+ else if (await leased(() => queue.failExited(lease, fenced)))
66214
66431
  this.failWork(fenced, new Error(childRetryLimit(fenced, "was refused by the root as stale on every attempt")));
66215
66432
  }
66216
66433
  return;
66217
66434
  }
66218
66435
  if (message.kind === "controlled") {
66219
- const control = await queue.control(lease);
66436
+ const control = await leased(() => queue.control(lease));
66220
66437
  if (message.revision > control.revision)
66221
66438
  throw new Error("Unknown control revision");
66222
66439
  this.controlAcks.set(worker, Math.max(this.controlAcks.get(worker) ?? -1, message.revision));
@@ -66227,21 +66444,21 @@ class PoolCoordinator {
66227
66444
  throw new Error("Worker already holds an attempt");
66228
66445
  if (this.budgetBlocked)
66229
66446
  return;
66230
- await this.recoverExpired();
66231
- slot.runs.clear();
66232
- slot.work = await queue.claim(lease, worker, this.sources.map((source) => ({
66233
- definition: `${source.path}#${source.exportName}`,
66234
- digest: source.digest
66235
- })), options.leaseMs);
66447
+ slot.work = await leased(async () => {
66448
+ await this.recoverExpired();
66449
+ slot.runs.clear();
66450
+ return queue.claim(lease, worker, this.sources.map((source) => ({
66451
+ definition: `${source.path}#${source.exportName}`,
66452
+ digest: source.digest
66453
+ })), options.leaseMs);
66454
+ });
66236
66455
  return slot.work;
66237
66456
  }
66238
- let work = slot.work;
66239
- if (!work)
66457
+ const held = slot.work;
66458
+ if (!held)
66240
66459
  throw new Error("Worker has no active attempt");
66241
- if (message.kind === "renew" || work.expiresAt - Date.now() < options.leaseMs / 2) {
66242
- work = await queue.extend(lease, work, options.leaseMs);
66243
- slot.work = work;
66244
- }
66460
+ const work = message.kind === "renew" || held.expiresAt - Date.now() < options.leaseMs / 2 ? await leased(() => queue.extend(lease, held, options.leaseMs)) : held;
66461
+ slot.work = work;
66245
66462
  if (message.kind === "activity") {
66246
66463
  for (const run of message.runs)
66247
66464
  slot.runs.add(run.id);
@@ -66301,24 +66518,24 @@ class PoolCoordinator {
66301
66518
  if (message.kind === "renew")
66302
66519
  return;
66303
66520
  if (message.kind === "commit") {
66304
- const receipt = await queue.commit(lease, work, message.operation, message.mutation);
66521
+ const receipt = await leased(() => queue.commit(lease, work, message.operation, message.mutation));
66305
66522
  if (message.mutation.kind !== "session-append")
66306
66523
  this.noteUsage(await this.usageLedger.total(root));
66307
66524
  return receipt;
66308
66525
  }
66309
66526
  if (message.kind === "usage") {
66310
- this.noteUsage(await this.usageLedger.report(lease, work, message.reports));
66527
+ this.noteUsage(await leased(() => this.usageLedger.report(lease, work, message.reports)));
66311
66528
  return;
66312
66529
  }
66313
66530
  if (message.kind === "budget") {
66314
- await queue.assertCurrent(lease, work);
66531
+ await leased(() => queue.assertCurrent(lease, work));
66315
66532
  if (this.budgetBlocked)
66316
66533
  throw new Error("Root workflow budget exhausted");
66317
- return queue.control(lease);
66534
+ return leased(() => queue.control(lease));
66318
66535
  }
66319
66536
  if (message.kind !== "complete")
66320
66537
  throw new Error("Invalid worker operation");
66321
- await queue.complete(lease, work, message.result);
66538
+ await leased(() => queue.complete(lease, work, message.result));
66322
66539
  this.endStageWatches((watch) => watch.slot === slot, "stage_finished");
66323
66540
  this.replacements = 0;
66324
66541
  slot.work = undefined;
@@ -66357,7 +66574,7 @@ class PoolCoordinator {
66357
66574
  if (this.recovering)
66358
66575
  return this.recovering;
66359
66576
  const root = this.root;
66360
- if (this.closed || root === undefined || !this.queue || performance.now() < this.nextRecoveryAt)
66577
+ if (this.closed || root === undefined || !this.queue || this.lateRoot !== undefined || performance.now() < this.nextRecoveryAt)
66361
66578
  return Promise.resolve();
66362
66579
  const queue = this.queue;
66363
66580
  const lease = this.lease;
@@ -66366,9 +66583,12 @@ class PoolCoordinator {
66366
66583
  await new Promise((resolve) => setImmediate(resolve));
66367
66584
  await this.admission?.reconcile();
66368
66585
  const expiredBefore = Date.now();
66369
- for (const work of await queue.inspect(root)) {
66370
- if (work.state !== "leased" || work.expiresAt >= expiredBefore)
66371
- continue;
66586
+ const expired = (await queue.inspect(root)).filter((work) => work.state === "leased" && work.expiresAt < expiredBefore);
66587
+ if (expired.length > 0 && await queue.rootLease(lease, COORDINATOR_LEASE_MS) === "late") {
66588
+ this.rootRenewal(queue).catch(() => {});
66589
+ return;
66590
+ }
66591
+ for (const work of expired) {
66372
66592
  if (this.quarantined.has(work.id))
66373
66593
  continue;
66374
66594
  if (this.held.has(work.id)) {
@@ -66484,7 +66704,7 @@ function childRetryLimit(work, reason) {
66484
66704
  const exportName = work.definition.slice(work.definition.lastIndexOf("#") + 1);
66485
66705
  return `Local worker retry limit: child ${work.childRunId} (${exportName}) ${reason} ${MAX_CHILD_ATTEMPTS} times`;
66486
66706
  }
66487
- var CONTROL_ACKNOWLEDGEMENT_TIMEOUT_MS = 5000, RECOVERY_INTERVAL_MS = 500, TICK_INTERVAL_MS = 100, STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, MIRROR_INTERVAL_MS = 1e4, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000, MAX_CHILD_ATTEMPTS = 3, ROUTE_REGISTRATION_WAIT_MS = 120000, STAGE_WATCH_BACKLOG_EVENTS = 2000, STAGE_WATCH_BACKLOG_BYTES, PENDING_STAGE_MESSAGE_EVENT2 = "atomic:workflow-pending-stage-message", STICKY_LIVE_DELIVERED_EVENT2 = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_UNDELIVERABLE_EVENT2 = "atomic:workflow-pending-stage-undeliverable", ENDED_WATCHES_REMEMBERED = 1024;
66707
+ var CONTROL_ACKNOWLEDGEMENT_STALL_MS = 5000, RECOVERY_INTERVAL_MS = 500, TICK_INTERVAL_MS = 100, STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, LATE_ROOT_POLL_MS, MIRROR_INTERVAL_MS = 1e4, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000, MAX_CHILD_ATTEMPTS = 3, ROUTE_REGISTRATION_WAIT_MS = 120000, STAGE_WATCH_BACKLOG_EVENTS = 2000, STAGE_WATCH_BACKLOG_BYTES, PENDING_STAGE_MESSAGE_EVENT2 = "atomic:workflow-pending-stage-message", STICKY_LIVE_DELIVERED_EVENT2 = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_UNDELIVERABLE_EVENT2 = "atomic:workflow-pending-stage-undeliverable", ENDED_WATCHES_REMEMBERED = 1024;
66488
66708
  var init_pool_coordinator = __esm(() => {
66489
66709
  init_internal();
66490
66710
  init_pool_remote_journal();
@@ -66498,12 +66718,14 @@ var init_pool_coordinator = __esm(() => {
66498
66718
  init_pending_stage_route_capability();
66499
66719
  init_pending_stage_route_readiness();
66500
66720
  init_store();
66721
+ init_workflow_infrastructure_error();
66501
66722
  init_pool_journal();
66502
66723
  init_pooled_watch_trace();
66503
66724
  init_resource_admission();
66504
66725
  init_stage_watch_coalesce();
66505
66726
  init_startup_timeout();
66506
66727
  COORDINATOR_LEASE_MS = 3 * STAGE_SESSION_HEARTBEAT_INTERVAL_MS;
66728
+ LATE_ROOT_POLL_MS = RECOVERY_INTERVAL_MS;
66507
66729
  STAGE_WATCH_BACKLOG_BYTES = 8 * 1024 * 1024;
66508
66730
  });
66509
66731
 
@@ -66594,7 +66816,8 @@ function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageNa
66594
66816
  const boundedDrain = (drain, sessionId) => {
66595
66817
  const waiting = new Promise((resolve, reject) => {
66596
66818
  drain.then(resolve, reject);
66597
- watchStall(drain, sessionId, resolve);
66819
+ if (options.holdUntilDrained !== true)
66820
+ watchStall(drain, sessionId, resolve);
66598
66821
  });
66599
66822
  waiting.catch(() => {});
66600
66823
  return waiting;
@@ -66976,10 +67199,14 @@ class PoolStageHandoff {
66976
67199
  if (run.endedAt !== undefined || this.owners.get(run.id) !== work.id)
66977
67200
  continue;
66978
67201
  if (run.id === work.childRunId) {
66979
- const execution = run.stages.find((stage) => stage.workerExecution !== undefined)?.workerExecution;
67202
+ const executions = new Map(run.stages.map((stage) => [stage.id, stage.workerExecution]));
67203
+ const attempt = run.stages.flatMap((stage) => stage.workerExecution === undefined ? [] : [stage.workerExecution]).reduce((latest, execution) => latest === undefined || execution.renewedAt > latest.renewedAt ? execution : latest, undefined);
66980
67204
  this.store.recordRunStart({
66981
67205
  ...run,
66982
- stages: result.stages.map((stage) => execution === undefined ? stage : { ...stage, workerExecution: execution }),
67206
+ stages: result.stages.map((stage) => {
67207
+ const execution = executions.get(stage.id) ?? attempt;
67208
+ return execution === undefined ? stage : { ...stage, workerExecution: execution };
67209
+ }),
66983
67210
  toolNodes: result.toolNodes
66984
67211
  });
66985
67212
  this.store.recordRunEnd(run.id, result.status, result.result, result.error);
@@ -67033,7 +67260,7 @@ class PoolStageHandoff {
67033
67260
  pendingStageMessages: [
67034
67261
  ...known.get(run.id)?.pendingStageMessages ?? pendingStageMessagesForDurableRun(this.backend, run.id, root)
67035
67262
  ],
67036
- stages: withReconnectingStages(known.get(run.id), run, run.stages.map((stage) => ({ ...stage, workerExecution: execution })), work.generation)
67263
+ stages: withReconnectingStages(known.get(run.id), run, run.stages.map((stage) => ({ ...stage, workerExecution: endedUnder(known.get(run.id), stage, execution) })), work.generation)
67037
67264
  }));
67038
67265
  const changed = published.some((run) => activity(known.get(run.id)) !== activity(run));
67039
67266
  for (const run of published)
@@ -67164,7 +67391,7 @@ class PoolStageHandoff {
67164
67391
  throw new Error(STALE);
67165
67392
  if (attempt.sessions.get(key) !== sessionId)
67166
67393
  throw new Error(`Stage session ${sessionId} was replaced before it acknowledged delivery`);
67167
- }, action));
67394
+ }, action), { holdUntilDrained: true });
67168
67395
  attempt.deliveries.set(key, delivery);
67169
67396
  }
67170
67397
  return delivery;
@@ -67176,6 +67403,12 @@ function outside2(runId) {
67176
67403
  function isLive(stage) {
67177
67404
  return (stage.sessionId !== undefined || stage.sessionFile !== undefined) && stage.status !== "completed" && stage.status !== "failed" && stage.status !== "skipped";
67178
67405
  }
67406
+ function endedUnder(known, stage, execution) {
67407
+ if (!isTerminalStageStatus(stage.status))
67408
+ return execution;
67409
+ const previous = known?.stages.find((candidate) => candidate.id === stage.id);
67410
+ return previous !== undefined && isTerminalStageStatus(previous.status) && previous.workerExecution?.generation === execution.generation && previous.workerExecution.worker === execution.worker ? previous.workerExecution : execution;
67411
+ }
67179
67412
  function withReconnectingStages(previous, run, stages, generation) {
67180
67413
  if (previous === undefined || run.endedAt !== undefined || isTerminalRunStatus(run.status))
67181
67414
  return stages;
@@ -67312,6 +67545,116 @@ class StageToolExecutionBuffer {
67312
67545
  }
67313
67546
  }
67314
67547
 
67548
+ // dist/builtin/workflows/src/workers/pooled-stage-task-source.ts
67549
+ function failure2(error) {
67550
+ return { ok: false, error: { message: typeof error === "string" ? error : error.message } };
67551
+ }
67552
+
67553
+ class PooledStageTaskSource {
67554
+ run;
67555
+ state = EMPTY;
67556
+ records = [];
67557
+ backgroundRecords = [];
67558
+ settlements = new Map;
67559
+ activity = new Map;
67560
+ statuses = new Map;
67561
+ stdin = new Set;
67562
+ listeners = new Set;
67563
+ constructor(run) {
67564
+ this.run = run;
67565
+ }
67566
+ apply(state) {
67567
+ this.state = state;
67568
+ this.records = [...state.tasks];
67569
+ const background = new Set(state.background);
67570
+ this.backgroundRecords = this.records.filter((task) => background.has(task.ref.taskId));
67571
+ this.settlements = new Map(state.settlement.map(([id, sequence]) => [id, BigInt(sequence)]));
67572
+ this.activity = new Map(state.activity.map((entry) => [entry.id, entry]));
67573
+ this.statuses = new Map(state.statuses.map((entry) => [entry.id, new Map(entry.entries)]));
67574
+ this.stdin = new Set(state.stdin);
67575
+ for (const listener of [...this.listeners])
67576
+ listener();
67577
+ }
67578
+ get empty() {
67579
+ return this.state.tasks.length === 0;
67580
+ }
67581
+ get tasks() {
67582
+ return this.records;
67583
+ }
67584
+ get backgroundTasks() {
67585
+ return this.backgroundRecords;
67586
+ }
67587
+ get settlementOrder() {
67588
+ return this.settlements;
67589
+ }
67590
+ recentActivity(id) {
67591
+ return [...this.activity.get(id)?.entries ?? []];
67592
+ }
67593
+ activityOmitted(id) {
67594
+ return this.activity.get(id)?.omitted === true;
67595
+ }
67596
+ subscribe(listener) {
67597
+ this.listeners.add(listener);
67598
+ return () => {
67599
+ this.listeners.delete(listener);
67600
+ };
67601
+ }
67602
+ async foregroundTask(id) {
67603
+ const reply = await this.request(id, { kind: "foreground" });
67604
+ return reply.ok ? reply.value.kind === "foreground" ? reply.value.result : unexpected() : reply;
67605
+ }
67606
+ async cancelTask(id) {
67607
+ const reply = await this.request(id, { kind: "cancel" });
67608
+ if (!reply.ok)
67609
+ return reply;
67610
+ return reply.value.kind === "cancel" ? reply.value.result ?? { ok: true, value: undefined } : unexpected();
67611
+ }
67612
+ stdinAvailable(id) {
67613
+ return this.stdin.has(id);
67614
+ }
67615
+ async writeTaskInput(id, text) {
67616
+ const reply = await this.request(id, { kind: "input", text });
67617
+ return reply.ok ? reply.value.kind === "input" ? reply.value.result : unexpected() : reply;
67618
+ }
67619
+ async readTaskOutput(id, range) {
67620
+ const reply = await this.request(id, { kind: "output", start: range.start, maximumBytes: range.maximumBytes });
67621
+ return reply.ok ? reply.value.kind === "output" ? reply.value.result : unexpected() : reply;
67622
+ }
67623
+ async taskDetailText(id) {
67624
+ const reply = await this.request(id, { kind: "detail" });
67625
+ return reply.ok && reply.value.kind === "detail" ? reply.value.detail : {};
67626
+ }
67627
+ async readTaskTranscript(id, cursor) {
67628
+ const reply = await this.request(id, { kind: "transcript", ...cursor === undefined ? {} : { cursor } });
67629
+ return reply.ok ? reply.value.kind === "transcript" ? reply.value.result : unexpected() : reply;
67630
+ }
67631
+ followTaskTranscript(id) {
67632
+ return this.records.some((task) => task.ref.taskId === id && task.kind === "agent") ? { historyOnly: true } : undefined;
67633
+ }
67634
+ extensionStatuses(id) {
67635
+ return this.statuses.get(id) ?? NO_STATUSES;
67636
+ }
67637
+ watchExtensionStatuses(_id, listener) {
67638
+ return this.subscribe(listener);
67639
+ }
67640
+ async request(id, action) {
67641
+ try {
67642
+ const reply = await this.run(id, action);
67643
+ return reply === undefined ? failure2("Task unavailable") : { ok: true, value: reply };
67644
+ } catch (error) {
67645
+ return failure2(error instanceof Error ? error : String(error));
67646
+ }
67647
+ }
67648
+ }
67649
+ function unexpected() {
67650
+ return failure2("Unexpected reply from the local worker");
67651
+ }
67652
+ var EMPTY, NO_STATUSES;
67653
+ var init_pooled_stage_task_source = __esm(() => {
67654
+ EMPTY = { tasks: [], background: [], settlement: [], activity: [], stdin: [], statuses: [] };
67655
+ NO_STATUSES = new Map;
67656
+ });
67657
+
67315
67658
  // dist/builtin/workflows/src/workers/pooled-stage-handle.ts
67316
67659
  import { randomUUID as randomUUID18 } from "node:crypto";
67317
67660
 
@@ -67342,6 +67685,11 @@ class PooledStageHandle {
67342
67685
  undelivered;
67343
67686
  disposed = false;
67344
67687
  session;
67688
+ commands;
67689
+ sessionStarted = true;
67690
+ started = Promise.withResolvers();
67691
+ bashRuns = new Map;
67692
+ taskView = new PooledStageTaskSource(async (taskId, action) => (await this.sessionCommand({ kind: "task", taskId, action })).task);
67345
67693
  pumping;
67346
67694
  pumpAgain = false;
67347
67695
  watchGone = Promise.withResolvers();
@@ -67404,6 +67752,43 @@ class PooledStageHandle {
67404
67752
  streamingMessage() {
67405
67753
  return this.streaming;
67406
67754
  }
67755
+ commandCatalog() {
67756
+ return this.commands;
67757
+ }
67758
+ taskSource() {
67759
+ return this.taskView;
67760
+ }
67761
+ get isSessionStarting() {
67762
+ return !this.sessionStarted && this.ended === undefined;
67763
+ }
67764
+ get isBashRunning() {
67765
+ return this.bashRuns.size > 0;
67766
+ }
67767
+ async executeBash(command, onChunk, options) {
67768
+ const id = randomUUID18();
67769
+ this.bashRuns.set(id, onChunk);
67770
+ try {
67771
+ const result = await this.sessionCommand({
67772
+ kind: "bash",
67773
+ id,
67774
+ command,
67775
+ ...options?.excludeFromContext === undefined ? {} : { excludeFromContext: options.excludeFromContext }
67776
+ });
67777
+ return result.bash ?? { output: "", exitCode: undefined, cancelled: true, truncated: false };
67778
+ } finally {
67779
+ this.bashRuns.delete(id);
67780
+ }
67781
+ }
67782
+ abortBash() {
67783
+ for (const id of this.bashRuns.keys())
67784
+ this.sessionCommand({ kind: "abort-bash", id }).catch(() => {});
67785
+ }
67786
+ async compact() {
67787
+ await this.sessionCommand({ kind: "compact" });
67788
+ }
67789
+ abortCompaction() {
67790
+ this.sessionCommand({ kind: "abort-compaction" }).catch(() => {});
67791
+ }
67407
67792
  pendingToolExecutionEvents() {
67408
67793
  return this.toolExecutions.replayEvents();
67409
67794
  }
@@ -67551,6 +67936,17 @@ class PooledStageHandle {
67551
67936
  if (this.disposed)
67552
67937
  throw new Error(`Stage chat "${this.stageName}" is closed.`);
67553
67938
  }
67939
+ async sessionCommand(command) {
67940
+ this.throwIfClosed();
67941
+ if (this.ended !== undefined)
67942
+ throw new PooledStageDeliveryError("StageNotLive", this.stageName);
67943
+ if (this.transport.session === undefined)
67944
+ throw new Error(`Stage "${this.stageName}" runs on a local worker that cannot run this command.`);
67945
+ const result = await this.transport.session(this.watchId, command);
67946
+ if (!result.ok)
67947
+ throw new PooledStageDeliveryError(result.refusal, this.stageName);
67948
+ return result;
67949
+ }
67554
67950
  undeliveredCount() {
67555
67951
  if (this.undelivered !== undefined)
67556
67952
  return this.undelivered;
@@ -67616,6 +68012,16 @@ class PooledStageHandle {
67616
68012
  entry.waiter = undefined;
67617
68013
  }
67618
68014
  }
68015
+ markSessionStarted(started) {
68016
+ if (started) {
68017
+ this.sessionStarted = true;
68018
+ this.started.resolve();
68019
+ return;
68020
+ }
68021
+ if (this.sessionStarted)
68022
+ this.started = Promise.withResolvers();
68023
+ this.sessionStarted = false;
68024
+ }
67619
68025
  forget(op) {
67620
68026
  const index = this.outbound.findIndex((entry) => entry.op === op);
67621
68027
  if (index >= 0)
@@ -67672,10 +68078,17 @@ class PooledStageHandle {
67672
68078
  const gone = this.watchGone.promise.then(() => ({ ok: false, refusal: "StaleAttempt" }));
67673
68079
  const sending = this.sendEntry(entry, watch);
67674
68080
  let bound;
68081
+ let stopped = false;
67675
68082
  const unconfirmed = new Promise((resolve) => {
67676
- bound = setTimeout(() => resolve("unconfirmed"), this.admissionWaitMs);
68083
+ this.started.promise.then(() => {
68084
+ if (!stopped)
68085
+ bound = setTimeout(() => resolve("unconfirmed"), this.admissionWaitMs);
68086
+ });
68087
+ });
68088
+ const outcome = await Promise.race([sending, gone, unconfirmed]).finally(() => {
68089
+ stopped = true;
68090
+ clearTimeout(bound);
67677
68091
  });
67678
- const outcome = await Promise.race([sending, gone, unconfirmed]).finally(() => clearTimeout(bound));
67679
68092
  if (outcome === "unconfirmed") {
67680
68093
  tracePooledWatch("root.outbox.unconfirmed", { op: entry.op, watch, generation });
67681
68094
  entry.unconfirmedOn = generation;
@@ -67765,6 +68178,10 @@ class PooledStageHandle {
67765
68178
  }
67766
68179
  adopt(snapshot) {
67767
68180
  this.session = snapshot.sessionId;
68181
+ this.commands = snapshot.commands;
68182
+ if (!this.taskView.empty)
68183
+ this.taskView.apply(NO_TASKS);
68184
+ this.markSessionStarted(snapshot.sessionStarted !== false);
67768
68185
  this.settled = [...snapshot.messages];
67769
68186
  this.streaming = snapshot.streamingMessage;
67770
68187
  this.currentStatus = snapshot.status;
@@ -67785,6 +68202,25 @@ class PooledStageHandle {
67785
68202
  this.forget(event.op);
67786
68203
  return false;
67787
68204
  }
68205
+ if (event.type === STAGE_SESSION_STARTED_EVENT) {
68206
+ this.commands = event.commands;
68207
+ this.markSessionStarted(true);
68208
+ const queued = this.queuedUserMessages();
68209
+ const redraw = { type: "queue_update", steering: [...queued.steering], followUp: [...queued.followUp] };
68210
+ for (const listener of [...this.listeners])
68211
+ listener(redraw);
68212
+ return false;
68213
+ }
68214
+ if (event.type === STAGE_TASKS_EVENT) {
68215
+ this.taskView.apply(event.tasks);
68216
+ return false;
68217
+ }
68218
+ if (event.type === STAGE_MESSAGES_EVENT) {
68219
+ this.settled = [...event.messages];
68220
+ return false;
68221
+ }
68222
+ if (event.type === "bash_execution_update" && event.id !== undefined)
68223
+ this.bashRuns.get(event.id)?.(event.delta);
67788
68224
  this.applyEvent(event);
67789
68225
  const turnUnderway = TURN_EVENTS.has(event.type) || (event.type === "message_start" || event.type === "message_update") && event.message.role === "assistant";
67790
68226
  if (turnUnderway && this.awaitingTurn) {
@@ -67867,10 +68303,12 @@ async function reconnectPooledStageHandle(handle, transport, wanted = () => true
67867
68303
  await rebinding;
67868
68304
  return !handle.isReconnecting;
67869
68305
  }
67870
- var DELIVERY_RECEIPT_EVENT = "atomic_pooled_delivery_receipt", POOLED_ADMISSION_WAIT_MS = 15000, RECONNECTABLE, TURN_EVENTS, PooledStageDeliveryError;
68306
+ var DELIVERY_RECEIPT_EVENT = "atomic_pooled_delivery_receipt", STAGE_SESSION_STARTED_EVENT = "atomic_pooled_stage_session_started", STAGE_MESSAGES_EVENT = "atomic_pooled_stage_messages", STAGE_TASKS_EVENT = "atomic_pooled_stage_tasks", NO_TASKS, POOLED_ADMISSION_WAIT_MS = 15000, RECONNECTABLE, TURN_EVENTS, PooledStageDeliveryError;
67871
68307
  var init_pooled_stage_handle = __esm(() => {
67872
68308
  init_stage_queued_user_messages();
68309
+ init_pooled_stage_task_source();
67873
68310
  init_pooled_watch_trace();
68311
+ NO_TASKS = { tasks: [], background: [], settlement: [], activity: [], stdin: [], statuses: [] };
67874
68312
  RECONNECTABLE = new Set(["attempt_fenced", "worker_exited", "resync"]);
67875
68313
  TURN_EVENTS = new Set(["agent_start", "turn_start", "tool_execution_start", "tool_execution_update"]);
67876
68314
  PooledStageDeliveryError = class PooledStageDeliveryError extends Error {
@@ -67930,6 +68368,9 @@ class RegisteredPooledStageHandle {
67930
68368
  pendingToolExecutionEvents() {
67931
68369
  return this.current()?.pendingToolExecutionEvents?.() ?? [];
67932
68370
  }
68371
+ commandCatalog() {
68372
+ return this.current()?.commandCatalog?.();
68373
+ }
67933
68374
  update(stage) {
67934
68375
  this.stage = stage;
67935
68376
  }
@@ -68228,6 +68669,7 @@ class LocalWorkerPool {
68228
68669
  rootSignature;
68229
68670
  completing = 0;
68230
68671
  rootFlush;
68672
+ connecting;
68231
68673
  starting;
68232
68674
  failure;
68233
68675
  closed = false;
@@ -68257,6 +68699,8 @@ class LocalWorkerPool {
68257
68699
  throw new Error("Invalid local worker startup timeout");
68258
68700
  if (options.leaseMs !== undefined && (!Number.isSafeInteger(options.leaseMs) || options.leaseMs < MINIMUM_WORK_LEASE_MS || options.leaseMs > MAX_TIMER_DELAY_MS))
68259
68701
  throw new Error(`Invalid local worker lease: must be an integer from ${MINIMUM_WORK_LEASE_MS} to ${MAX_TIMER_DELAY_MS} ms`);
68702
+ if (options.maxLoadPerCpu !== undefined && (!Number.isFinite(options.maxLoadPerCpu) || options.maxLoadPerCpu < 0))
68703
+ throw new Error("Invalid local worker load threshold: maxLoadPerCpu must be a number of at least 0");
68260
68704
  this.leaseMs = options.leaseMs ?? DEFAULT_WORK_LEASE_MS;
68261
68705
  this.releaseBinding = bindChildWorkerDispatcher(backend, this, options.rootId);
68262
68706
  if (options.admission && options.rootId === undefined)
@@ -68284,23 +68728,35 @@ class LocalWorkerPool {
68284
68728
  if (!state || state.holderId !== this.backend.holderId)
68285
68729
  throw new Error("Local worker pool requires the root owner");
68286
68730
  this.lease = { runId: root, holderId: state.holderId, epoch: state.epoch };
68287
- const workerEntry = this.options.workerEntry ?? (isBunBinary2 || this.options.coordinator === "in-process" ? defaultWorkerEntry() : undefined);
68288
- this.usageTotal = await this.command({
68289
- kind: "prepare",
68290
- root,
68291
- lease: this.lease,
68292
- options: {
68293
- journalPath: this.options.journalPath,
68294
- ...this.options.durable === undefined ? {} : { durable: this.options.durable },
68295
- workers: this.options.workers,
68296
- leaseMs: this.leaseMs,
68297
- startupTimeoutMs: this.options.startupTimeoutMs ?? defaultStartupTimeoutMs(this.options.workers),
68298
- ...this.options.admission === undefined ? {} : { admission: this.options.admission },
68299
- ...workerEntry === undefined ? {} : { workerEntry }
68300
- },
68301
- ...this.desiredControl === undefined ? {} : { control: this.desiredControl }
68302
- });
68303
68731
  this.queue ??= await openSqlPoolQueue(poolJournal, this.options.durable === undefined ? undefined : new PoolRemoteJournal(this.backend.journal, sqlPoolStorage(poolJournal)));
68732
+ if (await this.queue.initialized(root))
68733
+ await this.connectPool();
68734
+ }
68735
+ connectPool() {
68736
+ this.connecting ??= (async () => {
68737
+ const root = this.root;
68738
+ const lease = this.lease;
68739
+ if (root === undefined || lease === undefined)
68740
+ throw new Error("Local worker pool requires root preparation");
68741
+ const workerEntry = this.options.workerEntry ?? (isBunBinary2 || this.options.coordinator === "in-process" ? defaultWorkerEntry() : undefined);
68742
+ this.usageTotal = await this.command({
68743
+ kind: "prepare",
68744
+ root,
68745
+ lease,
68746
+ options: {
68747
+ journalPath: this.options.journalPath,
68748
+ ...this.options.durable === undefined ? {} : { durable: this.options.durable },
68749
+ workers: this.options.workers,
68750
+ leaseMs: this.leaseMs,
68751
+ startupTimeoutMs: this.options.startupTimeoutMs ?? defaultStartupTimeoutMs(this.options.workers),
68752
+ ...this.options.admission === undefined ? {} : { admission: this.options.admission },
68753
+ ...this.options.maxLoadPerCpu === undefined ? {} : { stageStartMaxLoadPerCpu: this.options.maxLoadPerCpu },
68754
+ ...workerEntry === undefined ? {} : { workerEntry }
68755
+ },
68756
+ ...this.desiredControl === undefined ? {} : { control: this.desiredControl }
68757
+ });
68758
+ })();
68759
+ return this.connecting;
68304
68760
  }
68305
68761
  async poolJournal() {
68306
68762
  const journal = this.backend.journal;
@@ -68349,6 +68805,7 @@ class LocalWorkerPool {
68349
68805
  async start(root, promptNodes) {
68350
68806
  if (this.closed)
68351
68807
  throw new Error("Local worker pool is closed");
68808
+ await this.connectPool();
68352
68809
  if (!this.queue || !this.lease || this.lease.runId !== root)
68353
68810
  throw new Error("Local worker pool requires root preparation");
68354
68811
  this.sources = await Promise.all(this.options.definitions.map(async (binding) => {
@@ -68631,6 +69088,11 @@ class LocalWorkerPool {
68631
69088
  ...message === undefined ? {} : { message }
68632
69089
  });
68633
69090
  }
69091
+ async sessionCommandOnPooledStage(watch, command) {
69092
+ if (this.closed || this.coordinatorLost !== undefined)
69093
+ return { ok: false, refusal: "StaleAttempt" };
69094
+ return await this.command({ kind: "stage-session", watch, command });
69095
+ }
68634
69096
  stageTransport(onEnded) {
68635
69097
  return {
68636
69098
  watch: (ref, listener) => this.watchPooledStage(ref, (message) => {
@@ -68641,7 +69103,8 @@ class LocalWorkerPool {
68641
69103
  unwatch: (watch) => this.unwatchPooledStage(watch),
68642
69104
  deliver: (delivery) => this.deliverToPooledStage(delivery),
68643
69105
  pause: (watch) => this.pausePooledStage(watch),
68644
- resume: (watch, message) => this.resumePooledStage(watch, message)
69106
+ resume: (watch, message) => this.resumePooledStage(watch, message),
69107
+ session: (watch, command) => this.sessionCommandOnPooledStage(watch, command)
68645
69108
  };
68646
69109
  }
68647
69110
  async acquirePooledStage(runId, stageId) {
@@ -69304,6 +69767,9 @@ var init_local_pool = __esm(() => {
69304
69767
  completeInTurns = createTurnSlicer(2, COMPLETION_TURN_BUDGET_MS);
69305
69768
  });
69306
69769
 
69770
+ // dist/builtin/workflows/src/workers/public-options.ts
69771
+ var DEFAULT_POOLED_STAGE_START_MAX_LOAD_PER_CPU = 3;
69772
+
69307
69773
  // dist/builtin/workflows/src/workers/public-pool.ts
69308
69774
  import { resolve as resolve15 } from "node:path";
69309
69775
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
@@ -69354,6 +69820,7 @@ async function activatePublicPool(backend, options) {
69354
69820
  ...options.localWorkers,
69355
69821
  startupTimeoutMs: undefined,
69356
69822
  leaseMs: undefined,
69823
+ maxLoadPerCpu: undefined,
69357
69824
  definitions: options.localWorkers.definitions.map((source) => ({ ...source, path: resolve15(cwd, source.path) }))
69358
69825
  };
69359
69826
  const admission = options.admission && {
@@ -69397,6 +69864,7 @@ async function activatePublicPool(backend, options) {
69397
69864
  admission: resources.admission ?? {},
69398
69865
  startupTimeoutMs: options.localWorkers?.startupTimeoutMs,
69399
69866
  leaseMs: options.localWorkers?.leaseMs,
69867
+ maxLoadPerCpu: options.localWorkers?.maxLoadPerCpu ?? DEFAULT_POOLED_STAGE_START_MAX_LOAD_PER_CPU,
69400
69868
  ...options.stageControlRegistry === undefined || options[hostWorkerBindings] ? {} : { stageControlRegistry: options.stageControlRegistry }
69401
69869
  });
69402
69870
  try {
@@ -75834,7 +76302,7 @@ class StageSessionController {
75834
76302
  return this.modelRoute ? this.checkedSession(await ready) : ready;
75835
76303
  }
75836
76304
  async ensureSessionFromId(sessionId, consumer = "prompt") {
75837
- const joinsLiveGeneration = !this.sessionShutdownPromise && (this.sessionPromise !== undefined || this.session !== undefined);
76305
+ const joinsLiveGeneration = !this.sessionShutdownPromise && (this.sessionPromise !== undefined || this.session !== undefined || this.activeCreation !== undefined || this.ownedCreationPromise !== undefined);
75838
76306
  if (!this.sessionShutdownPromise && !joinsLiveGeneration)
75839
76307
  this.reattachSessionId = sessionId;
75840
76308
  const session = await this.ensureSession(consumer);