@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.1ff37038366e
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 +165 -1
- 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 +9 -1
- package/dist/bridge/bridge.service.d.ts +129 -3
- package/dist/bridge/bridge.service.js +2427 -70
- package/dist/bridge/bridge.types.d.ts +218 -2
- 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 +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.d.ts +2 -0
- package/dist/cli/cli.help.js +217 -0
- package/dist/cli/cli.service.d.ts +55 -7
- package/dist/cli/cli.service.js +429 -219
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- 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 +18 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
- 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-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 +397 -0
- 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 +20 -0
- package/dist/runtime/generated-output-reset.js +157 -0
- 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 +191 -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 +51 -0
- package/dist/runtime/host-listener-inspector.js +385 -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 +58 -0
- package/dist/runtime/host-runtime-contract.js +813 -0
- package/dist/runtime/host-runtime-lease.d.ts +217 -0
- package/dist/runtime/host-runtime-lease.js +2487 -0
- package/dist/runtime/host-runtime-promotion.d.ts +32 -0
- package/dist/runtime/host-runtime-promotion.js +206 -0
- 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 +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +285 -0
- 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 +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
- 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.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +161 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +6 -0
- package/dist/sandbox/repository-lock.js +128 -0
- package/dist/sandbox/runtime.d.ts +302 -0
- package/dist/sandbox/runtime.js +2367 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +74 -0
- package/dist/session/managed-attempt-tools.d.ts +28 -0
- package/dist/session/managed-attempt-tools.js +1015 -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 +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 +27 -0
- package/dist/session/phase-completion.artifacts.js +829 -0
- package/dist/session/phase-completion.d.ts +53 -42
- package/dist/session/phase-completion.helpers.d.ts +117 -0
- package/dist/session/phase-completion.helpers.js +707 -0
- package/dist/session/phase-completion.js +125 -3007
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +233 -0
- package/dist/session/phase-completion.runtime.d.ts +49 -0
- package/dist/session/phase-completion.runtime.js +840 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +859 -0
- package/dist/session/phase-completion.validation.d.ts +38 -0
- package/dist/session/phase-completion.validation.js +918 -0
- 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 +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
- package/dist/session/provider-cli/provider-cli.process.js +874 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -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 +16 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +81 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
- package/dist/session/provider-cli/provider-cli.service.js +1257 -2497
- package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
- package/dist/session/provider-cli/provider-output-state.js +817 -0
- 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 +448 -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 +586 -59
- package/dist/session/session.types.d.ts +324 -3
- package/dist/session/session.types.js +939 -10
- 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 +21 -1
- 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.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 +3 -0
- package/dist/shared/process/process.js +13 -7
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +41 -0
- package/dist/shared/provider-capabilities.d.ts +4 -0
- package/dist/shared/provider-capabilities.js +88 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +47 -4
- 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 +82 -10
- package/dist/worktree/dependency-warmer.d.ts +53 -1
- package/dist/worktree/dependency-warmer.js +619 -11
- 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/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -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 +16 -3
- package/dist/worktree/worktree.service.js +334 -54
- package/dist/worktree/worktree.types.d.ts +9 -1
- package/package.json +12 -7
- package/runtime/windows-job-supervisor.ps1 +722 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { GitClient } from "../shared/git/git.js";
|
|
3
|
+
import { loadHostRuntimeContract } from "./host-runtime-contract.js";
|
|
4
|
+
import { configuredLaunchPortNames, hostRuntimeContractFingerprint, hostRuntimeParallelEligible } from "./host-runtime-promotion.js";
|
|
5
|
+
import { preparedRuntimeIdentity } from "./prepared-runtime-identity.js";
|
|
6
|
+
/** Foreground barrier: prepare a wave, verify its exact runtimes, then release agents. */
|
|
7
|
+
export class PreparedRuntimeReadiness {
|
|
8
|
+
runtime;
|
|
9
|
+
store;
|
|
10
|
+
releasePreparation;
|
|
11
|
+
admissionExpired;
|
|
12
|
+
preparations = new Map();
|
|
13
|
+
draining = new Set();
|
|
14
|
+
constructor(runtime, store, releasePreparation, admissionExpired) {
|
|
15
|
+
this.runtime = runtime;
|
|
16
|
+
this.store = store;
|
|
17
|
+
this.releasePreparation = releasePreparation;
|
|
18
|
+
this.admissionExpired = admissionExpired;
|
|
19
|
+
}
|
|
20
|
+
async acquire(dispatch, signal) {
|
|
21
|
+
const id = dispatch.sandbox.dispatchAttemptId;
|
|
22
|
+
if (this.preparations.has(id))
|
|
23
|
+
throw new Error("host_runtime_preparation_conflict: duplicate preparation.");
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const abort = () => this.cancel(id, dispatch.lease.fencingToken);
|
|
26
|
+
const entry = { dispatch, controller, phase: "preparing", expiresAt: dispatch.lease.expiresAt,
|
|
27
|
+
removeAbort: () => signal?.removeEventListener("abort", abort) };
|
|
28
|
+
this.preparations.set(id, entry);
|
|
29
|
+
this.scheduleDeadline(entry);
|
|
30
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
31
|
+
if (signal?.aborted)
|
|
32
|
+
abort();
|
|
33
|
+
try {
|
|
34
|
+
// Preparation consumes normal capacity, but no project-runtime claim.
|
|
35
|
+
return await this.runtime.acquire({ ...dispatch,
|
|
36
|
+
runtimeIntent: { ...dispatch.runtimeIntent, executionClass: "analysis_only", applicationRuntimeRequired: false },
|
|
37
|
+
}, controller.signal);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const failure = controller.signal.aborted ? controller.signal.reason : error;
|
|
41
|
+
this.cancel(id, dispatch.lease.fencingToken);
|
|
42
|
+
throw failure;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
has(id) { return this.preparations.has(id); }
|
|
46
|
+
hasPending() { return this.preparations.size > 0; }
|
|
47
|
+
pendingSnapshots() {
|
|
48
|
+
return [...this.preparations.values()].map(entry => ({
|
|
49
|
+
repoId: entry.dispatch.repo.id,
|
|
50
|
+
dispatchAttemptId: entry.dispatch.sandbox.dispatchAttemptId,
|
|
51
|
+
fencingToken: entry.dispatch.lease.fencingToken,
|
|
52
|
+
...(entry.probe
|
|
53
|
+
? {
|
|
54
|
+
probeDispatchAttemptId: entry.probe.sandbox.dispatchAttemptId,
|
|
55
|
+
probeFencingToken: entry.probe.lease.fencingToken,
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
async verify(dispatch, worktree, checkpoint) {
|
|
61
|
+
const id = dispatch.sandbox.dispatchAttemptId;
|
|
62
|
+
const entry = this.preparations.get(id);
|
|
63
|
+
if (!entry || entry.dispatch.lease.fencingToken !== dispatch.lease.fencingToken) {
|
|
64
|
+
throw new Error("host_runtime_preparation_missing: preparation ownership changed.");
|
|
65
|
+
}
|
|
66
|
+
// Release preparation headroom before waiting for peers; otherwise a one-slot
|
|
67
|
+
// host would deadlock while the rest of the wave tries to prepare.
|
|
68
|
+
await this.releasePreparation(id, dispatch.lease.fencingToken);
|
|
69
|
+
entry.controller.signal.throwIfAborted();
|
|
70
|
+
const completion = new Promise((resolve, reject) => {
|
|
71
|
+
entry.resolve = resolve;
|
|
72
|
+
entry.reject = reject;
|
|
73
|
+
});
|
|
74
|
+
entry.check = () => this.check(entry, worktree, checkpoint);
|
|
75
|
+
entry.phase = "queued";
|
|
76
|
+
void this.drain(dispatch.repo.id);
|
|
77
|
+
return await completion;
|
|
78
|
+
}
|
|
79
|
+
async renew(id, fence, expiresAt) {
|
|
80
|
+
const entry = this.preparations.get(id);
|
|
81
|
+
if (!entry || entry.dispatch.lease.fencingToken !== fence || Date.parse(expiresAt) <= Date.parse(entry.expiresAt))
|
|
82
|
+
return;
|
|
83
|
+
entry.expiresAt = expiresAt;
|
|
84
|
+
this.scheduleDeadline(entry);
|
|
85
|
+
if (entry.probe)
|
|
86
|
+
await this.runtime.updateLease(entry.probe.sandbox.dispatchAttemptId, entry.probe.lease.fencingToken, expiresAt);
|
|
87
|
+
}
|
|
88
|
+
cancel(id, fence, reason = new Error("host_runtime_preparation_cancelled: prepared readiness was cancelled.")) {
|
|
89
|
+
const entry = this.preparations.get(id);
|
|
90
|
+
if (!entry || entry.dispatch.lease.fencingToken !== fence)
|
|
91
|
+
return;
|
|
92
|
+
if (entry.deadline)
|
|
93
|
+
clearTimeout(entry.deadline);
|
|
94
|
+
entry.controller.abort(reason);
|
|
95
|
+
// Do not return to session teardown while a supervised probe still owns the worktree.
|
|
96
|
+
if (entry.phase === "checking")
|
|
97
|
+
return;
|
|
98
|
+
entry.reject?.(entry.controller.signal.reason);
|
|
99
|
+
entry.removeAbort();
|
|
100
|
+
this.preparations.delete(id);
|
|
101
|
+
void this.drain(entry.dispatch.repo.id);
|
|
102
|
+
}
|
|
103
|
+
async drain(repoId) {
|
|
104
|
+
if (this.draining.has(repoId))
|
|
105
|
+
return;
|
|
106
|
+
const entries = () => [...this.preparations.values()].filter(entry => entry.dispatch.repo.id === repoId);
|
|
107
|
+
if (entries().some(entry => entry.phase === "preparing"))
|
|
108
|
+
return;
|
|
109
|
+
this.draining.add(repoId);
|
|
110
|
+
const completed = [];
|
|
111
|
+
try {
|
|
112
|
+
for (const entry of entries()) {
|
|
113
|
+
if (entry.phase !== "queued")
|
|
114
|
+
continue;
|
|
115
|
+
entry.phase = "checking";
|
|
116
|
+
try {
|
|
117
|
+
completed.push({ entry, identity: await entry.check() });
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
completed.push({ entry, error });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// No provider from this wave can start while a sibling proof is running.
|
|
124
|
+
for (const result of completed) {
|
|
125
|
+
const { entry } = result;
|
|
126
|
+
this.preparations.delete(entry.dispatch.sandbox.dispatchAttemptId);
|
|
127
|
+
if (entry.deadline)
|
|
128
|
+
clearTimeout(entry.deadline);
|
|
129
|
+
entry.removeAbort();
|
|
130
|
+
if (entry.controller.signal.aborted)
|
|
131
|
+
entry.reject?.(entry.controller.signal.reason);
|
|
132
|
+
else if (result.error)
|
|
133
|
+
entry.reject?.(result.error);
|
|
134
|
+
else
|
|
135
|
+
entry.resolve?.(result.identity);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
this.draining.delete(repoId);
|
|
140
|
+
if (entries().some(entry => entry.phase === "queued"))
|
|
141
|
+
void this.drain(repoId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
scheduleDeadline(entry) {
|
|
145
|
+
if (entry.deadline)
|
|
146
|
+
clearTimeout(entry.deadline);
|
|
147
|
+
const delay = Date.parse(entry.expiresAt) - Date.now();
|
|
148
|
+
entry.deadline = setTimeout(() => this.cancel(entry.dispatch.sandbox.dispatchAttemptId, entry.dispatch.lease.fencingToken, this.admissionExpired()), Number.isFinite(delay) ? Math.max(0, Math.min(delay, 2_147_483_647)) : 0);
|
|
149
|
+
entry.deadline.unref();
|
|
150
|
+
}
|
|
151
|
+
async check(entry, worktree, checkpoint) {
|
|
152
|
+
const { dispatch, controller } = entry;
|
|
153
|
+
if (!dispatch.runtimeIntent.applicationRuntimeRequired)
|
|
154
|
+
return undefined;
|
|
155
|
+
const contract = await loadHostRuntimeContract(worktree);
|
|
156
|
+
if (!hostRuntimeParallelEligible(contract))
|
|
157
|
+
return undefined;
|
|
158
|
+
const preparedDiagnostics = { entries: new Map() };
|
|
159
|
+
const identity = await preparedRuntimeIdentity(worktree, controller.signal, preparedDiagnostics);
|
|
160
|
+
if (hostRuntimeContractFingerprint(contract) !== identity.runtimeContractFingerprint) {
|
|
161
|
+
throw new Error("host_runtime_prepared_source_changed: runtime contract changed while capturing its source identity.");
|
|
162
|
+
}
|
|
163
|
+
if (await this.store.load(dispatch.repo.id, identity))
|
|
164
|
+
return identity;
|
|
165
|
+
await checkpoint?.({ code: "host_runtime_prepared_probe_started", expiresAt: entry.expiresAt,
|
|
166
|
+
message: `Verifying prepared runtime ${identity.inputCommitSha.slice(0, 12)} before agent admission; project-default readiness is not a substitute.` });
|
|
167
|
+
const probeId = randomUUID();
|
|
168
|
+
const probe = { ...dispatch,
|
|
169
|
+
cycleRun: { ...dispatch.cycleRun, phase: "prepared_readiness" },
|
|
170
|
+
lease: { ...dispatch.lease, id: probeId, fencingToken: randomUUID(), expiresAt: entry.expiresAt },
|
|
171
|
+
sandbox: { ...dispatch.sandbox, dispatchAttemptId: probeId, executionLeaseId: probeId,
|
|
172
|
+
inputCommitSha: identity.inputCommitSha },
|
|
173
|
+
};
|
|
174
|
+
entry.probe = probe;
|
|
175
|
+
const stopPreparedHeadMonitor = new AbortController();
|
|
176
|
+
const preparedHeadMonitor = monitorPreparedHead(worktree, identity.inputCommitSha, controller, stopPreparedHeadMonitor.signal);
|
|
177
|
+
let cleanupConfirmed;
|
|
178
|
+
let verifiedRuntimeContractFingerprint;
|
|
179
|
+
let verifiedPorts;
|
|
180
|
+
try {
|
|
181
|
+
controller.signal.throwIfAborted();
|
|
182
|
+
await this.runtime.acquire(probe, controller.signal);
|
|
183
|
+
const prepared = await this.runtime.start(probe, worktree, controller.signal);
|
|
184
|
+
if (hostRuntimeContractFingerprint(prepared.runtimeContract) !== identity.runtimeContractFingerprint) {
|
|
185
|
+
throw new Error("host_runtime_prepared_source_changed: probe loaded a different runtime contract; application was not started.");
|
|
186
|
+
}
|
|
187
|
+
const started = await this.runtime.startConfiguredProject(probeId, probe.lease.fencingToken, controller.signal, progress => checkpoint?.({ code: "host_runtime_prepared_probe_progress", expiresAt: entry.expiresAt,
|
|
188
|
+
message: progress.message }));
|
|
189
|
+
verifiedRuntimeContractFingerprint = hostRuntimeContractFingerprint(started.runtimeContract);
|
|
190
|
+
verifiedPorts = started.lease.resources.ports.map(port => port.name).sort();
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
stopPreparedHeadMonitor.abort();
|
|
194
|
+
await preparedHeadMonitor;
|
|
195
|
+
// A failed teardown must not publish reusable evidence.
|
|
196
|
+
cleanupConfirmed = Boolean(await this.runtime.release(probeId, probe.lease.fencingToken));
|
|
197
|
+
entry.probe = undefined;
|
|
198
|
+
}
|
|
199
|
+
if (!cleanupConfirmed) {
|
|
200
|
+
throw new Error("host_runtime_prepared_probe_cleanup_unconfirmed: matching probe teardown was not confirmed.");
|
|
201
|
+
}
|
|
202
|
+
// Probe-owned processes may create transient, untracked files while they
|
|
203
|
+
// are alive. Verify the prepared source only after matching teardown is
|
|
204
|
+
// confirmed: persistent source edits still fail closed, while files that
|
|
205
|
+
// belong to the probe lifecycle cannot create a false source-change loop.
|
|
206
|
+
const currentDiagnostics = { entries: new Map() };
|
|
207
|
+
const currentIdentity = await preparedRuntimeIdentity(worktree, controller.signal, currentDiagnostics);
|
|
208
|
+
const changed = [];
|
|
209
|
+
if (verifiedRuntimeContractFingerprint !== identity.runtimeContractFingerprint
|
|
210
|
+
|| currentIdentity.runtimeContractFingerprint !== identity.runtimeContractFingerprint) {
|
|
211
|
+
changed.push("runtime contract");
|
|
212
|
+
}
|
|
213
|
+
if (currentIdentity.inputCommitSha !== identity.inputCommitSha)
|
|
214
|
+
changed.push("HEAD");
|
|
215
|
+
if (currentIdentity.sourceFingerprint !== identity.sourceFingerprint) {
|
|
216
|
+
const paths = changedIdentityPaths(preparedDiagnostics.entries, currentDiagnostics.entries);
|
|
217
|
+
changed.push(paths.length > 0
|
|
218
|
+
? `source fingerprint (${paths.join(", ")})`
|
|
219
|
+
: "source fingerprint");
|
|
220
|
+
}
|
|
221
|
+
if (changed.length > 0) {
|
|
222
|
+
throw new Error(`host_runtime_prepared_source_changed: ${changed.join(", ")} changed during isolation verification.`);
|
|
223
|
+
}
|
|
224
|
+
if (JSON.stringify(verifiedPorts) !== JSON.stringify(configuredLaunchPortNames(contract))) {
|
|
225
|
+
throw new Error("host_runtime_prepared_probe_incomplete: assigned-port verification did not confirm every configured launch port.");
|
|
226
|
+
}
|
|
227
|
+
controller.signal.throwIfAborted();
|
|
228
|
+
await this.store.observe(dispatch.repo.id, contract, probeId, configuredLaunchPortNames(contract), identity.inputCommitSha, identity.sourceFingerprint);
|
|
229
|
+
await checkpoint?.({ code: "host_runtime_prepared_probe_completed", expiresAt: entry.expiresAt,
|
|
230
|
+
message: `Prepared runtime ${identity.inputCommitSha.slice(0, 12)} passed isolation verification; its source-bound proof is retained independently.` });
|
|
231
|
+
return identity;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const preparedHeadPollIntervalMs = 1_000;
|
|
235
|
+
async function monitorPreparedHead(worktree, expectedCommitSha, preparationController, stopSignal) {
|
|
236
|
+
const git = new GitClient();
|
|
237
|
+
while (!stopSignal.aborted && !preparationController.signal.aborted) {
|
|
238
|
+
try {
|
|
239
|
+
const currentCommitSha = (await git.run(worktree, ["rev-parse", "--verify", "HEAD^{commit}"]))
|
|
240
|
+
.trim()
|
|
241
|
+
.toLowerCase();
|
|
242
|
+
if (currentCommitSha !== expectedCommitSha.toLowerCase()) {
|
|
243
|
+
throw new Error("host_runtime_prepared_source_changed: HEAD changed during isolation verification; the obsolete probe was cancelled.");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (!stopSignal.aborted && !preparationController.signal.aborted) {
|
|
248
|
+
const failure = error instanceof Error && error.message.startsWith("host_runtime_prepared_source_changed:")
|
|
249
|
+
? error
|
|
250
|
+
: new Error(`host_runtime_prepared_source_changed: HEAD could not be revalidated during isolation verification: ${errorMessage(error)}`);
|
|
251
|
+
preparationController.abort(failure);
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await waitForPreparedHeadPoll(stopSignal);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function waitForPreparedHeadPoll(signal) {
|
|
259
|
+
if (signal.aborted)
|
|
260
|
+
return;
|
|
261
|
+
await new Promise(resolve => {
|
|
262
|
+
const complete = () => {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
signal.removeEventListener("abort", complete);
|
|
265
|
+
resolve();
|
|
266
|
+
};
|
|
267
|
+
const timer = setTimeout(complete, preparedHeadPollIntervalMs);
|
|
268
|
+
timer.unref();
|
|
269
|
+
signal.addEventListener("abort", complete, { once: true });
|
|
270
|
+
if (signal.aborted)
|
|
271
|
+
complete();
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function errorMessage(error) {
|
|
275
|
+
return error instanceof Error ? error.message : String(error);
|
|
276
|
+
}
|
|
277
|
+
function changedIdentityPaths(before, after) {
|
|
278
|
+
const paths = new Set([...before.keys(), ...after.keys()]);
|
|
279
|
+
const changed = [...paths]
|
|
280
|
+
.filter(path => before.get(path) !== after.get(path))
|
|
281
|
+
.sort((left, right) => left.localeCompare(right));
|
|
282
|
+
if (changed.length <= 8)
|
|
283
|
+
return changed;
|
|
284
|
+
return [...changed.slice(0, 8), `+${changed.length - 8} more`];
|
|
285
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export declare const runtimeIsolationLeaseStates: readonly ["reserved", "starting", "active", "releasing", "released", "degraded"];
|
|
2
|
+
export type RuntimeIsolationLeaseState = typeof runtimeIsolationLeaseStates[number];
|
|
3
|
+
export type RuntimeIsolationMode = "docker" | "host";
|
|
4
|
+
export interface RuntimeIsolationPort {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly protocol: "tcp" | "udp";
|
|
7
|
+
readonly hostIp: "127.0.0.1" | "::1";
|
|
8
|
+
readonly hostPort: number;
|
|
9
|
+
readonly targetPort: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RuntimeIsolationPath {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly role: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RuntimeIsolationDockerResource {
|
|
16
|
+
readonly type: "container" | "network" | "volume";
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly role: string;
|
|
19
|
+
readonly id?: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface RuntimeIsolationResources {
|
|
22
|
+
readonly memoryReservationBytes: number;
|
|
23
|
+
readonly cpuMillis: number;
|
|
24
|
+
readonly pids: number;
|
|
25
|
+
readonly ports: readonly RuntimeIsolationPort[];
|
|
26
|
+
readonly paths: readonly RuntimeIsolationPath[];
|
|
27
|
+
readonly processSupervisorId: string | null;
|
|
28
|
+
readonly dockerResources: readonly RuntimeIsolationDockerResource[];
|
|
29
|
+
readonly singletonKeys: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
export interface RuntimeIsolationLease {
|
|
32
|
+
readonly schemaVersion: 1;
|
|
33
|
+
readonly leaseId: string;
|
|
34
|
+
readonly dispatchAttemptId: string;
|
|
35
|
+
readonly executionLeaseId: string;
|
|
36
|
+
readonly fencingToken: string;
|
|
37
|
+
readonly runnerId: string;
|
|
38
|
+
readonly runnerInstanceId: string;
|
|
39
|
+
readonly worktreeKey: string;
|
|
40
|
+
readonly worktreePath: string;
|
|
41
|
+
readonly mode: RuntimeIsolationMode;
|
|
42
|
+
readonly state: RuntimeIsolationLeaseState;
|
|
43
|
+
readonly createdAt: string;
|
|
44
|
+
readonly stateChangedAt: string;
|
|
45
|
+
readonly expiresAt: string;
|
|
46
|
+
readonly releasedAt: string | null;
|
|
47
|
+
readonly resources: RuntimeIsolationResources;
|
|
48
|
+
}
|
|
49
|
+
export interface RuntimeIsolationLeaseInput {
|
|
50
|
+
readonly leaseId: string;
|
|
51
|
+
readonly dispatchAttemptId: string;
|
|
52
|
+
readonly executionLeaseId: string;
|
|
53
|
+
readonly fencingToken: string;
|
|
54
|
+
readonly runnerId: string;
|
|
55
|
+
readonly runnerInstanceId: string;
|
|
56
|
+
readonly worktreeKey: string;
|
|
57
|
+
readonly worktreePath: string;
|
|
58
|
+
readonly mode: RuntimeIsolationMode;
|
|
59
|
+
readonly expiresAt: string;
|
|
60
|
+
readonly resources: RuntimeIsolationResources;
|
|
61
|
+
readonly createdAt?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function createRuntimeIsolationLease(input: RuntimeIsolationLeaseInput): RuntimeIsolationLease;
|
|
64
|
+
export declare function transitionRuntimeIsolationLease(lease: RuntimeIsolationLease, nextState: RuntimeIsolationLeaseState, occurredAt?: string): RuntimeIsolationLease;
|
|
65
|
+
export declare function renewRuntimeIsolationLease(lease: RuntimeIsolationLease, fencingToken: string, expiresAt: string): RuntimeIsolationLease | null;
|
|
66
|
+
export declare function withRuntimeIsolationResources(lease: RuntimeIsolationLease, resources: RuntimeIsolationResources): RuntimeIsolationLease;
|
|
67
|
+
export declare function validRuntimeIsolationLease(value: unknown): value is RuntimeIsolationLease;
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
|
61
|
+
|| lease.state === "releasing"
|
|
62
|
+
|| lease.state === "released"
|
|
63
|
+
|| !validTimestamp(expiresAt))
|
|
64
|
+
return null;
|
|
65
|
+
const renewed = { ...lease, expiresAt };
|
|
66
|
+
return validRuntimeIsolationLease(renewed) ? renewed : null;
|
|
67
|
+
}
|
|
68
|
+
export function withRuntimeIsolationResources(lease, resources) {
|
|
69
|
+
if (lease.state === "released") {
|
|
70
|
+
throw new Error("runtime_lease_transition_invalid: released leases cannot acquire resources.");
|
|
71
|
+
}
|
|
72
|
+
const updated = { ...lease, resources };
|
|
73
|
+
if (!validRuntimeIsolationLease(updated)) {
|
|
74
|
+
throw new Error("runtime_lease_invalid: resources are invalid.");
|
|
75
|
+
}
|
|
76
|
+
return updated;
|
|
77
|
+
}
|
|
78
|
+
export function validRuntimeIsolationLease(value) {
|
|
79
|
+
if (!isRecord(value)
|
|
80
|
+
|| value.schemaVersion !== 1
|
|
81
|
+
|| !isUuid(value.leaseId)
|
|
82
|
+
|| !isUuid(value.dispatchAttemptId)
|
|
83
|
+
|| !isUuid(value.executionLeaseId)
|
|
84
|
+
|| !isUuid(value.fencingToken)
|
|
85
|
+
|| !isUuid(value.runnerId)
|
|
86
|
+
|| !isUuid(value.runnerInstanceId)
|
|
87
|
+
|| !boundedText(value.worktreeKey, 255)
|
|
88
|
+
|| !boundedText(value.worktreePath, 4096)
|
|
89
|
+
|| (value.mode !== "docker" && value.mode !== "host")
|
|
90
|
+
|| !runtimeIsolationLeaseStates.includes(value.state)
|
|
91
|
+
|| !validTimestamp(value.createdAt)
|
|
92
|
+
|| !validTimestamp(value.stateChangedAt)
|
|
93
|
+
|| !validTimestamp(value.expiresAt)
|
|
94
|
+
|| Date.parse(value.stateChangedAt) < Date.parse(value.createdAt)
|
|
95
|
+
|| Date.parse(value.expiresAt) < Date.parse(value.createdAt)
|
|
96
|
+
|| (value.state === "released") !== validTimestamp(value.releasedAt)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (value.releasedAt !== null && Date.parse(value.releasedAt) < Date.parse(value.createdAt))
|
|
100
|
+
return false;
|
|
101
|
+
return validResources(value.resources, value.mode);
|
|
102
|
+
}
|
|
103
|
+
function validResources(value, mode) {
|
|
104
|
+
if (!isRecord(value)
|
|
105
|
+
|| !nonNegativeInteger(value.memoryReservationBytes)
|
|
106
|
+
|| !nonNegativeInteger(value.cpuMillis)
|
|
107
|
+
|| !nonNegativeInteger(value.pids)
|
|
108
|
+
|| !Array.isArray(value.ports) || value.ports.length > 64
|
|
109
|
+
|| !Array.isArray(value.paths) || value.paths.length > 64
|
|
110
|
+
|| !Array.isArray(value.dockerResources) || value.dockerResources.length > 128
|
|
111
|
+
|| !Array.isArray(value.singletonKeys) || value.singletonKeys.length > 64
|
|
112
|
+
|| (value.processSupervisorId !== null && !boundedText(value.processSupervisorId, 255))) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (value.ports.some(port => !validPort(port))
|
|
116
|
+
|| value.paths.some(entry => !isRecord(entry) || !boundedText(entry.path, 4096) || !boundedText(entry.role, 128))
|
|
117
|
+
|| value.singletonKeys.some(key => !boundedText(key, 255))
|
|
118
|
+
|| value.dockerResources.some(resource => !validDockerResource(resource))) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return mode === "docker" || value.dockerResources.length === 0;
|
|
122
|
+
}
|
|
123
|
+
function validPort(value) {
|
|
124
|
+
return isRecord(value)
|
|
125
|
+
&& boundedText(value.name, 128)
|
|
126
|
+
&& (value.protocol === "tcp" || value.protocol === "udp")
|
|
127
|
+
&& (value.hostIp === "127.0.0.1" || value.hostIp === "::1")
|
|
128
|
+
&& validPortNumber(value.hostPort)
|
|
129
|
+
&& validPortNumber(value.targetPort);
|
|
130
|
+
}
|
|
131
|
+
function validDockerResource(value) {
|
|
132
|
+
return isRecord(value)
|
|
133
|
+
&& (value.type === "container" || value.type === "network" || value.type === "volume")
|
|
134
|
+
&& boundedText(value.name, 255)
|
|
135
|
+
&& boundedText(value.role, 128)
|
|
136
|
+
&& (value.id === undefined || value.id === null || boundedText(value.id, 255));
|
|
137
|
+
}
|
|
138
|
+
function validPortNumber(value) {
|
|
139
|
+
return Number.isSafeInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
|
|
140
|
+
}
|
|
141
|
+
function nonNegativeInteger(value) {
|
|
142
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
143
|
+
}
|
|
144
|
+
function validTimestamp(value) {
|
|
145
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
146
|
+
}
|
|
147
|
+
function boundedText(value, maximumLength) {
|
|
148
|
+
return typeof value === "string"
|
|
149
|
+
&& value.length > 0
|
|
150
|
+
&& value.length <= maximumLength
|
|
151
|
+
&& Array.from(value).every((character) => {
|
|
152
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
153
|
+
return codePoint > 31 && codePoint !== 127;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function isUuid(value) {
|
|
157
|
+
return typeof value === "string" && /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(value);
|
|
158
|
+
}
|
|
159
|
+
function isRecord(value) {
|
|
160
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
161
|
+
}
|
|
@@ -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>;
|