@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
@@ -2050,7 +2050,7 @@ class JournalDurableBackend {
2050
2050
  timer;
2051
2051
  state = "open";
2052
2052
  closing;
2053
- renewing;
2053
+ renewing = new Map;
2054
2054
  mutationContext = new AsyncLocalStorage;
2055
2055
  processExitCleanup;
2056
2056
  unregisterExecutionStorages = registerExecutionStorages(this.executionContext, this.mutationContext);
@@ -2771,27 +2771,21 @@ class JournalDurableBackend {
2771
2771
  yield this.enqueue(runId, () => this.deleteOwned(runId));
2772
2772
  }
2773
2773
  renewLeases() {
2774
- if (this.renewing !== undefined)
2775
- return this.renewing;
2776
2774
  if (this.state !== "open")
2777
2775
  return Promise.resolve();
2778
- this.renewing = this.renewOwnedLeases().finally(() => {
2779
- this.renewing = undefined;
2780
- });
2781
- return this.renewing;
2782
- }
2783
- async renewOwnedLeases() {
2784
2776
  for (const [runId, lease] of this.leases) {
2785
- if (this.state !== "open")
2786
- return;
2787
- try {
2788
- await this.serialize(runId, () => this.renewLease(runId, lease), false);
2789
- } catch (error) {
2790
- if (error instanceof Error)
2791
- this.lost.set(runId, error);
2777
+ if (this.renewing.has(runId))
2778
+ continue;
2779
+ const renewal = this.serialize(runId, () => this.renewLease(runId, lease), false).catch((error) => {
2780
+ this.lost.set(runId, error);
2792
2781
  this.dropLease(runId);
2793
- }
2782
+ }).finally(() => {
2783
+ if (this.renewing.get(runId) === renewal)
2784
+ this.renewing.delete(runId);
2785
+ });
2786
+ this.renewing.set(runId, renewal);
2794
2787
  }
2788
+ return Promise.all(this.renewing.values()).then(() => {});
2795
2789
  }
2796
2790
  *renewLease(runId, lease) {
2797
2791
  if (this.leases.get(runId) !== lease || this.lost.has(runId))
@@ -2799,9 +2793,10 @@ class JournalDurableBackend {
2799
2793
  try {
2800
2794
  yield* invoke(() => this.journal.renew(lease));
2801
2795
  } catch (error) {
2796
+ if (!(error instanceof StaleLeaseError))
2797
+ return;
2802
2798
  if (this.leases.get(runId) === lease) {
2803
- if (error instanceof Error)
2804
- this.lost.set(runId, yield* this.mapOwnership(error, runId));
2799
+ this.lost.set(runId, yield* this.mapOwnership(error, runId));
2805
2800
  this.dropLease(runId);
2806
2801
  }
2807
2802
  }
@@ -2895,7 +2890,7 @@ class JournalDurableBackend {
2895
2890
  }
2896
2891
  async drainAndClose() {
2897
2892
  try {
2898
- await this.renewing;
2893
+ await Promise.all(this.renewing.values());
2899
2894
  while (this.pending.size > 0)
2900
2895
  await Promise.all(this.pending.values());
2901
2896
  for (const runId of this.leases.keys())
@@ -5102,6 +5097,75 @@ function redactSensitiveText(value2) {
5102
5097
  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]");
5103
5098
  }
5104
5099
 
5100
+ // dist/builtin/workflows/src/shared/workflow-infrastructure-error.ts
5101
+ function isInfrastructureError(value2) {
5102
+ if (value2 instanceof WorkflowInfrastructureError)
5103
+ return true;
5104
+ if (typeof value2 !== "object" || value2 === null)
5105
+ return false;
5106
+ const candidate = value2;
5107
+ return candidate.name === WORKFLOW_INFRASTRUCTURE_ERROR_NAME && candidate.retryable === true && typeof candidate.code === "string";
5108
+ }
5109
+ function isRetryableWorkflowError(error) {
5110
+ let current = error;
5111
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined && current !== null; depth++) {
5112
+ if (isInfrastructureError(current))
5113
+ return true;
5114
+ current = current instanceof Error ? current.cause : undefined;
5115
+ }
5116
+ return false;
5117
+ }
5118
+ function workflowInfrastructureFault(message) {
5119
+ return FAULT_PATTERNS.find(([pattern]) => pattern.test(message))?.[1];
5120
+ }
5121
+ function asWorkflowInfrastructureError(error) {
5122
+ if (isInfrastructureError(error))
5123
+ return error;
5124
+ if (!(error instanceof Error))
5125
+ return;
5126
+ const code = workflowInfrastructureFault(error.message);
5127
+ return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
5128
+ }
5129
+ function failedOnInfrastructureFault(result) {
5130
+ if (result.status !== "failed")
5131
+ return false;
5132
+ const names = (value2) => typeof value2 === "string" && (WORKFLOW_INFRASTRUCTURE_FAULTS.has(value2) || workflowInfrastructureFault(value2) !== undefined);
5133
+ const failure = result.failure;
5134
+ if (names(failure?.code) || names(result.error))
5135
+ return true;
5136
+ return Array.isArray(result.stages) && result.stages.some((stage) => stage?.status === "failed" && (names(stage.failureCode) || names(stage.error)));
5137
+ }
5138
+ var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", WORKFLOW_INFRASTRUCTURE_FAULTS, MAX_CAUSE_DEPTH = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
5139
+ var init_workflow_infrastructure_error = __esm(() => {
5140
+ WORKFLOW_INFRASTRUCTURE_FAULTS = new Set([
5141
+ "worker_lost",
5142
+ "worker_cleanup_unconfirmed",
5143
+ "worker_retry_limit",
5144
+ "worker_startup_failed",
5145
+ "stale_work_lease",
5146
+ "intercom_unavailable",
5147
+ "storage_unavailable"
5148
+ ]);
5149
+ FAULT_PATTERNS = [
5150
+ [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
5151
+ [/Local worker retry limit/, "worker_retry_limit"],
5152
+ [/Local worker (?:startup timed out|exited before readiness)/, "worker_startup_failed"],
5153
+ [/Worker exited before delivery acknowledgement|Local worker pool coordinator exited/, "worker_lost"],
5154
+ [/Stale work lease|Stale resource admission attempt/, "stale_work_lease"],
5155
+ [/Intercom runtime no longer active/, "intercom_unavailable"],
5156
+ [/Storage unavailable/, "storage_unavailable"]
5157
+ ];
5158
+ WorkflowInfrastructureError = class WorkflowInfrastructureError extends Error {
5159
+ name = WORKFLOW_INFRASTRUCTURE_ERROR_NAME;
5160
+ retryable = true;
5161
+ code;
5162
+ constructor(code, message, options) {
5163
+ super(message, options);
5164
+ this.code = code;
5165
+ }
5166
+ };
5167
+ });
5168
+
5105
5169
  // dist/builtin/workflows/src/shared/workflow-failures-decisions.ts
5106
5170
  function authDecision(code) {
5107
5171
  if (code === "invalid_api_key") {
@@ -5190,6 +5254,16 @@ function cancelledDecision() {
5190
5254
  disposition: "terminal_killed"
5191
5255
  };
5192
5256
  }
5257
+ function infrastructureFaultDecision(code) {
5258
+ return {
5259
+ kind: "unknown",
5260
+ code,
5261
+ retryable: true,
5262
+ resumable: true,
5263
+ recoverability: "recoverable",
5264
+ disposition: "terminal_failed"
5265
+ };
5266
+ }
5193
5267
  function unknownDecision() {
5194
5268
  return {
5195
5269
  kind: "unknown",
@@ -5217,6 +5291,8 @@ function strongDecisionFromNormalizedCode(normalized, retryAfterMs) {
5217
5291
  disposition: "active_blocked"
5218
5292
  };
5219
5293
  }
5294
+ if (WORKFLOW_INFRASTRUCTURE_FAULTS.has(normalized))
5295
+ return infrastructureFaultDecision(normalized);
5220
5296
  if (CANCELLED_CODES.has(normalized))
5221
5297
  return cancelledDecision();
5222
5298
  if (INVALID_API_KEY_CODES.has(normalized))
@@ -5329,6 +5405,9 @@ function classificationFromNormalizedCode(normalized, retryAfterMs, source, mess
5329
5405
  return weak !== undefined ? { weak: classificationForDecision(weak, source, message, "weak_signal") } : {};
5330
5406
  }
5331
5407
  function fallbackDecisionFromMessage(message, name) {
5408
+ const fault = workflowInfrastructureFault(message);
5409
+ if (fault !== undefined)
5410
+ return infrastructureFaultDecision(fault);
5332
5411
  const tokens = tokenize(message);
5333
5412
  const decision = decisionFromMessageTokens(tokens, name);
5334
5413
  const providerAuthDecision = classifyFallbackProviderAuthMessage(message, tokens);
@@ -5343,6 +5422,7 @@ function fallbackDecisionFromMessage(message, name) {
5343
5422
  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;
5344
5423
  var init_workflow_failures_decisions = __esm(() => {
5345
5424
  init_workflow_failures_contract();
5425
+ init_workflow_infrastructure_error();
5346
5426
  INVALID_API_KEY_CODES = new Set([
5347
5427
  "401",
5348
5428
  "invalid_api_key",
@@ -5530,6 +5610,13 @@ function decisionOfRecordedCode(code, retryAfterMs) {
5530
5610
  case "cancelled":
5531
5611
  return cancelledDecision();
5532
5612
  case "pending_stage_delivery_failed":
5613
+ case "worker_lost":
5614
+ case "worker_cleanup_unconfirmed":
5615
+ case "worker_retry_limit":
5616
+ case "worker_startup_failed":
5617
+ case "stale_work_lease":
5618
+ case "intercom_unavailable":
5619
+ case "storage_unavailable":
5533
5620
  return strongDecisionFromNormalizedCode(code, retryAfterMs);
5534
5621
  case "unknown":
5535
5622
  return;
@@ -16906,7 +16993,7 @@ class SqlResourceAdmission {
16906
16993
  for (const value2 of Object.values(limits ?? {}))
16907
16994
  if (!Number.isSafeInteger(value2) || value2 < 1)
16908
16995
  throw new Error("Invalid resource limit");
16909
- for (const value2 of Object.values(options.host ?? {}))
16996
+ for (const value2 of [...Object.values(options.host ?? {}), options.stageStartMaxLoadPerCpu ?? 0])
16910
16997
  if (!Number.isFinite(value2) || value2 < 0)
16911
16998
  throw new Error("Invalid host admission threshold");
16912
16999
  const admission = new SqlResourceAdmission(storage, {
@@ -16924,6 +17011,8 @@ class SqlResourceAdmission {
16924
17011
  for (const column of ["owner", "attempt", "process_identity"])
16925
17012
  if (!columns.some((entry) => entry.name === column))
16926
17013
  await tx.exec(`ALTER TABLE ${admission.permits} ADD COLUMN ${column} TEXT`);
17014
+ if (!columns.some((entry) => entry.name === "granted_at"))
17015
+ await tx.exec(`ALTER TABLE ${admission.permits} ADD COLUMN granted_at INTEGER`);
16927
17016
  await tx.exec(`CREATE INDEX IF NOT EXISTS ${admission.permits}_owner ON ${admission.permits}(owner, state);
16928
17017
  CREATE INDEX IF NOT EXISTS ${admission.permits}_state ON ${admission.permits}(state)`);
16929
17018
  if (!(await tx.all(`PRAGMA table_info(${admission.owners})`)).some((entry) => entry.name === "boot"))
@@ -16953,14 +17042,24 @@ class SqlResourceAdmission {
16953
17042
  await admission.reconcile();
16954
17043
  return admission;
16955
17044
  }
17045
+ sampleHost() {
17046
+ return this.options.sampleHost?.() ?? {
17047
+ freeMemoryBytes: freemem(),
17048
+ load: process.platform === "win32" ? undefined : loadavg()[0] / cpus().length
17049
+ };
17050
+ }
17051
+ stageStartPressure(kind, delegated) {
17052
+ const threshold = this.options.stageStartMaxLoadPerCpu;
17053
+ if (kind !== "session" || delegated === true || threshold === undefined || threshold === 0)
17054
+ return false;
17055
+ const load = this.sampleHost().load;
17056
+ return load !== undefined && load > threshold;
17057
+ }
16956
17058
  hostReason() {
16957
17059
  const host = this.options.host;
16958
17060
  if (!host)
16959
17061
  return;
16960
- const sample = this.options.sampleHost?.() ?? {
16961
- freeMemoryBytes: freemem(),
16962
- load: process.platform === "win32" ? undefined : loadavg()[0] / cpus().length
16963
- };
17062
+ const sample = this.sampleHost();
16964
17063
  if (host.minFreeMemoryBytes !== undefined) {
16965
17064
  if (sample.freeMemoryBytes === undefined)
16966
17065
  return "host memory unsupported";
@@ -16974,21 +17073,32 @@ class SqlResourceAdmission {
16974
17073
  return "host load";
16975
17074
  }
16976
17075
  }
16977
- async assertCurrentAttempt(tx) {
17076
+ async attemptStanding(tx) {
16978
17077
  const { root, attempt } = this.options;
16979
17078
  if (!attempt)
16980
- return;
16981
- 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
17079
+ return "current";
17080
+ const work = await tx.get(`SELECT (w.expires_at < ${NOW_MS} OR l.renewed_at + p.timeout < ${NOW_MS}) AS late
17081
+ FROM ${this.storage.tables.runs}_work w JOIN ${this.storage.tables.leases} l ON l.run_id = w.root
16982
17082
  JOIN ${this.storage.tables.runs}_pool p ON p.root = w.root JOIN ${this.storage.tables.runs} r ON r.run_id = w.root
16983
17083
  WHERE w.id = ? AND w.root = ? AND w.generation = ? AND w.epoch = ? AND w.state = 'leased' AND r.status IN ('running', 'paused')
16984
- AND w.expires_at >= CAST(unixepoch('subsec') * 1000 AS INTEGER)
16985
- AND l.epoch = w.epoch AND l.renewed_at + p.timeout >= CAST(unixepoch('subsec') * 1000 AS INTEGER)`, attempt.id, root, attempt.generation, attempt.epoch);
17084
+ AND l.epoch = w.epoch`, attempt.id, root, attempt.generation, attempt.epoch);
16986
17085
  if (!work)
17086
+ return "stale";
17087
+ return Number(work.late) === 1 ? "late" : "current";
17088
+ }
17089
+ async assertCurrentAttempt(tx) {
17090
+ const standing = await this.attemptStanding(tx);
17091
+ if (standing === "stale")
16987
17092
  throw new Error("Stale resource admission attempt");
17093
+ return standing === "current";
16988
17094
  }
16989
17095
  async validate(signal) {
16990
- signal?.throwIfAborted();
16991
- await this.storage.transaction((tx) => this.assertCurrentAttempt(tx));
17096
+ for (;; ) {
17097
+ signal?.throwIfAborted();
17098
+ if (await this.storage.transaction((tx) => this.assertCurrentAttempt(tx)))
17099
+ return;
17100
+ await delay(LATE_ATTEMPT_POLL_MS, undefined, { signal });
17101
+ }
16992
17102
  }
16993
17103
  async acquire(kind, signal, request) {
16994
17104
  signal?.throwIfAborted();
@@ -17022,11 +17132,19 @@ class SqlResourceAdmission {
17022
17132
  this.nextReconciliation = performance.now() + 1000;
17023
17133
  }
17024
17134
  const hostReason = this.hostReason();
17135
+ const pressure = this.stageStartPressure(kind, request?.delegated);
17025
17136
  const granted = await this.storage.transaction(async (tx) => {
17026
17137
  await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
17027
17138
  const { root } = this.options;
17028
- await this.assertCurrentAttempt(tx);
17139
+ if (!await this.assertCurrentAttempt(tx)) {
17140
+ const waiting = await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE operation = ? AND state = 'waiting'`, LATE_ATTEMPT_REASON, operation);
17141
+ if (waiting.changes !== 1)
17142
+ throw new Error("Resource admission holder was stopped");
17143
+ return false;
17144
+ }
17029
17145
  let reason = hostReason;
17146
+ 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)))
17147
+ reason ??= STAGE_START_PRESSURE_REASON;
17030
17148
  const controls = `${this.storage.tables.runs}_pool_control`;
17031
17149
  if (await tx.get("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", controls)) {
17032
17150
  const control = await tx.get(`SELECT state FROM ${controls} WHERE root = ?`, root);
@@ -17041,13 +17159,14 @@ class SqlResourceAdmission {
17041
17159
  const rootClause = scope === "user" ? "" : "AND root = ?";
17042
17160
  const args = scope === "user" ? [kind] : [kind, root];
17043
17161
  const count = await tx.get(`SELECT COUNT(*) AS count FROM ${this.permits} WHERE kind = ? ${rootClause} AND state = 'granted'`, ...args);
17044
- 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);
17162
+ 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);
17045
17163
  if (Number(count?.count) >= limit || older)
17046
17164
  reason ??= `${scope} ${kind}`;
17047
17165
  }
17048
17166
  if (reason && request?.delegated)
17049
17167
  throw new Error(`Delegated session cannot be admitted without capacity headroom: ${reason}`);
17050
- const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ? WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, operation);
17168
+ const stageStart = kind === "session" && request?.delegated !== true;
17169
+ 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);
17051
17170
  if (updated.changes !== 1)
17052
17171
  throw new Error("Resource admission holder was stopped");
17053
17172
  return !reason;
@@ -17207,7 +17326,7 @@ async function reapProcessGroups(journal, root, scope) {
17207
17326
  return { survivors: [], unprobeable: [] };
17208
17327
  return settleProcessGroups(storage, root, scope, true);
17209
17328
  }
17210
- 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)";
17329
+ 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";
17211
17330
  var init_resource_admission = __esm(() => {
17212
17331
  init_internal();
17213
17332
  init_sql_run_journal();
@@ -59780,14 +59899,23 @@ class SqlPoolQueue {
59780
59899
  }
59781
59900
  await tx.run(`UPDATE ${this.meta} SET version = ?`, POOL_SCHEMA_VERSION);
59782
59901
  }
59902
+ leaseRow(tx, lease) {
59903
+ 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);
59904
+ }
59783
59905
  async authority(tx, lease, timeout) {
59784
- 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);
59906
+ const row = await this.leaseRow(tx, lease);
59785
59907
  if (row?.timeout === null && timeout === undefined)
59786
59908
  throw new Error("Pool not initialized");
59787
59909
  if (!row || row.epoch !== lease.epoch || row.holder_id !== lease.holderId || isWorkflowLeaseExpired(row.renewed_at, row.timeout ?? timeout ?? 0, row.now))
59788
59910
  throw new StaleLeaseError(lease.runId, lease.epoch, Number(row?.epoch ?? 0));
59789
59911
  return row;
59790
59912
  }
59913
+ async rootLease(lease, timeout) {
59914
+ const row = await this.storage.read((executor) => this.leaseRow(executor, lease));
59915
+ if (!row || row.epoch !== lease.epoch || row.holder_id !== lease.holderId)
59916
+ return "lost";
59917
+ return isWorkflowLeaseExpired(row.renewed_at, row.timeout ?? timeout, row.now) ? "late" : "current";
59918
+ }
59791
59919
  async initialize(lease, coordinatorTimeoutMs) {
59792
59920
  positive(coordinatorTimeoutMs);
59793
59921
  lease = snapshotLease(lease);
@@ -59799,6 +59927,9 @@ class SqlPoolQueue {
59799
59927
  throw new Error("Pool configuration is immutable");
59800
59928
  });
59801
59929
  }
59930
+ async initialized(root) {
59931
+ return await this.storage.read((executor) => executor.get(`SELECT 1 AS held FROM ${this.roots} WHERE root = ?`, root)) !== undefined;
59932
+ }
59802
59933
  async control(lease) {
59803
59934
  return this.transaction(async (tx) => {
59804
59935
  await this.authority(tx, lease);
@@ -60193,6 +60324,21 @@ class SqlPoolQueue {
60193
60324
  return row ? JSON.parse(String(row.result)) : undefined;
60194
60325
  });
60195
60326
  }
60327
+ async reopenCompleted(lease, id, rerun) {
60328
+ lease = snapshotLease(lease);
60329
+ return this.transaction(async (tx) => {
60330
+ await this.authority(tx, lease);
60331
+ const row = await tx.get(`SELECT * FROM ${this.work} WHERE id = ? AND root = ?`, id, lease.runId);
60332
+ if (row?.state !== "completed" || row.epoch >= lease.epoch)
60333
+ return;
60334
+ const stored = await tx.get(`SELECT result FROM ${this.work}_results WHERE work = ?`, id);
60335
+ if (!stored || !rerun(JSON.parse(String(stored.result))))
60336
+ return;
60337
+ await tx.run(`DELETE FROM ${this.work}_results WHERE work = ?`, id);
60338
+ await tx.run(`UPDATE ${this.work} SET state = 'ready', worker = NULL, expires_at = 0, epoch = ? WHERE id = ?`, lease.epoch, id);
60339
+ return fromRow({ ...row, state: "ready", worker: null, expires_at: 0, epoch: lease.epoch });
60340
+ });
60341
+ }
60196
60342
  async expire(lease, token) {
60197
60343
  lease = snapshotLease(lease);
60198
60344
  token = { ...token };
@@ -60314,7 +60460,7 @@ function reportsClosedSession(error) {
60314
60460
  }
60315
60461
  function isSessionClosedError(error) {
60316
60462
  let current = error;
60317
- for (let depth = 0;depth < MAX_CAUSE_DEPTH && current !== undefined; depth++) {
60463
+ for (let depth = 0;depth < MAX_CAUSE_DEPTH2 && current !== undefined; depth++) {
60318
60464
  try {
60319
60465
  if (reportsClosedSession(current))
60320
60466
  return true;
@@ -60325,7 +60471,7 @@ function isSessionClosedError(error) {
60325
60471
  }
60326
60472
  return false;
60327
60473
  }
60328
- var MAX_CAUSE_DEPTH = 8;
60474
+ var MAX_CAUSE_DEPTH2 = 8;
60329
60475
  var init_session_closed_error = () => {};
60330
60476
 
60331
60477
  // dist/builtin/workflows/src/shared/external-run-routes.ts
@@ -64649,57 +64795,6 @@ var init_sdk_intercom = __esm(() => {
64649
64795
  owners2 = new WeakMap;
64650
64796
  });
64651
64797
 
64652
- // dist/builtin/workflows/src/shared/workflow-infrastructure-error.ts
64653
- function isInfrastructureError(value2) {
64654
- if (value2 instanceof WorkflowInfrastructureError)
64655
- return true;
64656
- if (typeof value2 !== "object" || value2 === null)
64657
- return false;
64658
- const candidate = value2;
64659
- return candidate.name === WORKFLOW_INFRASTRUCTURE_ERROR_NAME && candidate.retryable === true && typeof candidate.code === "string";
64660
- }
64661
- function isRetryableWorkflowError(error) {
64662
- let current = error;
64663
- for (let depth = 0;depth < MAX_CAUSE_DEPTH2 && current !== undefined && current !== null; depth++) {
64664
- if (isInfrastructureError(current))
64665
- return true;
64666
- current = current instanceof Error ? current.cause : undefined;
64667
- }
64668
- return false;
64669
- }
64670
- function workflowInfrastructureFault(message) {
64671
- return FAULT_PATTERNS.find(([pattern]) => pattern.test(message))?.[1];
64672
- }
64673
- function asWorkflowInfrastructureError(error) {
64674
- if (isInfrastructureError(error))
64675
- return error;
64676
- if (!(error instanceof Error))
64677
- return;
64678
- const code = workflowInfrastructureFault(error.message);
64679
- return code === undefined ? undefined : new WorkflowInfrastructureError(code, error.message, { cause: error });
64680
- }
64681
- var WORKFLOW_INFRASTRUCTURE_ERROR_NAME = "WorkflowInfrastructureError", MAX_CAUSE_DEPTH2 = 8, FAULT_PATTERNS, WorkflowInfrastructureError;
64682
- var init_workflow_infrastructure_error = __esm(() => {
64683
- FAULT_PATTERNS = [
64684
- [/Local worker (?:process )?cleanup (?:was not confirmed|unconfirmed)/, "worker_cleanup_unconfirmed"],
64685
- [/Local worker retry limit/, "worker_retry_limit"],
64686
- [/Local worker (?:startup timed out|exited before readiness)/, "worker_startup_failed"],
64687
- [/Worker exited before delivery acknowledgement|Local worker pool coordinator exited/, "worker_lost"],
64688
- [/Stale work lease/, "stale_work_lease"],
64689
- [/Intercom runtime no longer active/, "intercom_unavailable"],
64690
- [/Storage unavailable/, "storage_unavailable"]
64691
- ];
64692
- WorkflowInfrastructureError = class WorkflowInfrastructureError extends Error {
64693
- name = WORKFLOW_INFRASTRUCTURE_ERROR_NAME;
64694
- retryable = true;
64695
- code;
64696
- constructor(code, message, options) {
64697
- super(message, options);
64698
- this.code = code;
64699
- }
64700
- };
64701
- });
64702
-
64703
64798
  // dist/builtin/workflows/src/workers/coordinator-protocol.ts
64704
64799
  var POOL_COORDINATOR_ROLE = "pool-coordinator", POOL_SUMMARY_INTERVAL_MS = 250;
64705
64800
 
@@ -64983,6 +65078,16 @@ class RateLimitObservationOutbox {
64983
65078
  }
64984
65079
  var RATE_LIMIT_SHARE_INTERVAL_MS = 250;
64985
65080
 
65081
+ // dist/builtin/workflows/src/workers/stage-session-commands.ts
65082
+ function runsWhilePaused(command) {
65083
+ if (command.kind === "abort-bash" || command.kind === "abort-compaction")
65084
+ return true;
65085
+ if (command.kind !== "task")
65086
+ return false;
65087
+ const action = command.action.kind;
65088
+ return action === "cancel" || action === "output" || action === "detail" || action === "transcript";
65089
+ }
65090
+
64986
65091
  // dist/builtin/workflows/src/workers/stage-watch-coalesce.ts
64987
65092
  function streamedDelta(event) {
64988
65093
  if (event.type !== "message_update")
@@ -65309,6 +65414,7 @@ class PoolCoordinator {
65309
65414
  closing;
65310
65415
  replacements = 0;
65311
65416
  rootLeaseLost = false;
65417
+ lateRoot;
65312
65418
  attemptBase = new Map;
65313
65419
  constructor(port) {
65314
65420
  this.port = port;
@@ -65322,6 +65428,47 @@ class PoolCoordinator {
65322
65428
  await this.journal?.close().catch(() => {});
65323
65429
  await this.durable?.close().catch(() => {});
65324
65430
  }
65431
+ async whileRootLate(queue, action, resume) {
65432
+ for (let waited = false;; ) {
65433
+ try {
65434
+ if (waited)
65435
+ await resume?.();
65436
+ return await action();
65437
+ } catch (error) {
65438
+ if (!(error instanceof Error) || !isRootLeaseLost(error) || !await this.rootRenewal(queue))
65439
+ throw error;
65440
+ waited = true;
65441
+ }
65442
+ }
65443
+ }
65444
+ rootRenewal(queue) {
65445
+ this.lateRoot ??= this.awaitRootRenewal(queue).finally(() => {
65446
+ this.lateRoot = undefined;
65447
+ });
65448
+ return this.lateRoot;
65449
+ }
65450
+ async awaitRootRenewal(queue) {
65451
+ const lease = this.lease;
65452
+ const since = performance.now();
65453
+ let late = false;
65454
+ try {
65455
+ for (;; ) {
65456
+ if (this.closed)
65457
+ return false;
65458
+ const lost = await this.remote?.fresh(lease).then(() => false, () => true);
65459
+ if (lost)
65460
+ return false;
65461
+ const standing = await queue.rootLease(lease, COORDINATOR_LEASE_MS);
65462
+ if (standing !== "late")
65463
+ return standing === "current";
65464
+ late = true;
65465
+ await delay2(LATE_ROOT_POLL_MS);
65466
+ }
65467
+ } finally {
65468
+ if (late)
65469
+ this.stalledMs += performance.now() - since;
65470
+ }
65471
+ }
65325
65472
  receive(message) {
65326
65473
  if (message.kind === "request-reply") {
65327
65474
  const pending = this.requests.get(message.id);
@@ -65390,6 +65537,8 @@ class PoolCoordinator {
65390
65537
  return this.stageAnswer(command.runId, command.stageId, command.answer);
65391
65538
  case "stage-pause":
65392
65539
  return this.stageControl(command.watch, { op: "pause" });
65540
+ case "stage-session":
65541
+ return this.stageSession(command.watch, command.command);
65393
65542
  case "stage-resume":
65394
65543
  return this.stageControl(command.watch, {
65395
65544
  op: "resume",
@@ -65419,8 +65568,8 @@ class PoolCoordinator {
65419
65568
  this.usageTotal = await this.usageLedger.total(root);
65420
65569
  this.lease = lease;
65421
65570
  const queue = await openSqlPoolQueue(this.journal, this.remote);
65422
- await queue.initialize(lease, COORDINATOR_LEASE_MS);
65423
- await queue.drainOutbox(lease);
65571
+ await this.whileRootLate(queue, () => queue.initialize(lease, COORDINATOR_LEASE_MS));
65572
+ await this.whileRootLate(queue, () => queue.drainOutbox(lease));
65424
65573
  if (this.remote !== undefined) {
65425
65574
  const remote = this.remote;
65426
65575
  this.mirrorTimer = setInterval(() => void remote.fresh(lease, 0).catch(() => {}), MIRROR_INTERVAL_MS).unref();
@@ -65429,10 +65578,10 @@ class PoolCoordinator {
65429
65578
  for (const work of await queue.inspect(root))
65430
65579
  if (work.state === "leased" && work.epoch < lease.epoch && (await unconfirmedProcessGroups(this.journal, root, { attempt: work })).length > 0)
65431
65580
  retained.push(work);
65432
- await queue.releasePreviousIncarnations(lease, retained.map((work) => work.id));
65581
+ await this.whileRootLate(queue, () => queue.releasePreviousIncarnations(lease, retained.map((work) => work.id)));
65433
65582
  for (;; ) {
65434
65583
  const intended = this.desiredControl ?? "running";
65435
- await queue.setControl(lease, intended);
65584
+ await this.whileRootLate(queue, () => queue.setControl(lease, intended));
65436
65585
  if (intended === (this.desiredControl ?? "running"))
65437
65586
  break;
65438
65587
  }
@@ -65468,7 +65617,7 @@ class PoolCoordinator {
65468
65617
  return this.retainedTransition(async () => {
65469
65618
  const record = this.retained.get(work.id);
65470
65619
  this.retained.delete(work.id);
65471
- await this.queue.releaseRetained(this.lease, work, record?.enqueued === true).catch((error) => {
65620
+ await this.whileRootLate(this.queue, () => this.queue.releaseRetained(this.lease, work, record?.enqueued === true)).catch((error) => {
65472
65621
  if (error.message !== STALE_WORK_LEASE)
65473
65622
  throw error;
65474
65623
  });
@@ -65482,20 +65631,29 @@ class PoolCoordinator {
65482
65631
  async control(state) {
65483
65632
  this.desiredControl = state;
65484
65633
  if (!this.queue || !this.lease)
65485
- return;
65486
- const control = await this.queue.setControl(this.lease, state);
65487
- const deadline = Date.now() + CONTROL_ACKNOWLEDGEMENT_TIMEOUT_MS;
65488
- while ([...this.readyWorkers].some((worker) => (this.controlAcks.get(worker) ?? -1) < control.revision)) {
65634
+ return 0;
65635
+ const queue = this.queue;
65636
+ const lease = this.lease;
65637
+ const control = await this.whileRootLate(queue, () => queue.setControl(lease, state));
65638
+ const unacknowledged = () => [...this.readyWorkers].filter((worker) => (this.controlAcks.get(worker) ?? -1) < control.revision).length;
65639
+ let pending = unacknowledged();
65640
+ let progressAt = performance.now();
65641
+ let stalledAt = this.stalledMs;
65642
+ while (pending > 0) {
65489
65643
  if (this.desiredControl !== state)
65490
- return;
65491
- if (Date.now() >= deadline) {
65492
- if (state === "quit")
65493
- break;
65494
- throw new Error("Worker control acknowledgement unresolved");
65495
- }
65644
+ return pending;
65496
65645
  await delay2(25);
65646
+ this.observeResponsiveness();
65647
+ const now = unacknowledged();
65648
+ if (now < pending) {
65649
+ pending = now;
65650
+ progressAt = performance.now();
65651
+ stalledAt = this.stalledMs;
65652
+ } else if (performance.now() - progressAt - (this.stalledMs - stalledAt) >= CONTROL_ACKNOWLEDGEMENT_STALL_MS)
65653
+ break;
65497
65654
  }
65498
65655
  this.flush();
65656
+ return pending;
65499
65657
  }
65500
65658
  async start(sources, cwd, publish, rootRun) {
65501
65659
  if (this.closed)
@@ -65523,7 +65681,11 @@ class PoolCoordinator {
65523
65681
  return;
65524
65682
  });
65525
65683
  this.recoveryTimer = setInterval(() => {
65526
- this.recoverExpired().catch((error) => this.fail(error));
65684
+ this.recoverExpired().catch(async (error) => {
65685
+ if (this.queue && isRootLeaseLost(error) && await this.rootRenewal(this.queue).catch(() => false))
65686
+ return;
65687
+ this.fail(error);
65688
+ });
65527
65689
  }, RECOVERY_INTERVAL_MS).unref();
65528
65690
  const workers = Array.from({ length: this.options.workers }, () => this.spawn());
65529
65691
  try {
@@ -65540,7 +65702,9 @@ class PoolCoordinator {
65540
65702
  const queue = this.queue;
65541
65703
  const lease = this.lease;
65542
65704
  const work = await this.retainedTransition(async () => {
65543
- const enqueued = await queue.enqueue(lease, command.descriptor, command.boundary);
65705
+ let enqueued = await this.whileRootLate(queue, () => queue.enqueue(lease, command.descriptor, command.boundary));
65706
+ if (enqueued.state === "completed" && enqueued.epoch < lease.epoch)
65707
+ enqueued = await this.whileRootLate(queue, () => queue.reopenCompleted(lease, enqueued.id, failedOnInfrastructureFault)) ?? enqueued;
65544
65708
  const retained = this.retained.get(enqueued.id);
65545
65709
  if (retained)
65546
65710
  retained.enqueued = true;
@@ -65553,7 +65717,9 @@ class PoolCoordinator {
65553
65717
  }
65554
65718
  async transport(request, payload) {
65555
65719
  const target = this.deliveryRequests.get(request);
65556
- if (target === undefined || !this.incarnations.has(target.slot.worker))
65720
+ if (target === undefined)
65721
+ throw new Error("Pooled stage delivery request already answered");
65722
+ if (!this.incarnations.has(target.slot.worker))
65557
65723
  throw new Error("Worker exited before delivery acknowledgement");
65558
65724
  const { slot, workerRequest } = target;
65559
65725
  await new Promise((resolve, reject) => {
@@ -65781,6 +65947,34 @@ class PoolCoordinator {
65781
65947
  return { ok: false, refusal: "StaleAttempt" };
65782
65948
  return { ok: true, ...result.action === undefined ? {} : { action: result.action } };
65783
65949
  }
65950
+ async stageSession(id, command) {
65951
+ const watch = this.watches.get(id);
65952
+ if (watch === undefined || !watch.open)
65953
+ return {
65954
+ ok: false,
65955
+ refusal: this.endedWatches.get(id) === "stage_finished" ? "StageNotLive" : "StaleAttempt"
65956
+ };
65957
+ const { slot, attempt } = watch;
65958
+ if (slot.work?.id !== attempt.workId || slot.work.generation !== attempt.generation)
65959
+ return { ok: false, refusal: "StaleAttempt" };
65960
+ if (this.desiredControl !== undefined && this.desiredControl !== "running" && !runsWhilePaused(command))
65961
+ return { ok: false, refusal: "PoolPausedOrQuitting" };
65962
+ const result = await this.stageCommand(slot, { attempt, op: "session", watch: id, command });
65963
+ if (result === undefined)
65964
+ return { ok: false, refusal: "StaleAttempt" };
65965
+ if (result.error !== undefined)
65966
+ throw new Error(result.error);
65967
+ const refusal = result.refusal;
65968
+ if (refusal === "StageNotLive" || refusal === "PoolPausedOrQuitting")
65969
+ return { ok: false, refusal };
65970
+ if (refusal !== undefined)
65971
+ return { ok: false, refusal: "StaleAttempt" };
65972
+ return {
65973
+ ok: true,
65974
+ ...result.bash === undefined ? {} : { bash: result.bash },
65975
+ ...result.task === undefined ? {} : { task: result.task }
65976
+ };
65977
+ }
65784
65978
  stageCommand(slot, command) {
65785
65979
  if (!this.incarnations.has(slot.worker)) {
65786
65980
  tracePooledWatch("coordinator.command.no-worker", { op: command.op, worker: slot.worker });
@@ -65896,7 +66090,8 @@ class PoolCoordinator {
65896
66090
  root,
65897
66091
  definitions: this.sources,
65898
66092
  admission: options.admission,
65899
- ...this.promptNodes ? { promptNodes: true } : {}
66093
+ ...this.promptNodes ? { promptNodes: true } : {},
66094
+ ...options.stageStartMaxLoadPerCpu === undefined ? {} : { stageStartMaxLoadPerCpu: options.stageStartMaxLoadPerCpu }
65900
66095
  })
65901
66096
  ], {
65902
66097
  stdio: ["ignore", "ignore", "pipe", "ipc"],
@@ -66017,7 +66212,10 @@ class PoolCoordinator {
66017
66212
  if (this.closed)
66018
66213
  return;
66019
66214
  const exhausted = work !== undefined && this.attempts(work) >= MAX_CHILD_ATTEMPTS;
66020
- const settled = work !== undefined && (exhausted ? await this.queue.failExited(this.lease, work) : await this.queue.releaseExited(this.lease, work));
66215
+ const queue = this.queue;
66216
+ const lease = this.lease;
66217
+ const attempt = work;
66218
+ const settled = attempt !== undefined && await this.whileRootLate(queue, () => exhausted ? queue.failExited(lease, attempt) : queue.releaseExited(lease, attempt));
66021
66219
  const superseded = work !== undefined && !settled && await this.superseded(work);
66022
66220
  if (work !== undefined && exhausted && !superseded)
66023
66221
  this.failWork(work, diagnosed(childRetryLimit(work, "lost its worker")));
@@ -66079,21 +66277,40 @@ class PoolCoordinator {
66079
66277
  await this.starting;
66080
66278
  const queue = this.queue;
66081
66279
  const lease = this.lease;
66280
+ const renewAttempt = async () => {
66281
+ const attempt = slot.work;
66282
+ if (attempt === undefined)
66283
+ return;
66284
+ const renewed = await queue.extend(lease, attempt, options.leaseMs);
66285
+ if (slot.work === attempt)
66286
+ slot.work = renewed;
66287
+ };
66288
+ const current = async (action) => {
66289
+ try {
66290
+ return await action();
66291
+ } catch (error) {
66292
+ if (!(error instanceof Error) || error.message !== STALE_WORK_LEASE || slot.work === undefined)
66293
+ throw error;
66294
+ await renewAttempt();
66295
+ return action();
66296
+ }
66297
+ };
66298
+ const leased = (action) => this.whileRootLate(queue, () => current(action), renewAttempt);
66082
66299
  if (message.kind === "control")
66083
- return queue.control(lease);
66300
+ return leased(() => queue.control(lease));
66084
66301
  if (message.kind === "fenced") {
66085
66302
  const fenced = slot.work;
66086
66303
  slot.work = undefined;
66087
66304
  if (fenced && !await this.superseded(fenced)) {
66088
66305
  if (this.attempts(fenced) < MAX_CHILD_ATTEMPTS)
66089
- await queue.releaseExited(lease, fenced);
66090
- else if (await queue.failExited(lease, fenced))
66306
+ await leased(() => queue.releaseExited(lease, fenced));
66307
+ else if (await leased(() => queue.failExited(lease, fenced)))
66091
66308
  this.failWork(fenced, new Error(childRetryLimit(fenced, "was refused by the root as stale on every attempt")));
66092
66309
  }
66093
66310
  return;
66094
66311
  }
66095
66312
  if (message.kind === "controlled") {
66096
- const control = await queue.control(lease);
66313
+ const control = await leased(() => queue.control(lease));
66097
66314
  if (message.revision > control.revision)
66098
66315
  throw new Error("Unknown control revision");
66099
66316
  this.controlAcks.set(worker, Math.max(this.controlAcks.get(worker) ?? -1, message.revision));
@@ -66104,21 +66321,21 @@ class PoolCoordinator {
66104
66321
  throw new Error("Worker already holds an attempt");
66105
66322
  if (this.budgetBlocked)
66106
66323
  return;
66107
- await this.recoverExpired();
66108
- slot.runs.clear();
66109
- slot.work = await queue.claim(lease, worker, this.sources.map((source) => ({
66110
- definition: `${source.path}#${source.exportName}`,
66111
- digest: source.digest
66112
- })), options.leaseMs);
66324
+ slot.work = await leased(async () => {
66325
+ await this.recoverExpired();
66326
+ slot.runs.clear();
66327
+ return queue.claim(lease, worker, this.sources.map((source) => ({
66328
+ definition: `${source.path}#${source.exportName}`,
66329
+ digest: source.digest
66330
+ })), options.leaseMs);
66331
+ });
66113
66332
  return slot.work;
66114
66333
  }
66115
- let work = slot.work;
66116
- if (!work)
66334
+ const held = slot.work;
66335
+ if (!held)
66117
66336
  throw new Error("Worker has no active attempt");
66118
- if (message.kind === "renew" || work.expiresAt - Date.now() < options.leaseMs / 2) {
66119
- work = await queue.extend(lease, work, options.leaseMs);
66120
- slot.work = work;
66121
- }
66337
+ const work = message.kind === "renew" || held.expiresAt - Date.now() < options.leaseMs / 2 ? await leased(() => queue.extend(lease, held, options.leaseMs)) : held;
66338
+ slot.work = work;
66122
66339
  if (message.kind === "activity") {
66123
66340
  for (const run of message.runs)
66124
66341
  slot.runs.add(run.id);
@@ -66178,24 +66395,24 @@ class PoolCoordinator {
66178
66395
  if (message.kind === "renew")
66179
66396
  return;
66180
66397
  if (message.kind === "commit") {
66181
- const receipt = await queue.commit(lease, work, message.operation, message.mutation);
66398
+ const receipt = await leased(() => queue.commit(lease, work, message.operation, message.mutation));
66182
66399
  if (message.mutation.kind !== "session-append")
66183
66400
  this.noteUsage(await this.usageLedger.total(root));
66184
66401
  return receipt;
66185
66402
  }
66186
66403
  if (message.kind === "usage") {
66187
- this.noteUsage(await this.usageLedger.report(lease, work, message.reports));
66404
+ this.noteUsage(await leased(() => this.usageLedger.report(lease, work, message.reports)));
66188
66405
  return;
66189
66406
  }
66190
66407
  if (message.kind === "budget") {
66191
- await queue.assertCurrent(lease, work);
66408
+ await leased(() => queue.assertCurrent(lease, work));
66192
66409
  if (this.budgetBlocked)
66193
66410
  throw new Error("Root workflow budget exhausted");
66194
- return queue.control(lease);
66411
+ return leased(() => queue.control(lease));
66195
66412
  }
66196
66413
  if (message.kind !== "complete")
66197
66414
  throw new Error("Invalid worker operation");
66198
- await queue.complete(lease, work, message.result);
66415
+ await leased(() => queue.complete(lease, work, message.result));
66199
66416
  this.endStageWatches((watch) => watch.slot === slot, "stage_finished");
66200
66417
  this.replacements = 0;
66201
66418
  slot.work = undefined;
@@ -66234,7 +66451,7 @@ class PoolCoordinator {
66234
66451
  if (this.recovering)
66235
66452
  return this.recovering;
66236
66453
  const root = this.root;
66237
- if (this.closed || root === undefined || !this.queue || performance.now() < this.nextRecoveryAt)
66454
+ if (this.closed || root === undefined || !this.queue || this.lateRoot !== undefined || performance.now() < this.nextRecoveryAt)
66238
66455
  return Promise.resolve();
66239
66456
  const queue = this.queue;
66240
66457
  const lease = this.lease;
@@ -66243,9 +66460,12 @@ class PoolCoordinator {
66243
66460
  await new Promise((resolve) => setImmediate(resolve));
66244
66461
  await this.admission?.reconcile();
66245
66462
  const expiredBefore = Date.now();
66246
- for (const work of await queue.inspect(root)) {
66247
- if (work.state !== "leased" || work.expiresAt >= expiredBefore)
66248
- continue;
66463
+ const expired = (await queue.inspect(root)).filter((work) => work.state === "leased" && work.expiresAt < expiredBefore);
66464
+ if (expired.length > 0 && await queue.rootLease(lease, COORDINATOR_LEASE_MS) === "late") {
66465
+ this.rootRenewal(queue).catch(() => {});
66466
+ return;
66467
+ }
66468
+ for (const work of expired) {
66249
66469
  if (this.quarantined.has(work.id))
66250
66470
  continue;
66251
66471
  if (this.held.has(work.id)) {
@@ -66387,7 +66607,7 @@ function runPoolCoordinator() {
66387
66607
  port.onClose(() => process.exit(0));
66388
66608
  return coordinator;
66389
66609
  }
66390
- 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;
66610
+ 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;
66391
66611
  var init_pool_coordinator = __esm(() => {
66392
66612
  init_internal();
66393
66613
  init_pool_remote_journal();
@@ -66401,12 +66621,14 @@ var init_pool_coordinator = __esm(() => {
66401
66621
  init_pending_stage_route_capability();
66402
66622
  init_pending_stage_route_readiness();
66403
66623
  init_store();
66624
+ init_workflow_infrastructure_error();
66404
66625
  init_pool_journal();
66405
66626
  init_pooled_watch_trace();
66406
66627
  init_resource_admission();
66407
66628
  init_stage_watch_coalesce();
66408
66629
  init_startup_timeout();
66409
66630
  COORDINATOR_LEASE_MS = 3 * STAGE_SESSION_HEARTBEAT_INTERVAL_MS;
66631
+ LATE_ROOT_POLL_MS = RECOVERY_INTERVAL_MS;
66410
66632
  STAGE_WATCH_BACKLOG_BYTES = 8 * 1024 * 1024;
66411
66633
  });
66412
66634
 
@@ -66436,6 +66658,26 @@ function isWorkflowPendingStageDeliveryFailure(error) {
66436
66658
  return true;
66437
66659
  return error?.code === "pending_stage_delivery_failed";
66438
66660
  }
66661
+ function untilDrainedOrStalled(drain) {
66662
+ let lastProgressAt = Date.now();
66663
+ const running = drain(() => {
66664
+ lastProgressAt = Date.now();
66665
+ });
66666
+ return new Promise((resolve, reject) => {
66667
+ let timer;
66668
+ const check = () => {
66669
+ const remaining = lastProgressAt + PENDING_STAGE_READY_STALL_MS - Date.now();
66670
+ if (remaining <= 0) {
66671
+ resolve();
66672
+ return;
66673
+ }
66674
+ timer = setTimeout(check, remaining);
66675
+ timer.unref?.();
66676
+ };
66677
+ running.then(resolve, reject).finally(() => clearTimeout(timer));
66678
+ check();
66679
+ });
66680
+ }
66439
66681
  function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageName, commit = (action) => action(), options = {}) {
66440
66682
  let resolveReady;
66441
66683
  let rejectReady;
@@ -66497,7 +66739,8 @@ function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageNa
66497
66739
  const boundedDrain = (drain, sessionId) => {
66498
66740
  const waiting = new Promise((resolve, reject) => {
66499
66741
  drain.then(resolve, reject);
66500
- watchStall(drain, sessionId, resolve);
66742
+ if (options.holdUntilDrained !== true)
66743
+ watchStall(drain, sessionId, resolve);
66501
66744
  });
66502
66745
  waiting.catch(() => {});
66503
66746
  return waiting;
@@ -66879,10 +67122,14 @@ class PoolStageHandoff {
66879
67122
  if (run.endedAt !== undefined || this.owners.get(run.id) !== work.id)
66880
67123
  continue;
66881
67124
  if (run.id === work.childRunId) {
66882
- const execution = run.stages.find((stage) => stage.workerExecution !== undefined)?.workerExecution;
67125
+ const executions = new Map(run.stages.map((stage) => [stage.id, stage.workerExecution]));
67126
+ const attempt = run.stages.flatMap((stage) => stage.workerExecution === undefined ? [] : [stage.workerExecution]).reduce((latest, execution) => latest === undefined || execution.renewedAt > latest.renewedAt ? execution : latest, undefined);
66883
67127
  this.store.recordRunStart({
66884
67128
  ...run,
66885
- stages: result.stages.map((stage) => execution === undefined ? stage : { ...stage, workerExecution: execution }),
67129
+ stages: result.stages.map((stage) => {
67130
+ const execution = executions.get(stage.id) ?? attempt;
67131
+ return execution === undefined ? stage : { ...stage, workerExecution: execution };
67132
+ }),
66886
67133
  toolNodes: result.toolNodes
66887
67134
  });
66888
67135
  this.store.recordRunEnd(run.id, result.status, result.result, result.error);
@@ -66936,7 +67183,7 @@ class PoolStageHandoff {
66936
67183
  pendingStageMessages: [
66937
67184
  ...known.get(run.id)?.pendingStageMessages ?? pendingStageMessagesForDurableRun(this.backend, run.id, root)
66938
67185
  ],
66939
- stages: withReconnectingStages(known.get(run.id), run, run.stages.map((stage) => ({ ...stage, workerExecution: execution })), work.generation)
67186
+ stages: withReconnectingStages(known.get(run.id), run, run.stages.map((stage) => ({ ...stage, workerExecution: endedUnder(known.get(run.id), stage, execution) })), work.generation)
66940
67187
  }));
66941
67188
  const changed = published.some((run) => activity(known.get(run.id)) !== activity(run));
66942
67189
  for (const run of published)
@@ -67067,7 +67314,7 @@ class PoolStageHandoff {
67067
67314
  throw new Error(STALE);
67068
67315
  if (attempt.sessions.get(key) !== sessionId)
67069
67316
  throw new Error(`Stage session ${sessionId} was replaced before it acknowledged delivery`);
67070
- }, action));
67317
+ }, action), { holdUntilDrained: true });
67071
67318
  attempt.deliveries.set(key, delivery);
67072
67319
  }
67073
67320
  return delivery;
@@ -67079,6 +67326,12 @@ function outside2(runId) {
67079
67326
  function isLive(stage) {
67080
67327
  return (stage.sessionId !== undefined || stage.sessionFile !== undefined) && stage.status !== "completed" && stage.status !== "failed" && stage.status !== "skipped";
67081
67328
  }
67329
+ function endedUnder(known, stage, execution) {
67330
+ if (!isTerminalStageStatus(stage.status))
67331
+ return execution;
67332
+ const previous = known?.stages.find((candidate) => candidate.id === stage.id);
67333
+ return previous !== undefined && isTerminalStageStatus(previous.status) && previous.workerExecution?.generation === execution.generation && previous.workerExecution.worker === execution.worker ? previous.workerExecution : execution;
67334
+ }
67082
67335
  function withReconnectingStages(previous, run, stages, generation) {
67083
67336
  if (previous === undefined || run.endedAt !== undefined || isTerminalRunStatus(run.status))
67084
67337
  return stages;
@@ -67215,6 +67468,116 @@ class StageToolExecutionBuffer {
67215
67468
  }
67216
67469
  }
67217
67470
 
67471
+ // dist/builtin/workflows/src/workers/pooled-stage-task-source.ts
67472
+ function failure2(error) {
67473
+ return { ok: false, error: { message: typeof error === "string" ? error : error.message } };
67474
+ }
67475
+
67476
+ class PooledStageTaskSource {
67477
+ run;
67478
+ state = EMPTY;
67479
+ records = [];
67480
+ backgroundRecords = [];
67481
+ settlements = new Map;
67482
+ activity = new Map;
67483
+ statuses = new Map;
67484
+ stdin = new Set;
67485
+ listeners = new Set;
67486
+ constructor(run) {
67487
+ this.run = run;
67488
+ }
67489
+ apply(state) {
67490
+ this.state = state;
67491
+ this.records = [...state.tasks];
67492
+ const background = new Set(state.background);
67493
+ this.backgroundRecords = this.records.filter((task) => background.has(task.ref.taskId));
67494
+ this.settlements = new Map(state.settlement.map(([id, sequence]) => [id, BigInt(sequence)]));
67495
+ this.activity = new Map(state.activity.map((entry) => [entry.id, entry]));
67496
+ this.statuses = new Map(state.statuses.map((entry) => [entry.id, new Map(entry.entries)]));
67497
+ this.stdin = new Set(state.stdin);
67498
+ for (const listener of [...this.listeners])
67499
+ listener();
67500
+ }
67501
+ get empty() {
67502
+ return this.state.tasks.length === 0;
67503
+ }
67504
+ get tasks() {
67505
+ return this.records;
67506
+ }
67507
+ get backgroundTasks() {
67508
+ return this.backgroundRecords;
67509
+ }
67510
+ get settlementOrder() {
67511
+ return this.settlements;
67512
+ }
67513
+ recentActivity(id) {
67514
+ return [...this.activity.get(id)?.entries ?? []];
67515
+ }
67516
+ activityOmitted(id) {
67517
+ return this.activity.get(id)?.omitted === true;
67518
+ }
67519
+ subscribe(listener) {
67520
+ this.listeners.add(listener);
67521
+ return () => {
67522
+ this.listeners.delete(listener);
67523
+ };
67524
+ }
67525
+ async foregroundTask(id) {
67526
+ const reply = await this.request(id, { kind: "foreground" });
67527
+ return reply.ok ? reply.value.kind === "foreground" ? reply.value.result : unexpected() : reply;
67528
+ }
67529
+ async cancelTask(id) {
67530
+ const reply = await this.request(id, { kind: "cancel" });
67531
+ if (!reply.ok)
67532
+ return reply;
67533
+ return reply.value.kind === "cancel" ? reply.value.result ?? { ok: true, value: undefined } : unexpected();
67534
+ }
67535
+ stdinAvailable(id) {
67536
+ return this.stdin.has(id);
67537
+ }
67538
+ async writeTaskInput(id, text) {
67539
+ const reply = await this.request(id, { kind: "input", text });
67540
+ return reply.ok ? reply.value.kind === "input" ? reply.value.result : unexpected() : reply;
67541
+ }
67542
+ async readTaskOutput(id, range) {
67543
+ const reply = await this.request(id, { kind: "output", start: range.start, maximumBytes: range.maximumBytes });
67544
+ return reply.ok ? reply.value.kind === "output" ? reply.value.result : unexpected() : reply;
67545
+ }
67546
+ async taskDetailText(id) {
67547
+ const reply = await this.request(id, { kind: "detail" });
67548
+ return reply.ok && reply.value.kind === "detail" ? reply.value.detail : {};
67549
+ }
67550
+ async readTaskTranscript(id, cursor) {
67551
+ const reply = await this.request(id, { kind: "transcript", ...cursor === undefined ? {} : { cursor } });
67552
+ return reply.ok ? reply.value.kind === "transcript" ? reply.value.result : unexpected() : reply;
67553
+ }
67554
+ followTaskTranscript(id) {
67555
+ return this.records.some((task) => task.ref.taskId === id && task.kind === "agent") ? { historyOnly: true } : undefined;
67556
+ }
67557
+ extensionStatuses(id) {
67558
+ return this.statuses.get(id) ?? NO_STATUSES;
67559
+ }
67560
+ watchExtensionStatuses(_id, listener) {
67561
+ return this.subscribe(listener);
67562
+ }
67563
+ async request(id, action) {
67564
+ try {
67565
+ const reply = await this.run(id, action);
67566
+ return reply === undefined ? failure2("Task unavailable") : { ok: true, value: reply };
67567
+ } catch (error) {
67568
+ return failure2(error instanceof Error ? error : String(error));
67569
+ }
67570
+ }
67571
+ }
67572
+ function unexpected() {
67573
+ return failure2("Unexpected reply from the local worker");
67574
+ }
67575
+ var EMPTY, NO_STATUSES;
67576
+ var init_pooled_stage_task_source = __esm(() => {
67577
+ EMPTY = { tasks: [], background: [], settlement: [], activity: [], stdin: [], statuses: [] };
67578
+ NO_STATUSES = new Map;
67579
+ });
67580
+
67218
67581
  // dist/builtin/workflows/src/workers/pooled-stage-handle.ts
67219
67582
  import { randomUUID as randomUUID17 } from "node:crypto";
67220
67583
 
@@ -67245,6 +67608,11 @@ class PooledStageHandle {
67245
67608
  undelivered;
67246
67609
  disposed = false;
67247
67610
  session;
67611
+ commands;
67612
+ sessionStarted = true;
67613
+ started = Promise.withResolvers();
67614
+ bashRuns = new Map;
67615
+ taskView = new PooledStageTaskSource(async (taskId, action) => (await this.sessionCommand({ kind: "task", taskId, action })).task);
67248
67616
  pumping;
67249
67617
  pumpAgain = false;
67250
67618
  watchGone = Promise.withResolvers();
@@ -67307,6 +67675,43 @@ class PooledStageHandle {
67307
67675
  streamingMessage() {
67308
67676
  return this.streaming;
67309
67677
  }
67678
+ commandCatalog() {
67679
+ return this.commands;
67680
+ }
67681
+ taskSource() {
67682
+ return this.taskView;
67683
+ }
67684
+ get isSessionStarting() {
67685
+ return !this.sessionStarted && this.ended === undefined;
67686
+ }
67687
+ get isBashRunning() {
67688
+ return this.bashRuns.size > 0;
67689
+ }
67690
+ async executeBash(command, onChunk, options) {
67691
+ const id = randomUUID17();
67692
+ this.bashRuns.set(id, onChunk);
67693
+ try {
67694
+ const result = await this.sessionCommand({
67695
+ kind: "bash",
67696
+ id,
67697
+ command,
67698
+ ...options?.excludeFromContext === undefined ? {} : { excludeFromContext: options.excludeFromContext }
67699
+ });
67700
+ return result.bash ?? { output: "", exitCode: undefined, cancelled: true, truncated: false };
67701
+ } finally {
67702
+ this.bashRuns.delete(id);
67703
+ }
67704
+ }
67705
+ abortBash() {
67706
+ for (const id of this.bashRuns.keys())
67707
+ this.sessionCommand({ kind: "abort-bash", id }).catch(() => {});
67708
+ }
67709
+ async compact() {
67710
+ await this.sessionCommand({ kind: "compact" });
67711
+ }
67712
+ abortCompaction() {
67713
+ this.sessionCommand({ kind: "abort-compaction" }).catch(() => {});
67714
+ }
67310
67715
  pendingToolExecutionEvents() {
67311
67716
  return this.toolExecutions.replayEvents();
67312
67717
  }
@@ -67454,6 +67859,17 @@ class PooledStageHandle {
67454
67859
  if (this.disposed)
67455
67860
  throw new Error(`Stage chat "${this.stageName}" is closed.`);
67456
67861
  }
67862
+ async sessionCommand(command) {
67863
+ this.throwIfClosed();
67864
+ if (this.ended !== undefined)
67865
+ throw new PooledStageDeliveryError("StageNotLive", this.stageName);
67866
+ if (this.transport.session === undefined)
67867
+ throw new Error(`Stage "${this.stageName}" runs on a local worker that cannot run this command.`);
67868
+ const result = await this.transport.session(this.watchId, command);
67869
+ if (!result.ok)
67870
+ throw new PooledStageDeliveryError(result.refusal, this.stageName);
67871
+ return result;
67872
+ }
67457
67873
  undeliveredCount() {
67458
67874
  if (this.undelivered !== undefined)
67459
67875
  return this.undelivered;
@@ -67519,6 +67935,16 @@ class PooledStageHandle {
67519
67935
  entry.waiter = undefined;
67520
67936
  }
67521
67937
  }
67938
+ markSessionStarted(started) {
67939
+ if (started) {
67940
+ this.sessionStarted = true;
67941
+ this.started.resolve();
67942
+ return;
67943
+ }
67944
+ if (this.sessionStarted)
67945
+ this.started = Promise.withResolvers();
67946
+ this.sessionStarted = false;
67947
+ }
67522
67948
  forget(op) {
67523
67949
  const index = this.outbound.findIndex((entry) => entry.op === op);
67524
67950
  if (index >= 0)
@@ -67575,10 +68001,17 @@ class PooledStageHandle {
67575
68001
  const gone = this.watchGone.promise.then(() => ({ ok: false, refusal: "StaleAttempt" }));
67576
68002
  const sending = this.sendEntry(entry, watch);
67577
68003
  let bound;
68004
+ let stopped = false;
67578
68005
  const unconfirmed = new Promise((resolve) => {
67579
- bound = setTimeout(() => resolve("unconfirmed"), this.admissionWaitMs);
68006
+ this.started.promise.then(() => {
68007
+ if (!stopped)
68008
+ bound = setTimeout(() => resolve("unconfirmed"), this.admissionWaitMs);
68009
+ });
68010
+ });
68011
+ const outcome = await Promise.race([sending, gone, unconfirmed]).finally(() => {
68012
+ stopped = true;
68013
+ clearTimeout(bound);
67580
68014
  });
67581
- const outcome = await Promise.race([sending, gone, unconfirmed]).finally(() => clearTimeout(bound));
67582
68015
  if (outcome === "unconfirmed") {
67583
68016
  tracePooledWatch("root.outbox.unconfirmed", { op: entry.op, watch, generation });
67584
68017
  entry.unconfirmedOn = generation;
@@ -67668,6 +68101,10 @@ class PooledStageHandle {
67668
68101
  }
67669
68102
  adopt(snapshot) {
67670
68103
  this.session = snapshot.sessionId;
68104
+ this.commands = snapshot.commands;
68105
+ if (!this.taskView.empty)
68106
+ this.taskView.apply(NO_TASKS);
68107
+ this.markSessionStarted(snapshot.sessionStarted !== false);
67671
68108
  this.settled = [...snapshot.messages];
67672
68109
  this.streaming = snapshot.streamingMessage;
67673
68110
  this.currentStatus = snapshot.status;
@@ -67688,6 +68125,25 @@ class PooledStageHandle {
67688
68125
  this.forget(event.op);
67689
68126
  return false;
67690
68127
  }
68128
+ if (event.type === STAGE_SESSION_STARTED_EVENT) {
68129
+ this.commands = event.commands;
68130
+ this.markSessionStarted(true);
68131
+ const queued = this.queuedUserMessages();
68132
+ const redraw = { type: "queue_update", steering: [...queued.steering], followUp: [...queued.followUp] };
68133
+ for (const listener of [...this.listeners])
68134
+ listener(redraw);
68135
+ return false;
68136
+ }
68137
+ if (event.type === STAGE_TASKS_EVENT) {
68138
+ this.taskView.apply(event.tasks);
68139
+ return false;
68140
+ }
68141
+ if (event.type === STAGE_MESSAGES_EVENT) {
68142
+ this.settled = [...event.messages];
68143
+ return false;
68144
+ }
68145
+ if (event.type === "bash_execution_update" && event.id !== undefined)
68146
+ this.bashRuns.get(event.id)?.(event.delta);
67691
68147
  this.applyEvent(event);
67692
68148
  const turnUnderway = TURN_EVENTS.has(event.type) || (event.type === "message_start" || event.type === "message_update") && event.message.role === "assistant";
67693
68149
  if (turnUnderway && this.awaitingTurn) {
@@ -67770,10 +68226,12 @@ async function reconnectPooledStageHandle(handle, transport, wanted = () => true
67770
68226
  await rebinding;
67771
68227
  return !handle.isReconnecting;
67772
68228
  }
67773
- var DELIVERY_RECEIPT_EVENT = "atomic_pooled_delivery_receipt", POOLED_ADMISSION_WAIT_MS = 15000, RECONNECTABLE, TURN_EVENTS, PooledStageDeliveryError;
68229
+ 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;
67774
68230
  var init_pooled_stage_handle = __esm(() => {
67775
68231
  init_stage_queued_user_messages();
68232
+ init_pooled_stage_task_source();
67776
68233
  init_pooled_watch_trace();
68234
+ NO_TASKS = { tasks: [], background: [], settlement: [], activity: [], stdin: [], statuses: [] };
67777
68235
  RECONNECTABLE = new Set(["attempt_fenced", "worker_exited", "resync"]);
67778
68236
  TURN_EVENTS = new Set(["agent_start", "turn_start", "tool_execution_start", "tool_execution_update"]);
67779
68237
  PooledStageDeliveryError = class PooledStageDeliveryError extends Error {
@@ -67833,6 +68291,9 @@ class RegisteredPooledStageHandle {
67833
68291
  pendingToolExecutionEvents() {
67834
68292
  return this.current()?.pendingToolExecutionEvents?.() ?? [];
67835
68293
  }
68294
+ commandCatalog() {
68295
+ return this.current()?.commandCatalog?.();
68296
+ }
67836
68297
  update(stage) {
67837
68298
  this.stage = stage;
67838
68299
  }
@@ -68131,6 +68592,7 @@ class LocalWorkerPool {
68131
68592
  rootSignature;
68132
68593
  completing = 0;
68133
68594
  rootFlush;
68595
+ connecting;
68134
68596
  starting;
68135
68597
  failure;
68136
68598
  closed = false;
@@ -68160,6 +68622,8 @@ class LocalWorkerPool {
68160
68622
  throw new Error("Invalid local worker startup timeout");
68161
68623
  if (options.leaseMs !== undefined && (!Number.isSafeInteger(options.leaseMs) || options.leaseMs < MINIMUM_WORK_LEASE_MS || options.leaseMs > MAX_TIMER_DELAY_MS))
68162
68624
  throw new Error(`Invalid local worker lease: must be an integer from ${MINIMUM_WORK_LEASE_MS} to ${MAX_TIMER_DELAY_MS} ms`);
68625
+ if (options.maxLoadPerCpu !== undefined && (!Number.isFinite(options.maxLoadPerCpu) || options.maxLoadPerCpu < 0))
68626
+ throw new Error("Invalid local worker load threshold: maxLoadPerCpu must be a number of at least 0");
68163
68627
  this.leaseMs = options.leaseMs ?? DEFAULT_WORK_LEASE_MS;
68164
68628
  this.releaseBinding = bindChildWorkerDispatcher(backend, this, options.rootId);
68165
68629
  if (options.admission && options.rootId === undefined)
@@ -68187,23 +68651,35 @@ class LocalWorkerPool {
68187
68651
  if (!state || state.holderId !== this.backend.holderId)
68188
68652
  throw new Error("Local worker pool requires the root owner");
68189
68653
  this.lease = { runId: root, holderId: state.holderId, epoch: state.epoch };
68190
- const workerEntry = this.options.workerEntry ?? (isBunBinary2 || this.options.coordinator === "in-process" ? defaultWorkerEntry() : undefined);
68191
- this.usageTotal = await this.command({
68192
- kind: "prepare",
68193
- root,
68194
- lease: this.lease,
68195
- options: {
68196
- journalPath: this.options.journalPath,
68197
- ...this.options.durable === undefined ? {} : { durable: this.options.durable },
68198
- workers: this.options.workers,
68199
- leaseMs: this.leaseMs,
68200
- startupTimeoutMs: this.options.startupTimeoutMs ?? defaultStartupTimeoutMs(this.options.workers),
68201
- ...this.options.admission === undefined ? {} : { admission: this.options.admission },
68202
- ...workerEntry === undefined ? {} : { workerEntry }
68203
- },
68204
- ...this.desiredControl === undefined ? {} : { control: this.desiredControl }
68205
- });
68206
68654
  this.queue ??= await openSqlPoolQueue(poolJournal, this.options.durable === undefined ? undefined : new PoolRemoteJournal(this.backend.journal, sqlPoolStorage(poolJournal)));
68655
+ if (await this.queue.initialized(root))
68656
+ await this.connectPool();
68657
+ }
68658
+ connectPool() {
68659
+ this.connecting ??= (async () => {
68660
+ const root = this.root;
68661
+ const lease = this.lease;
68662
+ if (root === undefined || lease === undefined)
68663
+ throw new Error("Local worker pool requires root preparation");
68664
+ const workerEntry = this.options.workerEntry ?? (isBunBinary2 || this.options.coordinator === "in-process" ? defaultWorkerEntry() : undefined);
68665
+ this.usageTotal = await this.command({
68666
+ kind: "prepare",
68667
+ root,
68668
+ lease,
68669
+ options: {
68670
+ journalPath: this.options.journalPath,
68671
+ ...this.options.durable === undefined ? {} : { durable: this.options.durable },
68672
+ workers: this.options.workers,
68673
+ leaseMs: this.leaseMs,
68674
+ startupTimeoutMs: this.options.startupTimeoutMs ?? defaultStartupTimeoutMs(this.options.workers),
68675
+ ...this.options.admission === undefined ? {} : { admission: this.options.admission },
68676
+ ...this.options.maxLoadPerCpu === undefined ? {} : { stageStartMaxLoadPerCpu: this.options.maxLoadPerCpu },
68677
+ ...workerEntry === undefined ? {} : { workerEntry }
68678
+ },
68679
+ ...this.desiredControl === undefined ? {} : { control: this.desiredControl }
68680
+ });
68681
+ })();
68682
+ return this.connecting;
68207
68683
  }
68208
68684
  async poolJournal() {
68209
68685
  const journal = this.backend.journal;
@@ -68252,6 +68728,7 @@ class LocalWorkerPool {
68252
68728
  async start(root, promptNodes) {
68253
68729
  if (this.closed)
68254
68730
  throw new Error("Local worker pool is closed");
68731
+ await this.connectPool();
68255
68732
  if (!this.queue || !this.lease || this.lease.runId !== root)
68256
68733
  throw new Error("Local worker pool requires root preparation");
68257
68734
  this.sources = await Promise.all(this.options.definitions.map(async (binding) => {
@@ -68534,6 +69011,11 @@ class LocalWorkerPool {
68534
69011
  ...message === undefined ? {} : { message }
68535
69012
  });
68536
69013
  }
69014
+ async sessionCommandOnPooledStage(watch, command) {
69015
+ if (this.closed || this.coordinatorLost !== undefined)
69016
+ return { ok: false, refusal: "StaleAttempt" };
69017
+ return await this.command({ kind: "stage-session", watch, command });
69018
+ }
68537
69019
  stageTransport(onEnded) {
68538
69020
  return {
68539
69021
  watch: (ref, listener) => this.watchPooledStage(ref, (message) => {
@@ -68544,7 +69026,8 @@ class LocalWorkerPool {
68544
69026
  unwatch: (watch) => this.unwatchPooledStage(watch),
68545
69027
  deliver: (delivery) => this.deliverToPooledStage(delivery),
68546
69028
  pause: (watch) => this.pausePooledStage(watch),
68547
- resume: (watch, message) => this.resumePooledStage(watch, message)
69029
+ resume: (watch, message) => this.resumePooledStage(watch, message),
69030
+ session: (watch, command) => this.sessionCommandOnPooledStage(watch, command)
68548
69031
  };
68549
69032
  }
68550
69033
  async acquirePooledStage(runId, stageId) {
@@ -69207,6 +69690,9 @@ var init_local_pool = __esm(() => {
69207
69690
  completeInTurns = createTurnSlicer(2, COMPLETION_TURN_BUDGET_MS);
69208
69691
  });
69209
69692
 
69693
+ // dist/builtin/workflows/src/workers/public-options.ts
69694
+ var DEFAULT_POOLED_STAGE_START_MAX_LOAD_PER_CPU = 3;
69695
+
69210
69696
  // dist/builtin/workflows/src/workers/public-pool.ts
69211
69697
  import { resolve as resolve14 } from "node:path";
69212
69698
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
@@ -69257,6 +69743,7 @@ async function activatePublicPool(backend, options) {
69257
69743
  ...options.localWorkers,
69258
69744
  startupTimeoutMs: undefined,
69259
69745
  leaseMs: undefined,
69746
+ maxLoadPerCpu: undefined,
69260
69747
  definitions: options.localWorkers.definitions.map((source) => ({ ...source, path: resolve14(cwd, source.path) }))
69261
69748
  };
69262
69749
  const admission = options.admission && {
@@ -69300,6 +69787,7 @@ async function activatePublicPool(backend, options) {
69300
69787
  admission: resources.admission ?? {},
69301
69788
  startupTimeoutMs: options.localWorkers?.startupTimeoutMs,
69302
69789
  leaseMs: options.localWorkers?.leaseMs,
69790
+ maxLoadPerCpu: options.localWorkers?.maxLoadPerCpu ?? DEFAULT_POOLED_STAGE_START_MAX_LOAD_PER_CPU,
69303
69791
  ...options.stageControlRegistry === undefined || options[hostWorkerBindings] ? {} : { stageControlRegistry: options.stageControlRegistry }
69304
69792
  });
69305
69793
  try {
@@ -75737,7 +76225,7 @@ class StageSessionController {
75737
76225
  return this.modelRoute ? this.checkedSession(await ready) : ready;
75738
76226
  }
75739
76227
  async ensureSessionFromId(sessionId, consumer = "prompt") {
75740
- const joinsLiveGeneration = !this.sessionShutdownPromise && (this.sessionPromise !== undefined || this.session !== undefined);
76228
+ const joinsLiveGeneration = !this.sessionShutdownPromise && (this.sessionPromise !== undefined || this.session !== undefined || this.activeCreation !== undefined || this.ownedCreationPromise !== undefined);
75741
76229
  if (!this.sessionShutdownPromise && !joinsLiveGeneration)
75742
76230
  this.reattachSessionId = sessionId;
75743
76231
  const session = await this.ensureSession(consumer);
@@ -79722,10 +80210,12 @@ init_run();
79722
80210
  init_run_tool_control_registry();
79723
80211
  init_workflow_tool_abort();
79724
80212
  init_wiring();
80213
+ init_pending_stage_delivery2();
79725
80214
  init_stage_control_registry();
79726
80215
  init_stage_queued_user_messages();
79727
80216
  init_stage_ui_broker();
79728
80217
  init_store();
80218
+ init_workflow_infrastructure_error();
79729
80219
  init_admission_binding();
79730
80220
  init_definitions();
79731
80221
  import { randomUUID as randomUUID24 } from "node:crypto";
@@ -79808,6 +80298,9 @@ function workerSessionStore(input) {
79808
80298
  // dist/builtin/workflows/src/workers/stage-watch.ts
79809
80299
  init_stage_queued_user_messages();
79810
80300
  init_pooled_watch_trace();
80301
+ import {
80302
+ getSessionCommandCatalog
80303
+ } from "@bastani/atomic";
79811
80304
 
79812
80305
  // dist/builtin/workflows/src/workers/stage-delivery-receipts.ts
79813
80306
  function queueLengths(event) {
@@ -79901,6 +80394,167 @@ class StageDeliveryReceipts {
79901
80394
 
79902
80395
  // dist/builtin/workflows/src/workers/stage-watch.ts
79903
80396
  init_stage_watch_coalesce();
80397
+
80398
+ // dist/builtin/workflows/src/workers/stage-watch-tasks.ts
80399
+ import {
80400
+ getOwnerTaskStore,
80401
+ ownerTaskViewSource,
80402
+ watchOwnerTaskStoreBinding
80403
+ } from "@bastani/atomic";
80404
+ var STAGE_TASKS_INTERVAL_MS = 200;
80405
+ function snapshotStageTasks(source) {
80406
+ const tasks = [...source.tasks];
80407
+ const background = source.backgroundTasks.map((task) => task.ref.taskId);
80408
+ return {
80409
+ tasks,
80410
+ background,
80411
+ settlement: [...source.settlementOrder].map(([id, sequence]) => [id, sequence.toString()]),
80412
+ activity: background.flatMap((id) => {
80413
+ const entries = source.recentActivity(id);
80414
+ const omitted = source.activityOmitted(id);
80415
+ return entries.length === 0 && !omitted ? [] : [{ id, entries, omitted }];
80416
+ }),
80417
+ stdin: tasks.flatMap((task) => task.execution.kind !== "settled" && source.stdinAvailable(task.ref.taskId) ? [task.ref.taskId] : []),
80418
+ statuses: tasks.flatMap((task) => {
80419
+ if (task.kind !== "agent")
80420
+ return [];
80421
+ const entries = [...source.extensionStatuses(task.ref.taskId)];
80422
+ return entries.length === 0 ? [] : [{ id: task.ref.taskId, entries }];
80423
+ })
80424
+ };
80425
+ }
80426
+ async function runStageTaskAction(source, taskId, action) {
80427
+ switch (action.kind) {
80428
+ case "foreground":
80429
+ return { kind: "foreground", result: await source.foregroundTask(taskId) };
80430
+ case "cancel":
80431
+ return { kind: "cancel", result: await source.cancelTask(taskId) };
80432
+ case "input":
80433
+ return { kind: "input", result: await source.writeTaskInput(taskId, action.text) };
80434
+ case "output":
80435
+ return {
80436
+ kind: "output",
80437
+ result: await source.readTaskOutput(taskId, { start: action.start, maximumBytes: action.maximumBytes })
80438
+ };
80439
+ case "detail":
80440
+ return { kind: "detail", detail: await source.taskDetailText(taskId) };
80441
+ case "transcript": {
80442
+ const result = await source.readTaskTranscript(taskId, action.cursor);
80443
+ const streaming = result.ok && action.cursor === undefined ? source.followTaskTranscript(taskId)?.session?.getStreamingMessage?.() : undefined;
80444
+ if (!result.ok || streaming === undefined || streaming.role !== "assistant")
80445
+ return { kind: "transcript", result };
80446
+ const partial = { ...streaming, content: streaming.content.filter((block) => block.type !== "thinking") };
80447
+ return {
80448
+ kind: "transcript",
80449
+ result: { ok: true, value: { ...result.value, messages: [...result.value.messages, partial] } }
80450
+ };
80451
+ }
80452
+ }
80453
+ }
80454
+ function stageTaskSource(handle) {
80455
+ const session = handle.agentSession;
80456
+ const store = session === undefined ? undefined : getOwnerTaskStore(session);
80457
+ return store === undefined ? undefined : ownerTaskViewSource(store);
80458
+ }
80459
+
80460
+ class WatchedStageTasks {
80461
+ handle;
80462
+ send;
80463
+ intervalMs;
80464
+ session;
80465
+ store;
80466
+ unbind;
80467
+ unsubscribe;
80468
+ statusWatches = new Map;
80469
+ transcriptWatches = new Map;
80470
+ timer;
80471
+ disposed = false;
80472
+ constructor(handle, send, intervalMs = STAGE_TASKS_INTERVAL_MS) {
80473
+ this.handle = handle;
80474
+ this.send = send;
80475
+ this.intervalMs = intervalMs;
80476
+ }
80477
+ sync() {
80478
+ if (this.disposed)
80479
+ return;
80480
+ const session = this.handle.agentSession;
80481
+ if (session === undefined || session === this.session)
80482
+ return;
80483
+ this.release();
80484
+ this.session = session;
80485
+ this.unbind = watchOwnerTaskStoreBinding(session, () => this.bindStore());
80486
+ this.bindStore();
80487
+ }
80488
+ flushPending() {
80489
+ if (this.timer === undefined)
80490
+ return;
80491
+ clearTimeout(this.timer);
80492
+ this.timer = undefined;
80493
+ this.flush();
80494
+ }
80495
+ dispose() {
80496
+ this.disposed = true;
80497
+ this.release();
80498
+ }
80499
+ bindStore() {
80500
+ const session = this.session;
80501
+ const store = session === undefined ? undefined : getOwnerTaskStore(session);
80502
+ if (this.disposed || store === undefined || store === this.store)
80503
+ return;
80504
+ this.unsubscribe?.();
80505
+ this.store = store;
80506
+ this.unsubscribe = store.subscribe(() => this.schedule());
80507
+ this.schedule();
80508
+ }
80509
+ schedule() {
80510
+ if (this.disposed || this.timer !== undefined)
80511
+ return;
80512
+ this.timer = setTimeout(() => {
80513
+ this.timer = undefined;
80514
+ this.flush();
80515
+ }, this.intervalMs);
80516
+ this.timer.unref?.();
80517
+ }
80518
+ flush() {
80519
+ const store = this.store;
80520
+ if (this.disposed || store === undefined)
80521
+ return;
80522
+ const source = ownerTaskViewSource(store);
80523
+ const agents = new Set(source.tasks.filter((task) => task.kind === "agent").map((task) => task.ref.taskId));
80524
+ for (const watches of [this.statusWatches, this.transcriptWatches])
80525
+ for (const [id, release] of watches)
80526
+ if (!agents.has(id)) {
80527
+ release();
80528
+ watches.delete(id);
80529
+ }
80530
+ for (const id of agents) {
80531
+ if (!this.statusWatches.has(id))
80532
+ this.statusWatches.set(id, source.watchExtensionStatuses(id, () => this.schedule()));
80533
+ if (!this.transcriptWatches.has(id)) {
80534
+ const session = source.followTaskTranscript(id)?.session;
80535
+ if (session?.subscribe !== undefined)
80536
+ this.transcriptWatches.set(id, session.subscribe(() => this.schedule()));
80537
+ }
80538
+ }
80539
+ this.send(snapshotStageTasks(source));
80540
+ }
80541
+ release() {
80542
+ clearTimeout(this.timer);
80543
+ this.timer = undefined;
80544
+ this.unbind?.();
80545
+ this.unbind = undefined;
80546
+ this.unsubscribe?.();
80547
+ this.unsubscribe = undefined;
80548
+ for (const release of [...this.statusWatches.values(), ...this.transcriptWatches.values()])
80549
+ release();
80550
+ this.statusWatches.clear();
80551
+ this.transcriptWatches.clear();
80552
+ this.store = undefined;
80553
+ this.session = undefined;
80554
+ }
80555
+ }
80556
+
80557
+ // dist/builtin/workflows/src/workers/stage-watch.ts
79904
80558
  async function admitStageUserMessage(handle, text, intent, expandPromptTemplates) {
79905
80559
  const options = {
79906
80560
  ...intent === "auto" ? {} : { deliverAs: intent },
@@ -79963,6 +80617,9 @@ async function admitStageUserMessage(handle, text, intent, expandPromptTemplates
79963
80617
  }
79964
80618
  var DELIVERY_OP_ENTRY = "atomic-pooled-delivery-op";
79965
80619
  var DELIVERY_RECEIPT_EVENT2 = "atomic_pooled_delivery_receipt";
80620
+ var STAGE_SESSION_STARTED_EVENT2 = "atomic_pooled_stage_session_started";
80621
+ var STAGE_MESSAGES_EVENT2 = "atomic_pooled_stage_messages";
80622
+ var STAGE_TASKS_EVENT2 = "atomic_pooled_stage_tasks";
79966
80623
  function turnUnderway(handle) {
79967
80624
  return handle.isStreaming || handle.agentSession?.isStreaming === true || handle.isTurnLoopBusy === true;
79968
80625
  }
@@ -80012,8 +80669,8 @@ class WorkerStageWatches {
80012
80669
  this.send({ kind: "stage-command-result", commandId: command.commandId, ...this.answer(command) });
80013
80670
  return;
80014
80671
  }
80015
- if (command.op === "deliver" || command.op === "pause" || command.op === "resume") {
80016
- const result = command.op === "deliver" ? this.deliver(command) : command.op === "pause" ? this.pause(command) : this.resume(command);
80672
+ if (command.op === "deliver" || command.op === "pause" || command.op === "resume" || command.op === "session") {
80673
+ const result = command.op === "deliver" ? this.deliver(command) : command.op === "pause" ? this.pause(command) : command.op === "session" ? this.session(command) : this.resume(command);
80017
80674
  result.then((outcome) => this.send({ kind: "stage-command-result", commandId: command.commandId, ...outcome }));
80018
80675
  return;
80019
80676
  }
@@ -80043,13 +80700,18 @@ class WorkerStageWatches {
80043
80700
  }
80044
80701
  this.close(command.watch);
80045
80702
  const batch = [];
80703
+ const started = handle.agentSession !== undefined;
80704
+ const commands = commandCatalog(handle);
80046
80705
  const watch = {
80047
80706
  handle,
80048
80707
  batch,
80049
80708
  seq: 0,
80709
+ startedSent: started,
80050
80710
  unsubscribe: handle.subscribe((event) => this.record(command.watch, event))
80051
80711
  };
80052
80712
  this.watches.set(command.watch, watch);
80713
+ watch.tasks = new WatchedStageTasks(handle, (tasks) => this.sendTasks(command.watch, tasks));
80714
+ watch.tasks.sync();
80053
80715
  tracePooledWatch("worker.watch.opened", {
80054
80716
  watch: command.watch,
80055
80717
  status: handle.status,
@@ -80065,6 +80727,8 @@ class WorkerStageWatches {
80065
80727
  commandId: command.commandId,
80066
80728
  snapshot: {
80067
80729
  ...snapshot(handle, watch.seq),
80730
+ sessionStarted: started,
80731
+ ...commands === undefined ? {} : { commands },
80068
80732
  deliveredOps: [...delivered],
80069
80733
  turnStarted: handle.isStreaming || this.options.turnStarted?.(handle) === true
80070
80734
  }
@@ -80080,17 +80744,17 @@ class WorkerStageWatches {
80080
80744
  }
80081
80745
  sweep() {
80082
80746
  for (const [id, watch] of this.watches) {
80747
+ this.sendStartedOnce(id, watch);
80748
+ watch.tasks?.sync();
80083
80749
  const current = this.registry.get(watch.handle.runId, watch.handle.stageId);
80084
80750
  if (current !== undefined && current !== watch.handle && !isTerminal(current)) {
80085
80751
  tracePooledWatch("worker.watch.handle-replaced", { watch: id });
80086
- this.flush(id);
80087
- this.close(id);
80752
+ this.finish(id);
80088
80753
  this.send({ kind: "stage-watch-ended", watch: id, reason: "resync" });
80089
80754
  continue;
80090
80755
  }
80091
80756
  if (isTerminal(watch.handle)) {
80092
- this.flush(id);
80093
- this.close(id);
80757
+ this.finish(id);
80094
80758
  this.send({ kind: "stage-watch-ended", watch: id, reason: "stage_finished" });
80095
80759
  }
80096
80760
  }
@@ -80100,10 +80764,13 @@ class WorkerStageWatches {
80100
80764
  for (const receipts of this.receipts.values())
80101
80765
  receipts.dispose();
80102
80766
  this.receipts.clear();
80103
- for (const id of [...this.watches.keys()]) {
80104
- this.flush(id);
80105
- this.close(id);
80106
- }
80767
+ for (const id of [...this.watches.keys()])
80768
+ this.finish(id);
80769
+ }
80770
+ finish(id) {
80771
+ this.watches.get(id)?.tasks?.flushPending();
80772
+ this.flush(id);
80773
+ this.close(id);
80107
80774
  }
80108
80775
  question(command) {
80109
80776
  const held = this.attempt();
@@ -80167,6 +80834,47 @@ class WorkerStageWatches {
80167
80834
  return isTerminal(watch.handle) ? { refusal: "StageNotLive" } : { error: error.message };
80168
80835
  }
80169
80836
  }
80837
+ async session(command) {
80838
+ const watch = this.airlock(command);
80839
+ if (typeof watch === "string")
80840
+ return { refusal: watch };
80841
+ const request = command.command;
80842
+ if (this.control() !== "running" && !runsWhilePaused(request))
80843
+ return { refusal: "PoolPausedOrQuitting" };
80844
+ const session = watch.handle.agentSession;
80845
+ if (session === undefined)
80846
+ return {
80847
+ error: `Stage "${watch.handle.stageName}" is still starting on its local worker; try again shortly.`
80848
+ };
80849
+ try {
80850
+ switch (request.kind) {
80851
+ case "compact":
80852
+ await session.compact();
80853
+ return {};
80854
+ case "abort-compaction":
80855
+ session.abortCompaction();
80856
+ return {};
80857
+ case "bash":
80858
+ return {
80859
+ bash: await session.executeBash(request.command, undefined, {
80860
+ id: request.id,
80861
+ ...request.excludeFromContext === undefined ? {} : { excludeFromContext: request.excludeFromContext }
80862
+ })
80863
+ };
80864
+ case "abort-bash":
80865
+ session.abortBash(request.id);
80866
+ return {};
80867
+ case "task": {
80868
+ const source = stageTaskSource(watch.handle);
80869
+ if (source === undefined)
80870
+ return { error: `Stage "${watch.handle.stageName}" has no background tasks.` };
80871
+ return { task: await runStageTaskAction(source, request.taskId, request.action) };
80872
+ }
80873
+ }
80874
+ } catch (error) {
80875
+ return isTerminal(watch.handle) ? { refusal: "StageNotLive" } : { error: error.message };
80876
+ }
80877
+ }
80170
80878
  async resume(command) {
80171
80879
  const watch = this.airlock(command);
80172
80880
  if (typeof watch === "string")
@@ -80248,9 +80956,38 @@ class WorkerStageWatches {
80248
80956
  const watch = this.watches.get(id);
80249
80957
  if (watch === undefined)
80250
80958
  return;
80959
+ this.sendStartedOnce(id, watch);
80960
+ watch.tasks?.sync();
80961
+ if (event.type === "compaction_end" && event.aborted !== true && event.result !== undefined)
80962
+ this.append(id, watch, {
80963
+ type: STAGE_MESSAGES_EVENT2,
80964
+ messages: [...watch.handle.messages]
80965
+ });
80966
+ this.append(id, watch, event);
80967
+ }
80968
+ append(id, watch, event) {
80251
80969
  appendCoalesced(watch.batch, { seq: ++watch.seq, event });
80252
80970
  watch.timer ??= setTimeout(() => this.flush(id), STAGE_EVENT_BATCH_INTERVAL_MS);
80253
80971
  }
80972
+ sendTasks(id, tasks) {
80973
+ const watch = this.watches.get(id);
80974
+ if (watch === undefined)
80975
+ return;
80976
+ const waiting = watch.batch.findIndex(({ event }) => event.type === STAGE_TASKS_EVENT2);
80977
+ if (waiting >= 0)
80978
+ watch.batch.splice(waiting, 1);
80979
+ this.append(id, watch, { type: STAGE_TASKS_EVENT2, tasks });
80980
+ }
80981
+ sendStartedOnce(id, watch) {
80982
+ if (watch.startedSent || watch.handle.agentSession === undefined)
80983
+ return;
80984
+ watch.startedSent = true;
80985
+ const commands = commandCatalog(watch.handle);
80986
+ this.append(id, watch, {
80987
+ type: STAGE_SESSION_STARTED_EVENT2,
80988
+ ...commands === undefined ? {} : { commands }
80989
+ });
80990
+ }
80254
80991
  flush(id) {
80255
80992
  const watch = this.watches.get(id);
80256
80993
  if (watch === undefined)
@@ -80269,6 +81006,7 @@ class WorkerStageWatches {
80269
81006
  this.watches.delete(id);
80270
81007
  clearTimeout(watch.timer);
80271
81008
  watch.unsubscribe();
81009
+ watch.tasks?.dispose();
80272
81010
  }
80273
81011
  }
80274
81012
  function deliveredOps(entries) {
@@ -80279,6 +81017,16 @@ function deliveredOps(entries) {
80279
81017
  return typeof op === "string" ? [op] : [];
80280
81018
  });
80281
81019
  }
81020
+ function commandCatalog(handle) {
81021
+ const session = handle.agentSession;
81022
+ if (session?.resourceLoader === undefined || session.settingsManager === undefined)
81023
+ return;
81024
+ try {
81025
+ return getSessionCommandCatalog(session);
81026
+ } catch {
81027
+ return;
81028
+ }
81029
+ }
80282
81030
  function snapshot(handle, seq) {
80283
81031
  const streamingMessage = handle.agentSession?.agent?.state.streamingMessage;
80284
81032
  return {
@@ -80416,7 +81164,7 @@ async function runWorker(start) {
80416
81164
  rootLeaseLost = true;
80417
81165
  attemptFenced = true;
80418
81166
  receiver?.reject(new WorkAttemptFencedError(reply.error));
80419
- } else if (reply.error && reply.fenced) {
81167
+ } else if (reply.error && (reply.fenced || workflowInfrastructureFault(reply.error) !== undefined)) {
80420
81168
  attemptFenced = true;
80421
81169
  receiver?.reject(new WorkAttemptFencedError(reply.error));
80422
81170
  } else if (reply.error)
@@ -80602,7 +81350,8 @@ async function runWorker(start) {
80602
81350
  ...start.admission,
80603
81351
  root: start.root,
80604
81352
  holder: work.worker,
80605
- attempt: work
81353
+ attempt: work,
81354
+ ...start.stageStartMaxLoadPerCpu === undefined ? {} : { stageStartMaxLoadPerCpu: start.stageStartMaxLoadPerCpu }
80606
81355
  }) : undefined;
80607
81356
  const baseAdapter = createAgentSessionAdapter({ cwd: String(work.execution.cwd) }, stageUiBroker);
80608
81357
  const adapter = {
@@ -80624,7 +81373,14 @@ async function runWorker(start) {
80624
81373
  routeCapability: capability,
80625
81374
  async deliverPending(deliver, recipient) {
80626
81375
  await publication;
80627
- await request({ kind: "delivery", ...identity, recipient }, deliver);
81376
+ await untilDrainedOrStalled((progress) => request({ kind: "delivery", ...identity, recipient }, async (from, message) => {
81377
+ progress();
81378
+ try {
81379
+ await deliver(from, message);
81380
+ } finally {
81381
+ progress();
81382
+ }
81383
+ }).then(() => {}));
80628
81384
  },
80629
81385
  async ready(sessionId) {
80630
81386
  await publication;
@@ -80719,6 +81475,8 @@ async function runWorker(start) {
80719
81475
  await request({ kind: "controlled", revision: finalControl.revision });
80720
81476
  return;
80721
81477
  }
81478
+ if (attemptFenced)
81479
+ throw new WorkAttemptFencedError("Work attempt fenced before completion");
80722
81480
  await request({ kind: "complete", result });
80723
81481
  } catch (error) {
80724
81482
  if (!attemptFenced)