intentdna 1.9.0 → 1.9.3

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 (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -0,0 +1,1002 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { canonicalizeJson } from "./canonical-json.js";
3
+ import { parseStoredExecutableRunPlan } from "./executable-run-plan.js";
4
+ import { CALLER_ATTESTATION_SCHEMA_VERSION } from "./run-contracts.js";
5
+ import { parseStoredRunBinding, } from "./run-binding.js";
6
+ import { executeWorkerAttempt, } from "./worker-executor.js";
7
+ export class PushDriverError extends Error {
8
+ code;
9
+ constructor(code, message, options) {
10
+ super(message, options);
11
+ this.name = "PushDriverError";
12
+ this.code = code;
13
+ }
14
+ }
15
+ export class PushDriverIndeterminateAttachError extends PushDriverError {
16
+ disposition = "retain_preparation";
17
+ constructor(attemptId, cause) {
18
+ super("attach_indeterminate", `attempt ${attemptId} attach disposition is indeterminate; execution authority preparation was retained`, { cause });
19
+ this.name = "PushDriverIndeterminateAttachError";
20
+ }
21
+ }
22
+ const DEFAULT_HEARTBEAT_INTERVAL_MS = 5_000;
23
+ const DEFAULT_WAIT_POLL_MS = 50;
24
+ const NO_ATTEMPT_ERROR = Symbol("no_attempt_error");
25
+ const DEFAULT_CAPABILITIES = {
26
+ worktree_isolation: true,
27
+ parallelism: true,
28
+ cancellation: false,
29
+ hook_identity: false,
30
+ caller_attestation: false,
31
+ human_interaction: false,
32
+ direct_structured_submission: true,
33
+ network_policy_enforcement: false,
34
+ };
35
+ const RECOVERABLE_WAIT_REASONS = new Set([
36
+ "attempt_active",
37
+ "dependencies_pending",
38
+ "retry_backoff",
39
+ "run_cancelling",
40
+ ]);
41
+ function defaultSleep(milliseconds) {
42
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
43
+ }
44
+ function requirePositiveInteger(name, value) {
45
+ if (!Number.isSafeInteger(value) || value <= 0) {
46
+ throw new PushDriverError("invalid_configuration", `${name} must be a positive safe integer`);
47
+ }
48
+ }
49
+ function requireNonEmpty(name, value) {
50
+ if (value.length === 0) {
51
+ throw new PushDriverError("invalid_service_response", `${name} must not be empty`);
52
+ }
53
+ }
54
+ function assertRequiredCapabilities(required, provided) {
55
+ for (const [name, needed] of Object.entries(required)) {
56
+ if (needed && provided[name] !== true) {
57
+ throw new PushDriverError("unsupported", `driver capability ${name} is required`);
58
+ }
59
+ }
60
+ }
61
+ function defaultWaitStrategy(waitPollMs) {
62
+ return ({ reason_code }) => RECOVERABLE_WAIT_REASONS.has(reason_code)
63
+ ? { action: "continue", delay_ms: waitPollMs }
64
+ : { action: "stop", reason: `unsupported wait reason: ${reason_code}` };
65
+ }
66
+ function validateRunPayloads(view) {
67
+ try {
68
+ const plan = parseStoredExecutableRunPlan(view.plan);
69
+ const binding = parseStoredRunBinding(view.run.run_binding);
70
+ if (view.run.plan_id !== plan.plan_id
71
+ || view.run.plan_digest !== plan.plan_digest
72
+ || binding.binding.plan_id !== plan.plan_id) {
73
+ throw new Error("run ledger, plan, and binding cross-references disagree");
74
+ }
75
+ return {
76
+ plan_snapshot: canonicalizeJson(plan),
77
+ binding_snapshot: canonicalizeJson(binding),
78
+ };
79
+ }
80
+ catch (error) {
81
+ throw new PushDriverError("invalid_service_response", "service returned an invalid canonical plan or run binding", { cause: error });
82
+ }
83
+ }
84
+ function pinRun(view) {
85
+ const snapshots = validateRunPayloads(view);
86
+ return {
87
+ run_id: view.run.run_id,
88
+ plan_id: view.plan.plan_id,
89
+ plan_digest: view.plan.plan_digest,
90
+ binding_id: view.run.run_binding.binding_id,
91
+ max_concurrency: view.run.run_binding.binding.max_concurrency ?? 1,
92
+ ...snapshots,
93
+ };
94
+ }
95
+ function assertPinnedRun(pinned, view) {
96
+ const snapshots = validateRunPayloads(view);
97
+ if (view.run.run_id !== pinned.run_id
98
+ || view.plan.plan_id !== pinned.plan_id
99
+ || view.plan.plan_digest !== pinned.plan_digest
100
+ || view.run.plan_id !== pinned.plan_id
101
+ || view.run.plan_digest !== pinned.plan_digest
102
+ || view.run.run_binding.binding_id !== pinned.binding_id
103
+ || view.run.run_binding.binding.plan_id !== pinned.plan_id
104
+ || snapshots.plan_snapshot !== pinned.plan_snapshot
105
+ || snapshots.binding_snapshot !== pinned.binding_snapshot) {
106
+ throw new PushDriverError("invalid_service_response", "service response changed the pinned run, plan, or binding identity");
107
+ }
108
+ return view;
109
+ }
110
+ function requireWorkerNode(view, stepId) {
111
+ const matches = view.plan.plan.nodes.filter((node) => node.node_id === stepId);
112
+ if (matches.length !== 1 || matches[0]?.kind !== "worker") {
113
+ throw new PushDriverError("invalid_service_response", `acquired step ${stepId} is not exactly one pinned worker node`);
114
+ }
115
+ return matches[0];
116
+ }
117
+ function assertAttemptView(lease, workerSessionId, attached) {
118
+ const packet = attached.packet;
119
+ if (attached.lease.run_id !== lease.run_id
120
+ || attached.lease.step_id !== lease.step_id
121
+ || attached.lease.activation_id !== lease.activation_id
122
+ || attached.lease.attempt_id !== lease.attempt_id
123
+ || attached.lease.attempt_number !== lease.attempt_number
124
+ || attached.lease.claim_epoch !== lease.claim_epoch
125
+ || attached.lease.owner_id !== lease.owner_id
126
+ || attached.attempt.attempt_id !== lease.attempt_id
127
+ || attached.attempt.activation_id !== lease.activation_id
128
+ || attached.attempt.step_id !== lease.step_id
129
+ || attached.attempt.phase !== "running"
130
+ || attached.attempt.executor?.kind !== "worker"
131
+ || attached.attempt.executor.worker_session_id !== workerSessionId
132
+ || attached.attempt.started_at === null
133
+ || packet === null
134
+ || packet.run_id !== lease.run_id
135
+ || packet.step_id !== lease.step_id
136
+ || packet.attempt_id !== lease.attempt_id
137
+ || packet.attempt_number !== lease.attempt_number
138
+ || packet.worker_session_id !== workerSessionId) {
139
+ throw new PushDriverError("invalid_service_response", `attach response does not match attempt ${lease.attempt_id}`);
140
+ }
141
+ return packet;
142
+ }
143
+ function outputContractByName(node) {
144
+ return new Map(node.packet_template.output_contract.map((contract) => [
145
+ contract.name,
146
+ contract,
147
+ ]));
148
+ }
149
+ function referenceArtifactKind(contract) {
150
+ if (contract.artifact_kind !== "file"
151
+ && contract.artifact_kind !== "directory"
152
+ && contract.artifact_kind !== "git_commit") {
153
+ throw new PushDriverError("unsupported_output_contract", `reference output ${contract.name} uses unsupported artifact kind ${contract.artifact_kind}`);
154
+ }
155
+ return contract.artifact_kind;
156
+ }
157
+ function mapProviderOutput(output, contract) {
158
+ if (output.kind !== contract.kind) {
159
+ throw new PushDriverError("invalid_provider_output", `provider output ${output.name} kind does not match its pinned contract`);
160
+ }
161
+ if (output.kind === "text") {
162
+ return { name: contract.name, kind: "text", text: output.text };
163
+ }
164
+ if (output.kind === "structured") {
165
+ if (output.schema_ref !== contract.schema_ref) {
166
+ throw new PushDriverError("invalid_provider_output", `provider output ${output.name} schema does not match its pinned contract`);
167
+ }
168
+ canonicalizeJson(output.value);
169
+ return {
170
+ name: contract.name,
171
+ kind: "structured",
172
+ value: output.value,
173
+ schema_ref: contract.schema_ref,
174
+ };
175
+ }
176
+ const artifactKind = referenceArtifactKind(contract);
177
+ if (artifactKind === "git_commit") {
178
+ if (output.reference.kind !== "artifact") {
179
+ throw new PushDriverError("invalid_provider_output", `git_commit output ${output.name} must use an artifact reference`);
180
+ }
181
+ return {
182
+ name: contract.name,
183
+ kind: "reference",
184
+ artifact_kind: artifactKind,
185
+ source_path: output.reference.value,
186
+ };
187
+ }
188
+ if (output.reference.kind !== artifactKind) {
189
+ throw new PushDriverError("invalid_provider_output", `provider output ${output.name} reference kind does not match ${artifactKind}`);
190
+ }
191
+ if (contract.relative_path_template === null) {
192
+ throw new PushDriverError("unsupported_output_contract", `reference output ${contract.name} has no pinned source path`);
193
+ }
194
+ if (output.reference.value !== contract.relative_path_template) {
195
+ throw new PushDriverError("invalid_provider_output", `provider output ${output.name} path does not match its pinned contract`);
196
+ }
197
+ return {
198
+ name: contract.name,
199
+ kind: "reference",
200
+ artifact_kind: artifactKind,
201
+ source_path: contract.relative_path_template,
202
+ };
203
+ }
204
+ export function mapProviderOutputsToSubmission(node, outputs, requireRequiredOutputs) {
205
+ const contracts = outputContractByName(node);
206
+ const seen = new Set();
207
+ const mapped = [];
208
+ for (const output of outputs) {
209
+ if (seen.has(output.name)) {
210
+ throw new PushDriverError("invalid_provider_output", `provider returned duplicate output ${output.name}`);
211
+ }
212
+ seen.add(output.name);
213
+ const contract = contracts.get(output.name);
214
+ if (!contract) {
215
+ throw new PushDriverError("invalid_provider_output", `provider returned undeclared output ${output.name}`);
216
+ }
217
+ mapped.push(mapProviderOutput(output, contract));
218
+ }
219
+ if (requireRequiredOutputs) {
220
+ const missing = [...contracts.values()].find((contract) => (contract.required && !seen.has(contract.name)));
221
+ if (missing) {
222
+ throw new PushDriverError("invalid_provider_output", `provider omitted required output ${missing.name}`);
223
+ }
224
+ }
225
+ return mapped;
226
+ }
227
+ export function terminalOutcomeToCanonicalAttemptOutcome(process) {
228
+ switch (process.kind) {
229
+ case "success":
230
+ return {
231
+ kind: "worker_process",
232
+ status: "succeeded",
233
+ reason_code: null,
234
+ message: null,
235
+ process,
236
+ };
237
+ case "cancelled":
238
+ return {
239
+ kind: "worker_process",
240
+ status: "cancelled",
241
+ reason_code: "cancelled",
242
+ message: process.reason,
243
+ process,
244
+ };
245
+ case "malformed_result":
246
+ case "launch_error":
247
+ return {
248
+ kind: "worker_process",
249
+ status: "failed",
250
+ reason_code: process.kind,
251
+ message: process.error,
252
+ process,
253
+ };
254
+ case "non_zero_exit":
255
+ case "timeout":
256
+ case "signal":
257
+ return {
258
+ kind: "worker_process",
259
+ status: "failed",
260
+ reason_code: process.kind,
261
+ message: null,
262
+ process,
263
+ };
264
+ }
265
+ }
266
+ function stableSubmissionKey(lease, workerSessionId) {
267
+ const digest = createHash("sha256").update(canonicalizeJson({
268
+ run_id: lease.run_id,
269
+ activation_id: lease.activation_id,
270
+ attempt_id: lease.attempt_id,
271
+ worker_session_id: workerSessionId,
272
+ }), "utf8").digest("hex");
273
+ return `push-driver:${digest}`;
274
+ }
275
+ function digest(value) {
276
+ return `sha256:${createHash("sha256")
277
+ .update(canonicalizeJson(value), "utf8")
278
+ .digest("hex")}`;
279
+ }
280
+ function assertCallerAttestation(authority, request, attestation) {
281
+ const payload = attestation.payload;
282
+ if (authority.authority_id.length === 0
283
+ || payload === undefined
284
+ || payload.schema_version !== CALLER_ATTESTATION_SCHEMA_VERSION
285
+ || payload.authority_id !== authority.authority_id
286
+ || payload.agent_id.length === 0
287
+ || payload.session_id.length === 0
288
+ || payload.run_id !== request.run_id
289
+ || payload.step_id !== request.step_id
290
+ || payload.activation_id !== request.activation_id
291
+ || payload.attempt_id !== request.attempt_id
292
+ || payload.claim_epoch !== request.claim_epoch
293
+ || payload.worker_session_id !== request.worker_session_id
294
+ || payload.submission_key_digest !== request.submission_key_digest
295
+ || payload.execution_digest !== request.execution_digest
296
+ || attestation.attestation_ref.length === 0
297
+ || attestation.attestation_digest !== digest(payload)) {
298
+ throw new PushDriverError("invalid_execution_result", `caller attestation does not match attempt ${request.attempt_id}`);
299
+ }
300
+ return attestation;
301
+ }
302
+ function driverFailureKey(lease, workerSessionId) {
303
+ return `push-driver-failure:${createHash("sha256").update(canonicalizeJson({
304
+ run_id: lease.run_id,
305
+ attempt_id: lease.attempt_id,
306
+ worker_session_id: workerSessionId,
307
+ }), "utf8").digest("hex")}`;
308
+ }
309
+ function errorMessage(error) {
310
+ return error instanceof Error ? error.message : String(error);
311
+ }
312
+ function assertExecutionIdentity(context, provider, execution) {
313
+ const result = execution.result;
314
+ if (execution.worker_session_id !== context.worker_session_id
315
+ || result.run_id !== context.lease.run_id
316
+ || result.step_id !== context.lease.step_id
317
+ || result.attempt_id !== context.lease.attempt_id
318
+ || result.worker_session_id !== context.worker_session_id
319
+ || result.provider !== provider.name
320
+ || result.started_at !== context.attached.attempt.started_at) {
321
+ throw new PushDriverError("invalid_execution_result", `worker execution identity does not match attempt ${context.lease.attempt_id}`);
322
+ }
323
+ const startedAt = Date.parse(result.started_at);
324
+ const completedAt = Date.parse(result.completed_at);
325
+ if (!Number.isFinite(startedAt)
326
+ || !Number.isFinite(completedAt)
327
+ || completedAt < startedAt) {
328
+ throw new PushDriverError("invalid_execution_result", `worker execution timestamps are invalid for attempt ${context.lease.attempt_id}`);
329
+ }
330
+ return result;
331
+ }
332
+ export class PushDriver {
333
+ capabilities;
334
+ service;
335
+ providerFactory;
336
+ executionAuthority;
337
+ callerAttestationAuthority;
338
+ lifecycleHooks;
339
+ diagnosticSink;
340
+ maxConcurrency;
341
+ heartbeatIntervalMs;
342
+ waitStrategy;
343
+ driverId;
344
+ createWorkerSessionId;
345
+ executeAttempt;
346
+ workerOptions;
347
+ sleep;
348
+ constructor(options) {
349
+ const maxConcurrency = options.max_concurrency ?? 1;
350
+ const heartbeatIntervalMs = options.heartbeat_interval_ms
351
+ ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
352
+ const waitPollMs = options.wait_poll_ms ?? DEFAULT_WAIT_POLL_MS;
353
+ requirePositiveInteger("max_concurrency", maxConcurrency);
354
+ requirePositiveInteger("heartbeat_interval_ms", heartbeatIntervalMs);
355
+ requirePositiveInteger("wait_poll_ms", waitPollMs);
356
+ this.service = options.service;
357
+ this.providerFactory = options.provider_factory;
358
+ this.executionAuthority = options.execution_authority ?? null;
359
+ this.callerAttestationAuthority = options.caller_attestation_authority ?? null;
360
+ if (options.capabilities?.cancellation === true
361
+ && this.executionAuthority === null) {
362
+ throw new PushDriverError("invalid_configuration", "cancellation capability requires an execution authority binding factory");
363
+ }
364
+ if (options.capabilities?.caller_attestation === true
365
+ && this.callerAttestationAuthority === null) {
366
+ throw new PushDriverError("invalid_configuration", "caller attestation capability requires a trusted caller attestation authority");
367
+ }
368
+ this.lifecycleHooks = options.lifecycle_hooks ?? {};
369
+ this.diagnosticSink = options.diagnostic_sink ?? null;
370
+ this.maxConcurrency = maxConcurrency;
371
+ this.heartbeatIntervalMs = heartbeatIntervalMs;
372
+ this.waitStrategy = options.wait_strategy ?? defaultWaitStrategy(waitPollMs);
373
+ this.driverId = options.driver_id ?? `push-driver:${process.pid}:${randomUUID()}`;
374
+ if (this.driverId.length === 0) {
375
+ throw new PushDriverError("invalid_configuration", "driver_id must not be empty");
376
+ }
377
+ this.createWorkerSessionId = options.create_worker_session_id
378
+ ?? (() => randomUUID());
379
+ this.executeAttempt = options.execute_attempt ?? executeWorkerAttempt;
380
+ this.workerOptions = options.worker_options;
381
+ this.sleep = options.sleep ?? defaultSleep;
382
+ this.capabilities = Object.freeze({
383
+ ...DEFAULT_CAPABILITIES,
384
+ cancellation: this.executionAuthority !== null,
385
+ caller_attestation: this.callerAttestationAuthority !== null,
386
+ ...options.capabilities,
387
+ });
388
+ }
389
+ async emit(diagnostic) {
390
+ if (this.diagnosticSink === null)
391
+ return;
392
+ try {
393
+ await this.diagnosticSink(diagnostic);
394
+ }
395
+ catch {
396
+ // Diagnostics are observations and cannot become run authority.
397
+ }
398
+ }
399
+ async attemptError(context, lease, error) {
400
+ await this.emit({
401
+ type: "attempt_failed",
402
+ run_id: lease.run_id,
403
+ attempt_id: lease.attempt_id,
404
+ error,
405
+ });
406
+ try {
407
+ await this.lifecycleHooks.on_attempt_error?.(context, error);
408
+ }
409
+ catch {
410
+ // Preserve the original driving failure.
411
+ }
412
+ }
413
+ assertHeartbeatLease(lease, heartbeat) {
414
+ const heartbeatAt = Date.parse(heartbeat.heartbeat_at);
415
+ const expiresAt = Date.parse(heartbeat.claim_expires_at);
416
+ if (heartbeat.run_id !== lease.run_id
417
+ || heartbeat.step_id !== lease.step_id
418
+ || heartbeat.activation_id !== lease.activation_id
419
+ || heartbeat.attempt_id !== lease.attempt_id
420
+ || heartbeat.attempt_number !== lease.attempt_number
421
+ || heartbeat.claim_epoch !== lease.claim_epoch
422
+ || heartbeat.owner_id !== lease.owner_id
423
+ || heartbeat.execution_deadline_at !== lease.execution_deadline_at
424
+ || !Number.isFinite(heartbeatAt)
425
+ || !Number.isFinite(expiresAt)
426
+ || expiresAt <= heartbeatAt) {
427
+ throw new PushDriverError("invalid_service_response", `heartbeat response does not match attempt ${lease.attempt_id}`);
428
+ }
429
+ }
430
+ async startHeartbeat(lease) {
431
+ let stopped = false;
432
+ let failureError = NO_ATTEMPT_ERROR;
433
+ let timer;
434
+ let inFlight = Promise.resolve();
435
+ let rejectFailure;
436
+ const failure = new Promise((_resolve, reject) => {
437
+ rejectFailure = reject;
438
+ });
439
+ void failure.catch(() => undefined);
440
+ const heartbeat = async () => {
441
+ const response = await this.service.heartbeat({
442
+ lease_token: lease.claim_token,
443
+ owner_id: lease.owner_id,
444
+ });
445
+ this.assertHeartbeatLease(lease, response);
446
+ return response;
447
+ };
448
+ const schedule = (response) => {
449
+ if (stopped)
450
+ return;
451
+ const claimExpiresAt = Date.parse(response.claim_expires_at);
452
+ const executionDeadline = Date.parse(response.execution_deadline_at);
453
+ if (claimExpiresAt === executionDeadline)
454
+ return;
455
+ const leaseWindow = claimExpiresAt - Date.parse(response.heartbeat_at);
456
+ const delay = Math.max(1, Math.min(this.heartbeatIntervalMs, Math.floor(leaseWindow / 2)));
457
+ timer = setTimeout(() => {
458
+ inFlight = heartbeat().then((next) => schedule(next), async (error) => {
459
+ failureError = error;
460
+ await this.emit({
461
+ type: "heartbeat_failed",
462
+ run_id: lease.run_id,
463
+ attempt_id: lease.attempt_id,
464
+ error,
465
+ });
466
+ stopped = true;
467
+ rejectFailure(error);
468
+ });
469
+ }, delay);
470
+ timer.unref?.();
471
+ };
472
+ schedule(await heartbeat());
473
+ return {
474
+ failure,
475
+ race: (operation) => (failureError === NO_ATTEMPT_ERROR
476
+ ? Promise.race([failure, operation])
477
+ : Promise.reject(failureError)),
478
+ stop: async () => {
479
+ stopped = true;
480
+ if (timer !== undefined)
481
+ clearTimeout(timer);
482
+ await inFlight;
483
+ },
484
+ };
485
+ }
486
+ attachIsDefinitivelyFenced(view, lease, workerSessionId) {
487
+ const attempt = view.attempts.find((candidate) => candidate.attempt_id === lease.attempt_id);
488
+ const claims = view.claims
489
+ .filter((candidate) => candidate.attempt_id === lease.attempt_id)
490
+ .sort((left, right) => right.claim_epoch - left.claim_epoch);
491
+ const latestClaim = claims[0];
492
+ const attachedToOriginalWorker = attempt?.executor?.kind === "worker"
493
+ && attempt.executor.worker_session_id === workerSessionId;
494
+ if (attempt?.executor !== null && attempt?.executor !== undefined && !attachedToOriginalWorker) {
495
+ return true;
496
+ }
497
+ if (latestClaim !== undefined
498
+ && (latestClaim.claim_epoch !== lease.claim_epoch || latestClaim.owner_id !== lease.owner_id)
499
+ && !attachedToOriginalWorker) {
500
+ return true;
501
+ }
502
+ return (view.run.terminal !== null || view.run.status === "cancelling")
503
+ && !attachedToOriginalWorker;
504
+ }
505
+ async abandonPreparation(context, preparation, attachError) {
506
+ if (this.executionAuthority === null)
507
+ return;
508
+ try {
509
+ await this.executionAuthority.abandon(context, preparation);
510
+ }
511
+ catch (abandonError) {
512
+ throw new PushDriverError("authority_preparation_abandon_failed", `attempt ${context.lease.attempt_id} attach was fenced, but execution authority preparation cleanup failed: ${errorMessage(abandonError)}`, {
513
+ cause: new AggregateError([attachError, abandonError], "attempt attach fencing and authority preparation cleanup both failed"),
514
+ });
515
+ }
516
+ }
517
+ async reconcilePreparedAttach(request, preparationContext, preparation, originalError) {
518
+ const { lease, worker_session_id: workerSessionId } = preparationContext;
519
+ let inspected;
520
+ try {
521
+ inspected = await this.service.inspect(lease.run_id);
522
+ }
523
+ catch (inspectError) {
524
+ throw new PushDriverIndeterminateAttachError(lease.attempt_id, new AggregateError([originalError, inspectError], "attach and inspection failed"));
525
+ }
526
+ if (this.attachIsDefinitivelyFenced(inspected, lease, workerSessionId)) {
527
+ await this.abandonPreparation(preparationContext, preparation, originalError);
528
+ return { kind: "fenced", run: inspected };
529
+ }
530
+ try {
531
+ return { kind: "attached", value: await this.service.attachAttempt(request) };
532
+ }
533
+ catch (replayError) {
534
+ let replayInspection;
535
+ try {
536
+ replayInspection = await this.service.inspect(lease.run_id);
537
+ }
538
+ catch (inspectError) {
539
+ throw new PushDriverIndeterminateAttachError(lease.attempt_id, new AggregateError([originalError, replayError, inspectError], "attach replay and inspection failed"));
540
+ }
541
+ if (this.attachIsDefinitivelyFenced(replayInspection, lease, workerSessionId)) {
542
+ await this.abandonPreparation(preparationContext, preparation, replayError);
543
+ return { kind: "fenced", run: replayInspection };
544
+ }
545
+ throw new PushDriverIndeterminateAttachError(lease.attempt_id, new AggregateError([originalError, replayError], "attach disposition remains ambiguous"));
546
+ }
547
+ }
548
+ async driveAttempt(lease, run, workerSessionId, executionClaimEpoch, recoveryMode) {
549
+ let context = null;
550
+ let normalizedExecution = null;
551
+ let executionInvoked = false;
552
+ let processLaunchObserved = false;
553
+ let executionAuthority;
554
+ let launchAuthority = !recoveryMode;
555
+ let heartbeat = null;
556
+ try {
557
+ heartbeat = await this.startHeartbeat(lease);
558
+ const node = requireWorkerNode(run, lease.step_id);
559
+ await this.emit({
560
+ type: "attempt_acquired",
561
+ run_id: lease.run_id,
562
+ attempt_id: lease.attempt_id,
563
+ step_id: lease.step_id,
564
+ });
565
+ if (recoveryMode && this.executionAuthority === null) {
566
+ throw new PushDriverError("invalid_configuration", `attempt ${lease.attempt_id} cannot recover without execution authority`);
567
+ }
568
+ const authorityPreparationContext = {
569
+ run,
570
+ binding: run.run.run_binding.binding,
571
+ node,
572
+ lease,
573
+ worker_session_id: workerSessionId,
574
+ execution_claim_epoch: executionClaimEpoch,
575
+ };
576
+ const authorityPreparation = recoveryMode || this.executionAuthority === null
577
+ ? undefined
578
+ : await heartbeat.race(Promise.resolve(this.executionAuthority.prepare(authorityPreparationContext)));
579
+ const attachRequest = {
580
+ lease_token: lease.claim_token,
581
+ owner_id: lease.owner_id,
582
+ worker_identity: workerSessionId,
583
+ capabilities: this.capabilities,
584
+ };
585
+ const rawAttach = this.service.attachAttempt(attachRequest).then((value) => ({ kind: "attached", value }), (error) => ({ kind: "rejected", error }));
586
+ let attachOutcome;
587
+ try {
588
+ attachOutcome = await heartbeat.race(rawAttach);
589
+ }
590
+ catch (heartbeatError) {
591
+ await heartbeat.stop();
592
+ heartbeat = null;
593
+ const rawOutcome = await rawAttach;
594
+ if (recoveryMode || this.executionAuthority === null)
595
+ throw heartbeatError;
596
+ const reconciliation = await this.reconcilePreparedAttach(attachRequest, authorityPreparationContext, authorityPreparation, rawOutcome.kind === "attached"
597
+ ? heartbeatError
598
+ : new AggregateError([heartbeatError, rawOutcome.error], "heartbeat and attach failed"));
599
+ if (reconciliation.kind === "fenced")
600
+ return reconciliation.run;
601
+ assertAttemptView(lease, workerSessionId, reconciliation.value);
602
+ heartbeat = await this.startHeartbeat(lease);
603
+ attachOutcome = reconciliation;
604
+ }
605
+ if (attachOutcome.kind === "rejected") {
606
+ if (recoveryMode || this.executionAuthority === null)
607
+ throw attachOutcome.error;
608
+ const reconciliation = await this.reconcilePreparedAttach(attachRequest, authorityPreparationContext, authorityPreparation, attachOutcome.error);
609
+ if (reconciliation.kind === "fenced")
610
+ return reconciliation.run;
611
+ attachOutcome = reconciliation;
612
+ }
613
+ const attached = attachOutcome.value;
614
+ const packet = assertAttemptView(lease, workerSessionId, attached);
615
+ context = {
616
+ run,
617
+ binding: run.run.run_binding.binding,
618
+ node,
619
+ packet,
620
+ lease,
621
+ attached,
622
+ worker_session_id: workerSessionId,
623
+ };
624
+ await this.emit({
625
+ type: "attempt_attached",
626
+ run_id: lease.run_id,
627
+ attempt_id: lease.attempt_id,
628
+ worker_session_id: workerSessionId,
629
+ });
630
+ const startedAt = attached.attempt.started_at;
631
+ if (startedAt === null) {
632
+ throw new PushDriverError("invalid_service_response", `attempt ${lease.attempt_id} has no durable start time`);
633
+ }
634
+ if (this.executionAuthority !== null) {
635
+ const authorityContext = {
636
+ ...context,
637
+ started_at: startedAt,
638
+ execution_claim_epoch: executionClaimEpoch,
639
+ };
640
+ if (recoveryMode) {
641
+ const resolution = await heartbeat.race(Promise.resolve(this.executionAuthority.recover(authorityContext)));
642
+ launchAuthority = resolution.kind === "launch";
643
+ executionAuthority = resolution.binding;
644
+ }
645
+ else {
646
+ executionAuthority = await heartbeat.race(Promise.resolve(this.executionAuthority.bind(authorityContext, authorityPreparation)));
647
+ }
648
+ }
649
+ await heartbeat.race(Promise.resolve(this.lifecycleHooks.before_execute?.(context)));
650
+ const provider = await heartbeat.race(Promise.resolve(this.providerFactory(context)));
651
+ if (!provider.name) {
652
+ throw new PushDriverError("invalid_configuration", "provider factory returned an unnamed provider");
653
+ }
654
+ const observedExecutionAuthority = executionAuthority === undefined
655
+ ? undefined
656
+ : {
657
+ ...executionAuthority,
658
+ processStarted: async (processId) => {
659
+ processLaunchObserved = true;
660
+ await executionAuthority.processStarted(processId);
661
+ },
662
+ };
663
+ let execution;
664
+ let executionError = null;
665
+ const executionPromise = this.executeAttempt(packet, provider, {
666
+ ...this.workerOptions,
667
+ cancellationReason: null,
668
+ execution_authority: observedExecutionAuthority,
669
+ startedAt,
670
+ });
671
+ try {
672
+ executionInvoked = true;
673
+ execution = await heartbeat.race(executionPromise);
674
+ }
675
+ catch (error) {
676
+ executionError = error;
677
+ }
678
+ if (executionError !== null) {
679
+ void executionPromise.catch(() => undefined);
680
+ throw executionError;
681
+ }
682
+ if (execution === undefined) {
683
+ throw new PushDriverError("invalid_execution_result", `worker execution returned no result for attempt ${lease.attempt_id}`);
684
+ }
685
+ normalizedExecution = execution;
686
+ await this.lifecycleHooks.after_execute?.(context, execution);
687
+ const result = assertExecutionIdentity(context, provider, execution);
688
+ let outcome = terminalOutcomeToCanonicalAttemptOutcome(result.outcome);
689
+ let outputs;
690
+ try {
691
+ outputs = mapProviderOutputsToSubmission(node, result.outputs, outcome.kind === "worker_process" && outcome.status === "succeeded");
692
+ }
693
+ catch (error) {
694
+ outcome = {
695
+ kind: "worker_process",
696
+ status: "failed",
697
+ reason_code: error instanceof PushDriverError
698
+ ? error.code
699
+ : "driver_postprocess_failed",
700
+ message: errorMessage(error),
701
+ process: result.outcome,
702
+ };
703
+ outputs = [];
704
+ }
705
+ const submissionKey = stableSubmissionKey(lease, workerSessionId);
706
+ const submissionExecution = {
707
+ executor: { kind: "worker", worker_session_id: workerSessionId },
708
+ started_at: result.started_at,
709
+ completed_at: result.completed_at,
710
+ outcome,
711
+ outputs,
712
+ workspace: {
713
+ root: packet.workspace.working_directory,
714
+ workspace_id: lease.attempt_id,
715
+ },
716
+ stdout_reference: null,
717
+ stderr_reference: null,
718
+ };
719
+ let evidenceRefs;
720
+ if (run.plan.plan.required_capabilities.caller_attestation) {
721
+ const authority = this.callerAttestationAuthority;
722
+ if (authority === null) {
723
+ throw new PushDriverError("unsupported", "trusted caller attestation authority is unavailable");
724
+ }
725
+ const attestationRequest = {
726
+ run_id: lease.run_id,
727
+ step_id: lease.step_id,
728
+ activation_id: lease.activation_id,
729
+ attempt_id: lease.attempt_id,
730
+ claim_epoch: lease.claim_epoch,
731
+ worker_session_id: workerSessionId,
732
+ provider_session_id: result.provider_session_id,
733
+ submission_key_digest: digest({ submission_key: submissionKey }),
734
+ execution_digest: digest(submissionExecution),
735
+ };
736
+ const attestation = await heartbeat.race(Promise.resolve(authority.attest(attestationRequest)));
737
+ evidenceRefs = [assertCallerAttestation(authority, attestationRequest, attestation)];
738
+ }
739
+ const response = await heartbeat.race(this.service.submitAttempt({
740
+ lease_token: lease.claim_token,
741
+ owner_id: lease.owner_id,
742
+ submission_key: submissionKey,
743
+ execution: submissionExecution,
744
+ ...(evidenceRefs === undefined ? {} : { evidence_refs: evidenceRefs }),
745
+ }));
746
+ if (response.replayed) {
747
+ await this.emit({
748
+ type: "submission_replayed",
749
+ run_id: lease.run_id,
750
+ attempt_id: lease.attempt_id,
751
+ });
752
+ }
753
+ try {
754
+ await this.lifecycleHooks.after_submit?.(context, execution, response);
755
+ }
756
+ catch {
757
+ // Post-submit hooks are observations and cannot override a committed result.
758
+ }
759
+ return response.run;
760
+ }
761
+ catch (error) {
762
+ await this.attemptError(context, lease, error);
763
+ const canProvePrelaunch = launchAuthority && (!executionInvoked
764
+ || (executionAuthority !== undefined && !processLaunchObserved));
765
+ if (context !== null && normalizedExecution === null && canProvePrelaunch) {
766
+ try {
767
+ return await this.service.settleDriverFailure({
768
+ lease_token: lease.claim_token,
769
+ owner_id: lease.owner_id,
770
+ failure_key: driverFailureKey(lease, workerSessionId),
771
+ reason_code: "driver_prelaunch_failed",
772
+ message: errorMessage(error),
773
+ });
774
+ }
775
+ catch (settlementError) {
776
+ throw new PushDriverError("driver_failure_settlement_failed", `driver failed before launch, but canonical settlement was rejected: ${errorMessage(settlementError)}`, {
777
+ cause: new AggregateError([error, settlementError], "driver failure and canonical settlement both failed"),
778
+ });
779
+ }
780
+ }
781
+ throw error;
782
+ }
783
+ finally {
784
+ await heartbeat?.stop();
785
+ }
786
+ }
787
+ trackAttempt(lease, run, workerSessionId, executionClaimEpoch, recoveryMode) {
788
+ return this.driveAttempt(lease, run, workerSessionId, executionClaimEpoch, recoveryMode).then((nextRun) => ({
789
+ attempt_id: lease.attempt_id,
790
+ run: nextRun,
791
+ error: null,
792
+ }), (error) => ({
793
+ attempt_id: lease.attempt_id,
794
+ run: null,
795
+ error,
796
+ }));
797
+ }
798
+ async run(runId, options = {}) {
799
+ if (!runId) {
800
+ throw new PushDriverError("invalid_configuration", "run_id must not be empty");
801
+ }
802
+ if (options.initial_view !== undefined && options.resume === true) {
803
+ throw new PushDriverError("invalid_configuration", "initial_view and resume cannot be used together");
804
+ }
805
+ let current = options.initial_view
806
+ ?? (options.resume === true
807
+ ? await this.service.resume(runId)
808
+ : await this.service.inspect(runId));
809
+ if (current.run.run_id !== runId) {
810
+ throw new PushDriverError("invalid_service_response", "service returned a different run_id");
811
+ }
812
+ const pinned = pinRun(current);
813
+ if (pinned.max_concurrency !== this.maxConcurrency) {
814
+ throw new PushDriverError("invalid_configuration", `driver max_concurrency ${this.maxConcurrency} does not match immutable run binding ${pinned.max_concurrency}`);
815
+ }
816
+ current = assertPinnedRun(pinned, current);
817
+ assertRequiredCapabilities(current.plan.plan.required_capabilities, this.capabilities);
818
+ const active = new Map();
819
+ const activeDeadlines = new Map();
820
+ const launchedAttempts = new Set();
821
+ const workerSessions = new Set();
822
+ let firstAttemptError = NO_ATTEMPT_ERROR;
823
+ let cancellationRequested = false;
824
+ const abandonActiveWaits = () => {
825
+ for (const promise of active.values()) {
826
+ void promise.then(() => undefined, () => undefined);
827
+ }
828
+ active.clear();
829
+ activeDeadlines.clear();
830
+ };
831
+ const cancelAndDrainAfterDriverFailure = async (error) => {
832
+ if (active.size === 0)
833
+ throw error;
834
+ if (!cancellationRequested) {
835
+ cancellationRequested = true;
836
+ current = assertPinnedRun(pinned, await this.service.cancel(runId, `push driver failed: ${errorMessage(error)}`));
837
+ }
838
+ const latestDeadline = Math.max(Date.now(), ...activeDeadlines.values());
839
+ abandonActiveWaits();
840
+ while (current.run.terminal === null) {
841
+ current = assertPinnedRun(pinned, await this.service.inspect(runId));
842
+ if (current.run.terminal !== null)
843
+ break;
844
+ if (Date.now() >= latestDeadline)
845
+ throw error;
846
+ const decision = await this.waitStrategy({
847
+ reason_code: "run_cancelling",
848
+ run: current,
849
+ active_attempts: 0,
850
+ });
851
+ if (decision.action === "stop")
852
+ throw error;
853
+ const remaining = latestDeadline - Date.now();
854
+ await this.sleep(Math.min(decision.delay_ms, remaining));
855
+ }
856
+ throw error;
857
+ };
858
+ for (;;) {
859
+ if (current.run.terminal !== null) {
860
+ if (active.size > 0) {
861
+ const siblings = [...active.values()];
862
+ active.clear();
863
+ activeDeadlines.clear();
864
+ await Promise.allSettled(siblings);
865
+ }
866
+ return current;
867
+ }
868
+ let waiting = null;
869
+ while (active.size < this.maxConcurrency
870
+ && firstAttemptError === NO_ATTEMPT_ERROR) {
871
+ try {
872
+ const acquired = await this.service.acquireAttempt({
873
+ run_id: runId,
874
+ driver_id: this.driverId,
875
+ recover_running: options.resume === true,
876
+ });
877
+ current = assertPinnedRun(pinned, current);
878
+ if (acquired.kind === "terminal") {
879
+ current = assertPinnedRun(pinned, acquired.run);
880
+ break;
881
+ }
882
+ if (acquired.kind === "wait") {
883
+ current = assertPinnedRun(pinned, acquired.run);
884
+ waiting = acquired;
885
+ break;
886
+ }
887
+ const lease = acquired.lease;
888
+ if (lease.run_id !== runId || lease.owner_id !== this.driverId) {
889
+ throw new PushDriverError("invalid_service_response", "acquired lease does not belong to this driver and run");
890
+ }
891
+ requireNonEmpty("attempt_id", lease.attempt_id);
892
+ if (launchedAttempts.has(lease.attempt_id)) {
893
+ throw new PushDriverError("attempt_relaunch", `service attempted to relaunch ${lease.attempt_id}`);
894
+ }
895
+ const executionDeadline = Date.parse(lease.execution_deadline_at);
896
+ if (!Number.isFinite(executionDeadline)) {
897
+ throw new PushDriverError("invalid_service_response", `attempt ${lease.attempt_id} has an invalid execution deadline`);
898
+ }
899
+ const workerSessionId = acquired.recovery?.worker_session_id
900
+ ?? this.createWorkerSessionId();
901
+ requireNonEmpty("worker_session_id", workerSessionId);
902
+ if (workerSessions.has(workerSessionId)) {
903
+ throw new PushDriverError("invalid_configuration", `worker session factory reused ${workerSessionId}`);
904
+ }
905
+ launchedAttempts.add(lease.attempt_id);
906
+ workerSessions.add(workerSessionId);
907
+ const tracked = this.trackAttempt(lease, current, workerSessionId, acquired.recovery?.execution_claim_epoch ?? lease.claim_epoch, acquired.recovery !== null).then((completion) => {
908
+ if (completion.error !== null
909
+ && firstAttemptError === NO_ATTEMPT_ERROR) {
910
+ firstAttemptError = completion.error;
911
+ }
912
+ return completion;
913
+ });
914
+ active.set(lease.attempt_id, tracked);
915
+ activeDeadlines.set(lease.attempt_id, executionDeadline);
916
+ }
917
+ catch (error) {
918
+ await cancelAndDrainAfterDriverFailure(error);
919
+ }
920
+ }
921
+ if (current.run.terminal !== null)
922
+ continue;
923
+ if (active.size > 0) {
924
+ const completion = await Promise.race(active.values());
925
+ const completedDeadline = activeDeadlines.get(completion.attempt_id);
926
+ active.delete(completion.attempt_id);
927
+ activeDeadlines.delete(completion.attempt_id);
928
+ if (completion.error !== null) {
929
+ const attemptError = firstAttemptError === NO_ATTEMPT_ERROR
930
+ ? completion.error
931
+ : firstAttemptError;
932
+ if (attemptError instanceof PushDriverIndeterminateAttachError) {
933
+ const siblings = [...active.values()];
934
+ active.clear();
935
+ activeDeadlines.clear();
936
+ await Promise.allSettled(siblings);
937
+ throw attemptError;
938
+ }
939
+ if (!cancellationRequested) {
940
+ cancellationRequested = true;
941
+ current = assertPinnedRun(pinned, await this.service.cancel(runId, `push driver attempt failed: ${errorMessage(attemptError)}`));
942
+ if (current.run.terminal !== null) {
943
+ abandonActiveWaits();
944
+ continue;
945
+ }
946
+ }
947
+ const latestDeadline = Math.max(Date.now(), completedDeadline ?? Number.NEGATIVE_INFINITY, ...activeDeadlines.values());
948
+ abandonActiveWaits();
949
+ for (;;) {
950
+ current = assertPinnedRun(pinned, await this.service.inspect(runId));
951
+ if (current.run.terminal !== null)
952
+ break;
953
+ if (Date.now() >= latestDeadline)
954
+ throw attemptError;
955
+ const decision = await this.waitStrategy({
956
+ reason_code: "run_cancelling",
957
+ run: current,
958
+ active_attempts: 0,
959
+ });
960
+ if (decision.action === "stop")
961
+ throw attemptError;
962
+ const remaining = latestDeadline - Date.now();
963
+ await this.sleep(Math.min(decision.delay_ms, remaining));
964
+ }
965
+ continue;
966
+ }
967
+ if (completion.run === null) {
968
+ throw new PushDriverError("invalid_service_response", `attempt ${completion.attempt_id} completed without a run view`);
969
+ }
970
+ current = assertPinnedRun(pinned, completion.run);
971
+ continue;
972
+ }
973
+ if (waiting === null) {
974
+ throw new PushDriverError("invalid_service_response", "driver has neither an active attempt nor a typed wait response");
975
+ }
976
+ const decision = await this.waitStrategy({
977
+ reason_code: waiting.reason_code,
978
+ run: current,
979
+ active_attempts: 0,
980
+ });
981
+ if (decision.action === "stop") {
982
+ await this.emit({
983
+ type: "wait",
984
+ run_id: runId,
985
+ reason_code: waiting.reason_code,
986
+ delay_ms: null,
987
+ });
988
+ throw new PushDriverError("wait_stopped", decision.reason ?? `wait strategy stopped on ${waiting.reason_code}`);
989
+ }
990
+ if (!Number.isSafeInteger(decision.delay_ms) || decision.delay_ms < 0) {
991
+ throw new PushDriverError("invalid_configuration", "wait strategy delay_ms must be a non-negative safe integer");
992
+ }
993
+ await this.emit({
994
+ type: "wait",
995
+ run_id: runId,
996
+ reason_code: waiting.reason_code,
997
+ delay_ms: decision.delay_ms,
998
+ });
999
+ await this.sleep(decision.delay_ms);
1000
+ }
1001
+ }
1002
+ }