@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
|
@@ -10,6 +10,7 @@ export declare class ManagedWorktreeCleanupService implements CleanupService {
|
|
|
10
10
|
run(request: CleanupRequest): Promise<CleanupResult>;
|
|
11
11
|
private runLocked;
|
|
12
12
|
private listWorktrees;
|
|
13
|
+
private staleRuntimeReadinessPaths;
|
|
13
14
|
private scanManagedCandidates;
|
|
14
15
|
private readRuntimeLeases;
|
|
15
16
|
private worktreeMetadataRoot;
|
|
@@ -4,9 +4,10 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { RepositoryCoordinatorLock } from "../sandbox/repository-lock.js";
|
|
6
6
|
import { GitClient } from "../shared/git/git.js";
|
|
7
|
-
import { isSubPath, resolveExistingDirectoryPath } from "../shared/path/path.js";
|
|
7
|
+
import { isSubPath, resolveExistingDirectoryPath, resolvePathIdentity } from "../shared/path/path.js";
|
|
8
8
|
import { validRuntimeIsolationLease } from "../runtime/runtime-isolation-lease.js";
|
|
9
9
|
import { defaultWorktreeCoordinatorStateRoot, resolveManagedWorktreeRootForRepo, } from "../worktree/worktree-paths.js";
|
|
10
|
+
const staleRuntimeReadinessAgeMs = 30 * 60 * 1000;
|
|
10
11
|
export class ManagedWorktreeCleanupService {
|
|
11
12
|
stateRoot;
|
|
12
13
|
git;
|
|
@@ -17,8 +18,13 @@ export class ManagedWorktreeCleanupService {
|
|
|
17
18
|
this.coordinator = coordinator;
|
|
18
19
|
}
|
|
19
20
|
async run(request) {
|
|
20
|
-
const repoPath = await
|
|
21
|
-
|
|
21
|
+
const [repoPath, allowedRepoRoot] = await Promise.all([
|
|
22
|
+
resolveExistingDirectoryPath(request.repoRoot),
|
|
23
|
+
request.allowedRepoRoot
|
|
24
|
+
? resolvePathIdentity(request.allowedRepoRoot)
|
|
25
|
+
: Promise.resolve(undefined),
|
|
26
|
+
]);
|
|
27
|
+
const worktreeRoot = resolveManagedWorktreeRootForRepo(repoPath, allowedRepoRoot);
|
|
22
28
|
return await this.coordinator.withLock(repoPath, async () => {
|
|
23
29
|
return await this.runLocked(repoPath, worktreeRoot, request);
|
|
24
30
|
});
|
|
@@ -39,11 +45,13 @@ export class ManagedWorktreeCleanupService {
|
|
|
39
45
|
this.listWorktrees(repoPath),
|
|
40
46
|
this.readRuntimeLeases(),
|
|
41
47
|
]);
|
|
42
|
-
const
|
|
43
|
-
const activeWorktrees = activePaths(listedWorktrees);
|
|
44
|
-
const activeRuntimePaths = runtimeLeases
|
|
48
|
+
const activeRuntimePaths = await Promise.all(runtimeLeases
|
|
45
49
|
.filter(lease => lease.state !== "released")
|
|
46
|
-
.map(lease =>
|
|
50
|
+
.map(lease => resolveRuntimeLeasePath(lease.worktreePath)));
|
|
51
|
+
const metadataRoot = await this.worktreeMetadataRoot(repoPath);
|
|
52
|
+
const staleReadinessPaths = await this.staleRuntimeReadinessPaths(listedWorktrees, activeRuntimePaths);
|
|
53
|
+
const activeWorktrees = activePaths(listedWorktrees)
|
|
54
|
+
.filter(activePath => !staleReadinessPaths.some(stalePath => samePath(stalePath, activePath)));
|
|
47
55
|
if (request.mode === "all") {
|
|
48
56
|
const protectedPaths = uniquePaths([
|
|
49
57
|
...activeWorktrees.filter(activePath => isSubPath(safeRoot, activePath)),
|
|
@@ -88,9 +96,14 @@ export class ManagedWorktreeCleanupService {
|
|
|
88
96
|
this.listWorktrees(repoPath),
|
|
89
97
|
this.readRuntimeLeases(),
|
|
90
98
|
]);
|
|
99
|
+
const currentRuntimePaths = await Promise.all(currentLeases
|
|
100
|
+
.filter(lease => lease.state !== "released")
|
|
101
|
+
.map(lease => resolveRuntimeLeasePath(lease.worktreePath)));
|
|
102
|
+
const currentStaleReadinessPaths = await this.staleRuntimeReadinessPaths(currentWorktrees, currentRuntimePaths);
|
|
91
103
|
const currentlyProtected = [
|
|
92
|
-
...activePaths(currentWorktrees)
|
|
93
|
-
|
|
104
|
+
...activePaths(currentWorktrees)
|
|
105
|
+
.filter(activePath => !currentStaleReadinessPaths.some(stalePath => samePath(stalePath, activePath))),
|
|
106
|
+
...currentRuntimePaths,
|
|
94
107
|
];
|
|
95
108
|
if (worktreePathState(candidate, currentlyProtected) !== "none") {
|
|
96
109
|
continue;
|
|
@@ -118,6 +131,24 @@ export class ManagedWorktreeCleanupService {
|
|
|
118
131
|
const output = await this.git.run(repoPath, ["worktree", "list", "--porcelain", "-z"]);
|
|
119
132
|
return parseWorktreeListing(repoPath, output);
|
|
120
133
|
}
|
|
134
|
+
async staleRuntimeReadinessPaths(worktrees, runtimePaths) {
|
|
135
|
+
const stale = [];
|
|
136
|
+
for (const worktree of worktrees) {
|
|
137
|
+
if (worktree.prunable || !isRuntimeReadinessWorktree(worktree))
|
|
138
|
+
continue;
|
|
139
|
+
if (worktreePathState(worktree.worktreePath, runtimePaths) !== "none")
|
|
140
|
+
continue;
|
|
141
|
+
const stat = await fs.stat(worktree.worktreePath).catch(error => {
|
|
142
|
+
if (isEnoent(error))
|
|
143
|
+
return null;
|
|
144
|
+
throw error;
|
|
145
|
+
});
|
|
146
|
+
if (stat && Date.now() - stat.mtimeMs >= staleRuntimeReadinessAgeMs) {
|
|
147
|
+
stale.push(worktree.worktreePath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return stale;
|
|
151
|
+
}
|
|
121
152
|
async scanManagedCandidates(metadataRoot, worktreeRoot, activeWorktrees, runtimePaths) {
|
|
122
153
|
const candidates = [];
|
|
123
154
|
let protectedWorktrees = 0;
|
|
@@ -215,6 +246,28 @@ export class ManagedWorktreeCleanupService {
|
|
|
215
246
|
return path.join(await fs.realpath(commonDirPath), "worktrees");
|
|
216
247
|
}
|
|
217
248
|
}
|
|
249
|
+
async function resolveRuntimeLeasePath(leasePath) {
|
|
250
|
+
const resolved = path.resolve(leasePath);
|
|
251
|
+
const before = await fs.lstat(resolved).catch(error => {
|
|
252
|
+
if (isEnoent(error))
|
|
253
|
+
return null;
|
|
254
|
+
throw error;
|
|
255
|
+
});
|
|
256
|
+
if (!before)
|
|
257
|
+
return await resolvePathIdentity(resolved);
|
|
258
|
+
if (before.isSymbolicLink() || !before.isDirectory()) {
|
|
259
|
+
throw new Error(`Refusing managed worktree cleanup because runtime lease path ${resolved} is redirected or is not a directory.`);
|
|
260
|
+
}
|
|
261
|
+
const real = await fs.realpath(resolved);
|
|
262
|
+
const after = await fs.lstat(resolved);
|
|
263
|
+
if (after.isSymbolicLink()
|
|
264
|
+
|| !after.isDirectory()
|
|
265
|
+
|| (before.dev !== 0 && before.ino !== 0 && after.dev !== 0 && after.ino !== 0
|
|
266
|
+
&& (before.dev !== after.dev || before.ino !== after.ino))) {
|
|
267
|
+
throw new Error(`Refusing managed worktree cleanup because runtime lease path ${resolved} changed while being resolved.`);
|
|
268
|
+
}
|
|
269
|
+
return real;
|
|
270
|
+
}
|
|
218
271
|
async function readRuntimeLedger(target, entryName) {
|
|
219
272
|
const pathStat = await fs.lstat(target).catch(error => {
|
|
220
273
|
if (isEnoent(error))
|
|
@@ -399,16 +452,24 @@ function parseWorktreeListing(repoPath, output) {
|
|
|
399
452
|
current = {
|
|
400
453
|
worktreePath: path.resolve(path.isAbsolute(rawPath) ? rawPath : path.join(repoPath, rawPath)),
|
|
401
454
|
prunable: false,
|
|
455
|
+
branchName: null,
|
|
402
456
|
};
|
|
403
457
|
}
|
|
404
458
|
else if (field.startsWith("prunable") && current) {
|
|
405
459
|
current.prunable = true;
|
|
406
460
|
}
|
|
461
|
+
else if (field.startsWith("branch ") && current) {
|
|
462
|
+
current.branchName = field.slice("branch ".length).trim();
|
|
463
|
+
}
|
|
407
464
|
}
|
|
408
465
|
if (current)
|
|
409
466
|
worktrees.push(current);
|
|
410
467
|
return worktrees;
|
|
411
468
|
}
|
|
469
|
+
function isRuntimeReadinessWorktree(worktree) {
|
|
470
|
+
return worktree.branchName?.startsWith("refs/heads/runtime-readiness-") === true
|
|
471
|
+
|| path.basename(worktree.worktreePath).startsWith("runtime-readiness-");
|
|
472
|
+
}
|
|
412
473
|
function activePaths(worktrees) {
|
|
413
474
|
return worktrees.filter(entry => !entry.prunable).map(entry => entry.worktreePath);
|
|
414
475
|
}
|
package/dist/cli/cli.help.js
CHANGED
|
@@ -60,7 +60,7 @@ export function commandHelpText(command) {
|
|
|
60
60
|
" --repo-root <path> Folder where bridge-managed repos/worktrees live.",
|
|
61
61
|
" --name <name> Display name for this runner.",
|
|
62
62
|
" --fingerprint <value> Stable device fingerprint override.",
|
|
63
|
-
" --cli-provider <list> Comma-separated providers, e.g. codex,cursor.",
|
|
63
|
+
" --cli-provider <list> Comma-separated providers, e.g. codex,cursor,claude.",
|
|
64
64
|
" --cleanup-worktrees Enable managed worktree cleanup.",
|
|
65
65
|
" --no-cleanup-worktrees Disable managed worktree cleanup.",
|
|
66
66
|
"",
|
|
@@ -81,7 +81,7 @@ export function commandHelpText(command) {
|
|
|
81
81
|
" --runner-id <id> Runner id; saved config is used when omitted.",
|
|
82
82
|
" --device-token <token> Device token; saved config is used when omitted.",
|
|
83
83
|
" --repo-root <path> Override repo/worktree folder for this run.",
|
|
84
|
-
" --cli-provider <list> Comma-separated providers, e.g. codex,cursor.",
|
|
84
|
+
" --cli-provider <list> Comma-separated providers, e.g. codex,cursor,claude.",
|
|
85
85
|
" --heartbeat-interval-ms <n> Heartbeat interval in milliseconds.",
|
|
86
86
|
" --cleanup-worktrees Enable managed worktree cleanup for this run.",
|
|
87
87
|
" --no-cleanup-worktrees Disable managed worktree cleanup for this run.",
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
import { type DotNetToolchainSelection } from "../shared/process/dotnet-toolchain.js";
|
|
1
2
|
import type { BridgeService } from "../bridge/bridge.base.js";
|
|
2
3
|
import type { BridgeServiceConfig } from "../bridge/bridge.types.js";
|
|
3
4
|
import type { CleanupMode } from "../cleanup/cleanup.types.js";
|
|
4
5
|
import type { CleanupService } from "../cleanup/cleanup.base.js";
|
|
5
6
|
import type { SessionService } from "../session/session.base.js";
|
|
6
7
|
import type { CliIo, CliService } from "./cli.base.js";
|
|
8
|
+
type DotNetToolchainConfigurator = (repoRoots: readonly string[], options?: {
|
|
9
|
+
readonly cloneFolders?: readonly string[];
|
|
10
|
+
}) => Promise<DotNetToolchainSelection | null>;
|
|
7
11
|
export declare class ForgeBridgeCliService implements CliService {
|
|
8
12
|
private readonly sessions;
|
|
9
13
|
private readonly bridgeFactory;
|
|
10
14
|
private readonly pairRunner;
|
|
11
15
|
private readonly cleanupFactory;
|
|
12
|
-
|
|
16
|
+
private readonly dotNetToolchainConfigurator;
|
|
17
|
+
constructor(sessions?: SessionService, bridgeFactory?: (config: BridgeServiceConfig) => BridgeService, pairRunner?: PairRunnerClient, cleanupFactory?: CleanupServiceFactory, dotNetToolchainConfigurator?: DotNetToolchainConfigurator);
|
|
13
18
|
run(argv: readonly string[], io?: CliIo): Promise<number>;
|
|
14
19
|
}
|
|
15
20
|
export declare function parseArgs(argv: readonly string[]): {
|
|
@@ -21,11 +26,7 @@ export declare function parseArgs(argv: readonly string[]): {
|
|
|
21
26
|
readonly ok: true;
|
|
22
27
|
readonly command: "mcp";
|
|
23
28
|
readonly argv: readonly string[];
|
|
24
|
-
} | {
|
|
25
|
-
readonly ok: true;
|
|
26
|
-
readonly command: "connect";
|
|
27
|
-
readonly config: BridgeServiceConfig;
|
|
28
|
-
} | ({
|
|
29
|
+
} | ConnectArgs | ({
|
|
29
30
|
readonly ok: true;
|
|
30
31
|
readonly command: "pair";
|
|
31
32
|
} & PairArgs) | {
|
|
@@ -89,6 +90,12 @@ export declare function parseArgs(argv: readonly string[]): {
|
|
|
89
90
|
readonly ok: false;
|
|
90
91
|
readonly error: string;
|
|
91
92
|
};
|
|
93
|
+
interface ConnectArgs {
|
|
94
|
+
readonly ok: true;
|
|
95
|
+
readonly command: "connect";
|
|
96
|
+
readonly config: BridgeServiceConfig;
|
|
97
|
+
readonly implicitCloneDestination: boolean;
|
|
98
|
+
}
|
|
92
99
|
interface PairArgs {
|
|
93
100
|
readonly serverUrl: string;
|
|
94
101
|
readonly code?: string;
|
|
@@ -104,6 +111,7 @@ interface PairRunnerClaim {
|
|
|
104
111
|
readonly deviceToken: string;
|
|
105
112
|
}
|
|
106
113
|
type PairRunnerClient = (args: PairArgs) => Promise<PairRunnerClaim>;
|
|
114
|
+
export declare const claudeDefaultModels: readonly string[];
|
|
107
115
|
export declare function parseCodexModelCatalog(raw: string): string[];
|
|
108
116
|
export declare function parseCursorModelList(raw: string): string[];
|
|
109
117
|
type CleanupServiceFactory = (stateRoot: string) => CleanupService;
|
package/dist/cli/cli.service.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { resolveCursorAgentDirectLaunch } from "../shared/cursor-agent/cursor-agent.launch.js";
|
|
8
8
|
import { buildWindowsCmdLaunch } from "../shared/process/windows-launch.js";
|
|
9
|
+
import { configureDotNetToolchain } from "../shared/process/dotnet-toolchain.js";
|
|
9
10
|
import { supportsDraftImageAttachmentsFor } from "../shared/provider-capabilities.js";
|
|
10
11
|
import { redactText } from "../shared/redaction/redaction.js";
|
|
11
12
|
import { defaultBridgeServiceConfig, ForgeBridgeService } from "../bridge/bridge.service.js";
|
|
@@ -19,7 +20,7 @@ import { DockerPreflight } from "../sandbox/preflight.js";
|
|
|
19
20
|
import { DockerSandboxRuntime } from "../sandbox/runtime.js";
|
|
20
21
|
import { DockerSandboxSmoke } from "../sandbox/smoke.js";
|
|
21
22
|
import { parseCompletionMcpArgs, runCompletionMcpServer } from "../session/phase-completion.js";
|
|
22
|
-
import { failureResult, parseRunOnceDispatchPacket } from "../session/session.types.js";
|
|
23
|
+
import { failureFactsFromError, failureResult, parseRunOnceDispatchPacket } from "../session/session.types.js";
|
|
23
24
|
import { commandHelpText, helpText } from "./cli.help.js";
|
|
24
25
|
const apiKeySecretGrantEnv = "FORGE_API_KEY_SECRET_GRANT";
|
|
25
26
|
const execFileAsync = promisify(execFile);
|
|
@@ -28,11 +29,13 @@ export class ForgeBridgeCliService {
|
|
|
28
29
|
bridgeFactory;
|
|
29
30
|
pairRunner;
|
|
30
31
|
cleanupFactory;
|
|
31
|
-
|
|
32
|
+
dotNetToolchainConfigurator;
|
|
33
|
+
constructor(sessions = new BridgeSessionService(), bridgeFactory = (config) => new ForgeBridgeService(config), pairRunner = claimBridgeRunner, cleanupFactory = stateRoot => new ManagedWorktreeCleanupService(stateRoot), dotNetToolchainConfigurator = configureDotNetToolchain) {
|
|
32
34
|
this.sessions = sessions;
|
|
33
35
|
this.bridgeFactory = bridgeFactory;
|
|
34
36
|
this.pairRunner = pairRunner;
|
|
35
37
|
this.cleanupFactory = cleanupFactory;
|
|
38
|
+
this.dotNetToolchainConfigurator = dotNetToolchainConfigurator;
|
|
36
39
|
}
|
|
37
40
|
async run(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
38
41
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
@@ -67,11 +70,26 @@ export class ForgeBridgeCliService {
|
|
|
67
70
|
io.stderr.write(`forge-bridge connect requires ${missing[0]}.\n`);
|
|
68
71
|
return 2;
|
|
69
72
|
}
|
|
73
|
+
let dotNetToolchain;
|
|
74
|
+
try {
|
|
75
|
+
dotNetToolchain = await this.dotNetToolchainConfigurator(await dotNetPreflightRoots(config, args.implicitCloneDestination), { cloneFolders: [config.defaultRepoRoot.trim()].filter(Boolean) });
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
if (dotNetToolchain) {
|
|
82
|
+
io.stdout.write(`Using .NET SDK ${dotNetToolchain.sdkVersion}: ${dotNetToolchain.executablePath}\n`);
|
|
83
|
+
}
|
|
70
84
|
const controller = new AbortController();
|
|
71
85
|
const stop = () => controller.abort("signal received");
|
|
72
86
|
process.once("SIGINT", stop);
|
|
73
87
|
process.once("SIGTERM", stop);
|
|
74
88
|
try {
|
|
89
|
+
if (config.cleanupWorktrees && config.repoMappings.some(mapping => mapping.localPath?.trim())) {
|
|
90
|
+
io.stdout.write("Cleaning stale managed worktrees before accepting dispatches.\n");
|
|
91
|
+
await cleanupRepos(config, "stale", true, this.cleanupFactory(config.sandboxStateRoot), io);
|
|
92
|
+
}
|
|
75
93
|
io.stdout.write(connectStartupText(config));
|
|
76
94
|
await this.bridgeFactory(config).run(controller.signal);
|
|
77
95
|
return 0;
|
|
@@ -216,7 +234,7 @@ export class ForgeBridgeCliService {
|
|
|
216
234
|
}
|
|
217
235
|
catch (error) {
|
|
218
236
|
const summary = redactText(error instanceof Error ? error.message : String(error), redactionSecrets);
|
|
219
|
-
await writeJsonAtomic(args.resultPath, failureResult(summary)).catch(() => undefined);
|
|
237
|
+
await writeJsonAtomic(args.resultPath, redactRunOnceResultPacket(failureResult(summary, [], failureFactsFromError(error, { source: "bridge" })), redactionSecrets)).catch(() => undefined);
|
|
220
238
|
io.stderr.write(`${summary}\n`);
|
|
221
239
|
return 1;
|
|
222
240
|
}
|
|
@@ -582,6 +600,28 @@ async function reapSandboxes(io) {
|
|
|
582
600
|
await preflight.release();
|
|
583
601
|
}
|
|
584
602
|
}
|
|
603
|
+
async function dotNetPreflightRoots(config, implicitCloneDestination) {
|
|
604
|
+
const roots = config.repoMappings.map((mapping) => mapping.localPath?.trim())
|
|
605
|
+
.filter((repoRoot) => Boolean(repoRoot));
|
|
606
|
+
const defaultRoot = config.defaultRepoRoot.trim();
|
|
607
|
+
if (!defaultRoot)
|
|
608
|
+
return roots;
|
|
609
|
+
if (implicitCloneDestination) {
|
|
610
|
+
try {
|
|
611
|
+
// The built-in clone destination need not exist on first connect. lstat
|
|
612
|
+
// deliberately retains dangling links for the strict SDK scanner to reject.
|
|
613
|
+
// Explicit mappings (including this same path) remain required inputs.
|
|
614
|
+
await fs.lstat(defaultRoot);
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
if (error.code === "ENOENT")
|
|
618
|
+
return roots;
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
roots.push(defaultRoot);
|
|
623
|
+
return roots;
|
|
624
|
+
}
|
|
585
625
|
function parseConnectArgs(argv) {
|
|
586
626
|
let serverUrl = null;
|
|
587
627
|
let runnerId = null;
|
|
@@ -651,7 +691,10 @@ function parseConnectArgs(argv) {
|
|
|
651
691
|
return {
|
|
652
692
|
ok: true,
|
|
653
693
|
command: "connect",
|
|
654
|
-
config: configuredBridgeServiceConfig(overrides, localConfig)
|
|
694
|
+
config: configuredBridgeServiceConfig(overrides, localConfig),
|
|
695
|
+
implicitCloneDestination: repoRoot === null
|
|
696
|
+
&& process.env.FORGE_REPO_ROOT === undefined
|
|
697
|
+
&& localConfig.defaultRepoRoot === undefined,
|
|
655
698
|
};
|
|
656
699
|
}
|
|
657
700
|
function parsePairArgs(argv) {
|
|
@@ -714,7 +757,7 @@ function parsePairArgs(argv) {
|
|
|
714
757
|
name: name?.trim() || defaultName,
|
|
715
758
|
fingerprint: fingerprint?.trim() || `local-bridge:${os.hostname()}:${user}`,
|
|
716
759
|
repoRoot: repoRoot?.trim() ? path.resolve(repoRoot) : undefined,
|
|
717
|
-
cleanupWorktrees: cleanupWorktrees ??
|
|
760
|
+
cleanupWorktrees: cleanupWorktrees ?? defaultConfig.cleanupWorktrees,
|
|
718
761
|
cliProviders: cliProviders.length > 0 ? cliProviders : localConfig.cliProviders && localConfig.cliProviders.length > 0 ? [...localConfig.cliProviders] : ["codex"],
|
|
719
762
|
projectLabels: projectLabels.length > 0 ? projectLabels : localConfig.projectLabels ? [...localConfig.projectLabels] : []
|
|
720
763
|
};
|
|
@@ -869,6 +912,7 @@ function connectStartupText(config) {
|
|
|
869
912
|
` repo folder: ${config.defaultRepoRoot || "not configured"}`,
|
|
870
913
|
` providers: ${formatList(config.cliProviders)}`,
|
|
871
914
|
` cleanup worktrees: ${config.cleanupWorktrees ? "enabled" : "disabled"}`,
|
|
915
|
+
` Docker Desktop auto-recovery: ${config.dockerDesktopRecoveryEnabled ? "enabled" : "disabled"}`,
|
|
872
916
|
` open public sandbox networking: ${config.allowOpenPublicSandbox ? "enabled" : "disabled"}`,
|
|
873
917
|
"",
|
|
874
918
|
"Press Ctrl+C to stop.",
|
|
@@ -900,7 +944,7 @@ async function writeJsonAtomic(outputPath, value) {
|
|
|
900
944
|
await fs.rename(tempPath, outputPath);
|
|
901
945
|
}
|
|
902
946
|
async function writeStatus(config, checkUpdates, io) {
|
|
903
|
-
const detected = await detectCliProviders([...new Set([...config.cliProviders, "codex", "cursor"])]);
|
|
947
|
+
const detected = await detectCliProviders([...new Set([...config.cliProviders, "codex", "cursor", "claude"])]);
|
|
904
948
|
const repos = config.repoMappings.filter((mapping) => mapping.localPath?.trim());
|
|
905
949
|
io.stdout.write(`Forge bridge ${config.version}\n`);
|
|
906
950
|
io.stdout.write(`Config: ${resolveLocalConfigPath()}\n`);
|
|
@@ -973,7 +1017,7 @@ async function detectCliProviders(providers) {
|
|
|
973
1017
|
let detected = null;
|
|
974
1018
|
for (const probe of cliProbeCommandsFor(provider)) {
|
|
975
1019
|
try {
|
|
976
|
-
detected = await execVersionCommand(probe);
|
|
1020
|
+
detected = await execVersionCommand(probe, probe.timeoutMs);
|
|
977
1021
|
break;
|
|
978
1022
|
}
|
|
979
1023
|
catch {
|
|
@@ -997,6 +1041,13 @@ function cliProbeCommandsFor(provider) {
|
|
|
997
1041
|
{ command: "codex", args: ["--version"] }
|
|
998
1042
|
];
|
|
999
1043
|
}
|
|
1044
|
+
if (normalized === "claude") {
|
|
1045
|
+
return [
|
|
1046
|
+
// Claude Code's Node startup can exceed the default 2s probe on a cold Windows host.
|
|
1047
|
+
{ command: "claude", args: ["--version"], timeoutMs: 8000 },
|
|
1048
|
+
{ command: "claude", args: ["-v"], timeoutMs: 8000 }
|
|
1049
|
+
];
|
|
1050
|
+
}
|
|
1000
1051
|
if (normalized === "cursor") {
|
|
1001
1052
|
return [
|
|
1002
1053
|
{ command: "cursor-agent", args: ["-v"] },
|
|
@@ -1010,22 +1061,22 @@ function cliProbeCommandsFor(provider) {
|
|
|
1010
1061
|
{ command: provider, args: ["-V"] }
|
|
1011
1062
|
];
|
|
1012
1063
|
}
|
|
1013
|
-
async function execVersionCommand(probe, timeout = 2000) {
|
|
1064
|
+
async function execVersionCommand(probe, timeout = 2000, maxBuffer) {
|
|
1014
1065
|
const directLaunch = process.platform === "win32"
|
|
1015
1066
|
? await resolveCursorAgentDirectLaunch(probe.command, probe.args)
|
|
1016
1067
|
: null;
|
|
1017
1068
|
if (directLaunch) {
|
|
1018
|
-
return await execFileAsync(directLaunch.command, [...directLaunch.args], { timeout });
|
|
1069
|
+
return await execFileAsync(directLaunch.command, [...directLaunch.args], { timeout, maxBuffer });
|
|
1019
1070
|
}
|
|
1020
1071
|
try {
|
|
1021
|
-
return await execFileAsync(probe.command, probe.args, { timeout });
|
|
1072
|
+
return await execFileAsync(probe.command, probe.args, { timeout, maxBuffer });
|
|
1022
1073
|
}
|
|
1023
1074
|
catch (error) {
|
|
1024
1075
|
if (process.platform !== "win32") {
|
|
1025
1076
|
throw error;
|
|
1026
1077
|
}
|
|
1027
1078
|
const launch = buildWindowsCmdLaunch(probe.command, probe.args);
|
|
1028
|
-
return await execFileAsync(launch.command, [...launch.args], { timeout });
|
|
1079
|
+
return await execFileAsync(launch.command, [...launch.args], { timeout, maxBuffer });
|
|
1029
1080
|
}
|
|
1030
1081
|
}
|
|
1031
1082
|
function versionLine(output) {
|
|
@@ -1040,19 +1091,26 @@ async function detectCliModels(provider) {
|
|
|
1040
1091
|
const normalized = provider.toLowerCase();
|
|
1041
1092
|
try {
|
|
1042
1093
|
if (normalized === "codex") {
|
|
1043
|
-
|
|
1094
|
+
// Windows installs codex as a .cmd shim, which execFile cannot launch directly.
|
|
1095
|
+
const result = await execVersionCommand({ command: "codex", args: ["debug", "models"] }, 10_000, 10 * 1024 * 1024);
|
|
1044
1096
|
return parseCodexModelCatalog(result.stdout);
|
|
1045
1097
|
}
|
|
1046
1098
|
if (normalized === "cursor") {
|
|
1047
1099
|
const result = await execVersionCommand({ command: "cursor-agent", args: ["models"] }, 10_000);
|
|
1048
1100
|
return parseCursorModelList(`${result.stdout || result.stderr}`);
|
|
1049
1101
|
}
|
|
1102
|
+
if (normalized === "claude") {
|
|
1103
|
+
// Claude Code has no model-listing command; it accepts these aliases and full model IDs.
|
|
1104
|
+
const configured = splitCsv(process.env.FORGE_CLAUDE_MODELS ?? "");
|
|
1105
|
+
return configured.length > 0 ? configured : [...claudeDefaultModels];
|
|
1106
|
+
}
|
|
1050
1107
|
}
|
|
1051
1108
|
catch {
|
|
1052
1109
|
return [];
|
|
1053
1110
|
}
|
|
1054
1111
|
return [];
|
|
1055
1112
|
}
|
|
1113
|
+
export const claudeDefaultModels = ["sonnet", "opus", "haiku"];
|
|
1056
1114
|
export function parseCodexModelCatalog(raw) {
|
|
1057
1115
|
try {
|
|
1058
1116
|
const parsed = JSON.parse(raw);
|
|
@@ -1105,8 +1163,8 @@ function uniqueModels(models) {
|
|
|
1105
1163
|
function isRecord(value) {
|
|
1106
1164
|
return typeof value === "object" && value !== null;
|
|
1107
1165
|
}
|
|
1108
|
-
async function withDetectedCliProviders(config) {
|
|
1109
|
-
const detected = await detectCliProviders(
|
|
1166
|
+
async function withDetectedCliProviders(config, candidates = config.cliProviders) {
|
|
1167
|
+
const detected = await detectCliProviders(uniqueProviders(candidates));
|
|
1110
1168
|
const found = detected.filter((result) => result.found);
|
|
1111
1169
|
if (found.length === 0) {
|
|
1112
1170
|
return config;
|
|
@@ -1130,6 +1188,7 @@ async function withDetectedCliProviders(config) {
|
|
|
1130
1188
|
};
|
|
1131
1189
|
}
|
|
1132
1190
|
async function withDetectedPairCliProviders(args) {
|
|
1191
|
+
const configured = configuredProviderCandidates(args.cliProviders);
|
|
1133
1192
|
const detectedConfig = await withDetectedCliProviders({
|
|
1134
1193
|
...defaultBridgeServiceConfig({
|
|
1135
1194
|
serverUrl: args.serverUrl,
|
|
@@ -1139,14 +1198,14 @@ async function withDetectedPairCliProviders(args) {
|
|
|
1139
1198
|
cleanupWorktrees: args.cleanupWorktrees,
|
|
1140
1199
|
defaultRepoRoot: args.repoRoot ?? defaultBridgeServiceConfig().defaultRepoRoot
|
|
1141
1200
|
})
|
|
1142
|
-
});
|
|
1201
|
+
}, configured);
|
|
1143
1202
|
return {
|
|
1144
1203
|
...args,
|
|
1145
1204
|
cliProviders: detectedConfig.cliProviders
|
|
1146
1205
|
};
|
|
1147
1206
|
}
|
|
1148
1207
|
function configuredProviderCandidates(providers) {
|
|
1149
|
-
return uniqueProviders([...providers, "codex", "cursor"]);
|
|
1208
|
+
return uniqueProviders([...providers, "codex", "cursor", "claude"]);
|
|
1150
1209
|
}
|
|
1151
1210
|
function uniqueProviders(providers) {
|
|
1152
1211
|
return providers
|
|
@@ -8,6 +8,7 @@ export interface BridgeLocalConfig {
|
|
|
8
8
|
readonly defaultRepoRoot?: string;
|
|
9
9
|
readonly cleanupWorktrees?: boolean;
|
|
10
10
|
readonly dockerEnabled?: boolean;
|
|
11
|
+
readonly dockerDesktopRecoveryEnabled?: boolean;
|
|
11
12
|
readonly allowOpenPublicSandbox?: boolean;
|
|
12
13
|
readonly cliProviders?: readonly string[];
|
|
13
14
|
readonly providerStatuses?: Readonly<Record<string, BridgeProviderStatus>>;
|
package/dist/cli/local-config.js
CHANGED
|
@@ -48,6 +48,9 @@ export function applyLocalConfigDefaults(config, local) {
|
|
|
48
48
|
dockerEnabled: process.env.FORGE_DOCKER_SANDBOX === undefined
|
|
49
49
|
? local.dockerEnabled ?? config.dockerEnabled
|
|
50
50
|
: config.dockerEnabled,
|
|
51
|
+
dockerDesktopRecoveryEnabled: process.env.FORGE_DOCKER_DESKTOP_AUTO_RECOVERY === undefined
|
|
52
|
+
? local.dockerDesktopRecoveryEnabled ?? config.dockerDesktopRecoveryEnabled
|
|
53
|
+
: config.dockerDesktopRecoveryEnabled,
|
|
51
54
|
allowOpenPublicSandbox: process.env.FORGE_ALLOW_OPEN_PUBLIC_SANDBOX === undefined
|
|
52
55
|
? local.allowOpenPublicSandbox ?? config.allowOpenPublicSandbox
|
|
53
56
|
: config.allowOpenPublicSandbox,
|
|
@@ -126,6 +129,7 @@ function normalizeLocalConfig(config) {
|
|
|
126
129
|
defaultRepoRoot: cleanString(config.defaultRepoRoot),
|
|
127
130
|
cleanupWorktrees: config.cleanupWorktrees,
|
|
128
131
|
dockerEnabled: config.dockerEnabled,
|
|
132
|
+
dockerDesktopRecoveryEnabled: config.dockerDesktopRecoveryEnabled,
|
|
129
133
|
allowOpenPublicSandbox: config.allowOpenPublicSandbox,
|
|
130
134
|
cliProviders: cleanList(config.cliProviders),
|
|
131
135
|
providerStatuses: normalizeProviderStatuses(config.providerStatuses),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface AspNetRuntimePrebuildPlan {
|
|
2
|
+
readonly fingerprint: string;
|
|
3
|
+
readonly receiptPath: string;
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly args: readonly string[];
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
8
|
+
readonly projectPath: string;
|
|
9
|
+
readonly worktreePath: string;
|
|
10
|
+
readonly inputCommitSha: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AspNetRuntimePrebuildResult {
|
|
13
|
+
readonly cacheHit: boolean;
|
|
14
|
+
readonly receiptPath: string;
|
|
15
|
+
readonly fingerprint: string;
|
|
16
|
+
}
|
|
17
|
+
export interface AspNetRuntimeBuildExecutionResult {
|
|
18
|
+
readonly exitCode: number;
|
|
19
|
+
readonly stdoutTail: string;
|
|
20
|
+
readonly stderrTail: string;
|
|
21
|
+
readonly stdoutLogPath: string;
|
|
22
|
+
readonly stderrLogPath: string;
|
|
23
|
+
}
|
|
24
|
+
export type AspNetRuntimeBuildExecutor = (plan: AspNetRuntimePrebuildPlan, signal?: AbortSignal) => Promise<AspNetRuntimeBuildExecutionResult>;
|
|
25
|
+
export declare function createAspNetRuntimePrebuildPlan(options: {
|
|
26
|
+
readonly command: string;
|
|
27
|
+
readonly args: readonly string[];
|
|
28
|
+
readonly cwd: string;
|
|
29
|
+
readonly worktreePath: string;
|
|
30
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
31
|
+
readonly inputCommitSha: string;
|
|
32
|
+
readonly runtimeContractFingerprint: string;
|
|
33
|
+
}): AspNetRuntimePrebuildPlan | null;
|
|
34
|
+
export declare function ensureAspNetRuntimePrebuilt(plan: AspNetRuntimePrebuildPlan, execute: AspNetRuntimeBuildExecutor, signal?: AbortSignal): Promise<AspNetRuntimePrebuildResult>;
|
|
35
|
+
export declare function withAspNetPrebuiltRunArguments(command: string, args: readonly string[]): readonly string[];
|
|
36
|
+
export declare function resolveWorktreeCommitSha(worktreePath: string, supplied?: string | null): Promise<string>;
|