@loucompanion/forge-bridge 0.1.3-dev.0b2f81d660c0 → 0.1.3-dev.0bd9fb991b79
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 +44 -0
- package/dist/bridge/bridge.base.d.ts +5 -1
- package/dist/bridge/bridge.service.d.ts +40 -2
- package/dist/bridge/bridge.service.js +564 -14
- package/dist/bridge/bridge.types.d.ts +110 -1
- package/dist/cli/cli.help.js +12 -0
- package/dist/cli/cli.service.d.ts +12 -0
- package/dist/cli/cli.service.js +168 -1
- 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 +40 -0
- package/dist/sandbox/preflight.js +256 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +124 -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 +5 -0
- package/dist/sandbox/repository-lock.js +83 -0
- package/dist/sandbox/runtime.d.ts +234 -0
- package/dist/sandbox/runtime.js +1773 -0
- package/dist/sandbox/sandbox.types.d.ts +83 -0
- package/dist/sandbox/sandbox.types.js +1 -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 +228 -0
- package/dist/sandbox/workspace.service.d.ts +12 -0
- package/dist/sandbox/workspace.service.js +49 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +20 -9
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +2 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +31 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +3 -0
- package/dist/session/provider-cli/provider-cli.service.js +72 -15
- package/dist/session/session.service.js +14 -7
- package/dist/session/session.types.d.ts +41 -1
- package/dist/session/session.types.js +119 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -2
|
@@ -5,7 +5,7 @@ import { buildSpecBody, selectLaneCommand } from "../../worktree/artifact-inject
|
|
|
5
5
|
import { completionPhaseFromDispatch, prepareCompletionToolRuntime, readCompletionArtifact, usesDebugCycleCompletion, usesDevelopCycleCompletion } from "../phase-completion.js";
|
|
6
6
|
import { cleanupCompletionTools, commitWorktreeChanges, completionPhaseFromArtifact, emitArchitectStructuredEvents, emitCompletionArtifactRecorded, emitDecomposeGateArtifacts, emitDraftGateArtifacts, emitPhaseCompletionEvent, emitPhaseGateEvents, emitQaArtifactRecorded, emitQaStructuredEvents, enforceQaBrowserLaunchTargetGate, LiveArchitectArtifactEmitter, LiveCompletionArtifactEmitter, LiveQaArtifactEmitter, readCompletionArtifactsForRun, readQaCompletionArtifactBundle, summarizeStderr, waitForCompletionArtifact } from "./provider-cli.artifacts.js";
|
|
7
7
|
import { DeterministicProviderCliService } from "./provider-cli.deterministic.js";
|
|
8
|
-
import { buildArchitectPrompt, buildConflictResolvePrompt, buildDebugCyclePrompt, buildDevelopCyclePrompt, buildDraftPrompt, buildDraftSpec, buildImplementationPrompt, buildProviderCliCommand, durableDraftInput, buildPullRequestCommentsPrompt, buildQaPrompt, formatProviderCliCommandForCheckpoint, isDecomposeDraftDispatch, isPullRequestCommentsDispatch } from "./provider-cli.prompts.js";
|
|
8
|
+
import { buildArchitectPrompt, buildConflictResolvePrompt, buildDebugCyclePrompt, buildDevelopCyclePrompt, buildDraftPrompt, buildDraftSpec, buildImplementationPrompt, buildProviderCliCommand, durableDraftInput, buildPullRequestCheckRepairPrompt, buildPullRequestCommentsPrompt, buildQaPrompt, formatProviderCliCommandForCheckpoint, isDecomposeDraftDispatch, isPullRequestCheckRepairDispatch, isPullRequestCommentsDispatch } from "./provider-cli.prompts.js";
|
|
9
9
|
import { StreamingProviderProcessRunner } from "./provider-cli.process.js";
|
|
10
10
|
import { emitProviderUsageEvent, providerProcessEnv } from "./provider-cli.runtime.js";
|
|
11
11
|
import { clip, createProviderOutputState, parseJsonLine } from "./provider-output-state.js";
|
|
@@ -13,6 +13,45 @@ export { DeterministicProviderCliService } from "./provider-cli.deterministic.js
|
|
|
13
13
|
export { buildProviderCliCommand, codexSandboxModeForPlatform, formatProviderCliCommand, formatProviderCliCommandForCheckpoint } from "./provider-cli.prompts.js";
|
|
14
14
|
export { buildProviderChildEnvironment, chooseWindowsCommandCandidate, StreamingProviderProcessRunner } from "./provider-cli.process.js";
|
|
15
15
|
export { buildWindowsLaunchCommand } from "../../shared/process/windows-launch.js";
|
|
16
|
+
async function pushWorktreeBranch(git, worktreePath, branchName, emit) {
|
|
17
|
+
try {
|
|
18
|
+
await git.run(worktreePath, ["push", "origin", `HEAD:${branchName}`]);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
if (!isNonFastForwardPushError(err)) {
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
await emit?.({
|
|
26
|
+
role: "runner",
|
|
27
|
+
kind: "checkpoint",
|
|
28
|
+
content: `Remote branch ${branchName} moved before push; fetching latest and rebasing local changes.`,
|
|
29
|
+
eventType: "worktree_branch_push_rebase"
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
await git.run(worktreePath, ["fetch", "origin", remoteTrackingRefspec(branchName)]);
|
|
33
|
+
await git.run(worktreePath, ["rebase", `refs/remotes/origin/${branchName}`]);
|
|
34
|
+
await git.run(worktreePath, ["push", "origin", `HEAD:${branchName}`]);
|
|
35
|
+
}
|
|
36
|
+
catch (retryErr) {
|
|
37
|
+
throw new Error(`git push origin HEAD:${branchName} was rejected because the remote branch moved, and automatic fetch/rebase failed: ${errorMessage(retryErr)}`, { cause: retryErr });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function remoteTrackingRefspec(branchName) {
|
|
42
|
+
return `refs/heads/${branchName}:refs/remotes/origin/${branchName}`;
|
|
43
|
+
}
|
|
44
|
+
function isNonFastForwardPushError(err) {
|
|
45
|
+
const message = errorMessage(err).toLowerCase();
|
|
46
|
+
return message.includes("non-fast-forward")
|
|
47
|
+
|| message.includes("fetch first")
|
|
48
|
+
|| message.includes("tip of your current branch is behind")
|
|
49
|
+
|| message.includes("updates were rejected because the tip of your current branch is behind")
|
|
50
|
+
|| message.includes("stale info");
|
|
51
|
+
}
|
|
52
|
+
function errorMessage(err) {
|
|
53
|
+
return err instanceof Error ? err.message : String(err);
|
|
54
|
+
}
|
|
16
55
|
const fakeWorkerModel = "server-local-fake-worker";
|
|
17
56
|
export class RoutingProviderCliService {
|
|
18
57
|
realProvider;
|
|
@@ -31,6 +70,11 @@ export class RoutingProviderCliService {
|
|
|
31
70
|
? await this.deterministicProvider.runArchitect(context)
|
|
32
71
|
: await this.realProvider.runArchitect(context);
|
|
33
72
|
}
|
|
73
|
+
async runCiRepair(context) {
|
|
74
|
+
return isFakeWorkerModel(context.dispatch.execution.model)
|
|
75
|
+
? await this.deterministicProvider.runCiRepair(context)
|
|
76
|
+
: await this.realProvider.runCiRepair(context);
|
|
77
|
+
}
|
|
34
78
|
async runConflictResolve(context) {
|
|
35
79
|
return isFakeWorkerModel(context.dispatch.execution.model)
|
|
36
80
|
? await this.deterministicProvider.runConflictResolve(context)
|
|
@@ -295,7 +339,7 @@ export class ProcessBackedProviderCliService {
|
|
|
295
339
|
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
296
340
|
const committed = await commitWorktreeChanges(this.git, worktree.worktreePath, dispatch.workItem.id);
|
|
297
341
|
if (developCycle || debugCycle || phase === "implementation") {
|
|
298
|
-
await this.git
|
|
342
|
+
await pushWorktreeBranch(this.git, worktree.worktreePath, worktree.branchName, context.emit);
|
|
299
343
|
await context.emit({
|
|
300
344
|
role: "runner",
|
|
301
345
|
kind: "checkpoint",
|
|
@@ -527,19 +571,28 @@ export class ProcessBackedProviderCliService {
|
|
|
527
571
|
artifacts: [qaArtifact]
|
|
528
572
|
};
|
|
529
573
|
}
|
|
574
|
+
async runCiRepair(context) {
|
|
575
|
+
return await this.runPullRequestRepair(context, "ci_repair");
|
|
576
|
+
}
|
|
530
577
|
async runConflictResolve(context) {
|
|
578
|
+
return await this.runPullRequestRepair(context, "conflict_resolve");
|
|
579
|
+
}
|
|
580
|
+
async runPullRequestRepair(context, route) {
|
|
531
581
|
const { dispatch, worktree } = context;
|
|
532
|
-
const
|
|
582
|
+
const repairChecks = route === "ci_repair" || isPullRequestCheckRepairDispatch(dispatch);
|
|
583
|
+
const addressComments = !repairChecks && isPullRequestCommentsDispatch(dispatch);
|
|
533
584
|
const command = buildProviderCliCommand(dispatch, {
|
|
534
585
|
worktreePath: worktree.worktreePath,
|
|
535
|
-
prompt:
|
|
536
|
-
?
|
|
537
|
-
:
|
|
586
|
+
prompt: repairChecks
|
|
587
|
+
? buildPullRequestCheckRepairPrompt(dispatch, worktree.branchName)
|
|
588
|
+
: addressComments
|
|
589
|
+
? buildPullRequestCommentsPrompt(dispatch, worktree.branchName)
|
|
590
|
+
: buildConflictResolvePrompt(dispatch, worktree.branchName)
|
|
538
591
|
});
|
|
539
592
|
await context.emit({
|
|
540
593
|
role: "runner",
|
|
541
594
|
kind: "checkpoint",
|
|
542
|
-
content: `Starting provider CLI ${addressComments ? "PR comment address" : "conflict resolve"}: ${formatProviderCliCommandForCheckpoint(command)} in ${worktree.worktreePath}`,
|
|
595
|
+
content: `Starting provider CLI ${repairChecks ? "CI repair" : addressComments ? "PR comment address" : "conflict resolve"}: ${formatProviderCliCommandForCheckpoint(command)} in ${worktree.worktreePath}`,
|
|
543
596
|
eventType: "provider_cli_started"
|
|
544
597
|
});
|
|
545
598
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
@@ -560,20 +613,24 @@ export class ProcessBackedProviderCliService {
|
|
|
560
613
|
throw new Error(`${command.command} exited ${result.exitCode}${stderr ? `: ${stderr}` : ""}`);
|
|
561
614
|
}
|
|
562
615
|
const committed = await commitWorktreeChanges(this.git, worktree.worktreePath, dispatch.workItem.id);
|
|
563
|
-
await this.git
|
|
616
|
+
await pushWorktreeBranch(this.git, worktree.worktreePath, worktree.branchName, context.emit);
|
|
564
617
|
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
565
618
|
await context.emit({
|
|
566
619
|
role: "assistant",
|
|
567
620
|
kind: "message",
|
|
568
|
-
content: outputState.finalMessage || (
|
|
569
|
-
? `${dispatch.execution.provider} CLI
|
|
570
|
-
:
|
|
571
|
-
|
|
621
|
+
content: outputState.finalMessage || (repairChecks
|
|
622
|
+
? `${dispatch.execution.provider} CLI repaired pull request checks for ${worktree.branchName}.`
|
|
623
|
+
: addressComments
|
|
624
|
+
? `${dispatch.execution.provider} CLI addressed pull request comments for ${worktree.branchName}.`
|
|
625
|
+
: `${dispatch.execution.provider} CLI resolved merge conflicts for ${worktree.branchName}.`),
|
|
626
|
+
eventType: repairChecks ? "pr_checks_complete" : addressComments ? "pr_comments_complete" : "conflict_resolve_complete"
|
|
572
627
|
});
|
|
573
628
|
return {
|
|
574
|
-
summary:
|
|
575
|
-
? `${dispatch.execution.provider} CLI
|
|
576
|
-
:
|
|
629
|
+
summary: repairChecks
|
|
630
|
+
? `${dispatch.execution.provider} CLI repaired PR checks on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
|
|
631
|
+
: addressComments
|
|
632
|
+
? `${dispatch.execution.provider} CLI addressed PR comments on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
|
|
633
|
+
: `${dispatch.execution.provider} CLI resolved conflicts on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
|
|
577
634
|
};
|
|
578
635
|
}
|
|
579
636
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { redactSecrets } from "../shared/redaction/redaction.js";
|
|
2
2
|
import { RoutingProviderCliService } from "./provider-cli/provider-cli.service.js";
|
|
3
3
|
import { GitWorktreeService } from "../worktree/worktree.service.js";
|
|
4
|
+
import { SandboxWorkspaceService } from "../sandbox/workspace.service.js";
|
|
4
5
|
export class BridgeSessionService {
|
|
5
6
|
worktrees;
|
|
6
7
|
providerCli;
|
|
7
|
-
constructor(worktrees =
|
|
8
|
+
constructor(worktrees = process.env.FORGE_SANDBOX_ID
|
|
9
|
+
? new SandboxWorkspaceService()
|
|
10
|
+
: new GitWorktreeService(), providerCli = new RoutingProviderCliService()) {
|
|
8
11
|
this.worktrees = worktrees;
|
|
9
12
|
this.providerCli = providerCli;
|
|
10
13
|
}
|
|
@@ -75,10 +78,12 @@ export class BridgeSessionService {
|
|
|
75
78
|
}, redactionSecrets);
|
|
76
79
|
}
|
|
77
80
|
throwIfAborted(signal);
|
|
78
|
-
const lane = dispatch.execution.lane.toLowerCase();
|
|
79
|
-
const role = dispatch.execution.role.toLowerCase();
|
|
81
|
+
const lane = dispatch.execution.lane.trim().toLowerCase();
|
|
82
|
+
const role = dispatch.execution.role.trim().toLowerCase();
|
|
83
|
+
const cycleKind = dispatch.cycleRun.kind.trim().toLowerCase();
|
|
80
84
|
const isQa = role === "qa" || lane === "qa";
|
|
81
|
-
const isArchitect = role === "architect" || lane === "architect" ||
|
|
85
|
+
const isArchitect = role === "architect" || lane === "architect" || cycleKind === "architect";
|
|
86
|
+
const isCiRepair = role === "ci_repair" || lane === "ci_repair" || cycleKind === "pr_checks";
|
|
82
87
|
const isConflictResolve = role === "conflict_resolve" || lane === "conflict_resolve";
|
|
83
88
|
const context = {
|
|
84
89
|
dispatch: providerDispatch,
|
|
@@ -90,9 +95,11 @@ export class BridgeSessionService {
|
|
|
90
95
|
? await this.providerCli.runQa(context)
|
|
91
96
|
: isArchitect
|
|
92
97
|
? await this.providerCli.runArchitect(context)
|
|
93
|
-
:
|
|
94
|
-
? await this.providerCli.
|
|
95
|
-
:
|
|
98
|
+
: isCiRepair
|
|
99
|
+
? await this.providerCli.runCiRepair(context)
|
|
100
|
+
: isConflictResolve
|
|
101
|
+
? await this.providerCli.runConflictResolve(context)
|
|
102
|
+
: await this.providerCli.runImplementation(context);
|
|
96
103
|
throwIfAborted(signal);
|
|
97
104
|
const phaseArtifacts = run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : [];
|
|
98
105
|
return redactSecrets({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface RunOnceDispatchPacket {
|
|
2
|
-
readonly schemaVersion: 1;
|
|
2
|
+
readonly schemaVersion: 1 | 2;
|
|
3
3
|
readonly userId: string;
|
|
4
4
|
readonly workItem: DispatchWorkItem;
|
|
5
5
|
readonly repo: DispatchRepo;
|
|
@@ -8,8 +8,10 @@ export interface RunOnceDispatchPacket {
|
|
|
8
8
|
readonly lease: DispatchLease;
|
|
9
9
|
readonly execution: DispatchExecution;
|
|
10
10
|
readonly serverLocal: DispatchServerLocalConfig;
|
|
11
|
+
readonly sandbox?: DispatchSandboxContext | null;
|
|
11
12
|
readonly pullRequest?: DispatchPullRequestContext | null;
|
|
12
13
|
readonly apiKeySecretGrant?: ApiKeySecretGrant | null;
|
|
14
|
+
readonly apiKeySecretGrantHandle?: ApiKeySecretGrantHandle | null;
|
|
13
15
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
14
16
|
readonly warmPathOverrides?: readonly string[];
|
|
15
17
|
readonly qaBrowser?: DispatchQaBrowserContext | null;
|
|
@@ -22,6 +24,39 @@ export interface RunOnceDispatchPacket {
|
|
|
22
24
|
readonly designerRefs?: DispatchDesignerRefHandoffContext | null;
|
|
23
25
|
readonly draft?: DispatchDraftPrompt | null;
|
|
24
26
|
}
|
|
27
|
+
export interface DispatchSandboxContext {
|
|
28
|
+
readonly dispatchAttemptId: string;
|
|
29
|
+
readonly executionLeaseId: string;
|
|
30
|
+
readonly runnerInstanceId: string;
|
|
31
|
+
readonly executionMode: "host" | "docker";
|
|
32
|
+
readonly executionPolicy: "host_allowed" | "sandbox_preferred" | "sandbox_required";
|
|
33
|
+
readonly networkPolicy: DispatchSandboxNetworkPolicy;
|
|
34
|
+
readonly requestedResources: DispatchSandboxResourceRequest;
|
|
35
|
+
readonly runtime?: string | null;
|
|
36
|
+
readonly sandboxConfigCommitSha?: string | null;
|
|
37
|
+
readonly inputCommitSha?: string | null;
|
|
38
|
+
readonly contractDigest?: string | null;
|
|
39
|
+
readonly imageDigest?: string | null;
|
|
40
|
+
readonly publicationMode: "bridge_push" | "shared_filesystem";
|
|
41
|
+
readonly publishGrantHandle?: DispatchPublishGrantHandle | null;
|
|
42
|
+
}
|
|
43
|
+
export interface DispatchPublishGrantHandle {
|
|
44
|
+
readonly grantId: string;
|
|
45
|
+
readonly branch: string;
|
|
46
|
+
readonly expiresAt: string;
|
|
47
|
+
}
|
|
48
|
+
export interface DispatchSandboxResourceRequest {
|
|
49
|
+
readonly sandboxSlots: number;
|
|
50
|
+
readonly cpuMillis: number;
|
|
51
|
+
readonly memoryBytes: number;
|
|
52
|
+
readonly workspaceBytes: number;
|
|
53
|
+
readonly pids: number;
|
|
54
|
+
readonly buildSlots: number;
|
|
55
|
+
}
|
|
56
|
+
export interface DispatchSandboxNetworkPolicy {
|
|
57
|
+
readonly mode: "offline" | "public_allowlist" | "open_public";
|
|
58
|
+
readonly allowedHosts: readonly string[];
|
|
59
|
+
}
|
|
25
60
|
export interface DispatchDraftPrompt {
|
|
26
61
|
readonly kind: string;
|
|
27
62
|
readonly systemPrompt: string;
|
|
@@ -279,6 +314,11 @@ export interface ApiKeySecretGrant {
|
|
|
279
314
|
readonly secret: string;
|
|
280
315
|
readonly expiresAt: string;
|
|
281
316
|
}
|
|
317
|
+
export interface ApiKeySecretGrantHandle {
|
|
318
|
+
readonly grantId: string;
|
|
319
|
+
readonly provider: string;
|
|
320
|
+
readonly expiresAt: string;
|
|
321
|
+
}
|
|
282
322
|
export interface DispatchServerLocalConfig {
|
|
283
323
|
readonly repoRoot: string;
|
|
284
324
|
readonly cleanupWorktrees: boolean;
|
|
@@ -2,11 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
export function parseRunOnceDispatchPacket(value) {
|
|
3
3
|
const packet = asObject(value, "dispatch packet");
|
|
4
4
|
const schemaVersion = requiredNumber(packet, "schemaVersion");
|
|
5
|
-
if (schemaVersion !== 1) {
|
|
5
|
+
if (schemaVersion !== 1 && schemaVersion !== 2) {
|
|
6
6
|
throw new Error(`Unsupported dispatch schemaVersion: ${schemaVersion}`);
|
|
7
7
|
}
|
|
8
8
|
return {
|
|
9
|
-
schemaVersion
|
|
9
|
+
schemaVersion,
|
|
10
10
|
userId: requiredString(packet, "userId"),
|
|
11
11
|
workItem: parseWorkItem(packet.workItem),
|
|
12
12
|
repo: parseRepo(packet.repo),
|
|
@@ -15,8 +15,10 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
15
15
|
lease: parseLease(packet.lease),
|
|
16
16
|
execution: parseExecution(packet.execution),
|
|
17
17
|
serverLocal: parseServerLocal(packet.serverLocal),
|
|
18
|
+
sandbox: parseSandbox(packet.sandbox, schemaVersion),
|
|
18
19
|
pullRequest: parsePullRequest(packet.pullRequest),
|
|
19
20
|
apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
|
|
21
|
+
apiKeySecretGrantHandle: parseApiKeySecretGrantHandle(packet.apiKeySecretGrantHandle),
|
|
20
22
|
artifacts: parseArtifacts(packet.artifacts),
|
|
21
23
|
warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
|
|
22
24
|
qaBrowser: parseQaBrowser(packet.qaBrowser),
|
|
@@ -30,6 +32,104 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
30
32
|
draft: parseDraftPrompt(packet.draft)
|
|
31
33
|
};
|
|
32
34
|
}
|
|
35
|
+
function parseSandbox(value, schemaVersion) {
|
|
36
|
+
if (value === undefined || value === null) {
|
|
37
|
+
if (schemaVersion === 2) {
|
|
38
|
+
throw new Error("sandbox is required for dispatch schemaVersion 2.");
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const sandbox = asObject(value, "sandbox");
|
|
43
|
+
const executionMode = requiredString(sandbox, "executionMode");
|
|
44
|
+
if (executionMode !== "host" && executionMode !== "docker") {
|
|
45
|
+
throw new Error("sandbox.executionMode must be host or docker.");
|
|
46
|
+
}
|
|
47
|
+
const executionPolicy = requiredString(sandbox, "executionPolicy");
|
|
48
|
+
if (executionPolicy !== "host_allowed" && executionPolicy !== "sandbox_preferred" && executionPolicy !== "sandbox_required") {
|
|
49
|
+
throw new Error("sandbox.executionPolicy is invalid.");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
dispatchAttemptId: requiredString(sandbox, "dispatchAttemptId"),
|
|
53
|
+
executionLeaseId: requiredString(sandbox, "executionLeaseId"),
|
|
54
|
+
runnerInstanceId: requiredString(sandbox, "runnerInstanceId"),
|
|
55
|
+
executionMode,
|
|
56
|
+
executionPolicy,
|
|
57
|
+
networkPolicy: parseSandboxNetworkPolicy(sandbox.networkPolicy),
|
|
58
|
+
requestedResources: parseSandboxResourceRequest(sandbox.requestedResources),
|
|
59
|
+
runtime: optionalString(sandbox, "runtime"),
|
|
60
|
+
sandboxConfigCommitSha: optionalString(sandbox, "sandboxConfigCommitSha"),
|
|
61
|
+
inputCommitSha: optionalString(sandbox, "inputCommitSha"),
|
|
62
|
+
contractDigest: optionalString(sandbox, "contractDigest"),
|
|
63
|
+
imageDigest: optionalString(sandbox, "imageDigest"),
|
|
64
|
+
publicationMode: parsePublicationMode(sandbox.publicationMode),
|
|
65
|
+
publishGrantHandle: parsePublishGrantHandle(sandbox.publishGrantHandle)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parsePublicationMode(value) {
|
|
69
|
+
if (value === undefined || value === null)
|
|
70
|
+
return "shared_filesystem";
|
|
71
|
+
if (value !== "bridge_push" && value !== "shared_filesystem")
|
|
72
|
+
throw new Error("sandbox.publicationMode is invalid.");
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function parsePublishGrantHandle(value) {
|
|
76
|
+
if (value === undefined || value === null)
|
|
77
|
+
return null;
|
|
78
|
+
const handle = asObject(value, "sandbox.publishGrantHandle");
|
|
79
|
+
const grantId = requiredString(handle, "grantId");
|
|
80
|
+
const branch = requiredString(handle, "branch");
|
|
81
|
+
const expiresAt = requiredString(handle, "expiresAt");
|
|
82
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(grantId)) {
|
|
83
|
+
throw new Error("sandbox.publishGrantHandle.grantId is invalid.");
|
|
84
|
+
}
|
|
85
|
+
if (branch.length > 240)
|
|
86
|
+
throw new Error("sandbox.publishGrantHandle.branch is too long.");
|
|
87
|
+
if (!Number.isFinite(Date.parse(expiresAt)))
|
|
88
|
+
throw new Error("sandbox.publishGrantHandle.expiresAt is invalid.");
|
|
89
|
+
return { grantId, branch, expiresAt };
|
|
90
|
+
}
|
|
91
|
+
function parseSandboxResourceRequest(value) {
|
|
92
|
+
if (value === undefined || value === null) {
|
|
93
|
+
return {
|
|
94
|
+
sandboxSlots: 1,
|
|
95
|
+
cpuMillis: 2000,
|
|
96
|
+
memoryBytes: 2 * 1024 ** 3,
|
|
97
|
+
workspaceBytes: 10 * 1024 ** 3,
|
|
98
|
+
pids: 512,
|
|
99
|
+
buildSlots: 1
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const resources = asObject(value, "sandbox.requestedResources");
|
|
103
|
+
return {
|
|
104
|
+
sandboxSlots: boundedInteger(resources, "sandboxSlots", 1, 4),
|
|
105
|
+
cpuMillis: boundedInteger(resources, "cpuMillis", 100, 64_000),
|
|
106
|
+
memoryBytes: boundedInteger(resources, "memoryBytes", 128 * 1024 ** 2, 512 * 1024 ** 3),
|
|
107
|
+
workspaceBytes: boundedInteger(resources, "workspaceBytes", 1024 ** 3, 10 * 1024 ** 4),
|
|
108
|
+
pids: boundedInteger(resources, "pids", 32, 65_536),
|
|
109
|
+
buildSlots: boundedInteger(resources, "buildSlots", 0, 4)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function parseSandboxNetworkPolicy(value) {
|
|
113
|
+
if (value === undefined || value === null)
|
|
114
|
+
return { mode: "public_allowlist", allowedHosts: [] };
|
|
115
|
+
const policy = asObject(value, "sandbox.networkPolicy");
|
|
116
|
+
const mode = requiredString(policy, "mode");
|
|
117
|
+
if (mode !== "offline" && mode !== "public_allowlist" && mode !== "open_public") {
|
|
118
|
+
throw new Error("sandbox.networkPolicy.mode is invalid.");
|
|
119
|
+
}
|
|
120
|
+
const rawHosts = policy.allowedHosts ?? [];
|
|
121
|
+
if (!Array.isArray(rawHosts) || rawHosts.some((host) => typeof host !== "string")) {
|
|
122
|
+
throw new Error("sandbox.networkPolicy.allowedHosts must be a string array.");
|
|
123
|
+
}
|
|
124
|
+
const allowedHosts = [...new Set(rawHosts.map(host => host.trim().toLowerCase()).filter(Boolean))];
|
|
125
|
+
if (allowedHosts.length > 128 || allowedHosts.some(host => !/^(?:\*\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(host))) {
|
|
126
|
+
throw new Error("sandbox.networkPolicy.allowedHosts contains an invalid hostname.");
|
|
127
|
+
}
|
|
128
|
+
if (mode !== "public_allowlist" && allowedHosts.length > 0) {
|
|
129
|
+
throw new Error("sandbox.networkPolicy.allowedHosts requires public_allowlist mode.");
|
|
130
|
+
}
|
|
131
|
+
return { mode, allowedHosts };
|
|
132
|
+
}
|
|
33
133
|
export function failureResult(summary, events = []) {
|
|
34
134
|
return {
|
|
35
135
|
schemaVersion: 1,
|
|
@@ -172,6 +272,16 @@ function parseApiKeySecretGrant(value) {
|
|
|
172
272
|
expiresAt: requiredString(grant, "expiresAt")
|
|
173
273
|
};
|
|
174
274
|
}
|
|
275
|
+
function parseApiKeySecretGrantHandle(value) {
|
|
276
|
+
if (value === undefined || value === null)
|
|
277
|
+
return null;
|
|
278
|
+
const grant = asObject(value, "apiKeySecretGrantHandle");
|
|
279
|
+
return {
|
|
280
|
+
grantId: requiredString(grant, "grantId"),
|
|
281
|
+
provider: requiredString(grant, "provider"),
|
|
282
|
+
expiresAt: requiredString(grant, "expiresAt")
|
|
283
|
+
};
|
|
284
|
+
}
|
|
175
285
|
function parseServerLocal(value) {
|
|
176
286
|
const serverLocal = asObject(value, "serverLocal");
|
|
177
287
|
return {
|
|
@@ -616,3 +726,10 @@ function requiredNumber(source, key) {
|
|
|
616
726
|
}
|
|
617
727
|
return value;
|
|
618
728
|
}
|
|
729
|
+
function boundedInteger(source, key, minimum, maximum) {
|
|
730
|
+
const value = requiredNumber(source, key);
|
|
731
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
732
|
+
throw new Error(`${key} must be an integer from ${minimum} through ${maximum}.`);
|
|
733
|
+
}
|
|
734
|
+
return value;
|
|
735
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export declare const forgeBridgeVersion = "0.1.3-dev.0bd9fb991b79";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.3-dev.0bd9fb991b79";
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.3-dev.
|
|
3
|
+
"version": "0.1.3-dev.0bd9fb991b79",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
+
"forge": "dist/cli/bin.js",
|
|
8
9
|
"forge-bridge": "dist/cli/bin.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "./dist/index.js",
|
|
@@ -43,7 +44,8 @@
|
|
|
43
44
|
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\""
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@microsoft/signalr": "^8.0.17"
|
|
47
|
+
"@microsoft/signalr": "^8.0.17",
|
|
48
|
+
"yaml": "^2.9.0"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@eslint/js": "^10.0.1",
|