@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.3-dev.fbe15023cdfe

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 (150) hide show
  1. package/README.md +78 -0
  2. package/dist/bridge/bridge.base.d.ts +6 -1
  3. package/dist/bridge/bridge.service.d.ts +60 -3
  4. package/dist/bridge/bridge.service.js +970 -24
  5. package/dist/bridge/bridge.types.d.ts +172 -1
  6. package/dist/cleanup/cleanup.service.d.ts +13 -2
  7. package/dist/cleanup/cleanup.service.js +435 -3
  8. package/dist/cleanup/cleanup.types.d.ts +10 -0
  9. package/dist/cleanup/index.d.ts +1 -1
  10. package/dist/cleanup/index.js +1 -1
  11. package/dist/cleanup/internals.d.ts +2 -2
  12. package/dist/cleanup/internals.js +1 -1
  13. package/dist/cli/cli.help.d.ts +2 -0
  14. package/dist/cli/cli.help.js +217 -0
  15. package/dist/cli/cli.service.d.ts +42 -2
  16. package/dist/cli/cli.service.js +356 -205
  17. package/dist/cli/local-config.d.ts +6 -0
  18. package/dist/cli/local-config.js +47 -3
  19. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  20. package/dist/runtime/attempt-node-runtime-launcher.js +101 -0
  21. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  22. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  23. package/dist/runtime/attempt-process-supervisor.d.ts +86 -0
  24. package/dist/runtime/attempt-process-supervisor.js +350 -0
  25. package/dist/runtime/generated-output-reset.d.ts +10 -0
  26. package/dist/runtime/generated-output-reset.js +103 -0
  27. package/dist/runtime/host-listener-inspector.d.ts +44 -0
  28. package/dist/runtime/host-listener-inspector.js +324 -0
  29. package/dist/runtime/host-port-broker.d.ts +76 -0
  30. package/dist/runtime/host-port-broker.js +333 -0
  31. package/dist/runtime/host-runtime-contract.d.ts +47 -0
  32. package/dist/runtime/host-runtime-contract.js +700 -0
  33. package/dist/runtime/host-runtime-lease.d.ts +133 -0
  34. package/dist/runtime/host-runtime-lease.js +1460 -0
  35. package/dist/runtime/host-runtime-promotion.d.ts +23 -0
  36. package/dist/runtime/host-runtime-promotion.js +159 -0
  37. package/dist/runtime/node-host-runtime-adapter.d.ts +51 -0
  38. package/dist/runtime/node-host-runtime-adapter.js +268 -0
  39. package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
  40. package/dist/runtime/playwright-host-runtime-adapter.js +220 -0
  41. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  42. package/dist/runtime/runtime-isolation-lease.js +158 -0
  43. package/dist/sandbox/command-runner.d.ts +4 -0
  44. package/dist/sandbox/command-runner.js +47 -0
  45. package/dist/sandbox/contract.d.ts +5 -0
  46. package/dist/sandbox/contract.js +276 -0
  47. package/dist/sandbox/event-budget.d.ts +11 -0
  48. package/dist/sandbox/event-budget.js +27 -0
  49. package/dist/sandbox/image-policy.d.ts +35 -0
  50. package/dist/sandbox/image-policy.js +247 -0
  51. package/dist/sandbox/index.d.ts +5 -0
  52. package/dist/sandbox/index.js +5 -0
  53. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  54. package/dist/sandbox/lease-watchdog.js +67 -0
  55. package/dist/sandbox/preflight.d.ts +54 -0
  56. package/dist/sandbox/preflight.js +335 -0
  57. package/dist/sandbox/process.d.ts +20 -0
  58. package/dist/sandbox/process.js +209 -0
  59. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  60. package/dist/sandbox/provider-auth/codex.js +212 -0
  61. package/dist/sandbox/repository-lock.d.ts +5 -0
  62. package/dist/sandbox/repository-lock.js +119 -0
  63. package/dist/sandbox/runtime.d.ts +297 -0
  64. package/dist/sandbox/runtime.js +2338 -0
  65. package/dist/sandbox/sandbox.types.d.ts +86 -0
  66. package/dist/sandbox/sandbox.types.js +1 -0
  67. package/dist/sandbox/smoke.d.ts +76 -0
  68. package/dist/sandbox/smoke.js +380 -0
  69. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  70. package/dist/sandbox/worker-supervisor.js +36 -0
  71. package/dist/sandbox/worker.d.ts +1 -0
  72. package/dist/sandbox/worker.js +225 -0
  73. package/dist/sandbox/workspace.service.d.ts +13 -0
  74. package/dist/sandbox/workspace.service.js +72 -0
  75. package/dist/session/managed-attempt-tools.d.ts +12 -0
  76. package/dist/session/managed-attempt-tools.js +561 -0
  77. package/dist/session/phase-completion.artifacts.d.ts +10 -0
  78. package/dist/session/phase-completion.artifacts.js +627 -0
  79. package/dist/session/phase-completion.d.ts +43 -42
  80. package/dist/session/phase-completion.helpers.d.ts +112 -0
  81. package/dist/session/phase-completion.helpers.js +684 -0
  82. package/dist/session/phase-completion.js +114 -3007
  83. package/dist/session/phase-completion.prompts.d.ts +14 -0
  84. package/dist/session/phase-completion.prompts.js +234 -0
  85. package/dist/session/phase-completion.runtime.d.ts +47 -0
  86. package/dist/session/phase-completion.runtime.js +731 -0
  87. package/dist/session/phase-completion.schemas.d.ts +3 -0
  88. package/dist/session/phase-completion.schemas.js +812 -0
  89. package/dist/session/phase-completion.validation.d.ts +37 -0
  90. package/dist/session/phase-completion.validation.js +828 -0
  91. package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
  92. package/dist/session/provider-cli/ci-repair-readiness.js +90 -0
  93. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +93 -0
  94. package/dist/session/provider-cli/provider-cli.artifacts.js +778 -0
  95. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  96. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  97. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  98. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  99. package/dist/session/provider-cli/provider-cli.deterministic.js +375 -0
  100. package/dist/session/provider-cli/provider-cli.process.d.ts +48 -0
  101. package/dist/session/provider-cli/provider-cli.process.js +474 -0
  102. package/dist/session/provider-cli/provider-cli.prompts.d.ts +47 -0
  103. package/dist/session/provider-cli/provider-cli.prompts.js +595 -0
  104. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  105. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  106. package/dist/session/provider-cli/provider-cli.runtime.d.ts +9 -0
  107. package/dist/session/provider-cli/provider-cli.runtime.js +41 -0
  108. package/dist/session/provider-cli/provider-cli.service.d.ts +22 -53
  109. package/dist/session/provider-cli/provider-cli.service.js +587 -2530
  110. package/dist/session/provider-cli/provider-cli.types.d.ts +6 -0
  111. package/dist/session/provider-cli/provider-output-state.d.ts +22 -0
  112. package/dist/session/provider-cli/provider-output-state.js +473 -0
  113. package/dist/session/session-redaction.d.ts +3 -0
  114. package/dist/session/session-redaction.js +23 -0
  115. package/dist/session/session.base.d.ts +6 -1
  116. package/dist/session/session.service.d.ts +8 -2
  117. package/dist/session/session.service.js +463 -58
  118. package/dist/session/session.types.d.ts +177 -1
  119. package/dist/session/session.types.js +352 -3
  120. package/dist/shared/path/path.js +3 -1
  121. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  122. package/dist/shared/process/host-process-recovery.js +78 -0
  123. package/dist/shared/process/host-process-supervisor.d.ts +57 -0
  124. package/dist/shared/process/host-process-supervisor.js +282 -0
  125. package/dist/shared/process/process.d.ts +1 -0
  126. package/dist/shared/process/process.js +1 -0
  127. package/dist/shared/process/windows-launch.d.ts +8 -0
  128. package/dist/shared/process/windows-launch.js +41 -0
  129. package/dist/shared/provider-capabilities.d.ts +1 -0
  130. package/dist/shared/provider-capabilities.js +3 -0
  131. package/dist/shared/redaction/redaction.js +10 -3
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/dist/worktree/artifact-injector.d.ts +3 -2
  135. package/dist/worktree/artifact-injector.js +82 -10
  136. package/dist/worktree/dependency-warmer.d.ts +21 -1
  137. package/dist/worktree/dependency-warmer.js +138 -5
  138. package/dist/worktree/index.d.ts +1 -0
  139. package/dist/worktree/index.js +1 -0
  140. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  141. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  142. package/dist/worktree/worktree-paths.d.ts +4 -0
  143. package/dist/worktree/worktree-paths.js +30 -0
  144. package/dist/worktree/worktree.base.d.ts +3 -0
  145. package/dist/worktree/worktree.service.d.ts +9 -3
  146. package/dist/worktree/worktree.service.js +100 -52
  147. package/dist/worktree/worktree.types.d.ts +3 -1
  148. package/package.json +6 -3
  149. package/runtime/windows-job-supervisor.ps1 +356 -0
  150. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -7,6 +7,8 @@ export interface BridgeLocalConfig {
7
7
  readonly deviceName?: string;
8
8
  readonly defaultRepoRoot?: string;
9
9
  readonly cleanupWorktrees?: boolean;
10
+ readonly dockerEnabled?: boolean;
11
+ readonly allowOpenPublicSandbox?: boolean;
10
12
  readonly cliProviders?: readonly string[];
11
13
  readonly providerStatuses?: Readonly<Record<string, BridgeProviderStatus>>;
12
14
  readonly projectLabels?: readonly string[];
@@ -17,6 +19,8 @@ export interface BridgeLocalConfig {
17
19
  readonly supportsManagedForgeArtifacts?: boolean;
18
20
  readonly supportsRunnerAuthoredPhaseArtifacts?: boolean;
19
21
  readonly supportsDraftRunOnce?: boolean;
22
+ readonly supportsDraftAttachments?: boolean;
23
+ readonly supportsDraftImageAttachments?: boolean;
20
24
  }
21
25
  export interface LocalRepoMapping extends BridgeRepoMapping {
22
26
  readonly label: string;
@@ -32,4 +36,6 @@ export declare function removeRepoMapping(label: string): Promise<{
32
36
  readonly config: BridgeLocalConfig;
33
37
  readonly removed: boolean;
34
38
  }>;
39
+ export declare function setServerUrl(serverUrl: string): Promise<BridgeLocalConfig>;
40
+ export declare function setSandboxPolicy(dockerEnabled: boolean, allowOpenPublicSandbox: boolean): Promise<BridgeLocalConfig>;
35
41
  export declare function providerStatusesFor(providers: readonly string[]): Readonly<Record<string, BridgeProviderStatus>>;
@@ -45,6 +45,12 @@ export function applyLocalConfigDefaults(config, local) {
45
45
  deviceName: process.env.FORGE_DEVICE_NAME ?? local.deviceName ?? config.deviceName,
46
46
  defaultRepoRoot: process.env.FORGE_REPO_ROOT ?? local.defaultRepoRoot ?? config.defaultRepoRoot,
47
47
  cleanupWorktrees: local.cleanupWorktrees ?? config.cleanupWorktrees,
48
+ dockerEnabled: process.env.FORGE_DOCKER_SANDBOX === undefined
49
+ ? local.dockerEnabled ?? config.dockerEnabled
50
+ : config.dockerEnabled,
51
+ allowOpenPublicSandbox: process.env.FORGE_ALLOW_OPEN_PUBLIC_SANDBOX === undefined
52
+ ? local.allowOpenPublicSandbox ?? config.allowOpenPublicSandbox
53
+ : config.allowOpenPublicSandbox,
48
54
  cliProviders,
49
55
  providerStatuses: normalizeProviderStatuses(local.providerStatuses) ?? providerStatusesFor(cliProviders),
50
56
  projectLabels: local.projectLabels ?? config.projectLabels,
@@ -54,7 +60,9 @@ export function applyLocalConfigDefaults(config, local) {
54
60
  supportsManagedWorktrees: local.supportsManagedWorktrees ?? config.supportsManagedWorktrees,
55
61
  supportsManagedForgeArtifacts: local.supportsManagedForgeArtifacts ?? config.supportsManagedForgeArtifacts,
56
62
  supportsRunnerAuthoredPhaseArtifacts: local.supportsRunnerAuthoredPhaseArtifacts ?? config.supportsRunnerAuthoredPhaseArtifacts,
57
- supportsDraftRunOnce: local.supportsDraftRunOnce ?? config.supportsDraftRunOnce
63
+ supportsDraftRunOnce: local.supportsDraftRunOnce ?? config.supportsDraftRunOnce,
64
+ supportsDraftAttachments: local.supportsDraftAttachments ?? config.supportsDraftAttachments,
65
+ supportsDraftImageAttachments: local.supportsDraftImageAttachments ?? config.supportsDraftImageAttachments
58
66
  };
59
67
  }
60
68
  export async function addRepoMapping(label, repoPath) {
@@ -86,6 +94,19 @@ export async function removeRepoMapping(label) {
86
94
  await writeLocalConfig(next);
87
95
  return { config: next, removed: after.length !== before.length };
88
96
  }
97
+ export async function setServerUrl(serverUrl) {
98
+ const normalized = normalizeServerUrl(serverUrl);
99
+ const current = await readLocalConfig();
100
+ const next = normalizeLocalConfig({ ...current, serverUrl: normalized });
101
+ await writeLocalConfig(next);
102
+ return next;
103
+ }
104
+ export async function setSandboxPolicy(dockerEnabled, allowOpenPublicSandbox) {
105
+ const current = await readLocalConfig();
106
+ const next = normalizeLocalConfig({ ...current, dockerEnabled, allowOpenPublicSandbox });
107
+ await writeLocalConfig(next);
108
+ return next;
109
+ }
89
110
  export function providerStatusesFor(providers) {
90
111
  return Object.fromEntries(providers.map((provider) => [provider, { signedIn: true, version: "", models: configuredModelsFor(provider) }]));
91
112
  }
@@ -104,6 +125,8 @@ function normalizeLocalConfig(config) {
104
125
  deviceName: cleanString(config.deviceName),
105
126
  defaultRepoRoot: cleanString(config.defaultRepoRoot),
106
127
  cleanupWorktrees: config.cleanupWorktrees,
128
+ dockerEnabled: config.dockerEnabled,
129
+ allowOpenPublicSandbox: config.allowOpenPublicSandbox,
107
130
  cliProviders: cleanList(config.cliProviders),
108
131
  providerStatuses: normalizeProviderStatuses(config.providerStatuses),
109
132
  projectLabels: cleanList(config.projectLabels),
@@ -113,7 +136,9 @@ function normalizeLocalConfig(config) {
113
136
  supportsManagedWorktrees: config.supportsManagedWorktrees,
114
137
  supportsManagedForgeArtifacts: config.supportsManagedForgeArtifacts,
115
138
  supportsRunnerAuthoredPhaseArtifacts: config.supportsRunnerAuthoredPhaseArtifacts,
116
- supportsDraftRunOnce: config.supportsDraftRunOnce
139
+ supportsDraftRunOnce: config.supportsDraftRunOnce,
140
+ supportsDraftAttachments: config.supportsDraftAttachments,
141
+ supportsDraftImageAttachments: config.supportsDraftImageAttachments
117
142
  };
118
143
  }
119
144
  function emptyConfig() {
@@ -196,10 +221,11 @@ function normalizeQaAppLaunchTarget(value) {
196
221
  command: cleanString(value.command),
197
222
  working_directory: cleanString(value.working_directory),
198
223
  ready_url: cleanString(value.ready_url),
224
+ managed_runtime_port: cleanString(value.managed_runtime_port),
199
225
  routes,
200
226
  notes: cleanString(value.notes)
201
227
  };
202
- return normalized.url || normalized.command || normalized.ready_url || routes?.length
228
+ return normalized.url || normalized.command || normalized.ready_url || normalized.managed_runtime_port
203
229
  ? normalized
204
230
  : undefined;
205
231
  }
@@ -207,6 +233,24 @@ function cleanString(value) {
207
233
  const clean = value?.trim();
208
234
  return clean ? clean : undefined;
209
235
  }
236
+ function normalizeServerUrl(value) {
237
+ const clean = value.trim();
238
+ let parsed;
239
+ try {
240
+ parsed = new URL(clean);
241
+ }
242
+ catch {
243
+ throw new Error(`Invalid Forge server URL: ${value}`);
244
+ }
245
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
246
+ throw new Error("Forge server URL must use http or https.");
247
+ }
248
+ if (parsed.username || parsed.password || parsed.search || parsed.hash) {
249
+ throw new Error("Forge server URL cannot include credentials, a query string, or a fragment.");
250
+ }
251
+ parsed.pathname = parsed.pathname.replace(/\/+$/u, "") || "/";
252
+ return parsed.toString().replace(/\/$/u, "");
253
+ }
210
254
  function cleanList(values) {
211
255
  const clean = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
212
256
  return clean.length > 0 ? clean : undefined;
@@ -0,0 +1,17 @@
1
+ import type { AttemptProcessChildHandle, AttemptProcessSupervisor } from "./attempt-process-supervisor.js";
2
+ import type { HostPortLeaseOwner } from "./host-port-broker.js";
3
+ import { HostListenerInspector } from "./host-listener-inspector.js";
4
+ import type { HostListenerOwnership, NodeRuntimeCommandLauncher, NodeRuntimeLaunchRequest, NodeRuntimeReadinessDiagnostics } from "./node-host-runtime-adapter.js";
5
+ /** Launches one explicitly configured HTTP project beneath the attempt coordinator. */
6
+ export declare class AttemptNodeRuntimeLauncher implements NodeRuntimeCommandLauncher<AttemptProcessChildHandle> {
7
+ private readonly supervisor;
8
+ private readonly owner;
9
+ private readonly logDirectory;
10
+ private readonly listenerInspector;
11
+ private readonly diagnosticsByChild;
12
+ constructor(supervisor: AttemptProcessSupervisor, owner: HostPortLeaseOwner, logDirectory: string, listenerInspector?: HostListenerInspector);
13
+ launch(request: NodeRuntimeLaunchRequest, signal?: AbortSignal): Promise<AttemptProcessChildHandle>;
14
+ inspectListener(child: AttemptProcessChildHandle, request: NodeRuntimeLaunchRequest): Promise<HostListenerOwnership>;
15
+ stop(child: AttemptProcessChildHandle): Promise<void>;
16
+ readinessDiagnostics(child: AttemptProcessChildHandle): Promise<NodeRuntimeReadinessDiagnostics>;
17
+ }
@@ -0,0 +1,101 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { HostListenerInspector } from "./host-listener-inspector.js";
5
+ const diagnosticTailLength = 4_000;
6
+ /** Launches one explicitly configured HTTP project beneath the attempt coordinator. */
7
+ export class AttemptNodeRuntimeLauncher {
8
+ supervisor;
9
+ owner;
10
+ logDirectory;
11
+ listenerInspector;
12
+ diagnosticsByChild = new WeakMap();
13
+ constructor(supervisor, owner, logDirectory, listenerInspector = new HostListenerInspector()) {
14
+ this.supervisor = supervisor;
15
+ this.owner = owner;
16
+ this.logDirectory = logDirectory;
17
+ this.listenerInspector = listenerInspector;
18
+ }
19
+ async launch(request, signal) {
20
+ const childName = projectChildName(request.port.name);
21
+ const stdoutPath = path.join(this.logDirectory, `${childName}.stdout.log`);
22
+ const stderrPath = path.join(this.logDirectory, `${childName}.stderr.log`);
23
+ const diagnostics = { exited: false, stdoutTail: "", stderrTail: "" };
24
+ let child;
25
+ try {
26
+ child = await this.supervisor.startChildReady(childName, {
27
+ command: request.command,
28
+ args: request.args,
29
+ cwd: request.cwd,
30
+ env: request.environment,
31
+ captureOutput: false,
32
+ }, {
33
+ onStdoutLine: async (line) => {
34
+ diagnostics.stdoutTail = appendTail(diagnostics.stdoutTail, line);
35
+ await appendLine(stdoutPath, line);
36
+ },
37
+ onStderrLine: async (line) => {
38
+ diagnostics.stderrTail = appendTail(diagnostics.stderrTail, line);
39
+ await appendLine(stderrPath, line);
40
+ },
41
+ }, signal, { maxStartAttempts: 3, retryDelayMs: 250 });
42
+ }
43
+ catch (error) {
44
+ throw enrichLaunchFailure(error, diagnostics);
45
+ }
46
+ this.diagnosticsByChild.set(child, diagnostics);
47
+ void child.result.then(result => {
48
+ diagnostics.exited = true;
49
+ diagnostics.exitCode = result.exitCode;
50
+ diagnostics.stdoutTail = appendTail(diagnostics.stdoutTail, result.stdout);
51
+ diagnostics.stderrTail = appendTail(diagnostics.stderrTail, result.stderr);
52
+ }, error => {
53
+ diagnostics.exited = true;
54
+ diagnostics.stderrTail = appendTail(diagnostics.stderrTail, error instanceof Error ? error.message : String(error));
55
+ });
56
+ return child;
57
+ }
58
+ async inspectListener(child, request) {
59
+ return await this.listenerInspector.inspect(child, request.port, this.owner);
60
+ }
61
+ async stop(child) {
62
+ await child.stop(this.owner.fencingToken);
63
+ }
64
+ async readinessDiagnostics(child) {
65
+ // Yield once so an already-settled result can update the diagnostic record.
66
+ await Promise.resolve();
67
+ const diagnostics = this.diagnosticsByChild.get(child);
68
+ return diagnostics ?? { exited: false };
69
+ }
70
+ }
71
+ function projectChildName(portName) {
72
+ const candidate = `project-${portName}`;
73
+ if (/^[a-z][a-z0-9_-]{0,31}$/u.test(candidate))
74
+ return candidate;
75
+ const digest = createHash("sha256").update(portName).digest("hex").slice(0, 16);
76
+ return `project-${digest}`;
77
+ }
78
+ async function appendLine(target, line) {
79
+ await fs.appendFile(target, `${line}\n`, { encoding: "utf8", mode: 0o600 });
80
+ }
81
+ function appendTail(current, value) {
82
+ if (!value)
83
+ return current;
84
+ const combined = current ? `${current}\n${value}` : value;
85
+ return combined.slice(-diagnosticTailLength);
86
+ }
87
+ function enrichLaunchFailure(error, diagnostics) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ const evidence = [
90
+ diagnostics.stderrTail ? `stderr tail: ${boundedDiagnostic(diagnostics.stderrTail)}` : "",
91
+ diagnostics.stdoutTail ? `stdout tail: ${boundedDiagnostic(diagnostics.stdoutTail)}` : "",
92
+ ].filter(Boolean);
93
+ return evidence.length > 0
94
+ ? new Error(`${message} ${evidence.join("; ")}`, { cause: error })
95
+ : error instanceof Error
96
+ ? error
97
+ : new Error(message);
98
+ }
99
+ function boundedDiagnostic(value) {
100
+ return value.replaceAll(/[\r\n\t]+/gu, " ").trim().slice(-1_000);
101
+ }
@@ -0,0 +1,20 @@
1
+ import type { ProviderProcessHandlers, ProviderProcessRunRequest, ProviderProcessRunner, ProviderProcessRunResult } from "../session/provider-cli/provider-cli.process.js";
2
+ import { AttemptProcessSupervisor, type AttemptProcessSupervisorIdentity, type AttemptProcessSupervisorOptions } from "./attempt-process-supervisor.js";
3
+ /** Runner-local authority for active host attempt process coordinators. */
4
+ export declare class AttemptProcessSupervisorRegistry {
5
+ private readonly active;
6
+ register(identity: AttemptProcessSupervisorIdentity, runner: ProviderProcessRunner, options?: AttemptProcessSupervisorOptions): AttemptProcessSupervisor;
7
+ require(dispatchAttemptId: string, fencingToken: string): AttemptProcessSupervisor;
8
+ unregister(dispatchAttemptId: string, fencingToken: string): boolean;
9
+ has(dispatchAttemptId: string): boolean;
10
+ }
11
+ /**
12
+ * Routes managed-host provider commands through the attempt's named provider
13
+ * child while retaining the raw runner for Docker and unmanaged execution.
14
+ */
15
+ export declare class AttemptCoordinatedProviderProcessRunner implements ProviderProcessRunner {
16
+ private readonly registry;
17
+ private readonly rawRunner;
18
+ constructor(registry: AttemptProcessSupervisorRegistry, rawRunner: ProviderProcessRunner);
19
+ run(request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal): Promise<ProviderProcessRunResult>;
20
+ }
@@ -0,0 +1,64 @@
1
+ import { AttemptProcessSupervisor, } from "./attempt-process-supervisor.js";
2
+ /** Runner-local authority for active host attempt process coordinators. */
3
+ export class AttemptProcessSupervisorRegistry {
4
+ active = new Map();
5
+ register(identity, runner, options = {}) {
6
+ const existing = this.active.get(identity.dispatchAttemptId);
7
+ if (existing) {
8
+ if (existing.fencingToken !== identity.fencingToken) {
9
+ throw new Error("attempt_process_registry_conflict: dispatch attempt is registered under a different fence.");
10
+ }
11
+ return existing.supervisor;
12
+ }
13
+ const supervisor = new AttemptProcessSupervisor(identity, runner, options);
14
+ this.active.set(identity.dispatchAttemptId, { fencingToken: identity.fencingToken, supervisor });
15
+ return supervisor;
16
+ }
17
+ require(dispatchAttemptId, fencingToken) {
18
+ const registered = this.active.get(dispatchAttemptId);
19
+ if (!registered) {
20
+ throw new Error("attempt_process_registry_missing: no process coordinator owns this dispatch attempt.");
21
+ }
22
+ if (registered.fencingToken !== fencingToken) {
23
+ throw new Error("attempt_process_registry_fenced: process coordinator fence does not match the dispatch.");
24
+ }
25
+ return registered.supervisor;
26
+ }
27
+ unregister(dispatchAttemptId, fencingToken) {
28
+ const registered = this.active.get(dispatchAttemptId);
29
+ if (!registered || registered.fencingToken !== fencingToken)
30
+ return false;
31
+ return this.active.delete(dispatchAttemptId);
32
+ }
33
+ has(dispatchAttemptId) {
34
+ return this.active.has(dispatchAttemptId);
35
+ }
36
+ }
37
+ /**
38
+ * Routes managed-host provider commands through the attempt's named provider
39
+ * child while retaining the raw runner for Docker and unmanaged execution.
40
+ */
41
+ export class AttemptCoordinatedProviderProcessRunner {
42
+ registry;
43
+ rawRunner;
44
+ constructor(registry, rawRunner) {
45
+ this.registry = registry;
46
+ this.rawRunner = rawRunner;
47
+ }
48
+ async run(request, handlers, signal) {
49
+ if (request.env?.FORGE_RUNTIME_MODE !== "host") {
50
+ return await this.rawRunner.run(request, handlers, signal);
51
+ }
52
+ const dispatchAttemptId = requiredEnvironment(request, "FORGE_DISPATCH_ATTEMPT_ID");
53
+ const fencingToken = requiredEnvironment(request, "FORGE_FENCING_TOKEN");
54
+ const supervisor = this.registry.require(dispatchAttemptId, fencingToken);
55
+ const child = await supervisor.startChildReady("provider", request, handlers, signal, { maxStartAttempts: request.stdin === undefined ? 1 : 3, retryDelayMs: 100 });
56
+ return await child.result;
57
+ }
58
+ }
59
+ function requiredEnvironment(request, name) {
60
+ const value = request.env?.[name]?.trim();
61
+ if (!value)
62
+ throw new Error(`attempt_process_registry_invalid: ${name} is required for managed-host execution.`);
63
+ return value;
64
+ }
@@ -0,0 +1,86 @@
1
+ import type { ProviderProcessHandlers, ProviderProcessRunRequest, ProviderProcessRunner, ProviderProcessRunResult } from "../session/provider-cli/provider-cli.process.js";
2
+ export type AttemptProcessChildStatus = "starting" | "running" | "stopping" | "exited" | "failed";
3
+ export type AttemptProcessSupervisorStatus = "active" | "stopping" | "stopped" | "degraded";
4
+ export interface AttemptProcessSupervisorIdentity {
5
+ readonly processSupervisorId: string;
6
+ readonly dispatchAttemptId: string;
7
+ readonly executionLeaseId: string;
8
+ readonly fencingToken: string;
9
+ readonly logDirectory: string;
10
+ }
11
+ export interface AttemptProcessChildLedgerEntry {
12
+ readonly name: string;
13
+ readonly generation: number;
14
+ readonly childId: string;
15
+ readonly processSupervisorId: string;
16
+ readonly metadataPath: string;
17
+ readonly status: AttemptProcessChildStatus;
18
+ readonly startedAt: string;
19
+ readonly settledAt?: string;
20
+ readonly exitCode?: number;
21
+ readonly failure?: string;
22
+ }
23
+ export interface AttemptProcessSupervisorLedger {
24
+ readonly schemaVersion: 1;
25
+ readonly attemptSupervisorId: string;
26
+ readonly dispatchAttemptId: string;
27
+ readonly executionLeaseId: string;
28
+ readonly status: AttemptProcessSupervisorStatus;
29
+ readonly updatedAt: string;
30
+ readonly children: readonly AttemptProcessChildLedgerEntry[];
31
+ }
32
+ export interface AttemptProcessChildHandle {
33
+ readonly name: string;
34
+ readonly generation: number;
35
+ readonly childId: string;
36
+ readonly processSupervisorId: string;
37
+ readonly metadataPath: string;
38
+ readonly ready: Promise<void>;
39
+ readonly result: Promise<ProviderProcessRunResult>;
40
+ stop(fencingToken: string): Promise<ProviderProcessRunResult>;
41
+ }
42
+ export interface AttemptProcessSupervisorOptions {
43
+ readonly metadataReadyTimeoutMs?: number;
44
+ readonly signal?: AbortSignal;
45
+ }
46
+ export interface AttemptProcessChildReadyOptions {
47
+ readonly maxStartAttempts?: number;
48
+ readonly retryDelayMs?: number;
49
+ }
50
+ /**
51
+ * Owns the independently supervised process trees that belong to one host
52
+ * dispatch attempt. Each named child gets a distinct Windows Job Object or
53
+ * Unix process group through the existing process runner, while this
54
+ * coordinator supplies shared fencing, cancellation, and a durable ledger.
55
+ */
56
+ export declare class AttemptProcessSupervisor {
57
+ private readonly runner;
58
+ readonly ledgerPath: string;
59
+ private readonly identity;
60
+ private readonly metadataReadyTimeoutMs;
61
+ private readonly records;
62
+ private readonly currentByName;
63
+ private readonly attemptController;
64
+ private status;
65
+ private persistTail;
66
+ private persistenceFailure;
67
+ private stopOperation;
68
+ constructor(identity: AttemptProcessSupervisorIdentity, runner: ProviderProcessRunner, options?: AttemptProcessSupervisorOptions);
69
+ startChild(name: string, request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal): Promise<AttemptProcessChildHandle>;
70
+ /**
71
+ * Starts a child and proves that the OS process tree is supervised before
72
+ * returning it to the caller. A launch that settles before producing its
73
+ * provider-ready supervision record is safe to retry: the raw runner only
74
+ * persists that record after the actual provider child has started, before
75
+ * Forge announces the provider as active.
76
+ */
77
+ startChildReady(name: string, request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal, options?: AttemptProcessChildReadyOptions): Promise<AttemptProcessChildHandle>;
78
+ stopChild(name: string, generation: number, fencingToken: string, reason?: unknown): Promise<ProviderProcessRunResult>;
79
+ stopAll(fencingToken: string, reason?: unknown): Promise<void>;
80
+ private performStopAll;
81
+ snapshot(): AttemptProcessSupervisorLedger;
82
+ private observeChildReady;
83
+ private settleChild;
84
+ private assertFence;
85
+ private persist;
86
+ }