@loucompanion/forge-bridge 0.1.3-dev.d46a8e73d03d → 0.1.3-dev.e7b62d8fdd33
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 +83 -0
- package/dist/bridge/bridge.base.d.ts +4 -1
- package/dist/bridge/bridge.service.d.ts +55 -3
- package/dist/bridge/bridge.service.js +834 -87
- package/dist/bridge/bridge.types.d.ts +89 -1
- 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 +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.js +37 -3
- package/dist/cli/cli.service.d.ts +33 -2
- package/dist/cli/cli.service.js +209 -27
- package/dist/cli/local-config.d.ts +5 -0
- package/dist/cli/local-config.js +45 -1
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
- package/dist/runtime/attempt-process-supervisor.js +386 -0
- package/dist/runtime/docker-engine-probe.d.ts +83 -0
- package/dist/runtime/docker-engine-probe.js +300 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +103 -0
- package/dist/runtime/host-listener-inspector.d.ts +46 -0
- package/dist/runtime/host-listener-inspector.js +349 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +47 -0
- package/dist/runtime/host-runtime-contract.js +700 -0
- package/dist/runtime/host-runtime-lease.d.ts +197 -0
- package/dist/runtime/host-runtime-lease.js +2203 -0
- package/dist/runtime/host-runtime-promotion.d.ts +23 -0
- package/dist/runtime/host-runtime-promotion.js +159 -0
- package/dist/runtime/memory-capacity.d.ts +68 -0
- package/dist/runtime/memory-capacity.js +121 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +284 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +231 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +158 -0
- 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 +45 -1
- package/dist/sandbox/preflight.js +299 -34
- package/dist/sandbox/process.js +125 -40
- package/dist/sandbox/repository-lock.js +41 -3
- package/dist/sandbox/runtime.d.ts +66 -3
- package/dist/sandbox/runtime.js +670 -105
- package/dist/sandbox/sandbox.types.d.ts +10 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.js +1 -1
- package/dist/sandbox/worker.js +2 -5
- package/dist/sandbox/workspace.service.d.ts +1 -0
- package/dist/sandbox/workspace.service.js +28 -5
- package/dist/session/managed-attempt-tools.d.ts +21 -0
- package/dist/session/managed-attempt-tools.js +912 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +105 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +335 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +233 -24
- package/dist/session/phase-completion.d.ts +12 -2
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +51 -10
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.d.ts +3 -3
- package/dist/session/phase-completion.prompts.js +24 -8
- package/dist/session/phase-completion.runtime.d.ts +13 -1
- package/dist/session/phase-completion.runtime.js +189 -8
- package/dist/session/phase-completion.schemas.js +117 -4
- package/dist/session/phase-completion.validation.d.ts +4 -1
- package/dist/session/phase-completion.validation.js +153 -22
- package/dist/session/pre-publication-validation.d.ts +46 -0
- package/dist/session/pre-publication-validation.js +208 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
- package/dist/session/provider-cli/provider-cli.deterministic.js +49 -1
- package/dist/session/provider-cli/provider-cli.process.d.ts +25 -0
- package/dist/session/provider-cli/provider-cli.process.js +463 -34
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
- package/dist/session/provider-cli/provider-cli.prompts.js +108 -25
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
- 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 +1 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +22 -2
- package/dist/session/provider-cli/provider-cli.service.js +524 -100
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.js +91 -16
- 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.d.ts +3 -0
- package/dist/session/session-redaction.js +38 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +522 -41
- package/dist/session/session.types.d.ts +72 -0
- package/dist/session/session.types.js +74 -3
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
- package/dist/shared/process/dotnet-toolchain.js +179 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +78 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +330 -0
- package/dist/shared/process/process.d.ts +1 -0
- package/dist/shared/process/process.js +1 -0
- package/dist/shared/redaction/redaction.js +12 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +43 -4
- package/dist/worktree/dependency-warmer.d.ts +28 -1
- package/dist/worktree/dependency-warmer.js +308 -9
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +13 -2
- package/dist/worktree/worktree.service.js +238 -47
- package/dist/worktree/worktree.types.d.ts +5 -1
- package/package.json +5 -3
- package/runtime/windows-job-supervisor.ps1 +444 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
export const runtimeIsolationLeaseStates = [
|
|
2
|
+
"reserved",
|
|
3
|
+
"starting",
|
|
4
|
+
"active",
|
|
5
|
+
"releasing",
|
|
6
|
+
"released",
|
|
7
|
+
"degraded",
|
|
8
|
+
];
|
|
9
|
+
const allowedTransitions = {
|
|
10
|
+
reserved: new Set(["starting", "releasing", "degraded"]),
|
|
11
|
+
starting: new Set(["active", "releasing", "degraded"]),
|
|
12
|
+
active: new Set(["releasing", "degraded"]),
|
|
13
|
+
releasing: new Set(["released", "degraded"]),
|
|
14
|
+
released: new Set(),
|
|
15
|
+
degraded: new Set(["releasing"]),
|
|
16
|
+
};
|
|
17
|
+
export function createRuntimeIsolationLease(input) {
|
|
18
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
19
|
+
const lease = {
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
leaseId: input.leaseId,
|
|
22
|
+
dispatchAttemptId: input.dispatchAttemptId,
|
|
23
|
+
executionLeaseId: input.executionLeaseId,
|
|
24
|
+
fencingToken: input.fencingToken,
|
|
25
|
+
runnerId: input.runnerId,
|
|
26
|
+
runnerInstanceId: input.runnerInstanceId,
|
|
27
|
+
worktreeKey: input.worktreeKey,
|
|
28
|
+
worktreePath: input.worktreePath,
|
|
29
|
+
mode: input.mode,
|
|
30
|
+
state: "reserved",
|
|
31
|
+
createdAt,
|
|
32
|
+
stateChangedAt: createdAt,
|
|
33
|
+
expiresAt: input.expiresAt,
|
|
34
|
+
releasedAt: null,
|
|
35
|
+
resources: input.resources,
|
|
36
|
+
};
|
|
37
|
+
if (!validRuntimeIsolationLease(lease)) {
|
|
38
|
+
throw new Error("runtime_lease_invalid: lease identity, lifetime, or resources are invalid.");
|
|
39
|
+
}
|
|
40
|
+
return lease;
|
|
41
|
+
}
|
|
42
|
+
export function transitionRuntimeIsolationLease(lease, nextState, occurredAt = new Date().toISOString()) {
|
|
43
|
+
if (lease.state === nextState)
|
|
44
|
+
return lease;
|
|
45
|
+
if (!allowedTransitions[lease.state].has(nextState) || !validTimestamp(occurredAt)) {
|
|
46
|
+
throw new Error(`runtime_lease_transition_invalid: cannot transition ${lease.state} to ${nextState}.`);
|
|
47
|
+
}
|
|
48
|
+
const transitioned = {
|
|
49
|
+
...lease,
|
|
50
|
+
state: nextState,
|
|
51
|
+
stateChangedAt: occurredAt,
|
|
52
|
+
releasedAt: nextState === "released" ? occurredAt : null,
|
|
53
|
+
};
|
|
54
|
+
if (!validRuntimeIsolationLease(transitioned)) {
|
|
55
|
+
throw new Error("runtime_lease_invalid: transitioned lease is invalid.");
|
|
56
|
+
}
|
|
57
|
+
return transitioned;
|
|
58
|
+
}
|
|
59
|
+
export function renewRuntimeIsolationLease(lease, fencingToken, expiresAt) {
|
|
60
|
+
if (lease.fencingToken !== fencingToken || lease.state === "released" || !validTimestamp(expiresAt))
|
|
61
|
+
return null;
|
|
62
|
+
const renewed = { ...lease, expiresAt };
|
|
63
|
+
return validRuntimeIsolationLease(renewed) ? renewed : null;
|
|
64
|
+
}
|
|
65
|
+
export function withRuntimeIsolationResources(lease, resources) {
|
|
66
|
+
if (lease.state === "released") {
|
|
67
|
+
throw new Error("runtime_lease_transition_invalid: released leases cannot acquire resources.");
|
|
68
|
+
}
|
|
69
|
+
const updated = { ...lease, resources };
|
|
70
|
+
if (!validRuntimeIsolationLease(updated)) {
|
|
71
|
+
throw new Error("runtime_lease_invalid: resources are invalid.");
|
|
72
|
+
}
|
|
73
|
+
return updated;
|
|
74
|
+
}
|
|
75
|
+
export function validRuntimeIsolationLease(value) {
|
|
76
|
+
if (!isRecord(value)
|
|
77
|
+
|| value.schemaVersion !== 1
|
|
78
|
+
|| !isUuid(value.leaseId)
|
|
79
|
+
|| !isUuid(value.dispatchAttemptId)
|
|
80
|
+
|| !isUuid(value.executionLeaseId)
|
|
81
|
+
|| !isUuid(value.fencingToken)
|
|
82
|
+
|| !isUuid(value.runnerId)
|
|
83
|
+
|| !isUuid(value.runnerInstanceId)
|
|
84
|
+
|| !boundedText(value.worktreeKey, 255)
|
|
85
|
+
|| !boundedText(value.worktreePath, 4096)
|
|
86
|
+
|| (value.mode !== "docker" && value.mode !== "host")
|
|
87
|
+
|| !runtimeIsolationLeaseStates.includes(value.state)
|
|
88
|
+
|| !validTimestamp(value.createdAt)
|
|
89
|
+
|| !validTimestamp(value.stateChangedAt)
|
|
90
|
+
|| !validTimestamp(value.expiresAt)
|
|
91
|
+
|| Date.parse(value.stateChangedAt) < Date.parse(value.createdAt)
|
|
92
|
+
|| Date.parse(value.expiresAt) < Date.parse(value.createdAt)
|
|
93
|
+
|| (value.state === "released") !== validTimestamp(value.releasedAt)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (value.releasedAt !== null && Date.parse(value.releasedAt) < Date.parse(value.createdAt))
|
|
97
|
+
return false;
|
|
98
|
+
return validResources(value.resources, value.mode);
|
|
99
|
+
}
|
|
100
|
+
function validResources(value, mode) {
|
|
101
|
+
if (!isRecord(value)
|
|
102
|
+
|| !nonNegativeInteger(value.memoryReservationBytes)
|
|
103
|
+
|| !nonNegativeInteger(value.cpuMillis)
|
|
104
|
+
|| !nonNegativeInteger(value.pids)
|
|
105
|
+
|| !Array.isArray(value.ports) || value.ports.length > 64
|
|
106
|
+
|| !Array.isArray(value.paths) || value.paths.length > 64
|
|
107
|
+
|| !Array.isArray(value.dockerResources) || value.dockerResources.length > 128
|
|
108
|
+
|| !Array.isArray(value.singletonKeys) || value.singletonKeys.length > 64
|
|
109
|
+
|| (value.processSupervisorId !== null && !boundedText(value.processSupervisorId, 255))) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (value.ports.some(port => !validPort(port))
|
|
113
|
+
|| value.paths.some(entry => !isRecord(entry) || !boundedText(entry.path, 4096) || !boundedText(entry.role, 128))
|
|
114
|
+
|| value.singletonKeys.some(key => !boundedText(key, 255))
|
|
115
|
+
|| value.dockerResources.some(resource => !validDockerResource(resource))) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return mode === "docker" || value.dockerResources.length === 0;
|
|
119
|
+
}
|
|
120
|
+
function validPort(value) {
|
|
121
|
+
return isRecord(value)
|
|
122
|
+
&& boundedText(value.name, 128)
|
|
123
|
+
&& (value.protocol === "tcp" || value.protocol === "udp")
|
|
124
|
+
&& (value.hostIp === "127.0.0.1" || value.hostIp === "::1")
|
|
125
|
+
&& validPortNumber(value.hostPort)
|
|
126
|
+
&& validPortNumber(value.targetPort);
|
|
127
|
+
}
|
|
128
|
+
function validDockerResource(value) {
|
|
129
|
+
return isRecord(value)
|
|
130
|
+
&& (value.type === "container" || value.type === "network" || value.type === "volume")
|
|
131
|
+
&& boundedText(value.name, 255)
|
|
132
|
+
&& boundedText(value.role, 128)
|
|
133
|
+
&& (value.id === undefined || value.id === null || boundedText(value.id, 255));
|
|
134
|
+
}
|
|
135
|
+
function validPortNumber(value) {
|
|
136
|
+
return Number.isSafeInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
|
|
137
|
+
}
|
|
138
|
+
function nonNegativeInteger(value) {
|
|
139
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
140
|
+
}
|
|
141
|
+
function validTimestamp(value) {
|
|
142
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
143
|
+
}
|
|
144
|
+
function boundedText(value, maximumLength) {
|
|
145
|
+
return typeof value === "string"
|
|
146
|
+
&& value.length > 0
|
|
147
|
+
&& value.length <= maximumLength
|
|
148
|
+
&& Array.from(value).every((character) => {
|
|
149
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
150
|
+
return codePoint > 31 && codePoint !== 127;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function isUuid(value) {
|
|
154
|
+
return typeof value === "string" && /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(value);
|
|
155
|
+
}
|
|
156
|
+
function isRecord(value) {
|
|
157
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
158
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const minimumWorkspaceEstimateBytes: number;
|
|
2
|
+
export declare const architectMinimumWorkspaceEstimateBytes: number;
|
|
3
|
+
export interface RepositoryWorkspaceFootprint {
|
|
4
|
+
readonly sourceBytes: number;
|
|
5
|
+
readonly hasNode: boolean;
|
|
6
|
+
readonly hasDotNet: boolean;
|
|
7
|
+
readonly hasBrowser: boolean;
|
|
8
|
+
readonly serviceCount: number;
|
|
9
|
+
}
|
|
10
|
+
export interface WorkspaceEstimateHints {
|
|
11
|
+
readonly role?: string | null;
|
|
12
|
+
readonly serviceCount?: number;
|
|
13
|
+
readonly browser?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function estimateWorkspaceBytesFromRepo(repoPath: string, hints?: WorkspaceEstimateHints): Promise<number>;
|
|
16
|
+
export declare function calculateWorkspaceBytes(footprint: RepositoryWorkspaceFootprint, role?: string | null): number;
|
|
17
|
+
export declare function measureRepositoryWorkspace(repoPath: string): Promise<RepositoryWorkspaceFootprint>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
const GiB = 1024 ** 3;
|
|
6
|
+
const alignmentBytes = 512 * 1024 ** 2;
|
|
7
|
+
export const minimumWorkspaceEstimateBytes = 2 * GiB;
|
|
8
|
+
export const architectMinimumWorkspaceEstimateBytes = GiB;
|
|
9
|
+
const maximumBytes = 100 * GiB;
|
|
10
|
+
const footprintCacheLifetimeMs = 5 * 60_000;
|
|
11
|
+
const excludedDirectories = new Set([
|
|
12
|
+
".git",
|
|
13
|
+
".artifacts",
|
|
14
|
+
".forge-worktrees",
|
|
15
|
+
".local",
|
|
16
|
+
".next",
|
|
17
|
+
".nuget",
|
|
18
|
+
".pnpm-store",
|
|
19
|
+
".tmp",
|
|
20
|
+
".turbo",
|
|
21
|
+
"artifacts",
|
|
22
|
+
"bin",
|
|
23
|
+
"build",
|
|
24
|
+
"coverage",
|
|
25
|
+
"dist",
|
|
26
|
+
"node_modules",
|
|
27
|
+
"obj",
|
|
28
|
+
"testresults",
|
|
29
|
+
]);
|
|
30
|
+
const footprintCache = new Map();
|
|
31
|
+
export async function estimateWorkspaceBytesFromRepo(repoPath, hints = {}) {
|
|
32
|
+
const cacheKey = path.resolve(repoPath);
|
|
33
|
+
const cached = footprintCache.get(cacheKey);
|
|
34
|
+
const footprint = cached && Date.now() - cached.measuredAt < footprintCacheLifetimeMs
|
|
35
|
+
? cached.footprint
|
|
36
|
+
: await measureRepositoryWorkspace(cacheKey);
|
|
37
|
+
if (!cached || cached.footprint !== footprint) {
|
|
38
|
+
footprintCache.set(cacheKey, { measuredAt: Date.now(), footprint });
|
|
39
|
+
}
|
|
40
|
+
return calculateWorkspaceBytes({
|
|
41
|
+
...footprint,
|
|
42
|
+
hasBrowser: footprint.hasBrowser || hints.browser === true,
|
|
43
|
+
serviceCount: Math.max(footprint.serviceCount, Math.max(0, hints.serviceCount ?? 0)),
|
|
44
|
+
}, hints.role);
|
|
45
|
+
}
|
|
46
|
+
export function calculateWorkspaceBytes(footprint, role) {
|
|
47
|
+
const normalizedRole = role?.trim().toLowerCase() ?? "";
|
|
48
|
+
if (normalizedRole === "architect") {
|
|
49
|
+
// Architect owns a checkout and planning artifacts, not dependencies,
|
|
50
|
+
// build outputs, browser state, services, or application runtime output.
|
|
51
|
+
const estimate = architectMinimumWorkspaceEstimateBytes
|
|
52
|
+
+ Math.max(0, footprint.sourceBytes) * 2;
|
|
53
|
+
return Math.min(maximumBytes, Math.max(architectMinimumWorkspaceEstimateBytes, alignUp(estimate, alignmentBytes)));
|
|
54
|
+
}
|
|
55
|
+
let estimate = 2 * GiB + Math.max(0, footprint.sourceBytes) * 3;
|
|
56
|
+
if (footprint.hasNode)
|
|
57
|
+
estimate += 3 * GiB;
|
|
58
|
+
if (footprint.hasDotNet)
|
|
59
|
+
estimate += 2 * GiB;
|
|
60
|
+
if (footprint.hasBrowser)
|
|
61
|
+
estimate += 1536 * 1024 ** 2;
|
|
62
|
+
estimate += Math.max(0, footprint.serviceCount) * GiB;
|
|
63
|
+
if (["qa", "ci_repair", "pr_fix"].includes(normalizedRole)) {
|
|
64
|
+
estimate += GiB;
|
|
65
|
+
}
|
|
66
|
+
return Math.min(maximumBytes, Math.max(minimumWorkspaceEstimateBytes, alignUp(estimate, alignmentBytes)));
|
|
67
|
+
}
|
|
68
|
+
export async function measureRepositoryWorkspace(repoPath) {
|
|
69
|
+
return await measureGitWorkspace(repoPath) ?? await measureFilesystemFallback(repoPath);
|
|
70
|
+
}
|
|
71
|
+
async function measureGitWorkspace(repoPath) {
|
|
72
|
+
let sourceBytes = 0;
|
|
73
|
+
let hasNode = false;
|
|
74
|
+
let hasDotNet = false;
|
|
75
|
+
let hasBrowser = false;
|
|
76
|
+
let serviceCount = 0;
|
|
77
|
+
const child = spawn("git", ["-c", "core.quotepath=false", "ls-files", "--cached", "--others", "--exclude-standard"], {
|
|
78
|
+
cwd: repoPath,
|
|
79
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
80
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
});
|
|
83
|
+
const closed = new Promise((resolve) => {
|
|
84
|
+
child.once("close", resolve);
|
|
85
|
+
child.once("error", () => resolve(null));
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
const lines = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
89
|
+
for await (const relativePath of lines) {
|
|
90
|
+
if (!relativePath.trim())
|
|
91
|
+
continue;
|
|
92
|
+
const file = await inspectWorkspaceFile(path.join(repoPath, relativePath));
|
|
93
|
+
if (!file)
|
|
94
|
+
continue;
|
|
95
|
+
sourceBytes += file.bytes;
|
|
96
|
+
hasNode ||= file.hasNode;
|
|
97
|
+
hasDotNet ||= file.hasDotNet;
|
|
98
|
+
hasBrowser ||= file.hasBrowser;
|
|
99
|
+
serviceCount = Math.max(serviceCount, file.serviceCount);
|
|
100
|
+
}
|
|
101
|
+
return await closed === 0 ? { sourceBytes, hasNode, hasDotNet, hasBrowser, serviceCount } : null;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
child.kill();
|
|
105
|
+
await closed;
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function measureFilesystemFallback(repoPath) {
|
|
110
|
+
let sourceBytes = 0;
|
|
111
|
+
let hasNode = false;
|
|
112
|
+
let hasDotNet = false;
|
|
113
|
+
let hasBrowser = false;
|
|
114
|
+
let serviceCount = 0;
|
|
115
|
+
const pending = [path.resolve(repoPath)];
|
|
116
|
+
while (pending.length > 0) {
|
|
117
|
+
const current = pending.pop();
|
|
118
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const entryPath = path.join(current, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
if (!excludedDirectories.has(entry.name.toLowerCase()))
|
|
123
|
+
pending.push(entryPath);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!entry.isFile())
|
|
127
|
+
continue;
|
|
128
|
+
const file = await inspectWorkspaceFile(entryPath);
|
|
129
|
+
if (!file)
|
|
130
|
+
continue;
|
|
131
|
+
sourceBytes += file.bytes;
|
|
132
|
+
hasNode ||= file.hasNode;
|
|
133
|
+
hasDotNet ||= file.hasDotNet;
|
|
134
|
+
hasBrowser ||= file.hasBrowser;
|
|
135
|
+
serviceCount = Math.max(serviceCount, file.serviceCount);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { sourceBytes, hasNode, hasDotNet, hasBrowser, serviceCount };
|
|
139
|
+
}
|
|
140
|
+
async function inspectWorkspaceFile(filePath) {
|
|
141
|
+
const stat = await fs.stat(filePath).catch(() => null);
|
|
142
|
+
if (!stat?.isFile())
|
|
143
|
+
return null;
|
|
144
|
+
const name = path.basename(filePath).toLowerCase();
|
|
145
|
+
const sandbox = name === "forge.sandbox.yml" || name === "forge.sandbox.yaml";
|
|
146
|
+
return {
|
|
147
|
+
bytes: stat.size,
|
|
148
|
+
hasNode: ["package.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock"].includes(name),
|
|
149
|
+
hasDotNet: name.endsWith(".sln") || name.endsWith(".csproj") || name.endsWith(".fsproj"),
|
|
150
|
+
hasBrowser: name.startsWith("playwright.config"),
|
|
151
|
+
serviceCount: sandbox
|
|
152
|
+
? countSandboxServices(await fs.readFile(filePath, "utf8").catch(() => ""))
|
|
153
|
+
: 0,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function countSandboxServices(yaml) {
|
|
157
|
+
let inServices = false;
|
|
158
|
+
let count = 0;
|
|
159
|
+
for (const rawLine of yaml.split(/\r?\n/gu)) {
|
|
160
|
+
const line = rawLine.trim();
|
|
161
|
+
if (!line || line.startsWith("#"))
|
|
162
|
+
continue;
|
|
163
|
+
if (!/^\s/u.test(rawLine)) {
|
|
164
|
+
inServices = line.toLowerCase() === "services:";
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (inServices && line.startsWith("-"))
|
|
168
|
+
count += 1;
|
|
169
|
+
}
|
|
170
|
+
return count;
|
|
171
|
+
}
|
|
172
|
+
function alignUp(value, alignment) {
|
|
173
|
+
return Math.ceil(value / alignment) * alignment;
|
|
174
|
+
}
|
|
@@ -8,6 +8,10 @@ export interface DockerPreflightOptions {
|
|
|
8
8
|
readonly diskHighWatermarkPercent?: number;
|
|
9
9
|
readonly diskLowWatermarkPercent?: number;
|
|
10
10
|
readonly minimumWorkspaceBytes?: number;
|
|
11
|
+
readonly hostMemoryReserveBytes?: number;
|
|
12
|
+
readonly defaultWorkerMemoryLimitBytes?: number;
|
|
13
|
+
readonly defaultServiceMemoryLimitBytes?: number;
|
|
14
|
+
readonly runtimeOverheadBytes?: number;
|
|
11
15
|
readonly requireRootless?: boolean;
|
|
12
16
|
}
|
|
13
17
|
export declare class DockerPreflight {
|
|
@@ -15,26 +19,66 @@ export declare class DockerPreflight {
|
|
|
15
19
|
private readonly cacheMs;
|
|
16
20
|
private generation;
|
|
17
21
|
private cached;
|
|
18
|
-
private
|
|
22
|
+
private fullPreflightAt;
|
|
23
|
+
private staticPreflight;
|
|
24
|
+
private inspectionInFlight;
|
|
25
|
+
private lastCapacitySample;
|
|
19
26
|
private readonly options;
|
|
20
27
|
private readonly docker;
|
|
21
28
|
private readonly ownership;
|
|
22
29
|
private diskPressureLatched;
|
|
30
|
+
private readonly memoryAvailability;
|
|
23
31
|
constructor(options?: DockerPreflightOptions | string, commands?: CommandRunner, cacheMs?: number);
|
|
32
|
+
private unavailable;
|
|
33
|
+
private memoryReserveBytes;
|
|
34
|
+
private defaultAdmissionMemoryBytes;
|
|
24
35
|
current(): SandboxPreflightResult | null;
|
|
25
36
|
refresh(): Promise<SandboxPreflightResult>;
|
|
26
37
|
release(): Promise<void>;
|
|
27
38
|
run(force?: boolean): Promise<SandboxPreflightResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Refreshes only the admission inputs that change while the daemon's static
|
|
41
|
+
* capability snapshot remains valid. Heartbeats use this path so periodic
|
|
42
|
+
* liveness reporting does not wake Docker/WSL to rerun feature probes.
|
|
43
|
+
*/
|
|
44
|
+
refreshCapacity(): Promise<SandboxPreflightResult>;
|
|
45
|
+
/**
|
|
46
|
+
* When the cached static preflight remains usable, produces a capacity sample
|
|
47
|
+
* that began after this admission request. An older inspection is awaited,
|
|
48
|
+
* not preempted or reused. Later admission callers may share the next sample
|
|
49
|
+
* only when it began after they entered this method. Without a usable static
|
|
50
|
+
* snapshot, the existing fail-closed result is returned without probing.
|
|
51
|
+
*/
|
|
52
|
+
refreshCapacityForAdmission(): Promise<SandboxPreflightResult>;
|
|
53
|
+
private startInspection;
|
|
28
54
|
private inspect;
|
|
55
|
+
private inspectCapacity;
|
|
29
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Uses the same reviewed reserve vector as host-runtime admission. The input
|
|
59
|
+
* is always the physical host total; Docker Desktop's VM total is tracked as
|
|
60
|
+
* a separate daemon commitment domain.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveSandboxHostMemoryReserveBytes(totalMemoryBytes: number, explicitBytes?: number, environment?: NodeJS.ProcessEnv): number;
|
|
30
63
|
export declare function canRunDockerMaintenance(preflight: {
|
|
31
64
|
readonly healthy: boolean;
|
|
32
65
|
readonly unavailableCode?: string | null;
|
|
33
66
|
}): boolean;
|
|
34
67
|
export declare function evaluateDiskPressure(availableBytes: number, totalBytes: number, minimumAvailableBytes: number, highWatermarkPercent: number, lowWatermarkPercent: number, pressureLatched: boolean): boolean;
|
|
68
|
+
export declare function requiredAvailableBytesToClearDiskPressure(totalBytes: number, minimumAvailableBytes: number, lowWatermarkPercent: number): number;
|
|
35
69
|
export declare function storagePolicyAvailable(storageQuota: boolean, environment?: NodeJS.ProcessEnv): boolean;
|
|
36
70
|
export declare class DockerContextOwnershipLock {
|
|
37
71
|
private lockPath;
|
|
72
|
+
private lockToken;
|
|
38
73
|
acquire(contextName?: string): Promise<void>;
|
|
39
74
|
release(): Promise<void>;
|
|
40
75
|
}
|
|
76
|
+
export declare function parseDockerMemoryUsage(value: string): number;
|
|
77
|
+
interface DockerContextLockOwner {
|
|
78
|
+
readonly token?: string;
|
|
79
|
+
readonly pid?: number;
|
|
80
|
+
readonly processIdentity?: string | null;
|
|
81
|
+
readonly acquiredAt?: string;
|
|
82
|
+
}
|
|
83
|
+
export declare function dockerContextOwnerIsActive(owner: DockerContextLockOwner | null): boolean;
|
|
84
|
+
export {};
|