@loucompanion/forge-bridge 0.1.3-dev.fbe15023cdfe → 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.
- package/README.md +88 -2
- package/dist/bridge/bridge-chat.types.d.ts +111 -0
- package/dist/bridge/bridge-chat.types.js +401 -0
- package/dist/bridge/bridge.base.d.ts +4 -1
- package/dist/bridge/bridge.service.d.ts +72 -3
- package/dist/bridge/bridge.service.js +1563 -152
- package/dist/bridge/bridge.types.d.ts +46 -1
- package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
- package/dist/bridge/dispatch-result-outbox.js +62 -0
- package/dist/bridge/dispatch-result-worker.d.ts +2 -0
- package/dist/bridge/dispatch-result-worker.js +122 -0
- package/dist/bridge/internals.d.ts +2 -0
- package/dist/bridge/internals.js +1 -0
- package/dist/bridge/readiness-result-outbox.d.ts +21 -0
- package/dist/bridge/readiness-result-outbox.js +54 -0
- package/dist/bridge/readiness-result-worker.d.ts +2 -0
- package/dist/bridge/readiness-result-worker.js +121 -0
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +1 -0
- package/dist/cleanup/cleanup.service.js +70 -9
- package/dist/cli/cli.help.js +2 -2
- package/dist/cli/cli.service.d.ts +14 -6
- package/dist/cli/cli.service.js +75 -16
- package/dist/cli/local-config.d.ts +1 -0
- package/dist/cli/local-config.js +4 -0
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
- package/dist/runtime/attempt-database-broker.d.ts +89 -0
- package/dist/runtime/attempt-database-broker.js +390 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +2 -1
- package/dist/runtime/attempt-node-runtime-launcher.js +57 -18
- package/dist/runtime/attempt-path-operation.d.ts +21 -0
- package/dist/runtime/attempt-path-operation.js +94 -0
- package/dist/runtime/attempt-private-directory.d.ts +6 -0
- package/dist/runtime/attempt-private-directory.js +54 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +16 -0
- package/dist/runtime/attempt-process-supervisor.js +94 -23
- package/dist/runtime/canonical-source-file.d.ts +3 -0
- package/dist/runtime/canonical-source-file.js +54 -0
- package/dist/runtime/docker-engine-probe.d.ts +90 -0
- package/dist/runtime/docker-engine-probe.js +333 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +62 -8
- package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
- package/dist/runtime/host-database-capability-boundary.js +62 -0
- package/dist/runtime/host-diagnostics.d.ts +11 -0
- package/dist/runtime/host-diagnostics.js +192 -0
- package/dist/runtime/host-execution-root.d.ts +4 -0
- package/dist/runtime/host-execution-root.js +39 -0
- package/dist/runtime/host-listener-inspector.d.ts +8 -1
- package/dist/runtime/host-listener-inspector.js +121 -60
- package/dist/runtime/host-runtime-contract.d.ts +12 -1
- package/dist/runtime/host-runtime-contract.js +168 -55
- package/dist/runtime/host-runtime-lease.d.ts +92 -8
- package/dist/runtime/host-runtime-lease.js +1141 -114
- package/dist/runtime/host-runtime-promotion.d.ts +12 -3
- package/dist/runtime/host-runtime-promotion.js +59 -12
- package/dist/runtime/memory-capacity.d.ts +70 -0
- package/dist/runtime/memory-capacity.js +123 -0
- package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
- package/dist/runtime/native-test-postgres-fallback.js +91 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +3 -0
- package/dist/runtime/node-host-runtime-adapter.js +29 -11
- package/dist/runtime/pinned-directory-process.d.ts +9 -0
- package/dist/runtime/pinned-directory-process.js +61 -0
- package/dist/runtime/pinned-source-directory.d.ts +17 -0
- package/dist/runtime/pinned-source-directory.js +94 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +1 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +53 -5
- package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
- package/dist/runtime/prepared-runtime-identity.js +218 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
- package/dist/runtime/prepared-runtime-readiness.js +285 -0
- package/dist/runtime/runtime-isolation-lease.js +4 -1
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/preflight.d.ts +31 -1
- package/dist/sandbox/preflight.js +237 -51
- package/dist/sandbox/repository-lock.d.ts +2 -1
- package/dist/sandbox/repository-lock.js +19 -9
- package/dist/sandbox/runtime.d.ts +6 -1
- package/dist/sandbox/runtime.js +137 -107
- package/dist/sandbox/sandbox.types.d.ts +7 -0
- package/dist/sandbox/workspace.service.js +2 -0
- package/dist/session/managed-attempt-tools.d.ts +17 -1
- package/dist/session/managed-attempt-tools.js +489 -34
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +106 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +337 -0
- package/dist/session/managed-verification-timeouts.d.ts +8 -0
- package/dist/session/managed-verification-timeouts.js +17 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +224 -22
- package/dist/session/phase-completion.d.ts +11 -1
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +30 -7
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.js +17 -18
- package/dist/session/phase-completion.runtime.d.ts +3 -1
- package/dist/session/phase-completion.runtime.js +126 -17
- package/dist/session/phase-completion.schemas.js +49 -2
- package/dist/session/phase-completion.validation.d.ts +1 -0
- package/dist/session/phase-completion.validation.js +111 -21
- package/dist/session/pre-publication-validation.d.ts +75 -0
- package/dist/session/pre-publication-validation.js +661 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +23 -3
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +4 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +47 -13
- package/dist/session/provider-cli/provider-cli.deterministic.js +34 -2
- package/dist/session/provider-cli/provider-cli.process.d.ts +51 -0
- package/dist/session/provider-cli/provider-cli.process.js +422 -22
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +5 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +136 -16
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +14 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +54 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +30 -5
- package/dist/session/provider-cli/provider-cli.service.js +773 -69
- package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +10 -0
- package/dist/session/provider-cli/provider-output-state.js +361 -17
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.js +434 -9
- package/dist/session/session.service.js +145 -23
- package/dist/session/session.types.d.ts +147 -2
- package/dist/session/session.types.js +587 -7
- package/dist/session/unreferenced-exports.d.ts +12 -0
- package/dist/session/unreferenced-exports.js +59 -0
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/git/git.js +2 -1
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +18 -0
- package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
- package/dist/shared/process/dotnet-toolchain.js +307 -0
- package/dist/shared/process/host-process-recovery.js +3 -2
- package/dist/shared/process/host-process-supervisor.d.ts +2 -0
- package/dist/shared/process/host-process-supervisor.js +60 -11
- package/dist/shared/process/process.d.ts +2 -0
- package/dist/shared/process/process.js +12 -7
- package/dist/shared/provider-capabilities.d.ts +3 -0
- package/dist/shared/provider-capabilities.js +85 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +39 -3
- package/dist/shared/time/monotonic.d.ts +1 -0
- package/dist/shared/time/monotonic.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/dependency-warmer.d.ts +41 -9
- package/dist/worktree/dependency-warmer.js +497 -22
- package/dist/worktree/index.d.ts +1 -1
- package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -0
- package/dist/worktree/worktree.service.d.ts +7 -0
- package/dist/worktree/worktree.service.js +253 -21
- package/dist/worktree/worktree.types.d.ts +7 -1
- package/package.json +7 -5
- package/runtime/windows-job-supervisor.ps1 +380 -14
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { execFile, spawn } from "node:child_process";
|
|
1
|
+
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
@@ -7,7 +8,138 @@ import { promisify } from "node:util";
|
|
|
7
8
|
import { resolveCursorAgentDirectLaunch } from "../../shared/cursor-agent/cursor-agent.launch.js";
|
|
8
9
|
import { persistHostProcessSupervisorMetadata, resolveHostProcessSupervisedLaunch, terminateHostProcessTree, } from "../../shared/process/host-process-supervisor.js";
|
|
9
10
|
import { buildWindowsLaunchCommand } from "../../shared/process/windows-launch.js";
|
|
11
|
+
import { assertHostDatabaseCapabilityBoundary } from "../../runtime/host-database-capability-boundary.js";
|
|
12
|
+
import { ForgeFailureError } from "../session.types.js";
|
|
10
13
|
const execFileAsync = promisify(execFile);
|
|
14
|
+
// Provider processes can emit many gigabytes over a long-running session. The
|
|
15
|
+
// line handlers stream that output to Forge as it arrives; the completed result
|
|
16
|
+
// only needs a bounded diagnostic tail for failure classification.
|
|
17
|
+
export const PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS = 128 * 1024;
|
|
18
|
+
const PROVIDER_TERMINAL_EXIT_GRACE_MS = 15_000;
|
|
19
|
+
const maximumRecoverableCodexModelsCacheBytes = 16 * 1024 * 1024;
|
|
20
|
+
/** Recovery must wrap the attempt coordinator, never its raw OS runner. Each
|
|
21
|
+
* relaunch needs a new fenced child generation and exclusive launch files. */
|
|
22
|
+
export function recoveringProviderProcessRunner(inner) {
|
|
23
|
+
return new CodexResumeRecoveringProcessRunner(new CodexModelsCacheRecoveringProcessRunner(inner));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Codex Desktop and an independently installed Codex CLI can briefly disagree
|
|
27
|
+
* about the shared models-cache schema during a rolling upgrade. Recover only
|
|
28
|
+
* that exact failure by quarantining the bounded cache file and retrying once;
|
|
29
|
+
* auth, config, sessions, and every other CODEX_HOME entry remain untouched.
|
|
30
|
+
*/
|
|
31
|
+
export class CodexModelsCacheRecoveringProcessRunner {
|
|
32
|
+
inner;
|
|
33
|
+
constructor(inner) {
|
|
34
|
+
this.inner = inner;
|
|
35
|
+
}
|
|
36
|
+
async run(request, handlers, signal) {
|
|
37
|
+
const first = await this.inner.run(request, handlers, signal);
|
|
38
|
+
if (first.exitCode === 0
|
|
39
|
+
|| signal?.aborted
|
|
40
|
+
|| !isCodexCommand(request.command)
|
|
41
|
+
|| !isCodexModelsCacheSchemaFailure(first)) {
|
|
42
|
+
return first;
|
|
43
|
+
}
|
|
44
|
+
const recovered = await quarantineIncompatibleCodexModelsCache(request.env).catch(() => false);
|
|
45
|
+
if (!recovered || signal?.aborted)
|
|
46
|
+
return first;
|
|
47
|
+
return await this.inner.run(request, handlers, signal);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Some Codex CLI builds can start new turns but cannot resume an older thread
|
|
52
|
+
* because the backing app does not implement the paginated thread lookup RPC.
|
|
53
|
+
* Preserve the prepared worktree and prompt, then retry once as a fresh turn.
|
|
54
|
+
*/
|
|
55
|
+
export class CodexResumeRecoveringProcessRunner {
|
|
56
|
+
inner;
|
|
57
|
+
constructor(inner) {
|
|
58
|
+
this.inner = inner;
|
|
59
|
+
}
|
|
60
|
+
async run(request, handlers, signal) {
|
|
61
|
+
const first = await this.inner.run(request, handlers, signal);
|
|
62
|
+
if (first.exitCode === 0
|
|
63
|
+
|| signal?.aborted
|
|
64
|
+
|| !isCodexCommand(request.command)
|
|
65
|
+
|| !isRecoverableCodexResumeFailure(request.command, first)) {
|
|
66
|
+
return first;
|
|
67
|
+
}
|
|
68
|
+
const fallback = codexFreshTurnRequest(request);
|
|
69
|
+
return fallback ? await this.inner.run(fallback, handlers, signal) : first;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function isRecoverableCodexResumeFailure(command, result) {
|
|
73
|
+
const output = `${result.stderr}\n${result.stdout}`.toLowerCase();
|
|
74
|
+
return (output.includes("thread/resume failed")
|
|
75
|
+
&& output.includes("paginated_threads is not supported yet"))
|
|
76
|
+
|| isTransientCodexProviderNotFoundFailure(command, result);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Recognizes the exact nonzero Codex service response at the provider-process
|
|
80
|
+
* boundary. Provider stdout is deliberately excluded because it can contain
|
|
81
|
+
* arbitrary tool/command output and therefore cannot mint retry authority.
|
|
82
|
+
*/
|
|
83
|
+
export function isTransientCodexProviderNotFoundFailure(command, result) {
|
|
84
|
+
if (result.exitCode === 0 || !isCodexCommand(command))
|
|
85
|
+
return false;
|
|
86
|
+
return result.stderr
|
|
87
|
+
.split(/\r?\n/u)
|
|
88
|
+
.map(line => line.trim().toLowerCase())
|
|
89
|
+
.some(line => line === "not found: status code '404'"
|
|
90
|
+
|| line === 'not found: status code "404"');
|
|
91
|
+
}
|
|
92
|
+
function codexFreshTurnRequest(request) {
|
|
93
|
+
const args = [...request.args];
|
|
94
|
+
const resumeIndex = args.indexOf("resume");
|
|
95
|
+
if (resumeIndex < 0 || args.length < 2)
|
|
96
|
+
return null;
|
|
97
|
+
// buildProviderCliCommand always places the thread id immediately before
|
|
98
|
+
// the prompt. Remove it before moving exec-level --cd back out of the
|
|
99
|
+
// resume subcommand shape.
|
|
100
|
+
args.splice(args.length - 2, 1);
|
|
101
|
+
args.splice(resumeIndex, 1);
|
|
102
|
+
const cdIndex = args.indexOf("--cd");
|
|
103
|
+
if (cdIndex >= 0 && cdIndex + 1 < args.length) {
|
|
104
|
+
const cd = args.splice(cdIndex, 2);
|
|
105
|
+
args.splice(args.length - 1, 0, ...cd);
|
|
106
|
+
}
|
|
107
|
+
return { ...request, args };
|
|
108
|
+
}
|
|
109
|
+
function isCodexCommand(command) {
|
|
110
|
+
return path.parse(command).name.toLowerCase() === "codex";
|
|
111
|
+
}
|
|
112
|
+
function isCodexModelsCacheSchemaFailure(result) {
|
|
113
|
+
const output = `${result.stderr}\n${result.stdout}`.toLowerCase();
|
|
114
|
+
return output.includes("base_instructions")
|
|
115
|
+
&& output.includes("missing field")
|
|
116
|
+
&& (output.includes("models_cache") || output.includes("models cache") || output.includes("model cache"));
|
|
117
|
+
}
|
|
118
|
+
async function quarantineIncompatibleCodexModelsCache(requestEnv) {
|
|
119
|
+
const configuredHome = readEnvironmentValue(requestEnv ?? {}, "CODEX_HOME")
|
|
120
|
+
?? readEnvironmentValue(process.env, "CODEX_HOME")
|
|
121
|
+
?? readEnvironmentValue(process.env, "FORGE_CODEX_HOME");
|
|
122
|
+
const codexHome = path.resolve(configuredHome ?? path.join(os.homedir(), ".codex"));
|
|
123
|
+
if (!path.isAbsolute(codexHome) || codexHome === path.parse(codexHome).root)
|
|
124
|
+
return false;
|
|
125
|
+
const cachePath = path.join(codexHome, "models_cache.json");
|
|
126
|
+
const cacheStat = await fs.promises.lstat(cachePath).catch(error => {
|
|
127
|
+
if (error.code === "ENOENT")
|
|
128
|
+
return null;
|
|
129
|
+
throw error;
|
|
130
|
+
});
|
|
131
|
+
if (!cacheStat
|
|
132
|
+
|| !cacheStat.isFile()
|
|
133
|
+
|| cacheStat.isSymbolicLink()
|
|
134
|
+
|| cacheStat.nlink !== 1
|
|
135
|
+
|| cacheStat.size <= 0
|
|
136
|
+
|| cacheStat.size > maximumRecoverableCodexModelsCacheBytes) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const quarantinePath = path.join(codexHome, `models_cache.forge-incompatible-${Date.now()}-${randomUUID()}.json`);
|
|
140
|
+
await fs.promises.rename(cachePath, quarantinePath);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
11
143
|
const allowedProviderParentEnvNames = [
|
|
12
144
|
"PATH",
|
|
13
145
|
"HOME",
|
|
@@ -15,7 +147,8 @@ const allowedProviderParentEnvNames = [
|
|
|
15
147
|
"TMPDIR",
|
|
16
148
|
"TEMP",
|
|
17
149
|
"TMP",
|
|
18
|
-
"LANG"
|
|
150
|
+
"LANG",
|
|
151
|
+
"CLAUDE_CONFIG_DIR"
|
|
19
152
|
];
|
|
20
153
|
const windowsAllowedProviderParentEnvNames = [
|
|
21
154
|
"SystemRoot",
|
|
@@ -36,7 +169,8 @@ const windowsAllowedProviderParentEnvNames = [
|
|
|
36
169
|
"DOTNET_ROOT(x86)",
|
|
37
170
|
"DOTNET_CLI_HOME",
|
|
38
171
|
"NUGET_PACKAGES",
|
|
39
|
-
"NUGET_HTTP_CACHE_PATH"
|
|
172
|
+
"NUGET_HTTP_CACHE_PATH",
|
|
173
|
+
"CLAUDE_CODE_GIT_BASH_PATH"
|
|
40
174
|
];
|
|
41
175
|
export function buildProviderChildEnvironment(requestEnv, options = {}) {
|
|
42
176
|
const parentEnv = options.parentEnv ?? process.env;
|
|
@@ -58,8 +192,37 @@ export function buildProviderChildEnvironment(requestEnv, options = {}) {
|
|
|
58
192
|
if (platform === "win32") {
|
|
59
193
|
normalizeWindowsDotNetEnvironment(env, parentEnv, options.homeDir ?? os.homedir());
|
|
60
194
|
}
|
|
195
|
+
normalizeProviderBuildEnvironment(env);
|
|
61
196
|
return env;
|
|
62
197
|
}
|
|
198
|
+
function normalizeProviderBuildEnvironment(env) {
|
|
199
|
+
// Provider sessions are long lived and commonly run many dotnet builds. Persistent
|
|
200
|
+
// MSBuild and Roslyn compiler servers otherwise survive individual commands and can
|
|
201
|
+
// retain more memory than the active Forge lane. Keep those caches process-scoped by
|
|
202
|
+
// default on every runner OS; an explicitly supplied request value still wins.
|
|
203
|
+
setCanonicalProviderBuildEnvironmentValue(env, "DOTNET_CLI_USE_MSBUILD_SERVER", "0");
|
|
204
|
+
setCanonicalProviderBuildEnvironmentValue(env, "MSBUILDDISABLENODEREUSE", "1");
|
|
205
|
+
setCanonicalProviderBuildEnvironmentValue(env, "UseSharedCompilation", "false");
|
|
206
|
+
}
|
|
207
|
+
function setCanonicalProviderBuildEnvironmentValue(env, name, value) {
|
|
208
|
+
// The dotnet CLI uses case-sensitive environment lookup on POSIX, while MSBuild
|
|
209
|
+
// property names are case-insensitive on every OS. Collapse request aliases to
|
|
210
|
+
// one canonical key so the explicit value wins without producing ambiguity.
|
|
211
|
+
const exact = env[name];
|
|
212
|
+
const alias = Object.entries(env).find(([key, candidate]) => key !== name
|
|
213
|
+
&& key.toLowerCase() === name.toLowerCase()
|
|
214
|
+
&& typeof candidate === "string"
|
|
215
|
+
&& candidate.trim().length > 0)?.[1];
|
|
216
|
+
const resolved = typeof exact === "string" && exact.trim().length > 0
|
|
217
|
+
? exact
|
|
218
|
+
: alias ?? value;
|
|
219
|
+
for (const key of Object.keys(env)) {
|
|
220
|
+
if (key !== name && key.toLowerCase() === name.toLowerCase()) {
|
|
221
|
+
delete env[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
env[name] = resolved;
|
|
225
|
+
}
|
|
63
226
|
function isAllowedProviderParentEnv(key, platform = process.platform) {
|
|
64
227
|
return isAllowedProviderParentEnvName(key, allowedProviderParentEnvNames)
|
|
65
228
|
|| key.toUpperCase().startsWith("LC_")
|
|
@@ -75,6 +238,17 @@ function normalizeWindowsDotNetEnvironment(env, parentEnv, homeDir) {
|
|
|
75
238
|
setEnvironmentValueIfMissing(env, "SystemDrive", readEnvironmentValue(parentEnv, "SystemDrive") ?? systemDrive);
|
|
76
239
|
setEnvironmentValueIfMissing(env, "ComSpec", readEnvironmentValue(parentEnv, "ComSpec") ?? path.win32.join(systemRoot, "System32", "cmd.exe"));
|
|
77
240
|
setEnvironmentValueIfMissing(env, "PATHEXT", readEnvironmentValue(parentEnv, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL");
|
|
241
|
+
const resolvedSystemDrive = readEnvironmentValue(env, "SystemDrive") ?? systemDrive;
|
|
242
|
+
const defaultProgramFiles = path.win32.join(`${resolvedSystemDrive}\\`, "Program Files");
|
|
243
|
+
const defaultProgramFilesX86 = path.win32.join(`${resolvedSystemDrive}\\`, "Program Files (x86)");
|
|
244
|
+
const defaultProgramData = path.win32.join(`${resolvedSystemDrive}\\`, "ProgramData");
|
|
245
|
+
const programFiles = readEnvironmentValue(env, "ProgramFiles")
|
|
246
|
+
?? readEnvironmentValue(parentEnv, "ProgramFiles")
|
|
247
|
+
?? defaultProgramFiles;
|
|
248
|
+
setEnvironmentValueIfMissing(env, "ProgramFiles", programFiles);
|
|
249
|
+
setEnvironmentValueIfMissing(env, "ProgramW6432", readEnvironmentValue(parentEnv, "ProgramW6432") ?? defaultProgramFiles);
|
|
250
|
+
setEnvironmentValueIfMissing(env, "ProgramFiles(x86)", readEnvironmentValue(parentEnv, "ProgramFiles(x86)") ?? defaultProgramFilesX86);
|
|
251
|
+
setEnvironmentValueIfMissing(env, "ProgramData", readEnvironmentValue(parentEnv, "ProgramData") ?? defaultProgramData);
|
|
78
252
|
const userProfile = resolveWindowsUserProfile(env, parentEnv, homeDir);
|
|
79
253
|
if (userProfile) {
|
|
80
254
|
setEnvironmentValueIfMissing(env, "USERPROFILE", userProfile);
|
|
@@ -138,10 +312,17 @@ function setEnvironmentValueIfMissing(env, name, value) {
|
|
|
138
312
|
env[name] = value;
|
|
139
313
|
}
|
|
140
314
|
export class StreamingProviderProcessRunner {
|
|
315
|
+
terminalExitGraceMs;
|
|
316
|
+
constructor(terminalExitGraceMs = PROVIDER_TERMINAL_EXIT_GRACE_MS) {
|
|
317
|
+
this.terminalExitGraceMs = terminalExitGraceMs;
|
|
318
|
+
}
|
|
141
319
|
async run(request, handlers = {}, signal) {
|
|
320
|
+
assertHostDatabaseCapabilityBoundary();
|
|
142
321
|
const providerLaunch = await resolveProviderProcessLaunch(request.command, request.args);
|
|
143
322
|
const supervisedLaunch = resolveHostProcessSupervisedLaunch(request, providerLaunch.command, providerLaunch.args);
|
|
144
|
-
|
|
323
|
+
// An unsupervised Unix provider still gets a dedicated process group so a
|
|
324
|
+
// cancellation can stop descendants instead of only the CLI root.
|
|
325
|
+
const launch = supervisedLaunch ?? { ...providerLaunch, detached: process.platform !== "win32" };
|
|
145
326
|
return await new Promise((resolve, reject) => {
|
|
146
327
|
const rootCreatedAt = new Date().toISOString();
|
|
147
328
|
const child = spawn(launch.command, launch.args, {
|
|
@@ -151,6 +332,9 @@ export class StreamingProviderProcessRunner {
|
|
|
151
332
|
windowsHide: true,
|
|
152
333
|
detached: launch.detached,
|
|
153
334
|
});
|
|
335
|
+
const memoryObserver = supervisedLaunch
|
|
336
|
+
? new HostProcessTreeMemoryObserver(supervisedLaunch, child.pid ?? 0)
|
|
337
|
+
: null;
|
|
154
338
|
let termination = null;
|
|
155
339
|
const terminate = () => {
|
|
156
340
|
termination ??= terminateHostProcessTree(child, supervisedLaunch);
|
|
@@ -159,16 +343,23 @@ export class StreamingProviderProcessRunner {
|
|
|
159
343
|
const childStdout = child.stdout;
|
|
160
344
|
const childStderr = child.stderr;
|
|
161
345
|
if (!childStdout || !childStderr) {
|
|
162
|
-
|
|
163
|
-
reject(new
|
|
346
|
+
const pipeError = new Error("Provider process stdout/stderr streams were not piped.");
|
|
347
|
+
void terminate().then(() => reject(pipeError), terminationError => reject(new AggregateError([pipeError, terminationError], "Provider launch failed and its process tree could not be proven stopped.")));
|
|
164
348
|
return;
|
|
165
349
|
}
|
|
166
|
-
|
|
167
|
-
|
|
350
|
+
const stdout = new BoundedOutputTail(PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS);
|
|
351
|
+
const stderr = new BoundedOutputTail(PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS);
|
|
168
352
|
let settled = false;
|
|
169
353
|
let closed = false;
|
|
354
|
+
let cancellationRequested = false;
|
|
355
|
+
let terminalTerminationStarted = false;
|
|
356
|
+
let terminalExitTimer = null;
|
|
170
357
|
let readiness = null;
|
|
171
358
|
const cleanup = () => {
|
|
359
|
+
if (terminalExitTimer) {
|
|
360
|
+
clearTimeout(terminalExitTimer);
|
|
361
|
+
terminalExitTimer = null;
|
|
362
|
+
}
|
|
172
363
|
signal?.removeEventListener("abort", abort);
|
|
173
364
|
};
|
|
174
365
|
const fail = (error) => {
|
|
@@ -177,10 +368,12 @@ export class StreamingProviderProcessRunner {
|
|
|
177
368
|
}
|
|
178
369
|
settled = true;
|
|
179
370
|
cleanup();
|
|
180
|
-
|
|
181
|
-
|
|
371
|
+
// Do not release the caller's reservation/worktree/port ownership until
|
|
372
|
+
// descendant termination has completed (or failed with explicit proof).
|
|
373
|
+
void terminate().then(() => reject(error), terminationError => reject(new AggregateError([error, terminationError], "Provider process failed and its process tree could not be proven stopped.")));
|
|
182
374
|
};
|
|
183
375
|
const abort = () => {
|
|
376
|
+
cancellationRequested = true;
|
|
184
377
|
// Cancellation is not a launch failure when the supervised tree can be
|
|
185
378
|
// proven stopped. Let the close handler settle the command after exact
|
|
186
379
|
// termination; only a failed termination proof rejects the run.
|
|
@@ -240,15 +433,28 @@ export class StreamingProviderProcessRunner {
|
|
|
240
433
|
const readers = [
|
|
241
434
|
readProcessLines(childStdout, (line) => {
|
|
242
435
|
if (request.captureOutput !== false)
|
|
243
|
-
stdout
|
|
244
|
-
|
|
436
|
+
stdout.appendLine(line);
|
|
437
|
+
const handled = handlers.onStdoutLine?.(line);
|
|
438
|
+
if (!terminalExitTimer && isProviderTerminalSuccess(line)) {
|
|
439
|
+
terminalExitTimer = setTimeout(() => {
|
|
440
|
+
if (closed || settled || cancellationRequested)
|
|
441
|
+
return;
|
|
442
|
+
terminalTerminationStarted = true;
|
|
443
|
+
void terminate().catch(fail);
|
|
444
|
+
}, this.terminalExitGraceMs);
|
|
445
|
+
terminalExitTimer.unref?.();
|
|
446
|
+
}
|
|
447
|
+
return handled;
|
|
245
448
|
}),
|
|
246
449
|
readProcessLines(childStderr, (line) => {
|
|
247
450
|
if (request.captureOutput !== false)
|
|
248
|
-
stderr
|
|
451
|
+
stderr.appendLine(line);
|
|
249
452
|
return handlers.onStderrLine?.(line);
|
|
250
453
|
})
|
|
251
454
|
];
|
|
455
|
+
for (const reader of readers) {
|
|
456
|
+
void reader.catch(fail);
|
|
457
|
+
}
|
|
252
458
|
child.on("close", (code) => {
|
|
253
459
|
closed = true;
|
|
254
460
|
Promise.allSettled(readers).then(async (readerResults) => {
|
|
@@ -262,7 +468,8 @@ export class StreamingProviderProcessRunner {
|
|
|
262
468
|
return;
|
|
263
469
|
}
|
|
264
470
|
try {
|
|
265
|
-
|
|
471
|
+
memoryObserver?.sample();
|
|
472
|
+
if (supervisedLaunch?.strategy === "unix_process_group" || !supervisedLaunch) {
|
|
266
473
|
await terminate();
|
|
267
474
|
}
|
|
268
475
|
else if (termination) {
|
|
@@ -275,16 +482,189 @@ export class StreamingProviderProcessRunner {
|
|
|
275
482
|
}
|
|
276
483
|
settled = true;
|
|
277
484
|
cleanup();
|
|
485
|
+
const memory = memoryObserver?.complete();
|
|
278
486
|
resolve({
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
487
|
+
// A structured terminal-success event is the provider protocol's
|
|
488
|
+
// authoritative completion signal. If the CLI stays resident after
|
|
489
|
+
// that event, terminate the supervised tree after a short grace
|
|
490
|
+
// period and preserve the successful handoff so the existing
|
|
491
|
+
// artifact, commit, and publication gates can run.
|
|
492
|
+
exitCode: terminalTerminationStarted && !cancellationRequested ? 0 : code ?? 1,
|
|
493
|
+
stdout: stdout.toString(),
|
|
494
|
+
stderr: stderr.toString(),
|
|
495
|
+
...(memory ?? {}),
|
|
282
496
|
});
|
|
283
497
|
}).catch(fail);
|
|
284
498
|
});
|
|
285
499
|
});
|
|
286
500
|
}
|
|
287
501
|
}
|
|
502
|
+
function isProviderTerminalSuccess(line) {
|
|
503
|
+
const trimmed = line.trim();
|
|
504
|
+
if (!trimmed.startsWith("{"))
|
|
505
|
+
return false;
|
|
506
|
+
try {
|
|
507
|
+
const event = JSON.parse(trimmed);
|
|
508
|
+
return event.type === "turn.completed"
|
|
509
|
+
|| (event.type === "result" && event.subtype === "success" && event.is_error !== true);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
class HostProcessTreeMemoryObserver {
|
|
516
|
+
launch;
|
|
517
|
+
processGroupId;
|
|
518
|
+
peakMemoryBytes = 0;
|
|
519
|
+
sampleCount = 0;
|
|
520
|
+
observedAt = null;
|
|
521
|
+
timer;
|
|
522
|
+
constructor(launch, processGroupId) {
|
|
523
|
+
this.launch = launch;
|
|
524
|
+
this.processGroupId = processGroupId;
|
|
525
|
+
if (launch.strategy === "unix_process_group" && processGroupId > 0) {
|
|
526
|
+
this.sample();
|
|
527
|
+
this.timer = setInterval(() => this.sample(), 1_000);
|
|
528
|
+
this.timer.unref?.();
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
this.timer = null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
sample() {
|
|
535
|
+
if (this.launch.strategy !== "unix_process_group" || this.processGroupId <= 0)
|
|
536
|
+
return;
|
|
537
|
+
const bytes = processGroupResidentMemoryBytes(this.processGroupId);
|
|
538
|
+
if (bytes === null)
|
|
539
|
+
return;
|
|
540
|
+
this.sampleCount += 1;
|
|
541
|
+
this.peakMemoryBytes = Math.max(this.peakMemoryBytes, bytes);
|
|
542
|
+
this.observedAt = new Date().toISOString();
|
|
543
|
+
}
|
|
544
|
+
complete() {
|
|
545
|
+
if (this.timer)
|
|
546
|
+
clearInterval(this.timer);
|
|
547
|
+
if (this.launch.strategy === "windows_job_object") {
|
|
548
|
+
return readWindowsJobMemoryTelemetry(this.launch.memoryTelemetryPath);
|
|
549
|
+
}
|
|
550
|
+
if (this.peakMemoryBytes <= 0 || this.sampleCount <= 0 || !this.observedAt)
|
|
551
|
+
return undefined;
|
|
552
|
+
return {
|
|
553
|
+
peakMemoryBytes: this.peakMemoryBytes,
|
|
554
|
+
memorySampleCount: this.sampleCount,
|
|
555
|
+
memorySource: process.platform === "linux"
|
|
556
|
+
? "linux_process_group_rss"
|
|
557
|
+
: "unix_process_group_rss",
|
|
558
|
+
memoryObservedAt: this.observedAt,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/** Returns aggregate resident bytes for the exact supervised Unix process group. */
|
|
563
|
+
export function processGroupResidentMemoryBytes(processGroupId, platform = process.platform, procRoot = "/proc") {
|
|
564
|
+
if (!Number.isSafeInteger(processGroupId) || processGroupId <= 0)
|
|
565
|
+
return null;
|
|
566
|
+
if (platform === "linux") {
|
|
567
|
+
let totalKilobytes = 0;
|
|
568
|
+
let matched = 0;
|
|
569
|
+
try {
|
|
570
|
+
for (const entry of fs.readdirSync(procRoot, { withFileTypes: true })) {
|
|
571
|
+
if (!entry.isDirectory() || !/^\d+$/u.test(entry.name))
|
|
572
|
+
continue;
|
|
573
|
+
try {
|
|
574
|
+
const stat = fs.readFileSync(path.join(procRoot, entry.name, "stat"), "utf8");
|
|
575
|
+
const close = stat.lastIndexOf(")");
|
|
576
|
+
const fields = close >= 0 ? stat.slice(close + 2).trim().split(/\s+/u) : [];
|
|
577
|
+
if (Number.parseInt(fields[2] ?? "", 10) !== processGroupId)
|
|
578
|
+
continue;
|
|
579
|
+
const status = fs.readFileSync(path.join(procRoot, entry.name, "status"), "utf8");
|
|
580
|
+
const rss = /^VmRSS:\s+(\d+)\s+kB$/mu.exec(status);
|
|
581
|
+
if (!rss)
|
|
582
|
+
continue;
|
|
583
|
+
totalKilobytes += Number.parseInt(rss[1], 10);
|
|
584
|
+
matched += 1;
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
// Processes can exit between the directory, stat, and status reads.
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return matched > 0 ? totalKilobytes * 1024 : null;
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (platform !== "win32") {
|
|
597
|
+
try {
|
|
598
|
+
const output = execFileSync("ps", ["-axo", "pgid=,rss="], {
|
|
599
|
+
encoding: "utf8",
|
|
600
|
+
timeout: 5_000,
|
|
601
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
602
|
+
});
|
|
603
|
+
let totalKilobytes = 0;
|
|
604
|
+
let matched = 0;
|
|
605
|
+
for (const line of output.split(/\r?\n/u)) {
|
|
606
|
+
const fields = line.trim().split(/\s+/u);
|
|
607
|
+
if (Number.parseInt(fields[0] ?? "", 10) !== processGroupId)
|
|
608
|
+
continue;
|
|
609
|
+
const rss = Number.parseInt(fields[1] ?? "", 10);
|
|
610
|
+
if (!Number.isSafeInteger(rss) || rss < 0)
|
|
611
|
+
continue;
|
|
612
|
+
totalKilobytes += rss;
|
|
613
|
+
matched += 1;
|
|
614
|
+
}
|
|
615
|
+
return matched > 0 ? totalKilobytes * 1024 : null;
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
function readWindowsJobMemoryTelemetry(target) {
|
|
624
|
+
if (!target)
|
|
625
|
+
return undefined;
|
|
626
|
+
try {
|
|
627
|
+
const value = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
628
|
+
const peakMemoryBytes = value.peakMemoryBytes;
|
|
629
|
+
const sampleCount = value.sampleCount;
|
|
630
|
+
const source = value.source;
|
|
631
|
+
const observedAt = value.observedAt;
|
|
632
|
+
if (!Number.isSafeInteger(peakMemoryBytes) || peakMemoryBytes <= 0
|
|
633
|
+
|| !Number.isSafeInteger(sampleCount) || sampleCount <= 0
|
|
634
|
+
|| typeof source !== "string" || source.length === 0
|
|
635
|
+
|| typeof observedAt !== "string" || !Number.isFinite(Date.parse(observedAt))) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
peakMemoryBytes: peakMemoryBytes,
|
|
640
|
+
memorySampleCount: sampleCount,
|
|
641
|
+
memorySource: source,
|
|
642
|
+
memoryObservedAt: observedAt,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
class BoundedOutputTail {
|
|
650
|
+
maxChars;
|
|
651
|
+
value = "";
|
|
652
|
+
constructor(maxChars) {
|
|
653
|
+
this.maxChars = maxChars;
|
|
654
|
+
}
|
|
655
|
+
appendLine(line) {
|
|
656
|
+
const chunk = `${line}\n`;
|
|
657
|
+
if (chunk.length >= this.maxChars) {
|
|
658
|
+
this.value = chunk.slice(-this.maxChars);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const retainedPrefixChars = this.maxChars - chunk.length;
|
|
662
|
+
this.value = `${this.value.slice(-retainedPrefixChars)}${chunk}`;
|
|
663
|
+
}
|
|
664
|
+
toString() {
|
|
665
|
+
return this.value;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
288
668
|
async function resolveProviderProcessLaunch(command, args) {
|
|
289
669
|
if (process.platform !== "win32") {
|
|
290
670
|
return { command, args };
|
|
@@ -298,6 +678,7 @@ async function resolveProviderProcessLaunch(command, args) {
|
|
|
298
678
|
return { command, args };
|
|
299
679
|
}
|
|
300
680
|
return resolveCodexNpmDirectLaunch(resolved, args)
|
|
681
|
+
?? resolveClaudeNpmDirectLaunch(resolved, args)
|
|
301
682
|
?? resolvePackageManagerDirectLaunch(resolved, args)
|
|
302
683
|
?? buildWindowsLaunchCommand(resolved, args);
|
|
303
684
|
}
|
|
@@ -342,7 +723,7 @@ async function waitForWindowsProviderReady(launch, signal, isClosed) {
|
|
|
342
723
|
throw error;
|
|
343
724
|
}
|
|
344
725
|
if (isClosed()) {
|
|
345
|
-
throw new
|
|
726
|
+
throw new ForgeFailureError("attempt_process_supervisor_start_failed", "host_process_supervisor_start_failed: supervisor exited before the provider became ready.", "bridge");
|
|
346
727
|
}
|
|
347
728
|
if (signal?.aborted) {
|
|
348
729
|
throw signal.reason instanceof Error ? signal.reason : new Error("Provider process launch was aborted.");
|
|
@@ -361,6 +742,22 @@ export function resolveCodexNpmDirectLaunch(commandPath, args) {
|
|
|
361
742
|
? { command: process.execPath, args: [entryPoint, ...args] }
|
|
362
743
|
: null;
|
|
363
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* npm installs of Claude Code (`npm i -g @anthropic-ai/claude-code`) put a `claude.cmd` shim on
|
|
747
|
+
* PATH; launch its cli.js directly for the same Job Object reasons as Codex. The native installer
|
|
748
|
+
* ships `claude.exe`, which is preferred by PATH resolution and needs no rewrite.
|
|
749
|
+
*/
|
|
750
|
+
export function resolveClaudeNpmDirectLaunch(commandPath, args) {
|
|
751
|
+
const extension = path.extname(commandPath).toLowerCase();
|
|
752
|
+
if ((extension !== ".cmd" && extension !== ".ps1")
|
|
753
|
+
|| path.basename(commandPath, extension).toLowerCase() !== "claude") {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
const entryPoint = path.join(path.dirname(commandPath), "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
757
|
+
return fs.existsSync(entryPoint)
|
|
758
|
+
? { command: process.execPath, args: [entryPoint, ...args] }
|
|
759
|
+
: null;
|
|
760
|
+
}
|
|
364
761
|
/**
|
|
365
762
|
* Avoids cmd.exe's nested-quote handling for package-manager shims. That
|
|
366
763
|
* quoting loses executable paths containing spaces after the command is
|
|
@@ -371,11 +768,11 @@ export function resolvePackageManagerDirectLaunch(commandPath, args) {
|
|
|
371
768
|
if (extension !== ".cmd" && extension !== ".ps1")
|
|
372
769
|
return null;
|
|
373
770
|
const packageManager = path.basename(commandPath, extension).toLowerCase();
|
|
374
|
-
if (!["npm", "npx", "pnpm", "pnpx", "yarn", "yarnpkg"].includes(packageManager))
|
|
771
|
+
if (!["corepack", "npm", "npx", "pnpm", "pnpx", "yarn", "yarnpkg"].includes(packageManager))
|
|
375
772
|
return null;
|
|
376
773
|
const root = path.dirname(commandPath);
|
|
377
774
|
const candidates = [
|
|
378
|
-
path.join(root, "node_modules", "corepack", "dist", `${packageManager}.js`),
|
|
775
|
+
path.join(root, "node_modules", "corepack", "dist", packageManager === "corepack" ? "corepack.js" : `${packageManager}.js`),
|
|
379
776
|
...(packageManager === "npm" || packageManager === "npx"
|
|
380
777
|
? [path.join(root, "node_modules", "npm", "bin", `${packageManager}-cli.js`)]
|
|
381
778
|
: []),
|
|
@@ -432,18 +829,21 @@ export function resolveWindowsCommandFromPath(command, pathValue = process.env.P
|
|
|
432
829
|
".bat",
|
|
433
830
|
...pathExtValue.split(";").map(value => value.trim().toLowerCase()).filter(Boolean),
|
|
434
831
|
])];
|
|
435
|
-
const candidates = [];
|
|
436
832
|
for (const rawDirectory of pathValue.split(path.delimiter)) {
|
|
437
833
|
const directory = rawDirectory.trim().replace(/^"|"$/gu, "");
|
|
438
834
|
if (!directory)
|
|
439
835
|
continue;
|
|
836
|
+
const candidates = [];
|
|
440
837
|
for (const extension of extensions) {
|
|
441
838
|
const candidate = path.join(directory, `${command}${extension}`);
|
|
442
839
|
if (fs.existsSync(candidate))
|
|
443
840
|
candidates.push(candidate);
|
|
444
841
|
}
|
|
842
|
+
const resolved = chooseWindowsCommandCandidate(candidates);
|
|
843
|
+
if (resolved)
|
|
844
|
+
return resolved;
|
|
445
845
|
}
|
|
446
|
-
return
|
|
846
|
+
return null;
|
|
447
847
|
}
|
|
448
848
|
export function chooseWindowsCommandCandidate(candidates) {
|
|
449
849
|
const normalized = candidates
|
|
@@ -24,6 +24,11 @@ export declare function buildProviderCliCommand(dispatch: RunOnceDispatchPacket,
|
|
|
24
24
|
readonly imagePaths?: readonly string[];
|
|
25
25
|
readonly runtimeEnvironment?: Readonly<Record<string, string>>;
|
|
26
26
|
}): ProviderCliCommand;
|
|
27
|
+
/**
|
|
28
|
+
* Returns the effort level to pass to the provider CLI, or null to keep Forge's provider default.
|
|
29
|
+
* Values the provider does not accept are dropped rather than failing the attempt; Forge validates on save.
|
|
30
|
+
*/
|
|
31
|
+
export declare function providerEffort(provider: string, effort: string | null | undefined): string | null;
|
|
27
32
|
export declare function buildProviderCliPrompt(dispatch: RunOnceDispatchPacket, prompt: string, runtimeEnvironment?: Readonly<Record<string, string>>): string;
|
|
28
33
|
export declare function codexSandboxModeForPlatform(platform?: NodeJS.Platform, executionMode?: string): "workspace-write" | "danger-full-access";
|
|
29
34
|
export declare function buildArchitectPrompt(dispatch: RunOnceDispatchPacket): string;
|