@loucompanion/forge-bridge 0.1.3-dev.b3bce6ba65a6 → 0.1.3-dev.ba293ccf876b

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.
Files changed (189) hide show
  1. package/README.md +127 -0
  2. package/dist/bridge/bridge.base.d.ts +8 -1
  3. package/dist/bridge/bridge.service.d.ts +98 -3
  4. package/dist/bridge/bridge.service.js +1425 -45
  5. package/dist/bridge/bridge.types.d.ts +205 -1
  6. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  7. package/dist/bridge/readiness-result-outbox.js +54 -0
  8. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  9. package/dist/bridge/readiness-result-worker.js +100 -0
  10. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  11. package/dist/bridge/runner-host-capabilities.js +93 -0
  12. package/dist/cleanup/cleanup.service.d.ts +14 -2
  13. package/dist/cleanup/cleanup.service.js +496 -3
  14. package/dist/cleanup/cleanup.types.d.ts +10 -0
  15. package/dist/cleanup/index.d.ts +1 -1
  16. package/dist/cleanup/index.js +1 -1
  17. package/dist/cleanup/internals.d.ts +2 -2
  18. package/dist/cleanup/internals.js +1 -1
  19. package/dist/cli/cli.help.js +47 -1
  20. package/dist/cli/cli.service.d.ts +45 -2
  21. package/dist/cli/cli.service.js +377 -27
  22. package/dist/cli/local-config.d.ts +5 -0
  23. package/dist/cli/local-config.js +45 -1
  24. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  25. package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
  26. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  27. package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
  28. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  29. package/dist/runtime/attempt-path-operation.js +94 -0
  30. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  31. package/dist/runtime/attempt-private-directory.js +54 -0
  32. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  33. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  34. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  35. package/dist/runtime/attempt-process-supervisor.js +386 -0
  36. package/dist/runtime/canonical-source-file.d.ts +3 -0
  37. package/dist/runtime/canonical-source-file.js +54 -0
  38. package/dist/runtime/docker-engine-probe.d.ts +83 -0
  39. package/dist/runtime/docker-engine-probe.js +300 -0
  40. package/dist/runtime/generated-output-reset.d.ts +10 -0
  41. package/dist/runtime/generated-output-reset.js +103 -0
  42. package/dist/runtime/host-diagnostics.d.ts +4 -0
  43. package/dist/runtime/host-diagnostics.js +95 -0
  44. package/dist/runtime/host-listener-inspector.d.ts +46 -0
  45. package/dist/runtime/host-listener-inspector.js +349 -0
  46. package/dist/runtime/host-port-broker.d.ts +76 -0
  47. package/dist/runtime/host-port-broker.js +333 -0
  48. package/dist/runtime/host-runtime-contract.d.ts +56 -0
  49. package/dist/runtime/host-runtime-contract.js +709 -0
  50. package/dist/runtime/host-runtime-lease.d.ts +201 -0
  51. package/dist/runtime/host-runtime-lease.js +2314 -0
  52. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  53. package/dist/runtime/host-runtime-promotion.js +206 -0
  54. package/dist/runtime/memory-capacity.d.ts +68 -0
  55. package/dist/runtime/memory-capacity.js +121 -0
  56. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  57. package/dist/runtime/node-host-runtime-adapter.js +284 -0
  58. package/dist/runtime/pinned-directory-process.d.ts +4 -0
  59. package/dist/runtime/pinned-directory-process.js +20 -0
  60. package/dist/runtime/pinned-source-directory.d.ts +9 -0
  61. package/dist/runtime/pinned-source-directory.js +56 -0
  62. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  63. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  64. package/dist/runtime/prepared-runtime-identity.d.ts +3 -0
  65. package/dist/runtime/prepared-runtime-identity.js +191 -0
  66. package/dist/runtime/prepared-runtime-readiness.d.ts +22 -0
  67. package/dist/runtime/prepared-runtime-readiness.js +191 -0
  68. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  69. package/dist/runtime/runtime-isolation-lease.js +158 -0
  70. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  71. package/dist/runtime/workspace-resource-estimator.js +174 -0
  72. package/dist/sandbox/command-runner.d.ts +4 -0
  73. package/dist/sandbox/command-runner.js +47 -0
  74. package/dist/sandbox/contract.d.ts +5 -0
  75. package/dist/sandbox/contract.js +276 -0
  76. package/dist/sandbox/event-budget.d.ts +11 -0
  77. package/dist/sandbox/event-budget.js +27 -0
  78. package/dist/sandbox/image-policy.d.ts +35 -0
  79. package/dist/sandbox/image-policy.js +247 -0
  80. package/dist/sandbox/index.d.ts +5 -0
  81. package/dist/sandbox/index.js +5 -0
  82. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  83. package/dist/sandbox/lease-watchdog.js +67 -0
  84. package/dist/sandbox/preflight.d.ts +84 -0
  85. package/dist/sandbox/preflight.js +521 -0
  86. package/dist/sandbox/process.d.ts +20 -0
  87. package/dist/sandbox/process.js +209 -0
  88. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  89. package/dist/sandbox/provider-auth/codex.js +212 -0
  90. package/dist/sandbox/repository-lock.d.ts +5 -0
  91. package/dist/sandbox/repository-lock.js +121 -0
  92. package/dist/sandbox/runtime.d.ts +297 -0
  93. package/dist/sandbox/runtime.js +2338 -0
  94. package/dist/sandbox/sandbox.types.d.ts +93 -0
  95. package/dist/sandbox/sandbox.types.js +1 -0
  96. package/dist/sandbox/smoke.d.ts +76 -0
  97. package/dist/sandbox/smoke.js +380 -0
  98. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  99. package/dist/sandbox/worker-supervisor.js +36 -0
  100. package/dist/sandbox/worker.d.ts +1 -0
  101. package/dist/sandbox/worker.js +225 -0
  102. package/dist/sandbox/workspace.service.d.ts +13 -0
  103. package/dist/sandbox/workspace.service.js +72 -0
  104. package/dist/session/managed-attempt-tools.d.ts +21 -0
  105. package/dist/session/managed-attempt-tools.js +912 -0
  106. package/dist/session/managed-build-coordinator.d.ts +5 -0
  107. package/dist/session/managed-build-coordinator.js +105 -0
  108. package/dist/session/managed-build-receipt.d.ts +21 -0
  109. package/dist/session/managed-build-receipt.js +335 -0
  110. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  111. package/dist/session/phase-completion.artifacts.js +256 -26
  112. package/dist/session/phase-completion.d.ts +12 -2
  113. package/dist/session/phase-completion.helpers.d.ts +5 -0
  114. package/dist/session/phase-completion.helpers.js +51 -10
  115. package/dist/session/phase-completion.js +14 -3
  116. package/dist/session/phase-completion.prompts.d.ts +3 -3
  117. package/dist/session/phase-completion.prompts.js +24 -8
  118. package/dist/session/phase-completion.runtime.d.ts +13 -1
  119. package/dist/session/phase-completion.runtime.js +189 -8
  120. package/dist/session/phase-completion.schemas.js +117 -4
  121. package/dist/session/phase-completion.validation.d.ts +4 -1
  122. package/dist/session/phase-completion.validation.js +153 -22
  123. package/dist/session/pre-publication-validation.d.ts +47 -0
  124. package/dist/session/pre-publication-validation.js +249 -0
  125. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  126. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  127. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
  128. package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
  129. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  130. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
  131. package/dist/session/provider-cli/provider-cli.deterministic.js +68 -9
  132. package/dist/session/provider-cli/provider-cli.process.d.ts +26 -0
  133. package/dist/session/provider-cli/provider-cli.process.js +474 -23
  134. package/dist/session/provider-cli/provider-cli.prompts.d.ts +14 -2
  135. package/dist/session/provider-cli/provider-cli.prompts.js +147 -25
  136. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
  137. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  138. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  139. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  140. package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
  141. package/dist/session/provider-cli/provider-cli.service.d.ts +25 -2
  142. package/dist/session/provider-cli/provider-cli.service.js +636 -84
  143. package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
  144. package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
  145. package/dist/session/provider-cli/provider-output-state.js +91 -16
  146. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  147. package/dist/session/provider-cli/provider-usage.js +28 -5
  148. package/dist/session/session-redaction.d.ts +3 -0
  149. package/dist/session/session-redaction.js +38 -0
  150. package/dist/session/session.base.d.ts +6 -1
  151. package/dist/session/session.service.d.ts +8 -2
  152. package/dist/session/session.service.js +531 -42
  153. package/dist/session/session.types.d.ts +115 -1
  154. package/dist/session/session.types.js +194 -3
  155. package/dist/shared/format/format-bytes.d.ts +1 -0
  156. package/dist/shared/format/format-bytes.js +9 -0
  157. package/dist/shared/git/git.js +2 -1
  158. package/dist/shared/path/path.d.ts +1 -0
  159. package/dist/shared/path/path.js +21 -1
  160. package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
  161. package/dist/shared/process/dotnet-toolchain.js +179 -0
  162. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  163. package/dist/shared/process/host-process-recovery.js +78 -0
  164. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  165. package/dist/shared/process/host-process-supervisor.js +330 -0
  166. package/dist/shared/process/process.d.ts +2 -0
  167. package/dist/shared/process/process.js +11 -6
  168. package/dist/shared/provider-capabilities.d.ts +1 -0
  169. package/dist/shared/provider-capabilities.js +3 -0
  170. package/dist/shared/redaction/redaction.js +12 -2
  171. package/dist/version.d.ts +1 -1
  172. package/dist/version.js +1 -1
  173. package/dist/worktree/artifact-injector.d.ts +3 -2
  174. package/dist/worktree/artifact-injector.js +43 -4
  175. package/dist/worktree/dependency-warmer.d.ts +28 -1
  176. package/dist/worktree/dependency-warmer.js +308 -9
  177. package/dist/worktree/index.d.ts +2 -1
  178. package/dist/worktree/index.js +1 -0
  179. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  180. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  181. package/dist/worktree/worktree-paths.d.ts +4 -0
  182. package/dist/worktree/worktree-paths.js +30 -0
  183. package/dist/worktree/worktree.base.d.ts +3 -0
  184. package/dist/worktree/worktree.service.d.ts +14 -2
  185. package/dist/worktree/worktree.service.js +296 -47
  186. package/dist/worktree/worktree.types.d.ts +5 -1
  187. package/package.json +8 -4
  188. package/runtime/windows-job-supervisor.ps1 +444 -0
  189. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -1,12 +1,18 @@
1
+ import { type DotNetToolchainSelection } from "../shared/process/dotnet-toolchain.js";
1
2
  import type { BridgeService } from "../bridge/bridge.base.js";
2
3
  import type { BridgeServiceConfig } from "../bridge/bridge.types.js";
4
+ import type { CleanupMode } from "../cleanup/cleanup.types.js";
5
+ import type { CleanupService } from "../cleanup/cleanup.base.js";
3
6
  import type { SessionService } from "../session/session.base.js";
4
7
  import type { CliIo, CliService } from "./cli.base.js";
8
+ type DotNetToolchainConfigurator = (repoRoots: readonly string[]) => Promise<DotNetToolchainSelection | null>;
5
9
  export declare class ForgeBridgeCliService implements CliService {
6
10
  private readonly sessions;
7
11
  private readonly bridgeFactory;
8
12
  private readonly pairRunner;
9
- constructor(sessions?: SessionService, bridgeFactory?: (config: BridgeServiceConfig) => BridgeService, pairRunner?: PairRunnerClient);
13
+ private readonly cleanupFactory;
14
+ private readonly dotNetToolchainConfigurator;
15
+ constructor(sessions?: SessionService, bridgeFactory?: (config: BridgeServiceConfig) => BridgeService, pairRunner?: PairRunnerClient, cleanupFactory?: CleanupServiceFactory, dotNetToolchainConfigurator?: DotNetToolchainConfigurator);
10
16
  run(argv: readonly string[], io?: CliIo): Promise<number>;
11
17
  }
12
18
  export declare function parseArgs(argv: readonly string[]): {
@@ -34,6 +40,14 @@ export declare function parseArgs(argv: readonly string[]): {
34
40
  readonly ok: true;
35
41
  readonly command: "remove-repo";
36
42
  readonly label: string;
43
+ } | {
44
+ readonly ok: true;
45
+ readonly command: "server-show";
46
+ readonly config: BridgeServiceConfig;
47
+ } | {
48
+ readonly ok: true;
49
+ readonly command: "server-set";
50
+ readonly serverUrl: string;
37
51
  } | {
38
52
  readonly ok: true;
39
53
  readonly command: "status";
@@ -45,6 +59,35 @@ export declare function parseArgs(argv: readonly string[]): {
45
59
  readonly config: BridgeServiceConfig;
46
60
  readonly mode: CleanupMode;
47
61
  readonly force: boolean;
62
+ } | {
63
+ readonly ok: true;
64
+ readonly command: "sandbox-init";
65
+ readonly repoPath: string;
66
+ } | {
67
+ readonly ok: true;
68
+ readonly command: "sandbox-doctor";
69
+ readonly repoPath: string;
70
+ readonly requirePinnedBase: boolean;
71
+ } | {
72
+ readonly ok: true;
73
+ readonly command: "sandbox-smoke";
74
+ readonly repoPath: string;
75
+ readonly reportPath?: string;
76
+ readonly requirePinnedBase: boolean;
77
+ readonly probeLoopbackPort: boolean;
78
+ } | {
79
+ readonly ok: true;
80
+ readonly command: "sandbox-reap";
81
+ } | {
82
+ readonly ok: true;
83
+ readonly command: "sandbox-on";
84
+ readonly allowOpenPublic: boolean;
85
+ } | {
86
+ readonly ok: true;
87
+ readonly command: "sandbox-off";
88
+ } | {
89
+ readonly ok: true;
90
+ readonly command: "sandbox-status";
48
91
  } | {
49
92
  readonly ok: false;
50
93
  readonly error: string;
@@ -66,5 +109,5 @@ interface PairRunnerClaim {
66
109
  type PairRunnerClient = (args: PairArgs) => Promise<PairRunnerClaim>;
67
110
  export declare function parseCodexModelCatalog(raw: string): string[];
68
111
  export declare function parseCursorModelList(raw: string): string[];
69
- type CleanupMode = "stale" | "merged" | "all";
112
+ type CleanupServiceFactory = (stateRoot: string) => CleanupService;
70
113
  export {};
@@ -6,11 +6,19 @@ import { createInterface } from "node:readline/promises";
6
6
  import { promisify } from "node:util";
7
7
  import { resolveCursorAgentDirectLaunch } from "../shared/cursor-agent/cursor-agent.launch.js";
8
8
  import { buildWindowsCmdLaunch } from "../shared/process/windows-launch.js";
9
- import { redactSecrets, redactText } from "../shared/redaction/redaction.js";
9
+ import { configureDotNetToolchain } from "../shared/process/dotnet-toolchain.js";
10
+ import { supportsDraftImageAttachmentsFor } from "../shared/provider-capabilities.js";
11
+ import { redactText } from "../shared/redaction/redaction.js";
10
12
  import { defaultBridgeServiceConfig, ForgeBridgeService } from "../bridge/bridge.service.js";
11
13
  import { forgeBridgeVersion } from "../version.js";
12
- import { addRepoMapping, applyLocalConfigDefaults, providerStatusesFor, readLocalConfigSync, removeRepoMapping, resolveLocalConfigPath, writeLocalConfig } from "./local-config.js";
14
+ import { ManagedWorktreeCleanupService } from "../cleanup/cleanup.service.js";
15
+ import { addRepoMapping, applyLocalConfigDefaults, providerStatusesFor, readLocalConfigSync, removeRepoMapping, resolveLocalConfigPath, setSandboxPolicy, setServerUrl, writeLocalConfig } from "./local-config.js";
13
16
  import { BridgeSessionService } from "../session/session.service.js";
17
+ import { redactRunnerDispatchEvent, redactRunOnceResultPacket } from "../session/session-redaction.js";
18
+ import { validateDockerfilePolicy, validateSandboxContract } from "../sandbox/contract.js";
19
+ import { DockerPreflight } from "../sandbox/preflight.js";
20
+ import { DockerSandboxRuntime } from "../sandbox/runtime.js";
21
+ import { DockerSandboxSmoke } from "../sandbox/smoke.js";
14
22
  import { parseCompletionMcpArgs, runCompletionMcpServer } from "../session/phase-completion.js";
15
23
  import { failureResult, parseRunOnceDispatchPacket } from "../session/session.types.js";
16
24
  import { commandHelpText, helpText } from "./cli.help.js";
@@ -20,10 +28,14 @@ export class ForgeBridgeCliService {
20
28
  sessions;
21
29
  bridgeFactory;
22
30
  pairRunner;
23
- constructor(sessions = new BridgeSessionService(), bridgeFactory = (config) => new ForgeBridgeService(config), pairRunner = claimBridgeRunner) {
31
+ cleanupFactory;
32
+ dotNetToolchainConfigurator;
33
+ constructor(sessions = new BridgeSessionService(), bridgeFactory = (config) => new ForgeBridgeService(config), pairRunner = claimBridgeRunner, cleanupFactory = stateRoot => new ManagedWorktreeCleanupService(stateRoot), dotNetToolchainConfigurator = configureDotNetToolchain) {
24
34
  this.sessions = sessions;
25
35
  this.bridgeFactory = bridgeFactory;
26
36
  this.pairRunner = pairRunner;
37
+ this.cleanupFactory = cleanupFactory;
38
+ this.dotNetToolchainConfigurator = dotNetToolchainConfigurator;
27
39
  }
28
40
  async run(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
29
41
  if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
@@ -58,11 +70,29 @@ export class ForgeBridgeCliService {
58
70
  io.stderr.write(`forge-bridge connect requires ${missing[0]}.\n`);
59
71
  return 2;
60
72
  }
73
+ let dotNetToolchain;
74
+ try {
75
+ dotNetToolchain = await this.dotNetToolchainConfigurator([
76
+ ...config.repoMappings.map((mapping) => mapping.localPath?.trim()),
77
+ config.defaultRepoRoot.trim(),
78
+ ].filter((repoRoot) => Boolean(repoRoot)));
79
+ }
80
+ catch (error) {
81
+ io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
82
+ return 1;
83
+ }
84
+ if (dotNetToolchain) {
85
+ io.stdout.write(`Using .NET SDK ${dotNetToolchain.sdkVersion}: ${dotNetToolchain.executablePath}\n`);
86
+ }
61
87
  const controller = new AbortController();
62
88
  const stop = () => controller.abort("signal received");
63
89
  process.once("SIGINT", stop);
64
90
  process.once("SIGTERM", stop);
65
91
  try {
92
+ if (config.cleanupWorktrees && config.repoMappings.some(mapping => mapping.localPath?.trim())) {
93
+ io.stdout.write("Cleaning stale managed worktrees before accepting dispatches.\n");
94
+ await cleanupRepos(config, "stale", true, this.cleanupFactory(config.sandboxStateRoot), io);
95
+ }
66
96
  io.stdout.write(connectStartupText(config));
67
97
  await this.bridgeFactory(config).run(controller.signal);
68
98
  return 0;
@@ -88,6 +118,7 @@ export class ForgeBridgeCliService {
88
118
  if (args.command === "pair") {
89
119
  try {
90
120
  const current = readLocalConfigSync();
121
+ io.stdout.write(`Pairing with server: ${args.serverUrl}\n`);
91
122
  const code = await resolvePairingCode(args.code);
92
123
  const repoRoot = await resolvePairRepoRoot(args.repoRoot, current.defaultRepoRoot);
93
124
  await fs.mkdir(repoRoot, { recursive: true });
@@ -117,12 +148,66 @@ export class ForgeBridgeCliService {
117
148
  return 2;
118
149
  }
119
150
  }
151
+ if (args.command === "server-show") {
152
+ const saved = readLocalConfigSync().serverUrl ?? "not configured";
153
+ io.stdout.write(`Saved server: ${saved}\n`);
154
+ if (process.env.FORGE_URL?.trim()) {
155
+ io.stdout.write(`Effective server: ${process.env.FORGE_URL.trim()} (FORGE_URL override)\n`);
156
+ }
157
+ else {
158
+ io.stdout.write(`Effective server: ${args.config.serverUrl || "not configured"}\n`);
159
+ }
160
+ return 0;
161
+ }
162
+ if (args.command === "server-set") {
163
+ try {
164
+ const config = await setServerUrl(args.serverUrl);
165
+ io.stdout.write(`Saved Forge server: ${config.serverUrl}\n`);
166
+ io.stdout.write("Existing runner credentials were retained. Re-run `forge-bridge pair --server <url>` if this is a different Forge deployment.\n");
167
+ if (process.env.FORGE_URL?.trim()) {
168
+ io.stdout.write(`Warning: FORGE_URL is set to ${process.env.FORGE_URL.trim()} and overrides the saved server.\n`);
169
+ }
170
+ return 0;
171
+ }
172
+ catch (error) {
173
+ io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
174
+ return 2;
175
+ }
176
+ }
120
177
  if (args.command === "status") {
121
178
  await writeStatus(args.config, args.checkUpdates, io);
122
179
  return 0;
123
180
  }
124
181
  if (args.command === "cleanup") {
125
- return await cleanupRepos(args.config, args.mode, args.force, io);
182
+ return await cleanupRepos(args.config, args.mode, args.force, this.cleanupFactory(args.config.sandboxStateRoot), io);
183
+ }
184
+ if (args.command === "sandbox-init") {
185
+ return await initializeSandbox(args.repoPath, io);
186
+ }
187
+ if (args.command === "sandbox-doctor") {
188
+ return await diagnoseSandbox(args.repoPath, args.requirePinnedBase, io);
189
+ }
190
+ if (args.command === "sandbox-smoke") {
191
+ return await smokeSandbox(args.repoPath, args.reportPath, args.requirePinnedBase, args.probeLoopbackPort, io);
192
+ }
193
+ if (args.command === "sandbox-reap") {
194
+ return await reapSandboxes(io);
195
+ }
196
+ if (args.command === "sandbox-on") {
197
+ const saved = await setSandboxPolicy(true, args.allowOpenPublic);
198
+ io.stdout.write("Saved local Docker sandbox policy. Restart the bridge to apply it.\n");
199
+ writeSandboxPolicyStatus(saved, io);
200
+ return 0;
201
+ }
202
+ if (args.command === "sandbox-off") {
203
+ const saved = await setSandboxPolicy(false, false);
204
+ io.stdout.write("Disabled local Docker sandbox placement. Restart the bridge to apply it.\n");
205
+ writeSandboxPolicyStatus(saved, io);
206
+ return 0;
207
+ }
208
+ if (args.command === "sandbox-status") {
209
+ writeSandboxPolicyStatus(readLocalConfigSync(), io);
210
+ return 0;
126
211
  }
127
212
  if (args.command === "mcp") {
128
213
  const parsed = parseCompletionMcpArgs(args.argv);
@@ -142,12 +227,12 @@ export class ForgeBridgeCliService {
142
227
  emit: async (event) => {
143
228
  const line = {
144
229
  type: "runner_event",
145
- event: redactSecrets(event, redactionSecrets)
230
+ event: redactRunnerDispatchEvent(event, redactionSecrets)
146
231
  };
147
232
  io.stdout.write(`${JSON.stringify(line)}\n`);
148
233
  }
149
234
  });
150
- await writeJsonAtomic(args.resultPath, redactSecrets(result, redactionSecrets));
235
+ await writeJsonAtomic(args.resultPath, redactRunOnceResultPacket(result, redactionSecrets));
151
236
  return result.exitCode;
152
237
  }
153
238
  catch (error) {
@@ -233,6 +318,15 @@ export function parseArgs(argv) {
233
318
  }
234
319
  return { ok: true, command: "remove-repo", label: argv[1] };
235
320
  }
321
+ if (argv[0] === "server") {
322
+ if (argv.length === 1 || (argv[1] === "show" && argv.length === 2)) {
323
+ return { ok: true, command: "server-show", config: configuredBridgeServiceConfig() };
324
+ }
325
+ if (argv[1] === "set" && argv[2] && argv.length === 3) {
326
+ return { ok: true, command: "server-set", serverUrl: argv[2] };
327
+ }
328
+ return { ok: false, error: "Usage: forge-bridge server <show|set <url>>" };
329
+ }
236
330
  if (argv[0] === "status") {
237
331
  let checkUpdates = false;
238
332
  for (let index = 1; index < argv.length; index += 1) {
@@ -252,6 +346,9 @@ export function parseArgs(argv) {
252
346
  if (argv[0] === "mcp") {
253
347
  return { ok: true, command: "mcp", argv };
254
348
  }
349
+ if (argv[0] === "sandbox") {
350
+ return parseSandboxArgs(argv);
351
+ }
255
352
  if (argv[0] !== "run-once") {
256
353
  return {
257
354
  ok: false,
@@ -282,6 +379,230 @@ export function parseArgs(argv) {
282
379
  resultPath: path.resolve(resultPath)
283
380
  };
284
381
  }
382
+ function parseSandboxArgs(argv) {
383
+ const action = argv[1];
384
+ if (action === "on") {
385
+ let allowOpenPublic = false;
386
+ for (let index = 2; index < argv.length; index += 1) {
387
+ if (argv[index] === "--allow-open-public") {
388
+ allowOpenPublic = true;
389
+ }
390
+ else {
391
+ return { ok: false, error: `Unknown sandbox argument: ${argv[index]}` };
392
+ }
393
+ }
394
+ return { ok: true, command: "sandbox-on", allowOpenPublic };
395
+ }
396
+ if (action === "off" && argv.length === 2)
397
+ return { ok: true, command: "sandbox-off" };
398
+ if (action === "status" && argv.length === 2)
399
+ return { ok: true, command: "sandbox-status" };
400
+ let repoPath = process.cwd();
401
+ let reportPath;
402
+ let requirePinnedBase = false;
403
+ let probeLoopbackPort = false;
404
+ for (let index = 2; index < argv.length; index += 1) {
405
+ const arg = argv[index];
406
+ if (arg === "--repo") {
407
+ repoPath = argv[++index] ?? "";
408
+ if (!repoPath)
409
+ return { ok: false, error: "--repo requires a path." };
410
+ }
411
+ else if (arg === "--report" && action === "smoke") {
412
+ reportPath = argv[++index];
413
+ if (!reportPath)
414
+ return { ok: false, error: "--report requires a path." };
415
+ }
416
+ else if (arg === "--require-pinned-base" && (action === "doctor" || action === "smoke")) {
417
+ requirePinnedBase = true;
418
+ }
419
+ else if (arg === "--probe-loopback-port" && action === "smoke") {
420
+ probeLoopbackPort = true;
421
+ }
422
+ else {
423
+ return { ok: false, error: `Unknown sandbox argument: ${arg}` };
424
+ }
425
+ }
426
+ if (action === "init")
427
+ return { ok: true, command: "sandbox-init", repoPath: path.resolve(repoPath) };
428
+ if (action === "doctor")
429
+ return { ok: true, command: "sandbox-doctor", repoPath: path.resolve(repoPath), requirePinnedBase };
430
+ if (action === "smoke")
431
+ return {
432
+ ok: true,
433
+ command: "sandbox-smoke",
434
+ repoPath: path.resolve(repoPath),
435
+ reportPath: reportPath ? path.resolve(reportPath) : undefined,
436
+ requirePinnedBase,
437
+ probeLoopbackPort,
438
+ };
439
+ if (action === "reap" && argv.length === 2)
440
+ return { ok: true, command: "sandbox-reap" };
441
+ return { ok: false, error: "Usage: forge-bridge sandbox <on|off|status|init|doctor|smoke|reap> [--allow-open-public] [--repo <path>] [--report <path>] [--require-pinned-base] [--probe-loopback-port]" };
442
+ }
443
+ const sandboxContractTemplate = `schemaVersion: 1
444
+ workingDirectory: /workspace
445
+ commands:
446
+ install: npm ci
447
+ test: npm test
448
+ caches:
449
+ - name: npm
450
+ mode: readonly
451
+ services: []
452
+ environment: []
453
+ artifacts: []
454
+ logs: []
455
+ browser: false
456
+ `;
457
+ const sandboxDockerfileTemplate = `# Pin this base by digest before enabling sandbox_required.
458
+ FROM node:22.17.0-bookworm-slim
459
+ WORKDIR /workspace
460
+ `;
461
+ async function initializeSandbox(repoPath, io) {
462
+ const forgePath = path.join(repoPath, ".forge");
463
+ const files = [
464
+ ["forge.sandbox.yml", sandboxContractTemplate],
465
+ ["Dockerfile", sandboxDockerfileTemplate],
466
+ ["forge.env.example", "# List non-secret variable names in forge.sandbox.yml. Never commit secret values.\n"]
467
+ ];
468
+ await fs.mkdir(forgePath, { recursive: true });
469
+ let created = 0;
470
+ for (const [name, contents] of files) {
471
+ const target = path.join(forgePath, name);
472
+ try {
473
+ const handle = await fs.open(target, "wx", 0o600);
474
+ await handle.writeFile(contents);
475
+ await handle.close();
476
+ io.stdout.write(`Created ${path.relative(repoPath, target)}\n`);
477
+ created += 1;
478
+ }
479
+ catch (error) {
480
+ if (error.code !== "EEXIST")
481
+ throw error;
482
+ io.stdout.write(`Kept existing ${path.relative(repoPath, target)}\n`);
483
+ }
484
+ }
485
+ io.stdout.write(created > 0 ? "Sandbox scaffold is ready. Run `forge-bridge sandbox doctor`.\n" : "Sandbox scaffold already exists.\n");
486
+ return 0;
487
+ }
488
+ async function diagnoseSandbox(repoPath, requirePinnedBase, io) {
489
+ try {
490
+ const forgePath = path.join(repoPath, ".forge");
491
+ const contractSource = await fs.readFile(path.join(forgePath, "forge.sandbox.yml"), "utf8");
492
+ const dockerfile = await fs.readFile(path.join(forgePath, "Dockerfile"), "utf8");
493
+ const contract = validateSandboxContract(contractSource);
494
+ const warnings = validateDockerfilePolicy(dockerfile, requirePinnedBase);
495
+ io.stdout.write(`Contract: valid (${contract.digest})\n`);
496
+ for (const warning of warnings)
497
+ io.stdout.write(`Warning: ${warning}\n`);
498
+ const config = defaultBridgeServiceConfig();
499
+ const preflight = new DockerPreflight({
500
+ dockerExecutable: config.dockerExecutable,
501
+ dockerContext: config.dockerContext,
502
+ stateRoot: config.sandboxStateRoot,
503
+ maxConcurrentSandboxes: config.maxConcurrentSandboxes,
504
+ diskHighWatermarkPercent: config.diskHighWatermarkPercent,
505
+ diskLowWatermarkPercent: config.diskLowWatermarkPercent,
506
+ minimumWorkspaceBytes: config.minimumWorkspaceBytes,
507
+ hostMemoryReserveBytes: config.sandboxHostMemoryReserveBytes,
508
+ defaultWorkerMemoryLimitBytes: config.defaultSandboxWorkerMemoryLimitBytes,
509
+ defaultServiceMemoryLimitBytes: config.defaultSandboxServiceMemoryLimitBytes,
510
+ runtimeOverheadBytes: config.sandboxRuntimeOverheadBytes,
511
+ requireRootless: config.requireRootlessDocker
512
+ });
513
+ try {
514
+ const result = await preflight.refresh();
515
+ if (!result.healthy) {
516
+ io.stderr.write(`Docker: unavailable (${result.unavailableCode ?? "preflight_failed"}): ${result.unavailableReason ?? "unknown reason"}\n`);
517
+ return 1;
518
+ }
519
+ io.stdout.write(`Docker: healthy (${result.runtimeVersion}; capacity ${result.maxConcurrentSandboxes})\n`);
520
+ return 0;
521
+ }
522
+ finally {
523
+ await preflight.release();
524
+ }
525
+ }
526
+ catch (error) {
527
+ io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
528
+ return 1;
529
+ }
530
+ }
531
+ async function smokeSandbox(repoPath, reportPath, requirePinnedBase, probeLoopbackPort, io) {
532
+ try {
533
+ const result = await new DockerSandboxSmoke(defaultBridgeServiceConfig()).run({
534
+ repoPath,
535
+ reportPath,
536
+ requirePinnedBase,
537
+ probeLoopbackPort,
538
+ onProgress: (stage) => io.stdout.write(`Smoke: ${stage}...\n`),
539
+ });
540
+ const { report } = result;
541
+ if (report.repository.contractDigest)
542
+ io.stdout.write(`Contract: valid (${report.repository.contractDigest})\n`);
543
+ for (const warning of report.repository.policyWarnings)
544
+ io.stdout.write(`Warning: ${warning}\n`);
545
+ if (report.docker.runtimeVersion) {
546
+ io.stdout.write(`Docker: healthy (${report.docker.runtimeVersion}; capacity ${report.docker.capacity})\n`);
547
+ }
548
+ io.stdout.write(`Attempt: ${report.dispatchAttemptId}\n`);
549
+ io.stdout.write(`Resource lease: ${report.resourceLeaseId}\n`);
550
+ if (report.publishedPortProbe) {
551
+ io.stdout.write(`Loopback port: ${report.publishedPortProbe.hostIp}:${report.publishedPortProbe.hostPort} -> ${report.publishedPortProbe.containerPort}/tcp\n`);
552
+ }
553
+ io.stdout.write(`Cleanup: ${report.cleanup.verified ? "verified (zero exact-label resources)" : "failed"}\n`);
554
+ io.stdout.write(`Report: ${result.reportPath}\n`);
555
+ if (report.status === "passed") {
556
+ io.stdout.write("Sandbox smoke: PASS\n");
557
+ return 0;
558
+ }
559
+ io.stderr.write(`Sandbox smoke: FAIL (${report.failure?.code ?? "sandbox_smoke_failed"} at ${report.failure?.stage ?? "unknown"}): ${report.failure?.message ?? "unknown failure"}\n`);
560
+ return 1;
561
+ }
562
+ catch (error) {
563
+ io.stderr.write(`Sandbox smoke could not write its report: ${error instanceof Error ? error.message : String(error)}\n`);
564
+ return 1;
565
+ }
566
+ }
567
+ async function reapSandboxes(io) {
568
+ const config = defaultBridgeServiceConfig();
569
+ if (!/^[0-9a-f-]{32,36}$/iu.test(config.runnerId)) {
570
+ io.stderr.write("FORGE_RUNNER_ID must identify the runner whose exact-labeled resources may be reaped.\n");
571
+ return 2;
572
+ }
573
+ const preflight = new DockerPreflight({
574
+ dockerExecutable: config.dockerExecutable,
575
+ dockerContext: config.dockerContext,
576
+ stateRoot: config.sandboxStateRoot,
577
+ maxConcurrentSandboxes: config.maxConcurrentSandboxes,
578
+ diskHighWatermarkPercent: config.diskHighWatermarkPercent,
579
+ diskLowWatermarkPercent: config.diskLowWatermarkPercent,
580
+ minimumWorkspaceBytes: config.minimumWorkspaceBytes,
581
+ hostMemoryReserveBytes: config.sandboxHostMemoryReserveBytes,
582
+ defaultWorkerMemoryLimitBytes: config.defaultSandboxWorkerMemoryLimitBytes,
583
+ defaultServiceMemoryLimitBytes: config.defaultSandboxServiceMemoryLimitBytes,
584
+ runtimeOverheadBytes: config.sandboxRuntimeOverheadBytes,
585
+ requireRootless: config.requireRootlessDocker
586
+ });
587
+ try {
588
+ const result = await preflight.refresh();
589
+ if (!result.healthy && result.unavailableCode !== "docker_disk_low") {
590
+ io.stderr.write(`Docker: unavailable (${result.unavailableCode ?? "preflight_failed"}): ${result.unavailableReason ?? "unknown reason"}\n`);
591
+ return 1;
592
+ }
593
+ const runtime = new DockerSandboxRuntime(config);
594
+ await runtime.reap({ diskLow: result.diskLow });
595
+ io.stdout.write(`Sandbox reaper completed for runner ${config.runnerId}.\n`);
596
+ return 0;
597
+ }
598
+ catch (error) {
599
+ io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
600
+ return 1;
601
+ }
602
+ finally {
603
+ await preflight.release();
604
+ }
605
+ }
285
606
  function parseConnectArgs(argv) {
286
607
  let serverUrl = null;
287
608
  let runnerId = null;
@@ -414,7 +735,7 @@ function parsePairArgs(argv) {
414
735
  name: name?.trim() || defaultName,
415
736
  fingerprint: fingerprint?.trim() || `local-bridge:${os.hostname()}:${user}`,
416
737
  repoRoot: repoRoot?.trim() ? path.resolve(repoRoot) : undefined,
417
- cleanupWorktrees: cleanupWorktrees ?? localConfig.cleanupWorktrees ?? false,
738
+ cleanupWorktrees: cleanupWorktrees ?? defaultConfig.cleanupWorktrees,
418
739
  cliProviders: cliProviders.length > 0 ? cliProviders : localConfig.cliProviders && localConfig.cliProviders.length > 0 ? [...localConfig.cliProviders] : ["codex"],
419
740
  projectLabels: projectLabels.length > 0 ? projectLabels : localConfig.projectLabels ? [...localConfig.projectLabels] : []
420
741
  };
@@ -470,7 +791,7 @@ async function claimBridgeRunner(args) {
470
791
  })
471
792
  });
472
793
  if (!response.ok) {
473
- throw new Error(`Pairing failed (${response.status}): ${await response.text()}`);
794
+ throw new Error(formatPairingFailure(response.status, args.serverUrl, await response.text()));
474
795
  }
475
796
  const value = await response.json();
476
797
  if (!value.runnerId || !value.deviceToken) {
@@ -500,7 +821,7 @@ function pairedConfig(current, args, claim) {
500
821
  supportsRunnerAuthoredPhaseArtifacts: current.supportsRunnerAuthoredPhaseArtifacts ?? true,
501
822
  supportsDraftRunOnce: current.supportsDraftRunOnce ?? true,
502
823
  supportsDraftAttachments: current.supportsDraftAttachments ?? true,
503
- supportsDraftImageAttachments: current.supportsDraftImageAttachments ?? false,
824
+ supportsDraftImageAttachments: supportsDraftImageAttachmentsFor(args.cliProviders),
504
825
  repoMappings: current.repoMappings ?? []
505
826
  };
506
827
  }
@@ -545,6 +866,12 @@ async function resolvePairRepoRoot(repoRoot, configuredRoot) {
545
866
  function normalizePairingCode(code) {
546
867
  return code?.replace(/[\s-]/g, "").trim() ?? "";
547
868
  }
869
+ function formatPairingFailure(status, serverUrl, body) {
870
+ const hint = body.includes("runner.invalid_pairing_code")
871
+ ? " Make sure the code was generated by the same Forge server shown above."
872
+ : "";
873
+ return `Pairing failed (${status}) against ${serverUrl}: ${body}${hint}`;
874
+ }
548
875
  function normalizeServerUrl(serverUrl) {
549
876
  return serverUrl.endsWith("/") ? serverUrl : `${serverUrl}/`;
550
877
  }
@@ -563,11 +890,28 @@ function connectStartupText(config) {
563
890
  ` repo folder: ${config.defaultRepoRoot || "not configured"}`,
564
891
  ` providers: ${formatList(config.cliProviders)}`,
565
892
  ` cleanup worktrees: ${config.cleanupWorktrees ? "enabled" : "disabled"}`,
893
+ ` Docker Desktop auto-recovery: ${config.dockerDesktopRecoveryEnabled ? "enabled" : "disabled"}`,
894
+ ` open public sandbox networking: ${config.allowOpenPublicSandbox ? "enabled" : "disabled"}`,
566
895
  "",
567
896
  "Press Ctrl+C to stop.",
568
897
  "",
569
898
  ].join("\n");
570
899
  }
900
+ function writeSandboxPolicyStatus(local, io) {
901
+ const effective = configuredBridgeServiceConfig({}, local);
902
+ io.stdout.write(`Config: ${resolveLocalConfigPath()}\n`);
903
+ io.stdout.write(`Docker sandbox placement: ${effective.dockerEnabled ? "on" : "off"}\n`);
904
+ io.stdout.write(`Open public sandbox networking: ${effective.allowOpenPublicSandbox ? "allowed" : "denied"}\n`);
905
+ if (process.env.FORGE_DOCKER_SANDBOX !== undefined) {
906
+ io.stdout.write("Docker setting source: FORGE_DOCKER_SANDBOX override\n");
907
+ }
908
+ if (process.env.FORGE_ALLOW_OPEN_PUBLIC_SANDBOX !== undefined) {
909
+ io.stdout.write("Open public setting source: FORGE_ALLOW_OPEN_PUBLIC_SANDBOX override\n");
910
+ }
911
+ if (effective.allowOpenPublicSandbox) {
912
+ io.stdout.write("Warning: open_public permits unrestricted outbound network access from developer sandboxes.\n");
913
+ }
914
+ }
571
915
  function formatList(values) {
572
916
  return values.length > 0 ? values.join(", ") : "none";
573
917
  }
@@ -604,7 +948,7 @@ async function writeStatus(config, checkUpdates, io) {
604
948
  io.stdout.write("Update check: no update channel configured.\n");
605
949
  }
606
950
  }
607
- async function cleanupRepos(config, mode, force, io) {
951
+ async function cleanupRepos(config, mode, force, cleanup, io) {
608
952
  const repos = config.repoMappings.filter((mapping) => mapping.localPath?.trim());
609
953
  if (repos.length === 0) {
610
954
  io.stdout.write("No repos configured.\n");
@@ -613,20 +957,28 @@ async function cleanupRepos(config, mode, force, io) {
613
957
  let failures = 0;
614
958
  for (const repo of repos) {
615
959
  const repoPath = path.resolve(repo.localPath);
616
- const worktreeRoot = path.join(path.dirname(repoPath), ".forge-worktrees", path.basename(repoPath));
617
- if (!force) {
618
- const action = mode === "all" ? "remove" : "prune";
619
- io.stdout.write(`[dry-run] Would ${action} ${worktreeRoot}\n`);
620
- continue;
621
- }
622
960
  try {
623
- if (mode === "all") {
624
- await fs.rm(worktreeRoot, { recursive: true, force: true });
625
- await runGit(repoPath, ["worktree", "prune"]);
626
- io.stdout.write(`Removed ${worktreeRoot}\n`);
961
+ const result = await cleanup.run({
962
+ repoRoot: repoPath,
963
+ allowedRepoRoot: config.defaultRepoRoot || undefined,
964
+ mode,
965
+ force,
966
+ });
967
+ if (!force) {
968
+ const action = mode === "all"
969
+ ? "remove"
970
+ : mode === "stale"
971
+ ? `remove ${result.candidateWorktrees} stale managed ${result.candidateWorktrees === 1 ? "worktree" : "worktrees"} from`
972
+ : "prune";
973
+ io.stdout.write(`[dry-run] Would ${action} ${result.worktreeRoot}\n`);
974
+ }
975
+ else if (mode === "all") {
976
+ io.stdout.write(`Removed ${result.worktreeRoot}\n`);
977
+ }
978
+ else if (mode === "stale") {
979
+ io.stdout.write(`Removed ${result.removedWorktrees} stale managed ${result.removedWorktrees === 1 ? "worktree" : "worktrees"} from ${result.worktreeRoot}\n`);
627
980
  }
628
981
  else {
629
- await runGit(repoPath, ["worktree", "prune"]);
630
982
  io.stdout.write(`Pruned stale worktree metadata for ${repoPath}\n`);
631
983
  }
632
984
  }
@@ -775,8 +1127,8 @@ function uniqueModels(models) {
775
1127
  function isRecord(value) {
776
1128
  return typeof value === "object" && value !== null;
777
1129
  }
778
- async function withDetectedCliProviders(config) {
779
- const detected = await detectCliProviders(configuredProviderCandidates(config.cliProviders));
1130
+ async function withDetectedCliProviders(config, candidates = config.cliProviders) {
1131
+ const detected = await detectCliProviders(uniqueProviders(candidates));
780
1132
  const found = detected.filter((result) => result.found);
781
1133
  if (found.length === 0) {
782
1134
  return config;
@@ -800,6 +1152,7 @@ async function withDetectedCliProviders(config) {
800
1152
  };
801
1153
  }
802
1154
  async function withDetectedPairCliProviders(args) {
1155
+ const configured = configuredProviderCandidates(args.cliProviders);
803
1156
  const detectedConfig = await withDetectedCliProviders({
804
1157
  ...defaultBridgeServiceConfig({
805
1158
  serverUrl: args.serverUrl,
@@ -809,7 +1162,7 @@ async function withDetectedPairCliProviders(args) {
809
1162
  cleanupWorktrees: args.cleanupWorktrees,
810
1163
  defaultRepoRoot: args.repoRoot ?? defaultBridgeServiceConfig().defaultRepoRoot
811
1164
  })
812
- });
1165
+ }, configured);
813
1166
  return {
814
1167
  ...args,
815
1168
  cliProviders: detectedConfig.cliProviders
@@ -824,6 +1177,3 @@ function uniqueProviders(providers) {
824
1177
  .filter((provider) => provider.length > 0)
825
1178
  .filter((provider, index, all) => all.findIndex((candidate) => candidate.toLowerCase() === provider.toLowerCase()) === index);
826
1179
  }
827
- async function runGit(cwd, args) {
828
- await execFileAsync("git", args, { cwd, timeout: 10_000 });
829
- }
@@ -7,6 +7,9 @@ export interface BridgeLocalConfig {
7
7
  readonly deviceName?: string;
8
8
  readonly defaultRepoRoot?: string;
9
9
  readonly cleanupWorktrees?: boolean;
10
+ readonly dockerEnabled?: boolean;
11
+ readonly dockerDesktopRecoveryEnabled?: boolean;
12
+ readonly allowOpenPublicSandbox?: boolean;
10
13
  readonly cliProviders?: readonly string[];
11
14
  readonly providerStatuses?: Readonly<Record<string, BridgeProviderStatus>>;
12
15
  readonly projectLabels?: readonly string[];
@@ -34,4 +37,6 @@ export declare function removeRepoMapping(label: string): Promise<{
34
37
  readonly config: BridgeLocalConfig;
35
38
  readonly removed: boolean;
36
39
  }>;
40
+ export declare function setServerUrl(serverUrl: string): Promise<BridgeLocalConfig>;
41
+ export declare function setSandboxPolicy(dockerEnabled: boolean, allowOpenPublicSandbox: boolean): Promise<BridgeLocalConfig>;
37
42
  export declare function providerStatusesFor(providers: readonly string[]): Readonly<Record<string, BridgeProviderStatus>>;