@loucompanion/forge-bridge 0.1.3-dev.d1ca67076aa9 → 0.1.3-dev.eeefdb0d19c1

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 (143) 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 +962 -22
  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.js +47 -1
  14. package/dist/cli/cli.service.d.ts +42 -2
  15. package/dist/cli/cli.service.js +351 -22
  16. package/dist/cli/local-config.d.ts +6 -0
  17. package/dist/cli/local-config.js +47 -3
  18. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  19. package/dist/runtime/attempt-node-runtime-launcher.js +101 -0
  20. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  21. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  22. package/dist/runtime/attempt-process-supervisor.d.ts +86 -0
  23. package/dist/runtime/attempt-process-supervisor.js +343 -0
  24. package/dist/runtime/generated-output-reset.d.ts +10 -0
  25. package/dist/runtime/generated-output-reset.js +103 -0
  26. package/dist/runtime/host-listener-inspector.d.ts +44 -0
  27. package/dist/runtime/host-listener-inspector.js +324 -0
  28. package/dist/runtime/host-port-broker.d.ts +76 -0
  29. package/dist/runtime/host-port-broker.js +333 -0
  30. package/dist/runtime/host-runtime-contract.d.ts +47 -0
  31. package/dist/runtime/host-runtime-contract.js +700 -0
  32. package/dist/runtime/host-runtime-lease.d.ts +133 -0
  33. package/dist/runtime/host-runtime-lease.js +1460 -0
  34. package/dist/runtime/host-runtime-promotion.d.ts +23 -0
  35. package/dist/runtime/host-runtime-promotion.js +159 -0
  36. package/dist/runtime/node-host-runtime-adapter.d.ts +51 -0
  37. package/dist/runtime/node-host-runtime-adapter.js +268 -0
  38. package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
  39. package/dist/runtime/playwright-host-runtime-adapter.js +220 -0
  40. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  41. package/dist/runtime/runtime-isolation-lease.js +158 -0
  42. package/dist/sandbox/command-runner.d.ts +4 -0
  43. package/dist/sandbox/command-runner.js +47 -0
  44. package/dist/sandbox/contract.d.ts +5 -0
  45. package/dist/sandbox/contract.js +276 -0
  46. package/dist/sandbox/event-budget.d.ts +11 -0
  47. package/dist/sandbox/event-budget.js +27 -0
  48. package/dist/sandbox/image-policy.d.ts +35 -0
  49. package/dist/sandbox/image-policy.js +247 -0
  50. package/dist/sandbox/index.d.ts +5 -0
  51. package/dist/sandbox/index.js +5 -0
  52. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  53. package/dist/sandbox/lease-watchdog.js +67 -0
  54. package/dist/sandbox/preflight.d.ts +54 -0
  55. package/dist/sandbox/preflight.js +335 -0
  56. package/dist/sandbox/process.d.ts +20 -0
  57. package/dist/sandbox/process.js +209 -0
  58. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  59. package/dist/sandbox/provider-auth/codex.js +212 -0
  60. package/dist/sandbox/repository-lock.d.ts +5 -0
  61. package/dist/sandbox/repository-lock.js +119 -0
  62. package/dist/sandbox/runtime.d.ts +297 -0
  63. package/dist/sandbox/runtime.js +2338 -0
  64. package/dist/sandbox/sandbox.types.d.ts +86 -0
  65. package/dist/sandbox/sandbox.types.js +1 -0
  66. package/dist/sandbox/smoke.d.ts +76 -0
  67. package/dist/sandbox/smoke.js +380 -0
  68. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  69. package/dist/sandbox/worker-supervisor.js +36 -0
  70. package/dist/sandbox/worker.d.ts +1 -0
  71. package/dist/sandbox/worker.js +225 -0
  72. package/dist/sandbox/workspace.service.d.ts +13 -0
  73. package/dist/sandbox/workspace.service.js +72 -0
  74. package/dist/session/managed-attempt-tools.d.ts +12 -0
  75. package/dist/session/managed-attempt-tools.js +350 -0
  76. package/dist/session/phase-completion.artifacts.js +32 -4
  77. package/dist/session/phase-completion.d.ts +4 -2
  78. package/dist/session/phase-completion.helpers.js +27 -3
  79. package/dist/session/phase-completion.js +12 -3
  80. package/dist/session/phase-completion.prompts.d.ts +3 -3
  81. package/dist/session/phase-completion.prompts.js +19 -5
  82. package/dist/session/phase-completion.runtime.d.ts +12 -1
  83. package/dist/session/phase-completion.runtime.js +127 -7
  84. package/dist/session/phase-completion.schemas.js +108 -3
  85. package/dist/session/phase-completion.validation.d.ts +4 -1
  86. package/dist/session/phase-completion.validation.js +106 -2
  87. package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
  88. package/dist/session/provider-cli/ci-repair-readiness.js +90 -0
  89. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +1 -1
  90. package/dist/session/provider-cli/provider-cli.artifacts.js +28 -4
  91. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  92. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
  93. package/dist/session/provider-cli/provider-cli.deterministic.js +66 -9
  94. package/dist/session/provider-cli/provider-cli.process.d.ts +17 -0
  95. package/dist/session/provider-cli/provider-cli.process.js +223 -14
  96. package/dist/session/provider-cli/provider-cli.prompts.d.ts +16 -2
  97. package/dist/session/provider-cli/provider-cli.prompts.js +202 -18
  98. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
  99. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  100. package/dist/session/provider-cli/provider-cli.runtime.js +3 -2
  101. package/dist/session/provider-cli/provider-cli.service.d.ts +7 -3
  102. package/dist/session/provider-cli/provider-cli.service.js +399 -98
  103. package/dist/session/provider-cli/provider-cli.types.d.ts +6 -0
  104. package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
  105. package/dist/session/provider-cli/provider-output-state.js +30 -2
  106. package/dist/session/session-redaction.d.ts +3 -0
  107. package/dist/session/session-redaction.js +23 -0
  108. package/dist/session/session.base.d.ts +6 -1
  109. package/dist/session/session.service.d.ts +8 -2
  110. package/dist/session/session.service.js +463 -58
  111. package/dist/session/session.types.d.ts +107 -1
  112. package/dist/session/session.types.js +226 -3
  113. package/dist/shared/path/path.js +3 -1
  114. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  115. package/dist/shared/process/host-process-recovery.js +78 -0
  116. package/dist/shared/process/host-process-supervisor.d.ts +57 -0
  117. package/dist/shared/process/host-process-supervisor.js +282 -0
  118. package/dist/shared/process/process.d.ts +1 -0
  119. package/dist/shared/process/process.js +1 -0
  120. package/dist/shared/process/windows-launch.d.ts +1 -1
  121. package/dist/shared/process/windows-launch.js +6 -3
  122. package/dist/shared/provider-capabilities.d.ts +1 -0
  123. package/dist/shared/provider-capabilities.js +3 -0
  124. package/dist/shared/redaction/redaction.js +10 -3
  125. package/dist/version.d.ts +1 -1
  126. package/dist/version.js +1 -1
  127. package/dist/worktree/artifact-injector.d.ts +3 -2
  128. package/dist/worktree/artifact-injector.js +43 -4
  129. package/dist/worktree/dependency-warmer.d.ts +21 -1
  130. package/dist/worktree/dependency-warmer.js +138 -5
  131. package/dist/worktree/index.d.ts +1 -0
  132. package/dist/worktree/index.js +1 -0
  133. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  134. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  135. package/dist/worktree/worktree-paths.d.ts +4 -0
  136. package/dist/worktree/worktree-paths.js +30 -0
  137. package/dist/worktree/worktree.base.d.ts +3 -0
  138. package/dist/worktree/worktree.service.d.ts +7 -2
  139. package/dist/worktree/worktree.service.js +72 -44
  140. package/dist/worktree/worktree.types.d.ts +3 -1
  141. package/package.json +6 -3
  142. package/runtime/windows-job-supervisor.ps1 +356 -0
  143. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -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;
@@ -7,10 +7,14 @@ export interface RunOnceDispatchPacket {
7
7
  readonly session: DispatchSession;
8
8
  readonly lease: DispatchLease;
9
9
  readonly execution: DispatchExecution;
10
+ readonly runtimeIntent: DispatchRuntimeIntent;
10
11
  readonly serverLocal: DispatchServerLocalConfig;
12
+ readonly sandbox?: DispatchSandboxContext | null;
11
13
  readonly pullRequest?: DispatchPullRequestContext | null;
12
14
  readonly apiKeySecretGrant?: ApiKeySecretGrant | null;
15
+ readonly apiKeySecretGrantHandle?: ApiKeySecretGrantHandle | null;
13
16
  readonly artifacts: readonly DispatchResolvedArtifact[];
17
+ readonly priorPhaseArtifacts?: readonly DispatchPriorPhaseArtifact[];
14
18
  readonly warmPathOverrides?: readonly string[];
15
19
  readonly qaBrowser?: DispatchQaBrowserContext | null;
16
20
  readonly qaScope?: DispatchQaScope | null;
@@ -22,12 +26,63 @@ export interface RunOnceDispatchPacket {
22
26
  readonly designerRefs?: DispatchDesignerRefHandoffContext | null;
23
27
  readonly draft?: DispatchDraftPrompt | null;
24
28
  }
29
+ export interface DispatchRuntimeIntent {
30
+ readonly executionClass: "analysis_only" | "managed_runtime";
31
+ readonly applicationRuntimeRequired: boolean;
32
+ }
33
+ export interface DispatchSandboxContext {
34
+ readonly dispatchAttemptId: string;
35
+ readonly executionLeaseId: string;
36
+ readonly runnerInstanceId: string;
37
+ readonly executionMode: "host" | "docker";
38
+ readonly executionPolicy: "host_allowed" | "sandbox_preferred" | "sandbox_required";
39
+ readonly networkPolicy: DispatchSandboxNetworkPolicy;
40
+ readonly requestedResources: DispatchSandboxResourceRequest;
41
+ readonly runtime?: string | null;
42
+ readonly sandboxConfigCommitSha?: string | null;
43
+ readonly inputCommitSha?: string | null;
44
+ readonly contractDigest?: string | null;
45
+ readonly imageDigest?: string | null;
46
+ readonly publicationMode: "bridge_push" | "shared_filesystem";
47
+ readonly publishGrantHandle?: DispatchPublishGrantHandle | null;
48
+ }
49
+ export interface DispatchPublishGrantHandle {
50
+ readonly grantId: string;
51
+ readonly branch: string;
52
+ readonly expiresAt: string;
53
+ }
54
+ export interface DispatchSandboxResourceRequest {
55
+ readonly sandboxSlots: number;
56
+ readonly cpuMillis: number;
57
+ readonly workerMemoryLimitBytes: number;
58
+ readonly serviceMemoryLimitBytes: number;
59
+ readonly runtimeOverheadBytes: number;
60
+ readonly memoryBytes: number;
61
+ readonly workspaceBytes: number;
62
+ readonly pids: number;
63
+ readonly buildSlots: number;
64
+ }
65
+ export interface DispatchSandboxNetworkPolicy {
66
+ readonly mode: "offline" | "public_allowlist" | "open_public";
67
+ readonly allowedHosts: readonly string[];
68
+ }
25
69
  export interface DispatchDraftPrompt {
26
70
  readonly kind: string;
27
71
  readonly systemPrompt: string;
28
72
  readonly userPrompt: string;
29
73
  readonly projectName?: string | null;
30
74
  readonly repoName?: string | null;
75
+ readonly attachments?: readonly DispatchDraftAttachment[];
76
+ }
77
+ export interface DispatchDraftAttachment {
78
+ readonly index: number;
79
+ readonly fileName: string;
80
+ readonly contentType: string;
81
+ readonly kind: string;
82
+ readonly byteCount: number;
83
+ readonly summary: string;
84
+ readonly textContent?: string | null;
85
+ readonly contentBase64?: string | null;
31
86
  }
32
87
  export interface DispatchQaBrowserContext {
33
88
  readonly required: boolean;
@@ -55,6 +110,7 @@ export interface DispatchQaAppLaunchTarget {
55
110
  readonly command?: string | null;
56
111
  readonly working_directory?: string | null;
57
112
  readonly ready_url?: string | null;
113
+ readonly managed_runtime_port?: string | null;
58
114
  readonly routes: readonly string[];
59
115
  readonly notes?: string | null;
60
116
  }
@@ -143,6 +199,19 @@ export interface DispatchStoryQaDependencyContext {
143
199
  readonly fromWorkItemId: string;
144
200
  readonly toWorkItemId: string;
145
201
  }
202
+ export interface DispatchPriorPhaseArtifact {
203
+ readonly workItemId: string;
204
+ readonly artifactId: string;
205
+ readonly cycleRunId: string;
206
+ readonly phase: "implementation" | "self_review" | "validation";
207
+ readonly status: string;
208
+ readonly producedAt: string;
209
+ readonly sourcePath: string;
210
+ readonly verdictJson: string;
211
+ readonly report: string;
212
+ readonly verdictContentHash: string;
213
+ readonly reportContentHash: string;
214
+ }
146
215
  export interface DispatchDesignerRefHandoffContext {
147
216
  readonly schemaVersion: number;
148
217
  readonly kind: string;
@@ -268,6 +337,11 @@ export interface ApiKeySecretGrant {
268
337
  readonly secret: string;
269
338
  readonly expiresAt: string;
270
339
  }
340
+ export interface ApiKeySecretGrantHandle {
341
+ readonly grantId: string;
342
+ readonly provider: string;
343
+ readonly expiresAt: string;
344
+ }
271
345
  export interface DispatchServerLocalConfig {
272
346
  readonly repoRoot: string;
273
347
  readonly cleanupWorktrees: boolean;
@@ -317,4 +391,36 @@ export interface RunnerEventLine {
317
391
  readonly event: RunnerDispatchEvent;
318
392
  }
319
393
  export declare function parseRunOnceDispatchPacket(value: unknown): RunOnceDispatchPacket;
394
+ export interface HostRuntimeReadinessProbeRequest {
395
+ readonly requestId: string;
396
+ readonly fencingToken: string;
397
+ readonly userId: string;
398
+ readonly projectId: string;
399
+ readonly repoId: string;
400
+ readonly runnerId: string;
401
+ readonly runnerInstanceId: string;
402
+ readonly attempt: number;
403
+ readonly repoName: string;
404
+ readonly remoteUrl: string;
405
+ readonly defaultBranch: string;
406
+ readonly cloneOnDemand: boolean;
407
+ readonly runnerRepoLocalPath: string;
408
+ readonly revision: string;
409
+ readonly expiresAt: string;
410
+ readonly repoRoot: string;
411
+ readonly cleanupWorktrees: boolean;
412
+ }
413
+ export interface HostRuntimeReadinessProbeResult {
414
+ readonly status: "parallel_ready" | "serialized" | "failed";
415
+ readonly runtimeContractFingerprint?: string | null;
416
+ readonly runtimeContractSource?: string | null;
417
+ readonly reasonCode?: string | null;
418
+ readonly reasonMessage?: string | null;
419
+ readonly qaAppLaunchTarget?: HostRuntimeReadinessQaLaunchTarget | null;
420
+ readonly checkedAt: string;
421
+ }
422
+ export interface HostRuntimeReadinessQaLaunchTarget {
423
+ readonly managedRuntimePort: string;
424
+ readonly routes: readonly string[];
425
+ }
320
426
  export declare function failureResult(summary: string, events?: readonly RunnerDispatchEvent[]): RunOnceResultPacket;
@@ -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: 1,
9
+ schemaVersion,
10
10
  userId: requiredString(packet, "userId"),
11
11
  workItem: parseWorkItem(packet.workItem),
12
12
  repo: parseRepo(packet.repo),
@@ -14,10 +14,14 @@ export function parseRunOnceDispatchPacket(value) {
14
14
  session: parseSession(packet.session),
15
15
  lease: parseLease(packet.lease),
16
16
  execution: parseExecution(packet.execution),
17
+ runtimeIntent: parseRuntimeIntent(packet.runtimeIntent),
17
18
  serverLocal: parseServerLocal(packet.serverLocal),
19
+ sandbox: parseSandbox(packet.sandbox, schemaVersion),
18
20
  pullRequest: parsePullRequest(packet.pullRequest),
19
21
  apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
22
+ apiKeySecretGrantHandle: parseApiKeySecretGrantHandle(packet.apiKeySecretGrantHandle),
20
23
  artifacts: parseArtifacts(packet.artifacts),
24
+ priorPhaseArtifacts: parsePriorPhaseArtifacts(packet.priorPhaseArtifacts),
21
25
  warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
22
26
  qaBrowser: parseQaBrowser(packet.qaBrowser),
23
27
  qaScope: parseQaScope(packet.qaScope ?? packet.qa_scope),
@@ -30,6 +34,143 @@ export function parseRunOnceDispatchPacket(value) {
30
34
  draft: parseDraftPrompt(packet.draft)
31
35
  };
32
36
  }
37
+ function parseRuntimeIntent(value) {
38
+ if (value === undefined || value === null) {
39
+ return { executionClass: "managed_runtime", applicationRuntimeRequired: true };
40
+ }
41
+ const intent = asObject(value, "runtimeIntent");
42
+ const executionClass = requiredString(intent, "executionClass").trim().toLowerCase();
43
+ if (executionClass !== "analysis_only" && executionClass !== "managed_runtime") {
44
+ throw new Error("runtimeIntent.executionClass must be analysis_only or managed_runtime.");
45
+ }
46
+ const applicationRuntimeRequired = requiredBoolean(intent, "applicationRuntimeRequired");
47
+ if ((executionClass === "analysis_only") === applicationRuntimeRequired) {
48
+ throw new Error("runtimeIntent execution class and application runtime requirement are inconsistent.");
49
+ }
50
+ return { executionClass, applicationRuntimeRequired };
51
+ }
52
+ function parseSandbox(value, schemaVersion) {
53
+ if (value === undefined || value === null) {
54
+ if (schemaVersion === 2) {
55
+ throw new Error("sandbox is required for dispatch schemaVersion 2.");
56
+ }
57
+ return null;
58
+ }
59
+ const sandbox = asObject(value, "sandbox");
60
+ const executionMode = requiredString(sandbox, "executionMode");
61
+ if (executionMode !== "host" && executionMode !== "docker") {
62
+ throw new Error("sandbox.executionMode must be host or docker.");
63
+ }
64
+ const executionPolicy = requiredString(sandbox, "executionPolicy");
65
+ if (executionPolicy !== "host_allowed" && executionPolicy !== "sandbox_preferred" && executionPolicy !== "sandbox_required") {
66
+ throw new Error("sandbox.executionPolicy is invalid.");
67
+ }
68
+ return {
69
+ dispatchAttemptId: requiredString(sandbox, "dispatchAttemptId"),
70
+ executionLeaseId: requiredString(sandbox, "executionLeaseId"),
71
+ runnerInstanceId: requiredString(sandbox, "runnerInstanceId"),
72
+ executionMode,
73
+ executionPolicy,
74
+ networkPolicy: parseSandboxNetworkPolicy(sandbox.networkPolicy),
75
+ requestedResources: parseSandboxResourceRequest(sandbox.requestedResources),
76
+ runtime: optionalString(sandbox, "runtime"),
77
+ sandboxConfigCommitSha: optionalString(sandbox, "sandboxConfigCommitSha"),
78
+ inputCommitSha: optionalString(sandbox, "inputCommitSha"),
79
+ contractDigest: optionalString(sandbox, "contractDigest"),
80
+ imageDigest: optionalString(sandbox, "imageDigest"),
81
+ publicationMode: parsePublicationMode(sandbox.publicationMode),
82
+ publishGrantHandle: parsePublishGrantHandle(sandbox.publishGrantHandle)
83
+ };
84
+ }
85
+ function parsePublicationMode(value) {
86
+ if (value === undefined || value === null)
87
+ return "shared_filesystem";
88
+ if (value !== "bridge_push" && value !== "shared_filesystem")
89
+ throw new Error("sandbox.publicationMode is invalid.");
90
+ return value;
91
+ }
92
+ function parsePublishGrantHandle(value) {
93
+ if (value === undefined || value === null)
94
+ return null;
95
+ const handle = asObject(value, "sandbox.publishGrantHandle");
96
+ const grantId = requiredString(handle, "grantId");
97
+ const branch = requiredString(handle, "branch");
98
+ const expiresAt = requiredString(handle, "expiresAt");
99
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(grantId)) {
100
+ throw new Error("sandbox.publishGrantHandle.grantId is invalid.");
101
+ }
102
+ if (branch.length > 240)
103
+ throw new Error("sandbox.publishGrantHandle.branch is too long.");
104
+ if (!Number.isFinite(Date.parse(expiresAt)))
105
+ throw new Error("sandbox.publishGrantHandle.expiresAt is invalid.");
106
+ return { grantId, branch, expiresAt };
107
+ }
108
+ function parseSandboxResourceRequest(value) {
109
+ if (value === undefined || value === null) {
110
+ return {
111
+ sandboxSlots: 1,
112
+ cpuMillis: 2000,
113
+ workerMemoryLimitBytes: 2 * 1024 ** 3,
114
+ serviceMemoryLimitBytes: 0,
115
+ runtimeOverheadBytes: 128 * 1024 ** 2,
116
+ memoryBytes: 2 * 1024 ** 3 + 128 * 1024 ** 2,
117
+ workspaceBytes: 10 * 1024 ** 3,
118
+ pids: 512,
119
+ buildSlots: 1
120
+ };
121
+ }
122
+ const resources = asObject(value, "sandbox.requestedResources");
123
+ const memoryBytes = boundedInteger(resources, "memoryBytes", 128 * 1024 ** 2, 512 * 1024 ** 3);
124
+ const hasMemoryBreakdown = ["workerMemoryLimitBytes", "serviceMemoryLimitBytes", "runtimeOverheadBytes"]
125
+ .some(key => resources[key] !== undefined && resources[key] !== null);
126
+ const workerMemoryLimitBytes = hasMemoryBreakdown
127
+ ? boundedInteger(resources, "workerMemoryLimitBytes", 128 * 1024 ** 2, 512 * 1024 ** 3)
128
+ : memoryBytes;
129
+ const serviceMemoryLimitBytes = hasMemoryBreakdown
130
+ ? boundedInteger(resources, "serviceMemoryLimitBytes", 0, 512 * 1024 ** 3)
131
+ : 0;
132
+ const runtimeOverheadBytes = hasMemoryBreakdown
133
+ ? boundedInteger(resources, "runtimeOverheadBytes", 0, 64 * 1024 ** 3)
134
+ : 0;
135
+ if (workerMemoryLimitBytes + serviceMemoryLimitBytes + runtimeOverheadBytes !== memoryBytes) {
136
+ throw new Error("sandbox.requestedResources memory breakdown must equal memoryBytes.");
137
+ }
138
+ return {
139
+ sandboxSlots: boundedInteger(resources, "sandboxSlots", 1, 4),
140
+ cpuMillis: boundedInteger(resources, "cpuMillis", 100, 64_000),
141
+ workerMemoryLimitBytes,
142
+ serviceMemoryLimitBytes,
143
+ runtimeOverheadBytes,
144
+ memoryBytes,
145
+ workspaceBytes: boundedInteger(resources, "workspaceBytes", 1024 ** 3, 10 * 1024 ** 4),
146
+ pids: boundedInteger(resources, "pids", 32, 65_536),
147
+ buildSlots: boundedInteger(resources, "buildSlots", 0, 4)
148
+ };
149
+ }
150
+ function parseSandboxNetworkPolicy(value) {
151
+ if (value === undefined || value === null)
152
+ return { mode: "offline", allowedHosts: [] };
153
+ const policy = asObject(value, "sandbox.networkPolicy");
154
+ const mode = requiredString(policy, "mode");
155
+ if (mode !== "offline" && mode !== "public_allowlist" && mode !== "open_public") {
156
+ throw new Error("sandbox.networkPolicy.mode is invalid.");
157
+ }
158
+ const rawHosts = policy.allowedHosts ?? [];
159
+ if (!Array.isArray(rawHosts) || rawHosts.some((host) => typeof host !== "string")) {
160
+ throw new Error("sandbox.networkPolicy.allowedHosts must be a string array.");
161
+ }
162
+ const allowedHosts = [...new Set(rawHosts.map(host => host.trim().toLowerCase()).filter(Boolean))];
163
+ 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))) {
164
+ throw new Error("sandbox.networkPolicy.allowedHosts contains an invalid hostname.");
165
+ }
166
+ if (mode !== "public_allowlist" && allowedHosts.length > 0) {
167
+ throw new Error("sandbox.networkPolicy.allowedHosts requires public_allowlist mode.");
168
+ }
169
+ if (mode === "public_allowlist" && allowedHosts.length === 0) {
170
+ return { mode: "offline", allowedHosts: [] };
171
+ }
172
+ return { mode, allowedHosts };
173
+ }
33
174
  export function failureResult(summary, events = []) {
34
175
  return {
35
176
  schemaVersion: 1,
@@ -121,9 +262,45 @@ function parseDraftPrompt(value) {
121
262
  return {
122
263
  kind: requiredString(draft, "kind"),
123
264
  systemPrompt: requiredString(draft, "systemPrompt"),
124
- userPrompt: requiredString(draft, "userPrompt")
265
+ userPrompt: requiredString(draft, "userPrompt"),
266
+ projectName: optionalString(draft, "projectName"),
267
+ repoName: optionalString(draft, "repoName"),
268
+ attachments: parseDraftAttachments(draft.attachments)
125
269
  };
126
270
  }
271
+ function parseDraftAttachments(value) {
272
+ if (value === undefined || value === null) {
273
+ return [];
274
+ }
275
+ if (!Array.isArray(value)) {
276
+ throw new Error("draft.attachments must be an array when provided.");
277
+ }
278
+ return value.map((entry, index) => {
279
+ const attachment = asObject(entry, `draft.attachments[${index}]`);
280
+ const kind = requiredString(attachment, "kind").trim().toLowerCase();
281
+ if (kind !== "text" && kind !== "image") {
282
+ throw new Error(`draft.attachments[${index}].kind must be text or image.`);
283
+ }
284
+ const textContent = optionalString(attachment, "textContent");
285
+ const contentBase64 = optionalString(attachment, "contentBase64");
286
+ if (kind === "text" && textContent === null) {
287
+ throw new Error(`draft.attachments[${index}].textContent is required for text attachments.`);
288
+ }
289
+ if (kind === "image" && contentBase64 === null) {
290
+ throw new Error(`draft.attachments[${index}].contentBase64 is required for image attachments.`);
291
+ }
292
+ return {
293
+ index: optionalNonNegativeInteger(attachment, "index") ?? index,
294
+ fileName: requiredString(attachment, "fileName"),
295
+ contentType: optionalString(attachment, "contentType") ?? "",
296
+ kind,
297
+ byteCount: optionalNonNegativeInteger(attachment, "byteCount") ?? 0,
298
+ summary: optionalString(attachment, "summary") ?? "",
299
+ textContent,
300
+ contentBase64
301
+ };
302
+ });
303
+ }
127
304
  function parseApiKeySecretGrant(value) {
128
305
  if (value === undefined || value === null) {
129
306
  return null;
@@ -136,6 +313,16 @@ function parseApiKeySecretGrant(value) {
136
313
  expiresAt: requiredString(grant, "expiresAt")
137
314
  };
138
315
  }
316
+ function parseApiKeySecretGrantHandle(value) {
317
+ if (value === undefined || value === null)
318
+ return null;
319
+ const grant = asObject(value, "apiKeySecretGrantHandle");
320
+ return {
321
+ grantId: requiredString(grant, "grantId"),
322
+ provider: requiredString(grant, "provider"),
323
+ expiresAt: requiredString(grant, "expiresAt")
324
+ };
325
+ }
139
326
  function parseServerLocal(value) {
140
327
  const serverLocal = asObject(value, "serverLocal");
141
328
  return {
@@ -235,6 +422,7 @@ function parseQaAppLaunchTarget(value) {
235
422
  command: optionalString(launchTarget, "command"),
236
423
  working_directory: optionalString(launchTarget, "working_directory"),
237
424
  ready_url: optionalString(launchTarget, "ready_url"),
425
+ managed_runtime_port: optionalString(launchTarget, "managed_runtime_port"),
238
426
  routes: Array.isArray(routes)
239
427
  ? routes.map((entry, index) => {
240
428
  if (typeof entry !== "string") {
@@ -513,6 +701,34 @@ function parseArtifacts(value) {
513
701
  };
514
702
  });
515
703
  }
704
+ function parsePriorPhaseArtifacts(value) {
705
+ if (value === undefined || value === null) {
706
+ return [];
707
+ }
708
+ if (!Array.isArray(value)) {
709
+ throw new Error("priorPhaseArtifacts must be an array when provided.");
710
+ }
711
+ return value.map((entry, index) => {
712
+ const artifact = asObject(entry, `priorPhaseArtifacts[${index}]`);
713
+ const phase = requiredString(artifact, "phase").trim().toLowerCase().replace(/-/g, "_");
714
+ if (phase !== "implementation" && phase !== "self_review" && phase !== "validation") {
715
+ throw new Error(`priorPhaseArtifacts[${index}].phase must be implementation, self_review, or validation.`);
716
+ }
717
+ return {
718
+ workItemId: requiredString(artifact, "workItemId"),
719
+ artifactId: requiredString(artifact, "artifactId"),
720
+ cycleRunId: requiredString(artifact, "cycleRunId"),
721
+ phase,
722
+ status: requiredString(artifact, "status"),
723
+ producedAt: requiredString(artifact, "producedAt"),
724
+ sourcePath: requiredString(artifact, "sourcePath"),
725
+ verdictJson: requiredString(artifact, "verdictJson"),
726
+ report: optionalString(artifact, "report") ?? "",
727
+ verdictContentHash: requiredString(artifact, "verdictContentHash").toLowerCase(),
728
+ reportContentHash: requiredString(artifact, "reportContentHash").toLowerCase()
729
+ };
730
+ });
731
+ }
516
732
  function asObject(value, label) {
517
733
  if (!value || typeof value !== "object" || Array.isArray(value)) {
518
734
  throw new Error(`${label} must be an object.`);
@@ -580,3 +796,10 @@ function requiredNumber(source, key) {
580
796
  }
581
797
  return value;
582
798
  }
799
+ function boundedInteger(source, key, minimum, maximum) {
800
+ const value = requiredNumber(source, key);
801
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
802
+ throw new Error(`${key} must be an integer from ${minimum} through ${maximum}.`);
803
+ }
804
+ return value;
805
+ }
@@ -4,7 +4,9 @@ export function isSubPath(root, candidate) {
4
4
  const normalizedRoot = path.resolve(root);
5
5
  const normalizedCandidate = path.resolve(candidate);
6
6
  const relative = path.relative(normalizedRoot, normalizedCandidate);
7
- return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
7
+ return relative === "" || (relative !== ".."
8
+ && !relative.startsWith(`..${path.sep}`)
9
+ && !path.isAbsolute(relative));
8
10
  }
9
11
  export async function resolveExistingDirectoryPath(inputPath) {
10
12
  const stat = await fs.stat(inputPath);
@@ -0,0 +1,12 @@
1
+ import type { HostProcessSupervisorMetadata } from "./host-process-supervisor.js";
2
+ export type HostProcessRecoveryOutcome = "absent" | "terminated" | "pid_reused" | "unverifiable";
3
+ export interface HostProcessRecovery {
4
+ recover(metadata: HostProcessSupervisorMetadata): Promise<HostProcessRecoveryOutcome>;
5
+ }
6
+ /** Exact, birth-marker-fenced recovery for a process tree left by an old bridge. */
7
+ export declare class ExactHostProcessRecovery implements HostProcessRecovery {
8
+ private readonly platform;
9
+ private readonly graceMs;
10
+ constructor(platform?: NodeJS.Platform, graceMs?: number);
11
+ recover(metadata: HostProcessSupervisorMetadata): Promise<HostProcessRecoveryOutcome>;
12
+ }
@@ -0,0 +1,78 @@
1
+ import { captureHostProcessIdentity } from "./host-process-supervisor.js";
2
+ /** Exact, birth-marker-fenced recovery for a process tree left by an old bridge. */
3
+ export class ExactHostProcessRecovery {
4
+ platform;
5
+ graceMs;
6
+ constructor(platform = process.platform, graceMs = 2_000) {
7
+ this.platform = platform;
8
+ this.graceMs = graceMs;
9
+ }
10
+ async recover(metadata) {
11
+ if (!processExists(metadata.rootPid))
12
+ return "absent";
13
+ if (!metadata.rootIdentity)
14
+ return "unverifiable";
15
+ const currentIdentity = captureHostProcessIdentity(metadata.rootPid, this.platform);
16
+ if (!currentIdentity)
17
+ return processExists(metadata.rootPid) ? "unverifiable" : "absent";
18
+ if (currentIdentity !== metadata.rootIdentity)
19
+ return "pid_reused";
20
+ if (this.platform === "win32") {
21
+ signalExactProcess(metadata.rootPid, "SIGTERM");
22
+ }
23
+ else {
24
+ signalExactProcessGroup(metadata.rootPid, "SIGTERM");
25
+ }
26
+ await waitForExit(metadata.rootPid, this.graceMs);
27
+ if (processExists(metadata.rootPid)) {
28
+ if (captureHostProcessIdentity(metadata.rootPid, this.platform) !== metadata.rootIdentity) {
29
+ return "pid_reused";
30
+ }
31
+ if (this.platform === "win32") {
32
+ signalExactProcess(metadata.rootPid, "SIGKILL");
33
+ }
34
+ else {
35
+ signalExactProcessGroup(metadata.rootPid, "SIGKILL");
36
+ }
37
+ await waitForExit(metadata.rootPid, this.graceMs);
38
+ }
39
+ if (processExists(metadata.rootPid)
40
+ && captureHostProcessIdentity(metadata.rootPid, this.platform) === metadata.rootIdentity) {
41
+ throw new Error("host_process_recovery_degraded: exact supervised process tree did not stop.");
42
+ }
43
+ return "terminated";
44
+ }
45
+ }
46
+ function processExists(pid) {
47
+ try {
48
+ process.kill(pid, 0);
49
+ return true;
50
+ }
51
+ catch (error) {
52
+ return error.code === "EPERM";
53
+ }
54
+ }
55
+ function signalExactProcess(pid, signal) {
56
+ try {
57
+ process.kill(pid, signal);
58
+ }
59
+ catch (error) {
60
+ if (error.code !== "ESRCH")
61
+ throw error;
62
+ }
63
+ }
64
+ function signalExactProcessGroup(pid, signal) {
65
+ try {
66
+ process.kill(-pid, signal);
67
+ }
68
+ catch (error) {
69
+ if (error.code !== "ESRCH")
70
+ throw error;
71
+ }
72
+ }
73
+ async function waitForExit(pid, timeoutMs) {
74
+ const deadline = Date.now() + timeoutMs;
75
+ while (Date.now() < deadline && processExists(pid)) {
76
+ await new Promise(resolve => setTimeout(resolve, 25));
77
+ }
78
+ }
@@ -0,0 +1,57 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ export type HostProcessSupervisorStrategy = "windows_job_object" | "unix_process_group";
3
+ export interface HostProcessSupervisionRequest {
4
+ readonly command: string;
5
+ readonly args: readonly string[];
6
+ readonly cwd: string;
7
+ readonly env?: Readonly<Record<string, string>>;
8
+ }
9
+ export interface HostProcessSupervisedLaunch {
10
+ readonly command: string;
11
+ readonly args: readonly string[];
12
+ readonly detached: boolean;
13
+ readonly supervisorId: string;
14
+ readonly strategy: HostProcessSupervisorStrategy;
15
+ readonly metadataPath: string;
16
+ readonly payloadPath?: string;
17
+ readonly readyPath?: string;
18
+ readonly startGrantPath?: string;
19
+ readonly commandDigest: string;
20
+ }
21
+ export interface HostProcessProviderReady {
22
+ readonly schemaVersion: 1;
23
+ readonly supervisorId: string;
24
+ readonly rootPid?: number;
25
+ readonly rootIdentity?: string | null;
26
+ readonly providerPid: number;
27
+ readonly providerCreatedAt: string;
28
+ readonly providerIdentity?: string | null;
29
+ readonly startAttempt: number;
30
+ }
31
+ export interface HostProcessSupervisorMetadata {
32
+ readonly schemaVersion: 1;
33
+ readonly supervisorId: string;
34
+ readonly strategy: HostProcessSupervisorStrategy;
35
+ readonly rootPid: number;
36
+ readonly rootCreatedAt: string;
37
+ readonly rootIdentity?: string | null;
38
+ readonly commandDigest: string;
39
+ readonly worktreePath: string;
40
+ readonly runtimeLeaseId: string;
41
+ readonly dispatchAttemptId: string;
42
+ readonly fencingToken: string;
43
+ readonly bridgePid: number;
44
+ readonly providerPid?: number;
45
+ readonly providerCreatedAt?: string;
46
+ readonly providerIdentity?: string | null;
47
+ readonly providerStartAttempt?: number;
48
+ }
49
+ export declare function resolveHostProcessSupervisedLaunch(request: HostProcessSupervisionRequest, resolvedCommand: string, resolvedArgs: readonly string[], platform?: NodeJS.Platform): HostProcessSupervisedLaunch | null;
50
+ export declare function persistHostProcessSupervisorMetadata(launch: HostProcessSupervisedLaunch, request: HostProcessSupervisionRequest, rootPid: number, rootCreatedAt: string, providerReady?: HostProcessProviderReady): HostProcessSupervisorMetadata;
51
+ /**
52
+ * Captures an OS-issued process birth marker. Recovery compares this marker
53
+ * before signalling a persisted PID so PID reuse can never redirect cleanup
54
+ * at an unrelated process.
55
+ */
56
+ export declare function captureHostProcessIdentity(pid: number, platform?: NodeJS.Platform): string | null;
57
+ export declare function terminateHostProcessTree(child: ChildProcess, launch: HostProcessSupervisedLaunch | null, graceMs?: number): Promise<void>;