@loucompanion/forge-bridge 0.1.3-dev.fbe15023cdfe → 0.1.4-dev.41da3780c451

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 (165) hide show
  1. package/README.md +88 -2
  2. package/dist/bridge/bridge-chat.types.d.ts +111 -0
  3. package/dist/bridge/bridge-chat.types.js +401 -0
  4. package/dist/bridge/bridge.base.d.ts +4 -1
  5. package/dist/bridge/bridge.service.d.ts +72 -3
  6. package/dist/bridge/bridge.service.js +1563 -152
  7. package/dist/bridge/bridge.types.d.ts +46 -1
  8. package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
  9. package/dist/bridge/dispatch-result-outbox.js +62 -0
  10. package/dist/bridge/dispatch-result-worker.d.ts +2 -0
  11. package/dist/bridge/dispatch-result-worker.js +122 -0
  12. package/dist/bridge/internals.d.ts +2 -0
  13. package/dist/bridge/internals.js +1 -0
  14. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  15. package/dist/bridge/readiness-result-outbox.js +54 -0
  16. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  17. package/dist/bridge/readiness-result-worker.js +121 -0
  18. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  19. package/dist/bridge/runner-host-capabilities.js +93 -0
  20. package/dist/cleanup/cleanup.service.d.ts +1 -0
  21. package/dist/cleanup/cleanup.service.js +70 -9
  22. package/dist/cli/cli.help.js +2 -2
  23. package/dist/cli/cli.service.d.ts +14 -6
  24. package/dist/cli/cli.service.js +75 -16
  25. package/dist/cli/local-config.d.ts +1 -0
  26. package/dist/cli/local-config.js +4 -0
  27. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  28. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  29. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  30. package/dist/runtime/attempt-database-broker.js +390 -0
  31. package/dist/runtime/attempt-node-runtime-launcher.d.ts +2 -1
  32. package/dist/runtime/attempt-node-runtime-launcher.js +57 -18
  33. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  34. package/dist/runtime/attempt-path-operation.js +94 -0
  35. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  36. package/dist/runtime/attempt-private-directory.js +54 -0
  37. package/dist/runtime/attempt-process-supervisor.d.ts +11 -0
  38. package/dist/runtime/attempt-process-supervisor.js +70 -22
  39. package/dist/runtime/canonical-source-file.d.ts +3 -0
  40. package/dist/runtime/canonical-source-file.js +54 -0
  41. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  42. package/dist/runtime/docker-engine-probe.js +333 -0
  43. package/dist/runtime/generated-output-reset.d.ts +10 -0
  44. package/dist/runtime/generated-output-reset.js +62 -8
  45. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  46. package/dist/runtime/host-database-capability-boundary.js +62 -0
  47. package/dist/runtime/host-diagnostics.d.ts +11 -0
  48. package/dist/runtime/host-diagnostics.js +192 -0
  49. package/dist/runtime/host-execution-root.d.ts +4 -0
  50. package/dist/runtime/host-execution-root.js +39 -0
  51. package/dist/runtime/host-listener-inspector.d.ts +8 -1
  52. package/dist/runtime/host-listener-inspector.js +121 -60
  53. package/dist/runtime/host-runtime-contract.d.ts +12 -1
  54. package/dist/runtime/host-runtime-contract.js +168 -55
  55. package/dist/runtime/host-runtime-lease.d.ts +92 -8
  56. package/dist/runtime/host-runtime-lease.js +1141 -114
  57. package/dist/runtime/host-runtime-promotion.d.ts +12 -3
  58. package/dist/runtime/host-runtime-promotion.js +59 -12
  59. package/dist/runtime/memory-capacity.d.ts +70 -0
  60. package/dist/runtime/memory-capacity.js +123 -0
  61. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  62. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  63. package/dist/runtime/node-host-runtime-adapter.d.ts +3 -0
  64. package/dist/runtime/node-host-runtime-adapter.js +29 -11
  65. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  66. package/dist/runtime/pinned-directory-process.js +61 -0
  67. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  68. package/dist/runtime/pinned-source-directory.js +94 -0
  69. package/dist/runtime/playwright-host-runtime-adapter.d.ts +1 -0
  70. package/dist/runtime/playwright-host-runtime-adapter.js +53 -5
  71. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  72. package/dist/runtime/prepared-runtime-identity.js +218 -0
  73. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  74. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  75. package/dist/runtime/runtime-isolation-lease.js +4 -1
  76. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  77. package/dist/runtime/workspace-resource-estimator.js +174 -0
  78. package/dist/sandbox/preflight.d.ts +31 -1
  79. package/dist/sandbox/preflight.js +237 -51
  80. package/dist/sandbox/repository-lock.d.ts +2 -1
  81. package/dist/sandbox/repository-lock.js +19 -9
  82. package/dist/sandbox/runtime.d.ts +6 -1
  83. package/dist/sandbox/runtime.js +137 -107
  84. package/dist/sandbox/sandbox.types.d.ts +7 -0
  85. package/dist/sandbox/workspace.service.js +2 -0
  86. package/dist/session/managed-attempt-tools.d.ts +17 -1
  87. package/dist/session/managed-attempt-tools.js +489 -34
  88. package/dist/session/managed-build-coordinator.d.ts +5 -0
  89. package/dist/session/managed-build-coordinator.js +106 -0
  90. package/dist/session/managed-build-receipt.d.ts +21 -0
  91. package/dist/session/managed-build-receipt.js +337 -0
  92. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  93. package/dist/session/managed-verification-timeouts.js +17 -0
  94. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  95. package/dist/session/phase-completion.artifacts.js +224 -22
  96. package/dist/session/phase-completion.d.ts +11 -1
  97. package/dist/session/phase-completion.helpers.d.ts +5 -0
  98. package/dist/session/phase-completion.helpers.js +30 -7
  99. package/dist/session/phase-completion.js +14 -3
  100. package/dist/session/phase-completion.prompts.js +17 -18
  101. package/dist/session/phase-completion.runtime.d.ts +3 -1
  102. package/dist/session/phase-completion.runtime.js +126 -17
  103. package/dist/session/phase-completion.schemas.js +49 -2
  104. package/dist/session/phase-completion.validation.d.ts +1 -0
  105. package/dist/session/phase-completion.validation.js +111 -21
  106. package/dist/session/pre-publication-validation.d.ts +75 -0
  107. package/dist/session/pre-publication-validation.js +661 -0
  108. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  109. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  110. package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
  111. package/dist/session/provider-cli/ci-repair-readiness.js +23 -3
  112. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +4 -2
  113. package/dist/session/provider-cli/provider-cli.artifacts.js +47 -13
  114. package/dist/session/provider-cli/provider-cli.deterministic.js +34 -2
  115. package/dist/session/provider-cli/provider-cli.process.d.ts +51 -0
  116. package/dist/session/provider-cli/provider-cli.process.js +422 -22
  117. package/dist/session/provider-cli/provider-cli.prompts.d.ts +5 -0
  118. package/dist/session/provider-cli/provider-cli.prompts.js +136 -16
  119. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  120. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  121. package/dist/session/provider-cli/provider-cli.runtime.d.ts +14 -1
  122. package/dist/session/provider-cli/provider-cli.runtime.js +54 -3
  123. package/dist/session/provider-cli/provider-cli.service.d.ts +30 -5
  124. package/dist/session/provider-cli/provider-cli.service.js +773 -69
  125. package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
  126. package/dist/session/provider-cli/provider-output-state.d.ts +10 -0
  127. package/dist/session/provider-cli/provider-output-state.js +361 -17
  128. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  129. package/dist/session/provider-cli/provider-usage.js +28 -5
  130. package/dist/session/session-redaction.js +434 -9
  131. package/dist/session/session.service.js +145 -23
  132. package/dist/session/session.types.d.ts +147 -2
  133. package/dist/session/session.types.js +587 -7
  134. package/dist/session/unreferenced-exports.d.ts +12 -0
  135. package/dist/session/unreferenced-exports.js +59 -0
  136. package/dist/shared/format/format-bytes.d.ts +1 -0
  137. package/dist/shared/format/format-bytes.js +9 -0
  138. package/dist/shared/git/git.js +2 -1
  139. package/dist/shared/path/path.d.ts +1 -0
  140. package/dist/shared/path/path.js +18 -0
  141. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  142. package/dist/shared/process/dotnet-toolchain.js +307 -0
  143. package/dist/shared/process/host-process-recovery.js +3 -2
  144. package/dist/shared/process/host-process-supervisor.d.ts +2 -0
  145. package/dist/shared/process/host-process-supervisor.js +60 -11
  146. package/dist/shared/process/process.d.ts +2 -0
  147. package/dist/shared/process/process.js +12 -7
  148. package/dist/shared/provider-capabilities.d.ts +3 -0
  149. package/dist/shared/provider-capabilities.js +85 -0
  150. package/dist/shared/redaction/redaction.d.ts +1 -0
  151. package/dist/shared/redaction/redaction.js +39 -3
  152. package/dist/shared/time/monotonic.d.ts +1 -0
  153. package/dist/shared/time/monotonic.js +8 -0
  154. package/dist/version.d.ts +1 -1
  155. package/dist/version.js +1 -1
  156. package/dist/worktree/dependency-warmer.d.ts +41 -9
  157. package/dist/worktree/dependency-warmer.js +497 -22
  158. package/dist/worktree/index.d.ts +1 -1
  159. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  160. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  161. package/dist/worktree/worktree.service.d.ts +7 -0
  162. package/dist/worktree/worktree.service.js +253 -21
  163. package/dist/worktree/worktree.types.d.ts +7 -1
  164. package/package.json +7 -5
  165. package/runtime/windows-job-supervisor.ps1 +380 -14
@@ -1,5 +1,7 @@
1
1
  import { redactRunnerDispatchEvent, redactRunOnceResultPacket } from "./session-redaction.js";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { RoutingProviderCliService } from "./provider-cli/provider-cli.service.js";
4
+ import { ForgeFailureError, waitKindForEvent, waitWakeCondition } from "./session.types.js";
3
5
  import { GitWorktreeService } from "../worktree/worktree.service.js";
4
6
  import { SandboxWorkspaceService } from "../sandbox/workspace.service.js";
5
7
  import { HostRuntimeAdmissionExpiredError, hostRuntimeEventResources, } from "../runtime/host-runtime-lease.js";
@@ -37,9 +39,12 @@ export class BridgeSessionService {
37
39
  let cleanupFailure = null;
38
40
  try {
39
41
  throwIfAborted(signal);
40
- await this.hostRuntime.invalidatePromotion?.(request.repoId);
41
42
  await this.hostRuntime.acquire(dispatch, signal);
42
43
  admissionAcquired = true;
44
+ // Readiness probes acquire background-exclusive project admission. Only
45
+ // invalidate the prior proof after that fence is held so a user lane can
46
+ // never observe the proof disappear underneath shared admission.
47
+ await this.hostRuntime.invalidatePromotion?.(request.repoId, "discovery");
43
48
  assertReadinessProbeStartupBudget(request.expiresAt);
44
49
  worktree = await this.worktrees.prepare({
45
50
  repo: dispatch.repo,
@@ -51,7 +56,10 @@ export class BridgeSessionService {
51
56
  lane: "runtime_readiness",
52
57
  phase: "readiness_probe",
53
58
  allowedRepoRoot: request.repoRoot,
54
- cleanupWorktrees: request.cleanupWorktrees,
59
+ // Readiness probes are disposable infrastructure checks, not agent lanes.
60
+ // Always remove their worktree even when the bridge is configured to retain
61
+ // failed agent worktrees for inspection.
62
+ cleanupWorktrees: dispatch.serverLocal.cleanupWorktrees,
55
63
  artifacts: [],
56
64
  });
57
65
  assertReadinessProbeStartupBudget(request.expiresAt);
@@ -120,18 +128,39 @@ export class BridgeSessionService {
120
128
  validateApiKeySecretGrant(dispatch);
121
129
  const redactionSecrets = exactSecrets(dispatch);
122
130
  const events = [];
131
+ let waitObservationVersion = 0;
132
+ const waitFirstObservedAt = new Map();
133
+ const waitObservation = (eventType, queuePosition) => {
134
+ const occurredAt = new Date().toISOString();
135
+ const kind = waitKindForEvent(eventType);
136
+ const firstObservedAt = waitFirstObservedAt.get(kind) ?? occurredAt;
137
+ waitFirstObservedAt.set(kind, firstObservedAt);
138
+ waitObservationVersion += 1;
139
+ const wait = {
140
+ schemaVersion: 2,
141
+ kind,
142
+ scope: "project",
143
+ resourceKeys: [`project-runtime:${dispatch.repo.id}`],
144
+ ...(queuePosition === undefined ? {} : { queuePosition }),
145
+ wakeCondition: waitWakeCondition(kind),
146
+ firstObservedAt,
147
+ observationVersion: waitObservationVersion,
148
+ contractOrigin: "bridge_v2",
149
+ };
150
+ return { occurredAt, wait };
151
+ };
123
152
  const emit = async (event) => {
124
153
  if (signal?.aborted) {
125
154
  return;
126
155
  }
127
- const redacted = redactRunnerDispatchEvent(event, redactionSecrets);
156
+ const redacted = redactRunnerDispatchEvent({ ...event, eventId: event.eventId ?? randomUUID() }, redactionSecrets);
128
157
  if (redacted.eventType !== "provider_tool_heartbeat") {
129
158
  events.push(redacted);
130
159
  }
131
160
  await sink.emit(redacted);
132
161
  };
133
162
  const emitLifecycle = async (event) => {
134
- const redacted = redactRunnerDispatchEvent(event, redactionSecrets);
163
+ const redacted = redactRunnerDispatchEvent({ ...event, eventId: event.eventId ?? randomUUID() }, redactionSecrets);
135
164
  events.push(redacted);
136
165
  await sink.emit(redacted);
137
166
  };
@@ -147,14 +176,31 @@ export class BridgeSessionService {
147
176
  let hostLease = null;
148
177
  try {
149
178
  if (dispatch.sandbox?.executionMode === "host" && this.hostRuntime) {
179
+ const capacity = this.hostRuntime.snapshot?.() ?? null;
180
+ const projectCapacity = capacity?.projects.find(project => project.repoId === dispatch.repo.id) ?? null;
181
+ const admissionWait = waitObservation("host_runtime_admission_waiting", capacity ? capacity.waitingAttempts + 1 : undefined);
150
182
  await emitLifecycle({
151
183
  role: "runner",
152
184
  kind: "checkpoint",
153
- content: "Evaluating runner-local managed host admission before preparing the worktree.",
185
+ content: capacity
186
+ ? `Waiting for host-runtime admission before preparing the worktree (queue position ${capacity.waitingAttempts + 1}; runner ${capacity.activeAttempts} active, ${capacity.reservedAttempts} reserved; project ${projectCapacity?.activeAttempts ?? 0} active, ${projectCapacity?.waitingAttempts ?? 0} already waiting). This wait can be caused by the project serialization policy, readiness proof, or resource headroom; Forge will continue automatically when admission is available.`
187
+ : "Waiting for host-runtime admission before preparing the worktree. Forge will continue automatically when admission is available.",
154
188
  eventType: "host_runtime_admission_waiting",
189
+ ...admissionWait,
190
+ data: capacity ? JSON.stringify({
191
+ queue_position: capacity.waitingAttempts + 1,
192
+ runner_active_attempts: capacity.activeAttempts,
193
+ runner_reserved_attempts: capacity.reservedAttempts,
194
+ runner_waiting_attempts: capacity.waitingAttempts,
195
+ project_active_attempts: projectCapacity?.activeAttempts ?? 0,
196
+ project_reserved_attempts: projectCapacity?.reservedAttempts ?? 0,
197
+ project_waiting_attempts: projectCapacity?.waitingAttempts ?? 0,
198
+ project_safety_class: projectCapacity?.safetyClass ?? "unknown",
199
+ }) : undefined,
155
200
  });
156
201
  try {
157
- hostSingletonKeys = await this.hostRuntime.acquire(dispatch, signal);
202
+ hostSingletonKeys = await (this.hostRuntime.acquirePreparation?.(dispatch, signal)
203
+ ?? this.hostRuntime.acquire(dispatch, signal));
158
204
  }
159
205
  catch (error) {
160
206
  if (error instanceof HostRuntimeAdmissionExpiredError) {
@@ -168,13 +214,17 @@ export class BridgeSessionService {
168
214
  throw error;
169
215
  }
170
216
  throwIfAborted(signal);
217
+ const admissionAcquired = waitObservation("host_runtime_admission_acquired");
171
218
  await emitLifecycle({
172
219
  role: "runner",
173
220
  kind: "checkpoint",
174
- content: hostSingletonKeys.length > 0
175
- ? `Acquired managed host singleton ${hostSingletonKeys.join(", ")}.`
176
- : "Observed assigned-port evidence permits provisional parallel host admission; the prepared runtime contract must still match before launch.",
221
+ content: this.hostRuntime.acquirePreparation
222
+ ? "Acquired worktree-preparation capacity. Prepared runtime verification must finish before agent admission."
223
+ : hostSingletonKeys.length > 0
224
+ ? `Acquired managed host singleton ${hostSingletonKeys.join(", ")}.`
225
+ : "Observed assigned-port evidence permits provisional parallel host admission; the prepared runtime contract must still match before launch.",
177
226
  eventType: "host_runtime_admission_acquired",
227
+ ...admissionAcquired,
178
228
  });
179
229
  }
180
230
  try {
@@ -191,20 +241,28 @@ export class BridgeSessionService {
191
241
  cleanupWorktrees: dispatch.serverLocal.cleanupWorktrees,
192
242
  artifacts: dispatch.artifacts,
193
243
  priorPhaseArtifacts: dispatch.priorPhaseArtifacts,
244
+ storyQa: dispatch.storyQa,
245
+ referenceRunId: dispatch.sandbox?.dispatchAttemptId ?? dispatch.cycleRun.id,
246
+ referenceCycleRunId: dispatch.cycleRun.id,
247
+ effectivePlanAmendmentHash: dispatch.cycleRun.effectivePlanAmendmentHash,
194
248
  qaRework: dispatch.qaRework,
195
- warmPathOverrides: dispatch.warmPathOverrides
249
+ warmPathOverrides: dispatch.warmPathOverrides,
250
+ applicationRuntimeRequired: dispatch.runtimeIntent.applicationRuntimeRequired,
251
+ preserveExistingState: Boolean(dispatch.session.resumedFromSessionId)
196
252
  });
197
253
  }
198
254
  catch (error) {
199
- throw new Error(`worktree_prepare_failed: ${failureMessage(error)}`, { cause: error });
255
+ throw new ForgeFailureError("worktree_prepare_failed", `worktree_prepare_failed: ${failureMessage(error)}`, "bridge", { cause: error });
200
256
  }
201
257
  let providerDispatch = withPreparedQaReworkPacket(dispatch, worktree.qaReworkPacketPath);
202
258
  throwIfAborted(signal);
203
259
  await emit({
204
260
  role: "runner",
205
261
  kind: "checkpoint",
206
- content: `Prepared worktree ${worktree.branchName}.`,
207
- eventType: "worktree_prepared"
262
+ content: dispatch.session.resumedFromSessionId
263
+ ? `Reattached to worktree ${worktree.branchName} without resetting source or clearing attempt artifacts.`
264
+ : `Prepared worktree ${worktree.branchName}.`,
265
+ eventType: dispatch.session.resumedFromSessionId ? "worktree_reattached" : "worktree_prepared"
208
266
  });
209
267
  if ((worktree.hydratedPriorPhaseArtifactCount ?? 0) > 0) {
210
268
  await emit({
@@ -218,7 +276,17 @@ export class BridgeSessionService {
218
276
  await emitDependencyWarmEvents(worktree.dependencyWarm, emit);
219
277
  }
220
278
  if (dispatch.sandbox?.executionMode === "host" && this.hostRuntime) {
221
- hostLease = await this.hostRuntime.start(dispatch, worktree.worktreePath, signal);
279
+ hostLease = await this.hostRuntime.start(dispatch, worktree.worktreePath, signal, async (checkpoint) => {
280
+ const observation = waitObservation(checkpoint.code);
281
+ await emitLifecycle({
282
+ role: "runner",
283
+ kind: "checkpoint",
284
+ content: checkpoint.message,
285
+ eventType: checkpoint.code,
286
+ ...observation,
287
+ data: JSON.stringify({ expires_at: checkpoint.expiresAt }),
288
+ });
289
+ });
222
290
  await emitLifecycle({
223
291
  role: "runner",
224
292
  kind: "checkpoint",
@@ -238,7 +306,15 @@ export class BridgeSessionService {
238
306
  }),
239
307
  });
240
308
  const unconfiguredPortCount = hostLease.lease.resources.ports.length;
241
- hostLease = await this.hostRuntime.startConfiguredProject(dispatch.sandbox.dispatchAttemptId, dispatch.lease.fencingToken, signal);
309
+ hostLease = await this.hostRuntime.startConfiguredProject(dispatch.sandbox.dispatchAttemptId, dispatch.lease.fencingToken, signal, async (checkpoint) => {
310
+ await emitLifecycle({
311
+ role: "runner",
312
+ kind: "checkpoint",
313
+ content: checkpoint.message,
314
+ eventType: checkpoint.code,
315
+ data: checkpoint.data ? JSON.stringify(checkpoint.data) : undefined,
316
+ });
317
+ });
242
318
  if (hostLease.lease.resources.ports.length > unconfiguredPortCount) {
243
319
  const assigned = hostLease.lease.resources.ports.at(-1);
244
320
  await emitLifecycle({
@@ -267,7 +343,7 @@ export class BridgeSessionService {
267
343
  throwIfAborted(signal);
268
344
  const draftArtifacts = run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : [];
269
345
  return redactRunOnceResultPacket({
270
- schemaVersion: 1,
346
+ schemaVersion: 2,
271
347
  exitCode: 0,
272
348
  summary: run.summary,
273
349
  events,
@@ -308,18 +384,21 @@ export class BridgeSessionService {
308
384
  }
309
385
  catch (error) {
310
386
  const message = failureMessage(error);
311
- if (message.includes("attempt_process_supervisor_start_")
312
- || message.includes("host_process_supervisor_start_")) {
313
- throw new Error(`provider_start_unavailable: ${message}`, { cause: error });
387
+ if (error instanceof ForgeFailureError
388
+ && (error.failureCode === "attempt_process_supervisor_start_failed"
389
+ || error.failureCode === "attempt_process_supervisor_start_timeout")) {
390
+ throw new ForgeFailureError("provider_start_unavailable", `provider_start_unavailable: ${message}`, "bridge", { cause: error });
314
391
  }
315
392
  throw error;
316
393
  }
317
394
  throwIfAborted(signal);
318
395
  const phaseArtifacts = run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : [];
319
396
  return redactRunOnceResultPacket({
320
- schemaVersion: 1,
397
+ schemaVersion: 2,
321
398
  exitCode: 0,
322
399
  summary: run.summary,
400
+ resultCommitSha: run.resultCommitSha ?? null,
401
+ publishedCommitSha: run.publishedCommitSha ?? null,
323
402
  events,
324
403
  spec: run.spec ?? null,
325
404
  artifact: run.artifact ?? null,
@@ -328,6 +407,25 @@ export class BridgeSessionService {
328
407
  }
329
408
  finally {
330
409
  if (hostLease && dispatch.sandbox) {
410
+ const providerMemory = this.hostRuntime?.providerMemoryTelemetry?.(dispatch.sandbox.dispatchAttemptId, dispatch.lease.fencingToken);
411
+ if (providerMemory) {
412
+ await emitLifecycle({
413
+ role: "runner",
414
+ kind: "checkpoint",
415
+ content: `Observed managed-host provider process-tree peak memory ${formatMemoryBytes(providerMemory.peakMemoryBytes)} from ${providerMemory.sampleCount} sample${providerMemory.sampleCount === 1 ? "" : "s"}.`,
416
+ eventType: "sandbox_peak_memory_observed",
417
+ occurredAt: providerMemory.observedAt,
418
+ data: JSON.stringify({
419
+ peakMemoryBytes: providerMemory.peakMemoryBytes,
420
+ sampleCount: providerMemory.sampleCount,
421
+ source: providerMemory.source,
422
+ containers: [{
423
+ role: "agent",
424
+ peakMemoryBytes: providerMemory.peakMemoryBytes,
425
+ }],
426
+ }),
427
+ }).catch(() => undefined);
428
+ }
331
429
  const teardownAt = new Date().toISOString();
332
430
  await emitLifecycle({
333
431
  role: "runner",
@@ -351,7 +449,7 @@ export class BridgeSessionService {
351
449
  await emitLifecycle({
352
450
  role: "runner",
353
451
  kind: "checkpoint",
354
- content: `Released host runtime lease ${released.leaseId}.`,
452
+ content: `Released host runtime lease ${released.leaseId}. Diagnostics remain runner-local.`,
355
453
  eventType: "sandbox_teardown_completed",
356
454
  occurredAt: released.releasedAt,
357
455
  }).catch(() => undefined);
@@ -370,7 +468,7 @@ export class BridgeSessionService {
370
468
  if (!hostLease && hostSingletonKeys && dispatch.sandbox) {
371
469
  await this.hostRuntime?.release(dispatch.sandbox.dispatchAttemptId, dispatch.lease.fencingToken).catch(() => undefined);
372
470
  }
373
- if (worktree)
471
+ if (worktree && !isPauseCancellation(signal))
374
472
  await this.worktrees.release(worktree);
375
473
  }
376
474
  }
@@ -378,6 +476,13 @@ export class BridgeSessionService {
378
476
  function failureMessage(error) {
379
477
  return error instanceof Error ? error.message : String(error);
380
478
  }
479
+ function formatMemoryBytes(bytes) {
480
+ return `${(bytes / (1024 ** 2)).toFixed(bytes >= 1024 ** 3 ? 0 : 1)} MiB`;
481
+ }
482
+ function isPauseCancellation(signal) {
483
+ return signal?.aborted === true
484
+ && String(signal.reason ?? "").trim().toLowerCase() === "session pause requested";
485
+ }
381
486
  function readinessProbeDispatch(request) {
382
487
  return {
383
488
  schemaVersion: 2,
@@ -433,7 +538,7 @@ function readinessProbeDispatch(request) {
433
538
  runnerRepoLocalPath: request.runnerRepoLocalPath,
434
539
  },
435
540
  runtimeIntent: { executionClass: "managed_runtime", applicationRuntimeRequired: true },
436
- serverLocal: { repoRoot: request.repoRoot, cleanupWorktrees: request.cleanupWorktrees },
541
+ serverLocal: { repoRoot: request.repoRoot, cleanupWorktrees: true },
437
542
  sandbox: {
438
543
  dispatchAttemptId: request.requestId,
439
544
  executionLeaseId: request.requestId,
@@ -552,6 +657,23 @@ function withPreparedQaReworkPacket(dispatch, packetPath) {
552
657
  };
553
658
  }
554
659
  async function emitDependencyWarmEvents(warm, emit) {
660
+ for (const receipt of warm.receipts) {
661
+ await emit({
662
+ role: "runner",
663
+ kind: "checkpoint",
664
+ content: receipt.cacheHit
665
+ ? `Reused verified ${receipt.ecosystem} dependency preparation receipt ${receipt.fingerprint} in ${receipt.packageRoot}.`
666
+ : `Published ${receipt.ecosystem} dependency preparation receipt ${receipt.fingerprint} in ${receipt.packageRoot}.`,
667
+ eventType: receipt.cacheHit ? "dependencies_preparation_cached" : "dependencies_preparation_receipt_published",
668
+ data: JSON.stringify({
669
+ ecosystem: receipt.ecosystem,
670
+ package_root: receipt.packageRoot,
671
+ cache_hit: receipt.cacheHit,
672
+ fingerprint: receipt.fingerprint,
673
+ receipt_path: receipt.receiptPath,
674
+ }),
675
+ });
676
+ }
555
677
  if (warm.linked.length > 0) {
556
678
  await emit({
557
679
  role: "runner",
@@ -94,6 +94,7 @@ export interface DispatchQaScope {
94
94
  readonly schema_version: number;
95
95
  readonly surface: readonly string[];
96
96
  readonly required_evidence: readonly string[];
97
+ readonly required_capabilities?: readonly string[];
97
98
  readonly not_applicable: readonly string[];
98
99
  readonly browser_required: boolean;
99
100
  readonly api_required: boolean;
@@ -152,6 +153,7 @@ export interface DispatchStoryDevelopContext {
152
153
  readonly dependencyWorkItemIds: readonly string[];
153
154
  }
154
155
  export interface DispatchStoryQaContext {
156
+ readonly references?: DispatchStoryQaReferenceBundle | null;
155
157
  readonly storyWorkItemId: string;
156
158
  readonly planId: string;
157
159
  readonly planContentHash: string;
@@ -163,6 +165,24 @@ export interface DispatchStoryQaContext {
163
165
  readonly childTasks: readonly DispatchStoryQaChildTaskContext[];
164
166
  readonly dependencies: readonly DispatchStoryQaDependencyContext[];
165
167
  }
168
+ export interface DispatchStoryQaReferenceBundle {
169
+ readonly schemaVersion: number;
170
+ readonly cycleRunId: string;
171
+ readonly storyWorkItemId: string;
172
+ readonly projectId: string;
173
+ readonly repoId: string;
174
+ readonly planId: string;
175
+ readonly planContentHash: string;
176
+ readonly effectiveAmendmentHash: string;
177
+ readonly capturedAt: string;
178
+ readonly files: readonly DispatchStoryQaReferenceFile[];
179
+ }
180
+ export interface DispatchStoryQaReferenceFile {
181
+ readonly path: string;
182
+ readonly body: string;
183
+ readonly contentHash: string;
184
+ readonly provenance: string;
185
+ }
166
186
  export interface DispatchStoryQaChildTaskContext {
167
187
  readonly workItemId: string;
168
188
  readonly title: string;
@@ -263,6 +283,28 @@ export interface DispatchPullRequestContext {
263
283
  readonly unresolvedThreads: number;
264
284
  readonly humanApproved: boolean;
265
285
  readonly hasMergeConflicts: boolean;
286
+ readonly headSha: string;
287
+ readonly reviewIssueKey?: string | null;
288
+ readonly reviewFindings: readonly DispatchPullRequestReviewFinding[];
289
+ readonly openWorkOrderStories: readonly DispatchWorkOrderStory[];
290
+ }
291
+ export interface DispatchWorkOrderStory {
292
+ readonly workItemId: string;
293
+ readonly title: string;
294
+ readonly acceptanceCriteria: string;
295
+ }
296
+ export interface DispatchPullRequestReviewFinding {
297
+ readonly claimId: string;
298
+ readonly kind: string;
299
+ readonly bodySha256: string;
300
+ readonly body: string;
301
+ readonly contentAvailable: boolean;
302
+ readonly actionable: boolean;
303
+ readonly actionabilityReason: string;
304
+ readonly author?: string | null;
305
+ readonly url?: string | null;
306
+ readonly path?: string | null;
307
+ readonly line?: number | null;
266
308
  }
267
309
  export interface DispatchResolvedArtifact {
268
310
  readonly kind: string;
@@ -275,6 +317,7 @@ export interface DispatchResolvedArtifact {
275
317
  readonly event?: string | null;
276
318
  }
277
319
  export interface DispatchWorkItem {
320
+ readonly projectId?: string | null;
278
321
  readonly id: string;
279
322
  readonly kind: string;
280
323
  readonly title: string;
@@ -292,6 +335,7 @@ export interface DispatchRepo {
292
335
  readonly serverLocalPath?: string | null;
293
336
  }
294
337
  export interface DispatchCycleRun {
338
+ readonly effectivePlanAmendmentHash?: string | null;
295
339
  readonly id: string;
296
340
  readonly ownerType?: string | null;
297
341
  readonly ownerId?: string | null;
@@ -301,6 +345,7 @@ export interface DispatchCycleRun {
301
345
  readonly requestedBase: string;
302
346
  readonly baseBranch: string;
303
347
  readonly expectedCurrentBranch?: string | null;
348
+ readonly repairHeadSha?: string | null;
304
349
  readonly attempt: number;
305
350
  readonly model: string;
306
351
  }
@@ -308,6 +353,8 @@ export interface DispatchSession {
308
353
  readonly id: string;
309
354
  readonly model: string;
310
355
  readonly lane: string;
356
+ readonly resumedFromSessionId?: string | null;
357
+ readonly resumeProviderThreadId?: string | null;
311
358
  }
312
359
  export interface DispatchLease {
313
360
  readonly id: string;
@@ -330,6 +377,8 @@ export interface DispatchExecution {
330
377
  readonly lane: string;
331
378
  readonly requiresApiKeySecretGrant: boolean;
332
379
  readonly runnerRepoLocalPath?: string | null;
380
+ /** Reasoning effort from the Agents tab; null means the provider CLI's default. */
381
+ readonly effort?: string | null;
333
382
  }
334
383
  export interface ApiKeySecretGrant {
335
384
  readonly grantId: string;
@@ -347,6 +396,8 @@ export interface DispatchServerLocalConfig {
347
396
  readonly cleanupWorktrees: boolean;
348
397
  }
349
398
  export interface RunnerDispatchEvent {
399
+ /** Stable across callback retries; persisted atomically with the session event. */
400
+ readonly eventId?: string | null;
350
401
  readonly role: string;
351
402
  readonly kind: string;
352
403
  readonly content: string;
@@ -355,6 +406,22 @@ export interface RunnerDispatchEvent {
355
406
  readonly occurredAt?: string | null;
356
407
  /** Optional structured telemetry payload (JSON string), e.g. token usage for provider_usage events. */
357
408
  readonly data?: string | null;
409
+ /** Versioned nonterminal wait evidence. Legacy writers omit this field. */
410
+ readonly wait?: WaitEnvelope | null;
411
+ }
412
+ export interface WaitEnvelope {
413
+ readonly schemaVersion: 2;
414
+ readonly kind: string;
415
+ readonly scope: string;
416
+ readonly resourceKeys: readonly string[];
417
+ readonly queuePosition?: number | null;
418
+ readonly blockerSessionId?: string | null;
419
+ readonly blockerTitle?: string | null;
420
+ readonly wakeCondition: string;
421
+ readonly retryAt?: string | null;
422
+ readonly firstObservedAt: string;
423
+ readonly observationVersion: number;
424
+ readonly contractOrigin: "bridge_v2";
358
425
  }
359
426
  export interface RunnerDispatchSpec {
360
427
  readonly repoPath: string;
@@ -376,12 +443,74 @@ export interface RunnerDispatchArtifact {
376
443
  readonly producedBy: string;
377
444
  readonly status: string;
378
445
  readonly phase?: string | null;
446
+ /**
447
+ * QA sidecars are transported with the terminal result because managed
448
+ * worktrees may be released before the API mirrors the result. The API
449
+ * validates these bytes against the canonical evidence metadata and stores
450
+ * only that metadata.
451
+ */
452
+ readonly sidecars?: readonly RunnerDispatchSidecar[] | null;
453
+ }
454
+ export interface RunnerDispatchSidecar {
455
+ readonly path: string;
456
+ readonly contentBase64: string;
457
+ readonly contentHash: string;
458
+ readonly bytes: number;
459
+ }
460
+ export type FailureCategory = "domain" | "provider" | "transport" | "cancellation" | "ownership" | "resource" | "unknown";
461
+ export type FailurePhase = "offer" | "build" | "prepare" | "agent" | "validation" | "import" | "teardown" | "unknown";
462
+ export type FailureSource = "bridge" | "provider" | "sandbox" | "unknown";
463
+ export type FailureRetryability = "retryable" | "non_retryable" | "policy_required" | "unknown";
464
+ export type FailureOperatorAction = "none" | "retry_dispatch" | "retry_with_policy" | "inspect_failure" | "fix_input" | "fix_environment" | "reauthenticate" | "restore_ownership";
465
+ export interface FailureCause {
466
+ readonly code: string;
467
+ readonly message: string;
468
+ readonly source: FailureSource;
469
+ }
470
+ export interface FailureEnvelope {
471
+ readonly schemaVersion: 2;
472
+ readonly code: string;
473
+ readonly message: string;
474
+ readonly category: FailureCategory;
475
+ readonly phase: FailurePhase;
476
+ readonly source: FailureSource;
477
+ readonly retryability: FailureRetryability;
478
+ readonly operatorAction: FailureOperatorAction;
479
+ readonly causalChain: readonly FailureCause[];
480
+ readonly occurredAt: string;
481
+ readonly contractOrigin: "bridge_v2";
482
+ }
483
+ export interface FailureFacts {
484
+ readonly code: string;
485
+ readonly category: FailureCategory;
486
+ readonly phase: FailurePhase;
487
+ readonly source: FailureSource;
488
+ readonly retryability: FailureRetryability;
489
+ readonly operatorAction: FailureOperatorAction;
490
+ readonly causalChain?: readonly FailureCause[];
491
+ }
492
+ export interface FailurePolicy {
493
+ readonly code: string;
494
+ readonly category: FailureCategory;
495
+ readonly phase: FailurePhase;
496
+ readonly retryability: FailureRetryability;
497
+ readonly operatorAction: FailureOperatorAction;
498
+ readonly allowedSources: readonly FailureSource[];
499
+ readonly known: boolean;
500
+ }
501
+ export declare class ForgeFailureError extends Error {
502
+ readonly failureCode: string;
503
+ readonly failureSource: FailureSource;
504
+ constructor(failureCode: string, message: string, failureSource?: FailureSource, options?: ErrorOptions);
379
505
  }
380
506
  export interface RunOnceResultPacket {
381
- readonly schemaVersion: 1;
507
+ readonly schemaVersion: 1 | 2;
382
508
  readonly exitCode: number;
383
509
  readonly summary: string;
510
+ readonly resultCommitSha?: string | null;
511
+ readonly publishedCommitSha?: string | null;
384
512
  readonly events: readonly RunnerDispatchEvent[];
513
+ readonly failure?: FailureEnvelope | null;
385
514
  readonly spec?: RunnerDispatchSpec | null;
386
515
  readonly artifact?: RunnerDispatchArtifact | null;
387
516
  readonly artifacts?: readonly RunnerDispatchArtifact[] | null;
@@ -391,6 +520,9 @@ export interface RunnerEventLine {
391
520
  readonly event: RunnerDispatchEvent;
392
521
  }
393
522
  export declare function parseRunOnceDispatchPacket(value: unknown): RunOnceDispatchPacket;
523
+ export declare function parseRunOnceResultPacket(value: unknown): RunOnceResultPacket;
524
+ export declare function waitKindForEvent(eventType: string): string;
525
+ export declare function waitWakeCondition(kind: string): string;
394
526
  export interface HostRuntimeReadinessProbeRequest {
395
527
  readonly requestId: string;
396
528
  readonly fencingToken: string;
@@ -423,4 +555,17 @@ export interface HostRuntimeReadinessQaLaunchTarget {
423
555
  readonly managedRuntimePort: string;
424
556
  readonly routes: readonly string[];
425
557
  }
426
- export declare function failureResult(summary: string, events?: readonly RunnerDispatchEvent[]): RunOnceResultPacket;
558
+ export declare function failureResult(summary: string, events?: readonly RunnerDispatchEvent[], facts?: FailureFacts): RunOnceResultPacket;
559
+ export declare function createFailureEnvelope(message: string, facts: FailureFacts, occurredAt?: string): FailureEnvelope;
560
+ export declare function failureFactsFromError(error: unknown, options?: {
561
+ readonly cancelled?: boolean;
562
+ readonly source?: FailureSource;
563
+ }): FailureFacts;
564
+ export declare function failureFactsForCode(code: string, source?: FailureSource): FailureFacts;
565
+ export declare function failurePolicyForCode(code: string): FailurePolicy;
566
+ /**
567
+ * Rebuilds an error graph at a privileged execution boundary without
568
+ * retaining the raw error or any raw recursive cause. Forge failure metadata
569
+ * survives so the P7 taxonomy remains authoritative after sanitization.
570
+ */
571
+ export declare function sanitizeFailureError(error: unknown, exactSecrets?: readonly string[]): Error;