@loucompanion/forge-bridge 0.1.3-dev.b3bce6ba65a6 → 0.1.3-dev.ba293ccf876b

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 (189) hide show
  1. package/README.md +127 -0
  2. package/dist/bridge/bridge.base.d.ts +8 -1
  3. package/dist/bridge/bridge.service.d.ts +98 -3
  4. package/dist/bridge/bridge.service.js +1425 -45
  5. package/dist/bridge/bridge.types.d.ts +205 -1
  6. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  7. package/dist/bridge/readiness-result-outbox.js +54 -0
  8. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  9. package/dist/bridge/readiness-result-worker.js +100 -0
  10. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  11. package/dist/bridge/runner-host-capabilities.js +93 -0
  12. package/dist/cleanup/cleanup.service.d.ts +14 -2
  13. package/dist/cleanup/cleanup.service.js +496 -3
  14. package/dist/cleanup/cleanup.types.d.ts +10 -0
  15. package/dist/cleanup/index.d.ts +1 -1
  16. package/dist/cleanup/index.js +1 -1
  17. package/dist/cleanup/internals.d.ts +2 -2
  18. package/dist/cleanup/internals.js +1 -1
  19. package/dist/cli/cli.help.js +47 -1
  20. package/dist/cli/cli.service.d.ts +45 -2
  21. package/dist/cli/cli.service.js +377 -27
  22. package/dist/cli/local-config.d.ts +5 -0
  23. package/dist/cli/local-config.js +45 -1
  24. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  25. package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
  26. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  27. package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
  28. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  29. package/dist/runtime/attempt-path-operation.js +94 -0
  30. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  31. package/dist/runtime/attempt-private-directory.js +54 -0
  32. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  33. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  34. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  35. package/dist/runtime/attempt-process-supervisor.js +386 -0
  36. package/dist/runtime/canonical-source-file.d.ts +3 -0
  37. package/dist/runtime/canonical-source-file.js +54 -0
  38. package/dist/runtime/docker-engine-probe.d.ts +83 -0
  39. package/dist/runtime/docker-engine-probe.js +300 -0
  40. package/dist/runtime/generated-output-reset.d.ts +10 -0
  41. package/dist/runtime/generated-output-reset.js +103 -0
  42. package/dist/runtime/host-diagnostics.d.ts +4 -0
  43. package/dist/runtime/host-diagnostics.js +95 -0
  44. package/dist/runtime/host-listener-inspector.d.ts +46 -0
  45. package/dist/runtime/host-listener-inspector.js +349 -0
  46. package/dist/runtime/host-port-broker.d.ts +76 -0
  47. package/dist/runtime/host-port-broker.js +333 -0
  48. package/dist/runtime/host-runtime-contract.d.ts +56 -0
  49. package/dist/runtime/host-runtime-contract.js +709 -0
  50. package/dist/runtime/host-runtime-lease.d.ts +201 -0
  51. package/dist/runtime/host-runtime-lease.js +2314 -0
  52. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  53. package/dist/runtime/host-runtime-promotion.js +206 -0
  54. package/dist/runtime/memory-capacity.d.ts +68 -0
  55. package/dist/runtime/memory-capacity.js +121 -0
  56. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  57. package/dist/runtime/node-host-runtime-adapter.js +284 -0
  58. package/dist/runtime/pinned-directory-process.d.ts +4 -0
  59. package/dist/runtime/pinned-directory-process.js +20 -0
  60. package/dist/runtime/pinned-source-directory.d.ts +9 -0
  61. package/dist/runtime/pinned-source-directory.js +56 -0
  62. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  63. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  64. package/dist/runtime/prepared-runtime-identity.d.ts +3 -0
  65. package/dist/runtime/prepared-runtime-identity.js +191 -0
  66. package/dist/runtime/prepared-runtime-readiness.d.ts +22 -0
  67. package/dist/runtime/prepared-runtime-readiness.js +191 -0
  68. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  69. package/dist/runtime/runtime-isolation-lease.js +158 -0
  70. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  71. package/dist/runtime/workspace-resource-estimator.js +174 -0
  72. package/dist/sandbox/command-runner.d.ts +4 -0
  73. package/dist/sandbox/command-runner.js +47 -0
  74. package/dist/sandbox/contract.d.ts +5 -0
  75. package/dist/sandbox/contract.js +276 -0
  76. package/dist/sandbox/event-budget.d.ts +11 -0
  77. package/dist/sandbox/event-budget.js +27 -0
  78. package/dist/sandbox/image-policy.d.ts +35 -0
  79. package/dist/sandbox/image-policy.js +247 -0
  80. package/dist/sandbox/index.d.ts +5 -0
  81. package/dist/sandbox/index.js +5 -0
  82. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  83. package/dist/sandbox/lease-watchdog.js +67 -0
  84. package/dist/sandbox/preflight.d.ts +84 -0
  85. package/dist/sandbox/preflight.js +521 -0
  86. package/dist/sandbox/process.d.ts +20 -0
  87. package/dist/sandbox/process.js +209 -0
  88. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  89. package/dist/sandbox/provider-auth/codex.js +212 -0
  90. package/dist/sandbox/repository-lock.d.ts +5 -0
  91. package/dist/sandbox/repository-lock.js +121 -0
  92. package/dist/sandbox/runtime.d.ts +297 -0
  93. package/dist/sandbox/runtime.js +2338 -0
  94. package/dist/sandbox/sandbox.types.d.ts +93 -0
  95. package/dist/sandbox/sandbox.types.js +1 -0
  96. package/dist/sandbox/smoke.d.ts +76 -0
  97. package/dist/sandbox/smoke.js +380 -0
  98. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  99. package/dist/sandbox/worker-supervisor.js +36 -0
  100. package/dist/sandbox/worker.d.ts +1 -0
  101. package/dist/sandbox/worker.js +225 -0
  102. package/dist/sandbox/workspace.service.d.ts +13 -0
  103. package/dist/sandbox/workspace.service.js +72 -0
  104. package/dist/session/managed-attempt-tools.d.ts +21 -0
  105. package/dist/session/managed-attempt-tools.js +912 -0
  106. package/dist/session/managed-build-coordinator.d.ts +5 -0
  107. package/dist/session/managed-build-coordinator.js +105 -0
  108. package/dist/session/managed-build-receipt.d.ts +21 -0
  109. package/dist/session/managed-build-receipt.js +335 -0
  110. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  111. package/dist/session/phase-completion.artifacts.js +256 -26
  112. package/dist/session/phase-completion.d.ts +12 -2
  113. package/dist/session/phase-completion.helpers.d.ts +5 -0
  114. package/dist/session/phase-completion.helpers.js +51 -10
  115. package/dist/session/phase-completion.js +14 -3
  116. package/dist/session/phase-completion.prompts.d.ts +3 -3
  117. package/dist/session/phase-completion.prompts.js +24 -8
  118. package/dist/session/phase-completion.runtime.d.ts +13 -1
  119. package/dist/session/phase-completion.runtime.js +189 -8
  120. package/dist/session/phase-completion.schemas.js +117 -4
  121. package/dist/session/phase-completion.validation.d.ts +4 -1
  122. package/dist/session/phase-completion.validation.js +153 -22
  123. package/dist/session/pre-publication-validation.d.ts +47 -0
  124. package/dist/session/pre-publication-validation.js +249 -0
  125. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  126. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  127. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
  128. package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
  129. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  130. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
  131. package/dist/session/provider-cli/provider-cli.deterministic.js +68 -9
  132. package/dist/session/provider-cli/provider-cli.process.d.ts +26 -0
  133. package/dist/session/provider-cli/provider-cli.process.js +474 -23
  134. package/dist/session/provider-cli/provider-cli.prompts.d.ts +14 -2
  135. package/dist/session/provider-cli/provider-cli.prompts.js +147 -25
  136. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
  137. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  138. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  139. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  140. package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
  141. package/dist/session/provider-cli/provider-cli.service.d.ts +25 -2
  142. package/dist/session/provider-cli/provider-cli.service.js +636 -84
  143. package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
  144. package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
  145. package/dist/session/provider-cli/provider-output-state.js +91 -16
  146. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  147. package/dist/session/provider-cli/provider-usage.js +28 -5
  148. package/dist/session/session-redaction.d.ts +3 -0
  149. package/dist/session/session-redaction.js +38 -0
  150. package/dist/session/session.base.d.ts +6 -1
  151. package/dist/session/session.service.d.ts +8 -2
  152. package/dist/session/session.service.js +531 -42
  153. package/dist/session/session.types.d.ts +115 -1
  154. package/dist/session/session.types.js +194 -3
  155. package/dist/shared/format/format-bytes.d.ts +1 -0
  156. package/dist/shared/format/format-bytes.js +9 -0
  157. package/dist/shared/git/git.js +2 -1
  158. package/dist/shared/path/path.d.ts +1 -0
  159. package/dist/shared/path/path.js +21 -1
  160. package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
  161. package/dist/shared/process/dotnet-toolchain.js +179 -0
  162. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  163. package/dist/shared/process/host-process-recovery.js +78 -0
  164. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  165. package/dist/shared/process/host-process-supervisor.js +330 -0
  166. package/dist/shared/process/process.d.ts +2 -0
  167. package/dist/shared/process/process.js +11 -6
  168. package/dist/shared/provider-capabilities.d.ts +1 -0
  169. package/dist/shared/provider-capabilities.js +3 -0
  170. package/dist/shared/redaction/redaction.js +12 -2
  171. package/dist/version.d.ts +1 -1
  172. package/dist/version.js +1 -1
  173. package/dist/worktree/artifact-injector.d.ts +3 -2
  174. package/dist/worktree/artifact-injector.js +43 -4
  175. package/dist/worktree/dependency-warmer.d.ts +28 -1
  176. package/dist/worktree/dependency-warmer.js +308 -9
  177. package/dist/worktree/index.d.ts +2 -1
  178. package/dist/worktree/index.js +1 -0
  179. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  180. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  181. package/dist/worktree/worktree-paths.d.ts +4 -0
  182. package/dist/worktree/worktree-paths.js +30 -0
  183. package/dist/worktree/worktree.base.d.ts +3 -0
  184. package/dist/worktree/worktree.service.d.ts +14 -2
  185. package/dist/worktree/worktree.service.js +296 -47
  186. package/dist/worktree/worktree.types.d.ts +5 -1
  187. package/package.json +8 -4
  188. package/runtime/windows-job-supervisor.ps1 +444 -0
  189. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -1,7 +1,39 @@
1
- import type { RunOnceResultPacket, RunnerDispatchEvent } from "../session/session.types.js";
1
+ import type { ApiKeySecretGrant, DispatchSandboxResourceRequest, RunOnceResultPacket, RunnerDispatchEvent } from "../session/session.types.js";
2
+ import type { SandboxFeatureMatrix } from "../sandbox/sandbox.types.js";
3
+ import type { HostRuntimeCapacitySnapshot } from "../runtime/host-runtime-lease.js";
4
+ import type { MemoryAdmissionDiagnostics } from "../runtime/memory-capacity.js";
5
+ import type { DockerEngineCapabilitySnapshot } from "../runtime/docker-engine-probe.js";
2
6
  export interface BridgeServiceConfig {
3
7
  readonly serverUrl: string;
4
8
  readonly runnerId: string;
9
+ readonly runnerInstanceId: string;
10
+ readonly trustDomainId: string;
11
+ readonly maxConcurrentSandboxes: number;
12
+ readonly maxConcurrentSandboxBuilds: number;
13
+ readonly sandboxPidCapacity: number;
14
+ readonly dockerEnabled: boolean;
15
+ readonly allowOpenPublicSandbox: boolean;
16
+ readonly sandboxPublicationMode?: "bridge_push" | "shared_filesystem";
17
+ readonly dockerExecutable: string;
18
+ readonly dockerContext: string;
19
+ readonly dockerDesktopRecoveryEnabled: boolean;
20
+ readonly dockerRecoveryFailureThreshold: number;
21
+ readonly dockerRecoveryCooldownMs: number;
22
+ readonly dockerRecoveryStartupTimeoutMs: number;
23
+ readonly sandboxStateRoot: string;
24
+ readonly requireRootlessDocker: boolean;
25
+ readonly diskHighWatermarkPercent: number;
26
+ readonly diskLowWatermarkPercent: number;
27
+ readonly minimumWorkspaceBytes: number;
28
+ readonly sandboxHostMemoryReserveBytes?: number;
29
+ readonly defaultSandboxWorkerMemoryLimitBytes: number;
30
+ readonly defaultSandboxServiceMemoryLimitBytes: number;
31
+ readonly sandboxRuntimeOverheadBytes: number;
32
+ readonly sandboxMemorySampleIntervalMs: number;
33
+ readonly imageRetentionCount: number;
34
+ readonly imageRetentionTtlMs: number;
35
+ readonly auditRetentionCount: number;
36
+ readonly auditRetentionTtlMs: number;
5
37
  readonly deviceToken: string;
6
38
  readonly deviceName: string;
7
39
  readonly defaultRepoRoot: string;
@@ -23,6 +55,7 @@ export interface BridgeServiceConfig {
23
55
  readonly protocolVersion: number;
24
56
  readonly minServerVersion?: string | null;
25
57
  readonly heartbeatIntervalMs: number;
58
+ readonly callbackAckTimeoutMs?: number;
26
59
  readonly reconnectBackoffMs: readonly number[];
27
60
  }
28
61
  export interface BridgeRepoMapping {
@@ -37,6 +70,7 @@ export interface QaAppLaunchTarget {
37
70
  readonly command?: string | null;
38
71
  readonly working_directory?: string | null;
39
72
  readonly ready_url?: string | null;
73
+ readonly managed_runtime_port?: string | null;
40
74
  readonly routes?: readonly string[] | null;
41
75
  readonly notes?: string | null;
42
76
  }
@@ -45,6 +79,20 @@ export interface BridgeProviderStatus {
45
79
  readonly version?: string | null;
46
80
  readonly models?: readonly string[] | null;
47
81
  }
82
+ export interface BridgeToolCapability {
83
+ readonly available: boolean;
84
+ readonly executable: string;
85
+ readonly version?: string | null;
86
+ }
87
+ export interface BridgeToolchainCapabilities {
88
+ readonly node: BridgeToolCapability;
89
+ readonly pnpm: BridgeToolCapability;
90
+ readonly dotnet: BridgeToolCapability;
91
+ readonly git: BridgeToolCapability;
92
+ readonly bash: BridgeToolCapability;
93
+ readonly powershell: BridgeToolCapability;
94
+ readonly docker?: BridgeToolCapability | null;
95
+ }
48
96
  export interface BridgeCapabilities {
49
97
  readonly schemaVersion?: number | null;
50
98
  readonly activeSessionCount?: number | null;
@@ -54,6 +102,30 @@ export interface BridgeCapabilities {
54
102
  readonly repoMappings?: readonly BridgeRepoMapping[] | null;
55
103
  readonly os?: string | null;
56
104
  readonly sandboxProfiles?: readonly string[] | null;
105
+ readonly runnerInstanceId?: string | null;
106
+ readonly trustDomainId?: string | null;
107
+ readonly executionFacilities?: readonly string[] | null;
108
+ readonly dockerEngine?: DockerEngineCapabilitySnapshot | null;
109
+ readonly sandboxRuntimes?: readonly string[] | null;
110
+ readonly sandboxRuntimeVersion?: string | null;
111
+ readonly sandboxFeatures?: SandboxFeatureMatrix | null;
112
+ readonly allowsOpenPublicSandbox?: boolean | null;
113
+ readonly sandboxPublicationModes?: readonly string[] | null;
114
+ readonly sandboxAuthModes?: Readonly<Record<string, readonly string[]>> | null;
115
+ readonly maxConcurrentSandboxes?: number | null;
116
+ readonly reservedSandboxCount?: number | null;
117
+ readonly activeSandboxCount?: number | null;
118
+ readonly startingSandboxCount?: number | null;
119
+ readonly degradedReservationCount?: number | null;
120
+ readonly availableMemoryBytes?: number | null;
121
+ readonly availableCpuMillis?: number | null;
122
+ readonly availableWorkspaceBytes?: number | null;
123
+ readonly diskLow?: boolean | null;
124
+ readonly preflightGeneration?: number | null;
125
+ readonly sandboxUnavailableCode?: string | null;
126
+ readonly sandboxUnavailableReason?: string | null;
127
+ readonly sandboxMemoryCapacity?: SandboxMemoryCapacityDiagnostics | null;
128
+ readonly hostRuntime?: HostRuntimeCapacitySnapshot | null;
57
129
  readonly supportsApiSecretGrant?: boolean | null;
58
130
  readonly supportsManagedWorktrees?: boolean | null;
59
131
  readonly supportsManagedForgeArtifacts?: boolean | null;
@@ -61,12 +133,42 @@ export interface BridgeCapabilities {
61
133
  readonly supportsDraftRunOnce?: boolean | null;
62
134
  readonly supportsDraftAttachments?: boolean | null;
63
135
  readonly supportsDraftImageAttachments?: boolean | null;
136
+ readonly toolchains?: BridgeToolchainCapabilities | null;
137
+ readonly playwrightProjects?: readonly string[] | null;
138
+ readonly browserLaunch?: boolean | null;
139
+ readonly providerConnectivity?: Readonly<Record<string, boolean>> | null;
140
+ }
141
+ export interface SandboxMemoryCapacityDiagnostics {
142
+ readonly defaultWorkerMemoryLimitBytes: number;
143
+ readonly defaultServiceMemoryLimitBytes: number;
144
+ readonly runtimeOverheadBytes: number;
145
+ readonly hostReserveBytes: number;
146
+ readonly activeReservationBytes: number;
147
+ readonly daemonTotalBytes: number;
148
+ readonly daemonAvailableBytes: number;
149
+ readonly hostAvailableBytes: number;
150
+ readonly conservativeAvailableBytes: number;
151
+ readonly availableForAdmissionBytes: number;
152
+ readonly memoryAdmission: MemoryAdmissionDiagnostics;
64
153
  }
65
154
  export interface BridgeHelloRequest {
66
155
  readonly version: string;
67
156
  readonly protocolVersion: number;
68
157
  readonly capabilities: BridgeCapabilities;
69
158
  readonly minServerVersion?: string | null;
159
+ readonly runnerInstanceId: string;
160
+ }
161
+ export interface BridgePublishCredential {
162
+ readonly username: string;
163
+ readonly secret: string;
164
+ readonly remoteUrl: string;
165
+ readonly expiresAt: string;
166
+ }
167
+ export interface BridgePublishGrantRedemptionResponse {
168
+ readonly accepted: boolean;
169
+ readonly code?: string | null;
170
+ readonly message?: string | null;
171
+ readonly credential?: BridgePublishCredential | null;
70
172
  }
71
173
  export interface BridgeHeartbeatRequest {
72
174
  readonly status?: string | null;
@@ -82,17 +184,119 @@ export interface BridgeCallbackAck {
82
184
  export interface BridgeDispatchEventCallback {
83
185
  readonly leaseId: string;
84
186
  readonly fencingToken: string;
187
+ readonly dispatchAttemptId?: string | null;
188
+ readonly runnerInstanceId?: string | null;
85
189
  readonly event: RunnerDispatchEvent;
86
190
  }
87
191
  export interface BridgeDispatchResultCallback {
88
192
  readonly leaseId: string;
89
193
  readonly fencingToken: string;
194
+ readonly dispatchAttemptId?: string | null;
195
+ readonly runnerInstanceId?: string | null;
90
196
  readonly result: RunOnceResultPacket;
91
197
  }
198
+ export interface BridgeOfferDecisionCallback {
199
+ readonly dispatchAttemptId: string;
200
+ readonly leaseId: string;
201
+ readonly fencingToken: string;
202
+ readonly runnerInstanceId: string;
203
+ readonly accepted: boolean;
204
+ readonly code?: string | null;
205
+ readonly message?: string | null;
206
+ readonly reservedUntil?: string | null;
207
+ readonly reservedResources?: DispatchSandboxResourceRequest | null;
208
+ readonly sandboxConfigCommitSha?: string | null;
209
+ }
210
+ export interface BridgeDispatchStartedCallback {
211
+ readonly dispatchAttemptId: string;
212
+ readonly leaseId: string;
213
+ readonly fencingToken: string;
214
+ readonly runnerInstanceId: string;
215
+ }
216
+ export interface BridgeSecretGrantRedemptionRequest {
217
+ readonly dispatchAttemptId: string;
218
+ readonly leaseId: string;
219
+ readonly fencingToken: string;
220
+ readonly runnerInstanceId: string;
221
+ readonly grantId: string;
222
+ }
223
+ export interface BridgeSecretGrantRedemptionResponse extends BridgeCallbackAck {
224
+ readonly grant?: ApiKeySecretGrant | null;
225
+ }
226
+ export interface BridgeLeaseUpdateRequest {
227
+ readonly dispatchAttemptId: string;
228
+ readonly leaseId: string;
229
+ readonly fencingToken: string;
230
+ readonly runnerInstanceId: string;
231
+ readonly expiresAt: string;
232
+ }
92
233
  export interface BridgeCancelRequest {
93
234
  readonly leaseId: string;
94
235
  readonly fencingToken: string;
95
236
  readonly reason?: string | null;
96
237
  }
238
+ export interface BridgeReapRequest {
239
+ readonly runnerId: string;
240
+ readonly runnerInstanceId: string;
241
+ readonly requestedAt: string;
242
+ readonly reason?: string | null;
243
+ }
244
+ export interface BridgeAttemptSnapshotItem {
245
+ readonly dispatchAttemptId: string;
246
+ readonly fencingToken: string;
247
+ readonly runnerInstanceId: string;
248
+ readonly status: string;
249
+ readonly expiresAt: string;
250
+ }
251
+ export interface BridgeAttemptSnapshotResponse extends BridgeCallbackAck {
252
+ readonly attempts?: readonly BridgeAttemptSnapshotItem[] | null;
253
+ readonly generatedAt?: string | null;
254
+ }
97
255
  export type BridgeDispatchHandler = (dispatch: unknown) => Promise<void>;
256
+ export type BridgeOfferHandler = (dispatch: unknown) => Promise<void>;
257
+ export type BridgeStartHandler = (dispatch: unknown) => Promise<void>;
258
+ export type BridgeLeaseUpdateHandler = (request: BridgeLeaseUpdateRequest) => Promise<void>;
98
259
  export type BridgeCancelHandler = (request: BridgeCancelRequest) => Promise<void>;
260
+ export type BridgeReapHandler = (request: BridgeReapRequest) => Promise<void>;
261
+ export type BridgeReadinessProbeHandler = (request: unknown) => Promise<void>;
262
+ export interface BridgeRuntimeReadinessRequest {
263
+ readonly schemaVersion: 1;
264
+ readonly requestId: string;
265
+ readonly fencingToken: string;
266
+ readonly userId: string;
267
+ readonly projectId: string;
268
+ readonly repoId: string;
269
+ readonly runnerInstanceId: string;
270
+ readonly attempt: number;
271
+ readonly repoName: string;
272
+ readonly remoteUrl: string;
273
+ readonly defaultBranch: string;
274
+ readonly cloneOnDemand: boolean;
275
+ readonly runnerRepoLocalPath: string;
276
+ readonly revision: string;
277
+ readonly expiresAt: string;
278
+ }
279
+ export interface BridgeRuntimeReadinessResult {
280
+ readonly schemaVersion: 1;
281
+ readonly requestId: string;
282
+ readonly fencingToken: string;
283
+ readonly runnerInstanceId: string;
284
+ readonly status: "parallel_ready" | "serialized" | "failed";
285
+ readonly runtimeContractFingerprint?: string | null;
286
+ readonly runtimeContractSource?: string | null;
287
+ readonly reasonCode?: string | null;
288
+ readonly reasonMessage?: string | null;
289
+ readonly qaAppLaunchTarget?: BridgeRuntimeReadinessQaLaunchTarget | null;
290
+ readonly checkedAt: string;
291
+ }
292
+ export interface BridgeRuntimeReadinessAccepted {
293
+ readonly schemaVersion: 1;
294
+ readonly requestId: string;
295
+ readonly fencingToken: string;
296
+ readonly runnerInstanceId: string;
297
+ readonly acceptedAt: string;
298
+ }
299
+ export interface BridgeRuntimeReadinessQaLaunchTarget {
300
+ readonly managedRuntimePort: string;
301
+ readonly routes: readonly string[];
302
+ }
@@ -0,0 +1,21 @@
1
+ import type { BridgeRuntimeReadinessResult } from "./bridge.types.js";
2
+ export interface DurableReadinessResult {
3
+ readonly schemaVersion: 1;
4
+ readonly expiresAt: string;
5
+ readonly persistedAt: string;
6
+ readonly result: BridgeRuntimeReadinessResult;
7
+ }
8
+ export interface ReadinessResultOutbox {
9
+ persist(result: BridgeRuntimeReadinessResult, expiresAt: string): Promise<void>;
10
+ pending(): Promise<readonly DurableReadinessResult[]>;
11
+ acknowledge(requestId: string): Promise<void>;
12
+ }
13
+ export declare class FileReadinessResultOutbox implements ReadinessResultOutbox {
14
+ private readonly stateRoot;
15
+ private readonly writes;
16
+ constructor(stateRoot: string);
17
+ persist(result: BridgeRuntimeReadinessResult, expiresAt: string): Promise<void>;
18
+ pending(): Promise<readonly DurableReadinessResult[]>;
19
+ acknowledge(requestId: string): Promise<void>;
20
+ private operate;
21
+ }
@@ -0,0 +1,54 @@
1
+ import path from "node:path";
2
+ import { isDeepStrictEqual } from "node:util";
3
+ import { assertCanonicalDirectory, createCanonicalDirectory } from "../runtime/attempt-private-directory.js";
4
+ import { runPinnedDirectoryProcess } from "../runtime/pinned-directory-process.js";
5
+ import { readinessReceiptWorker } from "./readiness-result-worker.js";
6
+ export class FileReadinessResultOutbox {
7
+ stateRoot;
8
+ writes = new Map();
9
+ constructor(stateRoot) {
10
+ this.stateRoot = stateRoot;
11
+ }
12
+ async persist(result, expiresAt) {
13
+ const name = recordName(result.requestId);
14
+ // Capture caller-owned data before yielding. Compare semantic JSON, not key order.
15
+ const value = JSON.parse(JSON.stringify({ result, expiresAt }));
16
+ const pending = this.writes.get(name);
17
+ if (pending) {
18
+ if (!isDeepStrictEqual(pending.value, value))
19
+ throw new Error("runtime_readiness_result_conflict: request identity reused.");
20
+ return await pending.write;
21
+ }
22
+ const write = this.operate({ kind: "persist", name, record: {
23
+ schemaVersion: 1, persistedAt: new Date().toISOString(), ...value,
24
+ } }).then(() => undefined);
25
+ this.writes.set(name, { value, write });
26
+ try {
27
+ await write;
28
+ }
29
+ finally {
30
+ if (this.writes.get(name)?.write === write)
31
+ this.writes.delete(name);
32
+ }
33
+ }
34
+ async pending() {
35
+ return JSON.parse(await this.operate({ kind: "pending" }));
36
+ }
37
+ async acknowledge(requestId) {
38
+ await this.operate({ kind: "acknowledge", name: recordName(requestId) });
39
+ }
40
+ async operate(operation) {
41
+ const root = path.resolve(this.stateRoot, "readiness-results");
42
+ await createCanonicalDirectory(root);
43
+ const identity = await assertCanonicalDirectory(root);
44
+ if (!identity)
45
+ throw new Error("runtime_readiness_result_invalid: outbox directory is missing.");
46
+ return await runPinnedDirectoryProcess(root, identity, readinessReceiptWorker, operation, undefined, 256 * 256 * 1024 + 1024);
47
+ }
48
+ }
49
+ function recordName(requestId) {
50
+ const value = requestId.replaceAll("-", "").toLowerCase();
51
+ if (!/^[0-9a-f]{32}$/u.test(value))
52
+ throw new Error("runtime_readiness_result_invalid: request id must be a UUID.");
53
+ return `${value}.json`;
54
+ }
@@ -0,0 +1,2 @@
1
+ /** Runs in a bounded async child. Receipt IO stays relative to verified, pinned cwd. */
2
+ export declare const readinessReceiptWorker: string;
@@ -0,0 +1,100 @@
1
+ /** Runs in a bounded async child. Receipt IO stays relative to verified, pinned cwd. */
2
+ export const readinessReceiptWorker = String.raw `
3
+ const fs = require('node:fs');
4
+ const { randomUUID } = require('node:crypto');
5
+ const { isDeepStrictEqual } = require('node:util');
6
+ const job = JSON.parse(fs.readFileSync(0, 'utf8'));
7
+ const maximumBytes = 256 * 1024;
8
+ const same = (a, b) => a.dev === b.dev && a.ino === b.ino;
9
+ const validDate = value => typeof value === 'string' && Number.isFinite(Date.parse(value));
10
+ const uuid = value => typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
11
+ const validName = name => typeof name === 'string' && /^[0-9a-f]{32}\.json$/.test(name);
12
+ const validRecord = (record, name) => record && record.schemaVersion === 1
13
+ && validDate(record.expiresAt) && validDate(record.persistedAt) && record.result
14
+ && record.result.schemaVersion === 1 && uuid(record.result.requestId) && uuid(record.result.fencingToken)
15
+ && uuid(record.result.runnerInstanceId) && validDate(record.result.checkedAt)
16
+ && ['parallel_ready', 'serialized', 'failed'].includes(record.result.status)
17
+ && record.result.requestId.replaceAll('-', '').toLowerCase() + '.json' === name;
18
+ const read = name => {
19
+ if (!validName(name)) throw new Error('runtime_readiness_result_invalid: invalid name');
20
+ let before;
21
+ try { before = fs.lstatSync(name, { bigint: true }); }
22
+ catch (error) { if (error.code === 'ENOENT') return null; throw error; }
23
+ // Atomic publishers may briefly hold their own second hard link until cleanup.
24
+ if (!before.isFile() || before.isSymbolicLink() || before.size > BigInt(maximumBytes))
25
+ throw new Error('runtime_readiness_result_invalid: not a bounded regular file');
26
+ const fd = fs.openSync(name, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
27
+ try {
28
+ const opened = fs.fstatSync(fd, { bigint: true });
29
+ if (!same(before, opened) || !opened.isFile() || opened.size !== before.size)
30
+ throw new Error('runtime_readiness_result_invalid: receipt identity changed');
31
+ const bytes = Buffer.alloc(Number(opened.size));
32
+ let offset = 0;
33
+ while (offset < bytes.length) {
34
+ const count = fs.readSync(fd, bytes, offset, bytes.length - offset, offset);
35
+ if (!count) throw new Error('runtime_readiness_result_invalid: incomplete receipt');
36
+ offset += count;
37
+ }
38
+ const after = fs.fstatSync(fd, { bigint: true });
39
+ const current = fs.lstatSync(name, { bigint: true });
40
+ if (!same(opened, after) || !same(after, current) || current.isSymbolicLink()
41
+ || opened.size !== after.size || opened.mtimeNs !== after.mtimeNs || opened.ctimeNs !== after.ctimeNs)
42
+ throw new Error('runtime_readiness_result_invalid: receipt changed during read');
43
+ const record = JSON.parse(bytes.toString('utf8'));
44
+ if (!validRecord(record, name)) throw new Error('runtime_readiness_result_invalid: malformed or wrong identity');
45
+ return record;
46
+ } finally { fs.closeSync(fd); }
47
+ };
48
+ const identical = (existing, candidate) => {
49
+ if (!isDeepStrictEqual(existing.result, candidate.result) || existing.expiresAt !== candidate.expiresAt)
50
+ throw new Error('runtime_readiness_result_conflict: request identity reused');
51
+ };
52
+ try {
53
+ const cwd = fs.statSync('.', { bigint: true });
54
+ const real = fs.realpathSync('.');
55
+ if (!same(cwd, { dev: BigInt(job.dev), ino: BigInt(job.ino) })
56
+ || (process.platform === 'win32' ? real.toLowerCase() !== job.parent.toLowerCase() : real !== job.parent))
57
+ throw new Error('runtime_readiness_result_invalid: parent identity changed');
58
+ if (job.kind === 'persist') {
59
+ if (!validName(job.name) || !validRecord(job.record, job.name)) throw new Error('runtime_readiness_result_invalid: invalid candidate');
60
+ const content = JSON.stringify(job.record) + '\n';
61
+ if (Buffer.byteLength(content) > maximumBytes) throw new Error('runtime_readiness_result_invalid: receipt too large');
62
+ const existing = read(job.name);
63
+ if (existing) identical(existing, job.record);
64
+ else {
65
+ const temporary = job.name + '.' + randomUUID() + '.tmp';
66
+ let owned = false;
67
+ try {
68
+ const fd = fs.openSync(temporary, 'wx', 0o600);
69
+ owned = true;
70
+ try { fs.writeFileSync(fd, content, 'utf8'); fs.fsyncSync(fd); }
71
+ finally { fs.closeSync(fd); }
72
+ try { fs.linkSync(temporary, job.name); }
73
+ catch (error) {
74
+ if (error.code !== 'EEXIST') throw error;
75
+ const winner = read(job.name);
76
+ if (!winner) throw error;
77
+ identical(winner, job.record);
78
+ }
79
+ } finally { if (owned) fs.unlinkSync(temporary); }
80
+ }
81
+ } else if (job.kind === 'pending') {
82
+ const records = [];
83
+ const directory = fs.opendirSync('.');
84
+ try {
85
+ let entry;
86
+ while (records.length < 256 && (entry = directory.readSync()) !== null) {
87
+ if (!entry.isFile() || !validName(entry.name)) continue;
88
+ try { const record = read(entry.name); if (record) records.push(record); }
89
+ catch { /* Retain malformed evidence; never send it as a trusted callback. */ }
90
+ }
91
+ } finally { directory.closeSync(); }
92
+ records.sort((a, b) => a.persistedAt.localeCompare(b.persistedAt));
93
+ process.stdout.write(JSON.stringify(records));
94
+ } else if (job.kind === 'acknowledge') {
95
+ if (!validName(job.name)) throw new Error('runtime_readiness_result_invalid: invalid name');
96
+ // unlink is relative to pinned cwd and never follows a replaced leaf link.
97
+ try { fs.unlinkSync(job.name); } catch (error) { if (error.code !== 'ENOENT') throw error; }
98
+ } else throw new Error('runtime_readiness_result_invalid: invalid operation');
99
+ } catch (error) { process.stderr.write(error.message); process.exitCode = 1; }
100
+ `;
@@ -0,0 +1,15 @@
1
+ import type { BridgeServiceConfig, BridgeToolchainCapabilities } from "./bridge.types.js";
2
+ export interface RunnerHostCapabilitySnapshot {
3
+ readonly toolchains: BridgeToolchainCapabilities;
4
+ readonly playwrightProjects: readonly string[];
5
+ readonly browserLaunch: boolean;
6
+ readonly providerConnectivity: Readonly<Record<string, boolean>>;
7
+ }
8
+ export interface RunnerHostCapabilityProbe {
9
+ detect(config: BridgeServiceConfig): Promise<RunnerHostCapabilitySnapshot>;
10
+ }
11
+ export declare class ProcessRunnerHostCapabilityProbe implements RunnerHostCapabilityProbe {
12
+ detect(config: BridgeServiceConfig): Promise<RunnerHostCapabilitySnapshot>;
13
+ }
14
+ export declare function isUnavailablePlaywrightConfigError(error: unknown): boolean;
15
+ export declare function emptyRunnerHostCapabilitySnapshot(): RunnerHostCapabilitySnapshot;
@@ -0,0 +1,93 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ const execFileAsync = promisify(execFile);
6
+ export class ProcessRunnerHostCapabilityProbe {
7
+ async detect(config) {
8
+ const [pnpm, dotnet, git, bash, pwsh, windowsPowerShell] = await Promise.all([
9
+ probe("pnpm", ["--version"]),
10
+ probe("dotnet", ["--version"]),
11
+ probe("git", ["--version"]),
12
+ probe("bash", ["--version"]),
13
+ probe("pwsh", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"]),
14
+ process.platform === "win32"
15
+ ? probe("powershell.exe", ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"])
16
+ : Promise.resolve(unavailable("powershell.exe")),
17
+ ]);
18
+ const playwrightProjects = await detectPlaywrightProjects(config.repoMappings);
19
+ const browserLaunch = playwrightProjects.length > 0
20
+ || config.repoMappings.some(mapping => Boolean(mapping.qaAppLaunchTarget?.url
21
+ || mapping.qaAppLaunchTarget?.command
22
+ || mapping.qaAppLaunchTarget?.managed_runtime_port));
23
+ return {
24
+ toolchains: {
25
+ node: { available: true, executable: process.execPath, version: process.version },
26
+ pnpm,
27
+ dotnet,
28
+ git,
29
+ bash,
30
+ powershell: pwsh.available ? pwsh : windowsPowerShell,
31
+ },
32
+ playwrightProjects,
33
+ browserLaunch,
34
+ providerConnectivity: Object.fromEntries(Object.entries(config.providerStatuses)
35
+ .map(([provider, status]) => [provider, status.signedIn === true])),
36
+ };
37
+ }
38
+ }
39
+ async function probe(executable, args) {
40
+ try {
41
+ const result = await execFileAsync(executable, [...args], { encoding: "utf8", timeout: 5_000 });
42
+ const version = `${result.stdout || result.stderr}`.trim().split(/\r?\n/u)[0] ?? "";
43
+ return { available: true, executable, version };
44
+ }
45
+ catch {
46
+ return unavailable(executable);
47
+ }
48
+ }
49
+ function unavailable(executable) {
50
+ return { available: false, executable, version: null };
51
+ }
52
+ async function detectPlaywrightProjects(repoMappings) {
53
+ const projects = new Set();
54
+ for (const mapping of repoMappings) {
55
+ if (!mapping.localPath)
56
+ continue;
57
+ const candidates = [
58
+ "playwright.config.ts", "playwright.config.js", "playwright.config.mjs",
59
+ "tests/e2e/playwright.config.ts", "tests/e2e/playwright.config.js", "tests/e2e/playwright.config.mjs",
60
+ ];
61
+ for (const relative of candidates) {
62
+ try {
63
+ const source = await fs.readFile(path.join(mapping.localPath, relative), "utf8");
64
+ for (const match of source.matchAll(/\bname\s*:\s*["']([^"']+)["']/gu))
65
+ projects.add(match[1]);
66
+ }
67
+ catch (error) {
68
+ if (!isUnavailablePlaywrightConfigError(error))
69
+ throw error;
70
+ }
71
+ }
72
+ }
73
+ return [...projects].sort((left, right) => left.localeCompare(right));
74
+ }
75
+ export function isUnavailablePlaywrightConfigError(error) {
76
+ const code = error?.code;
77
+ return code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "ENOTDIR";
78
+ }
79
+ export function emptyRunnerHostCapabilitySnapshot() {
80
+ return {
81
+ toolchains: {
82
+ node: { available: true, executable: process.execPath, version: process.version },
83
+ pnpm: unavailable("pnpm"),
84
+ dotnet: unavailable("dotnet"),
85
+ git: unavailable("git"),
86
+ bash: unavailable("bash"),
87
+ powershell: unavailable(process.platform === "win32" ? "powershell.exe" : "pwsh"),
88
+ },
89
+ playwrightProjects: [],
90
+ browserLaunch: false,
91
+ providerConnectivity: {},
92
+ };
93
+ }
@@ -1,5 +1,17 @@
1
+ import { GitClient } from "../shared/git/git.js";
2
+ import type { WorktreeLifecycleCoordinator } from "../worktree/worktree.base.js";
1
3
  import type { CleanupService } from "./cleanup.base.js";
2
4
  import type { CleanupRequest, CleanupResult } from "./cleanup.types.js";
3
- export declare class NoopCleanupService implements CleanupService {
4
- run(_request: CleanupRequest): Promise<CleanupResult>;
5
+ export declare class ManagedWorktreeCleanupService implements CleanupService {
6
+ private readonly stateRoot;
7
+ private readonly git;
8
+ private readonly coordinator;
9
+ constructor(stateRoot?: string, git?: GitClient, coordinator?: WorktreeLifecycleCoordinator);
10
+ run(request: CleanupRequest): Promise<CleanupResult>;
11
+ private runLocked;
12
+ private listWorktrees;
13
+ private staleRuntimeReadinessPaths;
14
+ private scanManagedCandidates;
15
+ private readRuntimeLeases;
16
+ private worktreeMetadataRoot;
5
17
  }