@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484

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 (223) hide show
  1. package/README.md +165 -1
  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 +9 -1
  5. package/dist/bridge/bridge.service.d.ts +129 -3
  6. package/dist/bridge/bridge.service.js +2427 -70
  7. package/dist/bridge/bridge.types.d.ts +218 -2
  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 +14 -2
  21. package/dist/cleanup/cleanup.service.js +496 -3
  22. package/dist/cleanup/cleanup.types.d.ts +10 -0
  23. package/dist/cleanup/index.d.ts +1 -1
  24. package/dist/cleanup/index.js +1 -1
  25. package/dist/cleanup/internals.d.ts +2 -2
  26. package/dist/cleanup/internals.js +1 -1
  27. package/dist/cli/cli.help.d.ts +2 -0
  28. package/dist/cli/cli.help.js +217 -0
  29. package/dist/cli/cli.service.d.ts +55 -7
  30. package/dist/cli/cli.service.js +429 -219
  31. package/dist/cli/local-config.d.ts +7 -0
  32. package/dist/cli/local-config.js +51 -3
  33. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  34. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  35. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  36. package/dist/runtime/attempt-database-broker.js +390 -0
  37. package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
  38. package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
  39. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  40. package/dist/runtime/attempt-path-operation.js +94 -0
  41. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  42. package/dist/runtime/attempt-private-directory.js +54 -0
  43. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  44. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  45. package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
  46. package/dist/runtime/attempt-process-supervisor.js +421 -0
  47. package/dist/runtime/canonical-source-file.d.ts +3 -0
  48. package/dist/runtime/canonical-source-file.js +54 -0
  49. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  50. package/dist/runtime/docker-engine-probe.js +333 -0
  51. package/dist/runtime/generated-output-reset.d.ts +20 -0
  52. package/dist/runtime/generated-output-reset.js +157 -0
  53. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  54. package/dist/runtime/host-database-capability-boundary.js +62 -0
  55. package/dist/runtime/host-diagnostics.d.ts +11 -0
  56. package/dist/runtime/host-diagnostics.js +192 -0
  57. package/dist/runtime/host-execution-root.d.ts +4 -0
  58. package/dist/runtime/host-execution-root.js +39 -0
  59. package/dist/runtime/host-listener-inspector.d.ts +51 -0
  60. package/dist/runtime/host-listener-inspector.js +385 -0
  61. package/dist/runtime/host-port-broker.d.ts +76 -0
  62. package/dist/runtime/host-port-broker.js +333 -0
  63. package/dist/runtime/host-runtime-contract.d.ts +58 -0
  64. package/dist/runtime/host-runtime-contract.js +813 -0
  65. package/dist/runtime/host-runtime-lease.d.ts +217 -0
  66. package/dist/runtime/host-runtime-lease.js +2487 -0
  67. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  68. package/dist/runtime/host-runtime-promotion.js +206 -0
  69. package/dist/runtime/memory-capacity.d.ts +70 -0
  70. package/dist/runtime/memory-capacity.js +123 -0
  71. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  72. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  73. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  74. package/dist/runtime/node-host-runtime-adapter.js +286 -0
  75. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  76. package/dist/runtime/pinned-directory-process.js +61 -0
  77. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  78. package/dist/runtime/pinned-source-directory.js +94 -0
  79. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  80. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  81. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  82. package/dist/runtime/prepared-runtime-identity.js +218 -0
  83. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  84. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  85. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  86. package/dist/runtime/runtime-isolation-lease.js +161 -0
  87. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  88. package/dist/runtime/workspace-resource-estimator.js +174 -0
  89. package/dist/sandbox/command-runner.d.ts +4 -0
  90. package/dist/sandbox/command-runner.js +47 -0
  91. package/dist/sandbox/contract.d.ts +5 -0
  92. package/dist/sandbox/contract.js +276 -0
  93. package/dist/sandbox/event-budget.d.ts +11 -0
  94. package/dist/sandbox/event-budget.js +27 -0
  95. package/dist/sandbox/image-policy.d.ts +35 -0
  96. package/dist/sandbox/image-policy.js +247 -0
  97. package/dist/sandbox/index.d.ts +5 -0
  98. package/dist/sandbox/index.js +5 -0
  99. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  100. package/dist/sandbox/lease-watchdog.js +67 -0
  101. package/dist/sandbox/preflight.d.ts +84 -0
  102. package/dist/sandbox/preflight.js +521 -0
  103. package/dist/sandbox/process.d.ts +20 -0
  104. package/dist/sandbox/process.js +209 -0
  105. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  106. package/dist/sandbox/provider-auth/codex.js +212 -0
  107. package/dist/sandbox/repository-lock.d.ts +6 -0
  108. package/dist/sandbox/repository-lock.js +129 -0
  109. package/dist/sandbox/runtime.d.ts +302 -0
  110. package/dist/sandbox/runtime.js +2368 -0
  111. package/dist/sandbox/sandbox.types.d.ts +93 -0
  112. package/dist/sandbox/sandbox.types.js +1 -0
  113. package/dist/sandbox/smoke.d.ts +76 -0
  114. package/dist/sandbox/smoke.js +380 -0
  115. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  116. package/dist/sandbox/worker-supervisor.js +36 -0
  117. package/dist/sandbox/worker.d.ts +1 -0
  118. package/dist/sandbox/worker.js +225 -0
  119. package/dist/sandbox/workspace.service.d.ts +13 -0
  120. package/dist/sandbox/workspace.service.js +74 -0
  121. package/dist/session/managed-attempt-tools.d.ts +28 -0
  122. package/dist/session/managed-attempt-tools.js +1016 -0
  123. package/dist/session/managed-build-coordinator.d.ts +5 -0
  124. package/dist/session/managed-build-coordinator.js +106 -0
  125. package/dist/session/managed-build-receipt.d.ts +21 -0
  126. package/dist/session/managed-build-receipt.js +337 -0
  127. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  128. package/dist/session/managed-verification-timeouts.js +17 -0
  129. package/dist/session/phase-completion.artifacts.d.ts +27 -0
  130. package/dist/session/phase-completion.artifacts.js +829 -0
  131. package/dist/session/phase-completion.d.ts +53 -42
  132. package/dist/session/phase-completion.helpers.d.ts +117 -0
  133. package/dist/session/phase-completion.helpers.js +707 -0
  134. package/dist/session/phase-completion.js +125 -3007
  135. package/dist/session/phase-completion.prompts.d.ts +14 -0
  136. package/dist/session/phase-completion.prompts.js +233 -0
  137. package/dist/session/phase-completion.runtime.d.ts +49 -0
  138. package/dist/session/phase-completion.runtime.js +840 -0
  139. package/dist/session/phase-completion.schemas.d.ts +3 -0
  140. package/dist/session/phase-completion.schemas.js +859 -0
  141. package/dist/session/phase-completion.validation.d.ts +38 -0
  142. package/dist/session/phase-completion.validation.js +918 -0
  143. package/dist/session/pre-publication-validation.d.ts +75 -0
  144. package/dist/session/pre-publication-validation.js +661 -0
  145. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  146. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  147. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  148. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  149. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
  150. package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
  151. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  152. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  153. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  154. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  155. package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
  156. package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
  157. package/dist/session/provider-cli/provider-cli.process.js +874 -0
  158. package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
  159. package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
  160. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  161. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  162. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  163. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  164. package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
  165. package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
  166. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  167. package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
  168. package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
  169. package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
  170. package/dist/session/provider-cli/provider-output-state.js +817 -0
  171. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  172. package/dist/session/provider-cli/provider-usage.js +28 -5
  173. package/dist/session/session-redaction.d.ts +3 -0
  174. package/dist/session/session-redaction.js +448 -0
  175. package/dist/session/session.base.d.ts +6 -1
  176. package/dist/session/session.service.d.ts +8 -2
  177. package/dist/session/session.service.js +586 -59
  178. package/dist/session/session.types.d.ts +324 -3
  179. package/dist/session/session.types.js +939 -10
  180. package/dist/session/unreferenced-exports.d.ts +12 -0
  181. package/dist/session/unreferenced-exports.js +59 -0
  182. package/dist/shared/format/format-bytes.d.ts +1 -0
  183. package/dist/shared/format/format-bytes.js +9 -0
  184. package/dist/shared/git/git.js +2 -1
  185. package/dist/shared/path/path.d.ts +1 -0
  186. package/dist/shared/path/path.js +21 -1
  187. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  188. package/dist/shared/process/dotnet-toolchain.js +307 -0
  189. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  190. package/dist/shared/process/host-process-recovery.js +79 -0
  191. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  192. package/dist/shared/process/host-process-supervisor.js +331 -0
  193. package/dist/shared/process/process.d.ts +3 -0
  194. package/dist/shared/process/process.js +13 -7
  195. package/dist/shared/process/windows-launch.d.ts +8 -0
  196. package/dist/shared/process/windows-launch.js +41 -0
  197. package/dist/shared/provider-capabilities.d.ts +4 -0
  198. package/dist/shared/provider-capabilities.js +88 -0
  199. package/dist/shared/redaction/redaction.d.ts +1 -0
  200. package/dist/shared/redaction/redaction.js +47 -4
  201. package/dist/shared/time/monotonic.d.ts +1 -0
  202. package/dist/shared/time/monotonic.js +8 -0
  203. package/dist/version.d.ts +1 -1
  204. package/dist/version.js +1 -1
  205. package/dist/worktree/artifact-injector.d.ts +3 -2
  206. package/dist/worktree/artifact-injector.js +82 -10
  207. package/dist/worktree/dependency-warmer.d.ts +53 -1
  208. package/dist/worktree/dependency-warmer.js +619 -11
  209. package/dist/worktree/index.d.ts +2 -1
  210. package/dist/worktree/index.js +1 -0
  211. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  212. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  213. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  214. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  215. package/dist/worktree/worktree-paths.d.ts +4 -0
  216. package/dist/worktree/worktree-paths.js +30 -0
  217. package/dist/worktree/worktree.base.d.ts +3 -0
  218. package/dist/worktree/worktree.service.d.ts +16 -3
  219. package/dist/worktree/worktree.service.js +334 -54
  220. package/dist/worktree/worktree.types.d.ts +9 -1
  221. package/package.json +12 -7
  222. package/runtime/windows-job-supervisor.ps1 +722 -0
  223. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,225 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawn } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { MonotonicLeaseDeadline } from "./lease-watchdog.js";
7
+ import { terminationMessage, WorkerDeadlineSupervisor } from "./worker-supervisor.js";
8
+ const controlRoot = "/control";
9
+ const runtimeRoot = "/run/forge";
10
+ const workspace = process.env.FORGE_WORKSPACE ?? "/workspace";
11
+ async function main() {
12
+ const dispatchText = await readBoundedStdin(16 * 1024 * 1024);
13
+ const bootstrap = JSON.parse(dispatchText);
14
+ if (bootstrap.schemaVersion !== 1 || !bootstrap.dispatch || typeof bootstrap.leaseControlKey !== "string") {
15
+ throw new Error("Invalid sandbox bootstrap packet.");
16
+ }
17
+ const dispatch = bootstrap.dispatch;
18
+ const sandbox = dispatch.sandbox;
19
+ const attemptId = requiredId(sandbox?.dispatchAttemptId, "dispatchAttemptId");
20
+ const lease = dispatch.lease;
21
+ const fencingToken = requiredId(lease?.fencingToken, "fencingToken");
22
+ const leaseDeadline = new MonotonicLeaseDeadline(attemptId, fencingToken, requiredString(lease?.expiresAt, "expiresAt"), bootstrap.leaseControlKey);
23
+ const limits = requiredLimits(bootstrap.limits);
24
+ await installServiceEnvironment(attemptId);
25
+ await fs.mkdir(runtimeRoot, { recursive: true, mode: 0o700 });
26
+ await fs.mkdir(path.join(controlRoot, "output"), { recursive: true, mode: 0o700 });
27
+ const dispatchPath = path.join(runtimeRoot, "dispatch.json");
28
+ const resultPath = path.join(controlRoot, "output", "result.json");
29
+ await fs.writeFile(dispatchPath, JSON.stringify(dispatch), { mode: 0o600 });
30
+ const entrypoint = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../cli/bin.js");
31
+ const runOutcome = await run(process.execPath, [entrypoint, "run-once", "--dispatch", dispatchPath, "--result", resultPath], {
32
+ deadline: leaseDeadline,
33
+ updatePath: path.join(runtimeRoot, "lease-update.json"),
34
+ supervisor: new WorkerDeadlineSupervisor(leaseDeadline, limits.hardWallMs, limits.idleMs)
35
+ });
36
+ await fs.rm(dispatchPath, { force: true });
37
+ if (runOutcome.terminationCode) {
38
+ await writeTerminationResult(resultPath, runOutcome.terminationCode);
39
+ }
40
+ let resultCommitSha = null;
41
+ let bundleSha256 = null;
42
+ let bundleBytes = 0;
43
+ const bundlePath = path.join(controlRoot, "output", "result.bundle");
44
+ if (!runOutcome.terminationCode && runOutcome.exitCode === 0 && await exists(path.join(workspace, ".git"))) {
45
+ resultCommitSha = (await capture("git", ["-C", workspace, "rev-parse", "HEAD"])).trim();
46
+ const resultRef = `refs/forge/results/${attemptId.replace(/-/g, "")}`;
47
+ await runRequired("git", ["-C", workspace, "update-ref", resultRef, resultCommitSha]);
48
+ try {
49
+ await runRequired("git", ["-C", workspace, "bundle", "create", bundlePath, resultRef]);
50
+ }
51
+ finally {
52
+ await run("git", ["-C", workspace, "update-ref", "-d", resultRef]);
53
+ }
54
+ const bundle = await fs.readFile(bundlePath);
55
+ bundleBytes = bundle.length;
56
+ bundleSha256 = createHash("sha256").update(bundle).digest("hex");
57
+ }
58
+ const resultBytes = await fs.readFile(resultPath);
59
+ const manifest = {
60
+ schemaVersion: 1,
61
+ dispatchAttemptId: attemptId,
62
+ executionLeaseId: sandbox?.executionLeaseId,
63
+ fencingToken: dispatch.lease?.fencingToken,
64
+ runnerInstanceId: sandbox?.runnerInstanceId,
65
+ inputCommitSha: sandbox?.inputCommitSha,
66
+ resultCommitSha,
67
+ branch: dispatch.lease?.worktreeKey,
68
+ contractDigest: sandbox?.contractDigest,
69
+ imageDigest: sandbox?.imageDigest,
70
+ result: { path: "result.json", bytes: resultBytes.length, sha256: createHash("sha256").update(resultBytes).digest("hex") },
71
+ bundle: bundleSha256 ? { path: "result.bundle", bytes: bundleBytes, sha256: bundleSha256 } : null,
72
+ workerExitCode: runOutcome.exitCode,
73
+ workerExitClassification: runOutcome.terminationCode ?? "agent_exit"
74
+ };
75
+ const temporary = path.join(controlRoot, "output", "manifest.json.tmp");
76
+ await fs.writeFile(temporary, `${JSON.stringify(manifest)}\n`, { mode: 0o600 });
77
+ await fs.rename(temporary, path.join(controlRoot, "output", "manifest.json"));
78
+ return runOutcome.exitCode;
79
+ }
80
+ async function readBoundedStdin(limit) {
81
+ const chunks = [];
82
+ let total = 0;
83
+ for await (const chunk of process.stdin) {
84
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
85
+ total += buffer.length;
86
+ if (total > limit)
87
+ throw new Error("Dispatch bootstrap exceeds 16 MiB.");
88
+ chunks.push(buffer);
89
+ }
90
+ return Buffer.concat(chunks).toString("utf8");
91
+ }
92
+ async function run(command, args, watchdog) {
93
+ return await new Promise((resolve, reject) => {
94
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: false });
95
+ let readingUpdate = false;
96
+ let killTimer = null;
97
+ let terminationCode = null;
98
+ const terminate = (code) => {
99
+ if (child.exitCode !== null || child.signalCode !== null)
100
+ return;
101
+ terminationCode ??= code;
102
+ emitTerminationEvent(terminationCode);
103
+ child.kill("SIGTERM");
104
+ killTimer = setTimeout(() => child.kill("SIGKILL"), 10_000);
105
+ killTimer.unref?.();
106
+ };
107
+ child.stdout.on("data", chunk => {
108
+ watchdog?.supervisor.touch();
109
+ process.stdout.write(chunk);
110
+ });
111
+ child.stderr.on("data", chunk => {
112
+ watchdog?.supervisor.touch();
113
+ process.stderr.write(chunk);
114
+ });
115
+ const monitor = watchdog ? setInterval(() => {
116
+ const reason = watchdog.supervisor.termination();
117
+ if (reason) {
118
+ terminate(reason);
119
+ return;
120
+ }
121
+ if (readingUpdate)
122
+ return;
123
+ readingUpdate = true;
124
+ fs.readFile(watchdog.updatePath, "utf8")
125
+ .then(value => {
126
+ return watchdog.deadline.apply(JSON.parse(value));
127
+ })
128
+ .catch(() => false)
129
+ .finally(() => { readingUpdate = false; });
130
+ }, 250) : null;
131
+ monitor?.unref?.();
132
+ child.on("error", error => {
133
+ if (monitor)
134
+ clearInterval(monitor);
135
+ if (killTimer)
136
+ clearTimeout(killTimer);
137
+ reject(error);
138
+ });
139
+ child.on("close", code => {
140
+ if (monitor)
141
+ clearInterval(monitor);
142
+ if (killTimer)
143
+ clearTimeout(killTimer);
144
+ resolve({ exitCode: terminationCode ? 124 : code ?? 1, terminationCode });
145
+ });
146
+ });
147
+ }
148
+ async function runRequired(command, args) {
149
+ const outcome = await run(command, args);
150
+ if (outcome.exitCode !== 0)
151
+ throw new Error(`${command} failed with exit ${outcome.exitCode}.`);
152
+ }
153
+ async function capture(command, args) {
154
+ return await new Promise((resolve, reject) => {
155
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "inherit"], shell: false });
156
+ const chunks = [];
157
+ child.stdout.on("data", chunk => chunks.push(Buffer.from(chunk)));
158
+ child.on("error", reject);
159
+ child.on("close", code => code === 0 ? resolve(Buffer.concat(chunks).toString("utf8")) : reject(new Error(`${command} failed with exit ${code}.`)));
160
+ });
161
+ }
162
+ async function exists(target) { return fs.stat(target).then(() => true).catch(() => false); }
163
+ async function installServiceEnvironment(attemptId) {
164
+ const attemptHex = attemptId.replace(/-/gu, "").toLowerCase();
165
+ const postgresPassword = await readServiceCredential("postgres-password");
166
+ if (postgresPassword)
167
+ process.env.FORGE_TEST_DATABASE = `postgresql://forge:${encodeURIComponent(postgresPassword)}@forge-${attemptHex}-postgres:5432/forge_test`;
168
+ const redisPassword = await readServiceCredential("redis-password");
169
+ if (redisPassword)
170
+ process.env.FORGE_TEST_REDIS = `redis://:${encodeURIComponent(redisPassword)}@forge-${attemptHex}-redis:6379/0`;
171
+ const browserToken = await readServiceCredential("browser-token");
172
+ if (browserToken)
173
+ process.env.FORGE_BROWSER_TOKEN = browserToken;
174
+ }
175
+ async function readServiceCredential(name) {
176
+ const target = `/run/forge-secrets/${name}`;
177
+ const stat = await fs.lstat(target).catch(() => null);
178
+ if (!stat)
179
+ return null;
180
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 32 || stat.size > 256) {
181
+ throw new Error(`Invalid ${name} service credential file.`);
182
+ }
183
+ return (await fs.readFile(target, "utf8")).trim();
184
+ }
185
+ function requiredId(value, label) {
186
+ if (typeof value !== "string" || !/^[0-9a-f-]{32,36}$/i.test(value))
187
+ throw new Error(`Invalid ${label}.`);
188
+ return value;
189
+ }
190
+ function requiredString(value, label) {
191
+ if (typeof value !== "string" || value.length === 0)
192
+ throw new Error(`Invalid ${label}.`);
193
+ return value;
194
+ }
195
+ function requiredLimits(value) {
196
+ if (!value || typeof value !== "object" || Array.isArray(value))
197
+ throw new Error("Invalid sandbox watchdog limits.");
198
+ const limits = value;
199
+ if (typeof limits.hardWallMs !== "number" || !Number.isSafeInteger(limits.hardWallMs) || limits.hardWallMs < 60_000 || limits.hardWallMs > 28_800_000
200
+ || typeof limits.idleMs !== "number" || !Number.isSafeInteger(limits.idleMs) || limits.idleMs < 60_000 || limits.idleMs > limits.hardWallMs) {
201
+ throw new Error("Invalid sandbox watchdog limits.");
202
+ }
203
+ return { hardWallMs: limits.hardWallMs, idleMs: limits.idleMs };
204
+ }
205
+ async function writeTerminationResult(resultPath, code) {
206
+ const message = terminationMessage(code);
207
+ const result = {
208
+ schemaVersion: 1,
209
+ exitCode: 124,
210
+ summary: message,
211
+ events: [{ role: "runner", kind: "error", content: message, eventType: code }]
212
+ };
213
+ const temporary = `${resultPath}.timeout.tmp`;
214
+ await fs.writeFile(temporary, `${JSON.stringify(result)}\n`, { mode: 0o600 });
215
+ await fs.rename(temporary, resultPath);
216
+ }
217
+ function emitTerminationEvent(code) {
218
+ const message = terminationMessage(code);
219
+ process.stdout.write(`${JSON.stringify({ type: "runner_event", event: { role: "runner", kind: "error", content: message, eventType: code } })}\n`);
220
+ process.stderr.write(`${code}: ${message}\n`);
221
+ }
222
+ main().then(code => { process.exitCode = code; }).catch(error => {
223
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
224
+ process.exitCode = 1;
225
+ });
@@ -0,0 +1,13 @@
1
+ import { GitClient } from "../shared/git/git.js";
2
+ import type { WorktreeService } from "../worktree/worktree.base.js";
3
+ import type { WorktreeHandle, WorktreePrepareRequest } from "../worktree/worktree.types.js";
4
+ export declare class SandboxWorkspaceService implements WorktreeService {
5
+ private readonly workspace;
6
+ private readonly bundlePath;
7
+ private readonly inputCommitSha;
8
+ private readonly git;
9
+ constructor(workspace?: string, bundlePath?: string, inputCommitSha?: string, git?: GitClient);
10
+ prepare(request: WorktreePrepareRequest): Promise<WorktreeHandle>;
11
+ release(_handle: WorktreeHandle): Promise<void>;
12
+ }
13
+ export declare function sandboxInputRefFromBundle(bundleHeads: string, inputCommitSha: string): string;
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { GitClient } from "../shared/git/git.js";
4
+ import { injectWorktreeArtifacts, filterArtifactsForLane } from "../worktree/artifact-injector.js";
5
+ import { hydratePriorPhaseArtifacts } from "../worktree/prior-phase-artifact-hydrator.js";
6
+ import { materializeStoryQaReferences } from "../worktree/story-qa-reference-hydrator.js";
7
+ import { clearRunArtifactRoots, normalizeBranchName } from "../worktree/worktree.service.js";
8
+ export class SandboxWorkspaceService {
9
+ workspace;
10
+ bundlePath;
11
+ inputCommitSha;
12
+ git;
13
+ constructor(workspace = process.env.FORGE_WORKSPACE ?? "/workspace", bundlePath = process.env.FORGE_INPUT_BUNDLE ?? "/control/input/repo.bundle", inputCommitSha = process.env.FORGE_INPUT_COMMIT_SHA ?? "", git = new GitClient()) {
14
+ this.workspace = workspace;
15
+ this.bundlePath = bundlePath;
16
+ this.inputCommitSha = inputCommitSha;
17
+ this.git = git;
18
+ }
19
+ async prepare(request) {
20
+ if (!/^[0-9a-f]{40,64}$/i.test(this.inputCommitSha))
21
+ throw new Error("Sandbox input commit SHA is missing or invalid.");
22
+ const stat = await fs.stat(this.workspace).catch(() => null);
23
+ if (!stat?.isDirectory())
24
+ throw new Error("Sandbox workspace volume is unavailable.");
25
+ const entries = await fs.readdir(this.workspace);
26
+ if (entries.length > 0)
27
+ throw new Error("Sandbox workspace must be empty before bundle import.");
28
+ const bundleHeads = await this.git.run(path.dirname(this.workspace), ["bundle", "list-heads", this.bundlePath]);
29
+ const inputRef = sandboxInputRefFromBundle(bundleHeads, this.inputCommitSha);
30
+ await this.git.run(path.dirname(this.workspace), ["init", this.workspace]);
31
+ await this.git.run(this.workspace, ["fetch", "--no-tags", "--no-write-fetch-head", this.bundlePath, `${inputRef}:refs/forge/input`]);
32
+ const actualSha = (await this.git.run(this.workspace, ["rev-parse", "--verify", `${this.inputCommitSha}^{commit}`])).trim();
33
+ if (actualSha.toLowerCase() !== this.inputCommitSha.toLowerCase())
34
+ throw new Error("Imported bundle does not contain the exact recorded input commit.");
35
+ const branchName = normalizeBranchName(request.worktreeKey, `forge/${request.workItem.id.replace(/-/g, "")}`);
36
+ await this.git.run(this.workspace, ["checkout", "-B", branchName, this.inputCommitSha]);
37
+ await this.git.run(this.workspace, ["config", "user.email", "forge-agent@forge.local"]);
38
+ await this.git.run(this.workspace, ["config", "user.name", "Forge Agent"]);
39
+ await this.git.run(this.workspace, ["config", "core.hooksPath", "/dev/null"]);
40
+ await materializeStoryQaReferences(this.workspace, request, this.git);
41
+ await clearRunArtifactRoots(this.workspace, request);
42
+ const artifacts = filterArtifactsForLane(request.artifacts, request.lane, request.phase);
43
+ if (artifacts.length > 0) {
44
+ const trackedForgePaths = new Set((await this.git.run(this.workspace, ["ls-files", "-z", "--", ".forge"]))
45
+ .split("\0")
46
+ .filter(Boolean)
47
+ .map(value => value.replaceAll("\\", "/")));
48
+ await injectWorktreeArtifacts(this.workspace, artifacts, trackedForgePaths);
49
+ }
50
+ const hydratedPriorPhaseArtifactCount = await hydratePriorPhaseArtifacts(this.workspace, request.priorPhaseArtifacts);
51
+ return {
52
+ repoPath: this.workspace,
53
+ worktreePath: this.workspace,
54
+ branchName,
55
+ baseBranch: request.baseBranch,
56
+ cleanupWorktrees: false,
57
+ hydratedPriorPhaseArtifactCount
58
+ };
59
+ }
60
+ async release(_handle) {
61
+ // The host supervisor owns the workspace volume lifecycle.
62
+ }
63
+ }
64
+ export function sandboxInputRefFromBundle(bundleHeads, inputCommitSha) {
65
+ const expectedSha = inputCommitSha.toLowerCase();
66
+ const matches = bundleHeads
67
+ .split(/\r?\n/u)
68
+ .map(line => /^([0-9a-f]{40}|[0-9a-f]{64}) (refs\/forge\/inputs\/[0-9a-f]{32})$/iu.exec(line.trim()))
69
+ .filter((match) => Boolean(match) && match[1].toLowerCase() === expectedSha);
70
+ if (matches.length !== 1) {
71
+ throw new Error("Sandbox input bundle does not expose the exact recorded input ref.");
72
+ }
73
+ return matches[0][2];
74
+ }
@@ -0,0 +1,28 @@
1
+ import { type NativeTestPostgresFallback } from "../runtime/native-test-postgres-fallback.js";
2
+ export declare const managedAttemptToolNames: readonly ["forge.run_managed_command", "run_managed_command", "forge.start_managed_command", "start_managed_command", "forge.get_managed_command", "get_managed_command", "forge.wait_managed_command", "wait_managed_command", "forge.start_managed_verification", "start_managed_verification", "forge.reset_generated_outputs", "reset_generated_outputs"];
3
+ export declare function isManagedAttemptTool(name: string): boolean;
4
+ export declare function managedAttemptToolDefinition(name: string): unknown;
5
+ export interface ManagedAttemptToolResult {
6
+ readonly content: readonly {
7
+ readonly type: "text";
8
+ readonly text: string;
9
+ }[];
10
+ readonly structuredContent: Readonly<Record<string, unknown>>;
11
+ readonly isError?: boolean;
12
+ }
13
+ export declare function callManagedAttemptTool(name: string, input: unknown, worktreePath: string): Promise<ManagedAttemptToolResult>;
14
+ export declare function buildManagedVerificationCommand(profile: string, outputDirectory: string, shardCount: number, shardIndex: number, noBuild: boolean, runAllShards?: boolean, maxParallelism?: number): {
15
+ command: string;
16
+ args: readonly string[];
17
+ timeoutMs: number;
18
+ };
19
+ export declare function preferredPowerShellExecutable(platform?: NodeJS.Platform, pathValue?: string): string;
20
+ export declare function validatePlaywrightProjectName(project: string, availableProjects: readonly string[]): void;
21
+ export declare function playwrightProjectNamesFromConfigSource(source: string): readonly string[];
22
+ /**
23
+ * Runner-authored verification normally uses its attempt-owned Testcontainers
24
+ * database. Only when the Docker engine is unavailable does it switch to the
25
+ * operator-provisioned native test cluster, which holds no real data.
26
+ */
27
+ export declare function managedVerificationDatabaseEnvironment(dockerAvailable?: () => Promise<boolean>, readFallback?: () => NativeTestPostgresFallback | null): Promise<Readonly<Record<string, string>>>;
28
+ export declare function isBuildProducingManagedCommand(command: string, args: readonly string[]): boolean;