@loucompanion/forge-bridge 0.1.3-dev.8777774c67bc → 0.1.3-dev.88e93f53db5b

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 (137) hide show
  1. package/README.md +51 -0
  2. package/dist/bridge/bridge.base.d.ts +4 -1
  3. package/dist/bridge/bridge.service.d.ts +50 -3
  4. package/dist/bridge/bridge.service.js +730 -87
  5. package/dist/bridge/bridge.types.d.ts +63 -1
  6. package/dist/cleanup/cleanup.service.d.ts +14 -2
  7. package/dist/cleanup/cleanup.service.js +496 -3
  8. package/dist/cleanup/cleanup.types.d.ts +10 -0
  9. package/dist/cleanup/index.d.ts +1 -1
  10. package/dist/cleanup/index.js +1 -1
  11. package/dist/cleanup/internals.d.ts +2 -2
  12. package/dist/cleanup/internals.js +1 -1
  13. package/dist/cli/cli.help.js +37 -3
  14. package/dist/cli/cli.service.d.ts +30 -2
  15. package/dist/cli/cli.service.js +191 -27
  16. package/dist/cli/local-config.d.ts +4 -0
  17. package/dist/cli/local-config.js +41 -1
  18. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  19. package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
  20. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  21. package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
  22. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  23. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  24. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  25. package/dist/runtime/attempt-process-supervisor.js +384 -0
  26. package/dist/runtime/generated-output-reset.d.ts +10 -0
  27. package/dist/runtime/generated-output-reset.js +103 -0
  28. package/dist/runtime/host-listener-inspector.d.ts +46 -0
  29. package/dist/runtime/host-listener-inspector.js +349 -0
  30. package/dist/runtime/host-port-broker.d.ts +76 -0
  31. package/dist/runtime/host-port-broker.js +333 -0
  32. package/dist/runtime/host-runtime-contract.d.ts +47 -0
  33. package/dist/runtime/host-runtime-contract.js +700 -0
  34. package/dist/runtime/host-runtime-lease.d.ts +194 -0
  35. package/dist/runtime/host-runtime-lease.js +2138 -0
  36. package/dist/runtime/host-runtime-promotion.d.ts +23 -0
  37. package/dist/runtime/host-runtime-promotion.js +159 -0
  38. package/dist/runtime/memory-capacity.d.ts +68 -0
  39. package/dist/runtime/memory-capacity.js +121 -0
  40. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  41. package/dist/runtime/node-host-runtime-adapter.js +284 -0
  42. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  43. package/dist/runtime/playwright-host-runtime-adapter.js +225 -0
  44. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  45. package/dist/runtime/runtime-isolation-lease.js +158 -0
  46. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  47. package/dist/runtime/workspace-resource-estimator.js +174 -0
  48. package/dist/sandbox/image-policy.d.ts +3 -1
  49. package/dist/sandbox/image-policy.js +4 -2
  50. package/dist/sandbox/preflight.d.ts +49 -1
  51. package/dist/sandbox/preflight.js +313 -31
  52. package/dist/sandbox/process.js +125 -40
  53. package/dist/sandbox/repository-lock.js +41 -3
  54. package/dist/sandbox/runtime.d.ts +85 -4
  55. package/dist/sandbox/runtime.js +806 -126
  56. package/dist/sandbox/sandbox.types.d.ts +10 -0
  57. package/dist/sandbox/smoke.d.ts +76 -0
  58. package/dist/sandbox/smoke.js +380 -0
  59. package/dist/sandbox/worker-supervisor.js +1 -1
  60. package/dist/sandbox/worker.js +2 -5
  61. package/dist/sandbox/workspace.service.d.ts +1 -0
  62. package/dist/sandbox/workspace.service.js +28 -5
  63. package/dist/session/managed-attempt-tools.d.ts +18 -0
  64. package/dist/session/managed-attempt-tools.js +854 -0
  65. package/dist/session/managed-build-coordinator.d.ts +5 -0
  66. package/dist/session/managed-build-coordinator.js +105 -0
  67. package/dist/session/managed-build-receipt.d.ts +21 -0
  68. package/dist/session/managed-build-receipt.js +335 -0
  69. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  70. package/dist/session/phase-completion.artifacts.js +233 -24
  71. package/dist/session/phase-completion.d.ts +11 -2
  72. package/dist/session/phase-completion.helpers.d.ts +5 -0
  73. package/dist/session/phase-completion.helpers.js +51 -10
  74. package/dist/session/phase-completion.js +12 -3
  75. package/dist/session/phase-completion.prompts.d.ts +3 -3
  76. package/dist/session/phase-completion.prompts.js +21 -6
  77. package/dist/session/phase-completion.runtime.d.ts +13 -1
  78. package/dist/session/phase-completion.runtime.js +189 -8
  79. package/dist/session/phase-completion.schemas.js +108 -3
  80. package/dist/session/phase-completion.validation.d.ts +4 -1
  81. package/dist/session/phase-completion.validation.js +151 -21
  82. package/dist/session/provider-cli/ci-repair-readiness.d.ts +14 -0
  83. package/dist/session/provider-cli/ci-repair-readiness.js +105 -0
  84. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
  85. package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
  86. package/dist/session/provider-cli/provider-cli.deterministic.js +47 -1
  87. package/dist/session/provider-cli/provider-cli.process.d.ts +23 -0
  88. package/dist/session/provider-cli/provider-cli.process.js +420 -32
  89. package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
  90. package/dist/session/provider-cli/provider-cli.prompts.js +99 -19
  91. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
  92. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  93. package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
  94. package/dist/session/provider-cli/provider-cli.service.d.ts +20 -2
  95. package/dist/session/provider-cli/provider-cli.service.js +464 -102
  96. package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
  97. package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
  98. package/dist/session/provider-cli/provider-output-state.js +91 -16
  99. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  100. package/dist/session/provider-cli/provider-usage.js +28 -5
  101. package/dist/session/session-redaction.d.ts +3 -0
  102. package/dist/session/session-redaction.js +38 -0
  103. package/dist/session/session.base.d.ts +6 -1
  104. package/dist/session/session.service.d.ts +8 -2
  105. package/dist/session/session.service.js +522 -41
  106. package/dist/session/session.types.d.ts +70 -0
  107. package/dist/session/session.types.js +76 -4
  108. package/dist/shared/format/format-bytes.d.ts +1 -0
  109. package/dist/shared/format/format-bytes.js +9 -0
  110. package/dist/shared/path/path.d.ts +1 -0
  111. package/dist/shared/path/path.js +21 -1
  112. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  113. package/dist/shared/process/host-process-recovery.js +78 -0
  114. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  115. package/dist/shared/process/host-process-supervisor.js +330 -0
  116. package/dist/shared/process/process.d.ts +1 -0
  117. package/dist/shared/process/process.js +1 -0
  118. package/dist/shared/redaction/redaction.js +12 -2
  119. package/dist/version.d.ts +1 -1
  120. package/dist/version.js +1 -1
  121. package/dist/worktree/artifact-injector.d.ts +3 -2
  122. package/dist/worktree/artifact-injector.js +43 -4
  123. package/dist/worktree/dependency-warmer.d.ts +28 -1
  124. package/dist/worktree/dependency-warmer.js +308 -9
  125. package/dist/worktree/index.d.ts +2 -1
  126. package/dist/worktree/index.js +1 -0
  127. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  128. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  129. package/dist/worktree/worktree-paths.d.ts +4 -0
  130. package/dist/worktree/worktree-paths.js +30 -0
  131. package/dist/worktree/worktree.base.d.ts +3 -0
  132. package/dist/worktree/worktree.service.d.ts +9 -2
  133. package/dist/worktree/worktree.service.js +217 -48
  134. package/dist/worktree/worktree.types.d.ts +5 -1
  135. package/package.json +5 -3
  136. package/runtime/windows-job-supervisor.ps1 +444 -0
  137. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -1,4 +1,5 @@
1
1
  import { type LaunchCommand } from "../../shared/process/windows-launch.js";
2
+ export declare const PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS: number;
2
3
  export type ProviderCliCommand = LaunchCommand;
3
4
  export interface ProviderProcessRunRequest {
4
5
  readonly command: string;
@@ -6,16 +7,22 @@ export interface ProviderProcessRunRequest {
6
7
  readonly cwd: string;
7
8
  readonly env?: Readonly<Record<string, string>>;
8
9
  readonly stdin?: string;
10
+ readonly captureOutput?: boolean;
9
11
  }
10
12
  export interface ProviderProcessRunResult {
11
13
  readonly exitCode: number;
12
14
  readonly stdout: string;
13
15
  readonly stderr: string;
16
+ readonly peakMemoryBytes?: number;
17
+ readonly memorySampleCount?: number;
18
+ readonly memorySource?: string;
19
+ readonly memoryObservedAt?: string;
14
20
  }
15
21
  export interface ProviderProcessRunner {
16
22
  run(request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal): Promise<ProviderProcessRunResult>;
17
23
  }
18
24
  export interface ProviderProcessHandlers {
25
+ onStarted?(): Promise<void> | void;
19
26
  onStdoutLine?(line: string): Promise<void> | void;
20
27
  onStderrLine?(line: string): Promise<void> | void;
21
28
  }
@@ -28,5 +35,21 @@ export declare function buildProviderChildEnvironment(requestEnv?: Readonly<Reco
28
35
  export declare class StreamingProviderProcessRunner implements ProviderProcessRunner {
29
36
  run(request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal): Promise<ProviderProcessRunResult>;
30
37
  }
38
+ /** Returns aggregate resident bytes for the exact supervised Unix process group. */
39
+ export declare function processGroupResidentMemoryBytes(processGroupId: number, platform?: NodeJS.Platform, procRoot?: string): number | null;
40
+ export declare function resolveCodexNpmDirectLaunch(commandPath: string, args: readonly string[]): LaunchCommand | null;
41
+ /**
42
+ * Avoids cmd.exe's nested-quote handling for package-manager shims. That
43
+ * quoting loses executable paths containing spaces after the command is
44
+ * wrapped by the Windows Job Object supervisor.
45
+ */
46
+ export declare function resolvePackageManagerDirectLaunch(commandPath: string, args: readonly string[]): LaunchCommand | null;
47
+ /**
48
+ * Resolves Windows commands without spawning where.exe. Runtime launches can
49
+ * happen while the machine is under enough process pressure for where.exe to
50
+ * miss its timeout; returning the unresolved package-manager name in that case
51
+ * makes Node fall back to a shell shim that cannot survive Job Object wrapping.
52
+ */
53
+ export declare function resolveWindowsCommandFromPath(command: string, pathValue?: string, pathExtValue?: string): string | null;
31
54
  export declare function chooseWindowsCommandCandidate(candidates: readonly string[]): string | null;
32
55
  export {};
@@ -1,11 +1,17 @@
1
- import { execFile, spawn } from "node:child_process";
1
+ import { execFile, execFileSync, spawn } from "node:child_process";
2
+ import fs from "node:fs";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
5
  import readline from "node:readline";
5
6
  import { promisify } from "node:util";
6
7
  import { resolveCursorAgentDirectLaunch } from "../../shared/cursor-agent/cursor-agent.launch.js";
8
+ import { persistHostProcessSupervisorMetadata, resolveHostProcessSupervisedLaunch, terminateHostProcessTree, } from "../../shared/process/host-process-supervisor.js";
7
9
  import { buildWindowsLaunchCommand } from "../../shared/process/windows-launch.js";
8
10
  const execFileAsync = promisify(execFile);
11
+ // Provider processes can emit many gigabytes over a long-running session. The
12
+ // line handlers stream that output to Forge as it arrives; the completed result
13
+ // only needs a bounded diagnostic tail for failure classification.
14
+ export const PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS = 128 * 1024;
9
15
  const allowedProviderParentEnvNames = [
10
16
  "PATH",
11
17
  "HOME",
@@ -56,8 +62,37 @@ export function buildProviderChildEnvironment(requestEnv, options = {}) {
56
62
  if (platform === "win32") {
57
63
  normalizeWindowsDotNetEnvironment(env, parentEnv, options.homeDir ?? os.homedir());
58
64
  }
65
+ normalizeProviderBuildEnvironment(env);
59
66
  return env;
60
67
  }
68
+ function normalizeProviderBuildEnvironment(env) {
69
+ // Provider sessions are long lived and commonly run many dotnet builds. Persistent
70
+ // MSBuild and Roslyn compiler servers otherwise survive individual commands and can
71
+ // retain more memory than the active Forge lane. Keep those caches process-scoped by
72
+ // default on every runner OS; an explicitly supplied request value still wins.
73
+ setCanonicalProviderBuildEnvironmentValue(env, "DOTNET_CLI_USE_MSBUILD_SERVER", "0");
74
+ setCanonicalProviderBuildEnvironmentValue(env, "MSBUILDDISABLENODEREUSE", "1");
75
+ setCanonicalProviderBuildEnvironmentValue(env, "UseSharedCompilation", "false");
76
+ }
77
+ function setCanonicalProviderBuildEnvironmentValue(env, name, value) {
78
+ // The dotnet CLI uses case-sensitive environment lookup on POSIX, while MSBuild
79
+ // property names are case-insensitive on every OS. Collapse request aliases to
80
+ // one canonical key so the explicit value wins without producing ambiguity.
81
+ const exact = env[name];
82
+ const alias = Object.entries(env).find(([key, candidate]) => key !== name
83
+ && key.toLowerCase() === name.toLowerCase()
84
+ && typeof candidate === "string"
85
+ && candidate.trim().length > 0)?.[1];
86
+ const resolved = typeof exact === "string" && exact.trim().length > 0
87
+ ? exact
88
+ : alias ?? value;
89
+ for (const key of Object.keys(env)) {
90
+ if (key !== name && key.toLowerCase() === name.toLowerCase()) {
91
+ delete env[key];
92
+ }
93
+ }
94
+ env[name] = resolved;
95
+ }
61
96
  function isAllowedProviderParentEnv(key, platform = process.platform) {
62
97
  return isAllowedProviderParentEnvName(key, allowedProviderParentEnvNames)
63
98
  || key.toUpperCase().startsWith("LC_")
@@ -137,24 +172,40 @@ function setEnvironmentValueIfMissing(env, name, value) {
137
172
  }
138
173
  export class StreamingProviderProcessRunner {
139
174
  async run(request, handlers = {}, signal) {
140
- const launch = await resolveProviderProcessLaunch(request.command, request.args);
175
+ const providerLaunch = await resolveProviderProcessLaunch(request.command, request.args);
176
+ const supervisedLaunch = resolveHostProcessSupervisedLaunch(request, providerLaunch.command, providerLaunch.args);
177
+ // An unsupervised Unix provider still gets a dedicated process group so a
178
+ // cancellation can stop descendants instead of only the CLI root.
179
+ const launch = supervisedLaunch ?? { ...providerLaunch, detached: process.platform !== "win32" };
141
180
  return await new Promise((resolve, reject) => {
181
+ const rootCreatedAt = new Date().toISOString();
142
182
  const child = spawn(launch.command, launch.args, {
143
183
  cwd: request.cwd,
144
184
  env: buildProviderChildEnvironment(request.env),
145
185
  stdio: [request.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
146
- windowsHide: true
186
+ windowsHide: true,
187
+ detached: launch.detached,
147
188
  });
189
+ const memoryObserver = supervisedLaunch
190
+ ? new HostProcessTreeMemoryObserver(supervisedLaunch, child.pid ?? 0)
191
+ : null;
192
+ let termination = null;
193
+ const terminate = () => {
194
+ termination ??= terminateHostProcessTree(child, supervisedLaunch);
195
+ return termination;
196
+ };
148
197
  const childStdout = child.stdout;
149
198
  const childStderr = child.stderr;
150
199
  if (!childStdout || !childStderr) {
151
- child.kill("SIGTERM");
152
- reject(new Error("Provider process stdout/stderr streams were not piped."));
200
+ const pipeError = new Error("Provider process stdout/stderr streams were not piped.");
201
+ void terminate().then(() => reject(pipeError), terminationError => reject(new AggregateError([pipeError, terminationError], "Provider launch failed and its process tree could not be proven stopped.")));
153
202
  return;
154
203
  }
155
- let stdout = "";
156
- let stderr = "";
204
+ const stdout = new BoundedOutputTail(PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS);
205
+ const stderr = new BoundedOutputTail(PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS);
157
206
  let settled = false;
207
+ let closed = false;
208
+ let readiness = null;
158
209
  const cleanup = () => {
159
210
  signal?.removeEventListener("abort", abort);
160
211
  };
@@ -164,15 +215,15 @@ export class StreamingProviderProcessRunner {
164
215
  }
165
216
  settled = true;
166
217
  cleanup();
167
- reject(error);
218
+ // Do not release the caller's reservation/worktree/port ownership until
219
+ // descendant termination has completed (or failed with explicit proof).
220
+ void terminate().then(() => reject(error), terminationError => reject(new AggregateError([error, terminationError], "Provider process failed and its process tree could not be proven stopped.")));
168
221
  };
169
222
  const abort = () => {
170
- try {
171
- child.kill("SIGTERM");
172
- }
173
- catch {
174
- // Best effort; close/error will settle the promise.
175
- }
223
+ // Cancellation is not a launch failure when the supervised tree can be
224
+ // proven stopped. Let the close handler settle the command after exact
225
+ // termination; only a failed termination proof rejects the run.
226
+ void terminate().catch(fail);
176
227
  };
177
228
  if (signal?.aborted) {
178
229
  abort();
@@ -181,40 +232,69 @@ export class StreamingProviderProcessRunner {
181
232
  signal?.addEventListener("abort", abort, { once: true });
182
233
  }
183
234
  child.on("error", fail);
184
- if (request.stdin !== undefined) {
185
- if (!child.stdin) {
186
- try {
187
- child.kill("SIGTERM");
235
+ child.once("spawn", () => {
236
+ readiness = (async () => {
237
+ const providerReady = supervisedLaunch?.strategy === "windows_job_object"
238
+ ? await waitForWindowsProviderReady(supervisedLaunch, signal, () => closed)
239
+ : undefined;
240
+ if (supervisedLaunch) {
241
+ persistHostProcessSupervisorMetadata(supervisedLaunch, request, child.pid ?? 0, rootCreatedAt, providerReady);
188
242
  }
189
- catch {
190
- // Best effort; the missing stdin pipe will fail the provider run.
243
+ await handlers.onStarted?.();
244
+ if (supervisedLaunch?.strategy === "windows_job_object") {
245
+ grantWindowsProviderStart(supervisedLaunch);
191
246
  }
247
+ })();
248
+ void readiness.catch(error => {
249
+ if (!closed && !signal?.aborted)
250
+ fail(error);
251
+ });
252
+ });
253
+ const writeProviderInput = () => {
254
+ if (request.stdin === undefined)
255
+ return;
256
+ if (!child.stdin) {
257
+ void terminate();
192
258
  fail(new Error("Provider process stdin stream was not piped."));
193
259
  return;
194
260
  }
195
261
  child.stdin.on("error", (error) => {
196
- try {
197
- child.kill("SIGTERM");
198
- }
199
- catch {
200
- // Best effort; the stdin error will fail the provider run.
201
- }
262
+ void terminate();
202
263
  fail(error);
203
264
  });
204
265
  child.stdin.end(request.stdin, "utf8");
266
+ };
267
+ if (request.stdin !== undefined) {
268
+ if (supervisedLaunch?.strategy === "windows_job_object") {
269
+ // The outer supervisor must receive stdin before it can launch the
270
+ // inner provider. Its start-grant file prevents prompt delivery.
271
+ writeProviderInput();
272
+ }
273
+ else {
274
+ child.once("spawn", () => {
275
+ void readiness?.then(writeProviderInput).catch(fail);
276
+ });
277
+ }
205
278
  }
206
279
  const readers = [
207
280
  readProcessLines(childStdout, (line) => {
208
- stdout += `${line}\n`;
281
+ if (request.captureOutput !== false)
282
+ stdout.appendLine(line);
209
283
  return handlers.onStdoutLine?.(line);
210
284
  }),
211
285
  readProcessLines(childStderr, (line) => {
212
- stderr += `${line}\n`;
286
+ if (request.captureOutput !== false)
287
+ stderr.appendLine(line);
213
288
  return handlers.onStderrLine?.(line);
214
289
  })
215
290
  ];
291
+ for (const reader of readers) {
292
+ void reader.catch(fail);
293
+ }
216
294
  child.on("close", (code) => {
217
- Promise.allSettled(readers).then((readerResults) => {
295
+ closed = true;
296
+ Promise.allSettled(readers).then(async (readerResults) => {
297
+ await readiness?.catch(() => undefined);
218
298
  const rejected = readerResults.find((result) => result.status === "rejected");
219
299
  if (rejected?.status === "rejected") {
220
300
  fail(rejected.reason);
@@ -223,18 +303,186 @@ export class StreamingProviderProcessRunner {
223
303
  if (settled) {
224
304
  return;
225
305
  }
306
+ try {
307
+ memoryObserver?.sample();
308
+ if (supervisedLaunch?.strategy === "unix_process_group" || !supervisedLaunch) {
309
+ await terminate();
310
+ }
311
+ else if (termination) {
312
+ await termination;
313
+ }
314
+ }
315
+ catch (error) {
316
+ fail(error);
317
+ return;
318
+ }
226
319
  settled = true;
227
320
  cleanup();
321
+ const memory = memoryObserver?.complete();
228
322
  resolve({
229
323
  exitCode: code ?? 1,
230
- stdout,
231
- stderr
324
+ stdout: stdout.toString(),
325
+ stderr: stderr.toString(),
326
+ ...(memory ?? {}),
232
327
  });
233
328
  }).catch(fail);
234
329
  });
235
330
  });
236
331
  }
237
332
  }
333
+ class HostProcessTreeMemoryObserver {
334
+ launch;
335
+ processGroupId;
336
+ peakMemoryBytes = 0;
337
+ sampleCount = 0;
338
+ observedAt = null;
339
+ timer;
340
+ constructor(launch, processGroupId) {
341
+ this.launch = launch;
342
+ this.processGroupId = processGroupId;
343
+ if (launch.strategy === "unix_process_group" && processGroupId > 0) {
344
+ this.sample();
345
+ this.timer = setInterval(() => this.sample(), 1_000);
346
+ this.timer.unref?.();
347
+ }
348
+ else {
349
+ this.timer = null;
350
+ }
351
+ }
352
+ sample() {
353
+ if (this.launch.strategy !== "unix_process_group" || this.processGroupId <= 0)
354
+ return;
355
+ const bytes = processGroupResidentMemoryBytes(this.processGroupId);
356
+ if (bytes === null)
357
+ return;
358
+ this.sampleCount += 1;
359
+ this.peakMemoryBytes = Math.max(this.peakMemoryBytes, bytes);
360
+ this.observedAt = new Date().toISOString();
361
+ }
362
+ complete() {
363
+ if (this.timer)
364
+ clearInterval(this.timer);
365
+ if (this.launch.strategy === "windows_job_object") {
366
+ return readWindowsJobMemoryTelemetry(this.launch.memoryTelemetryPath);
367
+ }
368
+ if (this.peakMemoryBytes <= 0 || this.sampleCount <= 0 || !this.observedAt)
369
+ return undefined;
370
+ return {
371
+ peakMemoryBytes: this.peakMemoryBytes,
372
+ memorySampleCount: this.sampleCount,
373
+ memorySource: process.platform === "linux"
374
+ ? "linux_process_group_rss"
375
+ : "unix_process_group_rss",
376
+ memoryObservedAt: this.observedAt,
377
+ };
378
+ }
379
+ }
380
+ /** Returns aggregate resident bytes for the exact supervised Unix process group. */
381
+ export function processGroupResidentMemoryBytes(processGroupId, platform = process.platform, procRoot = "/proc") {
382
+ if (!Number.isSafeInteger(processGroupId) || processGroupId <= 0)
383
+ return null;
384
+ if (platform === "linux") {
385
+ let totalKilobytes = 0;
386
+ let matched = 0;
387
+ try {
388
+ for (const entry of fs.readdirSync(procRoot, { withFileTypes: true })) {
389
+ if (!entry.isDirectory() || !/^\d+$/u.test(entry.name))
390
+ continue;
391
+ try {
392
+ const stat = fs.readFileSync(path.join(procRoot, entry.name, "stat"), "utf8");
393
+ const close = stat.lastIndexOf(")");
394
+ const fields = close >= 0 ? stat.slice(close + 2).trim().split(/\s+/u) : [];
395
+ if (Number.parseInt(fields[2] ?? "", 10) !== processGroupId)
396
+ continue;
397
+ const status = fs.readFileSync(path.join(procRoot, entry.name, "status"), "utf8");
398
+ const rss = /^VmRSS:\s+(\d+)\s+kB$/mu.exec(status);
399
+ if (!rss)
400
+ continue;
401
+ totalKilobytes += Number.parseInt(rss[1], 10);
402
+ matched += 1;
403
+ }
404
+ catch {
405
+ // Processes can exit between the directory, stat, and status reads.
406
+ }
407
+ }
408
+ return matched > 0 ? totalKilobytes * 1024 : null;
409
+ }
410
+ catch {
411
+ return null;
412
+ }
413
+ }
414
+ if (platform !== "win32") {
415
+ try {
416
+ const output = execFileSync("ps", ["-axo", "pgid=,rss="], {
417
+ encoding: "utf8",
418
+ timeout: 5_000,
419
+ stdio: ["ignore", "pipe", "ignore"],
420
+ });
421
+ let totalKilobytes = 0;
422
+ let matched = 0;
423
+ for (const line of output.split(/\r?\n/u)) {
424
+ const fields = line.trim().split(/\s+/u);
425
+ if (Number.parseInt(fields[0] ?? "", 10) !== processGroupId)
426
+ continue;
427
+ const rss = Number.parseInt(fields[1] ?? "", 10);
428
+ if (!Number.isSafeInteger(rss) || rss < 0)
429
+ continue;
430
+ totalKilobytes += rss;
431
+ matched += 1;
432
+ }
433
+ return matched > 0 ? totalKilobytes * 1024 : null;
434
+ }
435
+ catch {
436
+ return null;
437
+ }
438
+ }
439
+ return null;
440
+ }
441
+ function readWindowsJobMemoryTelemetry(target) {
442
+ if (!target)
443
+ return undefined;
444
+ try {
445
+ const value = JSON.parse(fs.readFileSync(target, "utf8"));
446
+ const peakMemoryBytes = value.peakMemoryBytes;
447
+ const sampleCount = value.sampleCount;
448
+ const source = value.source;
449
+ const observedAt = value.observedAt;
450
+ if (!Number.isSafeInteger(peakMemoryBytes) || peakMemoryBytes <= 0
451
+ || !Number.isSafeInteger(sampleCount) || sampleCount <= 0
452
+ || typeof source !== "string" || source.length === 0
453
+ || typeof observedAt !== "string" || !Number.isFinite(Date.parse(observedAt))) {
454
+ return undefined;
455
+ }
456
+ return {
457
+ peakMemoryBytes: peakMemoryBytes,
458
+ memorySampleCount: sampleCount,
459
+ memorySource: source,
460
+ memoryObservedAt: observedAt,
461
+ };
462
+ }
463
+ catch {
464
+ return undefined;
465
+ }
466
+ }
467
+ class BoundedOutputTail {
468
+ maxChars;
469
+ value = "";
470
+ constructor(maxChars) {
471
+ this.maxChars = maxChars;
472
+ }
473
+ appendLine(line) {
474
+ const chunk = `${line}\n`;
475
+ if (chunk.length >= this.maxChars) {
476
+ this.value = chunk.slice(-this.maxChars);
477
+ return;
478
+ }
479
+ const retainedPrefixChars = this.maxChars - chunk.length;
480
+ this.value = `${this.value.slice(-retainedPrefixChars)}${chunk}`;
481
+ }
482
+ toString() {
483
+ return this.value;
484
+ }
485
+ }
238
486
  async function resolveProviderProcessLaunch(command, args) {
239
487
  if (process.platform !== "win32") {
240
488
  return { command, args };
@@ -244,12 +492,115 @@ async function resolveProviderProcessLaunch(command, args) {
244
492
  return cursorDirect;
245
493
  }
246
494
  const resolved = await resolveWindowsCommand(command);
247
- return resolved ? buildWindowsLaunchCommand(resolved, args) : { command, args };
495
+ if (!resolved) {
496
+ return { command, args };
497
+ }
498
+ return resolveCodexNpmDirectLaunch(resolved, args)
499
+ ?? resolvePackageManagerDirectLaunch(resolved, args)
500
+ ?? buildWindowsLaunchCommand(resolved, args);
501
+ }
502
+ function grantWindowsProviderStart(launch) {
503
+ if (!launch.startGrantPath) {
504
+ throw new Error("host_process_supervisor_invalid: Windows provider start-grant path was not assigned.");
505
+ }
506
+ fs.writeFileSync(launch.startGrantPath, "granted\n", {
507
+ encoding: "utf8",
508
+ flag: "wx",
509
+ mode: 0o600,
510
+ });
511
+ }
512
+ async function waitForWindowsProviderReady(launch, signal, isClosed) {
513
+ const readyPath = launch.readyPath;
514
+ if (!readyPath) {
515
+ throw new Error("host_process_supervisor_invalid: Windows provider-ready path was not assigned.");
516
+ }
517
+ while (true) {
518
+ try {
519
+ const parsed = JSON.parse(await fs.promises.readFile(readyPath, "utf8"));
520
+ if (parsed.schemaVersion !== 1
521
+ || parsed.supervisorId !== launch.supervisorId
522
+ || !Number.isSafeInteger(parsed.rootPid)
523
+ || (parsed.rootPid ?? 0) <= 0
524
+ || typeof parsed.rootIdentity !== "string"
525
+ || !/^win32:\d+$/u.test(parsed.rootIdentity)
526
+ || !Number.isSafeInteger(parsed.providerPid)
527
+ || (parsed.providerPid ?? 0) <= 0
528
+ || typeof parsed.providerCreatedAt !== "string"
529
+ || Number.isNaN(Date.parse(parsed.providerCreatedAt))
530
+ || (parsed.providerIdentity !== undefined
531
+ && (typeof parsed.providerIdentity !== "string" || !/^win32:\d+$/u.test(parsed.providerIdentity)))
532
+ || !Number.isSafeInteger(parsed.startAttempt)
533
+ || (parsed.startAttempt ?? 0) < 1) {
534
+ throw new Error("host_process_supervisor_invalid: provider-ready receipt was invalid.");
535
+ }
536
+ return parsed;
537
+ }
538
+ catch (error) {
539
+ if (error.code !== "ENOENT")
540
+ throw error;
541
+ }
542
+ if (isClosed()) {
543
+ throw new Error("host_process_supervisor_start_failed: supervisor exited before the provider became ready.");
544
+ }
545
+ if (signal?.aborted) {
546
+ throw signal.reason instanceof Error ? signal.reason : new Error("Provider process launch was aborted.");
547
+ }
548
+ await new Promise(resolve => setTimeout(resolve, 25));
549
+ }
550
+ }
551
+ export function resolveCodexNpmDirectLaunch(commandPath, args) {
552
+ const extension = path.extname(commandPath).toLowerCase();
553
+ if ((extension !== ".cmd" && extension !== ".ps1")
554
+ || path.basename(commandPath, extension).toLowerCase() !== "codex") {
555
+ return null;
556
+ }
557
+ const entryPoint = path.join(path.dirname(commandPath), "node_modules", "@openai", "codex", "bin", "codex.js");
558
+ return fs.existsSync(entryPoint)
559
+ ? { command: process.execPath, args: [entryPoint, ...args] }
560
+ : null;
561
+ }
562
+ /**
563
+ * Avoids cmd.exe's nested-quote handling for package-manager shims. That
564
+ * quoting loses executable paths containing spaces after the command is
565
+ * wrapped by the Windows Job Object supervisor.
566
+ */
567
+ export function resolvePackageManagerDirectLaunch(commandPath, args) {
568
+ const extension = path.extname(commandPath).toLowerCase();
569
+ if (extension !== ".cmd" && extension !== ".ps1")
570
+ return null;
571
+ const packageManager = path.basename(commandPath, extension).toLowerCase();
572
+ if (!["corepack", "npm", "npx", "pnpm", "pnpx", "yarn", "yarnpkg"].includes(packageManager))
573
+ return null;
574
+ const root = path.dirname(commandPath);
575
+ const candidates = [
576
+ path.join(root, "node_modules", "corepack", "dist", packageManager === "corepack" ? "corepack.js" : `${packageManager}.js`),
577
+ ...(packageManager === "npm" || packageManager === "npx"
578
+ ? [path.join(root, "node_modules", "npm", "bin", `${packageManager}-cli.js`)]
579
+ : []),
580
+ ...(packageManager === "pnpm" || packageManager === "pnpx"
581
+ ? [path.join(root, "node_modules", "pnpm", "bin", `${packageManager}.cjs`)]
582
+ : []),
583
+ ...(packageManager === "yarn" || packageManager === "yarnpkg"
584
+ ? [path.join(root, "node_modules", "yarn", "bin", "yarn.js")]
585
+ : []),
586
+ ];
587
+ const entryPoint = candidates.find(candidate => fs.existsSync(candidate));
588
+ if (!entryPoint)
589
+ return null;
590
+ const adjacentNode = path.join(root, "node.exe");
591
+ return {
592
+ command: fs.existsSync(adjacentNode) ? adjacentNode : process.execPath,
593
+ args: [entryPoint, ...args],
594
+ };
248
595
  }
249
596
  async function resolveWindowsCommand(command) {
250
597
  if (path.isAbsolute(command)) {
251
598
  return command;
252
599
  }
600
+ const resolvedFromPath = resolveWindowsCommandFromPath(command);
601
+ if (resolvedFromPath) {
602
+ return resolvedFromPath;
603
+ }
253
604
  try {
254
605
  const result = await execFileAsync("where.exe", [command], { timeout: 2000 });
255
606
  return chooseWindowsCommandCandidate(result.stdout.split(/\r?\n/));
@@ -258,6 +609,43 @@ async function resolveWindowsCommand(command) {
258
609
  return null;
259
610
  }
260
611
  }
612
+ /**
613
+ * Resolves Windows commands without spawning where.exe. Runtime launches can
614
+ * happen while the machine is under enough process pressure for where.exe to
615
+ * miss its timeout; returning the unresolved package-manager name in that case
616
+ * makes Node fall back to a shell shim that cannot survive Job Object wrapping.
617
+ */
618
+ export function resolveWindowsCommandFromPath(command, pathValue = process.env.PATH ?? "", pathExtValue = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
619
+ if (!command || path.isAbsolute(command) || command.includes("/") || command.includes("\\")) {
620
+ return null;
621
+ }
622
+ const hasExtension = path.extname(command).length > 0;
623
+ const extensions = hasExtension
624
+ ? [""]
625
+ : [...new Set([
626
+ "",
627
+ ".exe",
628
+ ".ps1",
629
+ ".cmd",
630
+ ".bat",
631
+ ...pathExtValue.split(";").map(value => value.trim().toLowerCase()).filter(Boolean),
632
+ ])];
633
+ for (const rawDirectory of pathValue.split(path.delimiter)) {
634
+ const directory = rawDirectory.trim().replace(/^"|"$/gu, "");
635
+ if (!directory)
636
+ continue;
637
+ const candidates = [];
638
+ for (const extension of extensions) {
639
+ const candidate = path.join(directory, `${command}${extension}`);
640
+ if (fs.existsSync(candidate))
641
+ candidates.push(candidate);
642
+ }
643
+ const resolved = chooseWindowsCommandCandidate(candidates);
644
+ if (resolved)
645
+ return resolved;
646
+ }
647
+ return null;
648
+ }
261
649
  export function chooseWindowsCommandCandidate(candidates) {
262
650
  const normalized = candidates
263
651
  .map((candidate) => candidate.trim())
@@ -17,12 +17,15 @@ export declare function formatProviderCliCommandForCheckpoint(command: ProviderC
17
17
  export declare function singleLine(value: string): string;
18
18
  export declare function buildProviderCliCommand(dispatch: RunOnceDispatchPacket, run?: {
19
19
  readonly prompt?: string;
20
+ readonly promptFromStdin?: boolean;
20
21
  readonly worktreePath?: string;
21
22
  readonly completionTools?: CompletionToolRuntime;
22
23
  readonly skipGitRepoCheck?: boolean;
23
24
  readonly imagePaths?: readonly string[];
25
+ readonly runtimeEnvironment?: Readonly<Record<string, string>>;
24
26
  }): ProviderCliCommand;
25
- export declare function codexSandboxModeForPlatform(platform?: NodeJS.Platform): "workspace-write" | "danger-full-access";
27
+ export declare function buildProviderCliPrompt(dispatch: RunOnceDispatchPacket, prompt: string, runtimeEnvironment?: Readonly<Record<string, string>>): string;
28
+ export declare function codexSandboxModeForPlatform(platform?: NodeJS.Platform, executionMode?: string): "workspace-write" | "danger-full-access";
26
29
  export declare function buildArchitectPrompt(dispatch: RunOnceDispatchPacket): string;
27
30
  export declare function architectDependencyInstruction(dispatch: RunOnceDispatchPacket): string;
28
31
  export declare function designerRefInstruction(dispatch: RunOnceDispatchPacket): string;